From d60eb03082aaf6d064e1a1caf53fa32b58fb0e6f Mon Sep 17 00:00:00 2001 From: "Greg T. Wallace" Date: Mon, 22 Jun 2026 19:07:09 -0400 Subject: [PATCH 01/16] remove ErrAnyType from tests new error checking is more strict (though still not ideal) --- pkg/storage/accounts_delete_test.go | 7 +- pkg/storage/accounts_get_test.go | 5 +- pkg/storage/accounts_post_test.go | 9 ++- pkg/storage/accounts_put_test.go | 13 ++-- pkg/storage/acme_servers_delete_test.go | 7 +- pkg/storage/acme_servers_get_test.go | 5 +- pkg/storage/acme_servers_post_test.go | 7 +- pkg/storage/acme_servers_put_test.go | 5 +- pkg/storage/keys_delete_test.go | 7 +- pkg/storage/keys_get_test.go | 5 +- pkg/storage/keys_post_test.go | 7 +- pkg/storage/keys_put_test.go | 17 +++-- pkg/storage/service_mock_test.go | 3 +- pkg/test_helpers/err_is.go | 49 ++++++++++--- pkg/test_helpers/err_is_test.go | 92 ++++++++++++++++++------- 15 files changed, 153 insertions(+), 85 deletions(-) diff --git a/pkg/storage/accounts_delete_test.go b/pkg/storage/accounts_delete_test.go index 4ea84f95..bb24985a 100644 --- a/pkg/storage/accounts_delete_test.go +++ b/pkg/storage/accounts_delete_test.go @@ -5,7 +5,6 @@ import ( "certwarden-backend/pkg/storage" "certwarden-backend/pkg/test_helpers" "database/sql" - "errors" "fmt" "testing" ) @@ -38,7 +37,7 @@ func TestAcmeAccountInUse(t *testing.T) { for _, tc := range testCases { t.Run(fmt.Sprintf("id: %d", tc.acctID), func(t *testing.T) { inUse, err := storage.AcmeAccountInUse(tc.acctID) - if !errors.Is(err, tc.expectedErr) { + if !test_helpers.ErrorsIs(err, tc.expectedErr) { t.Errorf("expected error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedErr), test_helpers.ErrorToVal(err)) } @@ -74,12 +73,12 @@ func TestDeleteAcmeAccount(t *testing.T) { for _, tc := range testCases { t.Run(fmt.Sprintf("id: %d", tc.acctID), func(t *testing.T) { err := storage.DeleteAcmeAccount(tc.acctID) - if !errors.Is(err, tc.expectedDelErr) { + if !test_helpers.ErrorsIs(err, tc.expectedDelErr) { t.Errorf("expected delete error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedDelErr), test_helpers.ErrorToVal(err)) } acct, err := storage.GetOneAcmeAccountById(tc.acctID) - if !errors.Is(err, tc.expectedGetErr) { + if !test_helpers.ErrorsIs(err, tc.expectedGetErr) { t.Errorf("expected get error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedGetErr), test_helpers.ErrorToVal(err)) } diff --git a/pkg/storage/accounts_get_test.go b/pkg/storage/accounts_get_test.go index f4c7b29d..b3b916f1 100644 --- a/pkg/storage/accounts_get_test.go +++ b/pkg/storage/accounts_get_test.go @@ -5,7 +5,6 @@ import ( "certwarden-backend/pkg/pagination_sort" "certwarden-backend/pkg/test_helpers" "database/sql" - "errors" "fmt" "testing" "time" @@ -150,7 +149,7 @@ func TestGetOneAccountById(t *testing.T) { for i, tc := range testCases { t.Run(fmt.Sprintf("#%d (id: %d)", i, tc.id), func(t *testing.T) { acct, err := storage.GetOneAcmeAccountById(tc.id) - if !errors.Is(err, tc.expectedErr) { + if !test_helpers.ErrorsIs(err, tc.expectedErr) { t.Errorf("expected error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedErr), test_helpers.ErrorToVal(err)) } @@ -181,7 +180,7 @@ func TestGetOneAccountByName(t *testing.T) { for i, tc := range testCases { t.Run(fmt.Sprintf("#%d (name: %s)", i, tc.name), func(t *testing.T) { acct, err := storage.GetOneAcmeAccountByName(tc.name) - if !errors.Is(err, tc.expectedErr) { + if !test_helpers.ErrorsIs(err, tc.expectedErr) { t.Errorf("expected error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedErr), test_helpers.ErrorToVal(err)) } diff --git a/pkg/storage/accounts_post_test.go b/pkg/storage/accounts_post_test.go index 40413628..65b64dd8 100644 --- a/pkg/storage/accounts_post_test.go +++ b/pkg/storage/accounts_post_test.go @@ -4,7 +4,6 @@ import ( "certwarden-backend/pkg/domain/acme_accounts" "certwarden-backend/pkg/test_helpers" "database/sql" - "errors" "fmt" "testing" "time" @@ -59,7 +58,7 @@ func TestPostNewAcmeAccount(t *testing.T) { UpdatedAt: 1888838000, Kid: "https://fake.example.com/123456", }, - test_helpers.ErrAnyType, + test_helpers.MakeTestErrorStringComp("UNIQUE constraint failed"), acme_accounts.Account{}, sql.ErrNoRows, }, @@ -76,7 +75,7 @@ func TestPostNewAcmeAccount(t *testing.T) { UpdatedAt: 1888838000, Kid: "https://fake.example.com/123456", }, - test_helpers.ErrAnyType, + test_helpers.MakeTestErrorStringComp("NOT NULL constraint failed"), acme_accounts.Account{}, sql.ErrNoRows, }, @@ -93,7 +92,7 @@ func TestPostNewAcmeAccount(t *testing.T) { UpdatedAt: 1888838000, Kid: "https://fake.example.com/123456", }, - test_helpers.ErrAnyType, + test_helpers.MakeTestErrorStringComp("NOT NULL constraint failed"), acme_accounts.Account{}, sql.ErrNoRows, }, @@ -115,7 +114,7 @@ func TestPostNewAcmeAccount(t *testing.T) { CompareAcmeAccount(t, acct, tc.expectedNew) acct, err = storage.GetOneAcmeAccountByName(acct.Name) - if !errors.Is(err, tc.expectedGetErr) { + if !test_helpers.ErrorsIs(err, tc.expectedGetErr) { t.Errorf("expected get error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedGetErr), test_helpers.ErrorToVal(err)) } diff --git a/pkg/storage/accounts_put_test.go b/pkg/storage/accounts_put_test.go index d5d12c4b..df3ef408 100644 --- a/pkg/storage/accounts_put_test.go +++ b/pkg/storage/accounts_put_test.go @@ -5,7 +5,6 @@ import ( "certwarden-backend/pkg/storage" "certwarden-backend/pkg/test_helpers" "database/sql" - "errors" "fmt" "testing" "time" @@ -155,7 +154,7 @@ func TestPutAcmeAccountUpdate(t *testing.T) { UpdatedAt: time.Unix(107800777, 0), }, acme_accounts.Account{}, - test_helpers.ErrAnyType, + test_helpers.MakeTestErrorStringComp("UNIQUE constraint failed"), 16, acmeAcct16, nil, @@ -254,7 +253,7 @@ func TestPutAcmeAccountUpdate(t *testing.T) { CompareAcmeAccount(t, acct, tc.expectedPutResult) acct, err = storage.GetOneAcmeAccountById(tc.getId) - if !errors.Is(err, tc.expectedGetErr) { + if !test_helpers.ErrorsIs(err, tc.expectedGetErr) { t.Errorf("expected get error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedGetErr), test_helpers.ErrorToVal(err)) } @@ -337,7 +336,7 @@ func TestPutAcmeAccountNewKey(t *testing.T) { UpdatedAt: time.Unix(1088000751, 0), }, acme_accounts.Account{}, - test_helpers.ErrAnyType, + test_helpers.MakeTestErrorStringComp("FOREIGN KEY constraint failed"), 1, acmeAcct1, nil, @@ -349,7 +348,7 @@ func TestPutAcmeAccountNewKey(t *testing.T) { UpdatedAt: time.Unix(1088000752, 0), }, acme_accounts.Account{}, - test_helpers.ErrAnyType, + test_helpers.MakeTestErrorStringComp("FOREIGN KEY constraint failed"), 1, acmeAcct1, nil, @@ -361,7 +360,7 @@ func TestPutAcmeAccountNewKey(t *testing.T) { UpdatedAt: time.Unix(1088000753, 0), }, acme_accounts.Account{}, - test_helpers.ErrAnyType, + test_helpers.MakeTestErrorStringComp("UNIQUE constraint failed"), 23, acmeAcct23, nil, @@ -384,7 +383,7 @@ func TestPutAcmeAccountNewKey(t *testing.T) { CompareAcmeAccount(t, acct, tc.expectedPutResult) acct, err = storage.GetOneAcmeAccountById(tc.getId) - if !errors.Is(err, tc.expectedGetErr) { + if !test_helpers.ErrorsIs(err, tc.expectedGetErr) { t.Errorf("expected get error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedGetErr), test_helpers.ErrorToVal(err)) } diff --git a/pkg/storage/acme_servers_delete_test.go b/pkg/storage/acme_servers_delete_test.go index 19870c79..f0b94ac0 100644 --- a/pkg/storage/acme_servers_delete_test.go +++ b/pkg/storage/acme_servers_delete_test.go @@ -5,7 +5,6 @@ import ( "certwarden-backend/pkg/storage" "certwarden-backend/pkg/test_helpers" "database/sql" - "errors" "fmt" "testing" ) @@ -34,7 +33,7 @@ func TestServerInUse(t *testing.T) { for _, tc := range testCases { t.Run(fmt.Sprintf("server id: %d", tc.serverID), func(t *testing.T) { inUse, err := storage.ServerInUse(tc.serverID) - if !errors.Is(err, tc.expectedErr) { + if !test_helpers.ErrorsIs(err, tc.expectedErr) { t.Errorf("expected error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedErr), test_helpers.ErrorToVal(err)) } @@ -70,12 +69,12 @@ func TestDeleteServer(t *testing.T) { for _, tc := range testCases { t.Run(fmt.Sprintf("server id: %d", tc.serverID), func(t *testing.T) { err := storage.DeleteServer(tc.serverID) - if !errors.Is(err, tc.expectedDelErr) { + if !test_helpers.ErrorsIs(err, tc.expectedDelErr) { t.Errorf("expected delete error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedDelErr), test_helpers.ErrorToVal(err)) } server, err := storage.GetOneServerById(tc.serverID) - if !errors.Is(err, tc.expectedGetErr) { + if !test_helpers.ErrorsIs(err, tc.expectedGetErr) { t.Errorf("expected get error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedGetErr), test_helpers.ErrorToVal(err)) } diff --git a/pkg/storage/acme_servers_get_test.go b/pkg/storage/acme_servers_get_test.go index 77b70f50..cfbd4063 100644 --- a/pkg/storage/acme_servers_get_test.go +++ b/pkg/storage/acme_servers_get_test.go @@ -5,7 +5,6 @@ import ( "certwarden-backend/pkg/pagination_sort" "certwarden-backend/pkg/test_helpers" "database/sql" - "errors" "fmt" "testing" "time" @@ -117,7 +116,7 @@ func TestGetOneServerById(t *testing.T) { for i, tc := range testCases { t.Run(fmt.Sprintf("#%d (id: %d)", i, tc.id), func(t *testing.T) { serv, err := storage.GetOneServerById(tc.id) - if !errors.Is(err, tc.expectedErr) { + if !test_helpers.ErrorsIs(err, tc.expectedErr) { t.Errorf("expected error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedErr), test_helpers.ErrorToVal(err)) } @@ -148,7 +147,7 @@ func TestGetOneServerByName(t *testing.T) { for i, tc := range testCases { t.Run(fmt.Sprintf("#%d (name: %s)", i, tc.name), func(t *testing.T) { serv, err := storage.GetOneServerByName(tc.name) - if !errors.Is(err, tc.expectedErr) { + if !test_helpers.ErrorsIs(err, tc.expectedErr) { t.Errorf("expected error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedErr), test_helpers.ErrorToVal(err)) } diff --git a/pkg/storage/acme_servers_post_test.go b/pkg/storage/acme_servers_post_test.go index cb4fc24d..9ad51a8b 100644 --- a/pkg/storage/acme_servers_post_test.go +++ b/pkg/storage/acme_servers_post_test.go @@ -4,7 +4,6 @@ import ( "certwarden-backend/pkg/domain/acme_servers" "certwarden-backend/pkg/test_helpers" "database/sql" - "errors" "fmt" "testing" "time" @@ -47,7 +46,7 @@ func TestPostNewServer(t *testing.T) { CreatedAt: 1780337449, UpdatedAt: 1780338040, }, - test_helpers.ErrAnyType, + test_helpers.MakeTestErrorStringComp("UNIQUE constraint failed"), acme_servers.Server{}, sql.ErrNoRows, }, @@ -60,7 +59,7 @@ func TestPostNewServer(t *testing.T) { CreatedAt: 1880337449, UpdatedAt: 1880338040, }, - test_helpers.ErrAnyType, + test_helpers.MakeTestErrorStringComp("NOT NULL constraint failed"), acme_servers.Server{}, sql.ErrNoRows, }, @@ -82,7 +81,7 @@ func TestPostNewServer(t *testing.T) { CompareAcmeServer(t, server, tc.expectedNew) server, err = storage.GetOneServerByName(server.Name) - if !errors.Is(err, tc.expectedGetErr) { + if !test_helpers.ErrorsIs(err, tc.expectedGetErr) { t.Errorf("expected get error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedGetErr), test_helpers.ErrorToVal(err)) } diff --git a/pkg/storage/acme_servers_put_test.go b/pkg/storage/acme_servers_put_test.go index f32dc050..3d9f2f8a 100644 --- a/pkg/storage/acme_servers_put_test.go +++ b/pkg/storage/acme_servers_put_test.go @@ -5,7 +5,6 @@ import ( "certwarden-backend/pkg/storage" "certwarden-backend/pkg/test_helpers" "database/sql" - "errors" "fmt" "testing" "time" @@ -137,14 +136,14 @@ func TestPutServerUpdate(t *testing.T) { for i, tc := range testCases { t.Run(fmt.Sprintf("#%d (id: %d)", i, tc.payload.ID), func(t *testing.T) { server, err := storage.PutServerUpdate(tc.payload) - if !errors.Is(err, tc.expectedPutErr) { + if !test_helpers.ErrorsIs(err, tc.expectedPutErr) { t.Errorf("expected put error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedPutErr), test_helpers.ErrorToVal(err)) } CompareAcmeServer(t, server, tc.expectedPutResult) server, err = storage.GetOneServerById(tc.getId) - if !errors.Is(err, tc.expectedGetErr) { + if !test_helpers.ErrorsIs(err, tc.expectedGetErr) { t.Errorf("expected get error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedGetErr), test_helpers.ErrorToVal(err)) } diff --git a/pkg/storage/keys_delete_test.go b/pkg/storage/keys_delete_test.go index ab070b89..f37cd449 100644 --- a/pkg/storage/keys_delete_test.go +++ b/pkg/storage/keys_delete_test.go @@ -5,7 +5,6 @@ import ( "certwarden-backend/pkg/storage" "certwarden-backend/pkg/test_helpers" "database/sql" - "errors" "fmt" "testing" ) @@ -36,7 +35,7 @@ func TestKeyInUse(t *testing.T) { for _, tc := range testCases { t.Run(fmt.Sprintf("key id: %d", tc.keyID), func(t *testing.T) { inUse, err := storage.KeyInUse(tc.keyID) - if !errors.Is(err, tc.expectedErr) { + if !test_helpers.ErrorsIs(err, tc.expectedErr) { t.Errorf("expected error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedErr), test_helpers.ErrorToVal(err)) } @@ -71,12 +70,12 @@ func TestDeleteKey(t *testing.T) { for _, tc := range testCases { t.Run(fmt.Sprintf("key id: %d", tc.keyID), func(t *testing.T) { err := storage.DeleteKey(tc.keyID) - if !errors.Is(err, tc.expectedDelErr) { + if !test_helpers.ErrorsIs(err, tc.expectedDelErr) { t.Errorf("expected delete error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedDelErr), test_helpers.ErrorToVal(err)) } key, err := storage.GetOneKeyById(tc.keyID) - if !errors.Is(err, tc.expectedGetErr) { + if !test_helpers.ErrorsIs(err, tc.expectedGetErr) { t.Errorf("expected get error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedGetErr), test_helpers.ErrorToVal(err)) } diff --git a/pkg/storage/keys_get_test.go b/pkg/storage/keys_get_test.go index a06b2612..00aec96b 100644 --- a/pkg/storage/keys_get_test.go +++ b/pkg/storage/keys_get_test.go @@ -6,7 +6,6 @@ import ( "certwarden-backend/pkg/pagination_sort" "certwarden-backend/pkg/test_helpers" "database/sql" - "errors" "fmt" "testing" "time" @@ -296,7 +295,7 @@ func TestGetOneKeyById(t *testing.T) { for i, tc := range testCases { t.Run(fmt.Sprintf("#%d (id: %d)", i, tc.id), func(t *testing.T) { key, err := storage.GetOneKeyById(tc.id) - if !errors.Is(err, tc.expectedErr) { + if !test_helpers.ErrorsIs(err, tc.expectedErr) { t.Errorf("expected error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedErr), test_helpers.ErrorToVal(err)) } @@ -326,7 +325,7 @@ func TestGetOneKeyByName(t *testing.T) { for i, tc := range testCases { t.Run(fmt.Sprintf("#%d (name: %s)", i, tc.name), func(t *testing.T) { key, err := storage.GetOneKeyByName(tc.name) - if !errors.Is(err, tc.expectedErr) { + if !test_helpers.ErrorsIs(err, tc.expectedErr) { t.Errorf("expected error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedErr), test_helpers.ErrorToVal(err)) } diff --git a/pkg/storage/keys_post_test.go b/pkg/storage/keys_post_test.go index 021adae2..0b685680 100644 --- a/pkg/storage/keys_post_test.go +++ b/pkg/storage/keys_post_test.go @@ -5,7 +5,6 @@ import ( "certwarden-backend/pkg/domain/private_keys/key_crypto" "certwarden-backend/pkg/test_helpers" "database/sql" - "errors" "fmt" "testing" "time" @@ -59,7 +58,7 @@ func TestPostNewKey(t *testing.T) { CreatedAt: 1780336477, UpdatedAt: 1780337010, }, - test_helpers.ErrAnyType, + test_helpers.MakeTestErrorStringComp("UNIQUE constraint failed"), private_keys.Key{}, sql.ErrNoRows, }, @@ -74,7 +73,7 @@ func TestPostNewKey(t *testing.T) { CreatedAt: 1780336480, UpdatedAt: 1780337001, }, - test_helpers.ErrAnyType, + test_helpers.MakeTestErrorStringComp("NOT NULL constraint failed"), private_keys.Key{}, sql.ErrNoRows, }, @@ -96,7 +95,7 @@ func TestPostNewKey(t *testing.T) { CompareKey(t, key, tc.expectedNewKey) key, err = storage.GetOneKeyByName(key.Name) - if !errors.Is(err, tc.expectedGetErr) { + if !test_helpers.ErrorsIs(err, tc.expectedGetErr) { t.Errorf("expected get error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedGetErr), test_helpers.ErrorToVal(err)) } diff --git a/pkg/storage/keys_put_test.go b/pkg/storage/keys_put_test.go index cb5d40d2..153ff627 100644 --- a/pkg/storage/keys_put_test.go +++ b/pkg/storage/keys_put_test.go @@ -6,7 +6,6 @@ import ( "certwarden-backend/pkg/storage" "certwarden-backend/pkg/test_helpers" "database/sql" - "errors" "fmt" "testing" "time" @@ -182,14 +181,14 @@ red-58 for i, tc := range testCases { t.Run(fmt.Sprintf("#%d (id: %d)", i, tc.payload.ID), func(t *testing.T) { key, err := storage.PutKeyUpdate(tc.payload) - if !errors.Is(err, tc.expectedPutErr) { + if !test_helpers.ErrorsIs(err, tc.expectedPutErr) { t.Errorf("expected put error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedPutErr), test_helpers.ErrorToVal(err)) } CompareKey(t, key, tc.expectedPutResult) key, err = storage.GetOneKeyById(tc.getId) - if !errors.Is(err, tc.expectedGetErr) { + if !test_helpers.ErrorsIs(err, tc.expectedGetErr) { t.Errorf("expected get error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedGetErr), test_helpers.ErrorToVal(err)) } @@ -284,12 +283,12 @@ red-62 for _, tc := range testCases { t.Run(fmt.Sprintf("id: %d)", tc.keyId), func(t *testing.T) { err := storage.PutKeyApiKey(tc.keyId, tc.apiKey, tc.updateTimeUnix) - if !errors.Is(err, tc.expectedPutErr) { + if !test_helpers.ErrorsIs(err, tc.expectedPutErr) { t.Errorf("expected put error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedPutErr), test_helpers.ErrorToVal(err)) } key, err := storage.GetOneKeyById(tc.keyId) - if !errors.Is(err, tc.expectedGetErr) { + if !test_helpers.ErrorsIs(err, tc.expectedGetErr) { t.Errorf("expected get error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedGetErr), test_helpers.ErrorToVal(err)) } @@ -384,12 +383,12 @@ red-67 for _, tc := range testCases { t.Run(fmt.Sprintf("id: %d)", tc.keyId), func(t *testing.T) { err := storage.PutKeyNewApiKey(tc.keyId, tc.apiKeyNew, tc.updateTimeUnix) - if !errors.Is(err, tc.expectedPutErr) { + if !test_helpers.ErrorsIs(err, tc.expectedPutErr) { t.Errorf("expected put error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedPutErr), test_helpers.ErrorToVal(err)) } key, err := storage.GetOneKeyById(tc.keyId) - if !errors.Is(err, tc.expectedGetErr) { + if !test_helpers.ErrorsIs(err, tc.expectedGetErr) { t.Errorf("expected get error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedGetErr), test_helpers.ErrorToVal(err)) } @@ -502,12 +501,12 @@ red-62 for _, tc := range testCases { t.Run(fmt.Sprintf("id: %d)", tc.keyId), func(t *testing.T) { err := storage.PutKeyLastAccess(tc.keyId, tc.lastAccessTimeUnix) - if !errors.Is(err, tc.expectedPutErr) { + if !test_helpers.ErrorsIs(err, tc.expectedPutErr) { t.Errorf("expected put error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedPutErr), test_helpers.ErrorToVal(err)) } key, err := storage.GetOneKeyById(tc.keyId) - if !errors.Is(err, tc.expectedGetErr) { + if !test_helpers.ErrorsIs(err, tc.expectedGetErr) { t.Errorf("expected get error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedGetErr), test_helpers.ErrorToVal(err)) } diff --git a/pkg/storage/service_mock_test.go b/pkg/storage/service_mock_test.go index 3035f9f2..ad54c8b7 100644 --- a/pkg/storage/service_mock_test.go +++ b/pkg/storage/service_mock_test.go @@ -3,6 +3,7 @@ package storage_test import ( "certwarden-backend/pkg/pagination_sort" "certwarden-backend/pkg/storage" + "certwarden-backend/pkg/test_helpers" "context" "errors" "io" @@ -61,7 +62,7 @@ func openStorageWithTestData(t *testing.T, testName string) (_ *storage.Storage, _, err := os.Stat(thisTestFolder) if err == nil { os.RemoveAll(thisTestFolder) - } else if !errors.Is(err, os.ErrNotExist) { + } else if !test_helpers.ErrorsIs(err, os.ErrNotExist) { return nil, err } diff --git a/pkg/test_helpers/err_is.go b/pkg/test_helpers/err_is.go index 2e644972..d05be456 100644 --- a/pkg/test_helpers/err_is.go +++ b/pkg/test_helpers/err_is.go @@ -1,16 +1,49 @@ package test_helpers -import "errors" +import ( + "errors" + "strings" +) -var ErrAnyType = errors.New("error of any type") +// testErrorStringComp is a special error type to check error text for a matching +// value (as opposed to a strict type match); this is useful when the exact type +// is not importable +type testErrorStringComp struct { + Inner error +} + +func (e testErrorStringComp) Error() string { + return e.Inner.Error() +} + +func (e testErrorStringComp) Unwrap() error { + return e.Inner +} -// ErrorsIs is a custom implementation of errors.Is() to provide an error type -// that will match any error type; otherwise, it is just a wrapper for errors.Is(); -// Only needs to be used when avoiding an import of the exact error type +// MakeTestErrorStringComp wraps the provided error text in a special error type that +// will be parsed and compared when the custom ErrorsIs is called +func MakeTestErrorStringComp(errText string) testErrorStringComp { + return testErrorStringComp{Inner: errors.New(errText)} +} + +// ErrorsIs func ErrorsIs(err error, target error) bool { - if err != nil && errors.Is(target, ErrAnyType) { - return true + // check if target is our special error type and if not, just do a normal errors.Is() + tError, isTestErrStringCmp := errors.AsType[testErrorStringComp](target) + if !isTestErrStringCmp { + return errors.Is(err, target) + } + + // if one is nil but not the other, they are not the same, return false early + // to avoid calls to nil value + if (err == nil && target != nil) || + (err != nil && target == nil) { + return false } - return errors.Is(err, target) + // comparison is case-insensitive + return strings.Contains( + strings.ToLower(err.Error()), + strings.ToLower(tError.Unwrap().Error()), + ) } diff --git a/pkg/test_helpers/err_is_test.go b/pkg/test_helpers/err_is_test.go index 72599d62..e657f209 100644 --- a/pkg/test_helpers/err_is_test.go +++ b/pkg/test_helpers/err_is_test.go @@ -4,53 +4,99 @@ import ( "certwarden-backend/pkg/acme" "certwarden-backend/pkg/test_helpers" "database/sql" + "errors" "fmt" "testing" ) func TestErrorsIs(t *testing.T) { testCases := []struct { - err error - target error - expectedResult bool + err error + target error + isTheSame bool }{ { - err: nil, - target: nil, - expectedResult: true, + err: nil, + target: nil, + isTheSame: true, }, { - err: nil, - target: test_helpers.ErrAnyType, - expectedResult: false, + err: nil, + target: test_helpers.MakeTestErrorStringComp("an error"), + isTheSame: false, }, { - err: test_helpers.ErrAnyType, - target: nil, - expectedResult: false, + err: test_helpers.MakeTestErrorStringComp("an error"), + target: nil, + isTheSame: false, }, { - err: sql.ErrNoRows, - target: acme.ErrChallengeMalformed, - expectedResult: false, + err: sql.ErrNoRows, + target: acme.ErrChallengeMalformed, + isTheSame: false, }, { - err: sql.ErrNoRows, - target: test_helpers.ErrAnyType, - expectedResult: true, + err: errors.New("an error 1"), + target: errors.New("another error 2"), + isTheSame: false, }, { - err: acme.ErrChallengeMalformed, - target: test_helpers.ErrAnyType, - expectedResult: true, + err: sql.ErrNoRows, + target: test_helpers.MakeTestErrorStringComp("an error"), + isTheSame: false, + }, + { + err: acme.ErrChallengeMalformed, + target: test_helpers.MakeTestErrorStringComp("an error"), + isTheSame: false, + }, + { + err: errors.New("some error"), + target: test_helpers.MakeTestErrorStringComp("uh oh, some error"), + isTheSame: false, + }, + { + err: errors.New("some error"), + target: test_helpers.MakeTestErrorStringComp("some error, uh oh"), + isTheSame: false, + }, + { + err: test_helpers.MakeTestErrorStringComp("uh oh, some error"), + target: errors.New("some error"), + isTheSame: false, + }, + { + err: test_helpers.MakeTestErrorStringComp("some error, uh oh"), + target: errors.New("some error"), + isTheSame: false, + }, + { + err: errors.New("uh oh, some error"), + target: test_helpers.MakeTestErrorStringComp("some error"), + isTheSame: true, + }, + { + err: errors.New("some error, uh oh"), + target: test_helpers.MakeTestErrorStringComp("some error"), + isTheSame: true, + }, + { + err: errors.New("uh oh, some error"), + target: test_helpers.MakeTestErrorStringComp("SOME errOR"), + isTheSame: true, + }, + { + err: errors.New("some error, uh oh"), + target: test_helpers.MakeTestErrorStringComp("SOME errOR"), + isTheSame: true, }, } for i, tc := range testCases { t.Run(fmt.Sprintf("#%d:", i), func(t *testing.T) { res := test_helpers.ErrorsIs(tc.err, tc.target) - if res != tc.expectedResult { - t.Errorf("err '%s' with target '%s' expected '%t' but got '%t'", test_helpers.ErrorToVal(tc.err), test_helpers.ErrorToVal(tc.target), tc.expectedResult, res) + if res != tc.isTheSame { + t.Errorf("err '%s' with target '%s' expected '%t' but got '%t'", test_helpers.ErrorToVal(tc.err), test_helpers.ErrorToVal(tc.target), tc.isTheSame, res) } }) } From 52be7cacc7b12f0f3ba5629fe75dd97567fc9e97 Mon Sep 17 00:00:00 2001 From: "Greg T. Wallace" Date: Mon, 22 Jun 2026 19:08:43 -0400 Subject: [PATCH 02/16] add TestGetCert --- pkg/domain/certificates/certificate.go | 42 ++--- .../certificates/certificate_extra_extn.go | 6 + pkg/domain/certificates/csr.go | 4 +- pkg/domain/certificates/handlers_put.go | 2 +- pkg/domain/certificates/validation.go | 2 +- pkg/domain/orders/auto_ordering_exp.go | 2 +- pkg/domain/orders/fulfilling_do.go | 6 +- pkg/domain/orders/handlers_post.go | 4 +- pkg/domain/orders/order.go | 40 ++-- pkg/domain/orders/order_acme_create.go | 4 +- pkg/domain/orders/order_acme_payloads.go | 2 +- pkg/storage/certificates.go | 4 +- pkg/storage/certificates_get_test.go | 171 ++++++++++++++++++ pkg/storage/certificates_test.go | 144 +++++++++++++++ pkg/storage/keys_get_test.go | 36 ++++ test_data/testdata_v11.db | Bin 557056 -> 557056 bytes 16 files changed, 413 insertions(+), 56 deletions(-) create mode 100644 pkg/storage/certificates_get_test.go create mode 100644 pkg/storage/certificates_test.go diff --git a/pkg/domain/certificates/certificate.go b/pkg/domain/certificates/certificate.go index aba02854..e6deb520 100644 --- a/pkg/domain/certificates/certificate.go +++ b/pkg/domain/certificates/certificate.go @@ -12,8 +12,8 @@ type Certificate struct { ID int Name string Description string - CertificateKey private_keys.Key - CertificateAccount acme_accounts.Account + Key private_keys.Key + Account acme_accounts.Account Subject string SubjectAltNames []string Organization string @@ -39,15 +39,15 @@ type Certificate struct { // certificateSummaryResponse is a JSON response containing only // fields desired for the summary type certificateSummaryResponse struct { - ID int `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - CertificateKey certificateKeySummaryResponse `json:"private_key"` - CertificateAccount certificateAccountSummaryResponse `json:"acme_account"` - Subject string `json:"subject"` - SubjectAltNames []string `json:"subject_alts"` - ApiKeyViaUrl bool `json:"api_key_via_url"` - LastAccess int64 `json:"last_access"` + ID int `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Key certificateKeySummaryResponse `json:"private_key"` + Account certificateAccountSummaryResponse `json:"acme_account"` + Subject string `json:"subject"` + SubjectAltNames []string `json:"subject_alts"` + ApiKeyViaUrl bool `json:"api_key_via_url"` + LastAccess int64 `json:"last_access"` } type certificateKeySummaryResponse struct { @@ -73,18 +73,18 @@ func (cert Certificate) summaryResponse() certificateSummaryResponse { ID: cert.ID, Name: cert.Name, Description: cert.Description, - CertificateKey: certificateKeySummaryResponse{ - ID: cert.CertificateKey.ID, - Name: cert.CertificateKey.Name, - Algorithm: cert.CertificateKey.Algorithm, + Key: certificateKeySummaryResponse{ + ID: cert.Key.ID, + Name: cert.Key.Name, + Algorithm: cert.Key.Algorithm, }, - CertificateAccount: certificateAccountSummaryResponse{ - ID: cert.CertificateAccount.ID, - Name: cert.CertificateAccount.Name, + Account: certificateAccountSummaryResponse{ + ID: cert.Account.ID, + Name: cert.Account.Name, CertAccountServer: certificateAccountServerSummaryResponse{ - ID: cert.CertificateAccount.AcmeServer.ID, - Name: cert.CertificateAccount.AcmeServer.Name, - IsStaging: cert.CertificateAccount.AcmeServer.IsStaging, + ID: cert.Account.AcmeServer.ID, + Name: cert.Account.AcmeServer.Name, + IsStaging: cert.Account.AcmeServer.IsStaging, }, }, Subject: cert.Subject, diff --git a/pkg/domain/certificates/certificate_extra_extn.go b/pkg/domain/certificates/certificate_extra_extn.go index 2eb9a4c6..70fb2def 100644 --- a/pkg/domain/certificates/certificate_extra_extn.go +++ b/pkg/domain/certificates/certificate_extra_extn.go @@ -5,6 +5,7 @@ import ( "encoding/asn1" "encoding/hex" "errors" + "fmt" "strconv" "strings" ) @@ -21,6 +22,11 @@ type CertExtension struct { Description string } +// String prints a log friendly version of the Certificate Extension (useful for testing) +func (ce CertExtension) String() string { + return fmt.Sprintf("CertExtension{Description: %s, Id: %s, Critical: %t, Value: %x}", ce.Description, ce.Id.String(), ce.Critical, ce.Value) +} + // CertExtensionJSON is the object to use in the API (both input and output) // to represent the custom CertificateExtension type CertExtensionJSON struct { diff --git a/pkg/domain/certificates/csr.go b/pkg/domain/certificates/csr.go index ded45d92..660b3633 100644 --- a/pkg/domain/certificates/csr.go +++ b/pkg/domain/certificates/csr.go @@ -56,7 +56,7 @@ func (cert *Certificate) MakeCsrDer() (csr []byte, err error) { // CSR template to create CSR from template := x509.CertificateRequest{ - SignatureAlgorithm: cert.CertificateKey.Algorithm.CsrSigningAlg(), + SignatureAlgorithm: cert.Key.Algorithm.CsrSigningAlg(), Subject: subj, DNSNames: append([]string{cert.Subject}, cert.SubjectAltNames...), // unused: EmailAddresses, IPAddresses, URIs, Attributes (deprecated) @@ -64,7 +64,7 @@ func (cert *Certificate) MakeCsrDer() (csr []byte, err error) { } // cert's private key for signing - certKey, err := key_crypto.PemStringToKey(cert.CertificateKey.Pem, cert.CertificateKey.Algorithm) + certKey, err := key_crypto.PemStringToKey(cert.Key.Pem, cert.Key.Algorithm) if err != nil { return nil, err } diff --git a/pkg/domain/certificates/handlers_put.go b/pkg/domain/certificates/handlers_put.go index eea37680..85d60983 100644 --- a/pkg/domain/certificates/handlers_put.go +++ b/pkg/domain/certificates/handlers_put.go @@ -92,7 +92,7 @@ func (service *Service) PutDetailsCert(w http.ResponseWriter, r *http.Request) * // profile Extension -- validate if specified if payload.Profile != nil && *payload.Profile != "" { // specified, validate against acme service - acmeService, err := service.acmeServerService.AcmeService(cert.CertificateAccount.AcmeServer.ID) + acmeService, err := service.acmeServerService.AcmeService(cert.Account.AcmeServer.ID) if err != nil { err = fmt.Errorf("failed to retrieve acme service (%s)", err) service.logger.Error(err) diff --git a/pkg/domain/certificates/validation.go b/pkg/domain/certificates/validation.go index 947e1786..ccd51816 100644 --- a/pkg/domain/certificates/validation.go +++ b/pkg/domain/certificates/validation.go @@ -98,7 +98,7 @@ func (service *Service) privateKeyIdValid(keyId int, certId *int) bool { } // if certificate's key id matches keyId, valid - if cert.CertificateKey.ID == keyId { + if cert.Key.ID == keyId { return true } diff --git a/pkg/domain/orders/auto_ordering_exp.go b/pkg/domain/orders/auto_ordering_exp.go index efe5e60c..3a062900 100644 --- a/pkg/domain/orders/auto_ordering_exp.go +++ b/pkg/domain/orders/auto_ordering_exp.go @@ -30,7 +30,7 @@ func (service *Service) orderExpiringCerts() { defer wg.Done() // Get relevant ACME Server service - acmeService, err := service.acmeServerService.AcmeService(orders[i].Certificate.CertificateAccount.AcmeServer.ID) + acmeService, err := service.acmeServerService.AcmeService(orders[i].Certificate.Account.AcmeServer.ID) if err != nil { service.logger.Errorf("orders: auto order failed to get acme service for order %d (%s)", orders[i].ID, err) return // done, failed diff --git a/pkg/domain/orders/fulfilling_do.go b/pkg/domain/orders/fulfilling_do.go index b2d89889..fb060b05 100644 --- a/pkg/domain/orders/fulfilling_do.go +++ b/pkg/domain/orders/fulfilling_do.go @@ -32,7 +32,7 @@ func (j *orderFulfillJob) Do(workerID int) { }() // get account key - key, err := order.Certificate.CertificateAccount.AcmeAccountKey() + key, err := order.Certificate.Account.AcmeAccountKey() if err != nil { j.service.logger.Errorf("orders: fulfilling worker %d: get account key error: %s", workerID, err) return // done, failed @@ -49,7 +49,7 @@ func (j *orderFulfillJob) Do(workerID int) { var acmeOrder acme.Order // acmeService to avoid repeated logic - acmeService, err := j.service.acmeServerService.AcmeService(order.Certificate.CertificateAccount.AcmeServer.ID) + acmeService, err := j.service.acmeServerService.AcmeService(order.Certificate.Account.AcmeServer.ID) if err != nil { j.service.logger.Errorf("orders: fulfilling worker %d: select acme service error: %s", workerID, err) return // done, failed @@ -104,7 +104,7 @@ fulfillLoop: // save finalized_key_id in storage (if finalize ACME cmd below fails, this will save any key change // upon next attempt to finalize with ACME; therefore this should always occur BEFORE the ACME finalize // command) - err = j.service.storage.UpdateFinalizedKey(order.ID, order.Certificate.CertificateKey.ID) + err = j.service.storage.UpdateFinalizedKey(order.ID, order.Certificate.Key.ID) if err != nil { j.service.logger.Errorf("orders: fulfilling worker %d: update finalized key error: %s", workerID, err) return // done, failed diff --git a/pkg/domain/orders/handlers_post.go b/pkg/domain/orders/handlers_post.go index e50e2c33..ac5d08af 100644 --- a/pkg/domain/orders/handlers_post.go +++ b/pkg/domain/orders/handlers_post.go @@ -160,14 +160,14 @@ func (service *Service) RevokeOrder(w http.ResponseWriter, r *http.Request) *out // end validation // get account key - key, err := order.Certificate.CertificateAccount.AcmeAccountKey() + key, err := order.Certificate.Account.AcmeAccountKey() if err != nil { service.logger.Error(err) return output.JsonErrInternal(err) } // revoke the certificate with ACME - acmeService, err := service.acmeServerService.AcmeService(order.Certificate.CertificateAccount.AcmeServer.ID) + acmeService, err := service.acmeServerService.AcmeService(order.Certificate.Account.AcmeServer.ID) if err != nil { service.logger.Error(err) return output.JsonErrInternal(err) diff --git a/pkg/domain/orders/order.go b/pkg/domain/orders/order.go index 3342e4af..cfb8decf 100644 --- a/pkg/domain/orders/order.go +++ b/pkg/domain/orders/order.go @@ -59,22 +59,22 @@ type orderSummaryResponse struct { } type orderCertificateSummaryResponse struct { - ID int `json:"id"` - Name string `json:"name"` - CertificateAccount orderCertificateAccountSummaryResponse `json:"acme_account"` - Subject string `json:"subject"` - SubjectAltNames []string `json:"subject_alts"` - ApiKeyViaUrl bool `json:"api_key_via_url"` - LastAccess int64 `json:"last_access"` + ID int `json:"id"` + Name string `json:"name"` + Account orderAccountSummaryResponse `json:"acme_account"` + Subject string `json:"subject"` + SubjectAltNames []string `json:"subject_alts"` + ApiKeyViaUrl bool `json:"api_key_via_url"` + LastAccess int64 `json:"last_access"` } -type orderCertificateAccountSummaryResponse struct { - ID int `json:"id"` - Name string `json:"name"` - OrderCertAccountServer orderCertificateAccountServerSummaryResponse `json:"acme_server"` +type orderAccountSummaryResponse struct { + ID int `json:"id"` + Name string `json:"name"` + OrderCertAccountServer orderAccountServerSummaryResponse `json:"acme_server"` } -type orderCertificateAccountServerSummaryResponse struct { +type orderAccountServerSummaryResponse struct { ID int `json:"id"` Name string `json:"name"` IsStaging bool `json:"is_staging"` @@ -118,13 +118,13 @@ func (order Order) summaryResponse(service *Service) orderSummaryResponse { Certificate: orderCertificateSummaryResponse{ ID: order.Certificate.ID, Name: order.Certificate.Name, - CertificateAccount: orderCertificateAccountSummaryResponse{ - ID: order.Certificate.CertificateAccount.ID, - Name: order.Certificate.CertificateAccount.Name, - OrderCertAccountServer: orderCertificateAccountServerSummaryResponse{ - ID: order.Certificate.CertificateAccount.AcmeServer.ID, - Name: order.Certificate.CertificateAccount.AcmeServer.Name, - IsStaging: order.Certificate.CertificateAccount.AcmeServer.IsStaging, + Account: orderAccountSummaryResponse{ + ID: order.Certificate.Account.ID, + Name: order.Certificate.Account.Name, + OrderCertAccountServer: orderAccountServerSummaryResponse{ + ID: order.Certificate.Account.AcmeServer.ID, + Name: order.Certificate.Account.AcmeServer.Name, + IsStaging: order.Certificate.Account.AcmeServer.IsStaging, }, }, Subject: order.Certificate.Subject, @@ -248,7 +248,7 @@ func (service *Service) NewOrderPayload(cert certificates.Certificate) acme.NewO // ACME ARI Extension: try to include the `replaces` field replaces := func() *string { - acmeServ, err := service.acmeServerService.AcmeService(cert.CertificateAccount.AcmeServer.ID) + acmeServ, err := service.acmeServerService.AcmeService(cert.Account.AcmeServer.ID) if err != nil { service.logger.Errorf("orders: new order cant populated `replaces`, failed to get acme service for cert %d (%s)", cert.ID, err) return nil diff --git a/pkg/domain/orders/order_acme_create.go b/pkg/domain/orders/order_acme_create.go index d60fe21c..027377f6 100644 --- a/pkg/domain/orders/order_acme_create.go +++ b/pkg/domain/orders/order_acme_create.go @@ -21,14 +21,14 @@ func (service *Service) placeNewOrderAndFulfill(certId int, highPriority bool) ( } // get account key - key, err := cert.CertificateAccount.AcmeAccountKey() + key, err := cert.Account.AcmeAccountKey() if err != nil { service.logger.Error(err) return Order{}, output.JsonErrInternal(err) } // send the new-order to ACME - acmeService, err := service.acmeServerService.AcmeService(cert.CertificateAccount.AcmeServer.ID) + acmeService, err := service.acmeServerService.AcmeService(cert.Account.AcmeServer.ID) if err != nil { service.logger.Error(err) return Order{}, output.JsonErrInternal(err) diff --git a/pkg/domain/orders/order_acme_payloads.go b/pkg/domain/orders/order_acme_payloads.go index 578644d7..7ff45e38 100644 --- a/pkg/domain/orders/order_acme_payloads.go +++ b/pkg/domain/orders/order_acme_payloads.go @@ -37,7 +37,7 @@ func makeNewOrderAcmePayload(cert certificates.Certificate, acmeResponse acme.Or payload := NewOrderAcmePayload{ CertId: cert.ID, - AccountId: cert.CertificateAccount.ID, + AccountId: cert.Account.ID, Status: acmeResponse.Status, KnownRevoked: false, Expires: acmeResponse.Expires, diff --git a/pkg/storage/certificates.go b/pkg/storage/certificates.go index d0d4a746..57b9d341 100644 --- a/pkg/storage/certificates.go +++ b/pkg/storage/certificates.go @@ -45,8 +45,8 @@ func (cert certificateDb) toCertificate() (certificates.Certificate, error) { ID: cert.id, Name: cert.name, Description: cert.description, - CertificateKey: cert.certificateKeyDb.toKey(), - CertificateAccount: cert.certificateAccountDb.toAccount(), + Key: cert.certificateKeyDb.toKey(), + Account: cert.certificateAccountDb.toAccount(), Subject: cert.subject, SubjectAltNames: cert.subjectAltNames.toSlice(), Organization: cert.organization, diff --git a/pkg/storage/certificates_get_test.go b/pkg/storage/certificates_get_test.go new file mode 100644 index 00000000..fbe6b531 --- /dev/null +++ b/pkg/storage/certificates_get_test.go @@ -0,0 +1,171 @@ +package storage_test + +import ( + "certwarden-backend/pkg/domain/certificates" + "certwarden-backend/pkg/test_helpers" + "crypto/x509/pkix" + "database/sql" + "encoding/asn1" + "fmt" + "testing" + "time" +) + +var ( + cert18 = certificates.Certificate{ + ID: 18, + Name: "serverdefault", + Description: "its a decript", + Key: key31, + Account: acmeAcct2, + Subject: "desk.dude.example.com", + SubjectAltNames: []string{"test011.test.example.com"}, + Organization: "my org", + OrganizationalUnit: "my ou", + Country: "your country", + State: "a state", + City: "springfield", + CSRExtraExtensions: []certificates.CertExtension{ + { + Extension: pkix.Extension{ + Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24}, + Critical: false, + Value: []byte{0x30, 0x03, 0x02, 0x01, 0x05}, + }, + Description: "OCSP Must Staple", + }, + }, + PreferredRootCN: "ISRG Root X1", + LastAccess: time.Unix(1745952074, 0), + CreatedAt: time.Unix(1709327717, 0), + UpdatedAt: time.Unix(1779386440, 0), + ApiKey: "api-secret-18", + ApiKeyNew: "api-new-secret-18", + ApiKeyViaUrl: true, + PostProcessingCommand: "./scripts/windows/post-processing.example.ps1", + PostProcessingEnvironment: []string{"asdasdasdsd=asasd"}, + PostProcessingClientAddress: "dude.greg.example.com", + PostProcessingClientKeyB64: "aaaaaaaaaaaaaaaaaaaaaaaaaaa-ccccccccccccccc", + Profile: "tlsserver", + } + + cert26 = certificates.Certificate{ + ID: 26, + Name: "test008.test.example.com", + Description: "", + Key: key55, + Account: acmeAcct1, + Subject: "test008.test.example.com", + SubjectAltNames: []string{}, + Organization: "", + OrganizationalUnit: "", + Country: "", + State: "", + City: "", + CSRExtraExtensions: []certificates.CertExtension{}, + PreferredRootCN: "", + LastAccess: time.Unix(0, 0), + CreatedAt: time.Unix(1743170701, 0), + UpdatedAt: time.Unix(1765392360, 0), + ApiKey: "api-secret-26", + ApiKeyNew: "", + ApiKeyViaUrl: false, + PostProcessingCommand: "./scripts/windows/post-processing.example.ps1", + PostProcessingEnvironment: []string{}, + PostProcessingClientAddress: "test008.test.example.com", + PostProcessingClientKeyB64: "", + Profile: "", + } + + cert27 = certificates.Certificate{ + ID: 27, + Name: "test008.test.example.com-p", + Description: "", + Key: key56, + Account: acmeAcct2, + Subject: "test008.test.example.com", + SubjectAltNames: []string{"test011.test.example.com", "*.test011.test.example.com"}, + Organization: "", + OrganizationalUnit: "", + Country: "", + State: "", + City: "", + CSRExtraExtensions: []certificates.CertExtension{}, + PreferredRootCN: "", + LastAccess: time.Unix(0, 0), + CreatedAt: time.Unix(1743171060, 0), + UpdatedAt: time.Unix(1746122825, 0), + ApiKey: "api-secret-27", + ApiKeyNew: "", + ApiKeyViaUrl: false, + PostProcessingCommand: "", + PostProcessingEnvironment: []string{}, + PostProcessingClientAddress: "", + PostProcessingClientKeyB64: "", + Profile: "", + } +) + +// TODO: TestGetAll + +func TestGetOneCertById(t *testing.T) { + testCases := []struct { + id int + expectedErr error + expectedCert certificates.Certificate + }{ + {-5, sql.ErrNoRows, certificates.Certificate{}}, + {50, sql.ErrNoRows, certificates.Certificate{}}, + {18, nil, cert18}, + {26, nil, cert26}, + {27, nil, cert27}, + } + + // create testing service + storage, err := openStorageWithTestData(t, "getonecertbyid") + if err != nil { + t.Fatal(err) + } + + for i, tc := range testCases { + t.Run(fmt.Sprintf("#%d (id: %d)", i, tc.id), func(t *testing.T) { + serv, err := storage.GetOneCertById(tc.id) + if !test_helpers.ErrorsIs(err, tc.expectedErr) { + t.Errorf("expected error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedErr), test_helpers.ErrorToVal(err)) + } + + CompareCertificate(t, serv, tc.expectedCert) + }) + } +} + +func TestGetOneCertByName(t *testing.T) { + testCases := []struct { + name string + expectedErr error + expectedCert certificates.Certificate + }{ + {"fake-bad-name", sql.ErrNoRows, certificates.Certificate{}}, + {"", sql.ErrNoRows, certificates.Certificate{}}, + {"serverdefault", nil, cert18}, + {"test008.test.example.com", nil, cert26}, + {"test008.test.example.com-p", nil, cert27}, + } + + // create testing service + storage, err := openStorageWithTestData(t, "getonecertbyname") + if err != nil { + t.Fatal(err) + } + + for i, tc := range testCases { + t.Run(fmt.Sprintf("#%d (name: %s)", i, tc.name), func(t *testing.T) { + serv, err := storage.GetOneCertByName(tc.name) + if !test_helpers.ErrorsIs(err, tc.expectedErr) { + t.Errorf("expected error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedErr), test_helpers.ErrorToVal(err)) + } + + CompareCertificate(t, serv, tc.expectedCert) + }) + } +} diff --git a/pkg/storage/certificates_test.go b/pkg/storage/certificates_test.go new file mode 100644 index 00000000..16845d3c --- /dev/null +++ b/pkg/storage/certificates_test.go @@ -0,0 +1,144 @@ +package storage_test + +import ( + "bytes" + "certwarden-backend/pkg/domain/certificates" + "slices" + "testing" +) + +// CompareCertificateCSRExtensions is for comparing the special csr extra extensions +func CompareCertificateCSRExtensions(t *testing.T, extns, expectedExtns []certificates.CertExtension) { + if len(extns) != len(expectedExtns) { + t.Errorf("certificate: csr extra extensions expected length '%d' but got '%d'", len(expectedExtns), len(extns)) + } + + // check all expected + for i, extn := range expectedExtns { + if i >= len(extns) { + t.Errorf("certificate: csr extra extensions expected '%v' but got none", extn) + continue + } + + // check each field + if !slices.Equal(extn.Id, extns[i].Id) { + t.Errorf("certificate: csr extra extension %d expected oid '%v' but got '%v'", i, extn.Id, extns[i].Id) + } + + if extn.Critical != extns[i].Critical { + t.Errorf("certificate: csr extra extension %d expected critical '%t' but got '%t'", i, extn.Critical, extns[i].Critical) + } + + if !bytes.Equal(extn.Value, extns[i].Value) { + t.Errorf("certificate: csr extra extension %d expected value '%v' but got '%v'", i, extn.Value, extns[i].Value) + } + + if extn.Description != extns[i].Description { + t.Errorf("certificate: csr extra extension %d expected description '%s' but got '%s'", i, extn.Description, extns[i].Description) + } + } + + // error for any extras + if len(extns) > len(expectedExtns) { + for i := len(expectedExtns); i < len(extns); i++ { + t.Errorf("certificate: csr extra extensions expected no additional but got '%v'", extns[i]) + } + } +} + +// CompareCertificate compares cert to expectedCert and throws appropriate errors for any differences +func CompareCertificate(t *testing.T, cert, expectedCert certificates.Certificate) { + if cert.ID != expectedCert.ID { + t.Errorf("certificate: id expected '%d' but got '%d'", expectedCert.ID, cert.ID) + } + + if cert.Name != expectedCert.Name { + t.Errorf("certificate: name expected '%s' but got '%s'", expectedCert.Name, cert.Name) + } + + if cert.Description != expectedCert.Description { + t.Errorf("certificate: description expected '%s' but got '%s'", expectedCert.Description, cert.Description) + } + + CompareKey(t, cert.Key, expectedCert.Key) + + CompareAcmeAccount(t, cert.Account, expectedCert.Account) + + if cert.Subject != expectedCert.Subject { + t.Errorf("certificate: subject expected '%s' but got '%s'", expectedCert.Subject, cert.Subject) + } + + if !slices.Equal(cert.SubjectAltNames, expectedCert.SubjectAltNames) { + t.Errorf("certificate: subject alt names expected '%v' but got '%v'", expectedCert.SubjectAltNames, cert.SubjectAltNames) + } + + if cert.Organization != expectedCert.Organization { + t.Errorf("certificate: organization expected '%s' but got '%s'", expectedCert.Organization, cert.Organization) + } + + if cert.OrganizationalUnit != expectedCert.OrganizationalUnit { + t.Errorf("certificate: organizational unit expected '%s' but got '%s'", expectedCert.OrganizationalUnit, cert.OrganizationalUnit) + } + + if cert.Country != expectedCert.Country { + t.Errorf("certificate: country expected '%s' but got '%s'", expectedCert.Country, cert.Country) + } + + if cert.State != expectedCert.State { + t.Errorf("certificate: state expected '%s' but got '%s'", expectedCert.State, cert.State) + } + + if cert.City != expectedCert.City { + t.Errorf("certificate: city expected '%s' but got '%s'", expectedCert.City, cert.City) + } + + CompareCertificateCSRExtensions(t, cert.CSRExtraExtensions, expectedCert.CSRExtraExtensions) + + if cert.PreferredRootCN != expectedCert.PreferredRootCN { + t.Errorf("certificate: preferred root cn expected '%s' but got '%s'", expectedCert.PreferredRootCN, cert.PreferredRootCN) + } + + if !cert.LastAccess.Equal(expectedCert.LastAccess) { + t.Errorf("certificate: last access expected '%s' but got '%s'", expectedCert.LastAccess.UTC(), cert.LastAccess.UTC()) + } + + if !cert.CreatedAt.Equal(expectedCert.CreatedAt) { + t.Errorf("certificater: created at expected '%s' but got '%s'", expectedCert.CreatedAt.UTC(), cert.CreatedAt.UTC()) + } + + if !cert.UpdatedAt.Equal(expectedCert.UpdatedAt) { + t.Errorf("certificate: updated at expected '%s' but got '%s'", expectedCert.UpdatedAt.UTC(), cert.UpdatedAt.UTC()) + } + + if cert.ApiKey != expectedCert.ApiKey { + t.Errorf("certificate: api key expected '%s' but got '%s'", expectedCert.ApiKey, cert.ApiKey) + } + + if cert.ApiKeyNew != expectedCert.ApiKeyNew { + t.Errorf("certificate: api key new expected '%s' but got '%s'", expectedCert.ApiKeyNew, cert.ApiKeyNew) + } + + if cert.ApiKeyViaUrl != expectedCert.ApiKeyViaUrl { + t.Errorf("acme server: api key via url expected '%t' but got '%t'", expectedCert.ApiKeyViaUrl, cert.ApiKeyViaUrl) + } + + if cert.PostProcessingCommand != expectedCert.PostProcessingCommand { + t.Errorf("acme server: post processing command expected '%s' but got '%s'", expectedCert.PostProcessingCommand, cert.PostProcessingCommand) + } + + if !slices.Equal(cert.PostProcessingEnvironment, expectedCert.PostProcessingEnvironment) { + t.Errorf("certificate: post processing environment expected '%v' but got '%v'", expectedCert.PostProcessingEnvironment, cert.PostProcessingEnvironment) + } + + if cert.PostProcessingClientAddress != expectedCert.PostProcessingClientAddress { + t.Errorf("acme server: post processing client address expected '%s' but got '%s'", expectedCert.PostProcessingClientAddress, cert.PostProcessingClientAddress) + } + + if cert.PostProcessingClientKeyB64 != expectedCert.PostProcessingClientKeyB64 { + t.Errorf("acme server: post processing client key base64 expected '%s' but got '%s'", expectedCert.PostProcessingClientKeyB64, cert.PostProcessingClientKeyB64) + } + + if cert.Profile != expectedCert.Profile { + t.Errorf("acme server: profile expected '%s' but got '%s'", expectedCert.Profile, cert.Profile) + } +} diff --git a/pkg/storage/keys_get_test.go b/pkg/storage/keys_get_test.go index 00aec96b..59a43884 100644 --- a/pkg/storage/keys_get_test.go +++ b/pkg/storage/keys_get_test.go @@ -67,6 +67,42 @@ red-31 UpdatedAt: time.Unix(1732748628, 0), } + key55 = private_keys.Key{ + ID: 55, + Name: "test008.test.example.com", + Description: "", + Algorithm: key_crypto.AlgorithmECDSAp256, + Pem: `-----BEGIN EC PRIVATE KEY----- +red-55 +-----END EC PRIVATE KEY----- +`, + ApiKey: "key-api-key-55", + ApiKeyNew: "", + ApiKeyDisabled: false, + ApiKeyViaUrl: false, + LastAccess: time.Unix(0, 0), + CreatedAt: time.Unix(1743170701, 0), + UpdatedAt: time.Unix(0, 0), + } + + key56 = private_keys.Key{ + ID: 56, + Name: "test008.test.example.com-p", + Description: "", + Algorithm: key_crypto.AlgorithmECDSAp256, + Pem: `-----BEGIN EC PRIVATE KEY----- +red-56 +-----END EC PRIVATE KEY----- +`, + ApiKey: "key-api-key-56", + ApiKeyNew: "", + ApiKeyDisabled: false, + ApiKeyViaUrl: false, + LastAccess: time.Unix(0, 0), + CreatedAt: time.Unix(1743171060, 0), + UpdatedAt: time.Unix(0, 0), + } + key58 = private_keys.Key{ ID: 58, Name: "_Buypass_Staging", diff --git a/test_data/testdata_v11.db b/test_data/testdata_v11.db index fb303bd5c1a97594376f668dd088ee76cc7c537d..75a604efd4efc999fa3eeeb68f0dc33f60bfcaa0 100644 GIT binary patch delta 379 zcmZo@P-kZs%oUXr6vpm$BBS*|ynQNSc9> zQC;2KRZ?74T3KD6lZ7SSJh?GmmBn0Mo~byss4TT8B{eOvG^Zr9q*x(QAtg1rD6^ns zdSDT|==5W1jM7@Mxs?j}Md={2v@*Z6NFh1DG_RznGEt$pB(Ws5xS%LAFFh?YH78~I zLVZTH>C4m^_3D)pi&G$|IK?)x7|2wLO~eg!lTkr*wNgrIG1w)U`FToKO8(Bl0SdmQ z#U%>CC5Z(&sY*IZ`I#v|2}3<&Ju^K+JyRew2hv6+K&fQYqRf)aEHi&)^_;g`oWW5`_rE_Ivt_K+FWh%s|Wn#H>Kf O2E^>!@9A?W1polDMtm{= delta 325 zcmZo@P-kZs%oUoIm}rE@Q22aY}KDZDMg^ zaf(uGxM6l!O0cO%S!ixfX|SVvX|i9ES&&zeYf-wde@b$Iv4vS+QJJZ)zoBb%Y=)q$ z*Yu6MOE!?LdvOL8wBqgFWu%y7)*~Q4;r7=KAih+?) zo!49tY^1vW^ozQTl1h!LAn`_dbXiUo7G85+L0%T~>9=(mO{PEAW0Vw+Rswntf)JjK fj%~lE&j`d!K+FupEI`Z(#B4y!zWts)hf)9l@4Q|a From 85312d22e44b9c513a48a9f1aa9d90d691669494 Mon Sep 17 00:00:00 2001 From: "Greg T. Wallace" Date: Thu, 6 Aug 2026 21:20:45 -0400 Subject: [PATCH 03/16] add TestGetAllCerts --- pkg/storage/accounts_get_test.go | 2 +- pkg/storage/acme_servers_get_test.go | 6 ++-- pkg/storage/certificates_get_test.go | 44 +++++++++++++++++++++++++++- pkg/storage/keys_get_test.go | 14 ++++----- 4 files changed, 54 insertions(+), 12 deletions(-) diff --git a/pkg/storage/accounts_get_test.go b/pkg/storage/accounts_get_test.go index b3b916f1..a3818b23 100644 --- a/pkg/storage/accounts_get_test.go +++ b/pkg/storage/accounts_get_test.go @@ -108,7 +108,7 @@ func TestGetAllAcmeAccounts(t *testing.T) { t.Run(fmt.Sprintf("#%d (%s)", i, tc.expectedAcctAtIndx.Name), func(t *testing.T) { accts, totalCt, err := storage.GetAllAcmeAccounts(tc.q) if err != nil { - t.Errorf("get all keys failed") + t.Errorf("get all failed") return } diff --git a/pkg/storage/acme_servers_get_test.go b/pkg/storage/acme_servers_get_test.go index cfbd4063..8811fc55 100644 --- a/pkg/storage/acme_servers_get_test.go +++ b/pkg/storage/acme_servers_get_test.go @@ -75,7 +75,7 @@ func TestGetAllAcmeServers(t *testing.T) { t.Run(fmt.Sprintf("#%d (%s)", i, tc.expectedServerAtIndx.Name), func(t *testing.T) { servers, totalCt, err := storage.GetAllAcmeServers(tc.q) if err != nil { - t.Errorf("get all keys failed") + t.Errorf("get all failed") return } @@ -83,12 +83,12 @@ func TestGetAllAcmeServers(t *testing.T) { t.Errorf("incorrect total count, expected '%d' but got '%d'", tc.expectedTotalCt, totalCt) } if len(servers) != tc.expectedResultLen { - t.Errorf("incorrect servers length, expected '%d' but got '%d'", tc.expectedResultLen, len(servers)) + t.Errorf("incorrect result length, expected '%d' but got '%d'", tc.expectedResultLen, len(servers)) } if tc.testIndx <= len(servers)-1 { CompareAcmeServer(t, servers[tc.testIndx], tc.expectedServerAtIndx) } else { - t.Errorf("couldnt test server at index '%d' because length of server array was only '%d'", tc.testIndx, len(servers)) + t.Errorf("couldnt test result at index '%d' because length of result array was only '%d'", tc.testIndx, len(servers)) } }) } diff --git a/pkg/storage/certificates_get_test.go b/pkg/storage/certificates_get_test.go index fbe6b531..ca803d2b 100644 --- a/pkg/storage/certificates_get_test.go +++ b/pkg/storage/certificates_get_test.go @@ -2,6 +2,7 @@ package storage_test import ( "certwarden-backend/pkg/domain/certificates" + "certwarden-backend/pkg/pagination_sort" "certwarden-backend/pkg/test_helpers" "crypto/x509/pkix" "database/sql" @@ -106,7 +107,48 @@ var ( } ) -// TODO: TestGetAll +func TestGetAllCerts(t *testing.T) { + testCases := []struct { + q pagination_sort.Query + expectedTotalCt int + expectedResultLen int + testIndx int + expectedAtIndx certificates.Certificate + }{ + {pagination_sort.Query{}, 9, 9, 2, cert18}, + {QueryBuilderForTest(1, 1, "id", true), 9, 1, 0, cert26}, + {QueryBuilderForTest(3, 6, "servername", false), 9, 3, 1, cert27}, + {QueryBuilderForTest(4, 1, "accountname", true), 9, 4, 0, cert18}, + } + + // create testing service + storage, err := openStorageWithTestData(t, "getallcerts") + if err != nil { + t.Fatal(err) + } + + for i, tc := range testCases { + t.Run(fmt.Sprintf("#%d (%s)", i, tc.expectedAtIndx.Name), func(t *testing.T) { + certs, totalCt, err := storage.GetAllCerts(tc.q) + if err != nil { + t.Errorf("get all failed") + return + } + + if totalCt != tc.expectedTotalCt { + t.Errorf("incorrect total count, expected '%d' but got '%d'", tc.expectedTotalCt, totalCt) + } + if len(certs) != tc.expectedResultLen { + t.Errorf("incorrect result length, expected '%d' but got '%d'", tc.expectedResultLen, len(certs)) + } + if tc.testIndx <= len(certs)-1 { + CompareCertificate(t, certs[tc.testIndx], tc.expectedAtIndx) + } else { + t.Errorf("couldnt test result at index '%d' because length of result array was only '%d'", tc.testIndx, len(certs)) + } + }) + } +} func TestGetOneCertById(t *testing.T) { testCases := []struct { diff --git a/pkg/storage/keys_get_test.go b/pkg/storage/keys_get_test.go index 59a43884..480992cb 100644 --- a/pkg/storage/keys_get_test.go +++ b/pkg/storage/keys_get_test.go @@ -291,20 +291,20 @@ func TestGetAllKeys(t *testing.T) { t.Run(fmt.Sprintf("#%d (%s)", i, tc.expectedKeyAtIndx.Name), func(t *testing.T) { keys, totalCt, err := storage.GetAllKeys(tc.q) if err != nil { - t.Errorf("get all keys failed") + t.Errorf("get all failed") return } if totalCt != tc.expectedTotalCt { - t.Errorf("get all keys returned incorrect total count, expected '%d' but got '%d'", tc.expectedTotalCt, totalCt) + t.Errorf("incorrect total count, expected '%d' but got '%d'", tc.expectedTotalCt, totalCt) } if len(keys) != tc.expectedResultLen { - t.Errorf("get all keys returned incorrect keys length, expected '%d' but got '%d'", tc.expectedResultLen, len(keys)) + t.Errorf("incorrect result length, expected '%d' but got '%d'", tc.expectedResultLen, len(keys)) } if tc.testIndx <= len(keys)-1 { CompareKey(t, keys[tc.testIndx], tc.expectedKeyAtIndx) } else { - t.Errorf("couldnt test key at index '%d' because length of key array was only '%d'", tc.testIndx, len(keys)) + t.Errorf("couldnt test result at index '%d' because length of result array was only '%d'", tc.testIndx, len(keys)) } }) } @@ -379,19 +379,19 @@ func TestGetAvailableKeys(t *testing.T) { keys, err := storage.GetAvailableKeys() if err != nil { - t.Errorf("get available keys failed") + t.Errorf("get all failed") return } expectedResultLen := 3 if len(keys) != expectedResultLen { - t.Errorf("get available keys returned incorrect keys length, expected '%d' but got '%d'", expectedResultLen, len(keys)) + t.Errorf("returned incorrect result length, expected '%d' but got '%d'", expectedResultLen, len(keys)) } expectedKeys := []private_keys.Key{key58, key62, key69} for i, expectedKey := range expectedKeys { if i > len(keys)-1 { - t.Errorf("expected key id '%d' at index '%d' but result was too short", expectedKey.ID, i) + t.Errorf("expected id '%d' at index '%d' but result was too short", expectedKey.ID, i) continue } From 0bb92a36b63d648c835ea74d55994fae7eeb65b8 Mon Sep 17 00:00:00 2001 From: "Greg T. Wallace" Date: Thu, 6 Aug 2026 21:20:45 -0400 Subject: [PATCH 04/16] add TestDeleteCert * refactor some other inuse checks to use a transaction * minor test print updates --- pkg/storage/accounts_delete.go | 65 ++++++++++--------------- pkg/storage/acme_servers_delete.go | 21 ++++++-- pkg/storage/certificates_delete.go | 25 +++++++--- pkg/storage/certificates_delete_test.go | 47 ++++++++++++++++++ pkg/storage/keys_delete.go | 37 +++++++++++--- pkg/storage/keys_delete_test.go | 4 +- 6 files changed, 140 insertions(+), 59 deletions(-) create mode 100644 pkg/storage/certificates_delete_test.go diff --git a/pkg/storage/accounts_delete.go b/pkg/storage/accounts_delete.go index 75023e7f..a0358478 100644 --- a/pkg/storage/accounts_delete.go +++ b/pkg/storage/accounts_delete.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "errors" + "fmt" ) // AcmeAccountInUse returns true if the specified accountId matches @@ -12,6 +13,12 @@ func (store *Storage) AcmeAccountInUse(accountId int) (inUse bool, err error) { ctx, cancel := context.WithTimeout(store.shutdownContext, store.timeout) defer cancel() + tx, err := store.db.BeginTx(ctx, nil) + if err != nil { + return false, err + } + defer tx.Rollback() + // check server exists query := ` SELECT id @@ -19,7 +26,7 @@ func (store *Storage) AcmeAccountInUse(accountId int) (inUse bool, err error) { WHERE id = $1 ` - row := store.db.QueryRowContext(ctx, query, accountId) + row := tx.QueryRowContext(ctx, query, accountId) _discardVar := -2 err = row.Scan(&_discardVar) if err != nil { @@ -34,73 +41,55 @@ func (store *Storage) AcmeAccountInUse(accountId int) (inUse bool, err error) { WHERE acme_account_id = $1 ` - row = store.db.QueryRowContext(ctx, query, accountId) + row = tx.QueryRowContext(ctx, query, accountId) err = row.Scan(&_discardVar) if !errors.Is(err, sql.ErrNoRows) { return true, err } + err = tx.Commit() + if err != nil { + return false, err + } + return false, nil } // DeleteAccount deletes an account from the database func (store *Storage) DeleteAcmeAccount(id int) error { - ctx, cancel := context.WithTimeout(store.shutdownContext, store.timeout) - defer cancel() - - tx, err := store.db.BeginTx(ctx, nil) + // check that delete is safe + inUse, err := store.AcmeAccountInUse(id) if err != nil { return err } - defer tx.Rollback() - - // check acct exists - // if scan in succeeds, key exists - query := ` - SELECT id - FROM acme_accounts - WHERE id = $1 - ` - - row := tx.QueryRowContext(ctx, query, id) - temp := -2 - row.Scan(&temp) - if temp == -2 { - return sql.ErrNoRows - } - - // check not in use in certs - // if scan in succeeds, record exists in certificates - query = ` - SELECT id - FROM certificates - WHERE acme_account_id = $1 - ` - - row = tx.QueryRowContext(ctx, query, id) - temp = -2 - row.Scan(&temp) - if temp != -2 { + if inUse { return ErrInUse } + ctx, cancel := context.WithTimeout(store.shutdownContext, store.timeout) + defer cancel() + // delete - query = ` + query := ` DELETE FROM acme_accounts WHERE id = $1 ` - _, err = tx.ExecContext(ctx, query, id) + res, err := store.db.ExecContext(ctx, query, id) if err != nil { return err } - err = tx.Commit() + // verify update actually happened + rowsAffected, err := res.RowsAffected() if err != nil { return err } + if rowsAffected != 1 { + return errors.Join(fmt.Errorf("expected 1 row update, but got '%d'", rowsAffected), ErrWrongUpdateRowCount) + } return nil } diff --git a/pkg/storage/acme_servers_delete.go b/pkg/storage/acme_servers_delete.go index cf416d89..e1a8e62a 100644 --- a/pkg/storage/acme_servers_delete.go +++ b/pkg/storage/acme_servers_delete.go @@ -12,6 +12,12 @@ func (store *Storage) ServerInUse(serverId int) (inUse bool, err error) { ctx, cancel := context.WithTimeout(store.shutdownContext, store.timeout) defer cancel() + tx, err := store.db.BeginTx(ctx, nil) + if err != nil { + return false, err + } + defer tx.Rollback() + // check server exists query := ` SELECT id @@ -19,7 +25,7 @@ func (store *Storage) ServerInUse(serverId int) (inUse bool, err error) { WHERE id = $1 ` - row := store.db.QueryRowContext(ctx, query, serverId) + row := tx.QueryRowContext(ctx, query, serverId) _discardVar := -2 err = row.Scan(&_discardVar) if err != nil { @@ -34,20 +40,22 @@ func (store *Storage) ServerInUse(serverId int) (inUse bool, err error) { WHERE acme_server_id = $1 ` - row = store.db.QueryRowContext(ctx, query, serverId) + row = tx.QueryRowContext(ctx, query, serverId) err = row.Scan(&_discardVar) if !errors.Is(err, sql.ErrNoRows) { return true, err } + err = tx.Commit() + if err != nil { + return false, err + } + return false, nil } // DeleteServer deletes an acme server from the database func (store *Storage) DeleteServer(serverId int) error { - ctx, cancel := context.WithTimeout(store.shutdownContext, store.timeout) - defer cancel() - // check that delete is safe inUse, err := store.ServerInUse(serverId) if err != nil { @@ -57,6 +65,9 @@ func (store *Storage) DeleteServer(serverId int) error { return ErrInUse } + ctx, cancel := context.WithTimeout(store.shutdownContext, store.timeout) + defer cancel() + // delete query := ` DELETE FROM diff --git a/pkg/storage/certificates_delete.go b/pkg/storage/certificates_delete.go index 32e81ab4..782105aa 100644 --- a/pkg/storage/certificates_delete.go +++ b/pkg/storage/certificates_delete.go @@ -2,11 +2,14 @@ package storage import ( "context" - "database/sql" + "errors" + "fmt" ) // DeleteCert deletes a cert from the database func (store *Storage) DeleteCert(id int) (err error) { + // Note: There is no CertInUse func, so this transaction lives here instead + ctx, cancel := context.WithTimeout(store.shutdownContext, store.timeout) defer cancel() @@ -25,10 +28,11 @@ func (store *Storage) DeleteCert(id int) (err error) { ` row := tx.QueryRowContext(ctx, query, id) - temp := -2 - row.Scan(&temp) - if temp == -2 { - return sql.ErrNoRows + _discardVar := -2 + err = row.Scan(&_discardVar) + if err != nil { + // sql.ErrNoRows included here + return err } // delete @@ -39,10 +43,19 @@ func (store *Storage) DeleteCert(id int) (err error) { id = $1 ` - _, err = tx.ExecContext(ctx, query, id) + res, err := tx.ExecContext(ctx, query, id) + if err != nil { + return err + } + + // verify update actually happened + rowsAffected, err := res.RowsAffected() if err != nil { return err } + if rowsAffected != 1 { + return errors.Join(fmt.Errorf("expected 1 row update, but got '%d'", rowsAffected), ErrWrongUpdateRowCount) + } err = tx.Commit() if err != nil { diff --git a/pkg/storage/certificates_delete_test.go b/pkg/storage/certificates_delete_test.go new file mode 100644 index 00000000..11940043 --- /dev/null +++ b/pkg/storage/certificates_delete_test.go @@ -0,0 +1,47 @@ +package storage_test + +import ( + "certwarden-backend/pkg/domain/certificates" + "certwarden-backend/pkg/test_helpers" + "database/sql" + "fmt" + "testing" +) + +func TestDeleteCert(t *testing.T) { + testCases := []struct { + id int + expectedDelErr error + expectedGetResult certificates.Certificate + expectedGetErr error + }{ + {-12, sql.ErrNoRows, certificates.Certificate{}, sql.ErrNoRows}, // non-existent + {2, sql.ErrNoRows, certificates.Certificate{}, sql.ErrNoRows}, // non-existent + {18, nil, certificates.Certificate{}, sql.ErrNoRows}, // not in use, gets deleted (Maybe TODO: Prevent delete from app to server's ssl cert) + {30, nil, certificates.Certificate{}, sql.ErrNoRows}, // not in use, gets deleted + {32, nil, certificates.Certificate{}, sql.ErrNoRows}, // not in use, gets deleted + {35, nil, certificates.Certificate{}, sql.ErrNoRows}, // not in use, gets deleted + } + + // create testing service + storage, err := openStorageWithTestData(t, "deletecert") + if err != nil { + t.Fatal(err) + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("id: %d", tc.id), func(t *testing.T) { + err := storage.DeleteCert(tc.id) + if !test_helpers.ErrorsIs(err, tc.expectedDelErr) { + t.Errorf("expected delete error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedDelErr), test_helpers.ErrorToVal(err)) + } + + cert, err := storage.GetOneCertById(tc.id) + if !test_helpers.ErrorsIs(err, tc.expectedGetErr) { + t.Errorf("expected get error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedGetErr), test_helpers.ErrorToVal(err)) + } + + CompareCertificate(t, cert, tc.expectedGetResult) + }) + } +} diff --git a/pkg/storage/keys_delete.go b/pkg/storage/keys_delete.go index 42f39cda..bcdb9a69 100644 --- a/pkg/storage/keys_delete.go +++ b/pkg/storage/keys_delete.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "errors" + "fmt" ) // KeyInUse returns a bool if the specified key is in use, it returns @@ -16,6 +17,12 @@ func (store *Storage) KeyInUse(id int) (inUse bool, err error) { ctx, cancel := context.WithTimeout(store.shutdownContext, store.timeout) defer cancel() + tx, err := store.db.BeginTx(ctx, nil) + if err != nil { + return false, err + } + defer tx.Rollback() + // check key exists query := ` SELECT id @@ -23,7 +30,7 @@ func (store *Storage) KeyInUse(id int) (inUse bool, err error) { WHERE id = $1 ` - row := store.db.QueryRowContext(ctx, query, id) + row := tx.QueryRowContext(ctx, query, id) _discardVar := -2 err = row.Scan(&_discardVar) if err != nil { @@ -39,7 +46,7 @@ func (store *Storage) KeyInUse(id int) (inUse bool, err error) { WHERE private_key_id = $1 ` - row = store.db.QueryRowContext(ctx, query, id) + row = tx.QueryRowContext(ctx, query, id) err = row.Scan(&_discardVar) if !errors.Is(err, sql.ErrNoRows) { return true, err @@ -54,7 +61,7 @@ func (store *Storage) KeyInUse(id int) (inUse bool, err error) { WHERE private_key_id = $1 ` - row = store.db.QueryRowContext(ctx, query, id) + row = tx.QueryRowContext(ctx, query, id) err = row.Scan(&_discardVar) if !errors.Is(err, sql.ErrNoRows) { return true, err @@ -86,20 +93,22 @@ func (store *Storage) KeyInUse(id int) (inUse bool, err error) { finalized_key_id = $1 ` - row = store.db.QueryRowContext(ctx, query, id) + row = tx.QueryRowContext(ctx, query, id) err = row.Scan(&_discardVar) if !errors.Is(err, sql.ErrNoRows) { return true, err } + err = tx.Commit() + if err != nil { + return false, err + } + return false, nil } // DeleteKey deletes a private key from the database func (store *Storage) DeleteKey(id int) error { - ctx, cancel := context.WithTimeout(store.shutdownContext, store.timeout) - defer cancel() - // check that delete is safe inUse, err := store.KeyInUse(id) if err != nil { @@ -109,6 +118,9 @@ func (store *Storage) DeleteKey(id int) error { return ErrInUse } + ctx, cancel := context.WithTimeout(store.shutdownContext, store.timeout) + defer cancel() + // delete query := ` DELETE FROM @@ -117,10 +129,19 @@ func (store *Storage) DeleteKey(id int) error { id = $1 ` - _, err = store.db.ExecContext(ctx, query, id) + res, err := store.db.ExecContext(ctx, query, id) if err != nil { return err } + // verify update actually happened + rowsAffected, err := res.RowsAffected() + if err != nil { + return err + } + if rowsAffected != 1 { + return errors.Join(fmt.Errorf("expected 1 row update, but got '%d'", rowsAffected), ErrWrongUpdateRowCount) + } + return nil } diff --git a/pkg/storage/keys_delete_test.go b/pkg/storage/keys_delete_test.go index f37cd449..fc5ad8ac 100644 --- a/pkg/storage/keys_delete_test.go +++ b/pkg/storage/keys_delete_test.go @@ -33,7 +33,7 @@ func TestKeyInUse(t *testing.T) { } for _, tc := range testCases { - t.Run(fmt.Sprintf("key id: %d", tc.keyID), func(t *testing.T) { + t.Run(fmt.Sprintf("id: %d", tc.keyID), func(t *testing.T) { inUse, err := storage.KeyInUse(tc.keyID) if !test_helpers.ErrorsIs(err, tc.expectedErr) { t.Errorf("expected error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedErr), test_helpers.ErrorToVal(err)) @@ -68,7 +68,7 @@ func TestDeleteKey(t *testing.T) { } for _, tc := range testCases { - t.Run(fmt.Sprintf("key id: %d", tc.keyID), func(t *testing.T) { + t.Run(fmt.Sprintf("id: %d", tc.keyID), func(t *testing.T) { err := storage.DeleteKey(tc.keyID) if !test_helpers.ErrorsIs(err, tc.expectedDelErr) { t.Errorf("expected delete error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedDelErr), test_helpers.ErrorToVal(err)) From 2037c61d644a9578d0fee566ae58d63173cb0076 Mon Sep 17 00:00:00 2001 From: "Greg T. Wallace" Date: Thu, 6 Aug 2026 21:20:45 -0400 Subject: [PATCH 05/16] add TestPostNewCert * update some get tests to wrong casing --- pkg/storage/accounts_get_test.go | 2 +- pkg/storage/acme_servers_get_test.go | 2 +- pkg/storage/certificates_get_test.go | 2 +- pkg/storage/certificates_post_test.go | 226 ++++++++++++++++++++++++++ pkg/storage/keys_get_test.go | 4 +- 5 files changed, 231 insertions(+), 5 deletions(-) create mode 100644 pkg/storage/certificates_post_test.go diff --git a/pkg/storage/accounts_get_test.go b/pkg/storage/accounts_get_test.go index a3818b23..71fe0308 100644 --- a/pkg/storage/accounts_get_test.go +++ b/pkg/storage/accounts_get_test.go @@ -166,7 +166,7 @@ func TestGetOneAccountByName(t *testing.T) { }{ {"", sql.ErrNoRows, acme_accounts.Account{}}, {"fake-name", sql.ErrNoRows, acme_accounts.Account{}}, - {"LE_Staging_Account", nil, acmeAcct1}, + {"le_staging_account", nil, acmeAcct1}, // case is wrong {"LE_Production_Account", nil, acmeAcct2}, {"Google_Cloud_Staging2", nil, acmeAcct23}, } diff --git a/pkg/storage/acme_servers_get_test.go b/pkg/storage/acme_servers_get_test.go index 8811fc55..31ac483e 100644 --- a/pkg/storage/acme_servers_get_test.go +++ b/pkg/storage/acme_servers_get_test.go @@ -133,7 +133,7 @@ func TestGetOneServerByName(t *testing.T) { }{ {"fake-bad-name", sql.ErrNoRows, acme_servers.Server{}}, {"", sql.ErrNoRows, acme_servers.Server{}}, - {"Lets_Encrypt", nil, acmeServer0}, + {"lets_encrypt", nil, acmeServer0}, // case is wrong {"Lets_Encrypt_Staging", nil, acmeServer1}, {"Google_Prod", nil, acmeServer4}, } diff --git a/pkg/storage/certificates_get_test.go b/pkg/storage/certificates_get_test.go index ca803d2b..1088fd35 100644 --- a/pkg/storage/certificates_get_test.go +++ b/pkg/storage/certificates_get_test.go @@ -189,7 +189,7 @@ func TestGetOneCertByName(t *testing.T) { }{ {"fake-bad-name", sql.ErrNoRows, certificates.Certificate{}}, {"", sql.ErrNoRows, certificates.Certificate{}}, - {"serverdefault", nil, cert18}, + {"serverDEFault", nil, cert18}, // case is wrong {"test008.test.example.com", nil, cert26}, {"test008.test.example.com-p", nil, cert27}, } diff --git a/pkg/storage/certificates_post_test.go b/pkg/storage/certificates_post_test.go new file mode 100644 index 00000000..0cafca30 --- /dev/null +++ b/pkg/storage/certificates_post_test.go @@ -0,0 +1,226 @@ +package storage_test + +import ( + "certwarden-backend/pkg/domain/certificates" + "certwarden-backend/pkg/test_helpers" + "crypto/x509/pkix" + "database/sql" + "encoding/asn1" + "fmt" + "testing" + "time" +) + +// ApiKeyViaUrl bool `json:"-"` +// CreatedAt int `json:"-"` +// UpdatedAt int `json:"-"` + +func TestPostNewCert(t *testing.T) { + testCases := []struct { + newPayload certificates.NewPayload + expectedPostErr error + expectedNew certificates.Certificate + expectedGetErr error + }{ + { // valid insertion + certificates.NewPayload{ + Name: new("NewCertHere"), + Description: new("some cert ins"), + PrivateKeyID: new(58), + NewKeyAlgorithmValue: new("some-alg"), // should be ignored + AcmeAccountID: new(1), + Subject: new("some.example.com"), + SubjectAltNames: []string{"some1.example.com", "some2.example.com"}, + Organization: new("an org"), + OrganizationalUnit: new("an ou"), + Country: new("usa"), + State: new("Ca"), + City: new("los santos"), + CSRExtraExtensions: []certificates.CertExtensionJSON{ + { + Description: "OCSP Must Staple", + OID: "1.3.6.1.5.5.7.1.24", + Critical: false, + ValueHexString: "3003020105", + }, + }, + PreferredRootCN: new("Root xyz"), + PostProcessingCommand: new("./run-me.py"), + PostProcessingEnvironment: []string{"a=123", "b=456"}, + PostProcessingClientAddress: new("endpoint.example.com"), + PostProcessingClientKeyB64: "an aes key", + Profile: new("test-prof"), + ApiKey: "12345fffff", + ApiKeyViaUrl: true, + CreatedAt: 770337479, + UpdatedAt: 770338000, + }, + nil, + certificates.Certificate{ + ID: 36, + Name: "NewCertHere", + Description: "some cert ins", + Key: key58, + Account: acmeAcct1, + Subject: "some.example.com", + SubjectAltNames: []string{"some1.example.com", "some2.example.com"}, + Organization: "an org", + OrganizationalUnit: "an ou", + Country: "usa", + State: "Ca", + City: "los santos", + CSRExtraExtensions: []certificates.CertExtension{ + { + Extension: pkix.Extension{ + Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24}, + Critical: false, + Value: []byte{0x30, 0x03, 0x02, 0x01, 0x05}, + }, + Description: "OCSP Must Staple", + }, + }, + PreferredRootCN: "Root xyz", + LastAccess: time.Unix(0, 0), + CreatedAt: time.Unix(770337479, 0), + UpdatedAt: time.Unix(770338000, 0), + ApiKey: "12345fffff", + ApiKeyNew: "", + ApiKeyViaUrl: true, + PostProcessingCommand: "./run-me.py", + PostProcessingEnvironment: []string{"a=123", "b=456"}, + PostProcessingClientAddress: "endpoint.example.com", + PostProcessingClientKeyB64: "an aes key", + Profile: "test-prof", + }, + nil, + }, + { // duplicate name (non-case sensitive) + certificates.NewPayload{ + Name: new("test008.TEST.example.com"), + Description: new("some cert ins"), + PrivateKeyID: new(58), + NewKeyAlgorithmValue: new("some-alg"), + AcmeAccountID: new(1), + Subject: new("some.example.com"), + SubjectAltNames: []string{}, + Organization: new("an org"), + OrganizationalUnit: new("an ou"), + Country: new("usa"), + State: new("Ca"), + City: new("los santos"), + CSRExtraExtensions: []certificates.CertExtensionJSON{}, + PreferredRootCN: new("Root xyz"), + PostProcessingCommand: new("./run-me.py"), + PostProcessingEnvironment: []string{}, + PostProcessingClientAddress: new("endpoint.example.com"), + PostProcessingClientKeyB64: "an aes key", + Profile: new("test-prof"), + ApiKey: "12345fffff", + ApiKeyViaUrl: true, + CreatedAt: 770337479, + UpdatedAt: 770338000, + }, + test_helpers.MakeTestErrorStringComp("UNIQUE constraint failed: certificates.name"), + certificates.Certificate{}, + sql.ErrNoRows, + }, + { // incomplete payload 1 + certificates.NewPayload{ + Name: new("NewCertHerexxxxy"), + Description: new("some cert ins"), + PrivateKeyID: new(58), + NewKeyAlgorithmValue: new("some-alg"), // should be ignored + // AcmeAccountID: new(1), + Subject: new("some.example.com"), + SubjectAltNames: []string{"some1.example.com", "some2.example.com"}, + Organization: new("an org"), + OrganizationalUnit: new("an ou"), + Country: new("usa"), + State: new("Ca"), + City: new("los santos"), + CSRExtraExtensions: []certificates.CertExtensionJSON{ + { + Description: "OCSP Must Staple", + OID: "1.3.6.1.5.5.7.1.24", + Critical: false, + ValueHexString: "3003020105", + }, + }, + PreferredRootCN: new("Root xyz"), + PostProcessingCommand: new("./run-me.py"), + PostProcessingEnvironment: []string{"a=123", "b=456"}, + PostProcessingClientAddress: new("endpoint.example.com"), + PostProcessingClientKeyB64: "an aes key", + Profile: new("test-prof"), + ApiKey: "12345fffff", + ApiKeyViaUrl: true, + CreatedAt: 770337479, + UpdatedAt: 770338000, + }, + test_helpers.MakeTestErrorStringComp("NOT NULL constraint failed: certificates.acme_account_id"), + certificates.Certificate{}, + sql.ErrNoRows, + }, + { // incomplete payload 2 + certificates.NewPayload{ + Name: new("NewCertHerexxxxyyy"), + Description: new("some cert ins"), + PrivateKeyID: new(58), + NewKeyAlgorithmValue: new("some-alg"), // should be ignored + AcmeAccountID: new(1), + // Subject: new("some.example.com"), + SubjectAltNames: []string{"some1.example.com", "some2.example.com"}, + Organization: new("an org"), + OrganizationalUnit: new("an ou"), + Country: new("usa"), + State: new("Ca"), + City: new("los santos"), + CSRExtraExtensions: []certificates.CertExtensionJSON{ + { + Description: "OCSP Must Staple", + OID: "1.3.6.1.5.5.7.1.24", + Critical: false, + ValueHexString: "3003020105", + }, + }, + PreferredRootCN: new("Root xyz"), + PostProcessingCommand: new("./run-me.py"), + PostProcessingEnvironment: []string{"a=123", "b=456"}, + PostProcessingClientAddress: new("endpoint.example.com"), + PostProcessingClientKeyB64: "an aes key", + Profile: new("test-prof"), + ApiKey: "12345fffff", + ApiKeyViaUrl: true, + CreatedAt: 770337479, + UpdatedAt: 770338000, + }, + test_helpers.MakeTestErrorStringComp("NOT NULL constraint failed: certificates.subject"), + certificates.Certificate{}, + sql.ErrNoRows, + }, + } + + // create testing service + storage, err := openStorageWithTestData(t, "postnewcert") + if err != nil { + t.Fatal(err) + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("post name: %s", test_helpers.StringPointerToVal(tc.newPayload.Name)), func(t *testing.T) { + record, err := storage.PostNewCert(tc.newPayload) + if !test_helpers.ErrorsIs(err, tc.expectedPostErr) { + t.Errorf("expected post error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedPostErr), test_helpers.ErrorToVal(err)) + } + + CompareCertificate(t, record, tc.expectedNew) + + record, err = storage.GetOneCertByName(record.Name) + if !test_helpers.ErrorsIs(err, tc.expectedGetErr) { + t.Errorf("expected get error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedGetErr), test_helpers.ErrorToVal(err)) + } + + CompareCertificate(t, record, tc.expectedNew) + }) + } +} diff --git a/pkg/storage/keys_get_test.go b/pkg/storage/keys_get_test.go index 480992cb..a7e26067 100644 --- a/pkg/storage/keys_get_test.go +++ b/pkg/storage/keys_get_test.go @@ -348,8 +348,8 @@ func TestGetOneKeyByName(t *testing.T) { }{ {"", sql.ErrNoRows, private_keys.Key{}}, {"fake-bad-name", sql.ErrNoRows, private_keys.Key{}}, - {"certwarden", nil, key31}, - {"_Another_Test_Acct_LE_Staging", nil, key63}, + {"cerTWarden", nil, key31}, + {"_Another_TEST_Acct_le_Staging", nil, key63}, // case is wrong } // create testing service From b209a62afde5c9bd9f4496fc7e9233dbc353dc75 Mon Sep 17 00:00:00 2001 From: "Greg T. Wallace" Date: Thu, 6 Aug 2026 21:20:46 -0400 Subject: [PATCH 06/16] rename helpers to make _test consistent --- go.mod | 2 +- pkg/domain/download/out_pfx_test.go | 14 ++++---- pkg/domain/download/service_mock_test.go | 10 +++--- pkg/{test_helpers => helpers_test}/err_is.go | 2 +- .../err_is_test.go | 32 ++++++++--------- .../log_values.go | 2 +- .../log_values_test.go | 22 ++++++------ pkg/storage/accounts_delete_test.go | 14 ++++---- pkg/storage/accounts_get_test.go | 10 +++--- pkg/storage/accounts_post_test.go | 18 +++++----- pkg/storage/accounts_put_test.go | 26 +++++++------- pkg/storage/acme_servers_delete_test.go | 14 ++++---- pkg/storage/acme_servers_get_test.go | 10 +++--- pkg/storage/acme_servers_post_test.go | 16 ++++----- pkg/storage/acme_servers_put_test.go | 10 +++--- pkg/storage/certificates_delete_test.go | 10 +++--- pkg/storage/certificates_get_test.go | 10 +++--- pkg/storage/certificates_post_test.go | 18 +++++----- pkg/storage/keys_delete_test.go | 14 ++++---- pkg/storage/keys_get_test.go | 10 +++--- pkg/storage/keys_post_test.go | 16 ++++----- pkg/storage/keys_put_test.go | 34 +++++++++---------- pkg/storage/service_mock_test.go | 4 +-- 23 files changed, 159 insertions(+), 159 deletions(-) rename pkg/{test_helpers => helpers_test}/err_is.go (98%) rename pkg/{test_helpers => helpers_test}/err_is_test.go (62%) rename pkg/{test_helpers => helpers_test}/log_values.go (97%) rename pkg/{test_helpers => helpers_test}/log_values_test.go (74%) diff --git a/go.mod b/go.mod index 6ec4dde2..11a018b1 100644 --- a/go.mod +++ b/go.mod @@ -291,6 +291,6 @@ replace certwarden-backend/pkg/storage => /pkg/storage replace certwarden-backend/pkg/storage/sqlite3 => /pkg/storage/sqlite3 -replace certwarden-backend/pkg/test_helpers => /pkg/test_helpers +replace certwarden-backend/pkg/helpers_test => /pkg/helpers_test replace certwarden-backend/pkg/validation => /pkg/validation diff --git a/pkg/domain/download/out_pfx_test.go b/pkg/domain/download/out_pfx_test.go index 131c659f..f5efe6c3 100644 --- a/pkg/domain/download/out_pfx_test.go +++ b/pkg/domain/download/out_pfx_test.go @@ -2,8 +2,8 @@ package download_test import ( "certwarden-backend/pkg/domain/download" + "certwarden-backend/pkg/helpers_test" "certwarden-backend/pkg/output" - "certwarden-backend/pkg/test_helpers" "context" "errors" "net/http" @@ -42,17 +42,17 @@ func onePfxTest(t *testing.T, handler func(w http.ResponseWriter, r *http.Reques jsonErr := handler(w, r) if !errors.Is(jsonErr, expectedJsonErr) { - t.Errorf("%s: name '%s' with header api-key '%s' and url api-key '%s' returned error '%s' but expected '%s'", test_helpers.GetFunctionName(handler), - certName, test_helpers.StringPointerToVal(apiKeyHeader), test_helpers.StringPointerToVal(apiKeyURL), jsonErr, expectedJsonErr) + t.Errorf("%s: name '%s' with header api-key '%s' and url api-key '%s' returned error '%s' but expected '%s'", helpers_test.GetFunctionName(handler), + certName, helpers_test.StringPointerToVal(apiKeyHeader), helpers_test.StringPointerToVal(apiKeyURL), jsonErr, expectedJsonErr) } body := w.Body.String() if jsonErr != nil && body != "" { - t.Errorf("%s: name '%s' with header api-key '%s' and url api-key '%s' returned body data but expected none", test_helpers.GetFunctionName(handler), - certName, test_helpers.StringPointerToVal(apiKeyHeader), test_helpers.StringPointerToVal(apiKeyURL)) + t.Errorf("%s: name '%s' with header api-key '%s' and url api-key '%s' returned body data but expected none", helpers_test.GetFunctionName(handler), + certName, helpers_test.StringPointerToVal(apiKeyHeader), helpers_test.StringPointerToVal(apiKeyURL)) } else if jsonErr == nil && body == "" { - t.Errorf("%s: name '%s' with header api-key '%s' and url api-key '%s' returned empty body but data was expected", test_helpers.GetFunctionName(handler), - certName, test_helpers.StringPointerToVal(apiKeyHeader), test_helpers.StringPointerToVal(apiKeyURL)) + t.Errorf("%s: name '%s' with header api-key '%s' and url api-key '%s' returned empty body but data was expected", helpers_test.GetFunctionName(handler), + certName, helpers_test.StringPointerToVal(apiKeyHeader), helpers_test.StringPointerToVal(apiKeyURL)) } } diff --git a/pkg/domain/download/service_mock_test.go b/pkg/domain/download/service_mock_test.go index 95c93c4a..e9070a4a 100644 --- a/pkg/domain/download/service_mock_test.go +++ b/pkg/domain/download/service_mock_test.go @@ -5,8 +5,8 @@ import ( "certwarden-backend/pkg/domain/download" "certwarden-backend/pkg/domain/orders" "certwarden-backend/pkg/domain/private_keys" + "certwarden-backend/pkg/helpers_test" "certwarden-backend/pkg/output" - "certwarden-backend/pkg/test_helpers" "context" "database/sql" "errors" @@ -490,13 +490,13 @@ func oneTest(t *testing.T, handler func(w http.ResponseWriter, r *http.Request) jsonErr := handler(w, r) if !errors.Is(jsonErr, expectedJsonErr) { - t.Errorf("%s: name '%s' with header api-key '%s' and url api-key '%s' returned error '%s' but expected '%s'", test_helpers.GetFunctionName(handler), - certName, test_helpers.StringPointerToVal(apiKeyHeader), test_helpers.StringPointerToVal(apiKeyURL), jsonErr, expectedJsonErr) + t.Errorf("%s: name '%s' with header api-key '%s' and url api-key '%s' returned error '%s' but expected '%s'", helpers_test.GetFunctionName(handler), + certName, helpers_test.StringPointerToVal(apiKeyHeader), helpers_test.StringPointerToVal(apiKeyURL), jsonErr, expectedJsonErr) } body := w.Body.String() if body != expectedBody { - t.Errorf("%s: name '%s' with header api-key '%s' and url api-key '%s' returned body '%s' but expected body '%s'", test_helpers.GetFunctionName(handler), - certName, test_helpers.StringPointerToVal(apiKeyHeader), test_helpers.StringPointerToVal(apiKeyURL), body, expectedBody) + t.Errorf("%s: name '%s' with header api-key '%s' and url api-key '%s' returned body '%s' but expected body '%s'", helpers_test.GetFunctionName(handler), + certName, helpers_test.StringPointerToVal(apiKeyHeader), helpers_test.StringPointerToVal(apiKeyURL), body, expectedBody) } } diff --git a/pkg/test_helpers/err_is.go b/pkg/helpers_test/err_is.go similarity index 98% rename from pkg/test_helpers/err_is.go rename to pkg/helpers_test/err_is.go index d05be456..e1b49506 100644 --- a/pkg/test_helpers/err_is.go +++ b/pkg/helpers_test/err_is.go @@ -1,4 +1,4 @@ -package test_helpers +package helpers_test import ( "errors" diff --git a/pkg/test_helpers/err_is_test.go b/pkg/helpers_test/err_is_test.go similarity index 62% rename from pkg/test_helpers/err_is_test.go rename to pkg/helpers_test/err_is_test.go index e657f209..48b146fc 100644 --- a/pkg/test_helpers/err_is_test.go +++ b/pkg/helpers_test/err_is_test.go @@ -1,8 +1,8 @@ -package test_helpers_test +package helpers_test_test import ( "certwarden-backend/pkg/acme" - "certwarden-backend/pkg/test_helpers" + "certwarden-backend/pkg/helpers_test" "database/sql" "errors" "fmt" @@ -22,11 +22,11 @@ func TestErrorsIs(t *testing.T) { }, { err: nil, - target: test_helpers.MakeTestErrorStringComp("an error"), + target: helpers_test.MakeTestErrorStringComp("an error"), isTheSame: false, }, { - err: test_helpers.MakeTestErrorStringComp("an error"), + err: helpers_test.MakeTestErrorStringComp("an error"), target: nil, isTheSame: false, }, @@ -42,61 +42,61 @@ func TestErrorsIs(t *testing.T) { }, { err: sql.ErrNoRows, - target: test_helpers.MakeTestErrorStringComp("an error"), + target: helpers_test.MakeTestErrorStringComp("an error"), isTheSame: false, }, { err: acme.ErrChallengeMalformed, - target: test_helpers.MakeTestErrorStringComp("an error"), + target: helpers_test.MakeTestErrorStringComp("an error"), isTheSame: false, }, { err: errors.New("some error"), - target: test_helpers.MakeTestErrorStringComp("uh oh, some error"), + target: helpers_test.MakeTestErrorStringComp("uh oh, some error"), isTheSame: false, }, { err: errors.New("some error"), - target: test_helpers.MakeTestErrorStringComp("some error, uh oh"), + target: helpers_test.MakeTestErrorStringComp("some error, uh oh"), isTheSame: false, }, { - err: test_helpers.MakeTestErrorStringComp("uh oh, some error"), + err: helpers_test.MakeTestErrorStringComp("uh oh, some error"), target: errors.New("some error"), isTheSame: false, }, { - err: test_helpers.MakeTestErrorStringComp("some error, uh oh"), + err: helpers_test.MakeTestErrorStringComp("some error, uh oh"), target: errors.New("some error"), isTheSame: false, }, { err: errors.New("uh oh, some error"), - target: test_helpers.MakeTestErrorStringComp("some error"), + target: helpers_test.MakeTestErrorStringComp("some error"), isTheSame: true, }, { err: errors.New("some error, uh oh"), - target: test_helpers.MakeTestErrorStringComp("some error"), + target: helpers_test.MakeTestErrorStringComp("some error"), isTheSame: true, }, { err: errors.New("uh oh, some error"), - target: test_helpers.MakeTestErrorStringComp("SOME errOR"), + target: helpers_test.MakeTestErrorStringComp("SOME errOR"), isTheSame: true, }, { err: errors.New("some error, uh oh"), - target: test_helpers.MakeTestErrorStringComp("SOME errOR"), + target: helpers_test.MakeTestErrorStringComp("SOME errOR"), isTheSame: true, }, } for i, tc := range testCases { t.Run(fmt.Sprintf("#%d:", i), func(t *testing.T) { - res := test_helpers.ErrorsIs(tc.err, tc.target) + res := helpers_test.ErrorsIs(tc.err, tc.target) if res != tc.isTheSame { - t.Errorf("err '%s' with target '%s' expected '%t' but got '%t'", test_helpers.ErrorToVal(tc.err), test_helpers.ErrorToVal(tc.target), tc.isTheSame, res) + t.Errorf("err '%s' with target '%s' expected '%t' but got '%t'", helpers_test.ErrorToVal(tc.err), helpers_test.ErrorToVal(tc.target), tc.isTheSame, res) } }) } diff --git a/pkg/test_helpers/log_values.go b/pkg/helpers_test/log_values.go similarity index 97% rename from pkg/test_helpers/log_values.go rename to pkg/helpers_test/log_values.go index d3b3f00a..8c1ba5e1 100644 --- a/pkg/test_helpers/log_values.go +++ b/pkg/helpers_test/log_values.go @@ -1,4 +1,4 @@ -package test_helpers +package helpers_test import ( "reflect" diff --git a/pkg/test_helpers/log_values_test.go b/pkg/helpers_test/log_values_test.go similarity index 74% rename from pkg/test_helpers/log_values_test.go rename to pkg/helpers_test/log_values_test.go index a4ec12ba..3de50402 100644 --- a/pkg/test_helpers/log_values_test.go +++ b/pkg/helpers_test/log_values_test.go @@ -1,7 +1,7 @@ -package test_helpers_test +package helpers_test_test import ( - "certwarden-backend/pkg/test_helpers" + "certwarden-backend/pkg/helpers_test" "errors" "testing" ) @@ -11,12 +11,12 @@ func someFunctionA() error { } func TestGetFunctionName(t *testing.T) { - fName := test_helpers.GetFunctionName(someFunctionA) + fName := helpers_test.GetFunctionName(someFunctionA) if fName != "someFunctionA" { t.Errorf("getfunctionname expected 'someFunctionA', but got '%s'", fName) } - fName = test_helpers.GetFunctionName(test_helpers.ErrorToVal) + fName = helpers_test.GetFunctionName(helpers_test.ErrorToVal) if fName != "ErrorToVal" { t.Errorf("getfunctionname expected 'ErrorToVal', but got '%s'", fName) } @@ -24,7 +24,7 @@ func TestGetFunctionName(t *testing.T) { // nil input var f *func() error f = nil - fName = test_helpers.GetFunctionName(f) + fName = helpers_test.GetFunctionName(f) if fName != "" { t.Errorf("getfunctionname expected '', but got '%s'", fName) } @@ -32,20 +32,20 @@ func TestGetFunctionName(t *testing.T) { func TestStringPointerToVal(t *testing.T) { s := "test-1" - result := test_helpers.StringPointerToVal(&s) + result := helpers_test.StringPointerToVal(&s) if result != "test-1" { t.Errorf("stringpointertoval expected 'test-1', but got '%s'", result) } s = "some other test, again" - result = test_helpers.StringPointerToVal(&s) + result = helpers_test.StringPointerToVal(&s) if result != "some other test, again" { t.Errorf("stringpointertoval expected 'some other test, again', but got '%s'", result) } // nil var ptr *string - result = test_helpers.StringPointerToVal(ptr) + result = helpers_test.StringPointerToVal(ptr) if result != "" { t.Errorf("stringpointertoval expected '', but got '%s'", result) } @@ -53,20 +53,20 @@ func TestStringPointerToVal(t *testing.T) { func TestErrorToVal(t *testing.T) { e := errors.New("test-2") - result := test_helpers.ErrorToVal(e) + result := helpers_test.ErrorToVal(e) if result != "test-2" { t.Errorf("errortoval expected 'test-2', but got '%s'", result) } e = errors.New("some other test 2, again") - result = test_helpers.ErrorToVal(e) + result = helpers_test.ErrorToVal(e) if result != "some other test 2, again" { t.Errorf("errortoval expected 'some other test 2, again', but got '%s'", result) } // nil e = nil - result = test_helpers.ErrorToVal(e) + result = helpers_test.ErrorToVal(e) if result != "" { t.Errorf("errortoval expected '', but got '%s'", result) } diff --git a/pkg/storage/accounts_delete_test.go b/pkg/storage/accounts_delete_test.go index bb24985a..916c3233 100644 --- a/pkg/storage/accounts_delete_test.go +++ b/pkg/storage/accounts_delete_test.go @@ -2,8 +2,8 @@ package storage_test import ( "certwarden-backend/pkg/domain/acme_accounts" + "certwarden-backend/pkg/helpers_test" "certwarden-backend/pkg/storage" - "certwarden-backend/pkg/test_helpers" "database/sql" "fmt" "testing" @@ -37,8 +37,8 @@ func TestAcmeAccountInUse(t *testing.T) { for _, tc := range testCases { t.Run(fmt.Sprintf("id: %d", tc.acctID), func(t *testing.T) { inUse, err := storage.AcmeAccountInUse(tc.acctID) - if !test_helpers.ErrorsIs(err, tc.expectedErr) { - t.Errorf("expected error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedErr) { + t.Errorf("expected error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedErr), helpers_test.ErrorToVal(err)) } if inUse != tc.expectedInUse { @@ -73,13 +73,13 @@ func TestDeleteAcmeAccount(t *testing.T) { for _, tc := range testCases { t.Run(fmt.Sprintf("id: %d", tc.acctID), func(t *testing.T) { err := storage.DeleteAcmeAccount(tc.acctID) - if !test_helpers.ErrorsIs(err, tc.expectedDelErr) { - t.Errorf("expected delete error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedDelErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedDelErr) { + t.Errorf("expected delete error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedDelErr), helpers_test.ErrorToVal(err)) } acct, err := storage.GetOneAcmeAccountById(tc.acctID) - if !test_helpers.ErrorsIs(err, tc.expectedGetErr) { - t.Errorf("expected get error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedGetErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedGetErr) { + t.Errorf("expected get error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedGetErr), helpers_test.ErrorToVal(err)) } CompareAcmeAccount(t, acct, tc.expectedGetResult) diff --git a/pkg/storage/accounts_get_test.go b/pkg/storage/accounts_get_test.go index 71fe0308..8372c9fa 100644 --- a/pkg/storage/accounts_get_test.go +++ b/pkg/storage/accounts_get_test.go @@ -2,8 +2,8 @@ package storage_test import ( "certwarden-backend/pkg/domain/acme_accounts" + "certwarden-backend/pkg/helpers_test" "certwarden-backend/pkg/pagination_sort" - "certwarden-backend/pkg/test_helpers" "database/sql" "fmt" "testing" @@ -149,8 +149,8 @@ func TestGetOneAccountById(t *testing.T) { for i, tc := range testCases { t.Run(fmt.Sprintf("#%d (id: %d)", i, tc.id), func(t *testing.T) { acct, err := storage.GetOneAcmeAccountById(tc.id) - if !test_helpers.ErrorsIs(err, tc.expectedErr) { - t.Errorf("expected error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedErr) { + t.Errorf("expected error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedErr), helpers_test.ErrorToVal(err)) } CompareAcmeAccount(t, acct, tc.expectedAcct) @@ -180,8 +180,8 @@ func TestGetOneAccountByName(t *testing.T) { for i, tc := range testCases { t.Run(fmt.Sprintf("#%d (name: %s)", i, tc.name), func(t *testing.T) { acct, err := storage.GetOneAcmeAccountByName(tc.name) - if !test_helpers.ErrorsIs(err, tc.expectedErr) { - t.Errorf("expected error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedErr) { + t.Errorf("expected error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedErr), helpers_test.ErrorToVal(err)) } CompareAcmeAccount(t, acct, tc.expectedAcct) diff --git a/pkg/storage/accounts_post_test.go b/pkg/storage/accounts_post_test.go index 65b64dd8..b758ec25 100644 --- a/pkg/storage/accounts_post_test.go +++ b/pkg/storage/accounts_post_test.go @@ -2,7 +2,7 @@ package storage_test import ( "certwarden-backend/pkg/domain/acme_accounts" - "certwarden-backend/pkg/test_helpers" + "certwarden-backend/pkg/helpers_test" "database/sql" "fmt" "testing" @@ -58,7 +58,7 @@ func TestPostNewAcmeAccount(t *testing.T) { UpdatedAt: 1888838000, Kid: "https://fake.example.com/123456", }, - test_helpers.MakeTestErrorStringComp("UNIQUE constraint failed"), + helpers_test.MakeTestErrorStringComp("UNIQUE constraint failed"), acme_accounts.Account{}, sql.ErrNoRows, }, @@ -75,7 +75,7 @@ func TestPostNewAcmeAccount(t *testing.T) { UpdatedAt: 1888838000, Kid: "https://fake.example.com/123456", }, - test_helpers.MakeTestErrorStringComp("NOT NULL constraint failed"), + helpers_test.MakeTestErrorStringComp("NOT NULL constraint failed"), acme_accounts.Account{}, sql.ErrNoRows, }, @@ -92,7 +92,7 @@ func TestPostNewAcmeAccount(t *testing.T) { UpdatedAt: 1888838000, Kid: "https://fake.example.com/123456", }, - test_helpers.MakeTestErrorStringComp("NOT NULL constraint failed"), + helpers_test.MakeTestErrorStringComp("NOT NULL constraint failed"), acme_accounts.Account{}, sql.ErrNoRows, }, @@ -105,17 +105,17 @@ func TestPostNewAcmeAccount(t *testing.T) { } for _, tc := range testCases { - t.Run(fmt.Sprintf("post name: %s", test_helpers.StringPointerToVal(tc.newPayload.Name)), func(t *testing.T) { + t.Run(fmt.Sprintf("post name: %s", helpers_test.StringPointerToVal(tc.newPayload.Name)), func(t *testing.T) { acct, err := storage.PostNewAcmeAccount(tc.newPayload) - if !test_helpers.ErrorsIs(err, tc.expectedPostErr) { - t.Errorf("expected post error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedPostErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedPostErr) { + t.Errorf("expected post error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedPostErr), helpers_test.ErrorToVal(err)) } CompareAcmeAccount(t, acct, tc.expectedNew) acct, err = storage.GetOneAcmeAccountByName(acct.Name) - if !test_helpers.ErrorsIs(err, tc.expectedGetErr) { - t.Errorf("expected get error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedGetErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedGetErr) { + t.Errorf("expected get error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedGetErr), helpers_test.ErrorToVal(err)) } CompareAcmeAccount(t, acct, tc.expectedNew) diff --git a/pkg/storage/accounts_put_test.go b/pkg/storage/accounts_put_test.go index df3ef408..371d3048 100644 --- a/pkg/storage/accounts_put_test.go +++ b/pkg/storage/accounts_put_test.go @@ -2,8 +2,8 @@ package storage_test import ( "certwarden-backend/pkg/domain/acme_accounts" + "certwarden-backend/pkg/helpers_test" "certwarden-backend/pkg/storage" - "certwarden-backend/pkg/test_helpers" "database/sql" "fmt" "testing" @@ -154,7 +154,7 @@ func TestPutAcmeAccountUpdate(t *testing.T) { UpdatedAt: time.Unix(107800777, 0), }, acme_accounts.Account{}, - test_helpers.MakeTestErrorStringComp("UNIQUE constraint failed"), + helpers_test.MakeTestErrorStringComp("UNIQUE constraint failed"), 16, acmeAcct16, nil, @@ -246,15 +246,15 @@ func TestPutAcmeAccountUpdate(t *testing.T) { for i, tc := range testCases { t.Run(fmt.Sprintf("#%d (id: %d)", i, tc.payload.ID), func(t *testing.T) { acct, err := storage.PutAcmeAccountUpdate(tc.payload) - if !test_helpers.ErrorsIs(err, tc.expectedPutErr) { - t.Errorf("expected put error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedPutErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedPutErr) { + t.Errorf("expected put error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedPutErr), helpers_test.ErrorToVal(err)) } CompareAcmeAccount(t, acct, tc.expectedPutResult) acct, err = storage.GetOneAcmeAccountById(tc.getId) - if !test_helpers.ErrorsIs(err, tc.expectedGetErr) { - t.Errorf("expected get error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedGetErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedGetErr) { + t.Errorf("expected get error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedGetErr), helpers_test.ErrorToVal(err)) } CompareAcmeAccount(t, acct, tc.expectedGetResult) @@ -336,7 +336,7 @@ func TestPutAcmeAccountNewKey(t *testing.T) { UpdatedAt: time.Unix(1088000751, 0), }, acme_accounts.Account{}, - test_helpers.MakeTestErrorStringComp("FOREIGN KEY constraint failed"), + helpers_test.MakeTestErrorStringComp("FOREIGN KEY constraint failed"), 1, acmeAcct1, nil, @@ -348,7 +348,7 @@ func TestPutAcmeAccountNewKey(t *testing.T) { UpdatedAt: time.Unix(1088000752, 0), }, acme_accounts.Account{}, - test_helpers.MakeTestErrorStringComp("FOREIGN KEY constraint failed"), + helpers_test.MakeTestErrorStringComp("FOREIGN KEY constraint failed"), 1, acmeAcct1, nil, @@ -360,7 +360,7 @@ func TestPutAcmeAccountNewKey(t *testing.T) { UpdatedAt: time.Unix(1088000753, 0), }, acme_accounts.Account{}, - test_helpers.MakeTestErrorStringComp("UNIQUE constraint failed"), + helpers_test.MakeTestErrorStringComp("UNIQUE constraint failed"), 23, acmeAcct23, nil, @@ -376,15 +376,15 @@ func TestPutAcmeAccountNewKey(t *testing.T) { for i, tc := range testCases { t.Run(fmt.Sprintf("#%d (id: %d)", i, tc.payload.ID), func(t *testing.T) { acct, err := storage.PutAcmeAccountNewKey(tc.payload) - if !test_helpers.ErrorsIs(err, tc.expectedPutErr) { - t.Errorf("expected put error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedPutErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedPutErr) { + t.Errorf("expected put error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedPutErr), helpers_test.ErrorToVal(err)) } CompareAcmeAccount(t, acct, tc.expectedPutResult) acct, err = storage.GetOneAcmeAccountById(tc.getId) - if !test_helpers.ErrorsIs(err, tc.expectedGetErr) { - t.Errorf("expected get error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedGetErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedGetErr) { + t.Errorf("expected get error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedGetErr), helpers_test.ErrorToVal(err)) } CompareAcmeAccount(t, acct, tc.expectedGetResult) diff --git a/pkg/storage/acme_servers_delete_test.go b/pkg/storage/acme_servers_delete_test.go index f0b94ac0..8fdba97c 100644 --- a/pkg/storage/acme_servers_delete_test.go +++ b/pkg/storage/acme_servers_delete_test.go @@ -2,8 +2,8 @@ package storage_test import ( "certwarden-backend/pkg/domain/acme_servers" + "certwarden-backend/pkg/helpers_test" "certwarden-backend/pkg/storage" - "certwarden-backend/pkg/test_helpers" "database/sql" "fmt" "testing" @@ -33,8 +33,8 @@ func TestServerInUse(t *testing.T) { for _, tc := range testCases { t.Run(fmt.Sprintf("server id: %d", tc.serverID), func(t *testing.T) { inUse, err := storage.ServerInUse(tc.serverID) - if !test_helpers.ErrorsIs(err, tc.expectedErr) { - t.Errorf("expected error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedErr) { + t.Errorf("expected error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedErr), helpers_test.ErrorToVal(err)) } if inUse != tc.expectedInUse { @@ -69,13 +69,13 @@ func TestDeleteServer(t *testing.T) { for _, tc := range testCases { t.Run(fmt.Sprintf("server id: %d", tc.serverID), func(t *testing.T) { err := storage.DeleteServer(tc.serverID) - if !test_helpers.ErrorsIs(err, tc.expectedDelErr) { - t.Errorf("expected delete error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedDelErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedDelErr) { + t.Errorf("expected delete error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedDelErr), helpers_test.ErrorToVal(err)) } server, err := storage.GetOneServerById(tc.serverID) - if !test_helpers.ErrorsIs(err, tc.expectedGetErr) { - t.Errorf("expected get error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedGetErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedGetErr) { + t.Errorf("expected get error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedGetErr), helpers_test.ErrorToVal(err)) } CompareAcmeServer(t, server, tc.expectedGetResult) diff --git a/pkg/storage/acme_servers_get_test.go b/pkg/storage/acme_servers_get_test.go index 31ac483e..1a83b09c 100644 --- a/pkg/storage/acme_servers_get_test.go +++ b/pkg/storage/acme_servers_get_test.go @@ -2,8 +2,8 @@ package storage_test import ( "certwarden-backend/pkg/domain/acme_servers" + "certwarden-backend/pkg/helpers_test" "certwarden-backend/pkg/pagination_sort" - "certwarden-backend/pkg/test_helpers" "database/sql" "fmt" "testing" @@ -116,8 +116,8 @@ func TestGetOneServerById(t *testing.T) { for i, tc := range testCases { t.Run(fmt.Sprintf("#%d (id: %d)", i, tc.id), func(t *testing.T) { serv, err := storage.GetOneServerById(tc.id) - if !test_helpers.ErrorsIs(err, tc.expectedErr) { - t.Errorf("expected error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedErr) { + t.Errorf("expected error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedErr), helpers_test.ErrorToVal(err)) } CompareAcmeServer(t, serv, tc.expectedServer) @@ -147,8 +147,8 @@ func TestGetOneServerByName(t *testing.T) { for i, tc := range testCases { t.Run(fmt.Sprintf("#%d (name: %s)", i, tc.name), func(t *testing.T) { serv, err := storage.GetOneServerByName(tc.name) - if !test_helpers.ErrorsIs(err, tc.expectedErr) { - t.Errorf("expected error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedErr) { + t.Errorf("expected error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedErr), helpers_test.ErrorToVal(err)) } CompareAcmeServer(t, serv, tc.expectedServer) diff --git a/pkg/storage/acme_servers_post_test.go b/pkg/storage/acme_servers_post_test.go index 9ad51a8b..2f5b7905 100644 --- a/pkg/storage/acme_servers_post_test.go +++ b/pkg/storage/acme_servers_post_test.go @@ -2,7 +2,7 @@ package storage_test import ( "certwarden-backend/pkg/domain/acme_servers" - "certwarden-backend/pkg/test_helpers" + "certwarden-backend/pkg/helpers_test" "database/sql" "fmt" "testing" @@ -46,7 +46,7 @@ func TestPostNewServer(t *testing.T) { CreatedAt: 1780337449, UpdatedAt: 1780338040, }, - test_helpers.MakeTestErrorStringComp("UNIQUE constraint failed"), + helpers_test.MakeTestErrorStringComp("UNIQUE constraint failed"), acme_servers.Server{}, sql.ErrNoRows, }, @@ -59,7 +59,7 @@ func TestPostNewServer(t *testing.T) { CreatedAt: 1880337449, UpdatedAt: 1880338040, }, - test_helpers.MakeTestErrorStringComp("NOT NULL constraint failed"), + helpers_test.MakeTestErrorStringComp("NOT NULL constraint failed"), acme_servers.Server{}, sql.ErrNoRows, }, @@ -72,17 +72,17 @@ func TestPostNewServer(t *testing.T) { } for _, tc := range testCases { - t.Run(fmt.Sprintf("post name: %s", test_helpers.StringPointerToVal(tc.newPayload.Name)), func(t *testing.T) { + t.Run(fmt.Sprintf("post name: %s", helpers_test.StringPointerToVal(tc.newPayload.Name)), func(t *testing.T) { server, err := storage.PostNewServer(tc.newPayload) - if !test_helpers.ErrorsIs(err, tc.expectedPostErr) { - t.Errorf("expected post error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedPostErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedPostErr) { + t.Errorf("expected post error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedPostErr), helpers_test.ErrorToVal(err)) } CompareAcmeServer(t, server, tc.expectedNew) server, err = storage.GetOneServerByName(server.Name) - if !test_helpers.ErrorsIs(err, tc.expectedGetErr) { - t.Errorf("expected get error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedGetErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedGetErr) { + t.Errorf("expected get error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedGetErr), helpers_test.ErrorToVal(err)) } CompareAcmeServer(t, server, tc.expectedNew) diff --git a/pkg/storage/acme_servers_put_test.go b/pkg/storage/acme_servers_put_test.go index 3d9f2f8a..c8ab65b0 100644 --- a/pkg/storage/acme_servers_put_test.go +++ b/pkg/storage/acme_servers_put_test.go @@ -2,8 +2,8 @@ package storage_test import ( "certwarden-backend/pkg/domain/acme_servers" + "certwarden-backend/pkg/helpers_test" "certwarden-backend/pkg/storage" - "certwarden-backend/pkg/test_helpers" "database/sql" "fmt" "testing" @@ -136,15 +136,15 @@ func TestPutServerUpdate(t *testing.T) { for i, tc := range testCases { t.Run(fmt.Sprintf("#%d (id: %d)", i, tc.payload.ID), func(t *testing.T) { server, err := storage.PutServerUpdate(tc.payload) - if !test_helpers.ErrorsIs(err, tc.expectedPutErr) { - t.Errorf("expected put error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedPutErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedPutErr) { + t.Errorf("expected put error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedPutErr), helpers_test.ErrorToVal(err)) } CompareAcmeServer(t, server, tc.expectedPutResult) server, err = storage.GetOneServerById(tc.getId) - if !test_helpers.ErrorsIs(err, tc.expectedGetErr) { - t.Errorf("expected get error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedGetErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedGetErr) { + t.Errorf("expected get error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedGetErr), helpers_test.ErrorToVal(err)) } CompareAcmeServer(t, server, tc.expectedGetResult) diff --git a/pkg/storage/certificates_delete_test.go b/pkg/storage/certificates_delete_test.go index 11940043..313b984c 100644 --- a/pkg/storage/certificates_delete_test.go +++ b/pkg/storage/certificates_delete_test.go @@ -2,7 +2,7 @@ package storage_test import ( "certwarden-backend/pkg/domain/certificates" - "certwarden-backend/pkg/test_helpers" + "certwarden-backend/pkg/helpers_test" "database/sql" "fmt" "testing" @@ -32,13 +32,13 @@ func TestDeleteCert(t *testing.T) { for _, tc := range testCases { t.Run(fmt.Sprintf("id: %d", tc.id), func(t *testing.T) { err := storage.DeleteCert(tc.id) - if !test_helpers.ErrorsIs(err, tc.expectedDelErr) { - t.Errorf("expected delete error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedDelErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedDelErr) { + t.Errorf("expected delete error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedDelErr), helpers_test.ErrorToVal(err)) } cert, err := storage.GetOneCertById(tc.id) - if !test_helpers.ErrorsIs(err, tc.expectedGetErr) { - t.Errorf("expected get error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedGetErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedGetErr) { + t.Errorf("expected get error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedGetErr), helpers_test.ErrorToVal(err)) } CompareCertificate(t, cert, tc.expectedGetResult) diff --git a/pkg/storage/certificates_get_test.go b/pkg/storage/certificates_get_test.go index 1088fd35..c9265346 100644 --- a/pkg/storage/certificates_get_test.go +++ b/pkg/storage/certificates_get_test.go @@ -2,8 +2,8 @@ package storage_test import ( "certwarden-backend/pkg/domain/certificates" + "certwarden-backend/pkg/helpers_test" "certwarden-backend/pkg/pagination_sort" - "certwarden-backend/pkg/test_helpers" "crypto/x509/pkix" "database/sql" "encoding/asn1" @@ -172,8 +172,8 @@ func TestGetOneCertById(t *testing.T) { for i, tc := range testCases { t.Run(fmt.Sprintf("#%d (id: %d)", i, tc.id), func(t *testing.T) { serv, err := storage.GetOneCertById(tc.id) - if !test_helpers.ErrorsIs(err, tc.expectedErr) { - t.Errorf("expected error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedErr) { + t.Errorf("expected error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedErr), helpers_test.ErrorToVal(err)) } CompareCertificate(t, serv, tc.expectedCert) @@ -203,8 +203,8 @@ func TestGetOneCertByName(t *testing.T) { for i, tc := range testCases { t.Run(fmt.Sprintf("#%d (name: %s)", i, tc.name), func(t *testing.T) { serv, err := storage.GetOneCertByName(tc.name) - if !test_helpers.ErrorsIs(err, tc.expectedErr) { - t.Errorf("expected error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedErr) { + t.Errorf("expected error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedErr), helpers_test.ErrorToVal(err)) } CompareCertificate(t, serv, tc.expectedCert) diff --git a/pkg/storage/certificates_post_test.go b/pkg/storage/certificates_post_test.go index 0cafca30..06f6c35b 100644 --- a/pkg/storage/certificates_post_test.go +++ b/pkg/storage/certificates_post_test.go @@ -2,7 +2,7 @@ package storage_test import ( "certwarden-backend/pkg/domain/certificates" - "certwarden-backend/pkg/test_helpers" + "certwarden-backend/pkg/helpers_test" "crypto/x509/pkix" "database/sql" "encoding/asn1" @@ -120,7 +120,7 @@ func TestPostNewCert(t *testing.T) { CreatedAt: 770337479, UpdatedAt: 770338000, }, - test_helpers.MakeTestErrorStringComp("UNIQUE constraint failed: certificates.name"), + helpers_test.MakeTestErrorStringComp("UNIQUE constraint failed: certificates.name"), certificates.Certificate{}, sql.ErrNoRows, }, @@ -157,7 +157,7 @@ func TestPostNewCert(t *testing.T) { CreatedAt: 770337479, UpdatedAt: 770338000, }, - test_helpers.MakeTestErrorStringComp("NOT NULL constraint failed: certificates.acme_account_id"), + helpers_test.MakeTestErrorStringComp("NOT NULL constraint failed: certificates.acme_account_id"), certificates.Certificate{}, sql.ErrNoRows, }, @@ -194,7 +194,7 @@ func TestPostNewCert(t *testing.T) { CreatedAt: 770337479, UpdatedAt: 770338000, }, - test_helpers.MakeTestErrorStringComp("NOT NULL constraint failed: certificates.subject"), + helpers_test.MakeTestErrorStringComp("NOT NULL constraint failed: certificates.subject"), certificates.Certificate{}, sql.ErrNoRows, }, @@ -207,17 +207,17 @@ func TestPostNewCert(t *testing.T) { } for _, tc := range testCases { - t.Run(fmt.Sprintf("post name: %s", test_helpers.StringPointerToVal(tc.newPayload.Name)), func(t *testing.T) { + t.Run(fmt.Sprintf("post name: %s", helpers_test.StringPointerToVal(tc.newPayload.Name)), func(t *testing.T) { record, err := storage.PostNewCert(tc.newPayload) - if !test_helpers.ErrorsIs(err, tc.expectedPostErr) { - t.Errorf("expected post error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedPostErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedPostErr) { + t.Errorf("expected post error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedPostErr), helpers_test.ErrorToVal(err)) } CompareCertificate(t, record, tc.expectedNew) record, err = storage.GetOneCertByName(record.Name) - if !test_helpers.ErrorsIs(err, tc.expectedGetErr) { - t.Errorf("expected get error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedGetErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedGetErr) { + t.Errorf("expected get error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedGetErr), helpers_test.ErrorToVal(err)) } CompareCertificate(t, record, tc.expectedNew) diff --git a/pkg/storage/keys_delete_test.go b/pkg/storage/keys_delete_test.go index fc5ad8ac..cbf89f96 100644 --- a/pkg/storage/keys_delete_test.go +++ b/pkg/storage/keys_delete_test.go @@ -2,8 +2,8 @@ package storage_test import ( "certwarden-backend/pkg/domain/private_keys" + "certwarden-backend/pkg/helpers_test" "certwarden-backend/pkg/storage" - "certwarden-backend/pkg/test_helpers" "database/sql" "fmt" "testing" @@ -35,8 +35,8 @@ func TestKeyInUse(t *testing.T) { for _, tc := range testCases { t.Run(fmt.Sprintf("id: %d", tc.keyID), func(t *testing.T) { inUse, err := storage.KeyInUse(tc.keyID) - if !test_helpers.ErrorsIs(err, tc.expectedErr) { - t.Errorf("expected error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedErr) { + t.Errorf("expected error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedErr), helpers_test.ErrorToVal(err)) } if inUse != tc.expectedInUse { @@ -70,13 +70,13 @@ func TestDeleteKey(t *testing.T) { for _, tc := range testCases { t.Run(fmt.Sprintf("id: %d", tc.keyID), func(t *testing.T) { err := storage.DeleteKey(tc.keyID) - if !test_helpers.ErrorsIs(err, tc.expectedDelErr) { - t.Errorf("expected delete error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedDelErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedDelErr) { + t.Errorf("expected delete error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedDelErr), helpers_test.ErrorToVal(err)) } key, err := storage.GetOneKeyById(tc.keyID) - if !test_helpers.ErrorsIs(err, tc.expectedGetErr) { - t.Errorf("expected get error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedGetErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedGetErr) { + t.Errorf("expected get error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedGetErr), helpers_test.ErrorToVal(err)) } CompareKey(t, key, tc.expectedGetResult) diff --git a/pkg/storage/keys_get_test.go b/pkg/storage/keys_get_test.go index a7e26067..739679fd 100644 --- a/pkg/storage/keys_get_test.go +++ b/pkg/storage/keys_get_test.go @@ -3,8 +3,8 @@ package storage_test import ( "certwarden-backend/pkg/domain/private_keys" "certwarden-backend/pkg/domain/private_keys/key_crypto" + "certwarden-backend/pkg/helpers_test" "certwarden-backend/pkg/pagination_sort" - "certwarden-backend/pkg/test_helpers" "database/sql" "fmt" "testing" @@ -331,8 +331,8 @@ func TestGetOneKeyById(t *testing.T) { for i, tc := range testCases { t.Run(fmt.Sprintf("#%d (id: %d)", i, tc.id), func(t *testing.T) { key, err := storage.GetOneKeyById(tc.id) - if !test_helpers.ErrorsIs(err, tc.expectedErr) { - t.Errorf("expected error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedErr) { + t.Errorf("expected error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedErr), helpers_test.ErrorToVal(err)) } CompareKey(t, key, tc.expectedKey) @@ -361,8 +361,8 @@ func TestGetOneKeyByName(t *testing.T) { for i, tc := range testCases { t.Run(fmt.Sprintf("#%d (name: %s)", i, tc.name), func(t *testing.T) { key, err := storage.GetOneKeyByName(tc.name) - if !test_helpers.ErrorsIs(err, tc.expectedErr) { - t.Errorf("expected error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedErr) { + t.Errorf("expected error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedErr), helpers_test.ErrorToVal(err)) } CompareKey(t, key, tc.expectedKey) diff --git a/pkg/storage/keys_post_test.go b/pkg/storage/keys_post_test.go index 0b685680..4ea0dcde 100644 --- a/pkg/storage/keys_post_test.go +++ b/pkg/storage/keys_post_test.go @@ -3,7 +3,7 @@ package storage_test import ( "certwarden-backend/pkg/domain/private_keys" "certwarden-backend/pkg/domain/private_keys/key_crypto" - "certwarden-backend/pkg/test_helpers" + "certwarden-backend/pkg/helpers_test" "database/sql" "fmt" "testing" @@ -58,7 +58,7 @@ func TestPostNewKey(t *testing.T) { CreatedAt: 1780336477, UpdatedAt: 1780337010, }, - test_helpers.MakeTestErrorStringComp("UNIQUE constraint failed"), + helpers_test.MakeTestErrorStringComp("UNIQUE constraint failed"), private_keys.Key{}, sql.ErrNoRows, }, @@ -73,7 +73,7 @@ func TestPostNewKey(t *testing.T) { CreatedAt: 1780336480, UpdatedAt: 1780337001, }, - test_helpers.MakeTestErrorStringComp("NOT NULL constraint failed"), + helpers_test.MakeTestErrorStringComp("NOT NULL constraint failed"), private_keys.Key{}, sql.ErrNoRows, }, @@ -86,17 +86,17 @@ func TestPostNewKey(t *testing.T) { } for _, tc := range testCases { - t.Run(fmt.Sprintf("post name: %s", test_helpers.StringPointerToVal(tc.newKeyPayload.Name)), func(t *testing.T) { + t.Run(fmt.Sprintf("post name: %s", helpers_test.StringPointerToVal(tc.newKeyPayload.Name)), func(t *testing.T) { key, err := storage.PostNewKey(tc.newKeyPayload) - if !test_helpers.ErrorsIs(err, tc.expectedPostErr) { - t.Errorf("expected post error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedPostErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedPostErr) { + t.Errorf("expected post error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedPostErr), helpers_test.ErrorToVal(err)) } CompareKey(t, key, tc.expectedNewKey) key, err = storage.GetOneKeyByName(key.Name) - if !test_helpers.ErrorsIs(err, tc.expectedGetErr) { - t.Errorf("expected get error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedGetErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedGetErr) { + t.Errorf("expected get error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedGetErr), helpers_test.ErrorToVal(err)) } CompareKey(t, key, tc.expectedNewKey) diff --git a/pkg/storage/keys_put_test.go b/pkg/storage/keys_put_test.go index 153ff627..e6ec3fba 100644 --- a/pkg/storage/keys_put_test.go +++ b/pkg/storage/keys_put_test.go @@ -3,8 +3,8 @@ package storage_test import ( "certwarden-backend/pkg/domain/private_keys" "certwarden-backend/pkg/domain/private_keys/key_crypto" + "certwarden-backend/pkg/helpers_test" "certwarden-backend/pkg/storage" - "certwarden-backend/pkg/test_helpers" "database/sql" "fmt" "testing" @@ -181,15 +181,15 @@ red-58 for i, tc := range testCases { t.Run(fmt.Sprintf("#%d (id: %d)", i, tc.payload.ID), func(t *testing.T) { key, err := storage.PutKeyUpdate(tc.payload) - if !test_helpers.ErrorsIs(err, tc.expectedPutErr) { - t.Errorf("expected put error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedPutErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedPutErr) { + t.Errorf("expected put error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedPutErr), helpers_test.ErrorToVal(err)) } CompareKey(t, key, tc.expectedPutResult) key, err = storage.GetOneKeyById(tc.getId) - if !test_helpers.ErrorsIs(err, tc.expectedGetErr) { - t.Errorf("expected get error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedGetErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedGetErr) { + t.Errorf("expected get error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedGetErr), helpers_test.ErrorToVal(err)) } CompareKey(t, key, tc.expectedGetResult) @@ -283,13 +283,13 @@ red-62 for _, tc := range testCases { t.Run(fmt.Sprintf("id: %d)", tc.keyId), func(t *testing.T) { err := storage.PutKeyApiKey(tc.keyId, tc.apiKey, tc.updateTimeUnix) - if !test_helpers.ErrorsIs(err, tc.expectedPutErr) { - t.Errorf("expected put error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedPutErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedPutErr) { + t.Errorf("expected put error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedPutErr), helpers_test.ErrorToVal(err)) } key, err := storage.GetOneKeyById(tc.keyId) - if !test_helpers.ErrorsIs(err, tc.expectedGetErr) { - t.Errorf("expected get error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedGetErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedGetErr) { + t.Errorf("expected get error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedGetErr), helpers_test.ErrorToVal(err)) } CompareKey(t, key, tc.expectedKey) @@ -383,13 +383,13 @@ red-67 for _, tc := range testCases { t.Run(fmt.Sprintf("id: %d)", tc.keyId), func(t *testing.T) { err := storage.PutKeyNewApiKey(tc.keyId, tc.apiKeyNew, tc.updateTimeUnix) - if !test_helpers.ErrorsIs(err, tc.expectedPutErr) { - t.Errorf("expected put error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedPutErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedPutErr) { + t.Errorf("expected put error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedPutErr), helpers_test.ErrorToVal(err)) } key, err := storage.GetOneKeyById(tc.keyId) - if !test_helpers.ErrorsIs(err, tc.expectedGetErr) { - t.Errorf("expected get error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedGetErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedGetErr) { + t.Errorf("expected get error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedGetErr), helpers_test.ErrorToVal(err)) } CompareKey(t, key, tc.expectedKey) @@ -501,13 +501,13 @@ red-62 for _, tc := range testCases { t.Run(fmt.Sprintf("id: %d)", tc.keyId), func(t *testing.T) { err := storage.PutKeyLastAccess(tc.keyId, tc.lastAccessTimeUnix) - if !test_helpers.ErrorsIs(err, tc.expectedPutErr) { - t.Errorf("expected put error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedPutErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedPutErr) { + t.Errorf("expected put error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedPutErr), helpers_test.ErrorToVal(err)) } key, err := storage.GetOneKeyById(tc.keyId) - if !test_helpers.ErrorsIs(err, tc.expectedGetErr) { - t.Errorf("expected get error '%s' but got '%s'", test_helpers.ErrorToVal(tc.expectedGetErr), test_helpers.ErrorToVal(err)) + if !helpers_test.ErrorsIs(err, tc.expectedGetErr) { + t.Errorf("expected get error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedGetErr), helpers_test.ErrorToVal(err)) } CompareKey(t, key, tc.expectedKey) diff --git a/pkg/storage/service_mock_test.go b/pkg/storage/service_mock_test.go index ad54c8b7..6bf8f00c 100644 --- a/pkg/storage/service_mock_test.go +++ b/pkg/storage/service_mock_test.go @@ -1,9 +1,9 @@ package storage_test import ( + "certwarden-backend/pkg/helpers_test" "certwarden-backend/pkg/pagination_sort" "certwarden-backend/pkg/storage" - "certwarden-backend/pkg/test_helpers" "context" "errors" "io" @@ -62,7 +62,7 @@ func openStorageWithTestData(t *testing.T, testName string) (_ *storage.Storage, _, err := os.Stat(thisTestFolder) if err == nil { os.RemoveAll(thisTestFolder) - } else if !test_helpers.ErrorsIs(err, os.ErrNotExist) { + } else if !helpers_test.ErrorsIs(err, os.ErrNotExist) { return nil, err } From acd5761e3614337e0b296a32c80d2f59a6bd674a Mon Sep 17 00:00:00 2001 From: "Greg T. Wallace" Date: Thu, 6 Aug 2026 21:20:46 -0400 Subject: [PATCH 07/16] dont export internal test func --- pkg/storage/accounts_get_test.go | 8 ++++---- pkg/storage/acme_servers_get_test.go | 4 ++-- pkg/storage/certificates_get_test.go | 6 +++--- pkg/storage/keys_get_test.go | 4 ++-- pkg/storage/service_mock_test.go | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pkg/storage/accounts_get_test.go b/pkg/storage/accounts_get_test.go index 8372c9fa..fff110bc 100644 --- a/pkg/storage/accounts_get_test.go +++ b/pkg/storage/accounts_get_test.go @@ -92,10 +92,10 @@ func TestGetAllAcmeAccounts(t *testing.T) { expectedAcctAtIndx acme_accounts.Account }{ {pagination_sort.Query{}, 7, 7, 3, acmeAcct23}, - {QueryBuilderForTest(1, 1, "id", true), 7, 1, 0, acmeAcct2}, - {QueryBuilderForTest(2, 1, "servername", false), 7, 2, 1, acmeAcct2}, - {QueryBuilderForTest(2, 4, "servername", false), 7, 2, 0, acmeAcct23}, - {QueryBuilderForTest(6, 1, "keyname", true), 7, 6, 5, acmeAcct1}, + {queryBuilderForTest(1, 1, "id", true), 7, 1, 0, acmeAcct2}, + {queryBuilderForTest(2, 1, "servername", false), 7, 2, 1, acmeAcct2}, + {queryBuilderForTest(2, 4, "servername", false), 7, 2, 0, acmeAcct23}, + {queryBuilderForTest(6, 1, "keyname", true), 7, 6, 5, acmeAcct1}, } // create testing service diff --git a/pkg/storage/acme_servers_get_test.go b/pkg/storage/acme_servers_get_test.go index 1a83b09c..19f0881a 100644 --- a/pkg/storage/acme_servers_get_test.go +++ b/pkg/storage/acme_servers_get_test.go @@ -61,8 +61,8 @@ func TestGetAllAcmeServers(t *testing.T) { expectedServerAtIndx acme_servers.Server }{ {pagination_sort.Query{}, 5, 5, 3, acmeServer0}, - {QueryBuilderForTest(1, 1, "id", true), 5, 1, 0, acmeServer1}, - {QueryBuilderForTest(2, 1, "updated_at", false), 5, 2, 1, acmeServer4}, + {queryBuilderForTest(1, 1, "id", true), 5, 1, 0, acmeServer1}, + {queryBuilderForTest(2, 1, "updated_at", false), 5, 2, 1, acmeServer4}, } // create testing service diff --git a/pkg/storage/certificates_get_test.go b/pkg/storage/certificates_get_test.go index c9265346..a40225e2 100644 --- a/pkg/storage/certificates_get_test.go +++ b/pkg/storage/certificates_get_test.go @@ -116,9 +116,9 @@ func TestGetAllCerts(t *testing.T) { expectedAtIndx certificates.Certificate }{ {pagination_sort.Query{}, 9, 9, 2, cert18}, - {QueryBuilderForTest(1, 1, "id", true), 9, 1, 0, cert26}, - {QueryBuilderForTest(3, 6, "servername", false), 9, 3, 1, cert27}, - {QueryBuilderForTest(4, 1, "accountname", true), 9, 4, 0, cert18}, + {queryBuilderForTest(1, 1, "id", true), 9, 1, 0, cert26}, + {queryBuilderForTest(3, 6, "servername", false), 9, 3, 1, cert27}, + {queryBuilderForTest(4, 1, "accountname", true), 9, 4, 0, cert18}, } // create testing service diff --git a/pkg/storage/keys_get_test.go b/pkg/storage/keys_get_test.go index 739679fd..1295d5c9 100644 --- a/pkg/storage/keys_get_test.go +++ b/pkg/storage/keys_get_test.go @@ -277,8 +277,8 @@ func TestGetAllKeys(t *testing.T) { expectedKeyAtIndx private_keys.Key }{ {pagination_sort.Query{}, 19, 19, 0, key63}, - {QueryBuilderForTest(5, 15, "algorithm", true), 19, 4, 2, key67}, - {QueryBuilderForTest(10, 0, "last_access", false), 19, 10, 2, key31}, + {queryBuilderForTest(5, 15, "algorithm", true), 19, 4, 2, key67}, + {queryBuilderForTest(10, 0, "last_access", false), 19, 10, 2, key31}, } // create testing service diff --git a/pkg/storage/service_mock_test.go b/pkg/storage/service_mock_test.go index 6bf8f00c..724298ff 100644 --- a/pkg/storage/service_mock_test.go +++ b/pkg/storage/service_mock_test.go @@ -109,8 +109,8 @@ func openStorageWithTestData(t *testing.T, testName string) (_ *storage.Storage, return storage, nil } -// QueryBuilderForTest generates a Query for use in tests -func QueryBuilderForTest(limit int, offset int, sortField string, sortAsc bool) pagination_sort.Query { +// queryBuilderForTest generates a Query for use in tests +func queryBuilderForTest(limit int, offset int, sortField string, sortAsc bool) pagination_sort.Query { sortDirText := "desc" if sortAsc { sortDirText = "asc" From dc05da9d8763c10c6465218acf59670e0f2d5865 Mon Sep 17 00:00:00 2001 From: "Greg T. Wallace" Date: Thu, 6 Aug 2026 21:20:46 -0400 Subject: [PATCH 08/16] add TestPutCertLastAccess --- pkg/domain/download/fetch_key.go | 2 +- pkg/domain/download/fetch_order.go | 6 +- pkg/domain/download/service.go | 5 +- pkg/domain/download/service_mock_test.go | 5 +- pkg/storage/certificates_put.go | 16 ++- pkg/storage/certificates_put_test.go | 146 +++++++++++++++++++++++ pkg/storage/keys_put.go | 5 +- pkg/storage/keys_put_test.go | 16 +-- 8 files changed, 180 insertions(+), 21 deletions(-) create mode 100644 pkg/storage/certificates_put_test.go diff --git a/pkg/domain/download/fetch_key.go b/pkg/domain/download/fetch_key.go index 934eed39..f68517b4 100644 --- a/pkg/domain/download/fetch_key.go +++ b/pkg/domain/download/fetch_key.go @@ -54,7 +54,7 @@ func (service *Service) getKey(keyName string, apiKey string, apiKeyViaUrl bool) } // before return, update key last access, dont fail our though if this step fails, just log error - err = service.storage.PutKeyLastAccess(key.ID, time.Now().Unix()) + err = service.storage.PutKeyLastAccess(key.ID, time.Now()) if err != nil { service.logger.Errorf("download: failed to update key (id: %d) last access time (%s)", key.ID, err) } diff --git a/pkg/domain/download/fetch_order.go b/pkg/domain/download/fetch_order.go index 0a45cdd7..ae0ba07a 100644 --- a/pkg/domain/download/fetch_order.go +++ b/pkg/domain/download/fetch_order.go @@ -73,7 +73,7 @@ func (service *Service) getCertNewestValidOrder(certName string, apiKeyOrKeys st order.FinalizedKey.Pem = "" // before return, update cert last access, dont fail our though if this step fails, just log error - err = service.storage.PutCertLastAccess(order.Certificate.ID, time.Now().Unix()) + err = service.storage.PutCertLastAccess(order.Certificate.ID, time.Now()) if err != nil { service.logger.Errorf("download: failed to update cert (id: %d) last access time (%s)", order.Certificate.ID, err) } @@ -122,11 +122,11 @@ func (service *Service) getCertNewestValidOrder(certName string, apiKeyOrKeys st // before return, update cert AND KEY last access, dont fail our though if this step fails, just log error nowT := time.Now() - err = service.storage.PutCertLastAccess(order.Certificate.ID, nowT.Unix()) + err = service.storage.PutCertLastAccess(order.Certificate.ID, nowT) if err != nil { service.logger.Errorf("download: failed to update cert (id: %d) last access time (%s)", order.Certificate.ID, err) } - err = service.storage.PutKeyLastAccess(order.FinalizedKey.ID, nowT.Unix()) + err = service.storage.PutKeyLastAccess(order.FinalizedKey.ID, nowT) if err != nil { service.logger.Errorf("download: failed to update key (id: %d) last access time (%s)", order.FinalizedKey.ID, err) } diff --git a/pkg/domain/download/service.go b/pkg/domain/download/service.go index 843c7d9d..8fa4b147 100644 --- a/pkg/domain/download/service.go +++ b/pkg/domain/download/service.go @@ -5,6 +5,7 @@ import ( "certwarden-backend/pkg/domain/private_keys" "certwarden-backend/pkg/output" "errors" + "time" "go.uber.org/zap" ) @@ -24,8 +25,8 @@ type Storage interface { GetCertNewestValidOrderByName(certName string) (order orders.Order, err error) - PutKeyLastAccess(keyId int, unixLastAccessTime int64) (err error) - PutCertLastAccess(certId int, unixLastAccessTime int64) (err error) + PutKeyLastAccess(keyId int, lastAccess time.Time) (err error) + PutCertLastAccess(certId int, lastAccess time.Time) (err error) } // Keys service struct diff --git a/pkg/domain/download/service_mock_test.go b/pkg/domain/download/service_mock_test.go index e9070a4a..6e88130d 100644 --- a/pkg/domain/download/service_mock_test.go +++ b/pkg/domain/download/service_mock_test.go @@ -13,6 +13,7 @@ import ( "net/http" "net/http/httptest" "testing" + "time" "github.com/julienschmidt/httprouter" "go.uber.org/zap" @@ -421,10 +422,10 @@ j1f1P6e7Khe0uXD3N+r34piMQT0WX0po2rf16x0i return orders.Order{}, sql.ErrNoRows } -func (fs *fakeStorage) PutKeyLastAccess(keyId int, unixLastAccessTime int64) (err error) { +func (fs *fakeStorage) PutKeyLastAccess(keyId int, lastAccess time.Time) (err error) { return errors.New("not implemented") } -func (fs *fakeStorage) PutCertLastAccess(certId int, unixLastAccessTime int64) (err error) { +func (fs *fakeStorage) PutCertLastAccess(certId int, lastAccess time.Time) (err error) { return errors.New("not implemented") } diff --git a/pkg/storage/certificates_put.go b/pkg/storage/certificates_put.go index 8f9124df..1d4686b1 100644 --- a/pkg/storage/certificates_put.go +++ b/pkg/storage/certificates_put.go @@ -3,6 +3,8 @@ package storage import ( "certwarden-backend/pkg/domain/certificates" "context" + "errors" + "fmt" "time" ) @@ -191,7 +193,7 @@ func (store *Storage) PutCertClientKey(certId int, newClientKeyB64 string, updat } // PutCertLastAccess sets a cert's last access time -func (store *Storage) PutCertLastAccess(certId int, unixLastAccessTime int64) (err error) { +func (store *Storage) PutCertLastAccess(certId int, lastAccess time.Time) (err error) { // database action ctx, cancel := context.WithTimeout(store.shutdownContext, store.timeout) defer cancel() @@ -205,14 +207,22 @@ func (store *Storage) PutCertLastAccess(certId int, unixLastAccessTime int64) (e id = $2 ` - _, err = store.db.ExecContext(ctx, query, - unixLastAccessTime, + res, err := store.db.ExecContext(ctx, query, + lastAccess.Unix(), certId, ) + if err != nil { + return err + } + // verify update actually happened + rowsAffected, err := res.RowsAffected() if err != nil { return err } + if rowsAffected != 1 { + return errors.Join(fmt.Errorf("expected 1 row update, but got '%d'", rowsAffected), ErrWrongUpdateRowCount) + } return nil } diff --git a/pkg/storage/certificates_put_test.go b/pkg/storage/certificates_put_test.go new file mode 100644 index 00000000..8e8d9776 --- /dev/null +++ b/pkg/storage/certificates_put_test.go @@ -0,0 +1,146 @@ +package storage_test + +import ( + "certwarden-backend/pkg/domain/certificates" + "certwarden-backend/pkg/helpers_test" + "certwarden-backend/pkg/storage" + "crypto/x509/pkix" + "database/sql" + "encoding/asn1" + "fmt" + "testing" + "time" +) + +func TestPutCertLastAccess(t *testing.T) { + testCases := []struct { + certId int + lastAccess time.Time + + expectedCert certificates.Certificate + expectedPutErr error + expectedGetErr error + }{ + { // invalid key id + -1, + time.Unix(88888111, 0), + certificates.Certificate{}, + storage.ErrWrongUpdateRowCount, + sql.ErrNoRows, + }, + { // invalid key id + 500, + time.Unix(88888222, 0), + certificates.Certificate{}, + storage.ErrWrongUpdateRowCount, + sql.ErrNoRows, + }, + // do update + { + 18, + time.Unix(0, 0), + certificates.Certificate{ + ID: 18, + Name: "serverdefault", + Description: "its a decript", + Key: key31, + Account: acmeAcct2, + Subject: "desk.dude.example.com", + SubjectAltNames: []string{"test011.test.example.com"}, + Organization: "my org", + OrganizationalUnit: "my ou", + Country: "your country", + State: "a state", + City: "springfield", + CSRExtraExtensions: []certificates.CertExtension{ + { + Extension: pkix.Extension{ + Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24}, + Critical: false, + Value: []byte{0x30, 0x03, 0x02, 0x01, 0x05}, + }, + Description: "OCSP Must Staple", + }, + }, + PreferredRootCN: "ISRG Root X1", + LastAccess: time.Unix(0, 0), + CreatedAt: time.Unix(1709327717, 0), + UpdatedAt: time.Unix(1779386440, 0), + ApiKey: "api-secret-18", + ApiKeyNew: "api-new-secret-18", + ApiKeyViaUrl: true, + PostProcessingCommand: "./scripts/windows/post-processing.example.ps1", + PostProcessingEnvironment: []string{"asdasdasdsd=asasd"}, + PostProcessingClientAddress: "dude.greg.example.com", + PostProcessingClientKeyB64: "aaaaaaaaaaaaaaaaaaaaaaaaaaa-ccccccccccccccc", + Profile: "tlsserver", + }, + nil, + nil, + }, + { + 18, + time.Unix(1122885, 0), + certificates.Certificate{ + ID: 18, + Name: "serverdefault", + Description: "its a decript", + Key: key31, + Account: acmeAcct2, + Subject: "desk.dude.example.com", + SubjectAltNames: []string{"test011.test.example.com"}, + Organization: "my org", + OrganizationalUnit: "my ou", + Country: "your country", + State: "a state", + City: "springfield", + CSRExtraExtensions: []certificates.CertExtension{ + { + Extension: pkix.Extension{ + Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24}, + Critical: false, + Value: []byte{0x30, 0x03, 0x02, 0x01, 0x05}, + }, + Description: "OCSP Must Staple", + }, + }, + PreferredRootCN: "ISRG Root X1", + LastAccess: time.Unix(1122885, 0), + CreatedAt: time.Unix(1709327717, 0), + UpdatedAt: time.Unix(1779386440, 0), + ApiKey: "api-secret-18", + ApiKeyNew: "api-new-secret-18", + ApiKeyViaUrl: true, + PostProcessingCommand: "./scripts/windows/post-processing.example.ps1", + PostProcessingEnvironment: []string{"asdasdasdsd=asasd"}, + PostProcessingClientAddress: "dude.greg.example.com", + PostProcessingClientKeyB64: "aaaaaaaaaaaaaaaaaaaaaaaaaaa-ccccccccccccccc", + Profile: "tlsserver", + }, + nil, + nil, + }, + } + + // create testing service + storage, err := openStorageWithTestData(t, "putcertlastaccess") + if err != nil { + t.Fatal(err) + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("id: %d)", tc.certId), func(t *testing.T) { + err := storage.PutCertLastAccess(tc.certId, tc.lastAccess) + if !helpers_test.ErrorsIs(err, tc.expectedPutErr) { + t.Errorf("expected put error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedPutErr), helpers_test.ErrorToVal(err)) + } + + record, err := storage.GetOneCertById(tc.certId) + if !helpers_test.ErrorsIs(err, tc.expectedGetErr) { + t.Errorf("expected get error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedGetErr), helpers_test.ErrorToVal(err)) + } + + CompareCertificate(t, record, tc.expectedCert) + }) + } +} diff --git a/pkg/storage/keys_put.go b/pkg/storage/keys_put.go index 1321c9ac..bf65b40f 100644 --- a/pkg/storage/keys_put.go +++ b/pkg/storage/keys_put.go @@ -5,6 +5,7 @@ import ( "context" "errors" "fmt" + "time" ) // PutKeyUpdate updates an existing key in the db using any non-null @@ -89,7 +90,7 @@ func (store *Storage) PutKeyNewApiKey(keyId int, newApiKey string, updateTimeUni } // PutKeyLastAccess sets a key's last access time -func (store *Storage) PutKeyLastAccess(keyId int, lastAccessTimeUnix int64) (err error) { +func (store *Storage) PutKeyLastAccess(keyId int, lastAccess time.Time) (err error) { // database action ctx, cancel := context.WithTimeout(store.shutdownContext, store.timeout) defer cancel() @@ -104,7 +105,7 @@ func (store *Storage) PutKeyLastAccess(keyId int, lastAccessTimeUnix int64) (err ` res, err := store.db.ExecContext(ctx, query, - lastAccessTimeUnix, + lastAccess.Unix(), keyId, ) if err != nil { diff --git a/pkg/storage/keys_put_test.go b/pkg/storage/keys_put_test.go index e6ec3fba..b7b96765 100644 --- a/pkg/storage/keys_put_test.go +++ b/pkg/storage/keys_put_test.go @@ -399,8 +399,8 @@ red-67 func TestPutKeyLastAccess(t *testing.T) { testCases := []struct { - keyId int - lastAccessTimeUnix int64 + keyId int + lastAccess time.Time expectedKey private_keys.Key expectedPutErr error @@ -408,14 +408,14 @@ func TestPutKeyLastAccess(t *testing.T) { }{ { // invalid key id -1, - 88888888, + time.Unix(88888888, 0), private_keys.Key{}, storage.ErrWrongUpdateRowCount, sql.ErrNoRows, }, { // invalid key id 500, - 88888888, + time.Unix(88888888, 0), private_keys.Key{}, storage.ErrWrongUpdateRowCount, sql.ErrNoRows, @@ -423,7 +423,7 @@ func TestPutKeyLastAccess(t *testing.T) { // do some updates { 64, - 1022885, + time.Unix(1022885, 0), private_keys.Key{ ID: 64, Name: "_Another_Test_Acct_LE_Staging_Roll", @@ -446,7 +446,7 @@ red-64 }, { 63, - 9999999, + time.Unix(9999999, 0), private_keys.Key{ ID: 63, Name: "_Another_Test_Acct_LE_Staging", @@ -469,7 +469,7 @@ red-63 }, { 62, - 0, + time.Unix(0, 0), private_keys.Key{ ID: 62, Name: "SomeKEy", @@ -500,7 +500,7 @@ red-62 for _, tc := range testCases { t.Run(fmt.Sprintf("id: %d)", tc.keyId), func(t *testing.T) { - err := storage.PutKeyLastAccess(tc.keyId, tc.lastAccessTimeUnix) + err := storage.PutKeyLastAccess(tc.keyId, tc.lastAccess) if !helpers_test.ErrorsIs(err, tc.expectedPutErr) { t.Errorf("expected put error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedPutErr), helpers_test.ErrorToVal(err)) } From 1350bd49c8cc72169ad1803130d533f560dcdbb5 Mon Sep 17 00:00:00 2001 From: "Greg T. Wallace" Date: Thu, 6 Aug 2026 21:20:47 -0400 Subject: [PATCH 09/16] fix compare key messages --- pkg/storage/keys_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/storage/keys_test.go b/pkg/storage/keys_test.go index a0bdaa4b..4ad33049 100644 --- a/pkg/storage/keys_test.go +++ b/pkg/storage/keys_test.go @@ -48,10 +48,10 @@ func CompareKey(t *testing.T, key, expectedKey private_keys.Key) { } if !key.CreatedAt.Equal(expectedKey.CreatedAt) { - t.Errorf("key: last access expected '%s' but got '%s'", expectedKey.CreatedAt.UTC(), key.CreatedAt.UTC()) + t.Errorf("key: created at expected '%s' but got '%s'", expectedKey.CreatedAt.UTC(), key.CreatedAt.UTC()) } if !key.UpdatedAt.Equal(expectedKey.UpdatedAt) { - t.Errorf("key: last access expected '%s' but got '%s'", expectedKey.UpdatedAt.UTC(), key.UpdatedAt.UTC()) + t.Errorf("key: updated at expected '%s' but got '%s'", expectedKey.UpdatedAt.UTC(), key.UpdatedAt.UTC()) } } From d3a4934900d5cf7d8028f95aa0d191ddd18db70d Mon Sep 17 00:00:00 2001 From: "Greg T. Wallace" Date: Thu, 6 Aug 2026 21:20:47 -0400 Subject: [PATCH 10/16] add TestPutDetailsCert * refactor CertExtension to use proper json interfaces and get business logic out of storage * rename time file --- pkg/domain/certificates/certificate.go | 41 +- .../certificates/certificate_extra_extn.go | 157 ++++--- pkg/domain/certificates/handlers_delete.go | 2 +- pkg/domain/certificates/handlers_post.go | 57 ++- pkg/domain/certificates/handlers_put.go | 58 ++- pkg/domain/certificates/services.go | 5 +- pkg/storage/certificates_post_test.go | 38 +- pkg/storage/certificates_put.go | 30 +- pkg/storage/certificates_put_test.go | 396 ++++++++++++++++++ pkg/storage/keys_put.go | 1 - pkg/storage/keys_put_test.go | 38 +- pkg/storage/{time.go => time_DELETE_ME.go} | 5 +- pkg/storage/types.go | 21 +- 13 files changed, 644 insertions(+), 205 deletions(-) rename pkg/storage/{time.go => time_DELETE_ME.go} (81%) diff --git a/pkg/domain/certificates/certificate.go b/pkg/domain/certificates/certificate.go index e6deb520..e636ac8c 100644 --- a/pkg/domain/certificates/certificate.go +++ b/pkg/domain/certificates/certificate.go @@ -98,32 +98,25 @@ func (cert Certificate) summaryResponse() certificateSummaryResponse { // fields that can be returned as JSON type certificateDetailedResponse struct { certificateSummaryResponse - Organization string `json:"organization"` - OrganizationalUnit string `json:"organizational_unit"` - Country string `json:"country"` - State string `json:"state"` - City string `json:"city"` - CSRExtraExtensions []CertExtensionJSON `json:"csr_extra_extensions"` - PreferredRootCN string `json:"preferred_root_cn"` - Profile string `json:"profile"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` - ApiKey string `json:"api_key"` - ApiKeyNew string `json:"api_key_new,omitempty"` - PostProcessingCommand string `json:"post_processing_command"` - PostProcessingEnvironment []string `json:"post_processing_environment"` - PostProcessingClientAddress string `json:"post_processing_client_address"` - PostProcessingClientKeyB64 string `json:"post_processing_client_key"` + Organization string `json:"organization"` + OrganizationalUnit string `json:"organizational_unit"` + Country string `json:"country"` + State string `json:"state"` + City string `json:"city"` + CSRExtraExtensions []CertExtension `json:"csr_extra_extensions"` + PreferredRootCN string `json:"preferred_root_cn"` + Profile string `json:"profile"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` + ApiKey string `json:"api_key"` + ApiKeyNew string `json:"api_key_new,omitempty"` + PostProcessingCommand string `json:"post_processing_command"` + PostProcessingEnvironment []string `json:"post_processing_environment"` + PostProcessingClientAddress string `json:"post_processing_client_address"` + PostProcessingClientKeyB64 string `json:"post_processing_client_key"` } func (cert Certificate) detailedResponse() certificateDetailedResponse { - // convert extensions to json output obj - extraExtensions := []CertExtensionJSON{} - for i := range cert.CSRExtraExtensions { - oneExt := cert.CSRExtraExtensions[i].toJSONObj() - extraExtensions = append(extraExtensions, oneExt) - } - return certificateDetailedResponse{ certificateSummaryResponse: cert.summaryResponse(), Organization: cert.Organization, @@ -131,7 +124,7 @@ func (cert Certificate) detailedResponse() certificateDetailedResponse { Country: cert.Country, State: cert.State, City: cert.City, - CSRExtraExtensions: extraExtensions, + CSRExtraExtensions: cert.CSRExtraExtensions, PreferredRootCN: cert.PreferredRootCN, Profile: cert.Profile, CreatedAt: cert.CreatedAt.Unix(), diff --git a/pkg/domain/certificates/certificate_extra_extn.go b/pkg/domain/certificates/certificate_extra_extn.go index 70fb2def..477cf7f1 100644 --- a/pkg/domain/certificates/certificate_extra_extn.go +++ b/pkg/domain/certificates/certificate_extra_extn.go @@ -4,17 +4,13 @@ import ( "crypto/x509/pkix" "encoding/asn1" "encoding/hex" + "encoding/json" "errors" "fmt" "strconv" "strings" ) -var ( - errCertExtOIDBadFormat = errors.New("certificates extension: OID string invalid (must be in dot notation)") - errCertExtValueBad = errors.New("certificates extension: Value invalid (must be hex string, hex string with colons, or hex string with spaces)") -) - // CertExtension us a pkix.Extension with an additional field for // a description type CertExtension struct { @@ -23,88 +19,141 @@ type CertExtension struct { } // String prints a log friendly version of the Certificate Extension (useful for testing) -func (ce CertExtension) String() string { - return fmt.Sprintf("CertExtension{Description: %s, Id: %s, Critical: %t, Value: %x}", ce.Description, ce.Id.String(), ce.Critical, ce.Value) +func (cext CertExtension) String() string { + return fmt.Sprintf("CertExtension{Description: %s, Id: %s, Critical: %t, Value: %x}", cext.Description, cext.Id.String(), cext.Critical, cext.Value) } -// CertExtensionJSON is the object to use in the API (both input and output) -// to represent the custom CertificateExtension -type CertExtensionJSON struct { - Description string `json:"description"` - OID string `json:"oid"` - Critical bool `json:"critical"` - ValueHexString string `json:"value_hex"` -} - -// toJSONObj returns the JSON object of the custom CertExtension -func (ce CertExtension) toJSONObj() CertExtensionJSON { - return CertExtensionJSON{ - Description: ce.Description, - OID: ce.Id.String(), - Critical: ce.Critical, - ValueHexString: hex.EncodeToString(ce.Value), +// UnmarshalJSON implements the unmarshalling interface for CertExtension +// json should be in the shape: +// +// { +// Description string `json:"description"` +// OID string `json:"oid"` +// Critical bool `json:"critical"` +// ValueHexString string `json:"value_hex"` +// } +func (cext *CertExtension) UnmarshalJSON(b []byte) error { + // generic unmarshal + m := map[string]interface{}{} + err := json.Unmarshal(b, &m) + if err != nil { + return err } -} -// toCertExtension validates the CertExtensionJSON and then returns -// the CertExtension object; if any fields fail to validate, an error -// is returned instead -func (cej CertExtensionJSON) ToCertExtension() (CertExtension, error) { - ce := CertExtension{} + result := CertExtension{} - // Description - no validation needed - ce.Description = cej.Description + // description + desc, ok := m["description"] + if !ok { + return errors.New("'description' missing") + } + result.Description, ok = desc.(string) + if !ok { + return errors.New("'description' is not string type") + } + + // oid - must convert into asn1.ObjectIdentifier (must be in dot notation) + desc, ok = m["oid"] + if !ok { + return errors.New("'oid' missing") + } + oidStr, ok := desc.(string) + if !ok { + return errors.New("'oid' is not string type") + } - // OID - must convert into asn1.ObjectIdentifier (must be in dot notation) - var err error - oidParts := strings.Split(cej.OID, ".") + oidParts := strings.Split(oidStr, ".") id := make(asn1.ObjectIdentifier, len(oidParts)) for i := range oidParts { id[i], err = strconv.Atoi(oidParts[i]) if err != nil { - return CertExtension{}, errCertExtOIDBadFormat + return errors.New("invalid oid format") } } - ce.Id = id + result.Id = id - // Cricial - no validation needed - ce.Critical = cej.Critical + // critical + desc, ok = m["critical"] + if !ok { + return errors.New("'critical' missing") + } + result.Critical, ok = desc.(bool) + if !ok { + return errors.New("'critical' is not bool type") + } + + // value - must convert from hex string to []byte + // allow no delimiter, ':' (colon), or ' ' (space) as delimiter + desc, ok = m["value_hex"] + if !ok { + return errors.New("'value_hex' missing") + } + valueHex, ok := desc.(string) + if !ok { + return errors.New("'value_hex' is not string type") + } - // ValueByteString - must be a valid hex byte string; will try a couple of parsing - // options (allow bytes to be separated by colons or spaces) - valueParts := []string{} - if strings.Contains(cej.ValueHexString, ":") { + valueHexParts := []string{} + // check for and deal with ':' or ' ' separation + if strings.Contains(valueHex, ":") { // has colons - valueParts = strings.Split(cej.ValueHexString, ":") - } else if strings.Contains(cej.ValueHexString, " ") { + valueHexParts = strings.Split(valueHex, ":") + } else if strings.Contains(valueHex, " ") { // has spaces - valueParts = strings.Split(cej.ValueHexString, " ") + valueHexParts = strings.Split(valueHex, " ") } // if we made value parts, build hex without separator string from them, if we did // not, use original hex value valueHexNoSep := "" - if len(valueParts) > 0 { - for i := range valueParts { + if len(valueHexParts) > 0 { + for i := range valueHexParts { // each byte must be explicityly two chars long - if len(valueParts[i]) != 2 { + if len(valueHexParts[i]) != 2 { // fail if not - return CertExtension{}, errCertExtValueBad + return fmt.Errorf("invalid value byte '%s' (not 2 chars)", valueHexParts[i]) } // add byte to the no seperation string - valueHexNoSep += valueParts[i] + valueHexNoSep += valueHexParts[i] } } else { // no separator was found, use as-is - valueHexNoSep = cej.ValueHexString + valueHexNoSep = valueHex } // decode hex string - ce.Value, err = hex.DecodeString(valueHexNoSep) + result.Value, err = hex.DecodeString(valueHexNoSep) if err != nil { - return CertExtension{}, errCertExtValueBad + return fmt.Errorf("failed to decode hex '%s'", valueHexNoSep) + } + + // good to go + *cext = result + return nil +} + +// MarshalJSON implements the marshalling interface for CertExtension +// and the output json will be in the shape: +// +// { +// Description string `json:"description"` +// OID string `json:"oid"` +// Critical bool `json:"critical"` +// ValueHexString string `json:"value_hex"` +// } +func (cext *CertExtension) MarshalJSON() ([]byte, error) { + out := struct { + Description string `json:"description"` + OID string `json:"oid"` + Critical bool `json:"critical"` + ValueHexString string `json:"value_hex"` + }{ + Description: cext.Description, + OID: cext.Id.String(), + Critical: cext.Critical, + ValueHexString: hex.EncodeToString(cext.Value), } - return ce, nil + return json.Marshal(out) } diff --git a/pkg/domain/certificates/handlers_delete.go b/pkg/domain/certificates/handlers_delete.go index ba40b51c..139ffa87 100644 --- a/pkg/domain/certificates/handlers_delete.go +++ b/pkg/domain/certificates/handlers_delete.go @@ -126,7 +126,7 @@ func (service *Service) DisableClientKey(w http.ResponseWriter, r *http.Request) // validation -- end // update storage - err = service.storage.PutCertClientKey(certId, "", int(time.Now().Unix())) + err = service.storage.PutCertClientKey(certId, "", time.Now()) if err != nil { service.logger.Error(err) return output.JsonErrStorageGeneric(err) diff --git a/pkg/domain/certificates/handlers_post.go b/pkg/domain/certificates/handlers_post.go index afbf9c66..b953a010 100644 --- a/pkg/domain/certificates/handlers_post.go +++ b/pkg/domain/certificates/handlers_post.go @@ -18,29 +18,29 @@ import ( // NewPayload is the struct for creating a new certificate type NewPayload struct { - Name *string `json:"name"` - Description *string `json:"description"` - PrivateKeyID *int `json:"private_key_id"` - NewKeyAlgorithmValue *string `json:"algorithm_value"` - AcmeAccountID *int `json:"acme_account_id"` - Subject *string `json:"subject"` - SubjectAltNames []string `json:"subject_alts"` - Organization *string `json:"organization"` - OrganizationalUnit *string `json:"organizational_unit"` - Country *string `json:"country"` - State *string `json:"state"` - City *string `json:"city"` - CSRExtraExtensions []CertExtensionJSON `json:"csr_extra_extensions"` - PreferredRootCN *string `json:"preferred_root_cn"` - PostProcessingCommand *string `json:"post_processing_command"` - PostProcessingEnvironment []string `json:"post_processing_environment"` - PostProcessingClientAddress *string `json:"post_processing_client_address"` - PostProcessingClientKeyB64 string `json:"-"` - Profile *string `json:"profile"` - ApiKey string `json:"-"` - ApiKeyViaUrl bool `json:"-"` - CreatedAt int `json:"-"` - UpdatedAt int `json:"-"` + Name *string `json:"name"` + Description *string `json:"description"` + PrivateKeyID *int `json:"private_key_id"` + NewKeyAlgorithmValue *string `json:"algorithm_value"` + AcmeAccountID *int `json:"acme_account_id"` + Subject *string `json:"subject"` + SubjectAltNames []string `json:"subject_alts"` + Organization *string `json:"organization"` + OrganizationalUnit *string `json:"organizational_unit"` + Country *string `json:"country"` + State *string `json:"state"` + City *string `json:"city"` + CSRExtraExtensions []CertExtension `json:"csr_extra_extensions"` + PreferredRootCN *string `json:"preferred_root_cn"` + PostProcessingCommand *string `json:"post_processing_command"` + PostProcessingEnvironment []string `json:"post_processing_environment"` + PostProcessingClientAddress *string `json:"post_processing_client_address"` + PostProcessingClientKeyB64 string `json:"-"` + Profile *string `json:"profile"` + ApiKey string `json:"-"` + ApiKeyViaUrl bool `json:"-"` + CreatedAt int `json:"-"` + UpdatedAt int `json:"-"` } // PostNewCert creates a new certificate object in storage. No actual encryption certificate @@ -165,14 +165,7 @@ func (service *Service) PostNewCert(w http.ResponseWriter, r *http.Request) *out payload.City = new(string) } - // CSR Extra Extensions - check each extra extension for proper formatting - for i := range payload.CSRExtraExtensions { - _, err = payload.CSRExtraExtensions[i].ToCertExtension() - if err != nil { - service.logger.Debug(err) - return output.JsonErrValidationFailed(err) - } - } + // CSR Extra Extensions - error checking is now in the custom unmarshal function if payload.PreferredRootCN == nil { payload.PreferredRootCN = new(string) @@ -351,7 +344,7 @@ func (service *Service) MakeNewClientKey(w http.ResponseWriter, r *http.Request) } // update storage - err = service.storage.PutCertClientKey(certId, clientKey, int(time.Now().Unix())) + err = service.storage.PutCertClientKey(certId, clientKey, time.Now()) if err != nil { service.logger.Error(err) return output.JsonErrStorageGeneric(err) diff --git a/pkg/domain/certificates/handlers_put.go b/pkg/domain/certificates/handlers_put.go index 85d60983..b1faebfd 100644 --- a/pkg/domain/certificates/handlers_put.go +++ b/pkg/domain/certificates/handlers_put.go @@ -12,36 +12,36 @@ import ( "github.com/julienschmidt/httprouter" ) -// DetailsUpdatePayload is the struct for editing an existing cert. A number of +// UpdatePayload is the struct for editing an existing cert. A number of // fields can be updated by the client on the fly (without ACME interaction). -type DetailsUpdatePayload struct { - ID int `json:"-"` - Name *string `json:"name"` - Description *string `json:"description"` - PrivateKeyId *int `json:"private_key_id"` - SubjectAltNames []string `json:"subject_alts"` - Organization *string `json:"organization"` - OrganizationalUnit *string `json:"organizational_unit"` - Country *string `json:"country"` - State *string `json:"state"` - City *string `json:"city"` - CSRExtraExtensions []CertExtensionJSON `json:"csr_extra_extensions"` - PreferredRootCN *string `json:"preferred_root_cn"` - PostProcessingCommand *string `json:"post_processing_command"` - PostProcessingEnvironment []string `json:"post_processing_environment"` - PostProcessingClientAddress *string `json:"post_processing_client_address"` - Profile *string `json:"profile"` - ApiKey *string `json:"api_key"` - ApiKeyNew *string `json:"api_key_new"` - ApiKeyViaUrl *bool `json:"api_key_via_url"` - UpdatedAt int `json:"-"` +type UpdatePayload struct { + ID int `json:"-"` + Name *string `json:"name"` + Description *string `json:"description"` + PrivateKeyId *int `json:"private_key_id"` + SubjectAltNames []string `json:"subject_alts"` + Organization *string `json:"organization"` + OrganizationalUnit *string `json:"organizational_unit"` + Country *string `json:"country"` + State *string `json:"state"` + City *string `json:"city"` + CSRExtraExtensions []CertExtension `json:"csr_extra_extensions"` + PreferredRootCN *string `json:"preferred_root_cn"` + PostProcessingCommand *string `json:"post_processing_command"` + PostProcessingEnvironment []string `json:"post_processing_environment"` + PostProcessingClientAddress *string `json:"post_processing_client_address"` + Profile *string `json:"profile"` + ApiKey *string `json:"api_key"` + ApiKeyNew *string `json:"api_key_new"` + ApiKeyViaUrl *bool `json:"api_key_via_url"` + UpdatedAt int `json:"-"` } // PutDetailsCert is a handler that sets various details about a cert and saves // them to storage. These are all details that should be editable any time. func (service *Service) PutDetailsCert(w http.ResponseWriter, r *http.Request) *output.JsonError { // payload decoding - var payload DetailsUpdatePayload + var payload UpdatePayload err := json.NewDecoder(r.Body).Decode(&payload) if err != nil { service.logger.Debug(err) @@ -114,16 +114,8 @@ func (service *Service) PutDetailsCert(w http.ResponseWriter, r *http.Request) * service.logger.Debug(ErrApiKeyNewBad) return output.JsonErrValidationFailed(ErrApiKeyNewBad) } - // TODO: Do any validation of CSR components? - // CSR Extra Extensions - check each extra extension for proper formatting - for i := range payload.CSRExtraExtensions { - _, err = payload.CSRExtraExtensions[i].ToCertExtension() - if err != nil { - service.logger.Debug(err) - return output.JsonErrValidationFailed(err) - } - } + // CSR Extra Extensions - error checking is now in the custom unmarshal function // post processing command & env are optional but nothing to validate @@ -145,7 +137,7 @@ func (service *Service) PutDetailsCert(w http.ResponseWriter, r *http.Request) * // save account name and desc to storage, which also returns the account id with new // name and description - updatedCert, err := service.storage.PutDetailsCert(payload) + updatedCert, err := service.storage.PutCertUpdate(payload) if err != nil { service.logger.Error(err) return output.JsonErrStorageGeneric(err) diff --git a/pkg/domain/certificates/services.go b/pkg/domain/certificates/services.go index 33d1d6c6..d6b97357 100644 --- a/pkg/domain/certificates/services.go +++ b/pkg/domain/certificates/services.go @@ -7,6 +7,7 @@ import ( "certwarden-backend/pkg/output" "certwarden-backend/pkg/pagination_sort" "errors" + "time" "go.uber.org/zap" ) @@ -31,10 +32,10 @@ type Storage interface { PostNewCert(payload NewPayload) (Certificate, error) - PutDetailsCert(payload DetailsUpdatePayload) (Certificate, error) + PutCertUpdate(payload UpdatePayload) (Certificate, error) PutCertApiKey(certId int, apiKey string, updateTimeUnix int) (err error) PutCertNewApiKey(certId int, newApiKey string, updateTimeUnix int) (err error) - PutCertClientKey(certId int, newClientKeyB64 string, updateTimeUnix int) (err error) + PutCertClientKey(certId int, newClientKeyB64 string, updatedAt time.Time) (err error) DeleteCert(id int) (err error) diff --git a/pkg/storage/certificates_post_test.go b/pkg/storage/certificates_post_test.go index 06f6c35b..87fe46a0 100644 --- a/pkg/storage/certificates_post_test.go +++ b/pkg/storage/certificates_post_test.go @@ -36,12 +36,14 @@ func TestPostNewCert(t *testing.T) { Country: new("usa"), State: new("Ca"), City: new("los santos"), - CSRExtraExtensions: []certificates.CertExtensionJSON{ + CSRExtraExtensions: []certificates.CertExtension{ { - Description: "OCSP Must Staple", - OID: "1.3.6.1.5.5.7.1.24", - Critical: false, - ValueHexString: "3003020105", + Extension: pkix.Extension{ + Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24}, + Critical: false, + Value: []byte{0x30, 0x03, 0x02, 0x01, 0x05}, + }, + Description: "OCSP Must Staple", }, }, PreferredRootCN: new("Root xyz"), @@ -108,7 +110,7 @@ func TestPostNewCert(t *testing.T) { Country: new("usa"), State: new("Ca"), City: new("los santos"), - CSRExtraExtensions: []certificates.CertExtensionJSON{}, + CSRExtraExtensions: []certificates.CertExtension{}, PreferredRootCN: new("Root xyz"), PostProcessingCommand: new("./run-me.py"), PostProcessingEnvironment: []string{}, @@ -138,12 +140,14 @@ func TestPostNewCert(t *testing.T) { Country: new("usa"), State: new("Ca"), City: new("los santos"), - CSRExtraExtensions: []certificates.CertExtensionJSON{ + CSRExtraExtensions: []certificates.CertExtension{ { - Description: "OCSP Must Staple", - OID: "1.3.6.1.5.5.7.1.24", - Critical: false, - ValueHexString: "3003020105", + Extension: pkix.Extension{ + Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24}, + Critical: false, + Value: []byte{0x30, 0x03, 0x02, 0x01, 0x05}, + }, + Description: "OCSP Must Staple", }, }, PreferredRootCN: new("Root xyz"), @@ -175,12 +179,14 @@ func TestPostNewCert(t *testing.T) { Country: new("usa"), State: new("Ca"), City: new("los santos"), - CSRExtraExtensions: []certificates.CertExtensionJSON{ + CSRExtraExtensions: []certificates.CertExtension{ { - Description: "OCSP Must Staple", - OID: "1.3.6.1.5.5.7.1.24", - Critical: false, - ValueHexString: "3003020105", + Extension: pkix.Extension{ + Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24}, + Critical: false, + Value: []byte{0x30, 0x03, 0x02, 0x01, 0x05}, + }, + Description: "OCSP Must Staple", }, }, PreferredRootCN: new("Root xyz"), diff --git a/pkg/storage/certificates_put.go b/pkg/storage/certificates_put.go index 1d4686b1..ad3df704 100644 --- a/pkg/storage/certificates_put.go +++ b/pkg/storage/certificates_put.go @@ -8,9 +8,9 @@ import ( "time" ) -// PutDetailsCert saves details about the cert that can be updated at any time. It only updates +// PutCertUpdate saves details about the cert that can be updated at any time. It only updates // the details which are provided -func (store *Storage) PutDetailsCert(payload certificates.DetailsUpdatePayload) (certificates.Certificate, error) { +func (store *Storage) PutCertUpdate(payload certificates.UpdatePayload) (certificates.Certificate, error) { // database update ctx, cancel := context.WithTimeout(store.shutdownContext, store.timeout) defer cancel() @@ -42,7 +42,7 @@ func (store *Storage) PutDetailsCert(payload certificates.DetailsUpdatePayload) id = $20 ` - _, err := store.db.ExecContext(ctx, query, + res, err := store.db.ExecContext(ctx, query, payload.Name, payload.Description, payload.PrivateKeyId, @@ -64,10 +64,18 @@ func (store *Storage) PutDetailsCert(payload certificates.DetailsUpdatePayload) payload.UpdatedAt, payload.ID, ) + if err != nil { + return certificates.Certificate{}, err + } + // verify update actually happened + rowsAffected, err := res.RowsAffected() if err != nil { return certificates.Certificate{}, err } + if rowsAffected != 1 { + return certificates.Certificate{}, errors.Join(fmt.Errorf("expected 1 row update, but got '%d'", rowsAffected), ErrWrongUpdateRowCount) + } // get updated to return updatedCert, err := store.GetOneCertById(payload.ID) @@ -164,7 +172,7 @@ func (store *Storage) PutCertApiKey(certId int, apiKey string, updateTimeUnix in } // PutCertClientKey sets a cert's client key and updates the updated at time -func (store *Storage) PutCertClientKey(certId int, newClientKeyB64 string, updateTimeUnix int) (err error) { +func (store *Storage) PutCertClientKey(certId int, clientKeyB64 string, updatedAt time.Time) (err error) { // database action ctx, cancel := context.WithTimeout(store.shutdownContext, store.timeout) defer cancel() @@ -179,15 +187,23 @@ func (store *Storage) PutCertClientKey(certId int, newClientKeyB64 string, updat id = $3 ` - _, err = store.db.ExecContext(ctx, query, - newClientKeyB64, - updateTimeUnix, + res, err := store.db.ExecContext(ctx, query, + clientKeyB64, + updatedAt.Unix(), certId, ) + if err != nil { + return err + } + // verify update actually happened + rowsAffected, err := res.RowsAffected() if err != nil { return err } + if rowsAffected != 1 { + return errors.Join(fmt.Errorf("expected 1 row update, but got '%d'", rowsAffected), ErrWrongUpdateRowCount) + } return nil } diff --git a/pkg/storage/certificates_put_test.go b/pkg/storage/certificates_put_test.go index 8e8d9776..160ae041 100644 --- a/pkg/storage/certificates_put_test.go +++ b/pkg/storage/certificates_put_test.go @@ -12,6 +12,402 @@ import ( "time" ) +// TODO: + +// UpdateCertUpdatedTime +// PutCertNewApiKey +// PutCertApiKey + +func TestPutDetailsCert(t *testing.T) { + testCases := []struct { + payload certificates.UpdatePayload + + expectedPutResult certificates.Certificate + expectedPutErr error + + getId int + expectedGetResult certificates.Certificate + expectedGetErr error + }{ + { // invalid cert + certificates.UpdatePayload{ + ID: -1, + }, + certificates.Certificate{}, + storage.ErrWrongUpdateRowCount, + -1, + certificates.Certificate{}, + sql.ErrNoRows, + }, + { // invalid key + certificates.UpdatePayload{ + ID: 522, + }, + certificates.Certificate{}, + storage.ErrWrongUpdateRowCount, + 522, + certificates.Certificate{}, + sql.ErrNoRows, + }, + { // update all things + certificates.UpdatePayload{ + ID: 18, + Name: new("somenewNameHere"), + Description: new("some new desc goes here"), + PrivateKeyId: new(58), + SubjectAltNames: []string{"new.example.com"}, + Organization: new("new org 1"), + OrganizationalUnit: new("new orgu 1"), + Country: new("new orgco 1"), + State: new("new orgst 1"), + City: new("new orgci 1"), + CSRExtraExtensions: []certificates.CertExtension{ + { + Extension: pkix.Extension{ + Id: asn1.ObjectIdentifier{1, 2, 4}, + Critical: true, + Value: []byte{0xaa, 0xbb, 0x11}, + }, + Description: "a", + }, + }, + PreferredRootCN: new("different new cn"), + PostProcessingCommand: new("./app.exe"), + PostProcessingEnvironment: []string{"a=123", "b=zba"}, + PostProcessingClientAddress: new("xyz.com"), + Profile: new("new prof 2"), + ApiKey: new("api-key---"), + ApiKeyNew: new("api-key-new---"), + ApiKeyViaUrl: new(false), + UpdatedAt: 222223333, + }, + certificates.Certificate{ + ID: 18, + Name: "somenewNameHere", + Description: "some new desc goes here", + Key: key58, + Account: acmeAcct2, + Subject: "desk.dude.example.com", + SubjectAltNames: []string{"new.example.com"}, + Organization: "new org 1", + OrganizationalUnit: "new orgu 1", + Country: "new orgco 1", + State: "new orgst 1", + City: "new orgci 1", + CSRExtraExtensions: []certificates.CertExtension{ + { + Extension: pkix.Extension{ + Id: asn1.ObjectIdentifier{1, 2, 4}, + Critical: true, + Value: []byte{0xaa, 0xbb, 0x11}, + }, + Description: "a", + }, + }, + PreferredRootCN: "different new cn", + LastAccess: time.Unix(1745952074, 0), + CreatedAt: time.Unix(1709327717, 0), + UpdatedAt: time.Unix(222223333, 0), + ApiKey: "api-key---", + ApiKeyNew: "api-key-new---", + ApiKeyViaUrl: false, + PostProcessingCommand: "./app.exe", + PostProcessingEnvironment: []string{"a=123", "b=zba"}, + PostProcessingClientAddress: "xyz.com", + PostProcessingClientKeyB64: "aaaaaaaaaaaaaaaaaaaaaaaaaaa-ccccccccccccccc", + Profile: "new prof 2", + }, + nil, + 18, + certificates.Certificate{ + ID: 18, + Name: "somenewNameHere", + Description: "some new desc goes here", + Key: key58, + Account: acmeAcct2, + Subject: "desk.dude.example.com", + SubjectAltNames: []string{"new.example.com"}, + Organization: "new org 1", + OrganizationalUnit: "new orgu 1", + Country: "new orgco 1", + State: "new orgst 1", + City: "new orgci 1", + CSRExtraExtensions: []certificates.CertExtension{ + { + Extension: pkix.Extension{ + Id: asn1.ObjectIdentifier{1, 2, 4}, + Critical: true, + Value: []byte{0xaa, 0xbb, 0x11}, + }, + Description: "a", + }, + }, + PreferredRootCN: "different new cn", + LastAccess: time.Unix(1745952074, 0), + CreatedAt: time.Unix(1709327717, 0), + UpdatedAt: time.Unix(222223333, 0), + ApiKey: "api-key---", + ApiKeyNew: "api-key-new---", + ApiKeyViaUrl: false, + PostProcessingCommand: "./app.exe", + PostProcessingEnvironment: []string{"a=123", "b=zba"}, + PostProcessingClientAddress: "xyz.com", + PostProcessingClientKeyB64: "aaaaaaaaaaaaaaaaaaaaaaaaaaa-ccccccccccccccc", + Profile: "new prof 2", + }, + nil, + }, + // update nothing (except mandatory update time) + { + certificates.UpdatePayload{ + ID: 27, + UpdatedAt: 15151515, + }, + certificates.Certificate{ + ID: 27, + Name: "test008.test.example.com-p", + Description: "", + Key: key56, + Account: acmeAcct2, + Subject: "test008.test.example.com", + SubjectAltNames: []string{"test011.test.example.com", "*.test011.test.example.com"}, + Organization: "", + OrganizationalUnit: "", + Country: "", + State: "", + City: "", + CSRExtraExtensions: []certificates.CertExtension{}, + PreferredRootCN: "", + LastAccess: time.Unix(0, 0), + CreatedAt: time.Unix(1743171060, 0), + UpdatedAt: time.Unix(15151515, 0), + ApiKey: "api-secret-27", + ApiKeyNew: "", + ApiKeyViaUrl: false, + PostProcessingCommand: "", + PostProcessingEnvironment: []string{}, + PostProcessingClientAddress: "", + PostProcessingClientKeyB64: "", + Profile: "", + }, + nil, + 27, + certificates.Certificate{ + ID: 27, + Name: "test008.test.example.com-p", + Description: "", + Key: key56, + Account: acmeAcct2, + Subject: "test008.test.example.com", + SubjectAltNames: []string{"test011.test.example.com", "*.test011.test.example.com"}, + Organization: "", + OrganizationalUnit: "", + Country: "", + State: "", + City: "", + CSRExtraExtensions: []certificates.CertExtension{}, + PreferredRootCN: "", + LastAccess: time.Unix(0, 0), + CreatedAt: time.Unix(1743171060, 0), + UpdatedAt: time.Unix(15151515, 0), + ApiKey: "api-secret-27", + ApiKeyNew: "", + ApiKeyViaUrl: false, + PostProcessingCommand: "", + PostProcessingEnvironment: []string{}, + PostProcessingClientAddress: "", + PostProcessingClientKeyB64: "", + Profile: "", + }, + nil, + }, + // update a couple things (including empty slice) + { + certificates.UpdatePayload{ + ID: 27, + SubjectAltNames: []string{}, + PostProcessingClientAddress: new("someaddr.example.com"), + UpdatedAt: 151222215, + }, + certificates.Certificate{ + ID: 27, + Name: "test008.test.example.com-p", + Description: "", + Key: key56, + Account: acmeAcct2, + Subject: "test008.test.example.com", + SubjectAltNames: []string{}, + Organization: "", + OrganizationalUnit: "", + Country: "", + State: "", + City: "", + CSRExtraExtensions: []certificates.CertExtension{}, + PreferredRootCN: "", + LastAccess: time.Unix(0, 0), + CreatedAt: time.Unix(1743171060, 0), + UpdatedAt: time.Unix(151222215, 0), + ApiKey: "api-secret-27", + ApiKeyNew: "", + ApiKeyViaUrl: false, + PostProcessingCommand: "", + PostProcessingEnvironment: []string{}, + PostProcessingClientAddress: "someaddr.example.com", + PostProcessingClientKeyB64: "", + Profile: "", + }, + nil, + 27, + certificates.Certificate{ + ID: 27, + Name: "test008.test.example.com-p", + Description: "", + Key: key56, + Account: acmeAcct2, + Subject: "test008.test.example.com", + SubjectAltNames: []string{}, + Organization: "", + OrganizationalUnit: "", + Country: "", + State: "", + City: "", + CSRExtraExtensions: []certificates.CertExtension{}, + PreferredRootCN: "", + LastAccess: time.Unix(0, 0), + CreatedAt: time.Unix(1743171060, 0), + UpdatedAt: time.Unix(151222215, 0), + ApiKey: "api-secret-27", + ApiKeyNew: "", + ApiKeyViaUrl: false, + PostProcessingCommand: "", + PostProcessingEnvironment: []string{}, + PostProcessingClientAddress: "someaddr.example.com", + PostProcessingClientKeyB64: "", + Profile: "", + }, + nil, + }, + } + + // create testing service + storage, err := openStorageWithTestData(t, "putcertupdate") + if err != nil { + t.Fatal(err) + } + + for i, tc := range testCases { + t.Run(fmt.Sprintf("#%d (id: %d)", i, tc.payload.ID), func(t *testing.T) { + c, err := storage.PutCertUpdate(tc.payload) + if !helpers_test.ErrorsIs(err, tc.expectedPutErr) { + t.Errorf("expected put error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedPutErr), helpers_test.ErrorToVal(err)) + } + + CompareCertificate(t, c, tc.expectedPutResult) + + c, err = storage.GetOneCertById(tc.getId) + if !helpers_test.ErrorsIs(err, tc.expectedGetErr) { + t.Errorf("expected get error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedGetErr), helpers_test.ErrorToVal(err)) + } + + CompareCertificate(t, c, tc.expectedGetResult) + }) + } +} + +func TestPutCertClientKey(t *testing.T) { + testCases := []struct { + certId int + newKey string + updatedAt time.Time + + expectedCert certificates.Certificate + expectedPutErr error + expectedGetErr error + }{ + { // invalid key id 1 + -1, + "new-b64-key", + time.Unix(8883333, 0), + certificates.Certificate{}, + storage.ErrWrongUpdateRowCount, + sql.ErrNoRows, + }, + { // invalid key id 2 + 8888, + "new-b64-key", + time.Unix(88833331, 0), + certificates.Certificate{}, + storage.ErrWrongUpdateRowCount, + sql.ErrNoRows, + }, + { // valid update + 18, + "new-b64-key-xxx111yyyy", + time.Unix(88832231, 0), + certificates.Certificate{ + ID: 18, + Name: "serverdefault", + Description: "its a decript", + Key: key31, + Account: acmeAcct2, + Subject: "desk.dude.example.com", + SubjectAltNames: []string{"test011.test.example.com"}, + Organization: "my org", + OrganizationalUnit: "my ou", + Country: "your country", + State: "a state", + City: "springfield", + CSRExtraExtensions: []certificates.CertExtension{ + { + Extension: pkix.Extension{ + Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24}, + Critical: false, + Value: []byte{0x30, 0x03, 0x02, 0x01, 0x05}, + }, + Description: "OCSP Must Staple", + }, + }, + PreferredRootCN: "ISRG Root X1", + LastAccess: time.Unix(1745952074, 0), + CreatedAt: time.Unix(1709327717, 0), + UpdatedAt: time.Unix(88832231, 0), + ApiKey: "api-secret-18", + ApiKeyNew: "api-new-secret-18", + ApiKeyViaUrl: true, + PostProcessingCommand: "./scripts/windows/post-processing.example.ps1", + PostProcessingEnvironment: []string{"asdasdasdsd=asasd"}, + PostProcessingClientAddress: "dude.greg.example.com", + PostProcessingClientKeyB64: "new-b64-key-xxx111yyyy", + Profile: "tlsserver", + }, + nil, + nil, + }, + } + + // create testing service + storage, err := openStorageWithTestData(t, "putcertclientkey") + if err != nil { + t.Fatal(err) + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("id: %d)", tc.certId), func(t *testing.T) { + err := storage.PutCertClientKey(tc.certId, tc.newKey, tc.updatedAt) + if !helpers_test.ErrorsIs(err, tc.expectedPutErr) { + t.Errorf("expected put error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedPutErr), helpers_test.ErrorToVal(err)) + } + + record, err := storage.GetOneCertById(tc.certId) + if !helpers_test.ErrorsIs(err, tc.expectedGetErr) { + t.Errorf("expected get error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedGetErr), helpers_test.ErrorToVal(err)) + } + + CompareCertificate(t, record, tc.expectedCert) + }) + } +} + func TestPutCertLastAccess(t *testing.T) { testCases := []struct { certId int diff --git a/pkg/storage/keys_put.go b/pkg/storage/keys_put.go index bf65b40f..78d9c0a0 100644 --- a/pkg/storage/keys_put.go +++ b/pkg/storage/keys_put.go @@ -40,7 +40,6 @@ func (store *Storage) PutKeyUpdate(payload private_keys.UpdatePayload) (private_ payload.UpdatedAt, payload.ID, ) - if err != nil { return private_keys.Key{}, err } diff --git a/pkg/storage/keys_put_test.go b/pkg/storage/keys_put_test.go index b7b96765..cf66ecf2 100644 --- a/pkg/storage/keys_put_test.go +++ b/pkg/storage/keys_put_test.go @@ -42,44 +42,50 @@ func TestPutKeyUpdate(t *testing.T) { }, { // update all things private_keys.UpdatePayload{ - ID: 31, - UpdatedAt: 100555, + ID: 31, + Name: new("newNameHere21"), + Description: new("a new desc"), + ApiKey: new("122222"), + ApiKeyNew: new("2222"), + ApiKeyDisabled: new(true), + ApiKeyViaUrl: new(false), + UpdatedAt: 1001111, }, private_keys.Key{ ID: 31, - Name: "certwarden", - Description: "localhost / dev work w/ real cert", + Name: "newNameHere21", + Description: "a new desc", Algorithm: key_crypto.AlgorithmECDSAp256, Pem: `-----BEGIN EC PRIVATE KEY----- red-31 -----END EC PRIVATE KEY----- `, - ApiKey: "key-api-key-31", - ApiKeyNew: "key-api-new-key-31", - ApiKeyDisabled: false, - ApiKeyViaUrl: true, + ApiKey: "122222", + ApiKeyNew: "2222", + ApiKeyDisabled: true, + ApiKeyViaUrl: false, LastAccess: time.Unix(1745952074, 0), CreatedAt: time.Unix(1709327549, 0), - UpdatedAt: time.Unix(100555, 0), + UpdatedAt: time.Unix(1001111, 0), }, nil, 31, private_keys.Key{ ID: 31, - Name: "certwarden", - Description: "localhost / dev work w/ real cert", + Name: "newNameHere21", + Description: "a new desc", Algorithm: key_crypto.AlgorithmECDSAp256, Pem: `-----BEGIN EC PRIVATE KEY----- red-31 -----END EC PRIVATE KEY----- `, - ApiKey: "key-api-key-31", - ApiKeyNew: "key-api-new-key-31", - ApiKeyDisabled: false, - ApiKeyViaUrl: true, + ApiKey: "122222", + ApiKeyNew: "2222", + ApiKeyDisabled: true, + ApiKeyViaUrl: false, LastAccess: time.Unix(1745952074, 0), CreatedAt: time.Unix(1709327549, 0), - UpdatedAt: time.Unix(100555, 0), + UpdatedAt: time.Unix(1001111, 0), }, nil, }, diff --git a/pkg/storage/time.go b/pkg/storage/time_DELETE_ME.go similarity index 81% rename from pkg/storage/time.go rename to pkg/storage/time_DELETE_ME.go index babffac5..93863093 100644 --- a/pkg/storage/time.go +++ b/pkg/storage/time_DELETE_ME.go @@ -1,10 +1,9 @@ package storage -import ( - "time" -) +import "time" // timeNow() returns unix time as a NullInt32 +// TODO: Remove func timeNow() (unixTime int) { return int(time.Now().Unix()) } diff --git a/pkg/storage/types.go b/pkg/storage/types.go index 9b5dc5ce..85288be0 100644 --- a/pkg/storage/types.go +++ b/pkg/storage/types.go @@ -52,35 +52,24 @@ func makeJsonStringSlice(stringSlice []string, nullOk bool) *jsonStringSlice { // jsonCertExtensionSlice is a json formatted string that is a slice of CertExtension type jsonCertExtensionSlice string -// transform JCES into a slice of proper CertExtension +// transform JCES into as slice of proper CertExtension func (jces jsonCertExtensionSlice) toCertExtensionSlice() ([]certificates.CertExtension, error) { if jces == "" { return []certificates.CertExtension{}, nil } // unmarshal the json to the json object - extSlice := []certificates.CertExtensionJSON{} - err := json.Unmarshal([]byte(jces), &extSlice) + cextSlice := []certificates.CertExtension{} + err := json.Unmarshal([]byte(jces), &cextSlice) if err != nil { return nil, err } - // convert json objs to real objs - certExtSlice := []certificates.CertExtension{} - for i := range extSlice { - certExt, err := extSlice[i].ToCertExtension() - if err != nil { - // if invalid data stored, return err - return nil, err - } - certExtSlice = append(certExtSlice, certExt) - } - - return certExtSlice, nil + return cextSlice, nil } // makeJsonCertExtensionSlice creates a JCES from a slice of CertExtensionJSON -func makeJsonCertExtensionSlice(extensionSlice []certificates.CertExtensionJSON, nullOk bool) *jsonCertExtensionSlice { +func makeJsonCertExtensionSlice(extensionSlice []certificates.CertExtension, nullOk bool) *jsonCertExtensionSlice { if extensionSlice == nil { if !nullOk { empty := jsonCertExtensionSlice("[]") From 6b70e2c277b460f31b2740da29cbdda89f29d60a Mon Sep 17 00:00:00 2001 From: "Greg T. Wallace" Date: Thu, 6 Aug 2026 21:20:48 -0400 Subject: [PATCH 11/16] add TestPutCertUpdatedAt * make b64 key post and putable (with validation) * refactor to leverage common func * modify put payload to use time.Time * rename PutCertUpdate function * rename PutCertUpdatedAt function --- pkg/domain/certificates/handlers_post.go | 18 ++- pkg/domain/certificates/handlers_put.go | 18 ++- pkg/domain/certificates/validation.go | 22 ++++ pkg/domain/orders/fulfilling_do.go | 2 +- pkg/domain/orders/handlers_post.go | 3 +- pkg/domain/orders/order_acme_create.go | 3 +- pkg/domain/orders/service.go | 3 +- pkg/storage/certificates_post_test.go | 8 +- pkg/storage/certificates_put.go | 79 ++++-------- pkg/storage/certificates_put_test.go | 147 +++++++++++++++++++++-- 10 files changed, 221 insertions(+), 82 deletions(-) diff --git a/pkg/domain/certificates/handlers_post.go b/pkg/domain/certificates/handlers_post.go index b953a010..45717d87 100644 --- a/pkg/domain/certificates/handlers_post.go +++ b/pkg/domain/certificates/handlers_post.go @@ -35,7 +35,7 @@ type NewPayload struct { PostProcessingCommand *string `json:"post_processing_command"` PostProcessingEnvironment []string `json:"post_processing_environment"` PostProcessingClientAddress *string `json:"post_processing_client_address"` - PostProcessingClientKeyB64 string `json:"-"` + PostProcessingClientKeyB64 *string `json:"post_processing_client_key"` Profile *string `json:"profile"` ApiKey string `json:"-"` ApiKeyViaUrl bool `json:"-"` @@ -188,6 +188,14 @@ func (service *Service) PostNewCert(w http.ResponseWriter, r *http.Request) *out return output.JsonErrValidationFailed(ErrClientAddressBad) } } + // post processing aes key (if specified) + if payload.PostProcessingClientKeyB64 != nil { + valid := clientKeyB64Valid(*payload.PostProcessingClientKeyB64) + if !valid { + service.logger.Debug(ErrPostProcessingClientKeyB64Bad) + return output.JsonErrValidationFailed(ErrPostProcessingClientKeyB64Bad) + } + } // end validation // if new private key was generated, save it to storage @@ -229,14 +237,16 @@ func (service *Service) PostNewCert(w http.ResponseWriter, r *http.Request) *out payload.ApiKeyViaUrl = false payload.CreatedAt = int(time.Now().Unix()) payload.UpdatedAt = payload.CreatedAt - // if client address specified, generate key to save (b64 raw url encoded) - if payload.PostProcessingClientAddress != nil && *payload.PostProcessingClientAddress != "" { - payload.PostProcessingClientKeyB64, err = randomness.GenerateAES256KeyAsBase64RawUrl() + + // if client address specified but no aes key, generate key to save (b64 raw url encoded) + if payload.PostProcessingClientAddress != nil && *payload.PostProcessingClientAddress != "" && payload.PostProcessingClientKeyB64 == nil { + key, err := randomness.GenerateAES256KeyAsBase64RawUrl() if err != nil { err = fmt.Errorf("failed to generate client key for certificate (%s)", err) service.logger.Error(err) return output.JsonErrInternal(err) } + payload.PostProcessingClientKeyB64 = &key } // save new cert diff --git a/pkg/domain/certificates/handlers_put.go b/pkg/domain/certificates/handlers_put.go index b1faebfd..02f41479 100644 --- a/pkg/domain/certificates/handlers_put.go +++ b/pkg/domain/certificates/handlers_put.go @@ -30,11 +30,12 @@ type UpdatePayload struct { PostProcessingCommand *string `json:"post_processing_command"` PostProcessingEnvironment []string `json:"post_processing_environment"` PostProcessingClientAddress *string `json:"post_processing_client_address"` + PostProcessingClientKeyB64 *string `json:"post_processing_client_key"` Profile *string `json:"profile"` ApiKey *string `json:"api_key"` ApiKeyNew *string `json:"api_key_new"` ApiKeyViaUrl *bool `json:"api_key_via_url"` - UpdatedAt int `json:"-"` + UpdatedAt time.Time `json:"-"` } // PutDetailsCert is a handler that sets various details about a cert and saves @@ -120,9 +121,7 @@ func (service *Service) PutDetailsCert(w http.ResponseWriter, r *http.Request) * // post processing command & env are optional but nothing to validate // post processing address - if payload.PostProcessingClientAddress == nil { - payload.PostProcessingClientAddress = new(string) - } else if *payload.PostProcessingClientAddress != "" { + if payload.PostProcessingClientAddress != nil && *payload.PostProcessingClientAddress != "" { valid := validation.DomainAndPortValid(*payload.PostProcessingClientAddress) if !valid { service.logger.Debug(ErrClientAddressBad) @@ -130,10 +129,19 @@ func (service *Service) PutDetailsCert(w http.ResponseWriter, r *http.Request) * } } + // post processing aes key (if specified) + if payload.PostProcessingClientKeyB64 != nil { + valid := clientKeyB64Valid(*payload.PostProcessingClientKeyB64) + if !valid { + service.logger.Debug(ErrPostProcessingClientKeyB64Bad) + return output.JsonErrValidationFailed(ErrPostProcessingClientKeyB64Bad) + } + } + // end validation // add additional details to the payload before saving - payload.UpdatedAt = int(time.Now().Unix()) + payload.UpdatedAt = time.Now() // save account name and desc to storage, which also returns the account id with new // name and description diff --git a/pkg/domain/certificates/validation.go b/pkg/domain/certificates/validation.go index ccd51816..921854a7 100644 --- a/pkg/domain/certificates/validation.go +++ b/pkg/domain/certificates/validation.go @@ -3,7 +3,9 @@ package certificates import ( "certwarden-backend/pkg/output" "certwarden-backend/pkg/validation" + "crypto/aes" "database/sql" + "encoding/base64" "errors" "fmt" ) @@ -28,6 +30,9 @@ var ( // domain ErrDomainBad = errors.New("domain or subject name not valid") ErrClientAddressBad = errors.New("client address is not valid") + + // aes key + ErrPostProcessingClientKeyB64Bad = errors.New("post processing aes key not valid") ) // GetCertificate returns the Certificate for the specified id. @@ -124,3 +129,20 @@ func subjectAltsValid(alts []string) bool { return true } + +// clientKeyB64Valid ensures the string is a base64 RawURL encoded AES key +func clientKeyB64Valid(b64Key string) bool { + // decode AES key + aesKey, err := base64.RawURLEncoding.DecodeString(b64Key) + if err != nil { + return false + } + + // ensure key is a proper size and can create a cipher + _, err = aes.NewCipher(aesKey) + if err != nil { + return false + } + + return true +} diff --git a/pkg/domain/orders/fulfilling_do.go b/pkg/domain/orders/fulfilling_do.go index fb060b05..5472cecc 100644 --- a/pkg/domain/orders/fulfilling_do.go +++ b/pkg/domain/orders/fulfilling_do.go @@ -25,7 +25,7 @@ func (j *orderFulfillJob) Do(workerID int) { // update certificate timestamp after fulfiller is done defer func() { - err = j.service.storage.UpdateCertUpdatedTime(order.Certificate.ID) + err = j.service.storage.PutCertUpdatedAt(order.Certificate.ID, time.Now()) if err != nil { j.service.logger.Errorf("orders: fulfilling worker %d: update cert time error: %s", workerID, err) } diff --git a/pkg/domain/orders/handlers_post.go b/pkg/domain/orders/handlers_post.go index ac5d08af..092509b0 100644 --- a/pkg/domain/orders/handlers_post.go +++ b/pkg/domain/orders/handlers_post.go @@ -7,6 +7,7 @@ import ( "errors" "net/http" "strconv" + "time" "github.com/julienschmidt/httprouter" ) @@ -191,7 +192,7 @@ func (service *Service) RevokeOrder(w http.ResponseWriter, r *http.Request) *out } // update certificate timestamp - err = service.storage.UpdateCertUpdatedTime(certId) + err = service.storage.PutCertUpdatedAt(certId, time.Now()) if err != nil { service.logger.Error(err) // no return diff --git a/pkg/domain/orders/order_acme_create.go b/pkg/domain/orders/order_acme_create.go index 027377f6..129f9926 100644 --- a/pkg/domain/orders/order_acme_create.go +++ b/pkg/domain/orders/order_acme_create.go @@ -4,6 +4,7 @@ import ( "certwarden-backend/pkg/output" "database/sql" "errors" + "time" ) // placeNewOrderAndFulfill creates a new ACME order for the specified Certificate ID, @@ -59,7 +60,7 @@ func (service *Service) placeNewOrderAndFulfill(certId int, highPriority bool) ( } // update certificate timestamp - err = service.storage.UpdateCertUpdatedTime(cert.ID) + err = service.storage.PutCertUpdatedAt(cert.ID, time.Now()) if err != nil { service.logger.Error(err) // no return diff --git a/pkg/domain/orders/service.go b/pkg/domain/orders/service.go index 9378e778..201db312 100644 --- a/pkg/domain/orders/service.go +++ b/pkg/domain/orders/service.go @@ -12,6 +12,7 @@ import ( "net/http" "os/exec" "sync" + "time" "github.com/scaleway/scaleway-sdk-go/logger" "go.uber.org/zap" @@ -60,7 +61,7 @@ type Storage interface { GetNewestIncompleteCertOrderId(certId int) (orderId int, err error) // certs - UpdateCertUpdatedTime(certId int) (err error) + PutCertUpdatedAt(certId int, updatedAt time.Time) (err error) } // service struct diff --git a/pkg/storage/certificates_post_test.go b/pkg/storage/certificates_post_test.go index 87fe46a0..c3493246 100644 --- a/pkg/storage/certificates_post_test.go +++ b/pkg/storage/certificates_post_test.go @@ -50,7 +50,7 @@ func TestPostNewCert(t *testing.T) { PostProcessingCommand: new("./run-me.py"), PostProcessingEnvironment: []string{"a=123", "b=456"}, PostProcessingClientAddress: new("endpoint.example.com"), - PostProcessingClientKeyB64: "an aes key", + PostProcessingClientKeyB64: new("an aes key"), Profile: new("test-prof"), ApiKey: "12345fffff", ApiKeyViaUrl: true, @@ -115,7 +115,7 @@ func TestPostNewCert(t *testing.T) { PostProcessingCommand: new("./run-me.py"), PostProcessingEnvironment: []string{}, PostProcessingClientAddress: new("endpoint.example.com"), - PostProcessingClientKeyB64: "an aes key", + PostProcessingClientKeyB64: new("an aes key"), Profile: new("test-prof"), ApiKey: "12345fffff", ApiKeyViaUrl: true, @@ -154,7 +154,7 @@ func TestPostNewCert(t *testing.T) { PostProcessingCommand: new("./run-me.py"), PostProcessingEnvironment: []string{"a=123", "b=456"}, PostProcessingClientAddress: new("endpoint.example.com"), - PostProcessingClientKeyB64: "an aes key", + PostProcessingClientKeyB64: new("an aes key"), Profile: new("test-prof"), ApiKey: "12345fffff", ApiKeyViaUrl: true, @@ -193,7 +193,7 @@ func TestPostNewCert(t *testing.T) { PostProcessingCommand: new("./run-me.py"), PostProcessingEnvironment: []string{"a=123", "b=456"}, PostProcessingClientAddress: new("endpoint.example.com"), - PostProcessingClientKeyB64: "an aes key", + PostProcessingClientKeyB64: new("an aes key"), Profile: new("test-prof"), ApiKey: "12345fffff", ApiKeyViaUrl: true, diff --git a/pkg/storage/certificates_put.go b/pkg/storage/certificates_put.go index ad3df704..60329bbb 100644 --- a/pkg/storage/certificates_put.go +++ b/pkg/storage/certificates_put.go @@ -36,10 +36,11 @@ func (store *Storage) PutCertUpdate(payload certificates.UpdatePayload) (certifi post_processing_command = case when $15 is null then post_processing_command else $15 end, post_processing_environment = case when $16 is null then post_processing_environment else $16 end, post_processing_client_address = case when $17 is null then post_processing_client_address else $17 end, - profile = case when $18 is null then profile else $18 end, - updated_at = $19 + post_processing_client_key = case when $18 is null then post_processing_client_key else $18 end, + profile = case when $19 is null then profile else $19 end, + updated_at = $20 WHERE - id = $20 + id = $21 ` res, err := store.db.ExecContext(ctx, query, @@ -60,8 +61,9 @@ func (store *Storage) PutCertUpdate(payload certificates.UpdatePayload) (certifi payload.PostProcessingCommand, makeJsonStringSlice(payload.PostProcessingEnvironment, true), payload.PostProcessingClientAddress, + payload.PostProcessingClientKeyB64, payload.Profile, - payload.UpdatedAt, + payload.UpdatedAt.Unix(), payload.ID, ) if err != nil { @@ -86,31 +88,16 @@ func (store *Storage) PutCertUpdate(payload certificates.UpdatePayload) (certifi return updatedCert, nil } -// UpdateCertUpdatedTime sets the specified order's updated_at to now -func (store *Storage) UpdateCertUpdatedTime(certId int) (err error) { - ctx, cancel := context.WithTimeout(store.shutdownContext, store.timeout) - defer cancel() - - query := ` - UPDATE - certificates - SET - updated_at = $1 - WHERE - id = $2 - ` - - _, err = store.db.ExecContext(ctx, query, - time.Now().Unix(), - certId, - ) - if err != nil { - return err +// PutCertUpdatedAt sets the specified cert's updated_at +func (store *Storage) PutCertUpdatedAt(certId int, updatedAt time.Time) (err error) { + // leverage main Put function + payload := certificates.UpdatePayload{ + ID: certId, + UpdatedAt: updatedAt, } - // TODO: Handle 0 rows updated. - - return nil + _, err = store.PutCertUpdate(payload) + return err } // PutCertNewApiKey sets a cert's new api key and updates the updated at time @@ -173,39 +160,15 @@ func (store *Storage) PutCertApiKey(certId int, apiKey string, updateTimeUnix in // PutCertClientKey sets a cert's client key and updates the updated at time func (store *Storage) PutCertClientKey(certId int, clientKeyB64 string, updatedAt time.Time) (err error) { - // database action - ctx, cancel := context.WithTimeout(store.shutdownContext, store.timeout) - defer cancel() - - query := ` - UPDATE - certificates - SET - post_processing_client_key = $1, - updated_at = $2 - WHERE - id = $3 - ` - - res, err := store.db.ExecContext(ctx, query, - clientKeyB64, - updatedAt.Unix(), - certId, - ) - if err != nil { - return err + // leverage main Put function + payload := certificates.UpdatePayload{ + ID: certId, + PostProcessingClientKeyB64: &clientKeyB64, + UpdatedAt: updatedAt, } - // verify update actually happened - rowsAffected, err := res.RowsAffected() - if err != nil { - return err - } - if rowsAffected != 1 { - return errors.Join(fmt.Errorf("expected 1 row update, but got '%d'", rowsAffected), ErrWrongUpdateRowCount) - } - - return nil + _, err = store.PutCertUpdate(payload) + return err } // PutCertLastAccess sets a cert's last access time diff --git a/pkg/storage/certificates_put_test.go b/pkg/storage/certificates_put_test.go index 160ae041..fa3dfd49 100644 --- a/pkg/storage/certificates_put_test.go +++ b/pkg/storage/certificates_put_test.go @@ -13,8 +13,6 @@ import ( ) // TODO: - -// UpdateCertUpdatedTime // PutCertNewApiKey // PutCertApiKey @@ -75,11 +73,12 @@ func TestPutDetailsCert(t *testing.T) { PostProcessingCommand: new("./app.exe"), PostProcessingEnvironment: []string{"a=123", "b=zba"}, PostProcessingClientAddress: new("xyz.com"), + PostProcessingClientKeyB64: new("aaa888aaabbbccc"), Profile: new("new prof 2"), ApiKey: new("api-key---"), ApiKeyNew: new("api-key-new---"), ApiKeyViaUrl: new(false), - UpdatedAt: 222223333, + UpdatedAt: time.Unix(222223333, 0), }, certificates.Certificate{ ID: 18, @@ -114,7 +113,7 @@ func TestPutDetailsCert(t *testing.T) { PostProcessingCommand: "./app.exe", PostProcessingEnvironment: []string{"a=123", "b=zba"}, PostProcessingClientAddress: "xyz.com", - PostProcessingClientKeyB64: "aaaaaaaaaaaaaaaaaaaaaaaaaaa-ccccccccccccccc", + PostProcessingClientKeyB64: "aaa888aaabbbccc", Profile: "new prof 2", }, nil, @@ -152,7 +151,7 @@ func TestPutDetailsCert(t *testing.T) { PostProcessingCommand: "./app.exe", PostProcessingEnvironment: []string{"a=123", "b=zba"}, PostProcessingClientAddress: "xyz.com", - PostProcessingClientKeyB64: "aaaaaaaaaaaaaaaaaaaaaaaaaaa-ccccccccccccccc", + PostProcessingClientKeyB64: "aaa888aaabbbccc", Profile: "new prof 2", }, nil, @@ -161,7 +160,7 @@ func TestPutDetailsCert(t *testing.T) { { certificates.UpdatePayload{ ID: 27, - UpdatedAt: 15151515, + UpdatedAt: time.Unix(15151515, 0), }, certificates.Certificate{ ID: 27, @@ -227,7 +226,7 @@ func TestPutDetailsCert(t *testing.T) { ID: 27, SubjectAltNames: []string{}, PostProcessingClientAddress: new("someaddr.example.com"), - UpdatedAt: 151222215, + UpdatedAt: time.Unix(151222215, 0), }, certificates.Certificate{ ID: 27, @@ -314,6 +313,140 @@ func TestPutDetailsCert(t *testing.T) { } } +func TestPutCertUpdatedAt(t *testing.T) { + testCases := []struct { + certId int + updatedAt time.Time + + expectedCert certificates.Certificate + expectedPutErr error + expectedGetErr error + }{ + { // invalid key id + -1, + time.Unix(28888111, 0), + certificates.Certificate{}, + storage.ErrWrongUpdateRowCount, + sql.ErrNoRows, + }, + { // invalid key id + 500, + time.Unix(28888222, 0), + certificates.Certificate{}, + storage.ErrWrongUpdateRowCount, + sql.ErrNoRows, + }, + // do update + { + 18, + time.Unix(0, 0), + certificates.Certificate{ + ID: 18, + Name: "serverdefault", + Description: "its a decript", + Key: key31, + Account: acmeAcct2, + Subject: "desk.dude.example.com", + SubjectAltNames: []string{"test011.test.example.com"}, + Organization: "my org", + OrganizationalUnit: "my ou", + Country: "your country", + State: "a state", + City: "springfield", + CSRExtraExtensions: []certificates.CertExtension{ + { + Extension: pkix.Extension{ + Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24}, + Critical: false, + Value: []byte{0x30, 0x03, 0x02, 0x01, 0x05}, + }, + Description: "OCSP Must Staple", + }, + }, + PreferredRootCN: "ISRG Root X1", + LastAccess: time.Unix(1745952074, 0), + CreatedAt: time.Unix(1709327717, 0), + UpdatedAt: time.Unix(0, 0), + ApiKey: "api-secret-18", + ApiKeyNew: "api-new-secret-18", + ApiKeyViaUrl: true, + PostProcessingCommand: "./scripts/windows/post-processing.example.ps1", + PostProcessingEnvironment: []string{"asdasdasdsd=asasd"}, + PostProcessingClientAddress: "dude.greg.example.com", + PostProcessingClientKeyB64: "aaaaaaaaaaaaaaaaaaaaaaaaaaa-ccccccccccccccc", + Profile: "tlsserver", + }, + nil, + nil, + }, + { + 18, + time.Unix(333444442, 0), + certificates.Certificate{ + ID: 18, + Name: "serverdefault", + Description: "its a decript", + Key: key31, + Account: acmeAcct2, + Subject: "desk.dude.example.com", + SubjectAltNames: []string{"test011.test.example.com"}, + Organization: "my org", + OrganizationalUnit: "my ou", + Country: "your country", + State: "a state", + City: "springfield", + CSRExtraExtensions: []certificates.CertExtension{ + { + Extension: pkix.Extension{ + Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24}, + Critical: false, + Value: []byte{0x30, 0x03, 0x02, 0x01, 0x05}, + }, + Description: "OCSP Must Staple", + }, + }, + PreferredRootCN: "ISRG Root X1", + LastAccess: time.Unix(1745952074, 0), + CreatedAt: time.Unix(1709327717, 0), + UpdatedAt: time.Unix(333444442, 0), + ApiKey: "api-secret-18", + ApiKeyNew: "api-new-secret-18", + ApiKeyViaUrl: true, + PostProcessingCommand: "./scripts/windows/post-processing.example.ps1", + PostProcessingEnvironment: []string{"asdasdasdsd=asasd"}, + PostProcessingClientAddress: "dude.greg.example.com", + PostProcessingClientKeyB64: "aaaaaaaaaaaaaaaaaaaaaaaaaaa-ccccccccccccccc", + Profile: "tlsserver", + }, + nil, + nil, + }, + } + + // create testing service + storage, err := openStorageWithTestData(t, "putcertupdatedat") + if err != nil { + t.Fatal(err) + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("id: %d)", tc.certId), func(t *testing.T) { + err := storage.PutCertUpdatedAt(tc.certId, tc.updatedAt) + if !helpers_test.ErrorsIs(err, tc.expectedPutErr) { + t.Errorf("expected put error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedPutErr), helpers_test.ErrorToVal(err)) + } + + record, err := storage.GetOneCertById(tc.certId) + if !helpers_test.ErrorsIs(err, tc.expectedGetErr) { + t.Errorf("expected get error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedGetErr), helpers_test.ErrorToVal(err)) + } + + CompareCertificate(t, record, tc.expectedCert) + }) + } + +} + func TestPutCertClientKey(t *testing.T) { testCases := []struct { certId int From b634e48cef032e3f98e9eee83b2d61556fe7ac63 Mon Sep 17 00:00:00 2001 From: "Greg T. Wallace" Date: Fri, 7 Aug 2026 18:49:37 -0400 Subject: [PATCH 12/16] add tests for put api keys * refactor some time types to time.Time * rename some funcs for clarity * --- pkg/domain/certificates/handlers_delete.go | 4 +- pkg/domain/certificates/handlers_post.go | 2 +- pkg/domain/certificates/services.go | 12 +- pkg/domain/private_keys/handlers_delete.go | 4 +- pkg/domain/private_keys/handlers_post.go | 2 +- pkg/domain/private_keys/handlers_put.go | 18 +- pkg/domain/private_keys/service.go | 5 +- pkg/storage/accounts_get_test.go | 1 - pkg/storage/certificates_delete_test.go | 2 +- pkg/storage/certificates_put.go | 68 ++---- pkg/storage/certificates_put_test.go | 263 ++++++++++++++++++++- pkg/storage/keys_put.go | 14 +- pkg/storage/keys_put_test.go | 46 ++-- 13 files changed, 332 insertions(+), 109 deletions(-) diff --git a/pkg/domain/certificates/handlers_delete.go b/pkg/domain/certificates/handlers_delete.go index 139ffa87..9084e8b3 100644 --- a/pkg/domain/certificates/handlers_delete.go +++ b/pkg/domain/certificates/handlers_delete.go @@ -76,7 +76,7 @@ func (service *Service) RemoveOldApiKey(w http.ResponseWriter, r *http.Request) // update storage // set current api key from new key - err = service.storage.PutCertApiKey(certId, cert.ApiKeyNew, int(time.Now().Unix())) + err = service.storage.PutCertApiKey(certId, cert.ApiKeyNew, time.Now()) if err != nil { service.logger.Error(err) return output.JsonErrStorageGeneric(err) @@ -84,7 +84,7 @@ func (service *Service) RemoveOldApiKey(w http.ResponseWriter, r *http.Request) cert.ApiKey = cert.ApiKeyNew // set new key to blank - err = service.storage.PutCertNewApiKey(certId, "", int(time.Now().Unix())) + err = service.storage.PutCertApiKeyNew(certId, "", time.Now()) if err != nil { service.logger.Error(err) return output.JsonErrStorageGeneric(err) diff --git a/pkg/domain/certificates/handlers_post.go b/pkg/domain/certificates/handlers_post.go index 45717d87..dff274d9 100644 --- a/pkg/domain/certificates/handlers_post.go +++ b/pkg/domain/certificates/handlers_post.go @@ -304,7 +304,7 @@ func (service *Service) StageNewApiKey(w http.ResponseWriter, r *http.Request) * } // update storage - err = service.storage.PutCertNewApiKey(certId, newApiKey, int(time.Now().Unix())) + err = service.storage.PutCertApiKeyNew(certId, newApiKey, time.Now()) if err != nil { service.logger.Error(err) return output.JsonErrStorageGeneric(err) diff --git a/pkg/domain/certificates/services.go b/pkg/domain/certificates/services.go index d6b97357..431637bc 100644 --- a/pkg/domain/certificates/services.go +++ b/pkg/domain/certificates/services.go @@ -27,17 +27,17 @@ type App interface { // Storage interface for storage functions type Storage interface { GetAllCerts(q pagination_sort.Query) (certs []Certificate, totalRowCount int, err error) - GetOneCertById(id int) (cert Certificate, err error) - GetOneCertByName(name string) (cert Certificate, err error) + GetOneCertById(id int) (Certificate, error) + GetOneCertByName(name string) (Certificate, error) PostNewCert(payload NewPayload) (Certificate, error) PutCertUpdate(payload UpdatePayload) (Certificate, error) - PutCertApiKey(certId int, apiKey string, updateTimeUnix int) (err error) - PutCertNewApiKey(certId int, newApiKey string, updateTimeUnix int) (err error) - PutCertClientKey(certId int, newClientKeyB64 string, updatedAt time.Time) (err error) + PutCertApiKey(certId int, apiKey string, updatedAt time.Time) error + PutCertApiKeyNew(certId int, apiKeyNew string, updatedAt time.Time) error + PutCertClientKey(certId int, newClientKeyB64 string, updatedAt time.Time) error - DeleteCert(id int) (err error) + DeleteCert(id int) error PostNewKey(private_keys.NewPayload) (private_keys.Key, error) } diff --git a/pkg/domain/private_keys/handlers_delete.go b/pkg/domain/private_keys/handlers_delete.go index a717cbb0..8571868f 100644 --- a/pkg/domain/private_keys/handlers_delete.go +++ b/pkg/domain/private_keys/handlers_delete.go @@ -89,7 +89,7 @@ func (service *Service) RemoveOldApiKey(w http.ResponseWriter, r *http.Request) // update storage // set current api key from new key - err = service.storage.PutKeyApiKey(keyId, key.ApiKeyNew, int(time.Now().Unix())) + err = service.storage.PutKeyApiKey(keyId, key.ApiKeyNew, time.Now()) if err != nil { service.logger.Error(err) return output.JsonErrStorageGeneric(err) @@ -97,7 +97,7 @@ func (service *Service) RemoveOldApiKey(w http.ResponseWriter, r *http.Request) key.ApiKey = key.ApiKeyNew // set new key to blank - err = service.storage.PutKeyNewApiKey(keyId, "", int(time.Now().Unix())) + err = service.storage.PutKeyApiKeyNew(keyId, "", time.Now()) if err != nil { service.logger.Error(err) return output.JsonErrStorageGeneric(err) diff --git a/pkg/domain/private_keys/handlers_post.go b/pkg/domain/private_keys/handlers_post.go index 0e491b9c..81d0c979 100644 --- a/pkg/domain/private_keys/handlers_post.go +++ b/pkg/domain/private_keys/handlers_post.go @@ -154,7 +154,7 @@ func (service *Service) StageNewApiKey(w http.ResponseWriter, r *http.Request) * } // update storage - err = service.storage.PutKeyNewApiKey(keyId, newApiKey, int(time.Now().Unix())) + err = service.storage.PutKeyApiKeyNew(keyId, newApiKey, time.Now()) if err != nil { service.logger.Error(err) return output.JsonErrStorageGeneric(err) diff --git a/pkg/domain/private_keys/handlers_put.go b/pkg/domain/private_keys/handlers_put.go index 0fde09ae..72d1a38c 100644 --- a/pkg/domain/private_keys/handlers_put.go +++ b/pkg/domain/private_keys/handlers_put.go @@ -13,14 +13,14 @@ import ( // UpdatePayload is the struct for editing an existing Key's // information (only certain fields are editable) type UpdatePayload struct { - ID int `json:"-"` - Name *string `json:"name"` - Description *string `json:"description"` - ApiKey *string `json:"api_key"` - ApiKeyNew *string `json:"api_key_new"` - ApiKeyDisabled *bool `json:"api_key_disabled"` - ApiKeyViaUrl *bool `json:"api_key_via_url"` - UpdatedAt int `json:"-"` + ID int `json:"-"` + Name *string `json:"name"` + Description *string `json:"description"` + ApiKey *string `json:"api_key"` + ApiKeyNew *string `json:"api_key_new"` + ApiKeyDisabled *bool `json:"api_key_disabled"` + ApiKeyViaUrl *bool `json:"api_key_via_url"` + UpdatedAt time.Time `json:"-"` } // PutKeyUpdate updates a Key that already exists in storage. @@ -67,7 +67,7 @@ func (service *Service) PutKeyUpdate(w http.ResponseWriter, r *http.Request) *ou // end validation // add additional details to the payload before saving - payload.UpdatedAt = int(time.Now().Unix()) + payload.UpdatedAt = time.Now() // save updated key info to storage updatedKey, err := service.storage.PutKeyUpdate(payload) diff --git a/pkg/domain/private_keys/service.go b/pkg/domain/private_keys/service.go index ac3ae7f7..1c8bd9ba 100644 --- a/pkg/domain/private_keys/service.go +++ b/pkg/domain/private_keys/service.go @@ -4,6 +4,7 @@ import ( "certwarden-backend/pkg/output" "certwarden-backend/pkg/pagination_sort" "errors" + "time" "go.uber.org/zap" ) @@ -26,8 +27,8 @@ type Storage interface { PostNewKey(NewPayload) (Key, error) PutKeyUpdate(UpdatePayload) (Key, error) - PutKeyApiKey(keyId int, apiKey string, updateTimeUnix int) (err error) - PutKeyNewApiKey(keyId int, newApiKey string, updateTimeUnix int) error + PutKeyApiKey(keyId int, apiKey string, updatedAt time.Time) error + PutKeyApiKeyNew(keyId int, apiKeyNew string, updatedAt time.Time) error DeleteKey(int) error diff --git a/pkg/storage/accounts_get_test.go b/pkg/storage/accounts_get_test.go index fff110bc..15122c85 100644 --- a/pkg/storage/accounts_get_test.go +++ b/pkg/storage/accounts_get_test.go @@ -82,7 +82,6 @@ var ( } ) -// TODO func TestGetAllAcmeAccounts(t *testing.T) { testCases := []struct { q pagination_sort.Query diff --git a/pkg/storage/certificates_delete_test.go b/pkg/storage/certificates_delete_test.go index 313b984c..daea0d1a 100644 --- a/pkg/storage/certificates_delete_test.go +++ b/pkg/storage/certificates_delete_test.go @@ -17,7 +17,7 @@ func TestDeleteCert(t *testing.T) { }{ {-12, sql.ErrNoRows, certificates.Certificate{}, sql.ErrNoRows}, // non-existent {2, sql.ErrNoRows, certificates.Certificate{}, sql.ErrNoRows}, // non-existent - {18, nil, certificates.Certificate{}, sql.ErrNoRows}, // not in use, gets deleted (Maybe TODO: Prevent delete from app to server's ssl cert) + {18, nil, certificates.Certificate{}, sql.ErrNoRows}, // not in use, gets deleted {30, nil, certificates.Certificate{}, sql.ErrNoRows}, // not in use, gets deleted {32, nil, certificates.Certificate{}, sql.ErrNoRows}, // not in use, gets deleted {35, nil, certificates.Certificate{}, sql.ErrNoRows}, // not in use, gets deleted diff --git a/pkg/storage/certificates_put.go b/pkg/storage/certificates_put.go index 60329bbb..53846b7e 100644 --- a/pkg/storage/certificates_put.go +++ b/pkg/storage/certificates_put.go @@ -100,62 +100,30 @@ func (store *Storage) PutCertUpdatedAt(certId int, updatedAt time.Time) (err err return err } -// PutCertNewApiKey sets a cert's new api key and updates the updated at time -func (store *Storage) PutCertNewApiKey(certId int, newApiKey string, updateTimeUnix int) (err error) { - // database action - ctx, cancel := context.WithTimeout(store.shutdownContext, store.timeout) - defer cancel() - - query := ` - UPDATE - certificates - SET - api_key_new = $1, - updated_at = $2 - WHERE - id = $3 - ` - - _, err = store.db.ExecContext(ctx, query, - newApiKey, - updateTimeUnix, - certId, - ) - - if err != nil { - return err +// PutCertApiKey sets a cert's api key and updates the updated at time +func (store *Storage) PutCertApiKey(certId int, apiKey string, updatedAt time.Time) (err error) { + // leverage main Put function + payload := certificates.UpdatePayload{ + ID: certId, + ApiKey: &apiKey, + UpdatedAt: updatedAt, } - return nil + _, err = store.PutCertUpdate(payload) + return err } -// PutCertApiKey sets a cert's api key and updates the updated at time -func (store *Storage) PutCertApiKey(certId int, apiKey string, updateTimeUnix int) (err error) { - // database action - ctx, cancel := context.WithTimeout(store.shutdownContext, store.timeout) - defer cancel() - - query := ` - UPDATE - certificates - SET - api_key = $1, - updated_at = $2 - WHERE - id = $3 - ` - - _, err = store.db.ExecContext(ctx, query, - apiKey, - updateTimeUnix, - certId, - ) - - if err != nil { - return err +// PutCertApiKeyNew sets a cert's new api key and updates the updated at time +func (store *Storage) PutCertApiKeyNew(certId int, apiKeyNew string, updatedAt time.Time) (err error) { + // leverage main Put function + payload := certificates.UpdatePayload{ + ID: certId, + ApiKeyNew: &apiKeyNew, + UpdatedAt: updatedAt, } - return nil + _, err = store.PutCertUpdate(payload) + return err } // PutCertClientKey sets a cert's client key and updates the updated at time diff --git a/pkg/storage/certificates_put_test.go b/pkg/storage/certificates_put_test.go index fa3dfd49..1bcdb8f4 100644 --- a/pkg/storage/certificates_put_test.go +++ b/pkg/storage/certificates_put_test.go @@ -12,10 +12,6 @@ import ( "time" ) -// TODO: -// PutCertNewApiKey -// PutCertApiKey - func TestPutDetailsCert(t *testing.T) { testCases := []struct { payload certificates.UpdatePayload @@ -313,6 +309,265 @@ func TestPutDetailsCert(t *testing.T) { } } +func TestPutCertApiKey(t *testing.T) { + testCases := []struct { + certId int + apiKey string + updateTime time.Time + + expectedCert certificates.Certificate + expectedPutErr error + expectedGetErr error + }{ + { // invalid id + -1, + "fake", + time.Unix(100005, 0), + certificates.Certificate{}, + storage.ErrWrongUpdateRowCount, + sql.ErrNoRows, + }, + { // invalid id + 500, + "anotherfake", + time.Unix(10005000, 0), + certificates.Certificate{}, + storage.ErrWrongUpdateRowCount, + sql.ErrNoRows, + }, + // do some updates + { + 18, + "somekey31cert", + time.Unix(101300522, 0), + certificates.Certificate{ + ID: 18, + Name: "serverdefault", + Description: "its a decript", + Key: key31, + Account: acmeAcct2, + Subject: "desk.dude.example.com", + SubjectAltNames: []string{"test011.test.example.com"}, + Organization: "my org", + OrganizationalUnit: "my ou", + Country: "your country", + State: "a state", + City: "springfield", + CSRExtraExtensions: []certificates.CertExtension{ + { + Extension: pkix.Extension{ + Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24}, + Critical: false, + Value: []byte{0x30, 0x03, 0x02, 0x01, 0x05}, + }, + Description: "OCSP Must Staple", + }, + }, + PreferredRootCN: "ISRG Root X1", + LastAccess: time.Unix(1745952074, 0), + CreatedAt: time.Unix(1709327717, 0), + UpdatedAt: time.Unix(101300522, 0), + ApiKey: "somekey31cert", + ApiKeyNew: "api-new-secret-18", + ApiKeyViaUrl: true, + PostProcessingCommand: "./scripts/windows/post-processing.example.ps1", + PostProcessingEnvironment: []string{"asdasdasdsd=asasd"}, + PostProcessingClientAddress: "dude.greg.example.com", + PostProcessingClientKeyB64: "aaaaaaaaaaaaaaaaaaaaaaaaaaa-ccccccccccccccc", + Profile: "tlsserver", + }, + nil, + nil, + }, + { + 26, + "", + time.Unix(0, 0), + certificates.Certificate{ + ID: 26, + Name: "test008.test.example.com", + Description: "", + Key: key55, + Account: acmeAcct1, + Subject: "test008.test.example.com", + SubjectAltNames: []string{}, + Organization: "", + OrganizationalUnit: "", + Country: "", + State: "", + City: "", + CSRExtraExtensions: []certificates.CertExtension{}, + PreferredRootCN: "", + LastAccess: time.Unix(0, 0), + CreatedAt: time.Unix(1743170701, 0), + UpdatedAt: time.Unix(0, 0), + ApiKey: "", + ApiKeyNew: "", + ApiKeyViaUrl: false, + PostProcessingCommand: "./scripts/windows/post-processing.example.ps1", + PostProcessingEnvironment: []string{}, + PostProcessingClientAddress: "test008.test.example.com", + PostProcessingClientKeyB64: "", + Profile: "", + }, + nil, + nil, + }, + } + + // create testing service + storage, err := openStorageWithTestData(t, "putcertapikey") + if err != nil { + t.Fatal(err) + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("id: %d)", tc.certId), func(t *testing.T) { + err := storage.PutCertApiKey(tc.certId, tc.apiKey, tc.updateTime) + if !helpers_test.ErrorsIs(err, tc.expectedPutErr) { + t.Errorf("expected put cert api key error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedPutErr), helpers_test.ErrorToVal(err)) + } + + cert, err := storage.GetOneCertById(tc.certId) + if !helpers_test.ErrorsIs(err, tc.expectedGetErr) { + t.Errorf("expected get cert error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedGetErr), helpers_test.ErrorToVal(err)) + } + + CompareCertificate(t, cert, tc.expectedCert) + }) + } +} + +func TestCertApiKeyNew(t *testing.T) { + testCases := []struct { + certId int + apiKeyNew string + updatedAt time.Time + + expectedCert certificates.Certificate + expectedPutErr error + expectedGetErr error + }{ + { // invalid id + -1, + "", + time.Unix(10999051, 0), + certificates.Certificate{}, + storage.ErrWrongUpdateRowCount, + sql.ErrNoRows, + }, + { // invalid id + 500, + "anotherfake", + time.Unix(10445000, 0), + certificates.Certificate{}, + storage.ErrWrongUpdateRowCount, + sql.ErrNoRows, + }, + // do some updates + { + 27, + "certkey27", + time.Unix(102202305, 0), + certificates.Certificate{ + ID: 27, + Name: "test008.test.example.com-p", + Description: "", + Key: key56, + Account: acmeAcct2, + Subject: "test008.test.example.com", + SubjectAltNames: []string{"test011.test.example.com", "*.test011.test.example.com"}, + Organization: "", + OrganizationalUnit: "", + Country: "", + State: "", + City: "", + CSRExtraExtensions: []certificates.CertExtension{}, + PreferredRootCN: "", + LastAccess: time.Unix(0, 0), + CreatedAt: time.Unix(1743171060, 0), + UpdatedAt: time.Unix(102202305, 0), + ApiKey: "api-secret-27", + ApiKeyNew: "certkey27", + ApiKeyViaUrl: false, + PostProcessingCommand: "", + PostProcessingEnvironment: []string{}, + PostProcessingClientAddress: "", + PostProcessingClientKeyB64: "", + Profile: "", + }, + nil, + nil, + }, + { + 18, + "", + time.Unix(0, 0), + certificates.Certificate{ + ID: 18, + Name: "serverdefault", + Description: "its a decript", + Key: key31, + Account: acmeAcct2, + Subject: "desk.dude.example.com", + SubjectAltNames: []string{"test011.test.example.com"}, + Organization: "my org", + OrganizationalUnit: "my ou", + Country: "your country", + State: "a state", + City: "springfield", + CSRExtraExtensions: []certificates.CertExtension{ + { + Extension: pkix.Extension{ + Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24}, + Critical: false, + Value: []byte{0x30, 0x03, 0x02, 0x01, 0x05}, + }, + Description: "OCSP Must Staple", + }, + }, + PreferredRootCN: "ISRG Root X1", + LastAccess: time.Unix(1745952074, 0), + CreatedAt: time.Unix(1709327717, 0), + UpdatedAt: time.Unix(0, 0), + ApiKey: "api-secret-18", + ApiKeyNew: "", + ApiKeyViaUrl: true, + PostProcessingCommand: "./scripts/windows/post-processing.example.ps1", + PostProcessingEnvironment: []string{"asdasdasdsd=asasd"}, + PostProcessingClientAddress: "dude.greg.example.com", + PostProcessingClientKeyB64: "aaaaaaaaaaaaaaaaaaaaaaaaaaa-ccccccccccccccc", + Profile: "tlsserver", + }, + nil, + nil, + }, + } + + // create testing service + storage, err := openStorageWithTestData(t, "putkeyapikeynew") + if err != nil { + t.Fatal(err) + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("id: %d)", tc.certId), func(t *testing.T) { + err := storage.PutCertApiKeyNew(tc.certId, tc.apiKeyNew, tc.updatedAt) + if !helpers_test.ErrorsIs(err, tc.expectedPutErr) { + t.Errorf("expected apikeynew put error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedPutErr), helpers_test.ErrorToVal(err)) + } + + cert, err := storage.GetOneCertById(tc.certId) + if !helpers_test.ErrorsIs(err, tc.expectedGetErr) { + t.Errorf("expected cert get error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedGetErr), helpers_test.ErrorToVal(err)) + } + + CompareCertificate(t, cert, tc.expectedCert) + }) + } + +} + func TestPutCertUpdatedAt(t *testing.T) { testCases := []struct { certId int diff --git a/pkg/storage/keys_put.go b/pkg/storage/keys_put.go index 78d9c0a0..aaa551fa 100644 --- a/pkg/storage/keys_put.go +++ b/pkg/storage/keys_put.go @@ -37,7 +37,7 @@ func (store *Storage) PutKeyUpdate(payload private_keys.UpdatePayload) (private_ payload.ApiKeyNew, payload.ApiKeyDisabled, payload.ApiKeyViaUrl, - payload.UpdatedAt, + payload.UpdatedAt.Unix(), payload.ID, ) if err != nil { @@ -63,25 +63,25 @@ func (store *Storage) PutKeyUpdate(payload private_keys.UpdatePayload) (private_ } // PutKeyApiKey sets a key's api key and updates the updated at time -func (store *Storage) PutKeyApiKey(keyId int, apiKey string, updateTimeUnix int) (err error) { +func (store *Storage) PutKeyApiKey(keyId int, apiKey string, updatedAt time.Time) (err error) { // leverage main Put function payload := private_keys.UpdatePayload{ ID: keyId, ApiKey: &apiKey, - UpdatedAt: updateTimeUnix, + UpdatedAt: updatedAt, } _, err = store.PutKeyUpdate(payload) return err } -// PutKeyUpdate sets a key's new api key and updates the updated at time -func (store *Storage) PutKeyNewApiKey(keyId int, newApiKey string, updateTimeUnix int) (err error) { +// PutKeyApiKeyNew sets a key's new api key and updates the updated at time +func (store *Storage) PutKeyApiKeyNew(keyId int, apiKeyNew string, updatedAt time.Time) (err error) { // leverage main Put function payload := private_keys.UpdatePayload{ ID: keyId, - ApiKeyNew: &newApiKey, - UpdatedAt: updateTimeUnix, + ApiKeyNew: &apiKeyNew, + UpdatedAt: updatedAt, } _, err = store.PutKeyUpdate(payload) diff --git a/pkg/storage/keys_put_test.go b/pkg/storage/keys_put_test.go index cf66ecf2..51773dfc 100644 --- a/pkg/storage/keys_put_test.go +++ b/pkg/storage/keys_put_test.go @@ -49,7 +49,7 @@ func TestPutKeyUpdate(t *testing.T) { ApiKeyNew: new("2222"), ApiKeyDisabled: new(true), ApiKeyViaUrl: new(false), - UpdatedAt: 1001111, + UpdatedAt: time.Unix(1001111, 0), }, private_keys.Key{ ID: 31, @@ -92,7 +92,7 @@ red-31 { // update none of the things (except last update) private_keys.UpdatePayload{ ID: 62, - UpdatedAt: 1001111, + UpdatedAt: time.Unix(1001111, 0), }, private_keys.Key{ ID: 62, @@ -136,7 +136,7 @@ red-62 private_keys.UpdatePayload{ ID: 58, ApiKeyDisabled: new(false), - UpdatedAt: 1751730000, + UpdatedAt: time.Unix(1751730000, 0), }, private_keys.Key{ ID: 58, @@ -205,9 +205,9 @@ red-58 func TestPutKeyApiKey(t *testing.T) { testCases := []struct { - keyId int - apiKey string - updateTimeUnix int + keyId int + apiKey string + updatedAt time.Time expectedKey private_keys.Key expectedPutErr error @@ -216,7 +216,7 @@ func TestPutKeyApiKey(t *testing.T) { { // invalid key id -1, "fake", - 100005, + time.Unix(100005, 0), private_keys.Key{}, storage.ErrWrongUpdateRowCount, sql.ErrNoRows, @@ -224,7 +224,7 @@ func TestPutKeyApiKey(t *testing.T) { { // invalid key id 500, "anotherfake", - 10005000, + time.Unix(10005000, 0), private_keys.Key{}, storage.ErrWrongUpdateRowCount, sql.ErrNoRows, @@ -233,7 +233,7 @@ func TestPutKeyApiKey(t *testing.T) { { 31, "fake31", - 1022005, + time.Unix(1022005, 0), private_keys.Key{ ID: 31, Name: "certwarden", @@ -256,8 +256,8 @@ red-31 }, { 62, - "62thing", - 0, + "", + time.Unix(0, 0), private_keys.Key{ ID: 62, Name: "SomeKEy", @@ -267,7 +267,7 @@ red-31 red-62 -----END EC PRIVATE KEY----- `, - ApiKey: "62thing", + ApiKey: "", ApiKeyNew: "key-api-new-key-62", ApiKeyDisabled: false, ApiKeyViaUrl: false, @@ -288,7 +288,7 @@ red-62 for _, tc := range testCases { t.Run(fmt.Sprintf("id: %d)", tc.keyId), func(t *testing.T) { - err := storage.PutKeyApiKey(tc.keyId, tc.apiKey, tc.updateTimeUnix) + err := storage.PutKeyApiKey(tc.keyId, tc.apiKey, tc.updatedAt) if !helpers_test.ErrorsIs(err, tc.expectedPutErr) { t.Errorf("expected put error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedPutErr), helpers_test.ErrorToVal(err)) } @@ -303,11 +303,11 @@ red-62 } } -func TestPutKeyNewApiKey(t *testing.T) { +func TestPutKeyApiKeyNew(t *testing.T) { testCases := []struct { - keyId int - apiKeyNew string - updateTimeUnix int + keyId int + apiKeyNew string + updatedAt time.Time expectedKey private_keys.Key expectedPutErr error @@ -316,7 +316,7 @@ func TestPutKeyNewApiKey(t *testing.T) { { // invalid key id -1, "", - 1099905, + time.Unix(1099905, 0), private_keys.Key{}, storage.ErrWrongUpdateRowCount, sql.ErrNoRows, @@ -324,7 +324,7 @@ func TestPutKeyNewApiKey(t *testing.T) { { // invalid key id 500, "anotherfake", - 10005000, + time.Unix(10005000, 0), private_keys.Key{}, storage.ErrWrongUpdateRowCount, sql.ErrNoRows, @@ -333,7 +333,7 @@ func TestPutKeyNewApiKey(t *testing.T) { { 69, "fakenew69", - 1022005, + time.Unix(1022005, 0), private_keys.Key{ ID: 69, Name: "STAGING_persist--test007.test.example2.com", @@ -357,7 +357,7 @@ red-69 { 67, "otherfakenew67", - 0, + time.Unix(0, 0), private_keys.Key{ ID: 67, Name: "_GC3", @@ -381,14 +381,14 @@ red-67 } // create testing service - storage, err := openStorageWithTestData(t, "putkeynewapikey") + storage, err := openStorageWithTestData(t, "putkeyapikeynew") if err != nil { t.Fatal(err) } for _, tc := range testCases { t.Run(fmt.Sprintf("id: %d)", tc.keyId), func(t *testing.T) { - err := storage.PutKeyNewApiKey(tc.keyId, tc.apiKeyNew, tc.updateTimeUnix) + err := storage.PutKeyApiKeyNew(tc.keyId, tc.apiKeyNew, tc.updatedAt) if !helpers_test.ErrorsIs(err, tc.expectedPutErr) { t.Errorf("expected put error '%s' but got '%s'", helpers_test.ErrorToVal(tc.expectedPutErr), helpers_test.ErrorToVal(err)) } From f9f184f55c7f592bd04b5d044d1c32f016926639 Mon Sep 17 00:00:00 2001 From: "Greg T. Wallace" Date: Fri, 7 Aug 2026 18:49:38 -0400 Subject: [PATCH 13/16] fix 0 updatedAt when cert generates a new key --- pkg/domain/certificates/handlers_post.go | 40 ++++++++++++++---------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/pkg/domain/certificates/handlers_post.go b/pkg/domain/certificates/handlers_post.go index dff274d9..c632337d 100644 --- a/pkg/domain/certificates/handlers_post.go +++ b/pkg/domain/certificates/handlers_post.go @@ -200,24 +200,24 @@ func (service *Service) PostNewCert(w http.ResponseWriter, r *http.Request) *out // if new private key was generated, save it to storage if generatedKeyPem != "" { + apiKey, err := randomness.GenerateApiKey() + if err != nil { + service.logger.Error(err) + return output.JsonErrInternal(err) + } + // create new key payload newKeyPayload := private_keys.NewPayload{ Name: payload.Name, Description: payload.Description, AlgorithmValue: payload.NewKeyAlgorithmValue, PemContent: &generatedKeyPem, - ApiKeyDisabled: new(bool), + ApiKeyDisabled: new(false), ApiKeyViaUrl: payload.ApiKeyViaUrl, + ApiKey: apiKey, + CreatedAt: int(time.Now().Unix()), + UpdatedAt: int(time.Now().Unix()), } - // set additional new key payload fields - newKeyPayload.ApiKey, err = randomness.GenerateApiKey() - if err != nil { - service.logger.Error(err) - return output.JsonErrInternal(err) - } - *newKeyPayload.ApiKeyDisabled = false - newKeyPayload.CreatedAt = int(time.Now().Unix()) - newKeyPayload.UpdatedAt = payload.CreatedAt // save new key to storage, and set the cert key id based on returned key's id newKey, err := service.storage.PostNewKey(newKeyPayload) @@ -239,14 +239,20 @@ func (service *Service) PostNewCert(w http.ResponseWriter, r *http.Request) *out payload.UpdatedAt = payload.CreatedAt // if client address specified but no aes key, generate key to save (b64 raw url encoded) - if payload.PostProcessingClientAddress != nil && *payload.PostProcessingClientAddress != "" && payload.PostProcessingClientKeyB64 == nil { - key, err := randomness.GenerateAES256KeyAsBase64RawUrl() - if err != nil { - err = fmt.Errorf("failed to generate client key for certificate (%s)", err) - service.logger.Error(err) - return output.JsonErrInternal(err) + if payload.PostProcessingClientKeyB64 == nil { + // empty if no processing address + payload.PostProcessingClientKeyB64 = new("") + + // processing address & user didnt specify an aes key -- generate one + if payload.PostProcessingClientAddress != nil && *payload.PostProcessingClientAddress != "" { + key, err := randomness.GenerateAES256KeyAsBase64RawUrl() + if err != nil { + err = fmt.Errorf("failed to generate client key for certificate (%s)", err) + service.logger.Error(err) + return output.JsonErrInternal(err) + } + payload.PostProcessingClientKeyB64 = &key } - payload.PostProcessingClientKeyB64 = &key } // save new cert From 7f68f4e00aa43a625b0394018d8068846396f79f Mon Sep 17 00:00:00 2001 From: "Greg T. Wallace" Date: Fri, 7 Aug 2026 18:49:38 -0400 Subject: [PATCH 14/16] more time.Time refactoring --- pkg/domain/acme_accounts/handlers_post.go | 25 ++++++++++++----------- pkg/domain/acme_servers/handlers_post.go | 17 +++++++-------- pkg/domain/acme_servers/handlers_put.go | 14 ++++++------- pkg/domain/certificates/handlers_post.go | 13 ++++++------ pkg/domain/private_keys/handlers_post.go | 23 +++++++++++---------- pkg/storage/accounts_post.go | 4 ++-- pkg/storage/accounts_post_test.go | 16 +++++++-------- pkg/storage/acme_servers_post.go | 4 ++-- pkg/storage/acme_servers_post_test.go | 12 +++++------ pkg/storage/acme_servers_put.go | 2 +- pkg/storage/acme_servers_put_test.go | 6 +++--- pkg/storage/certificates_post.go | 4 ++-- pkg/storage/certificates_post_test.go | 20 ++++++++---------- pkg/storage/keys_get_test.go | 3 +-- pkg/storage/keys_post.go | 4 ++-- pkg/storage/keys_post_test.go | 12 +++++------ 16 files changed, 89 insertions(+), 90 deletions(-) diff --git a/pkg/domain/acme_accounts/handlers_post.go b/pkg/domain/acme_accounts/handlers_post.go index 7943f2c7..a5e5b83d 100644 --- a/pkg/domain/acme_accounts/handlers_post.go +++ b/pkg/domain/acme_accounts/handlers_post.go @@ -20,16 +20,16 @@ import ( // NewPayload is the payload struct for creating a new account type NewPayload struct { - Name *string `json:"name"` - Description *string `json:"description"` - AcmeServerID *int `json:"acme_server_id"` - PrivateKeyID *int `json:"private_key_id"` - Status string `json:"-"` - Email *string `json:"email"` - AcceptedTos *bool `json:"accepted_tos"` - CreatedAt int `json:"-"` - UpdatedAt int `json:"-"` - Kid string `json:"-"` + Name *string `json:"name"` + Description *string `json:"description"` + AcmeServerID *int `json:"acme_server_id"` + PrivateKeyID *int `json:"private_key_id"` + Status string `json:"-"` + Email *string `json:"email"` + AcceptedTos *bool `json:"accepted_tos"` + CreatedAt time.Time `json:"-"` + UpdatedAt time.Time `json:"-"` + Kid string `json:"-"` } // PostNewAccount is the handler to save a new account to storage. No ACME @@ -89,8 +89,9 @@ func (service *Service) PostNewAccount(w http.ResponseWriter, r *http.Request) * // add additional details to the payload before saving payload.Status = "unknown" - payload.CreatedAt = int(time.Now().Unix()) - payload.UpdatedAt = payload.CreatedAt + t := time.Now() + payload.CreatedAt = t + payload.UpdatedAt = t payload.Kid = "" // Save new account details to storage. diff --git a/pkg/domain/acme_servers/handlers_post.go b/pkg/domain/acme_servers/handlers_post.go index b5d93ca0..e66a9ee0 100644 --- a/pkg/domain/acme_servers/handlers_post.go +++ b/pkg/domain/acme_servers/handlers_post.go @@ -12,12 +12,12 @@ import ( // NewPayload is used to post a new Server type NewPayload struct { - Name *string `json:"name"` - Description *string `json:"description"` - DirectoryURL *string `json:"directory_url"` - IsStaging *bool `json:"is_staging"` - CreatedAt int `json:"-"` - UpdatedAt int `json:"-"` + Name *string `json:"name"` + Description *string `json:"description"` + DirectoryURL *string `json:"directory_url"` + IsStaging *bool `json:"is_staging"` + CreatedAt time.Time `json:"-"` + UpdatedAt time.Time `json:"-"` } // PostNewServer creates a new server, saves it to storage, and starts an *acme.Service @@ -62,8 +62,9 @@ func (service *Service) PostNewServer(w http.ResponseWriter, r *http.Request) *o // end validation // add additional details to the payload before saving - payload.CreatedAt = int(time.Now().Unix()) - payload.UpdatedAt = payload.CreatedAt + t := time.Now() + payload.CreatedAt = t + payload.UpdatedAt = t // save new key to storage, which also returns the new server newServer, err := service.storage.PostNewServer(payload) diff --git a/pkg/domain/acme_servers/handlers_put.go b/pkg/domain/acme_servers/handlers_put.go index 07b35b7a..a31b501e 100644 --- a/pkg/domain/acme_servers/handlers_put.go +++ b/pkg/domain/acme_servers/handlers_put.go @@ -15,12 +15,12 @@ import ( // UpdatePayload is the struct for editing an existing Server's // information (only certain fields are editable) type UpdatePayload struct { - ID int `json:"-"` - Name *string `json:"name"` - Description *string `json:"description"` - DirectoryURL *string `json:"directory_url"` - IsStaging *bool `json:"is_staging"` - UpdatedAt int `json:"-"` + ID int `json:"-"` + Name *string `json:"name"` + Description *string `json:"description"` + DirectoryURL *string `json:"directory_url"` + IsStaging *bool `json:"is_staging"` + UpdatedAt time.Time `json:"-"` } // PutServerUpdate updates a Server that already exists in storage. @@ -66,7 +66,7 @@ func (service *Service) PutServerUpdate(w http.ResponseWriter, r *http.Request) // end validation // add additional details to the payload before saving - payload.UpdatedAt = int(time.Now().Unix()) + payload.UpdatedAt = time.Now() // save updated key info to storage updatedServer, err := service.storage.PutServerUpdate(payload) diff --git a/pkg/domain/certificates/handlers_post.go b/pkg/domain/certificates/handlers_post.go index c632337d..136e1fda 100644 --- a/pkg/domain/certificates/handlers_post.go +++ b/pkg/domain/certificates/handlers_post.go @@ -39,8 +39,8 @@ type NewPayload struct { Profile *string `json:"profile"` ApiKey string `json:"-"` ApiKeyViaUrl bool `json:"-"` - CreatedAt int `json:"-"` - UpdatedAt int `json:"-"` + CreatedAt time.Time `json:"-"` + UpdatedAt time.Time `json:"-"` } // PostNewCert creates a new certificate object in storage. No actual encryption certificate @@ -199,6 +199,7 @@ func (service *Service) PostNewCert(w http.ResponseWriter, r *http.Request) *out // end validation // if new private key was generated, save it to storage + createdAtAndUpdatedAt := time.Now() if generatedKeyPem != "" { apiKey, err := randomness.GenerateApiKey() if err != nil { @@ -215,8 +216,8 @@ func (service *Service) PostNewCert(w http.ResponseWriter, r *http.Request) *out ApiKeyDisabled: new(false), ApiKeyViaUrl: payload.ApiKeyViaUrl, ApiKey: apiKey, - CreatedAt: int(time.Now().Unix()), - UpdatedAt: int(time.Now().Unix()), + CreatedAt: createdAtAndUpdatedAt, + UpdatedAt: createdAtAndUpdatedAt, } // save new key to storage, and set the cert key id based on returned key's id @@ -235,8 +236,8 @@ func (service *Service) PostNewCert(w http.ResponseWriter, r *http.Request) *out return output.JsonErrInternal(err) } payload.ApiKeyViaUrl = false - payload.CreatedAt = int(time.Now().Unix()) - payload.UpdatedAt = payload.CreatedAt + payload.CreatedAt = createdAtAndUpdatedAt + payload.UpdatedAt = createdAtAndUpdatedAt // if client address specified but no aes key, generate key to save (b64 raw url encoded) if payload.PostProcessingClientKeyB64 == nil { diff --git a/pkg/domain/private_keys/handlers_post.go b/pkg/domain/private_keys/handlers_post.go index 81d0c979..bccb74cb 100644 --- a/pkg/domain/private_keys/handlers_post.go +++ b/pkg/domain/private_keys/handlers_post.go @@ -15,15 +15,15 @@ import ( // PostPayload is a struct for posting a new key type NewPayload struct { - Name *string `json:"name"` - Description *string `json:"description"` - AlgorithmValue *string `json:"algorithm_value"` - PemContent *string `json:"pem"` - ApiKey string `json:"-"` - ApiKeyDisabled *bool `json:"api_key_disabled"` - ApiKeyViaUrl bool `json:"-"` - CreatedAt int `json:"-"` - UpdatedAt int `json:"-"` + Name *string `json:"name"` + Description *string `json:"description"` + AlgorithmValue *string `json:"algorithm_value"` + PemContent *string `json:"pem"` + ApiKey string `json:"-"` + ApiKeyDisabled *bool `json:"api_key_disabled"` + ApiKeyViaUrl bool `json:"-"` + CreatedAt time.Time `json:"-"` + UpdatedAt time.Time `json:"-"` } // PostNewKey creates a new private key and saves it to storage @@ -95,8 +95,9 @@ func (service *Service) PostNewKey(w http.ResponseWriter, r *http.Request) *outp return output.JsonErrInternal(err) } payload.ApiKeyViaUrl = false - payload.CreatedAt = int(time.Now().Unix()) - payload.UpdatedAt = payload.CreatedAt + t := time.Now() + payload.CreatedAt = t + payload.UpdatedAt = t // save new key to storage, which also returns the new key id newKey, err := service.storage.PostNewKey(payload) diff --git a/pkg/storage/accounts_post.go b/pkg/storage/accounts_post.go index b540a1a7..db601679 100644 --- a/pkg/storage/accounts_post.go +++ b/pkg/storage/accounts_post.go @@ -30,8 +30,8 @@ func (store *Storage) PostNewAcmeAccount(payload acme_accounts.NewPayload) (acme payload.Status, payload.Email, payload.AcceptedTos, - payload.CreatedAt, - payload.UpdatedAt, + payload.CreatedAt.Unix(), + payload.UpdatedAt.Unix(), payload.Kid, ).Scan(&id) diff --git a/pkg/storage/accounts_post_test.go b/pkg/storage/accounts_post_test.go index b758ec25..24deca1d 100644 --- a/pkg/storage/accounts_post_test.go +++ b/pkg/storage/accounts_post_test.go @@ -25,8 +25,8 @@ func TestPostNewAcmeAccount(t *testing.T) { Status: "a status", Email: new("anemail@example.com"), AcceptedTos: new(false), - CreatedAt: 1788837479, - UpdatedAt: 1788838000, + CreatedAt: time.Unix(1788837479, 0), + UpdatedAt: time.Unix(1788838000, 0), Kid: "https://fake.example.com/1234", }, nil, @@ -54,8 +54,8 @@ func TestPostNewAcmeAccount(t *testing.T) { Status: "status", Email: new("email@example.com"), AcceptedTos: new(true), - CreatedAt: 1888837479, - UpdatedAt: 1888838000, + CreatedAt: time.Unix(1888837479, 0), + UpdatedAt: time.Unix(1888838000, 0), Kid: "https://fake.example.com/123456", }, helpers_test.MakeTestErrorStringComp("UNIQUE constraint failed"), @@ -71,8 +71,8 @@ func TestPostNewAcmeAccount(t *testing.T) { Status: "status", Email: new("fake2@example.com"), AcceptedTos: new(true), - CreatedAt: 1888837479, - UpdatedAt: 1888838000, + CreatedAt: time.Unix(1888837479, 0), + UpdatedAt: time.Unix(1888838000, 0), Kid: "https://fake.example.com/123456", }, helpers_test.MakeTestErrorStringComp("NOT NULL constraint failed"), @@ -88,8 +88,8 @@ func TestPostNewAcmeAccount(t *testing.T) { Status: "status", // Email: AcceptedTos: new(true), - CreatedAt: 1888837479, - UpdatedAt: 1888838000, + CreatedAt: time.Unix(1888837479, 0), + UpdatedAt: time.Unix(1888838000, 0), Kid: "https://fake.example.com/123456", }, helpers_test.MakeTestErrorStringComp("NOT NULL constraint failed"), diff --git a/pkg/storage/acme_servers_post.go b/pkg/storage/acme_servers_post.go index 45c371a4..af0c21e1 100644 --- a/pkg/storage/acme_servers_post.go +++ b/pkg/storage/acme_servers_post.go @@ -24,8 +24,8 @@ func (store *Storage) PostNewServer(payload acme_servers.NewPayload) (acme_serve payload.Description, payload.DirectoryURL, payload.IsStaging, - payload.CreatedAt, - payload.UpdatedAt, + payload.CreatedAt.Unix(), + payload.UpdatedAt.Unix(), ).Scan(&acmeServerId) if err != nil { diff --git a/pkg/storage/acme_servers_post_test.go b/pkg/storage/acme_servers_post_test.go index 2f5b7905..630e5fa7 100644 --- a/pkg/storage/acme_servers_post_test.go +++ b/pkg/storage/acme_servers_post_test.go @@ -22,8 +22,8 @@ func TestPostNewServer(t *testing.T) { Description: new("some service"), DirectoryURL: new("https://example.com/directory"), IsStaging: new(true), - CreatedAt: 1780337479, - UpdatedAt: 1780338000, + CreatedAt: time.Unix(1780337479, 0), + UpdatedAt: time.Unix(1780338000, 0), }, nil, acme_servers.Server{ @@ -43,8 +43,8 @@ func TestPostNewServer(t *testing.T) { Description: new("some service wont work"), DirectoryURL: new("https://example2.com/directory"), IsStaging: new(true), - CreatedAt: 1780337449, - UpdatedAt: 1780338040, + CreatedAt: time.Unix(1780337449, 0), + UpdatedAt: time.Unix(1780338040, 0), }, helpers_test.MakeTestErrorStringComp("UNIQUE constraint failed"), acme_servers.Server{}, @@ -56,8 +56,8 @@ func TestPostNewServer(t *testing.T) { Description: new("wont work"), // DirectoryURL IsStaging: new(false), - CreatedAt: 1880337449, - UpdatedAt: 1880338040, + CreatedAt: time.Unix(1880337449, 0), + UpdatedAt: time.Unix(1880338040, 0), }, helpers_test.MakeTestErrorStringComp("NOT NULL constraint failed"), acme_servers.Server{}, diff --git a/pkg/storage/acme_servers_put.go b/pkg/storage/acme_servers_put.go index bba7ab16..b8076607 100644 --- a/pkg/storage/acme_servers_put.go +++ b/pkg/storage/acme_servers_put.go @@ -31,7 +31,7 @@ func (store *Storage) PutServerUpdate(payload acme_servers.UpdatePayload) (acme_ payload.Description, payload.DirectoryURL, payload.IsStaging, - payload.UpdatedAt, + payload.UpdatedAt.Unix(), payload.ID, ) if err != nil { diff --git a/pkg/storage/acme_servers_put_test.go b/pkg/storage/acme_servers_put_test.go index c8ab65b0..bf7b2447 100644 --- a/pkg/storage/acme_servers_put_test.go +++ b/pkg/storage/acme_servers_put_test.go @@ -46,7 +46,7 @@ func TestPutServerUpdate(t *testing.T) { Description: new("new desc"), DirectoryURL: new("https://example-new.com/directory"), IsStaging: new(false), - UpdatedAt: 1733265750, + UpdatedAt: time.Unix(1733265750, 0), }, acme_servers.Server{ ID: 1, @@ -73,7 +73,7 @@ func TestPutServerUpdate(t *testing.T) { { // update none of the things (except last update) acme_servers.UpdatePayload{ ID: 19, - UpdatedAt: 11121111, + UpdatedAt: time.Unix(11121111, 0), }, acme_servers.Server{ ID: 19, @@ -101,7 +101,7 @@ func TestPutServerUpdate(t *testing.T) { acme_servers.UpdatePayload{ ID: 4, DirectoryURL: new("https://example-put.com/directory"), - UpdatedAt: 100800111, + UpdatedAt: time.Unix(100800111, 0), }, acme_servers.Server{ ID: 4, diff --git a/pkg/storage/certificates_post.go b/pkg/storage/certificates_post.go index 2681eac3..3cdd1f6e 100644 --- a/pkg/storage/certificates_post.go +++ b/pkg/storage/certificates_post.go @@ -40,8 +40,8 @@ func (store *Storage) PostNewCert(payload certificates.NewPayload) (certificates payload.City, makeJsonCertExtensionSlice(payload.CSRExtraExtensions, false), payload.PreferredRootCN, - payload.CreatedAt, - payload.UpdatedAt, + payload.CreatedAt.Unix(), + payload.UpdatedAt.Unix(), payload.ApiKey, payload.ApiKeyViaUrl, payload.PostProcessingCommand, diff --git a/pkg/storage/certificates_post_test.go b/pkg/storage/certificates_post_test.go index c3493246..aba0aec1 100644 --- a/pkg/storage/certificates_post_test.go +++ b/pkg/storage/certificates_post_test.go @@ -11,10 +11,6 @@ import ( "time" ) -// ApiKeyViaUrl bool `json:"-"` -// CreatedAt int `json:"-"` -// UpdatedAt int `json:"-"` - func TestPostNewCert(t *testing.T) { testCases := []struct { newPayload certificates.NewPayload @@ -54,8 +50,8 @@ func TestPostNewCert(t *testing.T) { Profile: new("test-prof"), ApiKey: "12345fffff", ApiKeyViaUrl: true, - CreatedAt: 770337479, - UpdatedAt: 770338000, + CreatedAt: time.Unix(770337479, 0), + UpdatedAt: time.Unix(770338000, 0), }, nil, certificates.Certificate{ @@ -119,8 +115,8 @@ func TestPostNewCert(t *testing.T) { Profile: new("test-prof"), ApiKey: "12345fffff", ApiKeyViaUrl: true, - CreatedAt: 770337479, - UpdatedAt: 770338000, + CreatedAt: time.Unix(770337479, 0), + UpdatedAt: time.Unix(770338000, 0), }, helpers_test.MakeTestErrorStringComp("UNIQUE constraint failed: certificates.name"), certificates.Certificate{}, @@ -158,8 +154,8 @@ func TestPostNewCert(t *testing.T) { Profile: new("test-prof"), ApiKey: "12345fffff", ApiKeyViaUrl: true, - CreatedAt: 770337479, - UpdatedAt: 770338000, + CreatedAt: time.Unix(770337479, 0), + UpdatedAt: time.Unix(770338000, 0), }, helpers_test.MakeTestErrorStringComp("NOT NULL constraint failed: certificates.acme_account_id"), certificates.Certificate{}, @@ -197,8 +193,8 @@ func TestPostNewCert(t *testing.T) { Profile: new("test-prof"), ApiKey: "12345fffff", ApiKeyViaUrl: true, - CreatedAt: 770337479, - UpdatedAt: 770338000, + CreatedAt: time.Unix(770337479, 0), + UpdatedAt: time.Unix(770338000, 0), }, helpers_test.MakeTestErrorStringComp("NOT NULL constraint failed: certificates.subject"), certificates.Certificate{}, diff --git a/pkg/storage/keys_get_test.go b/pkg/storage/keys_get_test.go index 1295d5c9..5196d733 100644 --- a/pkg/storage/keys_get_test.go +++ b/pkg/storage/keys_get_test.go @@ -262,8 +262,7 @@ red-69 ApiKeyViaUrl: false, LastAccess: time.Unix(1777555692, 0), CreatedAt: time.Unix(1775761592, 0), - // TODO: Research why this is 0 in the db. Might find while writing other tests. Also possible this is just junk data from a previous bug. - UpdatedAt: time.Unix(0, 0), + UpdatedAt: time.Unix(0, 0), } ) diff --git a/pkg/storage/keys_post.go b/pkg/storage/keys_post.go index 117c8c4f..3e2a609e 100644 --- a/pkg/storage/keys_post.go +++ b/pkg/storage/keys_post.go @@ -27,8 +27,8 @@ func (store *Storage) PostNewKey(payload private_keys.NewPayload) (private_keys. payload.ApiKey, payload.ApiKeyDisabled, payload.ApiKeyViaUrl, - payload.CreatedAt, - payload.UpdatedAt, + payload.CreatedAt.Unix(), + payload.UpdatedAt.Unix(), ).Scan(&id) if err != nil { diff --git a/pkg/storage/keys_post_test.go b/pkg/storage/keys_post_test.go index 4ea0dcde..43d542d3 100644 --- a/pkg/storage/keys_post_test.go +++ b/pkg/storage/keys_post_test.go @@ -26,8 +26,8 @@ func TestPostNewKey(t *testing.T) { ApiKey: "apikeyxyz", ApiKeyDisabled: new(true), ApiKeyViaUrl: true, - CreatedAt: 1780336479, - UpdatedAt: 1780337000, + CreatedAt: time.Unix(1780336479, 0), + UpdatedAt: time.Unix(1780337000, 0), }, nil, private_keys.Key{ @@ -55,8 +55,8 @@ func TestPostNewKey(t *testing.T) { ApiKey: "irrelevant", ApiKeyDisabled: new(false), ApiKeyViaUrl: false, - CreatedAt: 1780336477, - UpdatedAt: 1780337010, + CreatedAt: time.Unix(1780336477, 0), + UpdatedAt: time.Unix(1780337010, 0), }, helpers_test.MakeTestErrorStringComp("UNIQUE constraint failed"), private_keys.Key{}, @@ -70,8 +70,8 @@ func TestPostNewKey(t *testing.T) { ApiKey: "irrelevant2", ApiKeyDisabled: new(false), ApiKeyViaUrl: false, - CreatedAt: 1780336480, - UpdatedAt: 1780337001, + CreatedAt: time.Unix(1780336480, 0), + UpdatedAt: time.Unix(1780337001, 0), }, helpers_test.MakeTestErrorStringComp("NOT NULL constraint failed"), private_keys.Key{}, From 235966158438dea8d0680e8f61bc3a0958e658e2 Mon Sep 17 00:00:00 2001 From: "Greg T. Wallace" Date: Fri, 7 Aug 2026 18:49:38 -0400 Subject: [PATCH 15/16] add backup_test * also rename helpers_test func --- pkg/helpers_test/err_is.go | 4 +- pkg/helpers_test/err_is_test.go | 24 +++---- pkg/storage/accounts_post_test.go | 6 +- pkg/storage/accounts_put_test.go | 8 +-- pkg/storage/acme_servers_post_test.go | 4 +- pkg/storage/backup.go | 4 +- pkg/storage/backup_test.go | 95 +++++++++++++++++++++++++++ pkg/storage/certificates_post_test.go | 6 +- pkg/storage/keys_post_test.go | 4 +- 9 files changed, 125 insertions(+), 30 deletions(-) create mode 100644 pkg/storage/backup_test.go diff --git a/pkg/helpers_test/err_is.go b/pkg/helpers_test/err_is.go index e1b49506..4a909957 100644 --- a/pkg/helpers_test/err_is.go +++ b/pkg/helpers_test/err_is.go @@ -20,9 +20,9 @@ func (e testErrorStringComp) Unwrap() error { return e.Inner } -// MakeTestErrorStringComp wraps the provided error text in a special error type that +// NewTestErrorStringComp wraps the provided error text in a special error type that // will be parsed and compared when the custom ErrorsIs is called -func MakeTestErrorStringComp(errText string) testErrorStringComp { +func NewTestErrorStringComp(errText string) testErrorStringComp { return testErrorStringComp{Inner: errors.New(errText)} } diff --git a/pkg/helpers_test/err_is_test.go b/pkg/helpers_test/err_is_test.go index 48b146fc..1be758a4 100644 --- a/pkg/helpers_test/err_is_test.go +++ b/pkg/helpers_test/err_is_test.go @@ -22,11 +22,11 @@ func TestErrorsIs(t *testing.T) { }, { err: nil, - target: helpers_test.MakeTestErrorStringComp("an error"), + target: helpers_test.NewTestErrorStringComp("an error"), isTheSame: false, }, { - err: helpers_test.MakeTestErrorStringComp("an error"), + err: helpers_test.NewTestErrorStringComp("an error"), target: nil, isTheSame: false, }, @@ -42,52 +42,52 @@ func TestErrorsIs(t *testing.T) { }, { err: sql.ErrNoRows, - target: helpers_test.MakeTestErrorStringComp("an error"), + target: helpers_test.NewTestErrorStringComp("an error"), isTheSame: false, }, { err: acme.ErrChallengeMalformed, - target: helpers_test.MakeTestErrorStringComp("an error"), + target: helpers_test.NewTestErrorStringComp("an error"), isTheSame: false, }, { err: errors.New("some error"), - target: helpers_test.MakeTestErrorStringComp("uh oh, some error"), + target: helpers_test.NewTestErrorStringComp("uh oh, some error"), isTheSame: false, }, { err: errors.New("some error"), - target: helpers_test.MakeTestErrorStringComp("some error, uh oh"), + target: helpers_test.NewTestErrorStringComp("some error, uh oh"), isTheSame: false, }, { - err: helpers_test.MakeTestErrorStringComp("uh oh, some error"), + err: helpers_test.NewTestErrorStringComp("uh oh, some error"), target: errors.New("some error"), isTheSame: false, }, { - err: helpers_test.MakeTestErrorStringComp("some error, uh oh"), + err: helpers_test.NewTestErrorStringComp("some error, uh oh"), target: errors.New("some error"), isTheSame: false, }, { err: errors.New("uh oh, some error"), - target: helpers_test.MakeTestErrorStringComp("some error"), + target: helpers_test.NewTestErrorStringComp("some error"), isTheSame: true, }, { err: errors.New("some error, uh oh"), - target: helpers_test.MakeTestErrorStringComp("some error"), + target: helpers_test.NewTestErrorStringComp("some error"), isTheSame: true, }, { err: errors.New("uh oh, some error"), - target: helpers_test.MakeTestErrorStringComp("SOME errOR"), + target: helpers_test.NewTestErrorStringComp("SOME errOR"), isTheSame: true, }, { err: errors.New("some error, uh oh"), - target: helpers_test.MakeTestErrorStringComp("SOME errOR"), + target: helpers_test.NewTestErrorStringComp("SOME errOR"), isTheSame: true, }, } diff --git a/pkg/storage/accounts_post_test.go b/pkg/storage/accounts_post_test.go index 24deca1d..d673d52c 100644 --- a/pkg/storage/accounts_post_test.go +++ b/pkg/storage/accounts_post_test.go @@ -58,7 +58,7 @@ func TestPostNewAcmeAccount(t *testing.T) { UpdatedAt: time.Unix(1888838000, 0), Kid: "https://fake.example.com/123456", }, - helpers_test.MakeTestErrorStringComp("UNIQUE constraint failed"), + helpers_test.NewTestErrorStringComp("UNIQUE constraint failed"), acme_accounts.Account{}, sql.ErrNoRows, }, @@ -75,7 +75,7 @@ func TestPostNewAcmeAccount(t *testing.T) { UpdatedAt: time.Unix(1888838000, 0), Kid: "https://fake.example.com/123456", }, - helpers_test.MakeTestErrorStringComp("NOT NULL constraint failed"), + helpers_test.NewTestErrorStringComp("NOT NULL constraint failed"), acme_accounts.Account{}, sql.ErrNoRows, }, @@ -92,7 +92,7 @@ func TestPostNewAcmeAccount(t *testing.T) { UpdatedAt: time.Unix(1888838000, 0), Kid: "https://fake.example.com/123456", }, - helpers_test.MakeTestErrorStringComp("NOT NULL constraint failed"), + helpers_test.NewTestErrorStringComp("NOT NULL constraint failed"), acme_accounts.Account{}, sql.ErrNoRows, }, diff --git a/pkg/storage/accounts_put_test.go b/pkg/storage/accounts_put_test.go index 371d3048..1c251da3 100644 --- a/pkg/storage/accounts_put_test.go +++ b/pkg/storage/accounts_put_test.go @@ -154,7 +154,7 @@ func TestPutAcmeAccountUpdate(t *testing.T) { UpdatedAt: time.Unix(107800777, 0), }, acme_accounts.Account{}, - helpers_test.MakeTestErrorStringComp("UNIQUE constraint failed"), + helpers_test.NewTestErrorStringComp("UNIQUE constraint failed"), 16, acmeAcct16, nil, @@ -336,7 +336,7 @@ func TestPutAcmeAccountNewKey(t *testing.T) { UpdatedAt: time.Unix(1088000751, 0), }, acme_accounts.Account{}, - helpers_test.MakeTestErrorStringComp("FOREIGN KEY constraint failed"), + helpers_test.NewTestErrorStringComp("FOREIGN KEY constraint failed"), 1, acmeAcct1, nil, @@ -348,7 +348,7 @@ func TestPutAcmeAccountNewKey(t *testing.T) { UpdatedAt: time.Unix(1088000752, 0), }, acme_accounts.Account{}, - helpers_test.MakeTestErrorStringComp("FOREIGN KEY constraint failed"), + helpers_test.NewTestErrorStringComp("FOREIGN KEY constraint failed"), 1, acmeAcct1, nil, @@ -360,7 +360,7 @@ func TestPutAcmeAccountNewKey(t *testing.T) { UpdatedAt: time.Unix(1088000753, 0), }, acme_accounts.Account{}, - helpers_test.MakeTestErrorStringComp("UNIQUE constraint failed"), + helpers_test.NewTestErrorStringComp("UNIQUE constraint failed"), 23, acmeAcct23, nil, diff --git a/pkg/storage/acme_servers_post_test.go b/pkg/storage/acme_servers_post_test.go index 630e5fa7..ca9bc597 100644 --- a/pkg/storage/acme_servers_post_test.go +++ b/pkg/storage/acme_servers_post_test.go @@ -46,7 +46,7 @@ func TestPostNewServer(t *testing.T) { CreatedAt: time.Unix(1780337449, 0), UpdatedAt: time.Unix(1780338040, 0), }, - helpers_test.MakeTestErrorStringComp("UNIQUE constraint failed"), + helpers_test.NewTestErrorStringComp("UNIQUE constraint failed"), acme_servers.Server{}, sql.ErrNoRows, }, @@ -59,7 +59,7 @@ func TestPostNewServer(t *testing.T) { CreatedAt: time.Unix(1880337449, 0), UpdatedAt: time.Unix(1880338040, 0), }, - helpers_test.MakeTestErrorStringComp("NOT NULL constraint failed"), + helpers_test.NewTestErrorStringComp("NOT NULL constraint failed"), acme_servers.Server{}, sql.ErrNoRows, }, diff --git a/pkg/storage/backup.go b/pkg/storage/backup.go index 0acfd741..acb2b15a 100644 --- a/pkg/storage/backup.go +++ b/pkg/storage/backup.go @@ -23,9 +23,9 @@ func (store *Storage) LockDBForBackup() (unlockFunc func(), err error) { return nil, err } - // make function to rollback the tx (remove the lock) + // make function to remove lock unlockFunc = func() { - _ = tx.Rollback() + _ = tx.Commit() } return unlockFunc, nil diff --git a/pkg/storage/backup_test.go b/pkg/storage/backup_test.go new file mode 100644 index 00000000..c652197a --- /dev/null +++ b/pkg/storage/backup_test.go @@ -0,0 +1,95 @@ +package storage_test + +import ( + "certwarden-backend/pkg/domain/acme_servers" + "certwarden-backend/pkg/helpers_test" + "certwarden-backend/pkg/storage" + "context" + "fmt" + "sync" + "testing" + "time" +) + +// backupCheckErrOK triggers an error on t if err does not match the expected err +// Note: this includes deadline expiration as a lock error +func backupCheckErrOK(t *testing.T, err error, expectLockErr bool) { + if expectLockErr { + if !helpers_test.ErrorsIs(err, context.DeadlineExceeded) && !helpers_test.ErrorsIs(err, helpers_test.NewTestErrorStringComp("database is locked")) { + t.Errorf("err expected '%s' but got '%s'", helpers_test.ErrorToVal(context.DeadlineExceeded), helpers_test.ErrorToVal(err)) + } + } else { + if err != nil { + t.Errorf("err expected '%s' but got '%s'", helpers_test.ErrorToVal(nil), helpers_test.ErrorToVal(err)) + } + } +} + +// backupTestBattery is the group of tests run both while db is locked and while it is unlocked +func backupTestBattery(t *testing.T, storage *storage.Storage, expectLocked bool) { + wg := sync.WaitGroup{} + lockedStateTxt := "unlocked" + if !expectLocked { + lockedStateTxt = "locked" + } + + // read only + wg.Add(1) + go t.Run(fmt.Sprintf("%s: get all acme accounts", lockedStateTxt), func(t *testing.T) { + _, _, err := storage.GetAllAcmeAccounts(queryBuilderForTest(5, 0, "", true)) + backupCheckErrOK(t, err, false) + wg.Done() + }) + + wg.Add(1) + go t.Run(fmt.Sprintf("%s: get one key by id", lockedStateTxt), func(t *testing.T) { + _, err := storage.GetOneKeyById(62) + backupCheckErrOK(t, err, false) + wg.Done() + }) + + // trying to write + wg.Add(1) + go t.Run(fmt.Sprintf("%s: put key api key", lockedStateTxt), func(t *testing.T) { + err := storage.PutKeyApiKey(1, "xyz", time.Unix(123, 0)) + backupCheckErrOK(t, err, expectLocked) + wg.Done() + }) + + wg.Add(1) + go t.Run(fmt.Sprintf("%s: put acme server update", lockedStateTxt), func(t *testing.T) { + payload := acme_servers.UpdatePayload{ + ID: 1, + UpdatedAt: time.Unix(6323444, 0), + } + + _, err := storage.PutServerUpdate(payload) + backupCheckErrOK(t, err, expectLocked) + wg.Done() + }) + + // wait for all tests + wg.Wait() +} + +func TestLockDBForBackup(t *testing.T) { + // create testing service + storage, err := openStorageWithTestData(t, "lockdbforbackup") + if err != nil { + t.Fatal(err) + } + + unlock, err := storage.LockDBForBackup() + if err != nil { + t.Fatalf("failed to lock db: %s", err) + } + + // try various operations (locked) + backupTestBattery(t, storage, true) + + // verify things work after unlock + unlock() + + // try various operations (unlocked) + backupTestBattery(t, storage, false) +} diff --git a/pkg/storage/certificates_post_test.go b/pkg/storage/certificates_post_test.go index aba0aec1..aa545430 100644 --- a/pkg/storage/certificates_post_test.go +++ b/pkg/storage/certificates_post_test.go @@ -118,7 +118,7 @@ func TestPostNewCert(t *testing.T) { CreatedAt: time.Unix(770337479, 0), UpdatedAt: time.Unix(770338000, 0), }, - helpers_test.MakeTestErrorStringComp("UNIQUE constraint failed: certificates.name"), + helpers_test.NewTestErrorStringComp("UNIQUE constraint failed: certificates.name"), certificates.Certificate{}, sql.ErrNoRows, }, @@ -157,7 +157,7 @@ func TestPostNewCert(t *testing.T) { CreatedAt: time.Unix(770337479, 0), UpdatedAt: time.Unix(770338000, 0), }, - helpers_test.MakeTestErrorStringComp("NOT NULL constraint failed: certificates.acme_account_id"), + helpers_test.NewTestErrorStringComp("NOT NULL constraint failed: certificates.acme_account_id"), certificates.Certificate{}, sql.ErrNoRows, }, @@ -196,7 +196,7 @@ func TestPostNewCert(t *testing.T) { CreatedAt: time.Unix(770337479, 0), UpdatedAt: time.Unix(770338000, 0), }, - helpers_test.MakeTestErrorStringComp("NOT NULL constraint failed: certificates.subject"), + helpers_test.NewTestErrorStringComp("NOT NULL constraint failed: certificates.subject"), certificates.Certificate{}, sql.ErrNoRows, }, diff --git a/pkg/storage/keys_post_test.go b/pkg/storage/keys_post_test.go index 43d542d3..c000d9e5 100644 --- a/pkg/storage/keys_post_test.go +++ b/pkg/storage/keys_post_test.go @@ -58,7 +58,7 @@ func TestPostNewKey(t *testing.T) { CreatedAt: time.Unix(1780336477, 0), UpdatedAt: time.Unix(1780337010, 0), }, - helpers_test.MakeTestErrorStringComp("UNIQUE constraint failed"), + helpers_test.NewTestErrorStringComp("UNIQUE constraint failed"), private_keys.Key{}, sql.ErrNoRows, }, @@ -73,7 +73,7 @@ func TestPostNewKey(t *testing.T) { CreatedAt: time.Unix(1780336480, 0), UpdatedAt: time.Unix(1780337001, 0), }, - helpers_test.MakeTestErrorStringComp("NOT NULL constraint failed"), + helpers_test.NewTestErrorStringComp("NOT NULL constraint failed"), private_keys.Key{}, sql.ErrNoRows, }, From 10bec195f9b03a85ddc40a31d7f8759f60651630 Mon Sep 17 00:00:00 2001 From: "Greg T. Wallace" Date: Sat, 8 Aug 2026 09:54:42 -0400 Subject: [PATCH 16/16] backup test: only do 2 queries at a time --- pkg/storage/backup_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/storage/backup_test.go b/pkg/storage/backup_test.go index c652197a..adef82ec 100644 --- a/pkg/storage/backup_test.go +++ b/pkg/storage/backup_test.go @@ -48,6 +48,8 @@ func backupTestBattery(t *testing.T, storage *storage.Storage, expectLocked bool wg.Done() }) + wg.Wait() + // trying to write wg.Add(1) go t.Run(fmt.Sprintf("%s: put key api key", lockedStateTxt), func(t *testing.T) {