diff options
Diffstat (limited to 'packages/tests/src/api/views/video-views-timeserie-stats.ts')
-rw-r--r-- | packages/tests/src/api/views/video-views-timeserie-stats.ts | 253 |
1 files changed, 253 insertions, 0 deletions
diff --git a/packages/tests/src/api/views/video-views-timeserie-stats.ts b/packages/tests/src/api/views/video-views-timeserie-stats.ts new file mode 100644 index 000000000..44fccb644 --- /dev/null +++ b/packages/tests/src/api/views/video-views-timeserie-stats.ts | |||
@@ -0,0 +1,253 @@ | |||
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 '@tests/shared/views.js' | ||
6 | import { VideoStatsTimeserie, VideoStatsTimeserieMetric } from '@peertube/peertube-models' | ||
7 | import { cleanupTests, PeerTubeServer, stopFfmpeg } from '@peertube/peertube-server-commands' | ||
8 | |||
9 | function buildOneMonthAgo () { | ||
10 | const monthAgo = new Date() | ||
11 | monthAgo.setHours(0, 0, 0, 0) | ||
12 | |||
13 | monthAgo.setDate(monthAgo.getDate() - 29) | ||
14 | |||
15 | return monthAgo | ||
16 | } | ||
17 | |||
18 | describe('Test views timeserie stats', function () { | ||
19 | const availableMetrics: VideoStatsTimeserieMetric[] = [ 'viewers' ] | ||
20 | |||
21 | let servers: PeerTubeServer[] | ||
22 | |||
23 | before(async function () { | ||
24 | this.timeout(120000) | ||
25 | |||
26 | servers = await prepareViewsServers() | ||
27 | }) | ||
28 | |||
29 | describe('Common metric tests', function () { | ||
30 | let vodVideoId: string | ||
31 | |||
32 | before(async function () { | ||
33 | this.timeout(240000); | ||
34 | |||
35 | ({ vodVideoId } = await prepareViewsVideos({ servers, live: false, vod: true })) | ||
36 | }) | ||
37 | |||
38 | it('Should display empty metric stats', async function () { | ||
39 | for (const metric of availableMetrics) { | ||
40 | const { data } = await servers[0].videoStats.getTimeserieStats({ videoId: vodVideoId, metric }) | ||
41 | |||
42 | expect(data).to.have.length.at.least(1) | ||
43 | |||
44 | for (const d of data) { | ||
45 | expect(d.value).to.equal(0) | ||
46 | } | ||
47 | } | ||
48 | }) | ||
49 | }) | ||
50 | |||
51 | describe('Test viewer and watch time metrics on live and VOD', function () { | ||
52 | let vodVideoId: string | ||
53 | let liveVideoId: string | ||
54 | let command: FfmpegCommand | ||
55 | |||
56 | function expectTodayLastValue (result: VideoStatsTimeserie, lastValue?: number) { | ||
57 | const { data } = result | ||
58 | |||
59 | const last = data[data.length - 1] | ||
60 | const today = new Date().getDate() | ||
61 | expect(new Date(last.date).getDate()).to.equal(today) | ||
62 | |||
63 | if (lastValue) expect(last.value).to.equal(lastValue) | ||
64 | } | ||
65 | |||
66 | function expectTimeserieData (result: VideoStatsTimeserie, lastValue: number) { | ||
67 | const { data } = result | ||
68 | expect(data).to.have.length.at.least(25) | ||
69 | |||
70 | expectTodayLastValue(result, lastValue) | ||
71 | |||
72 | for (let i = 0; i < data.length - 2; i++) { | ||
73 | expect(data[i].value).to.equal(0) | ||
74 | } | ||
75 | } | ||
76 | |||
77 | function expectInterval (result: VideoStatsTimeserie, intervalMs: number) { | ||
78 | const first = result.data[0] | ||
79 | const second = result.data[1] | ||
80 | expect(new Date(second.date).getTime() - new Date(first.date).getTime()).to.equal(intervalMs) | ||
81 | } | ||
82 | |||
83 | before(async function () { | ||
84 | this.timeout(240000); | ||
85 | |||
86 | ({ vodVideoId, liveVideoId, ffmpegCommand: command } = await prepareViewsVideos({ servers, live: true, vod: true })) | ||
87 | }) | ||
88 | |||
89 | it('Should display appropriate viewers metrics', async function () { | ||
90 | for (const videoId of [ vodVideoId, liveVideoId ]) { | ||
91 | await servers[0].views.simulateViewer({ id: videoId, currentTimes: [ 0, 3 ] }) | ||
92 | await servers[1].views.simulateViewer({ id: videoId, currentTimes: [ 0, 5 ] }) | ||
93 | } | ||
94 | |||
95 | await processViewersStats(servers) | ||
96 | |||
97 | for (const videoId of [ vodVideoId, liveVideoId ]) { | ||
98 | const result = await servers[0].videoStats.getTimeserieStats({ | ||
99 | videoId, | ||
100 | startDate: buildOneMonthAgo(), | ||
101 | endDate: new Date(), | ||
102 | metric: 'viewers' | ||
103 | }) | ||
104 | expectTimeserieData(result, 2) | ||
105 | } | ||
106 | }) | ||
107 | |||
108 | it('Should display appropriate watch time metrics', async function () { | ||
109 | for (const videoId of [ vodVideoId, liveVideoId ]) { | ||
110 | const result = await servers[0].videoStats.getTimeserieStats({ | ||
111 | videoId, | ||
112 | startDate: buildOneMonthAgo(), | ||
113 | endDate: new Date(), | ||
114 | metric: 'aggregateWatchTime' | ||
115 | }) | ||
116 | expectTimeserieData(result, 8) | ||
117 | |||
118 | await servers[1].views.simulateViewer({ id: videoId, currentTimes: [ 0, 1 ] }) | ||
119 | } | ||
120 | |||
121 | await processViewersStats(servers) | ||
122 | |||
123 | for (const videoId of [ vodVideoId, liveVideoId ]) { | ||
124 | const result = await servers[0].videoStats.getTimeserieStats({ | ||
125 | videoId, | ||
126 | startDate: buildOneMonthAgo(), | ||
127 | endDate: new Date(), | ||
128 | metric: 'aggregateWatchTime' | ||
129 | }) | ||
130 | expectTimeserieData(result, 9) | ||
131 | } | ||
132 | }) | ||
133 | |||
134 | it('Should use a custom start/end date', async function () { | ||
135 | const now = new Date() | ||
136 | const twentyDaysAgo = new Date() | ||
137 | twentyDaysAgo.setDate(twentyDaysAgo.getDate() - 19) | ||
138 | |||
139 | const result = await servers[0].videoStats.getTimeserieStats({ | ||
140 | videoId: vodVideoId, | ||
141 | metric: 'aggregateWatchTime', | ||
142 | startDate: twentyDaysAgo, | ||
143 | endDate: now | ||
144 | }) | ||
145 | |||
146 | expect(result.groupInterval).to.equal('1 day') | ||
147 | expect(result.data).to.have.lengthOf(20) | ||
148 | |||
149 | const first = result.data[0] | ||
150 | expect(new Date(first.date).toLocaleDateString()).to.equal(twentyDaysAgo.toLocaleDateString()) | ||
151 | |||
152 | expectInterval(result, 24 * 3600 * 1000) | ||
153 | expectTodayLastValue(result, 9) | ||
154 | }) | ||
155 | |||
156 | it('Should automatically group by months', async function () { | ||
157 | const now = new Date() | ||
158 | const heightYearsAgo = new Date() | ||
159 | heightYearsAgo.setFullYear(heightYearsAgo.getFullYear() - 7) | ||
160 | |||
161 | const result = await servers[0].videoStats.getTimeserieStats({ | ||
162 | videoId: vodVideoId, | ||
163 | metric: 'aggregateWatchTime', | ||
164 | startDate: heightYearsAgo, | ||
165 | endDate: now | ||
166 | }) | ||
167 | |||
168 | expect(result.groupInterval).to.equal('6 months') | ||
169 | expect(result.data).to.have.length.above(10).and.below(200) | ||
170 | }) | ||
171 | |||
172 | it('Should automatically group by days', async function () { | ||
173 | const now = new Date() | ||
174 | const threeMonthsAgo = new Date() | ||
175 | threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3) | ||
176 | |||
177 | const result = await servers[0].videoStats.getTimeserieStats({ | ||
178 | videoId: vodVideoId, | ||
179 | metric: 'aggregateWatchTime', | ||
180 | startDate: threeMonthsAgo, | ||
181 | endDate: now | ||
182 | }) | ||
183 | |||
184 | expect(result.groupInterval).to.equal('2 days') | ||
185 | expect(result.data).to.have.length.above(10).and.below(200) | ||
186 | }) | ||
187 | |||
188 | it('Should automatically group by hours', async function () { | ||
189 | const now = new Date() | ||
190 | const twoDaysAgo = new Date() | ||
191 | twoDaysAgo.setDate(twoDaysAgo.getDate() - 1) | ||
192 | |||
193 | const result = await servers[0].videoStats.getTimeserieStats({ | ||
194 | videoId: vodVideoId, | ||
195 | metric: 'aggregateWatchTime', | ||
196 | startDate: twoDaysAgo, | ||
197 | endDate: now | ||
198 | }) | ||
199 | |||
200 | expect(result.groupInterval).to.equal('1 hour') | ||
201 | expect(result.data).to.have.length.above(24).and.below(50) | ||
202 | |||
203 | expectInterval(result, 3600 * 1000) | ||
204 | expectTodayLastValue(result, 9) | ||
205 | }) | ||
206 | |||
207 | it('Should automatically group by ten minutes', async function () { | ||
208 | const now = new Date() | ||
209 | const twoHoursAgo = new Date() | ||
210 | twoHoursAgo.setHours(twoHoursAgo.getHours() - 4) | ||
211 | |||
212 | const result = await servers[0].videoStats.getTimeserieStats({ | ||
213 | videoId: vodVideoId, | ||
214 | metric: 'aggregateWatchTime', | ||
215 | startDate: twoHoursAgo, | ||
216 | endDate: now | ||
217 | }) | ||
218 | |||
219 | expect(result.groupInterval).to.equal('10 minutes') | ||
220 | expect(result.data).to.have.length.above(20).and.below(30) | ||
221 | |||
222 | expectInterval(result, 60 * 10 * 1000) | ||
223 | expectTodayLastValue(result) | ||
224 | }) | ||
225 | |||
226 | it('Should automatically group by one minute', async function () { | ||
227 | const now = new Date() | ||
228 | const thirtyAgo = new Date() | ||
229 | thirtyAgo.setMinutes(thirtyAgo.getMinutes() - 30) | ||
230 | |||
231 | const result = await servers[0].videoStats.getTimeserieStats({ | ||
232 | videoId: vodVideoId, | ||
233 | metric: 'aggregateWatchTime', | ||
234 | startDate: thirtyAgo, | ||
235 | endDate: now | ||
236 | }) | ||
237 | |||
238 | expect(result.groupInterval).to.equal('1 minute') | ||
239 | expect(result.data).to.have.length.above(20).and.below(40) | ||
240 | |||
241 | expectInterval(result, 60 * 1000) | ||
242 | expectTodayLastValue(result) | ||
243 | }) | ||
244 | |||
245 | after(async function () { | ||
246 | await stopFfmpeg(command) | ||
247 | }) | ||
248 | }) | ||
249 | |||
250 | after(async function () { | ||
251 | await cleanupTests(servers) | ||
252 | }) | ||
253 | }) | ||