Improved code comments

master
oabrivard 1 month ago
parent 6cadc15448
commit 2fde7f1ff7

@ -15,6 +15,7 @@ type Service struct {
retention time.Duration retention time.Duration
} }
// NewService builds an admin service with a retention period in days.
func NewService(auditRepo audit.Repository, retentionDays int) *Service { func NewService(auditRepo audit.Repository, retentionDays int) *Service {
if retentionDays <= 0 { if retentionDays <= 0 {
retentionDays = 90 retentionDays = 90

@ -17,6 +17,7 @@ type AuditRepository struct {
db *sharedpostgres.Client db *sharedpostgres.Client
} }
// NewAuditRepository creates a PostgreSQL-backed audit repository.
func NewAuditRepository(db *sharedpostgres.Client) *AuditRepository { func NewAuditRepository(db *sharedpostgres.Client) *AuditRepository {
return &AuditRepository{db: db} return &AuditRepository{db: db}
} }

@ -20,6 +20,7 @@ type Handler struct {
metrics *metrics.Metrics metrics *metrics.Metrics
} }
// NewHandler creates HTTP handlers for admin endpoints.
func NewHandler(svc *appadmin.Service, logger *logging.Logger, metrics *metrics.Metrics) *Handler { func NewHandler(svc *appadmin.Service, logger *logging.Logger, metrics *metrics.Metrics) *Handler {
return &Handler{svc: svc, logger: logger, metrics: metrics} return &Handler{svc: svc, logger: logger, metrics: metrics}
} }

@ -1,5 +1,7 @@
package tests package tests
// integration_http_test.go contains tests for backend behavior.
import ( import (
"context" "context"
"encoding/json" "encoding/json"
@ -28,12 +30,15 @@ type inMemoryAuditRepo struct {
appendErr error appendErr error
} }
// newInMemoryAuditRepo is a test helper.
func newInMemoryAuditRepo() *inMemoryAuditRepo { func newInMemoryAuditRepo() *inMemoryAuditRepo {
return &inMemoryAuditRepo{entries: make([]audit.Entry, 0)} return &inMemoryAuditRepo{entries: make([]audit.Entry, 0)}
} }
// EnsureSchema is a test helper.
func (r *inMemoryAuditRepo) EnsureSchema(ctx context.Context) error { return nil } func (r *inMemoryAuditRepo) EnsureSchema(ctx context.Context) error { return nil }
// Append is a test helper.
func (r *inMemoryAuditRepo) Append(ctx context.Context, e audit.Entry) error { func (r *inMemoryAuditRepo) Append(ctx context.Context, e audit.Entry) error {
if r.appendErr != nil { if r.appendErr != nil {
return r.appendErr return r.appendErr
@ -42,6 +47,7 @@ func (r *inMemoryAuditRepo) Append(ctx context.Context, e audit.Entry) error {
return nil return nil
} }
// List is a test helper.
func (r *inMemoryAuditRepo) List(ctx context.Context, limit, offset int) ([]audit.Entry, error) { func (r *inMemoryAuditRepo) List(ctx context.Context, limit, offset int) ([]audit.Entry, error) {
if r.listErr != nil { if r.listErr != nil {
return nil, r.listErr return nil, r.listErr
@ -58,6 +64,7 @@ func (r *inMemoryAuditRepo) List(ctx context.Context, limit, offset int) ([]audi
return out, nil return out, nil
} }
// Count is a test helper.
func (r *inMemoryAuditRepo) Count(ctx context.Context) (int64, error) { func (r *inMemoryAuditRepo) Count(ctx context.Context) (int64, error) {
if r.countErr != nil { if r.countErr != nil {
return 0, r.countErr return 0, r.countErr
@ -65,10 +72,12 @@ func (r *inMemoryAuditRepo) Count(ctx context.Context) (int64, error) {
return int64(len(r.entries)), nil return int64(len(r.entries)), nil
} }
// PruneBefore is a test helper.
func (r *inMemoryAuditRepo) PruneBefore(ctx context.Context, before time.Time) (int64, error) { func (r *inMemoryAuditRepo) PruneBefore(ctx context.Context, before time.Time) (int64, error) {
return 0, nil return 0, nil
} }
// setupApp is a test helper.
func setupApp(t *testing.T, repo *inMemoryAuditRepo) *fiber.App { func setupApp(t *testing.T, repo *inMemoryAuditRepo) *fiber.App {
t.Helper() t.Helper()
@ -105,6 +114,7 @@ func setupApp(t *testing.T, repo *inMemoryAuditRepo) *fiber.App {
return app return app
} }
// TestAdminAuthRoute verifies expected behavior.
func TestAdminAuthRoute(t *testing.T) { func TestAdminAuthRoute(t *testing.T) {
repo := newInMemoryAuditRepo() repo := newInMemoryAuditRepo()
app := setupApp(t, repo) app := setupApp(t, repo)
@ -123,6 +133,7 @@ func TestAdminAuthRoute(t *testing.T) {
} }
} }
// TestDashboardRouteSuccessAndErrors verifies expected behavior.
func TestDashboardRouteSuccessAndErrors(t *testing.T) { func TestDashboardRouteSuccessAndErrors(t *testing.T) {
t.Run("forbidden for non-admin", func(t *testing.T) { t.Run("forbidden for non-admin", func(t *testing.T) {
repo := newInMemoryAuditRepo() repo := newInMemoryAuditRepo()
@ -170,6 +181,7 @@ func TestDashboardRouteSuccessAndErrors(t *testing.T) {
}) })
} }
// TestAuditRouteSuccessAndErrors verifies expected behavior.
func TestAuditRouteSuccessAndErrors(t *testing.T) { func TestAuditRouteSuccessAndErrors(t *testing.T) {
t.Run("forbidden for non-admin", func(t *testing.T) { t.Run("forbidden for non-admin", func(t *testing.T) {
repo := newInMemoryAuditRepo() repo := newInMemoryAuditRepo()
@ -221,6 +233,7 @@ func TestAuditRouteSuccessAndErrors(t *testing.T) {
}) })
} }
// TestRegisterRoutesDoesNotPanic verifies expected behavior.
func TestRegisterRoutesDoesNotPanic(t *testing.T) { func TestRegisterRoutesDoesNotPanic(t *testing.T) {
app := fiber.New() app := fiber.New()
repo := newInMemoryAuditRepo() repo := newInMemoryAuditRepo()

@ -1,5 +1,7 @@
package session package session
// service_test.go contains tests for backend behavior.
import ( import (
"context" "context"
"errors" "errors"
@ -17,6 +19,7 @@ type fakeRepo struct {
events []*domain.SessionEvent events []*domain.SessionEvent
} }
// newFakeRepo is a test helper.
func newFakeRepo() *fakeRepo { func newFakeRepo() *fakeRepo {
return &fakeRepo{ return &fakeRepo{
sessions: map[string]*domain.GameSession{}, sessions: map[string]*domain.GameSession{},
@ -25,8 +28,10 @@ func newFakeRepo() *fakeRepo {
} }
} }
// EnsureSchema is a test helper.
func (r *fakeRepo) EnsureSchema(ctx context.Context) error { return nil } func (r *fakeRepo) EnsureSchema(ctx context.Context) error { return nil }
// CreateSession is a test helper.
func (r *fakeRepo) CreateSession(ctx context.Context, session *domain.GameSession) (*domain.GameSession, error) { func (r *fakeRepo) CreateSession(ctx context.Context, session *domain.GameSession) (*domain.GameSession, error) {
session.ID = "sess-1" session.ID = "sess-1"
now := time.Now().UTC() now := time.Now().UTC()
@ -37,6 +42,7 @@ func (r *fakeRepo) CreateSession(ctx context.Context, session *domain.GameSessio
return &cp, nil return &cp, nil
} }
// GetSessionByID is a test helper.
func (r *fakeRepo) GetSessionByID(ctx context.Context, id string) (*domain.GameSession, error) { func (r *fakeRepo) GetSessionByID(ctx context.Context, id string) (*domain.GameSession, error) {
s, ok := r.sessions[id] s, ok := r.sessions[id]
if !ok { if !ok {
@ -46,6 +52,7 @@ func (r *fakeRepo) GetSessionByID(ctx context.Context, id string) (*domain.GameS
return &cp, nil return &cp, nil
} }
// GetActiveSessionByPlayerID is a test helper.
func (r *fakeRepo) GetActiveSessionByPlayerID(ctx context.Context, playerID string) (*domain.GameSession, error) { func (r *fakeRepo) GetActiveSessionByPlayerID(ctx context.Context, playerID string) (*domain.GameSession, error) {
for _, s := range r.sessions { for _, s := range r.sessions {
if s.PlayerID == playerID && s.Status == domain.StatusActive { if s.PlayerID == playerID && s.Status == domain.StatusActive {
@ -56,6 +63,7 @@ func (r *fakeRepo) GetActiveSessionByPlayerID(ctx context.Context, playerID stri
return nil, domain.ErrSessionNotFound return nil, domain.ErrSessionNotFound
} }
// UpdateSession is a test helper.
func (r *fakeRepo) UpdateSession(ctx context.Context, session *domain.GameSession) (*domain.GameSession, error) { func (r *fakeRepo) UpdateSession(ctx context.Context, session *domain.GameSession) (*domain.GameSession, error) {
if _, ok := r.sessions[session.ID]; !ok { if _, ok := r.sessions[session.ID]; !ok {
return nil, domain.ErrSessionNotFound return nil, domain.ErrSessionNotFound
@ -66,18 +74,21 @@ func (r *fakeRepo) UpdateSession(ctx context.Context, session *domain.GameSessio
return &cp, nil return &cp, nil
} }
// CreateAttempt is a test helper.
func (r *fakeRepo) CreateAttempt(ctx context.Context, attempt *domain.SessionAttempt) error { func (r *fakeRepo) CreateAttempt(ctx context.Context, attempt *domain.SessionAttempt) error {
cp := *attempt cp := *attempt
r.attempts = append(r.attempts, &cp) r.attempts = append(r.attempts, &cp)
return nil return nil
} }
// CreateEvent is a test helper.
func (r *fakeRepo) CreateEvent(ctx context.Context, event *domain.SessionEvent) error { func (r *fakeRepo) CreateEvent(ctx context.Context, event *domain.SessionEvent) error {
cp := *event cp := *event
r.events = append(r.events, &cp) r.events = append(r.events, &cp)
return nil return nil
} }
// ListQuestionIDsForSession is a test helper.
func (r *fakeRepo) ListQuestionIDsForSession(ctx context.Context, sessionID string) ([]string, error) { func (r *fakeRepo) ListQuestionIDsForSession(ctx context.Context, sessionID string) ([]string, error) {
seen := map[string]bool{} seen := map[string]bool{}
ids := make([]string, 0) ids := make([]string, 0)
@ -100,6 +111,7 @@ type fakeQuestionBank struct {
answerOK bool answerOK bool
} }
// GetRandomQuestion is a test helper.
func (f *fakeQuestionBank) GetRandomQuestion( func (f *fakeQuestionBank) GetRandomQuestion(
ctx context.Context, ctx context.Context,
exclusions []string, exclusions []string,
@ -119,6 +131,7 @@ func (f *fakeQuestionBank) GetRandomQuestion(
return nil, sharederrors.New(sharederrors.CodeNoQuestionsAvailable, "no questions available") return nil, sharederrors.New(sharederrors.CodeNoQuestionsAvailable, "no questions available")
} }
// GetQuestionByID is a test helper.
func (f *fakeQuestionBank) GetQuestionByID(ctx context.Context, id string) (*SessionQuestion, error) { func (f *fakeQuestionBank) GetQuestionByID(ctx context.Context, id string) (*SessionQuestion, error) {
for _, q := range f.questions { for _, q := range f.questions {
if q.ID == id { if q.ID == id {
@ -129,6 +142,7 @@ func (f *fakeQuestionBank) GetQuestionByID(ctx context.Context, id string) (*Ses
return nil, sharederrors.New(sharederrors.CodeQuestionNotFound, "question not found") return nil, sharederrors.New(sharederrors.CodeQuestionNotFound, "question not found")
} }
// ValidateAnswer is a test helper.
func (f *fakeQuestionBank) ValidateAnswer( func (f *fakeQuestionBank) ValidateAnswer(
ctx context.Context, ctx context.Context,
questionID, answer string, questionID, answer string,
@ -141,6 +155,7 @@ type fakeUserClient struct {
profile UserProfile profile UserProfile
} }
// GetUserProfile is a test helper.
func (f *fakeUserClient) GetUserProfile(ctx context.Context, userID, bearerToken string) (*UserProfile, error) { func (f *fakeUserClient) GetUserProfile(ctx context.Context, userID, bearerToken string) (*UserProfile, error) {
p := f.profile p := f.profile
if p.ID == "" { if p.ID == "" {
@ -156,6 +171,7 @@ type fakeStateStore struct {
locks map[string]bool locks map[string]bool
} }
// newFakeStateStore is a test helper.
func newFakeStateStore() *fakeStateStore { func newFakeStateStore() *fakeStateStore {
return &fakeStateStore{ return &fakeStateStore{
active: map[string]string{}, active: map[string]string{},
@ -164,30 +180,43 @@ func newFakeStateStore() *fakeStateStore {
} }
} }
// GetActiveSession is a test helper.
func (s *fakeStateStore) GetActiveSession(ctx context.Context, playerID string) (string, bool) { func (s *fakeStateStore) GetActiveSession(ctx context.Context, playerID string) (string, bool) {
id, ok := s.active[playerID] id, ok := s.active[playerID]
return id, ok return id, ok
} }
// SetActiveSession is a test helper.
func (s *fakeStateStore) SetActiveSession(ctx context.Context, playerID, sessionID string, ttl time.Duration) error { func (s *fakeStateStore) SetActiveSession(ctx context.Context, playerID, sessionID string, ttl time.Duration) error {
s.active[playerID] = sessionID s.active[playerID] = sessionID
return nil return nil
} }
// ClearActiveSession is a test helper.
func (s *fakeStateStore) ClearActiveSession(ctx context.Context, playerID string) error { func (s *fakeStateStore) ClearActiveSession(ctx context.Context, playerID string) error {
delete(s.active, playerID) delete(s.active, playerID)
return nil return nil
} }
// SetTimer is a test helper.
func (s *fakeStateStore) SetTimer(ctx context.Context, sessionID string, expiresAt time.Time, ttl time.Duration) error { func (s *fakeStateStore) SetTimer(ctx context.Context, sessionID string, expiresAt time.Time, ttl time.Duration) error {
s.timers[sessionID] = expiresAt s.timers[sessionID] = expiresAt
return nil return nil
} }
// GetTimer is a test helper.
func (s *fakeStateStore) GetTimer(ctx context.Context, sessionID string) (time.Time, bool) { func (s *fakeStateStore) GetTimer(ctx context.Context, sessionID string) (time.Time, bool) {
t, ok := s.timers[sessionID] t, ok := s.timers[sessionID]
return t, ok return t, ok
} }
// ClearTimer is a test helper.
func (s *fakeStateStore) ClearTimer(ctx context.Context, sessionID string) error { func (s *fakeStateStore) ClearTimer(ctx context.Context, sessionID string) error {
delete(s.timers, sessionID) delete(s.timers, sessionID)
return nil return nil
} }
// AcquireLock is a test helper.
func (s *fakeStateStore) AcquireLock(ctx context.Context, sessionID string, ttl time.Duration) bool { func (s *fakeStateStore) AcquireLock(ctx context.Context, sessionID string, ttl time.Duration) bool {
if s.locks[sessionID] { if s.locks[sessionID] {
return false return false
@ -195,6 +224,8 @@ func (s *fakeStateStore) AcquireLock(ctx context.Context, sessionID string, ttl
s.locks[sessionID] = true s.locks[sessionID] = true
return true return true
} }
// ReleaseLock is a test helper.
func (s *fakeStateStore) ReleaseLock(ctx context.Context, sessionID string) { func (s *fakeStateStore) ReleaseLock(ctx context.Context, sessionID string) {
delete(s.locks, sessionID) delete(s.locks, sessionID)
} }

@ -1,5 +1,7 @@
package http package http
// handler_unit_test.go contains tests for backend behavior.
import ( import (
"bytes" "bytes"
"net/http" "net/http"
@ -11,6 +13,7 @@ import (
"knowfoolery/backend/shared/infra/utils/validation" "knowfoolery/backend/shared/infra/utils/validation"
) )
// TestUnauthorizedBranches verifies expected behavior.
func TestUnauthorizedBranches(t *testing.T) { func TestUnauthorizedBranches(t *testing.T) {
h := NewHandler(nil, validation.NewValidator(), nil, nil) h := NewHandler(nil, validation.NewValidator(), nil, nil)
app := fiber.New() app := fiber.New()
@ -45,6 +48,7 @@ func TestUnauthorizedBranches(t *testing.T) {
} }
} }
// TestStartAndEndValidationBranches verifies expected behavior.
func TestStartAndEndValidationBranches(t *testing.T) { func TestStartAndEndValidationBranches(t *testing.T) {
h := NewHandler(nil, validation.NewValidator(), nil, nil) h := NewHandler(nil, validation.NewValidator(), nil, nil)
app := fiber.New() app := fiber.New()
@ -103,6 +107,7 @@ func TestStartAndEndValidationBranches(t *testing.T) {
} }
// TestBearerTokenAndClaimsHelpers verifies expected behavior.
func TestBearerTokenAndClaimsHelpers(t *testing.T) { func TestBearerTokenAndClaimsHelpers(t *testing.T) {
app := fiber.New() app := fiber.New()
app.Use(func(c fiber.Ctx) error { app.Use(func(c fiber.Ctx) error {

@ -29,6 +29,7 @@ type inMemoryRepo struct {
attempts []*domain.SessionAttempt attempts []*domain.SessionAttempt
} }
// newInMemoryRepo is a test helper.
func newInMemoryRepo() *inMemoryRepo { func newInMemoryRepo() *inMemoryRepo {
return &inMemoryRepo{ return &inMemoryRepo{
sessions: map[string]*domain.GameSession{}, sessions: map[string]*domain.GameSession{},
@ -36,8 +37,10 @@ func newInMemoryRepo() *inMemoryRepo {
} }
} }
// EnsureSchema is a test helper.
func (r *inMemoryRepo) EnsureSchema(ctx context.Context) error { return nil } func (r *inMemoryRepo) EnsureSchema(ctx context.Context) error { return nil }
// CreateSession is a test helper.
func (r *inMemoryRepo) CreateSession(ctx context.Context, session *domain.GameSession) (*domain.GameSession, error) { func (r *inMemoryRepo) CreateSession(ctx context.Context, session *domain.GameSession) (*domain.GameSession, error) {
session.ID = "sess-1" session.ID = "sess-1"
now := time.Now().UTC() now := time.Now().UTC()
@ -48,6 +51,7 @@ func (r *inMemoryRepo) CreateSession(ctx context.Context, session *domain.GameSe
return &cp, nil return &cp, nil
} }
// GetSessionByID is a test helper.
func (r *inMemoryRepo) GetSessionByID(ctx context.Context, id string) (*domain.GameSession, error) { func (r *inMemoryRepo) GetSessionByID(ctx context.Context, id string) (*domain.GameSession, error) {
s, ok := r.sessions[id] s, ok := r.sessions[id]
if !ok { if !ok {
@ -57,6 +61,7 @@ func (r *inMemoryRepo) GetSessionByID(ctx context.Context, id string) (*domain.G
return &cp, nil return &cp, nil
} }
// GetActiveSessionByPlayerID is a test helper.
func (r *inMemoryRepo) GetActiveSessionByPlayerID(ctx context.Context, playerID string) (*domain.GameSession, error) { func (r *inMemoryRepo) GetActiveSessionByPlayerID(ctx context.Context, playerID string) (*domain.GameSession, error) {
for _, s := range r.sessions { for _, s := range r.sessions {
if s.PlayerID == playerID && s.Status == domain.StatusActive { if s.PlayerID == playerID && s.Status == domain.StatusActive {
@ -67,6 +72,7 @@ func (r *inMemoryRepo) GetActiveSessionByPlayerID(ctx context.Context, playerID
return nil, domain.ErrSessionNotFound return nil, domain.ErrSessionNotFound
} }
// UpdateSession is a test helper.
func (r *inMemoryRepo) UpdateSession(ctx context.Context, session *domain.GameSession) (*domain.GameSession, error) { func (r *inMemoryRepo) UpdateSession(ctx context.Context, session *domain.GameSession) (*domain.GameSession, error) {
cp := *session cp := *session
cp.UpdatedAt = time.Now().UTC() cp.UpdatedAt = time.Now().UTC()
@ -74,14 +80,17 @@ func (r *inMemoryRepo) UpdateSession(ctx context.Context, session *domain.GameSe
return &cp, nil return &cp, nil
} }
// CreateAttempt is a test helper.
func (r *inMemoryRepo) CreateAttempt(ctx context.Context, attempt *domain.SessionAttempt) error { func (r *inMemoryRepo) CreateAttempt(ctx context.Context, attempt *domain.SessionAttempt) error {
cp := *attempt cp := *attempt
r.attempts = append(r.attempts, &cp) r.attempts = append(r.attempts, &cp)
return nil return nil
} }
// CreateEvent is a test helper.
func (r *inMemoryRepo) CreateEvent(ctx context.Context, event *domain.SessionEvent) error { return nil } func (r *inMemoryRepo) CreateEvent(ctx context.Context, event *domain.SessionEvent) error { return nil }
// ListQuestionIDsForSession is a test helper.
func (r *inMemoryRepo) ListQuestionIDsForSession(ctx context.Context, sessionID string) ([]string, error) { func (r *inMemoryRepo) ListQuestionIDsForSession(ctx context.Context, sessionID string) ([]string, error) {
seen := map[string]bool{} seen := map[string]bool{}
ids := make([]string, 0) ids := make([]string, 0)
@ -103,6 +112,7 @@ type fakeQuestionBank struct {
questions []appsession.SessionQuestion questions []appsession.SessionQuestion
} }
// GetRandomQuestion is a test helper.
func (f *fakeQuestionBank) GetRandomQuestion( func (f *fakeQuestionBank) GetRandomQuestion(
ctx context.Context, ctx context.Context,
exclusions []string, exclusions []string,
@ -122,6 +132,7 @@ func (f *fakeQuestionBank) GetRandomQuestion(
return nil, domain.ErrSessionNotFound return nil, domain.ErrSessionNotFound
} }
// GetQuestionByID is a test helper.
func (f *fakeQuestionBank) GetQuestionByID(ctx context.Context, id string) (*appsession.SessionQuestion, error) { func (f *fakeQuestionBank) GetQuestionByID(ctx context.Context, id string) (*appsession.SessionQuestion, error) {
for _, q := range f.questions { for _, q := range f.questions {
if q.ID == id { if q.ID == id {
@ -132,6 +143,7 @@ func (f *fakeQuestionBank) GetQuestionByID(ctx context.Context, id string) (*app
return nil, domain.ErrSessionNotFound return nil, domain.ErrSessionNotFound
} }
// ValidateAnswer is a test helper.
func (f *fakeQuestionBank) ValidateAnswer( func (f *fakeQuestionBank) ValidateAnswer(
ctx context.Context, ctx context.Context,
questionID, answer string, questionID, answer string,
@ -145,6 +157,7 @@ func (f *fakeQuestionBank) ValidateAnswer(
// fakeUserClient returns a verified user profile for tests. // fakeUserClient returns a verified user profile for tests.
type fakeUserClient struct{} type fakeUserClient struct{}
// GetUserProfile is a test helper.
func (f *fakeUserClient) GetUserProfile( func (f *fakeUserClient) GetUserProfile(
ctx context.Context, ctx context.Context,
userID, bearerToken string, userID, bearerToken string,
@ -162,6 +175,7 @@ type fakeStateStore struct {
locks map[string]bool locks map[string]bool
} }
// newFakeStateStore is a test helper.
func newFakeStateStore() *fakeStateStore { func newFakeStateStore() *fakeStateStore {
return &fakeStateStore{ return &fakeStateStore{
active: map[string]string{}, active: map[string]string{},
@ -169,25 +183,38 @@ func newFakeStateStore() *fakeStateStore {
} }
} }
// GetActiveSession is a test helper.
func (s *fakeStateStore) GetActiveSession(ctx context.Context, playerID string) (string, bool) { func (s *fakeStateStore) GetActiveSession(ctx context.Context, playerID string) (string, bool) {
id, ok := s.active[playerID] id, ok := s.active[playerID]
return id, ok return id, ok
} }
// SetActiveSession is a test helper.
func (s *fakeStateStore) SetActiveSession(ctx context.Context, playerID, sessionID string, ttl time.Duration) error { func (s *fakeStateStore) SetActiveSession(ctx context.Context, playerID, sessionID string, ttl time.Duration) error {
s.active[playerID] = sessionID s.active[playerID] = sessionID
return nil return nil
} }
// ClearActiveSession is a test helper.
func (s *fakeStateStore) ClearActiveSession(ctx context.Context, playerID string) error { func (s *fakeStateStore) ClearActiveSession(ctx context.Context, playerID string) error {
delete(s.active, playerID) delete(s.active, playerID)
return nil return nil
} }
// SetTimer is a test helper.
func (s *fakeStateStore) SetTimer(ctx context.Context, sessionID string, expiresAt time.Time, ttl time.Duration) error { func (s *fakeStateStore) SetTimer(ctx context.Context, sessionID string, expiresAt time.Time, ttl time.Duration) error {
return nil return nil
} }
// GetTimer is a test helper.
func (s *fakeStateStore) GetTimer(ctx context.Context, sessionID string) (time.Time, bool) { func (s *fakeStateStore) GetTimer(ctx context.Context, sessionID string) (time.Time, bool) {
return time.Time{}, false return time.Time{}, false
} }
// ClearTimer is a test helper.
func (s *fakeStateStore) ClearTimer(ctx context.Context, sessionID string) error { return nil } func (s *fakeStateStore) ClearTimer(ctx context.Context, sessionID string) error { return nil }
// AcquireLock is a test helper.
func (s *fakeStateStore) AcquireLock(ctx context.Context, sessionID string, ttl time.Duration) bool { func (s *fakeStateStore) AcquireLock(ctx context.Context, sessionID string, ttl time.Duration) bool {
if s.locks[sessionID] { if s.locks[sessionID] {
return false return false
@ -195,6 +222,8 @@ func (s *fakeStateStore) AcquireLock(ctx context.Context, sessionID string, ttl
s.locks[sessionID] = true s.locks[sessionID] = true
return true return true
} }
// ReleaseLock is a test helper.
func (s *fakeStateStore) ReleaseLock(ctx context.Context, sessionID string) { func (s *fakeStateStore) ReleaseLock(ctx context.Context, sessionID string) {
delete(s.locks, sessionID) delete(s.locks, sessionID)
} }
@ -343,6 +372,7 @@ func TestMetricsEndpoint(t *testing.T) {
assertStatus(t, resp, http.StatusOK, "metrics failed") assertStatus(t, resp, http.StatusOK, "metrics failed")
} }
// mustJSONRequest is a test helper.
func mustJSONRequest( func mustJSONRequest(
t *testing.T, t *testing.T,
app *fiber.App, app *fiber.App,
@ -369,6 +399,7 @@ func mustJSONRequest(
return sharedhttpx.MustTest(t, app, req) return sharedhttpx.MustTest(t, app, req)
} }
// assertStatus is a test helper.
func assertStatus(t *testing.T, resp *http.Response, want int, msg string) { func assertStatus(t *testing.T, resp *http.Response, want int, msg string) {
t.Helper() t.Helper()
if resp.StatusCode != want { if resp.StatusCode != want {
@ -376,6 +407,7 @@ func assertStatus(t *testing.T, resp *http.Response, want int, msg string) {
} }
} }
// decodeDataMap is a test helper.
func decodeDataMap(t *testing.T, resp *http.Response) map[string]any { func decodeDataMap(t *testing.T, resp *http.Response) map[string]any {
t.Helper() t.Helper()
@ -389,6 +421,7 @@ func decodeDataMap(t *testing.T, resp *http.Response) map[string]any {
return payload.Data return payload.Data
} }
// asMap is a test helper.
func asMap(t *testing.T, v any) map[string]any { func asMap(t *testing.T, v any) map[string]any {
t.Helper() t.Helper()
m, ok := v.(map[string]any) m, ok := v.(map[string]any)
@ -398,6 +431,7 @@ func asMap(t *testing.T, v any) map[string]any {
return m return m
} }
// asString is a test helper.
func asString(t *testing.T, v any) string { func asString(t *testing.T, v any) string {
t.Helper() t.Helper()
s, ok := v.(string) s, ok := v.(string)
@ -407,6 +441,7 @@ func asString(t *testing.T, v any) string {
return s return s
} }
// decodeAny is a test helper.
func decodeAny(t *testing.T, resp *http.Response) map[string]any { func decodeAny(t *testing.T, resp *http.Response) map[string]any {
t.Helper() t.Helper()

@ -1,5 +1,7 @@
package middleware package middleware
// middleware_test.go contains tests for backend behavior.
import ( import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@ -16,6 +18,7 @@ import (
"knowfoolery/backend/shared/infra/observability/logging" "knowfoolery/backend/shared/infra/observability/logging"
) )
// TestCORSMiddleware verifies expected behavior.
func TestCORSMiddleware(t *testing.T) { func TestCORSMiddleware(t *testing.T) {
app := fiber.New() app := fiber.New()
app.Use(CORS(gconfig.CORSConfig{ app.Use(CORS(gconfig.CORSConfig{
@ -66,6 +69,7 @@ func TestCORSMiddleware(t *testing.T) {
} }
} }
// TestCORSAllowAll verifies expected behavior.
func TestCORSAllowAll(t *testing.T) { func TestCORSAllowAll(t *testing.T) {
app := fiber.New() app := fiber.New()
app.Use(CORS(gconfig.CORSConfig{AllowedOrigins: []string{"*"}})) app.Use(CORS(gconfig.CORSConfig{AllowedOrigins: []string{"*"}}))
@ -83,6 +87,7 @@ func TestCORSAllowAll(t *testing.T) {
} }
} }
// TestSecurityHeadersMiddleware verifies expected behavior.
func TestSecurityHeadersMiddleware(t *testing.T) { func TestSecurityHeadersMiddleware(t *testing.T) {
app := fiber.New() app := fiber.New()
app.Use(SecurityHeaders(gconfig.SecurityHeadersConfig{ app.Use(SecurityHeaders(gconfig.SecurityHeadersConfig{
@ -118,6 +123,7 @@ func TestSecurityHeadersMiddleware(t *testing.T) {
} }
} }
// TestSecurityHeadersNoHSTSOnHTTP verifies expected behavior.
func TestSecurityHeadersNoHSTSOnHTTP(t *testing.T) { func TestSecurityHeadersNoHSTSOnHTTP(t *testing.T) {
app := fiber.New() app := fiber.New()
app.Use(SecurityHeaders(gconfig.SecurityHeadersConfig{ app.Use(SecurityHeaders(gconfig.SecurityHeadersConfig{
@ -137,6 +143,7 @@ func TestSecurityHeadersNoHSTSOnHTTP(t *testing.T) {
} }
} }
// TestRequestContextMiddlewareAndRequestID verifies expected behavior.
func TestRequestContextMiddlewareAndRequestID(t *testing.T) { func TestRequestContextMiddlewareAndRequestID(t *testing.T) {
app := fiber.New() app := fiber.New()
app.Use(RequestContext(nil)) app.Use(RequestContext(nil))
@ -172,6 +179,7 @@ func TestRequestContextMiddlewareAndRequestID(t *testing.T) {
} }
} }
// TestRequestContextWithLoggerAndInvalidRequestIDLocal verifies expected behavior.
func TestRequestContextWithLoggerAndInvalidRequestIDLocal(t *testing.T) { func TestRequestContextWithLoggerAndInvalidRequestIDLocal(t *testing.T) {
logger := logging.NewLogger(logging.DefaultConfig()) logger := logging.NewLogger(logging.DefaultConfig())
app := fiber.New() app := fiber.New()
@ -195,6 +203,7 @@ func TestRequestContextWithLoggerAndInvalidRequestIDLocal(t *testing.T) {
} }
} }
// TestRateLimitMiddlewareDegradedModeAndHelpers verifies expected behavior.
func TestRateLimitMiddlewareDegradedModeAndHelpers(t *testing.T) { func TestRateLimitMiddlewareDegradedModeAndHelpers(t *testing.T) {
app := fiber.New() app := fiber.New()
mw := RateLimitMiddleware(nil, gconfig.RateLimitConfig{ mw := RateLimitMiddleware(nil, gconfig.RateLimitConfig{
@ -296,6 +305,7 @@ func TestRateLimitMiddlewareDegradedModeAndHelpers(t *testing.T) {
} }
} }
// TestRateLimitMiddlewareRedisAllowedAndBlocked verifies expected behavior.
func TestRateLimitMiddlewareRedisAllowedAndBlocked(t *testing.T) { func TestRateLimitMiddlewareRedisAllowedAndBlocked(t *testing.T) {
mr, err := miniredis.Run() mr, err := miniredis.Run()
if err != nil { if err != nil {
@ -348,6 +358,7 @@ func TestRateLimitMiddlewareRedisAllowedAndBlocked(t *testing.T) {
} }
} }
// TestRateLimitMiddlewareRedisErrorDegrades verifies expected behavior.
func TestRateLimitMiddlewareRedisErrorDegrades(t *testing.T) { func TestRateLimitMiddlewareRedisErrorDegrades(t *testing.T) {
// Use a client pointing to an unreachable endpoint to force script.Run error. // Use a client pointing to an unreachable endpoint to force script.Run error.
client := redisv9.NewClient(&redisv9.Options{Addr: "127.0.0.1:1"}) client := redisv9.NewClient(&redisv9.Options{Addr: "127.0.0.1:1"})
@ -379,6 +390,7 @@ func TestRateLimitMiddlewareRedisErrorDegrades(t *testing.T) {
} }
} }
// TestIdentifyRequester verifies expected behavior.
func TestIdentifyRequester(t *testing.T) { func TestIdentifyRequester(t *testing.T) {
app := fiber.New() app := fiber.New()
app.Get("/id", func(c fiber.Ctx) error { app.Get("/id", func(c fiber.Ctx) error {

@ -1,5 +1,7 @@
package tests package tests
// integration_http_test.go contains tests for backend behavior.
import ( import (
"io" "io"
"net/http" "net/http"
@ -19,6 +21,7 @@ import (
"knowfoolery/backend/shared/infra/auth/zitadel" "knowfoolery/backend/shared/infra/auth/zitadel"
) )
// TestGateway_PublicRoute_ProxiesAndRewritesPath verifies expected behavior.
func TestGateway_PublicRoute_ProxiesAndRewritesPath(t *testing.T) { func TestGateway_PublicRoute_ProxiesAndRewritesPath(t *testing.T) {
t.Parallel() t.Parallel()
@ -46,6 +49,7 @@ func TestGateway_PublicRoute_ProxiesAndRewritesPath(t *testing.T) {
require.Equal(t, "degraded", res.Header.Get("X-RateLimit-Policy")) require.Equal(t, "degraded", res.Header.Get("X-RateLimit-Policy"))
} }
// TestGateway_ProtectedRoute_RequiresAuth verifies expected behavior.
func TestGateway_ProtectedRoute_RequiresAuth(t *testing.T) { func TestGateway_ProtectedRoute_RequiresAuth(t *testing.T) {
t.Parallel() t.Parallel()
@ -63,6 +67,7 @@ func TestGateway_ProtectedRoute_RequiresAuth(t *testing.T) {
require.Equal(t, http.StatusUnauthorized, res.StatusCode) require.Equal(t, http.StatusUnauthorized, res.StatusCode)
} }
// TestGateway_ProtectedRoute_ForwardsUserHeaders verifies expected behavior.
func TestGateway_ProtectedRoute_ForwardsUserHeaders(t *testing.T) { func TestGateway_ProtectedRoute_ForwardsUserHeaders(t *testing.T) {
t.Parallel() t.Parallel()
@ -90,6 +95,7 @@ func TestGateway_ProtectedRoute_ForwardsUserHeaders(t *testing.T) {
require.Equal(t, "true", received["x-user-mfa"]) require.Equal(t, "true", received["x-user-mfa"])
} }
// TestGateway_PreflightCors verifies expected behavior.
func TestGateway_PreflightCors(t *testing.T) { func TestGateway_PreflightCors(t *testing.T) {
t.Parallel() t.Parallel()
@ -110,6 +116,7 @@ func TestGateway_PreflightCors(t *testing.T) {
require.Equal(t, "http://localhost:5173", res.Header.Get("Access-Control-Allow-Origin")) require.Equal(t, "http://localhost:5173", res.Header.Get("Access-Control-Allow-Origin"))
} }
// buildTestApp is a test helper.
func buildTestApp(t *testing.T, upstreamURL string, client *http.Client) *fiber.App { func buildTestApp(t *testing.T, upstreamURL string, client *http.Client) *fiber.App {
t.Helper() t.Helper()
@ -179,10 +186,12 @@ func buildTestApp(t *testing.T, upstreamURL string, client *http.Client) *fiber.
type roundTripperFunc func(*http.Request) (*http.Response, error) type roundTripperFunc func(*http.Request) (*http.Response, error)
// RoundTrip is a test helper.
func (fn roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) { func (fn roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return fn(req) return fn(req)
} }
// jsonResponse is a test helper.
func jsonResponse(status int, body string) *http.Response { func jsonResponse(status int, body string) *http.Response {
return &http.Response{ return &http.Response{
StatusCode: status, StatusCode: status,

@ -1,5 +1,7 @@
package leaderboard package leaderboard
// service_test.go contains tests for backend behavior.
import ( import (
"context" "context"
"encoding/json" "encoding/json"
@ -23,6 +25,7 @@ type fakeRepo struct {
globalErr error globalErr error
} }
// newFakeRepo is a test helper.
func newFakeRepo() *fakeRepo { func newFakeRepo() *fakeRepo {
return &fakeRepo{ return &fakeRepo{
entries: make([]*domain.LeaderboardEntry, 0), entries: make([]*domain.LeaderboardEntry, 0),
@ -30,8 +33,10 @@ func newFakeRepo() *fakeRepo {
} }
} }
// EnsureSchema is a test helper.
func (r *fakeRepo) EnsureSchema(ctx context.Context) error { return nil } func (r *fakeRepo) EnsureSchema(ctx context.Context) error { return nil }
// IngestEntry is a test helper.
func (r *fakeRepo) IngestEntry( func (r *fakeRepo) IngestEntry(
ctx context.Context, ctx context.Context,
entry *domain.LeaderboardEntry, entry *domain.LeaderboardEntry,
@ -88,6 +93,7 @@ func (r *fakeRepo) IngestEntry(
return &cp, false, nil return &cp, false, nil
} }
// ListTop is a test helper.
func (r *fakeRepo) ListTop( func (r *fakeRepo) ListTop(
ctx context.Context, ctx context.Context,
filter domain.TopFilter, filter domain.TopFilter,
@ -106,6 +112,7 @@ func (r *fakeRepo) ListTop(
return out, nil return out, nil
} }
// GetPlayerStats is a test helper.
func (r *fakeRepo) GetPlayerStats(ctx context.Context, playerID string) (*domain.PlayerStats, error) { func (r *fakeRepo) GetPlayerStats(ctx context.Context, playerID string) (*domain.PlayerStats, error) {
if r.statsErr != nil { if r.statsErr != nil {
return nil, r.statsErr return nil, r.statsErr
@ -118,6 +125,7 @@ func (r *fakeRepo) GetPlayerStats(ctx context.Context, playerID string) (*domain
return &cp, nil return &cp, nil
} }
// GetPlayerRank is a test helper.
func (r *fakeRepo) GetPlayerRank(ctx context.Context, playerID string) (int64, error) { func (r *fakeRepo) GetPlayerRank(ctx context.Context, playerID string) (int64, error) {
if r.rankErr != nil { if r.rankErr != nil {
return 0, r.rankErr return 0, r.rankErr
@ -128,6 +136,7 @@ func (r *fakeRepo) GetPlayerRank(ctx context.Context, playerID string) (int64, e
return 1, nil return 1, nil
} }
// ListPlayerHistory is a test helper.
func (r *fakeRepo) ListPlayerHistory( func (r *fakeRepo) ListPlayerHistory(
ctx context.Context, ctx context.Context,
playerID string, playerID string,
@ -145,6 +154,7 @@ func (r *fakeRepo) ListPlayerHistory(
return out, int64(len(out)), nil return out, int64(len(out)), nil
} }
// GetGlobalStats is a test helper.
func (r *fakeRepo) GetGlobalStats(ctx context.Context, filter domain.TopFilter) (*domain.GlobalStats, error) { func (r *fakeRepo) GetGlobalStats(ctx context.Context, filter domain.TopFilter) (*domain.GlobalStats, error) {
if r.globalErr != nil { if r.globalErr != nil {
return nil, r.globalErr return nil, r.globalErr
@ -158,13 +168,18 @@ type fakeState struct {
deleteErr error deleteErr error
} }
// newFakeState is a test helper.
func newFakeState() *fakeState { func newFakeState() *fakeState {
return &fakeState{data: map[string]string{}} return &fakeState{data: map[string]string{}}
} }
// Get is a test helper.
func (s *fakeState) Get(ctx context.Context, key string) (string, bool) { func (s *fakeState) Get(ctx context.Context, key string) (string, bool) {
v, ok := s.data[key] v, ok := s.data[key]
return v, ok return v, ok
} }
// Set is a test helper.
func (s *fakeState) Set(ctx context.Context, key, value string, ttl time.Duration) error { func (s *fakeState) Set(ctx context.Context, key, value string, ttl time.Duration) error {
if s.setErr != nil { if s.setErr != nil {
return s.setErr return s.setErr
@ -172,6 +187,8 @@ func (s *fakeState) Set(ctx context.Context, key, value string, ttl time.Duratio
s.data[key] = value s.data[key] = value
return nil return nil
} }
// Delete is a test helper.
func (s *fakeState) Delete(ctx context.Context, keys ...string) error { func (s *fakeState) Delete(ctx context.Context, keys ...string) error {
if s.deleteErr != nil { if s.deleteErr != nil {
return s.deleteErr return s.deleteErr
@ -182,6 +199,7 @@ func (s *fakeState) Delete(ctx context.Context, keys ...string) error {
return nil return nil
} }
// TestUpdateScoreIdempotent verifies expected behavior.
func TestUpdateScoreIdempotent(t *testing.T) { func TestUpdateScoreIdempotent(t *testing.T) {
repo := newFakeRepo() repo := newFakeRepo()
state := newFakeState() state := newFakeState()
@ -212,6 +230,7 @@ func TestUpdateScoreIdempotent(t *testing.T) {
} }
} }
// TestUpdateScoreValidatesInput verifies expected behavior.
func TestUpdateScoreValidatesInput(t *testing.T) { func TestUpdateScoreValidatesInput(t *testing.T) {
svc := NewService(newFakeRepo(), newFakeState(), Config{}) svc := NewService(newFakeRepo(), newFakeState(), Config{})
_, err := svc.UpdateScore(context.Background(), UpdateScoreInput{ _, err := svc.UpdateScore(context.Background(), UpdateScoreInput{
@ -230,6 +249,7 @@ func TestUpdateScoreValidatesInput(t *testing.T) {
} }
} }
// TestGetPlayerRanking verifies expected behavior.
func TestGetPlayerRanking(t *testing.T) { func TestGetPlayerRanking(t *testing.T) {
repo := newFakeRepo() repo := newFakeRepo()
state := newFakeState() state := newFakeState()
@ -264,6 +284,7 @@ func TestGetPlayerRanking(t *testing.T) {
} }
} }
// TestUpdateScoreValidationAndErrorPaths verifies expected behavior.
func TestUpdateScoreValidationAndErrorPaths(t *testing.T) { func TestUpdateScoreValidationAndErrorPaths(t *testing.T) {
svc := NewService(newFakeRepo(), newFakeState(), Config{}) svc := NewService(newFakeRepo(), newFakeState(), Config{})
cases := []UpdateScoreInput{ cases := []UpdateScoreInput{
@ -291,6 +312,7 @@ func TestUpdateScoreValidationAndErrorPaths(t *testing.T) {
} }
} }
// TestTopAndStatsCachePaths verifies expected behavior.
func TestTopAndStatsCachePaths(t *testing.T) { func TestTopAndStatsCachePaths(t *testing.T) {
repo := newFakeRepo() repo := newFakeRepo()
state := newFakeState() state := newFakeState()
@ -346,6 +368,7 @@ func TestTopAndStatsCachePaths(t *testing.T) {
} }
} }
// TestRankingValidationAndErrorPaths verifies expected behavior.
func TestRankingValidationAndErrorPaths(t *testing.T) { func TestRankingValidationAndErrorPaths(t *testing.T) {
repo := newFakeRepo() repo := newFakeRepo()
state := newFakeState() state := newFakeState()
@ -400,6 +423,7 @@ func TestRankingValidationAndErrorPaths(t *testing.T) {
} }
} }
// TestGlobalStatsAndTopErrors verifies expected behavior.
func TestGlobalStatsAndTopErrors(t *testing.T) { func TestGlobalStatsAndTopErrors(t *testing.T) {
repo := newFakeRepo() repo := newFakeRepo()
repo.topErr = errors.New("top boom") repo.topErr = errors.New("top boom")

@ -1,5 +1,7 @@
package http package http
// handler_unit_test.go contains tests for backend behavior.
import ( import (
"bytes" "bytes"
"net/http" "net/http"
@ -11,6 +13,7 @@ import (
"knowfoolery/backend/shared/infra/utils/validation" "knowfoolery/backend/shared/infra/utils/validation"
) )
// TestUpdateAuthAndValidationBranches verifies expected behavior.
func TestUpdateAuthAndValidationBranches(t *testing.T) { func TestUpdateAuthAndValidationBranches(t *testing.T) {
h := NewHandler(nil, validation.NewValidator(), nil, nil, true, 20, 100) h := NewHandler(nil, validation.NewValidator(), nil, nil, true, 20, 100)
app := fiber.New() app := fiber.New()
@ -86,6 +89,7 @@ func TestUpdateAuthAndValidationBranches(t *testing.T) {
} }
} }
// TestGetPlayerRankingForbiddenBranch verifies expected behavior.
func TestGetPlayerRankingForbiddenBranch(t *testing.T) { func TestGetPlayerRankingForbiddenBranch(t *testing.T) {
h := NewHandler(nil, validation.NewValidator(), nil, nil, false, 20, 100) h := NewHandler(nil, validation.NewValidator(), nil, nil, false, 20, 100)
app := fiber.New() app := fiber.New()
@ -107,6 +111,7 @@ func TestGetPlayerRankingForbiddenBranch(t *testing.T) {
} }
} }
// TestHelperFunctions verifies expected behavior.
func TestHelperFunctions(t *testing.T) { func TestHelperFunctions(t *testing.T) {
if got := atoiWithDefault("", 3); got != 3 { if got := atoiWithDefault("", 3); got != 3 {
t.Fatalf("expected default for empty input") t.Fatalf("expected default for empty input")

@ -1,5 +1,7 @@
package tests package tests
// integration_http_test.go contains tests for backend behavior.
import ( import (
"bytes" "bytes"
"context" "context"
@ -27,6 +29,7 @@ type inMemoryRepo struct {
stats map[string]*domain.PlayerStats stats map[string]*domain.PlayerStats
} }
// newInMemoryRepo is a test helper.
func newInMemoryRepo() *inMemoryRepo { func newInMemoryRepo() *inMemoryRepo {
return &inMemoryRepo{ return &inMemoryRepo{
entries: make([]*domain.LeaderboardEntry, 0), entries: make([]*domain.LeaderboardEntry, 0),
@ -34,7 +37,10 @@ func newInMemoryRepo() *inMemoryRepo {
} }
} }
// EnsureSchema is a test helper.
func (r *inMemoryRepo) EnsureSchema(ctx context.Context) error { return nil } func (r *inMemoryRepo) EnsureSchema(ctx context.Context) error { return nil }
// IngestEntry is a test helper.
func (r *inMemoryRepo) IngestEntry( func (r *inMemoryRepo) IngestEntry(
ctx context.Context, ctx context.Context,
entry *domain.LeaderboardEntry, entry *domain.LeaderboardEntry,
@ -68,6 +74,8 @@ func (r *inMemoryRepo) IngestEntry(
} }
return &cp, false, nil return &cp, false, nil
} }
// ListTop is a test helper.
func (r *inMemoryRepo) ListTop( func (r *inMemoryRepo) ListTop(
ctx context.Context, ctx context.Context,
filter domain.TopFilter, filter domain.TopFilter,
@ -82,6 +90,8 @@ func (r *inMemoryRepo) ListTop(
} }
return out, nil return out, nil
} }
// GetPlayerStats is a test helper.
func (r *inMemoryRepo) GetPlayerStats(ctx context.Context, playerID string) (*domain.PlayerStats, error) { func (r *inMemoryRepo) GetPlayerStats(ctx context.Context, playerID string) (*domain.PlayerStats, error) {
stats := r.stats[playerID] stats := r.stats[playerID]
if stats == nil { if stats == nil {
@ -90,12 +100,16 @@ func (r *inMemoryRepo) GetPlayerStats(ctx context.Context, playerID string) (*do
cp := *stats cp := *stats
return &cp, nil return &cp, nil
} }
// GetPlayerRank is a test helper.
func (r *inMemoryRepo) GetPlayerRank(ctx context.Context, playerID string) (int64, error) { func (r *inMemoryRepo) GetPlayerRank(ctx context.Context, playerID string) (int64, error) {
if _, ok := r.stats[playerID]; !ok { if _, ok := r.stats[playerID]; !ok {
return 0, domain.ErrPlayerNotFound return 0, domain.ErrPlayerNotFound
} }
return 1, nil return 1, nil
} }
// ListPlayerHistory is a test helper.
func (r *inMemoryRepo) ListPlayerHistory( func (r *inMemoryRepo) ListPlayerHistory(
ctx context.Context, ctx context.Context,
playerID string, playerID string,
@ -109,6 +123,8 @@ func (r *inMemoryRepo) ListPlayerHistory(
} }
return out, int64(len(out)), nil return out, int64(len(out)), nil
} }
// GetGlobalStats is a test helper.
func (r *inMemoryRepo) GetGlobalStats(ctx context.Context, filter domain.TopFilter) (*domain.GlobalStats, error) { func (r *inMemoryRepo) GetGlobalStats(ctx context.Context, filter domain.TopFilter) (*domain.GlobalStats, error) {
return &domain.GlobalStats{ return &domain.GlobalStats{
TotalGames: int64(len(r.entries)), TotalGames: int64(len(r.entries)),
@ -119,12 +135,18 @@ func (r *inMemoryRepo) GetGlobalStats(ctx context.Context, filter domain.TopFilt
type fakeState struct{} type fakeState struct{}
// Get is a test helper.
func (s *fakeState) Get(ctx context.Context, key string) (string, bool) { return "", false } func (s *fakeState) Get(ctx context.Context, key string) (string, bool) { return "", false }
// Set is a test helper.
func (s *fakeState) Set(ctx context.Context, key, value string, ttl time.Duration) error { func (s *fakeState) Set(ctx context.Context, key, value string, ttl time.Duration) error {
return nil return nil
} }
// Delete is a test helper.
func (s *fakeState) Delete(ctx context.Context, keys ...string) error { return nil } func (s *fakeState) Delete(ctx context.Context, keys ...string) error { return nil }
// setupApp is a test helper.
func setupApp(t *testing.T) *fiber.App { func setupApp(t *testing.T) *fiber.App {
t.Helper() t.Helper()
repo := newInMemoryRepo() repo := newInMemoryRepo()
@ -156,6 +178,7 @@ func setupApp(t *testing.T) *fiber.App {
return app return app
} }
// TestUpdateAndTop10 verifies expected behavior.
func TestUpdateAndTop10(t *testing.T) { func TestUpdateAndTop10(t *testing.T) {
app := setupApp(t) app := setupApp(t)
@ -188,6 +211,7 @@ func TestUpdateAndTop10(t *testing.T) {
} }
} }
// TestPlayerAuthAndStats verifies expected behavior.
func TestPlayerAuthAndStats(t *testing.T) { func TestPlayerAuthAndStats(t *testing.T) {
app := setupApp(t) app := setupApp(t)
@ -215,6 +239,7 @@ func TestPlayerAuthAndStats(t *testing.T) {
} }
} }
// TestMetricsEndpoint verifies expected behavior.
func TestMetricsEndpoint(t *testing.T) { func TestMetricsEndpoint(t *testing.T) {
app := setupApp(t) app := setupApp(t)
req := httptest.NewRequest(http.MethodGet, "/metrics", nil) req := httptest.NewRequest(http.MethodGet, "/metrics", nil)
@ -225,6 +250,7 @@ func TestMetricsEndpoint(t *testing.T) {
} }
} }
// assertStatus is a test helper.
func assertStatus(t *testing.T, resp *http.Response, want int, msg string) { func assertStatus(t *testing.T, resp *http.Response, want int, msg string) {
t.Helper() t.Helper()
if resp.StatusCode != want { if resp.StatusCode != want {

@ -219,6 +219,7 @@ func TestValidateAnswerByQuestionID_ValidationError(t *testing.T) {
} }
} }
// TestGetRandomQuestion_InvalidDifficulty verifies expected behavior.
func TestGetRandomQuestion_InvalidDifficulty(t *testing.T) { func TestGetRandomQuestion_InvalidDifficulty(t *testing.T) {
svc := NewService(&fakeRepo{}, &fakeCache{}, time.Minute, 200) svc := NewService(&fakeRepo{}, &fakeCache{}, time.Minute, 200)
_, err := svc.GetRandomQuestion(context.Background(), RandomQuestionRequest{ _, err := svc.GetRandomQuestion(context.Background(), RandomQuestionRequest{
@ -229,6 +230,7 @@ func TestGetRandomQuestion_InvalidDifficulty(t *testing.T) {
} }
} }
// TestGetRandomQuestion_RepoErrors verifies expected behavior.
func TestGetRandomQuestion_RepoErrors(t *testing.T) { func TestGetRandomQuestion_RepoErrors(t *testing.T) {
repo := &fakeRepo{countErr: errors.New("count boom")} repo := &fakeRepo{countErr: errors.New("count boom")}
svc := NewService(repo, &fakeCache{}, time.Minute, 200) svc := NewService(repo, &fakeCache{}, time.Minute, 200)
@ -249,6 +251,7 @@ func TestGetRandomQuestion_RepoErrors(t *testing.T) {
} }
} }
// TestQuestionCRUDAndThemes verifies expected behavior.
func TestQuestionCRUDAndThemes(t *testing.T) { func TestQuestionCRUDAndThemes(t *testing.T) {
repo := &fakeRepo{} repo := &fakeRepo{}
cache := &fakeCache{} cache := &fakeCache{}
@ -304,6 +307,7 @@ func TestQuestionCRUDAndThemes(t *testing.T) {
} }
} }
// TestQuestionCRUDValidationAndErrors verifies expected behavior.
func TestQuestionCRUDValidationAndErrors(t *testing.T) { func TestQuestionCRUDValidationAndErrors(t *testing.T) {
repo := &fakeRepo{createErr: errors.New("create boom")} repo := &fakeRepo{createErr: errors.New("create boom")}
svc := NewService(repo, &fakeCache{}, time.Minute, 200) svc := NewService(repo, &fakeCache{}, time.Minute, 200)
@ -353,6 +357,7 @@ func TestQuestionCRUDValidationAndErrors(t *testing.T) {
} }
} }
// TestBulkImportScenarios verifies expected behavior.
func TestBulkImportScenarios(t *testing.T) { func TestBulkImportScenarios(t *testing.T) {
cache := &fakeCache{} cache := &fakeCache{}
repo := &fakeRepo{bulkCount: 1, bulkErrors: []domain.BulkError{{Index: 0, Reason: "row"}}} repo := &fakeRepo{bulkCount: 1, bulkErrors: []domain.BulkError{{Index: 0, Reason: "row"}}}

@ -29,22 +29,28 @@ type inMemoryRepo struct {
items map[string]*domain.Question items map[string]*domain.Question
} }
// newInMemoryRepo is a test helper.
func newInMemoryRepo() *inMemoryRepo { func newInMemoryRepo() *inMemoryRepo {
return &inMemoryRepo{items: map[string]*domain.Question{}} return &inMemoryRepo{items: map[string]*domain.Question{}}
} }
// GetByID is a test helper.
func (r *inMemoryRepo) GetByID(ctx context.Context, id string) (*domain.Question, error) { func (r *inMemoryRepo) GetByID(ctx context.Context, id string) (*domain.Question, error) {
if q, ok := r.items[id]; ok { if q, ok := r.items[id]; ok {
return q, nil return q, nil
} }
return nil, domain.ErrQuestionNotFound return nil, domain.ErrQuestionNotFound
} }
// Create is a test helper.
func (r *inMemoryRepo) Create(ctx context.Context, q *domain.Question) (*domain.Question, error) { func (r *inMemoryRepo) Create(ctx context.Context, q *domain.Question) (*domain.Question, error) {
q.ID = "q-created" q.ID = "q-created"
q.IsActive = true q.IsActive = true
r.items[q.ID] = q r.items[q.ID] = q
return q, nil return q, nil
} }
// Update is a test helper.
func (r *inMemoryRepo) Update(ctx context.Context, id string, q *domain.Question) (*domain.Question, error) { func (r *inMemoryRepo) Update(ctx context.Context, id string, q *domain.Question) (*domain.Question, error) {
if _, ok := r.items[id]; !ok { if _, ok := r.items[id]; !ok {
return nil, domain.ErrQuestionNotFound return nil, domain.ErrQuestionNotFound
@ -53,6 +59,8 @@ func (r *inMemoryRepo) Update(ctx context.Context, id string, q *domain.Question
r.items[id] = q r.items[id] = q
return q, nil return q, nil
} }
// SoftDelete is a test helper.
func (r *inMemoryRepo) SoftDelete(ctx context.Context, id string) error { func (r *inMemoryRepo) SoftDelete(ctx context.Context, id string) error {
if q, ok := r.items[id]; ok { if q, ok := r.items[id]; ok {
q.IsActive = false q.IsActive = false
@ -60,9 +68,13 @@ func (r *inMemoryRepo) SoftDelete(ctx context.Context, id string) error {
} }
return domain.ErrQuestionNotFound return domain.ErrQuestionNotFound
} }
// ListThemes is a test helper.
func (r *inMemoryRepo) ListThemes(ctx context.Context) ([]string, error) { func (r *inMemoryRepo) ListThemes(ctx context.Context) ([]string, error) {
return []string{"Science"}, nil return []string{"Science"}, nil
} }
// CountRandomCandidates is a test helper.
func (r *inMemoryRepo) CountRandomCandidates(ctx context.Context, filter domain.RandomFilter) (int, error) { func (r *inMemoryRepo) CountRandomCandidates(ctx context.Context, filter domain.RandomFilter) (int, error) {
count := 0 count := 0
for _, q := range r.items { for _, q := range r.items {
@ -73,6 +85,8 @@ func (r *inMemoryRepo) CountRandomCandidates(ctx context.Context, filter domain.
} }
return count, nil return count, nil
} }
// RandomByOffset is a test helper.
func (r *inMemoryRepo) RandomByOffset(ctx context.Context, func (r *inMemoryRepo) RandomByOffset(ctx context.Context,
filter domain.RandomFilter, offset int) (*domain.Question, error) { filter domain.RandomFilter, offset int) (*domain.Question, error) {
for _, q := range r.items { for _, q := range r.items {
@ -82,6 +96,8 @@ func (r *inMemoryRepo) RandomByOffset(ctx context.Context,
} }
return nil, domain.ErrNoQuestionsAvailable return nil, domain.ErrNoQuestionsAvailable
} }
// BulkCreate is a test helper.
func (r *inMemoryRepo) BulkCreate(ctx context.Context, questions []*domain.Question) (int, []domain.BulkError, error) { func (r *inMemoryRepo) BulkCreate(ctx context.Context, questions []*domain.Question) (int, []domain.BulkError, error) {
for i, q := range questions { for i, q := range questions {
id := "bulk-" + strconv.Itoa(i) id := "bulk-" + strconv.Itoa(i)
@ -95,12 +111,15 @@ func (r *inMemoryRepo) BulkCreate(ctx context.Context, questions []*domain.Quest
// noOpCache disables caching behavior in HTTP integration tests. // noOpCache disables caching behavior in HTTP integration tests.
type noOpCache struct{} type noOpCache struct{}
// Get is a test helper.
func (c *noOpCache) Get(ctx context.Context, key string) (*domain.Question, bool) { func (c *noOpCache) Get(ctx context.Context, key string) (*domain.Question, bool) {
return nil, false return nil, false
} }
// Set is a test helper.
func (c *noOpCache) Set(ctx context.Context, key string, q *domain.Question, ttl time.Duration) {} func (c *noOpCache) Set(ctx context.Context, key string, q *domain.Question, ttl time.Duration) {}
// Invalidate is a test helper.
func (c *noOpCache) Invalidate(ctx context.Context) {} func (c *noOpCache) Invalidate(ctx context.Context) {}
// setupApp wires a test Fiber app with in-memory dependencies and admin middleware. // setupApp wires a test Fiber app with in-memory dependencies and admin middleware.

@ -1,5 +1,7 @@
package user package user
// service_test.go contains tests for backend behavior.
import ( import (
"context" "context"
"errors" "errors"
@ -247,6 +249,7 @@ func TestDeleteAndExport(t *testing.T) {
} }
} }
// TestRegisterValidationAndRepoErrors verifies expected behavior.
func TestRegisterValidationAndRepoErrors(t *testing.T) { func TestRegisterValidationAndRepoErrors(t *testing.T) {
svc := NewService(newFakeRepo()) svc := NewService(newFakeRepo())
_, err := svc.Register(context.Background(), RegisterInput{}) _, err := svc.Register(context.Background(), RegisterInput{})
@ -305,6 +308,7 @@ func TestRegisterValidationAndRepoErrors(t *testing.T) {
} }
} }
// TestProfileAndEmailFlows verifies expected behavior.
func TestProfileAndEmailFlows(t *testing.T) { func TestProfileAndEmailFlows(t *testing.T) {
repo := newFakeRepo() repo := newFakeRepo()
svc := NewService(repo) svc := NewService(repo)
@ -345,6 +349,7 @@ func TestProfileAndEmailFlows(t *testing.T) {
} }
} }
// TestProfileValidationAndRepoErrors verifies expected behavior.
func TestProfileValidationAndRepoErrors(t *testing.T) { func TestProfileValidationAndRepoErrors(t *testing.T) {
repo := newFakeRepo() repo := newFakeRepo()
svc := NewService(repo) svc := NewService(repo)
@ -395,6 +400,7 @@ func TestProfileValidationAndRepoErrors(t *testing.T) {
} }
} }
// TestAdminListAndExport verifies expected behavior.
func TestAdminListAndExport(t *testing.T) { func TestAdminListAndExport(t *testing.T) {
repo := newFakeRepo() repo := newFakeRepo()
svc := NewService(repo) svc := NewService(repo)
@ -432,6 +438,7 @@ func TestAdminListAndExport(t *testing.T) {
} }
} }
// TestDeleteListExportErrors verifies expected behavior.
func TestDeleteListExportErrors(t *testing.T) { func TestDeleteListExportErrors(t *testing.T) {
repo := newFakeRepo() repo := newFakeRepo()
svc := NewService(repo) svc := NewService(repo)

@ -1,5 +1,7 @@
package tests package tests
// integration_http_test.go contains tests for backend behavior.
import ( import (
"bytes" "bytes"
"context" "context"

@ -54,10 +54,12 @@ func TestEventType_NonEmpty(t *testing.T) {
type dummyEventBus struct{} type dummyEventBus struct{}
// Publish is a test helper.
func (d *dummyEventBus) Publish(_ context.Context, _ Event) error { func (d *dummyEventBus) Publish(_ context.Context, _ Event) error {
return nil return nil
} }
// Subscribe is a test helper.
func (d *dummyEventBus) Subscribe(_ EventHandler) error { func (d *dummyEventBus) Subscribe(_ EventHandler) error {
return nil return nil
} }

@ -33,6 +33,7 @@ type jwks struct {
Keys []jwksKey `json:"keys"` Keys []jwksKey `json:"keys"`
} }
// generateJWKS is a test helper.
func generateJWKS(t *testing.T) (*rsa.PrivateKey, jwks, string) { func generateJWKS(t *testing.T) (*rsa.PrivateKey, jwks, string) {
t.Helper() t.Helper()
key, err := rsa.GenerateKey(rand.Reader, 2048) key, err := rsa.GenerateKey(rand.Reader, 2048)
@ -56,6 +57,7 @@ func generateJWKS(t *testing.T) (*rsa.PrivateKey, jwks, string) {
}, kid }, kid
} }
// signToken is a test helper.
func signToken(t *testing.T, key *rsa.PrivateKey, kid string, claims jwt.MapClaims) string { func signToken(t *testing.T, key *rsa.PrivateKey, kid string, claims jwt.MapClaims) string {
t.Helper() t.Helper()
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
@ -65,6 +67,7 @@ func signToken(t *testing.T, key *rsa.PrivateKey, kid string, claims jwt.MapClai
return signed return signed
} }
// newOIDCServer is a test helper.
func newOIDCServer(t *testing.T, jwksDoc jwks) *httptest.Server { func newOIDCServer(t *testing.T, jwksDoc jwks) *httptest.Server {
t.Helper() t.Helper()
var baseURL string var baseURL string
@ -295,6 +298,7 @@ func TestRefreshToken_UsesForm(t *testing.T) {
require.Contains(t, captured, "client_id=client") require.Contains(t, captured, "client_id=client")
} }
// serverURL is a test helper.
func serverURL(r *http.Request) string { func serverURL(r *http.Request) string {
return "http://" + r.Host return "http://" + r.Host
} }

@ -19,6 +19,7 @@ type fakeValidator struct {
called int called int
} }
// ValidateToken is a test helper.
func (f *fakeValidator) ValidateToken(ctx context.Context, token string, _ ValidationOptions) (*AuthClaims, error) { func (f *fakeValidator) ValidateToken(ctx context.Context, token string, _ ValidationOptions) (*AuthClaims, error) {
f.called++ f.called++
return f.claims, f.err return f.claims, f.err

@ -28,6 +28,7 @@ func TestAddr(t *testing.T) {
require.Equal(t, "localhost:6379", cfg.Addr()) require.Equal(t, "localhost:6379", cfg.Addr())
} }
// newClientForMiniRedis is a test helper.
func newClientForMiniRedis(t *testing.T) (*Client, *miniredis.Miniredis) { func newClientForMiniRedis(t *testing.T) (*Client, *miniredis.Miniredis) {
t.Helper() t.Helper()

@ -1,5 +1,7 @@
package metrics package metrics
// http_test.go contains tests for backend behavior.
import ( import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"

@ -1,5 +1,7 @@
package envutil package envutil
// env_test.go contains tests for backend behavior.
import ( import (
"testing" "testing"
"time" "time"
@ -7,31 +9,37 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
// TestString verifies expected behavior.
func TestString(t *testing.T) { func TestString(t *testing.T) {
t.Setenv("APP_VALUE", "configured") t.Setenv("APP_VALUE", "configured")
require.Equal(t, "configured", String("APP_VALUE", "fallback")) require.Equal(t, "configured", String("APP_VALUE", "fallback"))
} }
// TestStringFallback verifies expected behavior.
func TestStringFallback(t *testing.T) { func TestStringFallback(t *testing.T) {
t.Setenv("APP_VALUE", "") t.Setenv("APP_VALUE", "")
require.Equal(t, "fallback", String("APP_VALUE", "fallback")) require.Equal(t, "fallback", String("APP_VALUE", "fallback"))
} }
// TestInt verifies expected behavior.
func TestInt(t *testing.T) { func TestInt(t *testing.T) {
t.Setenv("APP_PORT", "8080") t.Setenv("APP_PORT", "8080")
require.Equal(t, 8080, Int("APP_PORT", 3000)) require.Equal(t, 8080, Int("APP_PORT", 3000))
} }
// TestIntFallbackInvalid verifies expected behavior.
func TestIntFallbackInvalid(t *testing.T) { func TestIntFallbackInvalid(t *testing.T) {
t.Setenv("APP_PORT", "oops") t.Setenv("APP_PORT", "oops")
require.Equal(t, 3000, Int("APP_PORT", 3000)) require.Equal(t, 3000, Int("APP_PORT", 3000))
} }
// TestDuration verifies expected behavior.
func TestDuration(t *testing.T) { func TestDuration(t *testing.T) {
t.Setenv("APP_TIMEOUT", "7s") t.Setenv("APP_TIMEOUT", "7s")
require.Equal(t, 7*time.Second, Duration("APP_TIMEOUT", time.Second)) require.Equal(t, 7*time.Second, Duration("APP_TIMEOUT", time.Second))
} }
// TestDurationFallbackInvalid verifies expected behavior.
func TestDurationFallbackInvalid(t *testing.T) { func TestDurationFallbackInvalid(t *testing.T) {
t.Setenv("APP_TIMEOUT", "oops") t.Setenv("APP_TIMEOUT", "oops")
require.Equal(t, 2*time.Second, Duration("APP_TIMEOUT", 2*time.Second)) require.Equal(t, 2*time.Second, Duration("APP_TIMEOUT", 2*time.Second))

@ -1,5 +1,7 @@
package serviceboot package serviceboot
// fiber_test.go contains tests for backend behavior.
import ( import (
"encoding/json" "encoding/json"
"net/http" "net/http"
@ -9,6 +11,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
// TestRegisterHealth verifies expected behavior.
func TestRegisterHealth(t *testing.T) { func TestRegisterHealth(t *testing.T) {
app := NewFiberApp(Config{AppName: "test-service"}) app := NewFiberApp(Config{AppName: "test-service"})
RegisterHealth(app, "svc") RegisterHealth(app, "svc")
@ -24,16 +27,19 @@ func TestRegisterHealth(t *testing.T) {
require.Equal(t, "svc", body["service"]) require.Equal(t, "svc", body["service"])
} }
// TestListenAddressFromEnv verifies expected behavior.
func TestListenAddressFromEnv(t *testing.T) { func TestListenAddressFromEnv(t *testing.T) {
t.Setenv("SERVICE_PORT", "9090") t.Setenv("SERVICE_PORT", "9090")
require.Equal(t, ":9090", ListenAddress("SERVICE_PORT", 8080)) require.Equal(t, ":9090", ListenAddress("SERVICE_PORT", 8080))
} }
// TestListenAddressFallback verifies expected behavior.
func TestListenAddressFallback(t *testing.T) { func TestListenAddressFallback(t *testing.T) {
t.Setenv("SERVICE_PORT", "bad") t.Setenv("SERVICE_PORT", "bad")
require.Equal(t, ":8080", ListenAddress("SERVICE_PORT", 8080)) require.Equal(t, ":8080", ListenAddress("SERVICE_PORT", 8080))
} }
// TestListenAddressOutOfRangeFallback verifies expected behavior.
func TestListenAddressOutOfRangeFallback(t *testing.T) { func TestListenAddressOutOfRangeFallback(t *testing.T) {
t.Setenv("SERVICE_PORT", "70000") t.Setenv("SERVICE_PORT", "70000")
require.Equal(t, ":8080", ListenAddress("SERVICE_PORT", 8080)) require.Equal(t, ":8080", ListenAddress("SERVICE_PORT", 8080))

Loading…
Cancel
Save