@webpieces/http-server 0.3.262 → 0.3.271
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -4
- package/src/WebpiecesMiddleware.d.ts +1 -1
- package/src/WebpiecesMiddleware.js +34 -34
- package/src/WebpiecesMiddleware.js.map +1 -1
- package/src/WebpiecesRouteCreator.js +2 -2
- package/src/WebpiecesRouteCreator.js.map +1 -1
- package/src/WebpiecesServerImpl.js +3 -4
- package/src/WebpiecesServerImpl.js.map +1 -1
- package/src/filters/ContextFilter.d.ts +1 -1
- package/src/filters/ContextFilter.js +5 -5
- package/src/filters/ContextFilter.js.map +1 -1
- package/src/filters/LogApiFilter.d.ts +1 -1
- package/src/filters/LogApiFilter.js +8 -8
- package/src/filters/LogApiFilter.js.map +1 -1
- package/src/filters/RecordingFilter.d.ts +1 -1
- package/src/filters/RecordingFilter.js +10 -10
- package/src/filters/RecordingFilter.js.map +1 -1
- package/src/filters/ServiceAuthFilter.d.ts +19 -0
- package/src/filters/ServiceAuthFilter.js +83 -0
- package/src/filters/ServiceAuthFilter.js.map +1 -0
- package/src/headers/WebpiecesCoreHeaders.d.ts +2 -2
- package/src/headers/WebpiecesCoreHeaders.js +3 -3
- package/src/headers/WebpiecesCoreHeaders.js.map +1 -1
- package/src/index.d.ts +2 -1
- package/src/index.js +6 -4
- package/src/index.js.map +1 -1
- package/src/modules/WebpiecesModule.js +9 -9
- package/src/modules/WebpiecesModule.js.map +1 -1
- package/src/recorder/SpecGenerator.d.ts +1 -1
- package/src/recorder/SpecGenerator.js +1 -1
- package/src/recorder/SpecGenerator.js.map +1 -1
- package/src/recorder/TestCaseRecorderImpl.d.ts +1 -1
- package/src/recorder/TestCaseRecorderImpl.js +7 -7
- package/src/recorder/TestCaseRecorderImpl.js.map +1 -1
- package/src/recorder/recordable.js +5 -5
- package/src/recorder/recordable.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LogApiFilter.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/filters/LogApiFilter.ts"],"names":[],"mappings":";;;;AAAA,yCAA6C;AAC7C,0DAA2F;AAC3F,0DAAsE;AACtE,
|
|
1
|
+
{"version":3,"file":"LogApiFilter.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/filters/LogApiFilter.ts"],"names":[],"mappings":";;;;AAAA,yCAA6C;AAC7C,0DAA2F;AAC3F,0DAAsE;AACtE,oDAAkD;AAClD,oDAK8B;AAE9B;;;;;;;;;;;;;;;GAeG;AACH,MAAM,GAAG,GAAG,sBAAU,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;AAI1C,IAAM,YAAY,GAAlB,MAAM,YAAa,SAAQ,qBAAuC;IAMlC;IAL3B,UAAU,CAAa;IACvB,UAAU,CAAmB;IAErC,YAC4B,QAAwB,EACjB,aAA4B;QAE3D,KAAK,EAAE,CAAC;QAFuB,kBAAa,GAAb,aAAa,CAAe;QAI3D,uEAAuE;QACvE,2EAA2E;QAC3E,IAAI,CAAC,UAAU,GAAG,QAAQ,CAAC,UAAU,EAAE,CAAC;QAExC,GAAG,CAAC,IAAI,CAAC,wBAAwB,IAAI,CAAC,UAAU,CAAC,MAAM,uCAAuC,CAAC,CAAC;QAEhG,IAAI,CAAC,UAAU,GAAG,IAAI,sBAAU,EAAE,CAAC;IACvC,CAAC;IAED,KAAK,CAAC,MAAM,CACR,IAAgB,EAChB,UAAoD;QAEpD,0FAA0F;QAC1F,MAAM,aAAa,GAAG,IAAI,mCAAoB,EAAE,CAAC;QACjD,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,qBAAqB,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;QAEzF,+DAA+D;QAC/D,MAAM,MAAM,GAAG,KAAK,IAAsB,EAAE;YACxC,MAAM,UAAU,GAAG,MAAM,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACjD,OAAO,UAAU,CAAC,QAAQ,CAAC;QAC/B,CAAC,CAAC;QAEF,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QACxG,OAAO,IAAI,yBAAU,CAAC,QAAQ,CAAC,CAAC;IACpC,CAAC;CACJ,CAAA;AApCY,oCAAY;uBAAZ,YAAY;IAFxB,IAAA,+BAAgB,GAAE;IAClB,IAAA,sBAAU,GAAE;IAMJ,mBAAA,IAAA,kBAAM,EAAC,0BAAc,CAAC,CAAA;IACtB,mBAAA,IAAA,kBAAM,EAAC,yBAAa,CAAC,CAAA;6CADY,0BAAc;QACF,yBAAa;GANtD,YAAY,CAoCxB","sourcesContent":["import {inject, injectable} from 'inversify';\nimport {provideSingleton, MethodMeta, RequestContextReader} from '@webpieces/http-routing';\nimport { Filter, WpResponse, Service } from '@webpieces/http-filters';\nimport { LogManager } from '@webpieces/core-util';\nimport {\n PlatformHeader,\n HeaderMethods,\n HeaderRegistry,\n LogApiCall,\n} from '@webpieces/core-util';\n\n/**\n * LogApiFilter - Structured API logging for all requests/responses.\n * Priority: 1800 (after ContextFilter at 2000, before custom filters)\n *\n * Logging patterns (via LogApiCall):\n * - [API-SVR-req] Class.method /url request={...} headers={...}\n * - [API-SVR-resp-SUCCESS] Class.method response={...}\n * - [API-SVR-resp-FAIL] Class.method error=... (server errors: 500, 502, 504)\n * - [API-SVR-resp-OTHER] Class.method errorType=... (user errors: 400, 401, 403, 404, 266)\n *\n * Headers are read from RequestContext (NOT from meta.requestHeaders which is undefined\n * after ContextFilter runs at priority 2000).\n *\n * User errors (HttpBadRequestError, etc.) are logged as OTHER, not FAIL,\n * because they are expected behavior from the server's perspective.\n */\nconst log = LogManager.getLogger('LogApiFilter');\n\n@provideSingleton()\n@injectable()\nexport class LogApiFilter extends Filter<MethodMeta, WpResponse<unknown>> {\n private logApiCall: LogApiCall;\n private allHeaders: PlatformHeader[];\n\n constructor(\n @inject(HeaderRegistry) registry: HeaderRegistry,\n @inject(HeaderMethods) private headerMethods: HeaderMethods\n ) {\n super();\n\n // The registry is the single source of truth (all modules' extensions,\n // duplicate-validated at startup). Log map keys use loggerMdcKey when set.\n this.allHeaders = registry.getHeaders();\n\n log.info(`[LogApiFilter] Using ${this.allHeaders.length} platform headers from HeaderRegistry`);\n\n this.logApiCall = new LogApiCall();\n }\n\n async filter(\n meta: MethodMeta,\n nextFilter: Service<MethodMeta, WpResponse<unknown>>,\n ): Promise<WpResponse<unknown>> {\n // Build header map from RequestContext (headers are already transferred by ContextFilter)\n const contextReader = new RequestContextReader();\n const headers = this.headerMethods.buildSecureMapForLogs(this.allHeaders, contextReader);\n\n // Wrap nextFilter.invoke in a method that returns the response\n const method = async (): Promise<unknown> => {\n const wpResponse = await nextFilter.invoke(meta);\n return wpResponse.response;\n };\n\n const response = await this.logApiCall.execute(\"SVR\", meta.routeMeta, meta.requestDto, headers, method);\n return new WpResponse(response);\n }\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MethodMeta, WebpiecesConfig } from '@webpieces/http-routing';
|
|
2
2
|
import { Filter, WpResponse, Service } from '@webpieces/http-filters';
|
|
3
|
-
import { HeaderRegistry } from '@webpieces/
|
|
3
|
+
import { HeaderRegistry } from '@webpieces/core-util';
|
|
4
4
|
/**
|
|
5
5
|
* RecordingFilter - Records a request as a replayable test case (port of Java
|
|
6
6
|
* RecordingFilter).
|
|
@@ -6,7 +6,7 @@ const inversify_1 = require("inversify");
|
|
|
6
6
|
const http_routing_1 = require("@webpieces/http-routing");
|
|
7
7
|
const core_context_1 = require("@webpieces/core-context");
|
|
8
8
|
const http_filters_1 = require("@webpieces/http-filters");
|
|
9
|
-
const
|
|
9
|
+
const core_util_1 = require("@webpieces/core-util");
|
|
10
10
|
const TestCaseRecorderImpl_1 = require("../recorder/TestCaseRecorderImpl");
|
|
11
11
|
/**
|
|
12
12
|
* RecordingFilter - Records a request as a replayable test case (port of Java
|
|
@@ -29,7 +29,7 @@ const TestCaseRecorderImpl_1 = require("../recorder/TestCaseRecorderImpl");
|
|
|
29
29
|
let RecordingFilter = class RecordingFilter extends http_filters_1.Filter {
|
|
30
30
|
config;
|
|
31
31
|
registry;
|
|
32
|
-
headerMethods = new
|
|
32
|
+
headerMethods = new core_util_1.HeaderMethods();
|
|
33
33
|
constructor(config, registry) {
|
|
34
34
|
super();
|
|
35
35
|
this.config = config;
|
|
@@ -41,7 +41,7 @@ let RecordingFilter = class RecordingFilter extends http_filters_1.Filter {
|
|
|
41
41
|
return await nextFilter.invoke(meta);
|
|
42
42
|
}
|
|
43
43
|
const recorder = new TestCaseRecorderImpl_1.TestCaseRecorderImpl();
|
|
44
|
-
core_context_1.RequestContext.putHeader(
|
|
44
|
+
core_context_1.RequestContext.putHeader(core_util_1.RecorderKeys.RECORDER, recorder);
|
|
45
45
|
const serverEndpoint = this.buildServerEndpoint(meta);
|
|
46
46
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- capture failure into the recording, then rethrow unchanged
|
|
47
47
|
try {
|
|
@@ -50,12 +50,12 @@ let RecordingFilter = class RecordingFilter extends http_filters_1.Filter {
|
|
|
50
50
|
return response;
|
|
51
51
|
}
|
|
52
52
|
catch (err) {
|
|
53
|
-
const error = (0,
|
|
54
|
-
serverEndpoint.failureResponse = new
|
|
53
|
+
const error = (0, core_util_1.toError)(err);
|
|
54
|
+
serverEndpoint.failureResponse = new core_util_1.RecordedError(error.name, error.message);
|
|
55
55
|
throw err;
|
|
56
56
|
}
|
|
57
57
|
finally {
|
|
58
|
-
core_context_1.RequestContext.remove(
|
|
58
|
+
core_context_1.RequestContext.remove(core_util_1.RecorderKeys.RECORDER.getHeaderName());
|
|
59
59
|
recorder.spitOutTestCase(serverEndpoint, this.config.recordingDir);
|
|
60
60
|
}
|
|
61
61
|
}
|
|
@@ -64,7 +64,7 @@ let RecordingFilter = class RecordingFilter extends http_filters_1.Filter {
|
|
|
64
64
|
return true;
|
|
65
65
|
}
|
|
66
66
|
// ContextFilter (priority 2000) already transferred the header into context
|
|
67
|
-
return core_context_1.RequestContext.hasHeader(
|
|
67
|
+
return core_context_1.RequestContext.hasHeader(core_util_1.WebpiecesCoreHeaders.RECORDING);
|
|
68
68
|
}
|
|
69
69
|
buildServerEndpoint(meta) {
|
|
70
70
|
// Masked snapshot of the magic context (secured values masked, MDC keys)
|
|
@@ -74,7 +74,7 @@ let RecordingFilter = class RecordingFilter extends http_filters_1.Filter {
|
|
|
74
74
|
ctxSnapshot[entry[0]] = entry[1];
|
|
75
75
|
}
|
|
76
76
|
const apiName = meta.routeMeta.controllerClassName ?? 'UnknownController';
|
|
77
|
-
return new
|
|
77
|
+
return new core_util_1.RecordedEndpoint(apiName, meta.methodName, [meta.requestDto], ctxSnapshot);
|
|
78
78
|
}
|
|
79
79
|
};
|
|
80
80
|
exports.RecordingFilter = RecordingFilter;
|
|
@@ -84,8 +84,8 @@ exports.RecordingFilter = RecordingFilter = tslib_1.__decorate([
|
|
|
84
84
|
// webpieces-disable no-any-unknown -- Filter generic params use unknown for response type flexibility
|
|
85
85
|
,
|
|
86
86
|
tslib_1.__param(0, (0, inversify_1.inject)(http_routing_1.WEBPIECES_CONFIG_TOKEN)),
|
|
87
|
-
tslib_1.__param(1, (0, inversify_1.inject)(
|
|
87
|
+
tslib_1.__param(1, (0, inversify_1.inject)(core_util_1.HeaderRegistry)),
|
|
88
88
|
tslib_1.__metadata("design:paramtypes", [http_routing_1.WebpiecesConfig,
|
|
89
|
-
|
|
89
|
+
core_util_1.HeaderRegistry])
|
|
90
90
|
], RecordingFilter);
|
|
91
91
|
//# sourceMappingURL=RecordingFilter.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RecordingFilter.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/filters/RecordingFilter.ts"],"names":[],"mappings":";;;;AAAA,yCAA+C;AAC/C,0DAMiC;AACjC,0DAAyD;AACzD,0DAAsE;AACtE,
|
|
1
|
+
{"version":3,"file":"RecordingFilter.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/filters/RecordingFilter.ts"],"names":[],"mappings":"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","sourcesContent":["import { inject, injectable } from 'inversify';\nimport {\n provideSingleton,\n MethodMeta,\n RequestContextReader,\n WebpiecesConfig,\n WEBPIECES_CONFIG_TOKEN,\n} from '@webpieces/http-routing';\nimport { RequestContext } from '@webpieces/core-context';\nimport { Filter, WpResponse, Service } from '@webpieces/http-filters';\nimport {\n HeaderMethods,\n HeaderRegistry,\n RecordedEndpoint,\n RecordedError,\n RecorderKeys,\n WebpiecesCoreHeaders,\n toError,\n} from '@webpieces/core-util';\nimport { TestCaseRecorderImpl } from '../recorder/TestCaseRecorderImpl';\n\n/**\n * RecordingFilter - Records a request as a replayable test case (port of Java\n * RecordingFilter).\n *\n * Suggested priority: 1850 (after ContextFilter 2000 so the RECORDING header\n * has been transferred to the context, after AuthFilter 1900 so only real\n * authorized flows are recorded, before LogApiFilter 1800).\n *\n * Activates when WebpiecesConfig.recordingAlwaysOn is set OR the request\n * carries WebpiecesCoreHeaders.RECORDING (x-webpieces-recording). While\n * active, a TestCaseRecorderImpl travels in the RequestContext under\n * RecorderKeys.RECORDER; the http-client proxy and recordable() wrappers add\n * every downstream call. On completion the fixture + generated spec are\n * logged (and written to config.recordingDir when set).\n *\n * Recording NEVER alters the response - failures inside the recorder are\n * caught and logged.\n */\n@provideSingleton()\n@injectable()\n// webpieces-disable no-any-unknown -- Filter generic params use unknown for response type flexibility\nexport class RecordingFilter extends Filter<MethodMeta, WpResponse<unknown>> {\n private headerMethods = new HeaderMethods();\n\n constructor(\n @inject(WEBPIECES_CONFIG_TOKEN) private config: WebpiecesConfig,\n @inject(HeaderRegistry) private registry: HeaderRegistry,\n ) {\n super();\n }\n\n // webpieces-disable no-any-unknown -- Filter generic params use unknown for response type flexibility\n async filter(\n meta: MethodMeta,\n nextFilter: Service<MethodMeta, WpResponse<unknown>>,\n ): Promise<WpResponse<unknown>> {\n if (!this.isRecordingRequested()) {\n return await nextFilter.invoke(meta);\n }\n\n const recorder = new TestCaseRecorderImpl();\n RequestContext.putHeader(RecorderKeys.RECORDER, recorder);\n\n const serverEndpoint = this.buildServerEndpoint(meta);\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- capture failure into the recording, then rethrow unchanged\n try {\n const response = await nextFilter.invoke(meta);\n serverEndpoint.successResponse = response.response;\n return response;\n } catch (err: unknown) {\n const error = toError(err);\n serverEndpoint.failureResponse = new RecordedError(error.name, error.message);\n throw err;\n } finally {\n RequestContext.remove(RecorderKeys.RECORDER.getHeaderName());\n recorder.spitOutTestCase(serverEndpoint, this.config.recordingDir);\n }\n }\n\n private isRecordingRequested(): boolean {\n if (this.config.recordingAlwaysOn) {\n return true;\n }\n // ContextFilter (priority 2000) already transferred the header into context\n return RequestContext.hasHeader(WebpiecesCoreHeaders.RECORDING);\n }\n\n private buildServerEndpoint(meta: MethodMeta): RecordedEndpoint {\n // Masked snapshot of the magic context (secured values masked, MDC keys)\n const logMap = this.headerMethods.buildSecureMapForLogs(this.registry.getHeaders(), new RequestContextReader());\n const ctxSnapshot: Record<string, string> = {};\n for (const entry of logMap.entries()) {\n ctxSnapshot[entry[0]] = entry[1];\n }\n\n const apiName = meta.routeMeta.controllerClassName ?? 'UnknownController';\n return new RecordedEndpoint(apiName, meta.methodName, [meta.requestDto], ctxSnapshot);\n }\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Filter, WpResponse, Service, MethodMeta } from '@webpieces/http-filters';
|
|
2
|
+
/**
|
|
3
|
+
* ServiceAuthFilter - framework filter that enforces the SERVICE-to-service auth modes
|
|
4
|
+
* (@AuthOidc, @AuthSharedSecret). Priority 1950: runs right after ContextFilter (2000)
|
|
5
|
+
* so the credential headers are already in RequestContext, and before app filters.
|
|
6
|
+
*
|
|
7
|
+
* This is what secures Cloud Tasks delivery: a @PubSub endpoint marked @AuthOidc only
|
|
8
|
+
* accepts a request carrying a valid Google OIDC token from an allowed caller SA. The
|
|
9
|
+
* `public` and `jwt` modes are NOT this filter's job (jwt stays in the app AuthFilter).
|
|
10
|
+
*/
|
|
11
|
+
export declare class ServiceAuthFilter extends Filter<MethodMeta, WpResponse<unknown>> {
|
|
12
|
+
filter(meta: MethodMeta, nextFilter: Service<MethodMeta, WpResponse<unknown>>): Promise<WpResponse<unknown>>;
|
|
13
|
+
/** Verify a Google OIDC bearer token from an allowed caller service account. */
|
|
14
|
+
private enforceOidc;
|
|
15
|
+
/** Constant-time compare of the shared-secret header against process.env[secretEnv]. */
|
|
16
|
+
private enforceSharedSecret;
|
|
17
|
+
private stripBearer;
|
|
18
|
+
private constantTimeEquals;
|
|
19
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ServiceAuthFilter = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const inversify_1 = require("inversify");
|
|
6
|
+
const crypto_1 = require("crypto");
|
|
7
|
+
const core_context_1 = require("@webpieces/core-context");
|
|
8
|
+
const http_filters_1 = require("@webpieces/http-filters");
|
|
9
|
+
const core_util_1 = require("@webpieces/core-util");
|
|
10
|
+
const gcp_identity_1 = require("@webpieces/gcp-identity");
|
|
11
|
+
const core_util_2 = require("@webpieces/core-util");
|
|
12
|
+
const log = core_util_2.LogManager.getLogger('ServiceAuthFilter');
|
|
13
|
+
/**
|
|
14
|
+
* ServiceAuthFilter - framework filter that enforces the SERVICE-to-service auth modes
|
|
15
|
+
* (@AuthOidc, @AuthSharedSecret). Priority 1950: runs right after ContextFilter (2000)
|
|
16
|
+
* so the credential headers are already in RequestContext, and before app filters.
|
|
17
|
+
*
|
|
18
|
+
* This is what secures Cloud Tasks delivery: a @PubSub endpoint marked @AuthOidc only
|
|
19
|
+
* accepts a request carrying a valid Google OIDC token from an allowed caller SA. The
|
|
20
|
+
* `public` and `jwt` modes are NOT this filter's job (jwt stays in the app AuthFilter).
|
|
21
|
+
*/
|
|
22
|
+
let ServiceAuthFilter = class ServiceAuthFilter extends http_filters_1.Filter {
|
|
23
|
+
// webpieces-disable no-any-unknown -- Filter generic params use unknown for response type flexibility
|
|
24
|
+
async filter(meta, nextFilter) {
|
|
25
|
+
const authMeta = meta.authMeta;
|
|
26
|
+
if (authMeta) {
|
|
27
|
+
const mode = authMeta.mode;
|
|
28
|
+
if (mode.kind === 'oidc') {
|
|
29
|
+
await this.enforceOidc(mode.callers);
|
|
30
|
+
}
|
|
31
|
+
else if (mode.kind === 'shared-secret') {
|
|
32
|
+
this.enforceSharedSecret(mode.secretEnv);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return nextFilter.invoke(meta);
|
|
36
|
+
}
|
|
37
|
+
/** Verify a Google OIDC bearer token from an allowed caller service account. */
|
|
38
|
+
async enforceOidc(callers) {
|
|
39
|
+
const header = core_context_1.RequestContext.getHeader(core_util_1.WebpiecesCoreHeaders.AUTHORIZATION);
|
|
40
|
+
const token = this.stripBearer(header);
|
|
41
|
+
if (!token) {
|
|
42
|
+
throw new core_util_1.HttpUnauthorizedError('Missing OIDC bearer token for @AuthOidc endpoint');
|
|
43
|
+
}
|
|
44
|
+
const result = await (0, gcp_identity_1.verifyOidcFromCallers)(token, callers);
|
|
45
|
+
if (!result.ok) {
|
|
46
|
+
throw new core_util_1.HttpUnauthorizedError(`OIDC auth failed: ${result.reason ?? 'unknown'}`);
|
|
47
|
+
}
|
|
48
|
+
log.debug(`OIDC caller verified: ${result.email}`);
|
|
49
|
+
}
|
|
50
|
+
/** Constant-time compare of the shared-secret header against process.env[secretEnv]. */
|
|
51
|
+
enforceSharedSecret(secretEnv) {
|
|
52
|
+
const expected = process.env[secretEnv];
|
|
53
|
+
if (!expected) {
|
|
54
|
+
throw new core_util_1.HttpUnauthorizedError(`Shared secret env '${secretEnv}' is not configured`);
|
|
55
|
+
}
|
|
56
|
+
const provided = core_context_1.RequestContext.getHeader(core_util_1.WebpiecesCoreHeaders.SHARED_SECRET);
|
|
57
|
+
if (!provided || !this.constantTimeEquals(provided, expected)) {
|
|
58
|
+
throw new core_util_1.HttpUnauthorizedError('Invalid shared secret for @AuthSharedSecret endpoint');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
stripBearer(header) {
|
|
62
|
+
if (!header) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
const prefix = 'Bearer ';
|
|
66
|
+
return header.startsWith(prefix) ? header.substring(prefix.length) : header;
|
|
67
|
+
}
|
|
68
|
+
constantTimeEquals(a, b) {
|
|
69
|
+
const bufA = Buffer.from(a, 'utf8');
|
|
70
|
+
const bufB = Buffer.from(b, 'utf8');
|
|
71
|
+
if (bufA.length !== bufB.length) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
return (0, crypto_1.timingSafeEqual)(bufA, bufB);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
exports.ServiceAuthFilter = ServiceAuthFilter;
|
|
78
|
+
exports.ServiceAuthFilter = ServiceAuthFilter = tslib_1.__decorate([
|
|
79
|
+
(0, core_context_1.provideSingleton)(),
|
|
80
|
+
(0, inversify_1.injectable)()
|
|
81
|
+
// webpieces-disable no-any-unknown -- Filter generic params use unknown for response type flexibility
|
|
82
|
+
], ServiceAuthFilter);
|
|
83
|
+
//# sourceMappingURL=ServiceAuthFilter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ServiceAuthFilter.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/filters/ServiceAuthFilter.ts"],"names":[],"mappings":";;;;AAAA,yCAAuC;AACvC,mCAAyC;AACzC,0DAA2E;AAC3E,0DAAkF;AAClF,oDAAmF;AACnF,0DAAgE;AAChE,oDAAkD;AAElD,MAAM,GAAG,GAAG,sBAAU,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;AAEtD;;;;;;;;GAQG;AAII,IAAM,iBAAiB,GAAvB,MAAM,iBAAkB,SAAQ,qBAAuC;IAE1E,sGAAsG;IAC7F,KAAK,CAAC,MAAM,CACjB,IAAgB,EAChB,UAAoD;QAEpD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC/B,IAAI,QAAQ,EAAE,CAAC;YACX,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC;YAC3B,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBACvB,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACzC,CAAC;iBAAM,IAAI,IAAI,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;gBACvC,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC7C,CAAC;QACL,CAAC;QACD,OAAO,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;IAED,gFAAgF;IACxE,KAAK,CAAC,WAAW,CAAC,OAAiB;QACvC,MAAM,MAAM,GAAG,6BAAc,CAAC,SAAS,CAAC,gCAAoB,CAAC,aAAa,CAAuB,CAAC;QAClG,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,CAAC,KAAK,EAAE,CAAC;YACT,MAAM,IAAI,iCAAqB,CAAC,kDAAkD,CAAC,CAAC;QACxF,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,IAAA,oCAAqB,EAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAC3D,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;YACb,MAAM,IAAI,iCAAqB,CAAC,qBAAqB,MAAM,CAAC,MAAM,IAAI,SAAS,EAAE,CAAC,CAAC;QACvF,CAAC;QACD,GAAG,CAAC,KAAK,CAAC,yBAAyB,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;IACvD,CAAC;IAED,wFAAwF;IAChF,mBAAmB,CAAC,SAAiB;QACzC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACxC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACZ,MAAM,IAAI,iCAAqB,CAAC,sBAAsB,SAAS,qBAAqB,CAAC,CAAC;QAC1F,CAAC;QACD,MAAM,QAAQ,GAAG,6BAAc,CAAC,SAAS,CAAC,gCAAoB,CAAC,aAAa,CAAuB,CAAC;QACpG,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC;YAC5D,MAAM,IAAI,iCAAqB,CAAC,sDAAsD,CAAC,CAAC;QAC5F,CAAC;IACL,CAAC;IAEO,WAAW,CAAC,MAA0B;QAC1C,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,OAAO,SAAS,CAAC;QACrB,CAAC;QACD,MAAM,MAAM,GAAG,SAAS,CAAC;QACzB,OAAO,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAChF,CAAC;IAEO,kBAAkB,CAAC,CAAS,EAAE,CAAS;QAC3C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QACpC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QACpC,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;YAC9B,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,OAAO,IAAA,wBAAe,EAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACvC,CAAC;CACJ,CAAA;AA7DY,8CAAiB;4BAAjB,iBAAiB;IAH7B,IAAA,+BAAgB,GAAE;IAClB,IAAA,sBAAU,GAAE;IACb,sGAAsG;GACzF,iBAAiB,CA6D7B","sourcesContent":["import { injectable } from 'inversify';\nimport { timingSafeEqual } from 'crypto';\nimport { provideSingleton, RequestContext } from '@webpieces/core-context';\nimport { Filter, WpResponse, Service, MethodMeta } from '@webpieces/http-filters';\nimport { HttpUnauthorizedError, WebpiecesCoreHeaders } from '@webpieces/core-util';\nimport { verifyOidcFromCallers } from '@webpieces/gcp-identity';\nimport { LogManager } from '@webpieces/core-util';\n\nconst log = LogManager.getLogger('ServiceAuthFilter');\n\n/**\n * ServiceAuthFilter - framework filter that enforces the SERVICE-to-service auth modes\n * (@AuthOidc, @AuthSharedSecret). Priority 1950: runs right after ContextFilter (2000)\n * so the credential headers are already in RequestContext, and before app filters.\n *\n * This is what secures Cloud Tasks delivery: a @PubSub endpoint marked @AuthOidc only\n * accepts a request carrying a valid Google OIDC token from an allowed caller SA. The\n * `public` and `jwt` modes are NOT this filter's job (jwt stays in the app AuthFilter).\n */\n@provideSingleton()\n@injectable()\n// webpieces-disable no-any-unknown -- Filter generic params use unknown for response type flexibility\nexport class ServiceAuthFilter extends Filter<MethodMeta, WpResponse<unknown>> {\n\n // webpieces-disable no-any-unknown -- Filter generic params use unknown for response type flexibility\n override async filter(\n meta: MethodMeta,\n nextFilter: Service<MethodMeta, WpResponse<unknown>>,\n ): Promise<WpResponse<unknown>> {\n const authMeta = meta.authMeta;\n if (authMeta) {\n const mode = authMeta.mode;\n if (mode.kind === 'oidc') {\n await this.enforceOidc(mode.callers);\n } else if (mode.kind === 'shared-secret') {\n this.enforceSharedSecret(mode.secretEnv);\n }\n }\n return nextFilter.invoke(meta);\n }\n\n /** Verify a Google OIDC bearer token from an allowed caller service account. */\n private async enforceOidc(callers: string[]): Promise<void> {\n const header = RequestContext.getHeader(WebpiecesCoreHeaders.AUTHORIZATION) as string | undefined;\n const token = this.stripBearer(header);\n if (!token) {\n throw new HttpUnauthorizedError('Missing OIDC bearer token for @AuthOidc endpoint');\n }\n const result = await verifyOidcFromCallers(token, callers);\n if (!result.ok) {\n throw new HttpUnauthorizedError(`OIDC auth failed: ${result.reason ?? 'unknown'}`);\n }\n log.debug(`OIDC caller verified: ${result.email}`);\n }\n\n /** Constant-time compare of the shared-secret header against process.env[secretEnv]. */\n private enforceSharedSecret(secretEnv: string): void {\n const expected = process.env[secretEnv];\n if (!expected) {\n throw new HttpUnauthorizedError(`Shared secret env '${secretEnv}' is not configured`);\n }\n const provided = RequestContext.getHeader(WebpiecesCoreHeaders.SHARED_SECRET) as string | undefined;\n if (!provided || !this.constantTimeEquals(provided, expected)) {\n throw new HttpUnauthorizedError('Invalid shared secret for @AuthSharedSecret endpoint');\n }\n }\n\n private stripBearer(header: string | undefined): string | undefined {\n if (!header) {\n return undefined;\n }\n const prefix = 'Bearer ';\n return header.startsWith(prefix) ? header.substring(prefix.length) : header;\n }\n\n private constantTimeEquals(a: string, b: string): boolean {\n const bufA = Buffer.from(a, 'utf8');\n const bufB = Buffer.from(b, 'utf8');\n if (bufA.length !== bufB.length) {\n return false;\n }\n return timingSafeEqual(bufA, bufB);\n }\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* WebpiecesCoreHeaders moved to @webpieces/
|
|
2
|
+
* WebpiecesCoreHeaders moved to @webpieces/core-util (browser-safe) so the
|
|
3
3
|
* http-client can reference REQUEST_ID/PREVIOUS_REQUEST_ID for request-id
|
|
4
4
|
* chaining. Re-exported here for backward compatibility with existing imports.
|
|
5
5
|
*/
|
|
6
|
-
export { WebpiecesCoreHeaders } from '@webpieces/
|
|
6
|
+
export { WebpiecesCoreHeaders } from '@webpieces/core-util';
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.WebpiecesCoreHeaders = void 0;
|
|
4
4
|
/**
|
|
5
|
-
* WebpiecesCoreHeaders moved to @webpieces/
|
|
5
|
+
* WebpiecesCoreHeaders moved to @webpieces/core-util (browser-safe) so the
|
|
6
6
|
* http-client can reference REQUEST_ID/PREVIOUS_REQUEST_ID for request-id
|
|
7
7
|
* chaining. Re-exported here for backward compatibility with existing imports.
|
|
8
8
|
*/
|
|
9
|
-
var
|
|
10
|
-
Object.defineProperty(exports, "WebpiecesCoreHeaders", { enumerable: true, get: function () { return
|
|
9
|
+
var core_util_1 = require("@webpieces/core-util");
|
|
10
|
+
Object.defineProperty(exports, "WebpiecesCoreHeaders", { enumerable: true, get: function () { return core_util_1.WebpiecesCoreHeaders; } });
|
|
11
11
|
//# sourceMappingURL=WebpiecesCoreHeaders.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebpiecesCoreHeaders.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/headers/WebpiecesCoreHeaders.ts"],"names":[],"mappings":";;;AAAA;;;;GAIG;AACH,
|
|
1
|
+
{"version":3,"file":"WebpiecesCoreHeaders.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/headers/WebpiecesCoreHeaders.ts"],"names":[],"mappings":";;;AAAA;;;;GAIG;AACH,kDAA4D;AAAnD,iHAAA,oBAAoB,OAAA","sourcesContent":["/**\n * WebpiecesCoreHeaders moved to @webpieces/core-util (browser-safe) so the\n * http-client can reference REQUEST_ID/PREVIOUS_REQUEST_ID for request-id\n * chaining. Re-exported here for backward compatibility with existing imports.\n */\nexport { WebpiecesCoreHeaders } from '@webpieces/core-util';\n"]}
|
package/src/index.d.ts
CHANGED
|
@@ -6,10 +6,11 @@ export { InProcessApiClientFactory } from './InProcessApiClientFactory';
|
|
|
6
6
|
export { ContextFilter } from './filters/ContextFilter';
|
|
7
7
|
export { LogApiFilter } from './filters/LogApiFilter';
|
|
8
8
|
export { RecordingFilter } from './filters/RecordingFilter';
|
|
9
|
+
export { ServiceAuthFilter } from './filters/ServiceAuthFilter';
|
|
9
10
|
export { TestCaseRecorderImpl } from './recorder/TestCaseRecorderImpl';
|
|
10
11
|
export { SpecGenerator } from './recorder/SpecGenerator';
|
|
11
12
|
export { recordable } from './recorder/recordable';
|
|
12
13
|
export { WebpiecesModule } from './modules/WebpiecesModule';
|
|
13
14
|
export { WebpiecesCoreHeaders } from './headers/WebpiecesCoreHeaders';
|
|
14
|
-
export { HeaderRegistry } from '@webpieces/
|
|
15
|
+
export { HeaderRegistry } from '@webpieces/core-util';
|
|
15
16
|
export { RouteHandler, ExpressRouteHandler, MethodMeta, RouteBuilderImpl, RouteHandlerWithMeta, FilterWithMeta, HttpFilter, FilterMatcher, FilterDefinition, } from '@webpieces/http-routing';
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.FilterDefinition = exports.FilterMatcher = exports.FilterWithMeta = exports.RouteHandlerWithMeta = exports.RouteBuilderImpl = exports.MethodMeta = exports.RouteHandler = exports.HeaderRegistry = exports.WebpiecesCoreHeaders = exports.WebpiecesModule = exports.recordable = exports.SpecGenerator = exports.TestCaseRecorderImpl = exports.RecordingFilter = exports.LogApiFilter = exports.ContextFilter = exports.InProcessApiClientFactory = exports.WebpiecesRouteCreator = exports.WebpiecesMiddleware = exports.WebpiecesFactory = void 0;
|
|
3
|
+
exports.FilterDefinition = exports.FilterMatcher = exports.FilterWithMeta = exports.RouteHandlerWithMeta = exports.RouteBuilderImpl = exports.MethodMeta = exports.RouteHandler = exports.HeaderRegistry = exports.WebpiecesCoreHeaders = exports.WebpiecesModule = exports.recordable = exports.SpecGenerator = exports.TestCaseRecorderImpl = exports.ServiceAuthFilter = exports.RecordingFilter = exports.LogApiFilter = exports.ContextFilter = exports.InProcessApiClientFactory = exports.WebpiecesRouteCreator = exports.WebpiecesMiddleware = exports.WebpiecesFactory = void 0;
|
|
4
4
|
var WebpiecesFactory_1 = require("./WebpiecesFactory");
|
|
5
5
|
Object.defineProperty(exports, "WebpiecesFactory", { enumerable: true, get: function () { return WebpiecesFactory_1.WebpiecesFactory; } });
|
|
6
6
|
var WebpiecesMiddleware_1 = require("./WebpiecesMiddleware");
|
|
@@ -15,7 +15,9 @@ var LogApiFilter_1 = require("./filters/LogApiFilter");
|
|
|
15
15
|
Object.defineProperty(exports, "LogApiFilter", { enumerable: true, get: function () { return LogApiFilter_1.LogApiFilter; } });
|
|
16
16
|
var RecordingFilter_1 = require("./filters/RecordingFilter");
|
|
17
17
|
Object.defineProperty(exports, "RecordingFilter", { enumerable: true, get: function () { return RecordingFilter_1.RecordingFilter; } });
|
|
18
|
-
|
|
18
|
+
var ServiceAuthFilter_1 = require("./filters/ServiceAuthFilter");
|
|
19
|
+
Object.defineProperty(exports, "ServiceAuthFilter", { enumerable: true, get: function () { return ServiceAuthFilter_1.ServiceAuthFilter; } });
|
|
20
|
+
// Test-case recording (contract lives in @webpieces/core-util)
|
|
19
21
|
var TestCaseRecorderImpl_1 = require("./recorder/TestCaseRecorderImpl");
|
|
20
22
|
Object.defineProperty(exports, "TestCaseRecorderImpl", { enumerable: true, get: function () { return TestCaseRecorderImpl_1.TestCaseRecorderImpl; } });
|
|
21
23
|
var SpecGenerator_1 = require("./recorder/SpecGenerator");
|
|
@@ -27,8 +29,8 @@ var WebpiecesModule_1 = require("./modules/WebpiecesModule");
|
|
|
27
29
|
Object.defineProperty(exports, "WebpiecesModule", { enumerable: true, get: function () { return WebpiecesModule_1.WebpiecesModule; } });
|
|
28
30
|
var WebpiecesCoreHeaders_1 = require("./headers/WebpiecesCoreHeaders");
|
|
29
31
|
Object.defineProperty(exports, "WebpiecesCoreHeaders", { enumerable: true, get: function () { return WebpiecesCoreHeaders_1.WebpiecesCoreHeaders; } });
|
|
30
|
-
var
|
|
31
|
-
Object.defineProperty(exports, "HeaderRegistry", { enumerable: true, get: function () { return
|
|
32
|
+
var core_util_1 = require("@webpieces/core-util");
|
|
33
|
+
Object.defineProperty(exports, "HeaderRegistry", { enumerable: true, get: function () { return core_util_1.HeaderRegistry; } });
|
|
32
34
|
// Re-export from http-routing for backward compatibility
|
|
33
35
|
// (FilterDefinition re-exported for one-import adapter ergonomics)
|
|
34
36
|
var http_routing_1 = require("@webpieces/http-routing");
|
package/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/index.ts"],"names":[],"mappings":";;;AACA,uDAAsD;AAA7C,oHAAA,gBAAgB,OAAA;AACzB,6DAA4D;AAAnD,0HAAA,mBAAmB,OAAA;AAC5B,iEAAgE;AAAvD,8HAAA,qBAAqB,OAAA;AAC9B,yEAAwE;AAA/D,sIAAA,yBAAyB,OAAA;AAClC,yDAAwD;AAA/C,8GAAA,aAAa,OAAA;AACtB,uDAAsD;AAA7C,4GAAA,YAAY,OAAA;AACrB,6DAA4D;AAAnD,kHAAA,eAAe,OAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/index.ts"],"names":[],"mappings":";;;AACA,uDAAsD;AAA7C,oHAAA,gBAAgB,OAAA;AACzB,6DAA4D;AAAnD,0HAAA,mBAAmB,OAAA;AAC5B,iEAAgE;AAAvD,8HAAA,qBAAqB,OAAA;AAC9B,yEAAwE;AAA/D,sIAAA,yBAAyB,OAAA;AAClC,yDAAwD;AAA/C,8GAAA,aAAa,OAAA;AACtB,uDAAsD;AAA7C,4GAAA,YAAY,OAAA;AACrB,6DAA4D;AAAnD,kHAAA,eAAe,OAAA;AACxB,iEAAgE;AAAvD,sHAAA,iBAAiB,OAAA;AAE1B,+DAA+D;AAC/D,wEAAuE;AAA9D,4HAAA,oBAAoB,OAAA;AAC7B,0DAAyD;AAAhD,8GAAA,aAAa,OAAA;AACtB,oDAAmD;AAA1C,wGAAA,UAAU,OAAA;AAEnB,mBAAmB;AACnB,6DAA4D;AAAnD,kHAAA,eAAe,OAAA;AACxB,uEAAsE;AAA7D,4HAAA,oBAAoB,OAAA;AAC7B,kDAAsD;AAA7C,2GAAA,cAAc,OAAA;AAEvB,yDAAyD;AACzD,mEAAmE;AACnE,wDAUiC;AAT7B,4GAAA,YAAY,OAAA;AAEZ,0GAAA,UAAU,OAAA;AACV,gHAAA,gBAAgB,OAAA;AAChB,oHAAA,oBAAoB,OAAA;AACpB,8GAAA,cAAc,OAAA;AAEd,6GAAA,aAAa,OAAA;AACb,gHAAA,gBAAgB,OAAA","sourcesContent":["export { WebpiecesServer } from './WebpiecesServer';\nexport { WebpiecesFactory } from './WebpiecesFactory';\nexport { WebpiecesMiddleware } from './WebpiecesMiddleware';\nexport { WebpiecesRouteCreator } from './WebpiecesRouteCreator';\nexport { InProcessApiClientFactory } from './InProcessApiClientFactory';\nexport { ContextFilter } from './filters/ContextFilter';\nexport { LogApiFilter } from './filters/LogApiFilter';\nexport { RecordingFilter } from './filters/RecordingFilter';\nexport { ServiceAuthFilter } from './filters/ServiceAuthFilter';\n\n// Test-case recording (contract lives in @webpieces/core-util)\nexport { TestCaseRecorderImpl } from './recorder/TestCaseRecorderImpl';\nexport { SpecGenerator } from './recorder/SpecGenerator';\nexport { recordable } from './recorder/recordable';\n\n// Platform Headers\nexport { WebpiecesModule } from './modules/WebpiecesModule';\nexport { WebpiecesCoreHeaders } from './headers/WebpiecesCoreHeaders';\nexport { HeaderRegistry } from '@webpieces/core-util';\n\n// Re-export from http-routing for backward compatibility\n// (FilterDefinition re-exported for one-import adapter ergonomics)\nexport {\n RouteHandler,\n ExpressRouteHandler,\n MethodMeta,\n RouteBuilderImpl,\n RouteHandlerWithMeta,\n FilterWithMeta,\n HttpFilter,\n FilterMatcher,\n FilterDefinition,\n} from '@webpieces/http-routing';\n"]}
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.WebpiecesModule = void 0;
|
|
4
4
|
const inversify_1 = require("inversify");
|
|
5
|
-
const
|
|
5
|
+
const core_util_1 = require("@webpieces/core-util");
|
|
6
6
|
const WebpiecesCoreHeaders_1 = require("../headers/WebpiecesCoreHeaders");
|
|
7
|
-
const
|
|
8
|
-
const log =
|
|
7
|
+
const core_util_2 = require("@webpieces/core-util");
|
|
8
|
+
const log = core_util_2.LogManager.getLogger('WebpiecesModule');
|
|
9
9
|
/**
|
|
10
10
|
* WebpiecesModule - Framework-level DI bindings.
|
|
11
11
|
*
|
|
@@ -27,19 +27,19 @@ const log = wp_logging_1.LogManager.getLogger('WebpiecesModule');
|
|
|
27
27
|
exports.WebpiecesModule = new inversify_1.ContainerModule((options) => {
|
|
28
28
|
const { bind } = options;
|
|
29
29
|
// Bind HeaderMethods as singleton (stateless utility, can be shared)
|
|
30
|
-
bind(
|
|
30
|
+
bind(core_util_1.HeaderMethods).toSelf().inSingletonScope();
|
|
31
31
|
// Create extension with core headers
|
|
32
|
-
const coreExtension = new
|
|
32
|
+
const coreExtension = new core_util_1.PlatformHeadersExtension(WebpiecesCoreHeaders_1.WebpiecesCoreHeaders.getAllHeaders());
|
|
33
33
|
// Bind extension for multiInject collection
|
|
34
34
|
// WebpiecesMiddleware will collect all PlatformHeadersExtension bindings via @multiInject
|
|
35
|
-
bind(
|
|
35
|
+
bind(core_util_1.HEADER_TYPES.PlatformHeadersExtension).toConstantValue(coreExtension);
|
|
36
36
|
// HeaderRegistry: the single source of truth for all platform headers.
|
|
37
37
|
// Collects EVERY module's PlatformHeadersExtension at first resolution and
|
|
38
38
|
// fail-fast validates duplicates/conflicts (port of Java HeaderTranslation).
|
|
39
|
-
bind(
|
|
39
|
+
bind(core_util_1.HeaderRegistry)
|
|
40
40
|
.toDynamicValue((ctx) => {
|
|
41
|
-
const extensions = ctx.getAll(
|
|
42
|
-
return new
|
|
41
|
+
const extensions = ctx.getAll(core_util_1.HEADER_TYPES.PlatformHeadersExtension);
|
|
42
|
+
return new core_util_1.HeaderRegistry(extensions);
|
|
43
43
|
})
|
|
44
44
|
.inSingletonScope();
|
|
45
45
|
log.info(`[WebpiecesModule] Registered core platform headers extension with ${coreExtension.headers.length} headers`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebpiecesModule.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/modules/WebpiecesModule.ts"],"names":[],"mappings":";;;AAAA,yCAA+D;AAC/D,
|
|
1
|
+
{"version":3,"file":"WebpiecesModule.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/modules/WebpiecesModule.ts"],"names":[],"mappings":";;;AAAA,yCAA+D;AAC/D,oDAA6G;AAC7G,0EAAuE;AACvE,oDAAkD;AAElD,MAAM,GAAG,GAAG,sBAAU,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;AAEpD;;;;;;;;;;;;;;;;;GAiBG;AACU,QAAA,eAAe,GAAG,IAAI,2BAAe,CAAC,CAAC,OAAO,EAAE,EAAE;IAC3D,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;IAEzB,qEAAqE;IACrE,IAAI,CAAgB,yBAAa,CAAC,CAAC,MAAM,EAAE,CAAC,gBAAgB,EAAE,CAAC;IAE/D,qCAAqC;IACrC,MAAM,aAAa,GAAG,IAAI,oCAAwB,CAAC,2CAAoB,CAAC,aAAa,EAAE,CAAC,CAAC;IAEzF,4CAA4C;IAC5C,0FAA0F;IAC1F,IAAI,CAA2B,wBAAY,CAAC,wBAAwB,CAAC,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC;IAErG,uEAAuE;IACvE,2EAA2E;IAC3E,6EAA6E;IAC7E,IAAI,CAAiB,0BAAc,CAAC;SAC/B,cAAc,CAAC,CAAC,GAAsB,EAAE,EAAE;QACvC,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,CAA2B,wBAAY,CAAC,wBAAwB,CAAC,CAAC;QAC/F,OAAO,IAAI,0BAAc,CAAC,UAAU,CAAC,CAAC;IAC1C,CAAC,CAAC;SACD,gBAAgB,EAAE,CAAC;IAExB,GAAG,CAAC,IAAI,CAAC,qEAAqE,aAAa,CAAC,OAAO,CAAC,MAAM,UAAU,CAAC,CAAC;AAC1H,CAAC,CAAC,CAAC","sourcesContent":["import { ContainerModule, ResolutionContext } from 'inversify';\nimport { HEADER_TYPES, PlatformHeadersExtension, HeaderMethods, HeaderRegistry } from '@webpieces/core-util';\nimport { WebpiecesCoreHeaders } from '../headers/WebpiecesCoreHeaders';\nimport { LogManager } from '@webpieces/core-util';\n\nconst log = LogManager.getLogger('WebpiecesModule');\n\n/**\n * WebpiecesModule - Framework-level DI bindings.\n *\n * This module is loaded by WebpiecesFactory BEFORE application modules.\n * It provides core framework services including platform headers.\n *\n * Platform Headers Pattern (Extension):\n * - Create PlatformHeadersExtension with array of headers\n * - Bind extension to HEADER_TYPES.PlatformHeadersExtension symbol\n * - Multiple modules bind their own extensions\n * - Consumer uses @multiInject to collect all extensions\n * - Pattern inspired by Guice Multibinder\n *\n * Module Loading Order:\n * 1. WebpiecesModule (framework headers) ← YOU ARE HERE\n * 2. CompanyModule (company-wide headers)\n * 3. InversifyModule (app-specific headers)\n */\nexport const WebpiecesModule = new ContainerModule((options) => {\n const { bind } = options;\n\n // Bind HeaderMethods as singleton (stateless utility, can be shared)\n bind<HeaderMethods>(HeaderMethods).toSelf().inSingletonScope();\n\n // Create extension with core headers\n const coreExtension = new PlatformHeadersExtension(WebpiecesCoreHeaders.getAllHeaders());\n\n // Bind extension for multiInject collection\n // WebpiecesMiddleware will collect all PlatformHeadersExtension bindings via @multiInject\n bind<PlatformHeadersExtension>(HEADER_TYPES.PlatformHeadersExtension).toConstantValue(coreExtension);\n\n // HeaderRegistry: the single source of truth for all platform headers.\n // Collects EVERY module's PlatformHeadersExtension at first resolution and\n // fail-fast validates duplicates/conflicts (port of Java HeaderTranslation).\n bind<HeaderRegistry>(HeaderRegistry)\n .toDynamicValue((ctx: ResolutionContext) => {\n const extensions = ctx.getAll<PlatformHeadersExtension>(HEADER_TYPES.PlatformHeadersExtension);\n return new HeaderRegistry(extensions);\n })\n .inSingletonScope();\n\n log.info(`[WebpiecesModule] Registered core platform headers extension with ${coreExtension.headers.length} headers`);\n});\n"]}
|
|
@@ -88,7 +88,7 @@ class SpecGenerator {
|
|
|
88
88
|
return `import 'reflect-metadata';
|
|
89
89
|
import * as fs from 'fs';
|
|
90
90
|
import * as path from 'path';
|
|
91
|
-
import { RecordedTestCase } from '@webpieces/
|
|
91
|
+
import { RecordedTestCase } from '@webpieces/core-util';
|
|
92
92
|
// TODO(generated): import your ServerMeta, the api class, DI tokens, and the
|
|
93
93
|
// downstream api types, then wire the appOverrides ContainerModule below.
|
|
94
94
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SpecGenerator.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/recorder/SpecGenerator.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;GAOG;AACH,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6CzB,CAAC;AAEH,MAAa,aAAa;IACtB,QAAQ,CAAC,QAA0B,EAAE,eAAuB;QACxD,MAAM,QAAQ,GAAG,QAAQ,CAAC,cAAc,CAAC;QACzC,MAAM,cAAc,GAAG,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;QAErE,MAAM,SAAS,GAAG,cAAc;aAC3B,GAAG,CAAC,CAAC,GAAW,EAAE,EAAE,CAAC,iBAAiB,GAAG,iBAAiB,GAAG,MAAM,GAAG,KAAK,CAAC;aAC5E,IAAI,CAAC,IAAI,CAAC,CAAC;QAChB,MAAM,UAAU,GAAG,QAAQ,CAAC,eAAe;aACtC,GAAG,CAAC,CAAC,IAAsB,EAAE,CAAS,EAAE,EAAE,CACvC,IAAI,CAAC,eAAe;YAChB,CAAC,CAAC,WAAW,IAAI,CAAC,OAAO,8BAA8B,IAAI,CAAC,UAAU,8CAA8C,CAAC,+BAA+B;YACpJ,CAAC,CAAC,WAAW,IAAI,CAAC,OAAO,2BAA2B,IAAI,CAAC,UAAU,8BAA8B,CAAC,qBAAqB,CAAC;aAC/H,IAAI,CAAC,IAAI,CAAC,CAAC;QAChB,MAAM,WAAW,GAAG,QAAQ,CAAC,eAAe;aACvC,GAAG,CAAC,CAAC,IAAsB,EAAE,CAAS,EAAE,EAAE,CACvC,kBAAkB,IAAI,CAAC,OAAO,+BAA+B,IAAI,CAAC,UAAU,0CAA0C,CAAC,aAAa,CAAC;aACxI,IAAI,CAAC,IAAI,CAAC,CAAC;QAEhB,OAAO;;;;;;;EAOb,kBAAkB;;YAER,QAAQ,CAAC,OAAO,IAAI,QAAQ,CAAC,UAAU,cAAc,QAAQ,CAAC,UAAU;;gDAEpC,eAAe;;;;EAI7D,SAAS;EACT,UAAU;;;;;;gDAMoC,QAAQ,CAAC,OAAO;wCACxB,QAAQ,CAAC,UAAU;;EAEzD,WAAW;;;CAGZ,CAAC;IACE,CAAC;IAEO,cAAc,CAAC,KAAyB;QAC5C,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACvB,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;gBAChC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC7B,CAAC;QACL,CAAC;QACD,OAAO,KAAK,CAAC;IACjB,CAAC;CACJ;AA3DD,sCA2DC","sourcesContent":["import { RecordedTestCase, RecordedEndpoint } from '@webpieces/
|
|
1
|
+
{"version":3,"file":"SpecGenerator.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/recorder/SpecGenerator.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;GAOG;AACH,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6CzB,CAAC;AAEH,MAAa,aAAa;IACtB,QAAQ,CAAC,QAA0B,EAAE,eAAuB;QACxD,MAAM,QAAQ,GAAG,QAAQ,CAAC,cAAc,CAAC;QACzC,MAAM,cAAc,GAAG,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;QAErE,MAAM,SAAS,GAAG,cAAc;aAC3B,GAAG,CAAC,CAAC,GAAW,EAAE,EAAE,CAAC,iBAAiB,GAAG,iBAAiB,GAAG,MAAM,GAAG,KAAK,CAAC;aAC5E,IAAI,CAAC,IAAI,CAAC,CAAC;QAChB,MAAM,UAAU,GAAG,QAAQ,CAAC,eAAe;aACtC,GAAG,CAAC,CAAC,IAAsB,EAAE,CAAS,EAAE,EAAE,CACvC,IAAI,CAAC,eAAe;YAChB,CAAC,CAAC,WAAW,IAAI,CAAC,OAAO,8BAA8B,IAAI,CAAC,UAAU,8CAA8C,CAAC,+BAA+B;YACpJ,CAAC,CAAC,WAAW,IAAI,CAAC,OAAO,2BAA2B,IAAI,CAAC,UAAU,8BAA8B,CAAC,qBAAqB,CAAC;aAC/H,IAAI,CAAC,IAAI,CAAC,CAAC;QAChB,MAAM,WAAW,GAAG,QAAQ,CAAC,eAAe;aACvC,GAAG,CAAC,CAAC,IAAsB,EAAE,CAAS,EAAE,EAAE,CACvC,kBAAkB,IAAI,CAAC,OAAO,+BAA+B,IAAI,CAAC,UAAU,0CAA0C,CAAC,aAAa,CAAC;aACxI,IAAI,CAAC,IAAI,CAAC,CAAC;QAEhB,OAAO;;;;;;;EAOb,kBAAkB;;YAER,QAAQ,CAAC,OAAO,IAAI,QAAQ,CAAC,UAAU,cAAc,QAAQ,CAAC,UAAU;;gDAEpC,eAAe;;;;EAI7D,SAAS;EACT,UAAU;;;;;;gDAMoC,QAAQ,CAAC,OAAO;wCACxB,QAAQ,CAAC,UAAU;;EAEzD,WAAW;;;CAGZ,CAAC;IACE,CAAC;IAEO,cAAc,CAAC,KAAyB;QAC5C,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACvB,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;gBAChC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC7B,CAAC;QACL,CAAC;QACD,OAAO,KAAK,CAAC;IACjB,CAAC;CACJ;AA3DD,sCA2DC","sourcesContent":["import { RecordedTestCase, RecordedEndpoint } from '@webpieces/core-util';\n\n/**\n * SpecGenerator - Deterministic template that turns a RecordedTestCase into a\n * small vitest spec (~30 lines) which loads the fixture JSON, primes an inline\n * createMock per downstream api, invokes the endpoint in-process, and deep-equal\n * asserts the response + the requests each mock received.\n *\n * The mock test-double is emitted inline into the generated spec (see\n * INLINE_MOCK_SOURCE) so the generated file is fully self-contained and does not\n * depend on any external mock package.\n *\n * Deliberately NO deep reflection over DTOs (the fragile part of the Java\n * TestCaseRecorderImpl codegen) - all data lives in the fixture; the spec is\n * pure plumbing. The fixture is also a stable artifact for an AI to write a\n * richer spec from.\n */\n\n/**\n * Self-contained recording test-double emitted into every generated spec. Mirrors\n * the small slice of the former @webpieces/core-mock API the template relies on:\n * createMock<T>(name) -> T & { mock: MockControls }\n * .mock.addValueToReturn(method, value) queue a return value (FIFO)\n * .mock.addExceptionToThrow(method, ()=>Err) queue an exception (FIFO)\n * .mock.getSingleRequestList(method) first argument of each recorded call\n */\nconst INLINE_MOCK_SOURCE = `type MockControls = {\n addValueToReturn(method: string, value: unknown): void;\n addExceptionToThrow(method: string, errorFactory: () => Error): void;\n getSingleRequestList(method: string): unknown[];\n};\n\nfunction createMock<T>(_name: string): T & { mock: MockControls } {\n const returns = new Map<string, unknown[]>();\n const throwers = new Map<string, Array<() => Error>>();\n const requests = new Map<string, unknown[]>();\n const controls: MockControls = {\n addValueToReturn(method, value) {\n const queue = returns.get(method) ?? [];\n queue.push(value);\n returns.set(method, queue);\n },\n addExceptionToThrow(method, errorFactory) {\n const queue = throwers.get(method) ?? [];\n queue.push(errorFactory);\n throwers.set(method, queue);\n },\n getSingleRequestList(method) {\n return requests.get(method) ?? [];\n },\n };\n return new Proxy({} as Record<string, unknown>, {\n get(_target, prop: string | symbol): unknown {\n if (prop === 'mock') return controls;\n const method = String(prop);\n return (...args: unknown[]): unknown => {\n const recorded = requests.get(method) ?? [];\n recorded.push(args[0]);\n requests.set(method, recorded);\n const throwQueue = throwers.get(method);\n if (throwQueue && throwQueue.length > 0) {\n throw throwQueue.shift()!();\n }\n const returnQueue = returns.get(method);\n if (returnQueue && returnQueue.length > 0) {\n return returnQueue.shift();\n }\n return undefined;\n };\n },\n }) as T & { mock: MockControls };\n}`;\n\nexport class SpecGenerator {\n generate(testCase: RecordedTestCase, fixtureFileName: string): string {\n const endpoint = testCase.serverEndpoint;\n const downstreamApis = this.uniqueApiNames(testCase.downstreamCalls);\n\n const mockDecls = downstreamApis\n .map((api: string) => ` const mock${api} = createMock<${api}>('${api}');`)\n .join('\\n');\n const mockPrimes = testCase.downstreamCalls\n .map((call: RecordedEndpoint, i: number) =>\n call.failureResponse\n ? ` mock${call.apiName}.mock.addExceptionToThrow('${call.methodName}', () => new Error(fixture.downstreamCalls[${i}].failureResponse!.message));`\n : ` mock${call.apiName}.mock.addValueToReturn('${call.methodName}', fixture.downstreamCalls[${i}].successResponse);`)\n .join('\\n');\n const mockAsserts = testCase.downstreamCalls\n .map((call: RecordedEndpoint, i: number) =>\n ` expect(mock${call.apiName}.mock.getSingleRequestList('${call.methodName}')[0]).toEqual(fixture.downstreamCalls[${i}].args[0]);`)\n .join('\\n');\n\n return `import 'reflect-metadata';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { RecordedTestCase } from '@webpieces/core-util';\n// TODO(generated): import your ServerMeta, the api class, DI tokens, and the\n// downstream api types, then wire the appOverrides ContainerModule below.\n\n${INLINE_MOCK_SOURCE}\n\ndescribe('${endpoint.apiName}.${endpoint.methodName} (recorded ${testCase.recordedAt})', () => {\n const fixture: RecordedTestCase = JSON.parse(\n fs.readFileSync(path.join(__dirname, '${fixtureFileName}'), 'utf-8'),\n );\n\n it('replays the recorded request against mocked downstream apis', async () => {\n${mockDecls}\n${mockPrimes}\n // TODO(generated): boot the server with the mocks rebound, e.g.:\n // const overrides = new ContainerModule(async (options) => {\n // (await options.rebind(TYPES.RemoteApi)).toConstantValue(mockRemoteApi);\n // });\n // const server = await WebpiecesFactory.create(new ProdServerMeta(), new WebpiecesConfig(), overrides);\n // const api = server.createApiClient(${endpoint.apiName});\n // const response = await api.${endpoint.methodName}(fixture.serverEndpoint.args[0]);\n // expect(response).toEqual(fixture.serverEndpoint.successResponse);\n${mockAsserts}\n });\n});\n`;\n }\n\n private uniqueApiNames(calls: RecordedEndpoint[]): string[] {\n const names: string[] = [];\n for (const call of calls) {\n if (!names.includes(call.apiName)) {\n names.push(call.apiName);\n }\n }\n return names;\n }\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RecordedEndpoint, RecordedTestCase, TestCaseRecorder } from '@webpieces/
|
|
1
|
+
import { RecordedEndpoint, RecordedTestCase, TestCaseRecorder } from '@webpieces/core-util';
|
|
2
2
|
export declare class TestCaseRecorderImpl implements TestCaseRecorder {
|
|
3
3
|
private downstreamCalls;
|
|
4
4
|
private serializer;
|
|
@@ -4,10 +4,10 @@ exports.TestCaseRecorderImpl = void 0;
|
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const fs = tslib_1.__importStar(require("fs"));
|
|
6
6
|
const path = tslib_1.__importStar(require("path"));
|
|
7
|
-
const http_api_1 = require("@webpieces/http-api");
|
|
8
7
|
const core_util_1 = require("@webpieces/core-util");
|
|
8
|
+
const core_util_2 = require("@webpieces/core-util");
|
|
9
9
|
const SpecGenerator_1 = require("./SpecGenerator");
|
|
10
|
-
const
|
|
10
|
+
const core_util_3 = require("@webpieces/core-util");
|
|
11
11
|
/**
|
|
12
12
|
* TestCaseRecorderImpl - Server-side recorder (port of Java
|
|
13
13
|
* TestCaseRecorderImpl, minus the fragile bean-reflection codegen).
|
|
@@ -24,10 +24,10 @@ const wp_logging_1 = require("@webpieces/wp-logging");
|
|
|
24
24
|
* Both are always logged; written to recordingDir when configured.
|
|
25
25
|
* NEVER breaks production - the whole body is caught and logged.
|
|
26
26
|
*/
|
|
27
|
-
const log =
|
|
27
|
+
const log = core_util_3.LogManager.getLogger('TestCaseRecorder');
|
|
28
28
|
class TestCaseRecorderImpl {
|
|
29
29
|
downstreamCalls = [];
|
|
30
|
-
serializer = new
|
|
30
|
+
serializer = new core_util_1.RecordSerializer();
|
|
31
31
|
specGenerator = new SpecGenerator_1.SpecGenerator();
|
|
32
32
|
addEndpointInfo(info) {
|
|
33
33
|
this.downstreamCalls.push(info);
|
|
@@ -45,7 +45,7 @@ class TestCaseRecorderImpl {
|
|
|
45
45
|
spitOutTestCase(serverEndpoint, recordingDir) {
|
|
46
46
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- recording must NEVER break production requests
|
|
47
47
|
try {
|
|
48
|
-
const testCase = new
|
|
48
|
+
const testCase = new core_util_1.RecordedTestCase(serverEndpoint, this.downstreamCalls, new Date().toISOString());
|
|
49
49
|
const fixtureJson = this.serializer.serialize(testCase);
|
|
50
50
|
const baseName = this.buildBaseName(serverEndpoint, testCase.recordedAt);
|
|
51
51
|
const specSource = this.specGenerator.generate(testCase, `${baseName}.fixture.json`);
|
|
@@ -62,8 +62,8 @@ class TestCaseRecorderImpl {
|
|
|
62
62
|
return testCase;
|
|
63
63
|
}
|
|
64
64
|
catch (err) {
|
|
65
|
-
const error = (0,
|
|
66
|
-
log.error('[TestCaseRecorder] Failed to emit test case (request unaffected)
|
|
65
|
+
const error = (0, core_util_2.toError)(err);
|
|
66
|
+
log.error('[TestCaseRecorder] Failed to emit test case (request unaffected)', error);
|
|
67
67
|
return undefined;
|
|
68
68
|
}
|
|
69
69
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TestCaseRecorderImpl.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/recorder/TestCaseRecorderImpl.ts"],"names":[],"mappings":";;;;AAAA,+CAAyB;AACzB,mDAA6B;AAC7B,
|
|
1
|
+
{"version":3,"file":"TestCaseRecorderImpl.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/recorder/TestCaseRecorderImpl.ts"],"names":[],"mappings":";;;;AAAA,+CAAyB;AACzB,mDAA6B;AAC7B,oDAK8B;AAC9B,oDAA+C;AAC/C,mDAAgD;AAChD,oDAAkD;AAElD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,GAAG,GAAG,sBAAU,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAC;AAErD,MAAa,oBAAoB;IACrB,eAAe,GAAuB,EAAE,CAAC;IACzC,UAAU,GAAG,IAAI,4BAAgB,EAAE,CAAC;IACpC,aAAa,GAAG,IAAI,6BAAa,EAAE,CAAC;IAE5C,eAAe,CAAC,IAAsB;QAClC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;IAED,mBAAmB;QACf,OAAO,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACjE,CAAC;IAED;;;;;;OAMG;IACH,eAAe,CAAC,cAAgC,EAAE,YAAqB;QACnE,gHAAgH;QAChH,IAAI,CAAC;YACD,MAAM,QAAQ,GAAG,IAAI,4BAAgB,CAAC,cAAc,EAAE,IAAI,CAAC,eAAe,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;YACtG,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;YAExD,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,cAAc,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;YACzE,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,QAAQ,EAAE,GAAG,QAAQ,eAAe,CAAC,CAAC;YAErF,GAAG,CAAC,IAAI,CAAC,+BAA+B,cAAc,CAAC,OAAO,IAAI,cAAc,CAAC,UAAU,GAAG;gBAC1F,IAAI,IAAI,CAAC,eAAe,CAAC,MAAM,sBAAsB;gBACrD,gBAAgB,QAAQ,uBAAuB,WAAW,IAAI;gBAC9D,uBAAuB,QAAQ,kBAAkB,UAAU,EAAE,CAAC,CAAC;YAEnE,IAAI,YAAY,EAAE,CAAC;gBACf,EAAE,CAAC,SAAS,CAAC,YAAY,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;gBAChD,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,GAAG,QAAQ,eAAe,CAAC,EAAE,WAAW,CAAC,CAAC;gBACnF,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,GAAG,QAAQ,UAAU,CAAC,EAAE,UAAU,CAAC,CAAC;gBAC7E,GAAG,CAAC,IAAI,CAAC,8CAA8C,YAAY,IAAI,QAAQ,IAAI,CAAC,CAAC;YACzF,CAAC;YAED,OAAO,QAAQ,CAAC;QACpB,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAA,mBAAO,EAAC,GAAG,CAAC,CAAC;YAC3B,GAAG,CAAC,KAAK,CAAC,kEAAkE,EAAE,KAAK,CAAC,CAAC;YACrF,OAAO,SAAS,CAAC;QACrB,CAAC;IACL,CAAC;IAEO,aAAa,CAAC,cAAgC,EAAE,UAAkB;QACtE,oEAAoE;QACpE,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACjE,OAAO,GAAG,cAAc,CAAC,OAAO,IAAI,cAAc,CAAC,UAAU,IAAI,KAAK,EAAE,CAAC;IAC7E,CAAC;CACJ;AAtDD,oDAsDC","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport {\n RecordedEndpoint,\n RecordedTestCase,\n RecordSerializer,\n TestCaseRecorder,\n} from '@webpieces/core-util';\nimport { toError } from '@webpieces/core-util';\nimport { SpecGenerator } from './SpecGenerator';\nimport { LogManager } from '@webpieces/core-util';\n\n/**\n * TestCaseRecorderImpl - Server-side recorder (port of Java\n * TestCaseRecorderImpl, minus the fragile bean-reflection codegen).\n *\n * One instance per recorded request (created by RecordingFilter and placed in\n * the RequestContext under RecorderKeys.RECORDER). Downstream hooks - the\n * http-client proxy and recordable() wrappers - add every call they make.\n *\n * spitOutTestCase() emits:\n * - a diffable JSON FIXTURE (the stable artifact: request, ctx snapshot,\n * response, all downstream calls) - also perfect input for an AI to write\n * a richer spec from\n * - a small deterministic .spec.ts from SpecGenerator\n * Both are always logged; written to recordingDir when configured.\n * NEVER breaks production - the whole body is caught and logged.\n */\nconst log = LogManager.getLogger('TestCaseRecorder');\n\nexport class TestCaseRecorderImpl implements TestCaseRecorder {\n private downstreamCalls: RecordedEndpoint[] = [];\n private serializer = new RecordSerializer();\n private specGenerator = new SpecGenerator();\n\n addEndpointInfo(info: RecordedEndpoint): void {\n this.downstreamCalls.push(info);\n }\n\n getLastEndpointInfo(): RecordedEndpoint | undefined {\n return this.downstreamCalls[this.downstreamCalls.length - 1];\n }\n\n /**\n * Emit the recorded test case. Called by RecordingFilter in its finally.\n *\n * @param serverEndpoint - The inbound endpoint capture (request + response)\n * @param recordingDir - Directory to write fixture + spec files (optional)\n * @returns The built RecordedTestCase, or undefined if emission failed\n */\n spitOutTestCase(serverEndpoint: RecordedEndpoint, recordingDir?: string): RecordedTestCase | undefined {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- recording must NEVER break production requests\n try {\n const testCase = new RecordedTestCase(serverEndpoint, this.downstreamCalls, new Date().toISOString());\n const fixtureJson = this.serializer.serialize(testCase);\n\n const baseName = this.buildBaseName(serverEndpoint, testCase.recordedAt);\n const specSource = this.specGenerator.generate(testCase, `${baseName}.fixture.json`);\n\n log.info(`[TestCaseRecorder] Recorded ${serverEndpoint.apiName}.${serverEndpoint.methodName} ` +\n `(${this.downstreamCalls.length} downstream calls)\\n` +\n `--- fixture (${baseName}.fixture.json) ---\\n${fixtureJson}\\n` +\n `--- generated spec (${baseName}.spec.ts) ---\\n${specSource}`);\n\n if (recordingDir) {\n fs.mkdirSync(recordingDir, { recursive: true });\n fs.writeFileSync(path.join(recordingDir, `${baseName}.fixture.json`), fixtureJson);\n fs.writeFileSync(path.join(recordingDir, `${baseName}.spec.ts`), specSource);\n log.info(`[TestCaseRecorder] Wrote fixture + spec to ${recordingDir}/${baseName}.*`);\n }\n\n return testCase;\n } catch (err: unknown) {\n const error = toError(err);\n log.error('[TestCaseRecorder] Failed to emit test case (request unaffected)', error);\n return undefined;\n }\n }\n\n private buildBaseName(serverEndpoint: RecordedEndpoint, recordedAt: string): string {\n // 2026-07-04T10:22:33.123Z -> 2026-07-04T10-22-33 (filesystem-safe)\n const stamp = recordedAt.replace(/:/g, '-').replace(/\\..*$/, '');\n return `${serverEndpoint.apiName}.${serverEndpoint.methodName}.${stamp}`;\n }\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.recordable = recordable;
|
|
4
|
-
const
|
|
4
|
+
const core_util_1 = require("@webpieces/core-util");
|
|
5
5
|
const core_context_1 = require("@webpieces/core-context");
|
|
6
6
|
/**
|
|
7
7
|
* recordable - Wrap an IN-PROCESS api implementation (simulator, local impl)
|
|
@@ -28,11 +28,11 @@ function recordable(apiName, impl) {
|
|
|
28
28
|
}
|
|
29
29
|
// webpieces-disable no-any-unknown -- api method args/returns are type-erased in the proxy
|
|
30
30
|
return async (...args) => {
|
|
31
|
-
const recorder = core_context_1.RequestContext.getHeader(
|
|
31
|
+
const recorder = core_context_1.RequestContext.getHeader(core_util_1.RecorderKeys.RECORDER);
|
|
32
32
|
if (!recorder) {
|
|
33
33
|
return await original.apply(target, args);
|
|
34
34
|
}
|
|
35
|
-
const recorded = new
|
|
35
|
+
const recorded = new core_util_1.RecordedEndpoint(apiName, prop, args);
|
|
36
36
|
recorder.addEndpointInfo(recorded);
|
|
37
37
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- capture failure into the recording, then rethrow unchanged
|
|
38
38
|
try {
|
|
@@ -41,8 +41,8 @@ function recordable(apiName, impl) {
|
|
|
41
41
|
return response;
|
|
42
42
|
}
|
|
43
43
|
catch (err) {
|
|
44
|
-
const error = (0,
|
|
45
|
-
recorded.failureResponse = new
|
|
44
|
+
const error = (0, core_util_1.toError)(err);
|
|
45
|
+
recorded.failureResponse = new core_util_1.RecordedError(error.name, error.message);
|
|
46
46
|
throw err;
|
|
47
47
|
}
|
|
48
48
|
};
|