test(core): #resolveRouting - concurrent requests
MODIFY
packages/core/src/+internal/testing/http.helper.ts
packages/core/src/+internal/testing/http.helper.ts
import * as http from 'http';
import { EventEmitter } from 'events';
import { Subject } from 'rxjs';
+import { delay, tap, mapTo } from 'rxjs/operators';
import {
HttpRequest,
HttpResponse,
@@ -15,6 +16,9 @@ import { createEffectContext } from '../../effects/effectsContext.factory';
import { HttpRequestBusToken, HttpServerClientToken } from '../../http/server/http.server.tokens';
import { ServerIO } from '../../listener/listener.interface';
import { LoggerToken, mockLogger } from '../../logger';
+import { factorizeRegExpWithParams } from '../../http/router/http.router.params.factory';
+import { HttpEffect } from '../../http/effects/http.effects.interface';
+import { RoutingItem } from '../../http/router/http.router.interface';
interface HttpRequestMockParams {
url?: string;
@@ -75,6 +79,29 @@ export const createMockEffectContext = () => {
return createEffectContext({ ask: lookup(context), client });
};
+export const createTestRoute = (opts?: { throwError?: boolean; delay?: number; method?: HttpMethod }) => {
+ const routeMethod = opts?.method ?? 'GET';
+ const routeDelay = opts?.delay ?? 0;
+
+ const req = createHttpRequest(({ url: `/delay_${routeDelay}`, method: 'GET' }));
+ const path = factorizeRegExpWithParams(`/delay_${routeDelay}`);
+
+ const effect: HttpEffect = req$ =>
+ req$.pipe(
+ delay(routeDelay),
+ tap(() => { if (opts?.throwError) throw new Error(); }),
+ mapTo({ body: `delay_${routeDelay}` }),
+ );
+
+ const item: RoutingItem = {
+ regExp: path.regExp,
+ path: path.path,
+ methods: { [routeMethod]: { effect, middlewares: [] } },
+ };
+
+ return { req, path, effect, item };
+};
+
export const createHttpServerTestBed = (server: Promise<ServerIO<HttpServer>>) => {
let httpServer: HttpServer;

MODIFY
packages/core/src/http/router/specs/http.router.resolver.v2.spec.ts
packages/core/src/http/router/specs/http.router.resolver.v2.spec.ts
-/* eslint-disable @typescript-eslint/camelcase */
-
-import { of } from 'rxjs';
+import { of, merge } from 'rxjs';
import { mapTo, take, toArray, delay, mergeMap, map } from 'rxjs/operators';
-import { createMockEffectContext, createHttpRequest, createHttpResponse } from '../../../+internal/testing';
+import { createMockEffectContext, createHttpRequest, createHttpResponse, createTestRoute } from '../../../+internal/testing';
import { HttpEffect } from '../../effects/http.effects.interface';
-import { Routing, RoutingItem } from '../http.router.interface';
+import { Routing } from '../http.router.interface';
import { resolveRouting } from '../http.router.resolver.v2';
import { factorizeRegExpWithParams } from '../http.router.params.factory';
@@ -137,34 +135,16 @@ describe('#resolveRouting', () => {
});
test('handles concurrent requests for different paths', done => {
- const createRoute = (routeDelay: number) => {
- const req = createHttpRequest(({ url: `/delay_${routeDelay}`, method: 'GET' }));
- const path = factorizeRegExpWithParams(`/delay_${routeDelay}`);
-
- const effect: HttpEffect = req$ =>
- req$.pipe(
- delay(routeDelay),
- mapTo({ body: `delay_${routeDelay}` }),
- );
-
- const item: RoutingItem = {
- regExp: path.regExp,
- path: path.path,
- methods: { 'GET': { effect, middlewares: [] } },
- };
-
- return { req, path, effect, item };
- };
-
// given
- const delays = [10, 20, 30, 40];
const ctx = createMockEffectContext();
- // [0] GET /delay_10
- // [1] GET /delay_20
- // [2] GET /delay_30
- // [3] GET /delay_40
- const testData = delays.map(createRoute);
+ const testData = [
+ createTestRoute({ delay: 10 }), // [0] GET /delay_10
+ createTestRoute({ delay: 20 }), // [1] GET /delay_20
+ createTestRoute({ delay: 30 }), // [2] GET /delay_30
+ createTestRoute({ delay: 40 }), // [3] GET /delay_40
+ ];
+
const routing: Routing = testData.map(route => route.item);
// when
@@ -189,4 +169,45 @@ describe('#resolveRouting', () => {
run();
});
+
+ test('handles concurrent requests for different paths even if one request throws an error', done => {
+ // given
+ const ctx = createMockEffectContext();
+
+ const testData = [
+ createTestRoute({ delay: 10 }), // [0] GET /delay_10
+ createTestRoute({ delay: 20, throwError: true }), // [1] GET /delay_20
+ createTestRoute({ delay: 30 }), // [2] GET /delay_30
+ createTestRoute({ delay: 40 }), // [3] GET /delay_40
+ ]
+
+ const routing: Routing = testData.map(route => route.item);
+
+ // when
+ const { resolve, outputSubject, errorSubject } = resolveRouting(routing, ctx)();
+ const run = () => {
+ resolve(testData[0].req); // 10 delay
+ resolve(testData[1].req); // 20 delay
+ resolve(testData[2].req); // 30 delay
+ resolve(testData[3].req); // 40 delay
+ };
+
+ // then
+ merge(
+ outputSubject.pipe(take(3), map(res => res.res)),
+ errorSubject.pipe(take(1), map(res => res.error)),
+ ).pipe(
+ toArray(),
+ ).subscribe(
+ result => {
+ expect(result[0]).toEqual({ body: 'delay_10', request: testData[0].req });
+ expect(result[1]).toEqual(new Error());
+ expect(result[2]).toEqual({ body: 'delay_30', request: testData[2].req });
+ expect(result[3]).toEqual({ body: 'delay_40', request: testData[3].req });
+ done();
+ },
+ );
+
+ run();
+ });
});

test(core): #resolveRouting - concurrent requests