63ede5c237
- refactor: move HTTP status and DTO concerns out of model and service layers into API and handler code. - feat: add admin service boundary and route auth through services instead of direct repository access. - test: add architecture and error tests covering package boundaries, redaction, and service behavior. - docs: record the protocol-neutral boundary decision and update architecture and roadmap notes.
80 lines
1.8 KiB
Go
80 lines
1.8 KiB
Go
package model
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
)
|
|
|
|
func TestAppErrorConstructors(t *testing.T) {
|
|
cause := errors.New("database offline")
|
|
|
|
tests := []struct {
|
|
name string
|
|
err *AppError
|
|
kind ErrorKind
|
|
message string
|
|
cause error
|
|
}{
|
|
{
|
|
name: "invalid argument",
|
|
err: NewInvalidArgumentError("bad input"),
|
|
kind: KindInvalidArgument,
|
|
message: "bad input",
|
|
},
|
|
{
|
|
name: "unauthenticated",
|
|
err: NewUnauthenticatedError("invalid token"),
|
|
kind: KindUnauthenticated,
|
|
message: "invalid token",
|
|
},
|
|
{
|
|
name: "permission denied",
|
|
err: NewPermissionDeniedError("access denied", ErrForbidden),
|
|
kind: KindPermissionDenied,
|
|
message: "access denied",
|
|
cause: ErrForbidden,
|
|
},
|
|
{
|
|
name: "not found",
|
|
err: NewNotFoundError("missing", ErrNotFound),
|
|
kind: KindNotFound,
|
|
message: "missing",
|
|
cause: ErrNotFound,
|
|
},
|
|
{
|
|
name: "conflict",
|
|
err: NewConflictError("duplicate"),
|
|
kind: KindConflict,
|
|
message: "duplicate",
|
|
},
|
|
{
|
|
name: "payload too large",
|
|
err: NewPayloadTooLargeError("too large", ErrUploadTooLarge),
|
|
kind: KindPayloadTooLarge,
|
|
message: "too large",
|
|
cause: ErrUploadTooLarge,
|
|
},
|
|
{
|
|
name: "internal",
|
|
err: NewInternalError("load record", cause),
|
|
kind: KindInternal,
|
|
message: "internal server error",
|
|
cause: cause,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.err.Kind != tt.kind {
|
|
t.Fatalf("Kind = %q, want %q", tt.err.Kind, tt.kind)
|
|
}
|
|
if tt.err.Message != tt.message {
|
|
t.Fatalf("Message = %q, want %q", tt.err.Message, tt.message)
|
|
}
|
|
if tt.cause != nil && !errors.Is(tt.err, tt.cause) {
|
|
t.Fatalf("errors.Is(%v, %v) = false", tt.err, tt.cause)
|
|
}
|
|
})
|
|
}
|
|
}
|