





















1+// Verifies marketplace feed and source profile config parsing.
2+import { describe, expect, it } from "vitest";
3+import { OpenClawSchema } from "./zod-schema.js";
4+5+function expectMarketplacesConfig(value: unknown) {
6+const result = OpenClawSchema.safeParse(value);
7+if (!result.success) {
8+throw new Error(JSON.stringify(result.error.issues, null, 2));
9+}
10+return result.data.marketplaces;
11+}
12+13+describe("OpenClawSchema marketplaces config", () => {
14+it("accepts hosted feed and local source profiles", () => {
15+const marketplaces = expectMarketplacesConfig({
16+marketplaces: {
17+feeds: {
18+"clawhub-public": {
19+url: "https://clawhub.ai/v1/feeds/plugins",
20+verification: { mode: "unsigned" },
21+},
22+acme: {
23+url: "https://packages.acme.example/openclaw/feed",
24+verification: { mode: "unsigned" },
25+},
26+},
27+sources: {
28+"public-clawhub": { type: "clawhub" },
29+"public-npm": { type: "npm" },
30+"acme-npm": { type: "npm" },
31+"acme-clawhub": { type: "clawhub" },
32+"acme-git": { type: "git" },
33+},
34+},
35+});
36+37+expect(marketplaces?.feeds?.acme.url).toBe("https://packages.acme.example/openclaw/feed");
38+expect(marketplaces?.sources?.["acme-git"].type).toBe("git");
39+});
40+41+it.each([
42+"http://packages.acme.example/openclaw/feed",
43+"https://token@packages.acme.example/openclaw/feed",
44+"https://user:pass@packages.acme.example/openclaw/feed",
45+"https://packages.acme.example/openclaw/feed?token=secret",
46+"https://packages.acme.example/openclaw/feed#access-token",
47+"not a url",
48+])("rejects invalid or auth-bearing hosted feed URL %s without throwing", (url) => {
49+expect(() =>
50+OpenClawSchema.safeParse({
51+marketplaces: {
52+feeds: { acme: { url } },
53+},
54+}),
55+).not.toThrow();
56+const result = OpenClawSchema.safeParse({
57+marketplaces: {
58+feeds: { acme: { url } },
59+},
60+});
61+62+expect(result.success).toBe(false);
63+if (!result.success) {
64+expect(result.error.issues.map((issue) => issue.path.join("."))).toContain(
65+"marketplaces.feeds.acme.url",
66+);
67+}
68+});
69+70+it("rejects refresh, auth, and signed verification until loader enforcement exists", () => {
71+expect(
72+OpenClawSchema.safeParse({
73+marketplaces: {
74+feeds: {
75+acme: {
76+url: "https://packages.acme.example/openclaw/feed",
77+auth: { scheme: "bearer", secret: "token" },
78+},
79+},
80+},
81+}).success,
82+).toBe(false);
83+expect(
84+OpenClawSchema.safeParse({
85+marketplaces: {
86+feeds: {
87+acme: {
88+url: "https://packages.acme.example/openclaw/feed",
89+refresh: { onStartup: "if-stale" },
90+},
91+},
92+},
93+}).success,
94+).toBe(false);
95+expect(
96+OpenClawSchema.safeParse({
97+marketplaces: {
98+feeds: {
99+acme: {
100+url: "https://packages.acme.example/openclaw/feed",
101+verification: { mode: "signed" },
102+},
103+},
104+},
105+}).success,
106+).toBe(false);
107+});
108+109+it("rejects unknown source profile types", () => {
110+const result = OpenClawSchema.safeParse({
111+marketplaces: {
112+sources: { acme: { type: "container" } },
113+},
114+});
115+116+expect(result.success).toBe(false);
117+});
118+119+it("rejects source endpoints until installer resolution can enforce them", () => {
120+const result = OpenClawSchema.safeParse({
121+marketplaces: {
122+sources: {
123+"acme-npm": { type: "npm", registry: "https://packages.acme.example/npm/" },
124+"acme-clawhub": { type: "clawhub", baseUrl: "https://packages.acme.example/clawhub/" },
125+},
126+},
127+});
128+129+expect(result.success).toBe(false);
130+});
131+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。