Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/check-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '16'
node-version: '24'

- name: Install dependencies
run: yarn install
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 16.x
- name: Use Node.js 24.x
uses: actions/setup-node@v1
with:
node-version: 16.x
node-version: 24.x
- name: Cache dependencies
uses: actions/cache@v3
with:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ jobs:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
- name: Use Node.js 16.x
- name: Use Node.js 24.x
uses: actions/setup-node@v4
with:
node-version: 16.x
node-version: 24.x
- run: yarn install
- run: yarn test
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v16.20.2
v24
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:18.20-slim as build-stage
FROM node:24-slim as build-stage

RUN apt update
RUN apt install git -y
Expand Down
2 changes: 1 addition & 1 deletion dev.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:16.20-slim
FROM node:24-slim

WORKDIR /usr/src/app

Expand Down
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@ module.exports = {
setupFilesAfterEnv: ['./jest.setup.redis-mock.js', './jest.setup.mongo-repl-set.js'],

globalTeardown: './jest.global-teardown.js',
testTimeout: 15000,
};
12 changes: 10 additions & 2 deletions jest.global-teardown.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
const process = require('process');
const mongoTeardown = require('@shelf/jest-mongodb/teardown');

module.exports = async () => {
/**
* Cleanup MongoDB Memory Server
*
* @shelf/jest-mongodb should handle this automatically, but we try to ensure cleanup

Check warning on line 8 in jest.global-teardown.js

View workflow job for this annotation

GitHub Actions / ESlint

Invalid JSDoc tag name "shelf/jest-mongodb"
*/
await mongoTeardown();

module.exports = () => {
if (process.env.CI) {
setTimeout(() => {
process.exit(0);
}, 1000);
}
};
};
6 changes: 3 additions & 3 deletions jest.setup.mongo-repl-set.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ beforeAll(async () => {
let status = await admin.command({ replSetGetStatus: 1 }).catch(() => null);

if (status && status.ok) {
console.log('✅ Replica set already initialized');
// console.log('✅ Replica set already initialized');
} else {
await admin.command({ replSetInitiate: {} });
console.log('✅ Replica set initiated');
// console.log('✅ Replica set initiated');
}

const startTime = Date.now();
Expand All @@ -39,7 +39,7 @@ beforeAll(async () => {
await new Promise(resolve => setTimeout(resolve, 1000));
} while (Date.now() - startTime < timeout);

console.log('✅ Replica set is stable');
// console.log('✅ Replica set is stable');
} catch (err) {
console.error('❌ Failed to initiate replica set:', err);
}
Expand Down
2 changes: 1 addition & 1 deletion jest.setup.redis-mock.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ beforeAll(async () => {
.start();

const port = redisTestContainer.getMappedPort(6379);
const host = redisTestContainer.getContainerIpAddress();
const host = redisTestContainer.getHost();

/**
* Set environment variable for redisHelper to connect to redis container
Expand Down
114 changes: 95 additions & 19 deletions lib/memoize/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,16 @@ describe('memoize decorator — per-test inline classes', () => {
});

it('should memoize return value with concat strategy across several calls', async () => {
/**
*
*/
class Sample {
public calls = 0;

/**
* @param a
* @param b
*/
@memoize({ strategy: 'concat', ttl: 60_000, max: 50 })
public async run(a: number, b: string) {
this.calls += 1;
Expand All @@ -47,9 +54,16 @@ describe('memoize decorator — per-test inline classes', () => {
});

it('should memoize return value with set of arguments with concat strategy across several calls', async () => {
/**
*
*/
class Sample {
public calls = 0;

/**
* @param a
* @param b
*/
@memoize({ strategy: 'concat' })
public async run(a: unknown, b: unknown) {
this.calls += 1;
Expand Down Expand Up @@ -84,9 +98,16 @@ describe('memoize decorator — per-test inline classes', () => {
});

it('should memoize return value for stringified objects across several calls', async () => {
/**
*
*/
class Sample {
public calls = 0;

/**
* @param x
* @param y
*/
@memoize({ strategy: 'concat' })
public async run(x: unknown, y: unknown) {
this.calls += 1;
Expand All @@ -105,9 +126,15 @@ describe('memoize decorator — per-test inline classes', () => {
});

it('should memoize return value for method with non-default arguments (NaN, Infinity, -0, Symbol, Date, RegExp) still cache same-args', async () => {
/**
*
*/
class Sample {
public calls = 0;

/**
* @param {...any} args
*/
@memoize({ strategy: 'concat' })
public async run(...args: unknown[]) {
this.calls += 1;
Expand All @@ -131,9 +158,15 @@ describe('memoize decorator — per-test inline classes', () => {
it('should call crypto hash with blake2b512 algo and base64url digest, should memoize return value with hash strategy', async () => {
const hashSpy = jest.spyOn(Crypto, 'hash');

/**
*
*/
class Sample {
public calls = 0;

/**
* @param {...any} args
*/
@memoize({ strategy: 'hash' })
public async run(...args: unknown[]) {
this.calls += 1;
Expand All @@ -151,9 +184,15 @@ describe('memoize decorator — per-test inline classes', () => {
});

it('should not memoize return value with hash strategy and different arguments', async () => {
/**
*
*/
class Sample {
public calls = 0;

/**
* @param {...any} args
*/
@memoize({ strategy: 'hash' })
public async run(...args: unknown[]) {
this.calls += 1;
Expand All @@ -171,9 +210,15 @@ describe('memoize decorator — per-test inline classes', () => {
});

it('should memoize return value with hash strategy across several calls with same args', async () => {
/**
*
*/
class Sample {
public calls = 0;

/**
* @param arg
*/
@memoize({ strategy: 'hash' })
public async run(arg: unknown) {
this.calls += 1;
Expand All @@ -196,9 +241,15 @@ describe('memoize decorator — per-test inline classes', () => {

const { memoize: memoizeWithMockedTimers } = await import('../memoize/index');

/**
*
*/
class Sample {
public calls = 0;

/**
* @param x
*/
@memoizeWithMockedTimers({ strategy: 'concat', ttl: 1_000 })
public async run(x: string) {
this.calls += 1;
Expand All @@ -221,9 +272,15 @@ describe('memoize decorator — per-test inline classes', () => {
});

it('error calls should never be momized', async () => {
/**
*
*/
class Sample {
public calls = 0;

/**
* @param x
*/
@memoize()
public async run(x: number) {
this.calls += 1;
Expand All @@ -245,9 +302,15 @@ describe('memoize decorator — per-test inline classes', () => {
});

it('should NOT cache results listed in skipCache (primitives)', async () => {
/**
*
*/
class Sample {
public calls = 0;


/**
* @param kind
*/
@memoize({ strategy: 'concat', skipCache: [null, undefined, 0, false, ''] })
public async run(kind: 'null' | 'undef' | 'zero' | 'false' | 'empty') {
this.calls += 1;
Expand All @@ -260,68 +323,81 @@ describe('memoize decorator — per-test inline classes', () => {
}
}
}

const sample = new Sample();

// Each repeated call should invoke the original again because result is in skipCache.
await sample.run('null');
await sample.run('null');

await sample.run('undef');
await sample.run('undef');

await sample.run('zero');
await sample.run('zero');

await sample.run('false');
await sample.run('false');

await sample.run('empty');
await sample.run('empty');

// 5 kinds × 2 calls each = 10 calls, none cached
expect(sample.calls).toBe(10);
});

it('should cache results NOT listed in skipCache', async () => {
/**
*
*/
class Sample {
public calls = 0;


/**
* @param x
*/
@memoize({ strategy: 'concat', skipCache: [null, undefined] })
public async run(x: number) {
this.calls += 1;

// returns a non-skipped primitive
return x * 2;
}
}

const sample = new Sample();

expect(await sample.run(21)).toBe(42);
expect(await sample.run(21)).toBe(42);

expect(sample.calls).toBe(1);
});

it('should use equality for skipCache with objects: deep equal objects are cached', async () => {
const deepEqualObject = { a: 1 };


/**
*
*/
class Sample {
public calls = 0;


/**
*
*/
@memoize({ strategy: 'concat', skipCache: [deepEqualObject] })
public async run() {
this.calls += 1;

return { a: 1 };
}
}

const sample = new Sample();

const first = await sample.run();
const second = await sample.run();

expect(first).toEqual({ a: 1 });
expect(second).toBe(first);
expect(sample.calls).toBe(1);
Expand Down
5 changes: 5 additions & 0 deletions lib/utils/payday.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ const resetMockedNow = (): void => {

// Override Date constructor
const RealDate = Date;

global.Date = class extends RealDate {
/**
* Constructor for mocked Date class
*
* @param args - arguments passed to Date constructor
*/
constructor(...args: unknown[]) {
Expand All @@ -30,6 +32,9 @@ global.Date = class extends RealDate {
}
}

/**
*
*/
public static now(): number {
return mockedNow !== null ? mockedNow : RealDate.now();
}
Expand Down
Loading
Loading