diff --git a/packages/async-local-storage/src/lib/async-local-storage.service.ts b/packages/async-local-storage/src/lib/async-local-storage.service.ts index c0fd958..e2b315e 100644 --- a/packages/async-local-storage/src/lib/async-local-storage.service.ts +++ b/packages/async-local-storage/src/lib/async-local-storage.service.ts @@ -118,7 +118,7 @@ export class AsyncLocalStorageService extends Map { 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; } diff --git a/packages/async-local-storage/test/async-local-storage.spec.ts b/packages/async-local-storage/test/async-local-storage-module.spec.ts similarity index 51% rename from packages/async-local-storage/test/async-local-storage.spec.ts rename to packages/async-local-storage/test/async-local-storage-module.spec.ts index cb4d4cc..2105c10 100644 --- a/packages/async-local-storage/test/async-local-storage.spec.ts +++ b/packages/async-local-storage/test/async-local-storage-module.spec.ts @@ -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[] = [], @@ -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(); - }); -}); diff --git a/packages/async-local-storage/test/async-local-storage-service.spec.ts b/packages/async-local-storage/test/async-local-storage-service.spec.ts new file mode 100644 index 0000000..354a020 --- /dev/null +++ b/packages/async-local-storage/test/async-local-storage-service.spec.ts @@ -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((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(); + }); +});