90dc-core 1.16.14 → 1.16.16
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/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -11
- package/dist/index.js.map +1 -1
- package/dist/lib/middlewares/ErrorMiddleware.d.ts.map +1 -1
- package/dist/lib/middlewares/ErrorMiddleware.js +25 -10
- package/dist/lib/middlewares/ErrorMiddleware.js.map +1 -1
- package/dist/lib/testing/testHelpers.d.ts +0 -126
- package/dist/lib/testing/testHelpers.d.ts.map +1 -1
- package/dist/lib/testing/testHelpers.js +0 -161
- package/dist/lib/testing/testHelpers.js.map +1 -1
- package/dist/lib/utils/SentryUtil.js +1 -1
- package/dist/lib/utils/SentryUtil.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export { NotificationClient } from "./lib/utils/NotificationClient.js";
|
|
|
15
15
|
export { Log } from "./lib/utils/Logger.js";
|
|
16
16
|
export { SecretManager } from "./lib/utils/SecretManager.js";
|
|
17
17
|
export { initializeSentry, isSentryEnabled, scrubObject, captureRequestBody, extractUserContext, buildRequestContext, buildResponseContext, reportErrorToSentry, reportMessageToSentry, type ErrorReportOptions, type SeverityLevel, } from "./lib/utils/SentryUtil.js";
|
|
18
|
-
export { createMockContext, createMockUser, createAuthenticatedContext, mockDatabase,
|
|
18
|
+
export { createMockContext, createMockUser, createAuthenticatedContext, mockDatabase, flushPromises, assertions, executeRoute, type BaseContext, type AuthenticatedContext, type RouterContext, type RouterMiddleware, type RouterLayer, type RouterLike, type SequelizeModelMethods, } from "./lib/testing/testHelpers.js";
|
|
19
19
|
export { ConfigValidator, BaseConfigSchema, CommonSchemas, createConfig, ConfigurationError, type BaseConfig } from "./lib/config/ConfigValidator.js";
|
|
20
20
|
export { AppError, ValidationError, AuthenticationError, ForbiddenError, NotFoundError, ConflictError, UnprocessableEntityError, RateLimitError, InternalServerError, ServiceUnavailableError, DatabaseError, ExternalAPIError, isAppError, isOperationalError, toAppError } from "./lib/Errors/AppError.js";
|
|
21
21
|
export { ErrorMiddleware, } from "./lib/middlewares/ErrorMiddleware.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,mCAAmC,CAAC;AAClD,cAAc,oCAAoC,CAAC;AACnD,cAAc,mCAAmC,CAAC;AAClD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,wCAAwC,CAAC;AAGvD,cAAc,yBAAyB,CAAA;AAGvC,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAC1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,yCAAyC,CAAA;AAChF,OAAO,EAAE,8BAA8B,EAAE,MAAM,iDAAiD,CAAA;AAChG,YAAY,EACV,wBAAwB,EACxB,yBAAyB,EACzB,aAAa,GACd,MAAM,8BAA8B,CAAA;AACrC,YAAY,EAAE,mBAAmB,EAAE,MAAM,yCAAyC,CAAA;AAGlF,OAAO,EAAC,kBAAkB,EAAC,MAAM,mCAAmC,CAAA;AACpE,OAAO,EAAC,iBAAiB,EAAC,MAAM,kCAAkC,CAAA;AAClE,OAAO,EAAC,kBAAkB,EAAC,MAAM,mCAAmC,CAAA;AACpE,OAAO,EAAC,GAAG,EAAC,MAAM,uBAAuB,CAAA;AACzC,OAAO,EAAC,aAAa,EAAC,MAAM,8BAA8B,CAAA;AAC1D,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,WAAW,EACX,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,qBAAqB,EACrB,KAAK,kBAAkB,EACvB,KAAK,aAAa,GACnB,MAAM,2BAA2B,CAAA;AAGlC,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,mCAAmC,CAAC;AAClD,cAAc,oCAAoC,CAAC;AACnD,cAAc,mCAAmC,CAAC;AAClD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,wCAAwC,CAAC;AAGvD,cAAc,yBAAyB,CAAA;AAGvC,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAC1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,yCAAyC,CAAA;AAChF,OAAO,EAAE,8BAA8B,EAAE,MAAM,iDAAiD,CAAA;AAChG,YAAY,EACV,wBAAwB,EACxB,yBAAyB,EACzB,aAAa,GACd,MAAM,8BAA8B,CAAA;AACrC,YAAY,EAAE,mBAAmB,EAAE,MAAM,yCAAyC,CAAA;AAGlF,OAAO,EAAC,kBAAkB,EAAC,MAAM,mCAAmC,CAAA;AACpE,OAAO,EAAC,iBAAiB,EAAC,MAAM,kCAAkC,CAAA;AAClE,OAAO,EAAC,kBAAkB,EAAC,MAAM,mCAAmC,CAAA;AACpE,OAAO,EAAC,GAAG,EAAC,MAAM,uBAAuB,CAAA;AACzC,OAAO,EAAC,aAAa,EAAC,MAAM,8BAA8B,CAAA;AAC1D,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,WAAW,EACX,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,qBAAqB,EACrB,KAAK,kBAAkB,EACvB,KAAK,aAAa,GACnB,MAAM,2BAA2B,CAAA;AAGlC,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,0BAA0B,EAC1B,YAAY,EACZ,aAAa,EACb,UAAU,EACV,YAAY,EACZ,KAAK,WAAW,EAChB,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,qBAAqB,GAC3B,MAAM,8BAA8B,CAAA;AAGrC,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,kBAAkB,EAClB,KAAK,UAAU,EAChB,MAAM,iCAAiC,CAAA;AAGxC,OAAO,EACL,QAAQ,EACR,eAAe,EACf,mBAAmB,EACnB,cAAc,EACd,aAAa,EACb,aAAa,EACb,wBAAwB,EACxB,cAAc,EACd,mBAAmB,EACnB,uBAAuB,EACvB,aAAa,EACb,gBAAgB,EAChB,UAAU,EACV,kBAAkB,EAClB,UAAU,EACX,MAAM,0BAA0B,CAAA;AAGjC,OAAO,EACL,eAAe,GAChB,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EAAE,QAAQ,EAAE,KAAK,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAG5F,OAAO,EAAE,cAAc,EAAE,KAAK,WAAW,EAAE,MAAM,qCAAqC,CAAC;AAGvF,OAAO,EAAC,WAAW,EAAC,MAAM,wBAAwB,CAAA;AAClD,OAAO,EAAC,cAAc,EAAE,KAAK,cAAc,EAAC,MAAM,2BAA2B,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -18,17 +18,7 @@ export { Log } from "./lib/utils/Logger.js";
|
|
|
18
18
|
export { SecretManager } from "./lib/utils/SecretManager.js";
|
|
19
19
|
export { initializeSentry, isSentryEnabled, scrubObject, captureRequestBody, extractUserContext, buildRequestContext, buildResponseContext, reportErrorToSentry, reportMessageToSentry } from "./lib/utils/SentryUtil.js";
|
|
20
20
|
//Testing Utilities
|
|
21
|
-
export {
|
|
22
|
-
createMockContext, createMockUser, createAuthenticatedContext, // Database mocks
|
|
23
|
-
mockDatabase, setupDatabaseMocks, // Async helpers
|
|
24
|
-
flushPromises, // Assertions
|
|
25
|
-
assertions, // Route execution
|
|
26
|
-
executeRoute, // Factory helpers
|
|
27
|
-
createMockFile, createMockFiles, createMockTransaction, // Time mocking
|
|
28
|
-
setupTimeMocks, // Mock errors
|
|
29
|
-
MockValidationError, MockNotFoundError, MockDatabaseError, // Typed mocking helpers
|
|
30
|
-
createTypedMock, spyOnPrototype, // Constants
|
|
31
|
-
TEST_CONSTANTS } from "./lib/testing/testHelpers.js";
|
|
21
|
+
export { createMockContext, createMockUser, createAuthenticatedContext, mockDatabase, flushPromises, assertions, executeRoute } from "./lib/testing/testHelpers.js";
|
|
32
22
|
//Config
|
|
33
23
|
export { ConfigValidator, BaseConfigSchema, CommonSchemas, createConfig, ConfigurationError } from "./lib/config/ConfigValidator.js";
|
|
34
24
|
//Errors
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["//Interfaces\nexport * from \"./lib/models/ProgramInterfaces.js\";\nexport * from \"./lib/models/ExerciseInterfaces.js\";\nexport * from \"./lib/models/WorkoutInterfaces.js\";\nexport * from \"./lib/models/UserInterfaces.js\";\nexport * from \"./lib/models/NotificationInterfaces.js\";\n\n//DB Models - Export all models and model arrays\nexport * from \"./lib/dbmodels/index.js\"\n\n//Clients\nexport { EmailClient } from \"./lib/clients/EmailClient.js\"\nexport { PushNotificationClient } from \"./lib/clients/PushNotificationClient.js\"\nexport { FirebasePushNotificationClient } from \"./lib/clients/FirebasePushNotificationClient.js\"\nexport type {\n SendTemplateEmailRequest,\n BatchTemplateEmailRequest,\n EmailResponse,\n} from \"./lib/clients/EmailClient.js\"\nexport type { NotificationPayload } from \"./lib/clients/PushNotificationClient.js\"\n\n//Utils\nexport {AuthenticationUtil} from \"./lib/utils/AuthenticationUtil.js\"\nexport {NotificationsUtil} from \"./lib/utils/NotificationsUtil.js\"\nexport {NotificationClient} from \"./lib/utils/NotificationClient.js\"\nexport {Log} from \"./lib/utils/Logger.js\"\nexport {SecretManager} from \"./lib/utils/SecretManager.js\"\nexport {\n initializeSentry,\n isSentryEnabled,\n scrubObject,\n captureRequestBody,\n extractUserContext,\n buildRequestContext,\n buildResponseContext,\n reportErrorToSentry,\n reportMessageToSentry,\n type ErrorReportOptions,\n type SeverityLevel,\n} from \"./lib/utils/SentryUtil.js\"\n\n//Testing Utilities\nexport {\n
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["//Interfaces\nexport * from \"./lib/models/ProgramInterfaces.js\";\nexport * from \"./lib/models/ExerciseInterfaces.js\";\nexport * from \"./lib/models/WorkoutInterfaces.js\";\nexport * from \"./lib/models/UserInterfaces.js\";\nexport * from \"./lib/models/NotificationInterfaces.js\";\n\n//DB Models - Export all models and model arrays\nexport * from \"./lib/dbmodels/index.js\"\n\n//Clients\nexport { EmailClient } from \"./lib/clients/EmailClient.js\"\nexport { PushNotificationClient } from \"./lib/clients/PushNotificationClient.js\"\nexport { FirebasePushNotificationClient } from \"./lib/clients/FirebasePushNotificationClient.js\"\nexport type {\n SendTemplateEmailRequest,\n BatchTemplateEmailRequest,\n EmailResponse,\n} from \"./lib/clients/EmailClient.js\"\nexport type { NotificationPayload } from \"./lib/clients/PushNotificationClient.js\"\n\n//Utils\nexport {AuthenticationUtil} from \"./lib/utils/AuthenticationUtil.js\"\nexport {NotificationsUtil} from \"./lib/utils/NotificationsUtil.js\"\nexport {NotificationClient} from \"./lib/utils/NotificationClient.js\"\nexport {Log} from \"./lib/utils/Logger.js\"\nexport {SecretManager} from \"./lib/utils/SecretManager.js\"\nexport {\n initializeSentry,\n isSentryEnabled,\n scrubObject,\n captureRequestBody,\n extractUserContext,\n buildRequestContext,\n buildResponseContext,\n reportErrorToSentry,\n reportMessageToSentry,\n type ErrorReportOptions,\n type SeverityLevel,\n} from \"./lib/utils/SentryUtil.js\"\n\n//Testing Utilities\nexport {\n createMockContext,\n createMockUser,\n createAuthenticatedContext,\n mockDatabase,\n flushPromises,\n assertions,\n executeRoute,\n type BaseContext,\n type AuthenticatedContext,\n type RouterContext,\n type RouterMiddleware,\n type RouterLayer,\n type RouterLike,\n type SequelizeModelMethods,\n} from \"./lib/testing/testHelpers.js\"\n\n//Config\nexport {\n ConfigValidator,\n BaseConfigSchema,\n CommonSchemas,\n createConfig,\n ConfigurationError,\n type BaseConfig\n} from \"./lib/config/ConfigValidator.js\"\n\n//Errors\nexport {\n AppError,\n ValidationError,\n AuthenticationError,\n ForbiddenError,\n NotFoundError,\n ConflictError,\n UnprocessableEntityError,\n RateLimitError,\n InternalServerError,\n ServiceUnavailableError,\n DatabaseError,\n ExternalAPIError,\n isAppError,\n isOperationalError,\n toAppError\n} from \"./lib/Errors/AppError.js\"\n\n//Middlewares\nexport {\n ErrorMiddleware,\n} from \"./lib/middlewares/ErrorMiddleware.js\"\nexport { validate, type ValidationConfig } from \"./lib/middlewares/ValidationMiddleware.js\";\n\n//Controllers\nexport { BaseController, type RouteConfig } from \"./lib/controllers/BaseController.js\";\n\n\nexport {RedisClient} from \"./lib/classes/Redis.js\"\nexport {DatabaseClient, type DatabaseConfig} from \"./lib/classes/Database.js\""],"names":["EmailClient","PushNotificationClient","FirebasePushNotificationClient","AuthenticationUtil","NotificationsUtil","NotificationClient","Log","SecretManager","initializeSentry","isSentryEnabled","scrubObject","captureRequestBody","extractUserContext","buildRequestContext","buildResponseContext","reportErrorToSentry","reportMessageToSentry","createMockContext","createMockUser","createAuthenticatedContext","mockDatabase","flushPromises","assertions","executeRoute","ConfigValidator","BaseConfigSchema","CommonSchemas","createConfig","ConfigurationError","AppError","ValidationError","AuthenticationError","ForbiddenError","NotFoundError","ConflictError","UnprocessableEntityError","RateLimitError","InternalServerError","ServiceUnavailableError","DatabaseError","ExternalAPIError","isAppError","isOperationalError","toAppError","ErrorMiddleware","validate","BaseController","RedisClient","DatabaseClient"],"mappings":"AAAA,YAAY;AACZ,cAAc,oCAAoC;AAClD,cAAc,qCAAqC;AACnD,cAAc,oCAAoC;AAClD,cAAc,iCAAiC;AAC/C,cAAc,yCAAyC;AAEvD,gDAAgD;AAChD,cAAc,iBAAyB;AAEvC,SAAS;AACT,SAASA,WAAW,QAAQ,+BAA8B;AAC1D,SAASC,sBAAsB,QAAQ,0CAAyC;AAChF,SAASC,8BAA8B,QAAQ,kDAAiD;AAQhG,OAAO;AACP,SAAQC,kBAAkB,QAAO,oCAAmC;AACpE,SAAQC,iBAAiB,QAAO,mCAAkC;AAClE,SAAQC,kBAAkB,QAAO,oCAAmC;AACpE,SAAQC,GAAG,QAAO,wBAAuB;AACzC,SAAQC,aAAa,QAAO,+BAA8B;AAC1D,SACEC,gBAAgB,EAChBC,eAAe,EACfC,WAAW,EACXC,kBAAkB,EAClBC,kBAAkB,EAClBC,mBAAmB,EACnBC,oBAAoB,EACpBC,mBAAmB,EACnBC,qBAAqB,QAGhB,4BAA2B;AAElC,mBAAmB;AACnB,SACEC,iBAAiB,EACjBC,cAAc,EACdC,0BAA0B,EAC1BC,YAAY,EACZC,aAAa,EACbC,UAAU,EACVC,YAAY,QAQP,+BAA8B;AAErC,QAAQ;AACR,SACEC,eAAe,EACfC,gBAAgB,EAChBC,aAAa,EACbC,YAAY,EACZC,kBAAkB,QAEb,kCAAiC;AAExC,QAAQ;AACR,SACEC,QAAQ,EACRC,eAAe,EACfC,mBAAmB,EACnBC,cAAc,EACdC,aAAa,EACbC,aAAa,EACbC,wBAAwB,EACxBC,cAAc,EACdC,mBAAmB,EACnBC,uBAAuB,EACvBC,aAAa,EACbC,gBAAgB,EAChBC,UAAU,EACVC,kBAAkB,EAClBC,UAAU,QACL,2BAA0B;AAEjC,aAAa;AACb,SACEC,eAAe,QACV,uCAAsC;AAC7C,SAASC,QAAQ,QAA+B,4CAA4C;AAE5F,aAAa;AACb,SAASC,cAAc,QAA0B,sCAAsC;AAGvF,SAAQC,WAAW,QAAO,yBAAwB;AAClD,SAAQC,cAAc,QAA4B,4BAA2B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ErrorMiddleware.d.ts","sourceRoot":"","sources":["../../../src/lib/middlewares/ErrorMiddleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AACzC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAc,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"ErrorMiddleware.d.ts","sourceRoot":"","sources":["../../../src/lib/middlewares/ErrorMiddleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AACzC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAc,MAAM,uBAAuB,CAAC;AAa7D,MAAM,WAAW,qBAAqB;IACpC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IAC9C,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;IACvD,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,wBAAgB,eAAe,CAAC,MAAM,GAAE,qBAA0B,IAWlD,KAAK,OAAO,EAAE,MAAM,IAAI,mBAmGvC;AA4DD,wBAAgB,eAAe,CAAC,KAAK,EAAE,CAAC,CAAC,QAAQ,GAAG;IAClD,gBAAgB,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACrE,CAQA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { AppError, toAppError } from "../Errors/AppError.js";
|
|
3
|
-
import { initializeSentry, isSentryEnabled, reportErrorToSentry, reportMessageToSentry } from "../utils/SentryUtil.js";
|
|
3
|
+
import { initializeSentry, isSentryEnabled, reportErrorToSentry, reportMessageToSentry, scrubObject, captureRequestBody } from "../utils/SentryUtil.js";
|
|
4
4
|
import { Log } from "../utils/Logger.js";
|
|
5
5
|
const dclogger = Log.getInstance().extend("ErrorMiddleware");
|
|
6
6
|
export function ErrorMiddleware(config = {}) {
|
|
@@ -16,11 +16,13 @@ export function ErrorMiddleware(config = {}) {
|
|
|
16
16
|
const requestContext = {
|
|
17
17
|
method: ctx.method,
|
|
18
18
|
path: ctx.path,
|
|
19
|
-
body: ctx.request.body,
|
|
20
|
-
query: ctx.query,
|
|
21
|
-
headers: ctx.headers,
|
|
19
|
+
body: captureRequestBody(ctx.request.body),
|
|
20
|
+
query: scrubObject(ctx.query),
|
|
21
|
+
headers: scrubObject(ctx.headers),
|
|
22
22
|
userUuid: ctx.state.user?.userUuid,
|
|
23
|
-
validationErrors: validationErrors.validationErrors
|
|
23
|
+
validationErrors: validationErrors.validationErrors,
|
|
24
|
+
traceId: ctx.state.traceId,
|
|
25
|
+
ip: ctx.ip
|
|
24
26
|
};
|
|
25
27
|
// Log validation error with full request context and validation details
|
|
26
28
|
dclogger.error("Validation error", requestContext);
|
|
@@ -36,11 +38,14 @@ export function ErrorMiddleware(config = {}) {
|
|
|
36
38
|
const requestContext = {
|
|
37
39
|
method: ctx.method,
|
|
38
40
|
path: ctx.path,
|
|
39
|
-
body: ctx.request.body,
|
|
40
|
-
query: ctx.query,
|
|
41
|
-
headers: ctx.headers,
|
|
41
|
+
body: captureRequestBody(ctx.request.body),
|
|
42
|
+
query: scrubObject(ctx.query),
|
|
43
|
+
headers: scrubObject(ctx.headers),
|
|
44
|
+
userUuid: ctx.state.user?.userUuid,
|
|
42
45
|
error: err instanceof Error ? err.message : String(err),
|
|
43
|
-
stack: err instanceof Error ? err.stack : undefined
|
|
46
|
+
stack: err instanceof Error ? err.stack : undefined,
|
|
47
|
+
traceId: ctx.state.traceId,
|
|
48
|
+
ip: ctx.ip
|
|
44
49
|
};
|
|
45
50
|
// Log error with full request context
|
|
46
51
|
dclogger.error("Error processing request", requestContext);
|
|
@@ -63,7 +68,17 @@ export function ErrorMiddleware(config = {}) {
|
|
|
63
68
|
ctx.status = appError.statusCode;
|
|
64
69
|
ctx.set("X-Error-Code", appError.code);
|
|
65
70
|
ctx.type = "application/json";
|
|
66
|
-
|
|
71
|
+
const responseBody = formatter ? formatter(appError, ctx) : formatErrorResponse(appError, exposeErrorDetails);
|
|
72
|
+
ctx.body = responseBody;
|
|
73
|
+
// Log response body for debugging (only in non-production or for server errors)
|
|
74
|
+
if (process.env.NODE_ENV !== "production" || appError.statusCode >= 500) {
|
|
75
|
+
dclogger.error("Error response sent", {
|
|
76
|
+
statusCode: appError.statusCode,
|
|
77
|
+
errorCode: appError.code,
|
|
78
|
+
responseBody: scrubObject(responseBody),
|
|
79
|
+
traceId: ctx.state.traceId
|
|
80
|
+
});
|
|
81
|
+
}
|
|
67
82
|
}
|
|
68
83
|
};
|
|
69
84
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/middlewares/ErrorMiddleware.ts"],"sourcesContent":["import type { Context, Next } from \"koa\";\nimport { z } from \"zod\";\nimport { AppError, toAppError } from \"../Errors/AppError.js\";\nimport {\n initializeSentry,\n isSentryEnabled,\n reportErrorToSentry,\n reportMessageToSentry,\n} from \"../utils/SentryUtil.js\";\nimport { Log } from \"../utils/Logger.js\";\n\nconst dclogger = Log.getInstance().extend(\"ErrorMiddleware\");\n\nexport interface ErrorMiddlewareConfig {\n exposeErrorDetails?: boolean;\n logErrors?: boolean;\n logger?: (error: Error, ctx: Context) => void;\n formatter?: (error: AppError, ctx: Context) => unknown;\n disableSentry?: boolean;\n}\n\nexport function ErrorMiddleware(config: ErrorMiddlewareConfig = {}) {\n const {\n exposeErrorDetails = false,\n logErrors = true,\n logger,\n formatter,\n disableSentry = false,\n } = config;\n\n initializeSentry();\n\n return async (ctx: Context, next: Next) => {\n try {\n await next();\n } catch (err) {\n let appError: AppError;\n\n if (err instanceof z.ZodError) {\n const validationErrors = formatZodErrors(err);\n const requestContext = {\n method: ctx.method,\n path: ctx.path,\n body: (ctx.request as any).body,\n query: ctx.query,\n headers: ctx.headers,\n userUuid: ctx.state.user?.userUuid,\n validationErrors: validationErrors.validationErrors,\n };\n\n // Log validation error with full request context and validation details\n dclogger.error(\"Validation error\", requestContext);\n\n // Report to Sentry with full context\n if (!disableSentry && isSentryEnabled()) {\n reportMessageToSentry(\n \"Validation error\",\n \"error\",\n {\n request: requestContext,\n validationErrors,\n }\n );\n }\n\n appError = new AppError(\n \"VALIDATION_ERROR\",\n \"Request validation failed\",\n 400,\n validationErrors\n );\n } else {\n const requestContext = {\n method: ctx.method,\n path: ctx.path,\n body: (ctx.request as any).body,\n query: ctx.query,\n headers: ctx.headers,\n error: err instanceof Error ? err.message : String(err),\n stack: err instanceof Error ? err.stack : undefined,\n };\n\n // Log error with full request context\n dclogger.error(\"Error processing request\", requestContext);\n\n appError = toAppError(err);\n }\n\n if (!disableSentry && isSentryEnabled() && appError.statusCode !== 401) {\n reportErrorToSentry(appError, ctx, {\n operational: appError.isOperational,\n errorType: appError.constructor.name,\n statusCode: appError.statusCode,\n });\n }\n\n if (logErrors) {\n if (logger) {\n logger(appError, ctx);\n } else {\n defaultErrorLogger(appError, ctx);\n }\n }\n\n ctx.status = appError.statusCode;\n ctx.set(\"X-Error-Code\", appError.code);\n ctx.type = \"application/json\";\n\n ctx.body = formatter\n ? formatter(appError, ctx)\n : formatErrorResponse(appError, exposeErrorDetails);\n\n }\n };\n}\n\nfunction defaultErrorLogger(error: AppError, ctx: Context): void {\n const isDev = process.env.NODE_ENV === \"development\";\n\n if (error.statusCode >= 500) {\n if (!isDev) {\n const prodLogData = {\n error: {\n code: error.code,\n message: error.message,\n statusCode: error.statusCode,\n },\n request: {\n path: ctx.path,\n },\n user: {\n uuid: ctx.state.user?.userUuid,\n },\n traceId: ctx.state.traceId,\n };\n console.error(\"Server Error:\", JSON.stringify(prodLogData, null, 2));\n } else {\n console.error(\"Server Error:\", error.stack || error.message);\n }\n } else {\n console.warn(\"Client Error:\", JSON.stringify({\n code: error.code,\n message: error.message,\n statusCode: error.statusCode,\n path: ctx.path,\n }));\n }\n}\n\n\nfunction formatErrorResponse(error: AppError, exposeDetails: boolean): unknown {\n const response: {\n code: string;\n message: string;\n details?: unknown;\n stack?: string;\n } = {\n code: error.code,\n message: error.message,\n };\n\n if (exposeDetails || error.statusCode < 500) {\n if (error.details !== undefined) {\n response.details = error.details;\n }\n }\n\n if (exposeDetails && error.stack) {\n response.stack = error.stack;\n }\n\n return response;\n}\n\nexport function formatZodErrors(error: z.ZodError): {\n validationErrors: { path: string; message: string; code: string }[];\n} {\n return {\n validationErrors: error.issues.map((issue) => ({\n path: issue.path.join(\".\"),\n message: issue.message,\n code: issue.code,\n })),\n };\n}"],"names":["z","AppError","toAppError","initializeSentry","isSentryEnabled","reportErrorToSentry","reportMessageToSentry","Log","dclogger","getInstance","extend","ErrorMiddleware","config","exposeErrorDetails","logErrors","logger","formatter","disableSentry","ctx","next","err","appError","ZodError","validationErrors","formatZodErrors","requestContext","method","path","body","request","query","headers","userUuid","state","user","error","Error","message","String","stack","undefined","statusCode","operational","isOperational","errorType","name","defaultErrorLogger","status","set","code","type","formatErrorResponse","isDev","process","env","NODE_ENV","prodLogData","uuid","traceId","console","JSON","stringify","warn","exposeDetails","response","details","issues","map","issue","join"],"mappings":"AACA,SAASA,CAAC,QAAQ,MAAM;AACxB,SAASC,QAAQ,EAAEC,UAAU,QAAQ,wBAAwB;AAC7D,SACEC,gBAAgB,EAChBC,eAAe,EACfC,mBAAmB,EACnBC,qBAAqB,QAChB,yBAAyB;AAChC,SAASC,GAAG,QAAQ,qBAAqB;AAEzC,MAAMC,WAAWD,IAAIE,WAAW,GAAGC,MAAM,CAAC;AAU1C,OAAO,SAASC,gBAAgBC,SAAgC,CAAC,CAAC;IAChE,MAAM,EACJC,qBAAqB,KAAK,EAC1BC,YAAY,IAAI,EAChBC,MAAM,EACNC,SAAS,EACTC,gBAAgB,KAAK,EACtB,GAAGL;IAEJT;IAEA,OAAO,OAAOe,KAAcC;QAC1B,IAAI;YACF,MAAMA;QACR,EAAE,OAAOC,KAAK;YACZ,IAAIC;YAEJ,IAAID,eAAepB,EAAEsB,QAAQ,EAAE;gBAC7B,MAAMC,mBAAmBC,gBAAgBJ;gBACzC,MAAMK,iBAAiB;oBACrBC,QAAQR,IAAIQ,MAAM;oBAClBC,MAAMT,IAAIS,IAAI;oBACdC,MAAM,AAACV,IAAIW,OAAO,CAASD,IAAI;oBAC/BE,OAAOZ,IAAIY,KAAK;oBAChBC,SAASb,IAAIa,OAAO;oBACpBC,UAAUd,IAAIe,KAAK,CAACC,IAAI,EAAEF;oBAC1BT,kBAAkBA,iBAAiBA,gBAAgB;gBACrD;gBAEA,wEAAwE;gBACxEf,SAAS2B,KAAK,CAAC,oBAAoBV;gBAEnC,qCAAqC;gBACrC,IAAI,CAACR,iBAAiBb,mBAAmB;oBACvCE,sBACE,oBACA,SACA;wBACEuB,SAASJ;wBACTF;oBACF;gBAEJ;gBAEAF,WAAW,IAAIpB,SACX,oBACA,6BACA,KACAsB;YAEN,OAAO;gBACL,MAAME,iBAAiB;oBACrBC,QAAQR,IAAIQ,MAAM;oBAClBC,MAAMT,IAAIS,IAAI;oBACdC,MAAM,AAACV,IAAIW,OAAO,CAASD,IAAI;oBAC/BE,OAAOZ,IAAIY,KAAK;oBAChBC,SAASb,IAAIa,OAAO;oBACpBI,OAAOf,eAAegB,QAAQhB,IAAIiB,OAAO,GAAGC,OAAOlB;oBACnDmB,OAAOnB,eAAegB,QAAQhB,IAAImB,KAAK,GAAGC;gBAC5C;gBAEA,sCAAsC;gBACtChC,SAAS2B,KAAK,CAAC,4BAA4BV;gBAE3CJ,WAAWnB,WAAWkB;YACxB;YAEA,IAAI,CAACH,iBAAiBb,qBAAqBiB,SAASoB,UAAU,KAAK,KAAK;gBACtEpC,oBAAoBgB,UAAUH,KAAK;oBACjCwB,aAAarB,SAASsB,aAAa;oBACnCC,WAAWvB,SAAS,WAAW,CAACwB,IAAI;oBACpCJ,YAAYpB,SAASoB,UAAU;gBACjC;YACF;YAEA,IAAI3B,WAAW;gBACb,IAAIC,QAAQ;oBACVA,OAAOM,UAAUH;gBACnB,OAAO;oBACL4B,mBAAmBzB,UAAUH;gBAC/B;YACF;YAEAA,IAAI6B,MAAM,GAAG1B,SAASoB,UAAU;YAChCvB,IAAI8B,GAAG,CAAC,gBAAgB3B,SAAS4B,IAAI;YACrC/B,IAAIgC,IAAI,GAAG;YAEXhC,IAAIU,IAAI,GAAGZ,YACLA,UAAUK,UAAUH,OACpBiC,oBAAoB9B,UAAUR;QAEtC;IACF;AACF;AAEA,SAASiC,mBAAmBX,KAAe,EAAEjB,GAAY;IACvD,MAAMkC,QAAQC,QAAQC,GAAG,CAACC,QAAQ,KAAK;IAEvC,IAAIpB,MAAMM,UAAU,IAAI,KAAK;QAC3B,IAAI,CAACW,OAAO;YACV,MAAMI,cAAc;gBAClBrB,OAAO;oBACLc,MAAMd,MAAMc,IAAI;oBAChBZ,SAASF,MAAME,OAAO;oBACtBI,YAAYN,MAAMM,UAAU;gBAC9B;gBACAZ,SAAS;oBACPF,MAAMT,IAAIS,IAAI;gBAChB;gBACAO,MAAM;oBACJuB,MAAMvC,IAAIe,KAAK,CAACC,IAAI,EAAEF;gBACxB;gBACA0B,SAASxC,IAAIe,KAAK,CAACyB,OAAO;YAC5B;YACAC,QAAQxB,KAAK,CAAC,iBAAiByB,KAAKC,SAAS,CAACL,aAAa,MAAM;QACnE,OAAO;YACLG,QAAQxB,KAAK,CAAC,iBAAiBA,MAAMI,KAAK,IAAIJ,MAAME,OAAO;QAC7D;IACF,OAAO;QACLsB,QAAQG,IAAI,CAAC,iBAAiBF,KAAKC,SAAS,CAAC;YAC3CZ,MAAMd,MAAMc,IAAI;YAChBZ,SAASF,MAAME,OAAO;YACtBI,YAAYN,MAAMM,UAAU;YAC5Bd,MAAMT,IAAIS,IAAI;QAChB;IACF;AACF;AAGA,SAASwB,oBAAoBhB,KAAe,EAAE4B,aAAsB;IAClE,MAAMC,WAKF;QACAf,MAAMd,MAAMc,IAAI;QAChBZ,SAASF,MAAME,OAAO;IAC1B;IAEA,IAAI0B,iBAAiB5B,MAAMM,UAAU,GAAG,KAAK;QAC3C,IAAIN,MAAM8B,OAAO,KAAKzB,WAAW;YAC/BwB,SAASC,OAAO,GAAG9B,MAAM8B,OAAO;QAClC;IACF;IAEA,IAAIF,iBAAiB5B,MAAMI,KAAK,EAAE;QAChCyB,SAASzB,KAAK,GAAGJ,MAAMI,KAAK;IAC9B;IAEA,OAAOyB;AACT;AAEA,OAAO,SAASxC,gBAAgBW,KAAiB;IAG/C,OAAO;QACLZ,kBAAkBY,MAAM+B,MAAM,CAACC,GAAG,CAAC,CAACC,QAAW,CAAA;gBAC7CzC,MAAMyC,MAAMzC,IAAI,CAAC0C,IAAI,CAAC;gBACtBhC,SAAS+B,MAAM/B,OAAO;gBACtBY,MAAMmB,MAAMnB,IAAI;YAClB,CAAA;IACF;AACF"}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/middlewares/ErrorMiddleware.ts"],"sourcesContent":["import type { Context, Next } from \"koa\";\nimport { z } from \"zod\";\nimport { AppError, toAppError } from \"../Errors/AppError.js\";\nimport {\n initializeSentry,\n isSentryEnabled,\n reportErrorToSentry,\n reportMessageToSentry,\n scrubObject,\n captureRequestBody,\n} from \"../utils/SentryUtil.js\";\nimport { Log } from \"../utils/Logger.js\";\n\nconst dclogger = Log.getInstance().extend(\"ErrorMiddleware\");\n\nexport interface ErrorMiddlewareConfig {\n exposeErrorDetails?: boolean;\n logErrors?: boolean;\n logger?: (error: Error, ctx: Context) => void;\n formatter?: (error: AppError, ctx: Context) => unknown;\n disableSentry?: boolean;\n}\n\nexport function ErrorMiddleware(config: ErrorMiddlewareConfig = {}) {\n const {\n exposeErrorDetails = false,\n logErrors = true,\n logger,\n formatter,\n disableSentry = false,\n } = config;\n\n initializeSentry();\n\n return async (ctx: Context, next: Next) => {\n try {\n await next();\n } catch (err) {\n let appError: AppError;\n\n if (err instanceof z.ZodError) {\n const validationErrors = formatZodErrors(err);\n const requestContext = {\n method: ctx.method,\n path: ctx.path,\n body: captureRequestBody((ctx.request as any).body),\n query: scrubObject(ctx.query),\n headers: scrubObject(ctx.headers),\n userUuid: ctx.state.user?.userUuid,\n validationErrors: validationErrors.validationErrors,\n traceId: ctx.state.traceId,\n ip: ctx.ip,\n };\n\n // Log validation error with full request context and validation details\n dclogger.error(\"Validation error\", requestContext);\n\n // Report to Sentry with full context\n if (!disableSentry && isSentryEnabled()) {\n reportMessageToSentry(\n \"Validation error\",\n \"error\",\n {\n request: requestContext,\n validationErrors,\n }\n );\n }\n\n appError = new AppError(\n \"VALIDATION_ERROR\",\n \"Request validation failed\",\n 400,\n validationErrors\n );\n } else {\n const requestContext = {\n method: ctx.method,\n path: ctx.path,\n body: captureRequestBody((ctx.request as any).body),\n query: scrubObject(ctx.query),\n headers: scrubObject(ctx.headers),\n userUuid: ctx.state.user?.userUuid,\n error: err instanceof Error ? err.message : String(err),\n stack: err instanceof Error ? err.stack : undefined,\n traceId: ctx.state.traceId,\n ip: ctx.ip,\n };\n\n // Log error with full request context\n dclogger.error(\"Error processing request\", requestContext);\n\n appError = toAppError(err);\n }\n\n if (!disableSentry && isSentryEnabled() && appError.statusCode !== 401) {\n reportErrorToSentry(appError, ctx, {\n operational: appError.isOperational,\n errorType: appError.constructor.name,\n statusCode: appError.statusCode,\n });\n }\n\n if (logErrors) {\n if (logger) {\n logger(appError, ctx);\n } else {\n defaultErrorLogger(appError, ctx);\n }\n }\n\n ctx.status = appError.statusCode;\n ctx.set(\"X-Error-Code\", appError.code);\n ctx.type = \"application/json\";\n\n const responseBody = formatter\n ? formatter(appError, ctx)\n : formatErrorResponse(appError, exposeErrorDetails);\n\n ctx.body = responseBody;\n\n // Log response body for debugging (only in non-production or for server errors)\n if (process.env.NODE_ENV !== \"production\" || appError.statusCode >= 500) {\n dclogger.error(\"Error response sent\", {\n statusCode: appError.statusCode,\n errorCode: appError.code,\n responseBody: scrubObject(responseBody),\n traceId: ctx.state.traceId,\n });\n }\n\n }\n };\n}\n\nfunction defaultErrorLogger(error: AppError, ctx: Context): void {\n const isDev = process.env.NODE_ENV === \"development\";\n\n if (error.statusCode >= 500) {\n if (!isDev) {\n const prodLogData = {\n error: {\n code: error.code,\n message: error.message,\n statusCode: error.statusCode,\n },\n request: {\n path: ctx.path,\n },\n user: {\n uuid: ctx.state.user?.userUuid,\n },\n traceId: ctx.state.traceId,\n };\n console.error(\"Server Error:\", JSON.stringify(prodLogData, null, 2));\n } else {\n console.error(\"Server Error:\", error.stack || error.message);\n }\n } else {\n console.warn(\"Client Error:\", JSON.stringify({\n code: error.code,\n message: error.message,\n statusCode: error.statusCode,\n path: ctx.path,\n }));\n }\n}\n\n\nfunction formatErrorResponse(error: AppError, exposeDetails: boolean): unknown {\n const response: {\n code: string;\n message: string;\n details?: unknown;\n stack?: string;\n } = {\n code: error.code,\n message: error.message,\n };\n\n if (exposeDetails || error.statusCode < 500) {\n if (error.details !== undefined) {\n response.details = error.details;\n }\n }\n\n if (exposeDetails && error.stack) {\n response.stack = error.stack;\n }\n\n return response;\n}\n\nexport function formatZodErrors(error: z.ZodError): {\n validationErrors: { path: string; message: string; code: string }[];\n} {\n return {\n validationErrors: error.issues.map((issue) => ({\n path: issue.path.join(\".\"),\n message: issue.message,\n code: issue.code,\n })),\n };\n}"],"names":["z","AppError","toAppError","initializeSentry","isSentryEnabled","reportErrorToSentry","reportMessageToSentry","scrubObject","captureRequestBody","Log","dclogger","getInstance","extend","ErrorMiddleware","config","exposeErrorDetails","logErrors","logger","formatter","disableSentry","ctx","next","err","appError","ZodError","validationErrors","formatZodErrors","requestContext","method","path","body","request","query","headers","userUuid","state","user","traceId","ip","error","Error","message","String","stack","undefined","statusCode","operational","isOperational","errorType","name","defaultErrorLogger","status","set","code","type","responseBody","formatErrorResponse","process","env","NODE_ENV","errorCode","isDev","prodLogData","uuid","console","JSON","stringify","warn","exposeDetails","response","details","issues","map","issue","join"],"mappings":"AACA,SAASA,CAAC,QAAQ,MAAM;AACxB,SAASC,QAAQ,EAAEC,UAAU,QAAQ,wBAAwB;AAC7D,SACEC,gBAAgB,EAChBC,eAAe,EACfC,mBAAmB,EACnBC,qBAAqB,EACrBC,WAAW,EACXC,kBAAkB,QACb,yBAAyB;AAChC,SAASC,GAAG,QAAQ,qBAAqB;AAEzC,MAAMC,WAAWD,IAAIE,WAAW,GAAGC,MAAM,CAAC;AAU1C,OAAO,SAASC,gBAAgBC,SAAgC,CAAC,CAAC;IAChE,MAAM,EACJC,qBAAqB,KAAK,EAC1BC,YAAY,IAAI,EAChBC,MAAM,EACNC,SAAS,EACTC,gBAAgB,KAAK,EACtB,GAAGL;IAEJX;IAEA,OAAO,OAAOiB,KAAcC;QAC1B,IAAI;YACF,MAAMA;QACR,EAAE,OAAOC,KAAK;YACZ,IAAIC;YAEJ,IAAID,eAAetB,EAAEwB,QAAQ,EAAE;gBAC7B,MAAMC,mBAAmBC,gBAAgBJ;gBACzC,MAAMK,iBAAiB;oBACrBC,QAAQR,IAAIQ,MAAM;oBAClBC,MAAMT,IAAIS,IAAI;oBACdC,MAAMtB,mBAAmB,AAACY,IAAIW,OAAO,CAASD,IAAI;oBAClDE,OAAOzB,YAAYa,IAAIY,KAAK;oBAC5BC,SAAS1B,YAAYa,IAAIa,OAAO;oBAChCC,UAAUd,IAAIe,KAAK,CAACC,IAAI,EAAEF;oBAC1BT,kBAAkBA,iBAAiBA,gBAAgB;oBACnDY,SAASjB,IAAIe,KAAK,CAACE,OAAO;oBAC1BC,IAAIlB,IAAIkB,EAAE;gBACZ;gBAEA,wEAAwE;gBACxE5B,SAAS6B,KAAK,CAAC,oBAAoBZ;gBAEnC,qCAAqC;gBACrC,IAAI,CAACR,iBAAiBf,mBAAmB;oBACvCE,sBACE,oBACA,SACA;wBACEyB,SAASJ;wBACTF;oBACF;gBAEJ;gBAEAF,WAAW,IAAItB,SACX,oBACA,6BACA,KACAwB;YAEN,OAAO;gBACL,MAAME,iBAAiB;oBACrBC,QAAQR,IAAIQ,MAAM;oBAClBC,MAAMT,IAAIS,IAAI;oBACdC,MAAMtB,mBAAmB,AAACY,IAAIW,OAAO,CAASD,IAAI;oBAClDE,OAAOzB,YAAYa,IAAIY,KAAK;oBAC5BC,SAAS1B,YAAYa,IAAIa,OAAO;oBAChCC,UAAUd,IAAIe,KAAK,CAACC,IAAI,EAAEF;oBAC1BK,OAAOjB,eAAekB,QAAQlB,IAAImB,OAAO,GAAGC,OAAOpB;oBACnDqB,OAAOrB,eAAekB,QAAQlB,IAAIqB,KAAK,GAAGC;oBAC1CP,SAASjB,IAAIe,KAAK,CAACE,OAAO;oBAC1BC,IAAIlB,IAAIkB,EAAE;gBACZ;gBAEA,sCAAsC;gBACtC5B,SAAS6B,KAAK,CAAC,4BAA4BZ;gBAE3CJ,WAAWrB,WAAWoB;YACxB;YAEA,IAAI,CAACH,iBAAiBf,qBAAqBmB,SAASsB,UAAU,KAAK,KAAK;gBACtExC,oBAAoBkB,UAAUH,KAAK;oBACjC0B,aAAavB,SAASwB,aAAa;oBACnCC,WAAWzB,SAAS,WAAW,CAAC0B,IAAI;oBACpCJ,YAAYtB,SAASsB,UAAU;gBACjC;YACF;YAEA,IAAI7B,WAAW;gBACb,IAAIC,QAAQ;oBACVA,OAAOM,UAAUH;gBACnB,OAAO;oBACL8B,mBAAmB3B,UAAUH;gBAC/B;YACF;YAEAA,IAAI+B,MAAM,GAAG5B,SAASsB,UAAU;YAChCzB,IAAIgC,GAAG,CAAC,gBAAgB7B,SAAS8B,IAAI;YACrCjC,IAAIkC,IAAI,GAAG;YAEX,MAAMC,eAAerC,YACfA,UAAUK,UAAUH,OACpBoC,oBAAoBjC,UAAUR;YAEpCK,IAAIU,IAAI,GAAGyB;YAEX,gFAAgF;YAChF,IAAIE,QAAQC,GAAG,CAACC,QAAQ,KAAK,gBAAgBpC,SAASsB,UAAU,IAAI,KAAK;gBACvEnC,SAAS6B,KAAK,CAAC,uBAAuB;oBACpCM,YAAYtB,SAASsB,UAAU;oBAC/Be,WAAWrC,SAAS8B,IAAI;oBACxBE,cAAchD,YAAYgD;oBAC1BlB,SAASjB,IAAIe,KAAK,CAACE,OAAO;gBAC5B;YACF;QAEF;IACF;AACF;AAEA,SAASa,mBAAmBX,KAAe,EAAEnB,GAAY;IACvD,MAAMyC,QAAQJ,QAAQC,GAAG,CAACC,QAAQ,KAAK;IAEvC,IAAIpB,MAAMM,UAAU,IAAI,KAAK;QAC3B,IAAI,CAACgB,OAAO;YACV,MAAMC,cAAc;gBAClBvB,OAAO;oBACLc,MAAMd,MAAMc,IAAI;oBAChBZ,SAASF,MAAME,OAAO;oBACtBI,YAAYN,MAAMM,UAAU;gBAC9B;gBACAd,SAAS;oBACPF,MAAMT,IAAIS,IAAI;gBAChB;gBACAO,MAAM;oBACJ2B,MAAM3C,IAAIe,KAAK,CAACC,IAAI,EAAEF;gBACxB;gBACAG,SAASjB,IAAIe,KAAK,CAACE,OAAO;YAC5B;YACA2B,QAAQzB,KAAK,CAAC,iBAAiB0B,KAAKC,SAAS,CAACJ,aAAa,MAAM;QACnE,OAAO;YACLE,QAAQzB,KAAK,CAAC,iBAAiBA,MAAMI,KAAK,IAAIJ,MAAME,OAAO;QAC7D;IACF,OAAO;QACLuB,QAAQG,IAAI,CAAC,iBAAiBF,KAAKC,SAAS,CAAC;YAC3Cb,MAAMd,MAAMc,IAAI;YAChBZ,SAASF,MAAME,OAAO;YACtBI,YAAYN,MAAMM,UAAU;YAC5BhB,MAAMT,IAAIS,IAAI;QAChB;IACF;AACF;AAGA,SAAS2B,oBAAoBjB,KAAe,EAAE6B,aAAsB;IAClE,MAAMC,WAKF;QACAhB,MAAMd,MAAMc,IAAI;QAChBZ,SAASF,MAAME,OAAO;IAC1B;IAEA,IAAI2B,iBAAiB7B,MAAMM,UAAU,GAAG,KAAK;QAC3C,IAAIN,MAAM+B,OAAO,KAAK1B,WAAW;YAC/ByB,SAASC,OAAO,GAAG/B,MAAM+B,OAAO;QAClC;IACF;IAEA,IAAIF,iBAAiB7B,MAAMI,KAAK,EAAE;QAChC0B,SAAS1B,KAAK,GAAGJ,MAAMI,KAAK;IAC9B;IAEA,OAAO0B;AACT;AAEA,OAAO,SAAS3C,gBAAgBa,KAAiB;IAG/C,OAAO;QACLd,kBAAkBc,MAAMgC,MAAM,CAACC,GAAG,CAAC,CAACC,QAAW,CAAA;gBAC7C5C,MAAM4C,MAAM5C,IAAI,CAAC6C,IAAI,CAAC;gBACtBjC,SAASgC,MAAMhC,OAAO;gBACtBY,MAAMoB,MAAMpB,IAAI;YAClB,CAAA;IACF;AACF"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { UserTypes } from '../models/UserInterfaces.js';
|
|
2
|
-
export type Mock = any;
|
|
3
2
|
export interface BaseContext {
|
|
4
3
|
request: {
|
|
5
4
|
body?: unknown;
|
|
@@ -43,131 +42,6 @@ export declare const mockDatabase: {
|
|
|
43
42
|
mockModel<T = unknown>(methods?: Partial<SequelizeModelMethods<T>>): SequelizeModelMethods<T>;
|
|
44
43
|
};
|
|
45
44
|
export declare function flushPromises(): Promise<void>;
|
|
46
|
-
/**
|
|
47
|
-
* ============================================================================
|
|
48
|
-
* TEST DATA FACTORIES
|
|
49
|
-
* Following industry best practices from Testing Blueprint
|
|
50
|
-
* ============================================================================
|
|
51
|
-
*/
|
|
52
|
-
/**
|
|
53
|
-
* Constants for testing - avoids magic values
|
|
54
|
-
*/
|
|
55
|
-
export declare const TEST_CONSTANTS: {
|
|
56
|
-
readonly VALID_UUID: "123e4567-e89b-12d3-a456-426614174000";
|
|
57
|
-
readonly INVALID_UUID: "invalid-uuid";
|
|
58
|
-
readonly VALID_TIMEZONE: "America/New_York";
|
|
59
|
-
readonly INVALID_TIMEZONE: "Invalid/Timezone";
|
|
60
|
-
readonly DEFAULT_TIMEZONE: "UTC";
|
|
61
|
-
readonly TEST_DATE: "2026-02-25T10:00:00Z";
|
|
62
|
-
readonly WEEK_START: "2026-02-23";
|
|
63
|
-
readonly MAX_JSON_SIZE: number;
|
|
64
|
-
};
|
|
65
|
-
/**
|
|
66
|
-
* Mock file upload structure (multer format)
|
|
67
|
-
*/
|
|
68
|
-
export interface MockFile {
|
|
69
|
-
fieldname: string;
|
|
70
|
-
originalname: string;
|
|
71
|
-
encoding: string;
|
|
72
|
-
mimetype: string;
|
|
73
|
-
buffer: Buffer;
|
|
74
|
-
size: number;
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Creates a mock file for upload testing
|
|
78
|
-
*/
|
|
79
|
-
export declare function createMockFile(overrides?: Partial<MockFile>): MockFile;
|
|
80
|
-
/**
|
|
81
|
-
* Creates multiple mock files
|
|
82
|
-
*/
|
|
83
|
-
export declare function createMockFiles(count: number): MockFile[];
|
|
84
|
-
/**
|
|
85
|
-
* Mock transaction for database testing
|
|
86
|
-
*/
|
|
87
|
-
export interface MockTransaction {
|
|
88
|
-
commit: Mock;
|
|
89
|
-
rollback: Mock;
|
|
90
|
-
finished?: string;
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Creates a mock Sequelize transaction
|
|
94
|
-
*/
|
|
95
|
-
export declare function createMockTransaction(): MockTransaction;
|
|
96
|
-
/**
|
|
97
|
-
* ============================================================================
|
|
98
|
-
* MOCK CONFIGURATIONS
|
|
99
|
-
* Pre-configured mocks for common testing scenarios
|
|
100
|
-
* ============================================================================
|
|
101
|
-
*/
|
|
102
|
-
/**
|
|
103
|
-
* Database mock configuration
|
|
104
|
-
*/
|
|
105
|
-
export interface DatabaseMocks {
|
|
106
|
-
findOne: Mock;
|
|
107
|
-
findOrCreate: Mock;
|
|
108
|
-
findByPk: Mock;
|
|
109
|
-
findAll: Mock;
|
|
110
|
-
create: Mock;
|
|
111
|
-
update: Mock;
|
|
112
|
-
upsert: Mock;
|
|
113
|
-
bulkCreate: Mock;
|
|
114
|
-
destroy: Mock;
|
|
115
|
-
reset: () => void;
|
|
116
|
-
restore: () => void;
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Creates a set of database operation mocks with reset/restore functionality
|
|
120
|
-
*/
|
|
121
|
-
export declare function setupDatabaseMocks(): DatabaseMocks;
|
|
122
|
-
/**
|
|
123
|
-
* Time-related mock utilities
|
|
124
|
-
*/
|
|
125
|
-
export interface TimeMocks {
|
|
126
|
-
setTime: (date: string | Date) => void;
|
|
127
|
-
advanceTime: (ms: number) => void;
|
|
128
|
-
restore: () => void;
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Sets up fake timers for timezone testing
|
|
132
|
-
* Usage:
|
|
133
|
-
* ```typescript
|
|
134
|
-
* describe('My test', () => {
|
|
135
|
-
* const timeMocks = setupTimeMocks();
|
|
136
|
-
*
|
|
137
|
-
* it('test with fixed time', () => {
|
|
138
|
-
* timeMocks.setTime('2026-02-25T10:00:00Z');
|
|
139
|
-
* // ... test code
|
|
140
|
-
* });
|
|
141
|
-
* });
|
|
142
|
-
* ```
|
|
143
|
-
*/
|
|
144
|
-
export declare function setupTimeMocks(): TimeMocks;
|
|
145
|
-
/**
|
|
146
|
-
* Mock error instances for testing error handling
|
|
147
|
-
*/
|
|
148
|
-
export declare class MockValidationError extends Error {
|
|
149
|
-
issues: any[];
|
|
150
|
-
constructor(message: string, issues?: any[]);
|
|
151
|
-
}
|
|
152
|
-
export declare class MockNotFoundError extends Error {
|
|
153
|
-
constructor(message: string);
|
|
154
|
-
}
|
|
155
|
-
export declare class MockDatabaseError extends Error {
|
|
156
|
-
constructor(message: string);
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Helper to create a typed mock function
|
|
160
|
-
*/
|
|
161
|
-
export declare function createTypedMock(): Mock;
|
|
162
|
-
/**
|
|
163
|
-
* Helper to create a typed spy on a prototype method
|
|
164
|
-
* Usage:
|
|
165
|
-
* ```typescript
|
|
166
|
-
* const mockMethod = spyOnPrototype(MyService.prototype, 'methodName');
|
|
167
|
-
* mockMethod.mockResolvedValue({ success: true });
|
|
168
|
-
* ```
|
|
169
|
-
*/
|
|
170
|
-
export declare function spyOnPrototype<T, K extends keyof T>(prototype: T, method: K): Mock;
|
|
171
45
|
export declare const assertions: {
|
|
172
46
|
assertDefined<T>(value: T | null | undefined, message?: string): asserts value is T;
|
|
173
47
|
assertStatusCode(ctx: BaseContext | {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testHelpers.d.ts","sourceRoot":"","sources":["../../../src/lib/testing/testHelpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAG7D,MAAM,
|
|
1
|
+
{"version":3,"file":"testHelpers.d.ts","sourceRoot":"","sources":["../../../src/lib/testing/testHelpers.ts"],"names":[],"mappings":"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"}
|
|
@@ -73,167 +73,6 @@ export const mockDatabase = {
|
|
|
73
73
|
export async function flushPromises() {
|
|
74
74
|
return new Promise((resolve)=>setImmediate(resolve));
|
|
75
75
|
}
|
|
76
|
-
/**
|
|
77
|
-
* ============================================================================
|
|
78
|
-
* TEST DATA FACTORIES
|
|
79
|
-
* Following industry best practices from Testing Blueprint
|
|
80
|
-
* ============================================================================
|
|
81
|
-
*/ /**
|
|
82
|
-
* Constants for testing - avoids magic values
|
|
83
|
-
*/ export const TEST_CONSTANTS = {
|
|
84
|
-
VALID_UUID: '123e4567-e89b-12d3-a456-426614174000',
|
|
85
|
-
INVALID_UUID: 'invalid-uuid',
|
|
86
|
-
VALID_TIMEZONE: 'America/New_York',
|
|
87
|
-
INVALID_TIMEZONE: 'Invalid/Timezone',
|
|
88
|
-
DEFAULT_TIMEZONE: 'UTC',
|
|
89
|
-
TEST_DATE: '2026-02-25T10:00:00Z',
|
|
90
|
-
WEEK_START: '2026-02-23',
|
|
91
|
-
MAX_JSON_SIZE: 1024 * 1024
|
|
92
|
-
};
|
|
93
|
-
/**
|
|
94
|
-
* Creates a mock file for upload testing
|
|
95
|
-
*/ export function createMockFile(overrides = {}) {
|
|
96
|
-
return {
|
|
97
|
-
fieldname: 'photos',
|
|
98
|
-
originalname: 'test-photo.jpg',
|
|
99
|
-
encoding: '7bit',
|
|
100
|
-
mimetype: 'image/jpeg',
|
|
101
|
-
buffer: Buffer.from('fake-image-data'),
|
|
102
|
-
size: 1024,
|
|
103
|
-
...overrides
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Creates multiple mock files
|
|
108
|
-
*/ export function createMockFiles(count) {
|
|
109
|
-
return Array.from({
|
|
110
|
-
length: count
|
|
111
|
-
}, (_, i)=>createMockFile({
|
|
112
|
-
originalname: `test-photo-${i}.jpg`,
|
|
113
|
-
size: 1024 * (i + 1)
|
|
114
|
-
}));
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* Creates a mock Sequelize transaction
|
|
118
|
-
*/ export function createMockTransaction() {
|
|
119
|
-
const mockFn = globalThis.jest?.fn || (()=>{
|
|
120
|
-
throw new Error('jest.fn is not available. Make sure jest is properly configured.');
|
|
121
|
-
});
|
|
122
|
-
return {
|
|
123
|
-
commit: mockFn().mockResolvedValue(undefined),
|
|
124
|
-
rollback: mockFn().mockResolvedValue(undefined)
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Creates a set of database operation mocks with reset/restore functionality
|
|
129
|
-
*/ export function setupDatabaseMocks() {
|
|
130
|
-
// Note: jest must be imported in the test file for this to work
|
|
131
|
-
const mockFn = globalThis.jest?.fn || (()=>{
|
|
132
|
-
throw new Error('jest.fn is not available. Make sure jest is properly configured.');
|
|
133
|
-
});
|
|
134
|
-
const mocks = {
|
|
135
|
-
findOne: mockFn(),
|
|
136
|
-
findOrCreate: mockFn(),
|
|
137
|
-
findByPk: mockFn(),
|
|
138
|
-
findAll: mockFn(),
|
|
139
|
-
create: mockFn(),
|
|
140
|
-
update: mockFn(),
|
|
141
|
-
upsert: mockFn(),
|
|
142
|
-
bulkCreate: mockFn(),
|
|
143
|
-
destroy: mockFn()
|
|
144
|
-
};
|
|
145
|
-
return {
|
|
146
|
-
...mocks,
|
|
147
|
-
reset: ()=>{
|
|
148
|
-
Object.values(mocks).forEach((mock)=>mock.mockReset?.());
|
|
149
|
-
},
|
|
150
|
-
restore: ()=>{
|
|
151
|
-
Object.values(mocks).forEach((mock)=>mock.mockRestore?.());
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Sets up fake timers for timezone testing
|
|
157
|
-
* Usage:
|
|
158
|
-
* ```typescript
|
|
159
|
-
* describe('My test', () => {
|
|
160
|
-
* const timeMocks = setupTimeMocks();
|
|
161
|
-
*
|
|
162
|
-
* it('test with fixed time', () => {
|
|
163
|
-
* timeMocks.setTime('2026-02-25T10:00:00Z');
|
|
164
|
-
* // ... test code
|
|
165
|
-
* });
|
|
166
|
-
* });
|
|
167
|
-
* ```
|
|
168
|
-
*/ export function setupTimeMocks() {
|
|
169
|
-
const jestGlobal = globalThis.jest;
|
|
170
|
-
const beforeEachGlobal = globalThis.beforeEach;
|
|
171
|
-
const afterEachGlobal = globalThis.afterEach;
|
|
172
|
-
if (beforeEachGlobal) {
|
|
173
|
-
beforeEachGlobal(()=>{
|
|
174
|
-
jestGlobal?.useFakeTimers();
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
if (afterEachGlobal) {
|
|
178
|
-
afterEachGlobal(()=>{
|
|
179
|
-
jestGlobal?.useRealTimers();
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
return {
|
|
183
|
-
setTime: (date)=>{
|
|
184
|
-
jestGlobal?.setSystemTime(new Date(date));
|
|
185
|
-
},
|
|
186
|
-
advanceTime: (ms)=>{
|
|
187
|
-
jestGlobal?.advanceTimersByTime(ms);
|
|
188
|
-
},
|
|
189
|
-
restore: ()=>{
|
|
190
|
-
jestGlobal?.useRealTimers();
|
|
191
|
-
}
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Mock error instances for testing error handling
|
|
196
|
-
*/ export class MockValidationError extends Error {
|
|
197
|
-
issues;
|
|
198
|
-
constructor(message, issues = []){
|
|
199
|
-
super(message), this.issues = issues;
|
|
200
|
-
this.name = 'ValidationError';
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
export class MockNotFoundError extends Error {
|
|
204
|
-
constructor(message){
|
|
205
|
-
super(message);
|
|
206
|
-
this.name = 'NotFoundError';
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
export class MockDatabaseError extends Error {
|
|
210
|
-
constructor(message){
|
|
211
|
-
super(message);
|
|
212
|
-
this.name = 'SequelizeDatabaseError';
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* Helper to create a typed mock function
|
|
217
|
-
*/ export function createTypedMock() {
|
|
218
|
-
const mockFn = globalThis.jest?.fn || (()=>{
|
|
219
|
-
throw new Error('jest.fn is not available. Make sure jest is properly configured.');
|
|
220
|
-
});
|
|
221
|
-
return mockFn();
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* Helper to create a typed spy on a prototype method
|
|
225
|
-
* Usage:
|
|
226
|
-
* ```typescript
|
|
227
|
-
* const mockMethod = spyOnPrototype(MyService.prototype, 'methodName');
|
|
228
|
-
* mockMethod.mockResolvedValue({ success: true });
|
|
229
|
-
* ```
|
|
230
|
-
*/ export function spyOnPrototype(prototype, method) {
|
|
231
|
-
const jestGlobal = globalThis.jest;
|
|
232
|
-
if (!jestGlobal?.spyOn) {
|
|
233
|
-
throw new Error('jest.spyOn is not available. Make sure jest is properly configured.');
|
|
234
|
-
}
|
|
235
|
-
return jestGlobal.spyOn(prototype, method);
|
|
236
|
-
}
|
|
237
76
|
export const assertions = {
|
|
238
77
|
assertDefined (value, message) {
|
|
239
78
|
if (value === null || value === undefined) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/testing/testHelpers.ts"],"sourcesContent":["import type { UserTypes } from '../models/UserInterfaces.js';\n\n// Mock type definition to avoid jest-mock dependency\nexport type Mock = any;\n\n\nexport interface BaseContext {\n request: {\n body?: unknown;\n headers?: Record<string, string | string[] | undefined>;\n files?: unknown;\n };\n response: {\n status: number;\n body?: unknown;\n };\n params?: Record<string, string>;\n query?: Record<string, string | string[] | undefined>;\n state: Record<string, unknown>;\n status: number;\n body?: unknown;\n headers: Record<string, string | string[] | undefined>;\n method?: string;\n path?: string;\n get: (field: string) => string | string[] | undefined;\n set: (field: string, value: string) => void;\n}\n\n\nexport function createMockContext<T extends BaseContext = BaseContext>(\n overrides: Partial<T> = {},\n): T {\n const ctx: BaseContext = {\n request: {\n body: {},\n headers: {},\n ...overrides.request,\n },\n response: {\n status: 200,\n body: undefined,\n ...overrides.response,\n },\n params: overrides.params || {},\n query: overrides.query || {},\n state: overrides.state || {},\n status: overrides.status || 200,\n body: overrides.body,\n headers: overrides.headers || {},\n method: overrides.method || 'GET',\n path: overrides.path || '/',\n get: (field: string) => {\n return ctx.headers[field.toLowerCase()];\n },\n set: (field: string, value: string) => {\n ctx.headers[field.toLowerCase()] = value;\n },\n };\n\n return { ...ctx, ...overrides } as T;\n}\n\n\nexport function createMockUser(overrides: Partial<UserTypes> = {}): UserTypes {\n return {\n userUuid: overrides.userUuid || 'test-user-uuid',\n email: overrides.email || 'test@example.com',\n firstName: overrides.firstName || 'Test',\n lastName: overrides.lastName || 'User',\n avatar: overrides.avatar || '1',\n role: overrides.role || 'user',\n isSubscriptionActive: overrides.isSubscriptionActive ?? true,\n ...overrides,\n };\n}\n\n\nexport interface AuthenticatedContext extends BaseContext {\n state: {\n user: UserTypes;\n [key: string]: unknown;\n };\n}\n\n\nexport function createAuthenticatedContext<T extends BaseContext = AuthenticatedContext>(\n userOverrides: Partial<UserTypes> = {},\n contextOverrides: Partial<T> = {},\n): T {\n const user = createMockUser(userOverrides);\n\n return createMockContext<T>({\n ...contextOverrides,\n state: {\n user,\n ...(contextOverrides.state || {}),\n },\n headers: {\n authorization: 'Bearer mock-token',\n ...(contextOverrides.headers || {}),\n },\n } as Partial<T>);\n}\n\nexport interface SequelizeModelMethods<T = unknown> {\n findOne: ((options?: unknown) => Promise<T | null>) | undefined;\n findAll: ((options?: unknown) => Promise<T[]>) | undefined;\n findByPk: ((id: string | number, options?: unknown) => Promise<T | null>) | undefined;\n create: ((values: Partial<T>, options?: unknown) => Promise<T>) | undefined;\n update: ((values: Partial<T>, options?: unknown) => Promise<[number, T[]]>) | undefined;\n destroy: ((options?: unknown) => Promise<number>) | undefined;\n count: ((options?: unknown) => Promise<number>) | undefined;\n}\n\n\nexport const mockDatabase = {\n\n mockModel<T = unknown>(\n methods: Partial<SequelizeModelMethods<T>> = {},\n ): SequelizeModelMethods<T> {\n return {\n findOne: undefined,\n findAll: undefined,\n findByPk: undefined,\n create: undefined,\n update: undefined,\n destroy: undefined,\n count: undefined,\n ...methods,\n };\n },\n};\n\n\nexport async function flushPromises(): Promise<void> {\n return new Promise((resolve) => setImmediate(resolve));\n}\n\n/**\n * ============================================================================\n * TEST DATA FACTORIES\n * Following industry best practices from Testing Blueprint\n * ============================================================================\n */\n\n/**\n * Constants for testing - avoids magic values\n */\nexport const TEST_CONSTANTS = {\n VALID_UUID: '123e4567-e89b-12d3-a456-426614174000',\n INVALID_UUID: 'invalid-uuid',\n VALID_TIMEZONE: 'America/New_York',\n INVALID_TIMEZONE: 'Invalid/Timezone',\n DEFAULT_TIMEZONE: 'UTC',\n TEST_DATE: '2026-02-25T10:00:00Z',\n WEEK_START: '2026-02-23',\n MAX_JSON_SIZE: 1024 * 1024, // 1MB\n} as const;\n\n/**\n * Mock file upload structure (multer format)\n */\nexport interface MockFile {\n fieldname: string;\n originalname: string;\n encoding: string;\n mimetype: string;\n buffer: Buffer;\n size: number;\n}\n\n/**\n * Creates a mock file for upload testing\n */\nexport function createMockFile(overrides: Partial<MockFile> = {}): MockFile {\n return {\n fieldname: 'photos',\n originalname: 'test-photo.jpg',\n encoding: '7bit',\n mimetype: 'image/jpeg',\n buffer: Buffer.from('fake-image-data'),\n size: 1024,\n ...overrides,\n };\n}\n\n/**\n * Creates multiple mock files\n */\nexport function createMockFiles(count: number): MockFile[] {\n return Array.from({ length: count }, (_, i) =>\n createMockFile({\n originalname: `test-photo-${i}.jpg`,\n size: 1024 * (i + 1),\n }),\n );\n}\n\n/**\n * Mock transaction for database testing\n */\nexport interface MockTransaction {\n commit: Mock;\n rollback: Mock;\n finished?: string;\n}\n\n/**\n * Creates a mock Sequelize transaction\n */\nexport function createMockTransaction(): MockTransaction {\n const mockFn = (globalThis as any).jest?.fn || (() => {\n throw new Error('jest.fn is not available. Make sure jest is properly configured.');\n });\n\n return {\n commit: mockFn().mockResolvedValue(undefined),\n rollback: mockFn().mockResolvedValue(undefined),\n };\n}\n\n/**\n * ============================================================================\n * MOCK CONFIGURATIONS\n * Pre-configured mocks for common testing scenarios\n * ============================================================================\n */\n\n/**\n * Database mock configuration\n */\nexport interface DatabaseMocks {\n findOne: Mock;\n findOrCreate: Mock;\n findByPk: Mock;\n findAll: Mock;\n create: Mock;\n update: Mock;\n upsert: Mock;\n bulkCreate: Mock;\n destroy: Mock;\n reset: () => void;\n restore: () => void;\n}\n\n/**\n * Creates a set of database operation mocks with reset/restore functionality\n */\nexport function setupDatabaseMocks(): DatabaseMocks {\n // Note: jest must be imported in the test file for this to work\n const mockFn = (globalThis as any).jest?.fn || (() => {\n throw new Error('jest.fn is not available. Make sure jest is properly configured.');\n });\n\n const mocks = {\n findOne: mockFn(),\n findOrCreate: mockFn(),\n findByPk: mockFn(),\n findAll: mockFn(),\n create: mockFn(),\n update: mockFn(),\n upsert: mockFn(),\n bulkCreate: mockFn(),\n destroy: mockFn(),\n };\n\n return {\n ...mocks,\n reset: () => {\n Object.values(mocks).forEach((mock) => mock.mockReset?.());\n },\n restore: () => {\n Object.values(mocks).forEach((mock) => mock.mockRestore?.());\n },\n };\n}\n\n/**\n * Time-related mock utilities\n */\nexport interface TimeMocks {\n setTime: (date: string | Date) => void;\n advanceTime: (ms: number) => void;\n restore: () => void;\n}\n\n/**\n * Sets up fake timers for timezone testing\n * Usage:\n * ```typescript\n * describe('My test', () => {\n * const timeMocks = setupTimeMocks();\n *\n * it('test with fixed time', () => {\n * timeMocks.setTime('2026-02-25T10:00:00Z');\n * // ... test code\n * });\n * });\n * ```\n */\nexport function setupTimeMocks(): TimeMocks {\n const jestGlobal = (globalThis as any).jest;\n const beforeEachGlobal = (globalThis as any).beforeEach;\n const afterEachGlobal = (globalThis as any).afterEach;\n\n if (beforeEachGlobal) {\n beforeEachGlobal(() => {\n jestGlobal?.useFakeTimers();\n });\n }\n\n if (afterEachGlobal) {\n afterEachGlobal(() => {\n jestGlobal?.useRealTimers();\n });\n }\n\n return {\n setTime: (date: string | Date) => {\n jestGlobal?.setSystemTime(new Date(date));\n },\n advanceTime: (ms: number) => {\n jestGlobal?.advanceTimersByTime(ms);\n },\n restore: () => {\n jestGlobal?.useRealTimers();\n },\n };\n}\n\n/**\n * Mock error instances for testing error handling\n */\nexport class MockValidationError extends Error {\n constructor(\n message: string,\n public issues: any[] = [],\n ) {\n super(message);\n this.name = 'ValidationError';\n }\n}\n\nexport class MockNotFoundError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'NotFoundError';\n }\n}\n\nexport class MockDatabaseError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'SequelizeDatabaseError';\n }\n}\n\n/**\n * Helper to create a typed mock function\n */\nexport function createTypedMock(): Mock {\n const mockFn = (globalThis as any).jest?.fn || (() => {\n throw new Error('jest.fn is not available. Make sure jest is properly configured.');\n });\n return mockFn() as Mock;\n}\n\n/**\n * Helper to create a typed spy on a prototype method\n * Usage:\n * ```typescript\n * const mockMethod = spyOnPrototype(MyService.prototype, 'methodName');\n * mockMethod.mockResolvedValue({ success: true });\n * ```\n */\nexport function spyOnPrototype<T, K extends keyof T>(\n prototype: T,\n method: K,\n): Mock {\n const jestGlobal = (globalThis as any).jest;\n if (!jestGlobal?.spyOn) {\n throw new Error('jest.spyOn is not available. Make sure jest is properly configured.');\n }\n return jestGlobal.spyOn(prototype as any, method as any) as any;\n}\n\n\nexport const assertions = {\n\n assertDefined<T>(value: T | null | undefined, message?: string): asserts value is T {\n if (value === null || value === undefined) {\n throw new Error(message || 'Expected value to be defined');\n }\n },\n\n\n assertStatusCode(ctx: BaseContext | { status: number; body?: unknown }, expected: number): void {\n if (ctx.status !== expected) {\n throw new Error(\n `Expected status ${expected} but got ${ctx.status}. Body: ${JSON.stringify(ctx.body)}`,\n );\n }\n },\n\n assertResponseShape(\n body: unknown,\n expectedKeys: string[],\n ): asserts body is Record<string, unknown> {\n if (typeof body !== 'object' || body === null) {\n throw new Error('Expected body to be an object');\n }\n const actualKeys = Object.keys(body);\n const missingKeys = expectedKeys.filter((key) => !actualKeys.includes(key));\n if (missingKeys.length > 0) {\n throw new Error(\n `Missing expected keys in response: ${missingKeys.join(', ')}. Got: ${actualKeys.join(', ')}`,\n );\n }\n },\n\n\n assertExists<T>(value: T | null | undefined, message?: string): asserts value is T {\n if (value === null || value === undefined) {\n throw new Error(message || 'Expected value to exist');\n }\n },\n\n assertType<T>(value: unknown, type: string, message?: string): asserts value is T {\n if (typeof value !== type) {\n throw new Error(message || `Expected value to be of type ${type}, got ${typeof value}`);\n }\n },\n\n validateObjectProperties<T extends Record<string, unknown>>(\n obj: unknown,\n expectedProps: (keyof T)[],\n ): obj is T {\n if (typeof obj !== 'object' || obj === null) {\n return false;\n }\n return expectedProps.every((prop) => prop in obj);\n },\n};\n\nexport interface RouterContext extends BaseContext {\n params: Record<string, string>;\n router?: unknown;\n _matchedRoute?: string;\n _matchedRouteName?: string;\n}\n\n\nexport type RouterMiddleware<TContext = BaseContext> = (\n ctx: TContext,\n next: () => Promise<void>\n) => Promise<void> | void;\n\n\nexport interface RouterLayer<TContext = BaseContext> {\n path: string | RegExp;\n methods: string[];\n stack: RouterMiddleware<TContext>[];\n name?: string | null;\n}\n\n\nexport interface RouterLike<TContext = BaseContext> {\n stack: RouterLayer<TContext>[];\n}\n\nexport async function executeRoute<TContext extends BaseContext = BaseContext>(\n router: { stack: { path: string | RegExp; methods: string[]; stack: unknown[] }[] },\n path: string,\n method: string,\n ctx: TContext,\n): Promise<void> {\n const route = router.stack.find(\n (layer) => layer.path === path && layer.methods.includes(method)\n );\n\n if (!route) {\n throw new Error(`Route not found: ${method} ${path}`);\n }\n\n // Cast context to include router properties\n // We add these properties dynamically, so TypeScript needs to trust us here\n const routerCtx = ctx as TContext & RouterContext;\n if (!routerCtx.params) {\n routerCtx.params = {};\n }\n if (!routerCtx.router) {\n routerCtx.router = router;\n }\n if (!routerCtx._matchedRoute) {\n routerCtx._matchedRoute = path;\n }\n\n // Execute all middlewares in the stack (validation, then handler)\n // Build the middleware chain from right to left\n let index = -1;\n\n const dispatch = async (i: number): Promise<void> => {\n if (i <= index) {\n throw new Error('next() called multiple times');\n }\n index = i;\n\n if (i >= route.stack.length) {\n return;\n }\n\n const middleware = route.stack[i] as (ctx: unknown, next: () => Promise<void>) => Promise<void>;\n return await middleware(routerCtx, () => dispatch(i + 1));\n };\n\n await dispatch(0);\n}\n"],"names":["createMockContext","overrides","ctx","request","body","headers","response","status","undefined","params","query","state","method","path","get","field","toLowerCase","set","value","createMockUser","userUuid","email","firstName","lastName","avatar","role","isSubscriptionActive","createAuthenticatedContext","userOverrides","contextOverrides","user","authorization","mockDatabase","mockModel","methods","findOne","findAll","findByPk","create","update","destroy","count","flushPromises","Promise","resolve","setImmediate","TEST_CONSTANTS","VALID_UUID","INVALID_UUID","VALID_TIMEZONE","INVALID_TIMEZONE","DEFAULT_TIMEZONE","TEST_DATE","WEEK_START","MAX_JSON_SIZE","createMockFile","fieldname","originalname","encoding","mimetype","buffer","Buffer","from","size","createMockFiles","Array","length","_","i","createMockTransaction","mockFn","globalThis","jest","fn","Error","commit","mockResolvedValue","rollback","setupDatabaseMocks","mocks","findOrCreate","upsert","bulkCreate","reset","Object","values","forEach","mock","mockReset","restore","mockRestore","setupTimeMocks","jestGlobal","beforeEachGlobal","beforeEach","afterEachGlobal","afterEach","useFakeTimers","useRealTimers","setTime","date","setSystemTime","Date","advanceTime","ms","advanceTimersByTime","MockValidationError","message","issues","name","MockNotFoundError","MockDatabaseError","createTypedMock","spyOnPrototype","prototype","spyOn","assertions","assertDefined","assertStatusCode","expected","JSON","stringify","assertResponseShape","expectedKeys","actualKeys","keys","missingKeys","filter","key","includes","join","assertExists","assertType","type","validateObjectProperties","obj","expectedProps","every","prop","executeRoute","router","route","stack","find","layer","routerCtx","_matchedRoute","index","dispatch","middleware"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/testing/testHelpers.ts"],"sourcesContent":["import type { UserTypes } from '../models/UserInterfaces.js';\n\n\nexport interface BaseContext {\n request: {\n body?: unknown;\n headers?: Record<string, string | string[] | undefined>;\n files?: unknown;\n };\n response: {\n status: number;\n body?: unknown;\n };\n params?: Record<string, string>;\n query?: Record<string, string | string[] | undefined>;\n state: Record<string, unknown>;\n status: number;\n body?: unknown;\n headers: Record<string, string | string[] | undefined>;\n method?: string;\n path?: string;\n get: (field: string) => string | string[] | undefined;\n set: (field: string, value: string) => void;\n}\n\n\nexport function createMockContext<T extends BaseContext = BaseContext>(\n overrides: Partial<T> = {},\n): T {\n const ctx: BaseContext = {\n request: {\n body: {},\n headers: {},\n ...overrides.request,\n },\n response: {\n status: 200,\n body: undefined,\n ...overrides.response,\n },\n params: overrides.params || {},\n query: overrides.query || {},\n state: overrides.state || {},\n status: overrides.status || 200,\n body: overrides.body,\n headers: overrides.headers || {},\n method: overrides.method || 'GET',\n path: overrides.path || '/',\n get: (field: string) => {\n return ctx.headers[field.toLowerCase()];\n },\n set: (field: string, value: string) => {\n ctx.headers[field.toLowerCase()] = value;\n },\n };\n\n return { ...ctx, ...overrides } as T;\n}\n\n\nexport function createMockUser(overrides: Partial<UserTypes> = {}): UserTypes {\n return {\n userUuid: overrides.userUuid || 'test-user-uuid',\n email: overrides.email || 'test@example.com',\n firstName: overrides.firstName || 'Test',\n lastName: overrides.lastName || 'User',\n avatar: overrides.avatar || '1',\n role: overrides.role || 'user',\n isSubscriptionActive: overrides.isSubscriptionActive ?? true,\n ...overrides,\n };\n}\n\n\nexport interface AuthenticatedContext extends BaseContext {\n state: {\n user: UserTypes;\n [key: string]: unknown;\n };\n}\n\n\nexport function createAuthenticatedContext<T extends BaseContext = AuthenticatedContext>(\n userOverrides: Partial<UserTypes> = {},\n contextOverrides: Partial<T> = {},\n): T {\n const user = createMockUser(userOverrides);\n\n return createMockContext<T>({\n ...contextOverrides,\n state: {\n user,\n ...(contextOverrides.state || {}),\n },\n headers: {\n authorization: 'Bearer mock-token',\n ...(contextOverrides.headers || {}),\n },\n } as Partial<T>);\n}\n\nexport interface SequelizeModelMethods<T = unknown> {\n findOne: ((options?: unknown) => Promise<T | null>) | undefined;\n findAll: ((options?: unknown) => Promise<T[]>) | undefined;\n findByPk: ((id: string | number, options?: unknown) => Promise<T | null>) | undefined;\n create: ((values: Partial<T>, options?: unknown) => Promise<T>) | undefined;\n update: ((values: Partial<T>, options?: unknown) => Promise<[number, T[]]>) | undefined;\n destroy: ((options?: unknown) => Promise<number>) | undefined;\n count: ((options?: unknown) => Promise<number>) | undefined;\n}\n\n\nexport const mockDatabase = {\n\n mockModel<T = unknown>(\n methods: Partial<SequelizeModelMethods<T>> = {},\n ): SequelizeModelMethods<T> {\n return {\n findOne: undefined,\n findAll: undefined,\n findByPk: undefined,\n create: undefined,\n update: undefined,\n destroy: undefined,\n count: undefined,\n ...methods,\n };\n },\n};\n\n\nexport async function flushPromises(): Promise<void> {\n return new Promise((resolve) => setImmediate(resolve));\n}\n\n\nexport const assertions = {\n\n assertDefined<T>(value: T | null | undefined, message?: string): asserts value is T {\n if (value === null || value === undefined) {\n throw new Error(message || 'Expected value to be defined');\n }\n },\n\n\n assertStatusCode(ctx: BaseContext | { status: number; body?: unknown }, expected: number): void {\n if (ctx.status !== expected) {\n throw new Error(\n `Expected status ${expected} but got ${ctx.status}. Body: ${JSON.stringify(ctx.body)}`,\n );\n }\n },\n\n assertResponseShape(\n body: unknown,\n expectedKeys: string[],\n ): asserts body is Record<string, unknown> {\n if (typeof body !== 'object' || body === null) {\n throw new Error('Expected body to be an object');\n }\n const actualKeys = Object.keys(body);\n const missingKeys = expectedKeys.filter((key) => !actualKeys.includes(key));\n if (missingKeys.length > 0) {\n throw new Error(\n `Missing expected keys in response: ${missingKeys.join(', ')}. Got: ${actualKeys.join(', ')}`,\n );\n }\n },\n\n\n assertExists<T>(value: T | null | undefined, message?: string): asserts value is T {\n if (value === null || value === undefined) {\n throw new Error(message || 'Expected value to exist');\n }\n },\n\n assertType<T>(value: unknown, type: string, message?: string): asserts value is T {\n if (typeof value !== type) {\n throw new Error(message || `Expected value to be of type ${type}, got ${typeof value}`);\n }\n },\n\n validateObjectProperties<T extends Record<string, unknown>>(\n obj: unknown,\n expectedProps: (keyof T)[],\n ): obj is T {\n if (typeof obj !== 'object' || obj === null) {\n return false;\n }\n return expectedProps.every((prop) => prop in obj);\n },\n};\n\nexport interface RouterContext extends BaseContext {\n params: Record<string, string>;\n router?: unknown;\n _matchedRoute?: string;\n _matchedRouteName?: string;\n}\n\n\nexport type RouterMiddleware<TContext = BaseContext> = (\n ctx: TContext,\n next: () => Promise<void>\n) => Promise<void> | void;\n\n\nexport interface RouterLayer<TContext = BaseContext> {\n path: string | RegExp;\n methods: string[];\n stack: RouterMiddleware<TContext>[];\n name?: string | null;\n}\n\n\nexport interface RouterLike<TContext = BaseContext> {\n stack: RouterLayer<TContext>[];\n}\n\nexport async function executeRoute<TContext extends BaseContext = BaseContext>(\n router: { stack: { path: string | RegExp; methods: string[]; stack: unknown[] }[] },\n path: string,\n method: string,\n ctx: TContext,\n): Promise<void> {\n const route = router.stack.find(\n (layer) => layer.path === path && layer.methods.includes(method)\n );\n\n if (!route) {\n throw new Error(`Route not found: ${method} ${path}`);\n }\n\n // Cast context to include router properties\n // We add these properties dynamically, so TypeScript needs to trust us here\n const routerCtx = ctx as TContext & RouterContext;\n if (!routerCtx.params) {\n routerCtx.params = {};\n }\n if (!routerCtx.router) {\n routerCtx.router = router;\n }\n if (!routerCtx._matchedRoute) {\n routerCtx._matchedRoute = path;\n }\n\n // Execute all middlewares in the stack (validation, then handler)\n // Build the middleware chain from right to left\n let index = -1;\n\n const dispatch = async (i: number): Promise<void> => {\n if (i <= index) {\n throw new Error('next() called multiple times');\n }\n index = i;\n\n if (i >= route.stack.length) {\n return;\n }\n\n const middleware = route.stack[i] as (ctx: unknown, next: () => Promise<void>) => Promise<void>;\n return await middleware(routerCtx, () => dispatch(i + 1));\n };\n\n await dispatch(0);\n}\n"],"names":["createMockContext","overrides","ctx","request","body","headers","response","status","undefined","params","query","state","method","path","get","field","toLowerCase","set","value","createMockUser","userUuid","email","firstName","lastName","avatar","role","isSubscriptionActive","createAuthenticatedContext","userOverrides","contextOverrides","user","authorization","mockDatabase","mockModel","methods","findOne","findAll","findByPk","create","update","destroy","count","flushPromises","Promise","resolve","setImmediate","assertions","assertDefined","message","Error","assertStatusCode","expected","JSON","stringify","assertResponseShape","expectedKeys","actualKeys","Object","keys","missingKeys","filter","key","includes","length","join","assertExists","assertType","type","validateObjectProperties","obj","expectedProps","every","prop","executeRoute","router","route","stack","find","layer","routerCtx","_matchedRoute","index","dispatch","i","middleware"],"mappings":"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"}
|
|
@@ -54,7 +54,7 @@ export function scrubObject(obj, depth = 0) {
|
|
|
54
54
|
}
|
|
55
55
|
return scrubbedObj;
|
|
56
56
|
}
|
|
57
|
-
const MAX_BODY_SIZE =
|
|
57
|
+
const MAX_BODY_SIZE = 100 * 1024; // 100KB - increased from 10KB for full payload visibility
|
|
58
58
|
export function captureRequestBody(body) {
|
|
59
59
|
if (!body) {
|
|
60
60
|
return body;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/utils/SentryUtil.ts"],"sourcesContent":["import * as Sentry from \"@sentry/node\";\nimport type { Context } from \"koa\";\n\nlet sentryInitialized = false;\n\nexport function initializeSentry(): void {\n if (sentryInitialized) {\n return;\n }\n\n const dsn = process.env.SENTRY_DSN;\n const nodeEnv = process.env.NODE_ENV;\n\n if (!dsn || nodeEnv !== \"production\") {\n return;\n }\n\n Sentry.init({\n dsn,\n environment: nodeEnv,\n enabled: true,\n tracesSampleRate: 0,\n });\n\n sentryInitialized = true;\n}\n\nexport function isSentryEnabled(): boolean {\n return sentryInitialized;\n}\n\nconst SENSITIVE_FIELDS = [\n \"password\",\n \"token\",\n \"apikey\",\n \"secret\",\n \"cookie\",\n];\n\nexport function scrubObject(obj: any, depth = 0): any {\n if (depth > 10) {\n return \"[Max Depth]\";\n }\n\n if (obj === null || obj === undefined) {\n return obj;\n }\n\n if (typeof obj !== \"object\") {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => scrubObject(item, depth + 1));\n }\n\n const scrubbedObj: any = {};\n for (const [key, value] of Object.entries(obj)) {\n const keyLower = key.toLowerCase();\n const isSensitive = SENSITIVE_FIELDS.some((field) =>\n keyLower.includes(field),\n );\n\n if (isSensitive) {\n scrubbedObj[key] = \"[REDACTED]\";\n } else if (typeof value === \"object\" && value !== null) {\n scrubbedObj[key] = scrubObject(value, depth + 1);\n } else {\n scrubbedObj[key] = value;\n }\n }\n\n return scrubbedObj;\n}\n\nconst MAX_BODY_SIZE =
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/utils/SentryUtil.ts"],"sourcesContent":["import * as Sentry from \"@sentry/node\";\nimport type { Context } from \"koa\";\n\nlet sentryInitialized = false;\n\nexport function initializeSentry(): void {\n if (sentryInitialized) {\n return;\n }\n\n const dsn = process.env.SENTRY_DSN;\n const nodeEnv = process.env.NODE_ENV;\n\n if (!dsn || nodeEnv !== \"production\") {\n return;\n }\n\n Sentry.init({\n dsn,\n environment: nodeEnv,\n enabled: true,\n tracesSampleRate: 0,\n });\n\n sentryInitialized = true;\n}\n\nexport function isSentryEnabled(): boolean {\n return sentryInitialized;\n}\n\nconst SENSITIVE_FIELDS = [\n \"password\",\n \"token\",\n \"apikey\",\n \"secret\",\n \"cookie\",\n];\n\nexport function scrubObject(obj: any, depth = 0): any {\n if (depth > 10) {\n return \"[Max Depth]\";\n }\n\n if (obj === null || obj === undefined) {\n return obj;\n }\n\n if (typeof obj !== \"object\") {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => scrubObject(item, depth + 1));\n }\n\n const scrubbedObj: any = {};\n for (const [key, value] of Object.entries(obj)) {\n const keyLower = key.toLowerCase();\n const isSensitive = SENSITIVE_FIELDS.some((field) =>\n keyLower.includes(field),\n );\n\n if (isSensitive) {\n scrubbedObj[key] = \"[REDACTED]\";\n } else if (typeof value === \"object\" && value !== null) {\n scrubbedObj[key] = scrubObject(value, depth + 1);\n } else {\n scrubbedObj[key] = value;\n }\n }\n\n return scrubbedObj;\n}\n\nconst MAX_BODY_SIZE = 100 * 1024; // 100KB - increased from 10KB for full payload visibility\n\nexport function captureRequestBody(body: any): any {\n if (!body) {\n return body;\n }\n\n const bodyString = JSON.stringify(body);\n if (bodyString.length > MAX_BODY_SIZE) {\n return {\n _truncated: true,\n _originalSize: bodyString.length,\n preview: bodyString.substring(0, MAX_BODY_SIZE),\n };\n }\n\n return scrubObject(body);\n}\n\nexport function extractUserContext(ctx: Context): { id?: string } {\n const user = (ctx.state as any)?.user;\n if (user?.userUuid) {\n return { id: user.userUuid };\n }\n return {};\n}\n\nexport function buildRequestContext(ctx: Context): {\n method: string;\n url: string;\n query: any;\n headers: any;\n data: any;\n ip?: string;\n} {\n return {\n method: ctx.method,\n url: ctx.url,\n query: scrubObject(ctx.query),\n headers: scrubObject(ctx.headers),\n data: captureRequestBody((ctx.request as any).body),\n ip: ctx.ip,\n };\n}\n\nexport function buildResponseContext(ctx: Context): { status_code: number } {\n return {\n status_code: ctx.status,\n };\n}\n\nexport interface ErrorReportOptions {\n operational?: boolean;\n errorType?: string;\n statusCode?: number;\n}\n\nexport function reportErrorToSentry(\n error: Error,\n ctx?: Context,\n options?: ErrorReportOptions,\n): void {\n if (!isSentryEnabled()) {\n return;\n }\n\n Sentry.withScope((scope) => {\n if (ctx) {\n scope.setContext(\"request\", buildRequestContext(ctx));\n scope.setContext(\"response\", buildResponseContext(ctx));\n scope.setUser(extractUserContext(ctx));\n }\n\n if (options?.operational !== undefined) {\n scope.setTag(\"error.operational\", options.operational);\n }\n if (options?.errorType) {\n scope.setTag(\"error.type\", options.errorType);\n }\n if (options?.statusCode) {\n scope.setTag(\"http.status_code\", options.statusCode);\n }\n\n scope.setTag(\"source\", \"middleware\");\n\n Sentry.captureException(error);\n });\n\n (error as any)._sentryReported = true;\n}\n\nexport type SeverityLevel = \"fatal\" | \"error\" | \"warning\" | \"info\" | \"debug\";\n\nexport function reportMessageToSentry(\n message: string,\n level: SeverityLevel,\n context?: Record<string, any>,\n): void {\n if (!isSentryEnabled()) {\n return;\n }\n\n Sentry.withScope((scope) => {\n if (context) {\n for (const [key, value] of Object.entries(context)) {\n if (key === \"logger_name\" || key === \"source\") {\n scope.setTag(key, value);\n } else {\n // Deeply serialize objects and arrays to avoid [Object] and [Array] in Sentry\n const serializedValue =\n typeof value === \"object\" && value !== null\n ? scrubObject(value)\n : value;\n scope.setContext(key, serializedValue);\n }\n }\n }\n\n Sentry.captureMessage(message, level);\n });\n}\n"],"names":["Sentry","sentryInitialized","initializeSentry","dsn","process","env","SENTRY_DSN","nodeEnv","NODE_ENV","init","environment","enabled","tracesSampleRate","isSentryEnabled","SENSITIVE_FIELDS","scrubObject","obj","depth","undefined","Array","isArray","map","item","scrubbedObj","key","value","Object","entries","keyLower","toLowerCase","isSensitive","some","field","includes","MAX_BODY_SIZE","captureRequestBody","body","bodyString","JSON","stringify","length","_truncated","_originalSize","preview","substring","extractUserContext","ctx","user","state","userUuid","id","buildRequestContext","method","url","query","headers","data","request","ip","buildResponseContext","status_code","status","reportErrorToSentry","error","options","withScope","scope","setContext","setUser","operational","setTag","errorType","statusCode","captureException","_sentryReported","reportMessageToSentry","message","level","context","serializedValue","captureMessage"],"mappings":"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"}
|