fix: persist keyword changes reliably in backend - #72
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
This PR improves how the backend persists and reads keyword configuration by making the keywords file path configurable and resolving it reliably (independent of process.cwd()), while also normalizing keyword lists (trim/dedupe/remove blanks) and updating tests for the new behavior.
Changes:
- Add
KEYWORDS_FILE_PATHsupport and resolve the default keywords file path relative to the module directory. - Normalize keyword lists on write/read to ensure consistent persisted and returned values (including allowing an intentionally empty list).
- Update unit/integration tests to cover the new env var and empty-list persistence behavior.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| backend/src/jobsApiApp.js | Writes/reads keywords using a configurable path, adds normalization, and ensures directories are created when persisting. |
| backend/src/config.js | Reads keywords from the same configurable file path (preferring file over SEARCH_KEYWORDS) and normalizes file-provided keywords. |
| backend/tests/unit/utils/config.test.js | Updates config tests to set KEYWORDS_FILE_PATH and adds a case for preserving an existing empty keyword list. |
| backend/tests/integration/jobsApi.test.js | Resets KEYWORDS_FILE_PATH between tests and adds a POST case for creating a nested keywords file and accepting an empty list. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| try { | ||
| const data = JSON.parse(readFileSync(envPath, "utf-8")); | ||
| return data && typeof data === "object" ? data : { KEYWORDS: [] }; |
There was a problem hiding this comment.
readEnvironmentData() treats any parsed JSON with typeof === "object" as valid, which includes arrays. If the file accidentally contains [], the function will return an array and later POST spreads it into an object (adding numeric keys) before writing back. Consider rejecting arrays explicitly (e.g., require a plain object) so corrupted/invalid shapes don't produce surprising persisted output.
| return data && typeof data === "object" ? data : { KEYWORDS: [] }; | |
| return data && typeof data === "object" && !Array.isArray(data) ? data : { KEYWORDS: [] }; |
| function getKeywordsFilePath() { | ||
| const configuredPath = process.env.KEYWORDS_FILE_PATH?.trim(); | ||
| return configuredPath | ||
| ? path.resolve(configuredPath) | ||
| : path.resolve(MODULE_DIR, "db", "environment.json"); | ||
| } | ||
|
|
||
| function normalizeKeywords(keywords) { | ||
| if (!Array.isArray(keywords)) { | ||
| return null; | ||
| } | ||
|
|
||
| return [...new Set(keywords.map((item) => String(item ?? "").trim()).filter(Boolean))]; | ||
| } |
There was a problem hiding this comment.
getKeywordsFilePath()/normalizeKeywords() are duplicated here and in src/config.js. Because both the API and getConfig() must agree on the exact keywords file path and normalization rules, duplicating the logic risks future drift (writing to one path while reading from another). Consider extracting these helpers into a shared module that both files import.
| it("POST /api/keywords cria o arquivo configurado e aceita lista vazia", async () => { | ||
| tmpDir = mkdtempSync(join(tmpdir(), "jobs-api-")); | ||
| process.env.KEYWORDS_FILE_PATH = join(tmpDir, "nested", "environment.json"); | ||
|
|
||
| const app = createJobsApiApp({ outputDir: tmpDir }); | ||
| const res = await request(app) | ||
| .post("/api/keywords") | ||
| .send({ keywords: [" ", ""] }) | ||
| .expect(200); | ||
|
|
||
| expect(res.body).toEqual({ | ||
| ok: true, | ||
| message: "Keywords atualizadas com sucesso.", | ||
| keywords: [], | ||
| }); | ||
| }); |
There was a problem hiding this comment.
This new test says it "cria o arquivo configurado", but it only asserts the HTTP response body. To actually validate the behavior the PR is targeting, add assertions that the file at KEYWORDS_FILE_PATH was created (including nested directories) and that its JSON content contains KEYWORDS: [] after normalization.
| it("parseia SEARCH_KEYWORDS em lista", () => { | ||
| vi.stubEnv("SEARCH_KEYWORDS", "Java","Spring","RabbitMQ","Docker"); | ||
| const tempDir = mkdtempSync(path.join(tmpdir(), "jobs-config-")); | ||
| vi.stubEnv("KEYWORDS_FILE_PATH", path.join(tempDir, "missing-environment.json")); | ||
| vi.stubEnv("SEARCH_KEYWORDS", "Java,Spring,RabbitMQ,Docker"); | ||
|
|
There was a problem hiding this comment.
These tests create temporary directories via mkdtempSync but never remove them. Over time (especially in local runs), this can leave many stray jobs-config-* folders in the OS temp dir. Consider tracking the created tempDir/keywordsFile per test and cleaning it up in afterEach (e.g., rmSync(tempDir, { recursive: true, force: true })).
| function getKeywordsFilePath() { | ||
| const configuredPath = process.env.KEYWORDS_FILE_PATH?.trim(); | ||
| return configuredPath | ||
| ? path.resolve(configuredPath) | ||
| : path.resolve(MODULE_DIR, "db", "environment.json"); | ||
| } | ||
|
|
||
| function normalizeKeywords(keywords) { | ||
| if (!Array.isArray(keywords)) { | ||
| return null; | ||
| } | ||
|
|
||
| return [...new Set(keywords.map((item) => String(item ?? "").trim()).filter(Boolean))]; | ||
| } |
There was a problem hiding this comment.
getKeywordsFilePath()/normalizeKeywords() are duplicated here and in src/jobsApiApp.js. Since the API writes the keywords file and getConfig() reads it, duplicating the path/normalization logic risks divergence and hard-to-debug persistence issues. Consider extracting a shared helper module to keep these rules in one place.
No description provided.