@rebasepro/server-core 0.4.0 → 0.6.0
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/README.md +62 -25
- package/dist/{server-core/src/api → api}/errors.d.ts +20 -2
- package/dist/{server-core/src/api → api}/rest/api-generator.d.ts +8 -2
- package/dist/{server-core/src/api → api}/types.d.ts +2 -0
- package/dist/auth/admin-user-ops.d.ts +79 -0
- package/dist/{server-core/src/auth → auth}/api-keys/index.d.ts +2 -2
- package/dist/{server-core/src/auth → auth}/auth-hooks.d.ts +37 -9
- package/dist/{server-core/src/auth → auth}/builtin-auth-adapter.d.ts +4 -4
- package/dist/{server-core/src/auth → auth}/index.d.ts +5 -2
- package/dist/{server-core/src/auth → auth}/jwt.d.ts +10 -0
- package/dist/auth/mfa-routes.d.ts +6 -0
- package/dist/auth/reset-password-admin.d.ts +29 -0
- package/dist/auth/session-routes.d.ts +25 -0
- package/dist/backend-CIxN4FVm.js +15 -0
- package/dist/backend-CIxN4FVm.js.map +1 -0
- package/dist/chunk-Dze3rakg.js +42 -0
- package/dist/{server-core/src/cron → cron}/cron-scheduler.d.ts +1 -1
- package/dist/dist-CZKP-Xz4.js +832 -0
- package/dist/dist-CZKP-Xz4.js.map +1 -0
- package/dist/env.d.ts +102 -0
- package/dist/from-VbwD7xRf.js +3849 -0
- package/dist/from-VbwD7xRf.js.map +1 -0
- package/dist/{server-core/src/index.d.ts → index.d.ts} +1 -0
- package/dist/index.es.js +60871 -50424
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +73151 -51256
- package/dist/index.umd.js.map +1 -1
- package/dist/init/docs.d.ts +4 -0
- package/dist/init/health.d.ts +2 -0
- package/dist/init/middlewares.d.ts +10 -0
- package/dist/init/shutdown.d.ts +11 -0
- package/dist/init/storage.d.ts +5 -0
- package/dist/{server-core/src/init.d.ts → init.d.ts} +4 -4
- package/dist/jwt-DHcQRGC3.js +4168 -0
- package/dist/jwt-DHcQRGC3.js.map +1 -0
- package/dist/logger-BYU66ENZ.js +94 -0
- package/dist/logger-BYU66ENZ.js.map +1 -0
- package/dist/ms-BeBSuOXN.js +125 -0
- package/dist/ms-BeBSuOXN.js.map +1 -0
- package/dist/multipart-parser-CedBDOeC.js +299 -0
- package/dist/multipart-parser-CedBDOeC.js.map +1 -0
- package/dist/src-COaj0G3P.js +1182 -0
- package/dist/src-COaj0G3P.js.map +1 -0
- package/dist/{server-core/src/storage → storage}/image-transform.d.ts +3 -1
- package/dist/utils/request-id.d.ts +4 -0
- package/package.json +29 -29
- package/src/api/collections_for_test/callbacks_test_collection.ts +7 -6
- package/src/api/errors.ts +95 -20
- package/src/api/graphql/graphql-schema-generator.ts +15 -10
- package/src/api/logs-routes.ts +5 -2
- package/src/api/openapi-generator.ts +1 -1
- package/src/api/rest/api-generator-count.test.ts +21 -10
- package/src/api/rest/api-generator.ts +151 -36
- package/src/api/rest/query-parser.ts +18 -12
- package/src/api/server.ts +13 -2
- package/src/api/types.ts +2 -0
- package/src/auth/adapter-middleware.ts +17 -10
- package/src/auth/admin-user-ops.ts +236 -0
- package/src/auth/api-keys/api-key-middleware.ts +15 -9
- package/src/auth/api-keys/api-key-permission-guard.ts +1 -1
- package/src/auth/api-keys/api-key-routes.ts +3 -3
- package/src/auth/api-keys/api-key-store.ts +14 -8
- package/src/auth/api-keys/index.ts +2 -2
- package/src/auth/apple-oauth.ts +4 -3
- package/src/auth/auth-hooks.ts +50 -10
- package/src/auth/bitbucket-oauth.ts +5 -4
- package/src/auth/builtin-auth-adapter.ts +64 -54
- package/src/auth/custom-auth-adapter.ts +4 -5
- package/src/auth/discord-oauth.ts +5 -4
- package/src/auth/facebook-oauth.ts +5 -4
- package/src/auth/github-oauth.ts +6 -5
- package/src/auth/gitlab-oauth.ts +5 -4
- package/src/auth/google-oauth.ts +2 -1
- package/src/auth/index.ts +6 -2
- package/src/auth/jwt.ts +15 -4
- package/src/auth/linkedin-oauth.ts +4 -3
- package/src/auth/mfa-routes.ts +299 -0
- package/src/auth/mfa.ts +2 -1
- package/src/auth/microsoft-oauth.ts +5 -4
- package/src/auth/middleware.ts +5 -4
- package/src/auth/rate-limiter.ts +1 -1
- package/src/auth/reset-password-admin.ts +144 -0
- package/src/auth/rls-scope.ts +1 -1
- package/src/auth/routes.ts +52 -600
- package/src/auth/session-routes.ts +341 -0
- package/src/auth/slack-oauth.ts +6 -5
- package/src/auth/spotify-oauth.ts +5 -4
- package/src/auth/twitter-oauth.ts +4 -3
- package/src/collections/loader.ts +5 -4
- package/src/cron/cron-scheduler.test.ts +23 -11
- package/src/cron/cron-scheduler.ts +14 -8
- package/src/cron/cron-store.ts +1 -1
- package/src/email/smtp-email-service.ts +3 -2
- package/src/env.ts +11 -10
- package/src/index.ts +1 -0
- package/src/init/docs.ts +47 -0
- package/src/init/health.ts +37 -0
- package/src/init/middlewares.ts +61 -0
- package/src/init/shutdown.ts +56 -0
- package/src/init/storage.ts +57 -0
- package/src/init.ts +93 -295
- package/src/serve-spa.ts +12 -5
- package/src/services/driver-registry.ts +4 -3
- package/src/storage/S3StorageController.ts +1 -2
- package/src/storage/image-transform.ts +25 -10
- package/src/storage/routes.ts +30 -11
- package/src/storage/storage-registry.ts +4 -3
- package/src/storage/tus-handler.ts +12 -12
- package/src/utils/request-id.ts +40 -0
- package/src/utils/request-logger.ts +6 -0
- package/test/api-generator.test.ts +90 -2
- package/test/api-key-permission-guard.test.ts +24 -12
- package/test/auth-routes.test.ts +5 -3
- package/test/backend-hooks-data.test.ts +99 -30
- package/test/custom-auth-adapter.test.ts +16 -15
- package/test/email-templates.test.ts +10 -5
- package/test/env.test.ts +10 -10
- package/test/function-loader.test.ts +7 -4
- package/test/graphql-schema-generator.test.ts +554 -0
- package/test/jwt-security.test.ts +1 -1
- package/test/query-parser.test.ts +0 -1
- package/test/reset-password-admin.test.ts +113 -0
- package/test/singleton.test.ts +5 -5
- package/test/smtp-email-service.test.ts +21 -21
- package/test/webhook-service.test.ts +22 -11
- package/tsconfig.json +2 -0
- package/tsconfig.prod.json +3 -0
- package/vite.config.ts +5 -5
- package/dist/common/src/collections/CollectionRegistry.d.ts +0 -56
- package/dist/common/src/collections/default-collections.d.ts +0 -9
- package/dist/common/src/collections/index.d.ts +0 -2
- package/dist/common/src/data/buildRebaseData.d.ts +0 -14
- package/dist/common/src/data/query_builder.d.ts +0 -55
- package/dist/common/src/index.d.ts +0 -4
- package/dist/common/src/util/builders.d.ts +0 -57
- package/dist/common/src/util/callbacks.d.ts +0 -6
- package/dist/common/src/util/collections.d.ts +0 -11
- package/dist/common/src/util/common.d.ts +0 -2
- package/dist/common/src/util/conditions.d.ts +0 -26
- package/dist/common/src/util/entities.d.ts +0 -58
- package/dist/common/src/util/enums.d.ts +0 -3
- package/dist/common/src/util/index.d.ts +0 -16
- package/dist/common/src/util/navigation_from_path.d.ts +0 -34
- package/dist/common/src/util/navigation_utils.d.ts +0 -20
- package/dist/common/src/util/parent_references_from_path.d.ts +0 -6
- package/dist/common/src/util/paths.d.ts +0 -14
- package/dist/common/src/util/permissions.d.ts +0 -6
- package/dist/common/src/util/references.d.ts +0 -2
- package/dist/common/src/util/relations.d.ts +0 -22
- package/dist/common/src/util/resolutions.d.ts +0 -72
- package/dist/common/src/util/storage.d.ts +0 -24
- package/dist/index-Cr1D21av.js +0 -49
- package/dist/index-Cr1D21av.js.map +0 -1
- package/dist/server-core/src/auth/admin-routes.d.ts +0 -27
- package/dist/server-core/src/env.d.ts +0 -137
- package/dist/types/src/controllers/analytics_controller.d.ts +0 -7
- package/dist/types/src/controllers/auth.d.ts +0 -104
- package/dist/types/src/controllers/client.d.ts +0 -168
- package/dist/types/src/controllers/collection_registry.d.ts +0 -46
- package/dist/types/src/controllers/customization_controller.d.ts +0 -60
- package/dist/types/src/controllers/data.d.ts +0 -207
- package/dist/types/src/controllers/data_driver.d.ts +0 -218
- package/dist/types/src/controllers/database_admin.d.ts +0 -11
- package/dist/types/src/controllers/dialogs_controller.d.ts +0 -36
- package/dist/types/src/controllers/effective_role.d.ts +0 -4
- package/dist/types/src/controllers/email.d.ts +0 -36
- package/dist/types/src/controllers/index.d.ts +0 -18
- package/dist/types/src/controllers/local_config_persistence.d.ts +0 -20
- package/dist/types/src/controllers/navigation.d.ts +0 -225
- package/dist/types/src/controllers/registry.d.ts +0 -63
- package/dist/types/src/controllers/side_dialogs_controller.d.ts +0 -67
- package/dist/types/src/controllers/side_entity_controller.d.ts +0 -97
- package/dist/types/src/controllers/snackbar.d.ts +0 -24
- package/dist/types/src/controllers/storage.d.ts +0 -171
- package/dist/types/src/index.d.ts +0 -4
- package/dist/types/src/rebase_context.d.ts +0 -122
- package/dist/types/src/types/auth_adapter.d.ts +0 -301
- package/dist/types/src/types/backend.d.ts +0 -536
- package/dist/types/src/types/backend_hooks.d.ts +0 -172
- package/dist/types/src/types/builders.d.ts +0 -15
- package/dist/types/src/types/chips.d.ts +0 -5
- package/dist/types/src/types/collections.d.ts +0 -941
- package/dist/types/src/types/component_ref.d.ts +0 -47
- package/dist/types/src/types/cron.d.ts +0 -102
- package/dist/types/src/types/data_source.d.ts +0 -64
- package/dist/types/src/types/database_adapter.d.ts +0 -94
- package/dist/types/src/types/entities.d.ts +0 -145
- package/dist/types/src/types/entity_actions.d.ts +0 -104
- package/dist/types/src/types/entity_callbacks.d.ts +0 -173
- package/dist/types/src/types/entity_link_builder.d.ts +0 -7
- package/dist/types/src/types/entity_overrides.d.ts +0 -10
- package/dist/types/src/types/entity_views.d.ts +0 -87
- package/dist/types/src/types/export_import.d.ts +0 -21
- package/dist/types/src/types/formex.d.ts +0 -40
- package/dist/types/src/types/index.d.ts +0 -28
- package/dist/types/src/types/locales.d.ts +0 -4
- package/dist/types/src/types/modify_collections.d.ts +0 -5
- package/dist/types/src/types/plugins.d.ts +0 -282
- package/dist/types/src/types/properties.d.ts +0 -1181
- package/dist/types/src/types/property_config.d.ts +0 -74
- package/dist/types/src/types/relations.d.ts +0 -336
- package/dist/types/src/types/slots.d.ts +0 -262
- package/dist/types/src/types/translations.d.ts +0 -900
- package/dist/types/src/types/user_management_delegate.d.ts +0 -86
- package/dist/types/src/types/websockets.d.ts +0 -78
- package/dist/types/src/users/index.d.ts +0 -1
- package/dist/types/src/users/user.d.ts +0 -50
- package/src/auth/admin-routes.ts +0 -534
- package/test/admin-routes.test.ts +0 -544
- package/test/backend-hooks-admin.test.ts +0 -388
- package/test.ts +0 -6
- /package/dist/{server-core/src/api → api}/ast-schema-editor.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/collections_for_test/callbacks_test_collection.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/graphql/graphql-schema-generator.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/graphql/index.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/index.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/logs-routes.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/openapi-generator.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/rest/index.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/rest/query-parser.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/schema-editor-routes.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/server.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/adapter-middleware.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/api-keys/api-key-middleware.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/api-keys/api-key-permission-guard.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/api-keys/api-key-routes.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/api-keys/api-key-store.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/api-keys/api-key-types.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/apple-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/bitbucket-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/crypto-utils.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/custom-auth-adapter.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/discord-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/facebook-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/github-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/gitlab-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/google-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/interfaces.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/linkedin-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/mfa.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/microsoft-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/middleware.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/password.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/rate-limiter.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/rls-scope.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/routes.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/slack-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/spotify-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/twitter-oauth.d.ts +0 -0
- /package/dist/{server-core/src/collections → collections}/BackendCollectionRegistry.d.ts +0 -0
- /package/dist/{server-core/src/collections → collections}/loader.d.ts +0 -0
- /package/dist/{server-core/src/cron → cron}/cron-loader.d.ts +0 -0
- /package/dist/{server-core/src/cron → cron}/cron-routes.d.ts +0 -0
- /package/dist/{server-core/src/cron → cron}/cron-store.d.ts +0 -0
- /package/dist/{server-core/src/cron → cron}/index.d.ts +0 -0
- /package/dist/{server-core/src/db → db}/interfaces.d.ts +0 -0
- /package/dist/{server-core/src/email → email}/index.d.ts +0 -0
- /package/dist/{server-core/src/email → email}/smtp-email-service.d.ts +0 -0
- /package/dist/{server-core/src/email → email}/templates.d.ts +0 -0
- /package/dist/{server-core/src/email → email}/types.d.ts +0 -0
- /package/dist/{server-core/src/functions → functions}/function-loader.d.ts +0 -0
- /package/dist/{server-core/src/functions → functions}/function-routes.d.ts +0 -0
- /package/dist/{server-core/src/functions → functions}/index.d.ts +0 -0
- /package/dist/{server-core/src/history → history}/history-routes.d.ts +0 -0
- /package/dist/{server-core/src/history → history}/index.d.ts +0 -0
- /package/dist/{server-core/src/serve-spa.d.ts → serve-spa.d.ts} +0 -0
- /package/dist/{server-core/src/services → services}/driver-registry.d.ts +0 -0
- /package/dist/{server-core/src/services → services}/webhook-service.d.ts +0 -0
- /package/dist/{server-core/src/singleton.d.ts → singleton.d.ts} +0 -0
- /package/dist/{server-core/src/storage → storage}/LocalStorageController.d.ts +0 -0
- /package/dist/{server-core/src/storage → storage}/S3StorageController.d.ts +0 -0
- /package/dist/{server-core/src/storage → storage}/index.d.ts +0 -0
- /package/dist/{server-core/src/storage → storage}/routes.d.ts +0 -0
- /package/dist/{server-core/src/storage → storage}/storage-registry.d.ts +0 -0
- /package/dist/{server-core/src/storage → storage}/tus-handler.d.ts +0 -0
- /package/dist/{server-core/src/storage → storage}/types.d.ts +0 -0
- /package/dist/{server-core/src/types → types}/index.d.ts +0 -0
- /package/dist/{server-core/src/utils → utils}/dev-port.d.ts +0 -0
- /package/dist/{server-core/src/utils → utils}/logger.d.ts +0 -0
- /package/dist/{server-core/src/utils → utils}/logging.d.ts +0 -0
- /package/dist/{server-core/src/utils → utils}/request-logger.d.ts +0 -0
- /package/dist/{server-core/src/utils → utils}/sql.d.ts +0 -0
|
@@ -32,13 +32,25 @@ function createMockDriver(): jest.Mocked<DataDriver> {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
const mockCollections: EntityCollection[] = [
|
|
35
|
-
{ slug: "products",
|
|
36
|
-
|
|
35
|
+
{ slug: "products",
|
|
36
|
+
name: "Products",
|
|
37
|
+
singularName: "Product",
|
|
38
|
+
properties: {} } as any,
|
|
39
|
+
{ slug: "orders",
|
|
40
|
+
name: "Orders",
|
|
41
|
+
singularName: "Order",
|
|
42
|
+
properties: {} } as any
|
|
37
43
|
];
|
|
38
44
|
|
|
39
45
|
function createApp(mockDriver: jest.Mocked<DataDriver>, hooks?: DataHooks) {
|
|
40
46
|
const app = new Hono();
|
|
41
47
|
app.onError(errorHandler);
|
|
48
|
+
// RestApiGenerator.getScopedDriver reads from c.get("driver") which is
|
|
49
|
+
// normally set by auth middleware. Replicate that here for tests.
|
|
50
|
+
app.use("/api/*", async (c, next) => {
|
|
51
|
+
c.set("driver", mockDriver);
|
|
52
|
+
await next();
|
|
53
|
+
});
|
|
42
54
|
const generator = new RestApiGenerator(mockCollections, mockDriver, hooks);
|
|
43
55
|
app.route("/api", generator.generateRoutes());
|
|
44
56
|
return app;
|
|
@@ -62,7 +74,8 @@ describe("DataHooks — REST API", () => {
|
|
|
62
74
|
afterRead(slug, entity) {
|
|
63
75
|
// Mask price for non-premium entities
|
|
64
76
|
if (slug === "products") {
|
|
65
|
-
return { ...entity,
|
|
77
|
+
return { ...entity,
|
|
78
|
+
price: "***" };
|
|
66
79
|
}
|
|
67
80
|
return entity;
|
|
68
81
|
}
|
|
@@ -70,8 +83,14 @@ describe("DataHooks — REST API", () => {
|
|
|
70
83
|
const app = createApp(mockDriver, hooks);
|
|
71
84
|
|
|
72
85
|
mockDriver.fetchCollection.mockResolvedValue([
|
|
73
|
-
{ id: "p1",
|
|
74
|
-
|
|
86
|
+
{ id: "p1",
|
|
87
|
+
path: "products",
|
|
88
|
+
values: { name: "Widget",
|
|
89
|
+
price: 99 } } as any,
|
|
90
|
+
{ id: "p2",
|
|
91
|
+
path: "products",
|
|
92
|
+
values: { name: "Gadget",
|
|
93
|
+
price: 199 } } as any
|
|
75
94
|
]);
|
|
76
95
|
mockDriver.countEntities!.mockResolvedValue(2);
|
|
77
96
|
|
|
@@ -97,9 +116,18 @@ describe("DataHooks — REST API", () => {
|
|
|
97
116
|
const app = createApp(mockDriver, hooks);
|
|
98
117
|
|
|
99
118
|
mockDriver.fetchCollection.mockResolvedValue([
|
|
100
|
-
{ id: "p1",
|
|
101
|
-
|
|
102
|
-
|
|
119
|
+
{ id: "p1",
|
|
120
|
+
path: "products",
|
|
121
|
+
values: { name: "Published",
|
|
122
|
+
status: "active" } } as any,
|
|
123
|
+
{ id: "p2",
|
|
124
|
+
path: "products",
|
|
125
|
+
values: { name: "Draft",
|
|
126
|
+
status: "draft" } } as any,
|
|
127
|
+
{ id: "p3",
|
|
128
|
+
path: "products",
|
|
129
|
+
values: { name: "Also Published",
|
|
130
|
+
status: "active" } } as any
|
|
103
131
|
]);
|
|
104
132
|
mockDriver.countEntities!.mockResolvedValue(3);
|
|
105
133
|
|
|
@@ -112,13 +140,16 @@ describe("DataHooks — REST API", () => {
|
|
|
112
140
|
it("transforms single entity GET response", async () => {
|
|
113
141
|
const hooks: DataHooks = {
|
|
114
142
|
afterRead(slug, entity) {
|
|
115
|
-
return { ...entity,
|
|
143
|
+
return { ...entity,
|
|
144
|
+
_readAt: "2024-01-01" };
|
|
116
145
|
}
|
|
117
146
|
};
|
|
118
147
|
const app = createApp(mockDriver, hooks);
|
|
119
148
|
|
|
120
149
|
mockDriver.fetchEntity.mockResolvedValue(
|
|
121
|
-
{ id: "p1",
|
|
150
|
+
{ id: "p1",
|
|
151
|
+
path: "products",
|
|
152
|
+
values: { name: "Widget" } } as any
|
|
122
153
|
);
|
|
123
154
|
|
|
124
155
|
const res = await app.request("/api/products/p1");
|
|
@@ -139,7 +170,9 @@ describe("DataHooks — REST API", () => {
|
|
|
139
170
|
const app = createApp(mockDriver, hooks);
|
|
140
171
|
|
|
141
172
|
mockDriver.fetchEntity.mockResolvedValue(
|
|
142
|
-
{ id: "hidden",
|
|
173
|
+
{ id: "hidden",
|
|
174
|
+
path: "products",
|
|
175
|
+
values: { name: "Secret" } } as any
|
|
143
176
|
);
|
|
144
177
|
|
|
145
178
|
const res = await app.request("/api/products/hidden");
|
|
@@ -150,7 +183,8 @@ describe("DataHooks — REST API", () => {
|
|
|
150
183
|
const hooks: DataHooks = {
|
|
151
184
|
afterRead(slug, entity) {
|
|
152
185
|
if (slug === "products") {
|
|
153
|
-
return { ...entity,
|
|
186
|
+
return { ...entity,
|
|
187
|
+
hooked: true };
|
|
154
188
|
}
|
|
155
189
|
return entity;
|
|
156
190
|
}
|
|
@@ -159,7 +193,9 @@ describe("DataHooks — REST API", () => {
|
|
|
159
193
|
|
|
160
194
|
// Products should be hooked
|
|
161
195
|
mockDriver.fetchEntity.mockResolvedValueOnce(
|
|
162
|
-
{ id: "p1",
|
|
196
|
+
{ id: "p1",
|
|
197
|
+
path: "products",
|
|
198
|
+
values: { name: "Widget" } } as any
|
|
163
199
|
);
|
|
164
200
|
const prodRes = await app.request("/api/products/p1");
|
|
165
201
|
const prodBody = await prodRes.json() as any;
|
|
@@ -167,7 +203,9 @@ describe("DataHooks — REST API", () => {
|
|
|
167
203
|
|
|
168
204
|
// Orders should NOT be hooked
|
|
169
205
|
mockDriver.fetchEntity.mockResolvedValueOnce(
|
|
170
|
-
{ id: "o1",
|
|
206
|
+
{ id: "o1",
|
|
207
|
+
path: "orders",
|
|
208
|
+
values: { total: 42 } } as any
|
|
171
209
|
);
|
|
172
210
|
const orderRes = await app.request("/api/orders/o1");
|
|
173
211
|
const orderBody = await orderRes.json() as any;
|
|
@@ -180,13 +218,17 @@ describe("DataHooks — REST API", () => {
|
|
|
180
218
|
it("transforms values before POST (create)", async () => {
|
|
181
219
|
const hooks: DataHooks = {
|
|
182
220
|
beforeSave(slug, values, entityId) {
|
|
183
|
-
return { ...values,
|
|
221
|
+
return { ...values,
|
|
222
|
+
slug: values.name?.toString().toLowerCase().replace(/\s+/g, "-") };
|
|
184
223
|
}
|
|
185
224
|
};
|
|
186
225
|
const app = createApp(mockDriver, hooks);
|
|
187
226
|
|
|
188
227
|
mockDriver.saveEntity.mockResolvedValue(
|
|
189
|
-
{ id: "new-1",
|
|
228
|
+
{ id: "new-1",
|
|
229
|
+
path: "products",
|
|
230
|
+
values: { name: "Cool Widget",
|
|
231
|
+
slug: "cool-widget" } } as any
|
|
190
232
|
);
|
|
191
233
|
|
|
192
234
|
const res = await app.request("/api/products", {
|
|
@@ -207,14 +249,20 @@ describe("DataHooks — REST API", () => {
|
|
|
207
249
|
const hooks: DataHooks = {
|
|
208
250
|
beforeSave(slug, values, entityId) {
|
|
209
251
|
// Add an updatedBy field
|
|
210
|
-
return { ...values,
|
|
252
|
+
return { ...values,
|
|
253
|
+
updatedBy: "hook" };
|
|
211
254
|
}
|
|
212
255
|
};
|
|
213
256
|
const app = createApp(mockDriver, hooks);
|
|
214
257
|
|
|
215
|
-
mockDriver.fetchEntity.mockResolvedValue({ id: "p1",
|
|
258
|
+
mockDriver.fetchEntity.mockResolvedValue({ id: "p1",
|
|
259
|
+
path: "products",
|
|
260
|
+
values: {} } as any);
|
|
216
261
|
mockDriver.saveEntity.mockResolvedValue(
|
|
217
|
-
{ id: "p1",
|
|
262
|
+
{ id: "p1",
|
|
263
|
+
path: "products",
|
|
264
|
+
values: { name: "Updated",
|
|
265
|
+
updatedBy: "hook" } } as any
|
|
218
266
|
);
|
|
219
267
|
|
|
220
268
|
const res = await app.request("/api/products/p1", {
|
|
@@ -238,7 +286,9 @@ describe("DataHooks — REST API", () => {
|
|
|
238
286
|
|
|
239
287
|
// POST
|
|
240
288
|
mockDriver.saveEntity.mockResolvedValueOnce(
|
|
241
|
-
{ id: "new-1",
|
|
289
|
+
{ id: "new-1",
|
|
290
|
+
path: "products",
|
|
291
|
+
values: { name: "A" } } as any
|
|
242
292
|
);
|
|
243
293
|
await app.request("/api/products", {
|
|
244
294
|
method: "POST",
|
|
@@ -248,9 +298,13 @@ describe("DataHooks — REST API", () => {
|
|
|
248
298
|
expect(beforeSaveSpy.mock.calls[0][2]).toBeUndefined(); // entityId
|
|
249
299
|
|
|
250
300
|
// PUT
|
|
251
|
-
mockDriver.fetchEntity.mockResolvedValueOnce({ id: "p1",
|
|
301
|
+
mockDriver.fetchEntity.mockResolvedValueOnce({ id: "p1",
|
|
302
|
+
path: "products",
|
|
303
|
+
values: {} } as any);
|
|
252
304
|
mockDriver.saveEntity.mockResolvedValueOnce(
|
|
253
|
-
{ id: "p1",
|
|
305
|
+
{ id: "p1",
|
|
306
|
+
path: "products",
|
|
307
|
+
values: { name: "B" } } as any
|
|
254
308
|
);
|
|
255
309
|
await app.request("/api/products/p1", {
|
|
256
310
|
method: "PUT",
|
|
@@ -289,7 +343,9 @@ describe("DataHooks — REST API", () => {
|
|
|
289
343
|
const app = createApp(mockDriver, hooks);
|
|
290
344
|
|
|
291
345
|
mockDriver.saveEntity.mockResolvedValue(
|
|
292
|
-
{ id: "new-1",
|
|
346
|
+
{ id: "new-1",
|
|
347
|
+
path: "products",
|
|
348
|
+
values: { name: "Widget" } } as any
|
|
293
349
|
);
|
|
294
350
|
|
|
295
351
|
const res = await app.request("/api/products", {
|
|
@@ -303,7 +359,8 @@ describe("DataHooks — REST API", () => {
|
|
|
303
359
|
expect(afterSaveSpy).toHaveBeenCalledTimes(1);
|
|
304
360
|
expect(afterSaveSpy).toHaveBeenCalledWith(
|
|
305
361
|
"products",
|
|
306
|
-
expect.objectContaining({ id: "new-1",
|
|
362
|
+
expect.objectContaining({ id: "new-1",
|
|
363
|
+
name: "Widget" }),
|
|
307
364
|
expect.objectContaining({ method: "POST" })
|
|
308
365
|
);
|
|
309
366
|
});
|
|
@@ -313,9 +370,13 @@ describe("DataHooks — REST API", () => {
|
|
|
313
370
|
const hooks: DataHooks = { afterSave: afterSaveSpy };
|
|
314
371
|
const app = createApp(mockDriver, hooks);
|
|
315
372
|
|
|
316
|
-
mockDriver.fetchEntity.mockResolvedValue({ id: "p1",
|
|
373
|
+
mockDriver.fetchEntity.mockResolvedValue({ id: "p1",
|
|
374
|
+
path: "products",
|
|
375
|
+
values: {} } as any);
|
|
317
376
|
mockDriver.saveEntity.mockResolvedValue(
|
|
318
|
-
{ id: "p1",
|
|
377
|
+
{ id: "p1",
|
|
378
|
+
path: "products",
|
|
379
|
+
values: { name: "Updated" } } as any
|
|
319
380
|
);
|
|
320
381
|
|
|
321
382
|
await app.request("/api/products/p1", {
|
|
@@ -346,7 +407,9 @@ describe("DataHooks — REST API", () => {
|
|
|
346
407
|
const app = createApp(mockDriver, hooks);
|
|
347
408
|
|
|
348
409
|
mockDriver.fetchEntity.mockResolvedValue(
|
|
349
|
-
{ id: "protected",
|
|
410
|
+
{ id: "protected",
|
|
411
|
+
path: "products",
|
|
412
|
+
values: {} } as any
|
|
350
413
|
);
|
|
351
414
|
|
|
352
415
|
const res = await app.request("/api/products/protected", { method: "DELETE" });
|
|
@@ -359,7 +422,9 @@ describe("DataHooks — REST API", () => {
|
|
|
359
422
|
const hooks: DataHooks = { beforeDelete: beforeDeleteSpy };
|
|
360
423
|
const app = createApp(mockDriver, hooks);
|
|
361
424
|
|
|
362
|
-
const existingEntity = { id: "p1",
|
|
425
|
+
const existingEntity = { id: "p1",
|
|
426
|
+
path: "products",
|
|
427
|
+
values: {} } as any;
|
|
363
428
|
mockDriver.fetchEntity.mockResolvedValue(existingEntity);
|
|
364
429
|
mockDriver.deleteEntity.mockResolvedValue();
|
|
365
430
|
|
|
@@ -376,7 +441,9 @@ describe("DataHooks — REST API", () => {
|
|
|
376
441
|
const hooks: DataHooks = { afterDelete: afterDeleteSpy };
|
|
377
442
|
const app = createApp(mockDriver, hooks);
|
|
378
443
|
|
|
379
|
-
mockDriver.fetchEntity.mockResolvedValue({ id: "p1",
|
|
444
|
+
mockDriver.fetchEntity.mockResolvedValue({ id: "p1",
|
|
445
|
+
path: "products",
|
|
446
|
+
values: {} } as any);
|
|
380
447
|
mockDriver.deleteEntity.mockResolvedValue();
|
|
381
448
|
|
|
382
449
|
await app.request("/api/products/p1", { method: "DELETE" });
|
|
@@ -393,7 +460,9 @@ describe("DataHooks — REST API", () => {
|
|
|
393
460
|
it("returns data unchanged when no hooks are provided", async () => {
|
|
394
461
|
const app = createApp(mockDriver); // no hooks
|
|
395
462
|
mockDriver.fetchCollection.mockResolvedValue([
|
|
396
|
-
{ id: "p1",
|
|
463
|
+
{ id: "p1",
|
|
464
|
+
path: "products",
|
|
465
|
+
values: { name: "Widget" } } as any
|
|
397
466
|
]);
|
|
398
467
|
mockDriver.countEntities!.mockResolvedValue(1);
|
|
399
468
|
|
|
@@ -7,20 +7,20 @@ const TEST_USER: AuthenticatedUser = {
|
|
|
7
7
|
displayName: "Test User",
|
|
8
8
|
roles: ["editor"],
|
|
9
9
|
isAdmin: false,
|
|
10
|
-
rawToken: "tok_abc"
|
|
10
|
+
rawToken: "tok_abc"
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
const ADMIN_USER: AuthenticatedUser = {
|
|
14
14
|
uid: "admin-1",
|
|
15
15
|
email: "admin@example.com",
|
|
16
16
|
roles: ["admin"],
|
|
17
|
-
isAdmin: true
|
|
17
|
+
isAdmin: true
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
describe("createCustomAuthAdapter", () => {
|
|
21
21
|
it("sets the adapter id to 'custom'", () => {
|
|
22
22
|
const adapter = createCustomAuthAdapter({
|
|
23
|
-
verifyRequest: async () => null
|
|
23
|
+
verifyRequest: async () => null
|
|
24
24
|
});
|
|
25
25
|
expect(adapter.id).toBe("custom");
|
|
26
26
|
});
|
|
@@ -29,7 +29,7 @@ describe("createCustomAuthAdapter", () => {
|
|
|
29
29
|
const verifyRequest = jest.fn(async () => TEST_USER);
|
|
30
30
|
const adapter = createCustomAuthAdapter({ verifyRequest });
|
|
31
31
|
const req = new Request("http://localhost/api", {
|
|
32
|
-
headers: { Authorization: "Bearer test-token" }
|
|
32
|
+
headers: { Authorization: "Bearer test-token" }
|
|
33
33
|
});
|
|
34
34
|
const result = await adapter.verifyRequest(req);
|
|
35
35
|
expect(verifyRequest).toHaveBeenCalledWith(req);
|
|
@@ -38,7 +38,7 @@ describe("createCustomAuthAdapter", () => {
|
|
|
38
38
|
|
|
39
39
|
it("returns null from verifyRequest when the user function returns null", async () => {
|
|
40
40
|
const adapter = createCustomAuthAdapter({
|
|
41
|
-
verifyRequest: async () => null
|
|
41
|
+
verifyRequest: async () => null
|
|
42
42
|
});
|
|
43
43
|
const result = await adapter.verifyRequest(new Request("http://localhost/"));
|
|
44
44
|
expect(result).toBeNull();
|
|
@@ -68,7 +68,7 @@ describe("createCustomAuthAdapter", () => {
|
|
|
68
68
|
|
|
69
69
|
it("returns null from fallback verifyToken for invalid token", async () => {
|
|
70
70
|
const adapter = createCustomAuthAdapter({
|
|
71
|
-
verifyRequest: async () => null
|
|
71
|
+
verifyRequest: async () => null
|
|
72
72
|
});
|
|
73
73
|
const result = await adapter.verifyToken!("bad-token");
|
|
74
74
|
expect(result).toBeNull();
|
|
@@ -84,7 +84,7 @@ describe("createCustomAuthAdapter", () => {
|
|
|
84
84
|
const verifyRequest = jest.fn(async () => null);
|
|
85
85
|
const adapter = createCustomAuthAdapter({
|
|
86
86
|
verifyRequest,
|
|
87
|
-
verifyToken: customVerifyToken
|
|
87
|
+
verifyToken: customVerifyToken
|
|
88
88
|
});
|
|
89
89
|
|
|
90
90
|
const result = await adapter.verifyToken!("direct-token");
|
|
@@ -97,7 +97,7 @@ describe("createCustomAuthAdapter", () => {
|
|
|
97
97
|
it("returns null from user-provided verifyToken for unknown token", async () => {
|
|
98
98
|
const adapter = createCustomAuthAdapter({
|
|
99
99
|
verifyRequest: async () => null,
|
|
100
|
-
verifyToken: async () => null
|
|
100
|
+
verifyToken: async () => null
|
|
101
101
|
});
|
|
102
102
|
const result = await adapter.verifyToken!("unknown");
|
|
103
103
|
expect(result).toBeNull();
|
|
@@ -107,7 +107,7 @@ describe("createCustomAuthAdapter", () => {
|
|
|
107
107
|
|
|
108
108
|
it("returns default capabilities when none are overridden", async () => {
|
|
109
109
|
const adapter = createCustomAuthAdapter({
|
|
110
|
-
verifyRequest: async () => null
|
|
110
|
+
verifyRequest: async () => null
|
|
111
111
|
});
|
|
112
112
|
const caps = await adapter.getCapabilities!();
|
|
113
113
|
expect(caps).toEqual({
|
|
@@ -118,14 +118,15 @@ describe("createCustomAuthAdapter", () => {
|
|
|
118
118
|
sessionManagement: false,
|
|
119
119
|
profileUpdate: false,
|
|
120
120
|
emailVerification: false,
|
|
121
|
-
enabledProviders: []
|
|
121
|
+
enabledProviders: []
|
|
122
122
|
});
|
|
123
123
|
});
|
|
124
124
|
|
|
125
125
|
it("merges user-provided capabilities with defaults", async () => {
|
|
126
126
|
const adapter = createCustomAuthAdapter({
|
|
127
127
|
verifyRequest: async () => null,
|
|
128
|
-
capabilities: { emailPasswordLogin: true,
|
|
128
|
+
capabilities: { emailPasswordLogin: true,
|
|
129
|
+
registration: true }
|
|
129
130
|
});
|
|
130
131
|
const caps = await adapter.getCapabilities!();
|
|
131
132
|
expect(caps.emailPasswordLogin).toBe(true);
|
|
@@ -138,7 +139,7 @@ describe("createCustomAuthAdapter", () => {
|
|
|
138
139
|
it("passes through serviceKey", () => {
|
|
139
140
|
const adapter = createCustomAuthAdapter({
|
|
140
141
|
verifyRequest: async () => null,
|
|
141
|
-
serviceKey: "sk_live_123"
|
|
142
|
+
serviceKey: "sk_live_123"
|
|
142
143
|
});
|
|
143
144
|
expect(adapter.serviceKey).toBe("sk_live_123");
|
|
144
145
|
});
|
|
@@ -149,12 +150,12 @@ describe("createCustomAuthAdapter", () => {
|
|
|
149
150
|
listUsers: jest.fn(),
|
|
150
151
|
createUser: jest.fn(),
|
|
151
152
|
updateUser: jest.fn(),
|
|
152
|
-
deleteUser: jest.fn()
|
|
153
|
+
deleteUser: jest.fn()
|
|
153
154
|
};
|
|
154
155
|
|
|
155
156
|
const adapter = createCustomAuthAdapter({
|
|
156
157
|
verifyRequest: async () => null,
|
|
157
|
-
userManagement: userMgmt as unknown as CustomAuthAdapterOptions["userManagement"]
|
|
158
|
+
userManagement: userMgmt as unknown as CustomAuthAdapterOptions["userManagement"]
|
|
158
159
|
});
|
|
159
160
|
|
|
160
161
|
expect(adapter.userManagement).toBe(userMgmt);
|
|
@@ -162,7 +163,7 @@ describe("createCustomAuthAdapter", () => {
|
|
|
162
163
|
|
|
163
164
|
it("omits userManagement when not provided", () => {
|
|
164
165
|
const adapter = createCustomAuthAdapter({
|
|
165
|
-
verifyRequest: async () => null
|
|
166
|
+
verifyRequest: async () => null
|
|
166
167
|
});
|
|
167
168
|
expect(adapter.userManagement).toBeUndefined();
|
|
168
169
|
});
|
|
@@ -6,7 +6,8 @@ import {
|
|
|
6
6
|
} from "../src/email/templates";
|
|
7
7
|
|
|
8
8
|
describe("getPasswordResetTemplate", () => {
|
|
9
|
-
const user = { email: "john@example.com",
|
|
9
|
+
const user = { email: "john@example.com",
|
|
10
|
+
displayName: "John Doe" };
|
|
10
11
|
const resetUrl = "https://example.com/reset?token=abc123";
|
|
11
12
|
|
|
12
13
|
it("returns subject, html, and text", () => {
|
|
@@ -43,7 +44,8 @@ describe("getPasswordResetTemplate", () => {
|
|
|
43
44
|
});
|
|
44
45
|
|
|
45
46
|
it("falls back to email prefix when displayName is null", () => {
|
|
46
|
-
const userNoName = { email: "jane@example.com",
|
|
47
|
+
const userNoName = { email: "jane@example.com",
|
|
48
|
+
displayName: null };
|
|
47
49
|
const result = getPasswordResetTemplate(resetUrl, userNoName);
|
|
48
50
|
expect(result.html).toContain("jane");
|
|
49
51
|
expect(result.text).toContain("jane");
|
|
@@ -58,7 +60,8 @@ describe("getPasswordResetTemplate", () => {
|
|
|
58
60
|
});
|
|
59
61
|
|
|
60
62
|
describe("getEmailVerificationTemplate", () => {
|
|
61
|
-
const user = { email: "alice@example.com",
|
|
63
|
+
const user = { email: "alice@example.com",
|
|
64
|
+
displayName: "Alice" };
|
|
62
65
|
const verifyUrl = "https://example.com/verify?token=xyz789";
|
|
63
66
|
|
|
64
67
|
it("returns subject, html, and text", () => {
|
|
@@ -96,7 +99,8 @@ describe("getEmailVerificationTemplate", () => {
|
|
|
96
99
|
});
|
|
97
100
|
|
|
98
101
|
describe("getUserInvitationTemplate", () => {
|
|
99
|
-
const user = { email: "bob@example.com",
|
|
102
|
+
const user = { email: "bob@example.com",
|
|
103
|
+
displayName: "Bob" };
|
|
100
104
|
const setPasswordUrl = "https://example.com/set-password?token=def456";
|
|
101
105
|
|
|
102
106
|
it("returns subject, html, and text", () => {
|
|
@@ -129,7 +133,8 @@ describe("getUserInvitationTemplate", () => {
|
|
|
129
133
|
});
|
|
130
134
|
|
|
131
135
|
describe("getWelcomeEmailTemplate", () => {
|
|
132
|
-
const user = { email: "dave@example.com",
|
|
136
|
+
const user = { email: "dave@example.com",
|
|
137
|
+
displayName: "Dave" };
|
|
133
138
|
|
|
134
139
|
it("returns subject, html, and text", () => {
|
|
135
140
|
const result = getWelcomeEmailTemplate(user);
|
package/test/env.test.ts
CHANGED
|
@@ -38,7 +38,7 @@ describe("env configuration and localhost validation", () => {
|
|
|
38
38
|
process.env.JWT_SECRET = "12345678901234567890123456789012";
|
|
39
39
|
process.env.FRONTEND_URL = "https://my-app.com";
|
|
40
40
|
|
|
41
|
-
expect(() => loadEnv()).
|
|
41
|
+
expect(() => loadEnv()).toThrow(/postgresql:\/\/localhost:5432\/rebase/);
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
it("should fail validation in production if DATABASE_URL contains 127.0.0.1", () => {
|
|
@@ -47,7 +47,7 @@ describe("env configuration and localhost validation", () => {
|
|
|
47
47
|
process.env.JWT_SECRET = "12345678901234567890123456789012";
|
|
48
48
|
process.env.FRONTEND_URL = "https://my-app.com";
|
|
49
49
|
|
|
50
|
-
expect(() => loadEnv()).
|
|
50
|
+
expect(() => loadEnv()).toThrow(/postgresql:\/\/127\.0\.0\.1:5432\/rebase/);
|
|
51
51
|
});
|
|
52
52
|
|
|
53
53
|
it("should fail validation in production if DATABASE_URL contains an IPv6 loopback [::1]", () => {
|
|
@@ -56,7 +56,7 @@ describe("env configuration and localhost validation", () => {
|
|
|
56
56
|
process.env.JWT_SECRET = "12345678901234567890123456789012";
|
|
57
57
|
process.env.FRONTEND_URL = "https://my-app.com";
|
|
58
58
|
|
|
59
|
-
expect(() => loadEnv()).
|
|
59
|
+
expect(() => loadEnv()).toThrow(/postgresql:\/\/\[::1\]:5432\/rebase/);
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
it("should fail validation in production if DATABASE_URL contains a loopback in the 127.x.x.x range", () => {
|
|
@@ -65,7 +65,7 @@ describe("env configuration and localhost validation", () => {
|
|
|
65
65
|
process.env.JWT_SECRET = "12345678901234567890123456789012";
|
|
66
66
|
process.env.FRONTEND_URL = "https://my-app.com";
|
|
67
67
|
|
|
68
|
-
expect(() => loadEnv()).
|
|
68
|
+
expect(() => loadEnv()).toThrow(/postgresql:\/\/127\.0\.0\.2:5432\/rebase/);
|
|
69
69
|
});
|
|
70
70
|
|
|
71
71
|
it("should succeed validation in production with a non-localhost DATABASE_URL", () => {
|
|
@@ -113,11 +113,11 @@ describe("env configuration and localhost validation", () => {
|
|
|
113
113
|
|
|
114
114
|
const extension = {
|
|
115
115
|
extend: z.object({
|
|
116
|
-
EXTERNAL_SERVICE_URL: z.string().url()
|
|
117
|
-
})
|
|
116
|
+
EXTERNAL_SERVICE_URL: z.string().url()
|
|
117
|
+
})
|
|
118
118
|
};
|
|
119
119
|
|
|
120
|
-
expect(() => loadEnv(extension)).
|
|
120
|
+
expect(() => loadEnv(extension)).toThrow(/https:\/\/localhost:8080\/api/);
|
|
121
121
|
});
|
|
122
122
|
|
|
123
123
|
it("should validate and block plain host string matching localhost", () => {
|
|
@@ -129,10 +129,10 @@ describe("env configuration and localhost validation", () => {
|
|
|
129
129
|
|
|
130
130
|
const extension = {
|
|
131
131
|
extend: z.object({
|
|
132
|
-
DB_HOST: z.string()
|
|
133
|
-
})
|
|
132
|
+
DB_HOST: z.string()
|
|
133
|
+
})
|
|
134
134
|
};
|
|
135
135
|
|
|
136
|
-
expect(() => loadEnv(extension)).
|
|
136
|
+
expect(() => loadEnv(extension)).toThrow(/localhost/);
|
|
137
137
|
});
|
|
138
138
|
});
|
|
@@ -62,7 +62,8 @@ describe("Function Loader & Routes", () => {
|
|
|
62
62
|
afterAll(() => {
|
|
63
63
|
// Clean up temp directory
|
|
64
64
|
if (tempDir && fs.existsSync(tempDir)) {
|
|
65
|
-
fs.rmSync(tempDir, { recursive: true,
|
|
65
|
+
fs.rmSync(tempDir, { recursive: true,
|
|
66
|
+
force: true });
|
|
66
67
|
}
|
|
67
68
|
});
|
|
68
69
|
|
|
@@ -101,12 +102,14 @@ describe("Function Loader & Routes", () => {
|
|
|
101
102
|
// 1. Verify GET / lists loaded functions
|
|
102
103
|
const listRes = await routes.request("/");
|
|
103
104
|
expect(listRes.status).toBe(200);
|
|
104
|
-
|
|
105
|
+
|
|
105
106
|
const listData = await listRes.json();
|
|
106
107
|
expect(listData).toEqual({
|
|
107
108
|
functions: [
|
|
108
|
-
{ name: "valid-app",
|
|
109
|
-
|
|
109
|
+
{ name: "valid-app",
|
|
110
|
+
endpoint: "/functions/valid-app" },
|
|
111
|
+
{ name: "valid-factory",
|
|
112
|
+
endpoint: "/functions/valid-factory" }
|
|
110
113
|
]
|
|
111
114
|
});
|
|
112
115
|
|