package nosql import ( "context" "encoding/json" "testing" "time" "github.com/pkg/errors" "github.com/smallstep/assert" "github.com/smallstep/certificates/authority/admin" "github.com/smallstep/certificates/db" "github.com/smallstep/nosql" "github.com/smallstep/nosql/database" "go.step.sm/linkedca" "google.golang.org/protobuf/types/known/timestamppb" ) func TestDB_getDBAdminBytes(t *testing.T) { adminID := "adminID" type test struct { db nosql.DB err error adminErr *admin.Error } var tests = map[string]func(t *testing.T) test{ "fail/not-found": func(t *testing.T) test { return test{ db: &db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, adminsTable) assert.Equals(t, string(key), adminID) return nil, database.ErrNotFound }, }, adminErr: admin.NewError(admin.ErrorNotFoundType, "admin adminID not found"), } }, "fail/db.Get-error": func(t *testing.T) test { return test{ db: &db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, adminsTable) assert.Equals(t, string(key), adminID) return nil, errors.New("force") }, }, err: errors.New("error loading admin adminID: force"), } }, "ok": func(t *testing.T) test { return test{ db: &db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, adminsTable) assert.Equals(t, string(key), adminID) return []byte("foo"), nil }, }, } }, } for name, run := range tests { tc := run(t) t.Run(name, func(t *testing.T) { d := DB{db: tc.db} if b, err := d.getDBAdminBytes(context.Background(), adminID); err != nil { switch k := err.(type) { case *admin.Error: if assert.NotNil(t, tc.adminErr) { assert.Equals(t, k.Type, tc.adminErr.Type) assert.Equals(t, k.Detail, tc.adminErr.Detail) assert.Equals(t, k.Status, tc.adminErr.Status) assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) assert.Equals(t, k.Detail, tc.adminErr.Detail) } default: if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } } } else if assert.Nil(t, tc.err) { assert.Equals(t, string(b), "foo") } }) } } func TestDB_getDBAdmin(t *testing.T) { adminID := "adminID" type test struct { db nosql.DB err error adminErr *admin.Error dba *dbAdmin } var tests = map[string]func(t *testing.T) test{ "fail/not-found": func(t *testing.T) test { return test{ db: &db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, adminsTable) assert.Equals(t, string(key), adminID) return nil, database.ErrNotFound }, }, adminErr: admin.NewError(admin.ErrorNotFoundType, "admin adminID not found"), } }, "fail/db.Get-error": func(t *testing.T) test { return test{ db: &db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, adminsTable) assert.Equals(t, string(key), adminID) return nil, errors.New("force") }, }, err: errors.New("error loading admin adminID: force"), } }, "fail/unmarshal-error": func(t *testing.T) test { return test{ db: &db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, adminsTable) assert.Equals(t, string(key), adminID) return []byte("foo"), nil }, }, err: errors.New("error unmarshaling admin adminID into dbAdmin"), } }, "fail/deleted": func(t *testing.T) test { now := clock.Now() dba := &dbAdmin{ ID: adminID, AuthorityID: admin.DefaultAuthorityID, ProvisionerID: "provID", Subject: "max@smallstep.com", Type: linkedca.Admin_SUPER_ADMIN, CreatedAt: now, DeletedAt: now, } b, err := json.Marshal(dba) assert.FatalError(t, err) return test{ db: &db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, adminsTable) assert.Equals(t, string(key), adminID) return b, nil }, }, adminErr: admin.NewError(admin.ErrorDeletedType, "admin adminID is deleted"), } }, "ok": func(t *testing.T) test { now := clock.Now() dba := &dbAdmin{ ID: adminID, AuthorityID: admin.DefaultAuthorityID, ProvisionerID: "provID", Subject: "max@smallstep.com", Type: linkedca.Admin_SUPER_ADMIN, CreatedAt: now, } b, err := json.Marshal(dba) assert.FatalError(t, err) return test{ db: &db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, adminsTable) assert.Equals(t, string(key), adminID) return b, nil }, }, dba: dba, } }, } for name, run := range tests { tc := run(t) t.Run(name, func(t *testing.T) { d := DB{db: tc.db, authorityID: admin.DefaultAuthorityID} if dba, err := d.getDBAdmin(context.Background(), adminID); err != nil { switch k := err.(type) { case *admin.Error: if assert.NotNil(t, tc.adminErr) { assert.Equals(t, k.Type, tc.adminErr.Type) assert.Equals(t, k.Detail, tc.adminErr.Detail) assert.Equals(t, k.Status, tc.adminErr.Status) assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) assert.Equals(t, k.Detail, tc.adminErr.Detail) } default: if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } } } else if assert.Nil(t, tc.err) && assert.Nil(t, tc.adminErr) { assert.Equals(t, dba.ID, adminID) assert.Equals(t, dba.AuthorityID, tc.dba.AuthorityID) assert.Equals(t, dba.ProvisionerID, tc.dba.ProvisionerID) assert.Equals(t, dba.Subject, tc.dba.Subject) assert.Equals(t, dba.Type, tc.dba.Type) assert.Equals(t, dba.CreatedAt, tc.dba.CreatedAt) assert.Fatal(t, dba.DeletedAt.IsZero()) } }) } } func TestDB_unmarshalDBAdmin(t *testing.T) { adminID := "adminID" type test struct { in []byte err error adminErr *admin.Error dba *dbAdmin } var tests = map[string]func(t *testing.T) test{ "fail/unmarshal-error": func(t *testing.T) test { return test{ in: []byte("foo"), err: errors.New("error unmarshaling admin adminID into dbAdmin"), } }, "fail/deleted-error": func(t *testing.T) test { dba := &dbAdmin{ DeletedAt: time.Now(), } data, err := json.Marshal(dba) assert.FatalError(t, err) return test{ in: data, adminErr: admin.NewError(admin.ErrorDeletedType, "admin adminID is deleted"), } }, "fail/authority-mismatch-error": func(t *testing.T) test { dba := &dbAdmin{ ID: adminID, AuthorityID: "foo", } data, err := json.Marshal(dba) assert.FatalError(t, err) return test{ in: data, adminErr: admin.NewError(admin.ErrorAuthorityMismatchType, "admin %s is not owned by authority %s", adminID, admin.DefaultAuthorityID), } }, "ok": func(t *testing.T) test { dba := &dbAdmin{ ID: adminID, Subject: "max@smallstep.com", ProvisionerID: "provID", AuthorityID: admin.DefaultAuthorityID, Type: linkedca.Admin_SUPER_ADMIN, CreatedAt: clock.Now(), } data, err := json.Marshal(dba) assert.FatalError(t, err) return test{ in: data, dba: dba, } }, } for name, run := range tests { tc := run(t) t.Run(name, func(t *testing.T) { d := DB{authorityID: admin.DefaultAuthorityID} if dba, err := d.unmarshalDBAdmin(tc.in, adminID); err != nil { switch k := err.(type) { case *admin.Error: if assert.NotNil(t, tc.adminErr) { assert.Equals(t, k.Type, tc.adminErr.Type) assert.Equals(t, k.Detail, tc.adminErr.Detail) assert.Equals(t, k.Status, tc.adminErr.Status) assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) assert.Equals(t, k.Detail, tc.adminErr.Detail) } default: if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } } } else if assert.Nil(t, tc.err) && assert.Nil(t, tc.adminErr) { assert.Equals(t, dba.ID, adminID) assert.Equals(t, dba.AuthorityID, tc.dba.AuthorityID) assert.Equals(t, dba.ProvisionerID, tc.dba.ProvisionerID) assert.Equals(t, dba.Subject, tc.dba.Subject) assert.Equals(t, dba.Type, tc.dba.Type) assert.Equals(t, dba.CreatedAt, tc.dba.CreatedAt) assert.Fatal(t, dba.DeletedAt.IsZero()) } }) } } func TestDB_unmarshalAdmin(t *testing.T) { adminID := "adminID" type test struct { in []byte err error adminErr *admin.Error dba *dbAdmin } var tests = map[string]func(t *testing.T) test{ "fail/unmarshal-error": func(t *testing.T) test { return test{ in: []byte("foo"), err: errors.New("error unmarshaling admin adminID into dbAdmin"), } }, "fail/deleted-error": func(t *testing.T) test { dba := &dbAdmin{ DeletedAt: time.Now(), } data, err := json.Marshal(dba) assert.FatalError(t, err) return test{ in: data, adminErr: admin.NewError(admin.ErrorDeletedType, "admin adminID is deleted"), } }, "ok": func(t *testing.T) test { dba := &dbAdmin{ ID: adminID, Subject: "max@smallstep.com", ProvisionerID: "provID", AuthorityID: admin.DefaultAuthorityID, Type: linkedca.Admin_SUPER_ADMIN, CreatedAt: clock.Now(), } data, err := json.Marshal(dba) assert.FatalError(t, err) return test{ in: data, dba: dba, } }, } for name, run := range tests { tc := run(t) t.Run(name, func(t *testing.T) { d := DB{authorityID: admin.DefaultAuthorityID} if adm, err := d.unmarshalAdmin(tc.in, adminID); err != nil { switch k := err.(type) { case *admin.Error: if assert.NotNil(t, tc.adminErr) { assert.Equals(t, k.Type, tc.adminErr.Type) assert.Equals(t, k.Detail, tc.adminErr.Detail) assert.Equals(t, k.Status, tc.adminErr.Status) assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) assert.Equals(t, k.Detail, tc.adminErr.Detail) } default: if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } } } else if assert.Nil(t, tc.err) && assert.Nil(t, tc.adminErr) { assert.Equals(t, adm.Id, adminID) assert.Equals(t, adm.AuthorityId, tc.dba.AuthorityID) assert.Equals(t, adm.ProvisionerId, tc.dba.ProvisionerID) assert.Equals(t, adm.Subject, tc.dba.Subject) assert.Equals(t, adm.Type, tc.dba.Type) assert.Equals(t, adm.CreatedAt, timestamppb.New(tc.dba.CreatedAt)) assert.Equals(t, adm.DeletedAt, timestamppb.New(tc.dba.DeletedAt)) } }) } } func TestDB_GetAdmin(t *testing.T) { adminID := "adminID" type test struct { db nosql.DB err error adminErr *admin.Error dba *dbAdmin } var tests = map[string]func(t *testing.T) test{ "fail/not-found": func(t *testing.T) test { return test{ db: &db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, adminsTable) assert.Equals(t, string(key), adminID) return nil, database.ErrNotFound }, }, adminErr: admin.NewError(admin.ErrorNotFoundType, "admin adminID not found"), } }, "fail/db.Get-error": func(t *testing.T) test { return test{ db: &db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, adminsTable) assert.Equals(t, string(key), adminID) return nil, errors.New("force") }, }, err: errors.New("error loading admin adminID: force"), } }, "fail/unmarshal-error": func(t *testing.T) test { return test{ db: &db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, adminsTable) assert.Equals(t, string(key), adminID) return []byte("foo"), nil }, }, err: errors.New("error unmarshaling admin adminID into dbAdmin"), } }, "fail/deleted": func(t *testing.T) test { dba := &dbAdmin{ ID: adminID, AuthorityID: admin.DefaultAuthorityID, ProvisionerID: "provID", Subject: "max@smallstep.com", Type: linkedca.Admin_SUPER_ADMIN, CreatedAt: clock.Now(), DeletedAt: clock.Now(), } b, err := json.Marshal(dba) assert.FatalError(t, err) return test{ db: &db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, adminsTable) assert.Equals(t, string(key), adminID) return b, nil }, }, dba: dba, adminErr: admin.NewError(admin.ErrorDeletedType, "admin adminID is deleted"), } }, "fail/authorityID-mismatch": func(t *testing.T) test { dba := &dbAdmin{ ID: adminID, AuthorityID: "foo", ProvisionerID: "provID", Subject: "max@smallstep.com", Type: linkedca.Admin_SUPER_ADMIN, CreatedAt: clock.Now(), } b, err := json.Marshal(dba) assert.FatalError(t, err) return test{ db: &db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, adminsTable) assert.Equals(t, string(key), adminID) return b, nil }, }, dba: dba, adminErr: admin.NewError(admin.ErrorAuthorityMismatchType, "admin %s is not owned by authority %s", dba.ID, admin.DefaultAuthorityID), } }, "ok": func(t *testing.T) test { dba := &dbAdmin{ ID: adminID, AuthorityID: admin.DefaultAuthorityID, ProvisionerID: "provID", Subject: "max@smallstep.com", Type: linkedca.Admin_SUPER_ADMIN, CreatedAt: clock.Now(), } b, err := json.Marshal(dba) assert.FatalError(t, err) return test{ db: &db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, adminsTable) assert.Equals(t, string(key), adminID) return b, nil }, }, dba: dba, } }, } for name, run := range tests { tc := run(t) t.Run(name, func(t *testing.T) { d := DB{db: tc.db, authorityID: admin.DefaultAuthorityID} if adm, err := d.GetAdmin(context.Background(), adminID); err != nil { switch k := err.(type) { case *admin.Error: if assert.NotNil(t, tc.adminErr) { assert.Equals(t, k.Type, tc.adminErr.Type) assert.Equals(t, k.Detail, tc.adminErr.Detail) assert.Equals(t, k.Status, tc.adminErr.Status) assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) assert.Equals(t, k.Detail, tc.adminErr.Detail) } default: if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } } } else if assert.Nil(t, tc.err) && assert.Nil(t, tc.adminErr) { assert.Equals(t, adm.Id, adminID) assert.Equals(t, adm.AuthorityId, tc.dba.AuthorityID) assert.Equals(t, adm.ProvisionerId, tc.dba.ProvisionerID) assert.Equals(t, adm.Subject, tc.dba.Subject) assert.Equals(t, adm.Type, tc.dba.Type) assert.Equals(t, adm.CreatedAt, timestamppb.New(tc.dba.CreatedAt)) assert.Equals(t, adm.DeletedAt, timestamppb.New(tc.dba.DeletedAt)) } }) } } func TestDB_DeleteAdmin(t *testing.T) { adminID := "adminID" type test struct { db nosql.DB err error adminErr *admin.Error } var tests = map[string]func(t *testing.T) test{ "fail/not-found": func(t *testing.T) test { return test{ db: &db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, adminsTable) assert.Equals(t, string(key), adminID) return nil, database.ErrNotFound }, }, adminErr: admin.NewError(admin.ErrorNotFoundType, "admin adminID not found"), } }, "fail/db.Get-error": func(t *testing.T) test { return test{ db: &db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, adminsTable) assert.Equals(t, string(key), adminID) return nil, errors.New("force") }, }, err: errors.New("error loading admin adminID: force"), } }, "fail/save-error": func(t *testing.T) test { dba := &dbAdmin{ ID: adminID, AuthorityID: admin.DefaultAuthorityID, ProvisionerID: "provID", Subject: "max@smallstep.com", Type: linkedca.Admin_SUPER_ADMIN, CreatedAt: clock.Now(), } data, err := json.Marshal(dba) assert.FatalError(t, err) return test{ db: &db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, adminsTable) assert.Equals(t, string(key), adminID) return data, nil }, MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { assert.Equals(t, bucket, adminsTable) assert.Equals(t, string(key), adminID) assert.Equals(t, string(old), string(data)) var _dba = new(dbAdmin) assert.FatalError(t, json.Unmarshal(nu, _dba)) assert.Equals(t, _dba.ID, dba.ID) assert.Equals(t, _dba.AuthorityID, dba.AuthorityID) assert.Equals(t, _dba.ProvisionerID, dba.ProvisionerID) assert.Equals(t, _dba.Subject, dba.Subject) assert.Equals(t, _dba.Type, dba.Type) assert.Equals(t, _dba.CreatedAt, dba.CreatedAt) assert.True(t, _dba.DeletedAt.Before(time.Now())) assert.True(t, _dba.DeletedAt.After(time.Now().Add(-time.Minute))) return nil, false, errors.New("force") }, }, err: errors.New("error saving authority admin: force"), } }, "ok": func(t *testing.T) test { dba := &dbAdmin{ ID: adminID, AuthorityID: admin.DefaultAuthorityID, ProvisionerID: "provID", Subject: "max@smallstep.com", Type: linkedca.Admin_SUPER_ADMIN, CreatedAt: clock.Now(), } data, err := json.Marshal(dba) assert.FatalError(t, err) return test{ db: &db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, adminsTable) assert.Equals(t, string(key), adminID) return data, nil }, MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { assert.Equals(t, bucket, adminsTable) assert.Equals(t, string(key), adminID) assert.Equals(t, string(old), string(data)) var _dba = new(dbAdmin) assert.FatalError(t, json.Unmarshal(nu, _dba)) assert.Equals(t, _dba.ID, dba.ID) assert.Equals(t, _dba.AuthorityID, dba.AuthorityID) assert.Equals(t, _dba.ProvisionerID, dba.ProvisionerID) assert.Equals(t, _dba.Subject, dba.Subject) assert.Equals(t, _dba.Type, dba.Type) assert.Equals(t, _dba.CreatedAt, dba.CreatedAt) assert.True(t, _dba.DeletedAt.Before(time.Now())) assert.True(t, _dba.DeletedAt.After(time.Now().Add(-time.Minute))) return nu, true, nil }, }, } }, } for name, run := range tests { tc := run(t) t.Run(name, func(t *testing.T) { d := DB{db: tc.db, authorityID: admin.DefaultAuthorityID} if err := d.DeleteAdmin(context.Background(), adminID); err != nil { switch k := err.(type) { case *admin.Error: if assert.NotNil(t, tc.adminErr) { assert.Equals(t, k.Type, tc.adminErr.Type) assert.Equals(t, k.Detail, tc.adminErr.Detail) assert.Equals(t, k.Status, tc.adminErr.Status) assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) assert.Equals(t, k.Detail, tc.adminErr.Detail) } default: if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } } } }) } } func TestDB_UpdateAdmin(t *testing.T) { adminID := "adminID" type test struct { db nosql.DB err error adminErr *admin.Error adm *linkedca.Admin } var tests = map[string]func(t *testing.T) test{ "fail/not-found": func(t *testing.T) test { return test{ adm: &linkedca.Admin{Id: adminID}, db: &db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, adminsTable) assert.Equals(t, string(key), adminID) return nil, database.ErrNotFound }, }, adminErr: admin.NewError(admin.ErrorNotFoundType, "admin adminID not found"), } }, "fail/db.Get-error": func(t *testing.T) test { return test{ adm: &linkedca.Admin{Id: adminID}, db: &db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, adminsTable) assert.Equals(t, string(key), adminID) return nil, errors.New("force") }, }, err: errors.New("error loading admin adminID: force"), } }, "fail/save-error": func(t *testing.T) test { dba := &dbAdmin{ ID: adminID, AuthorityID: admin.DefaultAuthorityID, ProvisionerID: "provID", Subject: "max@smallstep.com", Type: linkedca.Admin_SUPER_ADMIN, CreatedAt: clock.Now(), } upd := dba.convert() upd.Type = linkedca.Admin_ADMIN data, err := json.Marshal(dba) assert.FatalError(t, err) return test{ adm: upd, db: &db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, adminsTable) assert.Equals(t, string(key), adminID) return data, nil }, MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { assert.Equals(t, bucket, adminsTable) assert.Equals(t, string(key), adminID) assert.Equals(t, string(old), string(data)) var _dba = new(dbAdmin) assert.FatalError(t, json.Unmarshal(nu, _dba)) assert.Equals(t, _dba.ID, dba.ID) assert.Equals(t, _dba.AuthorityID, dba.AuthorityID) assert.Equals(t, _dba.ProvisionerID, dba.ProvisionerID) assert.Equals(t, _dba.Subject, dba.Subject) assert.Equals(t, _dba.Type, linkedca.Admin_ADMIN) assert.Equals(t, _dba.CreatedAt, dba.CreatedAt) return nil, false, errors.New("force") }, }, err: errors.New("error saving authority admin: force"), } }, "ok": func(t *testing.T) test { dba := &dbAdmin{ ID: adminID, AuthorityID: admin.DefaultAuthorityID, ProvisionerID: "provID", Subject: "max@smallstep.com", Type: linkedca.Admin_SUPER_ADMIN, CreatedAt: clock.Now(), } upd := dba.convert() upd.Type = linkedca.Admin_ADMIN data, err := json.Marshal(dba) assert.FatalError(t, err) return test{ adm: upd, db: &db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, adminsTable) assert.Equals(t, string(key), adminID) return data, nil }, MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { assert.Equals(t, bucket, adminsTable) assert.Equals(t, string(key), adminID) assert.Equals(t, string(old), string(data)) var _dba = new(dbAdmin) assert.FatalError(t, json.Unmarshal(nu, _dba)) assert.Equals(t, _dba.ID, dba.ID) assert.Equals(t, _dba.AuthorityID, dba.AuthorityID) assert.Equals(t, _dba.ProvisionerID, dba.ProvisionerID) assert.Equals(t, _dba.Subject, dba.Subject) assert.Equals(t, _dba.Type, linkedca.Admin_ADMIN) assert.Equals(t, _dba.CreatedAt, dba.CreatedAt) return nu, true, nil }, }, } }, } for name, run := range tests { tc := run(t) t.Run(name, func(t *testing.T) { d := DB{db: tc.db, authorityID: admin.DefaultAuthorityID} if err := d.UpdateAdmin(context.Background(), tc.adm); err != nil { switch k := err.(type) { case *admin.Error: if assert.NotNil(t, tc.adminErr) { assert.Equals(t, k.Type, tc.adminErr.Type) assert.Equals(t, k.Detail, tc.adminErr.Detail) assert.Equals(t, k.Status, tc.adminErr.Status) assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) assert.Equals(t, k.Detail, tc.adminErr.Detail) } default: if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } } } }) } } func TestDB_CreateAdmin(t *testing.T) { type test struct { db nosql.DB err error adminErr *admin.Error adm *linkedca.Admin } var tests = map[string]func(t *testing.T) test{ "fail/save-error": func(t *testing.T) test { adm := &linkedca.Admin{ AuthorityId: admin.DefaultAuthorityID, ProvisionerId: "provID", Subject: "max@smallstep.com", Type: linkedca.Admin_ADMIN, } return test{ adm: adm, db: &db.MockNoSQLDB{ MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { assert.Equals(t, bucket, adminsTable) assert.Equals(t, old, nil) var _dba = new(dbAdmin) assert.FatalError(t, json.Unmarshal(nu, _dba)) assert.True(t, len(_dba.ID) > 0 && _dba.ID == string(key)) assert.Equals(t, _dba.AuthorityID, adm.AuthorityId) assert.Equals(t, _dba.ProvisionerID, adm.ProvisionerId) assert.Equals(t, _dba.Subject, adm.Subject) assert.Equals(t, _dba.Type, linkedca.Admin_ADMIN) assert.True(t, _dba.CreatedAt.Before(time.Now())) assert.True(t, _dba.CreatedAt.After(time.Now().Add(-time.Minute))) return nil, false, errors.New("force") }, }, err: errors.New("error saving authority admin: force"), } }, "ok": func(t *testing.T) test { adm := &linkedca.Admin{ AuthorityId: admin.DefaultAuthorityID, ProvisionerId: "provID", Subject: "max@smallstep.com", Type: linkedca.Admin_ADMIN, } return test{ adm: adm, db: &db.MockNoSQLDB{ MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { assert.Equals(t, bucket, adminsTable) assert.Equals(t, old, nil) var _dba = new(dbAdmin) assert.FatalError(t, json.Unmarshal(nu, _dba)) assert.True(t, len(_dba.ID) > 0 && _dba.ID == string(key)) assert.Equals(t, _dba.AuthorityID, adm.AuthorityId) assert.Equals(t, _dba.ProvisionerID, adm.ProvisionerId) assert.Equals(t, _dba.Subject, adm.Subject) assert.Equals(t, _dba.Type, linkedca.Admin_ADMIN) assert.True(t, _dba.CreatedAt.Before(time.Now())) assert.True(t, _dba.CreatedAt.After(time.Now().Add(-time.Minute))) return nu, true, nil }, }, } }, } for name, run := range tests { tc := run(t) t.Run(name, func(t *testing.T) { d := DB{db: tc.db, authorityID: admin.DefaultAuthorityID} if err := d.CreateAdmin(context.Background(), tc.adm); err != nil { switch k := err.(type) { case *admin.Error: if assert.NotNil(t, tc.adminErr) { assert.Equals(t, k.Type, tc.adminErr.Type) assert.Equals(t, k.Detail, tc.adminErr.Detail) assert.Equals(t, k.Status, tc.adminErr.Status) assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) assert.Equals(t, k.Detail, tc.adminErr.Detail) } default: if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } } } }) } } func TestDB_GetAdmins(t *testing.T) { now := clock.Now() fooAdmin := &dbAdmin{ ID: "foo", AuthorityID: admin.DefaultAuthorityID, ProvisionerID: "provID", Subject: "foo@smallstep.com", Type: linkedca.Admin_SUPER_ADMIN, CreatedAt: now, } foob, err := json.Marshal(fooAdmin) assert.FatalError(t, err) barAdmin := &dbAdmin{ ID: "bar", AuthorityID: admin.DefaultAuthorityID, ProvisionerID: "provID", Subject: "bar@smallstep.com", Type: linkedca.Admin_ADMIN, CreatedAt: now, DeletedAt: now, } barb, err := json.Marshal(barAdmin) assert.FatalError(t, err) bazAdmin := &dbAdmin{ ID: "baz", AuthorityID: "bazzer", ProvisionerID: "provID", Subject: "baz@smallstep.com", Type: linkedca.Admin_ADMIN, CreatedAt: now, } bazb, err := json.Marshal(bazAdmin) assert.FatalError(t, err) zapAdmin := &dbAdmin{ ID: "zap", AuthorityID: admin.DefaultAuthorityID, ProvisionerID: "provID", Subject: "zap@smallstep.com", Type: linkedca.Admin_ADMIN, CreatedAt: now, } zapb, err := json.Marshal(zapAdmin) assert.FatalError(t, err) type test struct { db nosql.DB err error adminErr *admin.Error verify func(*testing.T, []*linkedca.Admin) } var tests = map[string]func(t *testing.T) test{ "fail/db.List-error": func(t *testing.T) test { return test{ db: &db.MockNoSQLDB{ MList: func(bucket []byte) ([]*database.Entry, error) { assert.Equals(t, bucket, adminsTable) return nil, errors.New("force") }, }, err: errors.New("error loading admins: force"), } }, "fail/unmarshal-error": func(t *testing.T) test { ret := []*database.Entry{ {Bucket: adminsTable, Key: []byte("foo"), Value: foob}, {Bucket: adminsTable, Key: []byte("bar"), Value: barb}, {Bucket: adminsTable, Key: []byte("zap"), Value: []byte("zap")}, } return test{ db: &db.MockNoSQLDB{ MList: func(bucket []byte) ([]*database.Entry, error) { assert.Equals(t, bucket, adminsTable) return ret, nil }, }, err: errors.New("error unmarshaling admin zap into dbAdmin"), } }, "ok/none": func(t *testing.T) test { ret := []*database.Entry{} return test{ db: &db.MockNoSQLDB{ MList: func(bucket []byte) ([]*database.Entry, error) { assert.Equals(t, bucket, adminsTable) return ret, nil }, }, verify: func(t *testing.T, admins []*linkedca.Admin) { assert.Equals(t, len(admins), 0) }, } }, "ok/only-invalid": func(t *testing.T) test { ret := []*database.Entry{ {Bucket: adminsTable, Key: []byte("bar"), Value: barb}, {Bucket: adminsTable, Key: []byte("baz"), Value: bazb}, } return test{ db: &db.MockNoSQLDB{ MList: func(bucket []byte) ([]*database.Entry, error) { assert.Equals(t, bucket, adminsTable) return ret, nil }, }, verify: func(t *testing.T, admins []*linkedca.Admin) { assert.Equals(t, len(admins), 0) }, } }, "ok": func(t *testing.T) test { ret := []*database.Entry{ {Bucket: adminsTable, Key: []byte("foo"), Value: foob}, {Bucket: adminsTable, Key: []byte("bar"), Value: barb}, {Bucket: adminsTable, Key: []byte("baz"), Value: bazb}, {Bucket: adminsTable, Key: []byte("zap"), Value: zapb}, } return test{ db: &db.MockNoSQLDB{ MList: func(bucket []byte) ([]*database.Entry, error) { assert.Equals(t, bucket, adminsTable) return ret, nil }, }, verify: func(t *testing.T, admins []*linkedca.Admin) { assert.Equals(t, len(admins), 2) assert.Equals(t, admins[0].Id, fooAdmin.ID) assert.Equals(t, admins[0].AuthorityId, fooAdmin.AuthorityID) assert.Equals(t, admins[0].ProvisionerId, fooAdmin.ProvisionerID) assert.Equals(t, admins[0].Subject, fooAdmin.Subject) assert.Equals(t, admins[0].Type, fooAdmin.Type) assert.Equals(t, admins[0].CreatedAt, timestamppb.New(fooAdmin.CreatedAt)) assert.Equals(t, admins[0].DeletedAt, timestamppb.New(fooAdmin.DeletedAt)) assert.Equals(t, admins[1].Id, zapAdmin.ID) assert.Equals(t, admins[1].AuthorityId, zapAdmin.AuthorityID) assert.Equals(t, admins[1].ProvisionerId, zapAdmin.ProvisionerID) assert.Equals(t, admins[1].Subject, zapAdmin.Subject) assert.Equals(t, admins[1].Type, zapAdmin.Type) assert.Equals(t, admins[1].CreatedAt, timestamppb.New(zapAdmin.CreatedAt)) assert.Equals(t, admins[1].DeletedAt, timestamppb.New(zapAdmin.DeletedAt)) }, } }, } for name, run := range tests { tc := run(t) t.Run(name, func(t *testing.T) { d := DB{db: tc.db, authorityID: admin.DefaultAuthorityID} if admins, err := d.GetAdmins(context.Background()); err != nil { switch k := err.(type) { case *admin.Error: if assert.NotNil(t, tc.adminErr) { assert.Equals(t, k.Type, tc.adminErr.Type) assert.Equals(t, k.Detail, tc.adminErr.Detail) assert.Equals(t, k.Status, tc.adminErr.Status) assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) assert.Equals(t, k.Detail, tc.adminErr.Detail) } default: if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } } } else if assert.Nil(t, tc.err) && assert.Nil(t, tc.adminErr) { tc.verify(t, admins) } }) } }