0
Fork 0
mirror of https://github.com/verdaccio/verdaccio.git synced 2024-12-16 21:56:25 -05:00

Merge pull request #1624 from verdaccio/e2e-pkg-poc

test: E2E with Verdaccio for our own CLI
This commit is contained in:
Juan Picado @jotadeveloper 2019-12-16 18:43:28 +01:00 committed by GitHub
commit 5189254fbb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 551 additions and 5 deletions

View file

@ -110,6 +110,14 @@ jobs:
- run:
name: Test End-to-End
command: yarn run test:e2e
test_e2e_cli:
<<: *defaults
executor: node_latest
steps:
- restore_repo
- run:
name: Test End-to-End ClI
command: yarn run test:e2e:cli
coverage:
<<: *defaults
@ -149,7 +157,15 @@ workflows:
- test_node_lts_10
- test_node_lts_12
<<: *ignore_non_dev_branches
- test_e2e_cli:
requires:
- prepare
- test_node_latest
- test_node_lts_10
- test_node_lts_12
<<: *ignore_non_dev_branches
- coverage:
requires:
- test_e2e
- test_e2e_cli
<<: *ignore_non_dev_branches

View file

@ -3,10 +3,10 @@
"@verdaccio"
],
"rules": {
"@typescript-eslint/no-var-requires": ["warn"],
"@typescript-eslint/no-var-requires": 0,
"@typescript-eslint/ban-ts-ignore": 0,
"@typescript-eslint/no-inferrable-types": ["warn"],
"@typescript-eslint/no-empty-function": ["warn"],
"@typescript-eslint/no-inferrable-types": 0,
"@typescript-eslint/no-empty-function": 0,
"@typescript-eslint/no-this-alias": ["warn"],
"@typescript-eslint/no-use-before-define": 0,
"@typescript-eslint/array-type": ["warn"],

View file

@ -78,6 +78,7 @@
"cross-env": "6.0.3",
"detect-secrets": "1.0.5",
"eslint": "5.16.0",
"fs-extra": "8.1.0",
"get-stdin": "7.0.0",
"husky": "2.7.0",
"in-publish": "2.0.0",
@ -92,6 +93,7 @@
"standard-version": "7.0.0",
"supertest": "4.0.2",
"typescript": "3.7.1-rc",
"verdaccio": "latest",
"verdaccio-auth-memory": "8.3.0",
"verdaccio-memory": "8.2.0"
},
@ -116,8 +118,9 @@
"test:clean": "npx jest --clearCache",
"test:unit": "cross-env NODE_ENV=test BABEL_ENV=test TZ=UTC FORCE_COLOR=1 jest --config ./jest.config.js --maxWorkers 2 --passWithNoTests",
"test:functional": "cross-env NODE_ENV=test jest --config ./test/jest.config.functional.js --testPathPattern ./test/functional/index* --passWithNoTests",
"test:e2e:cli": "cross-env NODE_ENV=test jest --config ./test/e2e-cli/jest.config.e2e.cli.js --passWithNoTests",
"test:e2e": "cross-env BABEL_ENV=test jest --config ./test/jest.config.e2e.js",
"test:all": "npm run test && npm run test:functional && npm run test:e2e",
"test:all": "npm run test && npm run test:functional && npm run test:e2e & npm run test:e2e:pkg",
"pre:ci": "npm run lint",
"coverage:publish": "codecov",
"lint": "npm run type-check && npm run lint:ts && npm run lint:lockfile",

View file

@ -24,6 +24,7 @@
}
],
"no-useless-escape": 0,
"@typescript-eslint/explicit-function-return-type": 0
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/no-empty-function": 0
}
}

6
test/e2e-cli/.eslintrc Normal file
View file

@ -0,0 +1,6 @@
{
"rules": {
"@typescript-eslint/no-var-requires": 0,
"@typescript-eslint/explicit-member-accessibility": 0
}
}

View file

@ -0,0 +1,37 @@
storage: ./storage
#store:
# memory:
# limit: 1000
auth:
htpasswd:
file: ./htpasswd
max_users: -1
web:
enable: true
title: verdaccio-e2e-pkg
uplinks:
npmjs:
url: https://registry.npmjs.org/
logs:
- { type: stdout, format: pretty, level: warn }
packages:
'@*/*':
access: $all
publish: $anonymous
unpublish: $authenticated
proxy: npmjs
'verdaccio':
access: $all
publish: $anonymous
'**':
access: $all
publish: $anonymous
unpublish: $authenticated
proxy: npmjs
_debug: true

View file

@ -0,0 +1,35 @@
storage: ./storage
#store:
# memory:
# limit: 1000
auth:
htpasswd:
file: ./htpasswd
max_users: -1
web:
enable: true
title: verdaccio-default
uplinks:
local:
url: http://localhost:4873
logs:
- { type: stdout, format: pretty, level: warn }
packages:
'@*/*':
access: $all
publish: $anonymous
unpublish: $authenticated
proxy: local
'**':
access: $all
publish: $all
unpublish: $authenticated
proxy: local
_debug: true

View file

@ -0,0 +1,4 @@
require('@babel/register')({
extensions: [".ts", ".js"]
});
module.exports = require('./setup/test_environment');

View file

@ -0,0 +1,4 @@
require('@babel/register')({
extensions: [".ts", ".js"]
});
module.exports = require('./setup/setup');

View file

@ -0,0 +1,4 @@
require('@babel/register')({
extensions: [".ts", ".js"]
});
module.exports = require('./setup/teardown');

View file

@ -0,0 +1,12 @@
const { defaults } = require('jest-config');
module.exports = {
name: 'verdaccio-e2e-cli-jest',
verbose: true,
collectCoverage: false,
moduleFileExtensions: [...defaults.moduleFileExtensions, 'ts'],
testEnvironment: './env_babel.js',
globalSetup: './env_setup.js',
globalTeardown: './env_teardown.js',
testRegex: '(/test/e2e.*\\.spec)\\.ts'
};

View file

View file

@ -0,0 +1,3 @@
# Simple project
This is a normal readme

View file

@ -0,0 +1,6 @@
module.exports = function() {
const message = "this is a basic project";
console.log(message);
return message;
};

View file

@ -0,0 +1,18 @@
{
"name": "basic-verdaccio",
"version": "1.0.0",
"description": "this is a basic project",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"verdaccio",
"sample"
],
"dependencies": {
"verdaccio": "latest"
},
"author": "Juan Picado <juanpicado19@gmail.com>",
"license": "MIT"
}

View file

@ -0,0 +1,3 @@
# Simple project
This is a normal readme

View file

@ -0,0 +1,6 @@
module.exports = function() {
const message = "this is a scoped basic project";
console.log(message);
return message;
};

View file

@ -0,0 +1,15 @@
{
"name": "@e2e-verdaccio/basic",
"version": "1.0.0",
"description": "this is a scoped basic project",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": ["verdaccio", "sample", "scoped"],
"dependencies": {
"verdaccio": "latest"
},
"author": "Juan Picado <juanpicado19@gmail.com>",
"license": "MIT"
}

View file

@ -0,0 +1,30 @@
import fs from "fs";
import path from "path";
import os from "os";
import {yellow} from "kleur";
import {spawn} from "child_process";
import { npm } from '../utils/process';
import * as __global from '../utils/global.js';
module.exports = async () => {
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'verdaccio-cli-e2e-'));
__global.addItem('dir-root', tempRoot);
console.log(yellow(`Add temp root folder: ${tempRoot}`));
fs.copyFileSync(
path.join(__dirname, '../config/_bootstrap_verdaccio.yaml'),
path.join(tempRoot, 'verdaccio.yaml'),
);
// @ts-ignore
global.__namespace = __global;
console.log(`current directory: ${process.cwd()}`);
// @ts-ignore
global.registryProcess = spawn(
'node',
[require.resolve('verdaccio/bin/verdaccio'), '-c', './verdaccio.yaml'],
// @ts-ignore
{ cwd: tempRoot, silence: true },
);
// publish current build version on local registry
await npm('publish', '--registry' ,'http://localhost:4873');
}

View file

@ -0,0 +1,4 @@
module.exports = async function() {
// @ts-ignore
global.registryProcess.kill();
};

View file

@ -0,0 +1,28 @@
const fs = require('fs');
import os from 'os';
import path from 'path';
import NodeEnvironment from 'jest-environment-node';
const __global = require('../utils/global');
// import { npm } from '../utils/process';
class E2ECliTestEnvironment extends NodeEnvironment {
constructor(config) {
super(config)
}
async setup() {
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'verdaccio-suite-test-'));
__global.addItem('dir-root', tempRoot);
this.global.__namespace = __global;
console.log(`current directory: ${process.cwd()}`);
}
async teardown() {}
runScript(script) {
return super.runScript(script);
}
}
export default E2ECliTestEnvironment;

View file

@ -0,0 +1,5 @@
import { silentNpm } from '../../utils/process';
export function installVerdaccio(verdaccioInstall) {
return silentNpm('install', '--prefix', verdaccioInstall, 'verdaccio', '--registry' ,'http://localhost:4873', '--no-package-lock');
}

View file

@ -0,0 +1,29 @@
import path from 'path';
import {runVerdaccio} from '../../utils/process';
import {installVerdaccio} from "../__partials/npm_commands";
describe('verdaccio info', ()=> {
jest.setTimeout(90000);
// @ts-ignore
const tempRootFolder = global.__namespace.getItem('dir-root');
const verdaccioInstall = path.join(tempRootFolder, 'verdaccio-root-info');
let registryProcess;
beforeAll(async () => {
await installVerdaccio(verdaccioInstall);
});
test('should run verdaccio info command', async () => {
const pathVerdaccioModule = require.resolve('verdaccio/bin/verdaccio', {
paths: [verdaccioInstall]
});
const hasMatch = await runVerdaccio(pathVerdaccioModule, verdaccioInstall, ['--info'], /Environment/);
expect(hasMatch.ok).toBeTruthy();
});
afterAll(() => {
registryProcess.kill();
});
});

View file

@ -0,0 +1,41 @@
import path from 'path';
import fs from "fs";
import {installVerdaccio} from "../__partials/npm_commands";
import {spawnRegistry} from "../../utils/registry";
import {callRegistry} from "../../utils/web";
describe('npm install', ()=> {
jest.setTimeout(90000);
const port = '9012';
// @ts-ignore
const tempRootFolder = global.__namespace.getItem('dir-root');
const verdaccioInstall = path.join(tempRootFolder, 'verdaccio-root-install');
let registryProcess;
const configPath = path.join(tempRootFolder, 'verdaccio.yaml');
beforeAll(async () => {
await installVerdaccio(verdaccioInstall);
fs.copyFileSync(path.join(__dirname, '../../config/default.yaml'), configPath);
});
test('should match the listing port and load metadata', async () => {
const pathVerdaccioModule = require.resolve('verdaccio/bin/verdaccio', {
paths: [verdaccioInstall]
});
registryProcess = await spawnRegistry(pathVerdaccioModule,
['-c', configPath, '-l', port],
{ cwd: verdaccioInstall, silent: true }
);
const body = await callRegistry(`http://localhost:${port}/verdaccio`);
const parsedBody = JSON.parse(body);
expect(parsedBody.name).toEqual('verdaccio');
});
afterAll(async () => {
registryProcess.kill();
});
});

View file

@ -0,0 +1,54 @@
import path from 'path';
import fs from "fs";
import * as __global from "../../utils/global";
import {spawnRegistry} from "../../utils/registry";
import {execAndWaitForOutputToMatch} from '../../utils/process';
import {installVerdaccio} from "../__partials/npm_commands";
import {expectFileToExist} from "../../utils/expect";
describe('npm install', ()=> {
jest.setTimeout(90000);
const port = '9011';
// @ts-ignore
const tempRootFolder = global.__namespace.getItem('dir-root');
const verdaccioInstall = path.join(tempRootFolder, 'verdaccio-root-install');
let registryProcess;
beforeAll(async () => {
await installVerdaccio(verdaccioInstall);
const configPath = path.join(tempRootFolder, 'verdaccio.yaml');
fs.copyFileSync(path.join(__dirname, '../../config/default.yaml'), configPath);
// @ts-ignore
global.__namespace = __global;
const pathVerdaccioModule = require.resolve('verdaccio/bin/verdaccio', {
paths: [verdaccioInstall]
});
registryProcess = await spawnRegistry(pathVerdaccioModule,
['-c', configPath, '-l', port],
{ cwd: verdaccioInstall, silent: true }
);
});
test('should match on npm info verdaccio', async () => {
// FIXME: not the best match, looking for a better way to match the terminal output
const output = await execAndWaitForOutputToMatch('npm', ['info', 'verdaccio', '--registry'], /A lightweight private npm proxy registry/);
expect(output.ok).toBeTruthy();
});
test('should install jquery', async () => {
const testCwd = path.join(tempRootFolder, '_jquery_');
await execAndWaitForOutputToMatch('npm', ['install', '--prefix', testCwd, 'jquery', '--registry' ,`http://localhost:${port}`], /''/, {
cwd: verdaccioInstall
});
const exist = await expectFileToExist(path.join(testCwd, 'node_modules', 'jquery', 'package.json'));
expect(exist).toBeTruthy();
});
afterAll(async () => {
registryProcess.kill();
});
});

View file

@ -0,0 +1,13 @@
import * as fs from 'fs-extra';
export function expectFileToExist(fileName: string) {
return new Promise((resolve, reject) => {
fs.exists(fileName, (exist) => {
if (exist) {
resolve(exist);
} else {
reject(new Error(`File ${fileName} was expected to exist but not found...`));
}
});
});
}

View file

@ -0,0 +1,14 @@
const namespace = Object.create(null);
exports.addItem = function(name, value) {
namespace[name] = value;
}
exports.getItem = function(name) {
console.log("get-item", name, namespace);
if (!(name in namespace)) {
throw new Error("The item ".concat(name, " does exist in the namespace"));
}
return namespace[name];
}

View file

@ -0,0 +1,97 @@
import * as child_process from 'child_process';
import {SpawnOptions} from "child_process";
export async function _exec(options, cmd, args) {
let stdout = '';
let stderr = '';
const flags = [];
const cwd = process.cwd();
const env = options.env;
console.log(`Running \`${cmd} ${args.map(x => `"${x}"`).join(' ')}\`${flags}...`);
console.log(`CWD: ${cwd}`);
console.log(`ENV: ${JSON.stringify(env)}`);
const spawnOptions = {
cwd,
...env ? { env } : {},
};
if (process.platform.startsWith('win')) {
args.unshift('/c', cmd);
cmd = 'cmd.exe';
spawnOptions['stdio'] = 'pipe';
}
const childProcess = child_process.spawn(cmd, args, spawnOptions);
childProcess.stdout.on('data', (data) => {
stdout += data.toString('utf-8');
if (options.silent) {
return;
}
data.toString('utf-8')
.split(/[\n\r]+/)
.filter(line => line !== '')
.forEach(line => console.log(' ' + line));
});
childProcess.stderr.on('data', (data) => {
stderr += data.toString('utf-8');
if (options.silent) {
return;
}
data.toString('utf-8')
.split(/[\n\r]+/)
.filter(line => line !== '')
.forEach(line => console.error((' ' + line)));
});
const err = new Error(`Running "${cmd} ${args.join(' ')}" returned error code `);
return new Promise((resolve, reject) => {
childProcess.on('exit', (error) => {
if (!error) {
resolve({ stdout, stderr });
} else {
err.message += `${error}...\n\nSTDOUT:\n${stdout}\n\nSTDERR:\n${stderr}\n`;
reject(err);
}
});
if (options.waitForMatch) {
const match = options.waitForMatch;
childProcess.stdout.on('data', (data) => {
// console.log("-->data==>", data.toString(), data.toString().match(match));
if (data.toString().match(match)) {
resolve({ok: true, stdout, stderr });
}
});
childProcess.stderr.on('data', (data) => {
if (data.toString().match(match)) {
resolve({ stdout, stderr });
}
});
}
});
}
export function execAndWaitForOutputToMatch(
cmd: string,
args: string[],
match: RegExp,
spawnOptions: SpawnOptions = {}): any {
return _exec({ waitForMatch: match, ...spawnOptions, silence: true }, cmd, args);
}
export function npm(...args) {
return _exec({}, 'npm', args);
}
export function runVerdaccio(cmd, installation, args, match: RegExp): any {
return _exec({ cwd: installation, silent: true, waitForMatch: match }, cmd, args);
}
export function silentNpm(...args) {
return _exec({silent: true}, 'npm', args);
}

View file

@ -0,0 +1,26 @@
import {fork} from "child_process";
export function prepareEnvironment(rootFolder: string, folderName: string) {
}
export function spawnRegistry(
verdaccioPath: string,
args: string[],
childOptions) {
return new Promise((resolve, reject) => {
let _childOptions = {silent: true, ...childOptions};
const childFork = fork(verdaccioPath, args, _childOptions);
childFork.on('message', (msg) => {
if ('verdaccio_started' in msg) {
resolve(childFork);
}
});
childFork.on('error', (err) => reject([err]));
childFork.on('disconnect', (err) => reject([err]));
childFork.on('exit', (err) => reject([err]));
});
}

View file

@ -0,0 +1,9 @@
import path from "path";
import fs from "fs";
export function copyConfigFile(rootFolder, configTemplate): string {
const configPath = path.join(rootFolder, 'verdaccio.yaml');
fs.copyFileSync(path.join(__dirname, configTemplate), configPath);
return configPath;
}

23
test/e2e-cli/utils/web.ts Normal file
View file

@ -0,0 +1,23 @@
import {IncomingMessage} from 'http';
import request from 'request';
export function callRegistry(url: string): Promise<string> {
return new Promise((resolve, reject) => {
let options = {
url: url,
headers: { 'Accept': 'application/json' },
};
// @ts-ignore
request(options, (error: any, response: IncomingMessage, body: string) => {
if (error) {
reject(error);
// @ts-ignore
} else if (response.statusCode >= 400) {
reject(new Error(`Requesting "${url}" returned status code ${response.statusCode}.`));
} else {
resolve(body);
}
});
});
}

BIN
yarn.lock

Binary file not shown.