aboutsummaryrefslogtreecommitdiffhomepage
path: root/shared/extra-utils
diff options
context:
space:
mode:
Diffstat (limited to 'shared/extra-utils')
-rw-r--r--shared/extra-utils/miscs/sql.ts35
-rw-r--r--shared/extra-utils/search/videos.ts19
-rw-r--r--shared/extra-utils/server/config.ts1
-rw-r--r--shared/extra-utils/server/follows.ts26
-rw-r--r--shared/extra-utils/server/servers.ts4
-rw-r--r--shared/extra-utils/users/accounts.ts4
-rw-r--r--shared/extra-utils/users/user-notifications.ts2
-rw-r--r--shared/extra-utils/users/users.ts39
-rw-r--r--shared/extra-utils/videos/video-channels.ts37
-rw-r--r--shared/extra-utils/videos/video-playlists.ts4
-rw-r--r--shared/extra-utils/videos/videos.ts6
11 files changed, 118 insertions, 59 deletions
diff --git a/shared/extra-utils/miscs/sql.ts b/shared/extra-utils/miscs/sql.ts
index 3cfae5c23..34477cb78 100644
--- a/shared/extra-utils/miscs/sql.ts
+++ b/shared/extra-utils/miscs/sql.ts
@@ -1,11 +1,12 @@
1import { QueryTypes, Sequelize } from 'sequelize' 1import { QueryTypes, Sequelize } from 'sequelize'
2import { ServerInfo } from '../server/servers'
2 3
3let sequelizes: { [ id: number ]: Sequelize } = {} 4let sequelizes: { [ id: number ]: Sequelize } = {}
4 5
5function getSequelize (serverNumber: number) { 6function getSequelize (internalServerNumber: number) {
6 if (sequelizes[serverNumber]) return sequelizes[serverNumber] 7 if (sequelizes[internalServerNumber]) return sequelizes[internalServerNumber]
7 8
8 const dbname = 'peertube_test' + serverNumber 9 const dbname = 'peertube_test' + internalServerNumber
9 const username = 'peertube' 10 const username = 'peertube'
10 const password = 'peertube' 11 const password = 'peertube'
11 const host = 'localhost' 12 const host = 'localhost'
@@ -18,37 +19,37 @@ function getSequelize (serverNumber: number) {
18 logging: false 19 logging: false
19 }) 20 })
20 21
21 sequelizes[serverNumber] = seq 22 sequelizes[internalServerNumber] = seq
22 23
23 return seq 24 return seq
24} 25}
25 26
26function setActorField (serverNumber: number, to: string, field: string, value: string) { 27function setActorField (internalServerNumber: number, to: string, field: string, value: string) {
27 const seq = getSequelize(serverNumber) 28 const seq = getSequelize(internalServerNumber)
28 29
29 const options = { type: QueryTypes.UPDATE } 30 const options = { type: QueryTypes.UPDATE }
30 31
31 return seq.query(`UPDATE actor SET "${field}" = '${value}' WHERE url = '${to}'`, options) 32 return seq.query(`UPDATE actor SET "${field}" = '${value}' WHERE url = '${to}'`, options)
32} 33}
33 34
34function setVideoField (serverNumber: number, uuid: string, field: string, value: string) { 35function setVideoField (internalServerNumber: number, uuid: string, field: string, value: string) {
35 const seq = getSequelize(serverNumber) 36 const seq = getSequelize(internalServerNumber)
36 37
37 const options = { type: QueryTypes.UPDATE } 38 const options = { type: QueryTypes.UPDATE }
38 39
39 return seq.query(`UPDATE video SET "${field}" = '${value}' WHERE uuid = '${uuid}'`, options) 40 return seq.query(`UPDATE video SET "${field}" = '${value}' WHERE uuid = '${uuid}'`, options)
40} 41}
41 42
42function setPlaylistField (serverNumber: number, uuid: string, field: string, value: string) { 43function setPlaylistField (internalServerNumber: number, uuid: string, field: string, value: string) {
43 const seq = getSequelize(serverNumber) 44 const seq = getSequelize(internalServerNumber)
44 45
45 const options = { type: QueryTypes.UPDATE } 46 const options = { type: QueryTypes.UPDATE }
46 47
47 return seq.query(`UPDATE "videoPlaylist" SET "${field}" = '${value}' WHERE uuid = '${uuid}'`, options) 48 return seq.query(`UPDATE "videoPlaylist" SET "${field}" = '${value}' WHERE uuid = '${uuid}'`, options)
48} 49}
49 50
50async function countVideoViewsOf (serverNumber: number, uuid: string) { 51async function countVideoViewsOf (internalServerNumber: number, uuid: string) {
51 const seq = getSequelize(serverNumber) 52 const seq = getSequelize(internalServerNumber)
52 53
53 // tslint:disable 54 // tslint:disable
54 const query = `SELECT SUM("videoView"."views") AS "total" FROM "videoView" INNER JOIN "video" ON "video"."id" = "videoView"."videoId" WHERE "video"."uuid" = '${uuid}'` 55 const query = `SELECT SUM("videoView"."views") AS "total" FROM "videoView" INNER JOIN "video" ON "video"."id" = "videoView"."videoId" WHERE "video"."uuid" = '${uuid}'`
@@ -62,11 +63,11 @@ async function countVideoViewsOf (serverNumber: number, uuid: string) {
62 return parseInt(total + '', 10) 63 return parseInt(total + '', 10)
63} 64}
64 65
65async function closeAllSequelize (servers: any[]) { 66async function closeAllSequelize (servers: ServerInfo[]) {
66 for (let i = 1; i <= servers.length; i++) { 67 for (const server of servers) {
67 if (sequelizes[ i ]) { 68 if (sequelizes[ server.internalServerNumber ]) {
68 await sequelizes[ i ].close() 69 await sequelizes[ server.internalServerNumber ].close()
69 delete sequelizes[ i ] 70 delete sequelizes[ server.internalServerNumber ]
70 } 71 }
71 } 72 }
72} 73}
diff --git a/shared/extra-utils/search/videos.ts b/shared/extra-utils/search/videos.ts
index ba4627017..5cf8d8cf0 100644
--- a/shared/extra-utils/search/videos.ts
+++ b/shared/extra-utils/search/videos.ts
@@ -6,9 +6,11 @@ import { immutableAssign } from '../miscs/miscs'
6 6
7function searchVideo (url: string, search: string) { 7function searchVideo (url: string, search: string) {
8 const path = '/api/v1/search/videos' 8 const path = '/api/v1/search/videos'
9
10 const query = { sort: '-publishedAt', search: search }
9 const req = request(url) 11 const req = request(url)
10 .get(path) 12 .get(path)
11 .query({ sort: '-publishedAt', search }) 13 .query(query)
12 .set('Accept', 'application/json') 14 .set('Accept', 'application/json')
13 15
14 return req.expect(200) 16 return req.expect(200)
@@ -30,11 +32,15 @@ function searchVideoWithToken (url: string, search: string, token: string, query
30function searchVideoWithPagination (url: string, search: string, start: number, count: number, sort?: string) { 32function searchVideoWithPagination (url: string, search: string, start: number, count: number, sort?: string) {
31 const path = '/api/v1/search/videos' 33 const path = '/api/v1/search/videos'
32 34
35 const query = {
36 start,
37 search,
38 count
39 }
40
33 const req = request(url) 41 const req = request(url)
34 .get(path) 42 .get(path)
35 .query({ start }) 43 .query(query)
36 .query({ search })
37 .query({ count })
38 44
39 if (sort) req.query({ sort }) 45 if (sort) req.query({ sort })
40 46
@@ -46,10 +52,11 @@ function searchVideoWithPagination (url: string, search: string, start: number,
46function searchVideoWithSort (url: string, search: string, sort: string) { 52function searchVideoWithSort (url: string, search: string, sort: string) {
47 const path = '/api/v1/search/videos' 53 const path = '/api/v1/search/videos'
48 54
55 const query = { search, sort }
56
49 return request(url) 57 return request(url)
50 .get(path) 58 .get(path)
51 .query({ search }) 59 .query(query)
52 .query({ sort })
53 .set('Accept', 'application/json') 60 .set('Accept', 'application/json')
54 .expect(200) 61 .expect(200)
55 .expect('Content-Type', /json/) 62 .expect('Content-Type', /json/)
diff --git a/shared/extra-utils/server/config.ts b/shared/extra-utils/server/config.ts
index deb77e9c0..a5f5989e0 100644
--- a/shared/extra-utils/server/config.ts
+++ b/shared/extra-utils/server/config.ts
@@ -91,6 +91,7 @@ function updateCustomSubConfig (url: string, token: string, newConfig: any) {
91 transcoding: { 91 transcoding: {
92 enabled: true, 92 enabled: true,
93 allowAdditionalExtensions: true, 93 allowAdditionalExtensions: true,
94 allowAudioFiles: true,
94 threads: 1, 95 threads: 1,
95 resolutions: { 96 resolutions: {
96 '240p': false, 97 '240p': false,
diff --git a/shared/extra-utils/server/follows.ts b/shared/extra-utils/server/follows.ts
index 1505804de..0379bb109 100644
--- a/shared/extra-utils/server/follows.ts
+++ b/shared/extra-utils/server/follows.ts
@@ -1,17 +1,21 @@
1import * as request from 'supertest' 1import * as request from 'supertest'
2import { ServerInfo } from './servers' 2import { ServerInfo } from './servers'
3import { waitJobs } from './jobs' 3import { waitJobs } from './jobs'
4import { makeGetRequest, makePostBodyRequest } from '..' 4import { makePostBodyRequest } from '../requests/requests'
5 5
6function getFollowersListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string) { 6function getFollowersListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string) {
7 const path = '/api/v1/server/followers' 7 const path = '/api/v1/server/followers'
8 8
9 const query = {
10 start,
11 count,
12 sort,
13 search
14 }
15
9 return request(url) 16 return request(url)
10 .get(path) 17 .get(path)
11 .query({ start }) 18 .query(query)
12 .query({ count })
13 .query({ sort })
14 .query({ search })
15 .set('Accept', 'application/json') 19 .set('Accept', 'application/json')
16 .expect(200) 20 .expect(200)
17 .expect('Content-Type', /json/) 21 .expect('Content-Type', /json/)
@@ -42,12 +46,16 @@ function rejectFollower (url: string, token: string, follower: string, statusCod
42function getFollowingListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string) { 46function getFollowingListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string) {
43 const path = '/api/v1/server/following' 47 const path = '/api/v1/server/following'
44 48
49 const query = {
50 start,
51 count,
52 sort,
53 search
54 }
55
45 return request(url) 56 return request(url)
46 .get(path) 57 .get(path)
47 .query({ start }) 58 .query(query)
48 .query({ count })
49 .query({ sort })
50 .query({ search })
51 .set('Accept', 'application/json') 59 .set('Accept', 'application/json')
52 .expect(200) 60 .expect(200)
53 .expect('Content-Type', /json/) 61 .expect('Content-Type', /json/)
diff --git a/shared/extra-utils/server/servers.ts b/shared/extra-utils/server/servers.ts
index ed41bfa48..4c7d6862a 100644
--- a/shared/extra-utils/server/servers.ts
+++ b/shared/extra-utils/server/servers.ts
@@ -246,7 +246,7 @@ async function checkTmpIsEmpty (server: ServerInfo) {
246} 246}
247 247
248async function checkDirectoryIsEmpty (server: ServerInfo, directory: string) { 248async function checkDirectoryIsEmpty (server: ServerInfo, directory: string) {
249 const testDirectory = 'test' + server.serverNumber 249 const testDirectory = 'test' + server.internalServerNumber
250 250
251 const directoryPath = join(root(), testDirectory, directory) 251 const directoryPath = join(root(), testDirectory, directory)
252 252
@@ -284,7 +284,7 @@ function cleanupTests (servers: ServerInfo[]) {
284} 284}
285 285
286async function waitUntilLog (server: ServerInfo, str: string, count = 1) { 286async function waitUntilLog (server: ServerInfo, str: string, count = 1) {
287 const logfile = join(root(), 'test' + server.serverNumber, 'logs/peertube.log') 287 const logfile = join(root(), 'test' + server.internalServerNumber, 'logs/peertube.log')
288 288
289 while (true) { 289 while (true) {
290 const buf = await readFile(logfile) 290 const buf = await readFile(logfile)
diff --git a/shared/extra-utils/users/accounts.ts b/shared/extra-utils/users/accounts.ts
index f64a2dbad..627e17cc3 100644
--- a/shared/extra-utils/users/accounts.ts
+++ b/shared/extra-utils/users/accounts.ts
@@ -39,7 +39,7 @@ async function expectAccountFollows (url: string, nameWithDomain: string, follow
39 expect(account.followingCount).to.equal(followingCount, message) 39 expect(account.followingCount).to.equal(followingCount, message)
40} 40}
41 41
42async function checkActorFilesWereRemoved (actorUUID: string, serverNumber: number) { 42async function checkActorFilesWereRemoved (filename: string, serverNumber: number) {
43 const testDirectory = 'test' + serverNumber 43 const testDirectory = 'test' + serverNumber
44 44
45 for (const directory of [ 'avatars' ]) { 45 for (const directory of [ 'avatars' ]) {
@@ -50,7 +50,7 @@ async function checkActorFilesWereRemoved (actorUUID: string, serverNumber: numb
50 50
51 const files = await readdir(directoryPath) 51 const files = await readdir(directoryPath)
52 for (const file of files) { 52 for (const file of files) {
53 expect(file).to.not.contain(actorUUID) 53 expect(file).to.not.contain(filename)
54 } 54 }
55 } 55 }
56} 56}
diff --git a/shared/extra-utils/users/user-notifications.ts b/shared/extra-utils/users/user-notifications.ts
index 495ff80d9..f7de542bf 100644
--- a/shared/extra-utils/users/user-notifications.ts
+++ b/shared/extra-utils/users/user-notifications.ts
@@ -380,7 +380,7 @@ async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string,
380 } 380 }
381 } 381 }
382 382
383 const commentUrl = `http://localhost:9001/videos/watch/${uuid};threadId=${threadId}` 383 const commentUrl = `http://localhost:${base.server.port}/videos/watch/${uuid};threadId=${threadId}`
384 function emailFinder (email: object) { 384 function emailFinder (email: object) {
385 return email[ 'text' ].indexOf(commentUrl) !== -1 385 return email[ 'text' ].indexOf(commentUrl) !== -1
386 } 386 }
diff --git a/shared/extra-utils/users/users.ts b/shared/extra-utils/users/users.ts
index 2bd37b8be..0f2f0ae15 100644
--- a/shared/extra-utils/users/users.ts
+++ b/shared/extra-utils/users/users.ts
@@ -1,10 +1,11 @@
1import * as request from 'supertest' 1import * as request from 'supertest'
2import { makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '../requests/requests' 2import { makeGetRequest, makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '../requests/requests'
3 3
4import { UserRole } from '../../index' 4import { UserCreate, UserRole } from '../../index'
5import { NSFWPolicyType } from '../../models/videos/nsfw-policy.type' 5import { NSFWPolicyType } from '../../models/videos/nsfw-policy.type'
6import { ServerInfo, userLogin } from '..' 6import { ServerInfo, userLogin } from '..'
7import { UserAdminFlag } from '../../models/users/user-flag.model' 7import { UserAdminFlag } from '../../models/users/user-flag.model'
8import { UserRegister } from '../../models/users/user-register.model'
8 9
9type CreateUserArgs = { url: string, 10type CreateUserArgs = { url: string,
10 accessToken: string, 11 accessToken: string,
@@ -70,6 +71,27 @@ function registerUser (url: string, username: string, password: string, specialS
70 .expect(specialStatus) 71 .expect(specialStatus)
71} 72}
72 73
74function registerUserWithChannel (options: {
75 url: string,
76 user: { username: string, password: string },
77 channel: { name: string, displayName: string }
78}) {
79 const path = '/api/v1/users/register'
80 const body: UserRegister = {
81 username: options.user.username,
82 password: options.user.password,
83 email: options.user.username + '@example.com',
84 channel: options.channel
85 }
86
87 return makePostBodyRequest({
88 url: options.url,
89 path,
90 fields: body,
91 statusCodeExpected: 204
92 })
93}
94
73function getMyUserInformation (url: string, accessToken: string, specialStatus = 200) { 95function getMyUserInformation (url: string, accessToken: string, specialStatus = 200) {
74 const path = '/api/v1/users/me' 96 const path = '/api/v1/users/me'
75 97
@@ -138,12 +160,16 @@ function getUsersList (url: string, accessToken: string) {
138function getUsersListPaginationAndSort (url: string, accessToken: string, start: number, count: number, sort: string, search?: string) { 160function getUsersListPaginationAndSort (url: string, accessToken: string, start: number, count: number, sort: string, search?: string) {
139 const path = '/api/v1/users' 161 const path = '/api/v1/users'
140 162
163 const query = {
164 start,
165 count,
166 sort,
167 search
168 }
169
141 return request(url) 170 return request(url)
142 .get(path) 171 .get(path)
143 .query({ start }) 172 .query(query)
144 .query({ count })
145 .query({ sort })
146 .query({ search })
147 .set('Accept', 'application/json') 173 .set('Accept', 'application/json')
148 .set('Authorization', 'Bearer ' + accessToken) 174 .set('Authorization', 'Bearer ' + accessToken)
149 .expect(200) 175 .expect(200)
@@ -312,6 +338,7 @@ export {
312 getMyUserInformation, 338 getMyUserInformation,
313 getMyUserVideoRating, 339 getMyUserVideoRating,
314 deleteMe, 340 deleteMe,
341 registerUserWithChannel,
315 getMyUserVideoQuotaUsed, 342 getMyUserVideoQuotaUsed,
316 getUsersList, 343 getUsersList,
317 getUsersListPaginationAndSort, 344 getUsersListPaginationAndSort,
diff --git a/shared/extra-utils/videos/video-channels.ts b/shared/extra-utils/videos/video-channels.ts
index 93a257bf9..3e79cf15a 100644
--- a/shared/extra-utils/videos/video-channels.ts
+++ b/shared/extra-utils/videos/video-channels.ts
@@ -1,6 +1,6 @@
1import * as request from 'supertest' 1import * as request from 'supertest'
2import { VideoChannelCreate, VideoChannelUpdate } from '../../models/videos' 2import { VideoChannelCreate, VideoChannelUpdate } from '../../models/videos'
3import { updateAvatarRequest } from '../requests/requests' 3import { makeGetRequest, updateAvatarRequest } from '../requests/requests'
4import { getMyUserInformation, ServerInfo } from '..' 4import { getMyUserInformation, ServerInfo } from '..'
5import { User } from '../..' 5import { User } from '../..'
6 6
@@ -19,14 +19,28 @@ function getVideoChannelsList (url: string, start: number, count: number, sort?:
19 .expect('Content-Type', /json/) 19 .expect('Content-Type', /json/)
20} 20}
21 21
22function getAccountVideoChannelsList (url: string, accountName: string, specialStatus = 200) { 22function getAccountVideoChannelsList (parameters: {
23 url: string,
24 accountName: string,
25 start?: number,
26 count?: number,
27 sort?: string,
28 specialStatus?: number
29}) {
30 const { url, accountName, start, count, sort = 'createdAt', specialStatus = 200 } = parameters
31
23 const path = '/api/v1/accounts/' + accountName + '/video-channels' 32 const path = '/api/v1/accounts/' + accountName + '/video-channels'
24 33
25 return request(url) 34 return makeGetRequest({
26 .get(path) 35 url,
27 .set('Accept', 'application/json') 36 path,
28 .expect(specialStatus) 37 query: {
29 .expect('Content-Type', /json/) 38 start,
39 count,
40 sort
41 },
42 statusCodeExpected: specialStatus
43 })
30} 44}
31 45
32function addVideoChannel ( 46function addVideoChannel (
@@ -60,12 +74,13 @@ function updateVideoChannel (
60 attributes: VideoChannelUpdate, 74 attributes: VideoChannelUpdate,
61 expectedStatus = 204 75 expectedStatus = 204
62) { 76) {
63 const body = {} 77 const body: any = {}
64 const path = '/api/v1/video-channels/' + channelName 78 const path = '/api/v1/video-channels/' + channelName
65 79
66 if (attributes.displayName) body['displayName'] = attributes.displayName 80 if (attributes.displayName) body.displayName = attributes.displayName
67 if (attributes.description) body['description'] = attributes.description 81 if (attributes.description) body.description = attributes.description
68 if (attributes.support) body['support'] = attributes.support 82 if (attributes.support) body.support = attributes.support
83 if (attributes.bulkVideosSupportUpdate) body.bulkVideosSupportUpdate = attributes.bulkVideosSupportUpdate
69 84
70 return request(url) 85 return request(url)
71 .put(path) 86 .put(path)
diff --git a/shared/extra-utils/videos/video-playlists.ts b/shared/extra-utils/videos/video-playlists.ts
index 4d110a131..fd62bef19 100644
--- a/shared/extra-utils/videos/video-playlists.ts
+++ b/shared/extra-utils/videos/video-playlists.ts
@@ -252,10 +252,10 @@ function reorderVideosPlaylist (options: {
252 252
253async function checkPlaylistFilesWereRemoved ( 253async function checkPlaylistFilesWereRemoved (
254 playlistUUID: string, 254 playlistUUID: string,
255 serverNumber: number, 255 internalServerNumber: number,
256 directories = [ 'thumbnails' ] 256 directories = [ 'thumbnails' ]
257) { 257) {
258 const testDirectory = 'test' + serverNumber 258 const testDirectory = 'test' + internalServerNumber
259 259
260 for (const directory of directories) { 260 for (const directory of directories) {
261 const directoryPath = join(root(), testDirectory, directory) 261 const directoryPath = join(root(), testDirectory, directory)
diff --git a/shared/extra-utils/videos/videos.ts b/shared/extra-utils/videos/videos.ts
index b5a07b792..debaaf9a7 100644
--- a/shared/extra-utils/videos/videos.ts
+++ b/shared/extra-utils/videos/videos.ts
@@ -355,6 +355,7 @@ async function uploadVideo (url: string, accessToken: string, videoAttributesArg
355 .set('Accept', 'application/json') 355 .set('Accept', 'application/json')
356 .set('Authorization', 'Bearer ' + accessToken) 356 .set('Authorization', 'Bearer ' + accessToken)
357 .field('name', attributes.name) 357 .field('name', attributes.name)
358 .field('support', attributes.support)
358 .field('nsfw', JSON.stringify(attributes.nsfw)) 359 .field('nsfw', JSON.stringify(attributes.nsfw))
359 .field('commentsEnabled', JSON.stringify(attributes.commentsEnabled)) 360 .field('commentsEnabled', JSON.stringify(attributes.commentsEnabled))
360 .field('downloadEnabled', JSON.stringify(attributes.downloadEnabled)) 361 .field('downloadEnabled', JSON.stringify(attributes.downloadEnabled))
@@ -524,7 +525,6 @@ async function completeVideoCheck (
524 expect(video.nsfw).to.equal(attributes.nsfw) 525 expect(video.nsfw).to.equal(attributes.nsfw)
525 expect(video.description).to.equal(attributes.description) 526 expect(video.description).to.equal(attributes.description)
526 expect(video.account.id).to.be.a('number') 527 expect(video.account.id).to.be.a('number')
527 expect(video.account.uuid).to.be.a('string')
528 expect(video.account.host).to.equal(attributes.account.host) 528 expect(video.account.host).to.equal(attributes.account.host)
529 expect(video.account.name).to.equal(attributes.account.name) 529 expect(video.account.name).to.equal(attributes.account.name)
530 expect(video.channel.displayName).to.equal(attributes.channel.displayName) 530 expect(video.channel.displayName).to.equal(attributes.channel.displayName)
@@ -568,8 +568,8 @@ async function completeVideoCheck (
568 expect(file).not.to.be.undefined 568 expect(file).not.to.be.undefined
569 569
570 let extension = extname(attributes.fixture) 570 let extension = extname(attributes.fixture)
571 // Transcoding enabled on server 2, extension will always be .mp4 571 // Transcoding enabled: extension will always be .mp4
572 if (attributes.account.host === 'localhost:9002') extension = '.mp4' 572 if (attributes.files.length > 1) extension = '.mp4'
573 573
574 const magnetUri = file.magnetUri 574 const magnetUri = file.magnetUri
575 expect(file.magnetUri).to.have.lengthOf.above(2) 575 expect(file.magnetUri).to.have.lengthOf.above(2)