smallstep-certificates/acme/db/nosql/account_test.go
2021-10-17 22:42:36 +02:00

1806 lines
51 KiB
Go

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"
"go.step.sm/crypto/jose"
)
func TestDB_getDBAccount(t *testing.T) {
accID := "accID"
type test struct {
db nosql.DB
err error
acmeErr *acme.Error
dbacc *dbAccount
}
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, accountTable)
assert.Equals(t, string(key), accID)
return nil, nosqldb.ErrNotFound
},
},
err: acme.ErrNotFound,
}
},
"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, accountTable)
assert.Equals(t, string(key), accID)
return nil, errors.New("force")
},
},
err: errors.New("error loading account accID: 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, accountTable)
assert.Equals(t, string(key), accID)
return []byte("foo"), nil
},
},
err: errors.New("error unmarshaling account accID into dbAccount"),
}
},
"ok": func(t *testing.T) test {
now := clock.Now()
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
assert.FatalError(t, err)
dbacc := &dbAccount{
ID: accID,
Status: acme.StatusDeactivated,
CreatedAt: now,
DeactivatedAt: now,
Contact: []string{"foo", "bar"},
Key: jwk,
}
b, err := json.Marshal(dbacc)
assert.FatalError(t, err)
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, accountTable)
assert.Equals(t, string(key), accID)
return b, nil
},
},
dbacc: dbacc,
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
d := DB{db: tc.db}
if dbacc, err := d.getDBAccount(context.Background(), accID); 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, dbacc.ID, tc.dbacc.ID)
assert.Equals(t, dbacc.Status, tc.dbacc.Status)
assert.Equals(t, dbacc.CreatedAt, tc.dbacc.CreatedAt)
assert.Equals(t, dbacc.DeactivatedAt, tc.dbacc.DeactivatedAt)
assert.Equals(t, dbacc.Contact, tc.dbacc.Contact)
assert.Equals(t, dbacc.Key.KeyID, tc.dbacc.Key.KeyID)
}
})
}
}
func TestDB_getAccountIDByKeyID(t *testing.T) {
accID := "accID"
kid := "kid"
type test struct {
db nosql.DB
err error
acmeErr *acme.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, accountByKeyIDTable)
assert.Equals(t, string(key), kid)
return nil, nosqldb.ErrNotFound
},
},
err: acme.ErrNotFound,
}
},
"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, accountByKeyIDTable)
assert.Equals(t, string(key), kid)
return nil, errors.New("force")
},
},
err: errors.New("error loading key-account index for key kid: force"),
}
},
"ok": func(t *testing.T) test {
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, accountByKeyIDTable)
assert.Equals(t, string(key), kid)
return []byte(accID), nil
},
},
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
d := DB{db: tc.db}
if retAccID, err := d.getAccountIDByKeyID(context.Background(), kid); 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, retAccID, accID)
}
})
}
}
func TestDB_GetAccount(t *testing.T) {
accID := "accID"
type test struct {
db nosql.DB
err error
acmeErr *acme.Error
dbacc *dbAccount
}
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, accountTable)
assert.Equals(t, string(key), accID)
return nil, errors.New("force")
},
},
err: errors.New("error loading account accID: force"),
}
},
"ok": func(t *testing.T) test {
now := clock.Now()
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
assert.FatalError(t, err)
dbacc := &dbAccount{
ID: accID,
Status: acme.StatusDeactivated,
CreatedAt: now,
DeactivatedAt: now,
Contact: []string{"foo", "bar"},
Key: jwk,
}
b, err := json.Marshal(dbacc)
assert.FatalError(t, err)
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, accountTable)
assert.Equals(t, string(key), accID)
return b, nil
},
},
dbacc: dbacc,
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
d := DB{db: tc.db}
if acc, err := d.GetAccount(context.Background(), accID); 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, acc.ID, tc.dbacc.ID)
assert.Equals(t, acc.Status, tc.dbacc.Status)
assert.Equals(t, acc.Contact, tc.dbacc.Contact)
assert.Equals(t, acc.Key.KeyID, tc.dbacc.Key.KeyID)
}
})
}
}
func TestDB_GetAccountByKeyID(t *testing.T) {
accID := "accID"
kid := "kid"
type test struct {
db nosql.DB
err error
acmeErr *acme.Error
dbacc *dbAccount
}
var tests = map[string]func(t *testing.T) test{
"fail/db.getAccountIDByKeyID-error": func(t *testing.T) test {
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, string(bucket), string(accountByKeyIDTable))
assert.Equals(t, string(key), kid)
return nil, errors.New("force")
},
},
err: errors.New("error loading key-account index for key kid: force"),
}
},
"fail/db.GetAccount-error": func(t *testing.T) test {
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
switch string(bucket) {
case string(accountByKeyIDTable):
assert.Equals(t, string(key), kid)
return []byte(accID), nil
case string(accountTable):
assert.Equals(t, string(key), accID)
return nil, errors.New("force")
default:
assert.FatalError(t, errors.Errorf("unrecognized bucket %s", string(bucket)))
return nil, errors.New("force")
}
},
},
err: errors.New("error loading account accID: force"),
}
},
"ok": func(t *testing.T) test {
now := clock.Now()
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
assert.FatalError(t, err)
dbacc := &dbAccount{
ID: accID,
Status: acme.StatusDeactivated,
CreatedAt: now,
DeactivatedAt: now,
Contact: []string{"foo", "bar"},
Key: jwk,
}
b, err := json.Marshal(dbacc)
assert.FatalError(t, err)
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
switch string(bucket) {
case string(accountByKeyIDTable):
assert.Equals(t, string(key), kid)
return []byte(accID), nil
case string(accountTable):
assert.Equals(t, string(key), accID)
return b, nil
default:
assert.FatalError(t, errors.Errorf("unrecognized bucket %s", string(bucket)))
return nil, errors.New("force")
}
},
},
dbacc: dbacc,
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
d := DB{db: tc.db}
if acc, err := d.GetAccountByKeyID(context.Background(), kid); 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, acc.ID, tc.dbacc.ID)
assert.Equals(t, acc.Status, tc.dbacc.Status)
assert.Equals(t, acc.Contact, tc.dbacc.Contact)
assert.Equals(t, acc.Key.KeyID, tc.dbacc.Key.KeyID)
}
})
}
}
func TestDB_CreateAccount(t *testing.T) {
type test struct {
db nosql.DB
acc *acme.Account
err error
_id *string
}
var tests = map[string]func(t *testing.T) test{
"fail/keyID-cmpAndSwap-error": func(t *testing.T) test {
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
assert.FatalError(t, err)
acc := &acme.Account{
Status: acme.StatusValid,
Contact: []string{"foo", "bar"},
Key: jwk,
}
return test{
db: &db.MockNoSQLDB{
MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) {
assert.Equals(t, bucket, accountByKeyIDTable)
assert.Equals(t, string(key), jwk.KeyID)
assert.Equals(t, old, nil)
assert.Equals(t, nu, []byte(acc.ID))
return nil, false, errors.New("force")
},
},
acc: acc,
err: errors.New("error storing keyID to accountID index: force"),
}
},
"fail/keyID-cmpAndSwap-false": func(t *testing.T) test {
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
assert.FatalError(t, err)
acc := &acme.Account{
Status: acme.StatusValid,
Contact: []string{"foo", "bar"},
Key: jwk,
}
return test{
db: &db.MockNoSQLDB{
MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) {
assert.Equals(t, bucket, accountByKeyIDTable)
assert.Equals(t, string(key), jwk.KeyID)
assert.Equals(t, old, nil)
assert.Equals(t, nu, []byte(acc.ID))
return nil, false, nil
},
},
acc: acc,
err: errors.New("key-id to account-id index already exists"),
}
},
"fail/account-save-error": func(t *testing.T) test {
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
assert.FatalError(t, err)
acc := &acme.Account{
Status: acme.StatusValid,
Contact: []string{"foo", "bar"},
Key: jwk,
}
return test{
db: &db.MockNoSQLDB{
MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) {
switch string(bucket) {
case string(accountByKeyIDTable):
assert.Equals(t, string(key), jwk.KeyID)
assert.Equals(t, old, nil)
return nu, true, nil
case string(accountTable):
assert.Equals(t, string(key), acc.ID)
assert.Equals(t, old, nil)
dbacc := new(dbAccount)
assert.FatalError(t, json.Unmarshal(nu, dbacc))
assert.Equals(t, dbacc.ID, string(key))
assert.Equals(t, dbacc.Contact, acc.Contact)
assert.Equals(t, dbacc.Key.KeyID, acc.Key.KeyID)
assert.True(t, clock.Now().Add(-time.Minute).Before(dbacc.CreatedAt))
assert.True(t, clock.Now().Add(time.Minute).After(dbacc.CreatedAt))
assert.True(t, dbacc.DeactivatedAt.IsZero())
return nil, false, errors.New("force")
default:
assert.FatalError(t, errors.Errorf("unrecognized bucket %s", string(bucket)))
return nil, false, errors.New("force")
}
},
},
acc: acc,
err: errors.New("error saving acme account: force"),
}
},
"ok": func(t *testing.T) test {
var (
id string
idPtr = &id
)
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
assert.FatalError(t, err)
acc := &acme.Account{
Status: acme.StatusValid,
Contact: []string{"foo", "bar"},
Key: jwk,
}
return test{
db: &db.MockNoSQLDB{
MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) {
id = string(key)
switch string(bucket) {
case string(accountByKeyIDTable):
assert.Equals(t, string(key), jwk.KeyID)
assert.Equals(t, old, nil)
return nu, true, nil
case string(accountTable):
assert.Equals(t, string(key), acc.ID)
assert.Equals(t, old, nil)
dbacc := new(dbAccount)
assert.FatalError(t, json.Unmarshal(nu, dbacc))
assert.Equals(t, dbacc.ID, string(key))
assert.Equals(t, dbacc.Contact, acc.Contact)
assert.Equals(t, dbacc.Key.KeyID, acc.Key.KeyID)
assert.True(t, clock.Now().Add(-time.Minute).Before(dbacc.CreatedAt))
assert.True(t, clock.Now().Add(time.Minute).After(dbacc.CreatedAt))
assert.True(t, dbacc.DeactivatedAt.IsZero())
return nu, true, nil
default:
assert.FatalError(t, errors.Errorf("unrecognized bucket %s", string(bucket)))
return nil, false, errors.New("force")
}
},
},
acc: acc,
_id: idPtr,
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
d := DB{db: tc.db}
if err := d.CreateAccount(context.Background(), tc.acc); 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.acc.ID, *tc._id)
}
}
})
}
}
func TestDB_UpdateAccount(t *testing.T) {
accID := "accID"
now := clock.Now()
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
assert.FatalError(t, err)
dbacc := &dbAccount{
ID: accID,
Status: acme.StatusDeactivated,
CreatedAt: now,
DeactivatedAt: now,
Contact: []string{"foo", "bar"},
Key: jwk,
}
b, err := json.Marshal(dbacc)
assert.FatalError(t, err)
type test struct {
db nosql.DB
acc *acme.Account
err error
}
var tests = map[string]func(t *testing.T) test{
"fail/db.Get-error": func(t *testing.T) test {
return test{
acc: &acme.Account{
ID: accID,
},
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, accountTable)
assert.Equals(t, string(key), accID)
return nil, errors.New("force")
},
},
err: errors.New("error loading account accID: force"),
}
},
"fail/already-deactivated": func(t *testing.T) test {
clone := dbacc.clone()
clone.Status = acme.StatusDeactivated
clone.DeactivatedAt = now
dbaccb, err := json.Marshal(clone)
assert.FatalError(t, err)
acc := &acme.Account{
ID: accID,
Status: acme.StatusDeactivated,
Contact: []string{"foo", "bar"},
}
return test{
acc: acc,
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, accountTable)
assert.Equals(t, string(key), accID)
return dbaccb, nil
},
MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) {
assert.Equals(t, bucket, accountTable)
assert.Equals(t, old, b)
dbNew := new(dbAccount)
assert.FatalError(t, json.Unmarshal(nu, dbNew))
assert.Equals(t, dbNew.ID, clone.ID)
assert.Equals(t, dbNew.Status, clone.Status)
assert.Equals(t, dbNew.Contact, clone.Contact)
assert.Equals(t, dbNew.Key.KeyID, clone.Key.KeyID)
assert.Equals(t, dbNew.CreatedAt, clone.CreatedAt)
assert.Equals(t, dbNew.DeactivatedAt, clone.DeactivatedAt)
return nil, false, errors.New("force")
},
},
err: errors.New("error saving acme account: force"),
}
},
"fail/db.CmpAndSwap-error": func(t *testing.T) test {
acc := &acme.Account{
ID: accID,
Status: acme.StatusDeactivated,
Contact: []string{"foo", "bar"},
}
return test{
acc: acc,
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, accountTable)
assert.Equals(t, string(key), accID)
return b, nil
},
MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) {
assert.Equals(t, bucket, accountTable)
assert.Equals(t, old, b)
dbNew := new(dbAccount)
assert.FatalError(t, json.Unmarshal(nu, dbNew))
assert.Equals(t, dbNew.ID, dbacc.ID)
assert.Equals(t, dbNew.Status, acc.Status)
assert.Equals(t, dbNew.Contact, dbacc.Contact)
assert.Equals(t, dbNew.Key.KeyID, dbacc.Key.KeyID)
assert.Equals(t, dbNew.CreatedAt, dbacc.CreatedAt)
assert.True(t, dbNew.DeactivatedAt.Add(-time.Minute).Before(now))
assert.True(t, dbNew.DeactivatedAt.Add(time.Minute).After(now))
return nil, false, errors.New("force")
},
},
err: errors.New("error saving acme account: force"),
}
},
"ok": func(t *testing.T) test {
acc := &acme.Account{
ID: accID,
Status: acme.StatusDeactivated,
Contact: []string{"foo", "bar"},
Key: jwk,
}
return test{
acc: acc,
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, accountTable)
assert.Equals(t, string(key), accID)
return b, nil
},
MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) {
assert.Equals(t, bucket, accountTable)
assert.Equals(t, old, b)
dbNew := new(dbAccount)
assert.FatalError(t, json.Unmarshal(nu, dbNew))
assert.Equals(t, dbNew.ID, dbacc.ID)
assert.Equals(t, dbNew.Status, acc.Status)
assert.Equals(t, dbNew.Contact, dbacc.Contact)
assert.Equals(t, dbNew.Key.KeyID, dbacc.Key.KeyID)
assert.Equals(t, dbNew.CreatedAt, dbacc.CreatedAt)
assert.True(t, dbNew.DeactivatedAt.Add(-time.Minute).Before(now))
assert.True(t, dbNew.DeactivatedAt.Add(time.Minute).After(now))
return nu, true, nil
},
},
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
d := DB{db: tc.db}
if err := d.UpdateAccount(context.Background(), tc.acc); 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.acc.ID, dbacc.ID)
assert.Equals(t, tc.acc.Status, dbacc.Status)
assert.Equals(t, tc.acc.Contact, dbacc.Contact)
assert.Equals(t, tc.acc.Key.KeyID, dbacc.Key.KeyID)
}
}
})
}
}
func TestDB_getDBExternalAccountKey(t *testing.T) {
keyID := "keyID"
type test struct {
db nosql.DB
err error
acmeErr *acme.Error
dbeak *dbExternalAccountKey
}
var tests = map[string]func(t *testing.T) test{
"ok": func(t *testing.T) test {
now := clock.Now()
dbeak := &dbExternalAccountKey{
ID: keyID,
Provisioner: "prov",
Reference: "ref",
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
b, err := json.Marshal(dbeak)
assert.FatalError(t, err)
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, externalAccountKeyTable)
assert.Equals(t, string(key), keyID)
return b, nil
},
},
err: nil,
dbeak: dbeak,
}
},
"fail/not-found": func(t *testing.T) test {
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, externalAccountKeyTable)
assert.Equals(t, string(key), keyID)
return nil, nosqldb.ErrNotFound
},
},
err: acme.ErrNotFound,
}
},
"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, externalAccountKeyTable)
assert.Equals(t, string(key), keyID)
return nil, errors.New("force")
},
},
err: errors.New("error loading external account key keyID: 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, externalAccountKeyTable)
assert.Equals(t, string(key), keyID)
return []byte("foo"), nil
},
},
err: errors.New("error unmarshaling external account key keyID into dbExternalAccountKey"),
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
d := DB{db: tc.db}
if dbeak, err := d.getDBExternalAccountKey(context.Background(), keyID); 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, dbeak.ID, tc.dbeak.ID)
assert.Equals(t, dbeak.KeyBytes, tc.dbeak.KeyBytes)
assert.Equals(t, dbeak.Provisioner, tc.dbeak.Provisioner)
assert.Equals(t, dbeak.Reference, tc.dbeak.Reference)
assert.Equals(t, dbeak.CreatedAt, tc.dbeak.CreatedAt)
assert.Equals(t, dbeak.AccountID, tc.dbeak.AccountID)
assert.Equals(t, dbeak.BoundAt, tc.dbeak.BoundAt)
}
})
}
}
func TestDB_GetExternalAccountKey(t *testing.T) {
keyID := "keyID"
prov := "acmeProv"
type test struct {
db nosql.DB
err error
acmeErr *acme.Error
eak *acme.ExternalAccountKey
}
var tests = map[string]func(t *testing.T) test{
"ok": func(t *testing.T) test {
now := clock.Now()
dbeak := &dbExternalAccountKey{
ID: keyID,
Provisioner: prov,
Reference: "ref",
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
b, err := json.Marshal(dbeak)
assert.FatalError(t, err)
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, externalAccountKeyTable)
assert.Equals(t, string(key), keyID)
return b, nil
},
},
eak: &acme.ExternalAccountKey{
ID: keyID,
Provisioner: prov,
Reference: "ref",
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
},
}
},
"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, externalAccountKeyTable)
assert.Equals(t, string(key), keyID)
return nil, errors.New("force")
},
},
err: errors.New("error loading external account key keyID: force"),
}
},
"fail/non-matching-provisioner": func(t *testing.T) test {
now := clock.Now()
dbeak := &dbExternalAccountKey{
ID: keyID,
Provisioner: "aDifferentProv",
Reference: "ref",
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
b, err := json.Marshal(dbeak)
assert.FatalError(t, err)
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, externalAccountKeyTable)
assert.Equals(t, string(key), keyID)
return b, nil
},
},
eak: &acme.ExternalAccountKey{
ID: keyID,
Provisioner: prov,
Reference: "ref",
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
},
acmeErr: acme.NewError(acme.ErrorUnauthorizedType, "name of provisioner does not match provisioner for which the EAB key was created"),
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
d := DB{db: tc.db}
if eak, err := d.GetExternalAccountKey(context.Background(), prov, keyID); 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, eak.ID, tc.eak.ID)
assert.Equals(t, eak.KeyBytes, tc.eak.KeyBytes)
assert.Equals(t, eak.Provisioner, tc.eak.Provisioner)
assert.Equals(t, eak.Reference, tc.eak.Reference)
assert.Equals(t, eak.CreatedAt, tc.eak.CreatedAt)
assert.Equals(t, eak.AccountID, tc.eak.AccountID)
assert.Equals(t, eak.BoundAt, tc.eak.BoundAt)
}
})
}
}
func TestDB_GetExternalAccountKeyByReference(t *testing.T) {
keyID := "keyID"
prov := "acmeProv"
ref := "ref"
type test struct {
db nosql.DB
err error
ref string
acmeErr *acme.Error
eak *acme.ExternalAccountKey
}
var tests = map[string]func(t *testing.T) test{
"ok": func(t *testing.T) test {
now := clock.Now()
dbeak := &dbExternalAccountKey{
ID: keyID,
Provisioner: prov,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
dbref := &dbExternalAccountKeyReference{
Reference: ref,
ExternalAccountKeyID: keyID,
}
b, err := json.Marshal(dbeak)
assert.FatalError(t, err)
dbrefBytes, err := json.Marshal(dbref)
assert.FatalError(t, err)
return test{
ref: ref,
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
switch string(bucket) {
case string(externalAccountKeysByReferenceTable):
assert.Equals(t, string(key), ref)
return dbrefBytes, nil
case string(externalAccountKeyTable):
assert.Equals(t, string(key), keyID)
return b, nil
default:
assert.FatalError(t, errors.Errorf("unrecognized bucket %s", string(bucket)))
return nil, errors.New("force")
}
},
},
eak: &acme.ExternalAccountKey{
ID: keyID,
Provisioner: prov,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
},
err: nil,
}
},
"ok/no-reference": func(t *testing.T) test {
return test{
ref: "",
eak: nil,
err: nil,
}
},
"fail/reference-not-found": func(t *testing.T) test {
return test{
ref: ref,
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, string(bucket), string(externalAccountKeysByReferenceTable))
assert.Equals(t, string(key), ref)
return nil, nosqldb.ErrNotFound
},
},
err: errors.New("not found"),
}
},
"fail/reference-load-error": func(t *testing.T) test {
return test{
ref: ref,
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, string(bucket), string(externalAccountKeysByReferenceTable))
assert.Equals(t, string(key), ref)
return nil, errors.New("force")
},
},
err: errors.New("error loading ACME EAB key for reference ref: force"),
}
},
"fail/reference-unmarshal-error": func(t *testing.T) test {
return test{
ref: ref,
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, string(bucket), string(externalAccountKeysByReferenceTable))
assert.Equals(t, string(key), ref)
return []byte{0}, nil
},
},
err: errors.New("error unmarshaling ACME EAB key for reference ref"),
}
},
"fail/db.GetExternalAccountKey-error": func(t *testing.T) test {
dbref := &dbExternalAccountKeyReference{
Reference: ref,
ExternalAccountKeyID: keyID,
}
dbrefBytes, err := json.Marshal(dbref)
assert.FatalError(t, err)
return test{
ref: ref,
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
switch string(bucket) {
case string(externalAccountKeysByReferenceTable):
assert.Equals(t, string(key), ref)
return dbrefBytes, nil
case string(externalAccountKeyTable):
assert.Equals(t, string(key), keyID)
return nil, errors.New("force")
default:
assert.FatalError(t, errors.Errorf("unrecognized bucket %s", string(bucket)))
return nil, errors.New("force")
}
},
},
err: errors.New("error loading external account key keyID: force"),
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
d := DB{db: tc.db}
if eak, err := d.GetExternalAccountKeyByReference(context.Background(), prov, tc.ref); 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) && tc.eak != nil {
assert.Equals(t, eak.ID, tc.eak.ID)
assert.Equals(t, eak.AccountID, tc.eak.AccountID)
assert.Equals(t, eak.BoundAt, tc.eak.BoundAt)
assert.Equals(t, eak.CreatedAt, tc.eak.CreatedAt)
assert.Equals(t, eak.KeyBytes, tc.eak.KeyBytes)
assert.Equals(t, eak.Provisioner, tc.eak.Provisioner)
assert.Equals(t, eak.Reference, tc.eak.Reference)
}
})
}
}
func TestDB_GetExternalAccountKeys(t *testing.T) {
keyID1 := "keyID1"
keyID2 := "keyID2"
keyID3 := "keyID3"
keyID4 := "keyID4"
prov := "acmeProv"
ref := "ref"
type test struct {
db nosql.DB
err error
cursor string
nextCursor string
limit int
acmeErr *acme.Error
eaks []*acme.ExternalAccountKey
}
var tests = map[string]func(t *testing.T) test{
"ok": func(t *testing.T) test {
now := clock.Now()
dbeak1 := &dbExternalAccountKey{
ID: keyID1,
Provisioner: prov,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
b1, err := json.Marshal(dbeak1)
assert.FatalError(t, err)
dbeak2 := &dbExternalAccountKey{
ID: keyID2,
Provisioner: prov,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
b2, err := json.Marshal(dbeak2)
assert.FatalError(t, err)
dbeak3 := &dbExternalAccountKey{
ID: keyID3,
Provisioner: "differentProvisioner",
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
b3, err := json.Marshal(dbeak3)
assert.FatalError(t, err)
return test{
db: &db.MockNoSQLDB{
MList: func(bucket []byte) ([]*nosqldb.Entry, error) {
assert.Equals(t, bucket, externalAccountKeyTable)
return []*nosqldb.Entry{
{
Bucket: bucket,
Key: []byte(keyID1),
Value: b1,
},
{
Bucket: bucket,
Key: []byte(keyID2),
Value: b2,
},
{
Bucket: bucket,
Key: []byte(keyID3),
Value: b3,
},
}, nil
},
},
eaks: []*acme.ExternalAccountKey{
{
ID: keyID1,
Provisioner: prov,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
},
{
ID: keyID2,
Provisioner: prov,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
},
},
}
},
"ok/paging-single-entry": func(t *testing.T) test {
now := clock.Now()
dbeak1 := &dbExternalAccountKey{
ID: keyID1,
Provisioner: prov,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
b1, err := json.Marshal(dbeak1)
assert.FatalError(t, err)
dbeak2 := &dbExternalAccountKey{
ID: keyID2,
Provisioner: prov,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
b2, err := json.Marshal(dbeak2)
assert.FatalError(t, err)
dbeak3 := &dbExternalAccountKey{
ID: keyID3,
Provisioner: "differentProvisioner",
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
b3, err := json.Marshal(dbeak3)
assert.FatalError(t, err)
dbeak4 := &dbExternalAccountKey{
ID: keyID4,
Provisioner: prov,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
b4, err := json.Marshal(dbeak4)
assert.FatalError(t, err)
return test{
db: &db.MockNoSQLDB{
MList: func(bucket []byte) ([]*nosqldb.Entry, error) {
assert.Equals(t, bucket, externalAccountKeyTable)
return []*nosqldb.Entry{
{
Bucket: bucket,
Key: []byte(keyID1),
Value: b1,
},
{
Bucket: bucket,
Key: []byte(keyID2),
Value: b2,
},
{
Bucket: bucket,
Key: []byte(keyID3),
Value: b3,
},
{
Bucket: bucket,
Key: []byte(keyID4),
Value: b4,
},
}, nil
},
},
cursor: keyID2,
limit: 1,
nextCursor: keyID4,
eaks: []*acme.ExternalAccountKey{
{
ID: keyID2,
Provisioner: prov,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
},
},
}
},
"ok/paging-max-limit": func(t *testing.T) test {
return test{
db: &db.MockNoSQLDB{
MList: func(bucket []byte) ([]*nosqldb.Entry, error) {
assert.Equals(t, bucket, externalAccountKeyTable)
return []*nosqldb.Entry{}, nil
},
},
limit: 1337,
eaks: []*acme.ExternalAccountKey{},
}
},
"fail/db.List-error": func(t *testing.T) test {
return test{
db: &db.MockNoSQLDB{
MList: func(bucket []byte) ([]*nosqldb.Entry, error) {
assert.Equals(t, string(bucket), string(externalAccountKeyTable))
return nil, errors.New("force")
},
},
err: errors.New("force"),
}
},
"fail/unmarshal-error": func(t *testing.T) test {
return test{
db: &db.MockNoSQLDB{
MList: func(bucket []byte) ([]*nosqldb.Entry, error) {
assert.Equals(t, bucket, externalAccountKeyTable)
return []*nosqldb.Entry{
{
Bucket: bucket,
Key: []byte(keyID1),
Value: []byte("foo"),
},
}, nil
},
},
eaks: []*acme.ExternalAccountKey{},
err: errors.Errorf("error unmarshaling external account key %s into ExternalAccountKey", keyID1),
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
d := DB{db: tc.db}
if eaks, nextCursor, err := d.GetExternalAccountKeys(context.Background(), prov, tc.cursor, tc.limit); 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, len(eaks), len(tc.eaks))
for i, eak := range eaks {
assert.Equals(t, eak.ID, tc.eaks[i].ID)
assert.Equals(t, eak.KeyBytes, tc.eaks[i].KeyBytes)
assert.Equals(t, eak.Provisioner, tc.eaks[i].Provisioner)
assert.Equals(t, eak.Reference, tc.eaks[i].Reference)
assert.Equals(t, eak.CreatedAt, tc.eaks[i].CreatedAt)
assert.Equals(t, eak.AccountID, tc.eaks[i].AccountID)
assert.Equals(t, eak.BoundAt, tc.eaks[i].BoundAt)
}
assert.Equals(t, nextCursor, tc.nextCursor)
}
})
}
}
func TestDB_DeleteExternalAccountKey(t *testing.T) {
keyID := "keyID"
prov := "acmeProv"
ref := "ref"
type test struct {
db nosql.DB
err error
acmeErr *acme.Error
}
var tests = map[string]func(t *testing.T) test{
"ok": func(t *testing.T) test {
now := clock.Now()
dbeak := &dbExternalAccountKey{
ID: keyID,
Provisioner: prov,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
dbref := &dbExternalAccountKeyReference{
Reference: ref,
ExternalAccountKeyID: keyID,
}
b, err := json.Marshal(dbeak)
assert.FatalError(t, err)
dbrefBytes, err := json.Marshal(dbref)
assert.FatalError(t, err)
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
switch string(bucket) {
case string(externalAccountKeysByReferenceTable):
assert.Equals(t, string(key), ref)
return dbrefBytes, nil
case string(externalAccountKeyTable):
assert.Equals(t, string(key), keyID)
return b, nil
default:
assert.FatalError(t, errors.Errorf("unrecognized bucket %s", string(bucket)))
return nil, errors.New("force")
}
},
MDel: func(bucket, key []byte) error {
switch string(bucket) {
case string(externalAccountKeysByReferenceTable):
assert.Equals(t, string(key), ref)
return nil
case string(externalAccountKeyTable):
assert.Equals(t, string(key), keyID)
return nil
default:
assert.FatalError(t, errors.Errorf("unrecognized bucket %s", string(bucket)))
return errors.New("force")
}
},
},
}
},
"fail/not-found": func(t *testing.T) test {
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, string(bucket), string(externalAccountKeyTable))
assert.Equals(t, string(key), keyID)
return nil, nosqldb.ErrNotFound
},
},
err: errors.New("error loading ACME EAB Key with Key ID keyID"),
}
},
"fail/non-matching-provisioner": func(t *testing.T) test {
now := clock.Now()
dbeak := &dbExternalAccountKey{
ID: keyID,
Provisioner: "differentProvisioner",
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
b, err := json.Marshal(dbeak)
assert.FatalError(t, err)
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, string(bucket), string(externalAccountKeyTable))
assert.Equals(t, string(key), keyID)
return b, nil
},
},
err: errors.New("name of provisioner does not match provisioner for which the EAB key was created"),
}
},
"fail/delete-reference": func(t *testing.T) test {
now := clock.Now()
dbeak := &dbExternalAccountKey{
ID: keyID,
Provisioner: prov,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
dbref := &dbExternalAccountKeyReference{
Reference: ref,
ExternalAccountKeyID: keyID,
}
b, err := json.Marshal(dbeak)
assert.FatalError(t, err)
dbrefBytes, err := json.Marshal(dbref)
assert.FatalError(t, err)
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
switch string(bucket) {
case string(externalAccountKeysByReferenceTable):
assert.Equals(t, string(key), ref)
return dbrefBytes, nil
case string(externalAccountKeyTable):
assert.Equals(t, string(key), keyID)
return b, nil
default:
assert.FatalError(t, errors.Errorf("unrecognized bucket %s", string(bucket)))
return nil, errors.New("force")
}
},
MDel: func(bucket, key []byte) error {
switch string(bucket) {
case string(externalAccountKeysByReferenceTable):
assert.Equals(t, string(key), ref)
return errors.New("force")
case string(externalAccountKeyTable):
assert.Equals(t, string(key), keyID)
return nil
default:
assert.FatalError(t, errors.Errorf("unrecognized bucket %s", string(bucket)))
return errors.New("force")
}
},
},
err: errors.New("error deleting ACME EAB Key Reference with Key ID keyID and reference ref"),
}
},
"fail/delete-eak": func(t *testing.T) test {
now := clock.Now()
dbeak := &dbExternalAccountKey{
ID: keyID,
Provisioner: prov,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
dbref := &dbExternalAccountKeyReference{
Reference: ref,
ExternalAccountKeyID: keyID,
}
b, err := json.Marshal(dbeak)
assert.FatalError(t, err)
dbrefBytes, err := json.Marshal(dbref)
assert.FatalError(t, err)
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
switch string(bucket) {
case string(externalAccountKeysByReferenceTable):
assert.Equals(t, string(key), ref)
return dbrefBytes, nil
case string(externalAccountKeyTable):
assert.Equals(t, string(key), keyID)
return b, nil
default:
assert.FatalError(t, errors.Errorf("unrecognized bucket %s", string(bucket)))
return nil, errors.New("force")
}
},
MDel: func(bucket, key []byte) error {
switch string(bucket) {
case string(externalAccountKeysByReferenceTable):
assert.Equals(t, string(key), ref)
return nil
case string(externalAccountKeyTable):
assert.Equals(t, string(key), keyID)
return errors.New("force")
default:
assert.FatalError(t, errors.Errorf("unrecognized bucket %s", string(bucket)))
return errors.New("force")
}
},
},
err: errors.New("error deleting ACME EAB Key with Key ID keyID"),
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
d := DB{db: tc.db}
if err := d.DeleteExternalAccountKey(context.Background(), prov, keyID); 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 {
assert.Nil(t, tc.err)
}
})
}
}
func TestDB_CreateExternalAccountKey(t *testing.T) {
keyID := "keyID"
prov := "acmeProv"
ref := "ref"
type test struct {
db nosql.DB
err error
_id *string
eak *acme.ExternalAccountKey
}
var tests = map[string]func(t *testing.T) test{
"ok": func(t *testing.T) test {
var (
id string
idPtr = &id
)
now := clock.Now()
eak := &acme.ExternalAccountKey{
ID: keyID,
Provisioner: prov,
Reference: "ref",
AccountID: "",
CreatedAt: now,
}
return test{
db: &db.MockNoSQLDB{
MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) {
switch string(bucket) {
case string(externalAccountKeysByReferenceTable):
assert.Equals(t, string(key), ref)
assert.Equals(t, old, nil)
return nu, true, nil
case string(externalAccountKeyTable):
assert.Equals(t, old, nil)
id = string(key)
dbeak := new(dbExternalAccountKey)
assert.FatalError(t, json.Unmarshal(nu, dbeak))
assert.Equals(t, string(key), dbeak.ID)
assert.Equals(t, eak.Provisioner, dbeak.Provisioner)
assert.Equals(t, eak.Reference, dbeak.Reference)
assert.Equals(t, 32, len(dbeak.KeyBytes))
assert.False(t, dbeak.CreatedAt.IsZero())
assert.Equals(t, dbeak.AccountID, eak.AccountID)
assert.True(t, dbeak.BoundAt.IsZero())
return nu, true, nil
default:
assert.FatalError(t, errors.Errorf("unrecognized bucket %s", string(bucket)))
return nil, false, errors.New("force")
}
},
},
eak: eak,
_id: idPtr,
}
},
"fail/externalAccountKeyID-cmpAndSwap-error": func(t *testing.T) test {
return test{
db: &db.MockNoSQLDB{
MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) {
switch string(bucket) {
case string(externalAccountKeysByReferenceTable):
assert.Equals(t, string(key), ref)
assert.Equals(t, old, nil)
return nu, true, nil
case string(externalAccountKeyTable):
assert.Equals(t, old, nil)
return nu, true, errors.New("force")
default:
assert.FatalError(t, errors.Errorf("unrecognized bucket %s", string(bucket)))
return nil, false, errors.New("force")
}
},
},
err: errors.New("error saving acme external_account_key"),
}
},
"fail/externalAccountKeyReference-cmpAndSwap-error": func(t *testing.T) test {
return test{
db: &db.MockNoSQLDB{
MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) {
switch string(bucket) {
case string(externalAccountKeysByReferenceTable):
assert.Equals(t, string(key), ref)
assert.Equals(t, old, nil)
return nu, true, errors.New("force")
case string(externalAccountKeyTable):
assert.Equals(t, old, nil)
return nu, true, nil
default:
assert.FatalError(t, errors.Errorf("unrecognized bucket %s", string(bucket)))
return nil, false, errors.New("force")
}
},
},
err: errors.New("error saving acme external_account_key"),
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
d := DB{db: tc.db}
eak, err := d.CreateExternalAccountKey(context.Background(), prov, ref)
if 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._id, eak.ID)
assert.Equals(t, prov, eak.Provisioner)
assert.Equals(t, ref, eak.Reference)
assert.Equals(t, "", eak.AccountID)
assert.False(t, eak.CreatedAt.IsZero())
assert.False(t, eak.AlreadyBound())
assert.True(t, eak.BoundAt.IsZero())
}
})
}
}
func TestDB_UpdateExternalAccountKey(t *testing.T) {
keyID := "keyID"
prov := "acmeProv"
ref := "ref"
now := clock.Now()
dbeak := &dbExternalAccountKey{
ID: keyID,
Provisioner: prov,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
b, err := json.Marshal(dbeak)
assert.FatalError(t, err)
type test struct {
db nosql.DB
eak *acme.ExternalAccountKey
err error
}
var tests = map[string]func(t *testing.T) test{
"ok": func(t *testing.T) test {
eak := &acme.ExternalAccountKey{
ID: keyID,
Provisioner: prov,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
return test{
eak: eak,
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, externalAccountKeyTable)
assert.Equals(t, string(key), keyID)
return b, nil
},
MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) {
assert.Equals(t, bucket, externalAccountKeyTable)
assert.Equals(t, old, b)
dbNew := new(dbExternalAccountKey)
assert.FatalError(t, json.Unmarshal(nu, dbNew))
assert.Equals(t, dbNew.ID, dbeak.ID)
assert.Equals(t, dbNew.Provisioner, dbeak.Provisioner)
assert.Equals(t, dbNew.Reference, dbeak.Reference)
assert.Equals(t, dbNew.AccountID, dbeak.AccountID)
assert.Equals(t, dbNew.CreatedAt, dbeak.CreatedAt)
assert.Equals(t, dbNew.BoundAt, dbeak.BoundAt)
assert.Equals(t, dbNew.KeyBytes, dbeak.KeyBytes)
return nu, true, nil
},
},
}
},
"fail/provisioner-mismatch": func(t *testing.T) test {
newDBEAK := &dbExternalAccountKey{
ID: keyID,
Provisioner: "differentProvisioner",
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
b, err := json.Marshal(newDBEAK)
assert.FatalError(t, err)
return test{
eak: &acme.ExternalAccountKey{
ID: keyID,
},
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, externalAccountKeyTable)
assert.Equals(t, string(key), keyID)
return b, nil
},
},
err: errors.New("name of provisioner does not match provisioner for which the EAB key was created"),
}
},
"fail/db.Get-error": func(t *testing.T) test {
return test{
eak: &acme.ExternalAccountKey{
ID: keyID,
},
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, externalAccountKeyTable)
assert.Equals(t, string(key), keyID)
return nil, errors.New("force")
},
},
err: errors.New("error loading external account key keyID: force"),
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
d := DB{db: tc.db}
if err := d.UpdateExternalAccountKey(context.Background(), prov, tc.eak); 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, dbeak.ID, tc.eak.ID)
assert.Equals(t, dbeak.Provisioner, tc.eak.Provisioner)
assert.Equals(t, dbeak.Reference, tc.eak.Reference)
assert.Equals(t, dbeak.AccountID, tc.eak.AccountID)
assert.Equals(t, dbeak.CreatedAt, tc.eak.CreatedAt)
assert.Equals(t, dbeak.BoundAt, tc.eak.BoundAt)
assert.Equals(t, dbeak.KeyBytes, tc.eak.KeyBytes)
}
})
}
}