aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/tests/api/notifications/comments-notifications.ts
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2021-07-21 15:51:30 +0200
committerChocobozzz <me@florianbigard.com>2021-07-21 15:51:30 +0200
commita24bd1ed41b43790bab6ba789580bb4e85f07d85 (patch)
treea54b0f6c921ba83a6e909cd0ced325b2d4b8863c /server/tests/api/notifications/comments-notifications.ts
parent5f26f13b3c16ac5ae0a3b0a7142d84a9528cf565 (diff)
parentc63830f15403ac4e750829f27d8bbbdc9a59282c (diff)
downloadPeerTube-a24bd1ed41b43790bab6ba789580bb4e85f07d85.tar.gz
PeerTube-a24bd1ed41b43790bab6ba789580bb4e85f07d85.tar.zst
PeerTube-a24bd1ed41b43790bab6ba789580bb4e85f07d85.zip
Merge branch 'next' into develop
Diffstat (limited to 'server/tests/api/notifications/comments-notifications.ts')
-rw-r--r--server/tests/api/notifications/comments-notifications.ts173
1 files changed, 65 insertions, 108 deletions
diff --git a/server/tests/api/notifications/comments-notifications.ts b/server/tests/api/notifications/comments-notifications.ts
index d2badf237..cbb46e510 100644
--- a/server/tests/api/notifications/comments-notifications.ts
+++ b/server/tests/api/notifications/comments-notifications.ts
@@ -3,30 +3,22 @@
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { 5import {
6 addAccountToAccountBlocklist,
7 addVideoCommentReply,
8 addVideoCommentThread,
9 checkCommentMention, 6 checkCommentMention,
10 CheckerBaseParams, 7 CheckerBaseParams,
11 checkNewCommentOnMyVideo, 8 checkNewCommentOnMyVideo,
12 cleanupTests, 9 cleanupTests,
13 getVideoCommentThreads,
14 getVideoThreadComments,
15 MockSmtpServer, 10 MockSmtpServer,
11 PeerTubeServer,
16 prepareNotificationsTest, 12 prepareNotificationsTest,
17 removeAccountFromAccountBlocklist,
18 ServerInfo,
19 updateMyUser,
20 uploadVideo,
21 waitJobs 13 waitJobs
22} from '@shared/extra-utils' 14} from '@shared/extra-utils'
23import { UserNotification, VideoCommentThreadTree } from '@shared/models' 15import { UserNotification } from '@shared/models'
24 16
25const expect = chai.expect 17const expect = chai.expect
26 18
27describe('Test comments notifications', function () { 19describe('Test comments notifications', function () {
28 let servers: ServerInfo[] = [] 20 let servers: PeerTubeServer[] = []
29 let userAccessToken: string 21 let userToken: string
30 let userNotifications: UserNotification[] = [] 22 let userNotifications: UserNotification[] = []
31 let emails: object[] = [] 23 let emails: object[] = []
32 24
@@ -40,7 +32,7 @@ describe('Test comments notifications', function () {
40 32
41 const res = await prepareNotificationsTest(2) 33 const res = await prepareNotificationsTest(2)
42 emails = res.emails 34 emails = res.emails
43 userAccessToken = res.userAccessToken 35 userToken = res.userAccessToken
44 servers = res.servers 36 servers = res.servers
45 userNotifications = res.userNotifications 37 userNotifications = res.userNotifications
46 }) 38 })
@@ -53,18 +45,17 @@ describe('Test comments notifications', function () {
53 server: servers[0], 45 server: servers[0],
54 emails, 46 emails,
55 socketNotifications: userNotifications, 47 socketNotifications: userNotifications,
56 token: userAccessToken 48 token: userToken
57 } 49 }
58 }) 50 })
59 51
60 it('Should not send a new comment notification after a comment on another video', async function () { 52 it('Should not send a new comment notification after a comment on another video', async function () {
61 this.timeout(20000) 53 this.timeout(20000)
62 54
63 const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'super video' }) 55 const { uuid } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
64 const uuid = resVideo.body.video.uuid
65 56
66 const resComment = await addVideoCommentThread(servers[0].url, servers[0].accessToken, uuid, 'comment') 57 const created = await servers[0].comments.createThread({ videoId: uuid, text: 'comment' })
67 const commentId = resComment.body.comment.id 58 const commentId = created.id
68 59
69 await waitJobs(servers) 60 await waitJobs(servers)
70 await checkNewCommentOnMyVideo(baseParams, uuid, commentId, commentId, 'absence') 61 await checkNewCommentOnMyVideo(baseParams, uuid, commentId, commentId, 'absence')
@@ -73,11 +64,10 @@ describe('Test comments notifications', function () {
73 it('Should not send a new comment notification if I comment my own video', async function () { 64 it('Should not send a new comment notification if I comment my own video', async function () {
74 this.timeout(20000) 65 this.timeout(20000)
75 66
76 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: 'super video' }) 67 const { uuid } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
77 const uuid = resVideo.body.video.uuid
78 68
79 const resComment = await addVideoCommentThread(servers[0].url, userAccessToken, uuid, 'comment') 69 const created = await servers[0].comments.createThread({ token: userToken, videoId: uuid, text: 'comment' })
80 const commentId = resComment.body.comment.id 70 const commentId = created.id
81 71
82 await waitJobs(servers) 72 await waitJobs(servers)
83 await checkNewCommentOnMyVideo(baseParams, uuid, commentId, commentId, 'absence') 73 await checkNewCommentOnMyVideo(baseParams, uuid, commentId, commentId, 'absence')
@@ -86,28 +76,26 @@ describe('Test comments notifications', function () {
86 it('Should not send a new comment notification if the account is muted', async function () { 76 it('Should not send a new comment notification if the account is muted', async function () {
87 this.timeout(20000) 77 this.timeout(20000)
88 78
89 await addAccountToAccountBlocklist(servers[0].url, userAccessToken, 'root') 79 await servers[0].blocklist.addToMyBlocklist({ token: userToken, account: 'root' })
90 80
91 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: 'super video' }) 81 const { uuid } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
92 const uuid = resVideo.body.video.uuid
93 82
94 const resComment = await addVideoCommentThread(servers[0].url, servers[0].accessToken, uuid, 'comment') 83 const created = await servers[0].comments.createThread({ videoId: uuid, text: 'comment' })
95 const commentId = resComment.body.comment.id 84 const commentId = created.id
96 85
97 await waitJobs(servers) 86 await waitJobs(servers)
98 await checkNewCommentOnMyVideo(baseParams, uuid, commentId, commentId, 'absence') 87 await checkNewCommentOnMyVideo(baseParams, uuid, commentId, commentId, 'absence')
99 88
100 await removeAccountFromAccountBlocklist(servers[0].url, userAccessToken, 'root') 89 await servers[0].blocklist.removeFromMyBlocklist({ token: userToken, account: 'root' })
101 }) 90 })
102 91
103 it('Should send a new comment notification after a local comment on my video', async function () { 92 it('Should send a new comment notification after a local comment on my video', async function () {
104 this.timeout(20000) 93 this.timeout(20000)
105 94
106 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: 'super video' }) 95 const { uuid } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
107 const uuid = resVideo.body.video.uuid
108 96
109 const resComment = await addVideoCommentThread(servers[0].url, servers[0].accessToken, uuid, 'comment') 97 const created = await servers[0].comments.createThread({ videoId: uuid, text: 'comment' })
110 const commentId = resComment.body.comment.id 98 const commentId = created.id
111 99
112 await waitJobs(servers) 100 await waitJobs(servers)
113 await checkNewCommentOnMyVideo(baseParams, uuid, commentId, commentId, 'presence') 101 await checkNewCommentOnMyVideo(baseParams, uuid, commentId, commentId, 'presence')
@@ -116,33 +104,29 @@ describe('Test comments notifications', function () {
116 it('Should send a new comment notification after a remote comment on my video', async function () { 104 it('Should send a new comment notification after a remote comment on my video', async function () {
117 this.timeout(20000) 105 this.timeout(20000)
118 106
119 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: 'super video' }) 107 const { uuid } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
120 const uuid = resVideo.body.video.uuid
121 108
122 await waitJobs(servers) 109 await waitJobs(servers)
123 110
124 await addVideoCommentThread(servers[1].url, servers[1].accessToken, uuid, 'comment') 111 await servers[1].comments.createThread({ videoId: uuid, text: 'comment' })
125 112
126 await waitJobs(servers) 113 await waitJobs(servers)
127 114
128 const resComment = await getVideoCommentThreads(servers[0].url, uuid, 0, 5) 115 const { data } = await servers[0].comments.listThreads({ videoId: uuid })
129 expect(resComment.body.data).to.have.lengthOf(1) 116 expect(data).to.have.lengthOf(1)
130 const commentId = resComment.body.data[0].id
131 117
118 const commentId = data[0].id
132 await checkNewCommentOnMyVideo(baseParams, uuid, commentId, commentId, 'presence') 119 await checkNewCommentOnMyVideo(baseParams, uuid, commentId, commentId, 'presence')
133 }) 120 })
134 121
135 it('Should send a new comment notification after a local reply on my video', async function () { 122 it('Should send a new comment notification after a local reply on my video', async function () {
136 this.timeout(20000) 123 this.timeout(20000)
137 124
138 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: 'super video' }) 125 const { uuid } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
139 const uuid = resVideo.body.video.uuid
140 126
141 const resThread = await addVideoCommentThread(servers[0].url, servers[0].accessToken, uuid, 'comment') 127 const { id: threadId } = await servers[0].comments.createThread({ videoId: uuid, text: 'comment' })
142 const threadId = resThread.body.comment.id
143 128
144 const resComment = await addVideoCommentReply(servers[0].url, servers[0].accessToken, uuid, threadId, 'reply') 129 const { id: commentId } = await servers[0].comments.addReply({ videoId: uuid, toCommentId: threadId, text: 'reply' })
145 const commentId = resComment.body.comment.id
146 130
147 await waitJobs(servers) 131 await waitJobs(servers)
148 await checkNewCommentOnMyVideo(baseParams, uuid, commentId, threadId, 'presence') 132 await checkNewCommentOnMyVideo(baseParams, uuid, commentId, threadId, 'presence')
@@ -151,24 +135,22 @@ describe('Test comments notifications', function () {
151 it('Should send a new comment notification after a remote reply on my video', async function () { 135 it('Should send a new comment notification after a remote reply on my video', async function () {
152 this.timeout(20000) 136 this.timeout(20000)
153 137
154 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: 'super video' }) 138 const { uuid } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
155 const uuid = resVideo.body.video.uuid
156 await waitJobs(servers) 139 await waitJobs(servers)
157 140
158 { 141 {
159 const resThread = await addVideoCommentThread(servers[1].url, servers[1].accessToken, uuid, 'comment') 142 const created = await servers[1].comments.createThread({ videoId: uuid, text: 'comment' })
160 const threadId = resThread.body.comment.id 143 const threadId = created.id
161 await addVideoCommentReply(servers[1].url, servers[1].accessToken, uuid, threadId, 'reply') 144 await servers[1].comments.addReply({ videoId: uuid, toCommentId: threadId, text: 'reply' })
162 } 145 }
163 146
164 await waitJobs(servers) 147 await waitJobs(servers)
165 148
166 const resThread = await getVideoCommentThreads(servers[0].url, uuid, 0, 5) 149 const { data } = await servers[0].comments.listThreads({ videoId: uuid })
167 expect(resThread.body.data).to.have.lengthOf(1) 150 expect(data).to.have.lengthOf(1)
168 const threadId = resThread.body.data[0].id
169 151
170 const resComments = await getVideoThreadComments(servers[0].url, uuid, threadId) 152 const threadId = data[0].id
171 const tree = resComments.body as VideoCommentThreadTree 153 const tree = await servers[0].comments.getThread({ videoId: uuid, threadId })
172 154
173 expect(tree.children).to.have.lengthOf(1) 155 expect(tree.children).to.have.lengthOf(1)
174 const commentId = tree.children[0].comment.id 156 const commentId = tree.children[0].comment.id
@@ -179,10 +161,9 @@ describe('Test comments notifications', function () {
179 it('Should convert markdown in comment to html', async function () { 161 it('Should convert markdown in comment to html', async function () {
180 this.timeout(20000) 162 this.timeout(20000)
181 163
182 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: 'cool video' }) 164 const { uuid } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'cool video' } })
183 const uuid = resVideo.body.video.uuid
184 165
185 await addVideoCommentThread(servers[0].url, servers[0].accessToken, uuid, commentText) 166 await servers[0].comments.createThread({ videoId: uuid, text: commentText })
186 167
187 await waitJobs(servers) 168 await waitJobs(servers)
188 169
@@ -199,30 +180,19 @@ describe('Test comments notifications', function () {
199 server: servers[0], 180 server: servers[0],
200 emails, 181 emails,
201 socketNotifications: userNotifications, 182 socketNotifications: userNotifications,
202 token: userAccessToken 183 token: userToken
203 } 184 }
204 185
205 await updateMyUser({ 186 await servers[0].users.updateMe({ displayName: 'super root name' })
206 url: servers[0].url, 187 await servers[1].users.updateMe({ displayName: 'super root 2 name' })
207 accessToken: servers[0].accessToken,
208 displayName: 'super root name'
209 })
210
211 await updateMyUser({
212 url: servers[1].url,
213 accessToken: servers[1].accessToken,
214 displayName: 'super root 2 name'
215 })
216 }) 188 })
217 189
218 it('Should not send a new mention comment notification if I mention the video owner', async function () { 190 it('Should not send a new mention comment notification if I mention the video owner', async function () {
219 this.timeout(10000) 191 this.timeout(10000)
220 192
221 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: 'super video' }) 193 const { uuid } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
222 const uuid = resVideo.body.video.uuid
223 194
224 const resComment = await addVideoCommentThread(servers[0].url, servers[0].accessToken, uuid, '@user_1 hello') 195 const { id: commentId } = await servers[0].comments.createThread({ videoId: uuid, text: '@user_1 hello' })
225 const commentId = resComment.body.comment.id
226 196
227 await waitJobs(servers) 197 await waitJobs(servers)
228 await checkCommentMention(baseParams, uuid, commentId, commentId, 'super root name', 'absence') 198 await checkCommentMention(baseParams, uuid, commentId, commentId, 'super root name', 'absence')
@@ -231,11 +201,9 @@ describe('Test comments notifications', function () {
231 it('Should not send a new mention comment notification if I mention myself', async function () { 201 it('Should not send a new mention comment notification if I mention myself', async function () {
232 this.timeout(10000) 202 this.timeout(10000)
233 203
234 const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'super video' }) 204 const { uuid } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
235 const uuid = resVideo.body.video.uuid
236 205
237 const resComment = await addVideoCommentThread(servers[0].url, userAccessToken, uuid, '@user_1 hello') 206 const { id: commentId } = await servers[0].comments.createThread({ token: userToken, videoId: uuid, text: '@user_1 hello' })
238 const commentId = resComment.body.comment.id
239 207
240 await waitJobs(servers) 208 await waitJobs(servers)
241 await checkCommentMention(baseParams, uuid, commentId, commentId, 'super root name', 'absence') 209 await checkCommentMention(baseParams, uuid, commentId, commentId, 'super root name', 'absence')
@@ -244,29 +212,25 @@ describe('Test comments notifications', function () {
244 it('Should not send a new mention notification if the account is muted', async function () { 212 it('Should not send a new mention notification if the account is muted', async function () {
245 this.timeout(10000) 213 this.timeout(10000)
246 214
247 await addAccountToAccountBlocklist(servers[0].url, userAccessToken, 'root') 215 await servers[0].blocklist.addToMyBlocklist({ token: userToken, account: 'root' })
248 216
249 const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'super video' }) 217 const { uuid } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
250 const uuid = resVideo.body.video.uuid
251 218
252 const resComment = await addVideoCommentThread(servers[0].url, servers[0].accessToken, uuid, '@user_1 hello') 219 const { id: commentId } = await servers[0].comments.createThread({ videoId: uuid, text: '@user_1 hello' })
253 const commentId = resComment.body.comment.id
254 220
255 await waitJobs(servers) 221 await waitJobs(servers)
256 await checkCommentMention(baseParams, uuid, commentId, commentId, 'super root name', 'absence') 222 await checkCommentMention(baseParams, uuid, commentId, commentId, 'super root name', 'absence')
257 223
258 await removeAccountFromAccountBlocklist(servers[0].url, userAccessToken, 'root') 224 await servers[0].blocklist.removeFromMyBlocklist({ token: userToken, account: 'root' })
259 }) 225 })
260 226
261 it('Should not send a new mention notification if the remote account mention a local account', async function () { 227 it('Should not send a new mention notification if the remote account mention a local account', async function () {
262 this.timeout(20000) 228 this.timeout(20000)
263 229
264 const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'super video' }) 230 const { uuid } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
265 const uuid = resVideo.body.video.uuid
266 231
267 await waitJobs(servers) 232 await waitJobs(servers)
268 const resThread = await addVideoCommentThread(servers[1].url, servers[1].accessToken, uuid, '@user_1 hello') 233 const { id: threadId } = await servers[1].comments.createThread({ videoId: uuid, text: '@user_1 hello' })
269 const threadId = resThread.body.comment.id
270 234
271 await waitJobs(servers) 235 await waitJobs(servers)
272 await checkCommentMention(baseParams, uuid, threadId, threadId, 'super root 2 name', 'absence') 236 await checkCommentMention(baseParams, uuid, threadId, threadId, 'super root 2 name', 'absence')
@@ -275,17 +239,14 @@ describe('Test comments notifications', function () {
275 it('Should send a new mention notification after local comments', async function () { 239 it('Should send a new mention notification after local comments', async function () {
276 this.timeout(10000) 240 this.timeout(10000)
277 241
278 const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'super video' }) 242 const { uuid } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
279 const uuid = resVideo.body.video.uuid
280 243
281 const resThread = await addVideoCommentThread(servers[0].url, servers[0].accessToken, uuid, '@user_1 hello 1') 244 const { id: threadId } = await servers[0].comments.createThread({ videoId: uuid, text: '@user_1 hellotext: 1' })
282 const threadId = resThread.body.comment.id
283 245
284 await waitJobs(servers) 246 await waitJobs(servers)
285 await checkCommentMention(baseParams, uuid, threadId, threadId, 'super root name', 'presence') 247 await checkCommentMention(baseParams, uuid, threadId, threadId, 'super root name', 'presence')
286 248
287 const resComment = await addVideoCommentReply(servers[0].url, servers[0].accessToken, uuid, threadId, 'hello 2 @user_1') 249 const { id: commentId } = await servers[0].comments.addReply({ videoId: uuid, toCommentId: threadId, text: 'hello 2 @user_1' })
288 const commentId = resComment.body.comment.id
289 250
290 await waitJobs(servers) 251 await waitJobs(servers)
291 await checkCommentMention(baseParams, uuid, commentId, threadId, 'super root name', 'presence') 252 await checkCommentMention(baseParams, uuid, commentId, threadId, 'super root name', 'presence')
@@ -294,29 +255,27 @@ describe('Test comments notifications', function () {
294 it('Should send a new mention notification after remote comments', async function () { 255 it('Should send a new mention notification after remote comments', async function () {
295 this.timeout(20000) 256 this.timeout(20000)
296 257
297 const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'super video' }) 258 const { uuid } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
298 const uuid = resVideo.body.video.uuid
299 259
300 await waitJobs(servers) 260 await waitJobs(servers)
301 261
302 const text1 = `hello @user_1@localhost:${servers[0].port} 1` 262 const text1 = `hello @user_1@localhost:${servers[0].port} 1`
303 const resThread = await addVideoCommentThread(servers[1].url, servers[1].accessToken, uuid, text1) 263 const { id: server2ThreadId } = await servers[1].comments.createThread({ videoId: uuid, text: text1 })
304 const server2ThreadId = resThread.body.comment.id
305 264
306 await waitJobs(servers) 265 await waitJobs(servers)
307 266
308 const resThread2 = await getVideoCommentThreads(servers[0].url, uuid, 0, 5) 267 const { data } = await servers[0].comments.listThreads({ videoId: uuid })
309 expect(resThread2.body.data).to.have.lengthOf(1) 268 expect(data).to.have.lengthOf(1)
310 const server1ThreadId = resThread2.body.data[0].id 269
270 const server1ThreadId = data[0].id
311 await checkCommentMention(baseParams, uuid, server1ThreadId, server1ThreadId, 'super root 2 name', 'presence') 271 await checkCommentMention(baseParams, uuid, server1ThreadId, server1ThreadId, 'super root 2 name', 'presence')
312 272
313 const text2 = `@user_1@localhost:${servers[0].port} hello 2 @root@localhost:${servers[0].port}` 273 const text2 = `@user_1@localhost:${servers[0].port} hello 2 @root@localhost:${servers[0].port}`
314 await addVideoCommentReply(servers[1].url, servers[1].accessToken, uuid, server2ThreadId, text2) 274 await servers[1].comments.addReply({ videoId: uuid, toCommentId: server2ThreadId, text: text2 })
315 275
316 await waitJobs(servers) 276 await waitJobs(servers)
317 277
318 const resComments = await getVideoThreadComments(servers[0].url, uuid, server1ThreadId) 278 const tree = await servers[0].comments.getThread({ videoId: uuid, threadId: server1ThreadId })
319 const tree = resComments.body as VideoCommentThreadTree
320 279
321 expect(tree.children).to.have.lengthOf(1) 280 expect(tree.children).to.have.lengthOf(1)
322 const commentId = tree.children[0].comment.id 281 const commentId = tree.children[0].comment.id
@@ -327,13 +286,11 @@ describe('Test comments notifications', function () {
327 it('Should convert markdown in comment to html', async function () { 286 it('Should convert markdown in comment to html', async function () {
328 this.timeout(10000) 287 this.timeout(10000)
329 288
330 const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'super video' }) 289 const { uuid } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
331 const uuid = resVideo.body.video.uuid
332 290
333 const resThread = await addVideoCommentThread(servers[0].url, servers[0].accessToken, uuid, '@user_1 hello 1') 291 const { id: threadId } = await servers[0].comments.createThread({ videoId: uuid, text: '@user_1 hello 1' })
334 const threadId = resThread.body.comment.id
335 292
336 await addVideoCommentReply(servers[0].url, servers[0].accessToken, uuid, threadId, '@user_1 ' + commentText) 293 await servers[0].comments.addReply({ videoId: uuid, toCommentId: threadId, text: '@user_1 ' + commentText })
337 294
338 await waitJobs(servers) 295 await waitJobs(servers)
339 296