Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,10 @@ 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

EMAIL_SMTP_HOST=localhost
EMAIL_SMTP_PORT=1025
EMAIL_SMTP_USER=
EMAIL_SMTP_PASSWORD=
EMAIL_HTTP_HOST=localhost
EMAIL_HTTP_PORT=1080
6 changes: 6 additions & 0 deletions infra/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,9 @@ services:
- ../.env.development
ports:
- "5432:5432"
mailcatcher:
container_name: "mailcatcher"
image: "sj26/mailcatcher"
ports:
- "1025:1025"
- "1080:1080"
21 changes: 21 additions & 0 deletions infra/email.js
Original file line number Diff line number Diff line change
@@ -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;
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"next": "15.3.1",
"next-connect": "1.0.0",
"node-pg-migrate": "7.9.1",
"nodemailer": "7.0.5",
"pg": "8.15.6",
"react": "19.1.0",
"react-dom": "19.1.0",
Expand Down
25 changes: 25 additions & 0 deletions tests/integration/infra/email.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import email from "infra/email.js";
import orchestrator from "tests/orchestrator.js";

beforeAll(async () => {
await orchestrator.waitForAllServices();
});

describe("infra/email.js", () => {
test("send()", async () => {
await orchestrator.deleteAllEmails();

await email.send({
from: "Diego Santos <contato@diegosantos.com.br>",
to: "contato@curso.dev",
subject: "Teste de assunto",
text: "Teste de corpo.",
});

const lastEmail = await orchestrator.getLastEmail();
expect(lastEmail.sender).toBe("<contato@diegosantos.com.br>");
expect(lastEmail.recipients[0]).toBe("<contato@curso.dev>");
expect(lastEmail.subject).toBe("Teste de assunto");
expect(lastEmail.text).toBe("Teste de corpo.\n");
});
});
40 changes: 40 additions & 0 deletions tests/orchestrator.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ import migrator from "models/migrator.js";
import user from "models/user";
import session from "models/session";

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, {
Expand All @@ -22,6 +25,20 @@ async function waitForAllServices() {
}
}
}

async function waitForEmailServer() {
return retry(fetchEmailPage, {
retries: 100,
maxTimeout: 6000,
});

async function fetchEmailPage() {
const response = await fetch(emailHttpUrl);
if (response.status !== 200) {
throw Error();
}
}
}
}

async function clearDatabase() {
Expand All @@ -45,12 +62,35 @@ async function createSession(userId) {
return await session.create(userId);
}

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 textEmailResponse = await fetch(
`${emailHttpUrl}/messages/${lastEmailItem.id}.plain`,
);
const emailTextBody = await textEmailResponse.text();

lastEmailItem.text = emailTextBody;

return lastEmailItem;
}

const orchestrator = {
waitForAllServices,
clearDatabase,
runPendingMigrations,
createUser,
createSession,
deleteAllEmails,
getLastEmail,
};

export default orchestrator;