feat(maven): Add Maven registry protocol support (storage, auth, routing, interfaces, and exports)
This commit is contained in:
372
test/test.maven.ts
Normal file
372
test/test.maven.ts
Normal file
@@ -0,0 +1,372 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { SmartRegistry } from '../ts/index.js';
|
||||
import {
|
||||
createTestRegistry,
|
||||
createTestTokens,
|
||||
createTestPom,
|
||||
createTestJar,
|
||||
calculateMavenChecksums,
|
||||
} from './helpers/registry.js';
|
||||
|
||||
let registry: SmartRegistry;
|
||||
let mavenToken: string;
|
||||
let userId: string;
|
||||
|
||||
// Test data
|
||||
const testGroupId = 'com.example.test';
|
||||
const testArtifactId = 'test-artifact';
|
||||
const testVersion = '1.0.0';
|
||||
const testJarData = createTestJar();
|
||||
const testPomData = Buffer.from(
|
||||
createTestPom(testGroupId, testArtifactId, testVersion),
|
||||
'utf-8'
|
||||
);
|
||||
|
||||
tap.test('Maven: should create registry instance', async () => {
|
||||
registry = await createTestRegistry();
|
||||
const tokens = await createTestTokens(registry);
|
||||
mavenToken = tokens.mavenToken;
|
||||
userId = tokens.userId;
|
||||
|
||||
expect(registry).toBeInstanceOf(SmartRegistry);
|
||||
expect(mavenToken).toBeTypeOf('string');
|
||||
});
|
||||
|
||||
tap.test('Maven: should upload POM file (PUT /{groupPath}/{artifactId}/{version}/*.pom)', async () => {
|
||||
const groupPath = testGroupId.replace(/\./g, '/');
|
||||
const pomFilename = `${testArtifactId}-${testVersion}.pom`;
|
||||
|
||||
const response = await registry.handleRequest({
|
||||
method: 'PUT',
|
||||
path: `/maven/${groupPath}/${testArtifactId}/${testVersion}/${pomFilename}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${mavenToken}`,
|
||||
'Content-Type': 'application/xml',
|
||||
},
|
||||
query: {},
|
||||
body: testPomData,
|
||||
});
|
||||
|
||||
expect(response.status).toEqual(201);
|
||||
});
|
||||
|
||||
tap.test('Maven: should upload JAR file (PUT /{groupPath}/{artifactId}/{version}/*.jar)', async () => {
|
||||
const groupPath = testGroupId.replace(/\./g, '/');
|
||||
const jarFilename = `${testArtifactId}-${testVersion}.jar`;
|
||||
|
||||
const response = await registry.handleRequest({
|
||||
method: 'PUT',
|
||||
path: `/maven/${groupPath}/${testArtifactId}/${testVersion}/${jarFilename}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${mavenToken}`,
|
||||
'Content-Type': 'application/java-archive',
|
||||
},
|
||||
query: {},
|
||||
body: testJarData,
|
||||
});
|
||||
|
||||
expect(response.status).toEqual(201);
|
||||
});
|
||||
|
||||
tap.test('Maven: should retrieve uploaded POM file (GET)', async () => {
|
||||
const groupPath = testGroupId.replace(/\./g, '/');
|
||||
const pomFilename = `${testArtifactId}-${testVersion}.pom`;
|
||||
|
||||
const response = await registry.handleRequest({
|
||||
method: 'GET',
|
||||
path: `/maven/${groupPath}/${testArtifactId}/${testVersion}/${pomFilename}`,
|
||||
headers: {},
|
||||
query: {},
|
||||
});
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toBeInstanceOf(Buffer);
|
||||
expect((response.body as Buffer).toString('utf-8')).toContain(testGroupId);
|
||||
expect((response.body as Buffer).toString('utf-8')).toContain(testArtifactId);
|
||||
expect((response.body as Buffer).toString('utf-8')).toContain(testVersion);
|
||||
expect(response.headers['Content-Type']).toEqual('application/xml');
|
||||
});
|
||||
|
||||
tap.test('Maven: should retrieve uploaded JAR file (GET)', async () => {
|
||||
const groupPath = testGroupId.replace(/\./g, '/');
|
||||
const jarFilename = `${testArtifactId}-${testVersion}.jar`;
|
||||
|
||||
const response = await registry.handleRequest({
|
||||
method: 'GET',
|
||||
path: `/maven/${groupPath}/${testArtifactId}/${testVersion}/${jarFilename}`,
|
||||
headers: {},
|
||||
query: {},
|
||||
});
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toBeInstanceOf(Buffer);
|
||||
expect(response.headers['Content-Type']).toEqual('application/java-archive');
|
||||
});
|
||||
|
||||
tap.test('Maven: should retrieve MD5 checksum for JAR (GET *.jar.md5)', async () => {
|
||||
const groupPath = testGroupId.replace(/\./g, '/');
|
||||
const jarFilename = `${testArtifactId}-${testVersion}.jar`;
|
||||
const checksums = calculateMavenChecksums(testJarData);
|
||||
|
||||
const response = await registry.handleRequest({
|
||||
method: 'GET',
|
||||
path: `/maven/${groupPath}/${testArtifactId}/${testVersion}/${jarFilename}.md5`,
|
||||
headers: {},
|
||||
query: {},
|
||||
});
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toBeInstanceOf(Buffer);
|
||||
expect((response.body as Buffer).toString('utf-8')).toEqual(checksums.md5);
|
||||
expect(response.headers['Content-Type']).toEqual('text/plain');
|
||||
});
|
||||
|
||||
tap.test('Maven: should retrieve SHA1 checksum for JAR (GET *.jar.sha1)', async () => {
|
||||
const groupPath = testGroupId.replace(/\./g, '/');
|
||||
const jarFilename = `${testArtifactId}-${testVersion}.jar`;
|
||||
const checksums = calculateMavenChecksums(testJarData);
|
||||
|
||||
const response = await registry.handleRequest({
|
||||
method: 'GET',
|
||||
path: `/maven/${groupPath}/${testArtifactId}/${testVersion}/${jarFilename}.sha1`,
|
||||
headers: {},
|
||||
query: {},
|
||||
});
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toBeInstanceOf(Buffer);
|
||||
expect((response.body as Buffer).toString('utf-8')).toEqual(checksums.sha1);
|
||||
expect(response.headers['Content-Type']).toEqual('text/plain');
|
||||
});
|
||||
|
||||
tap.test('Maven: should retrieve SHA256 checksum for JAR (GET *.jar.sha256)', async () => {
|
||||
const groupPath = testGroupId.replace(/\./g, '/');
|
||||
const jarFilename = `${testArtifactId}-${testVersion}.jar`;
|
||||
const checksums = calculateMavenChecksums(testJarData);
|
||||
|
||||
const response = await registry.handleRequest({
|
||||
method: 'GET',
|
||||
path: `/maven/${groupPath}/${testArtifactId}/${testVersion}/${jarFilename}.sha256`,
|
||||
headers: {},
|
||||
query: {},
|
||||
});
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toBeInstanceOf(Buffer);
|
||||
expect((response.body as Buffer).toString('utf-8')).toEqual(checksums.sha256);
|
||||
expect(response.headers['Content-Type']).toEqual('text/plain');
|
||||
});
|
||||
|
||||
tap.test('Maven: should retrieve SHA512 checksum for JAR (GET *.jar.sha512)', async () => {
|
||||
const groupPath = testGroupId.replace(/\./g, '/');
|
||||
const jarFilename = `${testArtifactId}-${testVersion}.jar`;
|
||||
const checksums = calculateMavenChecksums(testJarData);
|
||||
|
||||
const response = await registry.handleRequest({
|
||||
method: 'GET',
|
||||
path: `/maven/${groupPath}/${testArtifactId}/${testVersion}/${jarFilename}.sha512`,
|
||||
headers: {},
|
||||
query: {},
|
||||
});
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toBeInstanceOf(Buffer);
|
||||
expect((response.body as Buffer).toString('utf-8')).toEqual(checksums.sha512);
|
||||
expect(response.headers['Content-Type']).toEqual('text/plain');
|
||||
});
|
||||
|
||||
tap.test('Maven: should retrieve maven-metadata.xml (GET)', async () => {
|
||||
const groupPath = testGroupId.replace(/\./g, '/');
|
||||
|
||||
const response = await registry.handleRequest({
|
||||
method: 'GET',
|
||||
path: `/maven/${groupPath}/${testArtifactId}/maven-metadata.xml`,
|
||||
headers: {},
|
||||
query: {},
|
||||
});
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toBeInstanceOf(Buffer);
|
||||
const xml = (response.body as Buffer).toString('utf-8');
|
||||
expect(xml).toContain('<groupId>');
|
||||
expect(xml).toContain('<artifactId>');
|
||||
expect(xml).toContain('<version>1.0.0</version>');
|
||||
expect(xml).toContain('<latest>1.0.0</latest>');
|
||||
expect(xml).toContain('<release>1.0.0</release>');
|
||||
expect(response.headers['Content-Type']).toEqual('application/xml');
|
||||
});
|
||||
|
||||
tap.test('Maven: should upload a second version and update metadata', async () => {
|
||||
const groupPath = testGroupId.replace(/\./g, '/');
|
||||
const newVersion = '2.0.0';
|
||||
const pomFilename = `${testArtifactId}-${newVersion}.pom`;
|
||||
const jarFilename = `${testArtifactId}-${newVersion}.jar`;
|
||||
const newPomData = Buffer.from(
|
||||
createTestPom(testGroupId, testArtifactId, newVersion),
|
||||
'utf-8'
|
||||
);
|
||||
|
||||
// Upload POM
|
||||
await registry.handleRequest({
|
||||
method: 'PUT',
|
||||
path: `/maven/${groupPath}/${testArtifactId}/${newVersion}/${pomFilename}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${mavenToken}`,
|
||||
'Content-Type': 'application/xml',
|
||||
},
|
||||
query: {},
|
||||
body: newPomData,
|
||||
});
|
||||
|
||||
// Upload JAR
|
||||
await registry.handleRequest({
|
||||
method: 'PUT',
|
||||
path: `/maven/${groupPath}/${testArtifactId}/${newVersion}/${jarFilename}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${mavenToken}`,
|
||||
'Content-Type': 'application/java-archive',
|
||||
},
|
||||
query: {},
|
||||
body: testJarData,
|
||||
});
|
||||
|
||||
// Retrieve metadata and verify both versions are present
|
||||
const response = await registry.handleRequest({
|
||||
method: 'GET',
|
||||
path: `/maven/${groupPath}/${testArtifactId}/maven-metadata.xml`,
|
||||
headers: {},
|
||||
query: {},
|
||||
});
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
const xml = (response.body as Buffer).toString('utf-8');
|
||||
expect(xml).toContain('<version>1.0.0</version>');
|
||||
expect(xml).toContain('<version>2.0.0</version>');
|
||||
expect(xml).toContain('<latest>2.0.0</latest>');
|
||||
expect(xml).toContain('<release>2.0.0</release>');
|
||||
});
|
||||
|
||||
tap.test('Maven: should upload WAR file with correct content type', async () => {
|
||||
const groupPath = testGroupId.replace(/\./g, '/');
|
||||
const warVersion = '1.0.0-war';
|
||||
const warFilename = `${testArtifactId}-${warVersion}.war`;
|
||||
const warData = Buffer.from('fake war content', 'utf-8');
|
||||
|
||||
const response = await registry.handleRequest({
|
||||
method: 'PUT',
|
||||
path: `/maven/${groupPath}/${testArtifactId}/${warVersion}/${warFilename}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${mavenToken}`,
|
||||
'Content-Type': 'application/x-webarchive',
|
||||
},
|
||||
query: {},
|
||||
body: warData,
|
||||
});
|
||||
|
||||
expect(response.status).toEqual(201);
|
||||
});
|
||||
|
||||
tap.test('Maven: should return 404 for non-existent artifact', async () => {
|
||||
const groupPath = 'com/example/nonexistent';
|
||||
|
||||
const response = await registry.handleRequest({
|
||||
method: 'GET',
|
||||
path: `/maven/${groupPath}/fake-artifact/1.0.0/fake-artifact-1.0.0.jar`,
|
||||
headers: {},
|
||||
query: {},
|
||||
});
|
||||
|
||||
expect(response.status).toEqual(404);
|
||||
expect(response.body).toHaveProperty('error');
|
||||
});
|
||||
|
||||
tap.test('Maven: should return 401 for unauthorized upload', async () => {
|
||||
const groupPath = testGroupId.replace(/\./g, '/');
|
||||
const jarFilename = `${testArtifactId}-3.0.0.jar`;
|
||||
|
||||
const response = await registry.handleRequest({
|
||||
method: 'PUT',
|
||||
path: `/maven/${groupPath}/${testArtifactId}/3.0.0/${jarFilename}`,
|
||||
headers: {
|
||||
// No authorization header
|
||||
'Content-Type': 'application/java-archive',
|
||||
},
|
||||
query: {},
|
||||
body: testJarData,
|
||||
});
|
||||
|
||||
expect(response.status).toEqual(401);
|
||||
expect(response.body).toHaveProperty('error');
|
||||
});
|
||||
|
||||
tap.test('Maven: should reject POM upload with mismatched GAV', async () => {
|
||||
const groupPath = 'com/mismatch/test';
|
||||
const pomFilename = `different-artifact-1.0.0.pom`;
|
||||
// POM contains different GAV than the path
|
||||
const mismatchedPom = Buffer.from(
|
||||
createTestPom('com.other.group', 'other-artifact', '1.0.0'),
|
||||
'utf-8'
|
||||
);
|
||||
|
||||
const response = await registry.handleRequest({
|
||||
method: 'PUT',
|
||||
path: `/maven/${groupPath}/different-artifact/1.0.0/${pomFilename}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${mavenToken}`,
|
||||
'Content-Type': 'application/xml',
|
||||
},
|
||||
query: {},
|
||||
body: mismatchedPom,
|
||||
});
|
||||
|
||||
expect(response.status).toEqual(400);
|
||||
expect(response.body).toHaveProperty('error');
|
||||
});
|
||||
|
||||
tap.test('Maven: should delete an artifact (DELETE)', async () => {
|
||||
const groupPath = testGroupId.replace(/\./g, '/');
|
||||
const jarFilename = `${testArtifactId}-${testVersion}.jar`;
|
||||
|
||||
const response = await registry.handleRequest({
|
||||
method: 'DELETE',
|
||||
path: `/maven/${groupPath}/${testArtifactId}/${testVersion}/${jarFilename}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${mavenToken}`,
|
||||
},
|
||||
query: {},
|
||||
});
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
|
||||
// Verify artifact was deleted
|
||||
const getResponse = await registry.handleRequest({
|
||||
method: 'GET',
|
||||
path: `/maven/${groupPath}/${testArtifactId}/${testVersion}/${jarFilename}`,
|
||||
headers: {},
|
||||
query: {},
|
||||
});
|
||||
|
||||
expect(getResponse.status).toEqual(404);
|
||||
});
|
||||
|
||||
tap.test('Maven: should return 404 for checksum of deleted artifact', async () => {
|
||||
const groupPath = testGroupId.replace(/\./g, '/');
|
||||
const jarFilename = `${testArtifactId}-${testVersion}.jar`;
|
||||
|
||||
const response = await registry.handleRequest({
|
||||
method: 'GET',
|
||||
path: `/maven/${groupPath}/${testArtifactId}/${testVersion}/${jarFilename}.md5`,
|
||||
headers: {},
|
||||
query: {},
|
||||
});
|
||||
|
||||
expect(response.status).toEqual(404);
|
||||
});
|
||||
|
||||
tap.postTask('cleanup registry', async () => {
|
||||
if (registry) {
|
||||
registry.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
Reference in New Issue
Block a user