1
0
mirror of https://github.com/smallstep/cli.git synced 2026-10-24 11:19:19 +03:00
Files
step-ca-cli/flags/flags_test.go
Herman Slatman 1cec31e5b4 Fix some provisioner prompt issues related to SCEP and policies
This PR fixes the following issues:

 - SCEP provisioners not detected in admin token flows
 - Invalid provisioner selection logic when managing provisioner policies
 - Unexpected error messages showing "issuer" instead or "provisioner" flag
2025-03-27 19:50:30 +01:00

313 lines
11 KiB
Go

package flags
import (
"encoding/json"
"errors"
"flag"
"fmt"
"os"
"path/filepath"
"reflect"
"testing"
"github.com/smallstep/assert"
"github.com/stretchr/testify/require"
"github.com/urfave/cli"
"go.step.sm/crypto/fingerprint"
)
func TestParseCaURL(t *testing.T) {
// This is just to get a simple CLI context
app := &cli.App{}
set := flag.NewFlagSet("contrive", 0)
_ = set.String("ca-url", "", "")
ctx := cli.NewContext(app, set, nil)
type test struct {
name, caURL, ret string
err error
}
tests := []test{
{name: "fail/empty", caURL: "", ret: "", err: errors.New("' ' requires the '--ca-url' flag")},
{name: "fail/badCaURL", caURL: "git://git@github.com", ret: "", err: errors.New("invalid value 'git://git@github.com' for flag '--ca-url'; must have https scheme")},
{name: "ok", caURL: "https://ca.smallstep.com:8080", ret: "https://ca.smallstep.com:8080"},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ctx.Set("ca-url", tc.caURL)
ret, err := ParseCaURL(ctx)
if err != nil && assert.NotNil(t, tc.err, fmt.Sprintf("expected no error but got <%s>", err)) {
assert.HasPrefix(t, err.Error(), tc.err.Error())
} else if assert.Nil(t, tc.err, fmt.Sprintf("expected error <%s> but got nil", tc.err)) {
assert.Equals(t, ret, tc.ret)
}
})
}
}
func TestParseCaURLIfExists(t *testing.T) {
// This is just to get a simple CLI context
app := &cli.App{}
set := flag.NewFlagSet("contrive", 0)
_ = set.String("ca-url", "", "")
ctx := cli.NewContext(app, set, nil)
type test struct {
name, caURL, ret string
err error
}
tests := []test{
{name: "fail/badCaURL", caURL: "git://git@github.com", ret: "", err: errors.New("invalid value 'git://git@github.com' for flag '--ca-url'; must have https scheme")},
{name: "ok/empty", caURL: "", ret: ""},
{name: "ok", caURL: "https://ca.smallstep.com:8080", ret: "https://ca.smallstep.com:8080"},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ctx.Set("ca-url", tc.caURL)
ret, err := ParseCaURLIfExists(ctx)
if err != nil && assert.NotNil(t, tc.err, fmt.Sprintf("expected no error but got <%s>", err)) {
assert.HasPrefix(t, err.Error(), tc.err.Error())
} else if assert.Nil(t, tc.err, fmt.Sprintf("expected error <%s> but got nil", tc.err)) {
assert.Equals(t, ret, tc.ret)
}
})
}
}
func Test_parseCaURL(t *testing.T) {
// This is just to get a simple CLI context
app := &cli.App{}
set := flag.NewFlagSet("contrive", 0)
_ = set.String("ca-url", "", "")
ctx := cli.NewContext(app, set, nil)
type test struct {
name, caURL, ret string
err error
}
tests := []test{
{name: "fail/invalidURL", caURL: "#$%@#://&%^&%#$^#$", ret: "", err: errors.New("invalid value '#$%@#://&%^&%#$^#$' for flag '--ca-url'; invalid URL")},
{name: "fail/invalidScheme-git", caURL: "git://git@github.com", ret: "", err: errors.New("invalid value 'git://git@github.com' for flag '--ca-url'; must have https scheme")},
{name: "fail/invalidScheme-http", caURL: "http://ca.smallstep.com:8080", ret: "", err: errors.New("invalid value 'http://ca.smallstep.com:8080' for flag '--ca-url'; must have https scheme")},
{name: "ok", caURL: "https://ca.smallstep.com:8080", ret: "https://ca.smallstep.com:8080"},
{name: "ok/provide-scheme", caURL: "ca.smallstep.com:8080", ret: "https://ca.smallstep.com:8080"},
{name: "ok/ipv4", caURL: "https://127.0.0.1:8080", ret: "https://127.0.0.1:8080"},
{name: "ok/ipv4-no-port", caURL: "https://127.0.0.1", ret: "https://127.0.0.1"},
{name: "ok/ipv4-no-scheme", caURL: "127.0.0.1:8080", ret: "https://127.0.0.1:8080"},
{name: "ok/ipv4-no-port-no-scheme", caURL: "127.0.0.1", ret: "https://127.0.0.1"},
{name: "ok/ipv6-bracketed", caURL: "https://[::1]:8080", ret: "https://[::1]:8080"},
{name: "ok/ipv6-bracketed-no-port", caURL: "https://[::1]", ret: "https://[::1]"},
{name: "ok/ipv6-bracketed-no-scheme", caURL: "[::1]:8080", ret: "https://[::1]:8080"},
{name: "ok/ipv6-bracketed-no-port-no-scheme", caURL: "[::1]", ret: "https://[::1]"},
{name: "ok/ipv6-non-bracketed", caURL: "https://::1:8080", ret: "https://[::1]:8080"},
{name: "ok/ipv6-non-bracketed-no-port", caURL: "https://::1", ret: "https://[::1]"},
{name: "ok/ipv6-non-bracketed-no-scheme", caURL: "::1:8080", ret: "https://[::1]:8080"},
{name: "ok/ipv6-non-bracketed-no-port-no-scheme", caURL: "::1", ret: "https://[::1]"},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ret, err := parseCaURL(ctx, tc.caURL)
if err != nil && assert.NotNil(t, tc.err, fmt.Sprintf("expected no error but got <%s>", err)) {
assert.HasPrefix(t, err.Error(), tc.err.Error())
} else if assert.Nil(t, tc.err, fmt.Sprintf("expected error <%s> but got nil", tc.err)) {
assert.Equals(t, ret, tc.ret)
}
})
}
}
func TestParseTemplateData(t *testing.T) {
tempDir := t.TempDir()
write := func(t *testing.T, data []byte) string {
f, err := os.CreateTemp(tempDir, "parseTemplateData")
if err != nil {
t.Fatal(err)
}
_, err = f.Write(data)
if err1 := f.Close(); err1 != nil && err == nil {
t.Fatal(err1)
}
if err != nil {
t.Fatal(err)
}
return f.Name()
}
type args struct {
setData []string
setFileData []byte
}
tests := []struct {
name string
args args
want json.RawMessage
wantErr bool
}{
{"ok nil", args{nil, nil}, nil, false},
{"ok set", args{[]string{"foo=bar"}, nil}, []byte(`{"foo":"bar"}`), false},
{"ok set empty", args{[]string{"foo="}, nil}, []byte(`{"foo":""}`), false},
{"ok set int", args{[]string{"foo=123"}, nil}, []byte(`{"foo":123}`), false},
{"ok set int string", args{[]string{`foo="123"`}, nil}, []byte(`{"foo":"123"}`), false},
{"ok set object", args{[]string{`foo={"foo":"bar"}`}, nil}, []byte(`{"foo":{"foo":"bar"}}`), false},
{"ok set multiple", args{[]string{"foo=bar", "bar=123", "zar={}"}, nil}, []byte(`{"bar":123,"foo":"bar","zar":{}}`), false},
{"ok set overwrite", args{[]string{"foo=bar1", "foo=bar2"}, nil}, []byte(`{"foo":"bar2"}`), false},
{"ok set-file", args{nil, []byte(`{"foo":"bar","bar":123,"zar":{}}`)}, []byte(`{"bar":123,"foo":"bar","zar":{}}`), false},
{"ok set and set-file", args{[]string{"foo=bar-set", "bar=123"}, []byte(`{"foo":"bar-file","zar":{"foo":"bar"}}`)}, []byte(`{"bar":123,"foo":"bar-set","zar":{"foo":"bar"}}`), false},
{"fail set", args{[]string{"foo"}, nil}, nil, true},
{"fail set-file json", args{nil, []byte(`{"foo":"bar}`)}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
app := &cli.App{}
set := flag.NewFlagSet(t.Name(), 0)
if tt.args.setData != nil {
value := cli.StringSlice(tt.args.setData)
set.Var(&value, "set", "")
}
if tt.args.setFileData != nil {
fileName := write(t, tt.args.setFileData)
set.String("set-file", fileName, "")
}
got, err := ParseTemplateData(cli.NewContext(app, set, nil))
if (err != nil) != tt.wantErr {
t.Errorf("ParseTemplateData() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("ParseTemplateData() = %s, want %s", got, tt.want)
}
})
}
}
func TestParseTemplateData_missing(t *testing.T) {
tempDir := t.TempDir()
app := &cli.App{}
set := flag.NewFlagSet(t.Name(), 0)
set.String("set-file", filepath.Join(tempDir, "missing"), "")
_, err := ParseTemplateData(cli.NewContext(app, set, nil))
if err == nil {
t.Errorf("ParseTemplateData() error = %v, wantErr true", err)
}
}
func TestParseFingerprintFormat(t *testing.T) {
type args struct {
format string
}
tests := []struct {
name string
args args
want fingerprint.Encoding
wantErr bool
}{
{"hex", args{"hex"}, fingerprint.HexFingerprint, false},
{"base64", args{"base64"}, fingerprint.Base64Fingerprint, false},
{"base64url", args{"base64url"}, fingerprint.Base64URLFingerprint, false},
{"base64-url", args{"base64-url"}, fingerprint.Base64URLFingerprint, false},
{"base64urlraw", args{"base64urlraw"}, fingerprint.Base64RawURLFingerprint, false},
{"base64url-raw", args{"base64url-raw"}, fingerprint.Base64RawURLFingerprint, false},
{"base64-url-raw", args{"base64-url-raw"}, fingerprint.Base64RawURLFingerprint, false},
{"base64raw", args{"base64raw"}, fingerprint.Base64RawFingerprint, false},
{"base64-raw", args{"base64-raw"}, fingerprint.Base64RawFingerprint, false},
{"emoji", args{"emoji"}, fingerprint.EmojiFingerprint, false},
{"emojisum", args{"emojisum"}, fingerprint.EmojiFingerprint, false},
{"unknown", args{"unknown"}, 0, true},
{"empty", args{""}, 0, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseFingerprintFormat(tt.args.format)
if (err != nil) != tt.wantErr {
t.Errorf("ParseFingerprintFormat() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("ParseFingerprintFormat() = %v, want %v", got, tt.want)
}
})
}
}
func TestFirstStringOf(t *testing.T) {
getAppSet := func() (*cli.App, *flag.FlagSet) {
app := &cli.App{}
set := flag.NewFlagSet("contrive", 0)
return app, set
}
tests := []struct {
name string
getContext func() *cli.Context
inputs []string
want string
wantName string
}{
{
name: "no-flags-empty",
getContext: func() *cli.Context {
app, set := getAppSet()
//_ = set.String("ca-url", "", "")
return cli.NewContext(app, set, nil)
},
inputs: []string{"foo", "bar"},
want: "",
wantName: "foo",
},
{
name: "return-first-set-flag",
getContext: func() *cli.Context {
app, set := getAppSet()
_ = set.String("foo", "", "")
_ = set.String("bar", "", "")
_ = set.String("baz", "", "")
ctx := cli.NewContext(app, set, nil)
ctx.Set("bar", "test1")
ctx.Set("baz", "test2")
return ctx
},
inputs: []string{"foo", "bar", "baz"},
want: "test1",
wantName: "bar",
},
{
name: "return-first-default-flag",
getContext: func() *cli.Context {
app, set := getAppSet()
_ = set.String("foo", "", "")
_ = set.String("bar", "", "")
_ = set.String("baz", "test1", "")
ctx := cli.NewContext(app, set, nil)
return ctx
},
inputs: []string{"foo", "bar", "baz"},
want: "test1",
wantName: "baz",
},
{
name: "all-empty",
getContext: func() *cli.Context {
app, set := getAppSet()
_ = set.String("foo", "", "")
_ = set.String("bar", "", "")
_ = set.String("baz", "", "")
ctx := cli.NewContext(app, set, nil)
return ctx
},
inputs: []string{"foo", "bar", "baz"},
want: "",
wantName: "foo",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := tt.getContext()
val, name := FirstStringOf(ctx, tt.inputs...)
require.Equal(t, tt.want, val)
require.Equal(t, tt.wantName, name)
})
}
}