Files
gridpilot.gg/core/racing/application/use-cases/ApproveLeagueJoinRequestUseCase.test.ts
2026-01-16 16:46:57 +01:00

163 lines
5.6 KiB
TypeScript

import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import {
ApproveLeagueJoinRequestUseCase,
type ApproveLeagueJoinRequestResult,
} from './ApproveLeagueJoinRequestUseCase';
import { League } from '../../domain/entities/League';
import type { LeagueMembershipRepository } from '../../domain/repositories/LeagueMembershipRepository';
import type { LeagueRepository } from '../../domain/repositories/LeagueRepository';
describe('ApproveLeagueJoinRequestUseCase', () => {
let mockLeagueMembershipRepo: {
getJoinRequests: Mock;
removeJoinRequest: Mock;
saveMembership: Mock;
getLeagueMembers: Mock;
};
let mockLeagueRepo: {
findById: Mock;
};
beforeEach(() => {
mockLeagueMembershipRepo = {
getJoinRequests: vi.fn(),
removeJoinRequest: vi.fn(),
saveMembership: vi.fn(),
getLeagueMembers: vi.fn(),
};
mockLeagueRepo = {
findById: vi.fn(),
};
});
it('approve removes request and adds member', async () => {
const useCase = new ApproveLeagueJoinRequestUseCase(mockLeagueMembershipRepo as unknown as LeagueMembershipRepository,
mockLeagueRepo as unknown as LeagueRepository);
const leagueId = 'league-1';
const joinRequestId = 'req-1';
const joinRequests = [{ id: joinRequestId, leagueId, driverId: 'driver-1', requestedAt: new Date(), message: 'msg' }];
mockLeagueRepo.findById.mockResolvedValue(
League.create({
id: leagueId,
name: 'L',
description: 'D',
ownerId: 'owner-1',
settings: { maxDrivers: 32, visibility: 'unranked' },
participantCount: 0,
}),
);
mockLeagueMembershipRepo.getLeagueMembers.mockResolvedValue([]);
mockLeagueMembershipRepo.getJoinRequests.mockResolvedValue(joinRequests);
const result = await useCase.execute(
{ leagueId, joinRequestId },
);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toEqual({ success: true, message: expect.any(String) });
expect(mockLeagueMembershipRepo.removeJoinRequest).toHaveBeenCalledWith(joinRequestId);
expect(mockLeagueMembershipRepo.saveMembership).toHaveBeenCalledTimes(1);
const savedMembership = (mockLeagueMembershipRepo.saveMembership as Mock).mock.calls[0]?.[0] as unknown as {
id: string;
leagueId: { toString(): string };
driverId: { toString(): string };
role: { toString(): string };
status: { toString(): string };
joinedAt: { toDate(): Date };
};
expect(savedMembership.id).toEqual(expect.any(String));
expect(savedMembership.leagueId.toString()).toBe('league-1');
expect(savedMembership.driverId.toString()).toBe('driver-1');
expect(savedMembership.role.toString()).toBe('member');
expect(savedMembership.status.toString()).toBe('active');
expect(savedMembership.joinedAt.toDate()).toBeInstanceOf(Date);
});
it('approve returns error when request missing', async () => {
const useCase = new ApproveLeagueJoinRequestUseCase(mockLeagueMembershipRepo as unknown as LeagueMembershipRepository,
mockLeagueRepo as unknown as LeagueRepository);
mockLeagueRepo.findById.mockResolvedValue(
League.create({
id: 'league-1',
name: 'L',
description: 'D',
ownerId: 'owner-1',
settings: { maxDrivers: 32, visibility: 'unranked' },
participantCount: 0,
}),
);
mockLeagueMembershipRepo.getLeagueMembers.mockResolvedValue([]);
mockLeagueMembershipRepo.getJoinRequests.mockResolvedValue([]);
const result = await useCase.execute(
{ leagueId: 'league-1', joinRequestId: 'req-1' },
);
expect(result.isOk()).toBe(false);
expect(result.error!.code).toBe('JOIN_REQUEST_NOT_FOUND');
expect(mockLeagueMembershipRepo.removeJoinRequest).not.toHaveBeenCalled();
expect(mockLeagueMembershipRepo.saveMembership).not.toHaveBeenCalled();
});
it('rejects approval when league is at capacity and does not mutate state', async () => {
const useCase = new ApproveLeagueJoinRequestUseCase(mockLeagueMembershipRepo as unknown as LeagueMembershipRepository,
mockLeagueRepo as unknown as LeagueRepository);
const leagueId = 'league-1';
const joinRequestId = 'req-1';
const joinRequests = [{ id: joinRequestId, leagueId, driverId: 'driver-2', requestedAt: new Date(), message: 'msg' }];
mockLeagueRepo.findById.mockResolvedValue(
League.create({
id: leagueId,
name: 'L',
description: 'D',
ownerId: 'owner-1',
settings: { maxDrivers: 2, visibility: 'unranked' },
participantCount: 2,
}),
);
mockLeagueMembershipRepo.getLeagueMembers.mockResolvedValue([
{
id: `${leagueId}:owner-1`,
leagueId: { toString: () => leagueId },
driverId: { toString: () => 'owner-1' },
role: { toString: () => 'owner' },
status: { toString: () => 'active' },
joinedAt: { toDate: () => new Date() },
},
{
id: `${leagueId}:driver-1`,
leagueId: { toString: () => leagueId },
driverId: { toString: () => 'driver-1' },
role: { toString: () => 'member' },
status: { toString: () => 'active' },
joinedAt: { toDate: () => new Date() },
},
]);
mockLeagueMembershipRepo.getJoinRequests.mockResolvedValue(joinRequests);
const result = await useCase.execute(
{ leagueId, joinRequestId },
);
expect(result.isErr()).toBe(true);
expect(result.unwrapErr().code).toBe('LEAGUE_AT_CAPACITY');
expect(mockLeagueMembershipRepo.removeJoinRequest).not.toHaveBeenCalled();
expect(mockLeagueMembershipRepo.saveMembership).not.toHaveBeenCalled();
});
});