Files
galaxy-game/lobby/internal/service/declineinvite/service_test.go
T
2026-04-25 23:20:55 +02:00

161 lines
4.1 KiB
Go

package declineinvite_test
import (
"context"
"io"
"log/slog"
"testing"
"time"
"galaxy/lobby/internal/adapters/invitestub"
"galaxy/lobby/internal/domain/common"
"galaxy/lobby/internal/domain/invite"
"galaxy/lobby/internal/ports"
"galaxy/lobby/internal/service/declineinvite"
"galaxy/lobby/internal/service/shared"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
gameID = common.GameID("game-private")
ownerUserID = "user-owner"
inviteeUserID = "user-invitee"
)
func silentLogger() *slog.Logger { return slog.New(slog.NewTextHandler(io.Discard, nil)) }
func fixedClock(at time.Time) func() time.Time { return func() time.Time { return at } }
type fixture struct {
now time.Time
invites *invitestub.Store
}
func newFixture(t *testing.T) *fixture {
t.Helper()
return &fixture{
now: time.Date(2026, 4, 25, 10, 0, 0, 0, time.UTC),
invites: invitestub.NewStore(),
}
}
func newService(t *testing.T, f *fixture) *declineinvite.Service {
t.Helper()
svc, err := declineinvite.NewService(declineinvite.Dependencies{
Invites: f.invites,
Clock: fixedClock(f.now),
Logger: silentLogger(),
})
require.NoError(t, err)
return svc
}
func seedCreatedInvite(t *testing.T, f *fixture, inviteID common.InviteID, invitee string) invite.Invite {
t.Helper()
inv, err := invite.New(invite.NewInviteInput{
InviteID: inviteID,
GameID: gameID,
InviterUserID: ownerUserID,
InviteeUserID: invitee,
Now: f.now,
ExpiresAt: f.now.Add(24 * time.Hour),
})
require.NoError(t, err)
require.NoError(t, f.invites.Save(context.Background(), inv))
return inv
}
func TestDeclineHappyPath(t *testing.T) {
t.Parallel()
f := newFixture(t)
inv := seedCreatedInvite(t, f, "invite-1", inviteeUserID)
svc := newService(t, f)
got, err := svc.Handle(context.Background(), declineinvite.Input{
Actor: shared.NewUserActor(inviteeUserID),
GameID: gameID,
InviteID: inv.InviteID,
})
require.NoError(t, err)
assert.Equal(t, invite.StatusDeclined, got.Status)
require.NotNil(t, got.DecidedAt)
assert.Equal(t, f.now, *got.DecidedAt)
}
func TestDeclineAdminActorForbidden(t *testing.T) {
t.Parallel()
f := newFixture(t)
inv := seedCreatedInvite(t, f, "invite-1", inviteeUserID)
svc := newService(t, f)
_, err := svc.Handle(context.Background(), declineinvite.Input{
Actor: shared.NewAdminActor(),
GameID: gameID,
InviteID: inv.InviteID,
})
require.ErrorIs(t, err, shared.ErrForbidden)
}
func TestDeclineNonInviteeForbidden(t *testing.T) {
t.Parallel()
f := newFixture(t)
inv := seedCreatedInvite(t, f, "invite-1", inviteeUserID)
svc := newService(t, f)
_, err := svc.Handle(context.Background(), declineinvite.Input{
Actor: shared.NewUserActor("user-impostor"),
GameID: gameID,
InviteID: inv.InviteID,
})
require.ErrorIs(t, err, shared.ErrForbidden)
}
func TestDeclineNotFound(t *testing.T) {
t.Parallel()
f := newFixture(t)
svc := newService(t, f)
_, err := svc.Handle(context.Background(), declineinvite.Input{
Actor: shared.NewUserActor(inviteeUserID),
GameID: gameID,
InviteID: "invite-missing",
})
require.ErrorIs(t, err, invite.ErrNotFound)
}
func TestDeclineCrossGameNotFound(t *testing.T) {
t.Parallel()
f := newFixture(t)
inv := seedCreatedInvite(t, f, "invite-1", inviteeUserID)
svc := newService(t, f)
_, err := svc.Handle(context.Background(), declineinvite.Input{
Actor: shared.NewUserActor(inviteeUserID),
GameID: "game-other",
InviteID: inv.InviteID,
})
require.ErrorIs(t, err, invite.ErrNotFound)
}
func TestDeclineWrongStatusConflict(t *testing.T) {
t.Parallel()
f := newFixture(t)
inv := seedCreatedInvite(t, f, "invite-1", inviteeUserID)
require.NoError(t, f.invites.UpdateStatus(context.Background(), ports.UpdateInviteStatusInput{
InviteID: inv.InviteID,
ExpectedFrom: invite.StatusCreated,
To: invite.StatusRevoked,
At: f.now,
}))
svc := newService(t, f)
_, err := svc.Handle(context.Background(), declineinvite.Input{
Actor: shared.NewUserActor(inviteeUserID),
GameID: gameID,
InviteID: inv.InviteID,
})
require.ErrorIs(t, err, invite.ErrConflict)
}