[acme db interface] more unit tests

This commit is contained in:
max furman 2021-03-12 00:16:48 -08:00
parent 20b9785d20
commit 8d2ebcfd49
5 changed files with 316 additions and 121 deletions

View file

@ -19,7 +19,7 @@ import (
"go.step.sm/crypto/pemutil"
)
func TestNewOrderRequestValidate(t *testing.T) {
func TestNewOrderRequest_Validate(t *testing.T) {
type test struct {
nor *NewOrderRequest
nbf, naf time.Time
@ -148,12 +148,12 @@ func TestFinalizeRequestValidate(t *testing.T) {
}
func TestHandler_GetOrder(t *testing.T) {
expiry := time.Now().UTC().Add(6 * time.Hour)
nbf := time.Now().UTC()
naf := time.Now().UTC().Add(24 * time.Hour)
now := clock.Now()
nbf := now
naf := now.Add(24 * time.Hour)
expiry := now.Add(-time.Hour)
o := acme.Order{
ID: "orderID",
Expires: expiry,
NotBefore: nbf,
NotAfter: naf,
Identifiers: []acme.Identifier{
@ -166,8 +166,15 @@ func TestHandler_GetOrder(t *testing.T) {
Value: "*.smallstep.com",
},
},
Status: "pending",
AuthorizationURLs: []string{"foo", "bar"},
Expires: expiry,
Status: acme.StatusInvalid,
Error: acme.NewError(acme.ErrorMalformedType, "order has expired"),
AuthorizationURLs: []string{
"https://test.ca.smallstep.com/acme/test@acme-provisioner.com/authz/foo",
"https://test.ca.smallstep.com/acme/test@acme-provisioner.com/authz/bar",
"https://test.ca.smallstep.com/acme/test@acme-provisioner.com/authz/baz",
},
FinalizeURL: "https://test.ca.smallstep.com/acme/test@acme-provisioner.com/order/orderID/finalize",
}
// Request with chi context
@ -181,7 +188,6 @@ func TestHandler_GetOrder(t *testing.T) {
type test struct {
db acme.DB
linker Linker
ctx context.Context
statusCode int
err *acme.Error
@ -203,8 +209,27 @@ func TestHandler_GetOrder(t *testing.T) {
err: acme.NewError(acme.ErrorAccountDoesNotExistType, "account does not exist"),
}
},
"fail/getOrder-error": func(t *testing.T) test {
acc := &acme.Account{ID: "accID"}
"fail/no-provisioner": func(t *testing.T) test {
acc := &acme.Account{ID: "accountID"}
ctx := context.WithValue(context.Background(), accContextKey, acc)
return test{
ctx: ctx,
statusCode: 500,
err: acme.NewErrorISE("provisioner does not exist"),
}
},
"fail/nil-provisioner": func(t *testing.T) test {
acc := &acme.Account{ID: "accountID"}
ctx := context.WithValue(context.Background(), provisionerContextKey, nil)
ctx = context.WithValue(ctx, accContextKey, acc)
return test{
ctx: ctx,
statusCode: 500,
err: acme.NewErrorISE("provisioner does not exist"),
}
},
"fail/db.GetOrder-error": func(t *testing.T) test {
acc := &acme.Account{ID: "accountID"}
ctx := context.WithValue(context.Background(), provisionerContextKey, prov)
ctx = context.WithValue(ctx, accContextKey, acc)
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
@ -217,8 +242,64 @@ func TestHandler_GetOrder(t *testing.T) {
err: acme.NewErrorISE("force"),
}
},
"fail/account-id-mismatch": func(t *testing.T) test {
acc := &acme.Account{ID: "accountID"}
ctx := context.WithValue(context.Background(), provisionerContextKey, prov)
ctx = context.WithValue(ctx, accContextKey, acc)
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
return test{
db: &acme.MockDB{
MockGetOrder: func(ctx context.Context, id string) (*acme.Order, error) {
return &acme.Order{AccountID: "foo"}, nil
},
},
ctx: ctx,
statusCode: 401,
err: acme.NewError(acme.ErrorUnauthorizedType, "account id mismatch"),
}
},
"fail/provisioner-id-mismatch": func(t *testing.T) test {
acc := &acme.Account{ID: "accountID"}
ctx := context.WithValue(context.Background(), provisionerContextKey, prov)
ctx = context.WithValue(ctx, accContextKey, acc)
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
return test{
db: &acme.MockDB{
MockGetOrder: func(ctx context.Context, id string) (*acme.Order, error) {
return &acme.Order{AccountID: "accountID", ProvisionerID: "bar"}, nil
},
},
ctx: ctx,
statusCode: 401,
err: acme.NewError(acme.ErrorUnauthorizedType, "provisioner id mismatch"),
}
},
"fail/order-update-error": func(t *testing.T) test {
acc := &acme.Account{ID: "accountID"}
ctx := context.WithValue(context.Background(), provisionerContextKey, prov)
ctx = context.WithValue(ctx, accContextKey, acc)
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
return test{
db: &acme.MockDB{
MockGetOrder: func(ctx context.Context, id string) (*acme.Order, error) {
return &acme.Order{
AccountID: "accountID",
ProvisionerID: "acme/test@acme-provisioner.com",
Expires: clock.Now().Add(-time.Hour),
Status: acme.StatusReady,
}, nil
},
MockUpdateOrder: func(ctx context.Context, o *acme.Order) error {
return acme.NewErrorISE("force")
},
},
ctx: ctx,
statusCode: 500,
err: acme.NewErrorISE("force"),
}
},
"ok": func(t *testing.T) test {
acc := &acme.Account{ID: "accID"}
acc := &acme.Account{ID: "accountID"}
ctx := context.WithValue(context.Background(), provisionerContextKey, prov)
ctx = context.WithValue(ctx, accContextKey, acc)
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
@ -226,11 +307,31 @@ func TestHandler_GetOrder(t *testing.T) {
return test{
db: &acme.MockDB{
MockGetOrder: func(ctx context.Context, id string) (*acme.Order, error) {
assert.Equals(t, id, o.ID)
return &o, nil
return &acme.Order{
ID: "orderID",
AccountID: "accountID",
ProvisionerID: "acme/test@acme-provisioner.com",
Expires: expiry,
Status: acme.StatusReady,
AuthorizationIDs: []string{"foo", "bar", "baz"},
NotBefore: nbf,
NotAfter: naf,
Identifiers: []acme.Identifier{
{
Type: "dns",
Value: "example.com",
},
{
Type: "dns",
Value: "*.smallstep.com",
},
},
}, nil
},
MockUpdateOrder: func(ctx context.Context, o *acme.Order) error {
return nil
},
},
linker: NewLinker("dns", "acme"),
ctx: ctx,
statusCode: 200,
}
@ -239,7 +340,7 @@ func TestHandler_GetOrder(t *testing.T) {
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
h := &Handler{linker: tc.linker, db: tc.db}
h := &Handler{linker: NewLinker("dns", "acme"), db: tc.db}
req := httptest.NewRequest("GET", url, nil)
req = req.WithContext(tc.ctx)
w := httptest.NewRecorder()
@ -264,6 +365,7 @@ func TestHandler_GetOrder(t *testing.T) {
} else {
expB, err := json.Marshal(o)
assert.FatalError(t, err)
assert.Equals(t, bytes.TrimSpace(body), expB)
assert.Equals(t, res.Header["Location"], []string{url})
assert.Equals(t, res.Header["Content-Type"], []string{"application/json"})
@ -272,7 +374,7 @@ func TestHandler_GetOrder(t *testing.T) {
}
}
func TestHandlerNewOrder(t *testing.T) {
func TestHandler_NewOrder(t *testing.T) {
expiry := time.Now().UTC().Add(6 * time.Hour)
nbf := time.Now().UTC().Add(5 * time.Hour)
naf := nbf.Add(17 * time.Hour)
@ -297,7 +399,6 @@ func TestHandlerNewOrder(t *testing.T) {
type test struct {
db acme.DB
linker Linker
ctx context.Context
statusCode int
err *acme.Error
@ -319,14 +420,23 @@ func TestHandlerNewOrder(t *testing.T) {
err: acme.NewError(acme.ErrorAccountDoesNotExistType, "account does not exist"),
}
},
"fail/no-payload": func(t *testing.T) test {
"fail/no-provisioner": func(t *testing.T) test {
acc := &acme.Account{ID: "accID"}
ctx := context.WithValue(context.Background(), provisionerContextKey, prov)
ctx := context.WithValue(context.Background(), accContextKey, acc)
return test{
ctx: ctx,
statusCode: 500,
err: acme.NewErrorISE("provisioner expected in request context"),
}
},
"fail/nil-provisioner": func(t *testing.T) test {
acc := &acme.Account{ID: "accID"}
ctx := context.WithValue(context.Background(), provisionerContextKey, nil)
ctx = context.WithValue(ctx, accContextKey, acc)
return test{
ctx: ctx,
statusCode: 500,
err: acme.NewErrorISE("payload expected in request context"),
err: acme.NewErrorISE("provisioner expected in request context"),
}
},
"fail/nil-payload": func(t *testing.T) test {
@ -408,10 +518,10 @@ func TestHandlerNewOrder(t *testing.T) {
return test{
db: &acme.MockDB{
MockCreateOrder: func(ctx context.Context, o *acme.Order) error {
o.ID = "orderID"
return nil
},
},
linker: NewLinker("dns", "acme"),
ctx: ctx,
statusCode: 201,
}
@ -436,7 +546,6 @@ func TestHandlerNewOrder(t *testing.T) {
return nil
},
},
linker: NewLinker("dns", "acme"),
ctx: ctx,
statusCode: 201,
}
@ -445,7 +554,7 @@ func TestHandlerNewOrder(t *testing.T) {
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
h := &Handler{linker: tc.linker, db: tc.db}
h := &Handler{linker: NewLinker("dns", "prefix"), db: tc.db}
req := httptest.NewRequest("GET", url, nil)
req = req.WithContext(tc.ctx)
w := httptest.NewRecorder()
@ -481,26 +590,33 @@ func TestHandlerNewOrder(t *testing.T) {
}
func TestHandler_FinalizeOrder(t *testing.T) {
expiry := time.Now().UTC().Add(6 * time.Hour)
nbf := time.Now().UTC().Add(5 * time.Hour)
naf := nbf.Add(17 * time.Hour)
now := clock.Now()
nbf := now
naf := now.Add(24 * time.Hour)
o := acme.Order{
ID: "orderID",
Expires: expiry,
NotBefore: nbf,
NotAfter: naf,
Identifiers: []acme.Identifier{
{Type: "dns", Value: "example.com"},
{Type: "dns", Value: "bar.com"},
{
Type: "dns",
Value: "example.com",
},
{
Type: "dns",
Value: "*.smallstep.com",
},
},
Status: "valid",
AuthorizationURLs: []string{"foo", "bar"},
CertificateURL: "https://ca.smallstep.com/acme/certificate/certID",
Expires: naf,
Status: acme.StatusValid,
AuthorizationURLs: []string{
"https://test.ca.smallstep.com/acme/test@acme-provisioner.com/authz/foo",
"https://test.ca.smallstep.com/acme/test@acme-provisioner.com/authz/bar",
"https://test.ca.smallstep.com/acme/test@acme-provisioner.com/authz/baz",
},
FinalizeURL: "https://test.ca.smallstep.com/acme/test@acme-provisioner.com/order/orderID/finalize",
CertificateURL: "https://test.ca.smallstep.com/acme/test@acme-provisioner.com/certificate/certID",
}
_csr, err := pemutil.Read("../../authority/testdata/certs/foo.csr")
assert.FatalError(t, err)
csr, ok := _csr.(*x509.CertificateRequest)
assert.Fatal(t, ok)
// Request with chi context
chiCtx := chi.NewRouteContext()
@ -508,12 +624,22 @@ func TestHandler_FinalizeOrder(t *testing.T) {
prov := newProv()
provName := url.PathEscape(prov.GetName())
baseURL := &url.URL{Scheme: "https", Host: "test.ca.smallstep.com"}
url := fmt.Sprintf("%s/acme/%s/order/%s/finalize",
url := fmt.Sprintf("%s/acme/%s/order/%s",
baseURL.String(), provName, o.ID)
_csr, err := pemutil.Read("../../authority/testdata/certs/foo.csr")
assert.FatalError(t, err)
csr, ok := _csr.(*x509.CertificateRequest)
assert.Fatal(t, ok)
nor := &FinalizeRequest{
CSR: base64.RawURLEncoding.EncodeToString(csr.Raw),
}
payloadBytes, err := json.Marshal(nor)
assert.FatalError(t, err)
type test struct {
db acme.DB
linker Linker
ctx context.Context
statusCode int
err *acme.Error
@ -521,7 +647,6 @@ func TestHandler_FinalizeOrder(t *testing.T) {
var tests = map[string]func(t *testing.T) test{
"fail/no-account": func(t *testing.T) test {
return test{
db: &acme.MockDB{},
ctx: context.WithValue(context.Background(), provisionerContextKey, prov),
statusCode: 400,
err: acme.NewError(acme.ErrorAccountDoesNotExistType, "account does not exist"),
@ -531,31 +656,49 @@ func TestHandler_FinalizeOrder(t *testing.T) {
ctx := context.WithValue(context.Background(), provisionerContextKey, prov)
ctx = context.WithValue(ctx, accContextKey, nil)
return test{
db: &acme.MockDB{},
ctx: ctx,
statusCode: 400,
err: acme.NewError(acme.ErrorAccountDoesNotExistType, "account does not exist"),
}
},
"fail/no-payload": func(t *testing.T) test {
acc := &acme.Account{ID: "accID"}
ctx := context.WithValue(context.Background(), provisionerContextKey, prov)
"fail/no-provisioner": func(t *testing.T) test {
acc := &acme.Account{ID: "accountID"}
ctx := context.WithValue(context.Background(), accContextKey, acc)
return test{
ctx: ctx,
statusCode: 500,
err: acme.NewErrorISE("provisioner does not exist"),
}
},
"fail/nil-provisioner": func(t *testing.T) test {
acc := &acme.Account{ID: "accountID"}
ctx := context.WithValue(context.Background(), provisionerContextKey, nil)
ctx = context.WithValue(ctx, accContextKey, acc)
return test{
ctx: ctx,
statusCode: 500,
err: acme.NewErrorISE("payload expected in request context"),
err: acme.NewErrorISE("provisioner does not exist"),
}
},
"fail/no-payload": func(t *testing.T) test {
acc := &acme.Account{ID: "accountID"}
ctx := context.WithValue(context.Background(), accContextKey, acc)
ctx = context.WithValue(ctx, provisionerContextKey, prov)
return test{
ctx: ctx,
statusCode: 500,
err: acme.NewErrorISE("payload does not exist"),
}
},
"fail/nil-payload": func(t *testing.T) test {
acc := &acme.Account{ID: "accID"}
acc := &acme.Account{ID: "accountID"}
ctx := context.WithValue(context.Background(), provisionerContextKey, prov)
ctx = context.WithValue(ctx, accContextKey, acc)
ctx = context.WithValue(ctx, payloadContextKey, nil)
return test{
ctx: ctx,
statusCode: 500,
err: acme.NewErrorISE("payload expected in request context"),
err: acme.NewErrorISE("paylod does not exist"),
}
},
"fail/unmarshal-payload-error": func(t *testing.T) test {
@ -583,62 +726,112 @@ func TestHandler_FinalizeOrder(t *testing.T) {
err: acme.NewError(acme.ErrorMalformedType, "unable to parse csr: asn1: syntax error: sequence truncated"),
}
},
"fail/FinalizeOrder-error": func(t *testing.T) test {
acc := &acme.Account{ID: "accID"}
nor := &FinalizeRequest{
CSR: base64.RawURLEncoding.EncodeToString(csr.Raw),
}
b, err := json.Marshal(nor)
assert.FatalError(t, err)
"fail/db.GetOrder-error": func(t *testing.T) test {
acc := &acme.Account{ID: "accountID"}
ctx := context.WithValue(context.Background(), provisionerContextKey, prov)
ctx = context.WithValue(ctx, accContextKey, acc)
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b})
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: payloadBytes})
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
return test{
db: &acme.MockDB{
MockUpdateOrder: func(ctx context.Context, o *acme.Order) error {
/*
p, err := acme.ProvisionerFromContext(ctx)
assert.FatalError(t, err)
assert.Equals(t, p, prov)
assert.Equals(t, accID, acc.ID)
assert.Equals(t, id, o.ID)
assert.Equals(t, incsr.Raw, csr.Raw)
*/
return acme.NewError(acme.ErrorMalformedType, "force")
MockError: acme.NewErrorISE("force"),
},
ctx: ctx,
statusCode: 500,
err: acme.NewErrorISE("force"),
}
},
"fail/account-id-mismatch": func(t *testing.T) test {
acc := &acme.Account{ID: "accountID"}
ctx := context.WithValue(context.Background(), provisionerContextKey, prov)
ctx = context.WithValue(ctx, accContextKey, acc)
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: payloadBytes})
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
return test{
db: &acme.MockDB{
MockGetOrder: func(ctx context.Context, id string) (*acme.Order, error) {
return &acme.Order{AccountID: "foo"}, nil
},
},
ctx: ctx,
statusCode: 400,
err: acme.NewError(acme.ErrorMalformedType, "force"),
statusCode: 401,
err: acme.NewError(acme.ErrorUnauthorizedType, "account id mismatch"),
}
},
"fail/provisioner-id-mismatch": func(t *testing.T) test {
acc := &acme.Account{ID: "accountID"}
ctx := context.WithValue(context.Background(), provisionerContextKey, prov)
ctx = context.WithValue(ctx, accContextKey, acc)
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: payloadBytes})
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
return test{
db: &acme.MockDB{
MockGetOrder: func(ctx context.Context, id string) (*acme.Order, error) {
return &acme.Order{AccountID: "accountID", ProvisionerID: "bar"}, nil
},
},
ctx: ctx,
statusCode: 401,
err: acme.NewError(acme.ErrorUnauthorizedType, "provisioner id mismatch"),
}
},
"fail/order-finalize-error": func(t *testing.T) test {
acc := &acme.Account{ID: "accountID"}
ctx := context.WithValue(context.Background(), provisionerContextKey, prov)
ctx = context.WithValue(ctx, accContextKey, acc)
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: payloadBytes})
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
return test{
db: &acme.MockDB{
MockGetOrder: func(ctx context.Context, id string) (*acme.Order, error) {
return &acme.Order{
AccountID: "accountID",
ProvisionerID: "acme/test@acme-provisioner.com",
Expires: clock.Now().Add(-time.Hour),
Status: acme.StatusReady,
}, nil
},
MockUpdateOrder: func(ctx context.Context, o *acme.Order) error {
return acme.NewErrorISE("force")
},
},
ctx: ctx,
statusCode: 500,
err: acme.NewErrorISE("force"),
}
},
"ok": func(t *testing.T) test {
acc := &acme.Account{ID: "accID"}
nor := &FinalizeRequest{
CSR: base64.RawURLEncoding.EncodeToString(csr.Raw),
}
b, err := json.Marshal(nor)
assert.FatalError(t, err)
acc := &acme.Account{ID: "accountID"}
ctx := context.WithValue(context.Background(), provisionerContextKey, prov)
ctx = context.WithValue(ctx, accContextKey, acc)
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b})
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: payloadBytes})
ctx = context.WithValue(ctx, baseURLContextKey, baseURL)
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
return test{
linker: NewLinker("dns", "acme"),
db: &acme.MockDB{
MockUpdateOrder: func(ctx context.Context, o *acme.Order) error {
/*
p, err := acme.ProvisionerFromContext(ctx)
assert.FatalError(t, err)
assert.Equals(t, p, prov)
assert.Equals(t, accID, acc.ID)
assert.Equals(t, id, o.ID)
assert.Equals(t, incsr.Raw, csr.Raw)
return &o, nil
*/
return nil
MockGetOrder: func(ctx context.Context, id string) (*acme.Order, error) {
return &acme.Order{
ID: "orderID",
AccountID: "accountID",
ProvisionerID: "acme/test@acme-provisioner.com",
Expires: naf,
Status: acme.StatusValid,
AuthorizationIDs: []string{"foo", "bar", "baz"},
NotBefore: nbf,
NotAfter: naf,
Identifiers: []acme.Identifier{
{
Type: "dns",
Value: "example.com",
},
{
Type: "dns",
Value: "*.smallstep.com",
},
},
CertificateID: "certID",
}, nil
},
},
ctx: ctx,
@ -649,7 +842,7 @@ func TestHandler_FinalizeOrder(t *testing.T) {
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
h := &Handler{linker: tc.linker, db: tc.db}
h := &Handler{linker: NewLinker("dns", "acme"), db: tc.db}
req := httptest.NewRequest("GET", url, nil)
req = req.WithContext(tc.ctx)
w := httptest.NewRecorder()
@ -674,10 +867,12 @@ func TestHandler_FinalizeOrder(t *testing.T) {
} else {
expB, err := json.Marshal(o)
assert.FatalError(t, err)
ro := new(acme.Order)
err = json.Unmarshal(body, ro)
assert.Equals(t, bytes.TrimSpace(body), expB)
assert.Equals(t, res.Header["Location"],
[]string{fmt.Sprintf("%s/acme/%s/order/%s",
baseURL, provName, o.ID)})
assert.Equals(t, res.Header["Location"], []string{url})
assert.Equals(t, res.Header["Content-Type"], []string{"application/json"})
}
})