Adding application error tests
parent
0e5a46f050
commit
f8ccbe90b4
@ -0,0 +1,60 @@
|
|||||||
|
package ratchet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
const INTERNAL_ERROR_MESSAGE = "internal error"
|
||||||
|
|
||||||
|
// Application error codes.
|
||||||
|
//
|
||||||
|
// Note: these are generic codes but map well to HTTP status codes.
|
||||||
|
const (
|
||||||
|
EINTERNAL = "internal"
|
||||||
|
ENOTFOUND = "not_found"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error respresents an application specific-error. Application errors can be
|
||||||
|
// unwrapped by the caller to extract out the code and message
|
||||||
|
//
|
||||||
|
// Any non-application error (like disk error) should be reported as an EINTERNAL
|
||||||
|
// error and the human user should only see "Internal error" as the message.
|
||||||
|
// These low-level internal error details should only be logged and reported to
|
||||||
|
// the operation of the application, not the end user.
|
||||||
|
type Error struct {
|
||||||
|
// Machine-readable error code.
|
||||||
|
Code string
|
||||||
|
|
||||||
|
// Human-readable error message.
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error implements the error interface. Not used by the application otherwise.
|
||||||
|
func (e Error) Error() string {
|
||||||
|
return fmt.Sprintf("ratchet error: code=%s message=%s", e.Code, e.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorCode unwraps an application error and returns its code.
|
||||||
|
// Non-application errors always return EINTERNAL.
|
||||||
|
func ErrorCode(err error) string {
|
||||||
|
var e *Error
|
||||||
|
if err == nil {
|
||||||
|
return ""
|
||||||
|
} else if errors.As(err, &e) {
|
||||||
|
return e.Code
|
||||||
|
}
|
||||||
|
return EINTERNAL
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorMessage unwraps an application error and returns it's message.
|
||||||
|
// Non-application errors always return "Internal error".
|
||||||
|
func ErrorMessage(err error) string {
|
||||||
|
var e *Error
|
||||||
|
if err == nil {
|
||||||
|
return ""
|
||||||
|
} else if errors.As(err, &e) {
|
||||||
|
return e.Message
|
||||||
|
}
|
||||||
|
return INTERNAL_ERROR_MESSAGE
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package ratchet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AssertErrorString(t *testing.T, got, want string) {
|
||||||
|
t.Helper()
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("Incorrect error code/message got %q want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorCode(t *testing.T) {
|
||||||
|
|
||||||
|
t.Run("should return empty", func(t *testing.T) {
|
||||||
|
got := ErrorCode(nil)
|
||||||
|
|
||||||
|
AssertErrorString(t, got, "")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should return internal error", func(t *testing.T) {
|
||||||
|
want := EINTERNAL
|
||||||
|
got := ErrorCode(errors.New("Mock disk error"))
|
||||||
|
|
||||||
|
AssertErrorString(t, got, want)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should return my code", func(t *testing.T) {
|
||||||
|
e := &Error{Code: "my_code", Message: "my_message"}
|
||||||
|
got := ErrorCode(e)
|
||||||
|
|
||||||
|
AssertErrorString(t, got, e.Code)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorMessage(t *testing.T) {
|
||||||
|
t.Run("empty error should return empty string", func(t *testing.T) {
|
||||||
|
got := ErrorMessage(nil)
|
||||||
|
|
||||||
|
AssertErrorString(t, got, "")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should return internal error message", func(t *testing.T) {
|
||||||
|
got := ErrorMessage(errors.New("Mock disk error"))
|
||||||
|
|
||||||
|
AssertErrorString(t, got, INTERNAL_ERROR_MESSAGE)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should return application error message", func(t *testing.T) {
|
||||||
|
e := &Error{Message: "my_message"}
|
||||||
|
got := ErrorMessage(e)
|
||||||
|
|
||||||
|
AssertErrorString(t, got, e.Message)
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package ratchet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
|
||||||
|
// User prefered name and email
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
|
||||||
|
// Randomly generated API key for use with the API
|
||||||
|
// "-" omits the key from serialization
|
||||||
|
APIKey string `json:"-"`
|
||||||
|
|
||||||
|
// Timestamps for user creatation and last update
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
|
|
||||||
|
// List of associated Oauth authentication Objects
|
||||||
|
// Not yet implemented
|
||||||
|
// Auths []*Auth `json:"auths"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserService represents a service for managing users.
|
||||||
|
type UserService interface {
|
||||||
|
// Retrieves a user by ID along with their associated auth objects
|
||||||
|
// Returns ENOTFOUND if user does not exist.
|
||||||
|
FindUserByID(ctx context.Context, id int) (*User, error)
|
||||||
|
}
|
Loading…
Reference in New Issue