mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-05 09:31:27 +00:00
test(qa): cover ClawHub package compatibility (#118985)
This commit is contained in:
25
qa/scenarios/plugins/clawhub-package-compatibility.yaml
Normal file
25
qa/scenarios/plugins/clawhub-package-compatibility.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
title: ClawHub package compatibility validation
|
||||
|
||||
scenario:
|
||||
id: clawhub-package-compatibility
|
||||
surface: clawhub
|
||||
category: clawhub.compatibility-and-trust
|
||||
coverage:
|
||||
primary:
|
||||
- clawhub.clawhub-package-compatibility-validation
|
||||
objective: Verify ClawHub plugin compatibility metadata is enforced before trust checks or artifact download.
|
||||
successCriteria:
|
||||
- Incompatible package-level plugin API metadata fails visibly before trust checks or download.
|
||||
- Incompatible version-level minimum Gateway metadata fails visibly before trust checks or download.
|
||||
- Rejected installs leave no install record or telemetry.
|
||||
- Compatible metadata downloads and installs the plugin, persists its install record, and reports telemetry.
|
||||
docsRefs:
|
||||
- docs/cli/plugins.md
|
||||
- docs/plugins/manifest.md
|
||||
codeRefs:
|
||||
- src/cli/plugins-cli.clawhub-install.e2e.test.ts
|
||||
- src/plugins/clawhub.ts
|
||||
execution:
|
||||
kind: vitest
|
||||
path: src/cli/plugins-cli.clawhub-install.e2e.test.ts
|
||||
summary: Runs real CLI installs against a local ClawHub HTTP fixture and checks compatibility rejection ordering plus compatible install side effects.
|
||||
@@ -6,13 +6,16 @@ import type { AddressInfo } from "node:net";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import JSZip from "jszip";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { useAutoCleanupTempDirTracker } from "../../test/helpers/temp-dir.js";
|
||||
import { loadInstalledPluginIndexInstallRecords } from "../plugins/installed-plugin-index-records.js";
|
||||
|
||||
const PACKAGE_NAME = "@openclaw/telemetry-demo";
|
||||
const PACKAGE_VERSION = "1.0.0";
|
||||
const PLUGIN_ID = "telemetry-demo";
|
||||
const ENCODED_PACKAGE_NAME = encodeURIComponent(PACKAGE_NAME);
|
||||
const PACKAGE_API_PATH = `/api/v1/packages/${ENCODED_PACKAGE_NAME}`;
|
||||
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
|
||||
|
||||
async function readRequestBody(req: IncomingMessage): Promise<string> {
|
||||
const chunks: Buffer[] = [];
|
||||
@@ -71,6 +74,8 @@ async function buildPluginZip(): Promise<Buffer> {
|
||||
|
||||
type TestServerOptions = {
|
||||
artifactSha256?: string;
|
||||
artifactCompatibility?: Record<string, string> | null;
|
||||
packageCompatibility?: Record<string, string>;
|
||||
telemetryStatus?: number;
|
||||
};
|
||||
|
||||
@@ -84,7 +89,7 @@ async function startClawHubServer(options: TestServerOptions = {}) {
|
||||
async function handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {
|
||||
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
||||
requestLog.push(`${req.method ?? "GET"} ${url.pathname}`);
|
||||
const packagePath = `/api/v1/packages/${ENCODED_PACKAGE_NAME}`;
|
||||
const packagePath = PACKAGE_API_PATH;
|
||||
|
||||
if (req.method === "GET" && url.pathname === packagePath) {
|
||||
res.writeHead(200, { "Content-Type": "application/json" });
|
||||
@@ -99,7 +104,7 @@ async function startClawHubServer(options: TestServerOptions = {}) {
|
||||
isOfficial: false,
|
||||
latestVersion: PACKAGE_VERSION,
|
||||
tags: { latest: PACKAGE_VERSION },
|
||||
compatibility: {},
|
||||
compatibility: options.packageCompatibility ?? {},
|
||||
},
|
||||
owner: { handle: "openclaw" },
|
||||
}),
|
||||
@@ -124,7 +129,9 @@ async function startClawHubServer(options: TestServerOptions = {}) {
|
||||
createdAt: 1,
|
||||
changelog: "Initial release",
|
||||
sha256hash: artifactSha256,
|
||||
compatibility: {},
|
||||
...(options.artifactCompatibility === null
|
||||
? {}
|
||||
: { compatibility: options.artifactCompatibility ?? {} }),
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -257,6 +264,10 @@ describe("openclaw plugins install ClawHub E2E", () => {
|
||||
version: PACKAGE_VERSION,
|
||||
},
|
||||
]);
|
||||
expect(testServer.requestLog).toContain(
|
||||
`GET ${PACKAGE_API_PATH}/versions/${PACKAGE_VERSION}/security`,
|
||||
);
|
||||
expect(testServer.requestLog).toContain(`GET ${PACKAGE_API_PATH}/download`);
|
||||
|
||||
const repeat = await spawnOpenClaw(
|
||||
["plugins", "install", `clawhub:${PACKAGE_NAME}@${PACKAGE_VERSION}`, "--force"],
|
||||
@@ -271,6 +282,46 @@ describe("openclaw plugins install ClawHub E2E", () => {
|
||||
}
|
||||
}, 60_000);
|
||||
|
||||
it.each([
|
||||
{
|
||||
label: "package plugin API",
|
||||
options: {
|
||||
packageCompatibility: { pluginApiRange: ">=9999.0.0" },
|
||||
artifactCompatibility: null,
|
||||
},
|
||||
error: "requires plugin API >=9999.0.0",
|
||||
},
|
||||
{
|
||||
label: "version gateway",
|
||||
options: { artifactCompatibility: { minGatewayVersion: "9999.0.0" } },
|
||||
error: "requires OpenClaw >=9999.0.0",
|
||||
},
|
||||
])(
|
||||
"rejects incompatible $label metadata before trust and download",
|
||||
async ({ options, error }) => {
|
||||
const testServer = await startClawHubServer(options);
|
||||
const stateDir = tempDirs.make("openclaw-plugin-compatibility-e2e-");
|
||||
try {
|
||||
const result = await spawnOpenClaw(
|
||||
["plugins", "install", `clawhub:${PACKAGE_NAME}@${PACKAGE_VERSION}`],
|
||||
{ cwd: process.cwd(), env: buildEnv(stateDir, testServer.registry) },
|
||||
);
|
||||
|
||||
expect(result.status).not.toBe(0);
|
||||
expect(`${result.stdout}\n${result.stderr}`).toContain(error);
|
||||
expect(testServer.requestLog).not.toContain(
|
||||
`GET ${PACKAGE_API_PATH}/versions/${PACKAGE_VERSION}/security`,
|
||||
);
|
||||
expect(testServer.requestLog).not.toContain(`GET ${PACKAGE_API_PATH}/download`);
|
||||
expect(testServer.telemetryBodies).toEqual([]);
|
||||
await expect(readPersistedInstallRecord(stateDir)).resolves.toBeUndefined();
|
||||
} finally {
|
||||
await testServer.close();
|
||||
}
|
||||
},
|
||||
30_000,
|
||||
);
|
||||
|
||||
it("does not report success when plugin installation fails", async () => {
|
||||
const testServer = await startClawHubServer({ artifactSha256: "0".repeat(64) });
|
||||
const stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-plugin-telemetry-fail-"));
|
||||
|
||||
Reference in New Issue
Block a user