fix: close marketplace client review findings
This commit is contained in:
@@ -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 });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user