Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/strata/__serial__/core.test.ts

Raw
import { execFile } from "node:child_process";
import {
	chmod,
	copyFile,
	mkdir,
	mkdtemp,
	readFile,
	rm,
	unlink,
	writeFile,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { delimiter, dirname, join } from "node:path";
import { promisify } from "node:util";
import { afterEach, describe, expect, it } from "vitest";
import {
	cleanGitEnvironment,
	collectSnapshot,
	gitForWindowsBinary,
	gitText,
	isProtectedPath,
	MAX_PATCH_BYTES,
	parseArgs,
} from "../git";

const execFileAsync = promisify(execFile);
const roots: string[] = [];

async function git(cwd: string, ...args: string[]): Promise<string> {
	const { stdout } = await execFileAsync("git", args, {
		cwd,
		env: cleanGitEnvironment(),
		encoding: "utf8",
		maxBuffer: 2 * 1024 * 1024,
	});
	return stdout.trim();
}

async function repository(): Promise<string> {
	const root = await mkdtemp(join(tmpdir(), "strata-git-"));
	roots.push(root);
	await git(root, "init", "--quiet");
	await git(root, "config", "user.name", "Strata Test");
	await git(root, "config", "user.email", "strata@example.invalid");
	return root;
}

async function withFakeGit(
	root: string,
	program: string,
	check: () => Promise<void>,
): Promise<void> {
	const bin = join(root, "fake-bin");
	await mkdir(bin);
	const executable = join(
		bin,
		process.platform === "win32" ? "git.exe" : "git",
	);
	const previousOptions = process.env["NODE_OPTIONS"];
	await copyFile(process.execPath, executable);
	await chmod(executable, 0o755);
	const preload = join(bin, "fake-git.cjs");
	await writeFile(
		preload,
		`const original = process.argv.slice(1); original[0] = require("node:path").basename(original[0]); process.argv = [process.execPath, "fake-git", ...(process.execArgv.includes("-c") ? ["-c"] : []), ...original];\n${program}\nprocess.exit(process.exitCode ?? 0);`,
	);
	process.env["NODE_OPTIONS"] = `--require=${JSON.stringify(preload)}`;
	const previous = process.env["PATH"];
	process.env["PATH"] = `${bin}${delimiter}${previous ?? ""}`;
	try {
		await check();
	} finally {
		if (previous === undefined) delete process.env["PATH"];
		else process.env["PATH"] = previous;
		if (previousOptions === undefined) delete process.env["NODE_OPTIONS"];
		else process.env["NODE_OPTIONS"] = previousOptions;
	}
}

async function commitAll(root: string, message: string): Promise<string> {
	await git(root, "add", "--all");
	await git(root, "commit", "--quiet", "-m", message);
	return git(root, "rev-parse", "HEAD");
}

afterEach(async () => {
	await Promise.all(
		roots.splice(0).map((root) => rm(root, { recursive: true, force: true })),
	);
});

describe("parseArgs", () => {
	it("accepts only the documented sources and close", () => {
		expect(parseArgs("  ")).toEqual({ kind: "working" });
		expect(parseArgs("--staged")).toEqual({ kind: "staged" });
		expect(parseArgs("--base trunk")).toEqual({ kind: "base", ref: "trunk" });
		expect(parseArgs("--pr")).toEqual({ kind: "pr" });
		expect(parseArgs("--pr 42")).toEqual({ kind: "pr", identifier: "42" });
		expect(parseArgs("close")).toBe("close");
		expect(() => parseArgs("cancel")).toThrow(/usage/);
		expect(() => parseArgs("--base")).toThrow(/usage/);
		expect(() => parseArgs("--base --output")).toThrow(/not an option/);
		expect(() => parseArgs("--staged extra")).toThrow(/usage/);
	});
});

describe("Git for Windows launcher", () => {
	it.skipIf(process.platform !== "win32").each([
		{ name: "cwd git.com", location: "cwd" },
		{ name: "earlier PATH git.com", location: "earlier" },
		{ name: "same-directory git.com", location: "launcher" },
	])(
		"selects $name before a validated runtime launcher",
		async ({ location }) => {
			const root = await mkdtemp(join(tmpdir(), "strata-git-com-"));
			roots.push(root);
			const cwd = join(root, "work");
			const earlierPath = join(root, "earlier");
			const launcherDirectory = join(root, "installation", "cmd");
			const launcher = join(launcherDirectory, "git.exe");
			const direct = join(root, "installation", "ucrt64", "bin", "git.exe");
			const shadowDirectory =
				location === "cwd"
					? cwd
					: location === "earlier"
						? earlierPath
						: launcherDirectory;
			const shadow = join(shadowDirectory, "git.com");
			const preload = join(root, "git-fixture.cjs");
			await mkdir(cwd);
			await mkdir(earlierPath);
			await mkdir(launcherDirectory, { recursive: true });
			await mkdir(dirname(direct), { recursive: true });
			await copyFile(process.execPath, launcher);
			await copyFile(process.execPath, direct);
			await writeFile(
				preload,
				`const args = process.argv.slice(1); if (args.includes("--exec-path")) process.stdout.write(${JSON.stringify(join(root, "installation", "ucrt64", "libexec", "git-core"))} + "\\n"); else if (args.includes("version")) process.stdout.write("git version fixture\\n"); else process.stdout.write(process.execPath.toLowerCase().endsWith("git.com") ? "git.com fixture\\n" : "direct fixture\\n"); process.exit(0);`,
			);
			const previousPath = process.env["PATH"];
			const previousOptions = process.env["NODE_OPTIONS"];
			const testPath = [earlierPath, launcherDirectory].join(delimiter);
			process.env["PATH"] = testPath;
			process.env["NODE_OPTIONS"] = `--require=${JSON.stringify(preload)}`;
			try {
				expect(await gitText(cwd, [], 1024)).toBe("direct fixture\n");
				await copyFile(process.execPath, shadow);
				expect(process.env["PATH"]).toBe(testPath);
				expect(await gitText(cwd, [], 1024)).toBe("git.com fixture\n");
			} finally {
				if (previousPath === undefined) delete process.env["PATH"];
				else process.env["PATH"] = previousPath;
				if (previousOptions === undefined) delete process.env["NODE_OPTIONS"];
				else process.env["NODE_OPTIONS"] = previousOptions;
			}
		},
	);

	it("bypasses only a validated selected Git-for-Windows cmd launcher", async () => {
		if (process.platform !== "win32") return;
		const root = await mkdtemp(join(tmpdir(), "strata-git-launcher-"));
		roots.push(root);
		const launcher = join(root, "cmd", "git.exe");
		const direct = join(root, "ucrt64", "bin", "git.exe");
		const marker = join(root, "invocations");
		await mkdir(join(root, "cmd"));
		await mkdir(join(root, "ucrt64", "bin"), { recursive: true });
		await copyFile(process.execPath, launcher);
		await copyFile(process.execPath, direct);
		const preload = join(root, "git-fixture.cjs");
		await writeFile(
			preload,
			`const { appendFileSync } = require("node:fs"); const args = process.argv.slice(1); appendFileSync(${JSON.stringify(marker)}, process.execPath + " " + JSON.stringify(args) + "\\n"); if (args.some((arg) => arg === "version" || arg.endsWith("\\\\version"))) process.stdout.write(process.env.STRATA_GIT_MISMATCH && process.execPath === ${JSON.stringify(direct)} ? "git version other\\n" : "git version fixture\\n"); else if (args.includes("--exec-path")) process.stdout.write(${JSON.stringify(join(root, "ucrt64", "libexec", "git-core"))} + "\\n"); process.exit(0);`,
		);
		const previous = process.env["NODE_OPTIONS"];
		const previousMismatch = process.env["STRATA_GIT_MISMATCH"];
		process.env["NODE_OPTIONS"] = `--require=${JSON.stringify(preload)}`;
		try {
			const resolved = await gitForWindowsBinary(launcher);
			expect(resolved).toBe(direct);
			const invocations = (await readFile(marker, "utf8")).split(/\r?\n/u);
			expect(invocations.some((line) => line.startsWith(launcher))).toBe(true);
			expect(invocations.some((line) => line.startsWith(direct))).toBe(true);
			process.env["STRATA_GIT_MISMATCH"] = "1";
			expect(await gitForWindowsBinary(launcher)).toBe("git");
			expect(await gitForWindowsBinary(join(root, "custom", "git.exe"))).toBe(
				"git",
			);
			const controller = new AbortController();
			controller.abort(new Error("cancelled identity probe"));
			await expect(
				gitForWindowsBinary(launcher, controller.signal),
			).rejects.toThrow("cancelled identity probe");
		} finally {
			if (previous === undefined) delete process.env["NODE_OPTIONS"];
			else process.env["NODE_OPTIONS"] = previous;
			if (previousMismatch === undefined)
				delete process.env["STRATA_GIT_MISMATCH"];
			else process.env["STRATA_GIT_MISMATCH"] = previousMismatch;
		}
	});
});

describe("protected path filtering", () => {
	it("classifies pure path strings without creating protected fixtures", () => {
		expect(isProtectedPath("config/.env.production")).toBe(true);
		expect(isProtectedPath("config/.env.production/cache/value.txt")).toBe(
			true,
		);
		expect(isProtectedPath("home/.ssh/id_ed25519")).toBe(true);
		expect(isProtectedPath("infra/secrets.auto.tfvars")).toBe(true);
		expect(isProtectedPath(".config/gh/hosts.yml")).toBe(true);
		expect(isProtectedPath(".pi/google-workspace/tokens/account.json")).toBe(
			true,
		);
		expect(isProtectedPath("gcloud/legacy_credentials/account/token")).toBe(
			true,
		);
		expect(isProtectedPath("pi/agent/auth.json")).toBe(true);
		expect(isProtectedPath("ssh/deploy.key")).toBe(true);
		expect(isProtectedPath("gh/hosts.yml")).toBe(true);
		expect(isProtectedPath("keys/service.pem")).toBe(true);
		expect(isProtectedPath("config/.env.example")).toBe(false);
		expect(isProtectedPath("config/.env.example/schema/value.txt")).toBe(false);
		expect(isProtectedPath("src/environment.ts")).toBe(false);
	});
});

describe("collectSnapshot", () => {
	it("captures text, additions, deletions, renames, metadata, and special paths while omitting binary and untracked files", async () => {
		const root = await repository();
		await Promise.all([
			writeFile(join(root, "modify.txt"), "before\nshared\n"),
			writeFile(join(root, "delete.txt"), "gone\n"),
			writeFile(join(root, "rename-old.txt"), "renamed\n"),
			writeFile(join(root, "script.sh"), "echo strata\n"),
			writeFile(join(root, "space ü.txt"), "old special\n"),
			writeFile(join(root, "-leading.txt"), "old leading\n"),
			writeFile(join(root, "binary.bin"), Buffer.from([0, 1, 2, 3])),
		]);
		await commitAll(root, "initial");

		await writeFile(join(root, "modify.txt"), "after\nshared\n");
		await writeFile(join(root, "added.txt"), "added\n");
		await git(root, "add", "--", "added.txt");
		await unlink(join(root, "delete.txt"));
		await git(root, "mv", "--", "rename-old.txt", "rename new.txt");
		await writeFile(join(root, "space ü.txt"), "new special\n");
		await writeFile(join(root, "-leading.txt"), "new leading\n");
		await writeFile(join(root, "binary.bin"), Buffer.from([0, 9, 8, 7]));
		await writeFile(
			join(root, "scratch.todo"),
			"untracked content is never read\n",
		);

		const snapshot = await collectSnapshot(root, { kind: "working" });
		const paths = new Set(snapshot.hunks.map((hunk) => hunk.path));
		expect(paths).toEqual(
			new Set([
				"-leading.txt",
				"added.txt",
				"binary.bin",
				"delete.txt",
				"modify.txt",
				"rename new.txt",
				"space ü.txt",
			]),
		);
		expect(
			snapshot.hunks.find((hunk) => hunk.path === "rename new.txt"),
		).toMatchObject({
			oldPath: "rename-old.txt",
			header: "metadata",
		});
		const modified = snapshot.hunks.find((hunk) => hunk.path === "modify.txt");
		expect(modified?.lines).toEqual([
			{ kind: "delete", text: "before", oldLine: 1 },
			{ kind: "add", text: "after", newLine: 1 },
			{ kind: "context", text: "shared", oldLine: 2, newLine: 2 },
		]);
		expect(
			snapshot.hunks.find((hunk) => hunk.path === "binary.bin"),
		).toMatchObject({
			header: "metadata",
			lines: [
				expect.objectContaining({
					kind: "meta",
					text: expect.stringMatching(/^Binary files /),
				}),
			],
		});
		expect(snapshot.skipped).toEqual([
			{ path: "binary.bin", reason: "binary" },
			{ path: "scratch.todo", reason: "untracked" },
		]);
		expect(snapshot.base).toMatch(/^[0-9a-f]{40,64}$/);
		expect(snapshot.head).toBe(snapshot.base);
		expect(snapshot.id).toMatch(/^[0-9a-f]{64}$/);
		expect(new Set(snapshot.hunks.map((hunk) => hunk.id)).size).toBe(
			snapshot.hunks.length,
		);

		const repeated = await collectSnapshot(root, { kind: "working" });
		expect(repeated).toEqual(snapshot);
		await writeFile(join(root, "another.todo"), "also omitted\n");
		const changed = await collectSnapshot(root, { kind: "working" });
		expect(changed.id).not.toBe(snapshot.id);
		expect(changed.hunks.map((hunk) => hunk.id)).toEqual(
			snapshot.hunks.map((hunk) => hunk.id),
		);
	});

	it("pins the staged index and excludes unstaged changes", async () => {
		const root = await repository();
		await writeFile(join(root, "staged.txt"), "old staged\n");
		await writeFile(join(root, "unstaged.txt"), "old unstaged\n");
		await writeFile(join(root, "script.sh"), "echo strata\n");
		const commit = await commitAll(root, "initial");
		await writeFile(join(root, "staged.txt"), "new staged\n");
		await git(root, "add", "--", "staged.txt");
		await git(root, "update-index", "--chmod=+x", "script.sh");
		await writeFile(join(root, "unstaged.txt"), "new unstaged\n");

		const snapshot = await collectSnapshot(root, { kind: "staged" });
		expect(snapshot.base).toBe(commit);
		expect(snapshot.head).not.toBe(commit);
		expect(snapshot.hunks.map((hunk) => hunk.path)).toEqual([
			"script.sh",
			"staged.txt",
		]);
		expect(snapshot.hunks[0]).toMatchObject({
			path: "script.sh",
			header: "metadata",
			lines: [
				{ kind: "meta", text: "old mode 100644" },
				{ kind: "meta", text: "new mode 100755" },
			],
		});
		expect(snapshot.skipped).toEqual([]);
	});

	it("preserves mode metadata alongside a textual hunk", async () => {
		const root = await repository();
		await writeFile(join(root, "script.sh"), "old\n");
		await commitAll(root, "initial");
		await writeFile(join(root, "script.sh"), "new\n");
		await git(root, "add", "--", "script.sh");
		await git(root, "update-index", "--chmod=+x", "script.sh");

		const snapshot = await collectSnapshot(root, { kind: "staged" });
		expect(snapshot.hunks).toHaveLength(1);
		expect(snapshot.hunks[0]?.lines).toEqual([
			{ kind: "meta", text: "old mode 100644" },
			{ kind: "meta", text: "new mode 100755" },
			{ kind: "delete", text: "old", oldLine: 1 },
			{ kind: "add", text: "new", newLine: 1 },
		]);
	});

	it("pins a merge-base-to-HEAD range and ignores working tree changes", async () => {
		const root = await repository();
		await writeFile(join(root, "committed.txt"), "first\n");
		const first = await commitAll(root, "first");
		await writeFile(join(root, "committed.txt"), "second\n");
		const head = await commitAll(root, "second");
		await writeFile(join(root, "committed.txt"), "working only\n");

		const snapshot = await collectSnapshot(root, { kind: "base", ref: first });
		expect(snapshot.base).toBe(first);
		expect(snapshot.head).toBe(head);
		expect(snapshot.hunks).toHaveLength(1);
		expect(snapshot.hunks[0]?.lines).toContainEqual({
			kind: "add",
			text: "second",
			newLine: 1,
		});
		expect(snapshot.hunks[0]?.lines).not.toContainEqual(
			expect.objectContaining({ text: "working only" }),
		);
	});

	it("accepts a quoted Git header without requiring an invalid Win32 filename on disk", async () => {
		const root = await repository();
		const path = 'safe "quoted"\tü.txt';
		const patch = `diff --git ${JSON.stringify(`a/${path}`)} ${JSON.stringify(`b/${path}`)}\nindex 1..2 100644\n--- ${JSON.stringify(`a/${path}`)}\n+++ ${JSON.stringify(`b/${path}`)}\n@@ -1 +1 @@\n-old\n+new\n`;
		await withFakeGit(
			root,
			`const args = process.argv.slice(2); const out = value => process.stdout.write(value);
if (args.includes("rev-parse")) out(args.includes("--show-toplevel") ? process.cwd() + "\\n" : "${"a".repeat(40)}\\n");
else if (args[0] === "ls-files") out(args.includes("--others") ? "" : ${JSON.stringify(`${path}\0`)});
else if (args[0] === "ls-tree") out(${JSON.stringify(`${path}\0`)});
else if (args.includes("--name-status")) out(${JSON.stringify(`M\0${path}\0`)});
else if (args.includes("--patch")) out(${JSON.stringify(patch)});`,
			async () => {
				const snapshot = await collectSnapshot(root, { kind: "working" });
				expect(snapshot.hunks[0]).toMatchObject({
					path,
					lines: [
						{ kind: "delete", text: "old", oldLine: 1 },
						{ kind: "add", text: "new", newLine: 1 },
					],
				});
			},
		);
	});

	it("rejects a fake Git patch whose quoted header does not identify its metadata path", async () => {
		const root = await repository();
		await withFakeGit(
			root,
			`const args = process.argv.slice(2);
const out = (value) => process.stdout.write(value);
if (args.includes("rev-parse")) out(args.includes("--show-toplevel") ? process.cwd() + "\\n" : "${"a".repeat(40)}\\n");
else if (args[0] === "ls-files") out(args.includes("--others") ? "" : "safe \\"quoted\\"\\tü.txt\\0");
else if (args[0] === "ls-tree") out("safe \\"quoted\\"\\tü.txt\\0");
else if (args.includes("--name-status")) out("M\\0safe \\"quoted\\"\\tü.txt\\0");
else if (args.includes("--patch")) out("diff --git a/other.txt b/other.txt\\nindex 1..2 100644\\n--- a/other.txt\\n+++ b/other.txt\\n@@ -1 +1 @@\\n-old\\n+new\\n");`,
			async () => {
				await expect(
					collectSnapshot(root, { kind: "working" }),
				).rejects.toThrow(/patch header does not match metadata/);
			},
		);
	});

	it("rejects working snapshots when a fake Git manifest changes after the patch", async () => {
		const root = await repository();
		await withFakeGit(
			root,
			`const { existsSync, writeFileSync } = require("node:fs");
const { join } = require("node:path");
const args = process.argv.slice(2);
const marker = join(process.cwd(), ".strata-race");
const out = (value) => process.stdout.write(value);
if (args.includes("rev-parse")) out(args.includes("--show-toplevel") ? process.cwd() + "\\n" : "${"a".repeat(40)}\\n");
else if (args[0] === "ls-files") out(args.includes("--others") ? (existsSync(marker) ? "later.todo\\0" : "") : "before.txt\\0later.txt\\0");
else if (args[0] === "ls-tree") out("before.txt\\0later.txt\\0");
else if (args.includes("--name-status")) out(existsSync(marker) ? "M\\0later.txt\\0" : "M\\0before.txt\\0");
else if (args.includes("--patch")) { writeFileSync(marker, "changed"); out("diff --git a/before.txt b/before.txt\\nindex 1..2 100644\\n--- a/before.txt\\n+++ b/before.txt\\n@@ -1 +1 @@\\n-old\\n+new\\n"); }`,
			async () => {
				await expect(
					collectSnapshot(root, { kind: "working" }),
				).rejects.toThrow(/working tree changed during snapshot capture/);
			},
		);
	});

	it("excludes protected rename sides so the unprotected side remains reviewable", async () => {
		const root = await repository();
		await withFakeGit(
			root,
			`const args = process.argv.slice(2);
const out = (value) => process.stdout.write(value);
const protectedPath = "pi/agent/auth.json";
if (args.includes("rev-parse")) out(args.includes("--show-toplevel") ? process.cwd() + "\\n" : "${"a".repeat(40)}\\n");
else if (args[0] === "ls-files") out(args.includes("--others") ? "" : "public.txt\\0" + protectedPath + "\\0");
else if (args[0] === "ls-tree") out("public.txt\\0" + protectedPath + "\\0");
else if (args.includes("--name-status")) out("A\\0public.txt\\0M\\0" + protectedPath + "\\0");
else if (args.includes("--patch")) out("diff --git a/public.txt b/public.txt\\nnew file mode 100644\\nindex 0000000..1111111\\n--- /dev/null\\n+++ b/public.txt\\n@@ -0,0 +1 @@\\n+public\\n");`,
			async () => {
				const snapshot = await collectSnapshot(root, { kind: "working" });
				expect(snapshot.hunks.map((hunk) => hunk.path)).toEqual(["public.txt"]);
				expect(snapshot.skipped).toEqual([
					{ path: "pi/agent/auth.json", reason: "protected path" },
				]);
			},
		);
	});

	it("omits both sides of R100 renames touching protected paths", async () => {
		const renames: Array<[from: string, to: string, skipped: string]> = [
			["pi/agent/auth.json", "public.json", "public.json"],
			["public.txt", "pi/agent/auth.json", "pi/agent/auth.json"],
		];
		for (const [from, to, skipped] of renames) {
			const root = await repository();
			await mkdir(dirname(join(root, from)), { recursive: true });
			await writeFile(join(root, from), "unchanged\n");
			await commitAll(root, `add ${from}`);
			await mkdir(dirname(join(root, to)), { recursive: true });
			await git(root, "mv", "--", from, to);
			expect(
				await git(
					root,
					"diff",
					"--name-status",
					"--find-renames=50%",
					"-z",
					"HEAD",
				),
			).toContain(`R100\0${from}\0${to}\0`);

			const snapshot = await collectSnapshot(root, { kind: "working" });
			expect(snapshot.hunks).toEqual([]);
			expect(snapshot.skipped).toEqual([
				{ path: skipped, reason: "protected path" },
			]);
		}
	});

	it("disables external diff commands and text conversion from repository config", async () => {
		const root = await repository();
		await writeFile(
			join(root, ".gitattributes"),
			"external.txt diff=external\nconverted.tc diff=converted\n",
		);
		await writeFile(join(root, "external.txt"), "old external\n");
		await writeFile(join(root, "converted.tc"), "old conversion\n");
		await commitAll(root, "initial");
		await git(
			root,
			"config",
			"diff.external.command",
			"definitely-not-a-command",
		);
		await git(
			root,
			"config",
			"diff.converted.textconv",
			"definitely-not-a-command",
		);
		await writeFile(join(root, "external.txt"), "new external\n");
		await writeFile(join(root, "converted.tc"), "new conversion\n");

		const snapshot = await collectSnapshot(root, { kind: "working" });
		expect(snapshot.hunks.map((hunk) => hunk.path)).toEqual([
			"converted.tc",
			"external.txt",
		]);
	});

	it("ignores ambient Git repository redirection", async () => {
		const root = await repository();
		await writeFile(join(root, "tracked.txt"), "old\n");
		await commitAll(root, "initial");
		await writeFile(join(root, "tracked.txt"), "new\n");
		const previous = process.env["GIT_DIR"];
		process.env["GIT_DIR"] = join(root, "missing-git-dir");
		try {
			const snapshot = await collectSnapshot(root, { kind: "working" });
			expect(snapshot.hunks.map((hunk) => hunk.path)).toEqual(["tracked.txt"]);
		} finally {
			if (previous === undefined) delete process.env["GIT_DIR"];
			else process.env["GIT_DIR"] = previous;
		}
	});

	it("refuses oversized patches instead of truncating", async () => {
		const root = await repository();
		await writeFile(join(root, "large.txt"), "small\n");
		await commitAll(root, "initial");
		await writeFile(
			join(root, "large.txt"),
			"x".repeat(MAX_PATCH_BYTES + 8_192),
		);

		await expect(collectSnapshot(root, { kind: "working" })).rejects.toThrow(
			/byte limit/,
		);
	});

	it("honors cancellation before collection", async () => {
		const controller = new AbortController();
		controller.abort(new Error("cancelled by test"));
		await expect(
			collectSnapshot("unused", { kind: "working" }, controller.signal),
		).rejects.toThrow("cancelled by test");
	});
});