cli/cliui/output_test.go
Rama main · réplica de lectura del repositorio original.
package cliui_test
import (
"bytes"
"context"
"encoding/json"
"fmt"
"strings"
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/serpent"
)
type format struct {
id string
attachOptionsFn func(opts *serpent.OptionSet)
formatFn func(ctx context.Context, data any) (string, error)
}
var _ cliui.OutputFormat = &format{}
func (f *format) ID() string {
return f.id
}
func (f *format) AttachOptions(opts *serpent.OptionSet) {
if f.attachOptionsFn != nil {
f.attachOptionsFn(opts)
}
}
func (f *format) Format(ctx context.Context, data any) (string, error) {
if f.formatFn != nil {
return f.formatFn(ctx, data)
}
return "", nil
}
func Test_OutputFormatter(t *testing.T) {
t.Parallel()
t.Run("RequiresTwoFormatters", func(t *testing.T) {
t.Parallel()
require.Panics(t, func() {
cliui.NewOutputFormatter()
})
require.Panics(t, func() {
cliui.NewOutputFormatter(cliui.JSONFormat())
})
require.NotPanics(t, func() {
cliui.NewOutputFormatter(cliui.JSONFormat(), cliui.TextFormat())
})
})
t.Run("NoMissingFormatID", func(t *testing.T) {
t.Parallel()
require.Panics(t, func() {
cliui.NewOutputFormatter(
cliui.JSONFormat(),
&format{id: ""},
)
})
})
t.Run("NoDuplicateFormats", func(t *testing.T) {
t.Parallel()
require.Panics(t, func() {
cliui.NewOutputFormatter(
cliui.JSONFormat(),
cliui.JSONFormat(),
)
})
})
t.Run("OK", func(t *testing.T) {
t.Parallel()
var called atomic.Int64
f := cliui.NewOutputFormatter(
cliui.JSONFormat(),
&format{
id: "foo",
attachOptionsFn: func(opts *serpent.OptionSet) {
opts.Add(serpent.Option{
Name: "foo",
Flag: "foo",
FlagShorthand: "f",
Value: serpent.DiscardValue,
Description: "foo flag 1234",
})
},
formatFn: func(_ context.Context, _ any) (string, error) {
called.Add(1)
return "foo", nil
},
},
)
cmd := &serpent.Command{}
f.AttachOptions(&cmd.Options)
fs := cmd.Options.FlagSet()
selected := cmd.Options.ByFlag("output")
require.NotNil(t, selected)
require.Equal(t, "json", selected.Value.String())
usage := fs.FlagUsages()
require.Contains(t, usage, "Output format.")
require.Contains(t, usage, "foo flag 1234")
ctx := context.Background()
data := []string{"hi", "dean", "was", "here"}
out, err := f.Format(ctx, data)
require.NoError(t, err)
var got []string
require.NoError(t, json.Unmarshal([]byte(out), &got))
require.Equal(t, data, got)
require.EqualValues(t, 0, called.Load())
require.NoError(t, fs.Set("output", "foo"))
out, err = f.Format(ctx, data)
require.NoError(t, err)
require.Equal(t, "foo", out)
require.EqualValues(t, 1, called.Load())
require.Error(t, fs.Set("output", "bar"))
out, err = f.Format(ctx, data)
require.NoError(t, err)
require.Equal(t, "foo", out)
require.EqualValues(t, 2, called.Load())
})
t.Run("WriteLine", func(t *testing.T) {
t.Parallel()
f := cliui.NewOutputFormatter(cliui.TextFormat(), cliui.JSONFormat())
cmd := &serpent.Command{}
f.AttachOptions(&cmd.Options)
var buf bytes.Buffer
require.NoError(t, f.WriteLine(context.Background(), &buf, "hello"))
require.Equal(t, "hello\n", buf.String())
require.NoError(t, cmd.Options.FlagSet().Set("output", "json"))
buf.Reset()
require.NoError(t, f.WriteLine(context.Background(), &buf, "hello"))
require.Equal(t, "\"hello\"\n", buf.String())
require.NoError(t, cmd.Options.FlagSet().Set("output", "text"))
buf.Reset()
require.NoError(t, f.WriteLine(context.Background(), &buf, ""))
require.Equal(t, "\n", buf.String())
})
t.Run("WriteLineFormatError", func(t *testing.T) {
t.Parallel()
f := cliui.NewOutputFormatter(
cliui.TextFormatFunc(func(int) (string, error) { return "unreachable", nil }),
cliui.JSONFormat(),
)
cmd := &serpent.Command{}
f.AttachOptions(&cmd.Options)
var buf bytes.Buffer
require.Error(t, f.WriteLine(context.Background(), &buf, "not an int"))
require.Empty(t, buf.String())
})
}
func Test_TextFormatFunc(t *testing.T) {
t.Parallel()
type item struct {
Name string `json:"name"`
}
f := cliui.TextFormatFunc(func(i item) (string, error) {
return strings.ToUpper(i.Name), nil
})
require.Equal(t, "text", f.ID())
t.Run("OK", func(t *testing.T) {
t.Parallel()
out, err := f.Format(context.Background(), item{Name: "coder"})
require.NoError(t, err)
require.Equal(t, "CODER", out)
})
t.Run("WrongType", func(t *testing.T) {
t.Parallel()
_, err := f.Format(context.Background(), "coder")
require.ErrorContains(t, err, "expected cliui_test.item, got string")
})
t.Run("WrongTypeInterface", func(t *testing.T) {
t.Parallel()
stringer := cliui.TextFormatFunc(func(s fmt.Stringer) (string, error) {
return s.String(), nil
})
_, err := stringer.Format(context.Background(), 42)
require.ErrorContains(t, err, "expected fmt.Stringer, got int")
})
t.Run("FuncError", func(t *testing.T) {
t.Parallel()
failing := cliui.TextFormatFunc(func(item) (string, error) {
return "", assert.AnError
})
_, err := failing.Format(context.Background(), item{})
require.ErrorIs(t, err, assert.AnError)
})
}