diff options
author | Chocobozzz <me@florianbigard.com> | 2023-07-31 14:34:36 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2023-08-11 15:02:33 +0200 |
commit | 3a4992633ee62d5edfbb484d9c6bcb3cf158489d (patch) | |
tree | e4510b39bdac9c318fdb4b47018d08f15368b8f0 /packages/tests/src/plugins | |
parent | 04d1da5621d25d59bd5fa1543b725c497bf5d9a8 (diff) | |
download | PeerTube-3a4992633ee62d5edfbb484d9c6bcb3cf158489d.tar.gz PeerTube-3a4992633ee62d5edfbb484d9c6bcb3cf158489d.tar.zst PeerTube-3a4992633ee62d5edfbb484d9c6bcb3cf158489d.zip |
Migrate server to ESM
Sorry for the very big commit that may lead to git log issues and merge
conflicts, but it's a major step forward:
* Server can be faster at startup because imports() are async and we can
easily lazy import big modules
* Angular doesn't seem to support ES import (with .js extension), so we
had to correctly organize peertube into a monorepo:
* Use yarn workspace feature
* Use typescript reference projects for dependencies
* Shared projects have been moved into "packages", each one is now a
node module (with a dedicated package.json/tsconfig.json)
* server/tools have been moved into apps/ and is now a dedicated app
bundled and published on NPM so users don't have to build peertube
cli tools manually
* server/tests have been moved into packages/ so we don't compile
them every time we want to run the server
* Use isolatedModule option:
* Had to move from const enum to const
(https://www.typescriptlang.org/docs/handbook/enums.html#objects-vs-enums)
* Had to explictely specify "type" imports when used in decorators
* Prefer tsx (that uses esbuild under the hood) instead of ts-node to
load typescript files (tests with mocha or scripts):
* To reduce test complexity as esbuild doesn't support decorator
metadata, we only test server files that do not import server
models
* We still build tests files into js files for a faster CI
* Remove unmaintained peertube CLI import script
* Removed some barrels to speed up execution (less imports)
Diffstat (limited to 'packages/tests/src/plugins')
-rw-r--r-- | packages/tests/src/plugins/action-hooks.ts | 298 | ||||
-rw-r--r-- | packages/tests/src/plugins/external-auth.ts | 436 | ||||
-rw-r--r-- | packages/tests/src/plugins/filter-hooks.ts | 909 | ||||
-rw-r--r-- | packages/tests/src/plugins/html-injection.ts | 73 | ||||
-rw-r--r-- | packages/tests/src/plugins/id-and-pass-auth.ts | 248 | ||||
-rw-r--r-- | packages/tests/src/plugins/index.ts | 13 | ||||
-rw-r--r-- | packages/tests/src/plugins/plugin-helpers.ts | 383 | ||||
-rw-r--r-- | packages/tests/src/plugins/plugin-router.ts | 105 | ||||
-rw-r--r-- | packages/tests/src/plugins/plugin-storage.ts | 95 | ||||
-rw-r--r-- | packages/tests/src/plugins/plugin-transcoding.ts | 279 | ||||
-rw-r--r-- | packages/tests/src/plugins/plugin-unloading.ts | 75 | ||||
-rw-r--r-- | packages/tests/src/plugins/plugin-websocket.ts | 76 | ||||
-rw-r--r-- | packages/tests/src/plugins/translations.ts | 80 | ||||
-rw-r--r-- | packages/tests/src/plugins/video-constants.ts | 180 |
14 files changed, 3250 insertions, 0 deletions
diff --git a/packages/tests/src/plugins/action-hooks.ts b/packages/tests/src/plugins/action-hooks.ts new file mode 100644 index 000000000..136c7671b --- /dev/null +++ b/packages/tests/src/plugins/action-hooks.ts | |||
@@ -0,0 +1,298 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import { ServerHookName, VideoPlaylistPrivacy, VideoPrivacy } from '@peertube/peertube-models' | ||
4 | import { | ||
5 | cleanupTests, | ||
6 | createMultipleServers, | ||
7 | doubleFollow, | ||
8 | killallServers, | ||
9 | PeerTubeServer, | ||
10 | PluginsCommand, | ||
11 | setAccessTokensToServers, | ||
12 | setDefaultVideoChannel, | ||
13 | stopFfmpeg, | ||
14 | waitJobs | ||
15 | } from '@peertube/peertube-server-commands' | ||
16 | |||
17 | describe('Test plugin action hooks', function () { | ||
18 | let servers: PeerTubeServer[] | ||
19 | let videoUUID: string | ||
20 | let threadId: number | ||
21 | |||
22 | function checkHook (hook: ServerHookName, strictCount = true, count = 1) { | ||
23 | return servers[0].servers.waitUntilLog('Run hook ' + hook, count, strictCount) | ||
24 | } | ||
25 | |||
26 | before(async function () { | ||
27 | this.timeout(120000) | ||
28 | |||
29 | servers = await createMultipleServers(2) | ||
30 | await setAccessTokensToServers(servers) | ||
31 | await setDefaultVideoChannel(servers) | ||
32 | |||
33 | await servers[0].plugins.install({ path: PluginsCommand.getPluginTestPath() }) | ||
34 | |||
35 | await killallServers([ servers[0] ]) | ||
36 | |||
37 | await servers[0].run({ | ||
38 | live: { | ||
39 | enabled: true | ||
40 | } | ||
41 | }) | ||
42 | |||
43 | await servers[0].config.enableFileUpdate() | ||
44 | |||
45 | await doubleFollow(servers[0], servers[1]) | ||
46 | }) | ||
47 | |||
48 | describe('Application hooks', function () { | ||
49 | it('Should run action:application.listening', async function () { | ||
50 | await checkHook('action:application.listening') | ||
51 | }) | ||
52 | }) | ||
53 | |||
54 | describe('Videos hooks', function () { | ||
55 | |||
56 | it('Should run action:api.video.uploaded', async function () { | ||
57 | const { uuid } = await servers[0].videos.upload({ attributes: { name: 'video' } }) | ||
58 | videoUUID = uuid | ||
59 | |||
60 | await checkHook('action:api.video.uploaded') | ||
61 | }) | ||
62 | |||
63 | it('Should run action:api.video.updated', async function () { | ||
64 | await servers[0].videos.update({ id: videoUUID, attributes: { name: 'video updated' } }) | ||
65 | |||
66 | await checkHook('action:api.video.updated') | ||
67 | }) | ||
68 | |||
69 | it('Should run action:api.video.viewed', async function () { | ||
70 | await servers[0].views.simulateView({ id: videoUUID }) | ||
71 | |||
72 | await checkHook('action:api.video.viewed') | ||
73 | }) | ||
74 | |||
75 | it('Should run action:api.video.file-updated', async function () { | ||
76 | await servers[0].videos.replaceSourceFile({ videoId: videoUUID, fixture: 'video_short.mp4' }) | ||
77 | |||
78 | await checkHook('action:api.video.file-updated') | ||
79 | }) | ||
80 | |||
81 | it('Should run action:api.video.deleted', async function () { | ||
82 | await servers[0].videos.remove({ id: videoUUID }) | ||
83 | |||
84 | await checkHook('action:api.video.deleted') | ||
85 | }) | ||
86 | |||
87 | after(async function () { | ||
88 | const { uuid } = await servers[0].videos.quickUpload({ name: 'video' }) | ||
89 | videoUUID = uuid | ||
90 | }) | ||
91 | }) | ||
92 | |||
93 | describe('Video channel hooks', function () { | ||
94 | const channelName = 'my_super_channel' | ||
95 | |||
96 | it('Should run action:api.video-channel.created', async function () { | ||
97 | await servers[0].channels.create({ attributes: { name: channelName } }) | ||
98 | |||
99 | await checkHook('action:api.video-channel.created') | ||
100 | }) | ||
101 | |||
102 | it('Should run action:api.video-channel.updated', async function () { | ||
103 | await servers[0].channels.update({ channelName, attributes: { displayName: 'my display name' } }) | ||
104 | |||
105 | await checkHook('action:api.video-channel.updated') | ||
106 | }) | ||
107 | |||
108 | it('Should run action:api.video-channel.deleted', async function () { | ||
109 | await servers[0].channels.delete({ channelName }) | ||
110 | |||
111 | await checkHook('action:api.video-channel.deleted') | ||
112 | }) | ||
113 | }) | ||
114 | |||
115 | describe('Live hooks', function () { | ||
116 | |||
117 | it('Should run action:api.live-video.created', async function () { | ||
118 | const attributes = { | ||
119 | name: 'live', | ||
120 | privacy: VideoPrivacy.PUBLIC, | ||
121 | channelId: servers[0].store.channel.id | ||
122 | } | ||
123 | |||
124 | await servers[0].live.create({ fields: attributes }) | ||
125 | |||
126 | await checkHook('action:api.live-video.created') | ||
127 | }) | ||
128 | |||
129 | it('Should run action:live.video.state.updated', async function () { | ||
130 | this.timeout(60000) | ||
131 | |||
132 | const attributes = { | ||
133 | name: 'live', | ||
134 | privacy: VideoPrivacy.PUBLIC, | ||
135 | channelId: servers[0].store.channel.id | ||
136 | } | ||
137 | |||
138 | const { uuid: liveVideoId } = await servers[0].live.create({ fields: attributes }) | ||
139 | const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoId }) | ||
140 | await servers[0].live.waitUntilPublished({ videoId: liveVideoId }) | ||
141 | await waitJobs(servers) | ||
142 | |||
143 | await checkHook('action:live.video.state.updated', true, 1) | ||
144 | |||
145 | await stopFfmpeg(ffmpegCommand) | ||
146 | await servers[0].live.waitUntilEnded({ videoId: liveVideoId }) | ||
147 | await waitJobs(servers) | ||
148 | |||
149 | await checkHook('action:live.video.state.updated', true, 2) | ||
150 | }) | ||
151 | }) | ||
152 | |||
153 | describe('Comments hooks', function () { | ||
154 | it('Should run action:api.video-thread.created', async function () { | ||
155 | const created = await servers[0].comments.createThread({ videoId: videoUUID, text: 'thread' }) | ||
156 | threadId = created.id | ||
157 | |||
158 | await checkHook('action:api.video-thread.created') | ||
159 | }) | ||
160 | |||
161 | it('Should run action:api.video-comment-reply.created', async function () { | ||
162 | await servers[0].comments.addReply({ videoId: videoUUID, toCommentId: threadId, text: 'reply' }) | ||
163 | |||
164 | await checkHook('action:api.video-comment-reply.created') | ||
165 | }) | ||
166 | |||
167 | it('Should run action:api.video-comment.deleted', async function () { | ||
168 | await servers[0].comments.delete({ videoId: videoUUID, commentId: threadId }) | ||
169 | |||
170 | await checkHook('action:api.video-comment.deleted') | ||
171 | }) | ||
172 | }) | ||
173 | |||
174 | describe('Captions hooks', function () { | ||
175 | it('Should run action:api.video-caption.created', async function () { | ||
176 | await servers[0].captions.add({ videoId: videoUUID, language: 'en', fixture: 'subtitle-good.srt' }) | ||
177 | |||
178 | await checkHook('action:api.video-caption.created') | ||
179 | }) | ||
180 | |||
181 | it('Should run action:api.video-caption.deleted', async function () { | ||
182 | await servers[0].captions.delete({ videoId: videoUUID, language: 'en' }) | ||
183 | |||
184 | await checkHook('action:api.video-caption.deleted') | ||
185 | }) | ||
186 | }) | ||
187 | |||
188 | describe('Users hooks', function () { | ||
189 | let userId: number | ||
190 | |||
191 | it('Should run action:api.user.registered', async function () { | ||
192 | await servers[0].registrations.register({ username: 'registered_user' }) | ||
193 | |||
194 | await checkHook('action:api.user.registered') | ||
195 | }) | ||
196 | |||
197 | it('Should run action:api.user.created', async function () { | ||
198 | const user = await servers[0].users.create({ username: 'created_user' }) | ||
199 | userId = user.id | ||
200 | |||
201 | await checkHook('action:api.user.created') | ||
202 | }) | ||
203 | |||
204 | it('Should run action:api.user.oauth2-got-token', async function () { | ||
205 | await servers[0].login.login({ user: { username: 'created_user' } }) | ||
206 | |||
207 | await checkHook('action:api.user.oauth2-got-token') | ||
208 | }) | ||
209 | |||
210 | it('Should run action:api.user.blocked', async function () { | ||
211 | await servers[0].users.banUser({ userId }) | ||
212 | |||
213 | await checkHook('action:api.user.blocked') | ||
214 | }) | ||
215 | |||
216 | it('Should run action:api.user.unblocked', async function () { | ||
217 | await servers[0].users.unbanUser({ userId }) | ||
218 | |||
219 | await checkHook('action:api.user.unblocked') | ||
220 | }) | ||
221 | |||
222 | it('Should run action:api.user.updated', async function () { | ||
223 | await servers[0].users.update({ userId, videoQuota: 50 }) | ||
224 | |||
225 | await checkHook('action:api.user.updated') | ||
226 | }) | ||
227 | |||
228 | it('Should run action:api.user.deleted', async function () { | ||
229 | await servers[0].users.remove({ userId }) | ||
230 | |||
231 | await checkHook('action:api.user.deleted') | ||
232 | }) | ||
233 | }) | ||
234 | |||
235 | describe('Playlist hooks', function () { | ||
236 | let playlistId: number | ||
237 | let videoId: number | ||
238 | |||
239 | before(async function () { | ||
240 | { | ||
241 | const { id } = await servers[0].playlists.create({ | ||
242 | attributes: { | ||
243 | displayName: 'My playlist', | ||
244 | privacy: VideoPlaylistPrivacy.PRIVATE | ||
245 | } | ||
246 | }) | ||
247 | playlistId = id | ||
248 | } | ||
249 | |||
250 | { | ||
251 | const { id } = await servers[0].videos.upload({ attributes: { name: 'my super name' } }) | ||
252 | videoId = id | ||
253 | } | ||
254 | }) | ||
255 | |||
256 | it('Should run action:api.video-playlist-element.created', async function () { | ||
257 | await servers[0].playlists.addElement({ playlistId, attributes: { videoId } }) | ||
258 | |||
259 | await checkHook('action:api.video-playlist-element.created') | ||
260 | }) | ||
261 | }) | ||
262 | |||
263 | describe('Notification hook', function () { | ||
264 | |||
265 | it('Should run action:notifier.notification.created', async function () { | ||
266 | await checkHook('action:notifier.notification.created', false) | ||
267 | }) | ||
268 | }) | ||
269 | |||
270 | describe('Activity Pub hooks', function () { | ||
271 | let videoUUID: string | ||
272 | |||
273 | it('Should run action:activity-pub.remote-video.created', async function () { | ||
274 | this.timeout(30000) | ||
275 | |||
276 | const { uuid } = await servers[1].videos.quickUpload({ name: 'remote video' }) | ||
277 | videoUUID = uuid | ||
278 | |||
279 | await servers[0].servers.waitUntilLog('action:activity-pub.remote-video.created - AP remote video - video remote video') | ||
280 | }) | ||
281 | |||
282 | it('Should run action:activity-pub.remote-video.updated', async function () { | ||
283 | this.timeout(30000) | ||
284 | |||
285 | await servers[1].videos.update({ id: videoUUID, attributes: { name: 'remote video updated' } }) | ||
286 | |||
287 | await servers[0].servers.waitUntilLog( | ||
288 | 'action:activity-pub.remote-video.updated - AP remote video updated - video remote video updated', | ||
289 | 1, | ||
290 | false | ||
291 | ) | ||
292 | }) | ||
293 | }) | ||
294 | |||
295 | after(async function () { | ||
296 | await cleanupTests(servers) | ||
297 | }) | ||
298 | }) | ||
diff --git a/packages/tests/src/plugins/external-auth.ts b/packages/tests/src/plugins/external-auth.ts new file mode 100644 index 000000000..c7fe22185 --- /dev/null +++ b/packages/tests/src/plugins/external-auth.ts | |||
@@ -0,0 +1,436 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import { expect } from 'chai' | ||
4 | import { wait } from '@peertube/peertube-core-utils' | ||
5 | import { HttpStatusCode, HttpStatusCodeType, UserAdminFlag, UserRole } from '@peertube/peertube-models' | ||
6 | import { | ||
7 | cleanupTests, | ||
8 | createSingleServer, | ||
9 | decodeQueryString, | ||
10 | PeerTubeServer, | ||
11 | PluginsCommand, | ||
12 | setAccessTokensToServers | ||
13 | } from '@peertube/peertube-server-commands' | ||
14 | |||
15 | async function loginExternal (options: { | ||
16 | server: PeerTubeServer | ||
17 | npmName: string | ||
18 | authName: string | ||
19 | username: string | ||
20 | query?: any | ||
21 | expectedStatus?: HttpStatusCodeType | ||
22 | expectedStatusStep2?: HttpStatusCodeType | ||
23 | }) { | ||
24 | const res = await options.server.plugins.getExternalAuth({ | ||
25 | npmName: options.npmName, | ||
26 | npmVersion: '0.0.1', | ||
27 | authName: options.authName, | ||
28 | query: options.query, | ||
29 | expectedStatus: options.expectedStatus || HttpStatusCode.FOUND_302 | ||
30 | }) | ||
31 | |||
32 | if (res.status !== HttpStatusCode.FOUND_302) return | ||
33 | |||
34 | const location = res.header.location | ||
35 | const { externalAuthToken } = decodeQueryString(location) | ||
36 | |||
37 | const resLogin = await options.server.login.loginUsingExternalToken({ | ||
38 | username: options.username, | ||
39 | externalAuthToken: externalAuthToken as string, | ||
40 | expectedStatus: options.expectedStatusStep2 | ||
41 | }) | ||
42 | |||
43 | return resLogin.body | ||
44 | } | ||
45 | |||
46 | describe('Test external auth plugins', function () { | ||
47 | let server: PeerTubeServer | ||
48 | |||
49 | let cyanAccessToken: string | ||
50 | let cyanRefreshToken: string | ||
51 | |||
52 | let kefkaAccessToken: string | ||
53 | let kefkaRefreshToken: string | ||
54 | let kefkaId: number | ||
55 | |||
56 | let externalAuthToken: string | ||
57 | |||
58 | before(async function () { | ||
59 | this.timeout(30000) | ||
60 | |||
61 | server = await createSingleServer(1, { | ||
62 | rates_limit: { | ||
63 | login: { | ||
64 | max: 30 | ||
65 | } | ||
66 | } | ||
67 | }) | ||
68 | |||
69 | await setAccessTokensToServers([ server ]) | ||
70 | |||
71 | for (const suffix of [ 'one', 'two', 'three' ]) { | ||
72 | await server.plugins.install({ path: PluginsCommand.getPluginTestPath('-external-auth-' + suffix) }) | ||
73 | } | ||
74 | }) | ||
75 | |||
76 | it('Should display the correct configuration', async function () { | ||
77 | const config = await server.config.getConfig() | ||
78 | |||
79 | const auths = config.plugin.registeredExternalAuths | ||
80 | expect(auths).to.have.lengthOf(9) | ||
81 | |||
82 | const auth2 = auths.find((a) => a.authName === 'external-auth-2') | ||
83 | expect(auth2).to.exist | ||
84 | expect(auth2.authDisplayName).to.equal('External Auth 2') | ||
85 | expect(auth2.npmName).to.equal('peertube-plugin-test-external-auth-one') | ||
86 | }) | ||
87 | |||
88 | it('Should redirect for a Cyan login', async function () { | ||
89 | const res = await server.plugins.getExternalAuth({ | ||
90 | npmName: 'test-external-auth-one', | ||
91 | npmVersion: '0.0.1', | ||
92 | authName: 'external-auth-1', | ||
93 | query: { | ||
94 | username: 'cyan' | ||
95 | }, | ||
96 | expectedStatus: HttpStatusCode.FOUND_302 | ||
97 | }) | ||
98 | |||
99 | const location = res.header.location | ||
100 | expect(location.startsWith('/login?')).to.be.true | ||
101 | |||
102 | const searchParams = decodeQueryString(location) | ||
103 | |||
104 | expect(searchParams.externalAuthToken).to.exist | ||
105 | expect(searchParams.username).to.equal('cyan') | ||
106 | |||
107 | externalAuthToken = searchParams.externalAuthToken as string | ||
108 | }) | ||
109 | |||
110 | it('Should reject auto external login with a missing or invalid token', async function () { | ||
111 | const command = server.login | ||
112 | |||
113 | await command.loginUsingExternalToken({ username: 'cyan', externalAuthToken: '', expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | ||
114 | await command.loginUsingExternalToken({ username: 'cyan', externalAuthToken: 'blabla', expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | ||
115 | }) | ||
116 | |||
117 | it('Should reject auto external login with a missing or invalid username', async function () { | ||
118 | const command = server.login | ||
119 | |||
120 | await command.loginUsingExternalToken({ username: '', externalAuthToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | ||
121 | await command.loginUsingExternalToken({ username: '', externalAuthToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | ||
122 | }) | ||
123 | |||
124 | it('Should reject auto external login with an expired token', async function () { | ||
125 | this.timeout(15000) | ||
126 | |||
127 | await wait(5000) | ||
128 | |||
129 | await server.login.loginUsingExternalToken({ | ||
130 | username: 'cyan', | ||
131 | externalAuthToken, | ||
132 | expectedStatus: HttpStatusCode.BAD_REQUEST_400 | ||
133 | }) | ||
134 | |||
135 | await server.servers.waitUntilLog('expired external auth token', 4) | ||
136 | }) | ||
137 | |||
138 | it('Should auto login Cyan, create the user and use the token', async function () { | ||
139 | { | ||
140 | const res = await loginExternal({ | ||
141 | server, | ||
142 | npmName: 'test-external-auth-one', | ||
143 | authName: 'external-auth-1', | ||
144 | query: { | ||
145 | username: 'cyan' | ||
146 | }, | ||
147 | username: 'cyan' | ||
148 | }) | ||
149 | |||
150 | cyanAccessToken = res.access_token | ||
151 | cyanRefreshToken = res.refresh_token | ||
152 | } | ||
153 | |||
154 | { | ||
155 | const body = await server.users.getMyInfo({ token: cyanAccessToken }) | ||
156 | expect(body.username).to.equal('cyan') | ||
157 | expect(body.account.displayName).to.equal('cyan') | ||
158 | expect(body.email).to.equal('cyan@example.com') | ||
159 | expect(body.role.id).to.equal(UserRole.USER) | ||
160 | expect(body.adminFlags).to.equal(UserAdminFlag.NONE) | ||
161 | expect(body.videoQuota).to.equal(5242880) | ||
162 | expect(body.videoQuotaDaily).to.equal(-1) | ||
163 | } | ||
164 | }) | ||
165 | |||
166 | it('Should auto login Kefka, create the user and use the token', async function () { | ||
167 | { | ||
168 | const res = await loginExternal({ | ||
169 | server, | ||
170 | npmName: 'test-external-auth-one', | ||
171 | authName: 'external-auth-2', | ||
172 | username: 'kefka' | ||
173 | }) | ||
174 | |||
175 | kefkaAccessToken = res.access_token | ||
176 | kefkaRefreshToken = res.refresh_token | ||
177 | } | ||
178 | |||
179 | { | ||
180 | const body = await server.users.getMyInfo({ token: kefkaAccessToken }) | ||
181 | expect(body.username).to.equal('kefka') | ||
182 | expect(body.account.displayName).to.equal('Kefka Palazzo') | ||
183 | expect(body.email).to.equal('kefka@example.com') | ||
184 | expect(body.role.id).to.equal(UserRole.ADMINISTRATOR) | ||
185 | expect(body.adminFlags).to.equal(UserAdminFlag.BYPASS_VIDEO_AUTO_BLACKLIST) | ||
186 | expect(body.videoQuota).to.equal(42000) | ||
187 | expect(body.videoQuotaDaily).to.equal(42100) | ||
188 | |||
189 | kefkaId = body.id | ||
190 | } | ||
191 | }) | ||
192 | |||
193 | it('Should refresh Cyan token, but not Kefka token', async function () { | ||
194 | { | ||
195 | const resRefresh = await server.login.refreshToken({ refreshToken: cyanRefreshToken }) | ||
196 | cyanAccessToken = resRefresh.body.access_token | ||
197 | cyanRefreshToken = resRefresh.body.refresh_token | ||
198 | |||
199 | const body = await server.users.getMyInfo({ token: cyanAccessToken }) | ||
200 | expect(body.username).to.equal('cyan') | ||
201 | } | ||
202 | |||
203 | { | ||
204 | await server.login.refreshToken({ refreshToken: kefkaRefreshToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | ||
205 | } | ||
206 | }) | ||
207 | |||
208 | it('Should update Cyan profile', async function () { | ||
209 | await server.users.updateMe({ | ||
210 | token: cyanAccessToken, | ||
211 | displayName: 'Cyan Garamonde', | ||
212 | description: 'Retainer to the king of Doma' | ||
213 | }) | ||
214 | |||
215 | const body = await server.users.getMyInfo({ token: cyanAccessToken }) | ||
216 | expect(body.account.displayName).to.equal('Cyan Garamonde') | ||
217 | expect(body.account.description).to.equal('Retainer to the king of Doma') | ||
218 | }) | ||
219 | |||
220 | it('Should logout Cyan', async function () { | ||
221 | await server.login.logout({ token: cyanAccessToken }) | ||
222 | }) | ||
223 | |||
224 | it('Should have logged out Cyan', async function () { | ||
225 | await server.servers.waitUntilLog('On logout cyan') | ||
226 | |||
227 | await server.users.getMyInfo({ token: cyanAccessToken, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) | ||
228 | }) | ||
229 | |||
230 | it('Should login Cyan and keep the old existing profile', async function () { | ||
231 | { | ||
232 | const res = await loginExternal({ | ||
233 | server, | ||
234 | npmName: 'test-external-auth-one', | ||
235 | authName: 'external-auth-1', | ||
236 | query: { | ||
237 | username: 'cyan' | ||
238 | }, | ||
239 | username: 'cyan' | ||
240 | }) | ||
241 | |||
242 | cyanAccessToken = res.access_token | ||
243 | } | ||
244 | |||
245 | const body = await server.users.getMyInfo({ token: cyanAccessToken }) | ||
246 | expect(body.username).to.equal('cyan') | ||
247 | expect(body.account.displayName).to.equal('Cyan Garamonde') | ||
248 | expect(body.account.description).to.equal('Retainer to the king of Doma') | ||
249 | expect(body.role.id).to.equal(UserRole.USER) | ||
250 | }) | ||
251 | |||
252 | it('Should login Kefka and update the profile', async function () { | ||
253 | { | ||
254 | await server.users.update({ userId: kefkaId, videoQuota: 43000, videoQuotaDaily: 43100 }) | ||
255 | await server.users.updateMe({ token: kefkaAccessToken, displayName: 'kefka updated' }) | ||
256 | |||
257 | const body = await server.users.getMyInfo({ token: kefkaAccessToken }) | ||
258 | expect(body.username).to.equal('kefka') | ||
259 | expect(body.account.displayName).to.equal('kefka updated') | ||
260 | expect(body.videoQuota).to.equal(43000) | ||
261 | expect(body.videoQuotaDaily).to.equal(43100) | ||
262 | } | ||
263 | |||
264 | { | ||
265 | const res = await loginExternal({ | ||
266 | server, | ||
267 | npmName: 'test-external-auth-one', | ||
268 | authName: 'external-auth-2', | ||
269 | username: 'kefka' | ||
270 | }) | ||
271 | |||
272 | kefkaAccessToken = res.access_token | ||
273 | kefkaRefreshToken = res.refresh_token | ||
274 | |||
275 | const body = await server.users.getMyInfo({ token: kefkaAccessToken }) | ||
276 | expect(body.username).to.equal('kefka') | ||
277 | expect(body.account.displayName).to.equal('Kefka Palazzo') | ||
278 | expect(body.videoQuota).to.equal(42000) | ||
279 | expect(body.videoQuotaDaily).to.equal(43100) | ||
280 | } | ||
281 | }) | ||
282 | |||
283 | it('Should not update an external auth email', async function () { | ||
284 | await server.users.updateMe({ | ||
285 | token: cyanAccessToken, | ||
286 | email: 'toto@example.com', | ||
287 | currentPassword: 'toto', | ||
288 | expectedStatus: HttpStatusCode.BAD_REQUEST_400 | ||
289 | }) | ||
290 | }) | ||
291 | |||
292 | it('Should reject token of Kefka by the plugin hook', async function () { | ||
293 | await wait(5000) | ||
294 | |||
295 | await server.users.getMyInfo({ token: kefkaAccessToken, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) | ||
296 | }) | ||
297 | |||
298 | it('Should unregister external-auth-2 and do not login existing Kefka', async function () { | ||
299 | await server.plugins.updateSettings({ | ||
300 | npmName: 'peertube-plugin-test-external-auth-one', | ||
301 | settings: { disableKefka: true } | ||
302 | }) | ||
303 | |||
304 | await server.login.login({ user: { username: 'kefka', password: 'fake' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | ||
305 | |||
306 | await loginExternal({ | ||
307 | server, | ||
308 | npmName: 'test-external-auth-one', | ||
309 | authName: 'external-auth-2', | ||
310 | query: { | ||
311 | username: 'kefka' | ||
312 | }, | ||
313 | username: 'kefka', | ||
314 | expectedStatus: HttpStatusCode.NOT_FOUND_404 | ||
315 | }) | ||
316 | }) | ||
317 | |||
318 | it('Should have disabled this auth', async function () { | ||
319 | const config = await server.config.getConfig() | ||
320 | |||
321 | const auths = config.plugin.registeredExternalAuths | ||
322 | expect(auths).to.have.lengthOf(8) | ||
323 | |||
324 | const auth1 = auths.find(a => a.authName === 'external-auth-2') | ||
325 | expect(auth1).to.not.exist | ||
326 | }) | ||
327 | |||
328 | it('Should uninstall the plugin one and do not login Cyan', async function () { | ||
329 | await server.plugins.uninstall({ npmName: 'peertube-plugin-test-external-auth-one' }) | ||
330 | |||
331 | await loginExternal({ | ||
332 | server, | ||
333 | npmName: 'test-external-auth-one', | ||
334 | authName: 'external-auth-1', | ||
335 | query: { | ||
336 | username: 'cyan' | ||
337 | }, | ||
338 | username: 'cyan', | ||
339 | expectedStatus: HttpStatusCode.NOT_FOUND_404 | ||
340 | }) | ||
341 | |||
342 | await server.login.login({ user: { username: 'cyan', password: null }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | ||
343 | await server.login.login({ user: { username: 'cyan', password: '' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | ||
344 | await server.login.login({ user: { username: 'cyan', password: 'fake' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | ||
345 | }) | ||
346 | |||
347 | it('Should not login kefka with another plugin', async function () { | ||
348 | await loginExternal({ | ||
349 | server, | ||
350 | npmName: 'test-external-auth-two', | ||
351 | authName: 'external-auth-4', | ||
352 | username: 'kefka2', | ||
353 | expectedStatusStep2: HttpStatusCode.BAD_REQUEST_400 | ||
354 | }) | ||
355 | |||
356 | await loginExternal({ | ||
357 | server, | ||
358 | npmName: 'test-external-auth-two', | ||
359 | authName: 'external-auth-4', | ||
360 | username: 'kefka', | ||
361 | expectedStatusStep2: HttpStatusCode.BAD_REQUEST_400 | ||
362 | }) | ||
363 | }) | ||
364 | |||
365 | it('Should not login an existing user email', async function () { | ||
366 | await server.users.create({ username: 'existing_user', password: 'super_password' }) | ||
367 | |||
368 | await loginExternal({ | ||
369 | server, | ||
370 | npmName: 'test-external-auth-two', | ||
371 | authName: 'external-auth-6', | ||
372 | username: 'existing_user', | ||
373 | expectedStatusStep2: HttpStatusCode.BAD_REQUEST_400 | ||
374 | }) | ||
375 | }) | ||
376 | |||
377 | it('Should be able to login an existing user username and channel', async function () { | ||
378 | await server.users.create({ username: 'existing_user2' }) | ||
379 | await server.users.create({ username: 'existing_user2-1_channel' }) | ||
380 | |||
381 | // Test twice to ensure we don't generate a username on every login | ||
382 | for (let i = 0; i < 2; i++) { | ||
383 | const res = await loginExternal({ | ||
384 | server, | ||
385 | npmName: 'test-external-auth-two', | ||
386 | authName: 'external-auth-7', | ||
387 | username: 'existing_user2' | ||
388 | }) | ||
389 | |||
390 | const token = res.access_token | ||
391 | |||
392 | const myInfo = await server.users.getMyInfo({ token }) | ||
393 | expect(myInfo.username).to.equal('existing_user2-1') | ||
394 | |||
395 | expect(myInfo.videoChannels[0].name).to.equal('existing_user2-1_channel-1') | ||
396 | } | ||
397 | }) | ||
398 | |||
399 | it('Should display the correct configuration', async function () { | ||
400 | const config = await server.config.getConfig() | ||
401 | |||
402 | const auths = config.plugin.registeredExternalAuths | ||
403 | expect(auths).to.have.lengthOf(7) | ||
404 | |||
405 | const auth2 = auths.find((a) => a.authName === 'external-auth-2') | ||
406 | expect(auth2).to.not.exist | ||
407 | }) | ||
408 | |||
409 | after(async function () { | ||
410 | await cleanupTests([ server ]) | ||
411 | }) | ||
412 | |||
413 | it('Should forward the redirectUrl if the plugin returns one', async function () { | ||
414 | const resLogin = await loginExternal({ | ||
415 | server, | ||
416 | npmName: 'test-external-auth-three', | ||
417 | authName: 'external-auth-7', | ||
418 | username: 'cid' | ||
419 | }) | ||
420 | |||
421 | const { redirectUrl } = await server.login.logout({ token: resLogin.access_token }) | ||
422 | expect(redirectUrl).to.equal('https://example.com/redirectUrl') | ||
423 | }) | ||
424 | |||
425 | it('Should call the plugin\'s onLogout method with the request', async function () { | ||
426 | const resLogin = await loginExternal({ | ||
427 | server, | ||
428 | npmName: 'test-external-auth-three', | ||
429 | authName: 'external-auth-8', | ||
430 | username: 'cid' | ||
431 | }) | ||
432 | |||
433 | const { redirectUrl } = await server.login.logout({ token: resLogin.access_token }) | ||
434 | expect(redirectUrl).to.equal('https://example.com/redirectUrl?access_token=' + resLogin.access_token) | ||
435 | }) | ||
436 | }) | ||
diff --git a/packages/tests/src/plugins/filter-hooks.ts b/packages/tests/src/plugins/filter-hooks.ts new file mode 100644 index 000000000..88cfee631 --- /dev/null +++ b/packages/tests/src/plugins/filter-hooks.ts | |||
@@ -0,0 +1,909 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import { expect } from 'chai' | ||
4 | import { | ||
5 | HttpStatusCode, | ||
6 | PeerTubeProblemDocument, | ||
7 | VideoDetails, | ||
8 | VideoImportState, | ||
9 | VideoPlaylist, | ||
10 | VideoPlaylistPrivacy, | ||
11 | VideoPrivacy | ||
12 | } from '@peertube/peertube-models' | ||
13 | import { | ||
14 | cleanupTests, | ||
15 | createMultipleServers, | ||
16 | doubleFollow, | ||
17 | makeActivityPubGetRequest, | ||
18 | makeGetRequest, | ||
19 | makeRawRequest, | ||
20 | PeerTubeServer, | ||
21 | PluginsCommand, | ||
22 | setAccessTokensToServers, | ||
23 | setDefaultVideoChannel, | ||
24 | waitJobs | ||
25 | } from '@peertube/peertube-server-commands' | ||
26 | import { FIXTURE_URLS } from '../shared/tests.js' | ||
27 | |||
28 | describe('Test plugin filter hooks', function () { | ||
29 | let servers: PeerTubeServer[] | ||
30 | let videoUUID: string | ||
31 | let threadId: number | ||
32 | let videoPlaylistUUID: string | ||
33 | |||
34 | before(async function () { | ||
35 | this.timeout(120000) | ||
36 | |||
37 | servers = await createMultipleServers(2) | ||
38 | await setAccessTokensToServers(servers) | ||
39 | await setDefaultVideoChannel(servers) | ||
40 | await doubleFollow(servers[0], servers[1]) | ||
41 | |||
42 | await servers[0].plugins.install({ path: PluginsCommand.getPluginTestPath() }) | ||
43 | await servers[0].plugins.install({ path: PluginsCommand.getPluginTestPath('-filter-translations') }) | ||
44 | { | ||
45 | ({ uuid: videoPlaylistUUID } = await servers[0].playlists.create({ | ||
46 | attributes: { | ||
47 | displayName: 'my super playlist', | ||
48 | privacy: VideoPlaylistPrivacy.PUBLIC, | ||
49 | description: 'my super description', | ||
50 | videoChannelId: servers[0].store.channel.id | ||
51 | } | ||
52 | })) | ||
53 | } | ||
54 | |||
55 | for (let i = 0; i < 10; i++) { | ||
56 | const video = await servers[0].videos.upload({ attributes: { name: 'default video ' + i } }) | ||
57 | await servers[0].playlists.addElement({ playlistId: videoPlaylistUUID, attributes: { videoId: video.id } }) | ||
58 | } | ||
59 | |||
60 | const { data } = await servers[0].videos.list() | ||
61 | videoUUID = data[0].uuid | ||
62 | |||
63 | await servers[0].config.updateCustomSubConfig({ | ||
64 | newConfig: { | ||
65 | live: { enabled: true }, | ||
66 | signup: { enabled: true }, | ||
67 | videoFile: { | ||
68 | update: { | ||
69 | enabled: true | ||
70 | } | ||
71 | }, | ||
72 | import: { | ||
73 | videos: { | ||
74 | http: { enabled: true }, | ||
75 | torrent: { enabled: true } | ||
76 | } | ||
77 | } | ||
78 | } | ||
79 | }) | ||
80 | |||
81 | // Root subscribes to itself | ||
82 | await servers[0].subscriptions.add({ targetUri: 'root_channel@' + servers[0].host }) | ||
83 | }) | ||
84 | |||
85 | describe('Videos', function () { | ||
86 | |||
87 | it('Should run filter:api.videos.list.params', async function () { | ||
88 | const { data } = await servers[0].videos.list({ start: 0, count: 2 }) | ||
89 | |||
90 | // 2 plugins do +1 to the count parameter | ||
91 | expect(data).to.have.lengthOf(4) | ||
92 | }) | ||
93 | |||
94 | it('Should run filter:api.videos.list.result', async function () { | ||
95 | const { total } = await servers[0].videos.list({ start: 0, count: 0 }) | ||
96 | |||
97 | // Plugin do +1 to the total result | ||
98 | expect(total).to.equal(11) | ||
99 | }) | ||
100 | |||
101 | it('Should run filter:api.video-playlist.videos.list.params', async function () { | ||
102 | const { data } = await servers[0].playlists.listVideos({ | ||
103 | count: 2, | ||
104 | playlistId: videoPlaylistUUID | ||
105 | }) | ||
106 | |||
107 | // 1 plugin do +1 to the count parameter | ||
108 | expect(data).to.have.lengthOf(3) | ||
109 | }) | ||
110 | |||
111 | it('Should run filter:api.video-playlist.videos.list.result', async function () { | ||
112 | const { total } = await servers[0].playlists.listVideos({ | ||
113 | count: 0, | ||
114 | playlistId: videoPlaylistUUID | ||
115 | }) | ||
116 | |||
117 | // Plugin do +1 to the total result | ||
118 | expect(total).to.equal(11) | ||
119 | }) | ||
120 | |||
121 | it('Should run filter:api.accounts.videos.list.params', async function () { | ||
122 | const { data } = await servers[0].videos.listByAccount({ handle: 'root', start: 0, count: 2 }) | ||
123 | |||
124 | // 1 plugin do +1 to the count parameter | ||
125 | expect(data).to.have.lengthOf(3) | ||
126 | }) | ||
127 | |||
128 | it('Should run filter:api.accounts.videos.list.result', async function () { | ||
129 | const { total } = await servers[0].videos.listByAccount({ handle: 'root', start: 0, count: 2 }) | ||
130 | |||
131 | // Plugin do +2 to the total result | ||
132 | expect(total).to.equal(12) | ||
133 | }) | ||
134 | |||
135 | it('Should run filter:api.video-channels.videos.list.params', async function () { | ||
136 | const { data } = await servers[0].videos.listByChannel({ handle: 'root_channel', start: 0, count: 2 }) | ||
137 | |||
138 | // 1 plugin do +3 to the count parameter | ||
139 | expect(data).to.have.lengthOf(5) | ||
140 | }) | ||
141 | |||
142 | it('Should run filter:api.video-channels.videos.list.result', async function () { | ||
143 | const { total } = await servers[0].videos.listByChannel({ handle: 'root_channel', start: 0, count: 2 }) | ||
144 | |||
145 | // Plugin do +3 to the total result | ||
146 | expect(total).to.equal(13) | ||
147 | }) | ||
148 | |||
149 | it('Should run filter:api.user.me.videos.list.params', async function () { | ||
150 | const { data } = await servers[0].videos.listMyVideos({ start: 0, count: 2 }) | ||
151 | |||
152 | // 1 plugin do +4 to the count parameter | ||
153 | expect(data).to.have.lengthOf(6) | ||
154 | }) | ||
155 | |||
156 | it('Should run filter:api.user.me.videos.list.result', async function () { | ||
157 | const { total } = await servers[0].videos.listMyVideos({ start: 0, count: 2 }) | ||
158 | |||
159 | // Plugin do +4 to the total result | ||
160 | expect(total).to.equal(14) | ||
161 | }) | ||
162 | |||
163 | it('Should run filter:api.user.me.subscription-videos.list.params', async function () { | ||
164 | const { data } = await servers[0].videos.listMySubscriptionVideos({ start: 0, count: 2 }) | ||
165 | |||
166 | // 1 plugin do +1 to the count parameter | ||
167 | expect(data).to.have.lengthOf(3) | ||
168 | }) | ||
169 | |||
170 | it('Should run filter:api.user.me.subscription-videos.list.result', async function () { | ||
171 | const { total } = await servers[0].videos.listMySubscriptionVideos({ start: 0, count: 2 }) | ||
172 | |||
173 | // Plugin do +4 to the total result | ||
174 | expect(total).to.equal(14) | ||
175 | }) | ||
176 | |||
177 | it('Should run filter:api.video.get.result', async function () { | ||
178 | const video = await servers[0].videos.get({ id: videoUUID }) | ||
179 | expect(video.name).to.contain('<3') | ||
180 | }) | ||
181 | }) | ||
182 | |||
183 | describe('Video/live/import accept', function () { | ||
184 | |||
185 | it('Should run filter:api.video.upload.accept.result', async function () { | ||
186 | const options = { attributes: { name: 'video with bad word' }, expectedStatus: HttpStatusCode.FORBIDDEN_403 } | ||
187 | await servers[0].videos.upload({ mode: 'legacy', ...options }) | ||
188 | await servers[0].videos.upload({ mode: 'resumable', ...options }) | ||
189 | }) | ||
190 | |||
191 | it('Should run filter:api.video.update-file.accept.result', async function () { | ||
192 | const res = await servers[0].videos.replaceSourceFile({ | ||
193 | videoId: videoUUID, | ||
194 | fixture: 'video_short1.webm', | ||
195 | completedExpectedStatus: HttpStatusCode.FORBIDDEN_403 | ||
196 | }) | ||
197 | |||
198 | expect((res as any)?.error).to.equal('no webm') | ||
199 | }) | ||
200 | |||
201 | it('Should run filter:api.live-video.create.accept.result', async function () { | ||
202 | const attributes = { | ||
203 | name: 'video with bad word', | ||
204 | privacy: VideoPrivacy.PUBLIC, | ||
205 | channelId: servers[0].store.channel.id | ||
206 | } | ||
207 | |||
208 | await servers[0].live.create({ fields: attributes, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | ||
209 | }) | ||
210 | |||
211 | it('Should run filter:api.video.pre-import-url.accept.result', async function () { | ||
212 | const attributes = { | ||
213 | name: 'normal title', | ||
214 | privacy: VideoPrivacy.PUBLIC, | ||
215 | channelId: servers[0].store.channel.id, | ||
216 | targetUrl: FIXTURE_URLS.goodVideo + 'bad' | ||
217 | } | ||
218 | await servers[0].imports.importVideo({ attributes, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | ||
219 | }) | ||
220 | |||
221 | it('Should run filter:api.video.pre-import-torrent.accept.result', async function () { | ||
222 | const attributes = { | ||
223 | name: 'bad torrent', | ||
224 | privacy: VideoPrivacy.PUBLIC, | ||
225 | channelId: servers[0].store.channel.id, | ||
226 | torrentfile: 'video-720p.torrent' as any | ||
227 | } | ||
228 | await servers[0].imports.importVideo({ attributes, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | ||
229 | }) | ||
230 | |||
231 | it('Should run filter:api.video.post-import-url.accept.result', async function () { | ||
232 | this.timeout(60000) | ||
233 | |||
234 | let videoImportId: number | ||
235 | |||
236 | { | ||
237 | const attributes = { | ||
238 | name: 'title with bad word', | ||
239 | privacy: VideoPrivacy.PUBLIC, | ||
240 | channelId: servers[0].store.channel.id, | ||
241 | targetUrl: FIXTURE_URLS.goodVideo | ||
242 | } | ||
243 | const body = await servers[0].imports.importVideo({ attributes }) | ||
244 | videoImportId = body.id | ||
245 | } | ||
246 | |||
247 | await waitJobs(servers) | ||
248 | |||
249 | { | ||
250 | const body = await servers[0].imports.getMyVideoImports() | ||
251 | const videoImports = body.data | ||
252 | |||
253 | const videoImport = videoImports.find(i => i.id === videoImportId) | ||
254 | |||
255 | expect(videoImport.state.id).to.equal(VideoImportState.REJECTED) | ||
256 | expect(videoImport.state.label).to.equal('Rejected') | ||
257 | } | ||
258 | }) | ||
259 | |||
260 | it('Should run filter:api.video.post-import-torrent.accept.result', async function () { | ||
261 | this.timeout(60000) | ||
262 | |||
263 | let videoImportId: number | ||
264 | |||
265 | { | ||
266 | const attributes = { | ||
267 | name: 'title with bad word', | ||
268 | privacy: VideoPrivacy.PUBLIC, | ||
269 | channelId: servers[0].store.channel.id, | ||
270 | torrentfile: 'video-720p.torrent' as any | ||
271 | } | ||
272 | const body = await servers[0].imports.importVideo({ attributes }) | ||
273 | videoImportId = body.id | ||
274 | } | ||
275 | |||
276 | await waitJobs(servers) | ||
277 | |||
278 | { | ||
279 | const { data: videoImports } = await servers[0].imports.getMyVideoImports() | ||
280 | |||
281 | const videoImport = videoImports.find(i => i.id === videoImportId) | ||
282 | |||
283 | expect(videoImport.state.id).to.equal(VideoImportState.REJECTED) | ||
284 | expect(videoImport.state.label).to.equal('Rejected') | ||
285 | } | ||
286 | }) | ||
287 | }) | ||
288 | |||
289 | describe('Video comments accept', function () { | ||
290 | |||
291 | it('Should run filter:api.video-thread.create.accept.result', async function () { | ||
292 | await servers[0].comments.createThread({ | ||
293 | videoId: videoUUID, | ||
294 | text: 'comment with bad word', | ||
295 | expectedStatus: HttpStatusCode.FORBIDDEN_403 | ||
296 | }) | ||
297 | }) | ||
298 | |||
299 | it('Should run filter:api.video-comment-reply.create.accept.result', async function () { | ||
300 | const created = await servers[0].comments.createThread({ videoId: videoUUID, text: 'thread' }) | ||
301 | threadId = created.id | ||
302 | |||
303 | await servers[0].comments.addReply({ | ||
304 | videoId: videoUUID, | ||
305 | toCommentId: threadId, | ||
306 | text: 'comment with bad word', | ||
307 | expectedStatus: HttpStatusCode.FORBIDDEN_403 | ||
308 | }) | ||
309 | await servers[0].comments.addReply({ | ||
310 | videoId: videoUUID, | ||
311 | toCommentId: threadId, | ||
312 | text: 'comment with good word', | ||
313 | expectedStatus: HttpStatusCode.OK_200 | ||
314 | }) | ||
315 | }) | ||
316 | |||
317 | it('Should run filter:activity-pub.remote-video-comment.create.accept.result on a thread creation', async function () { | ||
318 | this.timeout(30000) | ||
319 | |||
320 | await servers[1].comments.createThread({ videoId: videoUUID, text: 'comment with bad word' }) | ||
321 | |||
322 | await waitJobs(servers) | ||
323 | |||
324 | { | ||
325 | const thread = await servers[0].comments.listThreads({ videoId: videoUUID }) | ||
326 | expect(thread.data).to.have.lengthOf(1) | ||
327 | expect(thread.data[0].text).to.not.include(' bad ') | ||
328 | } | ||
329 | |||
330 | { | ||
331 | const thread = await servers[1].comments.listThreads({ videoId: videoUUID }) | ||
332 | expect(thread.data).to.have.lengthOf(2) | ||
333 | } | ||
334 | }) | ||
335 | |||
336 | it('Should run filter:activity-pub.remote-video-comment.create.accept.result on a reply creation', async function () { | ||
337 | this.timeout(30000) | ||
338 | |||
339 | const { data } = await servers[1].comments.listThreads({ videoId: videoUUID }) | ||
340 | const threadIdServer2 = data.find(t => t.text === 'thread').id | ||
341 | |||
342 | await servers[1].comments.addReply({ | ||
343 | videoId: videoUUID, | ||
344 | toCommentId: threadIdServer2, | ||
345 | text: 'comment with bad word' | ||
346 | }) | ||
347 | |||
348 | await waitJobs(servers) | ||
349 | |||
350 | { | ||
351 | const tree = await servers[0].comments.getThread({ videoId: videoUUID, threadId }) | ||
352 | expect(tree.children).to.have.lengthOf(1) | ||
353 | expect(tree.children[0].comment.text).to.not.include(' bad ') | ||
354 | } | ||
355 | |||
356 | { | ||
357 | const tree = await servers[1].comments.getThread({ videoId: videoUUID, threadId: threadIdServer2 }) | ||
358 | expect(tree.children).to.have.lengthOf(2) | ||
359 | } | ||
360 | }) | ||
361 | }) | ||
362 | |||
363 | describe('Video comments', function () { | ||
364 | |||
365 | it('Should run filter:api.video-threads.list.params', async function () { | ||
366 | const { data } = await servers[0].comments.listThreads({ videoId: videoUUID, start: 0, count: 0 }) | ||
367 | |||
368 | // our plugin do +1 to the count parameter | ||
369 | expect(data).to.have.lengthOf(1) | ||
370 | }) | ||
371 | |||
372 | it('Should run filter:api.video-threads.list.result', async function () { | ||
373 | const { total } = await servers[0].comments.listThreads({ videoId: videoUUID, start: 0, count: 0 }) | ||
374 | |||
375 | // Plugin do +1 to the total result | ||
376 | expect(total).to.equal(2) | ||
377 | }) | ||
378 | |||
379 | it('Should run filter:api.video-thread-comments.list.params') | ||
380 | |||
381 | it('Should run filter:api.video-thread-comments.list.result', async function () { | ||
382 | const thread = await servers[0].comments.getThread({ videoId: videoUUID, threadId }) | ||
383 | |||
384 | expect(thread.comment.text.endsWith(' <3')).to.be.true | ||
385 | }) | ||
386 | |||
387 | it('Should run filter:api.overviews.videos.list.{params,result}', async function () { | ||
388 | await servers[0].overviews.getVideos({ page: 1 }) | ||
389 | |||
390 | // 3 because we get 3 samples per page | ||
391 | await servers[0].servers.waitUntilLog('Run hook filter:api.overviews.videos.list.params', 3) | ||
392 | await servers[0].servers.waitUntilLog('Run hook filter:api.overviews.videos.list.result', 3) | ||
393 | }) | ||
394 | }) | ||
395 | |||
396 | describe('filter:video.auto-blacklist.result', function () { | ||
397 | |||
398 | async function checkIsBlacklisted (id: number | string, value: boolean) { | ||
399 | const video = await servers[0].videos.getWithToken({ id }) | ||
400 | expect(video.blacklisted).to.equal(value) | ||
401 | } | ||
402 | |||
403 | it('Should blacklist on upload', async function () { | ||
404 | const { uuid } = await servers[0].videos.upload({ attributes: { name: 'video please blacklist me' } }) | ||
405 | await checkIsBlacklisted(uuid, true) | ||
406 | }) | ||
407 | |||
408 | it('Should blacklist on import', async function () { | ||
409 | this.timeout(15000) | ||
410 | |||
411 | const attributes = { | ||
412 | name: 'video please blacklist me', | ||
413 | targetUrl: FIXTURE_URLS.goodVideo, | ||
414 | channelId: servers[0].store.channel.id | ||
415 | } | ||
416 | const body = await servers[0].imports.importVideo({ attributes }) | ||
417 | await checkIsBlacklisted(body.video.uuid, true) | ||
418 | }) | ||
419 | |||
420 | it('Should blacklist on update', async function () { | ||
421 | const { uuid } = await servers[0].videos.upload({ attributes: { name: 'video' } }) | ||
422 | await checkIsBlacklisted(uuid, false) | ||
423 | |||
424 | await servers[0].videos.update({ id: uuid, attributes: { name: 'please blacklist me' } }) | ||
425 | await checkIsBlacklisted(uuid, true) | ||
426 | }) | ||
427 | |||
428 | it('Should blacklist on remote upload', async function () { | ||
429 | this.timeout(120000) | ||
430 | |||
431 | const { uuid } = await servers[1].videos.upload({ attributes: { name: 'remote please blacklist me' } }) | ||
432 | await waitJobs(servers) | ||
433 | |||
434 | await checkIsBlacklisted(uuid, true) | ||
435 | }) | ||
436 | |||
437 | it('Should blacklist on remote update', async function () { | ||
438 | this.timeout(120000) | ||
439 | |||
440 | const { uuid } = await servers[1].videos.upload({ attributes: { name: 'video' } }) | ||
441 | await waitJobs(servers) | ||
442 | |||
443 | await checkIsBlacklisted(uuid, false) | ||
444 | |||
445 | await servers[1].videos.update({ id: uuid, attributes: { name: 'please blacklist me' } }) | ||
446 | await waitJobs(servers) | ||
447 | |||
448 | await checkIsBlacklisted(uuid, true) | ||
449 | }) | ||
450 | }) | ||
451 | |||
452 | describe('Should run filter:api.user.signup.allowed.result', function () { | ||
453 | |||
454 | before(async function () { | ||
455 | await servers[0].config.updateExistingSubConfig({ newConfig: { signup: { requiresApproval: false } } }) | ||
456 | }) | ||
457 | |||
458 | it('Should run on config endpoint', async function () { | ||
459 | const body = await servers[0].config.getConfig() | ||
460 | expect(body.signup.allowed).to.be.true | ||
461 | }) | ||
462 | |||
463 | it('Should allow a signup', async function () { | ||
464 | await servers[0].registrations.register({ username: 'john1' }) | ||
465 | }) | ||
466 | |||
467 | it('Should not allow a signup', async function () { | ||
468 | const res = await servers[0].registrations.register({ | ||
469 | username: 'jma 1', | ||
470 | expectedStatus: HttpStatusCode.FORBIDDEN_403 | ||
471 | }) | ||
472 | |||
473 | expect(res.body.error).to.equal('No jma 1') | ||
474 | }) | ||
475 | }) | ||
476 | |||
477 | describe('Should run filter:api.user.request-signup.allowed.result', function () { | ||
478 | |||
479 | before(async function () { | ||
480 | await servers[0].config.updateExistingSubConfig({ newConfig: { signup: { requiresApproval: true } } }) | ||
481 | }) | ||
482 | |||
483 | it('Should run on config endpoint', async function () { | ||
484 | const body = await servers[0].config.getConfig() | ||
485 | expect(body.signup.allowed).to.be.true | ||
486 | }) | ||
487 | |||
488 | it('Should allow a signup request', async function () { | ||
489 | await servers[0].registrations.requestRegistration({ username: 'john2', registrationReason: 'tt' }) | ||
490 | }) | ||
491 | |||
492 | it('Should not allow a signup request', async function () { | ||
493 | const body = await servers[0].registrations.requestRegistration({ | ||
494 | username: 'jma 2', | ||
495 | registrationReason: 'tt', | ||
496 | expectedStatus: HttpStatusCode.FORBIDDEN_403 | ||
497 | }) | ||
498 | |||
499 | expect((body as unknown as PeerTubeProblemDocument).error).to.equal('No jma 2') | ||
500 | }) | ||
501 | }) | ||
502 | |||
503 | describe('Download hooks', function () { | ||
504 | const downloadVideos: VideoDetails[] = [] | ||
505 | let downloadVideo2Token: string | ||
506 | |||
507 | before(async function () { | ||
508 | this.timeout(120000) | ||
509 | |||
510 | await servers[0].config.updateCustomSubConfig({ | ||
511 | newConfig: { | ||
512 | transcoding: { | ||
513 | webVideos: { | ||
514 | enabled: true | ||
515 | }, | ||
516 | hls: { | ||
517 | enabled: true | ||
518 | } | ||
519 | } | ||
520 | } | ||
521 | }) | ||
522 | |||
523 | const uuids: string[] = [] | ||
524 | |||
525 | for (const name of [ 'bad torrent', 'bad file', 'bad playlist file' ]) { | ||
526 | const uuid = (await servers[0].videos.quickUpload({ name })).uuid | ||
527 | uuids.push(uuid) | ||
528 | } | ||
529 | |||
530 | await waitJobs(servers) | ||
531 | |||
532 | for (const uuid of uuids) { | ||
533 | downloadVideos.push(await servers[0].videos.get({ id: uuid })) | ||
534 | } | ||
535 | |||
536 | downloadVideo2Token = await servers[0].videoToken.getVideoFileToken({ videoId: downloadVideos[2].uuid }) | ||
537 | }) | ||
538 | |||
539 | it('Should run filter:api.download.torrent.allowed.result', async function () { | ||
540 | const res = await makeRawRequest({ url: downloadVideos[0].files[0].torrentDownloadUrl, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | ||
541 | expect(res.body.error).to.equal('Liu Bei') | ||
542 | |||
543 | await makeRawRequest({ url: downloadVideos[1].files[0].torrentDownloadUrl, expectedStatus: HttpStatusCode.OK_200 }) | ||
544 | await makeRawRequest({ url: downloadVideos[2].files[0].torrentDownloadUrl, expectedStatus: HttpStatusCode.OK_200 }) | ||
545 | }) | ||
546 | |||
547 | it('Should run filter:api.download.video.allowed.result', async function () { | ||
548 | { | ||
549 | const refused = downloadVideos[1].files[0].fileDownloadUrl | ||
550 | const allowed = [ | ||
551 | downloadVideos[0].files[0].fileDownloadUrl, | ||
552 | downloadVideos[2].files[0].fileDownloadUrl | ||
553 | ] | ||
554 | |||
555 | const res = await makeRawRequest({ url: refused, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | ||
556 | expect(res.body.error).to.equal('Cao Cao') | ||
557 | |||
558 | for (const url of allowed) { | ||
559 | await makeRawRequest({ url, expectedStatus: HttpStatusCode.OK_200 }) | ||
560 | await makeRawRequest({ url, expectedStatus: HttpStatusCode.OK_200 }) | ||
561 | } | ||
562 | } | ||
563 | |||
564 | { | ||
565 | const refused = downloadVideos[2].streamingPlaylists[0].files[0].fileDownloadUrl | ||
566 | |||
567 | const allowed = [ | ||
568 | downloadVideos[2].files[0].fileDownloadUrl, | ||
569 | downloadVideos[0].streamingPlaylists[0].files[0].fileDownloadUrl, | ||
570 | downloadVideos[1].streamingPlaylists[0].files[0].fileDownloadUrl | ||
571 | ] | ||
572 | |||
573 | // Only streaming playlist is refuse | ||
574 | const res = await makeRawRequest({ url: refused, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | ||
575 | expect(res.body.error).to.equal('Sun Jian') | ||
576 | |||
577 | // But not we there is a user in res | ||
578 | await makeRawRequest({ url: refused, token: servers[0].accessToken, expectedStatus: HttpStatusCode.OK_200 }) | ||
579 | await makeRawRequest({ url: refused, query: { videoFileToken: downloadVideo2Token }, expectedStatus: HttpStatusCode.OK_200 }) | ||
580 | |||
581 | // Other files work | ||
582 | for (const url of allowed) { | ||
583 | await makeRawRequest({ url, expectedStatus: HttpStatusCode.OK_200 }) | ||
584 | } | ||
585 | } | ||
586 | }) | ||
587 | }) | ||
588 | |||
589 | describe('Embed filters', function () { | ||
590 | const embedVideos: VideoDetails[] = [] | ||
591 | const embedPlaylists: VideoPlaylist[] = [] | ||
592 | |||
593 | before(async function () { | ||
594 | this.timeout(60000) | ||
595 | |||
596 | await servers[0].config.disableTranscoding() | ||
597 | |||
598 | for (const name of [ 'bad embed', 'good embed' ]) { | ||
599 | { | ||
600 | const uuid = (await servers[0].videos.quickUpload({ name })).uuid | ||
601 | embedVideos.push(await servers[0].videos.get({ id: uuid })) | ||
602 | } | ||
603 | |||
604 | { | ||
605 | const attributes = { displayName: name, videoChannelId: servers[0].store.channel.id, privacy: VideoPlaylistPrivacy.PUBLIC } | ||
606 | const { id } = await servers[0].playlists.create({ attributes }) | ||
607 | |||
608 | const playlist = await servers[0].playlists.get({ playlistId: id }) | ||
609 | embedPlaylists.push(playlist) | ||
610 | } | ||
611 | } | ||
612 | }) | ||
613 | |||
614 | it('Should run filter:html.embed.video.allowed.result', async function () { | ||
615 | const res = await makeGetRequest({ url: servers[0].url, path: embedVideos[0].embedPath, expectedStatus: HttpStatusCode.OK_200 }) | ||
616 | expect(res.text).to.equal('Lu Bu') | ||
617 | }) | ||
618 | |||
619 | it('Should run filter:html.embed.video-playlist.allowed.result', async function () { | ||
620 | const res = await makeGetRequest({ url: servers[0].url, path: embedPlaylists[0].embedPath, expectedStatus: HttpStatusCode.OK_200 }) | ||
621 | expect(res.text).to.equal('Diao Chan') | ||
622 | }) | ||
623 | }) | ||
624 | |||
625 | describe('Client HTML filters', function () { | ||
626 | let videoUUID: string | ||
627 | |||
628 | before(async function () { | ||
629 | this.timeout(60000) | ||
630 | |||
631 | const { uuid } = await servers[0].videos.quickUpload({ name: 'html video' }) | ||
632 | videoUUID = uuid | ||
633 | }) | ||
634 | |||
635 | it('Should run filter:html.client.json-ld.result', async function () { | ||
636 | const res = await makeGetRequest({ url: servers[0].url, path: '/w/' + videoUUID, expectedStatus: HttpStatusCode.OK_200 }) | ||
637 | expect(res.text).to.contain('"recordedAt":"http://example.com/recordedAt"') | ||
638 | }) | ||
639 | |||
640 | it('Should not run filter:html.client.json-ld.result with an account', async function () { | ||
641 | const res = await makeGetRequest({ url: servers[0].url, path: '/a/root', expectedStatus: HttpStatusCode.OK_200 }) | ||
642 | expect(res.text).not.to.contain('"recordedAt":"http://example.com/recordedAt"') | ||
643 | }) | ||
644 | }) | ||
645 | |||
646 | describe('Search filters', function () { | ||
647 | |||
648 | before(async function () { | ||
649 | await servers[0].config.updateCustomSubConfig({ | ||
650 | newConfig: { | ||
651 | search: { | ||
652 | searchIndex: { | ||
653 | enabled: true, | ||
654 | isDefaultSearch: false, | ||
655 | disableLocalSearch: false | ||
656 | } | ||
657 | } | ||
658 | } | ||
659 | }) | ||
660 | }) | ||
661 | |||
662 | it('Should run filter:api.search.videos.local.list.{params,result}', async function () { | ||
663 | await servers[0].search.advancedVideoSearch({ | ||
664 | search: { | ||
665 | search: 'Sun Quan' | ||
666 | } | ||
667 | }) | ||
668 | |||
669 | await servers[0].servers.waitUntilLog('Run hook filter:api.search.videos.local.list.params', 1) | ||
670 | await servers[0].servers.waitUntilLog('Run hook filter:api.search.videos.local.list.result', 1) | ||
671 | }) | ||
672 | |||
673 | it('Should run filter:api.search.videos.index.list.{params,result}', async function () { | ||
674 | await servers[0].search.advancedVideoSearch({ | ||
675 | search: { | ||
676 | search: 'Sun Quan', | ||
677 | searchTarget: 'search-index' | ||
678 | } | ||
679 | }) | ||
680 | |||
681 | await servers[0].servers.waitUntilLog('Run hook filter:api.search.videos.local.list.params', 1) | ||
682 | await servers[0].servers.waitUntilLog('Run hook filter:api.search.videos.local.list.result', 1) | ||
683 | await servers[0].servers.waitUntilLog('Run hook filter:api.search.videos.index.list.params', 1) | ||
684 | await servers[0].servers.waitUntilLog('Run hook filter:api.search.videos.index.list.result', 1) | ||
685 | }) | ||
686 | |||
687 | it('Should run filter:api.search.video-channels.local.list.{params,result}', async function () { | ||
688 | await servers[0].search.advancedChannelSearch({ | ||
689 | search: { | ||
690 | search: 'Sun Ce' | ||
691 | } | ||
692 | }) | ||
693 | |||
694 | await servers[0].servers.waitUntilLog('Run hook filter:api.search.video-channels.local.list.params', 1) | ||
695 | await servers[0].servers.waitUntilLog('Run hook filter:api.search.video-channels.local.list.result', 1) | ||
696 | }) | ||
697 | |||
698 | it('Should run filter:api.search.video-channels.index.list.{params,result}', async function () { | ||
699 | await servers[0].search.advancedChannelSearch({ | ||
700 | search: { | ||
701 | search: 'Sun Ce', | ||
702 | searchTarget: 'search-index' | ||
703 | } | ||
704 | }) | ||
705 | |||
706 | await servers[0].servers.waitUntilLog('Run hook filter:api.search.video-channels.local.list.params', 1) | ||
707 | await servers[0].servers.waitUntilLog('Run hook filter:api.search.video-channels.local.list.result', 1) | ||
708 | await servers[0].servers.waitUntilLog('Run hook filter:api.search.video-channels.index.list.params', 1) | ||
709 | await servers[0].servers.waitUntilLog('Run hook filter:api.search.video-channels.index.list.result', 1) | ||
710 | }) | ||
711 | |||
712 | it('Should run filter:api.search.video-playlists.local.list.{params,result}', async function () { | ||
713 | await servers[0].search.advancedPlaylistSearch({ | ||
714 | search: { | ||
715 | search: 'Sun Jian' | ||
716 | } | ||
717 | }) | ||
718 | |||
719 | await servers[0].servers.waitUntilLog('Run hook filter:api.search.video-playlists.local.list.params', 1) | ||
720 | await servers[0].servers.waitUntilLog('Run hook filter:api.search.video-playlists.local.list.result', 1) | ||
721 | }) | ||
722 | |||
723 | it('Should run filter:api.search.video-playlists.index.list.{params,result}', async function () { | ||
724 | await servers[0].search.advancedPlaylistSearch({ | ||
725 | search: { | ||
726 | search: 'Sun Jian', | ||
727 | searchTarget: 'search-index' | ||
728 | } | ||
729 | }) | ||
730 | |||
731 | await servers[0].servers.waitUntilLog('Run hook filter:api.search.video-playlists.local.list.params', 1) | ||
732 | await servers[0].servers.waitUntilLog('Run hook filter:api.search.video-playlists.local.list.result', 1) | ||
733 | await servers[0].servers.waitUntilLog('Run hook filter:api.search.video-playlists.index.list.params', 1) | ||
734 | await servers[0].servers.waitUntilLog('Run hook filter:api.search.video-playlists.index.list.result', 1) | ||
735 | }) | ||
736 | }) | ||
737 | |||
738 | describe('Upload/import/live attributes filters', function () { | ||
739 | |||
740 | before(async function () { | ||
741 | await servers[0].config.enableLive({ transcoding: false, allowReplay: false }) | ||
742 | await servers[0].config.enableImports() | ||
743 | await servers[0].config.disableTranscoding() | ||
744 | }) | ||
745 | |||
746 | it('Should run filter:api.video.upload.video-attribute.result', async function () { | ||
747 | for (const mode of [ 'legacy' as 'legacy', 'resumable' as 'resumable' ]) { | ||
748 | const { id } = await servers[0].videos.upload({ attributes: { name: 'video', description: 'upload' }, mode }) | ||
749 | |||
750 | const video = await servers[0].videos.get({ id }) | ||
751 | expect(video.description).to.equal('upload - filter:api.video.upload.video-attribute.result') | ||
752 | } | ||
753 | }) | ||
754 | |||
755 | it('Should run filter:api.video.import-url.video-attribute.result', async function () { | ||
756 | const attributes = { | ||
757 | name: 'video', | ||
758 | description: 'import url', | ||
759 | channelId: servers[0].store.channel.id, | ||
760 | targetUrl: FIXTURE_URLS.goodVideo, | ||
761 | privacy: VideoPrivacy.PUBLIC | ||
762 | } | ||
763 | const { video: { id } } = await servers[0].imports.importVideo({ attributes }) | ||
764 | |||
765 | const video = await servers[0].videos.get({ id }) | ||
766 | expect(video.description).to.equal('import url - filter:api.video.import-url.video-attribute.result') | ||
767 | }) | ||
768 | |||
769 | it('Should run filter:api.video.import-torrent.video-attribute.result', async function () { | ||
770 | const attributes = { | ||
771 | name: 'video', | ||
772 | description: 'import torrent', | ||
773 | channelId: servers[0].store.channel.id, | ||
774 | magnetUri: FIXTURE_URLS.magnet, | ||
775 | privacy: VideoPrivacy.PUBLIC | ||
776 | } | ||
777 | const { video: { id } } = await servers[0].imports.importVideo({ attributes }) | ||
778 | |||
779 | const video = await servers[0].videos.get({ id }) | ||
780 | expect(video.description).to.equal('import torrent - filter:api.video.import-torrent.video-attribute.result') | ||
781 | }) | ||
782 | |||
783 | it('Should run filter:api.video.live.video-attribute.result', async function () { | ||
784 | const fields = { | ||
785 | name: 'live', | ||
786 | description: 'live', | ||
787 | channelId: servers[0].store.channel.id, | ||
788 | privacy: VideoPrivacy.PUBLIC | ||
789 | } | ||
790 | const { id } = await servers[0].live.create({ fields }) | ||
791 | |||
792 | const video = await servers[0].videos.get({ id }) | ||
793 | expect(video.description).to.equal('live - filter:api.video.live.video-attribute.result') | ||
794 | }) | ||
795 | }) | ||
796 | |||
797 | describe('Stats filters', function () { | ||
798 | |||
799 | it('Should run filter:api.server.stats.get.result', async function () { | ||
800 | const data = await servers[0].stats.get() | ||
801 | |||
802 | expect((data as any).customStats).to.equal(14) | ||
803 | }) | ||
804 | |||
805 | }) | ||
806 | |||
807 | describe('Job queue filters', function () { | ||
808 | let videoUUID: string | ||
809 | |||
810 | before(async function () { | ||
811 | this.timeout(120_000) | ||
812 | |||
813 | await servers[0].config.enableMinimumTranscoding() | ||
814 | const { uuid } = await servers[0].videos.quickUpload({ name: 'studio' }) | ||
815 | |||
816 | const video = await servers[0].videos.get({ id: uuid }) | ||
817 | expect(video.duration).at.least(2) | ||
818 | videoUUID = video.uuid | ||
819 | |||
820 | await waitJobs(servers) | ||
821 | |||
822 | await servers[0].config.enableStudio() | ||
823 | }) | ||
824 | |||
825 | it('Should run filter:job-queue.process.params', async function () { | ||
826 | this.timeout(120_000) | ||
827 | |||
828 | await servers[0].videoStudio.createEditionTasks({ | ||
829 | videoId: videoUUID, | ||
830 | tasks: [ | ||
831 | { | ||
832 | name: 'add-intro', | ||
833 | options: { | ||
834 | file: 'video_very_short_240p.mp4' | ||
835 | } | ||
836 | } | ||
837 | ] | ||
838 | }) | ||
839 | |||
840 | await waitJobs(servers) | ||
841 | |||
842 | await servers[0].servers.waitUntilLog('Run hook filter:job-queue.process.params', 1, false) | ||
843 | |||
844 | const video = await servers[0].videos.get({ id: videoUUID }) | ||
845 | expect(video.duration).at.most(2) | ||
846 | }) | ||
847 | |||
848 | it('Should run filter:job-queue.process.result', async function () { | ||
849 | await servers[0].servers.waitUntilLog('Run hook filter:job-queue.process.result', 1, false) | ||
850 | }) | ||
851 | }) | ||
852 | |||
853 | describe('Transcoding filters', async function () { | ||
854 | |||
855 | it('Should run filter:transcoding.auto.resolutions-to-transcode.result', async function () { | ||
856 | const { uuid } = await servers[0].videos.quickUpload({ name: 'transcode-filter' }) | ||
857 | |||
858 | await waitJobs(servers) | ||
859 | |||
860 | const video = await servers[0].videos.get({ id: uuid }) | ||
861 | expect(video.files).to.have.lengthOf(2) | ||
862 | expect(video.files.find(f => f.resolution.id === 100 as any)).to.exist | ||
863 | }) | ||
864 | }) | ||
865 | |||
866 | describe('Video channel filters', async function () { | ||
867 | |||
868 | it('Should run filter:api.video-channels.list.params', async function () { | ||
869 | const { data } = await servers[0].channels.list({ start: 0, count: 0 }) | ||
870 | |||
871 | // plugin do +1 to the count parameter | ||
872 | expect(data).to.have.lengthOf(1) | ||
873 | }) | ||
874 | |||
875 | it('Should run filter:api.video-channels.list.result', async function () { | ||
876 | const { total } = await servers[0].channels.list({ start: 0, count: 1 }) | ||
877 | |||
878 | // plugin do +1 to the total parameter | ||
879 | expect(total).to.equal(4) | ||
880 | }) | ||
881 | |||
882 | it('Should run filter:api.video-channel.get.result', async function () { | ||
883 | const channel = await servers[0].channels.get({ channelName: 'root_channel' }) | ||
884 | expect(channel.displayName).to.equal('Main root channel <3') | ||
885 | }) | ||
886 | }) | ||
887 | |||
888 | describe('Activity Pub', function () { | ||
889 | |||
890 | it('Should run filter:activity-pub.activity.context.build.result', async function () { | ||
891 | const { body } = await makeActivityPubGetRequest(servers[0].url, '/w/' + videoUUID) | ||
892 | expect(body.type).to.equal('Video') | ||
893 | |||
894 | expect(body['@context'].some(c => { | ||
895 | return typeof c === 'object' && c.recordedAt === 'https://schema.org/recordedAt' | ||
896 | })).to.be.true | ||
897 | }) | ||
898 | |||
899 | it('Should run filter:activity-pub.video.json-ld.build.result', async function () { | ||
900 | const { body } = await makeActivityPubGetRequest(servers[0].url, '/w/' + videoUUID) | ||
901 | expect(body.name).to.equal('default video 0') | ||
902 | expect(body.videoName).to.equal('default video 0') | ||
903 | }) | ||
904 | }) | ||
905 | |||
906 | after(async function () { | ||
907 | await cleanupTests(servers) | ||
908 | }) | ||
909 | }) | ||
diff --git a/packages/tests/src/plugins/html-injection.ts b/packages/tests/src/plugins/html-injection.ts new file mode 100644 index 000000000..269a45b98 --- /dev/null +++ b/packages/tests/src/plugins/html-injection.ts | |||
@@ -0,0 +1,73 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import { expect } from 'chai' | ||
4 | import { | ||
5 | cleanupTests, | ||
6 | createSingleServer, | ||
7 | makeHTMLRequest, | ||
8 | PeerTubeServer, | ||
9 | PluginsCommand, | ||
10 | setAccessTokensToServers | ||
11 | } from '@peertube/peertube-server-commands' | ||
12 | |||
13 | describe('Test plugins HTML injection', function () { | ||
14 | let server: PeerTubeServer = null | ||
15 | let command: PluginsCommand | ||
16 | |||
17 | before(async function () { | ||
18 | this.timeout(30000) | ||
19 | |||
20 | server = await createSingleServer(1) | ||
21 | await setAccessTokensToServers([ server ]) | ||
22 | |||
23 | command = server.plugins | ||
24 | }) | ||
25 | |||
26 | it('Should not inject global css file in HTML', async function () { | ||
27 | { | ||
28 | const text = await command.getCSS() | ||
29 | expect(text).to.be.empty | ||
30 | } | ||
31 | |||
32 | for (const path of [ '/', '/videos/embed/1', '/video-playlists/embed/1' ]) { | ||
33 | const res = await makeHTMLRequest(server.url, path) | ||
34 | expect(res.text).to.not.include('link rel="stylesheet" href="/plugins/global.css') | ||
35 | } | ||
36 | }) | ||
37 | |||
38 | it('Should install a plugin and a theme', async function () { | ||
39 | this.timeout(30000) | ||
40 | |||
41 | await command.install({ npmName: 'peertube-plugin-hello-world' }) | ||
42 | }) | ||
43 | |||
44 | it('Should have the correct global css', async function () { | ||
45 | { | ||
46 | const text = await command.getCSS() | ||
47 | expect(text).to.contain('background-color: red') | ||
48 | } | ||
49 | |||
50 | for (const path of [ '/', '/videos/embed/1', '/video-playlists/embed/1' ]) { | ||
51 | const res = await makeHTMLRequest(server.url, path) | ||
52 | expect(res.text).to.include('link rel="stylesheet" href="/plugins/global.css') | ||
53 | } | ||
54 | }) | ||
55 | |||
56 | it('Should have an empty global css on uninstall', async function () { | ||
57 | await command.uninstall({ npmName: 'peertube-plugin-hello-world' }) | ||
58 | |||
59 | { | ||
60 | const text = await command.getCSS() | ||
61 | expect(text).to.be.empty | ||
62 | } | ||
63 | |||
64 | for (const path of [ '/', '/videos/embed/1', '/video-playlists/embed/1' ]) { | ||
65 | const res = await makeHTMLRequest(server.url, path) | ||
66 | expect(res.text).to.not.include('link rel="stylesheet" href="/plugins/global.css') | ||
67 | } | ||
68 | }) | ||
69 | |||
70 | after(async function () { | ||
71 | await cleanupTests([ server ]) | ||
72 | }) | ||
73 | }) | ||
diff --git a/packages/tests/src/plugins/id-and-pass-auth.ts b/packages/tests/src/plugins/id-and-pass-auth.ts new file mode 100644 index 000000000..a332f0eec --- /dev/null +++ b/packages/tests/src/plugins/id-and-pass-auth.ts | |||
@@ -0,0 +1,248 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import { expect } from 'chai' | ||
4 | import { wait } from '@peertube/peertube-core-utils' | ||
5 | import { HttpStatusCode, UserRole } from '@peertube/peertube-models' | ||
6 | import { | ||
7 | cleanupTests, | ||
8 | createSingleServer, | ||
9 | PeerTubeServer, | ||
10 | PluginsCommand, | ||
11 | setAccessTokensToServers | ||
12 | } from '@peertube/peertube-server-commands' | ||
13 | |||
14 | describe('Test id and pass auth plugins', function () { | ||
15 | let server: PeerTubeServer | ||
16 | |||
17 | let crashAccessToken: string | ||
18 | let crashRefreshToken: string | ||
19 | |||
20 | let lagunaAccessToken: string | ||
21 | let lagunaRefreshToken: string | ||
22 | let lagunaId: number | ||
23 | |||
24 | before(async function () { | ||
25 | this.timeout(30000) | ||
26 | |||
27 | server = await createSingleServer(1) | ||
28 | await setAccessTokensToServers([ server ]) | ||
29 | |||
30 | for (const suffix of [ 'one', 'two', 'three' ]) { | ||
31 | await server.plugins.install({ path: PluginsCommand.getPluginTestPath('-id-pass-auth-' + suffix) }) | ||
32 | } | ||
33 | }) | ||
34 | |||
35 | it('Should display the correct configuration', async function () { | ||
36 | const config = await server.config.getConfig() | ||
37 | |||
38 | const auths = config.plugin.registeredIdAndPassAuths | ||
39 | expect(auths).to.have.lengthOf(8) | ||
40 | |||
41 | const crashAuth = auths.find(a => a.authName === 'crash-auth') | ||
42 | expect(crashAuth).to.exist | ||
43 | expect(crashAuth.npmName).to.equal('peertube-plugin-test-id-pass-auth-one') | ||
44 | expect(crashAuth.weight).to.equal(50) | ||
45 | }) | ||
46 | |||
47 | it('Should not login', async function () { | ||
48 | await server.login.login({ user: { username: 'toto', password: 'password' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | ||
49 | }) | ||
50 | |||
51 | it('Should login Spyro, create the user and use the token', async function () { | ||
52 | const accessToken = await server.login.getAccessToken({ username: 'spyro', password: 'spyro password' }) | ||
53 | |||
54 | const body = await server.users.getMyInfo({ token: accessToken }) | ||
55 | |||
56 | expect(body.username).to.equal('spyro') | ||
57 | expect(body.account.displayName).to.equal('Spyro the Dragon') | ||
58 | expect(body.role.id).to.equal(UserRole.USER) | ||
59 | }) | ||
60 | |||
61 | it('Should login Crash, create the user and use the token', async function () { | ||
62 | { | ||
63 | const body = await server.login.login({ user: { username: 'crash', password: 'crash password' } }) | ||
64 | crashAccessToken = body.access_token | ||
65 | crashRefreshToken = body.refresh_token | ||
66 | } | ||
67 | |||
68 | { | ||
69 | const body = await server.users.getMyInfo({ token: crashAccessToken }) | ||
70 | |||
71 | expect(body.username).to.equal('crash') | ||
72 | expect(body.account.displayName).to.equal('Crash Bandicoot') | ||
73 | expect(body.role.id).to.equal(UserRole.MODERATOR) | ||
74 | } | ||
75 | }) | ||
76 | |||
77 | it('Should login the first Laguna, create the user and use the token', async function () { | ||
78 | { | ||
79 | const body = await server.login.login({ user: { username: 'laguna', password: 'laguna password' } }) | ||
80 | lagunaAccessToken = body.access_token | ||
81 | lagunaRefreshToken = body.refresh_token | ||
82 | } | ||
83 | |||
84 | { | ||
85 | const body = await server.users.getMyInfo({ token: lagunaAccessToken }) | ||
86 | |||
87 | expect(body.username).to.equal('laguna') | ||
88 | expect(body.account.displayName).to.equal('Laguna Loire') | ||
89 | expect(body.role.id).to.equal(UserRole.USER) | ||
90 | |||
91 | lagunaId = body.id | ||
92 | } | ||
93 | }) | ||
94 | |||
95 | it('Should refresh crash token, but not laguna token', async function () { | ||
96 | { | ||
97 | const resRefresh = await server.login.refreshToken({ refreshToken: crashRefreshToken }) | ||
98 | crashAccessToken = resRefresh.body.access_token | ||
99 | crashRefreshToken = resRefresh.body.refresh_token | ||
100 | |||
101 | const body = await server.users.getMyInfo({ token: crashAccessToken }) | ||
102 | expect(body.username).to.equal('crash') | ||
103 | } | ||
104 | |||
105 | { | ||
106 | await server.login.refreshToken({ refreshToken: lagunaRefreshToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | ||
107 | } | ||
108 | }) | ||
109 | |||
110 | it('Should update Crash profile', async function () { | ||
111 | await server.users.updateMe({ | ||
112 | token: crashAccessToken, | ||
113 | displayName: 'Beautiful Crash', | ||
114 | description: 'Mutant eastern barred bandicoot' | ||
115 | }) | ||
116 | |||
117 | const body = await server.users.getMyInfo({ token: crashAccessToken }) | ||
118 | |||
119 | expect(body.account.displayName).to.equal('Beautiful Crash') | ||
120 | expect(body.account.description).to.equal('Mutant eastern barred bandicoot') | ||
121 | }) | ||
122 | |||
123 | it('Should logout Crash', async function () { | ||
124 | await server.login.logout({ token: crashAccessToken }) | ||
125 | }) | ||
126 | |||
127 | it('Should have logged out Crash', async function () { | ||
128 | await server.servers.waitUntilLog('On logout for auth 1 - 2') | ||
129 | |||
130 | await server.users.getMyInfo({ token: crashAccessToken, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) | ||
131 | }) | ||
132 | |||
133 | it('Should login Crash and keep the old existing profile', async function () { | ||
134 | crashAccessToken = await server.login.getAccessToken({ username: 'crash', password: 'crash password' }) | ||
135 | |||
136 | const body = await server.users.getMyInfo({ token: crashAccessToken }) | ||
137 | |||
138 | expect(body.username).to.equal('crash') | ||
139 | expect(body.account.displayName).to.equal('Beautiful Crash') | ||
140 | expect(body.account.description).to.equal('Mutant eastern barred bandicoot') | ||
141 | expect(body.role.id).to.equal(UserRole.MODERATOR) | ||
142 | }) | ||
143 | |||
144 | it('Should login Laguna and update the profile', async function () { | ||
145 | { | ||
146 | await server.users.update({ userId: lagunaId, videoQuota: 43000, videoQuotaDaily: 43100 }) | ||
147 | await server.users.updateMe({ token: lagunaAccessToken, displayName: 'laguna updated' }) | ||
148 | |||
149 | const body = await server.users.getMyInfo({ token: lagunaAccessToken }) | ||
150 | expect(body.username).to.equal('laguna') | ||
151 | expect(body.account.displayName).to.equal('laguna updated') | ||
152 | expect(body.videoQuota).to.equal(43000) | ||
153 | expect(body.videoQuotaDaily).to.equal(43100) | ||
154 | } | ||
155 | |||
156 | { | ||
157 | const body = await server.login.login({ user: { username: 'laguna', password: 'laguna password' } }) | ||
158 | lagunaAccessToken = body.access_token | ||
159 | lagunaRefreshToken = body.refresh_token | ||
160 | } | ||
161 | |||
162 | { | ||
163 | const body = await server.users.getMyInfo({ token: lagunaAccessToken }) | ||
164 | expect(body.username).to.equal('laguna') | ||
165 | expect(body.account.displayName).to.equal('Laguna Loire') | ||
166 | expect(body.videoQuota).to.equal(42000) | ||
167 | expect(body.videoQuotaDaily).to.equal(43100) | ||
168 | } | ||
169 | }) | ||
170 | |||
171 | it('Should reject token of laguna by the plugin hook', async function () { | ||
172 | await wait(5000) | ||
173 | |||
174 | await server.users.getMyInfo({ token: lagunaAccessToken, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) | ||
175 | }) | ||
176 | |||
177 | it('Should reject an invalid username, email, role or display name', async function () { | ||
178 | const command = server.login | ||
179 | |||
180 | await command.login({ user: { username: 'ward', password: 'ward password' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | ||
181 | await server.servers.waitUntilLog('valid username') | ||
182 | |||
183 | await command.login({ user: { username: 'kiros', password: 'kiros password' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | ||
184 | await server.servers.waitUntilLog('valid displayName') | ||
185 | |||
186 | await command.login({ user: { username: 'raine', password: 'raine password' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | ||
187 | await server.servers.waitUntilLog('valid role') | ||
188 | |||
189 | await command.login({ user: { username: 'ellone', password: 'elonne password' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | ||
190 | await server.servers.waitUntilLog('valid email') | ||
191 | }) | ||
192 | |||
193 | it('Should unregister spyro-auth and do not login existing Spyro', async function () { | ||
194 | await server.plugins.updateSettings({ | ||
195 | npmName: 'peertube-plugin-test-id-pass-auth-one', | ||
196 | settings: { disableSpyro: true } | ||
197 | }) | ||
198 | |||
199 | const command = server.login | ||
200 | await command.login({ user: { username: 'spyro', password: 'spyro password' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | ||
201 | await command.login({ user: { username: 'spyro', password: 'fake' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | ||
202 | }) | ||
203 | |||
204 | it('Should have disabled this auth', async function () { | ||
205 | const config = await server.config.getConfig() | ||
206 | |||
207 | const auths = config.plugin.registeredIdAndPassAuths | ||
208 | expect(auths).to.have.lengthOf(7) | ||
209 | |||
210 | const spyroAuth = auths.find(a => a.authName === 'spyro-auth') | ||
211 | expect(spyroAuth).to.not.exist | ||
212 | }) | ||
213 | |||
214 | it('Should uninstall the plugin one and do not login existing Crash', async function () { | ||
215 | await server.plugins.uninstall({ npmName: 'peertube-plugin-test-id-pass-auth-one' }) | ||
216 | |||
217 | await server.login.login({ | ||
218 | user: { username: 'crash', password: 'crash password' }, | ||
219 | expectedStatus: HttpStatusCode.BAD_REQUEST_400 | ||
220 | }) | ||
221 | }) | ||
222 | |||
223 | it('Should display the correct configuration', async function () { | ||
224 | const config = await server.config.getConfig() | ||
225 | |||
226 | const auths = config.plugin.registeredIdAndPassAuths | ||
227 | expect(auths).to.have.lengthOf(6) | ||
228 | |||
229 | const crashAuth = auths.find(a => a.authName === 'crash-auth') | ||
230 | expect(crashAuth).to.not.exist | ||
231 | }) | ||
232 | |||
233 | it('Should display plugin auth information in users list', async function () { | ||
234 | const { data } = await server.users.list() | ||
235 | |||
236 | const root = data.find(u => u.username === 'root') | ||
237 | const crash = data.find(u => u.username === 'crash') | ||
238 | const laguna = data.find(u => u.username === 'laguna') | ||
239 | |||
240 | expect(root.pluginAuth).to.be.null | ||
241 | expect(crash.pluginAuth).to.equal('peertube-plugin-test-id-pass-auth-one') | ||
242 | expect(laguna.pluginAuth).to.equal('peertube-plugin-test-id-pass-auth-two') | ||
243 | }) | ||
244 | |||
245 | after(async function () { | ||
246 | await cleanupTests([ server ]) | ||
247 | }) | ||
248 | }) | ||
diff --git a/packages/tests/src/plugins/index.ts b/packages/tests/src/plugins/index.ts new file mode 100644 index 000000000..210af7236 --- /dev/null +++ b/packages/tests/src/plugins/index.ts | |||
@@ -0,0 +1,13 @@ | |||
1 | import './action-hooks' | ||
2 | import './external-auth' | ||
3 | import './filter-hooks' | ||
4 | import './html-injection' | ||
5 | import './id-and-pass-auth' | ||
6 | import './plugin-helpers' | ||
7 | import './plugin-router' | ||
8 | import './plugin-storage' | ||
9 | import './plugin-transcoding' | ||
10 | import './plugin-unloading' | ||
11 | import './plugin-websocket' | ||
12 | import './translations' | ||
13 | import './video-constants' | ||
diff --git a/packages/tests/src/plugins/plugin-helpers.ts b/packages/tests/src/plugins/plugin-helpers.ts new file mode 100644 index 000000000..d2bd8596e --- /dev/null +++ b/packages/tests/src/plugins/plugin-helpers.ts | |||
@@ -0,0 +1,383 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import { expect } from 'chai' | ||
4 | import { pathExists } from 'fs-extra/esm' | ||
5 | import { HttpStatusCode, ThumbnailType } from '@peertube/peertube-models' | ||
6 | import { | ||
7 | cleanupTests, | ||
8 | createMultipleServers, | ||
9 | doubleFollow, | ||
10 | makeGetRequest, | ||
11 | makePostBodyRequest, | ||
12 | makeRawRequest, | ||
13 | PeerTubeServer, | ||
14 | PluginsCommand, | ||
15 | setAccessTokensToServers, | ||
16 | waitJobs | ||
17 | } from '@peertube/peertube-server-commands' | ||
18 | import { checkVideoFilesWereRemoved } from '@tests/shared/videos.js' | ||
19 | |||
20 | function postCommand (server: PeerTubeServer, command: string, bodyArg?: object) { | ||
21 | const body = { command } | ||
22 | if (bodyArg) Object.assign(body, bodyArg) | ||
23 | |||
24 | return makePostBodyRequest({ | ||
25 | url: server.url, | ||
26 | path: '/plugins/test-four/router/commander', | ||
27 | fields: body, | ||
28 | expectedStatus: HttpStatusCode.NO_CONTENT_204 | ||
29 | }) | ||
30 | } | ||
31 | |||
32 | describe('Test plugin helpers', function () { | ||
33 | let servers: PeerTubeServer[] | ||
34 | |||
35 | before(async function () { | ||
36 | this.timeout(60000) | ||
37 | |||
38 | servers = await createMultipleServers(2) | ||
39 | await setAccessTokensToServers(servers) | ||
40 | |||
41 | await doubleFollow(servers[0], servers[1]) | ||
42 | |||
43 | await servers[0].plugins.install({ path: PluginsCommand.getPluginTestPath('-four') }) | ||
44 | }) | ||
45 | |||
46 | describe('Logger', function () { | ||
47 | |||
48 | it('Should have logged things', async function () { | ||
49 | await servers[0].servers.waitUntilLog(servers[0].host + ' peertube-plugin-test-four', 1, false) | ||
50 | await servers[0].servers.waitUntilLog('Hello world from plugin four', 1) | ||
51 | }) | ||
52 | }) | ||
53 | |||
54 | describe('Database', function () { | ||
55 | |||
56 | it('Should have made a query', async function () { | ||
57 | await servers[0].servers.waitUntilLog(`root email is admin${servers[0].internalServerNumber}@example.com`) | ||
58 | }) | ||
59 | }) | ||
60 | |||
61 | describe('Config', function () { | ||
62 | |||
63 | it('Should have the correct webserver url', async function () { | ||
64 | await servers[0].servers.waitUntilLog(`server url is ${servers[0].url}`) | ||
65 | }) | ||
66 | |||
67 | it('Should have the correct listening config', async function () { | ||
68 | const res = await makeGetRequest({ | ||
69 | url: servers[0].url, | ||
70 | path: '/plugins/test-four/router/server-listening-config', | ||
71 | expectedStatus: HttpStatusCode.OK_200 | ||
72 | }) | ||
73 | |||
74 | expect(res.body.config).to.exist | ||
75 | expect(res.body.config.hostname).to.equal('::') | ||
76 | expect(res.body.config.port).to.equal(servers[0].port) | ||
77 | }) | ||
78 | |||
79 | it('Should have the correct config', async function () { | ||
80 | const res = await makeGetRequest({ | ||
81 | url: servers[0].url, | ||
82 | path: '/plugins/test-four/router/server-config', | ||
83 | expectedStatus: HttpStatusCode.OK_200 | ||
84 | }) | ||
85 | |||
86 | expect(res.body.serverConfig).to.exist | ||
87 | expect(res.body.serverConfig.instance.name).to.equal('PeerTube') | ||
88 | }) | ||
89 | }) | ||
90 | |||
91 | describe('Server', function () { | ||
92 | |||
93 | it('Should get the server actor', async function () { | ||
94 | await servers[0].servers.waitUntilLog('server actor name is peertube') | ||
95 | }) | ||
96 | }) | ||
97 | |||
98 | describe('Socket', function () { | ||
99 | |||
100 | it('Should sendNotification without any exceptions', async () => { | ||
101 | const user = await servers[0].users.create({ username: 'notis_redding', password: 'secret1234?' }) | ||
102 | await makePostBodyRequest({ | ||
103 | url: servers[0].url, | ||
104 | path: '/plugins/test-four/router/send-notification', | ||
105 | fields: { | ||
106 | userId: user.id | ||
107 | }, | ||
108 | expectedStatus: HttpStatusCode.CREATED_201 | ||
109 | }) | ||
110 | }) | ||
111 | |||
112 | it('Should sendVideoLiveNewState without any exceptions', async () => { | ||
113 | const res = await servers[0].videos.quickUpload({ name: 'video server 1' }) | ||
114 | |||
115 | await makePostBodyRequest({ | ||
116 | url: servers[0].url, | ||
117 | path: '/plugins/test-four/router/send-video-live-new-state/' + res.uuid, | ||
118 | expectedStatus: HttpStatusCode.CREATED_201 | ||
119 | }) | ||
120 | |||
121 | await servers[0].videos.remove({ id: res.uuid }) | ||
122 | }) | ||
123 | }) | ||
124 | |||
125 | describe('Plugin', function () { | ||
126 | |||
127 | it('Should get the base static route', async function () { | ||
128 | const res = await makeGetRequest({ | ||
129 | url: servers[0].url, | ||
130 | path: '/plugins/test-four/router/static-route', | ||
131 | expectedStatus: HttpStatusCode.OK_200 | ||
132 | }) | ||
133 | |||
134 | expect(res.body.staticRoute).to.equal('/plugins/test-four/0.0.1/static/') | ||
135 | }) | ||
136 | |||
137 | it('Should get the base static route', async function () { | ||
138 | const baseRouter = '/plugins/test-four/0.0.1/router/' | ||
139 | |||
140 | const res = await makeGetRequest({ | ||
141 | url: servers[0].url, | ||
142 | path: baseRouter + 'router-route', | ||
143 | expectedStatus: HttpStatusCode.OK_200 | ||
144 | }) | ||
145 | |||
146 | expect(res.body.routerRoute).to.equal(baseRouter) | ||
147 | }) | ||
148 | }) | ||
149 | |||
150 | describe('User', function () { | ||
151 | let rootId: number | ||
152 | |||
153 | it('Should not get a user if not authenticated', async function () { | ||
154 | await makeGetRequest({ | ||
155 | url: servers[0].url, | ||
156 | path: '/plugins/test-four/router/user', | ||
157 | expectedStatus: HttpStatusCode.NOT_FOUND_404 | ||
158 | }) | ||
159 | }) | ||
160 | |||
161 | it('Should get a user if authenticated', async function () { | ||
162 | const res = await makeGetRequest({ | ||
163 | url: servers[0].url, | ||
164 | token: servers[0].accessToken, | ||
165 | path: '/plugins/test-four/router/user', | ||
166 | expectedStatus: HttpStatusCode.OK_200 | ||
167 | }) | ||
168 | |||
169 | expect(res.body.username).to.equal('root') | ||
170 | expect(res.body.displayName).to.equal('root') | ||
171 | expect(res.body.isAdmin).to.be.true | ||
172 | expect(res.body.isModerator).to.be.false | ||
173 | expect(res.body.isUser).to.be.false | ||
174 | |||
175 | rootId = res.body.id | ||
176 | }) | ||
177 | |||
178 | it('Should load a user by id', async function () { | ||
179 | { | ||
180 | const res = await makeGetRequest({ | ||
181 | url: servers[0].url, | ||
182 | path: '/plugins/test-four/router/user/' + rootId, | ||
183 | expectedStatus: HttpStatusCode.OK_200 | ||
184 | }) | ||
185 | |||
186 | expect(res.body.username).to.equal('root') | ||
187 | } | ||
188 | |||
189 | { | ||
190 | await makeGetRequest({ | ||
191 | url: servers[0].url, | ||
192 | path: '/plugins/test-four/router/user/42', | ||
193 | expectedStatus: HttpStatusCode.NOT_FOUND_404 | ||
194 | }) | ||
195 | } | ||
196 | }) | ||
197 | }) | ||
198 | |||
199 | describe('Moderation', function () { | ||
200 | let videoUUIDServer1: string | ||
201 | |||
202 | before(async function () { | ||
203 | this.timeout(60000) | ||
204 | |||
205 | { | ||
206 | const res = await servers[0].videos.quickUpload({ name: 'video server 1' }) | ||
207 | videoUUIDServer1 = res.uuid | ||
208 | } | ||
209 | |||
210 | { | ||
211 | await servers[1].videos.quickUpload({ name: 'video server 2' }) | ||
212 | } | ||
213 | |||
214 | await waitJobs(servers) | ||
215 | |||
216 | const { data } = await servers[0].videos.list() | ||
217 | |||
218 | expect(data).to.have.lengthOf(2) | ||
219 | }) | ||
220 | |||
221 | it('Should mute server 2', async function () { | ||
222 | await postCommand(servers[0], 'blockServer', { hostToBlock: servers[1].host }) | ||
223 | |||
224 | const { data } = await servers[0].videos.list() | ||
225 | |||
226 | expect(data).to.have.lengthOf(1) | ||
227 | expect(data[0].name).to.equal('video server 1') | ||
228 | }) | ||
229 | |||
230 | it('Should unmute server 2', async function () { | ||
231 | await postCommand(servers[0], 'unblockServer', { hostToUnblock: servers[1].host }) | ||
232 | |||
233 | const { data } = await servers[0].videos.list() | ||
234 | |||
235 | expect(data).to.have.lengthOf(2) | ||
236 | }) | ||
237 | |||
238 | it('Should mute account of server 2', async function () { | ||
239 | await postCommand(servers[0], 'blockAccount', { handleToBlock: `root@${servers[1].host}` }) | ||
240 | |||
241 | const { data } = await servers[0].videos.list() | ||
242 | |||
243 | expect(data).to.have.lengthOf(1) | ||
244 | expect(data[0].name).to.equal('video server 1') | ||
245 | }) | ||
246 | |||
247 | it('Should unmute account of server 2', async function () { | ||
248 | await postCommand(servers[0], 'unblockAccount', { handleToUnblock: `root@${servers[1].host}` }) | ||
249 | |||
250 | const { data } = await servers[0].videos.list() | ||
251 | |||
252 | expect(data).to.have.lengthOf(2) | ||
253 | }) | ||
254 | |||
255 | it('Should blacklist video', async function () { | ||
256 | await postCommand(servers[0], 'blacklist', { videoUUID: videoUUIDServer1, unfederate: true }) | ||
257 | |||
258 | await waitJobs(servers) | ||
259 | |||
260 | for (const server of servers) { | ||
261 | const { data } = await server.videos.list() | ||
262 | |||
263 | expect(data).to.have.lengthOf(1) | ||
264 | expect(data[0].name).to.equal('video server 2') | ||
265 | } | ||
266 | }) | ||
267 | |||
268 | it('Should unblacklist video', async function () { | ||
269 | await postCommand(servers[0], 'unblacklist', { videoUUID: videoUUIDServer1 }) | ||
270 | |||
271 | await waitJobs(servers) | ||
272 | |||
273 | for (const server of servers) { | ||
274 | const { data } = await server.videos.list() | ||
275 | |||
276 | expect(data).to.have.lengthOf(2) | ||
277 | } | ||
278 | }) | ||
279 | }) | ||
280 | |||
281 | describe('Videos', function () { | ||
282 | let videoUUID: string | ||
283 | let videoPath: string | ||
284 | |||
285 | before(async function () { | ||
286 | this.timeout(240000) | ||
287 | |||
288 | await servers[0].config.enableTranscoding() | ||
289 | |||
290 | const res = await servers[0].videos.quickUpload({ name: 'video1' }) | ||
291 | videoUUID = res.uuid | ||
292 | |||
293 | await waitJobs(servers) | ||
294 | }) | ||
295 | |||
296 | it('Should get video files', async function () { | ||
297 | const { body } = await makeGetRequest({ | ||
298 | url: servers[0].url, | ||
299 | path: '/plugins/test-four/router/video-files/' + videoUUID, | ||
300 | expectedStatus: HttpStatusCode.OK_200 | ||
301 | }) | ||
302 | |||
303 | // Video files check | ||
304 | { | ||
305 | expect(body.webVideo.videoFiles).to.be.an('array') | ||
306 | expect(body.hls.videoFiles).to.be.an('array') | ||
307 | |||
308 | for (const resolution of [ 144, 240, 360, 480, 720 ]) { | ||
309 | for (const files of [ body.webVideo.videoFiles, body.hls.videoFiles ]) { | ||
310 | const file = files.find(f => f.resolution === resolution) | ||
311 | expect(file).to.exist | ||
312 | |||
313 | expect(file.size).to.be.a('number') | ||
314 | expect(file.fps).to.equal(25) | ||
315 | |||
316 | expect(await pathExists(file.path)).to.be.true | ||
317 | await makeRawRequest({ url: file.url, expectedStatus: HttpStatusCode.OK_200 }) | ||
318 | } | ||
319 | } | ||
320 | |||
321 | videoPath = body.webVideo.videoFiles[0].path | ||
322 | } | ||
323 | |||
324 | // Thumbnails check | ||
325 | { | ||
326 | expect(body.thumbnails).to.be.an('array') | ||
327 | |||
328 | const miniature = body.thumbnails.find(t => t.type === ThumbnailType.MINIATURE) | ||
329 | expect(miniature).to.exist | ||
330 | expect(await pathExists(miniature.path)).to.be.true | ||
331 | await makeRawRequest({ url: miniature.url, expectedStatus: HttpStatusCode.OK_200 }) | ||
332 | |||
333 | const preview = body.thumbnails.find(t => t.type === ThumbnailType.PREVIEW) | ||
334 | expect(preview).to.exist | ||
335 | expect(await pathExists(preview.path)).to.be.true | ||
336 | await makeRawRequest({ url: preview.url, expectedStatus: HttpStatusCode.OK_200 }) | ||
337 | } | ||
338 | }) | ||
339 | |||
340 | it('Should probe a file', async function () { | ||
341 | const { body } = await makeGetRequest({ | ||
342 | url: servers[0].url, | ||
343 | path: '/plugins/test-four/router/ffprobe', | ||
344 | query: { | ||
345 | path: videoPath | ||
346 | }, | ||
347 | expectedStatus: HttpStatusCode.OK_200 | ||
348 | }) | ||
349 | |||
350 | expect(body.streams).to.be.an('array') | ||
351 | expect(body.streams).to.have.lengthOf(2) | ||
352 | }) | ||
353 | |||
354 | it('Should remove a video after a view', async function () { | ||
355 | this.timeout(40000) | ||
356 | |||
357 | // Should not throw -> video exists | ||
358 | const video = await servers[0].videos.get({ id: videoUUID }) | ||
359 | // Should delete the video | ||
360 | await servers[0].views.simulateView({ id: videoUUID }) | ||
361 | |||
362 | await servers[0].servers.waitUntilLog('Video deleted by plugin four.') | ||
363 | |||
364 | try { | ||
365 | // Should throw because the video should have been deleted | ||
366 | await servers[0].videos.get({ id: videoUUID }) | ||
367 | throw new Error('Video exists') | ||
368 | } catch (err) { | ||
369 | if (err.message.includes('exists')) throw err | ||
370 | } | ||
371 | |||
372 | await checkVideoFilesWereRemoved({ server: servers[0], video }) | ||
373 | }) | ||
374 | |||
375 | it('Should have fetched the video by URL', async function () { | ||
376 | await servers[0].servers.waitUntilLog(`video from DB uuid is ${videoUUID}`) | ||
377 | }) | ||
378 | }) | ||
379 | |||
380 | after(async function () { | ||
381 | await cleanupTests(servers) | ||
382 | }) | ||
383 | }) | ||
diff --git a/packages/tests/src/plugins/plugin-router.ts b/packages/tests/src/plugins/plugin-router.ts new file mode 100644 index 000000000..6f3571c05 --- /dev/null +++ b/packages/tests/src/plugins/plugin-router.ts | |||
@@ -0,0 +1,105 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import { expect } from 'chai' | ||
4 | import { | ||
5 | cleanupTests, | ||
6 | createSingleServer, | ||
7 | makeGetRequest, | ||
8 | makePostBodyRequest, | ||
9 | PeerTubeServer, | ||
10 | PluginsCommand, | ||
11 | setAccessTokensToServers | ||
12 | } from '@peertube/peertube-server-commands' | ||
13 | import { HttpStatusCode } from '@peertube/peertube-models' | ||
14 | |||
15 | describe('Test plugin helpers', function () { | ||
16 | let server: PeerTubeServer | ||
17 | const basePaths = [ | ||
18 | '/plugins/test-five/router/', | ||
19 | '/plugins/test-five/0.0.1/router/' | ||
20 | ] | ||
21 | |||
22 | before(async function () { | ||
23 | this.timeout(30000) | ||
24 | |||
25 | server = await createSingleServer(1) | ||
26 | await setAccessTokensToServers([ server ]) | ||
27 | |||
28 | await server.plugins.install({ path: PluginsCommand.getPluginTestPath('-five') }) | ||
29 | }) | ||
30 | |||
31 | it('Should answer "pong"', async function () { | ||
32 | for (const path of basePaths) { | ||
33 | const res = await makeGetRequest({ | ||
34 | url: server.url, | ||
35 | path: path + 'ping', | ||
36 | expectedStatus: HttpStatusCode.OK_200 | ||
37 | }) | ||
38 | |||
39 | expect(res.body.message).to.equal('pong') | ||
40 | } | ||
41 | }) | ||
42 | |||
43 | it('Should check if authenticated', async function () { | ||
44 | for (const path of basePaths) { | ||
45 | const res = await makeGetRequest({ | ||
46 | url: server.url, | ||
47 | path: path + 'is-authenticated', | ||
48 | token: server.accessToken, | ||
49 | expectedStatus: 200 | ||
50 | }) | ||
51 | |||
52 | expect(res.body.isAuthenticated).to.equal(true) | ||
53 | |||
54 | const secRes = await makeGetRequest({ | ||
55 | url: server.url, | ||
56 | path: path + 'is-authenticated', | ||
57 | expectedStatus: 200 | ||
58 | }) | ||
59 | |||
60 | expect(secRes.body.isAuthenticated).to.equal(false) | ||
61 | } | ||
62 | }) | ||
63 | |||
64 | it('Should mirror post body', async function () { | ||
65 | const body = { | ||
66 | hello: 'world', | ||
67 | riri: 'fifi', | ||
68 | loulou: 'picsou' | ||
69 | } | ||
70 | |||
71 | for (const path of basePaths) { | ||
72 | const res = await makePostBodyRequest({ | ||
73 | url: server.url, | ||
74 | path: path + 'form/post/mirror', | ||
75 | fields: body, | ||
76 | expectedStatus: HttpStatusCode.OK_200 | ||
77 | }) | ||
78 | |||
79 | expect(res.body).to.deep.equal(body) | ||
80 | } | ||
81 | }) | ||
82 | |||
83 | it('Should remove the plugin and remove the routes', async function () { | ||
84 | await server.plugins.uninstall({ npmName: 'peertube-plugin-test-five' }) | ||
85 | |||
86 | for (const path of basePaths) { | ||
87 | await makeGetRequest({ | ||
88 | url: server.url, | ||
89 | path: path + 'ping', | ||
90 | expectedStatus: HttpStatusCode.NOT_FOUND_404 | ||
91 | }) | ||
92 | |||
93 | await makePostBodyRequest({ | ||
94 | url: server.url, | ||
95 | path: path + 'ping', | ||
96 | fields: {}, | ||
97 | expectedStatus: HttpStatusCode.NOT_FOUND_404 | ||
98 | }) | ||
99 | } | ||
100 | }) | ||
101 | |||
102 | after(async function () { | ||
103 | await cleanupTests([ server ]) | ||
104 | }) | ||
105 | }) | ||
diff --git a/packages/tests/src/plugins/plugin-storage.ts b/packages/tests/src/plugins/plugin-storage.ts new file mode 100644 index 000000000..f9b0ead0c --- /dev/null +++ b/packages/tests/src/plugins/plugin-storage.ts | |||
@@ -0,0 +1,95 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import { expect } from 'chai' | ||
4 | import { pathExists } from 'fs-extra/esm' | ||
5 | import { readdir, readFile } from 'fs/promises' | ||
6 | import { join } from 'path' | ||
7 | import { HttpStatusCode } from '@peertube/peertube-models' | ||
8 | import { | ||
9 | cleanupTests, | ||
10 | createSingleServer, | ||
11 | makeGetRequest, | ||
12 | PeerTubeServer, | ||
13 | PluginsCommand, | ||
14 | setAccessTokensToServers | ||
15 | } from '@peertube/peertube-server-commands' | ||
16 | |||
17 | describe('Test plugin storage', function () { | ||
18 | let server: PeerTubeServer | ||
19 | |||
20 | before(async function () { | ||
21 | this.timeout(30000) | ||
22 | |||
23 | server = await createSingleServer(1) | ||
24 | await setAccessTokensToServers([ server ]) | ||
25 | |||
26 | await server.plugins.install({ path: PluginsCommand.getPluginTestPath('-six') }) | ||
27 | }) | ||
28 | |||
29 | describe('DB storage', function () { | ||
30 | it('Should correctly store a subkey', async function () { | ||
31 | await server.servers.waitUntilLog('superkey stored value is toto') | ||
32 | }) | ||
33 | |||
34 | it('Should correctly retrieve an array as array from the storage.', async function () { | ||
35 | await server.servers.waitUntilLog('storedArrayKey isArray is true') | ||
36 | await server.servers.waitUntilLog('storedArrayKey stored value is toto, toto2') | ||
37 | }) | ||
38 | }) | ||
39 | |||
40 | describe('Disk storage', function () { | ||
41 | let dataPath: string | ||
42 | let pluginDataPath: string | ||
43 | |||
44 | async function getFileContent () { | ||
45 | const files = await readdir(pluginDataPath) | ||
46 | expect(files).to.have.lengthOf(1) | ||
47 | |||
48 | return readFile(join(pluginDataPath, files[0]), 'utf8') | ||
49 | } | ||
50 | |||
51 | before(function () { | ||
52 | dataPath = server.servers.buildDirectory('plugins/data') | ||
53 | pluginDataPath = join(dataPath, 'peertube-plugin-test-six') | ||
54 | }) | ||
55 | |||
56 | it('Should have created the directory on install', async function () { | ||
57 | const dataPath = server.servers.buildDirectory('plugins/data') | ||
58 | const pluginDataPath = join(dataPath, 'peertube-plugin-test-six') | ||
59 | |||
60 | expect(await pathExists(dataPath)).to.be.true | ||
61 | expect(await pathExists(pluginDataPath)).to.be.true | ||
62 | expect(await readdir(pluginDataPath)).to.have.lengthOf(0) | ||
63 | }) | ||
64 | |||
65 | it('Should have created a file', async function () { | ||
66 | await makeGetRequest({ | ||
67 | url: server.url, | ||
68 | token: server.accessToken, | ||
69 | path: '/plugins/test-six/router/create-file', | ||
70 | expectedStatus: HttpStatusCode.OK_200 | ||
71 | }) | ||
72 | |||
73 | const content = await getFileContent() | ||
74 | expect(content).to.equal('Prince Ali') | ||
75 | }) | ||
76 | |||
77 | it('Should still have the file after an uninstallation', async function () { | ||
78 | await server.plugins.uninstall({ npmName: 'peertube-plugin-test-six' }) | ||
79 | |||
80 | const content = await getFileContent() | ||
81 | expect(content).to.equal('Prince Ali') | ||
82 | }) | ||
83 | |||
84 | it('Should still have the file after the reinstallation', async function () { | ||
85 | await server.plugins.install({ path: PluginsCommand.getPluginTestPath('-six') }) | ||
86 | |||
87 | const content = await getFileContent() | ||
88 | expect(content).to.equal('Prince Ali') | ||
89 | }) | ||
90 | }) | ||
91 | |||
92 | after(async function () { | ||
93 | await cleanupTests([ server ]) | ||
94 | }) | ||
95 | }) | ||
diff --git a/packages/tests/src/plugins/plugin-transcoding.ts b/packages/tests/src/plugins/plugin-transcoding.ts new file mode 100644 index 000000000..2f50f65ff --- /dev/null +++ b/packages/tests/src/plugins/plugin-transcoding.ts | |||
@@ -0,0 +1,279 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import { expect } from 'chai' | ||
4 | import { getAudioStream, getVideoStream, getVideoStreamFPS } from '@peertube/peertube-ffmpeg' | ||
5 | import { VideoPrivacy } from '@peertube/peertube-models' | ||
6 | import { | ||
7 | cleanupTests, | ||
8 | createSingleServer, | ||
9 | PeerTubeServer, | ||
10 | PluginsCommand, | ||
11 | setAccessTokensToServers, | ||
12 | setDefaultVideoChannel, | ||
13 | testFfmpegStreamError, | ||
14 | waitJobs | ||
15 | } from '@peertube/peertube-server-commands' | ||
16 | |||
17 | async function createLiveWrapper (server: PeerTubeServer) { | ||
18 | const liveAttributes = { | ||
19 | name: 'live video', | ||
20 | channelId: server.store.channel.id, | ||
21 | privacy: VideoPrivacy.PUBLIC | ||
22 | } | ||
23 | |||
24 | const { uuid } = await server.live.create({ fields: liveAttributes }) | ||
25 | |||
26 | return uuid | ||
27 | } | ||
28 | |||
29 | function updateConf (server: PeerTubeServer, vodProfile: string, liveProfile: string) { | ||
30 | return server.config.updateCustomSubConfig({ | ||
31 | newConfig: { | ||
32 | transcoding: { | ||
33 | enabled: true, | ||
34 | profile: vodProfile, | ||
35 | hls: { | ||
36 | enabled: true | ||
37 | }, | ||
38 | webVideos: { | ||
39 | enabled: true | ||
40 | }, | ||
41 | resolutions: { | ||
42 | '240p': true, | ||
43 | '360p': false, | ||
44 | '480p': false, | ||
45 | '720p': true | ||
46 | } | ||
47 | }, | ||
48 | live: { | ||
49 | transcoding: { | ||
50 | profile: liveProfile, | ||
51 | enabled: true, | ||
52 | resolutions: { | ||
53 | '240p': true, | ||
54 | '360p': false, | ||
55 | '480p': false, | ||
56 | '720p': true | ||
57 | } | ||
58 | } | ||
59 | } | ||
60 | } | ||
61 | }) | ||
62 | } | ||
63 | |||
64 | describe('Test transcoding plugins', function () { | ||
65 | let server: PeerTubeServer | ||
66 | |||
67 | before(async function () { | ||
68 | this.timeout(60000) | ||
69 | |||
70 | server = await createSingleServer(1) | ||
71 | await setAccessTokensToServers([ server ]) | ||
72 | await setDefaultVideoChannel([ server ]) | ||
73 | |||
74 | await updateConf(server, 'default', 'default') | ||
75 | }) | ||
76 | |||
77 | describe('When using a plugin adding profiles to existing encoders', function () { | ||
78 | |||
79 | async function checkVideoFPS (uuid: string, type: 'above' | 'below', fps: number) { | ||
80 | const video = await server.videos.get({ id: uuid }) | ||
81 | const files = video.files.concat(...video.streamingPlaylists.map(p => p.files)) | ||
82 | |||
83 | for (const file of files) { | ||
84 | if (type === 'above') { | ||
85 | expect(file.fps).to.be.above(fps) | ||
86 | } else { | ||
87 | expect(file.fps).to.be.below(fps) | ||
88 | } | ||
89 | } | ||
90 | } | ||
91 | |||
92 | async function checkLiveFPS (uuid: string, type: 'above' | 'below', fps: number) { | ||
93 | const playlistUrl = `${server.url}/static/streaming-playlists/hls/${uuid}/0.m3u8` | ||
94 | const videoFPS = await getVideoStreamFPS(playlistUrl) | ||
95 | |||
96 | if (type === 'above') { | ||
97 | expect(videoFPS).to.be.above(fps) | ||
98 | } else { | ||
99 | expect(videoFPS).to.be.below(fps) | ||
100 | } | ||
101 | } | ||
102 | |||
103 | before(async function () { | ||
104 | await server.plugins.install({ path: PluginsCommand.getPluginTestPath('-transcoding-one') }) | ||
105 | }) | ||
106 | |||
107 | it('Should have the appropriate available profiles', async function () { | ||
108 | const config = await server.config.getConfig() | ||
109 | |||
110 | expect(config.transcoding.availableProfiles).to.have.members([ 'default', 'low-vod', 'input-options-vod', 'bad-scale-vod' ]) | ||
111 | expect(config.live.transcoding.availableProfiles).to.have.members([ 'default', 'high-live', 'input-options-live', 'bad-scale-live' ]) | ||
112 | }) | ||
113 | |||
114 | describe('VOD', function () { | ||
115 | |||
116 | it('Should not use the plugin profile if not chosen by the admin', async function () { | ||
117 | this.timeout(240000) | ||
118 | |||
119 | const videoUUID = (await server.videos.quickUpload({ name: 'video' })).uuid | ||
120 | await waitJobs([ server ]) | ||
121 | |||
122 | await checkVideoFPS(videoUUID, 'above', 20) | ||
123 | }) | ||
124 | |||
125 | it('Should use the vod profile', async function () { | ||
126 | this.timeout(240000) | ||
127 | |||
128 | await updateConf(server, 'low-vod', 'default') | ||
129 | |||
130 | const videoUUID = (await server.videos.quickUpload({ name: 'video' })).uuid | ||
131 | await waitJobs([ server ]) | ||
132 | |||
133 | await checkVideoFPS(videoUUID, 'below', 12) | ||
134 | }) | ||
135 | |||
136 | it('Should apply input options in vod profile', async function () { | ||
137 | this.timeout(240000) | ||
138 | |||
139 | await updateConf(server, 'input-options-vod', 'default') | ||
140 | |||
141 | const videoUUID = (await server.videos.quickUpload({ name: 'video' })).uuid | ||
142 | await waitJobs([ server ]) | ||
143 | |||
144 | await checkVideoFPS(videoUUID, 'below', 6) | ||
145 | }) | ||
146 | |||
147 | it('Should apply the scale filter in vod profile', async function () { | ||
148 | this.timeout(240000) | ||
149 | |||
150 | await updateConf(server, 'bad-scale-vod', 'default') | ||
151 | |||
152 | const videoUUID = (await server.videos.quickUpload({ name: 'video' })).uuid | ||
153 | await waitJobs([ server ]) | ||
154 | |||
155 | // Transcoding failed | ||
156 | const video = await server.videos.get({ id: videoUUID }) | ||
157 | expect(video.files).to.have.lengthOf(1) | ||
158 | expect(video.streamingPlaylists).to.have.lengthOf(0) | ||
159 | }) | ||
160 | }) | ||
161 | |||
162 | describe('Live', function () { | ||
163 | |||
164 | it('Should not use the plugin profile if not chosen by the admin', async function () { | ||
165 | this.timeout(240000) | ||
166 | |||
167 | const liveVideoId = await createLiveWrapper(server) | ||
168 | |||
169 | await server.live.sendRTMPStreamInVideo({ videoId: liveVideoId, fixtureName: 'video_very_short_240p.mp4' }) | ||
170 | await server.live.waitUntilPublished({ videoId: liveVideoId }) | ||
171 | await waitJobs([ server ]) | ||
172 | |||
173 | await checkLiveFPS(liveVideoId, 'above', 20) | ||
174 | }) | ||
175 | |||
176 | it('Should use the live profile', async function () { | ||
177 | this.timeout(240000) | ||
178 | |||
179 | await updateConf(server, 'low-vod', 'high-live') | ||
180 | |||
181 | const liveVideoId = await createLiveWrapper(server) | ||
182 | |||
183 | await server.live.sendRTMPStreamInVideo({ videoId: liveVideoId, fixtureName: 'video_very_short_240p.mp4' }) | ||
184 | await server.live.waitUntilPublished({ videoId: liveVideoId }) | ||
185 | await waitJobs([ server ]) | ||
186 | |||
187 | await checkLiveFPS(liveVideoId, 'above', 45) | ||
188 | }) | ||
189 | |||
190 | it('Should apply the input options on live profile', async function () { | ||
191 | this.timeout(240000) | ||
192 | |||
193 | await updateConf(server, 'low-vod', 'input-options-live') | ||
194 | |||
195 | const liveVideoId = await createLiveWrapper(server) | ||
196 | |||
197 | await server.live.sendRTMPStreamInVideo({ videoId: liveVideoId, fixtureName: 'video_very_short_240p.mp4' }) | ||
198 | await server.live.waitUntilPublished({ videoId: liveVideoId }) | ||
199 | await waitJobs([ server ]) | ||
200 | |||
201 | await checkLiveFPS(liveVideoId, 'above', 45) | ||
202 | }) | ||
203 | |||
204 | it('Should apply the scale filter name on live profile', async function () { | ||
205 | this.timeout(240000) | ||
206 | |||
207 | await updateConf(server, 'low-vod', 'bad-scale-live') | ||
208 | |||
209 | const liveVideoId = await createLiveWrapper(server) | ||
210 | |||
211 | const command = await server.live.sendRTMPStreamInVideo({ videoId: liveVideoId, fixtureName: 'video_very_short_240p.mp4' }) | ||
212 | await testFfmpegStreamError(command, true) | ||
213 | }) | ||
214 | |||
215 | it('Should default to the default profile if the specified profile does not exist', async function () { | ||
216 | this.timeout(240000) | ||
217 | |||
218 | await server.plugins.uninstall({ npmName: 'peertube-plugin-test-transcoding-one' }) | ||
219 | |||
220 | const config = await server.config.getConfig() | ||
221 | |||
222 | expect(config.transcoding.availableProfiles).to.deep.equal([ 'default' ]) | ||
223 | expect(config.live.transcoding.availableProfiles).to.deep.equal([ 'default' ]) | ||
224 | |||
225 | const videoUUID = (await server.videos.quickUpload({ name: 'video', fixture: 'video_very_short_240p.mp4' })).uuid | ||
226 | await waitJobs([ server ]) | ||
227 | |||
228 | await checkVideoFPS(videoUUID, 'above', 20) | ||
229 | }) | ||
230 | }) | ||
231 | |||
232 | }) | ||
233 | |||
234 | describe('When using a plugin adding new encoders', function () { | ||
235 | |||
236 | before(async function () { | ||
237 | await server.plugins.install({ path: PluginsCommand.getPluginTestPath('-transcoding-two') }) | ||
238 | |||
239 | await updateConf(server, 'test-vod-profile', 'test-live-profile') | ||
240 | }) | ||
241 | |||
242 | it('Should use the new vod encoders', async function () { | ||
243 | this.timeout(240000) | ||
244 | |||
245 | const videoUUID = (await server.videos.quickUpload({ name: 'video', fixture: 'video_very_short_240p.mp4' })).uuid | ||
246 | await waitJobs([ server ]) | ||
247 | |||
248 | const video = await server.videos.get({ id: videoUUID }) | ||
249 | |||
250 | const path = server.servers.buildWebVideoFilePath(video.files[0].fileUrl) | ||
251 | const audioProbe = await getAudioStream(path) | ||
252 | expect(audioProbe.audioStream.codec_name).to.equal('opus') | ||
253 | |||
254 | const videoProbe = await getVideoStream(path) | ||
255 | expect(videoProbe.codec_name).to.equal('vp9') | ||
256 | }) | ||
257 | |||
258 | it('Should use the new live encoders', async function () { | ||
259 | this.timeout(240000) | ||
260 | |||
261 | const liveVideoId = await createLiveWrapper(server) | ||
262 | |||
263 | await server.live.sendRTMPStreamInVideo({ videoId: liveVideoId, fixtureName: 'video_short2.webm' }) | ||
264 | await server.live.waitUntilPublished({ videoId: liveVideoId }) | ||
265 | await waitJobs([ server ]) | ||
266 | |||
267 | const playlistUrl = `${server.url}/static/streaming-playlists/hls/${liveVideoId}/0.m3u8` | ||
268 | const audioProbe = await getAudioStream(playlistUrl) | ||
269 | expect(audioProbe.audioStream.codec_name).to.equal('opus') | ||
270 | |||
271 | const videoProbe = await getVideoStream(playlistUrl) | ||
272 | expect(videoProbe.codec_name).to.equal('h264') | ||
273 | }) | ||
274 | }) | ||
275 | |||
276 | after(async function () { | ||
277 | await cleanupTests([ server ]) | ||
278 | }) | ||
279 | }) | ||
diff --git a/packages/tests/src/plugins/plugin-unloading.ts b/packages/tests/src/plugins/plugin-unloading.ts new file mode 100644 index 000000000..70310bc8c --- /dev/null +++ b/packages/tests/src/plugins/plugin-unloading.ts | |||
@@ -0,0 +1,75 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import { expect } from 'chai' | ||
4 | import { | ||
5 | cleanupTests, | ||
6 | createSingleServer, | ||
7 | makeGetRequest, | ||
8 | PeerTubeServer, | ||
9 | PluginsCommand, | ||
10 | setAccessTokensToServers | ||
11 | } from '@peertube/peertube-server-commands' | ||
12 | import { HttpStatusCode } from '@peertube/peertube-models' | ||
13 | |||
14 | describe('Test plugins module unloading', function () { | ||
15 | let server: PeerTubeServer = null | ||
16 | const requestPath = '/plugins/test-unloading/router/get' | ||
17 | let value: string = null | ||
18 | |||
19 | before(async function () { | ||
20 | this.timeout(30000) | ||
21 | |||
22 | server = await createSingleServer(1) | ||
23 | await setAccessTokensToServers([ server ]) | ||
24 | |||
25 | await server.plugins.install({ path: PluginsCommand.getPluginTestPath('-unloading') }) | ||
26 | }) | ||
27 | |||
28 | it('Should return a numeric value', async function () { | ||
29 | const res = await makeGetRequest({ | ||
30 | url: server.url, | ||
31 | path: requestPath, | ||
32 | expectedStatus: HttpStatusCode.OK_200 | ||
33 | }) | ||
34 | |||
35 | expect(res.body.message).to.match(/^\d+$/) | ||
36 | value = res.body.message | ||
37 | }) | ||
38 | |||
39 | it('Should return the same value the second time', async function () { | ||
40 | const res = await makeGetRequest({ | ||
41 | url: server.url, | ||
42 | path: requestPath, | ||
43 | expectedStatus: HttpStatusCode.OK_200 | ||
44 | }) | ||
45 | |||
46 | expect(res.body.message).to.be.equal(value) | ||
47 | }) | ||
48 | |||
49 | it('Should uninstall the plugin and free the route', async function () { | ||
50 | await server.plugins.uninstall({ npmName: 'peertube-plugin-test-unloading' }) | ||
51 | |||
52 | await makeGetRequest({ | ||
53 | url: server.url, | ||
54 | path: requestPath, | ||
55 | expectedStatus: HttpStatusCode.NOT_FOUND_404 | ||
56 | }) | ||
57 | }) | ||
58 | |||
59 | it('Should return a different numeric value', async function () { | ||
60 | await server.plugins.install({ path: PluginsCommand.getPluginTestPath('-unloading') }) | ||
61 | |||
62 | const res = await makeGetRequest({ | ||
63 | url: server.url, | ||
64 | path: requestPath, | ||
65 | expectedStatus: HttpStatusCode.OK_200 | ||
66 | }) | ||
67 | |||
68 | expect(res.body.message).to.match(/^\d+$/) | ||
69 | expect(res.body.message).to.be.not.equal(value) | ||
70 | }) | ||
71 | |||
72 | after(async function () { | ||
73 | await cleanupTests([ server ]) | ||
74 | }) | ||
75 | }) | ||
diff --git a/packages/tests/src/plugins/plugin-websocket.ts b/packages/tests/src/plugins/plugin-websocket.ts new file mode 100644 index 000000000..832dcebd0 --- /dev/null +++ b/packages/tests/src/plugins/plugin-websocket.ts | |||
@@ -0,0 +1,76 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import WebSocket from 'ws' | ||
4 | import { | ||
5 | cleanupTests, | ||
6 | createSingleServer, | ||
7 | PeerTubeServer, | ||
8 | PluginsCommand, | ||
9 | setAccessTokensToServers | ||
10 | } from '@peertube/peertube-server-commands' | ||
11 | |||
12 | function buildWebSocket (server: PeerTubeServer, path: string) { | ||
13 | return new WebSocket('ws://' + server.host + path) | ||
14 | } | ||
15 | |||
16 | function expectErrorOrTimeout (server: PeerTubeServer, path: string, expectedTimeout: number) { | ||
17 | return new Promise<void>((res, rej) => { | ||
18 | const ws = buildWebSocket(server, path) | ||
19 | ws.on('error', () => res()) | ||
20 | |||
21 | const timeout = setTimeout(() => res(), expectedTimeout) | ||
22 | |||
23 | ws.on('open', () => { | ||
24 | clearTimeout(timeout) | ||
25 | |||
26 | return rej(new Error('Connect did not timeout')) | ||
27 | }) | ||
28 | }) | ||
29 | } | ||
30 | |||
31 | describe('Test plugin websocket', function () { | ||
32 | let server: PeerTubeServer | ||
33 | const basePaths = [ | ||
34 | '/plugins/test-websocket/ws/', | ||
35 | '/plugins/test-websocket/0.0.1/ws/' | ||
36 | ] | ||
37 | |||
38 | before(async function () { | ||
39 | this.timeout(30000) | ||
40 | |||
41 | server = await createSingleServer(1) | ||
42 | await setAccessTokensToServers([ server ]) | ||
43 | |||
44 | await server.plugins.install({ path: PluginsCommand.getPluginTestPath('-websocket') }) | ||
45 | }) | ||
46 | |||
47 | it('Should not connect to the websocket without the appropriate path', async function () { | ||
48 | const paths = [ | ||
49 | '/plugins/unknown/ws/', | ||
50 | '/plugins/unknown/0.0.1/ws/' | ||
51 | ] | ||
52 | |||
53 | for (const path of paths) { | ||
54 | await expectErrorOrTimeout(server, path, 1000) | ||
55 | } | ||
56 | }) | ||
57 | |||
58 | it('Should not connect to the websocket without the appropriate sub path', async function () { | ||
59 | for (const path of basePaths) { | ||
60 | await expectErrorOrTimeout(server, path + '/unknown', 1000) | ||
61 | } | ||
62 | }) | ||
63 | |||
64 | it('Should connect to the websocket and receive pong', function (done) { | ||
65 | const ws = buildWebSocket(server, basePaths[0]) | ||
66 | |||
67 | ws.on('open', () => ws.send('ping')) | ||
68 | ws.on('message', data => { | ||
69 | if (data.toString() === 'pong') return done() | ||
70 | }) | ||
71 | }) | ||
72 | |||
73 | after(async function () { | ||
74 | await cleanupTests([ server ]) | ||
75 | }) | ||
76 | }) | ||
diff --git a/packages/tests/src/plugins/translations.ts b/packages/tests/src/plugins/translations.ts new file mode 100644 index 000000000..a69e14134 --- /dev/null +++ b/packages/tests/src/plugins/translations.ts | |||
@@ -0,0 +1,80 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import { expect } from 'chai' | ||
4 | import { | ||
5 | cleanupTests, | ||
6 | createSingleServer, | ||
7 | PeerTubeServer, | ||
8 | PluginsCommand, | ||
9 | setAccessTokensToServers | ||
10 | } from '@peertube/peertube-server-commands' | ||
11 | |||
12 | describe('Test plugin translations', function () { | ||
13 | let server: PeerTubeServer | ||
14 | let command: PluginsCommand | ||
15 | |||
16 | before(async function () { | ||
17 | this.timeout(30000) | ||
18 | |||
19 | server = await createSingleServer(1) | ||
20 | await setAccessTokensToServers([ server ]) | ||
21 | |||
22 | command = server.plugins | ||
23 | |||
24 | await command.install({ path: PluginsCommand.getPluginTestPath() }) | ||
25 | await command.install({ path: PluginsCommand.getPluginTestPath('-filter-translations') }) | ||
26 | }) | ||
27 | |||
28 | it('Should not have translations for locale pt', async function () { | ||
29 | const body = await command.getTranslations({ locale: 'pt' }) | ||
30 | |||
31 | expect(body).to.deep.equal({}) | ||
32 | }) | ||
33 | |||
34 | it('Should have translations for locale fr', async function () { | ||
35 | const body = await command.getTranslations({ locale: 'fr-FR' }) | ||
36 | |||
37 | expect(body).to.deep.equal({ | ||
38 | 'peertube-plugin-test': { | ||
39 | Hi: 'Coucou' | ||
40 | }, | ||
41 | 'peertube-plugin-test-filter-translations': { | ||
42 | 'Hello world': 'Bonjour le monde' | ||
43 | } | ||
44 | }) | ||
45 | }) | ||
46 | |||
47 | it('Should have translations of locale it', async function () { | ||
48 | const body = await command.getTranslations({ locale: 'it-IT' }) | ||
49 | |||
50 | expect(body).to.deep.equal({ | ||
51 | 'peertube-plugin-test-filter-translations': { | ||
52 | 'Hello world': 'Ciao, mondo!' | ||
53 | } | ||
54 | }) | ||
55 | }) | ||
56 | |||
57 | it('Should remove the plugin and remove the locales', async function () { | ||
58 | await command.uninstall({ npmName: 'peertube-plugin-test-filter-translations' }) | ||
59 | |||
60 | { | ||
61 | const body = await command.getTranslations({ locale: 'fr-FR' }) | ||
62 | |||
63 | expect(body).to.deep.equal({ | ||
64 | 'peertube-plugin-test': { | ||
65 | Hi: 'Coucou' | ||
66 | } | ||
67 | }) | ||
68 | } | ||
69 | |||
70 | { | ||
71 | const body = await command.getTranslations({ locale: 'it-IT' }) | ||
72 | |||
73 | expect(body).to.deep.equal({}) | ||
74 | } | ||
75 | }) | ||
76 | |||
77 | after(async function () { | ||
78 | await cleanupTests([ server ]) | ||
79 | }) | ||
80 | }) | ||
diff --git a/packages/tests/src/plugins/video-constants.ts b/packages/tests/src/plugins/video-constants.ts new file mode 100644 index 000000000..b81240a64 --- /dev/null +++ b/packages/tests/src/plugins/video-constants.ts | |||
@@ -0,0 +1,180 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import { expect } from 'chai' | ||
4 | import { | ||
5 | cleanupTests, | ||
6 | createSingleServer, | ||
7 | makeGetRequest, | ||
8 | PeerTubeServer, | ||
9 | PluginsCommand, | ||
10 | setAccessTokensToServers | ||
11 | } from '@peertube/peertube-server-commands' | ||
12 | import { HttpStatusCode, VideoPlaylistPrivacy, VideoPrivacy } from '@peertube/peertube-models' | ||
13 | |||
14 | describe('Test plugin altering video constants', function () { | ||
15 | let server: PeerTubeServer | ||
16 | |||
17 | before(async function () { | ||
18 | this.timeout(30000) | ||
19 | |||
20 | server = await createSingleServer(1) | ||
21 | await setAccessTokensToServers([ server ]) | ||
22 | |||
23 | await server.plugins.install({ path: PluginsCommand.getPluginTestPath('-video-constants') }) | ||
24 | }) | ||
25 | |||
26 | it('Should have updated languages', async function () { | ||
27 | const languages = await server.videos.getLanguages() | ||
28 | |||
29 | expect(languages['en']).to.not.exist | ||
30 | expect(languages['fr']).to.not.exist | ||
31 | |||
32 | expect(languages['al_bhed']).to.equal('Al Bhed') | ||
33 | expect(languages['al_bhed2']).to.equal('Al Bhed 2') | ||
34 | expect(languages['al_bhed3']).to.not.exist | ||
35 | }) | ||
36 | |||
37 | it('Should have updated categories', async function () { | ||
38 | const categories = await server.videos.getCategories() | ||
39 | |||
40 | expect(categories[1]).to.not.exist | ||
41 | expect(categories[2]).to.not.exist | ||
42 | |||
43 | expect(categories[42]).to.equal('Best category') | ||
44 | expect(categories[43]).to.equal('High best category') | ||
45 | }) | ||
46 | |||
47 | it('Should have updated licences', async function () { | ||
48 | const licences = await server.videos.getLicences() | ||
49 | |||
50 | expect(licences[1]).to.not.exist | ||
51 | expect(licences[7]).to.not.exist | ||
52 | |||
53 | expect(licences[42]).to.equal('Best licence') | ||
54 | expect(licences[43]).to.equal('High best licence') | ||
55 | }) | ||
56 | |||
57 | it('Should have updated video privacies', async function () { | ||
58 | const privacies = await server.videos.getPrivacies() | ||
59 | |||
60 | expect(privacies[1]).to.exist | ||
61 | expect(privacies[2]).to.not.exist | ||
62 | expect(privacies[3]).to.exist | ||
63 | expect(privacies[4]).to.exist | ||
64 | }) | ||
65 | |||
66 | it('Should have updated playlist privacies', async function () { | ||
67 | const playlistPrivacies = await server.playlists.getPrivacies() | ||
68 | |||
69 | expect(playlistPrivacies[1]).to.exist | ||
70 | expect(playlistPrivacies[2]).to.exist | ||
71 | expect(playlistPrivacies[3]).to.not.exist | ||
72 | }) | ||
73 | |||
74 | it('Should not be able to create a video with this privacy', async function () { | ||
75 | const attributes = { name: 'video', privacy: VideoPrivacy.UNLISTED } | ||
76 | await server.videos.upload({ attributes, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | ||
77 | }) | ||
78 | |||
79 | it('Should not be able to create a video with this privacy', async function () { | ||
80 | const attributes = { displayName: 'video playlist', privacy: VideoPlaylistPrivacy.PRIVATE } | ||
81 | await server.playlists.create({ attributes, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | ||
82 | }) | ||
83 | |||
84 | it('Should be able to upload a video with these values', async function () { | ||
85 | const attributes = { name: 'video', category: 42, licence: 42, language: 'al_bhed2' } | ||
86 | const { uuid } = await server.videos.upload({ attributes }) | ||
87 | |||
88 | const video = await server.videos.get({ id: uuid }) | ||
89 | expect(video.language.label).to.equal('Al Bhed 2') | ||
90 | expect(video.licence.label).to.equal('Best licence') | ||
91 | expect(video.category.label).to.equal('Best category') | ||
92 | }) | ||
93 | |||
94 | it('Should uninstall the plugin and reset languages, categories, licences and privacies', async function () { | ||
95 | await server.plugins.uninstall({ npmName: 'peertube-plugin-test-video-constants' }) | ||
96 | |||
97 | { | ||
98 | const languages = await server.videos.getLanguages() | ||
99 | |||
100 | expect(languages['en']).to.equal('English') | ||
101 | expect(languages['fr']).to.equal('French') | ||
102 | |||
103 | expect(languages['al_bhed']).to.not.exist | ||
104 | expect(languages['al_bhed2']).to.not.exist | ||
105 | expect(languages['al_bhed3']).to.not.exist | ||
106 | } | ||
107 | |||
108 | { | ||
109 | const categories = await server.videos.getCategories() | ||
110 | |||
111 | expect(categories[1]).to.equal('Music') | ||
112 | expect(categories[2]).to.equal('Films') | ||
113 | |||
114 | expect(categories[42]).to.not.exist | ||
115 | expect(categories[43]).to.not.exist | ||
116 | } | ||
117 | |||
118 | { | ||
119 | const licences = await server.videos.getLicences() | ||
120 | |||
121 | expect(licences[1]).to.equal('Attribution') | ||
122 | expect(licences[7]).to.equal('Public Domain Dedication') | ||
123 | |||
124 | expect(licences[42]).to.not.exist | ||
125 | expect(licences[43]).to.not.exist | ||
126 | } | ||
127 | |||
128 | { | ||
129 | const privacies = await server.videos.getPrivacies() | ||
130 | |||
131 | expect(privacies[1]).to.exist | ||
132 | expect(privacies[2]).to.exist | ||
133 | expect(privacies[3]).to.exist | ||
134 | expect(privacies[4]).to.exist | ||
135 | } | ||
136 | |||
137 | { | ||
138 | const playlistPrivacies = await server.playlists.getPrivacies() | ||
139 | |||
140 | expect(playlistPrivacies[1]).to.exist | ||
141 | expect(playlistPrivacies[2]).to.exist | ||
142 | expect(playlistPrivacies[3]).to.exist | ||
143 | } | ||
144 | }) | ||
145 | |||
146 | it('Should be able to reset categories', async function () { | ||
147 | await server.plugins.install({ path: PluginsCommand.getPluginTestPath('-video-constants') }) | ||
148 | |||
149 | { | ||
150 | const categories = await server.videos.getCategories() | ||
151 | |||
152 | expect(categories[1]).to.not.exist | ||
153 | expect(categories[2]).to.not.exist | ||
154 | |||
155 | expect(categories[42]).to.exist | ||
156 | expect(categories[43]).to.exist | ||
157 | } | ||
158 | |||
159 | await makeGetRequest({ | ||
160 | url: server.url, | ||
161 | token: server.accessToken, | ||
162 | path: '/plugins/test-video-constants/router/reset-categories', | ||
163 | expectedStatus: HttpStatusCode.NO_CONTENT_204 | ||
164 | }) | ||
165 | |||
166 | { | ||
167 | const categories = await server.videos.getCategories() | ||
168 | |||
169 | expect(categories[1]).to.exist | ||
170 | expect(categories[2]).to.exist | ||
171 | |||
172 | expect(categories[42]).to.not.exist | ||
173 | expect(categories[43]).to.not.exist | ||
174 | } | ||
175 | }) | ||
176 | |||
177 | after(async function () { | ||
178 | await cleanupTests([ server ]) | ||
179 | }) | ||
180 | }) | ||