Files
gridpilot.gg/tests/integration/teams/team-creation-use-cases.integration.test.ts
Marc Mintel 2fba80da57
Some checks failed
Contract Testing / contract-tests (pull_request) Failing after 4m46s
Contract Testing / contract-snapshot (pull_request) Has been skipped
integration tests
2026-01-22 19:16:43 +01:00

404 lines
16 KiB
TypeScript

/**
* 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());
});
});
});