dsh 0.2.0-rc.2: in-container update (experimental channel), path-agnostic compose, upstream patches adapted to 0.1.x/0.2.x
This commit is contained in:
commit
e6dcef9bf5
302 files changed
+75178
No files matched your search
@@ -0,0 +1,111 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { chmod, lstat, mkdir, mkdtemp, readFile, readlink, symlink, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import test from 'node:test'
|
||||
import { PersistentStateSnapshots } from '../../control-plane/modules/updater/lib/snapshots.mjs'
|
||||
import { SnapshotClient } from '../../control-plane/modules/updater/lib/snapshot-client.mjs'
|
||||
import { UserPluginSnapshots } from '../../control-plane/modules/plugin-manager/user-snapshots.mjs'
|
||||
import { LocalApiClient } from '../../control-plane/modules/updater/lib/client.mjs'
|
||||
import { createSnapshotServer, listenSnapshots } from '../stage0/lib/snapshot-server.mjs'
|
||||
|
||||
async function fixture(options = {}) {
|
||||
const root = await mkdtemp(join(tmpdir(), 'dsh-snapshots-'))
|
||||
const sourceRoot = join(root, 'home')
|
||||
await mkdir(join(sourceRoot, 'nested'), { recursive: true })
|
||||
return {
|
||||
root,
|
||||
sourceRoot,
|
||||
snapshots: new PersistentStateSnapshots({ root: join(root, 'snapshots'), sourceRoot, ...options }),
|
||||
}
|
||||
}
|
||||
|
||||
const state = {
|
||||
id: 'before-rc8',
|
||||
runtimeId: '0.1.0-rc.7-1',
|
||||
environmentVersion: '1.0.0',
|
||||
dshVersion: '0.1.0-rc.7',
|
||||
}
|
||||
|
||||
test('snapshots and restores complete DSH_HOME contents and permissions', async () => {
|
||||
const { sourceRoot, snapshots } = await fixture({ now: () => new Date('2026-08-19T00:00:00.000Z') })
|
||||
await writeFile(join(sourceRoot, '.config'), 'before')
|
||||
await writeFile(join(sourceRoot, 'nested', 'database'), 'rows')
|
||||
await chmod(join(sourceRoot, 'nested', 'database'), 0o640)
|
||||
await symlink('nested/database', join(sourceRoot, 'database-link'))
|
||||
const createProgress = []
|
||||
const snapshot = await snapshots.create({ ...state, onProgress: value => { createProgress.push(value) } })
|
||||
assert.equal(snapshot.createdAt, '2026-08-19T00:00:00.000Z')
|
||||
assert.equal(createProgress.at(-1).processedItems, createProgress.at(-1).totalItems)
|
||||
|
||||
await writeFile(join(sourceRoot, '.config'), 'after')
|
||||
await writeFile(join(sourceRoot, 'new-data'), 'remove me')
|
||||
const restoreProgress = []
|
||||
await snapshots.restore(state.id, { onProgress: value => { restoreProgress.push(value) } })
|
||||
assert.equal(await readFile(join(sourceRoot, '.config'), 'utf8'), 'before')
|
||||
assert.equal(await readFile(join(sourceRoot, 'nested', 'database'), 'utf8'), 'rows')
|
||||
assert.equal((await lstat(join(sourceRoot, 'nested', 'database'))).mode & 0o777, 0o640)
|
||||
assert.equal(await readlink(join(sourceRoot, 'database-link')), 'nested/database')
|
||||
await assert.rejects(readFile(join(sourceRoot, 'new-data')), { code: 'ENOENT' })
|
||||
assert.equal(restoreProgress.at(-1).processedItems, restoreProgress.at(-1).totalItems)
|
||||
assert.equal((await snapshots.list())[0].id, state.id)
|
||||
})
|
||||
|
||||
test('verifies a snapshot before clearing current DSH_HOME', async () => {
|
||||
const { sourceRoot, snapshots } = await fixture()
|
||||
await writeFile(join(sourceRoot, 'database'), 'before')
|
||||
const snapshot = await snapshots.create(state)
|
||||
await writeFile(join(snapshot.path, 'data.tar.gz'), 'corrupt')
|
||||
await writeFile(join(sourceRoot, 'database'), 'current')
|
||||
await assert.rejects(snapshots.restore(state.id), { code: 'TRUST_ARTIFACT_MISMATCH' })
|
||||
assert.equal(await readFile(join(sourceRoot, 'database'), 'utf8'), 'current')
|
||||
})
|
||||
|
||||
test('does not publish a partial snapshot when archive creation fails', async () => {
|
||||
const { snapshots } = await fixture({ runImpl: async () => { throw new Error('no space left') } })
|
||||
await assert.rejects(snapshots.create(state), /no space/)
|
||||
assert.deepEqual(await snapshots.list(), [])
|
||||
})
|
||||
|
||||
test('removing an absent snapshot is idempotent while corrupt snapshots remain errors', async () => {
|
||||
const { snapshots } = await fixture()
|
||||
assert.equal(await snapshots.remove('already-removed'), false)
|
||||
const snapshot = await snapshots.create(state)
|
||||
await writeFile(join(snapshot.path, 'data.tar.gz'), 'corrupt')
|
||||
await assert.rejects(snapshots.remove(state.id), { code: 'TRUST_ARTIFACT_MISMATCH' })
|
||||
})
|
||||
|
||||
test('Stage-0 serves only fixed DSH and Web Profile snapshot scopes', async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), 'dsh-snapshot-server-'))
|
||||
const dshHome = join(root, 'dsh')
|
||||
const profileRoot = join(dshHome, 'profiles', 'web')
|
||||
await mkdir(profileRoot, { recursive: true })
|
||||
await writeFile(join(dshHome, 'settings.yaml'), 'before')
|
||||
await writeFile(join(profileRoot, 'package.json'), '{"before":true}')
|
||||
const server = createSnapshotServer({
|
||||
dshSnapshots: new PersistentStateSnapshots({ root: join(root, 'snapshots'), sourceRoot: dshHome }),
|
||||
userPluginSnapshots: new UserPluginSnapshots({ root: join(root, 'user-plugin-snapshots'), profileRoot }),
|
||||
})
|
||||
const socket = join(root, 'snapshot.sock')
|
||||
await listenSnapshots(server, socket)
|
||||
const api = new LocalApiClient(socket)
|
||||
const dsh = new SnapshotClient(api, 'dsh')
|
||||
const userPlugin = new SnapshotClient(api, 'user-plugin')
|
||||
try {
|
||||
const created = await dsh.create(state)
|
||||
assert.equal(created.id, state.id)
|
||||
assert.equal(created.path, undefined)
|
||||
await writeFile(join(dshHome, 'settings.yaml'), 'after')
|
||||
await dsh.restore(state.id)
|
||||
assert.equal(await readFile(join(dshHome, 'settings.yaml'), 'utf8'), 'before')
|
||||
|
||||
const plugin = await userPlugin.create('profile-before-change')
|
||||
assert.equal(plugin.id, 'profile-before-change')
|
||||
assert.equal(plugin.path, undefined)
|
||||
await writeFile(join(profileRoot, 'package.json'), '{"after":true}')
|
||||
await userPlugin.restore('profile-before-change')
|
||||
assert.equal(await readFile(join(profileRoot, 'package.json'), 'utf8'), '{"before":true}')
|
||||
} finally {
|
||||
await new Promise(resolve => server.close(resolve))
|
||||
}
|
||||
})
|
||||
Reference in new issue
Block a user