RELAY-PHASE1 Wave A2/B/C/D/E (12-agent workflow, all tsc-clean, 314/314 tests pass): - A2: control-plane server.ts entry + boot/redis.ts revocation-bus wiring + start script. - B1: relay-run shared-store EnforceDeps (relay-auth ports over the SAME Postgres+Redis as P3). - B2: registry-backed MtlsVerifier (verifyAgentCert, fail-closed, INV14). - B3: store-backed RouteResolver (subdomain->hostId). - B4: Redis relay:revocations subscriber -> tunnel teardown (INV12). - B5: main-phase1.ts production entry (public bind, real TLS, async-mTLS prefetch bridge) + staging /auth/mint. - B6 (PARTIAL): relay-web operator login + browser DPoP; proof offered via term.dpop.<b64u> subprotocol. - C: agent dist/cli.js build (esbuild) + runTunnel run-loop + CliDeps. - D1: same-origin static serve of relay-web/public from the browser WSS. - E: systemd units + gen-ca/gen-capability-key/issue-tls-cert scripts + deploy/RUNBOOK.md. Adversarial review: all hard invariants PASS. Follow-ups (B7): close DPoP-subprotocol read on browser-server (blocks browser connect); rate-limit /auth/mint (F1); wire activeSessionCount (F2); scrub error logs (F5). Excludes unrelated public/style.css (concurrent iOS job).
60 lines
2.4 KiB
TypeScript
60 lines
2.4 KiB
TypeScript
/**
|
|
* A2 — unit tests for the ioredis → RedisPublisher adapter (boot/redis.ts). The adapter is the only
|
|
* non-trivial logic in the server entrypoint's Redis wiring: it must forward publish(channel, message)
|
|
* verbatim to the underlying client and surface the driver's subscriber-count result unchanged. The
|
|
* live `createRedisClient` (a thin `new Redis(url)`) needs a real broker and is covered by the boot
|
|
* smoke, not here.
|
|
*/
|
|
import { describe, test, expect } from 'vitest'
|
|
import type { Redis } from 'ioredis'
|
|
import { createRedisPublisher } from '../src/boot/redis.js'
|
|
import { createRedisRevocationBus } from '../src/routing/bus.js'
|
|
import { RELAY_REVOCATIONS_CHANNEL, type KillSignal } from 'relay-contracts'
|
|
|
|
/** Minimal ioredis stand-in recording publish calls; cast to Redis at the seam (only `publish` is used). */
|
|
function fakeRedis(returnValue = 1): { calls: Array<[string, string]>; client: Redis } {
|
|
const calls: Array<[string, string]> = []
|
|
const client = {
|
|
publish: async (channel: string, message: string): Promise<number> => {
|
|
calls.push([channel, message])
|
|
return returnValue
|
|
},
|
|
} as unknown as Redis
|
|
return { calls, client }
|
|
}
|
|
|
|
describe('A2 createRedisPublisher (RedisPublisher adapter)', () => {
|
|
test('forwards channel + message verbatim and returns the driver subscriber count', async () => {
|
|
// Arrange
|
|
const { calls, client } = fakeRedis(3)
|
|
const publisher = createRedisPublisher(client)
|
|
|
|
// Act
|
|
const receivers = await publisher.publish('relay:revocations', 'payload')
|
|
|
|
// Assert
|
|
expect(receivers).toBe(3)
|
|
expect(calls).toEqual([['relay:revocations', 'payload']])
|
|
})
|
|
|
|
test('drives createRedisRevocationBus onto the FROZEN relay:revocations channel with JSON payload', async () => {
|
|
// Arrange
|
|
const { calls, client } = fakeRedis()
|
|
const bus = createRedisRevocationBus(createRedisPublisher(client))
|
|
const signal: KillSignal = {
|
|
scope: { kind: 'host', hostId: '11111111-1111-4111-8111-111111111111' },
|
|
at: 1_700_000_000,
|
|
reason: 'revoked',
|
|
}
|
|
|
|
// Act
|
|
await bus.publish(signal)
|
|
|
|
// Assert — bus publishes exactly one message on the shared channel, JSON-encoded.
|
|
expect(calls).toHaveLength(1)
|
|
const [channel, message] = calls[0]!
|
|
expect(channel).toBe(RELAY_REVOCATIONS_CHANNEL)
|
|
expect(JSON.parse(message)).toMatchObject({ scope: { kind: 'host', hostId: signal.scope.kind === 'host' ? signal.scope.hostId : '' } })
|
|
})
|
|
})
|