Refreshing rebuilt the whole tree, and TreeView.reload() with no argument drops the scroll position. Watching a job meant the view jumped every fifteen seconds. The provider now reconciles instead of replacing. update() rebuilds from the store, matches rows to the existing nodes by identifier, copies the rendered fields onto them, and reports the topmost rows that actually changed; main.js reloads just those. Node identity survives the merge, which is what makes a targeted reload possible. An open row that fetches its own children has that cache dropped so it refetches — the running-job path. A job finishing now reloads one run's row and nothing else. Sections also each describe what they draw, so a section only rebuilds when its own content moved: run activity no longer disturbs the pull request list, and an idle tree is left alone entirely. This does not slow anything down — a section showing a run in progress still updates on every poll, and contributes a time bucket so its elapsed clock keeps moving. A row set changing at the root still needs a whole-tree reload; there the selected row is re-revealed afterwards, which is the closest thing to a scroll anchor Nova exposes. Adds seventeen checks: no-op updates reporting nothing, a finishing job naming only its own row, node identity surviving, a new run reloading just its workflow group, and a new workflow escalating to a full reload. 168 checks total. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MQuusXgZC2dzwpJJ1qhtti
485 lines
21 KiB
JavaScript
485 lines
21 KiB
JavaScript
//
|
|
// End-to-end: activates the real extension against a real git checkout and a
|
|
// canned Gitea instance, then renders every sidebar section and drives the
|
|
// commands that touch the filesystem or the API.
|
|
//
|
|
|
|
const { EXTENSION_ROOT } = require("./nova-stub.js");
|
|
const fake = require("./fake-gitea.js");
|
|
const { Checker } = require("./assert.js");
|
|
|
|
const path = require("node:path");
|
|
const fs = require("node:fs");
|
|
const os = require("node:os");
|
|
const { execFileSync } = require("node:child_process");
|
|
|
|
const script = (...parts) => path.join(EXTENSION_ROOT, "Scripts", ...parts);
|
|
const t = new Checker();
|
|
|
|
// -- fixture ---------------------------------------------------------------
|
|
|
|
const WORKSPACE = fs.mkdtempSync(path.join(os.tmpdir(), "gitea-nova-test-"));
|
|
const REPO = path.join(WORKSPACE, "widget");
|
|
|
|
function buildRepository() {
|
|
fs.mkdirSync(path.join(REPO, "src"), { recursive: true });
|
|
fs.writeFileSync(
|
|
path.join(REPO, "src", "login.js"),
|
|
Array.from({ length: 20 }, (_, i) => `// line ${i + 1}`).join("\n") + "\n",
|
|
);
|
|
fs.writeFileSync(path.join(REPO, "README.md"), "# widget\n");
|
|
|
|
const git = (...args) => execFileSync("git", args, { cwd: REPO, encoding: "utf-8" });
|
|
git("init", "-q", "-b", "main");
|
|
git("config", "user.email", "[email protected]");
|
|
git("config", "user.name", "Test");
|
|
git("remote", "add", "origin", "[email protected]:acme/widget.git");
|
|
git("add", "-A");
|
|
git("commit", "-qm", "initial");
|
|
git("checkout", "-qb", "feature/login");
|
|
}
|
|
|
|
buildRepository();
|
|
|
|
nova.workspace.path = WORKSPACE;
|
|
nova.config.set("gitea.baseUrl", "https://gitea.test");
|
|
nova.credentials.setPassword("unsupervised.giteanova", "https://gitea.test", "tok_test");
|
|
|
|
// -- helpers ---------------------------------------------------------------
|
|
|
|
/** Renders a provider's tree to indented text, expanding lazy rows as it goes. */
|
|
async function render(provider, node = null, depth = 0, lines = []) {
|
|
for (const child of await provider.getChildren(node)) {
|
|
const item = provider.getTreeItem(child);
|
|
const description = item.descriptiveText ? ` [${item.descriptiveText}]` : "";
|
|
lines.push(`${" ".repeat(depth)}${item.name}${description}`);
|
|
if (depth < 3 && (child.children || child.load)) {
|
|
await render(provider, child, depth + 1, lines);
|
|
}
|
|
}
|
|
return lines;
|
|
}
|
|
|
|
/** Every command name the manifest exposes to the user. */
|
|
function declaredCommands(manifest) {
|
|
const declared = new Set();
|
|
|
|
for (const list of Object.values(manifest.commands || {})) {
|
|
for (const entry of list) if (entry.command) declared.add(entry.command);
|
|
}
|
|
for (const sidebar of manifest.sidebars || []) {
|
|
for (const section of sidebar.sections || []) {
|
|
const entries = [...(section.headerCommands || []), ...(section.contextCommands || [])];
|
|
for (const entry of entries) if (entry.command) declared.add(entry.command);
|
|
}
|
|
}
|
|
const walk = (entry) => {
|
|
if (entry.command) declared.add(entry.command);
|
|
for (const child of entry.children || []) walk(child);
|
|
};
|
|
for (const entry of manifest.config || []) walk(entry);
|
|
|
|
return declared;
|
|
}
|
|
|
|
// -- suite -----------------------------------------------------------------
|
|
|
|
(async () => {
|
|
const main = require(script("main.js"));
|
|
main.activate();
|
|
await new Promise((resolve) => setTimeout(resolve, 400)); // let activation settle
|
|
|
|
const store = require(script("controllers", "selection.js")).store();
|
|
const views = Object.fromEntries(global.__treeViews.map((view) => [view.id, view]));
|
|
|
|
t.section("discovery");
|
|
console.log(
|
|
store.repos
|
|
.map((r) => `${r.fullName} @ ${r.baseUrl} branch=${r.branch}`)
|
|
.join("\n") || "(none)",
|
|
);
|
|
t.check("one repository discovered", store.repos.length === 1, `got ${store.repos.length}`);
|
|
t.check("routed to the matching instance", store.repos[0]?.baseUrl === "https://gitea.test");
|
|
t.check("branch read from git", store.repos[0]?.branch === "feature/login", store.repos[0]?.branch);
|
|
t.check(
|
|
"instance reported healthy",
|
|
store.instanceStatus.get("https://gitea.test")?.ok === true,
|
|
JSON.stringify(store.instanceStatus.get("https://gitea.test")),
|
|
);
|
|
|
|
for (const id of ["gitea.branch", "gitea.workflows", "gitea.pullRequests", "gitea.settings"]) {
|
|
t.section(id);
|
|
const lines = await render(views[id].dataProvider);
|
|
console.log(lines.join("\n"));
|
|
t.check(`${id} renders rows`, lines.length > 0);
|
|
t.check(
|
|
`${id} renders no error rows`,
|
|
!lines.some((line) => line.includes("⚠")),
|
|
lines.filter((line) => line.includes("⚠")).join(" | "),
|
|
);
|
|
}
|
|
|
|
t.section("branch filtering");
|
|
const branchLines = await render(views["gitea.branch"].dataProvider);
|
|
t.check("shows the branch's pull request", branchLines.some((l) => l.includes("#7 Add login form")));
|
|
t.check("hides another branch's pull request", !branchLines.some((l) => l.includes("#6 Bump deps")));
|
|
t.check("hides another branch's run", !branchLines.some((l) => l.includes("Nightly")));
|
|
|
|
t.section("workflow grouping");
|
|
const workflowLines = await render(views["gitea.workflows"].dataProvider);
|
|
t.check(
|
|
"grouped by workflow file",
|
|
workflowLines.some((l) => l.trim().startsWith("ci.yml")) &&
|
|
workflowLines.some((l) => l.trim().startsWith("nightly.yml")),
|
|
);
|
|
t.check("jobs load under a run", workflowLines.some((l) => l.includes("build")));
|
|
t.check("steps load under a job", workflowLines.some((l) => l.includes("Checkout")));
|
|
t.check("artifacts listed under a run", workflowLines.some((l) => l.includes("coverage")));
|
|
|
|
t.section("inline review comments");
|
|
const { ReviewCommentsController } = require(script("controllers", "reviewComments.js"));
|
|
const comments = new ReviewCommentsController(store);
|
|
await comments.reload({ force: true });
|
|
|
|
const annotated = [...comments.collection.map.entries()];
|
|
for (const [uri, issues] of annotated) {
|
|
console.log(uri);
|
|
for (const issue of issues) console.log(` line ${issue.line} [${issue.source}] ${issue.message}`);
|
|
}
|
|
t.check("exactly one file annotated", annotated.length === 1, `got ${annotated.length}`);
|
|
t.check("annotation lands on the commented file", annotated[0]?.[0].endsWith("/src/login.js"));
|
|
// The hunk adds three lines after one context line starting at new line 10,
|
|
// so the commented line — the hunk's last — is line 14.
|
|
t.check("line derived from the diff hunk", annotated[0]?.[1][0].line === 14, annotated[0]?.[1][0].line);
|
|
t.check("request-changes comment raised as an error", annotated[0]?.[1][0].severity === IssueSeverity.Error);
|
|
comments.dispose();
|
|
|
|
t.section("manifest and command registration");
|
|
const manifest = JSON.parse(fs.readFileSync(path.join(EXTENSION_ROOT, "extension.json"), "utf-8"));
|
|
const declared = declaredCommands(manifest);
|
|
const registered = global.__commands;
|
|
|
|
const missing = [...declared].filter((name) => !registered.has(name));
|
|
t.check("every command the manifest offers is registered", missing.length === 0, missing.join(", "));
|
|
|
|
// gitea.openReviewComment is deliberately absent: it is a TreeItem.command
|
|
// for double-click, never a menu entry.
|
|
const extra = [...registered.keys()].filter((name) => !declared.has(name));
|
|
t.equals("only the double-click handler is manifest-free", extra, ["gitea.openReviewComment"]);
|
|
|
|
t.section("command behaviour");
|
|
const branch = views["gitea.branch"];
|
|
const roots = await branch.dataProvider.getChildren(null);
|
|
const pullFolder = roots.find((n) => n.kind === "prFolder");
|
|
const pullNode = (await branch.dataProvider.getChildren(pullFolder)).find((n) => n.kind === "pr");
|
|
branch.selection = [pullNode];
|
|
|
|
await registered.get("gitea.copyUrl")(branch);
|
|
t.check(
|
|
"copy URL uses the pull request's own page",
|
|
global.__clipboard === "https://gitea.test/acme/widget/pulls/7",
|
|
global.__clipboard,
|
|
);
|
|
|
|
await registered.get("gitea.openPullRequestDiff")(branch);
|
|
t.check("diff saved into the repository", fs.existsSync(path.join(REPO, ".tmp", "gitea-diffs", "pr-7.diff")));
|
|
|
|
await registered.get("gitea.openPullRequestOverview")(branch);
|
|
const overview = global.__opened?.untitled?.content ?? "";
|
|
t.check("overview headed with the pull request title", overview.includes("# #7 Add login form"));
|
|
t.check("overview lists reviews", overview.includes("alice") && overview.includes("request_changes"));
|
|
t.check("overview lists changed files", overview.includes("src/login.js"));
|
|
|
|
const runsFolder = roots.find((n) => n.kind === "runsFolder");
|
|
const runNode = (await branch.dataProvider.getChildren(runsFolder)).find((n) => n.kind === "run");
|
|
const jobNode = (await branch.dataProvider.getChildren(runNode)).find((n) => n.kind === "job");
|
|
branch.selection = [jobNode];
|
|
await registered.get("gitea.viewJobLogs")(branch);
|
|
|
|
const logsDir = path.join(REPO, ".tmp", "gitea-logs");
|
|
const logFiles = fs.existsSync(logsDir) ? fs.readdirSync(logsDir) : [];
|
|
t.check("job log written into the repository", logFiles.length === 1, logFiles.join(", "));
|
|
|
|
const workflows = views["gitea.workflows"];
|
|
const ciGroup = (await workflows.dataProvider.getChildren(null)).find(
|
|
(n) => n.kind === "workflow" && n.name === "ci.yml",
|
|
);
|
|
const finishedRun = (await workflows.dataProvider.getChildren(ciGroup)).find(
|
|
(n) => n.data.run.id === 101,
|
|
);
|
|
const artifactsFolder = (await workflows.dataProvider.getChildren(finishedRun)).find(
|
|
(n) => n.kind === "artifactsFolder",
|
|
);
|
|
workflows.selection = [(await workflows.dataProvider.getChildren(artifactsFolder))[0]];
|
|
await registered.get("gitea.downloadArtifact")(workflows);
|
|
|
|
const zip = path.join(REPO, ".tmp", "gitea-artifacts", "acme", "widget", "101", "coverage.zip");
|
|
t.check("artifact downloaded to the configured path", fs.existsSync(zip), zip);
|
|
|
|
t.section("secrets and variables");
|
|
const settingsLines = await render(views["gitea.settings"].dataProvider);
|
|
console.log(settingsLines.join("\n"));
|
|
t.check("secret listed", settingsLines.some((l) => l.includes("DEPLOY_KEY")));
|
|
t.check("variable listed", settingsLines.some((l) => l.includes("REGISTRY")));
|
|
|
|
t.section("write operations");
|
|
global.__messages.length = 0;
|
|
workflows.selection = [finishedRun];
|
|
await registered.get("gitea.rerunRun")(workflows);
|
|
t.check(
|
|
"re-run reported as requested",
|
|
global.__messages.some(([kind, message]) => kind === "info" && message.includes("Re-run")),
|
|
JSON.stringify(global.__messages),
|
|
);
|
|
|
|
global.__messages.length = 0;
|
|
global.__inputAnswer = "Looks good to me";
|
|
branch.selection = [pullNode];
|
|
await registered.get("gitea.reviewApprove")(branch);
|
|
t.check(
|
|
"approval submitted",
|
|
global.__messages.some(([kind, message]) => kind === "info" && message.includes("Review submitted")),
|
|
JSON.stringify(global.__messages),
|
|
);
|
|
|
|
t.section("the sidebar keeps its shape across refreshes");
|
|
// A poll that rebuilds every row used to close whatever the user had open.
|
|
const workflowsView = views["gitea.workflows"];
|
|
const workflowsProvider = workflowsView.dataProvider;
|
|
|
|
const group = (await workflowsProvider.getChildren(null)).find(
|
|
(n) => n.kind === "workflow" && n.name === "ci.yml",
|
|
);
|
|
const openMe = (await workflowsProvider.getChildren(group)).find((n) => n.data.run.id === 101);
|
|
|
|
t.equals(
|
|
"a run starts collapsed",
|
|
workflowsProvider.getTreeItem(openMe).collapsibleState,
|
|
TreeItemCollapsibleState.Collapsed,
|
|
);
|
|
|
|
workflowsView.expand(group);
|
|
workflowsView.expand(openMe);
|
|
|
|
// What a refresh does: throw the built tree away and rebuild it.
|
|
workflowsProvider.invalidate();
|
|
const rebuiltGroup = (await workflowsProvider.getChildren(null)).find(
|
|
(n) => n.kind === "workflow" && n.name === "ci.yml",
|
|
);
|
|
const rebuiltRun = (await workflowsProvider.getChildren(rebuiltGroup)).find(
|
|
(n) => n.data.run.id === 101,
|
|
);
|
|
|
|
t.check("the rebuilt run is a different object", rebuiltRun !== openMe);
|
|
t.equals("but carries the same identifier", rebuiltRun.identifier, openMe.identifier);
|
|
t.equals(
|
|
"and is still open",
|
|
workflowsProvider.getTreeItem(rebuiltRun).collapsibleState,
|
|
TreeItemCollapsibleState.Expanded,
|
|
);
|
|
t.equals(
|
|
"its parent too",
|
|
workflowsProvider.getTreeItem(rebuiltGroup).collapsibleState,
|
|
TreeItemCollapsibleState.Expanded,
|
|
);
|
|
|
|
workflowsView.collapse(rebuiltRun);
|
|
workflowsProvider.invalidate();
|
|
const closedAgain = (await workflowsProvider.getChildren(
|
|
(await workflowsProvider.getChildren(null)).find((n) => n.name === "ci.yml"),
|
|
)).find((n) => n.data.run.id === 101);
|
|
t.equals(
|
|
"closing it sticks too",
|
|
workflowsProvider.getTreeItem(closedAgain).collapsibleState,
|
|
TreeItemCollapsibleState.Collapsed,
|
|
);
|
|
|
|
// A section that opens by default must still be closable.
|
|
const branchProvider2 = views["gitea.branch"].dataProvider;
|
|
const folder = (await branchProvider2.getChildren(null)).find((n) => n.kind === "prFolder");
|
|
t.equals(
|
|
"a default-open folder starts open",
|
|
branchProvider2.getTreeItem(folder).collapsibleState,
|
|
TreeItemCollapsibleState.Expanded,
|
|
);
|
|
views["gitea.branch"].collapse(folder);
|
|
branchProvider2.invalidate();
|
|
const folderAgain = (await branchProvider2.getChildren(null)).find((n) => n.kind === "prFolder");
|
|
t.equals(
|
|
"and stays closed once the user closes it",
|
|
branchProvider2.getTreeItem(folderAgain).collapsibleState,
|
|
TreeItemCollapsibleState.Collapsed,
|
|
);
|
|
|
|
t.section("idle polls do not redraw");
|
|
const providers = Object.fromEntries(
|
|
Object.entries(views).map(([id, view]) => [id, view.dataProvider]),
|
|
);
|
|
const snapshot = () =>
|
|
Object.fromEntries(Object.entries(providers).map(([id, p]) => [id, p.signature()]));
|
|
|
|
const before = snapshot();
|
|
t.check(
|
|
"an unchanged store leaves every section's signature alone",
|
|
Object.entries(snapshot()).every(([id, value]) => value === before[id]),
|
|
);
|
|
|
|
// Run 102 is deliberately still in progress in the fixture.
|
|
t.check(
|
|
"a live run keeps an elapsed clock in the signature",
|
|
providers["gitea.workflows"].signature().includes("tick:"),
|
|
);
|
|
t.check(
|
|
"a section with no live run has none",
|
|
!providers["gitea.pullRequests"].signature().includes("tick:"),
|
|
);
|
|
|
|
const runs = store.runsFor(store.repos[0].key).runs;
|
|
const finished = runs.find((run) => run.id === 103);
|
|
const wasConcluded = finished.conclusion;
|
|
finished.conclusion = "success";
|
|
t.check("a run's conclusion changes the workflows section",
|
|
providers["gitea.workflows"].signature() !== before["gitea.workflows"]);
|
|
t.equals("but leaves the pull requests section alone",
|
|
providers["gitea.pullRequests"].signature(), before["gitea.pullRequests"]);
|
|
t.equals("and the settings section alone",
|
|
providers["gitea.settings"].signature(), before["gitea.settings"]);
|
|
finished.conclusion = wasConcluded;
|
|
|
|
const pulls = store.pullRequestsFor(store.repos[0].key).items;
|
|
const wasUpdated = pulls[0].updated_at;
|
|
pulls[0].updated_at = new Date().toISOString();
|
|
t.check("a pull request update changes the pull requests section",
|
|
providers["gitea.pullRequests"].signature() !== before["gitea.pullRequests"]);
|
|
t.equals("but leaves the workflows section alone",
|
|
providers["gitea.workflows"].signature(), before["gitea.workflows"]);
|
|
pulls[0].updated_at = wasUpdated;
|
|
|
|
t.check(
|
|
"restoring the data restores every signature",
|
|
Object.entries(snapshot()).every(([id, value]) => value === before[id]),
|
|
);
|
|
|
|
nova.workspace.config.set("gitea.branchFilter", "all");
|
|
t.check("changing the branch filter redraws the branch section",
|
|
providers["gitea.branch"].signature() !== before["gitea.branch"]);
|
|
t.equals("without touching the workflows section",
|
|
providers["gitea.workflows"].signature(), before["gitea.workflows"]);
|
|
nova.workspace.config.set("gitea.branchFilter", "current");
|
|
|
|
// A run still in progress must keep its elapsed clock moving.
|
|
const live = runs.find((run) => run.id === 102);
|
|
t.equals("because the fixture leaves one running", live.status, "running");
|
|
|
|
t.section("only the rows that changed are reloaded");
|
|
// Watching a job means the tree updates constantly. Rebuilding all of it
|
|
// would drop the scroll position, so only the affected rows are reloaded.
|
|
const wfView = views["gitea.workflows"];
|
|
const wf = wfView.dataProvider;
|
|
|
|
await wf.update();
|
|
t.equals("a second update with no changes reports nothing", await wf.update(), null);
|
|
|
|
const wfGroups = await wf.getChildren(null);
|
|
const ciGroupNode = wfGroups.find((n) => n.name === "ci.yml");
|
|
wfView.expand(ciGroupNode);
|
|
const ciRuns = await wf.getChildren(ciGroupNode);
|
|
const liveRun = ciRuns.find((n) => n.data.run.id === 102);
|
|
const idleRun = ciRuns.find((n) => n.data.run.id === 101);
|
|
|
|
// Open the running row, as someone monitoring a job would.
|
|
wfView.expand(liveRun);
|
|
await wf.getChildren(liveRun);
|
|
t.check("the open run has cached children", Array.isArray(liveRun.children));
|
|
|
|
// The job finishes.
|
|
const liveJobs = store.jobsFor(store.repos[0].key, 102);
|
|
liveJobs[0].status = "completed";
|
|
liveJobs[0].conclusion = "success";
|
|
runs.find((run) => run.id === 102).status = "completed";
|
|
runs.find((run) => run.id === 102).conclusion = "success";
|
|
|
|
const targeted = await wf.update();
|
|
t.check("an update is reported", Boolean(targeted));
|
|
t.equals("without falling back to a full reload", targeted?.full, false);
|
|
t.check(
|
|
"and it names the run that changed",
|
|
targeted?.nodes.includes(liveRun),
|
|
targeted?.nodes.map((n) => n.identifier).join(", "),
|
|
);
|
|
t.check(
|
|
"not the untouched sibling",
|
|
!targeted?.nodes.includes(idleRun),
|
|
targeted?.nodes.map((n) => n.identifier).join(", "),
|
|
);
|
|
t.check("the open row's children are dropped so they refetch", liveRun.children === null);
|
|
|
|
// Node identity has to survive, or Nova cannot match the reload to a row.
|
|
const groupsAfter = await wf.getChildren(null);
|
|
t.check("group objects are reused", groupsAfter.includes(ciGroupNode));
|
|
t.check("run objects are reused", (await wf.getChildren(ciGroupNode)).includes(liveRun));
|
|
t.check("the row's label was updated in place", liveRun.name.includes("✓"), liveRun.name);
|
|
|
|
// A new run changes its workflow group's row set, so that group is
|
|
// reloaded — still not the whole tree.
|
|
const newRun = (id, workflow) => ({
|
|
id, run_number: id, display_title: "New push", path: `.gitea/workflows/${workflow}`,
|
|
event: "push", status: "queued", conclusion: "", head_branch: "feature/login",
|
|
created_at: new Date().toISOString(), started_at: new Date().toISOString(),
|
|
});
|
|
|
|
runs.unshift(newRun(104, "ci.yml"));
|
|
const added = await wf.update();
|
|
t.equals("a new run does not force a full reload", added?.full, false);
|
|
t.check(
|
|
"it reloads the workflow group it belongs to",
|
|
added?.nodes.includes(ciGroupNode),
|
|
added?.nodes.map((n) => n.identifier).join(", "),
|
|
);
|
|
runs.shift();
|
|
await wf.update();
|
|
|
|
// A whole new workflow adds a top-level row, which the row-by-row merge
|
|
// cannot patch.
|
|
runs.unshift(newRun(105, "release.yml"));
|
|
const reshaped = await wf.update();
|
|
t.equals("a new workflow forces a full reload", reshaped?.full, true);
|
|
runs.shift();
|
|
await wf.update();
|
|
|
|
t.section("the scroll anchor survives a reload");
|
|
// Nova cannot report scroll position, so the selected row is the anchor.
|
|
const anchorView = views["gitea.workflows"];
|
|
const anchorProvider = anchorView.dataProvider;
|
|
const anchorGroup = (await anchorProvider.getChildren(null)).find((n) => n.name === "ci.yml");
|
|
anchorView.expand(anchorGroup);
|
|
const anchorRun = (await anchorProvider.getChildren(anchorGroup)).find(
|
|
(n) => n.data.run.id === 101,
|
|
);
|
|
anchorView.selection = [anchorRun];
|
|
|
|
anchorProvider.invalidate();
|
|
const found = await anchorProvider.findByIdentifier(anchorRun.identifier);
|
|
t.check("the rebuilt row is located again", Boolean(found));
|
|
t.check("as a fresh object", found !== anchorRun);
|
|
t.equals("with the same identity", found?.identifier, anchorRun.identifier);
|
|
|
|
anchorView.selection = [];
|
|
t.equals(
|
|
"a row inside a collapsed branch is not searched for",
|
|
await anchorProvider.findByIdentifier("step-9001-1"),
|
|
null,
|
|
);
|
|
|
|
t.section("api paths exercised");
|
|
console.log([...new Set(fake.calls.map((call) => call.split("?")[0]))].sort().join("\n"));
|
|
|
|
main.deactivate();
|
|
fs.rmSync(WORKSPACE, { recursive: true, force: true });
|
|
t.finish("INTEGRATION CHECKS");
|
|
})().catch((error) => {
|
|
console.error("HARNESS ERROR", error);
|
|
fs.rmSync(WORKSPACE, { recursive: true, force: true });
|
|
process.exit(2);
|
|
});
|