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

roster.test.ts

Language TypeScript3.9 KB100 lines

packages/ninety-client/test/roster.test.ts

  1. import { describe, expect, it, vi } from 'vitest'
  2. import { HttpClient } from '../src/http.js'
  3. import { Roster } from '../src/roster.js'
  4. import { NinetyAuthError } from '../src/errors.js'
  5. const json = (body: unknown, status = 200) =>
  6. new Response(JSON.stringify(body), { status, headers: { 'Content-Type': 'application/json' } })
  7. function http(fetchImpl: typeof fetch) {
  8. return new HttpClient({ token: 't', fetchImpl, sleep: async () => {}, rps: 1000 })
  9. }
  10. describe('Roster', () => {
  11. it('resolves a user id to a name', async () => {
  12. const fetchImpl = (async () => json({ _id: 'u1', firstName: 'Marco' })) as unknown as typeof fetch
  13. const r = new Roster(http(fetchImpl))
  14. await r.resolveMany(['u1'])
  15. expect(r.lookup('u1').name).toBe('Marco')
  16. })
  17. it('joins first and last name', async () => {
  18. const fetchImpl = (async () =>
  19. json({ _id: 'u2', firstName: 'Hazel', lastName: 'Okafor' })) as unknown as typeof fetch
  20. const r = new Roster(http(fetchImpl))
  21. await r.resolveMany(['u2'])
  22. expect(r.lookup('u2').name).toBe('Hazel Okafor')
  23. })
  24. it('returns "unknown user" rather than the raw id when resolution fails', async () => {
  25. const fetchImpl = (async () => json({}, 404)) as unknown as typeof fetch
  26. const r = new Roster(http(fetchImpl))
  27. await r.resolveMany(['ghost'])
  28. expect(r.lookup('ghost').name).toBe('unknown user')
  29. expect(r.unresolved).toContain('ghost')
  30. })
  31. it('reports "unassigned" for a null owner', () => {
  32. const r = new Roster(http((async () => json({})) as unknown as typeof fetch))
  33. expect(r.lookup(null).name).toBe('unassigned')
  34. })
  35. it('does not re-fetch an id it already failed on', async () => {
  36. let calls = 0
  37. const fetchImpl = (async () => {
  38. calls++
  39. return json({}, 404)
  40. }) as unknown as typeof fetch
  41. const r = new Roster(http(fetchImpl))
  42. await r.resolveMany(['ghost'])
  43. await r.resolveMany(['ghost'])
  44. expect(calls).toBe(1)
  45. })
  46. it('dedupes concurrent requests for the same id', async () => {
  47. const fetchImpl = vi.fn(async () => json({ _id: 'u1', fullName: 'Marco' })) as unknown as typeof fetch
  48. const r = new Roster(http(fetchImpl))
  49. await Promise.all([r.resolveMany(['u1']), r.resolveMany(['u1']), r.resolveMany(['u1', 'u1'])])
  50. expect(fetchImpl).toHaveBeenCalledTimes(1)
  51. })
  52. it('never overwrites a pinned seed entry', async () => {
  53. const fetchImpl = (async () => json({ _id: 'u1', fullName: 'Wrong Name' })) as unknown as typeof fetch
  54. const r = new Roster(http(fetchImpl), { seed: [{ id: 'u1', name: 'Marco' }] })
  55. await r.resolveMany(['u1'])
  56. expect(r.lookup('u1').name).toBe('Marco')
  57. })
  58. it('keeps seeded people visible even with no fetched data', () => {
  59. const r = new Roster(http((async () => json({})) as unknown as typeof fetch), {
  60. seed: [
  61. { id: 'u1', name: 'Marco' },
  62. { id: 'u2', name: 'Rafael' },
  63. ],
  64. })
  65. expect(r.toEntries()).toHaveLength(2)
  66. })
  67. it('finds people by partial name', () => {
  68. const r = new Roster(http((async () => json({})) as unknown as typeof fetch), {
  69. seed: [
  70. { id: 'u1', name: 'Hazel Okafor' },
  71. { id: 'u2', name: 'Nora Lindqvist' },
  72. ],
  73. })
  74. expect(r.find('haz').map((e) => e.name)).toEqual(['Hazel Okafor'])
  75. expect(r.find('or')).toHaveLength(2)
  76. })
  77. it('propagates auth failures instead of silently marking users unresolved', async () => {
  78. // The point of this test is PROPAGATION: a 401 must not be absorbed into
  79. // "unresolved", because then every name silently becomes "unknown user" and
  80. // the board looks merely incomplete rather than broken. Assert the error
  81. // type, not the wording; matching on /expired/ is what kept a wrong
  82. // diagnosis pinned in place.
  83. const fetchImpl = (async () => json({}, 401)) as unknown as typeof fetch
  84. const r = new Roster(http(fetchImpl))
  85. await expect(r.resolveMany(['u1'])).rejects.toBeInstanceOf(NinetyAuthError)
  86. expect(r.unresolved).toEqual([])
  87. })
  88. })