OverviewFilesCommitsIssuesDocsReleasesPeers

ap-bridge / test / per-user.test.js

58 lines · javascript ref:
// ⛔ ISOLATION. Routing many actors through one process while they share a record source, a follower
// list or a key is worse than not routing them at all: the second user serves the first's posts,
// their followers are the same people, and one key signs for both.

import test from 'brittle'
import { Users } from '../lib/users.js'
import { PerUser } from '../lib/per-user.js'

const users = () => new Users([
  { username: 'p2p', primary: true },
  { username: 'ashatron', favelaSocket: '/tmp/ash.sock' }
], 'ap.7rnx.net')

const mk = (shared = {}) => new PerUser({ users: users(), dataDir: '/tmp/apb-test', shared })

test('★★ a second user NEVER inherits the shared instances', async (t) => {
  // The failure this class exists to prevent: ashatron serving p2p's posts to p2p's followers.
  const shared = { records: { tag: 'shared' }, followerStore: { tag: 'shared' }, signer: { tag: 'shared' } }
  const pu = mk(shared)
  const u = pu.users.get('ashatron')
  t.not(pu.records(u), shared.records)
  t.not(pu.followers(u), shared.followerStore)
  t.not(pu.signer(u), shared.signer)
})

test('⚠ the PRIMARY does inherit them — its followers are real people', async (t) => {
  // A fresh follower store for the incumbent would silently stop delivering to everyone already
  // following it.
  const shared = { records: { tag: 'shared' }, followerStore: { tag: 'shared' }, signer: { tag: 'shared' } }
  const pu = mk(shared)
  const p = pu.users.primary
  t.is(pu.records(p), shared.records)
  t.is(pu.followers(p), shared.followerStore)
  t.is(pu.signer(p), shared.signer)
})

test('★★ a user with a peer gets a REMOTE signer — the bridge holds no key', async (t) => {
  const s = mk().signer(users().get('ashatron'))
  t.ok(s, 'there is a signer')
  t.is(s.custody, 'remote', 'and it holds nothing')
  t.is(s.keyId, 'https://ap.7rnx.net/ap/users/ashatron/actor#main-key', 'keyed to THEIR actor')
})

test('⛔ no key and no peer means NO SIGNER, and that is the safe direction', async (t) => {
  // An actor with no signer can still be fetched, followed and read; it simply cannot push. Failing
  // the other way would mean signing with somebody else's key.
  const u = new Users([{ username: 'p2p', primary: true }, { username: 'nobody' }], 'ap.7rnx.net')
  const pu = new PerUser({ users: u, dataDir: '/tmp/apb-test' })
  t.is(pu.signer(u.get('nobody')), null)
})

test('⚠ resolvers are memoised — a socket per request is not a design', async (t) => {
  const pu = mk()
  const u = pu.users.get('ashatron')
  t.is(pu.signer(u), pu.signer(u))
  t.is(pu.followers(u), pu.followers(u), 'and a store rebuilt per request forgets what it was told')
})