This commit is contained in:
2025-12-16 13:53:23 +01:00
parent 84f05598a6
commit 29dc11deb9
127 changed files with 538 additions and 547 deletions

View File

@@ -7,9 +7,9 @@
import type { AsyncUseCase } from '@core/shared/application';
import type { Logger } from '@core/shared/application';
import type { IPageViewRepository } from '../../domain/repositories/IPageViewRepository';
import type { IEngagementRepository } from '../../domain/repositories/IEngagementRepository';
import type { IAnalyticsSnapshotRepository } from '../../domain/repositories/IAnalyticsSnapshotRepository';
import type { IPageViewRepository } from '../repositories/IPageViewRepository';
import type { IEngagementRepository } from '@core/analytics/domain/repositories/IEngagementRepository';
import type { IAnalyticsSnapshotRepository } from '@core/analytics/domain/repositories/IAnalyticsSnapshotRepository';
import type { EntityType } from '../../domain/types/PageView';
import type { SnapshotPeriod } from '../../domain/types/AnalyticsSnapshot';
@@ -69,7 +69,8 @@ export class GetEntityAnalyticsQuery
);
this.logger.debug(`Total page views for entity ${input.entityId}: ${totalPageViews}`);
} catch (error) {
this.logger.error(`Error counting total page views for entity ${input.entityId}: ${error.message}`);
const err = error instanceof Error ? error : new Error(String(error));
this.logger.error(`Error counting total page views for entity ${input.entityId}: ${err.message}`);
throw error;
}
@@ -82,7 +83,8 @@ export class GetEntityAnalyticsQuery
);
this.logger.debug(`Unique visitors for entity ${input.entityId}: ${uniqueVisitors}`);
} catch (error) {
this.logger.error(`Error counting unique visitors for entity ${input.entityId}: ${error.message}`);
const err = error instanceof Error ? error : new Error(String(error));
this.logger.error(`Error counting unique visitors for entity ${input.entityId}: ${err.message}`);
throw error;
}
@@ -94,7 +96,8 @@ export class GetEntityAnalyticsQuery
);
this.logger.debug(`Sponsor clicks for entity ${input.entityId}: ${sponsorClicks}`);
} catch (error) {
this.logger.error(`Error getting sponsor clicks for entity ${input.entityId}: ${error.message}`);
const err = error instanceof Error ? error : new Error(String(error));
this.logger.error(`Error getting sponsor clicks for entity ${input.entityId}: ${err.message}`);
throw error;
}
@@ -104,7 +107,8 @@ export class GetEntityAnalyticsQuery
engagementScore = await this.calculateEngagementScore(input.entityId, since);
this.logger.debug(`Engagement score for entity ${input.entityId}: ${engagementScore}`);
} catch (error) {
this.logger.error(`Error calculating engagement score for entity ${input.entityId}: ${error.message}`);
const err = error instanceof Error ? error : new Error(String(error));
this.logger.error(`Error calculating engagement score for entity ${input.entityId}: ${err.message}`);
throw error;
}
@@ -130,7 +134,8 @@ export class GetEntityAnalyticsQuery
previousPageViews = fullPreviousPageViews - totalPageViews; // This calculates change, not just previous period's total
this.logger.debug(`Previous period full page views: ${fullPreviousPageViews}, change: ${previousPageViews}`);
} catch (error) {
this.logger.error(`Error counting previous period page views for entity ${input.entityId}: ${error.message}`);
const err = error instanceof Error ? error : new Error(String(error));
this.logger.error(`Error counting previous period page views for entity ${input.entityId}: ${err.message}`);
throw error;
}
@@ -145,7 +150,8 @@ export class GetEntityAnalyticsQuery
this.logger.debug(`Previous period full unique visitors: ${fullPreviousUniqueVisitors}, change: ${previousUniqueVisitors}`);
} catch (error) {
this.logger.error(`Error counting previous period unique visitors for entity ${input.entityId}: ${error.message}`);
const err = error instanceof Error ? error : new Error(String(error));
this.logger.error(`Error counting previous period unique visitors for entity ${input.entityId}: ${err.message}`);
throw error;
}
@@ -218,7 +224,8 @@ export class GetEntityAnalyticsQuery
sponsorClicks = await this.engagementRepository.getSponsorClicksForEntity(entityId, since);
this.logger.debug(`Sponsor clicks for engagement score for entity ${entityId}: ${sponsorClicks}`);
} catch (error) {
this.logger.error(`Error getting sponsor clicks for engagement score for entity ${entityId}: ${error.message}`);
const err = error instanceof Error ? error : new Error(String(error));
this.logger.error(`Error getting sponsor clicks for engagement score for entity ${entityId}: ${err.message}`);
throw error;
}
const score = sponsorClicks * 10; // Weighted score

View File

@@ -8,7 +8,7 @@ import type { AsyncUseCase } from '@core/shared/application';
import type { Logger } from '@core/shared/application';
import { PageView } from '../../domain/entities/PageView';
import type { EntityType, VisitorType } from '../../domain/types/PageView';
import type { IPageViewRepository } from '../../domain/repositories/IPageViewRepository';
import type { IPageViewRepository } from '../repositories/IPageViewRepository';
export interface RecordPageViewInput {
entityType: EntityType;
@@ -57,7 +57,8 @@ export class RecordPageViewUseCase
this.logger.info('Page view recorded successfully', { pageViewId, input });
return { pageViewId };
} catch (error) {
this.logger.error('Error recording page view', error, { input });
const err = error instanceof Error ? error : new Error(String(error));
this.logger.error('Error recording page view', err, { input });
throw error;
}
}

View File

@@ -1,6 +1,6 @@
export interface Logger {
debug(message: string, ...args: any[]): void;
info(message: string, ...args: any[]): void;
warn(message: string, ...args: any[]): void;
error(message: string, ...args: any[]): void;
debug(message: string, ...args: unknown[]): void;
info(message: string, ...args: unknown[]): void;
warn(message: string, ...args: unknown[]): void;
error(message: string, ...args: unknown[]): void;
}

View File

@@ -1,4 +1,4 @@
import { AnalyticsEntityId } from '../../../core/analytics/domain/value-objects/AnalyticsEntityId';
import { AnalyticsEntityId } from '@core/analytics/domain/value-objects/AnalyticsEntityId';
describe('AnalyticsEntityId', () => {
it('creates a valid AnalyticsEntityId from a non-empty string', () => {

View File

@@ -1,4 +1,4 @@
import { AnalyticsSessionId } from '../../../core/analytics/domain/value-objects/AnalyticsSessionId';
import { AnalyticsSessionId } from '@core/analytics/domain/value-objects/AnalyticsSessionId';
describe('AnalyticsSessionId', () => {
it('creates a valid AnalyticsSessionId from a non-empty string', () => {

View File

@@ -1,4 +1,4 @@
import { PageViewId } from '../../../core/analytics/domain/value-objects/PageViewId';
import { PageViewId } from '@core/analytics/domain/value-objects/PageViewId';
describe('PageViewId', () => {
it('creates a valid PageViewId from a non-empty string', () => {

View File

@@ -10,8 +10,8 @@ export * from './domain/entities/PageView';
export * from './domain/entities/EngagementEvent';
export * from './domain/entities/AnalyticsSnapshot';
// Domain repositories
export * from './domain/repositories/IPageViewRepository';
// Application repositories
export * from './application/repositories/IPageViewRepository';
export * from './domain/repositories/IEngagementRepository';
export * from './domain/repositories/IAnalyticsSnapshotRepository';
@@ -20,7 +20,7 @@ export * from './application/use-cases/RecordPageViewUseCase';
export * from './application/use-cases/RecordEngagementUseCase';
export * from './application/use-cases/GetEntityAnalyticsQuery';
// Infrastructure
export * from './infrastructure/repositories/InMemoryPageViewRepository';
export * from './infrastructure/repositories/InMemoryEngagementRepository';
export * from './infrastructure/repositories/InMemoryAnalyticsSnapshotRepository';
// Infrastructure (moved to adapters)
export type { IPageViewRepository } from './application/repositories/IPageViewRepository';
export type { IEngagementRepository } from './domain/repositories/IEngagementRepository';
export type { IAnalyticsSnapshotRepository } from './domain/repositories/IAnalyticsSnapshotRepository';

View File

@@ -5,7 +5,7 @@ import { CompleteOnboardingPresenter } from '@apps/api/src/modules/driver/presen
describe('CompleteDriverOnboardingUseCase', () => {
let useCase: CompleteDriverOnboardingUseCase;
let driverRepository: { findById: any; save: any };
let driverRepository: { findById: () => Promise<any>; save: () => Promise<void> };
beforeEach(() => {
driverRepository = {

View File

@@ -5,7 +5,7 @@ import type { CheckoutServicePort } from '@core/automation/application/ports/Che
import type { CheckoutConfirmationPort } from '@core/automation/application/ports/CheckoutConfirmationPort';
import type { CheckoutInfoDTO } from '@core/automation/application/dto/CheckoutInfoDTO';
import { CheckoutPrice } from '@core/automation/domain/value-objects/CheckoutPrice';
import { CheckoutState, CheckoutStateEnum } from '@core/automation/domain/value-objects/CheckoutState';
import { CheckoutConfirmation } from '@core/automation/domain/value-objects/CheckoutConfirmation';
/**

View File

@@ -35,7 +35,7 @@ describe('ICheckoutConfirmationPort contract', () => {
it('should define the required interface structure', () => {
// This test verifies the port interface contract exists
const mockPort: ICheckoutConfirmationPort = {
requestCheckoutConfirmation: async (_request: CheckoutConfirmationRequest) => {
requestCheckoutConfirmation: async (__request: CheckoutConfirmationRequest) => {
return Result.ok(CheckoutConfirmation.create('confirmed'));
},
};

View File

@@ -1,6 +1,5 @@
import { describe, expect, test } from 'vitest'
import { OverlayAction, ActionAck } from '@core/automation/application/ports/IOverlaySyncPort'
import { IAutomationEventPublisher, AutomationEvent } from '@core/automation/application/ports/IAutomationEventPublisher'
import { OverlayAction } from '@core/automation/application/ports/IOverlaySyncPort'
import { IAutomationLifecycleEmitter, LifecycleCallback } from '@core/automation/infrastructure//IAutomationLifecycleEmitter'
import { OverlaySyncService } from '@core/automation/application/services/OverlaySyncService'
@@ -26,7 +25,7 @@ describe('OverlaySyncService (unit)', () => {
// create service wiring: pass emitter as dependency (constructor shape expected)
const svc = new OverlaySyncService({
lifecycleEmitter: emitter,
logger: console as any,
logger: console as unknown,
publisher: { publish: async () => {} },
})

View File

@@ -23,7 +23,7 @@ describe('OverlaySyncService timeout (unit)', () => {
const emitter = new MockLifecycleEmitter()
const svc = new OverlaySyncService({
lifecycleEmitter: emitter,
logger: console as any,
logger: console as unknown,
publisher: { publish: async () => {} },
})

View File

@@ -469,7 +469,7 @@ describe('RecalculateChampionshipStandingsUseCase', () => {
races.forEach((race) => raceRepository.seedRace(race));
const drivers = ['driver-1', 'driver-2', 'driver-3'];
const _drivers = ['driver-1', 'driver-2', 'driver-3'];
const resultsData: Array<{
raceId: string;

View File

@@ -73,7 +73,7 @@ describe('StartAutomationSessionUseCase', () => {
expect(mockSessionRepository.save).toHaveBeenCalledWith(
expect.objectContaining({
config,
currentStep: expect.objectContaining({ value: 1 }),
_currentStep: expect.objectContaining({ value: 1 }),
})
);
});

View File

@@ -1,4 +1,3 @@
import type { LogLevel } from './LoggerLogLevel';
import type { LogContext } from './LoggerContext';
import type { Logger } from '@core/shared/application';

View File

@@ -1,5 +1,5 @@
import { AutomationSession } from '../../domain/entities/AutomationSession';
import { SessionStateValue } from '../../domain/value-objects/SessionState';
export interface SessionRepositoryPort {
save(session: AutomationSession): Promise<void>;

View File

@@ -1,4 +1,4 @@
import { OverlaySyncPort, OverlayAction, ActionAck } from '../ports/OverlaySyncPort';
import { AutomationEventPublisherPort, AutomationEvent } from '../ports/AutomationEventPublisherPort';
import { AutomationLifecycleEmitterPort, LifecycleCallback } from '../ports/AutomationLifecycleEmitterPort';
import { LoggerPort } from '../ports/LoggerPort';

View File

@@ -37,7 +37,7 @@ export class ClearSessionUseCase {
});
}
return result;
} catch (error: any) {
} catch (error) {
this.logger.error('Error clearing user session.', error, {
useCase: 'ClearSessionUseCase'
});

View File

@@ -2,7 +2,7 @@ import { Result } from '../../../shared/result/Result';
import type { Logger } from '@core/shared/application';
import type { CheckoutServicePort } from '../ports/CheckoutServicePort';
import type { CheckoutConfirmationPort } from '../ports/CheckoutConfirmationPort';
import { CheckoutStateEnum } from '../../domain/value-objects/CheckoutState';
interface SessionMetadata {
sessionName: string;

View File

@@ -31,7 +31,7 @@ export class InitiateLoginUseCase {
this.logger.warn('Login flow initiation failed.', { error: result.error });
}
return result;
} catch (error: any) {
} catch (error) {
this.logger.error('Error initiating login flow.', error);
return Result.fail(error.message || 'Unknown error during login initiation.');
}

View File

@@ -1,7 +1,6 @@
import { describe, it, expect } from 'vitest';
import { AutomationSession } from '@core/automation/domain/entities/AutomationSession';
import { StepId } from '@core/automation/domain/value-objects/StepId';
import { SessionState } from '@core/automation/domain/value-objects/SessionState';
describe('AutomationSession Entity', () => {
describe('create', () => {

View File

@@ -1,7 +1,7 @@
import { randomUUID } from 'crypto';
import type { IEntity } from '@core/shared/domain';
import { StepId } from '../value-objects/StepId';
import { SessionState } from '../value-objects/SessionState';
import type { HostedSessionConfig } from '../types/HostedSessionConfig';
import { AutomationDomainError } from '../errors/AutomationDomainError';

View File

@@ -7,7 +7,7 @@ describe('PageStateValidator', () => {
describe('validateState', () => {
it('should return valid when all required selectors are present', () => {
// Arrange
const actualState = (selector: string) => {
const actualState = (_selector: string) => {
return ['#add-car-button', '#cars-list'].includes(selector);
};
@@ -27,7 +27,7 @@ describe('PageStateValidator', () => {
it('should return invalid when required selectors are missing', () => {
// Arrange
const actualState = (selector: string) => {
const actualState = (_selector: string) => {
return selector === '#add-car-button'; // Only one of two selectors present
};
@@ -48,7 +48,7 @@ describe('PageStateValidator', () => {
it('should return invalid when forbidden selectors are present', () => {
// Arrange
const actualState = (selector: string) => {
const actualState = (_selector: string) => {
return ['#add-car-button', '#set-track'].includes(selector);
};
@@ -70,7 +70,7 @@ describe('PageStateValidator', () => {
it('should handle empty forbidden selectors array', () => {
// Arrange
const actualState = (selector: string) => {
const actualState = (_selector: string) => {
return selector === '#add-car-button';
};
@@ -89,7 +89,7 @@ describe('PageStateValidator', () => {
it('should handle undefined forbidden selectors', () => {
// Arrange
const actualState = (selector: string) => {
const actualState = (_selector: string) => {
return selector === '#add-car-button';
};
@@ -108,7 +108,7 @@ describe('PageStateValidator', () => {
it('should return error result when actualState function throws', () => {
// Arrange
const actualState = (selector: string) => {
const actualState = (_selector: string) => {
throw new Error('Selector evaluation failed');
};
@@ -144,7 +144,7 @@ describe('PageStateValidator', () => {
it('should validate complex state with both required and forbidden selectors', () => {
// Arrange - Simulate being on Cars page but Track page elements leaked through
const actualState = (selector: string) => {
const actualState = (_selector: string) => {
const presentSelectors = ['#add-car-button', '#cars-list', '#set-track'];
return presentSelectors.includes(selector);
};

View File

@@ -26,7 +26,7 @@ export interface PageStateValidationResult {
}
export interface PageStateValidationInput {
actualState: (selector: string) => boolean;
actualState: (_selector: string) => boolean;
validation: PageStateValidation;
realMode?: boolean;
}
@@ -59,14 +59,14 @@ export class PageStateValidator
* @returns Result with validation outcome
*/
validateState(
actualState: (selector: string) => boolean,
actualState: (_selector: string) => boolean,
validation: PageStateValidation
): Result<PageStateValidationResult, Error> {
try {
const { expectedStep, requiredSelectors, forbiddenSelectors = [] } = validation;
// Check required selectors are present
const missingSelectors = requiredSelectors.filter(selector => !actualState(selector));
const missingSelectors = requiredSelectors.filter(_selector => !actualState(_selector));
if (missingSelectors.length > 0) {
const result: PageStateValidationResult = {
@@ -79,7 +79,7 @@ export class PageStateValidator
}
// Check forbidden selectors are absent
const unexpectedSelectors = forbiddenSelectors.filter(selector => actualState(selector));
const unexpectedSelectors = forbiddenSelectors.filter(_selector => actualState(_selector));
if (unexpectedSelectors.length > 0) {
const result: PageStateValidationResult = {
@@ -118,7 +118,7 @@ export class PageStateValidator
* @returns Result with validation outcome
*/
validateStateEnhanced(
actualState: (selector: string) => boolean,
actualState: (_selector: string) => boolean,
validation: PageStateValidation,
realMode: boolean = false
): Result<PageStateValidationResult, Error> {
@@ -183,7 +183,7 @@ export class PageStateValidator
}
// Check required selectors are present (with fallbacks for real mode)
const missingSelectors = requiredSelectors.filter(selector => {
const missingSelectors = requiredSelectors.filter(_selector => {
if (realMode) {
const relatedSelectors = selectorsToCheck.filter(s =>
s.includes(expectedStep) ||
@@ -212,7 +212,7 @@ export class PageStateValidator
}
// Check forbidden selectors are absent
const unexpectedSelectors = forbiddenSelectors.filter(selector => actualState(selector));
const unexpectedSelectors = forbiddenSelectors.filter(_selector => actualState(_selector));
if (unexpectedSelectors.length > 0) {
const result: PageStateValidationResult = {

View File

@@ -1,7 +1,7 @@
import { describe, it, expect } from 'vitest';
import { StepTransitionValidator } from '@core/automation/domain/services/StepTransitionValidator';
import { StepId } from '@core/automation/domain/value-objects/StepId';
import { SessionState } from '@core/automation/domain/value-objects/SessionState';
describe('StepTransitionValidator Service', () => {
describe('canTransition', () => {

View File

@@ -1,5 +1,5 @@
import { StepId } from '../value-objects/StepId';
import { SessionState } from '../value-objects/SessionState';
import type { IDomainValidationService } from '@core/shared/domain';
import { Result } from '../../../shared/result/Result';
@@ -96,7 +96,7 @@ export class StepTransitionValidator
return { isValid: true };
}
static shouldStopAtStep18(nextStep: StepId): boolean {
static shouldStopAtStep18(_nextStep: StepId): boolean {
return nextStep.isFinalStep();
}

View File

@@ -19,7 +19,7 @@ describe('CheckoutConfirmation Value Object', () => {
});
it('should throw error for invalid decision', () => {
expect(() => CheckoutConfirmation.create('invalid' as any)).toThrow(
expect(() => CheckoutConfirmation.create('invalid' as unknown)).toThrow(
'Invalid checkout confirmation decision',
);
});

View File

@@ -24,7 +24,7 @@ export class CheckoutConfirmation {
return CheckoutConfirmation.create('confirmed');
}
static cancelled(_reason?: string): CheckoutConfirmation {
static cancelled(__reason?: string): CheckoutConfirmation {
return CheckoutConfirmation.create('cancelled');
}

View File

@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest';
import { CheckoutState, CheckoutStateEnum } from '@core/automation/domain/value-objects/CheckoutState';
/**
* CheckoutState Value Object - GREEN PHASE

View File

@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest';
import { SessionState } from '@core/automation/domain/value-objects/SessionState';
describe('SessionState Value Object', () => {
describe('create', () => {
@@ -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' as unknown)).toThrow('Invalid session state');
});
it('should throw error for empty string', () => {
expect(() => SessionState.create('' as any)).toThrow('Invalid session state');
expect(() => SessionState.create('' as unknown)).toThrow('Invalid session state');
});
});

View File

@@ -1,5 +1,5 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { loadAutomationConfig, getAutomationMode, AutomationMode } from '../../../core/automation/infrastructure/config/AutomationConfig';
describe('AutomationConfig', () => {
const originalEnv = process.env;
@@ -17,7 +17,7 @@ describe('AutomationConfig', () => {
describe('getAutomationMode', () => {
describe('NODE_ENV-based mode detection', () => {
it('should return production mode when NODE_ENV=production', () => {
(process.env as any).NODE_ENV = 'production';
(process.env as unknown).NODE_ENV = 'production';
delete process.env.AUTOMATION_MODE;
const mode = getAutomationMode();
@@ -26,7 +26,7 @@ describe('AutomationConfig', () => {
});
it('should return test mode when NODE_ENV=test', () => {
(process.env as any).NODE_ENV = 'test';
(process.env as unknown).NODE_ENV = 'test';
delete process.env.AUTOMATION_MODE;
const mode = getAutomationMode();
@@ -35,7 +35,7 @@ describe('AutomationConfig', () => {
});
it('should return test mode when NODE_ENV is not set', () => {
delete (process.env as any).NODE_ENV;
delete (process.env as unknown).NODE_ENV;
delete process.env.AUTOMATION_MODE;
const mode = getAutomationMode();
@@ -44,7 +44,7 @@ describe('AutomationConfig', () => {
});
it('should return test mode for unknown NODE_ENV values', () => {
(process.env as any).NODE_ENV = 'staging';
(process.env as unknown).NODE_ENV = 'staging';
delete process.env.AUTOMATION_MODE;
const mode = getAutomationMode();
@@ -53,7 +53,7 @@ describe('AutomationConfig', () => {
});
it('should return development mode when NODE_ENV=development', () => {
(process.env as any).NODE_ENV = 'development';
(process.env as unknown).NODE_ENV = 'development';
delete process.env.AUTOMATION_MODE;
const mode = getAutomationMode();
@@ -104,7 +104,7 @@ describe('AutomationConfig', () => {
it('should ignore invalid AUTOMATION_MODE and use NODE_ENV', () => {
process.env.AUTOMATION_MODE = 'invalid-mode';
(process.env as any).NODE_ENV = 'production';
(process.env as unknown).NODE_ENV = 'production';
const mode = getAutomationMode();
@@ -116,7 +116,7 @@ describe('AutomationConfig', () => {
describe('loadAutomationConfig', () => {
describe('default configuration', () => {
it('should return test mode when NODE_ENV is not set', () => {
delete (process.env as any).NODE_ENV;
delete (process.env as unknown).NODE_ENV;
delete process.env.AUTOMATION_MODE;
const config = loadAutomationConfig();
@@ -143,7 +143,7 @@ describe('AutomationConfig', () => {
describe('production mode configuration', () => {
it('should return production mode when NODE_ENV=production', () => {
(process.env as any).NODE_ENV = 'production';
(process.env as unknown).NODE_ENV = 'production';
delete process.env.AUTOMATION_MODE;
const config = loadAutomationConfig();
@@ -228,7 +228,7 @@ describe('AutomationConfig', () => {
});
it('should fallback to test mode for invalid NODE_ENV', () => {
(process.env as any).NODE_ENV = 'invalid-env';
(process.env as unknown).NODE_ENV = 'invalid-env';
delete process.env.AUTOMATION_MODE;
const config = loadAutomationConfig();
@@ -239,7 +239,7 @@ describe('AutomationConfig', () => {
describe('full configuration scenario', () => {
it('should load complete test environment configuration', () => {
(process.env as any).NODE_ENV = 'test';
(process.env as unknown).NODE_ENV = 'test';
delete process.env.AUTOMATION_MODE;
const config = loadAutomationConfig();
@@ -249,7 +249,7 @@ describe('AutomationConfig', () => {
});
it('should load complete production environment configuration', () => {
(process.env as any).NODE_ENV = 'production';
(process.env as unknown).NODE_ENV = 'production';
delete process.env.AUTOMATION_MODE;
const config = loadAutomationConfig();

View File

@@ -12,7 +12,7 @@ describe('BrowserModeConfig - GREEN Phase', () => {
beforeEach(() => {
process.env = { ...originalEnv };
delete (process.env as any).NODE_ENV;
delete (process.env as unknown).NODE_ENV;
});
afterEach(() => {
@@ -21,7 +21,7 @@ describe('BrowserModeConfig - GREEN Phase', () => {
describe('Development Mode with Runtime Control', () => {
it('should default to headless in development mode', () => {
(process.env as any).NODE_ENV = 'development';
(process.env as unknown).NODE_ENV = 'development';
const loader = new BrowserModeConfigLoader();
const config = loader.load();
@@ -31,7 +31,7 @@ describe('BrowserModeConfig - GREEN Phase', () => {
});
it('should allow runtime switch to headless mode in development', () => {
(process.env as any).NODE_ENV = 'development';
(process.env as unknown).NODE_ENV = 'development';
const loader = new BrowserModeConfigLoader();
loader.setDevelopmentMode('headless');
@@ -42,7 +42,7 @@ describe('BrowserModeConfig - GREEN Phase', () => {
});
it('should allow runtime switch to headed mode in development', () => {
(process.env as any).NODE_ENV = 'development';
(process.env as unknown).NODE_ENV = 'development';
const loader = new BrowserModeConfigLoader();
loader.setDevelopmentMode('headed');
@@ -53,7 +53,7 @@ describe('BrowserModeConfig - GREEN Phase', () => {
});
it('should persist runtime setting across multiple load() calls', () => {
(process.env as any).NODE_ENV = 'development';
(process.env as unknown).NODE_ENV = 'development';
const loader = new BrowserModeConfigLoader();
loader.setDevelopmentMode('headless');
@@ -66,7 +66,7 @@ describe('BrowserModeConfig - GREEN Phase', () => {
});
it('should return current development mode via getter', () => {
(process.env as any).NODE_ENV = 'development';
(process.env as unknown).NODE_ENV = 'development';
const loader = new BrowserModeConfigLoader();
expect(loader.getDevelopmentMode()).toBe('headless');
@@ -78,7 +78,7 @@ describe('BrowserModeConfig - GREEN Phase', () => {
describe('Production Mode', () => {
it('should use headless mode when NODE_ENV=production', () => {
(process.env as any).NODE_ENV = 'production';
(process.env as unknown).NODE_ENV = 'production';
const loader = new BrowserModeConfigLoader();
const config = loader.load();
@@ -88,7 +88,7 @@ describe('BrowserModeConfig - GREEN Phase', () => {
});
it('should ignore setDevelopmentMode in production', () => {
(process.env as any).NODE_ENV = 'production';
(process.env as unknown).NODE_ENV = 'production';
const loader = new BrowserModeConfigLoader();
loader.setDevelopmentMode('headed');
@@ -101,7 +101,7 @@ describe('BrowserModeConfig - GREEN Phase', () => {
describe('Test Mode', () => {
it('should use headless mode when NODE_ENV=test', () => {
(process.env as any).NODE_ENV = 'test';
(process.env as unknown).NODE_ENV = 'test';
const loader = new BrowserModeConfigLoader();
const config = loader.load();
@@ -111,7 +111,7 @@ describe('BrowserModeConfig - GREEN Phase', () => {
});
it('should ignore setDevelopmentMode in test mode', () => {
(process.env as any).NODE_ENV = 'test';
(process.env as unknown).NODE_ENV = 'test';
const loader = new BrowserModeConfigLoader();
loader.setDevelopmentMode('headed');
@@ -124,7 +124,7 @@ describe('BrowserModeConfig - GREEN Phase', () => {
describe('Default Mode', () => {
it('should default to headless mode when NODE_ENV is not set', () => {
delete (process.env as any).NODE_ENV;
delete (process.env as unknown).NODE_ENV;
const loader = new BrowserModeConfigLoader();
const config = loader.load();
@@ -134,7 +134,7 @@ describe('BrowserModeConfig - GREEN Phase', () => {
});
it('should use headless mode for any non-development NODE_ENV value', () => {
(process.env as any).NODE_ENV = 'staging';
(process.env as unknown).NODE_ENV = 'staging';
const loader = new BrowserModeConfigLoader();
const config = loader.load();
@@ -146,7 +146,7 @@ describe('BrowserModeConfig - GREEN Phase', () => {
describe('Source Tracking', () => {
it('should report GUI as source in development mode', () => {
(process.env as any).NODE_ENV = 'development';
(process.env as unknown).NODE_ENV = 'development';
const loader = new BrowserModeConfigLoader();
const config = loader.load();
@@ -155,7 +155,7 @@ describe('BrowserModeConfig - GREEN Phase', () => {
});
it('should report NODE_ENV as source in production mode', () => {
(process.env as any).NODE_ENV = 'production';
(process.env as unknown).NODE_ENV = 'production';
const loader = new BrowserModeConfigLoader();
const config = loader.load();
@@ -164,7 +164,7 @@ describe('BrowserModeConfig - GREEN Phase', () => {
});
it('should report NODE_ENV as source in test mode', () => {
(process.env as any).NODE_ENV = 'test';
(process.env as unknown).NODE_ENV = 'test';
const loader = new BrowserModeConfigLoader();
const config = loader.load();
@@ -173,7 +173,7 @@ describe('BrowserModeConfig - GREEN Phase', () => {
});
it('should report NODE_ENV as source when NODE_ENV is not set', () => {
delete (process.env as any).NODE_ENV;
delete (process.env as unknown).NODE_ENV;
const loader = new BrowserModeConfigLoader();
const config = loader.load();

View File

@@ -5,12 +5,12 @@ import type { CheckoutInfoDTO } from '../../../application/dto/CheckoutInfoDTO';
import { IRACING_SELECTORS } from './dom/IRacingSelectors';
interface Page {
locator(selector: string): Locator;
locator(_selector: string): Locator;
}
interface Locator {
first(): Locator;
locator(selector: string): Locator;
locator(_selector: string): Locator;
getAttribute(name: string): Promise<string | null>;
innerHTML(): Promise<string>;
textContent(): Promise<string | null>;

View File

@@ -190,7 +190,7 @@ describe('AuthenticationGuard', () => {
});
});
describe('Login button selector specificity', () => {
describe('Login button _selector specificity', () => {
test('should detect login button on actual login pages', async () => {
// Simulate a real login page with a login form
const mockLocator = {

View File

@@ -2,7 +2,7 @@ import { describe, test, expect, beforeEach } from 'vitest';
import { SessionCookieStore } from '@core/automation/infrastructure//automation/auth/SessionCookieStore';
import type { Cookie } from 'playwright';
const logger = console as any;
const logger = console as unknown;
describe('SessionCookieStore - Cookie Validation', () => {
let cookieStore: SessionCookieStore;

View File

@@ -20,9 +20,9 @@ import { Result } from '../../../../../shared/result/Result';
import { IRACING_SELECTORS, IRACING_URLS, IRACING_TIMEOUTS, ALL_BLOCKED_SELECTORS, BLOCKED_KEYWORDS } from '../dom/IRacingSelectors';
import { SessionCookieStore } from '../auth/SessionCookieStore';
import { PlaywrightBrowserSession } from './PlaywrightBrowserSession';
import { getFixtureForStep } from '../engine/FixtureServer';
import { BrowserModeConfigLoader, BrowserMode } from '../../../config/BrowserModeConfig';
import { getAutomationMode } from '../../../config/AutomationConfig';
import { PageStateValidator, PageStateValidation, PageStateValidationResult } from '@core/automation/domain/services/PageStateValidator';
import { IRacingDomNavigator } from '../dom/IRacingDomNavigator';
import { SafeClickService } from '../dom/SafeClickService';
@@ -543,7 +543,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
*/
async attachPanel(page?: Page, actionId?: string): Promise<void> {
const selector = '#gridpilot-overlay'
await this.emitLifecycle({ type: 'panel-attached', actionId, timestamp: Date.now(), payload: { selector } })
await this.emitLifecycle({ type: 'panel-attached', actionId, timestamp: Date.now(), payload: { _selector } })
await this.emitLifecycle({ type: 'action-started', actionId, timestamp: Date.now() })
}
private isRealMode(): boolean {
@@ -583,7 +583,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
}
}
const actualState = (selector: string): boolean => {
const actualState = (_selector: string): boolean => {
return selectorChecks[selector] === true;
};
@@ -1247,7 +1247,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
* @param elementText Optional text content of the element (should be direct text only)
* @returns true if the selector/text matches a blocked pattern
*/
private isBlockedSelector(selector: string, elementText?: string): boolean {
private isBlockedSelector(_selector: string, elementText?: string): boolean {
const selectorLower = selector.toLowerCase();
const textLower = elementText?.toLowerCase().trim() ?? '';
@@ -1293,7 +1293,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
* @param selector The CSS selector of the element to verify
* @throws Error if element is a blocked checkout/payment button
*/
private async verifyNotBlockedElement(selector: string): Promise<void> {
private async verifyNotBlockedElement(_selector: string): Promise<void> {
if (!this.page) return;
// In mock mode we bypass safety blocking to allow tests to exercise checkout flows
@@ -1394,7 +1394,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
throw error;
}
// Otherwise ignore - element might not exist yet, safeClick will handle that
this.log('debug', 'Could not verify element (may not exist yet)', { selector, error: String(error) });
this.log('debug', 'Could not verify element (may not exist yet)', { _selector, error: String(error) });
}
}
@@ -1409,7 +1409,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
* @param options Click options including timeout and force
* @returns Promise that resolves when click succeeds or throws after max retries
*/
private async safeClick(selector: string, options?: { timeout?: number; force?: boolean }): Promise<void> {
private async safeClick(_selector: string, options?: { timeout?: number; force?: boolean }): Promise<void> {
if (!this.page) {
throw new Error('Browser not connected');
}
@@ -1438,7 +1438,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
try {
// On final attempt, use force: true if datetime picker issues detected
const useForce = options?.force || attempt === maxRetries;
await this.page.click(selector, { timeout, force: useForce });
await this.page.click(_selector, { timeout, force: useForce });
return; // Success
} catch (error) {
// Re-throw blocked errors immediately
@@ -1496,7 +1496,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
this.log('debug', 'JS fallback click did not find element or failed', { selector });
}
} catch (e) {
this.log('debug', 'JS fallback click error', { selector, error: String(e) });
this.log('debug', 'JS fallback click error', { _selector, error: String(e) });
}
this.log('error', 'Max retries reached, click still blocked', { selector });
@@ -1559,7 +1559,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
if (isVisible) {
await radioLabel.click({ timeout: IRACING_TIMEOUTS.elementWait });
this.log('info', 'Selected weather type', { weatherType, selector: labelSelector });
this.log('info', 'Selected weather type', { weatherType, _selector: labelSelector });
} else {
this.log('debug', 'Weather type radio not visible, may already be selected or step is different');
}
@@ -1674,10 +1674,10 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
});
// Brief pause for modal animation (reduced from 300ms)
await this.page.waitForTimeout(150);
this.log('info', 'Add Car modal is visible', { selector: modalSelector });
this.log('info', 'Add Car modal is visible', { _selector: modalSelector });
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
this.log('warn', 'Add Car modal not found with primary selector, dumping #create-race-wizard innerHTML and retrying', { error: message });
this.log('warn', 'Add Car modal not found with primary _selector, dumping #create-race-wizard innerHTML and retrying', { error: message });
const html = await this.page!.innerHTML('#create-race-wizard').catch(() => '');
this.log('debug', 'create-race-wizard innerHTML (truncated)', { html: html ? html.slice(0, 2000) : '' });
this.log('info', 'Retrying wait for Add Car modal with extended timeout');
@@ -1688,7 +1688,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
timeout: 10000,
});
await this.page.waitForTimeout(150);
this.log('info', 'Add Car modal found after retry', { selector: modalSelectorRetry });
this.log('info', 'Add Car modal found after retry', { _selector: modalSelectorRetry });
} catch {
this.log('warn', 'Add Car modal still not found after retry');
}
@@ -1775,7 +1775,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
for (const selector of directSelectors) {
const button = this.page.locator(selector).first();
if (await button.count() > 0 && await button.isVisible()) {
await this.safeClick(selector, { timeout: IRACING_TIMEOUTS.elementWait });
await this.safeClick(_selector, { timeout: IRACING_TIMEOUTS.elementWait });
this.log('info', 'Clicked direct Select button for first search result', { selector });
return;
}
@@ -1788,7 +1788,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
if (await dropdownButton.count() > 0 && await dropdownButton.isVisible()) {
// Click dropdown to open menu
await this.safeClick(dropdownSelector, { timeout: IRACING_TIMEOUTS.elementWait });
this.log('debug', 'Clicked dropdown toggle, waiting for menu', { selector: dropdownSelector });
this.log('debug', 'Clicked dropdown toggle, waiting for menu', { _selector: dropdownSelector });
// Wait for dropdown menu to appear
await this.page.waitForSelector('.dropdown-menu.show', { timeout: 3000 }).catch(() => { });
@@ -1797,7 +1797,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
const itemSelector = IRACING_SELECTORS.steps.trackSelectDropdownItem;
await this.page.waitForTimeout(200);
await this.safeClick(itemSelector, { timeout: IRACING_TIMEOUTS.elementWait });
this.log('info', 'Clicked first dropdown item to select track config', { selector: itemSelector });
this.log('info', 'Clicked first dropdown item to select track config', { _selector: itemSelector });
return;
}
@@ -1805,7 +1805,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
const carRowSelector = '.car-row, .car-item, [data-testid*="car"], [id*="favorite_cars"], [id*="select-car"]';
const carRow = this.page.locator(carRowSelector).first();
if (await carRow.count() > 0) {
this.log('info', 'Fallback: clicking car row/item to select', { selector: carRowSelector });
this.log('info', 'Fallback: clicking car row/item to select', { _selector: carRowSelector });
// Click the row itself (or its first clickable descendant)
try {
await this.safeClick(carRowSelector, { timeout: IRACING_TIMEOUTS.elementWait });
@@ -1888,7 +1888,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
(await direct.isVisible().catch(() => false));
if (hasDirect) {
this.log('info', 'Clicking direct New Race button', { selector: directSelector });
this.log('info', 'Clicking direct New Race button', { _selector: directSelector });
await this.safeClick(directSelector, { timeout: IRACING_TIMEOUTS.elementWait });
} else {
const dropdownToggleSelector =
@@ -2100,7 +2100,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
}
try {
this.log('debug', `Waiting for wizard step: ${stepName}`, { selector: containerSelector });
this.log('debug', `Waiting for wizard step: ${stepName}`, { _selector: containerSelector });
// Use 'attached' instead of 'visible' because iRacing wizard steps are marked as
// 'active hidden' in the DOM - they exist but are hidden via CSS class
await this.page.waitForSelector(containerSelector, {
@@ -2129,11 +2129,11 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
const timeout = this.isRealMode() ? IRACING_TIMEOUTS.elementWait : this.config.timeout;
// Split combined selectors and try each one
const selectors = selector.split(', ').map(s => s.trim());
const selectors = _selector.split(', ').map(s => s.trim());
for (const sel of selectors) {
try {
this.log('debug', `Trying selector for ${fieldName}`, { selector: sel });
this.log('debug', `Trying _selector for ${fieldName}`, { _selector: sel });
// Check if element exists and is visible
const element = this.page.locator(sel).first();
@@ -2143,11 +2143,11 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
// Use 'attached' instead of 'visible' because iRacing wizard steps have class="hidden"
await element.waitFor({ state: 'attached', timeout });
await element.fill(value);
this.log('info', `Successfully filled ${fieldName}`, { selector: sel, value });
this.log('info', `Successfully filled ${fieldName}`, { _selector: sel, value });
return { success: true, fieldName, valueSet: value };
}
} catch (error) {
this.log('debug', `Selector failed for ${fieldName}`, { selector: sel, error: String(error) });
this.log('debug', `Selector failed for ${fieldName}`, { _selector: sel, error: String(error) });
}
}
@@ -2155,12 +2155,12 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
try {
this.log('debug', `Trying combined selector for ${fieldName}`, { selector });
// Use 'attached' instead of 'visible' because iRacing wizard steps have class="hidden"
await this.page.waitForSelector(selector, { state: 'attached', timeout });
await this.page.waitForSelector(_selector, { state: 'attached', timeout });
await this.page.fill(selector, value);
return { success: true, fieldName, valueSet: value };
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
this.log('error', `Failed to fill ${fieldName}`, { selector, error: message });
this.log('error', `Failed to fill ${fieldName}`, { _selector, error: message });
return { success: false, fieldName, valueSet: value, error: message };
}
}
@@ -2194,7 +2194,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
// Some wizard footer buttons are present/attached but not considered "visible" by Playwright
// (offscreen, overlapped by overlays, or transitional). Use a forced safe click first,
// then fall back to name-based or last-resort selectors if that fails.
this.log('debug', 'Attempting next button (primary) with forced click', { selector: nextButtonSelector });
this.log('debug', 'Attempting next button (primary) with forced click', { _selector: nextButtonSelector });
try {
await this.safeClick(nextButtonSelector, { timeout, force: true });
this.log('info', `Clicked next button to ${nextStepName} (primary forced)`);
@@ -2204,7 +2204,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
}
// Try fallback with step name (also attempt forced click)
this.log('debug', 'Trying fallback next button (forced)', { selector: fallbackSelector });
this.log('debug', 'Trying fallback next button (forced)', { _selector: fallbackSelector });
try {
await this.safeClick(fallbackSelector, { timeout, force: true });
this.log('info', `Clicked next button (fallback) to ${nextStepName}`);
@@ -2248,7 +2248,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
}
// Use 'attached' instead of 'visible' because mock fixtures/wizard steps may be present but hidden
await this.page.waitForSelector(selector, { state: 'attached', timeout });
await this.page.waitForSelector(_selector, { state: 'attached', timeout });
await this.safeClick(selector, { timeout });
return { success: true, target: selector };
}
@@ -2260,7 +2260,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
const selector = this.getFieldSelector(fieldName);
const timeout = this.isRealMode() ? IRACING_TIMEOUTS.elementWait : this.config.timeout;
this.log('debug', 'fillField', { fieldName, selector, mode: this.config.mode });
this.log('debug', 'fillField', { fieldName, _selector, mode: this.config.mode });
// In mock mode, reveal typical fixture-hidden containers to allow Playwright to interact.
if (!this.isRealMode()) {
@@ -2277,7 +2277,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
}
// Wait for the element to be attached to the DOM
await this.page.waitForSelector(selector, { state: 'attached', timeout });
await this.page.waitForSelector(_selector, { state: 'attached', timeout });
// Try normal Playwright fill first; fall back to JS injection in mock mode if Playwright refuses due to visibility.
try {
@@ -2315,7 +2315,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
// Try to wait for the canonical selector first
try {
await this.page.waitForSelector(selector, { state: 'attached', timeout });
await this.page.waitForSelector(_selector, { state: 'attached', timeout });
await this.page.selectOption(selector, value);
return;
} catch {
@@ -2774,7 +2774,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
// ignore evaluation errors during tests
}
}
await this.page.waitForSelector(selector, { state: 'attached', timeout });
await this.page.waitForSelector(_selector, { state: 'attached', timeout });
await this.safeClick(selector, { timeout });
}
@@ -2784,7 +2784,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
}
// Broaden trigger selector to match multiple fixture variants (buttons, anchors, data-action)
const escaped = type.replace(/"/g, '\\"');
const selector = `button:has-text("${escaped}"), a:has-text("${escaped}"), [aria-label*="${escaped}" i], [data-action="${escaped}"], [data-modal-trigger="${escaped}"]`;
const _selector = `button:has-text("${escaped}"), a:has-text("${escaped}"), [aria-label*="${escaped}" i], [data-action="${escaped}"], [data-modal-trigger="${escaped}"]`;
const timeout = this.isRealMode() ? IRACING_TIMEOUTS.elementWait : this.config.timeout;
// In mock mode, reveal typical hidden fixture containers so trigger buttons are discoverable.
@@ -2802,7 +2802,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
}
// Use 'attached' instead of 'visible' because iRacing wizard steps have class="hidden"
await this.page.waitForSelector(selector, { state: 'attached', timeout });
await this.page.waitForSelector(_selector, { state: 'attached', timeout });
await this.safeClick(selector, { timeout });
}
@@ -3355,7 +3355,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
* @param currentStep Current step number to determine if modal should be visible
* @throws Error with 'WIZARD_DISMISSED' message if modal was closed by user
*/
async checkWizardDismissed(currentStep: number): Promise<void> {
async checkWizardDismissed(_currentStep: number): Promise<void> {
if (!this.page || !this.isRealMode() || currentStep < 3) {
// Don't check before step 3 (modal opens at step 2)
return;
@@ -3503,7 +3503,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
const locator = this.page.locator(sel).first();
const count = await locator.count().catch(() => 0);
if (count > 0) {
this.log('debug', 'Found checkout candidate button selector', { selector: sel });
this.log('debug', 'Found checkout candidate button _selector', { _selector: sel });
// safeClick will no-op in mock mode if element is hidden and will enforce
// verifyNotBlockedElement() in real mode to avoid dangerous clicks.

View File

@@ -6,7 +6,7 @@ import * as path from 'path';
import type { LoggerPort } from '@core/automation/application/ports/LoggerPort';
import { BrowserModeConfigLoader, BrowserMode } from '../../../config/BrowserModeConfig';
import { getAutomationMode } from '../../../config/AutomationConfig';
import type { PlaywrightConfig } from './PlaywrightAutomationAdapter';
import { PlaywrightAutomationAdapter } from './PlaywrightAutomationAdapter';

View File

@@ -13,7 +13,7 @@ import { PlaywrightBrowserSession } from './PlaywrightBrowserSession';
import { IRacingDomNavigator } from '../dom/IRacingDomNavigator';
import { IRacingDomInteractor } from '../dom/IRacingDomInteractor';
import { IRACING_SELECTORS } from '../dom/IRacingSelectors';
import { getFixtureForStep } from '../engine/FixtureServer';
import type {
PageStateValidation,
PageStateValidationResult,
@@ -167,7 +167,7 @@ export class WizardStepOrchestrator {
await this.navigator.waitForWizardStep(stepName);
}
private async checkWizardDismissed(currentStep: number): Promise<void> {
private async checkWizardDismissed(_currentStep: number): Promise<void> {
await this.navigator.checkWizardDismissed(currentStep);
}

View File

@@ -8,7 +8,7 @@ import type { PlaywrightConfig } from '../core/PlaywrightAutomationAdapter';
import { PlaywrightBrowserSession } from '../core/PlaywrightBrowserSession';
import { IRACING_SELECTORS, IRACING_TIMEOUTS } from './IRacingSelectors';
import { SafeClickService } from './SafeClickService';
import { getFixtureForStep } from '../engine/FixtureServer';
export class IRacingDomInteractor {
constructor(
@@ -66,10 +66,10 @@ export class IRacingDomInteractor {
const selector = fieldMap[fieldName as keyof typeof fieldMap] ?? IRACING_SELECTORS.fields.textInput;
const timeout = this.isRealMode() ? IRACING_TIMEOUTS.elementWait : this.config.timeout;
this.log('debug', 'Filling form field', { fieldName, selector, mode: this.config.mode });
this.log('debug', 'Filling form field', { fieldName, _selector, mode: this.config.mode });
try {
await page.waitForSelector(selector, { state: 'attached', timeout });
await page.waitForSelector(_selector, { state: 'attached', timeout });
try {
await page.fill(selector, value);
@@ -115,8 +115,8 @@ export class IRacingDomInteractor {
const selector = this.getActionSelector(target);
const timeout = this.isRealMode() ? IRACING_TIMEOUTS.elementWait : this.config.timeout;
this.log('debug', 'Clicking element', { target, selector, mode: this.config.mode });
await page.waitForSelector(selector, { state: 'attached', timeout });
this.log('debug', 'Clicking element', { target, _selector, mode: this.config.mode });
await page.waitForSelector(_selector, { state: 'attached', timeout });
await page.click(selector);
return { success: true, target };
} catch (error) {
@@ -165,7 +165,7 @@ export class IRacingDomInteractor {
const selector = this.getFieldSelector(fieldName);
const timeout = this.isRealMode() ? IRACING_TIMEOUTS.elementWait : this.config.timeout;
this.log('debug', 'fillField', { fieldName, selector, mode: this.config.mode });
this.log('debug', 'fillField', { fieldName, _selector, mode: this.config.mode });
if (!this.isRealMode()) {
try {
@@ -182,7 +182,7 @@ export class IRacingDomInteractor {
}
}
await page.waitForSelector(selector, { state: 'attached', timeout });
await page.waitForSelector(_selector, { state: 'attached', timeout });
try {
await page.fill(selector, value);
@@ -218,11 +218,11 @@ export class IRacingDomInteractor {
const selector = this.getFieldSelector(fieldName);
const timeout = this.isRealMode() ? IRACING_TIMEOUTS.elementWait : this.config.timeout;
const selectors = selector.split(', ').map((s) => s.trim());
const selectors = _selector.split(', ').map((s) => s.trim());
for (const sel of selectors) {
try {
this.log('debug', `Trying selector for ${fieldName}`, { selector: sel });
this.log('debug', `Trying _selector for ${fieldName}`, { _selector: sel });
const element = page.locator(sel).first();
const isVisible = await element.isVisible().catch(() => false);
@@ -230,22 +230,22 @@ export class IRacingDomInteractor {
if (isVisible) {
await element.waitFor({ state: 'attached', timeout });
await element.fill(value);
this.log('info', `Successfully filled ${fieldName}`, { selector: sel, value });
this.log('info', `Successfully filled ${fieldName}`, { _selector: sel, value });
return { success: true, fieldName, valueSet: value };
}
} catch (error) {
this.log('debug', `Selector failed for ${fieldName}`, { selector: sel, error: String(error) });
this.log('debug', `Selector failed for ${fieldName}`, { _selector: sel, error: String(error) });
}
}
try {
this.log('debug', `Trying combined selector for ${fieldName}`, { selector });
await page.waitForSelector(selector, { state: 'attached', timeout });
await page.waitForSelector(_selector, { state: 'attached', timeout });
await page.fill(selector, value);
return { success: true, fieldName, valueSet: value };
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
this.log('error', `Failed to fill ${fieldName}`, { selector, error: message });
this.log('error', `Failed to fill ${fieldName}`, { _selector, error: message });
return { success: false, fieldName, valueSet: value, error: message };
}
}
@@ -273,7 +273,7 @@ export class IRacingDomInteractor {
selector = this.getActionSelector(action);
}
await page.waitForSelector(selector, { state: 'attached', timeout });
await page.waitForSelector(_selector, { state: 'attached', timeout });
await this.safeClickService.safeClick(selector, { timeout });
return { success: true, target: selector };
}
@@ -316,7 +316,7 @@ export class IRacingDomInteractor {
const fallbackSelector = `.wizard-footer a.btn:has-text("${nextStepName}")`;
try {
this.log('debug', 'Attempting next button (primary) with forced click', { selector: nextButtonSelector });
this.log('debug', 'Attempting next button (primary) with forced click', { _selector: nextButtonSelector });
try {
await this.safeClickService.safeClick(nextButtonSelector, { timeout, force: true });
this.log('info', `Clicked next button to ${nextStepName} (primary forced)`);
@@ -325,7 +325,7 @@ export class IRacingDomInteractor {
this.log('debug', 'Primary forced click failed, falling back', { error: String(e) });
}
this.log('debug', 'Trying fallback next button (forced)', { selector: fallbackSelector });
this.log('debug', 'Trying fallback next button (forced)', { _selector: fallbackSelector });
try {
await this.safeClickService.safeClick(fallbackSelector, { timeout, force: true });
this.log('info', `Clicked next button (fallback) to ${nextStepName}`);
@@ -350,7 +350,7 @@ export class IRacingDomInteractor {
const timeout = this.isRealMode() ? IRACING_TIMEOUTS.elementWait : this.config.timeout;
try {
await page.waitForSelector(selector, { state: 'attached', timeout });
await page.waitForSelector(_selector, { state: 'attached', timeout });
await page.selectOption(selector, value);
return;
} catch {
@@ -754,14 +754,14 @@ export class IRacingDomInteractor {
// ignore
}
}
await page.waitForSelector(selector, { state: 'attached', timeout });
await page.waitForSelector(_selector, { state: 'attached', timeout });
await this.safeClickService.safeClick(selector, { timeout });
}
async openModalTrigger(type: string): Promise<void> {
const page = this.getPage();
const escaped = type.replace(/"/g, '\\"');
const selector = `button:has-text("${escaped}"), a:has-text("${escaped}"), [aria-label*="${escaped}" i], [data-action="${escaped}"], [data-modal-trigger="${escaped}"]`;
const _selector = `button:has-text("${escaped}"), a:has-text("${escaped}"), [aria-label*="${escaped}" i], [data-action="${escaped}"], [data-modal-trigger="${escaped}"]`;
const timeout = this.isRealMode() ? IRACING_TIMEOUTS.elementWait : this.config.timeout;
if (!this.isRealMode()) {
@@ -779,7 +779,7 @@ export class IRacingDomInteractor {
}
}
await page.waitForSelector(selector, { state: 'attached', timeout });
await page.waitForSelector(_selector, { state: 'attached', timeout });
await this.safeClickService.safeClick(selector, { timeout });
}
@@ -816,7 +816,7 @@ export class IRacingDomInteractor {
timeout: this.isRealMode() ? IRACING_TIMEOUTS.elementWait : this.config.timeout,
});
await page.waitForTimeout(150);
this.log('info', 'Add Car modal is visible', { selector: modalSelector });
this.log('info', 'Add Car modal is visible', { _selector: modalSelector });
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
this.log('warn', 'Add Car modal not found with primary selector, dumping #create-race-wizard innerHTML and retrying', {
@@ -832,7 +832,7 @@ export class IRacingDomInteractor {
timeout: 10000,
});
await page.waitForTimeout(150);
this.log('info', 'Add Car modal found after retry', { selector: modalSelectorRetry });
this.log('info', 'Add Car modal found after retry', { _selector: modalSelectorRetry });
} catch {
this.log('warn', 'Add Car modal still not found after retry');
}
@@ -885,7 +885,7 @@ export class IRacingDomInteractor {
for (const selector of directSelectors) {
const button = page.locator(selector).first();
if ((await button.count()) > 0 && (await button.isVisible())) {
await this.safeClickService.safeClick(selector, { timeout: IRACING_TIMEOUTS.elementWait });
await this.safeClickService.safeClick(_selector, { timeout: IRACING_TIMEOUTS.elementWait });
this.log('info', 'Clicked direct Select button for first search result', { selector });
return;
}
@@ -896,14 +896,14 @@ export class IRacingDomInteractor {
if ((await dropdownButton.count()) > 0 && (await dropdownButton.isVisible())) {
await this.safeClickService.safeClick(dropdownSelector, { timeout: IRACING_TIMEOUTS.elementWait });
this.log('debug', 'Clicked dropdown toggle, waiting for menu', { selector: dropdownSelector });
this.log('debug', 'Clicked dropdown toggle, waiting for menu', { _selector: dropdownSelector });
await page.waitForSelector('.dropdown-menu.show', { timeout: 3000 }).catch(() => {});
const itemSelector = IRACING_SELECTORS.steps.trackSelectDropdownItem;
await page.waitForTimeout(200);
await this.safeClickService.safeClick(itemSelector, { timeout: IRACING_TIMEOUTS.elementWait });
this.log('info', 'Clicked first dropdown item to select track config', { selector: itemSelector });
this.log('info', 'Clicked first dropdown item to select track config', { _selector: itemSelector });
return;
}
@@ -911,7 +911,7 @@ export class IRacingDomInteractor {
'.car-row, .car-item, [data-testid*="car"], [id*="favorite_cars"], [id*="select-car"]';
const carRow = page.locator(carRowSelector).first();
if ((await carRow.count()) > 0) {
this.log('info', 'Fallback: clicking car row/item to select', { selector: carRowSelector });
this.log('info', 'Fallback: clicking car row/item to select', { _selector: carRowSelector });
try {
await this.safeClickService.safeClick(carRowSelector, { timeout: IRACING_TIMEOUTS.elementWait });
this.log('info', 'Clicked car row fallback selector');
@@ -1078,7 +1078,7 @@ export class IRacingDomInteractor {
if (isVisible) {
await radioLabel.click({ timeout: IRACING_TIMEOUTS.elementWait });
this.log('info', 'Selected weather type', { weatherType, selector: labelSelector });
this.log('info', 'Selected weather type', { weatherType, _selector: labelSelector });
} else {
this.log('debug', 'Weather type radio not visible, may already be selected or step is different');
}

View File

@@ -102,7 +102,7 @@ export class IRacingDomNavigator {
selector = IRACING_SELECTORS.wizard.modal;
}
this.log('debug', 'Waiting for element', { target, selector, mode: this.config.mode });
this.log('debug', 'Waiting for element', { target, _selector, mode: this.config.mode });
await page.waitForSelector(selector, {
state: 'attached',
timeout: maxWaitMs ?? defaultTimeout,
@@ -164,7 +164,7 @@ export class IRacingDomNavigator {
}
try {
this.log('debug', `Waiting for wizard step: ${stepName}`, { selector: containerSelector });
this.log('debug', `Waiting for wizard step: ${stepName}`, { _selector: containerSelector });
await page.waitForSelector(containerSelector, {
state: 'attached',
timeout: 15000,
@@ -297,7 +297,7 @@ export class IRacingDomNavigator {
}
}
async checkWizardDismissed(currentStep: number): Promise<void> {
async checkWizardDismissed(_currentStep: number): Promise<void> {
if (!this.isRealMode() || currentStep < 3) {
return;
}

View File

@@ -42,7 +42,7 @@ export class SafeClickService {
* @param elementText Optional text content of the element (should be direct text only)
* @returns true if the selector/text matches a blocked pattern
*/
private isBlockedSelector(selector: string, elementText?: string): boolean {
private isBlockedSelector(_selector: string, elementText?: string): boolean {
const selectorLower = selector.toLowerCase();
const textLower = elementText?.toLowerCase().trim() ?? '';
@@ -88,7 +88,7 @@ export class SafeClickService {
* @param selector The CSS selector of the element to verify
* @throws Error if element is a blocked checkout/payment button
*/
async verifyNotBlockedElement(selector: string): Promise<void> {
async verifyNotBlockedElement(_selector: string): Promise<void> {
const page = this.browserSession.getPage();
if (!page) return;
@@ -191,7 +191,7 @@ export class SafeClickService {
if (error instanceof Error && error.message.includes('BLOCKED')) {
throw error;
}
this.log('debug', 'Could not verify element (may not exist yet)', { selector, error: String(error) });
this.log('debug', 'Could not verify element (may not exist yet)', { _selector, error: String(error) });
}
}
@@ -368,7 +368,7 @@ export class SafeClickService {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const useForce = options?.force || attempt === maxRetries;
await page.click(selector, { timeout, force: useForce });
await page.click(_selector, { timeout, force: useForce });
return;
} catch (error) {
if (error instanceof Error && error.message.includes('BLOCKED')) {
@@ -418,7 +418,7 @@ export class SafeClickService {
this.log('debug', 'JS fallback click did not find element or failed', { selector });
}
} catch (e) {
this.log('debug', 'JS fallback click error', { selector, error: String(e) });
this.log('debug', 'JS fallback click error', { _selector, error: String(e) });
}
this.log('error', 'Max retries reached, click still blocked', { selector });

View File

@@ -70,7 +70,7 @@ export class MockBrowserAutomationAdapter implements IBrowserAutomation, IAutoma
};
}
async clickElement(selector: string): Promise<ClickResultDTO> {
async clickElement(_selector: string): Promise<ClickResultDTO> {
const delay = this.randomDelay(50, 300);
await this.sleep(delay);
return {
@@ -79,7 +79,7 @@ export class MockBrowserAutomationAdapter implements IBrowserAutomation, IAutoma
};
}
async waitForElement(selector: string, maxWaitMs: number = 5000): Promise<WaitResultDTO> {
async waitForElement(_selector: string, maxWaitMs: number = 5000): Promise<WaitResultDTO> {
const delay = this.randomDelay(100, 1000);
await this.sleep(delay);

View File

@@ -3,17 +3,17 @@ import type { LogContext } from '../../../application/ports/LoggerContext';
import type { Logger } from '@core/shared/application';
export class NoOpLogAdapter implements LoggerPort, Logger {
debug(_message: string, _context?: LogContext): void {}
debug(__message: string, __context?: LogContext): void {}
info(_message: string, _context?: LogContext): void {}
info(__message: string, __context?: LogContext): void {}
warn(_message: string, _context?: LogContext): void {}
warn(__message: string, __context?: LogContext): void {}
error(_message: string, _error?: Error, _context?: LogContext): void {}
error(__message: string, __error?: Error, __context?: LogContext): void {}
fatal(_message: string, _error?: Error, _context?: LogContext): void {}
fatal(__message: string, __error?: Error, __context?: LogContext): void {}
child(_context: LogContext): LoggerPort {
child(__context: LogContext): LoggerPort {
return this;
}

View File

@@ -1,5 +1,5 @@
import { AutomationSession } from '../../domain/entities/AutomationSession';
import { SessionStateValue } from '../../domain/value-objects/SessionState';
import type { SessionRepositoryPort } from '../../application/ports/SessionRepositoryPort';
export class InMemorySessionRepository implements SessionRepositoryPort {

View File

@@ -39,7 +39,7 @@ export class GetMembershipFeesUseCase
const fee = await this.membershipFeeRepository.findByLeagueId(leagueId);
let payments: any[] = [];
let payments: unknown[] = [];
if (driverId && fee) {
const memberPayments = await this.memberPaymentRepository.findByLeagueIdAndDriverId(leagueId, driverId, this.membershipFeeRepository);
payments = memberPayments.map(p => ({

View File

@@ -1,6 +1,6 @@
import { ApproveLeagueJoinRequestUseCase } from '@core/racing/application/use-cases/ApproveLeagueJoinRequestUseCase';
import { ApproveLeagueJoinRequestPresenter } from '@apps/api/src/modules/league/presenters/ApproveLeagueJoinRequestPresenter';
import { ILeagueMembershipRepository } from '@core/racing/domain/repositories/ILeagueMembershipRepository';
describe('ApproveLeagueJoinRequestUseCase', () => {
let useCase: ApproveLeagueJoinRequestUseCase;
@@ -12,7 +12,7 @@ describe('ApproveLeagueJoinRequestUseCase', () => {
getJoinRequests: jest.fn(),
removeJoinRequest: jest.fn(),
saveMembership: jest.fn(),
} as any;
} as unknown;
presenter = new ApproveLeagueJoinRequestPresenter();
useCase = new ApproveLeagueJoinRequestUseCase(leagueMembershipRepository);
});

View File

@@ -6,7 +6,7 @@ import { Race } from '@core/racing/domain/entities/Race';
import { Result } from '@core/racing/domain/entities/Result';
import { League } from '@core/racing/domain/entities/League';
import { Standing } from '@core/racing/domain/entities/Standing';
import { LeagueMembership } from '@core/racing/domain/entities/LeagueMembership';
import type { FeedItem } from '@core/social/domain/types/FeedItem';
import type {
IDashboardOverviewPresenter,

View File

@@ -1,6 +1,6 @@
import { GetLeagueJoinRequestsUseCase } from '@core/racing/application/use-cases/GetLeagueJoinRequestsUseCase';
import { LeagueJoinRequestsPresenter } from '@apps/api/src/modules/league/presenters/LeagueJoinRequestsPresenter';
import { ILeagueMembershipRepository } from '@core/racing/domain/repositories/ILeagueMembershipRepository';
import { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepository';
describe('GetLeagueJoinRequestsUseCase', () => {
@@ -12,10 +12,10 @@ describe('GetLeagueJoinRequestsUseCase', () => {
beforeEach(() => {
leagueMembershipRepository = {
getJoinRequests: jest.fn(),
} as any;
} as unknown;
driverRepository = {
findByIds: jest.fn(),
} as any;
} as unknown;
presenter = new LeagueJoinRequestsPresenter();
useCase = new GetLeagueJoinRequestsUseCase(leagueMembershipRepository, driverRepository);
});

View File

@@ -2,11 +2,7 @@ import { describe, it, expect, beforeEach } from 'vitest';
import { JoinLeagueUseCase } from '@core/racing/application/use-cases/JoinLeagueUseCase';
import type { ILeagueMembershipRepository } from '@core/racing/domain/repositories/ILeagueMembershipRepository';
import {
LeagueMembership,
type MembershipRole,
type MembershipStatus,
} from '@core/racing/domain/entities/LeagueMembership';
class InMemoryLeagueMembershipRepository implements ILeagueMembershipRepository {
private memberships: LeagueMembership[] = [];

View File

@@ -17,7 +17,7 @@ import { Race } from '@core/racing/domain/entities/Race';
import { League } from '@core/racing/domain/entities/League';
import { Result } from '@core/racing/domain/entities/Result';
import { Driver } from '@core/racing/domain/entities/Driver';
import { LeagueMembership } from '@core/racing/domain/entities/LeagueMembership';
import { GetRaceDetailUseCase } from '@core/racing/application/use-cases/GetRaceDetailUseCase';
import { CancelRaceUseCase } from '@core/racing/application/use-cases/CancelRaceUseCase';

View File

@@ -5,7 +5,7 @@ import { League } from '@core/racing/domain/entities/League';
import { Result } from '@core/racing/domain/entities/Result';
import { Penalty } from '@core/racing/domain/entities/Penalty';
import { Standing } from '@core/racing/domain/entities/Standing';
import { Driver } from '@core/racing/domain/entities/Driver';
import { GetRaceResultsDetailUseCase } from '@core/racing/application/use-cases/GetRaceResultsDetailUseCase';
import { ImportRaceResultsUseCase } from '@core/racing/application/use-cases/ImportRaceResultsUseCase';

View File

@@ -5,11 +5,8 @@ import type { ILeagueMembershipRepository } from '@core/racing/domain/repositori
import type { ITeamRepository } from '@core/racing/domain/repositories/ITeamRepository';
import type { ITeamMembershipRepository } from '@core/racing/domain/repositories/ITeamMembershipRepository';
import type { RaceRegistration } from '@core/racing/domain/entities/RaceRegistration';
import {
LeagueMembership,
type MembershipStatus,
} from '@core/racing/domain/entities/LeagueMembership';
import { Team } from '@core/racing/domain/entities/Team';
import { Driver } from '@core/racing/domain/entities/Driver';
import type {
TeamMembership,
@@ -23,17 +20,17 @@ import { WithdrawFromRaceUseCase } from '@core/racing/application/use-cases/With
import { IsDriverRegisteredForRaceUseCase } from '@core/racing/application/use-cases/IsDriverRegisteredForRaceUseCase';
import { GetRaceRegistrationsUseCase } from '@core/racing/application/use-cases/GetRaceRegistrationsUseCase';
import { CreateTeamUseCase } from '@core/racing/application/use-cases/CreateTeamUseCase';
import { JoinTeamUseCase } from '@core/racing/application/use-cases/JoinTeamUseCase';
import { LeaveTeamUseCase } from '@core/racing/application/use-cases/LeaveTeamUseCase';
import { ApproveTeamJoinRequestUseCase } from '@core/racing/application/use-cases/ApproveTeamJoinRequestUseCase';
import { RejectTeamJoinRequestUseCase } from '@core/racing/application/use-cases/RejectTeamJoinRequestUseCase';
import { UpdateTeamUseCase } from '@core/racing/application/use-cases/UpdateTeamUseCase';
import { GetAllTeamsUseCase } from '@core/racing/application/use-cases/GetAllTeamsUseCase';
import { GetTeamDetailsUseCase } from '@core/racing/application/use-cases/GetTeamDetailsUseCase';
import { GetTeamMembersUseCase } from '@core/racing/application/use-cases/GetTeamMembersUseCase';
import { GetTeamJoinRequestsUseCase } from '@core/racing/application/use-cases/GetTeamJoinRequestsUseCase';
import { GetDriverTeamUseCase } from '@core/racing/application/use-cases/GetDriverTeamUseCase';
import type { IDriverRegistrationStatusPresenter } from '@core/racing/application/presenters/IDriverRegistrationStatusPresenter';
import type { IRaceRegistrationsPresenter } from '@core/racing/application/presenters/IRaceRegistrationsPresenter';
import type {
@@ -510,9 +507,9 @@ describe('Racing application use-cases - teams', () => {
description: team.description,
memberCount: team.memberCount,
leagues: team.leagues,
specialization: (team as any).specialization,
region: (team as any).region,
languages: (team as any).languages,
specialization: (team as unknown).specialization,
region: (team as unknown).region,
languages: (team as unknown).languages,
})),
totalCount: input.teams.length,
};
@@ -522,7 +519,7 @@ describe('Racing application use-cases - teams', () => {
return this.viewModel;
}
get teams(): any[] {
get teams(): unknown[] {
return this.viewModel?.teams ?? [];
}
}
@@ -559,7 +556,7 @@ describe('Racing application use-cases - teams', () => {
return {
driverId,
driverName,
role: ((membership.role as any) === 'owner' ? 'owner' : (membership.role as any) === 'member' ? 'member' : (membership.role as any) === 'manager' ? 'manager' : (membership.role as any) === 'driver' ? 'member' : 'member') as "owner" | "member" | "manager",
role: ((membership.role as unknown) === 'owner' ? 'owner' : (membership.role as unknown) === 'member' ? 'member' : (membership.role as unknown) === 'manager' ? 'manager' : (membership.role as unknown) === 'driver' ? 'member' : 'member') as "owner" | "member" | "manager",
joinedAt: membership.joinedAt.toISOString(),
isActive: membership.status === 'active',
avatarUrl,
@@ -568,7 +565,7 @@ describe('Racing application use-cases - teams', () => {
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 as any) === 'member').length;
const memberCount = members.filter((m) => (m.role as unknown) === 'member').length;
this.viewModel = {
members,
@@ -583,7 +580,7 @@ describe('Racing application use-cases - teams', () => {
return this.viewModel;
}
get members(): any[] {
get members(): unknown[] {
return this.viewModel?.members ?? [];
}
}
@@ -625,7 +622,7 @@ describe('Racing application use-cases - teams', () => {
return this.viewModel;
}
get requests(): any[] {
get requests(): unknown[] {
return this.viewModel?.requests ?? [];
}
}

View File

@@ -1,6 +1,6 @@
import { RejectLeagueJoinRequestUseCase } from '@core/racing/application/use-cases/RejectLeagueJoinRequestUseCase';
import { RejectLeagueJoinRequestPresenter } from '@apps/api/src/modules/league/presenters/RejectLeagueJoinRequestPresenter';
import { ILeagueMembershipRepository } from '@core/racing/domain/repositories/ILeagueMembershipRepository';
describe('RejectLeagueJoinRequestUseCase', () => {
let useCase: RejectLeagueJoinRequestUseCase;
@@ -10,7 +10,7 @@ describe('RejectLeagueJoinRequestUseCase', () => {
beforeEach(() => {
leagueMembershipRepository = {
removeJoinRequest: jest.fn(),
} as any;
} as unknown;
presenter = new RejectLeagueJoinRequestPresenter();
useCase = new RejectLeagueJoinRequestUseCase(leagueMembershipRepository);
});

View File

@@ -1,6 +1,6 @@
import { RemoveLeagueMemberUseCase } from '@core/racing/application/use-cases/RemoveLeagueMemberUseCase';
import { RemoveLeagueMemberPresenter } from '@apps/api/src/modules/league/presenters/RemoveLeagueMemberPresenter';
import { ILeagueMembershipRepository } from '@core/racing/domain/repositories/ILeagueMembershipRepository';
describe('RemoveLeagueMemberUseCase', () => {
let useCase: RemoveLeagueMemberUseCase;
@@ -11,7 +11,7 @@ describe('RemoveLeagueMemberUseCase', () => {
leagueMembershipRepository = {
getLeagueMembers: jest.fn(),
saveMembership: jest.fn(),
} as any;
} as unknown;
presenter = new RemoveLeagueMemberPresenter();
useCase = new RemoveLeagueMemberUseCase(leagueMembershipRepository);
});

View File

@@ -1,6 +1,6 @@
import { UpdateLeagueMemberRoleUseCase } from '@core/racing/application/use-cases/UpdateLeagueMemberRoleUseCase';
import { UpdateLeagueMemberRolePresenter } from '@apps/api/src/modules/league/presenters/UpdateLeagueMemberRolePresenter';
import { ILeagueMembershipRepository } from '@core/racing/domain/repositories/ILeagueMembershipRepository';
describe('UpdateLeagueMemberRoleUseCase', () => {
let useCase: UpdateLeagueMemberRoleUseCase;
@@ -11,7 +11,7 @@ describe('UpdateLeagueMemberRoleUseCase', () => {
leagueMembershipRepository = {
getLeagueMembers: jest.fn(),
saveMembership: jest.fn(),
} as any;
} as unknown;
presenter = new UpdateLeagueMemberRolePresenter();
useCase = new UpdateLeagueMemberRoleUseCase(leagueMembershipRepository);
});

View File

@@ -14,7 +14,7 @@ export interface GetLeagueJoinRequestsViewModel {
}
export interface GetLeagueJoinRequestsResultDTO {
joinRequests: any[];
joinRequests: unknown[];
drivers: { id: string; name: string }[];
}

View File

@@ -14,7 +14,7 @@ export interface GetLeagueMembershipsViewModel {
}
export interface GetLeagueMembershipsResultDTO {
memberships: any[];
memberships: unknown[];
drivers: { id: string; name: string }[];
}

View File

@@ -1,14 +1,14 @@
import type { Presenter } from '@core/shared/presentation/Presenter';
export interface GetLeagueProtestsViewModel {
protests: any[];
protests: unknown[];
racesById: Record<string, any>;
driversById: Record<string, any>;
}
export interface GetLeagueProtestsResultDTO {
protests: any[];
races: any[];
protests: unknown[];
races: unknown[];
drivers: { id: string; name: string }[];
}

View File

@@ -15,7 +15,7 @@ export interface GetLeagueSeasonsViewModel {
}
export interface GetLeagueSeasonsResultDTO {
seasons: any[];
seasons: unknown[];
}
export interface IGetLeagueSeasonsPresenter extends Presenter<GetLeagueSeasonsResultDTO, GetLeagueSeasonsViewModel> {}

View File

@@ -28,7 +28,7 @@ export interface RacesPageViewModel {
}
export interface RacesPageResultDTO {
races: any[];
races: unknown[];
}
export interface IRacesPagePresenter

View File

@@ -96,7 +96,7 @@ export class AcceptSponsorshipRequestUseCase
platformFee: acceptedRequest.getPlatformFee().amount,
netAmount: acceptedRequest.getNetAmount().amount,
};
} catch (error: any) {
} catch (error) {
this.logger.error(`Failed to accept sponsorship request ${dto.requestId}: ${error.message}`, { requestId: dto.requestId, error: error.message, stack: error.stack });
throw error;
}

View File

@@ -128,7 +128,7 @@ export class CreateLeagueWithSeasonAndScoringUseCase
};
this.logger.debug('CreateLeagueWithSeasonAndScoringUseCase completed successfully.', { result });
return result;
} catch (error: any) {
} catch (error) {
this.logger.error('Error during CreateLeagueWithSeasonAndScoringUseCase execution.', {
command,
error: error.message,

View File

@@ -4,7 +4,7 @@
* Creates a new sponsor.
*/
import { Sponsor, type SponsorProps } from '../../domain/entities/Sponsor';
import type { ISponsorRepository } from '../../domain/repositories/ISponsorRepository';
import type {
ICreateSponsorPresenter,
@@ -41,7 +41,7 @@ export class CreateSponsorUseCase
contactEmail: input.contactEmail,
...(input.websiteUrl !== undefined ? { websiteUrl: input.websiteUrl } : {}),
...(input.logoUrl !== undefined ? { logoUrl: input.logoUrl } : {}),
} as any);
} as unknown);
await this.sponsorRepository.create(sponsor);

View File

@@ -138,8 +138,8 @@ export class GetDashboardOverviewUseCase {
presenter.present(viewModel);
}
private async getDriverLeagues(allLeagues: any[], driverId: string): Promise<any[]> {
const driverLeagues: any[] = [];
private async getDriverLeagues(allLeagues: unknown[], driverId: string): Promise<any[]> {
const driverLeagues: unknown[] = [];
for (const league of allLeagues) {
const membership = await this.leagueMembershipRepository.getMembership(league.id, driverId);
@@ -152,7 +152,7 @@ export class GetDashboardOverviewUseCase {
}
private async partitionUpcomingRacesByRegistration(
upcomingRaces: any[],
upcomingRaces: unknown[],
driverId: string,
leagueMap: Map<string, string>,
): Promise<{
@@ -194,9 +194,9 @@ export class GetDashboardOverviewUseCase {
}
private buildRecentResults(
allResults: any[],
allRaces: any[],
allLeagues: any[],
allResults: unknown[],
allRaces: unknown[],
allLeagues: unknown[],
driverId: string,
): DashboardRecentResultViewModel[] {
const raceById = new Map(allRaces.map(race => [race.id, race]));
@@ -237,7 +237,7 @@ export class GetDashboardOverviewUseCase {
}
private async buildLeagueStandingsSummaries(
driverLeagues: any[],
driverLeagues: unknown[],
driverId: string,
): Promise<DashboardLeagueStandingSummaryViewModel[]> {
const summaries: DashboardLeagueStandingSummaryViewModel[] = [];
@@ -277,7 +277,7 @@ export class GetDashboardOverviewUseCase {
return activeLeagueIds.size;
}
private buildFeedSummary(feedItems: any[]): DashboardFeedSummaryViewModel {
private buildFeedSummary(feedItems: unknown[]): DashboardFeedSummaryViewModel {
const items: DashboardFeedItemSummaryViewModel[] = feedItems.map(item => ({
id: item.id,
type: item.type,
@@ -297,7 +297,7 @@ export class GetDashboardOverviewUseCase {
};
}
private buildFriendsSummary(friends: any[]): DashboardFriendSummaryViewModel[] {
private buildFriendsSummary(friends: unknown[]): DashboardFriendSummaryViewModel[] {
return friends.map(friend => ({
id: friend.id,
name: friend.name,

View File

@@ -8,7 +8,7 @@ export interface GetLeagueJoinRequestsUseCaseParams {
}
export interface GetLeagueJoinRequestsResultDTO {
joinRequests: any[];
joinRequests: unknown[];
drivers: { id: string; name: string }[];
}

View File

@@ -9,8 +9,8 @@ export interface GetLeagueProtestsUseCaseParams {
}
export interface GetLeagueProtestsResultDTO {
protests: any[];
races: any[];
protests: unknown[];
races: unknown[];
drivers: { id: string; name: string }[];
}

View File

@@ -7,7 +7,7 @@ export interface GetLeagueSeasonsUseCaseParams {
}
export interface GetLeagueSeasonsResultDTO {
seasons: any[];
seasons: unknown[];
}
export class GetLeagueSeasonsUseCase implements UseCase<GetLeagueSeasonsUseCaseParams, GetLeagueSeasonsResultDTO, GetLeagueSeasonsViewModel, IGetLeagueSeasonsPresenter> {

View File

@@ -202,7 +202,7 @@ export class GetProfileOverviewUseCase {
private async buildTeamMemberships(
driverId: string,
teams: any[],
teams: unknown[],
): Promise<ProfileOverviewTeamMembershipViewModel[]> {
const memberships: ProfileOverviewTeamMembershipViewModel[] = [];
@@ -231,7 +231,7 @@ export class GetProfileOverviewUseCase {
return memberships;
}
private buildSocialSummary(friends: any[]): ProfileOverviewSocialSummaryViewModel {
private buildSocialSummary(friends: unknown[]): ProfileOverviewSocialSummaryViewModel {
return {
friendsCount: friends.length,
friends: friends.map(friend => ({

View File

@@ -32,7 +32,7 @@ export class GetTeamsLeaderboardUseCase
async execute(_input: void, presenter: ITeamsLeaderboardPresenter): Promise<void> {
const allTeams = await this.teamRepository.findAll();
const teams: any[] = [];
const teams: unknown[] = [];
await Promise.all(
allTeams.map(async (team) => {

View File

@@ -3,11 +3,7 @@ import type {
ILeagueMembershipRepository,
} from '@core/racing/domain/repositories/ILeagueMembershipRepository';
import type { AsyncUseCase } from '@core/shared/application';
import {
LeagueMembership,
type MembershipRole,
type MembershipStatus,
} from '@core/racing/domain/entities/LeagueMembership';
import type { JoinLeagueCommandDTO } from '../dto/JoinLeagueCommandDTO';
import { BusinessRuleViolationError } from '../errors/RacingApplicationError';

View File

@@ -1,6 +1,6 @@
import type { ITeamRepository } from '../../domain/repositories/ITeamRepository';
import type { ITeamMembershipRepository } from '../../domain/repositories/ITeamMembershipRepository';
import { Team } from '../../domain/entities/Team';
import type { UpdateTeamCommandDTO } from '../dto/TeamCommandAndQueryDTO';
export class UpdateTeamUseCase {

View File

@@ -4,7 +4,7 @@
* Represents a driver's custom livery for a specific car.
* Includes user-placed decals and league-specific overrides.
*/
import { RacingDomainValidationError, RacingDomainInvariantError, RacingDomainError } from '../errors/RacingDomainError';
import type { IEntity } from '@core/shared/domain';
import type { LiveryDecal } from '../value-objects/LiveryDecal';

View File

@@ -4,7 +4,7 @@
* Represents an admin-defined livery template for a specific car.
* Contains base image and sponsor decal placements.
*/
import { RacingDomainValidationError, RacingDomainInvariantError, RacingDomainError } from '../errors/RacingDomainError';
import type { IEntity } from '@core/shared/domain';
import type { LiveryDecal } from '../value-objects/LiveryDecal';

View File

@@ -4,10 +4,7 @@ import {
RacingDomainInvariantError,
RacingDomainValidationError,
} from '@core/racing/domain/errors/RacingDomainError';
import {
Season,
type SeasonStatus,
} from '@core/racing/domain/entities/Season';
import { SeasonScoringConfig } from '@core/racing/domain/value-objects/SeasonScoringConfig';
import {
SeasonDropPolicy,

View File

@@ -5,7 +5,7 @@
* Immutable entity with factory methods and domain validation.
*/
import { RacingDomainError, RacingDomainValidationError } from '../errors/RacingDomainError';
import type { IEntity } from '@core/shared/domain';
export class Standing implements IEntity<string> {

View File

@@ -1,7 +1,7 @@
import { SeasonSchedule } from '../value-objects/SeasonSchedule';
import { ScheduledRaceSlot } from '../value-objects/ScheduledRaceSlot';
import type { RecurrenceStrategy } from '../value-objects/RecurrenceStrategy';
import { RacingDomainError, RacingDomainValidationError } from '../errors/RacingDomainError';
import { RaceTimeOfDay } from '../value-objects/RaceTimeOfDay';
import type { Weekday } from '../types/Weekday';
import { weekdayToIndex } from '../types/Weekday';