Files
hackerspace.sh/__tests__/api-call-logic.test.ts
T
Moheeb Zara 7ec90b0f1e Door epic phases 4-5: inbound access-log ingest + universal API-call buttons
Phase 4 (migration 053): pull entry/denied events into door_access_log and
match each card to a member, via two transports sharing one ingest core.
Poll: a CRON_SECRET-guarded /api/cron/door-ingest reads each opted-in
native-HeatSync connection's ?z log through the existing hardened executor
and parses it (firmware-characterized, best-effort ring buffer). Webhook:
/api/door/inbound/[connection] accepts pushed event JSON authenticated by a
per-connection bearer secret from the vault. Both dedupe on
(connection_id, dedupe_key) so re-polls and retries are no-ops. Adds an
inbound config panel to /door/manage.

Phase 5 (migration 054): admins define api_buttons that fire a configured
HTTP call (full verbs + headers + body) when an authorized member presses
them, through the same hardened egress (now shared between callDoor and the
new callApi). Each button carries a required_permission (default the new
apicall.invoke code); members see only buttons they may press. Builder at
/door/buttons with a door-template preset; members press from /doors. Every
press is recorded in api_call_log with secrets redacted.

Both subsystems are inert until an admin enables inbound on a connection or
creates a button. 624 unit + 50 integration tests; migrations applied
top-to-bottom in schema.sql; types and docs updated.
2026-05-20 20:45:44 -07:00

57 lines
2.9 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import { buildApiRequest } from '@/lib/api-call-logic'
describe('buildApiRequest', () => {
it('injects a query secret with the right separator', () => {
const a = buildApiRequest({ baseUrl: 'https://h/x', method: 'GET', authMode: 'query', authParam: 'key', secret: 's3cret' })
expect(a.url).toBe('https://h/x?key=s3cret')
const b = buildApiRequest({ baseUrl: 'https://h/x?a=1', method: 'GET', authMode: 'query', authParam: 'key', secret: 's3cret' })
expect(b.url).toBe('https://h/x?a=1&key=s3cret')
})
it('url-encodes the query secret', () => {
const r = buildApiRequest({ baseUrl: 'https://h/x', method: 'GET', authMode: 'query', authParam: 'k', secret: 'a b&c' })
expect(r.url).toBe('https://h/x?k=a%20b%26c')
})
it('injects a bearer secret as an Authorization header', () => {
const r = buildApiRequest({ baseUrl: 'https://h', method: 'POST', authMode: 'bearer', secret: 'tok' })
expect(r.headers['authorization']).toBe('Bearer tok')
expect(r.url).toBe('https://h')
})
it('injects a custom-header secret under the named (lower-cased) header', () => {
const r = buildApiRequest({ baseUrl: 'https://h', method: 'POST', authMode: 'header', authParam: 'X-Api-Key', secret: 'k' })
expect(r.headers['x-api-key']).toBe('k')
})
it('injects nothing for auth_mode none, or when secret is absent', () => {
const none = buildApiRequest({ baseUrl: 'https://h', method: 'GET', authMode: 'none', secret: 'ignored-no-param' })
expect(none.url).toBe('https://h')
expect(Object.keys(none.headers)).not.toContain('authorization')
const noSecret = buildApiRequest({ baseUrl: 'https://h', method: 'GET', authMode: 'bearer', secret: null })
expect(noSecret.headers['authorization']).toBeUndefined()
})
it('drops a caller-supplied host header (executor forces the pinned host)', () => {
const r = buildApiRequest({ baseUrl: 'https://h', method: 'POST', authMode: 'none', headers: { Host: 'evil.example', 'X-Other': 'ok' } })
expect(r.headers['host']).toBeUndefined()
expect(r.headers['x-other']).toBe('ok')
})
it('appends url_template to the base url', () => {
const r = buildApiRequest({ baseUrl: 'https://h', urlTemplate: '/toggle?id=5', method: 'GET', authMode: 'none' })
expect(r.url).toBe('https://h/toggle?id=5')
})
it('carries a body only for body-bearing verbs', () => {
expect(buildApiRequest({ baseUrl: 'https://h', method: 'GET', authMode: 'none', body: '{"a":1}' }).body).toBeNull()
expect(buildApiRequest({ baseUrl: 'https://h', method: 'POST', authMode: 'none', body: '{"a":1}' }).body).toBe('{"a":1}')
expect(buildApiRequest({ baseUrl: 'https://h', method: 'delete', authMode: 'none', body: 'x' }).body).toBe('x')
})
it('uppercases the method', () => {
expect(buildApiRequest({ baseUrl: 'https://h', method: 'post', authMode: 'none' }).method).toBe('POST')
})
})