CI: stabilize bundle hook and path tests

This commit is contained in:
Alexander Davydov 2026-03-20 00:49:16 +03:00
parent 165deced34
commit 673b3f186c
5 changed files with 28 additions and 18 deletions

View File

@ -706,7 +706,6 @@
"file-type": "21.3.3",
"gaxios": "7.1.4",
"gigachat": "^0.0.18",
"grammy": "^1.41.1",
"hono": "4.12.8",
"ipaddr.js": "^2.3.0",
"jiti": "^2.6.1",
@ -741,6 +740,7 @@
"@types/ws": "^8.18.1",
"@typescript/native-preview": "7.0.0-dev.20260317.1",
"@vitest/coverage-v8": "^4.1.0",
"grammy": "^1.41.1",
"jscpd": "4.0.8",
"jsdom": "^29.0.0",
"lit": "^3.3.2",

6
pnpm-lock.yaml generated
View File

@ -101,9 +101,6 @@ importers:
gigachat:
specifier: ^0.0.18
version: 0.0.18
grammy:
specifier: ^1.41.1
version: 1.41.1
hono:
specifier: 4.12.8
version: 4.12.8
@ -204,6 +201,9 @@ importers:
'@vitest/coverage-v8':
specifier: ^4.1.0
version: 4.1.0(@vitest/browser@4.1.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.1.0))(vitest@4.1.0)
grammy:
specifier: ^1.41.1
version: 1.41.1
jscpd:
specifier: 4.0.8
version: 4.0.8

View File

@ -80,10 +80,17 @@ function loadHookFromDir(params: {
pluginId?: string;
nameHint?: string;
}): Hook | null {
const hookMdPath = path.join(params.hookDir, "HOOK.md");
let canonicalHookDir = path.resolve(params.hookDir);
try {
canonicalHookDir = fs.realpathSync.native(params.hookDir);
} catch {
// Fall back to the discovered path when realpath is unavailable.
}
const hookMdPath = path.join(canonicalHookDir, "HOOK.md");
const content = readBoundaryFileUtf8({
absolutePath: hookMdPath,
rootPath: params.hookDir,
rootPath: canonicalHookDir,
boundaryLabel: "hook directory",
});
if (content === null) {
@ -98,10 +105,10 @@ function loadHookFromDir(params: {
const handlerCandidates = ["handler.ts", "handler.js", "index.ts", "index.js"];
let handlerPath: string | undefined;
for (const candidate of handlerCandidates) {
const candidatePath = path.join(params.hookDir, candidate);
const candidatePath = path.join(canonicalHookDir, candidate);
const safeCandidatePath = resolveBoundaryFilePath({
absolutePath: candidatePath,
rootPath: params.hookDir,
rootPath: canonicalHookDir,
boundaryLabel: "hook directory",
});
if (safeCandidatePath) {
@ -115,20 +122,13 @@ function loadHookFromDir(params: {
return null;
}
let baseDir = params.hookDir;
try {
baseDir = fs.realpathSync.native(params.hookDir);
} catch {
// keep the discovered path when realpath is unavailable
}
return {
name,
description,
source: params.source,
pluginId: params.pluginId,
filePath: hookMdPath,
baseDir,
baseDir: canonicalHookDir,
handlerPath,
};
} catch (err) {

View File

@ -165,6 +165,10 @@ const LOCAL_EXTENSION_API_BARREL_EXCEPTIONS = [
// accounts.ts -> runtime-api.ts -> src/plugin-sdk/matrix -> extensions/matrix/api.ts -> accounts.ts
"extensions/matrix/src/matrix/accounts.ts",
] as const;
const CORE_GUARDRAIL_EXCEPTIONS = [
// Contract registries are test support, even though they live under src for co-located fixtures.
"src/channels/plugins/contracts/registry.ts",
] as const;
function readSource(path: string): string {
return readFileSync(resolve(ROOT_DIR, "..", path), "utf8");
@ -267,6 +271,7 @@ function collectCoreSourceFiles(): string[] {
normalizedFullPath.includes(".spec.") ||
normalizedFullPath.includes(".fixture.") ||
normalizedFullPath.includes(".snap") ||
CORE_GUARDRAIL_EXCEPTIONS.some((suffix) => normalizedFullPath.endsWith(`/${suffix}`)) ||
// src/plugin-sdk is the curated bridge layer; validate its contracts with dedicated
// plugin-sdk guardrails instead of the generic "core should not touch extensions" rule.
normalizedFullPath.includes(`${normalizedPluginSdkDir}/`)

View File

@ -6,11 +6,16 @@ import { captureEnv } from "../test-utils/env.js";
import { isRecord } from "../utils.js";
import { loadEnabledBundleMcpConfig } from "./bundle-mcp.js";
import { createBundleMcpTempHarness, createBundleProbePlugin } from "./bundle-mcp.test-support.js";
import { safeRealpathSync } from "./path-safety.js";
function getServerArgs(value: unknown): unknown[] | undefined {
return isRecord(value) && Array.isArray(value.args) ? value.args : undefined;
}
function canonicalizeExpectedBundlePath(value: string): string {
return safeRealpathSync(value) ?? path.resolve(value);
}
const tempHarness = createBundleMcpTempHarness();
afterEach(async () => {
@ -46,7 +51,7 @@ describe("loadEnabledBundleMcpConfig", () => {
const loadedServer = loaded.config.mcpServers.bundleProbe;
const loadedArgs = getServerArgs(loadedServer);
const loadedServerPath = typeof loadedArgs?.[0] === "string" ? loadedArgs[0] : undefined;
const resolvedPluginRoot = await fs.realpath(pluginRoot);
const resolvedPluginRoot = canonicalizeExpectedBundlePath(pluginRoot);
expect(loaded.diagnostics).toEqual([]);
expect(isRecord(loadedServer) ? loadedServer.command : undefined).toBe("node");
@ -178,7 +183,7 @@ describe("loadEnabledBundleMcpConfig", () => {
},
},
});
const resolvedPluginRoot = await fs.realpath(pluginRoot);
const resolvedPluginRoot = canonicalizeExpectedBundlePath(pluginRoot);
expect(loaded.diagnostics).toEqual([]);
expect(loaded.config.mcpServers.inlineProbe).toEqual({