mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 11:55:47 -06:00
fix(media): accept mixed-case local file URLs (#121226)
This commit is contained in:
committed by
GitHub
parent
727e48240b
commit
3bdfd60caa
@@ -164,6 +164,7 @@ describe("msteams media-helpers", () => {
|
||||
it("returns true for file:// URLs", () => {
|
||||
expect(isLocalPath("file:///tmp/image.png")).toBe(true);
|
||||
expect(isLocalPath("file://localhost/tmp/image.png")).toBe(true);
|
||||
expect(isLocalPath("FILE:///C:/Users/test/image.png")).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true for absolute paths", () => {
|
||||
|
||||
@@ -81,7 +81,7 @@ export async function extractFilename(url: string): Promise<string> {
|
||||
* Check if a URL refers to a local file path.
|
||||
*/
|
||||
export function isLocalPath(url: string): boolean {
|
||||
if (url.startsWith("file://") || url.startsWith("/") || url.startsWith("~")) {
|
||||
if (/^file:\/\//iu.test(url) || url.startsWith("/") || url.startsWith("~")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Msteams tests cover messenger plugin behavior.
|
||||
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { PlatformMessageNotDispatchedError } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { SILENT_REPLY_TOKEN } from "openclaw/plugin-sdk/reply-chunking";
|
||||
import type { PluginRuntime } from "openclaw/plugin-sdk/runtime-store";
|
||||
@@ -399,6 +400,38 @@ describe("msteams messenger", () => {
|
||||
expect(sendActivity).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("loads uppercase file URLs before sending personal images", async () => {
|
||||
const tmpDir = await mkdtemp(
|
||||
path.join(resolvePreferredOpenClawTmpDir(), "msteams-file-url-"),
|
||||
);
|
||||
const localFile = path.join(tmpDir, "café image.png");
|
||||
const png = Buffer.from(
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=",
|
||||
"base64",
|
||||
);
|
||||
await writeFile(localFile, png);
|
||||
|
||||
try {
|
||||
const mediaUrl = pathToFileURL(localFile).href.replace(/^file:/u, "FILE:");
|
||||
const activity = await buildActivity(
|
||||
{ mediaUrl },
|
||||
{
|
||||
...baseRef,
|
||||
conversation: { ...baseRef.conversation, conversationType: "personal" },
|
||||
},
|
||||
);
|
||||
const attachment = (activity.attachments as Array<Record<string, unknown>>)[0];
|
||||
|
||||
expect(attachment).toMatchObject({
|
||||
name: "café image.png",
|
||||
contentType: "image/png",
|
||||
contentUrl: `data:image/png;base64,${png.toString("base64")}`,
|
||||
});
|
||||
} finally {
|
||||
await rm(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("does not claim no dispatch after an earlier batch message was sent", async () => {
|
||||
const sendActivity = vi.fn(async () => ({ id: "sent-first" }));
|
||||
const missingPath = path.join(resolvePreferredOpenClawTmpDir(), "missing-second-file.txt");
|
||||
|
||||
+1
-1
@@ -1954,7 +1954,7 @@
|
||||
"test:unit:fast:audit": "node --import tsx scripts/test-unit-fast-audit.mts",
|
||||
"test:voicecall:closedloop": "node --import tsx scripts/test-voicecall-closedloop.mts",
|
||||
"test:watch": "node --import tsx scripts/test-projects.mts --watch",
|
||||
"test:windows:ci": "node --import tsx scripts/test-projects.mts src/shared/runtime-import.test.ts src/config/sessions/session-accessor.sqlite-archive.worker.test.ts src/commands/doctor-gateway-auth-token.windows.test.ts src/infra/sqlite-snapshot.test.ts src/infra/ssh-client.windows.test.ts src/infra/update-managed-service-handoff-command.test.ts src/infra/update-managed-service-handoff-lifecycle.test.ts src/infra/exec-allowlist-pattern.test.ts src/infra/executable-path.test.ts src/infra/fs-safe-remove.test.ts src/snapshot/local-repository.windows.test.ts src/state/openclaw-database-paths.windows.test.ts src/commands/backup-verify.test.ts src/infra/state-migrations.legacy-session-store.test.ts src/test-utils/openclaw-test-state.test.ts src/agents/sessions/windows-git-bash-path.test.ts src/agents/bash-tools.exec.script-preflight.test.ts src/process/exec.windows.test.ts src/process/exec.windows.integration.test.ts src/process/windows-command.test.ts src/infra/windows-install-roots.test.ts src/node-host/invoke-system-run-allowlist.test.ts src/daemon/schtasks.startup-fallback.test.ts extensions/lobster/src/lobster-runner.test.ts extensions/mxc/test/mxc-backend.test.ts extensions/mxc/test/sandbox-policy-loader.test.ts test/e2e/qa-lab/runtime/doctor-auth-secretref-checks.e2e.test.ts test/e2e/qa-lab/runtime/package-openclaw-for-docker.e2e.test.ts test/scripts/direct-run-entrypoints.test.ts test/scripts/format-generated-module.test.ts test/scripts/npm-runner.test.ts test/scripts/openclaw-cross-os-installer.windows.test.ts test/scripts/openclaw-cross-os-release-workflow.test.ts test/scripts/pnpm-runner.test.ts test/scripts/run-with-env.test.ts test/scripts/ts-topology.test.ts test/scripts/ui.test.ts test/scripts/vitest-process-group.test.ts",
|
||||
"test:windows:ci": "node --import tsx scripts/test-projects.mts src/shared/runtime-import.test.ts src/config/sessions/session-accessor.sqlite-archive.worker.test.ts src/commands/doctor-gateway-auth-token.windows.test.ts src/media/local-media-path.windows.test.ts src/infra/sqlite-snapshot.test.ts src/infra/ssh-client.windows.test.ts src/infra/update-managed-service-handoff-command.test.ts src/infra/update-managed-service-handoff-lifecycle.test.ts src/infra/exec-allowlist-pattern.test.ts src/infra/executable-path.test.ts src/infra/fs-safe-remove.test.ts src/snapshot/local-repository.windows.test.ts src/state/openclaw-database-paths.windows.test.ts src/commands/backup-verify.test.ts src/infra/state-migrations.legacy-session-store.test.ts src/test-utils/openclaw-test-state.test.ts src/agents/sessions/windows-git-bash-path.test.ts src/agents/bash-tools.exec.script-preflight.test.ts src/process/exec.windows.test.ts src/process/exec.windows.integration.test.ts src/process/windows-command.test.ts src/infra/windows-install-roots.test.ts src/node-host/invoke-system-run-allowlist.test.ts src/daemon/schtasks.startup-fallback.test.ts src/media/web-media.file-url.windows.test.ts extensions/lobster/src/lobster-runner.test.ts extensions/msteams/src/media-helpers.test.ts extensions/msteams/src/messenger.test.ts extensions/mxc/test/mxc-backend.test.ts extensions/mxc/test/sandbox-policy-loader.test.ts test/e2e/qa-lab/runtime/doctor-auth-secretref-checks.e2e.test.ts test/e2e/qa-lab/runtime/package-openclaw-for-docker.e2e.test.ts test/scripts/direct-run-entrypoints.test.ts test/scripts/format-generated-module.test.ts test/scripts/npm-runner.test.ts test/scripts/openclaw-cross-os-installer.windows.test.ts test/scripts/openclaw-cross-os-release-workflow.test.ts test/scripts/pnpm-runner.test.ts test/scripts/run-with-env.test.ts test/scripts/ts-topology.test.ts test/scripts/ui.test.ts test/scripts/vitest-process-group.test.ts",
|
||||
"test:windows:schtasks:integration": "node --import tsx scripts/run-with-env.mts CI_WINDOWS_SCHTASKS_INTEGRATION=1 OPENCLAW_E2E_VERBOSE=1 OPENCLAW_VITEST_MAX_WORKERS=1 -- node scripts/run-vitest.mjs src/daemon/schtasks.integration.e2e.test.ts",
|
||||
"tool-display:check": "node --import tsx scripts/tool-display.ts --check",
|
||||
"tool-display:write": "node --import tsx scripts/tool-display.ts --write",
|
||||
|
||||
@@ -55,6 +55,8 @@ const ANDROID_NATIVE_RE = /^(apps\/android\/|apps\/shared\/)/;
|
||||
const NODE_SCOPE_RE =
|
||||
/^(src\/|test\/|extensions\/|packages\/|scripts\/|ui\/|\.github\/|openclaw\.mjs$|package\.json$|pnpm-lock\.yaml$|pnpm-workspace\.yaml$|tsconfig.*\.json$|vitest.*\.ts$|tsdown\.config\.ts$|\.oxlintrc\.json$|\.oxfmtrc\.jsonc$)/;
|
||||
const WINDOWS_SQLITE_SCOPE_RE = /^src\/(?:state\/|.*sqlite.*\.ts$)/;
|
||||
const WINDOWS_FILE_URL_SCOPE_RE =
|
||||
/^(?:src\/media\/(?:local-media-path(?:\.windows\.test)?|local-roots(?:\.test)?|web-media(?:\.file-url\.windows\.test)?)|src\/channels\/inbound-event\/media(?:\.test)?|src\/gateway\/managed-image-attachments(?:\.test)?|extensions\/msteams\/src\/(?:media-helpers|messenger)(?:\.test)?)\.ts$/;
|
||||
const WINDOWS_SCOPE_RE =
|
||||
/^(extensions\/mxc\/|src\/agents\/(?:bash-tools\.exec-script-(?:preflight|target)|bash-tools\.exec\.script-preflight\.test)\.ts$|src\/config\/sessions\/(?:session-accessor\.sqlite-archive(?:\.worker(?:\.test)?)?|store\.session-lifecycle-mutation\.test)\.ts$|src\/process\/|src\/infra\/(?:(?:exec-allowlist-pattern|fs-safe-remove)(?:\.test)?|ssh-client(?:\.windows\.test)?|update-managed-service-handoff(?:-(?:command|lifecycle)\.test)?|windows-install-roots)\.ts$|src\/shared\/(?:import-specifier|runtime-import)(?:\.test)?\.ts$|src\/test-utils\/openclaw-test-state(?:\.test)?\.ts$|scripts\/(?:android-(?:app-i18n|pin-version)\.ts|ci-run-timings\.mjs|e2e\/lib\/package-compat\.mjs|generate-bundled-channel-config-metadata\.ts|install\.ps1|openclaw-cross-os-release-checks\.ts|plan-release-workflow-matrix\.mjs|run-additional-boundary-checks\.mts|verify-docker-attestations\.mjs|github\/run-openclaw-cross-os-release-checks\.sh|(?:npm-runner|pnpm-runner|ui|vitest-process-group)\.(?:mjs|mts|js)|lib\/(?:direct-run\.(?:mjs|mts)|format-generated-module\.mts|cross-os-release-checks\/[^/]+\.ts))$|test\/scripts\/(?:direct-run-entrypoints|format-generated-module|install-ps1|npm-runner|openclaw-cross-os-release-workflow|pnpm-runner|ui|vitest-process-group)\.test\.ts$|package\.json$|pnpm-lock\.yaml$|pnpm-workspace\.yaml$|\.github\/workflows\/(?:ci|openclaw-cross-os-release-checks-reusable)\.yml$|\.github\/actions\/setup-node-env\/action\.yml$|\.github\/actions\/setup-pnpm-store-cache\/action\.yml$)/;
|
||||
const WINDOWS_TEST_SCOPE_RE =
|
||||
@@ -170,10 +172,12 @@ export function detectChangedScope(changedPaths) {
|
||||
if (
|
||||
(WINDOWS_SCOPE_RE.test(path) ||
|
||||
WINDOWS_SQLITE_SCOPE_RE.test(path) ||
|
||||
WINDOWS_FILE_URL_SCOPE_RE.test(path) ||
|
||||
WINDOWS_SECRETREF_SCOPE_RE.test(path) ||
|
||||
WINDOWS_DAEMON_SCOPE_RE.test(path)) &&
|
||||
(!facts.isTestOnly ||
|
||||
WINDOWS_TEST_SCOPE_RE.test(path) ||
|
||||
WINDOWS_FILE_URL_SCOPE_RE.test(path) ||
|
||||
WINDOWS_SECRETREF_TEST_SCOPE_RE.test(path) ||
|
||||
WINDOWS_DAEMON_SCOPE_RE.test(path))
|
||||
) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Inbound event media tests cover channel media attachment normalization.
|
||||
import path from "node:path";
|
||||
import { kindFromMime } from "@openclaw/media-core/mime";
|
||||
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
@@ -340,8 +341,8 @@ describe("channel inbound media facts", () => {
|
||||
]);
|
||||
expect(probeMediaFilesWithinBudget).toHaveBeenCalledWith(
|
||||
[
|
||||
{ filePath: "/tmp/voice.ogg", kind: "audio" },
|
||||
{ filePath: "/tmp/clip.mp4", kind: "video" },
|
||||
{ filePath: path.resolve("/tmp/voice.ogg"), kind: "audio" },
|
||||
{ filePath: path.resolve("/tmp/clip.mp4"), kind: "video" },
|
||||
],
|
||||
{ budgetMs: 3000, concurrency: 2, maxProbes: 8 },
|
||||
);
|
||||
|
||||
@@ -12,7 +12,7 @@ export function resolveLocalMediaPath(source: string): string | undefined {
|
||||
if (!trimmed || isPassThroughRemoteMediaSource(trimmed) || DATA_URL_RE.test(trimmed)) {
|
||||
return undefined;
|
||||
}
|
||||
if (trimmed.startsWith("file://")) {
|
||||
if (/^file:\/\//iu.test(trimmed)) {
|
||||
try {
|
||||
return safeFileURLToPath(trimmed);
|
||||
} catch {
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createSolidPngBuffer } from "../../test/helpers/image-fixtures.js";
|
||||
import { toInboundMediaFactsWithMetadata } from "../channels/inbound-event/media.js";
|
||||
import { createManagedOutgoingMediaBlocks } from "../gateway/managed-image-attachments.js";
|
||||
import { closeOpenClawStateDatabaseForTest } from "../state/openclaw-state-db.js";
|
||||
import { withEnvAsync } from "../test-utils/env.js";
|
||||
import { resolveLocalMediaPath } from "./local-media-path.js";
|
||||
import { appendLocalMediaParentRoots } from "./local-roots.js";
|
||||
|
||||
const { probeMediaFilesWithinBudget } = vi.hoisted(() => ({
|
||||
probeMediaFilesWithinBudget: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./media-probe.js", async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import("./media-probe.js")>()),
|
||||
probeMediaFilesWithinBudget,
|
||||
}));
|
||||
|
||||
function toUppercaseFileUrl(filePath: string): string {
|
||||
return pathToFileURL(filePath).href.replace(/^file:/u, "FILE:");
|
||||
}
|
||||
|
||||
async function withTempRoot<T>(run: (root: string) => Promise<T>): Promise<T> {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-local-media-url-"));
|
||||
try {
|
||||
return await run(root);
|
||||
} finally {
|
||||
closeOpenClawStateDatabaseForTest();
|
||||
await fs.rm(root, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
describe.runIf(process.platform === "win32")("Windows local media file URLs", () => {
|
||||
beforeEach(() => {
|
||||
probeMediaFilesWithinBudget.mockReset();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
closeOpenClawStateDatabaseForTest();
|
||||
});
|
||||
|
||||
it("resolves mixed-case local file schemes and rejects unsafe file URLs", async () => {
|
||||
await withTempRoot(async (root) => {
|
||||
const sourcePath = path.join(root, "Media Folder Ω", "photo.png");
|
||||
|
||||
expect(resolveLocalMediaPath(toUppercaseFileUrl(sourcePath))).toBe(sourcePath);
|
||||
expect(resolveLocalMediaPath("FILE://server/share/photo.png")).toBeUndefined();
|
||||
expect(resolveLocalMediaPath("FILE:///C:/Media%2Fphoto.png")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("adds the exact parent root for an uppercase file URL", async () => {
|
||||
await withTempRoot(async (root) => {
|
||||
const sourcePath = path.join(root, "Media Folder Ω", "photo.png");
|
||||
|
||||
expect(appendLocalMediaParentRoots([], [toUppercaseFileUrl(sourcePath)])).toEqual([
|
||||
path.dirname(sourcePath),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it("probes inbound metadata through an uppercase file URL", async () => {
|
||||
await withTempRoot(async (root) => {
|
||||
const sourcePath = path.join(root, "Media Folder Ω", "voice.mp3");
|
||||
await fs.mkdir(path.dirname(sourcePath), { recursive: true });
|
||||
await fs.writeFile(sourcePath, Buffer.from([0xff, 0xfb, 0x90, 0x00]));
|
||||
probeMediaFilesWithinBudget.mockResolvedValueOnce([{ durationMs: 1250 }]);
|
||||
|
||||
await expect(
|
||||
toInboundMediaFactsWithMetadata([
|
||||
{ path: toUppercaseFileUrl(sourcePath), contentType: "audio/mpeg" },
|
||||
]),
|
||||
).resolves.toEqual([
|
||||
expect.objectContaining({ path: toUppercaseFileUrl(sourcePath), durationMs: 1250 }),
|
||||
]);
|
||||
expect(probeMediaFilesWithinBudget).toHaveBeenCalledWith(
|
||||
[{ filePath: sourcePath, kind: "audio" }],
|
||||
{ budgetMs: 3000, concurrency: 2, maxProbes: 8 },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("ingests an uppercase file URL into managed outgoing media", async () => {
|
||||
await withTempRoot(async (root) => {
|
||||
const sourcePath = path.join(root, "workspace", "Media Folder Ω", "photo.png");
|
||||
await fs.mkdir(path.dirname(sourcePath), { recursive: true });
|
||||
await fs.writeFile(sourcePath, createSolidPngBuffer(8, 8, { r: 24, g: 64, b: 128 }));
|
||||
const sourceUrl = new URL(toUppercaseFileUrl(sourcePath));
|
||||
sourceUrl.searchParams.set("sig", "secret");
|
||||
sourceUrl.hash = "preview";
|
||||
|
||||
await withEnvAsync({ OPENCLAW_STATE_DIR: root }, async () => {
|
||||
const blocks = await createManagedOutgoingMediaBlocks({
|
||||
stateDir: root,
|
||||
sessionKey: "agent:main:main",
|
||||
mediaUrls: [sourceUrl.href.replace(/^file:/u, "FILE:")],
|
||||
localRoots: [path.join(root, "workspace")],
|
||||
});
|
||||
|
||||
expect(blocks).toHaveLength(1);
|
||||
expect(blocks[0]).toMatchObject({ type: "image", alt: "photo.png" });
|
||||
expect(JSON.stringify(blocks[0])).not.toContain(sourcePath);
|
||||
expect(JSON.stringify(blocks[0])).not.toContain("sig=secret");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||
import { createSolidPngBuffer } from "../../test/helpers/image-fixtures.js";
|
||||
import { loadWebMedia } from "./web-media.js";
|
||||
|
||||
const TINY_PNG = createSolidPngBuffer(1, 1, { r: 255, g: 255, b: 255 });
|
||||
|
||||
describe.runIf(process.platform === "win32")("Windows web media file URLs", () => {
|
||||
let fixtureRoot = "";
|
||||
|
||||
beforeAll(async () => {
|
||||
fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-web-media-file-url-"));
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (fixtureRoot) {
|
||||
await fs.rm(fixtureRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("loads uppercase file URLs with spaces and Unicode", async () => {
|
||||
const filePath = path.join(fixtureRoot, "café image.png");
|
||||
await fs.writeFile(filePath, TINY_PNG);
|
||||
const fileUrl = pathToFileURL(filePath).href.replace(/^file:/u, "FILE:");
|
||||
|
||||
const result = await loadWebMedia(fileUrl, {
|
||||
maxBytes: 1024 * 1024,
|
||||
localRoots: [fixtureRoot],
|
||||
});
|
||||
|
||||
expect(result.buffer).toEqual(TINY_PNG);
|
||||
expect(result.fileName).toBe("café image.png");
|
||||
});
|
||||
|
||||
it.each([
|
||||
"FILE://attacker/share/evil.png",
|
||||
"FILE:///C:/safe/folder%2Fsecret.png",
|
||||
"FILE:///C:/safe/folder%5Csecret.png",
|
||||
"FILE:////attacker/share/evil.png",
|
||||
])("rejects unsafe uppercase file URL %s", async (fileUrl) => {
|
||||
await expect(loadWebMedia(fileUrl, { localRoots: [fixtureRoot] })).rejects.toMatchObject({
|
||||
code: "invalid-file-url",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1030,7 +1030,7 @@ async function loadWebMediaInternal(
|
||||
mediaUrl = stripLegacyMediaDirectivePrefix(mediaUrl);
|
||||
mediaUrl = (await resolveMediaStoreUriToPath(mediaUrl)) ?? mediaUrl;
|
||||
// Use fileURLToPath for proper handling of file:// URLs (handles file://localhost/path, etc.)
|
||||
if (mediaUrl.startsWith("file://")) {
|
||||
if (/^file:\/\//iu.test(mediaUrl)) {
|
||||
try {
|
||||
mediaUrl = safeFileURLToPath(mediaUrl);
|
||||
} catch (err) {
|
||||
|
||||
@@ -115,6 +115,29 @@ describe("detectChangedScope Windows routing", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("routes web and Teams file URL handling to Windows", () => {
|
||||
for (const fileUrlPath of [
|
||||
"src/media/local-media-path.ts",
|
||||
"src/media/local-media-path.windows.test.ts",
|
||||
"src/media/local-roots.ts",
|
||||
"src/media/local-roots.test.ts",
|
||||
"src/media/web-media.ts",
|
||||
"src/media/web-media.file-url.windows.test.ts",
|
||||
"src/channels/inbound-event/media.ts",
|
||||
"src/channels/inbound-event/media.test.ts",
|
||||
"src/gateway/managed-image-attachments.ts",
|
||||
"src/gateway/managed-image-attachments.test.ts",
|
||||
"extensions/msteams/src/media-helpers.ts",
|
||||
"extensions/msteams/src/media-helpers.test.ts",
|
||||
"extensions/msteams/src/messenger.test.ts",
|
||||
]) {
|
||||
expect(detectChangedScope([fileUrlPath]), fileUrlPath).toMatchObject({
|
||||
runNode: true,
|
||||
runWindows: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("routes SecretRef path-security changes and native fixtures to Windows", () => {
|
||||
for (const secretRefPath of [
|
||||
"src/commands/doctor-gateway-auth-token.ts",
|
||||
|
||||
@@ -221,6 +221,12 @@ describe("package scripts", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("runs mixed-case local media file URL coverage in Windows CI", () => {
|
||||
expect(readPackageJson().scripts["test:windows:ci"]).toContain(
|
||||
"src/media/local-media-path.windows.test.ts",
|
||||
);
|
||||
});
|
||||
|
||||
it("runs the native OpenSSH resolver proof in Windows CI", () => {
|
||||
expect(readPackageJson().scripts["test:windows:ci"]).toContain(
|
||||
"src/infra/ssh-client.windows.test.ts",
|
||||
@@ -312,4 +318,12 @@ describe("package scripts", () => {
|
||||
"src/infra/fs-safe-remove.test.ts",
|
||||
);
|
||||
});
|
||||
|
||||
it("runs web and Teams file URL coverage in Windows CI", () => {
|
||||
const script = readPackageJson().scripts["test:windows:ci"];
|
||||
|
||||
expect(script).toContain("src/media/web-media.file-url.windows.test.ts");
|
||||
expect(script).toContain("extensions/msteams/src/media-helpers.test.ts");
|
||||
expect(script).toContain("extensions/msteams/src/messenger.test.ts");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user