@restforgejs/platform 4.2.8 → 4.3.2

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 (325) hide show
  1. package/SECURITY.md +83 -4
  2. package/bin/sdf-tools.exe +0 -0
  3. package/build-info.json +2 -2
  4. package/cli/consumer-deploy.js +1 -1
  5. package/cli/consumer.js +1 -1
  6. package/generators/cli/dashboard/create.js +4 -1
  7. package/generators/cli/endpoint/create.js +1 -1
  8. package/generators/cli/key/generate.js +2 -1
  9. package/generators/cli/key/revoke.js +2 -1
  10. package/generators/cli/payload/diff.js +3 -2
  11. package/generators/cli/payload/generate.js +3 -2
  12. package/generators/cli/payload/sync.js +3 -2
  13. package/generators/cli/payload/validate.js +3 -2
  14. package/generators/cli/processor/create.js +14 -3
  15. package/generators/cli/project/delete.js +2 -1
  16. package/generators/cli/query/validate.js +3 -2
  17. package/generators/cli/schema/apply.js +3 -2
  18. package/generators/cli/schema/describe.js +3 -2
  19. package/generators/cli/schema/diff.js +3 -2
  20. package/generators/cli/schema/introspect.js +3 -2
  21. package/generators/cli/schema/list.js +3 -2
  22. package/generators/cli/schema/migrate.js +3 -2
  23. package/generators/lib/migration/audit-table-runner.js +213 -215
  24. package/generators/lib/payload/payload-runner.js +1 -1
  25. package/generators/lib/templates/dashboard-catalog.js +1 -437
  26. package/generators/lib/templates/db-connection-env.js +1 -212
  27. package/generators/lib/templates/dbschema-catalog.js +1 -489
  28. package/generators/lib/templates/field-validation-catalog.js +1 -531
  29. package/generators/lib/templates/mysql-template.js +1 -3863
  30. package/generators/lib/templates/oracle-template.js +1 -3915
  31. package/generators/lib/templates/postgres-template.js +1 -5838
  32. package/generators/lib/templates/query-declarative-catalog.js +1 -199
  33. package/generators/lib/templates/sqlite-template.js +1 -3440
  34. package/generators/lib/utils/env-manager.js +6 -0
  35. package/generators/lib/utils/path-validator.js +71 -0
  36. package/generators/lib/validators/payload-validator.js +1 -2
  37. package/integrity-manifest.json +28 -10
  38. package/package.json +11 -3
  39. package/scripts/verify-integrity.js +1 -1
  40. package/server.js +1 -1
  41. package/src/components/handlers/adjust_handler.js +1 -1
  42. package/src/components/handlers/audit_handler.js +1 -1
  43. package/src/components/handlers/delete_handler.js +1 -1
  44. package/src/components/handlers/export_handler.js +1 -1
  45. package/src/components/handlers/import_handler.js +1 -1
  46. package/src/components/handlers/insert_handler.js +1 -1
  47. package/src/components/handlers/update_handler.js +1 -1
  48. package/src/components/handlers/upload_handler.js +1 -1
  49. package/src/components/handlers/workflow_handler.js +1 -1
  50. package/src/components/integrations/webhook.js +1 -1
  51. package/src/consumers/baseConsumer.js +1 -1
  52. package/src/consumers/declarativeMapper.js +1 -1
  53. package/src/consumers/handlers/apiHandler.js +1 -1
  54. package/src/consumers/handlers/consoleHandler.js +1 -1
  55. package/src/consumers/handlers/databaseHandler.js +1 -1
  56. package/src/consumers/handlers/index.js +1 -1
  57. package/src/consumers/handlers/kafkaHandler.js +1 -1
  58. package/src/consumers/index.js +1 -1
  59. package/src/consumers/messageTransformer.js +1 -1
  60. package/src/consumers/validator.js +1 -1
  61. package/src/core/db/dialect/base-dialect.js +1 -1
  62. package/src/core/db/dialect/index.js +1 -1
  63. package/src/core/db/dialect/mysql-dialect.js +1 -1
  64. package/src/core/db/dialect/oracle-dialect.js +1 -1
  65. package/src/core/db/dialect/postgres-dialect.js +1 -1
  66. package/src/core/db/dialect/sqlite-dialect.js +1 -1
  67. package/src/core/db/flatten-helper.js +1 -1
  68. package/src/core/db/query-builder-error.js +1 -1
  69. package/src/core/db/query-builder.js +1 -1
  70. package/src/core/db/relation-helper.js +1 -1
  71. package/src/core/handlers/delete_handler.js +1 -1
  72. package/src/core/handlers/insert_handler.js +1 -1
  73. package/src/core/handlers/update_handler.js +1 -1
  74. package/src/core/models/base-model.js +1 -1
  75. package/src/core/utils/cache-manager.js +1 -1
  76. package/src/core/utils/component-engine.js +1 -1
  77. package/src/core/utils/context-builder.js +1 -1
  78. package/src/core/utils/datetime-formatter.js +1 -1
  79. package/src/core/utils/datetime-parser.js +1 -1
  80. package/src/core/utils/db.js +1 -1
  81. package/src/core/utils/logger.js +1 -1
  82. package/src/core/utils/payload-loader.js +1 -1
  83. package/src/core/utils/security-checks.js +1 -1
  84. package/src/middleware/body-options.js +1 -1
  85. package/src/middleware/cors.js +1 -1
  86. package/src/middleware/idempotency.js +1 -1
  87. package/src/middleware/rate-limiter.js +1 -1
  88. package/src/middleware/request-logger.js +1 -1
  89. package/src/middleware/security-headers.js +1 -1
  90. package/src/models/base-model-mysql.js +1 -1
  91. package/src/models/base-model-oracle.js +1 -1
  92. package/src/models/base-model-sqlite.js +1 -1
  93. package/src/models/base-model.js +1 -1
  94. package/src/pro/caching/redis-client.js +1 -1
  95. package/src/pro/caching/redis-helper.js +1 -1
  96. package/src/pro/consumers/baseConsumer.js +1 -1
  97. package/src/pro/consumers/declarativeMapper.js +1 -1
  98. package/src/pro/consumers/handlers/apiHandler.js +1 -1
  99. package/src/pro/consumers/handlers/consoleHandler.js +1 -1
  100. package/src/pro/consumers/handlers/databaseHandler.js +1 -1
  101. package/src/pro/consumers/handlers/index.js +1 -1
  102. package/src/pro/consumers/handlers/kafkaHandler.js +1 -1
  103. package/src/pro/consumers/index.js +1 -1
  104. package/src/pro/consumers/messageTransformer.js +1 -1
  105. package/src/pro/consumers/validator.js +1 -1
  106. package/src/pro/database/base-model-mysql.js +1 -1
  107. package/src/pro/database/base-model-oracle.js +1 -1
  108. package/src/pro/database/base-model-sqlite.js +1 -1
  109. package/src/pro/database/db-mysql.js +1 -1
  110. package/src/pro/database/db-oracle.js +1 -1
  111. package/src/pro/database/db-sqlite.js +1 -1
  112. package/src/pro/excel/excel-generator.js +1 -1
  113. package/src/pro/excel/excel-parser.js +1 -1
  114. package/src/pro/excel/export-service.js +1 -1
  115. package/src/pro/excel/export_handler.js +1 -1
  116. package/src/pro/excel/import-service.js +1 -1
  117. package/src/pro/excel/import-validator.js +1 -1
  118. package/src/pro/excel/import_handler.js +1 -1
  119. package/src/pro/excel/upsert-builder.js +1 -1
  120. package/src/pro/idgen/idgen-routes.js +1 -1
  121. package/src/pro/integrations/lookup-resolver.js +1 -1
  122. package/src/pro/integrations/upload-handler-v2.js +1 -1
  123. package/src/pro/integrations/upload-handler.js +1 -1
  124. package/src/pro/integrations/webhook.js +1 -1
  125. package/src/pro/locking/lock-routes.js +1 -1
  126. package/src/pro/locking/resource-lock-manager.js +1 -1
  127. package/src/pro/messaging/kafkaConsumerService.js +1 -1
  128. package/src/pro/messaging/kafkaService.js +1 -1
  129. package/src/pro/messaging/messagehubService.js +1 -1
  130. package/src/pro/messaging/rabbitmqService.js +1 -1
  131. package/src/pro/scheduler/job-manager.js +1 -1
  132. package/src/pro/scheduler/job-routes.js +1 -1
  133. package/src/pro/scheduler/job-validator.js +1 -1
  134. package/src/pro/storage/base-storage-provider.js +1 -1
  135. package/src/pro/storage/file-metadata-helper.js +1 -1
  136. package/src/pro/storage/index.js +1 -1
  137. package/src/pro/storage/local-storage-provider.js +1 -1
  138. package/src/pro/storage/s3-storage-provider.js +1 -1
  139. package/src/pro/storage/upload-cleanup-job.js +1 -1
  140. package/src/pro/storage/upload-cleanup-scheduler.js +1 -1
  141. package/src/pro/storage/upload-pending-tracker.js +1 -1
  142. package/src/pro/websocket/broadcast-helper.js +1 -1
  143. package/src/pro/websocket/index.js +1 -1
  144. package/src/pro/websocket/livesync-server.js +1 -1
  145. package/src/pro/websocket/ws-broadcaster.js +1 -1
  146. package/src/services/export-service.js +1 -1
  147. package/src/services/import-service.js +1 -1
  148. package/src/services/kafkaConsumerService.js +1 -1
  149. package/src/services/kafkaService.js +1 -1
  150. package/src/services/messagehubService.js +1 -1
  151. package/src/services/rabbitmqService.js +1 -1
  152. package/src/utils/cache-invalidation-registry.js +1 -1
  153. package/src/utils/cache-manager.js +1 -1
  154. package/src/utils/component-engine.js +1 -1
  155. package/src/utils/config-extractor.js +1 -1
  156. package/src/utils/consumerLogger.js +1 -1
  157. package/src/utils/context-builder.js +1 -1
  158. package/src/utils/dashboard-helpers.js +1 -1
  159. package/src/utils/dateHelper.js +1 -1
  160. package/src/utils/datetime-formatter.js +1 -1
  161. package/src/utils/datetime-parser.js +1 -1
  162. package/src/utils/db-bootstrap.js +1 -1
  163. package/src/utils/db-mysql.js +1 -1
  164. package/src/utils/db-oracle.js +1 -1
  165. package/src/utils/db-sqlite.js +1 -1
  166. package/src/utils/db.js +1 -1
  167. package/src/utils/demo-generator.js +1 -1
  168. package/src/utils/excel-generator.js +1 -1
  169. package/src/utils/excel-parser.js +1 -1
  170. package/src/utils/file-watcher.js +1 -1
  171. package/src/utils/id-generator.js +1 -1
  172. package/src/utils/idempotency-manager.js +1 -1
  173. package/src/utils/import-validator.js +1 -1
  174. package/src/utils/license-client.js +1 -1
  175. package/src/utils/lock-manager.js +1 -1
  176. package/src/utils/logger.js +1 -1
  177. package/src/utils/lookup-resolver.js +1 -1
  178. package/src/utils/payload-loader.js +1 -1
  179. package/src/utils/processor-response.js +1 -1
  180. package/src/utils/rabbitmq.js +1 -1
  181. package/src/utils/redis-client.js +1 -1
  182. package/src/utils/redis-helper.js +1 -1
  183. package/src/utils/request-scope.js +1 -1
  184. package/src/utils/security-checks.js +1 -1
  185. package/src/utils/service-resolver.js +1 -1
  186. package/src/utils/shutdown-coordinator.js +1 -1
  187. package/src/utils/trusted-keys.js +1 -1
  188. package/src/utils/upload-handler.js +1 -1
  189. package/src/utils/upsert-builder.js +1 -1
  190. package/src/utils/workflow-hook-executor.js +1 -1
  191. package/generators/metadata/global.json +0 -58
  192. package/generators/metadata/test-mysql-workbench.json +0 -118
  193. package/generators/metadata/test-mysql.json +0 -56
  194. package/generators/metadata/test-oracle-workbench.json +0 -118
  195. package/generators/metadata/test-oracle.json +0 -56
  196. package/generators/metadata/test-pg-workbench.json +0 -118
  197. package/generators/metadata/test-pg.json +0 -56
  198. package/generators/scripts/obfuscate-source.js +0 -356
  199. package/generators/scripts/validate-catalog.js +0 -430
  200. package/generators/scripts/validate-dbschema-catalog.js +0 -708
  201. package/generators/tests/baseline/mysql/mini_inventory_item/src/models/mini-inventory/item.js +0 -944
  202. package/generators/tests/baseline/mysql/mini_inventory_item/src/modules/mini-inventory/item.js +0 -740
  203. package/generators/tests/baseline/mysql/mini_inventory_item/src/modules/mini-inventory.js +0 -336
  204. package/generators/tests/baseline/oracle/mini_inventory_item/src/models/mini-inventory/item.js +0 -1002
  205. package/generators/tests/baseline/oracle/mini_inventory_item/src/modules/mini-inventory/item.js +0 -740
  206. package/generators/tests/baseline/oracle/mini_inventory_item/src/modules/mini-inventory.js +0 -336
  207. package/generators/tests/baseline/postgres/mini_inventory_item/src/models/mini-inventory/item.js +0 -1333
  208. package/generators/tests/baseline/postgres/mini_inventory_item/src/modules/mini-inventory/item.js +0 -1173
  209. package/generators/tests/baseline/postgres/mini_inventory_item/src/modules/mini-inventory.js +0 -496
  210. package/generators/tests/fixtures/payloads/custom-sensitive.json +0 -27
  211. package/generators/tests/fixtures/payloads/dynamic-search-optout.json +0 -23
  212. package/generators/tests/fixtures/payloads/login-with-password.json +0 -22
  213. package/generators/tests/fixtures/payloads/order-process.json +0 -52
  214. package/generators/tests/fixtures/payloads/with-inline-sql.json +0 -26
  215. package/generators/tests/integration-tahap4b/README.md +0 -145
  216. package/generators/tests/integration-tahap4b/run-concurrent.js +0 -77
  217. package/generators/tests/integration-tahap4b/seed.sql +0 -53
  218. package/generators/tests/integration-tahap4b/verify.sql +0 -110
  219. package/generators/tests/unit/cli/create-dashboard.test.js +0 -505
  220. package/generators/tests/unit/cli/create-processor.test.js +0 -319
  221. package/generators/tests/unit/cli/dispatch-dashboard.test.js +0 -149
  222. package/generators/tests/unit/lib/dashboard-generator.test.js +0 -895
  223. package/generators/tests/unit/lib/dashboard-validator.test.js +0 -354
  224. package/generators/tests/unit/lib/dbschema-kit/apply-executor.test.js +0 -437
  225. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-introspect.test.js +0 -393
  226. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-generate-ddl.test.js +0 -104
  227. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-init.test.js +0 -119
  228. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-list.test.js +0 -48
  229. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-migrate.test.js +0 -175
  230. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-validate.test.js +0 -102
  231. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-models.test.js +0 -43
  232. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/all-schemas-listing.js +0 -84
  233. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/connection-error.js +0 -13
  234. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/empty.js +0 -12
  235. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/multi-schema.js +0 -124
  236. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/single-schema-inventory.js +0 -64
  237. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/two-tables.js +0 -66
  238. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/connection-error.js +0 -9
  239. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/partial.js +0 -29
  240. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/rollback.js +0 -26
  241. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/success.js +0 -43
  242. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/multi-schema/audit/events.js +0 -18
  243. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/multi-schema/inventory/products.js +0 -9
  244. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/multi-schema/users.js +0 -8
  245. package/generators/tests/unit/lib/dbschema-kit/connection.test.js +0 -112
  246. package/generators/tests/unit/lib/dbschema-kit/ddl-generator.test.js +0 -205
  247. package/generators/tests/unit/lib/dbschema-kit/define-model.test.js +0 -56
  248. package/generators/tests/unit/lib/dbschema-kit/dialect/index.test.js +0 -46
  249. package/generators/tests/unit/lib/dbschema-kit/dialect/mysql.test.js +0 -126
  250. package/generators/tests/unit/lib/dbschema-kit/dialect/oracle.test.js +0 -126
  251. package/generators/tests/unit/lib/dbschema-kit/dialect/postgres.test.js +0 -131
  252. package/generators/tests/unit/lib/dbschema-kit/dialect/sqlite.test.js +0 -126
  253. package/generators/tests/unit/lib/dbschema-kit/driver-loader.test.js +0 -93
  254. package/generators/tests/unit/lib/dbschema-kit/emitters/create-index.test.js +0 -173
  255. package/generators/tests/unit/lib/dbschema-kit/emitters/create-table.test.js +0 -376
  256. package/generators/tests/unit/lib/dbschema-kit/emitters/drop-table.test.js +0 -78
  257. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/invalid-dialect.env +0 -6
  258. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/missing-dialect.env +0 -5
  259. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/missing-host.env +0 -5
  260. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/oracle-valid.env +0 -6
  261. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/postgres-valid.env +0 -7
  262. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/sqlite-valid.env +0 -2
  263. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/category.js +0 -11
  264. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/item_product.js +0 -11
  265. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/stock_inbound.js +0 -24
  266. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/stock_inbound_item.js +0 -28
  267. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/supplier.js +0 -9
  268. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/warehouse.js +0 -9
  269. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-invalid/orphan.js +0 -17
  270. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/category.js +0 -11
  271. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/item_product.js +0 -11
  272. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/supplier.js +0 -9
  273. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/warehouse.js +0 -9
  274. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/transactions/stock_inbound.js +0 -24
  275. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/transactions/stock_inbound_item.js +0 -28
  276. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/multi-schema/audit/events.js +0 -18
  277. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/multi-schema/inventory/products.js +0 -9
  278. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/multi-schema/public/users.js +0 -9
  279. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-subfolder/extra/category.js +0 -8
  280. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-subfolder/master/category.js +0 -8
  281. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-tablename/bar.js +0 -8
  282. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-tablename/foo.js +0 -8
  283. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/empty-folder/README.md +0 -1
  284. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/invalid-export/plain.js +0 -3
  285. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/invalid-schema/bad.js +0 -6
  286. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/legacy-pattern/legacy.js +0 -12
  287. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-distinct/audit/products.js +0 -9
  288. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-distinct/inventory/products.js +0 -9
  289. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-duplicate/a/products.js +0 -8
  290. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-duplicate/b/products.js +0 -8
  291. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/nested-deep/a/b/c/deep_table.js +0 -8
  292. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/.hidden/ignored.js +0 -7
  293. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/master/category.js +0 -8
  294. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/master/supplier.js +0 -8
  295. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/transactions/stock_inbound.js +0 -8
  296. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/transactions/stock_inbound_item.js +0 -8
  297. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/valid-multiple/category.js +0 -8
  298. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/valid-multiple/item_product.js +0 -9
  299. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/valid-single/category.js +0 -8
  300. package/generators/tests/unit/lib/dbschema-kit/integration.test.js +0 -217
  301. package/generators/tests/unit/lib/dbschema-kit/introspect-mapper.test.js +0 -403
  302. package/generators/tests/unit/lib/dbschema-kit/ir-builder.test.js +0 -390
  303. package/generators/tests/unit/lib/dbschema-kit/loader.test.js +0 -128
  304. package/generators/tests/unit/lib/dbschema-kit/naming.test.js +0 -170
  305. package/generators/tests/unit/lib/dbschema-kit/parser/shorthand-parser.test.js +0 -237
  306. package/generators/tests/unit/lib/dbschema-kit/schema-printer.test.js +0 -251
  307. package/generators/tests/unit/lib/dbschema-kit/statement-modifier.test.js +0 -105
  308. package/generators/tests/unit/lib/dbschema-kit/statement-splitter.test.js +0 -165
  309. package/generators/tests/unit/lib/dbschema-kit/topological-sort.test.js +0 -135
  310. package/generators/tests/unit/lib/dbschema-kit/validator/check-compatibility-validator.test.js +0 -373
  311. package/generators/tests/unit/lib/dbschema-kit/validator/circular-relation-validator.test.js +0 -454
  312. package/generators/tests/unit/lib/dbschema-kit/validator/cross-model-validator.test.js +0 -512
  313. package/generators/tests/unit/lib/dbschema-kit/validator/enhanced-validate-integration.test.js +0 -390
  314. package/generators/tests/unit/lib/dbschema-kit/validator/naming-convention-validator.test.js +0 -306
  315. package/generators/tests/unit/lib/dbschema-kit/validator/schema-validator.test.js +0 -443
  316. package/generators/tests/unit/lib/dbschema-kit/validator/type-compatibility-validator.test.js +0 -440
  317. package/generators/tests/unit/lib/dbschema-kit/validator/validator-reporter.test.js +0 -172
  318. package/generators/tests/unit/lib/metadata-manager-dashboard.test.js +0 -256
  319. package/generators/tests/unit/lib/payload-validator-fieldpolicy.test.js +0 -240
  320. package/generators/tests/unit/lib/processor-validation-generator.test.js +0 -300
  321. package/generators/tests/unit/lib/sensitive-field-masker.test.js +0 -170
  322. package/generators/tests/unit/lib/sql-table-extractor.test.js +0 -119
  323. package/scripts/generate-integrity-manifest.js +0 -124
  324. package/scripts/snapshot-cli-contracts.js +0 -194
  325. package/scripts/verify-publish.js +0 -56
@@ -1,895 +0,0 @@
1
- 'use strict';
2
-
3
- const test = require('node:test');
4
- const { before, after } = require('node:test');
5
- const assert = require('node:assert');
6
- const fs = require('fs');
7
- const os = require('os');
8
- const path = require('path');
9
-
10
- const DashboardGenerator = require('../../../lib/generators/dashboard-generator');
11
- const ConfigReader = require('../../../lib/config/config-reader');
12
-
13
- const SAMPLE_PAYLOAD_PATH = path.resolve(
14
- __dirname,
15
- '../../../../restforge/docs/architecture/dashboard-architecture/payload/dashboard-sales.json'
16
- );
17
-
18
- const SAMPLE_PAYLOAD_DIR = path.dirname(SAMPLE_PAYLOAD_PATH);
19
-
20
- function loadSamplePayload() {
21
- return JSON.parse(fs.readFileSync(SAMPLE_PAYLOAD_PATH, 'utf8'));
22
- }
23
-
24
- function makeTmpDir() {
25
- return fs.mkdtempSync(path.join(os.tmpdir(), 'dash-gen-test-'));
26
- }
27
-
28
- /**
29
- * Override ConfigReader.getWorkingDirectory selama eksekusi callback.
30
- * Pendekatan ini menghindari mutasi process.cwd() global yang bisa
31
- * bocor antar test paralel.
32
- */
33
- function withWorkingDir(dir, fn) {
34
- const original = ConfigReader.getWorkingDirectory;
35
- ConfigReader.getWorkingDirectory = () => dir;
36
- try {
37
- return fn();
38
- } finally {
39
- ConfigReader.getWorkingDirectory = original;
40
- }
41
- }
42
-
43
- // Suppress noisy console output dari FileUtils.setupModuleDirectories
44
- // tanpa menyembunyikan error sebenarnya.
45
- let _originalLog;
46
- let _originalWarn;
47
- before(() => {
48
- _originalLog = console.log;
49
- _originalWarn = console.warn;
50
- console.log = () => {};
51
- console.warn = () => {};
52
- });
53
- after(() => {
54
- console.log = _originalLog;
55
- console.warn = _originalWarn;
56
- });
57
-
58
- // ===== collectSqlFileReferences =====
59
-
60
- test('collectSqlFileReferences: payload sample mengembalikan 4 file SQL relatif terhadap payload dir', () => {
61
- const payload = loadSamplePayload();
62
- const refs = DashboardGenerator.collectSqlFileReferences(payload.widgets);
63
- const sorted = [...refs].sort();
64
- const expected = [
65
- 'query/author-sales.sql',
66
- 'query/sales-statistics.sql',
67
- 'query/sales-this-months-points.sql',
68
- 'query/sales-this-months-value.sql'
69
- ].sort();
70
- assert.deepStrictEqual(sorted, expected);
71
- });
72
-
73
- // ===== createDashboardModule: output struktur =====
74
-
75
- test('createDashboardModule: file output dibuat di src/modules/{project}/dash-{name}.js', () => {
76
- const tmpDir = makeTmpDir();
77
- withWorkingDir(tmpDir, () => {
78
- const payload = loadSamplePayload();
79
- const result = DashboardGenerator.createDashboardModule(
80
- 'mini-inventory', 'dash-sales', payload,
81
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
82
- );
83
- const expected = path.join(
84
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
85
- );
86
- assert.strictEqual(result.filePath, expected);
87
- assert.strictEqual(result.generated, true);
88
- assert.strictEqual(fs.existsSync(expected), true);
89
- });
90
- });
91
-
92
- test('createDashboardModule: folder src/models/{project}/dash-{name}/query/ TIDAK dibuat (SQL embedded, no copy)', () => {
93
- const tmpDir = makeTmpDir();
94
- withWorkingDir(tmpDir, () => {
95
- const payload = loadSamplePayload();
96
- DashboardGenerator.createDashboardModule(
97
- 'mini-inventory', 'dash-sales', payload,
98
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
99
- );
100
- const dashboardQueryDir = path.join(
101
- tmpDir, 'src', 'models', 'mini-inventory', 'dash-sales', 'query'
102
- );
103
- assert.strictEqual(
104
- fs.existsSync(dashboardQueryDir),
105
- false,
106
- 'folder dashboard query tidak boleh terbuat (SQL sudah embedded di JS)'
107
- );
108
- });
109
- });
110
-
111
- test('createDashboardModule: output mengandung require express, dashboard-helpers, db', () => {
112
- const tmpDir = makeTmpDir();
113
- withWorkingDir(tmpDir, () => {
114
- const payload = loadSamplePayload();
115
- DashboardGenerator.createDashboardModule(
116
- 'mini-inventory', 'dash-sales', payload,
117
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
118
- );
119
- const filePath = path.join(
120
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
121
- );
122
- const content = fs.readFileSync(filePath, 'utf8');
123
- assert.ok(content.includes("require('express')"), 'harus require express');
124
- assert.ok(
125
- content.includes("require('restforgejs/src/utils/dashboard-helpers')"),
126
- 'harus require dashboard-helpers via npm package style restforgejs/src/utils/...'
127
- );
128
- assert.ok(
129
- content.includes("require('restforgejs/src/utils/db')"),
130
- 'harus require db utility via npm package style'
131
- );
132
- assert.ok(
133
- !content.includes("require('../../utils/"),
134
- 'tidak boleh emit relative path require ../../utils/... — anti-pattern di deployment'
135
- );
136
- });
137
- });
138
-
139
- test('createDashboardModule: output mengandung paramsContract constant', () => {
140
- const tmpDir = makeTmpDir();
141
- withWorkingDir(tmpDir, () => {
142
- const payload = loadSamplePayload();
143
- DashboardGenerator.createDashboardModule(
144
- 'mini-inventory', 'dash-sales', payload,
145
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
146
- );
147
- const filePath = path.join(
148
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
149
- );
150
- const content = fs.readFileSync(filePath, 'utf8');
151
- assert.match(content, /const paramsContract\s*=/);
152
- });
153
- });
154
-
155
- test('createDashboardModule: output mengandung allWidgetIds array literal dengan ID widget yang benar', () => {
156
- const tmpDir = makeTmpDir();
157
- withWorkingDir(tmpDir, () => {
158
- const payload = loadSamplePayload();
159
- DashboardGenerator.createDashboardModule(
160
- 'mini-inventory', 'dash-sales', payload,
161
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
162
- );
163
- const filePath = path.join(
164
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
165
- );
166
- const content = fs.readFileSync(filePath, 'utf8');
167
- assert.match(content, /const allWidgetIds\s*=\s*\[/);
168
- assert.ok(content.includes('"author_sales"'), 'allWidgetIds harus mengandung author_sales');
169
- assert.ok(content.includes('"sales_statistics"'), 'allWidgetIds harus mengandung sales_statistics');
170
- assert.ok(content.includes('"sales_this_months"'), 'allWidgetIds harus mengandung sales_this_months');
171
- });
172
- });
173
-
174
- test('createDashboardModule: output mengandung router.post handler /dashboard', () => {
175
- const tmpDir = makeTmpDir();
176
- withWorkingDir(tmpDir, () => {
177
- const payload = loadSamplePayload();
178
- DashboardGenerator.createDashboardModule(
179
- 'mini-inventory', 'dash-sales', payload,
180
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
181
- );
182
- const filePath = path.join(
183
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
184
- );
185
- const content = fs.readFileSync(filePath, 'utf8');
186
- assert.ok(
187
- content.includes("router.post('/dashboard', async (req, res) => {"),
188
- 'output harus emit router.post handler untuk /dashboard'
189
- );
190
- });
191
- });
192
-
193
- test('createDashboardModule: output mengandung post-processing loop lowercaseKeysDeep + stringifyNumericDeep sebelum res.json', () => {
194
- const tmpDir = makeTmpDir();
195
- withWorkingDir(tmpDir, () => {
196
- const payload = loadSamplePayload();
197
- DashboardGenerator.createDashboardModule(
198
- 'mini-inventory', 'dash-sales', payload,
199
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
200
- );
201
- const filePath = path.join(
202
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
203
- );
204
- const content = fs.readFileSync(filePath, 'utf8');
205
- assert.ok(
206
- content.includes('helpers.lowercaseKeysDeep'),
207
- 'output harus memanggil helpers.lowercaseKeysDeep untuk normalisasi key Oracle UPPERCASE'
208
- );
209
- assert.ok(
210
- content.includes('helpers.stringifyNumericDeep'),
211
- 'output harus memanggil helpers.stringifyNumericDeep untuk normalisasi numeric → string'
212
- );
213
- const lowerIdx = content.indexOf('helpers.lowercaseKeysDeep');
214
- const stringifyIdx = content.indexOf('helpers.stringifyNumericDeep');
215
- const resJsonIdx = content.indexOf('res.json(response)');
216
- assert.ok(
217
- lowerIdx < stringifyIdx,
218
- 'lowercaseKeysDeep harus dipanggil sebelum stringifyNumericDeep'
219
- );
220
- assert.ok(
221
- stringifyIdx < resJsonIdx,
222
- 'post-processing loop harus dipanggil sebelum res.json(response)'
223
- );
224
- assert.match(
225
- content,
226
- /const response\s*=\s*\{\s*success:\s*true,\s*data\s*\};/,
227
- 'response envelope dibangun ke variable response sebelum res.json'
228
- );
229
- });
230
- });
231
-
232
- test('createDashboardModule: output mengandung Promise.allSettled call untuk eksekusi paralel', () => {
233
- const tmpDir = makeTmpDir();
234
- withWorkingDir(tmpDir, () => {
235
- const payload = loadSamplePayload();
236
- DashboardGenerator.createDashboardModule(
237
- 'mini-inventory', 'dash-sales', payload,
238
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
239
- );
240
- const filePath = path.join(
241
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
242
- );
243
- const content = fs.readFileSync(filePath, 'utf8');
244
- assert.ok(content.includes('Promise.allSettled'), 'harus pakai Promise.allSettled');
245
- });
246
- });
247
-
248
- test('createDashboardModule: output mengandung module.exports = router', () => {
249
- const tmpDir = makeTmpDir();
250
- withWorkingDir(tmpDir, () => {
251
- const payload = loadSamplePayload();
252
- DashboardGenerator.createDashboardModule(
253
- 'mini-inventory', 'dash-sales', payload,
254
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
255
- );
256
- const filePath = path.join(
257
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
258
- );
259
- const content = fs.readFileSync(filePath, 'utf8');
260
- assert.ok(content.includes('module.exports = router'));
261
- });
262
- });
263
-
264
- test('createDashboardModule: payload tanpa params mengemit paramsContract = {}', () => {
265
- const tmpDir = makeTmpDir();
266
- withWorkingDir(tmpDir, () => {
267
- const payload = loadSamplePayload();
268
- assert.ok(!('params' in payload), 'sample payload tidak punya params');
269
- DashboardGenerator.createDashboardModule(
270
- 'mini-inventory', 'dash-sales', payload,
271
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
272
- );
273
- const filePath = path.join(
274
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
275
- );
276
- const content = fs.readFileSync(filePath, 'utf8');
277
- assert.match(content, /const paramsContract\s*=\s*\{\}/);
278
- });
279
- });
280
-
281
- // ===== SQL embedded =====
282
-
283
- test('createDashboardModule: SQL author-sales.sql ter-embed apa adanya di output (string match)', () => {
284
- const tmpDir = makeTmpDir();
285
- withWorkingDir(tmpDir, () => {
286
- const payload = loadSamplePayload();
287
- DashboardGenerator.createDashboardModule(
288
- 'mini-inventory', 'dash-sales', payload,
289
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
290
- );
291
- const filePath = path.join(
292
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
293
- );
294
- const content = fs.readFileSync(filePath, 'utf8');
295
- assert.ok(content.includes('country AS label'), 'fragment SQL author-sales harus muncul di output');
296
- assert.ok(content.includes('SUM(amount) AS value'), 'fragment SQL author-sales harus muncul di output');
297
- assert.ok(content.includes('GROUP BY country'), 'fragment SQL author-sales harus muncul di output');
298
- });
299
- });
300
-
301
- test('createDashboardModule: SQL semua 4 fixture file dashboard-sales ter-embed', () => {
302
- const tmpDir = makeTmpDir();
303
- withWorkingDir(tmpDir, () => {
304
- const payload = loadSamplePayload();
305
- DashboardGenerator.createDashboardModule(
306
- 'mini-inventory', 'dash-sales', payload,
307
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
308
- );
309
- const filePath = path.join(
310
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
311
- );
312
- const content = fs.readFileSync(filePath, 'utf8');
313
-
314
- // Marker khas tiap fixture
315
- assert.ok(content.includes('country AS label'), 'author-sales.sql harus muncul');
316
- assert.ok(content.includes('product_name AS label'), 'sales-statistics.sql harus muncul');
317
- assert.ok(
318
- content.includes("date_trunc('month', sale_date)"),
319
- 'sales-this-months-*.sql harus muncul'
320
- );
321
- assert.ok(
322
- content.includes("TO_CHAR(sale_date, 'YYYY-MM-DD') AS period"),
323
- 'sales-this-months-points.sql (varian points) harus muncul'
324
- );
325
- });
326
- });
327
-
328
- // ===== SoC lock + residue lock =====
329
-
330
- test('createDashboardModule: SoC lock — output tidak boleh emit field frontend (widgetType, __dashboardEmbed, title, subtitle)', () => {
331
- const tmpDir = makeTmpDir();
332
- withWorkingDir(tmpDir, () => {
333
- const payload = loadSamplePayload();
334
- DashboardGenerator.createDashboardModule(
335
- 'mini-inventory', 'dash-sales', payload,
336
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
337
- );
338
- const filePath = path.join(
339
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
340
- );
341
- const content = fs.readFileSync(filePath, 'utf8');
342
-
343
- assert.ok(!content.includes('widgetType'), 'tidak boleh emit widgetType');
344
- assert.ok(!content.includes('__dashboardEmbed'), 'tidak boleh emit __dashboardEmbed');
345
- assert.ok(!/['"]title['"]\s*:/.test(content), 'tidak boleh emit field title');
346
- assert.ok(!/['"]subtitle['"]\s*:/.test(content), 'tidak boleh emit field subtitle');
347
- });
348
- });
349
-
350
- test('createDashboardModule: residue lock — output tidak boleh mengandung pattern lama (widgetsConfig/queryDir/dashboardRunner/runDashboard/queryFile/queryFiles)', () => {
351
- const tmpDir = makeTmpDir();
352
- withWorkingDir(tmpDir, () => {
353
- const payload = loadSamplePayload();
354
- DashboardGenerator.createDashboardModule(
355
- 'mini-inventory', 'dash-sales', payload,
356
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
357
- );
358
- const filePath = path.join(
359
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
360
- );
361
- const content = fs.readFileSync(filePath, 'utf8');
362
-
363
- assert.ok(!content.includes('widgetsConfig'), 'residue widgetsConfig harus hilang');
364
- assert.ok(!content.includes('queryDir'), 'residue queryDir harus hilang');
365
- assert.ok(!content.includes('dashboardRunner'), 'residue dashboardRunner harus hilang');
366
- assert.ok(!content.includes('runDashboard'), 'residue runDashboard harus hilang');
367
- assert.ok(!content.includes('queryFile'), 'residue queryFile harus hilang');
368
- assert.ok(!content.includes('queryFiles'), 'residue queryFiles harus hilang');
369
- });
370
- });
371
-
372
- // ===== Inline SQL rejection =====
373
-
374
- test('createDashboardModule: widget dengan inline SQL (tidak pakai file: prefix) ditolak', () => {
375
- const tmpDir = makeTmpDir();
376
- withWorkingDir(tmpDir, () => {
377
- const payload = {
378
- widgets: [{ id: 'x', query: 'SELECT 1' }]
379
- };
380
- assert.throws(
381
- () => DashboardGenerator.createDashboardModule(
382
- 'mini-inventory', 'dash-sales', payload,
383
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
384
- ),
385
- /inline SQL is not supported/
386
- );
387
- });
388
- });
389
-
390
- // ===== Conflict handling =====
391
-
392
- test('createDashboardModule: file sudah ada tanpa force harus throw error', () => {
393
- const tmpDir = makeTmpDir();
394
- withWorkingDir(tmpDir, () => {
395
- const payload = loadSamplePayload();
396
- DashboardGenerator.createDashboardModule(
397
- 'mini-inventory', 'dash-sales', payload,
398
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
399
- );
400
- assert.throws(
401
- () => DashboardGenerator.createDashboardModule(
402
- 'mini-inventory', 'dash-sales', payload,
403
- { force: false, payloadDir: SAMPLE_PAYLOAD_DIR }
404
- ),
405
- /already exists/
406
- );
407
- });
408
- });
409
-
410
- test('createDashboardModule: file sudah ada dengan force=true harus archive lalu rewrite', () => {
411
- const tmpDir = makeTmpDir();
412
- withWorkingDir(tmpDir, () => {
413
- const payload = loadSamplePayload();
414
- DashboardGenerator.createDashboardModule(
415
- 'mini-inventory', 'dash-sales', payload,
416
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
417
- );
418
- const moduleDir = path.join(tmpDir, 'src', 'modules', 'mini-inventory');
419
- const liveFile = path.join(moduleDir, 'dash-sales.js');
420
- const before = fs.statSync(liveFile).mtimeMs;
421
-
422
- // Pastikan timestamp archive berbeda dengan menunggu sedikit
423
- const start = Date.now();
424
- while (Date.now() === start) { /* spin */ }
425
-
426
- const result = DashboardGenerator.createDashboardModule(
427
- 'mini-inventory', 'dash-sales', payload,
428
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
429
- );
430
-
431
- assert.ok(result.archivedPath, 'createDashboardModule harus return archivedPath bila override');
432
- assert.strictEqual(fs.existsSync(result.archivedPath), true);
433
-
434
- const files = fs.readdirSync(moduleDir);
435
- const archived = files.filter((f) => f.startsWith('dash-sales.js.archive.'));
436
- const live = files.filter((f) => f === 'dash-sales.js');
437
- assert.strictEqual(live.length, 1);
438
- assert.ok(archived.length >= 1, 'minimal satu archive file harus terbuat');
439
-
440
- const after = fs.statSync(liveFile).mtimeMs;
441
- assert.ok(after >= before, 'live file harus ditulis ulang');
442
- });
443
- });
444
-
445
- // ===== Signature & validation =====
446
-
447
- test('createDashboardModule: payloadDir wajib di options (throw bila tidak ada)', () => {
448
- const tmpDir = makeTmpDir();
449
- withWorkingDir(tmpDir, () => {
450
- const payload = loadSamplePayload();
451
- assert.throws(
452
- () => DashboardGenerator.createDashboardModule(
453
- 'mini-inventory', 'dash-sales', payload, { force: true }
454
- ),
455
- /payloadDir/
456
- );
457
- });
458
- });
459
-
460
- // ===== SQL escaping (optional defensive) =====
461
-
462
- test('createDashboardModule: SQL berisi backtick di-escape jadi \\` di template literal output', () => {
463
- const tmpDir = makeTmpDir();
464
- // Buat payloadDir custom dengan SQL fixture yang punya backtick
465
- const customPayloadDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dash-gen-fixture-'));
466
- fs.mkdirSync(path.join(customPayloadDir, 'query'));
467
- const sqlPath = path.join(customPayloadDir, 'query', 'tick.sql');
468
- fs.writeFileSync(sqlPath, 'SELECT `col` FROM tbl', 'utf8');
469
-
470
- withWorkingDir(tmpDir, () => {
471
- const payload = {
472
- widgets: [{ id: 'tick_widget', query: 'file:query/tick.sql' }]
473
- };
474
- DashboardGenerator.createDashboardModule(
475
- 'mini-inventory', 'dash-tick', payload,
476
- { force: true, payloadDir: customPayloadDir }
477
- );
478
- const filePath = path.join(
479
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-tick.js'
480
- );
481
- const content = fs.readFileSync(filePath, 'utf8');
482
- assert.ok(
483
- content.includes('SELECT \\`col\\` FROM tbl'),
484
- 'backtick di SQL harus di-escape jadi \\`'
485
- );
486
- });
487
- });
488
-
489
- test('createDashboardModule: SQL berisi ${...} di-escape jadi \\${ di template literal output', () => {
490
- const tmpDir = makeTmpDir();
491
- const customPayloadDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dash-gen-fixture-'));
492
- fs.mkdirSync(path.join(customPayloadDir, 'query'));
493
- const sqlPath = path.join(customPayloadDir, 'query', 'dollar.sql');
494
- fs.writeFileSync(sqlPath, 'SELECT * FROM t WHERE name = \'${suspect}\'', 'utf8');
495
-
496
- withWorkingDir(tmpDir, () => {
497
- const payload = {
498
- widgets: [{ id: 'dollar_widget', query: 'file:query/dollar.sql' }]
499
- };
500
- DashboardGenerator.createDashboardModule(
501
- 'mini-inventory', 'dash-dollar', payload,
502
- { force: true, payloadDir: customPayloadDir }
503
- );
504
- const filePath = path.join(
505
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-dollar.js'
506
- );
507
- const content = fs.readFileSync(filePath, 'utf8');
508
- assert.ok(
509
- content.includes('\\${suspect}'),
510
- 'dollar-brace di SQL harus di-escape jadi \\${'
511
- );
512
- });
513
- });
514
-
515
- // ===== Multi-dialect: option database propagation =====
516
-
517
- test('createDashboardModule: database=postgres → emit require restforgejs/src/utils/db dan const dialect = postgres', () => {
518
- const tmpDir = makeTmpDir();
519
- withWorkingDir(tmpDir, () => {
520
- const payload = loadSamplePayload();
521
- DashboardGenerator.createDashboardModule(
522
- 'mini-inventory', 'dash-sales', payload,
523
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR, database: 'postgres' }
524
- );
525
- const filePath = path.join(
526
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
527
- );
528
- const content = fs.readFileSync(filePath, 'utf8');
529
- assert.ok(
530
- content.includes("require('restforgejs/src/utils/db')"),
531
- 'output harus require restforgejs/src/utils/db untuk dialect postgres'
532
- );
533
- assert.ok(
534
- content.includes("const dialect = 'postgres';"),
535
- "output harus emit const dialect = 'postgres'"
536
- );
537
- });
538
- });
539
-
540
- test('createDashboardModule: database=mysql → emit require restforgejs/src/utils/db-mysql dan const dialect = mysql', () => {
541
- const tmpDir = makeTmpDir();
542
- withWorkingDir(tmpDir, () => {
543
- const payload = loadSamplePayload();
544
- DashboardGenerator.createDashboardModule(
545
- 'mini-inventory', 'dash-sales', payload,
546
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR, database: 'mysql' }
547
- );
548
- const filePath = path.join(
549
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
550
- );
551
- const content = fs.readFileSync(filePath, 'utf8');
552
- assert.ok(
553
- content.includes("require('restforgejs/src/utils/db-mysql')"),
554
- 'output harus require restforgejs/src/utils/db-mysql untuk dialect mysql'
555
- );
556
- assert.ok(
557
- content.includes("const dialect = 'mysql';"),
558
- "output harus emit const dialect = 'mysql'"
559
- );
560
- assert.ok(
561
- !content.includes("require('restforgejs/src/utils/db')\n"),
562
- 'output tidak boleh require db postgres saat dialect mysql'
563
- );
564
- });
565
- });
566
-
567
- test('createDashboardModule: database=oracle → emit require restforgejs/src/utils/db-oracle dan const dialect = oracle', () => {
568
- const tmpDir = makeTmpDir();
569
- withWorkingDir(tmpDir, () => {
570
- const payload = loadSamplePayload();
571
- DashboardGenerator.createDashboardModule(
572
- 'mini-inventory', 'dash-sales', payload,
573
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR, database: 'oracle' }
574
- );
575
- const filePath = path.join(
576
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
577
- );
578
- const content = fs.readFileSync(filePath, 'utf8');
579
- assert.ok(
580
- content.includes("require('restforgejs/src/utils/db-oracle')"),
581
- 'output harus require restforgejs/src/utils/db-oracle untuk dialect oracle'
582
- );
583
- assert.ok(
584
- content.includes("const dialect = 'oracle';"),
585
- "output harus emit const dialect = 'oracle'"
586
- );
587
- });
588
- });
589
-
590
- test('createDashboardModule: database=sqlite (tidak didukung) → throw error dengan pesan jelas', () => {
591
- const tmpDir = makeTmpDir();
592
- withWorkingDir(tmpDir, () => {
593
- const payload = loadSamplePayload();
594
- assert.throws(
595
- () => DashboardGenerator.createDashboardModule(
596
- 'mini-inventory', 'dash-sales', payload,
597
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR, database: 'sqlite' }
598
- ),
599
- (err) =>
600
- err instanceof Error &&
601
- /sqlite/.test(err.message) &&
602
- /Supported: postgres, mysql, oracle/.test(err.message)
603
- );
604
- });
605
- });
606
-
607
- test('createDashboardModule: options tanpa database → default postgres (backward compat caller existing)', () => {
608
- const tmpDir = makeTmpDir();
609
- withWorkingDir(tmpDir, () => {
610
- const payload = loadSamplePayload();
611
- DashboardGenerator.createDashboardModule(
612
- 'mini-inventory', 'dash-sales', payload,
613
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
614
- );
615
- const filePath = path.join(
616
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
617
- );
618
- const content = fs.readFileSync(filePath, 'utf8');
619
- assert.ok(
620
- content.includes("require('restforgejs/src/utils/db')"),
621
- 'tanpa options.database, output default ke require db postgres'
622
- );
623
- assert.ok(
624
- content.includes("const dialect = 'postgres';"),
625
- "tanpa options.database, output default emit const dialect = 'postgres'"
626
- );
627
- });
628
- });
629
-
630
- test('createDashboardModule: generated handler memanggil substitutePlaceholders dengan 3 argument (dialect)', () => {
631
- const tmpDir = makeTmpDir();
632
- withWorkingDir(tmpDir, () => {
633
- const payload = loadSamplePayload();
634
- DashboardGenerator.createDashboardModule(
635
- 'mini-inventory', 'dash-sales', payload,
636
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR, database: 'mysql' }
637
- );
638
- const filePath = path.join(
639
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
640
- );
641
- const content = fs.readFileSync(filePath, 'utf8');
642
- assert.match(
643
- content,
644
- /helpers\.substitutePlaceholders\(\s*`[\s\S]*?`,\s*effectiveParams,\s*dialect\s*\)/,
645
- 'panggilan substitutePlaceholders harus menyertakan dialect sebagai argument ketiga'
646
- );
647
- });
648
- });
649
-
650
- // ============================================================================
651
- // CACHE WRAPPING (Phase 3)
652
- // ============================================================================
653
-
654
- function generateAndRead(projectName, dashboardName, payload, opts = {}) {
655
- const tmpDir = makeTmpDir();
656
- let content;
657
- withWorkingDir(tmpDir, () => {
658
- DashboardGenerator.createDashboardModule(
659
- projectName, dashboardName, payload,
660
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR, ...opts }
661
- );
662
- const filePath = path.join(
663
- tmpDir, 'src', 'modules', projectName, `${dashboardName}.js`
664
- );
665
- content = fs.readFileSync(filePath, 'utf8');
666
- });
667
- return content;
668
- }
669
-
670
- // ----- Cache disabled (3 test) -----
671
-
672
- test('cache: payload tanpa cache block — output TIDAK mengandung cacheManager / cache check / cache set', () => {
673
- const payload = loadSamplePayload(); // sample tidak punya cache
674
- const content = generateAndRead('mini-inventory', 'dash-sales', payload);
675
-
676
- assert.ok(
677
- !content.includes('cacheManager'),
678
- 'tanpa cache.enabled, generator TIDAK boleh emit referensi cacheManager'
679
- );
680
- assert.ok(
681
- !content.includes('Cache check'),
682
- 'tanpa cache.enabled, generator TIDAK boleh emit comment "Cache check"'
683
- );
684
- assert.ok(
685
- !content.includes('Cache set'),
686
- 'tanpa cache.enabled, generator TIDAK boleh emit comment "Cache set"'
687
- );
688
- assert.ok(
689
- !content.includes("require('restforgejs/src/utils/cache-manager')"),
690
- 'tanpa cache.enabled, generator TIDAK boleh require cache-manager'
691
- );
692
- });
693
-
694
- test('cache: payload dengan cache.enabled=false — output TIDAK mengandung cache wrapping', () => {
695
- const payload = { ...loadSamplePayload(), cache: { enabled: false } };
696
- const content = generateAndRead('mini-inventory', 'dash-sales', payload);
697
-
698
- assert.ok(!content.includes('cacheManager'), 'cache.enabled=false harus skip cache wrapping');
699
- assert.ok(!content.includes('Cache check'), 'comment Cache check harus absen');
700
- assert.ok(!content.includes('Cache set'), 'comment Cache set harus absen');
701
- });
702
-
703
- test('cache: output non-cache valid sebagai Express router (smoke parse via new Function)', () => {
704
- const payload = loadSamplePayload();
705
- const content = generateAndRead('mini-inventory', 'dash-sales', payload);
706
-
707
- assert.doesNotThrow(
708
- () => new Function(content),
709
- 'output non-cache harus parse-able sebagai JavaScript valid'
710
- );
711
- });
712
-
713
- // ----- Cache enabled (8 test) -----
714
-
715
- test('cache: payload dengan cache.enabled=true — output MENGANDUNG require cache-manager', () => {
716
- const payload = {
717
- ...loadSamplePayload(),
718
- cache: { enabled: true, invalidates: ['sales'] }
719
- };
720
- const content = generateAndRead('mini-inventory', 'dash-sales', payload);
721
-
722
- assert.ok(
723
- content.includes("require('restforgejs/src/utils/cache-manager')"),
724
- 'cache.enabled=true harus emit require cache-manager via npm package style'
725
- );
726
- assert.ok(
727
- content.includes('const cacheManager = '),
728
- 'output harus declare const cacheManager'
729
- );
730
- });
731
-
732
- test('cache: output mengandung buildKey dengan module/endpoint/type=dashboard', () => {
733
- const payload = {
734
- ...loadSamplePayload(),
735
- cache: { enabled: true, invalidates: ['sales'] }
736
- };
737
- const content = generateAndRead('mini-inventory', 'dash-sales', payload);
738
-
739
- assert.ok(content.includes('cacheManager.buildKey({'), 'harus emit buildKey call');
740
- assert.ok(
741
- content.includes("module: 'mini-inventory'"),
742
- 'buildKey module field harus literal projectName'
743
- );
744
- assert.ok(
745
- content.includes("endpoint: 'dash-sales'"),
746
- 'buildKey endpoint field harus literal dashboardName'
747
- );
748
- assert.ok(
749
- content.includes("type: 'dashboard'"),
750
- 'buildKey type field harus literal "dashboard"'
751
- );
752
- });
753
-
754
- test('cache: output mengandung crypto.createHash MD5 + cabang isEmpty="all"', () => {
755
- const payload = {
756
- ...loadSamplePayload(),
757
- cache: { enabled: true, invalidates: ['sales'] }
758
- };
759
- const content = generateAndRead('mini-inventory', 'dash-sales', payload);
760
-
761
- assert.ok(
762
- content.includes("crypto.createHash('md5')"),
763
- 'identifier hash WAJIB pakai MD5'
764
- );
765
- assert.ok(
766
- content.includes(".substring(0, 8)"),
767
- 'hash harus dipotong 8 karakter'
768
- );
769
- assert.ok(
770
- content.includes("'all'"),
771
- 'output harus emit literal "all" untuk body kosong'
772
- );
773
- assert.match(
774
- content,
775
- /const isEmpty\s*=\s*Object\.keys\(body\)\.length\s*===\s*0/,
776
- 'isEmpty derivation dari Object.keys(req.body).length'
777
- );
778
- });
779
-
780
- test('cache: output mengandung cacheManager.set(cacheKey, response, <ttl>)', () => {
781
- const payload = {
782
- ...loadSamplePayload(),
783
- cache: { enabled: true, ttl: 600, invalidates: ['sales'] }
784
- };
785
- const content = generateAndRead('mini-inventory', 'dash-sales', payload);
786
-
787
- assert.ok(
788
- content.includes('cacheManager.set(cacheKey, response, 600)'),
789
- 'cache set harus pakai cacheKey + response + ttl literal'
790
- );
791
- assert.ok(
792
- content.includes('if (cacheKey) {'),
793
- 'cache set di-guard dengan if (cacheKey)'
794
- );
795
- });
796
-
797
- test('cache: TTL literal — cache.ttl=600 emit "600", tanpa ttl emit "null"', () => {
798
- // case 1: cache.ttl=600
799
- const withTtl = generateAndRead('mini-inventory', 'dash-sales', {
800
- ...loadSamplePayload(),
801
- cache: { enabled: true, ttl: 600, invalidates: ['sales'] }
802
- });
803
- assert.ok(
804
- withTtl.includes('cacheManager.set(cacheKey, response, 600)'),
805
- 'ttl=600 harus emit literal numeric 600'
806
- );
807
-
808
- // case 2: tanpa ttl
809
- const withoutTtl = generateAndRead('mini-inventory', 'dash-sales', {
810
- ...loadSamplePayload(),
811
- cache: { enabled: true, invalidates: ['sales'] }
812
- });
813
- assert.ok(
814
- withoutTtl.includes('cacheManager.set(cacheKey, response, null)'),
815
- 'tanpa ttl harus emit literal null (fallback ke env CACHE_TTL)'
816
- );
817
- });
818
-
819
- test('cache: cache check muncul sebelum Promise.allSettled, cache set setelah loop normalisasi', () => {
820
- const payload = {
821
- ...loadSamplePayload(),
822
- cache: { enabled: true, ttl: 300, invalidates: ['sales'] }
823
- };
824
- const content = generateAndRead('mini-inventory', 'dash-sales', payload);
825
-
826
- const cacheCheckIdx = content.indexOf('// --- Cache check');
827
- const allSettledIdx = content.indexOf('Promise.allSettled');
828
- const stringifyIdx = content.indexOf('helpers.stringifyNumericDeep');
829
- const cacheSetIdx = content.indexOf('// --- Cache set');
830
- const resJsonIdx = content.indexOf('res.json(response)');
831
-
832
- assert.ok(cacheCheckIdx > 0, 'cache check block harus ada');
833
- assert.ok(cacheSetIdx > 0, 'cache set block harus ada');
834
- assert.ok(
835
- cacheCheckIdx < allSettledIdx,
836
- 'cache check WAJIB muncul sebelum Promise.allSettled'
837
- );
838
- assert.ok(
839
- stringifyIdx < cacheSetIdx,
840
- 'cache set WAJIB muncul setelah loop normalisasi (stringifyNumericDeep)'
841
- );
842
- assert.ok(
843
- cacheSetIdx < resJsonIdx,
844
- 'cache set WAJIB muncul sebelum res.json(response)'
845
- );
846
- });
847
-
848
- test('cache: generated code dengan cache wrapping tetap parse valid (smoke)', () => {
849
- const payload = {
850
- ...loadSamplePayload(),
851
- cache: { enabled: true, ttl: 300, invalidates: ['sales'] }
852
- };
853
- const content = generateAndRead('mini-inventory', 'dash-sales', payload);
854
-
855
- assert.doesNotThrow(
856
- () => new Function(content),
857
- 'output dengan cache wrapping harus parse-able sebagai JavaScript valid'
858
- );
859
- });
860
-
861
- test('cache: cache check guard menggunakan getter cacheManager.enabled (bukan isEnabled())', () => {
862
- const payload = {
863
- ...loadSamplePayload(),
864
- cache: { enabled: true, invalidates: ['sales'] }
865
- };
866
- const content = generateAndRead('mini-inventory', 'dash-sales', payload);
867
-
868
- assert.ok(
869
- content.includes('if (cacheManager.enabled) {'),
870
- 'cache check WAJIB pakai getter cacheManager.enabled, bukan cacheManager.isEnabled()'
871
- );
872
- assert.ok(
873
- !content.includes('cacheManager.isEnabled()'),
874
- 'cache check TIDAK boleh pakai method isEnabled() — gunakan getter enabled'
875
- );
876
- });
877
-
878
- test('cache: cache hit branch menjalankan early return res.json(cached)', () => {
879
- const payload = {
880
- ...loadSamplePayload(),
881
- cache: { enabled: true, ttl: 300, invalidates: ['sales'] }
882
- };
883
- const content = generateAndRead('mini-inventory', 'dash-sales', payload);
884
-
885
- assert.match(
886
- content,
887
- /const cached\s*=\s*await cacheManager\.get\(cacheKey\)/,
888
- 'cache check harus await cacheManager.get(cacheKey)'
889
- );
890
- assert.match(
891
- content,
892
- /if \(cached\)\s*return res\.json\(cached\)/,
893
- 'cache hit harus early-return res.json(cached)'
894
- );
895
- });