import test from "node:test"; import nodePath from "node:path"; import { strict as assert } from "node:assert/strict"; import { TaskTarget, cd, glob as taskGlob, read, cmd, assignMeta, verify, } from "../data-export/task.ts"; const THIS_FILE = import.meta.dirname; const FIXTURE_DIR = nodePath.join(THIS_FILE, 'fixtures/facebook-json-2021-05-01'); const FIXTURE_FILE = nodePath.join(FIXTURE_DIR, 'friends/friends.json'); // -- TaskTarget --------------------------------------------------------------- test("TaskTarget: constructor initializes path, pipeline", () => { const t = new TaskTarget("/foo/bar"); assert.equal(t.path, "/foo/bar"); assert.deepEqual(t.pipeline, []); }); test("TaskTarget: exists() returns true for a real file", () => { assert.equal(new TaskTarget(FIXTURE_FILE).exists(), true); }); test("TaskTarget: exists() returns false for a missing file", () => { assert.equal(new TaskTarget("/nonexistent-file-xyz").exists(), false); }); test("TaskTarget: basename safe-ifies the path basename", () => { const t = new TaskTarget("/foo/bar/some-file.txt"); assert.equal(t.basename, "some_file_txt"); }); test("TaskTarget: basenameN returns last n path segments joined with ___", () => { const t = new TaskTarget("/a/b/c/d"); assert.equal(t.basenameN(2), "c___d"); assert.equal(t.basenameN(1), "d"); }); test("TaskTarget: id throws when no idValue is set", () => { assert.throws(() => new TaskTarget("/foo").id, /must have an id/); }); test("TaskTarget: id with a string value is safe-ified", () => { const t = new TaskTarget("/foo").assignMeta({ idValue: "my-id" }); assert.equal(t.id, "my_id"); }); test("TaskTarget: id with a function value is resolved against the target", () => { const t = new TaskTarget("/foo/bar").assignMeta({ idValue: tgt => tgt.basename }); assert.equal(t.id, "bar"); }); test("TaskTarget: cd with an absolute path replaces the path", () => { const t = new TaskTarget("/foo"); t.cd("/bar/baz"); assert.equal(t.path, "/bar/baz"); }); test("TaskTarget: cd with a relative path joins with the current path", () => { const t = new TaskTarget("/foo"); t.cd("bar"); assert.equal(t.path, "/foo/bar"); }); test("TaskTarget: read adds a read op to the pipeline", () => { const t = new TaskTarget("/foo/bar.txt"); t.read(); assert.equal(t.pipeline.length, 1); assert.equal(t.pipeline[0].type, "read"); }); test("TaskTarget: cmd adds a mid op to the pipeline", () => { const t = new TaskTarget("/foo"); t.cmd("jq ."); assert.equal(t.pipeline.length, 1); assert.equal(t.pipeline[0].type, "mid"); }); test("TaskTarget: pushToPipeline throws if read is not the first op", () => { const t = new TaskTarget("/foo"); t.cmd("jq ."); assert.throws(() => t.read(), /first item/); }); test("TaskTarget: clone produces an independent copy", () => { const t = new TaskTarget("/foo").assignMeta({ idValue: "orig", columnMeta: ["any"] }); t.read(); const c = t.clone(); assert.equal(c.path, "/foo"); assert.equal(c.id, "orig"); assert(c.pipeline !== t.pipeline); // Different object references assert.equal(c.pipeline.length, 1); assert(c.columnMeta !== t.columnMeta); // Different object references c.path = "/other"; assert.equal(t.path, "/foo"); // original unchanged }); test("TaskTarget: glob returns matching TaskTargets from disk", () => { const t = new TaskTarget(FIXTURE_DIR); const results = t.glob("friends/*.json"); assert.ok(results.length > 0); assert.ok(results.every(r => r instanceof TaskTarget)); assert.ok(results.every(r => r.path.endsWith(".json"))); }); // -- toShell / shEscape ------------------------------------------------------- test("toShell: a single read produces a cat command", () => { const t = new TaskTarget("/foo/bar.txt"); t.read(); assert.equal(t.toShell(), "cat /foo/bar.txt"); }); test("toShell: read piped into cmd", () => { const t = new TaskTarget("/foo/bar.txt"); t.read(); t.cmd("jq ."); assert.equal(t.toShell(), "cat /foo/bar.txt | jq ."); }); for (const c of " $!&".split("")) { test(`toShell: quotes paths that contain ${JSON.stringify(c)}`, () => { const t = new TaskTarget(`/foo/bar${c}baz.txt`); t.read(); assert.equal(t.toShell(), `cat $'/foo/bar${c}baz.txt'`); }); } test(`toShell: quotes and escapes paths that contain '`, () => { const t = new TaskTarget(`/foo/bar'baz.txt`); t.read(); assert.equal(t.toShell(), `cat $'/foo/bar\\'baz.txt'`); }); test("toShell: cmd with array splits tokens", () => { const t = new TaskTarget("/foo"); t.cmd(["jq", "."]); assert.equal(t.toShell(), "jq ."); }); test("toShell: cmd with function resolves at shell-generation time", () => { const t = new TaskTarget("/foo/bar.json"); t.cmd(tgt => `jq -r .name ${tgt.path}`); assert.equal(t.toShell(), "jq -r .name /foo/bar.json"); }); // -- module-level functions --------------------------------------------------- test("cd: clones and changes directory of each target", async () => { const targets = [new TaskTarget("/a"), new TaskTarget("/b")]; const result = await cd("sub")(targets); assert.equal(result[0].path, "/a/sub"); assert.equal(result[1].path, "/b/sub"); assert.equal(targets[0].path, "/a"); // originals unchanged }); test("read: clones and adds a read op to each target", async () => { const targets = [new TaskTarget("/a.txt"), new TaskTarget("/b.txt")]; const result = await read()(targets); assert.equal(result[0].pipeline[0].type, "read"); assert.equal(result[1].pipeline[0].type, "read"); assert.equal(targets[0].pipeline.length, 0); // originals unchanged }); test("cmd: clones and appends a cmd op to each target", async () => { const targets = [new TaskTarget("/a.txt")]; targets[0].read(); const result = await cmd("jq .")(targets); assert.equal(result[0].pipeline.length, 2); assert.equal(targets[0].pipeline.length, 1); // original unchanged }); test("assignMeta: clones and sets meta on each target", async () => { const targets = [new TaskTarget("/a"), new TaskTarget("/b")]; const result = await assignMeta({ idValue: "myid" })(targets); assert.equal(result[0].id, "myid"); assert.equal(result[1].id, "myid"); assert.throws(() => targets[0].id); // originals have no id }); test("taskGlob: returns matching targets across all input targets", async () => { const targets = [new TaskTarget(FIXTURE_DIR)]; const result = await taskGlob("friends/*.json")(targets); assert.ok(result.length > 0); assert.ok(result.every(r => r.path.endsWith(".json"))); }); // -- verify ------------------------------------------------------------------- test("verify: removes targets with an empty pipeline", async () => { const t = new TaskTarget(FIXTURE_FILE); const result = await verify([t]); assert.equal(result.length, 0); }); test("verify: removes targets whose file does not exist", async () => { const t = new TaskTarget("/nonexistent-file-xyz"); t.read(); const result = await verify([t]); assert.equal(result.length, 0); }); test("verify: keeps targets that exist and have a pipeline", async () => { const t = new TaskTarget(FIXTURE_FILE); t.read(); const result = await verify([t]); assert.equal(result.length, 1); assert.equal(result[0].path, FIXTURE_FILE); }); test("verify: filters a mixed list to only valid targets", async () => { const good = new TaskTarget(FIXTURE_FILE); good.read(); const noPipeline = new TaskTarget(FIXTURE_FILE); const noFile = new TaskTarget("/nonexistent-xyz"); noFile.read(); const result = await verify([good, noPipeline, noFile]); assert.equal(result.length, 1); assert.equal(result[0], good); });