OverviewFilesCommitsIssuesDocsReleasesPeers

pgh-sidecar / test / store-gc-keep-releases.test.js

60 lines · javascript ref:
/**
 * store-gc RPC — release retention runs inside the sidecar (it holds the
 * registry; the full release list overflows the UDS frame for a CLI).
 * 2026-10-11: the 14-day age gate kept 75 hyper-tps .pck copies (~44 GiB).
 */

import test from 'brittle'
import fs from '#node:fs'
import path from '#node:path'
import os from '#node:os'

import { Client } from 'hyper-svc'
import { Sidecar } from '../lib/index.js'

function mktemp (prefix) {
  return fs.mkdtempSync(path.join(os.tmpdir(), prefix + '-'))
}

const h = (c) => c.repeat(64)

test('store-gc keeps the newest 2 releases per app and sweeps older ones, fresh or not', async (t) => {
  const root = mktemp('sidecar-root')
  const storeRoot = mktemp('sidecar-store')
  const sidecar = new Sidecar({
    root,
    socketPath: path.join(root, 'svc.sock'),
    storeRoot,
    registryStore: mktemp('sidecar-registry'),
    nameRegistryStore: mktemp('sidecar-names'),
    releaseRegistryReplicasDir: mktemp('sidecar-replicas'),
    enableSeed: false,
    passphrase: 'test-passphrase'
  })
  await sidecar.start()
  const client = await Client.connectUds(path.join(root, 'svc.sock'))

  // Three releases of one app; every tree pulled just now (the age gate would keep all).
  // v1 = root a + dep b; v2 = root c + dep b (shared); v3 = root d + prebuild e.
  for (const c of ['a', 'b', 'c', 'd', 'e']) {
    fs.mkdirSync(sidecar.treeStore.treePath(h(c)), { recursive: true })
    sidecar.treeStore._writeMeta(h(c), { pulledAt: Date.now(), schema: 2 })
  }
  const rel = (version, publishedAt, treeHash, deps) =>
    client.call('publish-release', { entry: { name: 'app', version, publishedAt, treeHash, deps } })
  await rel('1', '2026-10-01T00:00:00.000Z', h('a'), [{ name: 'x', version: '1', tree: h('b') }])
  await rel('2', '2026-10-02T00:00:00.000Z', h('c'), [{ name: 'x', version: '1', tree: h('b') }])
  await rel('3', '2026-10-03T00:00:00.000Z', h('d'), [{ name: 'y', version: '1', tree: null, prebuilds: { 'linux-x64': h('e') } }])

  const dry = await client.call('store-gc', { dryRun: true, keepReleases: 2 })
  t.alike(dry.candidates, [h('a')], 'dry run: only v1\'s own root is a candidate (b is shared with v2)')
  t.ok(sidecar.treeStore.hasTree(h('a')), 'dry run deletes nothing')

  const real = await client.call('store-gc', { keepReleases: 2 })
  t.alike(real.removed, [h('a')], 'v1 root removed')
  for (const c of ['b', 'c', 'd', 'e']) t.ok(sidecar.treeStore.hasTree(h(c)), 'kept ' + c)

  await client.close()
  await sidecar.stop()
})