bend_puzzle/test/field-save-load-v2-smoke-test.js
2026-08-01 16:06:14 +09:00

38 lines
5.3 KiB
JavaScript

'use strict';
const {assert,functionSource,app,read}=require('./helpers/app-source');
const openDb=functionSource('openWorldDb');
const loadV2=functionSource('loadV2SnapshotFromDb');
const persistence=functionSource('persistDirtyToDb');
const stageArchive=functionSource('stageArchiveV2');
const stageRecovery=functionSource('stageRecoverySnapshotV2');
const activation=functionSource('activateReadyWorldV2');
const reset=functionSource('clearDatabaseWorld');
const verify=functionSource('verifyActiveWorldActivation');
const readiness=functionSource('validateActiveWorldReadiness');
const validation=functionSource('validateStagedWorldV2');
const collect=functionSource('collectV2Garbage');
const batchDelete=functionSource('deleteV2EpochBatch');
const persistNow=functionSource('persistNow');
const init=functionSource('init');
const importSave=functionSource('importSaveFile');
const activateStaged=functionSource('activateStagedWorldV2');
const postflight=functionSource('verifyStagedStorageEstimate');
assert(openDb.includes('activeFormat:FIELD_STORAGE_FORMAT')&&openDb.includes("source:{kind:'fresh'}")&&!openDb.includes('activeFormat:1'),'Fresh storage does not start directly in the current V2 format');
assert(openDb.includes("for(const retired of['metas','states','global','recovery','tombstones','outbox'])")&&openDb.includes('db.deleteObjectStore(retired)'),'Retired object stores are not deleted during the current-only upgrade');
assert(!persistence.includes("objectStore('metas')")&&!persistence.includes("objectStore('states')")&&!persistence.includes("objectStore('outbox')")&&!persistence.includes("objectStore('recovery')")&&!persistence.includes("objectStore('tombstones')"),'Persistence still dual-writes the retired storage format');
assert(!init.includes('migrateLoadedWorldToV2')&&!importSave.includes('migrateLoadedWorldToV2'),'Runtime still invokes the retired storage migration');
assert(!app.includes('legacyCost')&&!app.includes('migrateScoreAmount')&&!app.includes('loadedFromLegacy')&&!app.includes("kind:'migration'")&&!app.includes('full-resync'),'Retired score, startup, or cloud migration compatibility remains');
assert(functionSource('assertCurrentArchive').includes('manifest?.saveSchema!==SAVE_SCHEMA')&&functionSource('assertCurrentArchive').includes('manifest?.worldGeneration!==WORLD_GENERATION'),'Archive import does not require the exact current format');
assert(!app.includes('archiveMigrationRegistry')&&!app.includes('PREVIOUS_WORLD_GENERATION')&&!importSave.includes('looksJson')&&!importSave.includes('.json'),'Old-generation or legacy JSON import compatibility remains');
assert(stageArchive.includes('validateStagedWorldV2')&&stageArchive.includes('verifyStagedStorageEstimate')&&stageArchive.includes('archive global record is not canonical')&&stageArchive.includes("status:'ready'")&&stageRecovery.includes('validateStagedWorldV2')&&stageRecovery.includes('verifyStagedStorageEstimate')&&stageRecovery.includes("status:'ready'"),'Current archive/recovery staging skips canonical, database, or storage validation');
assert(validation.includes('boardIndexMatchesDetails')&&validation.includes('count(IDBKeyRange.only(epoch))')&&validation.includes('global aggregates')&&validation.includes('originOwnedByB0'),'Staged validation does not verify detail revisions, row counts, summaries, aggregates, and B0 origin ownership');
assert(activation.includes("olderPrevious.status='garbage'")&&activation.includes("currentWorld.status='rollback'")&&activation.includes('activationVerified:false')&&reset.includes("activateReadyWorldV2({epoch:newEpoch,expected,world},{kind:'reset'})")&&reset.includes('deleteV2Epoch(newEpoch)')&&activateStaged.includes('deleteV2Epoch(staged?.epoch)'),'Import and reset do not share the atomic ready-world activation primitive or clean failed staging epochs');
assert(readiness.includes("indexes[0].id!=='B0'")&&readiness.includes('B0 revision check')&&readiness.includes('meta.x+dx===0')&&init.includes('rollbackUnverifiedWorld'),'Startup does not validate B0 origin ownership and roll back an unverified activation');
assert(persistNow.includes('result.count>0')&&persistNow.includes('verifyActiveWorldActivation')&&verify.includes("previous.status='garbage'"),'Previous epoch is not retained until the first durable board checkpoint');
assert(batchDelete.includes('getAllKeys(epochKeyRange(epoch),limit)')&&batchDelete.includes("phase:'cleanup'")&&app.includes('GC_BATCH_ROWS=500'),'Epoch garbage collection is not bounded and resumable');
assert(collect.includes('control?.previousEpoch')&&collect.includes("startsWith('pin:')")&&collect.includes("world.status==='ready'")&&collect.includes('cleanupTemporaryExports'),'Garbage collection does not protect rollback/recovery epochs or clean stale ready/export artifacts');
assert(postflight.includes('QUOTA_POSTFLIGHT'),'Import postflight does not recheck browser storage headroom');
assert(app.includes('cameraAnchor')&&app.includes('selectedBoardId')&&init.includes('randomUnsolvedMeta')&&loadV2.includes('selectedIndex'),'Saved board hints or randomized unsolved-board startup are missing');
assert(persistence.includes('v2Active')&&read('test/browser-field-storage-benchmark.js').includes("'10000,100000,200000'"),'Current V2 persistence or large-field benchmark profiles are missing');
console.log('Current-only field save/load V2 integration guards passed');