


























@@ -50,23 +50,35 @@ function withConfig(fileTransfer: Record<string, unknown> | undefined) {
5050}
5151}
525253+function expectResultFields(result: unknown, fields: Record<string, unknown>) {
54+expect(typeof result).toBe("object");
55+expect(result).not.toBeNull();
56+if (typeof result !== "object" || result === null) {
57+throw new Error("policy result was not an object");
58+}
59+const record = result as Record<string, unknown>;
60+for (const [key, value] of Object.entries(fields)) {
61+expect(record[key]).toEqual(value);
62+}
63+}
64+5365describe("evaluateFilePolicy — default deny", () => {
5466it("returns NO_POLICY when no plugin config block is present", () => {
5567getRuntimeConfigMock.mockReturnValue({});
5668const r = evaluateFilePolicy({ nodeId: "n1", kind: "read", path: "/tmp/x" });
57-expect(r).toMatchObject({ ok: false, code: "NO_POLICY", askable: false });
69+expectResultFields(r, { ok: false, code: "NO_POLICY", askable: false });
5870});
59716072it("returns NO_POLICY when plugin policy block is missing", () => {
6173getRuntimeConfigMock.mockReturnValue({ plugins: { entries: { "file-transfer": {} } } });
6274const r = evaluateFilePolicy({ nodeId: "n1", kind: "read", path: "/tmp/x" });
63-expect(r).toMatchObject({ ok: false, code: "NO_POLICY" });
75+expectResultFields(r, { ok: false, code: "NO_POLICY" });
6476});
65776678it("returns NO_POLICY when no entry exists for the node and no '*' fallback", () => {
6779withConfig({ "other-node": { allowReadPaths: ["/tmp/**"] } });
6880const r = evaluateFilePolicy({ nodeId: "n1", kind: "read", path: "/tmp/x" });
69-expect(r).toMatchObject({ ok: false, code: "NO_POLICY" });
81+expectResultFields(r, { ok: false, code: "NO_POLICY" });
7082});
71837284it("prefers the current runtime config over a stale passed plugin config", () => {
@@ -93,7 +105,7 @@ describe("evaluateFilePolicy — default deny", () => {
93105},
94106},
95107});
96-expect(r).toMatchObject({ ok: true, reason: "matched-allow" });
108+expectResultFields(r, { ok: true, reason: "matched-allow" });
97109});
98110});
99111@@ -107,7 +119,7 @@ describe("evaluateFilePolicy — '..' traversal short-circuit", () => {
107119kind: "read",
108120path: "/allowed/../etc/passwd",
109121});
110-expect(r).toMatchObject({ ok: false, code: "POLICY_DENIED", askable: false });
122+expectResultFields(r, { ok: false, code: "POLICY_DENIED", askable: false });
111123expect(r.ok ? "" : r.reason).toMatch(/\.\./);
112124});
113125@@ -120,15 +132,15 @@ describe("evaluateFilePolicy — '..' traversal short-circuit", () => {
120132kind: "read",
121133path: "/tmp/foo/..",
122134});
123-expect(r).toMatchObject({ ok: false, code: "POLICY_DENIED" });
135+expectResultFields(r, { ok: false, code: "POLICY_DENIED" });
124136});
125137126138it("rejects bare '..'", () => {
127139withConfig({
128140n1: { allowReadPaths: ["/**"] },
129141});
130142const r = evaluateFilePolicy({ nodeId: "n1", kind: "read", path: ".." });
131-expect(r).toMatchObject({ ok: false, code: "POLICY_DENIED" });
143+expectResultFields(r, { ok: false, code: "POLICY_DENIED" });
132144});
133145});
134146@@ -145,7 +157,7 @@ describe("evaluateFilePolicy — denyPaths always wins", () => {
145157kind: "read",
146158path: "/tmp/.ssh/id_rsa",
147159});
148-expect(r).toMatchObject({ ok: false, code: "POLICY_DENIED", askable: false });
160+expectResultFields(r, { ok: false, code: "POLICY_DENIED", askable: false });
149161expect(r.ok ? "" : r.reason).toMatch(/deny/);
150162});
151163@@ -161,7 +173,7 @@ describe("evaluateFilePolicy — denyPaths always wins", () => {
161173kind: "read",
162174path: "/var/secrets/api.key",
163175});
164-expect(r).toMatchObject({ ok: false, code: "POLICY_DENIED", askable: false });
176+expectResultFields(r, { ok: false, code: "POLICY_DENIED", askable: false });
165177});
166178});
167179@@ -183,34 +195,35 @@ describe("evaluateFilePolicy — allow matching", () => {
183195n1: { allowReadPaths: ["/tmp/**"], maxBytes: 1024 },
184196});
185197const r = evaluateFilePolicy({ nodeId: "n1", kind: "read", path: "/tmp/x" });
186-expect(r).toMatchObject({ ok: true, maxBytes: 1024 });
198+expectResultFields(r, { ok: true, maxBytes: 1024 });
187199});
188200189201it("uses kind=write to consult allowWritePaths, not allowReadPaths", () => {
190202withConfig({
191203n1: { allowReadPaths: ["/tmp/**"], allowWritePaths: ["/srv/**"] },
192204});
193-expect(evaluateFilePolicy({ nodeId: "n1", kind: "write", path: "/srv/out.txt" })).toMatchObject(
194-{ ok: true },
195-);
196-expect(evaluateFilePolicy({ nodeId: "n1", kind: "write", path: "/tmp/out.txt" })).toMatchObject(
197-{ ok: false, code: "POLICY_DENIED" },
198-);
205+expectResultFields(evaluateFilePolicy({ nodeId: "n1", kind: "write", path: "/srv/out.txt" }), {
206+ok: true,
207+});
208+expectResultFields(evaluateFilePolicy({ nodeId: "n1", kind: "write", path: "/tmp/out.txt" }), {
209+ok: false,
210+code: "POLICY_DENIED",
211+});
199212});
200213201214it("propagates followSymlinks=false by default and =true when configured", () => {
202215withConfig({
203216n1: { allowReadPaths: ["/tmp/**"] },
204217});
205-expect(evaluateFilePolicy({ nodeId: "n1", kind: "read", path: "/tmp/x" })).toMatchObject({
218+expectResultFields(evaluateFilePolicy({ nodeId: "n1", kind: "read", path: "/tmp/x" }), {
206219ok: true,
207220followSymlinks: false,
208221});
209222210223withConfig({
211224n2: { allowReadPaths: ["/tmp/**"], followSymlinks: true },
212225});
213-expect(evaluateFilePolicy({ nodeId: "n2", kind: "read", path: "/tmp/x" })).toMatchObject({
226+expectResultFields(evaluateFilePolicy({ nodeId: "n2", kind: "read", path: "/tmp/x" }), {
214227ok: true,
215228followSymlinks: true,
216229});
@@ -221,26 +234,28 @@ describe("evaluateFilePolicy — allow matching", () => {
221234withConfig({
222235n1: { allowReadPaths: ["~/Screenshots/**"] },
223236});
224-expect(
237+expectResultFields(
225238evaluateFilePolicy({
226239nodeId: "n1",
227240kind: "read",
228241path: path.join(home, "Screenshots", "shot.png"),
229242}),
230-).toMatchObject({ ok: true });
243+{ ok: true },
244+);
231245});
232246233247it("matches Windows node paths without gateway-local path semantics", () => {
234248withConfig({
235249n1: { allowReadPaths: ["C:/Users/me/**"] },
236250});
237-expect(
251+expectResultFields(
238252evaluateFilePolicy({
239253nodeId: "n1",
240254kind: "read",
241255path: "C:\\Users\\me\\file.txt",
242256}),
243-).toMatchObject({ ok: true });
257+{ ok: true },
258+);
244259});
245260});
246261@@ -250,7 +265,7 @@ describe("evaluateFilePolicy — ask modes", () => {
250265n1: { ask: "on-miss", allowReadPaths: ["/var/log/**"] },
251266});
252267const r = evaluateFilePolicy({ nodeId: "n1", kind: "read", path: "/tmp/x" });
253-expect(r).toMatchObject({
268+expectResultFields(r, {
254269ok: false,
255270code: "POLICY_DENIED",
256271askable: true,
@@ -268,7 +283,7 @@ describe("evaluateFilePolicy — ask modes", () => {
268283},
269284});
270285const r = evaluateFilePolicy({ nodeId: "n1", kind: "read", path: "/tmp/x" });
271-expect(r).toMatchObject({
286+expectResultFields(r, {
272287ok: false,
273288code: "POLICY_DENIED",
274289askable: true,
@@ -283,31 +298,31 @@ describe("evaluateFilePolicy — ask modes", () => {
283298n1: { ask: "on-miss", allowReadPaths: ["/tmp/**"] },
284299});
285300const r = evaluateFilePolicy({ nodeId: "n1", kind: "read", path: "/tmp/x" });
286-expect(r).toMatchObject({ ok: true, reason: "matched-allow" });
301+expectResultFields(r, { ok: true, reason: "matched-allow" });
287302});
288303289304it("ask=always always returns ask-always (prompt on every call)", () => {
290305withConfig({
291306n1: { ask: "always", allowReadPaths: ["/tmp/**"] },
292307});
293308const r = evaluateFilePolicy({ nodeId: "n1", kind: "read", path: "/tmp/x" });
294-expect(r).toMatchObject({ ok: true, reason: "ask-always", askMode: "always" });
309+expectResultFields(r, { ok: true, reason: "ask-always", askMode: "always" });
295310});
296311297312it("ask=off returns non-askable POLICY_DENIED on miss", () => {
298313withConfig({
299314n1: { ask: "off", allowReadPaths: ["/var/log/**"] },
300315});
301316const r = evaluateFilePolicy({ nodeId: "n1", kind: "read", path: "/tmp/x" });
302-expect(r).toMatchObject({ ok: false, code: "POLICY_DENIED", askable: false });
317+expectResultFields(r, { ok: false, code: "POLICY_DENIED", askable: false });
303318});
304319305320it("invalid ask values normalize to off", () => {
306321withConfig({
307322n1: { ask: "sometimes", allowReadPaths: ["/var/log/**"] },
308323});
309324const r = evaluateFilePolicy({ nodeId: "n1", kind: "read", path: "/tmp/x" });
310-expect(r).toMatchObject({ ok: false, askable: false });
325+expectResultFields(r, { ok: false, askable: false });
311326});
312327});
313328@@ -316,28 +331,30 @@ describe("evaluateFilePolicy — node-id resolution", () => {
316331withConfig({
317332"Lobster MacBook": { allowReadPaths: ["/tmp/**"] },
318333});
319-expect(
334+expectResultFields(
320335evaluateFilePolicy({
321336nodeId: "node-abc-123",
322337nodeDisplayName: "Lobster MacBook",
323338kind: "read",
324339path: "/tmp/x",
325340}),
326-).toMatchObject({ ok: true });
341+{ ok: true },
342+);
327343});
328344329345it("falls back to '*' wildcard when neither id nor displayName matches", () => {
330346withConfig({
331347"*": { allowReadPaths: ["/tmp/**"] },
332348});
333-expect(
349+expectResultFields(
334350evaluateFilePolicy({
335351nodeId: "n1",
336352nodeDisplayName: "anything",
337353kind: "read",
338354path: "/tmp/x",
339355}),
340-).toMatchObject({ ok: true });
356+{ ok: true },
357+);
341358});
342359});
343360此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。