]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/tests/api/views/video-views-overall-stats.ts
Bumped to version v5.2.1
[github/Chocobozzz/PeerTube.git] / server / tests / api / views / video-views-overall-stats.ts
1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3 import { expect } from 'chai'
4 import { FfmpegCommand } from 'fluent-ffmpeg'
5 import { prepareViewsServers, prepareViewsVideos, processViewersStats } from '@server/tests/shared'
6 import { cleanupTests, PeerTubeServer, stopFfmpeg, waitJobs } from '@shared/server-commands'
7 import { wait } from '@shared/core-utils'
8 import { VideoStatsOverall } from '@shared/models'
9
10 /**
11 *
12 * Simulate 5 sections of viewers
13 * * user0 started and ended before start date
14 * * user1 started before start date and ended in the interval
15 * * user2 started started in the interval and ended after end date
16 * * user3 started and ended in the interval
17 * * user4 started and ended after end date
18 */
19 async function simulateComplexViewers (servers: PeerTubeServer[], videoUUID: string) {
20 const user0 = '8.8.8.8,127.0.0.1'
21 const user1 = '8.8.8.8,127.0.0.1'
22 const user2 = '8.8.8.9,127.0.0.1'
23 const user3 = '8.8.8.10,127.0.0.1'
24 const user4 = '8.8.8.11,127.0.0.1'
25
26 await servers[0].views.view({ id: videoUUID, currentTime: 0, xForwardedFor: user0 }) // User 0 starts
27 await wait(500)
28
29 await servers[0].views.view({ id: videoUUID, currentTime: 0, xForwardedFor: user1 }) // User 1 starts
30 await servers[0].views.view({ id: videoUUID, currentTime: 2, xForwardedFor: user0 }) // User 0 ends
31 await wait(500)
32
33 const startDate = new Date().toISOString()
34 await servers[0].views.view({ id: videoUUID, currentTime: 0, xForwardedFor: user2 }) // User 2 starts
35 await wait(500)
36
37 await servers[0].views.view({ id: videoUUID, currentTime: 0, xForwardedFor: user3 }) // User 3 starts
38 await wait(500)
39
40 await servers[0].views.view({ id: videoUUID, currentTime: 4, xForwardedFor: user1 }) // User 1 ends
41 await wait(500)
42
43 await servers[0].views.view({ id: videoUUID, currentTime: 3, xForwardedFor: user3 }) // User 3 ends
44 await wait(500)
45
46 const endDate = new Date().toISOString()
47 await servers[0].views.view({ id: videoUUID, currentTime: 0, xForwardedFor: user4 }) // User 4 starts
48 await servers[0].views.view({ id: videoUUID, currentTime: 5, xForwardedFor: user2 }) // User 2 ends
49 await wait(500)
50
51 await servers[0].views.view({ id: videoUUID, currentTime: 1, xForwardedFor: user4 }) // User 4 ends
52
53 await processViewersStats(servers)
54
55 return { startDate, endDate }
56 }
57
58 describe('Test views overall stats', function () {
59 let servers: PeerTubeServer[]
60
61 before(async function () {
62 this.timeout(120000)
63
64 servers = await prepareViewsServers()
65 })
66
67 describe('Test watch time stats of local videos on live and VOD', function () {
68 let vodVideoId: string
69 let liveVideoId: string
70 let command: FfmpegCommand
71
72 before(async function () {
73 this.timeout(240000);
74
75 ({ vodVideoId, liveVideoId, ffmpegCommand: command } = await prepareViewsVideos({ servers, live: true, vod: true }))
76 })
77
78 it('Should display overall stats of a video with no viewers', async function () {
79 for (const videoId of [ liveVideoId, vodVideoId ]) {
80 const stats = await servers[0].videoStats.getOverallStats({ videoId })
81 const video = await servers[0].videos.get({ id: videoId })
82
83 expect(video.views).to.equal(0)
84 expect(stats.averageWatchTime).to.equal(0)
85 expect(stats.totalWatchTime).to.equal(0)
86 expect(stats.totalViewers).to.equal(0)
87 }
88 })
89
90 it('Should display overall stats with 1 viewer below the watch time limit', async function () {
91 this.timeout(60000)
92
93 for (const videoId of [ liveVideoId, vodVideoId ]) {
94 await servers[0].views.simulateViewer({ id: videoId, currentTimes: [ 0, 1 ] })
95 }
96
97 await processViewersStats(servers)
98
99 for (const videoId of [ liveVideoId, vodVideoId ]) {
100 const stats = await servers[0].videoStats.getOverallStats({ videoId })
101 const video = await servers[0].videos.get({ id: videoId })
102
103 expect(video.views).to.equal(0)
104 expect(stats.averageWatchTime).to.equal(1)
105 expect(stats.totalWatchTime).to.equal(1)
106 expect(stats.totalViewers).to.equal(1)
107 }
108 })
109
110 it('Should display overall stats with 2 viewers', async function () {
111 this.timeout(60000)
112
113 {
114 await servers[0].views.simulateViewer({ id: vodVideoId, currentTimes: [ 0, 3 ] })
115 await servers[0].views.simulateViewer({ id: liveVideoId, currentTimes: [ 0, 35, 40 ] })
116
117 await processViewersStats(servers)
118
119 {
120 const stats = await servers[0].videoStats.getOverallStats({ videoId: vodVideoId })
121 const video = await servers[0].videos.get({ id: vodVideoId })
122
123 expect(video.views).to.equal(1)
124 expect(stats.averageWatchTime).to.equal(2)
125 expect(stats.totalWatchTime).to.equal(4)
126 expect(stats.totalViewers).to.equal(2)
127 }
128
129 {
130 const stats = await servers[0].videoStats.getOverallStats({ videoId: liveVideoId })
131 const video = await servers[0].videos.get({ id: liveVideoId })
132
133 expect(video.views).to.equal(1)
134 expect(stats.averageWatchTime).to.equal(21)
135 expect(stats.totalWatchTime).to.equal(41)
136 expect(stats.totalViewers).to.equal(2)
137 }
138 }
139 })
140
141 it('Should display overall stats with a remote viewer below the watch time limit', async function () {
142 this.timeout(60000)
143
144 for (const videoId of [ liveVideoId, vodVideoId ]) {
145 await servers[1].views.simulateViewer({ id: videoId, currentTimes: [ 0, 2 ] })
146 }
147
148 await processViewersStats(servers)
149
150 {
151 const stats = await servers[0].videoStats.getOverallStats({ videoId: vodVideoId })
152 const video = await servers[0].videos.get({ id: vodVideoId })
153
154 expect(video.views).to.equal(1)
155 expect(stats.averageWatchTime).to.equal(2)
156 expect(stats.totalWatchTime).to.equal(6)
157 expect(stats.totalViewers).to.equal(3)
158 }
159
160 {
161 const stats = await servers[0].videoStats.getOverallStats({ videoId: liveVideoId })
162 const video = await servers[0].videos.get({ id: liveVideoId })
163
164 expect(video.views).to.equal(1)
165 expect(stats.averageWatchTime).to.equal(14)
166 expect(stats.totalWatchTime).to.equal(43)
167 expect(stats.totalViewers).to.equal(3)
168 }
169 })
170
171 it('Should display overall stats with a remote viewer above the watch time limit', async function () {
172 this.timeout(60000)
173
174 await servers[1].views.simulateViewer({ id: vodVideoId, currentTimes: [ 0, 5 ] })
175 await servers[1].views.simulateViewer({ id: liveVideoId, currentTimes: [ 0, 45 ] })
176 await processViewersStats(servers)
177
178 {
179 const stats = await servers[0].videoStats.getOverallStats({ videoId: vodVideoId })
180 const video = await servers[0].videos.get({ id: vodVideoId })
181
182 expect(video.views).to.equal(2)
183 expect(stats.averageWatchTime).to.equal(3)
184 expect(stats.totalWatchTime).to.equal(11)
185 expect(stats.totalViewers).to.equal(4)
186 }
187
188 {
189 const stats = await servers[0].videoStats.getOverallStats({ videoId: liveVideoId })
190 const video = await servers[0].videos.get({ id: liveVideoId })
191
192 expect(video.views).to.equal(2)
193 expect(stats.averageWatchTime).to.equal(22)
194 expect(stats.totalWatchTime).to.equal(88)
195 expect(stats.totalViewers).to.equal(4)
196 }
197 })
198
199 it('Should filter overall stats by date', async function () {
200 this.timeout(60000)
201
202 const beforeView = new Date()
203
204 await servers[0].views.simulateViewer({ id: vodVideoId, currentTimes: [ 0, 3 ] })
205 await processViewersStats(servers)
206
207 {
208 const stats = await servers[0].videoStats.getOverallStats({ videoId: vodVideoId, startDate: beforeView.toISOString() })
209 expect(stats.averageWatchTime).to.equal(3)
210 expect(stats.totalWatchTime).to.equal(3)
211 expect(stats.totalViewers).to.equal(1)
212 }
213
214 {
215 const stats = await servers[0].videoStats.getOverallStats({ videoId: liveVideoId, endDate: beforeView.toISOString() })
216 expect(stats.averageWatchTime).to.equal(22)
217 expect(stats.totalWatchTime).to.equal(88)
218 expect(stats.totalViewers).to.equal(4)
219 }
220 })
221
222 after(async function () {
223 await stopFfmpeg(command)
224 })
225 })
226
227 describe('Test watchers peak stats of local videos on VOD', function () {
228 let videoUUID: string
229 let before2Watchers: Date
230
231 before(async function () {
232 this.timeout(240000);
233
234 ({ vodVideoId: videoUUID } = await prepareViewsVideos({ servers, live: true, vod: true }))
235 })
236
237 it('Should not have watchers peak', async function () {
238 const stats = await servers[0].videoStats.getOverallStats({ videoId: videoUUID })
239
240 expect(stats.viewersPeak).to.equal(0)
241 expect(stats.viewersPeakDate).to.be.null
242 })
243
244 it('Should have watcher peak with 1 watcher', async function () {
245 this.timeout(60000)
246
247 const before = new Date()
248 await servers[0].views.simulateViewer({ id: videoUUID, currentTimes: [ 0, 2 ] })
249 const after = new Date()
250
251 await processViewersStats(servers)
252
253 const stats = await servers[0].videoStats.getOverallStats({ videoId: videoUUID })
254
255 expect(stats.viewersPeak).to.equal(1)
256 expect(new Date(stats.viewersPeakDate)).to.be.above(before).and.below(after)
257 })
258
259 it('Should have watcher peak with 2 watchers', async function () {
260 this.timeout(60000)
261
262 before2Watchers = new Date()
263 await servers[0].views.view({ id: videoUUID, currentTime: 0 })
264 await servers[1].views.view({ id: videoUUID, currentTime: 0 })
265 await servers[0].views.view({ id: videoUUID, currentTime: 2 })
266 await servers[1].views.view({ id: videoUUID, currentTime: 2 })
267 const after = new Date()
268
269 await processViewersStats(servers)
270
271 const stats = await servers[0].videoStats.getOverallStats({ videoId: videoUUID })
272
273 expect(stats.viewersPeak).to.equal(2)
274 expect(new Date(stats.viewersPeakDate)).to.be.above(before2Watchers).and.below(after)
275 })
276
277 it('Should filter peak viewers stats by date', async function () {
278 {
279 const stats = await servers[0].videoStats.getOverallStats({ videoId: videoUUID, startDate: new Date().toISOString() })
280 expect(stats.viewersPeak).to.equal(0)
281 expect(stats.viewersPeakDate).to.not.exist
282 }
283
284 {
285 const stats = await servers[0].videoStats.getOverallStats({ videoId: videoUUID, endDate: before2Watchers.toISOString() })
286 expect(stats.viewersPeak).to.equal(1)
287 expect(new Date(stats.viewersPeakDate)).to.be.below(before2Watchers)
288 }
289 })
290
291 it('Should complex filter peak viewers by date', async function () {
292 this.timeout(60000)
293
294 const { startDate, endDate } = await simulateComplexViewers(servers, videoUUID)
295
296 const expectCorrect = (stats: VideoStatsOverall) => {
297 expect(stats.viewersPeak).to.equal(3)
298 expect(new Date(stats.viewersPeakDate)).to.be.above(new Date(startDate)).and.below(new Date(endDate))
299 }
300
301 expectCorrect(await servers[0].videoStats.getOverallStats({ videoId: videoUUID, startDate, endDate }))
302 expectCorrect(await servers[0].videoStats.getOverallStats({ videoId: videoUUID, startDate }))
303 expectCorrect(await servers[0].videoStats.getOverallStats({ videoId: videoUUID, endDate }))
304 expectCorrect(await servers[0].videoStats.getOverallStats({ videoId: videoUUID }))
305 })
306 })
307
308 describe('Test countries', function () {
309 let videoUUID: string
310
311 it('Should not report countries if geoip is disabled', async function () {
312 this.timeout(120000)
313
314 const { uuid } = await servers[0].videos.quickUpload({ name: 'video' })
315 await waitJobs(servers)
316
317 await servers[1].views.view({ id: uuid, xForwardedFor: '8.8.8.8,127.0.0.1', currentTime: 1 })
318
319 await processViewersStats(servers)
320
321 const stats = await servers[0].videoStats.getOverallStats({ videoId: uuid })
322 expect(stats.countries).to.have.lengthOf(0)
323 })
324
325 it('Should report countries if geoip is enabled', async function () {
326 this.timeout(240000)
327
328 const { uuid } = await servers[0].videos.quickUpload({ name: 'video' })
329 videoUUID = uuid
330 await waitJobs(servers)
331
332 await Promise.all([
333 servers[0].kill(),
334 servers[1].kill()
335 ])
336
337 const config = { geo_ip: { enabled: true } }
338 await Promise.all([
339 servers[0].run(config),
340 servers[1].run(config)
341 ])
342
343 await servers[0].views.view({ id: uuid, xForwardedFor: '8.8.8.8,127.0.0.1', currentTime: 1 })
344 await servers[1].views.view({ id: uuid, xForwardedFor: '8.8.8.4,127.0.0.1', currentTime: 3 })
345 await servers[1].views.view({ id: uuid, xForwardedFor: '80.67.169.12,127.0.0.1', currentTime: 2 })
346
347 await processViewersStats(servers)
348
349 const stats = await servers[0].videoStats.getOverallStats({ videoId: uuid })
350 expect(stats.countries).to.have.lengthOf(2)
351
352 expect(stats.countries[0].isoCode).to.equal('US')
353 expect(stats.countries[0].viewers).to.equal(2)
354
355 expect(stats.countries[1].isoCode).to.equal('FR')
356 expect(stats.countries[1].viewers).to.equal(1)
357 })
358
359 it('Should filter countries stats by date', async function () {
360 const stats = await servers[0].videoStats.getOverallStats({ videoId: videoUUID, startDate: new Date().toISOString() })
361 expect(stats.countries).to.have.lengthOf(0)
362 })
363 })
364
365 after(async function () {
366 await cleanupTests(servers)
367 })
368 })