@restforgejs/platform 4.2.8 → 4.3.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.
- package/SECURITY.md +83 -4
- package/bin/sdf-tools.exe +0 -0
- package/build-info.json +2 -2
- package/cli/consumer-deploy.js +1 -1
- package/cli/consumer.js +1 -1
- package/generators/cli/dashboard/create.js +4 -1
- package/generators/cli/endpoint/create.js +1 -1
- package/generators/cli/key/generate.js +2 -1
- package/generators/cli/key/revoke.js +2 -1
- package/generators/cli/payload/diff.js +3 -2
- package/generators/cli/payload/generate.js +3 -2
- package/generators/cli/payload/sync.js +3 -2
- package/generators/cli/payload/validate.js +3 -2
- package/generators/cli/processor/create.js +14 -3
- package/generators/cli/project/delete.js +2 -1
- package/generators/cli/query/validate.js +3 -2
- package/generators/cli/schema/apply.js +3 -2
- package/generators/cli/schema/describe.js +3 -2
- package/generators/cli/schema/diff.js +3 -2
- package/generators/cli/schema/introspect.js +3 -2
- package/generators/cli/schema/list.js +3 -2
- package/generators/cli/schema/migrate.js +3 -2
- package/generators/lib/migration/audit-table-runner.js +213 -215
- package/generators/lib/templates/dashboard-catalog.js +1 -437
- package/generators/lib/templates/db-connection-env.js +1 -212
- package/generators/lib/templates/dbschema-catalog.js +1 -489
- package/generators/lib/templates/field-validation-catalog.js +1 -531
- package/generators/lib/templates/mysql-template.js +1 -3863
- package/generators/lib/templates/oracle-template.js +1 -3915
- package/generators/lib/templates/postgres-template.js +1 -5838
- package/generators/lib/templates/query-declarative-catalog.js +1 -199
- package/generators/lib/templates/sqlite-template.js +1 -3440
- package/generators/lib/utils/env-manager.js +6 -0
- package/generators/lib/utils/path-validator.js +71 -0
- package/generators/lib/validators/payload-validator.js +1 -2
- package/integrity-manifest.json +28 -10
- package/package.json +11 -3
- package/scripts/verify-integrity.js +1 -1
- package/server.js +1 -1
- package/src/components/handlers/adjust_handler.js +1 -1
- package/src/components/handlers/audit_handler.js +1 -1
- package/src/components/handlers/delete_handler.js +1 -1
- package/src/components/handlers/export_handler.js +1 -1
- package/src/components/handlers/import_handler.js +1 -1
- package/src/components/handlers/insert_handler.js +1 -1
- package/src/components/handlers/update_handler.js +1 -1
- package/src/components/handlers/upload_handler.js +1 -1
- package/src/components/handlers/workflow_handler.js +1 -1
- package/src/components/integrations/webhook.js +1 -1
- package/src/consumers/baseConsumer.js +1 -1
- package/src/consumers/declarativeMapper.js +1 -1
- package/src/consumers/handlers/apiHandler.js +1 -1
- package/src/consumers/handlers/consoleHandler.js +1 -1
- package/src/consumers/handlers/databaseHandler.js +1 -1
- package/src/consumers/handlers/index.js +1 -1
- package/src/consumers/handlers/kafkaHandler.js +1 -1
- package/src/consumers/index.js +1 -1
- package/src/consumers/messageTransformer.js +1 -1
- package/src/consumers/validator.js +1 -1
- package/src/core/db/dialect/base-dialect.js +1 -1
- package/src/core/db/dialect/index.js +1 -1
- package/src/core/db/dialect/mysql-dialect.js +1 -1
- package/src/core/db/dialect/oracle-dialect.js +1 -1
- package/src/core/db/dialect/postgres-dialect.js +1 -1
- package/src/core/db/dialect/sqlite-dialect.js +1 -1
- package/src/core/db/flatten-helper.js +1 -1
- package/src/core/db/query-builder-error.js +1 -1
- package/src/core/db/query-builder.js +1 -1
- package/src/core/db/relation-helper.js +1 -1
- package/src/core/handlers/delete_handler.js +1 -1
- package/src/core/handlers/insert_handler.js +1 -1
- package/src/core/handlers/update_handler.js +1 -1
- package/src/core/models/base-model.js +1 -1
- package/src/core/utils/cache-manager.js +1 -1
- package/src/core/utils/component-engine.js +1 -1
- package/src/core/utils/context-builder.js +1 -1
- package/src/core/utils/datetime-formatter.js +1 -1
- package/src/core/utils/datetime-parser.js +1 -1
- package/src/core/utils/db.js +1 -1
- package/src/core/utils/logger.js +1 -1
- package/src/core/utils/payload-loader.js +1 -1
- package/src/core/utils/security-checks.js +1 -1
- package/src/middleware/body-options.js +1 -1
- package/src/middleware/cors.js +1 -1
- package/src/middleware/idempotency.js +1 -1
- package/src/middleware/rate-limiter.js +1 -1
- package/src/middleware/request-logger.js +1 -1
- package/src/middleware/security-headers.js +1 -1
- package/src/models/base-model-mysql.js +1 -1
- package/src/models/base-model-oracle.js +1 -1
- package/src/models/base-model-sqlite.js +1 -1
- package/src/models/base-model.js +1 -1
- package/src/pro/caching/redis-client.js +1 -1
- package/src/pro/caching/redis-helper.js +1 -1
- package/src/pro/consumers/baseConsumer.js +1 -1
- package/src/pro/consumers/declarativeMapper.js +1 -1
- package/src/pro/consumers/handlers/apiHandler.js +1 -1
- package/src/pro/consumers/handlers/consoleHandler.js +1 -1
- package/src/pro/consumers/handlers/databaseHandler.js +1 -1
- package/src/pro/consumers/handlers/index.js +1 -1
- package/src/pro/consumers/handlers/kafkaHandler.js +1 -1
- package/src/pro/consumers/index.js +1 -1
- package/src/pro/consumers/messageTransformer.js +1 -1
- package/src/pro/consumers/validator.js +1 -1
- package/src/pro/database/base-model-mysql.js +1 -1
- package/src/pro/database/base-model-oracle.js +1 -1
- package/src/pro/database/base-model-sqlite.js +1 -1
- package/src/pro/database/db-mysql.js +1 -1
- package/src/pro/database/db-oracle.js +1 -1
- package/src/pro/database/db-sqlite.js +1 -1
- package/src/pro/excel/excel-generator.js +1 -1
- package/src/pro/excel/excel-parser.js +1 -1
- package/src/pro/excel/export-service.js +1 -1
- package/src/pro/excel/export_handler.js +1 -1
- package/src/pro/excel/import-service.js +1 -1
- package/src/pro/excel/import-validator.js +1 -1
- package/src/pro/excel/import_handler.js +1 -1
- package/src/pro/excel/upsert-builder.js +1 -1
- package/src/pro/idgen/idgen-routes.js +1 -1
- package/src/pro/integrations/lookup-resolver.js +1 -1
- package/src/pro/integrations/upload-handler-v2.js +1 -1
- package/src/pro/integrations/upload-handler.js +1 -1
- package/src/pro/integrations/webhook.js +1 -1
- package/src/pro/locking/lock-routes.js +1 -1
- package/src/pro/locking/resource-lock-manager.js +1 -1
- package/src/pro/messaging/kafkaConsumerService.js +1 -1
- package/src/pro/messaging/kafkaService.js +1 -1
- package/src/pro/messaging/messagehubService.js +1 -1
- package/src/pro/messaging/rabbitmqService.js +1 -1
- package/src/pro/scheduler/job-manager.js +1 -1
- package/src/pro/scheduler/job-routes.js +1 -1
- package/src/pro/scheduler/job-validator.js +1 -1
- package/src/pro/storage/base-storage-provider.js +1 -1
- package/src/pro/storage/file-metadata-helper.js +1 -1
- package/src/pro/storage/index.js +1 -1
- package/src/pro/storage/local-storage-provider.js +1 -1
- package/src/pro/storage/s3-storage-provider.js +1 -1
- package/src/pro/storage/upload-cleanup-job.js +1 -1
- package/src/pro/storage/upload-cleanup-scheduler.js +1 -1
- package/src/pro/storage/upload-pending-tracker.js +1 -1
- package/src/pro/websocket/broadcast-helper.js +1 -1
- package/src/pro/websocket/index.js +1 -1
- package/src/pro/websocket/livesync-server.js +1 -1
- package/src/pro/websocket/ws-broadcaster.js +1 -1
- package/src/services/export-service.js +1 -1
- package/src/services/import-service.js +1 -1
- package/src/services/kafkaConsumerService.js +1 -1
- package/src/services/kafkaService.js +1 -1
- package/src/services/messagehubService.js +1 -1
- package/src/services/rabbitmqService.js +1 -1
- package/src/utils/cache-invalidation-registry.js +1 -1
- package/src/utils/cache-manager.js +1 -1
- package/src/utils/component-engine.js +1 -1
- package/src/utils/config-extractor.js +1 -1
- package/src/utils/consumerLogger.js +1 -1
- package/src/utils/context-builder.js +1 -1
- package/src/utils/dashboard-helpers.js +1 -1
- package/src/utils/dateHelper.js +1 -1
- package/src/utils/datetime-formatter.js +1 -1
- package/src/utils/datetime-parser.js +1 -1
- package/src/utils/db-bootstrap.js +1 -1
- package/src/utils/db-mysql.js +1 -1
- package/src/utils/db-oracle.js +1 -1
- package/src/utils/db-sqlite.js +1 -1
- package/src/utils/db.js +1 -1
- package/src/utils/demo-generator.js +1 -1
- package/src/utils/excel-generator.js +1 -1
- package/src/utils/excel-parser.js +1 -1
- package/src/utils/file-watcher.js +1 -1
- package/src/utils/id-generator.js +1 -1
- package/src/utils/idempotency-manager.js +1 -1
- package/src/utils/import-validator.js +1 -1
- package/src/utils/license-client.js +1 -1
- package/src/utils/lock-manager.js +1 -1
- package/src/utils/logger.js +1 -1
- package/src/utils/lookup-resolver.js +1 -1
- package/src/utils/payload-loader.js +1 -1
- package/src/utils/processor-response.js +1 -1
- package/src/utils/rabbitmq.js +1 -1
- package/src/utils/redis-client.js +1 -1
- package/src/utils/redis-helper.js +1 -1
- package/src/utils/request-scope.js +1 -1
- package/src/utils/security-checks.js +1 -1
- package/src/utils/service-resolver.js +1 -1
- package/src/utils/shutdown-coordinator.js +1 -1
- package/src/utils/trusted-keys.js +1 -1
- package/src/utils/upload-handler.js +1 -1
- package/src/utils/upsert-builder.js +1 -1
- package/src/utils/workflow-hook-executor.js +1 -1
- package/generators/metadata/global.json +0 -58
- package/generators/metadata/test-mysql-workbench.json +0 -118
- package/generators/metadata/test-mysql.json +0 -56
- package/generators/metadata/test-oracle-workbench.json +0 -118
- package/generators/metadata/test-oracle.json +0 -56
- package/generators/metadata/test-pg-workbench.json +0 -118
- package/generators/metadata/test-pg.json +0 -56
- package/generators/scripts/obfuscate-source.js +0 -356
- package/generators/scripts/validate-catalog.js +0 -430
- package/generators/scripts/validate-dbschema-catalog.js +0 -708
- package/generators/tests/baseline/mysql/mini_inventory_item/src/models/mini-inventory/item.js +0 -944
- package/generators/tests/baseline/mysql/mini_inventory_item/src/modules/mini-inventory/item.js +0 -740
- package/generators/tests/baseline/mysql/mini_inventory_item/src/modules/mini-inventory.js +0 -336
- package/generators/tests/baseline/oracle/mini_inventory_item/src/models/mini-inventory/item.js +0 -1002
- package/generators/tests/baseline/oracle/mini_inventory_item/src/modules/mini-inventory/item.js +0 -740
- package/generators/tests/baseline/oracle/mini_inventory_item/src/modules/mini-inventory.js +0 -336
- package/generators/tests/baseline/postgres/mini_inventory_item/src/models/mini-inventory/item.js +0 -1333
- package/generators/tests/baseline/postgres/mini_inventory_item/src/modules/mini-inventory/item.js +0 -1173
- package/generators/tests/baseline/postgres/mini_inventory_item/src/modules/mini-inventory.js +0 -496
- package/generators/tests/fixtures/payloads/custom-sensitive.json +0 -27
- package/generators/tests/fixtures/payloads/dynamic-search-optout.json +0 -23
- package/generators/tests/fixtures/payloads/login-with-password.json +0 -22
- package/generators/tests/fixtures/payloads/order-process.json +0 -52
- package/generators/tests/fixtures/payloads/with-inline-sql.json +0 -26
- package/generators/tests/integration-tahap4b/README.md +0 -145
- package/generators/tests/integration-tahap4b/run-concurrent.js +0 -77
- package/generators/tests/integration-tahap4b/seed.sql +0 -53
- package/generators/tests/integration-tahap4b/verify.sql +0 -110
- package/generators/tests/unit/cli/create-dashboard.test.js +0 -505
- package/generators/tests/unit/cli/create-processor.test.js +0 -319
- package/generators/tests/unit/cli/dispatch-dashboard.test.js +0 -149
- package/generators/tests/unit/lib/dashboard-generator.test.js +0 -895
- package/generators/tests/unit/lib/dashboard-validator.test.js +0 -354
- package/generators/tests/unit/lib/dbschema-kit/apply-executor.test.js +0 -437
- package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-introspect.test.js +0 -393
- package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-generate-ddl.test.js +0 -104
- package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-init.test.js +0 -119
- package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-list.test.js +0 -48
- package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-migrate.test.js +0 -175
- package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-validate.test.js +0 -102
- package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-models.test.js +0 -43
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/all-schemas-listing.js +0 -84
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/connection-error.js +0 -13
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/empty.js +0 -12
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/multi-schema.js +0 -124
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/single-schema-inventory.js +0 -64
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/two-tables.js +0 -66
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/connection-error.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/partial.js +0 -29
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/rollback.js +0 -26
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/success.js +0 -43
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/multi-schema/audit/events.js +0 -18
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/multi-schema/inventory/products.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/multi-schema/users.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/connection.test.js +0 -112
- package/generators/tests/unit/lib/dbschema-kit/ddl-generator.test.js +0 -205
- package/generators/tests/unit/lib/dbschema-kit/define-model.test.js +0 -56
- package/generators/tests/unit/lib/dbschema-kit/dialect/index.test.js +0 -46
- package/generators/tests/unit/lib/dbschema-kit/dialect/mysql.test.js +0 -126
- package/generators/tests/unit/lib/dbschema-kit/dialect/oracle.test.js +0 -126
- package/generators/tests/unit/lib/dbschema-kit/dialect/postgres.test.js +0 -131
- package/generators/tests/unit/lib/dbschema-kit/dialect/sqlite.test.js +0 -126
- package/generators/tests/unit/lib/dbschema-kit/driver-loader.test.js +0 -93
- package/generators/tests/unit/lib/dbschema-kit/emitters/create-index.test.js +0 -173
- package/generators/tests/unit/lib/dbschema-kit/emitters/create-table.test.js +0 -376
- package/generators/tests/unit/lib/dbschema-kit/emitters/drop-table.test.js +0 -78
- package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/invalid-dialect.env +0 -6
- package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/missing-dialect.env +0 -5
- package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/missing-host.env +0 -5
- package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/oracle-valid.env +0 -6
- package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/postgres-valid.env +0 -7
- package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/sqlite-valid.env +0 -2
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/category.js +0 -11
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/item_product.js +0 -11
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/stock_inbound.js +0 -24
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/stock_inbound_item.js +0 -28
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/supplier.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/warehouse.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-invalid/orphan.js +0 -17
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/category.js +0 -11
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/item_product.js +0 -11
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/supplier.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/warehouse.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/transactions/stock_inbound.js +0 -24
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/transactions/stock_inbound_item.js +0 -28
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/multi-schema/audit/events.js +0 -18
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/multi-schema/inventory/products.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/multi-schema/public/users.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-subfolder/extra/category.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-subfolder/master/category.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-tablename/bar.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-tablename/foo.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/empty-folder/README.md +0 -1
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/invalid-export/plain.js +0 -3
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/invalid-schema/bad.js +0 -6
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/legacy-pattern/legacy.js +0 -12
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-distinct/audit/products.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-distinct/inventory/products.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-duplicate/a/products.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-duplicate/b/products.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/nested-deep/a/b/c/deep_table.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/.hidden/ignored.js +0 -7
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/master/category.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/master/supplier.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/transactions/stock_inbound.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/transactions/stock_inbound_item.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/valid-multiple/category.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/valid-multiple/item_product.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/valid-single/category.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/integration.test.js +0 -217
- package/generators/tests/unit/lib/dbschema-kit/introspect-mapper.test.js +0 -403
- package/generators/tests/unit/lib/dbschema-kit/ir-builder.test.js +0 -390
- package/generators/tests/unit/lib/dbschema-kit/loader.test.js +0 -128
- package/generators/tests/unit/lib/dbschema-kit/naming.test.js +0 -170
- package/generators/tests/unit/lib/dbschema-kit/parser/shorthand-parser.test.js +0 -237
- package/generators/tests/unit/lib/dbschema-kit/schema-printer.test.js +0 -251
- package/generators/tests/unit/lib/dbschema-kit/statement-modifier.test.js +0 -105
- package/generators/tests/unit/lib/dbschema-kit/statement-splitter.test.js +0 -165
- package/generators/tests/unit/lib/dbschema-kit/topological-sort.test.js +0 -135
- package/generators/tests/unit/lib/dbschema-kit/validator/check-compatibility-validator.test.js +0 -373
- package/generators/tests/unit/lib/dbschema-kit/validator/circular-relation-validator.test.js +0 -454
- package/generators/tests/unit/lib/dbschema-kit/validator/cross-model-validator.test.js +0 -512
- package/generators/tests/unit/lib/dbschema-kit/validator/enhanced-validate-integration.test.js +0 -390
- package/generators/tests/unit/lib/dbschema-kit/validator/naming-convention-validator.test.js +0 -306
- package/generators/tests/unit/lib/dbschema-kit/validator/schema-validator.test.js +0 -443
- package/generators/tests/unit/lib/dbschema-kit/validator/type-compatibility-validator.test.js +0 -440
- package/generators/tests/unit/lib/dbschema-kit/validator/validator-reporter.test.js +0 -172
- package/generators/tests/unit/lib/metadata-manager-dashboard.test.js +0 -256
- package/generators/tests/unit/lib/payload-validator-fieldpolicy.test.js +0 -240
- package/generators/tests/unit/lib/processor-validation-generator.test.js +0 -300
- package/generators/tests/unit/lib/sensitive-field-masker.test.js +0 -170
- package/generators/tests/unit/lib/sql-table-extractor.test.js +0 -119
- package/scripts/generate-integrity-manifest.js +0 -124
- package/scripts/snapshot-cli-contracts.js +0 -194
- 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
|
-
});
|