test: deduplicate diffs extension fixtures

This commit is contained in:
Peter Steinberger
2026-03-10 20:22:56 +00:00
parent 23cd997526
commit 0976317f96
5 changed files with 97 additions and 111 deletions

View File

@@ -1,8 +1,8 @@
import fs from "node:fs/promises"; import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path"; import path from "node:path";
import type { OpenClawConfig } from "openclaw/plugin-sdk/diffs"; import type { OpenClawConfig } from "openclaw/plugin-sdk/diffs";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createTempDiffRoot } from "./test-helpers.js";
const { launchMock } = vi.hoisted(() => ({ const { launchMock } = vi.hoisted(() => ({
launchMock: vi.fn(), launchMock: vi.fn(),
@@ -17,10 +17,11 @@ vi.mock("playwright-core", () => ({
describe("PlaywrightDiffScreenshotter", () => { describe("PlaywrightDiffScreenshotter", () => {
let rootDir: string; let rootDir: string;
let outputPath: string; let outputPath: string;
let cleanupRootDir: () => Promise<void>;
beforeEach(async () => { beforeEach(async () => {
vi.useFakeTimers(); vi.useFakeTimers();
rootDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-diffs-browser-")); ({ rootDir, cleanup: cleanupRootDir } = await createTempDiffRoot("openclaw-diffs-browser-"));
outputPath = path.join(rootDir, "preview.png"); outputPath = path.join(rootDir, "preview.png");
launchMock.mockReset(); launchMock.mockReset();
const browserModule = await import("./browser.js"); const browserModule = await import("./browser.js");
@@ -31,7 +32,7 @@ describe("PlaywrightDiffScreenshotter", () => {
const browserModule = await import("./browser.js"); const browserModule = await import("./browser.js");
await browserModule.resetSharedBrowserStateForTests(); await browserModule.resetSharedBrowserStateForTests();
vi.useRealTimers(); vi.useRealTimers();
await fs.rm(rootDir, { recursive: true, force: true }); await cleanupRootDir();
}); });
it("reuses the same browser across renders and closes it after the idle window", async () => { it("reuses the same browser across renders and closes it after the idle window", async () => {

View File

@@ -1,32 +1,24 @@
import fs from "node:fs/promises";
import type { IncomingMessage } from "node:http"; import type { IncomingMessage } from "node:http";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { createMockServerResponse } from "../../../src/test-utils/mock-http-response.js"; import { createMockServerResponse } from "../../../src/test-utils/mock-http-response.js";
import { createDiffsHttpHandler } from "./http.js"; import { createDiffsHttpHandler } from "./http.js";
import { DiffArtifactStore } from "./store.js"; import { DiffArtifactStore } from "./store.js";
import { createDiffStoreHarness } from "./test-helpers.js";
describe("createDiffsHttpHandler", () => { describe("createDiffsHttpHandler", () => {
let rootDir: string;
let store: DiffArtifactStore; let store: DiffArtifactStore;
let cleanupRootDir: () => Promise<void>;
beforeEach(async () => { beforeEach(async () => {
rootDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-diffs-http-")); ({ store, cleanup: cleanupRootDir } = await createDiffStoreHarness("openclaw-diffs-http-"));
store = new DiffArtifactStore({ rootDir });
}); });
afterEach(async () => { afterEach(async () => {
await fs.rm(rootDir, { recursive: true, force: true }); await cleanupRootDir();
}); });
it("serves a stored diff document", async () => { it("serves a stored diff document", async () => {
const artifact = await store.createArtifact({ const artifact = await createViewerArtifact(store);
html: "<html>viewer</html>",
title: "Demo",
inputKind: "before_after",
fileCount: 1,
});
const handler = createDiffsHttpHandler({ store }); const handler = createDiffsHttpHandler({ store });
const res = createMockServerResponse(); const res = createMockServerResponse();
@@ -45,12 +37,7 @@ describe("createDiffsHttpHandler", () => {
}); });
it("rejects invalid tokens", async () => { it("rejects invalid tokens", async () => {
const artifact = await store.createArtifact({ const artifact = await createViewerArtifact(store);
html: "<html>viewer</html>",
title: "Demo",
inputKind: "before_after",
fileCount: 1,
});
const handler = createDiffsHttpHandler({ store }); const handler = createDiffsHttpHandler({ store });
const res = createMockServerResponse(); const res = createMockServerResponse();
@@ -113,96 +100,52 @@ describe("createDiffsHttpHandler", () => {
expect(String(res.body)).toContain("openclawDiffsReady"); expect(String(res.body)).toContain("openclawDiffsReady");
}); });
it("blocks non-loopback viewer access by default", async () => { it.each([
const artifact = await store.createArtifact({ {
html: "<html>viewer</html>", name: "blocks non-loopback viewer access by default",
title: "Demo", request: remoteReq,
inputKind: "before_after", allowRemoteViewer: false,
fileCount: 1, expectedStatusCode: 404,
}); },
{
name: "blocks loopback requests that carry proxy forwarding headers by default",
request: localReq,
headers: { "x-forwarded-for": "203.0.113.10" },
allowRemoteViewer: false,
expectedStatusCode: 404,
},
{
name: "allows remote access when allowRemoteViewer is enabled",
request: remoteReq,
allowRemoteViewer: true,
expectedStatusCode: 200,
},
{
name: "allows proxied loopback requests when allowRemoteViewer is enabled",
request: localReq,
headers: { "x-forwarded-for": "203.0.113.10" },
allowRemoteViewer: true,
expectedStatusCode: 200,
},
])("$name", async ({ request, headers, allowRemoteViewer, expectedStatusCode }) => {
const artifact = await createViewerArtifact(store);
const handler = createDiffsHttpHandler({ store }); const handler = createDiffsHttpHandler({ store, allowRemoteViewer });
const res = createMockServerResponse(); const res = createMockServerResponse();
const handled = await handler( const handled = await handler(
remoteReq({ request({
method: "GET", method: "GET",
url: artifact.viewerPath, url: artifact.viewerPath,
headers,
}), }),
res, res,
); );
expect(handled).toBe(true); expect(handled).toBe(true);
expect(res.statusCode).toBe(404); expect(res.statusCode).toBe(expectedStatusCode);
}); if (expectedStatusCode === 200) {
expect(res.body).toBe("<html>viewer</html>");
it("blocks loopback requests that carry proxy forwarding headers by default", async () => { }
const artifact = await store.createArtifact({
html: "<html>viewer</html>",
title: "Demo",
inputKind: "before_after",
fileCount: 1,
});
const handler = createDiffsHttpHandler({ store });
const res = createMockServerResponse();
const handled = await handler(
localReq({
method: "GET",
url: artifact.viewerPath,
headers: { "x-forwarded-for": "203.0.113.10" },
}),
res,
);
expect(handled).toBe(true);
expect(res.statusCode).toBe(404);
});
it("allows remote access when allowRemoteViewer is enabled", async () => {
const artifact = await store.createArtifact({
html: "<html>viewer</html>",
title: "Demo",
inputKind: "before_after",
fileCount: 1,
});
const handler = createDiffsHttpHandler({ store, allowRemoteViewer: true });
const res = createMockServerResponse();
const handled = await handler(
remoteReq({
method: "GET",
url: artifact.viewerPath,
}),
res,
);
expect(handled).toBe(true);
expect(res.statusCode).toBe(200);
expect(res.body).toBe("<html>viewer</html>");
});
it("allows proxied loopback requests when allowRemoteViewer is enabled", async () => {
const artifact = await store.createArtifact({
html: "<html>viewer</html>",
title: "Demo",
inputKind: "before_after",
fileCount: 1,
});
const handler = createDiffsHttpHandler({ store, allowRemoteViewer: true });
const res = createMockServerResponse();
const handled = await handler(
localReq({
method: "GET",
url: artifact.viewerPath,
headers: { "x-forwarded-for": "203.0.113.10" },
}),
res,
);
expect(handled).toBe(true);
expect(res.statusCode).toBe(200);
expect(res.body).toBe("<html>viewer</html>");
}); });
it("rate-limits repeated remote misses", async () => { it("rate-limits repeated remote misses", async () => {
@@ -232,6 +175,15 @@ describe("createDiffsHttpHandler", () => {
}); });
}); });
async function createViewerArtifact(store: DiffArtifactStore) {
return await store.createArtifact({
html: "<html>viewer</html>",
title: "Demo",
inputKind: "before_after",
fileCount: 1,
});
}
function localReq(input: { function localReq(input: {
method: string; method: string;
url: string; url: string;

View File

@@ -1,21 +1,25 @@
import fs from "node:fs/promises"; import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path"; import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { DiffArtifactStore } from "./store.js"; import { DiffArtifactStore } from "./store.js";
import { createDiffStoreHarness } from "./test-helpers.js";
describe("DiffArtifactStore", () => { describe("DiffArtifactStore", () => {
let rootDir: string; let rootDir: string;
let store: DiffArtifactStore; let store: DiffArtifactStore;
let cleanupRootDir: () => Promise<void>;
beforeEach(async () => { beforeEach(async () => {
rootDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-diffs-store-")); ({
store = new DiffArtifactStore({ rootDir }); rootDir,
store,
cleanup: cleanupRootDir,
} = await createDiffStoreHarness("openclaw-diffs-store-"));
}); });
afterEach(async () => { afterEach(async () => {
vi.useRealTimers(); vi.useRealTimers();
await fs.rm(rootDir, { recursive: true, force: true }); await cleanupRootDir();
}); });
it("creates and retrieves an artifact", async () => { it("creates and retrieves an artifact", async () => {

View File

@@ -0,0 +1,30 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { DiffArtifactStore } from "./store.js";
export async function createTempDiffRoot(prefix: string): Promise<{
rootDir: string;
cleanup: () => Promise<void>;
}> {
const rootDir = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
return {
rootDir,
cleanup: async () => {
await fs.rm(rootDir, { recursive: true, force: true });
},
};
}
export async function createDiffStoreHarness(prefix: string): Promise<{
rootDir: string;
store: DiffArtifactStore;
cleanup: () => Promise<void>;
}> {
const { rootDir, cleanup } = await createTempDiffRoot(prefix);
return {
rootDir,
store: new DiffArtifactStore({ rootDir }),
cleanup,
};
}

View File

@@ -1,25 +1,24 @@
import fs from "node:fs/promises"; import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path"; import path from "node:path";
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/diffs"; import type { OpenClawPluginApi } from "openclaw/plugin-sdk/diffs";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { DiffScreenshotter } from "./browser.js"; import type { DiffScreenshotter } from "./browser.js";
import { DEFAULT_DIFFS_TOOL_DEFAULTS } from "./config.js"; import { DEFAULT_DIFFS_TOOL_DEFAULTS } from "./config.js";
import { DiffArtifactStore } from "./store.js"; import { DiffArtifactStore } from "./store.js";
import { createDiffStoreHarness } from "./test-helpers.js";
import { createDiffsTool } from "./tool.js"; import { createDiffsTool } from "./tool.js";
import type { DiffRenderOptions } from "./types.js"; import type { DiffRenderOptions } from "./types.js";
describe("diffs tool", () => { describe("diffs tool", () => {
let rootDir: string;
let store: DiffArtifactStore; let store: DiffArtifactStore;
let cleanupRootDir: () => Promise<void>;
beforeEach(async () => { beforeEach(async () => {
rootDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-diffs-tool-")); ({ store, cleanup: cleanupRootDir } = await createDiffStoreHarness("openclaw-diffs-tool-"));
store = new DiffArtifactStore({ rootDir });
}); });
afterEach(async () => { afterEach(async () => {
await fs.rm(rootDir, { recursive: true, force: true }); await cleanupRootDir();
}); });
it("returns a viewer URL in view mode", async () => { it("returns a viewer URL in view mode", async () => {