// Package gapactivationstub provides an in-memory // ports.GapActivationStore implementation for service-level tests. The // stub records every MarkActivated call and offers WasActivated / // ActivatedAt accessors so test bodies can assert the gap-window trigger // fired exactly once. package gapactivationstub import ( "context" "errors" "fmt" "sync" "time" "galaxy/lobby/internal/domain/common" "galaxy/lobby/internal/ports" ) // Store is a concurrency-safe in-memory implementation of // ports.GapActivationStore. The zero value is not usable; call NewStore // to construct. type Store struct { mu sync.Mutex records map[common.GameID]time.Time } // NewStore constructs one empty Store ready for use. func NewStore() *Store { return &Store{records: make(map[common.GameID]time.Time)} } // MarkActivated mirrors ports.GapActivationStore semantics: SETNX — // the first call wins, subsequent calls are silent no-ops. func (store *Store) MarkActivated(ctx context.Context, gameID common.GameID, at time.Time) error { if store == nil { return errors.New("mark gap activation: nil store") } if ctx == nil { return errors.New("mark gap activation: nil context") } if err := gameID.Validate(); err != nil { return fmt.Errorf("mark gap activation: %w", err) } if at.IsZero() { return errors.New("mark gap activation: at must not be zero") } store.mu.Lock() defer store.mu.Unlock() if _, exists := store.records[gameID]; exists { return nil } store.records[gameID] = at.UTC() return nil } // Get reports the activation time previously written for gameID. func (store *Store) Get(ctx context.Context, gameID common.GameID) (time.Time, bool, error) { if store == nil { return time.Time{}, false, errors.New("get gap activation: nil store") } if ctx == nil { return time.Time{}, false, errors.New("get gap activation: nil context") } if err := gameID.Validate(); err != nil { return time.Time{}, false, fmt.Errorf("get gap activation: %w", err) } store.mu.Lock() defer store.mu.Unlock() at, ok := store.records[gameID] return at, ok, nil } // WasActivated reports whether MarkActivated has been called for gameID. func (store *Store) WasActivated(gameID common.GameID) bool { if store == nil { return false } store.mu.Lock() defer store.mu.Unlock() _, ok := store.records[gameID] return ok } // ActivatedAt returns the recorded activation time for gameID, or zero // time when the game has not been activated. func (store *Store) ActivatedAt(gameID common.GameID) time.Time { if store == nil { return time.Time{} } store.mu.Lock() defer store.mu.Unlock() return store.records[gameID] } // Compile-time interface assertion. var _ ports.GapActivationStore = (*Store)(nil)