Ajout de la gestion des erreurs HTTP, implémentation de la validation des comptes, et mise à jour des dépendances. Création de tests pour les entités et services de compte, ainsi que l'ajout d'un système de limitation de taux.
This commit is contained in:
56
tests/AccountEntity.test.ts
Normal file
56
tests/AccountEntity.test.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { AccountEntity } from "../src/domain/account/entity/AccountEntity";
|
||||
import {
|
||||
InvalidEmailFormatError,
|
||||
WeakPasswordError,
|
||||
} from "../src/domain/account/errors/AccountErrors";
|
||||
import { MIN_PASSWORD_LENGTH } from "../src/domain/account/validation/AccountValidation";
|
||||
|
||||
describe("AccountEntity", () => {
|
||||
describe("create", () => {
|
||||
it("should create an account with valid email and password", () => {
|
||||
const email = "test@example.com";
|
||||
const password = "a".repeat(MIN_PASSWORD_LENGTH); // Use minimum length
|
||||
|
||||
const account = AccountEntity.create(email, password);
|
||||
|
||||
expect(account.email).toBe(email);
|
||||
expect(account.roleId).toBe(1);
|
||||
expect(account.id).toBeDefined();
|
||||
expect(account.createdAt).toBeInstanceOf(Date);
|
||||
expect(account.updatedAt).toBeInstanceOf(Date);
|
||||
});
|
||||
|
||||
it("should throw InvalidEmailFormatError for invalid email", () => {
|
||||
const invalidEmail = "invalid-email";
|
||||
const password = "password123";
|
||||
|
||||
expect(() => {
|
||||
AccountEntity.create(invalidEmail, password);
|
||||
}).toThrow(InvalidEmailFormatError);
|
||||
});
|
||||
|
||||
it("should throw WeakPasswordError for short password", () => {
|
||||
const email = "test@example.com";
|
||||
const shortPassword = "a".repeat(MIN_PASSWORD_LENGTH - 1); // One less than minimum
|
||||
|
||||
expect(() => {
|
||||
AccountEntity.create(email, shortPassword);
|
||||
}).toThrow(WeakPasswordError);
|
||||
});
|
||||
});
|
||||
|
||||
describe("verifyPassword", () => {
|
||||
it("should return true for correct password", () => {
|
||||
const account = AccountEntity.create("test@example.com", "password123");
|
||||
|
||||
expect(account.verifyPassword("password123")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false for incorrect password", () => {
|
||||
const account = AccountEntity.create("test@example.com", "password123");
|
||||
|
||||
expect(account.verifyPassword("wrongpassword")).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
116
tests/AccountRepository.test.ts
Normal file
116
tests/AccountRepository.test.ts
Normal file
@ -0,0 +1,116 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import AccountRepository from "../src/domain/account/repository/AccoutRepository";
|
||||
import { DatabaseInterface } from "../src/database/DatabaseInterface";
|
||||
import { AccountEntity } from "../src/domain/account/entity/AccountEntity";
|
||||
|
||||
describe("AccountRepository", () => {
|
||||
let accountRepository: AccountRepository;
|
||||
let mockDatabase: DatabaseInterface;
|
||||
|
||||
beforeEach(() => {
|
||||
mockDatabase = {
|
||||
ping: vi.fn(),
|
||||
fetchAll: vi.fn(),
|
||||
fetchOne: vi.fn(),
|
||||
execute: vi.fn(),
|
||||
};
|
||||
accountRepository = new AccountRepository(mockDatabase);
|
||||
});
|
||||
|
||||
describe("findByEmail", () => {
|
||||
it("should return account when found", async () => {
|
||||
const email = "test@example.com";
|
||||
const mockResult = {
|
||||
id: "123",
|
||||
email: email,
|
||||
password: "hashedPassword",
|
||||
role_id: 1,
|
||||
created_at: new Date(),
|
||||
updated_at: new Date(),
|
||||
};
|
||||
|
||||
vi.mocked(mockDatabase.fetchOne).mockResolvedValue(mockResult);
|
||||
|
||||
const result = await accountRepository.findByEmail(email);
|
||||
|
||||
expect(result).toBeInstanceOf(AccountEntity);
|
||||
expect(result?.email).toBe(email);
|
||||
expect(mockDatabase.fetchOne).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
"SELECT id, email, password, role_id, created_at, updated_at",
|
||||
),
|
||||
[email],
|
||||
);
|
||||
});
|
||||
|
||||
it("should return null when account not found", async () => {
|
||||
const email = "nonexistent@example.com";
|
||||
|
||||
vi.mocked(mockDatabase.fetchOne).mockResolvedValue(undefined);
|
||||
|
||||
const result = await accountRepository.findByEmail(email);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("findById", () => {
|
||||
it("should return account when found", async () => {
|
||||
const id = "123";
|
||||
const mockResult = {
|
||||
id: id,
|
||||
email: "test@example.com",
|
||||
password: "hashedPassword",
|
||||
role_id: 1,
|
||||
created_at: new Date(),
|
||||
updated_at: new Date(),
|
||||
};
|
||||
|
||||
vi.mocked(mockDatabase.fetchOne).mockResolvedValue(mockResult);
|
||||
|
||||
const result = await accountRepository.findById(id);
|
||||
|
||||
expect(result).toBeInstanceOf(AccountEntity);
|
||||
expect(result?.id).toBe(id);
|
||||
expect(mockDatabase.fetchOne).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
"SELECT id, email, password, role_id, created_at, updated_at",
|
||||
),
|
||||
[id],
|
||||
);
|
||||
});
|
||||
|
||||
it("should return null when account not found", async () => {
|
||||
const id = "nonexistent";
|
||||
|
||||
vi.mocked(mockDatabase.fetchOne).mockResolvedValue(undefined);
|
||||
|
||||
const result = await accountRepository.findById(id);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("save", () => {
|
||||
it("should save account successfully", async () => {
|
||||
const account = AccountEntity.create("test@example.com", "password123");
|
||||
|
||||
vi.mocked(mockDatabase.fetchOne).mockResolvedValue({ id: account.id });
|
||||
|
||||
const result = await accountRepository.save(account);
|
||||
|
||||
expect(result).toBe(account.id);
|
||||
expect(mockDatabase.fetchOne).toHaveBeenCalledWith(
|
||||
expect.stringContaining("INSERT INTO accounts"),
|
||||
expect.arrayContaining([
|
||||
account.id,
|
||||
account.email,
|
||||
expect.any(String),
|
||||
account.roleId,
|
||||
account.createdAt,
|
||||
account.updatedAt,
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
95
tests/AccountService.test.ts
Normal file
95
tests/AccountService.test.ts
Normal file
@ -0,0 +1,95 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import AccountService from "../src/domain/account/service/AccountService";
|
||||
import { AccountRepositoryInterface } from "../src/domain/account/repository/AccountRepositoryInterface";
|
||||
import { AccountEntity } from "../src/domain/account/entity/AccountEntity";
|
||||
import {
|
||||
AccountNotFoundError,
|
||||
AccountAlreadyExistsError,
|
||||
BadPasswordError,
|
||||
} from "../src/domain/account/errors/AccountErrors";
|
||||
|
||||
describe("AccountService", () => {
|
||||
let accountService: AccountService;
|
||||
let mockAccountRepository: AccountRepositoryInterface;
|
||||
|
||||
beforeEach(() => {
|
||||
mockAccountRepository = {
|
||||
findByEmail: vi.fn(),
|
||||
save: vi.fn(),
|
||||
findById: vi.fn(),
|
||||
};
|
||||
accountService = new AccountService(mockAccountRepository);
|
||||
});
|
||||
|
||||
describe("createAccount", () => {
|
||||
it("should create a new account successfully", async () => {
|
||||
const email = "test@example.com";
|
||||
const password = "password123";
|
||||
|
||||
vi.mocked(mockAccountRepository.findByEmail).mockResolvedValue(null);
|
||||
vi.mocked(mockAccountRepository.save).mockResolvedValue("123");
|
||||
|
||||
const result = await accountService.register(email, password);
|
||||
|
||||
expect(result).toBeInstanceOf(AccountEntity);
|
||||
expect(result.email).toBe(email);
|
||||
expect(mockAccountRepository.findByEmail).toHaveBeenCalledWith(email);
|
||||
expect(mockAccountRepository.save).toHaveBeenCalledWith(
|
||||
expect.any(AccountEntity),
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw error if account already exists", async () => {
|
||||
const email = "test@example.com";
|
||||
const password = "password123";
|
||||
const existingAccount = AccountEntity.create(email, password);
|
||||
|
||||
vi.mocked(mockAccountRepository.findByEmail).mockResolvedValue(
|
||||
existingAccount,
|
||||
);
|
||||
|
||||
await expect(accountService.register(email, password)).rejects.toThrow(
|
||||
AccountAlreadyExistsError,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("login", () => {
|
||||
it("should login successfully with correct credentials", async () => {
|
||||
const email = "test@example.com";
|
||||
const password = "password123";
|
||||
const account = AccountEntity.create(email, password);
|
||||
|
||||
vi.mocked(mockAccountRepository.findByEmail).mockResolvedValue(account);
|
||||
|
||||
const result = await accountService.login(email, password);
|
||||
|
||||
expect(result).toBe(account);
|
||||
expect(mockAccountRepository.findByEmail).toHaveBeenCalledWith(email);
|
||||
});
|
||||
|
||||
it("should throw error if account not found", async () => {
|
||||
const email = "test@example.com";
|
||||
const password = "password123";
|
||||
|
||||
vi.mocked(mockAccountRepository.findByEmail).mockResolvedValue(null);
|
||||
|
||||
await expect(accountService.login(email, password)).rejects.toThrow(
|
||||
AccountNotFoundError,
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw error if password is incorrect", async () => {
|
||||
const email = "test@example.com";
|
||||
const password = "password123";
|
||||
const wrongPassword = "wrongpassword";
|
||||
const account = AccountEntity.create(email, password);
|
||||
|
||||
vi.mocked(mockAccountRepository.findByEmail).mockResolvedValue(account);
|
||||
|
||||
await expect(accountService.login(email, wrongPassword)).rejects.toThrow(
|
||||
BadPasswordError,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
132
tests/AccountValidation.test.ts
Normal file
132
tests/AccountValidation.test.ts
Normal file
@ -0,0 +1,132 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import {
|
||||
emailSchema,
|
||||
passwordSchema,
|
||||
loginSchema,
|
||||
registerSchema,
|
||||
EMAIL_REGEX,
|
||||
MIN_PASSWORD_LENGTH,
|
||||
} from "../src/domain/account/validation/AccountValidation";
|
||||
import { AccountEntity } from "../src/domain/account/entity/AccountEntity";
|
||||
import {
|
||||
InvalidEmailFormatError,
|
||||
WeakPasswordError,
|
||||
} from "../src/domain/account/errors/AccountErrors";
|
||||
|
||||
describe("AccountValidation", () => {
|
||||
describe("Email validation consistency", () => {
|
||||
const validEmails = [
|
||||
"test@example.com",
|
||||
"user.name@domain.co.uk",
|
||||
"firstname+lastname@example.org",
|
||||
];
|
||||
|
||||
const invalidEmails = [
|
||||
"invalid-email",
|
||||
"@example.com",
|
||||
"test@",
|
||||
"test",
|
||||
"test@domain",
|
||||
"",
|
||||
];
|
||||
|
||||
it("should validate same emails in Zod and Entity", () => {
|
||||
validEmails.forEach((email) => {
|
||||
expect(emailSchema.safeParse(email).success).toBe(true);
|
||||
expect(() => AccountEntity.create(email, "password123")).not.toThrow(
|
||||
InvalidEmailFormatError,
|
||||
);
|
||||
});
|
||||
|
||||
invalidEmails.forEach((email) => {
|
||||
expect(emailSchema.safeParse(email).success).toBe(false);
|
||||
expect(() => AccountEntity.create(email, "password123")).toThrow(
|
||||
InvalidEmailFormatError,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("should use same regex pattern", () => {
|
||||
validEmails.forEach((email) => {
|
||||
expect(EMAIL_REGEX.test(email)).toBe(true);
|
||||
});
|
||||
|
||||
invalidEmails.forEach((email) => {
|
||||
expect(EMAIL_REGEX.test(email)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Password validation consistency", () => {
|
||||
const validPasswords = ["password123", "abcdefgh", "12345678", "P@ssw0rd!"];
|
||||
|
||||
const invalidPasswords = ["1234567", "short", "", "abc"];
|
||||
|
||||
it("should validate same passwords in Zod and Entity", () => {
|
||||
validPasswords.forEach((password) => {
|
||||
expect(passwordSchema.safeParse(password).success).toBe(true);
|
||||
expect(() =>
|
||||
AccountEntity.create("test@example.com", password),
|
||||
).not.toThrow(WeakPasswordError);
|
||||
});
|
||||
|
||||
invalidPasswords.forEach((password) => {
|
||||
expect(passwordSchema.safeParse(password).success).toBe(false);
|
||||
expect(() =>
|
||||
AccountEntity.create("test@example.com", password),
|
||||
).toThrow(WeakPasswordError);
|
||||
});
|
||||
});
|
||||
|
||||
it("should use same minimum length", () => {
|
||||
validPasswords.forEach((password) => {
|
||||
expect(password.length >= MIN_PASSWORD_LENGTH).toBe(true);
|
||||
});
|
||||
|
||||
invalidPasswords.forEach((password) => {
|
||||
expect(password.length >= MIN_PASSWORD_LENGTH).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Complete schemas", () => {
|
||||
it("should validate login schema correctly", () => {
|
||||
const validLogin = { email: "test@example.com", password: "password123" };
|
||||
const invalidLogin = { email: "invalid", password: "123" };
|
||||
|
||||
expect(loginSchema.safeParse(validLogin).success).toBe(true);
|
||||
expect(loginSchema.safeParse(invalidLogin).success).toBe(false);
|
||||
});
|
||||
|
||||
it("should validate register schema correctly", () => {
|
||||
const validRegister = {
|
||||
email: "test@example.com",
|
||||
password: "password123",
|
||||
};
|
||||
const invalidRegister = { email: "invalid", password: "123" };
|
||||
|
||||
expect(registerSchema.safeParse(validRegister).success).toBe(true);
|
||||
expect(registerSchema.safeParse(invalidRegister).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Error messages consistency", () => {
|
||||
it("should return consistent password error message", () => {
|
||||
const result = passwordSchema.safeParse("123");
|
||||
expect(result.success).toBe(false);
|
||||
if (!result.success) {
|
||||
expect(result.error.issues[0]?.message).toContain(
|
||||
`${MIN_PASSWORD_LENGTH} caractères`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("should return consistent email error message", () => {
|
||||
const result = emailSchema.safeParse("invalid-email");
|
||||
expect(result.success).toBe(false);
|
||||
if (!result.success) {
|
||||
expect(result.error.issues[0]?.message).toBe("Format d'email invalide");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user