108 lines
3.2 KiB
Go
108 lines
3.2 KiB
Go
// Package userservicestub provides an in-process
|
|
// ports.UserService implementation for service-level tests. The stub
|
|
// stores per-user Eligibility values and lets tests inject errors for
|
|
// specific user ids to exercise the unavailable / decode-failure paths.
|
|
package userservicestub
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
|
|
"galaxy/lobby/internal/ports"
|
|
)
|
|
|
|
// Service is a concurrency-safe in-memory implementation of
|
|
// ports.UserService. The zero value is not usable; call NewService to
|
|
// construct.
|
|
type Service struct {
|
|
mu sync.Mutex
|
|
eligibilities map[string]ports.Eligibility
|
|
failures map[string]error
|
|
defaultMissing bool
|
|
}
|
|
|
|
// NewService constructs an empty Service with no preloaded
|
|
// eligibilities. By default an unknown user maps to
|
|
// Eligibility{Exists:false}, mirroring the production HTTP client's
|
|
// 404 handling. Use WithDefaultUnavailable to flip the unknown-user
|
|
// behaviour to a transport failure.
|
|
func NewService(opts ...Option) *Service {
|
|
service := &Service{
|
|
eligibilities: make(map[string]ports.Eligibility),
|
|
failures: make(map[string]error),
|
|
}
|
|
for _, opt := range opts {
|
|
opt(service)
|
|
}
|
|
return service
|
|
}
|
|
|
|
// Option tunes Service construction.
|
|
type Option func(*Service)
|
|
|
|
// WithDefaultUnavailable makes the stub return ErrUserServiceUnavailable
|
|
// for any user id without a preloaded eligibility or failure entry.
|
|
// Useful for tests that exercise the "User Service down" path without
|
|
// having to enumerate every caller.
|
|
func WithDefaultUnavailable() Option {
|
|
return func(service *Service) {
|
|
service.defaultMissing = true
|
|
}
|
|
}
|
|
|
|
// SetEligibility preloads eligibility for userID. Subsequent calls
|
|
// overwrite the prior value.
|
|
func (service *Service) SetEligibility(userID string, eligibility ports.Eligibility) {
|
|
if service == nil {
|
|
return
|
|
}
|
|
service.mu.Lock()
|
|
defer service.mu.Unlock()
|
|
service.eligibilities[strings.TrimSpace(userID)] = eligibility
|
|
}
|
|
|
|
// SetFailure preloads err to be returned for userID. err takes
|
|
// precedence over any preloaded eligibility.
|
|
func (service *Service) SetFailure(userID string, err error) {
|
|
if service == nil {
|
|
return
|
|
}
|
|
service.mu.Lock()
|
|
defer service.mu.Unlock()
|
|
service.failures[strings.TrimSpace(userID)] = err
|
|
}
|
|
|
|
// GetEligibility returns the preloaded eligibility for userID.
|
|
func (service *Service) GetEligibility(ctx context.Context, userID string) (ports.Eligibility, error) {
|
|
if service == nil {
|
|
return ports.Eligibility{}, errors.New("get eligibility: nil service")
|
|
}
|
|
if ctx == nil {
|
|
return ports.Eligibility{}, errors.New("get eligibility: nil context")
|
|
}
|
|
trimmed := strings.TrimSpace(userID)
|
|
if trimmed == "" {
|
|
return ports.Eligibility{}, errors.New("get eligibility: user id must not be empty")
|
|
}
|
|
|
|
service.mu.Lock()
|
|
defer service.mu.Unlock()
|
|
|
|
if err, ok := service.failures[trimmed]; ok {
|
|
return ports.Eligibility{}, err
|
|
}
|
|
if eligibility, ok := service.eligibilities[trimmed]; ok {
|
|
return eligibility, nil
|
|
}
|
|
if service.defaultMissing {
|
|
return ports.Eligibility{}, fmt.Errorf("get eligibility: %w", ports.ErrUserServiceUnavailable)
|
|
}
|
|
return ports.Eligibility{Exists: false}, nil
|
|
}
|
|
|
|
// Compile-time interface assertion.
|
|
var _ ports.UserService = (*Service)(nil)
|