diff --git a/package-lock.json b/package-lock.json index 69e147e..762931a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "2.0.0", "license": "MIT", "devDependencies": { + "@oliverlockwood/express-http-context-intermediate-library": "0.0.5-original-library", "express": "^4.21.2", "jest": "^29.7.0", "supertest": "^7.0.0" @@ -903,6 +904,21 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@oliverlockwood/express-http-context-intermediate-library": { + "version": "0.0.5-original-library", + "resolved": "https://nexus-internal.ooflex.net/repository/npm/@oliverlockwood/express-http-context-intermediate-library/-/express-http-context-intermediate-library-0.0.5-original-library.tgz", + "integrity": "sha512-tJSMyEf+hdS73pd/FDaYLv4BweM4qR2Dv3NhwGfeWmxgGNk91/mv9Z4tv7uXbt4a6T4oB5yhXTMOr1cMBPbvhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "express": "4.21.2", + "express-http-context": "2.0.0", + "nanoid": "3.3.11" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -1974,6 +1990,19 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express-http-context": { + "version": "2.0.0", + "resolved": "https://nexus-internal.ooflex.net/repository/npm/express-http-context/-/express-http-context-2.0.0.tgz", + "integrity": "sha512-v1P99OnXAvoQNTNbQc9UR8qswhgR0RO/RBvtHbjO27+4QsENvIkV20DpOoXEey0tcrS+gJIBVpkEPi+F4Y3F9A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.8.0" + }, + "funding": { + "url": "https://github.com/skonves/express-http-context?sponsor=1" + } + }, "node_modules/express/node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", @@ -3442,6 +3471,25 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://nexus-internal.ooflex.net/repository/npm/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -5217,6 +5265,17 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "@oliverlockwood/express-http-context-intermediate-library": { + "version": "0.0.5-original-library", + "resolved": "https://nexus-internal.ooflex.net/repository/npm/@oliverlockwood/express-http-context-intermediate-library/-/express-http-context-intermediate-library-0.0.5-original-library.tgz", + "integrity": "sha512-tJSMyEf+hdS73pd/FDaYLv4BweM4qR2Dv3NhwGfeWmxgGNk91/mv9Z4tv7uXbt4a6T4oB5yhXTMOr1cMBPbvhg==", + "dev": true, + "requires": { + "express": "4.21.2", + "express-http-context": "2.0.0", + "nanoid": "3.3.11" + } + }, "@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -5812,7 +5871,8 @@ "version": "1.5.3", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", - "dev": true + "dev": true, + "requires": {} }, "deepmerge": { "version": "4.3.1", @@ -6053,6 +6113,12 @@ } } }, + "express-http-context": { + "version": "2.0.0", + "resolved": "https://nexus-internal.ooflex.net/repository/npm/express-http-context/-/express-http-context-2.0.0.tgz", + "integrity": "sha512-v1P99OnXAvoQNTNbQc9UR8qswhgR0RO/RBvtHbjO27+4QsENvIkV20DpOoXEey0tcrS+gJIBVpkEPi+F4Y3F9A==", + "dev": true + }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -6710,7 +6776,8 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true + "dev": true, + "requires": {} }, "jest-regex-util": { "version": "29.6.3", @@ -7128,6 +7195,12 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, + "nanoid": { + "version": "3.3.11", + "resolved": "https://nexus-internal.ooflex.net/repository/npm/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true + }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", diff --git a/package.json b/package.json index 07b2e41..fcf5e17 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "homepage": "https://github.com/skonves/express-http-context#readme", "funding": "https://github.com/skonves/express-http-context?sponsor=1", "devDependencies": { + "@oliverlockwood/express-http-context-intermediate-library": "0.0.5-original-library", "express": "^4.21.2", "jest": "^29.7.0", "supertest": "^7.0.0" diff --git a/tests/express-test-harness.js b/tests/express-test-harness.js index 18bb9b1..174d8d8 100644 --- a/tests/express-test-harness.js +++ b/tests/express-test-harness.js @@ -1,6 +1,7 @@ 'use strict' const express = require('express'); +const { init, REQUEST_ID_CONTEXT_KEY, REQUEST_ID_IN_RESPONSE_HTTP_HEADER_NAME } = require('@oliverlockwood/express-http-context-intermediate-library'); const supertest = require('supertest'); const httpContext = require('../index'); @@ -222,4 +223,63 @@ describe('express-http-context', function () { done(); }); }); + + it('maintains unique value when the library is depended upon both directly and transitively', async () => { + + // ARRANGE + const app = express(); + + // this function in the test library makes the following two calls: + // 1. app.use(middleware) and + // 2. httpContext.set(REQUEST_ID_CONTEXT_KEY, ) + // as can be seen in https://github.com/oliverlockwood/express-http-context-intermediate-library/blob/original-express-http-context/src/index.ts#L13-L19 + init(app); + + app.get('/', ((req, res) => { + httpContext.set('value', req.query['value']); + + res.status(200).json({ + fred: '123', + value: req.query['value'], + valueFromContext: httpContext.get('value'), + requestId: httpContext.get(REQUEST_ID_CONTEXT_KEY) + }); + })); + + const request = supertest(app); + + // ACT + const [response1, response2] = await Promise.all([ + request.get('/').query({ value: 'value1' }), + request.get('/').query({ value: 'value2' }), + ]) + + console.log(response1.body); + console.log(response1.headers); + console.log(response2.body); + console.log(response2.headers); + + expect(response1.body.value).toBe('value1'); + expect(response2.body.value).toBe('value2'); + + expect(response1.header[REQUEST_ID_IN_RESPONSE_HTTP_HEADER_NAME]?.length).toBe(21); + expect(response2.header[REQUEST_ID_IN_RESPONSE_HTTP_HEADER_NAME]?.length).toBe(21); + + // This is the specific example I had flagged in the Github issue - where + // setting something into the httpContext in a common library, but it's + // unusable from within the application code. + expect(response1.body.requestId).toBe(response1.header[REQUEST_ID_IN_RESPONSE_HTTP_HEADER_NAME]); + expect(response2.body.requestId).toBe(response2.header[REQUEST_ID_IN_RESPONSE_HTTP_HEADER_NAME]); + + // These operations also fail, I suspect, because neither of the set/get + // functions are usable, because the directly imported AsyncLocalStorage has + // not been initialised by a call to `app.use(middleware)` within our code + // here. Effectively this is another manifestation of the same bug - + // showing that although the middleware *has* already been initialised in + // Express request handler chain, it is not usable because the + // AsyncLocalStorage context is not identical for all usages of the + // `express-http-context` library code. + expect(response1.body.valueFromContext).toBe('value1'); + expect(response2.body.valueFromContext).toBe('value2'); + }); });