chore: renormalize line endings under the new .gitattributes
Line-ending only. Verified: 3667 insertions against 3667 deletions with zero content difference under --ignore-cr-at-eol.
This commit is contained in:
244
.obsidian/plugins/webinar-dash/test/coverage.test.js
vendored
244
.obsidian/plugins/webinar-dash/test/coverage.test.js
vendored
@@ -1,122 +1,122 @@
|
||||
"use strict";
|
||||
const test = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const { parseCoverageTable, groupByStation } = require("../main.js").__test__;
|
||||
|
||||
const DOC = [
|
||||
"# Script coverage",
|
||||
"",
|
||||
"#coverage",
|
||||
"",
|
||||
"## Metadata",
|
||||
"",
|
||||
"- **Script:** `raw/sources/Webinar script.md`",
|
||||
"- **Last synced:** 2026-07-28",
|
||||
"",
|
||||
"## Coverage",
|
||||
"",
|
||||
"| Concept | Status | Station | Pinned |",
|
||||
"|---|---|---|---|",
|
||||
"| [[harness]] | covered | Tools | |",
|
||||
"| [[agentic-loops]] | partial | Process | |",
|
||||
"| [[levels-of-ai-usage]] | partial | all | |",
|
||||
"| [[connections-as-moat]] | absent | — | yes |",
|
||||
].join("\n");
|
||||
|
||||
test("parseCoverageTable reads metadata", () => {
|
||||
const { meta } = parseCoverageTable(DOC);
|
||||
assert.equal(meta.script, "raw/sources/Webinar script.md");
|
||||
assert.equal(meta.lastSynced, "2026-07-28");
|
||||
});
|
||||
|
||||
test("parseCoverageTable reads every data row and skips the header", () => {
|
||||
const { rows, errors } = parseCoverageTable(DOC);
|
||||
assert.equal(errors.length, 0);
|
||||
assert.equal(rows.length, 4);
|
||||
assert.deepEqual(rows.map((r) => r.concept), [
|
||||
"harness", "agentic-loops", "levels-of-ai-usage", "connections-as-moat",
|
||||
]);
|
||||
});
|
||||
|
||||
test("parseCoverageTable normalises stations", () => {
|
||||
const { rows } = parseCoverageTable(DOC);
|
||||
assert.deepEqual(rows[0].stations, ["Tools"]);
|
||||
assert.deepEqual(rows[2].stations, ["all"]);
|
||||
assert.deepEqual(rows[3].stations, []);
|
||||
});
|
||||
|
||||
test("parseCoverageTable reads the pinned flag", () => {
|
||||
const { rows } = parseCoverageTable(DOC);
|
||||
assert.equal(rows[0].pinned, false);
|
||||
assert.equal(rows[3].pinned, true);
|
||||
});
|
||||
|
||||
test("parseCoverageTable strips a wikilink alias", () => {
|
||||
const doc = "| Concept | Status | Station |\n|---|---|---|\n| [[harness\\|The harness]] | covered | Tools |";
|
||||
const { rows } = parseCoverageTable(doc);
|
||||
assert.equal(rows[0].concept, "harness");
|
||||
});
|
||||
|
||||
test("parseCoverageTable splits a multi-station cell", () => {
|
||||
const doc = "| C | S | St |\n|---|---|---|\n| [[x]] | covered | Tools, Memory |";
|
||||
const { rows } = parseCoverageTable(doc);
|
||||
assert.deepEqual(rows[0].stations, ["Tools", "Memory"]);
|
||||
});
|
||||
|
||||
test("parseCoverageTable rejects an invalid status instead of coercing it", () => {
|
||||
const doc = "| C | S | St |\n|---|---|---|\n| [[x]] | maybe | Tools |";
|
||||
const { rows, errors } = parseCoverageTable(doc);
|
||||
assert.equal(rows.length, 0);
|
||||
assert.equal(errors.length, 1);
|
||||
assert.match(errors[0].reason, /invalid status/);
|
||||
assert.equal(errors[0].line, 3);
|
||||
});
|
||||
|
||||
test("parseCoverageTable reports a row with too few columns", () => {
|
||||
const doc = "| C | S | St |\n|---|---|---|\n| [[x]] | covered |";
|
||||
const { rows, errors } = parseCoverageTable(doc);
|
||||
assert.equal(rows.length, 0);
|
||||
assert.equal(errors.length, 1);
|
||||
assert.match(errors[0].reason, /at least 3 columns/);
|
||||
});
|
||||
|
||||
test("parseCoverageTable returns empty results for a document with no table", () => {
|
||||
const { rows, errors } = parseCoverageTable("# Nothing\n\nJust prose.");
|
||||
assert.equal(rows.length, 0);
|
||||
assert.equal(errors.length, 0);
|
||||
});
|
||||
|
||||
test("groupByStation orders all-stations first and no-station last", () => {
|
||||
const { rows } = parseCoverageTable(DOC);
|
||||
const groups = groupByStation(rows);
|
||||
assert.deepEqual(groups.map((g) => g.station), [
|
||||
"All stations", "Tools", "Process", "No station",
|
||||
]);
|
||||
assert.equal(groups[1].rows[0].concept, "harness");
|
||||
});
|
||||
|
||||
test("groupByStation places a multi-station row under its first station only", () => {
|
||||
const rows = [{ concept: "x", status: "covered", stations: ["Memory", "Skills"], pinned: false, line: 1 }];
|
||||
const groups = groupByStation(rows);
|
||||
assert.equal(groups.length, 1);
|
||||
assert.equal(groups[0].station, "Memory");
|
||||
});
|
||||
|
||||
const { reconcileConcepts } = require("../main.js").__test__;
|
||||
|
||||
test("reconcileConcepts finds concept pages with no row", () => {
|
||||
const rows = [{ concept: "harness", status: "covered", stations: ["Tools"], pinned: false, line: 1 }];
|
||||
const out = reconcileConcepts(rows, ["harness", "brand-new-concept"]);
|
||||
assert.deepEqual(out.unsynced, ["brand-new-concept"]);
|
||||
assert.deepEqual(out.stale, []);
|
||||
});
|
||||
|
||||
test("reconcileConcepts finds rows whose concept page is gone", () => {
|
||||
const rows = [
|
||||
{ concept: "harness", status: "covered", stations: ["Tools"], pinned: false, line: 1 },
|
||||
{ concept: "deleted-idea", status: "absent", stations: [], pinned: false, line: 2 },
|
||||
];
|
||||
const out = reconcileConcepts(rows, ["harness"]);
|
||||
assert.deepEqual(out.stale.map((r) => r.concept), ["deleted-idea"]);
|
||||
assert.deepEqual(out.unsynced, []);
|
||||
});
|
||||
"use strict";
|
||||
const test = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const { parseCoverageTable, groupByStation } = require("../main.js").__test__;
|
||||
|
||||
const DOC = [
|
||||
"# Script coverage",
|
||||
"",
|
||||
"#coverage",
|
||||
"",
|
||||
"## Metadata",
|
||||
"",
|
||||
"- **Script:** `raw/sources/Webinar script.md`",
|
||||
"- **Last synced:** 2026-07-28",
|
||||
"",
|
||||
"## Coverage",
|
||||
"",
|
||||
"| Concept | Status | Station | Pinned |",
|
||||
"|---|---|---|---|",
|
||||
"| [[harness]] | covered | Tools | |",
|
||||
"| [[agentic-loops]] | partial | Process | |",
|
||||
"| [[levels-of-ai-usage]] | partial | all | |",
|
||||
"| [[connections-as-moat]] | absent | — | yes |",
|
||||
].join("\n");
|
||||
|
||||
test("parseCoverageTable reads metadata", () => {
|
||||
const { meta } = parseCoverageTable(DOC);
|
||||
assert.equal(meta.script, "raw/sources/Webinar script.md");
|
||||
assert.equal(meta.lastSynced, "2026-07-28");
|
||||
});
|
||||
|
||||
test("parseCoverageTable reads every data row and skips the header", () => {
|
||||
const { rows, errors } = parseCoverageTable(DOC);
|
||||
assert.equal(errors.length, 0);
|
||||
assert.equal(rows.length, 4);
|
||||
assert.deepEqual(rows.map((r) => r.concept), [
|
||||
"harness", "agentic-loops", "levels-of-ai-usage", "connections-as-moat",
|
||||
]);
|
||||
});
|
||||
|
||||
test("parseCoverageTable normalises stations", () => {
|
||||
const { rows } = parseCoverageTable(DOC);
|
||||
assert.deepEqual(rows[0].stations, ["Tools"]);
|
||||
assert.deepEqual(rows[2].stations, ["all"]);
|
||||
assert.deepEqual(rows[3].stations, []);
|
||||
});
|
||||
|
||||
test("parseCoverageTable reads the pinned flag", () => {
|
||||
const { rows } = parseCoverageTable(DOC);
|
||||
assert.equal(rows[0].pinned, false);
|
||||
assert.equal(rows[3].pinned, true);
|
||||
});
|
||||
|
||||
test("parseCoverageTable strips a wikilink alias", () => {
|
||||
const doc = "| Concept | Status | Station |\n|---|---|---|\n| [[harness\\|The harness]] | covered | Tools |";
|
||||
const { rows } = parseCoverageTable(doc);
|
||||
assert.equal(rows[0].concept, "harness");
|
||||
});
|
||||
|
||||
test("parseCoverageTable splits a multi-station cell", () => {
|
||||
const doc = "| C | S | St |\n|---|---|---|\n| [[x]] | covered | Tools, Memory |";
|
||||
const { rows } = parseCoverageTable(doc);
|
||||
assert.deepEqual(rows[0].stations, ["Tools", "Memory"]);
|
||||
});
|
||||
|
||||
test("parseCoverageTable rejects an invalid status instead of coercing it", () => {
|
||||
const doc = "| C | S | St |\n|---|---|---|\n| [[x]] | maybe | Tools |";
|
||||
const { rows, errors } = parseCoverageTable(doc);
|
||||
assert.equal(rows.length, 0);
|
||||
assert.equal(errors.length, 1);
|
||||
assert.match(errors[0].reason, /invalid status/);
|
||||
assert.equal(errors[0].line, 3);
|
||||
});
|
||||
|
||||
test("parseCoverageTable reports a row with too few columns", () => {
|
||||
const doc = "| C | S | St |\n|---|---|---|\n| [[x]] | covered |";
|
||||
const { rows, errors } = parseCoverageTable(doc);
|
||||
assert.equal(rows.length, 0);
|
||||
assert.equal(errors.length, 1);
|
||||
assert.match(errors[0].reason, /at least 3 columns/);
|
||||
});
|
||||
|
||||
test("parseCoverageTable returns empty results for a document with no table", () => {
|
||||
const { rows, errors } = parseCoverageTable("# Nothing\n\nJust prose.");
|
||||
assert.equal(rows.length, 0);
|
||||
assert.equal(errors.length, 0);
|
||||
});
|
||||
|
||||
test("groupByStation orders all-stations first and no-station last", () => {
|
||||
const { rows } = parseCoverageTable(DOC);
|
||||
const groups = groupByStation(rows);
|
||||
assert.deepEqual(groups.map((g) => g.station), [
|
||||
"All stations", "Tools", "Process", "No station",
|
||||
]);
|
||||
assert.equal(groups[1].rows[0].concept, "harness");
|
||||
});
|
||||
|
||||
test("groupByStation places a multi-station row under its first station only", () => {
|
||||
const rows = [{ concept: "x", status: "covered", stations: ["Memory", "Skills"], pinned: false, line: 1 }];
|
||||
const groups = groupByStation(rows);
|
||||
assert.equal(groups.length, 1);
|
||||
assert.equal(groups[0].station, "Memory");
|
||||
});
|
||||
|
||||
const { reconcileConcepts } = require("../main.js").__test__;
|
||||
|
||||
test("reconcileConcepts finds concept pages with no row", () => {
|
||||
const rows = [{ concept: "harness", status: "covered", stations: ["Tools"], pinned: false, line: 1 }];
|
||||
const out = reconcileConcepts(rows, ["harness", "brand-new-concept"]);
|
||||
assert.deepEqual(out.unsynced, ["brand-new-concept"]);
|
||||
assert.deepEqual(out.stale, []);
|
||||
});
|
||||
|
||||
test("reconcileConcepts finds rows whose concept page is gone", () => {
|
||||
const rows = [
|
||||
{ concept: "harness", status: "covered", stations: ["Tools"], pinned: false, line: 1 },
|
||||
{ concept: "deleted-idea", status: "absent", stations: [], pinned: false, line: 2 },
|
||||
];
|
||||
const out = reconcileConcepts(rows, ["harness"]);
|
||||
assert.deepEqual(out.stale.map((r) => r.concept), ["deleted-idea"]);
|
||||
assert.deepEqual(out.unsynced, []);
|
||||
});
|
||||
|
||||
218
.obsidian/plugins/webinar-dash/test/pipeline.test.js
vendored
218
.obsidian/plugins/webinar-dash/test/pipeline.test.js
vendored
@@ -1,109 +1,109 @@
|
||||
"use strict";
|
||||
const test = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const { extractRawPath, derivePipeline } = require("../main.js").__test__;
|
||||
|
||||
test("extractRawPath pulls the backticked path", () => {
|
||||
const page = [
|
||||
"# You're reading way too much code",
|
||||
"",
|
||||
"#source",
|
||||
"",
|
||||
"## Source Metadata",
|
||||
"",
|
||||
"- **Date:** YouTube video, 24:11",
|
||||
"- **Raw path:** `raw/sources/You're reading way too much code.md`",
|
||||
"- **Source type:** video essay",
|
||||
].join("\n");
|
||||
assert.equal(extractRawPath(page), "raw/sources/You're reading way too much code.md");
|
||||
});
|
||||
|
||||
test("extractRawPath handles cyrillic and em dashes", () => {
|
||||
const page = "- **Raw path:** `raw/sources/Скиллы на базе git — новая память AI-агентов.md`";
|
||||
assert.equal(extractRawPath(page), "raw/sources/Скиллы на базе git — новая память AI-агентов.md");
|
||||
});
|
||||
|
||||
test("extractRawPath returns null when the line is absent", () => {
|
||||
assert.equal(extractRawPath("# A page\n\n#source\n\nNo metadata here."), null);
|
||||
});
|
||||
|
||||
test("derivePipeline splits claimed from unclaimed raw files", () => {
|
||||
const rawFiles = [
|
||||
{ path: "raw/sources/Nina interview.md", name: "Nina interview.md", size: 6614 },
|
||||
{ path: "raw/sources/Webinar script.md", name: "Webinar script.md", size: 15841 },
|
||||
];
|
||||
const sourcePages = [
|
||||
{
|
||||
path: "wiki/sources/2026-07-14-nina-interview.md",
|
||||
name: "2026-07-14-nina-interview.md",
|
||||
rawPath: "raw/sources/Nina interview.md",
|
||||
},
|
||||
];
|
||||
const out = derivePipeline({ rawFiles, sourcePages });
|
||||
assert.equal(out.processed.length, 1);
|
||||
assert.equal(out.processed[0].name, "Nina interview.md");
|
||||
assert.equal(out.processed[0].page.name, "2026-07-14-nina-interview.md");
|
||||
assert.equal(out.unprocessed.length, 1);
|
||||
assert.equal(out.unprocessed[0].name, "Webinar script.md");
|
||||
assert.equal(out.orphaned.length, 0);
|
||||
});
|
||||
|
||||
test("derivePipeline reports source pages whose raw file is gone", () => {
|
||||
const out = derivePipeline({
|
||||
rawFiles: [],
|
||||
sourcePages: [
|
||||
{ path: "wiki/sources/x.md", name: "x.md", rawPath: "raw/sources/deleted.md" },
|
||||
],
|
||||
});
|
||||
assert.equal(out.orphaned.length, 1);
|
||||
assert.equal(out.orphaned[0].name, "x.md");
|
||||
});
|
||||
|
||||
test("derivePipeline treats a page with no raw path as orphaned", () => {
|
||||
const out = derivePipeline({
|
||||
rawFiles: [{ path: "raw/sources/a.md", name: "a.md", size: 10 }],
|
||||
sourcePages: [{ path: "wiki/sources/y.md", name: "y.md", rawPath: null }],
|
||||
});
|
||||
assert.equal(out.orphaned.length, 1);
|
||||
assert.equal(out.unprocessed.length, 1);
|
||||
});
|
||||
|
||||
test("derivePipeline routes a duplicate raw-path claim to orphaned", () => {
|
||||
const out = derivePipeline({
|
||||
rawFiles: [{ path: "raw/sources/a.md", name: "a.md", size: 10 }],
|
||||
sourcePages: [
|
||||
{ path: "wiki/sources/first.md", name: "first.md", rawPath: "raw/sources/a.md" },
|
||||
{ path: "wiki/sources/second.md", name: "second.md", rawPath: "raw/sources/a.md" },
|
||||
],
|
||||
});
|
||||
assert.equal(out.processed.length, 1);
|
||||
assert.equal(out.processed[0].page.name, "first.md");
|
||||
assert.equal(out.unprocessed.length, 0);
|
||||
assert.deepEqual(out.orphaned.map((p) => p.name), ["second.md"]);
|
||||
});
|
||||
|
||||
test("derivePipeline lists a page once when it is both a duplicate claim and missing its raw file", () => {
|
||||
const out = derivePipeline({
|
||||
rawFiles: [{ path: "raw/sources/other.md", name: "other.md", size: 10 }],
|
||||
sourcePages: [
|
||||
{ path: "wiki/sources/first.md", name: "first.md", rawPath: "raw/sources/gone.md" },
|
||||
{ path: "wiki/sources/second.md", name: "second.md", rawPath: "raw/sources/gone.md" },
|
||||
],
|
||||
});
|
||||
assert.deepEqual(out.orphaned.map((p) => p.name), ["first.md", "second.md"]);
|
||||
});
|
||||
|
||||
test("derivePipeline sorts unprocessed by name and processed newest first", () => {
|
||||
const out = derivePipeline({
|
||||
rawFiles: [
|
||||
{ path: "raw/sources/b.md", name: "b.md", size: 1 },
|
||||
{ path: "raw/sources/a.md", name: "a.md", size: 1 },
|
||||
{ path: "raw/sources/c.md", name: "c.md", size: 1 },
|
||||
],
|
||||
sourcePages: [
|
||||
{ path: "wiki/sources/2026-07-14-x.md", name: "2026-07-14-x.md", rawPath: "raw/sources/c.md" },
|
||||
],
|
||||
});
|
||||
assert.deepEqual(out.unprocessed.map((f) => f.name), ["a.md", "b.md"]);
|
||||
assert.equal(out.processed.length, 1);
|
||||
});
|
||||
"use strict";
|
||||
const test = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const { extractRawPath, derivePipeline } = require("../main.js").__test__;
|
||||
|
||||
test("extractRawPath pulls the backticked path", () => {
|
||||
const page = [
|
||||
"# You're reading way too much code",
|
||||
"",
|
||||
"#source",
|
||||
"",
|
||||
"## Source Metadata",
|
||||
"",
|
||||
"- **Date:** YouTube video, 24:11",
|
||||
"- **Raw path:** `raw/sources/You're reading way too much code.md`",
|
||||
"- **Source type:** video essay",
|
||||
].join("\n");
|
||||
assert.equal(extractRawPath(page), "raw/sources/You're reading way too much code.md");
|
||||
});
|
||||
|
||||
test("extractRawPath handles cyrillic and em dashes", () => {
|
||||
const page = "- **Raw path:** `raw/sources/Скиллы на базе git — новая память AI-агентов.md`";
|
||||
assert.equal(extractRawPath(page), "raw/sources/Скиллы на базе git — новая память AI-агентов.md");
|
||||
});
|
||||
|
||||
test("extractRawPath returns null when the line is absent", () => {
|
||||
assert.equal(extractRawPath("# A page\n\n#source\n\nNo metadata here."), null);
|
||||
});
|
||||
|
||||
test("derivePipeline splits claimed from unclaimed raw files", () => {
|
||||
const rawFiles = [
|
||||
{ path: "raw/sources/Nina interview.md", name: "Nina interview.md", size: 6614 },
|
||||
{ path: "raw/sources/Webinar script.md", name: "Webinar script.md", size: 15841 },
|
||||
];
|
||||
const sourcePages = [
|
||||
{
|
||||
path: "wiki/sources/2026-07-14-nina-interview.md",
|
||||
name: "2026-07-14-nina-interview.md",
|
||||
rawPath: "raw/sources/Nina interview.md",
|
||||
},
|
||||
];
|
||||
const out = derivePipeline({ rawFiles, sourcePages });
|
||||
assert.equal(out.processed.length, 1);
|
||||
assert.equal(out.processed[0].name, "Nina interview.md");
|
||||
assert.equal(out.processed[0].page.name, "2026-07-14-nina-interview.md");
|
||||
assert.equal(out.unprocessed.length, 1);
|
||||
assert.equal(out.unprocessed[0].name, "Webinar script.md");
|
||||
assert.equal(out.orphaned.length, 0);
|
||||
});
|
||||
|
||||
test("derivePipeline reports source pages whose raw file is gone", () => {
|
||||
const out = derivePipeline({
|
||||
rawFiles: [],
|
||||
sourcePages: [
|
||||
{ path: "wiki/sources/x.md", name: "x.md", rawPath: "raw/sources/deleted.md" },
|
||||
],
|
||||
});
|
||||
assert.equal(out.orphaned.length, 1);
|
||||
assert.equal(out.orphaned[0].name, "x.md");
|
||||
});
|
||||
|
||||
test("derivePipeline treats a page with no raw path as orphaned", () => {
|
||||
const out = derivePipeline({
|
||||
rawFiles: [{ path: "raw/sources/a.md", name: "a.md", size: 10 }],
|
||||
sourcePages: [{ path: "wiki/sources/y.md", name: "y.md", rawPath: null }],
|
||||
});
|
||||
assert.equal(out.orphaned.length, 1);
|
||||
assert.equal(out.unprocessed.length, 1);
|
||||
});
|
||||
|
||||
test("derivePipeline routes a duplicate raw-path claim to orphaned", () => {
|
||||
const out = derivePipeline({
|
||||
rawFiles: [{ path: "raw/sources/a.md", name: "a.md", size: 10 }],
|
||||
sourcePages: [
|
||||
{ path: "wiki/sources/first.md", name: "first.md", rawPath: "raw/sources/a.md" },
|
||||
{ path: "wiki/sources/second.md", name: "second.md", rawPath: "raw/sources/a.md" },
|
||||
],
|
||||
});
|
||||
assert.equal(out.processed.length, 1);
|
||||
assert.equal(out.processed[0].page.name, "first.md");
|
||||
assert.equal(out.unprocessed.length, 0);
|
||||
assert.deepEqual(out.orphaned.map((p) => p.name), ["second.md"]);
|
||||
});
|
||||
|
||||
test("derivePipeline lists a page once when it is both a duplicate claim and missing its raw file", () => {
|
||||
const out = derivePipeline({
|
||||
rawFiles: [{ path: "raw/sources/other.md", name: "other.md", size: 10 }],
|
||||
sourcePages: [
|
||||
{ path: "wiki/sources/first.md", name: "first.md", rawPath: "raw/sources/gone.md" },
|
||||
{ path: "wiki/sources/second.md", name: "second.md", rawPath: "raw/sources/gone.md" },
|
||||
],
|
||||
});
|
||||
assert.deepEqual(out.orphaned.map((p) => p.name), ["first.md", "second.md"]);
|
||||
});
|
||||
|
||||
test("derivePipeline sorts unprocessed by name and processed newest first", () => {
|
||||
const out = derivePipeline({
|
||||
rawFiles: [
|
||||
{ path: "raw/sources/b.md", name: "b.md", size: 1 },
|
||||
{ path: "raw/sources/a.md", name: "a.md", size: 1 },
|
||||
{ path: "raw/sources/c.md", name: "c.md", size: 1 },
|
||||
],
|
||||
sourcePages: [
|
||||
{ path: "wiki/sources/2026-07-14-x.md", name: "2026-07-14-x.md", rawPath: "raw/sources/c.md" },
|
||||
],
|
||||
});
|
||||
assert.deepEqual(out.unprocessed.map((f) => f.name), ["a.md", "b.md"]);
|
||||
assert.equal(out.processed.length, 1);
|
||||
});
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
"use strict";
|
||||
const test = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const { isSafeFilename } = require("../main.js").__test__;
|
||||
|
||||
test("isSafeFilename accepts every filename currently in the vault", () => {
|
||||
const real = [
|
||||
"Agentic Engineering, explained by a 10x developer.md",
|
||||
"Webinar Plan - From Chat Box to Your Own OS.md",
|
||||
"Webinar script.md",
|
||||
"You're reading way too much code.md",
|
||||
"ИИ глупый!.md",
|
||||
"Скиллы на базе git — новая память AI-агентов.md",
|
||||
"sebastian interview - conclusions and insights.md",
|
||||
"In 1 Year, the Gap Between AI Users and Everyone Else Will Be Irreversible.md",
|
||||
];
|
||||
for (const name of real) {
|
||||
assert.equal(isSafeFilename(name), true, `should accept: ${name}`);
|
||||
}
|
||||
});
|
||||
|
||||
test("isSafeFilename rejects shell metacharacters", () => {
|
||||
for (const bad of ['a".md', "a`b.md", "a$b.md", "a&b.md", "a|b.md", "a;b.md",
|
||||
"a<b.md", "a>b.md", "a%b.md", "a\nb.md", "a\rb.md"]) {
|
||||
assert.equal(isSafeFilename(bad), false, `should reject: ${JSON.stringify(bad)}`);
|
||||
}
|
||||
});
|
||||
|
||||
test("isSafeFilename rejects the cmd.exe escape character and control characters", () => {
|
||||
// `^` escapes the next character in cmd.exe, so it can defuse the closing
|
||||
// quote. NUL additionally makes spawn() throw synchronously.
|
||||
for (const bad of ["a^b.md", "a\u0000b.md", "a\u001bb.md", "a\u007fb.md"]) {
|
||||
assert.equal(isSafeFilename(bad), false, `should reject: ${JSON.stringify(bad)}`);
|
||||
}
|
||||
});
|
||||
|
||||
test("isSafeFilename rejects path traversal", () => {
|
||||
assert.equal(isSafeFilename("../secrets.md"), false);
|
||||
assert.equal(isSafeFilename("a/../../b.md"), false);
|
||||
});
|
||||
|
||||
test("isSafeFilename rejects empty and non-string input", () => {
|
||||
assert.equal(isSafeFilename(""), false);
|
||||
assert.equal(isSafeFilename(null), false);
|
||||
assert.equal(isSafeFilename(undefined), false);
|
||||
assert.equal(isSafeFilename(42), false);
|
||||
});
|
||||
"use strict";
|
||||
const test = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const { isSafeFilename } = require("../main.js").__test__;
|
||||
|
||||
test("isSafeFilename accepts every filename currently in the vault", () => {
|
||||
const real = [
|
||||
"Agentic Engineering, explained by a 10x developer.md",
|
||||
"Webinar Plan - From Chat Box to Your Own OS.md",
|
||||
"Webinar script.md",
|
||||
"You're reading way too much code.md",
|
||||
"ИИ глупый!.md",
|
||||
"Скиллы на базе git — новая память AI-агентов.md",
|
||||
"sebastian interview - conclusions and insights.md",
|
||||
"In 1 Year, the Gap Between AI Users and Everyone Else Will Be Irreversible.md",
|
||||
];
|
||||
for (const name of real) {
|
||||
assert.equal(isSafeFilename(name), true, `should accept: ${name}`);
|
||||
}
|
||||
});
|
||||
|
||||
test("isSafeFilename rejects shell metacharacters", () => {
|
||||
for (const bad of ['a".md', "a`b.md", "a$b.md", "a&b.md", "a|b.md", "a;b.md",
|
||||
"a<b.md", "a>b.md", "a%b.md", "a\nb.md", "a\rb.md"]) {
|
||||
assert.equal(isSafeFilename(bad), false, `should reject: ${JSON.stringify(bad)}`);
|
||||
}
|
||||
});
|
||||
|
||||
test("isSafeFilename rejects the cmd.exe escape character and control characters", () => {
|
||||
// `^` escapes the next character in cmd.exe, so it can defuse the closing
|
||||
// quote. NUL additionally makes spawn() throw synchronously.
|
||||
for (const bad of ["a^b.md", "a\u0000b.md", "a\u001bb.md", "a\u007fb.md"]) {
|
||||
assert.equal(isSafeFilename(bad), false, `should reject: ${JSON.stringify(bad)}`);
|
||||
}
|
||||
});
|
||||
|
||||
test("isSafeFilename rejects path traversal", () => {
|
||||
assert.equal(isSafeFilename("../secrets.md"), false);
|
||||
assert.equal(isSafeFilename("a/../../b.md"), false);
|
||||
});
|
||||
|
||||
test("isSafeFilename rejects empty and non-string input", () => {
|
||||
assert.equal(isSafeFilename(""), false);
|
||||
assert.equal(isSafeFilename(null), false);
|
||||
assert.equal(isSafeFilename(undefined), false);
|
||||
assert.equal(isSafeFilename(42), false);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user