]>
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 { VideoStatsTimeserie, VideoStatsTimeserieMetric } from '@shared/models' | |
7 | import { cleanupTests, PeerTubeServer, stopFfmpeg } from '@shared/server-commands' | |
8 | ||
f40712ab C |
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 | ||
b2111066 C |
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 () { | |
c2419476 | 33 | this.timeout(240000); |
b2111066 C |
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 | ||
f40712ab | 42 | expect(data).to.have.length.at.least(1) |
b2111066 C |
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 | ||
f40712ab | 56 | function expectTodayLastValue (result: VideoStatsTimeserie, lastValue?: number) { |
b2111066 | 57 | const { data } = result |
b2111066 C |
58 | |
59 | const last = data[data.length - 1] | |
b2111066 C |
60 | const today = new Date().getDate() |
61 | expect(new Date(last.date).getDate()).to.equal(today) | |
f40712ab C |
62 | |
63 | if (lastValue) expect(last.value).to.equal(lastValue) | |
901bcf5c C |
64 | } |
65 | ||
66 | function expectTimeserieData (result: VideoStatsTimeserie, lastValue: number) { | |
67 | const { data } = result | |
f40712ab | 68 | expect(data).to.have.length.at.least(25) |
901bcf5c C |
69 | |
70 | expectTodayLastValue(result, lastValue) | |
b2111066 C |
71 | |
72 | for (let i = 0; i < data.length - 2; i++) { | |
73 | expect(data[i].value).to.equal(0) | |
74 | } | |
75 | } | |
76 | ||
901bcf5c C |
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 | ||
b2111066 | 83 | before(async function () { |
c2419476 | 84 | this.timeout(240000); |
b2111066 C |
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 ]) { | |
f40712ab C |
98 | const result = await servers[0].videoStats.getTimeserieStats({ |
99 | videoId, | |
100 | startDate: buildOneMonthAgo(), | |
101 | endDate: new Date(), | |
102 | metric: 'viewers' | |
103 | }) | |
b2111066 C |
104 | expectTimeserieData(result, 2) |
105 | } | |
106 | }) | |
107 | ||
108 | it('Should display appropriate watch time metrics', async function () { | |
109 | for (const videoId of [ vodVideoId, liveVideoId ]) { | |
f40712ab C |
110 | const result = await servers[0].videoStats.getTimeserieStats({ |
111 | videoId, | |
112 | startDate: buildOneMonthAgo(), | |
113 | endDate: new Date(), | |
114 | metric: 'aggregateWatchTime' | |
115 | }) | |
b2111066 C |
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 ]) { | |
f40712ab C |
124 | const result = await servers[0].videoStats.getTimeserieStats({ |
125 | videoId, | |
126 | startDate: buildOneMonthAgo(), | |
127 | endDate: new Date(), | |
128 | metric: 'aggregateWatchTime' | |
129 | }) | |
b2111066 C |
130 | expectTimeserieData(result, 9) |
131 | } | |
132 | }) | |
133 | ||
901bcf5c C |
134 | it('Should use a custom start/end date', async function () { |
135 | const now = new Date() | |
3eda9b77 C |
136 | const twentyDaysAgo = new Date() |
137 | twentyDaysAgo.setDate(twentyDaysAgo.getDate() - 19) | |
901bcf5c C |
138 | |
139 | const result = await servers[0].videoStats.getTimeserieStats({ | |
140 | videoId: vodVideoId, | |
141 | metric: 'aggregateWatchTime', | |
3eda9b77 | 142 | startDate: twentyDaysAgo, |
901bcf5c C |
143 | endDate: now |
144 | }) | |
145 | ||
3eda9b77 C |
146 | expect(result.groupInterval).to.equal('1 day') |
147 | expect(result.data).to.have.lengthOf(20) | |
901bcf5c C |
148 | |
149 | const first = result.data[0] | |
3eda9b77 | 150 | expect(new Date(first.date).toLocaleDateString()).to.equal(twentyDaysAgo.toLocaleDateString()) |
901bcf5c C |
151 | |
152 | expectInterval(result, 24 * 3600 * 1000) | |
153 | expectTodayLastValue(result, 9) | |
154 | }) | |
155 | ||
f40712ab C |
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 | ||
901bcf5c C |
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 | ||
3eda9b77 | 200 | expect(result.groupInterval).to.equal('1 hour') |
901bcf5c C |
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() | |
3eda9b77 | 210 | twoHoursAgo.setHours(twoHoursAgo.getHours() - 4) |
901bcf5c C |
211 | |
212 | const result = await servers[0].videoStats.getTimeserieStats({ | |
213 | videoId: vodVideoId, | |
214 | metric: 'aggregateWatchTime', | |
215 | startDate: twoHoursAgo, | |
216 | endDate: now | |
217 | }) | |
218 | ||
3eda9b77 C |
219 | expect(result.groupInterval).to.equal('10 minutes') |
220 | expect(result.data).to.have.length.above(20).and.below(30) | |
901bcf5c C |
221 | |
222 | expectInterval(result, 60 * 10 * 1000) | |
f40712ab | 223 | expectTodayLastValue(result) |
901bcf5c C |
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 | ||
3eda9b77 | 238 | expect(result.groupInterval).to.equal('1 minute') |
901bcf5c C |
239 | expect(result.data).to.have.length.above(20).and.below(40) |
240 | ||
241 | expectInterval(result, 60 * 1000) | |
f40712ab | 242 | expectTodayLastValue(result) |
901bcf5c C |
243 | }) |
244 | ||
b2111066 C |
245 | after(async function () { |
246 | await stopFfmpeg(command) | |
247 | }) | |
248 | }) | |
249 | ||
250 | after(async function () { | |
251 | await cleanupTests(servers) | |
252 | }) | |
253 | }) |