pushEventAdapter.test.ts 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. import { mkdtempSync, rmSync } from 'node:fs'
  2. import { tmpdir } from 'node:os'
  3. import { join } from 'node:path'
  4. import { afterEach, describe, expect, it } from 'vitest'
  5. import { PushCoordinator } from '../../src/server/pushCoordinator.js'
  6. import { PushCoordinatorEventAdapter } from '../../src/server/pushEventAdapter.js'
  7. import type { APNsRequest } from '../../src/server/apns.js'
  8. const roots: string[] = []
  9. afterEach(() => {
  10. for (const home of roots.splice(0)) rmSync(home, { recursive: true, force: true })
  11. })
  12. function fixture() {
  13. const home = mkdtempSync(join(tmpdir(), 'yaoyao-web-push-'))
  14. roots.push(home)
  15. const requests: APNsRequest[] = []
  16. const push = new PushCoordinator({
  17. home,
  18. autoFlush: false,
  19. apns: {
  20. keyFile: join(home, 'unused.p8'),
  21. keyId: 'KEY123',
  22. teamId: 'TEAM123',
  23. topic: 'cn.samien.yaoyao.hermes',
  24. },
  25. provider: {
  26. send: async (request) => {
  27. requests.push(request)
  28. return { disposition: 'success', status: 200 }
  29. },
  30. },
  31. })
  32. push.registerInstallation({
  33. userId: 'owner',
  34. installationId: 'phone',
  35. clientAccountId: 'account',
  36. deviceToken: 'ab'.repeat(32),
  37. environment: 'development',
  38. })
  39. return { push, requests, adapter: new PushCoordinatorEventAdapter(push) }
  40. }
  41. describe('Web-owned push delivery', () => {
  42. it('deduplicates workspace completion notifications and retains application navigation', async () => {
  43. const { push, requests, adapter } = fixture()
  44. push.setGroupSubscription('owner', 'room', true, 0)
  45. const candidate = {
  46. kind: 'group.message.completed' as const,
  47. eventID: 'workspace:run',
  48. localUserID: 'owner',
  49. roomID: 'room',
  50. title: '团队',
  51. body: '已完成',
  52. collapseID: 'workspace:room',
  53. data: { workspace: '1', conversationId: 'room' },
  54. }
  55. expect(adapter.enqueueNotification(candidate)).toBe('enqueued')
  56. expect(adapter.enqueueNotification(candidate)).toBe('duplicate')
  57. await push.flushDue()
  58. expect(requests).toHaveLength(1)
  59. expect(requests[0]!.payload).toMatchObject({ workspace: '1', conversationId: 'room' })
  60. push.close()
  61. })
  62. it('does not deliver group events to an unsubscribed account', () => {
  63. const { push, adapter } = fixture()
  64. expect(
  65. adapter.enqueueNotification({
  66. kind: 'group.message.completed',
  67. eventID: 'workspace:run',
  68. localUserID: 'owner',
  69. roomID: 'other-room',
  70. title: '团队',
  71. body: '结果',
  72. collapseID: 'workspace:other',
  73. data: { conversationId: 'other-room' },
  74. }),
  75. ).toBe('ignored')
  76. expect(push.status().pendingCount).toBe(0)
  77. push.close()
  78. })
  79. })