@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
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @module downloader-integrity.test
|
|
5
|
+
* @description Covers the contract a split-artifact transfer depends on: a download either
|
|
6
|
+
* produces the whole file or produces none. A response that ends early still ends, so the write
|
|
7
|
+
* stream finishes and, without a length check, a short file is reported as a completed download
|
|
8
|
+
* and only surfaces later as a corrupt archive assembled from it.
|
|
9
|
+
*
|
|
10
|
+
* Uses 'chai' for assertions.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { expect } from 'chai';
|
|
14
|
+
import fs from 'fs-extra';
|
|
15
|
+
import http from 'http';
|
|
16
|
+
import Downloader from '../../src/server/storage/downloader.js';
|
|
17
|
+
|
|
18
|
+
const BODY = Buffer.from('a'.repeat(64 * 1024));
|
|
19
|
+
|
|
20
|
+
/** Serves one response shape and resolves the base URL it listens on. */
|
|
21
|
+
const serverFactory = (handler) =>
|
|
22
|
+
new Promise((resolve) => {
|
|
23
|
+
const server = http.createServer(handler);
|
|
24
|
+
server.listen(0, '127.0.0.1', () => resolve({ server, url: `http://127.0.0.1:${server.address().port}/part` }));
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('download integrity', () => {
|
|
28
|
+
let fixturePath;
|
|
29
|
+
let running;
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
fixturePath = fs.mkdtempSync('/tmp/engine-downloader-');
|
|
33
|
+
running = [];
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
afterEach(async () => {
|
|
37
|
+
for (const server of running) await new Promise((resolve) => server.close(resolve));
|
|
38
|
+
fs.removeSync(fixturePath);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const serve = async (handler) => {
|
|
42
|
+
const { server, url } = await serverFactory(handler);
|
|
43
|
+
running.push(server);
|
|
44
|
+
return url;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
it('saves a complete response and reports the path', async () => {
|
|
48
|
+
const url = await serve((request, response) => {
|
|
49
|
+
response.writeHead(200, { 'content-length': String(BODY.length) });
|
|
50
|
+
response.end(BODY);
|
|
51
|
+
});
|
|
52
|
+
const target = `${fixturePath}/whole.bin`;
|
|
53
|
+
|
|
54
|
+
expect(await Downloader.downloadFile(url, target)).to.equal(target);
|
|
55
|
+
expect(fs.statSync(target).size).to.equal(BODY.length);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('rejects a response that stops short of its declared length, and leaves no partial file', async () => {
|
|
59
|
+
const url = await serve((request, response) => {
|
|
60
|
+
response.writeHead(200, { 'content-length': String(BODY.length) });
|
|
61
|
+
response.write(BODY.subarray(0, 1024));
|
|
62
|
+
// Ending early is what a cut-short transfer looks like to the client: the stream still ends.
|
|
63
|
+
response.destroy();
|
|
64
|
+
});
|
|
65
|
+
const target = `${fixturePath}/short.bin`;
|
|
66
|
+
|
|
67
|
+
let thrown;
|
|
68
|
+
await Downloader.downloadFile(url, target).catch((error) => (thrown = error));
|
|
69
|
+
|
|
70
|
+
expect(thrown, 'a truncated transfer must not resolve').to.not.equal(undefined);
|
|
71
|
+
expect(fs.existsSync(target), 'a partial file is indistinguishable from a whole one').to.equal(false);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('accepts a response that declares no length, since nothing can be checked against', async () => {
|
|
75
|
+
const url = await serve((request, response) => {
|
|
76
|
+
response.writeHead(200, { 'transfer-encoding': 'chunked' });
|
|
77
|
+
response.end(BODY);
|
|
78
|
+
});
|
|
79
|
+
const target = `${fixturePath}/chunked.bin`;
|
|
80
|
+
|
|
81
|
+
expect(await Downloader.downloadFile(url, target)).to.equal(target);
|
|
82
|
+
expect(fs.statSync(target).size).to.equal(BODY.length);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('rejects a request the server refuses, and leaves no file', async () => {
|
|
86
|
+
const url = await serve((request, response) => {
|
|
87
|
+
response.writeHead(404);
|
|
88
|
+
response.end();
|
|
89
|
+
});
|
|
90
|
+
const target = `${fixturePath}/missing.bin`;
|
|
91
|
+
|
|
92
|
+
let thrown;
|
|
93
|
+
await Downloader.downloadFile(url, target).catch((error) => (thrown = error));
|
|
94
|
+
|
|
95
|
+
expect(thrown).to.not.equal(undefined);
|
|
96
|
+
expect(fs.existsSync(target)).to.equal(false);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @module file-reference-registry.test
|
|
5
|
+
* @description Covers `src/api/file/file.ref.js`: the mapping of a model to the fields that hold
|
|
6
|
+
* a File `_id`, and the two deletions built on it. Every writer that removes a document reads
|
|
7
|
+
* this registry to take the blobs it owned with it, so what is asserted here is what keeps the
|
|
8
|
+
* File collection free of unreachable documents.
|
|
9
|
+
*
|
|
10
|
+
* Uses 'chai' for assertions.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { expect } from 'chai';
|
|
14
|
+
import {
|
|
15
|
+
deleteOwnedFiles,
|
|
16
|
+
deleteUnreferencedFiles,
|
|
17
|
+
documentFileIds,
|
|
18
|
+
fileRefApis,
|
|
19
|
+
fileRefFields,
|
|
20
|
+
} from '../../src/api/file/file.ref.js';
|
|
21
|
+
|
|
22
|
+
/** Minimal collection: the helpers only run `find(filter, projection).lean()` and `deleteMany`. */
|
|
23
|
+
const collection = (docs) => ({
|
|
24
|
+
docs,
|
|
25
|
+
find(filter) {
|
|
26
|
+
const clauses = filter.$or ?? [filter];
|
|
27
|
+
const matched = docs.filter((doc) =>
|
|
28
|
+
clauses.some((clause) =>
|
|
29
|
+
Object.entries(clause).every(([field, condition]) => {
|
|
30
|
+
const value = doc[field];
|
|
31
|
+
if (value === undefined || value === null) return false;
|
|
32
|
+
return condition.$in.map(String).includes(String(value));
|
|
33
|
+
}),
|
|
34
|
+
),
|
|
35
|
+
);
|
|
36
|
+
return { lean: async () => matched };
|
|
37
|
+
},
|
|
38
|
+
async deleteMany({ _id: { $in } }) {
|
|
39
|
+
const removing = new Set($in.map(String));
|
|
40
|
+
const before = docs.length;
|
|
41
|
+
this.docs = docs = docs.filter((doc) => !removing.has(String(doc._id)));
|
|
42
|
+
return { deletedCount: before - docs.length };
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('the File reference registry', () => {
|
|
47
|
+
it('answers the fields of a registered api and nothing for an unregistered one', () => {
|
|
48
|
+
expect(fileRefFields('atlas-sprite-sheet')).to.deep.equal(['fileId', 'minifyFileId', 'idlePreviewFileId']);
|
|
49
|
+
expect(fileRefFields('cyberia-map')).to.deep.equal(['thumbnail', 'preview']);
|
|
50
|
+
expect(fileRefFields('object-layer')).to.deep.equal([]);
|
|
51
|
+
expect(fileRefApis()).to.include('cyberia-audio');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('reads ids out of documents by field, skipping the ones that hold none', () => {
|
|
55
|
+
const docs = [{ fileId: 'a', minifyFileId: 'b' }, { fileId: 'a' }, { minifyFileId: null }];
|
|
56
|
+
expect(documentFileIds(docs, ['fileId', 'minifyFileId'])).to.deep.equal(['a', 'b']);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('deleting the files a collection owned', () => {
|
|
61
|
+
it('removes the ids no surviving document holds', async () => {
|
|
62
|
+
const files = collection([{ _id: 'atlas-1' }, { _id: 'minify-1' }, { _id: 'unrelated' }]);
|
|
63
|
+
const atlases = collection([]);
|
|
64
|
+
|
|
65
|
+
const removed = await deleteOwnedFiles({
|
|
66
|
+
File: files,
|
|
67
|
+
Owner: atlases,
|
|
68
|
+
fields: ['fileId', 'minifyFileId'],
|
|
69
|
+
ids: ['atlas-1', 'minify-1'],
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
expect(removed).to.equal(2);
|
|
73
|
+
expect(files.docs.map((doc) => doc._id)).to.deep.equal(['unrelated']);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('keeps an id a survivor still points at', async () => {
|
|
77
|
+
// Renders are content addressed, so a duplicate document of the same item resolves to the
|
|
78
|
+
// same File id. Dropping the duplicate must not blind the document that remains.
|
|
79
|
+
const files = collection([{ _id: 'shared' }, { _id: 'gone' }]);
|
|
80
|
+
const atlases = collection([{ _id: 'survivor', fileId: 'shared', minifyFileId: null }]);
|
|
81
|
+
|
|
82
|
+
const removed = await deleteOwnedFiles({
|
|
83
|
+
File: files,
|
|
84
|
+
Owner: atlases,
|
|
85
|
+
fields: ['fileId', 'minifyFileId'],
|
|
86
|
+
ids: ['shared', 'gone'],
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
expect(removed).to.equal(1);
|
|
90
|
+
expect(files.docs.map((doc) => doc._id)).to.deep.equal(['shared']);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('deletes nothing when handed no candidates', async () => {
|
|
94
|
+
const files = collection([{ _id: 'kept' }]);
|
|
95
|
+
expect(await deleteOwnedFiles({ File: files, Owner: collection([]), fields: ['fileId'], ids: [] })).to.equal(0);
|
|
96
|
+
expect(files.docs).to.have.lengthOf(1);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('sweeping files no registered model references', () => {
|
|
101
|
+
const loadedModels = (overrides = {}) => {
|
|
102
|
+
const models = Object.fromEntries(fileRefApis().map((api) => [api, collection([])]));
|
|
103
|
+
return { ...models, ...overrides };
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
it('refuses to delete while a registered api has no model to read', async () => {
|
|
107
|
+
// An unreadable collection cannot be shown to hold no reference, and a sweep that assumed
|
|
108
|
+
// otherwise would delete a live blob belonging to another domain.
|
|
109
|
+
const files = collection([{ _id: 'candidate' }]);
|
|
110
|
+
const models = loadedModels();
|
|
111
|
+
delete models.document;
|
|
112
|
+
|
|
113
|
+
const result = await deleteUnreferencedFiles({
|
|
114
|
+
File: files,
|
|
115
|
+
ids: ['candidate'],
|
|
116
|
+
getModel: (api) => {
|
|
117
|
+
if (!models[api]) throw new Error(`Model not loaded: ${api}`);
|
|
118
|
+
return models[api];
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
expect(result.skipped).to.equal('document');
|
|
123
|
+
expect(result.removed).to.equal(0);
|
|
124
|
+
expect(files.docs).to.have.lengthOf(1);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('deletes only the candidates every model agrees it never references', async () => {
|
|
128
|
+
const files = collection([{ _id: 'orphan' }, { _id: 'held' }]);
|
|
129
|
+
const models = loadedModels({ 'cyberia-map': collection([{ _id: 'map', thumbnail: 'held', preview: null }]) });
|
|
130
|
+
|
|
131
|
+
const result = await deleteUnreferencedFiles({
|
|
132
|
+
File: files,
|
|
133
|
+
ids: ['orphan', 'held'],
|
|
134
|
+
getModel: (api) => models[api],
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
expect(result.skipped).to.equal('');
|
|
138
|
+
expect(result.removed).to.equal(1);
|
|
139
|
+
expect(files.docs.map((doc) => doc._id)).to.deep.equal(['held']);
|
|
140
|
+
});
|
|
141
|
+
});
|