OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-note / test / unit / video-gc.test.js

100 lines · javascript ref:
import test from 'brittle'
import Corestore from 'corestore'
import path from 'bare-path'
import fs from 'bare-fs'
import os from 'bare-os'
import { MediaStore } from '../../lib/media-store.js'

function tmpdir () {
  const dir = path.join(os.tmpdir(), 'hn-vgc-test-' + Date.now() + '-' + Math.random().toString(36).slice(2))
  fs.mkdirSync(dir, { recursive: true })
  return dir
}

function rmrf (dir) {
  try { fs.rmSync(dir, { recursive: true, force: true }) } catch {}
}

test('Video GC - cascade clears poster, manifest, segments', async (t) => {
  const dir = tmpdir()
  try {
    const store = new Corestore(dir)
    const media = new MediaStore(store, {
      graceMs: 50,
      isReferencedAnywhere: async () => false
    })
    await media.ready()

    // Simulate a processed video: original + poster + manifest + segments
    const originalHash = await media.put(Buffer.from('original video data'), 'video/mp4')
    const posterHash = await media.put(Buffer.from('jpeg poster'), 'image/jpeg')
    const manifestHash = await media.put(Buffer.from('#EXTM3U\nmaster'), 'application/vnd.apple.mpegurl')
    const m3u8Hash = await media.put(Buffer.from('#EXTINF:6\nseg'), 'application/vnd.apple.mpegurl')
    const seg1Hash = await media.put(Buffer.from('segment 1 ts data'), 'video/mp2t')
    const seg2Hash = await media.put(Buffer.from('segment 2 ts data'), 'video/mp2t')

    // Write video index
    await media._index.put('video:' + originalHash, {
      status: 'ready',
      poster: posterHash,
      manifest: manifestHash,
      levels: [{
        name: '480p',
        m3u8: m3u8Hash,
        segments: [seg1Hash, seg2Hash]
      }]
    })

    // All blobs exist
    t.is(await media.has(originalHash), true)
    t.is(await media.has(posterHash), true)
    t.is(await media.has(manifestHash), true)
    t.is(await media.has(seg1Hash), true)
    t.is(await media.has(seg2Hash), true)

    // Schedule orphan - should cascade clear everything
    media.scheduleOrphan(originalHash)
    await new Promise(r => setTimeout(r, 200))

    // All blobs should be cleared
    t.is(await media.has(originalHash), false)
    t.is(await media.has(posterHash), false)
    t.is(await media.has(manifestHash), false)
    t.is(await media.has(m3u8Hash), false)
    t.is(await media.has(seg1Hash), false)
    t.is(await media.has(seg2Hash), false)

    // Video index should be deleted
    const videoEntry = await media._index.get('video:' + originalHash)
    t.is(videoEntry, null)

    await store.close()
  } finally {
    rmrf(dir)
  }
})

test('Video GC - non-video blob clears normally without cascade', async (t) => {
  const dir = tmpdir()
  try {
    const store = new Corestore(dir)
    const media = new MediaStore(store, {
      graceMs: 50,
      isReferencedAnywhere: async () => false
    })
    await media.ready()

    const hash = await media.put(Buffer.from('just an image'), 'image/jpeg')
    t.is(await media.has(hash), true)

    media.scheduleOrphan(hash)
    await new Promise(r => setTimeout(r, 200))

    t.is(await media.has(hash), false)

    await store.close()
  } finally {
    rmrf(dir)
  }
})