package acme

import (
	"context"
	"encoding/json"
	"fmt"
	"net/url"
	"testing"
	"time"

	"github.com/pkg/errors"
	"github.com/smallstep/assert"
	"github.com/smallstep/certificates/db"
	"github.com/smallstep/nosql/database"
	"go.step.sm/crypto/jose"
)

func TestAuthorityGetLink(t *testing.T) {
	auth, err := NewAuthority(new(db.MockNoSQLDB), "ca.smallstep.com", "acme", nil)
	assert.FatalError(t, err)
	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 {
		auth   *Authority
		typ    Link
		abs    bool
		inputs []string
		res    string
	}
	tests := map[string]func(t *testing.T) test{
		"ok/new-account/abs": func(t *testing.T) test {
			return test{
				auth: auth,
				typ:  NewAccountLink,
				abs:  true,
				res:  fmt.Sprintf("%s/acme/%s/new-account", baseURL.String(), provName),
			}
		},
		"ok/new-account/no-abs": func(t *testing.T) test {
			return test{
				auth: auth,
				typ:  NewAccountLink,
				abs:  false,
				res:  fmt.Sprintf("/%s/new-account", provName),
			}
		},
		"ok/order/abs": func(t *testing.T) test {
			return test{
				auth:   auth,
				typ:    OrderLink,
				abs:    true,
				inputs: []string{"foo"},
				res:    fmt.Sprintf("%s/acme/%s/order/foo", baseURL.String(), provName),
			}
		},
		"ok/order/no-abs": func(t *testing.T) test {
			return test{
				auth:   auth,
				typ:    OrderLink,
				abs:    false,
				inputs: []string{"foo"},
				res:    fmt.Sprintf("/%s/order/foo", provName),
			}
		},
	}
	for name, run := range tests {
		t.Run(name, func(t *testing.T) {
			tc := run(t)
			link := tc.auth.GetLink(ctx, tc.typ, tc.abs, tc.inputs...)
			assert.Equals(t, tc.res, link)
		})
	}
}

func TestAuthorityGetDirectory(t *testing.T) {
	auth, err := NewAuthority(new(db.MockNoSQLDB), "ca.smallstep.com", "acme", nil)
	assert.FatalError(t, err)

	prov := newProv()
	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 {
		ctx context.Context
		err *Error
	}
	tests := map[string]func(t *testing.T) test{
		"ok/empty-provisioner": func(t *testing.T) test {
			return test{
				ctx: context.Background(),
			}
		},
		"ok/no-baseURL": func(t *testing.T) test {
			return test{
				ctx: context.WithValue(context.Background(), ProvisionerContextKey, prov),
			}
		},
		"ok/baseURL": func(t *testing.T) test {
			return test{
				ctx: ctx,
			}
		},
	}
	for name, run := range tests {
		t.Run(name, func(t *testing.T) {
			tc := run(t)
			if dir, err := auth.GetDirectory(tc.ctx); 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) {
					bu := BaseURLFromContext(tc.ctx)
					if bu == nil {
						bu = &url.URL{Scheme: "https", Host: "ca.smallstep.com"}
					}

					var provName string
					prov, err := ProvisionerFromContext(tc.ctx)
					if err != nil {
						provName = ""
					} else {
						provName = url.PathEscape(prov.GetName())
					}

					assert.Equals(t, dir.NewNonce, fmt.Sprintf("%s/acme/%s/new-nonce", bu.String(), provName))
					assert.Equals(t, dir.NewAccount, fmt.Sprintf("%s/acme/%s/new-account", bu.String(), provName))
					assert.Equals(t, dir.NewOrder, fmt.Sprintf("%s/acme/%s/new-order", bu.String(), provName))
					assert.Equals(t, dir.RevokeCert, fmt.Sprintf("%s/acme/%s/revoke-cert", bu.String(), provName))
					assert.Equals(t, dir.KeyChange, fmt.Sprintf("%s/acme/%s/key-change", bu.String(), provName))
				}
			}
		})
	}
}

func TestAuthorityNewNonce(t *testing.T) {
	type test struct {
		auth *Authority
		res  *string
		err  *Error
	}
	tests := map[string]func(t *testing.T) test{
		"fail/newNonce-error": func(t *testing.T) test {
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
					return nil, false, errors.New("force")
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth: auth,
				res:  nil,
				err:  ServerInternalErr(errors.New("error storing nonce: force")),
			}
		},
		"ok": func(t *testing.T) test {
			var _res string
			res := &_res
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
					*res = string(key)
					return nil, true, nil
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth: auth,
				res:  res,
			}
		},
	}
	for name, run := range tests {
		t.Run(name, func(t *testing.T) {
			tc := run(t)
			if nonce, err := tc.auth.NewNonce(); 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, nonce, *tc.res)
				}
			}
		})
	}
}

func TestAuthorityUseNonce(t *testing.T) {
	type test struct {
		auth *Authority
		err  *Error
	}
	tests := map[string]func(t *testing.T) test{
		"fail/newNonce-error": func(t *testing.T) test {
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MUpdate: func(tx *database.Tx) error {
					return errors.New("force")
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth: auth,
				err:  ServerInternalErr(errors.New("error deleting nonce foo: force")),
			}
		},
		"ok": func(t *testing.T) test {
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MUpdate: func(tx *database.Tx) error {
					return nil
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth: auth,
			}
		},
	}
	for name, run := range tests {
		t.Run(name, func(t *testing.T) {
			tc := run(t)
			if err := tc.auth.UseNonce("foo"); 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 TestAuthorityNewAccount(t *testing.T) {
	jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
	assert.FatalError(t, err)
	ops := AccountOptions{
		Key: jwk, Contact: []string{"foo", "bar"},
	}
	prov := newProv()
	ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov)
	ctx = context.WithValue(ctx, BaseURLContextKey, "https://test.ca.smallstep.com:8080")
	type test struct {
		auth *Authority
		ops  AccountOptions
		err  *Error
		acc  **Account
	}
	tests := map[string]func(t *testing.T) test{
		"fail/newAccount-error": func(t *testing.T) test {
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
					return nil, false, errors.New("force")
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth: auth,
				ops:  ops,
				err:  ServerInternalErr(errors.New("error setting key-id to account-id index: force")),
			}
		},
		"ok": func(t *testing.T) test {
			var (
				_acmeacc = &Account{}
				acmeacc  = &_acmeacc
				count    = 0
				dir      = newDirectory("ca.smallstep.com", "acme")
			)
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
					if count == 1 {
						var acc *account
						assert.FatalError(t, json.Unmarshal(newval, &acc))
						*acmeacc, err = acc.toACME(ctx, nil, dir)
						return nil, true, nil
					}
					count++
					return nil, true, nil
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth: auth,
				ops:  ops,
				acc:  acmeacc,
			}
		},
	}
	for name, run := range tests {
		t.Run(name, func(t *testing.T) {
			tc := run(t)
			if acmeAcc, err := tc.auth.NewAccount(ctx, tc.ops); 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) {
					gotb, err := json.Marshal(acmeAcc)
					assert.FatalError(t, err)
					expb, err := json.Marshal(*tc.acc)
					assert.FatalError(t, err)
					assert.Equals(t, expb, gotb)
				}
			}
		})
	}
}

func TestAuthorityGetAccount(t *testing.T) {
	prov := newProv()
	ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov)
	ctx = context.WithValue(ctx, BaseURLContextKey, "https://test.ca.smallstep.com:8080")
	type test struct {
		auth *Authority
		id   string
		err  *Error
		acc  *account
	}
	tests := map[string]func(t *testing.T) test{
		"fail/getAccount-error": func(t *testing.T) test {
			id := "foo"
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					assert.Equals(t, bucket, accountTable)
					assert.Equals(t, key, []byte(id))
					return nil, errors.New("force")
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth: auth,
				id:   id,
				err:  ServerInternalErr(errors.Errorf("error loading account %s: force", id)),
			}
		},
		"ok": func(t *testing.T) test {
			acc, err := newAcc()
			assert.FatalError(t, err)
			b, err := json.Marshal(acc)
			assert.FatalError(t, err)
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					return b, nil
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth: auth,
				id:   acc.ID,
				acc:  acc,
			}
		},
	}
	for name, run := range tests {
		t.Run(name, func(t *testing.T) {
			tc := run(t)
			if acmeAcc, err := tc.auth.GetAccount(ctx, 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) {
					gotb, err := json.Marshal(acmeAcc)
					assert.FatalError(t, err)

					acmeExp, err := tc.acc.toACME(ctx, nil, tc.auth.dir)
					assert.FatalError(t, err)
					expb, err := json.Marshal(acmeExp)
					assert.FatalError(t, err)

					assert.Equals(t, expb, gotb)
				}
			}
		})
	}
}

func TestAuthorityGetAccountByKey(t *testing.T) {
	prov := newProv()
	ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov)
	ctx = context.WithValue(ctx, BaseURLContextKey, "https://test.ca.smallstep.com:8080")
	type test struct {
		auth *Authority
		jwk  *jose.JSONWebKey
		err  *Error
		acc  *account
	}
	tests := map[string]func(t *testing.T) test{
		"fail/generate-thumbprint-error": func(t *testing.T) test {
			jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
			assert.FatalError(t, err)
			jwk.Key = "foo"
			auth, err := NewAuthority(new(db.MockNoSQLDB), "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth: auth,
				jwk:  jwk,
				err:  ServerInternalErr(errors.New("error generating jwk thumbprint: square/go-jose: unknown key type 'string'")),
			}
		},
		"fail/getAccount-error": func(t *testing.T) test {
			jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
			assert.FatalError(t, err)
			kid, err := keyToID(jwk)
			assert.FatalError(t, err)
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					assert.Equals(t, bucket, accountByKeyIDTable)
					assert.Equals(t, key, []byte(kid))
					return nil, errors.New("force")
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth: auth,
				jwk:  jwk,
				err:  ServerInternalErr(errors.New("error loading key-account index: 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
			kid, err := keyToID(acc.Key)
			assert.FatalError(t, err)
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					var ret []byte
					switch {
					case count == 0:
						assert.Equals(t, bucket, accountByKeyIDTable)
						assert.Equals(t, key, []byte(kid))
						ret = []byte(acc.ID)
					case count == 1:
						assert.Equals(t, bucket, accountTable)
						assert.Equals(t, key, []byte(acc.ID))
						ret = b
					}
					count++
					return ret, nil
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth: auth,
				jwk:  acc.Key,
				acc:  acc,
			}
		},
	}
	for name, run := range tests {
		t.Run(name, func(t *testing.T) {
			tc := run(t)
			if acmeAcc, err := tc.auth.GetAccountByKey(ctx, tc.jwk); 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) {
					gotb, err := json.Marshal(acmeAcc)
					assert.FatalError(t, err)

					acmeExp, err := tc.acc.toACME(ctx, nil, tc.auth.dir)
					assert.FatalError(t, err)
					expb, err := json.Marshal(acmeExp)
					assert.FatalError(t, err)

					assert.Equals(t, expb, gotb)
				}
			}
		})
	}
}

func TestAuthorityGetOrder(t *testing.T) {
	prov := newProv()
	ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov)
	ctx = context.WithValue(ctx, BaseURLContextKey, "https://test.ca.smallstep.com:8080")
	type test struct {
		auth      *Authority
		id, accID string
		err       *Error
		o         *order
	}
	tests := map[string]func(t *testing.T) test{
		"fail/getOrder-error": func(t *testing.T) test {
			id := "foo"
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					assert.Equals(t, bucket, orderTable)
					assert.Equals(t, key, []byte(id))
					return nil, errors.New("force")
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth: auth,
				id:   id,
				err:  ServerInternalErr(errors.New("error loading order foo: force")),
			}
		},
		"fail/order-not-owned-by-account": func(t *testing.T) test {
			o, err := newO()
			assert.FatalError(t, err)
			b, err := json.Marshal(o)
			assert.FatalError(t, err)
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					assert.Equals(t, bucket, orderTable)
					assert.Equals(t, key, []byte(o.ID))
					return b, nil
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth:  auth,
				id:    o.ID,
				accID: "foo",
				err:   UnauthorizedErr(errors.New("account does not own order")),
			}
		},
		"fail/updateStatus-error": func(t *testing.T) test {
			o, err := newO()
			assert.FatalError(t, err)
			b, err := json.Marshal(o)
			assert.FatalError(t, err)
			i := 0
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					switch {
					case i == 0:
						i++
						assert.Equals(t, bucket, orderTable)
						assert.Equals(t, key, []byte(o.ID))
						return b, nil
					default:
						assert.Equals(t, bucket, authzTable)
						assert.Equals(t, key, []byte(o.Authorizations[0]))
						return nil, ServerInternalErr(errors.New("force"))
					}
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth:  auth,
				id:    o.ID,
				accID: o.AccountID,
				err:   ServerInternalErr(errors.Errorf("error loading authz %s: force", o.Authorizations[0])),
			}
		},
		"ok": func(t *testing.T) test {
			o, err := newO()
			assert.FatalError(t, err)
			o.Status = "valid"
			b, err := json.Marshal(o)
			assert.FatalError(t, err)
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					assert.Equals(t, bucket, orderTable)
					assert.Equals(t, key, []byte(o.ID))
					return b, nil
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth:  auth,
				id:    o.ID,
				accID: o.AccountID,
				o:     o,
			}
		},
	}
	for name, run := range tests {
		t.Run(name, func(t *testing.T) {
			tc := run(t)
			if acmeO, err := tc.auth.GetOrder(ctx, tc.accID, 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) {
					gotb, err := json.Marshal(acmeO)
					assert.FatalError(t, err)

					acmeExp, err := tc.o.toACME(ctx, nil, tc.auth.dir)
					assert.FatalError(t, err)
					expb, err := json.Marshal(acmeExp)
					assert.FatalError(t, err)

					assert.Equals(t, expb, gotb)
				}
			}
		})
	}
}

func TestAuthorityGetCertificate(t *testing.T) {
	type test struct {
		auth      *Authority
		id, accID string
		err       *Error
		cert      *certificate
	}
	tests := map[string]func(t *testing.T) test{
		"fail/getCertificate-error": func(t *testing.T) test {
			id := "foo"
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					assert.Equals(t, bucket, certTable)
					assert.Equals(t, key, []byte(id))
					return nil, errors.New("force")
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth: auth,
				id:   id,
				err:  ServerInternalErr(errors.New("error loading certificate: force")),
			}
		},
		"fail/certificate-not-owned-by-account": func(t *testing.T) test {
			cert, err := newcert()
			assert.FatalError(t, err)
			b, err := json.Marshal(cert)
			assert.FatalError(t, err)
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					assert.Equals(t, bucket, certTable)
					assert.Equals(t, key, []byte(cert.ID))
					return b, nil
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth:  auth,
				id:    cert.ID,
				accID: "foo",
				err:   UnauthorizedErr(errors.New("account does not own certificate")),
			}
		},
		"ok": func(t *testing.T) test {
			cert, err := newcert()
			assert.FatalError(t, err)
			b, err := json.Marshal(cert)
			assert.FatalError(t, err)
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					assert.Equals(t, bucket, certTable)
					assert.Equals(t, key, []byte(cert.ID))
					return b, nil
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth:  auth,
				id:    cert.ID,
				accID: cert.AccountID,
				cert:  cert,
			}
		},
	}
	for name, run := range tests {
		t.Run(name, func(t *testing.T) {
			tc := run(t)
			if acmeCert, err := tc.auth.GetCertificate(tc.accID, 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) {
					gotb, err := json.Marshal(acmeCert)
					assert.FatalError(t, err)

					acmeExp, err := tc.cert.toACME(nil, tc.auth.dir)
					assert.FatalError(t, err)
					expb, err := json.Marshal(acmeExp)
					assert.FatalError(t, err)

					assert.Equals(t, expb, gotb)
				}
			}
		})
	}
}

func TestAuthorityGetAuthz(t *testing.T) {
	prov := newProv()
	ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov)
	ctx = context.WithValue(ctx, BaseURLContextKey, "https://test.ca.smallstep.com:8080")
	type test struct {
		auth      *Authority
		id, accID string
		err       *Error
		acmeAz    *Authz
	}
	tests := map[string]func(t *testing.T) test{
		"fail/getAuthz-error": func(t *testing.T) test {
			id := "foo"
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					assert.Equals(t, bucket, authzTable)
					assert.Equals(t, key, []byte(id))
					return nil, errors.New("force")
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth: auth,
				id:   id,
				err:  ServerInternalErr(errors.Errorf("error loading authz %s: force", id)),
			}
		},
		"fail/authz-not-owned-by-account": func(t *testing.T) test {
			az, err := newAz()
			assert.FatalError(t, err)
			b, err := json.Marshal(az)
			assert.FatalError(t, err)
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					assert.Equals(t, bucket, authzTable)
					assert.Equals(t, key, []byte(az.getID()))
					return b, nil
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth:  auth,
				id:    az.getID(),
				accID: "foo",
				err:   UnauthorizedErr(errors.New("account does not own authz")),
			}
		},
		"fail/update-status-error": func(t *testing.T) test {
			az, err := newAz()
			assert.FatalError(t, err)
			b, err := json.Marshal(az)
			assert.FatalError(t, err)
			count := 0
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					var ret []byte
					switch count {
					case 0:
						assert.Equals(t, bucket, authzTable)
						assert.Equals(t, key, []byte(az.getID()))
						ret = b
					case 1:
						assert.Equals(t, bucket, challengeTable)
						assert.Equals(t, key, []byte(az.getChallenges()[0]))
						return nil, errors.New("force")
					}
					count++
					return ret, nil
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth:  auth,
				id:    az.getID(),
				accID: az.getAccountID(),
				err:   ServerInternalErr(errors.New("error updating authz status: error loading challenge")),
			}
		},
		"ok": func(t *testing.T) test {
			var ch1B, ch2B, ch3B = &[]byte{}, &[]byte{}, &[]byte{}
			count := 0
			mockdb := &db.MockNoSQLDB{
				MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
					switch count {
					case 0:
						*ch1B = newval
					case 1:
						*ch2B = newval
					case 2:
						*ch3B = newval
					}
					count++
					return nil, true, nil
				},
			}
			az, err := newAuthz(mockdb, "1234", Identifier{
				Type: "dns", Value: "acme.example.com",
			})
			assert.FatalError(t, err)
			_az, ok := az.(*dnsAuthz)
			assert.Fatal(t, ok)
			_az.baseAuthz.Status = StatusValid
			b, err := json.Marshal(az)
			assert.FatalError(t, err)

			ch1, err := unmarshalChallenge(*ch1B)
			assert.FatalError(t, err)
			ch2, err := unmarshalChallenge(*ch2B)
			assert.FatalError(t, err)
			ch3, err := unmarshalChallenge(*ch3B)
			assert.FatalError(t, err)
			count = 0
			mockdb = &db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					var ret []byte
					switch count {
					case 0:
						assert.Equals(t, bucket, challengeTable)
						assert.Equals(t, key, []byte(ch1.getID()))
						ret = *ch1B
					case 1:
						assert.Equals(t, bucket, challengeTable)
						assert.Equals(t, key, []byte(ch2.getID()))
						ret = *ch2B
					case 2:
						assert.Equals(t, bucket, challengeTable)
						assert.Equals(t, key, []byte(ch3.getID()))
						ret = *ch3B
					}
					count++
					return ret, nil
				},
			}
			acmeAz, err := az.toACME(ctx, mockdb, newDirectory("ca.smallstep.com", "acme"))
			assert.FatalError(t, err)

			count = 0
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					var ret []byte
					switch count {
					case 0:
						assert.Equals(t, bucket, authzTable)
						assert.Equals(t, key, []byte(az.getID()))
						ret = b
					case 1:
						assert.Equals(t, bucket, challengeTable)
						assert.Equals(t, key, []byte(ch1.getID()))
						ret = *ch1B
					case 2:
						assert.Equals(t, bucket, challengeTable)
						assert.Equals(t, key, []byte(ch2.getID()))
						ret = *ch2B
					case 3:
						assert.Equals(t, bucket, challengeTable)
						assert.Equals(t, key, []byte(ch3.getID()))
						ret = *ch3B
					}
					count++
					return ret, nil
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth:   auth,
				id:     az.getID(),
				accID:  az.getAccountID(),
				acmeAz: acmeAz,
			}
		},
	}
	for name, run := range tests {
		t.Run(name, func(t *testing.T) {
			tc := run(t)
			if acmeAz, err := tc.auth.GetAuthz(ctx, tc.accID, 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) {
					gotb, err := json.Marshal(acmeAz)
					assert.FatalError(t, err)

					expb, err := json.Marshal(tc.acmeAz)
					assert.FatalError(t, err)

					assert.Equals(t, expb, gotb)
				}
			}
		})
	}
}

func TestAuthorityNewOrder(t *testing.T) {
	prov := newProv()
	ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov)
	ctx = context.WithValue(ctx, BaseURLContextKey, "https://test.ca.smallstep.com:8080")
	type test struct {
		auth *Authority
		ops  OrderOptions
		ctx  context.Context
		err  *Error
		o    **Order
	}
	tests := map[string]func(t *testing.T) test{
		"fail/no-provisioner": func(t *testing.T) test {
			auth, err := NewAuthority(&db.MockNoSQLDB{}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth: auth,
				ops:  defaultOrderOps(),
				ctx:  context.Background(),
				err:  ServerInternalErr(errors.New("provisioner expected in request context")),
			}
		},
		"fail/newOrder-error": func(t *testing.T) test {
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
					return nil, false, errors.New("force")
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth: auth,
				ops:  defaultOrderOps(),
				ctx:  ctx,
				err:  ServerInternalErr(errors.New("error creating order: error creating http challenge: error saving acme challenge: force")),
			}
		},
		"ok": func(t *testing.T) test {
			var (
				_acmeO = &Order{}
				acmeO  = &_acmeO
				count  = 0
				dir    = newDirectory("ca.smallstep.com", "acme")
				err    error
				_accID string
				accID  = &_accID
			)
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
					switch count {
					case 0:
						assert.Equals(t, bucket, challengeTable)
					case 1:
						assert.Equals(t, bucket, challengeTable)
					case 2:
						assert.Equals(t, bucket, challengeTable)
					case 3:
						assert.Equals(t, bucket, authzTable)
					case 4:
						assert.Equals(t, bucket, challengeTable)
					case 5:
						assert.Equals(t, bucket, challengeTable)
					case 6:
						assert.Equals(t, bucket, challengeTable)
					case 7:
						assert.Equals(t, bucket, authzTable)
					case 8:
						assert.Equals(t, bucket, orderTable)
						var o order
						assert.FatalError(t, json.Unmarshal(newval, &o))
						*acmeO, err = o.toACME(ctx, nil, dir)
						assert.FatalError(t, err)
						*accID = o.AccountID
					case 9:
						assert.Equals(t, bucket, ordersByAccountIDTable)
						assert.Equals(t, string(key), *accID)
					}
					count++
					return nil, true, nil
				},
				MGet: func(bucket, key []byte) ([]byte, error) {
					return nil, database.ErrNotFound
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth: auth,
				ops:  defaultOrderOps(),
				ctx:  ctx,
				o:    acmeO,
			}
		},
	}
	for name, run := range tests {
		t.Run(name, func(t *testing.T) {
			tc := run(t)
			if acmeO, err := tc.auth.NewOrder(tc.ctx, tc.ops); 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) {
					gotb, err := json.Marshal(acmeO)
					assert.FatalError(t, err)
					expb, err := json.Marshal(*tc.o)
					assert.FatalError(t, err)
					assert.Equals(t, expb, gotb)
				}
			}
		})
	}
}

func TestAuthorityGetOrdersByAccount(t *testing.T) {
	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 {
		auth *Authority
		id   string
		err  *Error
		res  []string
	}
	tests := map[string]func(t *testing.T) test{
		"fail/getOrderIDsByAccount-error": func(t *testing.T) test {
			id := "foo"
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					assert.Equals(t, bucket, ordersByAccountIDTable)
					assert.Equals(t, key, []byte(id))
					return nil, errors.New("force")
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth: auth,
				id:   id,
				err:  ServerInternalErr(errors.New("error loading orderIDs for account foo: force")),
			}
		},
		"fail/getOrder-error": func(t *testing.T) test {
			var (
				id    = "zap"
				oids  = []string{"foo", "bar"}
				count = 0
				err   error
			)
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					var ret []byte
					switch count {
					case 0:
						assert.Equals(t, bucket, ordersByAccountIDTable)
						assert.Equals(t, key, []byte(id))
						ret, err = json.Marshal(oids)
						assert.FatalError(t, err)
					case 1:
						assert.Equals(t, bucket, orderTable)
						assert.Equals(t, key, []byte(oids[0]))
						return nil, errors.New("force")
					}
					count++
					return ret, nil
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth: auth,
				id:   id,
				err:  ServerInternalErr(errors.New("error loading order foo for account zap: error loading order foo: force")),
			}
		},
		"ok": func(t *testing.T) test {
			accID := "zap"

			foo, err := newO()
			assert.FatalError(t, err)
			bfoo, err := json.Marshal(foo)
			assert.FatalError(t, err)

			bar, err := newO()
			assert.FatalError(t, err)
			bar.Status = StatusInvalid
			bbar, err := json.Marshal(bar)
			assert.FatalError(t, err)

			zap, err := newO()
			assert.FatalError(t, err)
			bzap, err := json.Marshal(zap)
			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
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					switch string(bucket) {
					case string(orderTable):
						dbGetOrder++
						switch dbGetOrder {
						case 1:
							return bfoo, nil
						case 2:
							return bbar, nil
						case 3:
							return bzap, nil
						}
					case string(ordersByAccountIDTable):
						assert.Equals(t, bucket, ordersByAccountIDTable)
						assert.Equals(t, key, []byte(accID))
						ret, err := json.Marshal([]string{foo.ID, bar.ID, zap.ID})
						assert.FatalError(t, err)
						return ret, nil
					case string(challengeTable):
						return bch, nil
					case string(authzTable):
						return baz, nil
					}
					return nil, errors.Errorf("should not be query db table %s", bucket)
				},
				MCmpAndSwap: func(bucket, key, old, newVal []byte) ([]byte, bool, error) {
					assert.Equals(t, bucket, ordersByAccountIDTable)
					assert.Equals(t, string(key), accID)
					return nil, true, nil
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth: auth,
				id:   accID,
				res: []string{
					fmt.Sprintf("%s/acme/%s/order/%s", baseURL.String(), provName, foo.ID),
					fmt.Sprintf("%s/acme/%s/order/%s", baseURL.String(), provName, zap.ID),
				},
			}
		},
	}
	for name, run := range tests {
		t.Run(name, func(t *testing.T) {
			tc := run(t)
			if orderLinks, err := tc.auth.GetOrdersByAccount(ctx, 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, orderLinks)
				}
			}
		})
	}
}

func TestAuthorityFinalizeOrder(t *testing.T) {
	prov := newProv()
	ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov)
	ctx = context.WithValue(ctx, BaseURLContextKey, "https://test.ca.smallstep.com:8080")
	type test struct {
		auth      *Authority
		id, accID string
		ctx       context.Context
		err       *Error
		o         *order
	}
	tests := map[string]func(t *testing.T) test{
		"fail/no-provisioner": func(t *testing.T) test {
			auth, err := NewAuthority(&db.MockNoSQLDB{}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth: auth,
				id:   "foo",
				ctx:  context.Background(),
				err:  ServerInternalErr(errors.New("provisioner expected in request context")),
			}
		},
		"fail/getOrder-error": func(t *testing.T) test {
			id := "foo"
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					assert.Equals(t, bucket, orderTable)
					assert.Equals(t, key, []byte(id))
					return nil, errors.New("force")
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth: auth,
				id:   id,
				ctx:  ctx,
				err:  ServerInternalErr(errors.New("error loading order foo: force")),
			}
		},
		"fail/order-not-owned-by-account": func(t *testing.T) test {
			o, err := newO()
			assert.FatalError(t, err)
			b, err := json.Marshal(o)
			assert.FatalError(t, err)
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					assert.Equals(t, bucket, orderTable)
					assert.Equals(t, key, []byte(o.ID))
					return b, nil
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth:  auth,
				id:    o.ID,
				accID: "foo",
				ctx:   ctx,
				err:   UnauthorizedErr(errors.New("account does not own order")),
			}
		},
		"fail/finalize-error": func(t *testing.T) test {
			o, err := newO()
			assert.FatalError(t, err)
			o.Expires = time.Now().Add(-time.Minute)
			b, err := json.Marshal(o)
			assert.FatalError(t, err)
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					assert.Equals(t, bucket, orderTable)
					assert.Equals(t, key, []byte(o.ID))
					return b, nil
				},
				MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
					assert.Equals(t, bucket, orderTable)
					assert.Equals(t, key, []byte(o.ID))
					return nil, false, errors.New("force")
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth:  auth,
				id:    o.ID,
				accID: o.AccountID,
				ctx:   ctx,
				err:   ServerInternalErr(errors.New("error finalizing order: error storing order: force")),
			}
		},
		"ok": func(t *testing.T) test {
			o, err := newO()
			assert.FatalError(t, err)
			o.Status = StatusValid
			o.Certificate = "certID"
			b, err := json.Marshal(o)
			assert.FatalError(t, err)
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					assert.Equals(t, bucket, orderTable)
					assert.Equals(t, key, []byte(o.ID))
					return b, nil
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth:  auth,
				id:    o.ID,
				accID: o.AccountID,
				ctx:   ctx,
				o:     o,
			}
		},
	}
	for name, run := range tests {
		t.Run(name, func(t *testing.T) {
			tc := run(t)
			if acmeO, err := tc.auth.FinalizeOrder(tc.ctx, tc.accID, tc.id, nil); 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) {
					gotb, err := json.Marshal(acmeO)
					assert.FatalError(t, err)

					acmeExp, err := tc.o.toACME(ctx, nil, tc.auth.dir)
					assert.FatalError(t, err)
					expb, err := json.Marshal(acmeExp)
					assert.FatalError(t, err)

					assert.Equals(t, expb, gotb)
				}
			}
		})
	}
}

func TestAuthorityValidateChallenge(t *testing.T) {
	prov := newProv()
	ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov)
	ctx = context.WithValue(ctx, BaseURLContextKey, "https://test.ca.smallstep.com:8080")
	type test struct {
		auth      *Authority
		id, accID string
		err       *Error
		ch        challenge
	}
	tests := map[string]func(t *testing.T) test{
		"fail/getChallenge-error": func(t *testing.T) test {
			id := "foo"
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					assert.Equals(t, bucket, challengeTable)
					assert.Equals(t, key, []byte(id))
					return nil, errors.New("force")
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth: auth,
				id:   id,
				err:  ServerInternalErr(errors.Errorf("error loading challenge %s: force", id)),
			}
		},
		"fail/challenge-not-owned-by-account": func(t *testing.T) test {
			ch, err := newHTTPCh()
			assert.FatalError(t, err)
			b, err := json.Marshal(ch)
			assert.FatalError(t, err)
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					assert.Equals(t, bucket, challengeTable)
					assert.Equals(t, key, []byte(ch.getID()))
					return b, nil
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth:  auth,
				id:    ch.getID(),
				accID: "foo",
				err:   UnauthorizedErr(errors.New("account does not own challenge")),
			}
		},
		"fail/validate-error": func(t *testing.T) test {
			ch, err := newHTTPCh()
			assert.FatalError(t, err)
			b, err := json.Marshal(ch)
			assert.FatalError(t, err)
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					assert.Equals(t, bucket, challengeTable)
					assert.Equals(t, key, []byte(ch.getID()))
					return b, nil
				},
				MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
					assert.Equals(t, bucket, challengeTable)
					assert.Equals(t, key, []byte(ch.getID()))
					return nil, false, errors.New("force")
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth:  auth,
				id:    ch.getID(),
				accID: ch.getAccountID(),
				err:   ServerInternalErr(errors.New("error attempting challenge validation: error saving acme challenge: force")),
			}
		},
		"ok": func(t *testing.T) test {
			ch, err := newHTTPCh()
			assert.FatalError(t, err)
			_ch, ok := ch.(*http01Challenge)
			assert.Fatal(t, ok)
			_ch.baseChallenge.Status = StatusValid
			_ch.baseChallenge.Validated = clock.Now()
			b, err := json.Marshal(ch)
			assert.FatalError(t, err)
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					assert.Equals(t, bucket, challengeTable)
					assert.Equals(t, key, []byte(ch.getID()))
					return b, nil
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth:  auth,
				id:    ch.getID(),
				accID: ch.getAccountID(),
				ch:    ch,
			}
		},
	}
	for name, run := range tests {
		t.Run(name, func(t *testing.T) {
			tc := run(t)
			if acmeCh, err := tc.auth.ValidateChallenge(ctx, tc.accID, tc.id, nil); 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) {
					gotb, err := json.Marshal(acmeCh)
					assert.FatalError(t, err)

					acmeExp, err := tc.ch.toACME(ctx, nil, tc.auth.dir)
					assert.FatalError(t, err)
					expb, err := json.Marshal(acmeExp)
					assert.FatalError(t, err)

					assert.Equals(t, expb, gotb)
				}
			}
		})
	}
}

func TestAuthorityUpdateAccount(t *testing.T) {
	contact := []string{"baz", "zap"}
	prov := newProv()
	ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov)
	ctx = context.WithValue(ctx, BaseURLContextKey, "https://test.ca.smallstep.com:8080")
	type test struct {
		auth    *Authority
		id      string
		contact []string
		acc     *account
		err     *Error
	}
	tests := map[string]func(t *testing.T) test{
		"fail/getAccount-error": func(t *testing.T) test {
			id := "foo"
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					assert.Equals(t, bucket, accountTable)
					assert.Equals(t, key, []byte(id))
					return nil, errors.New("force")
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth:    auth,
				id:      id,
				contact: contact,
				err:     ServerInternalErr(errors.Errorf("error loading account %s: force", id)),
			}
		},
		"fail/update-error": func(t *testing.T) test {
			acc, err := newAcc()
			assert.FatalError(t, err)
			b, err := json.Marshal(acc)
			assert.FatalError(t, err)

			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					return b, nil
				},
				MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
					return nil, false, errors.New("force")
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth:    auth,
				id:      acc.ID,
				contact: contact,
				err:     ServerInternalErr(errors.New("error storing account: force")),
			}
		},

		"ok": func(t *testing.T) test {
			acc, err := newAcc()
			assert.FatalError(t, err)
			b, err := json.Marshal(acc)
			assert.FatalError(t, err)

			_acc := *acc
			clone := &_acc
			clone.Contact = contact
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					return b, nil
				},
				MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
					assert.Equals(t, bucket, accountTable)
					assert.Equals(t, key, []byte(acc.ID))
					return nil, true, nil
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth:    auth,
				id:      acc.ID,
				contact: contact,
				acc:     clone,
			}
		},
	}
	for name, run := range tests {
		t.Run(name, func(t *testing.T) {
			tc := run(t)
			if acmeAcc, err := tc.auth.UpdateAccount(ctx, tc.id, tc.contact); 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) {
					gotb, err := json.Marshal(acmeAcc)
					assert.FatalError(t, err)

					acmeExp, err := tc.acc.toACME(ctx, nil, tc.auth.dir)
					assert.FatalError(t, err)
					expb, err := json.Marshal(acmeExp)
					assert.FatalError(t, err)

					assert.Equals(t, expb, gotb)
				}
			}
		})
	}
}

func TestAuthorityDeactivateAccount(t *testing.T) {
	prov := newProv()
	ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov)
	ctx = context.WithValue(ctx, BaseURLContextKey, "https://test.ca.smallstep.com:8080")
	type test struct {
		auth *Authority
		id   string
		acc  *account
		err  *Error
	}
	tests := map[string]func(t *testing.T) test{
		"fail/getAccount-error": func(t *testing.T) test {
			id := "foo"
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					assert.Equals(t, bucket, accountTable)
					assert.Equals(t, key, []byte(id))
					return nil, errors.New("force")
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth: auth,
				id:   id,
				err:  ServerInternalErr(errors.Errorf("error loading account %s: force", id)),
			}
		},
		"fail/deactivate-error": func(t *testing.T) test {
			acc, err := newAcc()
			assert.FatalError(t, err)
			b, err := json.Marshal(acc)
			assert.FatalError(t, err)

			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					return b, nil
				},
				MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
					return nil, false, errors.New("force")
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth: auth,
				id:   acc.ID,
				err:  ServerInternalErr(errors.New("error storing account: force")),
			}
		},

		"ok": func(t *testing.T) test {
			acc, err := newAcc()
			assert.FatalError(t, err)
			b, err := json.Marshal(acc)
			assert.FatalError(t, err)

			_acc := *acc
			clone := &_acc
			clone.Status = StatusDeactivated
			clone.Deactivated = clock.Now()
			auth, err := NewAuthority(&db.MockNoSQLDB{
				MGet: func(bucket, key []byte) ([]byte, error) {
					return b, nil
				},
				MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
					assert.Equals(t, bucket, accountTable)
					assert.Equals(t, key, []byte(acc.ID))
					return nil, true, nil
				},
			}, "ca.smallstep.com", "acme", nil)
			assert.FatalError(t, err)
			return test{
				auth: auth,
				id:   acc.ID,
				acc:  clone,
			}
		},
	}
	for name, run := range tests {
		t.Run(name, func(t *testing.T) {
			tc := run(t)
			if acmeAcc, err := tc.auth.DeactivateAccount(ctx, 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) {
					gotb, err := json.Marshal(acmeAcc)
					assert.FatalError(t, err)

					acmeExp, err := tc.acc.toACME(ctx, nil, tc.auth.dir)
					assert.FatalError(t, err)
					expb, err := json.Marshal(acmeExp)
					assert.FatalError(t, err)

					assert.Equals(t, expb, gotb)
				}
			}
		})
	}
}