feat: add modular services and repositories for improved code organization

Introduce repository pattern across multiple services, including `favorites`, `tags`, `sessions`, `reports`, `auth`, and more. Decouple crypto functionalities into modular services like `HashingService`, `JwtService`, and `EncryptionService`. Improve testability and maintainability by simplifying dependencies and consolidating utility logic.
This commit is contained in:
Mathis HERRIOT
2026-01-14 12:11:39 +01:00
parent 9c45bf11e4
commit 514bd354bf
64 changed files with 1801 additions and 1295 deletions

View File

@@ -12,61 +12,46 @@ jest.mock("jose", () => ({
}));
import { Test, TestingModule } from "@nestjs/testing";
import { CryptoService } from "../crypto/crypto.service";
import { DatabaseService } from "../database/database.service";
import { CACHE_MANAGER } from "@nestjs/cache-manager";
import { UsersService } from "./users.service";
import { UsersRepository } from "./repositories/users.repository";
describe("UsersService", () => {
let service: UsersService;
let repository: UsersRepository;
const mockDb = {
insert: jest.fn(),
select: jest.fn(),
const mockUsersRepository = {
create: jest.fn(),
findOne: jest.fn(),
findByEmailHash: jest.fn(),
findOneWithPrivateData: jest.fn(),
countAll: jest.fn(),
findAll: jest.fn(),
findByUsername: jest.fn(),
update: jest.fn(),
transaction: jest.fn().mockImplementation((cb) => cb(mockDb)),
getTwoFactorSecret: jest.fn(),
getUserContents: jest.fn(),
getUserFavorites: jest.fn(),
softDeleteUserAndContents: jest.fn(),
};
const mockCryptoService = {
getPgpEncryptionKey: jest.fn().mockReturnValue("mock-pgp-key"),
const mockCacheManager = {
del: jest.fn(),
};
beforeEach(async () => {
jest.clearAllMocks();
const chain = {
insert: jest.fn().mockReturnThis(),
values: jest.fn().mockReturnThis(),
returning: jest.fn().mockReturnThis(),
select: jest.fn().mockReturnThis(),
from: jest.fn().mockReturnThis(),
where: jest.fn().mockReturnThis(),
limit: jest.fn().mockReturnThis(),
offset: jest.fn().mockReturnThis(),
update: jest.fn().mockReturnThis(),
set: jest.fn().mockReturnThis(),
// biome-ignore lint/suspicious/noThenProperty: Fine for testing purposes
then: jest.fn().mockImplementation(function (cb) {
return Promise.resolve(this).then(cb);
}),
};
const mockImplementation = () => Object.assign(Promise.resolve([]), chain);
for (const mock of Object.values(chain)) {
if (mock !== chain.then) {
mock.mockImplementation(mockImplementation);
}
}
Object.assign(mockDb, chain);
const module: TestingModule = await Test.createTestingModule({
providers: [
UsersService,
{ provide: DatabaseService, useValue: { db: mockDb } },
{ provide: CryptoService, useValue: mockCryptoService },
{ provide: UsersRepository, useValue: mockUsersRepository },
{ provide: CACHE_MANAGER, useValue: mockCacheManager },
],
}).compile();
service = module.get<UsersService>(UsersService);
repository = module.get<UsersRepository>(UsersRepository);
});
it("should be defined", () => {
@@ -81,24 +66,24 @@ describe("UsersService", () => {
passwordHash: "p1",
emailHash: "eh1",
};
mockDb.returning.mockResolvedValue([{ uuid: "uuid1", ...data }]);
mockUsersRepository.create.mockResolvedValue({ uuid: "uuid1", ...data });
const result = await service.create(data);
expect(result.uuid).toBe("uuid1");
expect(mockDb.insert).toHaveBeenCalled();
expect(repository.create).toHaveBeenCalledWith(data);
});
});
describe("findOne", () => {
it("should find a user", async () => {
mockDb.limit.mockResolvedValue([{ uuid: "uuid1" }]);
mockUsersRepository.findOne.mockResolvedValue({ uuid: "uuid1" });
const result = await service.findOne("uuid1");
expect(result.uuid).toBe("uuid1");
});
it("should return null if not found", async () => {
mockDb.limit.mockResolvedValue([]);
mockUsersRepository.findOne.mockResolvedValue(null);
const result = await service.findOne("uuid1");
expect(result).toBeNull();
});
@@ -106,7 +91,7 @@ describe("UsersService", () => {
describe("update", () => {
it("should update a user", async () => {
mockDb.returning.mockResolvedValue([{ uuid: "uuid1", displayName: "New" }]);
mockUsersRepository.update.mockResolvedValue([{ uuid: "uuid1", displayName: "New" }]);
const result = await service.update("uuid1", { displayName: "New" });
expect(result[0].displayName).toBe("New");
});