views.test.ts 15 KB

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