153 lines
6.5 KiB
TypeScript
153 lines
6.5 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
import { adaptEvent, adaptPlayer, adaptSeries, adaptTournament, api, type RawEvent, type RawPlayer, type RawSeries } from './client'
|
|
|
|
const player: RawPlayer = {
|
|
id: 'player-1',
|
|
accountId: 'account-1',
|
|
displayName: 'Nova',
|
|
ratings: { tank: 36, damage: 28, support: 24 },
|
|
preferredRoles: ['Tank'],
|
|
preferredPlayerIds: ['player-2'],
|
|
avoidedPlayerIds: ['player-3'],
|
|
createdAt: '2026-07-01T00:00:00Z',
|
|
updatedAt: '2026-07-18T00:00:00Z',
|
|
}
|
|
|
|
afterEach(() => vi.restoreAllMocks())
|
|
|
|
describe('backend DTO adapters', () => {
|
|
it('adapts backend role ratings without changing their values', () => {
|
|
expect(adaptPlayer(player)).toMatchObject({
|
|
displayName: 'Nova',
|
|
ratings: { tank: 36, damage: 28, support: 24 },
|
|
preferredRoles: ['tank'],
|
|
preferredPlayerIds: ['player-2'],
|
|
avoidedPlayerIds: ['player-3'],
|
|
})
|
|
})
|
|
|
|
it('adapts backend RSVP values and counts', () => {
|
|
const event: RawEvent = {
|
|
id: 'event-1',
|
|
name: 'Scrim',
|
|
description: '',
|
|
startsAt: '2099-07-20T19:00:00Z',
|
|
endsAt: '2099-07-20T23:00:00Z',
|
|
registrationDeadline: '2099-07-20T18:00:00Z',
|
|
createdBy: 'account-1',
|
|
createdAt: '2026-07-01T00:00:00Z',
|
|
updatedAt: '2026-07-01T00:00:00Z',
|
|
state: 'RegistrationOpen',
|
|
version: 0,
|
|
rulesetId: 'rules-1',
|
|
activeSeriesId: '',
|
|
tournamentId: '',
|
|
}
|
|
const adapted = adaptEvent(event, [
|
|
{ eventId: event.id, playerId: player.id, actorAccountId: 'account-1', status: 'Going', source: 'player', updatedAt: event.updatedAt },
|
|
], player.id)
|
|
expect(adapted.myRsvp).toBe('going')
|
|
expect(adapted.counts.going).toBe(1)
|
|
expect(adaptEvent({ ...event, state: 'Cancelled' }).status).toBe('cancelled')
|
|
expect(adaptEvent({ ...event, state: 'Completed' }).status).toBe('completed')
|
|
expect(adaptEvent({ ...event, state: 'Live' }).status).toBe('live')
|
|
expect(adaptEvent({ ...event, state: 'RegistrationClosed', endsAt: '2020-01-01T00:00:00Z' }).status).toBe('balancing')
|
|
})
|
|
|
|
it('keeps corrected results out of the authoritative score', () => {
|
|
const series: RawSeries = {
|
|
id: 'series-1',
|
|
eventId: 'event-1',
|
|
tournamentId: 't-1',
|
|
teamAId: 'a',
|
|
teamBId: 'b',
|
|
winnerTeamId: '',
|
|
bestOf: 3,
|
|
rulesetId: 'rules-1',
|
|
phase: 'Playing',
|
|
currentMap: 'Lijiang',
|
|
playedMaps: [],
|
|
nextMapPickerId: '',
|
|
availableMaps: [],
|
|
maps: [],
|
|
audit: [],
|
|
version: 2,
|
|
results: [
|
|
{ mapName: 'Lijiang', outcome: 'TeamAWin', actorAccountId: 'admin', recordedAt: '2026-07-18T00:00:00Z' },
|
|
{ mapName: 'Lijiang', outcome: 'TeamBWin', actorAccountId: 'admin', recordedAt: '2026-07-18T00:01:00Z', correctionOf: 1 },
|
|
],
|
|
}
|
|
expect(adaptSeries(series).score).toEqual({ alpha: 0, beta: 1 })
|
|
expect(adaptSeries({ ...series, phase: 'CoinTossPending', results: null, maps: null, audit: null }).score).toEqual({ alpha: 0, beta: 0 })
|
|
expect(adaptTournament({ id: 't-1', eventId: 'event-1', name: 'Cup', winnerTeamId: '', teamIds: ['a', 'b'], rounds: [[{ ...series, phase: 'MapBan' }]] }).matches[0].status).toBe('live')
|
|
const heroDraft = adaptSeries({
|
|
...series,
|
|
phase: 'HeroBan',
|
|
seriesBans: { a: ['Ana'], b: ['Tracer'] },
|
|
heroDraft: {
|
|
heroes: [{ name: 'Ana', role: 'Support' }, { name: 'Kiriko', role: 'Support' }],
|
|
teamIds: ['a', 'b'],
|
|
firstTeamId: 'a',
|
|
bansPerTeam: 2,
|
|
currentBans: [],
|
|
},
|
|
})
|
|
expect(heroDraft.heroBanHistory).toEqual({ alpha: ['Ana'], beta: ['Tracer'] })
|
|
expect(heroDraft.options.find((option) => option.name === 'Ana')).toMatchObject({ disabled: true })
|
|
})
|
|
})
|
|
|
|
describe('actual backend routes', () => {
|
|
it('updates the current player through PATCH /api/me/player', async () => {
|
|
const fetchMock = vi.spyOn(globalThis, 'fetch').mockResolvedValue(new Response(JSON.stringify(player), {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}))
|
|
await api.updateRatings('Nova', { tank: 37, damage: 29, support: 25 }, ['tank', 'damage'], ['player-2'], ['player-3'])
|
|
expect(fetchMock).toHaveBeenCalledWith('/api/me/player', expect.objectContaining({ method: 'PATCH' }))
|
|
expect(fetchMock.mock.calls[0][1]?.body).toContain('"preferredRoles":["Tank","Damage"]')
|
|
expect(fetchMock.mock.calls[0][1]?.body).toContain('"avoidedPlayerIds":["player-3"]')
|
|
})
|
|
|
|
it('sends backend RSVP enum values to the player-specific route', async () => {
|
|
const responses = [
|
|
{ eventId: 'event-1', playerId: 'player-1', actorAccountId: 'account-1', status: 'Going', source: 'player', updatedAt: '2026-07-18T00:00:00Z' },
|
|
[player],
|
|
{ account: { id: 'account-1', discordId: 'discord', username: 'Nova', avatarUrl: '', role: 'player', createdAt: '2026-07-01T00:00:00Z' }, player },
|
|
]
|
|
const fetchMock = vi.spyOn(globalThis, 'fetch').mockImplementation(async () => new Response(JSON.stringify(responses.shift()), {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}))
|
|
await api.setRsvp('event-1', 'player-1', 'going')
|
|
const rsvpCall = fetchMock.mock.calls.find(([url]) => url === '/api/events/event-1/rsvps/player-1')
|
|
expect(rsvpCall?.[1]?.body).toBe(JSON.stringify({ status: 'Going' }))
|
|
})
|
|
|
|
it('assigns moderators through the administrator-only route', async () => {
|
|
const account = { id: 'account-2', discordId: 'discord-2', username: 'Mod', avatarUrl: '', role: 'moderator', createdAt: '2026-07-01T00:00:00Z' }
|
|
const fetchMock = vi.spyOn(globalThis, 'fetch').mockResolvedValue(new Response(JSON.stringify(account), {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}))
|
|
await api.setModerator(account.id, true)
|
|
expect(fetchMock).toHaveBeenCalledWith('/api/accounts/account-2/moderator', expect.objectContaining({
|
|
method: 'PATCH',
|
|
body: JSON.stringify({ moderator: true }),
|
|
}))
|
|
})
|
|
|
|
it('renames a live team through its team route', async () => {
|
|
const team = { id: 'team-a', eventId: 'event-1', name: 'Ember Wolves', captainPlayerId: 'player-1', slots: [] }
|
|
const fetchMock = vi.spyOn(globalThis, 'fetch').mockResolvedValue(new Response(JSON.stringify(team), {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}))
|
|
await api.renameTeam(team.id, team.name)
|
|
expect(fetchMock).toHaveBeenCalledWith('/api/teams/team-a/name', expect.objectContaining({
|
|
method: 'PUT',
|
|
body: JSON.stringify({ name: 'Ember Wolves' }),
|
|
}))
|
|
})
|
|
})
|