Files
galaxy-game/lobby/internal/adapters/gapactivationstub/store.go
T
2026-04-25 23:20:55 +02:00

101 lines
2.7 KiB
Go

// 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)