seed data
This commit is contained in:
@@ -40,13 +40,13 @@ export const AuthProviders: Provider[] = [
|
||||
useFactory: (passwordHashingService: IPasswordHashingService, logger: Logger) => {
|
||||
// Seed initial users for InMemoryUserRepository
|
||||
const initialUsers: StoredUser[] = [
|
||||
// Example user (replace with actual test users as needed)
|
||||
{
|
||||
id: 'user-1',
|
||||
email: 'test@example.com',
|
||||
passwordHash: 'demo_salt_moc.elpmaxe@tset', // "test@example.com" reversed
|
||||
displayName: 'Test User',
|
||||
salt: '', // Handled by hashing service
|
||||
// Match seeded racing driver id so dashboard works in inmemory mode.
|
||||
id: 'driver-1',
|
||||
email: 'admin@gridpilot.local',
|
||||
passwordHash: 'demo_salt_321nimda', // InMemoryPasswordHashingService: "admin123" reversed.
|
||||
displayName: 'Admin',
|
||||
salt: '',
|
||||
createdAt: new Date(),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Provider } from '@nestjs/common';
|
||||
import { EnsureInitialData } from '../../../../../adapters/bootstrap/EnsureInitialData';
|
||||
import { EnsureInitialData, type InMemorySeedDependencies } from '../../../../../adapters/bootstrap/EnsureInitialData';
|
||||
import { SignupWithEmailUseCase, type SignupWithEmailResult } from '@core/identity/application/use-cases/SignupWithEmailUseCase';
|
||||
import {
|
||||
CreateAchievementUseCase,
|
||||
@@ -84,12 +84,52 @@ export const BootstrapProviders: Provider[] = [
|
||||
{
|
||||
provide: EnsureInitialData,
|
||||
useFactory: (
|
||||
signupUseCase: SignupWithEmailUseCase,
|
||||
createAchievementUseCase: CreateAchievementUseCase,
|
||||
logger: Logger
|
||||
signupUseCase: SignupWithEmailUseCase,
|
||||
createAchievementUseCase: CreateAchievementUseCase,
|
||||
logger: Logger,
|
||||
driverRepository: InMemorySeedDependencies['driverRepository'],
|
||||
leagueRepository: InMemorySeedDependencies['leagueRepository'],
|
||||
raceRepository: InMemorySeedDependencies['raceRepository'],
|
||||
resultRepository: InMemorySeedDependencies['resultRepository'],
|
||||
standingRepository: InMemorySeedDependencies['standingRepository'],
|
||||
leagueMembershipRepository: InMemorySeedDependencies['leagueMembershipRepository'],
|
||||
raceRegistrationRepository: InMemorySeedDependencies['raceRegistrationRepository'],
|
||||
teamRepository: InMemorySeedDependencies['teamRepository'],
|
||||
teamMembershipRepository: InMemorySeedDependencies['teamMembershipRepository'],
|
||||
feedRepository: InMemorySeedDependencies['feedRepository'],
|
||||
socialGraphRepository: InMemorySeedDependencies['socialGraphRepository'],
|
||||
) => {
|
||||
return new EnsureInitialData(signupUseCase, createAchievementUseCase, logger);
|
||||
const deps: InMemorySeedDependencies = {
|
||||
driverRepository,
|
||||
leagueRepository,
|
||||
raceRepository,
|
||||
resultRepository,
|
||||
standingRepository,
|
||||
leagueMembershipRepository,
|
||||
raceRegistrationRepository,
|
||||
teamRepository,
|
||||
teamMembershipRepository,
|
||||
feedRepository,
|
||||
socialGraphRepository,
|
||||
};
|
||||
|
||||
return new EnsureInitialData(signupUseCase, createAchievementUseCase, logger, deps);
|
||||
},
|
||||
inject: [SIGNUP_USE_CASE_TOKEN, CREATE_ACHIEVEMENT_USE_CASE_TOKEN, 'Logger'],
|
||||
inject: [
|
||||
SIGNUP_USE_CASE_TOKEN,
|
||||
CREATE_ACHIEVEMENT_USE_CASE_TOKEN,
|
||||
'Logger',
|
||||
'IDriverRepository',
|
||||
'ILeagueRepository',
|
||||
'IRaceRepository',
|
||||
'IResultRepository',
|
||||
'IStandingRepository',
|
||||
'ILeagueMembershipRepository',
|
||||
'IRaceRegistrationRepository',
|
||||
'ITeamRepository',
|
||||
'ITeamMembershipRepository',
|
||||
'IFeedRepository',
|
||||
'ISocialGraphRepository',
|
||||
],
|
||||
},
|
||||
];
|
||||
129
apps/api/src/domain/persistence/InMemoryPersistenceModule.ts
Normal file
129
apps/api/src/domain/persistence/InMemoryPersistenceModule.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
|
||||
import type { Logger } from '@core/shared/application/Logger';
|
||||
|
||||
import type { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepository';
|
||||
import type { ILeagueRepository } from '@core/racing/domain/repositories/ILeagueRepository';
|
||||
import type { IRaceRepository } from '@core/racing/domain/repositories/IRaceRepository';
|
||||
import type { IResultRepository } from '@core/racing/domain/repositories/IResultRepository';
|
||||
import type { IStandingRepository } from '@core/racing/domain/repositories/IStandingRepository';
|
||||
import type { ILeagueMembershipRepository } from '@core/racing/domain/repositories/ILeagueMembershipRepository';
|
||||
import type { IRaceRegistrationRepository } from '@core/racing/domain/repositories/IRaceRegistrationRepository';
|
||||
import type { ITeamRepository } from '@core/racing/domain/repositories/ITeamRepository';
|
||||
import type { ITeamMembershipRepository } from '@core/racing/domain/repositories/ITeamMembershipRepository';
|
||||
|
||||
import type { IFeedRepository } from '@core/social/domain/repositories/IFeedRepository';
|
||||
import type { ISocialGraphRepository } from '@core/social/domain/repositories/ISocialGraphRepository';
|
||||
|
||||
import { getPointsSystems } from '@adapters/bootstrap/PointsSystems';
|
||||
|
||||
import { InMemoryDriverRepository } from '@adapters/racing/persistence/inmemory/InMemoryDriverRepository';
|
||||
import { InMemoryLeagueRepository } from '@adapters/racing/persistence/inmemory/InMemoryLeagueRepository';
|
||||
import { InMemoryRaceRepository } from '@adapters/racing/persistence/inmemory/InMemoryRaceRepository';
|
||||
import { InMemoryResultRepository } from '@adapters/racing/persistence/inmemory/InMemoryResultRepository';
|
||||
import { InMemoryStandingRepository } from '@adapters/racing/persistence/inmemory/InMemoryStandingRepository';
|
||||
import { InMemoryLeagueMembershipRepository } from '@adapters/racing/persistence/inmemory/InMemoryLeagueMembershipRepository';
|
||||
import { InMemoryRaceRegistrationRepository } from '@adapters/racing/persistence/inmemory/InMemoryRaceRegistrationRepository';
|
||||
import { InMemoryTeamRepository } from '@adapters/racing/persistence/inmemory/InMemoryTeamRepository';
|
||||
import { InMemoryTeamMembershipRepository } from '@adapters/racing/persistence/inmemory/InMemoryTeamMembershipRepository';
|
||||
|
||||
import {
|
||||
InMemoryFeedRepository,
|
||||
InMemorySocialGraphRepository,
|
||||
} from '@adapters/social/persistence/inmemory/InMemorySocialAndFeed';
|
||||
|
||||
export const DRIVER_REPOSITORY_TOKEN = 'IDriverRepository';
|
||||
export const LEAGUE_REPOSITORY_TOKEN = 'ILeagueRepository';
|
||||
export const RACE_REPOSITORY_TOKEN = 'IRaceRepository';
|
||||
export const RESULT_REPOSITORY_TOKEN = 'IResultRepository';
|
||||
export const STANDING_REPOSITORY_TOKEN = 'IStandingRepository';
|
||||
export const LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN = 'ILeagueMembershipRepository';
|
||||
export const RACE_REGISTRATION_REPOSITORY_TOKEN = 'IRaceRegistrationRepository';
|
||||
export const TEAM_REPOSITORY_TOKEN = 'ITeamRepository';
|
||||
export const TEAM_MEMBERSHIP_REPOSITORY_TOKEN = 'ITeamMembershipRepository';
|
||||
|
||||
export const FEED_REPOSITORY_TOKEN = 'IFeedRepository';
|
||||
export const SOCIAL_GRAPH_REPOSITORY_TOKEN = 'ISocialGraphRepository';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [
|
||||
{
|
||||
provide: DRIVER_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger): IDriverRepository => new InMemoryDriverRepository(logger),
|
||||
inject: ['Logger'],
|
||||
},
|
||||
{
|
||||
provide: LEAGUE_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger): ILeagueRepository => new InMemoryLeagueRepository(logger),
|
||||
inject: ['Logger'],
|
||||
},
|
||||
{
|
||||
provide: RACE_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger): IRaceRepository => new InMemoryRaceRepository(logger),
|
||||
inject: ['Logger'],
|
||||
},
|
||||
{
|
||||
provide: RESULT_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger, raceRepo: IRaceRepository): IResultRepository =>
|
||||
new InMemoryResultRepository(logger, raceRepo),
|
||||
inject: ['Logger', RACE_REPOSITORY_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: STANDING_REPOSITORY_TOKEN,
|
||||
useFactory: (
|
||||
logger: Logger,
|
||||
resultRepo: IResultRepository,
|
||||
raceRepo: IRaceRepository,
|
||||
leagueRepo: ILeagueRepository,
|
||||
): IStandingRepository => new InMemoryStandingRepository(logger, getPointsSystems(), resultRepo, raceRepo, leagueRepo),
|
||||
inject: ['Logger', RESULT_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger): ILeagueMembershipRepository => new InMemoryLeagueMembershipRepository(logger),
|
||||
inject: ['Logger'],
|
||||
},
|
||||
{
|
||||
provide: RACE_REGISTRATION_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger): IRaceRegistrationRepository => new InMemoryRaceRegistrationRepository(logger),
|
||||
inject: ['Logger'],
|
||||
},
|
||||
{
|
||||
provide: TEAM_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger): ITeamRepository => new InMemoryTeamRepository(logger),
|
||||
inject: ['Logger'],
|
||||
},
|
||||
{
|
||||
provide: TEAM_MEMBERSHIP_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger): ITeamMembershipRepository => new InMemoryTeamMembershipRepository(logger),
|
||||
inject: ['Logger'],
|
||||
},
|
||||
{
|
||||
provide: FEED_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger): IFeedRepository =>
|
||||
new InMemoryFeedRepository(logger, { drivers: [], friendships: [], feedEvents: [] }),
|
||||
inject: ['Logger'],
|
||||
},
|
||||
{
|
||||
provide: SOCIAL_GRAPH_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger): ISocialGraphRepository =>
|
||||
new InMemorySocialGraphRepository(logger, { drivers: [], friendships: [], feedEvents: [] }),
|
||||
inject: ['Logger'],
|
||||
},
|
||||
],
|
||||
exports: [
|
||||
DRIVER_REPOSITORY_TOKEN,
|
||||
LEAGUE_REPOSITORY_TOKEN,
|
||||
RACE_REPOSITORY_TOKEN,
|
||||
RESULT_REPOSITORY_TOKEN,
|
||||
STANDING_REPOSITORY_TOKEN,
|
||||
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
|
||||
RACE_REGISTRATION_REPOSITORY_TOKEN,
|
||||
TEAM_REPOSITORY_TOKEN,
|
||||
TEAM_MEMBERSHIP_REPOSITORY_TOKEN,
|
||||
FEED_REPOSITORY_TOKEN,
|
||||
SOCIAL_GRAPH_REPOSITORY_TOKEN,
|
||||
],
|
||||
})
|
||||
export class InMemoryPersistenceModule {}
|
||||
@@ -1,59 +0,0 @@
|
||||
import React from 'react';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
vi.mock('@/lib/services/ServiceProvider', () => ({
|
||||
useServices: () => ({
|
||||
mediaService: {
|
||||
getLeagueLogo: () => '/logo.png',
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@/components/leagues/MembershipStatus', () => ({
|
||||
__esModule: true,
|
||||
default: () => <div data-testid="membership-status" />,
|
||||
}));
|
||||
|
||||
vi.mock('next/image', () => ({
|
||||
__esModule: true,
|
||||
default: (props: any) => <img {...props} />,
|
||||
}));
|
||||
|
||||
import LeagueHeader from './LeagueHeader';
|
||||
|
||||
describe('LeagueHeader', () => {
|
||||
it('renders league name, description and sponsor', () => {
|
||||
render(
|
||||
<LeagueHeader
|
||||
leagueId="league-1"
|
||||
leagueName="Test League"
|
||||
description="A fun test league"
|
||||
ownerId="owner-1"
|
||||
ownerName="Owner Name"
|
||||
mainSponsor={{
|
||||
name: 'Test Sponsor',
|
||||
websiteUrl: 'https://example.com',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Test League')).toBeInTheDocument();
|
||||
expect(screen.getByText('A fun test league')).toBeInTheDocument();
|
||||
expect(screen.getByText('by')).toBeInTheDocument();
|
||||
expect(screen.getByText('Test Sponsor')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders without description or sponsor', () => {
|
||||
render(
|
||||
<LeagueHeader
|
||||
leagueId="league-2"
|
||||
leagueName="League Without Details"
|
||||
ownerId="owner-2"
|
||||
ownerName="Owner 2"
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText('League Without Details')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,131 +0,0 @@
|
||||
import React from 'react';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
|
||||
import LeagueMembers from './LeagueMembers';
|
||||
import type { DriverDTO } from '@/lib/types/generated/DriverDTO';
|
||||
|
||||
// Stub global driver stats helper used by LeagueMembers sorting/rendering
|
||||
(globalThis as any).getDriverStats = (driverId: string) => ({
|
||||
driverId,
|
||||
rating: driverId === 'driver-1' ? 2500 : 2000,
|
||||
overallRank: driverId === 'driver-1' ? 1 : 2,
|
||||
wins: driverId === 'driver-1' ? 10 : 5,
|
||||
});
|
||||
|
||||
// Mock effective driver id so we can assert the "(You)" label
|
||||
vi.mock('@/hooks/useEffectiveDriverId', () => {
|
||||
return {
|
||||
useEffectiveDriverId: () => 'driver-1',
|
||||
};
|
||||
});
|
||||
|
||||
// Mock services hook to inject stub leagueMembershipService and driverService
|
||||
const mockFetchLeagueMemberships = vi.fn<(leagueId: string) => Promise<void>>();
|
||||
const mockGetLeagueMembers = vi.fn<(leagueId: string) => any[]>();
|
||||
const mockFindByIds = vi.fn<(ids: string[]) => Promise<DriverDTO[]>>();
|
||||
|
||||
const mockServices = {
|
||||
leagueMembershipService: {
|
||||
fetchLeagueMemberships: mockFetchLeagueMemberships,
|
||||
getLeagueMembers: mockGetLeagueMembers,
|
||||
},
|
||||
driverService: {
|
||||
findByIds: mockFindByIds,
|
||||
},
|
||||
};
|
||||
|
||||
vi.mock('@/lib/services/ServiceProvider', () => ({
|
||||
useServices: () => mockServices,
|
||||
}));
|
||||
|
||||
describe('LeagueMembers', () => {
|
||||
beforeEach(() => {
|
||||
mockFetchLeagueMemberships.mockReset();
|
||||
mockGetLeagueMembers.mockReset();
|
||||
mockFindByIds.mockReset();
|
||||
});
|
||||
|
||||
it('loads memberships via services and renders driver rows', async () => {
|
||||
const leagueId = 'league-1';
|
||||
|
||||
const memberships = [
|
||||
{
|
||||
id: 'm1',
|
||||
leagueId,
|
||||
driverId: 'driver-1',
|
||||
role: 'owner',
|
||||
status: 'active',
|
||||
joinedAt: '2024-01-01T00:00:00.000Z',
|
||||
},
|
||||
{
|
||||
id: 'm2',
|
||||
leagueId,
|
||||
driverId: 'driver-2',
|
||||
role: 'member',
|
||||
status: 'active',
|
||||
joinedAt: '2024-01-02T00:00:00.000Z',
|
||||
},
|
||||
];
|
||||
|
||||
const drivers: DriverDTO[] = [
|
||||
{
|
||||
id: 'driver-1',
|
||||
iracingId: 'ir-1',
|
||||
name: 'Driver One',
|
||||
country: 'DE',
|
||||
joinedAt: '2024-01-01T00:00:00.000Z',
|
||||
},
|
||||
{
|
||||
id: 'driver-2',
|
||||
iracingId: 'ir-2',
|
||||
name: 'Driver Two',
|
||||
country: 'US',
|
||||
joinedAt: '2024-01-01T00:00:00.000Z',
|
||||
},
|
||||
];
|
||||
|
||||
mockFetchLeagueMemberships.mockResolvedValue(undefined);
|
||||
mockGetLeagueMembers.mockReturnValue(memberships);
|
||||
mockFindByIds.mockResolvedValue(drivers);
|
||||
|
||||
render(<LeagueMembers leagueId={leagueId} showActions={false} />);
|
||||
|
||||
// Loading state first
|
||||
expect(screen.getByText('Loading members...')).toBeInTheDocument();
|
||||
|
||||
// Wait for data to be rendered
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('Loading members...')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Services should have been called with expected arguments
|
||||
expect(mockFetchLeagueMemberships).toHaveBeenCalledWith(leagueId);
|
||||
expect(mockGetLeagueMembers).toHaveBeenCalledWith(leagueId);
|
||||
expect(mockFindByIds).toHaveBeenCalledTimes(1);
|
||||
expect(mockFindByIds).toHaveBeenCalledWith(['driver-1', 'driver-2']);
|
||||
|
||||
// Driver rows should be rendered using DTO names
|
||||
expect(screen.getByText('Driver One')).toBeInTheDocument();
|
||||
expect(screen.getByText('Driver Two')).toBeInTheDocument();
|
||||
|
||||
// Current user marker should appear for effective driver id
|
||||
expect(screen.getByText('(You)')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('handles empty membership list gracefully', async () => {
|
||||
const leagueId = 'league-empty';
|
||||
|
||||
mockFetchLeagueMemberships.mockResolvedValue(undefined);
|
||||
mockGetLeagueMembers.mockReturnValue([]);
|
||||
mockFindByIds.mockResolvedValue([]);
|
||||
|
||||
render(<LeagueMembers leagueId={leagueId} showActions={false} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('Loading members...')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(screen.getByText('No members found')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user