mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 20:20:49 +00:00
Adapt per review
This commit is contained in:
parent
8353f353ae
commit
2df8ce463c
6 changed files with 127 additions and 107 deletions
|
|
@ -1,10 +1,11 @@
|
|||
import fs from "fs";
|
||||
import {
|
||||
fetchNewPackagesList,
|
||||
fetchNewPackagesListVersion,
|
||||
} from "../api/aikido.js";
|
||||
import {
|
||||
readNewPackagesListFromLocalCache,
|
||||
writeNewPackagesListToLocalCache,
|
||||
getNewPackagesListPath,
|
||||
getNewPackagesListVersionPath,
|
||||
} from "../config/configFile.js";
|
||||
import { ui } from "../environment/userInteraction.js";
|
||||
import {
|
||||
|
|
@ -138,3 +139,49 @@ async function getNewPackagesList() {
|
|||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("../api/aikido.js").NewPackageEntry[]} data
|
||||
* @param {string | number} version
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
export function writeNewPackagesListToLocalCache(data, version) {
|
||||
try {
|
||||
const listPath = getNewPackagesListPath();
|
||||
const versionPath = getNewPackagesListVersionPath();
|
||||
|
||||
fs.writeFileSync(listPath, JSON.stringify(data));
|
||||
fs.writeFileSync(versionPath, version.toString());
|
||||
} catch {
|
||||
ui.writeWarning(
|
||||
"Failed to write new packages list to local cache, next time the list will be fetched from the server again."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {{newPackagesList: import("../api/aikido.js").NewPackageEntry[] | null, version: string | null}}
|
||||
*/
|
||||
export function readNewPackagesListFromLocalCache() {
|
||||
try {
|
||||
const listPath = getNewPackagesListPath();
|
||||
if (!fs.existsSync(listPath)) {
|
||||
return { newPackagesList: null, version: null };
|
||||
}
|
||||
|
||||
const data = fs.readFileSync(listPath, "utf8");
|
||||
const newPackagesList = JSON.parse(data);
|
||||
const versionPath = getNewPackagesListVersionPath();
|
||||
let version = null;
|
||||
if (fs.existsSync(versionPath)) {
|
||||
version = fs.readFileSync(versionPath, "utf8").trim();
|
||||
}
|
||||
return { newPackagesList, version };
|
||||
} catch {
|
||||
ui.writeWarning(
|
||||
"Failed to read new packages list from local cache. Continuing without local cache."
|
||||
);
|
||||
return { newPackagesList: null, version: null };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { describe, it, mock, beforeEach } from "node:test";
|
||||
import assert from "node:assert";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
|
||||
// --- shared mutable state for mocks ---
|
||||
let cachedList = null;
|
||||
let cachedVersion = null;
|
||||
let fetchedList = [];
|
||||
let fetchedVersion = "etag-1";
|
||||
let fetchVersionResult = "etag-1";
|
||||
|
|
@ -13,6 +14,7 @@ let writeWarningCalls = [];
|
|||
let fetchListError = null;
|
||||
let fetchVersionError = null;
|
||||
let importCounter = 0;
|
||||
let testHomeDir = "";
|
||||
|
||||
mock.module("../api/aikido.js", {
|
||||
namedExports: {
|
||||
|
|
@ -36,16 +38,6 @@ mock.module("../api/aikido.js", {
|
|||
},
|
||||
});
|
||||
|
||||
mock.module("../config/configFile.js", {
|
||||
namedExports: {
|
||||
readNewPackagesListFromLocalCache: () => ({
|
||||
newPackagesList: cachedList,
|
||||
version: cachedVersion,
|
||||
}),
|
||||
writeNewPackagesListToLocalCache: () => {},
|
||||
},
|
||||
});
|
||||
|
||||
mock.module("../environment/userInteraction.js", {
|
||||
namedExports: {
|
||||
ui: {
|
||||
|
|
@ -66,8 +58,6 @@ mock.module("../config/settings.js", {
|
|||
|
||||
describe("newPackagesDatabase", async () => {
|
||||
beforeEach(() => {
|
||||
cachedList = null;
|
||||
cachedVersion = null;
|
||||
fetchedList = [];
|
||||
fetchedVersion = "etag-1";
|
||||
fetchVersionResult = "etag-1";
|
||||
|
|
@ -76,6 +66,13 @@ describe("newPackagesDatabase", async () => {
|
|||
writeWarningCalls = [];
|
||||
fetchListError = null;
|
||||
fetchVersionError = null;
|
||||
testHomeDir = path.join(
|
||||
os.tmpdir(),
|
||||
`safe-chain-new-packages-db-${process.pid}-${importCounter}`
|
||||
);
|
||||
fs.rmSync(testHomeDir, { recursive: true, force: true });
|
||||
fs.mkdirSync(testHomeDir, { recursive: true });
|
||||
process.env.HOME = testHomeDir;
|
||||
});
|
||||
|
||||
async function openNewPackagesDatabase() {
|
||||
|
|
@ -93,6 +90,19 @@ describe("newPackagesDatabase", async () => {
|
|||
return Math.floor((Date.now() - hours * 3600 * 1000) / 1000);
|
||||
}
|
||||
|
||||
function writeCachedList(list, version) {
|
||||
const safeChainDir = path.join(testHomeDir, ".safe-chain");
|
||||
fs.mkdirSync(safeChainDir, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(safeChainDir, `newPackagesList_${ecosystem}.json`),
|
||||
JSON.stringify(list)
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(safeChainDir, `newPackagesList_version_${ecosystem}.txt`),
|
||||
version
|
||||
);
|
||||
}
|
||||
|
||||
describe("isNewlyReleasedPackage", () => {
|
||||
it("returns true for a package released within the age threshold", async () => {
|
||||
fetchedList = [
|
||||
|
|
@ -171,10 +181,9 @@ describe("newPackagesDatabase", async () => {
|
|||
|
||||
describe("caching behaviour", () => {
|
||||
it("uses local cache when etag matches", async () => {
|
||||
cachedList = [
|
||||
writeCachedList([
|
||||
{ package_name: "cached-pkg", version: "1.0.0", released_on: hoursAgo(1), scraped_on: hoursAgo(1) },
|
||||
];
|
||||
cachedVersion = "etag-1";
|
||||
], "etag-1");
|
||||
fetchVersionResult = "etag-1";
|
||||
// fetchedList is empty — if we used the remote list, the lookup would return false
|
||||
fetchedList = [];
|
||||
|
|
@ -184,10 +193,9 @@ describe("newPackagesDatabase", async () => {
|
|||
});
|
||||
|
||||
it("fetches fresh list when etag does not match", async () => {
|
||||
cachedList = [
|
||||
writeCachedList([
|
||||
{ package_name: "stale-pkg", version: "1.0.0", released_on: hoursAgo(1), scraped_on: hoursAgo(1) },
|
||||
];
|
||||
cachedVersion = "etag-old";
|
||||
], "etag-old");
|
||||
fetchVersionResult = "etag-new";
|
||||
fetchedList = [
|
||||
{ package_name: "fresh-pkg", version: "2.0.0", released_on: hoursAgo(1), scraped_on: hoursAgo(1) },
|
||||
|
|
@ -199,15 +207,14 @@ describe("newPackagesDatabase", async () => {
|
|||
});
|
||||
|
||||
it("falls back to local cache when fetch fails", async () => {
|
||||
cachedList = [
|
||||
writeCachedList([
|
||||
{
|
||||
package_name: "cached-pkg",
|
||||
version: "1.0.0",
|
||||
released_on: hoursAgo(1),
|
||||
scraped_on: hoursAgo(1),
|
||||
},
|
||||
];
|
||||
cachedVersion = "etag-old";
|
||||
], "etag-old");
|
||||
fetchVersionResult = "etag-new";
|
||||
fetchListError = new Error("Network error");
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue