Files
Michael Kriese e144d8dbc1 fix(http): validate the cached response shape in the http cache schema (#46417)
refactor(http): validate the cached response shape in the http cache schema

Co-authored-by: Claude Fable 5.1 <michael.kriese+claude-code@mend.io>
2026-09-23 14:12:16 +00:00

282 lines
8.2 KiB
TypeScript

import { codeBlock } from 'common-tags';
import { mockDeep } from 'vitest-mock-extended';
import { XmlDocument } from 'xmldoc';
import { Fixtures } from '~test/fixtures.ts';
import * as httpMock from '~test/http-mock.ts';
import * as _packageCache from '../../../util/cache/package/index.ts';
import type { HttpCache } from '../../../util/http/cache/schema.ts';
import { id as versioning } from '../../versioning/maven/index.ts';
import { getPkgReleases } from '../index.ts';
import { MavenDatasource } from './index.ts';
import { CachedMavenXml } from './schema.ts';
vi.mock('../../../util/cache/package/index.ts', () => mockDeep());
const packageCache = vi.mocked(_packageCache);
const packageName = 'org.example:package';
const registryUrl = 'https://repo.maven.apache.org/maven2';
const metadataUrl =
'https://repo.maven.apache.org/maven2/org/example/package/maven-metadata.xml';
const pomUrl =
'https://repo.maven.apache.org/maven2/org/example/package/2.0.0/package-2.0.0.pom';
describe('modules/datasource/maven/cache', () => {
let cache: Record<string, HttpCache>;
beforeEach(() => {
cache = {};
packageCache.get.mockImplementation((namespace, key) => {
if (namespace === 'datasource-maven:metadata-not-found') {
return Promise.resolve(null as never);
}
return Promise.resolve(cache[key] as never);
});
packageCache.getCacheType.mockReturnValue(undefined);
packageCache.setWithRawTtl.mockImplementation((_namespace, key, value) => {
cache[key] = value as HttpCache;
return Promise.resolve(null as never);
});
});
it('persists trimmed metadata and pom bodies', async () => {
httpMock
.scope(registryUrl)
.get('/org/example/package/maven-metadata.xml')
.reply(200, Fixtures.get('metadata.xml'))
.get('/org/example/package/2.0.0/package-2.0.0.pom')
.reply(200, Fixtures.get('pom.xml'));
const result = await getPkgReleases({
datasource: MavenDatasource.id,
packageName,
registryUrls: [registryUrl],
versioning,
});
expect(result).toMatchObject({
homepage: 'https://package.example.org/about',
packageScope: 'org.example',
tags: {
latest: '2.0.0',
release: '2.0.0',
},
});
const metadataCache = cache[metadataUrl]!;
const metadata = new XmlDocument(
(metadataCache.httpResponse as { body: string }).body,
);
expect(metadata.valueWithPath('groupId')).toBeUndefined();
expect(metadata.valueWithPath('artifactId')).toBeUndefined();
expect(
metadata.descendantWithPath('versioning.lastUpdated'),
).toBeUndefined();
expect(metadata.valueWithPath('versioning.latest')).toBe('2.0.0');
expect(metadata.valueWithPath('versioning.release')).toBe('2.0.0');
const pomCache = cache[pomUrl]!;
const pom = new XmlDocument(
(pomCache.httpResponse as { body: string }).body,
);
expect(pom.valueWithPath('groupId')).toBe('org.example');
expect(pom.valueWithPath('url')).toBe('https://package.example.org/about');
expect(pom.valueWithPath('name')).toBeUndefined();
expect(pom.valueWithPath('description')).toBeUndefined();
});
it('serves cached trimmed XML without refetching', async () => {
const timestamp = new Date().toISOString();
cache[metadataUrl] = {
etag: 'etag',
httpResponse: {
statusCode: 200,
headers: {},
body: CachedMavenXml.parse(Fixtures.get('metadata.xml')),
},
timestamp,
};
cache[pomUrl] = {
etag: 'etag',
httpResponse: {
statusCode: 200,
headers: {},
body: CachedMavenXml.parse(Fixtures.get('pom.xml')),
},
timestamp,
};
const result = await getPkgReleases({
datasource: MavenDatasource.id,
packageName,
registryUrls: [registryUrl],
versioning,
});
expect(result).toMatchObject({
homepage: 'https://package.example.org/about',
packageScope: 'org.example',
tags: {
latest: '2.0.0',
release: '2.0.0',
},
});
expect(packageCache.setWithRawTtl).not.toHaveBeenCalled();
});
it('preserves empty relocation markers on cache hits', async () => {
const pomWithEmptyRelocation = codeBlock`
<project>
<distributionManagement>
<relocation />
</distributionManagement>
</project>
`;
const timestamp = new Date().toISOString();
cache[metadataUrl] = {
etag: 'etag',
httpResponse: {
statusCode: 200,
headers: {},
body: CachedMavenXml.parse(Fixtures.get('metadata.xml')),
},
timestamp,
};
cache[pomUrl] = {
etag: 'etag',
httpResponse: {
statusCode: 200,
headers: {},
body: CachedMavenXml.parse(pomWithEmptyRelocation),
},
timestamp,
};
const result = await getPkgReleases({
datasource: MavenDatasource.id,
packageName,
registryUrls: [registryUrl],
versioning,
});
expect(result).toMatchObject({
replacementName: 'org.example:package',
replacementVersion: '2.0.0',
});
});
it('revalidates trimmed cached XML after 304 responses', async () => {
const staleTimestamp = '2024-01-01T00:00:00.000Z';
cache[metadataUrl] = {
etag: 'metadata-etag',
lastModified: 'Mon, 01 Jan 2024 00:00:00 GMT',
httpResponse: {
statusCode: 200,
headers: { etag: 'metadata-etag' },
body: CachedMavenXml.parse(Fixtures.get('metadata.xml')),
},
timestamp: staleTimestamp,
};
cache[pomUrl] = {
etag: 'pom-etag',
lastModified: 'Mon, 01 Jan 2024 00:00:00 GMT',
httpResponse: {
statusCode: 200,
headers: { etag: 'pom-etag' },
body: CachedMavenXml.parse(Fixtures.get('pom.xml')),
},
timestamp: staleTimestamp,
};
httpMock
.scope(registryUrl)
.get('/org/example/package/maven-metadata.xml')
.reply(304)
.get('/org/example/package/2.0.0/package-2.0.0.pom')
.reply(304);
const result = await getPkgReleases({
datasource: MavenDatasource.id,
packageName,
registryUrls: [registryUrl],
versioning,
});
expect(result).toMatchObject({
homepage: 'https://package.example.org/about',
packageScope: 'org.example',
tags: {
latest: '2.0.0',
release: '2.0.0',
},
});
expect(packageCache.setWithRawTtl).toHaveBeenCalledTimes(2);
expect(cache[metadataUrl].timestamp).not.toBe(staleTimestamp);
expect(cache[pomUrl].timestamp).not.toBe(staleTimestamp);
});
it('serves cached trimmed snapshot XML without refetching', async () => {
const timestamp = new Date().toISOString();
const snapshotMetadataUrl =
'https://repo.maven.apache.org/maven2/org/example/package/1.0.3-SNAPSHOT/maven-metadata.xml';
const snapshotPomUrl =
'https://repo.maven.apache.org/maven2/org/example/package/1.0.3-SNAPSHOT/package-1.0.3-20200101.010003-3.pom';
cache[metadataUrl] = {
etag: 'etag',
httpResponse: {
statusCode: 200,
headers: {},
body: CachedMavenXml.parse(Fixtures.get('metadata-snapshot-only.xml')),
},
timestamp,
};
cache[snapshotMetadataUrl] = {
etag: 'etag',
httpResponse: {
statusCode: 200,
headers: {},
body: CachedMavenXml.parse(
Fixtures.get('metadata-snapshot-version.xml'),
),
},
timestamp,
};
cache[snapshotPomUrl] = {
etag: 'etag',
httpResponse: {
statusCode: 200,
headers: {},
body: CachedMavenXml.parse(Fixtures.get('pom.xml')),
},
timestamp,
};
const result = await getPkgReleases({
datasource: MavenDatasource.id,
packageName,
registryUrls: [registryUrl],
versioning,
});
expect(result).toEqual({
display: 'org.example:package',
group: 'org.example',
homepage: 'https://package.example.org/about',
name: 'package',
packageScope: 'org.example',
registryUrl,
releases: [{ version: '1.0.3-SNAPSHOT' }],
respectLatest: false,
tags: {
latest: '1.0.3-SNAPSHOT',
release: '1.0.3-SNAPSHOT',
},
});
expect(packageCache.setWithRawTtl).not.toHaveBeenCalled();
});
});