package pluginapi import ( "encoding/json" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) var testSchema = []Field{ {Key: "name"}, {Key: "mode"}, {Key: "flag"}, {Key: "n"}, {Key: "f"}, {Key: "opt"}, {Key: "status"}, {Key: "list"}, {Key: "lines"}, {Key: "roles"}, } func parse(t *testing.T, raw string) *Config { t.Helper() c, err := ParseConfig("demo", testSchema, json.RawMessage(raw)) require.NoError(t, err, "ParseConfig(%s): %v", raw, err) return c } func TestParseConfigRejectsUnknownAndInvalid(t *testing.T) { for raw, want := range map[string]string{ `{"bogus": 1}`: `demo: invalid config: unknown field "bogus"`, `{"name": `: "demo: invalid config:", `[1]`: "demo: invalid config:", } { _, err := ParseConfig("demo", testSchema, json.RawMessage(raw)) require.Error(t, err) assert.Contains(t, err.Error(), want) } for _, raw := range []string{``, ` `, `null`, `{}`} { c, err := ParseConfig("demo", testSchema, json.RawMessage(raw)) require.NoError(t, err) assert.Equal(t, "d", c.String("name", "d"), "%q: %v", raw, err) } } func TestConfigReaders(t *testing.T) { c := parse(t, `{"name": "x", "mode": "b", "flag": "yes", "n": "42", "f": 0.5, "opt": "0.25", "status": 451, "list": "a, b\n c,,", "lines": "one\n\n# two", "roles": "system, USER"}`) assert.Equal(t, "x", c.String("name", "")) assert.Equal(t, "d", c.String("missing", "d")) assert.Equal(t, "b", c.Choice("mode", "a", "a", "b")) assert.Equal(t, "a", c.Choice("missing", "a", "a", "b")) assert.True(t, c.Bool("flag")) assert.False(t, c.Bool("missing")) for raw, want := range map[string]bool{`true`: true, `false`: false, `"on"`: true, `"off"`: false, `"1"`: true, `"0"`: false, `"YES"`: true, `"no"`: false, `""`: false} { got := parse(t, `{"flag": `+raw+`}`).Bool("flag") assert.Equal(t, want, got, "Bool(%s) = %v", raw, got) } assert.Equal(t, 42, c.Int("n", 0, 0, 100)) assert.Equal(t, 7, c.Int("missing", 7, 0, 100)) assert.Equal(t, 0.5, c.Float("f", 0, 0, 1)) assert.Equal(t, float64(2), c.Float("missing", 2, 0, 3)) v := c.OptionalFloat("opt", 0, 1) require.NotNil(t, v) assert.Equal(t, 0.25, *v) assert.Nil(t, c.OptionalFloat("missing", 0, 1)) assert.Equal(t, 451, c.BlockStatus("status")) assert.Equal(t, 0, c.BlockStatus("missing")) got := c.List("list") assert.Equal(t, []string{"a", "b", "c"}, got) assert.Nil(t, c.List("missing")) got = c.Lines("lines") assert.Equal(t, []string{"one", "", "# two"}, got) assert.Equal(t, map[Role]bool{RoleSystem: true, RoleDeveloper: true, RoleUser: true}, c.Roles("roles"), "Roles") assert.Equal(t, map[Role]bool{RoleUser: true}, c.Roles("missing", RoleUser), "Roles default") assert.NotNil(t, c.Raw("name")) assert.Nil(t, c.Raw("missing")) err := c.Err() require.NoError(t, err) c = parse(t, `{"list": [" a ", "", "b"], "lines": ["x", "y"], "roles": [], "flag": false, "n": ""}`) got = c.List("list") assert.Equal(t, []string{"a", "b"}, got) got = c.Lines("lines") assert.Equal(t, []string{"x", "y"}, got) assert.Empty(t, c.Roles("roles", RoleUser), "Roles empty") assert.False(t, c.Bool("flag")) assert.Equal(t, 5, c.Int("n", 5, 0, 9)) } func TestConfigErrors(t *testing.T) { tests := []struct { raw string read func(c *Config) want string }{ {`{"name": 5}`, func(c *Config) { c.String("name", "") }, "demo: name must be a string"}, {`{"mode": "z"}`, func(c *Config) { c.Choice("mode", "a", "a", "b") }, `demo: mode must be one of a, b; got "z"`}, {`{"flag": "maybe"}`, func(c *Config) { c.Bool("flag") }, "demo: flag must be true or false"}, {`{"n": "abc"}`, func(c *Config) { c.Int("n", 0, 0, 9) }, `demo: n must be a number, got "abc"`}, {`{"n": true}`, func(c *Config) { c.Int("n", 0, 0, 9) }, "demo: n must be a number, got true"}, {`{"n": 1.5}`, func(c *Config) { c.Int("n", 0, 0, 9) }, "demo: n must be a whole number, got 1.5"}, {`{"n": 10}`, func(c *Config) { c.Int("n", 0, 0, 9) }, "demo: n must be between 0 and 9, got 10"}, {`{"f": 3}`, func(c *Config) { c.Float("f", 0, 0, 2) }, "demo: f must be between 0 and 2, got 3"}, {`{"f": "NaN"}`, func(c *Config) { c.Float("f", 0, 0, 2) }, `demo: f must be a number, got "NaN"`}, {`{"f": "Inf"}`, func(c *Config) { c.Float("f", 0, 0, 2) }, `demo: f must be a number, got "Inf"`}, {`{"opt": "nan"}`, func(c *Config) { c.OptionalFloat("opt", 0, 1) }, `demo: opt must be a number, got "nan"`}, {`{"opt": -1}`, func(c *Config) { c.OptionalFloat("opt", 0, 1) }, "demo: opt must be between 0 and 1, got -1"}, {`{"status": 302}`, func(c *Config) { c.BlockStatus("status") }, "demo: status must be an HTTP status between 400 and 599, got 302"}, {`{"status": 600}`, func(c *Config) { c.BlockStatus("status") }, "demo: status must be an HTTP status between 400 and 599, got 600"}, {`{"list": 5}`, func(c *Config) { c.List("list") }, "demo: list must be a list of strings"}, {`{"lines": 5}`, func(c *Config) { c.Lines("lines") }, "demo: lines must be text or a list of strings"}, {`{"roles": ["robot"]}`, func(c *Config) { c.Roles("roles") }, `demo: roles has unknown role "robot"`}, } for _, tt := range tests { c := parse(t, tt.raw) tt.read(c) err := c.Err() require.Error(t, err) assert.Contains(t, err.Error(), tt.want) } // The first problem wins and later reads keep their defaults. c := parse(t, `{"name": 5, "n": "x"}`) assert.Equal(t, "d", c.String("name", "d")) assert.Equal(t, 3, c.Int("n", 3, 0, 9)) err := c.Err() require.Error(t, err) assert.Contains(t, err.Error(), "name must be a string") } func TestEnforcement(t *testing.T) { e := Enforcement{Action: ActionBlock, Message: "no", BlockStatus: 451} d := e.Enforce("c", 1) assert.Equal(t, ActionBlock, d.Action) assert.Equal(t, 451, d.Status) assert.Equal(t, "c", d.Code) assert.Equal(t, "no", d.Message) assert.Equal(t, 1, d.Detail, "block = %+v", d) e.Action = ActionRespond d = e.Enforce("c", nil) assert.Equal(t, ActionRespond, d.Action) assert.Equal(t, "c", d.Code) assert.Equal(t, "no", d.Response.Text(0), "respond = %+v", d) e.RespondText = "sorry" d = e.Reject("c", nil) assert.Equal(t, "sorry", d.Response.Text(0), "respond text = %+v", d) e.Action = ActionWarn d = e.Enforce("c", 2) assert.Equal(t, ActionWarn, d.Action) assert.Equal(t, "c", d.Code) assert.Equal(t, "no", d.Message) assert.Equal(t, 2, d.Detail, "warn = %+v", d) d = e.Reject("c", nil) assert.Equal(t, ActionBlock, d.Action) assert.Equal(t, 451, d.Status, "reject under warn = %+v", d) f := BlockStatusField() assert.Equal(t, "block_status", f.Key) assert.Equal(t, InputNumber, f.Input) assert.NotEmpty(t, f.Help, "field = %+v", f) }