sseSource.test.ts 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. import { describe, test, it, expect, beforeEach, afterEach, vi } from 'vitest'
  2. import { EventSourcePolyfill } from 'event-source-polyfill'
  3. import SseSource from '~/services/sse/sseSource'
  4. import type { MercureEntityUpdate } from '~/types/interfaces'
  5. class TestableSseSource extends SseSource {
  6. public getUrl() {
  7. return this.url
  8. }
  9. public getOnOpen() {
  10. return this.onOpen
  11. }
  12. public getOnMessage() {
  13. return this.onMessage
  14. }
  15. public getOnClose() {
  16. return this.onClose
  17. }
  18. public getWithCredentials() {
  19. return this.withCredentials
  20. }
  21. public override createEventSource(
  22. url: string,
  23. withCredentials: boolean,
  24. ): EventSourcePolyfill {
  25. return super.createEventSource(url, withCredentials)
  26. }
  27. public setEventSource(eventSource: EventSourcePolyfill) {
  28. this.eventSource = eventSource
  29. }
  30. }
  31. let mercureUrl: string
  32. let topic: string
  33. let onOpen: () => any
  34. let onMessage: (data: MercureEntityUpdate) => any
  35. let onClose: () => any
  36. let sseSource: TestableSseSource
  37. const initConsoleLog = console.log
  38. beforeEach(() => {
  39. mercureUrl = 'https://my.mercure.com'
  40. topic = 'mytopic'
  41. onOpen = () => 'opened'
  42. onMessage = (data: MercureEntityUpdate) => 'message'
  43. onClose = () => 'closed'
  44. sseSource = new TestableSseSource(
  45. mercureUrl,
  46. topic,
  47. onOpen,
  48. onMessage,
  49. onClose,
  50. false,
  51. )
  52. })
  53. afterEach(() => {
  54. console.log = initConsoleLog
  55. vi.restoreAllMocks()
  56. })
  57. describe('test constructor', () => {
  58. test('with all params', () => {
  59. expect(sseSource.getUrl().toString()).toEqual(
  60. 'https://my.mercure.com/?topic=mytopic',
  61. )
  62. expect(sseSource.getOnOpen()).toEqual(onOpen)
  63. expect(sseSource.getOnMessage()).toEqual(onMessage)
  64. expect(sseSource.getOnClose()).toEqual(onClose)
  65. expect(sseSource.getWithCredentials()).toEqual(false)
  66. })
  67. })
  68. describe('createEventSource', () => {
  69. test('simple call', () => {
  70. const eventSource = sseSource.createEventSource(mercureUrl, false)
  71. expect(eventSource.readyState).toEqual(0)
  72. expect(eventSource.url).toEqual(mercureUrl)
  73. expect(eventSource.withCredentials).toEqual(false)
  74. })
  75. })
  76. describe('isConnected', () => {
  77. test('no event source', () => {
  78. expect(sseSource.isConnected()).toEqual(false)
  79. })
  80. test('got an event source, but it is not open', () => {
  81. const eventSource = sseSource.createEventSource(mercureUrl, true)
  82. sseSource.setEventSource(eventSource)
  83. expect(sseSource.isConnected()).toEqual(false)
  84. })
  85. test('got an open event source', () => {
  86. // @ts-ignore
  87. const eventSource = vi.fn() as EventSourcePolyfill
  88. // @ts-ignore
  89. // noinspection JSConstantReassignment
  90. eventSource.readyState = EventSourcePolyfill.OPEN
  91. sseSource.setEventSource(eventSource)
  92. expect(sseSource.isConnected()).toEqual(true)
  93. })
  94. })
  95. describe('subscribe', () => {
  96. test('already connected', () => {
  97. sseSource.isConnected = vi.fn(() => true)
  98. expect(() => sseSource.subscribe()).toThrowError(
  99. 'SSE - Already subscribed to this event source',
  100. )
  101. })
  102. test('is server side', () => {
  103. // Mock the SseSource.prototype.subscribe method to simulate server-side behavior
  104. const originalSubscribe = SseSource.prototype.subscribe
  105. SseSource.prototype.subscribe = function () {
  106. // Simulate the server-side check
  107. throw new Error('SSE - Cannot subscribe on server side')
  108. }
  109. // Verify that the subscribe method throws an error
  110. expect(() => sseSource.subscribe()).toThrowError(
  111. 'SSE - Cannot subscribe on server side',
  112. )
  113. // Restore the original method
  114. SseSource.prototype.subscribe = originalSubscribe
  115. })
  116. test('successful subscription', () => {
  117. onOpen = vi.fn(() => null)
  118. onMessage = vi.fn((data: MercureEntityUpdate) => null)
  119. sseSource = new TestableSseSource(
  120. mercureUrl,
  121. topic,
  122. onOpen,
  123. onMessage,
  124. onClose,
  125. )
  126. const dummyEventSource = new EventSourcePolyfill('https://my.mercure.com', {
  127. withCredentials: true,
  128. })
  129. sseSource.createEventSource = vi.fn(
  130. (url: string, withCredentials: boolean) => {
  131. return dummyEventSource
  132. },
  133. )
  134. console.log = vi.fn()
  135. console.error = vi.fn()
  136. sseSource.subscribe()
  137. expect(sseSource.createEventSource).toHaveBeenCalled()
  138. // @ts-ignore
  139. dummyEventSource.onopen()
  140. expect(onOpen).toHaveBeenCalled()
  141. expect(console.log).toHaveBeenCalledWith('SSE - Listening for events...')
  142. // @ts-ignore
  143. dummyEventSource.onmessage({ data: '1' })
  144. expect(onMessage).toHaveBeenCalledWith(1)
  145. // @ts-ignore
  146. const event = { target: { readyState: EventSourcePolyfill.CLOSED } }
  147. dummyEventSource.close = vi.fn()
  148. // @ts-ignore
  149. dummyEventSource.onerror(event)
  150. expect(console.log).toHaveBeenCalledWith('SSE closed')
  151. expect(dummyEventSource.close).toHaveBeenCalled()
  152. })
  153. })
  154. describe('unsubscribe', () => {
  155. test('if no event source, does nothing', () => {
  156. onClose = vi.fn(() => null)
  157. sseSource = new TestableSseSource(
  158. mercureUrl,
  159. topic,
  160. onOpen,
  161. onMessage,
  162. onClose,
  163. )
  164. sseSource.unsubscribe()
  165. expect(onClose).toHaveBeenCalledTimes(0)
  166. })
  167. test('if event source is not opened, does nothing', () => {
  168. onClose = vi.fn(() => null)
  169. sseSource = new TestableSseSource(
  170. mercureUrl,
  171. topic,
  172. onOpen,
  173. onMessage,
  174. onClose,
  175. )
  176. // @ts-ignore
  177. const eventSource = vi.fn() as EventSourcePolyfill
  178. // @ts-ignore
  179. // noinspection JSConstantReassignment
  180. eventSource.readyState = EventSourcePolyfill.CLOSED
  181. sseSource.setEventSource(eventSource)
  182. sseSource.unsubscribe()
  183. expect(onClose).toHaveBeenCalledTimes(0)
  184. })
  185. test('has an open subscription', () => {
  186. onClose = vi.fn(() => null)
  187. sseSource = new TestableSseSource(
  188. mercureUrl,
  189. topic,
  190. onOpen,
  191. onMessage,
  192. onClose,
  193. )
  194. // @ts-ignore
  195. const eventSource = vi.fn() as EventSourcePolyfill
  196. // @ts-ignore
  197. // noinspection JSConstantReassignment
  198. eventSource.readyState = EventSourcePolyfill.OPEN
  199. eventSource.close = vi.fn(() => null)
  200. sseSource.setEventSource(eventSource)
  201. console.log = vi.fn()
  202. sseSource.unsubscribe()
  203. expect(eventSource.close).toHaveBeenCalled()
  204. expect(onClose).toHaveBeenCalled()
  205. expect(console.log).toHaveBeenCalledWith('SSE - Subscription closed')
  206. })
  207. })