0
Fork 0
mirror of https://github.com/verdaccio/verdaccio.git synced 2025-03-11 02:15:57 -05:00

feat(experiment): accept async tarball_url_redirect function ()

* chore: await tarball_url_redirect function

* test: unit test async tarball_url_redirect

* chore: prettier format

* chore: refactor redirectOrDownloadStream to better error handling

* refactor tests

* format

* format

---------

Co-authored-by: Juan Picado <juanpicado19@gmail.com>
This commit is contained in:
Tiago Mota 2023-10-07 22:49:28 +01:00 committed by GitHub
parent 4ddb220ba5
commit e174e8c554
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 245 additions and 189 deletions
src/api/endpoint/api
test

View file

@ -34,32 +34,39 @@ const downloadStream = (
stream.pipe(res); stream.pipe(res);
}; };
const redirectOrDownloadStream = ( const redirectOrDownloadStream = async (
packageName: string, packageName: string,
filename: string, filename: string,
storage: any, storage: any,
req: $RequestExtend, req: $RequestExtend,
res: $ResponseExtend, res: $ResponseExtend,
config: Config config: Config
): void => { ): Promise<void> => {
const tarballUrlRedirect = _.get(config, 'experiments.tarball_url_redirect'); try {
storage const hasLocalTarball = await storage.hasLocalTarball(packageName, filename);
.hasLocalTarball(packageName, filename)
.then((hasLocalTarball) => { if (!hasLocalTarball) {
if (hasLocalTarball) { return downloadStream(packageName, filename, storage, req, res);
const context = { packageName, filename }; }
const tarballUrl =
typeof tarballUrlRedirect === 'function' const tarballUrlRedirect = _.get(config, 'experiments.tarball_url_redirect');
? tarballUrlRedirect(context) const context = { packageName, filename };
: _.template(tarballUrlRedirect)(context); let tarballUrl;
res.redirect(tarballUrl);
if (typeof tarballUrlRedirect === 'function') {
if (tarballUrlRedirect.constructor.name === 'AsyncFunction') {
tarballUrl = await tarballUrlRedirect(context);
} else { } else {
downloadStream(packageName, filename, storage, req, res); tarballUrl = tarballUrlRedirect(context);
} }
}) } else {
.catch((err) => { tarballUrl = _.template(tarballUrlRedirect)(context);
res.locals.report_error(err); }
});
res.redirect(tarballUrl);
} catch (err) {
res.locals.report_error(err);
}
}; };
export default function (route: Router, auth: Auth, storage: Storage, config: Config): void { export default function (route: Router, auth: Auth, storage: Storage, config: Config): void {

View file

@ -5,6 +5,7 @@ import path from 'path';
import { errorUtils } from '@verdaccio/core'; import { errorUtils } from '@verdaccio/core';
import { final } from '@verdaccio/middleware'; import { final } from '@verdaccio/middleware';
import { ConfigYaml } from '@verdaccio/types';
import { generateRandomHexString } from '@verdaccio/utils'; import { generateRandomHexString } from '@verdaccio/utils';
import { errorReportingMiddleware, handleError } from '../../src/api/middleware'; import { errorReportingMiddleware, handleError } from '../../src/api/middleware';
@ -14,12 +15,12 @@ import Config from '../../src/lib/config';
const debug = buildDebug('verdaccio:tools:helpers:server'); const debug = buildDebug('verdaccio:tools:helpers:server');
export async function initializeServer( export async function initializeServer(
configName, configuration: ConfigYaml,
routesMiddleware: any[] = [], routesMiddleware: any[] = [],
Storage Storage
): Promise<Application> { ): Promise<Application> {
const app = express(); const app = express();
const config = new Config(configName); const config = new Config(configuration);
config.storage = path.join(os.tmpdir(), '/storage', generateRandomHexString()); config.storage = path.join(os.tmpdir(), '/storage', generateRandomHexString());
// httpass would get path.basename() for configPath thus we need to create a dummy folder // httpass would get path.basename() for configPath thus we need to create a dummy folder
// to avoid conflics // to avoid conflics

View file

@ -5,7 +5,7 @@ import supertest from 'supertest';
import { parseConfigFile } from '@verdaccio/config'; import { parseConfigFile } from '@verdaccio/config';
import { HEADERS, HEADER_TYPE, HTTP_STATUS, TOKEN_BEARER } from '@verdaccio/core'; import { HEADERS, HEADER_TYPE, HTTP_STATUS, TOKEN_BEARER } from '@verdaccio/core';
import { GenericBody, PackageUsers } from '@verdaccio/types'; import { ConfigYaml, GenericBody, PackageUsers } from '@verdaccio/types';
import { buildToken, generateRandomHexString } from '@verdaccio/utils'; import { buildToken, generateRandomHexString } from '@verdaccio/utils';
import apiMiddleware from '../../../../src/api/endpoint'; import apiMiddleware from '../../../../src/api/endpoint';
@ -24,11 +24,15 @@ export const getConf = (conf) => {
return config; return config;
}; };
export async function initializeServer(configName): Promise<Application> { export async function initializeServer(configName: string): Promise<Application> {
const config = getConf(configName); const config = getConf(configName);
return initializeServerHelper(config, [apiMiddleware], Storage); return initializeServerHelper(config, [apiMiddleware], Storage);
} }
export async function initializeServerWithConfig(config: ConfigYaml): Promise<Application> {
return initializeServerHelper(config, [apiMiddleware], Storage);
}
export function createUser(app, name: string, password: string): supertest.Test { export function createUser(app, name: string, password: string): supertest.Test {
return supertest(app) return supertest(app)
.put(`/-/user/org.couchdb.user:${name}`) .put(`/-/user/org.couchdb.user:${name}`)

View file

@ -0,0 +1,25 @@
storage: ./storage_experiments
auth:
htpasswd:
file: ./htpasswd-package
web:
enable: true
title: verdaccio
publish:
allow_offline: false
uplinks:
log: { type: stdout, format: pretty, level: trace }
packages:
'@*/*':
access: $anonymous
publish: $anonymous
'**':
access: $anonymous
publish: $anonymous
_debug: true

View file

@ -0,0 +1,150 @@
import supertest from 'supertest';
import { DIST_TAGS, HEADERS, HEADER_TYPE, HTTP_STATUS } from '@verdaccio/core';
import { getConf, initializeServer, initializeServerWithConfig, publishVersion } from './_helper';
describe('experiments', () => {
describe('for a function value of tarball_url_redirect', () => {
let app;
beforeEach(async () => {
const baseTestConfig = getConf('experiments.yaml');
app = await initializeServerWithConfig({
...baseTestConfig,
experiments: {
// @ts-ignore
tarball_url_redirect(context) {
return `https://myapp.sfo1.mycdn.com/verdaccio/${context.packageName}/${context.filename}`;
},
},
});
await publishVersion(app, '@tarball_tester/testTarballPackage', '1.0.0');
await publishVersion(app, 'testTarballPackage', '1.0.0');
});
test('should redirect for package tarball as function', (done) => {
supertest(app)
.get('/testTarballPackage/-/testTarballPackage-1.0.0.tgz')
.expect(302)
.end(function (err, res) {
if (err) {
return done(err);
}
expect(res.headers.location).toEqual(
'https://myapp.sfo1.mycdn.com/verdaccio/testTarballPackage/testTarballPackage-1.0.0.tgz'
);
done();
});
});
test('should redirect for scoped package tarball', (done) => {
supertest(app)
.get('/@tarball_tester/testTarballPackage/-/testTarballPackage-1.0.0.tgz')
.expect(302)
.end(function (err, res) {
if (err) {
return done(err);
}
expect(res.headers.location).toEqual(
'https://myapp.sfo1.mycdn.com/verdaccio/@tarball_tester/testTarballPackage/testTarballPackage-1.0.0.tgz'
);
done();
});
});
});
describe('for a async function value of tarball_url_redirect', () => {
let app;
beforeEach(async () => {
const baseTestConfig = getConf('experiments.yaml');
app = await initializeServerWithConfig({
...baseTestConfig,
experiments: {
// @ts-ignore
async tarball_url_redirect(context) {
await new Promise((resolve) => {
setTimeout(resolve, 1000);
});
return `https://myapp.sfo1.mycdn.com/verdaccio/${context.packageName}/${context.filename}`;
},
},
});
await publishVersion(app, '@tarball_tester/testTarballPackage', '1.0.0');
await publishVersion(app, 'testTarballPackage', '1.0.0');
});
test('should redirect for package tarball', (done) => {
supertest(app)
.get('/testTarballPackage/-/testTarballPackage-1.0.0.tgz')
.expect(302)
.end(function (err, res) {
if (err) {
return done(err);
}
expect(res.headers.location).toEqual(
'https://myapp.sfo1.mycdn.com/verdaccio/testTarballPackage/testTarballPackage-1.0.0.tgz'
);
done();
});
});
test('should redirect for scoped package tarball', (done) => {
supertest(app)
.get('/@tarball_tester/testTarballPackage/-/testTarballPackage-1.0.0.tgz')
.expect(302)
.end(function (err, res) {
if (err) {
return done(err);
}
expect(res.headers.location).toEqual(
'https://myapp.sfo1.mycdn.com/verdaccio/@tarball_tester/testTarballPackage/testTarballPackage-1.0.0.tgz'
);
done();
});
});
});
describe('for a string value of tarball_url_redirect', () => {
let app;
beforeEach(async () => {
const baseTestConfig = getConf('experiments.yaml');
app = await initializeServerWithConfig({
...baseTestConfig,
experiments: {
// @ts-ignore
tarball_url_redirect: 'https://myapp.sfo1.mycdn.com/verdaccio/${packageName}/${filename}',
},
});
await publishVersion(app, '@tarball_tester/testTarballPackage', '1.0.0');
await publishVersion(app, 'testTarballPackage', '1.0.0');
});
test('should redirect for scoped package tarball', (done) => {
supertest(app)
.get('/@tarball_tester/testTarballPackage/-/testTarballPackage-1.0.0.tgz')
.expect(302)
.end(function (err, res) {
if (err) {
return done(err);
}
expect(res.headers.location).toEqual(
'https://myapp.sfo1.mycdn.com/verdaccio/@tarball_tester/testTarballPackage/testTarballPackage-1.0.0.tgz'
);
done();
});
});
test('should redirect for package tarball', (done) => {
supertest(app)
.get('/testTarballPackage/-/testTarballPackage-1.0.0.tgz')
.expect(302)
.end(function (err, res) {
if (err) {
return done(err);
}
expect(res.headers.location).toEqual(
'https://myapp.sfo1.mycdn.com/verdaccio/testTarballPackage/testTarballPackage-1.0.0.tgz'
);
done();
});
});
});
});

View file

@ -1,7 +1,7 @@
import _ from 'lodash'; import _ from 'lodash';
import nock from 'nock'; import nock from 'nock';
import path from 'path'; import path from 'path';
import rimraf from 'rimraf'; import { rimrafSync } from 'rimraf';
import { Readable } from 'stream'; import { Readable } from 'stream';
import request from 'supertest'; import request from 'supertest';
@ -63,44 +63,43 @@ describe('endpoint unit test', () => {
const mockServerPort = 55549; const mockServerPort = 55549;
let mockRegistry; let mockRegistry;
beforeAll(function (done) { beforeAll(async function () {
const store = path.join(__dirname, '../../partials/store/test-storage-api-spec'); const store = path.join(__dirname, '../../partials/store/test-storage-api-spec');
rimraf(store, async () => { rimrafSync(store);
const configForTest = configDefault(
{
auth: {
htpasswd: {
file: './test-storage-api-spec/.htpasswd',
},
},
filters: {
'../../modules/api/partials/plugin/filter': {
pkg: 'npm_test',
version: '2.0.0',
},
},
storage: store,
self_path: store,
uplinks: {
npmjs: {
url: `http://${DOMAIN_SERVERS}:${mockServerPort}`,
},
socketTimeout: {
url: `http://some.registry.timeout.com`,
max_fails: 2,
timeout: '1s',
fail_timeout: '1s',
},
},
log: { type: 'stdout', format: 'pretty', level: 'warn' },
},
'api.spec.yaml'
);
app = await endPointAPI(configForTest); const configForTest = configDefault(
mockRegistry = await mockServer(mockServerPort).init(); {
done(); auth: {
}); htpasswd: {
file: './test-storage-api-spec/.htpasswd',
},
},
filters: {
'../../modules/api/partials/plugin/filter': {
pkg: 'npm_test',
version: '2.0.0',
},
},
storage: store,
self_path: store,
uplinks: {
npmjs: {
url: `http://${DOMAIN_SERVERS}:${mockServerPort}`,
},
socketTimeout: {
url: `http://some.registry.timeout.com`,
max_fails: 2,
timeout: '1s',
fail_timeout: '1s',
},
},
log: { type: 'stdout', format: 'pretty', level: 'warn' },
},
'api.spec.yaml'
);
app = await endPointAPI(configForTest);
mockRegistry = await mockServer(mockServerPort).init();
}); });
afterAll(function (done) { afterAll(function (done) {
@ -1016,136 +1015,6 @@ describe('endpoint unit test', () => {
}); });
}); });
describe('should test tarball url redirect', () => {
const pkgName = 'testTarballPackage';
const scopedPkgName = '@tarball_tester/testTarballPackage';
const tarballUrlRedirectCredentials = { name: 'tarball_tester', password: 'secretPass' };
const store = path.join(__dirname, '../../partials/store/test-storage-api-spec');
const mockServerPort = 55549;
const baseTestConfig = configDefault(
{
auth: {
htpasswd: {
file: './test-storage-api-spec/.htpasswd',
},
},
filters: {
'../../modules/api/partials/plugin/filter': {
pkg: 'npm_test',
version: '2.0.0',
},
},
storage: store,
self_path: store,
uplinks: {
npmjs: {
url: `http://${DOMAIN_SERVERS}:${mockServerPort}`,
},
},
log: { type: 'stdout', format: 'pretty', level: 'warn' },
},
'api.spec.yaml'
);
let token;
beforeAll(async () => {
token = await getNewToken(request(app), tarballUrlRedirectCredentials);
await putPackage(request(app), `/${pkgName}`, generatePackageMetadata(pkgName), token);
await putPackage(
request(app),
`/${scopedPkgName}`,
generatePackageMetadata(scopedPkgName),
token
);
});
describe('for a string value of tarball_url_redirect', () => {
let app2;
beforeAll(async () => {
app2 = await endPointAPI({
...baseTestConfig,
experiments: {
tarball_url_redirect:
'https://myapp.sfo1.mycdn.com/verdaccio/${packageName}/${filename}',
},
});
});
test('should redirect for package tarball', (done) => {
request(app2)
.get('/testTarballPackage/-/testTarballPackage-1.0.0.tgz')
.expect(HTTP_STATUS.REDIRECT)
.end(function (err, res) {
if (err) {
return done(err);
}
expect(res.headers.location).toEqual(
'https://myapp.sfo1.mycdn.com/verdaccio/testTarballPackage/testTarballPackage-1.0.0.tgz'
);
done();
});
});
test('should redirect for scoped package tarball', (done) => {
request(app2)
.get('/@tarball_tester/testTarballPackage/-/testTarballPackage-1.0.0.tgz')
.expect(HTTP_STATUS.REDIRECT)
.end(function (err, res) {
if (err) {
return done(err);
}
expect(res.headers.location).toEqual(
'https://myapp.sfo1.mycdn.com/verdaccio/@tarball_tester/testTarballPackage/testTarballPackage-1.0.0.tgz'
);
done();
});
});
});
describe('for a function value of tarball_url_redirect', () => {
let app2;
beforeAll(async () => {
app2 = await endPointAPI({
...baseTestConfig,
experiments: {
tarball_url_redirect(context) {
return `https://myapp.sfo1.mycdn.com/verdaccio/${context.packageName}/${context.filename}`;
},
},
});
});
test('should redirect for package tarball', (done) => {
request(app2)
.get('/testTarballPackage/-/testTarballPackage-1.0.0.tgz')
.expect(HTTP_STATUS.REDIRECT)
.end(function (err, res) {
if (err) {
return done(err);
}
expect(res.headers.location).toEqual(
'https://myapp.sfo1.mycdn.com/verdaccio/testTarballPackage/testTarballPackage-1.0.0.tgz'
);
done();
});
});
test('should redirect for scoped package tarball', (done) => {
request(app2)
.get('/@tarball_tester/testTarballPackage/-/testTarballPackage-1.0.0.tgz')
.expect(HTTP_STATUS.REDIRECT)
.end(function (err, res) {
if (err) {
return done(err);
}
expect(res.headers.location).toEqual(
'https://myapp.sfo1.mycdn.com/verdaccio/@tarball_tester/testTarballPackage/testTarballPackage-1.0.0.tgz'
);
done();
});
});
});
});
describe('should test (un)deprecate api', () => { describe('should test (un)deprecate api', () => {
const pkgName = '@scope/deprecate'; const pkgName = '@scope/deprecate';
const credentials = { name: 'jota_deprecate', password: 'secretPass' }; const credentials = { name: 'jota_deprecate', password: 'secretPass' };