fix(marketplace): preserve release sync semantics
This commit is contained in:
@@ -374,7 +374,7 @@ describe('Marketplace client and account cache', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('derives stable resolve identity from one logical request and changes it when installed state changes', async () => {
|
||||
it('creates a new resolve identity per logical sync and preserves an explicit replay identity', async () => {
|
||||
const fetcher = vi.fn<typeof fetch>().mockImplementation(async (_input, init) => {
|
||||
const request = JSON.parse(init?.body as string) as Record<string, unknown>;
|
||||
return response({
|
||||
@@ -400,9 +400,40 @@ describe('Marketplace client and account cache', () => {
|
||||
await client.resolve(base);
|
||||
const firstId = requestBody(fetcher, 0).resolve_request_id;
|
||||
const secondId = requestBody(fetcher, 1).resolve_request_id;
|
||||
expect(firstId).toBe(secondId);
|
||||
await client.resolve({ ...base, installed: [{ pluginId: PLUGIN_ID, releaseId: RELEASE_ID, sha256: SHA256 }] });
|
||||
expect(requestBody(fetcher, 2).resolve_request_id).not.toBe(firstId);
|
||||
expect(secondId).not.toBe(firstId);
|
||||
expect(requestBody(fetcher, 1).resolve_request_digest).toBe(requestBody(fetcher, 0).resolve_request_digest);
|
||||
|
||||
const replay = { ...base, resolveRequestId: 'makelore-resolve-logical-sync-a' };
|
||||
await client.resolve(replay);
|
||||
await client.resolve(replay);
|
||||
expect(requestBody(fetcher, 2).resolve_request_id).toBe('makelore-resolve-logical-sync-a');
|
||||
expect(requestBody(fetcher, 3).resolve_request_id).toBe('makelore-resolve-logical-sync-a');
|
||||
});
|
||||
|
||||
it('keeps one generated resolve identity across the authenticated retry of a logical sync', async () => {
|
||||
const fetcher = vi.fn<typeof fetch>()
|
||||
.mockResolvedValueOnce(new Response('', { status: 401 }))
|
||||
.mockImplementationOnce(async (_input, init) => {
|
||||
const request = JSON.parse(init?.body as string) as Record<string, unknown>;
|
||||
return response({
|
||||
resolve_request_id: request.resolve_request_id,
|
||||
resolve_request_digest: request.resolve_request_digest,
|
||||
items: [],
|
||||
catalog_generation: 7,
|
||||
}, {}, { ETag: '"plugins-7-tp-none"' });
|
||||
});
|
||||
const client = createMarketplaceClient({
|
||||
fetchImpl: fetcher,
|
||||
apiBaseUrl: 'https://square.example',
|
||||
getAccessToken: async ({ forceRefresh } = {}) => forceRefresh ? 'refreshed-token' : 'initial-token',
|
||||
getAccountBinding: () => ACCOUNT_A,
|
||||
subscribeSession: () => () => undefined,
|
||||
});
|
||||
|
||||
await client.resolve({ makeloreVersion: '1.0.0', channel: 'stable', installed: [] });
|
||||
|
||||
expect(requestBody(fetcher, 1).resolve_request_id).toBe(requestBody(fetcher, 0).resolve_request_id);
|
||||
expect(requestBody(fetcher, 1).resolve_request_digest).toBe(requestBody(fetcher, 0).resolve_request_digest);
|
||||
});
|
||||
|
||||
it('rejects a response body above the bounded DTO limit', async () => {
|
||||
@@ -505,6 +536,38 @@ describe('PluginPackageStore', () => {
|
||||
expect(issueDownload).toHaveBeenCalledWith({ releaseId: RELEASE_ID, releaseAdmissionId: ADMISSION_ID });
|
||||
});
|
||||
|
||||
it('assigns a distinct logical resolve identity to each new Package Store sync', async () => {
|
||||
temporaryRoot = await mkdtemp(path.join(process.cwd(), '.marketplace-test-'));
|
||||
const archive = buildSkillOnlyArchive();
|
||||
const { grant, publicKey } = signedGrant(archive);
|
||||
const resolve = vi.fn(async (input: ResolveRequest) => makeResolveResult(input, {
|
||||
sha256: grant.sha256,
|
||||
sizeBytes: grant.sizeBytes,
|
||||
}));
|
||||
const marketplace: MarketplaceClient = {
|
||||
resolve,
|
||||
issueDownload: vi.fn(async () => grant),
|
||||
downloadContent: async () => archive,
|
||||
getCurrentAccountBinding: () => ACCOUNT_A,
|
||||
} as MarketplaceClient;
|
||||
const store = new PluginPackageStore({
|
||||
rootDir: temporaryRoot,
|
||||
marketplace,
|
||||
clientVersion: '1.0.0',
|
||||
keyStore: new Map([['test-key', publicKey]]),
|
||||
getAccountBinding: () => ACCOUNT_A,
|
||||
});
|
||||
|
||||
await store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' });
|
||||
await store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' });
|
||||
|
||||
const first = resolve.mock.calls[0]?.[0].resolveRequestId;
|
||||
const second = resolve.mock.calls[1]?.[0].resolveRequestId;
|
||||
expect(first).toMatch(/^makelore-resolve-/u);
|
||||
expect(second).toMatch(/^makelore-resolve-/u);
|
||||
expect(second).not.toBe(first);
|
||||
});
|
||||
|
||||
it('removes only releases with no account or active-worker reference', async () => {
|
||||
temporaryRoot = await mkdtemp(path.join(process.cwd(), '.marketplace-test-'));
|
||||
const archive = buildSkillOnlyArchive();
|
||||
|
||||
Reference in New Issue
Block a user