Files
gridpilot.gg/core/racing/application/use-cases/MembershipUseCases.test.ts
2025-12-16 21:05:01 +01:00

134 lines
4.1 KiB
TypeScript

import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import { JoinLeagueUseCase } from '@core/racing/application/use-cases/JoinLeagueUseCase';
import type { ILeagueMembershipRepository } from '@core/racing/domain/repositories/ILeagueMembershipRepository';
import type { Logger } from '@core/shared/application';
import { LeagueMembership } from '@core/racing/domain/entities/LeagueMembership';
import type { JoinRequest } from '@core/racing/domain/entities/LeagueMembership';
class InMemoryLeagueMembershipRepository implements ILeagueMembershipRepository {
private memberships: LeagueMembership[] = [];
private joinRequests: JoinRequest[] = [];
async getMembership(leagueId: string, driverId: string): Promise<LeagueMembership | null> {
return (
this.memberships.find(
(m) => m.leagueId === leagueId && m.driverId === driverId,
) || null
);
}
async getLeagueMembers(leagueId: string): Promise<LeagueMembership[]> {
return this.memberships.filter(
(m) => m.leagueId === leagueId && m.status === 'active',
);
}
async getJoinRequests(leagueId: string): Promise<JoinRequest[]> {
return this.joinRequests.filter(
(r) => r.leagueId === leagueId,
);
}
async saveMembership(membership: LeagueMembership): Promise<LeagueMembership> {
const existingIndex = this.memberships.findIndex(
(m) => m.leagueId === membership.leagueId && m.driverId === membership.driverId,
);
if (existingIndex >= 0) {
this.memberships[existingIndex] = membership;
} else {
this.memberships.push(membership);
}
return membership;
}
async removeMembership(leagueId: string, driverId: string): Promise<void> {
this.memberships = this.memberships.filter(
(m) => !(m.leagueId === leagueId && m.driverId === driverId),
);
}
async saveJoinRequest(request: JoinRequest): Promise<JoinRequest> {
this.joinRequests.push(request);
return request;
}
async removeJoinRequest(requestId: string): Promise<void> {
this.joinRequests = this.joinRequests.filter(
(r) => r.id !== requestId,
);
}
seedMembership(membership: LeagueMembership): void {
this.memberships.push(membership);
}
getAllMemberships(): LeagueMembership[] {
return [...this.memberships];
}
}
describe('Membership use-cases', () => {
describe('JoinLeagueUseCase', () => {
let repository: InMemoryLeagueMembershipRepository;
let useCase: JoinLeagueUseCase;
let logger: {
debug: Mock;
info: Mock;
warn: Mock;
error: Mock;
};
beforeEach(() => {
repository = new InMemoryLeagueMembershipRepository();
logger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
useCase = new JoinLeagueUseCase(
repository as unknown as ILeagueMembershipRepository,
logger as unknown as Logger,
);
});
it('creates an active member when driver has no membership', async () => {
const leagueId = 'league-1';
const driverId = 'driver-1';
const result = await useCase.execute({ leagueId, driverId });
expect(result.isOk()).toBe(true);
const membership = result.unwrap();
expect(membership.leagueId).toBe(leagueId);
expect(membership.driverId).toBe(driverId);
expect(membership.role).toBe('member');
expect(membership.status).toBe('active');
expect(membership.joinedAt).toBeInstanceOf(Date);
});
it('returns error when driver already has membership for league', async () => {
const leagueId = 'league-1';
const driverId = 'driver-1';
repository.seedMembership(LeagueMembership.create({
leagueId,
driverId,
role: 'member',
status: 'active',
joinedAt: new Date('2024-01-01'),
}));
const result = await useCase.execute({ leagueId, driverId });
expect(result.isErr()).toBe(true);
expect(result.unwrapErr()).toEqual({
code: 'ALREADY_MEMBER',
details: { message: 'Already a member or have a pending request' },
});
});
});
});