module cleanup

This commit is contained in:
2025-12-19 01:22:45 +01:00
parent d617654928
commit d0fac9e6c1
135 changed files with 5104 additions and 1315 deletions

View File

@@ -0,0 +1,108 @@
import { vi } from 'vitest';
import { AuthController } from './AuthController';
import { AuthService } from './AuthService';
import { SignupParams, LoginParams, AuthSessionDTO } from './dtos/AuthDto';
describe('AuthController', () => {
let controller: AuthController;
let service: ReturnType<typeof vi.mocked<AuthService>>;
beforeEach(() => {
service = vi.mocked<AuthService>({
signupWithEmail: vi.fn(),
loginWithEmail: vi.fn(),
getCurrentSession: vi.fn(),
logout: vi.fn(),
});
controller = new AuthController(service);
});
describe('signup', () => {
it('should call service.signupWithEmail and return session', async () => {
const params: SignupParams = {
email: 'test@example.com',
password: 'password123',
displayName: 'Test User',
iracingCustomerId: '12345',
primaryDriverId: 'driver1',
avatarUrl: 'http://example.com/avatar.jpg',
};
const session: AuthSessionDTO = {
token: 'token123',
user: {
userId: 'user1',
email: 'test@example.com',
displayName: 'Test User',
},
};
service.signupWithEmail.mockResolvedValue(session);
const result = await controller.signup(params);
expect(service.signupWithEmail).toHaveBeenCalledWith(params);
expect(result).toEqual(session);
});
});
describe('login', () => {
it('should call service.loginWithEmail and return session', async () => {
const params: LoginParams = {
email: 'test@example.com',
password: 'password123',
};
const session: AuthSessionDTO = {
token: 'token123',
user: {
userId: 'user1',
email: 'test@example.com',
displayName: 'Test User',
},
};
service.loginWithEmail.mockResolvedValue(session);
const result = await controller.login(params);
expect(service.loginWithEmail).toHaveBeenCalledWith(params);
expect(result).toEqual(session);
});
});
describe('getSession', () => {
it('should call service.getCurrentSession and return session', async () => {
const session: AuthSessionDTO = {
token: 'token123',
user: {
userId: 'user1',
email: 'test@example.com',
displayName: 'Test User',
},
};
service.getCurrentSession.mockResolvedValue(session);
const result = await controller.getSession();
expect(service.getCurrentSession).toHaveBeenCalled();
expect(result).toEqual(session);
});
it('should return null if no session', async () => {
service.getCurrentSession.mockResolvedValue(null);
const result = await controller.getSession();
expect(result).toBeNull();
});
});
describe('logout', () => {
it('should call service.logout', async () => {
service.logout.mockResolvedValue(undefined);
await controller.logout();
expect(service.logout).toHaveBeenCalled();
});
});
});

View File

@@ -1,7 +1,6 @@
import { Controller, Get, Post, Body, Query, Res, Redirect, HttpStatus } from '@nestjs/common';
import { Response } from 'express';
import { Controller, Get, Post, Body } from '@nestjs/common';
import { AuthService } from './AuthService';
import { LoginParams, SignupParams, LoginWithIracingCallbackParams, AuthSessionDTO, IracingAuthRedirectResult } from './dto/AuthDto';
import { LoginParams, SignupParams, AuthSessionDTO } from './dtos/AuthDto';
@Controller('auth')
export class AuthController {
@@ -27,16 +26,4 @@ export class AuthController {
return this.authService.logout();
}
@Get('iracing/start')
async startIracingAuthRedirect(@Query('returnTo') returnTo?: string, @Res() res?: Response): Promise<void> {
const { redirectUrl, state } = await this.authService.startIracingAuthRedirect(returnTo);
// In real application, you might want to store 'state' in a secure cookie or session.
// For this example, we'll just redirect.
res.redirect(HttpStatus.FOUND, redirectUrl);
}
@Get('iracing/callback')
async loginWithIracingCallback(@Query('code') code: string, @Query('state') state: string, @Query('returnTo') returnTo?: string): Promise<AuthSessionDTO> {
return this.authService.loginWithIracingCallback({ code, state, returnTo });
}
}

View File

@@ -0,0 +1,30 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AuthModule } from './AuthModule';
import { AuthController } from './AuthController';
import { AuthService } from './AuthService';
describe('AuthModule', () => {
let module: TestingModule;
beforeEach(async () => {
module = await Test.createTestingModule({
imports: [AuthModule],
}).compile();
});
it('should compile the module', () => {
expect(module).toBeDefined();
});
it('should provide AuthController', () => {
const controller = module.get<AuthController>(AuthController);
expect(controller).toBeDefined();
expect(controller).toBeInstanceOf(AuthController);
});
it('should provide AuthService', () => {
const service = module.get<AuthService>(AuthService);
expect(service).toBeDefined();
expect(service).toBeInstanceOf(AuthService);
});
});

View File

@@ -5,7 +5,7 @@ import { AuthProviders } from './AuthProviders';
@Module({
controllers: [AuthController],
providers: AuthProviders,
providers: [AuthService, ...AuthProviders],
exports: [AuthService],
})
export class AuthModule {}

View File

@@ -1,9 +1,7 @@
import { Provider } from '@nestjs/common';
import { AuthService } from './AuthService';
// Import interfaces and concrete implementations
import type { IAuthRepository } from '@core/identity/domain/repositories/IAuthRepository';
import { IUserRepository, StoredUser } from '@core/identity/domain/repositories/IUserRepository';
import { StoredUser } from '@core/identity/domain/repositories/IUserRepository';
import type { IPasswordHashingService } from '@core/identity/domain/services/PasswordHashingService';
import type { Logger } from '@core/shared/application';
@@ -11,7 +9,6 @@ import { InMemoryAuthRepository } from '@adapters/identity/persistence/inmemory/
import { InMemoryUserRepository } from '@adapters/identity/persistence/inmemory/InMemoryUserRepository';
import { InMemoryPasswordHashingService } from '@adapters/identity/services/InMemoryPasswordHashingService';
import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
import { IdentitySessionPort } from '@core/identity/application/ports/IdentitySessionPort';
import { CookieIdentitySessionAdapter } from '@adapters/identity/session/CookieIdentitySessionAdapter';
// Define the tokens for dependency injection
@@ -22,10 +19,9 @@ export const LOGGER_TOKEN = 'Logger';
export const IDENTITY_SESSION_PORT_TOKEN = 'IdentitySessionPort';
export const AuthProviders: Provider[] = [
AuthService, // Provide the service itself
{
provide: AUTH_REPOSITORY_TOKEN,
useFactory: (userRepository: IUserRepository, passwordHashingService: IPasswordHashingService, logger: Logger) => {
useFactory: (passwordHashingService: IPasswordHashingService, logger: Logger) => {
// Seed initial users for InMemoryUserRepository
const initialUsers: StoredUser[] = [
// Example user (replace with actual test users as needed)
@@ -41,7 +37,7 @@ export const AuthProviders: Provider[] = [
const inMemoryUserRepository = new InMemoryUserRepository(logger, initialUsers);
return new InMemoryAuthRepository(inMemoryUserRepository, passwordHashingService, logger);
},
inject: [USER_REPOSITORY_TOKEN, PASSWORD_HASHING_SERVICE_TOKEN, LOGGER_TOKEN],
inject: [PASSWORD_HASHING_SERVICE_TOKEN, LOGGER_TOKEN],
},
{
provide: USER_REPOSITORY_TOKEN,

View File

@@ -1,33 +1,26 @@
import { Injectable, Inject, InternalServerErrorException } from '@nestjs/common';
import type { AuthenticatedUserDTO, AuthSessionDTO, SignupParams, LoginParams, IracingAuthRedirectResult, LoginWithIracingCallbackParams } from './dto/AuthDto';
import { Inject, Injectable, InternalServerErrorException } from '@nestjs/common';
// Core Use Cases
import { LoginUseCase } from '@core/identity/application/use-cases/LoginUseCase';
import { SignupUseCase } from '@core/identity/application/use-cases/SignupUseCase';
import { GetCurrentSessionUseCase } from '@core/identity/application/use-cases/GetCurrentSessionUseCase';
import { LogoutUseCase } from '@core/identity/application/use-cases/LogoutUseCase';
import { StartIracingAuthRedirectUseCase } from '@core/identity/application/use-cases/StartIracingAuthRedirectUseCase';
import { LoginWithIracingCallbackUseCase } from '@core/identity/application/use-cases/LoginWithIracingCallbackUseCase';
import { SignupUseCase } from '@core/identity/application/use-cases/SignupUseCase';
// Core Interfaces and Tokens
import { AUTH_REPOSITORY_TOKEN, PASSWORD_HASHING_SERVICE_TOKEN, LOGGER_TOKEN, IDENTITY_SESSION_PORT_TOKEN, USER_REPOSITORY_TOKEN } from './AuthProviders';
import { AuthenticatedUserDTO as CoreAuthenticatedUserDTO } from '@core/identity/application/dto/AuthenticatedUserDTO';
import { IdentitySessionPort } from '@core/identity/application/ports/IdentitySessionPort';
import { User } from '@core/identity/domain/entities/User';
import type { IAuthRepository } from '@core/identity/domain/repositories/IAuthRepository';
import type { IUserRepository } from '@core/identity/domain/repositories/IUserRepository';
import type { IPasswordHashingService } from '@core/identity/domain/services/PasswordHashingService';
import type { Logger } from "@core/shared/application";
import { IdentitySessionPort } from '@core/identity/application/ports/IdentitySessionPort';
import { UserId } from '@core/identity/domain/value-objects/UserId';
import { User } from '@core/identity/domain/entities/User';
import type { IUserRepository } from '@core/identity/domain/repositories/IUserRepository';
import { AuthenticatedUserDTO as CoreAuthenticatedUserDTO } from '@core/identity/application/dto/AuthenticatedUserDTO';
import { AUTH_REPOSITORY_TOKEN, IDENTITY_SESSION_PORT_TOKEN, LOGGER_TOKEN, PASSWORD_HASHING_SERVICE_TOKEN, USER_REPOSITORY_TOKEN } from './AuthProviders';
import { AuthSessionDTO, LoginParams, SignupParams, AuthenticatedUserDTO } from './dtos/AuthDto';
@Injectable()
export class AuthService {
private readonly loginUseCase: LoginUseCase;
private readonly signupUseCase: SignupUseCase;
private readonly getCurrentSessionUseCase: GetCurrentSessionUseCase;
private readonly logoutUseCase: LogoutUseCase;
private readonly startIracingAuthRedirectUseCase: StartIracingAuthRedirectUseCase;
private readonly loginWithIracingCallbackUseCase: LoginWithIracingCallbackUseCase;
constructor(
@Inject(AUTH_REPOSITORY_TOKEN) private authRepository: IAuthRepository,
@@ -38,10 +31,7 @@ export class AuthService {
) {
this.loginUseCase = new LoginUseCase(this.authRepository, this.passwordHashingService);
this.signupUseCase = new SignupUseCase(this.authRepository, this.passwordHashingService);
this.getCurrentSessionUseCase = new GetCurrentSessionUseCase(); // Doesn't have constructor parameters normally
this.logoutUseCase = new LogoutUseCase(this.identitySessionPort);
this.startIracingAuthRedirectUseCase = new StartIracingAuthRedirectUseCase();
this.loginWithIracingCallbackUseCase = new LoginWithIracingCallbackUseCase();
}
private mapUserToAuthenticatedUserDTO(user: User): AuthenticatedUserDTO {
@@ -49,10 +39,14 @@ export class AuthService {
userId: user.getId().value,
email: user.getEmail() ?? '',
displayName: user.getDisplayName() ?? '',
// Map other fields as necessary
iracingCustomerId: user.getIracingCustomerId() ?? undefined,
primaryDriverId: user.getPrimaryDriverId() ?? undefined,
avatarUrl: user.getAvatarUrl() ?? undefined,
};
}
private mapToCoreAuthenticatedUserDTO(apiDto: AuthenticatedUserDTO): CoreAuthenticatedUserDTO {
return {
id: apiDto.userId,
displayName: apiDto.displayName,
email: apiDto.email,
};
}
@@ -85,7 +79,8 @@ export class AuthService {
// Create session after successful signup
const authenticatedUserDTO = this.mapUserToAuthenticatedUserDTO(user);
const session = await this.identitySessionPort.createSession(authenticatedUserDTO as CoreAuthenticatedUserDTO);
const coreDto = this.mapToCoreAuthenticatedUserDTO(authenticatedUserDTO);
const session = await this.identitySessionPort.createSession(coreDto);
return {
token: session.token,
@@ -99,7 +94,8 @@ export class AuthService {
const user = await this.loginUseCase.execute(params.email, params.password);
// Create session after successful login
const authenticatedUserDTO = this.mapUserToAuthenticatedUserDTO(user);
const session = await this.identitySessionPort.createSession(authenticatedUserDTO as CoreAuthenticatedUserDTO);
const coreDto = this.mapToCoreAuthenticatedUserDTO(authenticatedUserDTO);
const session = await this.identitySessionPort.createSession(coreDto);
return {
token: session.token,
@@ -111,27 +107,6 @@ export class AuthService {
}
}
async startIracingAuthRedirect(returnTo?: string): Promise<IracingAuthRedirectResult> {
this.logger.debug('[AuthService] Starting iRacing auth redirect.');
// Note: The StartIracingAuthRedirectUseCase takes optional returnTo, but the DTO doesnt
const result = await this.startIracingAuthRedirectUseCase.execute(returnTo);
// Map core IracingAuthRedirectResult to AuthDto's IracingAuthRedirectResult
return { redirectUrl: result.redirectUrl, state: result.state };
}
async loginWithIracingCallback(params: LoginWithIracingCallbackParams): Promise<AuthSessionDTO> {
this.logger.debug(`[AuthService] Handling iRacing callback for code: ${params.code}`);
const user = await this.loginWithIracingCallbackUseCase.execute(params); // Pass params as is
// Create session after successful iRacing login
const authenticatedUserDTO = this.mapUserToAuthenticatedUserDTO(user);
const session = await this.identitySessionPort.createSession(authenticatedUserDTO as CoreAuthenticatedUserDTO);
return {
token: session.token,
user: authenticatedUserDTO,
};
}
async logout(): Promise<void> {
this.logger.debug('[AuthService] Attempting logout.');