diff --git a/acme/db/nosql/challenge_test.go b/acme/db/nosql/challenge_test.go index 34af74ce..314fc5f7 100644 --- a/acme/db/nosql/challenge_test.go +++ b/acme/db/nosql/challenge_test.go @@ -101,7 +101,7 @@ func TestDB_getDBChallenge(t *testing.T) { assert.Equals(t, k.Type, tc.acmeErr.Type) assert.Equals(t, k.Detail, tc.acmeErr.Detail) assert.Equals(t, k.Status, tc.acmeErr.Status) - assert.Equals(t, k.Err, tc.acmeErr.Err) + assert.Equals(t, k.Err.Error(), tc.acmeErr.Err.Error()) assert.Equals(t, k.Detail, tc.acmeErr.Detail) } default: diff --git a/acme/db/nosql/nosql_test.go b/acme/db/nosql/nosql_test.go new file mode 100644 index 00000000..b7a91a2f --- /dev/null +++ b/acme/db/nosql/nosql_test.go @@ -0,0 +1,126 @@ +package nosql + +import ( + "context" + "testing" + + "github.com/pkg/errors" + "github.com/smallstep/assert" + "github.com/smallstep/certificates/db" + "github.com/smallstep/nosql" +) + +func TestNew(t *testing.T) { + type test struct { + db nosql.DB + err error + } + var tests = map[string]test{ + "fail/db.CreateTable-error": test{ + db: &db.MockNoSQLDB{ + MCreateTable: func(bucket []byte) error { + assert.Equals(t, string(bucket), string(accountTable)) + return errors.New("force") + }, + }, + err: errors.Errorf("error creating table %s: force", string(accountTable)), + }, + "ok": test{ + db: &db.MockNoSQLDB{ + MCreateTable: func(bucket []byte) error { + return nil + }, + }, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + if _, err := New(tc.db); err != nil { + if assert.NotNil(t, tc.err) { + assert.HasPrefix(t, err.Error(), tc.err.Error()) + } + } else { + assert.Nil(t, tc.err) + } + }) + } +} + +type errorThrower string + +func (et errorThrower) MarshalJSON() ([]byte, error) { + return nil, errors.New("force") +} + +func TestDB_save(t *testing.T) { + type test struct { + db nosql.DB + nu interface{} + old interface{} + err error + } + var tests = map[string]test{ + "fail/error-marshaling-new": test{ + nu: errorThrower("foo"), + err: errors.New("error marshaling acme type: challenge"), + }, + "fail/error-marshaling-old": test{ + nu: "new", + old: errorThrower("foo"), + err: errors.New("error marshaling acme type: challenge"), + }, + "fail/db.CmpAndSwap-error": test{ + nu: "new", + old: "old", + db: &db.MockNoSQLDB{ + MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { + assert.Equals(t, bucket, challengeTable) + assert.Equals(t, string(key), "id") + assert.Equals(t, string(old), "\"old\"") + assert.Equals(t, string(nu), "\"new\"") + return nil, false, errors.New("force") + }, + }, + err: errors.New("error saving acme challenge: force"), + }, + "fail/db.CmpAndSwap-false-marshaling-old": test{ + nu: "new", + old: "old", + db: &db.MockNoSQLDB{ + MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { + assert.Equals(t, bucket, challengeTable) + assert.Equals(t, string(key), "id") + assert.Equals(t, string(old), "\"old\"") + assert.Equals(t, string(nu), "\"new\"") + return nil, false, nil + }, + }, + err: errors.New("error saving acme challenge; changed since last read"), + }, + "ok": test{ + nu: "new", + old: "old", + db: &db.MockNoSQLDB{ + MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { + assert.Equals(t, bucket, challengeTable) + assert.Equals(t, string(key), "id") + assert.Equals(t, string(old), "\"old\"") + assert.Equals(t, string(nu), "\"new\"") + return nu, true, nil + }, + }, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + db := &DB{db: tc.db} + if err := db.save(context.Background(), "id", tc.nu, tc.old, "challenge", challengeTable); err != nil { + if assert.NotNil(t, tc.err) { + assert.HasPrefix(t, err.Error(), tc.err.Error()) + } + } else { + assert.Nil(t, tc.err) + } + }) + } +} diff --git a/acme/db/nosql/order.go b/acme/db/nosql/order.go index 862c32df..a64316a6 100644 --- a/acme/db/nosql/order.go +++ b/acme/db/nosql/order.go @@ -15,18 +15,18 @@ import ( var ordersByAccountMux sync.Mutex type dbOrder struct { - ID string `json:"id"` - AccountID string `json:"accountID"` - ProvisionerID string `json:"provisionerID"` - Created time.Time `json:"created"` - Expires time.Time `json:"expires,omitempty"` - Status acme.Status `json:"status"` - Identifiers []acme.Identifier `json:"identifiers"` - NotBefore time.Time `json:"notBefore,omitempty"` - NotAfter time.Time `json:"notAfter,omitempty"` - Error *acme.Error `json:"error,omitempty"` - Authorizations []string `json:"authorizations"` - CertificateID string `json:"certificate,omitempty"` + ID string `json:"id"` + AccountID string `json:"accountID"` + ProvisionerID string `json:"provisionerID"` + CreatedAt time.Time `json:"createdAt"` + ExpiresAt time.Time `json:"expiresAt,omitempty"` + Status acme.Status `json:"status"` + Identifiers []acme.Identifier `json:"identifiers"` + NotBefore time.Time `json:"notBefore,omitempty"` + NotAfter time.Time `json:"notAfter,omitempty"` + Error *acme.Error `json:"error,omitempty"` + AuthorizationIDs []string `json:"authorizationIDs"` + CertificateID string `json:"certificate,omitempty"` } func (a *dbOrder) clone() *dbOrder { @@ -38,13 +38,13 @@ func (a *dbOrder) clone() *dbOrder { func (db *DB) getDBOrder(ctx context.Context, id string) (*dbOrder, error) { b, err := db.db.Get(orderTable, []byte(id)) if nosql.IsErrNotFound(err) { - return nil, acme.WrapError(acme.ErrorMalformedType, err, "order %s not found", id) + return nil, acme.NewError(acme.ErrorMalformedType, "order %s not found", id) } else if err != nil { return nil, errors.Wrapf(err, "error loading order %s", id) } o := new(dbOrder) if err := json.Unmarshal(b, &o); err != nil { - return nil, errors.Wrap(err, "error unmarshaling order") + return nil, errors.Wrapf(err, "error unmarshaling order %s into dbOrder", id) } return o, nil } @@ -57,15 +57,17 @@ func (db *DB) GetOrder(ctx context.Context, id string) (*acme.Order, error) { } o := &acme.Order{ + ID: dbo.ID, + AccountID: dbo.AccountID, + ProvisionerID: dbo.ProvisionerID, + CertificateID: dbo.CertificateID, Status: dbo.Status, - ExpiresAt: dbo.Expires, + ExpiresAt: dbo.ExpiresAt, Identifiers: dbo.Identifiers, NotBefore: dbo.NotBefore, NotAfter: dbo.NotAfter, - AuthorizationIDs: dbo.Authorizations, - ID: dbo.ID, - ProvisionerID: dbo.ProvisionerID, - CertificateID: dbo.CertificateID, + AuthorizationIDs: dbo.AuthorizationIDs, + Error: dbo.Error, } return o, nil @@ -81,16 +83,16 @@ func (db *DB) CreateOrder(ctx context.Context, o *acme.Order) error { now := clock.Now() dbo := &dbOrder{ - ID: o.ID, - AccountID: o.AccountID, - ProvisionerID: o.ProvisionerID, - Created: now, - Status: acme.StatusPending, - Expires: o.ExpiresAt, - Identifiers: o.Identifiers, - NotBefore: o.NotBefore, - NotAfter: o.NotBefore, - Authorizations: o.AuthorizationIDs, + ID: o.ID, + AccountID: o.AccountID, + ProvisionerID: o.ProvisionerID, + Status: o.Status, + CreatedAt: now, + ExpiresAt: o.ExpiresAt, + Identifiers: o.Identifiers, + NotBefore: o.NotBefore, + NotAfter: o.NotBefore, + AuthorizationIDs: o.AuthorizationIDs, } if err := db.save(ctx, o.ID, dbo, nil, "order", orderTable); err != nil { return err @@ -103,6 +105,21 @@ func (db *DB) CreateOrder(ctx context.Context, o *acme.Order) error { return nil } +// UpdateOrder saves an updated ACME Order to the database. +func (db *DB) UpdateOrder(ctx context.Context, o *acme.Order) error { + old, err := db.getDBOrder(ctx, o.ID) + if err != nil { + return err + } + + nu := old.clone() + + nu.Status = o.Status + nu.Error = o.Error + nu.CertificateID = o.CertificateID + return db.save(ctx, old.ID, nu, old, "order", orderTable) +} + type orderIDsByAccount struct{} func (db *DB) updateAddOrderIDs(ctx context.Context, accID string, addOids ...string) ([]string, error) { @@ -158,18 +175,3 @@ func (db *DB) updateAddOrderIDs(ctx context.Context, accID string, addOids ...st func (db *DB) GetOrdersByAccountID(ctx context.Context, accID string) ([]string, error) { return db.updateAddOrderIDs(ctx, accID) } - -// UpdateOrder saves an updated ACME Order to the database. -func (db *DB) UpdateOrder(ctx context.Context, o *acme.Order) error { - old, err := db.getDBOrder(ctx, o.ID) - if err != nil { - return err - } - - nu := old.clone() - - nu.Status = o.Status - nu.Error = o.Error - nu.CertificateID = o.CertificateID - return db.save(ctx, old.ID, nu, old, "order", orderTable) -} diff --git a/acme/db/nosql/order_test.go b/acme/db/nosql/order_test.go new file mode 100644 index 00000000..8ce7ac79 --- /dev/null +++ b/acme/db/nosql/order_test.go @@ -0,0 +1,557 @@ +package nosql + +import ( + "context" + "encoding/json" + "testing" + "time" + + "github.com/pkg/errors" + "github.com/smallstep/assert" + "github.com/smallstep/certificates/acme" + "github.com/smallstep/certificates/db" + "github.com/smallstep/nosql" + nosqldb "github.com/smallstep/nosql/database" +) + +func TestDB_getDBOrder(t *testing.T) { + orderID := "orderID" + type test struct { + db nosql.DB + err error + acmeErr *acme.Error + dbo *dbOrder + } + 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, orderTable) + assert.Equals(t, string(key), orderID) + + return nil, nosqldb.ErrNotFound + }, + }, + acmeErr: acme.NewError(acme.ErrorMalformedType, "order orderID 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, orderTable) + assert.Equals(t, string(key), orderID) + + return nil, errors.New("force") + }, + }, + err: errors.New("error loading order orderID: 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, orderTable) + assert.Equals(t, string(key), orderID) + + return []byte("foo"), nil + }, + }, + err: errors.New("error unmarshaling order orderID into dbOrder"), + } + }, + "ok": func(t *testing.T) test { + now := clock.Now() + dbo := &dbOrder{ + ID: orderID, + AccountID: "accID", + ProvisionerID: "provID", + CertificateID: "certID", + Status: acme.StatusValid, + ExpiresAt: now, + CreatedAt: now, + NotBefore: now, + NotAfter: now, + Identifiers: []acme.Identifier{ + {Type: "dns", Value: "test.ca.smallstep.com"}, + {Type: "dns", Value: "example.foo.com"}, + }, + AuthorizationIDs: []string{"foo", "bar"}, + Error: acme.NewError(acme.ErrorMalformedType, "force"), + } + b, err := json.Marshal(dbo) + assert.FatalError(t, err) + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, orderTable) + assert.Equals(t, string(key), orderID) + + return b, nil + }, + }, + dbo: dbo, + } + }, + } + for name, run := range tests { + tc := run(t) + t.Run(name, func(t *testing.T) { + db := DB{db: tc.db} + if dbo, err := db.getDBOrder(context.Background(), orderID); err != nil { + switch k := err.(type) { + case *acme.Error: + if assert.NotNil(t, tc.acmeErr) { + assert.Equals(t, k.Type, tc.acmeErr.Type) + assert.Equals(t, k.Detail, tc.acmeErr.Detail) + assert.Equals(t, k.Status, tc.acmeErr.Status) + assert.Equals(t, k.Err.Error(), tc.acmeErr.Err.Error()) + assert.Equals(t, k.Detail, tc.acmeErr.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, dbo.ID, tc.dbo.ID) + assert.Equals(t, dbo.ProvisionerID, tc.dbo.ProvisionerID) + assert.Equals(t, dbo.CertificateID, tc.dbo.CertificateID) + assert.Equals(t, dbo.Status, tc.dbo.Status) + assert.Equals(t, dbo.CreatedAt, tc.dbo.CreatedAt) + assert.Equals(t, dbo.ExpiresAt, tc.dbo.ExpiresAt) + assert.Equals(t, dbo.NotBefore, tc.dbo.NotBefore) + assert.Equals(t, dbo.NotAfter, tc.dbo.NotAfter) + assert.Equals(t, dbo.Identifiers, tc.dbo.Identifiers) + assert.Equals(t, dbo.AuthorizationIDs, tc.dbo.AuthorizationIDs) + assert.Equals(t, dbo.Error.Error(), tc.dbo.Error.Error()) + } + } + }) + } +} + +func TestDB_GetOrder(t *testing.T) { + orderID := "orderID" + type test struct { + db nosql.DB + err error + acmeErr *acme.Error + dbo *dbOrder + } + var tests = map[string]func(t *testing.T) test{ + "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, orderTable) + assert.Equals(t, string(key), orderID) + + return nil, errors.New("force") + }, + }, + err: errors.New("error loading order orderID: force"), + } + }, + "fail/forward-acme-error": func(t *testing.T) test { + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, orderTable) + assert.Equals(t, string(key), orderID) + + return nil, nosqldb.ErrNotFound + }, + }, + acmeErr: acme.NewError(acme.ErrorMalformedType, "order orderID not found"), + } + }, + "ok": func(t *testing.T) test { + now := clock.Now() + dbo := &dbOrder{ + ID: orderID, + AccountID: "accID", + ProvisionerID: "provID", + CertificateID: "certID", + Status: acme.StatusValid, + ExpiresAt: now, + CreatedAt: now, + NotBefore: now, + NotAfter: now, + Identifiers: []acme.Identifier{ + {Type: "dns", Value: "test.ca.smallstep.com"}, + {Type: "dns", Value: "example.foo.com"}, + }, + AuthorizationIDs: []string{"foo", "bar"}, + Error: acme.NewError(acme.ErrorMalformedType, "force"), + } + b, err := json.Marshal(dbo) + assert.FatalError(t, err) + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, orderTable) + assert.Equals(t, string(key), orderID) + return b, nil + }, + }, + dbo: dbo, + } + }, + } + for name, run := range tests { + tc := run(t) + t.Run(name, func(t *testing.T) { + db := DB{db: tc.db} + if o, err := db.GetOrder(context.Background(), orderID); err != nil { + switch k := err.(type) { + case *acme.Error: + if assert.NotNil(t, tc.acmeErr) { + assert.Equals(t, k.Type, tc.acmeErr.Type) + assert.Equals(t, k.Detail, tc.acmeErr.Detail) + assert.Equals(t, k.Status, tc.acmeErr.Status) + assert.Equals(t, k.Err.Error(), tc.acmeErr.Err.Error()) + assert.Equals(t, k.Detail, tc.acmeErr.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, o.ID, tc.dbo.ID) + assert.Equals(t, o.AccountID, tc.dbo.AccountID) + assert.Equals(t, o.ProvisionerID, tc.dbo.ProvisionerID) + assert.Equals(t, o.CertificateID, tc.dbo.CertificateID) + assert.Equals(t, o.Status, tc.dbo.Status) + assert.Equals(t, o.ExpiresAt, tc.dbo.ExpiresAt) + assert.Equals(t, o.NotBefore, tc.dbo.NotBefore) + assert.Equals(t, o.NotAfter, tc.dbo.NotAfter) + assert.Equals(t, o.Identifiers, tc.dbo.Identifiers) + assert.Equals(t, o.AuthorizationIDs, tc.dbo.AuthorizationIDs) + assert.Equals(t, o.Error.Error(), tc.dbo.Error.Error()) + } + } + }) + } +} + +func TestDB_UpdateOrder(t *testing.T) { + orderID := "orderID" + now := clock.Now() + dbo := &dbOrder{ + ID: orderID, + AccountID: "accID", + ProvisionerID: "provID", + Status: acme.StatusPending, + ExpiresAt: now, + CreatedAt: now, + NotBefore: now, + NotAfter: now, + Identifiers: []acme.Identifier{ + {Type: "dns", Value: "test.ca.smallstep.com"}, + {Type: "dns", Value: "example.foo.com"}, + }, + AuthorizationIDs: []string{"foo", "bar"}, + } + b, err := json.Marshal(dbo) + assert.FatalError(t, err) + type test struct { + db nosql.DB + o *acme.Order + err error + } + var tests = map[string]func(t *testing.T) test{ + "fail/db.Get-error": func(t *testing.T) test { + return test{ + o: &acme.Order{ + ID: orderID, + }, + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, orderTable) + assert.Equals(t, string(key), orderID) + + return nil, errors.New("force") + }, + }, + err: errors.New("error loading order orderID: force"), + } + }, + "fail/save-error": func(t *testing.T) test { + o := &acme.Order{ + ID: orderID, + Status: acme.StatusValid, + CertificateID: "certID", + Error: acme.NewError(acme.ErrorMalformedType, "force"), + } + return test{ + o: o, + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, orderTable) + assert.Equals(t, string(key), orderID) + + return b, nil + }, + MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { + assert.Equals(t, bucket, orderTable) + assert.Equals(t, old, b) + + dbNew := new(dbOrder) + assert.FatalError(t, json.Unmarshal(nu, dbNew)) + assert.Equals(t, dbNew.ID, dbo.ID) + assert.Equals(t, dbNew.AccountID, dbo.AccountID) + assert.Equals(t, dbNew.ProvisionerID, dbo.ProvisionerID) + assert.Equals(t, dbNew.CertificateID, o.CertificateID) + assert.Equals(t, dbNew.Status, o.Status) + assert.Equals(t, dbNew.CreatedAt, dbo.CreatedAt) + assert.Equals(t, dbNew.ExpiresAt, dbo.ExpiresAt) + assert.Equals(t, dbNew.NotBefore, dbo.NotBefore) + assert.Equals(t, dbNew.NotAfter, dbo.NotAfter) + assert.Equals(t, dbNew.AuthorizationIDs, dbo.AuthorizationIDs) + assert.Equals(t, dbNew.Identifiers, dbo.Identifiers) + assert.Equals(t, dbNew.Error.Error(), o.Error.Error()) + return nil, false, errors.New("force") + }, + }, + err: errors.New("error saving acme order: force"), + } + }, + "ok": func(t *testing.T) test { + o := &acme.Order{ + ID: orderID, + Status: acme.StatusValid, + CertificateID: "certID", + Error: acme.NewError(acme.ErrorMalformedType, "force"), + } + return test{ + o: o, + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, orderTable) + assert.Equals(t, string(key), orderID) + + return b, nil + }, + MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { + assert.Equals(t, bucket, orderTable) + assert.Equals(t, old, b) + + dbNew := new(dbOrder) + assert.FatalError(t, json.Unmarshal(nu, dbNew)) + assert.Equals(t, dbNew.ID, dbo.ID) + assert.Equals(t, dbNew.AccountID, dbo.AccountID) + assert.Equals(t, dbNew.ProvisionerID, dbo.ProvisionerID) + assert.Equals(t, dbNew.CertificateID, o.CertificateID) + assert.Equals(t, dbNew.Status, o.Status) + assert.Equals(t, dbNew.CreatedAt, dbo.CreatedAt) + assert.Equals(t, dbNew.ExpiresAt, dbo.ExpiresAt) + assert.Equals(t, dbNew.NotBefore, dbo.NotBefore) + assert.Equals(t, dbNew.NotAfter, dbo.NotAfter) + assert.Equals(t, dbNew.AuthorizationIDs, dbo.AuthorizationIDs) + assert.Equals(t, dbNew.Identifiers, dbo.Identifiers) + assert.Equals(t, dbNew.Error.Error(), o.Error.Error()) + return nu, true, nil + }, + }, + } + }, + } + for name, run := range tests { + tc := run(t) + t.Run(name, func(t *testing.T) { + db := DB{db: tc.db} + if err := db.UpdateOrder(context.Background(), tc.o); err != nil { + if assert.NotNil(t, tc.err) { + assert.HasPrefix(t, err.Error(), tc.err.Error()) + } + } else { + if assert.Nil(t, tc.err) { + assert.Equals(t, tc.o.ID, dbo.ID) + assert.Equals(t, tc.o.CertificateID, "certID") + assert.Equals(t, tc.o.Status, acme.StatusValid) + assert.Equals(t, tc.o.Error.Error(), acme.NewError(acme.ErrorMalformedType, "force").Error()) + } + } + }) + } +} + +func TestDB_CreateOrder(t *testing.T) { + now := clock.Now() + type test struct { + db nosql.DB + o *acme.Order + err error + _id *string + } + var tests = map[string]func(t *testing.T) test{ + "fail/order-save-error": func(t *testing.T) test { + o := &acme.Order{ + AccountID: "accID", + ProvisionerID: "provID", + CertificateID: "certID", + Status: acme.StatusValid, + ExpiresAt: now, + NotBefore: now, + NotAfter: now, + Identifiers: []acme.Identifier{ + {Type: "dns", Value: "test.ca.smallstep.com"}, + {Type: "dns", Value: "example.foo.com"}, + }, + AuthorizationIDs: []string{"foo", "bar"}, + } + return test{ + db: &db.MockNoSQLDB{ + MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { + assert.Equals(t, string(bucket), string(orderTable)) + assert.Equals(t, string(key), o.ID) + assert.Equals(t, old, nil) + + dbo := new(dbOrder) + assert.FatalError(t, json.Unmarshal(nu, dbo)) + assert.Equals(t, dbo.ID, o.ID) + assert.Equals(t, dbo.AccountID, o.AccountID) + assert.Equals(t, dbo.ProvisionerID, o.ProvisionerID) + assert.Equals(t, dbo.CertificateID, "") + assert.Equals(t, dbo.Status, o.Status) + assert.True(t, dbo.CreatedAt.Add(-time.Minute).Before(now)) + assert.True(t, dbo.CreatedAt.Add(time.Minute).After(now)) + assert.Equals(t, dbo.ExpiresAt, o.ExpiresAt) + assert.Equals(t, dbo.NotBefore, o.NotBefore) + assert.Equals(t, dbo.NotAfter, o.NotAfter) + assert.Equals(t, dbo.AuthorizationIDs, o.AuthorizationIDs) + assert.Equals(t, dbo.Identifiers, o.Identifiers) + assert.Equals(t, dbo.Error, nil) + return nil, false, errors.New("force") + }, + }, + o: o, + err: errors.New("error saving acme order: force"), + } + }, + "fail/orderIDsByOrderUpdate-error": func(t *testing.T) test { + o := &acme.Order{ + AccountID: "accID", + ProvisionerID: "provID", + CertificateID: "certID", + Status: acme.StatusValid, + ExpiresAt: now, + NotBefore: now, + NotAfter: now, + Identifiers: []acme.Identifier{ + {Type: "dns", Value: "test.ca.smallstep.com"}, + {Type: "dns", Value: "example.foo.com"}, + }, + AuthorizationIDs: []string{"foo", "bar"}, + } + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, string(bucket), string(ordersByAccountIDTable)) + assert.Equals(t, string(key), o.AccountID) + return nil, errors.New("force") + }, + MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { + assert.Equals(t, string(bucket), string(orderTable)) + assert.Equals(t, string(key), o.ID) + assert.Equals(t, old, nil) + + dbo := new(dbOrder) + assert.FatalError(t, json.Unmarshal(nu, dbo)) + assert.Equals(t, dbo.ID, o.ID) + assert.Equals(t, dbo.AccountID, o.AccountID) + assert.Equals(t, dbo.ProvisionerID, o.ProvisionerID) + assert.Equals(t, dbo.CertificateID, "") + assert.Equals(t, dbo.Status, o.Status) + assert.True(t, dbo.CreatedAt.Add(-time.Minute).Before(now)) + assert.True(t, dbo.CreatedAt.Add(time.Minute).After(now)) + assert.Equals(t, dbo.ExpiresAt, o.ExpiresAt) + assert.Equals(t, dbo.NotBefore, o.NotBefore) + assert.Equals(t, dbo.NotAfter, o.NotAfter) + assert.Equals(t, dbo.AuthorizationIDs, o.AuthorizationIDs) + assert.Equals(t, dbo.Identifiers, o.Identifiers) + assert.Equals(t, dbo.Error, nil) + return nu, true, nil + }, + }, + o: o, + err: errors.New("error loading orderIDs for account accID: force"), + } + }, + "ok": func(t *testing.T) test { + var ( + id string + idptr = &id + ) + + o := &acme.Order{ + AccountID: "accID", + ProvisionerID: "provID", + Status: acme.StatusValid, + ExpiresAt: now, + NotBefore: now, + NotAfter: now, + Identifiers: []acme.Identifier{ + {Type: "dns", Value: "test.ca.smallstep.com"}, + {Type: "dns", Value: "example.foo.com"}, + }, + AuthorizationIDs: []string{"foo", "bar"}, + } + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, string(bucket), string(ordersByAccountIDTable)) + assert.Equals(t, string(key), o.AccountID) + return nil, nosqldb.ErrNotFound + }, + MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { + *idptr = string(key) + assert.Equals(t, string(bucket), string(orderTable)) + assert.Equals(t, string(key), o.ID) + assert.Equals(t, old, nil) + + dbo := new(dbOrder) + assert.FatalError(t, json.Unmarshal(nu, dbo)) + assert.Equals(t, dbo.ID, o.ID) + assert.Equals(t, dbo.AccountID, o.AccountID) + assert.Equals(t, dbo.ProvisionerID, o.ProvisionerID) + assert.Equals(t, dbo.CertificateID, "") + assert.Equals(t, dbo.Status, o.Status) + assert.True(t, dbo.CreatedAt.Add(-time.Minute).Before(now)) + assert.True(t, dbo.CreatedAt.Add(time.Minute).After(now)) + assert.Equals(t, dbo.ExpiresAt, o.ExpiresAt) + assert.Equals(t, dbo.NotBefore, o.NotBefore) + assert.Equals(t, dbo.NotAfter, o.NotAfter) + assert.Equals(t, dbo.AuthorizationIDs, o.AuthorizationIDs) + assert.Equals(t, dbo.Identifiers, o.Identifiers) + assert.Equals(t, dbo.Error, nil) + return nu, true, nil + }, + }, + o: o, + _id: idptr, + } + }, + } + for name, run := range tests { + tc := run(t) + t.Run(name, func(t *testing.T) { + db := DB{db: tc.db} + if err := db.CreateOrder(context.Background(), tc.o); err != nil { + if assert.NotNil(t, tc.err) { + assert.HasPrefix(t, err.Error(), tc.err.Error()) + } + } else { + if assert.Nil(t, tc.err) { + assert.Equals(t, tc.o.ID, *tc._id) + } + } + }) + } +}