remove companion tests

This commit is contained in:
2026-01-03 15:58:04 +01:00
parent afbe42b0e1
commit e151fe02d0
36 changed files with 10 additions and 5735 deletions

View File

@@ -1,51 +0,0 @@
import { describe, expect, test } from 'vitest'
import { OverlayAction } from 'apps/companion/main/automation/application/ports/IOverlaySyncPort'
import { IAutomationLifecycleEmitter, LifecycleCallback } from '@core/automation/infrastructure//IAutomationLifecycleEmitter'
import { OverlaySyncService } from 'apps/companion/main/automation/application/services/OverlaySyncService'
class MockLifecycleEmitter implements IAutomationLifecycleEmitter {
private callbacks: Set<LifecycleCallback> = new Set()
onLifecycle(cb: LifecycleCallback): void {
this.callbacks.add(cb)
}
offLifecycle(cb: LifecycleCallback): void {
this.callbacks.delete(cb)
}
async emit(event: AutomationEvent) {
for (const cb of Array.from(this.callbacks)) {
// fire without awaiting to simulate async emitter
cb(event)
}
}
}
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,
logger: console as unknown,
publisher: { publish: async () => {} },
})
const action: OverlayAction = { id: 'add-car', label: 'Adding...' }
// start the action but don't emit event yet
const promise = svc.startAction(action)
// wait a small tick to ensure promise hasn't resolved prematurely
await new Promise((r) => setTimeout(r, 10))
let resolved = false
promise.then(() => (resolved = true))
expect(resolved).toBe(false)
// now emit action-started
await emitter.emit({ type: 'action-started', actionId: 'add-car', timestamp: Date.now() })
const ack = await promise
expect(ack.status).toBe('confirmed')
expect(ack.id).toBe('add-car')
})
})

View File

@@ -1,400 +0,0 @@
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import { CheckAuthenticationUseCase } from 'apps/companion/main/automation/application/use-cases/CheckAuthenticationUseCase';
import { AuthenticationState } from 'apps/companion/main/automation/domain/value-objects/AuthenticationState';
import { BrowserAuthenticationState } from 'apps/companion/main/automation/domain/value-objects/BrowserAuthenticationState';
import { Result } from '@core/shared/application/Result';
import type { AuthenticationServicePort } from 'apps/companion/main/automation/application/ports/AuthenticationServicePort';
interface ISessionValidator {
validateSession(): Promise<Result<boolean>>;
}
describe('CheckAuthenticationUseCase', () => {
let mockAuthService: {
checkSession: Mock;
initiateLogin: Mock;
clearSession: Mock;
getState: Mock;
validateServerSide: Mock;
refreshSession: Mock;
getSessionExpiry: Mock;
verifyPageAuthentication: Mock;
};
let mockSessionValidator: {
validateSession: Mock;
};
beforeEach(() => {
mockAuthService = {
checkSession: vi.fn(),
initiateLogin: vi.fn(),
clearSession: vi.fn(),
getState: vi.fn(),
validateServerSide: vi.fn(),
refreshSession: vi.fn(),
getSessionExpiry: vi.fn(),
verifyPageAuthentication: vi.fn(),
};
mockSessionValidator = {
validateSession: vi.fn(),
};
});
describe('File-based validation only', () => {
it('should return AUTHENTICATED when cookies are valid', async () => {
const useCase = new CheckAuthenticationUseCase(
mockAuthService as unknown as AuthenticationServicePort
);
mockAuthService.checkSession.mockResolvedValue(
Result.ok(AuthenticationState.AUTHENTICATED)
);
mockAuthService.getSessionExpiry.mockResolvedValue(
Result.ok(new Date(Date.now() + 3600000))
);
const result = await useCase.execute();
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBe(AuthenticationState.AUTHENTICATED);
expect(mockAuthService.checkSession).toHaveBeenCalledTimes(1);
});
it('should return EXPIRED when cookies are expired', async () => {
const useCase = new CheckAuthenticationUseCase(
mockAuthService as unknown as AuthenticationServicePort
);
mockAuthService.checkSession.mockResolvedValue(
Result.ok(AuthenticationState.EXPIRED)
);
mockAuthService.getSessionExpiry.mockResolvedValue(
Result.ok(new Date(Date.now() - 3600000))
);
const result = await useCase.execute();
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBe(AuthenticationState.EXPIRED);
});
it('should return UNKNOWN when no session exists', async () => {
const useCase = new CheckAuthenticationUseCase(
mockAuthService as unknown as AuthenticationServicePort
);
mockAuthService.checkSession.mockResolvedValue(
Result.ok(AuthenticationState.UNKNOWN)
);
mockAuthService.getSessionExpiry.mockResolvedValue(
Result.ok(null)
);
const result = await useCase.execute();
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBe(AuthenticationState.UNKNOWN);
});
});
describe('Server-side validation enabled', () => {
it('should confirm AUTHENTICATED when file and server both validate', async () => {
const useCase = new CheckAuthenticationUseCase(
mockAuthService as unknown as AuthenticationServicePort,
mockSessionValidator as unknown as ISessionValidator
);
mockAuthService.checkSession.mockResolvedValue(
Result.ok(AuthenticationState.AUTHENTICATED)
);
mockAuthService.getSessionExpiry.mockResolvedValue(
Result.ok(new Date(Date.now() + 3600000))
);
mockSessionValidator.validateSession.mockResolvedValue(
Result.ok(true)
);
const result = await useCase.execute();
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBe(AuthenticationState.AUTHENTICATED);
expect(mockSessionValidator.validateSession).toHaveBeenCalledTimes(1);
});
it('should return EXPIRED when file says valid but server rejects', async () => {
const useCase = new CheckAuthenticationUseCase(
mockAuthService as unknown as AuthenticationServicePort,
mockSessionValidator as unknown as ISessionValidator
);
mockAuthService.checkSession.mockResolvedValue(
Result.ok(AuthenticationState.AUTHENTICATED)
);
mockAuthService.getSessionExpiry.mockResolvedValue(
Result.ok(new Date(Date.now() + 3600000))
);
mockSessionValidator.validateSession.mockResolvedValue(
Result.ok(false)
);
const result = await useCase.execute();
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBe(AuthenticationState.EXPIRED);
});
it('should work without ISessionValidator injected (optional dependency)', async () => {
const useCase = new CheckAuthenticationUseCase(
mockAuthService as unknown as AuthenticationServicePort
);
mockAuthService.checkSession.mockResolvedValue(
Result.ok(AuthenticationState.AUTHENTICATED)
);
mockAuthService.getSessionExpiry.mockResolvedValue(
Result.ok(new Date(Date.now() + 3600000))
);
const result = await useCase.execute();
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBe(AuthenticationState.AUTHENTICATED);
});
});
describe('Error handling', () => {
it('should not block file-based result if server validation fails', async () => {
const useCase = new CheckAuthenticationUseCase(
mockAuthService as unknown as AuthenticationServicePort,
mockSessionValidator as unknown as ISessionValidator
);
mockAuthService.checkSession.mockResolvedValue(
Result.ok(AuthenticationState.AUTHENTICATED)
);
mockAuthService.getSessionExpiry.mockResolvedValue(
Result.ok(new Date(Date.now() + 3600000))
);
mockSessionValidator.validateSession.mockResolvedValue(
Result.err('Network error')
);
const result = await useCase.execute();
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBe(AuthenticationState.AUTHENTICATED);
});
it('should handle authentication service errors gracefully', async () => {
const useCase = new CheckAuthenticationUseCase(
mockAuthService as unknown as AuthenticationServicePort
);
mockAuthService.checkSession.mockResolvedValue(
Result.err('File read error')
);
const result = await useCase.execute();
expect(result.isErr()).toBe(true);
expect(result.unwrapErr()).toContain('File read error');
});
it('should handle session expiry check errors gracefully', async () => {
const useCase = new CheckAuthenticationUseCase(
mockAuthService as unknown as AuthenticationServicePort
);
mockAuthService.checkSession.mockResolvedValue(
Result.ok(AuthenticationState.AUTHENTICATED)
);
mockAuthService.getSessionExpiry.mockResolvedValue(
Result.err('Invalid session format')
);
const result = await useCase.execute();
// Should not block on expiry check errors, return file-based state
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBe(AuthenticationState.AUTHENTICATED);
});
});
describe('Page content verification', () => {
it('should call verifyPageAuthentication when verifyPageContent is true', async () => {
const useCase = new CheckAuthenticationUseCase(
mockAuthService as unknown as AuthenticationServicePort
);
mockAuthService.checkSession.mockResolvedValue(
Result.ok(AuthenticationState.AUTHENTICATED)
);
mockAuthService.getSessionExpiry.mockResolvedValue(
Result.ok(new Date(Date.now() + 3600000))
);
mockAuthService.verifyPageAuthentication = vi.fn().mockResolvedValue(
Result.ok(new BrowserAuthenticationState(true, true))
);
await useCase.execute({ verifyPageContent: true });
expect(mockAuthService.verifyPageAuthentication).toHaveBeenCalledTimes(1);
});
it('should return EXPIRED when cookies valid but page shows login UI', async () => {
const useCase = new CheckAuthenticationUseCase(
mockAuthService as unknown as AuthenticationServicePort
);
mockAuthService.checkSession.mockResolvedValue(
Result.ok(AuthenticationState.AUTHENTICATED)
);
mockAuthService.getSessionExpiry.mockResolvedValue(
Result.ok(new Date(Date.now() + 3600000))
);
mockAuthService.verifyPageAuthentication = vi.fn().mockResolvedValue(
Result.ok(new BrowserAuthenticationState(true, false))
);
const result = await useCase.execute({ verifyPageContent: true });
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBe(AuthenticationState.EXPIRED);
});
it('should return AUTHENTICATED when both cookies AND page authenticated', async () => {
const useCase = new CheckAuthenticationUseCase(
mockAuthService as unknown as AuthenticationServicePort
);
mockAuthService.checkSession.mockResolvedValue(
Result.ok(AuthenticationState.AUTHENTICATED)
);
mockAuthService.getSessionExpiry.mockResolvedValue(
Result.ok(new Date(Date.now() + 3600000))
);
mockAuthService.verifyPageAuthentication = vi.fn().mockResolvedValue(
Result.ok(new BrowserAuthenticationState(true, true))
);
const result = await useCase.execute({ verifyPageContent: true });
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBe(AuthenticationState.AUTHENTICATED);
});
it('should default verifyPageContent to false (backward compatible)', async () => {
const useCase = new CheckAuthenticationUseCase(
mockAuthService as unknown as AuthenticationServicePort
);
mockAuthService.checkSession.mockResolvedValue(
Result.ok(AuthenticationState.AUTHENTICATED)
);
mockAuthService.getSessionExpiry.mockResolvedValue(
Result.ok(new Date(Date.now() + 3600000))
);
mockAuthService.verifyPageAuthentication = vi.fn();
await useCase.execute();
expect(mockAuthService.verifyPageAuthentication).not.toHaveBeenCalled();
});
it('should handle verifyPageAuthentication errors gracefully', async () => {
const useCase = new CheckAuthenticationUseCase(
mockAuthService as unknown as AuthenticationServicePort
);
mockAuthService.checkSession.mockResolvedValue(
Result.ok(AuthenticationState.AUTHENTICATED)
);
mockAuthService.getSessionExpiry.mockResolvedValue(
Result.ok(new Date(Date.now() + 3600000))
);
mockAuthService.verifyPageAuthentication = vi.fn().mockResolvedValue(
Result.err('Page navigation failed')
);
const result = await useCase.execute({ verifyPageContent: true });
// Should not block on page verification errors, return cookie-based state
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBe(AuthenticationState.AUTHENTICATED);
});
});
describe('BDD Scenarios', () => {
it('Given valid session cookies, When checking auth, Then return AUTHENTICATED', async () => {
const useCase = new CheckAuthenticationUseCase(
mockAuthService as unknown as AuthenticationServicePort
);
mockAuthService.checkSession.mockResolvedValue(
Result.ok(AuthenticationState.AUTHENTICATED)
);
mockAuthService.getSessionExpiry.mockResolvedValue(
Result.ok(new Date(Date.now() + 7200000))
);
const result = await useCase.execute();
expect(result.unwrap()).toBe(AuthenticationState.AUTHENTICATED);
});
it('Given expired session cookies, When checking auth, Then return EXPIRED', async () => {
const useCase = new CheckAuthenticationUseCase(
mockAuthService as unknown as AuthenticationServicePort
);
mockAuthService.checkSession.mockResolvedValue(
Result.ok(AuthenticationState.EXPIRED)
);
mockAuthService.getSessionExpiry.mockResolvedValue(
Result.ok(new Date(Date.now() - 1000))
);
const result = await useCase.execute();
expect(result.unwrap()).toBe(AuthenticationState.EXPIRED);
});
it('Given no session file, When checking auth, Then return UNKNOWN', async () => {
const useCase = new CheckAuthenticationUseCase(
mockAuthService as unknown as AuthenticationServicePort
);
mockAuthService.checkSession.mockResolvedValue(
Result.ok(AuthenticationState.UNKNOWN)
);
mockAuthService.getSessionExpiry.mockResolvedValue(
Result.ok(null)
);
const result = await useCase.execute();
expect(result.unwrap()).toBe(AuthenticationState.UNKNOWN);
});
it('Given valid cookies but page shows login, When verifying page content, Then return EXPIRED', async () => {
const useCase = new CheckAuthenticationUseCase(
mockAuthService as unknown as AuthenticationServicePort
);
mockAuthService.checkSession.mockResolvedValue(
Result.ok(AuthenticationState.AUTHENTICATED)
);
mockAuthService.getSessionExpiry.mockResolvedValue(
Result.ok(new Date(Date.now() + 3600000))
);
mockAuthService.verifyPageAuthentication = vi.fn().mockResolvedValue(
Result.ok(new BrowserAuthenticationState(true, false))
);
const result = await useCase.execute({ verifyPageContent: true });
expect(result.unwrap()).toBe(AuthenticationState.EXPIRED);
});
});
});

View File

@@ -1,106 +0,0 @@
import { vi, Mock } from 'vitest';
import { ClearSessionUseCase } from './ClearSessionUseCase';
import type { AuthenticationServicePort } from '../ports/AuthenticationServicePort';
import type { Logger } from '@core/shared/application';
import { Result } from '@core/shared/application/Result';
describe('ClearSessionUseCase', () => {
let useCase: ClearSessionUseCase;
let authService: AuthenticationServicePort;
let logger: Logger;
beforeEach(() => {
const mockAuthService = {
clearSession: vi.fn(),
checkSession: vi.fn(),
initiateLogin: vi.fn(),
getState: vi.fn(),
validateServerSide: vi.fn(),
refreshSession: vi.fn(),
getSessionExpiry: vi.fn(),
verifyPageAuthentication: vi.fn(),
};
const mockLogger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
authService = mockAuthService as unknown as AuthenticationServicePort;
logger = mockLogger as Logger;
useCase = new ClearSessionUseCase(authService, logger);
});
describe('execute', () => {
it('should clear session successfully and return ok result', async () => {
const successResult = Result.ok<void>(undefined);
(authService.clearSession as Mock).mockResolvedValue(successResult);
const result = await useCase.execute();
expect(authService.clearSession).toHaveBeenCalledTimes(1);
expect(logger.debug).toHaveBeenCalledWith('Attempting to clear user session.', {
useCase: 'ClearSessionUseCase'
});
expect(logger.info).toHaveBeenCalledWith('User session cleared successfully.', {
useCase: 'ClearSessionUseCase'
});
expect(result.isOk()).toBe(true);
});
it('should handle clearSession failure and return err result', async () => {
const error = new Error('Clear session failed');
const failureResult = Result.err<void>(error);
(authService.clearSession as Mock).mockResolvedValue(failureResult);
const result = await useCase.execute();
expect(authService.clearSession).toHaveBeenCalledTimes(1);
expect(logger.debug).toHaveBeenCalledWith('Attempting to clear user session.', {
useCase: 'ClearSessionUseCase'
});
expect(logger.warn).toHaveBeenCalledWith('Failed to clear user session.', {
useCase: 'ClearSessionUseCase',
error: error,
});
expect(result.isErr()).toBe(true);
expect(result.error).toBe(error);
});
it('should handle unexpected errors and return err result with Error', async () => {
const thrownError = new Error('Unexpected error');
(authService.clearSession as Mock).mockRejectedValue(thrownError);
const result = await useCase.execute();
expect(authService.clearSession).toHaveBeenCalledTimes(1);
expect(logger.debug).toHaveBeenCalledWith('Attempting to clear user session.', {
useCase: 'ClearSessionUseCase'
});
expect(logger.error).toHaveBeenCalledWith('Error clearing user session.', thrownError, {
useCase: 'ClearSessionUseCase'
});
expect(result.isErr()).toBe(true);
expect(result.error).toBeInstanceOf(Error);
expect(result.error?.message).toBe('Unexpected error');
});
it('should handle non-Error thrown values and convert to Error', async () => {
const thrownValue = 'String error';
(authService.clearSession as Mock).mockRejectedValue(thrownValue);
const result = await useCase.execute();
expect(authService.clearSession).toHaveBeenCalledTimes(1);
expect(logger.error).toHaveBeenCalledWith('Error clearing user session.', expect.any(Error), {
useCase: 'ClearSessionUseCase'
});
expect(result.isErr()).toBe(true);
expect(result.error).toBeInstanceOf(Error);
expect(result.error?.message).toBe('String error');
});
});
});

View File

@@ -1,121 +0,0 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { CompleteRaceCreationUseCase } from 'apps/companion/main/automation/application/use-cases/CompleteRaceCreationUseCase';
import { Result } from '@core/shared/application/Result';
import { RaceCreationResult } from 'apps/companion/main/automation/domain/value-objects/RaceCreationResult';
import { CheckoutPrice } from 'apps/companion/main/automation/domain/value-objects/CheckoutPrice';
import type { CheckoutServicePort } from 'apps/companion/main/automation/application/ports/CheckoutServicePort';
import { CheckoutState } from 'apps/companion/main/automation/domain/value-objects/CheckoutState';
describe('CompleteRaceCreationUseCase', () => {
let mockCheckoutService: CheckoutServicePort;
let useCase: CompleteRaceCreationUseCase;
beforeEach(() => {
mockCheckoutService = {
extractCheckoutInfo: vi.fn(),
proceedWithCheckout: vi.fn(),
};
useCase = new CompleteRaceCreationUseCase(mockCheckoutService);
});
describe('execute', () => {
it('should extract checkout price and create RaceCreationResult', async () => {
const price = CheckoutPrice.fromString('$25.50');
const state = CheckoutState.ready();
const sessionId = 'test-session-123';
vi.mocked(mockCheckoutService.extractCheckoutInfo).mockResolvedValue(
Result.ok({ price, state, buttonHtml: '<a>$25.50</a>' })
);
const result = await useCase.execute(sessionId);
expect(mockCheckoutService.extractCheckoutInfo).toHaveBeenCalled();
expect(result.isOk()).toBe(true);
const raceCreationResult = result.unwrap();
expect(raceCreationResult).toBeInstanceOf(RaceCreationResult);
expect(raceCreationResult.sessionId).toBe(sessionId);
expect(raceCreationResult.price).toBe('$25.50');
expect(raceCreationResult.timestamp).toBeInstanceOf(Date);
});
it('should return error if checkout info extraction fails', async () => {
vi.mocked(mockCheckoutService.extractCheckoutInfo).mockResolvedValue(
Result.err(new Error('Failed to extract checkout info'))
);
const result = await useCase.execute('test-session-123');
expect(result.isErr()).toBe(true);
expect(result.unwrapErr().message).toContain('Failed to extract checkout info');
});
it('should return error if price is missing', async () => {
const state = CheckoutState.ready();
vi.mocked(mockCheckoutService.extractCheckoutInfo).mockResolvedValue(
Result.ok({ price: null, state, buttonHtml: '<a>n/a</a>' })
);
const result = await useCase.execute('test-session-123');
expect(result.isErr()).toBe(true);
expect(result.unwrapErr().message).toContain('Could not extract price');
});
it('should validate session ID is provided', async () => {
const result = await useCase.execute('');
expect(result.isErr()).toBe(true);
expect(result.unwrapErr().message).toContain('Session ID is required');
});
it('should format different price values correctly', async () => {
const testCases = [
{ input: '$10.00', expected: '$10.00' },
{ 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, buttonHtml: `<a>${testCase.input}</a>` })
);
const result = await useCase.execute('test-session');
expect(result.isOk()).toBe(true);
const raceCreationResult = result.unwrap();
expect(raceCreationResult.price).toBe(testCase.expected);
}
});
it('should capture current timestamp when creating result', async () => {
const price = CheckoutPrice.fromString('$25.50');
const state = CheckoutState.ready();
const beforeExecution = new Date();
vi.mocked(mockCheckoutService.extractCheckoutInfo).mockResolvedValue(
Result.ok({ price, state, buttonHtml: '<a>$25.50</a>' })
);
const result = await useCase.execute('test-session');
const afterExecution = new Date();
expect(result.isOk()).toBe(true);
const raceCreationResult = result.unwrap();
expect(raceCreationResult.timestamp.getTime()).toBeGreaterThanOrEqual(
beforeExecution.getTime()
);
expect(raceCreationResult.timestamp.getTime()).toBeLessThanOrEqual(
afterExecution.getTime()
);
});
});
});

View File

@@ -1,431 +0,0 @@
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import { Result } from '@core/shared/application/Result';
import { ConfirmCheckoutUseCase } from 'apps/companion/main/automation/application/use-cases/ConfirmCheckoutUseCase';
import type { CheckoutServicePort } from 'apps/companion/main/automation/application/ports/CheckoutServicePort';
import type { CheckoutConfirmationPort } from 'apps/companion/main/automation/application/ports/CheckoutConfirmationPort';
import { CheckoutPrice } from 'apps/companion/main/automation/domain/value-objects/CheckoutPrice';
import { CheckoutState } from 'apps/companion/main/automation/domain/value-objects/CheckoutState';
import { CheckoutConfirmation } from 'apps/companion/main/automation/domain/value-objects/CheckoutConfirmation';
import type { Logger } from '@core/shared/application';
/**
* ConfirmCheckoutUseCase - GREEN PHASE
*
* Tests for checkout confirmation flow including price extraction,
* insufficient funds detection, and user confirmation.
*/
describe('ConfirmCheckoutUseCase', () => {
let mockCheckoutService: {
extractCheckoutInfo: Mock;
proceedWithCheckout: Mock;
};
let mockConfirmationPort: {
requestCheckoutConfirmation: Mock;
};
let mockLogger: Logger;
let mockPrice: CheckoutPrice;
beforeEach(() => {
mockCheckoutService = {
extractCheckoutInfo: vi.fn(),
proceedWithCheckout: vi.fn(),
};
mockConfirmationPort = {
requestCheckoutConfirmation: vi.fn(),
};
mockLogger = {
debug: vi.fn(),
info: vi.fn(),
error: vi.fn(),
warn: vi.fn(),
fatal: vi.fn(),
child: vi.fn(() => mockLogger),
flush: vi.fn(),
} as Logger;
mockPrice = {
getAmount: vi.fn(() => 0.50),
toDisplayString: vi.fn(() => '$0.50'),
isZero: vi.fn(() => false),
} as unknown as CheckoutPrice;
});
describe('Success flow', () => {
it('should extract price, get user confirmation, and proceed with checkout', async () => {
const useCase = new ConfirmCheckoutUseCase(
mockCheckoutService as unknown as CheckoutServicePort,
mockConfirmationPort as unknown as CheckoutConfirmationPort,
mockLogger
);
mockCheckoutService.extractCheckoutInfo.mockResolvedValue(
Result.ok({
price: mockPrice,
state: CheckoutState.ready(),
buttonHtml: '<a class="btn btn-success"><span>$0.50</span></a>',
})
);
mockConfirmationPort.requestCheckoutConfirmation.mockResolvedValue(
Result.ok(CheckoutConfirmation.create('confirmed'))
);
mockCheckoutService.proceedWithCheckout.mockResolvedValue(Result.ok(undefined));
const result = await useCase.execute();
expect(result.isOk()).toBe(true);
expect(mockCheckoutService.extractCheckoutInfo).toHaveBeenCalledTimes(1);
expect(mockConfirmationPort.requestCheckoutConfirmation).toHaveBeenCalledTimes(1);
expect(mockConfirmationPort.requestCheckoutConfirmation).toHaveBeenCalledWith(
expect.objectContaining({ price: mockPrice })
);
expect(mockCheckoutService.proceedWithCheckout).toHaveBeenCalledTimes(1);
});
it('should include price in confirmation message', async () => {
const useCase = new ConfirmCheckoutUseCase(
mockCheckoutService as unknown as CheckoutServicePort,
mockConfirmationPort as unknown as CheckoutConfirmationPort,
mockLogger
);
mockCheckoutService.extractCheckoutInfo.mockResolvedValue(
Result.ok({
price: mockPrice,
state: CheckoutState.ready(),
buttonHtml: '<a class="btn btn-success"><span>$0.50</span></a>',
})
);
mockConfirmationPort.requestCheckoutConfirmation.mockResolvedValue(
Result.ok(CheckoutConfirmation.create('confirmed'))
);
mockCheckoutService.proceedWithCheckout.mockResolvedValue(Result.ok(undefined));
await useCase.execute();
expect(mockConfirmationPort.requestCheckoutConfirmation).toHaveBeenCalledWith(
expect.objectContaining({ price: mockPrice })
);
});
});
describe('User cancellation', () => {
it('should abort checkout when user cancels confirmation', async () => {
const useCase = new ConfirmCheckoutUseCase(
mockCheckoutService as unknown as CheckoutServicePort,
mockConfirmationPort as unknown as CheckoutConfirmationPort,
mockLogger
);
mockCheckoutService.extractCheckoutInfo.mockResolvedValue(
Result.ok({
price: mockPrice,
state: CheckoutState.ready(),
buttonHtml: '<a class="btn btn-success"><span>$0.50</span></a>',
})
);
mockConfirmationPort.requestCheckoutConfirmation.mockResolvedValue(
Result.ok(CheckoutConfirmation.create('cancelled'))
);
const result = await useCase.execute();
expect(result.isErr()).toBe(true);
expect(result.unwrapErr().message).toMatch(/cancel/i);
expect(mockCheckoutService.proceedWithCheckout).not.toHaveBeenCalled();
});
it('should not proceed with checkout after cancellation', async () => {
const useCase = new ConfirmCheckoutUseCase(
mockCheckoutService as unknown as CheckoutServicePort,
mockConfirmationPort as unknown as CheckoutConfirmationPort,
mockLogger
);
mockCheckoutService.extractCheckoutInfo.mockResolvedValue(
Result.ok({
price: mockPrice,
state: CheckoutState.ready(),
buttonHtml: '<a class="btn btn-success"><span>$0.50</span></a>',
})
);
mockConfirmationPort.requestCheckoutConfirmation.mockResolvedValue(
Result.ok(CheckoutConfirmation.create('cancelled'))
);
await useCase.execute();
expect(mockCheckoutService.proceedWithCheckout).toHaveBeenCalledTimes(0);
});
});
describe('Insufficient funds detection', () => {
it('should return error when checkout state is INSUFFICIENT_FUNDS', async () => {
const useCase = new ConfirmCheckoutUseCase(
mockCheckoutService as unknown as CheckoutServicePort,
mockConfirmationPort as unknown as CheckoutConfirmationPort,
mockLogger
);
mockCheckoutService.extractCheckoutInfo.mockResolvedValue(
Result.ok({
price: mockPrice,
state: CheckoutState.insufficientFunds(),
buttonHtml: '<a class="btn btn-default"><span>$0.50</span></a>',
})
);
const result = await useCase.execute();
expect(result.isErr()).toBe(true);
expect(result.unwrapErr().message).toMatch(/insufficient.*funds/i);
expect(mockConfirmationPort.requestCheckoutConfirmation).not.toHaveBeenCalled();
expect(mockCheckoutService.proceedWithCheckout).not.toHaveBeenCalled();
});
it('should not ask for confirmation when funds are insufficient', async () => {
const useCase = new ConfirmCheckoutUseCase(
mockCheckoutService as unknown as CheckoutServicePort,
mockConfirmationPort as unknown as CheckoutConfirmationPort,
mockLogger
);
mockCheckoutService.extractCheckoutInfo.mockResolvedValue(
Result.ok({
price: mockPrice,
state: CheckoutState.insufficientFunds(),
buttonHtml: '<a class="btn btn-default"><span>$0.50</span></a>',
})
);
await useCase.execute();
expect(mockConfirmationPort.requestCheckoutConfirmation).toHaveBeenCalledTimes(0);
});
});
describe('Price extraction failure', () => {
it('should return error when price cannot be extracted', async () => {
const useCase = new ConfirmCheckoutUseCase(
mockCheckoutService as unknown as CheckoutServicePort,
mockConfirmationPort as unknown as CheckoutConfirmationPort,
mockLogger
);
mockCheckoutService.extractCheckoutInfo.mockResolvedValue(
Result.ok({
price: null,
state: CheckoutState.unknown(),
buttonHtml: '',
})
);
const result = await useCase.execute();
expect(result.isErr()).toBe(true);
expect(result.unwrapErr().message).toMatch(/extract|price|not found/i);
expect(mockConfirmationPort.requestCheckoutConfirmation).not.toHaveBeenCalled();
expect(mockCheckoutService.proceedWithCheckout).not.toHaveBeenCalled();
});
it('should return error when extraction service fails', async () => {
const useCase = new ConfirmCheckoutUseCase(
mockCheckoutService as unknown as CheckoutServicePort,
mockConfirmationPort as unknown as CheckoutConfirmationPort,
mockLogger
);
mockCheckoutService.extractCheckoutInfo.mockResolvedValue(
Result.err('Button not found')
);
const result = await useCase.execute();
expect(result.isErr()).toBe(true);
expect(mockConfirmationPort.requestCheckoutConfirmation).not.toHaveBeenCalled();
});
});
describe('Zero price warning', () => {
it('should still require confirmation for $0.00 price', async () => {
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 CheckoutServicePort,
mockConfirmationPort as unknown as CheckoutConfirmationPort,
mockLogger
);
mockCheckoutService.extractCheckoutInfo.mockResolvedValue(
Result.ok({
price: zeroPriceMock,
state: CheckoutState.ready(),
buttonHtml: '<a class="btn btn-success"><span>$0.00</span></a>',
})
);
mockConfirmationPort.requestCheckoutConfirmation.mockResolvedValue(
Result.ok(CheckoutConfirmation.create('confirmed'))
);
mockCheckoutService.proceedWithCheckout.mockResolvedValue(Result.ok(undefined));
const result = await useCase.execute();
expect(result.isOk()).toBe(true);
expect(mockConfirmationPort.requestCheckoutConfirmation).toHaveBeenCalledTimes(1);
expect(mockConfirmationPort.requestCheckoutConfirmation).toHaveBeenCalledWith(
expect.objectContaining({ price: zeroPriceMock })
);
});
it('should proceed with checkout for zero price after confirmation', async () => {
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 CheckoutServicePort,
mockConfirmationPort as unknown as CheckoutConfirmationPort,
mockLogger
);
mockCheckoutService.extractCheckoutInfo.mockResolvedValue(
Result.ok({
price: zeroPriceMock,
state: CheckoutState.ready(),
buttonHtml: '<a class="btn btn-success"><span>$0.00</span></a>',
})
);
mockConfirmationPort.requestCheckoutConfirmation.mockResolvedValue(
Result.ok(CheckoutConfirmation.create('confirmed'))
);
mockCheckoutService.proceedWithCheckout.mockResolvedValue(Result.ok(undefined));
await useCase.execute();
expect(mockCheckoutService.proceedWithCheckout).toHaveBeenCalledTimes(1);
});
});
describe('Checkout execution failure', () => {
it('should return error when proceedWithCheckout fails', async () => {
const useCase = new ConfirmCheckoutUseCase(
mockCheckoutService as unknown as CheckoutServicePort,
mockConfirmationPort as unknown as CheckoutConfirmationPort,
mockLogger
);
mockCheckoutService.extractCheckoutInfo.mockResolvedValue(
Result.ok({
price: mockPrice,
state: CheckoutState.ready(),
buttonHtml: '<a class="btn btn-success"><span>$0.50</span></a>',
})
);
mockConfirmationPort.requestCheckoutConfirmation.mockResolvedValue(
Result.ok(CheckoutConfirmation.create('confirmed'))
);
mockCheckoutService.proceedWithCheckout.mockResolvedValue(
Result.err('Network error')
);
const result = await useCase.execute();
expect(result.isErr()).toBe(true);
expect(result.unwrapErr()).toContain('Network error');
});
});
describe('BDD Scenarios', () => {
it('Given checkout price $0.50 and READY state, When user confirms, Then checkout proceeds', async () => {
const useCase = new ConfirmCheckoutUseCase(
mockCheckoutService as unknown as CheckoutServicePort,
mockConfirmationPort as unknown as CheckoutConfirmationPort,
mockLogger
);
mockCheckoutService.extractCheckoutInfo.mockResolvedValue(
Result.ok({
price: mockPrice,
state: CheckoutState.ready(),
buttonHtml: '<a class="btn btn-success"><span>$0.50</span></a>',
})
);
mockConfirmationPort.requestCheckoutConfirmation.mockResolvedValue(
Result.ok(CheckoutConfirmation.create('confirmed'))
);
mockCheckoutService.proceedWithCheckout.mockResolvedValue(Result.ok(undefined));
const result = await useCase.execute();
expect(result.isOk()).toBe(true);
});
it('Given checkout price $0.50, When user cancels, Then checkout is aborted', async () => {
const useCase = new ConfirmCheckoutUseCase(
mockCheckoutService as unknown as CheckoutServicePort,
mockConfirmationPort as unknown as CheckoutConfirmationPort,
mockLogger
);
mockCheckoutService.extractCheckoutInfo.mockResolvedValue(
Result.ok({
price: mockPrice,
state: CheckoutState.ready(),
buttonHtml: '<a class="btn btn-success"><span>$0.50</span></a>',
})
);
mockConfirmationPort.requestCheckoutConfirmation.mockResolvedValue(
Result.ok(CheckoutConfirmation.create('cancelled'))
);
const result = await useCase.execute();
expect(result.isErr()).toBe(true);
expect(mockCheckoutService.proceedWithCheckout).not.toHaveBeenCalled();
});
it('Given INSUFFICIENT_FUNDS state, When executing, Then error is returned', async () => {
const useCase = new ConfirmCheckoutUseCase(
mockCheckoutService as unknown as CheckoutServicePort,
mockConfirmationPort as unknown as CheckoutConfirmationPort,
mockLogger
);
mockCheckoutService.extractCheckoutInfo.mockResolvedValue(
Result.ok({
price: mockPrice,
state: CheckoutState.insufficientFunds(),
buttonHtml: '<a class="btn btn-default"><span>$0.50</span></a>',
})
);
const result = await useCase.execute();
expect(result.isErr()).toBe(true);
});
it('Given price extraction failure, When executing, Then error is returned', async () => {
const useCase = new ConfirmCheckoutUseCase(
mockCheckoutService as unknown as CheckoutServicePort,
mockConfirmationPort as unknown as CheckoutConfirmationPort,
mockLogger
);
mockCheckoutService.extractCheckoutInfo.mockResolvedValue(
Result.err('Button not found')
);
const result = await useCase.execute();
expect(result.isErr()).toBe(true);
});
});
});

View File

@@ -1,308 +0,0 @@
import { describe, it, expect, vi, beforeEach, Mock } from 'vitest';
import { StartAutomationSessionUseCase } from 'apps/companion/main/automation/application/use-cases/StartAutomationSessionUseCase';
import { AutomationEnginePort as IAutomationEngine } from 'apps/companion/main/automation/application/ports/AutomationEnginePort';
import { IBrowserAutomation as IScreenAutomation } from 'apps/companion/main/automation/application/ports/ScreenAutomationPort';
import { SessionRepositoryPort as ISessionRepository } from 'apps/companion/main/automation/application/ports/SessionRepositoryPort';
import type { Logger } from '@core/shared/application';
import { AutomationSession } from 'apps/companion/main/automation/domain/entities/AutomationSession';
describe('StartAutomationSessionUseCase', () => {
let mockAutomationEngine: {
executeStep: Mock;
validateConfiguration: Mock;
};
let mockBrowserAutomation: {
navigateToPage: Mock;
fillFormField: Mock;
clickElement: Mock;
waitForElement: Mock;
handleModal: Mock;
};
let mockSessionRepository: {
save: Mock;
findById: Mock;
update: Mock;
delete: Mock;
};
let mockLogger: {
debug: Mock;
info: Mock;
warn: Mock;
error: Mock;
};
let useCase: StartAutomationSessionUseCase;
beforeEach(() => {
mockAutomationEngine = {
executeStep: vi.fn(),
validateConfiguration: vi.fn(),
};
mockBrowserAutomation = {
navigateToPage: vi.fn(),
fillFormField: vi.fn(),
clickElement: vi.fn(),
waitForElement: vi.fn(),
handleModal: vi.fn(),
};
mockSessionRepository = {
save: vi.fn(),
findById: vi.fn(),
update: vi.fn(),
delete: vi.fn(),
};
mockLogger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
useCase = new StartAutomationSessionUseCase(
mockAutomationEngine as unknown as IAutomationEngine,
mockBrowserAutomation as unknown as IScreenAutomation,
mockSessionRepository as unknown as ISessionRepository,
mockLogger as unknown as Logger
);
});
describe('execute - happy path', () => {
it('should create and persist a new automation session', async () => {
const config = {
sessionName: 'Test Race Session',
trackId: 'spa',
carIds: ['dallara-f3'],
};
mockAutomationEngine.validateConfiguration.mockResolvedValue({ isValid: true });
mockSessionRepository.save.mockResolvedValue(undefined);
const result = await useCase.execute(config);
expect(result.sessionId).toBeDefined();
expect(result.state).toBe('PENDING');
expect(result.currentStep).toBe(1);
expect(mockAutomationEngine.validateConfiguration).toHaveBeenCalledWith(config);
expect(mockSessionRepository.save).toHaveBeenCalledWith(
expect.objectContaining({
config,
_currentStep: expect.objectContaining({ value: 1 }),
})
);
});
it('should return session DTO with correct structure', async () => {
const config = {
sessionName: 'Test Race Session',
trackId: 'spa',
carIds: ['dallara-f3'],
};
mockAutomationEngine.validateConfiguration.mockResolvedValue({ isValid: true });
mockSessionRepository.save.mockResolvedValue(undefined);
const result = await useCase.execute(config);
expect(result).toMatchObject({
sessionId: expect.any(String),
state: 'PENDING',
currentStep: 1,
config: {
sessionName: 'Test Race Session',
trackId: 'spa',
carIds: ['dallara-f3'],
},
});
expect(result.startedAt).toBeUndefined();
expect(result.completedAt).toBeUndefined();
expect(result.errorMessage).toBeUndefined();
});
it('should validate configuration before creating session', async () => {
const config = {
sessionName: 'Test Race Session',
trackId: 'spa',
carIds: ['dallara-f3'],
};
mockAutomationEngine.validateConfiguration.mockResolvedValue({ isValid: true });
mockSessionRepository.save.mockResolvedValue(undefined);
await useCase.execute(config);
expect(mockAutomationEngine.validateConfiguration).toHaveBeenCalledWith(config);
expect(mockSessionRepository.save).toHaveBeenCalled();
});
});
describe('execute - validation failures', () => {
it('should throw error for empty session name', async () => {
const config = {
sessionName: '',
trackId: 'spa',
carIds: ['dallara-f3'],
};
await expect(useCase.execute(config)).rejects.toThrow('Session name cannot be empty');
expect(mockSessionRepository.save).not.toHaveBeenCalled();
});
it('should throw error for missing track ID', async () => {
const config = {
sessionName: 'Test Race',
trackId: '',
carIds: ['dallara-f3'],
};
await expect(useCase.execute(config)).rejects.toThrow('Track ID is required');
expect(mockSessionRepository.save).not.toHaveBeenCalled();
});
it('should throw error for empty car list', async () => {
const config = {
sessionName: 'Test Race',
trackId: 'spa',
carIds: [],
};
await expect(useCase.execute(config)).rejects.toThrow('At least one car must be selected');
expect(mockSessionRepository.save).not.toHaveBeenCalled();
});
it('should throw error when automation engine validation fails', async () => {
const config = {
sessionName: 'Test Race',
trackId: 'invalid-track',
carIds: ['dallara-f3'],
};
mockAutomationEngine.validateConfiguration.mockResolvedValue({
isValid: false,
error: 'Invalid track ID: invalid-track',
});
await expect(useCase.execute(config)).rejects.toThrow('Invalid track ID: invalid-track');
expect(mockSessionRepository.save).not.toHaveBeenCalled();
});
it('should throw error when automation engine validation rejects', async () => {
const config = {
sessionName: 'Test Race',
trackId: 'spa',
carIds: ['invalid-car'],
};
mockAutomationEngine.validateConfiguration.mockRejectedValue(
new Error('Validation service unavailable')
);
await expect(useCase.execute(config)).rejects.toThrow('Validation service unavailable');
expect(mockSessionRepository.save).not.toHaveBeenCalled();
});
});
describe('execute - port interactions', () => {
it('should call automation engine before saving session', async () => {
const config = {
sessionName: 'Test Race',
trackId: 'spa',
carIds: ['dallara-f3'],
};
const callOrder: string[] = [];
mockAutomationEngine.validateConfiguration.mockImplementation(async () => {
callOrder.push('validateConfiguration');
return { isValid: true };
});
mockSessionRepository.save.mockImplementation(async () => {
callOrder.push('save');
});
await useCase.execute(config);
expect(callOrder).toEqual(['validateConfiguration', 'save']);
});
it('should persist session with domain entity', async () => {
const config = {
sessionName: 'Test Race',
trackId: 'spa',
carIds: ['dallara-f3'],
};
mockAutomationEngine.validateConfiguration.mockResolvedValue({ isValid: true });
mockSessionRepository.save.mockResolvedValue(undefined);
await useCase.execute(config);
expect(mockSessionRepository.save).toHaveBeenCalledWith(
expect.any(AutomationSession)
);
});
it('should throw error when repository save fails', async () => {
const config = {
sessionName: 'Test Race',
trackId: 'spa',
carIds: ['dallara-f3'],
};
mockAutomationEngine.validateConfiguration.mockResolvedValue({ isValid: true });
mockSessionRepository.save.mockRejectedValue(new Error('Database connection failed'));
await expect(useCase.execute(config)).rejects.toThrow('Database connection failed');
});
});
describe('execute - edge cases', () => {
it('should handle very long session names', async () => {
const config = {
sessionName: 'A'.repeat(200),
trackId: 'spa',
carIds: ['dallara-f3'],
};
mockAutomationEngine.validateConfiguration.mockResolvedValue({ isValid: true });
mockSessionRepository.save.mockResolvedValue(undefined);
const result = await useCase.execute(config);
expect(result.config.sessionName).toBe('A'.repeat(200));
});
it('should handle multiple cars in configuration', async () => {
const config = {
sessionName: 'Multi-car Race',
trackId: 'spa',
carIds: ['dallara-f3', 'porsche-911-gt3', 'bmw-m4-gt4'],
};
mockAutomationEngine.validateConfiguration.mockResolvedValue({ isValid: true });
mockSessionRepository.save.mockResolvedValue(undefined);
const result = await useCase.execute(config);
expect(result.config.carIds).toEqual(['dallara-f3', 'porsche-911-gt3', 'bmw-m4-gt4']);
});
it('should handle special characters in session name', async () => {
const config = {
sessionName: 'Test & Race #1 (2025)',
trackId: 'spa',
carIds: ['dallara-f3'],
};
mockAutomationEngine.validateConfiguration.mockResolvedValue({ isValid: true });
mockSessionRepository.save.mockResolvedValue(undefined);
const result = await useCase.execute(config);
expect(result.config.sessionName).toBe('Test & Race #1 (2025)');
});
});
});

View File

@@ -1,107 +0,0 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { VerifyAuthenticatedPageUseCase } from 'apps/companion/main/automation/application/use-cases/VerifyAuthenticatedPageUseCase';
import { AuthenticationServicePort as IAuthenticationService } from 'apps/companion/main/automation/application/ports/AuthenticationServicePort';
import { Result } from '@core/shared/application/Result';
import { BrowserAuthenticationState } from 'apps/companion/main/automation/domain/value-objects/BrowserAuthenticationState';
import { AuthenticationState } from 'apps/companion/main/automation/domain/value-objects/AuthenticationState';
describe('VerifyAuthenticatedPageUseCase', () => {
let useCase: VerifyAuthenticatedPageUseCase;
let mockAuthService: {
checkSession: ReturnType<typeof vi.fn>;
verifyPageAuthentication: ReturnType<typeof vi.fn>;
initiateLogin: ReturnType<typeof vi.fn>;
clearSession: ReturnType<typeof vi.fn>;
getState: ReturnType<typeof vi.fn>;
validateServerSide: ReturnType<typeof vi.fn>;
refreshSession: ReturnType<typeof vi.fn>;
getSessionExpiry: ReturnType<typeof vi.fn>;
};
beforeEach(() => {
mockAuthService = {
checkSession: vi.fn(),
verifyPageAuthentication: vi.fn(),
initiateLogin: vi.fn(),
clearSession: vi.fn(),
getState: vi.fn(),
validateServerSide: vi.fn(),
refreshSession: vi.fn(),
getSessionExpiry: vi.fn(),
};
useCase = new VerifyAuthenticatedPageUseCase(
mockAuthService as unknown as IAuthenticationService
);
});
it('should return fully authenticated browser state', async () => {
const mockBrowserState = new BrowserAuthenticationState(true, true);
mockAuthService.verifyPageAuthentication.mockResolvedValue(
Result.ok(mockBrowserState)
);
const result = await useCase.execute();
expect(result.isOk()).toBe(true);
const browserState = result.unwrap();
expect(browserState.isFullyAuthenticated()).toBe(true);
expect(browserState.getAuthenticationState()).toBe(AuthenticationState.AUTHENTICATED);
});
it('should return unauthenticated state when page not authenticated', async () => {
const mockBrowserState = new BrowserAuthenticationState(true, false);
mockAuthService.verifyPageAuthentication.mockResolvedValue(
Result.ok(mockBrowserState)
);
const result = await useCase.execute();
expect(result.isOk()).toBe(true);
const browserState = result.unwrap();
expect(browserState.isFullyAuthenticated()).toBe(false);
expect(browserState.getAuthenticationState()).toBe(AuthenticationState.EXPIRED);
});
it('should return requires reauth state when cookies invalid', async () => {
const mockBrowserState = new BrowserAuthenticationState(false, false);
mockAuthService.verifyPageAuthentication.mockResolvedValue(
Result.ok(mockBrowserState)
);
const result = await useCase.execute();
expect(result.isOk()).toBe(true);
const browserState = result.unwrap();
expect(browserState.requiresReauthentication()).toBe(true);
expect(browserState.getAuthenticationState()).toBe(AuthenticationState.UNKNOWN);
});
it('should propagate errors from verifyPageAuthentication', async () => {
const error = new Error('Verification failed');
mockAuthService.verifyPageAuthentication.mockResolvedValue(
Result.err(error)
);
const result = await useCase.execute();
expect(result.isErr()).toBe(true);
if (result.isErr()) {
expect(result.error).toBeInstanceOf(Error);
expect(result.error?.message).toBe('Verification failed');
}
});
it('should handle unexpected errors', async () => {
mockAuthService.verifyPageAuthentication.mockRejectedValue(
new Error('Unexpected error')
);
const result = await useCase.execute();
expect(result.isErr()).toBe(true);
if (result.isErr()) {
expect(result.error).toBeInstanceOf(Error);
expect(result.error?.message).toBe('Page verification failed: Unexpected error');
}
});
});