fix adapters

This commit is contained in:
2025-12-23 20:43:57 +01:00
parent b5431355ca
commit 16cd572c63
28 changed files with 1357 additions and 15 deletions

View File

@@ -0,0 +1,56 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { Logger } from '@core/shared/application';
import type { UserAchievement } from '@core/identity';
import { InMemoryAchievementRepository } from './InMemoryAchievementRepository';
describe('InMemoryAchievementRepository (identity)', () => {
let repository: InMemoryAchievementRepository;
let logger: Logger;
beforeEach(() => {
logger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
} as unknown as Logger;
repository = new InMemoryAchievementRepository(logger);
});
it('seeds predefined achievements', async () => {
const all = await repository.findAllAchievements();
expect(all.length).toBeGreaterThan(0);
const first = all[0]!;
const found = await repository.findAchievementById(first.id);
expect(found?.id).toBe(first.id);
});
it('creates and queries user achievements and stats', async () => {
const all = await repository.findAllAchievements();
const achievement = all[0]!;
const userId = 'user-1';
const ua: UserAchievement = {
id: 'ua-1',
userId,
achievementId: achievement.id,
isComplete: () => true,
} as unknown as UserAchievement;
await repository.createUserAchievement(ua);
expect(await repository.hasUserEarnedAchievement(userId, achievement.id)).toBe(true);
const leaderboard = await repository.getAchievementLeaderboard(10);
expect(leaderboard.length).toBe(1);
expect(leaderboard[0]?.userId).toBe(userId);
expect(leaderboard[0]?.count).toBe(1);
const stats = await repository.getUserAchievementStats(userId);
expect(stats.total).toBe(1);
expect(stats.points).toBeGreaterThan(0);
expect(Object.values(stats.byCategory).reduce((a, b) => a + b, 0)).toBe(1);
});
});

View File

@@ -0,0 +1,69 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { Logger } from '@core/shared/application';
import { UserId } from '@core/identity';
import { User } from '@core/identity/domain/entities/User';
import { InMemoryUserRepository } from './InMemoryUserRepository';
import { InMemoryAuthRepository } from './InMemoryAuthRepository';
import { InMemoryPasswordHashingService } from '../../services/InMemoryPasswordHashingService';
describe('InMemoryAuthRepository', () => {
let mockLogger: Logger;
beforeEach(() => {
mockLogger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
} as unknown as Logger;
});
it('creates and verifies a user password', async () => {
const userRepo = new InMemoryUserRepository(mockLogger);
const passwordService = new InMemoryPasswordHashingService();
const authRepo = new InMemoryAuthRepository(userRepo, passwordService, mockLogger);
const user = User.create({
id: UserId.fromString('user-1'),
displayName: 'Test User',
email: 'test@example.com',
});
const created = await authRepo.create(user, 'password123');
expect(created.getEmail()).toBe('test@example.com');
expect(await authRepo.userExists('test@example.com')).toBe(true);
const ok = await authRepo.verifyPassword('test@example.com', 'password123');
expect(ok).not.toBeNull();
expect(ok?.getId().value).toBe('user-1');
const bad = await authRepo.verifyPassword('test@example.com', 'wrong');
expect(bad).toBeNull();
});
it('save updates existing user', async () => {
const userRepo = new InMemoryUserRepository(mockLogger);
const passwordService = new InMemoryPasswordHashingService();
const authRepo = new InMemoryAuthRepository(userRepo, passwordService, mockLogger);
const user = User.create({
id: UserId.fromString('user-2'),
displayName: 'User Two',
email: 'two@example.com',
});
await authRepo.create(user, 'pw');
const updated = User.create({
id: UserId.fromString('user-2'),
displayName: 'User Two Updated',
email: 'two@example.com',
});
await authRepo.save(updated);
const stored = await userRepo.findById('user-2');
expect(stored?.displayName).toBe('User Two Updated');
});
});

View File

@@ -0,0 +1,54 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { Logger } from '@core/shared/application';
import { UserId, type SponsorAccount } from '@core/identity';
import { InMemorySponsorAccountRepository } from './InMemorySponsorAccountRepository';
describe('InMemorySponsorAccountRepository', () => {
let repository: InMemorySponsorAccountRepository;
let logger: Logger;
const makeAccount = (id: string, sponsorId: string, email: string): SponsorAccount => {
return {
getId: () => ({ value: id }),
getSponsorId: () => sponsorId,
getEmail: () => email,
} as unknown as SponsorAccount;
};
beforeEach(() => {
logger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
} as unknown as Logger;
repository = new InMemorySponsorAccountRepository(logger);
repository.clear();
});
it('saves and finds sponsor accounts by id / sponsorId / email', async () => {
const account = makeAccount('user-1', 'sponsor-1', 'Sponsor@Example.com');
await repository.save(account);
expect(await repository.findById(UserId.fromString('user-1'))).toBe(account);
expect(await repository.findBySponsorId('sponsor-1')).toBe(account);
expect(await repository.findByEmail('sponsor@example.com')).toBe(account);
});
it('deletes sponsor accounts', async () => {
const account = makeAccount('user-del', 'sponsor-del', 'del@example.com');
await repository.save(account);
await repository.delete(UserId.fromString('user-del'));
expect(await repository.findById(UserId.fromString('user-del'))).toBeNull();
});
it('seeds via constructor', async () => {
const seeded = makeAccount('user-seed', 'sponsor-seed', 'seed@example.com');
repository = new InMemorySponsorAccountRepository(logger, [seeded]);
expect(await repository.findBySponsorId('sponsor-seed')).toBe(seeded);
});
});

View File

@@ -0,0 +1,57 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { Logger } from '@core/shared/application';
import type { UserRating } from '@core/identity';
import { InMemoryUserRatingRepository } from './InMemoryUserRatingRepository';
describe('InMemoryUserRatingRepository', () => {
let repository: InMemoryUserRatingRepository;
let mockLogger: Logger;
const rating = (userId: string, driverValue: number, trustValue: number, canSteward: boolean): UserRating => {
return {
userId,
driver: { sampleSize: 10, value: driverValue },
trust: { sampleSize: 5, value: trustValue },
canBeSteward: () => canSteward,
getDriverTier: () => (driverValue >= 2400 ? 'elite' : 'rookie'),
} as unknown as UserRating;
};
beforeEach(() => {
mockLogger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
} as unknown as Logger;
repository = new InMemoryUserRatingRepository(mockLogger);
repository.clear();
});
it('saves and finds ratings', async () => {
await repository.save(rating('u1', 2300, 50, false));
await repository.save(rating('u2', 2500, 70, true));
expect((await repository.findByUserId('u1'))?.userId).toBe('u1');
expect((await repository.findByUserIds(['u1', 'u2'])).length).toBe(2);
const topDrivers = await repository.getTopDrivers(1);
expect(topDrivers[0]?.userId).toBe('u2');
const topTrusted = await repository.getTopTrusted(1);
expect(topTrusted[0]?.userId).toBe('u2');
const stewards = await repository.getEligibleStewards();
expect(stewards.map(r => r.userId)).toEqual(['u2']);
const elite = await repository.findByDriverTier('elite');
expect(elite.map(r => r.userId)).toEqual(['u2']);
});
it('deletes ratings', async () => {
await repository.save(rating('u3', 2200, 10, false));
await repository.delete('u3');
expect(await repository.findByUserId('u3')).toBeNull();
});
});

View File

@@ -0,0 +1,69 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { Logger } from '@core/shared/application';
import type { StoredUser } from '@core/identity/domain/repositories/IUserRepository';
import { InMemoryUserRepository } from './InMemoryUserRepository';
describe('InMemoryUserRepository', () => {
let repository: InMemoryUserRepository;
let mockLogger: Logger;
beforeEach(() => {
mockLogger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
} as unknown as Logger;
const seeded: StoredUser[] = [
{
id: 'u1',
email: 'seed@example.com',
displayName: 'Seed',
passwordHash: 'hash',
salt: 'salt',
createdAt: new Date('2025-01-01T00:00:00.000Z'),
},
];
repository = new InMemoryUserRepository(mockLogger, seeded);
});
it('finds by email and id', async () => {
expect((await repository.findByEmail('seed@example.com'))?.id).toBe('u1');
expect((await repository.findById('u1'))?.email).toBe('seed@example.com');
expect(await repository.findByEmail('missing@example.com')).toBeNull();
});
it('creates and rejects duplicate emails', async () => {
const user: StoredUser = {
id: 'u2',
email: 'new@example.com',
displayName: 'New',
passwordHash: 'hash2',
salt: 'salt2',
createdAt: new Date(),
};
await repository.create(user);
expect(await repository.emailExists('new@example.com')).toBe(true);
await expect(repository.create({ ...user, id: 'u3' })).rejects.toThrow('Email already exists');
});
it('updates and maintains email index', async () => {
const existing = await repository.findById('u1');
expect(existing).not.toBeNull();
const updated: StoredUser = {
...(existing as StoredUser),
email: 'changed@example.com',
displayName: 'Changed',
};
await repository.update(updated);
expect(await repository.findByEmail('seed@example.com')).toBeNull();
expect((await repository.findByEmail('changed@example.com'))?.displayName).toBe('Changed');
});
});