22 Commits

Author SHA1 Message Date
pm deb2605e08 Merge branch 'teste-bloqueio' of https://gitea.each-cs.com/pm/website-secure into teste-bloqueio
DevSecOps Enterprise Pipeline / security-gate-and-deploy (pull_request) Successful in 36s
2026-06-12 23:01:49 +01:00
pm 0ba2cb3a1b sonar v8 2026-06-12 23:00:27 +01:00
pm b8ae8e0a5b sonar v6
DevSecOps Enterprise Pipeline / security-gate-and-deploy (pull_request) Failing after 31s
2026-06-12 09:07:09 +01:00
pm 11399103e4 sonar v5
DevSecOps Enterprise Pipeline / security-gate-and-deploy (pull_request) Failing after 37s
2026-06-12 08:48:57 +01:00
pm 1b70908cad sonar v3
DevSecOps Enterprise Pipeline / security-gate-and-deploy (pull_request) Failing after 36s
2026-06-12 01:08:00 +01:00
pm a53b8e23e7 sonar v2
DevSecOps Enterprise Pipeline / security-gate-and-deploy (pull_request) Failing after 38s
2026-06-12 01:03:28 +01:00
pm 0689c0bc17 sonar v1
DevSecOps Enterprise Pipeline / security-gate-and-deploy (pull_request) Failing after 35s
2026-06-12 00:54:56 +01:00
pm 3a2685d5b6 ut v3
DevSecOps Enterprise Pipeline / security-gate-and-deploy (pull_request) Failing after 36s
2026-06-12 00:50:08 +01:00
pm f9e6fd47a4 ut v2
DevSecOps Enterprise Pipeline / security-gate-and-deploy (pull_request) Failing after 9s
2026-06-12 00:47:30 +01:00
pm cd5785c497 ut v1
DevSecOps Enterprise Pipeline / security-gate-and-deploy (pull_request) Failing after 8s
2026-06-12 00:45:12 +01:00
pm 3967393895 docker v3
DevSecOps Enterprise Pipeline / security-gate-and-deploy (pull_request) Failing after 9s
2026-06-12 00:41:21 +01:00
pm dbf0d7445c docker v2
DevSecOps Enterprise Pipeline / security-gate-and-deploy (pull_request) Failing after 8s
2026-06-12 00:39:12 +01:00
pm 20d454d5c6 docker
DevSecOps Enterprise Pipeline / security-gate-and-deploy (pull_request) Failing after 7s
2026-06-12 00:37:06 +01:00
pm 18ded6b7fc alterações deploy
DevSecOps Enterprise Pipeline / security-gate-and-deploy (pull_request) Failing after 5s
2026-06-12 00:35:09 +01:00
pm bba5d7c581 deploy en-us e unit tests melhorado
DevSecOps Enterprise Pipeline / security-gate-and-deploy (pull_request) Failing after 7s
2026-06-12 00:28:08 +01:00
pm 70321152a7 teste error
DevSecOps Enterprise Pipeline / security-gate-and-deploy (pull_request) Failing after 7s
2026-06-12 00:17:00 +01:00
pm 25849c273c chore: adiciona condicoes de execucao e exclusoes do sonar
DevSecOps Enterprise Pipeline / security-gate-and-deploy (pull_request) Successful in 34s
2026-06-12 00:13:49 +01:00
pm 740b6386ef deploy
DevSecOps Enterprise Pipeline / security-gate-and-deploy (pull_request) Successful in 1m20s
2026-06-12 00:06:46 +01:00
pm 980005151f fix: recria package.json do backend para remover BOM fantasma
DevSecOps Enterprise Pipeline / security-gate-and-deploy (pull_request) Failing after 37s
2026-06-12 00:00:20 +01:00
pm c234771d28 deploy atualizado; test correto
DevSecOps Enterprise Pipeline / security-gate-and-deploy (pull_request) Failing after 8s
2026-06-11 23:54:58 +01:00
pm b71d73afe0 feat: adiciona nova funcionalidade (com erro oculto)
DevSecOps Enterprise Pipeline / security-gate-and-deploy (pull_request) Successful in 1m17s
2026-06-11 23:41:19 +01:00
pm 002f696161 Merge pull request 'docs: altera readme para testar fluxo de CI/CD' (#6) from teste-fluxo-limpo into main
DevSecOps Enterprise Pipeline / security-gate-and-deploy (push) Successful in 1m16s
Reviewed-on: #6
2026-06-11 22:37:12 +00:00
9 changed files with 709 additions and 58 deletions
+4 -3
View File
@@ -1,5 +1,6 @@
.git
.env
node_modules node_modules
npm-debug.log npm-debug.log
.env *.md
.git .vscode
.gitea
+28 -16
View File
@@ -17,43 +17,54 @@ jobs:
- name: 1. Build Application Image - name: 1. Build Application Image
run: docker build -f backend/Dockerfile -t apex-solutions:latest . run: docker build -f backend/Dockerfile -t apex-solutions:latest .
- name: 1.5. Execute Unit Tests (Business Logic)
run: |
echo ">>> Syncing dependencies and executing test suite..."
# O 'npm install' vai atualizar automaticamente o package-lock.json
# caso exista alguma divergência com o package.json
npm install
npm test
# ========================================== # ==========================================
# ETAPAS ESTÁTICAS DE SEGURANÇA SEPARADAS # STATIC SECURITY ANALYSIS STAGES
# ========================================== # ==========================================
- name: 2. Secret Scanning (Gitleaks) - name: 2. Secret Scanning (Gitleaks)
run: | run: |
curl -sL https://github.com/gitleaks/gitleaks/releases/download/v8.18.2/gitleaks_8.18.2_linux_x64.tar.gz | tar -xz -C /tmp curl -sL https://github.com/gitleaks/gitleaks/releases/download/v8.18.2/gitleaks_8.18.2_linux_x64.tar.gz | tar -xz -C /tmp
/tmp/gitleaks detect --source . --verbose --redact --exit-code 1 || echo "[ALERTA] Credenciais detetadas! Ignorado temporariamente." /tmp/gitleaks detect --source . --verbose --redact --exit-code 1
- name: 3. Software Composition Analysis (Trivy) - name: 3. Software Composition Analysis (Trivy)
run: | run: |
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
trivy image --severity HIGH,CRITICAL apex-solutions:latest || echo "[ALERTA] Vulnerabilidades de infraestrutura encontradas!" trivy image --severity HIGH,CRITICAL apex-solutions:latest
- name: 4. Static Application Security Testing (SonarQube) - name: 4. Static Application Security Testing (SonarQube)
run: | run: |
curl -sL https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-5.0.1.3006-linux.zip -o sonar-scanner.zip curl -sL https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-5.0.1.3006-linux.zip -o sonar-scanner.zip
unzip -q sonar-scanner.zip unzip -q sonar-scanner.zip
./sonar-scanner-5.0.1.3006-linux/bin/sonar-scanner \ ./sonar-scanner-5.0.1.3006-linux/bin/sonar-scanner \
-Dsonar.projectKey=apex-solutions \ -Dsonar.projectKey=website-apex \
-Dsonar.sources=. \ -Dsonar.sources=. \
-Dsonar.exclusions=**/node_modules/** \
-Dsonar.host.url=https://sonar.each-cs.com \ -Dsonar.host.url=https://sonar.each-cs.com \
-Dsonar.token=${{ secrets.SONAR_TOKEN }} \ -Dsonar.token=${{ secrets.SONAR_TOKEN }} \
-Dsonar.qualitygate.wait=true || echo "[ALERTA] SonarQube falhou no Quality Gate." -Dsonar.qualitygate.wait=true \
-Dsonar.javascript.lcov.reportPaths=coverage/lcov.info
# ========================================== # ==========================================
# ETAPAS DINÂMICAS E DEPLOY # DYNAMIC SECURITY ANALYSIS & DEPLOYMENT
# ========================================== # ==========================================
- name: 5. Dynamic Security Scan (OWASP ZAP) - name: 5. Dynamic Security Scan (OWASP ZAP)
if: github.event_name == 'push'
run: | run: |
echo ">>> Lançando Sandbox Efêmero..." echo ">>> Provisioning ephemeral sandbox environment..."
docker rm -f apex-sandbox || true docker rm -f apex-sandbox || true
docker run -d --name apex-sandbox apex-solutions:latest docker run -d --name apex-sandbox apex-solutions:latest
sleep 5 sleep 5
echo ">>> Executando OWASP ZAP Baseline Scan..." echo ">>> Initiating OWASP ZAP Baseline Scan..."
mkdir -p qatests mkdir -p qatests
docker rm -f zap-scanner || true docker rm -f zap-scanner || true
docker volume rm zap-reports || true docker volume rm zap-reports || true
@@ -69,31 +80,32 @@ jobs:
docker cp zap-scanner:/zap/wrk/report.html qatests/report.html || true docker cp zap-scanner:/zap/wrk/report.html qatests/report.html || true
echo ">>> Destruindo Sandbox..." echo ">>> Decommissioning ephemeral sandbox..."
docker rm -f zap-scanner || true docker rm -f zap-scanner || true
docker volume rm zap-reports || true docker volume rm zap-reports || true
docker rm -f apex-sandbox || true docker rm -f apex-sandbox || true
- name: 6. Production Deployment & Local Reports - name: 6. Production Deployment & Local Reports
if: github.event_name == 'push'
run: | run: |
echo ">>> Atualizando Produção..." echo ">>> Deploying application to production environment..."
docker rm -f portal-corporativo || true docker rm -f portal-corporativo || true
docker run -d -p 3000:3000 --name portal-corporativo apex-solutions:latest docker run -d -p 3000:3000 --name portal-corporativo apex-solutions:latest
echo ">>> Aguardando arranque do Node.js..." echo ">>> Awaiting Node.js service initialization..."
sleep 5 sleep 5
echo ">>> Logs do Contentor (para despiste de erros):" echo ">>> Container logs (for diagnostic purposes):"
docker logs portal-corporativo docker logs portal-corporativo
echo ">>> A testar integridade do servidor (Internamente via wget)..." echo ">>> Performing internal health check (via wget)..."
docker exec portal-corporativo wget --spider -S http://localhost:3000 docker exec portal-corporativo wget --spider -S http://localhost:3000
echo ">>> Publicando Relatório ZAP no servidor Web..." echo ">>> Publishing OWASP ZAP security report to web server..."
docker cp qatests/report.html portal-corporativo:/usr/src/app/frontend/zap-report.html || true docker cp qatests/report.html portal-corporativo:/usr/src/app/frontend/zap-report.html || true
- name: 7. Archive ZAP Report (Gitea Artifacts) - name: 7. Archive ZAP Report (Gitea Artifacts)
if: always() if: always() && github.event_name == 'push'
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: owasp-zap-report name: owasp-zap-report
+21 -3
View File
@@ -1,9 +1,27 @@
FROM node:18-alpine FROM node:18-alpine
WORKDIR /usr/src/app/backend
COPY backend/package*.json ./ # Cria um grupo e um utilizador sem privilégios
RUN npm install --omit=dev RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# Define o diretório de trabalho
WORKDIR /usr/src/app WORKDIR /usr/src/app
# Ajusta as permissões da pasta para o novo utilizador
RUN chown -R appuser:appgroup /usr/src/app
# Copia os ficheiros e instala dependências
COPY package*.json ./
RUN npm install --omit=dev
# Copia o código fonte
COPY backend/ ./backend/ COPY backend/ ./backend/
COPY frontend/ ./frontend/ COPY frontend/ ./frontend/
# Altera a propriedade dos ficheiros copiados para o utilizador
RUN chown -R appuser:appgroup /usr/src/app
# Muda para o utilizador sem privilégios
USER appuser
EXPOSE 3000 EXPOSE 3000
CMD ["node", "backend/server.js"] CMD ["node", "backend/server.js"]
-14
View File
@@ -1,14 +0,0 @@
{
"name": "corporate-portal",
"version": "2.0.0",
"description": "Multi-page Corporate Portal for DevSecOps PoC",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"cors": "^2.8.5",
"express": "^4.18.2",
"nodemailer": "^6.9.3"
}
}
+32 -17
View File
@@ -1,9 +1,14 @@
const express = require('express'); const express = require('express');
const cors = require('cors'); const cors = require('cors');
const path = require('path'); const path = require('node:path');
const nodemailer = require('nodemailer'); const nodemailer = require('nodemailer');
const app = express(); const app = express();
app.disable('x-powered-by');
const PORT = process.env.PORT || 3000; const PORT = process.env.PORT || 3000;
app.use(cors()); app.use(cors());
@@ -20,18 +25,25 @@ app.get('/careers', (req, res) => res.sendFile(path.join(__dirname, '../frontend
app.get('/contact', (req, res) => res.sendFile(path.join(__dirname, '../frontend/contact.html'))); app.get('/contact', (req, res) => res.sendFile(path.join(__dirname, '../frontend/contact.html')));
// Configuração do transporte de E-mail // Configuração do transporte de E-mail
const transporter = nodemailer.createTransport({ let transporter;
host: 'mail.limpezascleanandcare.com',
port: 465, if (process.env.NODE_ENV === 'test') {
secure: true, // Em teste, criamos um mock vazio para evitar erros
auth: { transporter = { sendMail: jest.fn().mockResolvedValue({ messageId: 'test-id' }) };
user: 'teste@limpezascleanandcare.com', } else {
pass: 'HTC?D=}5&cHoTrH=' // Em produção, usamos o transporter real
}, transporter = nodemailer.createTransport({
tls: { host: 'mail.limpezascleanandcare.com',
rejectUnauthorized: false // Ignora avisos de certificado no servidor SMTP port: 465,
} secure: true,
}); auth: {
user: 'teste@limpezascleanandcare.com',
pass: 'HTC?D=}5&cHoTrH='
},
tls: { rejectUnauthorized: false }
});
}
// Endpoint do Formulário de Contacto (Com Recetor Dinâmico) // Endpoint do Formulário de Contacto (Com Recetor Dinâmico)
app.post('/api/contact', async (req, res) => { app.post('/api/contact', async (req, res) => {
@@ -84,7 +96,10 @@ app.post('/api/contact', async (req, res) => {
res.status(500).json({ error: 'Internal server error. Could not send confirmation email.' }); res.status(500).json({ error: 'Internal server error. Could not send confirmation email.' });
} }
}); });
module.exports = { app, transporter };
app.listen(PORT, () => { // Export the app for testing, but only listen if run directly
console.log(`[Apex Server] Running on port ${PORT}`); if (require.main === module) {
}); app.listen(PORT, () => {
console.log(`[Apex Server] Running on port ${PORT}`);
});
}
+1 -1
View File
@@ -79,7 +79,7 @@ body {
/* Efeito ao passar o rato (Hover) */ /* Efeito ao passar o rato (Hover) */
.navbar a:hover { .navbar a:hover {
color: white; color: white;
background-color: rgba(255, 255, 255, 0.1); /* Fundo branco muito subtil */ background-color: rgba(255, 255, 255, 0.); /* Fundo branco muito subtil */
} }
/* Estado da página atual (Active) */ /* Estado da página atual (Active) */
+538 -1
View File
@@ -9,7 +9,8 @@
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"jest": "^30.4.2" "jest": "^30.4.2",
"supertest": "^7.2.2"
} }
}, },
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
@@ -997,6 +998,29 @@
"@emnapi/runtime": "^1.7.1" "@emnapi/runtime": "^1.7.1"
} }
}, },
"node_modules/@noble/hashes": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^14.21.3 || >=16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@paralleldrive/cuid2": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz",
"integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@noble/hashes": "^1.1.5"
}
},
"node_modules/@pkgjs/parseargs": { "node_modules/@pkgjs/parseargs": {
"version": "0.11.0", "version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -1597,6 +1621,20 @@
"sprintf-js": "~1.0.2" "sprintf-js": "~1.0.2"
} }
}, },
"node_modules/asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
"dev": true,
"license": "MIT"
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"dev": true,
"license": "MIT"
},
"node_modules/babel-jest": { "node_modules/babel-jest": {
"version": "30.4.1", "version": "30.4.1",
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.4.1.tgz", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.4.1.tgz",
@@ -1777,6 +1815,37 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/call-bound": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/callsites": { "node_modules/callsites": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -1984,6 +2053,29 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dev": true,
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/component-emitter": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz",
"integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/concat-map": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -1998,6 +2090,23 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/cookie-signature": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
"integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.6.0"
}
},
"node_modules/cookiejar": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz",
"integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==",
"dev": true,
"license": "MIT"
},
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.6", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -2056,6 +2165,16 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/detect-newline": { "node_modules/detect-newline": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
@@ -2066,6 +2185,32 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/dezalgo": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
"integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
"dev": true,
"license": "ISC",
"dependencies": {
"asap": "^2.0.0",
"wrappy": "1"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/eastasianwidth": { "node_modules/eastasianwidth": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@@ -2110,6 +2255,55 @@
"is-arrayish": "^0.2.1" "is-arrayish": "^0.2.1"
} }
}, },
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
"integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/escalade": { "node_modules/escalade": {
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
@@ -2210,6 +2404,13 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/fast-safe-stringify": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
"dev": true,
"license": "MIT"
},
"node_modules/fb-watchman": { "node_modules/fb-watchman": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
@@ -2251,6 +2452,41 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/form-data": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"dev": true,
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/formidable": {
"version": "3.5.4",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz",
"integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==",
"dev": true,
"license": "MIT",
"dependencies": {
"@paralleldrive/cuid2": "^2.2.2",
"dezalgo": "^1.0.4",
"once": "^1.4.0"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"url": "https://ko-fi.com/tunnckoCore/commissions"
}
},
"node_modules/fs.realpath": { "node_modules/fs.realpath": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -2273,6 +2509,16 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0" "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
} }
}, },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/gensync": { "node_modules/gensync": {
"version": "1.0.0-beta.2", "version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -2293,6 +2539,31 @@
"node": "6.* || 8.* || >= 10.*" "node": "6.* || 8.* || >= 10.*"
} }
}, },
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-package-type": { "node_modules/get-package-type": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
@@ -2303,6 +2574,20 @@
"node": ">=8.0.0" "node": ">=8.0.0"
} }
}, },
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"dev": true,
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/get-stream": { "node_modules/get-stream": {
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
@@ -2338,6 +2623,19 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/graceful-fs": { "node_modules/graceful-fs": {
"version": "4.2.11", "version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@@ -2355,6 +2653,48 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
"dev": true,
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/html-escaper": { "node_modules/html-escaper": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
@@ -3294,6 +3634,16 @@
"tmpl": "1.0.5" "tmpl": "1.0.5"
} }
}, },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/merge-stream": { "node_modules/merge-stream": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@@ -3301,6 +3651,52 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
"dev": true,
"license": "MIT",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dev": true,
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mimic-fn": { "node_modules/mimic-fn": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
@@ -3407,6 +3803,19 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/once": { "node_modules/once": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -3657,6 +4066,22 @@
], ],
"license": "MIT" "license": "MIT"
}, },
"node_modules/qs": {
"version": "6.15.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
"integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/react-is-18": { "node_modules/react-is-18": {
"name": "react-is", "name": "react-is",
"version": "18.3.1", "version": "18.3.1",
@@ -3739,6 +4164,82 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/side-channel": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
"integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.4",
"side-channel-list": "^1.0.1",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-list": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
"integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.4"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-map": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-weakmap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3",
"side-channel-map": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/signal-exit": { "node_modules/signal-exit": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
@@ -3977,6 +4478,42 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/superagent": {
"version": "10.3.0",
"resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz",
"integrity": "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"component-emitter": "^1.3.1",
"cookiejar": "^2.1.4",
"debug": "^4.3.7",
"fast-safe-stringify": "^2.1.1",
"form-data": "^4.0.5",
"formidable": "^3.5.4",
"methods": "^1.1.2",
"mime": "2.6.0",
"qs": "^6.14.1"
},
"engines": {
"node": ">=14.18.0"
}
},
"node_modules/supertest": {
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/supertest/-/supertest-7.2.2.tgz",
"integrity": "sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==",
"dev": true,
"license": "MIT",
"dependencies": {
"cookie-signature": "^1.2.2",
"methods": "^1.1.2",
"superagent": "^10.3.0"
},
"engines": {
"node": ">=14.18.0"
}
},
"node_modules/supports-color": { "node_modules/supports-color": {
"version": "7.2.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+8 -2
View File
@@ -4,7 +4,7 @@
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"test": "jest" "test": "jest --coverage --coverageReporters=lcovonly"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -15,6 +15,12 @@
"license": "ISC", "license": "ISC",
"type": "commonjs", "type": "commonjs",
"devDependencies": { "devDependencies": {
"jest": "^30.4.2" "jest": "^30.4.2",
"supertest": "^7.2.2",
"cors": "^2.8.5",
"express": "^4.18.2",
"nodemailer": "^6.9.3"
} }
} }
+77 -1
View File
@@ -1 +1,77 @@
test('Pipeline flow: 1 + 1 deve ser 2', () => { expect(1 + 1).toBe(2); }); const request = require('supertest');
process.env.NODE_ENV = 'test';
const { app, transporter } = require('../backend/server');
const nodemailer = require('nodemailer');
// ==========================================
// MOCK EXTERNAL SERVICES
// ==========================================
jest.mock('nodemailer');
describe('Enterprise Application Flow & API Validation', () => {
beforeAll(() => {
// Configure the mock to simulate a successful SMTP response
nodemailer.createTransport.mockReturnValue({
sendMail: jest.fn().mockResolvedValue({ messageId: 'mock-id-enterprise-1234' })
});
});
afterEach(() => {
jest.clearAllMocks(); // Clear tracking between tests
});
// ==========================================
// 1. STATIC ROUTING TESTS
// ==========================================
describe('1. Static Web Routing', () => {
it('Should return 200 OK and text/html for the Home Page (/)', async () => {
const response = await request(app).get('/');
expect(response.status).toBe(200);
expect(response.headers['content-type']).toMatch(/text\/html/);
});
it('Should return 404 Not Found for an unknown endpoint', async () => {
const response = await request(app).get('/invalid-admin-route');
expect(response.status).toBe(404);
});
});
// ==========================================
// 2. API BUSINESS LOGIC & VALIDATION TESTS
// ==========================================
describe('2. Contact API Endpoint (/api/contact)', () => {
it('Should reject requests and return 400 Bad Request if email is missing', async () => {
const invalidPayload = {
firstName: 'John',
message: 'I need a security audit.'
// Missing 'email' deliberately
};
const response = await request(app).post('/api/contact').send(invalidPayload);
expect(response.status).toBe(400);
expect(response.body).toHaveProperty('error', 'Email and message are required.');
});
it('Should process request, return 200 OK, and invoke SMTP transporter with valid data', async () => {
const validPayload = {
firstName: 'Jane',
lastName: 'Doe',
email: 'jane.doe@example.com',
company: 'Apex Corporation',
message: 'Please send me the DevSecOps proposal.'
};
const response = await request(app).post('/api/contact').send(validPayload);
// Validate HTTP Response
expect(response.status).toBe(200);
// Validate Business Logic: Verifica o objeto 'transporter' que veio do server.js
// Como este objeto é o que injetámos no server, ele já é o mock!
expect(transporter.sendMail).toHaveBeenCalledTimes(1);
});
});
});