views.test.ts 13 KB

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