Skip to content

Commit b3b7ce0

Browse files
committed
test: aumenta cobertura de testes para atingir threshold de 80%
Adiciona suites de testes unitários para módulos sem cobertura: - pagination: testa parsePagination (clamp, valores inválidos, padrões) e paginate (paginação, bordas, lista vazia) - generateUsername: testa normalização, fallbacks, conflitos no banco e sufixos incrementais até o limite de 20 tentativas - kwsync: testa publish e publishBatch (serialização, source padrão, silenciamento de erros, batch atômico) - findUsers: testa findUserByProvider e findUserByEmail com tx explícito e db padrão Corrige testes existentes em app.test.ts e users.functions.test.ts: adequa contratos às rotas reais (Valkey, Drizzle, kwsync), corrige mock parcial dinâmico de createUser/createAccount via mockReset e ajusta mock de withSession para injetar sessão válida nos testes de integração das rotas.
1 parent 1320d1e commit b3b7ce0

62 files changed

Lines changed: 5750 additions & 1293 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.cspell/custom-dictionary-workspace.txt

Lines changed: 418 additions & 0 deletions
Large diffs are not rendered by default.

backend/bruno/Auth/Login.bru

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,17 @@ body:json {
1818
}
1919

2020
tests {
21-
const rawCookie = res.headers["set-cookie"];
21+
console.log("HEADERS:", res.headers);
22+
23+
const rawCookie =
24+
res.headers["set-cookie"] ||
25+
res.headers["Set-Cookie"];
26+
27+
console.log("RAW COOKIE:", rawCookie);
28+
29+
if (!rawCookie) {
30+
throw new Error("Set-Cookie não encontrado");
31+
}
2232

2333
const cookie = Array.isArray(rawCookie)
2434
? rawCookie[0]
@@ -28,7 +38,7 @@ tests {
2838

2939
bru.setEnvVar("sessionCookie", sessionCookie);
3040

31-
console.log(sessionCookie);
41+
console.log("SESSION COOKIE:", sessionCookie);
3242
}
3343

3444
settings {

backend/bruno/Jobs/SearchJobs.bru

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@ meta {
55
}
66

77
get {
8-
url: {{base_url}}/jobs/search
8+
url: {{base_url}}/jobs/search?keywords=java,node.js,react,angular
99
body: none
1010
auth: inherit
1111
}
1212

13+
params:query {
14+
keywords: java,node.js,react,angular
15+
}
16+
1317
settings {
1418
encodeUrl: true
1519
timeout: 0
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
vars {
22
base_url: http://localhost:3001/api
3-
sessionCookie:
3+
sessionCookie: vagas_session=Fe26.2*1*6b1ee4abcbe0ef8e42e8203e5e9110ead280c442ba3c9e1bf78d0c1f24fb5f5d*7c9X00fVX_sg3MhS9HZe6w*TPGS8-0VdhkyeqTAGGkuJB-4mBhf8ybA4wEB5RrO0S-tC9ykpwRVP8y1b1T8cQFYOPsm7Q-9dccgg92xnaMswi9GoU3Q55N1flcZHo_r7PmLsF8EsadRSraRo5TQOjG2isq6B6UavP_zf_Zr5jkpbA*1780502414077*3727896ffed5c4dd607f5d8460989c13de4218813e5d6263848dc882ae4a9027*Q7YEyyEcQbXbMzCdy4gyJU4RBccUkj6GKw5WW8JhzmE~2
44
}

backend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
"express": "^5.2.1",
4343
"iron-session": "^8.0.4",
4444
"pdfkit": "^0.18.0",
45-
"redis": "^4.7.1",
45+
"redis": "^5.12.1",
4646
"swagger-jsdoc": "^6.2.8",
4747
"swagger-ui-express": "^5.0.1",
4848
"xlsx": "^0.18.5",

backend/src/app.ts

Lines changed: 0 additions & 211 deletions
Original file line numberDiff line numberDiff line change
@@ -1,214 +1,3 @@
1-
// import cors from "cors";
2-
// import express, { NextFunction, Request, Response } from "express";
3-
// import {
4-
// loadKeywords,
5-
// normalizeKeywords,
6-
// saveKeywords,
7-
// } from "./adapters/goKeywords";
8-
// import { searchJobs, type ScrapeParams } from "./adapters/goScraper";
9-
// import { getConfig } from "./config";
10-
// import { logWarn } from "./logger";
11-
// import { authRoutes } from "./routes/auth.routes";
12-
// import { userRoutes } from "./routes/users.routes";
13-
14-
// interface JobsApiAppOptions {
15-
// outputDir?: string;
16-
// }
17-
18-
// const DEFAULT_ALLOWED_ORIGINS = [
19-
// "https://painel-vagas-lake.vercel.app",
20-
// "https://painel-vagas-m6hbzlqeh-bene-teslas-projects.vercel.app",
21-
// "https://jobsglobalscraper.ddns.net",
22-
// "http://jobsglobalscraper.ddns.net",
23-
// "http://localhost:5173",
24-
// "http://localhost:5174",
25-
// ];
26-
27-
// function parseAllowedOrigins(value: string | undefined): Set<string> {
28-
// const configured = String(value ?? "")
29-
// .split(",")
30-
// .map((o) => o.trim())
31-
// .filter(Boolean);
32-
// return new Set(configured.length > 0 ? configured : DEFAULT_ALLOWED_ORIGINS);
33-
// }
34-
35-
// export function createJobsApiApp(_options: JobsApiAppOptions = {}) {
36-
// const allowedOrigins = parseAllowedOrigins(process.env.CORS_ALLOWED_ORIGINS);
37-
38-
// const corsOptions: cors.CorsOptions = {
39-
// origin(origin, callback) {
40-
// if (!origin || allowedOrigins.has(origin)) return callback(null, true);
41-
// callback(new Error("Origin not allowed by CORS"));
42-
// },
43-
// methods: ["GET", "POST", "OPTIONS"],
44-
// allowedHeaders: ["Content-Type", "Authorization"],
45-
// credentials: true,
46-
// maxAge: 86400,
47-
// };
48-
49-
// const app = express();
50-
51-
// app.disable("x-powered-by");
52-
// app.use(express.json({ limit: "16kb" }));
53-
// app.use((_req: Request, res: Response, next: NextFunction) => {
54-
// res.setHeader("X-Content-Type-Options", "nosniff");
55-
// res.setHeader("X-Frame-Options", "DENY");
56-
// res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
57-
// res.setHeader(
58-
// "Permissions-Policy",
59-
// "camera=(), microphone=(), geolocation=()",
60-
// );
61-
// next();
62-
// });
63-
// app.use(cors(corsOptions));
64-
65-
// app.use("/api/auth", authRoutes);
66-
// app.use("/api/users", userRoutes);
67-
68-
// // ── health ────────────────────────────────────────────────────────────────
69-
70-
// /**
71-
// * @swagger
72-
// * /api/health:
73-
// * get:
74-
// * summary: Verifica se a API está online
75-
// * tags: [System]
76-
// * responses:
77-
// * 200:
78-
// * description: API funcionando
79-
// */
80-
// app.get("/api/health", (_req, res) => {
81-
// res.json({ ok: true });
82-
// });
83-
84-
// // ── jobs ──────────────────────────────────────────────────────────────────
85-
86-
// /**
87-
// * @swagger
88-
// * /api/jobs/search:
89-
// * get:
90-
// * summary: Busca vagas (cache e dedup gerenciados pelo Go)
91-
// * tags: [Jobs]
92-
// * parameters:
93-
// * - in: query
94-
// * name: keywords
95-
// * schema:
96-
// * type: string
97-
// * description: Palavras-chave separadas por vírgula
98-
// * responses:
99-
// * 200:
100-
// * description: Resultado da busca com jobs, total, cachedAt, fromCache
101-
// */
102-
// app.get("/api/jobs/search", async (req: Request, res: Response) => {
103-
// try {
104-
// const baseConfig = getConfig();
105-
106-
// const keywords = req.query.keywords
107-
// ? String(req.query.keywords)
108-
// .split(",")
109-
// .map((k) => k.trim())
110-
// .filter(Boolean)
111-
// : await loadKeywords(baseConfig.keywords);
112-
113-
// const params: ScrapeParams = {
114-
// keywords,
115-
// location: baseConfig.searchLocation,
116-
// };
117-
118-
// const result = await searchJobs(params);
119-
// return res.json(result);
120-
// } catch (error) {
121-
// logWarn("Erro ao buscar vagas", { error: (error as Error).message });
122-
// return res.status(500).json({
123-
// message: "Erro ao buscar vagas.",
124-
// error: (error as Error).message,
125-
// });
126-
// }
127-
// });
128-
129-
// // ── keywords ──────────────────────────────────────────────────────────────
130-
131-
// /**
132-
// * @swagger
133-
// * /api/keywords:
134-
// * get:
135-
// * summary: Retorna palavras-chave configuradas
136-
// * tags: [Keywords]
137-
// * responses:
138-
// * 200:
139-
// * description: Lista de keywords
140-
// */
141-
// app.get("/api/keywords", async (_req, res) => {
142-
// try {
143-
// const keywords = await loadKeywords(getConfig().keywords);
144-
// return res.json({ ok: true, keywords });
145-
// } catch (error) {
146-
// return res.status(500).json({
147-
// message: "Erro ao buscar keywords.",
148-
// error: (error as Error).message,
149-
// });
150-
// }
151-
// });
152-
153-
// /**
154-
// * @swagger
155-
// * /api/keywords:
156-
// * post:
157-
// * summary: Atualiza palavras-chave
158-
// * tags: [Keywords]
159-
// * requestBody:
160-
// * required: true
161-
// * content:
162-
// * application/json:
163-
// * schema:
164-
// * type: object
165-
// * properties:
166-
// * keywords:
167-
// * type: array
168-
// * items:
169-
// * type: string
170-
// * responses:
171-
// * 200:
172-
// * description: Keywords atualizadas
173-
// * 400:
174-
// * description: Dados inválidos
175-
// */
176-
// app.post("/api/keywords", async (req: Request, res: Response) => {
177-
// try {
178-
// const normalized = normalizeKeywords(req.body?.keywords);
179-
// if (normalized === null) {
180-
// return res.status(400).json({
181-
// message: "O campo 'keywords' deve ser um array de strings.",
182-
// });
183-
// }
184-
// const saved = await saveKeywords(normalized);
185-
// return res.json({
186-
// ok: true,
187-
// message: "Keywords atualizadas com sucesso.",
188-
// keywords: saved,
189-
// });
190-
// } catch (error) {
191-
// return res.status(500).json({
192-
// message: "Erro ao salvar keywords.",
193-
// error: (error as Error).message,
194-
// });
195-
// }
196-
// });
197-
198-
// // ── error handler ─────────────────────────────────────────────────────────
199-
200-
// app.use((error: Error, _req: Request, res: Response, _next: NextFunction) => {
201-
// if (error.message === "Origin not allowed by CORS") {
202-
// return res.status(403).json({ message: "Origem não permitida." });
203-
// }
204-
// return res
205-
// .status(500)
206-
// .json({ message: "Erro interno.", error: error.message });
207-
// });
208-
209-
// return app;
210-
// }
211-
2121
import cors from "cors";
2132
import express from "express";
2143
import { corsOptions } from "./middleware/cors";

backend/src/config.ts

Lines changed: 2 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -9,32 +9,16 @@ export interface AppConfig {
99
pageTimeoutMs: number;
1010
maxPagesPerKeyword: number;
1111
viewport: Viewport;
12-
outputFile: string;
13-
pdfFile: string;
1412
searchLocation: string;
1513
searchGeoId: string;
1614
searchLanguage: string;
1715
remoteOnly: boolean;
1816
jobTypes: string;
1917
timeFilter: string;
20-
keywords: string[];
21-
keywordsStorageMode: "redis" | "env";
22-
cacheTtlMs: number;
2318
databaseUrl: string;
24-
redisUrl: string;
25-
redisKeyPrefix: string;
19+
valkeyUrl: string;
2620
}
2721

28-
const DEFAULT_KEYWORDS = [
29-
"Java",
30-
"JavaScript",
31-
"React",
32-
"Node.js",
33-
"Mulesoft",
34-
"Desenvolvedor Junior",
35-
"Desenvolvedor Pleno",
36-
];
37-
3822
function parseBoolean(value: string | undefined, fallback: boolean): boolean {
3923
if (value === undefined) return fallback;
4024
const normalized = value.trim().toLowerCase();
@@ -49,34 +33,6 @@ function parseNumber(value: string | undefined, fallback: number): number {
4933
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
5034
}
5135

52-
function getKeywordsStorageMode(): "redis" | "env" {
53-
const mode = String(process.env.KEYWORDS_STORAGE_MODE ?? "redis")
54-
.trim()
55-
.toLowerCase();
56-
return mode === "env" ? "env" : "redis";
57-
}
58-
59-
function normalizeKeywords(keywords: string[]): string[] | null {
60-
if (!Array.isArray(keywords)) return null;
61-
const result = [
62-
...new Set(keywords.map((k) => String(k ?? "").trim()).filter(Boolean)),
63-
];
64-
return result.length ? result : null;
65-
}
66-
67-
function parseKeywordsFromEnv(value: string | undefined): string[] | null {
68-
return normalizeKeywords(
69-
String(value ?? "")
70-
.split(",")
71-
.map((k) => k.trim())
72-
.filter(Boolean),
73-
);
74-
}
75-
76-
function parseKeywords(value: string | undefined): string[] {
77-
return parseKeywordsFromEnv(value) ?? DEFAULT_KEYWORDS;
78-
}
79-
8036
function parseTimeFilter(value: string | undefined, fallback: string): string {
8137
if (!value) return fallback;
8238
const normalized = value.trim();
@@ -96,19 +52,13 @@ export function getConfig(): AppConfig {
9652
width: parseNumber(process.env.VIEWPORT_WIDTH, 1280),
9753
height: parseNumber(process.env.VIEWPORT_HEIGHT, 800),
9854
},
99-
outputFile: process.env.OUTPUT_FILE ?? "output/vagas_remoto.xlsx",
100-
pdfFile: process.env.PDF_FILE ?? "output/vagas_remoto.pdf",
10155
searchLocation: process.env.SEARCH_LOCATION ?? "Brasil",
10256
searchGeoId: process.env.SEARCH_GEO_ID ?? "106057199",
10357
searchLanguage: process.env.SEARCH_LANGUAGE ?? "pt",
10458
remoteOnly: parseBoolean(process.env.REMOTE_ONLY, true),
10559
jobTypes: process.env.JOB_TYPES ?? "C,F",
10660
timeFilter: parseTimeFilter(process.env.TIME_FILTER, "r604800"),
107-
keywords: parseKeywords(process.env.SEARCH_KEYWORDS),
108-
keywordsStorageMode: getKeywordsStorageMode(),
109-
cacheTtlMs: parseNumber(process.env.CACHE_TTL_MS, 10 * 60 * 1000),
11061
databaseUrl: process.env.DATABASE_URL?.trim() ?? "",
111-
redisUrl: process.env.REDIS_URL?.trim() ?? "",
112-
redisKeyPrefix: process.env.REDIS_KEY_PREFIX?.trim() ?? "vagas-full",
62+
valkeyUrl: process.env.VALKEY_URL?.trim() ?? "",
11363
};
11464
}

backend/src/db/client.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,3 @@
1-
// import { drizzle } from "drizzle-orm/node-postgres";
2-
// import { Pool } from "pg";
3-
// import * as schema from "./schema";
4-
5-
// const pool = new Pool({
6-
// connectionString: process.env.DATABASE_URL!,
7-
// });
8-
9-
// export const db = drizzle(pool, { schema });
10-
11-
// export type DB = typeof db;
12-
13-
// export { pool };
14-
15-
// client.ts
161
import { drizzle } from "drizzle-orm/node-postgres";
172
import { Pool } from "pg";
183
import * as schema from "./schema";

backend/src/db/schema/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from "./accounts";
22
export * from "./credentials";
3+
export * from "./keywords";
34
export * from "./savedJobs";
45
export * from "./userPreferences";
56
export * from "./users";

0 commit comments

Comments
 (0)