fix: close marketplace client review findings

This commit is contained in:
2026-08-28 21:01:51 +08:00
parent 8dfa542860
commit 1614f7efc1
25 changed files with 1224 additions and 143 deletions

View File

@@ -1,7 +1,7 @@
// @vitest-environment node
import { createHash, generateKeyPairSync, sign } from 'node:crypto';
import { mkdtemp, readFile, rm } from 'node:fs/promises';
import { mkdtemp, readFile, rm, stat } from 'node:fs/promises';
import path from 'node:path';
import AdmZip from 'adm-zip';
import { afterEach, describe, expect, it, vi } from 'vitest';
@@ -117,6 +117,7 @@ function signedGrant(
archive: Buffer,
options: {
readonly releaseId?: string;
readonly signingKeyId?: string;
readonly minMakeloreVersion?: string;
readonly maxMakeloreVersion?: string | null;
} = {},
@@ -146,7 +147,7 @@ function signedGrant(
maxMakeloreVersion: options.maxMakeloreVersion ?? null,
sizeBytes: archive.byteLength,
sha256,
signingKeyId: 'test-key',
signingKeyId: options.signingKeyId ?? 'test-key',
descriptorSignature: signature,
expiresAt: '2026-08-29T00:00:00Z',
contentUrl: `/api/plugin-marketplace/v1/releases/${releaseId}/content?release_admission_id=${ADMISSION_ID}`,
@@ -321,6 +322,36 @@ describe('Marketplace client and account cache', () => {
await expect(client.readCatalog({ limit: 10 })).rejects.toMatchObject({ code: 'marketplace_response_too_large' });
});
it('applies one deadline to response headers and a body that never completes', async () => {
const neverBody = new ReadableStream<Uint8Array>({ start() { /* intentionally never closes */ } });
const fetcher = vi.fn<typeof fetch>().mockResolvedValue(new Response(neverBody, { status: 200 }));
const client = createMarketplaceClient({
fetchImpl: fetcher,
apiBaseUrl: 'https://square.example',
requestTimeoutMs: 20,
getAccessToken: async () => null,
subscribeSession: () => () => undefined,
});
await expect(client.readCatalog({ limit: 10 })).rejects.toMatchObject({ code: 'marketplace_request_failed' });
});
it('preserves a bounded server release status from the response body', async () => {
const fetcher = vi.fn<typeof fetch>().mockResolvedValue(response({
success: false,
code: 'plugin_release_yanked',
error: 'Release is no longer available',
}, { status: 409 }));
const client = createMarketplaceClient({
fetchImpl: fetcher,
apiBaseUrl: 'https://square.example',
getAccessToken: async () => null,
subscribeSession: () => () => undefined,
});
await expect(client.readCatalog({ limit: 10 })).rejects.toMatchObject({
code: 'plugin_release_yanked', status: 409,
});
});
it('rejects a malformed authenticated response with a stable client error', async () => {
const fetcher = vi.fn<typeof fetch>().mockResolvedValue(response({ items: [] }));
const client = createMarketplaceClient({
@@ -471,6 +502,92 @@ describe('PluginPackageStore', () => {
await expect(failingStore.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' }))
.rejects.toMatchObject({ code: 'plugin_install_failed' });
await expect(failingStore.getInstalled(PLUGIN_ID)).resolves.toMatchObject({ releaseId: RELEASE_ID });
await expect(stat(path.join(temporaryRoot, 'packages', PLUGIN_ID, 'release-2')))
.resolves.toMatchObject({ isDirectory: expect.any(Function) });
const recoveredStore = new PluginPackageStore({
rootDir: temporaryRoot,
marketplace: replacementMarketplace,
clientVersion: '1.0.0',
keyStore: new Map([['test-key', replacement.publicKey]]),
getAccountBinding: () => ACCOUNT_A,
});
await expect(recoveredStore.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' }))
.resolves.toMatchObject({ status: 'installed', releaseId: 'release-2' });
await expect(recoveredStore.getInstalled(PLUGIN_ID)).resolves.toMatchObject({ releaseId: 'release-2' });
});
it('uninstalls a device package without removing the account Library snapshot', async () => {
temporaryRoot = await mkdtemp(path.join(process.cwd(), '.marketplace-test-'));
const archive = buildSkillOnlyArchive();
const { grant, publicKey } = signedGrant(archive);
const accountCache = new AccountPluginCache();
const library: MarketplaceLibrarySnapshot = {
items: [{
pluginId: PLUGIN_ID, title: 'Example', summary: 'Example', category: 'tools',
acquisition: 'free', acquisitionMode: 'user_acquired', catalogStatus: 'active',
runtimeStatus: 'enabled', acquiredAt: '2026-08-28T00:00:00Z', removedAt: null,
stableVersion: '1.0.0', betaVersion: null,
}], total: 1, stale: false, fetchedAt: 1,
};
const marketplace: MarketplaceClient = {
resolve: vi.fn(async (input: ResolveRequest) => makeResolveResult(input, {
sha256: grant.sha256, sizeBytes: grant.sizeBytes,
})),
issueDownload: vi.fn(async () => grant),
downloadContent: async () => archive,
readLibrary: vi.fn(async () => library),
getCurrentAccountBinding: () => ACCOUNT_A,
} as MarketplaceClient;
const store = new PluginPackageStore({
rootDir: temporaryRoot, marketplace, accountCache, getAccountBinding: () => ACCOUNT_A,
keyStore: new Map([['test-key', publicKey]]), clientVersion: '1.0.0',
});
await store.syncLibrary();
await store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' });
await expect(store.uninstall(PLUGIN_ID)).resolves.toMatchObject({ status: 'removed', pluginId: PLUGIN_ID });
expect(accountCache.getLibrary(ACCOUNT_A)).toEqual(library);
expect(accountCache.referencedReleaseIds()).toEqual(new Set());
await expect(store.getInstalled(PLUGIN_ID)).resolves.toBeNull();
});
it('makes a cached rollback the Package Store current selection while retaining both immutable releases', async () => {
temporaryRoot = await mkdtemp(path.join(process.cwd(), '.marketplace-test-'));
const archive = buildSkillOnlyArchive();
const first = signedGrant(archive, { releaseId: 'release-1' });
const second = signedGrant(archive, { releaseId: 'release-2', signingKeyId: 'test-key-2' });
let current = second;
const marketplace: MarketplaceClient = {
resolve: vi.fn(async (input: ResolveRequest) => makeResolveResult(input, {
releaseId: current.grant.releaseId,
sha256: current.grant.sha256,
sizeBytes: current.grant.sizeBytes,
})),
issueDownload: vi.fn(async () => current.grant),
downloadContent: async () => archive,
getCurrentAccountBinding: () => ACCOUNT_A,
} as MarketplaceClient;
const store = new PluginPackageStore({
rootDir: temporaryRoot, marketplace, getAccountBinding: () => ACCOUNT_A,
keyStore: new Map([['test-key', first.publicKey], ['test-key-2', second.publicKey]]),
clientVersion: '1.0.0', now: (() => { let value = 1; return () => value++ * 1_000; })(),
});
await store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' });
current = first;
await store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' });
await expect(store.readInstalledIndex()).resolves.toHaveLength(2);
await expect(store.getInstalled(PLUGIN_ID)).resolves.toMatchObject({ releaseId: 'release-1' });
const index = JSON.parse(await readFile(path.join(temporaryRoot, 'index.json'), 'utf8')) as {
releases: Array<{ release_id: string }>;
};
expect(index.releases.map(({ release_id }) => release_id)).toEqual(['release-2', 'release-1']);
store.registerActiveWorker('release-1');
await expect(store.uninstall(PLUGIN_ID)).resolves.toMatchObject({
status: 'removed', pluginId: PLUGIN_ID, releaseId: 'release-1', version: '1.0.0',
});
await expect(store.readInstalledIndex()).resolves.toHaveLength(1);
await expect(store.getInstalled(PLUGIN_ID)).resolves.toMatchObject({ releaseId: 'release-1' });
});
it('preserves the old release across download, signature, and extraction failures', async () => {
@@ -664,4 +781,34 @@ describe('PluginPackageStore', () => {
await expect(store.resolveAndInstall({ pluginId: PLUGIN_ID, channel: 'beta', makeloreVersion: '1.0.0' }))
.rejects.toMatchObject({ code: 'plugin_beta_selection_required' });
});
it.each([
'plugin_release_yanked',
'plugin_incompatible_client',
'plugin_signature_invalid',
] as const)('preserves bounded resolve unavailable code %s for the UI projection', async (reason) => {
temporaryRoot = await mkdtemp(path.join(process.cwd(), '.marketplace-test-'));
const marketplace: MarketplaceClient = {
resolve: vi.fn(async (input: ResolveRequest) => makeResolveResult(input, {
action: 'unavailable',
releaseId: null,
version: null,
sha256: null,
sizeBytes: null,
releaseAdmissionId: null,
reason,
})),
issueDownload: vi.fn(),
downloadContent: vi.fn(),
getCurrentAccountBinding: () => ACCOUNT_A,
} as MarketplaceClient;
const store = new PluginPackageStore({
rootDir: temporaryRoot,
marketplace,
getAccountBinding: () => ACCOUNT_A,
clientVersion: '1.0.0',
});
await expect(store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' }))
.rejects.toMatchObject({ code: reason });
});
});