- Each acme account has an index in this table. Before this change, the index would grow unchecked as orders accumulate. This change removes orders that have moved out of the 'PENDING' state.
1064 lines
28 KiB
Go
1064 lines
28 KiB
Go
package acme
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/smallstep/assert"
|
|
"github.com/smallstep/certificates/authority/provisioner"
|
|
"github.com/smallstep/certificates/db"
|
|
"github.com/smallstep/cli/jose"
|
|
"github.com/smallstep/nosql"
|
|
"github.com/smallstep/nosql/database"
|
|
)
|
|
|
|
var (
|
|
defaultDisableRenewal = false
|
|
globalProvisionerClaims = provisioner.Claims{
|
|
MinTLSDur: &provisioner.Duration{Duration: 5 * time.Minute},
|
|
MaxTLSDur: &provisioner.Duration{Duration: 24 * time.Hour},
|
|
DefaultTLSDur: &provisioner.Duration{Duration: 24 * time.Hour},
|
|
DisableRenewal: &defaultDisableRenewal,
|
|
}
|
|
)
|
|
|
|
func newProv() Provisioner {
|
|
// Initialize provisioners
|
|
p := &provisioner.ACME{
|
|
Type: "ACME",
|
|
Name: "test@acme-provisioner.com",
|
|
}
|
|
if err := p.Init(provisioner.Config{Claims: globalProvisionerClaims}); err != nil {
|
|
fmt.Printf("%v", err)
|
|
}
|
|
return p
|
|
}
|
|
|
|
func newAcc() (*account, error) {
|
|
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
mockdb := &db.MockNoSQLDB{
|
|
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
|
|
return nil, true, nil
|
|
},
|
|
}
|
|
return newAccount(mockdb, AccountOptions{
|
|
Key: jwk, Contact: []string{"foo", "bar"},
|
|
})
|
|
}
|
|
|
|
func TestGetAccountByID(t *testing.T) {
|
|
type test struct {
|
|
id string
|
|
db nosql.DB
|
|
acc *account
|
|
err *Error
|
|
}
|
|
tests := map[string]func(t *testing.T) test{
|
|
"fail/not-found": func(t *testing.T) test {
|
|
acc, err := newAcc()
|
|
assert.FatalError(t, err)
|
|
return test{
|
|
acc: acc,
|
|
id: acc.ID,
|
|
db: &db.MockNoSQLDB{
|
|
MGet: func(bucket, key []byte) ([]byte, error) {
|
|
return nil, database.ErrNotFound
|
|
},
|
|
},
|
|
err: MalformedErr(errors.Errorf("account %s not found: not found", acc.ID)),
|
|
}
|
|
},
|
|
"fail/db-error": func(t *testing.T) test {
|
|
acc, err := newAcc()
|
|
assert.FatalError(t, err)
|
|
return test{
|
|
acc: acc,
|
|
id: acc.ID,
|
|
db: &db.MockNoSQLDB{
|
|
MGet: func(bucket, key []byte) ([]byte, error) {
|
|
return nil, errors.New("force")
|
|
},
|
|
},
|
|
err: ServerInternalErr(errors.Errorf("error loading account %s: force", acc.ID)),
|
|
}
|
|
},
|
|
"fail/unmarshal-error": func(t *testing.T) test {
|
|
acc, err := newAcc()
|
|
assert.FatalError(t, err)
|
|
return test{
|
|
acc: acc,
|
|
id: acc.ID,
|
|
db: &db.MockNoSQLDB{
|
|
MGet: func(bucket, key []byte) ([]byte, error) {
|
|
assert.Equals(t, bucket, accountTable)
|
|
assert.Equals(t, key, []byte(acc.ID))
|
|
return nil, nil
|
|
},
|
|
},
|
|
err: ServerInternalErr(errors.New("error unmarshaling account: unexpected end of JSON input")),
|
|
}
|
|
},
|
|
"ok": func(t *testing.T) test {
|
|
acc, err := newAcc()
|
|
assert.FatalError(t, err)
|
|
b, err := json.Marshal(acc)
|
|
assert.FatalError(t, err)
|
|
return test{
|
|
acc: acc,
|
|
id: acc.ID,
|
|
db: &db.MockNoSQLDB{
|
|
MGet: func(bucket, key []byte) ([]byte, error) {
|
|
assert.Equals(t, bucket, accountTable)
|
|
assert.Equals(t, key, []byte(acc.ID))
|
|
return b, nil
|
|
},
|
|
},
|
|
}
|
|
},
|
|
}
|
|
for name, run := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
tc := run(t)
|
|
if acc, err := getAccountByID(tc.db, tc.id); err != nil {
|
|
if assert.NotNil(t, tc.err) {
|
|
ae, ok := err.(*Error)
|
|
assert.True(t, ok)
|
|
assert.HasPrefix(t, ae.Error(), tc.err.Error())
|
|
assert.Equals(t, ae.StatusCode(), tc.err.StatusCode())
|
|
assert.Equals(t, ae.Type, tc.err.Type)
|
|
}
|
|
} else {
|
|
if assert.Nil(t, tc.err) {
|
|
assert.Equals(t, tc.acc.ID, acc.ID)
|
|
assert.Equals(t, tc.acc.Status, acc.Status)
|
|
assert.Equals(t, tc.acc.Created, acc.Created)
|
|
assert.Equals(t, tc.acc.Deactivated, acc.Deactivated)
|
|
assert.Equals(t, tc.acc.Contact, acc.Contact)
|
|
assert.Equals(t, tc.acc.Key.KeyID, acc.Key.KeyID)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetAccountByKeyID(t *testing.T) {
|
|
type test struct {
|
|
kid string
|
|
db nosql.DB
|
|
acc *account
|
|
err *Error
|
|
}
|
|
tests := map[string]func(t *testing.T) test{
|
|
"fail/kid-not-found": func(t *testing.T) test {
|
|
return test{
|
|
kid: "foo",
|
|
db: &db.MockNoSQLDB{
|
|
MGet: func(bucket, key []byte) ([]byte, error) {
|
|
return nil, database.ErrNotFound
|
|
},
|
|
},
|
|
err: MalformedErr(errors.Errorf("account with key id foo not found: not found")),
|
|
}
|
|
},
|
|
"fail/db-error": func(t *testing.T) test {
|
|
return test{
|
|
kid: "foo",
|
|
db: &db.MockNoSQLDB{
|
|
MGet: func(bucket, key []byte) ([]byte, error) {
|
|
return nil, errors.New("force")
|
|
},
|
|
},
|
|
err: ServerInternalErr(errors.New("error loading key-account index: force")),
|
|
}
|
|
},
|
|
"fail/getAccount-error": func(t *testing.T) test {
|
|
count := 0
|
|
return test{
|
|
kid: "foo",
|
|
db: &db.MockNoSQLDB{
|
|
MGet: func(bucket, key []byte) ([]byte, error) {
|
|
if count == 0 {
|
|
assert.Equals(t, bucket, accountByKeyIDTable)
|
|
assert.Equals(t, key, []byte("foo"))
|
|
count++
|
|
return []byte("bar"), nil
|
|
}
|
|
return nil, errors.New("force")
|
|
},
|
|
},
|
|
err: ServerInternalErr(errors.New("error loading account bar: force")),
|
|
}
|
|
},
|
|
"ok": func(t *testing.T) test {
|
|
acc, err := newAcc()
|
|
assert.FatalError(t, err)
|
|
b, err := json.Marshal(acc)
|
|
assert.FatalError(t, err)
|
|
count := 0
|
|
return test{
|
|
kid: acc.Key.KeyID,
|
|
db: &db.MockNoSQLDB{
|
|
MGet: func(bucket, key []byte) ([]byte, error) {
|
|
var ret []byte
|
|
switch count {
|
|
case 0:
|
|
assert.Equals(t, bucket, accountByKeyIDTable)
|
|
assert.Equals(t, key, []byte(acc.Key.KeyID))
|
|
ret = []byte(acc.ID)
|
|
case 1:
|
|
assert.Equals(t, bucket, accountTable)
|
|
assert.Equals(t, key, []byte(acc.ID))
|
|
ret = b
|
|
}
|
|
count++
|
|
return ret, nil
|
|
},
|
|
},
|
|
acc: acc,
|
|
}
|
|
},
|
|
}
|
|
for name, run := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
tc := run(t)
|
|
if acc, err := getAccountByKeyID(tc.db, tc.kid); err != nil {
|
|
if assert.NotNil(t, tc.err) {
|
|
ae, ok := err.(*Error)
|
|
assert.True(t, ok)
|
|
assert.HasPrefix(t, ae.Error(), tc.err.Error())
|
|
assert.Equals(t, ae.StatusCode(), tc.err.StatusCode())
|
|
assert.Equals(t, ae.Type, tc.err.Type)
|
|
}
|
|
} else {
|
|
if assert.Nil(t, tc.err) {
|
|
assert.Equals(t, tc.acc.ID, acc.ID)
|
|
assert.Equals(t, tc.acc.Status, acc.Status)
|
|
assert.Equals(t, tc.acc.Created, acc.Created)
|
|
assert.Equals(t, tc.acc.Deactivated, acc.Deactivated)
|
|
assert.Equals(t, tc.acc.Contact, acc.Contact)
|
|
assert.Equals(t, tc.acc.Key.KeyID, acc.Key.KeyID)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_getOrderIDsByAccount(t *testing.T) {
|
|
type test struct {
|
|
id string
|
|
db nosql.DB
|
|
res []string
|
|
err *Error
|
|
}
|
|
tests := map[string]func(t *testing.T) test{
|
|
"ok/not-found": func(t *testing.T) test {
|
|
return test{
|
|
id: "foo",
|
|
db: &db.MockNoSQLDB{
|
|
MGet: func(bucket, key []byte) ([]byte, error) {
|
|
return nil, database.ErrNotFound
|
|
},
|
|
},
|
|
res: []string{},
|
|
}
|
|
},
|
|
"fail/db-error": func(t *testing.T) test {
|
|
return test{
|
|
id: "foo",
|
|
db: &db.MockNoSQLDB{
|
|
MGet: func(bucket, key []byte) ([]byte, error) {
|
|
return nil, errors.New("force")
|
|
},
|
|
},
|
|
err: ServerInternalErr(errors.New("error loading orderIDs for account foo: force")),
|
|
}
|
|
},
|
|
"fail/unmarshal-error": func(t *testing.T) test {
|
|
return test{
|
|
id: "foo",
|
|
db: &db.MockNoSQLDB{
|
|
MGet: func(bucket, key []byte) ([]byte, error) {
|
|
assert.Equals(t, bucket, ordersByAccountIDTable)
|
|
assert.Equals(t, key, []byte("foo"))
|
|
return nil, nil
|
|
},
|
|
},
|
|
err: ServerInternalErr(errors.New("error unmarshaling orderIDs for account foo: unexpected end of JSON input")),
|
|
}
|
|
},
|
|
"fail/error-loading-order-from-order-IDs": func(t *testing.T) test {
|
|
oids := []string{"o1", "o2", "o3"}
|
|
boids, err := json.Marshal(oids)
|
|
assert.FatalError(t, err)
|
|
dbHit := 0
|
|
return test{
|
|
id: "foo",
|
|
db: &db.MockNoSQLDB{
|
|
MGet: func(bucket, key []byte) ([]byte, error) {
|
|
dbHit++
|
|
switch dbHit {
|
|
case 1:
|
|
assert.Equals(t, bucket, ordersByAccountIDTable)
|
|
assert.Equals(t, key, []byte("foo"))
|
|
return boids, nil
|
|
case 2:
|
|
assert.Equals(t, bucket, orderTable)
|
|
assert.Equals(t, key, []byte("o1"))
|
|
return nil, errors.New("force")
|
|
default:
|
|
assert.FatalError(t, errors.New("should not be here"))
|
|
return nil, nil
|
|
}
|
|
},
|
|
},
|
|
err: ServerInternalErr(errors.New("error loading order o1 for account foo: error loading order o1: force")),
|
|
}
|
|
},
|
|
"fail/error-updating-order-from-order-IDs": func(t *testing.T) test {
|
|
oids := []string{"o1", "o2", "o3"}
|
|
boids, err := json.Marshal(oids)
|
|
assert.FatalError(t, err)
|
|
|
|
o, err := newO()
|
|
assert.FatalError(t, err)
|
|
bo, err := json.Marshal(o)
|
|
assert.FatalError(t, err)
|
|
|
|
dbHit := 0
|
|
return test{
|
|
id: "foo",
|
|
db: &db.MockNoSQLDB{
|
|
MGet: func(bucket, key []byte) ([]byte, error) {
|
|
dbHit++
|
|
switch dbHit {
|
|
case 1:
|
|
assert.Equals(t, bucket, ordersByAccountIDTable)
|
|
assert.Equals(t, key, []byte("foo"))
|
|
return boids, nil
|
|
case 2:
|
|
assert.Equals(t, bucket, orderTable)
|
|
assert.Equals(t, key, []byte("o1"))
|
|
return bo, nil
|
|
case 3:
|
|
assert.Equals(t, bucket, authzTable)
|
|
assert.Equals(t, key, []byte(o.Authorizations[0]))
|
|
return nil, errors.New("force")
|
|
default:
|
|
assert.FatalError(t, errors.New("should not be here"))
|
|
return nil, nil
|
|
}
|
|
},
|
|
},
|
|
err: ServerInternalErr(errors.Errorf("error updating order o1 for account foo: error loading authz %s: force", o.Authorizations[0])),
|
|
}
|
|
},
|
|
"ok/no-change-to-pending-orders": func(t *testing.T) test {
|
|
oids := []string{"o1", "o2", "o3"}
|
|
boids, err := json.Marshal(oids)
|
|
assert.FatalError(t, err)
|
|
|
|
o, err := newO()
|
|
assert.FatalError(t, err)
|
|
bo, err := json.Marshal(o)
|
|
assert.FatalError(t, err)
|
|
|
|
az, err := newAz()
|
|
assert.FatalError(t, err)
|
|
baz, err := json.Marshal(az)
|
|
assert.FatalError(t, err)
|
|
|
|
ch, err := newDNSCh()
|
|
assert.FatalError(t, err)
|
|
bch, err := json.Marshal(ch)
|
|
assert.FatalError(t, err)
|
|
|
|
return test{
|
|
id: "foo",
|
|
db: &db.MockNoSQLDB{
|
|
MGet: func(bucket, key []byte) ([]byte, error) {
|
|
switch string(bucket) {
|
|
case string(ordersByAccountIDTable):
|
|
assert.Equals(t, key, []byte("foo"))
|
|
return boids, nil
|
|
case string(orderTable):
|
|
return bo, nil
|
|
case string(authzTable):
|
|
return baz, nil
|
|
case string(challengeTable):
|
|
return bch, nil
|
|
default:
|
|
assert.FatalError(t, errors.Errorf("did not expect query to table %s", bucket))
|
|
return nil, nil
|
|
}
|
|
},
|
|
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
|
|
return nil, false, errors.New("should not be attempting to store anything")
|
|
},
|
|
},
|
|
res: oids,
|
|
}
|
|
},
|
|
"fail/error-storing-new-oids": func(t *testing.T) test {
|
|
oids := []string{"o1", "o2", "o3"}
|
|
boids, err := json.Marshal(oids)
|
|
assert.FatalError(t, err)
|
|
|
|
o, err := newO()
|
|
assert.FatalError(t, err)
|
|
bo, err := json.Marshal(o)
|
|
assert.FatalError(t, err)
|
|
|
|
invalidOrder, err := newO()
|
|
assert.FatalError(t, err)
|
|
invalidOrder.Status = StatusInvalid
|
|
binvalidOrder, err := json.Marshal(invalidOrder)
|
|
assert.FatalError(t, err)
|
|
|
|
az, err := newAz()
|
|
assert.FatalError(t, err)
|
|
baz, err := json.Marshal(az)
|
|
assert.FatalError(t, err)
|
|
|
|
ch, err := newDNSCh()
|
|
assert.FatalError(t, err)
|
|
bch, err := json.Marshal(ch)
|
|
assert.FatalError(t, err)
|
|
|
|
dbGetOrder := 0
|
|
return test{
|
|
id: "foo",
|
|
db: &db.MockNoSQLDB{
|
|
MGet: func(bucket, key []byte) ([]byte, error) {
|
|
switch string(bucket) {
|
|
case string(ordersByAccountIDTable):
|
|
assert.Equals(t, key, []byte("foo"))
|
|
return boids, nil
|
|
case string(orderTable):
|
|
dbGetOrder++
|
|
if dbGetOrder == 1 {
|
|
return binvalidOrder, nil
|
|
}
|
|
return bo, nil
|
|
case string(authzTable):
|
|
return baz, nil
|
|
case string(challengeTable):
|
|
return bch, nil
|
|
default:
|
|
assert.FatalError(t, errors.Errorf("did not expect query to table %s", bucket))
|
|
return nil, nil
|
|
}
|
|
},
|
|
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
|
|
assert.Equals(t, bucket, ordersByAccountIDTable)
|
|
assert.Equals(t, key, []byte("foo"))
|
|
return nil, false, errors.New("force")
|
|
},
|
|
},
|
|
err: ServerInternalErr(errors.New("error storing orderIDs as part of getOrderIDsByAccount logic: len(orderIDs) = 2: error storing order IDs for account foo: force")),
|
|
}
|
|
},
|
|
"ok": func(t *testing.T) test {
|
|
oids := []string{"o1", "o2", "o3", "o4"}
|
|
boids, err := json.Marshal(oids)
|
|
assert.FatalError(t, err)
|
|
|
|
o, err := newO()
|
|
assert.FatalError(t, err)
|
|
bo, err := json.Marshal(o)
|
|
assert.FatalError(t, err)
|
|
|
|
invalidOrder, err := newO()
|
|
assert.FatalError(t, err)
|
|
invalidOrder.Status = StatusInvalid
|
|
binvalidOrder, err := json.Marshal(invalidOrder)
|
|
assert.FatalError(t, err)
|
|
|
|
az, err := newAz()
|
|
assert.FatalError(t, err)
|
|
baz, err := json.Marshal(az)
|
|
assert.FatalError(t, err)
|
|
|
|
ch, err := newDNSCh()
|
|
assert.FatalError(t, err)
|
|
bch, err := json.Marshal(ch)
|
|
assert.FatalError(t, err)
|
|
|
|
dbGetOrder := 0
|
|
return test{
|
|
id: "foo",
|
|
db: &db.MockNoSQLDB{
|
|
MGet: func(bucket, key []byte) ([]byte, error) {
|
|
switch string(bucket) {
|
|
case string(ordersByAccountIDTable):
|
|
assert.Equals(t, key, []byte("foo"))
|
|
return boids, nil
|
|
case string(orderTable):
|
|
dbGetOrder++
|
|
if dbGetOrder == 1 || dbGetOrder == 3 {
|
|
return binvalidOrder, nil
|
|
}
|
|
return bo, nil
|
|
case string(authzTable):
|
|
return baz, nil
|
|
case string(challengeTable):
|
|
return bch, nil
|
|
default:
|
|
assert.FatalError(t, errors.Errorf("did not expect query to table %s", bucket))
|
|
return nil, nil
|
|
}
|
|
},
|
|
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
|
|
assert.Equals(t, bucket, ordersByAccountIDTable)
|
|
assert.Equals(t, key, []byte("foo"))
|
|
return nil, true, nil
|
|
},
|
|
},
|
|
res: []string{"o2", "o4"},
|
|
}
|
|
},
|
|
}
|
|
for name, run := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
tc := run(t)
|
|
if oids, err := getOrderIDsByAccount(tc.db, tc.id); err != nil {
|
|
if assert.NotNil(t, tc.err) {
|
|
ae, ok := err.(*Error)
|
|
assert.True(t, ok)
|
|
assert.HasPrefix(t, ae.Error(), tc.err.Error())
|
|
assert.Equals(t, ae.StatusCode(), tc.err.StatusCode())
|
|
assert.Equals(t, ae.Type, tc.err.Type)
|
|
}
|
|
} else {
|
|
if assert.Nil(t, tc.err) {
|
|
assert.Equals(t, tc.res, oids)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAccountToACME(t *testing.T) {
|
|
dir := newDirectory("ca.smallstep.com", "acme")
|
|
prov := newProv()
|
|
provName := url.PathEscape(prov.GetName())
|
|
baseURL := &url.URL{Scheme: "https", Host: "test.ca.smallstep.com"}
|
|
ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov)
|
|
ctx = context.WithValue(ctx, BaseURLContextKey, baseURL)
|
|
|
|
type test struct {
|
|
acc *account
|
|
err *Error
|
|
}
|
|
tests := map[string]func(t *testing.T) test{
|
|
"ok": func(t *testing.T) test {
|
|
acc, err := newAcc()
|
|
assert.FatalError(t, err)
|
|
return test{acc: acc}
|
|
},
|
|
}
|
|
for name, run := range tests {
|
|
tc := run(t)
|
|
t.Run(name, func(t *testing.T) {
|
|
acmeAccount, err := tc.acc.toACME(ctx, nil, dir)
|
|
if err != nil {
|
|
if assert.NotNil(t, tc.err) {
|
|
ae, ok := err.(*Error)
|
|
assert.True(t, ok)
|
|
assert.HasPrefix(t, ae.Error(), tc.err.Error())
|
|
assert.Equals(t, ae.StatusCode(), tc.err.StatusCode())
|
|
assert.Equals(t, ae.Type, tc.err.Type)
|
|
}
|
|
} else {
|
|
if assert.Nil(t, tc.err) {
|
|
assert.Equals(t, acmeAccount.ID, tc.acc.ID)
|
|
assert.Equals(t, acmeAccount.Status, tc.acc.Status)
|
|
assert.Equals(t, acmeAccount.Contact, tc.acc.Contact)
|
|
assert.Equals(t, acmeAccount.Key.KeyID, tc.acc.Key.KeyID)
|
|
assert.Equals(t, acmeAccount.Orders,
|
|
fmt.Sprintf("%s/acme/%s/account/%s/orders", baseURL.String(), provName, tc.acc.ID))
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAccountSave(t *testing.T) {
|
|
type test struct {
|
|
acc, old *account
|
|
db nosql.DB
|
|
err *Error
|
|
}
|
|
tests := map[string]func(t *testing.T) test{
|
|
"fail/old-nil/swap-error": func(t *testing.T) test {
|
|
acc, err := newAcc()
|
|
assert.FatalError(t, err)
|
|
return test{
|
|
acc: acc,
|
|
old: nil,
|
|
db: &db.MockNoSQLDB{
|
|
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
|
|
return nil, false, errors.New("force")
|
|
},
|
|
},
|
|
err: ServerInternalErr(errors.New("error storing account: force")),
|
|
}
|
|
},
|
|
"fail/old-nil/swap-false": func(t *testing.T) test {
|
|
acc, err := newAcc()
|
|
assert.FatalError(t, err)
|
|
return test{
|
|
acc: acc,
|
|
old: nil,
|
|
db: &db.MockNoSQLDB{
|
|
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
|
|
return []byte("foo"), false, nil
|
|
},
|
|
},
|
|
err: ServerInternalErr(errors.New("error storing account; value has changed since last read")),
|
|
}
|
|
},
|
|
"ok/old-nil": func(t *testing.T) test {
|
|
acc, err := newAcc()
|
|
assert.FatalError(t, err)
|
|
b, err := json.Marshal(acc)
|
|
assert.FatalError(t, err)
|
|
return test{
|
|
acc: acc,
|
|
old: nil,
|
|
db: &db.MockNoSQLDB{
|
|
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
|
|
assert.Equals(t, old, nil)
|
|
assert.Equals(t, b, newval)
|
|
assert.Equals(t, bucket, accountTable)
|
|
assert.Equals(t, []byte(acc.ID), key)
|
|
return nil, true, nil
|
|
},
|
|
},
|
|
}
|
|
},
|
|
"ok/old-not-nil": func(t *testing.T) test {
|
|
oldAcc, err := newAcc()
|
|
assert.FatalError(t, err)
|
|
acc, err := newAcc()
|
|
assert.FatalError(t, err)
|
|
|
|
oldb, err := json.Marshal(oldAcc)
|
|
assert.FatalError(t, err)
|
|
b, err := json.Marshal(acc)
|
|
assert.FatalError(t, err)
|
|
return test{
|
|
acc: acc,
|
|
old: oldAcc,
|
|
db: &db.MockNoSQLDB{
|
|
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
|
|
assert.Equals(t, old, oldb)
|
|
assert.Equals(t, newval, b)
|
|
assert.Equals(t, bucket, accountTable)
|
|
assert.Equals(t, []byte(acc.ID), key)
|
|
return []byte("foo"), true, nil
|
|
},
|
|
},
|
|
}
|
|
},
|
|
}
|
|
for name, run := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
tc := run(t)
|
|
if err := tc.acc.save(tc.db, tc.old); err != nil {
|
|
if assert.NotNil(t, tc.err) {
|
|
ae, ok := err.(*Error)
|
|
assert.True(t, ok)
|
|
assert.HasPrefix(t, ae.Error(), tc.err.Error())
|
|
assert.Equals(t, ae.StatusCode(), tc.err.StatusCode())
|
|
assert.Equals(t, ae.Type, tc.err.Type)
|
|
}
|
|
} else {
|
|
assert.Nil(t, tc.err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAccountSaveNew(t *testing.T) {
|
|
type test struct {
|
|
acc *account
|
|
db nosql.DB
|
|
err *Error
|
|
}
|
|
tests := map[string]func(t *testing.T) test{
|
|
"fail/keyToID-error": func(t *testing.T) test {
|
|
acc, err := newAcc()
|
|
assert.FatalError(t, err)
|
|
acc.Key.Key = "foo"
|
|
return test{
|
|
acc: acc,
|
|
err: ServerInternalErr(errors.New("error generating jwk thumbprint: square/go-jose: unknown key type 'string'")),
|
|
}
|
|
},
|
|
"fail/swap-error": func(t *testing.T) test {
|
|
acc, err := newAcc()
|
|
assert.FatalError(t, err)
|
|
kid, err := keyToID(acc.Key)
|
|
assert.FatalError(t, err)
|
|
return test{
|
|
acc: acc,
|
|
db: &db.MockNoSQLDB{
|
|
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
|
|
assert.Equals(t, bucket, accountByKeyIDTable)
|
|
assert.Equals(t, key, []byte(kid))
|
|
assert.Equals(t, old, nil)
|
|
assert.Equals(t, newval, []byte(acc.ID))
|
|
return nil, false, errors.New("force")
|
|
},
|
|
},
|
|
err: ServerInternalErr(errors.New("error setting key-id to account-id index: force")),
|
|
}
|
|
},
|
|
"fail/swap-false": func(t *testing.T) test {
|
|
acc, err := newAcc()
|
|
assert.FatalError(t, err)
|
|
kid, err := keyToID(acc.Key)
|
|
assert.FatalError(t, err)
|
|
return test{
|
|
acc: acc,
|
|
db: &db.MockNoSQLDB{
|
|
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
|
|
assert.Equals(t, bucket, accountByKeyIDTable)
|
|
assert.Equals(t, key, []byte(kid))
|
|
assert.Equals(t, old, nil)
|
|
assert.Equals(t, newval, []byte(acc.ID))
|
|
return nil, false, nil
|
|
},
|
|
},
|
|
err: ServerInternalErr(errors.New("key-id to account-id index already exists")),
|
|
}
|
|
},
|
|
"fail/save-error": func(t *testing.T) test {
|
|
acc, err := newAcc()
|
|
assert.FatalError(t, err)
|
|
kid, err := keyToID(acc.Key)
|
|
assert.FatalError(t, err)
|
|
b, err := json.Marshal(acc)
|
|
assert.FatalError(t, err)
|
|
count := 0
|
|
return test{
|
|
acc: acc,
|
|
db: &db.MockNoSQLDB{
|
|
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
|
|
if count == 0 {
|
|
assert.Equals(t, bucket, accountByKeyIDTable)
|
|
assert.Equals(t, key, []byte(kid))
|
|
assert.Equals(t, old, nil)
|
|
assert.Equals(t, newval, []byte(acc.ID))
|
|
count++
|
|
return nil, true, nil
|
|
}
|
|
assert.Equals(t, bucket, accountTable)
|
|
assert.Equals(t, key, []byte(acc.ID))
|
|
assert.Equals(t, old, nil)
|
|
assert.Equals(t, newval, b)
|
|
return nil, false, errors.New("force")
|
|
},
|
|
MDel: func(bucket, key []byte) error {
|
|
assert.Equals(t, bucket, accountByKeyIDTable)
|
|
assert.Equals(t, key, []byte(kid))
|
|
return nil
|
|
},
|
|
},
|
|
err: ServerInternalErr(errors.New("error storing account: force")),
|
|
}
|
|
},
|
|
"ok": func(t *testing.T) test {
|
|
acc, err := newAcc()
|
|
assert.FatalError(t, err)
|
|
kid, err := keyToID(acc.Key)
|
|
assert.FatalError(t, err)
|
|
b, err := json.Marshal(acc)
|
|
assert.FatalError(t, err)
|
|
count := 0
|
|
return test{
|
|
acc: acc,
|
|
db: &db.MockNoSQLDB{
|
|
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
|
|
if count == 0 {
|
|
assert.Equals(t, bucket, accountByKeyIDTable)
|
|
assert.Equals(t, key, []byte(kid))
|
|
assert.Equals(t, old, nil)
|
|
assert.Equals(t, newval, []byte(acc.ID))
|
|
count++
|
|
return nil, true, nil
|
|
}
|
|
assert.Equals(t, bucket, accountTable)
|
|
assert.Equals(t, key, []byte(acc.ID))
|
|
assert.Equals(t, old, nil)
|
|
assert.Equals(t, newval, b)
|
|
return nil, true, nil
|
|
},
|
|
},
|
|
}
|
|
},
|
|
}
|
|
for name, run := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
tc := run(t)
|
|
if err := tc.acc.saveNew(tc.db); err != nil {
|
|
if assert.NotNil(t, tc.err) {
|
|
ae, ok := err.(*Error)
|
|
assert.True(t, ok)
|
|
assert.HasPrefix(t, ae.Error(), tc.err.Error())
|
|
assert.Equals(t, ae.StatusCode(), tc.err.StatusCode())
|
|
assert.Equals(t, ae.Type, tc.err.Type)
|
|
}
|
|
} else {
|
|
assert.Nil(t, tc.err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAccountUpdate(t *testing.T) {
|
|
type test struct {
|
|
acc *account
|
|
contact []string
|
|
db nosql.DB
|
|
res []byte
|
|
err *Error
|
|
}
|
|
contact := []string{"foo", "bar"}
|
|
tests := map[string]func(t *testing.T) test{
|
|
"fail/save-error": func(t *testing.T) test {
|
|
acc, err := newAcc()
|
|
assert.FatalError(t, err)
|
|
oldb, err := json.Marshal(acc)
|
|
assert.FatalError(t, err)
|
|
|
|
_acc := *acc
|
|
clone := &_acc
|
|
clone.Contact = contact
|
|
b, err := json.Marshal(clone)
|
|
assert.FatalError(t, err)
|
|
return test{
|
|
acc: acc,
|
|
contact: contact,
|
|
db: &db.MockNoSQLDB{
|
|
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
|
|
assert.Equals(t, bucket, accountTable)
|
|
assert.Equals(t, key, []byte(acc.ID))
|
|
assert.Equals(t, old, oldb)
|
|
assert.Equals(t, newval, b)
|
|
return nil, false, errors.New("force")
|
|
},
|
|
},
|
|
err: ServerInternalErr(errors.New("error storing account: force")),
|
|
}
|
|
},
|
|
"ok": func(t *testing.T) test {
|
|
acc, err := newAcc()
|
|
assert.FatalError(t, err)
|
|
oldb, err := json.Marshal(acc)
|
|
assert.FatalError(t, err)
|
|
|
|
_acc := *acc
|
|
clone := &_acc
|
|
clone.Contact = contact
|
|
b, err := json.Marshal(clone)
|
|
assert.FatalError(t, err)
|
|
return test{
|
|
acc: acc,
|
|
contact: contact,
|
|
res: b,
|
|
db: &db.MockNoSQLDB{
|
|
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
|
|
assert.Equals(t, bucket, accountTable)
|
|
assert.Equals(t, key, []byte(acc.ID))
|
|
assert.Equals(t, old, oldb)
|
|
assert.Equals(t, newval, b)
|
|
return nil, true, nil
|
|
},
|
|
},
|
|
}
|
|
},
|
|
}
|
|
for name, run := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
tc := run(t)
|
|
acc, err := tc.acc.update(tc.db, tc.contact)
|
|
if err != nil {
|
|
if assert.NotNil(t, tc.err) {
|
|
ae, ok := err.(*Error)
|
|
assert.True(t, ok)
|
|
assert.HasPrefix(t, ae.Error(), tc.err.Error())
|
|
assert.Equals(t, ae.StatusCode(), tc.err.StatusCode())
|
|
assert.Equals(t, ae.Type, tc.err.Type)
|
|
}
|
|
} else {
|
|
if assert.Nil(t, tc.err) {
|
|
b, err := json.Marshal(acc)
|
|
assert.FatalError(t, err)
|
|
assert.Equals(t, b, tc.res)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAccountDeactivate(t *testing.T) {
|
|
type test struct {
|
|
acc *account
|
|
db nosql.DB
|
|
err *Error
|
|
}
|
|
tests := map[string]func(t *testing.T) test{
|
|
"fail/save-error": func(t *testing.T) test {
|
|
acc, err := newAcc()
|
|
assert.FatalError(t, err)
|
|
oldb, err := json.Marshal(acc)
|
|
assert.FatalError(t, err)
|
|
|
|
return test{
|
|
acc: acc,
|
|
db: &db.MockNoSQLDB{
|
|
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
|
|
assert.Equals(t, bucket, accountTable)
|
|
assert.Equals(t, key, []byte(acc.ID))
|
|
assert.Equals(t, old, oldb)
|
|
return nil, false, errors.New("force")
|
|
},
|
|
},
|
|
err: ServerInternalErr(errors.New("error storing account: force")),
|
|
}
|
|
},
|
|
"ok": func(t *testing.T) test {
|
|
acc, err := newAcc()
|
|
assert.FatalError(t, err)
|
|
oldb, err := json.Marshal(acc)
|
|
assert.FatalError(t, err)
|
|
|
|
return test{
|
|
acc: acc,
|
|
db: &db.MockNoSQLDB{
|
|
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
|
|
assert.Equals(t, bucket, accountTable)
|
|
assert.Equals(t, key, []byte(acc.ID))
|
|
assert.Equals(t, old, oldb)
|
|
return nil, true, nil
|
|
},
|
|
},
|
|
}
|
|
},
|
|
}
|
|
for name, run := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
tc := run(t)
|
|
acc, err := tc.acc.deactivate(tc.db)
|
|
if err != nil {
|
|
if assert.NotNil(t, tc.err) {
|
|
ae, ok := err.(*Error)
|
|
assert.True(t, ok)
|
|
assert.HasPrefix(t, ae.Error(), tc.err.Error())
|
|
assert.Equals(t, ae.StatusCode(), tc.err.StatusCode())
|
|
assert.Equals(t, ae.Type, tc.err.Type)
|
|
}
|
|
} else {
|
|
if assert.Nil(t, tc.err) {
|
|
assert.Equals(t, acc.ID, tc.acc.ID)
|
|
assert.Equals(t, acc.Contact, tc.acc.Contact)
|
|
assert.Equals(t, acc.Status, StatusDeactivated)
|
|
assert.Equals(t, acc.Key.KeyID, tc.acc.Key.KeyID)
|
|
assert.Equals(t, acc.Created, tc.acc.Created)
|
|
|
|
assert.True(t, acc.Deactivated.Before(time.Now().Add(time.Minute)))
|
|
assert.True(t, acc.Deactivated.After(time.Now().Add(-time.Minute)))
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNewAccount(t *testing.T) {
|
|
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
|
|
assert.FatalError(t, err)
|
|
kid, err := keyToID(jwk)
|
|
assert.FatalError(t, err)
|
|
ops := AccountOptions{
|
|
Key: jwk,
|
|
Contact: []string{"foo", "bar"},
|
|
}
|
|
type test struct {
|
|
ops AccountOptions
|
|
db nosql.DB
|
|
err *Error
|
|
id *string
|
|
}
|
|
tests := map[string]func(t *testing.T) test{
|
|
"fail/store-error": func(t *testing.T) test {
|
|
return test{
|
|
ops: ops,
|
|
db: &db.MockNoSQLDB{
|
|
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
|
|
return nil, false, errors.New("force")
|
|
},
|
|
},
|
|
err: ServerInternalErr(errors.New("error setting key-id to account-id index: force")),
|
|
}
|
|
},
|
|
"ok": func(t *testing.T) test {
|
|
var _id string
|
|
id := &_id
|
|
count := 0
|
|
return test{
|
|
ops: ops,
|
|
db: &db.MockNoSQLDB{
|
|
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
|
|
switch count {
|
|
case 0:
|
|
assert.Equals(t, bucket, accountByKeyIDTable)
|
|
assert.Equals(t, key, []byte(kid))
|
|
case 1:
|
|
assert.Equals(t, bucket, accountTable)
|
|
*id = string(key)
|
|
}
|
|
count++
|
|
return nil, true, nil
|
|
},
|
|
},
|
|
id: id,
|
|
}
|
|
},
|
|
}
|
|
for name, run := range tests {
|
|
tc := run(t)
|
|
t.Run(name, func(t *testing.T) {
|
|
acc, err := newAccount(tc.db, tc.ops)
|
|
if err != nil {
|
|
if assert.NotNil(t, tc.err) {
|
|
ae, ok := err.(*Error)
|
|
assert.True(t, ok)
|
|
assert.HasPrefix(t, ae.Error(), tc.err.Error())
|
|
assert.Equals(t, ae.StatusCode(), tc.err.StatusCode())
|
|
assert.Equals(t, ae.Type, tc.err.Type)
|
|
}
|
|
} else {
|
|
if assert.Nil(t, tc.err) {
|
|
assert.Equals(t, acc.ID, *tc.id)
|
|
assert.Equals(t, acc.Status, StatusValid)
|
|
assert.Equals(t, acc.Contact, ops.Contact)
|
|
assert.Equals(t, acc.Key.KeyID, ops.Key.KeyID)
|
|
|
|
assert.True(t, acc.Deactivated.IsZero())
|
|
|
|
assert.True(t, acc.Created.Before(time.Now().UTC().Add(time.Minute)))
|
|
assert.True(t, acc.Created.After(time.Now().UTC().Add(-1*time.Minute)))
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|