65 lines
3.0 KiB
TypeScript
65 lines
3.0 KiB
TypeScript
import { describe, test, expect } from 'bun:test';
|
|
import { indexChildDigests, localDigestIsIndexChild } from '../src/lib/server/scheduler/tasks/update-utils';
|
|
|
|
// Real shape from ghcr.io/mastodon/mastodon:v4.6.5 (the #1367 repro): an OCI index
|
|
// whose per-arch child digests differ from the index digest a HEAD returns.
|
|
const MASTODON_INDEX = {
|
|
mediaType: 'application/vnd.oci.image.index.v1+json',
|
|
manifests: [
|
|
{ platform: { architecture: 'arm64', os: 'linux' }, digest: 'sha256:2cd8bcf2e903dab42a759956008e17da16a1268e842e738c2c640f6f7b54bae8' },
|
|
{ platform: { architecture: 'amd64', os: 'linux' }, digest: 'sha256:696439e1ada71d0cf3d51d4d6a4744d6e40b57aafa64980b18f4d3b78230d0cf' }
|
|
]
|
|
};
|
|
const INDEX_DIGEST = 'sha256:77f11d1a6c674664217372d94ccdb9203524c60447827fe74ab6e11466825815';
|
|
const AMD64_CHILD = 'sha256:696439e1ada71d0cf3d51d4d6a4744d6e40b57aafa64980b18f4d3b78230d0cf';
|
|
|
|
describe('indexChildDigests', () => {
|
|
test('extracts every per-arch child digest from an image index', () => {
|
|
expect(indexChildDigests(MASTODON_INDEX)).toEqual([
|
|
'sha256:2cd8bcf2e903dab42a759956008e17da16a1268e842e738c2c640f6f7b54bae8',
|
|
AMD64_CHILD
|
|
]);
|
|
});
|
|
|
|
test('returns [] for a single-arch manifest (no manifests array)', () => {
|
|
expect(indexChildDigests({ mediaType: 'application/vnd.oci.image.manifest.v1+json', layers: [] })).toEqual([]);
|
|
});
|
|
|
|
test('returns [] for null / garbage / error bodies without throwing', () => {
|
|
expect(indexChildDigests(null)).toEqual([]);
|
|
expect(indexChildDigests(undefined)).toEqual([]);
|
|
expect(indexChildDigests('not json')).toEqual([]);
|
|
expect(indexChildDigests({ errors: [{ code: 'MANIFEST_UNKNOWN' }] })).toEqual([]);
|
|
});
|
|
|
|
test('skips malformed manifest entries (missing/empty/non-string digest)', () => {
|
|
const body = { manifests: [{ digest: AMD64_CHILD }, { digest: '' }, { digest: 42 }, {}, null] };
|
|
expect(indexChildDigests(body)).toEqual([AMD64_CHILD]);
|
|
});
|
|
});
|
|
|
|
describe('localDigestIsIndexChild', () => {
|
|
test('true when local per-arch digest is a child of the index (the #1367 fix)', () => {
|
|
// local image recorded its amd64 per-arch digest, HEAD returned the index digest
|
|
expect(localDigestIsIndexChild([AMD64_CHILD], MASTODON_INDEX)).toBe(true);
|
|
});
|
|
|
|
test('false when the local digest is the index digest itself (already handled upstream)', () => {
|
|
// upstream matches this against the HEAD digest first; here it is NOT a child
|
|
expect(localDigestIsIndexChild([INDEX_DIGEST], MASTODON_INDEX)).toBe(false);
|
|
});
|
|
|
|
test('false when local digest is genuinely unrelated (a real update)', () => {
|
|
expect(localDigestIsIndexChild(['sha256:deadbeef'], MASTODON_INDEX)).toBe(false);
|
|
});
|
|
|
|
test('false for a non-index body (single-arch / error) - degrades to "keep update"', () => {
|
|
expect(localDigestIsIndexChild([AMD64_CHILD], { layers: [] })).toBe(false);
|
|
expect(localDigestIsIndexChild([AMD64_CHILD], null)).toBe(false);
|
|
});
|
|
|
|
test('matches if ANY of several local digests is a child', () => {
|
|
expect(localDigestIsIndexChild(['sha256:unrelated', AMD64_CHILD], MASTODON_INDEX)).toBe(true);
|
|
});
|
|
});
|