192 lines
6.7 KiB
TypeScript
192 lines
6.7 KiB
TypeScript
// @vitest-environment node
|
|
|
|
import { describe, expect, it, vi } from 'vitest';
|
|
import {
|
|
MarketplaceHostedAdmissionError,
|
|
MarketplaceHostedAdmissionResolver,
|
|
} from '../../electron/coding-plugins/hosted-admission';
|
|
import type { MarketplacePackageClientPort, PluginPackageStore } from '../../electron/coding-plugins/package-store';
|
|
|
|
const PLUGIN_ID = 'makelore.game-resource';
|
|
const RELEASE_ID = 'release-game-1';
|
|
const REQUEST_ID = 'pi:run-a:resource-a';
|
|
const DIGEST = 'a'.repeat(64);
|
|
|
|
function installed() {
|
|
return {
|
|
pluginId: PLUGIN_ID,
|
|
releaseId: RELEASE_ID,
|
|
version: '1.0.0',
|
|
packageSchemaVersion: 2,
|
|
contractVersion: 1,
|
|
runtimeKind: 'platform_hosted' as const,
|
|
sha256: DIGEST,
|
|
sizeBytes: 1,
|
|
installedAt: '2026-09-01T00:00:00.000Z',
|
|
channel: 'stable' as const,
|
|
};
|
|
}
|
|
|
|
function resolved(overrides: Record<string, unknown> = {}) {
|
|
return {
|
|
resolveRequestId: REQUEST_ID,
|
|
resolveRequestDigest: DIGEST,
|
|
items: [{
|
|
pluginId: PLUGIN_ID,
|
|
action: 'keep' as const,
|
|
releaseId: RELEASE_ID,
|
|
version: '1.0.0',
|
|
sha256: DIGEST,
|
|
sizeBytes: 1,
|
|
releaseAdmissionId: 'admission-game-1',
|
|
expiresAt: '2100-01-01T00:00:00Z',
|
|
channel: 'stable' as const,
|
|
reason: null,
|
|
...overrides,
|
|
}],
|
|
catalogGeneration: 1,
|
|
etag: '"plugins-1-tp-free"',
|
|
stale: false,
|
|
};
|
|
}
|
|
|
|
function resolverFixture() {
|
|
const getInstalledRelease = vi.fn(async () => installed());
|
|
const resolve = vi.fn(async () => resolved());
|
|
const resolver = new MarketplaceHostedAdmissionResolver({
|
|
packageStore: { getInstalledRelease } as unknown as Pick<PluginPackageStore, 'getInstalledRelease'>,
|
|
marketplace: { resolve } as unknown as Pick<MarketplacePackageClientPort, 'resolve'>,
|
|
makeloreVersion: '2.0.0',
|
|
});
|
|
return { resolver, getInstalledRelease, resolve };
|
|
}
|
|
|
|
describe('MarketplaceHostedAdmissionResolver', () => {
|
|
it('resolves the frozen package release through the selected channel and current account admission', async () => {
|
|
const { resolver, getInstalledRelease, resolve } = resolverFixture();
|
|
|
|
await expect(resolver.resolve({
|
|
pluginId: PLUGIN_ID,
|
|
workerSnapshot: { requestId: REQUEST_ID, pluginReleaseId: RELEASE_ID },
|
|
})).resolves.toEqual({
|
|
releaseId: RELEASE_ID,
|
|
releaseAdmissionId: 'admission-game-1',
|
|
});
|
|
|
|
expect(getInstalledRelease).toHaveBeenCalledWith(PLUGIN_ID, RELEASE_ID);
|
|
expect(resolve).toHaveBeenCalledWith({
|
|
resolveRequestId: REQUEST_ID,
|
|
makeloreVersion: '2.0.0',
|
|
channel: 'stable',
|
|
installed: [{ pluginId: PLUGIN_ID, releaseId: RELEASE_ID, sha256: DIGEST }],
|
|
});
|
|
});
|
|
|
|
it('does not substitute the current package when the worker has no frozen release', async () => {
|
|
const { resolver, getInstalledRelease, resolve } = resolverFixture();
|
|
|
|
await expect(resolver.resolve({
|
|
pluginId: PLUGIN_ID,
|
|
workerSnapshot: { requestId: REQUEST_ID },
|
|
})).rejects.toMatchObject({
|
|
code: 'plugin_release_unavailable',
|
|
status: 409,
|
|
retryable: false,
|
|
});
|
|
|
|
expect(getInstalledRelease).not.toHaveBeenCalled();
|
|
expect(resolve).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it.each([
|
|
['release mismatch', { releaseId: 'release-other' }],
|
|
['channel mismatch', { channel: 'beta' }],
|
|
['action mismatch', { action: 'update' }],
|
|
['digest mismatch', { sha256: 'b'.repeat(64) }],
|
|
])('returns one typed stale failure for %s', async (_label, item) => {
|
|
const marketplace = {
|
|
resolve: vi.fn(async () => resolved(item)),
|
|
} as unknown as Pick<MarketplacePackageClientPort, 'resolve'>;
|
|
const staleResolver = new MarketplaceHostedAdmissionResolver({
|
|
packageStore: { getInstalledRelease: vi.fn(async () => installed()) } as unknown as Pick<PluginPackageStore, 'getInstalledRelease'>,
|
|
marketplace,
|
|
makeloreVersion: '2.0.0',
|
|
});
|
|
|
|
const error = await staleResolver.resolve({
|
|
pluginId: PLUGIN_ID,
|
|
workerSnapshot: { requestId: REQUEST_ID, pluginReleaseId: RELEASE_ID },
|
|
}).catch((value: unknown) => value);
|
|
|
|
expect(error).toBeInstanceOf(MarketplaceHostedAdmissionError);
|
|
expect(error).toMatchObject({ code: 'plugin_runtime_stale', status: 409, retryable: false });
|
|
});
|
|
|
|
it.each([
|
|
'plugin_release_not_ready',
|
|
'plugin_client_incompatible',
|
|
])('preserves an explicit unavailable Release as plugin_release_unavailable (%s)', async (reason) => {
|
|
const marketplace = {
|
|
resolve: vi.fn(async () => resolved({
|
|
action: 'unavailable',
|
|
releaseAdmissionId: null,
|
|
reason,
|
|
})),
|
|
} as unknown as Pick<MarketplacePackageClientPort, 'resolve'>;
|
|
const unavailableResolver = new MarketplaceHostedAdmissionResolver({
|
|
packageStore: { getInstalledRelease: vi.fn(async () => installed()) } as unknown as Pick<PluginPackageStore, 'getInstalledRelease'>,
|
|
marketplace,
|
|
makeloreVersion: '1.2.4',
|
|
});
|
|
|
|
await expect(unavailableResolver.resolve({
|
|
pluginId: PLUGIN_ID,
|
|
workerSnapshot: { requestId: REQUEST_ID, pluginReleaseId: RELEASE_ID },
|
|
})).rejects.toMatchObject({
|
|
code: 'plugin_release_unavailable',
|
|
status: 409,
|
|
retryable: false,
|
|
});
|
|
});
|
|
|
|
it('projects an account change as a typed non-retryable worker failure', async () => {
|
|
const { resolver, resolve } = resolverFixture();
|
|
resolve.mockRejectedValueOnce({ code: 'marketplace_account_changed' });
|
|
|
|
await expect(resolver.resolve({
|
|
pluginId: PLUGIN_ID,
|
|
workerSnapshot: { requestId: REQUEST_ID, pluginReleaseId: RELEASE_ID },
|
|
})).rejects.toMatchObject({
|
|
code: 'plugin_account_changed',
|
|
status: 409,
|
|
retryable: false,
|
|
});
|
|
});
|
|
|
|
it('resolves a code-owned bundled Release without consulting the device Package Store', async () => {
|
|
const getInstalledRelease = vi.fn(async () => null);
|
|
const resolve = vi.fn(async () => resolved({ sha256: null, sizeBytes: null }));
|
|
const options = {
|
|
packageStore: { getInstalledRelease },
|
|
marketplace: { resolve },
|
|
makeloreVersion: '2.0.0',
|
|
bundledReleases: {
|
|
[PLUGIN_ID]: { releaseId: RELEASE_ID, version: '1.0.0', channel: 'stable' as const },
|
|
},
|
|
} as unknown as ConstructorParameters<typeof MarketplaceHostedAdmissionResolver>[0];
|
|
const bundledResolver = new MarketplaceHostedAdmissionResolver(options);
|
|
|
|
await expect(bundledResolver.resolve({
|
|
pluginId: PLUGIN_ID,
|
|
workerSnapshot: { requestId: REQUEST_ID, pluginReleaseId: RELEASE_ID },
|
|
})).resolves.toEqual({ releaseId: RELEASE_ID, releaseAdmissionId: 'admission-game-1' });
|
|
expect(getInstalledRelease).not.toHaveBeenCalled();
|
|
expect(resolve).toHaveBeenCalledWith({
|
|
resolveRequestId: REQUEST_ID,
|
|
makeloreVersion: '2.0.0',
|
|
channel: 'stable',
|
|
installed: [],
|
|
});
|
|
});
|
|
});
|