fathom-attribution.test.ts
Language TypeScript14.1 KB308 lines
/*** Fathom attribution, pinned against the shapes the live API sends.** Every label and address in this file has the shape of a real* `GET /meetings?include_action_items=true` response. Each case below is one* the frozen seed does not exercise:** 1. A roster person must never sit in EXTERNAL_LABELS, or their own action* items resolve to "external" and reach nobody's view.* 2. `Marco Ibáñez Meridian Display` arrives with a null email. The* leading-token fallback splits on | - @ and that string has none of them,* so without a name-map entry it resolves to `unknown`.* 3. Unowned has two shapes: the bare "Meridian Display" and a fully null* `{name: null, email: null}`. Both mean nobody was named.* 4. Two people arrive under a second address the roster does not carry:* nora@ beside nora.l@, theo@ beside frontdesk@.** The through-line: a display name is a guess and an email is a key.*/import { describe, expect, it } from 'vitest'import {attributeFathomRef,isUnownedRef,participantsOf,EXTERNAL_LABELS,KNOWN_NON_PERSON_ADDRESSES,ROUTED_ADDRESSES,} from '../../../config/fathom-people.js'describe('email beats the display name', () => {it('resolves projects@ to Ines even though the name says "Lena K"', () => {// The case that justifies the order. An invitee with name "Lena K Meridian// Display" on projects@meridiandisplay.example, which the roster maps to Ines.// Matching the address settles it; matching the name would mean deciding// whether "Lena K" is Ines, which this code does not get to decide.const got = attributeFathomRef({ name: 'Lena K Meridian Display', email: 'projects@meridiandisplay.example' })expect(got.kind).toBe('roster')if (got.kind === 'roster') expect(got.person.name).toBe('Ines Carvalho')})it('is case-insensitive about the address', () => {const got = attributeFathomRef({ name: null, email: 'HELLO@MeridianDisplay.example' })expect(got.kind).toBe('roster')if (got.kind === 'roster') expect(got.person.name).toBe('Sam Whitaker')})})describe('the owner is on the roster, not external', () => {it.each([['nora.l@meridiandisplay.example', 'Nora Lindqvist @ Meridian Display'],['nora@meridiandisplay.example', 'Nora L'],])('resolves %s to the roster', (email, name) => {// Both addresses appeared live on the same day. Only nora.l@ is on the// roster, so nora@ has to come from the Fathom alias table.const got = attributeFathomRef({ name, email })expect(got.kind).toBe('roster')if (got.kind === 'roster') expect(got.person.name).toBe('Nora Lindqvist')})it('resolves her by label alone when the email is null', () => {const got = attributeFathomRef({ name: 'Nora L', email: null })expect(got.kind).toBe('roster')})it('no longer lists her as an external label', () => {for (const label of ['nora l', 'nora lindqvist', 'nora lindqvist @ meridian display']) {expect(EXTERNAL_LABELS.has(label)).toBe(false)}})})describe('null-email labels still land on a person', () => {it('attributes "Marco Ibáñez Meridian Display" with no email', () => {// The exact live string. Before the fix this was `unknown`.const got = attributeFathomRef({ name: 'Marco Ibáñez Meridian Display', email: null })expect(got.kind).toBe('roster')if (got.kind === 'roster') expect(got.person.name).toBe('Marco Ibáñez')})it.each([['Rosa Galván', 'Rosa Galván'],['Rosa', 'Rosa Galván'],['Theo | MeridianDisplay', 'Theo Bassett'],])('attributes %j with no email', (label, expected) => {const got = attributeFathomRef({ name: label, email: null })expect(got.kind).toBe('roster')if (got.kind === 'roster') expect(got.person.name).toBe(expected)})it('still maps a departed person’s label, and lands off the roster', () => {// Hazel has left and Rosa holds her seat. Her spellings stay in// config/fathom-people.ts on purpose: her meetings are in Fathom forever,// and a recording that resolved to a person yesterday must not resolve to// a bare label today. The destination is a name the roster no longer// carries, and that is the honest answer, not a bug to paper over.const got = attributeFathomRef({ name: 'Hazel O', email: null })expect(got.kind).not.toBe('roster')})it("keeps Theo's second address on the roster", () => {const got = attributeFathomRef({ name: 'Office MeridianDisplay', email: 'theo@meridiandisplay.example' })expect(got.kind).toBe('roster')if (got.kind === 'roster') expect(got.person.name).toBe('Theo Bassett')})})describe('unowned means nobody, in either shape', () => {it('treats the bare company name as unowned', () => {expect(isUnownedRef({ name: 'Meridian Display', email: null })).toBe(true)expect(attributeFathomRef({ name: 'Meridian Display', email: null }).kind).toBe('unowned')})it('treats a fully null assignee as unowned', () => {// Seen once live. A different shape, the same fact.expect(isUnownedRef({ name: null, email: null })).toBe(true)expect(isUnownedRef(null)).toBe(true)})it('does NOT treat a named person as unowned', () => {expect(isUnownedRef({ name: 'Hazel O', email: 'accounts@meridiandisplay.example' })).toBe(false)})it('never turns the company name into a person', () => {// "Meridian Display" as a designer would be the Plinth `--` bug in another costume.const got = attributeFathomRef({ name: 'Meridian Display', email: null })expect(got.kind).not.toBe('roster')})})describe('an outsider holding an action item is recognised, not hand-listed', () => {// A prospect held one action item in the live 14-day window and is in no// map. Adding every client contact to EXTERNAL_LABELS by hand builds in the// lag every hand-kept table has, so Fathom's own is_external flag on the// meeting's invitee list settles it instead.const invitees = [{ name: 'Peter Lang', email: 'peter@example.com', is_external: true },{ name: 'Sam Whitaker - Meridian Display', email: 'hello@meridiandisplay.example', is_external: false },]it('classifies them external using the invitee flag', () => {const got = attributeFathomRef({ name: 'Peter Lang', email: 'peter@example.com' }, invitees)expect(got.kind).toBe('external')})it('matches on the display name when the assignee has no email', () => {const got = attributeFathomRef({ name: 'Peter Lang', email: null }, invitees)expect(got.kind).toBe('external')})it('stays unknown without the invitee list, rather than guessing', () => {// No evidence, no verdict. Surfaced every run.expect(attributeFathomRef({ name: 'Peter Lang', email: null }).kind).toBe('unknown')})it('never lets the flag override a roster match', () => {// A roster person mis-flagged external must still be themselves; the email// path returns before any of this.const got = attributeFathomRef({ name: 'Sam Whitaker - Meridian Display', email: 'hello@meridiandisplay.example' },[{ name: 'Sam Whitaker - Meridian Display', email: 'hello@meridiandisplay.example', is_external: true }],)expect(got.kind).toBe('roster')})})describe('a routed mailbox sends its tasks to a person without putting them in the room', () => {// exec@meridiandisplay.example: the executive-assistant mailbox, with no// owner at present. Its tasks route to Sam, as a holding decision.const EA = { name: 'EA', email: 'exec@meridiandisplay.example' }it('is declared as routed, not as a person', () => {expect(ROUTED_ADDRESSES['exec@meridiandisplay.example']).toBe('Sam Whitaker')})it('attributes an action item assigned to the mailbox to Sam', () => {const got = attributeFathomRef(EA)expect(got.kind).toBe('roster')if (got.kind === 'roster') expect(got.person.name).toBe('Sam Whitaker')})it('does NOT make Sam a participant of a meeting the mailbox attended', () => {// The point: routing an item is not being in the room. If this fails, a// routing decision has silently become a visibility grant.const { roster, acknowledged, unmatched } = participantsOf([{ ...EA, is_external: false },{ name: 'Hazel O', email: 'accounts@meridiandisplay.example', is_external: false },])// An invitee list from when accounts@ was Hazel's desk; Rosa holds it now,// so the address resolves to her. Fathom records an address// and nothing else, so a historical call cannot be told apart from a// current one: a known imprecision, written down beside the mapping in// config/fathom-people.ts rather than hidden by editing the fixture.expect(roster.map((p) => p.name)).toEqual(['Rosa Galván'])expect(roster.map((p) => p.name)).not.toContain('Sam Whitaker')expect(acknowledged).toEqual(['EA'])expect(unmatched).toEqual([])})it('never lets a routing entry override a real person\'s own address', () => {// Defensive: routing is checked after the roster lookups, so even a// mistaken entry for a real address could not re-route that person's items.const got = attributeFathomRef({ name: 'Hazel O', email: 'accounts@meridiandisplay.example' })expect(got.kind).toBe('roster')// The address wins over the label, and the address is Rosa's now; see// the note two tests above.if (got.kind === 'roster') expect(got.person.name).toBe('Rosa Galván')})})describe('participants decide who may see a meeting', () => {// The live invitee list of an internal leadership call, verbatim.const leadership = [{ name: 'Nora Lindqvist @ Meridian Display', email: 'nora.l@meridiandisplay.example', is_external: false },{ name: 'Office MeridianDisplay', email: 'frontdesk@meridiandisplay.example', is_external: false },{ name: 'Lena K Meridian Display', email: 'projects@meridiandisplay.example', is_external: false },{ name: 'Hazel O', email: 'accounts@meridiandisplay.example', is_external: false },{ name: 'Sam Whitaker - Meridian Display', email: 'hello@meridiandisplay.example', is_external: false },]it('resolves every internal invitee on a real leadership call', () => {const { roster, external, unmatched } = participantsOf(leadership)expect(roster.map((p) => p.name).sort()).toEqual(['Ines Carvalho','Nora Lindqvist',// accounts@ on this call was Hazel; the seat is Rosa's now and the// address is all Fathom gives us.'Rosa Galván','Sam Whitaker','Theo Bassett',])expect(external).toEqual([])expect(unmatched).toEqual([])})it('EXCLUDES people who were not in the room', () => {// The whole point of the feature: Marco and Rafael were not on that call,// so they must not appear as viewers of it.const names = participantsOf(leadership).roster.map((p) => p.name)expect(names).not.toContain('Marco Ibáñez')expect(names).not.toContain('Rafael Montero')})it('surfaces an internal address that matches nobody instead of dropping it', () => {// Guessing who one is would be a silent fallback; dropping it would hide someone who// can see a meeting the board believes has no such viewer. `unmatched` is// meant to be empty, so anything in it is a question for a human.const { roster, unmatched } = participantsOf([{ name: 'Someone New', email: 'newhire@meridiandisplay.example', is_external: false },])expect(roster).toEqual([])expect(unmatched).toEqual(['Someone New'])})it('does NOT make a role alias a viewer', () => {// director@ is in KNOWN_NON_PERSON_ADDRESSES: it never appears without a// Nora address beside it, owns no action items, and// carries a role-shaped display name. Mapping it to her would gain no// visibility she lacks and would hand her access to whoever the alias covers// next: a permission widening with nobody editing the access list.const { roster, unmatched, acknowledged } = participantsOf([{ name: 'Director MeridianDisplay', email: 'director@meridiandisplay.example', is_external: false },])expect(roster).toEqual([])expect(acknowledged).toEqual(['Director MeridianDisplay'])// Known, so it is not an open question.expect(unmatched).toEqual([])})it('keeps the alias out of the roster even alongside real people', () => {const { roster, acknowledged } = participantsOf([{ name: 'Director MeridianDisplay', email: 'director@meridiandisplay.example', is_external: false },{ name: 'Nora Lindqvist', email: 'nora@meridiandisplay.example', is_external: false },])expect(roster.map((p) => p.name)).toEqual(['Nora Lindqvist'])expect(acknowledged).toEqual(['Director MeridianDisplay'])})it('never resolves the alias to a person through attribution either', () => {// Belt and braces: the alias must not sneak in via the email path.expect(KNOWN_NON_PERSON_ADDRESSES.has('director@meridiandisplay.example')).toBe(true)const got = attributeFathomRef({ name: 'Director MeridianDisplay', email: 'director@meridiandisplay.example' })expect(got.kind).not.toBe('roster')})it('trusts is_external over any domain list', () => {// Northgate arrives under two domains, so a hand-kept table would miss one.const { roster, external } = participantsOf([{ name: 'Rohan Mehta', email: 'rohan.mehta@northgate.example', is_external: true },{ name: 'Priya Nair', email: 'priya.nair@northgate.example', is_external: true },{ name: 'kc@clientco.example', email: 'kc@clientco.example', is_external: true },])expect(roster).toEqual([])expect(external).toHaveLength(3)})it('does not double-count someone invited twice', () => {const { roster } = participantsOf([...leadership, leadership[0]!])expect(roster.filter((p) => p.name === 'Nora Lindqvist')).toHaveLength(1)})it('reports an empty invitee list as nobody, not as everybody', () => {// Fail closed: a meeting with no resolvable participants must expose itself// to no one rather than defaulting to the whole roster.expect(participantsOf([]).roster).toEqual([])expect(participantsOf(null).roster).toEqual([])})})