@cosmicdrift/kumiko-framework 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +159 -0
- package/package.json +91 -0
- package/src/__tests__/anonymous-access.integration.ts +325 -0
- package/src/__tests__/error-contract.integration.ts +435 -0
- package/src/__tests__/field-access.integration.ts +269 -0
- package/src/__tests__/full-stack.integration.ts +914 -0
- package/src/__tests__/ownership.integration.ts +449 -0
- package/src/__tests__/reference-data.integration.ts +198 -0
- package/src/__tests__/transition-guard.integration.ts +340 -0
- package/src/api/__tests__/api.test.ts +337 -0
- package/src/api/__tests__/auth-middleware-transport.test.ts +80 -0
- package/src/api/__tests__/auth-routes-cookie.test.ts +179 -0
- package/src/api/__tests__/batch.integration.ts +404 -0
- package/src/api/__tests__/body-limit.test.ts +88 -0
- package/src/api/__tests__/csrf-middleware.test.ts +97 -0
- package/src/api/__tests__/dispatcher-live.integration.ts +216 -0
- package/src/api/__tests__/metrics-endpoint.test.ts +126 -0
- package/src/api/__tests__/nested-write.integration.ts +213 -0
- package/src/api/__tests__/readiness.test.ts +76 -0
- package/src/api/__tests__/request-id-middleware.test.ts +72 -0
- package/src/api/__tests__/sse-broker.test.ts +58 -0
- package/src/api/__tests__/sse-route.test.ts +112 -0
- package/src/api/anonymous-cookie.ts +60 -0
- package/src/api/api-constants.ts +64 -0
- package/src/api/auth-middleware.ts +418 -0
- package/src/api/auth-routes.ts +982 -0
- package/src/api/csrf-middleware.ts +77 -0
- package/src/api/index.ts +31 -0
- package/src/api/jwt.ts +66 -0
- package/src/api/observability-middleware.ts +89 -0
- package/src/api/readiness.ts +132 -0
- package/src/api/request-context.ts +49 -0
- package/src/api/request-id-middleware.ts +50 -0
- package/src/api/route-registrars.ts +195 -0
- package/src/api/routes.ts +135 -0
- package/src/api/server.ts +640 -0
- package/src/api/sse-broker.ts +71 -0
- package/src/api/sse-route.ts +62 -0
- package/src/api/tokens.ts +16 -0
- package/src/db/__tests__/apply-entity-event-tenant.integration.ts +159 -0
- package/src/db/__tests__/compound-types.test.ts +114 -0
- package/src/db/__tests__/connection-options.test.ts +68 -0
- package/src/db/__tests__/cursor.test.ts +41 -0
- package/src/db/__tests__/db-helpers.test.ts +369 -0
- package/src/db/__tests__/dialect-instant.test.ts +50 -0
- package/src/db/__tests__/drizzle-helpers.integration.ts +186 -0
- package/src/db/__tests__/drizzle-table-types.test.ts +162 -0
- package/src/db/__tests__/encryption.test.ts +39 -0
- package/src/db/__tests__/event-store-executor-list.integration.ts +313 -0
- package/src/db/__tests__/event-store-executor.integration.ts +235 -0
- package/src/db/__tests__/implicit-projection-equivalence.integration.ts +304 -0
- package/src/db/__tests__/located-timestamp.test.ts +184 -0
- package/src/db/__tests__/money.test.ts +199 -0
- package/src/db/__tests__/multi-row-insert.integration.ts +76 -0
- package/src/db/__tests__/parse-auto-verb.test.ts +70 -0
- package/src/db/__tests__/required-not-null-migration-safety.integration.ts +105 -0
- package/src/db/__tests__/row-helpers.test.ts +59 -0
- package/src/db/__tests__/schema-migration.integration.ts +273 -0
- package/src/db/__tests__/table-builder-indexes.test.ts +153 -0
- package/src/db/__tests__/table-builder-required.test.ts +216 -0
- package/src/db/__tests__/tenant-db.integration.ts +606 -0
- package/src/db/__tests__/unique-violation-mapping.integration.ts +166 -0
- package/src/db/apply-entity-event.ts +188 -0
- package/src/db/assert-exists-in.ts +59 -0
- package/src/db/compound-types.ts +47 -0
- package/src/db/connection.ts +104 -0
- package/src/db/cursor.ts +83 -0
- package/src/db/dialect.ts +109 -0
- package/src/db/eagerload.ts +174 -0
- package/src/db/encryption.ts +39 -0
- package/src/db/event-store-executor.ts +906 -0
- package/src/db/index.ts +55 -0
- package/src/db/located-timestamp.ts +114 -0
- package/src/db/money.ts +120 -0
- package/src/db/pg-error.ts +46 -0
- package/src/db/reference-data.ts +77 -0
- package/src/db/row-helpers.ts +53 -0
- package/src/db/schema-inspection.ts +25 -0
- package/src/db/table-builder.ts +475 -0
- package/src/db/tenant-db.ts +434 -0
- package/src/engine/__tests__/auth-claims-registrar.test.ts +74 -0
- package/src/engine/__tests__/boot-validator-located-timestamps.test.ts +108 -0
- package/src/engine/__tests__/boot-validator.test.ts +1865 -0
- package/src/engine/__tests__/build-app-schema.test.ts +154 -0
- package/src/engine/__tests__/claim-keys.test.ts +274 -0
- package/src/engine/__tests__/config-helpers.test.ts +236 -0
- package/src/engine/__tests__/effective-features.test.ts +86 -0
- package/src/engine/__tests__/engine.test.ts +1461 -0
- package/src/engine/__tests__/entity-handlers.test.ts +274 -0
- package/src/engine/__tests__/event-helpers.test.ts +68 -0
- package/src/engine/__tests__/extends-registrar.test.ts +159 -0
- package/src/engine/__tests__/factories-long-text.test.ts +84 -0
- package/src/engine/__tests__/factories-time.test.ts +158 -0
- package/src/engine/__tests__/field-predicates.test.ts +48 -0
- package/src/engine/__tests__/hook-phases.test.ts +132 -0
- package/src/engine/__tests__/identifiers.test.ts +35 -0
- package/src/engine/__tests__/lifecycle-hooks.test.ts +237 -0
- package/src/engine/__tests__/nav.test.ts +267 -0
- package/src/engine/__tests__/ownership.test.ts +421 -0
- package/src/engine/__tests__/parse-ref-target.test.ts +43 -0
- package/src/engine/__tests__/projection-helpers.test.ts +62 -0
- package/src/engine/__tests__/projection.test.ts +191 -0
- package/src/engine/__tests__/qualified-name.test.ts +264 -0
- package/src/engine/__tests__/resolve-config-or-param.test.ts +315 -0
- package/src/engine/__tests__/run-in.test.ts +38 -0
- package/src/engine/__tests__/schema-builder.test.ts +380 -0
- package/src/engine/__tests__/screen.test.ts +408 -0
- package/src/engine/__tests__/state-machine.test.ts +148 -0
- package/src/engine/__tests__/system-user.test.ts +57 -0
- package/src/engine/__tests__/validation-hooks.test.ts +71 -0
- package/src/engine/access.ts +23 -0
- package/src/engine/boot-validator.ts +1528 -0
- package/src/engine/build-app-schema.ts +125 -0
- package/src/engine/config-helpers.ts +115 -0
- package/src/engine/constants.ts +85 -0
- package/src/engine/create-app.ts +98 -0
- package/src/engine/define-feature.ts +702 -0
- package/src/engine/define-handler.ts +78 -0
- package/src/engine/define-roles.ts +19 -0
- package/src/engine/effective-features.ts +87 -0
- package/src/engine/entity-handlers.ts +364 -0
- package/src/engine/event-helpers.ts +73 -0
- package/src/engine/factories.ts +328 -0
- package/src/engine/feature-ast/__tests__/canonical-form.test.ts +416 -0
- package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +197 -0
- package/src/engine/feature-ast/__tests__/parse-real-features.test.ts +128 -0
- package/src/engine/feature-ast/__tests__/parse.test.ts +888 -0
- package/src/engine/feature-ast/__tests__/patch.test.ts +360 -0
- package/src/engine/feature-ast/__tests__/patcher.test.ts +469 -0
- package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +287 -0
- package/src/engine/feature-ast/extractors.ts +2562 -0
- package/src/engine/feature-ast/index.ts +105 -0
- package/src/engine/feature-ast/parse.ts +369 -0
- package/src/engine/feature-ast/patch.ts +525 -0
- package/src/engine/feature-ast/patcher.ts +518 -0
- package/src/engine/feature-ast/patterns.ts +434 -0
- package/src/engine/feature-ast/render.ts +602 -0
- package/src/engine/feature-ast/source-location.ts +45 -0
- package/src/engine/field-access.ts +120 -0
- package/src/engine/index.ts +254 -0
- package/src/engine/ownership.ts +337 -0
- package/src/engine/parse-ref-target.ts +22 -0
- package/src/engine/pattern-library/__tests__/library.test.ts +351 -0
- package/src/engine/pattern-library/index.ts +24 -0
- package/src/engine/pattern-library/library.ts +1117 -0
- package/src/engine/pattern-library/types.ts +255 -0
- package/src/engine/projection-helpers.ts +85 -0
- package/src/engine/qualified-name.ts +122 -0
- package/src/engine/read-claim.ts +31 -0
- package/src/engine/registry.ts +1325 -0
- package/src/engine/resolve-config-or-param.ts +153 -0
- package/src/engine/run-in.ts +29 -0
- package/src/engine/schema-builder.ts +175 -0
- package/src/engine/screen-filter-ops.ts +51 -0
- package/src/engine/state-machine.ts +70 -0
- package/src/engine/system-user.ts +32 -0
- package/src/engine/types/config.ts +306 -0
- package/src/engine/types/event-type-map.ts +37 -0
- package/src/engine/types/feature.ts +574 -0
- package/src/engine/types/fields.ts +422 -0
- package/src/engine/types/handlers.ts +742 -0
- package/src/engine/types/hooks.ts +142 -0
- package/src/engine/types/http-route.ts +54 -0
- package/src/engine/types/identifiers.ts +47 -0
- package/src/engine/types/index.ts +208 -0
- package/src/engine/types/nav.ts +46 -0
- package/src/engine/types/projection.ts +132 -0
- package/src/engine/types/relations.ts +51 -0
- package/src/engine/types/screen.ts +452 -0
- package/src/engine/types/workspace.ts +42 -0
- package/src/engine/validation.ts +33 -0
- package/src/entrypoint/__tests__/entrypoint-job-wiring.integration.ts +173 -0
- package/src/entrypoint/__tests__/split-deploy.integration.ts +297 -0
- package/src/entrypoint/index.ts +442 -0
- package/src/errors/__tests__/classes.test.ts +371 -0
- package/src/errors/__tests__/write-failures.test.ts +109 -0
- package/src/errors/classes.ts +249 -0
- package/src/errors/i18n/de.yaml +83 -0
- package/src/errors/i18n/en.yaml +80 -0
- package/src/errors/index.ts +41 -0
- package/src/errors/kumiko-error.ts +67 -0
- package/src/errors/reasons.ts +36 -0
- package/src/errors/serialize.ts +136 -0
- package/src/errors/transition-details.ts +30 -0
- package/src/errors/write-error-info.ts +123 -0
- package/src/errors/zod-bridge.ts +49 -0
- package/src/event-store/__tests__/admin-api.integration.ts +361 -0
- package/src/event-store/__tests__/event-store.integration.ts +584 -0
- package/src/event-store/__tests__/get-stream-version-perf.integration.ts +83 -0
- package/src/event-store/__tests__/perf.integration.ts +255 -0
- package/src/event-store/__tests__/snapshot.integration.ts +267 -0
- package/src/event-store/__tests__/upcaster-dead-letter.integration.ts +204 -0
- package/src/event-store/__tests__/upcaster.integration.ts +460 -0
- package/src/event-store/admin-api.ts +257 -0
- package/src/event-store/archive.ts +106 -0
- package/src/event-store/errors.ts +35 -0
- package/src/event-store/event-store.ts +405 -0
- package/src/event-store/events-schema.ts +90 -0
- package/src/event-store/index.ts +50 -0
- package/src/event-store/snapshot.ts +210 -0
- package/src/event-store/upcaster-dead-letter.ts +119 -0
- package/src/event-store/upcaster.ts +147 -0
- package/src/files/__tests__/content-disposition.test.ts +123 -0
- package/src/files/__tests__/file-field-column.integration.ts +103 -0
- package/src/files/__tests__/file-field-pipeline.integration.ts +211 -0
- package/src/files/__tests__/file-handle.test.ts +122 -0
- package/src/files/__tests__/files.integration.ts +830 -0
- package/src/files/__tests__/storage-tracking.integration.ts +153 -0
- package/src/files/content-disposition.ts +55 -0
- package/src/files/file-handle.ts +63 -0
- package/src/files/file-ref-table.ts +22 -0
- package/src/files/file-routes.ts +353 -0
- package/src/files/in-memory-provider.ts +62 -0
- package/src/files/index.ts +29 -0
- package/src/files/local-provider.ts +35 -0
- package/src/files/storage-tracking.ts +60 -0
- package/src/files/types.ts +118 -0
- package/src/i18n/__tests__/i18n.test.ts +72 -0
- package/src/i18n/index.ts +29 -0
- package/src/jobs/__tests__/job-event-trigger.integration.ts +172 -0
- package/src/jobs/__tests__/job-multi-trigger.integration.ts +144 -0
- package/src/jobs/__tests__/jobs.integration.ts +566 -0
- package/src/jobs/index.ts +2 -0
- package/src/jobs/job-runner.ts +574 -0
- package/src/lifecycle/__tests__/create-test-lifecycle.ts +19 -0
- package/src/lifecycle/__tests__/lifecycle-server.integration.ts +108 -0
- package/src/lifecycle/__tests__/lifecycle.test.ts +212 -0
- package/src/lifecycle/__tests__/signal-handlers.test.ts +106 -0
- package/src/lifecycle/index.ts +13 -0
- package/src/lifecycle/lifecycle.ts +160 -0
- package/src/lifecycle/signal-handlers.ts +62 -0
- package/src/logging/__tests__/pino-trace-bridge.test.ts +50 -0
- package/src/logging/index.ts +3 -0
- package/src/logging/pino-logger.ts +64 -0
- package/src/logging/types.ts +7 -0
- package/src/migrations/__tests__/compare-snapshots.test.ts +150 -0
- package/src/migrations/__tests__/detect-drift.integration.ts +320 -0
- package/src/migrations/__tests__/detect-projections-to-rebuild.integration.ts +134 -0
- package/src/migrations/__tests__/rebuild-marker.test.ts +79 -0
- package/src/migrations/index.ts +28 -0
- package/src/migrations/projection-detection.ts +149 -0
- package/src/migrations/rebuild-marker.ts +64 -0
- package/src/migrations/schema-drift.ts +395 -0
- package/src/observability/__tests__/console-provider.test.ts +67 -0
- package/src/observability/__tests__/metric-validator.test.ts +87 -0
- package/src/observability/__tests__/noop-provider.test.ts +82 -0
- package/src/observability/__tests__/observability.integration.ts +559 -0
- package/src/observability/__tests__/prometheus-meter.test.ts +144 -0
- package/src/observability/__tests__/recording-meter.test.ts +101 -0
- package/src/observability/__tests__/recording-tracer.test.ts +110 -0
- package/src/observability/__tests__/sensitive-filter.test.ts +98 -0
- package/src/observability/console-provider.ts +130 -0
- package/src/observability/context.ts +26 -0
- package/src/observability/fallback.ts +34 -0
- package/src/observability/ids.ts +25 -0
- package/src/observability/index.ts +79 -0
- package/src/observability/metric-validator.ts +86 -0
- package/src/observability/metrics-handle.ts +56 -0
- package/src/observability/noop-provider.ts +146 -0
- package/src/observability/prometheus-meter.ts +284 -0
- package/src/observability/recording-meter.ts +156 -0
- package/src/observability/recording-tracer.ts +198 -0
- package/src/observability/redis-wrapper.ts +132 -0
- package/src/observability/sensitive-filter.ts +108 -0
- package/src/observability/standard-metrics.ts +213 -0
- package/src/observability/types/index.ts +29 -0
- package/src/observability/types/metric.ts +56 -0
- package/src/observability/types/provider.ts +32 -0
- package/src/observability/types/span.ts +64 -0
- package/src/pipeline/__tests__/archive-stream.integration.ts +220 -0
- package/src/pipeline/__tests__/auth-claims-resolver.test.ts +279 -0
- package/src/pipeline/__tests__/cascade-handler.integration.ts +419 -0
- package/src/pipeline/__tests__/cascade-handler.test.ts +52 -0
- package/src/pipeline/__tests__/causation-chain.integration.ts +206 -0
- package/src/pipeline/__tests__/ctx-bridge.integration.ts +234 -0
- package/src/pipeline/__tests__/dispatcher.test.ts +379 -0
- package/src/pipeline/__tests__/distributed-lock.integration.ts +67 -0
- package/src/pipeline/__tests__/domain-events-projections.integration.ts +323 -0
- package/src/pipeline/__tests__/event-dedup.integration.ts +153 -0
- package/src/pipeline/__tests__/event-define-event-strict.integration.ts +202 -0
- package/src/pipeline/__tests__/event-dispatcher-lifecycle.integration.ts +220 -0
- package/src/pipeline/__tests__/event-dispatcher-multi-instance.integration.ts +423 -0
- package/src/pipeline/__tests__/event-dispatcher-pg-listen.integration.ts +123 -0
- package/src/pipeline/__tests__/event-dispatcher-recovery.integration.ts +202 -0
- package/src/pipeline/__tests__/event-dispatcher-second-audit.integration.ts +290 -0
- package/src/pipeline/__tests__/event-dispatcher-strict.test.ts +65 -0
- package/src/pipeline/__tests__/event-dispatcher.integration.ts +287 -0
- package/src/pipeline/__tests__/event-retention.integration.ts +239 -0
- package/src/pipeline/__tests__/fetch-for-writing.integration.ts +281 -0
- package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +430 -0
- package/src/pipeline/__tests__/load-aggregate-query.integration.ts +266 -0
- package/src/pipeline/__tests__/msp-error-mode.integration.ts +149 -0
- package/src/pipeline/__tests__/msp-multi-hop.integration.ts +228 -0
- package/src/pipeline/__tests__/msp-rebuild.integration.ts +368 -0
- package/src/pipeline/__tests__/multi-stream-projection.integration.ts +341 -0
- package/src/pipeline/__tests__/perf-rebuild.integration.ts +147 -0
- package/src/pipeline/__tests__/projection-rebuild.integration.ts +551 -0
- package/src/pipeline/__tests__/query-projection.integration.ts +201 -0
- package/src/pipeline/__tests__/redis-pipeline.integration.ts +306 -0
- package/src/pipeline/append-event-core.ts +117 -0
- package/src/pipeline/auth-claims-resolver.ts +103 -0
- package/src/pipeline/cascade-handler.ts +113 -0
- package/src/pipeline/dispatcher.ts +1585 -0
- package/src/pipeline/distributed-lock.ts +37 -0
- package/src/pipeline/entity-cache.ts +113 -0
- package/src/pipeline/event-consumer-state.ts +108 -0
- package/src/pipeline/event-dedup.ts +23 -0
- package/src/pipeline/event-dispatcher.ts +1016 -0
- package/src/pipeline/event-retention.ts +154 -0
- package/src/pipeline/idempotency.ts +76 -0
- package/src/pipeline/index.ts +66 -0
- package/src/pipeline/lifecycle-pipeline.ts +409 -0
- package/src/pipeline/msp-rebuild.ts +242 -0
- package/src/pipeline/multi-stream-apply-context.ts +115 -0
- package/src/pipeline/projection-rebuild.ts +334 -0
- package/src/pipeline/projection-state.ts +72 -0
- package/src/pipeline/projections-runner.ts +56 -0
- package/src/pipeline/redis-keys.ts +11 -0
- package/src/pipeline/system-hooks.ts +190 -0
- package/src/random/__tests__/generate.test.ts +149 -0
- package/src/random/generate.ts +141 -0
- package/src/random/index.ts +8 -0
- package/src/random/words.ts +392 -0
- package/src/rate-limit/__tests__/dispatcher-l3.integration.ts +111 -0
- package/src/rate-limit/__tests__/middleware.integration.ts +189 -0
- package/src/rate-limit/__tests__/resolver.integration.ts +189 -0
- package/src/rate-limit/bucket.ts +36 -0
- package/src/rate-limit/index.ts +14 -0
- package/src/rate-limit/middleware.ts +152 -0
- package/src/rate-limit/resolver.ts +267 -0
- package/src/redis/__tests__/redis-options.test.ts +54 -0
- package/src/redis/index.ts +74 -0
- package/src/search/__tests__/meilisearch-adapter.integration.ts +236 -0
- package/src/search/__tests__/search-adapter.test.ts +256 -0
- package/src/search/in-memory-adapter.ts +123 -0
- package/src/search/index.ts +12 -0
- package/src/search/meilisearch-adapter.ts +106 -0
- package/src/search/types.ts +39 -0
- package/src/secrets/__tests__/dek-cache.test.ts +213 -0
- package/src/secrets/__tests__/env-master-key-provider.test.ts +119 -0
- package/src/secrets/__tests__/envelope.test.ts +74 -0
- package/src/secrets/__tests__/leak-guard.test.ts +92 -0
- package/src/secrets/__tests__/rotation.test.ts +149 -0
- package/src/secrets/dek-cache.ts +116 -0
- package/src/secrets/env-master-key-provider.ts +162 -0
- package/src/secrets/envelope.ts +55 -0
- package/src/secrets/index.ts +19 -0
- package/src/secrets/leak-guard.ts +87 -0
- package/src/secrets/rotation.ts +34 -0
- package/src/secrets/types.ts +107 -0
- package/src/stack/db.ts +104 -0
- package/src/stack/event-collector.ts +23 -0
- package/src/stack/index.ts +32 -0
- package/src/stack/redis.ts +44 -0
- package/src/stack/request-helper.ts +168 -0
- package/src/stack/table-helpers.ts +104 -0
- package/src/stack/test-stack.ts +357 -0
- package/src/stack/test-users.ts +37 -0
- package/src/testing/__tests__/e2e-generator.test.ts +230 -0
- package/src/testing/__tests__/ensure-entity-table.integration.ts +54 -0
- package/src/testing/access-assertions.ts +15 -0
- package/src/testing/assertions.ts +35 -0
- package/src/testing/e2e-generator.ts +465 -0
- package/src/testing/expect-error.ts +25 -0
- package/src/testing/handler-context.ts +125 -0
- package/src/testing/http-cookies.ts +52 -0
- package/src/testing/index.ts +41 -0
- package/src/testing/late-bound.ts +39 -0
- package/src/testing/mutable-master-key-provider.ts +31 -0
- package/src/testing/observability-recorder.ts +54 -0
- package/src/testing/shared-entities.ts +49 -0
- package/src/testing/utils.ts +1 -0
- package/src/testing/wait-for.ts +31 -0
- package/src/time/__tests__/polyfill.test.ts +73 -0
- package/src/time/__tests__/tz-context.test.ts +121 -0
- package/src/time/index.ts +21 -0
- package/src/time/polyfill.ts +70 -0
- package/src/time/tz-context.ts +107 -0
- package/src/ui-types/app-schema.ts +57 -0
- package/src/ui-types/index.ts +65 -0
- package/src/utils/__tests__/assert.test.ts +17 -0
- package/src/utils/__tests__/env-parse.test.ts +54 -0
- package/src/utils/assert.ts +18 -0
- package/src/utils/env-parse.ts +16 -0
- package/src/utils/ids.ts +16 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/safe-json.ts +30 -0
- package/src/utils/serialization.ts +7 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Regression test for the file/image entity-column type: must be UUID to
|
|
2
|
+
// match fileRefsTable.id (uuid). Pre-fix the column was `integer`, which
|
|
3
|
+
// silently blocked any client from storing a fileRef reference on the
|
|
4
|
+
// entity — UUID → integer cast raised a PG error or truncated.
|
|
5
|
+
//
|
|
6
|
+
// Intentionally a minimal dedicated suite rather than a case in
|
|
7
|
+
// files.integration.ts because the bug is about *table generation*, not
|
|
8
|
+
// runtime behaviour of the upload route.
|
|
9
|
+
|
|
10
|
+
import { sql } from "drizzle-orm";
|
|
11
|
+
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
12
|
+
import { createEntity, createFileField, createImageField } from "../../engine";
|
|
13
|
+
import { createEntityTable, createTestDb, pushTables, type TestDb } from "../../stack";
|
|
14
|
+
import { generateId } from "../../utils";
|
|
15
|
+
import { fileRefsTable } from "../file-ref-table";
|
|
16
|
+
|
|
17
|
+
// Entity with BOTH singular file-field types exercised — the bug applied
|
|
18
|
+
// identically to `file` and `image` (same switch-case in table-builder).
|
|
19
|
+
const documentEntity = createEntity({
|
|
20
|
+
table: "regression_documents",
|
|
21
|
+
fields: {
|
|
22
|
+
attachment: createFileField(),
|
|
23
|
+
cover: createImageField(),
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
let testDb: TestDb;
|
|
28
|
+
|
|
29
|
+
beforeAll(async () => {
|
|
30
|
+
testDb = await createTestDb();
|
|
31
|
+
await pushTables(testDb.db, { fileRefsTable });
|
|
32
|
+
await createEntityTable(testDb.db, documentEntity);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
afterAll(async () => {
|
|
36
|
+
await testDb.cleanup();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe("file-field entity-column type", () => {
|
|
40
|
+
test("`file` and `image` fields generate UUID columns (not integer)", async () => {
|
|
41
|
+
// Pull the actual column type from information_schema. This is the
|
|
42
|
+
// load-bearing assertion: the type emitted by drizzle-kit during
|
|
43
|
+
// `createEntityTable` must be `uuid`. A regression to `integer` would
|
|
44
|
+
// fail here even if higher-level code happened to still work through
|
|
45
|
+
// implicit casts.
|
|
46
|
+
const rows = await testDb.db.execute<{ column_name: string; data_type: string }>(sql`
|
|
47
|
+
SELECT column_name, data_type
|
|
48
|
+
FROM information_schema.columns
|
|
49
|
+
WHERE table_name = 'regression_documents'
|
|
50
|
+
AND column_name IN ('attachment', 'cover')
|
|
51
|
+
ORDER BY column_name
|
|
52
|
+
`);
|
|
53
|
+
const arr = rows as unknown as Array<{ column_name: string; data_type: string }>;
|
|
54
|
+
expect(arr).toEqual([
|
|
55
|
+
{ column_name: "attachment", data_type: "uuid" },
|
|
56
|
+
{ column_name: "cover", data_type: "uuid" },
|
|
57
|
+
]);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("storing a fileRef UUID in a file-field column round-trips cleanly", async () => {
|
|
61
|
+
// Seed a fileRef row so we have a real UUID to reference. Full upload
|
|
62
|
+
// flow isn't exercised here — we're verifying the CRUD column contract.
|
|
63
|
+
const fileUuid = generateId();
|
|
64
|
+
const tenantId = generateId();
|
|
65
|
+
await testDb.db.execute(sql`
|
|
66
|
+
INSERT INTO file_refs (id, tenant_id, storage_key, file_name, mime_type, size)
|
|
67
|
+
VALUES (
|
|
68
|
+
${fileUuid}::uuid, ${tenantId}::uuid, 'seed-key',
|
|
69
|
+
'seed.pdf', 'application/pdf', 1024
|
|
70
|
+
)
|
|
71
|
+
`);
|
|
72
|
+
|
|
73
|
+
const docId = generateId();
|
|
74
|
+
await testDb.db.execute(sql`
|
|
75
|
+
INSERT INTO regression_documents (id, tenant_id, attachment, cover)
|
|
76
|
+
VALUES (
|
|
77
|
+
${docId}::uuid, ${tenantId}::uuid,
|
|
78
|
+
${fileUuid}::uuid, ${fileUuid}::uuid
|
|
79
|
+
)
|
|
80
|
+
`);
|
|
81
|
+
|
|
82
|
+
const read = await testDb.db.execute<{ attachment: string; cover: string }>(sql`
|
|
83
|
+
SELECT attachment, cover FROM regression_documents WHERE id = ${docId}::uuid
|
|
84
|
+
`);
|
|
85
|
+
const docArr = read as unknown as Array<{ attachment: string; cover: string }>;
|
|
86
|
+
expect(docArr[0]?.attachment).toBe(fileUuid);
|
|
87
|
+
expect(docArr[0]?.cover).toBe(fileUuid);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("storing a non-UUID value in a file-field column rejects — proves strict typing", async () => {
|
|
91
|
+
// If the column were still `integer`, `'not-a-uuid'` would either
|
|
92
|
+
// truncate or coerce to 0. With uuid the insert raises
|
|
93
|
+
// invalid_text_representation (22P02) — the type is actually enforced.
|
|
94
|
+
const docId = generateId();
|
|
95
|
+
const tenantId = generateId();
|
|
96
|
+
await expect(
|
|
97
|
+
testDb.db.execute(sql`
|
|
98
|
+
INSERT INTO regression_documents (id, tenant_id, attachment)
|
|
99
|
+
VALUES (${docId}::uuid, ${tenantId}::uuid, 'not-a-uuid')
|
|
100
|
+
`),
|
|
101
|
+
).rejects.toThrow();
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
// End-to-end regression for file/image-field wiring through the CRUD
|
|
2
|
+
// pipeline. The column-level fix in table-builder.ts is necessary but not
|
|
3
|
+
// sufficient: the Zod validation layer in schema-builder.ts also had a
|
|
4
|
+
// residual `z.number()` for file/image fields from an earlier era. Without
|
|
5
|
+
// that fix the pipeline would reject every valid UUID at the validation
|
|
6
|
+
// gate, before the column type ever mattered.
|
|
7
|
+
//
|
|
8
|
+
// This suite proves the whole path works end-to-end:
|
|
9
|
+
// POST /api/files → upload → receive file UUID
|
|
10
|
+
// POST /api/write → entity:create with file-field: <uuid>
|
|
11
|
+
// POST /api/query → entity:detail → UUID round-trips
|
|
12
|
+
// POST /api/write → entity:update with new file-UUID
|
|
13
|
+
// POST /api/query → entity:detail → new UUID persisted
|
|
14
|
+
|
|
15
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
16
|
+
import { tmpdir } from "node:os";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import { sql } from "drizzle-orm";
|
|
19
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
|
|
20
|
+
import {
|
|
21
|
+
createEntity,
|
|
22
|
+
createFileField,
|
|
23
|
+
createFilesField,
|
|
24
|
+
createImageField,
|
|
25
|
+
createImagesField,
|
|
26
|
+
createTextField,
|
|
27
|
+
defineEntityCreateHandler,
|
|
28
|
+
defineEntityDetailHandler,
|
|
29
|
+
defineEntityUpdateHandler,
|
|
30
|
+
defineFeature,
|
|
31
|
+
} from "../../engine";
|
|
32
|
+
import {
|
|
33
|
+
createEntityTable,
|
|
34
|
+
createTestUser,
|
|
35
|
+
setupTestStack,
|
|
36
|
+
type TestStack,
|
|
37
|
+
testTenantId,
|
|
38
|
+
} from "../../stack";
|
|
39
|
+
import { createLocalProvider } from "../local-provider";
|
|
40
|
+
|
|
41
|
+
// Covers ALL four file-field variants: singular (file/image) stores a UUID in
|
|
42
|
+
// the entity column; plural (files/images) has no entity column — the array
|
|
43
|
+
// of UUIDs lives in the event payload only (resolved via fileRefs otherwise).
|
|
44
|
+
// Both shapes must validate + round-trip through the CRUD pipeline.
|
|
45
|
+
const documentEntity = createEntity({
|
|
46
|
+
table: "pipeline_documents",
|
|
47
|
+
fields: {
|
|
48
|
+
title: createTextField({ required: true }),
|
|
49
|
+
attachment: createFileField(),
|
|
50
|
+
cover: createImageField(),
|
|
51
|
+
photos: createImagesField(),
|
|
52
|
+
docs: createFilesField(),
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const ROLES = { access: { roles: ["Admin", "User"] } } as const;
|
|
57
|
+
|
|
58
|
+
const documentFeature = defineFeature("pipeline-documents", (r) => {
|
|
59
|
+
r.entity("document", documentEntity);
|
|
60
|
+
r.writeHandler(defineEntityCreateHandler("document", documentEntity, ROLES));
|
|
61
|
+
r.writeHandler(defineEntityUpdateHandler("document", documentEntity, ROLES));
|
|
62
|
+
r.queryHandler(defineEntityDetailHandler("document", documentEntity, ROLES));
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
let stack: TestStack;
|
|
66
|
+
let storagePath: string;
|
|
67
|
+
|
|
68
|
+
const tenantId = testTenantId(1);
|
|
69
|
+
const user = createTestUser({ id: 1, tenantId, roles: ["Admin"] });
|
|
70
|
+
|
|
71
|
+
beforeAll(async () => {
|
|
72
|
+
storagePath = await mkdtemp(join(tmpdir(), "kumiko-file-field-pipeline-"));
|
|
73
|
+
stack = await setupTestStack({
|
|
74
|
+
features: [documentFeature],
|
|
75
|
+
files: { storageProvider: createLocalProvider(storagePath) },
|
|
76
|
+
});
|
|
77
|
+
await createEntityTable(stack.db, documentEntity);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
afterAll(async () => {
|
|
81
|
+
await stack.cleanup();
|
|
82
|
+
await rm(storagePath, { recursive: true, force: true });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
beforeEach(async () => {
|
|
86
|
+
await stack.db.execute(sql`TRUNCATE pipeline_documents`);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
async function uploadFile(fileName: string, body: Uint8Array, mimeType: string): Promise<string> {
|
|
90
|
+
const token = await stack.jwt.sign(user);
|
|
91
|
+
const fd = new FormData();
|
|
92
|
+
fd.append("file", new File([Buffer.from(body)], fileName, { type: mimeType }));
|
|
93
|
+
const res = await stack.app.request("/api/files", {
|
|
94
|
+
method: "POST",
|
|
95
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
96
|
+
body: fd,
|
|
97
|
+
});
|
|
98
|
+
// File-routes return 201 Created on successful upload.
|
|
99
|
+
expect(res.status).toBe(201);
|
|
100
|
+
const json = (await res.json()) as { id: string };
|
|
101
|
+
return json.id;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
describe("file/image field through the CRUD pipeline", () => {
|
|
105
|
+
test("create entity with file-field UUID → detail round-trips the UUID", async () => {
|
|
106
|
+
const fileId = await uploadFile("doc.pdf", new Uint8Array([1, 2, 3]), "application/pdf");
|
|
107
|
+
const imageId = await uploadFile("cover.png", new Uint8Array([4, 5, 6]), "image/png");
|
|
108
|
+
|
|
109
|
+
// Create through the standard write pipeline — this is the path where the
|
|
110
|
+
// pre-fix validation (z.number() for file/image) would have rejected the
|
|
111
|
+
// UUID with a zod error. If we get past this, both schema + column agree.
|
|
112
|
+
const created = await stack.http.writeOk<{ id: string }>(
|
|
113
|
+
"pipeline-documents:write:document:create",
|
|
114
|
+
{ title: "Annual report", attachment: fileId, cover: imageId },
|
|
115
|
+
user,
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const detail = await stack.http.queryOk<{
|
|
119
|
+
id: string;
|
|
120
|
+
title: string;
|
|
121
|
+
attachment: string;
|
|
122
|
+
cover: string;
|
|
123
|
+
}>("pipeline-documents:query:document:detail", { id: created.id }, user);
|
|
124
|
+
|
|
125
|
+
expect(detail).toMatchObject({
|
|
126
|
+
id: created.id,
|
|
127
|
+
title: "Annual report",
|
|
128
|
+
attachment: fileId,
|
|
129
|
+
cover: imageId,
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("update entity swaps file-field UUIDs cleanly", async () => {
|
|
134
|
+
const oldFile = await uploadFile("v1.pdf", new Uint8Array([1]), "application/pdf");
|
|
135
|
+
const newFile = await uploadFile("v2.pdf", new Uint8Array([2]), "application/pdf");
|
|
136
|
+
|
|
137
|
+
const created = await stack.http.writeOk<{ id: string }>(
|
|
138
|
+
"pipeline-documents:write:document:create",
|
|
139
|
+
{ title: "Swap target", attachment: oldFile },
|
|
140
|
+
user,
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
await stack.http.writeOk(
|
|
144
|
+
"pipeline-documents:write:document:update",
|
|
145
|
+
{ id: created.id, version: 1, changes: { attachment: newFile } },
|
|
146
|
+
user,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const detail = await stack.http.queryOk<{ attachment: string }>(
|
|
150
|
+
"pipeline-documents:query:document:detail",
|
|
151
|
+
{ id: created.id },
|
|
152
|
+
user,
|
|
153
|
+
);
|
|
154
|
+
expect(detail.attachment).toBe(newFile);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("invalid UUID rejected by validation (code=validation_error, not some other failure)", async () => {
|
|
158
|
+
// With the pre-fix z.number() this would have returned "expected number".
|
|
159
|
+
// With z.uuid() we get a proper uuid-format error. Assertion has to pin
|
|
160
|
+
// the specific failure-class — otherwise a DB error, access-denied, or
|
|
161
|
+
// any other throw would silently satisfy `toBeDefined()` and we'd miss
|
|
162
|
+
// a regression where the validation layer stopped firing at all.
|
|
163
|
+
const err = await stack.http.writeErr(
|
|
164
|
+
"pipeline-documents:write:document:create",
|
|
165
|
+
{ title: "Invalid", attachment: "not-a-uuid" },
|
|
166
|
+
user,
|
|
167
|
+
);
|
|
168
|
+
expect(err.code).toBe("validation_error");
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("plural files/images fields accept arrays of UUIDs end-to-end", async () => {
|
|
172
|
+
// Plural variants have NO entity-column (table-builder returns {} for
|
|
173
|
+
// files/images) — the array of UUIDs lives in the event payload. The
|
|
174
|
+
// pipeline still has to validate + accept it. Pre-fix this was
|
|
175
|
+
// z.array(z.number()) which would have rejected every UUID array.
|
|
176
|
+
const a = await uploadFile("a.jpg", new Uint8Array([1]), "image/jpeg");
|
|
177
|
+
const b = await uploadFile("b.jpg", new Uint8Array([2]), "image/jpeg");
|
|
178
|
+
const c = await uploadFile("notes.pdf", new Uint8Array([3]), "application/pdf");
|
|
179
|
+
|
|
180
|
+
const created = await stack.http.writeOk<{ id: string }>(
|
|
181
|
+
"pipeline-documents:write:document:create",
|
|
182
|
+
{ title: "With arrays", photos: [a, b], docs: [c] },
|
|
183
|
+
user,
|
|
184
|
+
);
|
|
185
|
+
expect(created.id).toBeTruthy();
|
|
186
|
+
|
|
187
|
+
// Follow-up update: swap one photo out, add a second doc. Proves the
|
|
188
|
+
// update-path handles plural arrays too, not just create.
|
|
189
|
+
const d = await uploadFile("c.jpg", new Uint8Array([4]), "image/jpeg");
|
|
190
|
+
const e = await uploadFile("more.pdf", new Uint8Array([5]), "application/pdf");
|
|
191
|
+
|
|
192
|
+
const updated = await stack.http.writeOk<{ id: string }>(
|
|
193
|
+
"pipeline-documents:write:document:update",
|
|
194
|
+
{ id: created.id, version: 1, changes: { photos: [a, d], docs: [c, e] } },
|
|
195
|
+
user,
|
|
196
|
+
);
|
|
197
|
+
// Version bumped by the CRUD executor → proves the write actually
|
|
198
|
+
// committed an event (not a silent no-op from validation-strip).
|
|
199
|
+
expect(updated.id).toBe(created.id);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("plural files field rejects non-UUID element (schema validates EACH array element)", async () => {
|
|
203
|
+
const valid = await uploadFile("ok.pdf", new Uint8Array([1]), "application/pdf");
|
|
204
|
+
const err = await stack.http.writeErr(
|
|
205
|
+
"pipeline-documents:write:document:create",
|
|
206
|
+
{ title: "Bad array", docs: [valid, "not-a-uuid"] },
|
|
207
|
+
user,
|
|
208
|
+
);
|
|
209
|
+
expect(err.code).toBe("validation_error");
|
|
210
|
+
});
|
|
211
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { createFileContext, createFileHandle, deriveKey } from "../file-handle";
|
|
3
|
+
import { createInMemoryFileProvider } from "../in-memory-provider";
|
|
4
|
+
|
|
5
|
+
describe("deriveKey", () => {
|
|
6
|
+
test("inserts suffix before extension", () => {
|
|
7
|
+
expect(deriveKey("foo/bar.jpg", "medium")).toBe("foo/bar.medium.jpg");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("handles keys without a slash", () => {
|
|
11
|
+
expect(deriveKey("bar.png", "thumb")).toBe("bar.thumb.png");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("appends to keys without an extension", () => {
|
|
15
|
+
expect(deriveKey("foo/bar", "small")).toBe("foo/bar.small");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("only splits on the last segment — earlier dots stay", () => {
|
|
19
|
+
expect(deriveKey("archive.v2/foo.jpg", "medium")).toBe("archive.v2/foo.medium.jpg");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("handles multi-dot filenames — splits on the final extension", () => {
|
|
23
|
+
expect(deriveKey("tenant/my.photo.jpg", "thumb")).toBe("tenant/my.photo.thumb.jpg");
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("FileHandle", () => {
|
|
28
|
+
test("read/write round-trip through the provider", async () => {
|
|
29
|
+
const provider = createInMemoryFileProvider();
|
|
30
|
+
const handle = createFileHandle("tenant/x.jpg", provider);
|
|
31
|
+
|
|
32
|
+
const payload = new Uint8Array([1, 2, 3, 4]);
|
|
33
|
+
await handle.write(payload, "image/jpeg");
|
|
34
|
+
|
|
35
|
+
const read = await handle.read();
|
|
36
|
+
expect(Array.from(read)).toEqual([1, 2, 3, 4]);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("exists reflects write/delete state", async () => {
|
|
40
|
+
const provider = createInMemoryFileProvider();
|
|
41
|
+
const handle = createFileHandle("tenant/x.jpg", provider);
|
|
42
|
+
|
|
43
|
+
expect(await handle.exists()).toBe(false);
|
|
44
|
+
await handle.write(new Uint8Array([9]));
|
|
45
|
+
expect(await handle.exists()).toBe(true);
|
|
46
|
+
await handle.delete();
|
|
47
|
+
expect(await handle.exists()).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("derive produces an independent handle at the derived key", async () => {
|
|
51
|
+
const provider = createInMemoryFileProvider();
|
|
52
|
+
const original = createFileHandle("tenant/photo.jpg", provider);
|
|
53
|
+
const thumb = original.derive("thumb");
|
|
54
|
+
|
|
55
|
+
expect(thumb.key).toBe("tenant/photo.thumb.jpg");
|
|
56
|
+
|
|
57
|
+
await original.write(new Uint8Array([1, 2]));
|
|
58
|
+
await thumb.write(new Uint8Array([9, 9, 9]));
|
|
59
|
+
|
|
60
|
+
expect(Array.from(await original.read())).toEqual([1, 2]);
|
|
61
|
+
expect(Array.from(await thumb.read())).toEqual([9, 9, 9]);
|
|
62
|
+
// Deleting the derived handle must not touch the original.
|
|
63
|
+
await thumb.delete();
|
|
64
|
+
expect(await original.exists()).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("writes copy the buffer — caller mutations don't corrupt stored data", async () => {
|
|
68
|
+
const provider = createInMemoryFileProvider();
|
|
69
|
+
const handle = createFileHandle("tenant/x.bin", provider);
|
|
70
|
+
|
|
71
|
+
const buf = new Uint8Array([1, 2, 3]);
|
|
72
|
+
await handle.write(buf);
|
|
73
|
+
buf[0] = 99;
|
|
74
|
+
|
|
75
|
+
const read = await handle.read();
|
|
76
|
+
expect(Array.from(read)).toEqual([1, 2, 3]);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe("createFileContext", () => {
|
|
81
|
+
test("ref returns a handle bound to the given key", async () => {
|
|
82
|
+
const provider = createInMemoryFileProvider();
|
|
83
|
+
const files = createFileContext(provider);
|
|
84
|
+
|
|
85
|
+
const h = files.ref("tenant/foo.pdf");
|
|
86
|
+
expect(h.key).toBe("tenant/foo.pdf");
|
|
87
|
+
await h.write(new Uint8Array([7]));
|
|
88
|
+
|
|
89
|
+
// Same key, new ref — should see the same stored bytes.
|
|
90
|
+
const h2 = files.ref("tenant/foo.pdf");
|
|
91
|
+
expect(Array.from(await h2.read())).toEqual([7]);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("InMemoryFileProvider", () => {
|
|
96
|
+
test("keys() lists every stored key", async () => {
|
|
97
|
+
const provider = createInMemoryFileProvider();
|
|
98
|
+
await provider.write("a/x.jpg", new Uint8Array([1]));
|
|
99
|
+
await provider.write("b/y.png", new Uint8Array([2]));
|
|
100
|
+
expect([...provider.keys()].sort()).toEqual(["a/x.jpg", "b/y.png"]);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("read on a missing key throws with the key in the message", async () => {
|
|
104
|
+
const provider = createInMemoryFileProvider();
|
|
105
|
+
await expect(provider.read("nope.jpg")).rejects.toThrow(/nope\.jpg/);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("clear() empties the store", async () => {
|
|
109
|
+
const provider = createInMemoryFileProvider();
|
|
110
|
+
await provider.write("a.jpg", new Uint8Array([1]));
|
|
111
|
+
provider.clear();
|
|
112
|
+
expect(provider.keys()).toEqual([]);
|
|
113
|
+
expect(await provider.exists("a.jpg")).toBe(false);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("overwrite replaces bytes in place", async () => {
|
|
117
|
+
const provider = createInMemoryFileProvider();
|
|
118
|
+
await provider.write("a.jpg", new Uint8Array([1, 2]));
|
|
119
|
+
await provider.write("a.jpg", new Uint8Array([9, 9, 9]));
|
|
120
|
+
expect(Array.from(await provider.read("a.jpg"))).toEqual([9, 9, 9]);
|
|
121
|
+
});
|
|
122
|
+
});
|