search-index.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
  2. import { expect } from 'chai'
  3. import {
  4. BooleanBothQuery,
  5. VideoChannelsSearchQuery,
  6. VideoPlaylistPrivacy,
  7. VideoPlaylistsSearchQuery,
  8. VideoPlaylistType,
  9. VideosSearchQuery
  10. } from '@peertube/peertube-models'
  11. import {
  12. cleanupTests,
  13. createSingleServer,
  14. PeerTubeServer,
  15. SearchCommand,
  16. setAccessTokensToServers
  17. } from '@peertube/peertube-server-commands'
  18. describe('Test index search', function () {
  19. const localVideoName = 'local video' + new Date().toISOString()
  20. let server: PeerTubeServer = null
  21. let command: SearchCommand
  22. before(async function () {
  23. this.timeout(30000)
  24. server = await createSingleServer(1)
  25. await setAccessTokensToServers([ server ])
  26. await server.videos.upload({ attributes: { name: localVideoName } })
  27. command = server.search
  28. })
  29. describe('Default search', async function () {
  30. it('Should make a local videos search by default', async function () {
  31. await server.config.updateExistingConfig({
  32. newConfig: {
  33. search: {
  34. searchIndex: {
  35. enabled: true,
  36. isDefaultSearch: false,
  37. disableLocalSearch: false
  38. }
  39. }
  40. }
  41. })
  42. const body = await command.searchVideos({ search: 'local video' })
  43. expect(body.total).to.equal(1)
  44. expect(body.data[0].name).to.equal(localVideoName)
  45. })
  46. it('Should make a local channels search by default', async function () {
  47. const body = await command.searchChannels({ search: 'root' })
  48. expect(body.total).to.equal(1)
  49. expect(body.data[0].name).to.equal('root_channel')
  50. expect(body.data[0].host).to.equal(server.host)
  51. })
  52. it('Should make an index videos search by default', async function () {
  53. await server.config.updateExistingConfig({
  54. newConfig: {
  55. search: {
  56. searchIndex: {
  57. enabled: true,
  58. isDefaultSearch: true,
  59. disableLocalSearch: false
  60. }
  61. }
  62. }
  63. })
  64. const body = await command.searchVideos({ search: 'local video' })
  65. expect(body.total).to.be.greaterThan(2)
  66. })
  67. it('Should make an index channels search by default', async function () {
  68. const body = await command.searchChannels({ search: 'root' })
  69. expect(body.total).to.be.greaterThan(2)
  70. })
  71. })
  72. describe('Videos search', async function () {
  73. async function check (search: VideosSearchQuery, exists = true) {
  74. const body = await command.advancedVideoSearch({ search })
  75. if (exists === false) {
  76. expect(body.total).to.equal(0)
  77. expect(body.data).to.have.lengthOf(0)
  78. return
  79. }
  80. expect(body.total).to.equal(1)
  81. expect(body.data).to.have.lengthOf(1)
  82. const video = body.data[0]
  83. expect(video.name).to.equal('What is PeerTube?')
  84. expect(video.category.label).to.equal('Science & Technology')
  85. expect(video.licence.label).to.equal('Attribution - Share Alike')
  86. expect(video.privacy.label).to.equal('Public')
  87. expect(video.duration).to.equal(113)
  88. expect(video.thumbnailUrl.startsWith('https://framatube.org/lazy-static/thumbnails')).to.be.true
  89. expect(video.account.host).to.equal('framatube.org')
  90. expect(video.account.name).to.equal('framasoft')
  91. expect(video.account.url).to.equal('https://framatube.org/accounts/framasoft')
  92. expect(video.account.avatars.length).to.equal(2, 'Account should have one avatar image')
  93. expect(video.channel.host).to.equal('framatube.org')
  94. expect(video.channel.name).to.equal('joinpeertube')
  95. expect(video.channel.url).to.equal('https://framatube.org/video-channels/joinpeertube')
  96. expect(video.channel.avatars.length).to.equal(2, 'Channel should have one avatar image')
  97. }
  98. const baseSearch: VideosSearchQuery = {
  99. search: 'what is peertube',
  100. start: 0,
  101. count: 2,
  102. categoryOneOf: [ 15 ],
  103. licenceOneOf: [ 2 ],
  104. tagsAllOf: [ 'framasoft', 'peertube' ],
  105. startDate: '2018-10-01T10:50:46.396Z',
  106. endDate: '2018-10-01T10:55:46.396Z'
  107. }
  108. it('Should make a simple search and not have results', async function () {
  109. const body = await command.searchVideos({ search: 'djidane'.repeat(50) })
  110. expect(body.total).to.equal(0)
  111. expect(body.data).to.have.lengthOf(0)
  112. })
  113. it('Should make a simple search and have results', async function () {
  114. const body = await command.searchVideos({ search: 'What is PeerTube' })
  115. expect(body.total).to.be.greaterThan(1)
  116. })
  117. it('Should make a simple search', async function () {
  118. await check(baseSearch)
  119. })
  120. it('Should search by start date', async function () {
  121. const search = { ...baseSearch, startDate: '2018-10-01T10:54:46.396Z' }
  122. await check(search, false)
  123. })
  124. it('Should search by tags', async function () {
  125. const search = { ...baseSearch, tagsAllOf: [ 'toto', 'framasoft' ] }
  126. await check(search, false)
  127. })
  128. it('Should search by duration', async function () {
  129. const search = { ...baseSearch, durationMin: 2000 }
  130. await check(search, false)
  131. })
  132. it('Should search by nsfw attribute', async function () {
  133. {
  134. const search = { ...baseSearch, nsfw: 'true' as BooleanBothQuery }
  135. await check(search, false)
  136. }
  137. {
  138. const search = { ...baseSearch, nsfw: 'false' as BooleanBothQuery }
  139. await check(search, true)
  140. }
  141. {
  142. const search = { ...baseSearch, nsfw: 'both' as BooleanBothQuery }
  143. await check(search, true)
  144. }
  145. })
  146. it('Should search by host', async function () {
  147. {
  148. const search = { ...baseSearch, host: 'example.com' }
  149. await check(search, false)
  150. }
  151. {
  152. const search = { ...baseSearch, host: 'framatube.org' }
  153. await check(search, true)
  154. }
  155. })
  156. it('Should search by uuids', async function () {
  157. const goodUUID = '9c9de5e8-0a1e-484a-b099-e80766180a6d'
  158. const goodShortUUID = 'kkGMgK9ZtnKfYAgnEtQxbv'
  159. const badUUID = 'c29c5b77-4a04-493d-96a9-2e9267e308f0'
  160. const badShortUUID = 'rP5RgUeX9XwTSrspCdkDej'
  161. {
  162. const uuidsMatrix = [
  163. [ goodUUID ],
  164. [ goodUUID, badShortUUID ],
  165. [ badShortUUID, goodShortUUID ],
  166. [ goodUUID, goodShortUUID ]
  167. ]
  168. for (const uuids of uuidsMatrix) {
  169. const search = { ...baseSearch, uuids }
  170. await check(search, true)
  171. }
  172. }
  173. {
  174. const uuidsMatrix = [
  175. [ badUUID ],
  176. [ badShortUUID ]
  177. ]
  178. for (const uuids of uuidsMatrix) {
  179. const search = { ...baseSearch, uuids }
  180. await check(search, false)
  181. }
  182. }
  183. })
  184. it('Should have a correct pagination', async function () {
  185. const search = {
  186. search: 'video',
  187. start: 0,
  188. count: 5
  189. }
  190. const body = await command.advancedVideoSearch({ search })
  191. expect(body.total).to.be.greaterThan(5)
  192. expect(body.data).to.have.lengthOf(5)
  193. })
  194. it('Should use the nsfw instance policy as default', async function () {
  195. let nsfwUUID: string
  196. {
  197. await server.config.updateExistingConfig({
  198. newConfig: {
  199. instance: { defaultNSFWPolicy: 'display' }
  200. }
  201. })
  202. const body = await command.searchVideos({ search: 'NSFW search index', sort: '-match' })
  203. expect(body.data).to.have.length.greaterThan(0)
  204. const video = body.data[0]
  205. expect(video.nsfw).to.be.true
  206. nsfwUUID = video.uuid
  207. }
  208. {
  209. await server.config.updateExistingConfig({
  210. newConfig: {
  211. instance: { defaultNSFWPolicy: 'do_not_list' }
  212. }
  213. })
  214. const body = await command.searchVideos({ search: 'NSFW search index', sort: '-match' })
  215. try {
  216. expect(body.data).to.have.lengthOf(0)
  217. } catch {
  218. const video = body.data[0]
  219. expect(video.uuid).not.equal(nsfwUUID)
  220. }
  221. }
  222. })
  223. })
  224. describe('Channels search', async function () {
  225. async function check (search: VideoChannelsSearchQuery, exists = true) {
  226. const body = await command.advancedChannelSearch({ search })
  227. if (exists === false) {
  228. expect(body.total).to.equal(0)
  229. expect(body.data).to.have.lengthOf(0)
  230. return
  231. }
  232. expect(body.total).to.be.greaterThan(0)
  233. expect(body.data).to.have.length.greaterThan(0)
  234. const videoChannel = body.data[0]
  235. expect(videoChannel.url).to.equal('https://framatube.org/video-channels/bf54d359-cfad-4935-9d45-9d6be93f63e8')
  236. expect(videoChannel.host).to.equal('framatube.org')
  237. expect(videoChannel.avatars.length).to.equal(2, 'Channel should have two avatar images')
  238. expect(videoChannel.displayName).to.exist
  239. expect(videoChannel.ownerAccount.url).to.equal('https://framatube.org/accounts/framasoft')
  240. expect(videoChannel.ownerAccount.name).to.equal('framasoft')
  241. expect(videoChannel.ownerAccount.host).to.equal('framatube.org')
  242. expect(videoChannel.ownerAccount.avatars.length).to.equal(2, 'Account should have two avatar images')
  243. }
  244. it('Should make a simple search and not have results', async function () {
  245. const body = await command.searchChannels({ search: 'a'.repeat(500) })
  246. expect(body.total).to.equal(0)
  247. expect(body.data).to.have.lengthOf(0)
  248. })
  249. it('Should make a search and have results', async function () {
  250. await check({ search: 'Framasoft vidéos', sort: 'createdAt' }, true)
  251. })
  252. it('Should make host search and have appropriate results', async function () {
  253. await check({ search: 'Framasoft videos', host: 'example.com' }, false)
  254. await check({ search: 'Framasoft videos', host: 'framatube.org' }, true)
  255. })
  256. it('Should make handles search and have appropriate results', async function () {
  257. await check({ handles: [ 'bf54d359-cfad-4935-9d45-9d6be93f63e8@framatube.org' ] }, true)
  258. await check({ handles: [ 'jeanine', 'bf54d359-cfad-4935-9d45-9d6be93f63e8@framatube.org' ] }, true)
  259. await check({ handles: [ 'jeanine', 'chocobozzz_channel2@peertube2.cpy.re' ] }, false)
  260. })
  261. it('Should have a correct pagination', async function () {
  262. const body = await command.advancedChannelSearch({ search: { search: 'root', start: 0, count: 2 } })
  263. expect(body.total).to.be.greaterThan(2)
  264. expect(body.data).to.have.lengthOf(2)
  265. })
  266. })
  267. describe('Playlists search', async function () {
  268. async function check (search: VideoPlaylistsSearchQuery, exists = true) {
  269. const body = await command.advancedPlaylistSearch({ search })
  270. if (exists === false) {
  271. expect(body.total).to.equal(0)
  272. expect(body.data).to.have.lengthOf(0)
  273. return
  274. }
  275. expect(body.total).to.be.greaterThan(0)
  276. expect(body.data).to.have.length.greaterThan(0)
  277. const videoPlaylist = body.data[0]
  278. expect(videoPlaylist.url).to.equal('https://peertube2.cpy.re/videos/watch/playlist/73804a40-da9a-40c2-b1eb-2c6d9eec8f0a')
  279. expect(videoPlaylist.thumbnailUrl).to.exist
  280. expect(videoPlaylist.embedUrl).to.equal('https://peertube2.cpy.re/video-playlists/embed/73804a40-da9a-40c2-b1eb-2c6d9eec8f0a')
  281. expect(videoPlaylist.type.id).to.equal(VideoPlaylistType.REGULAR)
  282. expect(videoPlaylist.privacy.id).to.equal(VideoPlaylistPrivacy.PUBLIC)
  283. expect(videoPlaylist.videosLength).to.exist
  284. expect(videoPlaylist.createdAt).to.exist
  285. expect(videoPlaylist.updatedAt).to.exist
  286. expect(videoPlaylist.uuid).to.equal('73804a40-da9a-40c2-b1eb-2c6d9eec8f0a')
  287. expect(videoPlaylist.displayName).to.exist
  288. expect(videoPlaylist.ownerAccount.url).to.equal('https://peertube2.cpy.re/accounts/chocobozzz')
  289. expect(videoPlaylist.ownerAccount.name).to.equal('chocobozzz')
  290. expect(videoPlaylist.ownerAccount.host).to.equal('peertube2.cpy.re')
  291. expect(videoPlaylist.ownerAccount.avatars.length).to.equal(2, 'Account should have two avatar images')
  292. expect(videoPlaylist.videoChannel.url).to.equal('https://peertube2.cpy.re/video-channels/chocobozzz_channel')
  293. expect(videoPlaylist.videoChannel.name).to.equal('chocobozzz_channel')
  294. expect(videoPlaylist.videoChannel.host).to.equal('peertube2.cpy.re')
  295. expect(videoPlaylist.videoChannel.avatars.length).to.equal(2, 'Channel should have two avatar images')
  296. }
  297. it('Should make a simple search and not have results', async function () {
  298. const body = await command.searchPlaylists({ search: 'a'.repeat(500) })
  299. expect(body.total).to.equal(0)
  300. expect(body.data).to.have.lengthOf(0)
  301. })
  302. it('Should make a search and have results', async function () {
  303. await check({ search: 'E2E playlist', sort: '-match' }, true)
  304. })
  305. it('Should make host search and have appropriate results', async function () {
  306. await check({ search: 'E2E playlist', host: 'example.com' }, false)
  307. await check({ search: 'E2E playlist', host: 'peertube2.cpy.re', sort: '-match' }, true)
  308. })
  309. it('Should make a search by uuids and have appropriate results', async function () {
  310. const goodUUID = '73804a40-da9a-40c2-b1eb-2c6d9eec8f0a'
  311. const goodShortUUID = 'fgei1ws1oa6FCaJ2qZPG29'
  312. const badUUID = 'c29c5b77-4a04-493d-96a9-2e9267e308f0'
  313. const badShortUUID = 'rP5RgUeX9XwTSrspCdkDej'
  314. {
  315. const uuidsMatrix = [
  316. [ goodUUID ],
  317. [ goodUUID, badShortUUID ],
  318. [ badShortUUID, goodShortUUID ],
  319. [ goodUUID, goodShortUUID ]
  320. ]
  321. for (const uuids of uuidsMatrix) {
  322. const search = { search: 'E2E playlist', sort: '-match', uuids }
  323. await check(search, true)
  324. }
  325. }
  326. {
  327. const uuidsMatrix = [
  328. [ badUUID ],
  329. [ badShortUUID ]
  330. ]
  331. for (const uuids of uuidsMatrix) {
  332. const search = { search: 'E2E playlist', sort: '-match', uuids }
  333. await check(search, false)
  334. }
  335. }
  336. })
  337. it('Should have a correct pagination', async function () {
  338. const body = await command.advancedChannelSearch({ search: { search: 'root', start: 0, count: 2 } })
  339. expect(body.total).to.be.greaterThan(2)
  340. expect(body.data).to.have.lengthOf(2)
  341. })
  342. })
  343. after(async function () {
  344. await cleanupTests([ server ])
  345. })
  346. })