@underpostnet/cyberia 3.3.73 → 3.3.77
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/.env.example +17 -5
- package/.github/workflows/coverall.cyberia.ci.yml +10 -0
- package/.github/workflows/docker-image.cyberia-client.ci.yml +1 -1
- package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +1 -1
- package/.github/workflows/docker-image.cyberia-server.ci.yml +1 -1
- package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +1 -1
- package/.github/workflows/docker-image.engine-cyberia.ci.yml +1 -1
- package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +1 -1
- package/.github/workflows/engine-cyberia.ci.yml +1 -1
- package/.github/workflows/publish.cyberia.ci.yml +44 -0
- package/AGENTS.md +61 -12
- package/CHANGELOG.md +159 -161
- package/CLI-HELP.md +20 -13
- package/Dockerfile +1 -1
- package/Dockerfile.dev +1 -1
- package/Dockerfile.test +2 -2
- package/README.md +28 -28
- package/bin/build.js +96 -21
- package/bin/cyberia.js +1247 -1373
- package/bin/index.js +1247 -1373
- package/compose.env +17 -5
- package/conf.js +7 -4
- package/deploy/cyberia-client/deploy.sh +10 -11
- package/deploy/cyberia-client/package.sh +0 -1
- package/deploy/cyberia-client/state.sh +3 -2
- package/deploy/cyberia-server/deploy.sh +10 -11
- package/deploy/cyberia-server/package.sh +0 -1
- package/deploy/cyberia-server/state.sh +3 -2
- package/deploy/dd-cyberia/deploy.sh +5 -5
- package/deploy/dd-cyberia/init.sh +15 -14
- package/deploy/dd-cyberia/package.sh +0 -1
- package/deploy/dd-cyberia/state.sh +2 -2
- package/deploy/dd-cyberia/sync-deploy.sh +139 -75
- package/deploy/lib/config.sh +29 -0
- package/deploy/lib/github-actions-logging.sh +92 -76
- package/deploy/lib/host.sh +28 -19
- package/deploy/lib/state.sh +4 -1
- package/deploy/pwa-microservices-template/deploy.sh +10 -8
- package/deploy/release/deploy.sh +0 -2
- package/deployment.yaml +1 -1
- package/docker-compose.yml +23 -17
- package/docs/coverage/cyberia/api/cyberia-instance/cyberia-fallback-capture.js.html +1591 -0
- package/docs/coverage/cyberia/api/cyberia-instance/index.html +116 -0
- package/docs/coverage/cyberia/api/cyberia-server-defaults/cyberia-server-defaults.js.html +4138 -0
- package/docs/coverage/cyberia/api/cyberia-server-defaults/index.html +116 -0
- package/docs/coverage/cyberia/api/object-layer/index.html +116 -0
- package/docs/coverage/cyberia/api/object-layer/object-layer.model.js.html +1393 -0
- package/docs/coverage/cyberia/base.css +362 -0
- package/docs/coverage/cyberia/block-navigation.js +82 -0
- package/docs/coverage/cyberia/favicon.png +0 -0
- package/docs/coverage/cyberia/index.html +161 -0
- package/docs/coverage/cyberia/prettify.css +101 -0
- package/docs/coverage/cyberia/prettify.js +937 -0
- package/docs/coverage/cyberia/projects/cyberia/atlas-sprite-sheet-generator.js.html +1315 -0
- package/docs/coverage/cyberia/projects/cyberia/index.html +176 -0
- package/docs/coverage/cyberia/projects/cyberia/instance-backup.js.html +715 -0
- package/docs/coverage/cyberia/projects/cyberia/instance-data.js.html +2182 -0
- package/docs/coverage/cyberia/projects/cyberia/shape-generator.js.html +3265 -0
- package/docs/coverage/cyberia/projects/cyberia/stat-balance.js.html +328 -0
- package/docs/coverage/cyberia/sort-arrow-sprite.png +0 -0
- package/docs/coverage/cyberia/sorter.js +205 -0
- package/hardhat/package-lock.json +2 -2
- package/hardhat/package.json +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +1 -1
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +1 -1
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/mongo-express/deployment.yaml +46 -27
- package/manifests/deployment/mongo-express/kustomization.yaml +9 -0
- package/manifests/deployment/mongo-express/mongo-express-nodeport.yaml +18 -0
- package/manifests/deployment/mongo-express/service.yaml +16 -0
- package/manifests/deployment/mongo-express-no-auth/disable-mongodb-auth.yaml +16 -0
- package/manifests/deployment/mongo-express-no-auth/kustomization.yaml +16 -0
- package/package.json +19 -17
- package/scripts/test-monitor.sh +3 -3
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +14 -9
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +36 -5
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +5 -0
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +88 -184
- package/src/api/cyberia-action/cyberia-action.router.js +2 -2
- package/src/api/cyberia-action/cyberia-action.service.js +3 -4
- package/src/api/cyberia-audio/cyberia-audio.controller.js +6 -0
- package/src/api/cyberia-audio/cyberia-audio.model.js +61 -0
- package/src/api/cyberia-audio/cyberia-audio.router.js +21 -0
- package/src/api/cyberia-audio/cyberia-audio.service.js +220 -0
- package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +1 -3
- package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +10 -19
- package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +12 -34
- package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +15 -32
- package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +1 -1
- package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +1 -1
- package/src/api/cyberia-entity/cyberia-entity.model.js +2 -0
- package/src/api/cyberia-entity/cyberia-entity.service.js +1 -1
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +8 -2
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.model.js +31 -20
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +16 -1
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +466 -3
- package/src/api/cyberia-instance/cyberia-fallback-capture.js +87 -51
- package/src/api/cyberia-instance/cyberia-fallback-world.js +21 -40
- package/src/api/cyberia-instance/cyberia-instance-boot.service.js +5 -5
- package/src/api/cyberia-instance/cyberia-instance-items.js +98 -0
- package/src/api/cyberia-instance/cyberia-instance-map.service.js +34 -141
- package/src/api/cyberia-instance/cyberia-instance.controller.js +1 -4
- package/src/api/cyberia-instance/cyberia-instance.model.js +0 -14
- package/src/api/cyberia-instance/cyberia-instance.router.js +3 -16
- package/src/api/cyberia-instance/cyberia-instance.service.js +3 -43
- package/src/api/cyberia-instance/cyberia-world-generator.js +8 -13
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +49 -75
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +74 -1
- package/src/api/cyberia-map/cyberia-map.service.js +6 -2
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.controller.js +31 -0
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.model.js +56 -0
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.router.js +33 -0
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +189 -0
- package/src/api/cyberia-quest/cyberia-quest.router.js +3 -3
- package/src/api/cyberia-quest/cyberia-quest.service.js +3 -3
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.model.js +1 -1
- package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +403 -188
- package/src/api/cyberia-skill/cyberia-skill.model.js +6 -6
- package/src/api/file/file.ref.js +153 -0
- package/src/api/file/file.ref.json +11 -2
- package/src/api/object-layer/object-layer.model.js +47 -12
- package/src/api/object-layer/object-layer.router.js +10 -3
- package/src/api/object-layer/object-layer.service.js +113 -120
- package/src/cli/app.js +12 -1
- package/src/cli/client.js +2 -2
- package/src/cli/cluster.js +80 -18
- package/src/cli/deploy.js +215 -34
- package/src/cli/docker-compose.js +1 -1
- package/src/cli/fs-selection.js +115 -0
- package/src/cli/fs.js +158 -342
- package/src/cli/index.js +22 -10
- package/src/cli/release.js +3 -1
- package/src/cli/repository.js +35 -9
- package/src/cli/run.js +80 -199
- package/src/cli/test.js +4 -0
- package/src/cli/wireguard.js +32 -31
- package/src/client/components/core/Badge.js +0 -1
- package/src/client/components/core/CssCore.js +8 -1
- package/src/client/components/core/Docs.js +77 -100
- package/src/client/components/core/Modal.js +77 -23
- package/src/client/components/core/ToolTip.js +15 -0
- package/src/client/components/cryptokoyn/AppShellCryptokoyn.js +0 -2
- package/src/client/components/cyberia/ActionEngineCyberia.js +12 -36
- package/src/client/components/cyberia/EntityEngineCyberia.js +492 -121
- package/src/client/components/cyberia/InstanceEngineCyberia.js +129 -160
- package/src/client/components/cyberia/InstanceSelectionView.js +7 -1
- package/src/client/components/cyberia/MapEngineCyberia.js +175 -69
- package/src/client/components/cyberia/ObjectLayerEngine.js +1011 -97
- package/src/client/components/cyberia/ObjectLayerEngineModal.js +81 -155
- package/src/client/components/cyberia/ObjectLayerEngineViewer.js +113 -98
- package/src/client/components/cyberia/SharedDefaultsCyberia.js +215 -56
- package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +0 -34
- package/src/client/components/cyberia-portal/CssCyberiaPortal.js +1 -1
- package/src/client/components/cyberia-portal/MainBodyCyberiaPortal.js +712 -76
- package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +0 -4
- package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +0 -4
- package/src/client/components/default/AppShellDefault.js +1 -4
- package/src/client/components/itemledger/AppShellItemledger.js +0 -2
- package/src/client/components/underpost/AppShellUnderpost.js +1 -1
- package/src/client/public/cyberia-docs/ARCHITECTURE.md +15 -15
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +280 -24
- package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +51 -29
- package/src/client/public/cyberia-docs/CYBERIA-SAGA.md +52 -53
- package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +16 -11
- package/src/client/public/cyberia-docs/CYBERIA.md +28 -28
- package/src/client/public/cyberia-docs/ENTITY-PROFILE.md +11 -5
- package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
- package/src/client/public/cyberia-docs/STATS-PROGRESSION.md +217 -0
- package/src/client/public/cyberia-docs/WHITE-PAPER.md +20 -12
- package/src/client/services/cyberia-audio/cyberia-audio.service.js +99 -0
- package/src/client/services/cyberia-entity-type-default/cyberia-entity-type-default.service.js +38 -0
- package/src/client/services/cyberia-instance/cyberia-instance.management.js +5 -4
- package/src/client/services/cyberia-instance/cyberia-instance.service.js +0 -40
- package/src/client/services/cyberia-map/cyberia-map.management.js +5 -4
- package/src/client/services/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +138 -0
- package/src/client/services/object-layer/object-layer.management.js +6 -10
- package/src/client/services/object-layer/object-layer.service.js +3 -1
- package/src/client/ssr/views/Cyberia404.js +3 -3
- package/src/client-builder/client-build-docs.js +52 -52
- package/src/client-builder/client-build.js +67 -44
- package/src/client-builder/client-bundle.js +347 -0
- package/src/db/mongo/MongoExpress.js +187 -0
- package/src/grpc/cyberia/grpc-server.js +5 -7
- package/src/index.js +1 -1
- package/src/projects/cyberia/atlas-sprite-sheet-generator.js +143 -146
- package/src/projects/cyberia/atlas-sprite-sheet-store.js +391 -0
- package/src/projects/cyberia/besu-genesis-generator.js +5 -14
- package/src/projects/cyberia/boot-contract-fixtures.js +32 -0
- package/src/projects/cyberia/catalog-cyberia.js +3 -2
- package/src/projects/cyberia/gemini-client.js +3 -3
- package/src/projects/cyberia/generate-saga.js +4 -11
- package/src/projects/cyberia/hot-reload-trigger.js +17 -28
- package/src/projects/cyberia/instance-backup.js +210 -0
- package/src/projects/cyberia/instance-data.js +177 -257
- package/src/projects/cyberia/map-preview-generator.js +32 -68
- package/src/projects/cyberia/object-layer.js +152 -319
- package/src/projects/cyberia/seed-audio.js +139 -0
- package/src/projects/cyberia/semantic-layer-generator-skin.js +2 -8
- package/src/projects/cyberia/semantic-layer-generator.js +2 -18
- package/src/projects/cyberia/stat-balance.js +81 -0
- package/src/projects/cyberia/stat-commands.js +23 -0
- package/src/projects/cyberia/stat-contract-generator.js +101 -0
- package/src/runtime/cyberia-client/Dockerfile +8 -4
- package/src/runtime/cyberia-client/Dockerfile.dev +5 -2
- package/src/runtime/cyberia-server/Dockerfile +9 -2
- package/src/runtime/cyberia-server/Dockerfile.dev +8 -1
- package/src/runtime/engine-cyberia/Dockerfile +1 -1
- package/src/runtime/engine-cyberia/Dockerfile.dev +1 -1
- package/src/runtime/engine-cyberia/Dockerfile.test +2 -2
- package/src/runtime/engine-cyberia/compose.env +17 -5
- package/src/runtime/engine-cyberia/docker-compose.yml +23 -17
- package/src/server/build/coverage.js +113 -42
- package/src/server/build/package.js +55 -12
- package/src/server/build/testing.js +59 -9
- package/src/server/network/middlewares.js +8 -2
- package/src/server/network/underpost-gateway.js +113 -14
- package/src/server/ops/logger.js +70 -35
- package/src/server/runtime/conf.js +40 -9
- package/src/server/runtime/start.js +11 -2
- package/src/server/storage/data-query.js +16 -0
- package/src/server/storage/downloader.js +62 -14
- package/src/server/storage/zip.js +153 -0
- package/test/integration/app/cyberia/atlas-sprite-sheet-store.test.js +402 -0
- package/test/integration/app/cyberia/atlas-sprite-sheet.test.js +86 -0
- package/test/integration/app/cyberia/cyberia-cli-plain-reads.test.js +34 -0
- package/test/integration/app/cyberia/cyberia-entity-type-default.test.js +842 -0
- package/test/integration/app/cyberia/cyberia-instance-conf-coerce.test.js +53 -0
- package/test/integration/app/cyberia/cyberia-instance-conf-defaults.test.js +16 -22
- package/test/integration/app/cyberia/cyberia-instance-items.test.js +90 -0
- package/test/integration/app/cyberia/cyberia-load.test.js +6 -7
- package/test/integration/app/cyberia/cyberia-map-audio-conf.test.js +256 -0
- package/test/integration/app/cyberia/cyberia-stats.test.js +150 -0
- package/test/integration/app/cyberia/fallback-world-capture.test.js +87 -2
- package/test/integration/app/cyberia/object-layer-item-selection.test.js +56 -0
- package/test/integration/app/cyberia/object-layer-natural-key.test.js +32 -0
- package/test/integration/app/cyberia/seed-audio.test.js +275 -0
- package/test/integration/infra/2-network/wireguard-cli.test.js +33 -9
- package/test/integration/infra/2-network/wireguard-edge.test.js +58 -18
- package/test/integration/infra/3-cluster/mongo-express-deploy.test.js +156 -0
- package/test/integration/infra/4-ingress/api-cross-origin.test.js +79 -0
- package/test/integration/infra/4-ingress/underpost-ingress.test.js +1 -1
- package/test/unit/catalog.test.js +13 -2
- package/test/unit/client-build-docs.test.js +97 -70
- package/test/unit/client-bundle.test.js +313 -0
- package/test/unit/conf-resolution.test.js +58 -7
- package/test/unit/coverage-artifact.test.js +120 -29
- package/test/unit/cyberia/instance-backup.test.js +183 -0
- package/test/unit/cyberia/instance-data.test.js +314 -0
- package/test/unit/cyberia/instance-object-layer-items.test.js +46 -0
- package/test/unit/cyberia/publish-workflow.test.js +22 -0
- package/test/unit/cyberia/stat-balance.test.js +69 -0
- package/test/unit/deploy-legacy-gateway-sweep.test.js +63 -0
- package/test/unit/deploy-log-table.test.js +76 -5
- package/test/unit/downloader-integrity.test.js +98 -0
- package/test/unit/file-reference-registry.test.js +141 -0
- package/test/unit/fs-storage-paths.test.js +596 -0
- package/test/unit/gateway-pod-fetch.test.js +95 -0
- package/test/unit/letsencrypt-issuer.test.js +101 -0
- package/test/unit/logger-redaction.test.js +29 -0
- package/test/unit/package.test.js +70 -16
- package/test/unit/prepare-host.test.js +84 -1
- package/test/unit/release-bump.test.js +3 -1
- package/test/unit/start-options.test.js +12 -0
- package/test/unit/test-tiers.test.js +27 -0
- package/test/unit/zip-archive.test.js +142 -0
- package/vitest.config.js +11 -3
- package/src/api/cyberia-instance/cyberia-fallback-default-items.js +0 -63
- package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +0 -368
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
resolveHostKeyContext,
|
|
36
36
|
resolveReplicaCount,
|
|
37
37
|
syncDeployIdSources,
|
|
38
|
+
syncDeployIdSourcesBack,
|
|
38
39
|
syncPrivateConf,
|
|
39
40
|
updatePrivateEngineTestRepo,
|
|
40
41
|
updatePrivateTemplateRepo,
|
|
@@ -712,6 +713,23 @@ describe('private conf sync', () => {
|
|
|
712
713
|
expect(copied).to.include('./engine-private/catalog/items.json -> ../engine-core-private/catalog/items.json');
|
|
713
714
|
});
|
|
714
715
|
|
|
716
|
+
// Regression: an instance backup that dropped an item left the item's stale atlas in the
|
|
717
|
+
// mirror, and the pod restored it over the current one from another instance.
|
|
718
|
+
it('replaces each payload entry in the checkout instead of merging into it', () => {
|
|
719
|
+
const calls = [];
|
|
720
|
+
checkout({ present: true });
|
|
721
|
+
vi.spyOn(fs, 'readdirSync').mockReturnValue([]);
|
|
722
|
+
vi.spyOn(fs, 'removeSync').mockImplementation((target) => calls.push(`remove ${target}`));
|
|
723
|
+
vi.spyOn(fs, 'copySync').mockImplementation((src, dest) => calls.push(`copy ${src} -> ${dest}`));
|
|
724
|
+
withEnv({ GITHUB_USERNAME: 'fixture-org' }, () => syncPrivateConf('dd-core', ['cyberia-instances/TEST']));
|
|
725
|
+
const remove = calls.indexOf('remove ../engine-core-private/cyberia-instances/TEST');
|
|
726
|
+
const copy = calls.indexOf(
|
|
727
|
+
'copy ./engine-private/cyberia-instances/TEST -> ../engine-core-private/cyberia-instances/TEST',
|
|
728
|
+
);
|
|
729
|
+
expect(remove).to.be.greaterThan(-1);
|
|
730
|
+
expect(copy).to.be.greaterThan(remove);
|
|
731
|
+
});
|
|
732
|
+
|
|
715
733
|
it('mirrors the cron conf the checkout declares', () => {
|
|
716
734
|
const copied = [];
|
|
717
735
|
checkout({ present: true, cronId: 'dd-cron' });
|
|
@@ -740,17 +758,50 @@ describe('private conf sync', () => {
|
|
|
740
758
|
expect(syncDeployIdSources([])).to.equal(false);
|
|
741
759
|
});
|
|
742
760
|
|
|
743
|
-
it('
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
761
|
+
it('copies in only the sources the engine tree lacks, and never moves', () => {
|
|
762
|
+
// Regression: the repo copy used to be moved over the engine copy, so a build discarded
|
|
763
|
+
// every edit made in the engine tree and left the repo reading as wholesale deletions.
|
|
764
|
+
const calls = [];
|
|
765
|
+
const present = new Set(['../engine-x/src/new.js', '../engine-x/src/edited.js', 'src/api/edited.js']);
|
|
766
|
+
vi.spyOn(fs, 'mkdirSync').mockImplementation(() => {});
|
|
767
|
+
vi.spyOn(fs, 'existsSync').mockImplementation((filePath) => present.has(`${filePath}`));
|
|
768
|
+
vi.spyOn(fs, 'removeSync').mockImplementation((target) => calls.push(`remove ${target}`));
|
|
769
|
+
vi.spyOn(fs, 'copySync').mockImplementation((src, dest) => calls.push(`copy ${src} -> ${dest}`));
|
|
770
|
+
vi.spyOn(fs, 'moveSync').mockImplementation(() => calls.push('move'));
|
|
747
771
|
expect(
|
|
748
772
|
syncDeployIdSources([
|
|
749
|
-
['src/
|
|
750
|
-
['src/
|
|
773
|
+
['../engine-x/src/new.js', 'src/api/new.js'],
|
|
774
|
+
['../engine-x/src/edited.js', 'src/api/edited.js'],
|
|
775
|
+
['../engine-x/src/absent.js', 'src/api/absent.js'],
|
|
751
776
|
]),
|
|
752
777
|
).to.equal(true);
|
|
753
|
-
expect(
|
|
778
|
+
expect(calls).to.deep.equal([
|
|
779
|
+
// The engine copy wins: it is mirrored out before anything is copied in.
|
|
780
|
+
'remove ../engine-x/src/edited.js',
|
|
781
|
+
'copy src/api/edited.js -> ../engine-x/src/edited.js',
|
|
782
|
+
'copy ../engine-x/src/new.js -> src/api/new.js',
|
|
783
|
+
]);
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
it('mirrors every source the tree holds back to its repo, replacing it whole', () => {
|
|
787
|
+
// The engine ignores the moved-in paths, so the source repo is the only place a change to
|
|
788
|
+
// them is diffed; replacing rather than overlaying makes deletions reach that diff too.
|
|
789
|
+
const calls = [];
|
|
790
|
+
vi.spyOn(fs, 'existsSync').mockImplementation((filePath) => `${filePath}`.includes('present'));
|
|
791
|
+
vi.spyOn(fs, 'removeSync').mockImplementation((target) => calls.push(`remove ${target}`));
|
|
792
|
+
vi.spyOn(fs, 'copySync').mockImplementation((src, dest) => calls.push(`copy ${src} -> ${dest}`));
|
|
793
|
+
vi.spyOn(fs, 'moveSync').mockImplementation(() => calls.push('move'));
|
|
794
|
+
expect(
|
|
795
|
+
syncDeployIdSourcesBack([
|
|
796
|
+
['../engine-x/src/present.js', 'src/api/present.js'],
|
|
797
|
+
['../engine-x/src/absent.js', 'src/api/absent.js'],
|
|
798
|
+
]),
|
|
799
|
+
).to.deep.equal(['../engine-x/src/present.js']);
|
|
800
|
+
expect(calls).to.deep.equal([
|
|
801
|
+
'remove ../engine-x/src/present.js',
|
|
802
|
+
'copy src/api/present.js -> ../engine-x/src/present.js',
|
|
803
|
+
]);
|
|
804
|
+
expect(syncDeployIdSourcesBack()).to.deep.equal([]);
|
|
754
805
|
});
|
|
755
806
|
});
|
|
756
807
|
|
|
@@ -13,95 +13,186 @@ import { expect } from 'chai';
|
|
|
13
13
|
import fs from 'fs-extra';
|
|
14
14
|
import {
|
|
15
15
|
COVERAGE_BUNDLE_DIRECTORY,
|
|
16
|
-
|
|
16
|
+
bundleCoverageReports,
|
|
17
17
|
coverageReportCandidates,
|
|
18
|
+
coverageReportCommand,
|
|
19
|
+
coverageReportsFactory,
|
|
18
20
|
coverageUnavailablePage,
|
|
21
|
+
deployCoverageReports,
|
|
19
22
|
resolveCoverageReportPath,
|
|
20
23
|
} from '../../src/server/build/coverage.js';
|
|
21
24
|
import { EXECUTION_PROFILE_ENV_KEY } from '../../src/server/build/execution.js';
|
|
22
25
|
import { isTestRuntime, runtimeStatusWritable } from '../../src/server/runtime/runtime-status.js';
|
|
23
26
|
|
|
27
|
+
describe('coverage report declaration', () => {
|
|
28
|
+
it('reads each declared report with its label defaulting to the id', () => {
|
|
29
|
+
expect(coverageReportsFactory({ coverage: [{ id: 'engine', suite: 'unit' }] })).to.deep.equal([
|
|
30
|
+
{ id: 'engine', label: 'engine', suite: 'unit', path: undefined },
|
|
31
|
+
]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('declares nothing for a deploy without coverage', () => {
|
|
35
|
+
expect(coverageReportsFactory({})).to.deep.equal([]);
|
|
36
|
+
expect(coverageReportsFactory(undefined)).to.deep.equal([]);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('rejects an entry without exactly one source, a non-slug id, or a repeated id', () => {
|
|
40
|
+
expect(() => coverageReportsFactory({ coverage: [{ id: 'x' }] })).to.throw("exactly one of 'suite' or 'path'");
|
|
41
|
+
expect(() => coverageReportsFactory({ coverage: [{ id: 'x', suite: 'unit', path: './' }] })).to.throw(
|
|
42
|
+
'exactly one',
|
|
43
|
+
);
|
|
44
|
+
expect(() => coverageReportsFactory({ coverage: [{ id: 'Bad Id', suite: 'unit' }] })).to.throw('slug');
|
|
45
|
+
expect(() =>
|
|
46
|
+
coverageReportsFactory({
|
|
47
|
+
coverage: [
|
|
48
|
+
{ id: 'x', suite: 'unit' },
|
|
49
|
+
{ id: 'x', suite: 'app' },
|
|
50
|
+
],
|
|
51
|
+
}),
|
|
52
|
+
).to.throw('declared twice');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("collects a deploy's reports across routes once each", () => {
|
|
56
|
+
const route = { docs: { coverage: [{ id: 'engine', suite: 'unit' }] } };
|
|
57
|
+
const confServer = { 'a.test': { '/': route, '/store': route }, 'b.test': { '/': {} } };
|
|
58
|
+
expect(deployCoverageReports(confServer).map(({ id }) => id)).to.deep.equal(['engine']);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('refuses one id declared with two sources', () => {
|
|
62
|
+
const confServer = {
|
|
63
|
+
'a.test': {
|
|
64
|
+
'/': { docs: { coverage: [{ id: 'engine', suite: 'unit' }] } },
|
|
65
|
+
'/store': { docs: { coverage: [{ id: 'engine', suite: 'app' }] } },
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
expect(() => deployCoverageReports(confServer)).to.throw('two different sources');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('names the command that produces each kind of report', () => {
|
|
72
|
+
expect(coverageReportCommand({ suite: 'unit,infra,app' })).to.equal('node bin test unit,infra,app');
|
|
73
|
+
expect(coverageReportCommand({ path: './hardhat/' })).to.equal('npm run coverage --prefix ./hardhat');
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
24
77
|
describe('coverage report resolution', () => {
|
|
25
78
|
let fixturePath;
|
|
79
|
+
let cwd;
|
|
26
80
|
|
|
81
|
+
// The bundled candidate is cwd-relative, and a published deploy artifact (the tree a
|
|
82
|
+
// downstream CI runs this suite in) carries real reports under it; resolve against the
|
|
83
|
+
// fixture, never the tree.
|
|
27
84
|
beforeEach(() => {
|
|
28
85
|
fixturePath = fs.mkdtempSync('/tmp/engine-coverage-artifact-');
|
|
86
|
+
cwd = process.cwd();
|
|
87
|
+
process.chdir(fixturePath);
|
|
29
88
|
});
|
|
30
89
|
|
|
31
90
|
afterEach(() => {
|
|
91
|
+
process.chdir(cwd);
|
|
32
92
|
fs.removeSync(fixturePath);
|
|
33
93
|
});
|
|
34
94
|
|
|
35
|
-
it(
|
|
36
|
-
expect(coverageReportCandidates('
|
|
95
|
+
it("looks for a suite's own run output first and the bundled artifact last", () => {
|
|
96
|
+
expect(coverageReportCandidates({ id: 'engine', suite: 'unit,infra,app' })).to.deep.equal([
|
|
97
|
+
'coverage/unit-infra-app',
|
|
98
|
+
`${COVERAGE_BUNDLE_DIRECTORY}/engine`,
|
|
99
|
+
]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("looks through an external tree's report layouts before the bundled artifact", () => {
|
|
103
|
+
expect(coverageReportCandidates({ id: 'hardhat', path: '/src/' })).to.deep.equal([
|
|
37
104
|
'/src/coverage/html',
|
|
38
105
|
'/src/coverage/lcov-report',
|
|
39
106
|
'/src/coverage',
|
|
40
|
-
|
|
107
|
+
`${COVERAGE_BUNDLE_DIRECTORY}/hardhat`,
|
|
41
108
|
]);
|
|
42
109
|
});
|
|
43
110
|
|
|
44
|
-
it('has nowhere to look without a source tree', () => {
|
|
45
|
-
expect(coverageReportCandidates(undefined)).to.deep.equal([]);
|
|
46
|
-
expect(resolveCoverageReportPath(undefined)).to.equal(undefined);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
111
|
it('ignores a coverage directory that carries no HTML index', () => {
|
|
50
112
|
fs.outputFileSync(`${fixturePath}/coverage/lcov.info`, 'TN:\n');
|
|
51
|
-
expect(resolveCoverageReportPath(fixturePath)).to.equal(undefined);
|
|
113
|
+
expect(resolveCoverageReportPath({ id: 'x', path: fixturePath })).to.equal(undefined);
|
|
52
114
|
});
|
|
53
115
|
|
|
54
|
-
it('resolves the
|
|
55
|
-
fs.outputFileSync(`${fixturePath}
|
|
56
|
-
|
|
116
|
+
it('resolves the report of the selection the deploy names, not the last run made', () => {
|
|
117
|
+
fs.outputFileSync(`${fixturePath}/coverage/unit-infra-app-cyberia/index.html`, '<!doctype html>');
|
|
118
|
+
fs.outputFileSync(`${fixturePath}/coverage/cyberia/index.html`, '<!doctype html>');
|
|
119
|
+
expect(resolveCoverageReportPath({ id: 'x', suite: 'cyberia' })).to.equal('coverage/cyberia');
|
|
120
|
+
expect(resolveCoverageReportPath({ id: 'x', suite: 'unit,infra,app' })).to.equal(undefined);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('falls back to the report a deploy artifact already carries', () => {
|
|
124
|
+
fs.outputFileSync(`${fixturePath}/${COVERAGE_BUNDLE_DIRECTORY}/hardhat/index.html`, '<!doctype html>');
|
|
125
|
+
expect(resolveCoverageReportPath({ id: 'hardhat', path: `${fixturePath}/hardhat` })).to.equal(
|
|
126
|
+
`${COVERAGE_BUNDLE_DIRECTORY}/hardhat`,
|
|
127
|
+
);
|
|
57
128
|
});
|
|
58
129
|
});
|
|
59
130
|
|
|
60
131
|
describe('coverage bundling into a deploy artifact', () => {
|
|
61
132
|
let fixturePath;
|
|
133
|
+
let cwd;
|
|
62
134
|
|
|
135
|
+
// The bundled candidate is cwd-relative, and a published deploy artifact (the tree a
|
|
136
|
+
// downstream CI runs this suite in) carries real reports under it; resolve against the
|
|
137
|
+
// fixture, never the tree.
|
|
63
138
|
beforeEach(() => {
|
|
64
139
|
fixturePath = fs.mkdtempSync('/tmp/engine-coverage-artifact-');
|
|
140
|
+
cwd = process.cwd();
|
|
141
|
+
process.chdir(fixturePath);
|
|
65
142
|
});
|
|
66
143
|
|
|
67
144
|
afterEach(() => {
|
|
145
|
+
process.chdir(cwd);
|
|
68
146
|
fs.removeSync(fixturePath);
|
|
69
147
|
});
|
|
70
148
|
|
|
71
|
-
it('carries
|
|
72
|
-
fs.outputFileSync(`${fixturePath}/
|
|
73
|
-
fs.outputFileSync(`${fixturePath}/
|
|
149
|
+
it('carries each report into the artifact under its own id', () => {
|
|
150
|
+
fs.outputFileSync(`${fixturePath}/hardhat/coverage/html/index.html`, '<!doctype html><title>report</title>');
|
|
151
|
+
fs.outputFileSync(`${fixturePath}/hardhat/coverage/html/base.css`, 'body {}');
|
|
74
152
|
|
|
75
|
-
const result =
|
|
153
|
+
const [result] = bundleCoverageReports(
|
|
154
|
+
[{ id: 'hardhat', path: `${fixturePath}/hardhat` }],
|
|
155
|
+
`${fixturePath}/template`,
|
|
156
|
+
);
|
|
76
157
|
|
|
77
158
|
expect(result.bundled).to.equal(true);
|
|
78
|
-
expect(result.to).to.equal(`${fixturePath}/template/${COVERAGE_BUNDLE_DIRECTORY}`);
|
|
159
|
+
expect(result.to).to.equal(`${fixturePath}/template/${COVERAGE_BUNDLE_DIRECTORY}/hardhat`);
|
|
79
160
|
expect(fs.readFileSync(`${result.to}/index.html`, 'utf8')).to.include('report');
|
|
80
161
|
expect(fs.existsSync(`${result.to}/base.css`)).to.equal(true);
|
|
81
162
|
});
|
|
82
163
|
|
|
83
164
|
it('replaces a previously bundled report rather than merging into it', () => {
|
|
84
|
-
fs.outputFileSync(`${fixturePath}/template/${COVERAGE_BUNDLE_DIRECTORY}/stale.html`, 'stale');
|
|
85
|
-
fs.outputFileSync(`${fixturePath}/
|
|
165
|
+
fs.outputFileSync(`${fixturePath}/template/${COVERAGE_BUNDLE_DIRECTORY}/hardhat/stale.html`, 'stale');
|
|
166
|
+
fs.outputFileSync(`${fixturePath}/hardhat/coverage/index.html`, '<!doctype html><title>fresh</title>');
|
|
86
167
|
|
|
87
|
-
const { to } =
|
|
168
|
+
const [{ to }] = bundleCoverageReports(
|
|
169
|
+
[{ id: 'hardhat', path: `${fixturePath}/hardhat` }],
|
|
170
|
+
`${fixturePath}/template`,
|
|
171
|
+
);
|
|
88
172
|
|
|
89
173
|
expect(fs.existsSync(`${to}/stale.html`)).to.equal(false);
|
|
90
174
|
expect(fs.readFileSync(`${to}/index.html`, 'utf8')).to.include('fresh');
|
|
91
175
|
});
|
|
92
176
|
|
|
93
|
-
it('reports
|
|
94
|
-
const result =
|
|
95
|
-
|
|
96
|
-
|
|
177
|
+
it('reports what was not bundled without creating its directory', () => {
|
|
178
|
+
const [result] = bundleCoverageReports(
|
|
179
|
+
[{ id: 'hardhat', path: `${fixturePath}/hardhat` }],
|
|
180
|
+
`${fixturePath}/template`,
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
expect(result).to.deep.equal({
|
|
184
|
+
id: 'hardhat',
|
|
185
|
+
bundled: false,
|
|
186
|
+
to: `${fixturePath}/template/${COVERAGE_BUNDLE_DIRECTORY}/hardhat`,
|
|
187
|
+
});
|
|
97
188
|
expect(fs.existsSync(result.to)).to.equal(false);
|
|
98
189
|
});
|
|
99
190
|
|
|
100
|
-
it('renders a static unavailable page
|
|
101
|
-
const page = coverageUnavailablePage();
|
|
191
|
+
it('renders a static unavailable page naming the report and the run that produces it', () => {
|
|
192
|
+
const page = coverageUnavailablePage({ id: 'engine', suite: 'unit,infra,app' });
|
|
102
193
|
expect(page).to.include('Coverage report unavailable');
|
|
103
|
-
expect(page).to.include('
|
|
104
|
-
expect(page).to.include(COVERAGE_BUNDLE_DIRECTORY);
|
|
194
|
+
expect(page).to.include('node bin test unit,infra,app');
|
|
195
|
+
expect(page).to.include(`${COVERAGE_BUNDLE_DIRECTORY}/engine`);
|
|
105
196
|
expect(page).not.to.include('<script');
|
|
106
197
|
});
|
|
107
198
|
});
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll, beforeEach, vi } from 'vitest';
|
|
2
|
+
import { mkdtemp, rm, mkdir, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
|
|
6
|
+
// The restore path only routes documents; the collections are in-memory stand-ins, IPFS is
|
|
7
|
+
// unreachable, and the static frame writer is a no-op, so what is asserted is the routing.
|
|
8
|
+
const models = {};
|
|
9
|
+
vi.mock('../../../src/db/DataBaseProvider.js', () => ({
|
|
10
|
+
DataBaseProviderService: { getModel: (name) => models[name] },
|
|
11
|
+
}));
|
|
12
|
+
vi.mock('../../../src/api/ipfs/ipfs.service.js', () => ({ createPinRecord: async () => ({}) }));
|
|
13
|
+
vi.mock('../../../src/projects/cyberia/ipfs-client.js', () => ({ IpfsClient: { addToIpfs: async () => null } }));
|
|
14
|
+
vi.mock('../../../src/projects/cyberia/object-layer.js', () => ({
|
|
15
|
+
ObjectLayerEngine: { writeStaticFrameAssets: async () => [], computeAndSaveFinalSha256: async () => ({}) },
|
|
16
|
+
}));
|
|
17
|
+
// The generator pulls in jimp, a cyberia product dependency the engine runner does not install;
|
|
18
|
+
// every store method that would reach it is spied out below, so only the constant is needed.
|
|
19
|
+
vi.mock('../../../src/projects/cyberia/atlas-sprite-sheet-generator.js', () => ({
|
|
20
|
+
AtlasSpriteSheetGenerator: {},
|
|
21
|
+
DEFAULT_ATLAS_UPSCALE_FACTOR: 20,
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
const { AtlasSpriteSheetStore } = await import('../../../src/projects/cyberia/atlas-sprite-sheet-store.js');
|
|
25
|
+
const { atlasBackupFileKey, readObjectLayerBackup, restoreObjectLayerBackup } =
|
|
26
|
+
await import('../../../src/projects/cyberia/instance-backup.js');
|
|
27
|
+
|
|
28
|
+
/** A collection that remembers what was created and hands the last upsert back as the live document. */
|
|
29
|
+
const collection = () => {
|
|
30
|
+
const created = [];
|
|
31
|
+
let live = null;
|
|
32
|
+
const query = (value) => Object.assign(Promise.resolve(value), { populate: () => Promise.resolve(value) });
|
|
33
|
+
return {
|
|
34
|
+
created,
|
|
35
|
+
deleteOne: async () => ({ deletedCount: 0 }),
|
|
36
|
+
create: async (doc) => (created.push(doc), doc),
|
|
37
|
+
findByItemId: () => query(live),
|
|
38
|
+
upsertByItemId: async (doc) => {
|
|
39
|
+
live = { ...doc, markModified() {}, save: async () => live };
|
|
40
|
+
return live;
|
|
41
|
+
},
|
|
42
|
+
get live() {
|
|
43
|
+
return live;
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
let backupDir;
|
|
49
|
+
const write = (rel, value) => writeFile(join(backupDir, rel), Buffer.isBuffer(value) ? value : JSON.stringify(value));
|
|
50
|
+
|
|
51
|
+
beforeAll(async () => {
|
|
52
|
+
backupDir = await mkdtemp(join(tmpdir(), 'cyberia-backup-'));
|
|
53
|
+
for (const dir of ['object-layers', 'render-frames', 'atlas-sprite-sheets', 'files', 'ipfs/content']) {
|
|
54
|
+
await mkdir(join(backupDir, dir), { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
await write('object-layers/hatchet.json', {
|
|
57
|
+
_id: 'ol1',
|
|
58
|
+
cid: 'cid-data',
|
|
59
|
+
objectLayerRenderFramesId: 'rf1',
|
|
60
|
+
atlasSpriteSheetId: 'at1',
|
|
61
|
+
data: { item: { id: 'hatchet', type: 'weapon' }, render: { cid: 'cid-png', metadataCid: 'cid-meta' } },
|
|
62
|
+
});
|
|
63
|
+
await write('render-frames/hatchet.json', { _id: 'rf1', frames: {}, colors: [], frame_duration: 100 });
|
|
64
|
+
await write('atlas-sprite-sheets/hatchet.json', {
|
|
65
|
+
_id: 'at1',
|
|
66
|
+
fileId: 'f-full',
|
|
67
|
+
minifyFileId: 'f-min',
|
|
68
|
+
idlePreviewFileId: 'f-idle',
|
|
69
|
+
cid: 'cid-png',
|
|
70
|
+
});
|
|
71
|
+
// Files are matched on _id, never on name: one is misnamed on purpose, one is a bystander.
|
|
72
|
+
await write('files/whatever.json', {
|
|
73
|
+
_id: 'f-full',
|
|
74
|
+
name: 'hatchet-atlas.png',
|
|
75
|
+
data: { $base64: Buffer.from('PNG').toString('base64') },
|
|
76
|
+
});
|
|
77
|
+
await write('files/atlas-minify-hatchet.json', {
|
|
78
|
+
_id: 'f-min',
|
|
79
|
+
name: 'hatchet-minify.png',
|
|
80
|
+
data: { type: 'Buffer', data: [1, 2, 3] },
|
|
81
|
+
});
|
|
82
|
+
await write('files/atlas-idle-hatchet.json', { _id: 'f-idle', name: 'hatchet-idle.png', data: { $base64: 'AA==' } });
|
|
83
|
+
await write('files/atlas-sword.json', { _id: 'f-other', name: 'sword-atlas.png', data: { $base64: 'AA==' } });
|
|
84
|
+
await write('ipfs/content/cid-data.bin', Buffer.from('data'));
|
|
85
|
+
await write('ipfs/content/cid-png.bin', Buffer.from('png'));
|
|
86
|
+
// cid-meta has no payload on purpose.
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
afterAll(() => rm(backupDir, { recursive: true, force: true }));
|
|
90
|
+
|
|
91
|
+
describe('reading one object layer out of an instance backup', () => {
|
|
92
|
+
it('gathers the object layer and every document it references', () => {
|
|
93
|
+
const backup = readObjectLayerBackup({ backupDir, itemId: 'hatchet' });
|
|
94
|
+
expect(backup.objectLayer.data.item.id).toBe('hatchet');
|
|
95
|
+
expect(backup.renderFrames._id).toBe('rf1');
|
|
96
|
+
expect(backup.atlas._id).toBe('at1');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('resolves the atlas render Files by _id and leaves other Files alone', () => {
|
|
100
|
+
const { files } = readObjectLayerBackup({ backupDir, itemId: 'hatchet' });
|
|
101
|
+
expect(files.map((f) => f._id).sort()).toEqual(['f-full', 'f-idle', 'f-min']);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('names each exported render after its atlas field', () => {
|
|
105
|
+
expect(atlasBackupFileKey('fileId', 'hatchet')).toBe('atlas-hatchet');
|
|
106
|
+
expect(atlasBackupFileKey('minifyFileId', 'hatchet')).toBe('atlas-minify-hatchet');
|
|
107
|
+
expect(atlasBackupFileKey('idlePreviewFileId', 'hatchet')).toBe('atlas-idle-hatchet');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('decodes both File byte encodings the export can write', () => {
|
|
111
|
+
const { files } = readObjectLayerBackup({ backupDir, itemId: 'hatchet' });
|
|
112
|
+
const byId = Object.fromEntries(files.map((f) => [f._id, f.data]));
|
|
113
|
+
expect(Buffer.isBuffer(byId['f-full']) && byId['f-full'].toString()).toBe('PNG');
|
|
114
|
+
expect(Buffer.isBuffer(byId['f-min']) && [...byId['f-min']]).toEqual([1, 2, 3]);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('collects only the payloads the backup actually carries', () => {
|
|
118
|
+
const { payloads } = readObjectLayerBackup({ backupDir, itemId: 'hatchet' });
|
|
119
|
+
expect([...payloads.keys()].sort()).toEqual(['cid-data', 'cid-png']);
|
|
120
|
+
expect(payloads.get('cid-png').toString()).toBe('png');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('names the backup and item when the item is missing', () => {
|
|
124
|
+
expect(() => readObjectLayerBackup({ backupDir, itemId: 'ghost' })).toThrow(/no object layer 'ghost'/);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('tolerates an item with no atlas or render frames yet', async () => {
|
|
128
|
+
await write('object-layers/bare.json', { _id: 'ol2', data: { item: { id: 'bare', type: 'floor' } } });
|
|
129
|
+
const backup = readObjectLayerBackup({ backupDir, itemId: 'bare' });
|
|
130
|
+
expect(backup.atlas).toBeNull();
|
|
131
|
+
expect(backup.renderFrames).toBeNull();
|
|
132
|
+
expect(backup.files).toEqual([]);
|
|
133
|
+
expect(backup.payloads.size).toBe(0);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('restoring one object layer from an instance backup', () => {
|
|
138
|
+
let persist;
|
|
139
|
+
let idle;
|
|
140
|
+
|
|
141
|
+
beforeEach(() => {
|
|
142
|
+
for (const name of ['ObjectLayer', 'ObjectLayerRenderFrames', 'AtlasSpriteSheet', 'File'])
|
|
143
|
+
models[name] = collection();
|
|
144
|
+
persist = vi.spyOn(AtlasSpriteSheetStore, 'persist').mockResolvedValue({
|
|
145
|
+
atlasDoc: { _id: 'at-rebuilt' },
|
|
146
|
+
atlasCid: 'cid-rebuilt-png',
|
|
147
|
+
atlasMetadataCid: 'cid-rebuilt-meta',
|
|
148
|
+
});
|
|
149
|
+
idle = vi.spyOn(AtlasSpriteSheetStore, 'syncIdlePreview').mockResolvedValue({ status: 'unchanged' });
|
|
150
|
+
vi.spyOn(AtlasSpriteSheetStore, 'pruneOrphanRenders').mockResolvedValue(0);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
afterAll(() => vi.restoreAllMocks());
|
|
154
|
+
|
|
155
|
+
it('keeps the atlas the backup carries when it holds its minified render', async () => {
|
|
156
|
+
await restoreObjectLayerBackup({ backupDir, itemId: 'hatchet', options: {} });
|
|
157
|
+
expect(models.AtlasSpriteSheet.created[0].minifyFileId).toBe('f-min');
|
|
158
|
+
expect(persist).not.toHaveBeenCalled();
|
|
159
|
+
expect(idle).toHaveBeenCalledWith({ itemKey: 'hatchet', options: {} });
|
|
160
|
+
expect(models.ObjectLayer.live.data.render).toEqual({ cid: 'cid-png', metadataCid: 'cid-meta' });
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Regression: a backup written before the minified render existed restored an atlas the
|
|
164
|
+
// blob route could not serve, and the client runtime got a 500 for every entity wearing it.
|
|
165
|
+
it('rebuilds the atlas a backup restores without its minified render and relinks the item', async () => {
|
|
166
|
+
await write('object-layers/relic.json', {
|
|
167
|
+
_id: 'ol3',
|
|
168
|
+
objectLayerRenderFramesId: 'rf3',
|
|
169
|
+
atlasSpriteSheetId: 'at3',
|
|
170
|
+
data: { item: { id: 'relic', type: 'skin' }, render: { cid: 'cid-old-png', metadataCid: 'cid-old-meta' } },
|
|
171
|
+
});
|
|
172
|
+
const frames = { _id: 'rf3', frames: { down_idle: [] }, colors: [], frame_duration: 100 };
|
|
173
|
+
await write('render-frames/relic.json', frames);
|
|
174
|
+
await write('atlas-sprite-sheets/relic.json', { _id: 'at3', fileId: 'f-relic', minifyFileId: null });
|
|
175
|
+
|
|
176
|
+
await restoreObjectLayerBackup({ backupDir, itemId: 'relic', options: {} });
|
|
177
|
+
|
|
178
|
+
expect(persist).toHaveBeenCalledWith({ itemKey: 'relic', objectLayerRenderFrames: frames, options: {} });
|
|
179
|
+
const { live } = models.ObjectLayer;
|
|
180
|
+
expect(live.atlasSpriteSheetId).toBe('at-rebuilt');
|
|
181
|
+
expect(live.data.render).toEqual({ cid: 'cid-rebuilt-png', metadataCid: 'cid-rebuilt-meta' });
|
|
182
|
+
});
|
|
183
|
+
});
|