Speed up test suite by using local npm.js stub

This commit is contained in:
Michael Jackson 2019-08-01 18:01:12 -07:00
parent f362fa9717
commit c3dc2dd014
16 changed files with 89 additions and 71 deletions

View File

@ -3,9 +3,9 @@ module.exports = {
'\\.css$': '<rootDir>/modules/__mocks__/styleMock.js', '\\.css$': '<rootDir>/modules/__mocks__/styleMock.js',
'\\.png$': '<rootDir>/modules/__mocks__/imageMock.js', '\\.png$': '<rootDir>/modules/__mocks__/imageMock.js',
'entry-manifest': '<rootDir>/modules/__mocks__/entryManifest.js', 'entry-manifest': '<rootDir>/modules/__mocks__/entryManifest.js',
'getStats\\.js': '<rootDir>/modules/__mocks__/getStatsMock.js' 'getStats\\.js': '<rootDir>/modules/__mocks__/getStatsMock.js',
'utils\\/npm\\.js': '<rootDir>/modules/__mocks__/npmMock.js'
}, },
testMatch: ['**/__tests__/*-test.js'], testMatch: ['**/__tests__/*-test.js'],
testURL: 'http://localhost/', testURL: 'http://localhost/'
setupTestFrameworkScriptFile: './jest.setup.js'
}; };

View File

@ -1,4 +0,0 @@
// TODO: Mock out the registry so tests don't actually hit
// the real registry so they don't take so long. Then we can
// remove this.
jest.setTimeout(10000);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,33 @@
import fs from 'fs';
import path from 'path';
function getPackageInfo(packageName) {
const file = path.resolve(__dirname, `./metadata/${packageName}.json`);
try {
return JSON.parse(fs.readFileSync(file, 'utf-8'));
} catch (error) {
return null;
}
}
export function getVersionsAndTags(packageName) {
const info = getPackageInfo(packageName);
return info
? { versions: Object.keys(info.versions), tags: info['dist-tags'] }
: [];
}
export function getPackageConfig(packageName, version) {
const info = getPackageInfo(packageName);
return info ? info.versions[version] : null;
}
export function getPackage(packageName, version) {
const file = path.resolve(
__dirname,
`./packages/${packageName}-${version}.tgz`
);
return fs.existsSync(file) ? fs.createReadStream(file) : null;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -10,7 +10,7 @@ describe('A request for a JavaScript file', () => {
it('returns 200', done => { it('returns 200', done => {
request(server) request(server)
.get('/react@16.8.6/index.js') .get('/react@16.8.0/index.js')
.end((err, res) => { .end((err, res) => {
expect(res.statusCode).toBe(200); expect(res.statusCode).toBe(200);
expect(res.headers['content-type']).toMatch( expect(res.headers['content-type']).toMatch(

View File

@ -10,7 +10,7 @@ describe('A request for metadata', () => {
it('returns 200', done => { it('returns 200', done => {
request(server) request(server)
.get('/react@16.8.6/?meta') .get('/react@16.8.0/?meta')
.end((err, res) => { .end((err, res) => {
expect(res.statusCode).toBe(200); expect(res.statusCode).toBe(200);
expect(res.headers['content-type']).toMatch(/\bapplication\/json\b/); expect(res.headers['content-type']).toMatch(/\bapplication\/json\b/);

View File

@ -1,11 +1,12 @@
import { renderToString, renderToStaticMarkup } from 'react-dom/server'; import { renderToString, renderToStaticMarkup } from 'react-dom/server';
import semver from 'semver';
import BrowseApp from '../client/browse/App.js'; import BrowseApp from '../client/browse/App.js';
import MainTemplate from '../templates/MainTemplate.js'; import MainTemplate from '../templates/MainTemplate.js';
import asyncHandler from '../utils/asyncHandler.js'; import asyncHandler from '../utils/asyncHandler.js';
import getScripts from '../utils/getScripts.js'; import getScripts from '../utils/getScripts.js';
import { createElement, createHTML } from '../utils/markup.js'; import { createElement, createHTML } from '../utils/markup.js';
import { getAvailableVersions } from '../utils/npm.js'; import { getVersionsAndTags } from '../utils/npm.js';
const doctype = '<!DOCTYPE html>'; const doctype = '<!DOCTYPE html>';
const globalURLs = const globalURLs =
@ -21,6 +22,15 @@ const globalURLs =
'react-dom': '/react-dom@16.8.6/umd/react-dom.development.js' 'react-dom': '/react-dom@16.8.6/umd/react-dom.development.js'
}; };
function byVersion(a, b) {
return semver.lt(a, b) ? -1 : semver.gt(a, b) ? 1 : 0;
}
async function getAvailableVersions(packageName) {
const versionsAndTags = await getVersionsAndTags(packageName);
return versionsAndTags ? versionsAndTags.versions.sort(byVersion) : [];
}
async function serveBrowsePage(req, res) { async function serveBrowsePage(req, res) {
const availableVersions = await getAvailableVersions(req.packageName); const availableVersions = await getAvailableVersions(req.packageName);
const data = { const data = {

View File

@ -1,6 +1,8 @@
import semver from 'semver';
import asyncHandler from '../utils/asyncHandler.js'; import asyncHandler from '../utils/asyncHandler.js';
import createPackageURL from '../utils/createPackageURL.js'; import createPackageURL from '../utils/createPackageURL.js';
import { getPackageConfig, resolveVersion } from '../utils/npm.js'; import { getPackageConfig, getVersionsAndTags } from '../utils/npm.js';
function semverRedirect(req, res, newVersion) { function semverRedirect(req, res, newVersion) {
res res
@ -15,6 +17,24 @@ function semverRedirect(req, res, newVersion) {
); );
} }
async function resolveVersion(packageName, range) {
const versionsAndTags = await getVersionsAndTags(packageName);
if (versionsAndTags) {
const { versions, tags } = versionsAndTags;
if (range in tags) {
range = tags[range];
}
return versions.includes(range)
? range
: semver.maxSatisfying(versions, range);
}
return null;
}
/** /**
* Check the package version/tag in the URL and make sure it's good. Also * Check the package version/tag in the URL and make sure it's good. Also
* fetch the package config and add it to req.packageConfig. Redirect to * fetch the package config and add it to req.packageConfig. Redirect to

View File

@ -1,7 +1,6 @@
import url from 'url'; import url from 'url';
import https from 'https'; import https from 'https';
import LRUCache from 'lru-cache'; import LRUCache from 'lru-cache';
import semver from 'semver';
import debug from './debug.js'; import debug from './debug.js';
import bufferStream from './bufferStream.js'; import bufferStream from './bufferStream.js';
@ -77,18 +76,16 @@ async function fetchPackageInfo(packageName) {
async function fetchVersionsAndTags(packageName) { async function fetchVersionsAndTags(packageName) {
const info = await fetchPackageInfo(packageName); const info = await fetchPackageInfo(packageName);
return info && info.versions
if (info && info.versions) { ? { versions: Object.keys(info.versions), tags: info['dist-tags'] }
return { : null;
versions: Object.keys(info.versions),
tags: info['dist-tags']
};
}
return null;
} }
async function getVersionsAndTags(packageName) { /**
* Returns an object of available { versions, tags }.
* Uses a cache to avoid over-fetching from the registry.
*/
export async function getVersionsAndTags(packageName) {
const cacheKey = `versions-${packageName}`; const cacheKey = `versions-${packageName}`;
const cacheValue = cache.get(cacheKey); const cacheValue = cache.get(cacheKey);
@ -107,45 +104,6 @@ async function getVersionsAndTags(packageName) {
return value; return value;
} }
function byVersion(a, b) {
return semver.lt(a, b) ? -1 : semver.gt(a, b) ? 1 : 0;
}
/**
* Returns an array of available versions, sorted by semver.
*/
export async function getAvailableVersions(packageName) {
const versionsAndTags = await getVersionsAndTags(packageName);
if (versionsAndTags) {
return versionsAndTags.versions.sort(byVersion);
}
return [];
}
/**
* Resolves the semver range or tag to a valid version.
* Output is cached to avoid over-fetching from the registry.
*/
export async function resolveVersion(packageName, range) {
const versionsAndTags = await getVersionsAndTags(packageName);
if (versionsAndTags) {
const { versions, tags } = versionsAndTags;
if (range in tags) {
range = tags[range];
}
return versions.includes(range)
? range
: semver.maxSatisfying(versions, range);
}
return null;
}
// All the keys that sometimes appear in package info // All the keys that sometimes appear in package info
// docs that we don't need. There are probably more. // docs that we don't need. There are probably more.
const packageConfigExcludeKeys = [ const packageConfigExcludeKeys = [
@ -160,10 +118,10 @@ const packageConfigExcludeKeys = [
'scripts' 'scripts'
]; ];
function cleanPackageConfig(doc) { function cleanPackageConfig(config) {
return Object.keys(doc).reduce((memo, key) => { return Object.keys(config).reduce((memo, key) => {
if (!key.startsWith('_') && !packageConfigExcludeKeys.includes(key)) { if (!key.startsWith('_') && !packageConfigExcludeKeys.includes(key)) {
memo[key] = doc[key]; memo[key] = config[key];
} }
return memo; return memo;
@ -172,17 +130,14 @@ function cleanPackageConfig(doc) {
async function fetchPackageConfig(packageName, version) { async function fetchPackageConfig(packageName, version) {
const info = await fetchPackageInfo(packageName); const info = await fetchPackageInfo(packageName);
return info && info.versions && version in info.versions
if (!info || !(version in info.versions)) { ? cleanPackageConfig(info.versions[version])
return null; : null;
}
return cleanPackageConfig(info.versions[version]);
} }
/** /**
* Returns metadata about a package, mostly the same as package.json. * Returns metadata about a package, mostly the same as package.json.
* Output is cached to avoid over-fetching from the registry. * Uses a cache to avoid over-fetching from the registry.
*/ */
export async function getPackageConfig(packageName, version) { export async function getPackageConfig(packageName, version) {
const cacheKey = `config-${packageName}-${version}`; const cacheKey = `config-${packageName}-${version}`;