321 lines
12 KiB
JavaScript
321 lines
12 KiB
JavaScript
'use strict';
|
|
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
|
|
const database = require('../../src/database');
|
|
const { createV3Repositories } = require('../../src/services/v3-repositories');
|
|
const { createV3DerivedViews } = require('../../src/services/v3-derived-views');
|
|
|
|
function withTempDatabase(fn) {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-v3-derived-views-'));
|
|
database.configure({
|
|
dataDir: root,
|
|
logger: {
|
|
info() {},
|
|
warn() {},
|
|
error() {},
|
|
},
|
|
});
|
|
return database.initialize()
|
|
.then(() => fn())
|
|
.finally(() => {
|
|
database.close();
|
|
fs.rmSync(root, { recursive: true, force: true });
|
|
});
|
|
}
|
|
|
|
function createVolume({ deviceFingerprint = 'device-A', volumeKey = 'volume-A', mountPath = '/media/a', displayName = 'Device A', deviceState = 'available' }) {
|
|
database.runMutatingSqlWithParams(`
|
|
INSERT INTO storage_devices (scope, stable_fingerprint, display_name, device_type, availability_state)
|
|
VALUES ('server', @fingerprint, @displayName, 'server', @state)
|
|
ON CONFLICT(scope, stable_fingerprint) DO UPDATE SET
|
|
display_name = excluded.display_name,
|
|
availability_state = excluded.availability_state
|
|
`, {
|
|
fingerprint: deviceFingerprint,
|
|
displayName,
|
|
state: deviceState,
|
|
});
|
|
const deviceId = Number(database.runQuery(`SELECT id FROM storage_devices WHERE scope = 'server' AND stable_fingerprint = '${deviceFingerprint}'`)[0]?.id || 0);
|
|
|
|
database.runMutatingSqlWithParams(`
|
|
INSERT INTO storage_volumes (device_id, stable_volume_key, mount_path, can_copy, can_play, can_install)
|
|
VALUES (@deviceId, @volumeKey, @mountPath, 1, 1, 1)
|
|
`, {
|
|
deviceId,
|
|
volumeKey,
|
|
mountPath,
|
|
});
|
|
const volumeId = Number(database.runQuery('SELECT last_insert_rowid() AS id')[0]?.id || 0);
|
|
|
|
return { deviceId, volumeId };
|
|
}
|
|
|
|
function createTitle({ titleId = 'PPSA00000', contentId = 'content-000', name = 'Title' }) {
|
|
database.runMutatingSqlWithParams(`
|
|
INSERT INTO titles (platform, title_id, content_id, name, sort_name, region, metadata_source)
|
|
VALUES ('ps5', @titleId, @contentId, @name, @name, 'US', 'tests')
|
|
`, {
|
|
titleId,
|
|
contentId,
|
|
name,
|
|
});
|
|
return Number(database.runQuery('SELECT last_insert_rowid() AS id')[0]?.id || 0);
|
|
}
|
|
|
|
function getFirst(rowSet, titleId) {
|
|
return rowSet.find((row) => row.titleId === titleId) || null;
|
|
}
|
|
|
|
test('derived state matrix', () => {
|
|
return withTempDatabase(() => {
|
|
const { volumeId } = createVolume({ deviceFingerprint: 'device-1', volumeKey: 'volume-1', mountPath: '/device-1' });
|
|
const { consoleInstalls } = createV3Repositories(database);
|
|
const { localCopies } = createV3Repositories(database);
|
|
const views = createV3DerivedViews(database);
|
|
|
|
const observedTitleId = createTitle({ titleId: 'PPSA10001', contentId: 'c1', name: 'Observed install' });
|
|
const staleTitleId = createTitle({ titleId: 'PPSA10002', contentId: 'c2', name: 'Stale install' });
|
|
const absentTitleId = createTitle({ titleId: 'PPSA10003', contentId: 'c3', name: 'Absent install' });
|
|
const unavailableTitleId = createTitle({ titleId: 'PPSA10004', contentId: 'c4', name: 'Unavailable device install' });
|
|
const unconfirmedTitleId = createTitle({ titleId: 'PPSA10005', contentId: 'c5', name: 'Unconfirmed install' });
|
|
const copyOnlyTitleId = createTitle({ titleId: 'PPSA10006', contentId: 'c6', name: 'Copy only' });
|
|
|
|
const observed = consoleInstalls.upsertConsoleInstall({
|
|
titleId: observedTitleId,
|
|
volumeId,
|
|
consoleProfileId: 'profile-observed',
|
|
remoteLocationKey: 'ux0:/app0/PPSA10001',
|
|
installedVersion: '1.00',
|
|
installState: 'observed',
|
|
generation: 10,
|
|
});
|
|
assert.equal(observed.ok, true);
|
|
|
|
const stale = consoleInstalls.upsertConsoleInstall({
|
|
titleId: staleTitleId,
|
|
volumeId,
|
|
consoleProfileId: 'profile-stale',
|
|
remoteLocationKey: 'ux0:/app0/PPSA10002',
|
|
installedVersion: '1.10',
|
|
installState: 'stale',
|
|
generation: 20,
|
|
});
|
|
assert.equal(stale.ok, true);
|
|
|
|
const absent = consoleInstalls.upsertConsoleInstall({
|
|
titleId: absentTitleId,
|
|
volumeId,
|
|
consoleProfileId: 'profile-absent',
|
|
remoteLocationKey: 'ux0:/app0/PPSA10003',
|
|
installedVersion: '',
|
|
installState: 'absent',
|
|
generation: 30,
|
|
});
|
|
assert.equal(absent.ok, true);
|
|
|
|
const unavailable = consoleInstalls.upsertConsoleInstall({
|
|
titleId: unavailableTitleId,
|
|
volumeId,
|
|
consoleProfileId: 'profile-unavailable',
|
|
remoteLocationKey: 'ux0:/app0/PPSA10004',
|
|
installedVersion: '',
|
|
installState: 'device_unavailable',
|
|
generation: 40,
|
|
});
|
|
assert.equal(unavailable.ok, true);
|
|
|
|
const unconfirmed = consoleInstalls.upsertConsoleInstall({
|
|
titleId: unconfirmedTitleId,
|
|
volumeId,
|
|
consoleProfileId: 'profile-unconfirmed',
|
|
remoteLocationKey: 'ux0:/app0/PPSA10005',
|
|
installedVersion: '',
|
|
installState: 'legacy-unverified',
|
|
generation: 50,
|
|
});
|
|
assert.equal(unconfirmed.ok, true);
|
|
|
|
const copyOnly = localCopies.upsertLocalCopy({
|
|
titleId: copyOnlyTitleId,
|
|
volumeId,
|
|
relativePath: 'copy/title-six',
|
|
copyType: 'folder',
|
|
baseVersion: '1.00',
|
|
versionSource: 'scan',
|
|
generation: 60,
|
|
});
|
|
assert.equal(copyOnly.ok, true);
|
|
|
|
const rows = views.listTitleAvailability().reduce((map, row) => {
|
|
map[row.titleId] = row;
|
|
return map;
|
|
}, {});
|
|
|
|
assert.equal(rows[observedTitleId].derivedState, 'installed');
|
|
assert.equal(rows[observedTitleId].confirmedInstallCount, 1);
|
|
assert.equal(rows[observedTitleId].consoleInstallCount, 1);
|
|
|
|
assert.equal(rows[staleTitleId].derivedState, 'stale');
|
|
assert.equal(rows[staleTitleId].staleInstallCount, 1);
|
|
|
|
assert.equal(rows[absentTitleId].derivedState, 'absent');
|
|
assert.equal(rows[absentTitleId].absentInstallCount, 1);
|
|
|
|
assert.equal(rows[unavailableTitleId].derivedState, 'device_unavailable');
|
|
assert.equal(rows[unavailableTitleId].deviceUnavailableInstallCount, 1);
|
|
|
|
assert.equal(rows[unconfirmedTitleId].derivedState, 'unknown_install_state');
|
|
assert.equal(rows[unconfirmedTitleId].confirmedInstallCount, 0);
|
|
|
|
assert.equal(rows[copyOnlyTitleId].derivedState, 'copy_only');
|
|
assert.equal(rows[copyOnlyTitleId].localCopyCount, 1);
|
|
});
|
|
});
|
|
|
|
test('protection diversity', () => {
|
|
return withTempDatabase(() => {
|
|
const { volumeId: sameDeviceVolumeA } = createVolume({ deviceFingerprint: 'device-same', volumeKey: 'volume-a', mountPath: '/device-same/a' });
|
|
const { volumeId: sameDeviceVolumeB } = createVolume({ deviceFingerprint: 'device-same', volumeKey: 'volume-b', mountPath: '/device-same/b' });
|
|
const { volumeId: differentDeviceVolumeA } = createVolume({ deviceFingerprint: 'device-diff-a', volumeKey: 'volume-a', mountPath: '/device-diff/a' });
|
|
|
|
const { localCopies } = createV3Repositories(database);
|
|
const views = createV3DerivedViews(database);
|
|
|
|
const sameDeviceTitle = createTitle({ titleId: 'PPSA11000', contentId: 'p1', name: 'Same device copies' });
|
|
localCopies.upsertLocalCopy({
|
|
titleId: sameDeviceTitle,
|
|
volumeId: sameDeviceVolumeA,
|
|
relativePath: 'copy/a1',
|
|
copyType: 'folder',
|
|
baseVersion: '1.00',
|
|
generation: 1,
|
|
});
|
|
localCopies.upsertLocalCopy({
|
|
titleId: sameDeviceTitle,
|
|
volumeId: sameDeviceVolumeB,
|
|
relativePath: 'copy/a2',
|
|
copyType: 'folder',
|
|
baseVersion: '1.00',
|
|
generation: 2,
|
|
});
|
|
|
|
const twoDeviceTitle = createTitle({ titleId: 'PPSA11001', contentId: 'p2', name: 'Different device copies' });
|
|
localCopies.upsertLocalCopy({
|
|
titleId: twoDeviceTitle,
|
|
volumeId: sameDeviceVolumeA,
|
|
relativePath: 'copy/b1',
|
|
copyType: 'folder',
|
|
baseVersion: '1.00',
|
|
generation: 3,
|
|
});
|
|
localCopies.upsertLocalCopy({
|
|
titleId: twoDeviceTitle,
|
|
volumeId: differentDeviceVolumeA,
|
|
relativePath: 'copy/c1',
|
|
copyType: 'folder',
|
|
baseVersion: '1.00',
|
|
generation: 4,
|
|
});
|
|
|
|
const sameDeviceRows = views.listProtectionHealth(sameDeviceTitle).map((row) => row.distinctCopyDeviceCount);
|
|
const twoDeviceRows = views.listProtectionHealth(twoDeviceTitle).map((row) => row.distinctCopyDeviceCount);
|
|
const sameDeviceHealth = views.listProtectionHealth(sameDeviceTitle);
|
|
const twoDeviceHealth = views.listProtectionHealth(twoDeviceTitle);
|
|
|
|
assert.equal(sameDeviceHealth[0].localCopyCount, 2);
|
|
assert.equal(sameDeviceHealth[0].distinctCopyDeviceCount, 1);
|
|
assert.equal(sameDeviceHealth[0].protectionHealthy, false);
|
|
|
|
assert.equal(twoDeviceHealth[0].localCopyCount, 2);
|
|
assert.equal(twoDeviceHealth[0].distinctCopyDeviceCount, 2);
|
|
assert.equal(twoDeviceHealth[0].protectionHealthy, true);
|
|
});
|
|
});
|
|
|
|
test('multiple version/location cases', () => {
|
|
return withTempDatabase(() => {
|
|
const { volumeId: volumeA } = createVolume({ deviceFingerprint: 'volume-device-a', volumeKey: 'vol-a', mountPath: '/device-a/vol-a' });
|
|
const { volumeId: volumeB } = createVolume({ deviceFingerprint: 'volume-device-b', volumeKey: 'vol-b', mountPath: '/device-b/vol-b' });
|
|
const { localCopies, consoleInstalls } = createV3Repositories(database);
|
|
const views = createV3DerivedViews(database);
|
|
|
|
const titleId = createTitle({ titleId: 'PPSA12000', contentId: 'p3', name: 'Multiple location title' });
|
|
|
|
localCopies.upsertLocalCopy({
|
|
titleId,
|
|
volumeId: volumeA,
|
|
relativePath: 'copies/main',
|
|
copyType: 'folder',
|
|
baseVersion: '1.00',
|
|
generation: 10,
|
|
});
|
|
localCopies.upsertLocalCopy({
|
|
titleId,
|
|
volumeId: volumeB,
|
|
relativePath: 'copies/secondary',
|
|
copyType: 'folder',
|
|
baseVersion: '1.10',
|
|
generation: 11,
|
|
});
|
|
|
|
const confirmedOld = consoleInstalls.upsertConsoleInstall({
|
|
titleId,
|
|
volumeId: volumeA,
|
|
consoleProfileId: 'profile-multi-1',
|
|
remoteLocationKey: 'ux0:/app0/PPSA12000-main',
|
|
installedVersion: '1.00',
|
|
installState: 'observed',
|
|
generation: 100,
|
|
});
|
|
assert.equal(confirmedOld.ok, true);
|
|
|
|
const staleModern = consoleInstalls.upsertConsoleInstall({
|
|
titleId,
|
|
volumeId: volumeB,
|
|
consoleProfileId: 'profile-multi-2',
|
|
remoteLocationKey: 'ux0:/app0/PPSA12000-stale',
|
|
installedVersion: '2.00',
|
|
installState: 'stale',
|
|
generation: 20,
|
|
});
|
|
assert.equal(staleModern.ok, true);
|
|
|
|
const confirmedNewer = consoleInstalls.upsertConsoleInstall({
|
|
titleId,
|
|
volumeId: volumeB,
|
|
consoleProfileId: 'profile-multi-3',
|
|
remoteLocationKey: 'ux0:/app1/PPSA12000-main',
|
|
installedVersion: '1.01',
|
|
installState: 'observed',
|
|
generation: 50,
|
|
});
|
|
assert.equal(confirmedNewer.ok, true);
|
|
|
|
const summary = views.listTitleInstallSummary(titleId);
|
|
assert.equal(summary.length, 3);
|
|
const observedCount = summary.filter((item) => item.isConfirmedInstall).length;
|
|
assert.equal(observedCount, 2);
|
|
|
|
const availability = views.getTitleAvailability(titleId);
|
|
assert.equal(availability.consoleInstallCount, 3);
|
|
assert.equal(availability.confirmedInstallCount, 2);
|
|
assert.equal(availability.localCopyCount, 2);
|
|
|
|
const updateStatus = views.getTitleUpdateStatus(titleId);
|
|
assert.equal(updateStatus.confirmedInstallCount, 2);
|
|
assert.equal(updateStatus.confirmedInstalledVersion, '1.00');
|
|
|
|
const decision = views.getUpdateDecision({
|
|
titleId,
|
|
latestCompatibleVersion: '1.20',
|
|
});
|
|
assert.equal(decision.updateAvailable, true);
|
|
assert.equal(decision.comparison, -1);
|
|
});
|
|
});
|