fix(ingestion): file deletion of old articles now syncs correctly

This commit is contained in:
venus
2026-09-14 01:17:19 -05:00
parent cf16e1f60d
commit f30d636bba
2 changed files with 84 additions and 2 deletions
+51 -1
View File
@@ -173,6 +173,17 @@ Here is an image: ![[camera-board.png]]
const assetPath = path.join(tmpAssets, "camera-board.png");
assert.ok(fs.existsSync(assetPath), "Asset file must be saved");
// Create simulated compiled static post files in siteDir
const compiledPostDir = path.join(tmpDir, "posts", "flock-firmware");
fs.mkdirSync(compiledPostDir, { recursive: true });
const compiledPostHtml = path.join(compiledPostDir, "index.html");
fs.writeFileSync(compiledPostHtml, "<html><body>Simulated compiled post</body></html>");
const standalonePostHtml = path.join(tmpDir, "posts", "flock-firmware.html");
fs.writeFileSync(standalonePostHtml, "<html><body>Simulated standalone post</body></html>");
assert.ok(fs.existsSync(compiledPostHtml), "Simulated compiled post must exist before delete");
assert.ok(fs.existsSync(standalonePostHtml), "Simulated standalone post must exist before delete");
// Delete post
const resDelete = await fetch(`http://127.0.0.1:${port}/api/posts?slug=flock-firmware`, {
method: "DELETE",
@@ -182,6 +193,8 @@ Here is an image: ![[camera-board.png]]
});
assert.strictEqual(resDelete.status, 200, "Delete must return 200");
assert.ok(!fs.existsSync(writtenPath), "Post markdown file must be deleted");
assert.ok(!fs.existsSync(compiledPostDir), "Compiled post directory must be deleted from _site");
assert.ok(!fs.existsSync(standalonePostHtml), "Compiled standalone HTML must be deleted from _site");
} finally {
await new Promise((resolve) => server.close(resolve));
fs.rmSync(tmpDir, { recursive: true, force: true });
@@ -190,7 +203,7 @@ Here is an image: ![[camera-board.png]]
}
});
test("Ingestion API: adversarial protection against path traversal in slug", async () => {
test("Ingestion API: adversarial protection against path traversal in slug and DELETE validation", async () => {
const tmpDir = fs.mkdtempSync(path.join(process.cwd(), "test-tmp-site-"));
const tmpPosts = fs.mkdtempSync(path.join(process.cwd(), "test-tmp-posts-"));
process.env.API_SECRET = TEST_SECRET;
@@ -200,6 +213,7 @@ test("Ingestion API: adversarial protection against path traversal in slug", asy
const port = server.address().port;
try {
// 1. Path traversal in POST
const resTraversal = await fetch(`http://127.0.0.1:${port}/api/posts`, {
method: "POST",
headers: {
@@ -218,6 +232,42 @@ test("Ingestion API: adversarial protection against path traversal in slug", asy
} else {
assert.strictEqual(resTraversal.status, 400, "Invalid slug must return 400");
}
// 2. DELETE without auth token
const resDeleteNoAuth = await fetch(`http://127.0.0.1:${port}/api/posts?slug=any-post`, {
method: "DELETE"
});
assert.strictEqual(resDeleteNoAuth.status, 401, "DELETE without Bearer token must return 401");
// 3. DELETE with missing slug parameter
const resDeleteNoSlug = await fetch(`http://127.0.0.1:${port}/api/posts`, {
method: "DELETE",
headers: {
Authorization: `Bearer ${TEST_SECRET}`
}
});
assert.strictEqual(resDeleteNoSlug.status, 400, "DELETE without slug parameter must return 400");
// 4. DELETE with invalid/empty slug
const resDeleteBadSlug = await fetch(`http://127.0.0.1:${port}/api/posts?slug=///`, {
method: "DELETE",
headers: {
Authorization: `Bearer ${TEST_SECRET}`
}
});
assert.strictEqual(resDeleteBadSlug.status, 400, "DELETE with invalid slug must return 400");
// 5. DELETE non-existent post succeeds gracefully (idempotent)
const resDeleteNonExistent = await fetch(`http://127.0.0.1:${port}/api/posts?slug=non-existent-post`, {
method: "DELETE",
headers: {
Authorization: `Bearer ${TEST_SECRET}`
}
});
assert.strictEqual(resDeleteNonExistent.status, 200, "DELETE non-existent post must return 200 idempotently");
const jsonNonExistent = await resDeleteNonExistent.json();
assert.strictEqual(jsonNonExistent.status, "deleted");
assert.strictEqual(jsonNonExistent.slug, "non-existent-post");
} finally {
await new Promise((resolve) => server.close(resolve));
fs.rmSync(tmpDir, { recursive: true, force: true });