]>
Commit | Line | Data |
---|---|---|
b2111066 C |
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | ||
86347717 | 3 | import { expect } from 'chai' |
b2111066 C |
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' | |
624ea01b C |
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 | } | |
b2111066 | 57 | |
b2111066 C |
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 | ||
b2111066 C |
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 () { | |
c2419476 | 73 | this.timeout(240000); |
b2111066 C |
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 }) | |
f18a060a | 81 | const video = await servers[0].videos.get({ id: videoId }) |
b2111066 | 82 | |
f18a060a | 83 | expect(video.views).to.equal(0) |
b2111066 C |
84 | expect(stats.averageWatchTime).to.equal(0) |
85 | expect(stats.totalWatchTime).to.equal(0) | |
305ec384 | 86 | expect(stats.totalViewers).to.equal(0) |
b2111066 C |
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 }) | |
f18a060a | 101 | const video = await servers[0].videos.get({ id: videoId }) |
b2111066 | 102 | |
f18a060a | 103 | expect(video.views).to.equal(0) |
b2111066 C |
104 | expect(stats.averageWatchTime).to.equal(1) |
105 | expect(stats.totalWatchTime).to.equal(1) | |
305ec384 | 106 | expect(stats.totalViewers).to.equal(1) |
b2111066 C |
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 }) | |
f18a060a C |
121 | const video = await servers[0].videos.get({ id: vodVideoId }) |
122 | ||
123 | expect(video.views).to.equal(1) | |
b2111066 C |
124 | expect(stats.averageWatchTime).to.equal(2) |
125 | expect(stats.totalWatchTime).to.equal(4) | |
305ec384 | 126 | expect(stats.totalViewers).to.equal(2) |
b2111066 C |
127 | } |
128 | ||
129 | { | |
130 | const stats = await servers[0].videoStats.getOverallStats({ videoId: liveVideoId }) | |
f18a060a C |
131 | const video = await servers[0].videos.get({ id: liveVideoId }) |
132 | ||
133 | expect(video.views).to.equal(1) | |
b2111066 C |
134 | expect(stats.averageWatchTime).to.equal(21) |
135 | expect(stats.totalWatchTime).to.equal(41) | |
305ec384 | 136 | expect(stats.totalViewers).to.equal(2) |
b2111066 C |
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 }) | |
f18a060a | 152 | const video = await servers[0].videos.get({ id: vodVideoId }) |
b2111066 | 153 | |
f18a060a | 154 | expect(video.views).to.equal(1) |
b2111066 C |
155 | expect(stats.averageWatchTime).to.equal(2) |
156 | expect(stats.totalWatchTime).to.equal(6) | |
305ec384 | 157 | expect(stats.totalViewers).to.equal(3) |
b2111066 C |
158 | } |
159 | ||
160 | { | |
161 | const stats = await servers[0].videoStats.getOverallStats({ videoId: liveVideoId }) | |
f18a060a | 162 | const video = await servers[0].videos.get({ id: liveVideoId }) |
b2111066 | 163 | |
f18a060a | 164 | expect(video.views).to.equal(1) |
b2111066 C |
165 | expect(stats.averageWatchTime).to.equal(14) |
166 | expect(stats.totalWatchTime).to.equal(43) | |
305ec384 | 167 | expect(stats.totalViewers).to.equal(3) |
b2111066 C |
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 }) | |
f18a060a C |
180 | const video = await servers[0].videos.get({ id: vodVideoId }) |
181 | ||
182 | expect(video.views).to.equal(2) | |
b2111066 C |
183 | expect(stats.averageWatchTime).to.equal(3) |
184 | expect(stats.totalWatchTime).to.equal(11) | |
305ec384 | 185 | expect(stats.totalViewers).to.equal(4) |
b2111066 C |
186 | } |
187 | ||
188 | { | |
189 | const stats = await servers[0].videoStats.getOverallStats({ videoId: liveVideoId }) | |
f18a060a C |
190 | const video = await servers[0].videos.get({ id: liveVideoId }) |
191 | ||
192 | expect(video.views).to.equal(2) | |
b2111066 C |
193 | expect(stats.averageWatchTime).to.equal(22) |
194 | expect(stats.totalWatchTime).to.equal(88) | |
305ec384 | 195 | expect(stats.totalViewers).to.equal(4) |
b2111066 C |
196 | } |
197 | }) | |
198 | ||
49f0468d C |
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) | |
305ec384 | 211 | expect(stats.totalViewers).to.equal(1) |
49f0468d C |
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) | |
305ec384 | 218 | expect(stats.totalViewers).to.equal(4) |
49f0468d C |
219 | } |
220 | }) | |
221 | ||
b2111066 C |
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 | |
f40712ab | 229 | let before2Watchers: Date |
b2111066 C |
230 | |
231 | before(async function () { | |
c2419476 | 232 | this.timeout(240000); |
b2111066 C |
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 | ||
f40712ab | 262 | before2Watchers = new Date() |
b2111066 C |
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) | |
f40712ab C |
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 | } | |
b2111066 | 289 | }) |
624ea01b C |
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 | }) | |
b2111066 C |
306 | }) |
307 | ||
308 | describe('Test countries', function () { | |
f40712ab | 309 | let videoUUID: string |
b2111066 C |
310 | |
311 | it('Should not report countries if geoip is disabled', async function () { | |
40fa53ac | 312 | this.timeout(120000) |
b2111066 C |
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 () { | |
389444e0 | 326 | this.timeout(240000) |
b2111066 C |
327 | |
328 | const { uuid } = await servers[0].videos.quickUpload({ name: 'video' }) | |
f40712ab | 329 | videoUUID = uuid |
b2111066 C |
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 | }) | |
f40712ab C |
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 | }) | |
b2111066 C |
363 | }) |
364 | ||
365 | after(async function () { | |
366 | await cleanupTests(servers) | |
367 | }) | |
368 | }) |