feat(core,storage,oci,registry-config): add streaming response support and configurable registry URLs across protocols

This commit is contained in:
2026-03-24 22:59:37 +00:00
parent 1f0acf2825
commit 7da1a35efe
42 changed files with 4179 additions and 5396 deletions

View File

@@ -1,5 +1,6 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { SmartRegistry } from '../ts/index.js';
import { streamToBuffer, streamToJson } from '../ts/core/helpers.stream.js';
import {
createTestRegistry,
createTestTokens,
@@ -54,7 +55,8 @@ tap.test('RubyGems: should upload gem file (POST /rubygems/api/v1/gems)', async
});
expect(response.status).toEqual(201);
expect(response.body).toHaveProperty('message');
const body = await streamToJson(response.body);
expect(body).toHaveProperty('message');
});
tap.test('RubyGems: should retrieve Compact Index versions file (GET /rubygems/versions)', async () => {
@@ -67,9 +69,10 @@ tap.test('RubyGems: should retrieve Compact Index versions file (GET /rubygems/v
expect(response.status).toEqual(200);
expect(response.headers['Content-Type']).toEqual('text/plain; charset=utf-8');
expect(response.body).toBeInstanceOf(Buffer);
const body = await streamToBuffer(response.body);
expect(body).toBeInstanceOf(Buffer);
const content = (response.body as Buffer).toString('utf-8');
const content = body.toString('utf-8');
expect(content).toContain('created_at:');
expect(content).toContain('---');
expect(content).toContain(testGemName);
@@ -86,9 +89,10 @@ tap.test('RubyGems: should retrieve Compact Index info file (GET /rubygems/info/
expect(response.status).toEqual(200);
expect(response.headers['Content-Type']).toEqual('text/plain; charset=utf-8');
expect(response.body).toBeInstanceOf(Buffer);
const body = await streamToBuffer(response.body);
expect(body).toBeInstanceOf(Buffer);
const content = (response.body as Buffer).toString('utf-8');
const content = body.toString('utf-8');
expect(content).toContain('---');
expect(content).toContain(testVersion);
expect(content).toContain('checksum:');
@@ -104,9 +108,10 @@ tap.test('RubyGems: should retrieve Compact Index names file (GET /rubygems/name
expect(response.status).toEqual(200);
expect(response.headers['Content-Type']).toEqual('text/plain; charset=utf-8');
expect(response.body).toBeInstanceOf(Buffer);
const body = await streamToBuffer(response.body);
expect(body).toBeInstanceOf(Buffer);
const content = (response.body as Buffer).toString('utf-8');
const content = body.toString('utf-8');
expect(content).toContain('---');
expect(content).toContain(testGemName);
});
@@ -120,8 +125,9 @@ tap.test('RubyGems: should download gem file (GET /rubygems/gems/{gem}-{version}
});
expect(response.status).toEqual(200);
expect(response.body).toBeInstanceOf(Buffer);
expect((response.body as Buffer).length).toEqual(testGemData.length);
const body = await streamToBuffer(response.body);
expect(body).toBeInstanceOf(Buffer);
expect(body.length).toEqual(testGemData.length);
expect(response.headers['Content-Type']).toEqual('application/octet-stream');
});
@@ -153,7 +159,8 @@ tap.test('RubyGems: should list multiple versions in Compact Index', async () =>
expect(response.status).toEqual(200);
const content = (response.body as Buffer).toString('utf-8');
const body = await streamToBuffer(response.body);
const content = body.toString('utf-8');
const lines = content.split('\n');
const gemLine = lines.find(l => l.startsWith(`${testGemName} `));
@@ -172,7 +179,8 @@ tap.test('RubyGems: should list multiple versions in info file', async () => {
expect(response.status).toEqual(200);
const content = (response.body as Buffer).toString('utf-8');
const body = await streamToBuffer(response.body);
const content = body.toString('utf-8');
expect(content).toContain('1.0.0');
expect(content).toContain('2.0.0');
});
@@ -203,7 +211,8 @@ tap.test('RubyGems: should support platform-specific gems', async () => {
query: {},
});
const content = (versionsResponse.body as Buffer).toString('utf-8');
const versionsBody = await streamToBuffer(versionsResponse.body);
const content = versionsBody.toString('utf-8');
const lines = content.split('\n');
const gemLine = lines.find(l => l.startsWith(`${testGemName} `));
@@ -224,8 +233,9 @@ tap.test('RubyGems: should yank a gem version (DELETE /rubygems/api/v1/gems/yank
});
expect(response.status).toEqual(200);
expect(response.body).toHaveProperty('message');
expect((response.body as any).message).toContain('yanked');
const body = await streamToJson(response.body);
expect(body).toHaveProperty('message');
expect(body.message).toContain('yanked');
});
tap.test('RubyGems: should mark yanked version in Compact Index', async () => {
@@ -238,7 +248,8 @@ tap.test('RubyGems: should mark yanked version in Compact Index', async () => {
expect(response.status).toEqual(200);
const content = (response.body as Buffer).toString('utf-8');
const body = await streamToBuffer(response.body);
const content = body.toString('utf-8');
const lines = content.split('\n');
const gemLine = lines.find(l => l.startsWith(`${testGemName} `));
@@ -256,7 +267,8 @@ tap.test('RubyGems: should still allow downloading yanked gem', async () => {
});
expect(response.status).toEqual(200);
expect(response.body).toBeInstanceOf(Buffer);
const body = await streamToBuffer(response.body);
expect(body).toBeInstanceOf(Buffer);
});
tap.test('RubyGems: should unyank a gem version (PUT /rubygems/api/v1/gems/unyank)', async () => {
@@ -273,8 +285,9 @@ tap.test('RubyGems: should unyank a gem version (PUT /rubygems/api/v1/gems/unyan
});
expect(response.status).toEqual(200);
expect(response.body).toHaveProperty('message');
expect((response.body as any).message).toContain('unyanked');
const body = await streamToJson(response.body);
expect(body).toHaveProperty('message');
expect(body.message).toContain('unyanked');
});
tap.test('RubyGems: should remove yank marker after unyank', async () => {
@@ -287,7 +300,8 @@ tap.test('RubyGems: should remove yank marker after unyank', async () => {
expect(response.status).toEqual(200);
const content = (response.body as Buffer).toString('utf-8');
const body = await streamToBuffer(response.body);
const content = body.toString('utf-8');
const lines = content.split('\n');
const gemLine = lines.find(l => l.startsWith(`${testGemName} `));
@@ -309,9 +323,9 @@ tap.test('RubyGems: should retrieve versions JSON (GET /rubygems/api/v1/versions
expect(response.status).toEqual(200);
expect(response.headers['Content-Type']).toEqual('application/json');
expect(response.body).toBeTypeOf('object');
const json = await streamToJson(response.body);
expect(json).toBeTypeOf('object');
const json = response.body as any;
expect(json).toHaveProperty('name');
expect(json.name).toEqual(testGemName);
expect(json).toHaveProperty('versions');
@@ -331,9 +345,9 @@ tap.test('RubyGems: should retrieve dependencies JSON (GET /rubygems/api/v1/depe
expect(response.status).toEqual(200);
expect(response.headers['Content-Type']).toEqual('application/json');
expect(response.body).toBeTypeOf('object');
const json = await streamToJson(response.body);
expect(json).toBeTypeOf('object');
const json = response.body as any;
expect(Array.isArray(json)).toEqual(true);
});
@@ -346,7 +360,8 @@ tap.test('RubyGems: should retrieve gem spec (GET /rubygems/quick/Marshal.4.8/{g
});
expect(response.status).toEqual(200);
expect(response.body).toBeInstanceOf(Buffer);
const body = await streamToBuffer(response.body);
expect(body).toBeInstanceOf(Buffer);
});
tap.test('RubyGems: should support latest specs endpoint (GET /rubygems/latest_specs.4.8.gz)', async () => {
@@ -359,7 +374,8 @@ tap.test('RubyGems: should support latest specs endpoint (GET /rubygems/latest_s
expect(response.status).toEqual(200);
expect(response.headers['Content-Type']).toEqual('application/octet-stream');
expect(response.body).toBeInstanceOf(Buffer);
const body = await streamToBuffer(response.body);
expect(body).toBeInstanceOf(Buffer);
});
tap.test('RubyGems: should support specs endpoint (GET /rubygems/specs.4.8.gz)', async () => {
@@ -372,7 +388,8 @@ tap.test('RubyGems: should support specs endpoint (GET /rubygems/specs.4.8.gz)',
expect(response.status).toEqual(200);
expect(response.headers['Content-Type']).toEqual('application/octet-stream');
expect(response.body).toBeInstanceOf(Buffer);
const body = await streamToBuffer(response.body);
expect(body).toBeInstanceOf(Buffer);
});
tap.test('RubyGems: should return 404 for non-existent gem', async () => {
@@ -384,7 +401,8 @@ tap.test('RubyGems: should return 404 for non-existent gem', async () => {
});
expect(response.status).toEqual(404);
expect(response.body).toHaveProperty('error');
const body = await streamToJson(response.body);
expect(body).toHaveProperty('error');
});
tap.test('RubyGems: should return 401 for unauthorized upload', async () => {
@@ -402,7 +420,8 @@ tap.test('RubyGems: should return 401 for unauthorized upload', async () => {
});
expect(response.status).toEqual(401);
expect(response.body).toHaveProperty('error');
const body = await streamToJson(response.body);
expect(body).toHaveProperty('error');
});
tap.test('RubyGems: should return 401 for unauthorized yank', async () => {
@@ -419,7 +438,8 @@ tap.test('RubyGems: should return 401 for unauthorized yank', async () => {
});
expect(response.status).toEqual(401);
expect(response.body).toHaveProperty('error');
const body = await streamToJson(response.body);
expect(body).toHaveProperty('error');
});
tap.test('RubyGems: should handle gem with dependencies', async () => {
@@ -450,7 +470,8 @@ tap.test('RubyGems: should handle gem with dependencies', async () => {
expect(infoResponse.status).toEqual(200);
const content = (infoResponse.body as Buffer).toString('utf-8');
const infoBody = await streamToBuffer(infoResponse.body);
const content = infoBody.toString('utf-8');
expect(content).toContain('checksum:');
});