OverviewFilesCommitsIssuesDocsReleasesPeers

favela-peer / test / peek-lazy.test.js

124 lines · javascript ref:
// Peeking must ANSWER before it has finished.
//
// ⛔ `hold()` opens an autobee on a key we have never seen and joins the swarm for it, and
// `SwarmTransport.join` awaits `discovery.flushed()` — a DHT announce round trip whose result is
// never used. Doing that inside the request meant a socket call that sat there until the DHT
// answered, which from a browser is indistinguishable from a hung daemon. `pairOffer` already
// learned this: "a socket request that blocked for five minutes would look like a hung daemon".
//
// So peek registers the intent, answers, and opens in the background. The three states a page needs
// are then: opening / held-as-peek / followed.

import test from 'brittle'
import fs from '#node:fs'
import os from '#node:os'
import { join } from '#node:path'
import { FavelaNode } from '../lib/node.js'
import { saveCredential } from '../lib/device.js'
import { identity } from './helpers.js'

const tmp = () => fs.mkdtempSync(join(os.tmpdir(), 'favela-lazy-'))
const KEY = 'ab'.repeat(32)

/**
 * A transport that can be made to stop answering — a DHT that is simply not there.
 *
 * ⚠ It starts OPEN. Blocking from the first call would hang the node's own boot, which opens its
 * own feed and its namespace through this same seam — an earlier version of this file did exactly
 * that and the failure looked like the code under test rather than the stub.
 */
function stuckTransport () {
  let release = () => {}
  const gate = new Promise((r) => { release = r })
  return {
    blocked: false,
    joins: 0,
    release,
    async join () {
      if (!this.blocked) return
      this.joins++
      return gate
    },
    async leave () {},
    async destroy () {}
  }
}

async function node (transport) {
  const storage = tmp()
  const id = await identity()
  saveCredential(storage, await id.device())
  const n = new FavelaNode(storage, { transport })
  await n.ready()
  // Boot is done; from here the swarm stops answering.
  if (transport) transport.blocked = true
  return n
}

test('★ peek answers immediately even when the swarm never does', async (t) => {
  const transport = stuckTransport()
  const n = await node(transport)
  t.teardown(async () => { transport.release(); await n.close() })

  const started = Date.now()
  const r = await n.peek(KEY)
  const took = Date.now() - started

  // ⛔ The assertion is about ANSWERING, not about speed of light: the gate below is still open, so
  // a blocking implementation could not have returned at all.
  t.ok(took < 2000, 'answered while the join is still outstanding (' + took + 'ms)')
  t.is(r.feed, KEY)
  t.is(r.status, 'opening', 'and it says so rather than implying the feed is ready')
})

test('the state a page needs: opening, then held', async (t) => {
  const transport = stuckTransport()
  const n = await node(transport)
  t.teardown(async () => { transport.release(); await n.close() })

  await n.peek(KEY)
  t.alike(n.holding(KEY), { held: false, peeked: false, following: false, opening: true, blocked: false },
    'not held YET — a page must not render "nothing from them" while it is still arriving')

  transport.release()
  await n.opening(KEY)
  const after = n.holding(KEY)
  t.is(after.opening, false)
  t.is(after.held, true, 'and now their page has something to render')
  t.is(after.peeked, true, 'held to read, still not followed')
})

test('⛔ peeking twice does not open twice', async (t) => {
  const transport = stuckTransport()
  const n = await node(transport)
  t.teardown(async () => { transport.release(); await n.close() })

  await n.peek(KEY)
  await n.peek(KEY)
  await n.peek(KEY)
  transport.release()
  await n.opening(KEY)
  // A page that polls, or a reader who presses twice, must not stack opens of the same feed.
  t.is(transport.joins, 1, 'one open, however many times it is asked for')
})

test('⚠ a swarm that is down does not stop you holding the feed', async (t) => {
  let boot = true
  const n = await node({
    async join () { if (boot) return; throw new Error('swarm is down') },
    async leave () {}, async destroy () {},
    set blocked (v) { boot = !v }
  })
  t.teardown(() => n.close())

  await n.peek(KEY)
  await n.opening(KEY)
  // ⛔ A feed with no swarm join is still a usable feed — it does not replicate, which is degraded
  // and not a failure to open. The join used to be awaited unguarded, so it threw after the feed
  // was already open and left it orphaned: draining, untracked, never closed.
  t.is(n.holding(KEY).held, true, 'held anyway, it just will not replicate')
  // And either way the attempt is OVER. A peek stuck on "opening" would leave the page saying
  // "fetching…" for ever with no way to retry.
  t.is(n.holding(KEY).opening, false)
})