mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
Move config file to .safe-chain path.
This commit is contained in:
parent
ffbdedc7cd
commit
cfaa8e45ad
5 changed files with 115 additions and 48 deletions
|
|
@ -252,7 +252,30 @@ function getDatabaseVersionPath() {
|
|||
* @returns {string}
|
||||
*/
|
||||
function getConfigFilePath() {
|
||||
return path.join(getAikidoDirectory(), "config.json");
|
||||
const primaryPath = path.join(getSafeChainDirectory(), "config.json");
|
||||
if (fs.existsSync(primaryPath)) {
|
||||
return primaryPath;
|
||||
}
|
||||
|
||||
const legacyPath = path.join(getAikidoDirectory(), "config.json");
|
||||
if (fs.existsSync(legacyPath)) {
|
||||
return legacyPath;
|
||||
}
|
||||
|
||||
return primaryPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
function getSafeChainDirectory() {
|
||||
const homeDir = os.homedir();
|
||||
const safeChainDir = path.join(homeDir, ".safe-chain");
|
||||
|
||||
if (!fs.existsSync(safeChainDir)) {
|
||||
fs.mkdirSync(safeChainDir, { recursive: true });
|
||||
}
|
||||
return safeChainDir;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,16 +1,35 @@
|
|||
import { describe, it, beforeEach, afterEach, mock } from "node:test";
|
||||
import assert from "node:assert";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
|
||||
let configFileContent = undefined;
|
||||
const safeChainConfigPath = path.join(os.homedir(), ".safe-chain", "config.json");
|
||||
const aikidoConfigPath = path.join(os.homedir(), ".aikido", "config.json");
|
||||
|
||||
/** @type {Map<string, string>} */
|
||||
let mockFiles = new Map();
|
||||
mock.module("fs", {
|
||||
namedExports: {
|
||||
existsSync: () => configFileContent !== undefined,
|
||||
readFileSync: () => configFileContent,
|
||||
writeFileSync: (content) => (configFileContent = content),
|
||||
existsSync: (filePath) => mockFiles.has(filePath),
|
||||
readFileSync: (filePath) => {
|
||||
if (!mockFiles.has(filePath)) {
|
||||
throw new Error(`ENOENT: no such file: ${filePath}`);
|
||||
}
|
||||
return mockFiles.get(filePath);
|
||||
},
|
||||
writeFileSync: (filePath, content) => mockFiles.set(filePath, content),
|
||||
mkdirSync: () => {},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper to set config content at the primary (~/.safe-chain/) location.
|
||||
* @param {string} content
|
||||
*/
|
||||
function setConfigContent(content) {
|
||||
mockFiles.set(safeChainConfigPath, content);
|
||||
}
|
||||
|
||||
describe("getScanTimeout", async () => {
|
||||
let originalEnv;
|
||||
|
||||
|
|
@ -29,12 +48,11 @@ describe("getScanTimeout", async () => {
|
|||
delete process.env.AIKIDO_SCAN_TIMEOUT_MS;
|
||||
}
|
||||
|
||||
configFileContent = undefined;
|
||||
mockFiles.clear();
|
||||
});
|
||||
|
||||
it("should return default timeout of 10000ms when no config or env var is set", () => {
|
||||
delete process.env.AIKIDO_SCAN_TIMEOUT_MS;
|
||||
configFileContent = undefined;
|
||||
|
||||
const timeout = getScanTimeout();
|
||||
|
||||
|
|
@ -43,7 +61,7 @@ describe("getScanTimeout", async () => {
|
|||
|
||||
it("should return timeout from config file when set", () => {
|
||||
delete process.env.AIKIDO_SCAN_TIMEOUT_MS;
|
||||
configFileContent = JSON.stringify({ scanTimeout: 5000 });
|
||||
setConfigContent(JSON.stringify({ scanTimeout: 5000 }));
|
||||
|
||||
const timeout = getScanTimeout();
|
||||
|
||||
|
|
@ -52,7 +70,7 @@ describe("getScanTimeout", async () => {
|
|||
|
||||
it("should prioritize environment variable over config file", () => {
|
||||
process.env.AIKIDO_SCAN_TIMEOUT_MS = "20000";
|
||||
configFileContent = JSON.stringify({ scanTimeout: 5000 });
|
||||
setConfigContent(JSON.stringify({ scanTimeout: 5000 }));
|
||||
|
||||
const timeout = getScanTimeout();
|
||||
|
||||
|
|
@ -61,7 +79,7 @@ describe("getScanTimeout", async () => {
|
|||
|
||||
it("should handle invalid environment variable and fall back to config", () => {
|
||||
process.env.AIKIDO_SCAN_TIMEOUT_MS = "invalid";
|
||||
configFileContent = JSON.stringify({ scanTimeout: 7000 });
|
||||
setConfigContent(JSON.stringify({ scanTimeout: 7000 }));
|
||||
|
||||
const timeout = getScanTimeout();
|
||||
|
||||
|
|
@ -69,8 +87,6 @@ describe("getScanTimeout", async () => {
|
|||
});
|
||||
|
||||
it("should ignore zero and negative values and fall back to default", () => {
|
||||
configFileContent = undefined;
|
||||
|
||||
process.env.AIKIDO_SCAN_TIMEOUT_MS = "0";
|
||||
|
||||
let timeout = getScanTimeout();
|
||||
|
|
@ -84,7 +100,7 @@ describe("getScanTimeout", async () => {
|
|||
|
||||
it("should ignore textual non-numeric values in environment variable and fall back to config", () => {
|
||||
process.env.AIKIDO_SCAN_TIMEOUT_MS = "fast";
|
||||
configFileContent = JSON.stringify({ scanTimeout: 8000 });
|
||||
setConfigContent(JSON.stringify({ scanTimeout: 8000 }));
|
||||
|
||||
const timeout = getScanTimeout();
|
||||
|
||||
|
|
@ -93,7 +109,7 @@ describe("getScanTimeout", async () => {
|
|||
|
||||
it("should ignore textual non-numeric values in config file and fall back to default", () => {
|
||||
delete process.env.AIKIDO_SCAN_TIMEOUT_MS;
|
||||
configFileContent = JSON.stringify({ scanTimeout: "slow" });
|
||||
setConfigContent(JSON.stringify({ scanTimeout: "slow" }));
|
||||
|
||||
const timeout = getScanTimeout();
|
||||
|
||||
|
|
@ -102,7 +118,7 @@ describe("getScanTimeout", async () => {
|
|||
|
||||
it("should ignore textual non-numeric values in both env and config, fall back to default", () => {
|
||||
process.env.AIKIDO_SCAN_TIMEOUT_MS = "quick";
|
||||
configFileContent = JSON.stringify({ scanTimeout: "medium" });
|
||||
setConfigContent(JSON.stringify({ scanTimeout: "medium" }));
|
||||
|
||||
const timeout = getScanTimeout();
|
||||
|
||||
|
|
@ -111,7 +127,7 @@ describe("getScanTimeout", async () => {
|
|||
|
||||
it("should ignore mixed alphanumeric strings in environment variable", () => {
|
||||
process.env.AIKIDO_SCAN_TIMEOUT_MS = "5000ms";
|
||||
configFileContent = JSON.stringify({ scanTimeout: 6000 });
|
||||
setConfigContent(JSON.stringify({ scanTimeout: 6000 }));
|
||||
|
||||
const timeout = getScanTimeout();
|
||||
|
||||
|
|
@ -120,7 +136,7 @@ describe("getScanTimeout", async () => {
|
|||
|
||||
it("should ignore mixed alphanumeric strings in config file", () => {
|
||||
delete process.env.AIKIDO_SCAN_TIMEOUT_MS;
|
||||
configFileContent = JSON.stringify({ scanTimeout: "3000ms" });
|
||||
setConfigContent(JSON.stringify({ scanTimeout: "3000ms" }));
|
||||
|
||||
const timeout = getScanTimeout();
|
||||
|
||||
|
|
@ -132,19 +148,17 @@ describe("getMinimumPackageAgeHours", async () => {
|
|||
const { getMinimumPackageAgeHours } = await import("./configFile.js");
|
||||
|
||||
afterEach(() => {
|
||||
configFileContent = undefined;
|
||||
mockFiles.clear();
|
||||
});
|
||||
|
||||
it("should return null when config file doesn't exist", () => {
|
||||
configFileContent = undefined;
|
||||
|
||||
const hours = getMinimumPackageAgeHours();
|
||||
|
||||
assert.strictEqual(hours, undefined);
|
||||
});
|
||||
|
||||
it("should return null when config file exists but minimumPackageAgeHours is not set", () => {
|
||||
configFileContent = JSON.stringify({ scanTimeout: 5000 });
|
||||
setConfigContent(JSON.stringify({ scanTimeout: 5000 }));
|
||||
|
||||
const hours = getMinimumPackageAgeHours();
|
||||
|
||||
|
|
@ -152,7 +166,7 @@ describe("getMinimumPackageAgeHours", async () => {
|
|||
});
|
||||
|
||||
it("should return value from config file when set to valid number", () => {
|
||||
configFileContent = JSON.stringify({ minimumPackageAgeHours: 48 });
|
||||
setConfigContent(JSON.stringify({ minimumPackageAgeHours: 48 }));
|
||||
|
||||
const hours = getMinimumPackageAgeHours();
|
||||
|
||||
|
|
@ -160,7 +174,7 @@ describe("getMinimumPackageAgeHours", async () => {
|
|||
});
|
||||
|
||||
it("should handle string numbers in config file", () => {
|
||||
configFileContent = JSON.stringify({ minimumPackageAgeHours: "72" });
|
||||
setConfigContent(JSON.stringify({ minimumPackageAgeHours: "72" }));
|
||||
|
||||
const hours = getMinimumPackageAgeHours();
|
||||
|
||||
|
|
@ -168,7 +182,7 @@ describe("getMinimumPackageAgeHours", async () => {
|
|||
});
|
||||
|
||||
it("should handle decimal values", () => {
|
||||
configFileContent = JSON.stringify({ minimumPackageAgeHours: 1.5 });
|
||||
setConfigContent(JSON.stringify({ minimumPackageAgeHours: 1.5 }));
|
||||
|
||||
const hours = getMinimumPackageAgeHours();
|
||||
|
||||
|
|
@ -176,7 +190,7 @@ describe("getMinimumPackageAgeHours", async () => {
|
|||
});
|
||||
|
||||
it("should return null for non-numeric strings", () => {
|
||||
configFileContent = JSON.stringify({ minimumPackageAgeHours: "invalid" });
|
||||
setConfigContent(JSON.stringify({ minimumPackageAgeHours: "invalid" }));
|
||||
|
||||
const hours = getMinimumPackageAgeHours();
|
||||
|
||||
|
|
@ -184,7 +198,7 @@ describe("getMinimumPackageAgeHours", async () => {
|
|||
});
|
||||
|
||||
it("should return undefined for values with units suffix", () => {
|
||||
configFileContent = JSON.stringify({ minimumPackageAgeHours: "48h" });
|
||||
setConfigContent(JSON.stringify({ minimumPackageAgeHours: "48h" }));
|
||||
|
||||
const hours = getMinimumPackageAgeHours();
|
||||
|
||||
|
|
@ -192,7 +206,7 @@ describe("getMinimumPackageAgeHours", async () => {
|
|||
});
|
||||
|
||||
it("should handle malformed JSON and return null", () => {
|
||||
configFileContent = "{ invalid json";
|
||||
setConfigContent("{ invalid json");
|
||||
|
||||
const hours = getMinimumPackageAgeHours();
|
||||
|
||||
|
|
@ -200,7 +214,7 @@ describe("getMinimumPackageAgeHours", async () => {
|
|||
});
|
||||
|
||||
it("should return 0 when minimumPackageAgeHours is set to 0", () => {
|
||||
configFileContent = JSON.stringify({ minimumPackageAgeHours: 0 });
|
||||
setConfigContent(JSON.stringify({ minimumPackageAgeHours: 0 }));
|
||||
|
||||
const hours = getMinimumPackageAgeHours();
|
||||
|
||||
|
|
@ -208,7 +222,7 @@ describe("getMinimumPackageAgeHours", async () => {
|
|||
});
|
||||
|
||||
it("should return 0 when minimumPackageAgeHours is set to string '0'", () => {
|
||||
configFileContent = JSON.stringify({ minimumPackageAgeHours: "0" });
|
||||
setConfigContent(JSON.stringify({ minimumPackageAgeHours: "0" }));
|
||||
|
||||
const hours = getMinimumPackageAgeHours();
|
||||
|
||||
|
|
@ -216,7 +230,7 @@ describe("getMinimumPackageAgeHours", async () => {
|
|||
});
|
||||
|
||||
it("should handle negative numeric values", () => {
|
||||
configFileContent = JSON.stringify({ minimumPackageAgeHours: -24 });
|
||||
setConfigContent(JSON.stringify({ minimumPackageAgeHours: -24 }));
|
||||
|
||||
const hours = getMinimumPackageAgeHours();
|
||||
|
||||
|
|
@ -224,7 +238,7 @@ describe("getMinimumPackageAgeHours", async () => {
|
|||
});
|
||||
|
||||
it("should handle negative string values", () => {
|
||||
configFileContent = JSON.stringify({ minimumPackageAgeHours: "-48" });
|
||||
setConfigContent(JSON.stringify({ minimumPackageAgeHours: "-48" }));
|
||||
|
||||
const hours = getMinimumPackageAgeHours();
|
||||
|
||||
|
|
@ -249,19 +263,17 @@ for (const { packageManager, getCustomRegistries } of [
|
|||
{
|
||||
describe(getCustomRegistries.name, async () => {
|
||||
afterEach(() => {
|
||||
configFileContent = undefined;
|
||||
mockFiles.clear();
|
||||
});
|
||||
|
||||
it("should return empty array when config file doesn't exist", () => {
|
||||
configFileContent = undefined;
|
||||
|
||||
const registries = getCustomRegistries();
|
||||
|
||||
assert.deepStrictEqual(registries, []);
|
||||
});
|
||||
|
||||
it(`should return empty array when ${packageManager} config is not set`, () => {
|
||||
configFileContent = JSON.stringify({ scanTimeout: 5000 });
|
||||
setConfigContent(JSON.stringify({ scanTimeout: 5000 }));
|
||||
|
||||
const registries = getCustomRegistries();
|
||||
|
||||
|
|
@ -269,9 +281,9 @@ for (const { packageManager, getCustomRegistries } of [
|
|||
});
|
||||
|
||||
it("should return empty array when customRegistries is not an array", () => {
|
||||
configFileContent = JSON.stringify({
|
||||
setConfigContent(JSON.stringify({
|
||||
[packageManager]: { customRegistries: "not-an-array" },
|
||||
});
|
||||
}));
|
||||
|
||||
const registries = getCustomRegistries();
|
||||
|
||||
|
|
@ -279,11 +291,11 @@ for (const { packageManager, getCustomRegistries } of [
|
|||
});
|
||||
|
||||
it("should return array of custom registries when set", () => {
|
||||
configFileContent = JSON.stringify({
|
||||
setConfigContent(JSON.stringify({
|
||||
[packageManager]: {
|
||||
customRegistries: [`${packageManager}.company.com`, "registry.internal.net"],
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
const registries = getCustomRegistries();
|
||||
|
||||
|
|
@ -294,7 +306,7 @@ for (const { packageManager, getCustomRegistries } of [
|
|||
});
|
||||
|
||||
it("should filter out non-string values", () => {
|
||||
configFileContent = JSON.stringify({
|
||||
setConfigContent(JSON.stringify({
|
||||
[packageManager]: {
|
||||
customRegistries: [
|
||||
`${packageManager}.company.com`,
|
||||
|
|
@ -305,7 +317,7 @@ for (const { packageManager, getCustomRegistries } of [
|
|||
{},
|
||||
],
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
const registries = getCustomRegistries();
|
||||
|
||||
|
|
@ -316,9 +328,9 @@ for (const { packageManager, getCustomRegistries } of [
|
|||
});
|
||||
|
||||
it("should return empty array for empty customRegistries array", () => {
|
||||
configFileContent = JSON.stringify({
|
||||
setConfigContent(JSON.stringify({
|
||||
[packageManager]: { customRegistries: [] },
|
||||
});
|
||||
}));
|
||||
|
||||
const registries = getCustomRegistries();
|
||||
|
||||
|
|
@ -326,7 +338,7 @@ for (const { packageManager, getCustomRegistries } of [
|
|||
});
|
||||
|
||||
it("should handle malformed JSON and return empty array", () => {
|
||||
configFileContent = "{ invalid json";
|
||||
setConfigContent("{ invalid json");
|
||||
|
||||
const registries = getCustomRegistries();
|
||||
|
||||
|
|
@ -334,3 +346,35 @@ for (const { packageManager, getCustomRegistries } of [
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe("config file location fallback", async () => {
|
||||
const { getScanTimeout } = await import("./configFile.js");
|
||||
|
||||
afterEach(() => {
|
||||
mockFiles.clear();
|
||||
delete process.env.AIKIDO_SCAN_TIMEOUT_MS;
|
||||
});
|
||||
|
||||
it("should read config from ~/.safe-chain/config.json when it exists", () => {
|
||||
mockFiles.set(safeChainConfigPath, JSON.stringify({ scanTimeout: 3000 }));
|
||||
|
||||
assert.strictEqual(getScanTimeout(), 3000);
|
||||
});
|
||||
|
||||
it("should fall back to ~/.aikido/config.json when primary does not exist", () => {
|
||||
mockFiles.set(aikidoConfigPath, JSON.stringify({ scanTimeout: 4000 }));
|
||||
|
||||
assert.strictEqual(getScanTimeout(), 4000);
|
||||
});
|
||||
|
||||
it("should prefer ~/.safe-chain/config.json when both exist", () => {
|
||||
mockFiles.set(safeChainConfigPath, JSON.stringify({ scanTimeout: 3000 }));
|
||||
mockFiles.set(aikidoConfigPath, JSON.stringify({ scanTimeout: 4000 }));
|
||||
|
||||
assert.strictEqual(getScanTimeout(), 3000);
|
||||
});
|
||||
|
||||
it("should return default when neither config file exists", () => {
|
||||
assert.strictEqual(getScanTimeout(), 10000);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue