Refactor infra tests, clean E2E step suites, and fix TS in tests

This commit is contained in:
2025-11-30 10:58:49 +01:00
parent af14526ae2
commit f8a1fbeb50
43 changed files with 883 additions and 2159 deletions

View File

@@ -1,4 +1,4 @@
import { jest } from '@jest/globals'
import { describe, expect, test } from 'vitest'
import { OverlayAction, ActionAck } from '../../../../packages/application/ports/IOverlaySyncPort'
import { IAutomationEventPublisher, AutomationEvent } from '../../../../packages/application/ports/IAutomationEventPublisher'
import { IAutomationLifecycleEmitter, LifecycleCallback } from '../../../../packages/infrastructure/adapters/IAutomationLifecycleEmitter'

View File

@@ -1,4 +1,4 @@
import { jest } from '@jest/globals'
import { describe, expect, test } from 'vitest'
import { OverlayAction } from '../../../../packages/application/ports/IOverlaySyncPort'
import { IAutomationLifecycleEmitter, LifecycleCallback } from '../../../../packages/infrastructure/adapters/IAutomationLifecycleEmitter'
import { OverlaySyncService } from '../../../../packages/application/services/OverlaySyncService'

View File

@@ -3,16 +3,7 @@ import { CheckAuthenticationUseCase } from '../../../../packages/application/use
import { AuthenticationState } from '../../../../packages/domain/value-objects/AuthenticationState';
import { BrowserAuthenticationState } from '../../../../packages/domain/value-objects/BrowserAuthenticationState';
import { Result } from '../../../../packages/shared/result/Result';
interface IAuthenticationService {
checkSession(): Promise<Result<AuthenticationState>>;
initiateLogin(): Promise<Result<void>>;
clearSession(): Promise<Result<void>>;
getState(): AuthenticationState;
validateServerSide(): Promise<Result<boolean>>;
refreshSession(): Promise<Result<void>>;
getSessionExpiry(): Promise<Result<Date | null>>;
}
import type { IAuthenticationService } from '../../../../packages/application/ports/IAuthenticationService';
interface ISessionValidator {
validateSession(): Promise<Result<boolean>>;
@@ -27,6 +18,7 @@ describe('CheckAuthenticationUseCase', () => {
validateServerSide: Mock;
refreshSession: Mock;
getSessionExpiry: Mock;
verifyPageAuthentication: Mock;
};
let mockSessionValidator: {
validateSession: Mock;
@@ -41,6 +33,7 @@ describe('CheckAuthenticationUseCase', () => {
validateServerSide: vi.fn(),
refreshSession: vi.fn(),
getSessionExpiry: vi.fn(),
verifyPageAuthentication: vi.fn(),
};
mockSessionValidator = {

View File

@@ -24,9 +24,9 @@ describe('CompleteRaceCreationUseCase', () => {
const price = CheckoutPrice.fromString('$25.50');
const state = CheckoutState.ready();
const sessionId = 'test-session-123';
vi.mocked(mockCheckoutService.extractCheckoutInfo).mockResolvedValue(
Result.ok({ price, state })
Result.ok({ price, state, buttonHtml: '<a>$25.50</a>' })
);
const result = await useCase.execute(sessionId);
@@ -54,9 +54,9 @@ describe('CompleteRaceCreationUseCase', () => {
it('should return error if price is missing', async () => {
const state = CheckoutState.ready();
vi.mocked(mockCheckoutService.extractCheckoutInfo).mockResolvedValue(
Result.ok({ price: undefined as any, state })
Result.ok({ price: undefined as any, state, buttonHtml: '<a>n/a</a>' })
);
const result = await useCase.execute('test-session-123');
@@ -78,13 +78,13 @@ describe('CompleteRaceCreationUseCase', () => {
{ input: '$100.50', expected: '$100.50' },
{ input: '$0.99', expected: '$0.99' },
];
for (const testCase of testCases) {
const price = CheckoutPrice.fromString(testCase.input);
const state = CheckoutState.ready();
vi.mocked(mockCheckoutService.extractCheckoutInfo).mockResolvedValue(
Result.ok({ price, state })
Result.ok({ price, state, buttonHtml: `<a>${testCase.input}</a>` })
);
const result = await useCase.execute('test-session');
@@ -99,9 +99,9 @@ describe('CompleteRaceCreationUseCase', () => {
const price = CheckoutPrice.fromString('$25.50');
const state = CheckoutState.ready();
const beforeExecution = new Date();
vi.mocked(mockCheckoutService.extractCheckoutInfo).mockResolvedValue(
Result.ok({ price, state })
Result.ok({ price, state, buttonHtml: '<a>$25.50</a>' })
);
const result = await useCase.execute('test-session');

View File

@@ -23,7 +23,7 @@ describe('ConfirmCheckoutUseCase', () => {
requestCheckoutConfirmation: Mock;
};
let mockPrice: CheckoutPrice;
beforeEach(() => {
mockCheckoutService = {
extractCheckoutInfo: vi.fn(),
@@ -33,12 +33,12 @@ describe('ConfirmCheckoutUseCase', () => {
mockConfirmationPort = {
requestCheckoutConfirmation: vi.fn(),
};
mockPrice = {
getAmount: vi.fn(() => 0.50),
toDisplayString: vi.fn(() => '$0.50'),
isZero: vi.fn(() => false),
};
} as unknown as CheckoutPrice;
});
describe('Success flow', () => {
@@ -230,11 +230,11 @@ describe('ConfirmCheckoutUseCase', () => {
describe('Zero price warning', () => {
it('should still require confirmation for $0.00 price', async () => {
const zeroPriceMock: CheckoutPrice = {
const zeroPriceMock = {
getAmount: vi.fn(() => 0.00),
toDisplayString: vi.fn(() => '$0.00'),
isZero: vi.fn(() => true),
};
} as unknown as CheckoutPrice;
const useCase = new ConfirmCheckoutUseCase(
mockCheckoutService as unknown as ICheckoutService,
@@ -263,11 +263,11 @@ describe('ConfirmCheckoutUseCase', () => {
});
it('should proceed with checkout for zero price after confirmation', async () => {
const zeroPriceMock: CheckoutPrice = {
const zeroPriceMock = {
getAmount: vi.fn(() => 0.00),
toDisplayString: vi.fn(() => '$0.00'),
isZero: vi.fn(() => true),
};
} as unknown as CheckoutPrice;
const useCase = new ConfirmCheckoutUseCase(
mockCheckoutService as unknown as ICheckoutService,

View File

@@ -290,20 +290,4 @@ describe('StartAutomationSessionUseCase', () => {
});
});
describe('execute - step count verification', () => {
it('should verify automation flow has exactly 17 steps (not 18)', async () => {
// This test verifies that step 17 "Race Options" has been completely removed
// Step 17 "Race Options" does not exist in real iRacing and must not be in the code
// The old step 18 (Track Conditions) is now the new step 17 (final step)
// Import the adapter to check its totalSteps property
const { PlaywrightAutomationAdapter } = await import('../../../../packages/infrastructure/adapters/automation/PlaywrightAutomationAdapter');
// Create a temporary adapter instance to check totalSteps
const adapter = new PlaywrightAutomationAdapter({ mode: 'mock' });
// Verify totalSteps is 17 (not 18)
expect((adapter as any).totalSteps).toBe(17);
});
});
});

View File

@@ -85,7 +85,10 @@ describe('VerifyAuthenticatedPageUseCase', () => {
const result = await useCase.execute();
expect(result.isErr()).toBe(true);
expect(result.error.message).toBe('Verification failed');
if (result.isErr()) {
expect(result.error).toBeInstanceOf(Error);
expect(result.error?.message).toBe('Verification failed');
}
});
it('should handle unexpected errors', async () => {
@@ -96,6 +99,9 @@ describe('VerifyAuthenticatedPageUseCase', () => {
const result = await useCase.execute();
expect(result.isErr()).toBe(true);
expect(result.error.message).toBe('Page verification failed: Unexpected error');
if (result.isErr()) {
expect(result.error).toBeInstanceOf(Error);
expect(result.error?.message).toBe('Page verification failed: Unexpected error');
}
});
});