Skip to content

Commit

Permalink
Merge pull request #110 from arnaugomez/feat/tests-services
Browse files Browse the repository at this point in the history
Feat/tests-services
  • Loading branch information
arnaugomez authored Jun 28, 2024
2 parents 83e8f59 + 3ba6a8c commit 018ce07
Show file tree
Hide file tree
Showing 12 changed files with 205 additions and 8 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pr-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
- name: Run unit tests with Vitest
run: pnpm test:unit
env:
BASE_URL: ${{vars.BASE_URL}}
PROJECT_URL: ${{vars.PROJECT_URL}}
MONGODB_URL: ${{secrets.MONGODB_URL}}
PASSWORD_PEPPER: ${{secrets.PASSWORD_PEPPER}}
SEND_EMAIL: ${{vars.SEND_EMAIL}}
2 changes: 1 addition & 1 deletion .github/workflows/pr-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
- name: Run unit tests with Vitest
run: pnpm test:unit
env:
BASE_URL: ${{vars.BASE_URL}}
PROJECT_URL: ${{vars.PROJECT_URL}}
MONGODB_URL: ${{secrets.MONGODB_URL}}
PASSWORD_PEPPER: ${{secrets.PASSWORD_PEPPER}}
SEND_EMAIL: ${{vars.SEND_EMAIL}}
2 changes: 1 addition & 1 deletion playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default defineConfig({
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://127.0.0.1:3000',
// baseUrl: 'http://127.0.0.1:3000',

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { describe, expect, it } from "vitest";
import { AiNotesGeneratorSourceType } from "../../domain/models/ai-notes-generator-source-type";
import { AiNotesGeneratorServiceFakeImpl } from "./ai-notes-generator-service-fake-impl";

describe("AiNotesGeneratorServiceFakeImpl", () => {
it("generate returns a non-empty list of notes regardless of the input", async () => {
const aiNotesGeneratorServiceFakeImpl =
new AiNotesGeneratorServiceFakeImpl();

await expect(
aiNotesGeneratorServiceFakeImpl.generate({
notesCount: 0,
noteTypes: [],
sourceType: AiNotesGeneratorSourceType.file,
text: "",
}),
).resolves.not.toHaveLength(0);
await expect(
aiNotesGeneratorServiceFakeImpl.generate({
notesCount: 10000,
noteTypes: [],
sourceType: AiNotesGeneratorSourceType.text,
text: "Example Test",
}),
).resolves.not.toHaveLength(0);
});
});
61 changes: 61 additions & 0 deletions src/common/data/services/cookie-service-next-impl.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { cookies } from "next/headers";
import { afterEach, describe, expect, it, vi } from "vitest";
import { CookieServiceNextImpl } from "@/src/common/data/services/cookie-service-next-impl";

vi.mock("next/headers", () => {
const mockGet = vi.fn();
const mockSet = vi.fn();
return {
cookies: () => ({
get: mockGet,
set: mockSet,
}),
};
});

describe("CookieServiceNextImpl", () => {
afterEach(() => {
vi.clearAllMocks();
});

describe("get method", () => {
it("returns the cookie value", () => {
const cookieService = new CookieServiceNextImpl();
const cookieName = "test-cookie-name";
const cookieValue = "Test cookie value";
vi.mocked(cookies().get).mockReturnValue({
name: cookieName,
value: cookieValue,
});

expect(cookieService.get(cookieName)).toBe(cookieValue);
expect(cookies().get).toHaveBeenCalledWith(cookieName);
});

it("returns undefined when the cookie does not exist", () => {
const cookieService = new CookieServiceNextImpl();
const cookieName = "test-cookie-name-2";
vi.mocked(cookies().get).mockReturnValue(undefined);

expect(cookieService.get(cookieName)).toBeUndefined();
expect(cookies().get).toHaveBeenCalledWith(cookieName);
});
});

describe("set method", () => {
it("should set the cookie with correct attributes", () => {
const cookieService = new CookieServiceNextImpl();
const cookieName = "test-cookie-name-3";
const cookieValue = "Test Cookie Value 3";
const attributes = { path: "/", maxAge: 3600 };

cookieService.set({ name: cookieName, value: cookieValue, attributes });

expect(cookies().set).toHaveBeenCalledWith(
cookieName,
cookieValue,
attributes,
);
});
});
});
2 changes: 1 addition & 1 deletion src/common/data/services/email-service-fake-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export class EmailServiceFakeImpl implements EmailService {
email: string,
forgotPasswordCode: string,
): Promise<void> {
const url = new URL(this.envService.baseUrl);
const url = new URL(this.envService.projectUrl);
url.pathname = "/auth/reset-password";
url.search = new URLSearchParams({
email,
Expand Down
57 changes: 57 additions & 0 deletions src/common/data/services/email-service-resend-impl.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { afterEach } from "node:test";
import { Resend } from "resend";
import { describe, expect, it, vi } from "vitest";
import { locator } from "../../di/locator";
import { EmailServiceResendImpl } from "./email-service-resend-impl";

vi.mock("resend", () => {
const sendMock = vi.fn();
class ResendMock {
emails = {
send: sendMock,
};
}
return { Resend: ResendMock };
});

describe("EmailServiceResendImpl", () => {
afterEach(() => {
vi.clearAllMocks();
});

it("sendVerificationCode sends an email with a verfication code", async () => {
const email = "test@example.com";
const verificationCode = "123456";

const envService = locator.EnvService();

const emailService = new EmailServiceResendImpl(envService);
await emailService.sendVerificationCode(email, verificationCode);

expect(new Resend().emails.send).toHaveBeenCalledWith({
from: "El equipo de clubmemo <noreply@app.clubmemo.com>",
to: email,
subject: "¡Bienvenido a clubmemo! Verifica tu email.",
html: expect.stringContaining(verificationCode),
});
});

it("sendForgotPasswordLink a forgot password email", async () => {
const email = "test@example.com";
const token = "reset-token";

const envService = locator.EnvService();
const emailService = new EmailServiceResendImpl(envService);
await emailService.sendForgotPasswordLink(email, token);

const expectedUrl =
"https://www.clubmemo.com/auth/reset-password?email=test%40example.com&token=reset-token";

expect(new Resend().emails.send).toHaveBeenCalledWith({
from: "El equipo de clubmemo <noreply@app.clubmemo.com>",
to: email,
subject: "Recupera tu cuenta de clubmemo",
html: expect.stringContaining(`href="${expectedUrl}"`),
});
});
});
2 changes: 1 addition & 1 deletion src/common/data/services/email-service-resend-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class EmailServiceResendImpl implements EmailService {
}

async sendForgotPasswordLink(email: string, token: string): Promise<void> {
const url = new URL(this.envService.baseUrl);
const url = new URL(this.envService.projectUrl);
url.pathname = "/auth/reset-password";
url.search = new URLSearchParams({
email,
Expand Down
2 changes: 1 addition & 1 deletion src/common/data/services/env-service-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export class EnvServiceImpl implements EnvService {
readonly mongodbUrl = process.env.MONGODB_URL;
readonly resendApiKey = process.env.RESEND_API_KEY;
readonly sendEmail = process.env.SEND_EMAIL === "true";
readonly baseUrl = process.env.BASE_URL;
readonly projectUrl = process.env.PROJECT_URL;
readonly passwordPepper = process.env.PASSWORD_PEPPER;
readonly openaiApiKey = process.env.OPENAI_API_KEY;
readonly fakeOpenAiApi = process.env.FAKE_OPENAI_API === "true";
Expand Down
52 changes: 52 additions & 0 deletions src/common/data/services/ip-service-vercel-impl.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import type { ReadonlyHeaders } from "next/dist/server/web/spec-extension/adapters/headers";
import { headers } from "next/headers";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { IpServiceVercelImpl } from "./ip-service-vercel-impl";

vi.mock("next/headers", () => {
const mockGet = vi.fn();
return {
headers() {
return {
get: mockGet,
};
},
};
});

describe("IpServiceVercelImpl", () => {
let mockHeaders: ReadonlyHeaders;

beforeEach(() => {
mockHeaders = headers();
});

afterEach(() => {
vi.clearAllMocks();
});

it("returns the first IP from x-forwarded-for, with trimmed spaces", async () => {
const ipService = new IpServiceVercelImpl();
vi.mocked(mockHeaders.get).mockReturnValue(" 192.168.1.1, 192.168.1.2");
await expect(ipService.getIp()).resolves.toBe("192.168.1.1");
expect(mockHeaders.get).toHaveBeenCalledWith("x-forwarded-for");
});

it("returns the IP from x-real-ip (with trimmed spaces) if x-forwarded-for is null", async () => {
const ipService = new IpServiceVercelImpl();
vi.mocked(mockHeaders.get).mockImplementation((header) =>
header === "x-real-ip" ? " 192.168.1.3 " : null,
);
await expect(ipService.getIp()).resolves.toBe("192.168.1.3");
expect(mockHeaders.get).toHaveBeenCalledWith("x-forwarded-for");
expect(mockHeaders.get).toHaveBeenCalledWith("x-real-ip");
});

it("returns '0.0.0.0' x-forwarded-for and x-real-ip are both null", async () => {
const ipService = new IpServiceVercelImpl();
vi.mocked(mockHeaders.get).mockReturnValue(null);
await expect(ipService.getIp()).resolves.toBe("0.0.0.0");
expect(mockHeaders.get).toHaveBeenCalledWith("x-forwarded-for");
expect(mockHeaders.get).toHaveBeenCalledWith("x-real-ip");
});
});
2 changes: 1 addition & 1 deletion src/common/domain/interfaces/env-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export interface EnvService {
/** The Resend api key, used to send emails */
readonly resendApiKey: string;
/** The base url of the website, for example, https://example.com */
readonly baseUrl: string;
readonly projectUrl: string;
/**
* Pepper code for the password hashing algorithm.
* Adds an extra layer of protection to the password hash.
Expand Down
2 changes: 1 addition & 1 deletion src/common/types/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ declare namespace NodeJS {
/** The Resend api key, used to send emails */
readonly RESEND_API_KEY: string;
/** The base url of the website */
readonly BASE_URL: string;
readonly PROJECT_URL: string;
/**
* Pepper code for the password hashing algorithm.
* Adds an extra layer of protection to the password hash.
Expand Down

0 comments on commit 018ce07

Please sign in to comment.