From d7de194a90ea25db844eb095e0af8b0d8d29b080 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Mon, 27 Jan 2025 11:41:32 -0600 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=85=20Improve=20test=20coverage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.go | 1 + app_test.go | 25 +++++++++++++++++++++++++ config/config_test.go | 38 ++++++++++++++++++++++++++++++++++++++ store/factory_test.go | 11 +++++++++++ 4 files changed, 75 insertions(+) diff --git a/app.go b/app.go index fbe7524..48caeaf 100644 --- a/app.go +++ b/app.go @@ -33,6 +33,7 @@ func NewApp(ver string, conf config.Config, db store.Store) (*App, error) { app := App{ version: ver, conf: conf, + storage: db, } selfIRI := boxap.DefaultServiceIRI(conf.BaseURL()) diff --git a/app_test.go b/app_test.go index 7819765..edcc536 100644 --- a/app_test.go +++ b/app_test.go @@ -78,6 +78,31 @@ func TestService(t *testing.T) { } } +func TestAppStorage(t *testing.T) { + th := setupAppTest(t) + base := "http://localhost:1234/" + th.conf.EXPECT().BaseURL().Return(base).MinTimes(1) + th.conf.EXPECT().Name().Return("").AnyTimes() + a, er := NewApp("0.0.0.0", th.conf, th.store) + assert.NoError(t, er) + if assert.NotNil(t, a) { + assert.Equal(t, th.store, a.Storage()) + } +} + +func TestAppUser(t *testing.T) { + // TODO: Actually fill this out at some point + th := setupAppTest(t) + base := "http://localhost:1234/" + th.conf.EXPECT().BaseURL().Return(base).MinTimes(1) + th.conf.EXPECT().Name().Return("").AnyTimes() + a, er := NewApp("0.0.0.0", th.conf, th.store) + assert.NoError(t, er) + if assert.NotNil(t, a) { + assert.Zero(t, a.User()) + } +} + func TestStrings(t *testing.T) { cases := [...]struct { given string diff --git a/config/config_test.go b/config/config_test.go index 28dfc9f..c146f7f 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -3,6 +3,7 @@ package config import ( "testing" + "github.com/BurntSushi/toml" "github.com/stretchr/testify/assert" ) @@ -24,3 +25,40 @@ func TestValidEnvReturnsCorrect(t *testing.T) { }) } } + +func TestConfigBaseURL(t *testing.T) { + c := config{baseURL: "https://me.goodevilgenius.org"} + assert.Equal(t, c.baseURL, c.BaseURL()) +} + +func TestConfigStoreName(t *testing.T) { + c := config{stores: stores{store: "cockroachdb"}} + assert.Equal(t, c.stores.store, c.StoreName()) +} + +func TestStoresMissingStore(t *testing.T) { + ss := stores{} + st, er := ss.GetStore("cockroachdb") + assert.Nil(t, st) + assert.ErrorIs(t, er, ErrMissingStore) +} + +var mockToml = ` +[cockroachdb] + +dsn = "cockroachdb://user:pass@127.0.0.1:26257/combluotion" +` + +type mockConn struct { + CockroachDB toml.Primitive +} + +func TestStoreMap(t *testing.T) { + var conn mockConn + md, _ := toml.Decode(mockToml, &conn) + st := store{"cockroachdb", conn.CockroachDB, md} + + m, er := st.Map() + assert.NoError(t, er) + assert.Equal(t, "cockroachdb://user:pass@127.0.0.1:26257/combluotion", m["dsn"]) +} diff --git a/store/factory_test.go b/store/factory_test.go index 6d3ed8f..aa97fdc 100644 --- a/store/factory_test.go +++ b/store/factory_test.go @@ -1,6 +1,7 @@ package store import ( + "errors" "testing" "github.com/stretchr/testify/assert" @@ -92,3 +93,13 @@ func TestMakeStoreNoName(t *testing.T) { assert.NotNil(t, s) assert.NoError(t, e) } + +func TestMakeStoreConfError(t *testing.T) { + th := setupFactoryTest(t) + mockError := errors.New("leave me alone") + th.conf.EXPECT().Store("mock").Return(nil, mockError) + s, e := MakeStore("mock", th.conf) + assert.Zero(t, s) + assert.ErrorIs(t, e, mockError) + +} From 917f919401d8d37c85ecb55f9eebcab085c75dd7 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Mon, 27 Jan 2025 15:23:46 -0600 Subject: [PATCH 2/3] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Use=20uint8=20for=20En?= =?UTF-8?q?v?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/combluotion/main.go | 1 + config/config_test.go | 2 +- config/env.go | 44 ++++++++++++++++++----- config/env_string.go | 27 ++++++++++++++ config/env_test.go | 80 +++++++++++++++++++++++++++++++++++------ config/load_test.go | 2 ++ 6 files changed, 135 insertions(+), 21 deletions(-) create mode 100644 config/env_string.go diff --git a/cmd/combluotion/main.go b/cmd/combluotion/main.go index 5557203..21b30d7 100644 --- a/cmd/combluotion/main.go +++ b/cmd/combluotion/main.go @@ -49,6 +49,7 @@ func getTomlFile() string { var confStr = ` base_url = "http://localhost:4523/" +env = "dev" [stores] store = "sqlite" diff --git a/config/config_test.go b/config/config_test.go index c146f7f..7c17fb2 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -13,7 +13,7 @@ func TestEnvDefaultsToDev(t *testing.T) { } func TestInvalidEnvReturnsDev(t *testing.T) { - c := config{env: Env("foobar")} + c := config{env: Env(255)} assert.Equal(t, Dev, c.Env()) } diff --git a/config/env.go b/config/env.go index b38e4fd..b8e2f43 100644 --- a/config/env.go +++ b/config/env.go @@ -1,12 +1,20 @@ package config -type Env string +import ( + "errors" + "fmt" + "strings" +) + +//go:generate stringer -type Env + +type Env uint8 const ( - Dev Env = "dev" - Prod Env = "prod" - Qa Env = "qa" - Test Env = "test" + Dev Env = iota + 1 + Prod + Qa + Test ) var Envs = [...]Env{ @@ -16,6 +24,28 @@ var Envs = [...]Env{ Test, } +func (e Env) MarshalText() ([]byte, error) { + return []byte(e.String()), nil +} + +var ErrEnvUnmarshal = errors.New("unable to unmarshal value") + +func (e *Env) UnmarshalText(text []byte) error { + if e == nil { + return fmt.Errorf("%w: nil pointer", ErrEnvUnmarshal) + } + + val := strings.ToUpper(string(text)) + + for _, ee := range Envs { + if val == strings.ToUpper(ee.String()) { + *e = ee + return nil + } + } + return fmt.Errorf("%w: invalid value: %s", ErrEnvUnmarshal, text) +} + func ValidEnvOrDev(e Env) Env { if ValidEnv(e) { return e @@ -31,7 +61,3 @@ func ValidEnv(env Env) bool { } return false } - -func (e Env) String() string { - return string(e) -} diff --git a/config/env_string.go b/config/env_string.go new file mode 100644 index 0000000..5fe0f83 --- /dev/null +++ b/config/env_string.go @@ -0,0 +1,27 @@ +// Code generated by "stringer -type Env"; DO NOT EDIT. + +package config + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[Dev-1] + _ = x[Prod-2] + _ = x[Qa-3] + _ = x[Test-4] +} + +const _Env_name = "DevProdQaTest" + +var _Env_index = [...]uint8{0, 3, 7, 9, 13} + +func (i Env) String() string { + i -= 1 + if i >= Env(len(_Env_index)-1) { + return "Env(" + strconv.FormatInt(int64(i+1), 10) + ")" + } + return _Env_name[_Env_index[i]:_Env_index[i+1]] +} diff --git a/config/env_test.go b/config/env_test.go index 680a79b..afb6507 100644 --- a/config/env_test.go +++ b/config/env_test.go @@ -1,21 +1,81 @@ package config import ( + "encoding" + "fmt" + "strings" "testing" "github.com/stretchr/testify/assert" ) +var _ encoding.TextMarshaler = Env(0) +var _ encoding.TextUnmarshaler = new(Env) +var _ fmt.Stringer = Env(0) + func TestEnvString(t *testing.T) { - cases := Envs[:] - cases = append(cases, Env("foobar"), Env(""), Env("42"), Env("✨")) - for _, e := range cases { - t.Run(string(e), func(t *testing.T) { - assert.Equal(t, string(e), e.String()) + testcases := []struct { + expected string + val Env + }{ + {"Dev", Dev}, + {"Prod", Prod}, + {"Qa", Qa}, + {"Test", Test}, + {"Env(0)", Env(0)}, + {"Env(255)", Env(255)}, + } + for _, testcase := range testcases { + t.Run(testcase.expected, func(sub *testing.T) { + assert.Equal(t, testcase.expected, testcase.val.String()) + by, er := testcase.val.MarshalText() + assert.NoError(t, er) + assert.Equal(t, []byte(testcase.expected), by) }) } } +type unmarshaltest struct { + name string + in []byte + exp Env + err string +} + +func TestEnvUnmarshalText(t *testing.T) { + testcases := []unmarshaltest{ + {"empty", []byte{}, Env(0), "invalid value: "}, + {"bad value", []byte("foobar"), Env(0), "invalid value: foobar"}, + } + for _, e := range Envs { + testcases = append(testcases, unmarshaltest{e.String() + "-lower", []byte(strings.ToLower(e.String())), e, ""}) + testcases = append(testcases, unmarshaltest{e.String() + "-upper", []byte(strings.ToUpper(e.String())), e, ""}) + testcases = append(testcases, unmarshaltest{e.String(), []byte(e.String()), e, ""}) + } + + for _, testcase := range testcases { + t.Run(testcase.name, func(sub *testing.T) { + var e Env + er := (&e).UnmarshalText(testcase.in) + if testcase.err == "" { + assert.NoError(t, er) + assert.Equal(t, testcase.exp, e) + } else { + assert.Empty(t, e) + assert.ErrorContains(t, er, testcase.err) + assert.ErrorIs(t, er, ErrEnvUnmarshal) + } + }) + } +} + +func TestEnvUnmarshalTextNil(t *testing.T) { + var e *Env + er := e.UnmarshalText([]byte("prod")) + assert.ErrorContains(t, er, "nil pointer") + assert.ErrorIs(t, er, ErrEnvUnmarshal) +} + func TestValidEnv(t *testing.T) { cases := [...]struct { e Env @@ -25,9 +85,8 @@ func TestValidEnv(t *testing.T) { {Prod, true}, {Qa, true}, {Test, true}, - {Env("foobar"), false}, - {Env(""), false}, - {Env("✨"), false}, + {Env(0), false}, + {Env(255), false}, } for _, c := range cases { @@ -46,9 +105,8 @@ func TestValidEnvOrDev(t *testing.T) { {Prod, Prod}, {Qa, Qa}, {Test, Test}, - {Env("foobar"), Dev}, - {Env(""), Dev}, - {Env("✨"), Dev}, + {Env(0), Dev}, + {Env(255), Dev}, } for _, c := range cases { diff --git a/config/load_test.go b/config/load_test.go index a7f4d1a..78f266c 100644 --- a/config/load_test.go +++ b/config/load_test.go @@ -23,6 +23,7 @@ func TestLoadTomlGood(t *testing.T) { defer os.Remove(tmp.Name()) defer tmp.Close() fmt.Fprintln(tmp, `name = "Cool"`) + fmt.Fprintln(tmp, `env = "prod"`) fmt.Fprintln(tmp, "[stores]") fmt.Fprintln(tmp, `store = "sqlite"`) fmt.Fprintln(tmp, "[stores.settings.sqlite]") @@ -31,6 +32,7 @@ func TestLoadTomlGood(t *testing.T) { c, e := LoadFromToml(tmp.Name()) assert.NoError(t, e) assert.Equal(t, "Cool", c.Name()) + assert.Equal(t, Prod, c.Env()) st, err := c.Store("") assert.NoError(t, err) From fe860fbc8290f1c35fa75c4fbbb34d33b5e05563 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Mon, 27 Jan 2025 17:18:09 -0600 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=9B=A0=20Add=20vulnerability=20checkR?= =?UTF-8?q?ename=20analyze=20check=20to=20lint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Taskfile.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Taskfile.yml b/Taskfile.yml index 933454b..95f6937 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -43,12 +43,20 @@ tasks: cmds: - staticcheck ./... - analyze: + vuln: + desc: Check for vulnerabilities + sources: + - '**/*.go' + cmds: + - govulncheck ./... + + lint: desc: Do static analysis deps: - vet - critic - staticcheck + - vuln test: desc: Run unit tests