feat(appstore): resolve repo manifests and docker digest-tracked images

This commit is contained in:
2026-05-25 01:39:59 +00:00
parent d29257dcf7
commit db52934f35
6 changed files with 691 additions and 10 deletions
+90
View File
@@ -81,6 +81,96 @@ Deno.test('appstore rejects invalid template ports and volumes', () => {
);
});
Deno.test('appstore resolves repo manifests and docker digest-tracked latest images', async () => {
const catalogBaseUrl = 'https://catalog.example.test';
const manifestUrl = 'https://code.example.test/cloudly/servezone.catalog.json';
const digest = 'sha256:1234567890abcdef';
const fakeFetch: typeof fetch = async (input, init) => {
const url = input instanceof Request ? input.url : input.toString();
const method = init?.method || 'GET';
if (url === `${catalogBaseUrl}/catalog.resolved.json`) {
return new Response('not found', { status: 404 });
}
if (url === `${catalogBaseUrl}/catalog.json`) {
return Response.json({
schemaVersion: 1,
updatedAt: '2026-05-24T00:00:00Z',
apps: [
{
id: 'cloudly',
name: 'Cloudly',
description: 'Central metadata can stay curated.',
category: 'Dev Tools',
latestVersion: '1.0.0',
source: {
type: 'repoManifest',
url: manifestUrl,
ref: 'main',
},
},
],
});
}
if (url === manifestUrl) {
return Response.json({
schemaVersion: 1,
app: {
id: 'cloudly',
name: 'Cloudly',
description: 'Manifest-owned app metadata.',
category: 'Dev Tools',
maintainer: 'serve.zone',
},
latestVersion: 'latest',
source: {
type: 'dockerImage',
image: 'registry.example.test/serve.zone/cloudly:latest',
tracking: 'digest',
},
runtime: {
image: 'registry.example.test/serve.zone/cloudly:latest',
port: 80,
},
});
}
if (
url === 'https://registry.example.test/v2/serve.zone/cloudly/manifests/latest' &&
method === 'HEAD'
) {
return new Response(null, {
status: 200,
headers: { 'docker-content-digest': digest },
});
}
return new Response(`unexpected ${method} ${url}`, { status: 500 });
};
const appStore = new AppStoreManager({} as any, {
repoBaseUrl: catalogBaseUrl,
fetch: fakeFetch,
});
const catalog = await appStore.getCatalog();
assertEquals(catalog.apps[0].latestVersion, `latest@${digest}`);
assertEquals(catalog.apps[0].resolvedSource?.manifestHash?.length, 64);
assertEquals(catalog.apps[0].upgradeStrategy, 'dockerDigest');
const appMeta = await appStore.getAppMeta('cloudly');
assertEquals(appMeta.latestVersion, `latest@${digest}`);
assertEquals(appMeta.versions, [`latest@${digest}`]);
const config = await appStore.getAppVersionConfig('cloudly', appMeta.latestVersion);
assertEquals(config.image, 'registry.example.test/serve.zone/cloudly:latest');
assertEquals(config.catalogVersion, `latest@${digest}`);
assertEquals(config.resolvedImageDigest, digest);
});
Deno.test('docker service spec validation rejects unsafe volume and port declarations', () => {
const dockerManager = new OneboxDockerManager();