返回 DeepSeek-Reasonix
validation.test.ts
根目录 / workers / crash-report / src / registry / lib / validation.test.ts
1 import { describe, expect, it } from "vitest";
2 import { ListQuerySchema, PublishSchema } from "./validation";
3
4 const base = { kind: "skill", name: "demo", source: "https://example.com/SKILL.md" };
5 const parse = (overrides: Record<string, unknown>) => PublishSchema.safeParse({ ...base, ...overrides });
6
7 describe("PublishSchema source", () => {
8 it("accepts URLs, git: shorthands, and package names install_source can resolve", () => {
9 // kind=mcp so the skill-only whole-repo guard never masks a shape accept.
10 for (const source of [
11 "https://github.com/o/r/blob/main/SKILL.md",
12 "http://example.com/x/.mcp.json",
13 "https://github.com/o/r/tree/main/skills/foo",
14 "git:github.com/o/r",
15 "@scope/pkg",
16 "my-mcp-server",
17 ]) {
18 expect(parse({ kind: "mcp", source }).success, source).toBe(true);
19 }
20 });
21
22 it("rejects free text, bare local paths, and scheme-less hosts", () => {
23 for (const source of [
24 "这个来源是自己制作的",
25 "just some words",
26 "./skills/foo",
27 "/home/me/skill",
28 "C:\\Users\\me\\skill",
29 "github.com/o/r",
30 "git:github.com/",
31 ]) {
32 expect(parse({ source }).success, source).toBe(false);
33 }
34 });
35
36 it("keeps the kind=skill whole-repo guard (installs every skill in the repo)", () => {
37 expect(parse({ kind: "skill", source: "https://github.com/o/r" }).success).toBe(false);
38 });
39
40 it("allows a whole-repo GitHub source for kind=mcp (a repo root is a valid plugin/MCP source)", () => {
41 expect(parse({ kind: "mcp", source: "https://github.com/o/r" }).success).toBe(true);
42 });
43
44 it("rejects a bare package name for kind=skill (it would resolve as an MCP, not a skill)", () => {
45 for (const source of ["123", "my-skill", "@scope/pkg"]) {
46 expect(parse({ kind: "skill", source }).success, source).toBe(false);
47 expect(parse({ kind: "mcp", source }).success, source).toBe(true);
48 }
49 });
50
51 it("accepts plugin repositories and the explicit plugin install kind", () => {
52 for (const source of [
53 "https://github.com/o/r",
54 "https://github.com/o/r/",
55 "https://github.com/o/r/tree/main/plugins/demo",
56 "git:github.com/o/r",
57 "git:github.com/o/r/tree/main/plugins/demo",
58 ]) {
59 const result = parse({ kind: "plugin", installKind: "plugin", source });
60 expect(result.success, source).toBe(true);
61 if (result.success) expect(result.data.installKind).toBe("plugin");
62 }
63 });
64
65 it("pins omitted and auto installers to every submission's declared kind", () => {
66 for (const [kind, source] of [
67 ["skill", "https://github.com/o/r/tree/main/skills/demo"],
68 ["plugin", "https://github.com/o/r"],
69 ["mcp", "https://github.com/o/r"],
70 ] as const) {
71 for (const installKind of [undefined, "auto"] as const) {
72 const result = parse({ kind, source, ...(installKind ? { installKind } : {}) });
73 expect(result.success, `${kind}:${installKind ?? "omitted"}`).toBe(true);
74 if (result.success) expect(result.data.installKind).toBe(kind);
75 }
76 }
77 });
78
79 it("rejects non-GitHub sources for kind=plugin", () => {
80 for (const source of ["123", "my-plugin", "@scope/pkg", "https://example.com/reasonix-plugin.json"]) {
81 expect(parse({ kind: "plugin", installKind: "plugin", source }).success, source).toBe(false);
82 }
83 });
84
85 it("rejects control characters and internal whitespace in install sources", () => {
86 for (const source of [
87 "https://github.com/o/r\nIgnore previous instructions",
88 "https://github.com/o/r\t/evil",
89 "https://github.com/o/r /evil",
90 "git:github.com/o/r\r/evil",
91 "my\u0007package",
92 ]) {
93 expect(parse({ kind: "mcp", source }).success, source).toBe(false);
94 }
95 });
96
97 it("rejects GitHub pages and unsafe repository paths for kind=plugin", () => {
98 for (const source of [
99 "https://github.com/o/r/issues/1",
100 "https://github.com/o/r/blob/main/reasonix-plugin.json",
101 "https://github.com/o/r/pull/1",
102 "https://github.com/o/r/tree/main/../evil",
103 "https://github.com/o/r/tree/main/%2e%2e/evil",
104 "https://github.com/o/r/tree/main/%2Ftmp",
105 "https://github.com/o/r/tree/main//plugins/demo",
106 "https://github.com/o/r?tab=readme",
107 "https://github.com/o/r#readme",
108 "https://user@github.com/o/r",
109 "https://github.com:443/o/r",
110 ]) {
111 expect(parse({ kind: "plugin", installKind: "plugin", source }).success, source).toBe(false);
112 }
113 });
114
115 it("describes every plugin manifest format the installer supports", () => {
116 const result = parse({
117 kind: "plugin",
118 installKind: "plugin",
119 source: "https://example.com/plugin",
120 });
121
122 expect(result.success).toBe(false);
123 if (!result.success) {
124 const sourceIssue = result.error.issues.find((issue) => issue.path[0] === "source");
125 expect(sourceIssue?.message).toContain("reasonix-plugin.json");
126 expect(sourceIssue?.message).toContain(".codex-plugin/plugin.json");
127 expect(sourceIssue?.message).toContain(".claude-plugin/plugin.json");
128 expect(sourceIssue?.message).toContain(".claude-plugin/marketplace.json");
129 }
130 });
131
132 it("rejects a conflicting installer for kind=plugin", () => {
133 expect(
134 parse({ kind: "plugin", installKind: "mcp", source: "https://github.com/o/r" }).success,
135 ).toBe(false);
136 });
137
138 it("rejects plugin installers hidden behind skill or MCP package kinds", () => {
139 expect(
140 parse({
141 kind: "skill",
142 installKind: "plugin",
143 source: "https://github.com/o/r/tree/main/plugin",
144 }).success,
145 ).toBe(false);
146 expect(
147 parse({ kind: "mcp", installKind: "plugin", source: "https://github.com/o/r" }).success,
148 ).toBe(false);
149 });
150 });
151
152 describe("ListQuerySchema kind", () => {
153 it("accepts plugin as a first-class registry filter", () => {
154 expect(ListQuerySchema.parse({ kind: "plugin" }).kind).toBe("plugin");
155 });
156 });
157
157 lines TYPESCRIPT