Convert everything over to TypeScript

This was mostly minor minor tweaking of imports and fiddling with a few types
This commit is contained in:
R. Tyler Croy 2018-10-05 19:21:31 -07:00
parent 7b539d05c2
commit 044ba25859
No known key found for this signature in database
GPG Key ID: 1426C7DC3F51E16F
34 changed files with 356 additions and 256 deletions

View File

@ -8,6 +8,8 @@ lint:: compile
check:: compile
unit:: compile
compile:
tsc
@ -27,6 +29,6 @@ run: depends build
EVERGREEN_DISABLE_SNAPSHOT=true \
EVERGREEN_ENDPOINT=http://127.0.0.1:3030 \
FLAVOR=docker-cloud \
npm run client
npm run start
.PHONY: run build docs compile

View File

@ -173,6 +173,12 @@
"integrity": "sha512-ojnbBiKkZFYRfQpmtnnWTMw+rzGp/JiystjluW9jgN3VzRwilXddJ6aGQ9V/7iuDG06SBgn7ozW9k3zcAnYjYQ==",
"dev": true
},
"@types/tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.0.33.tgz",
"integrity": "sha1-EHPEvIJHVK49EM+riKsCN7qWTk0=",
"dev": true
},
"@webassemblyjs/ast": {
"version": "1.5.13",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.5.13.tgz",

View File

@ -4,10 +4,11 @@
"description": "",
"main": "src/client.js",
"scripts": {
"client": "node src/client.js",
"test": "npm run eslint && npm run jest",
"eslint": "eslint src/. test/. --config .eslintrc.json",
"jest": "jest",
"prestart": "tsc",
"start": "node build/client.js",
"pretest": "eslint src/. test/. --config .eslintrc.json",
"eslint": "npm run pretest",
"test": "jest",
"postinstall": "patch-package"
},
"author": "R Tyler Croy",
@ -15,6 +16,8 @@
"devDependencies": {
"@types/feathersjs__feathers": "^3.0.5",
"@types/jest": "^23.3.3",
"@types/node": "^10.11.4",
"@types/tmp": "0.0.33",
"asciidoctor.js": "^1.5.7-rc.1",
"css-loader": "^1.0.0",
"eslint": "^4.19.1",
@ -60,11 +63,23 @@
"<rootDir>/ui/index.js",
"<rootDir>/src/lib/ui.js"
],
"transform": {
"^.+\\.tsx?$": "ts-jest"
},
"testRegex": "(/test/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"coverageReporters": [
"json",
"lcov",
"text-summary"
],
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json",
"node"
],
"coverageThreshold": {
"global": {
"statements": 54,

View File

@ -2,28 +2,37 @@
* This is the main entrypoint for the evergreen-client
*/
const process = require('process');
import feathers from '@feathersjs/feathers';
import * as logger from 'winston';
import socketio from '@feathersjs/socketio-client';
import auth from '@feathersjs/authentication-client';
import io from 'socket.io-client';
const feathers = require('@feathersjs/feathers');
const logger = require('winston');
const socketio = require('@feathersjs/socketio-client');
const auth = require('@feathersjs/authentication-client');
const io = require('socket.io-client');
const createCron = require('./lib/periodic');
const ErrorTelemetry = require('./lib/error-telemetry');
const HealthChecker = require('./lib/healthchecker');
const Registration = require('./lib/registration');
const Status = require('./lib/status');
const Storage = require('./lib/storage');
const UI = require('./lib/ui');
const Update = require('./lib/update');
import ErrorTelemetry from './lib/error-telemetry';
import HealthChecker from './lib/healthchecker';
import Registration from './lib/registration';
import Status from './lib/status';
import Storage from './lib/storage';
import UI from './lib/ui'
import Update from './lib/update';
import Periodic from './lib/periodic';
/*
* The Client class is a simple wrapper meant to start the basics of the client
* and then run a simple runloop to block the client from ever exiting
*/
class Client {
export default class Client {
protected readonly app : any;
protected readonly reg : Registration;
protected readonly healthChecker : HealthChecker;
protected readonly update : Update;
protected readonly status : Status;
protected readonly errorTelemetry : ErrorTelemetry;
protected socket : any;
public updating : boolean;
constructor() {
if (!process.env.FLAVOR) {
logger.error('Fatal error encountered while trying to start, no flavor set, exiting the client');
@ -79,7 +88,7 @@ class Client {
* Only setting on the cron once we have registered and logged in,
* otherwise it's not really useful to have anything running periodically
*/
const cron = createCron(app);
const cron = new Periodic();
this.runUpdates();
@ -151,7 +160,7 @@ class Client {
this.runUpdates();
});
this.reg.register().then((res, newRegistration) => {
this.reg.register().then(({res, newRegistration}) => {
UI.publish('Registered this Evergreen instance', { log: 'debug', error: res} );
this.status.authenticate(this.reg.uuid, this.reg.token);
this.update.authenticate(this.reg.uuid, this.reg.token);
@ -168,8 +177,6 @@ class Client {
}
}
module.exports = Client;
if (require.main === module) {
Storage.setBootingFlag();
UI.serve();
@ -180,6 +187,6 @@ if (require.main === module) {
logger.level = process.env.LOG_LEVEL || 'warn';
/* Main entrypoint for module */
UI.publish('Starting the evergreen-client..', { log: 'info' });
let client = new Client();
const client = new Client();
client.bootstrap();
}

View File

@ -1,10 +1,10 @@
'use strict';
const crypto = require('crypto');
const fs = require('fs');
const logger = require('winston');
import crypto from 'crypto';
import fs from 'fs';
import * as logger from 'winston';
class Checksum {
export default class Checksum {
/*
* Generate a SHA-256 checksum signature from the provided relative or
* absolute file path
@ -26,5 +26,3 @@ class Checksum {
}
}
}
module.exports = Checksum;

View File

@ -3,18 +3,19 @@
* disk and checking them against provided checksums.
*/
const fs = require('fs');
const path = require('path');
const url = require('url');
import fs from 'fs';
import path from 'path';
import url from 'url';
const rp = require('promise-request-retry');
const logger = require('winston');
const mkdirp = require('mkdirp');
import rp from 'promise-request-retry';
import * as logger from 'winston';
import mkdirp from 'mkdirp';
const Checksum = require('./checksum');
const UI = require('./ui');
import UI from './ui';
import Checksum from './checksum';
import { RequestOptions } from './request-options';
class Downloader {
export default class Downloader {
constructor() {
}
@ -40,7 +41,10 @@ class Downloader {
* @param {string} the filename to output at
* @parma {string} Optional sha256 signature to verify of the file
*/
static download(item, dir, fileNameToWrite, sha256, downloadOptions = {}) {
static download(item, dir,
fileNameToWrite?: string,
sha256?: string,
downloadOptions: RequestOptions = {}) {
const itemUrl = url.parse(item);
const itemUrlBaseName = path.basename(itemUrl.pathname);
@ -113,5 +117,3 @@ class Downloader {
});
}
}
module.exports = Downloader;

View File

@ -5,13 +5,24 @@
* handle both error and "metrics" telemetry, but let's start with a smaller
* scope at least for now.
*/
const { Tail } = require('tail');
const fs = require('fs');
const logger = require('winston');
const path = require('path');
const Storage = require('./storage');
import { Tail } from 'tail';
import fs from 'fs';
import * as logger from 'winston'
import path from 'path';
import Storage from './storage';
export interface ErrorTelemetryOptions {
flavor?: string,
};
export default class ErrorTelemetry {
protected readonly app : any;
protected readonly options : ErrorTelemetryOptions;
public uuid : string;
public token : string;
class ErrorTelemetry {
constructor(app, options) {
this.app = app;
this.options = options;
@ -45,7 +56,7 @@ class ErrorTelemetry {
* monitoredFile: path to the log file to watch
* outputFunction(app,line): the function that will be called on each new line detected
*/
setup(monitoredFile) {
setup(monitoredFile?: string) {
logger.info('Setting up error logging...');
let loggingFile = monitoredFile || this.fileToWatch();

View File

@ -1,12 +1,21 @@
const logger = require('winston');
const rp = require('promise-request-retry');
import * as logger from 'winston'
import rp from 'promise-request-retry';
import { RequestOptions } from './request-options';
const INSTANCE_IDENTITY_URL = '/instance-identity/';
const METRICS_URL = '/metrics/evergreen/healthcheck';
class HealthChecker {
export default class HealthChecker {
protected readonly defaultRequestOptions : any;
constructor(jenkinsRootUrl, requestOptions = {}) {
public readonly jenkinsRootUrl : string;
public readonly retry : number;
public readonly delay : number;
public readonly factor : number;
constructor(jenkinsRootUrl, requestOptions: RequestOptions = {}) {
this.jenkinsRootUrl = jenkinsRootUrl;
// let's target ~3 to 5 minutes overall of attempts for updates to arrive + Jenkins to start
// TODO: later, introduce some smarter delay depending on the number of things to download?
@ -111,5 +120,3 @@ class HealthChecker {
});
}
}
module.exports = HealthChecker;

View File

@ -4,16 +4,17 @@
*
*/
const logger = require('winston');
const cron = require('cron');
import * as logger from 'winston'
import cron from 'cron';
export default class Periodic {
protected readonly jobs : any;
protected readonly offset : number;
class Periodic {
/*
* Requires the feathersjs app instance on initialization
*/
constructor (app, options) {
this.app = app;
this.options = options || {};
constructor() {
this.jobs = {};
this.offset = this.computeOffset();
logger.info('Periodic using minute offset of', this.offset);
@ -48,8 +49,3 @@ class Periodic {
return Math.floor(Math.random() * 59);
}
}
module.exports = function(app, options) {
return new Periodic(app, options);
};
module.exports.Periodic = Periodic;

View File

@ -6,8 +6,8 @@
* It seems to not believe that we're in nodejs when we really are
*/
const crypto = require('crypto');
const rand = require('brorand');
import crypto from 'crypto';
import rand from 'brorand';
rand.Rand.prototype._rand = function _rand(n) {
return crypto.randomBytes(n);

View File

@ -3,20 +3,35 @@
* of the evergreen-client with the backend services layer
*/
const ecc = require('elliptic');
const fs = require('fs');
const logger = require('winston');
const path = require('path');
const mkdirp = require('mkdirp');
import ecc from 'elliptic';
import * as logger from 'winston';
import path from 'path';
import mkdirp from 'mkdirp'
import fs from 'fs';
const storage = require('./storage');
import Storage from './storage';
require('./rand-patch');
class Registration {
constructor (app, options) {
export interface FileOptions {
encoding?: string,
flag?: string,
};
export default class Registration {
protected readonly app : any;
protected readonly options : Map<string, any>;
protected readonly curve : string;
protected privateKey : string;
protected publicKey : string;
protected fileOptions : FileOptions;
public uuid : string;
public token : string;
constructor(app) {
this.app = app;
this.options = options || {};
this.uuid = null;
this.publicKey = null;
this.privateKey = null;
@ -46,15 +61,19 @@ class Registration {
* @return Promise
*/
async register() {
let self = this;
const self = this;
return new Promise((resolve, reject) => {
let api = self.app.service('registration');
const api = self.app.service('registration');
logger.info('Checking registration status..');
if (self.hasKeys() && self.hasUUID()) {
logger.info('We have keys and a UUID already');
self.loadKeysSync();
self.loadUUIDSync();
return self.login().then(res => resolve(res, false));
return self.login().then(res => resolve(
{
result: res,
created: false,
}));
} else {
if (!self.generateKeys()) {
return reject('Failed to generate keys');
@ -72,7 +91,10 @@ class Registration {
if (!self.saveUUIDSync()) {
reject('Failed to save UUID!');
} else {
return self.login().then(res => resolve(res, true));
return self.login().then(res => resolve({
result: res,
created: true,
}));
}
}).catch((res) => {
logger.error('Failed to register:', res);
@ -108,8 +130,8 @@ class Registration {
* @return Boolean
*/
generateKeys() {
let ec = new ecc.ec(this.curve);
let privkey = ec.genKeyPair();
const ec = new ecc.ec(this.curve);
const privkey = ec.genKeyPair();
this.publicKey = privkey.getPublic('hex');
this.privateKey = privkey.getPrivate('hex');
@ -126,7 +148,7 @@ class Registration {
*
* Will return null if no public key has yet been generated
*
* @return String
* @return string
*/
getPublicKey() {
return this.publicKey;
@ -154,7 +176,7 @@ class Registration {
}
loadUUIDSync() {
let config = JSON.parse(fs.readFileSync(this.uuidPath(), this.fileOptions));
const config = JSON.parse(fs.readFileSync(this.uuidPath(), this.fileOptions) as string);
this.uuid = config.uuid;
return (!!this.uuid);
}
@ -215,8 +237,8 @@ class Registration {
if (!this.hasKeys()) {
return false;
}
this.publicKey = fs.readFileSync(this.publicKeyPath(), this.fileOptions);
this.privateKey = fs.readFileSync(this.privateKeyPath(), this.fileOptions);
this.publicKey = fs.readFileSync(this.publicKeyPath(), this.fileOptions) as string;
this.privateKey = fs.readFileSync(this.privateKeyPath(), this.fileOptions) as string;
return true;
}
@ -240,10 +262,10 @@ class Registration {
/* Return the directory where registration keys should be stored
*
* @return String
* @return string
*/
keyPath() {
const keys = path.join(storage.homeDirectory(), 'keys');
const keys = path.join(Storage.homeDirectory(), 'keys');
/* Only bother making the directory if it doesn't already exist */
try {
@ -263,7 +285,7 @@ class Registration {
*
* This public key is safe to be shared with external services
*
* @return String
* @return string
*/
publicKeyPath() {
return path.join(this.keyPath(), 'evergreen.pub');
@ -277,5 +299,3 @@ class Registration {
return path.join(this.keyPath(), 'uuid.json');
}
}
module.exports = Registration;

View File

@ -0,0 +1,6 @@
export interface RequestOptions {
retry?: number,
delay?: number,
factor?: number,
};

View File

@ -4,9 +4,9 @@
* https://github.com/jenkinsci/jep/tree/master/jep/302
*/
const { spawnSync } = require('child_process');
const logger = require('winston');
const fs = require('fs');
import { spawnSync } from 'child_process';
import * as logger from 'winston';
import fs from 'fs';
const LOG_PREFIX = '[snapshotting]';
// https://github.com/jenkinsci/jep/tree/master/jep/302#user-content-files-to-store
@ -16,10 +16,10 @@ const GITIGNORE_CONTENT = `
/secrets/master.key
`;
class Snapshotter {
export default class Snapshotter {
protected workingDirectory : string;
constructor(app, options) {
this.options = options || {};
constructor() {
}
/**

View File

@ -1,20 +1,30 @@
'use strict';
const fs = require('fs');
const path = require('path');
import path from 'path';
import fs from 'fs';
const logger = require('winston');
const Storage = require('./storage');
const Checksum = require('./checksum');
import * as logger from 'winston';
import Storage from './storage';
import Checksum from './checksum';
export interface StatusOptions {
flavor?: string,
};
/*
* The status module is responsible for collecting and reporting the current
* state of this instance to the Evergreen backend `Status` service
*/
class Status {
constructor(app, options) {
this.options = options || {};
export default class Status {
protected readonly app : any;
protected readonly options : StatusOptions;
public uuid : string;
public token : string;
constructor(app : any, options : StatusOptions = {}) {
this.options = options;
this.token = null;
this.uuid = null;
this.app = app;
@ -38,8 +48,8 @@ class Status {
* Create a status record in the backend for this particular instance
*/
async create() {
let api = this.app.service('status');
let record = {
const api = this.app.service('status');
const record = {
uuid: this.uuid,
flavor: this.options.flavor,
timezone: this.getTimezone(),
@ -68,8 +78,8 @@ class Status {
}
reportLevel(updateLevel) {
let api = this.app.service('status');
let record = {
const api = this.app.service('status');
const record = {
uuid: this.uuid,
updateId: updateLevel,
};
@ -152,10 +162,6 @@ class Status {
throw err;
}
}
return versions;
}
}
module.exports = Status;

View File

@ -1,15 +1,16 @@
'use strict';
const path = require('path');
const fs = require('fs');
const logger = require('winston');
const UI = require('./ui');
import fs from 'fs';
import path from 'path';
import * as logger from 'winston';
import UI from './ui';
/*
* The Storage module simply contains common functions necessary for the
* evergreen-client to store its own data.
*/
class Storage {
export default class Storage {
/*
* Returns the default home directory or the value of EVERGREEN_HOME
*
@ -69,12 +70,12 @@ class Storage {
}
}
static removePlugins(plugins) {
static removePlugins(plugins?: Array<any>) {
if (!plugins) {
return;
}
let pluginPath = this.pluginsDirectory();
let retArray = [];
const pluginPath = this.pluginsDirectory();
const retArray = [];
plugins.forEach((plugin) => {
retArray.push(fs.unlink(`${pluginPath}/${plugin}.hpi`, () => {
logger.info(`${pluginPath}/${plugin}.hpi was deleted`);
@ -84,5 +85,3 @@ class Storage {
return retArray;
}
}
module.exports = Storage;

View File

@ -3,15 +3,18 @@
* XML-RPC API: http://supervisord.org/api.html
*/
const xmlrpc = require('xmlrpc');
const client = xmlrpc.createClient('http://localhost:9001/RPC2');
const logger = require('winston');
import xmlrpc from 'xmlrpc';
import * as logger from 'winston'
const client = xmlrpc.createClient('http://localhost:9001/RPC2');
export default class Supervisord {
protected readonly client : xmlrpc.Client;
class Supervisord {
constructor() {
}
isRunning() {
static isRunning() {
return new Promise((resolve, reject) => {
client.methodCall('supervisor.getState', null, (e, v) => {
if (e) {
@ -22,7 +25,7 @@ class Supervisord {
});
}
printState(name) {
static printState(name) {
return new Promise((resolve, reject) => {
client.methodCall('supervisor.getProcessInfo', [name], (e, value) => {
if (e) {
@ -33,7 +36,7 @@ class Supervisord {
});
}
isProcessRunning(name) {
static isProcessRunning(name) {
return new Promise((resolve, reject) => {
client.methodCall('supervisor.getProcessInfo', [name], (e, value) => {
if (e) {
@ -44,7 +47,7 @@ class Supervisord {
});
}
startProcess(name) {
static startProcess(name) {
logger.info(`[supervisord] Starting ${name} process`);
return new Promise((resolve, reject) => {
client.methodCall('supervisor.startProcess', [name], (e, value) => {
@ -56,7 +59,7 @@ class Supervisord {
});
}
stopProcess(name) {
static stopProcess(name) {
logger.info(`[supervisord] Stopping ${name} process`);
return new Promise((resolve, reject) => {
client.methodCall('supervisor.stopProcess', [name], (e, value) => {
@ -68,7 +71,7 @@ class Supervisord {
});
}
async restartProcess(name) {
static async restartProcess(name) {
logger.info(`[supervisord] Restarting ${name} process`);
if (await this.isProcessRunning(name)) {
await this.stopProcess(name);
@ -76,5 +79,3 @@ class Supervisord {
return this.startProcess(name);
}
}
module.exports = new Supervisord();

View File

@ -14,6 +14,9 @@ const logger = require('winston');
* Simple shim feathers service just to enable events
*/
class MessageService {
protected readonly app : any;
protected recent : Array<any>;
constructor(app) {
this.app = app;
this.recent = [];
@ -37,6 +40,9 @@ class MessageService {
}
class UI {
protected readonly app : any;
protected server : any;
constructor() {
const app = express(feathers());
this.app = app;
@ -60,7 +66,7 @@ class UI {
* necessarily be presented to the user.
*
*/
publish(message, params) {
publish(message, params?: any) {
return this.app.service('messages').create({
message: message,
timestamp: Date.now(),
@ -75,4 +81,4 @@ class UI {
}
}
module.exports = new UI();
export default new UI();

View File

@ -4,26 +4,38 @@
* https://github.com/jenkinsci/jep/tree/master/jep/307
*/
const fs = require('fs');
const path = require('path');
const mkdirp = require('mkdirp');
const logger = require('winston');
import fs from 'fs';
import path from 'path';
import mkdirp from 'mkdirp';
import * as logger from 'winston';
const Downloader = require('./downloader');
const HealthChecker = require('./healthchecker');
const Storage = require('./storage');
const Supervisord = require('./supervisord');
const UI = require('./ui');
const Snapshotter = require('./snapshotter');
import Downloader from './downloader';
import HealthChecker from './healthchecker';
import Storage from './storage';
import Supervisord from './supervisord';
import UI from './ui';
import Snapshotter from './snapshotter';
class Update {
constructor(app, options) {
this.options = options || {};
export interface FileOptions {
encoding?: string,
flag?: string,
};
export default class Update {
protected readonly app : any;
protected readonly snapshotter : Snapshotter;
protected readonly fileOptions : FileOptions;
protected readonly options : any;
protected readonly healthChecker : HealthChecker;
public uuid : string;
public token : string;
public manifest : any;
public updateInProgress : Date;
constructor(app, options = {}) {
this.app = app;
this.token = null;
this.uuid = null;
this.manifest = null;
this.updateInProgress = null;
this.options = options;
this.fileOptions = { encoding: 'utf8' };
this.snapshotter = new Snapshotter();
this.snapshotter.init(Storage.jenkinsHome());
@ -84,7 +96,7 @@ class Update {
UI.publish('Starting to apply updates');
// Setting this to a timestamp to make a timeout in the future
this.updateInProgress = new Date();
let tasks = [];
const tasks = [];
if ((updates.core) && (updates.core.url)) {
tasks.push(Downloader.download(updates.core.url,
@ -150,7 +162,7 @@ class Update {
while it's not been yet marked as tainted in the backend?
* how to report that borked case in a clear way
*/
restartJenkins(rollingBack) { // Add param to stop recursion?
restartJenkins(rollingBack?: boolean) { // Add param to stop recursion?
Supervisord.restartProcess('jenkins');
const messageWhileRestarting = 'Jenkins should now be online, health checking!';
@ -207,7 +219,7 @@ class Update {
getCurrentLevel() {
this.loadUpdateSync();
if (this.manifest) {
let level = this.manifest.meta.level;
const level = this.manifest.meta.level;
logger.silly('Currently at Update Level %d', level);
return level;
}
@ -235,7 +247,7 @@ class Update {
}
}
this.manifest = JSON.parse(fs.readFileSync(this.updatePath(),
this.fileOptions));
this.fileOptions) as string);
return this.manifest;
}
@ -260,5 +272,3 @@ class Update {
return dir;
}
}
module.exports = Update;

View File

@ -2,8 +2,9 @@
jest.mock('fs');
const fs = require('fs');
const Checksum = require('../src/lib/checksum');
const fs = require('fs');
import Checksum from '../src/lib/checksum';
describe('Checksum', () => {
beforeEach(() => {

View File

@ -1,13 +1,12 @@
const assert = require('assert');
const tmp = require('tmp');
const Client = require('../src/client');
const Storage = require('../src/lib/storage');
const mkdirp = require('mkdirp');
describe('The base client module', () => {
import tmp from 'tmp';
import Client from '../src/client';
import Storage from '../src/lib/storage';
describe('The base client module', () => {
it('should interpret properly', () => {
assert(Client);
expect(Client).toBeTruthy();
});
describe('flavorCheck', () => {

View File

@ -2,8 +2,9 @@ jest.mock('fs');
const fs = require('fs');
const mkdirp = require('mkdirp');
const Downloader = require('../src/lib/downloader');
const Checksum = require('../src/lib/checksum');
import Downloader from '../src/lib/downloader';
import Checksum from '../src/lib/checksum';
describe('the Downloader class', () => {
describe('download()', () => {
@ -63,8 +64,9 @@ describe('the Downloader class', () => {
await Downloader.download(toDownload, dir, 'thefile', null, {delay: 20, retry: 4, factor: 10});
expect(false).toBeTruthy(); // fail(), should not reach this line.
} catch (e) {
const endTime = new Date();
// 4 attempts, no delay for the first, then 20 ms, 20*10 (exponential factor), 20*10*10
expect(new Date() - startTime).toBeGreaterThan(0 + 20 + 200 + 2000);
expect((endTime as any) - (startTime as any)).toBeGreaterThan(0 + 20 + 200 + 2000);
}
});
});

View File

@ -5,7 +5,7 @@ const fs = require('fs');
const path = require('path');
const mkdirp = require('mkdirp');
const ErrorTelemetry = require('../src/lib/error-telemetry');
import ErrorTelemetry from '../src/lib/error-telemetry';
describe('Error Telemetry Logging', () => {
beforeEach(() => {
@ -15,13 +15,13 @@ describe('Error Telemetry Logging', () => {
describe('authenticate()', () => {
it('should store values', () => {
const telemetry = new ErrorTelemetry().authenticate('you-you-i-Dee', 'toe-ken-that-guy');
const telemetry = new ErrorTelemetry(null, null).authenticate('you-you-i-Dee', 'toe-ken-that-guy');
assert.equal(telemetry.uuid, 'you-you-i-Dee');
});
});
describe('setup() call', () => {
const errorTelemetryService = new ErrorTelemetry();
const errorTelemetryService = new ErrorTelemetry(null, null);
let logsDir = '/evergreen/jenkins/war/logs';
let logFile = path.join(logsDir, 'evergreen.log.0');

View File

@ -1,7 +1,8 @@
const HealthChecker = require('../src/lib/healthchecker');
const logger = require('winston');
const http = require('http');
import HealthChecker from '../src/lib/healthchecker';
describe('The healthchecker module', () => {
let server = null;
let port = -1;
@ -13,17 +14,19 @@ describe('The healthchecker module', () => {
instanceIdentity: true,
metrics: {
plugins: true,
deadlock: true
}
deadlock: true,
body: null,
},
};
describe('constructor', () => {
it('should use default values if none passed in', () => {
const healthChecker = new HealthChecker();
const healthChecker = new HealthChecker('http://example.com');
expect(healthChecker.retry).toEqual(25);
expect(healthChecker.delay).toEqual(3000);
expect(healthChecker.factor).toEqual(1.10);
});
it('should use passed in values', () => {
const jenkinsRootUrl = `http://localhost:${port}`;
const requestOptions = {delay: 100, retry: 1, factor: 1.01};
@ -35,7 +38,7 @@ describe('The healthchecker module', () => {
});
it('should override the delay if env var set', () => {
const retryOverride = 21;
process.env.PROCESS_RETRY_OVERRIDE = retryOverride;
process.env.PROCESS_RETRY_OVERRIDE = retryOverride.toString();
const jenkinsRootUrl = `http://localhost:${port}`;
const requestOptions = {delay: 100, retry: 1, factor: 1.01};
const healthChecker = new HealthChecker(jenkinsRootUrl, requestOptions);
@ -45,7 +48,7 @@ describe('The healthchecker module', () => {
expect(healthChecker.factor).toEqual(requestOptions.factor);
});
it('should handle non int value in env var', () => {
process.env.PROCESS_RETRY_OVERRIDE = true;
process.env.PROCESS_RETRY_OVERRIDE = 'true';
const jenkinsRootUrl = `http://localhost:${port}`;
const requestOptions = {delay: 100, retry: 1, factor: 1.01};
const healthChecker = new HealthChecker(jenkinsRootUrl, requestOptions);

View File

@ -1,33 +0,0 @@
const assert = require('assert');
const periodic = require('../src/lib/periodic');
describe('The periodic module', () => {
/* Just a simple fake app for unit test
*/
let app = new Object();
describe('runHourly()', () => {
it('allow registration of an hourly callback', () => {
let p = periodic(app);
assert.ok(p.runHourly('jest-fun', () => { }));
});
});
describe('runDaily()', () => {
it('allows registration of a daily callback', () => {
let p = periodic(app);
assert.ok(p.runDaily('jest-fun', () => { }));
});
});
describe('computeOffset()', () => {
let p = periodic(app);
it('should return a number between 0-59', () => {
let offset = p.computeOffset();
assert.equal(typeof offset, 'number');
assert.ok(offset <= 59);
assert.ok(offset >= 0);
});
});
});

View File

@ -0,0 +1,29 @@
import Periodic from '../src/lib/periodic';
describe('The periodic module', () => {
/* Just a simple fake app for unit test
*/
describe('runHourly()', () => {
it('allow registration of an hourly callback', () => {
const p = new Periodic();
expect(p.runHourly('jest-fun', () => {})).toBeTruthy();
});
});
describe('runDaily()', () => {
it('allows registration of a daily callback', () => {
const p = new Periodic();
expect(p.runDaily('jest-fun', () => {})).toBeTruthy();
});
});
describe('computeOffset()', () => {
const p = new Periodic();
it('should return a number between 0-59', () => {
const offset = p.computeOffset();
expect(offset).toBeGreaterThanOrEqual(0);
expect(offset).toBeLessThanOrEqual(59);
});
});
});

View File

@ -3,9 +3,11 @@ jest.mock('fs');
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const Registration = require('../src/lib/registration');
import Registration from '../src/lib/registration';
describe('The registration module', () => {
const app = new Object();
beforeEach(() => {
/* Make sure memfs is flushed every time */
fs.volume.reset();
@ -13,19 +15,19 @@ describe('The registration module', () => {
describe('register()', () => {
it('should return a Promise', () => {
const response = (new Registration()).register();
const response = (new Registration(app)).register();
assert(response instanceof Promise);
});
});
describe('getPublicKey()', () => {
it('should return null on a new instance', () => {
const r = new Registration();
const r = new Registration(app);
assert.equal(null, r.getPublicKey());
});
it('should return a public key after generateKeys() has been called', () => {
const r = new Registration();
const r = new Registration(app);
assert(r.generateKeys());
assert.equal(typeof r.getPublicKey(), 'string');
});
@ -33,7 +35,7 @@ describe('The registration module', () => {
describe('saveUUIDSync()', () => {
beforeEach(() => {
this.reg = new Registration();
this.reg = new Registration(app);
});
it('should not write anything by default', () => {
assert.equal(this.reg.saveUUIDSync(), false);
@ -62,7 +64,7 @@ describe('The registration module', () => {
describe('loadUUIDSync()', () => {
let uuid = 'just another-fake-uuid';
beforeEach(() => {
this.reg = new Registration();
this.reg = new Registration(app);
this.reg.uuid = uuid;
this.reg.saveUUIDSync();
});
@ -76,13 +78,13 @@ describe('The registration module', () => {
describe('saveKeysSync()', () => {
it('should return false if there are not keys', () => {
const r = new Registration();
const r = new Registration(app);
assert(!r.saveKeysSync());
});
describe('when keys have been generated', () => {
beforeEach(() => {
this.reg = new Registration();
this.reg = new Registration(app);
this.reg.generateKeys();
});
it('should return true if the public key has been written', () => {
@ -107,7 +109,7 @@ describe('The registration module', () => {
describe('loadKeysSync()', () => {
beforeEach(() => {
this.reg = new Registration();
this.reg = new Registration(app);
});
it('should return false by default when there are no keys', () => {
@ -121,7 +123,7 @@ describe('The registration module', () => {
describe('when keys are already on disk', () => {
beforeEach(() => {
const preflight = new Registration();
const preflight = new Registration(app);
preflight.generateKeys();
preflight.saveKeysSync();
});
@ -135,31 +137,31 @@ describe('The registration module', () => {
describe('hasKeys()', () => {
it('should return false by default', () => {
assert.equal((new Registration()).hasKeys(), false);
assert.equal((new Registration(app)).hasKeys(), false);
});
});
describe('generateKeys()', () => {
it('should return a boolean on success', () => {
assert.ok((new Registration()).generateKeys());
assert.ok((new Registration(app)).generateKeys());
});
});
describe('publicKeyPath()', () => {
it('should return a path', () => {
const p = (new Registration()).publicKeyPath();
const p = (new Registration(app)).publicKeyPath();
assert(p != path.basename(p), 'This doesn\'t look like a path');
});
});
describe('keyPath()', () => {
it('should return a path', () => {
const keys = (new Registration()).keyPath();
const keys = (new Registration(app)).keyPath();
assert(keys != path.basename(keys), 'This doesn\'t look like a path');
});
it('should create a directory if one does not exist', () => {
const keyPath = (new Registration()).keyPath();
const keyPath = (new Registration(app)).keyPath();
const stats = fs.statSync(keyPath);
assert(stats.isDirectory());
});
@ -176,7 +178,7 @@ describe('The registration module', () => {
describe('uuidPath()', () => {
it('should return a path', () => {
const p = (new Registration()).uuidPath();
const p = (new Registration(app)).uuidPath();
assert(p != path.basename(p), 'This doesn\'t look like a path');
});
});

View File

@ -1,10 +1,10 @@
const Snapshotter = require('../src/lib/snapshotter');
const tmp = require('tmp');
const fs = require('fs');
describe('The snapshotting module', () => {
import tmp from 'tmp';
import Snapshotter from '../src/lib/snapshotter';
let tmpDir = '';
describe('The snapshotting module', () => {
let tmpDir = null;
beforeEach(() => {
tmpDir = tmp.dirSync({unsafeCleanup: true});
});

View File

@ -5,7 +5,8 @@ jest.mock('fs');
const fs = require('fs');
const mkdirp = require('mkdirp');
const feathers = require('@feathersjs/feathers');
const Status = require('../src/lib/status');
import Status from '../src/lib/status';
describe('The status module', () => {
beforeEach(() => {
@ -57,7 +58,7 @@ describe('The status module', () => {
describe('collectVersions()', () => {
it('should contain a node version', () => {
const versions = (new Status(app)).collectVersions();
const versions : any = (new Status(app)).collectVersions();
expect(versions.container.tools.node).toBeTruthy();
});
@ -80,7 +81,7 @@ describe('The status module', () => {
});
it('should contain the signature of the plugin', () => {
const versions = (new Status(app)).collectVersions();
const versions : any = (new Status(app)).collectVersions();
expect(versions.jenkins.plugins.git).toBeTruthy();
});
});

View File

@ -3,8 +3,9 @@ jest.mock('fs');
const fs = require('fs');
const mkdirp = require('mkdirp');
const path = require('path');
const h = require('./helpers');
const Storage = require('../src/lib/storage');
import h from '../testlib/helpers';
import Storage from '../src/lib/storage';
describe('The storage module', () => {
let dir = '/tmp';

View File

@ -1,4 +1,5 @@
const UI = require('../src/lib/ui');
import UI from '../src/lib/ui';
describe('The UI module', () => {
it('should be a singleton', () => {

View File

@ -1,17 +1,19 @@
jest.mock('../src/lib/supervisord');
jest.mock('../src/lib/downloader');
const tmp = require('tmp');
const fs = require('fs');
const feathers = require('@feathersjs/feathers');
const h = require('./helpers');
const Update = require('../src/lib/update');
const HealthChecker = require('../src/lib/healthchecker');
const Storage = require('../src/lib/storage');
const Supervisord = require('../src/lib/supervisord');
const Downloader = require('../src/lib/downloader');
const mkdirp = require('mkdirp');
import tmp from 'tmp';
import h from '../testlib/helpers';
import Update from '../src/lib/update';
import HealthChecker from '../src/lib/healthchecker';
import Storage from '../src/lib/storage';
import Supervisord from '../src/lib/supervisord';
import Downloader from '../src/lib/downloader';
describe('The update module', () => {
let app = null;
let update = null;
@ -124,7 +126,7 @@ describe('The update module', () => {
it ('should execute updates if passed in with no deletes', async () => {
jest.setTimeout(10000);
Downloader.mockImplementationOnce(() => {
(Downloader as unknown as jest.Mock).mockImplementationOnce(() => {
return require.requireActual('../src/lib/downloader').default();
});
@ -146,7 +148,7 @@ describe('The update module', () => {
});
it('should execute both updates and deletes if both passed in', async () => {
Downloader.mockImplementationOnce(() => {
(Downloader as unknown as jest.Mock).mockImplementationOnce(() => {
return require.requireActual('../src/lib/downloader').default();
});
manifest.plugins.deletes = ['delete1'];

View File

@ -8,7 +8,7 @@ const open = promisify(fs.open);
const close = promisify(fs.close);
const access = promisify(fs.access);
class Helpers {
export class Helpers {
constructor () {
}
@ -23,4 +23,4 @@ class Helpers {
}
}
module.exports = new Helpers();
export default new Helpers();

View File

@ -5,7 +5,7 @@
"outDir": "./build",
"module" : "commonjs",
"skipLibCheck": true,
"lib" : ["es2017"],
"lib" : ["es2015"],
"module": "commonjs",
"moduleResolution": "node",
"esModuleInterop": true,

View File

@ -12,8 +12,8 @@ fix-formatting: depends
check:: lint
$(MAKE) unit
unit: depends
if [ -z "$${SKIP_TESTS}" ]; then $(NODE) npm run jest; \
unit:: depends
if [ -z "$${SKIP_TESTS}" ]; then $(NODE) npm run test; \
else echo "Tests are skipped!"; fi;
debug-unit: depends