1
0
mirror of https://github.com/verdaccio/verdaccio.git synced 2024-12-24 21:15:51 +01:00

refactor: test api refactor (#1826)

* test: add test for whoami

* Update middleware.ts

* test for user api

* more test for user api

* remove repeated code

* refactor

* Update index.spec.ts

* add package test

refactor others

* chore: upgrade deps

* chore: add test for package

* chore: update test

* update lock file

* Update ci.yml

* Update ci.yml

* Update package.spec.ts

* chore: update ci settings

* chore: update deps

* chore: update test
This commit is contained in:
Juan Picado 2020-06-30 21:55:14 +02:00
parent d3f78e5b34
commit a7ba76423e
49 changed files with 1059 additions and 436 deletions

@ -13,6 +13,9 @@ Dockerfile
*.html *.html
*.scss *.scss
*.png *.png
*.json
*.name
*.tgz
*.jpg *.jpg
*.sh *.sh
**/partials/** **/partials/**

@ -1,102 +1,21 @@
{ {
"extends": [ "extends": [
"eslint:recommended", "@verdaccio"
"google",
"plugin:react/recommended",
"plugin:jest/recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:import/typescript",
"plugin:jsx-a11y/recommended",
"prettier"
], ],
"plugins": ["import", "jest", "jsx-a11y", "react-hooks"],
"env": {
"es6": true,
"node": true,
"jest": true
},
"globals": {
"__APP_VERSION__": true
},
"parserOptions": {
"allowImportExportEverywhere": true,
"sourceType": "module",
"ecmaVersion": 11,
"ecmaFeatures": {
"impliedStrict": true,
"jsx": true
}
},
"settings": {
"import/resolver": {
"node": {
"extensions": [".js", ".jsx", ".ts", ".tsx"]
}
}
},
"parser": "@typescript-eslint/parser",
"rules": { "rules": {
"curly": ["error", "all"],
"react/prop-types": 0,
"jest/no-export": 0,
"jest/no-test-callback": 0,
"jest/expect-expect": 0,
"jest/no-try-expect": 0,
"jest/no-done-callback": "off",
"jest/no-conditional-expect": "off",
"keyword-spacing": "off",
"no-tabs": "off",
"no-useless-escape": "off",
"padded-blocks": "off",
"require-jsdoc": "off",
"valid-jsdoc": "off",
"import/order": ["error"],
"eol-last": "error",
"no-irregular-whitespace": "error",
"no-mixed-spaces-and-tabs": ["error", "smart-tabs"],
"no-trailing-spaces": "error",
"camelcase": "off",
"guard-for-in": "error",
"new-cap": "error",
"max-len": ["error", 180],
"no-console": ["error", { "allow": ["warn"] }],
"no-constant-condition": "error",
"no-debugger": "error",
"no-empty": "error",
"no-fallthrough": "error",
"no-invalid-this": "error",
"no-new-require": "error",
"no-undef": "error",
"no-unreachable": "error",
"no-var": "error",
"one-var": "error",
"prefer-rest-params": "error",
"prefer-spread": "error",
"handle-callback-err": 0,
"prefer-const": 0,
"@typescript-eslint/camelcase": 0,
"@typescript-eslint/ban-ts-ignore": 0,
"@typescript-eslint/no-var-requires": 0, "@typescript-eslint/no-var-requires": 0,
"@typescript-eslint/ban-ts-ignore": 0,
"@typescript-eslint/no-inferrable-types": 0, "@typescript-eslint/no-inferrable-types": 0,
"@typescript-eslint/no-empty-function": 0, "@typescript-eslint/no-empty-function": 0,
"@typescript-eslint/no-this-alias": 0, "@typescript-eslint/no-this-alias": ["warn"],
"@typescript-eslint/no-use-before-define": 0, "@typescript-eslint/no-use-before-define": 0,
"@typescript-eslint/array-type": ["error"], "@typescript-eslint/array-type": ["warn"],
"@typescript-eslint/no-explicit-any": 0, "@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/indent": 0, "@typescript-eslint/indent": 0,
"@typescript-eslint/ban-ts-comment": 0, "@typescript-eslint/interface-name-prefix": 0,
"@typescript-eslint/ban-types": 0, "import/order": 0,
"@typescript-eslint/explicit-module-boundary-types": 0, "handle-callback-err": 1,
"prefer-const": 1,
// rules to fix "prefer-promise-reject-errors": 1
"no-unused-vars": ["warn", { "vars": "all", "args": "none" }],
"jest/no-identical-title": ["warn"],
"prefer-promise-reject-errors": ["warn"],
"jest/no-disabled-tests": ["warn"],
"jest/no-commented-out-tests": ["warn"],
"@typescript-eslint/prefer-optional-chain": ["warn"],
"@typescript-eslint/explicit-member-accessibility": ["warn"],
"@typescript-eslint/no-unused-vars": ["warn"]
} }
} }

@ -1,6 +1,16 @@
name: CI name: CI
on: [push, pull_request] on:
push:
branches:
- master
pull_request:
paths:
- .github/workflows/ci.yml
- 'packages/**'
- 'jest/**'
- 'package.json'
- 'lerna.json'
jobs: jobs:
ci: ci:

@ -1,12 +1,6 @@
name: Docker & Publish Pre-check name: Docker & Publish Pre-check
on: on:
push:
paths:
- 'packages/**'
- 'docker-bin/**'
- 'package.json'
- 'lerna.json'
pull_request: pull_request:
paths: paths:
- .github/workflows/docker-publish-pre-check.yml - .github/workflows/docker-publish-pre-check.yml

@ -2,6 +2,8 @@ name: Docker publish to GitHub registry
on: on:
push: push:
branches:
- master
paths: paths:
- .github/workflows/docker-publish.yml - .github/workflows/docker-publish.yml
- 'packages/**' - 'packages/**'

23
debug/DEBUGGING.md Normal file

@ -0,0 +1,23 @@
# Debuging Documentation
## Debugging tests
### Running a single test
```bash
yarn test test/integration/package.spec.ts --runInBand
```
Using `--runInBand` allows you to see the `console.log` prints, without that `jest` hidde them.
#### Display additional information
You can take advance of `debug` module used by many dependencies, eg:
* `supertest`: `DEBUG=superagent yarn test test/integration/package.spec.ts --runInBand`
* `express`: `DEBUG=express:* yarn test test/integration/package.spec.ts --runInBand`
* `nock`: `DEBUG=nock yarn test test/integration/package.spec.ts --runInBand`
* All of if: `DEBUG=* yarn test test/integration/package.spec.ts --runInBand`

@ -21,18 +21,19 @@
"@commitlint/cli": "8.3.5", "@commitlint/cli": "8.3.5",
"@commitlint/config-conventional": "8.2.0", "@commitlint/config-conventional": "8.2.0",
"@octokit/rest": "17.0.0", "@octokit/rest": "17.0.0",
"@types/async": "3.0.3", "@types/async": "3.2.3",
"@types/express": "4.17.1", "@types/express": "4.17.6",
"@types/http-errors": "1.6.3", "@types/http-errors": "1.6.3",
"@types/jest": "25.2.3", "@types/jest": "26.0.1",
"@types/lodash": "4.14.151", "@types/lodash": "4.14.156",
"@types/mime": "2.0.2", "@types/mime": "2.0.2",
"@types/minimatch": "3.0.3", "@types/minimatch": "3.0.3",
"@types/node": "12.12.21", "@types/node": "14.0.14",
"@types/request": "2.48.3", "@types/request": "2.48.3",
"@types/semver": "7.2.0", "@types/semver": "7.2.0",
"@verdaccio/babel-preset": "9.4.0", "@types/supertest": "2.0.9",
"@verdaccio/eslint-config": "9.0.0", "@verdaccio/babel-preset": "9.6.1",
"@verdaccio/eslint-config": "9.3.2",
"@verdaccio/types": "9.5.0", "@verdaccio/types": "9.5.0",
"codecov": "3.6.1", "codecov": "3.6.1",
"cross-env": "7.0.2", "cross-env": "7.0.2",
@ -42,22 +43,22 @@
"get-stdin": "7.0.0", "get-stdin": "7.0.0",
"husky": "2.7.0", "husky": "2.7.0",
"in-publish": "2.0.0", "in-publish": "2.0.0",
"jest": "26.0.1", "jest": "26.1.0",
"jest-environment-node": "26.0.1", "jest-environment-node": "26.1.0",
"jest-junit": "10.0.0", "jest-junit": "11.0.1",
"kleur": "3.0.3", "kleur": "3.0.3",
"lerna": "3.21.0", "lerna": "3.22.1",
"lint-staged": "8.2.1", "lint-staged": "8.2.1",
"nock": "11.7.2", "nock": "12.0.3",
"prettier": "1.19.1", "prettier": "1.19.1",
"rimraf": "3.0.0", "rimraf": "3.0.2",
"selfsigned": "1.10.7", "selfsigned": "1.10.7",
"standard-version": "7.0.1", "standard-version": "8.0.0",
"supertest": "4.0.2", "supertest": "4.0.2",
"typescript": "3.9.2", "typescript": "3.9.5",
"verdaccio": "4.6.2", "verdaccio": "4.7.1",
"verdaccio-auth-memory": "9.3.0", "verdaccio-auth-memory": "9.7.0",
"verdaccio-memory": "9.3.0" "verdaccio-memory": "9.7.0"
}, },
"scripts": { "scripts": {
"bootstrap": "lerna bootstrap", "bootstrap": "lerna bootstrap",
@ -73,7 +74,7 @@
"release:prerelease": "lerna version --conventional-commits --conventional-prerelease --preid next --registry http://localhost:4873", "release:prerelease": "lerna version --conventional-commits --conventional-prerelease --preid next --registry http://localhost:4873",
"release:publish": "lerna publish from-git", "release:publish": "lerna publish from-git",
"release:publish-prerelease": "lerna publish from-git --pre-dist-tag next", "release:publish-prerelease": "lerna publish from-git --pre-dist-tag next",
"lint": "eslint \"packages/**/@(src|tests)/**\"", "lint": "eslint \"packages/**/@(src|tests|test)/**\"",
"test": "lerna run test --concurrency 1", "test": "lerna run test --concurrency 1",
"test:e2e:cli": "cross-env NODE_ENV=test jest --config ./test/e2e-cli/jest.config.e2e.cli.js --passWithNoTests", "test:e2e:cli": "cross-env NODE_ENV=test jest --config ./test/e2e-cli/jest.config.e2e.cli.js --passWithNoTests",
"website:lint": "cd website && yarn lint", "website:lint": "cd website && yarn lint",

@ -0,0 +1,19 @@
const setup = jest.fn();
const logger = {
child: jest.fn(() => ({
debug: jest.fn(),
trace: jest.fn(),
warn: jest.fn(),
info: jest.fn(),
error: jest.fn(),
fatal: jest.fn(),
})),
debug: jest.fn(),
trace: jest.fn(),
warn: jest.fn(),
info: jest.fn(),
error: jest.fn(),
fatal: jest.fn(),
};
export { setup, logger }

@ -24,11 +24,12 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@verdaccio/auth": "5.0.0-alpha.0", "@verdaccio/auth": "5.0.0-alpha.0",
"@verdaccio/commons-api": "9.4.0", "@verdaccio/commons-api": "9.6.1",
"@verdaccio/dev-commons": "5.0.0-alpha.0", "@verdaccio/dev-commons": "5.0.0-alpha.0",
"@verdaccio/hooks": "5.0.0-alpha.0", "@verdaccio/hooks": "5.0.0-alpha.0",
"@verdaccio/logger": "5.0.0-alpha.0", "@verdaccio/logger": "5.0.0-alpha.0",
"@verdaccio/middleware": "5.0.0-alpha.0", "@verdaccio/middleware": "5.0.0-alpha.0",
"@verdaccio/store": "5.0.0-alpha.0",
"@verdaccio/utils": "5.0.0-alpha.0", "@verdaccio/utils": "5.0.0-alpha.0",
"body-parser": "1.19.0", "body-parser": "1.19.0",
"cookies": "0.8.0", "cookies": "0.8.0",
@ -38,7 +39,8 @@
}, },
"devDependencies": { "devDependencies": {
"@verdaccio/dev-types": "5.0.0-alpha.0", "@verdaccio/dev-types": "5.0.0-alpha.0",
"@verdaccio/types": "9.3.0" "@verdaccio/types": "9.5.0",
"express": "4.17.1"
}, },
"gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982" "gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982"
} }

@ -37,7 +37,6 @@ export default function(config: Config, auth: IAuth, storage: IStorageHandler):
app.param('_rev', match(/^-rev$/)); app.param('_rev', match(/^-rev$/));
app.param('org_couchdb_user', match(/^org\.couchdb\.user:/)); app.param('org_couchdb_user', match(/^org\.couchdb\.user:/));
app.param('anything', match(/.*/)); app.param('anything', match(/.*/));
app.use(auth.apiJWTmiddleware()); app.use(auth.apiJWTmiddleware());
app.use(bodyParser.json({ strict: false, limit: config.max_body_size || '10mb' })); app.use(bodyParser.json({ strict: false, limit: config.max_body_size || '10mb' }));
// @ts-ignore // @ts-ignore

@ -15,7 +15,7 @@ const downloadStream = (packageName: string, filename: string, storage: any, req
}); });
stream.on('error', function(err): void { stream.on('error', function(err): void {
return res.report_error(err); return res.locals.report_error(err);
}); });
res.header(HEADERS.CONTENT_TYPE, HEADERS.OCTET_STREAM); res.header(HEADERS.CONTENT_TYPE, HEADERS.OCTET_STREAM);

@ -139,7 +139,7 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
storage.mergeTags(packageName, tags, cb); storage.mergeTags(packageName, tags, cb);
}; };
const afterChange = function(error, okMessage, metadata): void { const afterChange = function(error, okMessage, metadata: Package): void {
const metadataCopy: Package = { ...metadata }; const metadataCopy: Package = { ...metadata };
const { _attachments, versions } = metadataCopy; const { _attachments, versions } = metadataCopy;
@ -326,7 +326,7 @@ export function uploadPackageTarball(storage: IStorageHandler) {
}); });
stream.on('error', function(err) { stream.on('error', function(err) {
return res.report_error(err); return res.locals.report_error(err);
}); });
stream.on('success', function() { stream.on('success', function() {

@ -74,16 +74,4 @@ export default function(route: Router, auth: IAuth, config: Config): void {
ok: API_MESSAGE.LOGGED_OUT, ok: API_MESSAGE.LOGGED_OUT,
}); });
}); });
// placeholder 'cause npm require to be authenticated to publish
// we do not do any real authentication yet
route.post('/_session', Cookies.express(), function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
res.cookies.set('AuthSession', String(Math.random()), createSessionToken());
next({
ok: true,
name: 'somebody',
roles: [],
});
});
} }

@ -0,0 +1,8 @@
{
"rules": {
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/explicit-member-accessibility": 0,
"@typescript-eslint/no-unused-vars": 2,
"no-console": 0
}
}

@ -0,0 +1,70 @@
import path from "path";
import express, {Application} from 'express';
import supertest from 'supertest';
import {parseConfigFile} from '@verdaccio/utils';
import { Config } from '@verdaccio/config';
import { Storage } from '@verdaccio/store';
import { final, handleError, errorReportingMiddleware } from '@verdaccio/middleware';
import { Auth } from '@verdaccio/auth';
import apiEndpoints from '../../src';
import {IAuth} from "@verdaccio/dev-types";
import {HEADER_TYPE, HTTP_STATUS, generatePackageMetadata} from "@verdaccio/dev-commons";
import {HEADERS} from "@verdaccio/commons-api";
const getConf = (conf) => {
const configPath = path.join(__dirname, 'config', conf);
return parseConfigFile(configPath);
};
export async function initializeServer(configName): Promise<Application> {
const app = express();
const config = new Config(getConf(configName));
const storage = new Storage(config);
await storage.init(config, []);
const auth: IAuth = new Auth(config);
// @ts-ignore
app.use(errorReportingMiddleware);
// @ts-ignore
app.use(apiEndpoints(config, auth, storage));
// @ts-ignore
app.use(handleError);
// @ts-ignore
app.use(final);
app.use(function(request, response) {
response.status(590);
console.log('respo', response);
response.json({error: 'cannot handle this'});
});
return app;
}
export function publishVersion(app, configFile, pkgName, version): supertest.Test {
const pkgMetadata = generatePackageMetadata(pkgName, version);
return supertest(app)
.put(`/${encodeURIComponent(pkgName)}`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(JSON.stringify(pkgMetadata))
.set('accept', HEADERS.GZIP)
.set(HEADER_TYPE.ACCEPT_ENCODING, HEADERS.JSON)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON);
}
export async function publishTaggedVersion(app, configFile, pkgName: string, version: string, tag: string) {
const pkgMetadata = generatePackageMetadata(pkgName, version, {
[tag]: version
});
return supertest(app)
.put(`/${encodeURIComponent(pkgName)}/${encodeURIComponent(version)}/-tag/${encodeURIComponent(tag)}`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(JSON.stringify(pkgMetadata))
.expect(HTTP_STATUS.CREATED)
.set('accept', HEADERS.GZIP)
.set(HEADER_TYPE.ACCEPT_ENCODING, HEADERS.JSON)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON);
}

@ -0,0 +1,30 @@
store:
memory:
limit: 1000
auth:
auth-memory:
users:
test:
name: test
password: test
web:
enable: true
title: verdaccio
publish:
allow_offline: false
uplinks:
logs: { type: stdout, format: pretty, level: trace }
packages:
'@*/*':
access: $anonymous
publish: $anonymous
'**':
access: $anonymous
publish: $anonymous
_debug: true

@ -0,0 +1,30 @@
store:
memory:
limit: 10
auth:
auth-memory:
users:
web:
enable: true
title: verdaccio
uplinks:
logs: { type: stdout, format: pretty, level: trace }
packages:
'@*/*':
access: $all
publish: $all
unpublish: $all
proxy: npmjs
'verdaccio':
access: $all
publish: $all
'**':
access: $all
publish: $all
unpublish: $all
proxy: npmjs
_debug: true

@ -0,0 +1,30 @@
store:
memory:
limit: 1000
auth:
auth-memory:
users:
test:
name: test
password: test
web:
enable: true
title: verdaccio
publish:
allow_offline: false
uplinks:
logs: { type: stdout, format: pretty, level: trace }
packages:
'@*/*':
access: $anonymous
publish: $anonymous
'**':
access: $anonymous
publish: $anonymous
_debug: true

@ -0,0 +1,36 @@
store:
memory:
limit: 1000
auth:
auth-memory:
users:
test:
name: test
password: test
web:
enable: true
title: verdaccio
uplinks:
npmjs:
url: https://registry.npmjs.org/
logs: { type: stdout, format: pretty, level: trace }
packages:
'@*/*':
access: $all
publish: $all
unpublish: $all
proxy: npmjs
'verdaccio':
access: $all
publish: $all
'**':
access: $all
publish: $all
unpublish: $all
proxy: npmjs
_debug: true

@ -0,0 +1,36 @@
store:
memory:
limit: 1000
auth:
auth-memory:
users:
test:
name: test
password: test
web:
enable: true
title: verdaccio
uplinks:
npmjs:
url: https://registry.npmjs.org/
logs: { type: stdout, format: pretty, level: trace }
packages:
'@*/*':
access: $all
publish: $all
unpublish: $all
proxy: npmjs
'verdaccio':
access: $all
publish: $all
'**':
access: $all
publish: $all
unpublish: $all
proxy: npmjs
_debug: true

@ -0,0 +1,83 @@
import supertest from 'supertest';
import {initializeServer, publishTaggedVersion, publishVersion} from './_helper';
import { HTTP_STATUS } from '@verdaccio/commons-api';
import {HEADER_TYPE, HEADERS} from '@verdaccio/dev-commons';
import {$RequestExtend, $ResponseExtend} from "@verdaccio/dev-types";
const mockApiJWTmiddleware = jest.fn(() =>
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'foo', groups: [], real_groups: []}
_next();
}
);
jest.mock('@verdaccio/auth', () => ({
Auth: class {
apiJWTmiddleware() {
return mockApiJWTmiddleware();
}
allow_access (_d, f_, cb) {
cb(null, true)
}
allow_publish (_d, f_, cb) {
cb(null, true)
}
}
}));
describe('package', () => {
let app;
beforeAll(async () => {
app = await initializeServer('package.yaml');
await publishVersion(app, 'package.yaml', 'foo', '1.0.0');
});
test('should return a package', async (done) => {
return supertest(app)
.get('/foo')
.set('Accept', HEADERS.JSON)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK)
.then(response => {
expect(response.body.name).toEqual('foo');
done();
});
});
test('should return a package by version', async (done) => {
return supertest(app)
.get('/foo/1.0.0')
.set('Accept', HEADERS.JSON)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK)
.then(response => {
expect(response.body.name).toEqual('foo');
done();
});
});
// TODO: investigate the 404
test.skip('should return a package by dist-tag', async (done) => {
await publishVersion(app, 'package.yaml', 'foo-tagged', '1.0.0');
await publishTaggedVersion(app, 'package.yaml', 'foo-tagged', '1.0.1', 'test');
return supertest(app)
.get('/foo-tagged/1.0.0')
.set('Accept', HEADERS.JSON)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.CREATED)
.then(response => {
expect(response.body.name).toEqual('foo');
done();
});
});
test('should return 404', async () => {
return supertest(app)
.get('/404-not-found')
.set('Accept', HEADERS.JSON)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.NOT_FOUND);
});
});

@ -0,0 +1,16 @@
import supertest from 'supertest';
import {initializeServer } from './_helper';
import { HTTP_STATUS } from '@verdaccio/commons-api';
import {HEADER_TYPE, HEADERS} from '@verdaccio/dev-commons';
describe('ping', () => {
test('should return the reply the ping', async () => {
return supertest(await initializeServer('ping.yaml'))
.get('/-/ping')
.set('Accept', HEADERS.JSON)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK)
.then(response => expect(response.body).toEqual({}));
});
});

@ -0,0 +1,179 @@
import {initializeServer, publishVersion} from './_helper';
import { HTTP_STATUS } from '@verdaccio/commons-api';
import {API_ERROR, API_MESSAGE, generatePackageMetadata, HEADER_TYPE, HEADERS} from '@verdaccio/dev-commons';
import {$RequestExtend, $ResponseExtend} from '@verdaccio/dev-types';
import supertest from "supertest";
const mockApiJWTmiddleware = jest.fn(() =>
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'foo', groups: [], real_groups: []}
_next();
}
);
jest.setTimeout(50000000);
jest.mock('@verdaccio/auth', () => ({
Auth: class {
apiJWTmiddleware() {
return mockApiJWTmiddleware();
}
allow_access (_d, f_, cb) {
cb(null, true)
}
allow_publish (_d, f_, cb) {
cb(null, true)
}
allow_unpublish (_d, f_, cb) {
cb(null, true)
}
}
}));
// const mockStorage = jest.fn(() => {
// const { Storage } = jest.requireActual('@verdaccio/store');
// return {
// Storage: class extends Storage {
// addPackage(name, metadata, cb) {
// super.addPackage(name, metadata, cb);
// }
// }
// };
// });
// jest.mock('@verdaccio/store', () => {
// const { Storage } = jest.requireActual('@verdaccio/store');
// return ({
// Storage: class extends Storage {
// addPackage(name, metadata, cb) {
// // super.addPackage(name, metadata, cb);
// return mockStorage(name, metadata, cb);
// }
// }
// })
// });
describe('publish', () => {
describe('handle invalid publish formats', () => {
const pkgName = 'test';
const pkgMetadata = generatePackageMetadata(pkgName, '1.0.0');
test('should fail on publish a bad _attachments package', async (done) => {
const app = await initializeServer('publish.yaml');
return supertest(app)
.put(`/${encodeURIComponent(pkgName)}`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(JSON.stringify(Object.assign({}, pkgMetadata, {
_attachments: {}
})))
.set('accept', HEADERS.GZIP)
.expect(HTTP_STATUS.BAD_REQUEST)
.then(response => {
console.log("response.body", response.body);
expect(response.body.error).toEqual(API_ERROR.UNSUPORTED_REGISTRY_CALL);
done();
});
});
test('should fail on publish a bad versions package', async (done) => {
const app = await initializeServer('publish.yaml');
return supertest(app)
.put(`/${encodeURIComponent(pkgName)}`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(JSON.stringify(Object.assign({}, pkgMetadata, {
versions: ''
})))
.set('accept', HEADERS.GZIP)
.expect(HTTP_STATUS.BAD_REQUEST)
.then(response => {
console.log("response.body", response.body);
expect(response.body.error).toEqual(API_ERROR.UNSUPORTED_REGISTRY_CALL);
done();
});
});
});
describe('publish a package', () => {
test('should publish a package', async (done) => {
const app = await initializeServer('publish.yaml');
return publishVersion(app, 'publish.yaml', 'foo', '1.0.0')
.expect(HTTP_STATUS.CREATED)
.then(response => {
expect(response.body.ok).toEqual(API_MESSAGE.PKG_CREATED);
done();
});
});
test('should publish a new package', async (done) => {
const pkgName = 'test';
const pkgMetadata = generatePackageMetadata(pkgName, '1.0.0');
const app = await initializeServer('publish.yaml');
return supertest(app)
.put(`/${encodeURIComponent(pkgName)}`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(JSON.stringify(Object.assign({}, pkgMetadata, {
_attachments: null
})))
.set('accept', HEADERS.GZIP)
.expect(HTTP_STATUS.CREATED)
.then(response => {
expect(response.body.ok).toEqual(API_MESSAGE.PKG_CREATED);
done();
});
});
test('should publish a new package with no readme', async (done) => {
const pkgName = 'test';
const pkgMetadata = generatePackageMetadata(pkgName, '1.0.0');
const app = await initializeServer('publish.yaml');
return supertest(app)
.put(`/${encodeURIComponent(pkgName)}`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(JSON.stringify(Object.assign({}, pkgMetadata, {
versions: {
['1.0.0'] : {
readme: null
}
}
})))
.set('accept', HEADERS.GZIP)
.expect(HTTP_STATUS.CREATED)
.then(response => {
expect(response.body.ok).toEqual(API_MESSAGE.PKG_CREATED);
done();
});
});
});
test('should fails on publish a duplicated package', async (done) => {
const app = await initializeServer('publish.yaml');
await publishVersion(app, 'publish.yaml', 'foo', '1.0.0');
return publishVersion(app, 'publish.yaml', 'foo', '1.0.0')
.expect(HTTP_STATUS.CONFLICT)
.then(response => {
console.log("response.body", response.body);
expect(response.body.error).toEqual(API_ERROR.PACKAGE_EXIST);
done();
});
});
describe('unpublish a package', () => {
let app;
beforeEach(async () => {
app = await initializeServer('publish.yaml');
await publishVersion(app, 'publish.yaml', 'foo', '1.0.0');
})
test('should unpublish a package', () => {
})
});
describe('star a package', () => {
});
});

@ -0,0 +1,237 @@
import supertest from 'supertest';
import { initializeServer } from './_helper';
import { HTTP_STATUS, API_ERROR } from '@verdaccio/commons-api';
import {HEADERS, HEADER_TYPE, API_MESSAGE} from '@verdaccio/dev-commons';
import {$RequestExtend, $ResponseExtend} from "@verdaccio/dev-types";
import {getBadRequest, getConflict, getUnauthorized} from "@verdaccio/commons-api/lib";
import _ from "lodash";
const mockApiJWTmiddleware = jest.fn(() =>
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'test', groups: [], real_groups: []};
_next();
}
);
const mockAuthenticate = jest.fn(() => (_name, _password, callback): void => {
return callback(null, ['all']);
}
);
const mockAddUser = jest.fn(() => (_name, _password, callback): void => {
return callback(getConflict(API_ERROR.USERNAME_ALREADY_REGISTERED));
}
);
jest.mock('@verdaccio/auth', () => ({
getApiToken: () => 'token',
Auth: class {
apiJWTmiddleware() {
return mockApiJWTmiddleware();
}
allow_access (_d, f_, cb) {
cb(null, true);
}
add_user (name, password, callback) {
mockAddUser()(name, password, callback);
}
authenticate (_name, _password, callback) {
mockAuthenticate()(_name, _password, callback);
}
}
}));
describe('user', () => {
const credentials = { name: 'test', password: 'test' };
test('should test add a new user', async (done) => {
mockApiJWTmiddleware.mockImplementationOnce(() =>
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: undefined};
_next();
}
);
mockAddUser.mockImplementationOnce(() => (_name, _password, callback): void => {
return callback(null, true);
}
);
supertest(await initializeServer('user.yaml'))
.put(`/-/user/org.couchdb.user:newUser`)
.send({
name: 'newUser',
password: 'newUser'
})
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.CREATED)
.end(function(err, res) {
if (err) {
return done(err);
}
expect(res.body.ok).toBeDefined();
expect(res.body.token).toBeDefined();
const token = res.body.token;
expect(typeof token).toBe('string');
expect(res.body.ok).toMatch(`user 'newUser' created`);
done();
});
});
test('should test fails on add a existing user with login', async (done) => {
mockApiJWTmiddleware.mockImplementationOnce(() =>
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: undefined};
_next();
}
);
supertest(await initializeServer('user.yaml'))
.put('/-/user/org.couchdb.user:jotaNew')
.send(credentials)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.CONFLICT)
.end(function(err, res) {
if (err) {
return done(err);
}
expect(res.body.error).toBeDefined();
expect(res.body.error).toMatch(API_ERROR.USERNAME_ALREADY_REGISTERED);
done();
});
});
test('should log in as existing user', async (done) => {
supertest(await initializeServer('user.yaml'))
.put(`/-/user/org.couchdb.user:${credentials.name}`)
.send(credentials)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.CREATED)
.end((err, res) => {
if (err) {
return done(err);
}
expect(res.body).toBeTruthy();
expect(res.body.ok).toMatch(`you are authenticated as \'${credentials.name}\'`);
done();
});
});
test('should test fails add a new user with missing name', async (done) => {
mockApiJWTmiddleware.mockImplementationOnce(() =>
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: undefined };
_next();
}
);
mockAddUser.mockImplementationOnce(() => (_name, _password, callback): void => {
return callback(getBadRequest(API_ERROR.USERNAME_PASSWORD_REQUIRED));
}
);
const credentialsShort = _.cloneDeep(credentials);
delete credentialsShort.name;
supertest(await initializeServer('user.yaml'))
.put(`/-/user/org.couchdb.user:${credentials.name}`)
.send(credentialsShort)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.BAD_REQUEST)
.end(function(err, res) {
if (err) {
return done(err);
}
expect(res.body.error).toBeDefined();
expect(res.body.error).toMatch(API_ERROR.USERNAME_PASSWORD_REQUIRED);
done();
});
});
test('should test fails add a new user with missing password', async (done) => {
mockApiJWTmiddleware.mockImplementationOnce(() =>
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: undefined};
_next();
}
);
const credentialsShort = _.cloneDeep(credentials);
delete credentialsShort.password;
supertest(await initializeServer('user.yaml'))
.put(`/-/user/org.couchdb.user:${credentials.name}`)
.send(credentialsShort)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.BAD_REQUEST)
.end(function(err, res) {
if (err) {
return done(err);
}
expect(res.body.error).toBeDefined();
// FIXME: message is not 100% accurate
// eslint-disable-next-line new-cap
expect(res.body.error).toMatch(API_ERROR.PASSWORD_SHORT());
done();
});
});
test('should test fails add a new user with wrong password', async (done) => {
mockApiJWTmiddleware.mockImplementationOnce(() =>
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'test'};
_next();
}
);
mockAuthenticate.mockImplementationOnce(() => (_name, _password, callback): void => {
return callback(getUnauthorized(API_ERROR.BAD_USERNAME_PASSWORD));
}
);
const credentialsShort = _.cloneDeep(credentials);
credentialsShort.password = 'failPassword';
supertest(await initializeServer('user.yaml'))
.put('/-/user/org.couchdb.user:test')
.send(credentialsShort)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.UNAUTHORIZED)
.end(function(err, res) {
if (err) {
return done(err);
}
expect(res.body.error).toBeDefined();
expect(res.body.error).toMatch(API_ERROR.BAD_USERNAME_PASSWORD);
done();
});
});
test('should be able to logout an user', async (done) => {
mockApiJWTmiddleware.mockImplementationOnce(() =>
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'test'};
_next();
}
);
mockAuthenticate.mockImplementationOnce(() => (_name, _password, callback): void => {
return callback(getUnauthorized(API_ERROR.BAD_USERNAME_PASSWORD));
}
);
const credentialsShort = _.cloneDeep(credentials);
credentialsShort.password = 'failPassword';
supertest(await initializeServer('user.yaml'))
.delete('/-/user/token/someSecretToken')
.send(credentialsShort)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK)
.end(function(err, res) {
if (err) {
return done(err);
}
expect(res.body.ok).toMatch(API_MESSAGE.LOGGED_OUT);
done();
});
});
});

@ -0,0 +1,53 @@
import supertest from 'supertest';
import {initializeServer } from './_helper';
import { HTTP_STATUS } from '@verdaccio/commons-api';
import { HEADERS} from '@verdaccio/dev-commons';
import {$RequestExtend, $ResponseExtend} from "@verdaccio/dev-types";
const mockApiJWTmiddleware = jest.fn(() =>
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'foo', groups: [], real_groups: []}
_next();
}
);
jest.mock('@verdaccio/auth', () => ({
Auth: class {
apiJWTmiddleware() {
return mockApiJWTmiddleware();
}
allow_access (_d, f_, cb) {
cb(null, true)
}
}
}));
describe('whoami', () => {
test.skip('should test referer /whoami endpoint', async (done) => {
return supertest(await initializeServer('whoami.yaml'))
.get('/whoami')
.set('referer', 'whoami')
.expect(HTTP_STATUS.OK)
.end(done);
});
test.skip('should test no referer /whoami endpoint', async (done) => {
return supertest(await initializeServer('whoami.yaml'))
.get('/whoami')
.expect(HTTP_STATUS.NOT_FOUND)
.end(done);
});
test('should return the logged username', async () => {
return supertest(await initializeServer('whoami.yaml'))
.get('/-/whoami')
.set('Accept', HEADERS.JSON)
.expect('Content-Type', HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK)
.then(response => {
expect(response.body.username).toEqual('foo');
});
});
});

@ -1,4 +1,4 @@
import { addVersion, uploadPackageTarball, removeTarball, unPublishPackage, publishPackage } from '../src/publish'; import { addVersion, uploadPackageTarball, removeTarball, unPublishPackage, publishPackage } from '../../src/publish';
import { HTTP_STATUS, API_ERROR } from '@verdaccio/dev-commons'; import { HTTP_STATUS, API_ERROR } from '@verdaccio/dev-commons';
const REVISION_MOCK = '15-e53a77096b0ee33e'; const REVISION_MOCK = '15-e53a77096b0ee33e';
@ -82,7 +82,7 @@ describe('Publish endpoints - upload package tarball', () => {
pipe: jest.fn(), pipe: jest.fn(),
on: jest.fn(), on: jest.fn(),
}; };
res = { status: jest.fn(), report_error: jest.fn() }; res = { status: jest.fn(), locals: { report_error: jest.fn() }};
next = jest.fn(); next = jest.fn();
}); });

@ -1,3 +1,4 @@
/* eslint-disable curly */
// ensure that all arguments are validated // ensure that all arguments are validated
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs';
@ -13,7 +14,7 @@ import fs from 'fs';
*/ */
describe('api endpoint app.param()', () => { describe('api endpoint app.param()', () => {
let m; let m;
const requirePath = path.normalize(path.join(__dirname, '../src/index.ts')); const requirePath = path.normalize(path.join(__dirname, '../../src/index.ts'));
const source = fs.readFileSync(requirePath, 'utf8'); const source = fs.readFileSync(requirePath, 'utf8');
const very_scary_regexp = /\n\s*app\.(\w+)\s*\(\s*(("[^"]*")|('[^']*'))\s*,/g; const very_scary_regexp = /\n\s*app\.(\w+)\s*\(\s*(("[^"]*")|('[^']*'))\s*,/g;
const appParams = {}; const appParams = {};

@ -23,7 +23,7 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@verdaccio/commons-api": "9.4.0", "@verdaccio/commons-api": "9.6.1",
"@verdaccio/dev-commons": "5.0.0-alpha.0", "@verdaccio/dev-commons": "5.0.0-alpha.0",
"@verdaccio/loaders": "5.0.0-alpha.0", "@verdaccio/loaders": "5.0.0-alpha.0",
"@verdaccio/logger": "5.0.0-alpha.0", "@verdaccio/logger": "5.0.0-alpha.0",

@ -31,7 +31,7 @@
"@verdaccio/config": "5.0.0-alpha.0", "@verdaccio/config": "5.0.0-alpha.0",
"@verdaccio/node-api": "5.0.0-alpha.0", "@verdaccio/node-api": "5.0.0-alpha.0",
"@verdaccio/utils": "5.0.0-alpha.0", "@verdaccio/utils": "5.0.0-alpha.0",
"commander": "3.0.2", "commander": "5.1.0",
"envinfo": "7.4.0", "envinfo": "7.4.0",
"kleur": "3.0.3", "kleur": "3.0.3",
"semver": "7.3.2" "semver": "7.3.2"

@ -1,12 +1,16 @@
import { Package } from "@verdaccio/types"; import { Package } from "@verdaccio/types";
export function generatePackageMetadata(pkgName: string, version = '1.0.0'): Package { export interface DistTags {
[key: string]: string;
}
export function generatePackageMetadata(pkgName: string, version = '1.0.0', distTags: DistTags = {['latest']: version}): Package {
// @ts-ignore // @ts-ignore
return { return {
"_id": pkgName, "_id": pkgName,
"name": pkgName, "name": pkgName,
"dist-tags": { "dist-tags": {
"latest": version ...distTags
}, },
"versions": { "versions": {
[version]: { [version]: {

@ -15,14 +15,14 @@
"license": "MIT", "license": "MIT",
"homepage": "https://verdaccio.org", "homepage": "https://verdaccio.org",
"dependencies": { "dependencies": {
"@verdaccio/commons-api": "9.4.0", "@verdaccio/commons-api": "9.6.1",
"@verdaccio/logger": "5.0.0-alpha.0", "@verdaccio/logger": "5.0.0-alpha.0",
"handlebars": "4.5.3", "handlebars": "4.5.3",
"request": "2.87.0" "request": "2.87.0"
}, },
"devDependencies": { "devDependencies": {
"@verdaccio/dev-commons": "5.0.0-alpha.0", "@verdaccio/dev-commons": "5.0.0-alpha.0",
"@verdaccio/types": "9.3.0" "@verdaccio/types": "9.5.0"
}, },
"scripts": { "scripts": {
"clean": "rimraf ./build", "clean": "rimraf ./build",

@ -24,7 +24,7 @@
"build": "npm run build:js && npm run build:types" "build": "npm run build:js && npm run build:types"
}, },
"dependencies": { "dependencies": {
"@verdaccio/commons-api": "9.4.0", "@verdaccio/commons-api": "9.6.1",
"@verdaccio/dev-commons": "5.0.0-alpha.0", "@verdaccio/dev-commons": "5.0.0-alpha.0",
"dayjs": "1.8.19", "dayjs": "1.8.19",
"fast-safe-stringify": "2.0.7", "fast-safe-stringify": "2.0.7",

@ -29,7 +29,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/pino": "6.0.1", "@types/pino": "6.0.1",
"@verdaccio/types": "9.3.0" "@verdaccio/types": "9.5.0"
}, },
"gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982" "gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982"
} }

@ -22,7 +22,7 @@
"build": "npm run build:js && npm run build:types" "build": "npm run build:js && npm run build:types"
}, },
"dependencies": { "dependencies": {
"@verdaccio/commons-api": "9.4.0", "@verdaccio/commons-api": "9.6.1",
"@verdaccio/dev-commons": "5.0.0-alpha.0", "@verdaccio/dev-commons": "5.0.0-alpha.0",
"@verdaccio/logger": "5.0.0-alpha.0", "@verdaccio/logger": "5.0.0-alpha.0",
"@verdaccio/utils": "5.0.0-alpha.0", "@verdaccio/utils": "5.0.0-alpha.0",

@ -1,3 +1,4 @@
import _ from 'lodash'; import _ from 'lodash';
import { import {
@ -13,6 +14,7 @@ import { $ResponseExtend, $RequestExtend, $NextFunctionVer, IAuth } from '@verda
import { Config, Package, RemoteUser } from '@verdaccio/types'; import { Config, Package, RemoteUser } from '@verdaccio/types';
import { logger } from '@verdaccio/logger'; import { logger } from '@verdaccio/logger';
import { VerdaccioError } from '@verdaccio/commons-api'; import { VerdaccioError } from '@verdaccio/commons-api';
import {HttpError} from "http-errors";
export function match(regexp: RegExp): any { export function match(regexp: RegExp): any {
return function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer, value: string): void { return function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer, value: string): void {
@ -110,8 +112,7 @@ export function allow(auth: IAuth): Function {
const packageName = req.params.scope ? `@${req.params.scope}/${req.params.package}` : req.params.package; const packageName = req.params.scope ? `@${req.params.scope}/${req.params.package}` : req.params.package;
const packageVersion = req.params.filename ? getVersionFromTarball(req.params.filename) : undefined; const packageVersion = req.params.filename ? getVersionFromTarball(req.params.filename) : undefined;
const remote: RemoteUser = req.remote_user; const remote: RemoteUser = req.remote_user;
logger.trace({ action, user: remote.name }, `[middleware/allow][@{action}] allow for @{user}`); logger.trace({ action, user: remote?.name }, `[middleware/allow][@{action}] allow for @{user}`);
auth['allow_' + action]({ packageName, packageVersion }, remote, function(error, allowed): void { auth['allow_' + action]({ packageName, packageVersion }, remote, function(error, allowed): void {
req.resume(); req.resume();
if (error) { if (error) {
@ -148,7 +149,8 @@ export function final(body: FinalBody, req: $RequestExtend, res: $ResponseExtend
if (typeof body === 'object' && _.isNil(body) === false) { if (typeof body === 'object' && _.isNil(body) === false) {
if (typeof (body as MiddlewareError).error === 'string') { if (typeof (body as MiddlewareError).error === 'string') {
res._verdaccio_error = (body as MiddlewareError).error; res.locals._verdaccio_error = (body as MiddlewareError).error;
// res._verdaccio_error = (body as MiddlewareError).error;
} }
body = JSON.stringify(body, undefined, ' ') + '\n'; body = JSON.stringify(body, undefined, ' ') + '\n';
} }
@ -181,8 +183,7 @@ export const LOG_STATUS_MESSAGE = "@{status}, user: @{user}(@{remoteIP}), req: '
export const LOG_VERDACCIO_ERROR = `${LOG_STATUS_MESSAGE}, error: @{!error}`; export const LOG_VERDACCIO_ERROR = `${LOG_STATUS_MESSAGE}, error: @{!error}`;
export const LOG_VERDACCIO_BYTES = `${LOG_STATUS_MESSAGE}, bytes: @{bytes.in}/@{bytes.out}`; export const LOG_VERDACCIO_BYTES = `${LOG_STATUS_MESSAGE}, bytes: @{bytes.in}/@{bytes.out}`;
export function log(config: Config) { export function log(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
// logger // logger
req.log = logger.child({ sub: 'in' }); req.log = logger.child({ sub: 'in' });
@ -197,10 +198,7 @@ export function log(config: Config) {
} }
req.url = req.originalUrl; req.url = req.originalUrl;
// avoid log noise data from static content req.log.info({ req: req, ip: req.ip }, "@{ip} requested '@{req.method} @{req.url}'");
if (req.originalUrl.match(/static/) === null) {
req.log.info({req: req, ip: req.ip}, "@{ip} requested '@{req.method} @{req.url}'");
}
req.originalUrl = req.url; req.originalUrl = req.url;
if (_.isNil(_auth) === false) { if (_.isNil(_auth) === false) {
@ -212,11 +210,9 @@ export function log(config: Config) {
} }
let bytesin = 0; let bytesin = 0;
if (config?.experiments?.bytesin_off !== true) {
req.on('data', function(chunk): void { req.on('data', function(chunk): void {
bytesin += chunk.length; bytesin += chunk.length;
}); });
}
let bytesout = 0; let bytesout = 0;
const _write = res.write; const _write = res.write;
@ -229,26 +225,18 @@ export function log(config: Config) {
_write.apply(res, arguments); _write.apply(res, arguments);
}; };
let logHasBeenCalled = false;
const log = function(): void { const log = function(): void {
if (logHasBeenCalled) {
return;
}
logHasBeenCalled = true;
const forwardedFor = req.headers['x-forwarded-for']; const forwardedFor = req.headers['x-forwarded-for'];
const remoteAddress = req.connection.remoteAddress; const remoteAddress = req.connection.remoteAddress;
const remoteIP = forwardedFor ? `${forwardedFor} via ${remoteAddress}` : remoteAddress; const remoteIP = forwardedFor ? `${forwardedFor} via ${remoteAddress}` : remoteAddress;
let message; let message;
if (res._verdaccio_error) { if (res.locals._verdaccio_error) {
message = LOG_VERDACCIO_ERROR; message = LOG_VERDACCIO_ERROR;
} else { } else {
message = LOG_VERDACCIO_BYTES; message = LOG_VERDACCIO_BYTES;
} }
req.url = req.originalUrl; req.url = req.originalUrl;
// avoid log noise data from static content
if (req.url.match(/static/) === null) {
req.log.warn( req.log.warn(
{ {
request: { request: {
@ -259,7 +247,7 @@ export function log(config: Config) {
user: (req.remote_user && req.remote_user.name) || null, user: (req.remote_user && req.remote_user.name) || null,
remoteIP, remoteIP,
status: res.statusCode, status: res.statusCode,
error: res._verdaccio_error, error: res.locals._verdaccio_error,
bytes: { bytes: {
in: bytesin, in: bytesin,
out: bytesout, out: bytesout,
@ -268,8 +256,7 @@ export function log(config: Config) {
message message
); );
req.originalUrl = req.url; req.originalUrl = req.url;
} };
}
req.on('close', function(): void { req.on('close', function(): void {
log(); log();
@ -286,16 +273,32 @@ export function log(config: Config) {
log(); log();
}; };
next(); next();
}
export function handleError(err: HttpError, req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
if (_.isError(err)) {
if (err.code === 'ECONNABORT' && res.statusCode === HTTP_STATUS.NOT_MODIFIED) {
return next();
}
if (_.isFunction(res.locals.report_error) === false) {
// in case of very early error this middleware may not be loaded before error is generated
// fixing that
errorReportingMiddleware(req, res, _.noop);
}
res.locals.report_error(err);
} else {
// Fall to Middleware.final
return next(err);
} }
} }
// Middleware // Middleware
export function errorReportingMiddleware(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void { export function errorReportingMiddleware(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
res.report_error = res.locals.report_error =
res.report_error || res.locals.report_error ||
function(err: VerdaccioError): void { function(err: VerdaccioError): void {
if (err.status && err.status >= HTTP_STATUS.BAD_REQUEST && err.status < 600) { if (err.status && err.status >= HTTP_STATUS.BAD_REQUEST && err.status < 600) {
if (!res.headersSent) { if (_.isNil(res.headersSent) === false) {
res.status(err.status); res.status(err.status);
next({ error: err.message || API_ERROR.UNKNOWN_ERROR }); next({ error: err.message || API_ERROR.UNKNOWN_ERROR });
} }

@ -1,4 +1,7 @@
// <reference types="node" />
import { Logger, RemoteUser } from "@verdaccio/types"; import { Logger, RemoteUser } from "@verdaccio/types";
import * as http from "http";
declare global { declare global {
namespace Express { namespace Express {
@ -7,10 +10,11 @@ declare global {
log: Logger; log: Logger;
} }
export interface Response { // FIXME:
report_error: any; // export interface Response extends http.ServerResponse, Express.Response {
_verdaccio_error: any; // report_error: any;
socket?: any; // _verdaccio_error: any;
} // socket?: any;
// }
} }
} }

@ -24,16 +24,16 @@
}, },
"dependencies": { "dependencies": {
"@verdaccio/dev-commons": "5.0.0-alpha.0", "@verdaccio/dev-commons": "5.0.0-alpha.0",
"@verdaccio/local-storage": "9.5.0", "@verdaccio/local-storage": "9.6.1",
"@verdaccio/logger": "5.0.0-alpha.0", "@verdaccio/logger": "5.0.0-alpha.0",
"@verdaccio/streams": "9.5.0", "@verdaccio/streams": "9.6.1",
"@verdaccio/utils": "5.0.0-alpha.0", "@verdaccio/utils": "5.0.0-alpha.0",
"JSONStream": "1.3.5", "JSONStream": "1.3.5",
"request": "2.87.0" "request": "2.87.0"
}, },
"devDependencies": { "devDependencies": {
"@verdaccio/dev-types": "5.0.0-alpha.0", "@verdaccio/dev-types": "5.0.0-alpha.0",
"@verdaccio/types": "9.3.0" "@verdaccio/types": "9.5.0"
}, },
"gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982" "gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982"
} }

@ -85,12 +85,12 @@ const defineAPI = function(config: IConfig, storage: IStorageHandler): any {
if (err.code === 'ECONNABORT' && res.statusCode === HTTP_STATUS.NOT_MODIFIED) { if (err.code === 'ECONNABORT' && res.statusCode === HTTP_STATUS.NOT_MODIFIED) {
return next(); return next();
} }
if (_.isFunction(res.report_error) === false) { if (_.isFunction(res.locals.report_error) === false) {
// in case of very early error this middleware may not be loaded before error is generated // in case of very early error this middleware may not be loaded before error is generated
// fixing that // fixing that
errorReportingMiddleware(req, res, _.noop); errorReportingMiddleware(req, res, _.noop);
} }
res.report_error(err); res.locals.report_error(err);
} else { } else {
// Fall to Middleware.final // Fall to Middleware.final
return next(err); return next(err);

@ -0,0 +1,8 @@
{
"rules": {
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/explicit-member-accessibility": 0,
"@typescript-eslint/no-unused-vars": 2,
"no-console": 0
}
}

@ -5,7 +5,6 @@ import path from 'path';
import endPointAPI from '@verdaccio/server'; import endPointAPI from '@verdaccio/server';
import { import {
HEADERS, HEADERS,
API_ERROR,
HTTP_STATUS, HTTP_STATUS,
HEADER_TYPE, HEADER_TYPE,
API_MESSAGE, API_MESSAGE,
@ -85,67 +84,7 @@ describe('endpoint unit test', () => {
}); });
describe('Registry API Endpoints', () => { describe('Registry API Endpoints', () => {
describe('should test ping api', () => {
test('should test endpoint /-/ping', (done) => {
request(app)
.get('/-/ping')
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK)
.end(function(err) {
if (err) {
return done(err);
}
done();
});
});
});
describe('should test whoami api', () => {
test('should test referer /whoami endpoint', (done) => {
request(app)
.get('/whoami')
.set('referer', 'whoami')
.expect(HTTP_STATUS.OK)
.end(done);
});
test('should test no referer /whoami endpoint', (done) => {
request(app)
.get('/whoami')
.expect(HTTP_STATUS.NOT_FOUND)
.end(done);
});
test('should test /-/whoami endpoint', (done) => {
request(app)
.get('/-/whoami')
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK)
.end(function(err) {
if (err) {
return done(err);
}
done();
});
});
test('should test /whoami endpoint', (done) => {
request(app)
.get('/-/whoami')
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK)
.end(function(err) {
if (err) {
return done(err);
}
done();
});
});
});
describe('should test user api', () => { describe('should test user api', () => {
describe('should test authorization headers with tokens only errors', () => { describe('should test authorization headers with tokens only errors', () => {
test('should fails on protected endpoint /-/auth-package bad format', (done) => { test('should fails on protected endpoint /-/auth-package bad format', (done) => {
request(app) request(app)
@ -219,106 +158,6 @@ describe('endpoint unit test', () => {
}); });
}); });
}); });
test('should test fails add a new user with missing name', (done) => {
const credentialsShort = _.cloneDeep(credentials);
delete credentialsShort.name;
request(app)
.put(`/-/user/org.couchdb.user:${credentials.name}`)
.send(credentialsShort)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.BAD_REQUEST)
.end(function(err, res) {
if (err) {
return done(err);
}
expect(res.body.error).toBeDefined();
expect(res.body.error).toMatch(API_ERROR.USERNAME_PASSWORD_REQUIRED);
done();
});
});
test('should test fails add a new user with missing password', (done) => {
const credentialsShort = _.cloneDeep(credentials);
delete credentialsShort.password;
request(app)
.put(`/-/user/org.couchdb.user:${credentials.name}`)
.send(credentialsShort)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.BAD_REQUEST)
.end(function(err, res) {
if (err) {
return done(err);
}
expect(res.body.error).toBeDefined();
// FIXME: message is not 100% accurate
/* eslint new-cap: 0 */
expect(res.body.error).toMatch(API_ERROR.PASSWORD_SHORT());
done();
});
});
test('should test add a new user with login', (done) => {
const newCredentials = _.cloneDeep(credentials);
newCredentials.name = 'jotaNew';
request(app)
.put('/-/user/org.couchdb.user:jotaNew')
.send(newCredentials)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.CREATED)
.end(function(err, res) {
if (err) {
return done(err);
}
expect(res.body).toBeTruthy();
done();
});
});
test('should test fails on add a existing user with login', (done) => {
request(app)
.put('/-/user/org.couchdb.user:jotaNew')
.send(credentials)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.CONFLICT)
.end(function(err, res) {
if (err) {
return done(err);
}
expect(res.body.error).toBeDefined();
expect(res.body.error).toMatch(API_ERROR.USERNAME_ALREADY_REGISTERED);
done();
});
});
test('should test fails add a new user with wrong password', (done) => {
const credentialsShort = _.cloneDeep(credentials);
credentialsShort.password = 'failPassword';
request(app)
.put('/-/user/org.couchdb.user:jota')
.send(credentialsShort)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.UNAUTHORIZED)
.end(function(err, res) {
if (err) {
return done(err);
}
expect(res.body.error).toBeDefined();
expect(res.body.error).toMatch(/unauthorized/);
done();
});
});
}); });
describe('should test package api', () => { describe('should test package api', () => {

@ -6,11 +6,5 @@ declare global {
remote_user: RemoteUser; remote_user: RemoteUser;
log: Logger; log: Logger;
} }
export interface Response {
report_error: any;
_verdaccio_error: any;
socket?: any;
}
} }
} }

@ -23,20 +23,20 @@
"build": "npm run build:js && npm run build:types" "build": "npm run build:js && npm run build:types"
}, },
"dependencies": { "dependencies": {
"@verdaccio/commons-api": "9.4.0", "@verdaccio/commons-api": "9.6.1",
"@verdaccio/dev-commons": "5.0.0-alpha.0", "@verdaccio/dev-commons": "5.0.0-alpha.0",
"@verdaccio/loaders": "5.0.0-alpha.0", "@verdaccio/loaders": "5.0.0-alpha.0",
"@verdaccio/local-storage": "9.5.0", "@verdaccio/local-storage": "9.6.1",
"@verdaccio/logger": "5.0.0-alpha.0", "@verdaccio/logger": "5.0.0-alpha.0",
"@verdaccio/proxy": "5.0.0-alpha.0", "@verdaccio/proxy": "5.0.0-alpha.0",
"@verdaccio/streams": "9.5.0", "@verdaccio/streams": "9.6.1",
"@verdaccio/utils": "5.0.0-alpha.0", "@verdaccio/utils": "5.0.0-alpha.0",
"async": "3.1.1", "async": "3.1.1",
"lodash": "4.17.15", "lodash": "4.17.15",
"semver": "7.1.2" "semver": "7.1.2"
}, },
"devDependencies": { "devDependencies": {
"@verdaccio/types": "9.3.0" "@verdaccio/types": "9.5.0"
}, },
"gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982" "gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982"
} }

@ -16,7 +16,7 @@
"homepage": "https://verdaccio.org", "homepage": "https://verdaccio.org",
"dependencies": { "dependencies": {
"@verdaccio/dev-commons": "5.0.0-alpha.0", "@verdaccio/dev-commons": "5.0.0-alpha.0",
"@verdaccio/readme": "9.5.0", "@verdaccio/readme": "9.6.1",
"js-yaml": "3.13.1", "js-yaml": "3.13.1",
"jsonwebtoken": "8.5.1", "jsonwebtoken": "8.5.1",
"minimatch": "3.0.4", "minimatch": "3.0.4",

@ -0,0 +1,8 @@
{
"rules": {
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/explicit-member-accessibility": 0,
"no-console": 0,
"@typescript-eslint/no-unused-vars": 1
}
}

@ -16,7 +16,7 @@
}, },
"devDependencies": { "devDependencies": {
"@verdaccio/dev-types": "5.0.0-alpha.0", "@verdaccio/dev-types": "5.0.0-alpha.0",
"@verdaccio/types": "9.3.0" "@verdaccio/types": "9.5.0"
}, },
"scripts": { "scripts": {
"clean": "rimraf ./build", "clean": "rimraf ./build",

@ -6,11 +6,5 @@ declare global {
remote_user: RemoteUser; remote_user: RemoteUser;
log: Logger; log: Logger;
} }
export interface Response {
report_error: any;
_verdaccio_error: any;
socket?: any;
}
} }
} }

@ -5,7 +5,7 @@
"module": "commonjs", "module": "commonjs",
"declaration": true, "declaration": true,
"noImplicitAny": false, "noImplicitAny": false,
"incremental": true, "incremental": false,
"strict": true, "strict": true,
"strictNullChecks": true, "strictNullChecks": true,
"resolveJsonModule": true, "resolveJsonModule": true,

BIN
yarn.lock

Binary file not shown.