wip
This commit is contained in:
@@ -24,7 +24,11 @@ describe('OverlaySyncService (unit)', () => {
|
||||
test('startAction resolves as confirmed only after action-started event is emitted', async () => {
|
||||
const emitter = new MockLifecycleEmitter()
|
||||
// create service wiring: pass emitter as dependency (constructor shape expected)
|
||||
const svc = new OverlaySyncService({ lifecycleEmitter: emitter as any, logger: console as any, publisher: { publish: async () => {} } as any })
|
||||
const svc = new OverlaySyncService({
|
||||
lifecycleEmitter: emitter,
|
||||
logger: console,
|
||||
publisher: { publish: async () => {} },
|
||||
})
|
||||
|
||||
const action: OverlayAction = { id: 'add-car', label: 'Adding...' }
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ class MockLifecycleEmitter implements IAutomationLifecycleEmitter {
|
||||
offLifecycle(cb: LifecycleCallback): void {
|
||||
this.callbacks.delete(cb)
|
||||
}
|
||||
async emit(event: any) {
|
||||
async emit(event: { type: string; actionId: string; timestamp: number }) {
|
||||
for (const cb of Array.from(this.callbacks)) {
|
||||
cb(event)
|
||||
}
|
||||
@@ -21,7 +21,11 @@ class MockLifecycleEmitter implements IAutomationLifecycleEmitter {
|
||||
describe('OverlaySyncService timeout (unit)', () => {
|
||||
test('startAction with short timeout resolves as tentative when no events', async () => {
|
||||
const emitter = new MockLifecycleEmitter()
|
||||
const svc = new OverlaySyncService({ lifecycleEmitter: emitter as any, logger: console as any, publisher: { publish: async () => {} } as any })
|
||||
const svc = new OverlaySyncService({
|
||||
lifecycleEmitter: emitter,
|
||||
logger: console,
|
||||
publisher: { publish: async () => {} },
|
||||
})
|
||||
|
||||
const action: OverlayAction = { id: 'add-car', label: 'Adding...', timeoutMs: 50 }
|
||||
|
||||
|
||||
@@ -233,13 +233,13 @@ describe('CheckAuthenticationUseCase', () => {
|
||||
mockAuthService.getSessionExpiry.mockResolvedValue(
|
||||
Result.ok(new Date(Date.now() + 3600000))
|
||||
);
|
||||
(mockAuthService as any).verifyPageAuthentication = vi.fn().mockResolvedValue(
|
||||
mockAuthService.verifyPageAuthentication = vi.fn().mockResolvedValue(
|
||||
Result.ok(new BrowserAuthenticationState(true, true))
|
||||
);
|
||||
|
||||
await useCase.execute({ verifyPageContent: true });
|
||||
|
||||
expect((mockAuthService as any).verifyPageAuthentication).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(mockAuthService.verifyPageAuthentication).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should return EXPIRED when cookies valid but page shows login UI', async () => {
|
||||
@@ -253,7 +253,7 @@ describe('CheckAuthenticationUseCase', () => {
|
||||
mockAuthService.getSessionExpiry.mockResolvedValue(
|
||||
Result.ok(new Date(Date.now() + 3600000))
|
||||
);
|
||||
(mockAuthService as any).verifyPageAuthentication = vi.fn().mockResolvedValue(
|
||||
mockAuthService.verifyPageAuthentication = vi.fn().mockResolvedValue(
|
||||
Result.ok(new BrowserAuthenticationState(true, false))
|
||||
);
|
||||
|
||||
@@ -274,7 +274,7 @@ describe('CheckAuthenticationUseCase', () => {
|
||||
mockAuthService.getSessionExpiry.mockResolvedValue(
|
||||
Result.ok(new Date(Date.now() + 3600000))
|
||||
);
|
||||
(mockAuthService as any).verifyPageAuthentication = vi.fn().mockResolvedValue(
|
||||
mockAuthService.verifyPageAuthentication = vi.fn().mockResolvedValue(
|
||||
Result.ok(new BrowserAuthenticationState(true, true))
|
||||
);
|
||||
|
||||
@@ -295,11 +295,11 @@ describe('CheckAuthenticationUseCase', () => {
|
||||
mockAuthService.getSessionExpiry.mockResolvedValue(
|
||||
Result.ok(new Date(Date.now() + 3600000))
|
||||
);
|
||||
(mockAuthService as any).verifyPageAuthentication = vi.fn();
|
||||
|
||||
mockAuthService.verifyPageAuthentication = vi.fn();
|
||||
|
||||
await useCase.execute();
|
||||
|
||||
expect((mockAuthService as any).verifyPageAuthentication).not.toHaveBeenCalled();
|
||||
|
||||
expect(mockAuthService.verifyPageAuthentication).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle verifyPageAuthentication errors gracefully', async () => {
|
||||
@@ -313,7 +313,7 @@ describe('CheckAuthenticationUseCase', () => {
|
||||
mockAuthService.getSessionExpiry.mockResolvedValue(
|
||||
Result.ok(new Date(Date.now() + 3600000))
|
||||
);
|
||||
(mockAuthService as any).verifyPageAuthentication = vi.fn().mockResolvedValue(
|
||||
mockAuthService.verifyPageAuthentication = vi.fn().mockResolvedValue(
|
||||
Result.err('Page navigation failed')
|
||||
);
|
||||
|
||||
@@ -388,7 +388,7 @@ describe('CheckAuthenticationUseCase', () => {
|
||||
mockAuthService.getSessionExpiry.mockResolvedValue(
|
||||
Result.ok(new Date(Date.now() + 3600000))
|
||||
);
|
||||
(mockAuthService as any).verifyPageAuthentication = vi.fn().mockResolvedValue(
|
||||
mockAuthService.verifyPageAuthentication = vi.fn().mockResolvedValue(
|
||||
Result.ok(new BrowserAuthenticationState(true, false))
|
||||
);
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ describe('CompleteRaceCreationUseCase', () => {
|
||||
const state = CheckoutState.ready();
|
||||
|
||||
vi.mocked(mockCheckoutService.extractCheckoutInfo).mockResolvedValue(
|
||||
Result.ok({ price: undefined as any, state, buttonHtml: '<a>n/a</a>' })
|
||||
Result.ok({ price: undefined, state, buttonHtml: '<a>n/a</a>' })
|
||||
);
|
||||
|
||||
const result = await useCase.execute('test-session-123');
|
||||
|
||||
@@ -19,7 +19,9 @@ describe('CheckoutConfirmation Value Object', () => {
|
||||
});
|
||||
|
||||
it('should throw error for invalid decision', () => {
|
||||
expect(() => CheckoutConfirmation.create('invalid' as any)).toThrow('Invalid checkout confirmation decision');
|
||||
expect(() => CheckoutConfirmation.create('invalid')).toThrow(
|
||||
'Invalid checkout confirmation decision',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -159,7 +159,8 @@ describe('CheckoutPrice Value Object', () => {
|
||||
const amount = price.getAmount();
|
||||
expect(amount).toBe(5.00);
|
||||
// Verify no setters exist
|
||||
expect(typeof (price as any).setAmount).toBe('undefined');
|
||||
const mutablePrice = price as unknown as { setAmount?: unknown };
|
||||
expect(typeof mutablePrice.setAmount).toBe('undefined');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -93,7 +93,8 @@ describe('CheckoutState Value Object', () => {
|
||||
const originalState = state.getValue();
|
||||
expect(originalState).toBe(CheckoutStateEnum.READY);
|
||||
// Verify no setters exist
|
||||
expect(typeof (state as any).setState).toBe('undefined');
|
||||
const mutableState = state as unknown as { setState?: unknown };
|
||||
expect(typeof mutableState.setState).toBe('undefined');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -44,11 +44,11 @@ describe('SessionState Value Object', () => {
|
||||
});
|
||||
|
||||
it('should throw error for invalid state', () => {
|
||||
expect(() => SessionState.create('INVALID' as any)).toThrow('Invalid session state');
|
||||
expect(() => SessionState.create('INVALID')).toThrow('Invalid session state');
|
||||
});
|
||||
|
||||
|
||||
it('should throw error for empty string', () => {
|
||||
expect(() => SessionState.create('' as any)).toThrow('Invalid session state');
|
||||
expect(() => SessionState.create('')).toThrow('Invalid session state');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ describe('AuthenticationGuard', () => {
|
||||
isVisible: vi.fn().mockResolvedValue(true),
|
||||
};
|
||||
|
||||
vi.mocked(mockPage.locator).mockReturnValue(mockLocator as any);
|
||||
vi.mocked(mockPage.locator).mockReturnValue(mockLocator as unknown as Parameters<Page['locator']>[0] extends string ? ReturnType<Page['locator']> : never);
|
||||
|
||||
const result = await guard.checkForLoginUI();
|
||||
|
||||
@@ -41,8 +41,8 @@ describe('AuthenticationGuard', () => {
|
||||
};
|
||||
|
||||
vi.mocked(mockPage.locator)
|
||||
.mockReturnValueOnce(mockNotLoggedInLocator as any)
|
||||
.mockReturnValueOnce(mockLoginButtonLocator as any);
|
||||
.mockReturnValueOnce(mockNotLoggedInLocator as unknown as ReturnType<Page['locator']>)
|
||||
.mockReturnValueOnce(mockLoginButtonLocator as unknown as ReturnType<Page['locator']>);
|
||||
|
||||
const result = await guard.checkForLoginUI();
|
||||
|
||||
@@ -66,9 +66,9 @@ describe('AuthenticationGuard', () => {
|
||||
};
|
||||
|
||||
vi.mocked(mockPage.locator)
|
||||
.mockReturnValueOnce(mockNotLoggedInLocator as any)
|
||||
.mockReturnValueOnce(mockLoginButtonLocator as any)
|
||||
.mockReturnValueOnce(mockAriaLabelLocator as any);
|
||||
.mockReturnValueOnce(mockNotLoggedInLocator as unknown as ReturnType<Page['locator']>)
|
||||
.mockReturnValueOnce(mockLoginButtonLocator as unknown as ReturnType<Page['locator']>)
|
||||
.mockReturnValueOnce(mockAriaLabelLocator as unknown as ReturnType<Page['locator']>);
|
||||
|
||||
const result = await guard.checkForLoginUI();
|
||||
|
||||
@@ -82,7 +82,7 @@ describe('AuthenticationGuard', () => {
|
||||
isVisible: vi.fn().mockResolvedValue(false),
|
||||
};
|
||||
|
||||
vi.mocked(mockPage.locator).mockReturnValue(mockLocator as any);
|
||||
vi.mocked(mockPage.locator).mockReturnValue(mockLocator as unknown as ReturnType<Page['locator']>);
|
||||
|
||||
const result = await guard.checkForLoginUI();
|
||||
|
||||
@@ -97,7 +97,7 @@ describe('AuthenticationGuard', () => {
|
||||
isVisible: vi.fn().mockResolvedValue(false),
|
||||
};
|
||||
|
||||
vi.mocked(mockPage.locator).mockReturnValue(mockLocator as any);
|
||||
vi.mocked(mockPage.locator).mockReturnValue(mockLocator as unknown as ReturnType<Page['locator']>);
|
||||
|
||||
const result = await guard.checkForLoginUI();
|
||||
|
||||
@@ -112,7 +112,7 @@ describe('AuthenticationGuard', () => {
|
||||
isVisible: vi.fn().mockResolvedValue(false),
|
||||
};
|
||||
|
||||
vi.mocked(mockPage.locator).mockReturnValue(mockLocator as any);
|
||||
vi.mocked(mockPage.locator).mockReturnValue(mockLocator as unknown as ReturnType<Page['locator']>);
|
||||
|
||||
const result = await guard.checkForLoginUI();
|
||||
|
||||
@@ -125,7 +125,7 @@ describe('AuthenticationGuard', () => {
|
||||
isVisible: vi.fn().mockRejectedValue(new Error('Page not ready')),
|
||||
};
|
||||
|
||||
vi.mocked(mockPage.locator).mockReturnValue(mockLocator as any);
|
||||
vi.mocked(mockPage.locator).mockReturnValue(mockLocator as unknown as ReturnType<Page['locator']>);
|
||||
|
||||
const result = await guard.checkForLoginUI();
|
||||
|
||||
@@ -141,7 +141,7 @@ describe('AuthenticationGuard', () => {
|
||||
isVisible: vi.fn().mockResolvedValue(true),
|
||||
};
|
||||
|
||||
vi.mocked(mockPage.locator).mockReturnValue(mockLocator as any);
|
||||
vi.mocked(mockPage.locator).mockReturnValue(mockLocator as unknown as ReturnType<Page['locator']>);
|
||||
|
||||
await expect(guard.failFastIfUnauthenticated()).rejects.toThrow(
|
||||
'Authentication required: Login UI detected on page'
|
||||
@@ -154,7 +154,7 @@ describe('AuthenticationGuard', () => {
|
||||
isVisible: vi.fn().mockResolvedValue(false),
|
||||
};
|
||||
|
||||
vi.mocked(mockPage.locator).mockReturnValue(mockLocator as any);
|
||||
vi.mocked(mockPage.locator).mockReturnValue(mockLocator as unknown as ReturnType<Page['locator']>);
|
||||
|
||||
await expect(guard.failFastIfUnauthenticated()).resolves.toBeUndefined();
|
||||
});
|
||||
@@ -167,7 +167,9 @@ describe('AuthenticationGuard', () => {
|
||||
isVisible: vi.fn().mockResolvedValue(true),
|
||||
};
|
||||
|
||||
vi.mocked(mockPage.locator).mockReturnValue(mockLocator as any);
|
||||
vi.mocked(mockPage.locator).mockReturnValue(
|
||||
mockLocator as unknown as ReturnType<Page['locator']>,
|
||||
);
|
||||
|
||||
await expect(guard.failFastIfUnauthenticated()).rejects.toThrow(
|
||||
'Authentication required: Login UI detected on page'
|
||||
@@ -181,7 +183,7 @@ describe('AuthenticationGuard', () => {
|
||||
isVisible: vi.fn().mockRejectedValue(new Error('Network timeout')),
|
||||
};
|
||||
|
||||
vi.mocked(mockPage.locator).mockReturnValue(mockLocator as any);
|
||||
vi.mocked(mockPage.locator).mockReturnValue(mockLocator as unknown as ReturnType<Page['locator']>);
|
||||
|
||||
// Should not throw, checkForLoginUI catches errors
|
||||
await expect(guard.failFastIfUnauthenticated()).resolves.toBeUndefined();
|
||||
@@ -196,7 +198,7 @@ describe('AuthenticationGuard', () => {
|
||||
isVisible: vi.fn().mockResolvedValue(true),
|
||||
};
|
||||
|
||||
vi.mocked(mockPage.locator).mockReturnValue(mockLocator as any);
|
||||
vi.mocked(mockPage.locator).mockReturnValue(mockLocator as unknown as ReturnType<Page['locator']>);
|
||||
vi.mocked(mockPage.content).mockResolvedValue(`
|
||||
<form action="/login">
|
||||
<button>Log in</button>
|
||||
@@ -226,9 +228,9 @@ describe('AuthenticationGuard', () => {
|
||||
};
|
||||
|
||||
vi.mocked(mockPage.locator)
|
||||
.mockReturnValueOnce(mockNotLoggedInLocator as any)
|
||||
.mockReturnValueOnce(mockLoginButtonLocator as any)
|
||||
.mockReturnValueOnce(mockAriaLabelLocator as any);
|
||||
.mockReturnValueOnce(mockNotLoggedInLocator as unknown as ReturnType<Page['locator']>)
|
||||
.mockReturnValueOnce(mockLoginButtonLocator as unknown as ReturnType<Page['locator']>)
|
||||
.mockReturnValueOnce(mockAriaLabelLocator as unknown as ReturnType<Page['locator']>);
|
||||
|
||||
vi.mocked(mockPage.content).mockResolvedValue(`
|
||||
<div class="dashboard">
|
||||
@@ -261,9 +263,9 @@ describe('AuthenticationGuard', () => {
|
||||
};
|
||||
|
||||
vi.mocked(mockPage.locator)
|
||||
.mockReturnValueOnce(mockNotLoggedInLocator as any)
|
||||
.mockReturnValueOnce(mockLoginButtonLocator as any)
|
||||
.mockReturnValueOnce(mockAriaLabelLocator as any);
|
||||
.mockReturnValueOnce(mockNotLoggedInLocator as unknown as ReturnType<Page['locator']>)
|
||||
.mockReturnValueOnce(mockLoginButtonLocator as unknown as ReturnType<Page['locator']>)
|
||||
.mockReturnValueOnce(mockAriaLabelLocator as unknown as ReturnType<Page['locator']>);
|
||||
|
||||
vi.mocked(mockPage.content).mockResolvedValue(`
|
||||
<div class="authenticated-page">
|
||||
@@ -287,18 +289,20 @@ describe('AuthenticationGuard', () => {
|
||||
count: vi.fn().mockResolvedValue(1),
|
||||
};
|
||||
|
||||
vi.mocked(mockPage.locator).mockReturnValue(mockLocator as any);
|
||||
vi.mocked(mockPage.locator).mockReturnValue(mockLocator as unknown as ReturnType<Page['locator']>);
|
||||
|
||||
// This method doesn't exist yet - will be added in GREEN phase
|
||||
const guard = new AuthenticationGuard(mockPage);
|
||||
|
||||
// Mock the method for testing purposes
|
||||
(guard as any).checkForAuthenticatedUI = async () => {
|
||||
const userMenuCount = await mockPage.locator('[data-testid="user-menu"]').count();
|
||||
return userMenuCount > 0;
|
||||
};
|
||||
|
||||
const result = await (guard as any).checkForAuthenticatedUI();
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(guard as unknown as { checkForAuthenticatedUI: () => Promise<boolean> }).checkForAuthenticatedUI =
|
||||
async () => {
|
||||
const userMenuCount = await mockPage.locator('[data-testid="user-menu"]').count();
|
||||
return userMenuCount > 0;
|
||||
};
|
||||
|
||||
const result = await (guard as unknown as { checkForAuthenticatedUI: () => Promise<boolean> }).checkForAuthenticatedUI();
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(mockPage.locator).toHaveBeenCalledWith('[data-testid="user-menu"]');
|
||||
@@ -313,20 +317,21 @@ describe('AuthenticationGuard', () => {
|
||||
};
|
||||
|
||||
vi.mocked(mockPage.locator)
|
||||
.mockReturnValueOnce(mockUserMenuLocator as any)
|
||||
.mockReturnValueOnce(mockLogoutButtonLocator as any);
|
||||
.mockReturnValueOnce(mockUserMenuLocator as unknown as ReturnType<Page['locator']>)
|
||||
.mockReturnValueOnce(mockLogoutButtonLocator as unknown as ReturnType<Page['locator']>);
|
||||
|
||||
// Mock the method for testing purposes
|
||||
const guard = new AuthenticationGuard(mockPage);
|
||||
(guard as any).checkForAuthenticatedUI = async () => {
|
||||
const userMenuCount = await mockPage.locator('[data-testid="user-menu"]').count();
|
||||
if (userMenuCount > 0) return true;
|
||||
|
||||
const logoutCount = await mockPage.locator('button:has-text("Log out")').count();
|
||||
return logoutCount > 0;
|
||||
};
|
||||
|
||||
const result = await (guard as any).checkForAuthenticatedUI();
|
||||
(guard as unknown as { checkForAuthenticatedUI: () => Promise<boolean> }).checkForAuthenticatedUI =
|
||||
async () => {
|
||||
const userMenuCount = await mockPage.locator('[data-testid="user-menu"]').count();
|
||||
if (userMenuCount > 0) return true;
|
||||
|
||||
const logoutCount = await mockPage.locator('button:has-text("Log out")').count();
|
||||
return logoutCount > 0;
|
||||
};
|
||||
|
||||
const result = await (guard as unknown as { checkForAuthenticatedUI: () => Promise<boolean> }).checkForAuthenticatedUI();
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
@@ -336,17 +341,18 @@ describe('AuthenticationGuard', () => {
|
||||
count: vi.fn().mockResolvedValue(0),
|
||||
};
|
||||
|
||||
vi.mocked(mockPage.locator).mockReturnValue(mockLocator as any);
|
||||
vi.mocked(mockPage.locator).mockReturnValue(mockLocator as unknown as ReturnType<Page['locator']>);
|
||||
|
||||
// Mock the method for testing purposes
|
||||
const guard = new AuthenticationGuard(mockPage);
|
||||
(guard as any).checkForAuthenticatedUI = async () => {
|
||||
const userMenuCount = await mockPage.locator('[data-testid="user-menu"]').count();
|
||||
const logoutCount = await mockPage.locator('button:has-text("Log out")').count();
|
||||
return userMenuCount > 0 || logoutCount > 0;
|
||||
};
|
||||
|
||||
const result = await (guard as any).checkForAuthenticatedUI();
|
||||
(guard as unknown as { checkForAuthenticatedUI: () => Promise<boolean> }).checkForAuthenticatedUI =
|
||||
async () => {
|
||||
const userMenuCount = await mockPage.locator('[data-testid="user-menu"]').count();
|
||||
const logoutCount = await mockPage.locator('button:has-text("Log out")').count();
|
||||
return userMenuCount > 0 || logoutCount > 0;
|
||||
};
|
||||
|
||||
const result = await (guard as unknown as { checkForAuthenticatedUI: () => Promise<boolean> }).checkForAuthenticatedUI();
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
@@ -17,7 +17,8 @@ import { ipcMain } from 'electron';
|
||||
describe('ElectronCheckoutConfirmationAdapter', () => {
|
||||
let mockWindow: BrowserWindow;
|
||||
let adapter: ElectronCheckoutConfirmationAdapter;
|
||||
let ipcMainOnCallback: ((event: any, decision: 'confirmed' | 'cancelled' | 'timeout') => void) | null = null;
|
||||
type IpcEventLike = { sender?: unknown };
|
||||
let ipcMainOnCallback: ((event: IpcEventLike, decision: 'confirmed' | 'cancelled' | 'timeout') => void) | null = null;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
@@ -26,7 +27,7 @@ describe('ElectronCheckoutConfirmationAdapter', () => {
|
||||
// Capture the IPC handler callback
|
||||
vi.mocked(ipcMain.on).mockImplementation((channel, callback) => {
|
||||
if (channel === 'checkout:confirm') {
|
||||
ipcMainOnCallback = callback as any;
|
||||
ipcMainOnCallback = callback as (event: IpcEventLike, decision: 'confirmed' | 'cancelled' | 'timeout') => void;
|
||||
}
|
||||
return ipcMain;
|
||||
});
|
||||
@@ -56,7 +57,7 @@ describe('ElectronCheckoutConfirmationAdapter', () => {
|
||||
// Simulate immediate confirmation via IPC
|
||||
setTimeout(() => {
|
||||
if (ipcMainOnCallback) {
|
||||
ipcMainOnCallback({} as any, 'confirmed');
|
||||
ipcMainOnCallback({} as IpcEventLike, 'confirmed');
|
||||
}
|
||||
}, 10);
|
||||
|
||||
@@ -90,7 +91,7 @@ describe('ElectronCheckoutConfirmationAdapter', () => {
|
||||
|
||||
setTimeout(() => {
|
||||
if (ipcMainOnCallback) {
|
||||
ipcMainOnCallback({} as any, 'confirmed');
|
||||
ipcMainOnCallback({} as IpcEventLike, 'confirmed');
|
||||
}
|
||||
}, 10);
|
||||
|
||||
@@ -115,7 +116,7 @@ describe('ElectronCheckoutConfirmationAdapter', () => {
|
||||
|
||||
setTimeout(() => {
|
||||
if (ipcMainOnCallback) {
|
||||
ipcMainOnCallback({} as any, 'cancelled');
|
||||
ipcMainOnCallback({} as IpcEventLike, 'cancelled');
|
||||
}
|
||||
}, 10);
|
||||
|
||||
@@ -168,7 +169,7 @@ describe('ElectronCheckoutConfirmationAdapter', () => {
|
||||
|
||||
// Confirm first request to clean up
|
||||
if (ipcMainOnCallback) {
|
||||
ipcMainOnCallback({} as any, 'confirmed');
|
||||
ipcMainOnCallback({} as IpcEventLike, 'confirmed');
|
||||
}
|
||||
|
||||
await promise1;
|
||||
|
||||
@@ -28,7 +28,7 @@ describe('Wizard Dismissal Detection', () => {
|
||||
}),
|
||||
};
|
||||
|
||||
vi.mocked(mockPage.locator).mockReturnValue(mockLocator as any);
|
||||
vi.mocked(mockPage.locator).mockReturnValue(mockLocator as unknown as ReturnType<Page['locator']>);
|
||||
|
||||
// Simulate the isWizardModalDismissed logic
|
||||
const isWizardModalDismissed = async (): Promise<boolean> => {
|
||||
@@ -63,7 +63,7 @@ describe('Wizard Dismissal Detection', () => {
|
||||
isVisible: vi.fn().mockResolvedValue(false),
|
||||
};
|
||||
|
||||
vi.mocked(mockPage.locator).mockReturnValue(mockLocator as any);
|
||||
vi.mocked(mockPage.locator).mockReturnValue(mockLocator as unknown as ReturnType<Page['locator']>);
|
||||
|
||||
const isWizardModalDismissed = async (): Promise<boolean> => {
|
||||
const modalVisible = await mockPage.locator(modalSelector).isVisible().catch(() => false);
|
||||
@@ -92,7 +92,7 @@ describe('Wizard Dismissal Detection', () => {
|
||||
isVisible: vi.fn().mockResolvedValue(true),
|
||||
};
|
||||
|
||||
vi.mocked(mockPage.locator).mockReturnValue(mockLocator as any);
|
||||
vi.mocked(mockPage.locator).mockReturnValue(mockLocator as unknown as ReturnType<Page['locator']>);
|
||||
|
||||
const isWizardModalDismissed = async (): Promise<boolean> => {
|
||||
const modalVisible = await mockPage.locator(modalSelector).isVisible().catch(() => false);
|
||||
|
||||
@@ -19,10 +19,14 @@ class FakeDashboardOverviewPresenter implements IDashboardOverviewPresenter {
|
||||
}
|
||||
}
|
||||
|
||||
function createTestImageService() {
|
||||
interface TestImageService {
|
||||
getDriverAvatar(driverId: string): string;
|
||||
}
|
||||
|
||||
function createTestImageService(): TestImageService {
|
||||
return {
|
||||
getDriverAvatar: (driverId: string) => `avatar-${driverId}`,
|
||||
} as any;
|
||||
};
|
||||
}
|
||||
|
||||
describe('GetDashboardOverviewUseCase', () => {
|
||||
@@ -74,7 +78,7 @@ describe('GetDashboardOverviewUseCase', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const results: any[] = [];
|
||||
const results: unknown[] = [];
|
||||
|
||||
const memberships = [
|
||||
{
|
||||
@@ -92,29 +96,53 @@ describe('GetDashboardOverviewUseCase', () => {
|
||||
const registeredRaceIds = new Set<string>(['race-1', 'race-3']);
|
||||
|
||||
const feedItems: DashboardFeedItemSummaryViewModel[] = [];
|
||||
const friends: any[] = [];
|
||||
|
||||
const driverRepository = {
|
||||
const friends: Array<{ id: string }> = [];
|
||||
|
||||
const driverRepository: {
|
||||
findById: (id: string) => Promise<{ id: string; name: string; country: string } | null>;
|
||||
} = {
|
||||
findById: async (id: string) => (id === driver.id ? driver : null),
|
||||
} as any;
|
||||
|
||||
const raceRepository = {
|
||||
};
|
||||
|
||||
const raceRepository: {
|
||||
findAll: () => Promise<
|
||||
Array<{
|
||||
id: string;
|
||||
leagueId: string;
|
||||
track: string;
|
||||
car: string;
|
||||
scheduledAt: Date;
|
||||
status: 'scheduled';
|
||||
}>
|
||||
>;
|
||||
} = {
|
||||
findAll: async () => races,
|
||||
} as any;
|
||||
|
||||
const resultRepository = {
|
||||
};
|
||||
|
||||
const resultRepository: {
|
||||
findAll: () => Promise<unknown[]>;
|
||||
} = {
|
||||
findAll: async () => results,
|
||||
} as any;
|
||||
|
||||
const leagueRepository = {
|
||||
};
|
||||
|
||||
const leagueRepository: {
|
||||
findAll: () => Promise<Array<{ id: string; name: string }>>;
|
||||
} = {
|
||||
findAll: async () => leagues,
|
||||
} as any;
|
||||
|
||||
const standingRepository = {
|
||||
};
|
||||
|
||||
const standingRepository: {
|
||||
findByLeagueId: (leagueId: string) => Promise<unknown[]>;
|
||||
} = {
|
||||
findByLeagueId: async () => [],
|
||||
} as any;
|
||||
|
||||
const leagueMembershipRepository = {
|
||||
};
|
||||
|
||||
const leagueMembershipRepository: {
|
||||
getMembership: (
|
||||
leagueId: string,
|
||||
driverIdParam: string,
|
||||
) => Promise<{ leagueId: string; driverId: string; status: string } | null>;
|
||||
} = {
|
||||
getMembership: async (leagueId: string, driverIdParam: string) => {
|
||||
return (
|
||||
memberships.find(
|
||||
@@ -122,22 +150,28 @@ describe('GetDashboardOverviewUseCase', () => {
|
||||
) ?? null
|
||||
);
|
||||
},
|
||||
} as any;
|
||||
|
||||
const raceRegistrationRepository = {
|
||||
};
|
||||
|
||||
const raceRegistrationRepository: {
|
||||
isRegistered: (raceId: string, driverIdParam: string) => Promise<boolean>;
|
||||
} = {
|
||||
isRegistered: async (raceId: string, driverIdParam: string) => {
|
||||
if (driverIdParam !== driverId) return false;
|
||||
return registeredRaceIds.has(raceId);
|
||||
},
|
||||
} as any;
|
||||
|
||||
const feedRepository = {
|
||||
};
|
||||
|
||||
const feedRepository: {
|
||||
getFeedForDriver: (driverIdParam: string) => Promise<DashboardFeedItemSummaryViewModel[]>;
|
||||
} = {
|
||||
getFeedForDriver: async () => feedItems,
|
||||
} as any;
|
||||
|
||||
const socialRepository = {
|
||||
};
|
||||
|
||||
const socialRepository: {
|
||||
getFriends: (driverIdParam: string) => Promise<Array<{ id: string }>>;
|
||||
} = {
|
||||
getFriends: async () => friends,
|
||||
} as any;
|
||||
};
|
||||
|
||||
const imageService = createTestImageService();
|
||||
|
||||
@@ -250,7 +284,10 @@ describe('GetDashboardOverviewUseCase', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const standingsByLeague = new Map<string, any[]>();
|
||||
const standingsByLeague = new Map<
|
||||
string,
|
||||
Array<{ leagueId: string; driverId: string; position: number; points: number }>
|
||||
>();
|
||||
standingsByLeague.set('league-A', [
|
||||
{ leagueId: 'league-A', driverId, position: 3, points: 50 },
|
||||
{ leagueId: 'league-A', driverId: 'other-1', position: 1, points: 80 },
|
||||
@@ -260,28 +297,43 @@ describe('GetDashboardOverviewUseCase', () => {
|
||||
{ leagueId: 'league-B', driverId: 'other-2', position: 2, points: 90 },
|
||||
]);
|
||||
|
||||
const driverRepository = {
|
||||
const driverRepository: {
|
||||
findById: (id: string) => Promise<{ id: string; name: string; country: string } | null>;
|
||||
} = {
|
||||
findById: async (id: string) => (id === driver.id ? driver : null),
|
||||
} as any;
|
||||
|
||||
const raceRepository = {
|
||||
};
|
||||
|
||||
const raceRepository: {
|
||||
findAll: () => Promise<typeof races>;
|
||||
} = {
|
||||
findAll: async () => races,
|
||||
} as any;
|
||||
|
||||
const resultRepository = {
|
||||
};
|
||||
|
||||
const resultRepository: {
|
||||
findAll: () => Promise<typeof results>;
|
||||
} = {
|
||||
findAll: async () => results,
|
||||
} as any;
|
||||
|
||||
const leagueRepository = {
|
||||
};
|
||||
|
||||
const leagueRepository: {
|
||||
findAll: () => Promise<typeof leagues>;
|
||||
} = {
|
||||
findAll: async () => leagues,
|
||||
} as any;
|
||||
|
||||
const standingRepository = {
|
||||
};
|
||||
|
||||
const standingRepository: {
|
||||
findByLeagueId: (leagueId: string) => Promise<Array<{ leagueId: string; driverId: string; position: number; points: number }>>;
|
||||
} = {
|
||||
findByLeagueId: async (leagueId: string) =>
|
||||
standingsByLeague.get(leagueId) ?? [],
|
||||
} as any;
|
||||
|
||||
const leagueMembershipRepository = {
|
||||
};
|
||||
|
||||
const leagueMembershipRepository: {
|
||||
getMembership: (
|
||||
leagueId: string,
|
||||
driverIdParam: string,
|
||||
) => Promise<{ leagueId: string; driverId: string; status: string } | null>;
|
||||
} = {
|
||||
getMembership: async (leagueId: string, driverIdParam: string) => {
|
||||
return (
|
||||
memberships.find(
|
||||
@@ -289,19 +341,25 @@ describe('GetDashboardOverviewUseCase', () => {
|
||||
) ?? null
|
||||
);
|
||||
},
|
||||
} as any;
|
||||
|
||||
const raceRegistrationRepository = {
|
||||
};
|
||||
|
||||
const raceRegistrationRepository: {
|
||||
isRegistered: (raceId: string, driverIdParam: string) => Promise<boolean>;
|
||||
} = {
|
||||
isRegistered: async () => false,
|
||||
} as any;
|
||||
|
||||
const feedRepository = {
|
||||
};
|
||||
|
||||
const feedRepository: {
|
||||
getFeedForDriver: (driverIdParam: string) => Promise<DashboardFeedItemSummaryViewModel[]>;
|
||||
} = {
|
||||
getFeedForDriver: async () => [],
|
||||
} as any;
|
||||
|
||||
const socialRepository = {
|
||||
};
|
||||
|
||||
const socialRepository: {
|
||||
getFriends: (driverIdParam: string) => Promise<Array<{ id: string }>>;
|
||||
} = {
|
||||
getFriends: async () => [],
|
||||
} as any;
|
||||
};
|
||||
|
||||
const imageService = createTestImageService();
|
||||
|
||||
@@ -372,41 +430,53 @@ describe('GetDashboardOverviewUseCase', () => {
|
||||
|
||||
const driver = { id: driverId, name: 'New Racer', country: 'FR' };
|
||||
|
||||
const driverRepository = {
|
||||
const driverRepository: {
|
||||
findById: (id: string) => Promise<{ id: string; name: string; country: string } | null>;
|
||||
} = {
|
||||
findById: async (id: string) => (id === driver.id ? driver : null),
|
||||
} as any;
|
||||
|
||||
const raceRepository = {
|
||||
};
|
||||
|
||||
const raceRepository: { findAll: () => Promise<never[]> } = {
|
||||
findAll: async () => [],
|
||||
} as any;
|
||||
|
||||
const resultRepository = {
|
||||
};
|
||||
|
||||
const resultRepository: { findAll: () => Promise<never[]> } = {
|
||||
findAll: async () => [],
|
||||
} as any;
|
||||
|
||||
const leagueRepository = {
|
||||
};
|
||||
|
||||
const leagueRepository: { findAll: () => Promise<never[]> } = {
|
||||
findAll: async () => [],
|
||||
} as any;
|
||||
|
||||
const standingRepository = {
|
||||
};
|
||||
|
||||
const standingRepository: {
|
||||
findByLeagueId: (leagueId: string) => Promise<never[]>;
|
||||
} = {
|
||||
findByLeagueId: async () => [],
|
||||
} as any;
|
||||
|
||||
const leagueMembershipRepository = {
|
||||
};
|
||||
|
||||
const leagueMembershipRepository: {
|
||||
getMembership: (leagueId: string, driverIdParam: string) => Promise<null>;
|
||||
} = {
|
||||
getMembership: async () => null,
|
||||
} as any;
|
||||
|
||||
const raceRegistrationRepository = {
|
||||
};
|
||||
|
||||
const raceRegistrationRepository: {
|
||||
isRegistered: (raceId: string, driverIdParam: string) => Promise<boolean>;
|
||||
} = {
|
||||
isRegistered: async () => false,
|
||||
} as any;
|
||||
|
||||
const feedRepository = {
|
||||
};
|
||||
|
||||
const feedRepository: {
|
||||
getFeedForDriver: (driverIdParam: string) => Promise<DashboardFeedItemSummaryViewModel[]>;
|
||||
} = {
|
||||
getFeedForDriver: async () => [],
|
||||
} as any;
|
||||
|
||||
const socialRepository = {
|
||||
};
|
||||
|
||||
const socialRepository: {
|
||||
getFriends: (driverIdParam: string) => Promise<Array<{ id: string }>>;
|
||||
} = {
|
||||
getFriends: async () => [],
|
||||
} as any;
|
||||
};
|
||||
|
||||
const imageService = createTestImageService();
|
||||
|
||||
|
||||
@@ -121,28 +121,28 @@ class InMemoryLeagueRepository implements ILeagueRepository {
|
||||
}
|
||||
|
||||
class InMemoryDriverRepository implements IDriverRepository {
|
||||
private drivers = new Map<string, any>();
|
||||
|
||||
private drivers = new Map<string, { id: string; name: string; country: string }>();
|
||||
|
||||
constructor(drivers: Array<{ id: string; name: string; country: string }>) {
|
||||
for (const driver of drivers) {
|
||||
this.drivers.set(driver.id, {
|
||||
...driver,
|
||||
} as any);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<any | null> {
|
||||
|
||||
async findById(id: string): Promise<{ id: string; name: string; country: string } | null> {
|
||||
return this.drivers.get(id) ?? null;
|
||||
}
|
||||
|
||||
async findAll(): Promise<any[]> {
|
||||
|
||||
async findAll(): Promise<Array<{ id: string; name: string; country: string }>> {
|
||||
return [...this.drivers.values()];
|
||||
}
|
||||
|
||||
async findByIds(ids: string[]): Promise<any[]> {
|
||||
|
||||
async findByIds(ids: string[]): Promise<Array<{ id: string; name: string; country: string }>> {
|
||||
return ids
|
||||
.map(id => this.drivers.get(id))
|
||||
.filter((d): d is any => !!d);
|
||||
.filter((d): d is { id: string; name: string; country: string } => !!d);
|
||||
}
|
||||
|
||||
async create(): Promise<any> {
|
||||
|
||||
@@ -73,15 +73,22 @@ describe('ImportRaceResultsUseCase', () => {
|
||||
let existsByRaceIdCalled = false;
|
||||
const recalcCalls: string[] = [];
|
||||
|
||||
const raceRepository = {
|
||||
const raceRepository: {
|
||||
findById: (id: string) => Promise<Race | null>;
|
||||
} = {
|
||||
findById: async (id: string) => races.get(id) ?? null,
|
||||
} as unknown as any;
|
||||
|
||||
const leagueRepository = {
|
||||
};
|
||||
|
||||
const leagueRepository: {
|
||||
findById: (id: string) => Promise<League | null>;
|
||||
} = {
|
||||
findById: async (id: string) => leagues.get(id) ?? null,
|
||||
} as unknown as any;
|
||||
|
||||
const resultRepository = {
|
||||
};
|
||||
|
||||
const resultRepository: {
|
||||
existsByRaceId: (raceId: string) => Promise<boolean>;
|
||||
createMany: (results: Result[]) => Promise<Result[]>;
|
||||
} = {
|
||||
existsByRaceId: async (raceId: string) => {
|
||||
existsByRaceIdCalled = true;
|
||||
return storedResults.some((r) => r.raceId === raceId);
|
||||
@@ -90,13 +97,15 @@ describe('ImportRaceResultsUseCase', () => {
|
||||
storedResults.push(...results);
|
||||
return results;
|
||||
},
|
||||
} as unknown as any;
|
||||
|
||||
const standingRepository = {
|
||||
};
|
||||
|
||||
const standingRepository: {
|
||||
recalculate: (leagueId: string) => Promise<void>;
|
||||
} = {
|
||||
recalculate: async (leagueId: string) => {
|
||||
recalcCalls.push(leagueId);
|
||||
},
|
||||
} as unknown as any;
|
||||
};
|
||||
|
||||
const presenter = new FakeImportRaceResultsPresenter();
|
||||
|
||||
@@ -183,28 +192,37 @@ describe('ImportRaceResultsUseCase', () => {
|
||||
}),
|
||||
];
|
||||
|
||||
const raceRepository = {
|
||||
const raceRepository: {
|
||||
findById: (id: string) => Promise<Race | null>;
|
||||
} = {
|
||||
findById: async (id: string) => races.get(id) ?? null,
|
||||
} as unknown as any;
|
||||
|
||||
const leagueRepository = {
|
||||
};
|
||||
|
||||
const leagueRepository: {
|
||||
findById: (id: string) => Promise<League | null>;
|
||||
} = {
|
||||
findById: async (id: string) => leagues.get(id) ?? null,
|
||||
} as unknown as any;
|
||||
|
||||
const resultRepository = {
|
||||
};
|
||||
|
||||
const resultRepository: {
|
||||
existsByRaceId: (raceId: string) => Promise<boolean>;
|
||||
createMany: (results: Result[]) => Promise<Result[]>;
|
||||
} = {
|
||||
existsByRaceId: async (raceId: string) => {
|
||||
return storedResults.some((r) => r.raceId === raceId);
|
||||
},
|
||||
createMany: async (_results: Result[]) => {
|
||||
throw new Error('Should not be called when results already exist');
|
||||
},
|
||||
} as unknown as any;
|
||||
|
||||
const standingRepository = {
|
||||
};
|
||||
|
||||
const standingRepository: {
|
||||
recalculate: (leagueId: string) => Promise<void>;
|
||||
} = {
|
||||
recalculate: async (_leagueId: string) => {
|
||||
throw new Error('Should not be called when results already exist');
|
||||
},
|
||||
} as unknown as any;
|
||||
};
|
||||
|
||||
const presenter = new FakeImportRaceResultsPresenter();
|
||||
|
||||
@@ -257,8 +275,16 @@ describe('GetRaceResultsDetailUseCase', () => {
|
||||
status: 'completed',
|
||||
});
|
||||
|
||||
const driver1 = { id: 'driver-a', name: 'Driver A', country: 'US' } as any;
|
||||
const driver2 = { id: 'driver-b', name: 'Driver B', country: 'GB' } as any;
|
||||
const driver1: { id: string; name: string; country: string } = {
|
||||
id: 'driver-a',
|
||||
name: 'Driver A',
|
||||
country: 'US',
|
||||
};
|
||||
const driver2: { id: string; name: string; country: string } = {
|
||||
id: 'driver-b',
|
||||
name: 'Driver B',
|
||||
country: 'GB',
|
||||
};
|
||||
|
||||
const result1 = Result.create({
|
||||
id: 'r1',
|
||||
@@ -285,26 +311,36 @@ describe('GetRaceResultsDetailUseCase', () => {
|
||||
const results = [result1, result2];
|
||||
const drivers = [driver1, driver2];
|
||||
|
||||
const raceRepository = {
|
||||
const raceRepository: {
|
||||
findById: (id: string) => Promise<Race | null>;
|
||||
} = {
|
||||
findById: async (id: string) => races.get(id) ?? null,
|
||||
} as unknown as any;
|
||||
|
||||
const leagueRepository = {
|
||||
};
|
||||
|
||||
const leagueRepository: {
|
||||
findById: (id: string) => Promise<League | null>;
|
||||
} = {
|
||||
findById: async (id: string) => leagues.get(id) ?? null,
|
||||
} as unknown as any;
|
||||
|
||||
const resultRepository = {
|
||||
};
|
||||
|
||||
const resultRepository: {
|
||||
findByRaceId: (raceId: string) => Promise<Result[]>;
|
||||
} = {
|
||||
findByRaceId: async (raceId: string) =>
|
||||
results.filter((r) => r.raceId === raceId),
|
||||
} as unknown as any;
|
||||
|
||||
const driverRepository = {
|
||||
};
|
||||
|
||||
const driverRepository: {
|
||||
findAll: () => Promise<Array<{ id: string; name: string; country: string }>>;
|
||||
} = {
|
||||
findAll: async () => drivers,
|
||||
} as unknown as any;
|
||||
|
||||
const penaltyRepository = {
|
||||
};
|
||||
|
||||
const penaltyRepository: {
|
||||
findByRaceId: (raceId: string) => Promise<Penalty[]>;
|
||||
} = {
|
||||
findByRaceId: async () => [] as Penalty[],
|
||||
} as unknown as any;
|
||||
};
|
||||
|
||||
const presenter = new FakeRaceResultsDetailPresenter();
|
||||
|
||||
@@ -350,7 +386,11 @@ describe('GetRaceResultsDetailUseCase', () => {
|
||||
status: 'completed',
|
||||
});
|
||||
|
||||
const driver = { id: 'driver-pen', name: 'Penalty Driver', country: 'DE' } as any;
|
||||
const driver: { id: string; name: string; country: string } = {
|
||||
id: 'driver-pen',
|
||||
name: 'Penalty Driver',
|
||||
country: 'DE',
|
||||
};
|
||||
|
||||
const result = Result.create({
|
||||
id: 'res-pen',
|
||||
@@ -380,27 +420,37 @@ describe('GetRaceResultsDetailUseCase', () => {
|
||||
const drivers = [driver];
|
||||
const penalties = [penalty];
|
||||
|
||||
const raceRepository = {
|
||||
const raceRepository: {
|
||||
findById: (id: string) => Promise<Race | null>;
|
||||
} = {
|
||||
findById: async (id: string) => races.get(id) ?? null,
|
||||
} as unknown as any;
|
||||
|
||||
const leagueRepository = {
|
||||
};
|
||||
|
||||
const leagueRepository: {
|
||||
findById: (id: string) => Promise<League | null>;
|
||||
} = {
|
||||
findById: async (id: string) => leagues.get(id) ?? null,
|
||||
} as unknown as any;
|
||||
|
||||
const resultRepository = {
|
||||
};
|
||||
|
||||
const resultRepository: {
|
||||
findByRaceId: (raceId: string) => Promise<Result[]>;
|
||||
} = {
|
||||
findByRaceId: async (raceId: string) =>
|
||||
results.filter((r) => r.raceId === raceId),
|
||||
} as unknown as any;
|
||||
|
||||
const driverRepository = {
|
||||
};
|
||||
|
||||
const driverRepository: {
|
||||
findAll: () => Promise<Array<{ id: string; name: string; country: string }>>;
|
||||
} = {
|
||||
findAll: async () => drivers,
|
||||
} as unknown as any;
|
||||
|
||||
const penaltyRepository = {
|
||||
};
|
||||
|
||||
const penaltyRepository: {
|
||||
findByRaceId: (raceId: string) => Promise<Penalty[]>;
|
||||
} = {
|
||||
findByRaceId: async (raceId: string) =>
|
||||
penalties.filter((p) => p.raceId === raceId),
|
||||
} as unknown as any;
|
||||
};
|
||||
|
||||
const presenter = new FakeRaceResultsDetailPresenter();
|
||||
|
||||
@@ -437,28 +487,38 @@ describe('GetRaceResultsDetailUseCase', () => {
|
||||
|
||||
it('presents an error when race does not exist', async () => {
|
||||
// Given repositories without the requested race
|
||||
const raceRepository = {
|
||||
const raceRepository: {
|
||||
findById: (id: string) => Promise<Race | null>;
|
||||
} = {
|
||||
findById: async () => null,
|
||||
} as unknown as any;
|
||||
|
||||
const leagueRepository = {
|
||||
};
|
||||
|
||||
const leagueRepository: {
|
||||
findById: (id: string) => Promise<League | null>;
|
||||
} = {
|
||||
findById: async () => null,
|
||||
} as unknown as any;
|
||||
|
||||
const resultRepository = {
|
||||
};
|
||||
|
||||
const resultRepository: {
|
||||
findByRaceId: (raceId: string) => Promise<Result[]>;
|
||||
} = {
|
||||
findByRaceId: async () => [] as Result[],
|
||||
} as unknown as any;
|
||||
|
||||
const driverRepository = {
|
||||
findAll: async () => [] as any[],
|
||||
} as unknown as any;
|
||||
|
||||
const penaltyRepository = {
|
||||
};
|
||||
|
||||
const driverRepository: {
|
||||
findAll: () => Promise<Array<{ id: string; name: string; country: string }>>;
|
||||
} = {
|
||||
findAll: async () => [],
|
||||
};
|
||||
|
||||
const penaltyRepository: {
|
||||
findByRaceId: (raceId: string) => Promise<Penalty[]>;
|
||||
} = {
|
||||
findByRaceId: async () => [] as Penalty[],
|
||||
} as unknown as any;
|
||||
|
||||
};
|
||||
|
||||
const presenter = new FakeRaceResultsDetailPresenter();
|
||||
|
||||
|
||||
const useCase = new GetRaceResultsDetailUseCase(
|
||||
raceRepository,
|
||||
leagueRepository,
|
||||
@@ -467,10 +527,10 @@ describe('GetRaceResultsDetailUseCase', () => {
|
||||
penaltyRepository,
|
||||
presenter,
|
||||
);
|
||||
|
||||
|
||||
// When
|
||||
await useCase.execute({ raceId: 'missing-race' });
|
||||
|
||||
|
||||
const viewModel = presenter.getViewModel();
|
||||
expect(viewModel).not.toBeNull();
|
||||
expect(viewModel!.race).toBeNull();
|
||||
|
||||
@@ -35,11 +35,27 @@ import { GetTeamJoinRequestsUseCase } from '@gridpilot/racing/application/use-ca
|
||||
import { GetDriverTeamUseCase } from '@gridpilot/racing/application/use-cases/GetDriverTeamUseCase';
|
||||
import type { IDriverRegistrationStatusPresenter } from '@gridpilot/racing/application/presenters/IDriverRegistrationStatusPresenter';
|
||||
import type { IRaceRegistrationsPresenter } from '@gridpilot/racing/application/presenters/IRaceRegistrationsPresenter';
|
||||
import type { IAllTeamsPresenter } from '@gridpilot/racing/application/presenters/IAllTeamsPresenter';
|
||||
import type {
|
||||
IAllTeamsPresenter,
|
||||
AllTeamsResultDTO,
|
||||
AllTeamsViewModel,
|
||||
} from '@gridpilot/racing/application/presenters/IAllTeamsPresenter';
|
||||
import type { ITeamDetailsPresenter } from '@gridpilot/racing/application/presenters/ITeamDetailsPresenter';
|
||||
import type { ITeamMembersPresenter } from '@gridpilot/racing/application/presenters/ITeamMembersPresenter';
|
||||
import type { ITeamJoinRequestsPresenter } from '@gridpilot/racing/application/presenters/ITeamJoinRequestsPresenter';
|
||||
import type { IDriverTeamPresenter } from '@gridpilot/racing/application/presenters/IDriverTeamPresenter';
|
||||
import type {
|
||||
ITeamMembersPresenter,
|
||||
TeamMembersResultDTO,
|
||||
TeamMembersViewModel,
|
||||
} from '@gridpilot/racing/application/presenters/ITeamMembersPresenter';
|
||||
import type {
|
||||
ITeamJoinRequestsPresenter,
|
||||
TeamJoinRequestsResultDTO,
|
||||
TeamJoinRequestsViewModel,
|
||||
} from '@gridpilot/racing/application/presenters/ITeamJoinRequestsPresenter';
|
||||
import type {
|
||||
IDriverTeamPresenter,
|
||||
DriverTeamResultDTO,
|
||||
DriverTeamViewModel,
|
||||
} from '@gridpilot/racing/application/presenters/IDriverTeamPresenter';
|
||||
|
||||
/**
|
||||
* Simple in-memory fakes mirroring current alpha behavior.
|
||||
@@ -407,10 +423,35 @@ describe('Racing application use-cases - teams', () => {
|
||||
}
|
||||
|
||||
class TestAllTeamsPresenter implements IAllTeamsPresenter {
|
||||
teams: any[] = [];
|
||||
private viewModel: AllTeamsViewModel | null = null;
|
||||
|
||||
present(teams: any[]): void {
|
||||
this.teams = teams;
|
||||
reset(): void {
|
||||
this.viewModel = null;
|
||||
}
|
||||
|
||||
present(input: AllTeamsResultDTO): void {
|
||||
this.viewModel = {
|
||||
teams: input.teams.map((team) => ({
|
||||
id: team.id,
|
||||
name: team.name,
|
||||
tag: team.tag,
|
||||
description: team.description,
|
||||
memberCount: team.memberCount,
|
||||
leagues: team.leagues,
|
||||
specialization: team.specialization,
|
||||
region: team.region,
|
||||
languages: team.languages,
|
||||
})),
|
||||
totalCount: input.teams.length,
|
||||
};
|
||||
}
|
||||
|
||||
getViewModel(): AllTeamsViewModel | null {
|
||||
return this.viewModel;
|
||||
}
|
||||
|
||||
get teams(): any[] {
|
||||
return this.viewModel?.teams ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,26 +464,129 @@ describe('Racing application use-cases - teams', () => {
|
||||
}
|
||||
|
||||
class TestTeamMembersPresenter implements ITeamMembersPresenter {
|
||||
members: any[] = [];
|
||||
private viewModel: TeamMembersViewModel | null = null;
|
||||
|
||||
present(members: any[]): void {
|
||||
this.members = members;
|
||||
reset(): void {
|
||||
this.viewModel = null;
|
||||
}
|
||||
|
||||
present(input: TeamMembersResultDTO): void {
|
||||
const members = input.memberships.map((membership) => {
|
||||
const driverId = membership.driverId;
|
||||
const driverName = input.driverNames[driverId] ?? driverId;
|
||||
const avatarUrl = input.avatarUrls[driverId] ?? '';
|
||||
|
||||
return {
|
||||
driverId,
|
||||
driverName,
|
||||
role: membership.role,
|
||||
joinedAt: membership.joinedAt.toISOString(),
|
||||
isActive: membership.status === 'active',
|
||||
avatarUrl,
|
||||
};
|
||||
});
|
||||
|
||||
const ownerCount = members.filter((m) => m.role === 'owner').length;
|
||||
const managerCount = members.filter((m) => m.role === 'manager').length;
|
||||
const memberCount = members.filter((m) => m.role === 'member').length;
|
||||
|
||||
this.viewModel = {
|
||||
members,
|
||||
totalCount: members.length,
|
||||
ownerCount,
|
||||
managerCount,
|
||||
memberCount,
|
||||
};
|
||||
}
|
||||
|
||||
getViewModel(): TeamMembersViewModel | null {
|
||||
return this.viewModel;
|
||||
}
|
||||
|
||||
get members(): any[] {
|
||||
return this.viewModel?.members ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
class TestTeamJoinRequestsPresenter implements ITeamJoinRequestsPresenter {
|
||||
requests: any[] = [];
|
||||
private viewModel: TeamJoinRequestsViewModel | null = null;
|
||||
|
||||
present(requests: any[]): void {
|
||||
this.requests = requests;
|
||||
reset(): void {
|
||||
this.viewModel = null;
|
||||
}
|
||||
|
||||
present(input: TeamJoinRequestsResultDTO): void {
|
||||
const requests = input.requests.map((request) => {
|
||||
const driverId = request.driverId;
|
||||
const driverName = input.driverNames[driverId] ?? driverId;
|
||||
const avatarUrl = input.avatarUrls[driverId] ?? '';
|
||||
|
||||
return {
|
||||
requestId: request.id,
|
||||
driverId,
|
||||
driverName,
|
||||
teamId: request.teamId,
|
||||
status: 'pending',
|
||||
requestedAt: request.requestedAt.toISOString(),
|
||||
avatarUrl,
|
||||
};
|
||||
});
|
||||
|
||||
const pendingCount = requests.filter((r) => r.status === 'pending').length;
|
||||
|
||||
this.viewModel = {
|
||||
requests,
|
||||
pendingCount,
|
||||
totalCount: requests.length,
|
||||
};
|
||||
}
|
||||
|
||||
getViewModel(): TeamJoinRequestsViewModel | null {
|
||||
return this.viewModel;
|
||||
}
|
||||
|
||||
get requests(): any[] {
|
||||
return this.viewModel?.requests ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
class TestDriverTeamPresenter implements IDriverTeamPresenter {
|
||||
viewModel: any = null;
|
||||
private viewModel: DriverTeamViewModel | null = null;
|
||||
|
||||
present(team: any, membership: any, driverId: string): void {
|
||||
this.viewModel = { team, membership, driverId };
|
||||
reset(): void {
|
||||
this.viewModel = null;
|
||||
}
|
||||
|
||||
present(input: DriverTeamResultDTO): void {
|
||||
const { team, membership, driverId } = input;
|
||||
|
||||
const isOwner = team.ownerId === driverId;
|
||||
const canManage = membership.role === 'owner' || membership.role === 'manager';
|
||||
|
||||
this.viewModel = {
|
||||
team: {
|
||||
id: team.id,
|
||||
name: team.name,
|
||||
tag: team.tag,
|
||||
description: team.description,
|
||||
ownerId: team.ownerId,
|
||||
leagues: team.leagues,
|
||||
specialization: team.specialization,
|
||||
region: team.region,
|
||||
languages: team.languages,
|
||||
},
|
||||
membership: {
|
||||
role: membership.role,
|
||||
joinedAt: membership.joinedAt.toISOString(),
|
||||
isActive: membership.status === 'active',
|
||||
},
|
||||
isOwner,
|
||||
canManage,
|
||||
};
|
||||
}
|
||||
|
||||
getViewModel(): DriverTeamViewModel | null {
|
||||
return this.viewModel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,19 +621,22 @@ describe('Racing application use-cases - teams', () => {
|
||||
teamDetailsPresenter,
|
||||
);
|
||||
|
||||
const driverRepository = new FakeDriverRepository();
|
||||
const imageService = new FakeImageService();
|
||||
|
||||
teamMembersPresenter = new TestTeamMembersPresenter();
|
||||
getTeamMembersUseCase = new GetTeamMembersUseCase(
|
||||
membershipRepo,
|
||||
new FakeDriverRepository() as any,
|
||||
new FakeImageService() as any,
|
||||
driverRepository,
|
||||
imageService,
|
||||
teamMembersPresenter,
|
||||
);
|
||||
|
||||
|
||||
teamJoinRequestsPresenter = new TestTeamJoinRequestsPresenter();
|
||||
getTeamJoinRequestsUseCase = new GetTeamJoinRequestsUseCase(
|
||||
membershipRepo,
|
||||
new FakeDriverRepository() as any,
|
||||
new FakeImageService() as any,
|
||||
driverRepository,
|
||||
imageService,
|
||||
teamJoinRequestsPresenter,
|
||||
);
|
||||
|
||||
@@ -614,11 +761,12 @@ describe('Racing application use-cases - teams', () => {
|
||||
leagues: [],
|
||||
});
|
||||
|
||||
await getDriverTeamUseCase.execute(ownerId);
|
||||
await getDriverTeamUseCase.execute({ driverId: ownerId }, driverTeamPresenter);
|
||||
const result = driverTeamPresenter.viewModel;
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.team.id).toBe(team.id);
|
||||
expect(result?.membership.driverId).toBe(ownerId);
|
||||
expect(result?.membership.isActive).toBe(true);
|
||||
expect(result?.isOwner).toBe(true);
|
||||
});
|
||||
|
||||
it('lists all teams and members via queries after multiple operations', async () => {
|
||||
@@ -635,10 +783,10 @@ describe('Racing application use-cases - teams', () => {
|
||||
|
||||
await joinTeam.execute({ teamId: team.id, driverId: otherDriverId });
|
||||
|
||||
await getAllTeamsUseCase.execute();
|
||||
await getAllTeamsUseCase.execute(undefined as void, allTeamsPresenter);
|
||||
expect(allTeamsPresenter.teams.length).toBe(1);
|
||||
|
||||
await getTeamMembersUseCase.execute(team.id);
|
||||
await getTeamMembersUseCase.execute({ teamId: team.id }, teamMembersPresenter);
|
||||
const memberIds = teamMembersPresenter.members.map((m) => m.driverId).sort();
|
||||
expect(memberIds).toEqual([ownerId, otherDriverId].sort());
|
||||
});
|
||||
|
||||
@@ -10,10 +10,12 @@ import { describe, it, expect } from 'vitest';
|
||||
|
||||
describe('RootLayout auth caching behavior', () => {
|
||||
it('is configured as dynamic to avoid static auth caching', async () => {
|
||||
const layoutModule = await import('../../../../apps/website/app/layout');
|
||||
|
||||
const layoutModule = (await import(
|
||||
'../../../../apps/website/app/layout',
|
||||
)) as { dynamic?: string };
|
||||
|
||||
// Next.js dynamic routing flag
|
||||
const dynamic = (layoutModule as any).dynamic;
|
||||
const dynamic = layoutModule.dynamic;
|
||||
|
||||
expect(dynamic).toBe('force-dynamic');
|
||||
});
|
||||
@@ -21,9 +23,11 @@ describe('RootLayout auth caching behavior', () => {
|
||||
|
||||
describe('Dashboard auth caching behavior', () => {
|
||||
it('is configured as dynamic to evaluate auth per request', async () => {
|
||||
const dashboardModule = await import('../../../../apps/website/app/dashboard/page');
|
||||
|
||||
const dynamic = (dashboardModule as any).dynamic;
|
||||
const dashboardModule = (await import(
|
||||
'../../../../apps/website/app/dashboard/page',
|
||||
)) as { dynamic?: string };
|
||||
|
||||
const dynamic = dashboardModule.dynamic;
|
||||
|
||||
expect(dynamic).toBe('force-dynamic');
|
||||
});
|
||||
|
||||
@@ -26,7 +26,7 @@ describe('iRacing auth route handlers', () => {
|
||||
it('start route redirects to auth URL and sets state cookie', async () => {
|
||||
const req = new Request('http://localhost/auth/iracing/start?returnTo=/dashboard');
|
||||
|
||||
const res = await startGet(req as any);
|
||||
const res = await startGet(req);
|
||||
|
||||
expect(res.status).toBe(307);
|
||||
const location = res.headers.get('location') ?? '';
|
||||
@@ -51,7 +51,7 @@ describe('iRacing auth route handlers', () => {
|
||||
'http://localhost/auth/iracing/callback?code=demo-code&state=valid-state&returnTo=/dashboard',
|
||||
);
|
||||
|
||||
const res = await callbackGet(req as any);
|
||||
const res = await callbackGet(req);
|
||||
|
||||
expect(res.status).toBe(307);
|
||||
const location = res.headers.get('location');
|
||||
@@ -70,7 +70,7 @@ describe('iRacing auth route handlers', () => {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
const res = await logoutPost(req as any);
|
||||
const res = await logoutPost(req);
|
||||
|
||||
expect(res.status).toBe(307);
|
||||
const location = res.headers.get('location');
|
||||
|
||||
@@ -43,7 +43,7 @@ function createSearchParams(stepValue: string | null) {
|
||||
}
|
||||
return null;
|
||||
},
|
||||
} as any;
|
||||
} as URLSearchParams;
|
||||
}
|
||||
|
||||
describe('CreateLeaguePage - URL-bound wizard steps', () => {
|
||||
|
||||
@@ -10,13 +10,15 @@ const mockCheckRateLimit = vi.fn<[], Promise<RateLimitResult>>();
|
||||
const mockGetClientIp = vi.fn<[], string>();
|
||||
|
||||
vi.mock('../../../apps/website/lib/rate-limit', () => ({
|
||||
checkRateLimit: (...args: any[]) => mockCheckRateLimit(...(args as [])),
|
||||
getClientIp: (..._args: any[]) => mockGetClientIp(),
|
||||
checkRateLimit: (...args: unknown[]) => mockCheckRateLimit(...(args as [])),
|
||||
getClientIp: (..._args: unknown[]) => mockGetClientIp(),
|
||||
}));
|
||||
|
||||
async function getPostHandler() {
|
||||
const routeModule: any = await import('../../../apps/website/app/api/signup/route');
|
||||
return routeModule.POST as (request: Request) => Promise<Response>;
|
||||
const routeModule = (await import(
|
||||
'../../../apps/website/app/api/signup/route'
|
||||
)) as { POST: (request: Request) => Promise<Response> };
|
||||
return routeModule.POST;
|
||||
}
|
||||
|
||||
function createJsonRequest(body: unknown): Request {
|
||||
@@ -55,7 +57,7 @@ describe('/api/signup POST', () => {
|
||||
expect(response.status).toBeGreaterThanOrEqual(200);
|
||||
expect(response.status).toBeLessThan(300);
|
||||
|
||||
const data = (await response.json()) as any;
|
||||
const data = (await response.json()) as { message: unknown; ok: unknown };
|
||||
|
||||
expect(data).toHaveProperty('message');
|
||||
expect(typeof data.message).toBe('string');
|
||||
@@ -73,7 +75,7 @@ describe('/api/signup POST', () => {
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
|
||||
const data = (await response.json()) as any;
|
||||
const data = (await response.json()) as { error: unknown };
|
||||
expect(typeof data.error).toBe('string');
|
||||
expect(data.error.toLowerCase()).toContain('email');
|
||||
});
|
||||
@@ -89,7 +91,7 @@ describe('/api/signup POST', () => {
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
|
||||
const data = (await response.json()) as any;
|
||||
const data = (await response.json()) as { error: unknown };
|
||||
expect(typeof data.error).toBe('string');
|
||||
});
|
||||
|
||||
@@ -106,7 +108,7 @@ describe('/api/signup POST', () => {
|
||||
|
||||
expect(second.status).toBe(409);
|
||||
|
||||
const data = (await second.json()) as any;
|
||||
const data = (await second.json()) as { error: unknown };
|
||||
expect(typeof data.error).toBe('string');
|
||||
expect(data.error.toLowerCase()).toContain('already');
|
||||
});
|
||||
@@ -128,7 +130,7 @@ describe('/api/signup POST', () => {
|
||||
|
||||
expect(response.status).toBe(429);
|
||||
|
||||
const data = (await response.json()) as any;
|
||||
const data = (await response.json()) as { error: unknown; retryAfter?: unknown };
|
||||
expect(typeof data.error).toBe('string');
|
||||
expect(data).toHaveProperty('retryAfter');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user