Skip to content

Commit

Permalink
test(nestjs-tools-async-local-storage): fix AsyncLocalStorageService …
Browse files Browse the repository at this point in the history
…tests
  • Loading branch information
getlarge committed Sep 13, 2024
1 parent 84e733c commit b9046a0
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export class AsyncLocalStorageService extends Map<K, T[K]> {
return this.instance.getStore();
}

private isStoreInitialized(x: unknown): x is StoreMap {
isStoreInitialized(x: unknown): x is StoreMap {
return !!x && typeof x === 'object' && x instanceof Map;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,16 @@ import { Provider } from '@nestjs/common';
import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
import { Test, TestingModule } from '@nestjs/testing';
import { AsyncLocalStorage } from 'async_hooks';

import {
AsyncLocalStorageGuard,
AsyncLocalStorageInterceptor,
AsyncLocalStorageModule,
AsyncLocalStorageModuleOptions,
AsyncLocalStorageService,
} from '../src';
import { ExampleController } from './app.controller.mock';
import { ExampleService } from './app.service.mock';

declare module '../src/lib/async-local-storage.interfaces' {
interface RequestContext {
type: string;
}
}

const moduleFactory = async (
options: AsyncLocalStorageModuleOptions,
providers: Provider[] = [],
Expand Down Expand Up @@ -126,87 +118,3 @@ describe('AsyncLocalStorageModule', () => {
).rejects.toThrow("Can't use both guard and interceptor.");
});
});

describe('AsyncLocalStorageService', () => {
let service: AsyncLocalStorageService;
beforeEach(() => {
service = new AsyncLocalStorageService(new AsyncLocalStorage());
});

afterEach(() => {
service?.exit();
});

it('should throw when accessing store before initialization', () => {
expect(() => service.set('ctx', { type: '' })).toThrow(
"Store is not initialized. Call 'enterWith' or 'run' first.",
);
});

it('should allow to set/get/delete store properties once initialized', () => {
service.enterWith(new Map());
//
service.set('ctx', { type: '' });
const ctx = service.get('ctx');
expect(typeof ctx?.type).toBe('string');
expect(typeof service.requestContext?.type).toBe('string');
service.delete('ctx');
expect(typeof service.requestContext).toBe('undefined');
});

it('should allow to access all map properties and methods once initialized', () => {
service.enterWith(new Map());
//
expect(service.has('ctx')).toBeFalsy();
expect(service.size).toBe(0);
service.set('ctx', { type: '' });
expect(service.has('ctx')).toBeTruthy();
expect(service.size).toBe(1);
expect(Array.from(service.keys())).toEqual(['ctx']);
expect(Array.from(service.values())).toEqual([{ type: '' }]);
service.clear();
expect(service.size).toBe(0);
});

it('should allow to run a function with store', () => {
service.run(new Map(), () => {
service.set('ctx', { type: '' });
expect(service.get('ctx')).toEqual({ type: '' });
});
expect(service.store).toBeUndefined();
});

it('should contain the same store in static and instance members', () => {
const requestContext = { type: '' };
service.enterWith(new Map());
service.set('ctx', requestContext);
//
expect(service.requestContext).toEqual(requestContext);
expect(service.get('ctx')).toEqual(requestContext);
expect(AsyncLocalStorageService.requestContext).toEqual(requestContext);
expect(AsyncLocalStorageService.store?.get('ctx')).toEqual(requestContext);
});

it('should always use a single AsyncLocalStorage reference when creating new instance', () => {
const expectedRequestContext = { type: '' };
const newService = new AsyncLocalStorageService(new AsyncLocalStorage());
newService.enterWith(new Map());
newService.set('ctx', expectedRequestContext);
//
expect(AsyncLocalStorageService.instance).toBeDefined();
expect(AsyncLocalStorageService.requestContext).toEqual(expectedRequestContext);
expect(service.requestContext).toEqual(expectedRequestContext);
expect(newService.requestContext).toEqual(expectedRequestContext);
});

it('should allow to access static methods without initializing the store', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
AsyncLocalStorageService.instance = undefined as any;
//
expect(AsyncLocalStorageService.instance).toBeUndefined();
expect(AsyncLocalStorageService.store).toBeUndefined();
expect(() => AsyncLocalStorageService.requestContext).not.toThrow();
expect(AsyncLocalStorageService.requestContext).toBeUndefined();
expect(AsyncLocalStorageService.store?.has('ctx')).toBeFalsy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/* eslint-disable max-lines-per-function */
import { AsyncLocalStorage } from 'node:async_hooks';

import { AsyncLocalStorageService } from '../src';

declare module '../src/lib/async-local-storage.interfaces' {
interface RequestContext {
type: string;
}
}

describe('AsyncLocalStorageService', () => {
let service: AsyncLocalStorageService;

beforeEach(() => {
service = new AsyncLocalStorageService(new AsyncLocalStorage());
});

afterEach(() => {
service?.exit();
});

it('should throw when accessing store before initialization', () => {
expect(() => service.set('ctx', { type: '' })).toThrow(
"Store is not initialized. Call 'enterWith' or 'run' first.",
);
});

it('should allow to set/get/delete store properties once initialized', () => {
service.enterWith(new Map());
//
service.set('ctx', { type: '' });
const ctx = service.get('ctx');
expect(typeof ctx?.type).toBe('string');
expect(typeof service.requestContext?.type).toBe('string');
service.delete('ctx');
expect(typeof service.requestContext).toBe('undefined');
});

it('should allow to access all map properties and methods once initialized', () => {
service.enterWith(new Map());
//
expect(service.has('ctx')).toBeFalsy();
expect(service.size).toBe(0);
service.set('ctx', { type: '' });
expect(service.has('ctx')).toBeTruthy();
expect(service.size).toBe(1);
expect(Array.from(service.keys())).toEqual(['ctx']);
expect(Array.from(service.values())).toEqual([{ type: '' }]);
service.clear();
expect(service.size).toBe(0);
});

it('should allow to run a function with store', async () => {
const p = new Promise<void>((resolve) => {
service.run(new Map(), () => {
service.set('ctx', { type: '' });
expect(service.get('ctx')).toEqual({ type: '' });
resolve();
});
});
//
await p;
});

it('should contain the same store in static and instance members', () => {
const requestContext = { type: '' };
service.enterWith(new Map());
service.set('ctx', requestContext);
//
expect(service.requestContext).toEqual(requestContext);
expect(service.get('ctx')).toEqual(requestContext);
expect(AsyncLocalStorageService.requestContext).toEqual(requestContext);
expect(AsyncLocalStorageService.store?.get('ctx')).toEqual(requestContext);
});

it('should always use a single AsyncLocalStorage reference when creating new instance', () => {
const expectedRequestContext = { type: '' };
const newService = new AsyncLocalStorageService(new AsyncLocalStorage());
newService.enterWith(new Map());
newService.set('ctx', expectedRequestContext);
//
expect(AsyncLocalStorageService.instance).toBeDefined();
expect(AsyncLocalStorageService.requestContext).toEqual(expectedRequestContext);
expect(service.requestContext).toEqual(expectedRequestContext);
expect(newService.requestContext).toEqual(expectedRequestContext);
});

it('should allow to access static methods without initializing the store', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
AsyncLocalStorageService.instance = undefined as any;
//
expect(AsyncLocalStorageService.instance).toBeUndefined();
expect(AsyncLocalStorageService.store).toBeUndefined();
expect(() => AsyncLocalStorageService.requestContext).not.toThrow();
expect(AsyncLocalStorageService.requestContext).toBeUndefined();
expect(AsyncLocalStorageService.store?.has('ctx')).toBeFalsy();
});
});

0 comments on commit b9046a0

Please sign in to comment.