Skip to content

Commit

Permalink
additional tests for config pkg (verdaccio#2237)
Browse files Browse the repository at this point in the history
* add test config

* add test for home creation

* more checks

* add test

* skip peer platform

* lint

* fix windows
  • Loading branch information
juanpicado authored May 8, 2021
1 parent 52b4786 commit a54c18c
Show file tree
Hide file tree
Showing 6 changed files with 292 additions and 101 deletions.
87 changes: 61 additions & 26 deletions packages/config/src/config-path.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import fs from 'fs';
import path from 'path';
import Path from 'path';
import _ from 'lodash';
import buildDebug from 'debug';

Expand All @@ -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());
Expand All @@ -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);
}

Expand All @@ -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',
};
};
Expand Down
2 changes: 1 addition & 1 deletion packages/config/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
105 changes: 105 additions & 0 deletions packages/config/test/config.path.spec.ts
Original file line number Diff line number Diff line change
@@ -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');
});
}
});
});
Loading

0 comments on commit a54c18c

Please sign in to comment.