mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-03 03:03:24 -04:00
test: dedupe and optimize test suites
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveBrowserConfig } from "./config.js";
|
||||
import {
|
||||
allocateCdpPort,
|
||||
allocateColor,
|
||||
@@ -11,15 +12,12 @@ import {
|
||||
} from "./profiles.js";
|
||||
|
||||
describe("profile name validation", () => {
|
||||
it("accepts valid lowercase names", () => {
|
||||
expect(isValidProfileName("openclaw")).toBe(true);
|
||||
expect(isValidProfileName("work")).toBe(true);
|
||||
expect(isValidProfileName("my-profile")).toBe(true);
|
||||
expect(isValidProfileName("test123")).toBe(true);
|
||||
expect(isValidProfileName("a")).toBe(true);
|
||||
expect(isValidProfileName("a-b-c-1-2-3")).toBe(true);
|
||||
expect(isValidProfileName("1test")).toBe(true);
|
||||
});
|
||||
it.each(["openclaw", "work", "my-profile", "test123", "a", "a-b-c-1-2-3", "1test"])(
|
||||
"accepts valid lowercase name: %s",
|
||||
(name) => {
|
||||
expect(isValidProfileName(name)).toBe(true);
|
||||
},
|
||||
);
|
||||
|
||||
it("rejects empty or missing names", () => {
|
||||
expect(isValidProfileName("")).toBe(false);
|
||||
@@ -37,23 +35,19 @@ describe("profile name validation", () => {
|
||||
expect(isValidProfileName(maxName)).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects uppercase letters", () => {
|
||||
expect(isValidProfileName("MyProfile")).toBe(false);
|
||||
expect(isValidProfileName("PROFILE")).toBe(false);
|
||||
expect(isValidProfileName("Work")).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects spaces and special characters", () => {
|
||||
expect(isValidProfileName("my profile")).toBe(false);
|
||||
expect(isValidProfileName("my_profile")).toBe(false);
|
||||
expect(isValidProfileName("my.profile")).toBe(false);
|
||||
expect(isValidProfileName("my/profile")).toBe(false);
|
||||
expect(isValidProfileName("my@profile")).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects names starting with hyphen", () => {
|
||||
expect(isValidProfileName("-invalid")).toBe(false);
|
||||
expect(isValidProfileName("--double")).toBe(false);
|
||||
it.each([
|
||||
"MyProfile",
|
||||
"PROFILE",
|
||||
"Work",
|
||||
"my profile",
|
||||
"my_profile",
|
||||
"my.profile",
|
||||
"my/profile",
|
||||
"my@profile",
|
||||
"-invalid",
|
||||
"--double",
|
||||
])("rejects invalid name: %s", (name) => {
|
||||
expect(isValidProfileName(name)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -131,9 +125,8 @@ describe("getUsedPorts", () => {
|
||||
});
|
||||
|
||||
describe("port collision prevention", () => {
|
||||
it("raw config vs resolved config - shows the data source difference", async () => {
|
||||
it("raw config vs resolved config - shows the data source difference", () => {
|
||||
// This demonstrates WHY the route handler must use resolved config
|
||||
const { resolveBrowserConfig } = await import("./config.js");
|
||||
|
||||
// Fresh config with no profiles defined (like a new install)
|
||||
const rawConfigProfiles = undefined;
|
||||
@@ -148,9 +141,8 @@ describe("port collision prevention", () => {
|
||||
expect(usedFromResolved.has(CDP_PORT_RANGE_START)).toBe(true);
|
||||
});
|
||||
|
||||
it("create-profile must use resolved config to avoid port collision", async () => {
|
||||
it("create-profile must use resolved config to avoid port collision", () => {
|
||||
// The route handler must use state.resolved.profiles, not raw config
|
||||
const { resolveBrowserConfig } = await import("./config.js");
|
||||
|
||||
// Simulate what happens with raw config (empty) vs resolved config
|
||||
const rawConfig: { browser: { profiles?: Record<string, { cdpPort?: number }> } } = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
let page: { evaluate: ReturnType<typeof vi.fn> } | null = null;
|
||||
let locator: { evaluate: ReturnType<typeof vi.fn> } | null = null;
|
||||
@@ -29,64 +29,61 @@ vi.mock("./pw-session.js", () => {
|
||||
};
|
||||
});
|
||||
|
||||
describe("evaluateViaPlaywright (abort)", () => {
|
||||
it("rejects when aborted after page.evaluate starts", async () => {
|
||||
vi.clearAllMocks();
|
||||
const ctrl = new AbortController();
|
||||
let evaluateViaPlaywright: typeof import("./pw-tools-core.interactions.js").evaluateViaPlaywright;
|
||||
|
||||
let evalCalled!: () => void;
|
||||
const evalCalledPromise = new Promise<void>((resolve) => {
|
||||
evalCalled = resolve;
|
||||
});
|
||||
function createPendingEval() {
|
||||
let evalCalled!: () => void;
|
||||
const evalCalledPromise = new Promise<void>((resolve) => {
|
||||
evalCalled = resolve;
|
||||
});
|
||||
return {
|
||||
evalCalledPromise,
|
||||
resolveEvalCalled: evalCalled,
|
||||
};
|
||||
}
|
||||
|
||||
describe("evaluateViaPlaywright (abort)", () => {
|
||||
beforeAll(async () => {
|
||||
({ evaluateViaPlaywright } = await import("./pw-tools-core.interactions.js"));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ label: "page.evaluate", fn: "() => 1" },
|
||||
{ label: "locator.evaluate", fn: "(el) => el.textContent", ref: "e1" },
|
||||
])("rejects when aborted after $label starts", async ({ fn, ref }) => {
|
||||
const ctrl = new AbortController();
|
||||
const pending = createPendingEval();
|
||||
const pendingPromise = new Promise(() => {});
|
||||
|
||||
page = {
|
||||
evaluate: vi.fn(() => {
|
||||
evalCalled();
|
||||
return new Promise(() => {});
|
||||
if (!ref) {
|
||||
pending.resolveEvalCalled();
|
||||
}
|
||||
return pendingPromise;
|
||||
}),
|
||||
};
|
||||
locator = { evaluate: vi.fn() };
|
||||
|
||||
const { evaluateViaPlaywright } = await import("./pw-tools-core.interactions.js");
|
||||
const p = evaluateViaPlaywright({
|
||||
cdpUrl: "http://127.0.0.1:9222",
|
||||
fn: "() => 1",
|
||||
signal: ctrl.signal,
|
||||
});
|
||||
|
||||
await evalCalledPromise;
|
||||
ctrl.abort(new Error("aborted by test"));
|
||||
|
||||
await expect(p).rejects.toThrow("aborted by test");
|
||||
expect(forceDisconnectPlaywrightForTarget).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rejects when aborted after locator.evaluate starts", async () => {
|
||||
vi.clearAllMocks();
|
||||
const ctrl = new AbortController();
|
||||
|
||||
let evalCalled!: () => void;
|
||||
const evalCalledPromise = new Promise<void>((resolve) => {
|
||||
evalCalled = resolve;
|
||||
});
|
||||
|
||||
page = { evaluate: vi.fn() };
|
||||
locator = {
|
||||
evaluate: vi.fn(() => {
|
||||
evalCalled();
|
||||
return new Promise(() => {});
|
||||
if (ref) {
|
||||
pending.resolveEvalCalled();
|
||||
}
|
||||
return pendingPromise;
|
||||
}),
|
||||
};
|
||||
|
||||
const { evaluateViaPlaywright } = await import("./pw-tools-core.interactions.js");
|
||||
const p = evaluateViaPlaywright({
|
||||
cdpUrl: "http://127.0.0.1:9222",
|
||||
fn: "(el) => el.textContent",
|
||||
ref: "e1",
|
||||
fn,
|
||||
ref,
|
||||
signal: ctrl.signal,
|
||||
});
|
||||
|
||||
await evalCalledPromise;
|
||||
await pending.evalCalledPromise;
|
||||
ctrl.abort(new Error("aborted by test"));
|
||||
|
||||
await expect(p).rejects.toThrow("aborted by test");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { resolveBrowserConfig } from "./config.js";
|
||||
import {
|
||||
refreshResolvedBrowserConfigFromDisk,
|
||||
@@ -40,6 +40,12 @@ vi.mock("../config/config.js", () => ({
|
||||
}));
|
||||
|
||||
describe("server-context hot-reload profiles", () => {
|
||||
let loadConfig: typeof import("../config/config.js").loadConfig;
|
||||
|
||||
beforeAll(async () => {
|
||||
({ loadConfig } = await import("../config/config.js"));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
cfgProfiles = {
|
||||
@@ -49,8 +55,6 @@ describe("server-context hot-reload profiles", () => {
|
||||
});
|
||||
|
||||
it("forProfile hot-reloads newly added profiles from config", async () => {
|
||||
const { loadConfig } = await import("../config/config.js");
|
||||
|
||||
// Start with only openclaw profile
|
||||
// 1. Prime the cache by calling loadConfig() first
|
||||
const cfg = loadConfig();
|
||||
@@ -101,8 +105,6 @@ describe("server-context hot-reload profiles", () => {
|
||||
});
|
||||
|
||||
it("forProfile still throws for profiles that don't exist in fresh config", async () => {
|
||||
const { loadConfig } = await import("../config/config.js");
|
||||
|
||||
const cfg = loadConfig();
|
||||
const resolved = resolveBrowserConfig(cfg.browser, cfg);
|
||||
const state = {
|
||||
@@ -123,8 +125,6 @@ describe("server-context hot-reload profiles", () => {
|
||||
});
|
||||
|
||||
it("forProfile refreshes existing profile config after loadConfig cache updates", async () => {
|
||||
const { loadConfig } = await import("../config/config.js");
|
||||
|
||||
const cfg = loadConfig();
|
||||
const resolved = resolveBrowserConfig(cfg.browser, cfg);
|
||||
const state = {
|
||||
@@ -147,8 +147,6 @@ describe("server-context hot-reload profiles", () => {
|
||||
});
|
||||
|
||||
it("listProfiles refreshes config before enumerating profiles", async () => {
|
||||
const { loadConfig } = await import("../config/config.js");
|
||||
|
||||
const cfg = loadConfig();
|
||||
const resolved = resolveBrowserConfig(cfg.browser, cfg);
|
||||
const state = {
|
||||
|
||||
Reference in New Issue
Block a user