1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-05-08 01:15:49 +02:00

fix: be explicit when specifying time & replace moment with date-fns (#1072)

This commit is contained in:
Martin Lehmann 2021-11-02 15:13:46 +01:00 committed by GitHub
parent 55bec22754
commit b47e228181
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 240 additions and 158 deletions

View File

@ -99,7 +99,6 @@
"log4js": "^6.0.0",
"memoizee": "^0.4.15",
"mime": "^2.4.2",
"moment": "^2.24.0",
"multer": "^1.4.1",
"mustache": "^4.1.0",
"node-fetch": "^2.6.1",

View File

@ -19,6 +19,7 @@ import {
import { getDefaultLogProvider, LogLevel, validateLogProvider } from './logger';
import { defaultCustomAuthDenyAll } from './default-custom-auth-deny-all';
import { formatBaseUri } from './util/format-base-uri';
import { minutesToMilliseconds, secondsToMilliseconds } from 'date-fns';
const safeToUpper = (s: string) => (s ? s.toUpperCase() : s);
@ -94,13 +95,13 @@ const defaultDbOptions: IDBOption = {
: { rejectUnauthorized: false },
driver: 'postgres',
version: process.env.DATABASE_VERSION,
acquireConnectionTimeout: 30000,
acquireConnectionTimeout: secondsToMilliseconds(30),
pool: {
min: safeNumber(process.env.DATABASE_POOL_MIN, 0),
max: safeNumber(process.env.DATABASE_POOL_MAX, 4),
idleTimeoutMillis: safeNumber(
process.env.DATABASE_POOL_IDLE_TIMEOUT_MS,
30000,
secondsToMilliseconds(30),
),
propagateCreateError: false,
},
@ -120,8 +121,8 @@ const defaultServerOption: IServerOption = {
baseUriPath: formatBaseUri(process.env.BASE_URI_PATH),
unleashUrl: process.env.UNLEASH_URL || 'http://localhost:4242',
serverMetrics: true,
keepAliveTimeout: 60 * 1000,
headersTimeout: 61 * 1000,
keepAliveTimeout: minutesToMilliseconds(1),
headersTimeout: secondsToMilliseconds(61),
enableRequestLogger: false,
gracefulShutdownEnable: safeBoolean(
process.env.GRACEFUL_SHUTDOWN_ENABLE,
@ -129,7 +130,7 @@ const defaultServerOption: IServerOption = {
),
gracefulShutdownTimeout: safeNumber(
process.env.GRACEFUL_SHUTDOWN_TIMEOUT,
1000,
secondsToMilliseconds(1),
),
secret: process.env.UNLEASH_SECRET || 'super-secret',
};

View File

@ -1,12 +1,13 @@
import EventEmitter from 'events';
import { Knex } from 'knex';
import { Logger, LogProvider } from '../logger';
import Timeout = NodeJS.Timeout;
import {
IClientInstance,
IClientInstanceStore,
INewClientInstance,
} from '../types/stores/client-instance-store';
import { hoursToMilliseconds } from 'date-fns';
import Timeout = NodeJS.Timeout;
const metricsHelper = require('../util/metrics-helper');
const { DB_TIME } = require('../metric-events');
@ -22,8 +23,6 @@ const COLUMNS = [
];
const TABLE = 'client_instances';
const ONE_DAY = 24 * 61 * 60 * 1000;
const mapRow = (row) => ({
appName: row.app_name,
instanceId: row.instance_id,
@ -65,7 +64,7 @@ export default class ClientInstanceStore implements IClientInstanceStore {
});
const clearer = () => this._removeInstancesOlderThanTwoDays();
setTimeout(clearer, 10).unref();
this.timer = setInterval(clearer, ONE_DAY).unref();
this.timer = setInterval(clearer, hoursToMilliseconds(24)).unref();
}
async _removeInstancesOlderThanTwoDays(): Promise<void> {

View File

@ -1,12 +1,11 @@
import { Knex } from 'knex';
import { Logger, LogProvider } from '../logger';
import { IClientMetric } from '../types/stores/client-metrics-db';
import { minutesToMilliseconds } from 'date-fns';
const METRICS_COLUMNS = ['id', 'created_at', 'metrics'];
const TABLE = 'client_metrics';
const ONE_MINUTE = 60 * 1000;
const mapRow = (row) => ({
id: row.id,
createdAt: row.created_at,
@ -24,7 +23,7 @@ export class ClientMetricsDb {
// Clear old metrics regularly
const clearer = () => this.removeMetricsOlderThanOneHour();
setTimeout(clearer, 10).unref();
this.timer = setInterval(clearer, ONE_MINUTE).unref();
this.timer = setInterval(clearer, minutesToMilliseconds(1)).unref();
}
async removeMetricsOlderThanOneHour(): Promise<void> {

View File

@ -5,8 +5,7 @@ import { DB_TIME } from '../metric-events';
import { ClientMetricsDb } from './client-metrics-db';
import { IClientMetric } from '../types/stores/client-metrics-db';
import { IClientMetricsStore } from '../types/stores/client-metrics-store';
const TEN_SECONDS = 10 * 1000;
import { secondsToMilliseconds } from 'date-fns';
export class ClientMetricsStore
extends EventEmitter
@ -24,7 +23,7 @@ export class ClientMetricsStore
private metricsDb: ClientMetricsDb,
eventBus: EventEmitter,
getLogger: LogProvider,
pollInterval = TEN_SECONDS,
pollInterval = secondsToMilliseconds(10),
) {
super();
this.logger = getLogger('client-metrics-store.ts.js');

View File

@ -3,6 +3,7 @@ import { Knex } from 'knex';
import { Logger, LogProvider } from '../logger';
import NotFoundError from '../error/notfound-error';
import { ISession, ISessionStore } from '../types/stores/session-store';
import { addDays } from 'date-fns';
const TABLE = 'unleash_session';
@ -72,7 +73,7 @@ export default class SessionStore implements ISessionStore {
.insert({
sid: data.sid,
sess: JSON.stringify(data.sess),
expired: data.expired || new Date(Date.now() + 86400000),
expired: data.expired || addDays(Date.now(), 1),
})
.returning<ISessionRow>(['sid', 'sess', 'created_at', 'expired']);
if (row) {

View File

@ -3,19 +3,17 @@ import EventEmitter from 'events';
import { Knex } from 'knex';
import * as events from './metric-events';
import {
FEATURE_CREATED,
FEATURE_UPDATED,
FEATURE_ARCHIVED,
FEATURE_REVIVED,
DB_POOL_UPDATE,
FEATURE_ARCHIVED,
FEATURE_CREATED,
FEATURE_REVIVED,
FEATURE_UPDATED,
} from './types/events';
import { IUnleashConfig } from './types/option';
import { IUnleashStores } from './types/stores';
import { hoursToMilliseconds, minutesToMilliseconds } from 'date-fns';
import Timer = NodeJS.Timer;
const TWO_HOURS = 2 * 60 * 60 * 1000;
const ONE_MINUTE = 60 * 1000;
export default class MetricsMonitor {
timer?: Timer;
@ -111,7 +109,7 @@ export default class MetricsMonitor {
collectStaticCounters();
this.timer = setInterval(
() => collectStaticCounters(),
TWO_HOURS,
hoursToMilliseconds(2),
).unref();
eventBus.on(
@ -199,7 +197,7 @@ export default class MetricsMonitor {
this.registerPoolMetrics(db.client.pool, eventBus);
this.poolMetricsTimer = setInterval(
() => this.registerPoolMetrics(db.client.pool, eventBus),
ONE_MINUTE,
minutesToMilliseconds(1),
);
this.poolMetricsTimer.unref();
}

View File

@ -1,12 +1,13 @@
import helmet from 'helmet';
import { RequestHandler } from 'express';
import { IUnleashConfig } from '../types/option';
import { hoursToSeconds } from 'date-fns';
const secureHeaders: (config: IUnleashConfig) => RequestHandler = (config) => {
if (config.secureHeaders) {
return helmet({
hsts: {
maxAge: 63072000,
maxAge: hoursToSeconds(24 * 365 * 2), // 2 non-leap years
includeSubDomains: true,
preload: true,
},

View File

@ -3,16 +3,16 @@ import session from 'express-session';
import knexSessionStore from 'connect-session-knex';
import { RequestHandler } from 'express';
import { IUnleashConfig } from '../types/option';
import { hoursToMilliseconds } from 'date-fns';
const TWO_DAYS = 48 * 60 * 60 * 1000;
const HOUR = 60 * 60 * 1000;
function sessionDb(
config: Pick<IUnleashConfig, 'session' | 'server' | 'secureHeaders'>,
knex: Knex,
): RequestHandler {
let store;
const { db } = config.session;
const age = config.session.ttlHours * HOUR || TWO_DAYS;
const age =
hoursToMilliseconds(config.session.ttlHours) || hoursToMilliseconds(48);
const KnexSessionStore = knexSessionStore(session);
if (db) {
store = new KnexSessionStore({
@ -41,4 +41,5 @@ function sessionDb(
},
});
}
export default sessionDb;

View File

@ -1,7 +1,7 @@
import * as mime from 'mime';
import YAML from 'js-yaml';
import moment from 'moment';
import multer from 'multer';
import { format as formatDate } from 'date-fns';
import { Request, Response } from 'express';
import Controller from '../controller';
import { ADMIN } from '../../types/permissions';
@ -88,7 +88,7 @@ class StateController extends Controller {
includeTags,
includeEnvironments,
});
const timestamp = moment().format('YYYY-MM-DD_HH-mm-ss');
const timestamp = formatDate(Date.now(), 'yyyy-MM-dd_HH-mm-ss');
if (format === 'yaml') {
if (downloadFile) {
res.attachment(`export-${timestamp}.yml`);

View File

@ -6,6 +6,7 @@ import getApp from '../../app';
import { createServices } from '../../services';
import FeatureController from './feature';
import { createTestConfig } from '../../../test/config/test-config';
import { secondsToMilliseconds } from 'date-fns';
const eventBus = new EventEmitter();
@ -74,7 +75,7 @@ test('if caching is enabled should memoize', async () => {
experimental: {
clientFeatureMemoize: {
enabled: true,
maxAge: 10000,
maxAge: secondsToMilliseconds(10),
},
},
},
@ -100,7 +101,7 @@ test('if caching is not enabled all calls goes to service', async () => {
experimental: {
clientFeatureMemoize: {
enabled: false,
maxAge: 10000,
maxAge: secondsToMilliseconds(10),
},
},
},

View File

@ -12,11 +12,10 @@ import { IAddon, IAddonDto, IAddonStore } from '../types/stores/addon-store';
import { IUnleashStores } from '../types/stores';
import { IUnleashConfig } from '../types/option';
import { IAddonDefinition } from '../types/model';
import { minutesToMilliseconds } from 'date-fns';
const SUPPORTED_EVENTS = Object.keys(events).map((k) => events[k]);
const ADDONS_CACHE_TIME = 60 * 1000; // 60s
const MASKED_VALUE = '*****';
interface ISensitiveParams {
@ -75,7 +74,7 @@ export default class AddonService {
async () => addonStore.getAll({ enabled: true }),
{
promise: true,
maxAge: ADDONS_CACHE_TIME,
maxAge: minutesToMilliseconds(1),
},
);
}

View File

@ -13,8 +13,7 @@ import {
import { IApiTokenStore } from '../types/stores/api-token-store';
import { FOREIGN_KEY_VIOLATION } from '../error/db-error';
import BadDataError from '../error/bad-data-error';
const ONE_MINUTE = 60_000;
import { minutesToMilliseconds } from 'date-fns';
export class ApiTokenService {
private store: IApiTokenStore;
@ -34,7 +33,7 @@ export class ApiTokenService {
this.fetchActiveTokens();
this.timer = setInterval(
() => this.fetchActiveTokens(),
ONE_MINUTE,
minutesToMilliseconds(1),
).unref();
}

View File

@ -8,9 +8,7 @@ import {
IClientMetricsStoreV2,
} from '../../types/stores/client-metrics-store-v2';
import { clientMetricsSchema } from './client-metrics-schema';
const FIVE_MINUTES = 5 * 60 * 1000;
const ONE_DAY = 24 * 60 * 60 * 1000;
import { hoursToMilliseconds, minutesToMilliseconds } from 'date-fns';
export default class ClientMetricsServiceV2 {
private timer: NodeJS.Timeout;
@ -24,7 +22,7 @@ export default class ClientMetricsServiceV2 {
constructor(
{ clientMetricsStoreV2 }: Pick<IUnleashStores, 'clientMetricsStoreV2'>,
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>,
bulkInterval = FIVE_MINUTES,
bulkInterval = minutesToMilliseconds(5),
) {
this.clientMetricsStoreV2 = clientMetricsStoreV2;
@ -33,7 +31,7 @@ export default class ClientMetricsServiceV2 {
this.bulkInterval = bulkInterval;
this.timer = setInterval(() => {
this.clientMetricsStoreV2.clearMetrics(48);
}, ONE_DAY);
}, hoursToMilliseconds(24));
this.timer.unref();
}

View File

@ -1,8 +1,51 @@
import EventEmitter from 'events';
import moment from 'moment';
import ClientMetricsService from './index';
import getLogger from '../../../test/fixtures/no-logger';
import { IClientApp } from '../../types/model';
import {
addHours,
addMinutes,
hoursToMilliseconds,
minutesToMilliseconds,
secondsToMilliseconds,
subHours,
subMinutes,
subSeconds,
} from 'date-fns';
/**
* A utility to wait for any pending promises in the test subject code.
* For instance, if the test needs to wait for a timeout/interval handler,
* and that handler does something async, advancing the timers is not enough:
* We have to explicitly wait for the second promise.
* For more info, see https://stackoverflow.com/a/51045733/2868829
*
* Usage in test code after advancing timers, but before making assertions:
*
* test('hello', async () => {
* jest.useFakeTimers('modern');
*
* // Schedule a timeout with a callback that does something async
* // before calling our spy
* const spy = jest.fn();
* setTimeout(async () => {
* await Promise.resolve();
* spy();
* }, 1000);
*
* expect(spy).not.toHaveBeenCalled();
*
* jest.advanceTimersByTime(1500);
* await flushPromises(); // this is required to make it work!
*
* expect(spy).toHaveBeenCalledTimes(1);
*
* jest.useRealTimers();
* });
*/
function flushPromises() {
return Promise.resolve(setImmediate);
}
const appName = 'appName';
const instanceId = 'instanceId';
@ -40,8 +83,8 @@ test('data should expire', () => {
appName,
instanceId,
bucket: {
start: Date.now() - 2000,
stop: Date.now() - 1000,
start: subSeconds(Date.now(), 2),
stop: subSeconds(Date.now(), 1),
toggles: {
toggleX: {
yes: 123,
@ -61,11 +104,11 @@ test('data should expire', () => {
lastMinExpires++;
});
jest.advanceTimersByTime(60 * 1000);
jest.advanceTimersByTime(minutesToMilliseconds(1));
expect(lastMinExpires).toBe(1);
expect(lastHourExpires).toBe(0);
jest.advanceTimersByTime(60 * 60 * 1000);
jest.advanceTimersByTime(hoursToMilliseconds(1));
expect(lastMinExpires).toBe(1);
expect(lastHourExpires).toBe(1);
@ -201,36 +244,36 @@ test('should have correct values for lastMinute', () => {
const now = new Date();
const input = [
{
start: moment(now).subtract(1, 'hour'),
stop: moment(now).subtract(59, 'minutes'),
start: subHours(now, 1),
stop: subMinutes(now, 59),
toggles: {
toggle: { yes: 10, no: 10 },
},
},
{
start: moment(now).subtract(30, 'minutes'),
stop: moment(now).subtract(29, 'minutes'),
start: subMinutes(now, 30),
stop: subMinutes(now, 29),
toggles: {
toggle: { yes: 10, no: 10 },
},
},
{
start: moment(now).subtract(2, 'minutes'),
stop: moment(now).subtract(1, 'minutes'),
start: subMinutes(now, 2),
stop: subMinutes(now, 1),
toggles: {
toggle: { yes: 10, no: 10 },
},
},
{
start: moment(now).subtract(2, 'minutes'),
stop: moment(now).subtract(59, 'seconds'),
start: subMinutes(now, 2),
stop: subSeconds(now, 59),
toggles: {
toggle: { yes: 10, no: 10 },
},
},
{
start: moment(now),
stop: moment(now).subtract(30, 'seconds'),
start: now,
stop: subSeconds(now, 30),
toggles: {
toggle: { yes: 10, no: 10 },
},
@ -252,11 +295,11 @@ test('should have correct values for lastMinute', () => {
let c = metrics.getTogglesMetrics();
expect(c.lastMinute.toggle).toEqual({ yes: 20, no: 20 });
jest.advanceTimersByTime(10 * 1000);
jest.advanceTimersByTime(10_000);
c = metrics.getTogglesMetrics();
expect(c.lastMinute.toggle).toEqual({ yes: 10, no: 10 });
jest.advanceTimersByTime(20 * 1000);
jest.advanceTimersByTime(20_000);
c = metrics.getTogglesMetrics();
expect(c.lastMinute.toggle).toEqual({ yes: 0, no: 0 });
@ -270,32 +313,32 @@ test('should have correct values for lastHour', () => {
const clientMetricsStore = new EventEmitter();
const metrics = createMetricsService(clientMetricsStore);
const now = new Date();
const now = Date.now();
const input = [
{
start: moment(now).subtract(1, 'hour'),
stop: moment(now).subtract(59, 'minutes'),
start: subHours(now, 1),
stop: subMinutes(now, 59),
toggles: {
toggle: { yes: 10, no: 10 },
},
},
{
start: moment(now).subtract(30, 'minutes'),
stop: moment(now).subtract(29, 'minutes'),
start: subMinutes(now, 30),
stop: subMinutes(now, 29),
toggles: {
toggle: { yes: 10, no: 10 },
},
},
{
start: moment(now).subtract(15, 'minutes'),
stop: moment(now).subtract(14, 'minutes'),
start: subMinutes(now, 15),
stop: subMinutes(now, 14),
toggles: {
toggle: { yes: 10, no: 10 },
},
},
{
start: moment(now).add(59, 'minutes'),
stop: moment(now).add(1, 'hour'),
start: addMinutes(now, 59),
stop: addHours(now, 1),
toggles: {
toggle: { yes: 11, no: 11 },
},
@ -318,27 +361,27 @@ test('should have correct values for lastHour', () => {
let c = metrics.getTogglesMetrics();
expect(c.lastHour.toggle).toEqual({ yes: 41, no: 41 });
jest.advanceTimersByTime(10 * 1000);
jest.advanceTimersByTime(10_000);
c = metrics.getTogglesMetrics();
expect(c.lastHour.toggle).toEqual({ yes: 41, no: 41 });
// at 30
jest.advanceTimersByTime(30 * 60 * 1000);
jest.advanceTimersByTime(minutesToMilliseconds(30));
c = metrics.getTogglesMetrics();
expect(c.lastHour.toggle).toEqual({ yes: 31, no: 31 });
// at 45
jest.advanceTimersByTime(15 * 60 * 1000);
jest.advanceTimersByTime(minutesToMilliseconds(15));
c = metrics.getTogglesMetrics();
expect(c.lastHour.toggle).toEqual({ yes: 21, no: 21 });
// at 1:15
jest.advanceTimersByTime(30 * 60 * 1000);
jest.advanceTimersByTime(minutesToMilliseconds(30));
c = metrics.getTogglesMetrics();
expect(c.lastHour.toggle).toEqual({ yes: 11, no: 11 });
// at 2:00
jest.advanceTimersByTime(45 * 60 * 1000);
jest.advanceTimersByTime(minutesToMilliseconds(45));
c = metrics.getTogglesMetrics();
expect(c.lastHour.toggle).toEqual({ yes: 0, no: 0 });
@ -421,7 +464,8 @@ test('Multiple registrations of same appname and instanceid within same time per
await clientMetrics.registerClient(client1, '127.0.0.1');
await clientMetrics.registerClient(client1, '127.0.0.1');
await clientMetrics.registerClient(client1, '127.0.0.1');
await jest.advanceTimersByTime(7 * 1000);
jest.advanceTimersByTime(7000);
await flushPromises();
expect(appStoreSpy).toHaveBeenCalledTimes(1);
expect(bulkSpy).toHaveBeenCalledTimes(1);
@ -448,6 +492,7 @@ test('Multiple unique clients causes multiple registrations', async () => {
const clientInstanceStore: any = {
bulkUpsert: bulkSpy,
};
const clientMetrics = new ClientMetricsService(
{
clientMetricsStore,
@ -479,10 +524,9 @@ test('Multiple unique clients causes multiple registrations', async () => {
await clientMetrics.registerClient(client2, '127.0.0.1');
await clientMetrics.registerClient(client2, '127.0.0.1');
await clientMetrics.registerClient(client2, '127.0.0.1');
await jest.advanceTimersByTime(7 * 1000);
expect(appStoreSpy).toHaveBeenCalledTimes(1);
expect(bulkSpy).toHaveBeenCalledTimes(1);
jest.advanceTimersByTime(7000);
await flushPromises();
const registrations = appStoreSpy.mock.calls[0][0];
@ -501,7 +545,7 @@ test('Same client registered outside of dedup interval will be registered twice'
bulkUpsert: bulkSpy,
};
const bulkInterval = 2000;
const bulkInterval = secondsToMilliseconds(2);
const clientMetrics = new ClientMetricsService(
{
@ -525,11 +569,16 @@ test('Same client registered outside of dedup interval will be registered twice'
await clientMetrics.registerClient(client1, '127.0.0.1');
await clientMetrics.registerClient(client1, '127.0.0.1');
await clientMetrics.registerClient(client1, '127.0.0.1');
await jest.advanceTimersByTime(3 * 1000);
jest.advanceTimersByTime(3000);
await clientMetrics.registerClient(client1, '127.0.0.1');
await clientMetrics.registerClient(client1, '127.0.0.1');
await clientMetrics.registerClient(client1, '127.0.0.1');
await jest.advanceTimersByTime(3 * 1000);
jest.advanceTimersByTime(3000);
await flushPromises();
expect(appStoreSpy).toHaveBeenCalledTimes(2);
expect(bulkSpy).toHaveBeenCalledTimes(2);
@ -552,8 +601,7 @@ test('No registrations during a time period will not call stores', async () => {
const clientInstanceStore: any = {
bulkUpsert: bulkSpy,
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const clientMetrics = new ClientMetricsService(
new ClientMetricsService(
{
clientMetricsStore,
strategyStore: null,
@ -564,7 +612,7 @@ test('No registrations during a time period will not call stores', async () => {
},
{ getLogger },
);
await jest.advanceTimersByTime(6 * 1000);
jest.advanceTimersByTime(6000);
expect(appStoreSpy).toHaveBeenCalledTimes(0);
expect(bulkSpy).toHaveBeenCalledTimes(0);
jest.useRealTimers();

View File

@ -21,12 +21,9 @@ import {
IMetricCounts,
IMetricsBucket,
} from '../../types/model';
import TTLList = require('./ttl-list');
import { clientRegisterSchema } from './register-schema';
const FIVE_SECONDS = 5 * 1000;
const FIVE_MINUTES = 5 * 60 * 1000;
import { minutesToMilliseconds, secondsToMilliseconds } from 'date-fns';
import TTLList = require('./ttl-list');
export default class ClientMetricsService {
globalCount = 0;
@ -38,13 +35,13 @@ export default class ClientMetricsService {
lastMinuteProjection = new Projection();
lastHourList = new TTLList({
interval: 10000,
interval: secondsToMilliseconds(10),
});
logger = null;
lastMinuteList = new TTLList({
interval: 10000,
interval: secondsToMilliseconds(10),
expireType: 'minutes',
expireAmount: 1,
});
@ -87,8 +84,8 @@ export default class ClientMetricsService {
| 'eventStore'
>,
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>,
bulkInterval = FIVE_SECONDS,
announcementInterval = FIVE_MINUTES,
bulkInterval = secondsToMilliseconds(5),
announcementInterval = minutesToMilliseconds(5),
) {
this.clientMetricsStore = clientMetricsStore;
this.strategyStore = strategyStore;

View File

@ -1,13 +1,18 @@
'use strict';
const { EventEmitter } = require('events');
const moment = require('moment');
const List = require('./list');
const {
add,
isFuture,
addMilliseconds,
secondsToMilliseconds,
} = require('date-fns');
// this list must have entries with sorted ttl range
module.exports = class TTLList extends EventEmitter {
constructor({
interval = 1000,
interval = secondsToMilliseconds(1),
expireAmount = 1,
expireType = 'hours',
} = {}) {
@ -16,6 +21,14 @@ module.exports = class TTLList extends EventEmitter {
this.expireAmount = expireAmount;
this.expireType = expireType;
this.getExpiryFrom = (timestamp) => {
if (this.expireType === 'milliseconds') {
return addMilliseconds(timestamp, expireAmount);
} else {
return add(timestamp, { [expireType]: expireAmount });
}
};
this.list = new List();
this.list.on('evicted', ({ value, ttl }) => {
@ -36,8 +49,8 @@ module.exports = class TTLList extends EventEmitter {
}
add(value, timestamp = new Date()) {
const ttl = moment(timestamp).add(this.expireAmount, this.expireType);
if (moment().isBefore(ttl)) {
const ttl = this.getExpiryFrom(timestamp);
if (isFuture(ttl)) {
this.list.add({ ttl, value });
} else {
this.emit('expire', value, ttl);
@ -45,17 +58,13 @@ module.exports = class TTLList extends EventEmitter {
}
timedCheck() {
const now = moment();
this.list.reverseRemoveUntilTrue(({ value }) =>
now.isBefore(value.ttl),
);
this.list.reverseRemoveUntilTrue(({ value }) => isFuture(value.ttl));
this.startTimer();
}
destroy() {
// https://github.com/nodejs/node/issues/9561
// clearTimeout(this.timer);
// this.timer = null;
clearTimeout(this.timer);
this.timer = null;
this.list = null;
}
};

View File

@ -1,7 +1,7 @@
'use strict';
const moment = require('moment');
const TTLList = require('./ttl-list');
const { addMilliseconds } = require('date-fns');
test('should emit expire', (done) => {
jest.useFakeTimers('modern');
@ -31,10 +31,10 @@ test('should slice off list', () => {
expireType: 'milliseconds',
});
list.add({ n: '1' }, moment().add(1, 'milliseconds'));
list.add({ n: '2' }, moment().add(50, 'milliseconds'));
list.add({ n: '3' }, moment().add(200, 'milliseconds'));
list.add({ n: '4' }, moment().add(300, 'milliseconds'));
list.add({ n: '1' }, addMilliseconds(Date.now(), 1));
list.add({ n: '2' }, addMilliseconds(Date.now(), 50));
list.add({ n: '3' }, addMilliseconds(Date.now(), 200));
list.add({ n: '4' }, addMilliseconds(Date.now(), 300));
const expired = [];
@ -43,18 +43,45 @@ test('should slice off list', () => {
expired.push(entry);
});
expect(expired).toHaveLength(0);
expect(list.list.toArray()).toHaveLength(4);
jest.advanceTimersByTime(21);
expect(expired).toHaveLength(1);
expect(list.list.toArray()).toHaveLength(3);
jest.advanceTimersByTime(51);
expect(expired).toHaveLength(2);
expect(list.list.toArray()).toHaveLength(2);
jest.advanceTimersByTime(201);
expect(expired).toHaveLength(3);
expect(list.list.toArray()).toHaveLength(1);
jest.advanceTimersByTime(301);
expect(expired).toHaveLength(4);
expect(list.list.toArray()).toHaveLength(0);
list.destroy();
jest.useRealTimers();
});
test('should add item created in the past but expiring in the future', () => {
jest.useFakeTimers('modern');
const list = new TTLList({
interval: 10,
expireAmount: 10,
expireType: 'milliseconds',
});
const expireCallback = jest.fn();
list.on('expire', expireCallback);
list.add({ n: '1' }, new Date());
expect(expireCallback).not.toHaveBeenCalled();
expect(list.list.toArray()).toHaveLength(1);
jest.useRealTimers();
});

View File

@ -8,15 +8,12 @@ import {
IProjectHealthReport,
IProjectOverview,
} from '../types/model';
import {
MILLISECONDS_IN_DAY,
MILLISECONDS_IN_ONE_HOUR,
} from '../util/constants';
import { IFeatureToggleStore } from '../types/stores/feature-toggle-store';
import { IFeatureTypeStore } from '../types/stores/feature-type-store';
import { IProjectStore } from '../types/stores/project-store';
import Timer = NodeJS.Timer;
import FeatureToggleServiceV2 from './feature-toggle-service-v2';
import { hoursToMilliseconds } from 'date-fns';
import Timer = NodeJS.Timer;
export default class ProjectHealthService {
private logger: Logger;
@ -52,7 +49,7 @@ export default class ProjectHealthService {
this.featureTypes = new Map();
this.healthRatingTimer = setInterval(
() => this.setHealthRating(),
MILLISECONDS_IN_ONE_HOUR,
hoursToMilliseconds(1),
).unref();
this.featureToggleService = featureToggleService;
}
@ -116,7 +113,7 @@ export default class ProjectHealthService {
);
return (
!feature.stale &&
diff >= featureTypeExpectedLifetime * MILLISECONDS_IN_DAY
diff >= featureTypeExpectedLifetime * hoursToMilliseconds(24)
);
}).length;
}

View File

@ -11,8 +11,7 @@ import {
IResetToken,
IResetTokenStore,
} from '../types/stores/reset-token-store';
const ONE_DAY = 86_400_000;
import { hoursToMilliseconds } from 'date-fns';
interface IInviteLinks {
[key: string]: string;
@ -106,7 +105,7 @@ export default class ResetTokenService {
async createToken(
tokenUser: number,
creator: string,
expiryDelta: number = ONE_DAY,
expiryDelta: number = hoursToMilliseconds(24),
): Promise<IResetToken> {
const token = await this.generateToken();
const expiry = new Date(Date.now() + expiryDelta);

View File

@ -4,8 +4,7 @@ import { IUnleashConfig } from '../types/option';
import version from '../util/version';
import { Logger } from '../logger';
import { ISettingStore } from '../types/stores/settings-store';
const TWO_DAYS = 48 * 60 * 60 * 1000;
import { hoursToMilliseconds } from 'date-fns';
export interface IVersionInfo {
oss: string;
@ -66,7 +65,7 @@ export default class VersionService {
await this.checkLatestVersion();
this.timer = setInterval(
async () => this.checkLatestVersion(),
TWO_DAYS,
hoursToMilliseconds(48),
);
this.timer.unref();
}

View File

@ -1,3 +1 @@
export const MILLISECONDS_IN_DAY = 86400000;
export const MILLISECONDS_IN_ONE_HOUR = 3600000;
export const DEFAULT_ENV = 'default';

View File

@ -1,11 +1,15 @@
import { log } from 'db-migrate-shared';
import { getInstance } from 'db-migrate';
import { IUnleashConfig } from './lib/types/option';
import { secondsToMilliseconds } from 'date-fns';
log.setLogLevel('error');
export async function migrateDb({ db }: IUnleashConfig): Promise<void> {
const custom = { ...db, connectionTimeoutMillis: 10000 };
const custom = {
...db,
connectionTimeoutMillis: secondsToMilliseconds(10),
};
const dbm = getInstance(true, {
cwd: __dirname,
@ -18,7 +22,10 @@ export async function migrateDb({ db }: IUnleashConfig): Promise<void> {
// This exists to ease testing
export async function resetDb({ db }: IUnleashConfig): Promise<void> {
const custom = { ...db, connectionTimeoutMillis: 10000 };
const custom = {
...db,
connectionTimeoutMillis: secondsToMilliseconds(10),
};
const dbm = getInstance(true, {
cwd: __dirname,

View File

@ -1,6 +1,7 @@
import dbInit from '../../helpers/database-init';
import { setupApp } from '../../helpers/test-helper';
import getLogger from '../../../fixtures/no-logger';
import { parseISO } from 'date-fns';
let app;
let db;
@ -28,25 +29,27 @@ beforeEach(async () => {
description: 'Some desc',
announced: true,
});
const clientStartedDate = parseISO('2018-01-15T14:35:38.494Z');
await db.stores.clientInstanceStore.insert({
appName: 'demo-app-1',
instanceId: 'test-1',
strategies: ['default'],
started: 1516026938494,
started: clientStartedDate,
interval: 10,
});
await db.stores.clientInstanceStore.insert({
appName: 'demo-seed-2',
instanceId: 'test-2',
strategies: ['default'],
started: 1516026938494,
started: clientStartedDate,
interval: 10,
});
await db.stores.clientInstanceStore.insert({
appName: 'deletable-app',
instanceId: 'inst-1',
strategies: ['default'],
started: 1516026938494,
started: clientStartedDate,
interval: 10,
});
await app.services.clientMetricsService.addPayload({

View File

@ -60,7 +60,7 @@ test('should allow client to register multiple times', async () => {
.send(clientRegistration)
.expect(202);
jest.advanceTimersByTime(6 * 1000);
jest.advanceTimersByTime(6000);
expect(clientApplicationsStore.exists(clientRegistration)).toBeTruthy();
expect(clientInstanceStore.exists(clientRegistration)).toBeTruthy();
jest.useRealTimers();

View File

@ -78,7 +78,7 @@ test('should only return active addons', async () => {
await addonService.createAddon(config2, 'me@mail.com');
await addonService.createAddon(config3, 'me@mail.com');
jest.advanceTimersByTime(61 * 1000);
jest.advanceTimersByTime(61_000);
const activeAddons = await addonService.fetchAddonConfigs();
const allAddons = await addonService.getAddons();

View File

@ -4,6 +4,7 @@ import { ApiTokenService } from '../../../lib/services/api-token-service';
import { createTestConfig } from '../../config/test-config';
import { ApiTokenType, IApiToken } from '../../../lib/types/models/api-token';
import { DEFAULT_ENV } from '../../../lib/util/constants';
import { addDays, subDays } from 'date-fns';
let db;
let stores;
@ -102,13 +103,14 @@ test('should update expiry of token', async () => {
});
test('should only return valid tokens', async () => {
const today = new Date();
const tomorrow = new Date(today.getTime() + 24 * 60 * 60 * 1000);
const now = Date.now();
const yesterday = subDays(now, 1);
const tomorrow = addDays(now, 1);
await apiTokenService.createApiToken({
username: 'default-expired',
type: ApiTokenType.CLIENT,
expiresAt: new Date('2021-01-01'),
expiresAt: yesterday,
project: '*',
environment: DEFAULT_ENV,
});

View File

@ -1,5 +1,6 @@
import ClientMetricsService from '../../../lib/services/client-metrics';
import { IClientApp } from '../../../lib/types/model';
import { secondsToMilliseconds } from 'date-fns';
const faker = require('faker');
const dbInit = require('../helpers/database-init');
@ -13,11 +14,15 @@ let clientMetricsService;
beforeAll(async () => {
db = await dbInit('client_metrics_service_serial', getLogger);
stores = db.stores;
const bulkInterval = secondsToMilliseconds(0.5);
const announcementInterval = secondsToMilliseconds(2);
clientMetricsService = new ClientMetricsService(
stores,
{ getLogger },
500,
2000,
bulkInterval,
announcementInterval,
);
});
@ -53,7 +58,7 @@ test('Apps registered should be announced', async () => {
const first = await stores.clientApplicationsStore.getUnannounced();
expect(first.length).toBe(2);
await clientMetricsService.registerClient(clientRegistration, '127.0.0.1');
await new Promise((res) => setTimeout(res, 2000));
await new Promise((res) => setTimeout(res, secondsToMilliseconds(2)));
const second = await stores.clientApplicationsStore.getUnannounced();
expect(second.length).toBe(0);
const events = await stores.eventStore.getEvents();

View File

@ -2,6 +2,7 @@ import noLoggerProvider from '../../fixtures/no-logger';
import dbInit from '../helpers/database-init';
import SessionService from '../../../lib/services/session-service';
import NotFoundError from '../../../lib/error/notfound-error';
import { addDays, minutesToMilliseconds } from 'date-fns';
let stores;
let db;
@ -10,8 +11,8 @@ const newSession = {
sid: 'abc123',
sess: {
cookie: {
originalMaxAge: 2880000,
expires: new Date(Date.now() + 86_400_000).toDateString(),
originalMaxAge: minutesToMilliseconds(48),
expires: addDays(Date.now(), 1).toDateString(),
secure: false,
httpOnly: true,
path: '/',
@ -31,8 +32,8 @@ const otherSession = {
sid: 'xyz321',
sess: {
cookie: {
originalMaxAge: 2880000,
expires: new Date(Date.now() + 86400000).toDateString(),
originalMaxAge: minutesToMilliseconds(48),
expires: addDays(Date.now(), 1).toDateString(),
secure: false,
httpOnly: true,
path: '/',

View File

@ -12,6 +12,7 @@ import { IRole } from '../../../lib/types/stores/access-store';
import { RoleName } from '../../../lib/types/model';
import SettingService from '../../../lib/services/setting-service';
import { simpleAuthKey } from '../../../lib/types/settings/simple-auth-settings';
import { addDays, minutesToMilliseconds } from 'date-fns';
let db;
let stores;
@ -161,8 +162,8 @@ test("deleting a user should delete the user's sessions", async () => {
sid: 'xyz321',
sess: {
cookie: {
originalMaxAge: 2880000,
expires: new Date(Date.now() + 86400000).toDateString(),
originalMaxAge: minutesToMilliseconds(48),
expires: addDays(Date.now(), 1).toDateString(),
secure: false,
httpOnly: true,
path: '/',

View File

@ -1,4 +1,4 @@
import { subDays } from 'date-fns';
import { addHours, set, subDays } from 'date-fns';
import dbInit from '../helpers/database-init';
import getLogger from '../../fixtures/no-logger';
import { IUnleashStores } from '../../../lib/types';
@ -66,10 +66,9 @@ test('Should "increment" metrics within same hour', async () => {
});
test('Should get individual metrics outside same hour', async () => {
const d1 = new Date();
const d2 = new Date();
d1.setHours(10, 10, 11);
d2.setHours(11, 10, 11);
const d1 = set(Date.now(), { hours: 10, minutes: 10, seconds: 11 });
const d2 = addHours(d1, 1);
const metrics: IClientMetricsEnv[] = [
{
featureName: 'demo',
@ -265,7 +264,7 @@ test('Should not fail on undefined list of metrics', async () => {
});
test('Should return delete old metric', async () => {
const twoDaysAgo = subDays(new Date(), 2);
const twoDaysAgo = subDays(Date.now(), 2);
const metrics: IClientMetricsEnv[] = [
{
@ -311,7 +310,7 @@ test('Should return delete old metric', async () => {
});
test('Should get metric', async () => {
const twoDaysAgo = subDays(new Date(), 2);
const twoDaysAgo = subDays(Date.now(), 2);
const metrics: IClientMetricsEnv[] = [
{

View File

@ -102,7 +102,7 @@ test('Should be able to store multiple events at once', async () => {
const seen = [];
eventStore.on(APPLICATION_CREATED, (e) => seen.push(e));
await eventStore.batchStore([event1, event2, event3]);
await jest.advanceTimersByTime(100);
jest.advanceTimersByTime(100);
expect(seen.length).toBe(3);
seen.forEach((e) => {
expect(e.id).toBeTruthy();

View File

@ -4996,11 +4996,6 @@ module-not-found-error@^1.0.1:
resolved "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz"
integrity sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=
moment@^2.24.0:
version "2.29.1"
resolved "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz"
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
"mongodb-uri@>= 0.9.7":
version "0.9.7"
resolved "https://registry.npmjs.org/mongodb-uri/-/mongodb-uri-0.9.7.tgz"