diff --git a/.env.development b/.env.development index 68407a5..6a2a9b0 100644 --- a/.env.development +++ b/.env.development @@ -3,4 +3,12 @@ POSTGRES_PORT=5432 POSTGRES_USER=local_user POSTGRES_DB=local_db POSTGRES_PASSWORD=local_password -DATABASE_URL=postgres://$POSTGRES_USER:$POSTGRES_PASSWORD@$POSTGRES_HOST:$POSTGRES_PORT/$POSTGRES_DB \ No newline at end of file +DATABASE_URL=postgres://$POSTGRES_USER:$POSTGRES_PASSWORD@$POSTGRES_HOST:$POSTGRES_PORT/$POSTGRES_DB + +EMAIL_SMTP_HOST=localhost +EMAIL_SMTP_PORT=1025 +EMAIL_SMTP_USER= +EMAIL_SMTP_PASSWORD= + +EMAIL_HTTP_HOST=localhost +EMAIL_HTTP_PORT=1080 \ No newline at end of file diff --git a/infra/compose.yaml b/infra/compose.yaml index bb62205..8b007dc 100644 --- a/infra/compose.yaml +++ b/infra/compose.yaml @@ -6,3 +6,9 @@ services: - ../.env.development ports: - "5432:5432" + mailcatcher: + container_name: "mailcatcher-dev" + image: "sj26/mailcatcher" + ports: + - "1025:1025" + - "1080:1080" diff --git a/infra/email.js b/infra/email.js new file mode 100644 index 0000000..13d7883 --- /dev/null +++ b/infra/email.js @@ -0,0 +1,21 @@ +import nodemailer from "nodemailer"; + +const transporter = nodemailer.createTransport({ + host: process.env.EMAIL_SMTP_HOST, + port: process.env.EMAIL_SMTP_PORT, + auth: { + user: process.env.EMAIL_SMTP_USER, + pass: process.env.EMAIL_SMTP_PASSWORD, + }, + secure: process.env.NODE_ENV === "production" ? true : false, +}); + +async function send(mailOptions) { + await transporter.sendMail(mailOptions); +} + +const email = { + send, +}; + +export default email; diff --git a/package-lock.json b/package-lock.json index 4784782..686108c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "next": "16.2.6", "next-connect": "1.0.0", "node-pg-migrate": "^7.9.1", + "nodemailer": "7.0.5", "pg": "8.20.0", "react": "19.2.6", "react-dom": "19.2.6", @@ -8832,6 +8833,15 @@ "dev": true, "license": "MIT" }, + "node_modules/nodemailer": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.5.tgz", + "integrity": "sha512-nsrh2lO3j4GkLLXoeEksAMgAOqxOv6QumNRVQTJwKH4nuiww6iC2y7GyANs9kRAxCexg3+lTWM3PZ91iLlVjfg==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", diff --git a/package.json b/package.json index f78a9de..d37db71 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "next": "16.2.6", "next-connect": "1.0.0", "node-pg-migrate": "^7.9.1", + "nodemailer": "7.0.5", "pg": "8.20.0", "react": "19.2.6", "react-dom": "19.2.6", diff --git a/tests/integration/api/v1/migrations/get.test.js b/tests/integration/api/v1/migrations/get.test.js index eccf1ac..c9502c5 100644 --- a/tests/integration/api/v1/migrations/get.test.js +++ b/tests/integration/api/v1/migrations/get.test.js @@ -1,4 +1,4 @@ -import orchestrator from "../orchestrator"; +import orchestrator from "../../../../orchestrator"; beforeAll(async () => { await orchestrator.waitForAllServices(); diff --git a/tests/integration/api/v1/migrations/post.test.js b/tests/integration/api/v1/migrations/post.test.js index be3e7ce..15f7575 100644 --- a/tests/integration/api/v1/migrations/post.test.js +++ b/tests/integration/api/v1/migrations/post.test.js @@ -1,4 +1,4 @@ -import orchestrator from "../orchestrator"; +import orchestrator from "../../../../orchestrator"; beforeAll(async () => { await orchestrator.waitForAllServices(); diff --git a/tests/integration/api/v1/sessions/delete.test.js b/tests/integration/api/v1/sessions/delete.test.js index c6b6214..8620fd3 100644 --- a/tests/integration/api/v1/sessions/delete.test.js +++ b/tests/integration/api/v1/sessions/delete.test.js @@ -1,6 +1,6 @@ import { version as uuidVersion } from "uuid"; import setCookieParser from "set-cookie-parser"; -import orchestrator from "../orchestrator"; +import orchestrator from "../../../../orchestrator"; import session from "models/session"; beforeAll(async () => { diff --git a/tests/integration/api/v1/sessions/post.test.js b/tests/integration/api/v1/sessions/post.test.js index 6984721..b0e7543 100644 --- a/tests/integration/api/v1/sessions/post.test.js +++ b/tests/integration/api/v1/sessions/post.test.js @@ -1,6 +1,6 @@ import { version as uuidVersion } from "uuid"; import setCookieParser from "set-cookie-parser"; -import orchestrator from "../orchestrator"; +import orchestrator from "../../../../orchestrator"; import session from "models/session"; beforeAll(async () => { diff --git a/tests/integration/api/v1/status/get.test.js b/tests/integration/api/v1/status/get.test.js index c886cf1..30623b3 100644 --- a/tests/integration/api/v1/status/get.test.js +++ b/tests/integration/api/v1/status/get.test.js @@ -1,4 +1,4 @@ -import orchestrator from "../orchestrator.js"; +import orchestrator from "../../../../orchestrator.js"; beforeAll(async () => { await orchestrator.waitForAllServices(); diff --git a/tests/integration/api/v1/status/post.test.js b/tests/integration/api/v1/status/post.test.js index 1d6316a..8200cef 100644 --- a/tests/integration/api/v1/status/post.test.js +++ b/tests/integration/api/v1/status/post.test.js @@ -1,4 +1,4 @@ -import orchestrator from "../orchestrator.js"; +import orchestrator from "../../../../orchestrator.js"; beforeAll(async () => { await orchestrator.waitForAllServices(); diff --git a/tests/integration/api/v1/user/get.test.js b/tests/integration/api/v1/user/get.test.js index a160fe3..3beb826 100644 --- a/tests/integration/api/v1/user/get.test.js +++ b/tests/integration/api/v1/user/get.test.js @@ -1,6 +1,6 @@ import { version as uuidVersion } from "uuid"; import setCookieParser from "set-cookie-parser"; -import orchestrator from "../orchestrator"; +import orchestrator from "../../../../orchestrator"; import session from "models/session"; beforeAll(async () => { diff --git a/tests/integration/api/v1/users/[username]/get.test.js b/tests/integration/api/v1/users/[username]/get.test.js index abe72c7..63d2590 100644 --- a/tests/integration/api/v1/users/[username]/get.test.js +++ b/tests/integration/api/v1/users/[username]/get.test.js @@ -1,5 +1,5 @@ import { version as uuidVersion } from "uuid"; -import orchestrator from "../../orchestrator"; +import orchestrator from "../../../../../orchestrator"; beforeAll(async () => { await orchestrator.waitForAllServices(); diff --git a/tests/integration/api/v1/users/[username]/patch.test.js b/tests/integration/api/v1/users/[username]/patch.test.js index a5bc93b..c125a70 100644 --- a/tests/integration/api/v1/users/[username]/patch.test.js +++ b/tests/integration/api/v1/users/[username]/patch.test.js @@ -1,5 +1,5 @@ import { version as uuidVersion } from "uuid"; -import orchestrator from "../../orchestrator"; +import orchestrator from "../../../../../orchestrator"; import user from "models/user"; import password from "models/password"; diff --git a/tests/integration/api/v1/users/post.test.js b/tests/integration/api/v1/users/post.test.js index d25d9a9..7f61843 100644 --- a/tests/integration/api/v1/users/post.test.js +++ b/tests/integration/api/v1/users/post.test.js @@ -1,5 +1,5 @@ import { version as uuidVersion } from "uuid"; -import orchestrator from "../orchestrator"; +import orchestrator from "../../../../orchestrator"; import user from "models/user"; import password from "models/password"; diff --git a/tests/integration/infra/email.test.js b/tests/integration/infra/email.test.js new file mode 100644 index 0000000..5ef09ea --- /dev/null +++ b/tests/integration/infra/email.test.js @@ -0,0 +1,31 @@ +import email from "infra/email.js"; +import orchestrator from "tests/orchestrator"; + +beforeAll(async () => { + await orchestrator.waitForAllServices(); +}); + +describe("infra/email.js", () => { + test("Send()", async () => { + await orchestrator.deleteAllEmails(); + + await email.send({ + from: "Xedit ", + to: "contato@gmail.com", + subject: "Teste de assunto", + text: "Teste de corpo.", + }); + await email.send({ + from: "Xedit ", + to: "contato@gmail.com", + subject: "Último email enviado.", + text: "Corpo do ultimo email.", + }); + + const lastEmail = await orchestrator.getLastEmail(); + expect(lastEmail.sender).toBe(""); + expect(lastEmail.recipients[0]).toBe(""); + expect(lastEmail.subject).toBe("Último email enviado."); + expect(lastEmail.text).toBe("Corpo do ultimo email.\n"); + }); +}); diff --git a/tests/integration/api/v1/orchestrator.js b/tests/orchestrator.js similarity index 53% rename from tests/integration/api/v1/orchestrator.js rename to tests/orchestrator.js index fb885b2..dfeaa4a 100644 --- a/tests/integration/api/v1/orchestrator.js +++ b/tests/orchestrator.js @@ -1,13 +1,21 @@ import retry from "async-retry"; import { faker } from "@faker-js/faker"; +import dotenv from "dotenv"; import database from "infra/database"; import migrator from "models/migrator"; import user from "models/user"; import session from "models/session"; +dotenv.config({ + path: ".env.development", // ou .env.test, se você usar isso +}); + +const emailHttpUrl = `http://${process.env.EMAIL_HTTP_HOST}:${process.env.EMAIL_HTTP_PORT}`; + async function waitForAllServices() { await waitForWebServer(); + await waitForEmailServer(); async function waitForWebServer() { return retry(fetchStatusPage, { @@ -24,6 +32,23 @@ async function waitForAllServices() { await response.json(); } } + + async function waitForEmailServer() { + return retry(fetchEmailPage, { + retries: 100, + maxTimeout: 1000, + }); + + async function fetchEmailPage() { + const response = await fetch(`${emailHttpUrl}/messages`); + + if (response.status !== 200) { + throw Error(); + } + + await response.json(); + } + } } async function clearDatabase() { @@ -47,12 +72,34 @@ async function createUser(userObject) { }); } +async function deleteAllEmails() { + await fetch(`${emailHttpUrl}/messages`, { + method: "DELETE", + }); +} + +async function getLastEmail() { + const emailListResponse = await fetch(`${emailHttpUrl}/messages`); + const emailListBody = await emailListResponse.json(); + const lastEmailItem = emailListBody.pop(); + + const emailTextResponse = await fetch( + `${emailHttpUrl}/messages/${lastEmailItem.id}.plain`, + ); + const emailTextBody = await emailTextResponse.text(); + + lastEmailItem.text = emailTextBody; + return lastEmailItem; +} + const orchestrator = { waitForAllServices, clearDatabase, runPendingMigrations, createUser, createSession, + deleteAllEmails, + getLastEmail, }; export default orchestrator;