mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-06-11 01:45:58 +00:00
Merge remote-tracking branch 'origin/main' into codex/im-channel-connections
# Conflicts: # backend/app/channels/discord.py # backend/app/channels/manager.py # backend/app/channels/slack.py # backend/app/channels/telegram.py
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
import { AUTH_DISABLED_USER } from "@/core/auth/auth-disabled-user";
|
||||
import { STATIC_WEBSITE_USER } from "@/core/auth/static-user";
|
||||
|
||||
vi.mock("next/headers", () => ({
|
||||
@@ -10,6 +11,8 @@ vi.mock("next/headers", () => ({
|
||||
|
||||
const ENV_KEYS = [
|
||||
"DEER_FLOW_AUTH_DISABLED",
|
||||
"DEER_FLOW_ENV",
|
||||
"ENVIRONMENT",
|
||||
"NEXT_PUBLIC_STATIC_WEBSITE_ONLY",
|
||||
] as const;
|
||||
|
||||
@@ -51,6 +54,8 @@ describe("getServerSideUser", () => {
|
||||
beforeEach(() => {
|
||||
saved = snapshotEnv();
|
||||
setEnv("DEER_FLOW_AUTH_DISABLED", undefined);
|
||||
setEnv("DEER_FLOW_ENV", undefined);
|
||||
setEnv("ENVIRONMENT", undefined);
|
||||
setEnv("NEXT_PUBLIC_STATIC_WEBSITE_ONLY", undefined);
|
||||
});
|
||||
|
||||
@@ -74,4 +79,30 @@ describe("getServerSideUser", () => {
|
||||
});
|
||||
expect(fetchSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("bypasses gateway auth in auth-disabled mode", async () => {
|
||||
setEnv("DEER_FLOW_AUTH_DISABLED", "1");
|
||||
const fetchSpy = vi.fn(() => {
|
||||
throw new Error("fetch should not be called in auth-disabled mode");
|
||||
});
|
||||
vi.stubGlobal("fetch", fetchSpy);
|
||||
|
||||
const { getServerSideUser } = await loadFreshServerAuth();
|
||||
|
||||
await expect(getServerSideUser()).resolves.toEqual({
|
||||
tag: "authenticated",
|
||||
user: AUTH_DISABLED_USER,
|
||||
});
|
||||
expect(fetchSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("does not enable auth-disabled mode in explicit production environments", async () => {
|
||||
setEnv("DEER_FLOW_AUTH_DISABLED", "1");
|
||||
setEnv("DEER_FLOW_ENV", "production");
|
||||
|
||||
const { isAuthDisabledMode } =
|
||||
await import("@/core/auth/auth-disabled-user");
|
||||
|
||||
expect(isAuthDisabledMode()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
import { afterEach, expect, test, vi } from "vitest";
|
||||
|
||||
import { writeTextToClipboard } from "@/core/clipboard";
|
||||
import {
|
||||
installClipboardFallback,
|
||||
writeTextToClipboard,
|
||||
} from "@/core/clipboard";
|
||||
|
||||
const originalNavigator = globalThis.navigator;
|
||||
const hadOriginalNavigator = "navigator" in globalThis;
|
||||
const originalDocument = globalThis.document;
|
||||
const hadOriginalDocument = "document" in globalThis;
|
||||
const originalClipboardItemDescriptor = Object.getOwnPropertyDescriptor(
|
||||
globalThis,
|
||||
"ClipboardItem",
|
||||
);
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
@@ -26,6 +33,16 @@ afterEach(() => {
|
||||
value: originalDocument,
|
||||
});
|
||||
}
|
||||
|
||||
if (!originalClipboardItemDescriptor) {
|
||||
Reflect.deleteProperty(globalThis, "ClipboardItem");
|
||||
} else {
|
||||
Object.defineProperty(
|
||||
globalThis,
|
||||
"ClipboardItem",
|
||||
originalClipboardItemDescriptor,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("writes text with the Clipboard API when available", async () => {
|
||||
@@ -90,6 +107,95 @@ test("falls back to execCommand when Clipboard API is unavailable", async () =>
|
||||
expect(textarea.remove).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("falls back to parent removal when textarea.remove is unavailable", async () => {
|
||||
const parentNode = {
|
||||
removeChild: vi.fn(),
|
||||
};
|
||||
const textarea = {
|
||||
parentNode,
|
||||
select: vi.fn(),
|
||||
setAttribute: vi.fn(),
|
||||
style: {},
|
||||
value: "",
|
||||
};
|
||||
const execCommand = vi.fn().mockReturnValue(true);
|
||||
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: {},
|
||||
});
|
||||
Object.defineProperty(globalThis, "document", {
|
||||
configurable: true,
|
||||
value: {
|
||||
body: {
|
||||
appendChild: vi.fn(),
|
||||
},
|
||||
createElement: vi.fn().mockReturnValue(textarea),
|
||||
execCommand,
|
||||
},
|
||||
});
|
||||
|
||||
await expect(writeTextToClipboard("hello")).resolves.toBe(true);
|
||||
expect(parentNode.removeChild).toHaveBeenCalledWith(textarea);
|
||||
});
|
||||
|
||||
test("does not fail cleanup when textarea removal APIs are unavailable", async () => {
|
||||
const textarea = {
|
||||
parentNode: {},
|
||||
select: vi.fn(),
|
||||
setAttribute: vi.fn(),
|
||||
style: {},
|
||||
value: "",
|
||||
};
|
||||
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: {},
|
||||
});
|
||||
Object.defineProperty(globalThis, "document", {
|
||||
configurable: true,
|
||||
value: {
|
||||
body: {
|
||||
appendChild: vi.fn(),
|
||||
},
|
||||
createElement: vi.fn().mockReturnValue(textarea),
|
||||
execCommand: vi.fn().mockReturnValue(true),
|
||||
},
|
||||
});
|
||||
|
||||
await expect(writeTextToClipboard("hello")).resolves.toBe(true);
|
||||
});
|
||||
|
||||
test("cleans up the textarea when selecting text fails", async () => {
|
||||
const textarea = {
|
||||
remove: vi.fn(),
|
||||
select: vi.fn(() => {
|
||||
throw new Error("selection failed");
|
||||
}),
|
||||
setAttribute: vi.fn(),
|
||||
style: {},
|
||||
value: "",
|
||||
};
|
||||
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: {},
|
||||
});
|
||||
Object.defineProperty(globalThis, "document", {
|
||||
configurable: true,
|
||||
value: {
|
||||
body: {
|
||||
appendChild: vi.fn(),
|
||||
},
|
||||
createElement: vi.fn().mockReturnValue(textarea),
|
||||
execCommand: vi.fn(),
|
||||
},
|
||||
});
|
||||
|
||||
await expect(writeTextToClipboard("hello")).resolves.toBe(false);
|
||||
expect(textarea.remove).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("returns false when execCommand fallback fails", async () => {
|
||||
const textarea = {
|
||||
remove: vi.fn(),
|
||||
@@ -118,6 +224,24 @@ test("returns false when execCommand fallback fails", async () => {
|
||||
expect(textarea.remove).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("returns false when execCommand fallback cannot create an element", async () => {
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: {},
|
||||
});
|
||||
Object.defineProperty(globalThis, "document", {
|
||||
configurable: true,
|
||||
value: {
|
||||
body: {
|
||||
appendChild: vi.fn(),
|
||||
},
|
||||
execCommand: vi.fn(),
|
||||
},
|
||||
});
|
||||
|
||||
await expect(writeTextToClipboard("hello")).resolves.toBe(false);
|
||||
});
|
||||
|
||||
test("returns false when navigator is unavailable", async () => {
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
@@ -144,3 +268,495 @@ test("returns false when Clipboard API rejects", async () => {
|
||||
|
||||
await expect(writeTextToClipboard("hello")).resolves.toBe(false);
|
||||
});
|
||||
|
||||
test("installs a writeText fallback when Clipboard API is unavailable", async () => {
|
||||
const textarea = {
|
||||
remove: vi.fn(),
|
||||
select: vi.fn(),
|
||||
setAttribute: vi.fn(),
|
||||
style: {},
|
||||
value: "",
|
||||
};
|
||||
const appendChild = vi.fn();
|
||||
const execCommand = vi.fn().mockReturnValue(true);
|
||||
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: {},
|
||||
});
|
||||
Object.defineProperty(globalThis, "document", {
|
||||
configurable: true,
|
||||
value: {
|
||||
body: {
|
||||
appendChild,
|
||||
},
|
||||
createElement: vi.fn().mockReturnValue(textarea),
|
||||
execCommand,
|
||||
},
|
||||
});
|
||||
|
||||
installClipboardFallback();
|
||||
|
||||
await expect(globalThis.navigator.clipboard.writeText("hello")).resolves.toBe(
|
||||
undefined,
|
||||
);
|
||||
expect(textarea.value).toBe("hello");
|
||||
expect(appendChild).toHaveBeenCalledWith(textarea);
|
||||
expect(textarea.select).toHaveBeenCalled();
|
||||
expect(execCommand).toHaveBeenCalledWith("copy");
|
||||
expect(textarea.remove).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("installed writeText fallback rejects instead of throwing synchronously", async () => {
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: {},
|
||||
});
|
||||
Object.defineProperty(globalThis, "document", {
|
||||
configurable: true,
|
||||
value: undefined,
|
||||
});
|
||||
|
||||
installClipboardFallback();
|
||||
|
||||
const result = globalThis.navigator.clipboard.writeText("hello");
|
||||
expect(result).toBeInstanceOf(Promise);
|
||||
await expect(result).rejects.toThrow("Clipboard DOM fallback not available");
|
||||
});
|
||||
|
||||
test("installed writeText fallback converts thrown DOM failures to rejections", async () => {
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: {},
|
||||
});
|
||||
Object.defineProperty(globalThis, "document", {
|
||||
configurable: true,
|
||||
value: {
|
||||
body: {
|
||||
appendChild: vi.fn(),
|
||||
},
|
||||
createElement: vi.fn(() => {
|
||||
throw new Error("dom unavailable");
|
||||
}),
|
||||
execCommand: vi.fn(),
|
||||
},
|
||||
});
|
||||
|
||||
installClipboardFallback();
|
||||
|
||||
const result = globalThis.navigator.clipboard.writeText("hello");
|
||||
expect(result).toBeInstanceOf(Promise);
|
||||
await expect(result).rejects.toThrow("dom unavailable");
|
||||
});
|
||||
|
||||
test("installed writeText fallback distinguishes copy command failure", async () => {
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: {},
|
||||
});
|
||||
Object.defineProperty(globalThis, "document", {
|
||||
configurable: true,
|
||||
value: {
|
||||
body: {
|
||||
appendChild: vi.fn(),
|
||||
},
|
||||
createElement: vi.fn().mockReturnValue({
|
||||
remove: vi.fn(),
|
||||
select: vi.fn(),
|
||||
setAttribute: vi.fn(),
|
||||
style: {},
|
||||
value: "",
|
||||
}),
|
||||
execCommand: vi.fn().mockReturnValue(false),
|
||||
},
|
||||
});
|
||||
|
||||
installClipboardFallback();
|
||||
|
||||
await expect(
|
||||
globalThis.navigator.clipboard.writeText("hello"),
|
||||
).rejects.toThrow("Clipboard copy command failed");
|
||||
});
|
||||
|
||||
test("installs a write fallback for ClipboardItem text/plain payloads", async () => {
|
||||
const textarea = {
|
||||
remove: vi.fn(),
|
||||
select: vi.fn(),
|
||||
setAttribute: vi.fn(),
|
||||
style: {},
|
||||
value: "",
|
||||
};
|
||||
const execCommand = vi.fn().mockReturnValue(true);
|
||||
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: {},
|
||||
});
|
||||
Object.defineProperty(globalThis, "document", {
|
||||
configurable: true,
|
||||
value: {
|
||||
body: {
|
||||
appendChild: vi.fn(),
|
||||
},
|
||||
createElement: vi.fn().mockReturnValue(textarea),
|
||||
execCommand,
|
||||
},
|
||||
});
|
||||
Reflect.deleteProperty(globalThis, "ClipboardItem");
|
||||
|
||||
installClipboardFallback();
|
||||
|
||||
const item = new globalThis.ClipboardItem({
|
||||
"text/html": new Blob(["<table></table>"], { type: "text/html" }),
|
||||
"text/plain": "| A |\n| B |",
|
||||
});
|
||||
await expect(globalThis.navigator.clipboard.write([item])).resolves.toBe(
|
||||
undefined,
|
||||
);
|
||||
expect(textarea.value).toBe("| A |\n| B |");
|
||||
expect(execCommand).toHaveBeenCalledWith("copy");
|
||||
});
|
||||
|
||||
test("installed write fallback rejects when ClipboardItem lacks text/plain", async () => {
|
||||
const execCommand = vi.fn().mockReturnValue(true);
|
||||
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: {},
|
||||
});
|
||||
Object.defineProperty(globalThis, "document", {
|
||||
configurable: true,
|
||||
value: {
|
||||
body: {
|
||||
appendChild: vi.fn(),
|
||||
},
|
||||
createElement: vi.fn().mockReturnValue({
|
||||
remove: vi.fn(),
|
||||
select: vi.fn(),
|
||||
setAttribute: vi.fn(),
|
||||
style: {},
|
||||
value: "",
|
||||
}),
|
||||
execCommand,
|
||||
},
|
||||
});
|
||||
Reflect.deleteProperty(globalThis, "ClipboardItem");
|
||||
|
||||
installClipboardFallback();
|
||||
|
||||
const item = new globalThis.ClipboardItem({
|
||||
"text/html": new Blob(["<table></table>"], { type: "text/html" }),
|
||||
});
|
||||
await expect(globalThis.navigator.clipboard.write([item])).rejects.toThrow(
|
||||
"Clipboard item is missing text/plain data",
|
||||
);
|
||||
expect(execCommand).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("installed write fallback rejects when getType cannot provide text/plain", async () => {
|
||||
const execCommand = vi.fn().mockReturnValue(true);
|
||||
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: {},
|
||||
});
|
||||
Object.defineProperty(globalThis, "document", {
|
||||
configurable: true,
|
||||
value: {
|
||||
body: {
|
||||
appendChild: vi.fn(),
|
||||
},
|
||||
createElement: vi.fn().mockReturnValue({
|
||||
remove: vi.fn(),
|
||||
select: vi.fn(),
|
||||
setAttribute: vi.fn(),
|
||||
style: {},
|
||||
value: "",
|
||||
}),
|
||||
execCommand,
|
||||
},
|
||||
});
|
||||
|
||||
installClipboardFallback();
|
||||
|
||||
await expect(
|
||||
globalThis.navigator.clipboard.write([
|
||||
{
|
||||
getType: vi.fn().mockRejectedValue(new Error("missing")),
|
||||
types: ["text/plain"],
|
||||
} as unknown as ClipboardItem,
|
||||
]),
|
||||
).rejects.toThrow("missing");
|
||||
expect(execCommand).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("installed write fallback rejects before getType when item types exclude text/plain", async () => {
|
||||
const getType = vi.fn().mockResolvedValue(new Blob(["ignored"]));
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: {},
|
||||
});
|
||||
Object.defineProperty(globalThis, "document", {
|
||||
configurable: true,
|
||||
value: undefined,
|
||||
});
|
||||
|
||||
installClipboardFallback();
|
||||
|
||||
await expect(
|
||||
globalThis.navigator.clipboard.write([
|
||||
{
|
||||
getType,
|
||||
types: ["text/html"],
|
||||
} as unknown as ClipboardItem,
|
||||
]),
|
||||
).rejects.toThrow("Clipboard item is missing text/plain data");
|
||||
expect(getType).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("installed write fallback rejects when getType is missing", async () => {
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: {},
|
||||
});
|
||||
Object.defineProperty(globalThis, "document", {
|
||||
configurable: true,
|
||||
value: undefined,
|
||||
});
|
||||
|
||||
installClipboardFallback();
|
||||
|
||||
await expect(
|
||||
globalThis.navigator.clipboard.write([
|
||||
{
|
||||
types: ["text/plain"],
|
||||
} as unknown as ClipboardItem,
|
||||
]),
|
||||
).rejects.toThrow("Clipboard item cannot read text/plain data");
|
||||
});
|
||||
|
||||
test("installed write fallback rejects when getType returns a non-Blob", async () => {
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: {},
|
||||
});
|
||||
Object.defineProperty(globalThis, "document", {
|
||||
configurable: true,
|
||||
value: undefined,
|
||||
});
|
||||
|
||||
installClipboardFallback();
|
||||
|
||||
await expect(
|
||||
globalThis.navigator.clipboard.write([
|
||||
{
|
||||
getType: vi.fn().mockResolvedValue("plain text"),
|
||||
types: ["text/plain"],
|
||||
} as unknown as ClipboardItem,
|
||||
]),
|
||||
).rejects.toThrow("Clipboard item text/plain data is not a Blob");
|
||||
});
|
||||
|
||||
test("installed write fallback preserves existing clipboard prototype methods", async () => {
|
||||
const readText = vi.fn().mockResolvedValue("existing");
|
||||
const clipboard = Object.create({
|
||||
readText,
|
||||
});
|
||||
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: {
|
||||
clipboard,
|
||||
},
|
||||
});
|
||||
Object.defineProperty(globalThis, "document", {
|
||||
configurable: true,
|
||||
value: undefined,
|
||||
});
|
||||
Reflect.deleteProperty(globalThis, "ClipboardItem");
|
||||
|
||||
installClipboardFallback();
|
||||
|
||||
expect(globalThis.navigator.clipboard).toBe(clipboard);
|
||||
await expect(globalThis.navigator.clipboard.readText()).resolves.toBe(
|
||||
"existing",
|
||||
);
|
||||
expect(readText).toHaveBeenCalled();
|
||||
await expect(
|
||||
globalThis.navigator.clipboard.writeText("hello"),
|
||||
).rejects.toThrow("Clipboard DOM fallback not available");
|
||||
});
|
||||
|
||||
test("installClipboardFallback does not replace existing clipboard methods when only ClipboardItem is missing", async () => {
|
||||
const write = vi.fn().mockResolvedValue(undefined);
|
||||
const writeText = vi.fn().mockResolvedValue(undefined);
|
||||
const clipboard = {
|
||||
write,
|
||||
writeText,
|
||||
};
|
||||
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: {
|
||||
clipboard,
|
||||
},
|
||||
});
|
||||
Reflect.deleteProperty(globalThis, "ClipboardItem");
|
||||
|
||||
installClipboardFallback();
|
||||
|
||||
expect(globalThis.navigator.clipboard).toBe(clipboard);
|
||||
expect(Reflect.get(globalThis.navigator.clipboard, "write")).toBe(write);
|
||||
expect(Reflect.get(globalThis.navigator.clipboard, "writeText")).toBe(
|
||||
writeText,
|
||||
);
|
||||
expect(typeof globalThis.ClipboardItem).toBe("function");
|
||||
});
|
||||
|
||||
test("installClipboardFallback is idempotent for the same navigator", async () => {
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: {},
|
||||
});
|
||||
Object.defineProperty(globalThis, "document", {
|
||||
configurable: true,
|
||||
value: undefined,
|
||||
});
|
||||
Reflect.deleteProperty(globalThis, "ClipboardItem");
|
||||
|
||||
installClipboardFallback();
|
||||
const clipboard = globalThis.navigator.clipboard;
|
||||
const ClipboardItemFallback = globalThis.ClipboardItem;
|
||||
|
||||
installClipboardFallback();
|
||||
|
||||
expect(globalThis.navigator.clipboard).toBe(clipboard);
|
||||
expect(globalThis.ClipboardItem).toBe(ClipboardItemFallback);
|
||||
});
|
||||
|
||||
test("installClipboardFallback can recover when the same navigator loses fallback globals", async () => {
|
||||
const navigator = {};
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: navigator,
|
||||
});
|
||||
Object.defineProperty(globalThis, "document", {
|
||||
configurable: true,
|
||||
value: undefined,
|
||||
});
|
||||
Reflect.deleteProperty(globalThis, "ClipboardItem");
|
||||
|
||||
installClipboardFallback();
|
||||
Reflect.deleteProperty(globalThis, "ClipboardItem");
|
||||
Reflect.deleteProperty(navigator, "clipboard");
|
||||
|
||||
installClipboardFallback();
|
||||
|
||||
expect(typeof globalThis.navigator.clipboard.writeText).toBe("function");
|
||||
expect(typeof globalThis.ClipboardItem).toBe("function");
|
||||
});
|
||||
|
||||
test("installClipboardFallback defines writable fallback methods", async () => {
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: {},
|
||||
});
|
||||
Object.defineProperty(globalThis, "document", {
|
||||
configurable: true,
|
||||
value: undefined,
|
||||
});
|
||||
|
||||
installClipboardFallback();
|
||||
|
||||
expect(
|
||||
Object.getOwnPropertyDescriptor(globalThis.navigator.clipboard, "write")
|
||||
?.writable,
|
||||
).toBe(true);
|
||||
expect(
|
||||
Object.getOwnPropertyDescriptor(globalThis.navigator.clipboard, "writeText")
|
||||
?.writable,
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test("installClipboardFallback skips missing clipboard on non-extensible navigator while installing ClipboardItem", async () => {
|
||||
const navigator = {};
|
||||
Object.preventExtensions(navigator);
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: navigator,
|
||||
});
|
||||
Reflect.deleteProperty(globalThis, "ClipboardItem");
|
||||
|
||||
installClipboardFallback();
|
||||
|
||||
expect("clipboard" in globalThis.navigator).toBe(false);
|
||||
expect(typeof globalThis.ClipboardItem).toBe("function");
|
||||
});
|
||||
|
||||
test("installClipboardFallback handles non-object navigator.clipboard values", async () => {
|
||||
const navigator = {};
|
||||
Object.defineProperty(navigator, "clipboard", {
|
||||
configurable: true,
|
||||
value: "locked",
|
||||
});
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: navigator,
|
||||
});
|
||||
Object.defineProperty(globalThis, "document", {
|
||||
configurable: true,
|
||||
value: undefined,
|
||||
});
|
||||
|
||||
installClipboardFallback();
|
||||
|
||||
expect(typeof globalThis.navigator.clipboard.writeText).toBe("function");
|
||||
await expect(
|
||||
globalThis.navigator.clipboard.writeText("hello"),
|
||||
).rejects.toThrow("Clipboard DOM fallback not available");
|
||||
});
|
||||
|
||||
test("installClipboardFallback does not throw when ClipboardItem cannot be defined", async () => {
|
||||
const originalDefineProperty = Object.defineProperty;
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: {},
|
||||
});
|
||||
Object.defineProperty(globalThis, "document", {
|
||||
configurable: true,
|
||||
value: undefined,
|
||||
});
|
||||
Reflect.deleteProperty(globalThis, "ClipboardItem");
|
||||
vi.spyOn(Object, "defineProperty").mockImplementation(
|
||||
(target, property, descriptor) => {
|
||||
if (target === globalThis && property === "ClipboardItem") {
|
||||
throw new Error("locked global");
|
||||
}
|
||||
return originalDefineProperty(target, property, descriptor);
|
||||
},
|
||||
);
|
||||
|
||||
expect(() => installClipboardFallback()).not.toThrow();
|
||||
expect(typeof globalThis.navigator.clipboard.writeText).toBe("function");
|
||||
expect("ClipboardItem" in globalThis).toBe(false);
|
||||
});
|
||||
|
||||
test("installs ClipboardItem fallback when the global property exists but is unusable", async () => {
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: {
|
||||
clipboard: {
|
||||
write: vi.fn().mockResolvedValue(undefined),
|
||||
writeText: vi.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
},
|
||||
});
|
||||
Object.defineProperty(globalThis, "ClipboardItem", {
|
||||
configurable: true,
|
||||
value: undefined,
|
||||
});
|
||||
|
||||
installClipboardFallback();
|
||||
|
||||
expect(typeof globalThis.ClipboardItem).toBe("function");
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
hasContent,
|
||||
hasReasoning,
|
||||
isAssistantMessageGroupStreaming,
|
||||
stripUploadedFilesTag,
|
||||
} from "@/core/messages/utils";
|
||||
|
||||
function aiMessage(content: string): Message {
|
||||
@@ -173,6 +174,38 @@ describe("inline <think> tag splitting", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("human message internal context stripping", () => {
|
||||
test("strips slash skill activation context from display content", () => {
|
||||
const content =
|
||||
"<slash_skill_activation>\n<skill_content># Secret SKILL.md</skill_content>\n</slash_skill_activation>\nreal user task";
|
||||
|
||||
expect(stripUploadedFilesTag(content)).toBe("real user task");
|
||||
});
|
||||
|
||||
test("hides leaked slash skill activation messages with no user text", () => {
|
||||
const messages = [
|
||||
{
|
||||
id: "slash-activation",
|
||||
type: "human",
|
||||
content:
|
||||
"<slash_skill_activation>\n<skill_content># Secret SKILL.md</skill_content>\n</slash_skill_activation>",
|
||||
},
|
||||
{
|
||||
id: "ai-1",
|
||||
type: "ai",
|
||||
content: "Public answer",
|
||||
},
|
||||
] as Message[];
|
||||
|
||||
const groups = getMessageGroups(messages);
|
||||
|
||||
expect(groups.map((group) => group.type)).toEqual(["assistant"]);
|
||||
expect(
|
||||
groups.flatMap((group) => group.messages).map((message) => message.id),
|
||||
).toEqual(["ai-1"]);
|
||||
});
|
||||
});
|
||||
|
||||
test("hides internal todo reminder messages from message groups", () => {
|
||||
const messages = [
|
||||
{
|
||||
|
||||
@@ -260,6 +260,22 @@ describe("formatThreadAsJSON", () => {
|
||||
expect(raw).toContain("real user text");
|
||||
});
|
||||
|
||||
it("strips <slash_skill_activation> as defence in depth", () => {
|
||||
// Slash activation normally rides in a hidden HumanMessage. If a replay
|
||||
// or state merge loses the flag, export must still not leak full SKILL.md
|
||||
// content into a user-visible transcript.
|
||||
const leaky = human("real user task", {
|
||||
id: "leak-slash-skill",
|
||||
content:
|
||||
"<slash_skill_activation>\n<skill_content># Secret SKILL.md\nUse internal source.</skill_content>\n</slash_skill_activation>\nreal user task",
|
||||
} as unknown as Partial<Message>);
|
||||
const raw = formatThreadAsJSON(makeThread(), [leaky]);
|
||||
expect(raw).not.toContain("<slash_skill_activation>");
|
||||
expect(raw).not.toContain("Secret SKILL.md");
|
||||
expect(raw).not.toContain("internal source");
|
||||
expect(raw).toContain("real user task");
|
||||
});
|
||||
|
||||
it("sanitises tool message content when includeToolMessages is true", () => {
|
||||
const message = {
|
||||
id: "t-leak",
|
||||
|
||||
Reference in New Issue
Block a user