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

additional tests for config pkg (#2237)

* add test config

* add test for home creation

* more checks

* add test

* skip peer platform

* lint

* fix windows
This commit is contained in:
Juan Picado 2021-05-09 00:44:07 +02:00 committed by GitHub
parent 52b47868e3
commit a54c18c02a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 292 additions and 101 deletions

View file

@ -1,6 +1,5 @@
import fs from 'fs';
import path from 'path';
import Path from 'path';
import _ from 'lodash';
import buildDebug from 'debug';
@ -27,28 +26,34 @@ const debug = buildDebug('verdaccio:config');
* Find and get the first config file that match.
* @return {String} the config file path
*/
function findConfigFile(configPath: string | undefined): string {
function findConfigFile(configPath?: string): string {
// console.log(process.env);
if (typeof configPath !== 'undefined') {
return Path.resolve(configPath);
return path.resolve(configPath);
}
const configPaths: SetupDirectory[] = getConfigPaths();
debug('%o posible locations found', configPaths.length);
if (_.isEmpty(configPaths)) {
// this should never happens
throw new Error('no configuration files can be processed');
}
const primaryConf: any = _.find(configPaths, (configLocation: any) =>
// find the first location that already exist
const primaryConf: SetupDirectory | void = _.find(configPaths, (configLocation: SetupDirectory) =>
fileExists(configLocation.path)
);
if (_.isNil(primaryConf) === false) {
if (typeof primaryConf !== 'undefined') {
debug('previous location exist already %s', primaryConf?.path);
return primaryConf.path;
}
// @ts-ignore
return createConfigFile(_.head(configPaths)).path;
}
function createConfigFile(configLocation: any): SetupDirectory {
function createConfigFile(configLocation: SetupDirectory): SetupDirectory {
createConfigFolder(configLocation);
const defaultConfig = updateStorageLinks(configLocation, readDefaultConfig());
@ -60,13 +65,18 @@ function createConfigFile(configLocation: any): SetupDirectory {
export function readDefaultConfig(): Buffer {
const pathDefaultConf: string = path.resolve(__dirname, 'conf/default.yaml');
try {
debug('default configuration file %s', pathDefaultConf);
fs.accessSync(pathDefaultConf, fs.constants.R_OK);
} catch {
throw new TypeError('configuration file does not have enough permissions for reading');
}
// @ts-ignore
return fs.readFileSync(pathDefaultConf, CHARACTER_ENCODING.UTF8);
}
function createConfigFolder(configLocation): void {
fs.mkdirSync(Path.dirname(configLocation.path), { recursive: true });
fs.mkdirSync(path.dirname(configLocation.path), { recursive: true });
debug(`Creating default config file in %o`, configLocation?.path);
}
@ -78,64 +88,89 @@ function updateStorageLinks(configLocation, defaultConfig): string {
// $XDG_DATA_HOME defines the base directory relative to which user specific data
// files should be stored, If $XDG_DATA_HOME is either not set or empty, a default
// equal to $HOME/.local/share should be used.
// $FlowFixMe
let dataDir =
process.env.XDG_DATA_HOME || Path.join(process.env.HOME as string, '.local', 'share');
process.env.XDG_DATA_HOME || path.join(process.env.HOME as string, '.local', 'share');
if (folderExists(dataDir)) {
dataDir = Path.resolve(Path.join(dataDir, pkgJSON.name, 'storage'));
debug(`previous storage located`);
debug(`update storage links to %s`, dataDir);
dataDir = path.resolve(path.join(dataDir, pkgJSON.name, 'storage'));
return defaultConfig.replace(/^storage: .\/storage$/m, `storage: ${dataDir}`);
}
debug(`could not find a previous storage location, skip override`);
return defaultConfig;
}
/**
* Return a list of configuration locations by platform.
* @returns
*/
function getConfigPaths(): SetupDirectory[] {
const listPaths: SetupDirectory[] = [
const listPaths: (SetupDirectory | void)[] = [
getXDGDirectory(),
getWindowsDirectory(),
getRelativeDefaultDirectory(),
getOldDirectory(),
].reduce(function (acc, currentValue: any): SetupDirectory[] {
if (_.isUndefined(currentValue) === false) {
];
return listPaths.reduce(function (acc, currentValue: SetupDirectory | void): SetupDirectory[] {
if (typeof currentValue !== 'undefined') {
debug('directory detected path %s for type %s', currentValue?.path, currentValue.type);
acc.push(currentValue);
}
return acc;
}, [] as SetupDirectory[]);
return listPaths;
}
/**
* Get XDG_CONFIG_HOME or HOME location (usually unix)
* @returns
*/
const getXDGDirectory = (): SetupDirectory | void => {
const XDGConfig = getXDGHome() || (process.env.HOME && Path.join(process.env.HOME, '.config'));
if (XDGConfig && folderExists(XDGConfig)) {
const xDGConfigPath =
process.env.XDG_CONFIG_HOME || (process.env.HOME && path.join(process.env.HOME, '.config'));
if (xDGConfigPath && folderExists(xDGConfigPath)) {
debug('XDGConfig folder path %s', xDGConfigPath);
return {
path: Path.join(XDGConfig, pkgJSON.name, CONFIG_FILE),
path: path.join(xDGConfigPath, pkgJSON.name, CONFIG_FILE),
type: XDG,
};
}
};
const getXDGHome = (): string | void => process.env.XDG_CONFIG_HOME;
/**
* Detect windows location, APPDATA
* usually something like C:\User\<Build User>\AppData\Local
* @returns
*/
const getWindowsDirectory = (): SetupDirectory | void => {
if (process.platform === WIN32 && process.env.APPDATA && folderExists(process.env.APPDATA)) {
debug('is windows appdata: %s', process.env.APPDATA);
return {
path: Path.resolve(Path.join(process.env.APPDATA, pkgJSON.name, CONFIG_FILE)),
path: path.resolve(path.join(process.env.APPDATA, pkgJSON.name, CONFIG_FILE)),
type: WIN,
};
}
};
/**
* Return relative directory, this is the default.
* It will cretate config in your {currentLocation/verdaccio/config.yaml}
* @returns
*/
const getRelativeDefaultDirectory = (): SetupDirectory => {
return {
path: Path.resolve(Path.join('.', pkgJSON.name, CONFIG_FILE)),
path: path.resolve(path.join('.', pkgJSON.name, CONFIG_FILE)),
type: 'def',
};
};
/**
* This should never happens, consider it DEPRECATED
* @returns
*/
const getOldDirectory = (): SetupDirectory => {
return {
path: Path.resolve(Path.join('.', CONFIG_FILE)),
path: path.resolve(path.join('.', CONFIG_FILE)),
type: 'old',
};
};

View file

@ -106,7 +106,7 @@ class Config implements AppConfig {
/**
* Store or create whether receive a secret key
*/
public checkSecretKey(secret: string): string {
public checkSecretKey(secret?: string): string {
debug('check secret key');
if (_.isString(secret) && _.isEmpty(secret) === false) {
this.secret = secret;

View file

@ -0,0 +1,105 @@
import os from 'os';
import { findConfigFile } from '../src/config-path';
const mockmkDir = jest.fn();
const mockaccessSync = jest.fn();
const mockwriteFile = jest.fn();
jest.mock('fs', () => {
const fsOri = jest.requireActual('fs');
return {
...fsOri,
statSync: (path) => ({
isDirectory: () => {
if (path.match(/fail/)) {
throw Error('file does not exist');
}
return true;
},
}),
accessSync: (a) => mockaccessSync(a),
mkdirSync: (a) => mockmkDir(a),
writeFileSync: (a) => mockwriteFile(a),
};
});
jest.mock('fs');
describe('config-path', () => {
beforeEach(() => {
jest.clearAllMocks();
jest.resetAllMocks();
});
describe('findConfigFile', () => {
if (os.platform() !== 'win32') {
describe('using defiled location from arguments', () => {
test('with custom location', () => {
expect(findConfigFile('/home/user/custom/location/config.yaml')).toEqual(
'/home/user/custom/location/config.yaml'
);
expect(mockwriteFile).not.toHaveBeenCalled();
expect(mockmkDir).not.toHaveBeenCalled();
});
});
describe('whith env variables', () => {
test('with XDG_CONFIG_HOME if directory exist but config file is missing', () => {
process.env.XDG_CONFIG_HOME = '/home/user';
expect(findConfigFile()).toEqual('/home/user/verdaccio/config.yaml');
expect(mockwriteFile).toHaveBeenCalledWith('/home/user/verdaccio/config.yaml');
expect(mockmkDir).toHaveBeenCalledWith('/home/user/verdaccio');
});
test('with HOME if directory exist but config file is missing', () => {
delete process.env.XDG_CONFIG_HOME;
process.env.HOME = '/home/user';
expect(findConfigFile()).toEqual('/home/user/.config/verdaccio/config.yaml');
expect(mockwriteFile).toHaveBeenCalledWith('/home/user/.config/verdaccio/config.yaml');
expect(mockmkDir).toHaveBeenCalledWith('/home/user/.config/verdaccio');
});
describe('error handling', () => {
test('XDG_CONFIG_HOME is not directory fallback to default', () => {
process.env.XDG_CONFIG_HOME = '/home/user/fail';
mockaccessSync.mockImplementation(() => {});
mockwriteFile.mockImplementation(() => {});
expect(findConfigFile()).toMatch('packages/config/verdaccio/config.yaml');
});
test('no permissions on read default config file', () => {
process.env.XDG_CONFIG_HOME = '/home/user';
mockaccessSync.mockImplementation(() => {
throw new Error('error on write file');
});
expect(function () {
findConfigFile();
}).toThrow(/configuration file does not have enough permissions for reading/);
});
});
});
describe('with no env variables', () => {
test('with relative location', () => {
mockaccessSync.mockImplementation(() => {});
delete process.env.XDG_CONFIG_HOME;
delete process.env.HOME;
process.env.APPDATA = '/app/data/';
expect(findConfigFile()).toMatch('packages/config/verdaccio/config.yaml');
expect(mockwriteFile).toHaveBeenCalled();
expect(mockmkDir).toHaveBeenCalled();
});
});
} else {
test('with windows as directory exist but config file is missing', () => {
delete process.env.XDG_CONFIG_HOME;
delete process.env.HOME;
process.env.APPDATA = '/app/data/';
expect(findConfigFile()).toEqual('D:\\app\\data\\verdaccio\\config.yaml');
expect(mockwriteFile).toHaveBeenCalledWith('D:\\app\\data\\verdaccio\\config.yaml');
expect(mockmkDir).toHaveBeenCalledWith('D:\\app\\data\\verdaccio');
});
}
});
});

View file

@ -9,6 +9,7 @@ import {
parseConfigFile,
ROLES,
WEB_TITLE,
getMatchedPackagesSpec,
} from '../src';
import { parseConfigurationFile } from './utils';
@ -23,56 +24,56 @@ const checkDefaultUplink = (config) => {
expect(config.uplinks[DEFAULT_UPLINK].url).toMatch(DEFAULT_REGISTRY);
};
const checkDefaultConfPackages = (config) => {
// auth
expect(_.isObject(config.auth)).toBeTruthy();
expect(_.isObject(config.auth.htpasswd)).toBeTruthy();
expect(config.auth.htpasswd.file).toMatch(/htpasswd/);
// web
expect(_.isObject(config.web)).toBeTruthy();
expect(config.web.title).toBe(WEB_TITLE);
expect(config.web.enable).toBeUndefined();
// packages
expect(_.isObject(config.packages)).toBeTruthy();
expect(Object.keys(config.packages).join('|')).toBe('@*/*|**');
expect(config.packages['@*/*'].access).toBeDefined();
expect(config.packages['@*/*'].access).toContainEqual(ROLES.$ALL);
expect(config.packages['@*/*'].publish).toBeDefined();
expect(config.packages['@*/*'].publish).toContainEqual(ROLES.$AUTH);
expect(config.packages['@*/*'].proxy).toBeDefined();
expect(config.packages['@*/*'].proxy).toContainEqual(DEFAULT_UPLINK);
expect(config.packages['**'].access).toBeDefined();
expect(config.packages['**'].access).toContainEqual(ROLES.$ALL);
expect(config.packages['**'].publish).toBeDefined();
expect(config.packages['**'].publish).toContainEqual(ROLES.$AUTH);
expect(config.packages['**'].proxy).toBeDefined();
expect(config.packages['**'].proxy).toContainEqual(DEFAULT_UPLINK);
// uplinks
expect(config.uplinks[DEFAULT_UPLINK]).toBeDefined();
expect(config.uplinks[DEFAULT_UPLINK].url).toEqual(DEFAULT_REGISTRY);
// audit
expect(config.middlewares).toBeDefined();
expect(config.middlewares.audit).toBeDefined();
expect(config.middlewares.audit.enabled).toBeTruthy();
// logs
expect(config.logs).toBeDefined();
expect(config.logs.type).toEqual('stdout');
expect(config.logs.format).toEqual('pretty');
expect(config.logs.level).toEqual('http');
// must not be enabled by default
expect(config.notify).toBeUndefined();
expect(config.store).toBeUndefined();
expect(config.publish).toBeUndefined();
expect(config.url_prefix).toBeUndefined();
expect(config.url_prefix).toBeUndefined();
expect(config.experiments).toBeUndefined();
expect(config.security).toEqual(defaultSecurity);
};
describe('check basic content parsed file', () => {
const checkDefaultConfPackages = (config) => {
// auth
expect(_.isObject(config.auth)).toBeTruthy();
expect(_.isObject(config.auth.htpasswd)).toBeTruthy();
expect(config.auth.htpasswd.file).toMatch(/htpasswd/);
// web
expect(_.isObject(config.web)).toBeTruthy();
expect(config.web.title).toBe(WEB_TITLE);
expect(config.web.enable).toBeUndefined();
// packages
expect(_.isObject(config.packages)).toBeTruthy();
expect(Object.keys(config.packages).join('|')).toBe('@*/*|**');
expect(config.packages['@*/*'].access).toBeDefined();
expect(config.packages['@*/*'].access).toContainEqual(ROLES.$ALL);
expect(config.packages['@*/*'].publish).toBeDefined();
expect(config.packages['@*/*'].publish).toContainEqual(ROLES.$AUTH);
expect(config.packages['@*/*'].proxy).toBeDefined();
expect(config.packages['@*/*'].proxy).toContainEqual(DEFAULT_UPLINK);
expect(config.packages['**'].access).toBeDefined();
expect(config.packages['**'].access).toContainEqual(ROLES.$ALL);
expect(config.packages['**'].publish).toBeDefined();
expect(config.packages['**'].publish).toContainEqual(ROLES.$AUTH);
expect(config.packages['**'].proxy).toBeDefined();
expect(config.packages['**'].proxy).toContainEqual(DEFAULT_UPLINK);
// uplinks
expect(config.uplinks[DEFAULT_UPLINK]).toBeDefined();
expect(config.uplinks[DEFAULT_UPLINK].url).toEqual(DEFAULT_REGISTRY);
// audit
expect(config.middlewares).toBeDefined();
expect(config.middlewares.audit).toBeDefined();
expect(config.middlewares.audit.enabled).toBeTruthy();
// logs
expect(config.logs).toBeDefined();
expect(config.logs.type).toEqual('stdout');
expect(config.logs.format).toEqual('pretty');
expect(config.logs.level).toEqual('http');
// must not be enabled by default
expect(config.notify).toBeUndefined();
expect(config.store).toBeUndefined();
expect(config.publish).toBeUndefined();
expect(config.url_prefix).toBeUndefined();
expect(config.url_prefix).toBeUndefined();
expect(config.experiments).toBeUndefined();
expect(config.security).toEqual(defaultSecurity);
};
test('parse default.yaml', () => {
const config = new Config(parseConfigFile(resolveConf('default')));
checkDefaultUplink(config);
@ -81,6 +82,57 @@ describe('check basic content parsed file', () => {
checkDefaultConfPackages(config);
});
test('parse docker.yaml', () => {
const config = new Config(parseConfigFile(resolveConf('docker')));
checkDefaultUplink(config);
expect(config.storage).toBe('/verdaccio/storage/data');
expect(config.auth.htpasswd.file).toBe('/verdaccio/storage/htpasswd');
checkDefaultConfPackages(config);
});
});
describe('checkSecretKey', () => {
test('with default.yaml and pre selected secret', () => {
const config = new Config(parseConfigFile(resolveConf('default')));
expect(config.checkSecretKey('12345')).toEqual('12345');
});
test('with default.yaml and void secret', () => {
const config = new Config(parseConfigFile(resolveConf('default')));
expect(typeof config.checkSecretKey() === 'string').toBeTruthy();
});
test('with default.yaml and emtpy string secret', () => {
const config = new Config(parseConfigFile(resolveConf('default')));
expect(typeof config.checkSecretKey('') === 'string').toBeTruthy();
});
});
describe('getMatchedPackagesSpec', () => {
test('should match with react as defined in config file', () => {
const configParsed = parseConfigFile(parseConfigurationFile('config-getMatchedPackagesSpec'));
const config = new Config(configParsed);
expect(config.getMatchedPackagesSpec('react')).toEqual({
access: ['admin'],
proxy: ['facebook'],
publish: ['admin'],
unpublish: false,
});
});
test('should not match with react as defined in config file', () => {
const configParsed = parseConfigFile(parseConfigurationFile('config-getMatchedPackagesSpec'));
const config = new Config(configParsed);
expect(config.getMatchedPackagesSpec('somePackage')).toEqual({
access: [ROLES.$ALL],
proxy: ['npmjs'],
publish: [ROLES.$AUTH],
unpublish: false,
});
});
});
describe('VERDACCIO_STORAGE_PATH', () => {
test('should set storage to value set in VERDACCIO_STORAGE_PATH environment variable', () => {
const storageLocation = '/tmp/verdaccio';
process.env.VERDACCIO_STORAGE_PATH = storageLocation;
@ -106,12 +158,4 @@ describe('check basic content parsed file', () => {
expect(config.storage).toBe(storageLocation);
delete process.env.VERDACCIO_STORAGE_PATH;
});
test('parse docker.yaml', () => {
const config = new Config(parseConfigFile(resolveConf('docker')));
checkDefaultUplink(config);
expect(config.storage).toBe('/verdaccio/storage/data');
expect(config.auth.htpasswd.file).toBe('/verdaccio/storage/htpasswd');
checkDefaultConfPackages(config);
});
});

View file

@ -88,26 +88,17 @@ describe('Package access utilities', () => {
() => {
const { packages } = parseConfigFile(parseConfigurationFile('deprecated-pkgs-basic'));
const access = normalisePackageAccess(packages);
expect(access).toBeDefined();
const scoped = access[`${PACKAGE_ACCESS.SCOPE}`];
const all = access[`${PACKAGE_ACCESS.ALL}`];
const react = access['react-*'];
expect(react).toBeDefined();
expect(react.access).toBeDefined();
// Intended checks, Typescript should catch this, we test the runtime part
// @ts-ignore
expect(react.access).toEqual([]);
// @ts-ignore
expect(react.publish[0]).toBe('admin');
expect(react.proxy).toBeDefined();
// @ts-ignore
expect(react.proxy).toEqual([]);
expect(react.storage).toBeDefined();
expect(react.storage).toBe('react-storage');
expect(scoped).toBeDefined();
expect(scoped.storage).not.toBeDefined();
@ -126,7 +117,6 @@ describe('Package access utilities', () => {
const scoped = access[`${PACKAGE_ACCESS.SCOPE}`];
expect(scoped).toBeUndefined();
// ** should be added by default **
const all = access[`${PACKAGE_ACCESS.ALL}`];
expect(all).toBeDefined();
@ -141,23 +131,23 @@ describe('Package access utilities', () => {
describe('getMatchedPackagesSpec', () => {
test('should test basic config', () => {
const { packages } = parseConfigFile(parseConfigurationFile('pkgs-custom'));
// @ts-ignore
// @ts-expect-error
expect(getMatchedPackagesSpec('react', packages).proxy).toMatch('facebook');
// @ts-ignore
// @ts-expect-error
expect(getMatchedPackagesSpec('angular', packages).proxy).toMatch('google');
// @ts-ignore
// @ts-expect-error
expect(getMatchedPackagesSpec('vue', packages).proxy).toMatch('npmjs');
// @ts-ignore
// @ts-expect-error
expect(getMatchedPackagesSpec('@scope/vue', packages).proxy).toMatch('npmjs');
});
test('should test no ** wildcard on config', () => {
const { packages } = parseConfigFile(parseConfigurationFile('pkgs-nosuper-wildcard-custom'));
// @ts-ignore
// @ts-expect-error
expect(getMatchedPackagesSpec('react', packages).proxy).toMatch('facebook');
// @ts-ignore
// @ts-expect-error
expect(getMatchedPackagesSpec('angular', packages).proxy).toMatch('google');
// @ts-ignore
// @ts-expect-error
expect(getMatchedPackagesSpec('@fake/angular', packages).proxy).toMatch('npmjs');
expect(getMatchedPackagesSpec('vue', packages)).toBeUndefined();
expect(getMatchedPackagesSpec('@scope/vue', packages)).toBeUndefined();

View file

@ -0,0 +1,17 @@
packages:
'react':
access: admin
publish: admin
proxy: facebook
'angular':
access: admin
publish: admin
proxy: google
'@*/*':
access: $all
publish: $authenticated
proxy: npmjs
'**':
access: $all
publish: $authenticated
proxy: npmjs