github.com/volatiletech/authboss@v2.4.1+incompatible/mocks/mocks.go (about)

     1  // Package mocks defines implemented interfaces for testing modules
     2  package mocks
     3  
     4  import (
     5  	"context"
     6  	"io"
     7  	"net/http"
     8  	"net/url"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/pkg/errors"
    13  	"github.com/volatiletech/authboss"
    14  )
    15  
    16  // User represents all possible fields a authboss User may have
    17  type User struct {
    18  	Username           string
    19  	Email              string
    20  	Password           string
    21  	RecoverSelector    string
    22  	RecoverVerifier    string
    23  	RecoverTokenExpiry time.Time
    24  	ConfirmSelector    string
    25  	ConfirmVerifier    string
    26  	Confirmed          bool
    27  	AttemptCount       int
    28  	LastAttempt        time.Time
    29  	Locked             time.Time
    30  
    31  	OAuth2UID      string
    32  	OAuth2Provider string
    33  	OAuth2Token    string
    34  	OAuth2Refresh  string
    35  	OAuth2Expiry   time.Time
    36  
    37  	OTPs           string
    38  	TOTPSecretKey  string
    39  	SMSPhoneNumber string
    40  	RecoveryCodes  string
    41  
    42  	SMSPhoneNumberSeed string
    43  
    44  	Arbitrary map[string]string
    45  }
    46  
    47  // GetPID from user
    48  func (u User) GetPID() string { return u.Email }
    49  
    50  // GetEmail from user
    51  func (u User) GetEmail() string { return u.Email }
    52  
    53  // GetUsername from user
    54  func (u User) GetUsername() string { return u.Username }
    55  
    56  // GetPassword from user
    57  func (u User) GetPassword() string { return u.Password }
    58  
    59  // GetRecoverSelector from user
    60  func (u User) GetRecoverSelector() string { return u.RecoverSelector }
    61  
    62  // GetRecoverVerifier from user
    63  func (u User) GetRecoverVerifier() string { return u.RecoverVerifier }
    64  
    65  // GetRecoverExpiry from user
    66  func (u User) GetRecoverExpiry() time.Time { return u.RecoverTokenExpiry }
    67  
    68  // GetConfirmSelector from user
    69  func (u User) GetConfirmSelector() string { return u.ConfirmSelector }
    70  
    71  // GetConfirmVerifier from user
    72  func (u User) GetConfirmVerifier() string { return u.ConfirmVerifier }
    73  
    74  // GetConfirmed from user
    75  func (u User) GetConfirmed() bool { return u.Confirmed }
    76  
    77  // GetAttemptCount from user
    78  func (u User) GetAttemptCount() int { return u.AttemptCount }
    79  
    80  // GetLastAttempt from user
    81  func (u User) GetLastAttempt() time.Time { return u.LastAttempt }
    82  
    83  // GetLocked from user
    84  func (u User) GetLocked() time.Time { return u.Locked }
    85  
    86  // IsOAuth2User returns true if the user is an oauth2 user
    87  func (u User) IsOAuth2User() bool { return len(u.OAuth2Provider) != 0 }
    88  
    89  // GetOAuth2UID from user
    90  func (u User) GetOAuth2UID() string { return u.OAuth2UID }
    91  
    92  // GetOAuth2Provider from user
    93  func (u User) GetOAuth2Provider() string { return u.OAuth2Provider }
    94  
    95  // GetOAuth2AccessToken from user
    96  func (u User) GetOAuth2AccessToken() string { return u.OAuth2Token }
    97  
    98  // GetOAuth2RefreshToken from user
    99  func (u User) GetOAuth2RefreshToken() string { return u.OAuth2Refresh }
   100  
   101  // GetOAuth2Expiry from user
   102  func (u User) GetOAuth2Expiry() time.Time { return u.OAuth2Expiry }
   103  
   104  // GetArbitrary from user
   105  func (u User) GetArbitrary() map[string]string { return u.Arbitrary }
   106  
   107  // GetOTPs from user
   108  func (u User) GetOTPs() string { return u.OTPs }
   109  
   110  // GetTOTPSecretKey from user
   111  func (u User) GetTOTPSecretKey() string { return u.TOTPSecretKey }
   112  
   113  // GetSMSPhoneNumber from user
   114  func (u User) GetSMSPhoneNumber() string { return u.SMSPhoneNumber }
   115  
   116  // GetSMSPhoneNumber from user
   117  func (u User) GetSMSPhoneNumberSeed() string { return u.SMSPhoneNumberSeed }
   118  
   119  // GetRecoveryCodes from user
   120  func (u User) GetRecoveryCodes() string { return u.RecoveryCodes }
   121  
   122  // PutPID into user
   123  func (u *User) PutPID(email string) { u.Email = email }
   124  
   125  // PutUsername into user
   126  func (u *User) PutUsername(username string) { u.Username = username }
   127  
   128  // PutEmail into user
   129  func (u *User) PutEmail(email string) { u.Email = email }
   130  
   131  // PutPassword into user
   132  func (u *User) PutPassword(password string) { u.Password = password }
   133  
   134  // PutRecoverSelector into user
   135  func (u *User) PutRecoverSelector(recoverSelector string) { u.RecoverSelector = recoverSelector }
   136  
   137  // PutRecoverVerifier into user
   138  func (u *User) PutRecoverVerifier(recoverVerifier string) { u.RecoverVerifier = recoverVerifier }
   139  
   140  // PutRecoverExpiry into user
   141  func (u *User) PutRecoverExpiry(recoverTokenExpiry time.Time) {
   142  	u.RecoverTokenExpiry = recoverTokenExpiry
   143  }
   144  
   145  // PutConfirmSelector into user
   146  func (u *User) PutConfirmSelector(confirmSelector string) { u.ConfirmSelector = confirmSelector }
   147  
   148  // PutConfirmVerifier into user
   149  func (u *User) PutConfirmVerifier(confirmVerifier string) { u.ConfirmVerifier = confirmVerifier }
   150  
   151  // PutConfirmed into user
   152  func (u *User) PutConfirmed(confirmed bool) { u.Confirmed = confirmed }
   153  
   154  // PutAttemptCount into user
   155  func (u *User) PutAttemptCount(attemptCount int) { u.AttemptCount = attemptCount }
   156  
   157  // PutLastAttempt into user
   158  func (u *User) PutLastAttempt(attemptTime time.Time) { u.LastAttempt = attemptTime }
   159  
   160  // PutLocked into user
   161  func (u *User) PutLocked(locked time.Time) { u.Locked = locked }
   162  
   163  // PutOAuth2UID into user
   164  func (u *User) PutOAuth2UID(uid string) { u.OAuth2UID = uid }
   165  
   166  // PutOAuth2Provider into user
   167  func (u *User) PutOAuth2Provider(provider string) { u.OAuth2Provider = provider }
   168  
   169  // PutOAuth2AccessToken into user
   170  func (u *User) PutOAuth2AccessToken(token string) { u.OAuth2Token = token }
   171  
   172  // PutOAuth2RefreshToken into user
   173  func (u *User) PutOAuth2RefreshToken(refresh string) { u.OAuth2Refresh = refresh }
   174  
   175  // PutOAuth2Expiry into user
   176  func (u *User) PutOAuth2Expiry(expiry time.Time) { u.OAuth2Expiry = expiry }
   177  
   178  // PutArbitrary into user
   179  func (u *User) PutArbitrary(arb map[string]string) { u.Arbitrary = arb }
   180  
   181  // PutOTPs into user
   182  func (u *User) PutOTPs(otps string) { u.OTPs = otps }
   183  
   184  // PutTOTPSecretKey into user
   185  func (u *User) PutTOTPSecretKey(key string) { u.TOTPSecretKey = key }
   186  
   187  // PutSMSPhoneNumber into user
   188  func (u *User) PutSMSPhoneNumber(number string) { u.SMSPhoneNumber = number }
   189  
   190  // PutRecoveryCodes into user
   191  func (u *User) PutRecoveryCodes(codes string) { u.RecoveryCodes = codes }
   192  
   193  // ServerStorer should be valid for any module storer defined in authboss.
   194  type ServerStorer struct {
   195  	Users    map[string]*User
   196  	RMTokens map[string][]string
   197  }
   198  
   199  // NewServerStorer constructor
   200  func NewServerStorer() *ServerStorer {
   201  	return &ServerStorer{
   202  		Users:    make(map[string]*User),
   203  		RMTokens: make(map[string][]string),
   204  	}
   205  }
   206  
   207  // New constructs a blank user to later be created
   208  func (s *ServerStorer) New(context.Context) authboss.User {
   209  	return &User{}
   210  }
   211  
   212  // Create a user
   213  func (s *ServerStorer) Create(ctx context.Context, user authboss.User) error {
   214  	u := user.(*User)
   215  	if _, ok := s.Users[u.Email]; ok {
   216  		return authboss.ErrUserFound
   217  	}
   218  	s.Users[u.Email] = u
   219  	return nil
   220  }
   221  
   222  // Load a user
   223  func (s *ServerStorer) Load(ctx context.Context, key string) (authboss.User, error) {
   224  	user, ok := s.Users[key]
   225  	if ok {
   226  		return user, nil
   227  	}
   228  
   229  	return nil, authboss.ErrUserNotFound
   230  }
   231  
   232  // Save a user
   233  func (s *ServerStorer) Save(ctx context.Context, user authboss.User) error {
   234  	u := user.(*User)
   235  	if _, ok := s.Users[u.Email]; !ok {
   236  		return authboss.ErrUserNotFound
   237  	}
   238  	s.Users[u.Email] = u
   239  	return nil
   240  }
   241  
   242  // NewFromOAuth2 finds a user with the given details, or returns a new one
   243  func (s *ServerStorer) NewFromOAuth2(ctx context.Context, provider string, details map[string]string) (authboss.OAuth2User, error) {
   244  	uid := details["uid"]
   245  	email := details["email"]
   246  	name := details["name"]
   247  	pid := authboss.MakeOAuth2PID(provider, uid)
   248  
   249  	u, ok := s.Users[pid]
   250  	if ok {
   251  		u.Username = name
   252  		u.Email = email
   253  		return u, nil
   254  	}
   255  
   256  	return &User{
   257  		OAuth2UID:      uid,
   258  		OAuth2Provider: provider,
   259  		Email:          email,
   260  		Username:       name,
   261  	}, nil
   262  }
   263  
   264  // SaveOAuth2 creates a user if not found, or updates one that exists.
   265  func (s *ServerStorer) SaveOAuth2(ctx context.Context, user authboss.OAuth2User) error {
   266  	u := user.(*User)
   267  
   268  	pid := authboss.MakeOAuth2PID(u.OAuth2Provider, u.OAuth2UID)
   269  	// Since we don't have to differentiate between
   270  	// insert/update in a map, we just overwrite
   271  	s.Users[pid] = u
   272  	return nil
   273  }
   274  
   275  // LoadByConfirmSelector finds a user by his confirm selector
   276  func (s *ServerStorer) LoadByConfirmSelector(ctx context.Context, selector string) (authboss.ConfirmableUser, error) {
   277  	for _, v := range s.Users {
   278  		if v.ConfirmSelector == selector {
   279  			return v, nil
   280  		}
   281  	}
   282  
   283  	return nil, authboss.ErrUserNotFound
   284  }
   285  
   286  // LoadByRecoverSelector finds a user by his recover token
   287  func (s *ServerStorer) LoadByRecoverSelector(ctx context.Context, selector string) (authboss.RecoverableUser, error) {
   288  	for _, v := range s.Users {
   289  		if v.RecoverSelector == selector {
   290  			return v, nil
   291  		}
   292  	}
   293  
   294  	return nil, authboss.ErrUserNotFound
   295  }
   296  
   297  // AddRememberToken for remember me
   298  func (s *ServerStorer) AddRememberToken(ctx context.Context, key, token string) error {
   299  	arr := s.RMTokens[key]
   300  	s.RMTokens[key] = append(arr, token)
   301  	return nil
   302  }
   303  
   304  // DelRememberTokens for a user
   305  func (s *ServerStorer) DelRememberTokens(ctx context.Context, key string) error {
   306  	delete(s.RMTokens, key)
   307  	return nil
   308  }
   309  
   310  // UseRememberToken if it exists, deleting it in the process
   311  func (s *ServerStorer) UseRememberToken(ctx context.Context, givenKey, token string) (err error) {
   312  	arr, ok := s.RMTokens[givenKey]
   313  	if !ok {
   314  		return authboss.ErrTokenNotFound
   315  	}
   316  
   317  	for i, tok := range arr {
   318  		if tok == token {
   319  			if len(arr) == 1 {
   320  				delete(s.RMTokens, givenKey)
   321  				return nil
   322  			}
   323  
   324  			arr[i] = arr[len(arr)-1]
   325  			s.RMTokens[givenKey] = arr[:len(arr)-2]
   326  			return nil
   327  		}
   328  	}
   329  
   330  	return authboss.ErrTokenNotFound
   331  }
   332  
   333  // FailStorer is used for testing module initialize functions that
   334  // recover more than the base storer
   335  type FailStorer struct {
   336  	User
   337  }
   338  
   339  // Create fails
   340  func (FailStorer) Create(context.Context) error {
   341  	return errors.New("fail storer: create")
   342  }
   343  
   344  // Save fails
   345  func (FailStorer) Save(context.Context) error {
   346  	return errors.New("fail storer: put")
   347  }
   348  
   349  // Load fails
   350  func (FailStorer) Load(context.Context) error {
   351  	return errors.New("fail storer: get")
   352  }
   353  
   354  // ClientState is used for testing the client stores on context
   355  type ClientState struct {
   356  	Values        map[string]string
   357  	GetShouldFail bool
   358  }
   359  
   360  // NewClientState constructs a ClientStorer
   361  func NewClientState(data ...string) *ClientState {
   362  	if len(data) != 0 && len(data)%2 != 0 {
   363  		panic("It should be a key value list of arguments.")
   364  	}
   365  
   366  	values := make(map[string]string)
   367  
   368  	for i := 0; i < len(data)-1; i += 2 {
   369  		values[data[i]] = data[i+1]
   370  	}
   371  
   372  	return &ClientState{Values: values}
   373  }
   374  
   375  // Get a key's value
   376  func (m *ClientState) Get(key string) (string, bool) {
   377  	if m.GetShouldFail {
   378  		return "", false
   379  	}
   380  
   381  	v, ok := m.Values[key]
   382  	return v, ok
   383  }
   384  
   385  // Put a value
   386  func (m *ClientState) Put(key, val string) { m.Values[key] = val }
   387  
   388  // Del a key/value pair
   389  func (m *ClientState) Del(key string) { delete(m.Values, key) }
   390  
   391  // ClientStateRW stores things that would originally
   392  // go in a session, or a map, in memory!
   393  type ClientStateRW struct {
   394  	ClientValues map[string]string
   395  }
   396  
   397  // NewClientRW takes the data from a client state
   398  // and returns.
   399  func NewClientRW() *ClientStateRW {
   400  	return &ClientStateRW{
   401  		ClientValues: make(map[string]string),
   402  	}
   403  }
   404  
   405  // ReadState from memory
   406  func (c *ClientStateRW) ReadState(*http.Request) (authboss.ClientState, error) {
   407  	return &ClientState{Values: c.ClientValues}, nil
   408  }
   409  
   410  // WriteState to memory
   411  func (c *ClientStateRW) WriteState(w http.ResponseWriter, cstate authboss.ClientState, cse []authboss.ClientStateEvent) error {
   412  	for _, e := range cse {
   413  		switch e.Kind {
   414  		case authboss.ClientStateEventPut:
   415  			c.ClientValues[e.Key] = e.Value
   416  		case authboss.ClientStateEventDel:
   417  			delete(c.ClientValues, e.Key)
   418  		case authboss.ClientStateEventDelAll:
   419  			c.ClientValues = make(map[string]string)
   420  		}
   421  	}
   422  
   423  	return nil
   424  }
   425  
   426  // Request returns a new request with optional key-value body (form-post)
   427  func Request(method string, postKeyValues ...string) *http.Request {
   428  	var body io.Reader
   429  	location := "http://localhost"
   430  
   431  	if len(postKeyValues) > 0 {
   432  		urlValues := make(url.Values)
   433  		for i := 0; i < len(postKeyValues); i += 2 {
   434  			urlValues.Set(postKeyValues[i], postKeyValues[i+1])
   435  		}
   436  
   437  		if method == "POST" || method == "PUT" {
   438  			body = strings.NewReader(urlValues.Encode())
   439  		} else {
   440  			location += "?" + urlValues.Encode()
   441  		}
   442  	}
   443  
   444  	req, err := http.NewRequest(method, location, body)
   445  	if err != nil {
   446  		panic(err.Error())
   447  	}
   448  
   449  	if len(postKeyValues) > 0 {
   450  		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
   451  	}
   452  
   453  	return req
   454  }
   455  
   456  // Mailer helps simplify mailer testing by storing the last sent email
   457  type Mailer struct {
   458  	Last    authboss.Email
   459  	SendErr string
   460  }
   461  
   462  // NewMailer constructs a  mailer
   463  func NewMailer() *Mailer {
   464  	return &Mailer{}
   465  }
   466  
   467  // Send an e-mail
   468  func (m *Mailer) Send(ctx context.Context, email authboss.Email) error {
   469  	if len(m.SendErr) > 0 {
   470  		return errors.New(m.SendErr)
   471  	}
   472  
   473  	m.Last = email
   474  	return nil
   475  }
   476  
   477  // AfterCallback is a callback that knows if it was called
   478  type AfterCallback struct {
   479  	HasBeenCalled bool
   480  	Fn            authboss.EventHandler
   481  }
   482  
   483  // NewAfterCallback constructs a new aftercallback.
   484  func NewAfterCallback() *AfterCallback {
   485  	m := AfterCallback{}
   486  
   487  	m.Fn = func(http.ResponseWriter, *http.Request, bool) (bool, error) {
   488  		m.HasBeenCalled = true
   489  		return false, nil
   490  	}
   491  
   492  	return &m
   493  }
   494  
   495  // Renderer mock
   496  type Renderer struct {
   497  	Pages []string
   498  
   499  	// Render call variables
   500  	Context context.Context
   501  	Page    string
   502  	Data    authboss.HTMLData
   503  }
   504  
   505  // HasLoadedViews ensures the views were loaded
   506  func (r *Renderer) HasLoadedViews(pages ...string) error {
   507  	if len(r.Pages) != len(pages) {
   508  		return errors.Errorf("want: %d loaded views, got: %d", len(pages), len(r.Pages))
   509  	}
   510  
   511  	for i, want := range pages {
   512  		got := r.Pages[i]
   513  		if want != got {
   514  			return errors.Errorf("want: %s [%d], got: %s", want, i, got)
   515  		}
   516  	}
   517  
   518  	return nil
   519  }
   520  
   521  // Load nothing but store the pages that were loaded
   522  func (r *Renderer) Load(pages ...string) error {
   523  	r.Pages = append(r.Pages, pages...)
   524  	return nil
   525  }
   526  
   527  // Render nothing, but record the arguments into the renderer
   528  func (r *Renderer) Render(ctx context.Context, page string, data authboss.HTMLData) ([]byte, string, error) {
   529  	r.Context = ctx
   530  	r.Page = page
   531  	r.Data = data
   532  	return nil, "text/html", nil
   533  }
   534  
   535  // Responder records how a request was responded to
   536  type Responder struct {
   537  	Status int
   538  	Page   string
   539  	Data   authboss.HTMLData
   540  }
   541  
   542  // Respond stores the arguments in the struct
   543  func (r *Responder) Respond(w http.ResponseWriter, req *http.Request, code int, page string, data authboss.HTMLData) error {
   544  	r.Status = code
   545  	r.Page = page
   546  	r.Data = data
   547  
   548  	return nil
   549  }
   550  
   551  // Redirector stores the redirect options passed to it and writes the Code
   552  // to the ResponseWriter.
   553  type Redirector struct {
   554  	Options authboss.RedirectOptions
   555  }
   556  
   557  // Redirect a request
   558  func (r *Redirector) Redirect(w http.ResponseWriter, req *http.Request, ro authboss.RedirectOptions) error {
   559  	r.Options = ro
   560  	if len(ro.RedirectPath) == 0 {
   561  		panic("no redirect path on redirect call")
   562  	}
   563  	http.Redirect(w, req, ro.RedirectPath, ro.Code)
   564  	return nil
   565  }
   566  
   567  // Emailer that holds the options it was given
   568  type Emailer struct {
   569  	Email authboss.Email
   570  }
   571  
   572  // Send an e-mail
   573  func (e *Emailer) Send(ctx context.Context, email authboss.Email) error {
   574  	e.Email = email
   575  	return nil
   576  }
   577  
   578  // BodyReader reads the body of a request and returns some values
   579  type BodyReader struct {
   580  	Return authboss.Validator
   581  }
   582  
   583  // Read the return values
   584  func (b BodyReader) Read(page string, r *http.Request) (authboss.Validator, error) {
   585  	return b.Return, nil
   586  }
   587  
   588  // Values is returned from the BodyReader
   589  type Values struct {
   590  	PID         string
   591  	Password    string
   592  	Token       string
   593  	Code        string
   594  	Recovery    string
   595  	PhoneNumber string
   596  	Remember    bool
   597  
   598  	Errors []error
   599  }
   600  
   601  // GetPID from values
   602  func (v Values) GetPID() string {
   603  	return v.PID
   604  }
   605  
   606  // GetPassword from values
   607  func (v Values) GetPassword() string {
   608  	return v.Password
   609  }
   610  
   611  // GetToken from values
   612  func (v Values) GetToken() string {
   613  	return v.Token
   614  }
   615  
   616  // GetCode from values
   617  func (v Values) GetCode() string {
   618  	return v.Code
   619  }
   620  
   621  // GetPhoneNumber from values
   622  func (v Values) GetPhoneNumber() string {
   623  	return v.PhoneNumber
   624  }
   625  
   626  // GetRecoveryCode from values
   627  func (v Values) GetRecoveryCode() string {
   628  	return v.Recovery
   629  }
   630  
   631  // GetShouldRemember gets the value that tells
   632  // the remember module if it should remember the user
   633  func (v Values) GetShouldRemember() bool {
   634  	return v.Remember
   635  }
   636  
   637  // Validate the values
   638  func (v Values) Validate() []error {
   639  	return v.Errors
   640  }
   641  
   642  // ArbValues is arbitrary value storage
   643  type ArbValues struct {
   644  	Values map[string]string
   645  	Errors []error
   646  }
   647  
   648  // GetPID gets the pid
   649  func (a ArbValues) GetPID() string {
   650  	return a.Values["email"]
   651  }
   652  
   653  // GetPassword gets the password
   654  func (a ArbValues) GetPassword() string {
   655  	return a.Values["password"]
   656  }
   657  
   658  // GetValues returns all values
   659  func (a ArbValues) GetValues() map[string]string {
   660  	return a.Values
   661  }
   662  
   663  // Validate nothing
   664  func (a ArbValues) Validate() []error {
   665  	return a.Errors
   666  }
   667  
   668  // Logger logs to the void
   669  type Logger struct {
   670  }
   671  
   672  // Info logging
   673  func (l Logger) Info(string) {}
   674  
   675  // Error logging
   676  func (l Logger) Error(string) {}
   677  
   678  // Router records the routes that were registered
   679  type Router struct {
   680  	Gets    []string
   681  	Posts   []string
   682  	Deletes []string
   683  }
   684  
   685  // ServeHTTP does nothing
   686  func (Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   687  }
   688  
   689  // Get records the path in the router
   690  func (r *Router) Get(path string, _ http.Handler) {
   691  	r.Gets = append(r.Gets, path)
   692  }
   693  
   694  // Post records the path in the router
   695  func (r *Router) Post(path string, _ http.Handler) {
   696  	r.Posts = append(r.Posts, path)
   697  }
   698  
   699  // Delete records the path in the router
   700  func (r *Router) Delete(path string, _ http.Handler) {
   701  	r.Deletes = append(r.Deletes, path)
   702  }
   703  
   704  // HasGets ensures all gets routes are present
   705  func (r *Router) HasGets(gets ...string) error {
   706  	return r.hasRoutes(gets, r.Gets)
   707  }
   708  
   709  // HasPosts ensures all gets routes are present
   710  func (r *Router) HasPosts(posts ...string) error {
   711  	return r.hasRoutes(posts, r.Posts)
   712  }
   713  
   714  // HasDeletes ensures all gets routes are present
   715  func (r *Router) HasDeletes(deletes ...string) error {
   716  	return r.hasRoutes(deletes, r.Deletes)
   717  }
   718  
   719  func (r *Router) hasRoutes(want []string, got []string) error {
   720  	if len(got) != len(want) {
   721  		return errors.Errorf("want: %d get routes, got: %d", len(want), len(got))
   722  	}
   723  
   724  	for i, w := range want {
   725  		g := got[i]
   726  		if w != g {
   727  			return errors.Errorf("wanted route: %s [%d], but got: %s", w, i, g)
   728  		}
   729  	}
   730  
   731  	return nil
   732  }
   733  
   734  // ErrorHandler just holds the last error
   735  type ErrorHandler struct {
   736  	Error error
   737  }
   738  
   739  // Wrap an http method
   740  func (e *ErrorHandler) Wrap(handler func(w http.ResponseWriter, r *http.Request) error) http.Handler {
   741  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   742  		if err := handler(w, r); err != nil {
   743  			e.Error = err
   744  		}
   745  	})
   746  }