certificates/acme/db/nosql/account_test.go
Herman Slatman ef16febf40
Refactor ACME EAB queries
The ACME EAB keys are now also indexed by the provisioner. This
solves part of the issue in which too many EAB keys may be in
memory at a given time.
2022-01-07 16:59:55 +01:00

1783 lines
52 KiB
Go

package nosql
import (
"context"
"encoding/json"
"fmt"
"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("unexpected 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("unexpected 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("unexpected 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("unexpected 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"
provID := "provID"
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,
ProvisionerID: provID,
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.ProvisionerID, tc.dbeak.ProvisionerID)
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"
provID := "provID"
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,
ProvisionerID: provID,
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,
ProvisionerID: provID,
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,
ProvisionerID: "aDifferentProvID",
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,
ProvisionerID: provID,
Reference: "ref",
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
},
acmeErr: acme.NewError(acme.ErrorUnauthorizedType, "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(), provID, 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.ProvisionerID, tc.eak.ProvisionerID)
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"
provID := "provID"
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,
ProvisionerID: provID,
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), provID+"."+ref)
return dbrefBytes, nil
case string(externalAccountKeyTable):
assert.Equals(t, string(key), keyID)
return b, nil
default:
assert.FatalError(t, errors.Errorf("unexpected bucket %s", string(bucket)))
return nil, errors.New("force")
}
},
},
eak: &acme.ExternalAccountKey{
ID: keyID,
ProvisionerID: provID,
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), provID+"."+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), provID+"."+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), provID+"."+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), provID+"."+ref)
return dbrefBytes, nil
case string(externalAccountKeyTable):
assert.Equals(t, string(key), keyID)
return nil, errors.New("force")
default:
assert.FatalError(t, errors.Errorf("unexpected 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(), provID, 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.ProvisionerID, tc.eak.ProvisionerID)
assert.Equals(t, eak.Reference, tc.eak.Reference)
}
})
}
}
func TestDB_GetExternalAccountKeys(t *testing.T) {
keyID1 := "keyID1"
keyID2 := "keyID2"
keyID3 := "keyID3"
provID := "provID"
ref := "ref"
type test struct {
db nosql.DB
err error
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,
ProvisionerID: provID,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
b1, err := json.Marshal(dbeak1)
assert.FatalError(t, err)
dbeak2 := &dbExternalAccountKey{
ID: keyID2,
ProvisionerID: provID,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
}
b2, err := json.Marshal(dbeak2)
assert.FatalError(t, err)
dbeak3 := &dbExternalAccountKey{
ID: keyID3,
ProvisionerID: "aDifferentProvID",
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{
MGet: func(bucket, key []byte) ([]byte, error) {
switch string(bucket) {
case string(externalAccountKeysByProvisionerIDTable):
keys := []string{keyID1, keyID2}
b, err := json.Marshal(keys)
assert.FatalError(t, err)
return b, nil
case string(externalAccountKeyTable):
switch string(key) {
case keyID1:
return b1, nil
case keyID2:
return b2, nil
default:
assert.FatalError(t, errors.Errorf("unexpected key %s", string(key)))
return nil, errors.New("force")
}
default:
assert.FatalError(t, errors.Errorf("unexpected bucket %s", string(bucket)))
return nil, errors.New("force")
}
},
// TODO: remove the MList
MList: func(bucket []byte) ([]*nosqldb.Entry, error) {
switch string(bucket) {
case string(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
case string(externalAccountKeysByProvisionerIDTable):
keys := []string{keyID1, keyID2}
b, err := json.Marshal(keys)
assert.FatalError(t, err)
return []*nosqldb.Entry{
{
Bucket: bucket,
Key: []byte(provID),
Value: b,
},
}, nil
default:
assert.FatalError(t, errors.Errorf("unexpected bucket %s", string(bucket)))
return nil, errors.New("force default")
}
},
},
eaks: []*acme.ExternalAccountKey{
{
ID: keyID1,
ProvisionerID: provID,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
},
{
ID: keyID2,
ProvisionerID: provID,
Reference: ref,
AccountID: "",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: now,
},
},
}
},
"fail/db.Get-externalAccountKeysByProvisionerIDTable": func(t *testing.T) test {
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, string(bucket), string(externalAccountKeysByProvisionerIDTable))
return nil, errors.New("force")
},
},
err: errors.New("error loading ACME EAB Key IDs for provisioner provID: force"),
}
},
"fail/db.getDBExternalAccountKey": func(t *testing.T) test {
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
switch string(bucket) {
case string(externalAccountKeysByProvisionerIDTable):
keys := []string{keyID1, keyID2}
b, err := json.Marshal(keys)
assert.FatalError(t, err)
return b, nil
case string(externalAccountKeyTable):
return nil, errors.New("force")
default:
assert.FatalError(t, errors.Errorf("unexpected bucket %s", string(bucket)))
return nil, errors.New("force bucket")
}
},
},
err: errors.New("error retrieving ACME EAB Key for provisioner provID and keyID keyID1: error loading external account key keyID1: force"),
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
d := DB{db: tc.db}
if eaks, err := d.GetExternalAccountKeys(context.Background(), provID); 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.Equals(t, tc.err.Error(), 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.ProvisionerID, tc.eaks[i].ProvisionerID)
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)
}
}
})
}
}
func TestDB_DeleteExternalAccountKey(t *testing.T) {
keyID := "keyID"
provID := "provID"
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,
ProvisionerID: provID,
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), provID+"."+ref)
return dbrefBytes, nil
case string(externalAccountKeyTable):
assert.Equals(t, string(key), keyID)
return b, nil
case string(externalAccountKeysByProvisionerIDTable):
assert.Equals(t, provID, string(key))
b, err := json.Marshal([]string{keyID})
assert.FatalError(t, err)
return b, nil
default:
assert.FatalError(t, errors.Errorf("unexpected bucket %s", string(bucket)))
return nil, errors.New("force default")
}
},
MDel: func(bucket, key []byte) error {
switch string(bucket) {
case string(externalAccountKeysByReferenceTable):
assert.Equals(t, string(key), provID+"."+ref)
return nil
case string(externalAccountKeyTable):
assert.Equals(t, string(key), keyID)
return nil
default:
assert.FatalError(t, errors.Errorf("unexpected bucket %s", string(bucket)))
return errors.New("force default")
}
},
MCmpAndSwap: func(bucket, key, old, new []byte) ([]byte, bool, error) {
fmt.Println(string(bucket))
switch string(bucket) {
case string(externalAccountKeysByReferenceTable):
assert.Equals(t, provID+"."+ref, string(key))
return nil, true, nil
case string(externalAccountKeysByProvisionerIDTable):
assert.Equals(t, provID, string(key))
return nil, true, nil
default:
assert.FatalError(t, errors.Errorf("unexpected bucket %s", string(bucket)))
return nil, false, errors.New("force default")
}
},
},
}
},
"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: not found"),
}
},
"fail/non-matching-provisioner": func(t *testing.T) test {
now := clock.Now()
dbeak := &dbExternalAccountKey{
ID: keyID,
ProvisionerID: "aDifferentProvID",
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("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,
ProvisionerID: provID,
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("unexpected bucket %s", string(bucket)))
return nil, errors.New("force default")
}
},
MDel: func(bucket, key []byte) error {
switch string(bucket) {
case string(externalAccountKeysByReferenceTable):
assert.Equals(t, string(key), provID+"."+ref)
return errors.New("force")
case string(externalAccountKeyTable):
assert.Equals(t, string(key), keyID)
return nil
default:
assert.FatalError(t, errors.Errorf("unexpected bucket %s", string(bucket)))
return errors.New("force default")
}
},
},
err: errors.New("error deleting ACME EAB Key reference with Key ID keyID and reference ref: force"),
}
},
"fail/delete-eak": func(t *testing.T) test {
now := clock.Now()
dbeak := &dbExternalAccountKey{
ID: keyID,
ProvisionerID: provID,
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("unexpected 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), provID+"."+ref)
return nil
case string(externalAccountKeyTable):
assert.Equals(t, string(key), keyID)
return errors.New("force")
default:
assert.FatalError(t, errors.Errorf("unexpected bucket %s", string(bucket)))
return errors.New("force")
}
},
},
err: errors.New("error deleting ACME EAB Key with Key ID 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.DeleteExternalAccountKey(context.Background(), provID, 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.Equals(t, err.Error(), tc.err.Error())
}
}
} else {
assert.Nil(t, tc.err)
}
})
}
}
func TestDB_CreateExternalAccountKey(t *testing.T) {
keyID := "keyID"
provID := "provID"
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,
ProvisionerID: provID,
Reference: "ref",
AccountID: "",
CreatedAt: now,
}
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, string(bucket), string(externalAccountKeysByProvisionerIDTable))
assert.Equals(t, provID, string(key))
b, _ := json.Marshal([]string{})
return b, nil
},
MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) {
switch string(bucket) {
case string(externalAccountKeysByProvisionerIDTable):
assert.Equals(t, provID, string(key))
return nu, true, nil
case string(externalAccountKeysByReferenceTable):
assert.Equals(t, provID+"."+ref, string(key))
assert.Equals(t, nil, old)
return nu, true, nil
case string(externalAccountKeyTable):
assert.Equals(t, nil, old)
id = string(key)
dbeak := new(dbExternalAccountKey)
assert.FatalError(t, json.Unmarshal(nu, dbeak))
assert.Equals(t, string(key), dbeak.ID)
assert.Equals(t, eak.ProvisionerID, dbeak.ProvisionerID)
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("unexpected bucket %s", string(bucket)))
return nil, false, errors.New("force default")
}
},
},
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("unexpected bucket %s", string(bucket)))
return nil, false, errors.New("force")
}
},
},
err: errors.New("error saving acme external_account_key: force"),
}
},
"fail/externalAccountKeyReference-cmpAndSwap-error": func(t *testing.T) test {
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, string(bucket), string(externalAccountKeysByProvisionerIDTable))
assert.Equals(t, provID, string(key))
b, _ := json.Marshal([]string{})
return b, nil
},
MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) {
switch string(bucket) {
case string(externalAccountKeysByProvisionerIDTable):
assert.Equals(t, provID, string(key))
return nu, true, nil
case string(externalAccountKeysByReferenceTable):
assert.Equals(t, provID+"."+ref, string(key))
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("unexpected bucket %s", string(bucket)))
return nil, false, errors.New("force")
}
},
},
err: errors.New("error saving acme external_account_key_reference: force"),
}
},
}
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(), provID, ref)
fmt.Println(name, err)
if err != nil {
if assert.NotNil(t, tc.err) {
assert.Equals(t, err.Error(), tc.err.Error())
}
} else if assert.Nil(t, tc.err) {
assert.Equals(t, *tc._id, eak.ID)
assert.Equals(t, provID, eak.ProvisionerID)
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"
provID := "provID"
ref := "ref"
now := clock.Now()
dbeak := &dbExternalAccountKey{
ID: keyID,
ProvisionerID: provID,
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,
ProvisionerID: provID,
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.ProvisionerID, dbeak.ProvisionerID)
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,
ProvisionerID: "aDifferentProvID",
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("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(), provID, 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.ProvisionerID, tc.eak.ProvisionerID)
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)
}
})
}
}