@rebasepro/server-core 0.5.0 → 0.6.1

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.
Files changed (279) hide show
  1. package/dist/{server-core/src/api → api}/errors.d.ts +5 -2
  2. package/dist/{server-core/src/api → api}/rest/api-generator.d.ts +8 -2
  3. package/dist/{server-core/src/api → api}/types.d.ts +2 -0
  4. package/dist/auth/admin-user-ops.d.ts +79 -0
  5. package/dist/{server-core/src/auth → auth}/api-keys/index.d.ts +2 -2
  6. package/dist/{server-core/src/auth → auth}/auth-hooks.d.ts +37 -9
  7. package/dist/{server-core/src/auth → auth}/builtin-auth-adapter.d.ts +4 -4
  8. package/dist/{server-core/src/auth → auth}/index.d.ts +5 -2
  9. package/dist/auth/mfa-routes.d.ts +6 -0
  10. package/dist/auth/reset-password-admin.d.ts +29 -0
  11. package/dist/auth/session-routes.d.ts +25 -0
  12. package/dist/backend-CIxN4FVm.js +15 -0
  13. package/dist/backend-CIxN4FVm.js.map +1 -0
  14. package/dist/chunk-Dze3rakg.js +42 -0
  15. package/dist/{server-core/src/cron → cron}/cron-scheduler.d.ts +1 -1
  16. package/dist/dist-CZKP-Xz4.js +832 -0
  17. package/dist/dist-CZKP-Xz4.js.map +1 -0
  18. package/dist/env.d.ts +102 -0
  19. package/dist/from-VbwD7xRf.js +3849 -0
  20. package/dist/from-VbwD7xRf.js.map +1 -0
  21. package/dist/{server-core/src/index.d.ts → index.d.ts} +1 -0
  22. package/dist/index.es.js +60872 -50462
  23. package/dist/index.es.js.map +1 -1
  24. package/dist/index.umd.js +73156 -51298
  25. package/dist/index.umd.js.map +1 -1
  26. package/dist/init/docs.d.ts +4 -0
  27. package/dist/init/health.d.ts +2 -0
  28. package/dist/init/middlewares.d.ts +10 -0
  29. package/dist/init/shutdown.d.ts +11 -0
  30. package/dist/init/storage.d.ts +5 -0
  31. package/dist/{server-core/src/init.d.ts → init.d.ts} +4 -4
  32. package/dist/jwt-DHcQRGC3.js +4168 -0
  33. package/dist/jwt-DHcQRGC3.js.map +1 -0
  34. package/dist/logger-BYU66ENZ.js +94 -0
  35. package/dist/logger-BYU66ENZ.js.map +1 -0
  36. package/dist/ms-BeBSuOXN.js +125 -0
  37. package/dist/ms-BeBSuOXN.js.map +1 -0
  38. package/dist/multipart-parser-CedBDOeC.js +299 -0
  39. package/dist/multipart-parser-CedBDOeC.js.map +1 -0
  40. package/dist/src-COaj0G3P.js +1182 -0
  41. package/dist/src-COaj0G3P.js.map +1 -0
  42. package/dist/{server-core/src/storage → storage}/image-transform.d.ts +3 -1
  43. package/dist/utils/request-id.d.ts +4 -0
  44. package/package.json +29 -29
  45. package/src/api/collections_for_test/callbacks_test_collection.ts +7 -6
  46. package/src/api/errors.ts +75 -19
  47. package/src/api/graphql/graphql-schema-generator.ts +15 -10
  48. package/src/api/logs-routes.ts +5 -2
  49. package/src/api/rest/api-generator-count.test.ts +21 -10
  50. package/src/api/rest/api-generator.ts +102 -26
  51. package/src/api/rest/query-parser.ts +18 -12
  52. package/src/api/server.ts +12 -1
  53. package/src/api/types.ts +2 -0
  54. package/src/auth/adapter-middleware.ts +17 -10
  55. package/src/auth/admin-user-ops.ts +236 -0
  56. package/src/auth/api-keys/api-key-middleware.ts +15 -9
  57. package/src/auth/api-keys/api-key-permission-guard.ts +1 -1
  58. package/src/auth/api-keys/api-key-routes.ts +3 -3
  59. package/src/auth/api-keys/api-key-store.ts +14 -8
  60. package/src/auth/api-keys/index.ts +2 -2
  61. package/src/auth/apple-oauth.ts +4 -3
  62. package/src/auth/auth-hooks.ts +50 -10
  63. package/src/auth/bitbucket-oauth.ts +5 -4
  64. package/src/auth/builtin-auth-adapter.ts +60 -39
  65. package/src/auth/custom-auth-adapter.ts +4 -5
  66. package/src/auth/discord-oauth.ts +5 -4
  67. package/src/auth/facebook-oauth.ts +5 -4
  68. package/src/auth/github-oauth.ts +6 -5
  69. package/src/auth/gitlab-oauth.ts +5 -4
  70. package/src/auth/google-oauth.ts +2 -1
  71. package/src/auth/index.ts +6 -2
  72. package/src/auth/jwt.ts +5 -4
  73. package/src/auth/linkedin-oauth.ts +4 -3
  74. package/src/auth/mfa-routes.ts +299 -0
  75. package/src/auth/mfa.ts +2 -1
  76. package/src/auth/microsoft-oauth.ts +5 -4
  77. package/src/auth/middleware.ts +5 -4
  78. package/src/auth/rate-limiter.ts +1 -1
  79. package/src/auth/reset-password-admin.ts +144 -0
  80. package/src/auth/rls-scope.ts +1 -1
  81. package/src/auth/routes.ts +52 -596
  82. package/src/auth/session-routes.ts +341 -0
  83. package/src/auth/slack-oauth.ts +6 -5
  84. package/src/auth/spotify-oauth.ts +5 -4
  85. package/src/auth/twitter-oauth.ts +4 -3
  86. package/src/collections/loader.ts +5 -4
  87. package/src/cron/cron-scheduler.test.ts +23 -11
  88. package/src/cron/cron-scheduler.ts +14 -8
  89. package/src/cron/cron-store.ts +1 -1
  90. package/src/email/smtp-email-service.ts +3 -2
  91. package/src/env.ts +11 -10
  92. package/src/index.ts +1 -0
  93. package/src/init/docs.ts +47 -0
  94. package/src/init/health.ts +37 -0
  95. package/src/init/middlewares.ts +61 -0
  96. package/src/init/shutdown.ts +56 -0
  97. package/src/init/storage.ts +57 -0
  98. package/src/init.ts +85 -279
  99. package/src/serve-spa.ts +12 -5
  100. package/src/services/driver-registry.ts +4 -3
  101. package/src/storage/S3StorageController.ts +1 -2
  102. package/src/storage/image-transform.ts +25 -10
  103. package/src/storage/routes.ts +34 -11
  104. package/src/storage/storage-registry.ts +4 -3
  105. package/src/storage/tus-handler.ts +12 -12
  106. package/src/utils/request-id.ts +40 -0
  107. package/src/utils/request-logger.ts +6 -0
  108. package/test/api-generator.test.ts +90 -2
  109. package/test/api-key-permission-guard.test.ts +24 -12
  110. package/test/auth-routes.test.ts +5 -3
  111. package/test/backend-hooks-data.test.ts +99 -30
  112. package/test/custom-auth-adapter.test.ts +16 -15
  113. package/test/email-templates.test.ts +10 -5
  114. package/test/env.test.ts +10 -10
  115. package/test/function-loader.test.ts +7 -4
  116. package/test/graphql-schema-generator.test.ts +554 -0
  117. package/test/jwt-security.test.ts +1 -1
  118. package/test/query-parser.test.ts +0 -1
  119. package/test/reset-password-admin.test.ts +113 -0
  120. package/test/singleton.test.ts +5 -5
  121. package/test/smtp-email-service.test.ts +21 -21
  122. package/test/webhook-service.test.ts +22 -11
  123. package/tsconfig.json +2 -0
  124. package/tsconfig.prod.json +3 -0
  125. package/vite.config.ts +10 -5
  126. package/dist/common/src/collections/CollectionRegistry.d.ts +0 -56
  127. package/dist/common/src/collections/default-collections.d.ts +0 -9
  128. package/dist/common/src/collections/index.d.ts +0 -2
  129. package/dist/common/src/data/buildRebaseData.d.ts +0 -14
  130. package/dist/common/src/data/query_builder.d.ts +0 -55
  131. package/dist/common/src/index.d.ts +0 -4
  132. package/dist/common/src/util/builders.d.ts +0 -57
  133. package/dist/common/src/util/callbacks.d.ts +0 -6
  134. package/dist/common/src/util/collections.d.ts +0 -11
  135. package/dist/common/src/util/common.d.ts +0 -2
  136. package/dist/common/src/util/conditions.d.ts +0 -26
  137. package/dist/common/src/util/entities.d.ts +0 -58
  138. package/dist/common/src/util/enums.d.ts +0 -3
  139. package/dist/common/src/util/index.d.ts +0 -16
  140. package/dist/common/src/util/navigation_from_path.d.ts +0 -34
  141. package/dist/common/src/util/navigation_utils.d.ts +0 -20
  142. package/dist/common/src/util/parent_references_from_path.d.ts +0 -6
  143. package/dist/common/src/util/paths.d.ts +0 -14
  144. package/dist/common/src/util/permissions.d.ts +0 -14
  145. package/dist/common/src/util/references.d.ts +0 -2
  146. package/dist/common/src/util/relations.d.ts +0 -22
  147. package/dist/common/src/util/resolutions.d.ts +0 -72
  148. package/dist/common/src/util/storage.d.ts +0 -24
  149. package/dist/index-Cr1D21av.js +0 -49
  150. package/dist/index-Cr1D21av.js.map +0 -1
  151. package/dist/server-core/src/auth/admin-routes.d.ts +0 -27
  152. package/dist/server-core/src/env.d.ts +0 -137
  153. package/dist/types/src/controllers/analytics_controller.d.ts +0 -7
  154. package/dist/types/src/controllers/auth.d.ts +0 -104
  155. package/dist/types/src/controllers/client.d.ts +0 -168
  156. package/dist/types/src/controllers/collection_registry.d.ts +0 -46
  157. package/dist/types/src/controllers/customization_controller.d.ts +0 -60
  158. package/dist/types/src/controllers/data.d.ts +0 -207
  159. package/dist/types/src/controllers/data_driver.d.ts +0 -218
  160. package/dist/types/src/controllers/database_admin.d.ts +0 -11
  161. package/dist/types/src/controllers/dialogs_controller.d.ts +0 -36
  162. package/dist/types/src/controllers/effective_role.d.ts +0 -4
  163. package/dist/types/src/controllers/email.d.ts +0 -36
  164. package/dist/types/src/controllers/index.d.ts +0 -18
  165. package/dist/types/src/controllers/local_config_persistence.d.ts +0 -20
  166. package/dist/types/src/controllers/navigation.d.ts +0 -225
  167. package/dist/types/src/controllers/registry.d.ts +0 -63
  168. package/dist/types/src/controllers/side_dialogs_controller.d.ts +0 -67
  169. package/dist/types/src/controllers/side_entity_controller.d.ts +0 -97
  170. package/dist/types/src/controllers/snackbar.d.ts +0 -24
  171. package/dist/types/src/controllers/storage.d.ts +0 -171
  172. package/dist/types/src/index.d.ts +0 -4
  173. package/dist/types/src/rebase_context.d.ts +0 -122
  174. package/dist/types/src/types/auth_adapter.d.ts +0 -301
  175. package/dist/types/src/types/backend.d.ts +0 -571
  176. package/dist/types/src/types/backend_hooks.d.ts +0 -172
  177. package/dist/types/src/types/builders.d.ts +0 -15
  178. package/dist/types/src/types/chips.d.ts +0 -5
  179. package/dist/types/src/types/collections.d.ts +0 -961
  180. package/dist/types/src/types/component_ref.d.ts +0 -47
  181. package/dist/types/src/types/cron.d.ts +0 -102
  182. package/dist/types/src/types/data_source.d.ts +0 -64
  183. package/dist/types/src/types/database_adapter.d.ts +0 -94
  184. package/dist/types/src/types/entities.d.ts +0 -145
  185. package/dist/types/src/types/entity_actions.d.ts +0 -104
  186. package/dist/types/src/types/entity_callbacks.d.ts +0 -173
  187. package/dist/types/src/types/entity_link_builder.d.ts +0 -7
  188. package/dist/types/src/types/entity_overrides.d.ts +0 -10
  189. package/dist/types/src/types/entity_views.d.ts +0 -87
  190. package/dist/types/src/types/export_import.d.ts +0 -21
  191. package/dist/types/src/types/formex.d.ts +0 -40
  192. package/dist/types/src/types/index.d.ts +0 -28
  193. package/dist/types/src/types/locales.d.ts +0 -4
  194. package/dist/types/src/types/modify_collections.d.ts +0 -5
  195. package/dist/types/src/types/plugins.d.ts +0 -282
  196. package/dist/types/src/types/properties.d.ts +0 -1173
  197. package/dist/types/src/types/property_config.d.ts +0 -74
  198. package/dist/types/src/types/relations.d.ts +0 -336
  199. package/dist/types/src/types/slots.d.ts +0 -262
  200. package/dist/types/src/types/translations.d.ts +0 -900
  201. package/dist/types/src/types/user_management_delegate.d.ts +0 -86
  202. package/dist/types/src/types/websockets.d.ts +0 -78
  203. package/dist/types/src/users/index.d.ts +0 -1
  204. package/dist/types/src/users/user.d.ts +0 -50
  205. package/src/auth/admin-routes.ts +0 -534
  206. package/test/admin-routes.test.ts +0 -544
  207. package/test/backend-hooks-admin.test.ts +0 -388
  208. /package/dist/{server-core/src/api → api}/ast-schema-editor.d.ts +0 -0
  209. /package/dist/{server-core/src/api → api}/collections_for_test/callbacks_test_collection.d.ts +0 -0
  210. /package/dist/{server-core/src/api → api}/graphql/graphql-schema-generator.d.ts +0 -0
  211. /package/dist/{server-core/src/api → api}/graphql/index.d.ts +0 -0
  212. /package/dist/{server-core/src/api → api}/index.d.ts +0 -0
  213. /package/dist/{server-core/src/api → api}/logs-routes.d.ts +0 -0
  214. /package/dist/{server-core/src/api → api}/openapi-generator.d.ts +0 -0
  215. /package/dist/{server-core/src/api → api}/rest/index.d.ts +0 -0
  216. /package/dist/{server-core/src/api → api}/rest/query-parser.d.ts +0 -0
  217. /package/dist/{server-core/src/api → api}/schema-editor-routes.d.ts +0 -0
  218. /package/dist/{server-core/src/api → api}/server.d.ts +0 -0
  219. /package/dist/{server-core/src/auth → auth}/adapter-middleware.d.ts +0 -0
  220. /package/dist/{server-core/src/auth → auth}/api-keys/api-key-middleware.d.ts +0 -0
  221. /package/dist/{server-core/src/auth → auth}/api-keys/api-key-permission-guard.d.ts +0 -0
  222. /package/dist/{server-core/src/auth → auth}/api-keys/api-key-routes.d.ts +0 -0
  223. /package/dist/{server-core/src/auth → auth}/api-keys/api-key-store.d.ts +0 -0
  224. /package/dist/{server-core/src/auth → auth}/api-keys/api-key-types.d.ts +0 -0
  225. /package/dist/{server-core/src/auth → auth}/apple-oauth.d.ts +0 -0
  226. /package/dist/{server-core/src/auth → auth}/bitbucket-oauth.d.ts +0 -0
  227. /package/dist/{server-core/src/auth → auth}/crypto-utils.d.ts +0 -0
  228. /package/dist/{server-core/src/auth → auth}/custom-auth-adapter.d.ts +0 -0
  229. /package/dist/{server-core/src/auth → auth}/discord-oauth.d.ts +0 -0
  230. /package/dist/{server-core/src/auth → auth}/facebook-oauth.d.ts +0 -0
  231. /package/dist/{server-core/src/auth → auth}/github-oauth.d.ts +0 -0
  232. /package/dist/{server-core/src/auth → auth}/gitlab-oauth.d.ts +0 -0
  233. /package/dist/{server-core/src/auth → auth}/google-oauth.d.ts +0 -0
  234. /package/dist/{server-core/src/auth → auth}/interfaces.d.ts +0 -0
  235. /package/dist/{server-core/src/auth → auth}/jwt.d.ts +0 -0
  236. /package/dist/{server-core/src/auth → auth}/linkedin-oauth.d.ts +0 -0
  237. /package/dist/{server-core/src/auth → auth}/mfa.d.ts +0 -0
  238. /package/dist/{server-core/src/auth → auth}/microsoft-oauth.d.ts +0 -0
  239. /package/dist/{server-core/src/auth → auth}/middleware.d.ts +0 -0
  240. /package/dist/{server-core/src/auth → auth}/password.d.ts +0 -0
  241. /package/dist/{server-core/src/auth → auth}/rate-limiter.d.ts +0 -0
  242. /package/dist/{server-core/src/auth → auth}/rls-scope.d.ts +0 -0
  243. /package/dist/{server-core/src/auth → auth}/routes.d.ts +0 -0
  244. /package/dist/{server-core/src/auth → auth}/slack-oauth.d.ts +0 -0
  245. /package/dist/{server-core/src/auth → auth}/spotify-oauth.d.ts +0 -0
  246. /package/dist/{server-core/src/auth → auth}/twitter-oauth.d.ts +0 -0
  247. /package/dist/{server-core/src/collections → collections}/BackendCollectionRegistry.d.ts +0 -0
  248. /package/dist/{server-core/src/collections → collections}/loader.d.ts +0 -0
  249. /package/dist/{server-core/src/cron → cron}/cron-loader.d.ts +0 -0
  250. /package/dist/{server-core/src/cron → cron}/cron-routes.d.ts +0 -0
  251. /package/dist/{server-core/src/cron → cron}/cron-store.d.ts +0 -0
  252. /package/dist/{server-core/src/cron → cron}/index.d.ts +0 -0
  253. /package/dist/{server-core/src/db → db}/interfaces.d.ts +0 -0
  254. /package/dist/{server-core/src/email → email}/index.d.ts +0 -0
  255. /package/dist/{server-core/src/email → email}/smtp-email-service.d.ts +0 -0
  256. /package/dist/{server-core/src/email → email}/templates.d.ts +0 -0
  257. /package/dist/{server-core/src/email → email}/types.d.ts +0 -0
  258. /package/dist/{server-core/src/functions → functions}/function-loader.d.ts +0 -0
  259. /package/dist/{server-core/src/functions → functions}/function-routes.d.ts +0 -0
  260. /package/dist/{server-core/src/functions → functions}/index.d.ts +0 -0
  261. /package/dist/{server-core/src/history → history}/history-routes.d.ts +0 -0
  262. /package/dist/{server-core/src/history → history}/index.d.ts +0 -0
  263. /package/dist/{server-core/src/serve-spa.d.ts → serve-spa.d.ts} +0 -0
  264. /package/dist/{server-core/src/services → services}/driver-registry.d.ts +0 -0
  265. /package/dist/{server-core/src/services → services}/webhook-service.d.ts +0 -0
  266. /package/dist/{server-core/src/singleton.d.ts → singleton.d.ts} +0 -0
  267. /package/dist/{server-core/src/storage → storage}/LocalStorageController.d.ts +0 -0
  268. /package/dist/{server-core/src/storage → storage}/S3StorageController.d.ts +0 -0
  269. /package/dist/{server-core/src/storage → storage}/index.d.ts +0 -0
  270. /package/dist/{server-core/src/storage → storage}/routes.d.ts +0 -0
  271. /package/dist/{server-core/src/storage → storage}/storage-registry.d.ts +0 -0
  272. /package/dist/{server-core/src/storage → storage}/tus-handler.d.ts +0 -0
  273. /package/dist/{server-core/src/storage → storage}/types.d.ts +0 -0
  274. /package/dist/{server-core/src/types → types}/index.d.ts +0 -0
  275. /package/dist/{server-core/src/utils → utils}/dev-port.d.ts +0 -0
  276. /package/dist/{server-core/src/utils → utils}/logger.d.ts +0 -0
  277. /package/dist/{server-core/src/utils → utils}/logging.d.ts +0 -0
  278. /package/dist/{server-core/src/utils → utils}/request-logger.d.ts +0 -0
  279. /package/dist/{server-core/src/utils → utils}/sql.d.ts +0 -0
@@ -0,0 +1,554 @@
1
+ import { GraphQLSchemaGenerator } from "../src/api/graphql/graphql-schema-generator";
2
+ import { DataDriver } from "../../types/src/controllers/data_driver";
3
+ import { EntityCollection } from "../../types/src/types/collections";
4
+ import {
5
+ GraphQLSchema,
6
+ GraphQLObjectType,
7
+ GraphQLNonNull,
8
+ GraphQLString,
9
+ GraphQLFloat,
10
+ GraphQLBoolean,
11
+ GraphQLList,
12
+ GraphQLInputObjectType,
13
+ graphql,
14
+ printSchema
15
+ } from "graphql";
16
+
17
+ describe("GraphQLSchemaGenerator", () => {
18
+ let mockDriver: jest.Mocked<DataDriver>;
19
+ let collections: EntityCollection[];
20
+
21
+ beforeEach(() => {
22
+ mockDriver = {
23
+ key: "postgres",
24
+ initialised: true,
25
+ fetchCollection: jest.fn(),
26
+ listenCollection: jest.fn(),
27
+ fetchEntity: jest.fn(),
28
+ listenEntity: jest.fn(),
29
+ saveEntity: jest.fn(),
30
+ deleteEntity: jest.fn(),
31
+ checkUniqueField: jest.fn(),
32
+ countEntities: jest.fn(),
33
+ withAuth: jest.fn(),
34
+ admin: {} as any
35
+ } as unknown as jest.Mocked<DataDriver>;
36
+
37
+ collections = [
38
+ {
39
+ slug: "posts",
40
+ name: "Posts",
41
+ singularName: "Post",
42
+ description: "Blog posts",
43
+ properties: {
44
+ title: { name: "Title",
45
+ type: "string",
46
+ validation: { required: true } },
47
+ body: { name: "Body",
48
+ type: "string" },
49
+ views: { name: "Views",
50
+ type: "number" },
51
+ published: { name: "Published",
52
+ type: "boolean" },
53
+ createdAt: { name: "Created At",
54
+ type: "date" }
55
+ }
56
+ } as unknown as EntityCollection,
57
+ {
58
+ slug: "authors",
59
+ name: "Authors",
60
+ singularName: "Author",
61
+ properties: {
62
+ name: { name: "Name",
63
+ type: "string",
64
+ validation: { required: true } },
65
+ bio: { name: "Bio",
66
+ type: "string" }
67
+ }
68
+ } as unknown as EntityCollection
69
+ ];
70
+ });
71
+
72
+ // ─── Schema Generation ──────────────────────────────────────────
73
+
74
+ describe("generateSchema", () => {
75
+ it("returns a valid GraphQLSchema", () => {
76
+ const generator = new GraphQLSchemaGenerator(collections, mockDriver);
77
+ const schema = generator.generateSchema();
78
+ expect(schema).toBeInstanceOf(GraphQLSchema);
79
+ });
80
+
81
+ it("creates Query and Mutation root types", () => {
82
+ const generator = new GraphQLSchemaGenerator(collections, mockDriver);
83
+ const schema = generator.generateSchema();
84
+ expect(schema.getQueryType()).toBeDefined();
85
+ expect(schema.getMutationType()).toBeDefined();
86
+ });
87
+
88
+ it("generates a printable SDL without errors", () => {
89
+ const generator = new GraphQLSchemaGenerator(collections, mockDriver);
90
+ const schema = generator.generateSchema();
91
+ const sdl = printSchema(schema);
92
+ expect(sdl).toContain("type Post");
93
+ expect(sdl).toContain("type Author");
94
+ expect(sdl).toContain("type Query");
95
+ expect(sdl).toContain("type Mutation");
96
+ });
97
+ });
98
+
99
+ // ─── Entity Types ───────────────────────────────────────────────
100
+
101
+ describe("entity types", () => {
102
+ it("creates an object type for each collection", () => {
103
+ const generator = new GraphQLSchemaGenerator(collections, mockDriver);
104
+ const schema = generator.generateSchema();
105
+ const typeMap = schema.getTypeMap();
106
+ expect(typeMap["Post"]).toBeInstanceOf(GraphQLObjectType);
107
+ expect(typeMap["Author"]).toBeInstanceOf(GraphQLObjectType);
108
+ });
109
+
110
+ it("generates an id field as NonNull String", () => {
111
+ const generator = new GraphQLSchemaGenerator(collections, mockDriver);
112
+ const schema = generator.generateSchema();
113
+ const postType = schema.getType("Post") as GraphQLObjectType;
114
+ const fields = postType.getFields();
115
+ expect(fields.id).toBeDefined();
116
+ expect(fields.id.type).toBeInstanceOf(GraphQLNonNull);
117
+ expect((fields.id.type as GraphQLNonNull<any>).ofType).toBe(GraphQLString);
118
+ });
119
+
120
+ it("maps string properties to GraphQLString", () => {
121
+ const generator = new GraphQLSchemaGenerator(collections, mockDriver);
122
+ const schema = generator.generateSchema();
123
+ const postType = schema.getType("Post") as GraphQLObjectType;
124
+ const bodyField = postType.getFields().body;
125
+ expect(bodyField.type).toBe(GraphQLString);
126
+ });
127
+
128
+ it("maps number properties to GraphQLFloat", () => {
129
+ const generator = new GraphQLSchemaGenerator(collections, mockDriver);
130
+ const schema = generator.generateSchema();
131
+ const postType = schema.getType("Post") as GraphQLObjectType;
132
+ const viewsField = postType.getFields().views;
133
+ expect(viewsField.type).toBe(GraphQLFloat);
134
+ });
135
+
136
+ it("maps boolean properties to GraphQLBoolean", () => {
137
+ const generator = new GraphQLSchemaGenerator(collections, mockDriver);
138
+ const schema = generator.generateSchema();
139
+ const postType = schema.getType("Post") as GraphQLObjectType;
140
+ const publishedField = postType.getFields().published;
141
+ expect(publishedField.type).toBe(GraphQLBoolean);
142
+ });
143
+
144
+ it("maps date properties to GraphQLString", () => {
145
+ const generator = new GraphQLSchemaGenerator(collections, mockDriver);
146
+ const schema = generator.generateSchema();
147
+ const postType = schema.getType("Post") as GraphQLObjectType;
148
+ const createdAtField = postType.getFields().createdAt;
149
+ // Date is non-null because the original property definition doesn't set required,
150
+ // so it should be nullable GraphQLString
151
+ expect(createdAtField).toBeDefined();
152
+ });
153
+
154
+ it("wraps required properties in NonNull", () => {
155
+ const generator = new GraphQLSchemaGenerator(collections, mockDriver);
156
+ const schema = generator.generateSchema();
157
+ const postType = schema.getType("Post") as GraphQLObjectType;
158
+ const titleField = postType.getFields().title;
159
+ expect(titleField.type).toBeInstanceOf(GraphQLNonNull);
160
+ });
161
+
162
+ it("skips relation properties from entity fields", () => {
163
+ const withRelation: EntityCollection[] = [
164
+ {
165
+ slug: "comments",
166
+ name: "Comments",
167
+ singularName: "Comment",
168
+ properties: {
169
+ text: { name: "Text",
170
+ type: "string" },
171
+ author: { name: "Author",
172
+ type: "relation" }
173
+ }
174
+ } as unknown as EntityCollection
175
+ ];
176
+ const generator = new GraphQLSchemaGenerator(withRelation, mockDriver);
177
+ const schema = generator.generateSchema();
178
+ const commentType = schema.getType("Comment") as GraphQLObjectType;
179
+ const fields = commentType.getFields();
180
+ expect(fields.text).toBeDefined();
181
+ expect(fields.author).toBeUndefined();
182
+ });
183
+ });
184
+
185
+ // ─── Input Types ────────────────────────────────────────────────
186
+
187
+ describe("input types", () => {
188
+ it("creates input types for each collection", () => {
189
+ const generator = new GraphQLSchemaGenerator(collections, mockDriver);
190
+ const schema = generator.generateSchema();
191
+ const typeMap = schema.getTypeMap();
192
+ expect(typeMap["PostInput"]).toBeInstanceOf(GraphQLInputObjectType);
193
+ expect(typeMap["AuthorInput"]).toBeInstanceOf(GraphQLInputObjectType);
194
+ });
195
+
196
+ it("input types skip relation properties", () => {
197
+ const withRelation: EntityCollection[] = [
198
+ {
199
+ slug: "comments",
200
+ name: "Comments",
201
+ singularName: "Comment",
202
+ properties: {
203
+ text: { name: "Text",
204
+ type: "string" },
205
+ post: { name: "Post",
206
+ type: "relation" }
207
+ }
208
+ } as unknown as EntityCollection
209
+ ];
210
+ const generator = new GraphQLSchemaGenerator(withRelation, mockDriver);
211
+ const schema = generator.generateSchema();
212
+ const inputType = schema.getType("CommentInput") as GraphQLInputObjectType;
213
+ const fields = inputType.getFields();
214
+ expect(fields.text).toBeDefined();
215
+ expect(fields.post).toBeUndefined();
216
+ });
217
+ });
218
+
219
+ // ─── Query Resolvers ────────────────────────────────────────────
220
+
221
+ describe("query resolvers", () => {
222
+ it("creates single-entity and list queries for each collection", () => {
223
+ const generator = new GraphQLSchemaGenerator(collections, mockDriver);
224
+ const schema = generator.generateSchema();
225
+ const queryType = schema.getQueryType()!;
226
+ const fields = queryType.getFields();
227
+
228
+ // Single entity queries (lowercase singular name)
229
+ expect(fields.post).toBeDefined();
230
+ expect(fields.author).toBeDefined();
231
+
232
+ // List queries (slug)
233
+ expect(fields.posts).toBeDefined();
234
+ expect(fields.authors).toBeDefined();
235
+ });
236
+
237
+ it("single-entity query calls fetchEntity with correct args", async () => {
238
+ const generator = new GraphQLSchemaGenerator(collections, mockDriver);
239
+ const schema = generator.generateSchema();
240
+
241
+ const mockEntity = {
242
+ id: "1",
243
+ path: "posts",
244
+ values: { title: "Hello World",
245
+ body: "Content" }
246
+ };
247
+ mockDriver.fetchEntity.mockResolvedValue(mockEntity as any);
248
+
249
+ const result = await graphql({
250
+ schema,
251
+ source: "query { post(id: \"1\") { id title body } }",
252
+ contextValue: { driver: mockDriver }
253
+ });
254
+
255
+ expect(result.errors).toBeUndefined();
256
+ expect(mockDriver.fetchEntity).toHaveBeenCalledWith(
257
+ expect.objectContaining({
258
+ path: "posts",
259
+ entityId: "1"
260
+ })
261
+ );
262
+ });
263
+
264
+ it("list query calls fetchCollection with limit and offset", async () => {
265
+ const generator = new GraphQLSchemaGenerator(collections, mockDriver);
266
+ const schema = generator.generateSchema();
267
+
268
+ mockDriver.fetchCollection.mockResolvedValue([]);
269
+
270
+ const result = await graphql({
271
+ schema,
272
+ source: "query { posts(limit: 10, offset: 5) { id title } }",
273
+ contextValue: { driver: mockDriver }
274
+ });
275
+
276
+ expect(result.errors).toBeUndefined();
277
+ expect(mockDriver.fetchCollection).toHaveBeenCalledWith(
278
+ expect.objectContaining({
279
+ path: "posts",
280
+ limit: 10,
281
+ startAfter: "5"
282
+ })
283
+ );
284
+ });
285
+
286
+ it("list query parses JSON where filter", async () => {
287
+ const generator = new GraphQLSchemaGenerator(collections, mockDriver);
288
+ const schema = generator.generateSchema();
289
+
290
+ mockDriver.fetchCollection.mockResolvedValue([]);
291
+
292
+ const result = await graphql({
293
+ schema,
294
+ source: "query { posts(where: \"{\\\"published\\\": true}\") { id } }",
295
+ contextValue: { driver: mockDriver }
296
+ });
297
+
298
+ expect(result.errors).toBeUndefined();
299
+ expect(mockDriver.fetchCollection).toHaveBeenCalledWith(
300
+ expect.objectContaining({
301
+ filter: { published: true }
302
+ })
303
+ );
304
+ });
305
+
306
+ it("list query rejects invalid JSON where filter", async () => {
307
+ const generator = new GraphQLSchemaGenerator(collections, mockDriver);
308
+ const schema = generator.generateSchema();
309
+
310
+ const result = await graphql({
311
+ schema,
312
+ source: "query { posts(where: \"not-json\") { id } }",
313
+ contextValue: { driver: mockDriver }
314
+ });
315
+
316
+ expect(result.errors).toBeDefined();
317
+ expect(result.errors![0].message).toContain("Invalid 'where' filter");
318
+ });
319
+
320
+ it("list query rejects non-object JSON where filter", async () => {
321
+ const generator = new GraphQLSchemaGenerator(collections, mockDriver);
322
+ const schema = generator.generateSchema();
323
+
324
+ const result = await graphql({
325
+ schema,
326
+ source: "query { posts(where: \"[1,2,3]\") { id } }",
327
+ contextValue: { driver: mockDriver }
328
+ });
329
+
330
+ expect(result.errors).toBeDefined();
331
+ expect(result.errors![0].message).toContain("Filter must be a JSON object");
332
+ });
333
+ });
334
+
335
+ // ─── Mutation Resolvers ─────────────────────────────────────────
336
+
337
+ describe("mutation resolvers", () => {
338
+ it("creates create, update, and delete mutations for each collection", () => {
339
+ const generator = new GraphQLSchemaGenerator(collections, mockDriver);
340
+ const schema = generator.generateSchema();
341
+ const mutationType = schema.getMutationType()!;
342
+ const fields = mutationType.getFields();
343
+
344
+ expect(fields.createPost).toBeDefined();
345
+ expect(fields.updatePost).toBeDefined();
346
+ expect(fields.deletePost).toBeDefined();
347
+ expect(fields.createAuthor).toBeDefined();
348
+ expect(fields.updateAuthor).toBeDefined();
349
+ expect(fields.deleteAuthor).toBeDefined();
350
+ });
351
+
352
+ it("create mutation calls saveEntity with status 'new'", async () => {
353
+ const generator = new GraphQLSchemaGenerator(collections, mockDriver);
354
+ const schema = generator.generateSchema();
355
+
356
+ const mockEntity = {
357
+ id: "1",
358
+ path: "posts",
359
+ values: { title: "New Post" }
360
+ };
361
+ mockDriver.saveEntity.mockResolvedValue(mockEntity as any);
362
+
363
+ const result = await graphql({
364
+ schema,
365
+ source: "mutation { createPost(input: { title: \"New Post\" }) { id } }",
366
+ contextValue: { driver: mockDriver }
367
+ });
368
+
369
+ expect(result.errors).toBeUndefined();
370
+ expect(mockDriver.saveEntity).toHaveBeenCalledWith(
371
+ expect.objectContaining({
372
+ path: "posts",
373
+ values: { title: "New Post" },
374
+ status: "new"
375
+ })
376
+ );
377
+ });
378
+
379
+ it("update mutation calls saveEntity with status 'existing' and entityId", async () => {
380
+ const generator = new GraphQLSchemaGenerator(collections, mockDriver);
381
+ const schema = generator.generateSchema();
382
+
383
+ const mockEntity = {
384
+ id: "1",
385
+ path: "posts",
386
+ values: { title: "Updated" }
387
+ };
388
+ mockDriver.saveEntity.mockResolvedValue(mockEntity as any);
389
+
390
+ const result = await graphql({
391
+ schema,
392
+ source: "mutation { updatePost(id: \"1\", input: { title: \"Updated\" }) { id } }",
393
+ contextValue: { driver: mockDriver }
394
+ });
395
+
396
+ expect(result.errors).toBeUndefined();
397
+ expect(mockDriver.saveEntity).toHaveBeenCalledWith(
398
+ expect.objectContaining({
399
+ path: "posts",
400
+ entityId: "1",
401
+ values: { title: "Updated" },
402
+ status: "existing"
403
+ })
404
+ );
405
+ });
406
+
407
+ it("delete mutation returns true on success", async () => {
408
+ const generator = new GraphQLSchemaGenerator(collections, mockDriver);
409
+ const schema = generator.generateSchema();
410
+
411
+ const mockEntity = { id: "1",
412
+ path: "posts",
413
+ values: {} };
414
+ mockDriver.fetchEntity.mockResolvedValue(mockEntity as any);
415
+ mockDriver.deleteEntity.mockResolvedValue(undefined);
416
+
417
+ const result = await graphql({
418
+ schema,
419
+ source: "mutation { deletePost(id: \"1\") }",
420
+ contextValue: { driver: mockDriver }
421
+ });
422
+
423
+ expect(result.errors).toBeUndefined();
424
+ expect(result.data?.deletePost).toBe(true);
425
+ expect(mockDriver.deleteEntity).toHaveBeenCalledWith(
426
+ expect.objectContaining({
427
+ entity: mockEntity
428
+ })
429
+ );
430
+ });
431
+
432
+ it("delete mutation returns false when entity not found", async () => {
433
+ const generator = new GraphQLSchemaGenerator(collections, mockDriver);
434
+ const schema = generator.generateSchema();
435
+
436
+ mockDriver.fetchEntity.mockResolvedValue(undefined as any);
437
+
438
+ const result = await graphql({
439
+ schema,
440
+ source: "mutation { deletePost(id: \"nonexistent\") }",
441
+ contextValue: { driver: mockDriver }
442
+ });
443
+
444
+ expect(result.errors).toBeUndefined();
445
+ expect(result.data?.deletePost).toBe(false);
446
+ });
447
+
448
+ it("delete mutation returns false when deleteEntity throws", async () => {
449
+ const generator = new GraphQLSchemaGenerator(collections, mockDriver);
450
+ const schema = generator.generateSchema();
451
+
452
+ const mockEntity = { id: "1",
453
+ path: "posts",
454
+ values: {} };
455
+ mockDriver.fetchEntity.mockResolvedValue(mockEntity as any);
456
+ mockDriver.deleteEntity.mockRejectedValue(new Error("DB error"));
457
+
458
+ const result = await graphql({
459
+ schema,
460
+ source: "mutation { deletePost(id: \"1\") }",
461
+ contextValue: { driver: mockDriver }
462
+ });
463
+
464
+ expect(result.errors).toBeUndefined();
465
+ expect(result.data?.deletePost).toBe(false);
466
+ });
467
+ });
468
+
469
+ // ─── Edge Cases ─────────────────────────────────────────────────
470
+
471
+ describe("edge cases", () => {
472
+ it("handles empty collections array", () => {
473
+ const generator = new GraphQLSchemaGenerator([], mockDriver);
474
+ const schema = generator.generateSchema();
475
+ expect(schema).toBeInstanceOf(GraphQLSchema);
476
+ });
477
+
478
+ it("handles collection with array property", () => {
479
+ const arrayCollection: EntityCollection[] = [
480
+ {
481
+ slug: "tags",
482
+ name: "Tags",
483
+ singularName: "Tag",
484
+ properties: {
485
+ labels: { name: "Labels",
486
+ type: "array" }
487
+ }
488
+ } as unknown as EntityCollection
489
+ ];
490
+ const generator = new GraphQLSchemaGenerator(arrayCollection, mockDriver);
491
+ const schema = generator.generateSchema();
492
+ const tagType = schema.getType("Tag") as GraphQLObjectType;
493
+ const labelsField = tagType.getFields().labels;
494
+ expect(labelsField.type).toBeInstanceOf(GraphQLList);
495
+ });
496
+
497
+ it("handles collection with vector property", () => {
498
+ const vectorCollection: EntityCollection[] = [
499
+ {
500
+ slug: "embeddings",
501
+ name: "Embeddings",
502
+ singularName: "Embedding",
503
+ properties: {
504
+ embedding: { name: "Embedding",
505
+ type: "vector" }
506
+ }
507
+ } as unknown as EntityCollection
508
+ ];
509
+ const generator = new GraphQLSchemaGenerator(vectorCollection, mockDriver);
510
+ const schema = generator.generateSchema();
511
+ const embeddingType = schema.getType("Embedding") as GraphQLObjectType;
512
+ const embeddingField = embeddingType.getFields().embedding;
513
+ expect(embeddingField.type).toBeInstanceOf(GraphQLList);
514
+ });
515
+
516
+ it("handles collection with binary property", () => {
517
+ const binaryCollection: EntityCollection[] = [
518
+ {
519
+ slug: "files",
520
+ name: "Files",
521
+ singularName: "File",
522
+ properties: {
523
+ data: { name: "Data",
524
+ type: "binary" }
525
+ }
526
+ } as unknown as EntityCollection
527
+ ];
528
+ const generator = new GraphQLSchemaGenerator(binaryCollection, mockDriver);
529
+ const schema = generator.generateSchema();
530
+ const fileType = schema.getType("File") as GraphQLObjectType;
531
+ const dataField = fileType.getFields().data;
532
+ expect(dataField.type).toBe(GraphQLString);
533
+ });
534
+
535
+ it("deduplicates types when generateSchema is called", () => {
536
+ const dupeCollections: EntityCollection[] = [
537
+ {
538
+ slug: "items",
539
+ name: "Items",
540
+ singularName: "Item",
541
+ properties: {
542
+ name: { name: "Name",
543
+ type: "string" }
544
+ }
545
+ } as unknown as EntityCollection
546
+ ];
547
+ const generator = new GraphQLSchemaGenerator(dupeCollections, mockDriver);
548
+ // Calling generateSchema should not throw even if types are created internally multiple times
549
+ const schema = generator.generateSchema();
550
+ expect(schema.getType("Item")).toBeDefined();
551
+ expect(schema.getType("ItemInput")).toBeDefined();
552
+ });
553
+ });
554
+ });
@@ -66,7 +66,7 @@ refreshExpiresIn: "30d" });
66
66
 
67
67
  it("throws when secret is not configured", () => {
68
68
  // Force empty secret
69
- // eslint-disable-next-line @typescript-eslint/no-require-imports
69
+
70
70
  Object.defineProperty(require("../src/auth/jwt"), "jwtConfig", { value: { secret: "" },
71
71
  writable: true });
72
72
  // This won't work since jwtConfig is module-scoped, but generateAccessToken has its own check
@@ -157,7 +157,6 @@ describe("parseQueryOptions — PostgREST filters", () => {
157
157
  });
158
158
 
159
159
 
160
-
161
160
  // ─────────────────────────────────────────────────────────────
162
161
  // parseQueryOptions — Sorting
163
162
  // ─────────────────────────────────────────────────────────────
@@ -0,0 +1,113 @@
1
+ import { Hono } from "hono";
2
+ import { createResetPasswordRoute } from "../src/auth/reset-password-admin";
3
+ import type { AuthRepository } from "../src/auth/interfaces";
4
+ import { configureJwt, generateAccessToken } from "../src/auth/jwt";
5
+ import { errorHandler } from "../src/api/errors";
6
+
7
+ const TEST_SECRET = "integration-test-secret-key-that-is-definitely-32-chars-long!!";
8
+
9
+ describe("createResetPasswordRoute & onAdminResetPassword", () => {
10
+ let mockAuthRepo: jest.Mocked<AuthRepository>;
11
+ let mockEmailService: { send: jest.Mock; isConfigured: jest.Mock };
12
+
13
+ beforeAll(() => {
14
+ configureJwt({ secret: TEST_SECRET,
15
+ accessExpiresIn: "1h" });
16
+ });
17
+
18
+ beforeEach(() => {
19
+ mockAuthRepo = {
20
+ getUserById: jest.fn(),
21
+ getUserRoleIds: jest.fn().mockResolvedValue(["admin"]),
22
+ updatePassword: jest.fn().mockResolvedValue(undefined),
23
+ createPasswordResetToken: jest.fn().mockResolvedValue(undefined)
24
+ } as unknown as jest.Mocked<AuthRepository>;
25
+
26
+ mockEmailService = {
27
+ send: jest.fn().mockResolvedValue(undefined),
28
+ isConfigured: jest.fn().mockReturnValue(true)
29
+ };
30
+ });
31
+
32
+ function createApp(authHooks?: any) {
33
+ const app = new Hono();
34
+ app.onError(errorHandler);
35
+ const adminRoutes = createResetPasswordRoute({
36
+ authRepo: mockAuthRepo,
37
+ emailService: mockEmailService as any,
38
+ emailConfig: {
39
+ from: "test@example.com",
40
+ resetPasswordUrl: "https://reset.com"
41
+ },
42
+ authHooks
43
+ });
44
+ app.route("/api/admin", adminRoutes);
45
+ return app;
46
+ }
47
+
48
+ it("falls back to default password reset logic when no hook is provided", async () => {
49
+ mockAuthRepo.getUserById.mockResolvedValue({
50
+ id: "user-123",
51
+ email: "user@example.com",
52
+ displayName: "User One"
53
+ } as any);
54
+
55
+ const app = createApp();
56
+ const adminToken = generateAccessToken("admin-user", ["admin"]);
57
+
58
+ const res = await app.request("/api/admin/users/user-123/reset-password", {
59
+ method: "POST",
60
+ headers: {
61
+ "Authorization": `Bearer ${adminToken}`
62
+ }
63
+ });
64
+
65
+ expect(res.status).toBe(200);
66
+ const body = await res.json() as any;
67
+ expect(body.invitationSent).toBe(true);
68
+ expect(body.user.uid).toBe("user-123");
69
+ expect(mockAuthRepo.createPasswordResetToken).toHaveBeenCalled();
70
+ expect(mockEmailService.send).toHaveBeenCalled();
71
+ });
72
+
73
+ it("calls onAdminResetPassword hook when provided", async () => {
74
+ mockAuthRepo.getUserById.mockResolvedValue({
75
+ id: "user-123",
76
+ email: "user@example.com",
77
+ displayName: "User One"
78
+ } as any);
79
+
80
+ const onAdminResetPasswordMock = jest.fn().mockResolvedValue({
81
+ temporaryPassword: "hook-temp-password",
82
+ invitationSent: false
83
+ });
84
+
85
+ const app = createApp({
86
+ onAdminResetPassword: onAdminResetPasswordMock
87
+ });
88
+ const adminToken = generateAccessToken("admin-user", ["admin"]);
89
+
90
+ const res = await app.request("/api/admin/users/user-123/reset-password", {
91
+ method: "POST",
92
+ headers: {
93
+ "Authorization": `Bearer ${adminToken}`
94
+ }
95
+ });
96
+
97
+ expect(res.status).toBe(200);
98
+ const body = await res.json() as any;
99
+ expect(body.invitationSent).toBe(false);
100
+ expect(body.temporaryPassword).toBe("hook-temp-password");
101
+ expect(body.user.uid).toBe("user-123");
102
+
103
+ expect(onAdminResetPasswordMock).toHaveBeenCalledWith(
104
+ "user-123",
105
+ expect.objectContaining({
106
+ authRepo: mockAuthRepo,
107
+ emailService: mockEmailService
108
+ })
109
+ );
110
+ expect(mockAuthRepo.createPasswordResetToken).not.toHaveBeenCalled();
111
+ expect(mockEmailService.send).not.toHaveBeenCalled();
112
+ });
113
+ });