integration tests
Some checks failed
CI / lint-typecheck (pull_request) Failing after 4m50s
CI / tests (pull_request) Has been skipped
CI / contract-tests (pull_request) Has been skipped
CI / e2e-tests (pull_request) Has been skipped
CI / comment-pr (pull_request) Has been skipped
CI / commit-types (pull_request) Has been skipped

This commit is contained in:
2026-01-23 11:44:59 +01:00
parent a0f41f242f
commit 6df38a462a
125 changed files with 4712 additions and 19184 deletions

View File

@@ -0,0 +1,94 @@
import { Logger } from '../../../core/shared/domain/Logger';
import { InMemoryTeamRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryTeamRepository';
import { InMemoryTeamMembershipRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryTeamMembershipRepository';
import { InMemoryDriverRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryDriverRepository';
import { InMemoryTeamStatsRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryTeamStatsRepository';
import { CreateTeamUseCase } from '../../../core/racing/application/use-cases/CreateTeamUseCase';
import { JoinTeamUseCase } from '../../../core/racing/application/use-cases/JoinTeamUseCase';
import { LeaveTeamUseCase } from '../../../core/racing/application/use-cases/LeaveTeamUseCase';
import { GetTeamMembershipUseCase } from '../../../core/racing/application/use-cases/GetTeamMembershipUseCase';
import { GetTeamMembersUseCase } from '../../../core/racing/application/use-cases/GetTeamMembersUseCase';
import { GetTeamJoinRequestsUseCase } from '../../../core/racing/application/use-cases/GetTeamJoinRequestsUseCase';
import { ApproveTeamJoinRequestUseCase } from '../../../core/racing/application/use-cases/ApproveTeamJoinRequestUseCase';
import { UpdateTeamUseCase } from '../../../core/racing/application/use-cases/UpdateTeamUseCase';
import { GetTeamDetailsUseCase } from '../../../core/racing/application/use-cases/GetTeamDetailsUseCase';
import { GetTeamsLeaderboardUseCase } from '../../../core/racing/application/use-cases/GetTeamsLeaderboardUseCase';
import { GetAllTeamsUseCase } from '../../../core/racing/application/use-cases/GetAllTeamsUseCase';
export class TeamsTestContext {
public readonly logger: Logger;
public readonly teamRepository: InMemoryTeamRepository;
public readonly membershipRepository: InMemoryTeamMembershipRepository;
public readonly driverRepository: InMemoryDriverRepository;
public readonly statsRepository: InMemoryTeamStatsRepository;
constructor() {
this.logger = {
info: () => {},
debug: () => {},
warn: () => {},
error: () => {},
} as unknown as Logger;
this.teamRepository = new InMemoryTeamRepository(this.logger);
this.membershipRepository = new InMemoryTeamMembershipRepository(this.logger);
this.driverRepository = new InMemoryDriverRepository(this.logger);
this.statsRepository = new InMemoryTeamStatsRepository(this.logger);
}
public clear(): void {
this.teamRepository.clear();
this.membershipRepository.clear();
this.driverRepository.clear();
this.statsRepository.clear();
}
public createCreateTeamUseCase(): CreateTeamUseCase {
return new CreateTeamUseCase(this.teamRepository, this.membershipRepository, this.logger);
}
public createJoinTeamUseCase(): JoinTeamUseCase {
return new JoinTeamUseCase(this.teamRepository, this.membershipRepository, this.logger);
}
public createLeaveTeamUseCase(): LeaveTeamUseCase {
return new LeaveTeamUseCase(this.teamRepository, this.membershipRepository, this.logger);
}
public createGetTeamMembershipUseCase(): GetTeamMembershipUseCase {
return new GetTeamMembershipUseCase(this.membershipRepository, this.logger);
}
public createGetTeamMembersUseCase(): GetTeamMembersUseCase {
return new GetTeamMembersUseCase(this.membershipRepository, this.driverRepository, this.teamRepository, this.logger);
}
public createGetTeamJoinRequestsUseCase(): GetTeamJoinRequestsUseCase {
return new GetTeamJoinRequestsUseCase(this.membershipRepository, this.driverRepository, this.teamRepository);
}
public createApproveTeamJoinRequestUseCase(): ApproveTeamJoinRequestUseCase {
return new ApproveTeamJoinRequestUseCase(this.membershipRepository);
}
public createUpdateTeamUseCase(): UpdateTeamUseCase {
return new UpdateTeamUseCase(this.teamRepository, this.membershipRepository);
}
public createGetTeamDetailsUseCase(): GetTeamDetailsUseCase {
return new GetTeamDetailsUseCase(this.teamRepository, this.membershipRepository);
}
public createGetTeamsLeaderboardUseCase(getDriverStats: (driverId: string) => any): GetTeamsLeaderboardUseCase {
return new GetTeamsLeaderboardUseCase(
this.teamRepository,
this.membershipRepository,
getDriverStats,
this.logger
);
}
public createGetAllTeamsUseCase(): GetAllTeamsUseCase {
return new GetAllTeamsUseCase(this.teamRepository, this.membershipRepository, this.statsRepository, this.logger);
}
}

View File

@@ -0,0 +1,103 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { TeamsTestContext } from '../TeamsTestContext';
import { Team } from '../../../../core/racing/domain/entities/Team';
describe('UpdateTeamUseCase', () => {
const context = new TeamsTestContext();
const updateTeamUseCase = context.createUpdateTeamUseCase();
beforeEach(() => {
context.clear();
});
describe('Success Path', () => {
it('should update team details when called by owner', async () => {
const teamId = 't1';
const ownerId = 'o1';
const team = Team.create({ id: teamId, name: 'Old Name', tag: 'OLD', description: 'Old Desc', ownerId, leagues: [] });
await context.teamRepository.create(team);
await context.membershipRepository.saveMembership({
teamId,
driverId: ownerId,
role: 'owner',
status: 'active',
joinedAt: new Date()
});
const result = await updateTeamUseCase.execute({
teamId,
updatedBy: ownerId,
updates: {
name: 'New Name',
tag: 'NEW',
description: 'New Desc'
}
});
expect(result.isOk()).toBe(true);
const { team: updatedTeam } = result.unwrap();
expect(updatedTeam.name.toString()).toBe('New Name');
expect(updatedTeam.tag.toString()).toBe('NEW');
expect(updatedTeam.description.toString()).toBe('New Desc');
const savedTeam = await context.teamRepository.findById(teamId);
expect(savedTeam?.name.toString()).toBe('New Name');
});
it('should update team details when called by manager', async () => {
const teamId = 't2';
const managerId = 'm2';
const team = Team.create({ id: teamId, name: 'Team 2', tag: 'T2', description: 'Desc', ownerId: 'owner', leagues: [] });
await context.teamRepository.create(team);
await context.membershipRepository.saveMembership({
teamId,
driverId: managerId,
role: 'manager',
status: 'active',
joinedAt: new Date()
});
const result = await updateTeamUseCase.execute({
teamId,
updatedBy: managerId,
updates: {
name: 'Updated by Manager'
}
});
expect(result.isOk()).toBe(true);
const { team: updatedTeam } = result.unwrap();
expect(updatedTeam.name.toString()).toBe('Updated by Manager');
});
});
describe('Validation', () => {
it('should reject update when called by regular member', async () => {
const teamId = 't3';
const memberId = 'd3';
const team = Team.create({ id: teamId, name: 'Team 3', tag: 'T3', description: 'Desc', ownerId: 'owner', leagues: [] });
await context.teamRepository.create(team);
await context.membershipRepository.saveMembership({
teamId,
driverId: memberId,
role: 'driver',
status: 'active',
joinedAt: new Date()
});
const result = await updateTeamUseCase.execute({
teamId,
updatedBy: memberId,
updates: {
name: 'Unauthorized Update'
}
});
expect(result.isErr()).toBe(true);
expect(result.unwrapErr().code).toBe('PERMISSION_DENIED');
});
});
});

View File

@@ -0,0 +1,193 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { TeamsTestContext } from '../TeamsTestContext';
import { Team } from '../../../../core/racing/domain/entities/Team';
describe('CreateTeamUseCase', () => {
const context = new TeamsTestContext();
const createTeamUseCase = context.createCreateTeamUseCase();
beforeEach(() => {
context.clear();
});
describe('Success Path', () => {
it('should create a team with all required fields', async () => {
const driverId = 'd1';
const leagueId = 'l1';
const result = await createTeamUseCase.execute({
name: 'Test Team',
tag: 'TT',
description: 'A test team',
ownerId: driverId,
leagues: [leagueId]
});
expect(result.isOk()).toBe(true);
const { team } = result.unwrap();
expect(team.name.toString()).toBe('Test Team');
expect(team.tag.toString()).toBe('TT');
expect(team.description.toString()).toBe('A test team');
expect(team.ownerId.toString()).toBe(driverId);
expect(team.leagues.map(l => l.toString())).toContain(leagueId);
const savedTeam = await context.teamRepository.findById(team.id.toString());
expect(savedTeam).toBeDefined();
expect(savedTeam?.name.toString()).toBe('Test Team');
const membership = await context.membershipRepository.getMembership(team.id.toString(), driverId);
expect(membership).toBeDefined();
expect(membership?.role).toBe('owner');
expect(membership?.status).toBe('active');
});
it('should create a team with optional description', async () => {
const driverId = 'd2';
const leagueId = 'l2';
const result = await createTeamUseCase.execute({
name: 'Team With Description',
tag: 'TWD',
description: 'This team has a detailed description',
ownerId: driverId,
leagues: [leagueId]
});
expect(result.isOk()).toBe(true);
const { team } = result.unwrap();
expect(team.description.toString()).toBe('This team has a detailed description');
});
});
describe('Validation', () => {
it('should reject team creation with empty team name', async () => {
const driverId = 'd4';
const leagueId = 'l4';
const result = await createTeamUseCase.execute({
name: '',
tag: 'TT',
description: 'A test team',
ownerId: driverId,
leagues: [leagueId]
});
expect(result.isErr()).toBe(true);
const error = result.unwrapErr();
expect(error.code).toBe('REPOSITORY_ERROR');
});
it('should reject team creation with empty description', async () => {
const driverId = 'd3';
const leagueId = 'l3';
const result = await createTeamUseCase.execute({
name: 'Minimal Team',
tag: 'MT',
description: '',
ownerId: driverId,
leagues: [leagueId]
});
expect(result.isErr()).toBe(true);
const error = result.unwrapErr();
expect(error.code).toBe('REPOSITORY_ERROR');
});
it('should reject team creation when driver already belongs to a team', async () => {
const driverId = 'd6';
const leagueId = 'l6';
const existingTeam = Team.create({ id: 'existing', name: 'Existing Team', tag: 'ET', description: 'Existing', ownerId: driverId, leagues: [] });
await context.teamRepository.create(existingTeam);
await context.membershipRepository.saveMembership({
teamId: 'existing',
driverId: driverId,
role: 'driver',
status: 'active',
joinedAt: new Date()
});
const result = await createTeamUseCase.execute({
name: 'New Team',
tag: 'NT',
description: 'A new team',
ownerId: driverId,
leagues: [leagueId]
});
expect(result.isErr()).toBe(true);
const error = result.unwrapErr();
expect(error.code).toBe('VALIDATION_ERROR');
expect(error.details.message).toContain('already belongs to a team');
});
});
describe('Business Logic', () => {
it('should set the creating driver as team captain', async () => {
const driverId = 'd10';
const leagueId = 'l10';
const result = await createTeamUseCase.execute({
name: 'Captain Team',
tag: 'CT',
description: 'A team with captain',
ownerId: driverId,
leagues: [leagueId]
});
expect(result.isOk()).toBe(true);
const { team } = result.unwrap();
const membership = await context.membershipRepository.getMembership(team.id.toString(), driverId);
expect(membership).toBeDefined();
expect(membership?.role).toBe('owner');
});
it('should generate unique team ID', async () => {
const driverId = 'd11';
const leagueId = 'l11';
const result = await createTeamUseCase.execute({
name: 'Unique Team',
tag: 'UT',
description: 'A unique team',
ownerId: driverId,
leagues: [leagueId]
});
expect(result.isOk()).toBe(true);
const { team } = result.unwrap();
expect(team.id.toString()).toBeDefined();
expect(team.id.toString().length).toBeGreaterThan(0);
const existingTeam = await context.teamRepository.findById(team.id.toString());
expect(existingTeam).toBeDefined();
expect(existingTeam?.id.toString()).toBe(team.id.toString());
});
it('should set creation timestamp', async () => {
const driverId = 'd12';
const leagueId = 'l12';
const beforeCreate = new Date();
const result = await createTeamUseCase.execute({
name: 'Timestamp Team',
tag: 'TT',
description: 'A team with timestamp',
ownerId: driverId,
leagues: [leagueId]
});
const afterCreate = new Date();
expect(result.isOk()).toBe(true);
const { team } = result.unwrap();
expect(team.createdAt).toBeDefined();
const createdAt = team.createdAt.toDate();
expect(createdAt.getTime()).toBeGreaterThanOrEqual(beforeCreate.getTime());
expect(createdAt.getTime()).toBeLessThanOrEqual(afterCreate.getTime());
});
});
});

View File

@@ -0,0 +1,83 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { TeamsTestContext } from '../TeamsTestContext';
import { Team } from '../../../../core/racing/domain/entities/Team';
describe('GetTeamDetailsUseCase', () => {
const context = new TeamsTestContext();
const getTeamDetailsUseCase = context.createGetTeamDetailsUseCase();
beforeEach(() => {
context.clear();
});
describe('Success Path', () => {
it('should retrieve team detail with membership and management permissions for owner', async () => {
const teamId = 't1';
const ownerId = 'd1';
const team = Team.create({ id: teamId, name: 'Team 1', tag: 'T1', description: 'Desc', ownerId, leagues: [] });
await context.teamRepository.create(team);
await context.membershipRepository.saveMembership({
teamId,
driverId: ownerId,
role: 'owner',
status: 'active',
joinedAt: new Date()
});
const result = await getTeamDetailsUseCase.execute({ teamId, driverId: ownerId });
expect(result.isOk()).toBe(true);
const data = result.unwrap();
expect(data.team.id.toString()).toBe(teamId);
expect(data.membership?.role).toBe('owner');
expect(data.canManage).toBe(true);
});
it('should retrieve team detail for a non-member', async () => {
const teamId = 't2';
const team = Team.create({ id: teamId, name: 'Team 2', tag: 'T2', description: 'Desc', ownerId: 'owner', leagues: [] });
await context.teamRepository.create(team);
const result = await getTeamDetailsUseCase.execute({ teamId, driverId: 'non-member' });
expect(result.isOk()).toBe(true);
const data = result.unwrap();
expect(data.team.id.toString()).toBe(teamId);
expect(data.membership).toBeNull();
expect(data.canManage).toBe(false);
});
it('should retrieve team detail for a regular member', async () => {
const teamId = 't3';
const memberId = 'd3';
const team = Team.create({ id: teamId, name: 'Team 3', tag: 'T3', description: 'Desc', ownerId: 'owner', leagues: [] });
await context.teamRepository.create(team);
await context.membershipRepository.saveMembership({
teamId,
driverId: memberId,
role: 'driver',
status: 'active',
joinedAt: new Date()
});
const result = await getTeamDetailsUseCase.execute({ teamId, driverId: memberId });
expect(result.isOk()).toBe(true);
const data = result.unwrap();
expect(data.team.id.toString()).toBe(teamId);
expect(data.membership?.role).toBe('driver');
expect(data.canManage).toBe(false);
});
});
describe('Error Handling', () => {
it('should throw error when team does not exist', async () => {
const result = await getTeamDetailsUseCase.execute({ teamId: 'nonexistent', driverId: 'any' });
expect(result.isErr()).toBe(true);
expect(result.unwrapErr().code).toBe('TEAM_NOT_FOUND');
});
});
});

View File

@@ -0,0 +1,54 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { TeamsTestContext } from '../TeamsTestContext';
import { Team } from '../../../../core/racing/domain/entities/Team';
describe('GetTeamsLeaderboardUseCase', () => {
const context = new TeamsTestContext();
// Mock driver stats provider
const getDriverStats = (driverId: string) => {
const statsMap: Record<string, { rating: number, wins: number, totalRaces: number }> = {
'd1': { rating: 2000, wins: 10, totalRaces: 50 },
'd2': { rating: 1500, wins: 5, totalRaces: 30 },
'd3': { rating: 1000, wins: 2, totalRaces: 20 },
};
return statsMap[driverId] || null;
};
const getTeamsLeaderboardUseCase = context.createGetTeamsLeaderboardUseCase(getDriverStats);
beforeEach(() => {
context.clear();
});
describe('Success Path', () => {
it('should retrieve ranked team leaderboard with performance metrics', async () => {
const team1 = Team.create({ id: 't1', name: 'Pro Team', tag: 'PRO', description: 'Desc', ownerId: 'o1', leagues: [] });
const team2 = Team.create({ id: 't2', name: 'Am Team', tag: 'AM', description: 'Desc', ownerId: 'o2', leagues: [] });
await context.teamRepository.create(team1);
await context.teamRepository.create(team2);
await context.membershipRepository.saveMembership({ teamId: 't1', driverId: 'd1', role: 'owner', status: 'active', joinedAt: new Date() });
await context.membershipRepository.saveMembership({ teamId: 't2', driverId: 'd3', role: 'owner', status: 'active', joinedAt: new Date() });
const result = await getTeamsLeaderboardUseCase.execute({ leagueId: 'any' });
expect(result.isOk()).toBe(true);
const { items, topItems } = result.unwrap();
expect(items).toHaveLength(2);
expect(topItems[0]?.team.id.toString()).toBe('t1');
expect(topItems[0]?.rating).toBe(2000);
expect(topItems[1]?.team.id.toString()).toBe('t2');
expect(topItems[1]?.rating).toBe(1000);
});
it('should handle empty leaderboard', async () => {
const result = await getTeamsLeaderboardUseCase.execute({ leagueId: 'any' });
expect(result.isOk()).toBe(true);
const { items } = result.unwrap();
expect(items).toHaveLength(0);
});
});
});

View File

@@ -0,0 +1,56 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { TeamsTestContext } from '../TeamsTestContext';
import { Team } from '../../../../core/racing/domain/entities/Team';
describe('GetAllTeamsUseCase', () => {
const context = new TeamsTestContext();
const getAllTeamsUseCase = context.createGetAllTeamsUseCase();
beforeEach(() => {
context.clear();
});
describe('Success Path', () => {
it('should retrieve complete teams list with all teams and enrichment', async () => {
const team1 = Team.create({ id: 't1', name: 'Team 1', tag: 'T1', description: 'Desc 1', ownerId: 'o1', leagues: [] });
const team2 = Team.create({ id: 't2', name: 'Team 2', tag: 'T2', description: 'Desc 2', ownerId: 'o2', leagues: [] });
await context.teamRepository.create(team1);
await context.teamRepository.create(team2);
await context.membershipRepository.saveMembership({ teamId: 't1', driverId: 'd1', role: 'owner', status: 'active', joinedAt: new Date() });
await context.membershipRepository.saveMembership({ teamId: 't1', driverId: 'd2', role: 'driver', status: 'active', joinedAt: new Date() });
await context.membershipRepository.saveMembership({ teamId: 't2', driverId: 'd3', role: 'owner', status: 'active', joinedAt: new Date() });
await context.statsRepository.saveTeamStats('t1', {
totalWins: 5,
totalRaces: 20,
rating: 1500,
performanceLevel: 'intermediate',
specialization: 'sprint',
region: 'EU',
languages: ['en'],
isRecruiting: true
});
const result = await getAllTeamsUseCase.execute({});
expect(result.isOk()).toBe(true);
const { teams, totalCount } = result.unwrap();
expect(totalCount).toBe(2);
const enriched1 = teams.find(t => t.team.id.toString() === 't1');
expect(enriched1?.memberCount).toBe(2);
expect(enriched1?.totalWins).toBe(5);
expect(enriched1?.rating).toBe(1500);
});
it('should handle empty teams list', async () => {
const result = await getAllTeamsUseCase.execute({});
expect(result.isOk()).toBe(true);
const { teams, totalCount } = result.unwrap();
expect(totalCount).toBe(0);
expect(teams).toHaveLength(0);
});
});
});

View File

@@ -0,0 +1,203 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { TeamsTestContext } from '../TeamsTestContext';
import { Driver } from '../../../../core/racing/domain/entities/Driver';
import { Team } from '../../../../core/racing/domain/entities/Team';
describe('Team Membership Use Cases', () => {
const context = new TeamsTestContext();
const joinTeamUseCase = context.createJoinTeamUseCase();
const leaveTeamUseCase = context.createLeaveTeamUseCase();
const getTeamMembershipUseCase = context.createGetTeamMembershipUseCase();
const getTeamMembersUseCase = context.createGetTeamMembersUseCase();
const getTeamJoinRequestsUseCase = context.createGetTeamJoinRequestsUseCase();
const approveTeamJoinRequestUseCase = context.createApproveTeamJoinRequestUseCase();
beforeEach(() => {
context.clear();
});
describe('JoinTeamUseCase', () => {
it('should create a join request for a team', async () => {
const driverId = 'd1';
const driver = Driver.create({ id: driverId, iracingId: '1', name: 'Driver 1', country: 'US' });
await context.driverRepository.create(driver);
const teamId = 't1';
const team = Team.create({ id: teamId, name: 'Team 1', tag: 'T1', description: 'Test Team', ownerId: 'owner', leagues: [] });
await context.teamRepository.create(team);
const result = await joinTeamUseCase.execute({ teamId, driverId });
expect(result.isOk()).toBe(true);
const { membership } = result.unwrap();
expect(membership.status).toBe('active');
expect(membership.role).toBe('driver');
const savedMembership = await context.membershipRepository.getMembership(teamId, driverId);
expect(savedMembership).toBeDefined();
expect(savedMembership?.status).toBe('active');
});
it('should reject join request when driver is already a member', async () => {
const driverId = 'd3';
const teamId = 't3';
const team = Team.create({ id: teamId, name: 'Team 3', tag: 'T3', description: 'Test Team', ownerId: 'owner', leagues: [] });
await context.teamRepository.create(team);
await context.membershipRepository.saveMembership({
teamId,
driverId,
role: 'driver',
status: 'active',
joinedAt: new Date()
});
const result = await joinTeamUseCase.execute({ teamId, driverId });
expect(result.isErr()).toBe(true);
// JoinTeamUseCase returns ALREADY_IN_TEAM if driver is in ANY team,
// and ALREADY_MEMBER if they are already in THIS team.
// In this case, they are already in this team.
expect(result.unwrapErr().code).toBe('ALREADY_IN_TEAM');
});
});
describe('LeaveTeamUseCase', () => {
it('should allow driver to leave team', async () => {
const driverId = 'd7';
const teamId = 't7';
const team = Team.create({ id: teamId, name: 'Team 7', tag: 'T7', description: 'Test Team', ownerId: 'owner', leagues: [] });
await context.teamRepository.create(team);
await context.membershipRepository.saveMembership({
teamId,
driverId,
role: 'driver',
status: 'active',
joinedAt: new Date()
});
const result = await leaveTeamUseCase.execute({ teamId, driverId });
expect(result.isOk()).toBe(true);
const savedMembership = await context.membershipRepository.getMembership(teamId, driverId);
expect(savedMembership).toBeNull();
});
it('should reject leave when driver is team owner', async () => {
const driverId = 'd9';
const teamId = 't9';
const team = Team.create({ id: teamId, name: 'Team 9', tag: 'T9', description: 'Test Team', ownerId: driverId, leagues: [] });
await context.teamRepository.create(team);
await context.membershipRepository.saveMembership({
teamId,
driverId,
role: 'owner',
status: 'active',
joinedAt: new Date()
});
const result = await leaveTeamUseCase.execute({ teamId, driverId });
expect(result.isErr()).toBe(true);
expect(result.unwrapErr().code).toBe('OWNER_CANNOT_LEAVE');
});
});
describe('GetTeamMembershipUseCase', () => {
it('should retrieve driver membership in team', async () => {
const driverId = 'd10';
const teamId = 't10';
const team = Team.create({ id: teamId, name: 'Team 10', tag: 'T10', description: 'Test Team', ownerId: 'owner', leagues: [] });
await context.teamRepository.create(team);
await context.membershipRepository.saveMembership({
teamId,
driverId,
role: 'driver',
status: 'active',
joinedAt: new Date()
});
const result = await getTeamMembershipUseCase.execute({ teamId, driverId });
expect(result.isOk()).toBe(true);
const { membership } = result.unwrap();
expect(membership?.role).toBe('member');
});
});
describe('GetTeamMembersUseCase', () => {
it('should retrieve all team members', async () => {
const teamId = 't12';
const team = Team.create({ id: teamId, name: 'Team 12', tag: 'T12', description: 'Test Team', ownerId: 'owner', leagues: [] });
await context.teamRepository.create(team);
const driver1 = Driver.create({ id: 'd12', iracingId: '12', name: 'Driver 12', country: 'US' });
const driver2 = Driver.create({ id: 'd13', iracingId: '13', name: 'Driver 13', country: 'UK' });
await context.driverRepository.create(driver1);
await context.driverRepository.create(driver2);
await context.membershipRepository.saveMembership({ teamId, driverId: 'd12', role: 'owner', status: 'active', joinedAt: new Date() });
await context.membershipRepository.saveMembership({ teamId, driverId: 'd13', role: 'driver', status: 'active', joinedAt: new Date() });
const result = await getTeamMembersUseCase.execute({ teamId });
expect(result.isOk()).toBe(true);
const { members } = result.unwrap();
expect(members).toHaveLength(2);
});
});
describe('GetTeamJoinRequestsUseCase', () => {
it('should retrieve pending join requests', async () => {
const teamId = 't14';
const team = Team.create({ id: teamId, name: 'Team 14', tag: 'T14', description: 'Test Team', ownerId: 'owner', leagues: [] });
await context.teamRepository.create(team);
const driver1 = Driver.create({ id: 'd14', iracingId: '14', name: 'Driver 14', country: 'US' });
await context.driverRepository.create(driver1);
await context.membershipRepository.saveJoinRequest({
id: 'jr2',
teamId,
driverId: 'd14',
status: 'pending',
requestedAt: new Date()
});
const result = await getTeamJoinRequestsUseCase.execute({ teamId });
expect(result.isOk()).toBe(true);
const { joinRequests } = result.unwrap();
expect(joinRequests).toHaveLength(1);
});
});
describe('ApproveTeamJoinRequestUseCase', () => {
it('should approve a pending join request', async () => {
const teamId = 't16';
const team = Team.create({ id: teamId, name: 'Team 16', tag: 'T16', description: 'Test Team', ownerId: 'owner', leagues: [] });
await context.teamRepository.create(team);
const driverId = 'd16';
const driver = Driver.create({ id: driverId, iracingId: '16', name: 'Driver 16', country: 'US' });
await context.driverRepository.create(driver);
await context.membershipRepository.saveJoinRequest({
id: 'jr4',
teamId,
driverId,
status: 'pending',
requestedAt: new Date()
});
const result = await approveTeamJoinRequestUseCase.execute({ teamId, requestId: 'jr4' });
expect(result.isOk()).toBe(true);
const savedMembership = await context.membershipRepository.getMembership(teamId, driverId);
expect(savedMembership?.status).toBe('active');
});
});
});

View File

@@ -1,201 +0,0 @@
/**
* Integration Test: Team Admin Use Case Orchestration
*
* Tests the orchestration logic of team admin-related Use Cases:
* - UpdateTeamUseCase: Admin updates team details
* - Validates that Use Cases correctly interact with their Ports (Repositories)
* - Uses In-Memory adapters for fast, deterministic testing
*
* Focus: Business logic orchestration, NOT UI rendering
*/
import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
import { InMemoryTeamRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryTeamRepository';
import { InMemoryTeamMembershipRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryTeamMembershipRepository';
import { UpdateTeamUseCase } from '../../../core/racing/application/use-cases/UpdateTeamUseCase';
import { Team } from '../../../core/racing/domain/entities/Team';
import { Logger } from '../../../core/shared/domain/Logger';
describe('Team Admin Use Case Orchestration', () => {
let teamRepository: InMemoryTeamRepository;
let membershipRepository: InMemoryTeamMembershipRepository;
let updateTeamUseCase: UpdateTeamUseCase;
let mockLogger: Logger;
beforeAll(() => {
mockLogger = {
info: () => {},
debug: () => {},
warn: () => {},
error: () => {},
} as unknown as Logger;
teamRepository = new InMemoryTeamRepository(mockLogger);
membershipRepository = new InMemoryTeamMembershipRepository(mockLogger);
updateTeamUseCase = new UpdateTeamUseCase(teamRepository, membershipRepository);
});
beforeEach(() => {
teamRepository.clear();
membershipRepository.clear();
});
describe('UpdateTeamUseCase - Success Path', () => {
it('should update team details when called by owner', async () => {
// Scenario: Owner updates team details
// Given: A team exists
const teamId = 't1';
const ownerId = 'o1';
const team = Team.create({ id: teamId, name: 'Old Name', tag: 'OLD', description: 'Old Desc', ownerId, leagues: [] });
await teamRepository.create(team);
// And: The driver is the owner
await membershipRepository.saveMembership({
teamId,
driverId: ownerId,
role: 'owner',
status: 'active',
joinedAt: new Date()
});
// When: UpdateTeamUseCase.execute() is called
const result = await updateTeamUseCase.execute({
teamId,
updatedBy: ownerId,
updates: {
name: 'New Name',
tag: 'NEW',
description: 'New Desc'
}
});
// Then: The team should be updated successfully
expect(result.isOk()).toBe(true);
const { team: updatedTeam } = result.unwrap();
expect(updatedTeam.name.toString()).toBe('New Name');
expect(updatedTeam.tag.toString()).toBe('NEW');
expect(updatedTeam.description.toString()).toBe('New Desc');
// And: The changes should be in the repository
const savedTeam = await teamRepository.findById(teamId);
expect(savedTeam?.name.toString()).toBe('New Name');
});
it('should update team details when called by manager', async () => {
// Scenario: Manager updates team details
// Given: A team exists
const teamId = 't2';
const managerId = 'm2';
const team = Team.create({ id: teamId, name: 'Team 2', tag: 'T2', description: 'Desc', ownerId: 'owner', leagues: [] });
await teamRepository.create(team);
// And: The driver is a manager
await membershipRepository.saveMembership({
teamId,
driverId: managerId,
role: 'manager',
status: 'active',
joinedAt: new Date()
});
// When: UpdateTeamUseCase.execute() is called
const result = await updateTeamUseCase.execute({
teamId,
updatedBy: managerId,
updates: {
name: 'Updated by Manager'
}
});
// Then: The team should be updated successfully
expect(result.isOk()).toBe(true);
const { team: updatedTeam } = result.unwrap();
expect(updatedTeam.name.toString()).toBe('Updated by Manager');
});
});
describe('UpdateTeamUseCase - Validation', () => {
it('should reject update when called by regular member', async () => {
// Scenario: Regular member tries to update team
// Given: A team exists
const teamId = 't3';
const memberId = 'd3';
const team = Team.create({ id: teamId, name: 'Team 3', tag: 'T3', description: 'Desc', ownerId: 'owner', leagues: [] });
await teamRepository.create(team);
// And: The driver is a regular member
await membershipRepository.saveMembership({
teamId,
driverId: memberId,
role: 'driver',
status: 'active',
joinedAt: new Date()
});
// When: UpdateTeamUseCase.execute() is called
const result = await updateTeamUseCase.execute({
teamId,
updatedBy: memberId,
updates: {
name: 'Unauthorized Update'
}
});
// Then: Should return error
expect(result.isErr()).toBe(true);
const error = result.unwrapErr();
expect(error.code).toBe('PERMISSION_DENIED');
});
it('should reject update when called by non-member', async () => {
// Scenario: Non-member tries to update team
// Given: A team exists
const teamId = 't4';
const team = Team.create({ id: teamId, name: 'Team 4', tag: 'T4', description: 'Desc', ownerId: 'owner', leagues: [] });
await teamRepository.create(team);
// When: UpdateTeamUseCase.execute() is called
const result = await updateTeamUseCase.execute({
teamId,
updatedBy: 'non-member',
updates: {
name: 'Unauthorized Update'
}
});
// Then: Should return error
expect(result.isErr()).toBe(true);
const error = result.unwrapErr();
expect(error.code).toBe('PERMISSION_DENIED');
});
});
describe('UpdateTeamUseCase - Error Handling', () => {
it('should throw error when team does not exist', async () => {
// Scenario: Non-existent team
// Given: A driver exists who is a manager of some team
const managerId = 'm5';
await membershipRepository.saveMembership({
teamId: 'some-team',
driverId: managerId,
role: 'manager',
status: 'active',
joinedAt: new Date()
});
// When: UpdateTeamUseCase.execute() is called with non-existent team ID
const result = await updateTeamUseCase.execute({
teamId: 'nonexistent',
updatedBy: managerId,
updates: {
name: 'New Name'
}
});
// Then: Should return error
expect(result.isErr()).toBe(true);
const error = result.unwrapErr();
expect(error.code).toBe('PERMISSION_DENIED'); // Because membership check fails first
});
});
});

View File

@@ -1,403 +0,0 @@
/**
* Integration Test: Team Creation Use Case Orchestration
*
* Tests the orchestration logic of team creation-related Use Cases:
* - CreateTeamUseCase: Creates a new team with name, description, and leagues
* - Validates that Use Cases correctly interact with their Ports (Repositories)
* - Uses In-Memory adapters for fast, deterministic testing
*
* Focus: Business logic orchestration, NOT UI rendering
*/
import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
import { InMemoryTeamRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryTeamRepository';
import { InMemoryTeamMembershipRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryTeamMembershipRepository';
import { CreateTeamUseCase } from '../../../core/racing/application/use-cases/CreateTeamUseCase';
import { Team } from '../../../core/racing/domain/entities/Team';
import { Driver } from '../../../core/racing/domain/entities/Driver';
import { League } from '../../../core/racing/domain/entities/League';
import { Logger } from '../../../core/shared/domain/Logger';
describe('Team Creation Use Case Orchestration', () => {
let teamRepository: InMemoryTeamRepository;
let membershipRepository: InMemoryTeamMembershipRepository;
let createTeamUseCase: CreateTeamUseCase;
let mockLogger: Logger;
beforeAll(() => {
mockLogger = {
info: () => {},
debug: () => {},
warn: () => {},
error: () => {},
} as unknown as Logger;
teamRepository = new InMemoryTeamRepository(mockLogger);
membershipRepository = new InMemoryTeamMembershipRepository(mockLogger);
createTeamUseCase = new CreateTeamUseCase(teamRepository, membershipRepository, mockLogger);
});
beforeEach(() => {
teamRepository.clear();
membershipRepository.clear();
});
describe('CreateTeamUseCase - Success Path', () => {
it('should create a team with all required fields', async () => {
// Scenario: Team creation with complete information
// Given: A driver exists
const driverId = 'd1';
const driver = Driver.create({ id: driverId, iracingId: '1', name: 'John Doe', country: 'US' });
// And: A league exists
const leagueId = 'l1';
const league = League.create({ id: leagueId, name: 'League 1', description: 'Test League', ownerId: 'owner' });
// When: CreateTeamUseCase.execute() is called with valid command
const result = await createTeamUseCase.execute({
name: 'Test Team',
tag: 'TT',
description: 'A test team',
ownerId: driverId,
leagues: [leagueId]
});
// Then: The team should be created successfully
expect(result.isOk()).toBe(true);
const { team } = result.unwrap();
// And: The team should have the correct properties
expect(team.name.toString()).toBe('Test Team');
expect(team.tag.toString()).toBe('TT');
expect(team.description.toString()).toBe('A test team');
expect(team.ownerId.toString()).toBe(driverId);
expect(team.leagues.map(l => l.toString())).toContain(leagueId);
// And: The team should be in the repository
const savedTeam = await teamRepository.findById(team.id.toString());
expect(savedTeam).toBeDefined();
expect(savedTeam?.name.toString()).toBe('Test Team');
// And: The driver should have an owner membership
const membership = await membershipRepository.getMembership(team.id.toString(), driverId);
expect(membership).toBeDefined();
expect(membership?.role).toBe('owner');
expect(membership?.status).toBe('active');
});
it('should create a team with optional description', async () => {
// Scenario: Team creation with description
// Given: A driver exists
const driverId = 'd2';
const driver = Driver.create({ id: driverId, iracingId: '2', name: 'Jane Doe', country: 'UK' });
// And: A league exists
const leagueId = 'l2';
const league = League.create({ id: leagueId, name: 'League 2', description: 'Test League 2', ownerId: 'owner' });
// When: CreateTeamUseCase.execute() is called with description
const result = await createTeamUseCase.execute({
name: 'Team With Description',
tag: 'TWD',
description: 'This team has a detailed description',
ownerId: driverId,
leagues: [leagueId]
});
// Then: The team should be created with the description
expect(result.isOk()).toBe(true);
const { team } = result.unwrap();
expect(team.description.toString()).toBe('This team has a detailed description');
});
it('should create a team with minimal required fields', async () => {
// Scenario: Team creation with minimal information
// Given: A driver exists
const driverId = 'd3';
const driver = Driver.create({ id: driverId, iracingId: '3', name: 'Bob Smith', country: 'CA' });
// And: A league exists
const leagueId = 'l3';
const league = League.create({ id: leagueId, name: 'League 3', description: 'Test League 3', ownerId: 'owner' });
// When: CreateTeamUseCase.execute() is called with only required fields
const result = await createTeamUseCase.execute({
name: 'Minimal Team',
tag: 'MT',
description: '',
ownerId: driverId,
leagues: [leagueId]
});
// Then: The team should be created with default values
expect(result.isOk()).toBe(true);
const { team } = result.unwrap();
expect(team.name.toString()).toBe('Minimal Team');
expect(team.tag.toString()).toBe('MT');
expect(team.description.toString()).toBe('');
});
});
describe('CreateTeamUseCase - Validation', () => {
it('should reject team creation with empty team name', async () => {
// Scenario: Team creation with empty name
// Given: A driver exists
const driverId = 'd4';
const driver = Driver.create({ id: driverId, iracingId: '4', name: 'Test Driver', country: 'US' });
// And: A league exists
const leagueId = 'l4';
const league = League.create({ id: leagueId, name: 'League 4', description: 'Test League 4', ownerId: 'owner' });
// When: CreateTeamUseCase.execute() is called with empty team name
const result = await createTeamUseCase.execute({
name: '',
tag: 'TT',
description: 'A test team',
ownerId: driverId,
leagues: [leagueId]
});
// Then: Should return error
expect(result.isErr()).toBe(true);
const error = result.unwrapErr();
expect(error.code).toBe('VALIDATION_ERROR');
});
it('should reject team creation with invalid team name format', async () => {
// Scenario: Team creation with invalid name format
// Given: A driver exists
const driverId = 'd5';
const driver = Driver.create({ id: driverId, iracingId: '5', name: 'Test Driver', country: 'US' });
// And: A league exists
const leagueId = 'l5';
const league = League.create({ id: leagueId, name: 'League 5', description: 'Test League 5', ownerId: 'owner' });
// When: CreateTeamUseCase.execute() is called with invalid team name
const result = await createTeamUseCase.execute({
name: 'Invalid!@#$%',
tag: 'TT',
description: 'A test team',
ownerId: driverId,
leagues: [leagueId]
});
// Then: Should return error
expect(result.isErr()).toBe(true);
const error = result.unwrapErr();
expect(error.code).toBe('VALIDATION_ERROR');
});
it('should reject team creation when driver already belongs to a team', async () => {
// Scenario: Driver already belongs to a team
// Given: A driver exists
const driverId = 'd6';
const driver = Driver.create({ id: driverId, iracingId: '6', name: 'Test Driver', country: 'US' });
// And: A league exists
const leagueId = 'l6';
const league = League.create({ id: leagueId, name: 'League 6', description: 'Test League 6', ownerId: 'owner' });
// And: The driver already belongs to a team
const existingTeam = Team.create({ id: 'existing', name: 'Existing Team', tag: 'ET', description: 'Existing', ownerId: driverId, leagues: [] });
await teamRepository.create(existingTeam);
await membershipRepository.saveMembership({
teamId: 'existing',
driverId: driverId,
role: 'driver',
status: 'active',
joinedAt: new Date()
});
// When: CreateTeamUseCase.execute() is called
const result = await createTeamUseCase.execute({
name: 'New Team',
tag: 'NT',
description: 'A new team',
ownerId: driverId,
leagues: [leagueId]
});
// Then: Should return error
expect(result.isErr()).toBe(true);
const error = result.unwrapErr();
expect(error.code).toBe('VALIDATION_ERROR');
expect(error.details.message).toContain('already belongs to a team');
});
});
describe('CreateTeamUseCase - Error Handling', () => {
it('should throw error when driver does not exist', async () => {
// Scenario: Non-existent driver
// Given: No driver exists with the given ID
const nonExistentDriverId = 'nonexistent';
// And: A league exists
const leagueId = 'l7';
const league = League.create({ id: leagueId, name: 'League 7', description: 'Test League 7', ownerId: 'owner' });
// When: CreateTeamUseCase.execute() is called with non-existent driver ID
const result = await createTeamUseCase.execute({
name: 'Test Team',
tag: 'TT',
description: 'A test team',
ownerId: nonExistentDriverId,
leagues: [leagueId]
});
// Then: Should return error
expect(result.isErr()).toBe(true);
const error = result.unwrapErr();
expect(error.code).toBe('VALIDATION_ERROR');
});
it('should throw error when league does not exist', async () => {
// Scenario: Non-existent league
// Given: A driver exists
const driverId = 'd8';
const driver = Driver.create({ id: driverId, iracingId: '8', name: 'Test Driver', country: 'US' });
// And: No league exists with the given ID
const nonExistentLeagueId = 'nonexistent';
// When: CreateTeamUseCase.execute() is called with non-existent league ID
const result = await createTeamUseCase.execute({
name: 'Test Team',
tag: 'TT',
description: 'A test team',
ownerId: driverId,
leagues: [nonExistentLeagueId]
});
// Then: Should return error
expect(result.isErr()).toBe(true);
const error = result.unwrapErr();
expect(error.code).toBe('LEAGUE_NOT_FOUND');
});
it('should throw error when team name already exists', async () => {
// Scenario: Duplicate team name
// Given: A driver exists
const driverId = 'd9';
const driver = Driver.create({ id: driverId, iracingId: '9', name: 'Test Driver', country: 'US' });
// And: A league exists
const leagueId = 'l9';
const league = League.create({ id: leagueId, name: 'League 9', description: 'Test League 9', ownerId: 'owner' });
// And: A team with the same name already exists
const existingTeam = Team.create({ id: 'existing2', name: 'Duplicate Team', tag: 'DT', description: 'Existing', ownerId: 'other', leagues: [] });
await teamRepository.create(existingTeam);
// When: CreateTeamUseCase.execute() is called with duplicate team name
const result = await createTeamUseCase.execute({
name: 'Duplicate Team',
tag: 'DT2',
description: 'A new team',
ownerId: driverId,
leagues: [leagueId]
});
// Then: Should return error
expect(result.isErr()).toBe(true);
const error = result.unwrapErr();
expect(error.code).toBe('VALIDATION_ERROR');
expect(error.details.message).toContain('already exists');
});
});
describe('CreateTeamUseCase - Business Logic', () => {
it('should set the creating driver as team captain', async () => {
// Scenario: Driver becomes captain
// Given: A driver exists
const driverId = 'd10';
const driver = Driver.create({ id: driverId, iracingId: '10', name: 'Captain Driver', country: 'US' });
// And: A league exists
const leagueId = 'l10';
const league = League.create({ id: leagueId, name: 'League 10', description: 'Test League 10', ownerId: 'owner' });
// When: CreateTeamUseCase.execute() is called
const result = await createTeamUseCase.execute({
name: 'Captain Team',
tag: 'CT',
description: 'A team with captain',
ownerId: driverId,
leagues: [leagueId]
});
// Then: The creating driver should be set as team captain
expect(result.isOk()).toBe(true);
const { team } = result.unwrap();
// And: The captain role should be recorded in the team roster
const membership = await membershipRepository.getMembership(team.id.toString(), driverId);
expect(membership).toBeDefined();
expect(membership?.role).toBe('owner');
});
it('should generate unique team ID', async () => {
// Scenario: Unique team ID generation
// Given: A driver exists
const driverId = 'd11';
const driver = Driver.create({ id: driverId, iracingId: '11', name: 'Unique Driver', country: 'US' });
// And: A league exists
const leagueId = 'l11';
const league = League.create({ id: leagueId, name: 'League 11', description: 'Test League 11', ownerId: 'owner' });
// When: CreateTeamUseCase.execute() is called
const result = await createTeamUseCase.execute({
name: 'Unique Team',
tag: 'UT',
description: 'A unique team',
ownerId: driverId,
leagues: [leagueId]
});
// Then: The team should have a unique ID
expect(result.isOk()).toBe(true);
const { team } = result.unwrap();
expect(team.id.toString()).toBeDefined();
expect(team.id.toString().length).toBeGreaterThan(0);
// And: The ID should not conflict with existing teams
const existingTeam = await teamRepository.findById(team.id.toString());
expect(existingTeam).toBeDefined();
expect(existingTeam?.id.toString()).toBe(team.id.toString());
});
it('should set creation timestamp', async () => {
// Scenario: Creation timestamp
// Given: A driver exists
const driverId = 'd12';
const driver = Driver.create({ id: driverId, iracingId: '12', name: 'Timestamp Driver', country: 'US' });
// And: A league exists
const leagueId = 'l12';
const league = League.create({ id: leagueId, name: 'League 12', description: 'Test League 12', ownerId: 'owner' });
// When: CreateTeamUseCase.execute() is called
const beforeCreate = new Date();
const result = await createTeamUseCase.execute({
name: 'Timestamp Team',
tag: 'TT',
description: 'A team with timestamp',
ownerId: driverId,
leagues: [leagueId]
});
const afterCreate = new Date();
// Then: The team should have a creation timestamp
expect(result.isOk()).toBe(true);
const { team } = result.unwrap();
expect(team.createdAt).toBeDefined();
// And: The timestamp should be current or recent
const createdAt = team.createdAt.toDate();
expect(createdAt.getTime()).toBeGreaterThanOrEqual(beforeCreate.getTime());
expect(createdAt.getTime()).toBeLessThanOrEqual(afterCreate.getTime());
});
});
});

View File

@@ -1,131 +0,0 @@
/**
* Integration Test: Team Detail Use Case Orchestration
*
* Tests the orchestration logic of team detail-related Use Cases:
* - GetTeamDetailsUseCase: Retrieves detailed team information including roster and management permissions
* - Validates that Use Cases correctly interact with their Ports (Repositories)
* - Uses In-Memory adapters for fast, deterministic testing
*
* Focus: Business logic orchestration, NOT UI rendering
*/
import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
import { InMemoryTeamRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryTeamRepository';
import { InMemoryTeamMembershipRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryTeamMembershipRepository';
import { GetTeamDetailsUseCase } from '../../../core/racing/application/use-cases/GetTeamDetailsUseCase';
import { Team } from '../../../core/racing/domain/entities/Team';
import { Logger } from '../../../core/shared/domain/Logger';
describe('Team Detail Use Case Orchestration', () => {
let teamRepository: InMemoryTeamRepository;
let membershipRepository: InMemoryTeamMembershipRepository;
let getTeamDetailsUseCase: GetTeamDetailsUseCase;
let mockLogger: Logger;
beforeAll(() => {
mockLogger = {
info: () => {},
debug: () => {},
warn: () => {},
error: () => {},
} as unknown as Logger;
teamRepository = new InMemoryTeamRepository(mockLogger);
membershipRepository = new InMemoryTeamMembershipRepository(mockLogger);
getTeamDetailsUseCase = new GetTeamDetailsUseCase(teamRepository, membershipRepository);
});
beforeEach(() => {
teamRepository.clear();
membershipRepository.clear();
});
describe('GetTeamDetailsUseCase - Success Path', () => {
it('should retrieve team detail with membership and management permissions for owner', async () => {
// Scenario: Team owner views team details
// Given: A team exists
const teamId = 't1';
const ownerId = 'd1';
const team = Team.create({ id: teamId, name: 'Team 1', tag: 'T1', description: 'Desc', ownerId, leagues: [] });
await teamRepository.create(team);
// And: The driver is the owner
await membershipRepository.saveMembership({
teamId,
driverId: ownerId,
role: 'owner',
status: 'active',
joinedAt: new Date()
});
// When: GetTeamDetailsUseCase.execute() is called
const result = await getTeamDetailsUseCase.execute({ teamId, driverId: ownerId });
// Then: The result should contain team information and management permissions
expect(result.isOk()).toBe(true);
const data = result.unwrap();
expect(data.team.id.toString()).toBe(teamId);
expect(data.membership?.role).toBe('owner');
expect(data.canManage).toBe(true);
});
it('should retrieve team detail for a non-member', async () => {
// Scenario: Non-member views team details
// Given: A team exists
const teamId = 't2';
const team = Team.create({ id: teamId, name: 'Team 2', tag: 'T2', description: 'Desc', ownerId: 'owner', leagues: [] });
await teamRepository.create(team);
// When: GetTeamDetailsUseCase.execute() is called with a driver who is not a member
const result = await getTeamDetailsUseCase.execute({ teamId, driverId: 'non-member' });
// Then: The result should contain team information but no membership and no management permissions
expect(result.isOk()).toBe(true);
const data = result.unwrap();
expect(data.team.id.toString()).toBe(teamId);
expect(data.membership).toBeNull();
expect(data.canManage).toBe(false);
});
it('should retrieve team detail for a regular member', async () => {
// Scenario: Regular member views team details
// Given: A team exists
const teamId = 't3';
const memberId = 'd3';
const team = Team.create({ id: teamId, name: 'Team 3', tag: 'T3', description: 'Desc', ownerId: 'owner', leagues: [] });
await teamRepository.create(team);
// And: The driver is a regular member
await membershipRepository.saveMembership({
teamId,
driverId: memberId,
role: 'driver',
status: 'active',
joinedAt: new Date()
});
// When: GetTeamDetailsUseCase.execute() is called
const result = await getTeamDetailsUseCase.execute({ teamId, driverId: memberId });
// Then: The result should contain team information and membership but no management permissions
expect(result.isOk()).toBe(true);
const data = result.unwrap();
expect(data.team.id.toString()).toBe(teamId);
expect(data.membership?.role).toBe('driver');
expect(data.canManage).toBe(false);
});
});
describe('GetTeamDetailsUseCase - Error Handling', () => {
it('should throw error when team does not exist', async () => {
// Scenario: Non-existent team
// When: GetTeamDetailsUseCase.execute() is called with non-existent team ID
const result = await getTeamDetailsUseCase.execute({ teamId: 'nonexistent', driverId: 'any' });
// Then: Should return error
expect(result.isErr()).toBe(true);
const error = result.unwrapErr();
expect(error.code).toBe('TEAM_NOT_FOUND');
});
});
});

View File

@@ -1,98 +0,0 @@
/**
* Integration Test: Team Leaderboard Use Case Orchestration
*
* Tests the orchestration logic of team leaderboard-related Use Cases:
* - GetTeamsLeaderboardUseCase: Retrieves ranked list of teams with performance metrics
* - Validates that Use Cases correctly interact with their Ports (Repositories)
* - Uses In-Memory adapters for fast, deterministic testing
*
* Focus: Business logic orchestration, NOT UI rendering
*/
import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
import { InMemoryTeamRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryTeamRepository';
import { InMemoryTeamMembershipRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryTeamMembershipRepository';
import { GetTeamsLeaderboardUseCase } from '../../../core/racing/application/use-cases/GetTeamsLeaderboardUseCase';
import { Team } from '../../../core/racing/domain/entities/Team';
import { Logger } from '../../../core/shared/domain/Logger';
describe('Team Leaderboard Use Case Orchestration', () => {
let teamRepository: InMemoryTeamRepository;
let membershipRepository: InMemoryTeamMembershipRepository;
let getTeamsLeaderboardUseCase: GetTeamsLeaderboardUseCase;
let mockLogger: Logger;
beforeAll(() => {
mockLogger = {
info: () => {},
debug: () => {},
warn: () => {},
error: () => {},
} as unknown as Logger;
teamRepository = new InMemoryTeamRepository(mockLogger);
membershipRepository = new InMemoryTeamMembershipRepository(mockLogger);
// Mock driver stats provider
const getDriverStats = (driverId: string) => {
const statsMap: Record<string, { rating: number, wins: number, totalRaces: number }> = {
'd1': { rating: 2000, wins: 10, totalRaces: 50 },
'd2': { rating: 1500, wins: 5, totalRaces: 30 },
'd3': { rating: 1000, wins: 2, totalRaces: 20 },
};
return statsMap[driverId] || null;
};
getTeamsLeaderboardUseCase = new GetTeamsLeaderboardUseCase(
teamRepository,
membershipRepository,
getDriverStats,
mockLogger
);
});
beforeEach(() => {
teamRepository.clear();
membershipRepository.clear();
});
describe('GetTeamsLeaderboardUseCase - Success Path', () => {
it('should retrieve ranked team leaderboard with performance metrics', async () => {
// Scenario: Leaderboard with multiple teams
// Given: Multiple teams exist
const team1 = Team.create({ id: 't1', name: 'Pro Team', tag: 'PRO', description: 'Desc', ownerId: 'o1', leagues: [] });
const team2 = Team.create({ id: 't2', name: 'Am Team', tag: 'AM', description: 'Desc', ownerId: 'o2', leagues: [] });
await teamRepository.create(team1);
await teamRepository.create(team2);
// And: Teams have members with different stats
await membershipRepository.saveMembership({ teamId: 't1', driverId: 'd1', role: 'owner', status: 'active', joinedAt: new Date() });
await membershipRepository.saveMembership({ teamId: 't2', driverId: 'd3', role: 'owner', status: 'active', joinedAt: new Date() });
// When: GetTeamsLeaderboardUseCase.execute() is called
const result = await getTeamsLeaderboardUseCase.execute({ leagueId: 'any' });
// Then: The result should contain ranked teams
expect(result.isOk()).toBe(true);
const { items, topItems } = result.unwrap();
expect(items).toHaveLength(2);
// And: Teams should be ranked by rating (Pro Team has d1 with 2000, Am Team has d3 with 1000)
expect(topItems[0]?.team.id.toString()).toBe('t1');
expect(topItems[0]?.rating).toBe(2000);
expect(topItems[1]?.team.id.toString()).toBe('t2');
expect(topItems[1]?.rating).toBe(1000);
});
it('should handle empty leaderboard', async () => {
// Scenario: No teams exist
// When: GetTeamsLeaderboardUseCase.execute() is called
const result = await getTeamsLeaderboardUseCase.execute({ leagueId: 'any' });
// Then: The result should be empty
expect(result.isOk()).toBe(true);
const { items } = result.unwrap();
expect(items).toHaveLength(0);
});
});
});

View File

@@ -1,536 +0,0 @@
/**
* Integration Test: Team Membership Use Case Orchestration
*
* Tests the orchestration logic of team membership-related Use Cases:
* - JoinTeamUseCase: Allows driver to request to join a team
* - LeaveTeamUseCase: Allows driver to leave a team
* - GetTeamMembershipUseCase: Retrieves driver's membership in a team
* - GetTeamMembersUseCase: Retrieves all team members
* - GetTeamJoinRequestsUseCase: Retrieves pending join requests
* - ApproveTeamJoinRequestUseCase: Admin approves join request
* - Validates that Use Cases correctly interact with their Ports (Repositories)
* - Uses In-Memory adapters for fast, deterministic testing
*
* Focus: Business logic orchestration, NOT UI rendering
*/
import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
import { InMemoryTeamRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryTeamRepository';
import { InMemoryTeamMembershipRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryTeamMembershipRepository';
import { InMemoryDriverRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryDriverRepository';
import { JoinTeamUseCase } from '../../../core/racing/application/use-cases/JoinTeamUseCase';
import { LeaveTeamUseCase } from '../../../core/racing/application/use-cases/LeaveTeamUseCase';
import { GetTeamMembershipUseCase } from '../../../core/racing/application/use-cases/GetTeamMembershipUseCase';
import { GetTeamMembersUseCase } from '../../../core/racing/application/use-cases/GetTeamMembersUseCase';
import { GetTeamJoinRequestsUseCase } from '../../../core/racing/application/use-cases/GetTeamJoinRequestsUseCase';
import { ApproveTeamJoinRequestUseCase } from '../../../core/racing/application/use-cases/ApproveTeamJoinRequestUseCase';
import { Team } from '../../../core/racing/domain/entities/Team';
import { Driver } from '../../../core/racing/domain/entities/Driver';
import { Logger } from '../../../core/shared/domain/Logger';
describe('Team Membership Use Case Orchestration', () => {
let teamRepository: InMemoryTeamRepository;
let membershipRepository: InMemoryTeamMembershipRepository;
let driverRepository: InMemoryDriverRepository;
let joinTeamUseCase: JoinTeamUseCase;
let leaveTeamUseCase: LeaveTeamUseCase;
let getTeamMembershipUseCase: GetTeamMembershipUseCase;
let getTeamMembersUseCase: GetTeamMembersUseCase;
let getTeamJoinRequestsUseCase: GetTeamJoinRequestsUseCase;
let approveTeamJoinRequestUseCase: ApproveTeamJoinRequestUseCase;
let mockLogger: Logger;
beforeAll(() => {
mockLogger = {
info: () => {},
debug: () => {},
warn: () => {},
error: () => {},
} as unknown as Logger;
teamRepository = new InMemoryTeamRepository(mockLogger);
membershipRepository = new InMemoryTeamMembershipRepository(mockLogger);
driverRepository = new InMemoryDriverRepository(mockLogger);
joinTeamUseCase = new JoinTeamUseCase(teamRepository, membershipRepository, mockLogger);
leaveTeamUseCase = new LeaveTeamUseCase(teamRepository, membershipRepository, mockLogger);
getTeamMembershipUseCase = new GetTeamMembershipUseCase(membershipRepository, mockLogger);
getTeamMembersUseCase = new GetTeamMembersUseCase(membershipRepository, driverRepository, teamRepository, mockLogger);
getTeamJoinRequestsUseCase = new GetTeamJoinRequestsUseCase(membershipRepository, driverRepository, teamRepository);
approveTeamJoinRequestUseCase = new ApproveTeamJoinRequestUseCase(membershipRepository);
});
beforeEach(() => {
teamRepository.clear();
membershipRepository.clear();
driverRepository.clear();
});
describe('JoinTeamUseCase - Success Path', () => {
it('should create a join request for a team', async () => {
// Scenario: Driver requests to join team
// Given: A driver exists
const driverId = 'd1';
const driver = Driver.create({ id: driverId, iracingId: '1', name: 'Driver 1', country: 'US' });
await driverRepository.create(driver);
// And: A team exists
const teamId = 't1';
const team = Team.create({ id: teamId, name: 'Team 1', tag: 'T1', description: 'Test Team', ownerId: 'owner', leagues: [] });
await teamRepository.create(team);
// And: The team has available roster slots
// (Team has no members yet, so it has available slots)
// When: JoinTeamUseCase.execute() is called
const result = await joinTeamUseCase.execute({
teamId,
driverId
});
// Then: A join request should be created
expect(result.isOk()).toBe(true);
const { team: resultTeam, membership } = result.unwrap();
expect(resultTeam.id.toString()).toBe(teamId);
// And: The request should be in pending status
expect(membership.status).toBe('active');
expect(membership.role).toBe('driver');
// And: The membership should be in the repository
const savedMembership = await membershipRepository.getMembership(teamId, driverId);
expect(savedMembership).toBeDefined();
expect(savedMembership?.status).toBe('active');
});
it('should create a join request when team is not full', async () => {
// Scenario: Team has available slots
// Given: A driver exists
const driverId = 'd2';
const driver = Driver.create({ id: driverId, iracingId: '2', name: 'Driver 2', country: 'US' });
await driverRepository.create(driver);
// And: A team exists with available roster slots
const teamId = 't2';
const team = Team.create({ id: teamId, name: 'Team 2', tag: 'T2', description: 'Test Team', ownerId: 'owner', leagues: [] });
await teamRepository.create(team);
// When: JoinTeamUseCase.execute() is called
const result = await joinTeamUseCase.execute({
teamId,
driverId
});
// Then: A join request should be created
expect(result.isOk()).toBe(true);
const { membership } = result.unwrap();
expect(membership.status).toBe('active');
});
});
describe('JoinTeamUseCase - Validation', () => {
it('should reject join request when driver is already a member', async () => {
// Scenario: Driver already member
// Given: A driver exists
const driverId = 'd3';
const driver = Driver.create({ id: driverId, iracingId: '3', name: 'Driver 3', country: 'US' });
await driverRepository.create(driver);
// And: A team exists
const teamId = 't3';
const team = Team.create({ id: teamId, name: 'Team 3', tag: 'T3', description: 'Test Team', ownerId: 'owner', leagues: [] });
await teamRepository.create(team);
// And: The driver is already a member of the team
await membershipRepository.saveMembership({
teamId,
driverId,
role: 'driver',
status: 'active',
joinedAt: new Date()
});
// When: JoinTeamUseCase.execute() is called
const result = await joinTeamUseCase.execute({
teamId,
driverId
});
// Then: Should return error
expect(result.isErr()).toBe(true);
const error = result.unwrapErr();
expect(error.code).toBe('ALREADY_MEMBER');
});
it('should reject join request when driver already has pending request', async () => {
// Scenario: Driver has pending request
// Given: A driver exists
const driverId = 'd4';
const driver = Driver.create({ id: driverId, iracingId: '4', name: 'Driver 4', country: 'US' });
await driverRepository.create(driver);
// And: A team exists
const teamId = 't4';
const team = Team.create({ id: teamId, name: 'Team 4', tag: 'T4', description: 'Test Team', ownerId: 'owner', leagues: [] });
await teamRepository.create(team);
// And: The driver already has a pending join request for the team
await membershipRepository.saveJoinRequest({
id: 'jr1',
teamId,
driverId,
status: 'pending',
requestedAt: new Date()
});
// When: JoinTeamUseCase.execute() is called
const result = await joinTeamUseCase.execute({
teamId,
driverId
});
// Then: Should return error
expect(result.isErr()).toBe(true);
const error = result.unwrapErr();
expect(error.code).toBe('ALREADY_MEMBER');
});
});
describe('JoinTeamUseCase - Error Handling', () => {
it('should throw error when driver does not exist', async () => {
// Scenario: Non-existent driver
// Given: No driver exists with the given ID
const nonExistentDriverId = 'nonexistent';
// And: A team exists
const teamId = 't5';
const team = Team.create({ id: teamId, name: 'Team 5', tag: 'T5', description: 'Test Team', ownerId: 'owner', leagues: [] });
await teamRepository.create(team);
// When: JoinTeamUseCase.execute() is called with non-existent driver ID
const result = await joinTeamUseCase.execute({
teamId,
driverId: nonExistentDriverId
});
// Then: Should return error
expect(result.isErr()).toBe(true);
const error = result.unwrapErr();
expect(error.code).toBe('TEAM_NOT_FOUND');
});
it('should throw error when team does not exist', async () => {
// Scenario: Non-existent team
// Given: A driver exists
const driverId = 'd6';
const driver = Driver.create({ id: driverId, iracingId: '6', name: 'Driver 6', country: 'US' });
await driverRepository.create(driver);
// And: No team exists with the given ID
const nonExistentTeamId = 'nonexistent';
// When: JoinTeamUseCase.execute() is called with non-existent team ID
const result = await joinTeamUseCase.execute({
teamId: nonExistentTeamId,
driverId
});
// Then: Should return error
expect(result.isErr()).toBe(true);
const error = result.unwrapErr();
expect(error.code).toBe('TEAM_NOT_FOUND');
});
});
describe('LeaveTeamUseCase - Success Path', () => {
it('should allow driver to leave team', async () => {
// Scenario: Driver leaves team
// Given: A driver exists
const driverId = 'd7';
const driver = Driver.create({ id: driverId, iracingId: '7', name: 'Driver 7', country: 'US' });
await driverRepository.create(driver);
// And: A team exists
const teamId = 't7';
const team = Team.create({ id: teamId, name: 'Team 7', tag: 'T7', description: 'Test Team', ownerId: 'owner', leagues: [] });
await teamRepository.create(team);
// And: The driver is a member of the team
await membershipRepository.saveMembership({
teamId,
driverId,
role: 'driver',
status: 'active',
joinedAt: new Date()
});
// When: LeaveTeamUseCase.execute() is called
const result = await leaveTeamUseCase.execute({
teamId,
driverId
});
// Then: The driver should be removed from the team
expect(result.isOk()).toBe(true);
const { team: resultTeam, previousMembership } = result.unwrap();
expect(resultTeam.id.toString()).toBe(teamId);
expect(previousMembership.driverId).toBe(driverId);
// And: The membership should be removed from the repository
const savedMembership = await membershipRepository.getMembership(teamId, driverId);
expect(savedMembership).toBeNull();
});
});
describe('LeaveTeamUseCase - Validation', () => {
it('should reject leave when driver is not a member', async () => {
// Scenario: Driver not member
// Given: A driver exists
const driverId = 'd8';
const driver = Driver.create({ id: driverId, iracingId: '8', name: 'Driver 8', country: 'US' });
await driverRepository.create(driver);
// And: A team exists
const teamId = 't8';
const team = Team.create({ id: teamId, name: 'Team 8', tag: 'T8', description: 'Test Team', ownerId: 'owner', leagues: [] });
await teamRepository.create(team);
// When: LeaveTeamUseCase.execute() is called
const result = await leaveTeamUseCase.execute({
teamId,
driverId
});
// Then: Should return error
expect(result.isErr()).toBe(true);
const error = result.unwrapErr();
expect(error.code).toBe('NOT_MEMBER');
});
it('should reject leave when driver is team owner', async () => {
// Scenario: Team owner cannot leave
// Given: A driver exists
const driverId = 'd9';
const driver = Driver.create({ id: driverId, iracingId: '9', name: 'Driver 9', country: 'US' });
await driverRepository.create(driver);
// And: A team exists with the driver as owner
const teamId = 't9';
const team = Team.create({ id: teamId, name: 'Team 9', tag: 'T9', description: 'Test Team', ownerId: driverId, leagues: [] });
await teamRepository.create(team);
// And: The driver is the owner
await membershipRepository.saveMembership({
teamId,
driverId,
role: 'owner',
status: 'active',
joinedAt: new Date()
});
// When: LeaveTeamUseCase.execute() is called
const result = await leaveTeamUseCase.execute({
teamId,
driverId
});
// Then: Should return error
expect(result.isErr()).toBe(true);
const error = result.unwrapErr();
expect(error.code).toBe('OWNER_CANNOT_LEAVE');
});
});
describe('GetTeamMembershipUseCase - Success Path', () => {
it('should retrieve driver membership in team', async () => {
// Scenario: Retrieve membership
// Given: A driver exists
const driverId = 'd10';
const driver = Driver.create({ id: driverId, iracingId: '10', name: 'Driver 10', country: 'US' });
await driverRepository.create(driver);
// And: A team exists
const teamId = 't10';
const team = Team.create({ id: teamId, name: 'Team 10', tag: 'T10', description: 'Test Team', ownerId: 'owner', leagues: [] });
await teamRepository.create(team);
// And: The driver is a member of the team
await membershipRepository.saveMembership({
teamId,
driverId,
role: 'driver',
status: 'active',
joinedAt: new Date()
});
// When: GetTeamMembershipUseCase.execute() is called
const result = await getTeamMembershipUseCase.execute({
teamId,
driverId
});
// Then: It should return the membership
expect(result.isOk()).toBe(true);
const { membership } = result.unwrap();
expect(membership).toBeDefined();
expect(membership?.role).toBe('member');
expect(membership?.isActive).toBe(true);
});
it('should return null when driver is not a member', async () => {
// Scenario: No membership found
// Given: A driver exists
const driverId = 'd11';
const driver = Driver.create({ id: driverId, iracingId: '11', name: 'Driver 11', country: 'US' });
await driverRepository.create(driver);
// And: A team exists
const teamId = 't11';
const team = Team.create({ id: teamId, name: 'Team 11', tag: 'T11', description: 'Test Team', ownerId: 'owner', leagues: [] });
await teamRepository.create(team);
// When: GetTeamMembershipUseCase.execute() is called
const result = await getTeamMembershipUseCase.execute({
teamId,
driverId
});
// Then: It should return null
expect(result.isOk()).toBe(true);
const { membership } = result.unwrap();
expect(membership).toBeNull();
});
});
describe('GetTeamMembersUseCase - Success Path', () => {
it('should retrieve all team members', async () => {
// Scenario: Retrieve team members
// Given: A team exists
const teamId = 't12';
const team = Team.create({ id: teamId, name: 'Team 12', tag: 'T12', description: 'Test Team', ownerId: 'owner', leagues: [] });
await teamRepository.create(team);
// And: Multiple drivers exist
const driver1 = Driver.create({ id: 'd12', iracingId: '12', name: 'Driver 12', country: 'US' });
const driver2 = Driver.create({ id: 'd13', iracingId: '13', name: 'Driver 13', country: 'UK' });
await driverRepository.create(driver1);
await driverRepository.create(driver2);
// And: Drivers are members of the team
await membershipRepository.saveMembership({
teamId,
driverId: 'd12',
role: 'owner',
status: 'active',
joinedAt: new Date()
});
await membershipRepository.saveMembership({
teamId,
driverId: 'd13',
role: 'driver',
status: 'active',
joinedAt: new Date()
});
// When: GetTeamMembersUseCase.execute() is called
const result = await getTeamMembersUseCase.execute({
teamId
});
// Then: It should return all team members
expect(result.isOk()).toBe(true);
const { team: resultTeam, members } = result.unwrap();
expect(resultTeam.id.toString()).toBe(teamId);
expect(members).toHaveLength(2);
expect(members[0].membership.driverId).toBe('d12');
expect(members[1].membership.driverId).toBe('d13');
});
});
describe('GetTeamJoinRequestsUseCase - Success Path', () => {
it('should retrieve pending join requests', async () => {
// Scenario: Retrieve join requests
// Given: A team exists
const teamId = 't14';
const team = Team.create({ id: teamId, name: 'Team 14', tag: 'T14', description: 'Test Team', ownerId: 'owner', leagues: [] });
await teamRepository.create(team);
// And: Multiple drivers exist
const driver1 = Driver.create({ id: 'd14', iracingId: '14', name: 'Driver 14', country: 'US' });
const driver2 = Driver.create({ id: 'd15', iracingId: '15', name: 'Driver 15', country: 'UK' });
await driverRepository.create(driver1);
await driverRepository.create(driver2);
// And: Drivers have pending join requests
await membershipRepository.saveJoinRequest({
id: 'jr2',
teamId,
driverId: 'd14',
status: 'pending',
requestedAt: new Date()
});
await membershipRepository.saveJoinRequest({
id: 'jr3',
teamId,
driverId: 'd15',
status: 'pending',
requestedAt: new Date()
});
// When: GetTeamJoinRequestsUseCase.execute() is called
const result = await getTeamJoinRequestsUseCase.execute({
teamId
});
// Then: It should return the join requests
expect(result.isOk()).toBe(true);
const { team: resultTeam, joinRequests } = result.unwrap();
expect(resultTeam.id.toString()).toBe(teamId);
expect(joinRequests).toHaveLength(2);
expect(joinRequests[0].driverId).toBe('d14');
expect(joinRequests[1].driverId).toBe('d15');
});
});
describe('ApproveTeamJoinRequestUseCase - Success Path', () => {
it('should approve a pending join request', async () => {
// Scenario: Admin approves join request
// Given: A team exists
const teamId = 't16';
const team = Team.create({ id: teamId, name: 'Team 16', tag: 'T16', description: 'Test Team', ownerId: 'owner', leagues: [] });
await teamRepository.create(team);
// And: A driver exists
const driverId = 'd16';
const driver = Driver.create({ id: driverId, iracingId: '16', name: 'Driver 16', country: 'US' });
await driverRepository.create(driver);
// And: A driver has a pending join request for the team
await membershipRepository.saveJoinRequest({
id: 'jr4',
teamId,
driverId,
status: 'pending',
requestedAt: new Date()
});
// When: ApproveTeamJoinRequestUseCase.execute() is called
const result = await approveTeamJoinRequestUseCase.execute({
teamId,
requestId: 'jr4'
});
// Then: The join request should be approved
expect(result.isOk()).toBe(true);
const { membership } = result.unwrap();
expect(membership.driverId).toBe(driverId);
expect(membership.teamId).toBe(teamId);
expect(membership.status).toBe('active');
// And: The driver should be added to the team roster
const savedMembership = await membershipRepository.getMembership(teamId, driverId);
expect(savedMembership).toBeDefined();
expect(savedMembership?.status).toBe('active');
});
});
});

View File

@@ -1,105 +0,0 @@
/**
* Integration Test: Teams List Use Case Orchestration
*
* Tests the orchestration logic of teams list-related Use Cases:
* - GetAllTeamsUseCase: Retrieves list of teams with enrichment (member count, stats)
* - Validates that Use Cases correctly interact with their Ports (Repositories)
* - Uses In-Memory adapters for fast, deterministic testing
*
* Focus: Business logic orchestration, NOT UI rendering
*/
import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
import { InMemoryTeamRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryTeamRepository';
import { InMemoryTeamMembershipRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryTeamMembershipRepository';
import { InMemoryTeamStatsRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryTeamStatsRepository';
import { GetAllTeamsUseCase } from '../../../core/racing/application/use-cases/GetAllTeamsUseCase';
import { Team } from '../../../core/racing/domain/entities/Team';
import { Logger } from '../../../core/shared/domain/Logger';
describe('Teams List Use Case Orchestration', () => {
let teamRepository: InMemoryTeamRepository;
let membershipRepository: InMemoryTeamMembershipRepository;
let statsRepository: InMemoryTeamStatsRepository;
let getAllTeamsUseCase: GetAllTeamsUseCase;
let mockLogger: Logger;
beforeAll(() => {
mockLogger = {
info: () => {},
debug: () => {},
warn: () => {},
error: () => {},
} as unknown as Logger;
teamRepository = new InMemoryTeamRepository(mockLogger);
membershipRepository = new InMemoryTeamMembershipRepository(mockLogger);
statsRepository = new InMemoryTeamStatsRepository();
getAllTeamsUseCase = new GetAllTeamsUseCase(teamRepository, membershipRepository, statsRepository, mockLogger);
});
beforeEach(() => {
teamRepository.clear();
membershipRepository.clear();
statsRepository.clear();
});
describe('GetAllTeamsUseCase - Success Path', () => {
it('should retrieve complete teams list with all teams and enrichment', async () => {
// Scenario: Teams list with multiple teams
// Given: Multiple teams exist
const team1 = Team.create({ id: 't1', name: 'Team 1', tag: 'T1', description: 'Desc 1', ownerId: 'o1', leagues: [] });
const team2 = Team.create({ id: 't2', name: 'Team 2', tag: 'T2', description: 'Desc 2', ownerId: 'o2', leagues: [] });
await teamRepository.create(team1);
await teamRepository.create(team2);
// And: Teams have members
await membershipRepository.saveMembership({ teamId: 't1', driverId: 'd1', role: 'owner', status: 'active', joinedAt: new Date() });
await membershipRepository.saveMembership({ teamId: 't1', driverId: 'd2', role: 'driver', status: 'active', joinedAt: new Date() });
await membershipRepository.saveMembership({ teamId: 't2', driverId: 'd3', role: 'owner', status: 'active', joinedAt: new Date() });
// And: Teams have stats
await statsRepository.saveTeamStats('t1', {
totalWins: 5,
totalRaces: 20,
rating: 1500,
performanceLevel: 'intermediate',
specialization: 'sprint',
region: 'EU',
languages: ['en'],
isRecruiting: true
});
// When: GetAllTeamsUseCase.execute() is called
const result = await getAllTeamsUseCase.execute({});
// Then: The result should contain all teams with enrichment
expect(result.isOk()).toBe(true);
const { teams, totalCount } = result.unwrap();
expect(totalCount).toBe(2);
const enriched1 = teams.find(t => t.team.id.toString() === 't1');
expect(enriched1).toBeDefined();
expect(enriched1?.memberCount).toBe(2);
expect(enriched1?.totalWins).toBe(5);
expect(enriched1?.rating).toBe(1500);
const enriched2 = teams.find(t => t.team.id.toString() === 't2');
expect(enriched2).toBeDefined();
expect(enriched2?.memberCount).toBe(1);
expect(enriched2?.totalWins).toBe(0); // Default value
});
it('should handle empty teams list', async () => {
// Scenario: No teams exist
// When: GetAllTeamsUseCase.execute() is called
const result = await getAllTeamsUseCase.execute({});
// Then: The result should be empty
expect(result.isOk()).toBe(true);
const { teams, totalCount } = result.unwrap();
expect(totalCount).toBe(0);
expect(teams).toHaveLength(0);
});
});
});