views.test.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. import { ApolloServer } from 'apollo-server-express'
  2. import { Mongoose } from 'mongoose'
  3. import { Aggregates } from '../src/types'
  4. import { ApolloServerTestClient } from 'apollo-server-testing/dist/createTestClient'
  5. import { buildAggregates, connectMongoose, createServer } from '../src/server'
  6. import { createTestClient } from 'apollo-server-testing'
  7. import {
  8. ADD_VIDEO_VIEW,
  9. AddVideoView,
  10. AddVideoViewArgs,
  11. GET_CHANNEL_VIEWS,
  12. GET_MOST_VIEWED_CHANNELS,
  13. GET_VIDEO_VIEWS,
  14. GET_MOST_VIEWED_VIDEOS,
  15. GET_MOST_VIEWED_CATEGORIES,
  16. GetChannelViews,
  17. GetChannelViewsArgs,
  18. GetVideoViews,
  19. GetVideoViewsArgs,
  20. GetMostViewedVideosArgs,
  21. GetMostViewedChannelsArgs,
  22. GetMostViewedVideos,
  23. GetMostViewedChannels,
  24. GetMostViewedCategoriessArgs,
  25. GetMostViewedCategories,
  26. } from './queries/views'
  27. import { EntityViewsInfo } from '../src/entities/EntityViewsInfo'
  28. import { VideoEventsBucketModel } from '../src/models/VideoEvent'
  29. import { TEST_BUCKET_SIZE } from './setup'
  30. const FIRST_VIDEO_ID = '12'
  31. const SECOND_VIDEO_ID = '13'
  32. const FIRST_CHANNEL_ID = '22'
  33. const SECOND_CHANNEL_ID = '23'
  34. const FIRST_CATEGORY_ID = '32'
  35. describe('Video and channel views resolver', () => {
  36. let server: ApolloServer
  37. let mongoose: Mongoose
  38. let aggregates: Aggregates
  39. let query: ApolloServerTestClient['query']
  40. let mutate: ApolloServerTestClient['mutate']
  41. beforeEach(async () => {
  42. mongoose = await connectMongoose(process.env.MONGO_URL!)
  43. aggregates = await buildAggregates()
  44. server = await createServer(mongoose, aggregates)
  45. const testClient = createTestClient(server)
  46. query = testClient.query
  47. mutate = testClient.mutate
  48. })
  49. afterEach(async () => {
  50. await server.stop()
  51. await VideoEventsBucketModel.deleteMany({})
  52. await mongoose.disconnect()
  53. })
  54. const addVideoView = async (videoId: string, channelId: string, categoryId?: string) => {
  55. const addVideoViewResponse = await mutate<AddVideoView, AddVideoViewArgs>({
  56. mutation: ADD_VIDEO_VIEW,
  57. variables: { videoId, channelId, categoryId },
  58. })
  59. expect(addVideoViewResponse.errors).toBeUndefined()
  60. return addVideoViewResponse.data?.addVideoView
  61. }
  62. const getVideoViews = async (videoId: string) => {
  63. const videoViewsResponse = await query<GetVideoViews, GetVideoViewsArgs>({
  64. query: GET_VIDEO_VIEWS,
  65. variables: { videoId },
  66. })
  67. expect(videoViewsResponse.errors).toBeUndefined()
  68. return videoViewsResponse.data?.videoViews
  69. }
  70. const getMostViewedVideos = async (period?: number) => {
  71. const mostViewedVideosResponse = await query<GetMostViewedVideos, GetMostViewedVideosArgs>({
  72. query: GET_MOST_VIEWED_VIDEOS,
  73. variables: { period },
  74. })
  75. expect(mostViewedVideosResponse.errors).toBeUndefined()
  76. return mostViewedVideosResponse.data?.mostViewedVideos
  77. }
  78. const getChannelViews = async (channelId: string) => {
  79. const channelViewsResponse = await query<GetChannelViews, GetChannelViewsArgs>({
  80. query: GET_CHANNEL_VIEWS,
  81. variables: { channelId },
  82. })
  83. expect(channelViewsResponse.errors).toBeUndefined()
  84. return channelViewsResponse.data?.channelViews
  85. }
  86. const getMostViewedChannels = async (period?: number) => {
  87. const mostViewedChannelsResponse = await query<GetMostViewedChannels, GetMostViewedChannelsArgs>({
  88. query: GET_MOST_VIEWED_CHANNELS,
  89. variables: { period },
  90. })
  91. expect(mostViewedChannelsResponse.errors).toBeUndefined()
  92. return mostViewedChannelsResponse.data?.mostViewedChannels
  93. }
  94. const getMostViewedCategories = async (period?: number) => {
  95. const mostViewedCategoriesResponse = await query<GetMostViewedCategories, GetMostViewedCategoriessArgs>({
  96. query: GET_MOST_VIEWED_CATEGORIES,
  97. variables: { period },
  98. })
  99. expect(mostViewedCategoriesResponse.errors).toBeUndefined()
  100. return mostViewedCategoriesResponse.data?.mostViewedCategories
  101. }
  102. it('should return null for unknown video, channel and category views', async () => {
  103. const videoViews = await getVideoViews(FIRST_VIDEO_ID)
  104. const mostViewedVideos = await getMostViewedVideos()
  105. const channelViews = await getChannelViews(FIRST_CHANNEL_ID)
  106. const mostViewedChannels = await getMostViewedChannels()
  107. const mostViewedCategories = await getMostViewedCategories()
  108. expect(videoViews).toBeNull()
  109. expect(mostViewedVideos).toHaveLength(0)
  110. expect(channelViews).toBeNull()
  111. expect(mostViewedChannels).toHaveLength(0)
  112. expect(mostViewedCategories).toHaveLength(0)
  113. })
  114. it('should properly save video and channel views', async () => {
  115. const expectedVideoViews: EntityViewsInfo = {
  116. id: FIRST_VIDEO_ID,
  117. views: 1,
  118. }
  119. const expectedChannelViews: EntityViewsInfo = {
  120. id: FIRST_CHANNEL_ID,
  121. views: 1,
  122. }
  123. const expectedCategoryViews: EntityViewsInfo = {
  124. id: FIRST_CATEGORY_ID,
  125. views: 1,
  126. }
  127. const checkViews = async () => {
  128. const videoViews = await getVideoViews(FIRST_VIDEO_ID)
  129. const mostViewedVideos = await getMostViewedVideos()
  130. const channelViews = await getChannelViews(FIRST_CHANNEL_ID)
  131. const mostViewedChannels = await getMostViewedChannels()
  132. const mostViewedCategories = await getMostViewedCategories()
  133. expect(videoViews).toEqual(expectedVideoViews)
  134. expect(mostViewedVideos).toEqual([expectedVideoViews])
  135. expect(channelViews).toEqual(expectedChannelViews)
  136. expect(mostViewedChannels).toEqual([expectedChannelViews])
  137. expect(mostViewedCategories).toEqual([expectedCategoryViews])
  138. }
  139. let addVideoViewData = await addVideoView(FIRST_VIDEO_ID, FIRST_CHANNEL_ID, FIRST_CATEGORY_ID)
  140. expect(addVideoViewData).toEqual(expectedVideoViews)
  141. await checkViews()
  142. expectedVideoViews.views++
  143. expectedChannelViews.views++
  144. expectedCategoryViews.views++
  145. addVideoViewData = await addVideoView(FIRST_VIDEO_ID, FIRST_CHANNEL_ID, FIRST_CATEGORY_ID)
  146. expect(addVideoViewData).toEqual(expectedVideoViews)
  147. await checkViews()
  148. })
  149. it('should distinct views of separate videos', async () => {
  150. const expectedFirstVideoViews: EntityViewsInfo = {
  151. id: FIRST_VIDEO_ID,
  152. views: 1,
  153. }
  154. const expectedSecondVideoViews: EntityViewsInfo = {
  155. id: SECOND_VIDEO_ID,
  156. views: 1,
  157. }
  158. const addFirstVideoViewData = await addVideoView(FIRST_VIDEO_ID, FIRST_CHANNEL_ID)
  159. const addSecondVideoViewData = await addVideoView(SECOND_VIDEO_ID, FIRST_CHANNEL_ID)
  160. expect(addFirstVideoViewData).toEqual(expectedFirstVideoViews)
  161. expect(addSecondVideoViewData).toEqual(expectedSecondVideoViews)
  162. expectedFirstVideoViews.views++
  163. await addVideoView(FIRST_VIDEO_ID, FIRST_CHANNEL_ID)
  164. const firstVideoViews = await getVideoViews(FIRST_VIDEO_ID)
  165. const secondVideoViews = await getVideoViews(SECOND_VIDEO_ID)
  166. const mostViewedVideos = await getMostViewedVideos()
  167. expect(firstVideoViews).toEqual(expectedFirstVideoViews)
  168. expect(secondVideoViews).toEqual(expectedSecondVideoViews)
  169. expect(mostViewedVideos).toEqual([expectedFirstVideoViews, expectedSecondVideoViews])
  170. })
  171. it('should distinct views of separate channels', async () => {
  172. const expectedFirstChanelViews: EntityViewsInfo = {
  173. id: FIRST_CHANNEL_ID,
  174. views: 1,
  175. }
  176. const expectedSecondChannelViews: EntityViewsInfo = {
  177. id: SECOND_CHANNEL_ID,
  178. views: 1,
  179. }
  180. await addVideoView(FIRST_VIDEO_ID, FIRST_CHANNEL_ID)
  181. await addVideoView(SECOND_VIDEO_ID, SECOND_CHANNEL_ID)
  182. const firstChannelViews = await getChannelViews(FIRST_CHANNEL_ID)
  183. const secondChannelViews = await getChannelViews(SECOND_CHANNEL_ID)
  184. const mostViewedChannels = await getMostViewedChannels()
  185. expect(firstChannelViews).toEqual(expectedFirstChanelViews)
  186. expect(secondChannelViews).toEqual(expectedSecondChannelViews)
  187. expect(mostViewedChannels).toEqual([expectedFirstChanelViews, expectedSecondChannelViews])
  188. })
  189. it('should properly aggregate views of a channel', async () => {
  190. const expectedChannelViews: EntityViewsInfo = {
  191. id: FIRST_CHANNEL_ID,
  192. views: 2,
  193. }
  194. await addVideoView(FIRST_VIDEO_ID, FIRST_CHANNEL_ID)
  195. await addVideoView(SECOND_VIDEO_ID, FIRST_CHANNEL_ID)
  196. const channelViews = await getChannelViews(FIRST_CHANNEL_ID)
  197. const mostViewedChannels = await getMostViewedChannels()
  198. expect(channelViews).toEqual(expectedChannelViews)
  199. expect(mostViewedChannels).toEqual([expectedChannelViews])
  200. })
  201. it('should properly aggregate views of a category', async () => {
  202. const expectedChannelViews: EntityViewsInfo = {
  203. id: FIRST_CATEGORY_ID,
  204. views: 2,
  205. }
  206. await addVideoView(FIRST_VIDEO_ID, FIRST_CHANNEL_ID, FIRST_CATEGORY_ID)
  207. await addVideoView(SECOND_VIDEO_ID, FIRST_CHANNEL_ID, FIRST_CATEGORY_ID)
  208. const mostViewedCategories = await getMostViewedCategories()
  209. expect(mostViewedCategories).toEqual([expectedChannelViews])
  210. })
  211. it('should properly rebuild the aggregate', async () => {
  212. const expectedFirstVideoViews: EntityViewsInfo = {
  213. id: FIRST_VIDEO_ID,
  214. views: 3,
  215. }
  216. const expectedSecondVideoViews: EntityViewsInfo = {
  217. id: SECOND_VIDEO_ID,
  218. views: 4,
  219. }
  220. const expectedChannelViews: EntityViewsInfo = {
  221. id: FIRST_CHANNEL_ID,
  222. views: 7,
  223. }
  224. const expectedCategoryViews: EntityViewsInfo = {
  225. id: FIRST_CATEGORY_ID,
  226. views: 7,
  227. }
  228. const checkViews = async () => {
  229. const firstVideoViews = await getVideoViews(FIRST_VIDEO_ID)
  230. const secondVideoViews = await getVideoViews(SECOND_VIDEO_ID)
  231. const channelViews = await getChannelViews(FIRST_CHANNEL_ID)
  232. const mostViewedVideos = await getMostViewedVideos()
  233. const mostViewedChannels = await getMostViewedChannels()
  234. const mostViewedCategories = await getMostViewedCategories()
  235. expect(firstVideoViews).toEqual(expectedFirstVideoViews)
  236. expect(secondVideoViews).toEqual(expectedSecondVideoViews)
  237. expect(mostViewedVideos).toEqual([expectedSecondVideoViews, expectedFirstVideoViews])
  238. expect(channelViews).toEqual(expectedChannelViews)
  239. expect(mostViewedChannels).toEqual([expectedChannelViews])
  240. expect(mostViewedCategories).toEqual([expectedCategoryViews])
  241. }
  242. await addVideoView(FIRST_VIDEO_ID, FIRST_CHANNEL_ID, FIRST_CATEGORY_ID)
  243. await addVideoView(FIRST_VIDEO_ID, FIRST_CHANNEL_ID, FIRST_CATEGORY_ID)
  244. await addVideoView(FIRST_VIDEO_ID, FIRST_CHANNEL_ID, FIRST_CATEGORY_ID)
  245. await addVideoView(SECOND_VIDEO_ID, FIRST_CHANNEL_ID, FIRST_CATEGORY_ID)
  246. await addVideoView(SECOND_VIDEO_ID, FIRST_CHANNEL_ID, FIRST_CATEGORY_ID)
  247. await addVideoView(SECOND_VIDEO_ID, FIRST_CHANNEL_ID, FIRST_CATEGORY_ID)
  248. await addVideoView(SECOND_VIDEO_ID, FIRST_CHANNEL_ID, FIRST_CATEGORY_ID)
  249. await checkViews()
  250. await server.stop()
  251. aggregates = await buildAggregates()
  252. server = await createServer(mongoose, aggregates)
  253. const testClient = createTestClient(server)
  254. query = testClient.query
  255. mutate = testClient.mutate
  256. await checkViews()
  257. })
  258. it('should properly handle saving events across buckets', async () => {
  259. const eventsCount = TEST_BUCKET_SIZE * 2 + 1
  260. const expectedVideoViews: EntityViewsInfo = {
  261. id: FIRST_VIDEO_ID,
  262. views: eventsCount,
  263. }
  264. for (let i = 0; i < eventsCount; i++) {
  265. await addVideoView(FIRST_VIDEO_ID, FIRST_CHANNEL_ID)
  266. }
  267. const videoViews = await getVideoViews(FIRST_VIDEO_ID)
  268. const mostViewedVideos = await getMostViewedVideos()
  269. expect(videoViews).toEqual(expectedVideoViews)
  270. expect(mostViewedVideos).toEqual([expectedVideoViews])
  271. })
  272. })