Skip to content

Develop - #73

Merged
Benevanio merged 2 commits into
masterfrom
develop
Apr 7, 2026
Merged

Benevanio merged 2 commits into
masterfrom
develop

Conversation

@Benevanio

Copy link
Copy Markdown
Collaborator

No description provided.

Copilot AI review requested due to automatic review settings April 7, 2026 15:50
@Benevanio Benevanio self-assigned this Apr 7, 2026
@vercel

vercel Bot commented Apr 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
painel-vagas Ready Ready Preview, Comment Apr 7, 2026 3:50pm

@Benevanio
Benevanio merged commit 63326a4 into master Apr 7, 2026
6 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in JobAtlas – Kanban Apr 7, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR makes backend keyword persistence/configuration more robust by introducing a configurable keywords file path (KEYWORDS_FILE_PATH) and switching default path resolution from process.cwd() to module-relative paths, ensuring consistent behavior across different runtimes (e.g., Electron/Docker/tests).

Changes:

  • Add KEYWORDS_FILE_PATH support and default keyword file location based on import.meta.url (module directory).
  • Centralize keyword file read/write + keyword normalization in the API endpoints, and adjust getConfig() keyword parsing to read from the same file.
  • Update/add unit and integration tests to cover missing keyword file scenarios and “empty keywords” 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 Adds configurable keyword storage path, keyword normalization, and read/write helpers used by /api/keywords.
backend/src/config.js Reads keywords from configurable file path and normalizes file-provided keywords.
backend/tests/unit/utils/config.test.js Updates config keyword parsing tests and adds coverage for empty-list preservation when file exists.
backend/tests/integration/jobsApi.test.js Ensures KEYWORDS_FILE_PATH is cleaned between tests and adds coverage for creating nested keyword file path + empty list behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread backend/src/jobsApiApp.js
return null;
}

return [...new Set(keywords.map((item) => String(item ?? "").trim()).filter(Boolean))];

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

normalizeKeywords coerces every array item with String(...), so non-string values (e.g. numbers/objects) are silently accepted even though the 400 message says the field must be an array of strings. Consider validating that every element is a string (and optionally rejecting non-primitive values) before normalizing, or adjust the error contract/message to match the coercion behavior.

Suggested change
return [...new Set(keywords.map((item) => String(item ?? "").trim()).filter(Boolean))];
if (!keywords.every((item) => typeof item === "string")) {
return null;
}
return [...new Set(keywords.map((item) => item.trim()).filter(Boolean))];

Copilot uses AI. Check for mistakes.
Comment thread backend/src/jobsApiApp.js
Comment on lines +20 to +53
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))];
}

function readEnvironmentData() {
const envPath = getKeywordsFilePath();

if (!existsSync(envPath)) {
return { KEYWORDS: [] };
}

try {
const data = JSON.parse(readFileSync(envPath, "utf-8"));
return data && typeof data === "object" ? data : { KEYWORDS: [] };
} catch {
return { KEYWORDS: [] };
}
}

function writeEnvironmentData(data) {
const envPath = getKeywordsFilePath();
mkdirSync(path.dirname(envPath), { recursive: true });
writeFileSync(envPath, JSON.stringify(data, null, 2), "utf-8");

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The keyword file path resolution + keyword normalization logic is duplicated here and in backend/src/config.js (and there is additional keyword normalization elsewhere). This increases the risk of behavior drifting (e.g., different handling of invalid file content). Consider extracting a single shared helper (e.g. src/keywordsStorage.js) used by both the API and getConfig().

Suggested change
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))];
}
function readEnvironmentData() {
const envPath = getKeywordsFilePath();
if (!existsSync(envPath)) {
return { KEYWORDS: [] };
}
try {
const data = JSON.parse(readFileSync(envPath, "utf-8"));
return data && typeof data === "object" ? data : { KEYWORDS: [] };
} catch {
return { KEYWORDS: [] };
}
}
function writeEnvironmentData(data) {
const envPath = getKeywordsFilePath();
mkdirSync(path.dirname(envPath), { recursive: true });
writeFileSync(envPath, JSON.stringify(data, null, 2), "utf-8");
function createKeywordsStorage() {
function getFilePath() {
const configuredPath = process.env.KEYWORDS_FILE_PATH?.trim();
return configuredPath
? path.resolve(configuredPath)
: path.resolve(MODULE_DIR, "db", "environment.json");
}
function normalize(keywords) {
if (!Array.isArray(keywords)) {
return null;
}
return [...new Set(keywords.map((item) => String(item ?? "").trim()).filter(Boolean))];
}
function read() {
const envPath = getFilePath();
if (!existsSync(envPath)) {
return { KEYWORDS: [] };
}
try {
const data = JSON.parse(readFileSync(envPath, "utf-8"));
return data && typeof data === "object" ? data : { KEYWORDS: [] };
} catch {
return { KEYWORDS: [] };
}
}
function write(data) {
const envPath = getFilePath();
mkdirSync(path.dirname(envPath), { recursive: true });
writeFileSync(envPath, JSON.stringify(data, null, 2), "utf-8");
}
return {
getFilePath,
normalize,
read,
write,
};
}
const keywordsStorage = createKeywordsStorage();
function getKeywordsFilePath() {
return keywordsStorage.getFilePath();
}
function normalizeKeywords(keywords) {
return keywordsStorage.normalize(keywords);
}
function readEnvironmentData() {
return keywordsStorage.read();
}
function writeEnvironmentData(data) {
keywordsStorage.write(data);

Copilot uses AI. Check for mistakes.
Comment on lines +184 to +199
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: [],
});
});

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test claims it "cria o arquivo configurado" but it only asserts on the HTTP response. As written, it would pass even if the file was never created/written. Either add assertions that the file exists (and contains the expected JSON) or rename the test to reflect what is actually being verified.

Copilot uses AI. Check for mistakes.
Comment on lines 51 to 58
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");

const config = getConfig();
expect(config.keywords).toEqual(["Java","Spring","RabbitMQ","Docker"]);
expect(config.keywords).toEqual(["Java", "Spring", "RabbitMQ", "Docker"]);
});

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests create temp directories/files via mkdtempSync/writeFileSync but never remove them, which can leave artifacts in the OS temp directory across repeated runs. Consider tracking created paths and cleaning them up in afterEach (e.g., rmSync(tempDir, { recursive: true, force: true })).

Copilot uses AI. Check for mistakes.
Comment thread backend/src/config.js
Comment on lines +62 to 64
if (Array.isArray(data.KEYWORDS)) {
return normalizeKeywords(data.KEYWORDS) ?? [];
}

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseKeywords only uses the file when data.KEYWORDS is an array; if the file exists but KEYWORDS has an invalid type, it falls back to env/defaults. Meanwhile, the API's GET /api/keywords will return an empty list for the same invalid content. Consider aligning the behavior (e.g., treat "file exists but KEYWORDS invalid" as an empty list everywhere, or reuse a shared read/normalize helper).

Suggested change
if (Array.isArray(data.KEYWORDS)) {
return normalizeKeywords(data.KEYWORDS) ?? [];
}
return normalizeKeywords(data?.KEYWORDS) ?? [];

Copilot uses AI. Check for mistakes.

This branch was previously deployed

1 inactive deployment
Preview — 9071ee2f Deployed Apr 7, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants