aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/tests/api/views
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2022-03-24 13:36:47 +0100
committerChocobozzz <chocobozzz@cpy.re>2022-04-15 09:49:35 +0200
commitb211106695bb82f6c32e53306081b5262c3d109d (patch)
treefa187de1c33b0956665f5362e29af6b0f6d8bb57 /server/tests/api/views
parent69d48ee30c9d47cddf0c3c047dc99a99dcb6e894 (diff)
downloadPeerTube-b211106695bb82f6c32e53306081b5262c3d109d.tar.gz
PeerTube-b211106695bb82f6c32e53306081b5262c3d109d.tar.zst
PeerTube-b211106695bb82f6c32e53306081b5262c3d109d.zip
Support video views/viewers stats in server
* Add "currentTime" and "event" body params to view endpoint * Merge watching and view endpoints * Introduce WatchAction AP activity * Add tables to store viewer information of local videos * Add endpoints to fetch video views/viewers stats of local videos * Refactor views/viewers handlers * Support "views" and "viewers" counters for both VOD and live videos
Diffstat (limited to 'server/tests/api/views')
-rw-r--r--server/tests/api/views/index.ts5
-rw-r--r--server/tests/api/views/video-views-counter.ts155
-rw-r--r--server/tests/api/views/video-views-overall-stats.ts291
-rw-r--r--server/tests/api/views/video-views-retention-stats.ts56
-rw-r--r--server/tests/api/views/video-views-timeserie-stats.ts109
-rw-r--r--server/tests/api/views/videos-views-cleaner.ts101
6 files changed, 717 insertions, 0 deletions
diff --git a/server/tests/api/views/index.ts b/server/tests/api/views/index.ts
new file mode 100644
index 000000000..5e06b31fb
--- /dev/null
+++ b/server/tests/api/views/index.ts
@@ -0,0 +1,5 @@
1export * from './video-views-counter'
2export * from './video-views-overall-stats'
3export * from './video-views-retention-stats'
4export * from './video-views-timeserie-stats'
5export * from './videos-views-cleaner'
diff --git a/server/tests/api/views/video-views-counter.ts b/server/tests/api/views/video-views-counter.ts
new file mode 100644
index 000000000..b68aaa350
--- /dev/null
+++ b/server/tests/api/views/video-views-counter.ts
@@ -0,0 +1,155 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import 'mocha'
4import * as chai from 'chai'
5import { FfmpegCommand } from 'fluent-ffmpeg'
6import { prepareViewsServers, prepareViewsVideos, processViewsBuffer } from '@server/tests/shared'
7import { wait } from '@shared/core-utils'
8import { cleanupTests, PeerTubeServer, stopFfmpeg, waitJobs } from '@shared/server-commands'
9
10const expect = chai.expect
11
12describe('Test video views/viewers counters', function () {
13 let servers: PeerTubeServer[]
14
15 async function checkCounter (field: 'views' | 'viewers', id: string, expected: number) {
16 for (const server of servers) {
17 const video = await server.videos.get({ id })
18
19 const messageSuffix = video.isLive
20 ? 'live video'
21 : 'vod video'
22
23 expect(video[field]).to.equal(expected, `${field} not valid on server ${server.serverNumber} for ${messageSuffix} ${video.uuid}`)
24 }
25 }
26
27 before(async function () {
28 this.timeout(120000)
29
30 servers = await prepareViewsServers()
31 })
32
33 describe('Test views counter on VOD', function () {
34 let videoUUID: string
35
36 before(async function () {
37 this.timeout(30000)
38
39 const { uuid } = await servers[0].videos.quickUpload({ name: 'video' })
40 videoUUID = uuid
41
42 await waitJobs(servers)
43 })
44
45 it('Should not view a video if watch time is below the threshold', async function () {
46 await servers[0].views.simulateViewer({ id: videoUUID, currentTimes: [ 1, 2 ] })
47 await processViewsBuffer(servers)
48
49 await checkCounter('views', videoUUID, 0)
50 })
51
52 it('Should view a video if watch time is above the threshold', async function () {
53 await servers[0].views.simulateViewer({ id: videoUUID, currentTimes: [ 1, 4 ] })
54 await processViewsBuffer(servers)
55
56 await checkCounter('views', videoUUID, 1)
57 })
58
59 it('Should not view again this video with the same IP', async function () {
60 await servers[0].views.simulateViewer({ id: videoUUID, currentTimes: [ 1, 4 ] })
61 await processViewsBuffer(servers)
62
63 await checkCounter('views', videoUUID, 1)
64 })
65
66 it('Should view the video from server 2 and send the event', async function () {
67 await servers[1].views.simulateViewer({ id: videoUUID, currentTimes: [ 1, 4 ] })
68 await waitJobs(servers)
69 await processViewsBuffer(servers)
70
71 await checkCounter('views', videoUUID, 2)
72 })
73 })
74
75 describe('Test views and viewers counters on live and VOD', function () {
76 let liveVideoId: string
77 let vodVideoId: string
78 let command: FfmpegCommand
79
80 before(async function () {
81 this.timeout(60000);
82
83 ({ vodVideoId, liveVideoId, ffmpegCommand: command } = await prepareViewsVideos({ servers, live: true, vod: true }))
84 })
85
86 it('Should display no views and viewers', async function () {
87 await checkCounter('views', liveVideoId, 0)
88 await checkCounter('viewers', liveVideoId, 0)
89
90 await checkCounter('views', vodVideoId, 0)
91 await checkCounter('viewers', vodVideoId, 0)
92 })
93
94 it('Should view twice and display 1 view/viewer', async function () {
95 this.timeout(30000)
96
97 await servers[0].views.simulateViewer({ id: liveVideoId, currentTimes: [ 0, 35 ] })
98 await servers[0].views.simulateViewer({ id: liveVideoId, currentTimes: [ 0, 35 ] })
99 await servers[0].views.simulateViewer({ id: vodVideoId, currentTimes: [ 0, 5 ] })
100 await servers[0].views.simulateViewer({ id: vodVideoId, currentTimes: [ 0, 5 ] })
101
102 await waitJobs(servers)
103 await checkCounter('viewers', liveVideoId, 1)
104 await checkCounter('viewers', vodVideoId, 1)
105
106 await processViewsBuffer(servers)
107
108 await checkCounter('views', liveVideoId, 1)
109 await checkCounter('views', vodVideoId, 1)
110 })
111
112 it('Should wait and display 0 viewers but still have 1 view', async function () {
113 this.timeout(30000)
114
115 await wait(12000)
116 await waitJobs(servers)
117
118 await checkCounter('views', liveVideoId, 1)
119 await checkCounter('viewers', liveVideoId, 0)
120
121 await checkCounter('views', vodVideoId, 1)
122 await checkCounter('viewers', vodVideoId, 0)
123 })
124
125 it('Should view on a remote and on local and display 2 viewers and 3 views', async function () {
126 this.timeout(30000)
127
128 await servers[0].views.simulateViewer({ id: vodVideoId, currentTimes: [ 0, 5 ] })
129 await servers[1].views.simulateViewer({ id: vodVideoId, currentTimes: [ 0, 5 ] })
130 await servers[1].views.simulateViewer({ id: vodVideoId, currentTimes: [ 0, 5 ] })
131
132 await servers[0].views.simulateViewer({ id: liveVideoId, currentTimes: [ 0, 35 ] })
133 await servers[1].views.simulateViewer({ id: liveVideoId, currentTimes: [ 0, 35 ] })
134 await servers[1].views.simulateViewer({ id: liveVideoId, currentTimes: [ 0, 35 ] })
135
136 await waitJobs(servers)
137
138 await checkCounter('viewers', liveVideoId, 2)
139 await checkCounter('viewers', vodVideoId, 2)
140
141 await processViewsBuffer(servers)
142
143 await checkCounter('views', liveVideoId, 3)
144 await checkCounter('views', vodVideoId, 3)
145 })
146
147 after(async function () {
148 await stopFfmpeg(command)
149 })
150 })
151
152 after(async function () {
153 await cleanupTests(servers)
154 })
155})
diff --git a/server/tests/api/views/video-views-overall-stats.ts b/server/tests/api/views/video-views-overall-stats.ts
new file mode 100644
index 000000000..22761d6ec
--- /dev/null
+++ b/server/tests/api/views/video-views-overall-stats.ts
@@ -0,0 +1,291 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import 'mocha'
4import * as chai from 'chai'
5import { FfmpegCommand } from 'fluent-ffmpeg'
6import { prepareViewsServers, prepareViewsVideos, processViewersStats } from '@server/tests/shared'
7import { cleanupTests, PeerTubeServer, stopFfmpeg, waitJobs } from '@shared/server-commands'
8
9const expect = chai.expect
10
11describe('Test views overall stats', function () {
12 let servers: PeerTubeServer[]
13
14 before(async function () {
15 this.timeout(120000)
16
17 servers = await prepareViewsServers()
18 })
19
20 describe('Test rates and comments of local videos on VOD', function () {
21 let vodVideoId: string
22
23 before(async function () {
24 this.timeout(60000);
25
26 ({ vodVideoId } = await prepareViewsVideos({ servers, live: false, vod: true }))
27 })
28
29 it('Should have the appropriate likes', async function () {
30 this.timeout(60000)
31
32 await servers[0].videos.rate({ id: vodVideoId, rating: 'like' })
33 await servers[1].videos.rate({ id: vodVideoId, rating: 'like' })
34
35 await waitJobs(servers)
36
37 const stats = await servers[0].videoStats.getOverallStats({ videoId: vodVideoId })
38
39 expect(stats.likes).to.equal(2)
40 expect(stats.dislikes).to.equal(0)
41 })
42
43 it('Should have the appropriate dislikes', async function () {
44 this.timeout(60000)
45
46 await servers[0].videos.rate({ id: vodVideoId, rating: 'dislike' })
47 await servers[1].videos.rate({ id: vodVideoId, rating: 'dislike' })
48
49 await waitJobs(servers)
50
51 const stats = await servers[0].videoStats.getOverallStats({ videoId: vodVideoId })
52
53 expect(stats.likes).to.equal(0)
54 expect(stats.dislikes).to.equal(2)
55 })
56
57 it('Should have the appropriate comments', async function () {
58 this.timeout(60000)
59
60 await servers[0].comments.createThread({ videoId: vodVideoId, text: 'root' })
61 await servers[0].comments.addReplyToLastThread({ text: 'reply' })
62 await servers[1].comments.createThread({ videoId: vodVideoId, text: 'root' })
63
64 await waitJobs(servers)
65
66 const stats = await servers[0].videoStats.getOverallStats({ videoId: vodVideoId })
67 expect(stats.comments).to.equal(3)
68 })
69 })
70
71 describe('Test watch time stats of local videos on live and VOD', function () {
72 let vodVideoId: string
73 let liveVideoId: string
74 let command: FfmpegCommand
75
76 before(async function () {
77 this.timeout(60000);
78
79 ({ vodVideoId, liveVideoId, ffmpegCommand: command } = await prepareViewsVideos({ servers, live: true, vod: true }))
80 })
81
82 it('Should display overall stats of a video with no viewers', async function () {
83 for (const videoId of [ liveVideoId, vodVideoId ]) {
84 const stats = await servers[0].videoStats.getOverallStats({ videoId })
85
86 expect(stats.views).to.equal(0)
87 expect(stats.averageWatchTime).to.equal(0)
88 expect(stats.totalWatchTime).to.equal(0)
89 }
90 })
91
92 it('Should display overall stats with 1 viewer below the watch time limit', async function () {
93 this.timeout(60000)
94
95 for (const videoId of [ liveVideoId, vodVideoId ]) {
96 await servers[0].views.simulateViewer({ id: videoId, currentTimes: [ 0, 1 ] })
97 }
98
99 await processViewersStats(servers)
100
101 for (const videoId of [ liveVideoId, vodVideoId ]) {
102 const stats = await servers[0].videoStats.getOverallStats({ videoId })
103
104 expect(stats.views).to.equal(0)
105 expect(stats.averageWatchTime).to.equal(1)
106 expect(stats.totalWatchTime).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 expect(stats.views).to.equal(1)
122 expect(stats.averageWatchTime).to.equal(2)
123 expect(stats.totalWatchTime).to.equal(4)
124 }
125
126 {
127 const stats = await servers[0].videoStats.getOverallStats({ videoId: liveVideoId })
128 expect(stats.views).to.equal(1)
129 expect(stats.averageWatchTime).to.equal(21)
130 expect(stats.totalWatchTime).to.equal(41)
131 }
132 }
133 })
134
135 it('Should display overall stats with a remote viewer below the watch time limit', async function () {
136 this.timeout(60000)
137
138 for (const videoId of [ liveVideoId, vodVideoId ]) {
139 await servers[1].views.simulateViewer({ id: videoId, currentTimes: [ 0, 2 ] })
140 }
141
142 await processViewersStats(servers)
143
144 {
145 const stats = await servers[0].videoStats.getOverallStats({ videoId: vodVideoId })
146
147 expect(stats.views).to.equal(1)
148 expect(stats.averageWatchTime).to.equal(2)
149 expect(stats.totalWatchTime).to.equal(6)
150 }
151
152 {
153 const stats = await servers[0].videoStats.getOverallStats({ videoId: liveVideoId })
154
155 expect(stats.views).to.equal(1)
156 expect(stats.averageWatchTime).to.equal(14)
157 expect(stats.totalWatchTime).to.equal(43)
158 }
159 })
160
161 it('Should display overall stats with a remote viewer above the watch time limit', async function () {
162 this.timeout(60000)
163
164 await servers[1].views.simulateViewer({ id: vodVideoId, currentTimes: [ 0, 5 ] })
165 await servers[1].views.simulateViewer({ id: liveVideoId, currentTimes: [ 0, 45 ] })
166 await processViewersStats(servers)
167
168 {
169 const stats = await servers[0].videoStats.getOverallStats({ videoId: vodVideoId })
170 expect(stats.views).to.equal(2)
171 expect(stats.averageWatchTime).to.equal(3)
172 expect(stats.totalWatchTime).to.equal(11)
173 }
174
175 {
176 const stats = await servers[0].videoStats.getOverallStats({ videoId: liveVideoId })
177 expect(stats.views).to.equal(2)
178 expect(stats.averageWatchTime).to.equal(22)
179 expect(stats.totalWatchTime).to.equal(88)
180 }
181 })
182
183 after(async function () {
184 await stopFfmpeg(command)
185 })
186 })
187
188 describe('Test watchers peak stats of local videos on VOD', function () {
189 let videoUUID: string
190
191 before(async function () {
192 this.timeout(60000);
193
194 ({ vodVideoId: videoUUID } = await prepareViewsVideos({ servers, live: true, vod: true }))
195 })
196
197 it('Should not have watchers peak', async function () {
198 const stats = await servers[0].videoStats.getOverallStats({ videoId: videoUUID })
199
200 expect(stats.viewersPeak).to.equal(0)
201 expect(stats.viewersPeakDate).to.be.null
202 })
203
204 it('Should have watcher peak with 1 watcher', async function () {
205 this.timeout(60000)
206
207 const before = new Date()
208 await servers[0].views.simulateViewer({ id: videoUUID, currentTimes: [ 0, 2 ] })
209 const after = new Date()
210
211 await processViewersStats(servers)
212
213 const stats = await servers[0].videoStats.getOverallStats({ videoId: videoUUID })
214
215 expect(stats.viewersPeak).to.equal(1)
216 expect(new Date(stats.viewersPeakDate)).to.be.above(before).and.below(after)
217 })
218
219 it('Should have watcher peak with 2 watchers', async function () {
220 this.timeout(60000)
221
222 const before = new Date()
223 await servers[0].views.view({ id: videoUUID, currentTime: 0 })
224 await servers[1].views.view({ id: videoUUID, currentTime: 0 })
225 await servers[0].views.view({ id: videoUUID, currentTime: 2 })
226 await servers[1].views.view({ id: videoUUID, currentTime: 2 })
227 const after = new Date()
228
229 await processViewersStats(servers)
230
231 const stats = await servers[0].videoStats.getOverallStats({ videoId: videoUUID })
232
233 expect(stats.viewersPeak).to.equal(2)
234 expect(new Date(stats.viewersPeakDate)).to.be.above(before).and.below(after)
235 })
236 })
237
238 describe('Test countries', function () {
239
240 it('Should not report countries if geoip is disabled', async function () {
241 this.timeout(60000)
242
243 const { uuid } = await servers[0].videos.quickUpload({ name: 'video' })
244 await waitJobs(servers)
245
246 await servers[1].views.view({ id: uuid, xForwardedFor: '8.8.8.8,127.0.0.1', currentTime: 1 })
247
248 await processViewersStats(servers)
249
250 const stats = await servers[0].videoStats.getOverallStats({ videoId: uuid })
251 expect(stats.countries).to.have.lengthOf(0)
252 })
253
254 it('Should report countries if geoip is enabled', async function () {
255 this.timeout(60000)
256
257 const { uuid } = await servers[0].videos.quickUpload({ name: 'video' })
258 await waitJobs(servers)
259
260 await Promise.all([
261 servers[0].kill(),
262 servers[1].kill()
263 ])
264
265 const config = { geo_ip: { enabled: true } }
266 await Promise.all([
267 servers[0].run(config),
268 servers[1].run(config)
269 ])
270
271 await servers[0].views.view({ id: uuid, xForwardedFor: '8.8.8.8,127.0.0.1', currentTime: 1 })
272 await servers[1].views.view({ id: uuid, xForwardedFor: '8.8.8.4,127.0.0.1', currentTime: 3 })
273 await servers[1].views.view({ id: uuid, xForwardedFor: '80.67.169.12,127.0.0.1', currentTime: 2 })
274
275 await processViewersStats(servers)
276
277 const stats = await servers[0].videoStats.getOverallStats({ videoId: uuid })
278 expect(stats.countries).to.have.lengthOf(2)
279
280 expect(stats.countries[0].isoCode).to.equal('US')
281 expect(stats.countries[0].viewers).to.equal(2)
282
283 expect(stats.countries[1].isoCode).to.equal('FR')
284 expect(stats.countries[1].viewers).to.equal(1)
285 })
286 })
287
288 after(async function () {
289 await cleanupTests(servers)
290 })
291})
diff --git a/server/tests/api/views/video-views-retention-stats.ts b/server/tests/api/views/video-views-retention-stats.ts
new file mode 100644
index 000000000..98be7bfdb
--- /dev/null
+++ b/server/tests/api/views/video-views-retention-stats.ts
@@ -0,0 +1,56 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import 'mocha'
4import * as chai from 'chai'
5import { prepareViewsServers, prepareViewsVideos, processViewersStats } from '@server/tests/shared'
6import { cleanupTests, PeerTubeServer } from '@shared/server-commands'
7
8const expect = chai.expect
9
10describe('Test views retention stats', function () {
11 let servers: PeerTubeServer[]
12
13 before(async function () {
14 this.timeout(120000)
15
16 servers = await prepareViewsServers()
17 })
18
19 describe('Test retention stats on VOD', function () {
20 let vodVideoId: string
21
22 before(async function () {
23 this.timeout(60000);
24
25 ({ vodVideoId } = await prepareViewsVideos({ servers, live: false, vod: true }))
26 })
27
28 it('Should display empty retention', async function () {
29 const { data } = await servers[0].videoStats.getRetentionStats({ videoId: vodVideoId })
30 expect(data).to.have.lengthOf(6)
31
32 for (let i = 0; i < 6; i++) {
33 expect(data[i].second).to.equal(i)
34 expect(data[i].retentionPercent).to.equal(0)
35 }
36 })
37
38 it('Should display appropriate retention metrics', async function () {
39 await servers[0].views.simulateViewer({ xForwardedFor: '127.0.0.2,127.0.0.1', id: vodVideoId, currentTimes: [ 0, 1 ] })
40 await servers[0].views.simulateViewer({ xForwardedFor: '127.0.0.3,127.0.0.1', id: vodVideoId, currentTimes: [ 1, 3 ] })
41 await servers[1].views.simulateViewer({ xForwardedFor: '127.0.0.2,127.0.0.1', id: vodVideoId, currentTimes: [ 4 ] })
42 await servers[1].views.simulateViewer({ xForwardedFor: '127.0.0.3,127.0.0.1', id: vodVideoId, currentTimes: [ 0, 1 ] })
43
44 await processViewersStats(servers)
45
46 const { data } = await servers[0].videoStats.getRetentionStats({ videoId: vodVideoId })
47 expect(data).to.have.lengthOf(6)
48
49 expect(data.map(d => d.retentionPercent)).to.deep.equal([ 50, 75, 25, 25, 25, 0 ])
50 })
51 })
52
53 after(async function () {
54 await cleanupTests(servers)
55 })
56})
diff --git a/server/tests/api/views/video-views-timeserie-stats.ts b/server/tests/api/views/video-views-timeserie-stats.ts
new file mode 100644
index 000000000..98c041cdf
--- /dev/null
+++ b/server/tests/api/views/video-views-timeserie-stats.ts
@@ -0,0 +1,109 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import 'mocha'
4import * as chai from 'chai'
5import { FfmpegCommand } from 'fluent-ffmpeg'
6import { prepareViewsServers, prepareViewsVideos, processViewersStats } from '@server/tests/shared'
7import { VideoStatsTimeserie, VideoStatsTimeserieMetric } from '@shared/models'
8import { cleanupTests, PeerTubeServer, stopFfmpeg } from '@shared/server-commands'
9
10const expect = chai.expect
11
12describe('Test views timeserie stats', function () {
13 const availableMetrics: VideoStatsTimeserieMetric[] = [ 'viewers' ]
14
15 let servers: PeerTubeServer[]
16
17 before(async function () {
18 this.timeout(120000)
19
20 servers = await prepareViewsServers()
21 })
22
23 describe('Common metric tests', function () {
24 let vodVideoId: string
25
26 before(async function () {
27 this.timeout(60000);
28
29 ({ vodVideoId } = await prepareViewsVideos({ servers, live: false, vod: true }))
30 })
31
32 it('Should display empty metric stats', async function () {
33 for (const metric of availableMetrics) {
34 const { data } = await servers[0].videoStats.getTimeserieStats({ videoId: vodVideoId, metric })
35
36 expect(data).to.have.lengthOf(30)
37
38 for (const d of data) {
39 expect(d.value).to.equal(0)
40 }
41 }
42 })
43 })
44
45 describe('Test viewer and watch time metrics on live and VOD', function () {
46 let vodVideoId: string
47 let liveVideoId: string
48 let command: FfmpegCommand
49
50 function expectTimeserieData (result: VideoStatsTimeserie, lastValue: number) {
51 const { data } = result
52 expect(data).to.have.lengthOf(30)
53
54 const last = data[data.length - 1]
55
56 const today = new Date().getDate()
57 expect(new Date(last.date).getDate()).to.equal(today)
58 expect(last.value).to.equal(lastValue)
59
60 for (let i = 0; i < data.length - 2; i++) {
61 expect(data[i].value).to.equal(0)
62 }
63 }
64
65 before(async function () {
66 this.timeout(60000);
67
68 ({ vodVideoId, liveVideoId, ffmpegCommand: command } = await prepareViewsVideos({ servers, live: true, vod: true }))
69 })
70
71 it('Should display appropriate viewers metrics', async function () {
72 for (const videoId of [ vodVideoId, liveVideoId ]) {
73 await servers[0].views.simulateViewer({ id: videoId, currentTimes: [ 0, 3 ] })
74 await servers[1].views.simulateViewer({ id: videoId, currentTimes: [ 0, 5 ] })
75 }
76
77 await processViewersStats(servers)
78
79 for (const videoId of [ vodVideoId, liveVideoId ]) {
80 const result = await servers[0].videoStats.getTimeserieStats({ videoId, metric: 'viewers' })
81 expectTimeserieData(result, 2)
82 }
83 })
84
85 it('Should display appropriate watch time metrics', async function () {
86 for (const videoId of [ vodVideoId, liveVideoId ]) {
87 const result = await servers[0].videoStats.getTimeserieStats({ videoId, metric: 'aggregateWatchTime' })
88 expectTimeserieData(result, 8)
89
90 await servers[1].views.simulateViewer({ id: videoId, currentTimes: [ 0, 1 ] })
91 }
92
93 await processViewersStats(servers)
94
95 for (const videoId of [ vodVideoId, liveVideoId ]) {
96 const result = await servers[0].videoStats.getTimeserieStats({ videoId, metric: 'aggregateWatchTime' })
97 expectTimeserieData(result, 9)
98 }
99 })
100
101 after(async function () {
102 await stopFfmpeg(command)
103 })
104 })
105
106 after(async function () {
107 await cleanupTests(servers)
108 })
109})
diff --git a/server/tests/api/views/videos-views-cleaner.ts b/server/tests/api/views/videos-views-cleaner.ts
new file mode 100644
index 000000000..ef988837f
--- /dev/null
+++ b/server/tests/api/views/videos-views-cleaner.ts
@@ -0,0 +1,101 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import 'mocha'
4import * as chai from 'chai'
5import { wait } from '@shared/core-utils'
6import {
7 cleanupTests,
8 createMultipleServers,
9 doubleFollow,
10 killallServers,
11 PeerTubeServer,
12 setAccessTokensToServers,
13 waitJobs
14} from '@shared/server-commands'
15
16const expect = chai.expect
17
18describe('Test video views cleaner', function () {
19 let servers: PeerTubeServer[]
20
21 let videoIdServer1: string
22 let videoIdServer2: string
23
24 before(async function () {
25 this.timeout(120000)
26
27 servers = await createMultipleServers(2)
28 await setAccessTokensToServers(servers)
29
30 await doubleFollow(servers[0], servers[1])
31
32 videoIdServer1 = (await servers[0].videos.quickUpload({ name: 'video server 1' })).uuid
33 videoIdServer2 = (await servers[1].videos.quickUpload({ name: 'video server 2' })).uuid
34
35 await waitJobs(servers)
36
37 await servers[0].views.simulateView({ id: videoIdServer1 })
38 await servers[1].views.simulateView({ id: videoIdServer1 })
39 await servers[0].views.simulateView({ id: videoIdServer2 })
40 await servers[1].views.simulateView({ id: videoIdServer2 })
41
42 await waitJobs(servers)
43 })
44
45 it('Should not clean old video views', async function () {
46 this.timeout(50000)
47
48 await killallServers([ servers[0] ])
49
50 await servers[0].run({ views: { videos: { remote: { max_age: '10 days' } } } })
51
52 await wait(6000)
53
54 // Should still have views
55
56 {
57 for (const server of servers) {
58 const total = await server.sql.countVideoViewsOf(videoIdServer1)
59 expect(total).to.equal(2, 'Server ' + server.serverNumber + ' does not have the correct amount of views')
60 }
61 }
62
63 {
64 for (const server of servers) {
65 const total = await server.sql.countVideoViewsOf(videoIdServer2)
66 expect(total).to.equal(2, 'Server ' + server.serverNumber + ' does not have the correct amount of views')
67 }
68 }
69 })
70
71 it('Should clean old video views', async function () {
72 this.timeout(50000)
73
74 await killallServers([ servers[0] ])
75
76 await servers[0].run({ views: { videos: { remote: { max_age: '5 seconds' } } } })
77
78 await wait(6000)
79
80 // Should still have views
81
82 {
83 for (const server of servers) {
84 const total = await server.sql.countVideoViewsOf(videoIdServer1)
85 expect(total).to.equal(2)
86 }
87 }
88
89 {
90 const totalServer1 = await servers[0].sql.countVideoViewsOf(videoIdServer2)
91 expect(totalServer1).to.equal(0)
92
93 const totalServer2 = await servers[1].sql.countVideoViewsOf(videoIdServer2)
94 expect(totalServer2).to.equal(2)
95 }
96 })
97
98 after(async function () {
99 await cleanupTests(servers)
100 })
101})