mattrguerra/meridian-ops-boardmainPrivate on GitHub, published here in full

fathom-multi-key.test.ts

Language TypeScript6.5 KB164 lines

apps/dashboard/test/fathom-multi-key.test.ts

  1. /**
  2. * Several keys, and what happens when one of them is dark.
  3. *
  4. * Fathom has no org keys; each key returns its owner's recordings plus
  5. * whatever Fathom shares with that user. Adding keys buys coverage, never
  6. * gating (config/fathom-keys.ts).
  7. *
  8. * With more than one key there are two ways to lie, and both break the prime
  9. * invariant:
  10. *
  11. * - all-or-nothing failure. One revoked key blanks meetings for everyone,
  12. * throwing away the other keys' real data to report one.
  13. * - a swallowed per-key failure, which is worse. A key that cannot reach
  14. * someone's meetings then looks exactly like a person who recorded none,
  15. * and one of those two means free capacity.
  16. *
  17. * So the rule asserted here: a partial read stays partial. The data survives,
  18. * and every surface that shows it says which key is dark, by owner (report
  19. * what happened, name no cause).
  20. */
  21. import { describe, expect, it } from 'vitest'
  22. import { FIXTURE_SNAPSHOT } from '../src/fixtures.js'
  23. import { headline, peopleBoard, type Snapshot, type FathomSourceStatus } from '../src/collect.js'
  24. import { sourceStatuses, myDay, personViews } from '../src/view-model.js'
  25. import { render } from '../src/render.js'
  26. import { viewerFor } from '../src/viewer.js'
  27. import { FATHOM_KEYS } from '../../../config/fathom-keys.js'
  28. /**
  29. * `mode: 'live'` because sourceStatuses() deliberately overrides every pill to
  30. * "sample data" on a fixture snapshot, so a fixture never renders `live` pills
  31. * over frozen data. These tests are about which live state the
  32. * Fathom pill reaches, so they have to be on the live side of that override.
  33. */
  34. const snap: Snapshot = { ...FIXTURE_SNAPSHOT, mode: 'live', fixtureCapturedAt: null }
  35. const withSources = (fathomSources: FathomSourceStatus[]): Snapshot => ({ ...snap, fathomSources })
  36. const fathomPill = (s: Snapshot) => sourceStatuses(s).find((r) => r.name === 'Fathom')!
  37. const DARK_INES: FathomSourceStatus = {
  38. owner: 'Ines Carvalho',
  39. ok: false,
  40. error: 'HTTP 401',
  41. returned: 0,
  42. truncated: false,
  43. }
  44. describe('the config still says what this suite assumes', () => {
  45. it('lists Ines and Theo alongside Sam', () => {
  46. const owners = FATHOM_KEYS.map((k) => k.owner)
  47. expect(owners).toContain('Sam Whitaker')
  48. expect(owners).toContain('Ines Carvalho')
  49. expect(owners).toContain('Theo Bassett')
  50. })
  51. it('is a hand-written list, not a loop over the roster', () => {
  52. // Four people have handed over no key. If this ever equals the roster
  53. // length, somebody has generated the list and the deliberate gap is gone.
  54. expect(FATHOM_KEYS.length).toBe(3)
  55. })
  56. it('names an environment variable per owner and never an inline secret', () => {
  57. for (const k of FATHOM_KEYS) {
  58. expect(k.secret).toMatch(/^FATHOM_KEY_[A-Z]+$/)
  59. }
  60. })
  61. })
  62. describe('every key clean', () => {
  63. it('reads live and names the keys it read', () => {
  64. const pill = fathomPill(snap)
  65. expect(pill.status).toBe('live')
  66. expect(pill.tone).toBe('live')
  67. expect(pill.note).toContain('Ines Carvalho')
  68. expect(pill.note).toContain('Theo Bassett')
  69. })
  70. it('says a person at zero meetings is a floor, not a fact', () => {
  71. // A gap that stands in for knowledge is surfaced every run (rule 2).
  72. expect(fathomPill(snap).note).toMatch(/floor, not a fact/)
  73. })
  74. })
  75. describe('one dark key degrades the source without erasing it', () => {
  76. const partial = withSources([
  77. { owner: 'Sam Whitaker', ok: true, error: null, returned: 8, truncated: false },
  78. DARK_INES,
  79. { owner: 'Theo Bassett', ok: true, error: null, returned: 2, truncated: false },
  80. ])
  81. it('is partial, not live and not failed', () => {
  82. expect(fathomPill(partial).status).toBe('partial')
  83. expect(fathomPill(partial).tone).toBe('degraded')
  84. })
  85. it('names the owner whose key failed, and the failure verbatim', () => {
  86. const note = fathomPill(partial).note
  87. expect(note).toContain('Ines Carvalho')
  88. expect(note).toContain('HTTP 401')
  89. })
  90. it('states no cause for the failure', () => {
  91. // The 401 handler once said a token had "most likely expired" about a token
  92. // with eleven months left. Report the status; do not pick a reason.
  93. expect(fathomPill(partial).note).not.toMatch(/expired|revoked|most likely/i)
  94. })
  95. it('keeps every meeting the working keys returned', () => {
  96. // The whole point. Two keys' data must not be discarded to report a third.
  97. expect(partial.meetings.ok).toBe(true)
  98. expect(partial.meetings.data).toEqual(snap.meetings.data)
  99. })
  100. })
  101. describe('a truncated sweep is reported, not silently short', () => {
  102. it('reads partial and says the window is incomplete', () => {
  103. // Keep what was fetched AND say it was capped.
  104. const capped = withSources([
  105. { owner: 'Sam Whitaker', ok: true, error: null, returned: 400, truncated: true },
  106. { owner: 'Ines Carvalho', ok: true, error: null, returned: 3, truncated: false },
  107. { owner: 'Theo Bassett', ok: true, error: null, returned: 2, truncated: false },
  108. ])
  109. expect(fathomPill(capped).status).toBe('partial')
  110. expect(fathomPill(capped).note).toMatch(/page cap/)
  111. expect(fathomPill(capped).note).toContain('Sam Whitaker')
  112. })
  113. })
  114. describe("a person's own card says a key is dark", () => {
  115. const partial = withSources([
  116. { owner: 'Sam Whitaker', ok: true, error: null, returned: 8, truncated: false },
  117. DARK_INES,
  118. { owner: 'Theo Bassett', ok: true, error: null, returned: 2, truncated: false },
  119. ])
  120. const viewFor = (s: Snapshot, name: string) =>
  121. personViews(peopleBoard(s), s.tasks.data).find((v) => v.name === name)!
  122. it('sets commitmentsPartial when a key failed', () => {
  123. const d = myDay(partial, viewFor(partial, 'Sam Whitaker'), viewerFor('hello@meridiandisplay.example'))
  124. expect(d.commitmentsTracked).toBe(true)
  125. expect(d.commitmentsPartial).toContain('Ines Carvalho')
  126. })
  127. it('leaves it null when every key is clean', () => {
  128. const d = myDay(snap, viewFor(snap, 'Sam Whitaker'), viewerFor('hello@meridiandisplay.example'))
  129. expect(d.commitmentsPartial).toBeNull()
  130. })
  131. it('reaches the rendered page, not just the view-model', () => {
  132. // A card that is short by one key looks exactly like a light fortnight, and
  133. // nobody scrolls to the source band to find out which. The finished document
  134. // is the only thing that proves the sentence is actually on it.
  135. const html = render(
  136. partial,
  137. headline(partial),
  138. [],
  139. '2026-08-12',
  140. null,
  141. viewerFor('hello@meridiandisplay.example'),
  142. )
  143. expect(html).toContain('Ines Carvalho')
  144. expect(html).toMatch(/could not be read this run/)
  145. })
  146. })