From: Chocobozzz Date: Fri, 30 Jul 2021 09:38:19 +0000 (+0200) Subject: Merge branch 'release/3.3.0' into develop X-Git-Tag: v3.4.0-rc.1~162 X-Git-Url: https://git.immae.eu/?a=commitdiff_plain;h=171efc48e67498406feb6d7873b3482b41505515;hp=3ce48a0cd062d9ff64d9411d702453503a49f3b1;p=github%2FChocobozzz%2FPeerTube.git Merge branch 'release/3.3.0' into develop --- diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 46b243244..c5bbd9e2c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,11 +2,6 @@ name: Test Suite on: push: - branches: - - develop - - master - - ci - - next pull_request: types: [synchronize, opened] schedule: diff --git a/client/src/app/+about/about-instance/contact-admin-modal.component.ts b/client/src/app/+about/about-instance/contact-admin-modal.component.ts index a528faa20..37e9feacb 100644 --- a/client/src/app/+about/about-instance/contact-admin-modal.component.ts +++ b/client/src/app/+about/about-instance/contact-admin-modal.component.ts @@ -11,8 +11,7 @@ import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' import { InstanceService } from '@app/shared/shared-instance' import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' -import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' -import { HTMLServerConfig } from '@shared/models' +import { HTMLServerConfig, HttpStatusCode } from '@shared/models' type Prefill = { subject?: string diff --git a/client/src/app/+accounts/accounts.component.ts b/client/src/app/+accounts/accounts.component.ts index c69b04a01..5b59f3cd0 100644 --- a/client/src/app/+accounts/accounts.component.ts +++ b/client/src/app/+accounts/accounts.component.ts @@ -13,8 +13,7 @@ import { VideoService } from '@app/shared/shared-main' import { AccountReportComponent } from '@app/shared/shared-moderation' -import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' -import { User, UserRight } from '@shared/models' +import { HttpStatusCode, User, UserRight } from '@shared/models' import { AccountSearchComponent } from './account-search/account-search.component' @Component({ diff --git a/client/src/app/+admin/admin.component.ts b/client/src/app/+admin/admin.component.ts index dd92ed2ca..4b6fab6ed 100644 --- a/client/src/app/+admin/admin.component.ts +++ b/client/src/app/+admin/admin.component.ts @@ -26,12 +26,12 @@ export class AdminComponent implements OnInit { label: $localize`Federation`, children: [ { - label: $localize`Instances you follow`, + label: $localize`Following`, routerLink: '/admin/follows/following-list', iconName: 'following' }, { - label: $localize`Instances following you`, + label: $localize`Followers`, routerLink: '/admin/follows/followers-list', iconName: 'follower' }, diff --git a/client/src/app/+admin/admin.module.ts b/client/src/app/+admin/admin.module.ts index a7fe20b07..1ea7b9784 100644 --- a/client/src/app/+admin/admin.module.ts +++ b/client/src/app/+admin/admin.module.ts @@ -25,7 +25,7 @@ import { EditVODTranscodingComponent } from './config' import { ConfigService } from './config/shared/config.service' -import { FollowersListComponent, FollowsComponent, VideoRedundanciesListComponent } from './follows' +import { FollowersListComponent, FollowModalComponent, FollowsComponent, VideoRedundanciesListComponent } from './follows' import { FollowingListComponent } from './follows/following-list/following-list.component' import { RedundancyCheckboxComponent } from './follows/shared/redundancy-checkbox.component' import { VideoRedundancyInformationComponent } from './follows/video-redundancies-list/video-redundancy-information.component' @@ -68,6 +68,7 @@ import { UserCreateComponent, UserListComponent, UserPasswordComponent, UsersCom FollowsComponent, FollowersListComponent, FollowingListComponent, + FollowModalComponent, RedundancyCheckboxComponent, VideoRedundanciesListComponent, VideoRedundancyInformationComponent, diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.html b/client/src/app/+admin/follows/followers-list/followers-list.component.html index c2e9a4df6..08459634d 100644 --- a/client/src/app/+admin/follows/followers-list/followers-list.component.html +++ b/client/src/app/+admin/follows/followers-list/followers-list.component.html @@ -1,6 +1,6 @@

- Instances following you + Followers of your instance

Actions - Follower handle + Follower State Score Created diff --git a/client/src/app/+admin/follows/following-list/follow-modal.component.html b/client/src/app/+admin/follows/following-list/follow-modal.component.html new file mode 100644 index 000000000..d0761b718 --- /dev/null +++ b/client/src/app/+admin/follows/following-list/follow-modal.component.html @@ -0,0 +1,42 @@ + + + + + + diff --git a/client/src/app/+admin/follows/following-list/follow-modal.component.scss b/client/src/app/+admin/follows/following-list/follow-modal.component.scss new file mode 100644 index 000000000..9621a566f --- /dev/null +++ b/client/src/app/+admin/follows/following-list/follow-modal.component.scss @@ -0,0 +1,3 @@ +textarea { + height: 200px; +} diff --git a/client/src/app/+admin/follows/following-list/follow-modal.component.ts b/client/src/app/+admin/follows/following-list/follow-modal.component.ts new file mode 100644 index 000000000..dc6909200 --- /dev/null +++ b/client/src/app/+admin/follows/following-list/follow-modal.component.ts @@ -0,0 +1,69 @@ +import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' +import { Notifier } from '@app/core' +import { splitAndGetNotEmpty, UNIQUE_HOSTS_OR_HANDLE_VALIDATOR } from '@app/shared/form-validators/host-validators' +import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' +import { InstanceFollowService } from '@app/shared/shared-instance' +import { NgbModal } from '@ng-bootstrap/ng-bootstrap' +import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' + +@Component({ + selector: 'my-follow-modal', + templateUrl: './follow-modal.component.html', + styleUrls: [ './follow-modal.component.scss' ] +}) +export class FollowModalComponent extends FormReactive implements OnInit { + @ViewChild('modal', { static: true }) modal: NgbModal + + @Output() newFollow = new EventEmitter() + + placeholder = 'example.com\nchocobozzz@example.com\nchocobozzz_channel@example.com' + + private openedModal: NgbModalRef + + constructor ( + protected formValidatorService: FormValidatorService, + private modalService: NgbModal, + private followService: InstanceFollowService, + private notifier: Notifier + ) { + super() + } + + ngOnInit () { + this.buildForm({ + hostsOrHandles: UNIQUE_HOSTS_OR_HANDLE_VALIDATOR + }) + } + + openModal () { + this.openedModal = this.modalService.open(this.modal, { centered: true }) + } + + hide () { + this.openedModal.close() + } + + submit () { + this.addFollowing() + + this.form.reset() + this.hide() + } + + httpEnabled () { + return window.location.protocol === 'https:' + } + + private async addFollowing () { + const hostsOrHandles = splitAndGetNotEmpty(this.form.value['hostsOrHandles']) + + this.followService.follow(hostsOrHandles).subscribe( + () => { + this.notifier.success($localize`Follow request(s) sent!`) + this.newFollow.emit() + }, + + err => this.notifier.error(err.message) + ) + } +} diff --git a/client/src/app/+admin/follows/following-list/following-list.component.html b/client/src/app/+admin/follows/following-list/following-list.component.html index e7c0c9088..75b0efca8 100644 --- a/client/src/app/+admin/follows/following-list/following-list.component.html +++ b/client/src/app/+admin/follows/following-list/following-list.component.html @@ -1,6 +1,6 @@

- Instances you follow + Your instance subscriptions

@@ -28,7 +28,7 @@ Action - Host + Following State Created Redundancy allowed @@ -41,8 +41,8 @@ - - {{ follow.following.host }} + + {{ follow.following.name + '@' + follow.following.host }} @@ -57,6 +57,7 @@ {{ follow.createdAt | date: 'short' }} @@ -75,10 +76,4 @@ - - -
- It seems that you are not on a HTTPS server. Your webserver needs to have TLS activated in order to follow servers. -
-
-
+ diff --git a/client/src/app/+admin/follows/following-list/following-list.component.ts b/client/src/app/+admin/follows/following-list/following-list.component.ts index b63fe08c0..ba62dfa23 100644 --- a/client/src/app/+admin/follows/following-list/following-list.component.ts +++ b/client/src/app/+admin/follows/following-list/following-list.component.ts @@ -4,13 +4,14 @@ import { ConfirmService, Notifier, RestPagination, RestTable } from '@app/core' import { InstanceFollowService } from '@app/shared/shared-instance' import { BatchDomainsModalComponent } from '@app/shared/shared-moderation' import { ActorFollow } from '@shared/models' +import { FollowModalComponent } from './follow-modal.component' @Component({ templateUrl: './following-list.component.html', styleUrls: [ '../follows.component.scss', './following-list.component.scss' ] }) export class FollowingListComponent extends RestTable implements OnInit { - @ViewChild('batchDomainsModal') batchDomainsModal: BatchDomainsModalComponent + @ViewChild('followModal') followModal: FollowModalComponent following: ActorFollow[] = [] totalRecords = 0 @@ -33,23 +34,12 @@ export class FollowingListComponent extends RestTable implements OnInit { return 'FollowingListComponent' } - addDomainsToFollow () { - this.batchDomainsModal.openModal() + openFollowModal () { + this.followModal.openModal() } - httpEnabled () { - return window.location.protocol === 'https:' - } - - async addFollowing (hosts: string[]) { - this.followService.follow(hosts).subscribe( - () => { - this.notifier.success($localize`Follow request(s) sent!`) - this.reloadData() - }, - - err => this.notifier.error(err.message) - ) + isInstanceFollowing (follow: ActorFollow) { + return follow.following.name === 'peertube' } async removeFollowing (follow: ActorFollow) { diff --git a/client/src/app/+admin/follows/following-list/index.ts b/client/src/app/+admin/follows/following-list/index.ts index a70d46a7e..88be0ed4c 100644 --- a/client/src/app/+admin/follows/following-list/index.ts +++ b/client/src/app/+admin/follows/following-list/index.ts @@ -1 +1,2 @@ +export * from './follow-modal.component' export * from './following-list.component' diff --git a/client/src/app/+admin/follows/follows.routes.ts b/client/src/app/+admin/follows/follows.routes.ts index cd70daf77..3843b42b5 100644 --- a/client/src/app/+admin/follows/follows.routes.ts +++ b/client/src/app/+admin/follows/follows.routes.ts @@ -25,7 +25,7 @@ export const FollowsRoutes: Routes = [ component: FollowingListComponent, data: { meta: { - title: $localize`Following list` + title: $localize`Following` } } }, @@ -34,7 +34,7 @@ export const FollowsRoutes: Routes = [ component: FollowersListComponent, data: { meta: { - title: $localize`Followers list` + title: $localize`Followers` } } }, diff --git a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts index 08500ef5c..4fe5ec441 100644 --- a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts +++ b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts @@ -1,6 +1,6 @@ import { SortMeta } from 'primeng/api' import { switchMap } from 'rxjs/operators' -import { buildVideoLink, buildVideoOrPlaylistEmbed } from 'src/assets/player/utils' +import { buildVideoOrPlaylistEmbed } from 'src/assets/player/utils' import { environment } from 'src/environments/environment' import { Component, OnInit } from '@angular/core' import { DomSanitizer } from '@angular/platform-browser' @@ -9,6 +9,7 @@ import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable, S import { AdvancedInputFilter } from '@app/shared/shared-forms' import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' import { VideoBlockService } from '@app/shared/shared-moderation' +import { buildVideoEmbedLink, decorateVideoLink } from '@shared/core-utils' import { VideoBlacklist, VideoBlacklistType } from '@shared/models' @Component({ @@ -147,8 +148,9 @@ export class VideoBlockListComponent extends RestTable implements OnInit { getVideoEmbed (entry: VideoBlacklist) { return buildVideoOrPlaylistEmbed( - buildVideoLink({ - baseUrl: `${environment.originServerUrl}/videos/embed/${entry.video.uuid}`, + decorateVideoLink({ + url: buildVideoEmbedLink(entry.video, environment.originServerUrl), + title: false, warningTitle: false }), diff --git a/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts b/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts index 6af224920..968abcbe5 100644 --- a/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts +++ b/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts @@ -4,7 +4,7 @@ import { ActivatedRoute, Router } from '@angular/router' import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service' import { ComponentPagination, ConfirmService, hasMoreItems, Notifier } from '@app/core' import { PluginService } from '@app/core/plugins/plugin.service' -import { compareSemVer } from '@shared/core-utils/miscs/miscs' +import { compareSemVer } from '@shared/core-utils' import { PeerTubePlugin, PluginType } from '@shared/models' @Component({ diff --git a/client/src/app/+admin/users/user-list/user-list.component.ts b/client/src/app/+admin/users/user-list/user-list.component.ts index e02d8e1ad..e3ae68a93 100644 --- a/client/src/app/+admin/users/user-list/user-list.component.ts +++ b/client/src/app/+admin/users/user-list/user-list.component.ts @@ -108,18 +108,18 @@ export class UserListComponent extends RestTable implements OnInit { ] this.columns = [ - { id: 'username', label: 'Username' }, - { id: 'email', label: 'Email' }, - { id: 'quota', label: 'Video quota' }, - { id: 'role', label: 'Role' }, - { id: 'createdAt', label: 'Created' } + { id: 'username', label: $localize`Username` }, + { id: 'email', label: $localize`Email` }, + { id: 'quota', label: $localize`Video quota` }, + { id: 'role', label: $localize`Role` }, + { id: 'createdAt', label: $localize`Created` } ] this.selectedColumns = this.columns.map(c => c.id) - this.columns.push({ id: 'quotaDaily', label: 'Daily quota' }) - this.columns.push({ id: 'pluginAuth', label: 'Auth plugin' }) - this.columns.push({ id: 'lastLoginDate', label: 'Last login' }) + this.columns.push({ id: 'quotaDaily', label: $localize`Daily quota` }) + this.columns.push({ id: 'pluginAuth', label: $localize`Auth plugin` }) + this.columns.push({ id: 'lastLoginDate', label: $localize`Last login` }) } getIdentifier () { diff --git a/client/src/app/+login/login.component.html b/client/src/app/+login/login.component.html index 5f5b0f565..27793ff0c 100644 --- a/client/src/app/+login/login.component.html +++ b/client/src/app/+login/login.component.html @@ -28,6 +28,10 @@
{{ formErrors.username }}
+ +
+ ⚠️ Most email addresses do not include capital letters. +
diff --git a/client/src/app/+login/login.component.ts b/client/src/app/+login/login.component.ts index d8ad49081..9731383af 100644 --- a/client/src/app/+login/login.component.ts +++ b/client/src/app/+login/login.component.ts @@ -141,6 +141,10 @@ The link will expire within 1 hour.` this.accordion = instanceAboutAccordion.accordion } + hasUsernameUppercase () { + return this.form.value['username'].match(/[A-Z]/) + } + private loadExternalAuthToken (username: string, token: string) { this.isAuthenticatedWithExternalAuth = true diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channel-create.component.ts b/client/src/app/+my-library/+my-video-channels/my-video-channel-create.component.ts index b3265210f..433475f66 100644 --- a/client/src/app/+my-library/+my-video-channels/my-video-channel-create.component.ts +++ b/client/src/app/+my-library/+my-video-channels/my-video-channel-create.component.ts @@ -1,3 +1,5 @@ +import { of } from 'rxjs' +import { switchMap } from 'rxjs/operators' import { Component, OnInit } from '@angular/core' import { Router } from '@angular/router' import { AuthService, Notifier } from '@app/core' @@ -9,11 +11,8 @@ import { } from '@app/shared/form-validators/video-channel-validators' import { FormValidatorService } from '@app/shared/shared-forms' import { VideoChannel, VideoChannelService } from '@app/shared/shared-main' -import { VideoChannelCreate } from '@shared/models' -import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' +import { HttpStatusCode, VideoChannelCreate } from '@shared/models' import { MyVideoChannelEdit } from './my-video-channel-edit' -import { switchMap } from 'rxjs/operators' -import { of } from 'rxjs' @Component({ templateUrl: './my-video-channel-edit.component.html', diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts index 67b3ee496..b6a2f592d 100644 --- a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts +++ b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts @@ -45,9 +45,9 @@ export class MyVideoChannelsComponent { It will delete ${videoChannel.videosCount} videos uploaded in this channel, and you will not be able to create another channel with the same name (${videoChannel.name})!`, - $localize`Please type the display name of the video channel (${videoChannel.displayName}) to confirm`, + $localize`Please type the name of the video channel (${videoChannel.name}) to confirm`, - videoChannel.displayName, + videoChannel.name, $localize`Delete` ) diff --git a/client/src/app/+my-library/my-videos/my-videos.component.html b/client/src/app/+my-library/my-videos/my-videos.component.html index 8d8b482ad..0552b8ce4 100644 --- a/client/src/app/+my-library/my-videos/my-videos.component.html +++ b/client/src/app/+my-library/my-videos/my-videos.component.html @@ -23,7 +23,7 @@
+
@@ -79,7 +79,7 @@
- +
@@ -174,6 +174,14 @@
+
+ + + +
+
diff --git a/client/src/app/+search/search-filters.component.ts b/client/src/app/+search/search-filters.component.ts index afa523b91..5972ba553 100644 --- a/client/src/app/+search/search-filters.component.ts +++ b/client/src/app/+search/search-filters.component.ts @@ -108,14 +108,14 @@ export class SearchFiltersComponent implements OnInit { this.loadOriginallyPublishedAtYears() } - onInputUpdated () { + onDurationOrPublishedUpdated () { this.updateModelFromDurationRange() this.updateModelFromPublishedRange() this.updateModelFromOriginallyPublishedAtYears() } formUpdated () { - this.onInputUpdated() + this.onDurationOrPublishedUpdated() this.filtered.emit(this.advancedSearch) } @@ -127,7 +127,7 @@ export class SearchFiltersComponent implements OnInit { this.durationRange = undefined this.publishedDateRange = undefined - this.onInputUpdated() + this.onDurationOrPublishedUpdated() } resetField (fieldName: string, value?: any) { @@ -136,7 +136,7 @@ export class SearchFiltersComponent implements OnInit { resetLocalField (fieldName: string, value?: any) { this[fieldName] = value - this.onInputUpdated() + this.onDurationOrPublishedUpdated() } resetOriginalPublicationYears () { diff --git a/client/src/app/+search/search.component.html b/client/src/app/+search/search.component.html index b28abca6a..dc8b4d595 100644 --- a/client/src/app/+search/search.component.html +++ b/client/src/app/+search/search.component.html @@ -24,6 +24,8 @@
+ +
{{ error }}
diff --git a/client/src/app/+search/search.component.scss b/client/src/app/+search/search.component.scss index fca704d27..b521825e5 100644 --- a/client/src/app/+search/search.component.scss +++ b/client/src/app/+search/search.component.scss @@ -15,6 +15,10 @@ padding: 40px; } +.alert-danger { + margin-top: 10px; +} + .results-header { font-size: 16px; padding-bottom: 20px; diff --git a/client/src/app/+search/search.component.ts b/client/src/app/+search/search.component.ts index 235bbfa4c..7425b7016 100644 --- a/client/src/app/+search/search.component.ts +++ b/client/src/app/+search/search.component.ts @@ -1,9 +1,10 @@ -import { forkJoin, of, Subscription } from 'rxjs' +import { forkJoin, Subscription } from 'rxjs' import { LinkType } from 'src/types/link.type' import { Component, OnDestroy, OnInit } from '@angular/core' import { ActivatedRoute, Router } from '@angular/router' import { AuthService, HooksService, MetaService, Notifier, ServerService, User, UserService } from '@app/core' import { immutableAssign } from '@app/helpers' +import { validateHost } from '@app/shared/form-validators/host-validators' import { Video, VideoChannel } from '@app/shared/shared-main' import { AdvancedSearch, SearchService } from '@app/shared/shared-search' import { MiniatureDisplayOptions } from '@app/shared/shared-video-miniature' @@ -16,7 +17,9 @@ import { HTMLServerConfig, SearchTargetType } from '@shared/models' templateUrl: './search.component.html' }) export class SearchComponent implements OnInit, OnDestroy { - results: (Video | VideoChannel)[] = [] + error: string + + results: (Video | VideoChannel | VideoPlaylist)[] = [] pagination = { currentPage: 1, @@ -89,8 +92,10 @@ export class SearchComponent implements OnInit, OnDestroy { this.advancedSearch.searchTarget = this.getDefaultSearchTarget() } - // Don't hide filters if we have some of them AND the user just came on the webpage - this.isSearchFilterCollapsed = this.isInitialLoad === false || !this.advancedSearch.containsValues() + this.error = this.checkFieldsAndGetError() + + // Don't hide filters if we have some of them AND the user just came on the webpage, or we have an error + this.isSearchFilterCollapsed = !this.error && (this.isInitialLoad === false || !this.advancedSearch.containsValues()) this.isInitialLoad = false this.search() @@ -126,6 +131,9 @@ export class SearchComponent implements OnInit, OnDestroy { } search () { + this.error = this.checkFieldsAndGetError() + if (this.error) return + this.isSearching = true forkJoin([ @@ -275,12 +283,10 @@ export class SearchComponent implements OnInit, OnDestroy { } private getVideoChannelObs () { - if (!this.currentSearch) return of({ data: [], total: 0 }) - const params = { search: this.currentSearch, componentPagination: immutableAssign(this.pagination, { itemsPerPage: this.channelsPerPage }), - searchTarget: this.advancedSearch.searchTarget + advancedSearch: this.advancedSearch } return this.hooks.wrapObsFun( @@ -293,12 +299,10 @@ export class SearchComponent implements OnInit, OnDestroy { } private getVideoPlaylistObs () { - if (!this.currentSearch) return of({ data: [], total: 0 }) - const params = { search: this.currentSearch, componentPagination: immutableAssign(this.pagination, { itemsPerPage: this.playlistsPerPage }), - searchTarget: this.advancedSearch.searchTarget + advancedSearch: this.advancedSearch } return this.hooks.wrapObsFun( @@ -319,4 +323,12 @@ export class SearchComponent implements OnInit, OnDestroy { return 'local' } + + private checkFieldsAndGetError () { + if (this.advancedSearch.host && !validateHost(this.advancedSearch.host)) { + return $localize`PeerTube instance host filter is invalid` + } + + return undefined + } } diff --git a/client/src/app/+video-channels/video-channels.component.ts b/client/src/app/+video-channels/video-channels.component.ts index 3833d9c54..6479644f1 100644 --- a/client/src/app/+video-channels/video-channels.component.ts +++ b/client/src/app/+video-channels/video-channels.component.ts @@ -7,7 +7,7 @@ import { AuthService, MarkdownService, Notifier, RestExtractor, ScreenService } import { ListOverflowItem, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main' import { SupportModalComponent } from '@app/shared/shared-support-modal' import { SubscribeButtonComponent } from '@app/shared/shared-user-subscription' -import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' +import { HttpStatusCode } from '@shared/models' @Component({ templateUrl: './video-channels.component.html', diff --git a/client/src/app/+videos/+video-edit/shared/video-edit.component.html b/client/src/app/+videos/+video-edit/shared/video-edit.component.html index 50d030ac9..ee5a50611 100644 --- a/client/src/app/+videos/+video-edit/shared/video-edit.component.html +++ b/client/src/app/+videos/+video-edit/shared/video-edit.component.html @@ -45,7 +45,7 @@ - +
{{ formErrors.description }} diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts index d8d20a249..189bc9669 100644 --- a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts +++ b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts @@ -7,8 +7,7 @@ import { genericUploadErrorHandler, scrollToTop } from '@app/helpers' import { FormValidatorService } from '@app/shared/shared-forms' import { BytesPipe, Video, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main' import { LoadingBarService } from '@ngx-loading-bar/core' -import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' -import { VideoPrivacy } from '@shared/models' +import { HttpStatusCode, VideoPrivacy } from '@shared/models' import { UploaderXFormData } from './uploaderx-form-data' import { VideoSend } from './video-send' diff --git a/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.ts b/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.ts index 04f8f0d58..0e1c4c207 100644 --- a/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.ts +++ b/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.ts @@ -161,7 +161,7 @@ export class VideoCommentComponent implements OnInit, OnChanges { // Before HTML rendering restore line feed for markdown list compatibility const commentText = this.comment.text.replace(//g, '\r\n') const html = await this.markdownService.textMarkdownToHTML(commentText, true, true) - this.sanitizedCommentHTML = this.markdownService.processVideoTimestamps(html) + this.sanitizedCommentHTML = this.markdownService.processVideoTimestamps(this.video.shortUUID, html) this.newParentComments = this.parentComments.concat([ this.comment ]) if (this.comment.account) { diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.html b/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.html index 598bc485d..362a21905 100644 --- a/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.html +++ b/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.html @@ -1,54 +1,62 @@ -
- Privacy - {{ video.privacy.label }} +
+ Privacy + {{ video.privacy.label }}
-
- Origin - {{ video.originInstanceHost }} + -
- Originally published - {{ video.originallyPublishedAt | date: 'dd MMMM yyyy' }} +
+ Originally published + {{ video.originallyPublishedAt | date: 'dd MMMM yyyy' }}
-
- Category - {{ video.category.label }} +
+ Category + {{ video.category.label }} {{ video.category.label }}
-
- Licence - {{ video.licence.label }} +
+ Licence + {{ video.licence.label }} {{ video.licence.label }}
-
- Language - {{ video.language.label }} +
+ Language + {{ video.language.label }} {{ video.language.label }}
-
- Tags +
+ Tags {{ tag }}
-
- Duration - {{ video.duration | myDurationFormatter }} +
+ Duration + {{ video.duration | myDurationFormatter }}
diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.scss b/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.scss index 45190a3e3..26bead124 100644 --- a/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.scss +++ b/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.scss @@ -1,13 +1,13 @@ @use '_variables' as *; @use '_mixins' as *; -.video-attribute { +.attribute { font-size: 13px; display: block; margin-bottom: 12px; } -.video-attribute-label { +.attribute-label { @include padding-right(5px); min-width: 142px; @@ -16,7 +16,7 @@ font-weight: $font-bold; } -a.video-attribute-value { +a.attribute-value { @include disable-default-a-behaviour; color: pvar(--mainForegroundColor); @@ -25,16 +25,22 @@ a.video-attribute-value { } } -.video-attribute-tags { - .video-attribute-value:not(:nth-child(2)) { +.attribute-tags { + .attribute-value:not(:nth-child(2)) { &::before { content: ', '; } } } +.glyphicon-new-window { + color: pvar(--inputPlaceholderColor); + margin-left: 5px; + font-size: 12px; +} + @media screen and (max-width: 1600px) { - .video-attributes .video-attribute { + .attributes .attribute { margin-bottom: 5px; } } diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.ts b/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.ts index 5cb77f0c8..9429581ac 100644 --- a/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.ts +++ b/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.ts @@ -17,6 +17,10 @@ export class VideoAttributesComponent { return this.video.url } + getVideoHost () { + return this.video.channel.host + } + getVideoTags () { if (!this.video || Array.isArray(this.video.tags) === false) return [] diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.ts b/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.ts index 23d00d31a..870c7ae3f 100644 --- a/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.ts +++ b/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.ts @@ -80,6 +80,7 @@ export class VideoDescriptionComponent implements OnChanges { private async setVideoDescriptionHTML () { const html = await this.markdownService.textMarkdownToHTML(this.video.description) - this.videoHTMLDescription = this.markdownService.processVideoTimestamps(html) + + this.videoHTMLDescription = this.markdownService.processVideoTimestamps(this.video.shortUUID, html) } } diff --git a/client/src/app/+videos/+video-watch/video-watch.component.ts b/client/src/app/+videos/+video-watch/video-watch.component.ts index d078844c3..ccb9c5e71 100644 --- a/client/src/app/+videos/+video-watch/video-watch.component.ts +++ b/client/src/app/+videos/+video-watch/video-watch.component.ts @@ -21,8 +21,16 @@ import { isXPercentInViewport, scrollToTop } from '@app/helpers' import { Video, VideoCaptionService, VideoDetails, VideoService } from '@app/shared/shared-main' import { SubscribeButtonComponent } from '@app/shared/shared-user-subscription' import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist' -import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' -import { HTMLServerConfig, PeerTubeProblemDocument, ServerErrorCode, VideoCaption, VideoPrivacy, VideoState } from '@shared/models' +import { timeToInt } from '@shared/core-utils' +import { + HTMLServerConfig, + HttpStatusCode, + PeerTubeProblemDocument, + ServerErrorCode, + VideoCaption, + VideoPrivacy, + VideoState +} from '@shared/models' import { cleanupVideoWatch, getStoredTheater, getStoredVideoWatchHistory } from '../../../assets/player/peertube-player-local-storage' import { CustomizationOptions, @@ -32,7 +40,6 @@ import { PlayerMode, videojs } from '../../../assets/player/peertube-player-manager' -import { timeToInt } from '../../../assets/player/utils' import { environment } from '../../../environments/environment' import { VideoWatchPlaylistComponent } from './shared' @@ -575,6 +582,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { videoCaptions: playerCaptions, + videoShortUUID: video.shortUUID, videoUUID: video.uuid }, diff --git a/client/src/app/core/auth/auth.service.ts b/client/src/app/core/auth/auth.service.ts index cdf13186b..60bd72c60 100644 --- a/client/src/app/core/auth/auth.service.ts +++ b/client/src/app/core/auth/auth.service.ts @@ -6,12 +6,11 @@ import { Injectable } from '@angular/core' import { Router } from '@angular/router' import { Notifier } from '@app/core/notification/notifier.service' import { objectToUrlEncoded, peertubeLocalStorage } from '@root-helpers/index' -import { MyUser as UserServerModel, OAuthClientLocal, User, UserLogin, UserRefreshToken } from '@shared/models' +import { HttpStatusCode, MyUser as UserServerModel, OAuthClientLocal, User, UserLogin, UserRefreshToken } from '@shared/models' import { environment } from '../../../environments/environment' import { RestExtractor } from '../rest/rest-extractor.service' import { AuthStatus } from './auth-status.model' import { AuthUser } from './auth-user.model' -import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' interface UserLoginWithUsername extends UserLogin { access_token: string diff --git a/client/src/app/core/menu/menu.service.ts b/client/src/app/core/menu/menu.service.ts index 60130382f..0b8d0191e 100644 --- a/client/src/app/core/menu/menu.service.ts +++ b/client/src/app/core/menu/menu.service.ts @@ -101,7 +101,7 @@ export class MenuService { return { key: 'in-my-library', - title: 'In my library', + title: $localize`In my library`, links } } diff --git a/client/src/app/core/renderer/markdown.service.ts b/client/src/app/core/renderer/markdown.service.ts index ca1bf4eb9..36258ca98 100644 --- a/client/src/app/core/renderer/markdown.service.ts +++ b/client/src/app/core/renderer/markdown.service.ts @@ -1,6 +1,6 @@ import * as MarkdownIt from 'markdown-it' -import { buildVideoLink } from 'src/assets/player/utils' import { Injectable } from '@angular/core' +import { buildVideoLink, decorateVideoLink } from '@shared/core-utils' import { COMPLETE_RULES, ENHANCED_RULES, @@ -82,10 +82,14 @@ export class MarkdownService { return this.render({ name: 'customPageMarkdownIt', markdown, withEmoji: true, additionalAllowedTags }) } - processVideoTimestamps (html: string) { + processVideoTimestamps (videoShortUUID: string, html: string) { return html.replace(/((\d{1,2}):)?(\d{1,2}):(\d{1,2})/g, function (str, _, h, m, s) { const t = (3600 * +(h || 0)) + (60 * +(m || 0)) + (+(s || 0)) - const url = buildVideoLink({ startTime: t }) + + const url = decorateVideoLink({ + url: buildVideoLink({ shortUUID: videoShortUUID }), + startTime: t + }) return `${str}` }) } diff --git a/client/src/app/core/rest/rest-extractor.service.ts b/client/src/app/core/rest/rest-extractor.service.ts index 08ab49512..2a926e68f 100644 --- a/client/src/app/core/rest/rest-extractor.service.ts +++ b/client/src/app/core/rest/rest-extractor.service.ts @@ -2,8 +2,7 @@ import { throwError as observableThrowError } from 'rxjs' import { Injectable } from '@angular/core' import { Router } from '@angular/router' import { dateToHuman } from '@app/helpers' -import { ResultList } from '@shared/models' -import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' +import { HttpStatusCode, ResultList } from '@shared/models' @Injectable() export class RestExtractor { diff --git a/client/src/app/helpers/utils.ts b/client/src/app/helpers/utils.ts index 94f6def26..edcaf50e0 100644 --- a/client/src/app/helpers/utils.ts +++ b/client/src/app/helpers/utils.ts @@ -3,7 +3,7 @@ import { SelectChannelItem } from 'src/types/select-options-item.model' import { DatePipe } from '@angular/common' import { HttpErrorResponse } from '@angular/common/http' import { Notifier } from '@app/core' -import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' +import { HttpStatusCode } from '@shared/models' import { environment } from '../../environments/environment' import { AuthService } from '../core/auth' diff --git a/client/src/app/shared/form-validators/batch-domains-validators.ts b/client/src/app/shared/form-validators/batch-domains-validators.ts deleted file mode 100644 index 423d1337f..000000000 --- a/client/src/app/shared/form-validators/batch-domains-validators.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { AbstractControl, FormControl, ValidatorFn, Validators } from '@angular/forms' -import { BuildFormValidator } from './form-validator.model' -import { validateHost } from './host' - -export function getNotEmptyHosts (hosts: string) { - return hosts - .split('\n') - .filter((host: string) => host && host.length !== 0) // Eject empty hosts -} - -const validDomains: ValidatorFn = (control: FormControl) => { - if (!control.value) return null - - const newHostsErrors = [] - const hosts = getNotEmptyHosts(control.value) - - for (const host of hosts) { - if (validateHost(host) === false) { - newHostsErrors.push($localize`${host} is not valid`) - } - } - - /* Is not valid. */ - if (newHostsErrors.length !== 0) { - return { - 'validDomains': { - reason: 'invalid', - value: newHostsErrors.join('. ') + '.' - } - } - } - - /* Is valid. */ - return null -} - -const isHostsUnique: ValidatorFn = (control: AbstractControl) => { - if (!control.value) return null - - const hosts = getNotEmptyHosts(control.value) - - if (hosts.every((host: string) => hosts.indexOf(host) === hosts.lastIndexOf(host))) { - return null - } else { - return { - 'uniqueDomains': { - reason: 'invalid' - } - } - } -} - -export const DOMAINS_VALIDATOR: BuildFormValidator = { - VALIDATORS: [Validators.required, validDomains, isHostsUnique], - MESSAGES: { - 'required': $localize`Domain is required.`, - 'validDomains': $localize`Domains entered are invalid.`, - 'uniqueDomains': $localize`Domains entered contain duplicates.` - } -} diff --git a/client/src/app/shared/form-validators/host-validators.ts b/client/src/app/shared/form-validators/host-validators.ts new file mode 100644 index 000000000..6f410a50a --- /dev/null +++ b/client/src/app/shared/form-validators/host-validators.ts @@ -0,0 +1,105 @@ +import { AbstractControl, ValidatorFn, Validators } from '@angular/forms' +import { BuildFormValidator } from './form-validator.model' + +export function validateHost (value: string) { + // Thanks to http://stackoverflow.com/a/106223 + const HOST_REGEXP = new RegExp( + '^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$' + ) + + return HOST_REGEXP.test(value) +} + +export function validateHandle (value: string) { + if (!value) return false + + return value.includes('@') +} + +const validHosts: ValidatorFn = (control: AbstractControl) => { + if (!control.value) return null + + const errors = [] + const hosts = splitAndGetNotEmpty(control.value) + + for (const host of hosts) { + if (validateHost(host) === false) { + errors.push($localize`${host} is not valid`) + } + } + + // valid + if (errors.length === 0) return null + + return { + 'validHosts': { + reason: 'invalid', + value: errors.join('. ') + '.' + } + } +} + +const validHostsOrHandles: ValidatorFn = (control: AbstractControl) => { + if (!control.value) return null + + const errors = [] + const lines = splitAndGetNotEmpty(control.value) + + for (const line of lines) { + if (validateHost(line) === false && validateHandle(line) === false) { + errors.push($localize`${line} is not valid`) + } + } + + // valid + if (errors.length === 0) return null + + return { + 'validHostsOrHandles': { + reason: 'invalid', + value: errors.join('. ') + '.' + } + } +} + +// --------------------------------------------------------------------------- + +export function splitAndGetNotEmpty (value: string) { + return value + .split('\n') + .filter(line => line && line.length !== 0) // Eject empty hosts +} + +export const unique: ValidatorFn = (control: AbstractControl) => { + if (!control.value) return null + + const hosts = splitAndGetNotEmpty(control.value) + + if (hosts.every((host: string) => hosts.indexOf(host) === hosts.lastIndexOf(host))) { + return null + } + + return { + 'unique': { + reason: 'invalid' + } + } +} + +export const UNIQUE_HOSTS_VALIDATOR: BuildFormValidator = { + VALIDATORS: [ Validators.required, validHosts, unique ], + MESSAGES: { + 'required': $localize`Domain is required.`, + 'validHosts': $localize`Hosts entered are invalid.`, + 'unique': $localize`Hosts entered contain duplicates.` + } +} + +export const UNIQUE_HOSTS_OR_HANDLE_VALIDATOR: BuildFormValidator = { + VALIDATORS: [ Validators.required, validHostsOrHandles, unique ], + MESSAGES: { + 'required': $localize`Domain is required.`, + 'validHostsOrHandles': $localize`Hosts or handles are invalid.`, + 'unique': $localize`Hosts or handles contain duplicates.` + } +} diff --git a/client/src/app/shared/form-validators/host.ts b/client/src/app/shared/form-validators/host.ts deleted file mode 100644 index c18a35f9b..000000000 --- a/client/src/app/shared/form-validators/host.ts +++ /dev/null @@ -1,8 +0,0 @@ -export function validateHost (value: string) { - // Thanks to http://stackoverflow.com/a/106223 - const HOST_REGEXP = new RegExp( - '^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$' - ) - - return HOST_REGEXP.test(value) -} diff --git a/client/src/app/shared/form-validators/index.ts b/client/src/app/shared/form-validators/index.ts index f621f03a4..0b605719c 100644 --- a/client/src/app/shared/form-validators/index.ts +++ b/client/src/app/shared/form-validators/index.ts @@ -1,7 +1,6 @@ export * from './form-validator.model' -export * from './host' -// Don't re export const variables because webpack 4 cannot do tree shaking with them +// Don't re export const variables because webpack cannot do tree shaking with them // export * from './abuse-validators' // export * from './batch-domains-validators' // export * from './custom-config-validators' diff --git a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts index 67aa0e399..a7932ebab 100644 --- a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts +++ b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts @@ -1,7 +1,7 @@ import * as debug from 'debug' import truncate from 'lodash-es/truncate' import { SortMeta } from 'primeng/api' -import { buildVideoLink, buildVideoOrPlaylistEmbed } from 'src/assets/player/utils' +import { buildVideoOrPlaylistEmbed } from 'src/assets/player/utils' import { environment } from 'src/environments/environment' import { Component, Input, OnInit, ViewChild } from '@angular/core' import { DomSanitizer } from '@angular/platform-browser' @@ -10,6 +10,7 @@ import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable } import { Account, Actor, DropdownAction, Video, VideoService } from '@app/shared/shared-main' import { AbuseService, BlocklistService, VideoBlockService } from '@app/shared/shared-moderation' import { VideoCommentService } from '@app/shared/shared-video-comment' +import { buildVideoEmbedLink, decorateVideoLink } from '@shared/core-utils' import { AbuseState, AdminAbuse } from '@shared/models' import { AdvancedInputFilter } from '../shared-forms' import { AbuseMessageModalComponent } from './abuse-message-modal.component' @@ -129,8 +130,8 @@ export class AbuseListTableComponent extends RestTable implements OnInit { getVideoEmbed (abuse: AdminAbuse) { return buildVideoOrPlaylistEmbed( - buildVideoLink({ - baseUrl: `${environment.originServerUrl}/videos/embed/${abuse.video.uuid}`, + decorateVideoLink({ + url: buildVideoEmbedLink(abuse.video, environment.originServerUrl), title: false, warningTitle: false, startTime: abuse.video.startAt, diff --git a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/embed-markup.component.ts b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/embed-markup.component.ts index 4462903db..53b70cc47 100644 --- a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/embed-markup.component.ts +++ b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/embed-markup.component.ts @@ -1,6 +1,7 @@ -import { buildPlaylistLink, buildVideoLink, buildVideoOrPlaylistEmbed } from 'src/assets/player/utils' +import { buildVideoOrPlaylistEmbed } from 'src/assets/player/utils' import { environment } from 'src/environments/environment' import { Component, ElementRef, Input, OnInit } from '@angular/core' +import { buildPlaylistEmbedLink, buildVideoEmbedLink } from '@shared/core-utils' import { CustomMarkupComponent } from './shared' @Component({ @@ -17,8 +18,8 @@ export class EmbedMarkupComponent implements CustomMarkupComponent, OnInit { ngOnInit () { const link = this.type === 'video' - ? buildVideoLink({ baseUrl: `${environment.originServerUrl}/videos/embed/${this.uuid}` }) - : buildPlaylistLink({ baseUrl: `${environment.originServerUrl}/video-playlists/embed/${this.uuid}` }) + ? buildVideoEmbedLink({ uuid: this.uuid }, environment.originServerUrl) + : buildPlaylistEmbedLink({ uuid: this.uuid }, environment.originServerUrl) this.el.nativeElement.innerHTML = buildVideoOrPlaylistEmbed(link, this.uuid) } diff --git a/client/src/app/shared/shared-forms/markdown-textarea.component.ts b/client/src/app/shared/shared-forms/markdown-textarea.component.ts index a233a4205..8f51d47df 100644 --- a/client/src/app/shared/shared-forms/markdown-textarea.component.ts +++ b/client/src/app/shared/shared-forms/markdown-textarea.component.ts @@ -6,6 +6,7 @@ import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@an import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' import { SafeHtml } from '@angular/platform-browser' import { MarkdownService, ScreenService } from '@app/core' +import { Video } from '@shared/models' @Component({ selector: 'my-markdown-textarea', @@ -33,7 +34,7 @@ export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit { @Input() markdownType: 'text' | 'enhanced' = 'text' @Input() customMarkdownRenderer?: (text: string) => Promise - @Input() markdownVideo = false + @Input() markdownVideo: Video @Input() name = 'description' @@ -147,7 +148,7 @@ export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit { } if (this.markdownVideo) { - html = this.markdownService.processVideoTimestamps(html) + html = this.markdownService.processVideoTimestamps(this.markdownVideo.shortUUID, html) } return html diff --git a/client/src/app/shared/shared-forms/timestamp-input.component.ts b/client/src/app/shared/shared-forms/timestamp-input.component.ts index 0ffd03d02..3fc705905 100644 --- a/client/src/app/shared/shared-forms/timestamp-input.component.ts +++ b/client/src/app/shared/shared-forms/timestamp-input.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectorRef, Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core' import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' -import { secondsToTime, timeToInt } from '../../../assets/player/utils' +import { secondsToTime, timeToInt } from '@shared/core-utils' @Component({ selector: 'my-timestamp-input', diff --git a/client/src/app/shared/shared-instance/instance-follow.service.ts b/client/src/app/shared/shared-instance/instance-follow.service.ts index e52660140..af44020cf 100644 --- a/client/src/app/shared/shared-instance/instance-follow.service.ts +++ b/client/src/app/shared/shared-instance/instance-follow.service.ts @@ -4,7 +4,7 @@ import { catchError, map } from 'rxjs/operators' import { HttpClient, HttpParams } from '@angular/common/http' import { Injectable } from '@angular/core' import { RestExtractor, RestPagination, RestService } from '@app/core' -import { ActivityPubActorType, ActorFollow, FollowState, ResultList } from '@shared/models' +import { ActivityPubActorType, ActorFollow, FollowState, ResultList, ServerFollowCreate } from '@shared/models' import { environment } from '../../../environments/environment' @Injectable() @@ -64,9 +64,10 @@ export class InstanceFollowService { ) } - follow (notEmptyHosts: string[]) { - const body = { - hosts: notEmptyHosts + follow (hostsOrHandles: string[]) { + const body: ServerFollowCreate = { + handles: hostsOrHandles.filter(v => v.includes('@')), + hosts: hostsOrHandles.filter(v => !v.includes('@')) } return this.authHttp.post(InstanceFollowService.BASE_APPLICATION_URL + '/following', body) @@ -77,7 +78,9 @@ export class InstanceFollowService { } unfollow (follow: ActorFollow) { - return this.authHttp.delete(InstanceFollowService.BASE_APPLICATION_URL + '/following/' + follow.following.host) + const handle = follow.following.name + '@' + follow.following.host + + return this.authHttp.delete(InstanceFollowService.BASE_APPLICATION_URL + '/following/' + handle) .pipe( map(this.restExtractor.extractDataBool), catchError(res => this.restExtractor.handleError(res)) diff --git a/client/src/app/shared/shared-main/auth/auth-interceptor.service.ts b/client/src/app/shared/shared-main/auth/auth-interceptor.service.ts index 5bcad36d0..a75c8a25c 100644 --- a/client/src/app/shared/shared-main/auth/auth-interceptor.service.ts +++ b/client/src/app/shared/shared-main/auth/auth-interceptor.service.ts @@ -1,11 +1,11 @@ import { Observable, of, throwError as observableThrowError } from 'rxjs' import { catchError, switchMap } from 'rxjs/operators' -import { HTTP_INTERCEPTORS, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpErrorResponse } from '@angular/common/http' +import { HTTP_INTERCEPTORS, HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http' import { Injectable, Injector } from '@angular/core' -import { AuthService } from '@app/core/auth/auth.service' import { Router } from '@angular/router' -import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' -import { OAuth2ErrorCode, PeerTubeProblemDocument, ServerErrorCode } from '@shared/models/server' +import { AuthService } from '@app/core/auth/auth.service' +import { HttpStatusCode } from '@shared/models' +import { OAuth2ErrorCode, PeerTubeProblemDocument } from '@shared/models/server' @Injectable() export class AuthInterceptor implements HttpInterceptor { diff --git a/client/src/app/shared/shared-main/users/user-notification.model.ts b/client/src/app/shared/shared-main/users/user-notification.model.ts index 4c15eb981..439547102 100644 --- a/client/src/app/shared/shared-main/users/user-notification.model.ts +++ b/client/src/app/shared/shared-main/users/user-notification.model.ts @@ -47,11 +47,7 @@ export class UserNotification implements UserNotificationServer { comment?: { threadId: number - video: { - id: number - uuid: string - name: string - } + video: VideoInfo } account?: ActorInfo diff --git a/client/src/app/shared/shared-main/users/user-notifications.component.ts b/client/src/app/shared/shared-main/users/user-notifications.component.ts index d7c722355..96b141543 100644 --- a/client/src/app/shared/shared-main/users/user-notifications.component.ts +++ b/client/src/app/shared/shared-main/users/user-notifications.component.ts @@ -1,7 +1,7 @@ import { Subject } from 'rxjs' import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' import { ComponentPagination, hasMoreItems, Notifier } from '@app/core' -import { UserNotificationType, AbuseState } from '@shared/models' +import { AbuseState } from '@shared/models' import { UserNotification } from './user-notification.model' import { UserNotificationService } from './user-notification.service' diff --git a/client/src/app/shared/shared-main/video/video.model.ts b/client/src/app/shared/shared-main/video/video.model.ts index f0a4a3f37..b7720c8d2 100644 --- a/client/src/app/shared/shared-main/video/video.model.ts +++ b/client/src/app/shared/shared-main/video/video.model.ts @@ -2,6 +2,7 @@ import { AuthUser } from '@app/core' import { User } from '@app/core/users/user.model' import { durationToString, getAbsoluteAPIUrl, getAbsoluteEmbedUrl } from '@app/helpers' import { Actor } from '@app/shared/shared-main/account/actor.model' +import { buildVideoWatchPath } from '@shared/core-utils' import { peertubeTranslate } from '@shared/core-utils/i18n' import { ActorImage, @@ -92,7 +93,7 @@ export class Video implements VideoServerModel { pluginData?: any static buildWatchUrl (video: Partial>) { - return '/w/' + (video.shortUUID || video.uuid) + return buildVideoWatchPath({ shortUUID: video.shortUUID || video.uuid }) } static buildUpdateUrl (video: Pick) { diff --git a/client/src/app/shared/shared-moderation/batch-domains-modal.component.html b/client/src/app/shared/shared-moderation/batch-domains-modal.component.html index 6a3c65721..8306a96bc 100644 --- a/client/src/app/shared/shared-moderation/batch-domains-modal.component.html +++ b/client/src/app/shared/shared-moderation/batch-domains-modal.component.html @@ -1,6 +1,6 @@ @@ -11,15 +11,15 @@ -
- {{ formErrors.domains }} +
+ {{ formErrors.hosts }} -
- {{ form.controls['domains'].errors.validDomains.value }} +
+ {{ form.controls['hosts'].errors.validHosts.value }}
diff --git a/client/src/app/shared/shared-moderation/batch-domains-modal.component.ts b/client/src/app/shared/shared-moderation/batch-domains-modal.component.ts index 6edbb6023..20be728f6 100644 --- a/client/src/app/shared/shared-moderation/batch-domains-modal.component.ts +++ b/client/src/app/shared/shared-moderation/batch-domains-modal.component.ts @@ -2,7 +2,7 @@ import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angu import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' -import { DOMAINS_VALIDATOR, getNotEmptyHosts } from '../form-validators/batch-domains-validators' +import { splitAndGetNotEmpty, UNIQUE_HOSTS_VALIDATOR } from '../form-validators/host-validators' @Component({ selector: 'my-batch-domains-modal', @@ -28,7 +28,7 @@ export class BatchDomainsModalComponent extends FormReactive implements OnInit { if (!this.action) this.action = $localize`Process domains` this.buildForm({ - domains: DOMAINS_VALIDATOR + hosts: UNIQUE_HOSTS_VALIDATOR }) } @@ -41,9 +41,7 @@ export class BatchDomainsModalComponent extends FormReactive implements OnInit { } submit () { - this.domains.emit( - getNotEmptyHosts(this.form.controls['domains'].value) - ) + this.domains.emit(splitAndGetNotEmpty(this.form.controls['hosts'].value)) this.form.reset() this.hide() } diff --git a/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts b/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts index 4ca6f52ad..e509ac88f 100644 --- a/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts +++ b/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts @@ -1,5 +1,5 @@ import { mapValues, pickBy } from 'lodash-es' -import { buildVideoLink, buildVideoOrPlaylistEmbed } from 'src/assets/player/utils' +import { buildVideoOrPlaylistEmbed } from 'src/assets/player/utils' import { Component, Input, OnInit, ViewChild } from '@angular/core' import { DomSanitizer, SafeHtml } from '@angular/platform-browser' import { Notifier } from '@app/core' @@ -7,6 +7,7 @@ import { ABUSE_REASON_VALIDATOR } from '@app/shared/form-validators/abuse-valida import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' +import { decorateVideoLink } from '@shared/core-utils' import { abusePredefinedReasonsMap } from '@shared/core-utils/abuse' import { AbusePredefinedReasonsString } from '@shared/models' import { Video } from '../../shared-main' @@ -57,11 +58,12 @@ export class VideoReportComponent extends FormReactive implements OnInit { getVideoEmbed () { return this.sanitizer.bypassSecurityTrustHtml( buildVideoOrPlaylistEmbed( - buildVideoLink({ - baseUrl: this.video.embedUrl, + decorateVideoLink({ + url: this.video.embedUrl, title: false, warningTitle: false }), + this.video.name ) ) diff --git a/client/src/app/shared/shared-search/advanced-search.model.ts b/client/src/app/shared/shared-search/advanced-search.model.ts index 2c83f53b6..9c55f6cd8 100644 --- a/client/src/app/shared/shared-search/advanced-search.model.ts +++ b/client/src/app/shared/shared-search/advanced-search.model.ts @@ -1,4 +1,11 @@ -import { BooleanBothQuery, BooleanQuery, SearchTargetType, VideosSearchQuery } from '@shared/models' +import { + BooleanBothQuery, + BooleanQuery, + SearchTargetType, + VideoChannelsSearchQuery, + VideoPlaylistsSearchQuery, + VideosSearchQuery +} from '@shared/models' export class AdvancedSearch { startDate: string // ISO 8601 @@ -23,6 +30,8 @@ export class AdvancedSearch { isLive: BooleanQuery + host: string + sort: string searchTarget: SearchTargetType @@ -45,6 +54,8 @@ export class AdvancedSearch { isLive?: BooleanQuery + host?: string + durationMin?: string durationMax?: string sort?: string @@ -68,6 +79,8 @@ export class AdvancedSearch { this.durationMin = parseInt(options.durationMin, 10) this.durationMax = parseInt(options.durationMax, 10) + this.host = options.host || undefined + this.searchTarget = options.searchTarget || undefined if (isNaN(this.durationMin)) this.durationMin = undefined @@ -101,6 +114,7 @@ export class AdvancedSearch { this.durationMin = undefined this.durationMax = undefined this.isLive = undefined + this.host = undefined this.sort = '-match' } @@ -120,12 +134,13 @@ export class AdvancedSearch { durationMin: this.durationMin, durationMax: this.durationMax, isLive: this.isLive, + host: this.host, sort: this.sort, searchTarget: this.searchTarget } } - toAPIObject (): VideosSearchQuery { + toVideosAPIObject (): VideosSearchQuery { let isLive: boolean if (this.isLive) isLive = this.isLive === 'true' @@ -142,12 +157,27 @@ export class AdvancedSearch { tagsAllOf: this.tagsAllOf, durationMin: this.durationMin, durationMax: this.durationMax, + host: this.host, isLive, sort: this.sort, searchTarget: this.searchTarget } } + toPlaylistAPIObject (): VideoPlaylistsSearchQuery { + return { + host: this.host, + searchTarget: this.searchTarget + } + } + + toChannelAPIObject (): VideoChannelsSearchQuery { + return { + host: this.host, + searchTarget: this.searchTarget + } + } + size () { let acc = 0 diff --git a/client/src/app/shared/shared-search/search.service.ts b/client/src/app/shared/shared-search/search.service.ts index ad258f5e5..a1603da98 100644 --- a/client/src/app/shared/shared-search/search.service.ts +++ b/client/src/app/shared/shared-search/search.service.ts @@ -7,7 +7,6 @@ import { Video, VideoChannel, VideoChannelService, VideoService } from '@app/sha import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage' import { ResultList, - SearchTargetType, Video as VideoServerModel, VideoChannel as VideoChannelServerModel, VideoPlaylist as VideoPlaylistServerModel @@ -33,8 +32,8 @@ export class SearchService { } searchVideos (parameters: { - search: string, - componentPagination?: ComponentPaginationLight, + search: string + componentPagination?: ComponentPaginationLight advancedSearch?: AdvancedSearch }): Observable> { const { search, componentPagination, advancedSearch } = parameters @@ -52,7 +51,7 @@ export class SearchService { if (search) params = params.append('search', search) if (advancedSearch) { - const advancedSearchObject = advancedSearch.toAPIObject() + const advancedSearchObject = advancedSearch.toVideosAPIObject() params = this.restService.addObjectParams(params, advancedSearchObject) } @@ -65,11 +64,11 @@ export class SearchService { } searchVideoChannels (parameters: { - search: string, - searchTarget?: SearchTargetType, + search: string + advancedSearch?: AdvancedSearch componentPagination?: ComponentPaginationLight }): Observable> { - const { search, componentPagination, searchTarget } = parameters + const { search, advancedSearch, componentPagination } = parameters const url = SearchService.BASE_SEARCH_URL + 'video-channels' @@ -80,10 +79,12 @@ export class SearchService { let params = new HttpParams() params = this.restService.addRestGetParams(params, pagination) - params = params.append('search', search) - if (searchTarget) { - params = params.append('searchTarget', searchTarget as string) + if (search) params = params.append('search', search) + + if (advancedSearch) { + const advancedSearchObject = advancedSearch.toChannelAPIObject() + params = this.restService.addObjectParams(params, advancedSearchObject) } return this.authHttp @@ -95,11 +96,11 @@ export class SearchService { } searchVideoPlaylists (parameters: { - search: string, - searchTarget?: SearchTargetType, + search: string + advancedSearch?: AdvancedSearch componentPagination?: ComponentPaginationLight }): Observable> { - const { search, componentPagination, searchTarget } = parameters + const { search, advancedSearch, componentPagination } = parameters const url = SearchService.BASE_SEARCH_URL + 'video-playlists' @@ -110,10 +111,12 @@ export class SearchService { let params = new HttpParams() params = this.restService.addRestGetParams(params, pagination) - params = params.append('search', search) - if (searchTarget) { - params = params.append('searchTarget', searchTarget as string) + if (search) params = params.append('search', search) + + if (advancedSearch) { + const advancedSearchObject = advancedSearch.toPlaylistAPIObject() + params = this.restService.addObjectParams(params, advancedSearchObject) } return this.authHttp diff --git a/client/src/app/shared/shared-share-modal/video-share.component.ts b/client/src/app/shared/shared-share-modal/video-share.component.ts index a41ff248b..341abdc2b 100644 --- a/client/src/app/shared/shared-share-modal/video-share.component.ts +++ b/client/src/app/shared/shared-share-modal/video-share.component.ts @@ -1,9 +1,10 @@ import { Component, ElementRef, Input, ViewChild } from '@angular/core' -import { Video, VideoDetails } from '@app/shared/shared-main' +import { VideoDetails } from '@app/shared/shared-main' import { VideoPlaylist } from '@app/shared/shared-video-playlist' import { NgbModal } from '@ng-bootstrap/ng-bootstrap' +import { buildPlaylistLink, buildVideoLink, decoratePlaylistLink, decorateVideoLink } from '@shared/core-utils' import { VideoCaption } from '@shared/models' -import { buildPlaylistLink, buildVideoLink, buildVideoOrPlaylistEmbed } from '../../../assets/player/utils' +import { buildVideoOrPlaylistEmbed } from '../../../assets/player/utils' type Customizations = { startAtCheckbox: boolean @@ -83,34 +84,34 @@ export class VideoShareComponent { } getVideoIframeCode () { - const options = this.getVideoOptions(this.video.embedUrl) + const embedUrl = decorateVideoLink({ url: this.video.embedUrl, ...this.getVideoOptions() }) - const embedUrl = buildVideoLink(options) return buildVideoOrPlaylistEmbed(embedUrl, this.video.name) } getPlaylistIframeCode () { - const options = this.getPlaylistOptions(this.playlist.embedUrl) + const embedUrl = decoratePlaylistLink({ url: this.playlist.embedUrl, ...this.getPlaylistOptions() }) - const embedUrl = buildPlaylistLink(options) return buildVideoOrPlaylistEmbed(embedUrl, this.playlist.displayName) } getVideoUrl () { - let baseUrl = this.customizations.originUrl ? this.video.originInstanceUrl : window.location.origin - baseUrl += Video.buildWatchUrl(this.video) + const baseUrl = this.customizations.originUrl + ? this.video.originInstanceUrl + : window.location.origin - const options = this.getVideoOptions(baseUrl) + return decorateVideoLink({ + url: buildVideoLink(this.video, baseUrl), - return buildVideoLink(options) + ...this.getVideoOptions() + }) } getPlaylistUrl () { - const base = window.location.origin + VideoPlaylist.buildWatchUrl(this.playlist) + const url = buildPlaylistLink(this.playlist) + if (!this.includeVideoInPlaylist) return url - if (!this.includeVideoInPlaylist) return base - - return base + '?playlistPosition=' + this.playlistPosition + return decoratePlaylistLink({ url, playlistPosition: this.playlistPosition }) } notSecure () { @@ -133,10 +134,8 @@ export class VideoShareComponent { } } - private getVideoOptions (baseUrl?: string) { + private getVideoOptions () { return { - baseUrl, - startTime: this.customizations.startAtCheckbox ? this.customizations.startAt : undefined, stopTime: this.customizations.stopAtCheckbox ? this.customizations.stopAt : undefined, diff --git a/client/src/app/shared/shared-video-miniature/abstract-video-list.ts b/client/src/app/shared/shared-video-miniature/abstract-video-list.ts index 52e72d35b..33061a837 100644 --- a/client/src/app/shared/shared-video-miniature/abstract-video-list.ts +++ b/client/src/app/shared/shared-video-miniature/abstract-video-list.ts @@ -24,7 +24,7 @@ import { } from '@app/core' import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook' import { GlobalIconName } from '@app/shared/shared-icons' -import { isLastMonth, isLastWeek, isThisMonth, isToday, isYesterday } from '@shared/core-utils/miscs/date' +import { isLastMonth, isLastWeek, isThisMonth, isToday, isYesterday } from '@shared/core-utils' import { HTMLServerConfig, UserRight, VideoFilter, VideoSortField } from '@shared/models' import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type' import { Syndication, Video } from '../shared-main' diff --git a/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts index 681e5becd..8b019103c 100644 --- a/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts +++ b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts @@ -4,6 +4,7 @@ import { debounceTime, filter } from 'rxjs/operators' import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core' import { AuthService, DisableForReuseHook, Notifier } from '@app/core' import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' +import { secondsToTime } from '@shared/core-utils' import { Video, VideoExistInPlaylist, @@ -12,7 +13,6 @@ import { VideoPlaylistElementUpdate, VideoPlaylistPrivacy } from '@shared/models' -import { secondsToTime } from '../../../assets/player/utils' import { VIDEO_PLAYLIST_DISPLAY_NAME_VALIDATOR } from '../form-validators/video-playlist-validators' import { CachedPlaylist, VideoPlaylistService } from './video-playlist.service' diff --git a/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts index d99170e4e..2e495ec26 100644 --- a/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts +++ b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts @@ -2,8 +2,8 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, In import { AuthService, Notifier, ServerService } from '@app/core' import { Video } from '@app/shared/shared-main' import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' +import { secondsToTime } from '@shared/core-utils' import { HTMLServerConfig, VideoPlaylistElementType, VideoPlaylistElementUpdate } from '@shared/models' -import { secondsToTime } from '../../../assets/player/utils' import { VideoPlaylistElement } from './video-playlist-element.model' import { VideoPlaylist } from './video-playlist.model' import { VideoPlaylistService } from './video-playlist.service' diff --git a/client/src/app/shared/shared-video-playlist/video-playlist.model.ts b/client/src/app/shared/shared-video-playlist/video-playlist.model.ts index 55013e4c5..fcc2ce705 100644 --- a/client/src/app/shared/shared-video-playlist/video-playlist.model.ts +++ b/client/src/app/shared/shared-video-playlist/video-playlist.model.ts @@ -1,5 +1,6 @@ import { getAbsoluteAPIUrl, getAbsoluteEmbedUrl } from '@app/helpers' import { Actor } from '@app/shared/shared-main' +import { buildPlaylistWatchPath } from '@shared/core-utils' import { peertubeTranslate } from '@shared/core-utils/i18n' import { AccountSummary, @@ -44,7 +45,7 @@ export class VideoPlaylist implements ServerVideoPlaylist { videoChannelBy?: string static buildWatchUrl (playlist: Pick) { - return '/w/p/' + (playlist.shortUUID || playlist.uuid) + return buildPlaylistWatchPath({ shortUUID: playlist.shortUUID || playlist.uuid }) } constructor (hash: ServerVideoPlaylist, translations: {}) { diff --git a/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts b/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts index f1bd9f0c4..2eb849d2b 100644 --- a/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts +++ b/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts @@ -2,8 +2,8 @@ import * as Hlsjs from 'hls.js/dist/hls.light.js' import { Events, Segment } from 'p2p-media-loader-core' import { Engine, initHlsJsPlayer, initVideoJsContribHlsJsPlayer } from 'p2p-media-loader-hlsjs' import videojs from 'video.js' +import { timeToInt } from '@shared/core-utils' import { P2PMediaLoaderPluginOptions, PlayerNetworkInfo } from '../peertube-videojs-typings' -import { timeToInt } from '../utils' import { registerConfigPlugin, registerSourceHandler } from './hls-plugin' registerConfigPlugin(videojs) diff --git a/client/src/assets/player/peertube-player-manager.ts b/client/src/assets/player/peertube-player-manager.ts index b071a0938..766ad203e 100644 --- a/client/src/assets/player/peertube-player-manager.ts +++ b/client/src/assets/player/peertube-player-manager.ts @@ -23,6 +23,7 @@ import './videojs-components/theater-button' import './playlist/playlist-plugin' import videojs from 'video.js' import { PluginsManager } from '@root-helpers/plugins-manager' +import { buildVideoLink, decorateVideoLink } from '@shared/core-utils' import { isDefaultLocale } from '@shared/core-utils/i18n' import { VideoFile } from '@shared/models' import { copyToClipboard } from '../../root-helpers/utils' @@ -33,13 +34,14 @@ import { getStoredP2PEnabled } from './peertube-player-local-storage' import { NextPreviousVideoButtonOptions, P2PMediaLoaderPluginOptions, + PeerTubeLinkButtonOptions, PlaylistPluginOptions, UserWatching, VideoJSCaption, VideoJSPluginOptions } from './peertube-videojs-typings' import { TranslationsManager } from './translations-manager' -import { buildVideoLink, buildVideoOrPlaylistEmbed, getRtcConfig, isIOS, isSafari } from './utils' +import { buildVideoOrPlaylistEmbed, getRtcConfig, isIOS, isSafari } from './utils' // Change 'Playback Rate' to 'Speed' (smaller for our settings menu) (videojs.getComponent('PlaybackRateMenuButton') as any).prototype.controlText_ = 'Speed' @@ -110,6 +112,7 @@ export interface CommonOptions extends CustomizationOptions { videoCaptions: VideoJSCaption[] videoUUID: string + videoShortUUID: string userWatching?: UserWatching @@ -175,7 +178,13 @@ export class PeertubePlayerManager { PeertubePlayerManager.alreadyPlayed = true }) - self.addContextMenu(mode, player, options.common.embedUrl, options.common.embedTitle) + self.addContextMenu({ + mode, + player, + videoShortUUID: options.common.videoShortUUID, + videoEmbedUrl: options.common.embedUrl, + videoEmbedTitle: options.common.embedTitle + }) player.bezels() player.stats({ @@ -218,7 +227,13 @@ export class PeertubePlayerManager { videojs(newVideoElement, videojsOptions, function (this: videojs.Player) { const player = this - self.addContextMenu(mode, player, options.common.embedUrl, options.common.embedTitle) + self.addContextMenu({ + mode, + player, + videoShortUUID: options.common.videoShortUUID, + videoEmbedUrl: options.common.embedUrl, + videoEmbedTitle: options.common.embedTitle + }) PeertubePlayerManager.onPlayerChange(player) }) @@ -295,6 +310,8 @@ export class PeertubePlayerManager { controlBar: { children: this.getControlBarChildren(mode, { + videoShortUUID: commonOptions.videoShortUUID, + captions: commonOptions.captions, peertubeLink: commonOptions.peertubeLink, theaterButton: commonOptions.theaterButton, @@ -409,6 +426,8 @@ export class PeertubePlayerManager { } private static getControlBarChildren (mode: PlayerMode, options: { + videoShortUUID: string + peertubeLink: boolean theaterButton: boolean captions: boolean @@ -497,7 +516,7 @@ export class PeertubePlayerManager { if (options.peertubeLink === true) { Object.assign(children, { - 'peerTubeLinkButton': {} + 'peerTubeLinkButton': { shortUUID: options.videoShortUUID } as PeerTubeLinkButtonOptions }) } @@ -514,7 +533,15 @@ export class PeertubePlayerManager { return children } - private static addContextMenu (mode: PlayerMode, player: videojs.Player, videoEmbedUrl: string, videoEmbedTitle: string) { + private static addContextMenu (options: { + mode: PlayerMode + player: videojs.Player + videoShortUUID: string + videoEmbedUrl: string + videoEmbedTitle: string + }) { + const { mode, player, videoEmbedTitle, videoEmbedUrl, videoShortUUID } = options + const content = () => { const isLoopEnabled = player.options_['loop'] const items = [ @@ -528,13 +555,15 @@ export class PeertubePlayerManager { { label: player.localize('Copy the video URL'), listener: function () { - copyToClipboard(buildVideoLink()) + copyToClipboard(buildVideoLink({ shortUUID: videoShortUUID })) } }, { label: player.localize('Copy the video URL at the current time'), listener: function (this: videojs.Player) { - copyToClipboard(buildVideoLink({ startTime: this.currentTime() })) + const url = buildVideoLink({ shortUUID: videoShortUUID }) + + copyToClipboard(decorateVideoLink({ url, startTime: this.currentTime() })) } }, { diff --git a/client/src/assets/player/peertube-plugin.ts b/client/src/assets/player/peertube-plugin.ts index 07c7e33f6..919b7c239 100644 --- a/client/src/assets/player/peertube-plugin.ts +++ b/client/src/assets/player/peertube-plugin.ts @@ -1,12 +1,6 @@ -import videojs from 'video.js' import './videojs-components/settings-menu-button' -import { - PeerTubePluginOptions, - ResolutionUpdateData, - UserWatching, - VideoJSCaption -} from './peertube-videojs-typings' -import { isMobile, timeToInt } from './utils' +import videojs from 'video.js' +import { timeToInt } from '@shared/core-utils' import { getStoredLastSubtitle, getStoredMute, @@ -16,6 +10,8 @@ import { saveVideoWatchHistory, saveVolumeInStore } from './peertube-player-local-storage' +import { PeerTubePluginOptions, ResolutionUpdateData, UserWatching, VideoJSCaption } from './peertube-videojs-typings' +import { isMobile } from './utils' const Plugin = videojs.getPlugin('plugin') diff --git a/client/src/assets/player/peertube-videojs-typings.ts b/client/src/assets/player/peertube-videojs-typings.ts index 8afb424a7..d3c75990b 100644 --- a/client/src/assets/player/peertube-videojs-typings.ts +++ b/client/src/assets/player/peertube-videojs-typings.ts @@ -132,6 +132,10 @@ type NextPreviousVideoButtonOptions = { isDisabled: () => boolean } +type PeerTubeLinkButtonOptions = { + shortUUID: string +} + type WebtorrentPluginOptions = { playerElement: HTMLVideoElement @@ -225,5 +229,6 @@ export { VideoJSPluginOptions, LoadedQualityData, QualityLevelRepresentation, + PeerTubeLinkButtonOptions, QualityLevels } diff --git a/client/src/assets/player/playlist/playlist-menu-item.ts b/client/src/assets/player/playlist/playlist-menu-item.ts index 87a72b6a3..2519a34c7 100644 --- a/client/src/assets/player/playlist/playlist-menu-item.ts +++ b/client/src/assets/player/playlist/playlist-menu-item.ts @@ -1,7 +1,7 @@ import videojs from 'video.js' +import { secondsToTime } from '@shared/core-utils' import { VideoPlaylistElement } from '@shared/models' import { PlaylistItemOptions } from '../peertube-videojs-typings' -import { secondsToTime } from '../utils' const Component = videojs.getComponent('Component') diff --git a/client/src/assets/player/stats/stats-card.ts b/client/src/assets/player/stats/stats-card.ts index a93f59506..b271d0526 100644 --- a/client/src/assets/player/stats/stats-card.ts +++ b/client/src/assets/player/stats/stats-card.ts @@ -1,6 +1,7 @@ import videojs from 'video.js' +import { secondsToTime } from '@shared/core-utils' import { PlayerNetworkInfo as EventPlayerNetworkInfo } from '../peertube-videojs-typings' -import { bytes, secondsToTime } from '../utils' +import { bytes } from '../utils' interface StatsCardOptions extends videojs.ComponentOptions { videoUUID: string diff --git a/client/src/assets/player/utils.ts b/client/src/assets/player/utils.ts index f26176acc..f0a1b1aee 100644 --- a/client/src/assets/player/utils.ts +++ b/client/src/assets/player/utils.ts @@ -1,5 +1,5 @@ -import { VideoFile } from '@shared/models' import { escapeHTML } from '@shared/core-utils/renderer' +import { VideoFile } from '@shared/models' function toTitleCase (str: string) { return str.charAt(0).toUpperCase() + str.slice(1) @@ -43,136 +43,9 @@ function isMobile () { return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent) } -function buildVideoLink (options: { - baseUrl?: string - - startTime?: number - stopTime?: number - - subtitle?: string - - loop?: boolean - autoplay?: boolean - muted?: boolean - - // Embed options - title?: boolean - warningTitle?: boolean - controls?: boolean - peertubeLink?: boolean -} = {}) { - const { baseUrl } = options - - const url = baseUrl - ? baseUrl - : window.location.origin + window.location.pathname.replace('/videos/embed/', '/w/') - - const params = generateParams(window.location.search) - - if (options.startTime !== undefined && options.startTime !== null) { - const startTimeInt = Math.floor(options.startTime) - params.set('start', secondsToTime(startTimeInt)) - } - - if (options.stopTime) { - const stopTimeInt = Math.floor(options.stopTime) - params.set('stop', secondsToTime(stopTimeInt)) - } - - if (options.subtitle) params.set('subtitle', options.subtitle) - - if (options.loop === true) params.set('loop', '1') - if (options.autoplay === true) params.set('autoplay', '1') - if (options.muted === true) params.set('muted', '1') - if (options.title === false) params.set('title', '0') - if (options.warningTitle === false) params.set('warningTitle', '0') - if (options.controls === false) params.set('controls', '0') - if (options.peertubeLink === false) params.set('peertubeLink', '0') - - return buildUrl(url, params) -} - -function buildPlaylistLink (options: { - baseUrl?: string - - playlistPosition?: number -}) { - const { baseUrl } = options - - const url = baseUrl - ? baseUrl - : window.location.origin + window.location.pathname.replace('/video-playlists/embed/', '/w/p/') - - const params = generateParams(window.location.search) - - if (options.playlistPosition) params.set('playlistPosition', '' + options.playlistPosition) - - return buildUrl(url, params) -} - -function buildUrl (url: string, params: URLSearchParams) { - let hasParams = false - params.forEach(() => hasParams = true) - - if (hasParams) return url + '?' + params.toString() - - return url -} - -function generateParams (url: string) { - const params = new URLSearchParams(window.location.search) - // Unused parameters in embed - params.delete('videoId') - params.delete('resume') - - return params -} - -function timeToInt (time: number | string) { - if (!time) return 0 - if (typeof time === 'number') return time - - const reg = /^((\d+)[h:])?((\d+)[m:])?((\d+)s?)?$/ - const matches = time.match(reg) - - if (!matches) return 0 - - const hours = parseInt(matches[2] || '0', 10) - const minutes = parseInt(matches[4] || '0', 10) - const seconds = parseInt(matches[6] || '0', 10) - - return hours * 3600 + minutes * 60 + seconds -} - -function secondsToTime (seconds: number, full = false, symbol?: string) { - let time = '' - - if (seconds === 0 && !full) return '0s' - - const hourSymbol = (symbol || 'h') - const minuteSymbol = (symbol || 'm') - const secondsSymbol = full ? '' : 's' - - const hours = Math.floor(seconds / 3600) - if (hours >= 1) time = hours + hourSymbol - else if (full) time = '0' + hourSymbol - - seconds %= 3600 - const minutes = Math.floor(seconds / 60) - if (minutes >= 1 && minutes < 10 && full) time += '0' + minutes + minuteSymbol - else if (minutes >= 1) time += minutes + minuteSymbol - else if (full) time += '00' + minuteSymbol - - seconds %= 60 - if (seconds >= 1 && seconds < 10 && full) time += '0' + seconds + secondsSymbol - else if (seconds >= 1) time += seconds + secondsSymbol - else if (full) time += '00' - - return time -} - function buildVideoOrPlaylistEmbed (embedUrl: string, embedTitle: string) { const title = escapeHTML(embedTitle) + return '' @@ -78,7 +62,7 @@ describe('Test services', function () { expect(res.body.html).to.equal(expectedHtml) expect(res.body.title).to.equal(video.name) - expect(res.body.author_name).to.equal(server.videoChannel.displayName) + expect(res.body.author_name).to.equal(server.store.channel.displayName) expect(res.body.width).to.equal(560) expect(res.body.height).to.equal(315) expect(res.body.thumbnail_url).to.equal(expectedThumbnailUrl) @@ -91,14 +75,14 @@ describe('Test services', function () { for (const basePath of [ '/videos/watch/playlist/', '/w/p/' ]) { const oembedUrl = 'http://localhost:' + server.port + basePath + playlistUUID - const res = await getOEmbed(server.url, oembedUrl) + const res = await server.services.getOEmbed({ oembedUrl }) const expectedHtml = '' expect(res.body.html).to.equal(expectedHtml) expect(res.body.title).to.equal('The Life and Times of Scrooge McDuck') - expect(res.body.author_name).to.equal(server.videoChannel.displayName) + expect(res.body.author_name).to.equal(server.store.channel.displayName) expect(res.body.width).to.equal(560) expect(res.body.height).to.equal(315) expect(res.body.thumbnail_url).exist @@ -114,14 +98,14 @@ describe('Test services', function () { const maxHeight = 50 const maxWidth = 50 - const res = await getOEmbed(server.url, oembedUrl, format, maxHeight, maxWidth) + const res = await server.services.getOEmbed({ oembedUrl, format, maxHeight, maxWidth }) const expectedHtml = '' expect(res.body.html).to.equal(expectedHtml) expect(res.body.title).to.equal(video.name) - expect(res.body.author_name).to.equal(server.videoChannel.displayName) + expect(res.body.author_name).to.equal(server.store.channel.displayName) expect(res.body.height).to.equal(50) expect(res.body.width).to.equal(50) expect(res.body).to.not.have.property('thumbnail_url') diff --git a/server/tests/api/server/stats.ts b/server/tests/api/server/stats.ts index 304181a6d..5ec771429 100644 --- a/server/tests/api/server/stats.ts +++ b/server/tests/api/server/stats.ts @@ -3,33 +3,20 @@ import 'mocha' import * as chai from 'chai' import { - addVideoChannel, cleanupTests, - createUser, - createVideoPlaylist, + createMultipleServers, doubleFollow, - flushAndRunMultipleServers, - follow, - ServerInfo, - unfollow, - updateCustomSubConfig, - uploadVideo, - userLogin, - viewVideo, - wait -} from '../../../../shared/extra-utils' -import { setAccessTokensToServers } from '../../../../shared/extra-utils/index' -import { waitJobs } from '../../../../shared/extra-utils/server/jobs' -import { getStats } from '../../../../shared/extra-utils/server/stats' -import { addVideoCommentThread } from '../../../../shared/extra-utils/videos/video-comments' -import { ServerStats } from '../../../../shared/models/server/server-stats.model' -import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' -import { ActivityType } from '@shared/models' + PeerTubeServer, + setAccessTokensToServers, + wait, + waitJobs +} from '@shared/extra-utils' +import { ActivityType, VideoPlaylistPrivacy } from '@shared/models' const expect = chai.expect describe('Test stats (excluding redundancy)', function () { - let servers: ServerInfo[] = [] + let servers: PeerTubeServer[] = [] let channelId const user = { username: 'user1', @@ -39,31 +26,29 @@ describe('Test stats (excluding redundancy)', function () { before(async function () { this.timeout(60000) - servers = await flushAndRunMultipleServers(3) + servers = await createMultipleServers(3) await setAccessTokensToServers(servers) await doubleFollow(servers[0], servers[1]) - await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: user.username, password: user.password }) + await servers[0].users.create({ username: user.username, password: user.password }) - const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { fixture: 'video_short.webm' }) - const videoUUID = resVideo.body.video.uuid + const { uuid } = await servers[0].videos.upload({ attributes: { fixture: 'video_short.webm' } }) - await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoUUID, 'comment') + await servers[0].comments.createThread({ videoId: uuid, text: 'comment' }) - await viewVideo(servers[0].url, videoUUID) + await servers[0].videos.view({ id: uuid }) // Wait the video views repeatable job await wait(8000) - await follow(servers[2].url, [ servers[0].url ], servers[2].accessToken) + await servers[2].follows.follow({ hosts: [ servers[0].url ] }) await waitJobs(servers) }) it('Should have the correct stats on instance 1', async function () { - const res = await getStats(servers[0].url) - const data: ServerStats = res.body + const data = await servers[0].stats.get() expect(data.totalLocalVideoComments).to.equal(1) expect(data.totalLocalVideos).to.equal(1) @@ -78,8 +63,7 @@ describe('Test stats (excluding redundancy)', function () { }) it('Should have the correct stats on instance 2', async function () { - const res = await getStats(servers[1].url) - const data: ServerStats = res.body + const data = await servers[1].stats.get() expect(data.totalLocalVideoComments).to.equal(0) expect(data.totalLocalVideos).to.equal(0) @@ -94,8 +78,7 @@ describe('Test stats (excluding redundancy)', function () { }) it('Should have the correct stats on instance 3', async function () { - const res = await getStats(servers[2].url) - const data: ServerStats = res.body + const data = await servers[2].stats.get() expect(data.totalLocalVideoComments).to.equal(0) expect(data.totalLocalVideos).to.equal(0) @@ -111,11 +94,10 @@ describe('Test stats (excluding redundancy)', function () { it('Should have the correct total videos stats after an unfollow', async function () { this.timeout(15000) - await unfollow(servers[2].url, servers[2].accessToken, servers[0]) + await servers[2].follows.unfollow({ target: servers[0] }) await waitJobs(servers) - const res = await getStats(servers[2].url) - const data: ServerStats = res.body + const data = await servers[2].stats.get() expect(data.totalVideos).to.equal(0) }) @@ -124,18 +106,18 @@ describe('Test stats (excluding redundancy)', function () { const server = servers[0] { - const res = await getStats(server.url) - const data: ServerStats = res.body + const data = await server.stats.get() + expect(data.totalDailyActiveUsers).to.equal(1) expect(data.totalWeeklyActiveUsers).to.equal(1) expect(data.totalMonthlyActiveUsers).to.equal(1) } { - await userLogin(server, user) + await server.login.getAccessToken(user) + + const data = await server.stats.get() - const res = await getStats(server.url) - const data: ServerStats = res.body expect(data.totalDailyActiveUsers).to.equal(2) expect(data.totalWeeklyActiveUsers).to.equal(2) expect(data.totalMonthlyActiveUsers).to.equal(2) @@ -146,33 +128,33 @@ describe('Test stats (excluding redundancy)', function () { const server = servers[0] { - const res = await getStats(server.url) - const data: ServerStats = res.body + const data = await server.stats.get() + expect(data.totalLocalDailyActiveVideoChannels).to.equal(1) expect(data.totalLocalWeeklyActiveVideoChannels).to.equal(1) expect(data.totalLocalMonthlyActiveVideoChannels).to.equal(1) } { - const channelAttributes = { + const attributes = { name: 'stats_channel', displayName: 'My stats channel' } - const resChannel = await addVideoChannel(server.url, server.accessToken, channelAttributes) - channelId = resChannel.body.videoChannel.id + const created = await server.channels.create({ attributes }) + channelId = created.id + + const data = await server.stats.get() - const res = await getStats(server.url) - const data: ServerStats = res.body expect(data.totalLocalDailyActiveVideoChannels).to.equal(1) expect(data.totalLocalWeeklyActiveVideoChannels).to.equal(1) expect(data.totalLocalMonthlyActiveVideoChannels).to.equal(1) } { - await uploadVideo(server.url, server.accessToken, { fixture: 'video_short.webm', channelId }) + await server.videos.upload({ attributes: { fixture: 'video_short.webm', channelId } }) + + const data = await server.stats.get() - const res = await getStats(server.url) - const data: ServerStats = res.body expect(data.totalLocalDailyActiveVideoChannels).to.equal(2) expect(data.totalLocalWeeklyActiveVideoChannels).to.equal(2) expect(data.totalLocalMonthlyActiveVideoChannels).to.equal(2) @@ -183,66 +165,62 @@ describe('Test stats (excluding redundancy)', function () { const server = servers[0] { - const resStats = await getStats(server.url) - const dataStats: ServerStats = resStats.body - expect(dataStats.totalLocalPlaylists).to.equal(0) + const data = await server.stats.get() + expect(data.totalLocalPlaylists).to.equal(0) } { - await createVideoPlaylist({ - url: server.url, - token: server.accessToken, - playlistAttrs: { + await server.playlists.create({ + attributes: { displayName: 'playlist for count', privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: channelId } }) - const resStats = await getStats(server.url) - const dataStats: ServerStats = resStats.body - expect(dataStats.totalLocalPlaylists).to.equal(1) + const data = await server.stats.get() + expect(data.totalLocalPlaylists).to.equal(1) } }) it('Should correctly count video file sizes if transcoding is enabled', async function () { this.timeout(60000) - await updateCustomSubConfig(servers[0].url, servers[0].accessToken, { - transcoding: { - enabled: true, - webtorrent: { - enabled: true - }, - hls: { - enabled: true - }, - resolutions: { - '0p': false, - '240p': false, - '360p': false, - '480p': false, - '720p': false, - '1080p': false, - '1440p': false, - '2160p': false + await servers[0].config.updateCustomSubConfig({ + newConfig: { + transcoding: { + enabled: true, + webtorrent: { + enabled: true + }, + hls: { + enabled: true + }, + resolutions: { + '0p': false, + '240p': false, + '360p': false, + '480p': false, + '720p': false, + '1080p': false, + '1440p': false, + '2160p': false + } } } }) - await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video', fixture: 'video_short.webm' }) + await servers[0].videos.upload({ attributes: { name: 'video', fixture: 'video_short.webm' } }) await waitJobs(servers) { - const res = await getStats(servers[1].url) - const data: ServerStats = res.body + const data = await servers[1].stats.get() expect(data.totalLocalVideoFilesSize).to.equal(0) } { - const res = await getStats(servers[0].url) - const data: ServerStats = res.body + const data = await servers[0].stats.get() expect(data.totalLocalVideoFilesSize).to.be.greaterThan(500000) expect(data.totalLocalVideoFilesSize).to.be.lessThan(600000) } @@ -251,27 +229,27 @@ describe('Test stats (excluding redundancy)', function () { it('Should have the correct AP stats', async function () { this.timeout(60000) - await updateCustomSubConfig(servers[0].url, servers[0].accessToken, { - transcoding: { - enabled: false + await servers[0].config.updateCustomSubConfig({ + newConfig: { + transcoding: { + enabled: false + } } }) - const res1 = await getStats(servers[1].url) - const first = res1.body as ServerStats + const first = await servers[1].stats.get() for (let i = 0; i < 10; i++) { - await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video' }) + await servers[0].videos.upload({ attributes: { name: 'video' } }) } await waitJobs(servers) await wait(6000) - const res2 = await getStats(servers[1].url) - const second: ServerStats = res2.body - + const second = await servers[1].stats.get() expect(second.totalActivityPubMessagesProcessed).to.be.greaterThan(first.totalActivityPubMessagesProcessed) + const apTypes: ActivityType[] = [ 'Create', 'Update', 'Delete', 'Follow', 'Accept', 'Announce', 'Undo', 'Like', 'Reject', 'View', 'Dislike', 'Flag' ] @@ -291,9 +269,7 @@ describe('Test stats (excluding redundancy)', function () { await wait(6000) - const res3 = await getStats(servers[1].url) - const third: ServerStats = res3.body - + const third = await servers[1].stats.get() expect(third.totalActivityPubMessagesWaiting).to.equal(0) expect(third.activityPubMessagesProcessedPerSecond).to.be.lessThan(second.activityPubMessagesProcessedPerSecond) }) diff --git a/server/tests/api/server/tracker.ts b/server/tests/api/server/tracker.ts index 4b86e0b90..f597ac60c 100644 --- a/server/tests/api/server/tracker.ts +++ b/server/tests/api/server/tracker.ts @@ -1,36 +1,23 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await,@typescript-eslint/no-floating-promises */ -import * as magnetUtil from 'magnet-uri' import 'mocha' -import { - cleanupTests, - flushAndRunServer, - getVideo, - killallServers, - reRunServer, - ServerInfo, - uploadVideo -} from '../../../../shared/extra-utils' -import { setAccessTokensToServers } from '../../../../shared/extra-utils/index' -import { VideoDetails } from '../../../../shared/models/videos' +import * as magnetUtil from 'magnet-uri' import * as WebTorrent from 'webtorrent' +import { cleanupTests, createSingleServer, killallServers, PeerTubeServer, setAccessTokensToServers } from '@shared/extra-utils' describe('Test tracker', function () { - let server: ServerInfo + let server: PeerTubeServer let badMagnet: string let goodMagnet: string before(async function () { this.timeout(60000) - server = await flushAndRunServer(1) + server = await createSingleServer(1) await setAccessTokensToServers([ server ]) { - const res = await uploadVideo(server.url, server.accessToken, {}) - const videoUUID = res.body.video.uuid - - const resGet = await getVideo(server.url, videoUUID) - const video: VideoDetails = resGet.body + const { uuid } = await server.videos.upload() + const video = await server.videos.get({ id: uuid }) goodMagnet = video.files[0].magnetUri const parsed = magnetUtil.decode(goodMagnet) @@ -61,8 +48,7 @@ describe('Test tracker', function () { const errCb = () => done(new Error('Tracker is enabled')) killallServers([ server ]) - - reRunServer(server, { tracker: { enabled: false } }) + .then(() => server.run({ tracker: { enabled: false } })) .then(() => { const webtorrent = new WebTorrent() @@ -86,8 +72,7 @@ describe('Test tracker', function () { this.timeout(20000) killallServers([ server ]) - - reRunServer(server) + .then(() => server.run()) .then(() => { const webtorrent = new WebTorrent() diff --git a/server/tests/api/users/user-subscriptions.ts b/server/tests/api/users/user-subscriptions.ts index 60676a37b..77b99886d 100644 --- a/server/tests/api/users/user-subscriptions.ts +++ b/server/tests/api/users/user-subscriptions.ts @@ -1,42 +1,30 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ -import * as chai from 'chai' import 'mocha' +import * as chai from 'chai' import { cleanupTests, - createUser, + createMultipleServers, doubleFollow, - flushAndRunMultipleServers, - follow, - getVideosList, - unfollow, - updateVideo, - userLogin -} from '../../../../shared/extra-utils' -import { ServerInfo, uploadVideo } from '../../../../shared/extra-utils/index' -import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login' -import { Video, VideoChannel } from '../../../../shared/models/videos' -import { waitJobs } from '../../../../shared/extra-utils/server/jobs' -import { - addUserSubscription, - areSubscriptionsExist, - getUserSubscription, - listUserSubscriptions, - listUserSubscriptionVideos, - removeUserSubscription -} from '../../../../shared/extra-utils/users/user-subscriptions' + PeerTubeServer, + setAccessTokensToServers, + SubscriptionsCommand, + waitJobs +} from '@shared/extra-utils' const expect = chai.expect describe('Test users subscriptions', function () { - let servers: ServerInfo[] = [] + let servers: PeerTubeServer[] = [] const users: { accessToken: string }[] = [] let video3UUID: string + let command: SubscriptionsCommand + before(async function () { this.timeout(120000) - servers = await flushAndRunMultipleServers(3) + servers = await createMultipleServers(3) // Get the access tokens await setAccessTokensToServers(servers) @@ -47,47 +35,50 @@ describe('Test users subscriptions', function () { { for (const server of servers) { const user = { username: 'user' + server.serverNumber, password: 'password' } - await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password }) + await server.users.create({ username: user.username, password: user.password }) - const accessToken = await userLogin(server, user) + const accessToken = await server.login.getAccessToken(user) users.push({ accessToken }) const videoName1 = 'video 1-' + server.serverNumber - await uploadVideo(server.url, accessToken, { name: videoName1 }) + await server.videos.upload({ token: accessToken, attributes: { name: videoName1 } }) const videoName2 = 'video 2-' + server.serverNumber - await uploadVideo(server.url, accessToken, { name: videoName2 }) + await server.videos.upload({ token: accessToken, attributes: { name: videoName2 } }) } } await waitJobs(servers) + + command = servers[0].subscriptions }) it('Should display videos of server 2 on server 1', async function () { - const res = await getVideosList(servers[0].url) + const { total } = await servers[0].videos.list() - expect(res.body.total).to.equal(4) + expect(total).to.equal(4) }) it('User of server 1 should follow user of server 3 and root of server 1', async function () { this.timeout(60000) - await addUserSubscription(servers[0].url, users[0].accessToken, 'user3_channel@localhost:' + servers[2].port) - await addUserSubscription(servers[0].url, users[0].accessToken, 'root_channel@localhost:' + servers[0].port) + await command.add({ token: users[0].accessToken, targetUri: 'user3_channel@localhost:' + servers[2].port }) + await command.add({ token: users[0].accessToken, targetUri: 'root_channel@localhost:' + servers[0].port }) await waitJobs(servers) - const res = await uploadVideo(servers[2].url, users[2].accessToken, { name: 'video server 3 added after follow' }) - video3UUID = res.body.video.uuid + const attributes = { name: 'video server 3 added after follow' } + const { uuid } = await servers[2].videos.upload({ token: users[2].accessToken, attributes }) + video3UUID = uuid await waitJobs(servers) }) it('Should not display videos of server 3 on server 1', async function () { - const res = await getVideosList(servers[0].url) + const { total, data } = await servers[0].videos.list() + expect(total).to.equal(4) - expect(res.body.total).to.equal(4) - for (const video of res.body.data) { + for (const video of data) { expect(video.name).to.not.contain('1-3') expect(video.name).to.not.contain('2-3') expect(video.name).to.not.contain('video server 3 added after follow') @@ -96,17 +87,17 @@ describe('Test users subscriptions', function () { it('Should list subscriptions', async function () { { - const res = await listUserSubscriptions({ url: servers[0].url, token: servers[0].accessToken }) - expect(res.body.total).to.equal(0) - expect(res.body.data).to.be.an('array') - expect(res.body.data).to.have.lengthOf(0) + const body = await command.list() + expect(body.total).to.equal(0) + expect(body.data).to.be.an('array') + expect(body.data).to.have.lengthOf(0) } { - const res = await listUserSubscriptions({ url: servers[0].url, token: users[0].accessToken, sort: 'createdAt' }) - expect(res.body.total).to.equal(2) + const body = await command.list({ token: users[0].accessToken, sort: 'createdAt' }) + expect(body.total).to.equal(2) - const subscriptions: VideoChannel[] = res.body.data + const subscriptions = body.data expect(subscriptions).to.be.an('array') expect(subscriptions).to.have.lengthOf(2) @@ -117,8 +108,7 @@ describe('Test users subscriptions', function () { it('Should get subscription', async function () { { - const res = await getUserSubscription(servers[0].url, users[0].accessToken, 'user3_channel@localhost:' + servers[2].port) - const videoChannel: VideoChannel = res.body + const videoChannel = await command.get({ token: users[0].accessToken, uri: 'user3_channel@localhost:' + servers[2].port }) expect(videoChannel.name).to.equal('user3_channel') expect(videoChannel.host).to.equal('localhost:' + servers[2].port) @@ -128,8 +118,7 @@ describe('Test users subscriptions', function () { } { - const res = await getUserSubscription(servers[0].url, users[0].accessToken, 'root_channel@localhost:' + servers[0].port) - const videoChannel: VideoChannel = res.body + const videoChannel = await command.get({ token: users[0].accessToken, uri: 'root_channel@localhost:' + servers[0].port }) expect(videoChannel.name).to.equal('root_channel') expect(videoChannel.host).to.equal('localhost:' + servers[0].port) @@ -147,8 +136,7 @@ describe('Test users subscriptions', function () { 'user3_channel@localhost:' + servers[0].port ] - const res = await areSubscriptionsExist(servers[0].url, users[0].accessToken, uris) - const body = res.body + const body = await command.exist({ token: users[0].accessToken, uris }) expect(body['user3_channel@localhost:' + servers[2].port]).to.be.true expect(body['root2_channel@localhost:' + servers[0].port]).to.be.false @@ -158,45 +146,31 @@ describe('Test users subscriptions', function () { it('Should search among subscriptions', async function () { { - const res = await listUserSubscriptions({ - url: servers[0].url, - token: users[0].accessToken, - sort: '-createdAt', - search: 'user3_channel' - }) - expect(res.body.total).to.equal(1) - - const subscriptions = res.body.data - expect(subscriptions).to.have.lengthOf(1) + const body = await command.list({ token: users[0].accessToken, sort: '-createdAt', search: 'user3_channel' }) + expect(body.total).to.equal(1) + expect(body.data).to.have.lengthOf(1) } { - const res = await listUserSubscriptions({ - url: servers[0].url, - token: users[0].accessToken, - sort: '-createdAt', - search: 'toto' - }) - expect(res.body.total).to.equal(0) - - const subscriptions = res.body.data - expect(subscriptions).to.have.lengthOf(0) + const body = await command.list({ token: users[0].accessToken, sort: '-createdAt', search: 'toto' }) + expect(body.total).to.equal(0) + expect(body.data).to.have.lengthOf(0) } }) it('Should list subscription videos', async function () { { - const res = await listUserSubscriptionVideos(servers[0].url, servers[0].accessToken) - expect(res.body.total).to.equal(0) - expect(res.body.data).to.be.an('array') - expect(res.body.data).to.have.lengthOf(0) + const body = await command.listVideos() + expect(body.total).to.equal(0) + expect(body.data).to.be.an('array') + expect(body.data).to.have.lengthOf(0) } { - const res = await listUserSubscriptionVideos(servers[0].url, users[0].accessToken, 'createdAt') - expect(res.body.total).to.equal(3) + const body = await command.listVideos({ token: users[0].accessToken, sort: 'createdAt' }) + expect(body.total).to.equal(3) - const videos: Video[] = res.body.data + const videos = body.data expect(videos).to.be.an('array') expect(videos).to.have.lengthOf(3) @@ -210,22 +184,22 @@ describe('Test users subscriptions', function () { this.timeout(60000) const videoName = 'video server 1 added after follow' - await uploadVideo(servers[0].url, servers[0].accessToken, { name: videoName }) + await servers[0].videos.upload({ attributes: { name: videoName } }) await waitJobs(servers) { - const res = await listUserSubscriptionVideos(servers[0].url, servers[0].accessToken) - expect(res.body.total).to.equal(0) - expect(res.body.data).to.be.an('array') - expect(res.body.data).to.have.lengthOf(0) + const body = await command.listVideos() + expect(body.total).to.equal(0) + expect(body.data).to.be.an('array') + expect(body.data).to.have.lengthOf(0) } { - const res = await listUserSubscriptionVideos(servers[0].url, users[0].accessToken, 'createdAt') - expect(res.body.total).to.equal(4) + const body = await command.listVideos({ token: users[0].accessToken, sort: 'createdAt' }) + expect(body.total).to.equal(4) - const videos: Video[] = res.body.data + const videos = body.data expect(videos).to.be.an('array') expect(videos).to.have.lengthOf(4) @@ -236,10 +210,10 @@ describe('Test users subscriptions', function () { } { - const res = await getVideosList(servers[0].url) + const { data, total } = await servers[0].videos.list() + expect(total).to.equal(5) - expect(res.body.total).to.equal(5) - for (const video of res.body.data) { + for (const video of data) { expect(video.name).to.not.contain('1-3') expect(video.name).to.not.contain('2-3') expect(video.name).to.not.contain('video server 3 added after follow') @@ -250,17 +224,16 @@ describe('Test users subscriptions', function () { it('Should have server 1 follow server 3 and display server 3 videos', async function () { this.timeout(60000) - await follow(servers[0].url, [ servers[2].url ], servers[0].accessToken) + await servers[0].follows.follow({ hosts: [ servers[2].url ] }) await waitJobs(servers) - const res = await getVideosList(servers[0].url) - - expect(res.body.total).to.equal(8) + const { data, total } = await servers[0].videos.list() + expect(total).to.equal(8) const names = [ '1-3', '2-3', 'video server 3 added after follow' ] for (const name of names) { - const video = res.body.data.find(v => v.name.indexOf(name) === -1) + const video = data.find(v => v.name.includes(name)) expect(video).to.not.be.undefined } }) @@ -268,14 +241,14 @@ describe('Test users subscriptions', function () { it('Should remove follow server 1 -> server 3 and hide server 3 videos', async function () { this.timeout(60000) - await unfollow(servers[0].url, servers[0].accessToken, servers[2]) + await servers[0].follows.unfollow({ target: servers[2] }) await waitJobs(servers) - const res = await getVideosList(servers[0].url) + const { total, data } = await servers[0].videos.list() + expect(total).to.equal(5) - expect(res.body.total).to.equal(5) - for (const video of res.body.data) { + for (const video of data) { expect(video.name).to.not.contain('1-3') expect(video.name).to.not.contain('2-3') expect(video.name).to.not.contain('video server 3 added after follow') @@ -284,17 +257,17 @@ describe('Test users subscriptions', function () { it('Should still list subscription videos', async function () { { - const res = await listUserSubscriptionVideos(servers[0].url, servers[0].accessToken) - expect(res.body.total).to.equal(0) - expect(res.body.data).to.be.an('array') - expect(res.body.data).to.have.lengthOf(0) + const body = await command.listVideos() + expect(body.total).to.equal(0) + expect(body.data).to.be.an('array') + expect(body.data).to.have.lengthOf(0) } { - const res = await listUserSubscriptionVideos(servers[0].url, users[0].accessToken, 'createdAt') - expect(res.body.total).to.equal(4) + const body = await command.listVideos({ token: users[0].accessToken, sort: 'createdAt' }) + expect(body.total).to.equal(4) - const videos: Video[] = res.body.data + const videos = body.data expect(videos).to.be.an('array') expect(videos).to.have.lengthOf(4) @@ -308,58 +281,55 @@ describe('Test users subscriptions', function () { it('Should update a video of server 3 and see the updated video on server 1', async function () { this.timeout(30000) - await updateVideo(servers[2].url, users[2].accessToken, video3UUID, { name: 'video server 3 added after follow updated' }) + await servers[2].videos.update({ id: video3UUID, attributes: { name: 'video server 3 added after follow updated' } }) await waitJobs(servers) - const res = await listUserSubscriptionVideos(servers[0].url, users[0].accessToken, 'createdAt') - const videos: Video[] = res.body.data - expect(videos[2].name).to.equal('video server 3 added after follow updated') + const body = await command.listVideos({ token: users[0].accessToken, sort: 'createdAt' }) + expect(body.data[2].name).to.equal('video server 3 added after follow updated') }) it('Should remove user of server 3 subscription', async function () { this.timeout(30000) - await removeUserSubscription(servers[0].url, users[0].accessToken, 'user3_channel@localhost:' + servers[2].port) + await command.remove({ token: users[0].accessToken, uri: 'user3_channel@localhost:' + servers[2].port }) await waitJobs(servers) }) it('Should not display its videos anymore', async function () { - { - const res = await listUserSubscriptionVideos(servers[0].url, users[0].accessToken, 'createdAt') - expect(res.body.total).to.equal(1) + const body = await command.listVideos({ token: users[0].accessToken, sort: 'createdAt' }) + expect(body.total).to.equal(1) - const videos: Video[] = res.body.data - expect(videos).to.be.an('array') - expect(videos).to.have.lengthOf(1) + const videos = body.data + expect(videos).to.be.an('array') + expect(videos).to.have.lengthOf(1) - expect(videos[0].name).to.equal('video server 1 added after follow') - } + expect(videos[0].name).to.equal('video server 1 added after follow') }) it('Should remove the root subscription and not display the videos anymore', async function () { this.timeout(30000) - await removeUserSubscription(servers[0].url, users[0].accessToken, 'root_channel@localhost:' + servers[0].port) + await command.remove({ token: users[0].accessToken, uri: 'root_channel@localhost:' + servers[0].port }) await waitJobs(servers) { - const res = await listUserSubscriptionVideos(servers[0].url, users[0].accessToken, 'createdAt') - expect(res.body.total).to.equal(0) + const body = await command.list({ token: users[0].accessToken, sort: 'createdAt' }) + expect(body.total).to.equal(0) - const videos: Video[] = res.body.data + const videos = body.data expect(videos).to.be.an('array') expect(videos).to.have.lengthOf(0) } }) it('Should correctly display public videos on server 1', async function () { - const res = await getVideosList(servers[0].url) + const { total, data } = await servers[0].videos.list() + expect(total).to.equal(5) - expect(res.body.total).to.equal(5) - for (const video of res.body.data) { + for (const video of data) { expect(video.name).to.not.contain('1-3') expect(video.name).to.not.contain('2-3') expect(video.name).to.not.contain('video server 3 added after follow updated') @@ -369,15 +339,15 @@ describe('Test users subscriptions', function () { it('Should follow user of server 3 again', async function () { this.timeout(60000) - await addUserSubscription(servers[0].url, users[0].accessToken, 'user3_channel@localhost:' + servers[2].port) + await command.add({ token: users[0].accessToken, targetUri: 'user3_channel@localhost:' + servers[2].port }) await waitJobs(servers) { - const res = await listUserSubscriptionVideos(servers[0].url, users[0].accessToken, 'createdAt') - expect(res.body.total).to.equal(3) + const body = await command.listVideos({ token: users[0].accessToken, sort: 'createdAt' }) + expect(body.total).to.equal(3) - const videos: Video[] = res.body.data + const videos = body.data expect(videos).to.be.an('array') expect(videos).to.have.lengthOf(3) @@ -387,10 +357,10 @@ describe('Test users subscriptions', function () { } { - const res = await getVideosList(servers[0].url) + const { total, data } = await servers[0].videos.list() + expect(total).to.equal(5) - expect(res.body.total).to.equal(5) - for (const video of res.body.data) { + for (const video of data) { expect(video.name).to.not.contain('1-3') expect(video.name).to.not.contain('2-3') expect(video.name).to.not.contain('video server 3 added after follow updated') diff --git a/server/tests/api/users/users-multiple-servers.ts b/server/tests/api/users/users-multiple-servers.ts index f60c66e4b..d0ca82b07 100644 --- a/server/tests/api/users/users-multiple-servers.ts +++ b/server/tests/api/users/users-multiple-servers.ts @@ -1,34 +1,30 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ -import * as chai from 'chai' import 'mocha' -import { Account } from '../../../../shared/models/actors' +import * as chai from 'chai' import { + checkActorFilesWereRemoved, checkTmpIsEmpty, checkVideoFilesWereRemoved, cleanupTests, - createUser, + createMultipleServers, doubleFollow, - flushAndRunMultipleServers, - getAccountVideos, - getVideoChannelsList, - removeUser, - updateMyUser, - userLogin -} from '../../../../shared/extra-utils' -import { getMyUserInformation, ServerInfo, testImage, updateMyAvatar, uploadVideo } from '../../../../shared/extra-utils/index' -import { checkActorFilesWereRemoved, getAccount, getAccountsList } from '../../../../shared/extra-utils/users/accounts' -import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login' -import { User } from '../../../../shared/models/users' -import { VideoChannel } from '../../../../shared/models/videos' -import { waitJobs } from '../../../../shared/extra-utils/server/jobs' + PeerTubeServer, + saveVideoInServers, + setAccessTokensToServers, + testImage, + waitJobs +} from '@shared/extra-utils' +import { MyUser } from '@shared/models' const expect = chai.expect describe('Test users with multiple servers', function () { - let servers: ServerInfo[] = [] - let user: User + let servers: PeerTubeServer[] = [] + + let user: MyUser let userId: number + let videoUUID: string let userAccessToken: string let userAvatarFilename: string @@ -36,7 +32,7 @@ describe('Test users with multiple servers', function () { before(async function () { this.timeout(120_000) - servers = await flushAndRunMultipleServers(3) + servers = await createMultipleServers(3) // Get the access tokens await setAccessTokensToServers(servers) @@ -49,43 +45,31 @@ describe('Test users with multiple servers', function () { await doubleFollow(servers[1], servers[2]) // The root user of server 1 is propagated to servers 2 and 3 - await uploadVideo(servers[0].url, servers[0].accessToken, {}) + await servers[0].videos.upload() { - const user = { - username: 'user1', - password: 'password' - } - const res = await createUser({ - url: servers[0].url, - accessToken: servers[0].accessToken, - username: user.username, - password: user.password - }) - userId = res.body.user.id - userAccessToken = await userLogin(servers[0], user) + const username = 'user1' + const created = await servers[0].users.create({ username }) + userId = created.id + userAccessToken = await servers[0].login.getAccessToken(username) } { - const resVideo = await uploadVideo(servers[0].url, userAccessToken, {}) - videoUUID = resVideo.body.video.uuid - } + const { uuid } = await servers[0].videos.upload({ token: userAccessToken }) + videoUUID = uuid - await waitJobs(servers) + await waitJobs(servers) + + await saveVideoInServers(servers, videoUUID) + } }) it('Should be able to update my display name', async function () { this.timeout(10000) - await updateMyUser({ - url: servers[0].url, - accessToken: servers[0].accessToken, - displayName: 'my super display name' - }) - - const res = await getMyUserInformation(servers[0].url, servers[0].accessToken) - user = res.body + await servers[0].users.updateMe({ displayName: 'my super display name' }) + user = await servers[0].users.getMyInfo() expect(user.account.displayName).to.equal('my super display name') await waitJobs(servers) @@ -94,14 +78,9 @@ describe('Test users with multiple servers', function () { it('Should be able to update my description', async function () { this.timeout(10_000) - await updateMyUser({ - url: servers[0].url, - accessToken: servers[0].accessToken, - description: 'my super description updated' - }) + await servers[0].users.updateMe({ description: 'my super description updated' }) - const res = await getMyUserInformation(servers[0].url, servers[0].accessToken) - user = res.body + user = await servers[0].users.getMyInfo() expect(user.account.displayName).to.equal('my super display name') expect(user.account.description).to.equal('my super description updated') @@ -113,15 +92,9 @@ describe('Test users with multiple servers', function () { const fixture = 'avatar2.png' - await updateMyAvatar({ - url: servers[0].url, - accessToken: servers[0].accessToken, - fixture - }) - - const res = await getMyUserInformation(servers[0].url, servers[0].accessToken) - user = res.body + await servers[0].users.updateMyAvatar({ fixture }) + user = await servers[0].users.getMyInfo() userAvatarFilename = user.account.avatar.path await testImage(servers[0].url, 'avatar2-resized', userAvatarFilename, '.png') @@ -133,13 +106,12 @@ describe('Test users with multiple servers', function () { let createdAt: string | Date for (const server of servers) { - const resAccounts = await getAccountsList(server.url, '-createdAt') + const body = await server.accounts.list({ sort: '-createdAt' }) - const resList = resAccounts.body.data.find(a => a.name === 'root' && a.host === 'localhost:' + servers[0].port) as Account + const resList = body.data.find(a => a.name === 'root' && a.host === 'localhost:' + servers[0].port) expect(resList).not.to.be.undefined - const resAccount = await getAccount(server.url, resList.name + '@' + resList.host) - const account = resAccount.body as Account + const account = await server.accounts.get({ accountName: resList.name + '@' + resList.host }) if (!createdAt) createdAt = account.createdAt @@ -161,31 +133,29 @@ describe('Test users with multiple servers', function () { it('Should list account videos', async function () { for (const server of servers) { - const res = await getAccountVideos(server.url, server.accessToken, 'user1@localhost:' + servers[0].port, 0, 5) + const { total, data } = await server.videos.listByAccount({ handle: 'user1@localhost:' + servers[0].port }) - expect(res.body.total).to.equal(1) - expect(res.body.data).to.be.an('array') - expect(res.body.data).to.have.lengthOf(1) - expect(res.body.data[0].uuid).to.equal(videoUUID) + expect(total).to.equal(1) + expect(data).to.be.an('array') + expect(data).to.have.lengthOf(1) + expect(data[0].uuid).to.equal(videoUUID) } }) it('Should search through account videos', async function () { this.timeout(10_000) - const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: 'Kami no chikara' }) + const created = await servers[0].videos.upload({ token: userAccessToken, attributes: { name: 'Kami no chikara' } }) await waitJobs(servers) for (const server of servers) { - const res = await getAccountVideos(server.url, server.accessToken, 'user1@localhost:' + servers[0].port, 0, 5, undefined, { - search: 'Kami' - }) - - expect(res.body.total).to.equal(1) - expect(res.body.data).to.be.an('array') - expect(res.body.data).to.have.lengthOf(1) - expect(res.body.data[0].uuid).to.equal(resVideo.body.video.uuid) + const { total, data } = await server.videos.listByAccount({ handle: 'user1@localhost:' + servers[0].port, search: 'Kami' }) + + expect(total).to.equal(1) + expect(data).to.be.an('array') + expect(data).to.have.lengthOf(1) + expect(data[0].uuid).to.equal(created.uuid) } }) @@ -193,32 +163,28 @@ describe('Test users with multiple servers', function () { this.timeout(10_000) for (const server of servers) { - const resAccounts = await getAccountsList(server.url, '-createdAt') + const body = await server.accounts.list({ sort: '-createdAt' }) - const accountDeleted = resAccounts.body.data.find(a => a.name === 'user1' && a.host === 'localhost:' + servers[0].port) as Account + const accountDeleted = body.data.find(a => a.name === 'user1' && a.host === 'localhost:' + servers[0].port) expect(accountDeleted).not.to.be.undefined - const resVideoChannels = await getVideoChannelsList(server.url, 0, 10) - const videoChannelDeleted = resVideoChannels.body.data.find(a => { - return a.displayName === 'Main user1 channel' && a.host === 'localhost:' + servers[0].port - }) as VideoChannel + const { data } = await server.channels.list() + const videoChannelDeleted = data.find(a => a.displayName === 'Main user1 channel' && a.host === 'localhost:' + servers[0].port) expect(videoChannelDeleted).not.to.be.undefined } - await removeUser(servers[0].url, userId, servers[0].accessToken) + await servers[0].users.remove({ userId }) await waitJobs(servers) for (const server of servers) { - const resAccounts = await getAccountsList(server.url, '-createdAt') + const body = await server.accounts.list({ sort: '-createdAt' }) - const accountDeleted = resAccounts.body.data.find(a => a.name === 'user1' && a.host === 'localhost:' + servers[0].port) as Account + const accountDeleted = body.data.find(a => a.name === 'user1' && a.host === 'localhost:' + servers[0].port) expect(accountDeleted).to.be.undefined - const resVideoChannels = await getVideoChannelsList(server.url, 0, 10) - const videoChannelDeleted = resVideoChannels.body.data.find(a => { - return a.name === 'Main user1 channel' && a.host === 'localhost:' + servers[0].port - }) as VideoChannel + const { data } = await server.channels.list() + const videoChannelDeleted = data.find(a => a.name === 'Main user1 channel' && a.host === 'localhost:' + servers[0].port) expect(videoChannelDeleted).to.be.undefined } }) @@ -231,7 +197,7 @@ describe('Test users with multiple servers', function () { it('Should not have video files', async () => { for (const server of servers) { - await checkVideoFilesWereRemoved(videoUUID, server.internalServerNumber) + await checkVideoFilesWereRemoved({ server, video: server.store.videoDetails }) } }) diff --git a/server/tests/api/users/users-verification.ts b/server/tests/api/users/users-verification.ts index e0f2f2112..f54463359 100644 --- a/server/tests/api/users/users-verification.ts +++ b/server/tests/api/users/users-verification.ts @@ -1,30 +1,14 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ -import * as chai from 'chai' import 'mocha' -import { - cleanupTests, - flushAndRunServer, - getMyUserInformation, - getUserInformation, - login, - registerUser, - ServerInfo, - updateCustomSubConfig, - updateMyUser, - userLogin, - verifyEmail -} from '../../../../shared/extra-utils' -import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login' -import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email' -import { waitJobs } from '../../../../shared/extra-utils/server/jobs' -import { User } from '../../../../shared/models/users' -import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' +import * as chai from 'chai' +import { cleanupTests, createSingleServer, MockSmtpServer, PeerTubeServer, setAccessTokensToServers, waitJobs } from '@shared/extra-utils' +import { HttpStatusCode } from '@shared/models' const expect = chai.expect describe('Test users account verification', function () { - let server: ServerInfo + let server: PeerTubeServer let userId: number let userAccessToken: string let verificationString: string @@ -50,7 +34,7 @@ describe('Test users account verification', function () { port } } - server = await flushAndRunServer(1, overrideConfig) + server = await createSingleServer(1, overrideConfig) await setAccessTokensToServers([ server ]) }) @@ -58,15 +42,17 @@ describe('Test users account verification', function () { it('Should register user and send verification email if verification required', async function () { this.timeout(30000) - await updateCustomSubConfig(server.url, server.accessToken, { - signup: { - enabled: true, - requiresEmailVerification: true, - limit: 10 + await server.config.updateCustomSubConfig({ + newConfig: { + signup: { + enabled: true, + requiresEmailVerification: true, + limit: 10 + } } }) - await registerUser(server.url, user1.username, user1.password) + await server.users.register(user1) await waitJobs(server) expectedEmailsLength++ @@ -85,23 +71,23 @@ describe('Test users account verification', function () { userId = parseInt(userIdMatches[1], 10) - const resUserInfo = await getUserInformation(server.url, server.accessToken, userId) - expect(resUserInfo.body.emailVerified).to.be.false + const body = await server.users.get({ userId }) + expect(body.emailVerified).to.be.false }) it('Should not allow login for user with unverified email', async function () { - const resLogin = await login(server.url, server.client, user1, HttpStatusCode.BAD_REQUEST_400) - expect(resLogin.body.detail).to.contain('User email is not verified.') + const { detail } = await server.login.login({ user: user1, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) + expect(detail).to.contain('User email is not verified.') }) it('Should verify the user via email and allow login', async function () { - await verifyEmail(server.url, userId, verificationString) + await server.users.verifyEmail({ userId, verificationString }) - const res = await login(server.url, server.client, user1) - userAccessToken = res.body.access_token + const body = await server.login.login({ user: user1 }) + userAccessToken = body.access_token - const resUserVerified = await getUserInformation(server.url, server.accessToken, userId) - expect(resUserVerified.body.emailVerified).to.be.true + const user = await server.users.get({ userId }) + expect(user.emailVerified).to.be.true }) it('Should be able to change the user email', async function () { @@ -110,9 +96,8 @@ describe('Test users account verification', function () { let updateVerificationString: string { - await updateMyUser({ - url: server.url, - accessToken: userAccessToken, + await server.users.updateMe({ + token: userAccessToken, email: 'updated@example.com', currentPassword: user1.password }) @@ -128,19 +113,15 @@ describe('Test users account verification', function () { } { - const res = await getMyUserInformation(server.url, userAccessToken) - const me: User = res.body - + const me = await server.users.getMyInfo({ token: userAccessToken }) expect(me.email).to.equal('user_1@example.com') expect(me.pendingEmail).to.equal('updated@example.com') } { - await verifyEmail(server.url, userId, updateVerificationString, true) - - const res = await getMyUserInformation(server.url, userAccessToken) - const me: User = res.body + await server.users.verifyEmail({ userId, verificationString: updateVerificationString, isPendingEmail: true }) + const me = await server.users.getMyInfo({ token: userAccessToken }) expect(me.email).to.equal('updated@example.com') expect(me.pendingEmail).to.be.null } @@ -148,35 +129,39 @@ describe('Test users account verification', function () { it('Should register user not requiring email verification if setting not enabled', async function () { this.timeout(5000) - await updateCustomSubConfig(server.url, server.accessToken, { - signup: { - enabled: true, - requiresEmailVerification: false, - limit: 10 + await server.config.updateCustomSubConfig({ + newConfig: { + signup: { + enabled: true, + requiresEmailVerification: false, + limit: 10 + } } }) - await registerUser(server.url, user2.username, user2.password) + await server.users.register(user2) await waitJobs(server) expect(emails).to.have.lengthOf(expectedEmailsLength) - const accessToken = await userLogin(server, user2) + const accessToken = await server.login.getAccessToken(user2) - const resMyUserInfo = await getMyUserInformation(server.url, accessToken) - expect(resMyUserInfo.body.emailVerified).to.be.null + const user = await server.users.getMyInfo({ token: accessToken }) + expect(user.emailVerified).to.be.null }) it('Should allow login for user with unverified email when setting later enabled', async function () { - await updateCustomSubConfig(server.url, server.accessToken, { - signup: { - enabled: true, - requiresEmailVerification: true, - limit: 10 + await server.config.updateCustomSubConfig({ + newConfig: { + signup: { + enabled: true, + requiresEmailVerification: true, + limit: 10 + } } }) - await userLogin(server, user2) + await server.login.getAccessToken(user2) }) after(async function () { diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts index 87ba775f6..1419ae820 100644 --- a/server/tests/api/users/users.ts +++ b/server/tests/api/users/users.ts @@ -2,63 +2,24 @@ import 'mocha' import * as chai from 'chai' -import { AbuseState, AbuseUpdate, MyUser, User, UserRole, Video, VideoPlaylistType } from '@shared/models' -import { CustomConfig, OAuth2ErrorCode } from '@shared/models/server' -import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' import { - addVideoCommentThread, - blockUser, cleanupTests, - closeAllSequelize, - createUser, - deleteMe, - flushAndRunServer, - getAccountRatings, - getAdminAbusesList, - getBlacklistedVideosList, - getCustomConfig, - getMyUserInformation, - getMyUserVideoQuotaUsed, - getMyUserVideoRating, - getUserInformation, - getUsersList, - getUsersListPaginationAndSort, - getVideoChannel, - getVideosList, - installPlugin, + createSingleServer, killallServers, - login, makePutBodyRequest, - rateVideo, - registerUserWithChannel, - removeUser, - removeVideo, - reportAbuse, - reRunServer, - ServerInfo, - setTokenField, + PeerTubeServer, + setAccessTokensToServers, testImage, - unblockUser, - updateAbuse, - updateCustomSubConfig, - updateMyAvatar, - updateMyUser, - updateUser, - uploadVideo, - userLogin, waitJobs -} from '../../../../shared/extra-utils' -import { follow } from '../../../../shared/extra-utils/server/follows' -import { logout, refreshToken, setAccessTokensToServers } from '../../../../shared/extra-utils/users/login' -import { getMyVideos } from '../../../../shared/extra-utils/videos/videos' -import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model' +} from '@shared/extra-utils' +import { AbuseState, HttpStatusCode, OAuth2ErrorCode, UserAdminFlag, UserRole, Video, VideoPlaylistType } from '@shared/models' const expect = chai.expect describe('Test users', function () { - let server: ServerInfo - let accessToken: string - let accessTokenUser: string + let server: PeerTubeServer + let token: string + let userToken: string let videoId: number let userId: number const user = { @@ -69,7 +30,7 @@ describe('Test users', function () { before(async function () { this.timeout(30000) - server = await flushAndRunServer(1, { + server = await createSingleServer(1, { rates_limit: { login: { max: 30 @@ -79,7 +40,7 @@ describe('Test users', function () { await setAccessTokensToServers([ server ]) - await installPlugin({ url: server.url, accessToken: server.accessToken, npmName: 'peertube-theme-background-red' }) + await server.plugins.install({ npmName: 'peertube-theme-background-red' }) }) describe('OAuth client', function () { @@ -90,158 +51,156 @@ describe('Test users', function () { it('Should remove the last client') it('Should not login with an invalid client id', async function () { - const client = { id: 'client', secret: server.client.secret } - const res = await login(server.url, client, server.user, HttpStatusCode.BAD_REQUEST_400) + const client = { id: 'client', secret: server.store.client.secret } + const body = await server.login.login({ client, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) - expect(res.body.code).to.equal(OAuth2ErrorCode.INVALID_CLIENT) - expect(res.body.error).to.contain('client is invalid') - expect(res.body.type.startsWith('https://')).to.be.true - expect(res.body.type).to.contain(OAuth2ErrorCode.INVALID_CLIENT) + expect(body.code).to.equal(OAuth2ErrorCode.INVALID_CLIENT) + expect(body.error).to.contain('client is invalid') + expect(body.type.startsWith('https://')).to.be.true + expect(body.type).to.contain(OAuth2ErrorCode.INVALID_CLIENT) }) it('Should not login with an invalid client secret', async function () { - const client = { id: server.client.id, secret: 'coucou' } - const res = await login(server.url, client, server.user, HttpStatusCode.BAD_REQUEST_400) + const client = { id: server.store.client.id, secret: 'coucou' } + const body = await server.login.login({ client, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) - expect(res.body.code).to.equal(OAuth2ErrorCode.INVALID_CLIENT) - expect(res.body.error).to.contain('client is invalid') - expect(res.body.type.startsWith('https://')).to.be.true - expect(res.body.type).to.contain(OAuth2ErrorCode.INVALID_CLIENT) + expect(body.code).to.equal(OAuth2ErrorCode.INVALID_CLIENT) + expect(body.error).to.contain('client is invalid') + expect(body.type.startsWith('https://')).to.be.true + expect(body.type).to.contain(OAuth2ErrorCode.INVALID_CLIENT) }) }) describe('Login', function () { it('Should not login with an invalid username', async function () { - const user = { username: 'captain crochet', password: server.user.password } - const res = await login(server.url, server.client, user, HttpStatusCode.BAD_REQUEST_400) + const user = { username: 'captain crochet', password: server.store.user.password } + const body = await server.login.login({ user, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) - expect(res.body.code).to.equal(OAuth2ErrorCode.INVALID_GRANT) - expect(res.body.error).to.contain('credentials are invalid') - expect(res.body.type.startsWith('https://')).to.be.true - expect(res.body.type).to.contain(OAuth2ErrorCode.INVALID_GRANT) + expect(body.code).to.equal(OAuth2ErrorCode.INVALID_GRANT) + expect(body.error).to.contain('credentials are invalid') + expect(body.type.startsWith('https://')).to.be.true + expect(body.type).to.contain(OAuth2ErrorCode.INVALID_GRANT) }) it('Should not login with an invalid password', async function () { - const user = { username: server.user.username, password: 'mew_three' } - const res = await login(server.url, server.client, user, HttpStatusCode.BAD_REQUEST_400) + const user = { username: server.store.user.username, password: 'mew_three' } + const body = await server.login.login({ user, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) - expect(res.body.code).to.equal(OAuth2ErrorCode.INVALID_GRANT) - expect(res.body.error).to.contain('credentials are invalid') - expect(res.body.type.startsWith('https://')).to.be.true - expect(res.body.type).to.contain(OAuth2ErrorCode.INVALID_GRANT) + expect(body.code).to.equal(OAuth2ErrorCode.INVALID_GRANT) + expect(body.error).to.contain('credentials are invalid') + expect(body.type.startsWith('https://')).to.be.true + expect(body.type).to.contain(OAuth2ErrorCode.INVALID_GRANT) }) it('Should not be able to upload a video', async function () { - accessToken = 'my_super_token' + token = 'my_super_token' - const videoAttributes = {} - await uploadVideo(server.url, accessToken, videoAttributes, HttpStatusCode.UNAUTHORIZED_401) + await server.videos.upload({ token, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) }) it('Should not be able to follow', async function () { - accessToken = 'my_super_token' - await follow(server.url, [ 'http://example.com' ], accessToken, HttpStatusCode.UNAUTHORIZED_401) + token = 'my_super_token' + + await server.follows.follow({ + hosts: [ 'http://example.com' ], + token, + expectedStatus: HttpStatusCode.UNAUTHORIZED_401 + }) }) it('Should not be able to unfollow') it('Should be able to login', async function () { - const res = await login(server.url, server.client, server.user, HttpStatusCode.OK_200) + const body = await server.login.login({ expectedStatus: HttpStatusCode.OK_200 }) - accessToken = res.body.access_token + token = body.access_token }) it('Should be able to login with an insensitive username', async function () { - const user = { username: 'RoOt', password: server.user.password } - await login(server.url, server.client, user, HttpStatusCode.OK_200) + const user = { username: 'RoOt', password: server.store.user.password } + await server.login.login({ user, expectedStatus: HttpStatusCode.OK_200 }) - const user2 = { username: 'rOoT', password: server.user.password } - await login(server.url, server.client, user2, HttpStatusCode.OK_200) + const user2 = { username: 'rOoT', password: server.store.user.password } + await server.login.login({ user: user2, expectedStatus: HttpStatusCode.OK_200 }) - const user3 = { username: 'ROOt', password: server.user.password } - await login(server.url, server.client, user3, HttpStatusCode.OK_200) + const user3 = { username: 'ROOt', password: server.store.user.password } + await server.login.login({ user: user3, expectedStatus: HttpStatusCode.OK_200 }) }) }) describe('Upload', function () { it('Should upload the video with the correct token', async function () { - const videoAttributes = {} - await uploadVideo(server.url, accessToken, videoAttributes) - const res = await getVideosList(server.url) - const video = res.body.data[0] + await server.videos.upload({ token }) + const { data } = await server.videos.list() + const video = data[0] expect(video.account.name).to.equal('root') videoId = video.id }) it('Should upload the video again with the correct token', async function () { - const videoAttributes = {} - await uploadVideo(server.url, accessToken, videoAttributes) + await server.videos.upload({ token }) }) }) describe('Ratings', function () { it('Should retrieve a video rating', async function () { - await rateVideo(server.url, accessToken, videoId, 'like') - const res = await getMyUserVideoRating(server.url, accessToken, videoId) - const rating = res.body + await server.videos.rate({ id: videoId, rating: 'like' }) + const rating = await server.users.getMyRating({ token, videoId }) expect(rating.videoId).to.equal(videoId) expect(rating.rating).to.equal('like') }) it('Should retrieve ratings list', async function () { - await rateVideo(server.url, accessToken, videoId, 'like') + await server.videos.rate({ id: videoId, rating: 'like' }) - const res = await getAccountRatings(server.url, server.user.username, server.accessToken, null, HttpStatusCode.OK_200) - const ratings = res.body + const body = await server.accounts.listRatings({ accountName: server.store.user.username }) - expect(ratings.total).to.equal(1) - expect(ratings.data[0].video.id).to.equal(videoId) - expect(ratings.data[0].rating).to.equal('like') + expect(body.total).to.equal(1) + expect(body.data[0].video.id).to.equal(videoId) + expect(body.data[0].rating).to.equal('like') }) it('Should retrieve ratings list by rating type', async function () { { - const res = await getAccountRatings(server.url, server.user.username, server.accessToken, 'like') - const ratings = res.body - expect(ratings.data.length).to.equal(1) + const body = await server.accounts.listRatings({ accountName: server.store.user.username, rating: 'like' }) + expect(body.data.length).to.equal(1) } { - const res = await getAccountRatings(server.url, server.user.username, server.accessToken, 'dislike') - const ratings = res.body - expect(ratings.data.length).to.equal(0) + const body = await server.accounts.listRatings({ accountName: server.store.user.username, rating: 'dislike' }) + expect(body.data.length).to.equal(0) } }) }) describe('Remove video', function () { it('Should not be able to remove the video with an incorrect token', async function () { - await removeVideo(server.url, 'bad_token', videoId, HttpStatusCode.UNAUTHORIZED_401) + await server.videos.remove({ token: 'bad_token', id: videoId, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) }) it('Should not be able to remove the video with the token of another account') it('Should be able to remove the video with the correct token', async function () { - await removeVideo(server.url, accessToken, videoId) + await server.videos.remove({ token, id: videoId }) }) }) describe('Logout', function () { it('Should logout (revoke token)', async function () { - await logout(server.url, server.accessToken) + await server.login.logout({ token: server.accessToken }) }) it('Should not be able to get the user information', async function () { - await getMyUserInformation(server.url, server.accessToken, HttpStatusCode.UNAUTHORIZED_401) + await server.users.getMyInfo({ expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) }) it('Should not be able to upload a video', async function () { - await uploadVideo(server.url, server.accessToken, { name: 'video' }, HttpStatusCode.UNAUTHORIZED_401) + await server.videos.upload({ attributes: { name: 'video' }, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) }) it('Should not be able to rate a video', async function () { @@ -255,79 +214,70 @@ describe('Test users', function () { path: path + videoId, token: 'wrong token', fields: data, - statusCodeExpected: HttpStatusCode.UNAUTHORIZED_401 + expectedStatus: HttpStatusCode.UNAUTHORIZED_401 } await makePutBodyRequest(options) }) it('Should be able to login again', async function () { - const res = await login(server.url, server.client, server.user) - server.accessToken = res.body.access_token - server.refreshToken = res.body.refresh_token + const body = await server.login.login() + server.accessToken = body.access_token + server.refreshToken = body.refresh_token }) it('Should be able to get my user information again', async function () { - await getMyUserInformation(server.url, server.accessToken) + await server.users.getMyInfo() }) it('Should have an expired access token', async function () { this.timeout(15000) - await setTokenField(server.internalServerNumber, server.accessToken, 'accessTokenExpiresAt', new Date().toISOString()) - await setTokenField(server.internalServerNumber, server.accessToken, 'refreshTokenExpiresAt', new Date().toISOString()) + await server.sql.setTokenField(server.accessToken, 'accessTokenExpiresAt', new Date().toISOString()) + await server.sql.setTokenField(server.accessToken, 'refreshTokenExpiresAt', new Date().toISOString()) - killallServers([ server ]) - await reRunServer(server) + await killallServers([ server ]) + await server.run() - await getMyUserInformation(server.url, server.accessToken, 401) + await server.users.getMyInfo({ expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) }) it('Should not be able to refresh an access token with an expired refresh token', async function () { - await refreshToken(server, server.refreshToken, 400) + await server.login.refreshToken({ refreshToken: server.refreshToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) }) it('Should refresh the token', async function () { this.timeout(15000) const futureDate = new Date(new Date().getTime() + 1000 * 60).toISOString() - await setTokenField(server.internalServerNumber, server.accessToken, 'refreshTokenExpiresAt', futureDate) + await server.sql.setTokenField(server.accessToken, 'refreshTokenExpiresAt', futureDate) - killallServers([ server ]) - await reRunServer(server) + await killallServers([ server ]) + await server.run() - const res = await refreshToken(server, server.refreshToken) + const res = await server.login.refreshToken({ refreshToken: server.refreshToken }) server.accessToken = res.body.access_token server.refreshToken = res.body.refresh_token }) it('Should be able to get my user information again', async function () { - await getMyUserInformation(server.url, server.accessToken) + await server.users.getMyInfo() }) }) describe('Creating a user', function () { it('Should be able to create a new user', async function () { - await createUser({ - url: server.url, - accessToken: accessToken, - username: user.username, - password: user.password, - videoQuota: 2 * 1024 * 1024, - adminFlags: UserAdminFlag.BYPASS_VIDEO_AUTO_BLACKLIST - }) + await server.users.create({ ...user, videoQuota: 2 * 1024 * 1024, adminFlags: UserAdminFlag.BYPASS_VIDEO_AUTO_BLACKLIST }) }) it('Should be able to login with this user', async function () { - accessTokenUser = await userLogin(server, user) + userToken = await server.login.getAccessToken(user) }) it('Should be able to get user information', async function () { - const res1 = await getMyUserInformation(server.url, accessTokenUser) - const userMe: MyUser = res1.body + const userMe = await server.users.getMyInfo({ token: userToken }) - const res2 = await getUserInformation(server.url, server.accessToken, userMe.id, true) - const userGet: User = res2.body + const userGet = await server.users.get({ userId: userMe.id, withStats: true }) for (const user of [ userMe, userGet ]) { expect(user.username).to.equal('user_1') @@ -363,34 +313,28 @@ describe('Test users', function () { it('Should be able to upload a video with this user', async function () { this.timeout(10000) - const videoAttributes = { + const attributes = { name: 'super user video', fixture: 'video_short.webm' } - await uploadVideo(server.url, accessTokenUser, videoAttributes) + await server.videos.upload({ token: userToken, attributes }) }) it('Should have video quota updated', async function () { - const res = await getMyUserVideoQuotaUsed(server.url, accessTokenUser) - const data = res.body - - expect(data.videoQuotaUsed).to.equal(218910) - - const resUsers = await getUsersList(server.url, server.accessToken) + const quota = await server.users.getMyQuotaUsed({ token: userToken }) + expect(quota.videoQuotaUsed).to.equal(218910) - const users: User[] = resUsers.body.data - const tmpUser = users.find(u => u.username === user.username) + const { data } = await server.users.list() + const tmpUser = data.find(u => u.username === user.username) expect(tmpUser.videoQuotaUsed).to.equal(218910) }) it('Should be able to list my videos', async function () { - const res = await getMyVideos(server.url, accessTokenUser, 0, 5) - expect(res.body.total).to.equal(1) + const { total, data } = await server.videos.listMyVideos({ token: userToken }) + expect(total).to.equal(1) + expect(data).to.have.lengthOf(1) - const videos = res.body.data - expect(videos).to.have.lengthOf(1) - - const video: Video = videos[0] + const video: Video = data[0] expect(video.name).to.equal('super user video') expect(video.thumbnailPath).to.not.be.null expect(video.previewPath).to.not.be.null @@ -398,19 +342,15 @@ describe('Test users', function () { it('Should be able to search in my videos', async function () { { - const res = await getMyVideos(server.url, accessTokenUser, 0, 5, '-createdAt', 'user video') - expect(res.body.total).to.equal(1) - - const videos = res.body.data - expect(videos).to.have.lengthOf(1) + const { total, data } = await server.videos.listMyVideos({ token: userToken, sort: '-createdAt', search: 'user video' }) + expect(total).to.equal(1) + expect(data).to.have.lengthOf(1) } { - const res = await getMyVideos(server.url, accessTokenUser, 0, 5, '-createdAt', 'toto') - expect(res.body.total).to.equal(0) - - const videos = res.body.data - expect(videos).to.have.lengthOf(0) + const { total, data } = await server.videos.listMyVideos({ token: userToken, sort: '-createdAt', search: 'toto' }) + expect(total).to.equal(0) + expect(data).to.have.lengthOf(0) } }) @@ -418,28 +358,25 @@ describe('Test users', function () { this.timeout(60000) { - const res = await getCustomConfig(server.url, server.accessToken) - const config = res.body as CustomConfig + const config = await server.config.getCustomConfig() config.transcoding.webtorrent.enabled = false config.transcoding.hls.enabled = true config.transcoding.enabled = true - await updateCustomSubConfig(server.url, server.accessToken, config) + await server.config.updateCustomSubConfig({ newConfig: config }) } { - const videoAttributes = { + const attributes = { name: 'super user video 2', fixture: 'video_short.webm' } - await uploadVideo(server.url, accessTokenUser, videoAttributes) + await server.videos.upload({ token: userToken, attributes }) await waitJobs([ server ]) } { - const res = await getMyUserVideoQuotaUsed(server.url, accessTokenUser) - const data = res.body - + const data = await server.users.getMyQuotaUsed({ token: userToken }) expect(data.videoQuotaUsed).to.be.greaterThan(220000) } }) @@ -448,21 +385,18 @@ describe('Test users', function () { describe('Users listing', function () { it('Should list all the users', async function () { - const res = await getUsersList(server.url, server.accessToken) - const result = res.body - const total = result.total - const users = result.data + const { data, total } = await server.users.list() expect(total).to.equal(2) - expect(users).to.be.an('array') - expect(users.length).to.equal(2) + expect(data).to.be.an('array') + expect(data.length).to.equal(2) - const user = users[0] + const user = data[0] expect(user.username).to.equal('user_1') expect(user.email).to.equal('user_1@example.com') expect(user.nsfwPolicy).to.equal('display') - const rootUser = users[1] + const rootUser = data[1] expect(rootUser.username).to.equal('root') expect(rootUser.email).to.equal('admin' + server.internalServerNumber + '@example.com') expect(user.nsfwPolicy).to.equal('display') @@ -474,16 +408,12 @@ describe('Test users', function () { }) it('Should list only the first user by username asc', async function () { - const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 1, 'username') - - const result = res.body - const total = result.total - const users = result.data + const { total, data } = await server.users.list({ start: 0, count: 1, sort: 'username' }) expect(total).to.equal(2) - expect(users.length).to.equal(1) + expect(data.length).to.equal(1) - const user = users[0] + const user = data[0] expect(user.username).to.equal('root') expect(user.email).to.equal('admin' + server.internalServerNumber + '@example.com') expect(user.roleLabel).to.equal('Administrator') @@ -491,111 +421,90 @@ describe('Test users', function () { }) it('Should list only the first user by username desc', async function () { - const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 1, '-username') - const result = res.body - const total = result.total - const users = result.data + const { total, data } = await server.users.list({ start: 0, count: 1, sort: '-username' }) expect(total).to.equal(2) - expect(users.length).to.equal(1) + expect(data.length).to.equal(1) - const user = users[0] + const user = data[0] expect(user.username).to.equal('user_1') expect(user.email).to.equal('user_1@example.com') expect(user.nsfwPolicy).to.equal('display') }) it('Should list only the second user by createdAt desc', async function () { - const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 1, '-createdAt') - const result = res.body - const total = result.total - const users = result.data - + const { data, total } = await server.users.list({ start: 0, count: 1, sort: '-createdAt' }) expect(total).to.equal(2) - expect(users.length).to.equal(1) - const user = users[0] + expect(data.length).to.equal(1) + + const user = data[0] expect(user.username).to.equal('user_1') expect(user.email).to.equal('user_1@example.com') expect(user.nsfwPolicy).to.equal('display') }) it('Should list all the users by createdAt asc', async function () { - const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt') - const result = res.body - const total = result.total - const users = result.data + const { data, total } = await server.users.list({ start: 0, count: 2, sort: 'createdAt' }) expect(total).to.equal(2) - expect(users.length).to.equal(2) + expect(data.length).to.equal(2) - expect(users[0].username).to.equal('root') - expect(users[0].email).to.equal('admin' + server.internalServerNumber + '@example.com') - expect(users[0].nsfwPolicy).to.equal('display') + expect(data[0].username).to.equal('root') + expect(data[0].email).to.equal('admin' + server.internalServerNumber + '@example.com') + expect(data[0].nsfwPolicy).to.equal('display') - expect(users[1].username).to.equal('user_1') - expect(users[1].email).to.equal('user_1@example.com') - expect(users[1].nsfwPolicy).to.equal('display') + expect(data[1].username).to.equal('user_1') + expect(data[1].email).to.equal('user_1@example.com') + expect(data[1].nsfwPolicy).to.equal('display') }) it('Should search user by username', async function () { - const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt', 'oot') - const users = res.body.data as User[] - - expect(res.body.total).to.equal(1) - expect(users.length).to.equal(1) - - expect(users[0].username).to.equal('root') + const { data, total } = await server.users.list({ start: 0, count: 2, sort: 'createdAt', search: 'oot' }) + expect(total).to.equal(1) + expect(data.length).to.equal(1) + expect(data[0].username).to.equal('root') }) it('Should search user by email', async function () { { - const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt', 'r_1@exam') - const users = res.body.data as User[] - - expect(res.body.total).to.equal(1) - expect(users.length).to.equal(1) - - expect(users[0].username).to.equal('user_1') - expect(users[0].email).to.equal('user_1@example.com') + const { total, data } = await server.users.list({ start: 0, count: 2, sort: 'createdAt', search: 'r_1@exam' }) + expect(total).to.equal(1) + expect(data.length).to.equal(1) + expect(data[0].username).to.equal('user_1') + expect(data[0].email).to.equal('user_1@example.com') } { - const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt', 'example') - const users = res.body.data as User[] - - expect(res.body.total).to.equal(2) - expect(users.length).to.equal(2) - - expect(users[0].username).to.equal('root') - expect(users[1].username).to.equal('user_1') + const { total, data } = await server.users.list({ start: 0, count: 2, sort: 'createdAt', search: 'example' }) + expect(total).to.equal(2) + expect(data.length).to.equal(2) + expect(data[0].username).to.equal('root') + expect(data[1].username).to.equal('user_1') } }) }) describe('Update my account', function () { + it('Should update my password', async function () { - await updateMyUser({ - url: server.url, - accessToken: accessTokenUser, + await server.users.updateMe({ + token: userToken, currentPassword: 'super password', password: 'new password' }) user.password = 'new password' - await userLogin(server, user, HttpStatusCode.OK_200) + await server.login.login({ user }) }) it('Should be able to change the NSFW display attribute', async function () { - await updateMyUser({ - url: server.url, - accessToken: accessTokenUser, + await server.users.updateMe({ + token: userToken, nsfwPolicy: 'do_not_list' }) - const res = await getMyUserInformation(server.url, accessTokenUser) - const user = res.body - + const user = await server.users.getMyInfo({ token: userToken }) expect(user.username).to.equal('user_1') expect(user.email).to.equal('user_1@example.com') expect(user.nsfwPolicy).to.equal('do_not_list') @@ -606,42 +515,33 @@ describe('Test users', function () { }) it('Should be able to change the autoPlayVideo attribute', async function () { - await updateMyUser({ - url: server.url, - accessToken: accessTokenUser, + await server.users.updateMe({ + token: userToken, autoPlayVideo: false }) - const res = await getMyUserInformation(server.url, accessTokenUser) - const user = res.body - + const user = await server.users.getMyInfo({ token: userToken }) expect(user.autoPlayVideo).to.be.false }) it('Should be able to change the autoPlayNextVideo attribute', async function () { - await updateMyUser({ - url: server.url, - accessToken: accessTokenUser, + await server.users.updateMe({ + token: userToken, autoPlayNextVideo: true }) - const res = await getMyUserInformation(server.url, accessTokenUser) - const user = res.body - + const user = await server.users.getMyInfo({ token: userToken }) expect(user.autoPlayNextVideo).to.be.true }) it('Should be able to change the email attribute', async function () { - await updateMyUser({ - url: server.url, - accessToken: accessTokenUser, + await server.users.updateMe({ + token: userToken, currentPassword: 'new password', email: 'updated@example.com' }) - const res = await getMyUserInformation(server.url, accessTokenUser) - const user = res.body - + const user = await server.users.getMyInfo({ token: userToken }) expect(user.username).to.equal('user_1') expect(user.email).to.equal('updated@example.com') expect(user.nsfwPolicy).to.equal('do_not_list') @@ -654,15 +554,9 @@ describe('Test users', function () { it('Should be able to update my avatar with a gif', async function () { const fixture = 'avatar.gif' - await updateMyAvatar({ - url: server.url, - accessToken: accessTokenUser, - fixture - }) - - const res = await getMyUserInformation(server.url, accessTokenUser) - const user = res.body + await server.users.updateMyAvatar({ token: userToken, fixture }) + const user = await server.users.getMyInfo({ token: userToken }) await testImage(server.url, 'avatar-resized', user.account.avatar.path, '.gif') }) @@ -670,29 +564,17 @@ describe('Test users', function () { for (const extension of [ '.png', '.gif' ]) { const fixture = 'avatar' + extension - await updateMyAvatar({ - url: server.url, - accessToken: accessTokenUser, - fixture - }) - - const res = await getMyUserInformation(server.url, accessTokenUser) - const user = res.body + await server.users.updateMyAvatar({ token: userToken, fixture }) + const user = await server.users.getMyInfo({ token: userToken }) await testImage(server.url, 'avatar-resized', user.account.avatar.path, extension) } }) it('Should be able to update my display name', async function () { - await updateMyUser({ - url: server.url, - accessToken: accessTokenUser, - displayName: 'new display name' - }) - - const res = await getMyUserInformation(server.url, accessTokenUser) - const user = res.body + await server.users.updateMe({ token: userToken, displayName: 'new display name' }) + const user = await server.users.getMyInfo({ token: userToken }) expect(user.username).to.equal('user_1') expect(user.email).to.equal('updated@example.com') expect(user.nsfwPolicy).to.equal('do_not_list') @@ -703,15 +585,9 @@ describe('Test users', function () { }) it('Should be able to update my description', async function () { - await updateMyUser({ - url: server.url, - accessToken: accessTokenUser, - description: 'my super description updated' - }) - - const res = await getMyUserInformation(server.url, accessTokenUser) - const user: User = res.body + await server.users.updateMe({ token: userToken, description: 'my super description updated' }) + const user = await server.users.getMyInfo({ token: userToken }) expect(user.username).to.equal('user_1') expect(user.email).to.equal('updated@example.com') expect(user.nsfwPolicy).to.equal('do_not_list') @@ -725,30 +601,21 @@ describe('Test users', function () { it('Should be able to update my theme', async function () { for (const theme of [ 'background-red', 'default', 'instance-default' ]) { - await updateMyUser({ - url: server.url, - accessToken: accessTokenUser, - theme - }) + await server.users.updateMe({ token: userToken, theme }) - const res = await getMyUserInformation(server.url, accessTokenUser) - const body: User = res.body - - expect(body.theme).to.equal(theme) + const user = await server.users.getMyInfo({ token: userToken }) + expect(user.theme).to.equal(theme) } }) it('Should be able to update my modal preferences', async function () { - await updateMyUser({ - url: server.url, - accessToken: accessTokenUser, + await server.users.updateMe({ + token: userToken, noInstanceConfigWarningModal: true, noWelcomeModal: true }) - const res = await getMyUserInformation(server.url, accessTokenUser) - const user: User = res.body - + const user = await server.users.getMyInfo({ token: userToken }) expect(user.noWelcomeModal).to.be.true expect(user.noInstanceConfigWarningModal).to.be.true }) @@ -756,10 +623,9 @@ describe('Test users', function () { describe('Updating another user', function () { it('Should be able to update another user', async function () { - await updateUser({ - url: server.url, + await server.users.update({ userId, - accessToken, + token, email: 'updated2@example.com', emailVerified: true, videoQuota: 42, @@ -768,8 +634,7 @@ describe('Test users', function () { pluginAuth: 'toto' }) - const res = await getUserInformation(server.url, accessToken, userId) - const user = res.body as User + const user = await server.users.get({ token, userId }) expect(user.username).to.equal('user_1') expect(user.email).to.equal('updated2@example.com') @@ -783,57 +648,50 @@ describe('Test users', function () { }) it('Should reset the auth plugin', async function () { - await updateUser({ url: server.url, userId, accessToken, pluginAuth: null }) + await server.users.update({ userId, token, pluginAuth: null }) - const res = await getUserInformation(server.url, accessToken, userId) - const user = res.body as User + const user = await server.users.get({ token, userId }) expect(user.pluginAuth).to.be.null }) it('Should have removed the user token', async function () { - await getMyUserVideoQuotaUsed(server.url, accessTokenUser, HttpStatusCode.UNAUTHORIZED_401) + await server.users.getMyQuotaUsed({ token: userToken, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) - accessTokenUser = await userLogin(server, user) + userToken = await server.login.getAccessToken(user) }) it('Should be able to update another user password', async function () { - await updateUser({ - url: server.url, - userId, - accessToken, - password: 'password updated' - }) + await server.users.update({ userId, token, password: 'password updated' }) - await getMyUserVideoQuotaUsed(server.url, accessTokenUser, HttpStatusCode.UNAUTHORIZED_401) + await server.users.getMyQuotaUsed({ token: userToken, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) - await userLogin(server, user, HttpStatusCode.BAD_REQUEST_400) + await server.login.login({ user, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) user.password = 'password updated' - accessTokenUser = await userLogin(server, user) + userToken = await server.login.getAccessToken(user) }) }) describe('Video blacklists', function () { it('Should be able to list video blacklist by a moderator', async function () { - await getBlacklistedVideosList({ url: server.url, token: accessTokenUser }) + await server.blacklist.list({ token: userToken }) }) }) describe('Remove a user', function () { it('Should be able to remove this user', async function () { - await removeUser(server.url, userId, accessToken) + await server.users.remove({ userId, token }) }) it('Should not be able to login with this user', async function () { - await userLogin(server, user, HttpStatusCode.BAD_REQUEST_400) + await server.login.login({ user, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) }) it('Should not have videos of this user', async function () { - const res = await getVideosList(server.url) - - expect(res.body.total).to.equal(1) + const { data, total } = await server.videos.list() + expect(total).to.equal(1) - const video = res.body.data[0] + const video = data[0] expect(video.account.name).to.equal('root') }) }) @@ -845,7 +703,7 @@ describe('Test users', function () { const user = { displayName: 'super user 15', username: 'user_15', password: 'my super password' } const channel = { name: 'my_user_15_channel', displayName: 'my channel rocks' } - await registerUserWithChannel({ url: server.url, user, channel }) + await server.users.register({ ...user, channel }) }) it('Should be able to login with this registered user', async function () { @@ -854,40 +712,36 @@ describe('Test users', function () { password: 'my super password' } - user15AccessToken = await userLogin(server, user15) + user15AccessToken = await server.login.getAccessToken(user15) }) it('Should have the correct display name', async function () { - const res = await getMyUserInformation(server.url, user15AccessToken) - const user: User = res.body - + const user = await server.users.getMyInfo({ token: user15AccessToken }) expect(user.account.displayName).to.equal('super user 15') }) it('Should have the correct video quota', async function () { - const res = await getMyUserInformation(server.url, user15AccessToken) - const user = res.body - + const user = await server.users.getMyInfo({ token: user15AccessToken }) expect(user.videoQuota).to.equal(5 * 1024 * 1024) }) it('Should have created the channel', async function () { - const res = await getVideoChannel(server.url, 'my_user_15_channel') + const { displayName } = await server.channels.get({ channelName: 'my_user_15_channel' }) - expect(res.body.displayName).to.equal('my channel rocks') + expect(displayName).to.equal('my channel rocks') }) it('Should remove me', async function () { { - const res = await getUsersList(server.url, server.accessToken) - expect(res.body.data.find(u => u.username === 'user_15')).to.not.be.undefined + const { data } = await server.users.list() + expect(data.find(u => u.username === 'user_15')).to.not.be.undefined } - await deleteMe(server.url, user15AccessToken) + await server.users.deleteMe({ token: user15AccessToken }) { - const res = await getUsersList(server.url, server.accessToken) - expect(res.body.data.find(u => u.username === 'user_15')).to.be.undefined + const { data } = await server.users.list() + expect(data.find(u => u.username === 'user_15')).to.be.undefined } }) }) @@ -901,49 +755,40 @@ describe('Test users', function () { } it('Should block a user', async function () { - const resUser = await createUser({ - url: server.url, - accessToken: server.accessToken, - username: user16.username, - password: user16.password - }) - user16Id = resUser.body.user.id + const user = await server.users.create({ ...user16 }) + user16Id = user.id - user16AccessToken = await userLogin(server, user16) + user16AccessToken = await server.login.getAccessToken(user16) - await getMyUserInformation(server.url, user16AccessToken, HttpStatusCode.OK_200) - await blockUser(server.url, user16Id, server.accessToken) + await server.users.getMyInfo({ token: user16AccessToken, expectedStatus: HttpStatusCode.OK_200 }) + await server.users.banUser({ userId: user16Id }) - await getMyUserInformation(server.url, user16AccessToken, HttpStatusCode.UNAUTHORIZED_401) - await userLogin(server, user16, HttpStatusCode.BAD_REQUEST_400) + await server.users.getMyInfo({ token: user16AccessToken, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) + await server.login.login({ user: user16, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) }) it('Should search user by banned status', async function () { { - const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt', undefined, true) - const users = res.body.data as User[] + const { data, total } = await server.users.list({ start: 0, count: 2, sort: 'createdAt', blocked: true }) + expect(total).to.equal(1) + expect(data.length).to.equal(1) - expect(res.body.total).to.equal(1) - expect(users.length).to.equal(1) - - expect(users[0].username).to.equal(user16.username) + expect(data[0].username).to.equal(user16.username) } { - const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt', undefined, false) - const users = res.body.data as User[] - - expect(res.body.total).to.equal(1) - expect(users.length).to.equal(1) + const { data, total } = await server.users.list({ start: 0, count: 2, sort: 'createdAt', blocked: false }) + expect(total).to.equal(1) + expect(data.length).to.equal(1) - expect(users[0].username).to.not.equal(user16.username) + expect(data[0].username).to.not.equal(user16.username) } }) it('Should unblock a user', async function () { - await unblockUser(server.url, user16Id, server.accessToken) - user16AccessToken = await userLogin(server, user16) - await getMyUserInformation(server.url, user16AccessToken, HttpStatusCode.OK_200) + await server.users.unbanUser({ userId: user16Id }) + user16AccessToken = await server.login.getAccessToken(user16) + await server.users.getMyInfo({ token: user16AccessToken, expectedStatus: HttpStatusCode.OK_200 }) }) }) @@ -956,19 +801,12 @@ describe('Test users', function () { username: 'user_17', password: 'my super password' } - const resUser = await createUser({ - url: server.url, - accessToken: server.accessToken, - username: user17.username, - password: user17.password - }) + const created = await server.users.create({ ...user17 }) - user17Id = resUser.body.user.id - user17AccessToken = await userLogin(server, user17) - - const res = await getUserInformation(server.url, server.accessToken, user17Id, true) - const user: User = res.body + user17Id = created.id + user17AccessToken = await server.login.getAccessToken(user17) + const user = await server.users.get({ userId: user17Id, withStats: true }) expect(user.videosCount).to.equal(0) expect(user.videoCommentsCount).to.equal(0) expect(user.abusesCount).to.equal(0) @@ -977,54 +815,43 @@ describe('Test users', function () { }) it('Should report correct videos count', async function () { - const videoAttributes = { - name: 'video to test user stats' - } - await uploadVideo(server.url, user17AccessToken, videoAttributes) - const res1 = await getVideosList(server.url) - videoId = res1.body.data.find(video => video.name === videoAttributes.name).id + const attributes = { name: 'video to test user stats' } + await server.videos.upload({ token: user17AccessToken, attributes }) - const res2 = await getUserInformation(server.url, server.accessToken, user17Id, true) - const user: User = res2.body + const { data } = await server.videos.list() + videoId = data.find(video => video.name === attributes.name).id + const user = await server.users.get({ userId: user17Id, withStats: true }) expect(user.videosCount).to.equal(1) }) it('Should report correct video comments for user', async function () { const text = 'super comment' - await addVideoCommentThread(server.url, user17AccessToken, videoId, text) - - const res = await getUserInformation(server.url, server.accessToken, user17Id, true) - const user: User = res.body + await server.comments.createThread({ token: user17AccessToken, videoId, text }) + const user = await server.users.get({ userId: user17Id, withStats: true }) expect(user.videoCommentsCount).to.equal(1) }) it('Should report correct abuses counts', async function () { const reason = 'my super bad reason' - await reportAbuse({ url: server.url, token: user17AccessToken, videoId, reason }) - - const res1 = await getAdminAbusesList({ url: server.url, token: server.accessToken }) - const abuseId = res1.body.data[0].id + await server.abuses.report({ token: user17AccessToken, videoId, reason }) - const res2 = await getUserInformation(server.url, server.accessToken, user17Id, true) - const user2: User = res2.body + const body1 = await server.abuses.getAdminList() + const abuseId = body1.data[0].id + const user2 = await server.users.get({ userId: user17Id, withStats: true }) expect(user2.abusesCount).to.equal(1) // number of incriminations expect(user2.abusesCreatedCount).to.equal(1) // number of reports created - const body: AbuseUpdate = { state: AbuseState.ACCEPTED } - await updateAbuse(server.url, server.accessToken, abuseId, body) - - const res3 = await getUserInformation(server.url, server.accessToken, user17Id, true) - const user3: User = res3.body + await server.abuses.update({ abuseId, body: { state: AbuseState.ACCEPTED } }) + const user3 = await server.users.get({ userId: user17Id, withStats: true }) expect(user3.abusesAcceptedCount).to.equal(1) // number of reports created accepted }) }) after(async function () { - await closeAllSequelize([ server ]) await cleanupTests([ server ]) }) }) diff --git a/server/tests/api/videos/audio-only.ts b/server/tests/api/videos/audio-only.ts index 7ddbd5cd9..7fac6e738 100644 --- a/server/tests/api/videos/audio-only.ts +++ b/server/tests/api/videos/audio-only.ts @@ -2,26 +2,16 @@ import 'mocha' import * as chai from 'chai' -import { join } from 'path' import { getAudioStream, getVideoStreamSize } from '@server/helpers/ffprobe-utils' -import { - buildServerDirectory, - cleanupTests, - doubleFollow, - flushAndRunMultipleServers, - getVideo, - ServerInfo, - setAccessTokensToServers, - uploadVideo, - waitJobs -} from '../../../../shared/extra-utils' -import { VideoDetails } from '../../../../shared/models/videos' +import { cleanupTests, createMultipleServers, doubleFollow, PeerTubeServer, setAccessTokensToServers, waitJobs } from '@shared/extra-utils' const expect = chai.expect describe('Test audio only video transcoding', function () { - let servers: ServerInfo[] = [] + let servers: PeerTubeServer[] = [] let videoUUID: string + let webtorrentAudioFileUrl: string + let fragmentedAudioFileUrl: string before(async function () { this.timeout(120000) @@ -47,7 +37,7 @@ describe('Test audio only video transcoding', function () { } } } - servers = await flushAndRunMultipleServers(2, configOverride) + servers = await createMultipleServers(2, configOverride) // Get the access tokens await setAccessTokensToServers(servers) @@ -59,15 +49,13 @@ describe('Test audio only video transcoding', function () { it('Should upload a video and transcode it', async function () { this.timeout(120000) - const resUpload = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'audio only' }) - videoUUID = resUpload.body.video.uuid + const { uuid } = await servers[0].videos.upload({ attributes: { name: 'audio only' } }) + videoUUID = uuid await waitJobs(servers) for (const server of servers) { - const res = await getVideo(server.url, videoUUID) - const video: VideoDetails = res.body - + const video = await server.videos.get({ id: videoUUID }) expect(video.streamingPlaylists).to.have.lengthOf(1) for (const files of [ video.files, video.streamingPlaylists[0].files ]) { @@ -76,13 +64,18 @@ describe('Test audio only video transcoding', function () { expect(files[1].resolution.id).to.equal(240) expect(files[2].resolution.id).to.equal(0) } + + if (server.serverNumber === 1) { + webtorrentAudioFileUrl = video.files[2].fileUrl + fragmentedAudioFileUrl = video.streamingPlaylists[0].files[2].fileUrl + } } }) it('0p transcoded video should not have video', async function () { const paths = [ - buildServerDirectory(servers[0], join('videos', videoUUID + '-0.mp4')), - buildServerDirectory(servers[0], join('streaming-playlists', 'hls', videoUUID, videoUUID + '-0-fragmented.mp4')) + servers[0].servers.buildWebTorrentFilePath(webtorrentAudioFileUrl), + servers[0].servers.buildFragmentedFilePath(videoUUID, fragmentedAudioFileUrl) ] for (const path of paths) { diff --git a/server/tests/api/videos/multiple-servers.ts b/server/tests/api/videos/multiple-servers.ts index a8c8a889b..f9220e4b3 100644 --- a/server/tests/api/videos/multiple-servers.ts +++ b/server/tests/api/videos/multiple-servers.ts @@ -3,49 +3,29 @@ import 'mocha' import * as chai from 'chai' import * as request from 'supertest' -import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' import { - addVideoChannel, buildAbsoluteFixturePath, checkTmpIsEmpty, checkVideoFilesWereRemoved, cleanupTests, completeVideoCheck, - createUser, + createMultipleServers, dateIsValid, doubleFollow, - flushAndRunMultipleServers, - getLocalVideos, - getVideo, - getVideoChannelsList, - getVideosList, - rateVideo, - removeVideo, - ServerInfo, + PeerTubeServer, + saveVideoInServers, setAccessTokensToServers, testImage, - updateVideo, - uploadVideo, - userLogin, - viewVideo, wait, + waitJobs, webtorrentAdd -} from '../../../../shared/extra-utils' -import { waitJobs } from '../../../../shared/extra-utils/server/jobs' -import { - addVideoCommentReply, - addVideoCommentThread, - deleteVideoComment, - findCommentId, - getVideoCommentThreads, - getVideoThreadComments -} from '../../../../shared/extra-utils/videos/video-comments' -import { VideoComment, VideoCommentThreadTree, VideoPrivacy } from '../../../../shared/models/videos' +} from '@shared/extra-utils' +import { HttpStatusCode, VideoCommentThreadTree, VideoPrivacy } from '@shared/models' const expect = chai.expect describe('Test multiple servers', function () { - let servers: ServerInfo[] = [] + let servers: PeerTubeServer[] = [] const toRemove = [] let videoUUID = '' let videoChannelId: number @@ -53,7 +33,7 @@ describe('Test multiple servers', function () { before(async function () { this.timeout(120000) - servers = await flushAndRunMultipleServers(3) + servers = await createMultipleServers(3) // Get the access tokens await setAccessTokensToServers(servers) @@ -64,9 +44,9 @@ describe('Test multiple servers', function () { displayName: 'my channel', description: 'super channel' } - await addVideoChannel(servers[0].url, servers[0].accessToken, videoChannel) - const channelRes = await getVideoChannelsList(servers[0].url, 0, 1) - videoChannelId = channelRes.body.data[0].id + await servers[0].channels.create({ attributes: videoChannel }) + const { data } = await servers[0].channels.list({ start: 0, count: 1 }) + videoChannelId = data[0].id } // Server 1 and server 2 follow each other @@ -79,10 +59,9 @@ describe('Test multiple servers', function () { it('Should not have videos for all servers', async function () { for (const server of servers) { - const res = await getVideosList(server.url) - const videos = res.body.data - expect(videos).to.be.an('array') - expect(videos.length).to.equal(0) + const { data } = await server.videos.list() + expect(data).to.be.an('array') + expect(data.length).to.equal(0) } }) @@ -90,7 +69,7 @@ describe('Test multiple servers', function () { it('Should upload the video on server 1 and propagate on each server', async function () { this.timeout(25000) - const videoAttributes = { + const attributes = { name: 'my super name for server 1', category: 5, licence: 4, @@ -103,7 +82,7 @@ describe('Test multiple servers', function () { channelId: videoChannelId, fixture: 'video_short1.webm' } - await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes) + await servers[0].videos.upload({ attributes }) await waitJobs(servers) @@ -146,14 +125,13 @@ describe('Test multiple servers', function () { ] } - const res = await getVideosList(server.url) - const videos = res.body.data - expect(videos).to.be.an('array') - expect(videos.length).to.equal(1) - const video = videos[0] + const { data } = await server.videos.list() + expect(data).to.be.an('array') + expect(data.length).to.equal(1) + const video = data[0] - await completeVideoCheck(server.url, video, checkAttributes) - publishedAt = video.publishedAt + await completeVideoCheck(server, video, checkAttributes) + publishedAt = video.publishedAt as string } }) @@ -164,10 +142,10 @@ describe('Test multiple servers', function () { username: 'user1', password: 'super_password' } - await createUser({ url: servers[1].url, accessToken: servers[1].accessToken, username: user.username, password: user.password }) - const userAccessToken = await userLogin(servers[1], user) + await servers[1].users.create({ username: user.username, password: user.password }) + const userAccessToken = await servers[1].login.getAccessToken(user) - const videoAttributes = { + const attributes = { name: 'my super name for server 2', category: 4, licence: 3, @@ -180,7 +158,7 @@ describe('Test multiple servers', function () { thumbnailfile: 'thumbnail.jpg', previewfile: 'preview.jpg' } - await uploadVideo(servers[1].url, userAccessToken, videoAttributes, HttpStatusCode.OK_200, 'resumable') + await servers[1].videos.upload({ token: userAccessToken, attributes, mode: 'resumable' }) // Transcoding await waitJobs(servers) @@ -235,65 +213,67 @@ describe('Test multiple servers', function () { previewfile: 'preview' } - const res = await getVideosList(server.url) - const videos = res.body.data - expect(videos).to.be.an('array') - expect(videos.length).to.equal(2) - const video = videos[1] + const { data } = await server.videos.list() + expect(data).to.be.an('array') + expect(data.length).to.equal(2) + const video = data[1] - await completeVideoCheck(server.url, video, checkAttributes) + await completeVideoCheck(server, video, checkAttributes) } }) it('Should upload two videos on server 3 and propagate on each server', async function () { this.timeout(45000) - const videoAttributes1 = { - name: 'my super name for server 3', - category: 6, - licence: 5, - language: 'de', - nsfw: true, - description: 'my super description for server 3', - support: 'my super support text for server 3', - tags: [ 'tag1p3' ], - fixture: 'video_short3.webm' + { + const attributes = { + name: 'my super name for server 3', + category: 6, + licence: 5, + language: 'de', + nsfw: true, + description: 'my super description for server 3', + support: 'my super support text for server 3', + tags: [ 'tag1p3' ], + fixture: 'video_short3.webm' + } + await servers[2].videos.upload({ attributes }) } - await uploadVideo(servers[2].url, servers[2].accessToken, videoAttributes1) - - const videoAttributes2 = { - name: 'my super name for server 3-2', - category: 7, - licence: 6, - language: 'ko', - nsfw: false, - description: 'my super description for server 3-2', - support: 'my super support text for server 3-2', - tags: [ 'tag2p3', 'tag3p3', 'tag4p3' ], - fixture: 'video_short.webm' + + { + const attributes = { + name: 'my super name for server 3-2', + category: 7, + licence: 6, + language: 'ko', + nsfw: false, + description: 'my super description for server 3-2', + support: 'my super support text for server 3-2', + tags: [ 'tag2p3', 'tag3p3', 'tag4p3' ], + fixture: 'video_short.webm' + } + await servers[2].videos.upload({ attributes }) } - await uploadVideo(servers[2].url, servers[2].accessToken, videoAttributes2) await waitJobs(servers) // All servers should have this video for (const server of servers) { const isLocal = server.url === 'http://localhost:' + servers[2].port - const res = await getVideosList(server.url) + const { data } = await server.videos.list() - const videos = res.body.data - expect(videos).to.be.an('array') - expect(videos.length).to.equal(4) + expect(data).to.be.an('array') + expect(data.length).to.equal(4) // We not sure about the order of the two last uploads let video1 = null let video2 = null - if (videos[2].name === 'my super name for server 3') { - video1 = videos[2] - video2 = videos[3] + if (data[2].name === 'my super name for server 3') { + video1 = data[2] + video2 = data[3] } else { - video1 = videos[3] - video2 = videos[2] + video1 = data[3] + video2 = data[2] } const checkAttributesVideo1 = { @@ -328,7 +308,7 @@ describe('Test multiple servers', function () { } ] } - await completeVideoCheck(server.url, video1, checkAttributesVideo1) + await completeVideoCheck(server, video1, checkAttributesVideo1) const checkAttributesVideo2 = { name: 'my super name for server 3-2', @@ -362,38 +342,38 @@ describe('Test multiple servers', function () { } ] } - await completeVideoCheck(server.url, video2, checkAttributesVideo2) + await completeVideoCheck(server, video2, checkAttributesVideo2) } }) }) describe('It should list local videos', function () { it('Should list only local videos on server 1', async function () { - const { body } = await getLocalVideos(servers[0].url) + const { data, total } = await servers[0].videos.list({ filter: 'local' }) - expect(body.total).to.equal(1) - expect(body.data).to.be.an('array') - expect(body.data.length).to.equal(1) - expect(body.data[0].name).to.equal('my super name for server 1') + expect(total).to.equal(1) + expect(data).to.be.an('array') + expect(data.length).to.equal(1) + expect(data[0].name).to.equal('my super name for server 1') }) it('Should list only local videos on server 2', async function () { - const { body } = await getLocalVideos(servers[1].url) + const { data, total } = await servers[1].videos.list({ filter: 'local' }) - expect(body.total).to.equal(1) - expect(body.data).to.be.an('array') - expect(body.data.length).to.equal(1) - expect(body.data[0].name).to.equal('my super name for server 2') + expect(total).to.equal(1) + expect(data).to.be.an('array') + expect(data.length).to.equal(1) + expect(data[0].name).to.equal('my super name for server 2') }) it('Should list only local videos on server 3', async function () { - const { body } = await getLocalVideos(servers[2].url) + const { data, total } = await servers[2].videos.list({ filter: 'local' }) - expect(body.total).to.equal(2) - expect(body.data).to.be.an('array') - expect(body.data.length).to.equal(2) - expect(body.data[0].name).to.equal('my super name for server 3') - expect(body.data[1].name).to.equal('my super name for server 3-2') + expect(total).to.equal(2) + expect(data).to.be.an('array') + expect(data.length).to.equal(2) + expect(data[0].name).to.equal('my super name for server 3') + expect(data[1].name).to.equal('my super name for server 3-2') }) }) @@ -401,15 +381,13 @@ describe('Test multiple servers', function () { it('Should add the file 1 by asking server 3', async function () { this.timeout(10000) - const res = await getVideosList(servers[2].url) - - const video = res.body.data[0] - toRemove.push(res.body.data[2]) - toRemove.push(res.body.data[3]) + const { data } = await servers[2].videos.list() - const res2 = await getVideo(servers[2].url, video.id) - const videoDetails = res2.body + const video = data[0] + toRemove.push(data[2]) + toRemove.push(data[3]) + const videoDetails = await servers[2].videos.get({ id: video.id }) const torrent = await webtorrentAdd(videoDetails.files[0].magnetUri, true) expect(torrent.files).to.be.an('array') expect(torrent.files.length).to.equal(1) @@ -419,11 +397,10 @@ describe('Test multiple servers', function () { it('Should add the file 2 by asking server 1', async function () { this.timeout(10000) - const res = await getVideosList(servers[0].url) + const { data } = await servers[0].videos.list() - const video = res.body.data[1] - const res2 = await getVideo(servers[0].url, video.id) - const videoDetails = res2.body + const video = data[1] + const videoDetails = await servers[0].videos.get({ id: video.id }) const torrent = await webtorrentAdd(videoDetails.files[0].magnetUri, true) expect(torrent.files).to.be.an('array') @@ -434,11 +411,10 @@ describe('Test multiple servers', function () { it('Should add the file 3 by asking server 2', async function () { this.timeout(10000) - const res = await getVideosList(servers[1].url) + const { data } = await servers[1].videos.list() - const video = res.body.data[2] - const res2 = await getVideo(servers[1].url, video.id) - const videoDetails = res2.body + const video = data[2] + const videoDetails = await servers[1].videos.get({ id: video.id }) const torrent = await webtorrentAdd(videoDetails.files[0].magnetUri, true) expect(torrent.files).to.be.an('array') @@ -449,11 +425,10 @@ describe('Test multiple servers', function () { it('Should add the file 3-2 by asking server 1', async function () { this.timeout(10000) - const res = await getVideosList(servers[0].url) + const { data } = await servers[0].videos.list() - const video = res.body.data[3] - const res2 = await getVideo(servers[0].url, video.id) - const videoDetails = res2.body + const video = data[3] + const videoDetails = await servers[0].videos.get({ id: video.id }) const torrent = await webtorrentAdd(videoDetails.files[0].magnetUri) expect(torrent.files).to.be.an('array') @@ -464,11 +439,10 @@ describe('Test multiple servers', function () { it('Should add the file 2 in 360p by asking server 1', async function () { this.timeout(10000) - const res = await getVideosList(servers[0].url) + const { data } = await servers[0].videos.list() - const video = res.body.data.find(v => v.name === 'my super name for server 2') - const res2 = await getVideo(servers[0].url, video.id) - const videoDetails = res2.body + const video = data.find(v => v.name === 'my super name for server 2') + const videoDetails = await servers[0].videos.get({ id: video.id }) const file = videoDetails.files.find(f => f.resolution.id === 360) expect(file).not.to.be.undefined @@ -487,30 +461,36 @@ describe('Test multiple servers', function () { let remoteVideosServer3 = [] before(async function () { - const res1 = await getVideosList(servers[0].url) - remoteVideosServer1 = res1.body.data.filter(video => video.isLocal === false).map(video => video.uuid) + { + const { data } = await servers[0].videos.list() + remoteVideosServer1 = data.filter(video => video.isLocal === false).map(video => video.uuid) + } - const res2 = await getVideosList(servers[1].url) - remoteVideosServer2 = res2.body.data.filter(video => video.isLocal === false).map(video => video.uuid) + { + const { data } = await servers[1].videos.list() + remoteVideosServer2 = data.filter(video => video.isLocal === false).map(video => video.uuid) + } - const res3 = await getVideosList(servers[2].url) - localVideosServer3 = res3.body.data.filter(video => video.isLocal === true).map(video => video.uuid) - remoteVideosServer3 = res3.body.data.filter(video => video.isLocal === false).map(video => video.uuid) + { + const { data } = await servers[2].videos.list() + localVideosServer3 = data.filter(video => video.isLocal === true).map(video => video.uuid) + remoteVideosServer3 = data.filter(video => video.isLocal === false).map(video => video.uuid) + } }) it('Should view multiple videos on owned servers', async function () { this.timeout(30000) - await viewVideo(servers[2].url, localVideosServer3[0]) + await servers[2].videos.view({ id: localVideosServer3[0] }) await wait(1000) - await viewVideo(servers[2].url, localVideosServer3[0]) - await viewVideo(servers[2].url, localVideosServer3[1]) + await servers[2].videos.view({ id: localVideosServer3[0] }) + await servers[2].videos.view({ id: localVideosServer3[1] }) await wait(1000) - await viewVideo(servers[2].url, localVideosServer3[0]) - await viewVideo(servers[2].url, localVideosServer3[0]) + await servers[2].videos.view({ id: localVideosServer3[0] }) + await servers[2].videos.view({ id: localVideosServer3[0] }) await waitJobs(servers) @@ -520,11 +500,10 @@ describe('Test multiple servers', function () { await waitJobs(servers) for (const server of servers) { - const res = await getVideosList(server.url) + const { data } = await server.videos.list() - const videos = res.body.data - const video0 = videos.find(v => v.uuid === localVideosServer3[0]) - const video1 = videos.find(v => v.uuid === localVideosServer3[1]) + const video0 = data.find(v => v.uuid === localVideosServer3[0]) + const video1 = data.find(v => v.uuid === localVideosServer3[1]) expect(video0.views).to.equal(3) expect(video1.views).to.equal(1) @@ -535,16 +514,16 @@ describe('Test multiple servers', function () { this.timeout(45000) const tasks: Promise[] = [] - tasks.push(viewVideo(servers[0].url, remoteVideosServer1[0])) - tasks.push(viewVideo(servers[1].url, remoteVideosServer2[0])) - tasks.push(viewVideo(servers[1].url, remoteVideosServer2[0])) - tasks.push(viewVideo(servers[2].url, remoteVideosServer3[0])) - tasks.push(viewVideo(servers[2].url, remoteVideosServer3[1])) - tasks.push(viewVideo(servers[2].url, remoteVideosServer3[1])) - tasks.push(viewVideo(servers[2].url, remoteVideosServer3[1])) - tasks.push(viewVideo(servers[2].url, localVideosServer3[1])) - tasks.push(viewVideo(servers[2].url, localVideosServer3[1])) - tasks.push(viewVideo(servers[2].url, localVideosServer3[1])) + tasks.push(servers[0].videos.view({ id: remoteVideosServer1[0] })) + tasks.push(servers[1].videos.view({ id: remoteVideosServer2[0] })) + tasks.push(servers[1].videos.view({ id: remoteVideosServer2[0] })) + tasks.push(servers[2].videos.view({ id: remoteVideosServer3[0] })) + tasks.push(servers[2].videos.view({ id: remoteVideosServer3[1] })) + tasks.push(servers[2].videos.view({ id: remoteVideosServer3[1] })) + tasks.push(servers[2].videos.view({ id: remoteVideosServer3[1] })) + tasks.push(servers[2].videos.view({ id: localVideosServer3[1] })) + tasks.push(servers[2].videos.view({ id: localVideosServer3[1] })) + tasks.push(servers[2].videos.view({ id: localVideosServer3[1] })) await Promise.all(tasks) @@ -558,18 +537,16 @@ describe('Test multiple servers', function () { let baseVideos = null for (const server of servers) { - const res = await getVideosList(server.url) - - const videos = res.body.data + const { data } = await server.videos.list() // Initialize base videos for future comparisons if (baseVideos === null) { - baseVideos = videos + baseVideos = data continue } for (const baseVideo of baseVideos) { - const sameVideo = videos.find(video => video.name === baseVideo.name) + const sameVideo = data.find(video => video.name === baseVideo.name) expect(baseVideo.views).to.equal(sameVideo.views) } } @@ -578,35 +555,34 @@ describe('Test multiple servers', function () { it('Should like and dislikes videos on different services', async function () { this.timeout(50000) - await rateVideo(servers[0].url, servers[0].accessToken, remoteVideosServer1[0], 'like') + await servers[0].videos.rate({ id: remoteVideosServer1[0], rating: 'like' }) await wait(500) - await rateVideo(servers[0].url, servers[0].accessToken, remoteVideosServer1[0], 'dislike') + await servers[0].videos.rate({ id: remoteVideosServer1[0], rating: 'dislike' }) await wait(500) - await rateVideo(servers[0].url, servers[0].accessToken, remoteVideosServer1[0], 'like') - await rateVideo(servers[2].url, servers[2].accessToken, localVideosServer3[1], 'like') + await servers[0].videos.rate({ id: remoteVideosServer1[0], rating: 'like' }) + await servers[2].videos.rate({ id: localVideosServer3[1], rating: 'like' }) await wait(500) - await rateVideo(servers[2].url, servers[2].accessToken, localVideosServer3[1], 'dislike') - await rateVideo(servers[2].url, servers[2].accessToken, remoteVideosServer3[1], 'dislike') + await servers[2].videos.rate({ id: localVideosServer3[1], rating: 'dislike' }) + await servers[2].videos.rate({ id: remoteVideosServer3[1], rating: 'dislike' }) await wait(500) - await rateVideo(servers[2].url, servers[2].accessToken, remoteVideosServer3[0], 'like') + await servers[2].videos.rate({ id: remoteVideosServer3[0], rating: 'like' }) await waitJobs(servers) await wait(5000) + await waitJobs(servers) let baseVideos = null for (const server of servers) { - const res = await getVideosList(server.url) - - const videos = res.body.data + const { data } = await server.videos.list() // Initialize base videos for future comparisons if (baseVideos === null) { - baseVideos = videos + baseVideos = data continue } for (const baseVideo of baseVideos) { - const sameVideo = videos.find(video => video.name === baseVideo.name) + const sameVideo = data.find(video => video.name === baseVideo.name) expect(baseVideo.likes).to.equal(sameVideo.likes) expect(baseVideo.dislikes).to.equal(sameVideo.dislikes) } @@ -632,7 +608,7 @@ describe('Test multiple servers', function () { previewfile: 'preview.jpg' } - await updateVideo(servers[2].url, servers[2].accessToken, toRemove[0].id, attributes) + await servers[2].videos.update({ id: toRemove[0].id, attributes }) await waitJobs(servers) }) @@ -641,10 +617,9 @@ describe('Test multiple servers', function () { this.timeout(10000) for (const server of servers) { - const res = await getVideosList(server.url) + const { data } = await server.videos.list() - const videos = res.body.data - const videoUpdated = videos.find(video => video.name === 'my super video updated') + const videoUpdated = data.find(video => video.name === 'my super video updated') expect(!!videoUpdated).to.be.true const isLocal = server.url === 'http://localhost:' + servers[2].port @@ -683,49 +658,46 @@ describe('Test multiple servers', function () { thumbnailfile: 'thumbnail', previewfile: 'preview' } - await completeVideoCheck(server.url, videoUpdated, checkAttributes) + await completeVideoCheck(server, videoUpdated, checkAttributes) } }) - it('Should remove the videos 3 and 3-2 by asking server 3', async function () { - this.timeout(10000) + it('Should remove the videos 3 and 3-2 by asking server 3 and correctly delete files', async function () { + this.timeout(30000) - await removeVideo(servers[2].url, servers[2].accessToken, toRemove[0].id) - await removeVideo(servers[2].url, servers[2].accessToken, toRemove[1].id) + for (const id of [ toRemove[0].id, toRemove[1].id ]) { + await saveVideoInServers(servers, id) - await waitJobs(servers) - }) + await servers[2].videos.remove({ id }) - it('Should not have files of videos 3 and 3-2 on each server', async function () { - for (const server of servers) { - await checkVideoFilesWereRemoved(toRemove[0].uuid, server.internalServerNumber) - await checkVideoFilesWereRemoved(toRemove[1].uuid, server.internalServerNumber) + await waitJobs(servers) + + for (const server of servers) { + await checkVideoFilesWereRemoved({ server, video: server.store.videoDetails }) + } } }) it('Should have videos 1 and 3 on each server', async function () { for (const server of servers) { - const res = await getVideosList(server.url) - - const videos = res.body.data - expect(videos).to.be.an('array') - expect(videos.length).to.equal(2) - expect(videos[0].name).not.to.equal(videos[1].name) - expect(videos[0].name).not.to.equal(toRemove[0].name) - expect(videos[1].name).not.to.equal(toRemove[0].name) - expect(videos[0].name).not.to.equal(toRemove[1].name) - expect(videos[1].name).not.to.equal(toRemove[1].name) - - videoUUID = videos.find(video => video.name === 'my super name for server 1').uuid + const { data } = await server.videos.list() + + expect(data).to.be.an('array') + expect(data.length).to.equal(2) + expect(data[0].name).not.to.equal(data[1].name) + expect(data[0].name).not.to.equal(toRemove[0].name) + expect(data[1].name).not.to.equal(toRemove[0].name) + expect(data[0].name).not.to.equal(toRemove[1].name) + expect(data[1].name).not.to.equal(toRemove[1].name) + + videoUUID = data.find(video => video.name === 'my super name for server 1').uuid } }) it('Should get the same video by UUID on each server', async function () { let baseVideo = null for (const server of servers) { - const res = await getVideo(server.url, videoUUID) - - const video = res.body + const video = await server.videos.get({ id: videoUUID }) if (baseVideo === null) { baseVideo = video @@ -748,8 +720,7 @@ describe('Test multiple servers', function () { it('Should get the preview from each server', async function () { for (const server of servers) { - const res = await getVideo(server.url, videoUUID) - const video = res.body + const video = await server.videos.get({ id: videoUUID }) await testImage(server.url, 'video_short1-preview.webm', video.previewPath) } @@ -764,36 +735,36 @@ describe('Test multiple servers', function () { { const text = 'my super first comment' - await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoUUID, text) + await servers[0].comments.createThread({ videoId: videoUUID, text }) } { const text = 'my super second comment' - await addVideoCommentThread(servers[2].url, servers[2].accessToken, videoUUID, text) + await servers[2].comments.createThread({ videoId: videoUUID, text }) } await waitJobs(servers) { - const threadId = await findCommentId(servers[1].url, videoUUID, 'my super first comment') + const threadId = await servers[1].comments.findCommentId({ videoId: videoUUID, text: 'my super first comment' }) const text = 'my super answer to thread 1' - await addVideoCommentReply(servers[1].url, servers[1].accessToken, videoUUID, threadId, text) + await servers[1].comments.addReply({ videoId: videoUUID, toCommentId: threadId, text }) } await waitJobs(servers) { - const threadId = await findCommentId(servers[2].url, videoUUID, 'my super first comment') + const threadId = await servers[2].comments.findCommentId({ videoId: videoUUID, text: 'my super first comment' }) - const res2 = await getVideoThreadComments(servers[2].url, videoUUID, threadId) - const childCommentId = res2.body.children[0].comment.id + const body = await servers[2].comments.getThread({ videoId: videoUUID, threadId }) + const childCommentId = body.children[0].comment.id const text3 = 'my second answer to thread 1' - await addVideoCommentReply(servers[2].url, servers[2].accessToken, videoUUID, threadId, text3) + await servers[2].comments.addReply({ videoId: videoUUID, toCommentId: threadId, text: text3 }) const text2 = 'my super answer to answer of thread 1' - await addVideoCommentReply(servers[2].url, servers[2].accessToken, videoUUID, childCommentId, text2) + await servers[2].comments.addReply({ videoId: videoUUID, toCommentId: childCommentId, text: text2 }) } await waitJobs(servers) @@ -801,14 +772,14 @@ describe('Test multiple servers', function () { it('Should have these threads', async function () { for (const server of servers) { - const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5) + const body = await server.comments.listThreads({ videoId: videoUUID }) - expect(res.body.total).to.equal(2) - expect(res.body.data).to.be.an('array') - expect(res.body.data).to.have.lengthOf(2) + expect(body.total).to.equal(2) + expect(body.data).to.be.an('array') + expect(body.data).to.have.lengthOf(2) { - const comment: VideoComment = res.body.data.find(c => c.text === 'my super first comment') + const comment = body.data.find(c => c.text === 'my super first comment') expect(comment).to.not.be.undefined expect(comment.inReplyToCommentId).to.be.null expect(comment.account.name).to.equal('root') @@ -819,7 +790,7 @@ describe('Test multiple servers', function () { } { - const comment: VideoComment = res.body.data.find(c => c.text === 'my super second comment') + const comment = body.data.find(c => c.text === 'my super second comment') expect(comment).to.not.be.undefined expect(comment.inReplyToCommentId).to.be.null expect(comment.account.name).to.equal('root') @@ -833,12 +804,11 @@ describe('Test multiple servers', function () { it('Should have these comments', async function () { for (const server of servers) { - const res1 = await getVideoCommentThreads(server.url, videoUUID, 0, 5) - const threadId = res1.body.data.find(c => c.text === 'my super first comment').id + const body = await server.comments.listThreads({ videoId: videoUUID }) + const threadId = body.data.find(c => c.text === 'my super first comment').id - const res2 = await getVideoThreadComments(server.url, videoUUID, threadId) + const tree = await server.comments.getThread({ videoId: videoUUID, threadId }) - const tree: VideoCommentThreadTree = res2.body expect(tree.comment.text).equal('my super first comment') expect(tree.comment.account.name).equal('root') expect(tree.comment.account.host).equal('localhost:' + servers[0].port) @@ -867,19 +837,17 @@ describe('Test multiple servers', function () { it('Should delete a reply', async function () { this.timeout(10000) - await deleteVideoComment(servers[2].url, servers[2].accessToken, videoUUID, childOfFirstChild.comment.id) + await servers[2].comments.delete({ videoId: videoUUID, commentId: childOfFirstChild.comment.id }) await waitJobs(servers) }) it('Should have this comment marked as deleted', async function () { for (const server of servers) { - const res1 = await getVideoCommentThreads(server.url, videoUUID, 0, 5) - const threadId = res1.body.data.find(c => c.text === 'my super first comment').id - - const res2 = await getVideoThreadComments(server.url, videoUUID, threadId) + const { data } = await server.comments.listThreads({ videoId: videoUUID }) + const threadId = data.find(c => c.text === 'my super first comment').id - const tree: VideoCommentThreadTree = res2.body + const tree = await server.comments.getThread({ videoId: videoUUID, threadId }) expect(tree.comment.text).equal('my super first comment') const firstChild = tree.children[0] @@ -900,23 +868,23 @@ describe('Test multiple servers', function () { it('Should delete the thread comments', async function () { this.timeout(10000) - const res = await getVideoCommentThreads(servers[0].url, videoUUID, 0, 5) - const threadId = res.body.data.find(c => c.text === 'my super first comment').id - await deleteVideoComment(servers[0].url, servers[0].accessToken, videoUUID, threadId) + const { data } = await servers[0].comments.listThreads({ videoId: videoUUID }) + const commentId = data.find(c => c.text === 'my super first comment').id + await servers[0].comments.delete({ videoId: videoUUID, commentId }) await waitJobs(servers) }) it('Should have the threads marked as deleted on other servers too', async function () { for (const server of servers) { - const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5) + const body = await server.comments.listThreads({ videoId: videoUUID }) - expect(res.body.total).to.equal(2) - expect(res.body.data).to.be.an('array') - expect(res.body.data).to.have.lengthOf(2) + expect(body.total).to.equal(2) + expect(body.data).to.be.an('array') + expect(body.data).to.have.lengthOf(2) { - const comment: VideoComment = res.body.data[0] + const comment = body.data[0] expect(comment).to.not.be.undefined expect(comment.inReplyToCommentId).to.be.null expect(comment.account.name).to.equal('root') @@ -927,7 +895,7 @@ describe('Test multiple servers', function () { } { - const deletedComment: VideoComment = res.body.data[1] + const deletedComment = body.data[1] expect(deletedComment).to.not.be.undefined expect(deletedComment.isDeleted).to.be.true expect(deletedComment.deletedAt).to.not.be.null @@ -945,22 +913,22 @@ describe('Test multiple servers', function () { it('Should delete a remote thread by the origin server', async function () { this.timeout(5000) - const res = await getVideoCommentThreads(servers[0].url, videoUUID, 0, 5) - const threadId = res.body.data.find(c => c.text === 'my super second comment').id - await deleteVideoComment(servers[0].url, servers[0].accessToken, videoUUID, threadId) + const { data } = await servers[0].comments.listThreads({ videoId: videoUUID }) + const commentId = data.find(c => c.text === 'my super second comment').id + await servers[0].comments.delete({ videoId: videoUUID, commentId }) await waitJobs(servers) }) it('Should have the threads marked as deleted on other servers too', async function () { for (const server of servers) { - const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5) + const body = await server.comments.listThreads({ videoId: videoUUID }) - expect(res.body.total).to.equal(2) - expect(res.body.data).to.have.lengthOf(2) + expect(body.total).to.equal(2) + expect(body.data).to.have.lengthOf(2) { - const comment: VideoComment = res.body.data[0] + const comment = body.data[0] expect(comment.text).to.equal('') expect(comment.isDeleted).to.be.true expect(comment.createdAt).to.not.be.null @@ -970,7 +938,7 @@ describe('Test multiple servers', function () { } { - const comment: VideoComment = res.body.data[1] + const comment = body.data[1] expect(comment.text).to.equal('') expect(comment.isDeleted).to.be.true expect(comment.createdAt).to.not.be.null @@ -989,17 +957,17 @@ describe('Test multiple servers', function () { downloadEnabled: false } - await updateVideo(servers[0].url, servers[0].accessToken, videoUUID, attributes) + await servers[0].videos.update({ id: videoUUID, attributes }) await waitJobs(servers) for (const server of servers) { - const res = await getVideo(server.url, videoUUID) - expect(res.body.commentsEnabled).to.be.false - expect(res.body.downloadEnabled).to.be.false + const video = await server.videos.get({ id: videoUUID }) + expect(video.commentsEnabled).to.be.false + expect(video.downloadEnabled).to.be.false const text = 'my super forbidden comment' - await addVideoCommentThread(server.url, server.accessToken, videoUUID, text, HttpStatusCode.CONFLICT_409) + await server.comments.createThread({ videoId: videoUUID, text, expectedStatus: HttpStatusCode.CONFLICT_409 }) } }) }) @@ -1024,8 +992,8 @@ describe('Test multiple servers', function () { await waitJobs(servers) for (const server of servers) { - const res = await getVideosList(server.url) - const video = res.body.data.find(v => v.name === 'minimum parameters') + const { data } = await server.videos.list() + const video = data.find(v => v.name === 'minimum parameters') const isLocal = server.url === 'http://localhost:' + servers[1].port const checkAttributes = { @@ -1072,7 +1040,7 @@ describe('Test multiple servers', function () { } ] } - await completeVideoCheck(server.url, video, checkAttributes) + await completeVideoCheck(server, video, checkAttributes) } }) }) diff --git a/server/tests/api/videos/resumable-upload.ts b/server/tests/api/videos/resumable-upload.ts index 4fc3317df..857859fd3 100644 --- a/server/tests/api/videos/resumable-upload.ts +++ b/server/tests/api/videos/resumable-upload.ts @@ -4,22 +4,15 @@ import 'mocha' import * as chai from 'chai' import { pathExists, readdir, stat } from 'fs-extra' import { join } from 'path' -import { HttpStatusCode } from '@shared/core-utils' import { buildAbsoluteFixturePath, - buildServerDirectory, cleanupTests, - flushAndRunServer, - getMyUserInformation, - prepareResumableUpload, - sendDebugCommand, - sendResumableChunks, - ServerInfo, + createSingleServer, + PeerTubeServer, setAccessTokensToServers, - setDefaultVideoChannel, - updateUser + setDefaultVideoChannel } from '@shared/extra-utils' -import { MyUser, VideoPrivacy } from '@shared/models' +import { HttpStatusCode, VideoPrivacy } from '@shared/models' const expect = chai.expect @@ -27,7 +20,7 @@ const expect = chai.expect describe('Test resumable upload', function () { const defaultFixture = 'video_short.mp4' - let server: ServerInfo + let server: PeerTubeServer let rootId: number async function buildSize (fixture: string, size?: number) { @@ -42,14 +35,14 @@ describe('Test resumable upload', function () { const attributes = { name: 'video', - channelId: server.videoChannel.id, + channelId: server.store.channel.id, privacy: VideoPrivacy.PUBLIC, fixture: defaultFixture } const mimetype = 'video/mp4' - const res = await prepareResumableUpload({ url: server.url, token: server.accessToken, attributes, size, mimetype }) + const res = await server.videos.prepareResumableUpload({ attributes, size, mimetype }) return res.header['location'].split('?')[1] } @@ -67,15 +60,13 @@ describe('Test resumable upload', function () { const size = await buildSize(defaultFixture, options.size) const absoluteFilePath = buildAbsoluteFixturePath(defaultFixture) - return sendResumableChunks({ - url: server.url, - token: server.accessToken, + return server.videos.sendResumableChunks({ pathUploadId, videoFilePath: absoluteFilePath, size, contentLength, contentRangeBuilder, - specialStatus: expectedStatus + expectedStatus }) } @@ -83,7 +74,7 @@ describe('Test resumable upload', function () { const uploadId = uploadIdArg.replace(/^upload_id=/, '') const subPath = join('tmp', 'resumable-uploads', uploadId) - const filePath = buildServerDirectory(server, subPath) + const filePath = server.servers.buildDirectory(subPath) const exists = await pathExists(filePath) if (expectedSize === null) { @@ -98,7 +89,7 @@ describe('Test resumable upload', function () { async function countResumableUploads () { const subPath = join('tmp', 'resumable-uploads') - const filePath = buildServerDirectory(server, subPath) + const filePath = server.servers.buildDirectory(subPath) const files = await readdir(filePath) return files.length @@ -107,19 +98,14 @@ describe('Test resumable upload', function () { before(async function () { this.timeout(30000) - server = await flushAndRunServer(1) + server = await createSingleServer(1) await setAccessTokensToServers([ server ]) await setDefaultVideoChannel([ server ]) - const res = await getMyUserInformation(server.url, server.accessToken) - rootId = (res.body as MyUser).id + const body = await server.users.getMyInfo() + rootId = body.id - await updateUser({ - url: server.url, - userId: rootId, - accessToken: server.accessToken, - videoQuota: 10_000_000 - }) + await server.users.update({ userId: rootId, videoQuota: 10_000_000 }) }) describe('Directory cleaning', function () { @@ -138,13 +124,13 @@ describe('Test resumable upload', function () { }) it('Should not delete recent uploads', async function () { - await sendDebugCommand(server.url, server.accessToken, { command: 'remove-dandling-resumable-uploads' }) + await server.debug.sendCommand({ body: { command: 'remove-dandling-resumable-uploads' } }) expect(await countResumableUploads()).to.equal(2) }) it('Should delete old uploads', async function () { - await sendDebugCommand(server.url, server.accessToken, { command: 'remove-dandling-resumable-uploads' }) + await server.debug.sendCommand({ body: { command: 'remove-dandling-resumable-uploads' } }) expect(await countResumableUploads()).to.equal(0) }) @@ -160,8 +146,7 @@ describe('Test resumable upload', function () { }) it('Should not accept more chunks than expected', async function () { - const size = 100 - const uploadId = await prepareUpload(size) + const uploadId = await prepareUpload(100) await sendChunks({ pathUploadId: uploadId, expectedStatus: HttpStatusCode.CONFLICT_409 }) await checkFileSize(uploadId, 0) @@ -170,8 +155,14 @@ describe('Test resumable upload', function () { it('Should not accept more chunks than expected with an invalid content length/content range', async function () { const uploadId = await prepareUpload(1500) - await sendChunks({ pathUploadId: uploadId, expectedStatus: HttpStatusCode.BAD_REQUEST_400, contentLength: 1000 }) - await checkFileSize(uploadId, 0) + // Content length check seems to have changed in v16 + if (process.version.startsWith('v16')) { + await sendChunks({ pathUploadId: uploadId, expectedStatus: HttpStatusCode.CONFLICT_409, contentLength: 1000 }) + await checkFileSize(uploadId, 1000) + } else { + await sendChunks({ pathUploadId: uploadId, expectedStatus: HttpStatusCode.BAD_REQUEST_400, contentLength: 1000 }) + await checkFileSize(uploadId, 0) + } }) it('Should not accept more chunks than expected with an invalid content length', async function () { @@ -179,8 +170,13 @@ describe('Test resumable upload', function () { const size = 1000 - const contentRangeBuilder = start => `bytes ${start}-${start + size - 1}/${size}` - await sendChunks({ pathUploadId: uploadId, expectedStatus: HttpStatusCode.BAD_REQUEST_400, contentRangeBuilder, contentLength: size }) + // Content length check seems to have changed in v16 + const expectedStatus = process.version.startsWith('v16') + ? HttpStatusCode.CONFLICT_409 + : HttpStatusCode.BAD_REQUEST_400 + + const contentRangeBuilder = (start: number) => `bytes ${start}-${start + size - 1}/${size}` + await sendChunks({ pathUploadId: uploadId, expectedStatus, contentRangeBuilder, contentLength: size }) await checkFileSize(uploadId, 0) }) }) diff --git a/server/tests/api/videos/single-server.ts b/server/tests/api/videos/single-server.ts index 1058a1e9c..29dac6ec1 100644 --- a/server/tests/api/videos/single-server.ts +++ b/server/tests/api/videos/single-server.ts @@ -2,43 +2,26 @@ import 'mocha' import * as chai from 'chai' -import { keyBy } from 'lodash' - import { checkVideoFilesWereRemoved, cleanupTests, completeVideoCheck, - flushAndRunServer, - getVideo, - getVideoCategories, - getVideoLanguages, - getVideoLicences, - getVideoPrivacies, - getVideosList, - getVideosListPagination, - getVideosListSort, - getVideosWithFilters, - rateVideo, - removeVideo, - ServerInfo, + createSingleServer, + PeerTubeServer, setAccessTokensToServers, testImage, - updateVideo, - uploadVideo, - viewVideo, wait -} from '../../../../shared/extra-utils' -import { VideoPrivacy } from '../../../../shared/models/videos' -import { HttpStatusCode } from '@shared/core-utils' +} from '@shared/extra-utils' +import { Video, VideoPrivacy } from '@shared/models' const expect = chai.expect describe('Test a single server', function () { function runSuite (mode: 'legacy' | 'resumable') { - let server: ServerInfo = null - let videoId = -1 - let videoId2 = -1 + let server: PeerTubeServer = null + let videoId: number | string + let videoId2: string let videoUUID = '' let videosListBase: any[] = null @@ -111,134 +94,123 @@ describe('Test a single server', function () { before(async function () { this.timeout(30000) - server = await flushAndRunServer(1) + server = await createSingleServer(1) await setAccessTokensToServers([ server ]) }) it('Should list video categories', async function () { - const res = await getVideoCategories(server.url) - - const categories = res.body + const categories = await server.videos.getCategories() expect(Object.keys(categories)).to.have.length.above(10) expect(categories[11]).to.equal('News & Politics') }) it('Should list video licences', async function () { - const res = await getVideoLicences(server.url) - - const licences = res.body + const licences = await server.videos.getLicences() expect(Object.keys(licences)).to.have.length.above(5) expect(licences[3]).to.equal('Attribution - No Derivatives') }) it('Should list video languages', async function () { - const res = await getVideoLanguages(server.url) - - const languages = res.body + const languages = await server.videos.getLanguages() expect(Object.keys(languages)).to.have.length.above(5) expect(languages['ru']).to.equal('Russian') }) it('Should list video privacies', async function () { - const res = await getVideoPrivacies(server.url) - - const privacies = res.body + const privacies = await server.videos.getPrivacies() expect(Object.keys(privacies)).to.have.length.at.least(3) expect(privacies[3]).to.equal('Private') }) it('Should not have videos', async function () { - const res = await getVideosList(server.url) + const { data, total } = await server.videos.list() - expect(res.body.total).to.equal(0) - expect(res.body.data).to.be.an('array') - expect(res.body.data.length).to.equal(0) + expect(total).to.equal(0) + expect(data).to.be.an('array') + expect(data.length).to.equal(0) }) it('Should upload the video', async function () { this.timeout(10000) - const videoAttributes = { + const attributes = { name: 'my super name', category: 2, nsfw: true, licence: 6, tags: [ 'tag1', 'tag2', 'tag3' ] } - const res = await uploadVideo(server.url, server.accessToken, videoAttributes, HttpStatusCode.OK_200, mode) - expect(res.body.video).to.not.be.undefined - expect(res.body.video.id).to.equal(1) - expect(res.body.video.uuid).to.have.length.above(5) + const video = await server.videos.upload({ attributes, mode }) + expect(video).to.not.be.undefined + expect(video.id).to.equal(1) + expect(video.uuid).to.have.length.above(5) - videoId = res.body.video.id - videoUUID = res.body.video.uuid + videoId = video.id + videoUUID = video.uuid }) it('Should get and seed the uploaded video', async function () { this.timeout(5000) - const res = await getVideosList(server.url) + const { data, total } = await server.videos.list() - expect(res.body.total).to.equal(1) - expect(res.body.data).to.be.an('array') - expect(res.body.data.length).to.equal(1) + expect(total).to.equal(1) + expect(data).to.be.an('array') + expect(data.length).to.equal(1) - const video = res.body.data[0] - await completeVideoCheck(server.url, video, getCheckAttributes()) + const video = data[0] + await completeVideoCheck(server, video, getCheckAttributes()) }) it('Should get the video by UUID', async function () { this.timeout(5000) - const res = await getVideo(server.url, videoUUID) - - const video = res.body - await completeVideoCheck(server.url, video, getCheckAttributes()) + const video = await server.videos.get({ id: videoUUID }) + await completeVideoCheck(server, video, getCheckAttributes()) }) it('Should have the views updated', async function () { this.timeout(20000) - await viewVideo(server.url, videoId) - await viewVideo(server.url, videoId) - await viewVideo(server.url, videoId) + await server.videos.view({ id: videoId }) + await server.videos.view({ id: videoId }) + await server.videos.view({ id: videoId }) await wait(1500) - await viewVideo(server.url, videoId) - await viewVideo(server.url, videoId) + await server.videos.view({ id: videoId }) + await server.videos.view({ id: videoId }) await wait(1500) - await viewVideo(server.url, videoId) - await viewVideo(server.url, videoId) + await server.videos.view({ id: videoId }) + await server.videos.view({ id: videoId }) // Wait the repeatable job await wait(8000) - const res = await getVideo(server.url, videoId) - - const video = res.body + const video = await server.videos.get({ id: videoId }) expect(video.views).to.equal(3) }) it('Should remove the video', async function () { - await removeVideo(server.url, server.accessToken, videoId) + const video = await server.videos.get({ id: videoId }) + await server.videos.remove({ id: videoId }) - await checkVideoFilesWereRemoved(videoUUID, 1) + await checkVideoFilesWereRemoved({ video, server }) }) it('Should not have videos', async function () { - const res = await getVideosList(server.url) + const { total, data } = await server.videos.list() - expect(res.body.total).to.equal(0) - expect(res.body.data).to.be.an('array') - expect(res.body.data).to.have.lengthOf(0) + expect(total).to.equal(0) + expect(data).to.be.an('array') + expect(data).to.have.lengthOf(0) }) it('Should upload 6 videos', async function () { @@ -250,7 +222,7 @@ describe('Test a single server', function () { ]) for (const video of videos) { - const videoAttributes = { + const attributes = { name: video + ' name', description: video + ' description', category: 2, @@ -261,19 +233,20 @@ describe('Test a single server', function () { fixture: video } - await uploadVideo(server.url, server.accessToken, videoAttributes, HttpStatusCode.OK_200, mode) + await server.videos.upload({ attributes, mode }) } }) it('Should have the correct durations', async function () { - const res = await getVideosList(server.url) + const { total, data } = await server.videos.list() + + expect(total).to.equal(6) + expect(data).to.be.an('array') + expect(data).to.have.lengthOf(6) - expect(res.body.total).to.equal(6) - const videos = res.body.data - expect(videos).to.be.an('array') - expect(videos).to.have.lengthOf(6) + const videosByName: { [ name: string ]: Video } = {} + data.forEach(v => { videosByName[v.name] = v }) - const videosByName = keyBy<{ duration: number }>(videos, 'name') expect(videosByName['video_short.mp4 name'].duration).to.equal(5) expect(videosByName['video_short.ogv name'].duration).to.equal(5) expect(videosByName['video_short.webm name'].duration).to.equal(5) @@ -283,96 +256,87 @@ describe('Test a single server', function () { }) it('Should have the correct thumbnails', async function () { - const res = await getVideosList(server.url) + const { data } = await server.videos.list() - const videos = res.body.data // For the next test - videosListBase = videos + videosListBase = data - for (const video of videos) { + for (const video of data) { const videoName = video.name.replace(' name', '') await testImage(server.url, videoName, video.thumbnailPath) } }) it('Should list only the two first videos', async function () { - const res = await getVideosListPagination(server.url, 0, 2, 'name') + const { total, data } = await server.videos.list({ start: 0, count: 2, sort: 'name' }) - const videos = res.body.data - expect(res.body.total).to.equal(6) - expect(videos.length).to.equal(2) - expect(videos[0].name).to.equal(videosListBase[0].name) - expect(videos[1].name).to.equal(videosListBase[1].name) + expect(total).to.equal(6) + expect(data.length).to.equal(2) + expect(data[0].name).to.equal(videosListBase[0].name) + expect(data[1].name).to.equal(videosListBase[1].name) }) it('Should list only the next three videos', async function () { - const res = await getVideosListPagination(server.url, 2, 3, 'name') + const { total, data } = await server.videos.list({ start: 2, count: 3, sort: 'name' }) - const videos = res.body.data - expect(res.body.total).to.equal(6) - expect(videos.length).to.equal(3) - expect(videos[0].name).to.equal(videosListBase[2].name) - expect(videos[1].name).to.equal(videosListBase[3].name) - expect(videos[2].name).to.equal(videosListBase[4].name) + expect(total).to.equal(6) + expect(data.length).to.equal(3) + expect(data[0].name).to.equal(videosListBase[2].name) + expect(data[1].name).to.equal(videosListBase[3].name) + expect(data[2].name).to.equal(videosListBase[4].name) }) it('Should list the last video', async function () { - const res = await getVideosListPagination(server.url, 5, 6, 'name') + const { total, data } = await server.videos.list({ start: 5, count: 6, sort: 'name' }) - const videos = res.body.data - expect(res.body.total).to.equal(6) - expect(videos.length).to.equal(1) - expect(videos[0].name).to.equal(videosListBase[5].name) + expect(total).to.equal(6) + expect(data.length).to.equal(1) + expect(data[0].name).to.equal(videosListBase[5].name) }) it('Should not have the total field', async function () { - const res = await getVideosListPagination(server.url, 5, 6, 'name', true) + const { total, data } = await server.videos.list({ start: 5, count: 6, sort: 'name', skipCount: true }) - const videos = res.body.data - expect(res.body.total).to.not.exist - expect(videos.length).to.equal(1) - expect(videos[0].name).to.equal(videosListBase[5].name) + expect(total).to.not.exist + expect(data.length).to.equal(1) + expect(data[0].name).to.equal(videosListBase[5].name) }) it('Should list and sort by name in descending order', async function () { - const res = await getVideosListSort(server.url, '-name') + const { total, data } = await server.videos.list({ sort: '-name' }) - const videos = res.body.data - expect(res.body.total).to.equal(6) - expect(videos.length).to.equal(6) - expect(videos[0].name).to.equal('video_short.webm name') - expect(videos[1].name).to.equal('video_short.ogv name') - expect(videos[2].name).to.equal('video_short.mp4 name') - expect(videos[3].name).to.equal('video_short3.webm name') - expect(videos[4].name).to.equal('video_short2.webm name') - expect(videos[5].name).to.equal('video_short1.webm name') + expect(total).to.equal(6) + expect(data.length).to.equal(6) + expect(data[0].name).to.equal('video_short.webm name') + expect(data[1].name).to.equal('video_short.ogv name') + expect(data[2].name).to.equal('video_short.mp4 name') + expect(data[3].name).to.equal('video_short3.webm name') + expect(data[4].name).to.equal('video_short2.webm name') + expect(data[5].name).to.equal('video_short1.webm name') - videoId = videos[3].uuid - videoId2 = videos[5].uuid + videoId = data[3].uuid + videoId2 = data[5].uuid }) it('Should list and sort by trending in descending order', async function () { - const res = await getVideosListPagination(server.url, 0, 2, '-trending') + const { total, data } = await server.videos.list({ start: 0, count: 2, sort: '-trending' }) - const videos = res.body.data - expect(res.body.total).to.equal(6) - expect(videos.length).to.equal(2) + expect(total).to.equal(6) + expect(data.length).to.equal(2) }) it('Should list and sort by hotness in descending order', async function () { - const res = await getVideosListPagination(server.url, 0, 2, '-hot') + const { total, data } = await server.videos.list({ start: 0, count: 2, sort: '-hot' }) - const videos = res.body.data - expect(res.body.total).to.equal(6) - expect(videos.length).to.equal(2) + expect(total).to.equal(6) + expect(data.length).to.equal(2) }) it('Should list and sort by best in descending order', async function () { - const res = await getVideosListPagination(server.url, 0, 2, '-best') + const { total, data } = await server.videos.list({ start: 0, count: 2, sort: '-best' }) - const videos = res.body.data - expect(res.body.total).to.equal(6) - expect(videos.length).to.equal(2) + expect(total).to.equal(6) + expect(data.length).to.equal(2) }) it('Should update a video', async function () { @@ -387,67 +351,66 @@ describe('Test a single server', function () { downloadEnabled: false, tags: [ 'tagup1', 'tagup2' ] } - await updateVideo(server.url, server.accessToken, videoId, attributes) + await server.videos.update({ id: videoId, attributes }) }) it('Should filter by tags and category', async function () { - const res1 = await getVideosWithFilters(server.url, { tagsAllOf: [ 'tagup1', 'tagup2' ], categoryOneOf: [ 4 ] }) - expect(res1.body.total).to.equal(1) - expect(res1.body.data[0].name).to.equal('my super video updated') + { + const { data, total } = await server.videos.list({ tagsAllOf: [ 'tagup1', 'tagup2' ], categoryOneOf: [ 4 ] }) + expect(total).to.equal(1) + expect(data[0].name).to.equal('my super video updated') + } - const res2 = await getVideosWithFilters(server.url, { tagsAllOf: [ 'tagup1', 'tagup2' ], categoryOneOf: [ 3 ] }) - expect(res2.body.total).to.equal(0) + { + const { total } = await server.videos.list({ tagsAllOf: [ 'tagup1', 'tagup2' ], categoryOneOf: [ 3 ] }) + expect(total).to.equal(0) + } }) it('Should have the video updated', async function () { this.timeout(60000) - const res = await getVideo(server.url, videoId) - const video = res.body + const video = await server.videos.get({ id: videoId }) - await completeVideoCheck(server.url, video, updateCheckAttributes()) + await completeVideoCheck(server, video, updateCheckAttributes()) }) it('Should update only the tags of a video', async function () { const attributes = { tags: [ 'supertag', 'tag1', 'tag2' ] } - await updateVideo(server.url, server.accessToken, videoId, attributes) + await server.videos.update({ id: videoId, attributes }) - const res = await getVideo(server.url, videoId) - const video = res.body + const video = await server.videos.get({ id: videoId }) - await completeVideoCheck(server.url, video, Object.assign(updateCheckAttributes(), attributes)) + await completeVideoCheck(server, video, Object.assign(updateCheckAttributes(), attributes)) }) it('Should update only the description of a video', async function () { const attributes = { description: 'hello everybody' } - await updateVideo(server.url, server.accessToken, videoId, attributes) + await server.videos.update({ id: videoId, attributes }) - const res = await getVideo(server.url, videoId) - const video = res.body + const video = await server.videos.get({ id: videoId }) const expectedAttributes = Object.assign(updateCheckAttributes(), { tags: [ 'supertag', 'tag1', 'tag2' ] }, attributes) - await completeVideoCheck(server.url, video, expectedAttributes) + await completeVideoCheck(server, video, expectedAttributes) }) it('Should like a video', async function () { - await rateVideo(server.url, server.accessToken, videoId, 'like') + await server.videos.rate({ id: videoId, rating: 'like' }) - const res = await getVideo(server.url, videoId) - const video = res.body + const video = await server.videos.get({ id: videoId }) expect(video.likes).to.equal(1) expect(video.dislikes).to.equal(0) }) it('Should dislike the same video', async function () { - await rateVideo(server.url, server.accessToken, videoId, 'dislike') + await server.videos.rate({ id: videoId, rating: 'dislike' }) - const res = await getVideo(server.url, videoId) - const video = res.body + const video = await server.videos.get({ id: videoId }) expect(video.likes).to.equal(0) expect(video.dislikes).to.equal(1) @@ -457,10 +420,10 @@ describe('Test a single server', function () { { const now = new Date() const attributes = { originallyPublishedAt: now.toISOString() } - await updateVideo(server.url, server.accessToken, videoId, attributes) + await server.videos.update({ id: videoId, attributes }) - const res = await getVideosListSort(server.url, '-originallyPublishedAt') - const names = res.body.data.map(v => v.name) + const { data } = await server.videos.list({ sort: '-originallyPublishedAt' }) + const names = data.map(v => v.name) expect(names[0]).to.equal('my super video updated') expect(names[1]).to.equal('video_short2.webm name') @@ -473,10 +436,10 @@ describe('Test a single server', function () { { const now = new Date() const attributes = { originallyPublishedAt: now.toISOString() } - await updateVideo(server.url, server.accessToken, videoId2, attributes) + await server.videos.update({ id: videoId2, attributes }) - const res = await getVideosListSort(server.url, '-originallyPublishedAt') - const names = res.body.data.map(v => v.name) + const { data } = await server.videos.list({ sort: '-originallyPublishedAt' }) + const names = data.map(v => v.name) expect(names[0]).to.equal('video_short1.webm name') expect(names[1]).to.equal('my super video updated') diff --git a/server/tests/api/videos/video-captions.ts b/server/tests/api/videos/video-captions.ts index 14ecedfa6..3bb0d131c 100644 --- a/server/tests/api/videos/video-captions.ts +++ b/server/tests/api/videos/video-captions.ts @@ -1,72 +1,61 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ -import * as chai from 'chai' import 'mocha' +import * as chai from 'chai' import { checkVideoFilesWereRemoved, cleanupTests, + createMultipleServers, doubleFollow, - flushAndRunMultipleServers, - removeVideo, - uploadVideo, - wait -} from '../../../../shared/extra-utils' -import { ServerInfo, setAccessTokensToServers } from '../../../../shared/extra-utils/index' -import { waitJobs } from '../../../../shared/extra-utils/server/jobs' -import { - createVideoCaption, - deleteVideoCaption, - listVideoCaptions, - testCaptionFile -} from '../../../../shared/extra-utils/videos/video-captions' -import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model' + PeerTubeServer, + setAccessTokensToServers, + testCaptionFile, + wait, + waitJobs +} from '@shared/extra-utils' const expect = chai.expect describe('Test video captions', function () { const uuidRegex = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' - let servers: ServerInfo[] + let servers: PeerTubeServer[] let videoUUID: string before(async function () { this.timeout(60000) - servers = await flushAndRunMultipleServers(2) + servers = await createMultipleServers(2) await setAccessTokensToServers(servers) await doubleFollow(servers[0], servers[1]) await waitJobs(servers) - const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'my video name' }) - videoUUID = res.body.video.uuid + const { uuid } = await servers[0].videos.upload({ attributes: { name: 'my video name' } }) + videoUUID = uuid await waitJobs(servers) }) it('Should list the captions and return an empty list', async function () { for (const server of servers) { - const res = await listVideoCaptions(server.url, videoUUID) - expect(res.body.total).to.equal(0) - expect(res.body.data).to.have.lengthOf(0) + const body = await server.captions.list({ videoId: videoUUID }) + expect(body.total).to.equal(0) + expect(body.data).to.have.lengthOf(0) } }) it('Should create two new captions', async function () { this.timeout(30000) - await createVideoCaption({ - url: servers[0].url, - accessToken: servers[0].accessToken, + await servers[0].captions.add({ language: 'ar', videoId: videoUUID, fixture: 'subtitle-good1.vtt' }) - await createVideoCaption({ - url: servers[0].url, - accessToken: servers[0].accessToken, + await servers[0].captions.add({ language: 'zh', videoId: videoUUID, fixture: 'subtitle-good2.vtt', @@ -78,17 +67,17 @@ describe('Test video captions', function () { it('Should list these uploaded captions', async function () { for (const server of servers) { - const res = await listVideoCaptions(server.url, videoUUID) - expect(res.body.total).to.equal(2) - expect(res.body.data).to.have.lengthOf(2) + const body = await server.captions.list({ videoId: videoUUID }) + expect(body.total).to.equal(2) + expect(body.data).to.have.lengthOf(2) - const caption1: VideoCaption = res.body.data[0] + const caption1 = body.data[0] expect(caption1.language.id).to.equal('ar') expect(caption1.language.label).to.equal('Arabic') expect(caption1.captionPath).to.match(new RegExp('^/lazy-static/video-captions/' + uuidRegex + '-ar.vtt$')) await testCaptionFile(server.url, caption1.captionPath, 'Subtitle good 1.') - const caption2: VideoCaption = res.body.data[1] + const caption2 = body.data[1] expect(caption2.language.id).to.equal('zh') expect(caption2.language.label).to.equal('Chinese') expect(caption2.captionPath).to.match(new RegExp('^/lazy-static/video-captions/' + uuidRegex + '-zh.vtt$')) @@ -99,9 +88,7 @@ describe('Test video captions', function () { it('Should replace an existing caption', async function () { this.timeout(30000) - await createVideoCaption({ - url: servers[0].url, - accessToken: servers[0].accessToken, + await servers[0].captions.add({ language: 'ar', videoId: videoUUID, fixture: 'subtitle-good2.vtt' @@ -112,11 +99,11 @@ describe('Test video captions', function () { it('Should have this caption updated', async function () { for (const server of servers) { - const res = await listVideoCaptions(server.url, videoUUID) - expect(res.body.total).to.equal(2) - expect(res.body.data).to.have.lengthOf(2) + const body = await server.captions.list({ videoId: videoUUID }) + expect(body.total).to.equal(2) + expect(body.data).to.have.lengthOf(2) - const caption1: VideoCaption = res.body.data[0] + const caption1 = body.data[0] expect(caption1.language.id).to.equal('ar') expect(caption1.language.label).to.equal('Arabic') expect(caption1.captionPath).to.match(new RegExp('^/lazy-static/video-captions/' + uuidRegex + '-ar.vtt$')) @@ -127,9 +114,7 @@ describe('Test video captions', function () { it('Should replace an existing caption with a srt file and convert it', async function () { this.timeout(30000) - await createVideoCaption({ - url: servers[0].url, - accessToken: servers[0].accessToken, + await servers[0].captions.add({ language: 'ar', videoId: videoUUID, fixture: 'subtitle-good.srt' @@ -143,11 +128,11 @@ describe('Test video captions', function () { it('Should have this caption updated and converted', async function () { for (const server of servers) { - const res = await listVideoCaptions(server.url, videoUUID) - expect(res.body.total).to.equal(2) - expect(res.body.data).to.have.lengthOf(2) + const body = await server.captions.list({ videoId: videoUUID }) + expect(body.total).to.equal(2) + expect(body.data).to.have.lengthOf(2) - const caption1: VideoCaption = res.body.data[0] + const caption1 = body.data[0] expect(caption1.language.id).to.equal('ar') expect(caption1.language.label).to.equal('Arabic') expect(caption1.captionPath).to.match(new RegExp('^/lazy-static/video-captions/' + uuidRegex + '-ar.vtt$')) @@ -172,18 +157,18 @@ describe('Test video captions', function () { it('Should remove one caption', async function () { this.timeout(30000) - await deleteVideoCaption(servers[0].url, servers[0].accessToken, videoUUID, 'ar') + await servers[0].captions.delete({ videoId: videoUUID, language: 'ar' }) await waitJobs(servers) }) it('Should only list the caption that was not deleted', async function () { for (const server of servers) { - const res = await listVideoCaptions(server.url, videoUUID) - expect(res.body.total).to.equal(1) - expect(res.body.data).to.have.lengthOf(1) + const body = await server.captions.list({ videoId: videoUUID }) + expect(body.total).to.equal(1) + expect(body.data).to.have.lengthOf(1) - const caption: VideoCaption = res.body.data[0] + const caption = body.data[0] expect(caption.language.id).to.equal('zh') expect(caption.language.label).to.equal('Chinese') @@ -193,9 +178,12 @@ describe('Test video captions', function () { }) it('Should remove the video, and thus all video captions', async function () { - await removeVideo(servers[0].url, servers[0].accessToken, videoUUID) + const video = await servers[0].videos.get({ id: videoUUID }) + const { data: captions } = await servers[0].captions.list({ videoId: videoUUID }) + + await servers[0].videos.remove({ id: videoUUID }) - await checkVideoFilesWereRemoved(videoUUID, 1) + await checkVideoFilesWereRemoved({ server: servers[0], video, captions }) }) after(async function () { diff --git a/server/tests/api/videos/video-change-ownership.ts b/server/tests/api/videos/video-change-ownership.ts index a3384851b..d6665fe4e 100644 --- a/server/tests/api/videos/video-change-ownership.ts +++ b/server/tests/api/videos/video-change-ownership.ts @@ -2,234 +2,212 @@ import 'mocha' import * as chai from 'chai' -import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' import { - acceptChangeOwnership, - changeVideoOwnership, + ChangeOwnershipCommand, cleanupTests, - createLive, - createUser, + createMultipleServers, + createSingleServer, doubleFollow, - flushAndRunMultipleServers, - flushAndRunServer, - getMyUserInformation, - getVideo, - getVideoChangeOwnershipList, - getVideosList, - refuseChangeOwnership, - ServerInfo, + PeerTubeServer, setAccessTokensToServers, setDefaultVideoChannel, - updateCustomSubConfig, - uploadVideo, - userLogin -} from '../../../../shared/extra-utils' -import { waitJobs } from '../../../../shared/extra-utils/server/jobs' -import { User } from '../../../../shared/models/users' -import { VideoDetails, VideoPrivacy } from '../../../../shared/models/videos' + waitJobs +} from '@shared/extra-utils' +import { HttpStatusCode, VideoPrivacy } from '@shared/models' const expect = chai.expect describe('Test video change ownership - nominal', function () { - let servers: ServerInfo[] = [] - const firstUser = { - username: 'first', - password: 'My great password' - } - const secondUser = { - username: 'second', - password: 'My other password' - } - - let firstUserAccessToken = '' + let servers: PeerTubeServer[] = [] + + const firstUser = 'first' + const secondUser = 'second' + + let firstUserToken = '' let firstUserChannelId: number - let secondUserAccessToken = '' + let secondUserToken = '' let secondUserChannelId: number - let lastRequestChangeOwnershipId = '' + let lastRequestId: number let liveId: number + let command: ChangeOwnershipCommand + before(async function () { this.timeout(50000) - servers = await flushAndRunMultipleServers(2) + servers = await createMultipleServers(2) await setAccessTokensToServers(servers) await setDefaultVideoChannel(servers) - await updateCustomSubConfig(servers[0].url, servers[0].accessToken, { - transcoding: { - enabled: false - }, - live: { - enabled: true + await servers[0].config.updateCustomSubConfig({ + newConfig: { + transcoding: { + enabled: false + }, + live: { + enabled: true + } } }) - const videoQuota = 42000000 - await createUser({ - url: servers[0].url, - accessToken: servers[0].accessToken, - username: firstUser.username, - password: firstUser.password, - videoQuota: videoQuota - }) - await createUser({ - url: servers[0].url, - accessToken: servers[0].accessToken, - username: secondUser.username, - password: secondUser.password, - videoQuota: videoQuota - }) - - firstUserAccessToken = await userLogin(servers[0], firstUser) - secondUserAccessToken = await userLogin(servers[0], secondUser) + firstUserToken = await servers[0].users.generateUserAndToken(firstUser) + secondUserToken = await servers[0].users.generateUserAndToken(secondUser) { - const res = await getMyUserInformation(servers[0].url, firstUserAccessToken) - const firstUserInformation: User = res.body - firstUserChannelId = firstUserInformation.videoChannels[0].id + const { videoChannels } = await servers[0].users.getMyInfo({ token: firstUserToken }) + firstUserChannelId = videoChannels[0].id } { - const res = await getMyUserInformation(servers[0].url, secondUserAccessToken) - const secondUserInformation: User = res.body - secondUserChannelId = secondUserInformation.videoChannels[0].id + const { videoChannels } = await servers[0].users.getMyInfo({ token: secondUserToken }) + secondUserChannelId = videoChannels[0].id } { - const videoAttributes = { + const attributes = { name: 'my super name', description: 'my super description' } - const res = await uploadVideo(servers[0].url, firstUserAccessToken, videoAttributes) + const { id } = await servers[0].videos.upload({ token: firstUserToken, attributes }) - const resVideo = await getVideo(servers[0].url, res.body.video.id) - servers[0].video = resVideo.body + servers[0].store.videoCreated = await servers[0].videos.get({ id }) } { const attributes = { name: 'live', channelId: firstUserChannelId, privacy: VideoPrivacy.PUBLIC } - const res = await createLive(servers[0].url, firstUserAccessToken, attributes) + const video = await servers[0].live.create({ token: firstUserToken, fields: attributes }) - liveId = res.body.video.id + liveId = video.id } + command = servers[0].changeOwnership + await doubleFollow(servers[0], servers[1]) }) it('Should not have video change ownership', async function () { - const resFirstUser = await getVideoChangeOwnershipList(servers[0].url, firstUserAccessToken) + { + const body = await command.list({ token: firstUserToken }) - expect(resFirstUser.body.total).to.equal(0) - expect(resFirstUser.body.data).to.be.an('array') - expect(resFirstUser.body.data.length).to.equal(0) + expect(body.total).to.equal(0) + expect(body.data).to.be.an('array') + expect(body.data.length).to.equal(0) + } - const resSecondUser = await getVideoChangeOwnershipList(servers[0].url, secondUserAccessToken) + { + const body = await command.list({ token: secondUserToken }) - expect(resSecondUser.body.total).to.equal(0) - expect(resSecondUser.body.data).to.be.an('array') - expect(resSecondUser.body.data.length).to.equal(0) + expect(body.total).to.equal(0) + expect(body.data).to.be.an('array') + expect(body.data.length).to.equal(0) + } }) it('Should send a request to change ownership of a video', async function () { this.timeout(15000) - await changeVideoOwnership(servers[0].url, firstUserAccessToken, servers[0].video.id, secondUser.username) + await command.create({ token: firstUserToken, videoId: servers[0].store.videoCreated.id, username: secondUser }) }) it('Should only return a request to change ownership for the second user', async function () { - const resFirstUser = await getVideoChangeOwnershipList(servers[0].url, firstUserAccessToken) + { + const body = await command.list({ token: firstUserToken }) - expect(resFirstUser.body.total).to.equal(0) - expect(resFirstUser.body.data).to.be.an('array') - expect(resFirstUser.body.data.length).to.equal(0) + expect(body.total).to.equal(0) + expect(body.data).to.be.an('array') + expect(body.data.length).to.equal(0) + } - const resSecondUser = await getVideoChangeOwnershipList(servers[0].url, secondUserAccessToken) + { + const body = await command.list({ token: secondUserToken }) - expect(resSecondUser.body.total).to.equal(1) - expect(resSecondUser.body.data).to.be.an('array') - expect(resSecondUser.body.data.length).to.equal(1) + expect(body.total).to.equal(1) + expect(body.data).to.be.an('array') + expect(body.data.length).to.equal(1) - lastRequestChangeOwnershipId = resSecondUser.body.data[0].id + lastRequestId = body.data[0].id + } }) it('Should accept the same change ownership request without crashing', async function () { this.timeout(10000) - await changeVideoOwnership(servers[0].url, firstUserAccessToken, servers[0].video.id, secondUser.username) + await command.create({ token: firstUserToken, videoId: servers[0].store.videoCreated.id, username: secondUser }) }) it('Should not create multiple change ownership requests while one is waiting', async function () { this.timeout(10000) - const resSecondUser = await getVideoChangeOwnershipList(servers[0].url, secondUserAccessToken) + const body = await command.list({ token: secondUserToken }) - expect(resSecondUser.body.total).to.equal(1) - expect(resSecondUser.body.data).to.be.an('array') - expect(resSecondUser.body.data.length).to.equal(1) + expect(body.total).to.equal(1) + expect(body.data).to.be.an('array') + expect(body.data.length).to.equal(1) }) it('Should not be possible to refuse the change of ownership from first user', async function () { this.timeout(10000) - await refuseChangeOwnership(servers[0].url, firstUserAccessToken, lastRequestChangeOwnershipId, HttpStatusCode.FORBIDDEN_403) + await command.refuse({ token: firstUserToken, ownershipId: lastRequestId, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) }) it('Should be possible to refuse the change of ownership from second user', async function () { this.timeout(10000) - await refuseChangeOwnership(servers[0].url, secondUserAccessToken, lastRequestChangeOwnershipId) + await command.refuse({ token: secondUserToken, ownershipId: lastRequestId }) }) it('Should send a new request to change ownership of a video', async function () { this.timeout(15000) - await changeVideoOwnership(servers[0].url, firstUserAccessToken, servers[0].video.id, secondUser.username) + await command.create({ token: firstUserToken, videoId: servers[0].store.videoCreated.id, username: secondUser }) }) it('Should return two requests to change ownership for the second user', async function () { - const resFirstUser = await getVideoChangeOwnershipList(servers[0].url, firstUserAccessToken) + { + const body = await command.list({ token: firstUserToken }) - expect(resFirstUser.body.total).to.equal(0) - expect(resFirstUser.body.data).to.be.an('array') - expect(resFirstUser.body.data.length).to.equal(0) + expect(body.total).to.equal(0) + expect(body.data).to.be.an('array') + expect(body.data.length).to.equal(0) + } - const resSecondUser = await getVideoChangeOwnershipList(servers[0].url, secondUserAccessToken) + { + const body = await command.list({ token: secondUserToken }) - expect(resSecondUser.body.total).to.equal(2) - expect(resSecondUser.body.data).to.be.an('array') - expect(resSecondUser.body.data.length).to.equal(2) + expect(body.total).to.equal(2) + expect(body.data).to.be.an('array') + expect(body.data.length).to.equal(2) - lastRequestChangeOwnershipId = resSecondUser.body.data[0].id + lastRequestId = body.data[0].id + } }) it('Should not be possible to accept the change of ownership from first user', async function () { this.timeout(10000) - await acceptChangeOwnership( - servers[0].url, - firstUserAccessToken, - lastRequestChangeOwnershipId, - secondUserChannelId, - HttpStatusCode.FORBIDDEN_403 - ) + await command.accept({ + token: firstUserToken, + ownershipId: lastRequestId, + channelId: secondUserChannelId, + expectedStatus: HttpStatusCode.FORBIDDEN_403 + }) }) it('Should be possible to accept the change of ownership from second user', async function () { this.timeout(10000) - await acceptChangeOwnership(servers[0].url, secondUserAccessToken, lastRequestChangeOwnershipId, secondUserChannelId) + await command.accept({ token: secondUserToken, ownershipId: lastRequestId, channelId: secondUserChannelId }) await waitJobs(servers) }) it('Should have the channel of the video updated', async function () { for (const server of servers) { - const res = await getVideo(server.url, servers[0].video.uuid) - - const video: VideoDetails = res.body + const video = await server.videos.get({ id: servers[0].store.videoCreated.uuid }) expect(video.name).to.equal('my super name') expect(video.channel.displayName).to.equal('Main second channel') @@ -240,27 +218,25 @@ describe('Test video change ownership - nominal', function () { it('Should send a request to change ownership of a live', async function () { this.timeout(15000) - await changeVideoOwnership(servers[0].url, firstUserAccessToken, liveId, secondUser.username) + await command.create({ token: firstUserToken, videoId: liveId, username: secondUser }) - const resSecondUser = await getVideoChangeOwnershipList(servers[0].url, secondUserAccessToken) + const body = await command.list({ token: secondUserToken }) - expect(resSecondUser.body.total).to.equal(3) - expect(resSecondUser.body.data.length).to.equal(3) + expect(body.total).to.equal(3) + expect(body.data.length).to.equal(3) - lastRequestChangeOwnershipId = resSecondUser.body.data[0].id + lastRequestId = body.data[0].id }) it('Should accept a live ownership change', async function () { this.timeout(20000) - await acceptChangeOwnership(servers[0].url, secondUserAccessToken, lastRequestChangeOwnershipId, secondUserChannelId) + await command.accept({ token: secondUserToken, ownershipId: lastRequestId, channelId: secondUserChannelId }) await waitJobs(servers) for (const server of servers) { - const res = await getVideo(server.url, servers[0].video.uuid) - - const video: VideoDetails = res.body + const video = await server.videos.get({ id: servers[0].store.videoCreated.uuid }) expect(video.name).to.equal('my super name') expect(video.channel.displayName).to.equal('Main second channel') @@ -274,99 +250,79 @@ describe('Test video change ownership - nominal', function () { }) describe('Test video change ownership - quota too small', function () { - let server: ServerInfo - const firstUser = { - username: 'first', - password: 'My great password' - } - const secondUser = { - username: 'second', - password: 'My other password' - } - let firstUserAccessToken = '' - let secondUserAccessToken = '' - let lastRequestChangeOwnershipId = '' + let server: PeerTubeServer + const firstUser = 'first' + const secondUser = 'second' + + let firstUserToken = '' + let secondUserToken = '' + let lastRequestId: number before(async function () { this.timeout(50000) // Run one server - server = await flushAndRunServer(1) + server = await createSingleServer(1) await setAccessTokensToServers([ server ]) - const videoQuota = 42000000 - const limitedVideoQuota = 10 - await createUser({ - url: server.url, - accessToken: server.accessToken, - username: firstUser.username, - password: firstUser.password, - videoQuota: videoQuota - }) - await createUser({ - url: server.url, - accessToken: server.accessToken, - username: secondUser.username, - password: secondUser.password, - videoQuota: limitedVideoQuota - }) + await server.users.create({ username: secondUser, videoQuota: 10 }) - firstUserAccessToken = await userLogin(server, firstUser) - secondUserAccessToken = await userLogin(server, secondUser) + firstUserToken = await server.users.generateUserAndToken(firstUser) + secondUserToken = await server.login.getAccessToken(secondUser) // Upload some videos on the server - const video1Attributes = { + const attributes = { name: 'my super name', description: 'my super description' } - await uploadVideo(server.url, firstUserAccessToken, video1Attributes) + await server.videos.upload({ token: firstUserToken, attributes }) await waitJobs(server) - const res = await getVideosList(server.url) - const videos = res.body.data - - expect(videos.length).to.equal(1) + const { data } = await server.videos.list() + expect(data.length).to.equal(1) - server.video = videos.find(video => video.name === 'my super name') + server.store.videoCreated = data.find(video => video.name === 'my super name') }) it('Should send a request to change ownership of a video', async function () { this.timeout(15000) - await changeVideoOwnership(server.url, firstUserAccessToken, server.video.id, secondUser.username) + await server.changeOwnership.create({ token: firstUserToken, videoId: server.store.videoCreated.id, username: secondUser }) }) it('Should only return a request to change ownership for the second user', async function () { - const resFirstUser = await getVideoChangeOwnershipList(server.url, firstUserAccessToken) + { + const body = await server.changeOwnership.list({ token: firstUserToken }) - expect(resFirstUser.body.total).to.equal(0) - expect(resFirstUser.body.data).to.be.an('array') - expect(resFirstUser.body.data.length).to.equal(0) + expect(body.total).to.equal(0) + expect(body.data).to.be.an('array') + expect(body.data.length).to.equal(0) + } - const resSecondUser = await getVideoChangeOwnershipList(server.url, secondUserAccessToken) + { + const body = await server.changeOwnership.list({ token: secondUserToken }) - expect(resSecondUser.body.total).to.equal(1) - expect(resSecondUser.body.data).to.be.an('array') - expect(resSecondUser.body.data.length).to.equal(1) + expect(body.total).to.equal(1) + expect(body.data).to.be.an('array') + expect(body.data.length).to.equal(1) - lastRequestChangeOwnershipId = resSecondUser.body.data[0].id + lastRequestId = body.data[0].id + } }) it('Should not be possible to accept the change of ownership from second user because of exceeded quota', async function () { this.timeout(10000) - const secondUserInformationResponse = await getMyUserInformation(server.url, secondUserAccessToken) - const secondUserInformation: User = secondUserInformationResponse.body - const channelId = secondUserInformation.videoChannels[0].id + const { videoChannels } = await server.users.getMyInfo({ token: secondUserToken }) + const channelId = videoChannels[0].id - await acceptChangeOwnership( - server.url, - secondUserAccessToken, - lastRequestChangeOwnershipId, + await server.changeOwnership.accept({ + token: secondUserToken, + ownershipId: lastRequestId, channelId, - HttpStatusCode.PAYLOAD_TOO_LARGE_413 - ) + expectedStatus: HttpStatusCode.PAYLOAD_TOO_LARGE_413 + }) }) after(async function () { diff --git a/server/tests/api/videos/video-channels.ts b/server/tests/api/videos/video-channels.ts index 865098777..c25754eb6 100644 --- a/server/tests/api/videos/video-channels.ts +++ b/server/tests/api/videos/video-channels.ts @@ -6,48 +6,28 @@ import { basename } from 'path' import { ACTOR_IMAGES_SIZE } from '@server/initializers/constants' import { cleanupTests, - createUser, - deleteVideoChannelImage, + createMultipleServers, doubleFollow, - flushAndRunMultipleServers, - getActorImage, - getVideo, - getVideoChannel, - getVideoChannelVideos, + PeerTubeServer, + setAccessTokensToServers, setDefaultVideoChannel, testFileExistsOrNot, testImage, - updateVideo, - updateVideoChannelImage, - uploadVideo, - userLogin, - wait -} from '../../../../shared/extra-utils' -import { - addVideoChannel, - deleteVideoChannel, - getAccountVideoChannelsList, - getMyUserInformation, - getVideoChannelsList, - ServerInfo, - setAccessTokensToServers, - updateVideoChannel, - viewVideo -} from '../../../../shared/extra-utils/index' -import { waitJobs } from '../../../../shared/extra-utils/server/jobs' -import { User, Video, VideoChannel, VideoDetails } from '../../../../shared/index' + wait, + waitJobs +} from '@shared/extra-utils' +import { User, VideoChannel } from '@shared/models' const expect = chai.expect -async function findChannel (server: ServerInfo, channelId: number) { - const res = await getVideoChannelsList(server.url, 0, 5, '-name') - const videoChannel = res.body.data.find(c => c.id === channelId) +async function findChannel (server: PeerTubeServer, channelId: number) { + const body = await server.channels.list({ sort: '-name' }) - return videoChannel as VideoChannel + return body.data.find(c => c.id === channelId) } describe('Test video channels', function () { - let servers: ServerInfo[] + let servers: PeerTubeServer[] let userInfo: User let secondVideoChannelId: number let totoChannel: number @@ -60,7 +40,7 @@ describe('Test video channels', function () { before(async function () { this.timeout(60000) - servers = await flushAndRunMultipleServers(2) + servers = await createMultipleServers(2) await setAccessTokensToServers(servers) await setDefaultVideoChannel(servers) @@ -69,11 +49,11 @@ describe('Test video channels', function () { }) it('Should have one video channel (created with root)', async () => { - const res = await getVideoChannelsList(servers[0].url, 0, 2) + const body = await servers[0].channels.list({ start: 0, count: 2 }) - expect(res.body.total).to.equal(1) - expect(res.body.data).to.be.an('array') - expect(res.body.data).to.have.lengthOf(1) + expect(body.total).to.equal(1) + expect(body.data).to.be.an('array') + expect(body.data).to.have.lengthOf(1) }) it('Should create another video channel', async function () { @@ -86,23 +66,22 @@ describe('Test video channels', function () { description: 'super video channel description', support: 'super video channel support text' } - const res = await addVideoChannel(servers[0].url, servers[0].accessToken, videoChannel) - secondVideoChannelId = res.body.videoChannel.id + const created = await servers[0].channels.create({ attributes: videoChannel }) + secondVideoChannelId = created.id } // The channel is 1 is propagated to servers 2 { - const videoAttributesArg = { name: 'my video name', channelId: secondVideoChannelId, support: 'video support field' } - const res = await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributesArg) - videoUUID = res.body.video.uuid + const attributes = { name: 'my video name', channelId: secondVideoChannelId, support: 'video support field' } + const { uuid } = await servers[0].videos.upload({ attributes }) + videoUUID = uuid } await waitJobs(servers) }) it('Should have two video channels when getting my information', async () => { - const res = await getMyUserInformation(servers[0].url, servers[0].accessToken) - userInfo = res.body + userInfo = await servers[0].users.getMyInfo() expect(userInfo.videoChannels).to.be.an('array') expect(userInfo.videoChannels).to.have.lengthOf(2) @@ -120,16 +99,14 @@ describe('Test video channels', function () { }) it('Should have two video channels when getting account channels on server 1', async function () { - const res = await getAccountVideoChannelsList({ - url: servers[0].url, - accountName - }) + const body = await servers[0].channels.listByAccount({ accountName }) + expect(body.total).to.equal(2) + + const videoChannels = body.data - expect(res.body.total).to.equal(2) - expect(res.body.data).to.be.an('array') - expect(res.body.data).to.have.lengthOf(2) + expect(videoChannels).to.be.an('array') + expect(videoChannels).to.have.lengthOf(2) - const videoChannels = res.body.data expect(videoChannels[0].name).to.equal('root_channel') expect(videoChannels[0].displayName).to.equal('Main root channel') @@ -141,79 +118,69 @@ describe('Test video channels', function () { it('Should paginate and sort account channels', async function () { { - const res = await getAccountVideoChannelsList({ - url: servers[0].url, + const body = await servers[0].channels.listByAccount({ accountName, start: 0, count: 1, sort: 'createdAt' }) - expect(res.body.total).to.equal(2) - expect(res.body.data).to.have.lengthOf(1) + expect(body.total).to.equal(2) + expect(body.data).to.have.lengthOf(1) - const videoChannel: VideoChannel = res.body.data[0] + const videoChannel: VideoChannel = body.data[0] expect(videoChannel.name).to.equal('root_channel') } { - const res = await getAccountVideoChannelsList({ - url: servers[0].url, + const body = await servers[0].channels.listByAccount({ accountName, start: 0, count: 1, sort: '-createdAt' }) - expect(res.body.total).to.equal(2) - expect(res.body.data).to.have.lengthOf(1) - - const videoChannel: VideoChannel = res.body.data[0] - expect(videoChannel.name).to.equal('second_video_channel') + expect(body.total).to.equal(2) + expect(body.data).to.have.lengthOf(1) + expect(body.data[0].name).to.equal('second_video_channel') } { - const res = await getAccountVideoChannelsList({ - url: servers[0].url, + const body = await servers[0].channels.listByAccount({ accountName, start: 1, count: 1, sort: '-createdAt' }) - expect(res.body.total).to.equal(2) - expect(res.body.data).to.have.lengthOf(1) - - const videoChannel: VideoChannel = res.body.data[0] - expect(videoChannel.name).to.equal('root_channel') + expect(body.total).to.equal(2) + expect(body.data).to.have.lengthOf(1) + expect(body.data[0].name).to.equal('root_channel') } }) it('Should have one video channel when getting account channels on server 2', async function () { - const res = await getAccountVideoChannelsList({ - url: servers[1].url, - accountName - }) + const body = await servers[1].channels.listByAccount({ accountName }) - expect(res.body.total).to.equal(1) - expect(res.body.data).to.be.an('array') - expect(res.body.data).to.have.lengthOf(1) + expect(body.total).to.equal(1) + expect(body.data).to.be.an('array') + expect(body.data).to.have.lengthOf(1) - const videoChannels = res.body.data - expect(videoChannels[0].name).to.equal('second_video_channel') - expect(videoChannels[0].displayName).to.equal('second video channel') - expect(videoChannels[0].description).to.equal('super video channel description') - expect(videoChannels[0].support).to.equal('super video channel support text') + const videoChannel = body.data[0] + expect(videoChannel.name).to.equal('second_video_channel') + expect(videoChannel.displayName).to.equal('second video channel') + expect(videoChannel.description).to.equal('super video channel description') + expect(videoChannel.support).to.equal('super video channel support text') }) it('Should list video channels', async function () { - const res = await getVideoChannelsList(servers[0].url, 1, 1, '-name') + const body = await servers[0].channels.list({ start: 1, count: 1, sort: '-name' }) - expect(res.body.total).to.equal(2) - expect(res.body.data).to.be.an('array') - expect(res.body.data).to.have.lengthOf(1) - expect(res.body.data[0].name).to.equal('root_channel') - expect(res.body.data[0].displayName).to.equal('Main root channel') + expect(body.total).to.equal(2) + expect(body.data).to.be.an('array') + expect(body.data).to.have.lengthOf(1) + expect(body.data[0].name).to.equal('root_channel') + expect(body.data[0].displayName).to.equal('Main root channel') }) it('Should update video channel', async function () { @@ -225,30 +192,29 @@ describe('Test video channels', function () { support: 'support updated' } - await updateVideoChannel(servers[0].url, servers[0].accessToken, 'second_video_channel', videoChannelAttributes) + await servers[0].channels.update({ channelName: 'second_video_channel', attributes: videoChannelAttributes }) await waitJobs(servers) }) it('Should have video channel updated', async function () { for (const server of servers) { - const res = await getVideoChannelsList(server.url, 0, 1, '-name') - - expect(res.body.total).to.equal(2) - expect(res.body.data).to.be.an('array') - expect(res.body.data).to.have.lengthOf(1) - expect(res.body.data[0].name).to.equal('second_video_channel') - expect(res.body.data[0].displayName).to.equal('video channel updated') - expect(res.body.data[0].description).to.equal('video channel description updated') - expect(res.body.data[0].support).to.equal('support updated') + const body = await server.channels.list({ start: 0, count: 1, sort: '-name' }) + + expect(body.total).to.equal(2) + expect(body.data).to.be.an('array') + expect(body.data).to.have.lengthOf(1) + + expect(body.data[0].name).to.equal('second_video_channel') + expect(body.data[0].displayName).to.equal('video channel updated') + expect(body.data[0].description).to.equal('video channel description updated') + expect(body.data[0].support).to.equal('support updated') } }) it('Should not have updated the video support field', async function () { for (const server of servers) { - const res = await getVideo(server.url, videoUUID) - const video: VideoDetails = res.body - + const video = await server.videos.get({ id: videoUUID }) expect(video.support).to.equal('video support field') } }) @@ -261,14 +227,12 @@ describe('Test video channels', function () { bulkVideosSupportUpdate: true } - await updateVideoChannel(servers[0].url, servers[0].accessToken, 'second_video_channel', videoChannelAttributes) + await servers[0].channels.update({ channelName: 'second_video_channel', attributes: videoChannelAttributes }) await waitJobs(servers) for (const server of servers) { - const res = await getVideo(server.url, videoUUID) - const video: VideoDetails = res.body - + const video = await server.videos.get({ id: videoUUID }) expect(video.support).to.equal(videoChannelAttributes.support) } }) @@ -278,10 +242,8 @@ describe('Test video channels', function () { const fixture = 'avatar.png' - await updateVideoChannelImage({ - url: servers[0].url, - accessToken: servers[0].accessToken, - videoChannelName: 'second_video_channel', + await servers[0].channels.updateImage({ + channelName: 'second_video_channel', fixture, type: 'avatar' }) @@ -295,7 +257,7 @@ describe('Test video channels', function () { await testImage(server.url, 'avatar-resized', avatarPaths[server.port], '.png') await testFileExistsOrNot(server, 'avatars', basename(avatarPaths[server.port]), true) - const row = await getActorImage(server.internalServerNumber, basename(avatarPaths[server.port])) + const row = await server.sql.getActorImage(basename(avatarPaths[server.port])) expect(row.height).to.equal(ACTOR_IMAGES_SIZE.AVATARS.height) expect(row.width).to.equal(ACTOR_IMAGES_SIZE.AVATARS.width) } @@ -306,10 +268,8 @@ describe('Test video channels', function () { const fixture = 'banner.jpg' - await updateVideoChannelImage({ - url: servers[0].url, - accessToken: servers[0].accessToken, - videoChannelName: 'second_video_channel', + await servers[0].channels.updateImage({ + channelName: 'second_video_channel', fixture, type: 'banner' }) @@ -317,14 +277,13 @@ describe('Test video channels', function () { await waitJobs(servers) for (const server of servers) { - const res = await getVideoChannel(server.url, 'second_video_channel@' + servers[0].host) - const videoChannel = res.body + const videoChannel = await server.channels.get({ channelName: 'second_video_channel@' + servers[0].host }) bannerPaths[server.port] = videoChannel.banner.path await testImage(server.url, 'banner-resized', bannerPaths[server.port]) await testFileExistsOrNot(server, 'avatars', basename(bannerPaths[server.port]), true) - const row = await getActorImage(server.internalServerNumber, basename(bannerPaths[server.port])) + const row = await server.sql.getActorImage(basename(bannerPaths[server.port])) expect(row.height).to.equal(ACTOR_IMAGES_SIZE.BANNERS.height) expect(row.width).to.equal(ACTOR_IMAGES_SIZE.BANNERS.width) } @@ -333,12 +292,7 @@ describe('Test video channels', function () { it('Should delete the video channel avatar', async function () { this.timeout(15000) - await deleteVideoChannelImage({ - url: servers[0].url, - accessToken: servers[0].accessToken, - videoChannelName: 'second_video_channel', - type: 'avatar' - }) + await servers[0].channels.deleteImage({ channelName: 'second_video_channel', type: 'avatar' }) await waitJobs(servers) @@ -353,12 +307,7 @@ describe('Test video channels', function () { it('Should delete the video channel banner', async function () { this.timeout(15000) - await deleteVideoChannelImage({ - url: servers[0].url, - accessToken: servers[0].accessToken, - videoChannelName: 'second_video_channel', - type: 'banner' - }) + await servers[0].channels.deleteImage({ channelName: 'second_video_channel', type: 'banner' }) await waitJobs(servers) @@ -375,18 +324,19 @@ describe('Test video channels', function () { for (const server of servers) { const channelURI = 'second_video_channel@localhost:' + servers[0].port - const res1 = await getVideoChannelVideos(server.url, server.accessToken, channelURI, 0, 5) - expect(res1.body.total).to.equal(1) - expect(res1.body.data).to.be.an('array') - expect(res1.body.data).to.have.lengthOf(1) - expect(res1.body.data[0].name).to.equal('my video name') + const { total, data } = await server.videos.listByChannel({ handle: channelURI }) + + expect(total).to.equal(1) + expect(data).to.be.an('array') + expect(data).to.have.lengthOf(1) + expect(data[0].name).to.equal('my video name') } }) it('Should change the video channel of a video', async function () { this.timeout(10000) - await updateVideo(servers[0].url, servers[0].accessToken, videoUUID, { channelId: servers[0].videoChannel.id }) + await servers[0].videos.update({ id: videoUUID, attributes: { channelId: servers[0].store.channel.id } }) await waitJobs(servers) }) @@ -395,47 +345,50 @@ describe('Test video channels', function () { this.timeout(10000) for (const server of servers) { - const secondChannelURI = 'second_video_channel@localhost:' + servers[0].port - const res1 = await getVideoChannelVideos(server.url, server.accessToken, secondChannelURI, 0, 5) - expect(res1.body.total).to.equal(0) - - const channelURI = 'root_channel@localhost:' + servers[0].port - const res2 = await getVideoChannelVideos(server.url, server.accessToken, channelURI, 0, 5) - expect(res2.body.total).to.equal(1) - - const videos: Video[] = res2.body.data - expect(videos).to.be.an('array') - expect(videos).to.have.lengthOf(1) - expect(videos[0].name).to.equal('my video name') + { + const secondChannelURI = 'second_video_channel@localhost:' + servers[0].port + const { total } = await server.videos.listByChannel({ handle: secondChannelURI }) + expect(total).to.equal(0) + } + + { + const channelURI = 'root_channel@localhost:' + servers[0].port + const { total, data } = await server.videos.listByChannel({ handle: channelURI }) + expect(total).to.equal(1) + + expect(data).to.be.an('array') + expect(data).to.have.lengthOf(1) + expect(data[0].name).to.equal('my video name') + } } }) it('Should delete video channel', async function () { - await deleteVideoChannel(servers[0].url, servers[0].accessToken, 'second_video_channel') + await servers[0].channels.delete({ channelName: 'second_video_channel' }) }) it('Should have video channel deleted', async function () { - const res = await getVideoChannelsList(servers[0].url, 0, 10) + const body = await servers[0].channels.list({ start: 0, count: 10 }) - expect(res.body.total).to.equal(1) - expect(res.body.data).to.be.an('array') - expect(res.body.data).to.have.lengthOf(1) - expect(res.body.data[0].displayName).to.equal('Main root channel') + expect(body.total).to.equal(1) + expect(body.data).to.be.an('array') + expect(body.data).to.have.lengthOf(1) + expect(body.data[0].displayName).to.equal('Main root channel') }) it('Should create the main channel with an uuid if there is a conflict', async function () { { const videoChannel = { name: 'toto_channel', displayName: 'My toto channel' } - const res = await addVideoChannel(servers[0].url, servers[0].accessToken, videoChannel) - totoChannel = res.body.videoChannel.id + const created = await servers[0].channels.create({ attributes: videoChannel }) + totoChannel = created.id } { - await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: 'toto', password: 'password' }) - const accessToken = await userLogin(servers[0], { username: 'toto', password: 'password' }) + await servers[0].users.create({ username: 'toto', password: 'password' }) + const accessToken = await servers[0].login.getAccessToken({ username: 'toto', password: 'password' }) - const res = await getMyUserInformation(servers[0].url, accessToken) - const videoChannel = res.body.videoChannels[0] + const { videoChannels } = await servers[0].users.getMyInfo({ token: accessToken }) + const videoChannel = videoChannels[0] expect(videoChannel.name).to.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/) } }) @@ -444,15 +397,9 @@ describe('Test video channels', function () { this.timeout(10000) { - const res = await getAccountVideoChannelsList({ - url: servers[0].url, - accountName, - withStats: true - }) - - const channels: VideoChannel[] = res.body.data + const { data } = await servers[0].channels.listByAccount({ accountName, withStats: true }) - for (const channel of channels) { + for (const channel of data) { expect(channel).to.haveOwnProperty('viewsPerDay') expect(channel.viewsPerDay).to.have.length(30 + 1) // daysPrior + today @@ -464,33 +411,24 @@ describe('Test video channels', function () { } { - // video has been posted on channel servers[0].videoChannel.id since last update - await viewVideo(servers[0].url, videoUUID, 204, '0.0.0.1,127.0.0.1') - await viewVideo(servers[0].url, videoUUID, 204, '0.0.0.2,127.0.0.1') + // video has been posted on channel servers[0].store.videoChannel.id since last update + await servers[0].videos.view({ id: videoUUID, xForwardedFor: '0.0.0.1,127.0.0.1' }) + await servers[0].videos.view({ id: videoUUID, xForwardedFor: '0.0.0.2,127.0.0.1' }) // Wait the repeatable job await wait(8000) - const res = await getAccountVideoChannelsList({ - url: servers[0].url, - accountName, - withStats: true - }) - const channelWithView = res.body.data.find((channel: VideoChannel) => channel.id === servers[0].videoChannel.id) + const { data } = await servers[0].channels.listByAccount({ accountName, withStats: true }) + const channelWithView = data.find(channel => channel.id === servers[0].store.channel.id) expect(channelWithView.viewsPerDay.slice(-1)[0].views).to.equal(2) } }) it('Should report correct videos count', async function () { - const res = await getAccountVideoChannelsList({ - url: servers[0].url, - accountName, - withStats: true - }) - const channels: VideoChannel[] = res.body.data + const { data } = await servers[0].channels.listByAccount({ accountName, withStats: true }) - const totoChannel = channels.find(c => c.name === 'toto_channel') - const rootChannel = channels.find(c => c.name === 'root_channel') + const totoChannel = data.find(c => c.name === 'toto_channel') + const rootChannel = data.find(c => c.name === 'root_channel') expect(rootChannel.videosCount).to.equal(1) expect(totoChannel.videosCount).to.equal(0) @@ -498,26 +436,18 @@ describe('Test video channels', function () { it('Should search among account video channels', async function () { { - const res = await getAccountVideoChannelsList({ - url: servers[0].url, - accountName, - search: 'root' - }) - expect(res.body.total).to.equal(1) + const body = await servers[0].channels.listByAccount({ accountName, search: 'root' }) + expect(body.total).to.equal(1) - const channels = res.body.data + const channels = body.data expect(channels).to.have.lengthOf(1) } { - const res = await getAccountVideoChannelsList({ - url: servers[0].url, - accountName, - search: 'does not exist' - }) - expect(res.body.total).to.equal(0) + const body = await servers[0].channels.listByAccount({ accountName, search: 'does not exist' }) + expect(body.total).to.equal(0) - const channels = res.body.data + const channels = body.data expect(channels).to.have.lengthOf(0) } }) @@ -525,34 +455,24 @@ describe('Test video channels', function () { it('Should list channels by updatedAt desc if a video has been uploaded', async function () { this.timeout(30000) - await uploadVideo(servers[0].url, servers[0].accessToken, { channelId: totoChannel }) + await servers[0].videos.upload({ attributes: { channelId: totoChannel } }) await waitJobs(servers) for (const server of servers) { - const res = await getAccountVideoChannelsList({ - url: server.url, - accountName, - sort: '-updatedAt' - }) + const { data } = await server.channels.listByAccount({ accountName, sort: '-updatedAt' }) - const channels: VideoChannel[] = res.body.data - expect(channels[0].name).to.equal('toto_channel') - expect(channels[1].name).to.equal('root_channel') + expect(data[0].name).to.equal('toto_channel') + expect(data[1].name).to.equal('root_channel') } - await uploadVideo(servers[0].url, servers[0].accessToken, { channelId: servers[0].videoChannel.id }) + await servers[0].videos.upload({ attributes: { channelId: servers[0].store.channel.id } }) await waitJobs(servers) for (const server of servers) { - const res = await getAccountVideoChannelsList({ - url: server.url, - accountName, - sort: '-updatedAt' - }) + const { data } = await server.channels.listByAccount({ accountName, sort: '-updatedAt' }) - const channels: VideoChannel[] = res.body.data - expect(channels[0].name).to.equal('root_channel') - expect(channels[1].name).to.equal('toto_channel') + expect(data[0].name).to.equal('root_channel') + expect(data[1].name).to.equal('toto_channel') } }) diff --git a/server/tests/api/videos/video-comments.ts b/server/tests/api/videos/video-comments.ts index b6b002307..61ee54540 100644 --- a/server/tests/api/videos/video-comments.ts +++ b/server/tests/api/videos/video-comments.ts @@ -2,80 +2,62 @@ import 'mocha' import * as chai from 'chai' -import { VideoComment, VideoCommentAdmin, VideoCommentThreadTree } from '@shared/models' -import { cleanupTests, testImage } from '../../../../shared/extra-utils' import { - createUser, + cleanupTests, + CommentsCommand, + createSingleServer, dateIsValid, - flushAndRunServer, - getAccessToken, - ServerInfo, + PeerTubeServer, setAccessTokensToServers, - updateMyAvatar, - uploadVideo -} from '../../../../shared/extra-utils/index' -import { - addVideoCommentReply, - addVideoCommentThread, - deleteVideoComment, - getAdminVideoComments, - getVideoCommentThreads, - getVideoThreadComments -} from '../../../../shared/extra-utils/videos/video-comments' + testImage +} from '@shared/extra-utils' const expect = chai.expect describe('Test video comments', function () { - let server: ServerInfo - let videoId - let videoUUID - let threadId + let server: PeerTubeServer + let videoId: number + let videoUUID: string + let threadId: number let replyToDeleteId: number let userAccessTokenServer1: string + let command: CommentsCommand + before(async function () { this.timeout(30000) - server = await flushAndRunServer(1) + server = await createSingleServer(1) await setAccessTokensToServers([ server ]) - const res = await uploadVideo(server.url, server.accessToken, {}) - videoUUID = res.body.video.uuid - videoId = res.body.video.id + const { id, uuid } = await server.videos.upload() + videoUUID = uuid + videoId = id - await updateMyAvatar({ - url: server.url, - accessToken: server.accessToken, - fixture: 'avatar.png' - }) + await server.users.updateMyAvatar({ fixture: 'avatar.png' }) - await createUser({ - url: server.url, - accessToken: server.accessToken, - username: 'user1', - password: 'password' - }) - userAccessTokenServer1 = await getAccessToken(server.url, 'user1', 'password') + userAccessTokenServer1 = await server.users.generateUserAndToken('user1') + + command = server.comments }) describe('User comments', function () { it('Should not have threads on this video', async function () { - const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5) + const body = await command.listThreads({ videoId: videoUUID }) - expect(res.body.total).to.equal(0) - expect(res.body.totalNotDeletedComments).to.equal(0) - expect(res.body.data).to.be.an('array') - expect(res.body.data).to.have.lengthOf(0) + expect(body.total).to.equal(0) + expect(body.totalNotDeletedComments).to.equal(0) + expect(body.data).to.be.an('array') + expect(body.data).to.have.lengthOf(0) }) it('Should create a thread in this video', async function () { const text = 'my super first comment' - const res = await addVideoCommentThread(server.url, server.accessToken, videoUUID, text) - const comment = res.body.comment + const comment = await command.createThread({ videoId: videoUUID, text }) expect(comment.inReplyToCommentId).to.be.null expect(comment.text).equal('my super first comment') @@ -91,14 +73,14 @@ describe('Test video comments', function () { }) it('Should list threads of this video', async function () { - const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5) + const body = await command.listThreads({ videoId: videoUUID }) - expect(res.body.total).to.equal(1) - expect(res.body.totalNotDeletedComments).to.equal(1) - expect(res.body.data).to.be.an('array') - expect(res.body.data).to.have.lengthOf(1) + expect(body.total).to.equal(1) + expect(body.totalNotDeletedComments).to.equal(1) + expect(body.data).to.be.an('array') + expect(body.data).to.have.lengthOf(1) - const comment: VideoComment = res.body.data[0] + const comment = body.data[0] expect(comment.inReplyToCommentId).to.be.null expect(comment.text).equal('my super first comment') expect(comment.videoId).to.equal(videoId) @@ -117,9 +99,9 @@ describe('Test video comments', function () { }) it('Should get all the thread created', async function () { - const res = await getVideoThreadComments(server.url, videoUUID, threadId) + const body = await command.getThread({ videoId: videoUUID, threadId }) - const rootComment = res.body.comment + const rootComment = body.comment expect(rootComment.inReplyToCommentId).to.be.null expect(rootComment.text).equal('my super first comment') expect(rootComment.videoId).to.equal(videoId) @@ -129,20 +111,19 @@ describe('Test video comments', function () { it('Should create multiple replies in this thread', async function () { const text1 = 'my super answer to thread 1' - const childCommentRes = await addVideoCommentReply(server.url, server.accessToken, videoId, threadId, text1) - const childCommentId = childCommentRes.body.comment.id + const created = await command.addReply({ videoId, toCommentId: threadId, text: text1 }) + const childCommentId = created.id const text2 = 'my super answer to answer of thread 1' - await addVideoCommentReply(server.url, server.accessToken, videoId, childCommentId, text2) + await command.addReply({ videoId, toCommentId: childCommentId, text: text2 }) const text3 = 'my second answer to thread 1' - await addVideoCommentReply(server.url, server.accessToken, videoId, threadId, text3) + await command.addReply({ videoId, toCommentId: threadId, text: text3 }) }) it('Should get correctly the replies', async function () { - const res = await getVideoThreadComments(server.url, videoUUID, threadId) + const tree = await command.getThread({ videoId: videoUUID, threadId }) - const tree: VideoCommentThreadTree = res.body expect(tree.comment.text).equal('my super first comment') expect(tree.children).to.have.lengthOf(2) @@ -163,42 +144,41 @@ describe('Test video comments', function () { it('Should create other threads', async function () { const text1 = 'super thread 2' - await addVideoCommentThread(server.url, server.accessToken, videoUUID, text1) + await command.createThread({ videoId: videoUUID, text: text1 }) const text2 = 'super thread 3' - await addVideoCommentThread(server.url, server.accessToken, videoUUID, text2) + await command.createThread({ videoId: videoUUID, text: text2 }) }) it('Should list the threads', async function () { - const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5, 'createdAt') - - expect(res.body.total).to.equal(3) - expect(res.body.totalNotDeletedComments).to.equal(6) - expect(res.body.data).to.be.an('array') - expect(res.body.data).to.have.lengthOf(3) - - expect(res.body.data[0].text).to.equal('my super first comment') - expect(res.body.data[0].totalReplies).to.equal(3) - expect(res.body.data[1].text).to.equal('super thread 2') - expect(res.body.data[1].totalReplies).to.equal(0) - expect(res.body.data[2].text).to.equal('super thread 3') - expect(res.body.data[2].totalReplies).to.equal(0) + const body = await command.listThreads({ videoId: videoUUID, sort: 'createdAt' }) + + expect(body.total).to.equal(3) + expect(body.totalNotDeletedComments).to.equal(6) + expect(body.data).to.be.an('array') + expect(body.data).to.have.lengthOf(3) + + expect(body.data[0].text).to.equal('my super first comment') + expect(body.data[0].totalReplies).to.equal(3) + expect(body.data[1].text).to.equal('super thread 2') + expect(body.data[1].totalReplies).to.equal(0) + expect(body.data[2].text).to.equal('super thread 3') + expect(body.data[2].totalReplies).to.equal(0) }) it('Should delete a reply', async function () { - await deleteVideoComment(server.url, server.accessToken, videoId, replyToDeleteId) + await command.delete({ videoId, commentId: replyToDeleteId }) { - const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5, 'createdAt') + const body = await command.listThreads({ videoId: videoUUID, sort: 'createdAt' }) - expect(res.body.total).to.equal(3) - expect(res.body.totalNotDeletedComments).to.equal(5) + expect(body.total).to.equal(3) + expect(body.totalNotDeletedComments).to.equal(5) } { - const res = await getVideoThreadComments(server.url, videoUUID, threadId) + const tree = await command.getThread({ videoId: videoUUID, threadId }) - const tree: VideoCommentThreadTree = res.body expect(tree.comment.text).equal('my super first comment') expect(tree.children).to.have.lengthOf(2) @@ -220,99 +200,88 @@ describe('Test video comments', function () { }) it('Should delete a complete thread', async function () { - await deleteVideoComment(server.url, server.accessToken, videoId, threadId) - - const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5, 'createdAt') - expect(res.body.total).to.equal(3) - expect(res.body.data).to.be.an('array') - expect(res.body.data).to.have.lengthOf(3) - - expect(res.body.data[0].text).to.equal('') - expect(res.body.data[0].isDeleted).to.be.true - expect(res.body.data[0].deletedAt).to.not.be.null - expect(res.body.data[0].account).to.be.null - expect(res.body.data[0].totalReplies).to.equal(2) - expect(res.body.data[1].text).to.equal('super thread 2') - expect(res.body.data[1].totalReplies).to.equal(0) - expect(res.body.data[2].text).to.equal('super thread 3') - expect(res.body.data[2].totalReplies).to.equal(0) + await command.delete({ videoId, commentId: threadId }) + + const body = await command.listThreads({ videoId: videoUUID, sort: 'createdAt' }) + expect(body.total).to.equal(3) + expect(body.data).to.be.an('array') + expect(body.data).to.have.lengthOf(3) + + expect(body.data[0].text).to.equal('') + expect(body.data[0].isDeleted).to.be.true + expect(body.data[0].deletedAt).to.not.be.null + expect(body.data[0].account).to.be.null + expect(body.data[0].totalReplies).to.equal(2) + expect(body.data[1].text).to.equal('super thread 2') + expect(body.data[1].totalReplies).to.equal(0) + expect(body.data[2].text).to.equal('super thread 3') + expect(body.data[2].totalReplies).to.equal(0) }) it('Should count replies from the video author correctly', async function () { - const text = 'my super first comment' - await addVideoCommentThread(server.url, server.accessToken, videoUUID, text) - let res = await getVideoCommentThreads(server.url, videoUUID, 0, 5) - const comment: VideoComment = res.body.data[0] - const threadId2 = comment.threadId + await command.createThread({ videoId: videoUUID, text: 'my super first comment' }) + + const { data } = await command.listThreads({ videoId: videoUUID }) + const threadId2 = data[0].threadId const text2 = 'a first answer to thread 4 by a third party' - await addVideoCommentReply(server.url, userAccessTokenServer1, videoId, threadId2, text2) + await command.addReply({ token: userAccessTokenServer1, videoId, toCommentId: threadId2, text: text2 }) const text3 = 'my second answer to thread 4' - await addVideoCommentReply(server.url, server.accessToken, videoId, threadId2, text3) + await command.addReply({ videoId, toCommentId: threadId2, text: text3 }) - res = await getVideoThreadComments(server.url, videoUUID, threadId2) - const tree: VideoCommentThreadTree = res.body + const tree = await command.getThread({ videoId: videoUUID, threadId: threadId2 }) expect(tree.comment.totalReplies).to.equal(tree.comment.totalRepliesFromVideoAuthor + 1) }) }) describe('All instance comments', function () { - async function getComments (options: any = {}) { - const res = await getAdminVideoComments(Object.assign({ - url: server.url, - token: server.accessToken, - start: 0, - count: 10 - }, options)) - - return { comments: res.body.data as VideoCommentAdmin[], total: res.body.total as number } - } it('Should list instance comments as admin', async function () { - const { comments } = await getComments({ start: 0, count: 1 }) + const { data } = await command.listForAdmin({ start: 0, count: 1 }) - expect(comments[0].text).to.equal('my second answer to thread 4') + expect(data[0].text).to.equal('my second answer to thread 4') }) it('Should filter instance comments by isLocal', async function () { - const { total, comments } = await getComments({ isLocal: false }) + const { total, data } = await command.listForAdmin({ isLocal: false }) - expect(comments).to.have.lengthOf(0) + expect(data).to.have.lengthOf(0) expect(total).to.equal(0) }) it('Should search instance comments by account', async function () { - const { total, comments } = await getComments({ searchAccount: 'user' }) + const { total, data } = await command.listForAdmin({ searchAccount: 'user' }) - expect(comments).to.have.lengthOf(1) + expect(data).to.have.lengthOf(1) expect(total).to.equal(1) - expect(comments[0].text).to.equal('a first answer to thread 4 by a third party') + expect(data[0].text).to.equal('a first answer to thread 4 by a third party') }) it('Should search instance comments by video', async function () { { - const { total, comments } = await getComments({ searchVideo: 'video' }) + const { total, data } = await command.listForAdmin({ searchVideo: 'video' }) - expect(comments).to.have.lengthOf(7) + expect(data).to.have.lengthOf(7) expect(total).to.equal(7) } { - const { total, comments } = await getComments({ searchVideo: 'hello' }) + const { total, data } = await command.listForAdmin({ searchVideo: 'hello' }) - expect(comments).to.have.lengthOf(0) + expect(data).to.have.lengthOf(0) expect(total).to.equal(0) } }) it('Should search instance comments', async function () { - const { total, comments } = await getComments({ search: 'super thread 3' }) + const { total, data } = await command.listForAdmin({ search: 'super thread 3' }) - expect(comments).to.have.lengthOf(1) expect(total).to.equal(1) - expect(comments[0].text).to.equal('super thread 3') + + expect(data).to.have.lengthOf(1) + expect(data[0].text).to.equal('super thread 3') }) }) diff --git a/server/tests/api/videos/video-description.ts b/server/tests/api/videos/video-description.ts index b8e98e45f..d22b4ed96 100644 --- a/server/tests/api/videos/video-description.ts +++ b/server/tests/api/videos/video-description.ts @@ -1,25 +1,13 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ -import * as chai from 'chai' import 'mocha' -import { - cleanupTests, - flushAndRunMultipleServers, - getVideo, - getVideoDescription, - getVideosList, - ServerInfo, - setAccessTokensToServers, - updateVideo, - uploadVideo -} from '../../../../shared/extra-utils/index' -import { doubleFollow } from '../../../../shared/extra-utils/server/follows' -import { waitJobs } from '../../../../shared/extra-utils/server/jobs' +import * as chai from 'chai' +import { cleanupTests, createMultipleServers, doubleFollow, PeerTubeServer, setAccessTokensToServers, waitJobs } from '@shared/extra-utils' const expect = chai.expect describe('Test video description', function () { - let servers: ServerInfo[] = [] + let servers: PeerTubeServer[] = [] let videoUUID = '' let videoId: number const longDescription = 'my super description for server 1'.repeat(50) @@ -28,7 +16,7 @@ describe('Test video description', function () { this.timeout(40000) // Run servers - servers = await flushAndRunMultipleServers(2) + servers = await createMultipleServers(2) // Get the access tokens await setAccessTokensToServers(servers) @@ -43,20 +31,19 @@ describe('Test video description', function () { const attributes = { description: longDescription } - await uploadVideo(servers[0].url, servers[0].accessToken, attributes) + await servers[0].videos.upload({ attributes }) await waitJobs(servers) - const res = await getVideosList(servers[0].url) + const { data } = await servers[0].videos.list() - videoId = res.body.data[0].id - videoUUID = res.body.data[0].uuid + videoId = data[0].id + videoUUID = data[0].uuid }) it('Should have a truncated description on each server', async function () { for (const server of servers) { - const res = await getVideo(server.url, videoUUID) - const video = res.body + const video = await server.videos.get({ id: videoUUID }) // 30 characters * 6 -> 240 characters const truncatedDescription = 'my super description for server 1'.repeat(7) + @@ -68,11 +55,10 @@ describe('Test video description', function () { it('Should fetch long description on each server', async function () { for (const server of servers) { - const res = await getVideo(server.url, videoUUID) - const video = res.body + const video = await server.videos.get({ id: videoUUID }) - const res2 = await getVideoDescription(server.url, video.descriptionPath) - expect(res2.body.description).to.equal(longDescription) + const { description } = await server.videos.getDescription({ descriptionPath: video.descriptionPath }) + expect(description).to.equal(longDescription) } }) @@ -82,20 +68,19 @@ describe('Test video description', function () { const attributes = { description: 'short description' } - await updateVideo(servers[0].url, servers[0].accessToken, videoId, attributes) + await servers[0].videos.update({ id: videoId, attributes }) await waitJobs(servers) }) it('Should have a small description on each server', async function () { for (const server of servers) { - const res = await getVideo(server.url, videoUUID) - const video = res.body + const video = await server.videos.get({ id: videoUUID }) expect(video.description).to.equal('short description') - const res2 = await getVideoDescription(server.url, video.descriptionPath) - expect(res2.body.description).to.equal('short description') + const { description } = await server.videos.getDescription({ descriptionPath: video.descriptionPath }) + expect(description).to.equal('short description') } }) diff --git a/server/tests/api/videos/video-hls.ts b/server/tests/api/videos/video-hls.ts index 03ac3f321..961f0e617 100644 --- a/server/tests/api/videos/video-hls.ts +++ b/server/tests/api/videos/video-hls.ts @@ -2,38 +2,30 @@ import 'mocha' import * as chai from 'chai' -import { join } from 'path' +import { basename, join } from 'path' +import { removeFragmentedMP4Ext, uuidRegex } from '@shared/core-utils' import { checkDirectoryIsEmpty, checkResolutionsInMasterPlaylist, checkSegmentHash, checkTmpIsEmpty, cleanupTests, + createMultipleServers, doubleFollow, - flushAndRunMultipleServers, - getPlaylist, - getVideo, makeRawRequest, - removeVideo, - ServerInfo, + PeerTubeServer, setAccessTokensToServers, - updateCustomSubConfig, - updateVideo, - uploadVideo, waitJobs, webtorrentAdd -} from '../../../../shared/extra-utils' -import { VideoDetails } from '../../../../shared/models/videos' -import { VideoStreamingPlaylistType } from '../../../../shared/models/videos/video-streaming-playlist.type' +} from '@shared/extra-utils' +import { HttpStatusCode, VideoStreamingPlaylistType } from '@shared/models' import { DEFAULT_AUDIO_RESOLUTION } from '../../../initializers/constants' -import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' const expect = chai.expect -async function checkHlsPlaylist (servers: ServerInfo[], videoUUID: string, hlsOnly: boolean, resolutions = [ 240, 360, 480, 720 ]) { +async function checkHlsPlaylist (servers: PeerTubeServer[], videoUUID: string, hlsOnly: boolean, resolutions = [ 240, 360, 480, 720 ]) { for (const server of servers) { - const resVideoDetails = await getVideo(server.url, videoUUID) - const videoDetails: VideoDetails = resVideoDetails.body + const videoDetails = await server.videos.get({ id: videoUUID }) const baseUrl = `http://${videoDetails.account.host}` expect(videoDetails.streamingPlaylists).to.have.lengthOf(1) @@ -47,14 +39,17 @@ async function checkHlsPlaylist (servers: ServerInfo[], videoUUID: string, hlsOn if (hlsOnly) expect(videoDetails.files).to.have.lengthOf(0) else expect(videoDetails.files).to.have.lengthOf(resolutions.length) + // Check JSON files for (const resolution of resolutions) { const file = hlsFiles.find(f => f.resolution.id === resolution) expect(file).to.not.be.undefined expect(file.magnetUri).to.have.lengthOf.above(2) - expect(file.torrentUrl).to.equal(`http://${server.host}/lazy-static/torrents/${videoDetails.uuid}-${file.resolution.id}-hls.torrent`) - expect(file.fileUrl).to.equal( - `${baseUrl}/static/streaming-playlists/hls/${videoDetails.uuid}/${videoDetails.uuid}-${file.resolution.id}-fragmented.mp4` + expect(file.torrentUrl).to.match( + new RegExp(`http://${server.host}/lazy-static/torrents/${uuidRegex}-${file.resolution.id}-hls.torrent`) + ) + expect(file.fileUrl).to.match( + new RegExp(`${baseUrl}/static/streaming-playlists/hls/${videoDetails.uuid}/${uuidRegex}-${file.resolution.id}-fragmented.mp4`) ) expect(file.resolution.label).to.equal(resolution + 'p') @@ -67,11 +62,11 @@ async function checkHlsPlaylist (servers: ServerInfo[], videoUUID: string, hlsOn expect(torrent.files[0].path).to.exist.and.to.not.equal('') } + // Check master playlist { - await checkResolutionsInMasterPlaylist(hlsPlaylist.playlistUrl, resolutions) + await checkResolutionsInMasterPlaylist({ server, playlistUrl: hlsPlaylist.playlistUrl, resolutions }) - const res = await getPlaylist(hlsPlaylist.playlistUrl) - const masterPlaylist = res.text + const masterPlaylist = await server.streamingPlaylists.get({ url: hlsPlaylist.playlistUrl }) for (const resolution of resolutions) { expect(masterPlaylist).to.contain(`${resolution}.m3u8`) @@ -79,12 +74,18 @@ async function checkHlsPlaylist (servers: ServerInfo[], videoUUID: string, hlsOn } } + // Check resolution playlists { for (const resolution of resolutions) { - const res = await getPlaylist(`${baseUrl}/static/streaming-playlists/hls/${videoUUID}/${resolution}.m3u8`) + const file = hlsFiles.find(f => f.resolution.id === resolution) + const playlistName = removeFragmentedMP4Ext(basename(file.fileUrl)) + '.m3u8' + + const subPlaylist = await server.streamingPlaylists.get({ + url: `${baseUrl}/static/streaming-playlists/hls/${videoUUID}/${playlistName}` + }) - const subPlaylist = res.text - expect(subPlaylist).to.contain(`${videoUUID}-${resolution}-fragmented.mp4`) + expect(subPlaylist).to.match(new RegExp(`${uuidRegex}-${resolution}-fragmented.mp4`)) + expect(subPlaylist).to.contain(basename(file.fileUrl)) } } @@ -92,23 +93,31 @@ async function checkHlsPlaylist (servers: ServerInfo[], videoUUID: string, hlsOn const baseUrlAndPath = baseUrl + '/static/streaming-playlists/hls' for (const resolution of resolutions) { - await checkSegmentHash(baseUrlAndPath, baseUrlAndPath, videoUUID, resolution, hlsPlaylist) + await checkSegmentHash({ + server, + baseUrlPlaylist: baseUrlAndPath, + baseUrlSegment: baseUrlAndPath, + videoUUID, + resolution, + hlsPlaylist + }) } } } } describe('Test HLS videos', function () { - let servers: ServerInfo[] = [] + let servers: PeerTubeServer[] = [] let videoUUID = '' let videoAudioUUID = '' function runTestSuite (hlsOnly: boolean) { + it('Should upload a video and transcode it to HLS', async function () { this.timeout(120000) - const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video 1', fixture: 'video_short.webm' }) - videoUUID = res.body.video.uuid + const { uuid } = await servers[0].videos.upload({ attributes: { name: 'video 1', fixture: 'video_short.webm' } }) + videoUUID = uuid await waitJobs(servers) @@ -118,8 +127,8 @@ describe('Test HLS videos', function () { it('Should upload an audio file and transcode it to HLS', async function () { this.timeout(120000) - const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video audio', fixture: 'sample.ogg' }) - videoAudioUUID = res.body.video.uuid + const { uuid } = await servers[0].videos.upload({ attributes: { name: 'video audio', fixture: 'sample.ogg' } }) + videoAudioUUID = uuid await waitJobs(servers) @@ -129,7 +138,7 @@ describe('Test HLS videos', function () { it('Should update the video', async function () { this.timeout(10000) - await updateVideo(servers[0].url, servers[0].accessToken, videoUUID, { name: 'video 1 updated' }) + await servers[0].videos.update({ id: videoUUID, attributes: { name: 'video 1 updated' } }) await waitJobs(servers) @@ -139,14 +148,14 @@ describe('Test HLS videos', function () { it('Should delete videos', async function () { this.timeout(10000) - await removeVideo(servers[0].url, servers[0].accessToken, videoUUID) - await removeVideo(servers[0].url, servers[0].accessToken, videoAudioUUID) + await servers[0].videos.remove({ id: videoUUID }) + await servers[0].videos.remove({ id: videoAudioUUID }) await waitJobs(servers) for (const server of servers) { - await getVideo(server.url, videoUUID, HttpStatusCode.NOT_FOUND_404) - await getVideo(server.url, videoAudioUUID, HttpStatusCode.NOT_FOUND_404) + await server.videos.get({ id: videoUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 }) + await server.videos.get({ id: videoAudioUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 }) } }) @@ -176,7 +185,7 @@ describe('Test HLS videos', function () { } } } - servers = await flushAndRunMultipleServers(2, configOverride) + servers = await createMultipleServers(2, configOverride) // Get the access tokens await setAccessTokensToServers(servers) @@ -192,24 +201,26 @@ describe('Test HLS videos', function () { describe('With only HLS enabled', function () { before(async function () { - await updateCustomSubConfig(servers[0].url, servers[0].accessToken, { - transcoding: { - enabled: true, - allowAudioFiles: true, - resolutions: { - '240p': true, - '360p': true, - '480p': true, - '720p': true, - '1080p': true, - '1440p': true, - '2160p': true - }, - hls: { - enabled: true - }, - webtorrent: { - enabled: false + await servers[0].config.updateCustomSubConfig({ + newConfig: { + transcoding: { + enabled: true, + allowAudioFiles: true, + resolutions: { + '240p': true, + '360p': true, + '480p': true, + '720p': true, + '1080p': true, + '1440p': true, + '2160p': true + }, + hls: { + enabled: true + }, + webtorrent: { + enabled: false + } } } }) diff --git a/server/tests/api/videos/video-imports.ts b/server/tests/api/videos/video-imports.ts index 80834ca86..2eac130d2 100644 --- a/server/tests/api/videos/video-imports.ts +++ b/server/tests/api/videos/video-imports.ts @@ -3,43 +3,30 @@ import 'mocha' import * as chai from 'chai' import { + areHttpImportTestsDisabled, cleanupTests, + createMultipleServers, doubleFollow, - flushAndRunMultipleServers, - getMyUserInformation, - getMyVideos, - getVideo, - getVideosList, - immutableAssign, - listVideoCaptions, - ServerInfo, + FIXTURE_URLS, + PeerTubeServer, setAccessTokensToServers, testCaptionFile, - updateCustomSubConfig -} from '../../../../shared/extra-utils' -import { areHttpImportTestsDisabled, testImage } from '../../../../shared/extra-utils/miscs/miscs' -import { waitJobs } from '../../../../shared/extra-utils/server/jobs' -import { - getMagnetURI, - getMyVideoImports, - getYoutubeHDRVideoUrl, - getYoutubeVideoUrl, - importVideo -} from '../../../../shared/extra-utils/videos/video-imports' -import { VideoCaption, VideoDetails, VideoImport, VideoPrivacy, VideoResolution } from '../../../../shared/models/videos' + testImage, + waitJobs +} from '@shared/extra-utils' +import { VideoPrivacy, VideoResolution } from '@shared/models' const expect = chai.expect describe('Test video imports', function () { - let servers: ServerInfo[] = [] + let servers: PeerTubeServer[] = [] let channelIdServer1: number let channelIdServer2: number if (areHttpImportTestsDisabled()) return - async function checkVideosServer1 (url: string, idHttp: string, idMagnet: string, idTorrent: string) { - const resHttp = await getVideo(url, idHttp) - const videoHttp: VideoDetails = resHttp.body + async function checkVideosServer1 (server: PeerTubeServer, idHttp: string, idMagnet: string, idTorrent: string) { + const videoHttp = await server.videos.get({ id: idHttp }) expect(videoHttp.name).to.equal('small video - youtube') // FIXME: youtube-dl seems broken @@ -56,10 +43,8 @@ describe('Test video imports', function () { expect(originallyPublishedAt.getMonth()).to.equal(0) expect(originallyPublishedAt.getFullYear()).to.equal(2019) - const resMagnet = await getVideo(url, idMagnet) - const videoMagnet: VideoDetails = resMagnet.body - const resTorrent = await getVideo(url, idTorrent) - const videoTorrent: VideoDetails = resTorrent.body + const videoMagnet = await server.videos.get({ id: idMagnet }) + const videoTorrent = await server.videos.get({ id: idTorrent }) for (const video of [ videoMagnet, videoTorrent ]) { expect(video.category.label).to.equal('Misc') @@ -74,13 +59,12 @@ describe('Test video imports', function () { expect(videoTorrent.name).to.contain('你好 世界 720p.mp4') expect(videoMagnet.name).to.contain('super peertube2 video') - const resCaptions = await listVideoCaptions(url, idHttp) - expect(resCaptions.body.total).to.equal(2) + const bodyCaptions = await server.captions.list({ videoId: idHttp }) + expect(bodyCaptions.total).to.equal(2) } - async function checkVideoServer2 (url: string, id: number | string) { - const res = await getVideo(url, id) - const video: VideoDetails = res.body + async function checkVideoServer2 (server: PeerTubeServer, id: number | string) { + const video = await server.videos.get({ id }) expect(video.name).to.equal('my super name') expect(video.category.label).to.equal('Entertainment') @@ -92,26 +76,26 @@ describe('Test video imports', function () { expect(video.files).to.have.lengthOf(1) - const resCaptions = await listVideoCaptions(url, id) - expect(resCaptions.body.total).to.equal(2) + const bodyCaptions = await server.captions.list({ videoId: id }) + expect(bodyCaptions.total).to.equal(2) } before(async function () { this.timeout(30_000) // Run servers - servers = await flushAndRunMultipleServers(2) + servers = await createMultipleServers(2) await setAccessTokensToServers(servers) { - const res = await getMyUserInformation(servers[0].url, servers[0].accessToken) - channelIdServer1 = res.body.videoChannels[0].id + const { videoChannels } = await servers[0].users.getMyInfo() + channelIdServer1 = videoChannels[0].id } { - const res = await getMyUserInformation(servers[1].url, servers[1].accessToken) - channelIdServer2 = res.body.videoChannels[0].id + const { videoChannels } = await servers[1].users.getMyInfo() + channelIdServer2 = videoChannels[0].id } await doubleFollow(servers[0], servers[1]) @@ -126,18 +110,18 @@ describe('Test video imports', function () { } { - const attributes = immutableAssign(baseAttributes, { targetUrl: getYoutubeVideoUrl() }) - const res = await importVideo(servers[0].url, servers[0].accessToken, attributes) - expect(res.body.video.name).to.equal('small video - youtube') + const attributes = { ...baseAttributes, targetUrl: FIXTURE_URLS.youtube } + const { video } = await servers[0].imports.importVideo({ attributes }) + expect(video.name).to.equal('small video - youtube') - expect(res.body.video.thumbnailPath).to.match(new RegExp(`^/static/thumbnails/.+.jpg$`)) - expect(res.body.video.previewPath).to.match(new RegExp(`^/lazy-static/previews/.+.jpg$`)) + expect(video.thumbnailPath).to.match(new RegExp(`^/static/thumbnails/.+.jpg$`)) + expect(video.previewPath).to.match(new RegExp(`^/lazy-static/previews/.+.jpg$`)) - await testImage(servers[0].url, 'video_import_thumbnail', res.body.video.thumbnailPath) - await testImage(servers[0].url, 'video_import_preview', res.body.video.previewPath) + await testImage(servers[0].url, 'video_import_thumbnail', video.thumbnailPath) + await testImage(servers[0].url, 'video_import_preview', video.previewPath) - const resCaptions = await listVideoCaptions(servers[0].url, res.body.video.id) - const videoCaptions: VideoCaption[] = resCaptions.body.data + const bodyCaptions = await servers[0].captions.list({ videoId: video.id }) + const videoCaptions = bodyCaptions.data expect(videoCaptions).to.have.lengthOf(2) const enCaption = videoCaptions.find(caption => caption.language.id === 'en') @@ -176,52 +160,52 @@ Ajouter un sous-titre est vraiment facile`) } { - const attributes = immutableAssign(baseAttributes, { - magnetUri: getMagnetURI(), + const attributes = { + ...baseAttributes, + magnetUri: FIXTURE_URLS.magnet, description: 'this is a super torrent description', tags: [ 'tag_torrent1', 'tag_torrent2' ] - }) - const res = await importVideo(servers[0].url, servers[0].accessToken, attributes) - expect(res.body.video.name).to.equal('super peertube2 video') + } + const { video } = await servers[0].imports.importVideo({ attributes }) + expect(video.name).to.equal('super peertube2 video') } { - const attributes = immutableAssign(baseAttributes, { + const attributes = { + ...baseAttributes, torrentfile: 'video-720p.torrent' as any, description: 'this is a super torrent description', tags: [ 'tag_torrent1', 'tag_torrent2' ] - }) - const res = await importVideo(servers[0].url, servers[0].accessToken, attributes) - expect(res.body.video.name).to.equal('你好 世界 720p.mp4') + } + const { video } = await servers[0].imports.importVideo({ attributes }) + expect(video.name).to.equal('你好 世界 720p.mp4') } }) it('Should list the videos to import in my videos on server 1', async function () { - const res = await getMyVideos(servers[0].url, servers[0].accessToken, 0, 5, 'createdAt') + const { total, data } = await servers[0].videos.listMyVideos({ sort: 'createdAt' }) - expect(res.body.total).to.equal(3) + expect(total).to.equal(3) - const videos = res.body.data - expect(videos).to.have.lengthOf(3) - expect(videos[0].name).to.equal('small video - youtube') - expect(videos[1].name).to.equal('super peertube2 video') - expect(videos[2].name).to.equal('你好 世界 720p.mp4') + expect(data).to.have.lengthOf(3) + expect(data[0].name).to.equal('small video - youtube') + expect(data[1].name).to.equal('super peertube2 video') + expect(data[2].name).to.equal('你好 世界 720p.mp4') }) it('Should list the videos to import in my imports on server 1', async function () { - const res = await getMyVideoImports(servers[0].url, servers[0].accessToken, '-createdAt') + const { total, data: videoImports } = await servers[0].imports.getMyVideoImports({ sort: '-createdAt' }) + expect(total).to.equal(3) - expect(res.body.total).to.equal(3) - const videoImports: VideoImport[] = res.body.data expect(videoImports).to.have.lengthOf(3) - expect(videoImports[2].targetUrl).to.equal(getYoutubeVideoUrl()) + expect(videoImports[2].targetUrl).to.equal(FIXTURE_URLS.youtube) expect(videoImports[2].magnetUri).to.be.null expect(videoImports[2].torrentName).to.be.null expect(videoImports[2].video.name).to.equal('small video - youtube') expect(videoImports[1].targetUrl).to.be.null - expect(videoImports[1].magnetUri).to.equal(getMagnetURI()) + expect(videoImports[1].magnetUri).to.equal(FIXTURE_URLS.magnet) expect(videoImports[1].torrentName).to.be.null expect(videoImports[1].video.name).to.equal('super peertube2 video') @@ -237,12 +221,12 @@ Ajouter un sous-titre est vraiment facile`) await waitJobs(servers) for (const server of servers) { - const res = await getVideosList(server.url) - expect(res.body.total).to.equal(3) - expect(res.body.data).to.have.lengthOf(3) + const { total, data } = await server.videos.list() + expect(total).to.equal(3) + expect(data).to.have.lengthOf(3) - const [ videoHttp, videoMagnet, videoTorrent ] = res.body.data - await checkVideosServer1(server.url, videoHttp.uuid, videoMagnet.uuid, videoTorrent.uuid) + const [ videoHttp, videoMagnet, videoTorrent ] = data + await checkVideosServer1(server, videoHttp.uuid, videoMagnet.uuid, videoTorrent.uuid) } }) @@ -250,7 +234,7 @@ Ajouter un sous-titre est vraiment facile`) this.timeout(60_000) const attributes = { - targetUrl: getYoutubeVideoUrl(), + targetUrl: FIXTURE_URLS.youtube, channelId: channelIdServer2, privacy: VideoPrivacy.PUBLIC, category: 10, @@ -260,8 +244,8 @@ Ajouter un sous-titre est vraiment facile`) description: 'my super description', tags: [ 'supertag1', 'supertag2' ] } - const res = await importVideo(servers[1].url, servers[1].accessToken, attributes) - expect(res.body.video.name).to.equal('my super name') + const { video } = await servers[1].imports.importVideo({ attributes }) + expect(video.name).to.equal('my super name') }) it('Should have the videos listed on the two instances', async function () { @@ -270,14 +254,14 @@ Ajouter un sous-titre est vraiment facile`) await waitJobs(servers) for (const server of servers) { - const res = await getVideosList(server.url) - expect(res.body.total).to.equal(4) - expect(res.body.data).to.have.lengthOf(4) + const { total, data } = await server.videos.list() + expect(total).to.equal(4) + expect(data).to.have.lengthOf(4) - await checkVideoServer2(server.url, res.body.data[0].uuid) + await checkVideoServer2(server, data[0].uuid) - const [ , videoHttp, videoMagnet, videoTorrent ] = res.body.data - await checkVideosServer1(server.url, videoHttp.uuid, videoMagnet.uuid, videoTorrent.uuid) + const [ , videoHttp, videoMagnet, videoTorrent ] = data + await checkVideosServer1(server, videoHttp.uuid, videoMagnet.uuid, videoTorrent.uuid) } }) @@ -286,18 +270,17 @@ Ajouter un sous-titre est vraiment facile`) const attributes = { name: 'transcoded video', - magnetUri: getMagnetURI(), + magnetUri: FIXTURE_URLS.magnet, channelId: channelIdServer2, privacy: VideoPrivacy.PUBLIC } - const res = await importVideo(servers[1].url, servers[1].accessToken, attributes) - const videoUUID = res.body.video.uuid + const { video } = await servers[1].imports.importVideo({ attributes }) + const videoUUID = video.uuid await waitJobs(servers) for (const server of servers) { - const res = await getVideo(server.url, videoUUID) - const video: VideoDetails = res.body + const video = await server.videos.get({ id: videoUUID }) expect(video.name).to.equal('transcoded video') expect(video.files).to.have.lengthOf(4) @@ -333,22 +316,21 @@ Ajouter un sous-titre est vraiment facile`) } } } - await updateCustomSubConfig(servers[0].url, servers[0].accessToken, config) + await servers[0].config.updateCustomSubConfig({ newConfig: config }) const attributes = { name: 'hdr video', - targetUrl: getYoutubeHDRVideoUrl(), + targetUrl: FIXTURE_URLS.youtubeHDR, channelId: channelIdServer1, privacy: VideoPrivacy.PUBLIC } - const res1 = await importVideo(servers[0].url, servers[0].accessToken, attributes) - const videoUUID = res1.body.video.uuid + const { video: videoImported } = await servers[0].imports.importVideo({ attributes }) + const videoUUID = videoImported.uuid await waitJobs(servers) // test resolution - const res2 = await getVideo(servers[0].url, videoUUID) - const video: VideoDetails = res2.body + const video = await servers[0].videos.get({ id: videoUUID }) expect(video.name).to.equal('hdr video') const maxResolution = Math.max.apply(Math, video.files.map(function (o) { return o.resolution.id })) expect(maxResolution, 'expected max resolution not met').to.equals(VideoResolution.H_1080P) diff --git a/server/tests/api/videos/video-nsfw.ts b/server/tests/api/videos/video-nsfw.ts index b16b484b9..b5d183d62 100644 --- a/server/tests/api/videos/video-nsfw.ts +++ b/server/tests/api/videos/video-nsfw.ts @@ -1,117 +1,96 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ -import * as chai from 'chai' import 'mocha' -import { cleanupTests, getVideosList, ServerInfo, setAccessTokensToServers, uploadVideo } from '../../../../shared/extra-utils/index' -import { userLogin } from '../../../../shared/extra-utils/users/login' -import { createUser } from '../../../../shared/extra-utils/users/users' -import { getMyVideos } from '../../../../shared/extra-utils/videos/videos' -import { - flushAndRunServer, - getAccountVideos, - getConfig, - getCustomConfig, - getMyUserInformation, - getVideoChannelVideos, - getVideosListWithToken, - searchVideo, - searchVideoWithToken, - updateCustomConfig, - updateMyUser -} from '../../../../shared/extra-utils' -import { ServerConfig, VideosOverview } from '../../../../shared/models' -import { CustomConfig } from '../../../../shared/models/server/custom-config.model' -import { User } from '../../../../shared/models/users' -import { getVideosOverview, getVideosOverviewWithToken } from '@shared/extra-utils/overviews/overviews' +import * as chai from 'chai' +import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '@shared/extra-utils' +import { BooleanBothQuery, CustomConfig, ResultList, Video, VideosOverview } from '@shared/models' const expect = chai.expect -function createOverviewRes (res: any) { - const overview = res.body as VideosOverview - +function createOverviewRes (overview: VideosOverview) { const videos = overview.categories[0].videos - return { body: { data: videos, total: videos.length } } + return { data: videos, total: videos.length } } describe('Test video NSFW policy', function () { - let server: ServerInfo + let server: PeerTubeServer let userAccessToken: string let customConfig: CustomConfig - function getVideosFunctions (token?: string, query = {}) { - return getMyUserInformation(server.url, server.accessToken) - .then(res => { - const user: User = res.body - const videoChannelName = user.videoChannels[0].name - const accountName = user.account.name + '@' + user.account.host - const hasQuery = Object.keys(query).length !== 0 - let promises: Promise[] - - if (token) { - promises = [ - getVideosListWithToken(server.url, token, query), - searchVideoWithToken(server.url, 'n', token, query), - getAccountVideos(server.url, token, accountName, 0, 5, undefined, query), - getVideoChannelVideos(server.url, token, videoChannelName, 0, 5, undefined, query) - ] - - // Overviews do not support video filters - if (!hasQuery) { - promises.push(getVideosOverviewWithToken(server.url, 1, token).then(res => createOverviewRes(res))) - } - - return Promise.all(promises) - } - - promises = [ - getVideosList(server.url), - searchVideo(server.url, 'n'), - getAccountVideos(server.url, undefined, accountName, 0, 5), - getVideoChannelVideos(server.url, undefined, videoChannelName, 0, 5) - ] - - // Overviews do not support video filters - if (!hasQuery) { - promises.push(getVideosOverview(server.url, 1).then(res => createOverviewRes(res))) - } - - return Promise.all(promises) - }) + async function getVideosFunctions (token?: string, query: { nsfw?: BooleanBothQuery } = {}) { + const user = await server.users.getMyInfo() + + const channelName = user.videoChannels[0].name + const accountName = user.account.name + '@' + user.account.host + + const hasQuery = Object.keys(query).length !== 0 + let promises: Promise>[] + + if (token) { + promises = [ + server.search.advancedVideoSearch({ token, search: { search: 'n', sort: '-publishedAt', ...query } }), + server.videos.listWithToken({ token, ...query }), + server.videos.listByAccount({ token, handle: accountName, ...query }), + server.videos.listByChannel({ token, handle: channelName, ...query }) + ] + + // Overviews do not support video filters + if (!hasQuery) { + const p = server.overviews.getVideos({ page: 1, token }) + .then(res => createOverviewRes(res)) + promises.push(p) + } + + return Promise.all(promises) + } + + promises = [ + server.search.searchVideos({ search: 'n', sort: '-publishedAt' }), + server.videos.list(), + server.videos.listByAccount({ token: null, handle: accountName }), + server.videos.listByChannel({ token: null, handle: channelName }) + ] + + // Overviews do not support video filters + if (!hasQuery) { + const p = server.overviews.getVideos({ page: 1 }) + .then(res => createOverviewRes(res)) + promises.push(p) + } + + return Promise.all(promises) } before(async function () { this.timeout(50000) - server = await flushAndRunServer(1) + server = await createSingleServer(1) // Get the access tokens await setAccessTokensToServers([ server ]) { const attributes = { name: 'nsfw', nsfw: true, category: 1 } - await uploadVideo(server.url, server.accessToken, attributes) + await server.videos.upload({ attributes }) } { const attributes = { name: 'normal', nsfw: false, category: 1 } - await uploadVideo(server.url, server.accessToken, attributes) + await server.videos.upload({ attributes }) } - { - const res = await getCustomConfig(server.url, server.accessToken) - customConfig = res.body - } + customConfig = await server.config.getCustomConfig() }) describe('Instance default NSFW policy', function () { + it('Should display NSFW videos with display default NSFW policy', async function () { - const resConfig = await getConfig(server.url) - const serverConfig: ServerConfig = resConfig.body + const serverConfig = await server.config.getConfig() expect(serverConfig.instance.defaultNSFWPolicy).to.equal('display') - for (const res of await getVideosFunctions()) { - expect(res.body.total).to.equal(2) + for (const body of await getVideosFunctions()) { + expect(body.total).to.equal(2) - const videos = res.body.data + const videos = body.data expect(videos).to.have.lengthOf(2) expect(videos[0].name).to.equal('normal') expect(videos[1].name).to.equal('nsfw') @@ -120,16 +99,15 @@ describe('Test video NSFW policy', function () { it('Should not display NSFW videos with do_not_list default NSFW policy', async function () { customConfig.instance.defaultNSFWPolicy = 'do_not_list' - await updateCustomConfig(server.url, server.accessToken, customConfig) + await server.config.updateCustomConfig({ newCustomConfig: customConfig }) - const resConfig = await getConfig(server.url) - const serverConfig: ServerConfig = resConfig.body + const serverConfig = await server.config.getConfig() expect(serverConfig.instance.defaultNSFWPolicy).to.equal('do_not_list') - for (const res of await getVideosFunctions()) { - expect(res.body.total).to.equal(1) + for (const body of await getVideosFunctions()) { + expect(body.total).to.equal(1) - const videos = res.body.data + const videos = body.data expect(videos).to.have.lengthOf(1) expect(videos[0].name).to.equal('normal') } @@ -137,16 +115,15 @@ describe('Test video NSFW policy', function () { it('Should display NSFW videos with blur default NSFW policy', async function () { customConfig.instance.defaultNSFWPolicy = 'blur' - await updateCustomConfig(server.url, server.accessToken, customConfig) + await server.config.updateCustomConfig({ newCustomConfig: customConfig }) - const resConfig = await getConfig(server.url) - const serverConfig: ServerConfig = resConfig.body + const serverConfig = await server.config.getConfig() expect(serverConfig.instance.defaultNSFWPolicy).to.equal('blur') - for (const res of await getVideosFunctions()) { - expect(res.body.total).to.equal(2) + for (const body of await getVideosFunctions()) { + expect(body.total).to.equal(2) - const videos = res.body.data + const videos = body.data expect(videos).to.have.lengthOf(2) expect(videos[0].name).to.equal('normal') expect(videos[1].name).to.equal('nsfw') @@ -159,24 +136,22 @@ describe('Test video NSFW policy', function () { it('Should create a user having the default nsfw policy', async function () { const username = 'user1' const password = 'my super password' - await createUser({ url: server.url, accessToken: server.accessToken, username: username, password: password }) - - userAccessToken = await userLogin(server, { username, password }) + await server.users.create({ username: username, password: password }) - const res = await getMyUserInformation(server.url, userAccessToken) - const user = res.body + userAccessToken = await server.login.getAccessToken({ username, password }) + const user = await server.users.getMyInfo({ token: userAccessToken }) expect(user.nsfwPolicy).to.equal('blur') }) it('Should display NSFW videos with blur user NSFW policy', async function () { customConfig.instance.defaultNSFWPolicy = 'do_not_list' - await updateCustomConfig(server.url, server.accessToken, customConfig) + await server.config.updateCustomConfig({ newCustomConfig: customConfig }) - for (const res of await getVideosFunctions(userAccessToken)) { - expect(res.body.total).to.equal(2) + for (const body of await getVideosFunctions(userAccessToken)) { + expect(body.total).to.equal(2) - const videos = res.body.data + const videos = body.data expect(videos).to.have.lengthOf(2) expect(videos[0].name).to.equal('normal') expect(videos[1].name).to.equal('nsfw') @@ -184,16 +159,12 @@ describe('Test video NSFW policy', function () { }) it('Should display NSFW videos with display user NSFW policy', async function () { - await updateMyUser({ - url: server.url, - accessToken: server.accessToken, - nsfwPolicy: 'display' - }) + await server.users.updateMe({ nsfwPolicy: 'display' }) - for (const res of await getVideosFunctions(server.accessToken)) { - expect(res.body.total).to.equal(2) + for (const body of await getVideosFunctions(server.accessToken)) { + expect(body.total).to.equal(2) - const videos = res.body.data + const videos = body.data expect(videos).to.have.lengthOf(2) expect(videos[0].name).to.equal('normal') expect(videos[1].name).to.equal('nsfw') @@ -201,56 +172,51 @@ describe('Test video NSFW policy', function () { }) it('Should not display NSFW videos with do_not_list user NSFW policy', async function () { - await updateMyUser({ - url: server.url, - accessToken: server.accessToken, - nsfwPolicy: 'do_not_list' - }) + await server.users.updateMe({ nsfwPolicy: 'do_not_list' }) - for (const res of await getVideosFunctions(server.accessToken)) { - expect(res.body.total).to.equal(1) + for (const body of await getVideosFunctions(server.accessToken)) { + expect(body.total).to.equal(1) - const videos = res.body.data + const videos = body.data expect(videos).to.have.lengthOf(1) expect(videos[0].name).to.equal('normal') } }) it('Should be able to see my NSFW videos even with do_not_list user NSFW policy', async function () { - const res = await getMyVideos(server.url, server.accessToken, 0, 5) - expect(res.body.total).to.equal(2) + const { total, data } = await server.videos.listMyVideos() + expect(total).to.equal(2) - const videos = res.body.data - expect(videos).to.have.lengthOf(2) - expect(videos[0].name).to.equal('normal') - expect(videos[1].name).to.equal('nsfw') + expect(data).to.have.lengthOf(2) + expect(data[0].name).to.equal('normal') + expect(data[1].name).to.equal('nsfw') }) it('Should display NSFW videos when the nsfw param === true', async function () { - for (const res of await getVideosFunctions(server.accessToken, { nsfw: true })) { - expect(res.body.total).to.equal(1) + for (const body of await getVideosFunctions(server.accessToken, { nsfw: 'true' })) { + expect(body.total).to.equal(1) - const videos = res.body.data + const videos = body.data expect(videos).to.have.lengthOf(1) expect(videos[0].name).to.equal('nsfw') } }) it('Should hide NSFW videos when the nsfw param === true', async function () { - for (const res of await getVideosFunctions(server.accessToken, { nsfw: false })) { - expect(res.body.total).to.equal(1) + for (const body of await getVideosFunctions(server.accessToken, { nsfw: 'false' })) { + expect(body.total).to.equal(1) - const videos = res.body.data + const videos = body.data expect(videos).to.have.lengthOf(1) expect(videos[0].name).to.equal('normal') } }) it('Should display both videos when the nsfw param === both', async function () { - for (const res of await getVideosFunctions(server.accessToken, { nsfw: 'both' })) { - expect(res.body.total).to.equal(2) + for (const body of await getVideosFunctions(server.accessToken, { nsfw: 'both' })) { + expect(body.total).to.equal(2) - const videos = res.body.data + const videos = body.data expect(videos).to.have.lengthOf(2) expect(videos[0].name).to.equal('normal') expect(videos[1].name).to.equal('nsfw') diff --git a/server/tests/api/videos/video-playlist-thumbnails.ts b/server/tests/api/videos/video-playlist-thumbnails.ts index a93a0b7de..f0b2ca169 100644 --- a/server/tests/api/videos/video-playlist-thumbnails.ts +++ b/server/tests/api/videos/video-playlist-thumbnails.ts @@ -1,21 +1,15 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ -import * as chai from 'chai' import 'mocha' +import * as chai from 'chai' import { - addVideoInPlaylist, cleanupTests, - createVideoPlaylist, + createMultipleServers, doubleFollow, - flushAndRunMultipleServers, - getVideoPlaylistsList, - removeVideoFromPlaylist, - reorderVideosPlaylist, - ServerInfo, + PeerTubeServer, setAccessTokensToServers, setDefaultVideoChannel, testImage, - uploadVideoAndGetId, waitJobs } from '../../../../shared/extra-utils' import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' @@ -23,10 +17,10 @@ import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/ const expect = chai.expect describe('Playlist thumbnail', function () { - let servers: ServerInfo[] = [] + let servers: PeerTubeServer[] = [] - let playlistWithoutThumbnail: number - let playlistWithThumbnail: number + let playlistWithoutThumbnailId: number + let playlistWithThumbnailId: number let withThumbnailE1: number let withThumbnailE2: number @@ -36,22 +30,22 @@ describe('Playlist thumbnail', function () { let video1: number let video2: number - async function getPlaylistWithoutThumbnail (server: ServerInfo) { - const res = await getVideoPlaylistsList(server.url, 0, 10) + async function getPlaylistWithoutThumbnail (server: PeerTubeServer) { + const body = await server.playlists.list({ start: 0, count: 10 }) - return res.body.data.find(p => p.displayName === 'playlist without thumbnail') + return body.data.find(p => p.displayName === 'playlist without thumbnail') } - async function getPlaylistWithThumbnail (server: ServerInfo) { - const res = await getVideoPlaylistsList(server.url, 0, 10) + async function getPlaylistWithThumbnail (server: PeerTubeServer) { + const body = await server.playlists.list({ start: 0, count: 10 }) - return res.body.data.find(p => p.displayName === 'playlist with thumbnail') + return body.data.find(p => p.displayName === 'playlist with thumbnail') } before(async function () { this.timeout(120000) - servers = await flushAndRunMultipleServers(2, { transcoding: { enabled: false } }) + servers = await createMultipleServers(2, { transcoding: { enabled: false } }) // Get the access tokens await setAccessTokensToServers(servers) @@ -60,8 +54,8 @@ describe('Playlist thumbnail', function () { // Server 1 and server 2 follow each other await doubleFollow(servers[0], servers[1]) - video1 = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video 1' })).id - video2 = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video 2' })).id + video1 = (await servers[0].videos.quickUpload({ name: 'video 1' })).id + video2 = (await servers[0].videos.quickUpload({ name: 'video 2' })).id await waitJobs(servers) }) @@ -69,24 +63,20 @@ describe('Playlist thumbnail', function () { it('Should automatically update the thumbnail when adding an element', async function () { this.timeout(30000) - const res = await createVideoPlaylist({ - url: servers[1].url, - token: servers[1].accessToken, - playlistAttrs: { + const created = await servers[1].playlists.create({ + attributes: { displayName: 'playlist without thumbnail', privacy: VideoPlaylistPrivacy.PUBLIC, - videoChannelId: servers[1].videoChannel.id + videoChannelId: servers[1].store.channel.id } }) - playlistWithoutThumbnail = res.body.videoPlaylist.id + playlistWithoutThumbnailId = created.id - const res2 = await addVideoInPlaylist({ - url: servers[1].url, - token: servers[1].accessToken, - playlistId: playlistWithoutThumbnail, - elementAttrs: { videoId: video1 } + const added = await servers[1].playlists.addElement({ + playlistId: playlistWithoutThumbnailId, + attributes: { videoId: video1 } }) - withoutThumbnailE1 = res2.body.videoPlaylistElement.id + withoutThumbnailE1 = added.id await waitJobs(servers) @@ -99,25 +89,21 @@ describe('Playlist thumbnail', function () { it('Should not update the thumbnail if we explicitly uploaded a thumbnail', async function () { this.timeout(30000) - const res = await createVideoPlaylist({ - url: servers[1].url, - token: servers[1].accessToken, - playlistAttrs: { + const created = await servers[1].playlists.create({ + attributes: { displayName: 'playlist with thumbnail', privacy: VideoPlaylistPrivacy.PUBLIC, - videoChannelId: servers[1].videoChannel.id, + videoChannelId: servers[1].store.channel.id, thumbnailfile: 'thumbnail.jpg' } }) - playlistWithThumbnail = res.body.videoPlaylist.id + playlistWithThumbnailId = created.id - const res2 = await addVideoInPlaylist({ - url: servers[1].url, - token: servers[1].accessToken, - playlistId: playlistWithThumbnail, - elementAttrs: { videoId: video1 } + const added = await servers[1].playlists.addElement({ + playlistId: playlistWithThumbnailId, + attributes: { videoId: video1 } }) - withThumbnailE1 = res2.body.videoPlaylistElement.id + withThumbnailE1 = added.id await waitJobs(servers) @@ -130,19 +116,15 @@ describe('Playlist thumbnail', function () { it('Should automatically update the thumbnail when moving the first element', async function () { this.timeout(30000) - const res = await addVideoInPlaylist({ - url: servers[1].url, - token: servers[1].accessToken, - playlistId: playlistWithoutThumbnail, - elementAttrs: { videoId: video2 } + const added = await servers[1].playlists.addElement({ + playlistId: playlistWithoutThumbnailId, + attributes: { videoId: video2 } }) - withoutThumbnailE2 = res.body.videoPlaylistElement.id + withoutThumbnailE2 = added.id - await reorderVideosPlaylist({ - url: servers[1].url, - token: servers[1].accessToken, - playlistId: playlistWithoutThumbnail, - elementAttrs: { + await servers[1].playlists.reorderElements({ + playlistId: playlistWithoutThumbnailId, + attributes: { startPosition: 1, insertAfterPosition: 2 } @@ -159,19 +141,15 @@ describe('Playlist thumbnail', function () { it('Should not update the thumbnail when moving the first element if we explicitly uploaded a thumbnail', async function () { this.timeout(30000) - const res = await addVideoInPlaylist({ - url: servers[1].url, - token: servers[1].accessToken, - playlistId: playlistWithThumbnail, - elementAttrs: { videoId: video2 } + const added = await servers[1].playlists.addElement({ + playlistId: playlistWithThumbnailId, + attributes: { videoId: video2 } }) - withThumbnailE2 = res.body.videoPlaylistElement.id + withThumbnailE2 = added.id - await reorderVideosPlaylist({ - url: servers[1].url, - token: servers[1].accessToken, - playlistId: playlistWithThumbnail, - elementAttrs: { + await servers[1].playlists.reorderElements({ + playlistId: playlistWithThumbnailId, + attributes: { startPosition: 1, insertAfterPosition: 2 } @@ -188,11 +166,9 @@ describe('Playlist thumbnail', function () { it('Should automatically update the thumbnail when deleting the first element', async function () { this.timeout(30000) - await removeVideoFromPlaylist({ - url: servers[1].url, - token: servers[1].accessToken, - playlistId: playlistWithoutThumbnail, - playlistElementId: withoutThumbnailE1 + await servers[1].playlists.removeElement({ + playlistId: playlistWithoutThumbnailId, + elementId: withoutThumbnailE1 }) await waitJobs(servers) @@ -206,11 +182,9 @@ describe('Playlist thumbnail', function () { it('Should not update the thumbnail when deleting the first element if we explicitly uploaded a thumbnail', async function () { this.timeout(30000) - await removeVideoFromPlaylist({ - url: servers[1].url, - token: servers[1].accessToken, - playlistId: playlistWithThumbnail, - playlistElementId: withThumbnailE1 + await servers[1].playlists.removeElement({ + playlistId: playlistWithThumbnailId, + elementId: withThumbnailE1 }) await waitJobs(servers) @@ -224,11 +198,9 @@ describe('Playlist thumbnail', function () { it('Should the thumbnail when we delete the last element', async function () { this.timeout(30000) - await removeVideoFromPlaylist({ - url: servers[1].url, - token: servers[1].accessToken, - playlistId: playlistWithoutThumbnail, - playlistElementId: withoutThumbnailE2 + await servers[1].playlists.removeElement({ + playlistId: playlistWithoutThumbnailId, + elementId: withoutThumbnailE2 }) await waitJobs(servers) @@ -242,11 +214,9 @@ describe('Playlist thumbnail', function () { it('Should not update the thumbnail when we delete the last element if we explicitly uploaded a thumbnail', async function () { this.timeout(30000) - await removeVideoFromPlaylist({ - url: servers[1].url, - token: servers[1].accessToken, - playlistId: playlistWithThumbnail, - playlistElementId: withThumbnailE2 + await servers[1].playlists.removeElement({ + playlistId: playlistWithThumbnailId, + elementId: withThumbnailE2 }) await waitJobs(servers) diff --git a/server/tests/api/videos/video-playlists.ts b/server/tests/api/videos/video-playlists.ts index da8de054b..f42aee2ff 100644 --- a/server/tests/api/videos/video-playlists.ts +++ b/server/tests/api/videos/video-playlists.ts @@ -2,71 +2,33 @@ import 'mocha' import * as chai from 'chai' -import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' import { - addVideoChannel, - addVideoInPlaylist, - addVideoToBlacklist, checkPlaylistFilesWereRemoved, cleanupTests, - createUser, - createVideoPlaylist, - deleteVideoChannel, - deleteVideoPlaylist, + createMultipleServers, doubleFollow, - doVideosExistInMyPlaylist, - flushAndRunMultipleServers, - generateUserAccessToken, - getAccessToken, - getAccountPlaylistsList, - getAccountPlaylistsListWithToken, - getMyUserInformation, - getPlaylistVideos, - getVideoChannelPlaylistsList, - getVideoPlaylist, - getVideoPlaylistPrivacies, - getVideoPlaylistsList, - getVideoPlaylistWithToken, - removeUser, - removeVideoFromBlacklist, - removeVideoFromPlaylist, - reorderVideosPlaylist, - ServerInfo, + PeerTubeServer, + PlaylistsCommand, setAccessTokensToServers, setDefaultVideoChannel, testImage, - unfollow, - updateVideo, - updateVideoPlaylist, - updateVideoPlaylistElement, - uploadVideo, - uploadVideoAndGetId, - userLogin, wait, waitJobs -} from '../../../../shared/extra-utils' +} from '@shared/extra-utils' import { - addAccountToAccountBlocklist, - addAccountToServerBlocklist, - addServerToAccountBlocklist, - addServerToServerBlocklist, - removeAccountFromAccountBlocklist, - removeAccountFromServerBlocklist, - removeServerFromAccountBlocklist, - removeServerFromServerBlocklist -} from '../../../../shared/extra-utils/users/blocklist' -import { User } from '../../../../shared/models/users' -import { VideoPlaylistCreateResult, VideoPrivacy } from '../../../../shared/models/videos' -import { VideoExistInPlaylist } from '../../../../shared/models/videos/playlist/video-exist-in-playlist.model' -import { VideoPlaylistElement, VideoPlaylistElementType } from '../../../../shared/models/videos/playlist/video-playlist-element.model' -import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' -import { VideoPlaylistType } from '../../../../shared/models/videos/playlist/video-playlist-type.model' -import { VideoPlaylist } from '../../../../shared/models/videos/playlist/video-playlist.model' + HttpStatusCode, + VideoPlaylist, + VideoPlaylistCreateResult, + VideoPlaylistElementType, + VideoPlaylistPrivacy, + VideoPlaylistType, + VideoPrivacy +} from '@shared/models' const expect = chai.expect async function checkPlaylistElementType ( - servers: ServerInfo[], + servers: PeerTubeServer[], playlistId: string, type: VideoPlaylistElementType, position: number, @@ -74,10 +36,10 @@ async function checkPlaylistElementType ( total: number ) { for (const server of servers) { - const res = await getPlaylistVideos(server.url, server.accessToken, playlistId, 0, 10) - expect(res.body.total).to.equal(total) + const body = await server.playlists.listVideos({ token: server.accessToken, playlistId, start: 0, count: 10 }) + expect(body.total).to.equal(total) - const videoElement: VideoPlaylistElement = res.body.data.find((e: VideoPlaylistElement) => e.position === position) + const videoElement = body.data.find(e => e.position === position) expect(videoElement.type).to.equal(type, 'On server ' + server.url) if (type === VideoPlaylistElementType.REGULAR) { @@ -90,11 +52,11 @@ async function checkPlaylistElementType ( } describe('Test video playlists', function () { - let servers: ServerInfo[] = [] + let servers: PeerTubeServer[] = [] let playlistServer2Id1: number let playlistServer2Id2: number - let playlistServer2UUID2: number + let playlistServer2UUID2: string let playlistServer1Id: number let playlistServer1UUID: string @@ -106,12 +68,14 @@ describe('Test video playlists', function () { let nsfwVideoServer1: number - let userAccessTokenServer1: string + let userTokenServer1: string + + let commands: PlaylistsCommand[] before(async function () { this.timeout(120000) - servers = await flushAndRunMultipleServers(3, { transcoding: { enabled: false } }) + servers = await createMultipleServers(3, { transcoding: { enabled: false } }) // Get the access tokens await setAccessTokensToServers(servers) @@ -122,86 +86,78 @@ describe('Test video playlists', function () { // Server 1 and server 3 follow each other await doubleFollow(servers[0], servers[2]) + commands = servers.map(s => s.playlists) + { - servers[0].videos = [] - servers[1].videos = [] - servers[2].videos = [] + servers[0].store.videos = [] + servers[1].store.videos = [] + servers[2].store.videos = [] for (const server of servers) { for (let i = 0; i < 7; i++) { const name = `video ${i} server ${server.serverNumber}` - const resVideo = await uploadVideo(server.url, server.accessToken, { name, nsfw: false }) + const video = await server.videos.upload({ attributes: { name, nsfw: false } }) - server.videos.push(resVideo.body.video) + server.store.videos.push(video) } } } - nsfwVideoServer1 = (await uploadVideoAndGetId({ server: servers[0], videoName: 'NSFW video', nsfw: true })).id + nsfwVideoServer1 = (await servers[0].videos.quickUpload({ name: 'NSFW video', nsfw: true })).id - { - await createUser({ - url: servers[0].url, - accessToken: servers[0].accessToken, - username: 'user1', - password: 'password' - }) - userAccessTokenServer1 = await getAccessToken(servers[0].url, 'user1', 'password') - } + userTokenServer1 = await servers[0].users.generateUserAndToken('user1') await waitJobs(servers) }) describe('Get default playlists', function () { + it('Should list video playlist privacies', async function () { - const res = await getVideoPlaylistPrivacies(servers[0].url) + const privacies = await commands[0].getPrivacies() - const privacies = res.body expect(Object.keys(privacies)).to.have.length.at.least(3) - expect(privacies[3]).to.equal('Private') }) it('Should list watch later playlist', async function () { - const url = servers[0].url - const accessToken = servers[0].accessToken + const token = servers[0].accessToken { - const res = await getAccountPlaylistsListWithToken(url, accessToken, 'root', 0, 5, VideoPlaylistType.WATCH_LATER) + const body = await commands[0].listByAccount({ token, handle: 'root', playlistType: VideoPlaylistType.WATCH_LATER }) - expect(res.body.total).to.equal(1) - expect(res.body.data).to.have.lengthOf(1) + expect(body.total).to.equal(1) + expect(body.data).to.have.lengthOf(1) - const playlist: VideoPlaylist = res.body.data[0] + const playlist = body.data[0] expect(playlist.displayName).to.equal('Watch later') expect(playlist.type.id).to.equal(VideoPlaylistType.WATCH_LATER) expect(playlist.type.label).to.equal('Watch later') } { - const res = await getAccountPlaylistsListWithToken(url, accessToken, 'root', 0, 5, VideoPlaylistType.REGULAR) + const body = await commands[0].listByAccount({ token, handle: 'root', playlistType: VideoPlaylistType.REGULAR }) - expect(res.body.total).to.equal(0) - expect(res.body.data).to.have.lengthOf(0) + expect(body.total).to.equal(0) + expect(body.data).to.have.lengthOf(0) } { - const res = await getAccountPlaylistsList(url, 'root', 0, 5) - expect(res.body.total).to.equal(0) - expect(res.body.data).to.have.lengthOf(0) + const body = await commands[0].listByAccount({ handle: 'root' }) + expect(body.total).to.equal(0) + expect(body.data).to.have.lengthOf(0) } }) it('Should get private playlist for a classic user', async function () { - const token = await generateUserAccessToken(servers[0], 'toto') + const token = await servers[0].users.generateUserAndToken('toto') - const res = await getAccountPlaylistsListWithToken(servers[0].url, token, 'toto', 0, 5) + const body = await commands[0].listByAccount({ token, handle: 'toto' }) - expect(res.body.total).to.equal(1) - expect(res.body.data).to.have.lengthOf(1) + expect(body.total).to.equal(1) + expect(body.data).to.have.lengthOf(1) - const playlistId = res.body.data[0].id - await getPlaylistVideos(servers[0].url, token, playlistId, 0, 5) + const playlistId = body.data[0].id + await commands[0].listVideos({ token, playlistId }) }) }) @@ -210,15 +166,13 @@ describe('Test video playlists', function () { it('Should create a playlist on server 1 and have the playlist on server 2 and 3', async function () { this.timeout(30000) - await createVideoPlaylist({ - url: servers[0].url, - token: servers[0].accessToken, - playlistAttrs: { + await commands[0].create({ + attributes: { displayName: 'my super playlist', privacy: VideoPlaylistPrivacy.PUBLIC, description: 'my super description', thumbnailfile: 'thumbnail.jpg', - videoChannelId: servers[0].videoChannel.id + videoChannelId: servers[0].store.channel.id } }) @@ -227,14 +181,13 @@ describe('Test video playlists', function () { await wait(3000) for (const server of servers) { - const res = await getVideoPlaylistsList(server.url, 0, 5) - expect(res.body.total).to.equal(1) - expect(res.body.data).to.have.lengthOf(1) + const body = await server.playlists.list({ start: 0, count: 5 }) + expect(body.total).to.equal(1) + expect(body.data).to.have.lengthOf(1) - const playlistFromList = res.body.data[0] as VideoPlaylist + const playlistFromList = body.data[0] - const res2 = await getVideoPlaylist(server.url, playlistFromList.uuid) - const playlistFromGet = res2.body as VideoPlaylist + const playlistFromGet = await server.playlists.get({ playlistId: playlistFromList.uuid }) for (const playlist of [ playlistFromGet, playlistFromList ]) { expect(playlist.id).to.be.a('number') @@ -264,46 +217,38 @@ describe('Test video playlists', function () { this.timeout(30000) { - const res = await createVideoPlaylist({ - url: servers[1].url, - token: servers[1].accessToken, - playlistAttrs: { + const playlist = await servers[1].playlists.create({ + attributes: { displayName: 'playlist 2', privacy: VideoPlaylistPrivacy.PUBLIC, - videoChannelId: servers[1].videoChannel.id + videoChannelId: servers[1].store.channel.id } }) - playlistServer2Id1 = res.body.videoPlaylist.id + playlistServer2Id1 = playlist.id } { - const res = await createVideoPlaylist({ - url: servers[1].url, - token: servers[1].accessToken, - playlistAttrs: { + const playlist = await servers[1].playlists.create({ + attributes: { displayName: 'playlist 3', privacy: VideoPlaylistPrivacy.PUBLIC, thumbnailfile: 'thumbnail.jpg', - videoChannelId: servers[1].videoChannel.id + videoChannelId: servers[1].store.channel.id } }) - playlistServer2Id2 = res.body.videoPlaylist.id - playlistServer2UUID2 = res.body.videoPlaylist.uuid + playlistServer2Id2 = playlist.id + playlistServer2UUID2 = playlist.uuid } for (const id of [ playlistServer2Id1, playlistServer2Id2 ]) { - await addVideoInPlaylist({ - url: servers[1].url, - token: servers[1].accessToken, + await servers[1].playlists.addElement({ playlistId: id, - elementAttrs: { videoId: servers[1].videos[0].id, startTimestamp: 1, stopTimestamp: 2 } + attributes: { videoId: servers[1].store.videos[0].id, startTimestamp: 1, stopTimestamp: 2 } }) - await addVideoInPlaylist({ - url: servers[1].url, - token: servers[1].accessToken, + await servers[1].playlists.addElement({ playlistId: id, - elementAttrs: { videoId: servers[1].videos[1].id } + attributes: { videoId: servers[1].store.videos[1].id } }) } @@ -311,20 +256,20 @@ describe('Test video playlists', function () { await wait(3000) for (const server of [ servers[0], servers[1] ]) { - const res = await getVideoPlaylistsList(server.url, 0, 5) + const body = await server.playlists.list({ start: 0, count: 5 }) - const playlist2 = res.body.data.find(p => p.displayName === 'playlist 2') + const playlist2 = body.data.find(p => p.displayName === 'playlist 2') expect(playlist2).to.not.be.undefined await testImage(server.url, 'thumbnail-playlist', playlist2.thumbnailPath) - const playlist3 = res.body.data.find(p => p.displayName === 'playlist 3') + const playlist3 = body.data.find(p => p.displayName === 'playlist 3') expect(playlist3).to.not.be.undefined await testImage(server.url, 'thumbnail', playlist3.thumbnailPath) } - const res = await getVideoPlaylistsList(servers[2].url, 0, 5) - expect(res.body.data.find(p => p.displayName === 'playlist 2')).to.be.undefined - expect(res.body.data.find(p => p.displayName === 'playlist 3')).to.be.undefined + const body = await servers[2].playlists.list({ start: 0, count: 5 }) + expect(body.data.find(p => p.displayName === 'playlist 2')).to.be.undefined + expect(body.data.find(p => p.displayName === 'playlist 3')).to.be.undefined }) it('Should have the playlist on server 3 after a new follow', async function () { @@ -333,13 +278,13 @@ describe('Test video playlists', function () { // Server 2 and server 3 follow each other await doubleFollow(servers[1], servers[2]) - const res = await getVideoPlaylistsList(servers[2].url, 0, 5) + const body = await servers[2].playlists.list({ start: 0, count: 5 }) - const playlist2 = res.body.data.find(p => p.displayName === 'playlist 2') + const playlist2 = body.data.find(p => p.displayName === 'playlist 2') expect(playlist2).to.not.be.undefined await testImage(servers[2].url, 'thumbnail-playlist', playlist2.thumbnailPath) - expect(res.body.data.find(p => p.displayName === 'playlist 3')).to.not.be.undefined + expect(body.data.find(p => p.displayName === 'playlist 3')).to.not.be.undefined }) }) @@ -349,22 +294,20 @@ describe('Test video playlists', function () { this.timeout(30000) { - const res = await getVideoPlaylistsList(servers[2].url, 1, 2, 'createdAt') - - expect(res.body.total).to.equal(3) + const body = await servers[2].playlists.list({ start: 1, count: 2, sort: 'createdAt' }) + expect(body.total).to.equal(3) - const data: VideoPlaylist[] = res.body.data + const data = body.data expect(data).to.have.lengthOf(2) expect(data[0].displayName).to.equal('playlist 2') expect(data[1].displayName).to.equal('playlist 3') } { - const res = await getVideoPlaylistsList(servers[2].url, 1, 2, '-createdAt') + const body = await servers[2].playlists.list({ start: 1, count: 2, sort: '-createdAt' }) + expect(body.total).to.equal(3) - expect(res.body.total).to.equal(3) - - const data: VideoPlaylist[] = res.body.data + const data = body.data expect(data).to.have.lengthOf(2) expect(data[0].displayName).to.equal('playlist 2') expect(data[1].displayName).to.equal('my super playlist') @@ -375,11 +318,10 @@ describe('Test video playlists', function () { this.timeout(30000) { - const res = await getVideoChannelPlaylistsList(servers[0].url, 'root_channel', 0, 2, '-createdAt') - - expect(res.body.total).to.equal(1) + const body = await commands[0].listByChannel({ handle: 'root_channel', start: 0, count: 2, sort: '-createdAt' }) + expect(body.total).to.equal(1) - const data: VideoPlaylist[] = res.body.data + const data = body.data expect(data).to.have.lengthOf(1) expect(data[0].displayName).to.equal('my super playlist') } @@ -389,41 +331,37 @@ describe('Test video playlists', function () { this.timeout(30000) { - const res = await getAccountPlaylistsList(servers[1].url, 'root', 1, 2, '-createdAt') + const body = await servers[1].playlists.listByAccount({ handle: 'root', start: 1, count: 2, sort: '-createdAt' }) + expect(body.total).to.equal(2) - expect(res.body.total).to.equal(2) - - const data: VideoPlaylist[] = res.body.data + const data = body.data expect(data).to.have.lengthOf(1) expect(data[0].displayName).to.equal('playlist 2') } { - const res = await getAccountPlaylistsList(servers[1].url, 'root', 1, 2, 'createdAt') - - expect(res.body.total).to.equal(2) + const body = await servers[1].playlists.listByAccount({ handle: 'root', start: 1, count: 2, sort: 'createdAt' }) + expect(body.total).to.equal(2) - const data: VideoPlaylist[] = res.body.data + const data = body.data expect(data).to.have.lengthOf(1) expect(data[0].displayName).to.equal('playlist 3') } { - const res = await getAccountPlaylistsList(servers[1].url, 'root', 0, 10, 'createdAt', '3') + const body = await servers[1].playlists.listByAccount({ handle: 'root', sort: 'createdAt', search: '3' }) + expect(body.total).to.equal(1) - expect(res.body.total).to.equal(1) - - const data: VideoPlaylist[] = res.body.data + const data = body.data expect(data).to.have.lengthOf(1) expect(data[0].displayName).to.equal('playlist 3') } { - const res = await getAccountPlaylistsList(servers[1].url, 'root', 0, 10, 'createdAt', '4') - - expect(res.body.total).to.equal(0) + const body = await servers[1].playlists.listByAccount({ handle: 'root', sort: 'createdAt', search: '4' }) + expect(body.total).to.equal(0) - const data: VideoPlaylist[] = res.body.data + const data = body.data expect(data).to.have.lengthOf(0) } }) @@ -437,28 +375,22 @@ describe('Test video playlists', function () { this.timeout(30000) { - const res = await createVideoPlaylist({ - url: servers[1].url, - token: servers[1].accessToken, - playlistAttrs: { + unlistedPlaylist = await servers[1].playlists.create({ + attributes: { displayName: 'playlist unlisted', privacy: VideoPlaylistPrivacy.UNLISTED, - videoChannelId: servers[1].videoChannel.id + videoChannelId: servers[1].store.channel.id } }) - unlistedPlaylist = res.body.videoPlaylist } { - const res = await createVideoPlaylist({ - url: servers[1].url, - token: servers[1].accessToken, - playlistAttrs: { + privatePlaylist = await servers[1].playlists.create({ + attributes: { displayName: 'playlist private', privacy: VideoPlaylistPrivacy.PRIVATE } }) - privatePlaylist = res.body.videoPlaylist } await waitJobs(servers) @@ -468,15 +400,15 @@ describe('Test video playlists', function () { it('Should not list unlisted or private playlists', async function () { for (const server of servers) { const results = [ - await getAccountPlaylistsList(server.url, 'root@localhost:' + servers[1].port, 0, 5, '-createdAt'), - await getVideoPlaylistsList(server.url, 0, 2, '-createdAt') + await server.playlists.listByAccount({ handle: 'root@localhost:' + servers[1].port, sort: '-createdAt' }), + await server.playlists.list({ start: 0, count: 2, sort: '-createdAt' }) ] - expect(results[0].body.total).to.equal(2) - expect(results[1].body.total).to.equal(3) + expect(results[0].total).to.equal(2) + expect(results[1].total).to.equal(3) - for (const res of results) { - const data: VideoPlaylist[] = res.body.data + for (const body of results) { + const data = body.data expect(data).to.have.lengthOf(2) expect(data[0].displayName).to.equal('playlist 3') expect(data[1].displayName).to.equal('playlist 2') @@ -485,23 +417,23 @@ describe('Test video playlists', function () { }) it('Should not get unlisted playlist using only the id', async function () { - await getVideoPlaylist(servers[1].url, unlistedPlaylist.id, 404) + await servers[1].playlists.get({ playlistId: unlistedPlaylist.id, expectedStatus: 404 }) }) it('Should get unlisted plyaylist using uuid or shortUUID', async function () { - await getVideoPlaylist(servers[1].url, unlistedPlaylist.uuid) - await getVideoPlaylist(servers[1].url, unlistedPlaylist.shortUUID) + await servers[1].playlists.get({ playlistId: unlistedPlaylist.uuid }) + await servers[1].playlists.get({ playlistId: unlistedPlaylist.shortUUID }) }) it('Should not get private playlist without token', async function () { for (const id of [ privatePlaylist.id, privatePlaylist.uuid, privatePlaylist.shortUUID ]) { - await getVideoPlaylist(servers[1].url, id, 401) + await servers[1].playlists.get({ playlistId: id, expectedStatus: 401 }) } }) it('Should get private playlist with a token', async function () { for (const id of [ privatePlaylist.id, privatePlaylist.uuid, privatePlaylist.shortUUID ]) { - await getVideoPlaylistWithToken(servers[1].url, servers[1].accessToken, id) + await servers[1].playlists.get({ token: servers[1].accessToken, playlistId: id }) } }) }) @@ -511,15 +443,13 @@ describe('Test video playlists', function () { it('Should update a playlist', async function () { this.timeout(30000) - await updateVideoPlaylist({ - url: servers[1].url, - token: servers[1].accessToken, - playlistAttrs: { + await servers[1].playlists.update({ + attributes: { displayName: 'playlist 3 updated', description: 'description updated', privacy: VideoPlaylistPrivacy.UNLISTED, thumbnailfile: 'thumbnail.jpg', - videoChannelId: servers[1].videoChannel.id + videoChannelId: servers[1].store.channel.id }, playlistId: playlistServer2Id2 }) @@ -527,8 +457,7 @@ describe('Test video playlists', function () { await waitJobs(servers) for (const server of servers) { - const res = await getVideoPlaylist(server.url, playlistServer2UUID2) - const playlist: VideoPlaylist = res.body + const playlist = await server.playlists.get({ playlistId: playlistServer2UUID2 }) expect(playlist.displayName).to.equal('playlist 3 updated') expect(playlist.description).to.equal('description updated') @@ -554,39 +483,37 @@ describe('Test video playlists', function () { it('Should create a playlist containing different startTimestamp/endTimestamp videos', async function () { this.timeout(30000) - const addVideo = (elementAttrs: any) => { - return addVideoInPlaylist({ url: servers[0].url, token: servers[0].accessToken, playlistId: playlistServer1Id, elementAttrs }) + const addVideo = (attributes: any) => { + return commands[0].addElement({ playlistId: playlistServer1Id, attributes }) } - const res = await createVideoPlaylist({ - url: servers[0].url, - token: servers[0].accessToken, - playlistAttrs: { + const playlist = await commands[0].create({ + attributes: { displayName: 'playlist 4', privacy: VideoPlaylistPrivacy.PUBLIC, - videoChannelId: servers[0].videoChannel.id + videoChannelId: servers[0].store.channel.id } }) - playlistServer1Id = res.body.videoPlaylist.id - playlistServer1UUID = res.body.videoPlaylist.uuid + playlistServer1Id = playlist.id + playlistServer1UUID = playlist.uuid - await addVideo({ videoId: servers[0].videos[0].uuid, startTimestamp: 15, stopTimestamp: 28 }) - await addVideo({ videoId: servers[2].videos[1].uuid, startTimestamp: 35 }) - await addVideo({ videoId: servers[2].videos[2].uuid }) + await addVideo({ videoId: servers[0].store.videos[0].uuid, startTimestamp: 15, stopTimestamp: 28 }) + await addVideo({ videoId: servers[2].store.videos[1].uuid, startTimestamp: 35 }) + await addVideo({ videoId: servers[2].store.videos[2].uuid }) { - const res = await addVideo({ videoId: servers[0].videos[3].uuid, stopTimestamp: 35 }) - playlistElementServer1Video4 = res.body.videoPlaylistElement.id + const element = await addVideo({ videoId: servers[0].store.videos[3].uuid, stopTimestamp: 35 }) + playlistElementServer1Video4 = element.id } { - const res = await addVideo({ videoId: servers[0].videos[4].uuid, startTimestamp: 45, stopTimestamp: 60 }) - playlistElementServer1Video5 = res.body.videoPlaylistElement.id + const element = await addVideo({ videoId: servers[0].store.videos[4].uuid, startTimestamp: 45, stopTimestamp: 60 }) + playlistElementServer1Video5 = element.id } { - const res = await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 5 }) - playlistElementNSFW = res.body.videoPlaylistElement.id + const element = await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 5 }) + playlistElementNSFW = element.id await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 4 }) await addVideo({ videoId: nsfwVideoServer1 }) @@ -599,64 +526,68 @@ describe('Test video playlists', function () { this.timeout(30000) for (const server of servers) { - const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) - - expect(res.body.total).to.equal(8) - - const videoElements: VideoPlaylistElement[] = res.body.data - expect(videoElements).to.have.lengthOf(8) - - expect(videoElements[0].video.name).to.equal('video 0 server 1') - expect(videoElements[0].position).to.equal(1) - expect(videoElements[0].startTimestamp).to.equal(15) - expect(videoElements[0].stopTimestamp).to.equal(28) - - expect(videoElements[1].video.name).to.equal('video 1 server 3') - expect(videoElements[1].position).to.equal(2) - expect(videoElements[1].startTimestamp).to.equal(35) - expect(videoElements[1].stopTimestamp).to.be.null - - expect(videoElements[2].video.name).to.equal('video 2 server 3') - expect(videoElements[2].position).to.equal(3) - expect(videoElements[2].startTimestamp).to.be.null - expect(videoElements[2].stopTimestamp).to.be.null - - expect(videoElements[3].video.name).to.equal('video 3 server 1') - expect(videoElements[3].position).to.equal(4) - expect(videoElements[3].startTimestamp).to.be.null - expect(videoElements[3].stopTimestamp).to.equal(35) - - expect(videoElements[4].video.name).to.equal('video 4 server 1') - expect(videoElements[4].position).to.equal(5) - expect(videoElements[4].startTimestamp).to.equal(45) - expect(videoElements[4].stopTimestamp).to.equal(60) - - expect(videoElements[5].video.name).to.equal('NSFW video') - expect(videoElements[5].position).to.equal(6) - expect(videoElements[5].startTimestamp).to.equal(5) - expect(videoElements[5].stopTimestamp).to.be.null - - expect(videoElements[6].video.name).to.equal('NSFW video') - expect(videoElements[6].position).to.equal(7) - expect(videoElements[6].startTimestamp).to.equal(4) - expect(videoElements[6].stopTimestamp).to.be.null - - expect(videoElements[7].video.name).to.equal('NSFW video') - expect(videoElements[7].position).to.equal(8) - expect(videoElements[7].startTimestamp).to.be.null - expect(videoElements[7].stopTimestamp).to.be.null - - const res3 = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 2) - expect(res3.body.data).to.have.lengthOf(2) + { + const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 }) + + expect(body.total).to.equal(8) + + const videoElements = body.data + expect(videoElements).to.have.lengthOf(8) + + expect(videoElements[0].video.name).to.equal('video 0 server 1') + expect(videoElements[0].position).to.equal(1) + expect(videoElements[0].startTimestamp).to.equal(15) + expect(videoElements[0].stopTimestamp).to.equal(28) + + expect(videoElements[1].video.name).to.equal('video 1 server 3') + expect(videoElements[1].position).to.equal(2) + expect(videoElements[1].startTimestamp).to.equal(35) + expect(videoElements[1].stopTimestamp).to.be.null + + expect(videoElements[2].video.name).to.equal('video 2 server 3') + expect(videoElements[2].position).to.equal(3) + expect(videoElements[2].startTimestamp).to.be.null + expect(videoElements[2].stopTimestamp).to.be.null + + expect(videoElements[3].video.name).to.equal('video 3 server 1') + expect(videoElements[3].position).to.equal(4) + expect(videoElements[3].startTimestamp).to.be.null + expect(videoElements[3].stopTimestamp).to.equal(35) + + expect(videoElements[4].video.name).to.equal('video 4 server 1') + expect(videoElements[4].position).to.equal(5) + expect(videoElements[4].startTimestamp).to.equal(45) + expect(videoElements[4].stopTimestamp).to.equal(60) + + expect(videoElements[5].video.name).to.equal('NSFW video') + expect(videoElements[5].position).to.equal(6) + expect(videoElements[5].startTimestamp).to.equal(5) + expect(videoElements[5].stopTimestamp).to.be.null + + expect(videoElements[6].video.name).to.equal('NSFW video') + expect(videoElements[6].position).to.equal(7) + expect(videoElements[6].startTimestamp).to.equal(4) + expect(videoElements[6].stopTimestamp).to.be.null + + expect(videoElements[7].video.name).to.equal('NSFW video') + expect(videoElements[7].position).to.equal(8) + expect(videoElements[7].startTimestamp).to.be.null + expect(videoElements[7].stopTimestamp).to.be.null + } + + { + const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 2 }) + expect(body.data).to.have.lengthOf(2) + } } }) }) describe('Element type', function () { - let groupUser1: ServerInfo[] - let groupWithoutToken1: ServerInfo[] - let group1: ServerInfo[] - let group2: ServerInfo[] + let groupUser1: PeerTubeServer[] + let groupWithoutToken1: PeerTubeServer[] + let group1: PeerTubeServer[] + let group2: PeerTubeServer[] let video1: string let video2: string @@ -665,31 +596,30 @@ describe('Test video playlists', function () { before(async function () { this.timeout(60000) - groupUser1 = [ Object.assign({}, servers[0], { accessToken: userAccessTokenServer1 }) ] + groupUser1 = [ Object.assign({}, servers[0], { accessToken: userTokenServer1 }) ] groupWithoutToken1 = [ Object.assign({}, servers[0], { accessToken: undefined }) ] group1 = [ servers[0] ] group2 = [ servers[1], servers[2] ] - const res = await createVideoPlaylist({ - url: servers[0].url, - token: userAccessTokenServer1, - playlistAttrs: { + const playlist = await commands[0].create({ + token: userTokenServer1, + attributes: { displayName: 'playlist 56', privacy: VideoPlaylistPrivacy.PUBLIC, - videoChannelId: servers[0].videoChannel.id + videoChannelId: servers[0].store.channel.id } }) - const playlistServer1Id2 = res.body.videoPlaylist.id - playlistServer1UUID2 = res.body.videoPlaylist.uuid + const playlistServer1Id2 = playlist.id + playlistServer1UUID2 = playlist.uuid - const addVideo = (elementAttrs: any) => { - return addVideoInPlaylist({ url: servers[0].url, token: userAccessTokenServer1, playlistId: playlistServer1Id2, elementAttrs }) + const addVideo = (attributes: any) => { + return commands[0].addElement({ token: userTokenServer1, playlistId: playlistServer1Id2, attributes }) } - video1 = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video 89', token: userAccessTokenServer1 })).uuid - video2 = (await uploadVideoAndGetId({ server: servers[1], videoName: 'video 90' })).uuid - video3 = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video 91', nsfw: true })).uuid + video1 = (await servers[0].videos.quickUpload({ name: 'video 89', token: userTokenServer1 })).uuid + video2 = (await servers[1].videos.quickUpload({ name: 'video 90' })).uuid + video3 = (await servers[0].videos.quickUpload({ name: 'video 91', nsfw: true })).uuid await waitJobs(servers) @@ -707,7 +637,7 @@ describe('Test video playlists', function () { const position = 1 { - await updateVideo(servers[0].url, servers[0].accessToken, video1, { privacy: VideoPrivacy.PRIVATE }) + await servers[0].videos.update({ id: video1, attributes: { privacy: VideoPrivacy.PRIVATE } }) await waitJobs(servers) await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) @@ -717,7 +647,7 @@ describe('Test video playlists', function () { } { - await updateVideo(servers[0].url, servers[0].accessToken, video1, { privacy: VideoPrivacy.PUBLIC }) + await servers[0].videos.update({ id: video1, attributes: { privacy: VideoPrivacy.PUBLIC } }) await waitJobs(servers) await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) @@ -735,7 +665,7 @@ describe('Test video playlists', function () { const position = 1 { - await addVideoToBlacklist(servers[0].url, servers[0].accessToken, video1, 'reason', true) + await servers[0].blacklist.add({ videoId: video1, reason: 'reason', unfederate: true }) await waitJobs(servers) await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) @@ -745,7 +675,7 @@ describe('Test video playlists', function () { } { - await removeVideoFromBlacklist(servers[0].url, servers[0].accessToken, video1) + await servers[0].blacklist.remove({ videoId: video1 }) await waitJobs(servers) await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) @@ -759,56 +689,58 @@ describe('Test video playlists', function () { it('Should update the element type if the account or server of the video is blocked', async function () { this.timeout(90000) + const command = servers[0].blocklist + const name = 'video 90' const position = 2 { - await addAccountToAccountBlocklist(servers[0].url, userAccessTokenServer1, 'root@localhost:' + servers[1].port) + await command.addToMyBlocklist({ token: userTokenServer1, account: 'root@localhost:' + servers[1].port }) await waitJobs(servers) await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3) await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) - await removeAccountFromAccountBlocklist(servers[0].url, userAccessTokenServer1, 'root@localhost:' + servers[1].port) + await command.removeFromMyBlocklist({ token: userTokenServer1, account: 'root@localhost:' + servers[1].port }) await waitJobs(servers) await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) } { - await addServerToAccountBlocklist(servers[0].url, userAccessTokenServer1, 'localhost:' + servers[1].port) + await command.addToMyBlocklist({ token: userTokenServer1, server: 'localhost:' + servers[1].port }) await waitJobs(servers) await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3) await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) - await removeServerFromAccountBlocklist(servers[0].url, userAccessTokenServer1, 'localhost:' + servers[1].port) + await command.removeFromMyBlocklist({ token: userTokenServer1, server: 'localhost:' + servers[1].port }) await waitJobs(servers) await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) } { - await addAccountToServerBlocklist(servers[0].url, servers[0].accessToken, 'root@localhost:' + servers[1].port) + await command.addToServerBlocklist({ account: 'root@localhost:' + servers[1].port }) await waitJobs(servers) await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3) await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) - await removeAccountFromServerBlocklist(servers[0].url, servers[0].accessToken, 'root@localhost:' + servers[1].port) + await command.removeFromServerBlocklist({ account: 'root@localhost:' + servers[1].port }) await waitJobs(servers) await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) } { - await addServerToServerBlocklist(servers[0].url, servers[0].accessToken, 'localhost:' + servers[1].port) + await command.addToServerBlocklist({ server: 'localhost:' + servers[1].port }) await waitJobs(servers) await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3) await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) - await removeServerFromServerBlocklist(servers[0].url, servers[0].accessToken, 'localhost:' + servers[1].port) + await command.removeFromServerBlocklist({ server: 'localhost:' + servers[1].port }) await waitJobs(servers) await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) @@ -816,10 +748,10 @@ describe('Test video playlists', function () { }) it('Should hide the video if it is NSFW', async function () { - const res = await getPlaylistVideos(servers[0].url, userAccessTokenServer1, playlistServer1UUID2, 0, 10, { nsfw: false }) - expect(res.body.total).to.equal(3) + const body = await commands[0].listVideos({ token: userTokenServer1, playlistId: playlistServer1UUID2, query: { nsfw: 'false' } }) + expect(body.total).to.equal(3) - const elements: VideoPlaylistElement[] = res.body.data + const elements = body.data const element = elements.find(e => e.position === 3) expect(element).to.exist @@ -835,11 +767,9 @@ describe('Test video playlists', function () { this.timeout(30000) { - await reorderVideosPlaylist({ - url: servers[0].url, - token: servers[0].accessToken, + await commands[0].reorderElements({ playlistId: playlistServer1Id, - elementAttrs: { + attributes: { startPosition: 2, insertAfterPosition: 3 } @@ -848,8 +778,8 @@ describe('Test video playlists', function () { await waitJobs(servers) for (const server of servers) { - const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) - const names = (res.body.data as VideoPlaylistElement[]).map(v => v.video.name) + const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 }) + const names = body.data.map(v => v.video.name) expect(names).to.deep.equal([ 'video 0 server 1', @@ -865,11 +795,9 @@ describe('Test video playlists', function () { } { - await reorderVideosPlaylist({ - url: servers[0].url, - token: servers[0].accessToken, + await commands[0].reorderElements({ playlistId: playlistServer1Id, - elementAttrs: { + attributes: { startPosition: 1, reorderLength: 3, insertAfterPosition: 4 @@ -879,8 +807,8 @@ describe('Test video playlists', function () { await waitJobs(servers) for (const server of servers) { - const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) - const names = (res.body.data as VideoPlaylistElement[]).map(v => v.video.name) + const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 }) + const names = body.data.map(v => v.video.name) expect(names).to.deep.equal([ 'video 3 server 1', @@ -896,11 +824,9 @@ describe('Test video playlists', function () { } { - await reorderVideosPlaylist({ - url: servers[0].url, - token: servers[0].accessToken, + await commands[0].reorderElements({ playlistId: playlistServer1Id, - elementAttrs: { + attributes: { startPosition: 6, insertAfterPosition: 3 } @@ -909,8 +835,7 @@ describe('Test video playlists', function () { await waitJobs(servers) for (const server of servers) { - const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) - const elements: VideoPlaylistElement[] = res.body.data + const { data: elements } = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 }) const names = elements.map(v => v.video.name) expect(names).to.deep.equal([ @@ -934,22 +859,18 @@ describe('Test video playlists', function () { it('Should update startTimestamp/endTimestamp of some elements', async function () { this.timeout(30000) - await updateVideoPlaylistElement({ - url: servers[0].url, - token: servers[0].accessToken, + await commands[0].updateElement({ playlistId: playlistServer1Id, - playlistElementId: playlistElementServer1Video4, - elementAttrs: { + elementId: playlistElementServer1Video4, + attributes: { startTimestamp: 1 } }) - await updateVideoPlaylistElement({ - url: servers[0].url, - token: servers[0].accessToken, + await commands[0].updateElement({ playlistId: playlistServer1Id, - playlistElementId: playlistElementServer1Video5, - elementAttrs: { + elementId: playlistElementServer1Video5, + attributes: { stopTimestamp: null } }) @@ -957,8 +878,7 @@ describe('Test video playlists', function () { await waitJobs(servers) for (const server of servers) { - const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) - const elements: VideoPlaylistElement[] = res.body.data + const { data: elements } = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 }) expect(elements[0].video.name).to.equal('video 3 server 1') expect(elements[0].position).to.equal(1) @@ -974,17 +894,16 @@ describe('Test video playlists', function () { it('Should check videos existence in my playlist', async function () { const videoIds = [ - servers[0].videos[0].id, + servers[0].store.videos[0].id, 42000, - servers[0].videos[3].id, + servers[0].store.videos[3].id, 43000, - servers[0].videos[4].id + servers[0].store.videos[4].id ] - const res = await doVideosExistInMyPlaylist(servers[0].url, servers[0].accessToken, videoIds) - const obj = res.body as VideoExistInPlaylist + const obj = await commands[0].videosExist({ videoIds }) { - const elem = obj[servers[0].videos[0].id] + const elem = obj[servers[0].store.videos[0].id] expect(elem).to.have.lengthOf(1) expect(elem[0].playlistElementId).to.exist expect(elem[0].playlistId).to.equal(playlistServer1Id) @@ -993,7 +912,7 @@ describe('Test video playlists', function () { } { - const elem = obj[servers[0].videos[3].id] + const elem = obj[servers[0].store.videos[3].id] expect(elem).to.have.lengthOf(1) expect(elem[0].playlistElementId).to.equal(playlistElementServer1Video4) expect(elem[0].playlistId).to.equal(playlistServer1Id) @@ -1002,7 +921,7 @@ describe('Test video playlists', function () { } { - const elem = obj[servers[0].videos[4].id] + const elem = obj[servers[0].store.videos[4].id] expect(elem).to.have.lengthOf(1) expect(elem[0].playlistId).to.equal(playlistServer1Id) expect(elem[0].startTimestamp).to.equal(45) @@ -1015,42 +934,29 @@ describe('Test video playlists', function () { it('Should automatically update updatedAt field of playlists', async function () { const server = servers[1] - const videoId = servers[1].videos[5].id + const videoId = servers[1].store.videos[5].id async function getPlaylistNames () { - const res = await getAccountPlaylistsListWithToken(server.url, server.accessToken, 'root', 0, 5, undefined, '-updatedAt') + const { data } = await server.playlists.listByAccount({ token: server.accessToken, handle: 'root', sort: '-updatedAt' }) - return (res.body.data as VideoPlaylist[]).map(p => p.displayName) + return data.map(p => p.displayName) } - const elementAttrs = { videoId } - const res1 = await addVideoInPlaylist({ url: server.url, token: server.accessToken, playlistId: playlistServer2Id1, elementAttrs }) - const res2 = await addVideoInPlaylist({ url: server.url, token: server.accessToken, playlistId: playlistServer2Id2, elementAttrs }) - - const element1 = res1.body.videoPlaylistElement.id - const element2 = res2.body.videoPlaylistElement.id + const attributes = { videoId } + const element1 = await server.playlists.addElement({ playlistId: playlistServer2Id1, attributes }) + const element2 = await server.playlists.addElement({ playlistId: playlistServer2Id2, attributes }) const names1 = await getPlaylistNames() expect(names1[0]).to.equal('playlist 3 updated') expect(names1[1]).to.equal('playlist 2') - await removeVideoFromPlaylist({ - url: server.url, - token: server.accessToken, - playlistId: playlistServer2Id1, - playlistElementId: element1 - }) + await server.playlists.removeElement({ playlistId: playlistServer2Id1, elementId: element1.id }) const names2 = await getPlaylistNames() expect(names2[0]).to.equal('playlist 2') expect(names2[1]).to.equal('playlist 3 updated') - await removeVideoFromPlaylist({ - url: server.url, - token: server.accessToken, - playlistId: playlistServer2Id2, - playlistElementId: element2 - }) + await server.playlists.removeElement({ playlistId: playlistServer2Id2, elementId: element2.id }) const names3 = await getPlaylistNames() expect(names3[0]).to.equal('playlist 3 updated') @@ -1060,28 +966,16 @@ describe('Test video playlists', function () { it('Should delete some elements', async function () { this.timeout(30000) - await removeVideoFromPlaylist({ - url: servers[0].url, - token: servers[0].accessToken, - playlistId: playlistServer1Id, - playlistElementId: playlistElementServer1Video4 - }) - - await removeVideoFromPlaylist({ - url: servers[0].url, - token: servers[0].accessToken, - playlistId: playlistServer1Id, - playlistElementId: playlistElementNSFW - }) + await commands[0].removeElement({ playlistId: playlistServer1Id, elementId: playlistElementServer1Video4 }) + await commands[0].removeElement({ playlistId: playlistServer1Id, elementId: playlistElementNSFW }) await waitJobs(servers) for (const server of servers) { - const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) + const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 }) + expect(body.total).to.equal(6) - expect(res.body.total).to.equal(6) - - const elements: VideoPlaylistElement[] = res.body.data + const elements = body.data expect(elements).to.have.lengthOf(6) expect(elements[0].video.name).to.equal('video 0 server 1') @@ -1107,34 +1001,31 @@ describe('Test video playlists', function () { it('Should be able to create a public playlist, and set it to private', async function () { this.timeout(30000) - const res = await createVideoPlaylist({ - url: servers[0].url, - token: servers[0].accessToken, - playlistAttrs: { + const videoPlaylistIds = await commands[0].create({ + attributes: { displayName: 'my super public playlist', privacy: VideoPlaylistPrivacy.PUBLIC, - videoChannelId: servers[0].videoChannel.id + videoChannelId: servers[0].store.channel.id } }) - const videoPlaylistIds = res.body.videoPlaylist await waitJobs(servers) for (const server of servers) { - await getVideoPlaylist(server.url, videoPlaylistIds.uuid, HttpStatusCode.OK_200) + await server.playlists.get({ playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.OK_200 }) } - const playlistAttrs = { privacy: VideoPlaylistPrivacy.PRIVATE } - await updateVideoPlaylist({ url: servers[0].url, token: servers[0].accessToken, playlistId: videoPlaylistIds.id, playlistAttrs }) + const attributes = { privacy: VideoPlaylistPrivacy.PRIVATE } + await commands[0].update({ playlistId: videoPlaylistIds.id, attributes }) await waitJobs(servers) for (const server of [ servers[1], servers[2] ]) { - await getVideoPlaylist(server.url, videoPlaylistIds.uuid, HttpStatusCode.NOT_FOUND_404) + await server.playlists.get({ playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.NOT_FOUND_404 }) } - await getVideoPlaylist(servers[0].url, videoPlaylistIds.uuid, HttpStatusCode.UNAUTHORIZED_401) - await getVideoPlaylistWithToken(servers[0].url, servers[0].accessToken, videoPlaylistIds.uuid, HttpStatusCode.OK_200) + await commands[0].get({ playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) + await commands[0].get({ token: servers[0].accessToken, playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.OK_200 }) }) }) @@ -1143,12 +1034,12 @@ describe('Test video playlists', function () { it('Should delete the playlist on server 1 and delete on server 2 and 3', async function () { this.timeout(30000) - await deleteVideoPlaylist(servers[0].url, servers[0].accessToken, playlistServer1Id) + await commands[0].delete({ playlistId: playlistServer1Id }) await waitJobs(servers) for (const server of servers) { - await getVideoPlaylist(server.url, playlistServer1UUID, HttpStatusCode.NOT_FOUND_404) + await server.playlists.get({ playlistId: playlistServer1UUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 }) } }) @@ -1163,75 +1054,61 @@ describe('Test video playlists', function () { it('Should unfollow servers 1 and 2 and hide their playlists', async function () { this.timeout(30000) - const finder = data => data.find(p => p.displayName === 'my super playlist') + const finder = (data: VideoPlaylist[]) => data.find(p => p.displayName === 'my super playlist') { - const res = await getVideoPlaylistsList(servers[2].url, 0, 5) - expect(res.body.total).to.equal(3) - expect(finder(res.body.data)).to.not.be.undefined + const body = await servers[2].playlists.list({ start: 0, count: 5 }) + expect(body.total).to.equal(3) + + expect(finder(body.data)).to.not.be.undefined } - await unfollow(servers[2].url, servers[2].accessToken, servers[0]) + await servers[2].follows.unfollow({ target: servers[0] }) { - const res = await getVideoPlaylistsList(servers[2].url, 0, 5) - expect(res.body.total).to.equal(1) + const body = await servers[2].playlists.list({ start: 0, count: 5 }) + expect(body.total).to.equal(1) - expect(finder(res.body.data)).to.be.undefined + expect(finder(body.data)).to.be.undefined } }) it('Should delete a channel and put the associated playlist in private mode', async function () { this.timeout(30000) - const res = await addVideoChannel(servers[0].url, servers[0].accessToken, { name: 'super_channel', displayName: 'super channel' }) - const videoChannelId = res.body.videoChannel.id + const channel = await servers[0].channels.create({ attributes: { name: 'super_channel', displayName: 'super channel' } }) - const res2 = await createVideoPlaylist({ - url: servers[0].url, - token: servers[0].accessToken, - playlistAttrs: { + const playlistCreated = await commands[0].create({ + attributes: { displayName: 'channel playlist', privacy: VideoPlaylistPrivacy.PUBLIC, - videoChannelId + videoChannelId: channel.id } }) - const videoPlaylistUUID = res2.body.videoPlaylist.uuid await waitJobs(servers) - await deleteVideoChannel(servers[0].url, servers[0].accessToken, 'super_channel') + await servers[0].channels.delete({ channelName: 'super_channel' }) await waitJobs(servers) - const res3 = await getVideoPlaylistWithToken(servers[0].url, servers[0].accessToken, videoPlaylistUUID) - expect(res3.body.displayName).to.equal('channel playlist') - expect(res3.body.privacy.id).to.equal(VideoPlaylistPrivacy.PRIVATE) + const body = await commands[0].get({ token: servers[0].accessToken, playlistId: playlistCreated.uuid }) + expect(body.displayName).to.equal('channel playlist') + expect(body.privacy.id).to.equal(VideoPlaylistPrivacy.PRIVATE) - await getVideoPlaylist(servers[1].url, videoPlaylistUUID, HttpStatusCode.NOT_FOUND_404) + await servers[1].playlists.get({ playlistId: playlistCreated.uuid, expectedStatus: HttpStatusCode.NOT_FOUND_404 }) }) it('Should delete an account and delete its playlists', async function () { this.timeout(30000) - const user = { username: 'user_1', password: 'password' } - const res = await createUser({ - url: servers[0].url, - accessToken: servers[0].accessToken, - username: user.username, - password: user.password - }) - - const userId = res.body.user.id - const userAccessToken = await userLogin(servers[0], user) + const { userId, token } = await servers[0].users.generate('user_1') - const resChannel = await getMyUserInformation(servers[0].url, userAccessToken) - const userChannel = (resChannel.body as User).videoChannels[0] + const { videoChannels } = await servers[0].users.getMyInfo({ token }) + const userChannel = videoChannels[0] - await createVideoPlaylist({ - url: servers[0].url, - token: userAccessToken, - playlistAttrs: { + await commands[0].create({ + attributes: { displayName: 'playlist to be deleted', privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: userChannel.id @@ -1240,22 +1117,24 @@ describe('Test video playlists', function () { await waitJobs(servers) - const finder = data => data.find(p => p.displayName === 'playlist to be deleted') + const finder = (data: VideoPlaylist[]) => data.find(p => p.displayName === 'playlist to be deleted') { for (const server of [ servers[0], servers[1] ]) { - const res = await getVideoPlaylistsList(server.url, 0, 15) - expect(finder(res.body.data)).to.not.be.undefined + const body = await server.playlists.list({ start: 0, count: 15 }) + + expect(finder(body.data)).to.not.be.undefined } } - await removeUser(servers[0].url, userId, servers[0].accessToken) + await servers[0].users.remove({ userId }) await waitJobs(servers) { for (const server of [ servers[0], servers[1] ]) { - const res = await getVideoPlaylistsList(server.url, 0, 15) - expect(finder(res.body.data)).to.be.undefined + const body = await server.playlists.list({ start: 0, count: 15 }) + + expect(finder(body.data)).to.be.undefined } } }) diff --git a/server/tests/api/videos/video-privacy.ts b/server/tests/api/videos/video-privacy.ts index 950aeb7cf..b51b3bcdd 100644 --- a/server/tests/api/videos/video-privacy.ts +++ b/server/tests/api/videos/video-privacy.ts @@ -2,28 +2,13 @@ import 'mocha' import * as chai from 'chai' -import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' -import { Video, VideoCreateResult } from '@shared/models' -import { - cleanupTests, - flushAndRunServer, - getVideosList, - getVideosListWithToken, - ServerInfo, - setAccessTokensToServers, - uploadVideo -} from '../../../../shared/extra-utils/index' -import { doubleFollow } from '../../../../shared/extra-utils/server/follows' -import { waitJobs } from '../../../../shared/extra-utils/server/jobs' -import { userLogin } from '../../../../shared/extra-utils/users/login' -import { createUser } from '../../../../shared/extra-utils/users/users' -import { getMyVideos, getVideo, getVideoWithToken, updateVideo } from '../../../../shared/extra-utils/videos/videos' -import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum' +import { cleanupTests, createSingleServer, doubleFollow, PeerTubeServer, setAccessTokensToServers, waitJobs } from '@shared/extra-utils' +import { HttpStatusCode, VideoCreateResult, VideoPrivacy } from '@shared/models' const expect = chai.expect describe('Test video privacy', function () { - const servers: ServerInfo[] = [] + const servers: PeerTubeServer[] = [] let anotherUserToken: string let privateVideoId: number @@ -49,8 +34,8 @@ describe('Test video privacy', function () { this.timeout(50000) // Run servers - servers.push(await flushAndRunServer(1, dontFederateUnlistedConfig)) - servers.push(await flushAndRunServer(2)) + servers.push(await createSingleServer(1, dontFederateUnlistedConfig)) + servers.push(await createSingleServer(2)) // Get the access tokens await setAccessTokensToServers(servers) @@ -66,55 +51,53 @@ describe('Test video privacy', function () { for (const privacy of [ VideoPrivacy.PRIVATE, VideoPrivacy.INTERNAL ]) { const attributes = { privacy } - await uploadVideo(servers[0].url, servers[0].accessToken, attributes) + await servers[0].videos.upload({ attributes }) } await waitJobs(servers) }) it('Should not have these private and internal videos on server 2', async function () { - const res = await getVideosList(servers[1].url) + const { total, data } = await servers[1].videos.list() - expect(res.body.total).to.equal(0) - expect(res.body.data).to.have.lengthOf(0) + expect(total).to.equal(0) + expect(data).to.have.lengthOf(0) }) it('Should not list the private and internal videos for an unauthenticated user on server 1', async function () { - const res = await getVideosList(servers[0].url) + const { total, data } = await servers[0].videos.list() - expect(res.body.total).to.equal(0) - expect(res.body.data).to.have.lengthOf(0) + expect(total).to.equal(0) + expect(data).to.have.lengthOf(0) }) it('Should not list the private video and list the internal video for an authenticated user on server 1', async function () { - const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken) + const { total, data } = await servers[0].videos.listWithToken() - expect(res.body.total).to.equal(1) - expect(res.body.data).to.have.lengthOf(1) + expect(total).to.equal(1) + expect(data).to.have.lengthOf(1) - expect(res.body.data[0].privacy.id).to.equal(VideoPrivacy.INTERNAL) + expect(data[0].privacy.id).to.equal(VideoPrivacy.INTERNAL) }) it('Should list my (private and internal) videos', async function () { - const res = await getMyVideos(servers[0].url, servers[0].accessToken, 0, 10) + const { total, data } = await servers[0].videos.listMyVideos() - expect(res.body.total).to.equal(2) - expect(res.body.data).to.have.lengthOf(2) + expect(total).to.equal(2) + expect(data).to.have.lengthOf(2) - const videos: Video[] = res.body.data - - const privateVideo = videos.find(v => v.privacy.id === VideoPrivacy.PRIVATE) + const privateVideo = data.find(v => v.privacy.id === VideoPrivacy.PRIVATE) privateVideoId = privateVideo.id privateVideoUUID = privateVideo.uuid - const internalVideo = videos.find(v => v.privacy.id === VideoPrivacy.INTERNAL) + const internalVideo = data.find(v => v.privacy.id === VideoPrivacy.INTERNAL) internalVideoId = internalVideo.id internalVideoUUID = internalVideo.uuid }) it('Should not be able to watch the private/internal video with non authenticated user', async function () { - await getVideo(servers[0].url, privateVideoUUID, HttpStatusCode.UNAUTHORIZED_401) - await getVideo(servers[0].url, internalVideoUUID, HttpStatusCode.UNAUTHORIZED_401) + await servers[0].videos.get({ id: privateVideoUUID, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) + await servers[0].videos.get({ id: internalVideoUUID, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) }) it('Should not be able to watch the private video with another user', async function () { @@ -124,18 +107,23 @@ describe('Test video privacy', function () { username: 'hello', password: 'super password' } - await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: user.username, password: user.password }) + await servers[0].users.create({ username: user.username, password: user.password }) + + anotherUserToken = await servers[0].login.getAccessToken(user) - anotherUserToken = await userLogin(servers[0], user) - await getVideoWithToken(servers[0].url, anotherUserToken, privateVideoUUID, HttpStatusCode.FORBIDDEN_403) + await servers[0].videos.getWithToken({ + token: anotherUserToken, + id: privateVideoUUID, + expectedStatus: HttpStatusCode.FORBIDDEN_403 + }) }) it('Should be able to watch the internal video with another user', async function () { - await getVideoWithToken(servers[0].url, anotherUserToken, internalVideoUUID, HttpStatusCode.OK_200) + await servers[0].videos.getWithToken({ token: anotherUserToken, id: internalVideoUUID }) }) it('Should be able to watch the private video with the correct user', async function () { - await getVideoWithToken(servers[0].url, servers[0].accessToken, privateVideoUUID, HttpStatusCode.OK_200) + await servers[0].videos.getWithToken({ id: privateVideoUUID }) }) }) @@ -148,7 +136,7 @@ describe('Test video privacy', function () { name: 'unlisted video', privacy: VideoPrivacy.UNLISTED } - await uploadVideo(servers[1].url, servers[1].accessToken, attributes) + await servers[1].videos.upload({ attributes }) // Server 2 has transcoding enabled await waitJobs(servers) @@ -156,32 +144,32 @@ describe('Test video privacy', function () { it('Should not have this unlisted video listed on server 1 and 2', async function () { for (const server of servers) { - const res = await getVideosList(server.url) + const { total, data } = await server.videos.list() - expect(res.body.total).to.equal(0) - expect(res.body.data).to.have.lengthOf(0) + expect(total).to.equal(0) + expect(data).to.have.lengthOf(0) } }) it('Should list my (unlisted) videos', async function () { - const res = await getMyVideos(servers[1].url, servers[1].accessToken, 0, 1) + const { total, data } = await servers[1].videos.listMyVideos() - expect(res.body.total).to.equal(1) - expect(res.body.data).to.have.lengthOf(1) + expect(total).to.equal(1) + expect(data).to.have.lengthOf(1) - unlistedVideo = res.body.data[0] + unlistedVideo = data[0] }) it('Should not be able to get this unlisted video using its id', async function () { - await getVideo(servers[1].url, unlistedVideo.id, 404) + await servers[1].videos.get({ id: unlistedVideo.id, expectedStatus: HttpStatusCode.NOT_FOUND_404 }) }) it('Should be able to get this unlisted video using its uuid/shortUUID', async function () { for (const server of servers) { for (const id of [ unlistedVideo.uuid, unlistedVideo.shortUUID ]) { - const res = await getVideo(server.url, id) + const video = await server.videos.get({ id }) - expect(res.body.name).to.equal('unlisted video') + expect(video.name).to.equal('unlisted video') } } }) @@ -193,28 +181,28 @@ describe('Test video privacy', function () { name: 'unlisted video', privacy: VideoPrivacy.UNLISTED } - await uploadVideo(servers[0].url, servers[0].accessToken, attributes) + await servers[0].videos.upload({ attributes }) await waitJobs(servers) }) it('Should list my new unlisted video', async function () { - const res = await getMyVideos(servers[0].url, servers[0].accessToken, 0, 3) + const { total, data } = await servers[0].videos.listMyVideos() - expect(res.body.total).to.equal(3) - expect(res.body.data).to.have.lengthOf(3) + expect(total).to.equal(3) + expect(data).to.have.lengthOf(3) - nonFederatedUnlistedVideoUUID = res.body.data[0].uuid + nonFederatedUnlistedVideoUUID = data[0].uuid }) it('Should be able to get non-federated unlisted video from origin', async function () { - const res = await getVideo(servers[0].url, nonFederatedUnlistedVideoUUID) + const video = await servers[0].videos.get({ id: nonFederatedUnlistedVideoUUID }) - expect(res.body.name).to.equal('unlisted video') + expect(video.name).to.equal('unlisted video') }) it('Should not be able to get non-federated unlisted video from federated server', async function () { - await getVideo(servers[1].url, nonFederatedUnlistedVideoUUID, HttpStatusCode.NOT_FOUND_404) + await servers[1].videos.get({ id: nonFederatedUnlistedVideoUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 }) }) }) @@ -226,20 +214,20 @@ describe('Test video privacy', function () { now = Date.now() { - const attribute = { + const attributes = { name: 'private video becomes public', privacy: VideoPrivacy.PUBLIC } - await updateVideo(servers[0].url, servers[0].accessToken, privateVideoId, attribute) + await servers[0].videos.update({ id: privateVideoId, attributes }) } { - const attribute = { + const attributes = { name: 'internal video becomes public', privacy: VideoPrivacy.PUBLIC } - await updateVideo(servers[0].url, servers[0].accessToken, internalVideoId, attribute) + await servers[0].videos.update({ id: internalVideoId, attributes }) } await waitJobs(servers) @@ -247,13 +235,12 @@ describe('Test video privacy', function () { it('Should have this new public video listed on server 1 and 2', async function () { for (const server of servers) { - const res = await getVideosList(server.url) - expect(res.body.total).to.equal(2) - expect(res.body.data).to.have.lengthOf(2) + const { total, data } = await server.videos.list() + expect(total).to.equal(2) + expect(data).to.have.lengthOf(2) - const videos: Video[] = res.body.data - const privateVideo = videos.find(v => v.name === 'private video becomes public') - const internalVideo = videos.find(v => v.name === 'internal video becomes public') + const privateVideo = data.find(v => v.name === 'private video becomes public') + const internalVideo = data.find(v => v.name === 'internal video becomes public') expect(privateVideo).to.not.be.undefined expect(internalVideo).to.not.be.undefined @@ -270,27 +257,25 @@ describe('Test video privacy', function () { it('Should set these videos as private and internal', async function () { this.timeout(10000) - await updateVideo(servers[0].url, servers[0].accessToken, internalVideoId, { privacy: VideoPrivacy.PRIVATE }) - await updateVideo(servers[0].url, servers[0].accessToken, privateVideoId, { privacy: VideoPrivacy.INTERNAL }) + await servers[0].videos.update({ id: internalVideoId, attributes: { privacy: VideoPrivacy.PRIVATE } }) + await servers[0].videos.update({ id: privateVideoId, attributes: { privacy: VideoPrivacy.INTERNAL } }) await waitJobs(servers) for (const server of servers) { - const res = await getVideosList(server.url) + const { total, data } = await server.videos.list() - expect(res.body.total).to.equal(0) - expect(res.body.data).to.have.lengthOf(0) + expect(total).to.equal(0) + expect(data).to.have.lengthOf(0) } { - const res = await getMyVideos(servers[0].url, servers[0].accessToken, 0, 5) - const videos = res.body.data - - expect(res.body.total).to.equal(3) - expect(videos).to.have.lengthOf(3) + const { total, data } = await servers[0].videos.listMyVideos() + expect(total).to.equal(3) + expect(data).to.have.lengthOf(3) - const privateVideo = videos.find(v => v.name === 'private video becomes public') - const internalVideo = videos.find(v => v.name === 'internal video becomes public') + const privateVideo = data.find(v => v.name === 'private video becomes public') + const internalVideo = data.find(v => v.name === 'internal video becomes public') expect(privateVideo).to.not.be.undefined expect(internalVideo).to.not.be.undefined diff --git a/server/tests/api/videos/video-schedule-update.ts b/server/tests/api/videos/video-schedule-update.ts index 204f43611..3f7738784 100644 --- a/server/tests/api/videos/video-schedule-update.ts +++ b/server/tests/api/videos/video-schedule-update.ts @@ -1,22 +1,17 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ -import * as chai from 'chai' import 'mocha' -import { VideoPrivacy } from '../../../../shared/models/videos' +import * as chai from 'chai' import { cleanupTests, + createMultipleServers, doubleFollow, - flushAndRunMultipleServers, - getMyVideos, - getVideosList, - getVideoWithToken, - ServerInfo, + PeerTubeServer, setAccessTokensToServers, - updateVideo, - uploadVideo, - wait -} from '../../../../shared/extra-utils' -import { waitJobs } from '../../../../shared/extra-utils/server/jobs' + wait, + waitJobs +} from '@shared/extra-utils' +import { VideoPrivacy } from '@shared/models' const expect = chai.expect @@ -28,14 +23,14 @@ function in10Seconds () { } describe('Test video update scheduler', function () { - let servers: ServerInfo[] = [] + let servers: PeerTubeServer[] = [] let video2UUID: string before(async function () { this.timeout(30000) // Run servers - servers = await flushAndRunMultipleServers(2) + servers = await createMultipleServers(2) await setAccessTokensToServers(servers) @@ -45,35 +40,34 @@ describe('Test video update scheduler', function () { it('Should upload a video and schedule an update in 10 seconds', async function () { this.timeout(10000) - const videoAttributes = { + const attributes = { name: 'video 1', privacy: VideoPrivacy.PRIVATE, scheduleUpdate: { updateAt: in10Seconds().toISOString(), - privacy: VideoPrivacy.PUBLIC + privacy: VideoPrivacy.PUBLIC as VideoPrivacy.PUBLIC } } - await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes) + await servers[0].videos.upload({ attributes }) await waitJobs(servers) }) it('Should not list the video (in privacy mode)', async function () { for (const server of servers) { - const res = await getVideosList(server.url) + const { total } = await server.videos.list() - expect(res.body.total).to.equal(0) + expect(total).to.equal(0) } }) it('Should have my scheduled video in my account videos', async function () { - const res = await getMyVideos(servers[0].url, servers[0].accessToken, 0, 5) - expect(res.body.total).to.equal(1) + const { total, data } = await servers[0].videos.listMyVideos() + expect(total).to.equal(1) - const videoFromList = res.body.data[0] - const res2 = await getVideoWithToken(servers[0].url, servers[0].accessToken, videoFromList.uuid) - const videoFromGet = res2.body + const videoFromList = data[0] + const videoFromGet = await servers[0].videos.getWithToken({ id: videoFromList.uuid }) for (const video of [ videoFromList, videoFromGet ]) { expect(video.name).to.equal('video 1') @@ -90,23 +84,23 @@ describe('Test video update scheduler', function () { await waitJobs(servers) for (const server of servers) { - const res = await getVideosList(server.url) + const { total, data } = await server.videos.list() - expect(res.body.total).to.equal(1) - expect(res.body.data[0].name).to.equal('video 1') + expect(total).to.equal(1) + expect(data[0].name).to.equal('video 1') } }) it('Should upload a video without scheduling an update', async function () { this.timeout(10000) - const videoAttributes = { + const attributes = { name: 'video 2', privacy: VideoPrivacy.PRIVATE } - const res = await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes) - video2UUID = res.body.video.uuid + const { uuid } = await servers[0].videos.upload({ attributes }) + video2UUID = uuid await waitJobs(servers) }) @@ -114,31 +108,31 @@ describe('Test video update scheduler', function () { it('Should update a video by scheduling an update', async function () { this.timeout(10000) - const videoAttributes = { + const attributes = { name: 'video 2 updated', scheduleUpdate: { updateAt: in10Seconds().toISOString(), - privacy: VideoPrivacy.PUBLIC + privacy: VideoPrivacy.PUBLIC as VideoPrivacy.PUBLIC } } - await updateVideo(servers[0].url, servers[0].accessToken, video2UUID, videoAttributes) + await servers[0].videos.update({ id: video2UUID, attributes }) await waitJobs(servers) }) it('Should not display the updated video', async function () { for (const server of servers) { - const res = await getVideosList(server.url) + const { total } = await server.videos.list() - expect(res.body.total).to.equal(1) + expect(total).to.equal(1) } }) it('Should have my scheduled updated video in my account videos', async function () { - const res = await getMyVideos(servers[0].url, servers[0].accessToken, 0, 5) - expect(res.body.total).to.equal(2) + const { total, data } = await servers[0].videos.listMyVideos() + expect(total).to.equal(2) - const video = res.body.data.find(v => v.uuid === video2UUID) + const video = data.find(v => v.uuid === video2UUID) expect(video).not.to.be.undefined expect(video.name).to.equal('video 2 updated') @@ -155,11 +149,10 @@ describe('Test video update scheduler', function () { await waitJobs(servers) for (const server of servers) { - const res = await getVideosList(server.url) - - expect(res.body.total).to.equal(2) + const { total, data } = await server.videos.list() + expect(total).to.equal(2) - const video = res.body.data.find(v => v.uuid === video2UUID) + const video = data.find(v => v.uuid === video2UUID) expect(video).not.to.be.undefined expect(video.name).to.equal('video 2 updated') } diff --git a/server/tests/api/videos/video-transcoder.ts b/server/tests/api/videos/video-transcoder.ts index ea5ffd239..2a09e95bf 100644 --- a/server/tests/api/videos/video-transcoder.ts +++ b/server/tests/api/videos/video-transcoder.ts @@ -2,36 +2,23 @@ import 'mocha' import * as chai from 'chai' -import { FfprobeData } from 'fluent-ffmpeg' import { omit } from 'lodash' -import { join } from 'path' -import { Job } from '@shared/models' -import { VIDEO_TRANSCODING_FPS } from '../../../../server/initializers/constants' -import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' import { buildAbsoluteFixturePath, - buildServerDirectory, cleanupTests, + createMultipleServers, doubleFollow, - flushAndRunMultipleServers, generateHighBitrateVideo, generateVideoWithFramerate, - getJobsListPaginationAndSort, - getMyVideos, - getServerFileSize, - getVideo, - getVideoFileMetadataUrl, - getVideosList, + getFileSize, makeGetRequest, - ServerInfo, + PeerTubeServer, setAccessTokensToServers, - updateCustomSubConfig, - uploadVideo, - uploadVideoAndGetId, waitJobs, webtorrentAdd -} from '../../../../shared/extra-utils' -import { getMaxBitrate, VideoDetails, VideoResolution, VideoState } from '../../../../shared/models/videos' +} from '@shared/extra-utils' +import { getMaxBitrate, HttpStatusCode, VideoResolution, VideoState } from '@shared/models' +import { VIDEO_TRANSCODING_FPS } from '../../../../server/initializers/constants' import { canDoQuickTranscode, getAudioStream, @@ -43,37 +30,39 @@ import { const expect = chai.expect -function updateConfigForTranscoding (server: ServerInfo) { - return updateCustomSubConfig(server.url, server.accessToken, { - transcoding: { - enabled: true, - allowAdditionalExtensions: true, - allowAudioFiles: true, - hls: { enabled: true }, - webtorrent: { enabled: true }, - resolutions: { - '0p': false, - '240p': true, - '360p': true, - '480p': true, - '720p': true, - '1080p': true, - '1440p': true, - '2160p': true +function updateConfigForTranscoding (server: PeerTubeServer) { + return server.config.updateCustomSubConfig({ + newConfig: { + transcoding: { + enabled: true, + allowAdditionalExtensions: true, + allowAudioFiles: true, + hls: { enabled: true }, + webtorrent: { enabled: true }, + resolutions: { + '0p': false, + '240p': true, + '360p': true, + '480p': true, + '720p': true, + '1080p': true, + '1440p': true, + '2160p': true + } } } }) } describe('Test video transcoding', function () { - let servers: ServerInfo[] = [] + let servers: PeerTubeServer[] = [] let video4k: string before(async function () { this.timeout(30_000) // Run servers - servers = await flushAndRunMultipleServers(2) + servers = await createMultipleServers(2) await setAccessTokensToServers(servers) @@ -87,21 +76,20 @@ describe('Test video transcoding', function () { it('Should not transcode video on server 1', async function () { this.timeout(60_000) - const videoAttributes = { + const attributes = { name: 'my super name for server 1', description: 'my super description for server 1', fixture: 'video_short.webm' } - await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes) + await servers[0].videos.upload({ attributes }) await waitJobs(servers) for (const server of servers) { - const res = await getVideosList(server.url) - const video = res.body.data[0] + const { data } = await server.videos.list() + const video = data[0] - const res2 = await getVideo(server.url, video.id) - const videoDetails = res2.body + const videoDetails = await server.videos.get({ id: video.id }) expect(videoDetails.files).to.have.lengthOf(1) const magnetUri = videoDetails.files[0].magnetUri @@ -117,21 +105,20 @@ describe('Test video transcoding', function () { it('Should transcode video on server 2', async function () { this.timeout(120_000) - const videoAttributes = { + const attributes = { name: 'my super name for server 2', description: 'my super description for server 2', fixture: 'video_short.webm' } - await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes) + await servers[1].videos.upload({ attributes }) await waitJobs(servers) for (const server of servers) { - const res = await getVideosList(server.url) + const { data } = await server.videos.list() - const video = res.body.data.find(v => v.name === videoAttributes.name) - const res2 = await getVideo(server.url, video.id) - const videoDetails = res2.body + const video = data.find(v => v.name === attributes.name) + const videoDetails = await server.videos.get({ id: video.id }) expect(videoDetails.files).to.have.lengthOf(4) @@ -150,47 +137,50 @@ describe('Test video transcoding', function () { { // Upload the video, but wait transcoding - const videoAttributes = { + const attributes = { name: 'waiting video', fixture: 'video_short1.webm', waitTranscoding: true } - const resVideo = await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes) - const videoId = resVideo.body.video.uuid + const { uuid } = await servers[1].videos.upload({ attributes }) + const videoId = uuid // Should be in transcode state - const { body } = await getVideo(servers[1].url, videoId) + const body = await servers[1].videos.get({ id: videoId }) expect(body.name).to.equal('waiting video') expect(body.state.id).to.equal(VideoState.TO_TRANSCODE) expect(body.state.label).to.equal('To transcode') expect(body.waitTranscoding).to.be.true - // Should have my video - const resMyVideos = await getMyVideos(servers[1].url, servers[1].accessToken, 0, 10) - const videoToFindInMine = resMyVideos.body.data.find(v => v.name === videoAttributes.name) - expect(videoToFindInMine).not.to.be.undefined - expect(videoToFindInMine.state.id).to.equal(VideoState.TO_TRANSCODE) - expect(videoToFindInMine.state.label).to.equal('To transcode') - expect(videoToFindInMine.waitTranscoding).to.be.true + { + // Should have my video + const { data } = await servers[1].videos.listMyVideos() + const videoToFindInMine = data.find(v => v.name === attributes.name) + expect(videoToFindInMine).not.to.be.undefined + expect(videoToFindInMine.state.id).to.equal(VideoState.TO_TRANSCODE) + expect(videoToFindInMine.state.label).to.equal('To transcode') + expect(videoToFindInMine.waitTranscoding).to.be.true + } - // Should not list this video - const resVideos = await getVideosList(servers[1].url) - const videoToFindInList = resVideos.body.data.find(v => v.name === videoAttributes.name) - expect(videoToFindInList).to.be.undefined + { + // Should not list this video + const { data } = await servers[1].videos.list() + const videoToFindInList = data.find(v => v.name === attributes.name) + expect(videoToFindInList).to.be.undefined + } // Server 1 should not have the video yet - await getVideo(servers[0].url, videoId, HttpStatusCode.NOT_FOUND_404) + await servers[0].videos.get({ id: videoId, expectedStatus: HttpStatusCode.NOT_FOUND_404 }) } await waitJobs(servers) for (const server of servers) { - const res = await getVideosList(server.url) - const videoToFind = res.body.data.find(v => v.name === 'waiting video') + const { data } = await server.videos.list() + const videoToFind = data.find(v => v.name === 'waiting video') expect(videoToFind).not.to.be.undefined - const res2 = await getVideo(server.url, videoToFind.id) - const videoDetails: VideoDetails = res2.body + const videoDetails = await server.videos.get({ id: videoToFind.id }) expect(videoDetails.state.id).to.equal(VideoState.PUBLISHED) expect(videoDetails.state.label).to.equal('Published') @@ -211,22 +201,20 @@ describe('Test video transcoding', function () { } for (const fixture of [ 'video_short.mkv', 'video_short.avi' ]) { - const videoAttributes = { + const attributes = { name: fixture, fixture } - await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes) + await servers[1].videos.upload({ attributes }) await waitJobs(servers) for (const server of servers) { - const res = await getVideosList(server.url) - - const video = res.body.data.find(v => v.name === videoAttributes.name) - const res2 = await getVideo(server.url, video.id) - const videoDetails = res2.body + const { data } = await server.videos.list() + const video = data.find(v => v.name === attributes.name) + const videoDetails = await server.videos.get({ id: video.id }) expect(videoDetails.files).to.have.lengthOf(4) const magnetUri = videoDetails.files[0].magnetUri @@ -238,22 +226,20 @@ describe('Test video transcoding', function () { it('Should transcode a 4k video', async function () { this.timeout(200_000) - const videoAttributes = { + const attributes = { name: '4k video', fixture: 'video_short_4k.mp4' } - const resUpload = await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes) - video4k = resUpload.body.video.uuid + const { uuid } = await servers[1].videos.upload({ attributes }) + video4k = uuid await waitJobs(servers) const resolutions = [ 240, 360, 480, 720, 1080, 1440, 2160 ] for (const server of servers) { - const res = await getVideo(server.url, video4k) - const videoDetails: VideoDetails = res.body - + const videoDetails = await server.videos.get({ id: video4k }) expect(videoDetails.files).to.have.lengthOf(resolutions.length) for (const r of resolutions) { @@ -269,24 +255,24 @@ describe('Test video transcoding', function () { it('Should transcode high bit rate mp3 to proper bit rate', async function () { this.timeout(60_000) - const videoAttributes = { + const attributes = { name: 'mp3_256k', fixture: 'video_short_mp3_256k.mp4' } - await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes) + await servers[1].videos.upload({ attributes }) await waitJobs(servers) for (const server of servers) { - const res = await getVideosList(server.url) + const { data } = await server.videos.list() - const video = res.body.data.find(v => v.name === videoAttributes.name) - const res2 = await getVideo(server.url, video.id) - const videoDetails: VideoDetails = res2.body + const video = data.find(v => v.name === attributes.name) + const videoDetails = await server.videos.get({ id: video.id }) expect(videoDetails.files).to.have.lengthOf(4) - const path = buildServerDirectory(servers[1], join('videos', video.uuid + '-240.mp4')) + const file = videoDetails.files.find(f => f.resolution.id === 240) + const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl) const probe = await getAudioStream(path) if (probe.audioStream) { @@ -301,23 +287,23 @@ describe('Test video transcoding', function () { it('Should transcode video with no audio and have no audio itself', async function () { this.timeout(60_000) - const videoAttributes = { + const attributes = { name: 'no_audio', fixture: 'video_short_no_audio.mp4' } - await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes) + await servers[1].videos.upload({ attributes }) await waitJobs(servers) for (const server of servers) { - const res = await getVideosList(server.url) + const { data } = await server.videos.list() - const video = res.body.data.find(v => v.name === videoAttributes.name) - const res2 = await getVideo(server.url, video.id) - const videoDetails: VideoDetails = res2.body + const video = data.find(v => v.name === attributes.name) + const videoDetails = await server.videos.get({ id: video.id }) + + const file = videoDetails.files.find(f => f.resolution.id === 240) + const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl) - expect(videoDetails.files).to.have.lengthOf(4) - const path = buildServerDirectory(servers[1], join('videos', video.uuid + '-240.mp4')) const probe = await getAudioStream(path) expect(probe).to.not.have.property('audioStream') } @@ -326,26 +312,27 @@ describe('Test video transcoding', function () { it('Should leave the audio untouched, but properly transcode the video', async function () { this.timeout(60_000) - const videoAttributes = { + const attributes = { name: 'untouched_audio', fixture: 'video_short.mp4' } - await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes) + await servers[1].videos.upload({ attributes }) await waitJobs(servers) for (const server of servers) { - const res = await getVideosList(server.url) + const { data } = await server.videos.list() - const video = res.body.data.find(v => v.name === videoAttributes.name) - const res2 = await getVideo(server.url, video.id) - const videoDetails: VideoDetails = res2.body + const video = data.find(v => v.name === attributes.name) + const videoDetails = await server.videos.get({ id: video.id }) expect(videoDetails.files).to.have.lengthOf(4) - const fixturePath = buildAbsoluteFixturePath(videoAttributes.fixture) + const fixturePath = buildAbsoluteFixturePath(attributes.fixture) const fixtureVideoProbe = await getAudioStream(fixturePath) - const path = buildServerDirectory(servers[1], join('videos', video.uuid + '-240.mp4')) + + const file = videoDetails.files.find(f => f.resolution.id === 240) + const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl) const videoProbe = await getAudioStream(path) @@ -364,19 +351,21 @@ describe('Test video transcoding', function () { function runSuite (mode: 'legacy' | 'resumable') { before(async function () { - await updateCustomSubConfig(servers[1].url, servers[1].accessToken, { - transcoding: { - hls: { enabled: true }, - webtorrent: { enabled: true }, - resolutions: { - '0p': false, - '240p': false, - '360p': false, - '480p': false, - '720p': false, - '1080p': false, - '1440p': false, - '2160p': false + await servers[1].config.updateCustomSubConfig({ + newConfig: { + transcoding: { + hls: { enabled: true }, + webtorrent: { enabled: true }, + resolutions: { + '0p': false, + '240p': false, + '360p': false, + '480p': false, + '720p': false, + '1080p': false, + '1440p': false, + '2160p': false + } } } }) @@ -385,22 +374,21 @@ describe('Test video transcoding', function () { it('Should merge an audio file with the preview file', async function () { this.timeout(60_000) - const videoAttributesArg = { name: 'audio_with_preview', previewfile: 'preview.jpg', fixture: 'sample.ogg' } - await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributesArg, HttpStatusCode.OK_200, mode) + const attributes = { name: 'audio_with_preview', previewfile: 'preview.jpg', fixture: 'sample.ogg' } + await servers[1].videos.upload({ attributes, mode }) await waitJobs(servers) for (const server of servers) { - const res = await getVideosList(server.url) + const { data } = await server.videos.list() - const video = res.body.data.find(v => v.name === 'audio_with_preview') - const res2 = await getVideo(server.url, video.id) - const videoDetails: VideoDetails = res2.body + const video = data.find(v => v.name === 'audio_with_preview') + const videoDetails = await server.videos.get({ id: video.id }) expect(videoDetails.files).to.have.lengthOf(1) - await makeGetRequest({ url: server.url, path: videoDetails.thumbnailPath, statusCodeExpected: HttpStatusCode.OK_200 }) - await makeGetRequest({ url: server.url, path: videoDetails.previewPath, statusCodeExpected: HttpStatusCode.OK_200 }) + await makeGetRequest({ url: server.url, path: videoDetails.thumbnailPath, expectedStatus: HttpStatusCode.OK_200 }) + await makeGetRequest({ url: server.url, path: videoDetails.previewPath, expectedStatus: HttpStatusCode.OK_200 }) const magnetUri = videoDetails.files[0].magnetUri expect(magnetUri).to.contain('.mp4') @@ -410,22 +398,21 @@ describe('Test video transcoding', function () { it('Should upload an audio file and choose a default background image', async function () { this.timeout(60_000) - const videoAttributesArg = { name: 'audio_without_preview', fixture: 'sample.ogg' } - await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributesArg, HttpStatusCode.OK_200, mode) + const attributes = { name: 'audio_without_preview', fixture: 'sample.ogg' } + await servers[1].videos.upload({ attributes, mode }) await waitJobs(servers) for (const server of servers) { - const res = await getVideosList(server.url) + const { data } = await server.videos.list() - const video = res.body.data.find(v => v.name === 'audio_without_preview') - const res2 = await getVideo(server.url, video.id) - const videoDetails = res2.body + const video = data.find(v => v.name === 'audio_without_preview') + const videoDetails = await server.videos.get({ id: video.id }) expect(videoDetails.files).to.have.lengthOf(1) - await makeGetRequest({ url: server.url, path: videoDetails.thumbnailPath, statusCodeExpected: HttpStatusCode.OK_200 }) - await makeGetRequest({ url: server.url, path: videoDetails.previewPath, statusCodeExpected: HttpStatusCode.OK_200 }) + await makeGetRequest({ url: server.url, path: videoDetails.thumbnailPath, expectedStatus: HttpStatusCode.OK_200 }) + await makeGetRequest({ url: server.url, path: videoDetails.previewPath, expectedStatus: HttpStatusCode.OK_200 }) const magnetUri = videoDetails.files[0].magnetUri expect(magnetUri).to.contain('.mp4') @@ -435,26 +422,27 @@ describe('Test video transcoding', function () { it('Should upload an audio file and create an audio version only', async function () { this.timeout(60_000) - await updateCustomSubConfig(servers[1].url, servers[1].accessToken, { - transcoding: { - hls: { enabled: true }, - webtorrent: { enabled: true }, - resolutions: { - '0p': true, - '240p': false, - '360p': false + await servers[1].config.updateCustomSubConfig({ + newConfig: { + transcoding: { + hls: { enabled: true }, + webtorrent: { enabled: true }, + resolutions: { + '0p': true, + '240p': false, + '360p': false + } } } }) - const videoAttributesArg = { name: 'audio_with_preview', previewfile: 'preview.jpg', fixture: 'sample.ogg' } - const resVideo = await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributesArg, HttpStatusCode.OK_200, mode) + const attributes = { name: 'audio_with_preview', previewfile: 'preview.jpg', fixture: 'sample.ogg' } + const { id } = await servers[1].videos.upload({ attributes, mode }) await waitJobs(servers) for (const server of servers) { - const res2 = await getVideo(server.url, resVideo.body.video.id) - const videoDetails: VideoDetails = res2.body + const videoDetails = await server.videos.get({ id }) for (const files of [ videoDetails.files, videoDetails.streamingPlaylists[0].files ]) { expect(files).to.have.lengthOf(2) @@ -480,21 +468,20 @@ describe('Test video transcoding', function () { it('Should transcode a 60 FPS video', async function () { this.timeout(60_000) - const videoAttributes = { + const attributes = { name: 'my super 30fps name for server 2', description: 'my super 30fps description for server 2', fixture: '60fps_720p_small.mp4' } - await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes) + await servers[1].videos.upload({ attributes }) await waitJobs(servers) for (const server of servers) { - const res = await getVideosList(server.url) + const { data } = await server.videos.list() - const video = res.body.data.find(v => v.name === videoAttributes.name) - const res2 = await getVideo(server.url, video.id) - const videoDetails: VideoDetails = res2.body + const video = data.find(v => v.name === attributes.name) + const videoDetails = await server.videos.get({ id: video.id }) expect(videoDetails.files).to.have.lengthOf(4) expect(videoDetails.files[0].fps).to.be.above(58).and.below(62) @@ -502,14 +489,16 @@ describe('Test video transcoding', function () { expect(videoDetails.files[2].fps).to.be.below(31) expect(videoDetails.files[3].fps).to.be.below(31) - for (const resolution of [ '240', '360', '480' ]) { - const path = buildServerDirectory(servers[1], join('videos', video.uuid + '-' + resolution + '.mp4')) + for (const resolution of [ 240, 360, 480 ]) { + const file = videoDetails.files.find(f => f.resolution.id === resolution) + const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl) const fps = await getVideoFileFPS(path) expect(fps).to.be.below(31) } - const path = buildServerDirectory(servers[1], join('videos', video.uuid + '-720.mp4')) + const file = videoDetails.files.find(f => f.resolution.id === 720) + const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl) const fps = await getVideoFileFPS(path) expect(fps).to.be.above(58).and.below(62) @@ -528,29 +517,32 @@ describe('Test video transcoding', function () { expect(fps).to.be.equal(59) } - const videoAttributes = { + const attributes = { name: '59fps video', description: '59fps video', fixture: tempFixturePath } - await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes) + await servers[1].videos.upload({ attributes }) await waitJobs(servers) for (const server of servers) { - const res = await getVideosList(server.url) + const { data } = await server.videos.list() - const video = res.body.data.find(v => v.name === videoAttributes.name) + const { id } = data.find(v => v.name === attributes.name) + const video = await server.videos.get({ id }) { - const path = buildServerDirectory(servers[1], join('videos', video.uuid + '-240.mp4')) + const file = video.files.find(f => f.resolution.id === 240) + const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl) const fps = await getVideoFileFPS(path) expect(fps).to.be.equal(25) } { - const path = buildServerDirectory(servers[1], join('videos', video.uuid + '-720.mp4')) + const file = video.files.find(f => f.resolution.id === 720) + const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl) const fps = await getVideoFileFPS(path) expect(fps).to.be.equal(59) } @@ -559,6 +551,7 @@ describe('Test video transcoding', function () { }) describe('Bitrate control', function () { + it('Should respect maximum bitrate values', async function () { this.timeout(160_000) @@ -571,30 +564,32 @@ describe('Test video transcoding', function () { expect(bitrate).to.be.above(getMaxBitrate(VideoResolution.H_1080P, 25, VIDEO_TRANSCODING_FPS)) } - const videoAttributes = { + const attributes = { name: 'high bitrate video', description: 'high bitrate video', fixture: tempFixturePath } - await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes) + await servers[1].videos.upload({ attributes }) await waitJobs(servers) for (const server of servers) { - const res = await getVideosList(server.url) + const { data } = await server.videos.list() - const video = res.body.data.find(v => v.name === videoAttributes.name) + const { id } = data.find(v => v.name === attributes.name) + const video = await server.videos.get({ id }) - for (const resolution of [ '240', '360', '480', '720', '1080' ]) { - const path = buildServerDirectory(servers[1], join('videos', video.uuid + '-' + resolution + '.mp4')) + for (const resolution of [ 240, 360, 480, 720, 1080 ]) { + const file = video.files.find(f => f.resolution.id === resolution) + const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl) const bitrate = await getVideoFileBitrate(path) const fps = await getVideoFileFPS(path) - const resolution2 = await getVideoFileResolution(path) + const { videoFileResolution } = await getVideoFileResolution(path) - expect(resolution2.videoFileResolution.toString()).to.equal(resolution) - expect(bitrate).to.be.below(getMaxBitrate(resolution2.videoFileResolution, fps, VIDEO_TRANSCODING_FPS)) + expect(videoFileResolution).to.equal(resolution) + expect(bitrate).to.be.below(getMaxBitrate(videoFileResolution, fps, VIDEO_TRANSCODING_FPS)) } } }) @@ -602,7 +597,7 @@ describe('Test video transcoding', function () { it('Should not transcode to an higher bitrate than the original file', async function () { this.timeout(160_000) - const config = { + const newConfig = { transcoding: { enabled: true, resolutions: { @@ -618,22 +613,25 @@ describe('Test video transcoding', function () { hls: { enabled: true } } } - await updateCustomSubConfig(servers[1].url, servers[1].accessToken, config) + await servers[1].config.updateCustomSubConfig({ newConfig }) - const videoAttributes = { + const attributes = { name: 'low bitrate', fixture: 'low-bitrate.mp4' } - const resUpload = await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes) - const videoUUID = resUpload.body.video.uuid + const { id } = await servers[1].videos.upload({ attributes }) await waitJobs(servers) + const video = await servers[1].videos.get({ id }) + const resolutions = [ 240, 360, 480, 720, 1080 ] for (const r of resolutions) { - const path = `videos/${videoUUID}-${r}.mp4` - const size = await getServerFileSize(servers[1], path) + const file = video.files.find(f => f.resolution.id === r) + + const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl) + const size = await getFileSize(path) expect(size, `${path} not below ${60_000}`).to.be.below(60_000) } }) @@ -644,11 +642,13 @@ describe('Test video transcoding', function () { it('Should provide valid ffprobe data', async function () { this.timeout(160_000) - const videoUUID = (await uploadVideoAndGetId({ server: servers[1], videoName: 'ffprobe data' })).uuid + const videoUUID = (await servers[1].videos.quickUpload({ name: 'ffprobe data' })).uuid await waitJobs(servers) { - const path = buildServerDirectory(servers[1], join('videos', videoUUID + '-240.mp4')) + const video = await servers[1].videos.get({ id: videoUUID }) + const file = video.files.find(f => f.resolution.id === 240) + const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl) const metadata = await getMetadataFromFile(path) // expected format properties @@ -678,8 +678,7 @@ describe('Test video transcoding', function () { } for (const server of servers) { - const res2 = await getVideo(server.url, videoUUID) - const videoDetails: VideoDetails = res2.body + const videoDetails = await server.videos.get({ id: videoUUID }) const videoFiles = videoDetails.files .concat(videoDetails.streamingPlaylists[0].files) @@ -691,8 +690,7 @@ describe('Test video transcoding', function () { expect(file.metadataUrl).to.contain(servers[1].url) expect(file.metadataUrl).to.contain(videoUUID) - const res3 = await getVideoFileMetadataUrl(file.metadataUrl) - const metadata: FfprobeData = res3.body + const metadata = await server.videos.getFileMetadata({ url: file.metadataUrl }) expect(metadata).to.have.nested.property('format.size') } } @@ -709,17 +707,14 @@ describe('Test video transcoding', function () { describe('Transcoding job queue', function () { it('Should have the appropriate priorities for transcoding jobs', async function () { - const res = await getJobsListPaginationAndSort({ - url: servers[1].url, - accessToken: servers[1].accessToken, + const body = await servers[1].jobs.getJobsList({ start: 0, count: 100, sort: '-createdAt', jobType: 'video-transcoding' }) - const jobs = res.body.data as Job[] - + const jobs = body.data const transcodingJobs = jobs.filter(j => j.data.videoUUID === video4k) expect(transcodingJobs).to.have.lengthOf(14) diff --git a/server/tests/api/videos/videos-filter.ts b/server/tests/api/videos/videos-filter.ts index 7428b82c5..2306807bf 100644 --- a/server/tests/api/videos/videos-filter.ts +++ b/server/tests/api/videos/videos-filter.ts @@ -1,25 +1,18 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ -import * as chai from 'chai' import 'mocha' +import { expect } from 'chai' import { cleanupTests, - createUser, + createMultipleServers, doubleFollow, - flushAndRunMultipleServers, makeGetRequest, - ServerInfo, - setAccessTokensToServers, - uploadVideo, - userLogin -} from '../../../../shared/extra-utils' -import { Video, VideoPrivacy } from '../../../../shared/models/videos' -import { UserRole } from '../../../../shared/models/users' -import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' - -const expect = chai.expect - -async function getVideosNames (server: ServerInfo, token: string, filter: string, statusCodeExpected = HttpStatusCode.OK_200) { + PeerTubeServer, + setAccessTokensToServers +} from '@shared/extra-utils' +import { HttpStatusCode, UserRole, Video, VideoPrivacy } from '@shared/models' + +async function getVideosNames (server: PeerTubeServer, token: string, filter: string, expectedStatus = HttpStatusCode.OK_200) { const paths = [ '/api/v1/video-channels/root_channel/videos', '/api/v1/accounts/root/videos', @@ -38,7 +31,7 @@ async function getVideosNames (server: ServerInfo, token: string, filter: string sort: 'createdAt', filter }, - statusCodeExpected + expectedStatus }) videosResults.push(res.body.data.map(v => v.name)) @@ -48,42 +41,32 @@ async function getVideosNames (server: ServerInfo, token: string, filter: string } describe('Test videos filter', function () { - let servers: ServerInfo[] + let servers: PeerTubeServer[] // --------------------------------------------------------------- before(async function () { this.timeout(160000) - servers = await flushAndRunMultipleServers(2) + servers = await createMultipleServers(2) await setAccessTokensToServers(servers) for (const server of servers) { const moderator = { username: 'moderator', password: 'my super password' } - await createUser( - { - url: server.url, - accessToken: server.accessToken, - username: moderator.username, - password: moderator.password, - videoQuota: undefined, - videoQuotaDaily: undefined, - role: UserRole.MODERATOR - } - ) - server['moderatorAccessToken'] = await userLogin(server, moderator) + await server.users.create({ username: moderator.username, password: moderator.password, role: UserRole.MODERATOR }) + server['moderatorAccessToken'] = await server.login.getAccessToken(moderator) - await uploadVideo(server.url, server.accessToken, { name: 'public ' + server.serverNumber }) + await server.videos.upload({ attributes: { name: 'public ' + server.serverNumber } }) { const attributes = { name: 'unlisted ' + server.serverNumber, privacy: VideoPrivacy.UNLISTED } - await uploadVideo(server.url, server.accessToken, attributes) + await server.videos.upload({ attributes }) } { const attributes = { name: 'private ' + server.serverNumber, privacy: VideoPrivacy.PRIVATE } - await uploadVideo(server.url, server.accessToken, attributes) + await server.videos.upload({ attributes }) } } diff --git a/server/tests/api/videos/videos-history.ts b/server/tests/api/videos/videos-history.ts index b25cff879..e4bc0bb3a 100644 --- a/server/tests/api/videos/videos-history.ts +++ b/server/tests/api/videos/videos-history.ts @@ -1,74 +1,66 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ -import * as chai from 'chai' import 'mocha' +import * as chai from 'chai' import { cleanupTests, - createUser, - flushAndRunServer, - getVideosListWithToken, - getVideoWithToken, + createSingleServer, + HistoryCommand, killallServers, - reRunServer, - searchVideoWithToken, - ServerInfo, + PeerTubeServer, setAccessTokensToServers, - updateMyUser, - uploadVideo, - userLogin, wait -} from '../../../../shared/extra-utils' -import { Video, VideoDetails } from '../../../../shared/models/videos' -import { listMyVideosHistory, removeMyVideosHistory, userWatchVideo } from '../../../../shared/extra-utils/videos/video-history' -import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' +} from '@shared/extra-utils' +import { HttpStatusCode, Video } from '@shared/models' const expect = chai.expect describe('Test videos history', function () { - let server: ServerInfo = null + let server: PeerTubeServer = null let video1UUID: string let video2UUID: string let video3UUID: string let video3WatchedDate: Date let userAccessToken: string + let command: HistoryCommand before(async function () { this.timeout(30000) - server = await flushAndRunServer(1) + server = await createSingleServer(1) await setAccessTokensToServers([ server ]) + command = server.history + { - const res = await uploadVideo(server.url, server.accessToken, { name: 'video 1' }) - video1UUID = res.body.video.uuid + const { uuid } = await server.videos.upload({ attributes: { name: 'video 1' } }) + video1UUID = uuid } { - const res = await uploadVideo(server.url, server.accessToken, { name: 'video 2' }) - video2UUID = res.body.video.uuid + const { uuid } = await server.videos.upload({ attributes: { name: 'video 2' } }) + video2UUID = uuid } { - const res = await uploadVideo(server.url, server.accessToken, { name: 'video 3' }) - video3UUID = res.body.video.uuid + const { uuid } = await server.videos.upload({ attributes: { name: 'video 3' } }) + video3UUID = uuid } const user = { username: 'user_1', password: 'super password' } - await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password }) - userAccessToken = await userLogin(server, user) + await server.users.create({ username: user.username, password: user.password }) + userAccessToken = await server.login.getAccessToken(user) }) it('Should get videos, without watching history', async function () { - const res = await getVideosListWithToken(server.url, server.accessToken) - const videos: Video[] = res.body.data + const { data } = await server.videos.listWithToken() - for (const video of videos) { - const resDetail = await getVideoWithToken(server.url, server.accessToken, video.id) - const videoDetails: VideoDetails = resDetail.body + for (const video of data) { + const videoDetails = await server.videos.getWithToken({ id: video.id }) expect(video.userHistory).to.be.undefined expect(videoDetails.userHistory).to.be.undefined @@ -76,21 +68,21 @@ describe('Test videos history', function () { }) it('Should watch the first and second video', async function () { - await userWatchVideo(server.url, server.accessToken, video2UUID, 8) - await userWatchVideo(server.url, server.accessToken, video1UUID, 3) + await command.wathVideo({ videoId: video2UUID, currentTime: 8 }) + await command.wathVideo({ videoId: video1UUID, currentTime: 3 }) }) it('Should return the correct history when listing, searching and getting videos', async function () { const videosOfVideos: Video[][] = [] { - const res = await getVideosListWithToken(server.url, server.accessToken) - videosOfVideos.push(res.body.data) + const { data } = await server.videos.listWithToken() + videosOfVideos.push(data) } { - const res = await searchVideoWithToken(server.url, 'video', server.accessToken) - videosOfVideos.push(res.body.data) + const body = await server.search.searchVideos({ token: server.accessToken, search: 'video' }) + videosOfVideos.push(body.data) } for (const videos of videosOfVideos) { @@ -108,24 +100,21 @@ describe('Test videos history', function () { } { - const resDetail = await getVideoWithToken(server.url, server.accessToken, video1UUID) - const videoDetails: VideoDetails = resDetail.body + const videoDetails = await server.videos.getWithToken({ id: video1UUID }) expect(videoDetails.userHistory).to.not.be.undefined expect(videoDetails.userHistory.currentTime).to.equal(3) } { - const resDetail = await getVideoWithToken(server.url, server.accessToken, video2UUID) - const videoDetails: VideoDetails = resDetail.body + const videoDetails = await server.videos.getWithToken({ id: video2UUID }) expect(videoDetails.userHistory).to.not.be.undefined expect(videoDetails.userHistory.currentTime).to.equal(8) } { - const resDetail = await getVideoWithToken(server.url, server.accessToken, video3UUID) - const videoDetails: VideoDetails = resDetail.body + const videoDetails = await server.videos.getWithToken({ id: video3UUID }) expect(videoDetails.userHistory).to.be.undefined } @@ -133,71 +122,64 @@ describe('Test videos history', function () { it('Should have these videos when listing my history', async function () { video3WatchedDate = new Date() - await userWatchVideo(server.url, server.accessToken, video3UUID, 2) + await command.wathVideo({ videoId: video3UUID, currentTime: 2 }) - const res = await listMyVideosHistory(server.url, server.accessToken) + const body = await command.list() - expect(res.body.total).to.equal(3) + expect(body.total).to.equal(3) - const videos: Video[] = res.body.data + const videos = body.data expect(videos[0].name).to.equal('video 3') expect(videos[1].name).to.equal('video 1') expect(videos[2].name).to.equal('video 2') }) it('Should not have videos history on another user', async function () { - const res = await listMyVideosHistory(server.url, userAccessToken) + const body = await command.list({ token: userAccessToken }) - expect(res.body.total).to.equal(0) - expect(res.body.data).to.have.lengthOf(0) + expect(body.total).to.equal(0) + expect(body.data).to.have.lengthOf(0) }) it('Should be able to search through videos in my history', async function () { - const res = await listMyVideosHistory(server.url, server.accessToken, '2') - - expect(res.body.total).to.equal(1) + const body = await command.list({ search: '2' }) + expect(body.total).to.equal(1) - const videos: Video[] = res.body.data + const videos = body.data expect(videos[0].name).to.equal('video 2') }) it('Should clear my history', async function () { - await removeMyVideosHistory(server.url, server.accessToken, video3WatchedDate.toISOString()) + await command.remove({ beforeDate: video3WatchedDate.toISOString() }) }) it('Should have my history cleared', async function () { - const res = await listMyVideosHistory(server.url, server.accessToken) + const body = await command.list() + expect(body.total).to.equal(1) - expect(res.body.total).to.equal(1) - - const videos: Video[] = res.body.data + const videos = body.data expect(videos[0].name).to.equal('video 3') }) it('Should disable videos history', async function () { - await updateMyUser({ - url: server.url, - accessToken: server.accessToken, + await server.users.updateMe({ videosHistoryEnabled: false }) - await userWatchVideo(server.url, server.accessToken, video2UUID, 8, HttpStatusCode.CONFLICT_409) + await command.wathVideo({ videoId: video2UUID, currentTime: 8, expectedStatus: HttpStatusCode.CONFLICT_409 }) }) it('Should re-enable videos history', async function () { - await updateMyUser({ - url: server.url, - accessToken: server.accessToken, + await server.users.updateMe({ videosHistoryEnabled: true }) - await userWatchVideo(server.url, server.accessToken, video1UUID, 8) - - const res = await listMyVideosHistory(server.url, server.accessToken) + await command.wathVideo({ videoId: video1UUID, currentTime: 8 }) - expect(res.body.total).to.equal(2) + const body = await command.list() + expect(body.total).to.equal(2) - const videos: Video[] = res.body.data + const videos = body.data expect(videos[0].name).to.equal('video 1') expect(videos[1].name).to.equal('video 3') }) @@ -205,30 +187,29 @@ describe('Test videos history', function () { it('Should not clean old history', async function () { this.timeout(50000) - killallServers([ server ]) + await killallServers([ server ]) - await reRunServer(server, { history: { videos: { max_age: '10 days' } } }) + await server.run({ history: { videos: { max_age: '10 days' } } }) await wait(6000) // Should still have history - const res = await listMyVideosHistory(server.url, server.accessToken) - - expect(res.body.total).to.equal(2) + const body = await command.list() + expect(body.total).to.equal(2) }) it('Should clean old history', async function () { this.timeout(50000) - killallServers([ server ]) + await killallServers([ server ]) - await reRunServer(server, { history: { videos: { max_age: '5 seconds' } } }) + await server.run({ history: { videos: { max_age: '5 seconds' } } }) await wait(6000) - const res = await listMyVideosHistory(server.url, server.accessToken) - expect(res.body.total).to.equal(0) + const body = await command.list() + expect(body.total).to.equal(0) }) after(async function () { diff --git a/server/tests/api/videos/videos-overview.ts b/server/tests/api/videos/videos-overview.ts index c266a1dc5..70aa66549 100644 --- a/server/tests/api/videos/videos-overview.ts +++ b/server/tests/api/videos/videos-overview.ts @@ -2,29 +2,15 @@ import 'mocha' import * as chai from 'chai' - -import { - cleanupTests, - flushAndRunServer, - generateUserAccessToken, - ServerInfo, - setAccessTokensToServers, - uploadVideo, - wait -} from '../../../../shared/extra-utils' -import { getVideosOverview, getVideosOverviewWithToken } from '../../../../shared/extra-utils/overviews/overviews' -import { VideosOverview } from '../../../../shared/models/overviews' -import { addAccountToAccountBlocklist } from '@shared/extra-utils/users/blocklist' -import { Response } from 'superagent' +import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers, wait } from '@shared/extra-utils' +import { VideosOverview } from '@shared/models' const expect = chai.expect describe('Test a videos overview', function () { - let server: ServerInfo = null - - function testOverviewCount (res: Response, expected: number) { - const overview: VideosOverview = res.body + let server: PeerTubeServer = null + function testOverviewCount (overview: VideosOverview, expected: number) { expect(overview.tags).to.have.lengthOf(expected) expect(overview.categories).to.have.lengthOf(expected) expect(overview.channels).to.have.lengthOf(expected) @@ -33,15 +19,15 @@ describe('Test a videos overview', function () { before(async function () { this.timeout(30000) - server = await flushAndRunServer(1) + server = await createSingleServer(1) await setAccessTokensToServers([ server ]) }) it('Should send empty overview', async function () { - const res = await getVideosOverview(server.url, 1) + const body = await server.overviews.getVideos({ page: 1 }) - testOverviewCount(res, 0) + testOverviewCount(body, 0) }) it('Should upload 5 videos in a specific category, tag and channel but not include them in overview', async function () { @@ -49,40 +35,45 @@ describe('Test a videos overview', function () { await wait(3000) - await uploadVideo(server.url, server.accessToken, { - name: 'video 0', - category: 3, - tags: [ 'coucou1', 'coucou2' ] + await server.videos.upload({ + attributes: { + name: 'video 0', + category: 3, + tags: [ 'coucou1', 'coucou2' ] + } }) - const res = await getVideosOverview(server.url, 1) + const body = await server.overviews.getVideos({ page: 1 }) - testOverviewCount(res, 0) + testOverviewCount(body, 0) }) it('Should upload another video and include all videos in the overview', async function () { this.timeout(30000) - for (let i = 1; i < 6; i++) { - await uploadVideo(server.url, server.accessToken, { - name: 'video ' + i, - category: 3, - tags: [ 'coucou1', 'coucou2' ] - }) + { + for (let i = 1; i < 6; i++) { + await server.videos.upload({ + attributes: { + name: 'video ' + i, + category: 3, + tags: [ 'coucou1', 'coucou2' ] + } + }) + } + + await wait(3000) } - await wait(3000) - { - const res = await getVideosOverview(server.url, 1) + const body = await server.overviews.getVideos({ page: 1 }) - testOverviewCount(res, 1) + testOverviewCount(body, 1) } { - const res = await getVideosOverview(server.url, 2) + const overview = await server.overviews.getVideos({ page: 2 }) - const overview: VideosOverview = res.body expect(overview.tags).to.have.lengthOf(1) expect(overview.categories).to.have.lengthOf(0) expect(overview.channels).to.have.lengthOf(0) @@ -90,20 +81,10 @@ describe('Test a videos overview', function () { }) it('Should have the correct overview', async function () { - const res1 = await getVideosOverview(server.url, 1) - const res2 = await getVideosOverview(server.url, 2) - - const overview1: VideosOverview = res1.body - const overview2: VideosOverview = res2.body - - const tmp = [ - overview1.tags, - overview1.categories, - overview1.channels, - overview2.tags - ] + const overview1 = await server.overviews.getVideos({ page: 1 }) + const overview2 = await server.overviews.getVideos({ page: 2 }) - for (const arr of tmp) { + for (const arr of [ overview1.tags, overview1.categories, overview1.channels, overview2.tags ]) { expect(arr).to.have.lengthOf(1) const obj = arr[0] @@ -127,20 +108,20 @@ describe('Test a videos overview', function () { }) it('Should hide muted accounts', async function () { - const token = await generateUserAccessToken(server, 'choco') + const token = await server.users.generateUserAndToken('choco') - await addAccountToAccountBlocklist(server.url, token, 'root@' + server.host) + await server.blocklist.addToMyBlocklist({ token, account: 'root@' + server.host }) { - const res = await getVideosOverview(server.url, 1) + const body = await server.overviews.getVideos({ page: 1 }) - testOverviewCount(res, 1) + testOverviewCount(body, 1) } { - const res = await getVideosOverviewWithToken(server.url, 1, token) + const body = await server.overviews.getVideos({ page: 1, token }) - testOverviewCount(res, 0) + testOverviewCount(body, 0) } }) diff --git a/server/tests/api/videos/videos-views-cleaner.ts b/server/tests/api/videos/videos-views-cleaner.ts index b89f33217..82268b1be 100644 --- a/server/tests/api/videos/videos-views-cleaner.ts +++ b/server/tests/api/videos/videos-views-cleaner.ts @@ -1,19 +1,14 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ -import * as chai from 'chai' import 'mocha' +import * as chai from 'chai' import { cleanupTests, - closeAllSequelize, - countVideoViewsOf, + createMultipleServers, doubleFollow, - flushAndRunMultipleServers, killallServers, - reRunServer, - ServerInfo, + PeerTubeServer, setAccessTokensToServers, - uploadVideoAndGetId, - viewVideo, wait, waitJobs } from '../../../../shared/extra-utils' @@ -21,7 +16,7 @@ import { const expect = chai.expect describe('Test video views cleaner', function () { - let servers: ServerInfo[] + let servers: PeerTubeServer[] let videoIdServer1: string let videoIdServer2: string @@ -29,20 +24,20 @@ describe('Test video views cleaner', function () { before(async function () { this.timeout(120000) - servers = await flushAndRunMultipleServers(2) + servers = await createMultipleServers(2) await setAccessTokensToServers(servers) await doubleFollow(servers[0], servers[1]) - videoIdServer1 = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video server 1' })).uuid - videoIdServer2 = (await uploadVideoAndGetId({ server: servers[1], videoName: 'video server 2' })).uuid + videoIdServer1 = (await servers[0].videos.quickUpload({ name: 'video server 1' })).uuid + videoIdServer2 = (await servers[1].videos.quickUpload({ name: 'video server 2' })).uuid await waitJobs(servers) - await viewVideo(servers[0].url, videoIdServer1) - await viewVideo(servers[1].url, videoIdServer1) - await viewVideo(servers[0].url, videoIdServer2) - await viewVideo(servers[1].url, videoIdServer2) + await servers[0].videos.view({ id: videoIdServer1 }) + await servers[1].videos.view({ id: videoIdServer1 }) + await servers[0].videos.view({ id: videoIdServer2 }) + await servers[1].videos.view({ id: videoIdServer2 }) await waitJobs(servers) }) @@ -50,9 +45,9 @@ describe('Test video views cleaner', function () { it('Should not clean old video views', async function () { this.timeout(50000) - killallServers([ servers[0] ]) + await killallServers([ servers[0] ]) - await reRunServer(servers[0], { views: { videos: { remote: { max_age: '10 days' } } } }) + await servers[0].run({ views: { videos: { remote: { max_age: '10 days' } } } }) await wait(6000) @@ -60,14 +55,14 @@ describe('Test video views cleaner', function () { { for (const server of servers) { - const total = await countVideoViewsOf(server.internalServerNumber, videoIdServer1) + const total = await server.sql.countVideoViewsOf(videoIdServer1) expect(total).to.equal(2, 'Server ' + server.serverNumber + ' does not have the correct amount of views') } } { for (const server of servers) { - const total = await countVideoViewsOf(server.internalServerNumber, videoIdServer2) + const total = await server.sql.countVideoViewsOf(videoIdServer2) expect(total).to.equal(2, 'Server ' + server.serverNumber + ' does not have the correct amount of views') } } @@ -76,9 +71,9 @@ describe('Test video views cleaner', function () { it('Should clean old video views', async function () { this.timeout(50000) - killallServers([ servers[0] ]) + await killallServers([ servers[0] ]) - await reRunServer(servers[0], { views: { videos: { remote: { max_age: '5 seconds' } } } }) + await servers[0].run({ views: { videos: { remote: { max_age: '5 seconds' } } } }) await wait(6000) @@ -86,23 +81,21 @@ describe('Test video views cleaner', function () { { for (const server of servers) { - const total = await countVideoViewsOf(server.internalServerNumber, videoIdServer1) + const total = await server.sql.countVideoViewsOf(videoIdServer1) expect(total).to.equal(2) } } { - const totalServer1 = await countVideoViewsOf(servers[0].internalServerNumber, videoIdServer2) + const totalServer1 = await servers[0].sql.countVideoViewsOf(videoIdServer2) expect(totalServer1).to.equal(0) - const totalServer2 = await countVideoViewsOf(servers[1].internalServerNumber, videoIdServer2) + const totalServer2 = await servers[1].sql.countVideoViewsOf(videoIdServer2) expect(totalServer2).to.equal(2) } }) after(async function () { - await closeAllSequelize(servers) - await cleanupTests(servers) }) }) diff --git a/server/tests/cli/create-import-video-file-job.ts b/server/tests/cli/create-import-video-file-job.ts index 49758ff56..bddcff5e7 100644 --- a/server/tests/cli/create-import-video-file-job.ts +++ b/server/tests/cli/create-import-video-file-job.ts @@ -2,21 +2,8 @@ import 'mocha' import * as chai from 'chai' -import { VideoFile } from '@shared/models/videos/video-file.model' -import { - cleanupTests, - doubleFollow, - execCLI, - flushAndRunMultipleServers, - getEnvCli, - getVideo, - getVideosList, - ServerInfo, - setAccessTokensToServers, - uploadVideo -} from '../../../shared/extra-utils' -import { waitJobs } from '../../../shared/extra-utils/server/jobs' -import { VideoDetails } from '../../../shared/models/videos' +import { cleanupTests, createMultipleServers, doubleFollow, PeerTubeServer, setAccessTokensToServers, waitJobs } from '@shared/extra-utils' +import { VideoFile } from '@shared/models' const expect = chai.expect @@ -33,7 +20,7 @@ function assertVideoProperties (video: VideoFile, resolution: number, extname: s describe('Test create import video jobs', function () { this.timeout(60000) - let servers: ServerInfo[] = [] + let servers: PeerTubeServer[] = [] let video1UUID: string let video2UUID: string @@ -41,56 +28,61 @@ describe('Test create import video jobs', function () { this.timeout(90000) // Run server 2 to have transcoding enabled - servers = await flushAndRunMultipleServers(2) + servers = await createMultipleServers(2) await setAccessTokensToServers(servers) await doubleFollow(servers[0], servers[1]) // Upload two videos for our needs - const res1 = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video1' }) - video1UUID = res1.body.video.uuid - const res2 = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video2' }) - video2UUID = res2.body.video.uuid + { + const { uuid } = await servers[0].videos.upload({ attributes: { name: 'video1' } }) + video1UUID = uuid + } + + { + const { uuid } = await servers[1].videos.upload({ attributes: { name: 'video2' } }) + video2UUID = uuid + } // Transcoding await waitJobs(servers) }) it('Should run a import job on video 1 with a lower resolution', async function () { - const env = getEnvCli(servers[0]) - await execCLI(`${env} npm run create-import-video-file-job -- -v ${video1UUID} -i server/tests/fixtures/video_short-480.webm`) + const command = `npm run create-import-video-file-job -- -v ${video1UUID} -i server/tests/fixtures/video_short-480.webm` + await servers[0].cli.execWithEnv(command) await waitJobs(servers) for (const server of servers) { - const { data: videos } = (await getVideosList(server.url)).body + const { data: videos } = await server.videos.list() expect(videos).to.have.lengthOf(2) const video = videos.find(({ uuid }) => uuid === video1UUID) - const videoDetail: VideoDetails = (await getVideo(server.url, video.uuid)).body + const videoDetails = await server.videos.get({ id: video.uuid }) - expect(videoDetail.files).to.have.lengthOf(2) - const [ originalVideo, transcodedVideo ] = videoDetail.files + expect(videoDetails.files).to.have.lengthOf(2) + const [ originalVideo, transcodedVideo ] = videoDetails.files assertVideoProperties(originalVideo, 720, 'webm', 218910) assertVideoProperties(transcodedVideo, 480, 'webm', 69217) } }) it('Should run a import job on video 2 with the same resolution and a different extension', async function () { - const env = getEnvCli(servers[1]) - await execCLI(`${env} npm run create-import-video-file-job -- -v ${video2UUID} -i server/tests/fixtures/video_short.ogv`) + const command = `npm run create-import-video-file-job -- -v ${video2UUID} -i server/tests/fixtures/video_short.ogv` + await servers[1].cli.execWithEnv(command) await waitJobs(servers) for (const server of servers) { - const { data: videos } = (await getVideosList(server.url)).body + const { data: videos } = await server.videos.list() expect(videos).to.have.lengthOf(2) const video = videos.find(({ uuid }) => uuid === video2UUID) - const videoDetail: VideoDetails = (await getVideo(server.url, video.uuid)).body + const videoDetails = await server.videos.get({ id: video.uuid }) - expect(videoDetail.files).to.have.lengthOf(4) - const [ originalVideo, transcodedVideo420, transcodedVideo320, transcodedVideo240 ] = videoDetail.files + expect(videoDetails.files).to.have.lengthOf(4) + const [ originalVideo, transcodedVideo420, transcodedVideo320, transcodedVideo240 ] = videoDetails.files assertVideoProperties(originalVideo, 720, 'ogv', 140849) assertVideoProperties(transcodedVideo420, 480, 'mp4') assertVideoProperties(transcodedVideo320, 360, 'mp4') @@ -99,20 +91,20 @@ describe('Test create import video jobs', function () { }) it('Should run a import job on video 2 with the same resolution and the same extension', async function () { - const env = getEnvCli(servers[0]) - await execCLI(`${env} npm run create-import-video-file-job -- -v ${video1UUID} -i server/tests/fixtures/video_short2.webm`) + const command = `npm run create-import-video-file-job -- -v ${video1UUID} -i server/tests/fixtures/video_short2.webm` + await servers[0].cli.execWithEnv(command) await waitJobs(servers) for (const server of servers) { - const { data: videos } = (await getVideosList(server.url)).body + const { data: videos } = await server.videos.list() expect(videos).to.have.lengthOf(2) const video = videos.find(({ uuid }) => uuid === video1UUID) - const videoDetail: VideoDetails = (await getVideo(server.url, video.uuid)).body + const videoDetails = await server.videos.get({ id: video.uuid }) - expect(videoDetail.files).to.have.lengthOf(2) - const [ video720, video480 ] = videoDetail.files + expect(videoDetails.files).to.have.lengthOf(2) + const [ video720, video480 ] = videoDetails.files assertVideoProperties(video720, 720, 'webm', 942961) assertVideoProperties(video480, 480, 'webm', 69217) } diff --git a/server/tests/cli/create-transcoding-job.ts b/server/tests/cli/create-transcoding-job.ts index 5bc1687cd..df787ccdc 100644 --- a/server/tests/cli/create-transcoding-job.ts +++ b/server/tests/cli/create-transcoding-job.ts @@ -2,26 +2,19 @@ import 'mocha' import * as chai from 'chai' -import { VideoDetails } from '../../../shared/models/videos' import { cleanupTests, + createMultipleServers, doubleFollow, - execCLI, - flushAndRunMultipleServers, - getEnvCli, - getVideo, - getVideosList, - ServerInfo, + PeerTubeServer, setAccessTokensToServers, - updateCustomSubConfig, - uploadVideo + waitJobs } from '../../../shared/extra-utils' -import { waitJobs } from '../../../shared/extra-utils/server/jobs' const expect = chai.expect describe('Test create transcoding jobs', function () { - let servers: ServerInfo[] = [] + let servers: PeerTubeServer[] = [] const videosUUID: string[] = [] const config = { @@ -46,16 +39,16 @@ describe('Test create transcoding jobs', function () { this.timeout(60000) // Run server 2 to have transcoding enabled - servers = await flushAndRunMultipleServers(2) + servers = await createMultipleServers(2) await setAccessTokensToServers(servers) - await updateCustomSubConfig(servers[0].url, servers[0].accessToken, config) + await servers[0].config.updateCustomSubConfig({ newConfig: config }) await doubleFollow(servers[0], servers[1]) for (let i = 1; i <= 5; i++) { - const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video' + i }) - videosUUID.push(res.body.video.uuid) + const { uuid } = await servers[0].videos.upload({ attributes: { name: 'video' + i } }) + videosUUID.push(uuid) } await waitJobs(servers) @@ -65,13 +58,11 @@ describe('Test create transcoding jobs', function () { this.timeout(30000) for (const server of servers) { - const res = await getVideosList(server.url) - const videos = res.body.data - expect(videos).to.have.lengthOf(videosUUID.length) + const { data } = await server.videos.list() + expect(data).to.have.lengthOf(videosUUID.length) - for (const video of videos) { - const res2 = await getVideo(server.url, video.uuid) - const videoDetail: VideoDetails = res2.body + for (const video of data) { + const videoDetail = await server.videos.get({ id: video.uuid }) expect(videoDetail.files).to.have.lengthOf(1) expect(videoDetail.streamingPlaylists).to.have.lengthOf(0) } @@ -81,20 +72,16 @@ describe('Test create transcoding jobs', function () { it('Should run a transcoding job on video 2', async function () { this.timeout(60000) - const env = getEnvCli(servers[0]) - await execCLI(`${env} npm run create-transcoding-job -- -v ${videosUUID[1]}`) - + await servers[0].cli.execWithEnv(`npm run create-transcoding-job -- -v ${videosUUID[1]}`) await waitJobs(servers) for (const server of servers) { - const res = await getVideosList(server.url) - const videos = res.body.data + const { data } = await server.videos.list() let infoHashes: { [id: number]: string } - for (const video of videos) { - const res2 = await getVideo(server.url, video.uuid) - const videoDetail: VideoDetails = res2.body + for (const video of data) { + const videoDetail = await server.videos.get({ id: video.uuid }) if (video.uuid === videosUUID[1]) { expect(videoDetail.files).to.have.lengthOf(4) @@ -123,43 +110,38 @@ describe('Test create transcoding jobs', function () { it('Should run a transcoding job on video 1 with resolution', async function () { this.timeout(60000) - const env = getEnvCli(servers[0]) - await execCLI(`${env} npm run create-transcoding-job -- -v ${videosUUID[0]} -r 480`) + await servers[0].cli.execWithEnv(`npm run create-transcoding-job -- -v ${videosUUID[0]} -r 480`) await waitJobs(servers) for (const server of servers) { - const res = await getVideosList(server.url) - const videos = res.body.data - expect(videos).to.have.lengthOf(videosUUID.length) + const { data } = await server.videos.list() + expect(data).to.have.lengthOf(videosUUID.length) - const res2 = await getVideo(server.url, videosUUID[0]) - const videoDetail: VideoDetails = res2.body + const videoDetails = await server.videos.get({ id: videosUUID[0] }) - expect(videoDetail.files).to.have.lengthOf(2) - expect(videoDetail.files[0].resolution.id).to.equal(720) - expect(videoDetail.files[1].resolution.id).to.equal(480) + expect(videoDetails.files).to.have.lengthOf(2) + expect(videoDetails.files[0].resolution.id).to.equal(720) + expect(videoDetails.files[1].resolution.id).to.equal(480) - expect(videoDetail.streamingPlaylists).to.have.lengthOf(0) + expect(videoDetails.streamingPlaylists).to.have.lengthOf(0) } }) it('Should generate an HLS resolution', async function () { this.timeout(120000) - const env = getEnvCli(servers[0]) - await execCLI(`${env} npm run create-transcoding-job -- -v ${videosUUID[2]} --generate-hls -r 480`) + await servers[0].cli.execWithEnv(`npm run create-transcoding-job -- -v ${videosUUID[2]} --generate-hls -r 480`) await waitJobs(servers) for (const server of servers) { - const res = await getVideo(server.url, videosUUID[2]) - const videoDetail: VideoDetails = res.body + const videoDetails = await server.videos.get({ id: videosUUID[2] }) - expect(videoDetail.files).to.have.lengthOf(1) - expect(videoDetail.streamingPlaylists).to.have.lengthOf(1) + expect(videoDetails.files).to.have.lengthOf(1) + expect(videoDetails.streamingPlaylists).to.have.lengthOf(1) - const files = videoDetail.streamingPlaylists[0].files + const files = videoDetails.streamingPlaylists[0].files expect(files).to.have.lengthOf(1) expect(files[0].resolution.id).to.equal(480) } @@ -168,16 +150,14 @@ describe('Test create transcoding jobs', function () { it('Should not duplicate an HLS resolution', async function () { this.timeout(120000) - const env = getEnvCli(servers[0]) - await execCLI(`${env} npm run create-transcoding-job -- -v ${videosUUID[2]} --generate-hls -r 480`) + await servers[0].cli.execWithEnv(`npm run create-transcoding-job -- -v ${videosUUID[2]} --generate-hls -r 480`) await waitJobs(servers) for (const server of servers) { - const res = await getVideo(server.url, videosUUID[2]) - const videoDetail: VideoDetails = res.body + const videoDetails = await server.videos.get({ id: videosUUID[2] }) - const files = videoDetail.streamingPlaylists[0].files + const files = videoDetails.streamingPlaylists[0].files expect(files).to.have.lengthOf(1) expect(files[0].resolution.id).to.equal(480) } @@ -186,19 +166,17 @@ describe('Test create transcoding jobs', function () { it('Should generate all HLS resolutions', async function () { this.timeout(120000) - const env = getEnvCli(servers[0]) - await execCLI(`${env} npm run create-transcoding-job -- -v ${videosUUID[3]} --generate-hls`) + await servers[0].cli.execWithEnv(`npm run create-transcoding-job -- -v ${videosUUID[3]} --generate-hls`) await waitJobs(servers) for (const server of servers) { - const res = await getVideo(server.url, videosUUID[3]) - const videoDetail: VideoDetails = res.body + const videoDetails = await server.videos.get({ id: videosUUID[3] }) - expect(videoDetail.files).to.have.lengthOf(1) - expect(videoDetail.streamingPlaylists).to.have.lengthOf(1) + expect(videoDetails.files).to.have.lengthOf(1) + expect(videoDetails.streamingPlaylists).to.have.lengthOf(1) - const files = videoDetail.streamingPlaylists[0].files + const files = videoDetails.streamingPlaylists[0].files expect(files).to.have.lengthOf(4) } }) @@ -207,20 +185,18 @@ describe('Test create transcoding jobs', function () { this.timeout(120000) config.transcoding.hls.enabled = true - await updateCustomSubConfig(servers[0].url, servers[0].accessToken, config) + await servers[0].config.updateCustomSubConfig({ newConfig: config }) - const env = getEnvCli(servers[0]) - await execCLI(`${env} npm run create-transcoding-job -- -v ${videosUUID[4]}`) + await servers[0].cli.execWithEnv(`npm run create-transcoding-job -- -v ${videosUUID[4]}`) await waitJobs(servers) for (const server of servers) { - const res = await getVideo(server.url, videosUUID[4]) - const videoDetail: VideoDetails = res.body + const videoDetails = await server.videos.get({ id: videosUUID[4] }) - expect(videoDetail.files).to.have.lengthOf(4) - expect(videoDetail.streamingPlaylists).to.have.lengthOf(1) - expect(videoDetail.streamingPlaylists[0].files).to.have.lengthOf(4) + expect(videoDetails.files).to.have.lengthOf(4) + expect(videoDetails.streamingPlaylists).to.have.lengthOf(1) + expect(videoDetails.streamingPlaylists[0].files).to.have.lengthOf(4) } }) diff --git a/server/tests/cli/optimize-old-videos.ts b/server/tests/cli/optimize-old-videos.ts index 91a1c9cc4..579b2e7d8 100644 --- a/server/tests/cli/optimize-old-videos.ts +++ b/server/tests/cli/optimize-old-videos.ts @@ -2,38 +2,30 @@ import 'mocha' import * as chai from 'chai' -import { join } from 'path' import { - buildServerDirectory, cleanupTests, + createMultipleServers, doubleFollow, - execCLI, - flushAndRunMultipleServers, generateHighBitrateVideo, - getEnvCli, - getVideo, - getVideosList, - ServerInfo, + PeerTubeServer, setAccessTokensToServers, - uploadVideo, - viewVideo, - wait -} from '../../../shared/extra-utils' -import { waitJobs } from '../../../shared/extra-utils/server/jobs' -import { getMaxBitrate, Video, VideoDetails, VideoResolution } from '../../../shared/models/videos' + wait, + waitJobs +} from '@shared/extra-utils' +import { getMaxBitrate, VideoResolution } from '@shared/models' import { getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from '../../helpers/ffprobe-utils' import { VIDEO_TRANSCODING_FPS } from '../../initializers/constants' const expect = chai.expect describe('Test optimize old videos', function () { - let servers: ServerInfo[] = [] + let servers: PeerTubeServer[] = [] before(async function () { this.timeout(200000) // Run server 2 to have transcoding enabled - servers = await flushAndRunMultipleServers(2) + servers = await createMultipleServers(2) await setAccessTokensToServers(servers) await doubleFollow(servers[0], servers[1]) @@ -48,8 +40,8 @@ describe('Test optimize old videos', function () { } // Upload two videos for our needs - await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video1', fixture: tempFixturePath }) - await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video2', fixture: tempFixturePath }) + await servers[0].videos.upload({ attributes: { name: 'video1', fixture: tempFixturePath } }) + await servers[0].videos.upload({ attributes: { name: 'video2', fixture: tempFixturePath } }) await waitJobs(servers) }) @@ -58,14 +50,12 @@ describe('Test optimize old videos', function () { this.timeout(30000) for (const server of servers) { - const res = await getVideosList(server.url) - const videos = res.body.data - expect(videos).to.have.lengthOf(2) - - for (const video of videos) { - const res2 = await getVideo(server.url, video.uuid) - const videoDetail: VideoDetails = res2.body - expect(videoDetail.files).to.have.lengthOf(1) + const { data } = await server.videos.list() + expect(data).to.have.lengthOf(2) + + for (const video of data) { + const videoDetails = await server.videos.get({ id: video.uuid }) + expect(videoDetails.files).to.have.lengthOf(1) } } }) @@ -73,34 +63,29 @@ describe('Test optimize old videos', function () { it('Should run optimize script', async function () { this.timeout(200000) - const env = getEnvCli(servers[0]) - await execCLI(`${env} npm run optimize-old-videos`) - + await servers[0].cli.execWithEnv('npm run optimize-old-videos') await waitJobs(servers) for (const server of servers) { - const res = await getVideosList(server.url) - const videos: Video[] = res.body.data - - expect(videos).to.have.lengthOf(2) + const { data } = await server.videos.list() + expect(data).to.have.lengthOf(2) - for (const video of videos) { - await viewVideo(server.url, video.uuid) + for (const video of data) { + await server.videos.view({ id: video.uuid }) // Refresh video await waitJobs(servers) await wait(5000) await waitJobs(servers) - const res2 = await getVideo(server.url, video.uuid) - const videosDetails: VideoDetails = res2.body + const videoDetails = await server.videos.get({ id: video.uuid }) - expect(videosDetails.files).to.have.lengthOf(1) - const file = videosDetails.files[0] + expect(videoDetails.files).to.have.lengthOf(1) + const file = videoDetails.files[0] expect(file.size).to.be.below(8000000) - const path = buildServerDirectory(servers[0], join('videos', video.uuid + '-' + file.resolution.id + '.mp4')) + const path = servers[0].servers.buildWebTorrentFilePath(file.fileUrl) const bitrate = await getVideoFileBitrate(path) const fps = await getVideoFileFPS(path) const resolution = await getVideoFileResolution(path) diff --git a/server/tests/cli/peertube.ts b/server/tests/cli/peertube.ts index fcf7e2e2e..f2a984962 100644 --- a/server/tests/cli/peertube.ts +++ b/server/tests/cli/peertube.ts @@ -2,97 +2,91 @@ import 'mocha' import { expect } from 'chai' -import { Video, VideoDetails } from '../../../shared' import { - addVideoChannel, areHttpImportTestsDisabled, buildAbsoluteFixturePath, cleanupTests, - createUser, + CLICommand, + createSingleServer, doubleFollow, - execCLI, - flushAndRunServer, - getEnvCli, - getLocalIdByUUID, - getVideo, - getVideosList, - removeVideo, - ServerInfo, + FIXTURE_URLS, + PeerTubeServer, setAccessTokensToServers, testHelloWorldRegisteredSettings, - uploadVideoAndGetId, - userLogin, waitJobs } from '../../../shared/extra-utils' -import { getYoutubeVideoUrl } from '../../../shared/extra-utils/videos/video-imports' describe('Test CLI wrapper', function () { - let server: ServerInfo + let server: PeerTubeServer let userAccessToken: string + let cliCommand: CLICommand + const cmd = 'node ./dist/server/tools/peertube.js' before(async function () { this.timeout(30000) - server = await flushAndRunServer(1) + server = await createSingleServer(1) await setAccessTokensToServers([ server ]) - await createUser({ url: server.url, accessToken: server.accessToken, username: 'user_1', password: 'super_password' }) + await server.users.create({ username: 'user_1', password: 'super_password' }) - userAccessToken = await userLogin(server, { username: 'user_1', password: 'super_password' }) + userAccessToken = await server.login.getAccessToken({ username: 'user_1', password: 'super_password' }) { - const args = { name: 'user_channel', displayName: 'User channel', support: 'super support text' } - await addVideoChannel(server.url, userAccessToken, args) + const attributes = { name: 'user_channel', displayName: 'User channel', support: 'super support text' } + await server.channels.create({ token: userAccessToken, attributes }) } + + cliCommand = server.cli }) describe('Authentication and instance selection', function () { + it('Should get an access token', async function () { + const stdout = await cliCommand.execWithEnv(`${cmd} token --url ${server.url} --username user_1 --password super_password`) + const token = stdout.trim() + + const body = await server.users.getMyInfo({ token }) + expect(body.username).to.equal('user_1') + }) + it('Should display no selected instance', async function () { this.timeout(60000) - const env = getEnvCli(server) - const stdout = await execCLI(`${env} ${cmd} --help`) - + const stdout = await cliCommand.execWithEnv(`${cmd} --help`) expect(stdout).to.contain('no instance selected') }) it('Should add a user', async function () { this.timeout(60000) - const env = getEnvCli(server) - await execCLI(`${env} ${cmd} auth add -u ${server.url} -U user_1 -p super_password`) + await cliCommand.execWithEnv(`${cmd} auth add -u ${server.url} -U user_1 -p super_password`) }) it('Should not fail to add a user if there is a slash at the end of the instance URL', async function () { this.timeout(60000) - const env = getEnvCli(server) - let fullServerURL - fullServerURL = server.url + '/' - await execCLI(`${env} ${cmd} auth add -u ${fullServerURL} -U user_1 -p super_password`) + let fullServerURL = server.url + '/' + + await cliCommand.execWithEnv(`${cmd} auth add -u ${fullServerURL} -U user_1 -p super_password`) fullServerURL = server.url + '/asdfasdf' - await execCLI(`${env} ${cmd} auth add -u ${fullServerURL} -U user_1 -p super_password`) + await cliCommand.execWithEnv(`${cmd} auth add -u ${fullServerURL} -U user_1 -p super_password`) }) it('Should default to this user', async function () { this.timeout(60000) - const env = getEnvCli(server) - const stdout = await execCLI(`${env} ${cmd} --help`) - + const stdout = await cliCommand.execWithEnv(`${cmd} --help`) expect(stdout).to.contain(`instance ${server.url} selected`) }) it('Should remember the user', async function () { this.timeout(60000) - const env = getEnvCli(server) - const stdout = await execCLI(`${env} ${cmd} auth list`) - + const stdout = await cliCommand.execWithEnv(`${cmd} auth list`) expect(stdout).to.contain(server.url) }) }) @@ -102,24 +96,17 @@ describe('Test CLI wrapper', function () { it('Should upload a video', async function () { this.timeout(60000) - const env = getEnvCli(server) - const fixture = buildAbsoluteFixturePath('60fps_720p_small.mp4') - const params = `-f ${fixture} --video-name 'test upload' --channel-name user_channel --support 'support_text'` - await execCLI(`${env} ${cmd} upload ${params}`) + await cliCommand.execWithEnv(`${cmd} upload ${params}`) }) it('Should have the video uploaded', async function () { - const res = await getVideosList(server.url) - - expect(res.body.total).to.equal(1) - - const videos: Video[] = res.body.data - - const video: VideoDetails = (await getVideo(server.url, videos[0].uuid)).body + const { total, data } = await server.videos.list() + expect(total).to.equal(1) + const video = await server.videos.get({ id: data[0].uuid }) expect(video.name).to.equal('test upload') expect(video.support).to.equal('support_text') expect(video.channel.name).to.equal('user_channel') @@ -130,11 +117,8 @@ describe('Test CLI wrapper', function () { this.timeout(60000) - const env = getEnvCli(server) - - const params = `--target-url ${getYoutubeVideoUrl()} --channel-name user_channel` - - await execCLI(`${env} ${cmd} import ${params}`) + const params = `--target-url ${FIXTURE_URLS.youtube} --channel-name user_channel` + await cliCommand.execWithEnv(`${cmd} import ${params}`) }) it('Should have imported the video', async function () { @@ -144,21 +128,19 @@ describe('Test CLI wrapper', function () { await waitJobs([ server ]) - const res = await getVideosList(server.url) - - expect(res.body.total).to.equal(2) + const { total, data } = await server.videos.list() + expect(total).to.equal(2) - const videos: Video[] = res.body.data - const video = videos.find(v => v.name === 'small video - youtube') + const video = data.find(v => v.name === 'small video - youtube') expect(video).to.not.be.undefined - const videoDetails: VideoDetails = (await getVideo(server.url, video.id)).body + const videoDetails = await server.videos.get({ id: video.id }) expect(videoDetails.channel.name).to.equal('user_channel') expect(videoDetails.support).to.equal('super support text') expect(videoDetails.nsfw).to.be.false // So we can reimport it - await removeVideo(server.url, userAccessToken, video.id) + await server.videos.remove({ token: userAccessToken, id: video.id }) }) it('Should import and override some imported attributes', async function () { @@ -166,23 +148,20 @@ describe('Test CLI wrapper', function () { this.timeout(60000) - const env = getEnvCli(server) - - const params = `--target-url ${getYoutubeVideoUrl()} --channel-name user_channel --video-name toto --nsfw --support support` - - await execCLI(`${env} ${cmd} import ${params}`) + const params = `--target-url ${FIXTURE_URLS.youtube} ` + + `--channel-name user_channel --video-name toto --nsfw --support support` + await cliCommand.execWithEnv(`${cmd} import ${params}`) await waitJobs([ server ]) { - const res = await getVideosList(server.url) - expect(res.body.total).to.equal(2) + const { total, data } = await server.videos.list() + expect(total).to.equal(2) - const videos: Video[] = res.body.data - const video = videos.find(v => v.name === 'toto') + const video = data.find(v => v.name === 'toto') expect(video).to.not.be.undefined - const videoDetails: VideoDetails = (await getVideo(server.url, video.id)).body + const videoDetails = await server.videos.get({ id: video.id }) expect(videoDetails.channel.name).to.equal('user_channel') expect(videoDetails.support).to.equal('support') expect(videoDetails.nsfw).to.be.true @@ -194,18 +173,14 @@ describe('Test CLI wrapper', function () { describe('Admin auth', function () { it('Should remove the auth user', async function () { - const env = getEnvCli(server) - - await execCLI(`${env} ${cmd} auth del ${server.url}`) - - const stdout = await execCLI(`${env} ${cmd} --help`) + await cliCommand.execWithEnv(`${cmd} auth del ${server.url}`) + const stdout = await cliCommand.execWithEnv(`${cmd} --help`) expect(stdout).to.contain('no instance selected') }) it('Should add the admin user', async function () { - const env = getEnvCli(server) - await execCLI(`${env} ${cmd} auth add -u ${server.url} -U root -p test${server.internalServerNumber}`) + await cliCommand.execWithEnv(`${cmd} auth add -u ${server.url} -U root -p test${server.internalServerNumber}`) }) }) @@ -214,8 +189,7 @@ describe('Test CLI wrapper', function () { it('Should install a plugin', async function () { this.timeout(60000) - const env = getEnvCli(server) - await execCLI(`${env} ${cmd} plugins install --npm-name peertube-plugin-hello-world`) + await cliCommand.execWithEnv(`${cmd} plugins install --npm-name peertube-plugin-hello-world`) }) it('Should have registered settings', async function () { @@ -223,29 +197,27 @@ describe('Test CLI wrapper', function () { }) it('Should list installed plugins', async function () { - const env = getEnvCli(server) - const res = await execCLI(`${env} ${cmd} plugins list`) + const res = await cliCommand.execWithEnv(`${cmd} plugins list`) expect(res).to.contain('peertube-plugin-hello-world') }) it('Should uninstall the plugin', async function () { - const env = getEnvCli(server) - const res = await execCLI(`${env} ${cmd} plugins uninstall --npm-name peertube-plugin-hello-world`) + const res = await cliCommand.execWithEnv(`${cmd} plugins uninstall --npm-name peertube-plugin-hello-world`) expect(res).to.not.contain('peertube-plugin-hello-world') }) }) describe('Manage video redundancies', function () { - let anotherServer: ServerInfo + let anotherServer: PeerTubeServer let video1Server2: number - let servers: ServerInfo[] + let servers: PeerTubeServer[] before(async function () { this.timeout(120000) - anotherServer = await flushAndRunServer(2) + anotherServer = await createSingleServer(2) await setAccessTokensToServers([ anotherServer ]) await doubleFollow(server, anotherServer) @@ -253,20 +225,17 @@ describe('Test CLI wrapper', function () { servers = [ server, anotherServer ] await waitJobs(servers) - const uuid = (await uploadVideoAndGetId({ server: anotherServer, videoName: 'super video' })).uuid + const { uuid } = await anotherServer.videos.quickUpload({ name: 'super video' }) await waitJobs(servers) - video1Server2 = await getLocalIdByUUID(server.url, uuid) + video1Server2 = await server.videos.getId({ uuid }) }) it('Should add a redundancy', async function () { this.timeout(60000) - const env = getEnvCli(server) - const params = `add --video ${video1Server2}` - - await execCLI(`${env} ${cmd} redundancy ${params}`) + await cliCommand.execWithEnv(`${cmd} redundancy ${params}`) await waitJobs(servers) }) @@ -275,10 +244,8 @@ describe('Test CLI wrapper', function () { this.timeout(60000) { - const env = getEnvCli(server) - const params = 'list-my-redundancies' - const stdout = await execCLI(`${env} ${cmd} redundancy ${params}`) + const stdout = await cliCommand.execWithEnv(`${cmd} redundancy ${params}`) expect(stdout).to.contain('super video') expect(stdout).to.contain(`localhost:${server.port}`) @@ -288,18 +255,14 @@ describe('Test CLI wrapper', function () { it('Should remove a redundancy', async function () { this.timeout(60000) - const env = getEnvCli(server) - const params = `remove --video ${video1Server2}` - - await execCLI(`${env} ${cmd} redundancy ${params}`) + await cliCommand.execWithEnv(`${cmd} redundancy ${params}`) await waitJobs(servers) { - const env = getEnvCli(server) const params = 'list-my-redundancies' - const stdout = await execCLI(`${env} ${cmd} redundancy ${params}`) + const stdout = await cliCommand.execWithEnv(`${cmd} redundancy ${params}`) expect(stdout).to.not.contain('super video') } diff --git a/server/tests/cli/plugins.ts b/server/tests/cli/plugins.ts index 7f19f14b7..07c78cc89 100644 --- a/server/tests/cli/plugins.ts +++ b/server/tests/cli/plugins.ts @@ -1,55 +1,47 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ import 'mocha' +import { expect } from 'chai' import { cleanupTests, - execCLI, - flushAndRunServer, - getConfig, - getEnvCli, - getPluginTestPath, + createSingleServer, killallServers, - reRunServer, - ServerInfo, + PeerTubeServer, + PluginsCommand, setAccessTokensToServers } from '../../../shared/extra-utils' -import { ServerConfig } from '../../../shared/models/server' -import { expect } from 'chai' describe('Test plugin scripts', function () { - let server: ServerInfo + let server: PeerTubeServer before(async function () { this.timeout(30000) - server = await flushAndRunServer(1) + server = await createSingleServer(1) await setAccessTokensToServers([ server ]) }) it('Should install a plugin from stateless CLI', async function () { this.timeout(60000) - const packagePath = getPluginTestPath() + const packagePath = PluginsCommand.getPluginTestPath() - const env = getEnvCli(server) - await execCLI(`${env} npm run plugin:install -- --plugin-path ${packagePath}`) + await server.cli.execWithEnv(`npm run plugin:install -- --plugin-path ${packagePath}`) }) it('Should install a theme from stateless CLI', async function () { this.timeout(60000) - const env = getEnvCli(server) - await execCLI(`${env} npm run plugin:install -- --npm-name peertube-theme-background-red`) + await server.cli.execWithEnv(`npm run plugin:install -- --npm-name peertube-theme-background-red`) }) it('Should have the theme and the plugin registered when we restart peertube', async function () { this.timeout(30000) - killallServers([ server ]) - await reRunServer(server) + await killallServers([ server ]) + await server.run() - const res = await getConfig(server.url) - const config: ServerConfig = res.body + const config = await server.config.getConfig() const plugin = config.plugin.registered .find(p => p.name === 'test') @@ -63,18 +55,16 @@ describe('Test plugin scripts', function () { it('Should uninstall a plugin from stateless CLI', async function () { this.timeout(60000) - const env = getEnvCli(server) - await execCLI(`${env} npm run plugin:uninstall -- --npm-name peertube-plugin-test`) + await server.cli.execWithEnv(`npm run plugin:uninstall -- --npm-name peertube-plugin-test`) }) it('Should have removed the plugin on another peertube restart', async function () { this.timeout(30000) - killallServers([ server ]) - await reRunServer(server) + await killallServers([ server ]) + await server.run() - const res = await getConfig(server.url) - const config: ServerConfig = res.body + const config = await server.config.getConfig() const plugin = config.plugin.registered .find(p => p.name === 'test') diff --git a/server/tests/cli/print-transcode-command.ts b/server/tests/cli/print-transcode-command.ts index 2d7255db7..3a7969e68 100644 --- a/server/tests/cli/print-transcode-command.ts +++ b/server/tests/cli/print-transcode-command.ts @@ -2,14 +2,15 @@ import 'mocha' import * as chai from 'chai' -import { execCLI } from '../../../shared/extra-utils' +import { getVideoFileBitrate, getVideoFileFPS } from '@server/helpers/ffprobe-utils' +import { CLICommand } from '@shared/extra-utils' import { getTargetBitrate, VideoResolution } from '../../../shared/models/videos' import { VIDEO_TRANSCODING_FPS } from '../../initializers/constants' -import { getVideoFileBitrate, getVideoFileFPS } from '@server/helpers/ffprobe-utils' const expect = chai.expect describe('Test create transcoding jobs', function () { + it('Should print the correct command for each resolution', async function () { const fixturePath = 'server/tests/fixtures/video_short.webm' const fps = await getVideoFileFPS(fixturePath) @@ -19,7 +20,7 @@ describe('Test create transcoding jobs', function () { VideoResolution.H_720P, VideoResolution.H_1080P ]) { - const command = await execCLI(`npm run print-transcode-command -- ${fixturePath} -r ${resolution}`) + const command = await CLICommand.exec(`npm run print-transcode-command -- ${fixturePath} -r ${resolution}`) const targetBitrate = Math.min(getTargetBitrate(resolution, fps, VIDEO_TRANSCODING_FPS), bitrate) expect(command).to.includes(`-vf scale=w=-2:h=${resolution}`) diff --git a/server/tests/cli/prune-storage.ts b/server/tests/cli/prune-storage.ts index a0af09de8..2d4c02da7 100644 --- a/server/tests/cli/prune-storage.ts +++ b/server/tests/cli/prune-storage.ts @@ -5,87 +5,89 @@ import * as chai from 'chai' import { createFile, readdir } from 'fs-extra' import { join } from 'path' import { buildUUID } from '@server/helpers/uuid' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' import { - buildServerDirectory, cleanupTests, - createVideoPlaylist, + CLICommand, + createMultipleServers, doubleFollow, - execCLI, - flushAndRunMultipleServers, - getAccount, - getEnvCli, killallServers, makeGetRequest, - ServerInfo, + PeerTubeServer, setAccessTokensToServers, setDefaultVideoChannel, - updateMyAvatar, - uploadVideo, - wait -} from '../../../shared/extra-utils' -import { waitJobs } from '../../../shared/extra-utils/server/jobs' -import { Account, VideoPlaylistPrivacy } from '../../../shared/models' + wait, + waitJobs +} from '@shared/extra-utils' +import { HttpStatusCode, VideoPlaylistPrivacy } from '@shared/models' const expect = chai.expect -async function countFiles (internalServerNumber: number, directory: string) { - const files = await readdir(buildServerDirectory({ internalServerNumber }, directory)) +async function countFiles (server: PeerTubeServer, directory: string) { + const files = await readdir(server.servers.buildDirectory(directory)) return files.length } -async function assertNotExists (internalServerNumber: number, directory: string, substring: string) { - const files = await readdir(buildServerDirectory({ internalServerNumber }, directory)) +async function assertNotExists (server: PeerTubeServer, directory: string, substring: string) { + const files = await readdir(server.servers.buildDirectory(directory)) for (const f of files) { expect(f).to.not.contain(substring) } } -async function assertCountAreOkay (servers: ServerInfo[]) { +async function assertCountAreOkay (servers: PeerTubeServer[], videoServer2UUID: string) { for (const server of servers) { - const videosCount = await countFiles(server.internalServerNumber, 'videos') + const videosCount = await countFiles(server, 'videos') expect(videosCount).to.equal(8) - const torrentsCount = await countFiles(server.internalServerNumber, 'torrents') + const torrentsCount = await countFiles(server, 'torrents') expect(torrentsCount).to.equal(16) - const previewsCount = await countFiles(server.internalServerNumber, 'previews') + const previewsCount = await countFiles(server, 'previews') expect(previewsCount).to.equal(2) - const thumbnailsCount = await countFiles(server.internalServerNumber, 'thumbnails') + const thumbnailsCount = await countFiles(server, 'thumbnails') expect(thumbnailsCount).to.equal(6) - const avatarsCount = await countFiles(server.internalServerNumber, 'avatars') + const avatarsCount = await countFiles(server, 'avatars') expect(avatarsCount).to.equal(2) } + + // When we'll prune HLS directories too + // const hlsRootCount = await countFiles(servers[1], 'streaming-playlists/hls/') + // expect(hlsRootCount).to.equal(2) + + // const hlsCount = await countFiles(servers[1], 'streaming-playlists/hls/' + videoServer2UUID) + // expect(hlsCount).to.equal(10) } describe('Test prune storage scripts', function () { - let servers: ServerInfo[] + let servers: PeerTubeServer[] const badNames: { [directory: string]: string[] } = {} + let videoServer2UUID: string + before(async function () { this.timeout(120000) - servers = await flushAndRunMultipleServers(2, { transcoding: { enabled: true } }) + servers = await createMultipleServers(2, { transcoding: { enabled: true } }) await setAccessTokensToServers(servers) await setDefaultVideoChannel(servers) for (const server of servers) { - await uploadVideo(server.url, server.accessToken, { name: 'video 1' }) - await uploadVideo(server.url, server.accessToken, { name: 'video 2' }) + await server.videos.upload({ attributes: { name: 'video 1' } }) - await updateMyAvatar({ url: server.url, accessToken: server.accessToken, fixture: 'avatar.png' }) + const { uuid } = await server.videos.upload({ attributes: { name: 'video 2' } }) + if (server.serverNumber === 2) videoServer2UUID = uuid - await createVideoPlaylist({ - url: server.url, - token: server.accessToken, - playlistAttrs: { + await server.users.updateMyAvatar({ fixture: 'avatar.png' }) + + await server.playlists.create({ + attributes: { displayName: 'playlist', privacy: VideoPlaylistPrivacy.PUBLIC, - videoChannelId: server.videoChannel.id, + videoChannelId: server.store.channel.id, thumbnailfile: 'thumbnail.jpg' } }) @@ -95,41 +97,39 @@ describe('Test prune storage scripts', function () { // Lazy load the remote avatar { - const res = await getAccount(servers[0].url, 'root@localhost:' + servers[1].port) - const account: Account = res.body + const account = await servers[0].accounts.get({ accountName: 'root@localhost:' + servers[1].port }) await makeGetRequest({ url: servers[0].url, path: account.avatar.path, - statusCodeExpected: HttpStatusCode.OK_200 + expectedStatus: HttpStatusCode.OK_200 }) } { - const res = await getAccount(servers[1].url, 'root@localhost:' + servers[0].port) - const account: Account = res.body + const account = await servers[1].accounts.get({ accountName: 'root@localhost:' + servers[0].port }) await makeGetRequest({ url: servers[1].url, path: account.avatar.path, - statusCodeExpected: HttpStatusCode.OK_200 + expectedStatus: HttpStatusCode.OK_200 }) } await wait(1000) await waitJobs(servers) - killallServers(servers) + await killallServers(servers) await wait(1000) }) it('Should have the files on the disk', async function () { - await assertCountAreOkay(servers) + await assertCountAreOkay(servers, videoServer2UUID) }) it('Should create some dirty files', async function () { for (let i = 0; i < 2; i++) { { - const base = buildServerDirectory(servers[0], 'videos') + const base = servers[0].servers.buildDirectory('videos') const n1 = buildUUID() + '.mp4' const n2 = buildUUID() + '.webm' @@ -141,7 +141,7 @@ describe('Test prune storage scripts', function () { } { - const base = buildServerDirectory(servers[0], 'torrents') + const base = servers[0].servers.buildDirectory('torrents') const n1 = buildUUID() + '-240.torrent' const n2 = buildUUID() + '-480.torrent' @@ -153,7 +153,7 @@ describe('Test prune storage scripts', function () { } { - const base = buildServerDirectory(servers[0], 'thumbnails') + const base = servers[0].servers.buildDirectory('thumbnails') const n1 = buildUUID() + '.jpg' const n2 = buildUUID() + '.jpg' @@ -165,7 +165,7 @@ describe('Test prune storage scripts', function () { } { - const base = buildServerDirectory(servers[0], 'previews') + const base = servers[0].servers.buildDirectory('previews') const n1 = buildUUID() + '.jpg' const n2 = buildUUID() + '.jpg' @@ -177,7 +177,7 @@ describe('Test prune storage scripts', function () { } { - const base = buildServerDirectory(servers[0], 'avatars') + const base = servers[0].servers.buildDirectory('avatars') const n1 = buildUUID() + '.png' const n2 = buildUUID() + '.jpg' @@ -187,22 +187,44 @@ describe('Test prune storage scripts', function () { badNames['avatars'] = [ n1, n2 ] } + + // When we'll prune HLS directories too + // { + // const directory = join('streaming-playlists', 'hls') + // const base = servers[1].servers.buildDirectory(directory) + + // const n1 = buildUUID() + // await createFile(join(base, n1)) + // badNames[directory] = [ n1 ] + // } + + // { + // const directory = join('streaming-playlists', 'hls', videoServer2UUID) + // const base = servers[1].servers.buildDirectory(directory) + // const n1 = buildUUID() + '-240-fragmented-.mp4' + // const n2 = buildUUID() + '-master.m3u8' + + // await createFile(join(base, n1)) + // await createFile(join(base, n2)) + + // badNames[directory] = [ n1, n2 ] + // } } }) it('Should run prune storage', async function () { this.timeout(30000) - const env = getEnvCli(servers[0]) - await execCLI(`echo y | ${env} npm run prune-storage`) + const env = servers[0].cli.getEnv() + await CLICommand.exec(`echo y | ${env} npm run prune-storage`) }) it('Should have removed files', async function () { - await assertCountAreOkay(servers) + await assertCountAreOkay(servers, videoServer2UUID) for (const directory of Object.keys(badNames)) { for (const name of badNames[directory]) { - await assertNotExists(servers[0].internalServerNumber, directory, name) + await assertNotExists(servers[0], directory, name) } } }) diff --git a/server/tests/cli/regenerate-thumbnails.ts b/server/tests/cli/regenerate-thumbnails.ts index 8acb9f263..780c9b4bd 100644 --- a/server/tests/cli/regenerate-thumbnails.ts +++ b/server/tests/cli/regenerate-thumbnails.ts @@ -2,36 +2,33 @@ import 'mocha' import { expect } from 'chai' import { writeFile } from 'fs-extra' import { basename, join } from 'path' -import { Video, VideoDetails } from '@shared/models' +import { HttpStatusCode, Video } from '@shared/models' import { - buildServerDirectory, cleanupTests, + createMultipleServers, doubleFollow, - execCLI, - flushAndRunMultipleServers, - getEnvCli, - getVideo, makeRawRequest, - ServerInfo, + PeerTubeServer, setAccessTokensToServers, - uploadVideoAndGetId, waitJobs } from '../../../shared/extra-utils' -import { HttpStatusCode } from '@shared/core-utils' -async function testThumbnail (server: ServerInfo, videoId: number | string) { - const res = await getVideo(server.url, videoId) - const video: VideoDetails = res.body +async function testThumbnail (server: PeerTubeServer, videoId: number | string) { + const video = await server.videos.get({ id: videoId }) - const res1 = await makeRawRequest(join(server.url, video.thumbnailPath), HttpStatusCode.OK_200) - expect(res1.body).to.not.have.lengthOf(0) + const requests = [ + makeRawRequest(join(server.url, video.thumbnailPath), HttpStatusCode.OK_200), + makeRawRequest(join(server.url, video.thumbnailPath), HttpStatusCode.OK_200) + ] - const res2 = await makeRawRequest(join(server.url, video.thumbnailPath), HttpStatusCode.OK_200) - expect(res2.body).to.not.have.lengthOf(0) + for (const req of requests) { + const res = await req + expect(res.body).to.not.have.lengthOf(0) + } } describe('Test regenerate thumbnails script', function () { - let servers: ServerInfo[] + let servers: PeerTubeServer[] let video1: Video let video2: Video @@ -43,28 +40,28 @@ describe('Test regenerate thumbnails script', function () { before(async function () { this.timeout(60000) - servers = await flushAndRunMultipleServers(2) + servers = await createMultipleServers(2) await setAccessTokensToServers(servers) await doubleFollow(servers[0], servers[1]) { - const videoUUID1 = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video 1' })).uuid - video1 = await (getVideo(servers[0].url, videoUUID1).then(res => res.body)) + const videoUUID1 = (await servers[0].videos.quickUpload({ name: 'video 1' })).uuid + video1 = await servers[0].videos.get({ id: videoUUID1 }) - thumbnail1Path = join(buildServerDirectory(servers[0], 'thumbnails'), basename(video1.thumbnailPath)) + thumbnail1Path = join(servers[0].servers.buildDirectory('thumbnails'), basename(video1.thumbnailPath)) - const videoUUID2 = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video 2' })).uuid - video2 = await (getVideo(servers[0].url, videoUUID2).then(res => res.body)) + const videoUUID2 = (await servers[0].videos.quickUpload({ name: 'video 2' })).uuid + video2 = await servers[0].videos.get({ id: videoUUID2 }) } { - const videoUUID = (await uploadVideoAndGetId({ server: servers[1], videoName: 'video 3' })).uuid + const videoUUID = (await servers[1].videos.quickUpload({ name: 'video 3' })).uuid await waitJobs(servers) - remoteVideo = await (getVideo(servers[0].url, videoUUID).then(res => res.body)) + remoteVideo = await servers[0].videos.get({ id: videoUUID }) - thumbnailRemotePath = join(buildServerDirectory(servers[0], 'thumbnails'), basename(remoteVideo.thumbnailPath)) + thumbnailRemotePath = join(servers[0].servers.buildDirectory('thumbnails'), basename(remoteVideo.thumbnailPath)) } await writeFile(thumbnail1Path, '') @@ -91,8 +88,7 @@ describe('Test regenerate thumbnails script', function () { it('Should regenerate local thumbnails from the CLI', async function () { this.timeout(15000) - const env = getEnvCli(servers[0]) - await execCLI(`${env} npm run regenerate-thumbnails`) + await servers[0].cli.execWithEnv(`npm run regenerate-thumbnails`) }) it('Should have generated new thumbnail files', async function () { diff --git a/server/tests/cli/reset-password.ts b/server/tests/cli/reset-password.ts index a84463b33..4a02db35d 100644 --- a/server/tests/cli/reset-password.ts +++ b/server/tests/cli/reset-password.ts @@ -1,35 +1,24 @@ import 'mocha' - -import { - cleanupTests, - createUser, - execCLI, - flushAndRunServer, - getEnvCli, - login, - ServerInfo, - setAccessTokensToServers -} from '../../../shared/extra-utils' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' +import { cleanupTests, CLICommand, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '../../../shared/extra-utils' describe('Test reset password scripts', function () { - let server: ServerInfo + let server: PeerTubeServer before(async function () { this.timeout(30000) - server = await flushAndRunServer(1) + server = await createSingleServer(1) await setAccessTokensToServers([ server ]) - await createUser({ url: server.url, accessToken: server.accessToken, username: 'user_1', password: 'super password' }) + await server.users.create({ username: 'user_1', password: 'super password' }) }) it('Should change the user password from CLI', async function () { this.timeout(60000) - const env = getEnvCli(server) - await execCLI(`echo coucou | ${env} npm run reset-password -- -u user_1`) + const env = server.cli.getEnv() + await CLICommand.exec(`echo coucou | ${env} npm run reset-password -- -u user_1`) - await login(server.url, server.client, { username: 'user_1', password: 'coucou' }, HttpStatusCode.OK_200) + await server.login.login({ user: { username: 'user_1', password: 'coucou' } }) }) after(async function () { diff --git a/server/tests/cli/update-host.ts b/server/tests/cli/update-host.ts index 2070f16f5..43fbaec30 100644 --- a/server/tests/cli/update-host.ts +++ b/server/tests/cli/update-host.ts @@ -1,33 +1,20 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ import 'mocha' -import * as chai from 'chai' -import { VideoDetails } from '../../../shared/models/videos' -import { waitJobs } from '../../../shared/extra-utils/server/jobs' -import { addVideoCommentThread } from '../../../shared/extra-utils/videos/video-comments' +import { expect } from 'chai' import { - addVideoChannel, cleanupTests, - createUser, - execCLI, - flushAndRunServer, - getEnvCli, - getVideo, - getVideoChannelsList, - getVideosList, + createSingleServer, killallServers, makeActivityPubGetRequest, - parseTorrentVideo, reRunServer, - ServerInfo, + parseTorrentVideo, + PeerTubeServer, setAccessTokensToServers, - uploadVideo -} from '../../../shared/extra-utils' -import { getAccountsList } from '../../../shared/extra-utils/users/accounts' - -const expect = chai.expect + waitJobs +} from '@shared/extra-utils' describe('Test update host scripts', function () { - let server: ServerInfo + let server: PeerTubeServer before(async function () { this.timeout(60000) @@ -38,17 +25,15 @@ describe('Test update host scripts', function () { } } // Run server 2 to have transcoding enabled - server = await flushAndRunServer(2, overrideConfig) + server = await createSingleServer(2, overrideConfig) await setAccessTokensToServers([ server ]) // Upload two videos for our needs - const videoAttributes = {} - const resVideo1 = await uploadVideo(server.url, server.accessToken, videoAttributes) - const video1UUID = resVideo1.body.video.uuid - await uploadVideo(server.url, server.accessToken, videoAttributes) + const { uuid: video1UUID } = await server.videos.upload() + await server.videos.upload() // Create a user - await createUser({ url: server.url, accessToken: server.accessToken, username: 'toto', password: 'coucou' }) + await server.users.create({ username: 'toto', password: 'coucou' }) // Create channel const videoChannel = { @@ -56,11 +41,11 @@ describe('Test update host scripts', function () { displayName: 'second video channel', description: 'super video channel description' } - await addVideoChannel(server.url, server.accessToken, videoChannel) + await server.channels.create({ attributes: videoChannel }) // Create comments const text = 'my super first comment' - await addVideoCommentThread(server.url, server.accessToken, video1UUID, text) + await server.comments.createThread({ videoId: video1UUID, text }) await waitJobs(server) }) @@ -68,25 +53,23 @@ describe('Test update host scripts', function () { it('Should run update host', async function () { this.timeout(30000) - killallServers([ server ]) + await killallServers([ server ]) // Run server with standard configuration - await reRunServer(server) + await server.run() - const env = getEnvCli(server) - await execCLI(`${env} npm run update-host`) + await server.cli.execWithEnv(`npm run update-host`) }) it('Should have updated videos url', async function () { - const res = await getVideosList(server.url) - expect(res.body.total).to.equal(2) + const { total, data } = await server.videos.list() + expect(total).to.equal(2) - for (const video of res.body.data) { + for (const video of data) { const { body } = await makeActivityPubGetRequest(server.url, '/videos/watch/' + video.uuid) expect(body.id).to.equal('http://localhost:9002/videos/watch/' + video.uuid) - const res = await getVideo(server.url, video.uuid) - const videoDetails: VideoDetails = res.body + const videoDetails = await server.videos.get({ id: video.uuid }) expect(videoDetails.trackerUrls[0]).to.include(server.host) expect(videoDetails.streamingPlaylists[0].playlistUrl).to.include(server.host) @@ -95,10 +78,10 @@ describe('Test update host scripts', function () { }) it('Should have updated video channels url', async function () { - const res = await getVideoChannelsList(server.url, 0, 5, '-name') - expect(res.body.total).to.equal(3) + const { data, total } = await server.channels.list({ sort: '-name' }) + expect(total).to.equal(3) - for (const channel of res.body.data) { + for (const channel of data) { const { body } = await makeActivityPubGetRequest(server.url, '/video-channels/' + channel.name) expect(body.id).to.equal('http://localhost:9002/video-channels/' + channel.name) @@ -106,10 +89,10 @@ describe('Test update host scripts', function () { }) it('Should have updated accounts url', async function () { - const res = await getAccountsList(server.url) - expect(res.body.total).to.equal(3) + const body = await server.accounts.list() + expect(body.total).to.equal(3) - for (const account of res.body.data) { + for (const account of body.data) { const usernameWithDomain = account.name const { body } = await makeActivityPubGetRequest(server.url, '/accounts/' + usernameWithDomain) @@ -120,28 +103,27 @@ describe('Test update host scripts', function () { it('Should have updated torrent hosts', async function () { this.timeout(30000) - const res = await getVideosList(server.url) - const videos = res.body.data - expect(videos).to.have.lengthOf(2) + const { data } = await server.videos.list() + expect(data).to.have.lengthOf(2) - for (const video of videos) { - const res2 = await getVideo(server.url, video.id) - const videoDetails: VideoDetails = res2.body + for (const video of data) { + const videoDetails = await server.videos.get({ id: video.id }) + const files = videoDetails.files.concat(videoDetails.streamingPlaylists[0].files) - expect(videoDetails.files).to.have.lengthOf(4) + expect(files).to.have.lengthOf(8) - for (const file of videoDetails.files) { + for (const file of files) { expect(file.magnetUri).to.contain('localhost%3A9002%2Ftracker%2Fsocket') - expect(file.magnetUri).to.contain('localhost%3A9002%2Fstatic%2Fwebseed%2F') + expect(file.magnetUri).to.contain('localhost%3A9002%2Fstatic%2F') - const torrent = await parseTorrentVideo(server, videoDetails.uuid, file.resolution.id) + const torrent = await parseTorrentVideo(server, file) const announceWS = torrent.announce.find(a => a === 'ws://localhost:9002/tracker/socket') expect(announceWS).to.not.be.undefined const announceHttp = torrent.announce.find(a => a === 'http://localhost:9002/tracker/announce') expect(announceHttp).to.not.be.undefined - expect(torrent.urlList[0]).to.contain('http://localhost:9002/static/webseed') + expect(torrent.urlList[0]).to.contain('http://localhost:9002/static/') } } }) diff --git a/server/tests/client.ts b/server/tests/client.ts index 7c4fb4e46..4cbdb2cb3 100644 --- a/server/tests/client.ts +++ b/server/tests/client.ts @@ -3,28 +3,16 @@ import 'mocha' import * as chai from 'chai' import { omit } from 'lodash' -import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' -import { Account, CustomConfig, HTMLServerConfig, ServerConfig, VideoPlaylistCreateResult, VideoPlaylistPrivacy } from '@shared/models' +import { Account, HTMLServerConfig, HttpStatusCode, ServerConfig, VideoPlaylistCreateResult, VideoPlaylistPrivacy } from '@shared/models' import { - addVideoInPlaylist, cleanupTests, - createVideoPlaylist, + createMultipleServers, doubleFollow, - flushAndRunMultipleServers, - getAccount, - getConfig, - getCustomConfig, - getVideosList, makeGetRequest, makeHTMLRequest, - ServerInfo, + PeerTubeServer, setAccessTokensToServers, setDefaultVideoChannel, - updateCustomConfig, - updateCustomSubConfig, - updateMyUser, - updateVideoChannel, - uploadVideo, waitJobs } from '../../shared/extra-utils' @@ -40,7 +28,7 @@ function checkIndexTags (html: string, title: string, description: string, css: } describe('Test a client controllers', function () { - let servers: ServerInfo[] = [] + let servers: PeerTubeServer[] = [] let account: Account const videoName = 'my super name for server 1' @@ -62,7 +50,7 @@ describe('Test a client controllers', function () { before(async function () { this.timeout(120000) - servers = await flushAndRunMultipleServers(2) + servers = await createMultipleServers(2) await setAccessTokensToServers(servers) @@ -70,47 +58,48 @@ describe('Test a client controllers', function () { await setDefaultVideoChannel(servers) - await updateVideoChannel(servers[0].url, servers[0].accessToken, servers[0].videoChannel.name, { description: channelDescription }) + await servers[0].channels.update({ + channelName: servers[0].store.channel.name, + attributes: { description: channelDescription } + }) // Video - const videoAttributes = { name: videoName, description: videoDescription } - await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes) + { + const attributes = { name: videoName, description: videoDescription } + await servers[0].videos.upload({ attributes }) - const resVideosRequest = await getVideosList(servers[0].url) - const videos = resVideosRequest.body.data - expect(videos.length).to.equal(1) + const { data } = await servers[0].videos.list() + expect(data.length).to.equal(1) - const video = videos[0] - servers[0].video = video - videoIds = [ video.id, video.uuid, video.shortUUID ] + const video = data[0] + servers[0].store.video = video + videoIds = [ video.id, video.uuid, video.shortUUID ] + } // Playlist - const playlistAttrs = { - displayName: playlistName, - description: playlistDescription, - privacy: VideoPlaylistPrivacy.PUBLIC, - videoChannelId: servers[0].videoChannel.id - } + { + const attributes = { + displayName: playlistName, + description: playlistDescription, + privacy: VideoPlaylistPrivacy.PUBLIC, + videoChannelId: servers[0].store.channel.id + } - const resVideoPlaylistRequest = await createVideoPlaylist({ url: servers[0].url, token: servers[0].accessToken, playlistAttrs }) - playlist = resVideoPlaylistRequest.body.videoPlaylist - playlistIds = [ playlist.id, playlist.shortUUID, playlist.uuid ] + playlist = await servers[0].playlists.create({ attributes }) + playlistIds = [ playlist.id, playlist.shortUUID, playlist.uuid ] - await addVideoInPlaylist({ - url: servers[0].url, - token: servers[0].accessToken, - playlistId: playlist.shortUUID, - elementAttrs: { videoId: video.id } - }) + await servers[0].playlists.addElement({ playlistId: playlist.shortUUID, attributes: { videoId: servers[0].store.video.id } }) + } // Account - await updateMyUser({ url: servers[0].url, accessToken: servers[0].accessToken, description: 'my account description' }) + { + await servers[0].users.updateMe({ description: 'my account description' }) - const resAccountRequest = await getAccount(servers[0].url, `${servers[0].user.username}@${servers[0].host}`) - account = resAccountRequest.body + account = await servers[0].accounts.get({ accountName: `${servers[0].store.user.username}@${servers[0].host}` }) + } await waitJobs(servers) }) @@ -124,14 +113,14 @@ describe('Test a client controllers', function () { url: servers[0].url, path: basePath + id, accept: 'text/html', - statusCodeExpected: HttpStatusCode.OK_200 + expectedStatus: HttpStatusCode.OK_200 }) const port = servers[0].port const expectedLink = '` + `url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2F${servers[0].store.video.shortUUID}" ` + + `title="${servers[0].store.video.name}" />` expect(res.text).to.contain(expectedLink) } @@ -145,13 +134,13 @@ describe('Test a client controllers', function () { url: servers[0].url, path: basePath + id, accept: 'text/html', - statusCodeExpected: HttpStatusCode.OK_200 + expectedStatus: HttpStatusCode.OK_200 }) const port = servers[0].port const expectedLink = '` expect(res.text).to.contain(expectedLink) @@ -163,55 +152,55 @@ describe('Test a client controllers', function () { describe('Open Graph', function () { async function accountPageTest (path: string) { - const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 }) + const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 }) const text = res.text expect(text).to.contain(``) expect(text).to.contain(``) expect(text).to.contain('') - expect(text).to.contain(``) + expect(text).to.contain(``) } async function channelPageTest (path: string) { - const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 }) + const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 }) const text = res.text - expect(text).to.contain(``) + expect(text).to.contain(``) expect(text).to.contain(``) expect(text).to.contain('') - expect(text).to.contain(``) + expect(text).to.contain(``) } async function watchVideoPageTest (path: string) { - const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 }) + const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 }) const text = res.text expect(text).to.contain(``) expect(text).to.contain(``) expect(text).to.contain('') - expect(text).to.contain(``) + expect(text).to.contain(``) } async function watchPlaylistPageTest (path: string) { - const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 }) + const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 }) const text = res.text expect(text).to.contain(``) expect(text).to.contain(``) expect(text).to.contain('') - expect(text).to.contain(``) + expect(text).to.contain(``) } it('Should have valid Open Graph tags on the account page', async function () { - await accountPageTest('/accounts/' + servers[0].user.username) - await accountPageTest('/a/' + servers[0].user.username) - await accountPageTest('/@' + servers[0].user.username) + await accountPageTest('/accounts/' + servers[0].store.user.username) + await accountPageTest('/a/' + servers[0].store.user.username) + await accountPageTest('/@' + servers[0].store.user.username) }) it('Should have valid Open Graph tags on the channel page', async function () { - await channelPageTest('/video-channels/' + servers[0].videoChannel.name) - await channelPageTest('/c/' + servers[0].videoChannel.name) - await channelPageTest('/@' + servers[0].videoChannel.name) + await channelPageTest('/video-channels/' + servers[0].store.channel.name) + await channelPageTest('/c/' + servers[0].store.channel.name) + await channelPageTest('/@' + servers[0].store.channel.name) }) it('Should have valid Open Graph tags on the watch page', async function () { @@ -236,7 +225,7 @@ describe('Test a client controllers', function () { describe('Not whitelisted', function () { async function accountPageTest (path: string) { - const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 }) + const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 }) const text = res.text expect(text).to.contain('') @@ -246,17 +235,17 @@ describe('Test a client controllers', function () { } async function channelPageTest (path: string) { - const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 }) + const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 }) const text = res.text expect(text).to.contain('') expect(text).to.contain('') - expect(text).to.contain(``) + expect(text).to.contain(``) expect(text).to.contain(``) } async function watchVideoPageTest (path: string) { - const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 }) + const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 }) const text = res.text expect(text).to.contain('') @@ -266,7 +255,7 @@ describe('Test a client controllers', function () { } async function watchPlaylistPageTest (path: string) { - const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 }) + const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 }) const text = res.text expect(text).to.contain('') @@ -298,27 +287,26 @@ describe('Test a client controllers', function () { }) it('Should have valid twitter card on the channel page', async function () { - await channelPageTest('/video-channels/' + servers[0].videoChannel.name) - await channelPageTest('/c/' + servers[0].videoChannel.name) - await channelPageTest('/@' + servers[0].videoChannel.name) + await channelPageTest('/video-channels/' + servers[0].store.channel.name) + await channelPageTest('/c/' + servers[0].store.channel.name) + await channelPageTest('/@' + servers[0].store.channel.name) }) }) describe('Whitelisted', function () { before(async function () { - const res = await getCustomConfig(servers[0].url, servers[0].accessToken) - const config = res.body as CustomConfig + const config = await servers[0].config.getCustomConfig() config.services.twitter = { username: '@Kuja', whitelisted: true } - await updateCustomConfig(servers[0].url, servers[0].accessToken, config) + await servers[0].config.updateCustomConfig({ newCustomConfig: config }) }) async function accountPageTest (path: string) { - const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 }) + const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 }) const text = res.text expect(text).to.contain('') @@ -326,7 +314,7 @@ describe('Test a client controllers', function () { } async function channelPageTest (path: string) { - const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 }) + const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 }) const text = res.text expect(text).to.contain('') @@ -334,7 +322,7 @@ describe('Test a client controllers', function () { } async function watchVideoPageTest (path: string) { - const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 }) + const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 }) const text = res.text expect(text).to.contain('') @@ -342,7 +330,7 @@ describe('Test a client controllers', function () { } async function watchPlaylistPageTest (path: string) { - const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 }) + const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 }) const text = res.text expect(text).to.contain('') @@ -372,9 +360,9 @@ describe('Test a client controllers', function () { }) it('Should have valid twitter card on the channel page', async function () { - await channelPageTest('/video-channels/' + servers[0].videoChannel.name) - await channelPageTest('/c/' + servers[0].videoChannel.name) - await channelPageTest('/@' + servers[0].videoChannel.name) + await channelPageTest('/video-channels/' + servers[0].store.channel.name) + await channelPageTest('/c/' + servers[0].store.channel.name) + await channelPageTest('/@' + servers[0].store.channel.name) }) }) }) @@ -382,53 +370,55 @@ describe('Test a client controllers', function () { describe('Index HTML', function () { it('Should have valid index html tags (title, description...)', async function () { - const resConfig = await getConfig(servers[0].url) + const config = await servers[0].config.getConfig() const res = await makeHTMLRequest(servers[0].url, '/videos/trending') const description = 'PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.' - checkIndexTags(res.text, 'PeerTube', description, '', resConfig.body) + checkIndexTags(res.text, 'PeerTube', description, '', config) }) it('Should update the customized configuration and have the correct index html tags', async function () { - await updateCustomSubConfig(servers[0].url, servers[0].accessToken, { - instance: { - name: 'PeerTube updated', - shortDescription: 'my short description', - description: 'my super description', - terms: 'my super terms', - defaultNSFWPolicy: 'blur', - defaultClientRoute: '/videos/recently-added', - customizations: { - javascript: 'alert("coucou")', - css: 'body { background-color: red; }' + await servers[0].config.updateCustomSubConfig({ + newConfig: { + instance: { + name: 'PeerTube updated', + shortDescription: 'my short description', + description: 'my super description', + terms: 'my super terms', + defaultNSFWPolicy: 'blur', + defaultClientRoute: '/videos/recently-added', + customizations: { + javascript: 'alert("coucou")', + css: 'body { background-color: red; }' + } } } }) - const resConfig = await getConfig(servers[0].url) + const config = await servers[0].config.getConfig() const res = await makeHTMLRequest(servers[0].url, '/videos/trending') - checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', resConfig.body) + checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config) }) it('Should have valid index html updated tags (title, description...)', async function () { - const resConfig = await getConfig(servers[0].url) + const config = await servers[0].config.getConfig() const res = await makeHTMLRequest(servers[0].url, '/videos/trending') - checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', resConfig.body) + checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config) }) it('Should use the original video URL for the canonical tag', async function () { for (const basePath of watchVideoBasePaths) { for (const id of videoIds) { const res = await makeHTMLRequest(servers[1].url, basePath + id) - expect(res.text).to.contain(``) + expect(res.text).to.contain(``) } } }) it('Should use the original account URL for the canonical tag', async function () { - const accountURLtest = (res) => { + const accountURLtest = res => { expect(res.text).to.contain(``) } @@ -438,7 +428,7 @@ describe('Test a client controllers', function () { }) it('Should use the original channel URL for the canonical tag', async function () { - const channelURLtests = (res) => { + const channelURLtests = res => { expect(res.text).to.contain(``) } @@ -460,10 +450,10 @@ describe('Test a client controllers', function () { describe('Embed HTML', function () { it('Should have the correct embed html tags', async function () { - const resConfig = await getConfig(servers[0].url) - const res = await makeHTMLRequest(servers[0].url, servers[0].video.embedPath) + const config = await servers[0].config.getConfig() + const res = await makeHTMLRequest(servers[0].url, servers[0].store.video.embedPath) - checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', resConfig.body) + checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config) }) }) diff --git a/server/tests/external-plugins/auth-ldap.ts b/server/tests/external-plugins/auth-ldap.ts index e4eae7e8c..acec69df5 100644 --- a/server/tests/external-plugins/auth-ldap.ts +++ b/server/tests/external-plugins/auth-ldap.ts @@ -2,46 +2,29 @@ import 'mocha' import { expect } from 'chai' -import { User } from '@shared/models/users/user.model' -import { - blockUser, - getMyUserInformation, - installPlugin, - setAccessTokensToServers, - unblockUser, - uninstallPlugin, - updatePluginSettings, - uploadVideo, - userLogin -} from '../../../shared/extra-utils' -import { cleanupTests, flushAndRunServer, ServerInfo } from '../../../shared/extra-utils/server/servers' +import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '@shared/extra-utils' +import { HttpStatusCode } from '@shared/models' describe('Official plugin auth-ldap', function () { - let server: ServerInfo + let server: PeerTubeServer let accessToken: string let userId: number before(async function () { this.timeout(30000) - server = await flushAndRunServer(1) + server = await createSingleServer(1) await setAccessTokensToServers([ server ]) - await installPlugin({ - url: server.url, - accessToken: server.accessToken, - npmName: 'peertube-plugin-auth-ldap' - }) + await server.plugins.install({ npmName: 'peertube-plugin-auth-ldap' }) }) it('Should not login with without LDAP settings', async function () { - await userLogin(server, { username: 'fry', password: 'fry' }, 400) + await server.login.login({ user: { username: 'fry', password: 'fry' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) }) it('Should not login with bad LDAP settings', async function () { - await updatePluginSettings({ - url: server.url, - accessToken: server.accessToken, + await server.plugins.updateSettings({ npmName: 'peertube-plugin-auth-ldap', settings: { 'bind-credentials': 'GoodNewsEveryone', @@ -55,13 +38,11 @@ describe('Official plugin auth-ldap', function () { } }) - await userLogin(server, { username: 'fry', password: 'fry' }, 400) + await server.login.login({ user: { username: 'fry', password: 'fry' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) }) it('Should not login with good LDAP settings but wrong username/password', async function () { - await updatePluginSettings({ - url: server.url, - accessToken: server.accessToken, + await server.plugins.updateSettings({ npmName: 'peertube-plugin-auth-ldap', settings: { 'bind-credentials': 'GoodNewsEveryone', @@ -75,22 +56,20 @@ describe('Official plugin auth-ldap', function () { } }) - await userLogin(server, { username: 'fry', password: 'bad password' }, 400) - await userLogin(server, { username: 'fryr', password: 'fry' }, 400) + await server.login.login({ user: { username: 'fry', password: 'bad password' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) + await server.login.login({ user: { username: 'fryr', password: 'fry' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) }) it('Should login with the appropriate username/password', async function () { - accessToken = await userLogin(server, { username: 'fry', password: 'fry' }) + accessToken = await server.login.getAccessToken({ username: 'fry', password: 'fry' }) }) it('Should login with the appropriate email/password', async function () { - accessToken = await userLogin(server, { username: 'fry@planetexpress.com', password: 'fry' }) + accessToken = await server.login.getAccessToken({ username: 'fry@planetexpress.com', password: 'fry' }) }) it('Should login get my profile', async function () { - const res = await getMyUserInformation(server.url, accessToken) - const body: User = res.body - + const body = await server.users.getMyInfo({ token: accessToken }) expect(body.username).to.equal('fry') expect(body.email).to.equal('fry@planetexpress.com') @@ -98,25 +77,31 @@ describe('Official plugin auth-ldap', function () { }) it('Should upload a video', async function () { - await uploadVideo(server.url, accessToken, { name: 'my super video' }) + await server.videos.upload({ token: accessToken, attributes: { name: 'my super video' } }) }) it('Should not be able to login if the user is banned', async function () { - await blockUser(server.url, userId, server.accessToken) + await server.users.banUser({ userId }) - await userLogin(server, { username: 'fry@planetexpress.com', password: 'fry' }, 400) + await server.login.login({ + user: { username: 'fry@planetexpress.com', password: 'fry' }, + expectedStatus: HttpStatusCode.BAD_REQUEST_400 + }) }) it('Should be able to login if the user is unbanned', async function () { - await unblockUser(server.url, userId, server.accessToken) + await server.users.unbanUser({ userId }) - await userLogin(server, { username: 'fry@planetexpress.com', password: 'fry' }) + await server.login.login({ user: { username: 'fry@planetexpress.com', password: 'fry' } }) }) it('Should not login if the plugin is uninstalled', async function () { - await uninstallPlugin({ url: server.url, accessToken: server.accessToken, npmName: 'peertube-plugin-auth-ldap' }) + await server.plugins.uninstall({ npmName: 'peertube-plugin-auth-ldap' }) - await userLogin(server, { username: 'fry@planetexpress.com', password: 'fry' }, 400) + await server.login.login({ + user: { username: 'fry@planetexpress.com', password: 'fry' }, + expectedStatus: HttpStatusCode.BAD_REQUEST_400 + }) }) after(async function () { diff --git a/server/tests/external-plugins/auto-block-videos.ts b/server/tests/external-plugins/auto-block-videos.ts index 18ea17d78..0eb4bda9a 100644 --- a/server/tests/external-plugins/auto-block-videos.ts +++ b/server/tests/external-plugins/auto-block-videos.ts @@ -2,41 +2,29 @@ import 'mocha' import { expect } from 'chai' -import { Video, VideoBlacklist } from '@shared/models' import { + cleanupTests, + createMultipleServers, doubleFollow, - getBlacklistedVideosList, - getVideosList, - installPlugin, + killallServers, MockBlocklist, - removeVideoFromBlacklist, + PeerTubeServer, setAccessTokensToServers, - updatePluginSettings, - uploadVideoAndGetId, wait -} from '../../../shared/extra-utils' -import { - cleanupTests, - flushAndRunMultipleServers, - killallServers, - reRunServer, - ServerInfo -} from '../../../shared/extra-utils/server/servers' +} from '@shared/extra-utils' +import { Video } from '@shared/models' -async function check (server: ServerInfo, videoUUID: string, exists = true) { - const res = await getVideosList(server.url) +async function check (server: PeerTubeServer, videoUUID: string, exists = true) { + const { data } = await server.videos.list() - const video = res.body.data.find(v => v.uuid === videoUUID) + const video = data.find(v => v.uuid === videoUUID) - if (exists) { - expect(video).to.not.be.undefined - } else { - expect(video).to.be.undefined - } + if (exists) expect(video).to.not.be.undefined + else expect(video).to.be.undefined } describe('Official plugin auto-block videos', function () { - let servers: ServerInfo[] + let servers: PeerTubeServer[] let blocklistServer: MockBlocklist let server1Videos: Video[] = [] let server2Videos: Video[] = [] @@ -45,42 +33,36 @@ describe('Official plugin auto-block videos', function () { before(async function () { this.timeout(60000) - servers = await flushAndRunMultipleServers(2) + servers = await createMultipleServers(2) await setAccessTokensToServers(servers) for (const server of servers) { - await installPlugin({ - url: server.url, - accessToken: server.accessToken, - npmName: 'peertube-plugin-auto-block-videos' - }) + await server.plugins.install({ npmName: 'peertube-plugin-auto-block-videos' }) } blocklistServer = new MockBlocklist() port = await blocklistServer.initialize() - await uploadVideoAndGetId({ server: servers[0], videoName: 'video server 1' }) - await uploadVideoAndGetId({ server: servers[1], videoName: 'video server 2' }) - await uploadVideoAndGetId({ server: servers[1], videoName: 'video 2 server 2' }) - await uploadVideoAndGetId({ server: servers[1], videoName: 'video 3 server 2' }) + await servers[0].videos.quickUpload({ name: 'video server 1' }) + await servers[1].videos.quickUpload({ name: 'video server 2' }) + await servers[1].videos.quickUpload({ name: 'video 2 server 2' }) + await servers[1].videos.quickUpload({ name: 'video 3 server 2' }) { - const res = await getVideosList(servers[0].url) - server1Videos = res.body.data.map(v => Object.assign(v, { url: servers[0].url + '/videos/watch/' + v.uuid })) + const { data } = await servers[0].videos.list() + server1Videos = data.map(v => Object.assign(v, { url: servers[0].url + '/videos/watch/' + v.uuid })) } { - const res = await getVideosList(servers[1].url) - server2Videos = res.body.data.map(v => Object.assign(v, { url: servers[1].url + '/videos/watch/' + v.uuid })) + const { data } = await servers[1].videos.list() + server2Videos = data.map(v => Object.assign(v, { url: servers[1].url + '/videos/watch/' + v.uuid })) } await doubleFollow(servers[0], servers[1]) }) it('Should update plugin settings', async function () { - await updatePluginSettings({ - url: servers[0].url, - accessToken: servers[0].accessToken, + await servers[0].plugins.updateSettings({ npmName: 'peertube-plugin-auto-block-videos', settings: { 'blocklist-urls': `http://localhost:${port}/blocklist`, @@ -108,10 +90,9 @@ describe('Official plugin auto-block videos', function () { }) it('Should have video in blacklists', async function () { - const res = await getBlacklistedVideosList({ url: servers[0].url, token: servers[0].accessToken }) - - const videoBlacklists = res.body.data as VideoBlacklist[] + const body = await servers[0].blacklist.list() + const videoBlacklists = body.data expect(videoBlacklists).to.have.lengthOf(1) expect(videoBlacklists[0].reason).to.contains('Automatically blocked from auto block plugin') expect(videoBlacklists[0].video.name).to.equal(server2Videos[0].name) @@ -174,12 +155,12 @@ describe('Official plugin auto-block videos', function () { await check(servers[0], video.uuid, false) - await removeVideoFromBlacklist(servers[0].url, servers[0].accessToken, video.uuid) + await servers[0].blacklist.remove({ videoId: video.uuid }) await check(servers[0], video.uuid, true) - killallServers([ servers[0] ]) - await reRunServer(servers[0]) + await killallServers([ servers[0] ]) + await servers[0].run() await wait(2000) await check(servers[0], video.uuid, true) diff --git a/server/tests/external-plugins/auto-mute.ts b/server/tests/external-plugins/auto-mute.ts index 09355d932..271779dd4 100644 --- a/server/tests/external-plugins/auto-mute.ts +++ b/server/tests/external-plugins/auto-mute.ts @@ -3,63 +3,45 @@ import 'mocha' import { expect } from 'chai' import { - addAccountToServerBlocklist, - addServerToAccountBlocklist, - removeAccountFromServerBlocklist -} from '@shared/extra-utils/users/blocklist' -import { + cleanupTests, + createMultipleServers, doubleFollow, - getVideosList, - installPlugin, + killallServers, makeGetRequest, MockBlocklist, + PeerTubeServer, setAccessTokensToServers, - updatePluginSettings, - uploadVideoAndGetId, wait -} from '../../../shared/extra-utils' -import { - cleanupTests, - flushAndRunMultipleServers, - killallServers, - reRunServer, - ServerInfo -} from '../../../shared/extra-utils/server/servers' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' +} from '@shared/extra-utils' +import { HttpStatusCode } from '@shared/models' describe('Official plugin auto-mute', function () { const autoMuteListPath = '/plugins/auto-mute/router/api/v1/mute-list' - let servers: ServerInfo[] + let servers: PeerTubeServer[] let blocklistServer: MockBlocklist let port: number before(async function () { this.timeout(30000) - servers = await flushAndRunMultipleServers(2) + servers = await createMultipleServers(2) await setAccessTokensToServers(servers) for (const server of servers) { - await installPlugin({ - url: server.url, - accessToken: server.accessToken, - npmName: 'peertube-plugin-auto-mute' - }) + await server.plugins.install({ npmName: 'peertube-plugin-auto-mute' }) } blocklistServer = new MockBlocklist() port = await blocklistServer.initialize() - await uploadVideoAndGetId({ server: servers[0], videoName: 'video server 1' }) - await uploadVideoAndGetId({ server: servers[1], videoName: 'video server 2' }) + await servers[0].videos.quickUpload({ name: 'video server 1' }) + await servers[1].videos.quickUpload({ name: 'video server 2' }) await doubleFollow(servers[0], servers[1]) }) it('Should update plugin settings', async function () { - await updatePluginSettings({ - url: servers[0].url, - accessToken: servers[0].accessToken, + await servers[0].plugins.updateSettings({ npmName: 'peertube-plugin-auto-mute', settings: { 'blocklist-urls': `http://localhost:${port}/blocklist`, @@ -81,8 +63,8 @@ describe('Official plugin auto-mute', function () { await wait(2000) - const res = await getVideosList(servers[0].url) - expect(res.body.total).to.equal(1) + const { total } = await servers[0].videos.list() + expect(total).to.equal(1) }) it('Should remove a server blocklist', async function () { @@ -99,8 +81,8 @@ describe('Official plugin auto-mute', function () { await wait(2000) - const res = await getVideosList(servers[0].url) - expect(res.body.total).to.equal(2) + const { total } = await servers[0].videos.list() + expect(total).to.equal(2) }) it('Should add an account blocklist', async function () { @@ -116,8 +98,8 @@ describe('Official plugin auto-mute', function () { await wait(2000) - const res = await getVideosList(servers[0].url) - expect(res.body.total).to.equal(1) + const { total } = await servers[0].videos.list() + expect(total).to.equal(1) }) it('Should remove an account blocklist', async function () { @@ -134,8 +116,8 @@ describe('Official plugin auto-mute', function () { await wait(2000) - const res = await getVideosList(servers[0].url) - expect(res.body.total).to.equal(2) + const { total } = await servers[0].videos.list() + expect(total).to.equal(2) }) it('Should auto mute an account, manually unmute it and do not remute it automatically', async function () { @@ -155,24 +137,24 @@ describe('Official plugin auto-mute', function () { await wait(2000) { - const res = await getVideosList(servers[0].url) - expect(res.body.total).to.equal(1) + const { total } = await servers[0].videos.list() + expect(total).to.equal(1) } - await removeAccountFromServerBlocklist(servers[0].url, servers[0].accessToken, account) + await servers[0].blocklist.removeFromServerBlocklist({ account }) { - const res = await getVideosList(servers[0].url) - expect(res.body.total).to.equal(2) + const { total } = await servers[0].videos.list() + expect(total).to.equal(2) } - killallServers([ servers[0] ]) - await reRunServer(servers[0]) + await killallServers([ servers[0] ]) + await servers[0].run() await wait(2000) { - const res = await getVideosList(servers[0].url) - expect(res.body.total).to.equal(2) + const { total } = await servers[0].videos.list() + expect(total).to.equal(2) } }) @@ -180,14 +162,12 @@ describe('Official plugin auto-mute', function () { await makeGetRequest({ url: servers[0].url, path: '/plugins/auto-mute/router/api/v1/mute-list', - statusCodeExpected: HttpStatusCode.FORBIDDEN_403 + expectedStatus: HttpStatusCode.FORBIDDEN_403 }) }) it('Should enable auto mute list', async function () { - await updatePluginSettings({ - url: servers[0].url, - accessToken: servers[0].accessToken, + await servers[0].plugins.updateSettings({ npmName: 'peertube-plugin-auto-mute', settings: { 'blocklist-urls': '', @@ -199,16 +179,14 @@ describe('Official plugin auto-mute', function () { await makeGetRequest({ url: servers[0].url, path: '/plugins/auto-mute/router/api/v1/mute-list', - statusCodeExpected: HttpStatusCode.OK_200 + expectedStatus: HttpStatusCode.OK_200 }) }) it('Should mute an account on server 1, and server 2 auto mutes it', async function () { this.timeout(20000) - await updatePluginSettings({ - url: servers[1].url, - accessToken: servers[1].accessToken, + await servers[1].plugins.updateSettings({ npmName: 'peertube-plugin-auto-mute', settings: { 'blocklist-urls': 'http://localhost:' + servers[0].port + autoMuteListPath, @@ -217,13 +195,13 @@ describe('Official plugin auto-mute', function () { } }) - await addAccountToServerBlocklist(servers[0].url, servers[0].accessToken, 'root@localhost:' + servers[1].port) - await addServerToAccountBlocklist(servers[0].url, servers[0].accessToken, 'localhost:' + servers[1].port) + await servers[0].blocklist.addToServerBlocklist({ account: 'root@localhost:' + servers[1].port }) + await servers[0].blocklist.addToMyBlocklist({ server: 'localhost:' + servers[1].port }) const res = await makeGetRequest({ url: servers[0].url, path: '/plugins/auto-mute/router/api/v1/mute-list', - statusCodeExpected: HttpStatusCode.OK_200 + expectedStatus: HttpStatusCode.OK_200 }) const data = res.body.data @@ -234,8 +212,8 @@ describe('Official plugin auto-mute', function () { await wait(2000) for (const server of servers) { - const res = await getVideosList(server.url) - expect(res.body.total).to.equal(1) + const { total } = await server.videos.list() + expect(total).to.equal(1) } }) diff --git a/server/tests/feeds/feeds.ts b/server/tests/feeds/feeds.ts index 7bad81751..55b434846 100644 --- a/server/tests/feeds/feeds.ts +++ b/server/tests/feeds/feeds.ts @@ -3,35 +3,17 @@ import 'mocha' import * as chai from 'chai' import * as xmlParser from 'fast-xml-parser' -import { - addAccountToAccountBlocklist, - addAccountToServerBlocklist, - removeAccountFromServerBlocklist -} from '@shared/extra-utils/users/blocklist' -import { addUserSubscription, listUserSubscriptionVideos } from '@shared/extra-utils/users/user-subscriptions' -import { VideoPrivacy } from '@shared/models' -import { ScopedToken } from '@shared/models/users/user-scoped-token' import { cleanupTests, - createUser, + createMultipleServers, + createSingleServer, doubleFollow, - flushAndRunMultipleServers, - flushAndRunServer, - getJSONfeed, - getMyUserInformation, - getUserScopedTokens, - getXMLfeed, - renewUserScopedTokens, - ServerInfo, + makeGetRequest, + PeerTubeServer, setAccessTokensToServers, - uploadVideo, - uploadVideoAndGetId, - userLogin -} from '../../../shared/extra-utils' -import { waitJobs } from '../../../shared/extra-utils/server/jobs' -import { addVideoCommentThread } from '../../../shared/extra-utils/videos/video-comments' -import { User } from '../../../shared/models/users' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' + waitJobs +} from '@shared/extra-utils' +import { HttpStatusCode, VideoPrivacy } from '@shared/models' chai.use(require('chai-xml')) chai.use(require('chai-json-schema')) @@ -39,8 +21,8 @@ chai.config.includeStack = true const expect = chai.expect describe('Test syndication feeds', () => { - let servers: ServerInfo[] = [] - let serverHLSOnly: ServerInfo + let servers: PeerTubeServer[] = [] + let serverHLSOnly: PeerTubeServer let userAccessToken: string let rootAccountId: number let rootChannelId: number @@ -52,8 +34,8 @@ describe('Test syndication feeds', () => { this.timeout(120000) // Run servers - servers = await flushAndRunMultipleServers(2) - serverHLSOnly = await flushAndRunServer(3, { + servers = await createMultipleServers(2) + serverHLSOnly = await createSingleServer(3, { transcoding: { enabled: true, webtorrent: { enabled: false }, @@ -65,50 +47,43 @@ describe('Test syndication feeds', () => { await doubleFollow(servers[0], servers[1]) { - const res = await getMyUserInformation(servers[0].url, servers[0].accessToken) - const user: User = res.body + const user = await servers[0].users.getMyInfo() rootAccountId = user.account.id rootChannelId = user.videoChannels[0].id } { - const attr = { username: 'john', password: 'password' } - await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: attr.username, password: attr.password }) - userAccessToken = await userLogin(servers[0], attr) + userAccessToken = await servers[0].users.generateUserAndToken('john') - const res = await getMyUserInformation(servers[0].url, userAccessToken) - const user: User = res.body + const user = await servers[0].users.getMyInfo({ token: userAccessToken }) userAccountId = user.account.id userChannelId = user.videoChannels[0].id - const res2 = await getUserScopedTokens(servers[0].url, userAccessToken) - const token: ScopedToken = res2.body + const token = await servers[0].users.getMyScopedTokens({ token: userAccessToken }) userFeedToken = token.feedToken } { - await uploadVideo(servers[0].url, userAccessToken, { name: 'user video' }) + await servers[0].videos.upload({ token: userAccessToken, attributes: { name: 'user video' } }) } { - const videoAttributes = { + const attributes = { name: 'my super name for server 1', description: 'my super description for server 1', fixture: 'video_short.webm' } - const res = await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes) - const videoId = res.body.video.id + const { id } = await servers[0].videos.upload({ attributes }) - await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoId, 'super comment 1') - await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoId, 'super comment 2') + await servers[0].comments.createThread({ videoId: id, text: 'super comment 1' }) + await servers[0].comments.createThread({ videoId: id, text: 'super comment 2' }) } { - const videoAttributes = { name: 'unlisted video', privacy: VideoPrivacy.UNLISTED } - const res = await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes) - const videoId = res.body.video.id + const attributes = { name: 'unlisted video', privacy: VideoPrivacy.UNLISTED } + const { id } = await servers[0].videos.upload({ attributes }) - await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoId, 'comment on unlisted video') + await servers[0].comments.createThread({ videoId: id, text: 'comment on unlisted video' }) } await waitJobs(servers) @@ -118,30 +93,65 @@ describe('Test syndication feeds', () => { it('Should be well formed XML (covers RSS 2.0 and ATOM 1.0 endpoints)', async function () { for (const feed of [ 'video-comments' as 'video-comments', 'videos' as 'videos' ]) { - const rss = await getXMLfeed(servers[0].url, feed) - expect(rss.text).xml.to.be.valid() + const rss = await servers[0].feed.getXML({ feed }) + expect(rss).xml.to.be.valid() - const atom = await getXMLfeed(servers[0].url, feed, 'atom') - expect(atom.text).xml.to.be.valid() + const atom = await servers[0].feed.getXML({ feed, format: 'atom' }) + expect(atom).xml.to.be.valid() } }) it('Should be well formed JSON (covers JSON feed 1.0 endpoint)', async function () { for (const feed of [ 'video-comments' as 'video-comments', 'videos' as 'videos' ]) { - const json = await getJSONfeed(servers[0].url, feed) - expect(JSON.parse(json.text)).to.be.jsonSchema({ type: 'object' }) + const jsonText = await servers[0].feed.getJSON({ feed }) + expect(JSON.parse(jsonText)).to.be.jsonSchema({ type: 'object' }) } }) + + it('Should serve the endpoint with a classic request', async function () { + await makeGetRequest({ + url: servers[0].url, + path: '/feeds/videos.xml', + accept: 'application/xml', + expectedStatus: HttpStatusCode.OK_200 + }) + }) + + it('Should serve the endpoint as a cached request', async function () { + const res = await makeGetRequest({ + url: servers[0].url, + path: '/feeds/videos.xml', + accept: 'application/xml', + expectedStatus: HttpStatusCode.OK_200 + }) + + expect(res.headers['x-api-cache-cached']).to.equal('true') + }) + + it('Should not serve the endpoint as a cached request', async function () { + const res = await makeGetRequest({ + url: servers[0].url, + path: '/feeds/videos.xml?v=186', + accept: 'application/xml', + expectedStatus: HttpStatusCode.OK_200 + }) + + expect(res.headers['x-api-cache-cached']).to.not.exist + }) + + it('Should refuse to serve the endpoint without accept header', async function () { + await makeGetRequest({ url: servers[0].url, path: '/feeds/videos.xml', expectedStatus: HttpStatusCode.NOT_ACCEPTABLE_406 }) + }) }) describe('Videos feed', function () { it('Should contain a valid enclosure (covers RSS 2.0 endpoint)', async function () { for (const server of servers) { - const rss = await getXMLfeed(server.url, 'videos') - expect(xmlParser.validate(rss.text)).to.be.true + const rss = await server.feed.getXML({ feed: 'videos' }) + expect(xmlParser.validate(rss)).to.be.true - const xmlDoc = xmlParser.parse(rss.text, { parseAttributeValue: true, ignoreAttributes: false }) + const xmlDoc = xmlParser.parse(rss, { parseAttributeValue: true, ignoreAttributes: false }) const enclosure = xmlDoc.rss.channel.item[0].enclosure expect(enclosure).to.exist @@ -153,8 +163,8 @@ describe('Test syndication feeds', () => { it('Should contain a valid \'attachments\' object (covers JSON feed 1.0 endpoint)', async function () { for (const server of servers) { - const json = await getJSONfeed(server.url, 'videos') - const jsonObj = JSON.parse(json.text) + const json = await server.feed.getJSON({ feed: 'videos' }) + const jsonObj = JSON.parse(json) expect(jsonObj.items.length).to.be.equal(2) expect(jsonObj.items[0].attachments).to.exist expect(jsonObj.items[0].attachments.length).to.be.eq(1) @@ -166,16 +176,16 @@ describe('Test syndication feeds', () => { it('Should filter by account', async function () { { - const json = await getJSONfeed(servers[0].url, 'videos', { accountId: rootAccountId }) - const jsonObj = JSON.parse(json.text) + const json = await servers[0].feed.getJSON({ feed: 'videos', query: { accountId: rootAccountId } }) + const jsonObj = JSON.parse(json) expect(jsonObj.items.length).to.be.equal(1) expect(jsonObj.items[0].title).to.equal('my super name for server 1') expect(jsonObj.items[0].author.name).to.equal('root') } { - const json = await getJSONfeed(servers[0].url, 'videos', { accountId: userAccountId }) - const jsonObj = JSON.parse(json.text) + const json = await servers[0].feed.getJSON({ feed: 'videos', query: { accountId: userAccountId } }) + const jsonObj = JSON.parse(json) expect(jsonObj.items.length).to.be.equal(1) expect(jsonObj.items[0].title).to.equal('user video') expect(jsonObj.items[0].author.name).to.equal('john') @@ -183,15 +193,15 @@ describe('Test syndication feeds', () => { for (const server of servers) { { - const json = await getJSONfeed(server.url, 'videos', { accountName: 'root@localhost:' + servers[0].port }) - const jsonObj = JSON.parse(json.text) + const json = await server.feed.getJSON({ feed: 'videos', query: { accountName: 'root@localhost:' + servers[0].port } }) + const jsonObj = JSON.parse(json) expect(jsonObj.items.length).to.be.equal(1) expect(jsonObj.items[0].title).to.equal('my super name for server 1') } { - const json = await getJSONfeed(server.url, 'videos', { accountName: 'john@localhost:' + servers[0].port }) - const jsonObj = JSON.parse(json.text) + const json = await server.feed.getJSON({ feed: 'videos', query: { accountName: 'john@localhost:' + servers[0].port } }) + const jsonObj = JSON.parse(json) expect(jsonObj.items.length).to.be.equal(1) expect(jsonObj.items[0].title).to.equal('user video') } @@ -200,16 +210,16 @@ describe('Test syndication feeds', () => { it('Should filter by video channel', async function () { { - const json = await getJSONfeed(servers[0].url, 'videos', { videoChannelId: rootChannelId }) - const jsonObj = JSON.parse(json.text) + const json = await servers[0].feed.getJSON({ feed: 'videos', query: { videoChannelId: rootChannelId } }) + const jsonObj = JSON.parse(json) expect(jsonObj.items.length).to.be.equal(1) expect(jsonObj.items[0].title).to.equal('my super name for server 1') expect(jsonObj.items[0].author.name).to.equal('root') } { - const json = await getJSONfeed(servers[0].url, 'videos', { videoChannelId: userChannelId }) - const jsonObj = JSON.parse(json.text) + const json = await servers[0].feed.getJSON({ feed: 'videos', query: { videoChannelId: userChannelId } }) + const jsonObj = JSON.parse(json) expect(jsonObj.items.length).to.be.equal(1) expect(jsonObj.items[0].title).to.equal('user video') expect(jsonObj.items[0].author.name).to.equal('john') @@ -217,15 +227,17 @@ describe('Test syndication feeds', () => { for (const server of servers) { { - const json = await getJSONfeed(server.url, 'videos', { videoChannelName: 'root_channel@localhost:' + servers[0].port }) - const jsonObj = JSON.parse(json.text) + const query = { videoChannelName: 'root_channel@localhost:' + servers[0].port } + const json = await server.feed.getJSON({ feed: 'videos', query }) + const jsonObj = JSON.parse(json) expect(jsonObj.items.length).to.be.equal(1) expect(jsonObj.items[0].title).to.equal('my super name for server 1') } { - const json = await getJSONfeed(server.url, 'videos', { videoChannelName: 'john_channel@localhost:' + servers[0].port }) - const jsonObj = JSON.parse(json.text) + const query = { videoChannelName: 'john_channel@localhost:' + servers[0].port } + const json = await server.feed.getJSON({ feed: 'videos', query }) + const jsonObj = JSON.parse(json) expect(jsonObj.items.length).to.be.equal(1) expect(jsonObj.items[0].title).to.equal('user video') } @@ -235,12 +247,12 @@ describe('Test syndication feeds', () => { it('Should correctly have videos feed with HLS only', async function () { this.timeout(120000) - await uploadVideo(serverHLSOnly.url, serverHLSOnly.accessToken, { name: 'hls only video' }) + await serverHLSOnly.videos.upload({ attributes: { name: 'hls only video' } }) await waitJobs([ serverHLSOnly ]) - const json = await getJSONfeed(serverHLSOnly.url, 'videos') - const jsonObj = JSON.parse(json.text) + const json = await serverHLSOnly.feed.getJSON({ feed: 'videos' }) + const jsonObj = JSON.parse(json) expect(jsonObj.items.length).to.be.equal(1) expect(jsonObj.items[0].attachments).to.exist expect(jsonObj.items[0].attachments.length).to.be.eq(4) @@ -257,9 +269,9 @@ describe('Test syndication feeds', () => { it('Should contain valid comments (covers JSON feed 1.0 endpoint) and not from unlisted videos', async function () { for (const server of servers) { - const json = await getJSONfeed(server.url, 'video-comments') + const json = await server.feed.getJSON({ feed: 'video-comments' }) - const jsonObj = JSON.parse(json.text) + const jsonObj = JSON.parse(json) expect(jsonObj.items.length).to.be.equal(2) expect(jsonObj.items[0].html_content).to.equal('super comment 2') expect(jsonObj.items[1].html_content).to.equal('super comment 1') @@ -271,32 +283,32 @@ describe('Test syndication feeds', () => { const remoteHandle = 'root@localhost:' + servers[0].port - await addAccountToServerBlocklist(servers[1].url, servers[1].accessToken, remoteHandle) + await servers[1].blocklist.addToServerBlocklist({ account: remoteHandle }) { - const json = await getJSONfeed(servers[1].url, 'video-comments', { version: 2 }) - const jsonObj = JSON.parse(json.text) + const json = await servers[1].feed.getJSON({ feed: 'video-comments', query: { version: 2 } }) + const jsonObj = JSON.parse(json) expect(jsonObj.items.length).to.be.equal(0) } - await removeAccountFromServerBlocklist(servers[1].url, servers[1].accessToken, remoteHandle) + await servers[1].blocklist.removeFromServerBlocklist({ account: remoteHandle }) { - const videoUUID = (await uploadVideoAndGetId({ server: servers[1], videoName: 'server 2' })).uuid + const videoUUID = (await servers[1].videos.quickUpload({ name: 'server 2' })).uuid await waitJobs(servers) - await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoUUID, 'super comment') + await servers[0].comments.createThread({ videoId: videoUUID, text: 'super comment' }) await waitJobs(servers) - const json = await getJSONfeed(servers[1].url, 'video-comments', { version: 3 }) - const jsonObj = JSON.parse(json.text) + const json = await servers[1].feed.getJSON({ feed: 'video-comments', query: { version: 3 } }) + const jsonObj = JSON.parse(json) expect(jsonObj.items.length).to.be.equal(3) } - await addAccountToAccountBlocklist(servers[1].url, servers[1].accessToken, remoteHandle) + await servers[1].blocklist.addToMyBlocklist({ account: remoteHandle }) { - const json = await getJSONfeed(servers[1].url, 'video-comments', { version: 4 }) - const jsonObj = JSON.parse(json.text) + const json = await servers[1].feed.getJSON({ feed: 'video-comments', query: { version: 4 } }) + const jsonObj = JSON.parse(json) expect(jsonObj.items.length).to.be.equal(2) } }) @@ -308,66 +320,64 @@ describe('Test syndication feeds', () => { it('Should list no videos for a user with no videos and no subscriptions', async function () { const attr = { username: 'feeduser', password: 'password' } - await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: attr.username, password: attr.password }) - const feeduserAccessToken = await userLogin(servers[0], attr) + await servers[0].users.create({ username: attr.username, password: attr.password }) + const feeduserAccessToken = await servers[0].login.getAccessToken(attr) { - const res = await getMyUserInformation(servers[0].url, feeduserAccessToken) - const user: User = res.body + const user = await servers[0].users.getMyInfo({ token: feeduserAccessToken }) feeduserAccountId = user.account.id } { - const res = await getUserScopedTokens(servers[0].url, feeduserAccessToken) - const token: ScopedToken = res.body + const token = await servers[0].users.getMyScopedTokens({ token: feeduserAccessToken }) feeduserFeedToken = token.feedToken } { - const res = await listUserSubscriptionVideos(servers[0].url, feeduserAccessToken) - expect(res.body.total).to.equal(0) + const body = await servers[0].subscriptions.listVideos({ token: feeduserAccessToken }) + expect(body.total).to.equal(0) - const json = await getJSONfeed(servers[0].url, 'subscriptions', { accountId: feeduserAccountId, token: feeduserFeedToken }) - const jsonObj = JSON.parse(json.text) + const query = { accountId: feeduserAccountId, token: feeduserFeedToken } + const json = await servers[0].feed.getJSON({ feed: 'subscriptions', query }) + const jsonObj = JSON.parse(json) expect(jsonObj.items.length).to.be.equal(0) // no subscription, it should not list the instance's videos but list 0 videos } }) it('Should fail with an invalid token', async function () { - await getJSONfeed(servers[0].url, 'subscriptions', { accountId: feeduserAccountId, token: 'toto' }, HttpStatusCode.FORBIDDEN_403) + const query = { accountId: feeduserAccountId, token: 'toto' } + await servers[0].feed.getJSON({ feed: 'subscriptions', query, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) }) it('Should fail with a token of another user', async function () { - await getJSONfeed( - servers[0].url, - 'subscriptions', - { accountId: feeduserAccountId, token: userFeedToken }, - HttpStatusCode.FORBIDDEN_403 - ) + const query = { accountId: feeduserAccountId, token: userFeedToken } + await servers[0].feed.getJSON({ feed: 'subscriptions', query, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) }) it('Should list no videos for a user with videos but no subscriptions', async function () { - const res = await listUserSubscriptionVideos(servers[0].url, userAccessToken) - expect(res.body.total).to.equal(0) + const body = await servers[0].subscriptions.listVideos({ token: userAccessToken }) + expect(body.total).to.equal(0) - const json = await getJSONfeed(servers[0].url, 'subscriptions', { accountId: userAccountId, token: userFeedToken }) - const jsonObj = JSON.parse(json.text) + const query = { accountId: userAccountId, token: userFeedToken } + const json = await servers[0].feed.getJSON({ feed: 'subscriptions', query }) + const jsonObj = JSON.parse(json) expect(jsonObj.items.length).to.be.equal(0) // no subscription, it should not list the instance's videos but list 0 videos }) it('Should list self videos for a user with a subscription to themselves', async function () { this.timeout(30000) - await addUserSubscription(servers[0].url, userAccessToken, 'john_channel@localhost:' + servers[0].port) + await servers[0].subscriptions.add({ token: userAccessToken, targetUri: 'john_channel@localhost:' + servers[0].port }) await waitJobs(servers) { - const res = await listUserSubscriptionVideos(servers[0].url, userAccessToken) - expect(res.body.total).to.equal(1) - expect(res.body.data[0].name).to.equal('user video') + const body = await servers[0].subscriptions.listVideos({ token: userAccessToken }) + expect(body.total).to.equal(1) + expect(body.data[0].name).to.equal('user video') - const json = await getJSONfeed(servers[0].url, 'subscriptions', { accountId: userAccountId, token: userFeedToken, version: 1 }) - const jsonObj = JSON.parse(json.text) + const query = { accountId: userAccountId, token: userFeedToken, version: 1 } + const json = await servers[0].feed.getJSON({ feed: 'subscriptions', query }) + const jsonObj = JSON.parse(json) expect(jsonObj.items.length).to.be.equal(1) // subscribed to self, it should not list the instance's videos but list john's } }) @@ -375,36 +385,33 @@ describe('Test syndication feeds', () => { it('Should list videos of a user\'s subscription', async function () { this.timeout(30000) - await addUserSubscription(servers[0].url, userAccessToken, 'root_channel@localhost:' + servers[0].port) + await servers[0].subscriptions.add({ token: userAccessToken, targetUri: 'root_channel@localhost:' + servers[0].port }) await waitJobs(servers) { - const res = await listUserSubscriptionVideos(servers[0].url, userAccessToken) - expect(res.body.total).to.equal(2, "there should be 2 videos part of the subscription") + const body = await servers[0].subscriptions.listVideos({ token: userAccessToken }) + expect(body.total).to.equal(2, "there should be 2 videos part of the subscription") - const json = await getJSONfeed(servers[0].url, 'subscriptions', { accountId: userAccountId, token: userFeedToken, version: 2 }) - const jsonObj = JSON.parse(json.text) + const query = { accountId: userAccountId, token: userFeedToken, version: 2 } + const json = await servers[0].feed.getJSON({ feed: 'subscriptions', query }) + const jsonObj = JSON.parse(json) expect(jsonObj.items.length).to.be.equal(2) // subscribed to root, it should not list the instance's videos but list root/john's } }) it('Should renew the token, and so have an invalid old token', async function () { - await renewUserScopedTokens(servers[0].url, userAccessToken) - - await getJSONfeed( - servers[0].url, - 'subscriptions', - { accountId: userAccountId, token: userFeedToken, version: 3 }, - HttpStatusCode.FORBIDDEN_403 - ) + await servers[0].users.renewMyScopedTokens({ token: userAccessToken }) + + const query = { accountId: userAccountId, token: userFeedToken, version: 3 } + await servers[0].feed.getJSON({ feed: 'subscriptions', query, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) }) it('Should succeed with the new token', async function () { - const res2 = await getUserScopedTokens(servers[0].url, userAccessToken) - const token: ScopedToken = res2.body + const token = await servers[0].users.getMyScopedTokens({ token: userAccessToken }) userFeedToken = token.feedToken - await getJSONfeed(servers[0].url, 'subscriptions', { accountId: userAccountId, token: userFeedToken, version: 4 }) + const query = { accountId: userAccountId, token: userFeedToken, version: 4 } + await servers[0].feed.getJSON({ feed: 'subscriptions', query }) }) }) diff --git a/server/tests/fixtures/peertube-plugin-test-transcoding-one/main.js b/server/tests/fixtures/peertube-plugin-test-transcoding-one/main.js index 59b136947..c4ae777f5 100644 --- a/server/tests/fixtures/peertube-plugin-test-transcoding-one/main.js +++ b/server/tests/fixtures/peertube-plugin-test-transcoding-one/main.js @@ -18,12 +18,12 @@ async function register ({ transcodingManager }) { const builder = (options) => { return { outputOptions: [ - '-r:' + options.streamNum + ' 5' + '-r:' + options.streamNum + ' 50' ] } } - transcodingManager.addLiveProfile('libx264', 'low-live', builder) + transcodingManager.addLiveProfile('libx264', 'high-live', builder) } } @@ -45,7 +45,7 @@ async function register ({ transcodingManager }) { const builder = () => { return { inputOptions: [ - '-r 5' + '-r 50' ] } } @@ -82,7 +82,6 @@ async function register ({ transcodingManager }) { } } - async function unregister () { return } diff --git a/server/tests/fixtures/peertube-plugin-test-video-constants/main.js b/server/tests/fixtures/peertube-plugin-test-video-constants/main.js index 3e650e0a1..06527bd35 100644 --- a/server/tests/fixtures/peertube-plugin-test-video-constants/main.js +++ b/server/tests/fixtures/peertube-plugin-test-video-constants/main.js @@ -1,46 +1,46 @@ async function register ({ - registerHook, - registerSetting, - settingsManager, - storageManager, videoCategoryManager, videoLicenceManager, videoLanguageManager, videoPrivacyManager, - playlistPrivacyManager + playlistPrivacyManager, + getRouter }) { - videoLanguageManager.addLanguage('al_bhed', 'Al Bhed') + videoLanguageManager.addConstant('al_bhed', 'Al Bhed') videoLanguageManager.addLanguage('al_bhed2', 'Al Bhed 2') - videoLanguageManager.addLanguage('al_bhed3', 'Al Bhed 3') - videoLanguageManager.deleteLanguage('en') + videoLanguageManager.addConstant('al_bhed3', 'Al Bhed 3') + videoLanguageManager.deleteConstant('en') videoLanguageManager.deleteLanguage('fr') - videoLanguageManager.deleteLanguage('al_bhed3') + videoLanguageManager.deleteConstant('al_bhed3') videoCategoryManager.addCategory(42, 'Best category') - videoCategoryManager.addCategory(43, 'High best category') - videoCategoryManager.deleteCategory(1) // Music + videoCategoryManager.addConstant(43, 'High best category') + videoCategoryManager.deleteConstant(1) // Music videoCategoryManager.deleteCategory(2) // Films videoLicenceManager.addLicence(42, 'Best licence') - videoLicenceManager.addLicence(43, 'High best licence') - videoLicenceManager.deleteLicence(1) // Attribution - videoLicenceManager.deleteLicence(7) // Public domain + videoLicenceManager.addConstant(43, 'High best licence') + videoLicenceManager.deleteConstant(1) // Attribution + videoLicenceManager.deleteConstant(7) // Public domain + videoPrivacyManager.deleteConstant(2) videoPrivacyManager.deletePrivacy(2) + playlistPrivacyManager.deleteConstant(3) playlistPrivacyManager.deletePlaylistPrivacy(3) -} -async function unregister () { - return + { + const router = getRouter() + router.get('/reset-categories', (req, res) => { + videoCategoryManager.resetConstants() + + res.sendStatus(204) + }) + } } +async function unregister () {} + module.exports = { register, unregister } - -// ############################################################################ - -function addToCount (obj) { - return Object.assign({}, obj, { count: obj.count + 1 }) -} diff --git a/server/tests/fixtures/peertube-plugin-test/main.js b/server/tests/fixtures/peertube-plugin-test/main.js index f8e6f0b98..db405ff31 100644 --- a/server/tests/fixtures/peertube-plugin-test/main.js +++ b/server/tests/fixtures/peertube-plugin-test/main.js @@ -234,7 +234,7 @@ async function register ({ registerHook, registerSetting, settingsManager, stora }) { - const searchHooks = [ + const filterHooks = [ 'filter:api.search.videos.local.list.params', 'filter:api.search.videos.local.list.result', 'filter:api.search.videos.index.list.params', @@ -246,10 +246,13 @@ async function register ({ registerHook, registerSetting, settingsManager, stora 'filter:api.search.video-playlists.local.list.params', 'filter:api.search.video-playlists.local.list.result', 'filter:api.search.video-playlists.index.list.params', - 'filter:api.search.video-playlists.index.list.result' + 'filter:api.search.video-playlists.index.list.result', + + 'filter:api.overviews.videos.list.params', + 'filter:api.overviews.videos.list.result' ] - for (const h of searchHooks) { + for (const h of filterHooks) { registerHook({ target: h, handler: (obj) => { diff --git a/server/tests/fixtures/video_very_short_240p.mp4 b/server/tests/fixtures/video_very_short_240p.mp4 new file mode 100644 index 000000000..95b6be92a Binary files /dev/null and b/server/tests/fixtures/video_very_short_240p.mp4 differ diff --git a/server/tests/helpers/comment-model.ts b/server/tests/helpers/comment-model.ts index 4c51b7000..31dc6ec72 100644 --- a/server/tests/helpers/comment-model.ts +++ b/server/tests/helpers/comment-model.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ -import * as chai from 'chai' import 'mocha' +import * as chai from 'chai' import { VideoCommentModel } from '../../models/video/video-comment' const expect = chai.expect diff --git a/server/tests/helpers/core-utils.ts b/server/tests/helpers/core-utils.ts index c028b316d..d5cac51a3 100644 --- a/server/tests/helpers/core-utils.ts +++ b/server/tests/helpers/core-utils.ts @@ -1,10 +1,10 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ -import * as chai from 'chai' import 'mocha' +import * as chai from 'chai' import { snakeCase } from 'lodash' -import { objectConverter, parseBytes } from '../../helpers/core-utils' import validator from 'validator' +import { objectConverter, parseBytes } from '../../helpers/core-utils' const expect = chai.expect diff --git a/server/tests/helpers/image.ts b/server/tests/helpers/image.ts index 54911697f..9fe9aa4cb 100644 --- a/server/tests/helpers/image.ts +++ b/server/tests/helpers/image.ts @@ -1,11 +1,11 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ import 'mocha' +import { expect } from 'chai' import { readFile, remove } from 'fs-extra' import { join } from 'path' import { processImage } from '../../../server/helpers/image-utils' import { buildAbsoluteFixturePath, root } from '../../../shared/extra-utils' -import { expect } from 'chai' async function checkBuffers (path1: string, path2: string, equals: boolean) { const [ buf1, buf2 ] = await Promise.all([ diff --git a/server/tests/helpers/request.ts b/server/tests/helpers/request.ts index 5e77f129e..7f7873df3 100644 --- a/server/tests/helpers/request.ts +++ b/server/tests/helpers/request.ts @@ -4,7 +4,7 @@ import 'mocha' import { expect } from 'chai' import { pathExists, remove } from 'fs-extra' import { join } from 'path' -import { get4KFileUrl, root, wait } from '../../../shared/extra-utils' +import { FIXTURE_URLS, root, wait } from '../../../shared/extra-utils' import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests' describe('Request helpers', function () { @@ -13,7 +13,7 @@ describe('Request helpers', function () { it('Should throw an error when the bytes limit is exceeded for request', async function () { try { - await doRequest(get4KFileUrl(), { bodyKBLimit: 3 }) + await doRequest(FIXTURE_URLS.video4K, { bodyKBLimit: 3 }) } catch { return } @@ -23,7 +23,7 @@ describe('Request helpers', function () { it('Should throw an error when the bytes limit is exceeded for request and save file', async function () { try { - await doRequestAndSaveToFile(get4KFileUrl(), destPath1, { bodyKBLimit: 3 }) + await doRequestAndSaveToFile(FIXTURE_URLS.video4K, destPath1, { bodyKBLimit: 3 }) } catch { await wait(500) @@ -35,8 +35,8 @@ describe('Request helpers', function () { }) it('Should succeed if the file is below the limit', async function () { - await doRequest(get4KFileUrl(), { bodyKBLimit: 5 }) - await doRequestAndSaveToFile(get4KFileUrl(), destPath2, { bodyKBLimit: 5 }) + await doRequest(FIXTURE_URLS.video4K, { bodyKBLimit: 5 }) + await doRequestAndSaveToFile(FIXTURE_URLS.video4K, destPath2, { bodyKBLimit: 5 }) expect(await pathExists(destPath2)).to.be.true }) diff --git a/server/tests/index.ts b/server/tests/index.ts index 3fbd0ebbd..1718ac424 100644 --- a/server/tests/index.ts +++ b/server/tests/index.ts @@ -6,3 +6,4 @@ import './cli/' import './api/' import './plugins/' import './helpers/' +import './lib/' diff --git a/server/tests/lib/index.ts b/server/tests/lib/index.ts new file mode 100644 index 000000000..a40df35fd --- /dev/null +++ b/server/tests/lib/index.ts @@ -0,0 +1 @@ +export * from './video-constant-registry-factory' diff --git a/server/tests/lib/video-constant-registry-factory.ts b/server/tests/lib/video-constant-registry-factory.ts new file mode 100644 index 000000000..e26b286e1 --- /dev/null +++ b/server/tests/lib/video-constant-registry-factory.ts @@ -0,0 +1,155 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions */ +import 'mocha' +import { expect } from 'chai' +import { VideoConstantManagerFactory } from '@server/lib/plugins/video-constant-manager-factory' +import { + VIDEO_CATEGORIES, + VIDEO_LANGUAGES, + VIDEO_LICENCES, + VIDEO_PLAYLIST_PRIVACIES, + VIDEO_PRIVACIES +} from '@server/initializers/constants' +import { + VideoPlaylistPrivacy, + VideoPrivacy +} from '@shared/models' + +describe('VideoConstantManagerFactory', function () { + const factory = new VideoConstantManagerFactory('peertube-plugin-constants') + + afterEach(() => { + factory.resetVideoConstants('peertube-plugin-constants') + }) + + describe('VideoCategoryManager', () => { + const videoCategoryManager = factory.createVideoConstantManager('category') + + it('Should be able to list all video category constants', () => { + const constants = videoCategoryManager.getConstants() + expect(constants).to.deep.equal(VIDEO_CATEGORIES) + }) + + it('Should be able to delete a video category constant', () => { + const successfullyDeleted = videoCategoryManager.deleteConstant(1) + expect(successfullyDeleted).to.be.true + expect(videoCategoryManager.getConstantValue(1)).to.be.undefined + }) + + it('Should be able to add a video category constant', () => { + const successfullyAdded = videoCategoryManager.addConstant(42, 'The meaning of life') + expect(successfullyAdded).to.be.true + expect(videoCategoryManager.getConstantValue(42)).to.equal('The meaning of life') + }) + + it('Should be able to reset video category constants', () => { + videoCategoryManager.deleteConstant(1) + videoCategoryManager.resetConstants() + expect(videoCategoryManager.getConstantValue(1)).not.be.undefined + }) + }) + + describe('VideoLicenceManager', () => { + const videoLicenceManager = factory.createVideoConstantManager('licence') + it('Should be able to list all video licence constants', () => { + const constants = videoLicenceManager.getConstants() + expect(constants).to.deep.equal(VIDEO_LICENCES) + }) + + it('Should be able to delete a video licence constant', () => { + const successfullyDeleted = videoLicenceManager.deleteConstant(1) + expect(successfullyDeleted).to.be.true + expect(videoLicenceManager.getConstantValue(1)).to.be.undefined + }) + + it('Should be able to add a video licence constant', () => { + const successfullyAdded = videoLicenceManager.addConstant(42, 'European Union Public Licence') + expect(successfullyAdded).to.be.true + expect(videoLicenceManager.getConstantValue(42)).to.equal('European Union Public Licence') + }) + + it('Should be able to reset video licence constants', () => { + videoLicenceManager.deleteConstant(1) + videoLicenceManager.resetConstants() + expect(videoLicenceManager.getConstantValue(1)).not.be.undefined + }) + }) + + describe('PlaylistPrivacyManager', () => { + const playlistPrivacyManager = factory.createVideoConstantManager('playlistPrivacy') + it('Should be able to list all video playlist privacy constants', () => { + const constants = playlistPrivacyManager.getConstants() + expect(constants).to.deep.equal(VIDEO_PLAYLIST_PRIVACIES) + }) + + it('Should be able to delete a video playlist privacy constant', () => { + const successfullyDeleted = playlistPrivacyManager.deleteConstant(1) + expect(successfullyDeleted).to.be.true + expect(playlistPrivacyManager.getConstantValue(1)).to.be.undefined + }) + + it('Should be able to add a video playlist privacy constant', () => { + const successfullyAdded = playlistPrivacyManager.addConstant(42, 'Friends only') + expect(successfullyAdded).to.be.true + expect(playlistPrivacyManager.getConstantValue(42)).to.equal('Friends only') + }) + + it('Should be able to reset video playlist privacy constants', () => { + playlistPrivacyManager.deleteConstant(1) + playlistPrivacyManager.resetConstants() + expect(playlistPrivacyManager.getConstantValue(1)).not.be.undefined + }) + }) + + describe('VideoPrivacyManager', () => { + const videoPrivacyManager = factory.createVideoConstantManager('privacy') + it('Should be able to list all video privacy constants', () => { + const constants = videoPrivacyManager.getConstants() + expect(constants).to.deep.equal(VIDEO_PRIVACIES) + }) + + it('Should be able to delete a video privacy constant', () => { + const successfullyDeleted = videoPrivacyManager.deleteConstant(1) + expect(successfullyDeleted).to.be.true + expect(videoPrivacyManager.getConstantValue(1)).to.be.undefined + }) + + it('Should be able to add a video privacy constant', () => { + const successfullyAdded = videoPrivacyManager.addConstant(42, 'Friends only') + expect(successfullyAdded).to.be.true + expect(videoPrivacyManager.getConstantValue(42)).to.equal('Friends only') + }) + + it('Should be able to reset video privacy constants', () => { + videoPrivacyManager.deleteConstant(1) + videoPrivacyManager.resetConstants() + expect(videoPrivacyManager.getConstantValue(1)).not.be.undefined + }) + }) + + describe('VideoLanguageManager', () => { + const videoLanguageManager = factory.createVideoConstantManager('language') + it('Should be able to list all video language constants', () => { + const constants = videoLanguageManager.getConstants() + expect(constants).to.deep.equal(VIDEO_LANGUAGES) + }) + + it('Should be able to add a video language constant', () => { + const successfullyAdded = videoLanguageManager.addConstant('fr', 'Fr occitan') + expect(successfullyAdded).to.be.true + expect(videoLanguageManager.getConstantValue('fr')).to.equal('Fr occitan') + }) + + it('Should be able to delete a video language constant', () => { + videoLanguageManager.addConstant('fr', 'Fr occitan') + const successfullyDeleted = videoLanguageManager.deleteConstant('fr') + expect(successfullyDeleted).to.be.true + expect(videoLanguageManager.getConstantValue('fr')).to.be.undefined + }) + + it('Should be able to reset video language constants', () => { + videoLanguageManager.addConstant('fr', 'Fr occitan') + videoLanguageManager.resetConstants() + expect(videoLanguageManager.getConstantValue('fr')).to.be.undefined + }) + }) +}) diff --git a/server/tests/misc-endpoints.ts b/server/tests/misc-endpoints.ts index 09e5afcf9..4968eef08 100644 --- a/server/tests/misc-endpoints.ts +++ b/server/tests/misc-endpoints.ts @@ -2,28 +2,18 @@ import 'mocha' import * as chai from 'chai' -import { - addVideoChannel, - cleanupTests, - createUser, - flushAndRunServer, - makeGetRequest, - ServerInfo, - setAccessTokensToServers, - uploadVideo -} from '../../shared/extra-utils' -import { VideoPrivacy } from '../../shared/models/videos' -import { HttpStatusCode } from '@shared/core-utils' +import { cleanupTests, createSingleServer, makeGetRequest, PeerTubeServer, setAccessTokensToServers } from '@shared/extra-utils' +import { HttpStatusCode, VideoPrivacy } from '@shared/models' const expect = chai.expect describe('Test misc endpoints', function () { - let server: ServerInfo + let server: PeerTubeServer before(async function () { this.timeout(120000) - server = await flushAndRunServer(1) + server = await createSingleServer(1) await setAccessTokensToServers([ server ]) }) @@ -33,7 +23,7 @@ describe('Test misc endpoints', function () { const res = await makeGetRequest({ url: server.url, path: '/.well-known/security.txt', - statusCodeExpected: HttpStatusCode.OK_200 + expectedStatus: HttpStatusCode.OK_200 }) expect(res.text).to.contain('security issue') @@ -43,7 +33,7 @@ describe('Test misc endpoints', function () { const res = await makeGetRequest({ url: server.url, path: '/.well-known/nodeinfo', - statusCodeExpected: HttpStatusCode.OK_200 + expectedStatus: HttpStatusCode.OK_200 }) expect(res.body.links).to.be.an('array') @@ -55,7 +45,7 @@ describe('Test misc endpoints', function () { const res = await makeGetRequest({ url: server.url, path: '/.well-known/dnt-policy.txt', - statusCodeExpected: HttpStatusCode.OK_200 + expectedStatus: HttpStatusCode.OK_200 }) expect(res.text).to.contain('http://www.w3.org/TR/tracking-dnt') @@ -65,7 +55,7 @@ describe('Test misc endpoints', function () { const res = await makeGetRequest({ url: server.url, path: '/.well-known/dnt', - statusCodeExpected: HttpStatusCode.OK_200 + expectedStatus: HttpStatusCode.OK_200 }) expect(res.body.tracking).to.equal('N') @@ -75,7 +65,7 @@ describe('Test misc endpoints', function () { const res = await makeGetRequest({ url: server.url, path: '/.well-known/change-password', - statusCodeExpected: HttpStatusCode.FOUND_302 + expectedStatus: HttpStatusCode.FOUND_302 }) expect(res.header.location).to.equal('/my-account/settings') @@ -88,7 +78,7 @@ describe('Test misc endpoints', function () { const res = await makeGetRequest({ url: server.url, path: '/.well-known/webfinger?resource=' + resource, - statusCodeExpected: HttpStatusCode.OK_200 + expectedStatus: HttpStatusCode.OK_200 }) const data = res.body @@ -113,7 +103,7 @@ describe('Test misc endpoints', function () { const res = await makeGetRequest({ url: server.url, path: '/robots.txt', - statusCodeExpected: HttpStatusCode.OK_200 + expectedStatus: HttpStatusCode.OK_200 }) expect(res.text).to.contain('User-agent') @@ -123,7 +113,7 @@ describe('Test misc endpoints', function () { await makeGetRequest({ url: server.url, path: '/security.txt', - statusCodeExpected: HttpStatusCode.MOVED_PERMANENTLY_301 + expectedStatus: HttpStatusCode.MOVED_PERMANENTLY_301 }) }) @@ -131,7 +121,7 @@ describe('Test misc endpoints', function () { const res = await makeGetRequest({ url: server.url, path: '/nodeinfo/2.0.json', - statusCodeExpected: HttpStatusCode.OK_200 + expectedStatus: HttpStatusCode.OK_200 }) expect(res.body.software.name).to.equal('peertube') @@ -146,7 +136,7 @@ describe('Test misc endpoints', function () { const res = await makeGetRequest({ url: server.url, path: '/sitemap.xml', - statusCodeExpected: HttpStatusCode.OK_200 + expectedStatus: HttpStatusCode.OK_200 }) expect(res.text).to.contain('xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"') @@ -157,7 +147,7 @@ describe('Test misc endpoints', function () { const res = await makeGetRequest({ url: server.url, path: '/sitemap.xml', - statusCodeExpected: HttpStatusCode.OK_200 + expectedStatus: HttpStatusCode.OK_200 }) expect(res.text).to.contain('xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"') @@ -167,20 +157,20 @@ describe('Test misc endpoints', function () { it('Should add videos, channel and accounts and get sitemap', async function () { this.timeout(35000) - await uploadVideo(server.url, server.accessToken, { name: 'video 1', nsfw: false }) - await uploadVideo(server.url, server.accessToken, { name: 'video 2', nsfw: false }) - await uploadVideo(server.url, server.accessToken, { name: 'video 3', privacy: VideoPrivacy.PRIVATE }) + await server.videos.upload({ attributes: { name: 'video 1', nsfw: false } }) + await server.videos.upload({ attributes: { name: 'video 2', nsfw: false } }) + await server.videos.upload({ attributes: { name: 'video 3', privacy: VideoPrivacy.PRIVATE } }) - await addVideoChannel(server.url, server.accessToken, { name: 'channel1', displayName: 'channel 1' }) - await addVideoChannel(server.url, server.accessToken, { name: 'channel2', displayName: 'channel 2' }) + await server.channels.create({ attributes: { name: 'channel1', displayName: 'channel 1' } }) + await server.channels.create({ attributes: { name: 'channel2', displayName: 'channel 2' } }) - await createUser({ url: server.url, accessToken: server.accessToken, username: 'user1', password: 'password' }) - await createUser({ url: server.url, accessToken: server.accessToken, username: 'user2', password: 'password' }) + await server.users.create({ username: 'user1', password: 'password' }) + await server.users.create({ username: 'user2', password: 'password' }) const res = await makeGetRequest({ url: server.url, path: '/sitemap.xml?t=1', // avoid using cache - statusCodeExpected: HttpStatusCode.OK_200 + expectedStatus: HttpStatusCode.OK_200 }) expect(res.text).to.contain('xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"') diff --git a/server/tests/plugins/action-hooks.ts b/server/tests/plugins/action-hooks.ts index 0f57ef7fe..4c1bc7d06 100644 --- a/server/tests/plugins/action-hooks.ts +++ b/server/tests/plugins/action-hooks.ts @@ -1,63 +1,38 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ import 'mocha' -import { ServerHookName, VideoPlaylistPrivacy, VideoPrivacy } from '@shared/models' -import { - addVideoCommentReply, - addVideoCommentThread, - addVideoInPlaylist, - blockUser, - createLive, - createUser, - createVideoPlaylist, - deleteVideoComment, - getPluginTestPath, - installPlugin, - registerUser, - removeUser, - setAccessTokensToServers, - setDefaultVideoChannel, - unblockUser, - updateUser, - updateVideo, - uploadVideo, - userLogin, - viewVideo -} from '../../../shared/extra-utils' import { cleanupTests, - flushAndRunMultipleServers, + createMultipleServers, killallServers, - reRunServer, - ServerInfo, - waitUntilLog -} from '../../../shared/extra-utils/server/servers' + PeerTubeServer, + PluginsCommand, + setAccessTokensToServers, + setDefaultVideoChannel +} from '@shared/extra-utils' +import { ServerHookName, VideoPlaylistPrivacy, VideoPrivacy } from '@shared/models' describe('Test plugin action hooks', function () { - let servers: ServerInfo[] + let servers: PeerTubeServer[] let videoUUID: string let threadId: number function checkHook (hook: ServerHookName) { - return waitUntilLog(servers[0], 'Run hook ' + hook) + return servers[0].servers.waitUntilLog('Run hook ' + hook) } before(async function () { this.timeout(30000) - servers = await flushAndRunMultipleServers(2) + servers = await createMultipleServers(2) await setAccessTokensToServers(servers) await setDefaultVideoChannel(servers) - await installPlugin({ - url: servers[0].url, - accessToken: servers[0].accessToken, - path: getPluginTestPath() - }) + await servers[0].plugins.install({ path: PluginsCommand.getPluginTestPath() }) - killallServers([ servers[0] ]) + await killallServers([ servers[0] ]) - await reRunServer(servers[0], { + await servers[0].run({ live: { enabled: true } @@ -73,20 +48,20 @@ describe('Test plugin action hooks', function () { describe('Videos hooks', function () { it('Should run action:api.video.uploaded', async function () { - const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video' }) - videoUUID = res.body.video.uuid + const { uuid } = await servers[0].videos.upload({ attributes: { name: 'video' } }) + videoUUID = uuid await checkHook('action:api.video.uploaded') }) it('Should run action:api.video.updated', async function () { - await updateVideo(servers[0].url, servers[0].accessToken, videoUUID, { name: 'video updated' }) + await servers[0].videos.update({ id: videoUUID, attributes: { name: 'video updated' } }) await checkHook('action:api.video.updated') }) it('Should run action:api.video.viewed', async function () { - await viewVideo(servers[0].url, videoUUID) + await servers[0].videos.view({ id: videoUUID }) await checkHook('action:api.video.viewed') }) @@ -98,10 +73,10 @@ describe('Test plugin action hooks', function () { const attributes = { name: 'live', privacy: VideoPrivacy.PUBLIC, - channelId: servers[0].videoChannel.id + channelId: servers[0].store.channel.id } - await createLive(servers[0].url, servers[0].accessToken, attributes) + await servers[0].live.create({ fields: attributes }) await checkHook('action:api.live-video.created') }) @@ -109,20 +84,20 @@ describe('Test plugin action hooks', function () { describe('Comments hooks', function () { it('Should run action:api.video-thread.created', async function () { - const res = await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoUUID, 'thread') - threadId = res.body.comment.id + const created = await servers[0].comments.createThread({ videoId: videoUUID, text: 'thread' }) + threadId = created.id await checkHook('action:api.video-thread.created') }) it('Should run action:api.video-comment-reply.created', async function () { - await addVideoCommentReply(servers[0].url, servers[0].accessToken, videoUUID, threadId, 'reply') + await servers[0].comments.addReply({ videoId: videoUUID, toCommentId: threadId, text: 'reply' }) await checkHook('action:api.video-comment-reply.created') }) it('Should run action:api.video-comment.deleted', async function () { - await deleteVideoComment(servers[0].url, servers[0].accessToken, videoUUID, threadId) + await servers[0].comments.delete({ videoId: videoUUID, commentId: threadId }) await checkHook('action:api.video-comment.deleted') }) @@ -132,49 +107,44 @@ describe('Test plugin action hooks', function () { let userId: number it('Should run action:api.user.registered', async function () { - await registerUser(servers[0].url, 'registered_user', 'super_password') + await servers[0].users.register({ username: 'registered_user' }) await checkHook('action:api.user.registered') }) it('Should run action:api.user.created', async function () { - const res = await createUser({ - url: servers[0].url, - accessToken: servers[0].accessToken, - username: 'created_user', - password: 'super_password' - }) - userId = res.body.user.id + const user = await servers[0].users.create({ username: 'created_user' }) + userId = user.id await checkHook('action:api.user.created') }) it('Should run action:api.user.oauth2-got-token', async function () { - await userLogin(servers[0], { username: 'created_user', password: 'super_password' }) + await servers[0].login.login({ user: { username: 'created_user' } }) await checkHook('action:api.user.oauth2-got-token') }) it('Should run action:api.user.blocked', async function () { - await blockUser(servers[0].url, userId, servers[0].accessToken) + await servers[0].users.banUser({ userId }) await checkHook('action:api.user.blocked') }) it('Should run action:api.user.unblocked', async function () { - await unblockUser(servers[0].url, userId, servers[0].accessToken) + await servers[0].users.unbanUser({ userId }) await checkHook('action:api.user.unblocked') }) it('Should run action:api.user.updated', async function () { - await updateUser({ url: servers[0].url, accessToken: servers[0].accessToken, userId, videoQuota: 50 }) + await servers[0].users.update({ userId, videoQuota: 50 }) await checkHook('action:api.user.updated') }) it('Should run action:api.user.deleted', async function () { - await removeUser(servers[0].url, userId, servers[0].accessToken) + await servers[0].users.remove({ userId }) await checkHook('action:api.user.deleted') }) @@ -186,30 +156,23 @@ describe('Test plugin action hooks', function () { before(async function () { { - const res = await createVideoPlaylist({ - url: servers[0].url, - token: servers[0].accessToken, - playlistAttrs: { + const { id } = await servers[0].playlists.create({ + attributes: { displayName: 'My playlist', privacy: VideoPlaylistPrivacy.PRIVATE } }) - playlistId = res.body.videoPlaylist.id + playlistId = id } { - const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'my super name' }) - videoId = res.body.video.id + const { id } = await servers[0].videos.upload({ attributes: { name: 'my super name' } }) + videoId = id } }) it('Should run action:api.video-playlist-element.created', async function () { - await addVideoInPlaylist({ - url: servers[0].url, - token: servers[0].accessToken, - playlistId, - elementAttrs: { videoId } - }) + await servers[0].playlists.addElement({ playlistId, attributes: { videoId } }) await checkHook('action:api.video-playlist-element.created') }) diff --git a/server/tests/plugins/external-auth.ts b/server/tests/plugins/external-auth.ts index 5addb45c7..f3e018d43 100644 --- a/server/tests/plugins/external-auth.ts +++ b/server/tests/plugins/external-auth.ts @@ -2,44 +2,32 @@ import 'mocha' import { expect } from 'chai' -import { ServerConfig, User, UserRole } from '@shared/models' import { + cleanupTests, + createSingleServer, decodeQueryString, - getConfig, - getExternalAuth, - getMyUserInformation, - getPluginTestPath, - installPlugin, - loginUsingExternalToken, - logout, - refreshToken, + PeerTubeServer, + PluginsCommand, setAccessTokensToServers, - uninstallPlugin, - updateMyUser, - wait, - userLogin, - updatePluginSettings, - createUser -} from '../../../shared/extra-utils' -import { cleanupTests, flushAndRunServer, ServerInfo, waitUntilLog } from '../../../shared/extra-utils/server/servers' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' + wait +} from '@shared/extra-utils' +import { HttpStatusCode, UserRole } from '@shared/models' async function loginExternal (options: { - server: ServerInfo + server: PeerTubeServer npmName: string authName: string username: string query?: any - statusCodeExpected?: HttpStatusCode - statusCodeExpectedStep2?: HttpStatusCode + expectedStatus?: HttpStatusCode + expectedStatusStep2?: HttpStatusCode }) { - const res = await getExternalAuth({ - url: options.server.url, + const res = await options.server.plugins.getExternalAuth({ npmName: options.npmName, npmVersion: '0.0.1', authName: options.authName, query: options.query, - statusCodeExpected: options.statusCodeExpected || HttpStatusCode.FOUND_302 + expectedStatus: options.expectedStatus || HttpStatusCode.FOUND_302 }) if (res.status !== HttpStatusCode.FOUND_302) return @@ -47,18 +35,17 @@ async function loginExternal (options: { const location = res.header.location const { externalAuthToken } = decodeQueryString(location) - const resLogin = await loginUsingExternalToken( - options.server, - options.username, - externalAuthToken as string, - options.statusCodeExpectedStep2 - ) + const resLogin = await options.server.login.loginUsingExternalToken({ + username: options.username, + externalAuthToken: externalAuthToken as string, + expectedStatus: options.expectedStatusStep2 + }) return resLogin.body } describe('Test external auth plugins', function () { - let server: ServerInfo + let server: PeerTubeServer let cyanAccessToken: string let cyanRefreshToken: string @@ -71,22 +58,16 @@ describe('Test external auth plugins', function () { before(async function () { this.timeout(30000) - server = await flushAndRunServer(1) + server = await createSingleServer(1) await setAccessTokensToServers([ server ]) for (const suffix of [ 'one', 'two', 'three' ]) { - await installPlugin({ - url: server.url, - accessToken: server.accessToken, - path: getPluginTestPath('-external-auth-' + suffix) - }) + await server.plugins.install({ path: PluginsCommand.getPluginTestPath('-external-auth-' + suffix) }) } }) it('Should display the correct configuration', async function () { - const res = await getConfig(server.url) - - const config: ServerConfig = res.body + const config = await server.config.getConfig() const auths = config.plugin.registeredExternalAuths expect(auths).to.have.lengthOf(8) @@ -98,15 +79,14 @@ describe('Test external auth plugins', function () { }) it('Should redirect for a Cyan login', async function () { - const res = await getExternalAuth({ - url: server.url, + const res = await server.plugins.getExternalAuth({ npmName: 'test-external-auth-one', npmVersion: '0.0.1', authName: 'external-auth-1', query: { username: 'cyan' }, - statusCodeExpected: HttpStatusCode.FOUND_302 + expectedStatus: HttpStatusCode.FOUND_302 }) const location = res.header.location @@ -121,13 +101,17 @@ describe('Test external auth plugins', function () { }) it('Should reject auto external login with a missing or invalid token', async function () { - await loginUsingExternalToken(server, 'cyan', '', HttpStatusCode.BAD_REQUEST_400) - await loginUsingExternalToken(server, 'cyan', 'blabla', HttpStatusCode.BAD_REQUEST_400) + const command = server.login + + await command.loginUsingExternalToken({ username: 'cyan', externalAuthToken: '', expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) + await command.loginUsingExternalToken({ username: 'cyan', externalAuthToken: 'blabla', expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) }) it('Should reject auto external login with a missing or invalid username', async function () { - await loginUsingExternalToken(server, '', externalAuthToken, HttpStatusCode.BAD_REQUEST_400) - await loginUsingExternalToken(server, '', externalAuthToken, HttpStatusCode.BAD_REQUEST_400) + const command = server.login + + await command.loginUsingExternalToken({ username: '', externalAuthToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) + await command.loginUsingExternalToken({ username: '', externalAuthToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) }) it('Should reject auto external login with an expired token', async function () { @@ -135,9 +119,13 @@ describe('Test external auth plugins', function () { await wait(5000) - await loginUsingExternalToken(server, 'cyan', externalAuthToken, HttpStatusCode.BAD_REQUEST_400) + await server.login.loginUsingExternalToken({ + username: 'cyan', + externalAuthToken, + expectedStatus: HttpStatusCode.BAD_REQUEST_400 + }) - await waitUntilLog(server, 'expired external auth token', 2) + await server.servers.waitUntilLog('expired external auth token', 2) }) it('Should auto login Cyan, create the user and use the token', async function () { @@ -157,9 +145,7 @@ describe('Test external auth plugins', function () { } { - const res = await getMyUserInformation(server.url, cyanAccessToken) - - const body: User = res.body + const body = await server.users.getMyInfo({ token: cyanAccessToken }) expect(body.username).to.equal('cyan') expect(body.account.displayName).to.equal('cyan') expect(body.email).to.equal('cyan@example.com') @@ -181,9 +167,7 @@ describe('Test external auth plugins', function () { } { - const res = await getMyUserInformation(server.url, kefkaAccessToken) - - const body: User = res.body + const body = await server.users.getMyInfo({ token: kefkaAccessToken }) expect(body.username).to.equal('kefka') expect(body.account.displayName).to.equal('Kefka Palazzo') expect(body.email).to.equal('kefka@example.com') @@ -193,43 +177,39 @@ describe('Test external auth plugins', function () { it('Should refresh Cyan token, but not Kefka token', async function () { { - const resRefresh = await refreshToken(server, cyanRefreshToken) + const resRefresh = await server.login.refreshToken({ refreshToken: cyanRefreshToken }) cyanAccessToken = resRefresh.body.access_token cyanRefreshToken = resRefresh.body.refresh_token - const res = await getMyUserInformation(server.url, cyanAccessToken) - const user: User = res.body - expect(user.username).to.equal('cyan') + const body = await server.users.getMyInfo({ token: cyanAccessToken }) + expect(body.username).to.equal('cyan') } { - await refreshToken(server, kefkaRefreshToken, HttpStatusCode.BAD_REQUEST_400) + await server.login.refreshToken({ refreshToken: kefkaRefreshToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) } }) it('Should update Cyan profile', async function () { - await updateMyUser({ - url: server.url, - accessToken: cyanAccessToken, + await server.users.updateMe({ + token: cyanAccessToken, displayName: 'Cyan Garamonde', description: 'Retainer to the king of Doma' }) - const res = await getMyUserInformation(server.url, cyanAccessToken) - - const body: User = res.body + const body = await server.users.getMyInfo({ token: cyanAccessToken }) expect(body.account.displayName).to.equal('Cyan Garamonde') expect(body.account.description).to.equal('Retainer to the king of Doma') }) it('Should logout Cyan', async function () { - await logout(server.url, cyanAccessToken) + await server.login.logout({ token: cyanAccessToken }) }) it('Should have logged out Cyan', async function () { - await waitUntilLog(server, 'On logout cyan') + await server.servers.waitUntilLog('On logout cyan') - await getMyUserInformation(server.url, cyanAccessToken, HttpStatusCode.UNAUTHORIZED_401) + await server.users.getMyInfo({ token: cyanAccessToken, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) }) it('Should login Cyan and keep the old existing profile', async function () { @@ -247,9 +227,7 @@ describe('Test external auth plugins', function () { cyanAccessToken = res.access_token } - const res = await getMyUserInformation(server.url, cyanAccessToken) - - const body: User = res.body + const body = await server.users.getMyInfo({ token: cyanAccessToken }) expect(body.username).to.equal('cyan') expect(body.account.displayName).to.equal('Cyan Garamonde') expect(body.account.description).to.equal('Retainer to the king of Doma') @@ -257,12 +235,11 @@ describe('Test external auth plugins', function () { }) it('Should not update an external auth email', async function () { - await updateMyUser({ - url: server.url, - accessToken: cyanAccessToken, + await server.users.updateMe({ + token: cyanAccessToken, email: 'toto@example.com', currentPassword: 'toto', - statusCodeExpected: HttpStatusCode.BAD_REQUEST_400 + expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) }) @@ -271,18 +248,16 @@ describe('Test external auth plugins', function () { await wait(5000) - await getMyUserInformation(server.url, kefkaAccessToken, HttpStatusCode.UNAUTHORIZED_401) + await server.users.getMyInfo({ token: kefkaAccessToken, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) }) it('Should unregister external-auth-2 and do not login existing Kefka', async function () { - await updatePluginSettings({ - url: server.url, - accessToken: server.accessToken, + await server.plugins.updateSettings({ npmName: 'peertube-plugin-test-external-auth-one', settings: { disableKefka: true } }) - await userLogin(server, { username: 'kefka', password: 'fake' }, HttpStatusCode.BAD_REQUEST_400) + await server.login.login({ user: { username: 'kefka', password: 'fake' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) await loginExternal({ server, @@ -292,14 +267,12 @@ describe('Test external auth plugins', function () { username: 'kefka' }, username: 'kefka', - statusCodeExpected: HttpStatusCode.NOT_FOUND_404 + expectedStatus: HttpStatusCode.NOT_FOUND_404 }) }) it('Should have disabled this auth', async function () { - const res = await getConfig(server.url) - - const config: ServerConfig = res.body + const config = await server.config.getConfig() const auths = config.plugin.registeredExternalAuths expect(auths).to.have.lengthOf(7) @@ -309,11 +282,7 @@ describe('Test external auth plugins', function () { }) it('Should uninstall the plugin one and do not login Cyan', async function () { - await uninstallPlugin({ - url: server.url, - accessToken: server.accessToken, - npmName: 'peertube-plugin-test-external-auth-one' - }) + await server.plugins.uninstall({ npmName: 'peertube-plugin-test-external-auth-one' }) await loginExternal({ server, @@ -323,12 +292,12 @@ describe('Test external auth plugins', function () { username: 'cyan' }, username: 'cyan', - statusCodeExpected: HttpStatusCode.NOT_FOUND_404 + expectedStatus: HttpStatusCode.NOT_FOUND_404 }) - await userLogin(server, { username: 'cyan', password: null }, HttpStatusCode.BAD_REQUEST_400) - await userLogin(server, { username: 'cyan', password: '' }, HttpStatusCode.BAD_REQUEST_400) - await userLogin(server, { username: 'cyan', password: 'fake' }, HttpStatusCode.BAD_REQUEST_400) + await server.login.login({ user: { username: 'cyan', password: null }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) + await server.login.login({ user: { username: 'cyan', password: '' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) + await server.login.login({ user: { username: 'cyan', password: 'fake' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) }) it('Should not login kefka with another plugin', async function () { @@ -337,7 +306,7 @@ describe('Test external auth plugins', function () { npmName: 'test-external-auth-two', authName: 'external-auth-4', username: 'kefka2', - statusCodeExpectedStep2: HttpStatusCode.BAD_REQUEST_400 + expectedStatusStep2: HttpStatusCode.BAD_REQUEST_400 }) await loginExternal({ @@ -345,31 +314,24 @@ describe('Test external auth plugins', function () { npmName: 'test-external-auth-two', authName: 'external-auth-4', username: 'kefka', - statusCodeExpectedStep2: HttpStatusCode.BAD_REQUEST_400 + expectedStatusStep2: HttpStatusCode.BAD_REQUEST_400 }) }) it('Should not login an existing user', async function () { - await createUser({ - url: server.url, - accessToken: server.accessToken, - username: 'existing_user', - password: 'super_password' - }) + await server.users.create({ username: 'existing_user', password: 'super_password' }) await loginExternal({ server, npmName: 'test-external-auth-two', authName: 'external-auth-6', username: 'existing_user', - statusCodeExpectedStep2: HttpStatusCode.BAD_REQUEST_400 + expectedStatusStep2: HttpStatusCode.BAD_REQUEST_400 }) }) it('Should display the correct configuration', async function () { - const res = await getConfig(server.url) - - const config: ServerConfig = res.body + const config = await server.config.getConfig() const auths = config.plugin.registeredExternalAuths expect(auths).to.have.lengthOf(6) @@ -390,9 +352,8 @@ describe('Test external auth plugins', function () { username: 'cid' }) - const resLogout = await logout(server.url, resLogin.access_token) - - expect(resLogout.body.redirectUrl).to.equal('https://example.com/redirectUrl') + const { redirectUrl } = await server.login.logout({ token: resLogin.access_token }) + expect(redirectUrl).to.equal('https://example.com/redirectUrl') }) it('Should call the plugin\'s onLogout method with the request', async function () { @@ -403,8 +364,7 @@ describe('Test external auth plugins', function () { username: 'cid' }) - const resLogout = await logout(server.url, resLogin.access_token) - - expect(resLogout.body.redirectUrl).to.equal('https://example.com/redirectUrl?access_token=' + resLogin.access_token) + const { redirectUrl } = await server.login.logout({ token: resLogin.access_token }) + expect(redirectUrl).to.equal('https://example.com/redirectUrl?access_token=' + resLogin.access_token) }) }) diff --git a/server/tests/plugins/filter-hooks.ts b/server/tests/plugins/filter-hooks.ts index 644b41dea..02915f08c 100644 --- a/server/tests/plugins/filter-hooks.ts +++ b/server/tests/plugins/filter-hooks.ts @@ -2,192 +2,152 @@ import 'mocha' import * as chai from 'chai' -import { advancedVideoChannelSearch } from '@shared/extra-utils/search/video-channels' -import { ServerConfig } from '@shared/models' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' import { - addVideoCommentReply, - addVideoCommentThread, - advancedVideoPlaylistSearch, - advancedVideosSearch, - createLive, - createVideoPlaylist, + cleanupTests, + createMultipleServers, doubleFollow, - getAccountVideos, - getConfig, - getMyVideos, - getPluginTestPath, - getVideo, - getVideoChannelVideos, - getVideoCommentThreads, - getVideoPlaylist, - getVideosList, - getVideosListPagination, - getVideoThreadComments, - getVideoWithToken, - installPlugin, + FIXTURE_URLS, makeRawRequest, - registerUser, + PeerTubeServer, + PluginsCommand, setAccessTokensToServers, setDefaultVideoChannel, - updateCustomSubConfig, - updateVideo, - uploadVideo, - uploadVideoAndGetId, waitJobs -} from '../../../shared/extra-utils' -import { cleanupTests, flushAndRunMultipleServers, ServerInfo, waitUntilLog } from '../../../shared/extra-utils/server/servers' -import { getGoodVideoUrl, getMyVideoImports, importVideo } from '../../../shared/extra-utils/videos/video-imports' -import { - VideoCommentThreadTree, - VideoDetails, - VideoImport, - VideoImportState, - VideoPlaylist, - VideoPlaylistPrivacy, - VideoPrivacy -} from '../../../shared/models/videos' +} from '@shared/extra-utils' +import { HttpStatusCode, VideoDetails, VideoImportState, VideoPlaylist, VideoPlaylistPrivacy, VideoPrivacy } from '@shared/models' const expect = chai.expect describe('Test plugin filter hooks', function () { - let servers: ServerInfo[] + let servers: PeerTubeServer[] let videoUUID: string let threadId: number before(async function () { this.timeout(60000) - servers = await flushAndRunMultipleServers(2) + servers = await createMultipleServers(2) await setAccessTokensToServers(servers) await setDefaultVideoChannel(servers) await doubleFollow(servers[0], servers[1]) - await installPlugin({ - url: servers[0].url, - accessToken: servers[0].accessToken, - path: getPluginTestPath() - }) - - await installPlugin({ - url: servers[0].url, - accessToken: servers[0].accessToken, - path: getPluginTestPath('-filter-translations') - }) + await servers[0].plugins.install({ path: PluginsCommand.getPluginTestPath() }) + await servers[0].plugins.install({ path: PluginsCommand.getPluginTestPath('-filter-translations') }) for (let i = 0; i < 10; i++) { - await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'default video ' + i }) + await servers[0].videos.upload({ attributes: { name: 'default video ' + i } }) } - const res = await getVideosList(servers[0].url) - videoUUID = res.body.data[0].uuid - - await updateCustomSubConfig(servers[0].url, servers[0].accessToken, { - live: { enabled: true }, - signup: { enabled: true }, - import: { - videos: { - http: { enabled: true }, - torrent: { enabled: true } + const { data } = await servers[0].videos.list() + videoUUID = data[0].uuid + + await servers[0].config.updateCustomSubConfig({ + newConfig: { + live: { enabled: true }, + signup: { enabled: true }, + import: { + videos: { + http: { enabled: true }, + torrent: { enabled: true } + } } } }) }) it('Should run filter:api.videos.list.params', async function () { - const res = await getVideosListPagination(servers[0].url, 0, 2) + const { data } = await servers[0].videos.list({ start: 0, count: 2 }) // 2 plugins do +1 to the count parameter - expect(res.body.data).to.have.lengthOf(4) + expect(data).to.have.lengthOf(4) }) it('Should run filter:api.videos.list.result', async function () { - const res = await getVideosListPagination(servers[0].url, 0, 0) + const { total } = await servers[0].videos.list({ start: 0, count: 0 }) // Plugin do +1 to the total result - expect(res.body.total).to.equal(11) + expect(total).to.equal(11) }) it('Should run filter:api.accounts.videos.list.params', async function () { - const res = await getAccountVideos(servers[0].url, servers[0].accessToken, 'root', 0, 2) + const { data } = await servers[0].videos.listByAccount({ handle: 'root', start: 0, count: 2 }) // 1 plugin do +1 to the count parameter - expect(res.body.data).to.have.lengthOf(3) + expect(data).to.have.lengthOf(3) }) it('Should run filter:api.accounts.videos.list.result', async function () { - const res = await getAccountVideos(servers[0].url, servers[0].accessToken, 'root', 0, 2) + const { total } = await servers[0].videos.listByAccount({ handle: 'root', start: 0, count: 2 }) // Plugin do +2 to the total result - expect(res.body.total).to.equal(12) + expect(total).to.equal(12) }) it('Should run filter:api.video-channels.videos.list.params', async function () { - const res = await getVideoChannelVideos(servers[0].url, servers[0].accessToken, 'root_channel', 0, 2) + const { data } = await servers[0].videos.listByChannel({ handle: 'root_channel', start: 0, count: 2 }) // 1 plugin do +3 to the count parameter - expect(res.body.data).to.have.lengthOf(5) + expect(data).to.have.lengthOf(5) }) it('Should run filter:api.video-channels.videos.list.result', async function () { - const res = await getVideoChannelVideos(servers[0].url, servers[0].accessToken, 'root_channel', 0, 2) + const { total } = await servers[0].videos.listByChannel({ handle: 'root_channel', start: 0, count: 2 }) // Plugin do +3 to the total result - expect(res.body.total).to.equal(13) + expect(total).to.equal(13) }) it('Should run filter:api.user.me.videos.list.params', async function () { - const res = await getMyVideos(servers[0].url, servers[0].accessToken, 0, 2) + const { data } = await servers[0].videos.listMyVideos({ start: 0, count: 2 }) // 1 plugin do +4 to the count parameter - expect(res.body.data).to.have.lengthOf(6) + expect(data).to.have.lengthOf(6) }) it('Should run filter:api.user.me.videos.list.result', async function () { - const res = await getMyVideos(servers[0].url, servers[0].accessToken, 0, 2) + const { total } = await servers[0].videos.listMyVideos({ start: 0, count: 2 }) // Plugin do +4 to the total result - expect(res.body.total).to.equal(14) + expect(total).to.equal(14) }) it('Should run filter:api.video.get.result', async function () { - const res = await getVideo(servers[0].url, videoUUID) - - expect(res.body.name).to.contain('<3') + const video = await servers[0].videos.get({ id: videoUUID }) + expect(video.name).to.contain('<3') }) it('Should run filter:api.video.upload.accept.result', async function () { - await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video with bad word' }, HttpStatusCode.FORBIDDEN_403) + await servers[0].videos.upload({ attributes: { name: 'video with bad word' }, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) }) it('Should run filter:api.live-video.create.accept.result', async function () { const attributes = { name: 'video with bad word', privacy: VideoPrivacy.PUBLIC, - channelId: servers[0].videoChannel.id + channelId: servers[0].store.channel.id } - await createLive(servers[0].url, servers[0].accessToken, attributes, HttpStatusCode.FORBIDDEN_403) + await servers[0].live.create({ fields: attributes, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) }) it('Should run filter:api.video.pre-import-url.accept.result', async function () { - const baseAttributes = { + const attributes = { name: 'normal title', privacy: VideoPrivacy.PUBLIC, - channelId: servers[0].videoChannel.id, - targetUrl: getGoodVideoUrl() + 'bad' + channelId: servers[0].store.channel.id, + targetUrl: FIXTURE_URLS.goodVideo + 'bad' } - await importVideo(servers[0].url, servers[0].accessToken, baseAttributes, HttpStatusCode.FORBIDDEN_403) + await servers[0].imports.importVideo({ attributes, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) }) it('Should run filter:api.video.pre-import-torrent.accept.result', async function () { - const baseAttributes = { + const attributes = { name: 'bad torrent', privacy: VideoPrivacy.PUBLIC, - channelId: servers[0].videoChannel.id, + channelId: servers[0].store.channel.id, torrentfile: 'video-720p.torrent' as any } - await importVideo(servers[0].url, servers[0].accessToken, baseAttributes, HttpStatusCode.FORBIDDEN_403) + await servers[0].imports.importVideo({ attributes, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) }) it('Should run filter:api.video.post-import-url.accept.result', async function () { @@ -196,21 +156,21 @@ describe('Test plugin filter hooks', function () { let videoImportId: number { - const baseAttributes = { + const attributes = { name: 'title with bad word', privacy: VideoPrivacy.PUBLIC, - channelId: servers[0].videoChannel.id, - targetUrl: getGoodVideoUrl() + channelId: servers[0].store.channel.id, + targetUrl: FIXTURE_URLS.goodVideo } - const res = await importVideo(servers[0].url, servers[0].accessToken, baseAttributes) - videoImportId = res.body.id + const body = await servers[0].imports.importVideo({ attributes }) + videoImportId = body.id } await waitJobs(servers) { - const res = await getMyVideoImports(servers[0].url, servers[0].accessToken) - const videoImports = res.body.data as VideoImport[] + const body = await servers[0].imports.getMyVideoImports() + const videoImports = body.data const videoImport = videoImports.find(i => i.id === videoImportId) @@ -225,21 +185,20 @@ describe('Test plugin filter hooks', function () { let videoImportId: number { - const baseAttributes = { + const attributes = { name: 'title with bad word', privacy: VideoPrivacy.PUBLIC, - channelId: servers[0].videoChannel.id, + channelId: servers[0].store.channel.id, torrentfile: 'video-720p.torrent' as any } - const res = await importVideo(servers[0].url, servers[0].accessToken, baseAttributes) - videoImportId = res.body.id + const body = await servers[0].imports.importVideo({ attributes }) + videoImportId = body.id } await waitJobs(servers) { - const res = await getMyVideoImports(servers[0].url, servers[0].accessToken) - const videoImports = res.body.data as VideoImport[] + const { data: videoImports } = await servers[0].imports.getMyVideoImports() const videoImport = videoImports.find(i => i.id === videoImportId) @@ -249,60 +208,71 @@ describe('Test plugin filter hooks', function () { }) it('Should run filter:api.video-thread.create.accept.result', async function () { - await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoUUID, 'comment with bad word', HttpStatusCode.FORBIDDEN_403) + await servers[0].comments.createThread({ + videoId: videoUUID, + text: 'comment with bad word', + expectedStatus: HttpStatusCode.FORBIDDEN_403 + }) }) it('Should run filter:api.video-comment-reply.create.accept.result', async function () { - const res = await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoUUID, 'thread') - threadId = res.body.comment.id - - await addVideoCommentReply( - servers[0].url, - servers[0].accessToken, - videoUUID, - threadId, - 'comment with bad word', - HttpStatusCode.FORBIDDEN_403 - ) - await addVideoCommentReply(servers[0].url, servers[0].accessToken, videoUUID, threadId, 'comment with good word', HttpStatusCode.OK_200) + const created = await servers[0].comments.createThread({ videoId: videoUUID, text: 'thread' }) + threadId = created.id + + await servers[0].comments.addReply({ + videoId: videoUUID, + toCommentId: threadId, + text: 'comment with bad word', + expectedStatus: HttpStatusCode.FORBIDDEN_403 + }) + await servers[0].comments.addReply({ + videoId: videoUUID, + toCommentId: threadId, + text: 'comment with good word', + expectedStatus: HttpStatusCode.OK_200 + }) }) it('Should run filter:api.video-threads.list.params', async function () { - const res = await getVideoCommentThreads(servers[0].url, videoUUID, 0, 0) + const { data } = await servers[0].comments.listThreads({ videoId: videoUUID, start: 0, count: 0 }) // our plugin do +1 to the count parameter - expect(res.body.data).to.have.lengthOf(1) + expect(data).to.have.lengthOf(1) }) it('Should run filter:api.video-threads.list.result', async function () { - const res = await getVideoCommentThreads(servers[0].url, videoUUID, 0, 0) + const { total } = await servers[0].comments.listThreads({ videoId: videoUUID, start: 0, count: 0 }) // Plugin do +1 to the total result - expect(res.body.total).to.equal(2) + expect(total).to.equal(2) }) it('Should run filter:api.video-thread-comments.list.params') it('Should run filter:api.video-thread-comments.list.result', async function () { - const res = await getVideoThreadComments(servers[0].url, videoUUID, threadId) + const thread = await servers[0].comments.getThread({ videoId: videoUUID, threadId }) - const thread = res.body as VideoCommentThreadTree expect(thread.comment.text.endsWith(' <3')).to.be.true }) - describe('Should run filter:video.auto-blacklist.result', function () { + it('Should run filter:api.overviews.videos.list.{params,result}', async function () { + await servers[0].overviews.getVideos({ page: 1 }) - async function checkIsBlacklisted (oldRes: any, value: boolean) { - const videoId = oldRes.body.video.uuid + // 3 because we get 3 samples per page + await servers[0].servers.waitUntilLog('Run hook filter:api.overviews.videos.list.params', 3) + await servers[0].servers.waitUntilLog('Run hook filter:api.overviews.videos.list.result', 3) + }) - const res = await getVideoWithToken(servers[0].url, servers[0].accessToken, videoId) - const video: VideoDetails = res.body + describe('Should run filter:video.auto-blacklist.result', function () { + + async function checkIsBlacklisted (id: number | string, value: boolean) { + const video = await servers[0].videos.getWithToken({ id }) expect(video.blacklisted).to.equal(value) } it('Should blacklist on upload', async function () { - const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video please blacklist me' }) - await checkIsBlacklisted(res, true) + const { uuid } = await servers[0].videos.upload({ attributes: { name: 'video please blacklist me' } }) + await checkIsBlacklisted(uuid, true) }) it('Should blacklist on import', async function () { @@ -310,60 +280,62 @@ describe('Test plugin filter hooks', function () { const attributes = { name: 'video please blacklist me', - targetUrl: getGoodVideoUrl(), - channelId: servers[0].videoChannel.id + targetUrl: FIXTURE_URLS.goodVideo, + channelId: servers[0].store.channel.id } - const res = await importVideo(servers[0].url, servers[0].accessToken, attributes) - await checkIsBlacklisted(res, true) + const body = await servers[0].imports.importVideo({ attributes }) + await checkIsBlacklisted(body.video.uuid, true) }) it('Should blacklist on update', async function () { - const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video' }) - const videoId = res.body.video.uuid - await checkIsBlacklisted(res, false) + const { uuid } = await servers[0].videos.upload({ attributes: { name: 'video' } }) + await checkIsBlacklisted(uuid, false) - await updateVideo(servers[0].url, servers[0].accessToken, videoId, { name: 'please blacklist me' }) - await checkIsBlacklisted(res, true) + await servers[0].videos.update({ id: uuid, attributes: { name: 'please blacklist me' } }) + await checkIsBlacklisted(uuid, true) }) it('Should blacklist on remote upload', async function () { this.timeout(120000) - const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'remote please blacklist me' }) + const { uuid } = await servers[1].videos.upload({ attributes: { name: 'remote please blacklist me' } }) await waitJobs(servers) - await checkIsBlacklisted(res, true) + await checkIsBlacklisted(uuid, true) }) it('Should blacklist on remote update', async function () { this.timeout(120000) - const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video' }) + const { uuid } = await servers[1].videos.upload({ attributes: { name: 'video' } }) await waitJobs(servers) - const videoId = res.body.video.uuid - await checkIsBlacklisted(res, false) + await checkIsBlacklisted(uuid, false) - await updateVideo(servers[1].url, servers[1].accessToken, videoId, { name: 'please blacklist me' }) + await servers[1].videos.update({ id: uuid, attributes: { name: 'please blacklist me' } }) await waitJobs(servers) - await checkIsBlacklisted(res, true) + await checkIsBlacklisted(uuid, true) }) }) describe('Should run filter:api.user.signup.allowed.result', function () { it('Should run on config endpoint', async function () { - const res = await getConfig(servers[0].url) - expect((res.body as ServerConfig).signup.allowed).to.be.true + const body = await servers[0].config.getConfig() + expect(body.signup.allowed).to.be.true }) it('Should allow a signup', async function () { - await registerUser(servers[0].url, 'john', 'password') + await servers[0].users.register({ username: 'john', password: 'password' }) }) it('Should not allow a signup', async function () { - const res = await registerUser(servers[0].url, 'jma', 'password', HttpStatusCode.FORBIDDEN_403) + const res = await servers[0].users.register({ + username: 'jma', + password: 'password', + expectedStatus: HttpStatusCode.FORBIDDEN_403 + }) expect(res.body.error).to.equal('No jma') }) @@ -375,13 +347,15 @@ describe('Test plugin filter hooks', function () { before(async function () { this.timeout(120000) - await updateCustomSubConfig(servers[0].url, servers[0].accessToken, { - transcoding: { - webtorrent: { - enabled: true - }, - hls: { - enabled: true + await servers[0].config.updateCustomSubConfig({ + newConfig: { + transcoding: { + webtorrent: { + enabled: true + }, + hls: { + enabled: true + } } } }) @@ -389,15 +363,14 @@ describe('Test plugin filter hooks', function () { const uuids: string[] = [] for (const name of [ 'bad torrent', 'bad file', 'bad playlist file' ]) { - const uuid = (await uploadVideoAndGetId({ server: servers[0], videoName: name })).uuid + const uuid = (await servers[0].videos.quickUpload({ name: name })).uuid uuids.push(uuid) } await waitJobs(servers) for (const uuid of uuids) { - const res = await getVideo(servers[0].url, uuid) - downloadVideos.push(res.body) + downloadVideos.push(await servers[0].videos.get({ id: uuid })) } }) @@ -437,25 +410,26 @@ describe('Test plugin filter hooks', function () { before(async function () { this.timeout(60000) - await updateCustomSubConfig(servers[0].url, servers[0].accessToken, { - transcoding: { - enabled: false + await servers[0].config.updateCustomSubConfig({ + newConfig: { + transcoding: { + enabled: false + } } }) for (const name of [ 'bad embed', 'good embed' ]) { { - const uuid = (await uploadVideoAndGetId({ server: servers[0], videoName: name })).uuid - const res = await getVideo(servers[0].url, uuid) - embedVideos.push(res.body) + const uuid = (await servers[0].videos.quickUpload({ name: name })).uuid + embedVideos.push(await servers[0].videos.get({ id: uuid })) } { - const playlistAttrs = { displayName: name, videoChannelId: servers[0].videoChannel.id, privacy: VideoPlaylistPrivacy.PUBLIC } - const res = await createVideoPlaylist({ url: servers[0].url, token: servers[0].accessToken, playlistAttrs }) + const attributes = { displayName: name, videoChannelId: servers[0].store.channel.id, privacy: VideoPlaylistPrivacy.PUBLIC } + const { id } = await servers[0].playlists.create({ attributes }) - const resPlaylist = await getVideoPlaylist(servers[0].url, res.body.videoPlaylist.id) - embedPlaylists.push(resPlaylist.body) + const playlist = await servers[0].playlists.get({ playlistId: id }) + embedPlaylists.push(playlist) } } }) @@ -474,78 +448,92 @@ describe('Test plugin filter hooks', function () { describe('Search filters', function () { before(async function () { - await updateCustomSubConfig(servers[0].url, servers[0].accessToken, { - search: { - searchIndex: { - enabled: true, - isDefaultSearch: false, - disableLocalSearch: false + await servers[0].config.updateCustomSubConfig({ + newConfig: { + search: { + searchIndex: { + enabled: true, + isDefaultSearch: false, + disableLocalSearch: false + } } } }) }) it('Should run filter:api.search.videos.local.list.{params,result}', async function () { - await advancedVideosSearch(servers[0].url, { - search: 'Sun Quan' + await servers[0].search.advancedVideoSearch({ + search: { + search: 'Sun Quan' + } }) - await waitUntilLog(servers[0], 'Run hook filter:api.search.videos.local.list.params', 1) - await waitUntilLog(servers[0], 'Run hook filter:api.search.videos.local.list.result', 1) + await servers[0].servers.waitUntilLog('Run hook filter:api.search.videos.local.list.params', 1) + await servers[0].servers.waitUntilLog('Run hook filter:api.search.videos.local.list.result', 1) }) it('Should run filter:api.search.videos.index.list.{params,result}', async function () { - await advancedVideosSearch(servers[0].url, { - search: 'Sun Quan', - searchTarget: 'search-index' + await servers[0].search.advancedVideoSearch({ + search: { + search: 'Sun Quan', + searchTarget: 'search-index' + } }) - await waitUntilLog(servers[0], 'Run hook filter:api.search.videos.local.list.params', 1) - await waitUntilLog(servers[0], 'Run hook filter:api.search.videos.local.list.result', 1) - await waitUntilLog(servers[0], 'Run hook filter:api.search.videos.index.list.params', 1) - await waitUntilLog(servers[0], 'Run hook filter:api.search.videos.index.list.result', 1) + await servers[0].servers.waitUntilLog('Run hook filter:api.search.videos.local.list.params', 1) + await servers[0].servers.waitUntilLog('Run hook filter:api.search.videos.local.list.result', 1) + await servers[0].servers.waitUntilLog('Run hook filter:api.search.videos.index.list.params', 1) + await servers[0].servers.waitUntilLog('Run hook filter:api.search.videos.index.list.result', 1) }) it('Should run filter:api.search.video-channels.local.list.{params,result}', async function () { - await advancedVideoChannelSearch(servers[0].url, { - search: 'Sun Ce' + await servers[0].search.advancedChannelSearch({ + search: { + search: 'Sun Ce' + } }) - await waitUntilLog(servers[0], 'Run hook filter:api.search.video-channels.local.list.params', 1) - await waitUntilLog(servers[0], 'Run hook filter:api.search.video-channels.local.list.result', 1) + await servers[0].servers.waitUntilLog('Run hook filter:api.search.video-channels.local.list.params', 1) + await servers[0].servers.waitUntilLog('Run hook filter:api.search.video-channels.local.list.result', 1) }) it('Should run filter:api.search.video-channels.index.list.{params,result}', async function () { - await advancedVideoChannelSearch(servers[0].url, { - search: 'Sun Ce', - searchTarget: 'search-index' + await servers[0].search.advancedChannelSearch({ + search: { + search: 'Sun Ce', + searchTarget: 'search-index' + } }) - await waitUntilLog(servers[0], 'Run hook filter:api.search.video-channels.local.list.params', 1) - await waitUntilLog(servers[0], 'Run hook filter:api.search.video-channels.local.list.result', 1) - await waitUntilLog(servers[0], 'Run hook filter:api.search.video-channels.index.list.params', 1) - await waitUntilLog(servers[0], 'Run hook filter:api.search.video-channels.index.list.result', 1) + await servers[0].servers.waitUntilLog('Run hook filter:api.search.video-channels.local.list.params', 1) + await servers[0].servers.waitUntilLog('Run hook filter:api.search.video-channels.local.list.result', 1) + await servers[0].servers.waitUntilLog('Run hook filter:api.search.video-channels.index.list.params', 1) + await servers[0].servers.waitUntilLog('Run hook filter:api.search.video-channels.index.list.result', 1) }) it('Should run filter:api.search.video-playlists.local.list.{params,result}', async function () { - await advancedVideoPlaylistSearch(servers[0].url, { - search: 'Sun Jian' + await servers[0].search.advancedPlaylistSearch({ + search: { + search: 'Sun Jian' + } }) - await waitUntilLog(servers[0], 'Run hook filter:api.search.video-playlists.local.list.params', 1) - await waitUntilLog(servers[0], 'Run hook filter:api.search.video-playlists.local.list.result', 1) + await servers[0].servers.waitUntilLog('Run hook filter:api.search.video-playlists.local.list.params', 1) + await servers[0].servers.waitUntilLog('Run hook filter:api.search.video-playlists.local.list.result', 1) }) it('Should run filter:api.search.video-playlists.index.list.{params,result}', async function () { - await advancedVideoPlaylistSearch(servers[0].url, { - search: 'Sun Jian', - searchTarget: 'search-index' + await servers[0].search.advancedPlaylistSearch({ + search: { + search: 'Sun Jian', + searchTarget: 'search-index' + } }) - await waitUntilLog(servers[0], 'Run hook filter:api.search.video-playlists.local.list.params', 1) - await waitUntilLog(servers[0], 'Run hook filter:api.search.video-playlists.local.list.result', 1) - await waitUntilLog(servers[0], 'Run hook filter:api.search.video-playlists.index.list.params', 1) - await waitUntilLog(servers[0], 'Run hook filter:api.search.video-playlists.index.list.result', 1) + await servers[0].servers.waitUntilLog('Run hook filter:api.search.video-playlists.local.list.params', 1) + await servers[0].servers.waitUntilLog('Run hook filter:api.search.video-playlists.local.list.result', 1) + await servers[0].servers.waitUntilLog('Run hook filter:api.search.video-playlists.index.list.params', 1) + await servers[0].servers.waitUntilLog('Run hook filter:api.search.video-playlists.index.list.result', 1) }) }) diff --git a/server/tests/plugins/html-injection.ts b/server/tests/plugins/html-injection.ts index 4fa8caa3a..95c0cd687 100644 --- a/server/tests/plugins/html-injection.ts +++ b/server/tests/plugins/html-injection.ts @@ -4,31 +4,32 @@ import 'mocha' import * as chai from 'chai' import { cleanupTests, - flushAndRunServer, - getPluginsCSS, - installPlugin, + createSingleServer, makeHTMLRequest, - ServerInfo, - setAccessTokensToServers, - uninstallPlugin + PeerTubeServer, + PluginsCommand, + setAccessTokensToServers } from '../../../shared/extra-utils' const expect = chai.expect describe('Test plugins HTML injection', function () { - let server: ServerInfo = null + let server: PeerTubeServer = null + let command: PluginsCommand before(async function () { this.timeout(30000) - server = await flushAndRunServer(1) + server = await createSingleServer(1) await setAccessTokensToServers([ server ]) + + command = server.plugins }) it('Should not inject global css file in HTML', async function () { { - const res = await getPluginsCSS(server.url) - expect(res.text).to.be.empty + const text = await command.getCSS() + expect(text).to.be.empty } for (const path of [ '/', '/videos/embed/1', '/video-playlists/embed/1' ]) { @@ -40,17 +41,13 @@ describe('Test plugins HTML injection', function () { it('Should install a plugin and a theme', async function () { this.timeout(30000) - await installPlugin({ - url: server.url, - accessToken: server.accessToken, - npmName: 'peertube-plugin-hello-world' - }) + await command.install({ npmName: 'peertube-plugin-hello-world' }) }) it('Should have the correct global css', async function () { { - const res = await getPluginsCSS(server.url) - expect(res.text).to.contain('background-color: red') + const text = await command.getCSS() + expect(text).to.contain('background-color: red') } for (const path of [ '/', '/videos/embed/1', '/video-playlists/embed/1' ]) { @@ -60,15 +57,11 @@ describe('Test plugins HTML injection', function () { }) it('Should have an empty global css on uninstall', async function () { - await uninstallPlugin({ - url: server.url, - accessToken: server.accessToken, - npmName: 'peertube-plugin-hello-world' - }) + await command.uninstall({ npmName: 'peertube-plugin-hello-world' }) { - const res = await getPluginsCSS(server.url) - expect(res.text).to.be.empty + const text = await command.getCSS() + expect(text).to.be.empty } for (const path of [ '/', '/videos/embed/1', '/video-playlists/embed/1' ]) { diff --git a/server/tests/plugins/id-and-pass-auth.ts b/server/tests/plugins/id-and-pass-auth.ts index cbba638c2..fde0166f9 100644 --- a/server/tests/plugins/id-and-pass-auth.ts +++ b/server/tests/plugins/id-and-pass-auth.ts @@ -1,24 +1,12 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ import 'mocha' -import { cleanupTests, flushAndRunServer, ServerInfo, waitUntilLog } from '../../../shared/extra-utils/server/servers' -import { - getMyUserInformation, - getPluginTestPath, - installPlugin, - logout, - setAccessTokensToServers, - uninstallPlugin, - updateMyUser, - userLogin, - wait, - login, refreshToken, getConfig, updatePluginSettings, getUsersList -} from '../../../shared/extra-utils' -import { User, UserRole, ServerConfig } from '@shared/models' import { expect } from 'chai' +import { cleanupTests, createSingleServer, PeerTubeServer, PluginsCommand, setAccessTokensToServers, wait } from '@shared/extra-utils' +import { HttpStatusCode, UserRole } from '@shared/models' describe('Test id and pass auth plugins', function () { - let server: ServerInfo + let server: PeerTubeServer let crashAccessToken: string let crashRefreshToken: string @@ -29,22 +17,16 @@ describe('Test id and pass auth plugins', function () { before(async function () { this.timeout(30000) - server = await flushAndRunServer(1) + server = await createSingleServer(1) await setAccessTokensToServers([ server ]) for (const suffix of [ 'one', 'two', 'three' ]) { - await installPlugin({ - url: server.url, - accessToken: server.accessToken, - path: getPluginTestPath('-id-pass-auth-' + suffix) - }) + await server.plugins.install({ path: PluginsCommand.getPluginTestPath('-id-pass-auth-' + suffix) }) } }) it('Should display the correct configuration', async function () { - const res = await getConfig(server.url) - - const config: ServerConfig = res.body + const config = await server.config.getConfig() const auths = config.plugin.registeredIdAndPassAuths expect(auths).to.have.lengthOf(8) @@ -56,15 +38,14 @@ describe('Test id and pass auth plugins', function () { }) it('Should not login', async function () { - await userLogin(server, { username: 'toto', password: 'password' }, 400) + await server.login.login({ user: { username: 'toto', password: 'password' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) }) it('Should login Spyro, create the user and use the token', async function () { - const accessToken = await userLogin(server, { username: 'spyro', password: 'spyro password' }) + const accessToken = await server.login.getAccessToken({ username: 'spyro', password: 'spyro password' }) - const res = await getMyUserInformation(server.url, accessToken) + const body = await server.users.getMyInfo({ token: accessToken }) - const body: User = res.body expect(body.username).to.equal('spyro') expect(body.account.displayName).to.equal('Spyro the Dragon') expect(body.role).to.equal(UserRole.USER) @@ -72,15 +53,14 @@ describe('Test id and pass auth plugins', function () { it('Should login Crash, create the user and use the token', async function () { { - const res = await login(server.url, server.client, { username: 'crash', password: 'crash password' }) - crashAccessToken = res.body.access_token - crashRefreshToken = res.body.refresh_token + const body = await server.login.login({ user: { username: 'crash', password: 'crash password' } }) + crashAccessToken = body.access_token + crashRefreshToken = body.refresh_token } { - const res = await getMyUserInformation(server.url, crashAccessToken) + const body = await server.users.getMyInfo({ token: crashAccessToken }) - const body: User = res.body expect(body.username).to.equal('crash') expect(body.account.displayName).to.equal('Crash Bandicoot') expect(body.role).to.equal(UserRole.MODERATOR) @@ -89,15 +69,14 @@ describe('Test id and pass auth plugins', function () { it('Should login the first Laguna, create the user and use the token', async function () { { - const res = await login(server.url, server.client, { username: 'laguna', password: 'laguna password' }) - lagunaAccessToken = res.body.access_token - lagunaRefreshToken = res.body.refresh_token + const body = await server.login.login({ user: { username: 'laguna', password: 'laguna password' } }) + lagunaAccessToken = body.access_token + lagunaRefreshToken = body.refresh_token } { - const res = await getMyUserInformation(server.url, lagunaAccessToken) + const body = await server.users.getMyInfo({ token: lagunaAccessToken }) - const body: User = res.body expect(body.username).to.equal('laguna') expect(body.account.displayName).to.equal('laguna') expect(body.role).to.equal(UserRole.USER) @@ -106,51 +85,47 @@ describe('Test id and pass auth plugins', function () { it('Should refresh crash token, but not laguna token', async function () { { - const resRefresh = await refreshToken(server, crashRefreshToken) + const resRefresh = await server.login.refreshToken({ refreshToken: crashRefreshToken }) crashAccessToken = resRefresh.body.access_token crashRefreshToken = resRefresh.body.refresh_token - const res = await getMyUserInformation(server.url, crashAccessToken) - const user: User = res.body - expect(user.username).to.equal('crash') + const body = await server.users.getMyInfo({ token: crashAccessToken }) + expect(body.username).to.equal('crash') } { - await refreshToken(server, lagunaRefreshToken, 400) + await server.login.refreshToken({ refreshToken: lagunaRefreshToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) } }) it('Should update Crash profile', async function () { - await updateMyUser({ - url: server.url, - accessToken: crashAccessToken, + await server.users.updateMe({ + token: crashAccessToken, displayName: 'Beautiful Crash', description: 'Mutant eastern barred bandicoot' }) - const res = await getMyUserInformation(server.url, crashAccessToken) + const body = await server.users.getMyInfo({ token: crashAccessToken }) - const body: User = res.body expect(body.account.displayName).to.equal('Beautiful Crash') expect(body.account.description).to.equal('Mutant eastern barred bandicoot') }) it('Should logout Crash', async function () { - await logout(server.url, crashAccessToken) + await server.login.logout({ token: crashAccessToken }) }) it('Should have logged out Crash', async function () { - await waitUntilLog(server, 'On logout for auth 1 - 2') + await server.servers.waitUntilLog('On logout for auth 1 - 2') - await getMyUserInformation(server.url, crashAccessToken, 401) + await server.users.getMyInfo({ token: crashAccessToken, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) }) it('Should login Crash and keep the old existing profile', async function () { - crashAccessToken = await userLogin(server, { username: 'crash', password: 'crash password' }) + crashAccessToken = await server.login.getAccessToken({ username: 'crash', password: 'crash password' }) - const res = await getMyUserInformation(server.url, crashAccessToken) + const body = await server.users.getMyInfo({ token: crashAccessToken }) - const body: User = res.body expect(body.username).to.equal('crash') expect(body.account.displayName).to.equal('Beautiful Crash') expect(body.account.description).to.equal('Mutant eastern barred bandicoot') @@ -162,39 +137,38 @@ describe('Test id and pass auth plugins', function () { await wait(5000) - await getMyUserInformation(server.url, lagunaAccessToken, 401) + await server.users.getMyInfo({ token: lagunaAccessToken, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) }) it('Should reject an invalid username, email, role or display name', async function () { - await userLogin(server, { username: 'ward', password: 'ward password' }, 400) - await waitUntilLog(server, 'valid username') + const command = server.login - await userLogin(server, { username: 'kiros', password: 'kiros password' }, 400) - await waitUntilLog(server, 'valid display name') + await command.login({ user: { username: 'ward', password: 'ward password' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) + await server.servers.waitUntilLog('valid username') - await userLogin(server, { username: 'raine', password: 'raine password' }, 400) - await waitUntilLog(server, 'valid role') + await command.login({ user: { username: 'kiros', password: 'kiros password' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) + await server.servers.waitUntilLog('valid display name') - await userLogin(server, { username: 'ellone', password: 'elonne password' }, 400) - await waitUntilLog(server, 'valid email') + await command.login({ user: { username: 'raine', password: 'raine password' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) + await server.servers.waitUntilLog('valid role') + + await command.login({ user: { username: 'ellone', password: 'elonne password' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) + await server.servers.waitUntilLog('valid email') }) it('Should unregister spyro-auth and do not login existing Spyro', async function () { - await updatePluginSettings({ - url: server.url, - accessToken: server.accessToken, + await server.plugins.updateSettings({ npmName: 'peertube-plugin-test-id-pass-auth-one', settings: { disableSpyro: true } }) - await userLogin(server, { username: 'spyro', password: 'spyro password' }, 400) - await userLogin(server, { username: 'spyro', password: 'fake' }, 400) + const command = server.login + await command.login({ user: { username: 'spyro', password: 'spyro password' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) + await command.login({ user: { username: 'spyro', password: 'fake' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) }) it('Should have disabled this auth', async function () { - const res = await getConfig(server.url) - - const config: ServerConfig = res.body + const config = await server.config.getConfig() const auths = config.plugin.registeredIdAndPassAuths expect(auths).to.have.lengthOf(7) @@ -204,19 +178,16 @@ describe('Test id and pass auth plugins', function () { }) it('Should uninstall the plugin one and do not login existing Crash', async function () { - await uninstallPlugin({ - url: server.url, - accessToken: server.accessToken, - npmName: 'peertube-plugin-test-id-pass-auth-one' - }) + await server.plugins.uninstall({ npmName: 'peertube-plugin-test-id-pass-auth-one' }) - await userLogin(server, { username: 'crash', password: 'crash password' }, 400) + await server.login.login({ + user: { username: 'crash', password: 'crash password' }, + expectedStatus: HttpStatusCode.BAD_REQUEST_400 + }) }) it('Should display the correct configuration', async function () { - const res = await getConfig(server.url) - - const config: ServerConfig = res.body + const config = await server.config.getConfig() const auths = config.plugin.registeredIdAndPassAuths expect(auths).to.have.lengthOf(6) @@ -226,13 +197,11 @@ describe('Test id and pass auth plugins', function () { }) it('Should display plugin auth information in users list', async function () { - const res = await getUsersList(server.url, server.accessToken) - - const users: User[] = res.body.data + const { data } = await server.users.list() - const root = users.find(u => u.username === 'root') - const crash = users.find(u => u.username === 'crash') - const laguna = users.find(u => u.username === 'laguna') + const root = data.find(u => u.username === 'root') + const crash = data.find(u => u.username === 'crash') + const laguna = data.find(u => u.username === 'laguna') expect(root.pluginAuth).to.be.null expect(crash.pluginAuth).to.equal('peertube-plugin-test-id-pass-auth-one') diff --git a/server/tests/plugins/plugin-helpers.ts b/server/tests/plugins/plugin-helpers.ts index 0296d6eb7..5d16b28a4 100644 --- a/server/tests/plugins/plugin-helpers.ts +++ b/server/tests/plugins/plugin-helpers.ts @@ -1,25 +1,22 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ import 'mocha' +import { expect } from 'chai' import { checkVideoFilesWereRemoved, + cleanupTests, + createMultipleServers, doubleFollow, - getPluginTestPath, - getVideo, - installPlugin, + makeGetRequest, makePostBodyRequest, + PeerTubeServer, + PluginsCommand, setAccessTokensToServers, - uploadVideoAndGetId, - viewVideo, - getVideosList, - waitJobs, - makeGetRequest -} from '../../../shared/extra-utils' -import { cleanupTests, flushAndRunMultipleServers, ServerInfo, waitUntilLog } from '../../../shared/extra-utils/server/servers' -import { expect } from 'chai' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' + waitJobs +} from '@shared/extra-utils' +import { HttpStatusCode } from '@shared/models' -function postCommand (server: ServerInfo, command: string, bodyArg?: object) { +function postCommand (server: PeerTubeServer, command: string, bodyArg?: object) { const body = { command } if (bodyArg) Object.assign(body, bodyArg) @@ -27,54 +24,50 @@ function postCommand (server: ServerInfo, command: string, bodyArg?: object) { url: server.url, path: '/plugins/test-four/router/commander', fields: body, - statusCodeExpected: HttpStatusCode.NO_CONTENT_204 + expectedStatus: HttpStatusCode.NO_CONTENT_204 }) } describe('Test plugin helpers', function () { - let servers: ServerInfo[] + let servers: PeerTubeServer[] before(async function () { this.timeout(60000) - servers = await flushAndRunMultipleServers(2) + servers = await createMultipleServers(2) await setAccessTokensToServers(servers) await doubleFollow(servers[0], servers[1]) - await installPlugin({ - url: servers[0].url, - accessToken: servers[0].accessToken, - path: getPluginTestPath('-four') - }) + await servers[0].plugins.install({ path: PluginsCommand.getPluginTestPath('-four') }) }) describe('Logger', function () { it('Should have logged things', async function () { - await waitUntilLog(servers[0], 'localhost:' + servers[0].port + ' peertube-plugin-test-four', 1, false) - await waitUntilLog(servers[0], 'Hello world from plugin four', 1) + await servers[0].servers.waitUntilLog('localhost:' + servers[0].port + ' peertube-plugin-test-four', 1, false) + await servers[0].servers.waitUntilLog('Hello world from plugin four', 1) }) }) describe('Database', function () { it('Should have made a query', async function () { - await waitUntilLog(servers[0], `root email is admin${servers[0].internalServerNumber}@example.com`) + await servers[0].servers.waitUntilLog(`root email is admin${servers[0].internalServerNumber}@example.com`) }) }) describe('Config', function () { it('Should have the correct webserver url', async function () { - await waitUntilLog(servers[0], `server url is http://localhost:${servers[0].port}`) + await servers[0].servers.waitUntilLog(`server url is http://localhost:${servers[0].port}`) }) it('Should have the correct config', async function () { const res = await makeGetRequest({ url: servers[0].url, path: '/plugins/test-four/router/server-config', - statusCodeExpected: HttpStatusCode.OK_200 + expectedStatus: HttpStatusCode.OK_200 }) expect(res.body.serverConfig).to.exist @@ -85,7 +78,7 @@ describe('Test plugin helpers', function () { describe('Server', function () { it('Should get the server actor', async function () { - await waitUntilLog(servers[0], 'server actor name is peertube') + await servers[0].servers.waitUntilLog('server actor name is peertube') }) }) @@ -95,7 +88,7 @@ describe('Test plugin helpers', function () { const res = await makeGetRequest({ url: servers[0].url, path: '/plugins/test-four/router/static-route', - statusCodeExpected: HttpStatusCode.OK_200 + expectedStatus: HttpStatusCode.OK_200 }) expect(res.body.staticRoute).to.equal('/plugins/test-four/0.0.1/static/') @@ -107,7 +100,7 @@ describe('Test plugin helpers', function () { const res = await makeGetRequest({ url: servers[0].url, path: baseRouter + 'router-route', - statusCodeExpected: HttpStatusCode.OK_200 + expectedStatus: HttpStatusCode.OK_200 }) expect(res.body.routerRoute).to.equal(baseRouter) @@ -120,7 +113,7 @@ describe('Test plugin helpers', function () { await makeGetRequest({ url: servers[0].url, path: '/plugins/test-four/router/user', - statusCodeExpected: HttpStatusCode.NOT_FOUND_404 + expectedStatus: HttpStatusCode.NOT_FOUND_404 }) }) @@ -129,7 +122,7 @@ describe('Test plugin helpers', function () { url: servers[0].url, token: servers[0].accessToken, path: '/plugins/test-four/router/user', - statusCodeExpected: HttpStatusCode.OK_200 + expectedStatus: HttpStatusCode.OK_200 }) expect(res.body.username).to.equal('root') @@ -147,59 +140,54 @@ describe('Test plugin helpers', function () { this.timeout(60000) { - const res = await uploadVideoAndGetId({ server: servers[0], videoName: 'video server 1' }) + const res = await servers[0].videos.quickUpload({ name: 'video server 1' }) videoUUIDServer1 = res.uuid } { - await uploadVideoAndGetId({ server: servers[1], videoName: 'video server 2' }) + await servers[1].videos.quickUpload({ name: 'video server 2' }) } await waitJobs(servers) - const res = await getVideosList(servers[0].url) - const videos = res.body.data + const { data } = await servers[0].videos.list() - expect(videos).to.have.lengthOf(2) + expect(data).to.have.lengthOf(2) }) it('Should mute server 2', async function () { this.timeout(10000) await postCommand(servers[0], 'blockServer', { hostToBlock: `localhost:${servers[1].port}` }) - const res = await getVideosList(servers[0].url) - const videos = res.body.data + const { data } = await servers[0].videos.list() - expect(videos).to.have.lengthOf(1) - expect(videos[0].name).to.equal('video server 1') + expect(data).to.have.lengthOf(1) + expect(data[0].name).to.equal('video server 1') }) it('Should unmute server 2', async function () { await postCommand(servers[0], 'unblockServer', { hostToUnblock: `localhost:${servers[1].port}` }) - const res = await getVideosList(servers[0].url) - const videos = res.body.data + const { data } = await servers[0].videos.list() - expect(videos).to.have.lengthOf(2) + expect(data).to.have.lengthOf(2) }) it('Should mute account of server 2', async function () { await postCommand(servers[0], 'blockAccount', { handleToBlock: `root@localhost:${servers[1].port}` }) - const res = await getVideosList(servers[0].url) - const videos = res.body.data + const { data } = await servers[0].videos.list() - expect(videos).to.have.lengthOf(1) - expect(videos[0].name).to.equal('video server 1') + expect(data).to.have.lengthOf(1) + expect(data[0].name).to.equal('video server 1') }) it('Should unmute account of server 2', async function () { await postCommand(servers[0], 'unblockAccount', { handleToUnblock: `root@localhost:${servers[1].port}` }) - const res = await getVideosList(servers[0].url) - const videos = res.body.data + const { data } = await servers[0].videos.list() - expect(videos).to.have.lengthOf(2) + expect(data).to.have.lengthOf(2) }) it('Should blacklist video', async function () { @@ -210,11 +198,10 @@ describe('Test plugin helpers', function () { await waitJobs(servers) for (const server of servers) { - const res = await getVideosList(server.url) - const videos = res.body.data + const { data } = await server.videos.list() - expect(videos).to.have.lengthOf(1) - expect(videos[0].name).to.equal('video server 2') + expect(data).to.have.lengthOf(1) + expect(data[0].name).to.equal('video server 2') } }) @@ -226,10 +213,9 @@ describe('Test plugin helpers', function () { await waitJobs(servers) for (const server of servers) { - const res = await getVideosList(server.url) - const videos = res.body.data + const { data } = await server.videos.list() - expect(videos).to.have.lengthOf(2) + expect(data).to.have.lengthOf(2) } }) }) @@ -238,7 +224,7 @@ describe('Test plugin helpers', function () { let videoUUID: string before(async () => { - const res = await uploadVideoAndGetId({ server: servers[0], videoName: 'video1' }) + const res = await servers[0].videos.quickUpload({ name: 'video1' }) videoUUID = res.uuid }) @@ -246,25 +232,25 @@ describe('Test plugin helpers', function () { this.timeout(40000) // Should not throw -> video exists - await getVideo(servers[0].url, videoUUID) + const video = await servers[0].videos.get({ id: videoUUID }) // Should delete the video - await viewVideo(servers[0].url, videoUUID) + await servers[0].videos.view({ id: videoUUID }) - await waitUntilLog(servers[0], 'Video deleted by plugin four.') + await servers[0].servers.waitUntilLog('Video deleted by plugin four.') try { // Should throw because the video should have been deleted - await getVideo(servers[0].url, videoUUID) + await servers[0].videos.get({ id: videoUUID }) throw new Error('Video exists') } catch (err) { if (err.message.includes('exists')) throw err } - await checkVideoFilesWereRemoved(videoUUID, servers[0].internalServerNumber) + await checkVideoFilesWereRemoved({ server: servers[0], video }) }) it('Should have fetched the video by URL', async function () { - await waitUntilLog(servers[0], `video from DB uuid is ${videoUUID}`) + await servers[0].servers.waitUntilLog(`video from DB uuid is ${videoUUID}`) }) }) diff --git a/server/tests/plugins/plugin-router.ts b/server/tests/plugins/plugin-router.ts index 24e6a1e83..b1ac9e2fe 100644 --- a/server/tests/plugins/plugin-router.ts +++ b/server/tests/plugins/plugin-router.ts @@ -1,19 +1,20 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ import 'mocha' -import { cleanupTests, flushAndRunServer, ServerInfo } from '../../../shared/extra-utils/server/servers' +import { expect } from 'chai' import { - getPluginTestPath, - installPlugin, + cleanupTests, + createSingleServer, makeGetRequest, makePostBodyRequest, - setAccessTokensToServers, uninstallPlugin -} from '../../../shared/extra-utils' -import { expect } from 'chai' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' + PeerTubeServer, + PluginsCommand, + setAccessTokensToServers +} from '@shared/extra-utils' +import { HttpStatusCode } from '@shared/models' describe('Test plugin helpers', function () { - let server: ServerInfo + let server: PeerTubeServer const basePaths = [ '/plugins/test-five/router/', '/plugins/test-five/0.0.1/router/' @@ -22,14 +23,10 @@ describe('Test plugin helpers', function () { before(async function () { this.timeout(30000) - server = await flushAndRunServer(1) + server = await createSingleServer(1) await setAccessTokensToServers([ server ]) - await installPlugin({ - url: server.url, - accessToken: server.accessToken, - path: getPluginTestPath('-five') - }) + await server.plugins.install({ path: PluginsCommand.getPluginTestPath('-five') }) }) it('Should answer "pong"', async function () { @@ -37,7 +34,7 @@ describe('Test plugin helpers', function () { const res = await makeGetRequest({ url: server.url, path: path + 'ping', - statusCodeExpected: HttpStatusCode.OK_200 + expectedStatus: HttpStatusCode.OK_200 }) expect(res.body.message).to.equal('pong') @@ -50,7 +47,7 @@ describe('Test plugin helpers', function () { url: server.url, path: path + 'is-authenticated', token: server.accessToken, - statusCodeExpected: 200 + expectedStatus: 200 }) expect(res.body.isAuthenticated).to.equal(true) @@ -58,7 +55,7 @@ describe('Test plugin helpers', function () { const secRes = await makeGetRequest({ url: server.url, path: path + 'is-authenticated', - statusCodeExpected: 200 + expectedStatus: 200 }) expect(secRes.body.isAuthenticated).to.equal(false) @@ -77,7 +74,7 @@ describe('Test plugin helpers', function () { url: server.url, path: path + 'form/post/mirror', fields: body, - statusCodeExpected: HttpStatusCode.OK_200 + expectedStatus: HttpStatusCode.OK_200 }) expect(res.body).to.deep.equal(body) @@ -85,24 +82,20 @@ describe('Test plugin helpers', function () { }) it('Should remove the plugin and remove the routes', async function () { - await uninstallPlugin({ - url: server.url, - accessToken: server.accessToken, - npmName: 'peertube-plugin-test-five' - }) + await server.plugins.uninstall({ npmName: 'peertube-plugin-test-five' }) for (const path of basePaths) { await makeGetRequest({ url: server.url, path: path + 'ping', - statusCodeExpected: HttpStatusCode.NOT_FOUND_404 + expectedStatus: HttpStatusCode.NOT_FOUND_404 }) await makePostBodyRequest({ url: server.url, path: path + 'ping', fields: {}, - statusCodeExpected: HttpStatusCode.NOT_FOUND_404 + expectedStatus: HttpStatusCode.NOT_FOUND_404 }) } }) diff --git a/server/tests/plugins/plugin-storage.ts b/server/tests/plugins/plugin-storage.ts index 3c46b2585..e20c36dba 100644 --- a/server/tests/plugins/plugin-storage.ts +++ b/server/tests/plugins/plugin-storage.ts @@ -4,37 +4,32 @@ import 'mocha' import { expect } from 'chai' import { pathExists, readdir, readFile } from 'fs-extra' import { join } from 'path' -import { HttpStatusCode } from '@shared/core-utils' import { - buildServerDirectory, - getPluginTestPath, - installPlugin, + cleanupTests, + createSingleServer, makeGetRequest, - setAccessTokensToServers, - uninstallPlugin -} from '../../../shared/extra-utils' -import { cleanupTests, flushAndRunServer, ServerInfo, waitUntilLog } from '../../../shared/extra-utils/server/servers' + PeerTubeServer, + PluginsCommand, + setAccessTokensToServers +} from '@shared/extra-utils' +import { HttpStatusCode } from '@shared/models' describe('Test plugin storage', function () { - let server: ServerInfo + let server: PeerTubeServer before(async function () { this.timeout(30000) - server = await flushAndRunServer(1) + server = await createSingleServer(1) await setAccessTokensToServers([ server ]) - await installPlugin({ - url: server.url, - accessToken: server.accessToken, - path: getPluginTestPath('-six') - }) + await server.plugins.install({ path: PluginsCommand.getPluginTestPath('-six') }) }) describe('DB storage', function () { it('Should correctly store a subkey', async function () { - await waitUntilLog(server, 'superkey stored value is toto') + await server.servers.waitUntilLog('superkey stored value is toto') }) }) @@ -50,12 +45,12 @@ describe('Test plugin storage', function () { } before(function () { - dataPath = buildServerDirectory(server, 'plugins/data') + dataPath = server.servers.buildDirectory('plugins/data') pluginDataPath = join(dataPath, 'peertube-plugin-test-six') }) it('Should have created the directory on install', async function () { - const dataPath = buildServerDirectory(server, 'plugins/data') + const dataPath = server.servers.buildDirectory('plugins/data') const pluginDataPath = join(dataPath, 'peertube-plugin-test-six') expect(await pathExists(dataPath)).to.be.true @@ -68,7 +63,7 @@ describe('Test plugin storage', function () { url: server.url, token: server.accessToken, path: '/plugins/test-six/router/create-file', - statusCodeExpected: HttpStatusCode.OK_200 + expectedStatus: HttpStatusCode.OK_200 }) const content = await getFileContent() @@ -76,22 +71,14 @@ describe('Test plugin storage', function () { }) it('Should still have the file after an uninstallation', async function () { - await uninstallPlugin({ - url: server.url, - accessToken: server.accessToken, - npmName: 'peertube-plugin-test-six' - }) + await server.plugins.uninstall({ npmName: 'peertube-plugin-test-six' }) const content = await getFileContent() expect(content).to.equal('Prince Ali') }) it('Should still have the file after the reinstallation', async function () { - await installPlugin({ - url: server.url, - accessToken: server.accessToken, - path: getPluginTestPath('-six') - }) + await server.plugins.install({ path: PluginsCommand.getPluginTestPath('-six') }) const content = await getFileContent() expect(content).to.equal('Prince Ali') diff --git a/server/tests/plugins/plugin-transcoding.ts b/server/tests/plugins/plugin-transcoding.ts index eefb2294d..93637e3ce 100644 --- a/server/tests/plugins/plugin-transcoding.ts +++ b/server/tests/plugins/plugin-transcoding.ts @@ -2,79 +2,73 @@ import 'mocha' import { expect } from 'chai' -import { join } from 'path' import { getAudioStream, getVideoFileFPS, getVideoStreamFromFile } from '@server/helpers/ffprobe-utils' -import { ServerConfig, VideoDetails, VideoPrivacy } from '@shared/models' import { - buildServerDirectory, - createLive, - getConfig, - getPluginTestPath, - getVideo, - installPlugin, - sendRTMPStreamInVideo, + cleanupTests, + createSingleServer, + PeerTubeServer, + PluginsCommand, setAccessTokensToServers, setDefaultVideoChannel, testFfmpegStreamError, - uninstallPlugin, - updateCustomSubConfig, - uploadVideoAndGetId, - waitJobs, - waitUntilLivePublished -} from '../../../shared/extra-utils' -import { cleanupTests, flushAndRunServer, ServerInfo } from '../../../shared/extra-utils/server/servers' - -async function createLiveWrapper (server: ServerInfo) { + waitJobs +} from '@shared/extra-utils' +import { VideoPrivacy } from '@shared/models' + +async function createLiveWrapper (server: PeerTubeServer) { const liveAttributes = { name: 'live video', - channelId: server.videoChannel.id, + channelId: server.store.channel.id, privacy: VideoPrivacy.PUBLIC } - const res = await createLive(server.url, server.accessToken, liveAttributes) - return res.body.video.uuid + const { uuid } = await server.live.create({ fields: liveAttributes }) + + return uuid } -function updateConf (server: ServerInfo, vodProfile: string, liveProfile: string) { - return updateCustomSubConfig(server.url, server.accessToken, { - transcoding: { - enabled: true, - profile: vodProfile, - hls: { - enabled: true - }, - webtorrent: { - enabled: true - }, - resolutions: { - '240p': true, - '360p': false, - '480p': false, - '720p': true - } - }, - live: { +function updateConf (server: PeerTubeServer, vodProfile: string, liveProfile: string) { + return server.config.updateCustomSubConfig({ + newConfig: { transcoding: { - profile: liveProfile, enabled: true, + profile: vodProfile, + hls: { + enabled: true + }, + webtorrent: { + enabled: true + }, resolutions: { '240p': true, '360p': false, '480p': false, '720p': true } + }, + live: { + transcoding: { + profile: liveProfile, + enabled: true, + resolutions: { + '240p': true, + '360p': false, + '480p': false, + '720p': true + } + } } } }) } describe('Test transcoding plugins', function () { - let server: ServerInfo + let server: PeerTubeServer before(async function () { this.timeout(60000) - server = await flushAndRunServer(1) + server = await createSingleServer(1) await setAccessTokensToServers([ server ]) await setDefaultVideoChannel([ server ]) @@ -84,8 +78,7 @@ describe('Test transcoding plugins', function () { describe('When using a plugin adding profiles to existing encoders', function () { async function checkVideoFPS (uuid: string, type: 'above' | 'below', fps: number) { - const res = await getVideo(server.url, uuid) - const video = res.body as VideoDetails + const video = await server.videos.get({ id: uuid }) const files = video.files.concat(...video.streamingPlaylists.map(p => p.files)) for (const file of files) { @@ -109,134 +102,132 @@ describe('Test transcoding plugins', function () { } before(async function () { - await installPlugin({ - url: server.url, - accessToken: server.accessToken, - path: getPluginTestPath('-transcoding-one') - }) + await server.plugins.install({ path: PluginsCommand.getPluginTestPath('-transcoding-one') }) }) it('Should have the appropriate available profiles', async function () { - const res = await getConfig(server.url) - const config = res.body as ServerConfig + const config = await server.config.getConfig() expect(config.transcoding.availableProfiles).to.have.members([ 'default', 'low-vod', 'input-options-vod', 'bad-scale-vod' ]) - expect(config.live.transcoding.availableProfiles).to.have.members([ 'default', 'low-live', 'input-options-live', 'bad-scale-live' ]) + expect(config.live.transcoding.availableProfiles).to.have.members([ 'default', 'high-live', 'input-options-live', 'bad-scale-live' ]) }) - it('Should not use the plugin profile if not chosen by the admin', async function () { - this.timeout(240000) + describe('VOD', function () { - const videoUUID = (await uploadVideoAndGetId({ server, videoName: 'video' })).uuid - await waitJobs([ server ]) + it('Should not use the plugin profile if not chosen by the admin', async function () { + this.timeout(240000) - await checkVideoFPS(videoUUID, 'above', 20) - }) + const videoUUID = (await server.videos.quickUpload({ name: 'video' })).uuid + await waitJobs([ server ]) - it('Should use the vod profile', async function () { - this.timeout(240000) + await checkVideoFPS(videoUUID, 'above', 20) + }) - await updateConf(server, 'low-vod', 'default') + it('Should use the vod profile', async function () { + this.timeout(240000) - const videoUUID = (await uploadVideoAndGetId({ server, videoName: 'video' })).uuid - await waitJobs([ server ]) + await updateConf(server, 'low-vod', 'default') - await checkVideoFPS(videoUUID, 'below', 12) - }) + const videoUUID = (await server.videos.quickUpload({ name: 'video' })).uuid + await waitJobs([ server ]) - it('Should apply input options in vod profile', async function () { - this.timeout(240000) + await checkVideoFPS(videoUUID, 'below', 12) + }) - await updateConf(server, 'input-options-vod', 'default') + it('Should apply input options in vod profile', async function () { + this.timeout(240000) - const videoUUID = (await uploadVideoAndGetId({ server, videoName: 'video' })).uuid - await waitJobs([ server ]) + await updateConf(server, 'input-options-vod', 'default') - await checkVideoFPS(videoUUID, 'below', 6) - }) + const videoUUID = (await server.videos.quickUpload({ name: 'video' })).uuid + await waitJobs([ server ]) - it('Should apply the scale filter in vod profile', async function () { - this.timeout(240000) + await checkVideoFPS(videoUUID, 'below', 6) + }) - await updateConf(server, 'bad-scale-vod', 'default') + it('Should apply the scale filter in vod profile', async function () { + this.timeout(240000) - const videoUUID = (await uploadVideoAndGetId({ server, videoName: 'video' })).uuid - await waitJobs([ server ]) + await updateConf(server, 'bad-scale-vod', 'default') - // Transcoding failed - const res = await getVideo(server.url, videoUUID) - const video: VideoDetails = res.body + const videoUUID = (await server.videos.quickUpload({ name: 'video' })).uuid + await waitJobs([ server ]) - expect(video.files).to.have.lengthOf(1) - expect(video.streamingPlaylists).to.have.lengthOf(0) + // Transcoding failed + const video = await server.videos.get({ id: videoUUID }) + expect(video.files).to.have.lengthOf(1) + expect(video.streamingPlaylists).to.have.lengthOf(0) + }) }) - it('Should not use the plugin profile if not chosen by the admin', async function () { - this.timeout(240000) + describe('Live', function () { - const liveVideoId = await createLiveWrapper(server) + it('Should not use the plugin profile if not chosen by the admin', async function () { + this.timeout(240000) - await sendRTMPStreamInVideo(server.url, server.accessToken, liveVideoId, 'video_short2.webm') - await waitUntilLivePublished(server.url, server.accessToken, liveVideoId) - await waitJobs([ server ]) + const liveVideoId = await createLiveWrapper(server) - await checkLiveFPS(liveVideoId, 'above', 20) - }) + await server.live.sendRTMPStreamInVideo({ videoId: liveVideoId, fixtureName: 'video_very_short_240p.mp4' }) + await server.live.waitUntilPublished({ videoId: liveVideoId }) + await waitJobs([ server ]) - it('Should use the live profile', async function () { - this.timeout(240000) + await checkLiveFPS(liveVideoId, 'above', 20) + }) - await updateConf(server, 'low-vod', 'low-live') + it('Should use the live profile', async function () { + this.timeout(240000) - const liveVideoId = await createLiveWrapper(server) + await updateConf(server, 'low-vod', 'high-live') - await sendRTMPStreamInVideo(server.url, server.accessToken, liveVideoId, 'video_short2.webm') - await waitUntilLivePublished(server.url, server.accessToken, liveVideoId) - await waitJobs([ server ]) + const liveVideoId = await createLiveWrapper(server) - await checkLiveFPS(liveVideoId, 'below', 12) - }) + await server.live.sendRTMPStreamInVideo({ videoId: liveVideoId, fixtureName: 'video_very_short_240p.mp4' }) + await server.live.waitUntilPublished({ videoId: liveVideoId }) + await waitJobs([ server ]) - it('Should apply the input options on live profile', async function () { - this.timeout(240000) + await checkLiveFPS(liveVideoId, 'above', 45) + }) - await updateConf(server, 'low-vod', 'input-options-live') + it('Should apply the input options on live profile', async function () { + this.timeout(240000) - const liveVideoId = await createLiveWrapper(server) + await updateConf(server, 'low-vod', 'input-options-live') - await sendRTMPStreamInVideo(server.url, server.accessToken, liveVideoId, 'video_short2.webm') - await waitUntilLivePublished(server.url, server.accessToken, liveVideoId) - await waitJobs([ server ]) + const liveVideoId = await createLiveWrapper(server) - await checkLiveFPS(liveVideoId, 'below', 6) - }) + await server.live.sendRTMPStreamInVideo({ videoId: liveVideoId, fixtureName: 'video_very_short_240p.mp4' }) + await server.live.waitUntilPublished({ videoId: liveVideoId }) + await waitJobs([ server ]) - it('Should apply the scale filter name on live profile', async function () { - this.timeout(240000) + await checkLiveFPS(liveVideoId, 'above', 45) + }) - await updateConf(server, 'low-vod', 'bad-scale-live') + it('Should apply the scale filter name on live profile', async function () { + this.timeout(240000) - const liveVideoId = await createLiveWrapper(server) + await updateConf(server, 'low-vod', 'bad-scale-live') - const command = await sendRTMPStreamInVideo(server.url, server.accessToken, liveVideoId, 'video_short2.webm') - await testFfmpegStreamError(command, true) - }) + const liveVideoId = await createLiveWrapper(server) - it('Should default to the default profile if the specified profile does not exist', async function () { - this.timeout(240000) + const command = await server.live.sendRTMPStreamInVideo({ videoId: liveVideoId, fixtureName: 'video_very_short_240p.mp4' }) + await testFfmpegStreamError(command, true) + }) - await uninstallPlugin({ url: server.url, accessToken: server.accessToken, npmName: 'peertube-plugin-test-transcoding-one' }) + it('Should default to the default profile if the specified profile does not exist', async function () { + this.timeout(240000) - const res = await getConfig(server.url) - const config = res.body as ServerConfig + await server.plugins.uninstall({ npmName: 'peertube-plugin-test-transcoding-one' }) - expect(config.transcoding.availableProfiles).to.deep.equal([ 'default' ]) - expect(config.live.transcoding.availableProfiles).to.deep.equal([ 'default' ]) + const config = await server.config.getConfig() - const videoUUID = (await uploadVideoAndGetId({ server, videoName: 'video' })).uuid - await waitJobs([ server ]) + expect(config.transcoding.availableProfiles).to.deep.equal([ 'default' ]) + expect(config.live.transcoding.availableProfiles).to.deep.equal([ 'default' ]) + + const videoUUID = (await server.videos.quickUpload({ name: 'video', fixture: 'video_very_short_240p.mp4' })).uuid + await waitJobs([ server ]) - await checkVideoFPS(videoUUID, 'above', 20) + await checkVideoFPS(videoUUID, 'above', 20) + }) }) }) @@ -244,11 +235,7 @@ describe('Test transcoding plugins', function () { describe('When using a plugin adding new encoders', function () { before(async function () { - await installPlugin({ - url: server.url, - accessToken: server.accessToken, - path: getPluginTestPath('-transcoding-two') - }) + await server.plugins.install({ path: PluginsCommand.getPluginTestPath('-transcoding-two') }) await updateConf(server, 'test-vod-profile', 'test-live-profile') }) @@ -256,10 +243,12 @@ describe('Test transcoding plugins', function () { it('Should use the new vod encoders', async function () { this.timeout(240000) - const videoUUID = (await uploadVideoAndGetId({ server, videoName: 'video', fixture: 'video_short_240p.mp4' })).uuid + const videoUUID = (await server.videos.quickUpload({ name: 'video', fixture: 'video_very_short_240p.mp4' })).uuid await waitJobs([ server ]) - const path = buildServerDirectory(server, join('videos', videoUUID + '-240.mp4')) + const video = await server.videos.get({ id: videoUUID }) + + const path = server.servers.buildWebTorrentFilePath(video.files[0].fileUrl) const audioProbe = await getAudioStream(path) expect(audioProbe.audioStream.codec_name).to.equal('opus') @@ -272,8 +261,8 @@ describe('Test transcoding plugins', function () { const liveVideoId = await createLiveWrapper(server) - await sendRTMPStreamInVideo(server.url, server.accessToken, liveVideoId, 'video_short2.webm') - await waitUntilLivePublished(server.url, server.accessToken, liveVideoId) + await server.live.sendRTMPStreamInVideo({ videoId: liveVideoId, fixtureName: 'video_short2.webm' }) + await server.live.waitUntilPublished({ videoId: liveVideoId }) await waitJobs([ server ]) const playlistUrl = `${server.url}/static/streaming-playlists/hls/${liveVideoId}/0.m3u8` diff --git a/server/tests/plugins/plugin-unloading.ts b/server/tests/plugins/plugin-unloading.ts index 74ca82e2f..6bf2fda9b 100644 --- a/server/tests/plugins/plugin-unloading.ts +++ b/server/tests/plugins/plugin-unloading.ts @@ -1,42 +1,36 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ import 'mocha' +import { expect } from 'chai' import { cleanupTests, - flushAndRunServer, - getPluginTestPath, + createSingleServer, makeGetRequest, - installPlugin, - uninstallPlugin, - ServerInfo, + PeerTubeServer, + PluginsCommand, setAccessTokensToServers -} from '../../../shared/extra-utils' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' -import { expect } from 'chai' +} from '@shared/extra-utils' +import { HttpStatusCode } from '@shared/models' describe('Test plugins module unloading', function () { - let server: ServerInfo = null + let server: PeerTubeServer = null const requestPath = '/plugins/test-unloading/router/get' let value: string = null before(async function () { this.timeout(30000) - server = await flushAndRunServer(1) + server = await createSingleServer(1) await setAccessTokensToServers([ server ]) - await installPlugin({ - url: server.url, - accessToken: server.accessToken, - path: getPluginTestPath('-unloading') - }) + await server.plugins.install({ path: PluginsCommand.getPluginTestPath('-unloading') }) }) it('Should return a numeric value', async function () { const res = await makeGetRequest({ url: server.url, path: requestPath, - statusCodeExpected: HttpStatusCode.OK_200 + expectedStatus: HttpStatusCode.OK_200 }) expect(res.body.message).to.match(/^\d+$/) @@ -47,36 +41,29 @@ describe('Test plugins module unloading', function () { const res = await makeGetRequest({ url: server.url, path: requestPath, - statusCodeExpected: HttpStatusCode.OK_200 + expectedStatus: HttpStatusCode.OK_200 }) expect(res.body.message).to.be.equal(value) }) it('Should uninstall the plugin and free the route', async function () { - await uninstallPlugin({ - url: server.url, - accessToken: server.accessToken, - npmName: 'peertube-plugin-test-unloading' - }) + await server.plugins.uninstall({ npmName: 'peertube-plugin-test-unloading' }) await makeGetRequest({ url: server.url, path: requestPath, - statusCodeExpected: HttpStatusCode.NOT_FOUND_404 + expectedStatus: HttpStatusCode.NOT_FOUND_404 }) }) it('Should return a different numeric value', async function () { - await installPlugin({ - url: server.url, - accessToken: server.accessToken, - path: getPluginTestPath('-unloading') - }) + await server.plugins.install({ path: PluginsCommand.getPluginTestPath('-unloading') }) + const res = await makeGetRequest({ url: server.url, path: requestPath, - statusCodeExpected: HttpStatusCode.OK_200 + expectedStatus: HttpStatusCode.OK_200 }) expect(res.body.message).to.match(/^\d+$/) diff --git a/server/tests/plugins/translations.ts b/server/tests/plugins/translations.ts index 9fd2ba1c5..8b25c6b75 100644 --- a/server/tests/plugins/translations.ts +++ b/server/tests/plugins/translations.ts @@ -1,50 +1,37 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ -import * as chai from 'chai' import 'mocha' -import { cleanupTests, flushAndRunServer, ServerInfo } from '../../../shared/extra-utils/server/servers' -import { - getPluginTestPath, - getPluginTranslations, - installPlugin, - setAccessTokensToServers, - uninstallPlugin -} from '../../../shared/extra-utils' +import * as chai from 'chai' +import { cleanupTests, createSingleServer, PeerTubeServer, PluginsCommand, setAccessTokensToServers } from '@shared/extra-utils' const expect = chai.expect describe('Test plugin translations', function () { - let server: ServerInfo + let server: PeerTubeServer + let command: PluginsCommand before(async function () { this.timeout(30000) - server = await flushAndRunServer(1) + server = await createSingleServer(1) await setAccessTokensToServers([ server ]) - await installPlugin({ - url: server.url, - accessToken: server.accessToken, - path: getPluginTestPath() - }) + command = server.plugins - await installPlugin({ - url: server.url, - accessToken: server.accessToken, - path: getPluginTestPath('-filter-translations') - }) + await command.install({ path: PluginsCommand.getPluginTestPath() }) + await command.install({ path: PluginsCommand.getPluginTestPath('-filter-translations') }) }) it('Should not have translations for locale pt', async function () { - const res = await getPluginTranslations({ url: server.url, locale: 'pt' }) + const body = await command.getTranslations({ locale: 'pt' }) - expect(res.body).to.deep.equal({}) + expect(body).to.deep.equal({}) }) it('Should have translations for locale fr', async function () { - const res = await getPluginTranslations({ url: server.url, locale: 'fr-FR' }) + const body = await command.getTranslations({ locale: 'fr-FR' }) - expect(res.body).to.deep.equal({ + expect(body).to.deep.equal({ 'peertube-plugin-test': { Hi: 'Coucou' }, @@ -55,9 +42,9 @@ describe('Test plugin translations', function () { }) it('Should have translations of locale it', async function () { - const res = await getPluginTranslations({ url: server.url, locale: 'it-IT' }) + const body = await command.getTranslations({ locale: 'it-IT' }) - expect(res.body).to.deep.equal({ + expect(body).to.deep.equal({ 'peertube-plugin-test-filter-translations': { 'Hello world': 'Ciao, mondo!' } @@ -65,12 +52,12 @@ describe('Test plugin translations', function () { }) it('Should remove the plugin and remove the locales', async function () { - await uninstallPlugin({ url: server.url, accessToken: server.accessToken, npmName: 'peertube-plugin-test-filter-translations' }) + await command.uninstall({ npmName: 'peertube-plugin-test-filter-translations' }) { - const res = await getPluginTranslations({ url: server.url, locale: 'fr-FR' }) + const body = await command.getTranslations({ locale: 'fr-FR' }) - expect(res.body).to.deep.equal({ + expect(body).to.deep.equal({ 'peertube-plugin-test': { Hi: 'Coucou' } @@ -78,9 +65,9 @@ describe('Test plugin translations', function () { } { - const res = await getPluginTranslations({ url: server.url, locale: 'it-IT' }) + const body = await command.getTranslations({ locale: 'it-IT' }) - expect(res.body).to.deep.equal({}) + expect(body).to.deep.equal({}) } }) diff --git a/server/tests/plugins/video-constants.ts b/server/tests/plugins/video-constants.ts index eb014c596..19cba6c2c 100644 --- a/server/tests/plugins/video-constants.ts +++ b/server/tests/plugins/video-constants.ts @@ -1,44 +1,33 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ -import * as chai from 'chai' import 'mocha' -import { cleanupTests, flushAndRunServer, ServerInfo } from '../../../shared/extra-utils/server/servers' +import * as chai from 'chai' import { - createVideoPlaylist, - getPluginTestPath, - getVideo, - getVideoCategories, - getVideoLanguages, - getVideoLicences, getVideoPlaylistPrivacies, getVideoPrivacies, - installPlugin, - setAccessTokensToServers, - uninstallPlugin, - uploadVideo -} from '../../../shared/extra-utils' -import { VideoDetails, VideoPlaylistPrivacy } from '../../../shared/models/videos' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' + cleanupTests, + createSingleServer, + makeGetRequest, + PeerTubeServer, + PluginsCommand, + setAccessTokensToServers +} from '@shared/extra-utils' +import { HttpStatusCode, VideoPlaylistPrivacy } from '@shared/models' const expect = chai.expect describe('Test plugin altering video constants', function () { - let server: ServerInfo + let server: PeerTubeServer before(async function () { this.timeout(30000) - server = await flushAndRunServer(1) + server = await createSingleServer(1) await setAccessTokensToServers([ server ]) - await installPlugin({ - url: server.url, - accessToken: server.accessToken, - path: getPluginTestPath('-video-constants') - }) + await server.plugins.install({ path: PluginsCommand.getPluginTestPath('-video-constants') }) }) it('Should have updated languages', async function () { - const res = await getVideoLanguages(server.url) - const languages = res.body + const languages = await server.videos.getLanguages() expect(languages['en']).to.not.exist expect(languages['fr']).to.not.exist @@ -49,8 +38,7 @@ describe('Test plugin altering video constants', function () { }) it('Should have updated categories', async function () { - const res = await getVideoCategories(server.url) - const categories = res.body + const categories = await server.videos.getCategories() expect(categories[1]).to.not.exist expect(categories[2]).to.not.exist @@ -60,8 +48,7 @@ describe('Test plugin altering video constants', function () { }) it('Should have updated licences', async function () { - const res = await getVideoLicences(server.url) - const licences = res.body + const licences = await server.videos.getLicences() expect(licences[1]).to.not.exist expect(licences[7]).to.not.exist @@ -71,8 +58,7 @@ describe('Test plugin altering video constants', function () { }) it('Should have updated video privacies', async function () { - const res = await getVideoPrivacies(server.url) - const privacies = res.body + const privacies = await server.videos.getPrivacies() expect(privacies[1]).to.exist expect(privacies[2]).to.not.exist @@ -81,8 +67,7 @@ describe('Test plugin altering video constants', function () { }) it('Should have updated playlist privacies', async function () { - const res = await getVideoPlaylistPrivacies(server.url) - const playlistPrivacies = res.body + const playlistPrivacies = await server.playlists.getPrivacies() expect(playlistPrivacies[1]).to.exist expect(playlistPrivacies[2]).to.exist @@ -90,38 +75,30 @@ describe('Test plugin altering video constants', function () { }) it('Should not be able to create a video with this privacy', async function () { - const attrs = { name: 'video', privacy: 2 } - await uploadVideo(server.url, server.accessToken, attrs, HttpStatusCode.BAD_REQUEST_400) + const attributes = { name: 'video', privacy: 2 } + await server.videos.upload({ attributes, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) }) it('Should not be able to create a video with this privacy', async function () { - const attrs = { displayName: 'video playlist', privacy: VideoPlaylistPrivacy.PRIVATE } - await createVideoPlaylist({ - url: server.url, - token: server.accessToken, - playlistAttrs: attrs, - expectedStatus: HttpStatusCode.BAD_REQUEST_400 - }) + const attributes = { displayName: 'video playlist', privacy: VideoPlaylistPrivacy.PRIVATE } + await server.playlists.create({ attributes, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) }) it('Should be able to upload a video with these values', async function () { - const attrs = { name: 'video', category: 42, licence: 42, language: 'al_bhed2' } - const resUpload = await uploadVideo(server.url, server.accessToken, attrs) + const attributes = { name: 'video', category: 42, licence: 42, language: 'al_bhed2' } + const { uuid } = await server.videos.upload({ attributes }) - const res = await getVideo(server.url, resUpload.body.video.uuid) - - const video: VideoDetails = res.body + const video = await server.videos.get({ id: uuid }) expect(video.language.label).to.equal('Al Bhed 2') expect(video.licence.label).to.equal('Best licence') expect(video.category.label).to.equal('Best category') }) it('Should uninstall the plugin and reset languages, categories, licences and privacies', async function () { - await uninstallPlugin({ url: server.url, accessToken: server.accessToken, npmName: 'peertube-plugin-test-video-constants' }) + await server.plugins.uninstall({ npmName: 'peertube-plugin-test-video-constants' }) { - const res = await getVideoLanguages(server.url) - const languages = res.body + const languages = await server.videos.getLanguages() expect(languages['en']).to.equal('English') expect(languages['fr']).to.equal('French') @@ -132,8 +109,7 @@ describe('Test plugin altering video constants', function () { } { - const res = await getVideoCategories(server.url) - const categories = res.body + const categories = await server.videos.getCategories() expect(categories[1]).to.equal('Music') expect(categories[2]).to.equal('Films') @@ -143,8 +119,7 @@ describe('Test plugin altering video constants', function () { } { - const res = await getVideoLicences(server.url) - const licences = res.body + const licences = await server.videos.getLicences() expect(licences[1]).to.equal('Attribution') expect(licences[7]).to.equal('Public Domain Dedication') @@ -154,8 +129,7 @@ describe('Test plugin altering video constants', function () { } { - const res = await getVideoPrivacies(server.url) - const privacies = res.body + const privacies = await server.videos.getPrivacies() expect(privacies[1]).to.exist expect(privacies[2]).to.exist @@ -164,8 +138,7 @@ describe('Test plugin altering video constants', function () { } { - const res = await getVideoPlaylistPrivacies(server.url) - const playlistPrivacies = res.body + const playlistPrivacies = await server.playlists.getPrivacies() expect(playlistPrivacies[1]).to.exist expect(playlistPrivacies[2]).to.exist @@ -173,6 +146,37 @@ describe('Test plugin altering video constants', function () { } }) + it('Should be able to reset categories', async function () { + await server.plugins.install({ path: PluginsCommand.getPluginTestPath('-video-constants') }) + + { + const categories = await server.videos.getCategories() + + expect(categories[1]).to.not.exist + expect(categories[2]).to.not.exist + + expect(categories[42]).to.exist + expect(categories[43]).to.exist + } + + await makeGetRequest({ + url: server.url, + token: server.accessToken, + path: '/plugins/test-video-constants/router/reset-categories', + expectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + + { + const categories = await server.videos.getCategories() + + expect(categories[1]).to.exist + expect(categories[2]).to.exist + + expect(categories[42]).to.not.exist + expect(categories[43]).to.not.exist + } + }) + after(async function () { await cleanupTests([ server ]) }) diff --git a/server/tools/cli.ts b/server/tools/cli.ts index 7b94306cd..52e6ea593 100644 --- a/server/tools/cli.ts +++ b/server/tools/cli.ts @@ -1,14 +1,11 @@ +import { Command } from 'commander' import { Netrc } from 'netrc-parser' -import { getAppNumber, isTestInstance } from '../helpers/core-utils' import { join } from 'path' -import { root } from '../../shared/extra-utils/miscs/miscs' -import { getVideoChannel } from '../../shared/extra-utils/videos/video-channels' -import { VideoChannel, VideoPrivacy } from '../../shared/models/videos' import { createLogger, format, transports } from 'winston' -import { getMyUserInformation } from '@shared/extra-utils/users/users' -import { User, UserRole } from '@shared/models' -import { getAccessToken } from '@shared/extra-utils/users/login' -import { Command } from 'commander' +import { PeerTubeServer } from '@shared/extra-utils' +import { UserRole } from '@shared/models' +import { VideoPrivacy } from '../../shared/models/videos' +import { getAppNumber, isTestInstance, root } from '../helpers/core-utils' let configName = 'PeerTube/CLI' if (isTestInstance()) configName += `-${getAppNumber()}` @@ -17,17 +14,16 @@ const config = require('application-config')(configName) const version = require('../../../package.json').version -async function getAdminTokenOrDie (url: string, username: string, password: string) { - const accessToken = await getAccessToken(url, username, password) - const resMe = await getMyUserInformation(url, accessToken) - const me: User = resMe.body +async function getAdminTokenOrDie (server: PeerTubeServer, username: string, password: string) { + const token = await server.login.getAccessToken(username, password) + const me = await server.users.getMyInfo({ token }) if (me.role !== UserRole.ADMINISTRATOR) { console.error('You must be an administrator.') process.exit(-1) } - return accessToken + return token } interface Settings { @@ -128,7 +124,7 @@ function buildCommonVideoOptions (command: Command) { .option('-v, --verbose ', 'Verbosity, from 0/\'error\' to 4/\'debug\'', 'info') } -async function buildVideoAttributesFromCommander (url: string, command: Command, defaultAttributes: any = {}) { +async function buildVideoAttributesFromCommander (server: PeerTubeServer, command: Command, defaultAttributes: any = {}) { const options = command.opts() const defaultBooleanAttributes = { @@ -164,8 +160,7 @@ async function buildVideoAttributesFromCommander (url: string, command: Command, Object.assign(videoAttributes, booleanAttributes) if (options.channelName) { - const res = await getVideoChannel(url, options.channelName) - const videoChannel: VideoChannel = res.body + const videoChannel = await server.channels.get({ channelName: options.channelName }) Object.assign(videoAttributes, { channelId: videoChannel.id }) @@ -184,6 +179,19 @@ function getServerCredentials (program: Command) { }) } +function buildServer (url: string) { + return new PeerTubeServer({ url }) +} + +async function assignToken (server: PeerTubeServer, username: string, password: string) { + const bodyClient = await server.login.getClient() + const client = { id: bodyClient.client_id, secret: bodyClient.client_secret } + + const body = await server.login.login({ client, user: { username, password } }) + + server.accessToken = body.access_token +} + function getLogger (logLevel = 'info') { const logLevels = { 0: 0, @@ -230,5 +238,7 @@ export { buildCommonVideoOptions, buildVideoAttributesFromCommander, - getAdminTokenOrDie + getAdminTokenOrDie, + buildServer, + assignToken } diff --git a/server/tools/peertube-auth.ts b/server/tools/peertube-auth.ts index 1934e7986..b9f4ef4f8 100644 --- a/server/tools/peertube-auth.ts +++ b/server/tools/peertube-auth.ts @@ -5,9 +5,8 @@ registerTSPaths() import { OptionValues, program } from 'commander' import * as prompt from 'prompt' -import { getNetrc, getSettings, writeSettings } from './cli' +import { assignToken, buildServer, getNetrc, getSettings, writeSettings } from './cli' import { isUserUsernameValid } from '../helpers/custom-validators/users' -import { getAccessToken } from '../../shared/extra-utils' import * as CliTable3 from 'cli-table3' async function delInstance (url: string) { @@ -97,7 +96,8 @@ program // @see https://github.com/Chocobozzz/PeerTube/issues/3520 result.url = stripExtraneousFromPeerTubeUrl(result.url) - await getAccessToken(result.url, result.username, result.password) + const server = buildServer(result.url) + await assignToken(server, result.username, result.password) } catch (err) { console.error(err.message) process.exit(-1) diff --git a/server/tools/peertube-get-access-token.ts b/server/tools/peertube-get-access-token.ts index 9488eba0e..a67de9180 100644 --- a/server/tools/peertube-get-access-token.ts +++ b/server/tools/peertube-get-access-token.ts @@ -2,7 +2,7 @@ import { registerTSPaths } from '../helpers/register-ts-paths' registerTSPaths() import { program } from 'commander' -import { getClient, Server, serverLogin } from '../../shared/extra-utils' +import { assignToken, buildServer } from './cli' program .option('-u, --url ', 'Server url') @@ -24,24 +24,11 @@ if ( process.exit(-1) } -getClient(options.url) - .then(res => { - const server = { - url: options.url, - user: { - username: options.username, - password: options.password - }, - client: { - id: res.body.client_id, - secret: res.body.client_secret - } - } as Server +const server = buildServer(options.url) - return serverLogin(server) - }) - .then(accessToken => { - console.log(accessToken) +assignToken(server, options.username, options.password) + .then(() => { + console.log(server.accessToken) process.exit(0) }) .catch(err => { diff --git a/server/tools/peertube-import-videos.ts b/server/tools/peertube-import-videos.ts index 101a95b2a..52aae3d2c 100644 --- a/server/tools/peertube-import-videos.ts +++ b/server/tools/peertube-import-videos.ts @@ -8,17 +8,19 @@ import { truncate } from 'lodash' import { join } from 'path' import * as prompt from 'prompt' import { promisify } from 'util' -import { advancedVideosSearch, getClient, getVideoCategories, login, uploadVideo } from '../../shared/extra-utils/index' +import { YoutubeDL } from '@server/helpers/youtube-dl' import { sha256 } from '../helpers/core-utils' import { doRequestAndSaveToFile } from '../helpers/requests' import { CONSTRAINTS_FIELDS } from '../initializers/constants' -import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getLogger, getServerCredentials } from './cli' -import { YoutubeDL } from '@server/helpers/youtube-dl' - -type UserInfo = { - username: string - password: string -} +import { + assignToken, + buildCommonVideoOptions, + buildServer, + buildVideoAttributesFromCommander, + getLogger, + getServerCredentials +} from './cli' +import { PeerTubeServer } from '@shared/extra-utils' const processOptions = { maxBuffer: Infinity @@ -62,17 +64,13 @@ getServerCredentials(command) url = normalizeTargetUrl(url) options.targetUrl = normalizeTargetUrl(options.targetUrl) - const user = { username, password } - - run(url, user) + run(url, username, password) .catch(err => exitError(err)) }) .catch(err => console.error(err)) -async function run (url: string, user: UserInfo) { - if (!user.password) { - user.password = await promptPassword() - } +async function run (url: string, username: string, password: string) { + if (!password) password = await promptPassword() const youtubeDLBinary = await YoutubeDL.safeGetYoutubeDL() @@ -111,7 +109,8 @@ async function run (url: string, user: UserInfo) { await processVideo({ cwd: options.tmpdir, url, - user, + username, + password, youtubeInfo: info }) } catch (err) { @@ -119,17 +118,18 @@ async function run (url: string, user: UserInfo) { } } - log.info('Video/s for user %s imported: %s', user.username, options.targetUrl) + log.info('Video/s for user %s imported: %s', username, options.targetUrl) process.exit(0) } async function processVideo (parameters: { cwd: string url: string - user: { username: string, password: string } + username: string + password: string youtubeInfo: any }) { - const { youtubeInfo, cwd, url, user } = parameters + const { youtubeInfo, cwd, url, username, password } = parameters const youtubeDL = new YoutubeDL('', []) log.debug('Fetching object.', youtubeInfo) @@ -138,22 +138,29 @@ async function processVideo (parameters: { log.debug('Fetched object.', videoInfo) const originallyPublishedAt = youtubeDL.buildOriginallyPublishedAt(videoInfo) + if (options.since && originallyPublishedAt && originallyPublishedAt.getTime() < options.since.getTime()) { - log.info('Video "%s" has been published before "%s", don\'t upload it.\n', - videoInfo.title, formatDate(options.since)) + log.info('Video "%s" has been published before "%s", don\'t upload it.\n', videoInfo.title, formatDate(options.since)) return } + if (options.until && originallyPublishedAt && originallyPublishedAt.getTime() > options.until.getTime()) { - log.info('Video "%s" has been published after "%s", don\'t upload it.\n', - videoInfo.title, formatDate(options.until)) + log.info('Video "%s" has been published after "%s", don\'t upload it.\n', videoInfo.title, formatDate(options.until)) return } - const result = await advancedVideosSearch(url, { search: videoInfo.title, sort: '-match', searchTarget: 'local' }) + const server = buildServer(url) + const { data } = await server.search.advancedVideoSearch({ + search: { + search: videoInfo.title, + sort: '-match', + searchTarget: 'local' + } + }) log.info('############################################################\n') - if (result.body.data.find(v => v.name === videoInfo.title)) { + if (data.find(v => v.name === videoInfo.title)) { log.info('Video "%s" already exists, don\'t reupload it.\n', videoInfo.title) return } @@ -172,7 +179,8 @@ async function processVideo (parameters: { youtubeDL, cwd, url, - user, + username, + password, videoInfo: normalizeObject(videoInfo), videoPath: path }) @@ -187,11 +195,15 @@ async function uploadVideoOnPeerTube (parameters: { videoPath: string cwd: string url: string - user: { username: string, password: string } + username: string + password: string }) { - const { youtubeDL, videoInfo, videoPath, cwd, url, user } = parameters + const { youtubeDL, videoInfo, videoPath, cwd, url, username, password } = parameters - const category = await getCategory(videoInfo.categories, url) + const server = buildServer(url) + await assignToken(server, username, password) + + const category = await getCategory(server, videoInfo.categories) const licence = getLicence(videoInfo.license) let tags = [] if (Array.isArray(videoInfo.tags)) { @@ -223,28 +235,28 @@ async function uploadVideoOnPeerTube (parameters: { tags } - const videoAttributes = await buildVideoAttributesFromCommander(url, program, defaultAttributes) + const baseAttributes = await buildVideoAttributesFromCommander(server, program, defaultAttributes) + + const attributes = { + ...baseAttributes, - Object.assign(videoAttributes, { originallyPublishedAt: originallyPublishedAt ? originallyPublishedAt.toISOString() : null, thumbnailfile, previewfile: thumbnailfile, fixture: videoPath - }) - - log.info('\nUploading on PeerTube video "%s".', videoAttributes.name) + } - let accessToken = await getAccessTokenOrDie(url, user) + log.info('\nUploading on PeerTube video "%s".', attributes.name) try { - await uploadVideo(url, accessToken, videoAttributes) + await server.videos.upload({ attributes }) } catch (err) { if (err.message.indexOf('401') !== -1) { log.info('Got 401 Unauthorized, token may have expired, renewing token and retry.') - accessToken = await getAccessTokenOrDie(url, user) + server.accessToken = await server.login.getAccessToken(username, password) - await uploadVideo(url, accessToken, videoAttributes) + await server.videos.upload({ attributes }) } else { exitError(err.message) } @@ -253,20 +265,19 @@ async function uploadVideoOnPeerTube (parameters: { await remove(videoPath) if (thumbnailfile) await remove(thumbnailfile) - log.warn('Uploaded video "%s"!\n', videoAttributes.name) + log.warn('Uploaded video "%s"!\n', attributes.name) } /* ---------------------------------------------------------- */ -async function getCategory (categories: string[], url: string) { +async function getCategory (server: PeerTubeServer, categories: string[]) { if (!categories) return undefined const categoryString = categories[0] if (categoryString === 'News & Politics') return 11 - const res = await getVideoCategories(url) - const categoriesServer = res.body + const categoriesServer = await server.videos.getCategories() for (const key of Object.keys(categoriesServer)) { const categoryServer = categoriesServer[key] @@ -362,21 +373,6 @@ async function promptPassword () { }) } -async function getAccessTokenOrDie (url: string, user: UserInfo) { - const resClient = await getClient(url) - const client = { - id: resClient.body.client_id, - secret: resClient.body.client_secret - } - - try { - const res = await login(url, client, user) - return res.body.access_token - } catch (err) { - exitError('Cannot authenticate. Please check your username/password.') - } -} - function parseDate (dateAsStr: string): Date { if (!/\d{4}-\d{2}-\d{2}/.test(dateAsStr)) { exitError(`Invalid date passed: ${dateAsStr}. Expected format: YYYY-MM-DD. See help for usage.`) diff --git a/server/tools/peertube-plugins.ts b/server/tools/peertube-plugins.ts index 54ea1264d..d9c285115 100644 --- a/server/tools/peertube-plugins.ts +++ b/server/tools/peertube-plugins.ts @@ -4,9 +4,8 @@ import { registerTSPaths } from '../helpers/register-ts-paths' registerTSPaths() import { program, Command, OptionValues } from 'commander' -import { installPlugin, listPlugins, uninstallPlugin, updatePlugin } from '../../shared/extra-utils/server/plugins' -import { getAdminTokenOrDie, getServerCredentials } from './cli' -import { PeerTubePlugin, PluginType } from '../../shared/models' +import { assignToken, buildServer, getServerCredentials } from './cli' +import { PluginType } from '../../shared/models' import { isAbsolute } from 'path' import * as CliTable3 from 'cli-table3' @@ -63,28 +62,21 @@ program.parse(process.argv) async function pluginsListCLI (command: Command, options: OptionValues) { const { url, username, password } = await getServerCredentials(command) - const accessToken = await getAdminTokenOrDie(url, username, password) + const server = buildServer(url) + await assignToken(server, username, password) let pluginType: PluginType if (options.onlyThemes) pluginType = PluginType.THEME if (options.onlyPlugins) pluginType = PluginType.PLUGIN - const res = await listPlugins({ - url, - accessToken, - start: 0, - count: 100, - sort: 'name', - pluginType - }) - const plugins: PeerTubePlugin[] = res.body.data + const { data } = await server.plugins.list({ start: 0, count: 100, sort: 'name', pluginType }) const table = new CliTable3({ head: [ 'name', 'version', 'homepage' ], colWidths: [ 50, 10, 50 ] }) as any - for (const plugin of plugins) { + for (const plugin of data) { const npmName = plugin.type === PluginType.PLUGIN ? 'peertube-plugin-' + plugin.name : 'peertube-theme-' + plugin.name @@ -113,15 +105,11 @@ async function installPluginCLI (command: Command, options: OptionValues) { } const { url, username, password } = await getServerCredentials(command) - const accessToken = await getAdminTokenOrDie(url, username, password) + const server = buildServer(url) + await assignToken(server, username, password) try { - await installPlugin({ - url, - accessToken, - npmName: options.npmName, - path: options.path - }) + await server.plugins.install({ npmName: options.npmName, path: options.path }) } catch (err) { console.error('Cannot install plugin.', err) process.exit(-1) @@ -144,15 +132,11 @@ async function updatePluginCLI (command: Command, options: OptionValues) { } const { url, username, password } = await getServerCredentials(command) - const accessToken = await getAdminTokenOrDie(url, username, password) + const server = buildServer(url) + await assignToken(server, username, password) try { - await updatePlugin({ - url, - accessToken, - npmName: options.npmName, - path: options.path - }) + await server.plugins.update({ npmName: options.npmName, path: options.path }) } catch (err) { console.error('Cannot update plugin.', err) process.exit(-1) @@ -170,14 +154,11 @@ async function uninstallPluginCLI (command: Command, options: OptionValues) { } const { url, username, password } = await getServerCredentials(command) - const accessToken = await getAdminTokenOrDie(url, username, password) + const server = buildServer(url) + await assignToken(server, username, password) try { - await uninstallPlugin({ - url, - accessToken, - npmName: options.npmName - }) + await server.plugins.uninstall({ npmName: options.npmName }) } catch (err) { console.error('Cannot uninstall plugin.', err) process.exit(-1) diff --git a/server/tools/peertube-redundancy.ts b/server/tools/peertube-redundancy.ts index 4810deee0..73b026ac8 100644 --- a/server/tools/peertube-redundancy.ts +++ b/server/tools/peertube-redundancy.ts @@ -1,17 +1,13 @@ -// eslint-disable @typescript-eslint/no-unnecessary-type-assertion - import { registerTSPaths } from '../helpers/register-ts-paths' registerTSPaths() -import { program, Command } from 'commander' -import { getAdminTokenOrDie, getServerCredentials } from './cli' -import { VideoRedundanciesTarget, VideoRedundancy } from '@shared/models' -import { addVideoRedundancy, listVideoRedundancies, removeVideoRedundancy } from '@shared/extra-utils/server/redundancy' -import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' -import validator from 'validator' import * as CliTable3 from 'cli-table3' -import { URL } from 'url' +import { Command, program } from 'commander' import { uniq } from 'lodash' +import { URL } from 'url' +import validator from 'validator' +import { HttpStatusCode, VideoRedundanciesTarget } from '@shared/models' +import { assignToken, buildServer, getServerCredentials } from './cli' import bytes = require('bytes') @@ -63,15 +59,16 @@ program.parse(process.argv) async function listRedundanciesCLI (target: VideoRedundanciesTarget) { const { url, username, password } = await getServerCredentials(program) - const accessToken = await getAdminTokenOrDie(url, username, password) + const server = buildServer(url) + await assignToken(server, username, password) - const redundancies = await listVideoRedundanciesData(url, accessToken, target) + const { data } = await server.redundancy.listVideos({ start: 0, count: 100, sort: 'name', target }) const table = new CliTable3({ head: [ 'video id', 'video name', 'video url', 'files', 'playlists', 'by instances', 'total size' ] }) as any - for (const redundancy of redundancies) { + for (const redundancy of data) { const webtorrentFiles = redundancy.redundancies.files const streamingPlaylists = redundancy.redundancies.streamingPlaylists @@ -106,7 +103,8 @@ async function listRedundanciesCLI (target: VideoRedundanciesTarget) { async function addRedundancyCLI (options: { video: number }, command: Command) { const { url, username, password } = await getServerCredentials(command) - const accessToken = await getAdminTokenOrDie(url, username, password) + const server = buildServer(url) + await assignToken(server, username, password) if (!options.video || validator.isInt('' + options.video) === false) { console.error('You need to specify the video id to duplicate and it should be a number.\n') @@ -115,11 +113,7 @@ async function addRedundancyCLI (options: { video: number }, command: Command) { } try { - await addVideoRedundancy({ - url, - accessToken, - videoId: options.video - }) + await server.redundancy.addVideo({ videoId: options.video }) console.log('Video will be duplicated by your instance!') @@ -139,7 +133,8 @@ async function addRedundancyCLI (options: { video: number }, command: Command) { async function removeRedundancyCLI (options: { video: number }, command: Command) { const { url, username, password } = await getServerCredentials(command) - const accessToken = await getAdminTokenOrDie(url, username, password) + const server = buildServer(url) + await assignToken(server, username, password) if (!options.video || validator.isInt('' + options.video) === false) { console.error('You need to specify the video id to remove from your redundancies.\n') @@ -149,12 +144,12 @@ async function removeRedundancyCLI (options: { video: number }, command: Command const videoId = parseInt(options.video + '', 10) - let redundancies = await listVideoRedundanciesData(url, accessToken, 'my-videos') - let videoRedundancy = redundancies.find(r => videoId === r.id) + const myVideoRedundancies = await server.redundancy.listVideos({ target: 'my-videos' }) + let videoRedundancy = myVideoRedundancies.data.find(r => videoId === r.id) if (!videoRedundancy) { - redundancies = await listVideoRedundanciesData(url, accessToken, 'remote-videos') - videoRedundancy = redundancies.find(r => videoId === r.id) + const remoteVideoRedundancies = await server.redundancy.listVideos({ target: 'remote-videos' }) + videoRedundancy = remoteVideoRedundancies.data.find(r => videoId === r.id) } if (!videoRedundancy) { @@ -168,11 +163,7 @@ async function removeRedundancyCLI (options: { video: number }, command: Command .map(r => r.id) for (const id of ids) { - await removeVideoRedundancy({ - url, - accessToken, - redundancyId: id - }) + await server.redundancy.removeVideo({ redundancyId: id }) } console.log('Video redundancy removed!') @@ -183,16 +174,3 @@ async function removeRedundancyCLI (options: { video: number }, command: Command process.exit(-1) } } - -async function listVideoRedundanciesData (url: string, accessToken: string, target: VideoRedundanciesTarget) { - const res = await listVideoRedundancies({ - url, - accessToken, - start: 0, - count: 100, - sort: 'name', - target - }) - - return res.body.data as VideoRedundancy[] -} diff --git a/server/tools/peertube-upload.ts b/server/tools/peertube-upload.ts index 02edbd809..01fb1fe8d 100644 --- a/server/tools/peertube-upload.ts +++ b/server/tools/peertube-upload.ts @@ -4,9 +4,7 @@ registerTSPaths() import { program } from 'commander' import { access, constants } from 'fs-extra' import { isAbsolute } from 'path' -import { getAccessToken } from '../../shared/extra-utils' -import { uploadVideo } from '../../shared/extra-utils/' -import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getServerCredentials } from './cli' +import { assignToken, buildCommonVideoOptions, buildServer, buildVideoAttributesFromCommander, getServerCredentials } from './cli' let command = program .name('upload') @@ -46,22 +44,25 @@ getServerCredentials(command) .catch(err => console.error(err)) async function run (url: string, username: string, password: string) { - const accessToken = await getAccessToken(url, username, password) + const server = buildServer(url) + await assignToken(server, username, password) await access(options.file, constants.F_OK) console.log('Uploading %s video...', options.videoName) - const videoAttributes = await buildVideoAttributesFromCommander(url, program) + const baseAttributes = await buildVideoAttributesFromCommander(server, program) + + const attributes = { + ...baseAttributes, - Object.assign(videoAttributes, { fixture: options.file, thumbnailfile: options.thumbnail, previewfile: options.preview - }) + } try { - await uploadVideo(url, accessToken, videoAttributes) + await server.videos.upload({ attributes }) console.log(`Video ${options.videoName} uploaded.`) process.exit(0) } catch (err) { diff --git a/server/tools/test.ts b/server/tools/test-live.ts similarity index 59% rename from server/tools/test.ts rename to server/tools/test-live.ts index fbdbae0b0..50dc04438 100644 --- a/server/tools/test.ts +++ b/server/tools/test-live.ts @@ -1,26 +1,23 @@ -import { registerTSPaths } from '../helpers/register-ts-paths' -registerTSPaths() - -import { LiveVideo, LiveVideoCreate, VideoPrivacy } from '@shared/models' import { program } from 'commander' +import { LiveVideoCreate, VideoPrivacy } from '@shared/models' import { - createLive, - flushAndRunServer, - getLive, + createSingleServer, killallServers, sendRTMPStream, - ServerInfo, + PeerTubeServer, setAccessTokensToServers, - setDefaultVideoChannel, - updateCustomSubConfig + setDefaultVideoChannel } from '../../shared/extra-utils' +import { registerTSPaths } from '../helpers/register-ts-paths' + +registerTSPaths() type CommandType = 'live-mux' | 'live-transcoding' registerTSPaths() const command = program - .name('test') + .name('test-live') .option('-t, --type ', 'live-muxing|live-transcoding') .parse(process.argv) @@ -39,11 +36,11 @@ async function run () { console.log('Starting server.') - const server = await flushAndRunServer(1, {}, [], { hideLogs: false, execArgv: [ '--inspect' ] }) + const server = await createSingleServer(1, {}, { hideLogs: false, nodeArgs: [ '--inspect' ] }) - const cleanup = () => { + const cleanup = async () => { console.log('Killing server') - killallServers([ server ]) + await killallServers([ server ]) } process.on('exit', cleanup) @@ -57,17 +54,15 @@ async function run () { const attributes: LiveVideoCreate = { name: 'live', saveReplay: true, - channelId: server.videoChannel.id, + channelId: server.store.channel.id, privacy: VideoPrivacy.PUBLIC } console.log('Creating live.') - const res = await createLive(server.url, server.accessToken, attributes) - const liveVideoUUID = res.body.video.uuid + const { uuid: liveVideoUUID } = await server.live.create({ fields: attributes }) - const resLive = await getLive(server.url, server.accessToken, liveVideoUUID) - const live: LiveVideo = resLive.body + const live = await server.live.get({ videoId: liveVideoUUID }) console.log('Sending RTMP stream.') @@ -86,19 +81,21 @@ async function run () { // ---------------------------------------------------------------------------- -async function buildConfig (server: ServerInfo, commandType: CommandType) { - await updateCustomSubConfig(server.url, server.accessToken, { - instance: { - customizations: { - javascript: '', - css: '' - } - }, - live: { - enabled: true, - allowReplay: true, - transcoding: { - enabled: commandType === 'live-transcoding' +async function buildConfig (server: PeerTubeServer, commandType: CommandType) { + await server.config.updateCustomSubConfig({ + newConfig: { + instance: { + customizations: { + javascript: '', + css: '' + } + }, + live: { + enabled: true, + allowReplay: true, + transcoding: { + enabled: commandType === 'live-transcoding' + } } } }) diff --git a/server/types/models/video/video-streaming-playlist.ts b/server/types/models/video/video-streaming-playlist.ts index 8b3ef51fc..1e4dccb8e 100644 --- a/server/types/models/video/video-streaming-playlist.ts +++ b/server/types/models/video/video-streaming-playlist.ts @@ -39,5 +39,5 @@ export type MStreamingPlaylistRedundanciesOpt = PickWithOpt export function isStreamingPlaylist (value: MVideo | MStreamingPlaylistVideo): value is MStreamingPlaylistVideo { - return !!(value as MStreamingPlaylist).playlistUrl + return !!(value as MStreamingPlaylist).videoId } diff --git a/server/typings/express/index.d.ts b/server/typings/express/index.d.ts index 1a8dc3430..1a99b598a 100644 --- a/server/typings/express/index.d.ts +++ b/server/typings/express/index.d.ts @@ -1,4 +1,5 @@ +import { OutgoingHttpHeaders } from 'http' import { RegisterServerAuthExternalOptions } from '@server/types' import { MAbuseMessage, @@ -22,8 +23,7 @@ import { MPlugin, MServer, MServerBlocklist } from '@server/types/models/server' import { MVideoImportDefault } from '@server/types/models/video/video-import' import { MVideoPlaylistElement, MVideoPlaylistElementVideoUrlPlaylistPrivacy } from '@server/types/models/video/video-playlist-element' import { MAccountVideoRateAccountVideo } from '@server/types/models/video/video-rate' -import { HttpMethod } from '@shared/core-utils/miscs/http-methods' -import { PeerTubeProblemDocumentData, ServerErrorCode, VideoCreate } from '@shared/models' +import { HttpMethod, PeerTubeProblemDocumentData, ServerErrorCode, VideoCreate } from '@shared/models' import { File as UploadXFile, Metadata } from '@uploadx/core' import { RegisteredPlugin } from '../../lib/plugins/plugin-manager' import { @@ -41,6 +41,7 @@ import { MVideoShareActor, MVideoThumbnail } from '../../types/models' +import { Writable } from 'stream' declare module 'express' { export interface Request { @@ -99,6 +100,15 @@ declare module 'express' { }) => void locals: { + apicache: { + content: string | Buffer + write: Writable['write'] + writeHead: Response['writeHead'] + end: Response['end'] + cacheable: boolean + headers: OutgoingHttpHeaders + } + docUrl?: string videoAPI?: MVideoFormattableDetails diff --git a/shared/core-utils/miscs/date.ts b/shared/core-utils/common/date.ts similarity index 53% rename from shared/core-utils/miscs/date.ts rename to shared/core-utils/common/date.ts index 4f92f758f..3e4a3c08c 100644 --- a/shared/core-utils/miscs/date.ts +++ b/shared/core-utils/common/date.ts @@ -43,6 +43,49 @@ function isLastWeek (d: Date) { return getDaysDifferences(now, d) <= 7 } +function timeToInt (time: number | string) { + if (!time) return 0 + if (typeof time === 'number') return time + + const reg = /^((\d+)[h:])?((\d+)[m:])?((\d+)s?)?$/ + const matches = time.match(reg) + + if (!matches) return 0 + + const hours = parseInt(matches[2] || '0', 10) + const minutes = parseInt(matches[4] || '0', 10) + const seconds = parseInt(matches[6] || '0', 10) + + return hours * 3600 + minutes * 60 + seconds +} + +function secondsToTime (seconds: number, full = false, symbol?: string) { + let time = '' + + if (seconds === 0 && !full) return '0s' + + const hourSymbol = (symbol || 'h') + const minuteSymbol = (symbol || 'm') + const secondsSymbol = full ? '' : 's' + + const hours = Math.floor(seconds / 3600) + if (hours >= 1) time = hours + hourSymbol + else if (full) time = '0' + hourSymbol + + seconds %= 3600 + const minutes = Math.floor(seconds / 60) + if (minutes >= 1 && minutes < 10 && full) time += '0' + minutes + minuteSymbol + else if (minutes >= 1) time += minutes + minuteSymbol + else if (full) time += '00' + minuteSymbol + + seconds %= 60 + if (seconds >= 1 && seconds < 10 && full) time += '0' + seconds + secondsSymbol + else if (seconds >= 1) time += seconds + secondsSymbol + else if (full) time += '00' + + return time +} + // --------------------------------------------------------------------------- export { @@ -51,7 +94,9 @@ export { isThisMonth, isToday, isLastMonth, - isLastWeek + isLastWeek, + timeToInt, + secondsToTime } // --------------------------------------------------------------------------- diff --git a/shared/core-utils/common/index.ts b/shared/core-utils/common/index.ts new file mode 100644 index 000000000..0908ff981 --- /dev/null +++ b/shared/core-utils/common/index.ts @@ -0,0 +1,6 @@ +export * from './date' +export * from './miscs' +export * from './regexp' +export * from './promises' +export * from './types' +export * from './url' diff --git a/shared/core-utils/miscs/miscs.ts b/shared/core-utils/common/miscs.ts similarity index 81% rename from shared/core-utils/miscs/miscs.ts rename to shared/core-utils/common/miscs.ts index 4780ca922..bc65dc338 100644 --- a/shared/core-utils/miscs/miscs.ts +++ b/shared/core-utils/common/miscs.ts @@ -20,14 +20,6 @@ function compareSemVer (a: string, b: string) { return segmentsA.length - segmentsB.length } -function isPromise (value: any) { - return value && typeof value.then === 'function' -} - -function isCatchable (value: any) { - return value && typeof value.catch === 'function' -} - function sortObjectComparator (key: string, order: 'asc' | 'desc') { return (a: any, b: any) => { if (a[key] < b[key]) { @@ -45,7 +37,5 @@ function sortObjectComparator (key: string, order: 'asc' | 'desc') { export { randomInt, compareSemVer, - isPromise, - isCatchable, sortObjectComparator } diff --git a/shared/core-utils/common/promises.ts b/shared/core-utils/common/promises.ts new file mode 100644 index 000000000..7ef9d60b6 --- /dev/null +++ b/shared/core-utils/common/promises.ts @@ -0,0 +1,12 @@ +function isPromise (value: any) { + return value && typeof value.then === 'function' +} + +function isCatchable (value: any) { + return value && typeof value.catch === 'function' +} + +export { + isPromise, + isCatchable +} diff --git a/shared/core-utils/common/regexp.ts b/shared/core-utils/common/regexp.ts new file mode 100644 index 000000000..59eb87eb6 --- /dev/null +++ b/shared/core-utils/common/regexp.ts @@ -0,0 +1,5 @@ +export const uuidRegex = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' + +export function removeFragmentedMP4Ext (path: string) { + return path.replace(/-fragmented.mp4$/i, '') +} diff --git a/shared/core-utils/miscs/types.ts b/shared/core-utils/common/types.ts similarity index 100% rename from shared/core-utils/miscs/types.ts rename to shared/core-utils/common/types.ts diff --git a/shared/core-utils/common/url.ts b/shared/core-utils/common/url.ts new file mode 100644 index 000000000..52ed247c4 --- /dev/null +++ b/shared/core-utils/common/url.ts @@ -0,0 +1,130 @@ +import { Video, VideoPlaylist } from '../../models' +import { secondsToTime } from './date' + +function buildPlaylistLink (playlist: Pick, base?: string) { + return (base ?? window.location.origin) + buildPlaylistWatchPath(playlist) +} + +function buildPlaylistWatchPath (playlist: Pick) { + return '/w/p/' + playlist.shortUUID +} + +function buildVideoWatchPath (video: Pick) { + return '/w/' + video.shortUUID +} + +function buildVideoLink (video: Pick, base?: string) { + return (base ?? window.location.origin) + buildVideoWatchPath(video) +} + +function buildPlaylistEmbedPath (playlist: Pick) { + return '/video-playlists/embed/' + playlist.uuid +} + +function buildPlaylistEmbedLink (playlist: Pick, base?: string) { + return (base ?? window.location.origin) + buildPlaylistEmbedPath(playlist) +} + +function buildVideoEmbedPath (video: Pick) { + return '/videos/embed/' + video.uuid +} + +function buildVideoEmbedLink (video: Pick, base?: string) { + return (base ?? window.location.origin) + buildVideoEmbedPath(video) +} + +function decorateVideoLink (options: { + url: string + + startTime?: number + stopTime?: number + + subtitle?: string + + loop?: boolean + autoplay?: boolean + muted?: boolean + + // Embed options + title?: boolean + warningTitle?: boolean + controls?: boolean + peertubeLink?: boolean +}) { + const { url } = options + + const params = generateParams(window.location.search) + + if (options.startTime !== undefined && options.startTime !== null) { + const startTimeInt = Math.floor(options.startTime) + params.set('start', secondsToTime(startTimeInt)) + } + + if (options.stopTime) { + const stopTimeInt = Math.floor(options.stopTime) + params.set('stop', secondsToTime(stopTimeInt)) + } + + if (options.subtitle) params.set('subtitle', options.subtitle) + + if (options.loop === true) params.set('loop', '1') + if (options.autoplay === true) params.set('autoplay', '1') + if (options.muted === true) params.set('muted', '1') + if (options.title === false) params.set('title', '0') + if (options.warningTitle === false) params.set('warningTitle', '0') + if (options.controls === false) params.set('controls', '0') + if (options.peertubeLink === false) params.set('peertubeLink', '0') + + return buildUrl(url, params) +} + +function decoratePlaylistLink (options: { + url: string + + playlistPosition?: number +}) { + const { url } = options + + const params = generateParams(window.location.search) + + if (options.playlistPosition) params.set('playlistPosition', '' + options.playlistPosition) + + return buildUrl(url, params) +} + +// --------------------------------------------------------------------------- + +export { + buildPlaylistLink, + buildVideoLink, + + buildVideoWatchPath, + buildPlaylistWatchPath, + + buildPlaylistEmbedPath, + buildVideoEmbedPath, + + buildPlaylistEmbedLink, + buildVideoEmbedLink, + + decorateVideoLink, + decoratePlaylistLink +} + +function buildUrl (url: string, params: URLSearchParams) { + let hasParams = false + params.forEach(() => { hasParams = true }) + + if (hasParams) return url + '?' + params.toString() + + return url +} + +function generateParams (url: string) { + const params = new URLSearchParams(window.location.search) + // Unused parameters in embed + params.delete('videoId') + params.delete('resume') + + return params +} diff --git a/shared/core-utils/index.ts b/shared/core-utils/index.ts index 42d7cab1d..2a7d4d982 100644 --- a/shared/core-utils/index.ts +++ b/shared/core-utils/index.ts @@ -1,7 +1,7 @@ export * from './abuse' +export * from './common' export * from './i18n' -export * from './logs' -export * from './miscs' export * from './plugins' export * from './renderer' export * from './users' +export * from './utils' diff --git a/shared/core-utils/logs/index.ts b/shared/core-utils/logs/index.ts deleted file mode 100644 index ceb5d7a7f..000000000 --- a/shared/core-utils/logs/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './logs' diff --git a/shared/core-utils/logs/logs.ts b/shared/core-utils/logs/logs.ts deleted file mode 100644 index d0996cf55..000000000 --- a/shared/core-utils/logs/logs.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { stat } from 'fs-extra' - -async function mtimeSortFilesDesc (files: string[], basePath: string) { - const promises = [] - const out: { file: string, mtime: number }[] = [] - - for (const file of files) { - const p = stat(basePath + '/' + file) - .then(stats => { - if (stats.isFile()) out.push({ file, mtime: stats.mtime.getTime() }) - }) - - promises.push(p) - } - - await Promise.all(promises) - - out.sort((a, b) => b.mtime - a.mtime) - - return out -} - -export { - mtimeSortFilesDesc -} diff --git a/shared/core-utils/miscs/index.ts b/shared/core-utils/miscs/index.ts deleted file mode 100644 index 251df1de2..000000000 --- a/shared/core-utils/miscs/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './date' -export * from './miscs' -export * from './types' -export * from './http-error-codes' -export * from './http-methods' diff --git a/shared/core-utils/plugins/hooks.ts b/shared/core-utils/plugins/hooks.ts index 5405e0529..92cb5ad68 100644 --- a/shared/core-utils/plugins/hooks.ts +++ b/shared/core-utils/plugins/hooks.ts @@ -1,5 +1,5 @@ import { HookType } from '../../models/plugins/hook-type.enum' -import { isCatchable, isPromise } from '../miscs/miscs' +import { isCatchable, isPromise } from '../common/promises' function getHookType (hookName: string) { if (hookName.startsWith('filter:')) return HookType.FILTER diff --git a/shared/core-utils/utils/index.ts b/shared/core-utils/utils/index.ts new file mode 100644 index 000000000..a71977d88 --- /dev/null +++ b/shared/core-utils/utils/index.ts @@ -0,0 +1 @@ +export * from './object' diff --git a/shared/core-utils/utils/object.ts b/shared/core-utils/utils/object.ts new file mode 100644 index 000000000..9a8a98f9b --- /dev/null +++ b/shared/core-utils/utils/object.ts @@ -0,0 +1,15 @@ +function pick (object: O, keys: K[]): Pick { + const result: any = {} + + for (const key of keys) { + if (Object.prototype.hasOwnProperty.call(object, key)) { + result[key] = object[key] + } + } + + return result +} + +export { + pick +} diff --git a/shared/extra-utils/bulk/bulk-command.ts b/shared/extra-utils/bulk/bulk-command.ts new file mode 100644 index 000000000..b5c5673ce --- /dev/null +++ b/shared/extra-utils/bulk/bulk-command.ts @@ -0,0 +1,20 @@ +import { BulkRemoveCommentsOfBody, HttpStatusCode } from '@shared/models' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export class BulkCommand extends AbstractCommand { + + removeCommentsOf (options: OverrideCommandOptions & { + attributes: BulkRemoveCommentsOfBody + }) { + const { attributes } = options + + return this.postBodyRequest({ + ...options, + + path: '/api/v1/bulk/remove-comments-of', + fields: attributes, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } +} diff --git a/shared/extra-utils/bulk/bulk.ts b/shared/extra-utils/bulk/bulk.ts deleted file mode 100644 index b6f437b8b..000000000 --- a/shared/extra-utils/bulk/bulk.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { BulkRemoveCommentsOfBody } from "@shared/models/bulk/bulk-remove-comments-of-body.model" -import { makePostBodyRequest } from "../requests/requests" -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' - -function bulkRemoveCommentsOf (options: { - url: string - token: string - attributes: BulkRemoveCommentsOfBody - expectedStatus?: number -}) { - const { url, token, attributes, expectedStatus } = options - const path = '/api/v1/bulk/remove-comments-of' - - return makePostBodyRequest({ - url, - path, - token, - fields: attributes, - statusCodeExpected: expectedStatus || HttpStatusCode.NO_CONTENT_204 - }) -} - -export { - bulkRemoveCommentsOf -} diff --git a/shared/extra-utils/bulk/index.ts b/shared/extra-utils/bulk/index.ts new file mode 100644 index 000000000..391597243 --- /dev/null +++ b/shared/extra-utils/bulk/index.ts @@ -0,0 +1 @@ +export * from './bulk-command' diff --git a/shared/extra-utils/cli/cli-command.ts b/shared/extra-utils/cli/cli-command.ts new file mode 100644 index 000000000..bc1dddc68 --- /dev/null +++ b/shared/extra-utils/cli/cli-command.ts @@ -0,0 +1,23 @@ +import { exec } from 'child_process' +import { AbstractCommand } from '../shared' + +export class CLICommand extends AbstractCommand { + + static exec (command: string) { + return new Promise((res, rej) => { + exec(command, (err, stdout, _stderr) => { + if (err) return rej(err) + + return res(stdout) + }) + }) + } + + getEnv () { + return `NODE_ENV=test NODE_APP_INSTANCE=${this.server.internalServerNumber}` + } + + async execWithEnv (command: string) { + return CLICommand.exec(`${this.getEnv()} ${command}`) + } +} diff --git a/shared/extra-utils/cli/cli.ts b/shared/extra-utils/cli/cli.ts deleted file mode 100644 index c62e170bb..000000000 --- a/shared/extra-utils/cli/cli.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { exec } from 'child_process' - -import { ServerInfo } from '../server/servers' - -function getEnvCli (server?: ServerInfo) { - return `NODE_ENV=test NODE_APP_INSTANCE=${server.internalServerNumber}` -} - -async function execCLI (command: string) { - return new Promise((res, rej) => { - exec(command, (err, stdout, stderr) => { - if (err) return rej(err) - - return res(stdout) - }) - }) -} - -// --------------------------------------------------------------------------- - -export { - execCLI, - getEnvCli -} diff --git a/shared/extra-utils/cli/index.ts b/shared/extra-utils/cli/index.ts new file mode 100644 index 000000000..91b5abfbe --- /dev/null +++ b/shared/extra-utils/cli/index.ts @@ -0,0 +1 @@ +export * from './cli-command' diff --git a/shared/extra-utils/custom-pages/custom-pages-command.ts b/shared/extra-utils/custom-pages/custom-pages-command.ts new file mode 100644 index 000000000..cd869a8de --- /dev/null +++ b/shared/extra-utils/custom-pages/custom-pages-command.ts @@ -0,0 +1,33 @@ +import { CustomPage, HttpStatusCode } from '@shared/models' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export class CustomPagesCommand extends AbstractCommand { + + getInstanceHomepage (options: OverrideCommandOptions = {}) { + const path = '/api/v1/custom-pages/homepage/instance' + + return this.getRequestBody({ + ...options, + + path, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + updateInstanceHomepage (options: OverrideCommandOptions & { + content: string + }) { + const { content } = options + const path = '/api/v1/custom-pages/homepage/instance' + + return this.putBodyRequest({ + ...options, + + path, + fields: { content }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } +} diff --git a/shared/extra-utils/custom-pages/custom-pages.ts b/shared/extra-utils/custom-pages/custom-pages.ts deleted file mode 100644 index bf2d16c70..000000000 --- a/shared/extra-utils/custom-pages/custom-pages.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' -import { makeGetRequest, makePutBodyRequest } from '../requests/requests' - -function getInstanceHomepage (url: string, statusCodeExpected = HttpStatusCode.OK_200) { - const path = '/api/v1/custom-pages/homepage/instance' - - return makeGetRequest({ - url, - path, - statusCodeExpected - }) -} - -function updateInstanceHomepage (url: string, token: string, content: string) { - const path = '/api/v1/custom-pages/homepage/instance' - - return makePutBodyRequest({ - url, - path, - token, - fields: { content }, - statusCodeExpected: HttpStatusCode.NO_CONTENT_204 - }) -} - -// --------------------------------------------------------------------------- - -export { - getInstanceHomepage, - updateInstanceHomepage -} diff --git a/shared/extra-utils/custom-pages/index.ts b/shared/extra-utils/custom-pages/index.ts new file mode 100644 index 000000000..58aed04f2 --- /dev/null +++ b/shared/extra-utils/custom-pages/index.ts @@ -0,0 +1 @@ +export * from './custom-pages-command' diff --git a/shared/extra-utils/feeds/feeds-command.ts b/shared/extra-utils/feeds/feeds-command.ts new file mode 100644 index 000000000..3c95f9536 --- /dev/null +++ b/shared/extra-utils/feeds/feeds-command.ts @@ -0,0 +1,44 @@ + +import { HttpStatusCode } from '@shared/models' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +type FeedType = 'videos' | 'video-comments' | 'subscriptions' + +export class FeedCommand extends AbstractCommand { + + getXML (options: OverrideCommandOptions & { + feed: FeedType + format?: string + }) { + const { feed, format } = options + const path = '/feeds/' + feed + '.xml' + + return this.getRequestText({ + ...options, + + path, + query: format ? { format } : undefined, + accept: 'application/xml', + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + getJSON (options: OverrideCommandOptions & { + feed: FeedType + query?: { [ id: string ]: any } + }) { + const { feed, query } = options + const path = '/feeds/' + feed + '.json' + + return this.getRequestText({ + ...options, + + path, + query, + accept: 'application/json', + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } +} diff --git a/shared/extra-utils/feeds/feeds.ts b/shared/extra-utils/feeds/feeds.ts deleted file mode 100644 index ce0a98c6d..000000000 --- a/shared/extra-utils/feeds/feeds.ts +++ /dev/null @@ -1,33 +0,0 @@ -import * as request from 'supertest' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' - -type FeedType = 'videos' | 'video-comments' | 'subscriptions' - -function getXMLfeed (url: string, feed: FeedType, format?: string) { - const path = '/feeds/' + feed + '.xml' - - return request(url) - .get(path) - .query((format) ? { format: format } : {}) - .set('Accept', 'application/xml') - .expect(HttpStatusCode.OK_200) - .expect('Content-Type', /xml/) -} - -function getJSONfeed (url: string, feed: FeedType, query: any = {}, statusCodeExpected = HttpStatusCode.OK_200) { - const path = '/feeds/' + feed + '.json' - - return request(url) - .get(path) - .query(query) - .set('Accept', 'application/json') - .expect(statusCodeExpected) - .expect('Content-Type', /json/) -} - -// --------------------------------------------------------------------------- - -export { - getXMLfeed, - getJSONfeed -} diff --git a/shared/extra-utils/feeds/index.ts b/shared/extra-utils/feeds/index.ts new file mode 100644 index 000000000..662a22b6f --- /dev/null +++ b/shared/extra-utils/feeds/index.ts @@ -0,0 +1 @@ +export * from './feeds-command' diff --git a/shared/extra-utils/index.ts b/shared/extra-utils/index.ts index 87ee8abba..4b3636d06 100644 --- a/shared/extra-utils/index.ts +++ b/shared/extra-utils/index.ts @@ -1,51 +1,15 @@ -export * from './bulk/bulk' - -export * from './cli/cli' - -export * from './custom-pages/custom-pages' - -export * from './feeds/feeds' - -export * from './mock-servers/mock-instances-index' - -export * from './miscs/email' -export * from './miscs/sql' -export * from './miscs/miscs' -export * from './miscs/stubs' - -export * from './moderation/abuses' -export * from './plugins/mock-blocklist' - -export * from './requests/check-api-params' -export * from './requests/requests' - -export * from './search/video-channels' -export * from './search/video-playlists' -export * from './search/videos' - -export * from './server/activitypub' -export * from './server/clients' -export * from './server/config' -export * from './server/debug' -export * from './server/follows' -export * from './server/jobs' -export * from './server/plugins' -export * from './server/servers' - -export * from './users/accounts' -export * from './users/blocklist' -export * from './users/login' -export * from './users/user-notifications' -export * from './users/user-subscriptions' -export * from './users/users' - -export * from './videos/live' -export * from './videos/services' -export * from './videos/video-blacklist' -export * from './videos/video-captions' -export * from './videos/video-change-ownership' -export * from './videos/video-channels' -export * from './videos/video-comments' -export * from './videos/video-playlists' -export * from './videos/video-streaming-playlists' -export * from './videos/videos' +export * from './bulk' +export * from './cli' +export * from './custom-pages' +export * from './feeds' +export * from './logs' +export * from './miscs' +export * from './mock-servers' +export * from './moderation' +export * from './overviews' +export * from './requests' +export * from './search' +export * from './server' +export * from './socket' +export * from './users' +export * from './videos' diff --git a/shared/extra-utils/logs/index.ts b/shared/extra-utils/logs/index.ts new file mode 100644 index 000000000..69452d7f0 --- /dev/null +++ b/shared/extra-utils/logs/index.ts @@ -0,0 +1 @@ +export * from './logs-command' diff --git a/shared/extra-utils/logs/logs-command.ts b/shared/extra-utils/logs/logs-command.ts new file mode 100644 index 000000000..5912e814f --- /dev/null +++ b/shared/extra-utils/logs/logs-command.ts @@ -0,0 +1,43 @@ +import { HttpStatusCode } from '@shared/models' +import { LogLevel } from '../../models/server/log-level.type' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export class LogsCommand extends AbstractCommand { + + getLogs (options: OverrideCommandOptions & { + startDate: Date + endDate?: Date + level?: LogLevel + }) { + const { startDate, endDate, level } = options + const path = '/api/v1/server/logs' + + return this.getRequestBody({ + ...options, + + path, + query: { startDate, endDate, level }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + getAuditLogs (options: OverrideCommandOptions & { + startDate: Date + endDate?: Date + }) { + const { startDate, endDate } = options + + const path = '/api/v1/server/audit-logs' + + return this.getRequestBody({ + ...options, + + path, + query: { startDate, endDate }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + +} diff --git a/shared/extra-utils/logs/logs.ts b/shared/extra-utils/logs/logs.ts deleted file mode 100644 index 8d741276c..000000000 --- a/shared/extra-utils/logs/logs.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { makeGetRequest } from '../requests/requests' -import { LogLevel } from '../../models/server/log-level.type' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' - -function getLogs (url: string, accessToken: string, startDate: Date, endDate?: Date, level?: LogLevel) { - const path = '/api/v1/server/logs' - - return makeGetRequest({ - url, - path, - token: accessToken, - query: { startDate, endDate, level }, - statusCodeExpected: HttpStatusCode.OK_200 - }) -} - -function getAuditLogs (url: string, accessToken: string, startDate: Date, endDate?: Date) { - const path = '/api/v1/server/audit-logs' - - return makeGetRequest({ - url, - path, - token: accessToken, - query: { startDate, endDate }, - statusCodeExpected: HttpStatusCode.OK_200 - }) -} - -export { - getLogs, - getAuditLogs -} diff --git a/shared/extra-utils/miscs/checks.ts b/shared/extra-utils/miscs/checks.ts new file mode 100644 index 000000000..7fc92f804 --- /dev/null +++ b/shared/extra-utils/miscs/checks.ts @@ -0,0 +1,46 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */ + +import { expect } from 'chai' +import { pathExists, readFile } from 'fs-extra' +import { join } from 'path' +import { root } from '@server/helpers/core-utils' +import { HttpStatusCode } from '@shared/models' +import { makeGetRequest } from '../requests' +import { PeerTubeServer } from '../server' + +// Default interval -> 5 minutes +function dateIsValid (dateString: string, interval = 300000) { + const dateToCheck = new Date(dateString) + const now = new Date() + + return Math.abs(now.getTime() - dateToCheck.getTime()) <= interval +} + +async function testImage (url: string, imageName: string, imagePath: string, extension = '.jpg') { + const res = await makeGetRequest({ + url, + path: imagePath, + expectedStatus: HttpStatusCode.OK_200 + }) + + const body = res.body + + const data = await readFile(join(root(), 'server', 'tests', 'fixtures', imageName + extension)) + const minLength = body.length - ((30 * body.length) / 100) + const maxLength = body.length + ((30 * body.length) / 100) + + expect(data.length).to.be.above(minLength, 'the generated image is way smaller than the recorded fixture') + expect(data.length).to.be.below(maxLength, 'the generated image is way larger than the recorded fixture') +} + +async function testFileExistsOrNot (server: PeerTubeServer, directory: string, filePath: string, exist: boolean) { + const base = server.servers.buildDirectory(directory) + + expect(await pathExists(join(base, filePath))).to.equal(exist) +} + +export { + dateIsValid, + testImage, + testFileExistsOrNot +} diff --git a/shared/extra-utils/miscs/generate.ts b/shared/extra-utils/miscs/generate.ts new file mode 100644 index 000000000..8d6435481 --- /dev/null +++ b/shared/extra-utils/miscs/generate.ts @@ -0,0 +1,61 @@ +import * as ffmpeg from 'fluent-ffmpeg' +import { ensureDir, pathExists } from 'fs-extra' +import { dirname } from 'path' +import { buildAbsoluteFixturePath } from './tests' + +async function generateHighBitrateVideo () { + const tempFixturePath = buildAbsoluteFixturePath('video_high_bitrate_1080p.mp4', true) + + await ensureDir(dirname(tempFixturePath)) + + const exists = await pathExists(tempFixturePath) + if (!exists) { + console.log('Generating high bitrate video.') + + // Generate a random, high bitrate video on the fly, so we don't have to include + // a large file in the repo. The video needs to have a certain minimum length so + // that FFmpeg properly applies bitrate limits. + // https://stackoverflow.com/a/15795112 + return new Promise((res, rej) => { + ffmpeg() + .outputOptions([ '-f rawvideo', '-video_size 1920x1080', '-i /dev/urandom' ]) + .outputOptions([ '-ac 2', '-f s16le', '-i /dev/urandom', '-t 10' ]) + .outputOptions([ '-maxrate 10M', '-bufsize 10M' ]) + .output(tempFixturePath) + .on('error', rej) + .on('end', () => res(tempFixturePath)) + .run() + }) + } + + return tempFixturePath +} + +async function generateVideoWithFramerate (fps = 60) { + const tempFixturePath = buildAbsoluteFixturePath(`video_${fps}fps.mp4`, true) + + await ensureDir(dirname(tempFixturePath)) + + const exists = await pathExists(tempFixturePath) + if (!exists) { + console.log('Generating video with framerate %d.', fps) + + return new Promise((res, rej) => { + ffmpeg() + .outputOptions([ '-f rawvideo', '-video_size 1280x720', '-i /dev/urandom' ]) + .outputOptions([ '-ac 2', '-f s16le', '-i /dev/urandom', '-t 10' ]) + .outputOptions([ `-r ${fps}` ]) + .output(tempFixturePath) + .on('error', rej) + .on('end', () => res(tempFixturePath)) + .run() + }) + } + + return tempFixturePath +} + +export { + generateHighBitrateVideo, + generateVideoWithFramerate +} diff --git a/shared/extra-utils/miscs/index.ts b/shared/extra-utils/miscs/index.ts new file mode 100644 index 000000000..4474661de --- /dev/null +++ b/shared/extra-utils/miscs/index.ts @@ -0,0 +1,5 @@ +export * from './checks' +export * from './generate' +export * from './sql-command' +export * from './tests' +export * from './webtorrent' diff --git a/shared/extra-utils/miscs/miscs.ts b/shared/extra-utils/miscs/miscs.ts deleted file mode 100644 index 462b914d4..000000000 --- a/shared/extra-utils/miscs/miscs.ts +++ /dev/null @@ -1,170 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ - -import * as chai from 'chai' -import * as ffmpeg from 'fluent-ffmpeg' -import { ensureDir, pathExists, readFile, stat } from 'fs-extra' -import { basename, dirname, isAbsolute, join, resolve } from 'path' -import * as request from 'supertest' -import * as WebTorrent from 'webtorrent' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' - -const expect = chai.expect -let webtorrent: WebTorrent.Instance - -function immutableAssign (target: T, source: U) { - return Object.assign<{}, T, U>({}, target, source) -} - -// Default interval -> 5 minutes -function dateIsValid (dateString: string, interval = 300000) { - const dateToCheck = new Date(dateString) - const now = new Date() - - return Math.abs(now.getTime() - dateToCheck.getTime()) <= interval -} - -function wait (milliseconds: number) { - return new Promise(resolve => setTimeout(resolve, milliseconds)) -} - -function webtorrentAdd (torrent: string, refreshWebTorrent = false) { - const WebTorrent = require('webtorrent') - - if (!webtorrent) webtorrent = new WebTorrent() - if (refreshWebTorrent === true) webtorrent = new WebTorrent() - - return new Promise(res => webtorrent.add(torrent, res)) -} - -function root () { - // We are in /miscs - let root = join(__dirname, '..', '..', '..') - - if (basename(root) === 'dist') root = resolve(root, '..') - - return root -} - -function buildServerDirectory (server: { internalServerNumber: number }, directory: string) { - return join(root(), 'test' + server.internalServerNumber, directory) -} - -async function testImage (url: string, imageName: string, imagePath: string, extension = '.jpg') { - const res = await request(url) - .get(imagePath) - .expect(HttpStatusCode.OK_200) - - const body = res.body - - const data = await readFile(join(root(), 'server', 'tests', 'fixtures', imageName + extension)) - const minLength = body.length - ((30 * body.length) / 100) - const maxLength = body.length + ((30 * body.length) / 100) - - expect(data.length).to.be.above(minLength, 'the generated image is way smaller than the recorded fixture') - expect(data.length).to.be.below(maxLength, 'the generated image is way larger than the recorded fixture') -} - -async function testFileExistsOrNot (server: { internalServerNumber: number }, directory: string, filePath: string, exist: boolean) { - const base = buildServerDirectory(server, directory) - - expect(await pathExists(join(base, filePath))).to.equal(exist) -} - -function isGithubCI () { - return !!process.env.GITHUB_WORKSPACE -} - -function buildAbsoluteFixturePath (path: string, customCIPath = false) { - if (isAbsolute(path)) return path - - if (customCIPath && process.env.GITHUB_WORKSPACE) { - return join(process.env.GITHUB_WORKSPACE, 'fixtures', path) - } - - return join(root(), 'server', 'tests', 'fixtures', path) -} - -function areHttpImportTestsDisabled () { - const disabled = process.env.DISABLE_HTTP_IMPORT_TESTS === 'true' - - if (disabled) console.log('Import tests are disabled') - - return disabled -} - -async function generateHighBitrateVideo () { - const tempFixturePath = buildAbsoluteFixturePath('video_high_bitrate_1080p.mp4', true) - - await ensureDir(dirname(tempFixturePath)) - - const exists = await pathExists(tempFixturePath) - if (!exists) { - console.log('Generating high bitrate video.') - - // Generate a random, high bitrate video on the fly, so we don't have to include - // a large file in the repo. The video needs to have a certain minimum length so - // that FFmpeg properly applies bitrate limits. - // https://stackoverflow.com/a/15795112 - return new Promise((res, rej) => { - ffmpeg() - .outputOptions([ '-f rawvideo', '-video_size 1920x1080', '-i /dev/urandom' ]) - .outputOptions([ '-ac 2', '-f s16le', '-i /dev/urandom', '-t 10' ]) - .outputOptions([ '-maxrate 10M', '-bufsize 10M' ]) - .output(tempFixturePath) - .on('error', rej) - .on('end', () => res(tempFixturePath)) - .run() - }) - } - - return tempFixturePath -} - -async function generateVideoWithFramerate (fps = 60) { - const tempFixturePath = buildAbsoluteFixturePath(`video_${fps}fps.mp4`, true) - - await ensureDir(dirname(tempFixturePath)) - - const exists = await pathExists(tempFixturePath) - if (!exists) { - console.log('Generating video with framerate %d.', fps) - - return new Promise((res, rej) => { - ffmpeg() - .outputOptions([ '-f rawvideo', '-video_size 1280x720', '-i /dev/urandom' ]) - .outputOptions([ '-ac 2', '-f s16le', '-i /dev/urandom', '-t 10' ]) - .outputOptions([ `-r ${fps}` ]) - .output(tempFixturePath) - .on('error', rej) - .on('end', () => res(tempFixturePath)) - .run() - }) - } - - return tempFixturePath -} - -async function getFileSize (path: string) { - const stats = await stat(path) - - return stats.size -} - -// --------------------------------------------------------------------------- - -export { - dateIsValid, - wait, - areHttpImportTestsDisabled, - buildServerDirectory, - webtorrentAdd, - getFileSize, - immutableAssign, - testImage, - isGithubCI, - buildAbsoluteFixturePath, - testFileExistsOrNot, - root, - generateHighBitrateVideo, - generateVideoWithFramerate -} diff --git a/shared/extra-utils/miscs/sql-command.ts b/shared/extra-utils/miscs/sql-command.ts new file mode 100644 index 000000000..80c8cd271 --- /dev/null +++ b/shared/extra-utils/miscs/sql-command.ts @@ -0,0 +1,142 @@ +import { QueryTypes, Sequelize } from 'sequelize' +import { AbstractCommand } from '../shared/abstract-command' + +export class SQLCommand extends AbstractCommand { + private sequelize: Sequelize + + deleteAll (table: string) { + const seq = this.getSequelize() + + const options = { type: QueryTypes.DELETE } + + return seq.query(`DELETE FROM "${table}"`, options) + } + + async getCount (table: string) { + const seq = this.getSequelize() + + const options = { type: QueryTypes.SELECT as QueryTypes.SELECT } + + const [ { total } ] = await seq.query<{ total: string }>(`SELECT COUNT(*) as total FROM "${table}"`, options) + if (total === null) return 0 + + return parseInt(total, 10) + } + + setActorField (to: string, field: string, value: string) { + const seq = this.getSequelize() + + const options = { type: QueryTypes.UPDATE } + + return seq.query(`UPDATE actor SET "${field}" = '${value}' WHERE url = '${to}'`, options) + } + + setVideoField (uuid: string, field: string, value: string) { + const seq = this.getSequelize() + + const options = { type: QueryTypes.UPDATE } + + return seq.query(`UPDATE video SET "${field}" = '${value}' WHERE uuid = '${uuid}'`, options) + } + + setPlaylistField (uuid: string, field: string, value: string) { + const seq = this.getSequelize() + + const options = { type: QueryTypes.UPDATE } + + return seq.query(`UPDATE "videoPlaylist" SET "${field}" = '${value}' WHERE uuid = '${uuid}'`, options) + } + + async countVideoViewsOf (uuid: string) { + const seq = this.getSequelize() + + // tslint:disable + const query = 'SELECT SUM("videoView"."views") AS "total" FROM "videoView" ' + + `INNER JOIN "video" ON "video"."id" = "videoView"."videoId" WHERE "video"."uuid" = '${uuid}'` + + const options = { type: QueryTypes.SELECT as QueryTypes.SELECT } + const [ { total } ] = await seq.query<{ total: number }>(query, options) + + if (!total) return 0 + + return parseInt(total + '', 10) + } + + getActorImage (filename: string) { + return this.selectQuery(`SELECT * FROM "actorImage" WHERE filename = '${filename}'`) + .then(rows => rows[0]) + } + + selectQuery (query: string) { + const seq = this.getSequelize() + const options = { type: QueryTypes.SELECT as QueryTypes.SELECT } + + return seq.query(query, options) + } + + updateQuery (query: string) { + const seq = this.getSequelize() + const options = { type: QueryTypes.UPDATE as QueryTypes.UPDATE } + + return seq.query(query, options) + } + + setPluginField (pluginName: string, field: string, value: string) { + const seq = this.getSequelize() + + const options = { type: QueryTypes.UPDATE } + + return seq.query(`UPDATE "plugin" SET "${field}" = '${value}' WHERE "name" = '${pluginName}'`, options) + } + + setPluginVersion (pluginName: string, newVersion: string) { + return this.setPluginField(pluginName, 'version', newVersion) + } + + setPluginLatestVersion (pluginName: string, newVersion: string) { + return this.setPluginField(pluginName, 'latestVersion', newVersion) + } + + setActorFollowScores (newScore: number) { + const seq = this.getSequelize() + + const options = { type: QueryTypes.UPDATE } + + return seq.query(`UPDATE "actorFollow" SET "score" = ${newScore}`, options) + } + + setTokenField (accessToken: string, field: string, value: string) { + const seq = this.getSequelize() + + const options = { type: QueryTypes.UPDATE } + + return seq.query(`UPDATE "oAuthToken" SET "${field}" = '${value}' WHERE "accessToken" = '${accessToken}'`, options) + } + + async cleanup () { + if (!this.sequelize) return + + await this.sequelize.close() + this.sequelize = undefined + } + + private getSequelize () { + if (this.sequelize) return this.sequelize + + const dbname = 'peertube_test' + this.server.internalServerNumber + const username = 'peertube' + const password = 'peertube' + const host = 'localhost' + const port = 5432 + + this.sequelize = new Sequelize(dbname, username, password, { + dialect: 'postgres', + host, + port, + logging: false + }) + + return this.sequelize + } + +} diff --git a/shared/extra-utils/miscs/sql.ts b/shared/extra-utils/miscs/sql.ts deleted file mode 100644 index 65a0aa5fe..000000000 --- a/shared/extra-utils/miscs/sql.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { QueryTypes, Sequelize } from 'sequelize' -import { ServerInfo } from '../server/servers' - -const sequelizes: { [ id: number ]: Sequelize } = {} - -function getSequelize (internalServerNumber: number) { - if (sequelizes[internalServerNumber]) return sequelizes[internalServerNumber] - - const dbname = 'peertube_test' + internalServerNumber - const username = 'peertube' - const password = 'peertube' - const host = 'localhost' - const port = 5432 - - const seq = new Sequelize(dbname, username, password, { - dialect: 'postgres', - host, - port, - logging: false - }) - - sequelizes[internalServerNumber] = seq - - return seq -} - -function deleteAll (internalServerNumber: number, table: string) { - const seq = getSequelize(internalServerNumber) - - const options = { type: QueryTypes.DELETE } - - return seq.query(`DELETE FROM "${table}"`, options) -} - -async function getCount (internalServerNumber: number, table: string) { - const seq = getSequelize(internalServerNumber) - - const options = { type: QueryTypes.SELECT as QueryTypes.SELECT } - - const [ { total } ] = await seq.query<{ total: string }>(`SELECT COUNT(*) as total FROM "${table}"`, options) - if (total === null) return 0 - - return parseInt(total, 10) -} - -function setActorField (internalServerNumber: number, to: string, field: string, value: string) { - const seq = getSequelize(internalServerNumber) - - const options = { type: QueryTypes.UPDATE } - - return seq.query(`UPDATE actor SET "${field}" = '${value}' WHERE url = '${to}'`, options) -} - -function setVideoField (internalServerNumber: number, uuid: string, field: string, value: string) { - const seq = getSequelize(internalServerNumber) - - const options = { type: QueryTypes.UPDATE } - - return seq.query(`UPDATE video SET "${field}" = '${value}' WHERE uuid = '${uuid}'`, options) -} - -function setPlaylistField (internalServerNumber: number, uuid: string, field: string, value: string) { - const seq = getSequelize(internalServerNumber) - - const options = { type: QueryTypes.UPDATE } - - return seq.query(`UPDATE "videoPlaylist" SET "${field}" = '${value}' WHERE uuid = '${uuid}'`, options) -} - -async function countVideoViewsOf (internalServerNumber: number, uuid: string) { - const seq = getSequelize(internalServerNumber) - - // tslint:disable - const query = 'SELECT SUM("videoView"."views") AS "total" FROM "videoView" ' + - `INNER JOIN "video" ON "video"."id" = "videoView"."videoId" WHERE "video"."uuid" = '${uuid}'` - - const options = { type: QueryTypes.SELECT as QueryTypes.SELECT } - const [ { total } ] = await seq.query<{ total: number }>(query, options) - - if (!total) return 0 - - return parseInt(total + '', 10) -} - -function getActorImage (internalServerNumber: number, filename: string) { - return selectQuery(internalServerNumber, `SELECT * FROM "actorImage" WHERE filename = '${filename}'`) - .then(rows => rows[0]) -} - -function selectQuery (internalServerNumber: number, query: string) { - const seq = getSequelize(internalServerNumber) - const options = { type: QueryTypes.SELECT as QueryTypes.SELECT } - - return seq.query(query, options) -} - -function updateQuery (internalServerNumber: number, query: string) { - const seq = getSequelize(internalServerNumber) - const options = { type: QueryTypes.UPDATE as QueryTypes.UPDATE } - - return seq.query(query, options) -} - -async function closeAllSequelize (servers: ServerInfo[]) { - for (const server of servers) { - if (sequelizes[server.internalServerNumber]) { - await sequelizes[server.internalServerNumber].close() - // eslint-disable-next-line - delete sequelizes[server.internalServerNumber] - } - } -} - -function setPluginField (internalServerNumber: number, pluginName: string, field: string, value: string) { - const seq = getSequelize(internalServerNumber) - - const options = { type: QueryTypes.UPDATE } - - return seq.query(`UPDATE "plugin" SET "${field}" = '${value}' WHERE "name" = '${pluginName}'`, options) -} - -function setPluginVersion (internalServerNumber: number, pluginName: string, newVersion: string) { - return setPluginField(internalServerNumber, pluginName, 'version', newVersion) -} - -function setPluginLatestVersion (internalServerNumber: number, pluginName: string, newVersion: string) { - return setPluginField(internalServerNumber, pluginName, 'latestVersion', newVersion) -} - -function setActorFollowScores (internalServerNumber: number, newScore: number) { - const seq = getSequelize(internalServerNumber) - - const options = { type: QueryTypes.UPDATE } - - return seq.query(`UPDATE "actorFollow" SET "score" = ${newScore}`, options) -} - -function setTokenField (internalServerNumber: number, accessToken: string, field: string, value: string) { - const seq = getSequelize(internalServerNumber) - - const options = { type: QueryTypes.UPDATE } - - return seq.query(`UPDATE "oAuthToken" SET "${field}" = '${value}' WHERE "accessToken" = '${accessToken}'`, options) -} - -export { - setVideoField, - setPlaylistField, - setActorField, - countVideoViewsOf, - setPluginVersion, - setPluginLatestVersion, - selectQuery, - getActorImage, - deleteAll, - setTokenField, - updateQuery, - setActorFollowScores, - closeAllSequelize, - getCount -} diff --git a/shared/extra-utils/miscs/stubs.ts b/shared/extra-utils/miscs/stubs.ts deleted file mode 100644 index d1eb0e3b2..000000000 --- a/shared/extra-utils/miscs/stubs.ts +++ /dev/null @@ -1,14 +0,0 @@ -function buildRequestStub (): any { - return { } -} - -function buildResponseStub (): any { - return { - locals: {} - } -} - -export { - buildResponseStub, - buildRequestStub -} diff --git a/shared/extra-utils/miscs/tests.ts b/shared/extra-utils/miscs/tests.ts new file mode 100644 index 000000000..3dfb2487e --- /dev/null +++ b/shared/extra-utils/miscs/tests.ts @@ -0,0 +1,94 @@ +import { stat } from 'fs-extra' +import { basename, isAbsolute, join, resolve } from 'path' + +const FIXTURE_URLS = { + youtube: 'https://www.youtube.com/watch?v=msX3jv1XdvM', + + /** + * The video is used to check format-selection correctness wrt. HDR, + * which brings its own set of oddities outside of a MediaSource. + * FIXME: refactor once HDR is supported at playback + * + * The video needs to have the following format_ids: + * (which you can check by using `youtube-dl -F`): + * - 303 (1080p webm vp9) + * - 299 (1080p mp4 avc1) + * - 335 (1080p webm vp9.2 HDR) + * + * 15 jan. 2021: TEST VIDEO NOT CURRENTLY PROVIDING + * - 400 (1080p mp4 av01) + * - 315 (2160p webm vp9 HDR) + * - 337 (2160p webm vp9.2 HDR) + * - 401 (2160p mp4 av01 HDR) + */ + youtubeHDR: 'https://www.youtube.com/watch?v=qR5vOXbZsI4', + + // eslint-disable-next-line max-len + magnet: 'magnet:?xs=https%3A%2F%2Fpeertube2.cpy.re%2Fstatic%2Ftorrents%2Fb209ca00-c8bb-4b2b-b421-1ede169f3dbc-720.torrent&xt=urn:btih:0f498834733e8057ed5c6f2ee2b4efd8d84a76ee&dn=super+peertube2+video&tr=wss%3A%2F%2Fpeertube2.cpy.re%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fpeertube2.cpy.re%2Ftracker%2Fannounce&ws=https%3A%2F%2Fpeertube2.cpy.re%2Fstatic%2Fwebseed%2Fb209ca00-c8bb-4b2b-b421-1ede169f3dbc-720.mp4', + + badVideo: 'https://download.cpy.re/peertube/bad_video.mp4', + goodVideo: 'https://download.cpy.re/peertube/good_video.mp4', + video4K: 'https://download.cpy.re/peertube/4k_file.txt' +} + +function parallelTests () { + return process.env.MOCHA_PARALLEL === 'true' +} + +function isGithubCI () { + return !!process.env.GITHUB_WORKSPACE +} + +function areHttpImportTestsDisabled () { + const disabled = process.env.DISABLE_HTTP_IMPORT_TESTS === 'true' + + if (disabled) console.log('Import tests are disabled') + + return disabled +} + +function buildAbsoluteFixturePath (path: string, customCIPath = false) { + if (isAbsolute(path)) return path + + if (customCIPath && process.env.GITHUB_WORKSPACE) { + return join(process.env.GITHUB_WORKSPACE, 'fixtures', path) + } + + return join(root(), 'server', 'tests', 'fixtures', path) +} + +function root () { + // We are in /miscs + let root = join(__dirname, '..', '..', '..') + + if (basename(root) === 'dist') root = resolve(root, '..') + + return root +} + +function wait (milliseconds: number) { + return new Promise(resolve => setTimeout(resolve, milliseconds)) +} + +async function getFileSize (path: string) { + const stats = await stat(path) + + return stats.size +} + +function buildRequestStub (): any { + return { } +} + +export { + FIXTURE_URLS, + + parallelTests, + isGithubCI, + areHttpImportTestsDisabled, + buildAbsoluteFixturePath, + getFileSize, + buildRequestStub, + wait, + root +} diff --git a/shared/extra-utils/miscs/webtorrent.ts b/shared/extra-utils/miscs/webtorrent.ts new file mode 100644 index 000000000..a1097effe --- /dev/null +++ b/shared/extra-utils/miscs/webtorrent.ts @@ -0,0 +1,31 @@ +import { readFile } from 'fs-extra' +import * as parseTorrent from 'parse-torrent' +import { basename, join } from 'path' +import * as WebTorrent from 'webtorrent' +import { VideoFile } from '@shared/models' +import { PeerTubeServer } from '../server' + +let webtorrent: WebTorrent.Instance + +function webtorrentAdd (torrent: string, refreshWebTorrent = false) { + const WebTorrent = require('webtorrent') + + if (!webtorrent) webtorrent = new WebTorrent() + if (refreshWebTorrent === true) webtorrent = new WebTorrent() + + return new Promise(res => webtorrent.add(torrent, res)) +} + +async function parseTorrentVideo (server: PeerTubeServer, file: VideoFile) { + const torrentName = basename(file.torrentUrl) + const torrentPath = server.servers.buildDirectory(join('torrents', torrentName)) + + const data = await readFile(torrentPath) + + return parseTorrent(data) +} + +export { + webtorrentAdd, + parseTorrentVideo +} diff --git a/shared/extra-utils/mock-servers/index.ts b/shared/extra-utils/mock-servers/index.ts new file mode 100644 index 000000000..0ec07f685 --- /dev/null +++ b/shared/extra-utils/mock-servers/index.ts @@ -0,0 +1,4 @@ +export * from './mock-email' +export * from './mock-instances-index' +export * from './mock-joinpeertube-versions' +export * from './mock-plugin-blocklist' diff --git a/shared/extra-utils/miscs/email.ts b/shared/extra-utils/mock-servers/mock-email.ts similarity index 92% rename from shared/extra-utils/miscs/email.ts rename to shared/extra-utils/mock-servers/mock-email.ts index 9fc9a5ad0..ffd62e325 100644 --- a/shared/extra-utils/miscs/email.ts +++ b/shared/extra-utils/mock-servers/mock-email.ts @@ -1,6 +1,6 @@ import { ChildProcess } from 'child_process' -import { randomInt } from '../../core-utils/miscs/miscs' -import { parallelTests } from '../server/servers' +import { randomInt } from '@shared/core-utils' +import { parallelTests } from '../miscs' const MailDev = require('maildev') diff --git a/shared/extra-utils/mock-servers/joinpeertube-versions.ts b/shared/extra-utils/mock-servers/mock-joinpeertube-versions.ts similarity index 100% rename from shared/extra-utils/mock-servers/joinpeertube-versions.ts rename to shared/extra-utils/mock-servers/mock-joinpeertube-versions.ts diff --git a/shared/extra-utils/plugins/mock-blocklist.ts b/shared/extra-utils/mock-servers/mock-plugin-blocklist.ts similarity index 100% rename from shared/extra-utils/plugins/mock-blocklist.ts rename to shared/extra-utils/mock-servers/mock-plugin-blocklist.ts diff --git a/shared/extra-utils/moderation/abuses-command.ts b/shared/extra-utils/moderation/abuses-command.ts new file mode 100644 index 000000000..0db32ba46 --- /dev/null +++ b/shared/extra-utils/moderation/abuses-command.ts @@ -0,0 +1,228 @@ +import { pick } from '@shared/core-utils' +import { + AbuseFilter, + AbuseMessage, + AbusePredefinedReasonsString, + AbuseState, + AbuseUpdate, + AbuseVideoIs, + AdminAbuse, + HttpStatusCode, + ResultList, + UserAbuse +} from '@shared/models' +import { unwrapBody } from '../requests/requests' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export class AbusesCommand extends AbstractCommand { + + report (options: OverrideCommandOptions & { + reason: string + + accountId?: number + videoId?: number + commentId?: number + + predefinedReasons?: AbusePredefinedReasonsString[] + + startAt?: number + endAt?: number + }) { + const path = '/api/v1/abuses' + + const video = options.videoId + ? { + id: options.videoId, + startAt: options.startAt, + endAt: options.endAt + } + : undefined + + const comment = options.commentId + ? { id: options.commentId } + : undefined + + const account = options.accountId + ? { id: options.accountId } + : undefined + + const body = { + account, + video, + comment, + + reason: options.reason, + predefinedReasons: options.predefinedReasons + } + + return unwrapBody<{ abuse: { id: number } }>(this.postBodyRequest({ + ...options, + + path, + fields: body, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + })) + } + + getAdminList (options: OverrideCommandOptions & { + start?: number + count?: number + sort?: string + + id?: number + predefinedReason?: AbusePredefinedReasonsString + search?: string + filter?: AbuseFilter + state?: AbuseState + videoIs?: AbuseVideoIs + searchReporter?: string + searchReportee?: string + searchVideo?: string + searchVideoChannel?: string + } = {}) { + const toPick: (keyof typeof options)[] = [ + 'count', + 'filter', + 'id', + 'predefinedReason', + 'search', + 'searchReportee', + 'searchReporter', + 'searchVideo', + 'searchVideoChannel', + 'sort', + 'start', + 'state', + 'videoIs' + ] + + const path = '/api/v1/abuses' + + const defaultQuery = { sort: 'createdAt' } + const query = { ...defaultQuery, ...pick(options, toPick) } + + return this.getRequestBody>({ + ...options, + + path, + query, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + getUserList (options: OverrideCommandOptions & { + start?: number + count?: number + sort?: string + + id?: number + search?: string + state?: AbuseState + }) { + const toPick: (keyof typeof options)[] = [ + 'id', + 'search', + 'state', + 'start', + 'count', + 'sort' + ] + + const path = '/api/v1/users/me/abuses' + + const defaultQuery = { sort: 'createdAt' } + const query = { ...defaultQuery, ...pick(options, toPick) } + + return this.getRequestBody>({ + ...options, + + path, + query, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + update (options: OverrideCommandOptions & { + abuseId: number + body: AbuseUpdate + }) { + const { abuseId, body } = options + const path = '/api/v1/abuses/' + abuseId + + return this.putBodyRequest({ + ...options, + + path, + fields: body, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + delete (options: OverrideCommandOptions & { + abuseId: number + }) { + const { abuseId } = options + const path = '/api/v1/abuses/' + abuseId + + return this.deleteRequest({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + listMessages (options: OverrideCommandOptions & { + abuseId: number + }) { + const { abuseId } = options + const path = '/api/v1/abuses/' + abuseId + '/messages' + + return this.getRequestBody>({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + deleteMessage (options: OverrideCommandOptions & { + abuseId: number + messageId: number + }) { + const { abuseId, messageId } = options + const path = '/api/v1/abuses/' + abuseId + '/messages/' + messageId + + return this.deleteRequest({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + addMessage (options: OverrideCommandOptions & { + abuseId: number + message: string + }) { + const { abuseId, message } = options + const path = '/api/v1/abuses/' + abuseId + '/messages' + + return this.postBodyRequest({ + ...options, + + path, + fields: { message }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + +} diff --git a/shared/extra-utils/moderation/abuses.ts b/shared/extra-utils/moderation/abuses.ts deleted file mode 100644 index c0fda722f..000000000 --- a/shared/extra-utils/moderation/abuses.ts +++ /dev/null @@ -1,244 +0,0 @@ -import { AbuseFilter, AbusePredefinedReasonsString, AbuseState, AbuseUpdate, AbuseVideoIs } from '@shared/models' -import { makeDeleteRequest, makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' - -function reportAbuse (options: { - url: string - token: string - - reason: string - - accountId?: number - videoId?: number - commentId?: number - - predefinedReasons?: AbusePredefinedReasonsString[] - - startAt?: number - endAt?: number - - statusCodeExpected?: number -}) { - const path = '/api/v1/abuses' - - const video = options.videoId - ? { - id: options.videoId, - startAt: options.startAt, - endAt: options.endAt - } - : undefined - - const comment = options.commentId - ? { id: options.commentId } - : undefined - - const account = options.accountId - ? { id: options.accountId } - : undefined - - const body = { - account, - video, - comment, - - reason: options.reason, - predefinedReasons: options.predefinedReasons - } - - return makePostBodyRequest({ - url: options.url, - path, - token: options.token, - - fields: body, - statusCodeExpected: options.statusCodeExpected || HttpStatusCode.OK_200 - }) -} - -function getAdminAbusesList (options: { - url: string - token: string - - start?: number - count?: number - sort?: string - - id?: number - predefinedReason?: AbusePredefinedReasonsString - search?: string - filter?: AbuseFilter - state?: AbuseState - videoIs?: AbuseVideoIs - searchReporter?: string - searchReportee?: string - searchVideo?: string - searchVideoChannel?: string -}) { - const { - url, - token, - start, - count, - sort, - id, - predefinedReason, - search, - filter, - state, - videoIs, - searchReporter, - searchReportee, - searchVideo, - searchVideoChannel - } = options - const path = '/api/v1/abuses' - - const query = { - id, - predefinedReason, - search, - state, - filter, - videoIs, - start, - count, - sort: sort || 'createdAt', - searchReporter, - searchReportee, - searchVideo, - searchVideoChannel - } - - return makeGetRequest({ - url, - path, - token, - query, - statusCodeExpected: HttpStatusCode.OK_200 - }) -} - -function getUserAbusesList (options: { - url: string - token: string - - start?: number - count?: number - sort?: string - - id?: number - search?: string - state?: AbuseState -}) { - const { - url, - token, - start, - count, - sort, - id, - search, - state - } = options - const path = '/api/v1/users/me/abuses' - - const query = { - id, - search, - state, - start, - count, - sort: sort || 'createdAt' - } - - return makeGetRequest({ - url, - path, - token, - query, - statusCodeExpected: HttpStatusCode.OK_200 - }) -} - -function updateAbuse ( - url: string, - token: string, - abuseId: number, - body: AbuseUpdate, - statusCodeExpected = HttpStatusCode.NO_CONTENT_204 -) { - const path = '/api/v1/abuses/' + abuseId - - return makePutBodyRequest({ - url, - token, - path, - fields: body, - statusCodeExpected - }) -} - -function deleteAbuse (url: string, token: string, abuseId: number, statusCodeExpected = HttpStatusCode.NO_CONTENT_204) { - const path = '/api/v1/abuses/' + abuseId - - return makeDeleteRequest({ - url, - token, - path, - statusCodeExpected - }) -} - -function listAbuseMessages (url: string, token: string, abuseId: number, statusCodeExpected = HttpStatusCode.OK_200) { - const path = '/api/v1/abuses/' + abuseId + '/messages' - - return makeGetRequest({ - url, - token, - path, - statusCodeExpected - }) -} - -function deleteAbuseMessage ( - url: string, - token: string, - abuseId: number, - messageId: number, - statusCodeExpected = HttpStatusCode.NO_CONTENT_204 -) { - const path = '/api/v1/abuses/' + abuseId + '/messages/' + messageId - - return makeDeleteRequest({ - url, - token, - path, - statusCodeExpected - }) -} - -function addAbuseMessage (url: string, token: string, abuseId: number, message: string, statusCodeExpected = HttpStatusCode.OK_200) { - const path = '/api/v1/abuses/' + abuseId + '/messages' - - return makePostBodyRequest({ - url, - token, - path, - fields: { message }, - statusCodeExpected - }) -} - -// --------------------------------------------------------------------------- - -export { - reportAbuse, - getAdminAbusesList, - updateAbuse, - deleteAbuse, - getUserAbusesList, - listAbuseMessages, - deleteAbuseMessage, - addAbuseMessage -} diff --git a/shared/extra-utils/moderation/index.ts b/shared/extra-utils/moderation/index.ts new file mode 100644 index 000000000..b37643956 --- /dev/null +++ b/shared/extra-utils/moderation/index.ts @@ -0,0 +1 @@ +export * from './abuses-command' diff --git a/shared/extra-utils/overviews/index.ts b/shared/extra-utils/overviews/index.ts new file mode 100644 index 000000000..e19551907 --- /dev/null +++ b/shared/extra-utils/overviews/index.ts @@ -0,0 +1 @@ +export * from './overviews-command' diff --git a/shared/extra-utils/overviews/overviews-command.ts b/shared/extra-utils/overviews/overviews-command.ts new file mode 100644 index 000000000..06b4892d2 --- /dev/null +++ b/shared/extra-utils/overviews/overviews-command.ts @@ -0,0 +1,23 @@ +import { HttpStatusCode, VideosOverview } from '@shared/models' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export class OverviewsCommand extends AbstractCommand { + + getVideos (options: OverrideCommandOptions & { + page: number + }) { + const { page } = options + const path = '/api/v1/overviews/videos' + + const query = { page } + + return this.getRequestBody({ + ...options, + + path, + query, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } +} diff --git a/shared/extra-utils/overviews/overviews.ts b/shared/extra-utils/overviews/overviews.ts deleted file mode 100644 index 5e1a13e5e..000000000 --- a/shared/extra-utils/overviews/overviews.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { makeGetRequest } from '../requests/requests' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' - -function getVideosOverview (url: string, page: number, statusCodeExpected = HttpStatusCode.OK_200) { - const path = '/api/v1/overviews/videos' - - const query = { page } - - return makeGetRequest({ - url, - path, - query, - statusCodeExpected - }) -} - -function getVideosOverviewWithToken (url: string, page: number, token: string, statusCodeExpected = HttpStatusCode.OK_200) { - const path = '/api/v1/overviews/videos' - - const query = { page } - - return makeGetRequest({ - url, - path, - query, - token, - statusCodeExpected - }) -} - -export { - getVideosOverview, - getVideosOverviewWithToken -} diff --git a/shared/extra-utils/requests/activitypub.ts b/shared/extra-utils/requests/activitypub.ts index ecd8ce823..4ae878384 100644 --- a/shared/extra-utils/requests/activitypub.ts +++ b/shared/extra-utils/requests/activitypub.ts @@ -1,7 +1,7 @@ +import { activityPubContextify } from '../../../server/helpers/activitypub' import { doRequest } from '../../../server/helpers/requests' import { HTTP_SIGNATURE } from '../../../server/initializers/constants' import { buildGlobalHeaders } from '../../../server/lib/job-queue/handlers/utils/activitypub-http-utils' -import { activityPubContextify } from '../../../server/helpers/activitypub' function makePOSTAPRequest (url: string, body: any, httpSignature: any, headers: any) { const options = { diff --git a/shared/extra-utils/requests/check-api-params.ts b/shared/extra-utils/requests/check-api-params.ts index 7f5ff775c..26ba1e913 100644 --- a/shared/extra-utils/requests/check-api-params.ts +++ b/shared/extra-utils/requests/check-api-params.ts @@ -1,14 +1,13 @@ +import { HttpStatusCode } from '@shared/models' import { makeGetRequest } from './requests' -import { immutableAssign } from '../miscs/miscs' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' function checkBadStartPagination (url: string, path: string, token?: string, query = {}) { return makeGetRequest({ url, path, token, - query: immutableAssign(query, { start: 'hello' }), - statusCodeExpected: HttpStatusCode.BAD_REQUEST_400 + query: { ...query, start: 'hello' }, + expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) } @@ -17,16 +16,16 @@ async function checkBadCountPagination (url: string, path: string, token?: strin url, path, token, - query: immutableAssign(query, { count: 'hello' }), - statusCodeExpected: HttpStatusCode.BAD_REQUEST_400 + query: { ...query, count: 'hello' }, + expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) await makeGetRequest({ url, path, token, - query: immutableAssign(query, { count: 2000 }), - statusCodeExpected: HttpStatusCode.BAD_REQUEST_400 + query: { ...query, count: 2000 }, + expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) } @@ -35,8 +34,8 @@ function checkBadSortPagination (url: string, path: string, token?: string, quer url, path, token, - query: immutableAssign(query, { sort: 'hello' }), - statusCodeExpected: HttpStatusCode.BAD_REQUEST_400 + query: { ...query, sort: 'hello' }, + expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) } diff --git a/shared/extra-utils/requests/index.ts b/shared/extra-utils/requests/index.ts new file mode 100644 index 000000000..501163f92 --- /dev/null +++ b/shared/extra-utils/requests/index.ts @@ -0,0 +1,3 @@ +// Don't include activitypub that import stuff from server +export * from './check-api-params' +export * from './requests' diff --git a/shared/extra-utils/requests/requests.ts b/shared/extra-utils/requests/requests.ts index 38e24d897..70f790222 100644 --- a/shared/extra-utils/requests/requests.ts +++ b/shared/extra-utils/requests/requests.ts @@ -1,103 +1,82 @@ -/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */ +/* eslint-disable @typescript-eslint/no-floating-promises */ +import { decode } from 'querystring' import * as request from 'supertest' -import { buildAbsoluteFixturePath, root } from '../miscs/miscs' -import { isAbsolute, join } from 'path' import { URL } from 'url' -import { decode } from 'querystring' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' +import { HttpStatusCode } from '@shared/models' +import { buildAbsoluteFixturePath } from '../miscs/tests' -function get4KFileUrl () { - return 'https://download.cpy.re/peertube/4k_file.txt' +export type CommonRequestParams = { + url: string + path?: string + contentType?: string + range?: string + redirects?: number + accept?: string + host?: string + token?: string + headers?: { [ name: string ]: string } + type?: string + xForwardedFor?: string + expectedStatus?: HttpStatusCode } -function makeRawRequest (url: string, statusCodeExpected?: HttpStatusCode, range?: string) { +function makeRawRequest (url: string, expectedStatus?: HttpStatusCode, range?: string) { const { host, protocol, pathname } = new URL(url) - return makeGetRequest({ url: `${protocol}//${host}`, path: pathname, statusCodeExpected, range }) + return makeGetRequest({ url: `${protocol}//${host}`, path: pathname, expectedStatus, range }) } -function makeGetRequest (options: { - url: string - path?: string +function makeGetRequest (options: CommonRequestParams & { query?: any - token?: string - statusCodeExpected?: HttpStatusCode - contentType?: string - range?: string - redirects?: number - accept?: string }) { - if (!options.statusCodeExpected) options.statusCodeExpected = HttpStatusCode.BAD_REQUEST_400 - if (options.contentType === undefined) options.contentType = 'application/json' - const req = request(options.url).get(options.path) + .query(options.query) - if (options.contentType) req.set('Accept', options.contentType) - if (options.token) req.set('Authorization', 'Bearer ' + options.token) - if (options.query) req.query(options.query) - if (options.range) req.set('Range', options.range) - if (options.accept) req.set('Accept', options.accept) - if (options.redirects) req.redirects(options.redirects) - - return req.expect(options.statusCodeExpected) + return buildRequest(req, { contentType: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options }) } -function makeDeleteRequest (options: { - url: string - path: string - token?: string - statusCodeExpected?: HttpStatusCode -}) { - if (!options.statusCodeExpected) options.statusCodeExpected = HttpStatusCode.BAD_REQUEST_400 +function makeHTMLRequest (url: string, path: string) { + return makeGetRequest({ + url, + path, + accept: 'text/html', + expectedStatus: HttpStatusCode.OK_200 + }) +} - const req = request(options.url) - .delete(options.path) - .set('Accept', 'application/json') +function makeActivityPubGetRequest (url: string, path: string, expectedStatus = HttpStatusCode.OK_200) { + return makeGetRequest({ + url, + path, + expectedStatus: expectedStatus, + accept: 'application/activity+json,text/html;q=0.9,\\*/\\*;q=0.8' + }) +} - if (options.token) req.set('Authorization', 'Bearer ' + options.token) +function makeDeleteRequest (options: CommonRequestParams) { + const req = request(options.url).delete(options.path) - return req.expect(options.statusCodeExpected) + return buildRequest(req, { accept: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options }) } -function makeUploadRequest (options: { - url: string +function makeUploadRequest (options: CommonRequestParams & { method?: 'POST' | 'PUT' - path: string - token?: string + fields: { [ fieldName: string ]: any } attaches?: { [ attachName: string ]: any | any[] } - statusCodeExpected?: HttpStatusCode }) { - if (!options.statusCodeExpected) options.statusCodeExpected = HttpStatusCode.BAD_REQUEST_400 - - let req: request.Test - if (options.method === 'PUT') { - req = request(options.url).put(options.path) - } else { - req = request(options.url).post(options.path) - } - - req.set('Accept', 'application/json') + let req = options.method === 'PUT' + ? request(options.url).put(options.path) + : request(options.url).post(options.path) - if (options.token) req.set('Authorization', 'Bearer ' + options.token) + req = buildRequest(req, { accept: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options }) - Object.keys(options.fields).forEach(field => { - const value = options.fields[field] - - if (value === undefined) return - - if (Array.isArray(value)) { - for (let i = 0; i < value.length; i++) { - req.field(field + '[' + i + ']', value[i]) - } - } else { - req.field(field, value) - } - }) + buildFields(req, options.fields) Object.keys(options.attaches || {}).forEach(attach => { const value = options.attaches[attach] + if (Array.isArray(value)) { req.attach(attach, buildAbsoluteFixturePath(value[0]), value[1]) } else { @@ -105,27 +84,16 @@ function makeUploadRequest (options: { } }) - return req.expect(options.statusCodeExpected) + return req } -function makePostBodyRequest (options: { - url: string - path: string - token?: string +function makePostBodyRequest (options: CommonRequestParams & { fields?: { [ fieldName: string ]: any } - statusCodeExpected?: HttpStatusCode }) { - if (!options.fields) options.fields = {} - if (!options.statusCodeExpected) options.statusCodeExpected = HttpStatusCode.BAD_REQUEST_400 - - const req = request(options.url) - .post(options.path) - .set('Accept', 'application/json') + const req = request(options.url).post(options.path) + .send(options.fields) - if (options.token) req.set('Authorization', 'Bearer ' + options.token) - - return req.send(options.fields) - .expect(options.statusCodeExpected) + return buildRequest(req, { accept: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options }) } function makePutBodyRequest (options: { @@ -133,59 +101,29 @@ function makePutBodyRequest (options: { path: string token?: string fields: { [ fieldName: string ]: any } - statusCodeExpected?: HttpStatusCode + expectedStatus?: HttpStatusCode }) { - if (!options.statusCodeExpected) options.statusCodeExpected = HttpStatusCode.BAD_REQUEST_400 - - const req = request(options.url) - .put(options.path) - .set('Accept', 'application/json') + const req = request(options.url).put(options.path) + .send(options.fields) - if (options.token) req.set('Authorization', 'Bearer ' + options.token) - - return req.send(options.fields) - .expect(options.statusCodeExpected) + return buildRequest(req, { accept: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options }) } -function makeHTMLRequest (url: string, path: string) { - return request(url) - .get(path) - .set('Accept', 'text/html') - .expect(HttpStatusCode.OK_200) +function decodeQueryString (path: string) { + return decode(path.split('?')[1]) } -function updateImageRequest (options: { - url: string - path: string - accessToken: string - fixture: string - fieldname: string -}) { - let filePath = '' - if (isAbsolute(options.fixture)) { - filePath = options.fixture - } else { - filePath = join(root(), 'server', 'tests', 'fixtures', options.fixture) - } - - return makeUploadRequest({ - url: options.url, - path: options.path, - token: options.accessToken, - fields: {}, - attaches: { [options.fieldname]: filePath }, - statusCodeExpected: HttpStatusCode.OK_200 - }) +function unwrapBody (test: request.Test): Promise { + return test.then(res => res.body) } -function decodeQueryString (path: string) { - return decode(path.split('?')[1]) +function unwrapText (test: request.Test): Promise { + return test.then(res => res.text) } // --------------------------------------------------------------------------- export { - get4KFileUrl, makeHTMLRequest, makeGetRequest, decodeQueryString, @@ -194,5 +132,51 @@ export { makePutBodyRequest, makeDeleteRequest, makeRawRequest, - updateImageRequest + makeActivityPubGetRequest, + unwrapBody, + unwrapText +} + +// --------------------------------------------------------------------------- + +function buildRequest (req: request.Test, options: CommonRequestParams) { + if (options.contentType) req.set('Accept', options.contentType) + if (options.token) req.set('Authorization', 'Bearer ' + options.token) + if (options.range) req.set('Range', options.range) + if (options.accept) req.set('Accept', options.accept) + if (options.host) req.set('Host', options.host) + if (options.redirects) req.redirects(options.redirects) + if (options.expectedStatus) req.expect(options.expectedStatus) + if (options.xForwardedFor) req.set('X-Forwarded-For', options.xForwardedFor) + if (options.type) req.type(options.type) + + Object.keys(options.headers || {}).forEach(name => { + req.set(name, options.headers[name]) + }) + + return req +} + +function buildFields (req: request.Test, fields: { [ fieldName: string ]: any }, namespace?: string) { + if (!fields) return + + let formKey: string + + for (const key of Object.keys(fields)) { + if (namespace) formKey = `${namespace}[${key}]` + else formKey = key + + if (fields[key] === undefined) continue + + if (Array.isArray(fields[key]) && fields[key].length === 0) { + req.field(key, null) + continue + } + + if (fields[key] !== null && typeof fields[key] === 'object') { + buildFields(req, fields[key], formKey) + } else { + req.field(formKey, fields[key]) + } + } } diff --git a/shared/extra-utils/search/index.ts b/shared/extra-utils/search/index.ts new file mode 100644 index 000000000..48dbe8ae9 --- /dev/null +++ b/shared/extra-utils/search/index.ts @@ -0,0 +1 @@ +export * from './search-command' diff --git a/shared/extra-utils/search/search-command.ts b/shared/extra-utils/search/search-command.ts new file mode 100644 index 000000000..0fbbcd6ef --- /dev/null +++ b/shared/extra-utils/search/search-command.ts @@ -0,0 +1,98 @@ +import { + HttpStatusCode, + ResultList, + Video, + VideoChannel, + VideoChannelsSearchQuery, + VideoPlaylist, + VideoPlaylistsSearchQuery, + VideosSearchQuery +} from '@shared/models' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export class SearchCommand extends AbstractCommand { + + searchChannels (options: OverrideCommandOptions & { + search: string + }) { + return this.advancedChannelSearch({ + ...options, + + search: { search: options.search } + }) + } + + advancedChannelSearch (options: OverrideCommandOptions & { + search: VideoChannelsSearchQuery + }) { + const { search } = options + const path = '/api/v1/search/video-channels' + + return this.getRequestBody>({ + ...options, + + path, + query: search, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + searchPlaylists (options: OverrideCommandOptions & { + search: string + }) { + return this.advancedPlaylistSearch({ + ...options, + + search: { search: options.search } + }) + } + + advancedPlaylistSearch (options: OverrideCommandOptions & { + search: VideoPlaylistsSearchQuery + }) { + const { search } = options + const path = '/api/v1/search/video-playlists' + + return this.getRequestBody>({ + ...options, + + path, + query: search, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + searchVideos (options: OverrideCommandOptions & { + search: string + sort?: string + }) { + const { search, sort } = options + + return this.advancedVideoSearch({ + ...options, + + search: { + search: search, + sort: sort ?? '-publishedAt' + } + }) + } + + advancedVideoSearch (options: OverrideCommandOptions & { + search: VideosSearchQuery + }) { + const { search } = options + const path = '/api/v1/search/videos' + + return this.getRequestBody>({ + ...options, + + path, + query: search, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } +} diff --git a/shared/extra-utils/search/video-channels.ts b/shared/extra-utils/search/video-channels.ts deleted file mode 100644 index 8e0f42578..000000000 --- a/shared/extra-utils/search/video-channels.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { VideoChannelsSearchQuery } from '@shared/models' -import { makeGetRequest } from '../requests/requests' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' - -function searchVideoChannel (url: string, search: string, token?: string, statusCodeExpected = HttpStatusCode.OK_200) { - const path = '/api/v1/search/video-channels' - - return makeGetRequest({ - url, - path, - query: { - sort: '-createdAt', - search - }, - token, - statusCodeExpected - }) -} - -function advancedVideoChannelSearch (url: string, search: VideoChannelsSearchQuery) { - const path = '/api/v1/search/video-channels' - - return makeGetRequest({ - url, - path, - query: search, - statusCodeExpected: HttpStatusCode.OK_200 - }) -} - -// --------------------------------------------------------------------------- - -export { - searchVideoChannel, - advancedVideoChannelSearch -} diff --git a/shared/extra-utils/search/video-playlists.ts b/shared/extra-utils/search/video-playlists.ts deleted file mode 100644 index c22831df7..000000000 --- a/shared/extra-utils/search/video-playlists.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { VideoPlaylistsSearchQuery } from '@shared/models' -import { HttpStatusCode } from '../../core-utils/miscs/http-error-codes' -import { makeGetRequest } from '../requests/requests' - -function searchVideoPlaylists (url: string, search: string, token?: string, statusCodeExpected = HttpStatusCode.OK_200) { - const path = '/api/v1/search/video-playlists' - - return makeGetRequest({ - url, - path, - query: { - sort: '-createdAt', - search - }, - token, - statusCodeExpected - }) -} - -function advancedVideoPlaylistSearch (url: string, search: VideoPlaylistsSearchQuery) { - const path = '/api/v1/search/video-playlists' - - return makeGetRequest({ - url, - path, - query: search, - statusCodeExpected: HttpStatusCode.OK_200 - }) -} - -// --------------------------------------------------------------------------- - -export { - searchVideoPlaylists, - advancedVideoPlaylistSearch -} diff --git a/shared/extra-utils/search/videos.ts b/shared/extra-utils/search/videos.ts deleted file mode 100644 index db6edbd58..000000000 --- a/shared/extra-utils/search/videos.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ - -import * as request from 'supertest' -import { VideosSearchQuery } from '../../models/search' -import { immutableAssign } from '../miscs/miscs' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' - -function searchVideo (url: string, search: string, sort = '-publishedAt') { - const path = '/api/v1/search/videos' - - const query = { sort, search: search } - const req = request(url) - .get(path) - .query(query) - .set('Accept', 'application/json') - - return req.expect(HttpStatusCode.OK_200) - .expect('Content-Type', /json/) -} - -function searchVideoWithToken (url: string, search: string, token: string, query: { nsfw?: boolean } = {}) { - const path = '/api/v1/search/videos' - const req = request(url) - .get(path) - .set('Authorization', 'Bearer ' + token) - .query(immutableAssign(query, { sort: '-publishedAt', search })) - .set('Accept', 'application/json') - - return req.expect(HttpStatusCode.OK_200) - .expect('Content-Type', /json/) -} - -function searchVideoWithSort (url: string, search: string, sort: string) { - const path = '/api/v1/search/videos' - - const query = { search, sort } - - return request(url) - .get(path) - .query(query) - .set('Accept', 'application/json') - .expect(HttpStatusCode.OK_200) - .expect('Content-Type', /json/) -} - -function advancedVideosSearch (url: string, options: VideosSearchQuery) { - const path = '/api/v1/search/videos' - - return request(url) - .get(path) - .query(options) - .set('Accept', 'application/json') - .expect(HttpStatusCode.OK_200) - .expect('Content-Type', /json/) -} - -// --------------------------------------------------------------------------- - -export { - searchVideo, - advancedVideosSearch, - searchVideoWithToken, - searchVideoWithSort -} diff --git a/shared/extra-utils/server/activitypub.ts b/shared/extra-utils/server/activitypub.ts deleted file mode 100644 index cf967ed7d..000000000 --- a/shared/extra-utils/server/activitypub.ts +++ /dev/null @@ -1,15 +0,0 @@ -import * as request from 'supertest' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' - -function makeActivityPubGetRequest (url: string, path: string, expectedStatus = HttpStatusCode.OK_200) { - return request(url) - .get(path) - .set('Accept', 'application/activity+json,text/html;q=0.9,\\*/\\*;q=0.8') - .expect(expectedStatus) -} - -// --------------------------------------------------------------------------- - -export { - makeActivityPubGetRequest -} diff --git a/shared/extra-utils/server/clients.ts b/shared/extra-utils/server/clients.ts deleted file mode 100644 index 894fe4911..000000000 --- a/shared/extra-utils/server/clients.ts +++ /dev/null @@ -1,20 +0,0 @@ -import * as request from 'supertest' -import { URL } from 'url' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' - -function getClient (url: string) { - const path = '/api/v1/oauth-clients/local' - - return request(url) - .get(path) - .set('Host', new URL(url).host) - .set('Accept', 'application/json') - .expect(HttpStatusCode.OK_200) - .expect('Content-Type', /json/) -} - -// --------------------------------------------------------------------------- - -export { - getClient -} diff --git a/shared/extra-utils/server/config-command.ts b/shared/extra-utils/server/config-command.ts new file mode 100644 index 000000000..11148aa46 --- /dev/null +++ b/shared/extra-utils/server/config-command.ts @@ -0,0 +1,263 @@ +import { merge } from 'lodash' +import { DeepPartial } from '@shared/core-utils' +import { About, HttpStatusCode, ServerConfig } from '@shared/models' +import { CustomConfig } from '../../models/server/custom-config.model' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export class ConfigCommand extends AbstractCommand { + + static getCustomConfigResolutions (enabled: boolean) { + return { + '240p': enabled, + '360p': enabled, + '480p': enabled, + '720p': enabled, + '1080p': enabled, + '1440p': enabled, + '2160p': enabled + } + } + + getConfig (options: OverrideCommandOptions = {}) { + const path = '/api/v1/config' + + return this.getRequestBody({ + ...options, + + path, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + getAbout (options: OverrideCommandOptions = {}) { + const path = '/api/v1/config/about' + + return this.getRequestBody({ + ...options, + + path, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + getCustomConfig (options: OverrideCommandOptions = {}) { + const path = '/api/v1/config/custom' + + return this.getRequestBody({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + updateCustomConfig (options: OverrideCommandOptions & { + newCustomConfig: CustomConfig + }) { + const path = '/api/v1/config/custom' + + return this.putBodyRequest({ + ...options, + + path, + fields: options.newCustomConfig, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + deleteCustomConfig (options: OverrideCommandOptions = {}) { + const path = '/api/v1/config/custom' + + return this.deleteRequest({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + updateCustomSubConfig (options: OverrideCommandOptions & { + newConfig: DeepPartial + }) { + const newCustomConfig: CustomConfig = { + instance: { + name: 'PeerTube updated', + shortDescription: 'my short description', + description: 'my super description', + terms: 'my super terms', + codeOfConduct: 'my super coc', + + creationReason: 'my super creation reason', + moderationInformation: 'my super moderation information', + administrator: 'Kuja', + maintenanceLifetime: 'forever', + businessModel: 'my super business model', + hardwareInformation: '2vCore 3GB RAM', + + languages: [ 'en', 'es' ], + categories: [ 1, 2 ], + + isNSFW: true, + defaultNSFWPolicy: 'blur', + + defaultClientRoute: '/videos/recently-added', + + customizations: { + javascript: 'alert("coucou")', + css: 'body { background-color: red; }' + } + }, + theme: { + default: 'default' + }, + services: { + twitter: { + username: '@MySuperUsername', + whitelisted: true + } + }, + cache: { + previews: { + size: 2 + }, + captions: { + size: 3 + }, + torrents: { + size: 4 + } + }, + signup: { + enabled: false, + limit: 5, + requiresEmailVerification: false, + minimumAge: 16 + }, + admin: { + email: 'superadmin1@example.com' + }, + contactForm: { + enabled: true + }, + user: { + videoQuota: 5242881, + videoQuotaDaily: 318742 + }, + transcoding: { + enabled: true, + allowAdditionalExtensions: true, + allowAudioFiles: true, + threads: 1, + concurrency: 3, + profile: 'default', + resolutions: { + '0p': false, + '240p': false, + '360p': true, + '480p': true, + '720p': false, + '1080p': false, + '1440p': false, + '2160p': false + }, + webtorrent: { + enabled: true + }, + hls: { + enabled: false + } + }, + live: { + enabled: true, + allowReplay: false, + maxDuration: -1, + maxInstanceLives: -1, + maxUserLives: 50, + transcoding: { + enabled: true, + threads: 4, + profile: 'default', + resolutions: { + '240p': true, + '360p': true, + '480p': true, + '720p': true, + '1080p': true, + '1440p': true, + '2160p': true + } + } + }, + import: { + videos: { + concurrency: 3, + http: { + enabled: false + }, + torrent: { + enabled: false + } + } + }, + trending: { + videos: { + algorithms: { + enabled: [ 'best', 'hot', 'most-viewed', 'most-liked' ], + default: 'hot' + } + } + }, + autoBlacklist: { + videos: { + ofUsers: { + enabled: false + } + } + }, + followers: { + instance: { + enabled: true, + manualApproval: false + } + }, + followings: { + instance: { + autoFollowBack: { + enabled: false + }, + autoFollowIndex: { + indexUrl: 'https://instances.joinpeertube.org/api/v1/instances/hosts', + enabled: false + } + } + }, + broadcastMessage: { + enabled: true, + level: 'warning', + message: 'hello', + dismissable: true + }, + search: { + remoteUri: { + users: true, + anonymous: true + }, + searchIndex: { + enabled: true, + url: 'https://search.joinpeertube.org', + disableLocalSearch: true, + isDefaultSearch: true + } + } + } + + merge(newCustomConfig, options.newConfig) + + return this.updateCustomConfig({ ...options, newCustomConfig }) + } +} diff --git a/shared/extra-utils/server/config.ts b/shared/extra-utils/server/config.ts deleted file mode 100644 index 9fcfb31fd..000000000 --- a/shared/extra-utils/server/config.ts +++ /dev/null @@ -1,260 +0,0 @@ -import { makeDeleteRequest, makeGetRequest, makePutBodyRequest } from '../requests/requests' -import { CustomConfig } from '../../models/server/custom-config.model' -import { DeepPartial, HttpStatusCode } from '@shared/core-utils' -import { merge } from 'lodash' - -function getConfig (url: string) { - const path = '/api/v1/config' - - return makeGetRequest({ - url, - path, - statusCodeExpected: HttpStatusCode.OK_200 - }) -} - -function getAbout (url: string) { - const path = '/api/v1/config/about' - - return makeGetRequest({ - url, - path, - statusCodeExpected: HttpStatusCode.OK_200 - }) -} - -function getCustomConfig (url: string, token: string, statusCodeExpected = HttpStatusCode.OK_200) { - const path = '/api/v1/config/custom' - - return makeGetRequest({ - url, - token, - path, - statusCodeExpected - }) -} - -function updateCustomConfig (url: string, token: string, newCustomConfig: CustomConfig, statusCodeExpected = HttpStatusCode.OK_200) { - const path = '/api/v1/config/custom' - - return makePutBodyRequest({ - url, - token, - path, - fields: newCustomConfig, - statusCodeExpected - }) -} - -function updateCustomSubConfig (url: string, token: string, newConfig: DeepPartial) { - const updateParams: CustomConfig = { - instance: { - name: 'PeerTube updated', - shortDescription: 'my short description', - description: 'my super description', - terms: 'my super terms', - codeOfConduct: 'my super coc', - - creationReason: 'my super creation reason', - moderationInformation: 'my super moderation information', - administrator: 'Kuja', - maintenanceLifetime: 'forever', - businessModel: 'my super business model', - hardwareInformation: '2vCore 3GB RAM', - - languages: [ 'en', 'es' ], - categories: [ 1, 2 ], - - isNSFW: true, - defaultNSFWPolicy: 'blur', - - defaultClientRoute: '/videos/recently-added', - - customizations: { - javascript: 'alert("coucou")', - css: 'body { background-color: red; }' - } - }, - theme: { - default: 'default' - }, - services: { - twitter: { - username: '@MySuperUsername', - whitelisted: true - } - }, - cache: { - previews: { - size: 2 - }, - captions: { - size: 3 - }, - torrents: { - size: 4 - } - }, - signup: { - enabled: false, - limit: 5, - requiresEmailVerification: false, - minimumAge: 16 - }, - admin: { - email: 'superadmin1@example.com' - }, - contactForm: { - enabled: true - }, - user: { - videoQuota: 5242881, - videoQuotaDaily: 318742 - }, - transcoding: { - enabled: true, - allowAdditionalExtensions: true, - allowAudioFiles: true, - threads: 1, - concurrency: 3, - profile: 'default', - resolutions: { - '0p': false, - '240p': false, - '360p': true, - '480p': true, - '720p': false, - '1080p': false, - '1440p': false, - '2160p': false - }, - webtorrent: { - enabled: true - }, - hls: { - enabled: false - } - }, - live: { - enabled: true, - allowReplay: false, - maxDuration: -1, - maxInstanceLives: -1, - maxUserLives: 50, - transcoding: { - enabled: true, - threads: 4, - profile: 'default', - resolutions: { - '240p': true, - '360p': true, - '480p': true, - '720p': true, - '1080p': true, - '1440p': true, - '2160p': true - } - } - }, - import: { - videos: { - concurrency: 3, - http: { - enabled: false - }, - torrent: { - enabled: false - } - } - }, - trending: { - videos: { - algorithms: { - enabled: [ 'best', 'hot', 'most-viewed', 'most-liked' ], - default: 'hot' - } - } - }, - autoBlacklist: { - videos: { - ofUsers: { - enabled: false - } - } - }, - followers: { - instance: { - enabled: true, - manualApproval: false - } - }, - followings: { - instance: { - autoFollowBack: { - enabled: false - }, - autoFollowIndex: { - indexUrl: 'https://instances.joinpeertube.org/api/v1/instances/hosts', - enabled: false - } - } - }, - broadcastMessage: { - enabled: true, - level: 'warning', - message: 'hello', - dismissable: true - }, - search: { - remoteUri: { - users: true, - anonymous: true - }, - searchIndex: { - enabled: true, - url: 'https://search.joinpeertube.org', - disableLocalSearch: true, - isDefaultSearch: true - } - } - } - - merge(updateParams, newConfig) - - return updateCustomConfig(url, token, updateParams) -} - -function getCustomConfigResolutions (enabled: boolean) { - return { - '240p': enabled, - '360p': enabled, - '480p': enabled, - '720p': enabled, - '1080p': enabled, - '1440p': enabled, - '2160p': enabled - } -} - -function deleteCustomConfig (url: string, token: string, statusCodeExpected = HttpStatusCode.OK_200) { - const path = '/api/v1/config/custom' - - return makeDeleteRequest({ - url, - token, - path, - statusCodeExpected - }) -} - -// --------------------------------------------------------------------------- - -export { - getConfig, - getCustomConfig, - updateCustomConfig, - getAbout, - deleteCustomConfig, - updateCustomSubConfig, - getCustomConfigResolutions -} diff --git a/shared/extra-utils/server/contact-form-command.ts b/shared/extra-utils/server/contact-form-command.ts new file mode 100644 index 000000000..0e8fd6d84 --- /dev/null +++ b/shared/extra-utils/server/contact-form-command.ts @@ -0,0 +1,31 @@ +import { HttpStatusCode } from '@shared/models' +import { ContactForm } from '../../models/server' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export class ContactFormCommand extends AbstractCommand { + + send (options: OverrideCommandOptions & { + fromEmail: string + fromName: string + subject: string + body: string + }) { + const path = '/api/v1/server/contact' + + const body: ContactForm = { + fromEmail: options.fromEmail, + fromName: options.fromName, + subject: options.subject, + body: options.body + } + + return this.postBodyRequest({ + ...options, + + path, + fields: body, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } +} diff --git a/shared/extra-utils/server/contact-form.ts b/shared/extra-utils/server/contact-form.ts deleted file mode 100644 index 6c9232cc6..000000000 --- a/shared/extra-utils/server/contact-form.ts +++ /dev/null @@ -1,31 +0,0 @@ -import * as request from 'supertest' -import { ContactForm } from '../../models/server' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' - -function sendContactForm (options: { - url: string - fromEmail: string - fromName: string - subject: string - body: string - expectedStatus?: number -}) { - const path = '/api/v1/server/contact' - - const body: ContactForm = { - fromEmail: options.fromEmail, - fromName: options.fromName, - subject: options.subject, - body: options.body - } - return request(options.url) - .post(path) - .send(body) - .expect(options.expectedStatus || HttpStatusCode.NO_CONTENT_204) -} - -// --------------------------------------------------------------------------- - -export { - sendContactForm -} diff --git a/shared/extra-utils/server/debug-command.ts b/shared/extra-utils/server/debug-command.ts new file mode 100644 index 000000000..3c5a785bb --- /dev/null +++ b/shared/extra-utils/server/debug-command.ts @@ -0,0 +1,33 @@ +import { Debug, HttpStatusCode, SendDebugCommand } from '@shared/models' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export class DebugCommand extends AbstractCommand { + + getDebug (options: OverrideCommandOptions = {}) { + const path = '/api/v1/server/debug' + + return this.getRequestBody({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + sendCommand (options: OverrideCommandOptions & { + body: SendDebugCommand + }) { + const { body } = options + const path = '/api/v1/server/debug/run-command' + + return this.postBodyRequest({ + ...options, + + path, + fields: body, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } +} diff --git a/shared/extra-utils/server/debug.ts b/shared/extra-utils/server/debug.ts deleted file mode 100644 index f196812b7..000000000 --- a/shared/extra-utils/server/debug.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { makeGetRequest, makePostBodyRequest } from '../requests/requests' -import { HttpStatusCode } from '../../core-utils/miscs/http-error-codes' -import { SendDebugCommand } from '@shared/models' - -function getDebug (url: string, token: string) { - const path = '/api/v1/server/debug' - - return makeGetRequest({ - url, - path, - token, - statusCodeExpected: HttpStatusCode.OK_200 - }) -} - -function sendDebugCommand (url: string, token: string, body: SendDebugCommand) { - const path = '/api/v1/server/debug/run-command' - - return makePostBodyRequest({ - url, - path, - token, - fields: body, - statusCodeExpected: HttpStatusCode.NO_CONTENT_204 - }) -} - -// --------------------------------------------------------------------------- - -export { - getDebug, - sendDebugCommand -} diff --git a/shared/extra-utils/server/directories.ts b/shared/extra-utils/server/directories.ts new file mode 100644 index 000000000..b6465cbf4 --- /dev/null +++ b/shared/extra-utils/server/directories.ts @@ -0,0 +1,34 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ + +import { expect } from 'chai' +import { pathExists, readdir } from 'fs-extra' +import { join } from 'path' +import { root } from '@server/helpers/core-utils' +import { PeerTubeServer } from './server' + +async function checkTmpIsEmpty (server: PeerTubeServer) { + await checkDirectoryIsEmpty(server, 'tmp', [ 'plugins-global.css', 'hls', 'resumable-uploads' ]) + + if (await pathExists(join('test' + server.internalServerNumber, 'tmp', 'hls'))) { + await checkDirectoryIsEmpty(server, 'tmp/hls') + } +} + +async function checkDirectoryIsEmpty (server: PeerTubeServer, directory: string, exceptions: string[] = []) { + const testDirectory = 'test' + server.internalServerNumber + + const directoryPath = join(root(), testDirectory, directory) + + const directoryExists = await pathExists(directoryPath) + expect(directoryExists).to.be.true + + const files = await readdir(directoryPath) + const filtered = files.filter(f => exceptions.includes(f) === false) + + expect(filtered).to.have.lengthOf(0) +} + +export { + checkTmpIsEmpty, + checkDirectoryIsEmpty +} diff --git a/shared/extra-utils/server/follows-command.ts b/shared/extra-utils/server/follows-command.ts new file mode 100644 index 000000000..01ef6f179 --- /dev/null +++ b/shared/extra-utils/server/follows-command.ts @@ -0,0 +1,139 @@ +import { pick } from '@shared/core-utils' +import { ActivityPubActorType, ActorFollow, FollowState, HttpStatusCode, ResultList, ServerFollowCreate } from '@shared/models' +import { AbstractCommand, OverrideCommandOptions } from '../shared' +import { PeerTubeServer } from './server' + +export class FollowsCommand extends AbstractCommand { + + getFollowers (options: OverrideCommandOptions & { + start: number + count: number + sort: string + search?: string + actorType?: ActivityPubActorType + state?: FollowState + }) { + const path = '/api/v1/server/followers' + + const query = pick(options, [ 'start', 'count', 'sort', 'search', 'state', 'actorType' ]) + + return this.getRequestBody>({ + ...options, + + path, + query, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + getFollowings (options: OverrideCommandOptions & { + start?: number + count?: number + sort?: string + search?: string + actorType?: ActivityPubActorType + state?: FollowState + } = {}) { + const path = '/api/v1/server/following' + + const query = pick(options, [ 'start', 'count', 'sort', 'search', 'state', 'actorType' ]) + + return this.getRequestBody>({ + ...options, + + path, + query, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + follow (options: OverrideCommandOptions & { + hosts?: string[] + handles?: string[] + }) { + const path = '/api/v1/server/following' + + const fields: ServerFollowCreate = {} + + if (options.hosts) { + fields.hosts = options.hosts.map(f => f.replace(/^http:\/\//, '')) + } + + if (options.handles) { + fields.handles = options.handles + } + + return this.postBodyRequest({ + ...options, + + path, + fields, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + async unfollow (options: OverrideCommandOptions & { + target: PeerTubeServer | string + }) { + const { target } = options + + const handle = typeof target === 'string' + ? target + : target.host + + const path = '/api/v1/server/following/' + handle + + return this.deleteRequest({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + acceptFollower (options: OverrideCommandOptions & { + follower: string + }) { + const path = '/api/v1/server/followers/' + options.follower + '/accept' + + return this.postBodyRequest({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + rejectFollower (options: OverrideCommandOptions & { + follower: string + }) { + const path = '/api/v1/server/followers/' + options.follower + '/reject' + + return this.postBodyRequest({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + removeFollower (options: OverrideCommandOptions & { + follower: PeerTubeServer + }) { + const path = '/api/v1/server/followers/peertube@' + options.follower.host + + return this.deleteRequest({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } +} diff --git a/shared/extra-utils/server/follows.ts b/shared/extra-utils/server/follows.ts index 6aae4a31d..698238f29 100644 --- a/shared/extra-utils/server/follows.ts +++ b/shared/extra-utils/server/follows.ts @@ -1,126 +1,10 @@ -import * as request from 'supertest' -import { ServerInfo } from './servers' import { waitJobs } from './jobs' -import { makePostBodyRequest } from '../requests/requests' -import { ActivityPubActorType, FollowState } from '@shared/models' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' +import { PeerTubeServer } from './server' -function getFollowersListPaginationAndSort (options: { - url: string - start: number - count: number - sort: string - search?: string - actorType?: ActivityPubActorType - state?: FollowState -}) { - const { url, start, count, sort, search, state, actorType } = options - const path = '/api/v1/server/followers' - - const query = { - start, - count, - sort, - search, - state, - actorType - } - - return request(url) - .get(path) - .query(query) - .set('Accept', 'application/json') - .expect(HttpStatusCode.OK_200) - .expect('Content-Type', /json/) -} - -function acceptFollower (url: string, token: string, follower: string, statusCodeExpected = HttpStatusCode.NO_CONTENT_204) { - const path = '/api/v1/server/followers/' + follower + '/accept' - - return makePostBodyRequest({ - url, - token, - path, - statusCodeExpected - }) -} - -function rejectFollower (url: string, token: string, follower: string, statusCodeExpected = HttpStatusCode.NO_CONTENT_204) { - const path = '/api/v1/server/followers/' + follower + '/reject' - - return makePostBodyRequest({ - url, - token, - path, - statusCodeExpected - }) -} - -function getFollowingListPaginationAndSort (options: { - url: string - start: number - count: number - sort: string - search?: string - actorType?: ActivityPubActorType - state?: FollowState -}) { - const { url, start, count, sort, search, state, actorType } = options - const path = '/api/v1/server/following' - - const query = { - start, - count, - sort, - search, - state, - actorType - } - - return request(url) - .get(path) - .query(query) - .set('Accept', 'application/json') - .expect(HttpStatusCode.OK_200) - .expect('Content-Type', /json/) -} - -function follow (follower: string, following: string[], accessToken: string, expectedStatus = HttpStatusCode.NO_CONTENT_204) { - const path = '/api/v1/server/following' - - const followingHosts = following.map(f => f.replace(/^http:\/\//, '')) - return request(follower) - .post(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .send({ hosts: followingHosts }) - .expect(expectedStatus) -} - -async function unfollow (url: string, accessToken: string, target: ServerInfo, expectedStatus = HttpStatusCode.NO_CONTENT_204) { - const path = '/api/v1/server/following/' + target.host - - return request(url) - .delete(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(expectedStatus) -} - -function removeFollower (url: string, accessToken: string, follower: ServerInfo, expectedStatus = HttpStatusCode.NO_CONTENT_204) { - const path = '/api/v1/server/followers/peertube@' + follower.host - - return request(url) - .delete(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(expectedStatus) -} - -async function doubleFollow (server1: ServerInfo, server2: ServerInfo) { +async function doubleFollow (server1: PeerTubeServer, server2: PeerTubeServer) { await Promise.all([ - follow(server1.url, [ server2.url ], server1.accessToken), - follow(server2.url, [ server1.url ], server2.accessToken) + server1.follows.follow({ hosts: [ server2.url ] }), + server2.follows.follow({ hosts: [ server1.url ] }) ]) // Wait request propagation @@ -132,12 +16,5 @@ async function doubleFollow (server1: ServerInfo, server2: ServerInfo) { // --------------------------------------------------------------------------- export { - getFollowersListPaginationAndSort, - getFollowingListPaginationAndSort, - unfollow, - removeFollower, - follow, - doubleFollow, - acceptFollower, - rejectFollower + doubleFollow } diff --git a/shared/extra-utils/server/index.ts b/shared/extra-utils/server/index.ts new file mode 100644 index 000000000..9055dfc57 --- /dev/null +++ b/shared/extra-utils/server/index.ts @@ -0,0 +1,15 @@ +export * from './config-command' +export * from './contact-form-command' +export * from './debug-command' +export * from './directories' +export * from './follows-command' +export * from './follows' +export * from './jobs' +export * from './jobs-command' +export * from './plugins-command' +export * from './plugins' +export * from './redundancy-command' +export * from './server' +export * from './servers-command' +export * from './servers' +export * from './stats-command' diff --git a/shared/extra-utils/server/jobs-command.ts b/shared/extra-utils/server/jobs-command.ts new file mode 100644 index 000000000..c4eb12dc2 --- /dev/null +++ b/shared/extra-utils/server/jobs-command.ts @@ -0,0 +1,36 @@ +import { pick } from '@shared/core-utils' +import { HttpStatusCode } from '@shared/models' +import { Job, JobState, JobType, ResultList } from '../../models' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export class JobsCommand extends AbstractCommand { + + getJobsList (options: OverrideCommandOptions & { + state?: JobState + jobType?: JobType + start?: number + count?: number + sort?: string + } = {}) { + const path = this.buildJobsUrl(options.state) + + const query = pick(options, [ 'start', 'count', 'sort', 'jobType' ]) + + return this.getRequestBody>({ + ...options, + + path, + query, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + private buildJobsUrl (state?: JobState) { + let path = '/api/v1/jobs' + + if (state) path += '/' + state + + return path + } +} diff --git a/shared/extra-utils/server/jobs.ts b/shared/extra-utils/server/jobs.ts index 763374e03..64a0353eb 100644 --- a/shared/extra-utils/server/jobs.ts +++ b/shared/extra-utils/server/jobs.ts @@ -1,66 +1,17 @@ -import * as request from 'supertest' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' -import { getDebug, makeGetRequest } from '../../../shared/extra-utils' -import { Job, JobState, JobType, ServerDebug } from '../../models' -import { wait } from '../miscs/miscs' -import { ServerInfo } from './servers' -function buildJobsUrl (state?: JobState) { - let path = '/api/v1/jobs' +import { JobState } from '../../models' +import { wait } from '../miscs' +import { PeerTubeServer } from './server' - if (state) path += '/' + state - - return path -} - -function getJobsList (url: string, accessToken: string, state?: JobState) { - const path = buildJobsUrl(state) - - return request(url) - .get(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(HttpStatusCode.OK_200) - .expect('Content-Type', /json/) -} - -function getJobsListPaginationAndSort (options: { - url: string - accessToken: string - start: number - count: number - sort: string - state?: JobState - jobType?: JobType -}) { - const { url, accessToken, state, start, count, sort, jobType } = options - const path = buildJobsUrl(state) - - const query = { - start, - count, - sort, - jobType - } - - return makeGetRequest({ - url, - path, - token: accessToken, - statusCodeExpected: HttpStatusCode.OK_200, - query - }) -} - -async function waitJobs (serversArg: ServerInfo[] | ServerInfo) { +async function waitJobs (serversArg: PeerTubeServer[] | PeerTubeServer) { const pendingJobWait = process.env.NODE_PENDING_JOB_WAIT ? parseInt(process.env.NODE_PENDING_JOB_WAIT, 10) : 250 - let servers: ServerInfo[] + let servers: PeerTubeServer[] - if (Array.isArray(serversArg) === false) servers = [ serversArg as ServerInfo ] - else servers = serversArg as ServerInfo[] + if (Array.isArray(serversArg) === false) servers = [ serversArg as PeerTubeServer ] + else servers = serversArg as PeerTubeServer[] const states: JobState[] = [ 'waiting', 'active', 'delayed' ] const repeatableJobs = [ 'videos-views', 'activitypub-cleaner' ] @@ -72,15 +23,13 @@ async function waitJobs (serversArg: ServerInfo[] | ServerInfo) { // Check if each server has pending request for (const server of servers) { for (const state of states) { - const p = getJobsListPaginationAndSort({ - url: server.url, - accessToken: server.accessToken, - state: state, + const p = server.jobs.getJobsList({ + state, start: 0, count: 10, sort: '-createdAt' - }).then(res => res.body.data) - .then((jobs: Job[]) => jobs.filter(j => !repeatableJobs.includes(j.type))) + }).then(body => body.data) + .then(jobs => jobs.filter(j => !repeatableJobs.includes(j.type))) .then(jobs => { if (jobs.length !== 0) { pendingRequests = true @@ -90,9 +39,8 @@ async function waitJobs (serversArg: ServerInfo[] | ServerInfo) { tasks.push(p) } - const p = getDebug(server.url, server.accessToken) - .then(res => res.body) - .then((obj: ServerDebug) => { + const p = server.debug.getDebug() + .then(obj => { if (obj.activityPubMessagesWaiting !== 0) { pendingRequests = true } @@ -123,7 +71,5 @@ async function waitJobs (serversArg: ServerInfo[] | ServerInfo) { // --------------------------------------------------------------------------- export { - getJobsList, - waitJobs, - getJobsListPaginationAndSort + waitJobs } diff --git a/shared/extra-utils/server/plugins-command.ts b/shared/extra-utils/server/plugins-command.ts new file mode 100644 index 000000000..b944475a2 --- /dev/null +++ b/shared/extra-utils/server/plugins-command.ts @@ -0,0 +1,256 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ + +import { readJSON, writeJSON } from 'fs-extra' +import { join } from 'path' +import { root } from '@server/helpers/core-utils' +import { + HttpStatusCode, + PeerTubePlugin, + PeerTubePluginIndex, + PeertubePluginIndexList, + PluginPackageJson, + PluginTranslation, + PluginType, + PublicServerSetting, + RegisteredServerSettings, + ResultList +} from '@shared/models' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export class PluginsCommand extends AbstractCommand { + + static getPluginTestPath (suffix = '') { + return join(root(), 'server', 'tests', 'fixtures', 'peertube-plugin-test' + suffix) + } + + list (options: OverrideCommandOptions & { + start?: number + count?: number + sort?: string + pluginType?: PluginType + uninstalled?: boolean + }) { + const { start, count, sort, pluginType, uninstalled } = options + const path = '/api/v1/plugins' + + return this.getRequestBody>({ + ...options, + + path, + query: { + start, + count, + sort, + pluginType, + uninstalled + }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + listAvailable (options: OverrideCommandOptions & { + start?: number + count?: number + sort?: string + pluginType?: PluginType + currentPeerTubeEngine?: string + search?: string + expectedStatus?: HttpStatusCode + }) { + const { start, count, sort, pluginType, search, currentPeerTubeEngine } = options + const path = '/api/v1/plugins/available' + + const query: PeertubePluginIndexList = { + start, + count, + sort, + pluginType, + currentPeerTubeEngine, + search + } + + return this.getRequestBody>({ + ...options, + + path, + query, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + get (options: OverrideCommandOptions & { + npmName: string + }) { + const path = '/api/v1/plugins/' + options.npmName + + return this.getRequestBody({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + updateSettings (options: OverrideCommandOptions & { + npmName: string + settings: any + }) { + const { npmName, settings } = options + const path = '/api/v1/plugins/' + npmName + '/settings' + + return this.putBodyRequest({ + ...options, + + path, + fields: { settings }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + getRegisteredSettings (options: OverrideCommandOptions & { + npmName: string + }) { + const path = '/api/v1/plugins/' + options.npmName + '/registered-settings' + + return this.getRequestBody({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + getPublicSettings (options: OverrideCommandOptions & { + npmName: string + }) { + const { npmName } = options + const path = '/api/v1/plugins/' + npmName + '/public-settings' + + return this.getRequestBody({ + ...options, + + path, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + getTranslations (options: OverrideCommandOptions & { + locale: string + }) { + const { locale } = options + const path = '/plugins/translations/' + locale + '.json' + + return this.getRequestBody({ + ...options, + + path, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + install (options: OverrideCommandOptions & { + path?: string + npmName?: string + }) { + const { npmName, path } = options + const apiPath = '/api/v1/plugins/install' + + return this.postBodyRequest({ + ...options, + + path: apiPath, + fields: { npmName, path }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + update (options: OverrideCommandOptions & { + path?: string + npmName?: string + }) { + const { npmName, path } = options + const apiPath = '/api/v1/plugins/update' + + return this.postBodyRequest({ + ...options, + + path: apiPath, + fields: { npmName, path }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + uninstall (options: OverrideCommandOptions & { + npmName: string + }) { + const { npmName } = options + const apiPath = '/api/v1/plugins/uninstall' + + return this.postBodyRequest({ + ...options, + + path: apiPath, + fields: { npmName }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + getCSS (options: OverrideCommandOptions = {}) { + const path = '/plugins/global.css' + + return this.getRequestText({ + ...options, + + path, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + getExternalAuth (options: OverrideCommandOptions & { + npmName: string + npmVersion: string + authName: string + query?: any + }) { + const { npmName, npmVersion, authName, query } = options + + const path = '/plugins/' + npmName + '/' + npmVersion + '/auth/' + authName + + return this.getRequest({ + ...options, + + path, + query, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200, + redirects: 0 + }) + } + + updatePackageJSON (npmName: string, json: any) { + const path = this.getPackageJSONPath(npmName) + + return writeJSON(path, json) + } + + getPackageJSON (npmName: string): Promise { + const path = this.getPackageJSONPath(npmName) + + return readJSON(path) + } + + private getPackageJSONPath (npmName: string) { + return this.server.servers.buildDirectory(join('plugins', 'node_modules', npmName, 'package.json')) + } +} diff --git a/shared/extra-utils/server/plugins.ts b/shared/extra-utils/server/plugins.ts index d53e5b382..0f5fabd5a 100644 --- a/shared/extra-utils/server/plugins.ts +++ b/shared/extra-utils/server/plugins.ts @@ -1,307 +1,18 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ import { expect } from 'chai' -import { readJSON, writeJSON } from 'fs-extra' -import { join } from 'path' -import { RegisteredServerSettings } from '@shared/models' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' -import { PeertubePluginIndexList } from '../../models/plugins/plugin-index/peertube-plugin-index-list.model' -import { PluginType } from '../../models/plugins/plugin.type' -import { buildServerDirectory, root } from '../miscs/miscs' -import { makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests' -import { ServerInfo } from './servers' +import { PeerTubeServer } from '../server/server' -function listPlugins (parameters: { - url: string - accessToken: string - start?: number - count?: number - sort?: string - pluginType?: PluginType - uninstalled?: boolean - expectedStatus?: HttpStatusCode -}) { - const { url, accessToken, start, count, sort, pluginType, uninstalled, expectedStatus = HttpStatusCode.OK_200 } = parameters - const path = '/api/v1/plugins' - - return makeGetRequest({ - url, - path, - token: accessToken, - query: { - start, - count, - sort, - pluginType, - uninstalled - }, - statusCodeExpected: expectedStatus - }) -} - -function listAvailablePlugins (parameters: { - url: string - accessToken: string - start?: number - count?: number - sort?: string - pluginType?: PluginType - currentPeerTubeEngine?: string - search?: string - expectedStatus?: HttpStatusCode -}) { - const { - url, - accessToken, - start, - count, - sort, - pluginType, - search, - currentPeerTubeEngine, - expectedStatus = HttpStatusCode.OK_200 - } = parameters - const path = '/api/v1/plugins/available' - - const query: PeertubePluginIndexList = { - start, - count, - sort, - pluginType, - currentPeerTubeEngine, - search - } - - return makeGetRequest({ - url, - path, - token: accessToken, - query, - statusCodeExpected: expectedStatus - }) -} - -function getPlugin (parameters: { - url: string - accessToken: string - npmName: string - expectedStatus?: HttpStatusCode -}) { - const { url, accessToken, npmName, expectedStatus = HttpStatusCode.OK_200 } = parameters - const path = '/api/v1/plugins/' + npmName - - return makeGetRequest({ - url, - path, - token: accessToken, - statusCodeExpected: expectedStatus - }) -} - -function updatePluginSettings (parameters: { - url: string - accessToken: string - npmName: string - settings: any - expectedStatus?: HttpStatusCode -}) { - const { url, accessToken, npmName, settings, expectedStatus = HttpStatusCode.NO_CONTENT_204 } = parameters - const path = '/api/v1/plugins/' + npmName + '/settings' - - return makePutBodyRequest({ - url, - path, - token: accessToken, - fields: { settings }, - statusCodeExpected: expectedStatus - }) -} - -function getPluginRegisteredSettings (parameters: { - url: string - accessToken: string - npmName: string - expectedStatus?: HttpStatusCode -}) { - const { url, accessToken, npmName, expectedStatus = HttpStatusCode.OK_200 } = parameters - const path = '/api/v1/plugins/' + npmName + '/registered-settings' - - return makeGetRequest({ - url, - path, - token: accessToken, - statusCodeExpected: expectedStatus - }) -} - -async function testHelloWorldRegisteredSettings (server: ServerInfo) { - const res = await getPluginRegisteredSettings({ - url: server.url, - accessToken: server.accessToken, - npmName: 'peertube-plugin-hello-world' - }) - - const registeredSettings = (res.body as RegisteredServerSettings).registeredSettings +async function testHelloWorldRegisteredSettings (server: PeerTubeServer) { + const body = await server.plugins.getRegisteredSettings({ npmName: 'peertube-plugin-hello-world' }) + const registeredSettings = body.registeredSettings expect(registeredSettings).to.have.length.at.least(1) const adminNameSettings = registeredSettings.find(s => s.name === 'admin-name') expect(adminNameSettings).to.not.be.undefined } -function getPublicSettings (parameters: { - url: string - npmName: string - expectedStatus?: HttpStatusCode -}) { - const { url, npmName, expectedStatus = HttpStatusCode.OK_200 } = parameters - const path = '/api/v1/plugins/' + npmName + '/public-settings' - - return makeGetRequest({ - url, - path, - statusCodeExpected: expectedStatus - }) -} - -function getPluginTranslations (parameters: { - url: string - locale: string - expectedStatus?: HttpStatusCode -}) { - const { url, locale, expectedStatus = HttpStatusCode.OK_200 } = parameters - const path = '/plugins/translations/' + locale + '.json' - - return makeGetRequest({ - url, - path, - statusCodeExpected: expectedStatus - }) -} - -function installPlugin (parameters: { - url: string - accessToken: string - path?: string - npmName?: string - expectedStatus?: HttpStatusCode -}) { - const { url, accessToken, npmName, path, expectedStatus = HttpStatusCode.OK_200 } = parameters - const apiPath = '/api/v1/plugins/install' - - return makePostBodyRequest({ - url, - path: apiPath, - token: accessToken, - fields: { npmName, path }, - statusCodeExpected: expectedStatus - }) -} - -function updatePlugin (parameters: { - url: string - accessToken: string - path?: string - npmName?: string - expectedStatus?: HttpStatusCode -}) { - const { url, accessToken, npmName, path, expectedStatus = HttpStatusCode.OK_200 } = parameters - const apiPath = '/api/v1/plugins/update' - - return makePostBodyRequest({ - url, - path: apiPath, - token: accessToken, - fields: { npmName, path }, - statusCodeExpected: expectedStatus - }) -} - -function uninstallPlugin (parameters: { - url: string - accessToken: string - npmName: string - expectedStatus?: HttpStatusCode -}) { - const { url, accessToken, npmName, expectedStatus = HttpStatusCode.NO_CONTENT_204 } = parameters - const apiPath = '/api/v1/plugins/uninstall' - - return makePostBodyRequest({ - url, - path: apiPath, - token: accessToken, - fields: { npmName }, - statusCodeExpected: expectedStatus - }) -} - -function getPluginsCSS (url: string) { - const path = '/plugins/global.css' - - return makeGetRequest({ - url, - path, - statusCodeExpected: HttpStatusCode.OK_200 - }) -} - -function getPackageJSONPath (server: ServerInfo, npmName: string) { - return buildServerDirectory(server, join('plugins', 'node_modules', npmName, 'package.json')) -} - -function updatePluginPackageJSON (server: ServerInfo, npmName: string, json: any) { - const path = getPackageJSONPath(server, npmName) - - return writeJSON(path, json) -} - -function getPluginPackageJSON (server: ServerInfo, npmName: string) { - const path = getPackageJSONPath(server, npmName) - - return readJSON(path) -} - -function getPluginTestPath (suffix = '') { - return join(root(), 'server', 'tests', 'fixtures', 'peertube-plugin-test' + suffix) -} - -function getExternalAuth (options: { - url: string - npmName: string - npmVersion: string - authName: string - query?: any - statusCodeExpected?: HttpStatusCode -}) { - const { url, npmName, npmVersion, authName, statusCodeExpected, query } = options - - const path = '/plugins/' + npmName + '/' + npmVersion + '/auth/' + authName - - return makeGetRequest({ - url, - path, - query, - statusCodeExpected: statusCodeExpected || HttpStatusCode.OK_200, - redirects: 0 - }) -} - export { - listPlugins, - listAvailablePlugins, - installPlugin, - getPluginTranslations, - getPluginsCSS, - updatePlugin, - getPlugin, - uninstallPlugin, - testHelloWorldRegisteredSettings, - updatePluginSettings, - getPluginRegisteredSettings, - getPackageJSONPath, - updatePluginPackageJSON, - getPluginPackageJSON, - getPluginTestPath, - getPublicSettings, - getExternalAuth + testHelloWorldRegisteredSettings } diff --git a/shared/extra-utils/server/redundancy-command.ts b/shared/extra-utils/server/redundancy-command.ts new file mode 100644 index 000000000..e7a8b3c29 --- /dev/null +++ b/shared/extra-utils/server/redundancy-command.ts @@ -0,0 +1,80 @@ +import { HttpStatusCode, ResultList, VideoRedundanciesTarget, VideoRedundancy } from '@shared/models' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export class RedundancyCommand extends AbstractCommand { + + updateRedundancy (options: OverrideCommandOptions & { + host: string + redundancyAllowed: boolean + }) { + const { host, redundancyAllowed } = options + const path = '/api/v1/server/redundancy/' + host + + return this.putBodyRequest({ + ...options, + + path, + fields: { redundancyAllowed }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + listVideos (options: OverrideCommandOptions & { + target: VideoRedundanciesTarget + start?: number + count?: number + sort?: string + }) { + const path = '/api/v1/server/redundancy/videos' + + const { target, start, count, sort } = options + + return this.getRequestBody>({ + ...options, + + path, + + query: { + start: start ?? 0, + count: count ?? 5, + sort: sort ?? 'name', + target + }, + + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + addVideo (options: OverrideCommandOptions & { + videoId: number + }) { + const path = '/api/v1/server/redundancy/videos' + const { videoId } = options + + return this.postBodyRequest({ + ...options, + + path, + fields: { videoId }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + removeVideo (options: OverrideCommandOptions & { + redundancyId: number + }) { + const { redundancyId } = options + const path = '/api/v1/server/redundancy/videos/' + redundancyId + + return this.deleteRequest({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } +} diff --git a/shared/extra-utils/server/redundancy.ts b/shared/extra-utils/server/redundancy.ts deleted file mode 100644 index b83815a37..000000000 --- a/shared/extra-utils/server/redundancy.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { makeDeleteRequest, makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests' -import { VideoRedundanciesTarget } from '@shared/models' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' - -function updateRedundancy ( - url: string, - accessToken: string, - host: string, - redundancyAllowed: boolean, - expectedStatus = HttpStatusCode.NO_CONTENT_204 -) { - const path = '/api/v1/server/redundancy/' + host - - return makePutBodyRequest({ - url, - path, - token: accessToken, - fields: { redundancyAllowed }, - statusCodeExpected: expectedStatus - }) -} - -function listVideoRedundancies (options: { - url: string - accessToken: string - target: VideoRedundanciesTarget - start?: number - count?: number - sort?: string - statusCodeExpected?: HttpStatusCode -}) { - const path = '/api/v1/server/redundancy/videos' - - const { url, accessToken, target, statusCodeExpected, start, count, sort } = options - - return makeGetRequest({ - url, - token: accessToken, - path, - query: { - start: start ?? 0, - count: count ?? 5, - sort: sort ?? 'name', - target - }, - statusCodeExpected: statusCodeExpected || HttpStatusCode.OK_200 - }) -} - -function addVideoRedundancy (options: { - url: string - accessToken: string - videoId: number -}) { - const path = '/api/v1/server/redundancy/videos' - const { url, accessToken, videoId } = options - - return makePostBodyRequest({ - url, - token: accessToken, - path, - fields: { videoId }, - statusCodeExpected: HttpStatusCode.NO_CONTENT_204 - }) -} - -function removeVideoRedundancy (options: { - url: string - accessToken: string - redundancyId: number -}) { - const { url, accessToken, redundancyId } = options - const path = '/api/v1/server/redundancy/videos/' + redundancyId - - return makeDeleteRequest({ - url, - token: accessToken, - path, - statusCodeExpected: HttpStatusCode.NO_CONTENT_204 - }) -} - -export { - updateRedundancy, - listVideoRedundancies, - addVideoRedundancy, - removeVideoRedundancy -} diff --git a/shared/extra-utils/server/server.ts b/shared/extra-utils/server/server.ts new file mode 100644 index 000000000..3c335b8e4 --- /dev/null +++ b/shared/extra-utils/server/server.ts @@ -0,0 +1,369 @@ +import { ChildProcess, fork } from 'child_process' +import { copy } from 'fs-extra' +import { join } from 'path' +import { root } from '@server/helpers/core-utils' +import { randomInt } from '@shared/core-utils' +import { Video, VideoChannel, VideoCreateResult, VideoDetails } from '../../models/videos' +import { BulkCommand } from '../bulk' +import { CLICommand } from '../cli' +import { CustomPagesCommand } from '../custom-pages' +import { FeedCommand } from '../feeds' +import { LogsCommand } from '../logs' +import { parallelTests, SQLCommand } from '../miscs' +import { AbusesCommand } from '../moderation' +import { OverviewsCommand } from '../overviews' +import { SearchCommand } from '../search' +import { SocketIOCommand } from '../socket' +import { AccountsCommand, BlocklistCommand, LoginCommand, NotificationsCommand, SubscriptionsCommand, UsersCommand } from '../users' +import { + BlacklistCommand, + CaptionsCommand, + ChangeOwnershipCommand, + ChannelsCommand, + HistoryCommand, + ImportsCommand, + LiveCommand, + PlaylistsCommand, + ServicesCommand, + StreamingPlaylistsCommand, + VideosCommand +} from '../videos' +import { CommentsCommand } from '../videos/comments-command' +import { ConfigCommand } from './config-command' +import { ContactFormCommand } from './contact-form-command' +import { DebugCommand } from './debug-command' +import { FollowsCommand } from './follows-command' +import { JobsCommand } from './jobs-command' +import { PluginsCommand } from './plugins-command' +import { RedundancyCommand } from './redundancy-command' +import { ServersCommand } from './servers-command' +import { StatsCommand } from './stats-command' + +export type RunServerOptions = { + hideLogs?: boolean + nodeArgs?: string[] + peertubeArgs?: string[] +} + +export class PeerTubeServer { + app?: ChildProcess + + url: string + host?: string + hostname?: string + port?: number + + rtmpPort?: number + + parallel?: boolean + internalServerNumber: number + + serverNumber?: number + customConfigFile?: string + + store?: { + client?: { + id?: string + secret?: string + } + + user?: { + username: string + password: string + email?: string + } + + channel?: VideoChannel + + video?: Video + videoCreated?: VideoCreateResult + videoDetails?: VideoDetails + + videos?: { id: number, uuid: string }[] + } + + accessToken?: string + refreshToken?: string + + bulk?: BulkCommand + cli?: CLICommand + customPage?: CustomPagesCommand + feed?: FeedCommand + logs?: LogsCommand + abuses?: AbusesCommand + overviews?: OverviewsCommand + search?: SearchCommand + contactForm?: ContactFormCommand + debug?: DebugCommand + follows?: FollowsCommand + jobs?: JobsCommand + plugins?: PluginsCommand + redundancy?: RedundancyCommand + stats?: StatsCommand + config?: ConfigCommand + socketIO?: SocketIOCommand + accounts?: AccountsCommand + blocklist?: BlocklistCommand + subscriptions?: SubscriptionsCommand + live?: LiveCommand + services?: ServicesCommand + blacklist?: BlacklistCommand + captions?: CaptionsCommand + changeOwnership?: ChangeOwnershipCommand + playlists?: PlaylistsCommand + history?: HistoryCommand + imports?: ImportsCommand + streamingPlaylists?: StreamingPlaylistsCommand + channels?: ChannelsCommand + comments?: CommentsCommand + sql?: SQLCommand + notifications?: NotificationsCommand + servers?: ServersCommand + login?: LoginCommand + users?: UsersCommand + videos?: VideosCommand + + constructor (options: { serverNumber: number } | { url: string }) { + if ((options as any).url) { + this.setUrl((options as any).url) + } else { + this.setServerNumber((options as any).serverNumber) + } + + this.store = { + client: { + id: null, + secret: null + }, + user: { + username: null, + password: null + } + } + + this.assignCommands() + } + + setServerNumber (serverNumber: number) { + this.serverNumber = serverNumber + + this.parallel = parallelTests() + + this.internalServerNumber = this.parallel ? this.randomServer() : this.serverNumber + this.rtmpPort = this.parallel ? this.randomRTMP() : 1936 + this.port = 9000 + this.internalServerNumber + + this.url = `http://localhost:${this.port}` + this.host = `localhost:${this.port}` + this.hostname = 'localhost' + } + + setUrl (url: string) { + const parsed = new URL(url) + + this.url = url + this.host = parsed.host + this.hostname = parsed.hostname + this.port = parseInt(parsed.port) + } + + async flushAndRun (configOverride?: Object, options: RunServerOptions = {}) { + await ServersCommand.flushTests(this.internalServerNumber) + + return this.run(configOverride, options) + } + + async run (configOverrideArg?: any, options: RunServerOptions = {}) { + // These actions are async so we need to be sure that they have both been done + const serverRunString = { + 'HTTP server listening': false + } + const key = 'Database peertube_test' + this.internalServerNumber + ' is ready' + serverRunString[key] = false + + const regexps = { + client_id: 'Client id: (.+)', + client_secret: 'Client secret: (.+)', + user_username: 'Username: (.+)', + user_password: 'User password: (.+)' + } + + await this.assignCustomConfigFile() + + const configOverride = this.buildConfigOverride() + + if (configOverrideArg !== undefined) { + Object.assign(configOverride, configOverrideArg) + } + + // Share the environment + const env = Object.create(process.env) + env['NODE_ENV'] = 'test' + env['NODE_APP_INSTANCE'] = this.internalServerNumber.toString() + env['NODE_CONFIG'] = JSON.stringify(configOverride) + + const forkOptions = { + silent: true, + env, + detached: true, + execArgv: options.nodeArgs || [] + } + + return new Promise(res => { + const self = this + + this.app = fork(join(root(), 'dist', 'server.js'), options.peertubeArgs || [], forkOptions) + this.app.stdout.on('data', function onStdout (data) { + let dontContinue = false + + // Capture things if we want to + for (const key of Object.keys(regexps)) { + const regexp = regexps[key] + const matches = data.toString().match(regexp) + if (matches !== null) { + if (key === 'client_id') self.store.client.id = matches[1] + else if (key === 'client_secret') self.store.client.secret = matches[1] + else if (key === 'user_username') self.store.user.username = matches[1] + else if (key === 'user_password') self.store.user.password = matches[1] + } + } + + // Check if all required sentences are here + for (const key of Object.keys(serverRunString)) { + if (data.toString().indexOf(key) !== -1) serverRunString[key] = true + if (serverRunString[key] === false) dontContinue = true + } + + // If no, there is maybe one thing not already initialized (client/user credentials generation...) + if (dontContinue === true) return + + if (options.hideLogs === false) { + console.log(data.toString()) + } else { + self.app.stdout.removeListener('data', onStdout) + } + + process.on('exit', () => { + try { + process.kill(self.app.pid) + } catch { /* empty */ } + }) + + res() + }) + }) + } + + async kill () { + if (!this.app) return + + await this.sql.cleanup() + + process.kill(-this.app.pid) + + this.app = null + } + + private randomServer () { + const low = 10 + const high = 10000 + + return randomInt(low, high) + } + + private randomRTMP () { + const low = 1900 + const high = 2100 + + return randomInt(low, high) + } + + private async assignCustomConfigFile () { + if (this.internalServerNumber === this.serverNumber) return + + const basePath = join(root(), 'config') + + const tmpConfigFile = join(basePath, `test-${this.internalServerNumber}.yaml`) + await copy(join(basePath, `test-${this.serverNumber}.yaml`), tmpConfigFile) + + this.customConfigFile = tmpConfigFile + } + + private buildConfigOverride () { + if (!this.parallel) return {} + + return { + listen: { + port: this.port + }, + webserver: { + port: this.port + }, + database: { + suffix: '_test' + this.internalServerNumber + }, + storage: { + tmp: `test${this.internalServerNumber}/tmp/`, + avatars: `test${this.internalServerNumber}/avatars/`, + videos: `test${this.internalServerNumber}/videos/`, + streaming_playlists: `test${this.internalServerNumber}/streaming-playlists/`, + redundancy: `test${this.internalServerNumber}/redundancy/`, + logs: `test${this.internalServerNumber}/logs/`, + previews: `test${this.internalServerNumber}/previews/`, + thumbnails: `test${this.internalServerNumber}/thumbnails/`, + torrents: `test${this.internalServerNumber}/torrents/`, + captions: `test${this.internalServerNumber}/captions/`, + cache: `test${this.internalServerNumber}/cache/`, + plugins: `test${this.internalServerNumber}/plugins/` + }, + admin: { + email: `admin${this.internalServerNumber}@example.com` + }, + live: { + rtmp: { + port: this.rtmpPort + } + } + } + } + + private assignCommands () { + this.bulk = new BulkCommand(this) + this.cli = new CLICommand(this) + this.customPage = new CustomPagesCommand(this) + this.feed = new FeedCommand(this) + this.logs = new LogsCommand(this) + this.abuses = new AbusesCommand(this) + this.overviews = new OverviewsCommand(this) + this.search = new SearchCommand(this) + this.contactForm = new ContactFormCommand(this) + this.debug = new DebugCommand(this) + this.follows = new FollowsCommand(this) + this.jobs = new JobsCommand(this) + this.plugins = new PluginsCommand(this) + this.redundancy = new RedundancyCommand(this) + this.stats = new StatsCommand(this) + this.config = new ConfigCommand(this) + this.socketIO = new SocketIOCommand(this) + this.accounts = new AccountsCommand(this) + this.blocklist = new BlocklistCommand(this) + this.subscriptions = new SubscriptionsCommand(this) + this.live = new LiveCommand(this) + this.services = new ServicesCommand(this) + this.blacklist = new BlacklistCommand(this) + this.captions = new CaptionsCommand(this) + this.changeOwnership = new ChangeOwnershipCommand(this) + this.playlists = new PlaylistsCommand(this) + this.history = new HistoryCommand(this) + this.imports = new ImportsCommand(this) + this.streamingPlaylists = new StreamingPlaylistsCommand(this) + this.channels = new ChannelsCommand(this) + this.comments = new CommentsCommand(this) + this.sql = new SQLCommand(this) + this.notifications = new NotificationsCommand(this) + this.servers = new ServersCommand(this) + this.login = new LoginCommand(this) + this.users = new UsersCommand(this) + this.videos = new VideosCommand(this) + } +} diff --git a/shared/extra-utils/server/servers-command.ts b/shared/extra-utils/server/servers-command.ts new file mode 100644 index 000000000..40a11e8d7 --- /dev/null +++ b/shared/extra-utils/server/servers-command.ts @@ -0,0 +1,88 @@ +import { exec } from 'child_process' +import { copy, ensureDir, readFile, remove } from 'fs-extra' +import { basename, join } from 'path' +import { root } from '@server/helpers/core-utils' +import { HttpStatusCode } from '@shared/models' +import { getFileSize, isGithubCI, wait } from '../miscs' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export class ServersCommand extends AbstractCommand { + + static flushTests (internalServerNumber: number) { + return new Promise((res, rej) => { + const suffix = ` -- ${internalServerNumber}` + + return exec('npm run clean:server:test' + suffix, (err, _stdout, stderr) => { + if (err || stderr) return rej(err || new Error(stderr)) + + return res() + }) + }) + } + + ping (options: OverrideCommandOptions = {}) { + return this.getRequestBody({ + ...options, + + path: '/api/v1/ping', + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + async cleanupTests () { + const p: Promise[] = [] + + if (isGithubCI()) { + await ensureDir('artifacts') + + const origin = this.buildDirectory('logs/peertube.log') + const destname = `peertube-${this.server.internalServerNumber}.log` + console.log('Saving logs %s.', destname) + + await copy(origin, join('artifacts', destname)) + } + + if (this.server.parallel) { + p.push(ServersCommand.flushTests(this.server.internalServerNumber)) + } + + if (this.server.customConfigFile) { + p.push(remove(this.server.customConfigFile)) + } + + return p + } + + async waitUntilLog (str: string, count = 1, strictCount = true) { + const logfile = this.server.servers.buildDirectory('logs/peertube.log') + + while (true) { + const buf = await readFile(logfile) + + const matches = buf.toString().match(new RegExp(str, 'g')) + if (matches && matches.length === count) return + if (matches && strictCount === false && matches.length >= count) return + + await wait(1000) + } + } + + buildDirectory (directory: string) { + return join(root(), 'test' + this.server.internalServerNumber, directory) + } + + buildWebTorrentFilePath (fileUrl: string) { + return this.buildDirectory(join('videos', basename(fileUrl))) + } + + buildFragmentedFilePath (videoUUID: string, fileUrl: string) { + return this.buildDirectory(join('streaming-playlists', 'hls', videoUUID, basename(fileUrl))) + } + + async getServerFileSize (subPath: string) { + const path = this.server.servers.buildDirectory(subPath) + + return getFileSize(path) + } +} diff --git a/shared/extra-utils/server/servers.ts b/shared/extra-utils/server/servers.ts index 28e431e94..f0622feb0 100644 --- a/shared/extra-utils/server/servers.ts +++ b/shared/extra-utils/server/servers.ts @@ -1,384 +1,49 @@ -/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */ +import { ensureDir } from 'fs-extra' +import { isGithubCI } from '../miscs' +import { PeerTubeServer, RunServerOptions } from './server' -import { expect } from 'chai' -import { ChildProcess, exec, fork } from 'child_process' -import { copy, ensureDir, pathExists, readdir, readFile, remove } from 'fs-extra' -import { join } from 'path' -import { randomInt } from '../../core-utils/miscs/miscs' -import { VideoChannel } from '../../models/videos' -import { buildServerDirectory, getFileSize, isGithubCI, root, wait } from '../miscs/miscs' -import { makeGetRequest } from '../requests/requests' +async function createSingleServer (serverNumber: number, configOverride?: Object, options: RunServerOptions = {}) { + const server = new PeerTubeServer({ serverNumber }) -interface ServerInfo { - app: ChildProcess - - url: string - host: string - hostname: string - port: number - - rtmpPort: number - - parallel: boolean - internalServerNumber: number - serverNumber: number - - client: { - id: string - secret: string - } - - user: { - username: string - password: string - email?: string - } - - customConfigFile?: string - - accessToken?: string - refreshToken?: string - videoChannel?: VideoChannel - - video?: { - id: number - uuid: string - shortUUID: string - name?: string - url?: string - - account?: { - name: string - } - - embedPath?: string - } - - remoteVideo?: { - id: number - uuid: string - } - - videos?: { id: number, uuid: string }[] -} - -function parallelTests () { - return process.env.MOCHA_PARALLEL === 'true' -} - -function flushAndRunMultipleServers (totalServers: number, configOverride?: Object) { - const apps = [] - let i = 0 - - return new Promise(res => { - function anotherServerDone (serverNumber, app) { - apps[serverNumber - 1] = app - i++ - if (i === totalServers) { - return res(apps) - } - } - - for (let j = 1; j <= totalServers; j++) { - flushAndRunServer(j, configOverride).then(app => anotherServerDone(j, app)) - } - }) -} - -function flushTests (serverNumber?: number) { - return new Promise((res, rej) => { - const suffix = serverNumber ? ` -- ${serverNumber}` : '' - - return exec('npm run clean:server:test' + suffix, (err, _stdout, stderr) => { - if (err || stderr) return rej(err || new Error(stderr)) - - return res() - }) - }) -} - -function randomServer () { - const low = 10 - const high = 10000 - - return randomInt(low, high) -} - -function randomRTMP () { - const low = 1900 - const high = 2100 - - return randomInt(low, high) -} - -type RunServerOptions = { - hideLogs?: boolean - execArgv?: string[] -} - -async function flushAndRunServer (serverNumber: number, configOverride?: Object, args = [], options: RunServerOptions = {}) { - const parallel = parallelTests() - - const internalServerNumber = parallel ? randomServer() : serverNumber - const rtmpPort = parallel ? randomRTMP() : 1936 - const port = 9000 + internalServerNumber - - await flushTests(internalServerNumber) - - const server: ServerInfo = { - app: null, - port, - internalServerNumber, - rtmpPort, - parallel, - serverNumber, - url: `http://localhost:${port}`, - host: `localhost:${port}`, - hostname: 'localhost', - client: { - id: null, - secret: null - }, - user: { - username: null, - password: null - } - } - - return runServer(server, configOverride, args, options) -} - -async function runServer (server: ServerInfo, configOverrideArg?: any, args = [], options: RunServerOptions = {}) { - // These actions are async so we need to be sure that they have both been done - const serverRunString = { - 'HTTP server listening': false - } - const key = 'Database peertube_test' + server.internalServerNumber + ' is ready' - serverRunString[key] = false - - const regexps = { - client_id: 'Client id: (.+)', - client_secret: 'Client secret: (.+)', - user_username: 'Username: (.+)', - user_password: 'User password: (.+)' - } - - if (server.internalServerNumber !== server.serverNumber) { - const basePath = join(root(), 'config') - - const tmpConfigFile = join(basePath, `test-${server.internalServerNumber}.yaml`) - await copy(join(basePath, `test-${server.serverNumber}.yaml`), tmpConfigFile) - - server.customConfigFile = tmpConfigFile - } - - const configOverride: any = {} - - if (server.parallel) { - Object.assign(configOverride, { - listen: { - port: server.port - }, - webserver: { - port: server.port - }, - database: { - suffix: '_test' + server.internalServerNumber - }, - storage: { - tmp: `test${server.internalServerNumber}/tmp/`, - avatars: `test${server.internalServerNumber}/avatars/`, - videos: `test${server.internalServerNumber}/videos/`, - streaming_playlists: `test${server.internalServerNumber}/streaming-playlists/`, - redundancy: `test${server.internalServerNumber}/redundancy/`, - logs: `test${server.internalServerNumber}/logs/`, - previews: `test${server.internalServerNumber}/previews/`, - thumbnails: `test${server.internalServerNumber}/thumbnails/`, - torrents: `test${server.internalServerNumber}/torrents/`, - captions: `test${server.internalServerNumber}/captions/`, - cache: `test${server.internalServerNumber}/cache/`, - plugins: `test${server.internalServerNumber}/plugins/` - }, - admin: { - email: `admin${server.internalServerNumber}@example.com` - }, - live: { - rtmp: { - port: server.rtmpPort - } - } - }) - } - - if (configOverrideArg !== undefined) { - Object.assign(configOverride, configOverrideArg) - } - - // Share the environment - const env = Object.create(process.env) - env['NODE_ENV'] = 'test' - env['NODE_APP_INSTANCE'] = server.internalServerNumber.toString() - env['NODE_CONFIG'] = JSON.stringify(configOverride) - - const forkOptions = { - silent: true, - env, - detached: true, - execArgv: options.execArgv || [] - } - - return new Promise(res => { - server.app = fork(join(root(), 'dist', 'server.js'), args, forkOptions) - server.app.stdout.on('data', function onStdout (data) { - let dontContinue = false - - // Capture things if we want to - for (const key of Object.keys(regexps)) { - const regexp = regexps[key] - const matches = data.toString().match(regexp) - if (matches !== null) { - if (key === 'client_id') server.client.id = matches[1] - else if (key === 'client_secret') server.client.secret = matches[1] - else if (key === 'user_username') server.user.username = matches[1] - else if (key === 'user_password') server.user.password = matches[1] - } - } - - // Check if all required sentences are here - for (const key of Object.keys(serverRunString)) { - if (data.toString().indexOf(key) !== -1) serverRunString[key] = true - if (serverRunString[key] === false) dontContinue = true - } - - // If no, there is maybe one thing not already initialized (client/user credentials generation...) - if (dontContinue === true) return - - if (options.hideLogs === false) { - console.log(data.toString()) - } else { - server.app.stdout.removeListener('data', onStdout) - } - - process.on('exit', () => { - try { - process.kill(server.app.pid) - } catch { /* empty */ } - }) - - res(server) - }) - }) -} - -async function reRunServer (server: ServerInfo, configOverride?: any) { - const newServer = await runServer(server, configOverride) - server.app = newServer.app + await server.flushAndRun(configOverride, options) return server } -async function checkTmpIsEmpty (server: ServerInfo) { - await checkDirectoryIsEmpty(server, 'tmp', [ 'plugins-global.css', 'hls', 'resumable-uploads' ]) +function createMultipleServers (totalServers: number, configOverride?: Object) { + const serverPromises: Promise[] = [] - if (await pathExists(join('test' + server.internalServerNumber, 'tmp', 'hls'))) { - await checkDirectoryIsEmpty(server, 'tmp/hls') + for (let i = 1; i <= totalServers; i++) { + serverPromises.push(createSingleServer(i, configOverride)) } -} - -async function checkDirectoryIsEmpty (server: ServerInfo, directory: string, exceptions: string[] = []) { - const testDirectory = 'test' + server.internalServerNumber - - const directoryPath = join(root(), testDirectory, directory) - const directoryExists = await pathExists(directoryPath) - expect(directoryExists).to.be.true - - const files = await readdir(directoryPath) - const filtered = files.filter(f => exceptions.includes(f) === false) - - expect(filtered).to.have.lengthOf(0) + return Promise.all(serverPromises) } -function killallServers (servers: ServerInfo[]) { - for (const server of servers) { - if (!server.app) continue - - process.kill(-server.app.pid) - server.app = null - } +async function killallServers (servers: PeerTubeServer[]) { + return Promise.all(servers.map(s => s.kill())) } -async function cleanupTests (servers: ServerInfo[]) { - killallServers(servers) +async function cleanupTests (servers: PeerTubeServer[]) { + await killallServers(servers) if (isGithubCI()) { await ensureDir('artifacts') } - const p: Promise[] = [] + let p: Promise[] = [] for (const server of servers) { - if (isGithubCI()) { - const origin = await buildServerDirectory(server, 'logs/peertube.log') - const destname = `peertube-${server.internalServerNumber}.log` - console.log('Saving logs %s.', destname) - - await copy(origin, join('artifacts', destname)) - } - - if (server.parallel) { - p.push(flushTests(server.internalServerNumber)) - } - - if (server.customConfigFile) { - p.push(remove(server.customConfigFile)) - } + p = p.concat(server.servers.cleanupTests()) } return Promise.all(p) } -async function waitUntilLog (server: ServerInfo, str: string, count = 1, strictCount = true) { - const logfile = buildServerDirectory(server, 'logs/peertube.log') - - while (true) { - const buf = await readFile(logfile) - - const matches = buf.toString().match(new RegExp(str, 'g')) - if (matches && matches.length === count) return - if (matches && strictCount === false && matches.length >= count) return - - await wait(1000) - } -} - -async function getServerFileSize (server: ServerInfo, subPath: string) { - const path = buildServerDirectory(server, subPath) - - return getFileSize(path) -} - -function makePingRequest (server: ServerInfo) { - return makeGetRequest({ - url: server.url, - path: '/api/v1/ping', - statusCodeExpected: 200 - }) -} - // --------------------------------------------------------------------------- export { - checkDirectoryIsEmpty, - checkTmpIsEmpty, - getServerFileSize, - ServerInfo, - parallelTests, + createSingleServer, + createMultipleServers, cleanupTests, - flushAndRunMultipleServers, - flushTests, - makePingRequest, - flushAndRunServer, - killallServers, - reRunServer, - waitUntilLog + killallServers } diff --git a/shared/extra-utils/server/stats-command.ts b/shared/extra-utils/server/stats-command.ts new file mode 100644 index 000000000..64a452306 --- /dev/null +++ b/shared/extra-utils/server/stats-command.ts @@ -0,0 +1,25 @@ +import { HttpStatusCode, ServerStats } from '@shared/models' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export class StatsCommand extends AbstractCommand { + + get (options: OverrideCommandOptions & { + useCache?: boolean // default false + } = {}) { + const { useCache = false } = options + const path = '/api/v1/server/stats' + + const query = { + t: useCache ? undefined : new Date().getTime() + } + + return this.getRequestBody({ + ...options, + + path, + query, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } +} diff --git a/shared/extra-utils/server/stats.ts b/shared/extra-utils/server/stats.ts deleted file mode 100644 index b9dae24e2..000000000 --- a/shared/extra-utils/server/stats.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { makeGetRequest } from '../requests/requests' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' - -function getStats (url: string, useCache = false) { - const path = '/api/v1/server/stats' - - const query = { - t: useCache ? undefined : new Date().getTime() - } - - return makeGetRequest({ - url, - path, - query, - statusCodeExpected: HttpStatusCode.OK_200 - }) -} - -// --------------------------------------------------------------------------- - -export { - getStats -} diff --git a/shared/extra-utils/shared/abstract-command.ts b/shared/extra-utils/shared/abstract-command.ts new file mode 100644 index 000000000..021045e49 --- /dev/null +++ b/shared/extra-utils/shared/abstract-command.ts @@ -0,0 +1,199 @@ +import { isAbsolute, join } from 'path' +import { root } from '../miscs/tests' +import { + makeDeleteRequest, + makeGetRequest, + makePostBodyRequest, + makePutBodyRequest, + makeUploadRequest, + unwrapBody, + unwrapText +} from '../requests/requests' +import { PeerTubeServer } from '../server/server' + +export interface OverrideCommandOptions { + token?: string + expectedStatus?: number +} + +interface InternalCommonCommandOptions extends OverrideCommandOptions { + // Default to server.url + url?: string + + path: string + // If we automatically send the server token if the token is not provided + implicitToken: boolean + defaultExpectedStatus: number + + // Common optional request parameters + contentType?: string + accept?: string + redirects?: number + range?: string + host?: string + headers?: { [ name: string ]: string } + requestType?: string + xForwardedFor?: string +} + +interface InternalGetCommandOptions extends InternalCommonCommandOptions { + query?: { [ id: string ]: any } +} + +abstract class AbstractCommand { + + constructor ( + protected server: PeerTubeServer + ) { + + } + + protected getRequestBody (options: InternalGetCommandOptions) { + return unwrapBody(this.getRequest(options)) + } + + protected getRequestText (options: InternalGetCommandOptions) { + return unwrapText(this.getRequest(options)) + } + + protected getRawRequest (options: Omit) { + const { url, range } = options + const { host, protocol, pathname } = new URL(url) + + return this.getRequest({ + ...options, + + token: this.buildCommonRequestToken(options), + defaultExpectedStatus: this.buildExpectedStatus(options), + + url: `${protocol}//${host}`, + path: pathname, + range + }) + } + + protected getRequest (options: InternalGetCommandOptions) { + const { query } = options + + return makeGetRequest({ + ...this.buildCommonRequestOptions(options), + + query + }) + } + + protected deleteRequest (options: InternalCommonCommandOptions) { + return makeDeleteRequest(this.buildCommonRequestOptions(options)) + } + + protected putBodyRequest (options: InternalCommonCommandOptions & { + fields?: { [ fieldName: string ]: any } + }) { + const { fields } = options + + return makePutBodyRequest({ + ...this.buildCommonRequestOptions(options), + + fields + }) + } + + protected postBodyRequest (options: InternalCommonCommandOptions & { + fields?: { [ fieldName: string ]: any } + }) { + const { fields } = options + + return makePostBodyRequest({ + ...this.buildCommonRequestOptions(options), + + fields + }) + } + + protected postUploadRequest (options: InternalCommonCommandOptions & { + fields?: { [ fieldName: string ]: any } + attaches?: { [ fieldName: string ]: any } + }) { + const { fields, attaches } = options + + return makeUploadRequest({ + ...this.buildCommonRequestOptions(options), + + method: 'POST', + fields, + attaches + }) + } + + protected putUploadRequest (options: InternalCommonCommandOptions & { + fields?: { [ fieldName: string ]: any } + attaches?: { [ fieldName: string ]: any } + }) { + const { fields, attaches } = options + + return makeUploadRequest({ + ...this.buildCommonRequestOptions(options), + + method: 'PUT', + fields, + attaches + }) + } + + protected updateImageRequest (options: InternalCommonCommandOptions & { + fixture: string + fieldname: string + }) { + const filePath = isAbsolute(options.fixture) + ? options.fixture + : join(root(), 'server', 'tests', 'fixtures', options.fixture) + + return this.postUploadRequest({ + ...options, + + fields: {}, + attaches: { [options.fieldname]: filePath } + }) + } + + protected buildCommonRequestOptions (options: InternalCommonCommandOptions) { + const { url, path, redirects, contentType, accept, range, host, headers, requestType, xForwardedFor } = options + + return { + url: url ?? this.server.url, + path, + + token: this.buildCommonRequestToken(options), + expectedStatus: this.buildExpectedStatus(options), + + redirects, + contentType, + range, + host, + accept, + headers, + type: requestType, + xForwardedFor + } + } + + protected buildCommonRequestToken (options: Pick) { + const { token } = options + + const fallbackToken = options.implicitToken + ? this.server.accessToken + : undefined + + return token !== undefined ? token : fallbackToken + } + + protected buildExpectedStatus (options: Pick) { + const { expectedStatus, defaultExpectedStatus } = options + + return expectedStatus !== undefined ? expectedStatus : defaultExpectedStatus + } +} + +export { + AbstractCommand +} diff --git a/shared/extra-utils/shared/index.ts b/shared/extra-utils/shared/index.ts new file mode 100644 index 000000000..e807ab4f7 --- /dev/null +++ b/shared/extra-utils/shared/index.ts @@ -0,0 +1 @@ +export * from './abstract-command' diff --git a/shared/extra-utils/socket/index.ts b/shared/extra-utils/socket/index.ts new file mode 100644 index 000000000..594329b2f --- /dev/null +++ b/shared/extra-utils/socket/index.ts @@ -0,0 +1 @@ +export * from './socket-io-command' diff --git a/shared/extra-utils/socket/socket-io-command.ts b/shared/extra-utils/socket/socket-io-command.ts new file mode 100644 index 000000000..c277ead28 --- /dev/null +++ b/shared/extra-utils/socket/socket-io-command.ts @@ -0,0 +1,15 @@ +import { io } from 'socket.io-client' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export class SocketIOCommand extends AbstractCommand { + + getUserNotificationSocket (options: OverrideCommandOptions = {}) { + return io(this.server.url + '/user-notifications', { + query: { accessToken: options.token ?? this.server.accessToken } + }) + } + + getLiveNotificationSocket () { + return io(this.server.url + '/live-videos') + } +} diff --git a/shared/extra-utils/socket/socket-io.ts b/shared/extra-utils/socket/socket-io.ts deleted file mode 100644 index 4ca93f453..000000000 --- a/shared/extra-utils/socket/socket-io.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { io } from 'socket.io-client' - -function getUserNotificationSocket (serverUrl: string, accessToken: string) { - return io(serverUrl + '/user-notifications', { - query: { accessToken } - }) -} - -function getLiveNotificationSocket (serverUrl: string) { - return io(serverUrl + '/live-videos') -} - -// --------------------------------------------------------------------------- - -export { - getUserNotificationSocket, - getLiveNotificationSocket -} diff --git a/shared/extra-utils/users/accounts-command.ts b/shared/extra-utils/users/accounts-command.ts new file mode 100644 index 000000000..2f586104e --- /dev/null +++ b/shared/extra-utils/users/accounts-command.ts @@ -0,0 +1,56 @@ +import { HttpStatusCode, ResultList } from '@shared/models' +import { Account } from '../../models/actors' +import { AccountVideoRate, VideoRateType } from '../../models/videos' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export class AccountsCommand extends AbstractCommand { + + list (options: OverrideCommandOptions & { + sort?: string // default -createdAt + } = {}) { + const { sort = '-createdAt' } = options + const path = '/api/v1/accounts' + + return this.getRequestBody>({ + ...options, + + path, + query: { sort }, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + get (options: OverrideCommandOptions & { + accountName: string + }) { + const path = '/api/v1/accounts/' + options.accountName + + return this.getRequestBody({ + ...options, + + path, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + listRatings (options: OverrideCommandOptions & { + accountName: string + rating?: VideoRateType + }) { + const { rating, accountName } = options + const path = '/api/v1/accounts/' + accountName + '/ratings' + + const query = { rating } + + return this.getRequestBody>({ + ...options, + + path, + query, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } +} diff --git a/shared/extra-utils/users/accounts.ts b/shared/extra-utils/users/accounts.ts deleted file mode 100644 index 4ea7f1402..000000000 --- a/shared/extra-utils/users/accounts.ts +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ - -import * as request from 'supertest' -import { expect } from 'chai' -import { existsSync, readdir } from 'fs-extra' -import { join } from 'path' -import { Account } from '../../models/actors' -import { root } from '../miscs/miscs' -import { makeGetRequest } from '../requests/requests' -import { VideoRateType } from '../../models/videos' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' - -function getAccountsList (url: string, sort = '-createdAt', statusCodeExpected = HttpStatusCode.OK_200) { - const path = '/api/v1/accounts' - - return makeGetRequest({ - url, - query: { sort }, - path, - statusCodeExpected - }) -} - -function getAccount (url: string, accountName: string, statusCodeExpected = HttpStatusCode.OK_200) { - const path = '/api/v1/accounts/' + accountName - - return makeGetRequest({ - url, - path, - statusCodeExpected - }) -} - -async function expectAccountFollows (url: string, nameWithDomain: string, followersCount: number, followingCount: number) { - const res = await getAccountsList(url) - const account = res.body.data.find((a: Account) => a.name + '@' + a.host === nameWithDomain) - - const message = `${nameWithDomain} on ${url}` - expect(account.followersCount).to.equal(followersCount, message) - expect(account.followingCount).to.equal(followingCount, message) -} - -async function checkActorFilesWereRemoved (filename: string, serverNumber: number) { - const testDirectory = 'test' + serverNumber - - for (const directory of [ 'avatars' ]) { - const directoryPath = join(root(), testDirectory, directory) - - const directoryExists = existsSync(directoryPath) - expect(directoryExists).to.be.true - - const files = await readdir(directoryPath) - for (const file of files) { - expect(file).to.not.contain(filename) - } - } -} - -function getAccountRatings ( - url: string, - accountName: string, - accessToken: string, - rating?: VideoRateType, - statusCodeExpected = HttpStatusCode.OK_200 -) { - const path = '/api/v1/accounts/' + accountName + '/ratings' - - const query = rating ? { rating } : {} - - return request(url) - .get(path) - .query(query) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(statusCodeExpected) - .expect('Content-Type', /json/) -} - -// --------------------------------------------------------------------------- - -export { - getAccount, - expectAccountFollows, - getAccountsList, - checkActorFilesWereRemoved, - getAccountRatings -} diff --git a/shared/extra-utils/users/actors.ts b/shared/extra-utils/users/actors.ts new file mode 100644 index 000000000..cfcc7d0a7 --- /dev/null +++ b/shared/extra-utils/users/actors.ts @@ -0,0 +1,73 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ + +import { expect } from 'chai' +import { pathExists, readdir } from 'fs-extra' +import { join } from 'path' +import { root } from '@server/helpers/core-utils' +import { Account, VideoChannel } from '@shared/models' +import { PeerTubeServer } from '../server' + +async function expectChannelsFollows (options: { + server: PeerTubeServer + handle: string + followers: number + following: number +}) { + const { server } = options + const { data } = await server.channels.list() + + return expectActorFollow({ ...options, data }) +} + +async function expectAccountFollows (options: { + server: PeerTubeServer + handle: string + followers: number + following: number +}) { + const { server } = options + const { data } = await server.accounts.list() + + return expectActorFollow({ ...options, data }) +} + +async function checkActorFilesWereRemoved (filename: string, serverNumber: number) { + const testDirectory = 'test' + serverNumber + + for (const directory of [ 'avatars' ]) { + const directoryPath = join(root(), testDirectory, directory) + + const directoryExists = await pathExists(directoryPath) + expect(directoryExists).to.be.true + + const files = await readdir(directoryPath) + for (const file of files) { + expect(file).to.not.contain(filename) + } + } +} + +export { + expectAccountFollows, + expectChannelsFollows, + checkActorFilesWereRemoved +} + +// --------------------------------------------------------------------------- + +function expectActorFollow (options: { + server: PeerTubeServer + data: (Account | VideoChannel)[] + handle: string + followers: number + following: number +}) { + const { server, data, handle, followers, following } = options + + const actor = data.find(a => a.name + '@' + a.host === handle) + const message = `${handle} on ${server.url}` + + expect(actor, message).to.exist + expect(actor.followersCount).to.equal(followers, message) + expect(actor.followingCount).to.equal(following, message) +} diff --git a/shared/extra-utils/users/blocklist-command.ts b/shared/extra-utils/users/blocklist-command.ts new file mode 100644 index 000000000..14491a1ae --- /dev/null +++ b/shared/extra-utils/users/blocklist-command.ts @@ -0,0 +1,139 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ + +import { AccountBlock, HttpStatusCode, ResultList, ServerBlock } from '@shared/models' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +type ListBlocklistOptions = OverrideCommandOptions & { + start: number + count: number + sort: string // default -createdAt +} + +export class BlocklistCommand extends AbstractCommand { + + listMyAccountBlocklist (options: ListBlocklistOptions) { + const path = '/api/v1/users/me/blocklist/accounts' + + return this.listBlocklist(options, path) + } + + listMyServerBlocklist (options: ListBlocklistOptions) { + const path = '/api/v1/users/me/blocklist/servers' + + return this.listBlocklist(options, path) + } + + listServerAccountBlocklist (options: ListBlocklistOptions) { + const path = '/api/v1/server/blocklist/accounts' + + return this.listBlocklist(options, path) + } + + listServerServerBlocklist (options: ListBlocklistOptions) { + const path = '/api/v1/server/blocklist/servers' + + return this.listBlocklist(options, path) + } + + // --------------------------------------------------------------------------- + + addToMyBlocklist (options: OverrideCommandOptions & { + account?: string + server?: string + }) { + const { account, server } = options + + const path = account + ? '/api/v1/users/me/blocklist/accounts' + : '/api/v1/users/me/blocklist/servers' + + return this.postBodyRequest({ + ...options, + + path, + fields: { + accountName: account, + host: server + }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + addToServerBlocklist (options: OverrideCommandOptions & { + account?: string + server?: string + }) { + const { account, server } = options + + const path = account + ? '/api/v1/server/blocklist/accounts' + : '/api/v1/server/blocklist/servers' + + return this.postBodyRequest({ + ...options, + + path, + fields: { + accountName: account, + host: server + }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + // --------------------------------------------------------------------------- + + removeFromMyBlocklist (options: OverrideCommandOptions & { + account?: string + server?: string + }) { + const { account, server } = options + + const path = account + ? '/api/v1/users/me/blocklist/accounts/' + account + : '/api/v1/users/me/blocklist/servers/' + server + + return this.deleteRequest({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + removeFromServerBlocklist (options: OverrideCommandOptions & { + account?: string + server?: string + }) { + const { account, server } = options + + const path = account + ? '/api/v1/server/blocklist/accounts/' + account + : '/api/v1/server/blocklist/servers/' + server + + return this.deleteRequest({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + private listBlocklist (options: ListBlocklistOptions, path: string) { + const { start, count, sort = '-createdAt' } = options + + return this.getRequestBody>({ + ...options, + + path, + query: { start, count, sort }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + +} diff --git a/shared/extra-utils/users/blocklist.ts b/shared/extra-utils/users/blocklist.ts deleted file mode 100644 index bdf7ee58a..000000000 --- a/shared/extra-utils/users/blocklist.ts +++ /dev/null @@ -1,238 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ - -import { makeGetRequest, makeDeleteRequest, makePostBodyRequest } from '../requests/requests' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' - -function getAccountBlocklistByAccount ( - url: string, - token: string, - start: number, - count: number, - sort = '-createdAt', - statusCodeExpected = HttpStatusCode.OK_200 -) { - const path = '/api/v1/users/me/blocklist/accounts' - - return makeGetRequest({ - url, - token, - query: { start, count, sort }, - path, - statusCodeExpected - }) -} - -function addAccountToAccountBlocklist ( - url: string, - token: string, - accountToBlock: string, - statusCodeExpected = HttpStatusCode.NO_CONTENT_204 -) { - const path = '/api/v1/users/me/blocklist/accounts' - - return makePostBodyRequest({ - url, - path, - token, - fields: { - accountName: accountToBlock - }, - statusCodeExpected - }) -} - -function removeAccountFromAccountBlocklist ( - url: string, - token: string, - accountToUnblock: string, - statusCodeExpected = HttpStatusCode.NO_CONTENT_204 -) { - const path = '/api/v1/users/me/blocklist/accounts/' + accountToUnblock - - return makeDeleteRequest({ - url, - path, - token, - statusCodeExpected - }) -} - -function getServerBlocklistByAccount ( - url: string, - token: string, - start: number, - count: number, - sort = '-createdAt', - statusCodeExpected = HttpStatusCode.OK_200 -) { - const path = '/api/v1/users/me/blocklist/servers' - - return makeGetRequest({ - url, - token, - query: { start, count, sort }, - path, - statusCodeExpected - }) -} - -function addServerToAccountBlocklist ( - url: string, - token: string, - serverToBlock: string, - statusCodeExpected = HttpStatusCode.NO_CONTENT_204 -) { - const path = '/api/v1/users/me/blocklist/servers' - - return makePostBodyRequest({ - url, - path, - token, - fields: { - host: serverToBlock - }, - statusCodeExpected - }) -} - -function removeServerFromAccountBlocklist ( - url: string, - token: string, - serverToBlock: string, - statusCodeExpected = HttpStatusCode.NO_CONTENT_204 -) { - const path = '/api/v1/users/me/blocklist/servers/' + serverToBlock - - return makeDeleteRequest({ - url, - path, - token, - statusCodeExpected - }) -} - -function getAccountBlocklistByServer ( - url: string, - token: string, - start: number, - count: number, - sort = '-createdAt', - statusCodeExpected = HttpStatusCode.OK_200 -) { - const path = '/api/v1/server/blocklist/accounts' - - return makeGetRequest({ - url, - token, - query: { start, count, sort }, - path, - statusCodeExpected - }) -} - -function addAccountToServerBlocklist ( - url: string, - token: string, - accountToBlock: string, - statusCodeExpected = HttpStatusCode.NO_CONTENT_204 -) { - const path = '/api/v1/server/blocklist/accounts' - - return makePostBodyRequest({ - url, - path, - token, - fields: { - accountName: accountToBlock - }, - statusCodeExpected - }) -} - -function removeAccountFromServerBlocklist ( - url: string, - token: string, - accountToUnblock: string, - statusCodeExpected = HttpStatusCode.NO_CONTENT_204 -) { - const path = '/api/v1/server/blocklist/accounts/' + accountToUnblock - - return makeDeleteRequest({ - url, - path, - token, - statusCodeExpected - }) -} - -function getServerBlocklistByServer ( - url: string, - token: string, - start: number, - count: number, - sort = '-createdAt', - statusCodeExpected = HttpStatusCode.OK_200 -) { - const path = '/api/v1/server/blocklist/servers' - - return makeGetRequest({ - url, - token, - query: { start, count, sort }, - path, - statusCodeExpected - }) -} - -function addServerToServerBlocklist ( - url: string, - token: string, - serverToBlock: string, - statusCodeExpected = HttpStatusCode.NO_CONTENT_204 -) { - const path = '/api/v1/server/blocklist/servers' - - return makePostBodyRequest({ - url, - path, - token, - fields: { - host: serverToBlock - }, - statusCodeExpected - }) -} - -function removeServerFromServerBlocklist ( - url: string, - token: string, - serverToBlock: string, - statusCodeExpected = HttpStatusCode.NO_CONTENT_204 -) { - const path = '/api/v1/server/blocklist/servers/' + serverToBlock - - return makeDeleteRequest({ - url, - path, - token, - statusCodeExpected - }) -} - -// --------------------------------------------------------------------------- - -export { - getAccountBlocklistByAccount, - addAccountToAccountBlocklist, - removeAccountFromAccountBlocklist, - getServerBlocklistByAccount, - addServerToAccountBlocklist, - removeServerFromAccountBlocklist, - - getAccountBlocklistByServer, - addAccountToServerBlocklist, - removeAccountFromServerBlocklist, - getServerBlocklistByServer, - addServerToServerBlocklist, - removeServerFromServerBlocklist -} diff --git a/shared/extra-utils/users/index.ts b/shared/extra-utils/users/index.ts new file mode 100644 index 000000000..460a06f70 --- /dev/null +++ b/shared/extra-utils/users/index.ts @@ -0,0 +1,9 @@ +export * from './accounts-command' +export * from './actors' +export * from './blocklist-command' +export * from './login' +export * from './login-command' +export * from './notifications' +export * from './notifications-command' +export * from './subscriptions-command' +export * from './users-command' diff --git a/shared/extra-utils/users/login-command.ts b/shared/extra-utils/users/login-command.ts new file mode 100644 index 000000000..143f72a59 --- /dev/null +++ b/shared/extra-utils/users/login-command.ts @@ -0,0 +1,132 @@ +import { HttpStatusCode, PeerTubeProblemDocument } from '@shared/models' +import { unwrapBody } from '../requests' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export class LoginCommand extends AbstractCommand { + + login (options: OverrideCommandOptions & { + client?: { id?: string, secret?: string } + user?: { username: string, password?: string } + } = {}) { + const { client = this.server.store.client, user = this.server.store.user } = options + const path = '/api/v1/users/token' + + const body = { + client_id: client.id, + client_secret: client.secret, + username: user.username, + password: user.password ?? 'password', + response_type: 'code', + grant_type: 'password', + scope: 'upload' + } + + return unwrapBody<{ access_token: string, refresh_token: string } & PeerTubeProblemDocument>(this.postBodyRequest({ + ...options, + + path, + requestType: 'form', + fields: body, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + })) + } + + getAccessToken (arg1?: { username: string, password?: string }): Promise + getAccessToken (arg1: string, password?: string): Promise + async getAccessToken (arg1?: { username: string, password?: string } | string, password?: string) { + let user: { username: string, password?: string } + + if (!arg1) user = this.server.store.user + else if (typeof arg1 === 'object') user = arg1 + else user = { username: arg1, password } + + try { + const body = await this.login({ user }) + + return body.access_token + } catch (err) { + throw new Error(`Cannot authenticate. Please check your username/password. (${err})`) + } + } + + loginUsingExternalToken (options: OverrideCommandOptions & { + username: string + externalAuthToken: string + }) { + const { username, externalAuthToken } = options + const path = '/api/v1/users/token' + + const body = { + client_id: this.server.store.client.id, + client_secret: this.server.store.client.secret, + username: username, + response_type: 'code', + grant_type: 'password', + scope: 'upload', + externalAuthToken + } + + return this.postBodyRequest({ + ...options, + + path, + requestType: 'form', + fields: body, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + logout (options: OverrideCommandOptions & { + token: string + }) { + const path = '/api/v1/users/revoke-token' + + return unwrapBody<{ redirectUrl: string }>(this.postBodyRequest({ + ...options, + + path, + requestType: 'form', + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + })) + } + + refreshToken (options: OverrideCommandOptions & { + refreshToken: string + }) { + const path = '/api/v1/users/token' + + const body = { + client_id: this.server.store.client.id, + client_secret: this.server.store.client.secret, + refresh_token: options.refreshToken, + response_type: 'code', + grant_type: 'refresh_token' + } + + return this.postBodyRequest({ + ...options, + + path, + requestType: 'form', + fields: body, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + getClient (options: OverrideCommandOptions = {}) { + const path = '/api/v1/oauth-clients/local' + + return this.getRequestBody<{ client_id: string, client_secret: string }>({ + ...options, + + path, + host: this.server.host, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } +} diff --git a/shared/extra-utils/users/login.ts b/shared/extra-utils/users/login.ts index 39e1a2747..f1df027d3 100644 --- a/shared/extra-utils/users/login.ts +++ b/shared/extra-utils/users/login.ts @@ -1,133 +1,19 @@ -import * as request from 'supertest' +import { PeerTubeServer } from '../server/server' -import { ServerInfo } from '../server/servers' -import { getClient } from '../server/clients' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' - -type Client = { id: string, secret: string } -type User = { username: string, password: string } -type Server = { url: string, client: Client, user: User } - -function login (url: string, client: Client, user: User, expectedStatus = HttpStatusCode.OK_200) { - const path = '/api/v1/users/token' - - const body = { - client_id: client.id, - client_secret: client.secret, - username: user.username, - password: user.password, - response_type: 'code', - grant_type: 'password', - scope: 'upload' - } - - return request(url) - .post(path) - .type('form') - .send(body) - .expect(expectedStatus) -} - -function logout (url: string, token: string, expectedStatus = HttpStatusCode.OK_200) { - const path = '/api/v1/users/revoke-token' - - return request(url) - .post(path) - .set('Authorization', 'Bearer ' + token) - .type('form') - .expect(expectedStatus) -} - -async function serverLogin (server: Server) { - const res = await login(server.url, server.client, server.user, HttpStatusCode.OK_200) - - return res.body.access_token as string -} - -function refreshToken (server: ServerInfo, refreshToken: string, expectedStatus = HttpStatusCode.OK_200) { - const path = '/api/v1/users/token' - - const body = { - client_id: server.client.id, - client_secret: server.client.secret, - refresh_token: refreshToken, - response_type: 'code', - grant_type: 'refresh_token' - } - - return request(server.url) - .post(path) - .type('form') - .send(body) - .expect(expectedStatus) -} - -async function userLogin (server: Server, user: User, expectedStatus = HttpStatusCode.OK_200) { - const res = await login(server.url, server.client, user, expectedStatus) - - return res.body.access_token as string -} - -async function getAccessToken (url: string, username: string, password: string) { - const resClient = await getClient(url) - const client = { - id: resClient.body.client_id, - secret: resClient.body.client_secret - } - - const user = { username, password } - - try { - const res = await login(url, client, user) - return res.body.access_token - } catch (err) { - throw new Error('Cannot authenticate. Please check your username/password.') - } -} - -function setAccessTokensToServers (servers: ServerInfo[]) { +function setAccessTokensToServers (servers: PeerTubeServer[]) { const tasks: Promise[] = [] for (const server of servers) { - const p = serverLogin(server).then(t => { server.accessToken = t }) + const p = server.login.getAccessToken() + .then(t => { server.accessToken = t }) tasks.push(p) } return Promise.all(tasks) } -function loginUsingExternalToken (server: Server, username: string, externalAuthToken: string, expectedStatus = HttpStatusCode.OK_200) { - const path = '/api/v1/users/token' - - const body = { - client_id: server.client.id, - client_secret: server.client.secret, - username: username, - response_type: 'code', - grant_type: 'password', - scope: 'upload', - externalAuthToken - } - - return request(server.url) - .post(path) - .type('form') - .send(body) - .expect(expectedStatus) -} - // --------------------------------------------------------------------------- export { - login, - logout, - serverLogin, - refreshToken, - userLogin, - getAccessToken, - setAccessTokensToServers, - Server, - Client, - User, - loginUsingExternalToken + setAccessTokensToServers } diff --git a/shared/extra-utils/users/notifications-command.ts b/shared/extra-utils/users/notifications-command.ts new file mode 100644 index 000000000..2d79a3747 --- /dev/null +++ b/shared/extra-utils/users/notifications-command.ts @@ -0,0 +1,86 @@ +import { HttpStatusCode, ResultList } from '@shared/models' +import { UserNotification, UserNotificationSetting } from '../../models/users' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export class NotificationsCommand extends AbstractCommand { + + updateMySettings (options: OverrideCommandOptions & { + settings: UserNotificationSetting + }) { + const path = '/api/v1/users/me/notification-settings' + + return this.putBodyRequest({ + ...options, + + path, + fields: options.settings, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + list (options: OverrideCommandOptions & { + start?: number + count?: number + unread?: boolean + sort?: string + }) { + const { start, count, unread, sort = '-createdAt' } = options + const path = '/api/v1/users/me/notifications' + + return this.getRequestBody>({ + ...options, + + path, + query: { + start, + count, + sort, + unread + }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + markAsRead (options: OverrideCommandOptions & { + ids: number[] + }) { + const { ids } = options + const path = '/api/v1/users/me/notifications/read' + + return this.postBodyRequest({ + ...options, + + path, + fields: { ids }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + markAsReadAll (options: OverrideCommandOptions) { + const path = '/api/v1/users/me/notifications/read-all' + + return this.postBodyRequest({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + async getLastest (options: OverrideCommandOptions = {}) { + const { total, data } = await this.list({ + ...options, + start: 0, + count: 1, + sort: '-createdAt' + }) + + if (total === 0) return undefined + + return data[0] + } +} diff --git a/shared/extra-utils/users/user-notifications.ts b/shared/extra-utils/users/notifications.ts similarity index 63% rename from shared/extra-utils/users/user-notifications.ts rename to shared/extra-utils/users/notifications.ts index 844f4442d..7db4bfd3f 100644 --- a/shared/extra-utils/users/user-notifications.ts +++ b/shared/extra-utils/users/notifications.ts @@ -3,91 +3,15 @@ import { expect } from 'chai' import { inspect } from 'util' import { AbuseState, PluginType } from '@shared/models' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' import { UserNotification, UserNotificationSetting, UserNotificationSettingValue, UserNotificationType } from '../../models/users' -import { MockSmtpServer } from '../miscs/email' -import { makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests' +import { MockSmtpServer } from '../mock-servers/mock-email' +import { PeerTubeServer } from '../server' import { doubleFollow } from '../server/follows' -import { flushAndRunMultipleServers, ServerInfo } from '../server/servers' -import { getUserNotificationSocket } from '../socket/socket-io' -import { setAccessTokensToServers, userLogin } from './login' -import { createUser, getMyUserInformation } from './users' - -function updateMyNotificationSettings ( - url: string, - token: string, - settings: UserNotificationSetting, - statusCodeExpected = HttpStatusCode.NO_CONTENT_204 -) { - const path = '/api/v1/users/me/notification-settings' - - return makePutBodyRequest({ - url, - path, - token, - fields: settings, - statusCodeExpected - }) -} - -async function getUserNotifications ( - url: string, - token: string, - start: number, - count: number, - unread?: boolean, - sort = '-createdAt', - statusCodeExpected = HttpStatusCode.OK_200 -) { - const path = '/api/v1/users/me/notifications' - - return makeGetRequest({ - url, - path, - token, - query: { - start, - count, - sort, - unread - }, - statusCodeExpected - }) -} - -function markAsReadNotifications (url: string, token: string, ids: number[], statusCodeExpected = HttpStatusCode.NO_CONTENT_204) { - const path = '/api/v1/users/me/notifications/read' - - return makePostBodyRequest({ - url, - path, - token, - fields: { ids }, - statusCodeExpected - }) -} - -function markAsReadAllNotifications (url: string, token: string, statusCodeExpected = HttpStatusCode.NO_CONTENT_204) { - const path = '/api/v1/users/me/notifications/read-all' - - return makePostBodyRequest({ - url, - path, - token, - statusCodeExpected - }) -} - -async function getLastNotification (serverUrl: string, accessToken: string) { - const res = await getUserNotifications(serverUrl, accessToken, 0, 1, undefined, '-createdAt') - - if (res.body.total === 0) return undefined - - return res.body.data[0] as UserNotification -} +import { createMultipleServers } from '../server/servers' +import { setAccessTokensToServers } from './login' type CheckerBaseParams = { - server: ServerInfo + server: PeerTubeServer emails: any[] socketNotifications: UserNotification[] token: string @@ -96,91 +20,41 @@ type CheckerBaseParams = { type CheckerType = 'presence' | 'absence' -async function checkNotification ( - base: CheckerBaseParams, - notificationChecker: (notification: UserNotification, type: CheckerType) => void, - emailNotificationFinder: (email: object) => boolean, - checkType: CheckerType -) { - const check = base.check || { web: true, mail: true } - - if (check.web) { - const notification = await getLastNotification(base.server.url, base.token) - - if (notification || checkType !== 'absence') { - notificationChecker(notification, checkType) - } - - const socketNotification = base.socketNotifications.find(n => { - try { - notificationChecker(n, 'presence') - return true - } catch { - return false - } - }) - - if (checkType === 'presence') { - const obj = inspect(base.socketNotifications, { depth: 5 }) - expect(socketNotification, 'The socket notification is absent when it should be present. ' + obj).to.not.be.undefined - } else { - const obj = inspect(socketNotification, { depth: 5 }) - expect(socketNotification, 'The socket notification is present when it should not be present. ' + obj).to.be.undefined - } - } - - if (check.mail) { - // Last email - const email = base.emails - .slice() - .reverse() - .find(e => emailNotificationFinder(e)) - - if (checkType === 'presence') { - const emails = base.emails.map(e => e.text) - expect(email, 'The email is absent when is should be present. ' + inspect(emails)).to.not.be.undefined - } else { - expect(email, 'The email is present when is should not be present. ' + inspect(email)).to.be.undefined - } - } -} - -function checkVideo (video: any, videoName?: string, videoUUID?: string) { - if (videoName) { - expect(video.name).to.be.a('string') - expect(video.name).to.not.be.empty - expect(video.name).to.equal(videoName) - } - - if (videoUUID) { - expect(video.uuid).to.be.a('string') - expect(video.uuid).to.not.be.empty - expect(video.uuid).to.equal(videoUUID) +function getAllNotificationsSettings (): UserNotificationSetting { + return { + newVideoFromSubscription: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, + newCommentOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, + abuseAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, + videoAutoBlacklistAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, + blacklistOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, + myVideoImportFinished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, + myVideoPublished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, + commentMention: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, + newFollow: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, + newUserRegistration: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, + newInstanceFollower: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, + abuseNewMessage: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, + abuseStateChange: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, + autoInstanceFollowing: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, + newPeerTubeVersion: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, + newPluginVersion: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL } - - expect(video.id).to.be.a('number') } -function checkActor (actor: any) { - expect(actor.displayName).to.be.a('string') - expect(actor.displayName).to.not.be.empty - expect(actor.host).to.not.be.undefined -} - -function checkComment (comment: any, commentId: number, threadId: number) { - expect(comment.id).to.equal(commentId) - expect(comment.threadId).to.equal(threadId) -} - -async function checkNewVideoFromSubscription (base: CheckerBaseParams, videoName: string, videoUUID: string, type: CheckerType) { +async function checkNewVideoFromSubscription (options: CheckerBaseParams & { + videoName: string + shortUUID: string + checkType: CheckerType +}) { + const { videoName, shortUUID } = options const notificationType = UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION - function notificationChecker (notification: UserNotification, type: CheckerType) { - if (type === 'presence') { + function notificationChecker (notification: UserNotification, checkType: CheckerType) { + if (checkType === 'presence') { expect(notification).to.not.be.undefined expect(notification.type).to.equal(notificationType) - checkVideo(notification.video, videoName, videoUUID) + checkVideo(notification.video, videoName, shortUUID) checkActor(notification.video.channel) } else { expect(notification).to.satisfy((n: UserNotification) => { @@ -191,21 +65,26 @@ async function checkNewVideoFromSubscription (base: CheckerBaseParams, videoName function emailNotificationFinder (email: object) { const text = email['text'] - return text.indexOf(videoUUID) !== -1 && text.indexOf('Your subscription') !== -1 + return text.indexOf(shortUUID) !== -1 && text.indexOf('Your subscription') !== -1 } - await checkNotification(base, notificationChecker, emailNotificationFinder, type) + await checkNotification({ ...options, notificationChecker, emailNotificationFinder }) } -async function checkVideoIsPublished (base: CheckerBaseParams, videoName: string, videoUUID: string, type: CheckerType) { +async function checkVideoIsPublished (options: CheckerBaseParams & { + videoName: string + shortUUID: string + checkType: CheckerType +}) { + const { videoName, shortUUID } = options const notificationType = UserNotificationType.MY_VIDEO_PUBLISHED - function notificationChecker (notification: UserNotification, type: CheckerType) { - if (type === 'presence') { + function notificationChecker (notification: UserNotification, checkType: CheckerType) { + if (checkType === 'presence') { expect(notification).to.not.be.undefined expect(notification.type).to.equal(notificationType) - checkVideo(notification.video, videoName, videoUUID) + checkVideo(notification.video, videoName, shortUUID) checkActor(notification.video.channel) } else { expect(notification.video).to.satisfy(v => v === undefined || v.name !== videoName) @@ -214,30 +93,31 @@ async function checkVideoIsPublished (base: CheckerBaseParams, videoName: string function emailNotificationFinder (email: object) { const text: string = email['text'] - return text.includes(videoUUID) && text.includes('Your video') + return text.includes(shortUUID) && text.includes('Your video') } - await checkNotification(base, notificationChecker, emailNotificationFinder, type) + await checkNotification({ ...options, notificationChecker, emailNotificationFinder }) } -async function checkMyVideoImportIsFinished ( - base: CheckerBaseParams, - videoName: string, - videoUUID: string, - url: string, - success: boolean, - type: CheckerType -) { +async function checkMyVideoImportIsFinished (options: CheckerBaseParams & { + videoName: string + shortUUID: string + url: string + success: boolean + checkType: CheckerType +}) { + const { videoName, shortUUID, url, success } = options + const notificationType = success ? UserNotificationType.MY_VIDEO_IMPORT_SUCCESS : UserNotificationType.MY_VIDEO_IMPORT_ERROR - function notificationChecker (notification: UserNotification, type: CheckerType) { - if (type === 'presence') { + function notificationChecker (notification: UserNotification, checkType: CheckerType) { + if (checkType === 'presence') { expect(notification).to.not.be.undefined expect(notification.type).to.equal(notificationType) expect(notification.videoImport.targetUrl).to.equal(url) - if (success) checkVideo(notification.videoImport.video, videoName, videoUUID) + if (success) checkVideo(notification.videoImport.video, videoName, shortUUID) } else { expect(notification.videoImport).to.satisfy(i => i === undefined || i.targetUrl !== url) } @@ -250,14 +130,18 @@ async function checkMyVideoImportIsFinished ( return text.includes(url) && text.includes(toFind) } - await checkNotification(base, notificationChecker, emailNotificationFinder, type) + await checkNotification({ ...options, notificationChecker, emailNotificationFinder }) } -async function checkUserRegistered (base: CheckerBaseParams, username: string, type: CheckerType) { +async function checkUserRegistered (options: CheckerBaseParams & { + username: string + checkType: CheckerType +}) { + const { username } = options const notificationType = UserNotificationType.NEW_USER_REGISTRATION - function notificationChecker (notification: UserNotification, type: CheckerType) { - if (type === 'presence') { + function notificationChecker (notification: UserNotification, checkType: CheckerType) { + if (checkType === 'presence') { expect(notification).to.not.be.undefined expect(notification.type).to.equal(notificationType) @@ -274,21 +158,21 @@ async function checkUserRegistered (base: CheckerBaseParams, username: string, t return text.includes(' registered.') && text.includes(username) } - await checkNotification(base, notificationChecker, emailNotificationFinder, type) + await checkNotification({ ...options, notificationChecker, emailNotificationFinder }) } -async function checkNewActorFollow ( - base: CheckerBaseParams, - followType: 'channel' | 'account', - followerName: string, - followerDisplayName: string, - followingDisplayName: string, - type: CheckerType -) { +async function checkNewActorFollow (options: CheckerBaseParams & { + followType: 'channel' | 'account' + followerName: string + followerDisplayName: string + followingDisplayName: string + checkType: CheckerType +}) { + const { followType, followerName, followerDisplayName, followingDisplayName } = options const notificationType = UserNotificationType.NEW_FOLLOW - function notificationChecker (notification: UserNotification, type: CheckerType) { - if (type === 'presence') { + function notificationChecker (notification: UserNotification, checkType: CheckerType) { + if (checkType === 'presence') { expect(notification).to.not.be.undefined expect(notification.type).to.equal(notificationType) @@ -314,14 +198,18 @@ async function checkNewActorFollow ( return text.includes(followType) && text.includes(followingDisplayName) && text.includes(followerDisplayName) } - await checkNotification(base, notificationChecker, emailNotificationFinder, type) + await checkNotification({ ...options, notificationChecker, emailNotificationFinder }) } -async function checkNewInstanceFollower (base: CheckerBaseParams, followerHost: string, type: CheckerType) { +async function checkNewInstanceFollower (options: CheckerBaseParams & { + followerHost: string + checkType: CheckerType +}) { + const { followerHost } = options const notificationType = UserNotificationType.NEW_INSTANCE_FOLLOWER - function notificationChecker (notification: UserNotification, type: CheckerType) { - if (type === 'presence') { + function notificationChecker (notification: UserNotification, checkType: CheckerType) { + if (checkType === 'presence') { expect(notification).to.not.be.undefined expect(notification.type).to.equal(notificationType) @@ -343,14 +231,19 @@ async function checkNewInstanceFollower (base: CheckerBaseParams, followerHost: return text.includes('instance has a new follower') && text.includes(followerHost) } - await checkNotification(base, notificationChecker, emailNotificationFinder, type) + await checkNotification({ ...options, notificationChecker, emailNotificationFinder }) } -async function checkAutoInstanceFollowing (base: CheckerBaseParams, followerHost: string, followingHost: string, type: CheckerType) { +async function checkAutoInstanceFollowing (options: CheckerBaseParams & { + followerHost: string + followingHost: string + checkType: CheckerType +}) { + const { followerHost, followingHost } = options const notificationType = UserNotificationType.AUTO_INSTANCE_FOLLOWING - function notificationChecker (notification: UserNotification, type: CheckerType) { - if (type === 'presence') { + function notificationChecker (notification: UserNotification, checkType: CheckerType) { + if (checkType === 'presence') { expect(notification).to.not.be.undefined expect(notification.type).to.equal(notificationType) @@ -374,21 +267,21 @@ async function checkAutoInstanceFollowing (base: CheckerBaseParams, followerHost return text.includes(' automatically followed a new instance') && text.includes(followingHost) } - await checkNotification(base, notificationChecker, emailNotificationFinder, type) + await checkNotification({ ...options, notificationChecker, emailNotificationFinder }) } -async function checkCommentMention ( - base: CheckerBaseParams, - uuid: string, - commentId: number, - threadId: number, - byAccountDisplayName: string, - type: CheckerType -) { +async function checkCommentMention (options: CheckerBaseParams & { + shortUUID: string + commentId: number + threadId: number + byAccountDisplayName: string + checkType: CheckerType +}) { + const { shortUUID, commentId, threadId, byAccountDisplayName } = options const notificationType = UserNotificationType.COMMENT_MENTION - function notificationChecker (notification: UserNotification, type: CheckerType) { - if (type === 'presence') { + function notificationChecker (notification: UserNotification, checkType: CheckerType) { + if (checkType === 'presence') { expect(notification).to.not.be.undefined expect(notification.type).to.equal(notificationType) @@ -396,7 +289,7 @@ async function checkCommentMention ( checkActor(notification.comment.account) expect(notification.comment.account.displayName).to.equal(byAccountDisplayName) - checkVideo(notification.comment.video, undefined, uuid) + checkVideo(notification.comment.video, undefined, shortUUID) } else { expect(notification).to.satisfy(n => n.type !== notificationType || n.comment.id !== commentId) } @@ -405,25 +298,31 @@ async function checkCommentMention ( function emailNotificationFinder (email: object) { const text: string = email['text'] - return text.includes(' mentioned ') && text.includes(uuid) && text.includes(byAccountDisplayName) + return text.includes(' mentioned ') && text.includes(shortUUID) && text.includes(byAccountDisplayName) } - await checkNotification(base, notificationChecker, emailNotificationFinder, type) + await checkNotification({ ...options, notificationChecker, emailNotificationFinder }) } let lastEmailCount = 0 -async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string, commentId: number, threadId: number, type: CheckerType) { +async function checkNewCommentOnMyVideo (options: CheckerBaseParams & { + shortUUID: string + commentId: number + threadId: number + checkType: CheckerType +}) { + const { server, shortUUID, commentId, threadId, checkType, emails } = options const notificationType = UserNotificationType.NEW_COMMENT_ON_MY_VIDEO - function notificationChecker (notification: UserNotification, type: CheckerType) { - if (type === 'presence') { + function notificationChecker (notification: UserNotification, checkType: CheckerType) { + if (checkType === 'presence') { expect(notification).to.not.be.undefined expect(notification.type).to.equal(notificationType) checkComment(notification.comment, commentId, threadId) checkActor(notification.comment.account) - checkVideo(notification.comment.video, undefined, uuid) + checkVideo(notification.comment.video, undefined, shortUUID) } else { expect(notification).to.satisfy((n: UserNotification) => { return n === undefined || n.comment === undefined || n.comment.id !== commentId @@ -431,51 +330,62 @@ async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string, } } - const commentUrl = `http://localhost:${base.server.port}/w/${uuid};threadId=${threadId}` + const commentUrl = `http://localhost:${server.port}/w/${shortUUID};threadId=${threadId}` function emailNotificationFinder (email: object) { return email['text'].indexOf(commentUrl) !== -1 } - await checkNotification(base, notificationChecker, emailNotificationFinder, type) + await checkNotification({ ...options, notificationChecker, emailNotificationFinder }) - if (type === 'presence') { + if (checkType === 'presence') { // We cannot detect email duplicates, so check we received another email - expect(base.emails).to.have.length.above(lastEmailCount) - lastEmailCount = base.emails.length + expect(emails).to.have.length.above(lastEmailCount) + lastEmailCount = emails.length } } -async function checkNewVideoAbuseForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) { +async function checkNewVideoAbuseForModerators (options: CheckerBaseParams & { + shortUUID: string + videoName: string + checkType: CheckerType +}) { + const { shortUUID, videoName } = options const notificationType = UserNotificationType.NEW_ABUSE_FOR_MODERATORS - function notificationChecker (notification: UserNotification, type: CheckerType) { - if (type === 'presence') { + function notificationChecker (notification: UserNotification, checkType: CheckerType) { + if (checkType === 'presence') { expect(notification).to.not.be.undefined expect(notification.type).to.equal(notificationType) expect(notification.abuse.id).to.be.a('number') - checkVideo(notification.abuse.video, videoName, videoUUID) + checkVideo(notification.abuse.video, videoName, shortUUID) } else { expect(notification).to.satisfy((n: UserNotification) => { - return n === undefined || n.abuse === undefined || n.abuse.video.uuid !== videoUUID + return n === undefined || n.abuse === undefined || n.abuse.video.shortUUID !== shortUUID }) } } function emailNotificationFinder (email: object) { const text = email['text'] - return text.indexOf(videoUUID) !== -1 && text.indexOf('abuse') !== -1 + return text.indexOf(shortUUID) !== -1 && text.indexOf('abuse') !== -1 } - await checkNotification(base, notificationChecker, emailNotificationFinder, type) + await checkNotification({ ...options, notificationChecker, emailNotificationFinder }) } -async function checkNewAbuseMessage (base: CheckerBaseParams, abuseId: number, message: string, toEmail: string, type: CheckerType) { +async function checkNewAbuseMessage (options: CheckerBaseParams & { + abuseId: number + message: string + toEmail: string + checkType: CheckerType +}) { + const { abuseId, message, toEmail } = options const notificationType = UserNotificationType.ABUSE_NEW_MESSAGE - function notificationChecker (notification: UserNotification, type: CheckerType) { - if (type === 'presence') { + function notificationChecker (notification: UserNotification, checkType: CheckerType) { + if (checkType === 'presence') { expect(notification).to.not.be.undefined expect(notification.type).to.equal(notificationType) @@ -494,14 +404,19 @@ async function checkNewAbuseMessage (base: CheckerBaseParams, abuseId: number, m return text.indexOf(message) !== -1 && to.length !== 0 } - await checkNotification(base, notificationChecker, emailNotificationFinder, type) + await checkNotification({ ...options, notificationChecker, emailNotificationFinder }) } -async function checkAbuseStateChange (base: CheckerBaseParams, abuseId: number, state: AbuseState, type: CheckerType) { +async function checkAbuseStateChange (options: CheckerBaseParams & { + abuseId: number + state: AbuseState + checkType: CheckerType +}) { + const { abuseId, state } = options const notificationType = UserNotificationType.ABUSE_STATE_CHANGE - function notificationChecker (notification: UserNotification, type: CheckerType) { - if (type === 'presence') { + function notificationChecker (notification: UserNotification, checkType: CheckerType) { + if (checkType === 'presence') { expect(notification).to.not.be.undefined expect(notification.type).to.equal(notificationType) @@ -524,39 +439,48 @@ async function checkAbuseStateChange (base: CheckerBaseParams, abuseId: number, return text.indexOf(contains) !== -1 } - await checkNotification(base, notificationChecker, emailNotificationFinder, type) + await checkNotification({ ...options, notificationChecker, emailNotificationFinder }) } -async function checkNewCommentAbuseForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) { +async function checkNewCommentAbuseForModerators (options: CheckerBaseParams & { + shortUUID: string + videoName: string + checkType: CheckerType +}) { + const { shortUUID, videoName } = options const notificationType = UserNotificationType.NEW_ABUSE_FOR_MODERATORS - function notificationChecker (notification: UserNotification, type: CheckerType) { - if (type === 'presence') { + function notificationChecker (notification: UserNotification, checkType: CheckerType) { + if (checkType === 'presence') { expect(notification).to.not.be.undefined expect(notification.type).to.equal(notificationType) expect(notification.abuse.id).to.be.a('number') - checkVideo(notification.abuse.comment.video, videoName, videoUUID) + checkVideo(notification.abuse.comment.video, videoName, shortUUID) } else { expect(notification).to.satisfy((n: UserNotification) => { - return n === undefined || n.abuse === undefined || n.abuse.comment.video.uuid !== videoUUID + return n === undefined || n.abuse === undefined || n.abuse.comment.video.shortUUID !== shortUUID }) } } function emailNotificationFinder (email: object) { const text = email['text'] - return text.indexOf(videoUUID) !== -1 && text.indexOf('abuse') !== -1 + return text.indexOf(shortUUID) !== -1 && text.indexOf('abuse') !== -1 } - await checkNotification(base, notificationChecker, emailNotificationFinder, type) + await checkNotification({ ...options, notificationChecker, emailNotificationFinder }) } -async function checkNewAccountAbuseForModerators (base: CheckerBaseParams, displayName: string, type: CheckerType) { +async function checkNewAccountAbuseForModerators (options: CheckerBaseParams & { + displayName: string + checkType: CheckerType +}) { + const { displayName } = options const notificationType = UserNotificationType.NEW_ABUSE_FOR_MODERATORS - function notificationChecker (notification: UserNotification, type: CheckerType) { - if (type === 'presence') { + function notificationChecker (notification: UserNotification, checkType: CheckerType) { + if (checkType === 'presence') { expect(notification).to.not.be.undefined expect(notification.type).to.equal(notificationType) @@ -574,40 +498,45 @@ async function checkNewAccountAbuseForModerators (base: CheckerBaseParams, displ return text.indexOf(displayName) !== -1 && text.indexOf('abuse') !== -1 } - await checkNotification(base, notificationChecker, emailNotificationFinder, type) + await checkNotification({ ...options, notificationChecker, emailNotificationFinder }) } -async function checkVideoAutoBlacklistForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) { +async function checkVideoAutoBlacklistForModerators (options: CheckerBaseParams & { + shortUUID: string + videoName: string + checkType: CheckerType +}) { + const { shortUUID, videoName } = options const notificationType = UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS - function notificationChecker (notification: UserNotification, type: CheckerType) { - if (type === 'presence') { + function notificationChecker (notification: UserNotification, checkType: CheckerType) { + if (checkType === 'presence') { expect(notification).to.not.be.undefined expect(notification.type).to.equal(notificationType) expect(notification.videoBlacklist.video.id).to.be.a('number') - checkVideo(notification.videoBlacklist.video, videoName, videoUUID) + checkVideo(notification.videoBlacklist.video, videoName, shortUUID) } else { expect(notification).to.satisfy((n: UserNotification) => { - return n === undefined || n.video === undefined || n.video.uuid !== videoUUID + return n === undefined || n.video === undefined || n.video.shortUUID !== shortUUID }) } } function emailNotificationFinder (email: object) { const text = email['text'] - return text.indexOf(videoUUID) !== -1 && email['text'].indexOf('video-auto-blacklist/list') !== -1 + return text.indexOf(shortUUID) !== -1 && email['text'].indexOf('video-auto-blacklist/list') !== -1 } - await checkNotification(base, notificationChecker, emailNotificationFinder, type) + await checkNotification({ ...options, notificationChecker, emailNotificationFinder }) } -async function checkNewBlacklistOnMyVideo ( - base: CheckerBaseParams, - videoUUID: string, - videoName: string, +async function checkNewBlacklistOnMyVideo (options: CheckerBaseParams & { + shortUUID: string + videoName: string blacklistType: 'blacklist' | 'unblacklist' -) { +}) { + const { videoName, shortUUID, blacklistType } = options const notificationType = blacklistType === 'blacklist' ? UserNotificationType.BLACKLIST_ON_MY_VIDEO : UserNotificationType.UNBLACKLIST_ON_MY_VIDEO @@ -618,22 +547,30 @@ async function checkNewBlacklistOnMyVideo ( const video = blacklistType === 'blacklist' ? notification.videoBlacklist.video : notification.video - checkVideo(video, videoName, videoUUID) + checkVideo(video, videoName, shortUUID) } function emailNotificationFinder (email: object) { const text = email['text'] - return text.indexOf(videoUUID) !== -1 && text.indexOf(' ' + blacklistType) !== -1 + const blacklistText = blacklistType === 'blacklist' + ? 'blacklisted' + : 'unblacklisted' + + return text.includes(shortUUID) && text.includes(blacklistText) } - await checkNotification(base, notificationChecker, emailNotificationFinder, 'presence') + await checkNotification({ ...options, notificationChecker, emailNotificationFinder, checkType: 'presence' }) } -async function checkNewPeerTubeVersion (base: CheckerBaseParams, latestVersion: string, type: CheckerType) { +async function checkNewPeerTubeVersion (options: CheckerBaseParams & { + latestVersion: string + checkType: CheckerType +}) { + const { latestVersion } = options const notificationType = UserNotificationType.NEW_PEERTUBE_VERSION - function notificationChecker (notification: UserNotification, type: CheckerType) { - if (type === 'presence') { + function notificationChecker (notification: UserNotification, checkType: CheckerType) { + if (checkType === 'presence') { expect(notification).to.not.be.undefined expect(notification.type).to.equal(notificationType) @@ -652,14 +589,19 @@ async function checkNewPeerTubeVersion (base: CheckerBaseParams, latestVersion: return text.includes(latestVersion) } - await checkNotification(base, notificationChecker, emailNotificationFinder, type) + await checkNotification({ ...options, notificationChecker, emailNotificationFinder }) } -async function checkNewPluginVersion (base: CheckerBaseParams, pluginType: PluginType, pluginName: string, type: CheckerType) { +async function checkNewPluginVersion (options: CheckerBaseParams & { + pluginType: PluginType + pluginName: string + checkType: CheckerType +}) { + const { pluginName, pluginType } = options const notificationType = UserNotificationType.NEW_PLUGIN_VERSION - function notificationChecker (notification: UserNotification, type: CheckerType) { - if (type === 'presence') { + function notificationChecker (notification: UserNotification, checkType: CheckerType) { + if (checkType === 'presence') { expect(notification).to.not.be.undefined expect(notification.type).to.equal(notificationType) @@ -678,28 +620,7 @@ async function checkNewPluginVersion (base: CheckerBaseParams, pluginType: Plugi return text.includes(pluginName) } - await checkNotification(base, notificationChecker, emailNotificationFinder, type) -} - -function getAllNotificationsSettings (): UserNotificationSetting { - return { - newVideoFromSubscription: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, - newCommentOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, - abuseAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, - videoAutoBlacklistAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, - blacklistOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, - myVideoImportFinished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, - myVideoPublished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, - commentMention: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, - newFollow: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, - newUserRegistration: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, - newInstanceFollower: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, - abuseNewMessage: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, - abuseStateChange: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, - autoInstanceFollowing: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, - newPeerTubeVersion: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, - newPluginVersion: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL - } + await checkNotification({ ...options, notificationChecker, emailNotificationFinder }) } async function prepareNotificationsTest (serversCount = 3, overrideConfigArg: any = {}) { @@ -719,7 +640,7 @@ async function prepareNotificationsTest (serversCount = 3, overrideConfigArg: an limit: 20 } } - const servers = await flushAndRunMultipleServers(serversCount, Object.assign(overrideConfig, overrideConfigArg)) + const servers = await createMultipleServers(serversCount, Object.assign(overrideConfig, overrideConfigArg)) await setAccessTokensToServers(servers) @@ -727,42 +648,33 @@ async function prepareNotificationsTest (serversCount = 3, overrideConfigArg: an await doubleFollow(servers[0], servers[1]) } - const user = { - username: 'user_1', - password: 'super password' - } - await createUser({ - url: servers[0].url, - accessToken: servers[0].accessToken, - username: user.username, - password: user.password, - videoQuota: 10 * 1000 * 1000 - }) - const userAccessToken = await userLogin(servers[0], user) + const user = { username: 'user_1', password: 'super password' } + await servers[0].users.create({ ...user, videoQuota: 10 * 1000 * 1000 }) + const userAccessToken = await servers[0].login.getAccessToken(user) - await updateMyNotificationSettings(servers[0].url, userAccessToken, getAllNotificationsSettings()) - await updateMyNotificationSettings(servers[0].url, servers[0].accessToken, getAllNotificationsSettings()) + await servers[0].notifications.updateMySettings({ token: userAccessToken, settings: getAllNotificationsSettings() }) + await servers[0].notifications.updateMySettings({ settings: getAllNotificationsSettings() }) if (serversCount > 1) { - await updateMyNotificationSettings(servers[1].url, servers[1].accessToken, getAllNotificationsSettings()) + await servers[1].notifications.updateMySettings({ settings: getAllNotificationsSettings() }) } { - const socket = getUserNotificationSocket(servers[0].url, userAccessToken) + const socket = servers[0].socketIO.getUserNotificationSocket({ token: userAccessToken }) socket.on('new-notification', n => userNotifications.push(n)) } { - const socket = getUserNotificationSocket(servers[0].url, servers[0].accessToken) + const socket = servers[0].socketIO.getUserNotificationSocket() socket.on('new-notification', n => adminNotifications.push(n)) } if (serversCount > 1) { - const socket = getUserNotificationSocket(servers[1].url, servers[1].accessToken) + const socket = servers[1].socketIO.getUserNotificationSocket() socket.on('new-notification', n => adminNotificationsServer2.push(n)) } - const resChannel = await getMyUserInformation(servers[0].url, servers[0].accessToken) - const channelId = resChannel.body.videoChannels[0].id + const { videoChannels } = await servers[0].users.getMyInfo() + const channelId = videoChannels[0].id return { userNotifications, @@ -778,11 +690,10 @@ async function prepareNotificationsTest (serversCount = 3, overrideConfigArg: an // --------------------------------------------------------------------------- export { + getAllNotificationsSettings, + CheckerBaseParams, CheckerType, - getAllNotificationsSettings, - checkNotification, - markAsReadAllNotifications, checkMyVideoImportIsFinished, checkUserRegistered, checkAutoInstanceFollowing, @@ -792,14 +703,10 @@ export { checkNewCommentOnMyVideo, checkNewBlacklistOnMyVideo, checkCommentMention, - updateMyNotificationSettings, checkNewVideoAbuseForModerators, checkVideoAutoBlacklistForModerators, checkNewAbuseMessage, checkAbuseStateChange, - getUserNotifications, - markAsReadNotifications, - getLastNotification, checkNewInstanceFollower, prepareNotificationsTest, checkNewCommentAbuseForModerators, @@ -807,3 +714,82 @@ export { checkNewPeerTubeVersion, checkNewPluginVersion } + +// --------------------------------------------------------------------------- + +async function checkNotification (options: CheckerBaseParams & { + notificationChecker: (notification: UserNotification, checkType: CheckerType) => void + emailNotificationFinder: (email: object) => boolean + checkType: CheckerType +}) { + const { server, token, checkType, notificationChecker, emailNotificationFinder, socketNotifications, emails } = options + + const check = options.check || { web: true, mail: true } + + if (check.web) { + const notification = await server.notifications.getLastest({ token: token }) + + if (notification || checkType !== 'absence') { + notificationChecker(notification, checkType) + } + + const socketNotification = socketNotifications.find(n => { + try { + notificationChecker(n, 'presence') + return true + } catch { + return false + } + }) + + if (checkType === 'presence') { + const obj = inspect(socketNotifications, { depth: 5 }) + expect(socketNotification, 'The socket notification is absent when it should be present. ' + obj).to.not.be.undefined + } else { + const obj = inspect(socketNotification, { depth: 5 }) + expect(socketNotification, 'The socket notification is present when it should not be present. ' + obj).to.be.undefined + } + } + + if (check.mail) { + // Last email + const email = emails + .slice() + .reverse() + .find(e => emailNotificationFinder(e)) + + if (checkType === 'presence') { + const texts = emails.map(e => e.text) + expect(email, 'The email is absent when is should be present. ' + inspect(texts)).to.not.be.undefined + } else { + expect(email, 'The email is present when is should not be present. ' + inspect(email)).to.be.undefined + } + } +} + +function checkVideo (video: any, videoName?: string, shortUUID?: string) { + if (videoName) { + expect(video.name).to.be.a('string') + expect(video.name).to.not.be.empty + expect(video.name).to.equal(videoName) + } + + if (shortUUID) { + expect(video.shortUUID).to.be.a('string') + expect(video.shortUUID).to.not.be.empty + expect(video.shortUUID).to.equal(shortUUID) + } + + expect(video.id).to.be.a('number') +} + +function checkActor (actor: any) { + expect(actor.displayName).to.be.a('string') + expect(actor.displayName).to.not.be.empty + expect(actor.host).to.not.be.undefined +} + +function checkComment (comment: any, commentId: number, threadId: number) { + expect(comment.id).to.equal(commentId) + expect(comment.threadId).to.equal(threadId) +} diff --git a/shared/extra-utils/users/subscriptions-command.ts b/shared/extra-utils/users/subscriptions-command.ts new file mode 100644 index 000000000..edc60e612 --- /dev/null +++ b/shared/extra-utils/users/subscriptions-command.ts @@ -0,0 +1,99 @@ +import { HttpStatusCode, ResultList, Video, VideoChannel } from '@shared/models' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export class SubscriptionsCommand extends AbstractCommand { + + add (options: OverrideCommandOptions & { + targetUri: string + }) { + const path = '/api/v1/users/me/subscriptions' + + return this.postBodyRequest({ + ...options, + + path, + fields: { uri: options.targetUri }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + list (options: OverrideCommandOptions & { + sort?: string // default -createdAt + search?: string + } = {}) { + const { sort = '-createdAt', search } = options + const path = '/api/v1/users/me/subscriptions' + + return this.getRequestBody>({ + ...options, + + path, + query: { + sort, + search + }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + listVideos (options: OverrideCommandOptions & { + sort?: string // default -createdAt + } = {}) { + const { sort = '-createdAt' } = options + const path = '/api/v1/users/me/subscriptions/videos' + + return this.getRequestBody>({ + ...options, + + path, + query: { sort }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + get (options: OverrideCommandOptions & { + uri: string + }) { + const path = '/api/v1/users/me/subscriptions/' + options.uri + + return this.getRequestBody({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + remove (options: OverrideCommandOptions & { + uri: string + }) { + const path = '/api/v1/users/me/subscriptions/' + options.uri + + return this.deleteRequest({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + exist (options: OverrideCommandOptions & { + uris: string[] + }) { + const path = '/api/v1/users/me/subscriptions/exist' + + return this.getRequestBody<{ [id: string ]: boolean }>({ + ...options, + + path, + query: { 'uris[]': options.uris }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } +} diff --git a/shared/extra-utils/users/user-subscriptions.ts b/shared/extra-utils/users/user-subscriptions.ts deleted file mode 100644 index edc7a3562..000000000 --- a/shared/extra-utils/users/user-subscriptions.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { makeDeleteRequest, makeGetRequest, makePostBodyRequest } from '../requests/requests' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' - -function addUserSubscription (url: string, token: string, targetUri: string, statusCodeExpected = HttpStatusCode.NO_CONTENT_204) { - const path = '/api/v1/users/me/subscriptions' - - return makePostBodyRequest({ - url, - path, - token, - statusCodeExpected, - fields: { uri: targetUri } - }) -} - -function listUserSubscriptions (parameters: { - url: string - token: string - sort?: string - search?: string - statusCodeExpected?: number -}) { - const { url, token, sort = '-createdAt', search, statusCodeExpected = HttpStatusCode.OK_200 } = parameters - const path = '/api/v1/users/me/subscriptions' - - return makeGetRequest({ - url, - path, - token, - statusCodeExpected, - query: { - sort, - search - } - }) -} - -function listUserSubscriptionVideos (url: string, token: string, sort = '-createdAt', statusCodeExpected = HttpStatusCode.OK_200) { - const path = '/api/v1/users/me/subscriptions/videos' - - return makeGetRequest({ - url, - path, - token, - statusCodeExpected, - query: { sort } - }) -} - -function getUserSubscription (url: string, token: string, uri: string, statusCodeExpected = HttpStatusCode.OK_200) { - const path = '/api/v1/users/me/subscriptions/' + uri - - return makeGetRequest({ - url, - path, - token, - statusCodeExpected - }) -} - -function removeUserSubscription (url: string, token: string, uri: string, statusCodeExpected = HttpStatusCode.NO_CONTENT_204) { - const path = '/api/v1/users/me/subscriptions/' + uri - - return makeDeleteRequest({ - url, - path, - token, - statusCodeExpected - }) -} - -function areSubscriptionsExist (url: string, token: string, uris: string[], statusCodeExpected = HttpStatusCode.OK_200) { - const path = '/api/v1/users/me/subscriptions/exist' - - return makeGetRequest({ - url, - path, - query: { 'uris[]': uris }, - token, - statusCodeExpected - }) -} - -// --------------------------------------------------------------------------- - -export { - areSubscriptionsExist, - addUserSubscription, - listUserSubscriptions, - getUserSubscription, - listUserSubscriptionVideos, - removeUserSubscription -} diff --git a/shared/extra-utils/users/users-command.ts b/shared/extra-utils/users/users-command.ts new file mode 100644 index 000000000..ddd20d041 --- /dev/null +++ b/shared/extra-utils/users/users-command.ts @@ -0,0 +1,415 @@ +import { omit } from 'lodash' +import { pick } from '@shared/core-utils' +import { + HttpStatusCode, + MyUser, + ResultList, + User, + UserAdminFlag, + UserCreateResult, + UserRole, + UserUpdate, + UserUpdateMe, + UserVideoQuota, + UserVideoRate +} from '@shared/models' +import { ScopedToken } from '@shared/models/users/user-scoped-token' +import { unwrapBody } from '../requests' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export class UsersCommand extends AbstractCommand { + + askResetPassword (options: OverrideCommandOptions & { + email: string + }) { + const { email } = options + const path = '/api/v1/users/ask-reset-password' + + return this.postBodyRequest({ + ...options, + + path, + fields: { email }, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + resetPassword (options: OverrideCommandOptions & { + userId: number + verificationString: string + password: string + }) { + const { userId, verificationString, password } = options + const path = '/api/v1/users/' + userId + '/reset-password' + + return this.postBodyRequest({ + ...options, + + path, + fields: { password, verificationString }, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + // --------------------------------------------------------------------------- + + askSendVerifyEmail (options: OverrideCommandOptions & { + email: string + }) { + const { email } = options + const path = '/api/v1/users/ask-send-verify-email' + + return this.postBodyRequest({ + ...options, + + path, + fields: { email }, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + verifyEmail (options: OverrideCommandOptions & { + userId: number + verificationString: string + isPendingEmail?: boolean // default false + }) { + const { userId, verificationString, isPendingEmail = false } = options + const path = '/api/v1/users/' + userId + '/verify-email' + + return this.postBodyRequest({ + ...options, + + path, + fields: { + verificationString, + isPendingEmail + }, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + // --------------------------------------------------------------------------- + + banUser (options: OverrideCommandOptions & { + userId: number + reason?: string + }) { + const { userId, reason } = options + const path = '/api/v1/users' + '/' + userId + '/block' + + return this.postBodyRequest({ + ...options, + + path, + fields: { reason }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + unbanUser (options: OverrideCommandOptions & { + userId: number + }) { + const { userId } = options + const path = '/api/v1/users' + '/' + userId + '/unblock' + + return this.postBodyRequest({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + // --------------------------------------------------------------------------- + + getMyScopedTokens (options: OverrideCommandOptions = {}) { + const path = '/api/v1/users/scoped-tokens' + + return this.getRequestBody({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + renewMyScopedTokens (options: OverrideCommandOptions = {}) { + const path = '/api/v1/users/scoped-tokens' + + return this.postBodyRequest({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + // --------------------------------------------------------------------------- + + create (options: OverrideCommandOptions & { + username: string + password?: string + videoQuota?: number + videoQuotaDaily?: number + role?: UserRole + adminFlags?: UserAdminFlag + }) { + const { + username, + adminFlags, + password = 'password', + videoQuota = 42000000, + videoQuotaDaily = -1, + role = UserRole.USER + } = options + + const path = '/api/v1/users' + + return unwrapBody<{ user: UserCreateResult }>(this.postBodyRequest({ + ...options, + + path, + fields: { + username, + password, + role, + adminFlags, + email: username + '@example.com', + videoQuota, + videoQuotaDaily + }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + })).then(res => res.user) + } + + async generate (username: string) { + const password = 'password' + const user = await this.create({ username, password }) + + const token = await this.server.login.getAccessToken({ username, password }) + + const me = await this.getMyInfo({ token }) + + return { + token, + userId: user.id, + userChannelId: me.videoChannels[0].id + } + } + + async generateUserAndToken (username: string) { + const password = 'password' + await this.create({ username, password }) + + return this.server.login.getAccessToken({ username, password }) + } + + register (options: OverrideCommandOptions & { + username: string + password?: string + displayName?: string + channel?: { + name: string + displayName: string + } + }) { + const { username, password = 'password', displayName, channel } = options + const path = '/api/v1/users/register' + + return this.postBodyRequest({ + ...options, + + path, + fields: { + username, + password, + email: username + '@example.com', + displayName, + channel + }, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + // --------------------------------------------------------------------------- + + getMyInfo (options: OverrideCommandOptions = {}) { + const path = '/api/v1/users/me' + + return this.getRequestBody({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + getMyQuotaUsed (options: OverrideCommandOptions = {}) { + const path = '/api/v1/users/me/video-quota-used' + + return this.getRequestBody({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + getMyRating (options: OverrideCommandOptions & { + videoId: number | string + }) { + const { videoId } = options + const path = '/api/v1/users/me/videos/' + videoId + '/rating' + + return this.getRequestBody({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + deleteMe (options: OverrideCommandOptions = {}) { + const path = '/api/v1/users/me' + + return this.deleteRequest({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + updateMe (options: OverrideCommandOptions & UserUpdateMe) { + const path = '/api/v1/users/me' + + const toSend: UserUpdateMe = omit(options, 'url', 'accessToken') + + return this.putBodyRequest({ + ...options, + + path, + fields: toSend, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + updateMyAvatar (options: OverrideCommandOptions & { + fixture: string + }) { + const { fixture } = options + const path = '/api/v1/users/me/avatar/pick' + + return this.updateImageRequest({ + ...options, + + path, + fixture, + fieldname: 'avatarfile', + + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + // --------------------------------------------------------------------------- + + get (options: OverrideCommandOptions & { + userId: number + withStats?: boolean // default false + }) { + const { userId, withStats } = options + const path = '/api/v1/users/' + userId + + return this.getRequestBody({ + ...options, + + path, + query: { withStats }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + list (options: OverrideCommandOptions & { + start?: number + count?: number + sort?: string + search?: string + blocked?: boolean + } = {}) { + const path = '/api/v1/users' + + return this.getRequestBody>({ + ...options, + + path, + query: pick(options, [ 'start', 'count', 'sort', 'search', 'blocked' ]), + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + remove (options: OverrideCommandOptions & { + userId: number + }) { + const { userId } = options + const path = '/api/v1/users/' + userId + + return this.deleteRequest({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + update (options: OverrideCommandOptions & { + userId: number + email?: string + emailVerified?: boolean + videoQuota?: number + videoQuotaDaily?: number + password?: string + adminFlags?: UserAdminFlag + pluginAuth?: string + role?: UserRole + }) { + const path = '/api/v1/users/' + options.userId + + const toSend: UserUpdate = {} + if (options.password !== undefined && options.password !== null) toSend.password = options.password + if (options.email !== undefined && options.email !== null) toSend.email = options.email + if (options.emailVerified !== undefined && options.emailVerified !== null) toSend.emailVerified = options.emailVerified + if (options.videoQuota !== undefined && options.videoQuota !== null) toSend.videoQuota = options.videoQuota + if (options.videoQuotaDaily !== undefined && options.videoQuotaDaily !== null) toSend.videoQuotaDaily = options.videoQuotaDaily + if (options.role !== undefined && options.role !== null) toSend.role = options.role + if (options.adminFlags !== undefined && options.adminFlags !== null) toSend.adminFlags = options.adminFlags + if (options.pluginAuth !== undefined) toSend.pluginAuth = options.pluginAuth + + return this.putBodyRequest({ + ...options, + + path, + fields: toSend, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } +} diff --git a/shared/extra-utils/users/users.ts b/shared/extra-utils/users/users.ts deleted file mode 100644 index 0f15962ad..000000000 --- a/shared/extra-utils/users/users.ts +++ /dev/null @@ -1,415 +0,0 @@ -import { omit } from 'lodash' -import * as request from 'supertest' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' -import { UserUpdateMe } from '../../models/users' -import { UserAdminFlag } from '../../models/users/user-flag.model' -import { UserRegister } from '../../models/users/user-register.model' -import { UserRole } from '../../models/users/user-role' -import { makeGetRequest, makePostBodyRequest, makePutBodyRequest, updateImageRequest } from '../requests/requests' -import { ServerInfo } from '../server/servers' -import { userLogin } from './login' - -function createUser (parameters: { - url: string - accessToken: string - username: string - password: string - videoQuota?: number - videoQuotaDaily?: number - role?: UserRole - adminFlags?: UserAdminFlag - specialStatus?: number -}) { - const { - url, - accessToken, - username, - adminFlags, - password = 'password', - videoQuota = 1000000, - videoQuotaDaily = -1, - role = UserRole.USER, - specialStatus = HttpStatusCode.OK_200 - } = parameters - - const path = '/api/v1/users' - const body = { - username, - password, - role, - adminFlags, - email: username + '@example.com', - videoQuota, - videoQuotaDaily - } - - return request(url) - .post(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .send(body) - .expect(specialStatus) -} - -async function generateUser (server: ServerInfo, username: string) { - const password = 'my super password' - const resCreate = await createUser({ url: server.url, accessToken: server.accessToken, username: username, password: password }) - - const token = await userLogin(server, { username, password }) - - const resMe = await getMyUserInformation(server.url, token) - - return { - token, - userId: resCreate.body.user.id, - userChannelId: resMe.body.videoChannels[0].id - } -} - -async function generateUserAccessToken (server: ServerInfo, username: string) { - const password = 'my super password' - await createUser({ url: server.url, accessToken: server.accessToken, username: username, password: password }) - - return userLogin(server, { username, password }) -} - -function registerUser (url: string, username: string, password: string, specialStatus = HttpStatusCode.NO_CONTENT_204) { - const path = '/api/v1/users/register' - const body = { - username, - password, - email: username + '@example.com' - } - - return request(url) - .post(path) - .set('Accept', 'application/json') - .send(body) - .expect(specialStatus) -} - -function registerUserWithChannel (options: { - url: string - user: { username: string, password: string, displayName?: string } - channel: { name: string, displayName: string } -}) { - const path = '/api/v1/users/register' - const body: UserRegister = { - username: options.user.username, - password: options.user.password, - email: options.user.username + '@example.com', - channel: options.channel - } - - if (options.user.displayName) { - Object.assign(body, { displayName: options.user.displayName }) - } - - return makePostBodyRequest({ - url: options.url, - path, - fields: body, - statusCodeExpected: HttpStatusCode.NO_CONTENT_204 - }) -} - -function getMyUserInformation (url: string, accessToken: string, specialStatus = HttpStatusCode.OK_200) { - const path = '/api/v1/users/me' - - return request(url) - .get(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(specialStatus) - .expect('Content-Type', /json/) -} - -function getUserScopedTokens (url: string, token: string, statusCodeExpected = HttpStatusCode.OK_200) { - const path = '/api/v1/users/scoped-tokens' - - return makeGetRequest({ - url, - path, - token, - statusCodeExpected - }) -} - -function renewUserScopedTokens (url: string, token: string, statusCodeExpected = HttpStatusCode.OK_200) { - const path = '/api/v1/users/scoped-tokens' - - return makePostBodyRequest({ - url, - path, - token, - statusCodeExpected - }) -} - -function deleteMe (url: string, accessToken: string, specialStatus = HttpStatusCode.NO_CONTENT_204) { - const path = '/api/v1/users/me' - - return request(url) - .delete(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(specialStatus) -} - -function getMyUserVideoQuotaUsed (url: string, accessToken: string, specialStatus = HttpStatusCode.OK_200) { - const path = '/api/v1/users/me/video-quota-used' - - return request(url) - .get(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(specialStatus) - .expect('Content-Type', /json/) -} - -function getUserInformation (url: string, accessToken: string, userId: number, withStats = false) { - const path = '/api/v1/users/' + userId - - return request(url) - .get(path) - .query({ withStats }) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(HttpStatusCode.OK_200) - .expect('Content-Type', /json/) -} - -function getMyUserVideoRating (url: string, accessToken: string, videoId: number | string, specialStatus = HttpStatusCode.OK_200) { - const path = '/api/v1/users/me/videos/' + videoId + '/rating' - - return request(url) - .get(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(specialStatus) - .expect('Content-Type', /json/) -} - -function getUsersList (url: string, accessToken: string) { - const path = '/api/v1/users' - - return request(url) - .get(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(HttpStatusCode.OK_200) - .expect('Content-Type', /json/) -} - -function getUsersListPaginationAndSort ( - url: string, - accessToken: string, - start: number, - count: number, - sort: string, - search?: string, - blocked?: boolean -) { - const path = '/api/v1/users' - - const query = { - start, - count, - sort, - search, - blocked - } - - return request(url) - .get(path) - .query(query) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(HttpStatusCode.OK_200) - .expect('Content-Type', /json/) -} - -function removeUser (url: string, userId: number | string, accessToken: string, expectedStatus = HttpStatusCode.NO_CONTENT_204) { - const path = '/api/v1/users' - - return request(url) - .delete(path + '/' + userId) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(expectedStatus) -} - -function blockUser ( - url: string, - userId: number | string, - accessToken: string, - expectedStatus = HttpStatusCode.NO_CONTENT_204, - reason?: string -) { - const path = '/api/v1/users' - let body: any - if (reason) body = { reason } - - return request(url) - .post(path + '/' + userId + '/block') - .send(body) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(expectedStatus) -} - -function unblockUser (url: string, userId: number | string, accessToken: string, expectedStatus = HttpStatusCode.NO_CONTENT_204) { - const path = '/api/v1/users' - - return request(url) - .post(path + '/' + userId + '/unblock') - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(expectedStatus) -} - -function updateMyUser (options: { url: string, accessToken: string, statusCodeExpected?: HttpStatusCode } & UserUpdateMe) { - const path = '/api/v1/users/me' - - const toSend: UserUpdateMe = omit(options, 'url', 'accessToken') - - return makePutBodyRequest({ - url: options.url, - path, - token: options.accessToken, - fields: toSend, - statusCodeExpected: options.statusCodeExpected || HttpStatusCode.NO_CONTENT_204 - }) -} - -function updateMyAvatar (options: { - url: string - accessToken: string - fixture: string -}) { - const path = '/api/v1/users/me/avatar/pick' - - return updateImageRequest({ ...options, path, fieldname: 'avatarfile' }) -} - -function updateUser (options: { - url: string - userId: number - accessToken: string - email?: string - emailVerified?: boolean - videoQuota?: number - videoQuotaDaily?: number - password?: string - adminFlags?: UserAdminFlag - pluginAuth?: string - role?: UserRole -}) { - const path = '/api/v1/users/' + options.userId - - const toSend = {} - if (options.password !== undefined && options.password !== null) toSend['password'] = options.password - if (options.email !== undefined && options.email !== null) toSend['email'] = options.email - if (options.emailVerified !== undefined && options.emailVerified !== null) toSend['emailVerified'] = options.emailVerified - if (options.videoQuota !== undefined && options.videoQuota !== null) toSend['videoQuota'] = options.videoQuota - if (options.videoQuotaDaily !== undefined && options.videoQuotaDaily !== null) toSend['videoQuotaDaily'] = options.videoQuotaDaily - if (options.role !== undefined && options.role !== null) toSend['role'] = options.role - if (options.adminFlags !== undefined && options.adminFlags !== null) toSend['adminFlags'] = options.adminFlags - if (options.pluginAuth !== undefined) toSend['pluginAuth'] = options.pluginAuth - - return makePutBodyRequest({ - url: options.url, - path, - token: options.accessToken, - fields: toSend, - statusCodeExpected: HttpStatusCode.NO_CONTENT_204 - }) -} - -function askResetPassword (url: string, email: string) { - const path = '/api/v1/users/ask-reset-password' - - return makePostBodyRequest({ - url, - path, - fields: { email }, - statusCodeExpected: HttpStatusCode.NO_CONTENT_204 - }) -} - -function resetPassword ( - url: string, - userId: number, - verificationString: string, - password: string, - statusCodeExpected = HttpStatusCode.NO_CONTENT_204 -) { - const path = '/api/v1/users/' + userId + '/reset-password' - - return makePostBodyRequest({ - url, - path, - fields: { password, verificationString }, - statusCodeExpected - }) -} - -function askSendVerifyEmail (url: string, email: string) { - const path = '/api/v1/users/ask-send-verify-email' - - return makePostBodyRequest({ - url, - path, - fields: { email }, - statusCodeExpected: HttpStatusCode.NO_CONTENT_204 - }) -} - -function verifyEmail ( - url: string, - userId: number, - verificationString: string, - isPendingEmail = false, - statusCodeExpected = HttpStatusCode.NO_CONTENT_204 -) { - const path = '/api/v1/users/' + userId + '/verify-email' - - return makePostBodyRequest({ - url, - path, - fields: { - verificationString, - isPendingEmail - }, - statusCodeExpected - }) -} - -// --------------------------------------------------------------------------- - -export { - createUser, - registerUser, - getMyUserInformation, - getMyUserVideoRating, - deleteMe, - registerUserWithChannel, - getMyUserVideoQuotaUsed, - getUsersList, - getUsersListPaginationAndSort, - removeUser, - updateUser, - updateMyUser, - getUserInformation, - blockUser, - unblockUser, - askResetPassword, - resetPassword, - renewUserScopedTokens, - updateMyAvatar, - generateUser, - askSendVerifyEmail, - generateUserAccessToken, - verifyEmail, - getUserScopedTokens -} diff --git a/shared/extra-utils/videos/blacklist-command.ts b/shared/extra-utils/videos/blacklist-command.ts new file mode 100644 index 000000000..3a2ef89ba --- /dev/null +++ b/shared/extra-utils/videos/blacklist-command.ts @@ -0,0 +1,76 @@ + +import { HttpStatusCode, ResultList } from '@shared/models' +import { VideoBlacklist, VideoBlacklistType } from '../../models/videos' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export class BlacklistCommand extends AbstractCommand { + + add (options: OverrideCommandOptions & { + videoId: number | string + reason?: string + unfederate?: boolean + }) { + const { videoId, reason, unfederate } = options + const path = '/api/v1/videos/' + videoId + '/blacklist' + + return this.postBodyRequest({ + ...options, + + path, + fields: { reason, unfederate }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + update (options: OverrideCommandOptions & { + videoId: number | string + reason?: string + }) { + const { videoId, reason } = options + const path = '/api/v1/videos/' + videoId + '/blacklist' + + return this.putBodyRequest({ + ...options, + + path, + fields: { reason }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + remove (options: OverrideCommandOptions & { + videoId: number | string + }) { + const { videoId } = options + const path = '/api/v1/videos/' + videoId + '/blacklist' + + return this.deleteRequest({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + list (options: OverrideCommandOptions & { + sort?: string + type?: VideoBlacklistType + } = {}) { + const { sort, type } = options + const path = '/api/v1/videos/blacklist/' + + const query = { sort, type } + + return this.getRequestBody>({ + ...options, + + path, + query, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } +} diff --git a/shared/extra-utils/videos/captions-command.ts b/shared/extra-utils/videos/captions-command.ts new file mode 100644 index 000000000..a65ea99e3 --- /dev/null +++ b/shared/extra-utils/videos/captions-command.ts @@ -0,0 +1,65 @@ +import { HttpStatusCode, ResultList, VideoCaption } from '@shared/models' +import { buildAbsoluteFixturePath } from '../miscs' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export class CaptionsCommand extends AbstractCommand { + + add (options: OverrideCommandOptions & { + videoId: string | number + language: string + fixture: string + mimeType?: string + }) { + const { videoId, language, fixture, mimeType } = options + + const path = '/api/v1/videos/' + videoId + '/captions/' + language + + const captionfile = buildAbsoluteFixturePath(fixture) + const captionfileAttach = mimeType + ? [ captionfile, { contentType: mimeType } ] + : captionfile + + return this.putUploadRequest({ + ...options, + + path, + fields: {}, + attaches: { + captionfile: captionfileAttach + }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + list (options: OverrideCommandOptions & { + videoId: string | number + }) { + const { videoId } = options + const path = '/api/v1/videos/' + videoId + '/captions' + + return this.getRequestBody>({ + ...options, + + path, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + delete (options: OverrideCommandOptions & { + videoId: string | number + language: string + }) { + const { videoId, language } = options + const path = '/api/v1/videos/' + videoId + '/captions/' + language + + return this.deleteRequest({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } +} diff --git a/shared/extra-utils/videos/captions.ts b/shared/extra-utils/videos/captions.ts new file mode 100644 index 000000000..ff8a43366 --- /dev/null +++ b/shared/extra-utils/videos/captions.ts @@ -0,0 +1,17 @@ +import { expect } from 'chai' +import * as request from 'supertest' +import { HttpStatusCode } from '@shared/models' + +async function testCaptionFile (url: string, captionPath: string, containsString: string) { + const res = await request(url) + .get(captionPath) + .expect(HttpStatusCode.OK_200) + + expect(res.text).to.contain(containsString) +} + +// --------------------------------------------------------------------------- + +export { + testCaptionFile +} diff --git a/shared/extra-utils/videos/change-ownership-command.ts b/shared/extra-utils/videos/change-ownership-command.ts new file mode 100644 index 000000000..ad4c726ef --- /dev/null +++ b/shared/extra-utils/videos/change-ownership-command.ts @@ -0,0 +1,68 @@ + +import { HttpStatusCode, ResultList, VideoChangeOwnership } from '@shared/models' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export class ChangeOwnershipCommand extends AbstractCommand { + + create (options: OverrideCommandOptions & { + videoId: number | string + username: string + }) { + const { videoId, username } = options + const path = '/api/v1/videos/' + videoId + '/give-ownership' + + return this.postBodyRequest({ + ...options, + + path, + fields: { username }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + list (options: OverrideCommandOptions = {}) { + const path = '/api/v1/videos/ownership' + + return this.getRequestBody>({ + ...options, + + path, + query: { sort: '-createdAt' }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + accept (options: OverrideCommandOptions & { + ownershipId: number + channelId: number + }) { + const { ownershipId, channelId } = options + const path = '/api/v1/videos/ownership/' + ownershipId + '/accept' + + return this.postBodyRequest({ + ...options, + + path, + fields: { channelId }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + refuse (options: OverrideCommandOptions & { + ownershipId: number + }) { + const { ownershipId } = options + const path = '/api/v1/videos/ownership/' + ownershipId + '/refuse' + + return this.postBodyRequest({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } +} diff --git a/shared/extra-utils/videos/channels-command.ts b/shared/extra-utils/videos/channels-command.ts new file mode 100644 index 000000000..255e1d62d --- /dev/null +++ b/shared/extra-utils/videos/channels-command.ts @@ -0,0 +1,156 @@ +import { pick } from '@shared/core-utils' +import { HttpStatusCode, ResultList, VideoChannel, VideoChannelCreateResult } from '@shared/models' +import { VideoChannelCreate } from '../../models/videos/channel/video-channel-create.model' +import { VideoChannelUpdate } from '../../models/videos/channel/video-channel-update.model' +import { unwrapBody } from '../requests' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export class ChannelsCommand extends AbstractCommand { + + list (options: OverrideCommandOptions & { + start?: number + count?: number + sort?: string + withStats?: boolean + } = {}) { + const path = '/api/v1/video-channels' + + return this.getRequestBody>({ + ...options, + + path, + query: pick(options, [ 'start', 'count', 'sort', 'withStats' ]), + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + listByAccount (options: OverrideCommandOptions & { + accountName: string + start?: number + count?: number + sort?: string + withStats?: boolean + search?: string + }) { + const { accountName, sort = 'createdAt' } = options + const path = '/api/v1/accounts/' + accountName + '/video-channels' + + return this.getRequestBody>({ + ...options, + + path, + query: { sort, ...pick(options, [ 'start', 'count', 'withStats', 'search' ]) }, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + async create (options: OverrideCommandOptions & { + attributes: VideoChannelCreate + }) { + const path = '/api/v1/video-channels/' + + // Default attributes + const defaultAttributes = { + displayName: 'my super video channel', + description: 'my super channel description', + support: 'my super channel support' + } + const attributes = { ...defaultAttributes, ...options.attributes } + + const body = await unwrapBody<{ videoChannel: VideoChannelCreateResult }>(this.postBodyRequest({ + ...options, + + path, + fields: attributes, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + })) + + return body.videoChannel + } + + update (options: OverrideCommandOptions & { + channelName: string + attributes: VideoChannelUpdate + }) { + const { channelName, attributes } = options + const path = '/api/v1/video-channels/' + channelName + + return this.putBodyRequest({ + ...options, + + path, + fields: attributes, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + delete (options: OverrideCommandOptions & { + channelName: string + }) { + const path = '/api/v1/video-channels/' + options.channelName + + return this.deleteRequest({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + get (options: OverrideCommandOptions & { + channelName: string + }) { + const path = '/api/v1/video-channels/' + options.channelName + + return this.getRequestBody({ + ...options, + + path, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + updateImage (options: OverrideCommandOptions & { + fixture: string + channelName: string | number + type: 'avatar' | 'banner' + }) { + const { channelName, fixture, type } = options + + const path = `/api/v1/video-channels/${channelName}/${type}/pick` + + return this.updateImageRequest({ + ...options, + + path, + fixture, + fieldname: type + 'file', + + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + deleteImage (options: OverrideCommandOptions & { + channelName: string | number + type: 'avatar' | 'banner' + }) { + const { channelName, type } = options + + const path = `/api/v1/video-channels/${channelName}/${type}` + + return this.deleteRequest({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } +} diff --git a/shared/extra-utils/videos/channels.ts b/shared/extra-utils/videos/channels.ts new file mode 100644 index 000000000..756c47453 --- /dev/null +++ b/shared/extra-utils/videos/channels.ts @@ -0,0 +1,18 @@ +import { PeerTubeServer } from '../server/server' + +function setDefaultVideoChannel (servers: PeerTubeServer[]) { + const tasks: Promise[] = [] + + for (const server of servers) { + const p = server.users.getMyInfo() + .then(user => { server.store.channel = user.videoChannels[0] }) + + tasks.push(p) + } + + return Promise.all(tasks) +} + +export { + setDefaultVideoChannel +} diff --git a/shared/extra-utils/videos/comments-command.ts b/shared/extra-utils/videos/comments-command.ts new file mode 100644 index 000000000..f0d163a07 --- /dev/null +++ b/shared/extra-utils/videos/comments-command.ts @@ -0,0 +1,152 @@ +import { pick } from 'lodash' +import { HttpStatusCode, ResultList, VideoComment, VideoCommentThreads, VideoCommentThreadTree } from '@shared/models' +import { unwrapBody } from '../requests' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export class CommentsCommand extends AbstractCommand { + + private lastVideoId: number | string + private lastThreadId: number + private lastReplyId: number + + listForAdmin (options: OverrideCommandOptions & { + start?: number + count?: number + sort?: string + isLocal?: boolean + search?: string + searchAccount?: string + searchVideo?: string + } = {}) { + const { sort = '-createdAt' } = options + const path = '/api/v1/videos/comments' + + const query = { sort, ...pick(options, [ 'start', 'count', 'isLocal', 'search', 'searchAccount', 'searchVideo' ]) } + + return this.getRequestBody>({ + ...options, + + path, + query, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + listThreads (options: OverrideCommandOptions & { + videoId: number | string + start?: number + count?: number + sort?: string + }) { + const { start, count, sort, videoId } = options + const path = '/api/v1/videos/' + videoId + '/comment-threads' + + return this.getRequestBody({ + ...options, + + path, + query: { start, count, sort }, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + getThread (options: OverrideCommandOptions & { + videoId: number | string + threadId: number + }) { + const { videoId, threadId } = options + const path = '/api/v1/videos/' + videoId + '/comment-threads/' + threadId + + return this.getRequestBody({ + ...options, + + path, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + async createThread (options: OverrideCommandOptions & { + videoId: number | string + text: string + }) { + const { videoId, text } = options + const path = '/api/v1/videos/' + videoId + '/comment-threads' + + const body = await unwrapBody<{ comment: VideoComment }>(this.postBodyRequest({ + ...options, + + path, + fields: { text }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + })) + + this.lastThreadId = body.comment?.id + this.lastVideoId = videoId + + return body.comment + } + + async addReply (options: OverrideCommandOptions & { + videoId: number | string + toCommentId: number + text: string + }) { + const { videoId, toCommentId, text } = options + const path = '/api/v1/videos/' + videoId + '/comments/' + toCommentId + + const body = await unwrapBody<{ comment: VideoComment }>(this.postBodyRequest({ + ...options, + + path, + fields: { text }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + })) + + this.lastReplyId = body.comment?.id + + return body.comment + } + + async addReplyToLastReply (options: OverrideCommandOptions & { + text: string + }) { + return this.addReply({ ...options, videoId: this.lastVideoId, toCommentId: this.lastReplyId }) + } + + async addReplyToLastThread (options: OverrideCommandOptions & { + text: string + }) { + return this.addReply({ ...options, videoId: this.lastVideoId, toCommentId: this.lastThreadId }) + } + + async findCommentId (options: OverrideCommandOptions & { + videoId: number | string + text: string + }) { + const { videoId, text } = options + const { data } = await this.listThreads({ videoId, count: 25, sort: '-createdAt' }) + + return data.find(c => c.text === text).id + } + + delete (options: OverrideCommandOptions & { + videoId: number | string + commentId: number + }) { + const { videoId, commentId } = options + const path = '/api/v1/videos/' + videoId + '/comments/' + commentId + + return this.deleteRequest({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } +} diff --git a/shared/extra-utils/videos/history-command.ts b/shared/extra-utils/videos/history-command.ts new file mode 100644 index 000000000..13b7150c1 --- /dev/null +++ b/shared/extra-utils/videos/history-command.ts @@ -0,0 +1,58 @@ +import { HttpStatusCode, ResultList, Video } from '@shared/models' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export class HistoryCommand extends AbstractCommand { + + wathVideo (options: OverrideCommandOptions & { + videoId: number | string + currentTime: number + }) { + const { videoId, currentTime } = options + + const path = '/api/v1/videos/' + videoId + '/watching' + const fields = { currentTime } + + return this.putBodyRequest({ + ...options, + + path, + fields, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + list (options: OverrideCommandOptions & { + search?: string + } = {}) { + const { search } = options + const path = '/api/v1/users/me/history/videos' + + return this.getRequestBody>({ + ...options, + + path, + query: { + search + }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + remove (options: OverrideCommandOptions & { + beforeDate?: string + } = {}) { + const { beforeDate } = options + const path = '/api/v1/users/me/history/videos/remove' + + return this.postBodyRequest({ + ...options, + + path, + fields: { beforeDate }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } +} diff --git a/shared/extra-utils/videos/imports-command.ts b/shared/extra-utils/videos/imports-command.ts new file mode 100644 index 000000000..e4944694d --- /dev/null +++ b/shared/extra-utils/videos/imports-command.ts @@ -0,0 +1,47 @@ + +import { HttpStatusCode, ResultList } from '@shared/models' +import { VideoImport, VideoImportCreate } from '../../models/videos' +import { unwrapBody } from '../requests' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export class ImportsCommand extends AbstractCommand { + + importVideo (options: OverrideCommandOptions & { + attributes: VideoImportCreate & { torrentfile?: string } + }) { + const { attributes } = options + const path = '/api/v1/videos/imports' + + let attaches: any = {} + if (attributes.torrentfile) attaches = { torrentfile: attributes.torrentfile } + + return unwrapBody(this.postUploadRequest({ + ...options, + + path, + attaches, + fields: options.attributes, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + })) + } + + getMyVideoImports (options: OverrideCommandOptions & { + sort?: string + } = {}) { + const { sort } = options + const path = '/api/v1/users/me/videos/imports' + + const query = {} + if (sort) query['sort'] = sort + + return this.getRequestBody>({ + ...options, + + path, + query: { sort }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } +} diff --git a/shared/extra-utils/videos/index.ts b/shared/extra-utils/videos/index.ts new file mode 100644 index 000000000..26e663f46 --- /dev/null +++ b/shared/extra-utils/videos/index.ts @@ -0,0 +1,19 @@ +export * from './blacklist-command' +export * from './captions-command' +export * from './captions' +export * from './change-ownership-command' +export * from './channels' +export * from './channels-command' +export * from './comments-command' +export * from './history-command' +export * from './imports-command' +export * from './live-command' +export * from './live' +export * from './playlists-command' +export * from './playlists' +export * from './services-command' +export * from './streaming-playlists-command' +export * from './streaming-playlists' +export * from './comments-command' +export * from './videos-command' +export * from './videos' diff --git a/shared/extra-utils/videos/live-command.ts b/shared/extra-utils/videos/live-command.ts new file mode 100644 index 000000000..bf9486a05 --- /dev/null +++ b/shared/extra-utils/videos/live-command.ts @@ -0,0 +1,154 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ + +import { readdir } from 'fs-extra' +import { omit } from 'lodash' +import { join } from 'path' +import { HttpStatusCode, LiveVideo, LiveVideoCreate, LiveVideoUpdate, VideoCreateResult, VideoDetails, VideoState } from '@shared/models' +import { wait } from '../miscs' +import { unwrapBody } from '../requests' +import { AbstractCommand, OverrideCommandOptions } from '../shared' +import { sendRTMPStream, testFfmpegStreamError } from './live' + +export class LiveCommand extends AbstractCommand { + + get (options: OverrideCommandOptions & { + videoId: number | string + }) { + const path = '/api/v1/videos/live' + + return this.getRequestBody({ + ...options, + + path: path + '/' + options.videoId, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + update (options: OverrideCommandOptions & { + videoId: number | string + fields: LiveVideoUpdate + }) { + const { videoId, fields } = options + const path = '/api/v1/videos/live' + + return this.putBodyRequest({ + ...options, + + path: path + '/' + videoId, + fields, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + async create (options: OverrideCommandOptions & { + fields: LiveVideoCreate + }) { + const { fields } = options + const path = '/api/v1/videos/live' + + const attaches: any = {} + if (fields.thumbnailfile) attaches.thumbnailfile = fields.thumbnailfile + if (fields.previewfile) attaches.previewfile = fields.previewfile + + const body = await unwrapBody<{ video: VideoCreateResult }>(this.postUploadRequest({ + ...options, + + path, + attaches, + fields: omit(fields, 'thumbnailfile', 'previewfile'), + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + })) + + return body.video + } + + async sendRTMPStreamInVideo (options: OverrideCommandOptions & { + videoId: number | string + fixtureName?: string + }) { + const { videoId, fixtureName } = options + const videoLive = await this.get({ videoId }) + + return sendRTMPStream(videoLive.rtmpUrl, videoLive.streamKey, fixtureName) + } + + async runAndTestStreamError (options: OverrideCommandOptions & { + videoId: number | string + shouldHaveError: boolean + }) { + const command = await this.sendRTMPStreamInVideo(options) + + return testFfmpegStreamError(command, options.shouldHaveError) + } + + waitUntilPublished (options: OverrideCommandOptions & { + videoId: number | string + }) { + const { videoId } = options + return this.waitUntilState({ videoId, state: VideoState.PUBLISHED }) + } + + waitUntilWaiting (options: OverrideCommandOptions & { + videoId: number | string + }) { + const { videoId } = options + return this.waitUntilState({ videoId, state: VideoState.WAITING_FOR_LIVE }) + } + + waitUntilEnded (options: OverrideCommandOptions & { + videoId: number | string + }) { + const { videoId } = options + return this.waitUntilState({ videoId, state: VideoState.LIVE_ENDED }) + } + + waitUntilSegmentGeneration (options: OverrideCommandOptions & { + videoUUID: string + resolution: number + segment: number + }) { + const { resolution, segment, videoUUID } = options + const segmentName = `${resolution}-00000${segment}.ts` + + return this.server.servers.waitUntilLog(`${videoUUID}/${segmentName}`, 2, false) + } + + async waitUntilSaved (options: OverrideCommandOptions & { + videoId: number | string + }) { + let video: VideoDetails + + do { + video = await this.server.videos.getWithToken({ token: options.token, id: options.videoId }) + + await wait(500) + } while (video.isLive === true && video.state.id !== VideoState.PUBLISHED) + } + + async countPlaylists (options: OverrideCommandOptions & { + videoUUID: string + }) { + const basePath = this.server.servers.buildDirectory('streaming-playlists') + const hlsPath = join(basePath, 'hls', options.videoUUID) + + const files = await readdir(hlsPath) + + return files.filter(f => f.endsWith('.m3u8')).length + } + + private async waitUntilState (options: OverrideCommandOptions & { + videoId: number | string + state: VideoState + }) { + let video: VideoDetails + + do { + video = await this.server.videos.getWithToken({ token: options.token, id: options.videoId }) + + await wait(500) + } while (video.state.id !== options.state) + } +} diff --git a/shared/extra-utils/videos/live.ts b/shared/extra-utils/videos/live.ts index c0384769b..94f5f5b59 100644 --- a/shared/extra-utils/videos/live.ts +++ b/shared/extra-utils/videos/live.ts @@ -3,69 +3,9 @@ import { expect } from 'chai' import * as ffmpeg from 'fluent-ffmpeg' import { pathExists, readdir } from 'fs-extra' -import { omit } from 'lodash' import { join } from 'path' -import { LiveVideo, LiveVideoCreate, LiveVideoUpdate, VideoDetails, VideoState } from '@shared/models' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' -import { buildAbsoluteFixturePath, buildServerDirectory, wait } from '../miscs/miscs' -import { makeGetRequest, makePutBodyRequest, makeUploadRequest } from '../requests/requests' -import { ServerInfo, waitUntilLog } from '../server/servers' -import { getVideoWithToken } from './videos' - -function getLive (url: string, token: string, videoId: number | string, statusCodeExpected = HttpStatusCode.OK_200) { - const path = '/api/v1/videos/live' - - return makeGetRequest({ - url, - token, - path: path + '/' + videoId, - statusCodeExpected - }) -} - -function updateLive ( - url: string, - token: string, - videoId: number | string, - fields: LiveVideoUpdate, - statusCodeExpected = HttpStatusCode.NO_CONTENT_204 -) { - const path = '/api/v1/videos/live' - - return makePutBodyRequest({ - url, - token, - path: path + '/' + videoId, - fields, - statusCodeExpected - }) -} - -function createLive (url: string, token: string, fields: LiveVideoCreate, statusCodeExpected = HttpStatusCode.OK_200) { - const path = '/api/v1/videos/live' - - const attaches: any = {} - if (fields.thumbnailfile) attaches.thumbnailfile = fields.thumbnailfile - if (fields.previewfile) attaches.previewfile = fields.previewfile - - const updatedFields = omit(fields, 'thumbnailfile', 'previewfile') - - return makeUploadRequest({ - url, - path, - token, - attaches, - fields: updatedFields, - statusCodeExpected - }) -} - -async function sendRTMPStreamInVideo (url: string, token: string, videoId: number | string, fixtureName?: string) { - const res = await getLive(url, token, videoId) - const videoLive = res.body as LiveVideo - - return sendRTMPStream(videoLive.rtmpUrl, videoLive.streamKey, fixtureName) -} +import { buildAbsoluteFixturePath, wait } from '../miscs' +import { PeerTubeServer } from '../server/server' function sendRTMPStream (rtmpBaseUrl: string, streamKey: string, fixtureName = 'video_short.mp4') { const fixture = buildAbsoluteFixturePath(fixtureName) @@ -109,12 +49,6 @@ function waitFfmpegUntilError (command: ffmpeg.FfmpegCommand, successAfterMS = 1 }) } -async function runAndTestFfmpegStreamError (url: string, token: string, videoId: number | string, shouldHaveError: boolean) { - const command = await sendRTMPStreamInVideo(url, token, videoId) - - return testFfmpegStreamError(command, shouldHaveError) -} - async function testFfmpegStreamError (command: ffmpeg.FfmpegCommand, shouldHaveError: boolean) { let error: Error @@ -136,53 +70,14 @@ async function stopFfmpeg (command: ffmpeg.FfmpegCommand) { await wait(500) } -function waitUntilLivePublished (url: string, token: string, videoId: number | string) { - return waitUntilLiveState(url, token, videoId, VideoState.PUBLISHED) -} - -function waitUntilLiveWaiting (url: string, token: string, videoId: number | string) { - return waitUntilLiveState(url, token, videoId, VideoState.WAITING_FOR_LIVE) -} - -function waitUntilLiveEnded (url: string, token: string, videoId: number | string) { - return waitUntilLiveState(url, token, videoId, VideoState.LIVE_ENDED) -} - -function waitUntilLiveSegmentGeneration (server: ServerInfo, videoUUID: string, resolutionNum: number, segmentNum: number) { - const segmentName = `${resolutionNum}-00000${segmentNum}.ts` - return waitUntilLog(server, `${videoUUID}/${segmentName}`, 2, false) -} - -async function waitUntilLiveState (url: string, token: string, videoId: number | string, state: VideoState) { - let video: VideoDetails - - do { - const res = await getVideoWithToken(url, token, videoId) - video = res.body - - await wait(500) - } while (video.state.id !== state) -} - -async function waitUntilLiveSaved (url: string, token: string, videoId: number | string) { - let video: VideoDetails - - do { - const res = await getVideoWithToken(url, token, videoId) - video = res.body - - await wait(500) - } while (video.isLive === true && video.state.id !== VideoState.PUBLISHED) -} - -async function waitUntilLivePublishedOnAllServers (servers: ServerInfo[], videoId: string) { +async function waitUntilLivePublishedOnAllServers (servers: PeerTubeServer[], videoId: string) { for (const server of servers) { - await waitUntilLivePublished(server.url, server.accessToken, videoId) + await server.live.waitUntilPublished({ videoId }) } } -async function checkLiveCleanup (server: ServerInfo, videoUUID: string, resolutions: number[] = []) { - const basePath = buildServerDirectory(server, 'streaming-playlists') +async function checkLiveCleanupAfterSave (server: PeerTubeServer, videoUUID: string, resolutions: number[] = []) { + const basePath = server.servers.buildDirectory('streaming-playlists') const hlsPath = join(basePath, 'hls', videoUUID) if (resolutions.length === 0) { @@ -198,41 +93,25 @@ async function checkLiveCleanup (server: ServerInfo, videoUUID: string, resoluti expect(files).to.have.lengthOf(resolutions.length * 2 + 2) for (const resolution of resolutions) { - expect(files).to.contain(`${videoUUID}-${resolution}-fragmented.mp4`) - expect(files).to.contain(`${resolution}.m3u8`) - } - - expect(files).to.contain('master.m3u8') - expect(files).to.contain('segments-sha256.json') -} + const fragmentedFile = files.find(f => f.endsWith(`-${resolution}-fragmented.mp4`)) + expect(fragmentedFile).to.exist -async function getPlaylistsCount (server: ServerInfo, videoUUID: string) { - const basePath = buildServerDirectory(server, 'streaming-playlists') - const hlsPath = join(basePath, 'hls', videoUUID) + const playlistFile = files.find(f => f.endsWith(`${resolution}.m3u8`)) + expect(playlistFile).to.exist + } - const files = await readdir(hlsPath) + const masterPlaylistFile = files.find(f => f.endsWith('-master.m3u8')) + expect(masterPlaylistFile).to.exist - return files.filter(f => f.endsWith('.m3u8')).length + const shaFile = files.find(f => f.endsWith('-segments-sha256.json')) + expect(shaFile).to.exist } -// --------------------------------------------------------------------------- - export { - getLive, - getPlaylistsCount, - waitUntilLiveSaved, - waitUntilLivePublished, - updateLive, - createLive, - runAndTestFfmpegStreamError, - checkLiveCleanup, - waitUntilLiveSegmentGeneration, - stopFfmpeg, - waitUntilLiveWaiting, - sendRTMPStreamInVideo, - waitUntilLiveEnded, + sendRTMPStream, waitFfmpegUntilError, + testFfmpegStreamError, + stopFfmpeg, waitUntilLivePublishedOnAllServers, - sendRTMPStream, - testFfmpegStreamError + checkLiveCleanupAfterSave } diff --git a/shared/extra-utils/videos/playlists-command.ts b/shared/extra-utils/videos/playlists-command.ts new file mode 100644 index 000000000..ce23900d3 --- /dev/null +++ b/shared/extra-utils/videos/playlists-command.ts @@ -0,0 +1,280 @@ +import { omit } from 'lodash' +import { pick } from '@shared/core-utils' +import { + BooleanBothQuery, + HttpStatusCode, + ResultList, + VideoExistInPlaylist, + VideoPlaylist, + VideoPlaylistCreate, + VideoPlaylistCreateResult, + VideoPlaylistElement, + VideoPlaylistElementCreate, + VideoPlaylistElementCreateResult, + VideoPlaylistElementUpdate, + VideoPlaylistReorder, + VideoPlaylistType, + VideoPlaylistUpdate +} from '@shared/models' +import { unwrapBody } from '../requests' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export class PlaylistsCommand extends AbstractCommand { + + list (options: OverrideCommandOptions & { + start?: number + count?: number + sort?: string + }) { + const path = '/api/v1/video-playlists' + const query = pick(options, [ 'start', 'count', 'sort' ]) + + return this.getRequestBody>({ + ...options, + + path, + query, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + listByChannel (options: OverrideCommandOptions & { + handle: string + start?: number + count?: number + sort?: string + }) { + const path = '/api/v1/video-channels/' + options.handle + '/video-playlists' + const query = pick(options, [ 'start', 'count', 'sort' ]) + + return this.getRequestBody>({ + ...options, + + path, + query, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + listByAccount (options: OverrideCommandOptions & { + handle: string + start?: number + count?: number + sort?: string + search?: string + playlistType?: VideoPlaylistType + }) { + const path = '/api/v1/accounts/' + options.handle + '/video-playlists' + const query = pick(options, [ 'start', 'count', 'sort', 'search', 'playlistType' ]) + + return this.getRequestBody>({ + ...options, + + path, + query, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + get (options: OverrideCommandOptions & { + playlistId: number | string + }) { + const { playlistId } = options + const path = '/api/v1/video-playlists/' + playlistId + + return this.getRequestBody({ + ...options, + + path, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + listVideos (options: OverrideCommandOptions & { + playlistId: number | string + start?: number + count?: number + query?: { nsfw?: BooleanBothQuery } + }) { + const path = '/api/v1/video-playlists/' + options.playlistId + '/videos' + const query = options.query ?? {} + + return this.getRequestBody>({ + ...options, + + path, + query: { + ...query, + start: options.start, + count: options.count + }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + delete (options: OverrideCommandOptions & { + playlistId: number | string + }) { + const path = '/api/v1/video-playlists/' + options.playlistId + + return this.deleteRequest({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + async create (options: OverrideCommandOptions & { + attributes: VideoPlaylistCreate + }) { + const path = '/api/v1/video-playlists' + + const fields = omit(options.attributes, 'thumbnailfile') + + const attaches = options.attributes.thumbnailfile + ? { thumbnailfile: options.attributes.thumbnailfile } + : {} + + const body = await unwrapBody<{ videoPlaylist: VideoPlaylistCreateResult }>(this.postUploadRequest({ + ...options, + + path, + fields, + attaches, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + })) + + return body.videoPlaylist + } + + update (options: OverrideCommandOptions & { + attributes: VideoPlaylistUpdate + playlistId: number | string + }) { + const path = '/api/v1/video-playlists/' + options.playlistId + + const fields = omit(options.attributes, 'thumbnailfile') + + const attaches = options.attributes.thumbnailfile + ? { thumbnailfile: options.attributes.thumbnailfile } + : {} + + return this.putUploadRequest({ + ...options, + + path, + fields, + attaches, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + async addElement (options: OverrideCommandOptions & { + playlistId: number | string + attributes: VideoPlaylistElementCreate | { videoId: string } + }) { + const attributes = { + ...options.attributes, + + videoId: await this.server.videos.getId({ ...options, uuid: options.attributes.videoId }) + } + + const path = '/api/v1/video-playlists/' + options.playlistId + '/videos' + + const body = await unwrapBody<{ videoPlaylistElement: VideoPlaylistElementCreateResult }>(this.postBodyRequest({ + ...options, + + path, + fields: attributes, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + })) + + return body.videoPlaylistElement + } + + updateElement (options: OverrideCommandOptions & { + playlistId: number | string + elementId: number | string + attributes: VideoPlaylistElementUpdate + }) { + const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/' + options.elementId + + return this.putBodyRequest({ + ...options, + + path, + fields: options.attributes, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + removeElement (options: OverrideCommandOptions & { + playlistId: number | string + elementId: number + }) { + const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/' + options.elementId + + return this.deleteRequest({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + reorderElements (options: OverrideCommandOptions & { + playlistId: number | string + attributes: VideoPlaylistReorder + }) { + const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/reorder' + + return this.postBodyRequest({ + ...options, + + path, + fields: options.attributes, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + getPrivacies (options: OverrideCommandOptions = {}) { + const path = '/api/v1/video-playlists/privacies' + + return this.getRequestBody<{ [ id: number ]: string }>({ + ...options, + + path, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + videosExist (options: OverrideCommandOptions & { + videoIds: number[] + }) { + const { videoIds } = options + const path = '/api/v1/users/me/video-playlists/videos-exist' + + return this.getRequestBody({ + ...options, + + path, + query: { videoIds }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } +} diff --git a/shared/extra-utils/videos/playlists.ts b/shared/extra-utils/videos/playlists.ts new file mode 100644 index 000000000..3dde52bb9 --- /dev/null +++ b/shared/extra-utils/videos/playlists.ts @@ -0,0 +1,25 @@ +import { expect } from 'chai' +import { readdir } from 'fs-extra' +import { join } from 'path' +import { root } from '../miscs' + +async function checkPlaylistFilesWereRemoved ( + playlistUUID: string, + internalServerNumber: number, + directories = [ 'thumbnails' ] +) { + const testDirectory = 'test' + internalServerNumber + + for (const directory of directories) { + const directoryPath = join(root(), testDirectory, directory) + + const files = await readdir(directoryPath) + for (const file of files) { + expect(file).to.not.contain(playlistUUID) + } + } +} + +export { + checkPlaylistFilesWereRemoved +} diff --git a/shared/extra-utils/videos/services-command.ts b/shared/extra-utils/videos/services-command.ts new file mode 100644 index 000000000..06760df42 --- /dev/null +++ b/shared/extra-utils/videos/services-command.ts @@ -0,0 +1,29 @@ +import { HttpStatusCode } from '@shared/models' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export class ServicesCommand extends AbstractCommand { + + getOEmbed (options: OverrideCommandOptions & { + oembedUrl: string + format?: string + maxHeight?: number + maxWidth?: number + }) { + const path = '/services/oembed' + const query = { + url: options.oembedUrl, + format: options.format, + maxheight: options.maxHeight, + maxwidth: options.maxWidth + } + + return this.getRequest({ + ...options, + + path, + query, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } +} diff --git a/shared/extra-utils/videos/services.ts b/shared/extra-utils/videos/services.ts deleted file mode 100644 index e13a788bd..000000000 --- a/shared/extra-utils/videos/services.ts +++ /dev/null @@ -1,24 +0,0 @@ -import * as request from 'supertest' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' - -function getOEmbed (url: string, oembedUrl: string, format?: string, maxHeight?: number, maxWidth?: number) { - const path = '/services/oembed' - const query = { - url: oembedUrl, - format, - maxheight: maxHeight, - maxwidth: maxWidth - } - - return request(url) - .get(path) - .query(query) - .set('Accept', 'application/json') - .expect(HttpStatusCode.OK_200) -} - -// --------------------------------------------------------------------------- - -export { - getOEmbed -} diff --git a/shared/extra-utils/videos/streaming-playlists-command.ts b/shared/extra-utils/videos/streaming-playlists-command.ts new file mode 100644 index 000000000..9662685da --- /dev/null +++ b/shared/extra-utils/videos/streaming-playlists-command.ts @@ -0,0 +1,44 @@ +import { HttpStatusCode } from '@shared/models' +import { unwrapBody, unwrapText } from '../requests' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export class StreamingPlaylistsCommand extends AbstractCommand { + + get (options: OverrideCommandOptions & { + url: string + }) { + return unwrapText(this.getRawRequest({ + ...options, + + url: options.url, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + })) + } + + getSegment (options: OverrideCommandOptions & { + url: string + range?: string + }) { + return unwrapBody(this.getRawRequest({ + ...options, + + url: options.url, + range: options.range, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + })) + } + + getSegmentSha256 (options: OverrideCommandOptions & { + url: string + }) { + return unwrapBody<{ [ id: string ]: string }>(this.getRawRequest({ + ...options, + + url: options.url, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + })) + } +} diff --git a/shared/extra-utils/videos/streaming-playlists.ts b/shared/extra-utils/videos/streaming-playlists.ts new file mode 100644 index 000000000..a224b8f5f --- /dev/null +++ b/shared/extra-utils/videos/streaming-playlists.ts @@ -0,0 +1,78 @@ +import { expect } from 'chai' +import { basename } from 'path' +import { sha256 } from '@server/helpers/core-utils' +import { removeFragmentedMP4Ext } from '@shared/core-utils' +import { HttpStatusCode, VideoStreamingPlaylist } from '@shared/models' +import { PeerTubeServer } from '../server' + +async function checkSegmentHash (options: { + server: PeerTubeServer + baseUrlPlaylist: string + baseUrlSegment: string + videoUUID: string + resolution: number + hlsPlaylist: VideoStreamingPlaylist +}) { + const { server, baseUrlPlaylist, baseUrlSegment, videoUUID, resolution, hlsPlaylist } = options + const command = server.streamingPlaylists + + const file = hlsPlaylist.files.find(f => f.resolution.id === resolution) + const videoName = basename(file.fileUrl) + + const playlist = await command.get({ url: `${baseUrlPlaylist}/${videoUUID}/${removeFragmentedMP4Ext(videoName)}.m3u8` }) + + const matches = /#EXT-X-BYTERANGE:(\d+)@(\d+)/.exec(playlist) + + const length = parseInt(matches[1], 10) + const offset = parseInt(matches[2], 10) + const range = `${offset}-${offset + length - 1}` + + const segmentBody = await command.getSegment({ + url: `${baseUrlSegment}/${videoUUID}/${videoName}`, + expectedStatus: HttpStatusCode.PARTIAL_CONTENT_206, + range: `bytes=${range}` + }) + + const shaBody = await command.getSegmentSha256({ url: hlsPlaylist.segmentsSha256Url }) + expect(sha256(segmentBody)).to.equal(shaBody[videoName][range]) +} + +async function checkLiveSegmentHash (options: { + server: PeerTubeServer + baseUrlSegment: string + videoUUID: string + segmentName: string + hlsPlaylist: VideoStreamingPlaylist +}) { + const { server, baseUrlSegment, videoUUID, segmentName, hlsPlaylist } = options + const command = server.streamingPlaylists + + const segmentBody = await command.getSegment({ url: `${baseUrlSegment}/${videoUUID}/${segmentName}` }) + const shaBody = await command.getSegmentSha256({ url: hlsPlaylist.segmentsSha256Url }) + + expect(sha256(segmentBody)).to.equal(shaBody[segmentName]) +} + +async function checkResolutionsInMasterPlaylist (options: { + server: PeerTubeServer + playlistUrl: string + resolutions: number[] +}) { + const { server, playlistUrl, resolutions } = options + + const masterPlaylist = await server.streamingPlaylists.get({ url: playlistUrl }) + + for (const resolution of resolutions) { + const reg = new RegExp( + '#EXT-X-STREAM-INF:BANDWIDTH=\\d+,RESOLUTION=\\d+x' + resolution + ',(FRAME-RATE=\\d+,)?CODECS="avc1.64001f,mp4a.40.2"' + ) + + expect(masterPlaylist).to.match(reg) + } +} + +export { + checkSegmentHash, + checkLiveSegmentHash, + checkResolutionsInMasterPlaylist +} diff --git a/shared/extra-utils/videos/video-blacklist.ts b/shared/extra-utils/videos/video-blacklist.ts deleted file mode 100644 index aa1548537..000000000 --- a/shared/extra-utils/videos/video-blacklist.ts +++ /dev/null @@ -1,79 +0,0 @@ -import * as request from 'supertest' -import { VideoBlacklistType } from '../../models/videos' -import { makeGetRequest } from '..' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' - -function addVideoToBlacklist ( - url: string, - token: string, - videoId: number | string, - reason?: string, - unfederate?: boolean, - specialStatus = HttpStatusCode.NO_CONTENT_204 -) { - const path = '/api/v1/videos/' + videoId + '/blacklist' - - return request(url) - .post(path) - .send({ reason, unfederate }) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - .expect(specialStatus) -} - -function updateVideoBlacklist ( - url: string, - token: string, - videoId: number, - reason?: string, - specialStatus = HttpStatusCode.NO_CONTENT_204 -) { - const path = '/api/v1/videos/' + videoId + '/blacklist' - - return request(url) - .put(path) - .send({ reason }) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - .expect(specialStatus) -} - -function removeVideoFromBlacklist (url: string, token: string, videoId: number | string, specialStatus = HttpStatusCode.NO_CONTENT_204) { - const path = '/api/v1/videos/' + videoId + '/blacklist' - - return request(url) - .delete(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - .expect(specialStatus) -} - -function getBlacklistedVideosList (parameters: { - url: string - token: string - sort?: string - type?: VideoBlacklistType - specialStatus?: HttpStatusCode -}) { - const { url, token, sort, type, specialStatus = HttpStatusCode.OK_200 } = parameters - const path = '/api/v1/videos/blacklist/' - - const query = { sort, type } - - return makeGetRequest({ - url, - path, - query, - token, - statusCodeExpected: specialStatus - }) -} - -// --------------------------------------------------------------------------- - -export { - addVideoToBlacklist, - removeVideoFromBlacklist, - getBlacklistedVideosList, - updateVideoBlacklist -} diff --git a/shared/extra-utils/videos/video-captions.ts b/shared/extra-utils/videos/video-captions.ts deleted file mode 100644 index 62eec7b90..000000000 --- a/shared/extra-utils/videos/video-captions.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { makeDeleteRequest, makeGetRequest, makeUploadRequest } from '../requests/requests' -import * as request from 'supertest' -import * as chai from 'chai' -import { buildAbsoluteFixturePath } from '../miscs/miscs' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' - -const expect = chai.expect - -function createVideoCaption (args: { - url: string - accessToken: string - videoId: string | number - language: string - fixture: string - mimeType?: string - statusCodeExpected?: number -}) { - const path = '/api/v1/videos/' + args.videoId + '/captions/' + args.language - - const captionfile = buildAbsoluteFixturePath(args.fixture) - const captionfileAttach = args.mimeType ? [ captionfile, { contentType: args.mimeType } ] : captionfile - - return makeUploadRequest({ - method: 'PUT', - url: args.url, - path, - token: args.accessToken, - fields: {}, - attaches: { - captionfile: captionfileAttach - }, - statusCodeExpected: args.statusCodeExpected || HttpStatusCode.NO_CONTENT_204 - }) -} - -function listVideoCaptions (url: string, videoId: string | number) { - const path = '/api/v1/videos/' + videoId + '/captions' - - return makeGetRequest({ - url, - path, - statusCodeExpected: HttpStatusCode.OK_200 - }) -} - -function deleteVideoCaption (url: string, token: string, videoId: string | number, language: string) { - const path = '/api/v1/videos/' + videoId + '/captions/' + language - - return makeDeleteRequest({ - url, - token, - path, - statusCodeExpected: HttpStatusCode.NO_CONTENT_204 - }) -} - -async function testCaptionFile (url: string, captionPath: string, containsString: string) { - const res = await request(url) - .get(captionPath) - .expect(HttpStatusCode.OK_200) - - expect(res.text).to.contain(containsString) -} - -// --------------------------------------------------------------------------- - -export { - createVideoCaption, - listVideoCaptions, - testCaptionFile, - deleteVideoCaption -} diff --git a/shared/extra-utils/videos/video-change-ownership.ts b/shared/extra-utils/videos/video-change-ownership.ts deleted file mode 100644 index ef82a7636..000000000 --- a/shared/extra-utils/videos/video-change-ownership.ts +++ /dev/null @@ -1,72 +0,0 @@ -import * as request from 'supertest' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' - -function changeVideoOwnership ( - url: string, - token: string, - videoId: number | string, - username, - expectedStatus = HttpStatusCode.NO_CONTENT_204 -) { - const path = '/api/v1/videos/' + videoId + '/give-ownership' - - return request(url) - .post(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - .send({ username }) - .expect(expectedStatus) -} - -function getVideoChangeOwnershipList (url: string, token: string) { - const path = '/api/v1/videos/ownership' - - return request(url) - .get(path) - .query({ sort: '-createdAt' }) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - .expect(HttpStatusCode.OK_200) - .expect('Content-Type', /json/) -} - -function acceptChangeOwnership ( - url: string, - token: string, - ownershipId: string, - channelId: number, - expectedStatus = HttpStatusCode.NO_CONTENT_204 -) { - const path = '/api/v1/videos/ownership/' + ownershipId + '/accept' - - return request(url) - .post(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - .send({ channelId }) - .expect(expectedStatus) -} - -function refuseChangeOwnership ( - url: string, - token: string, - ownershipId: string, - expectedStatus = HttpStatusCode.NO_CONTENT_204 -) { - const path = '/api/v1/videos/ownership/' + ownershipId + '/refuse' - - return request(url) - .post(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - .expect(expectedStatus) -} - -// --------------------------------------------------------------------------- - -export { - changeVideoOwnership, - getVideoChangeOwnershipList, - acceptChangeOwnership, - refuseChangeOwnership -} diff --git a/shared/extra-utils/videos/video-channels.ts b/shared/extra-utils/videos/video-channels.ts deleted file mode 100644 index 0aab93e52..000000000 --- a/shared/extra-utils/videos/video-channels.ts +++ /dev/null @@ -1,192 +0,0 @@ -/* eslint-disable @typescript-eslint/no-floating-promises */ - -import * as request from 'supertest' -import { VideoChannelUpdate } from '../../models/videos/channel/video-channel-update.model' -import { VideoChannelCreate } from '../../models/videos/channel/video-channel-create.model' -import { makeDeleteRequest, makeGetRequest, updateImageRequest } from '../requests/requests' -import { ServerInfo } from '../server/servers' -import { MyUser, User } from '../../models/users/user.model' -import { getMyUserInformation } from '../users/users' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' - -function getVideoChannelsList (url: string, start: number, count: number, sort?: string, withStats?: boolean) { - const path = '/api/v1/video-channels' - - const req = request(url) - .get(path) - .query({ start: start }) - .query({ count: count }) - - if (sort) req.query({ sort }) - if (withStats) req.query({ withStats }) - - return req.set('Accept', 'application/json') - .expect(HttpStatusCode.OK_200) - .expect('Content-Type', /json/) -} - -function getAccountVideoChannelsList (parameters: { - url: string - accountName: string - start?: number - count?: number - sort?: string - specialStatus?: HttpStatusCode - withStats?: boolean - search?: string -}) { - const { - url, - accountName, - start, - count, - sort = 'createdAt', - specialStatus = HttpStatusCode.OK_200, - withStats = false, - search - } = parameters - - const path = '/api/v1/accounts/' + accountName + '/video-channels' - - return makeGetRequest({ - url, - path, - query: { - start, - count, - sort, - withStats, - search - }, - statusCodeExpected: specialStatus - }) -} - -function addVideoChannel ( - url: string, - token: string, - videoChannelAttributesArg: VideoChannelCreate, - expectedStatus = HttpStatusCode.OK_200 -) { - const path = '/api/v1/video-channels/' - - // Default attributes - let attributes = { - displayName: 'my super video channel', - description: 'my super channel description', - support: 'my super channel support' - } - attributes = Object.assign(attributes, videoChannelAttributesArg) - - return request(url) - .post(path) - .send(attributes) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - .expect(expectedStatus) -} - -function updateVideoChannel ( - url: string, - token: string, - channelName: string, - attributes: VideoChannelUpdate, - expectedStatus = HttpStatusCode.NO_CONTENT_204 -) { - const body: any = {} - const path = '/api/v1/video-channels/' + channelName - - if (attributes.displayName) body.displayName = attributes.displayName - if (attributes.description) body.description = attributes.description - if (attributes.support) body.support = attributes.support - if (attributes.bulkVideosSupportUpdate) body.bulkVideosSupportUpdate = attributes.bulkVideosSupportUpdate - - return request(url) - .put(path) - .send(body) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - .expect(expectedStatus) -} - -function deleteVideoChannel (url: string, token: string, channelName: string, expectedStatus = HttpStatusCode.NO_CONTENT_204) { - const path = '/api/v1/video-channels/' + channelName - - return request(url) - .delete(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - .expect(expectedStatus) -} - -function getVideoChannel (url: string, channelName: string) { - const path = '/api/v1/video-channels/' + channelName - - return request(url) - .get(path) - .set('Accept', 'application/json') - .expect(HttpStatusCode.OK_200) - .expect('Content-Type', /json/) -} - -function updateVideoChannelImage (options: { - url: string - accessToken: string - fixture: string - videoChannelName: string | number - type: 'avatar' | 'banner' -}) { - const path = `/api/v1/video-channels/${options.videoChannelName}/${options.type}/pick` - - return updateImageRequest({ ...options, path, fieldname: options.type + 'file' }) -} - -function deleteVideoChannelImage (options: { - url: string - accessToken: string - videoChannelName: string | number - type: 'avatar' | 'banner' -}) { - const path = `/api/v1/video-channels/${options.videoChannelName}/${options.type}` - - return makeDeleteRequest({ - url: options.url, - token: options.accessToken, - path, - statusCodeExpected: 204 - }) -} - -function setDefaultVideoChannel (servers: ServerInfo[]) { - const tasks: Promise[] = [] - - for (const server of servers) { - const p = getMyUserInformation(server.url, server.accessToken) - .then(res => { server.videoChannel = (res.body as User).videoChannels[0] }) - - tasks.push(p) - } - - return Promise.all(tasks) -} - -async function getDefaultVideoChannel (url: string, token: string) { - const res = await getMyUserInformation(url, token) - - return (res.body as MyUser).videoChannels[0].id -} - -// --------------------------------------------------------------------------- - -export { - updateVideoChannelImage, - getVideoChannelsList, - getAccountVideoChannelsList, - addVideoChannel, - updateVideoChannel, - deleteVideoChannel, - getVideoChannel, - setDefaultVideoChannel, - deleteVideoChannelImage, - getDefaultVideoChannel -} diff --git a/shared/extra-utils/videos/video-comments.ts b/shared/extra-utils/videos/video-comments.ts deleted file mode 100644 index 71b9f875a..000000000 --- a/shared/extra-utils/videos/video-comments.ts +++ /dev/null @@ -1,138 +0,0 @@ -/* eslint-disable @typescript-eslint/no-floating-promises */ - -import * as request from 'supertest' -import { makeDeleteRequest, makeGetRequest } from '../requests/requests' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' - -function getAdminVideoComments (options: { - url: string - token: string - start: number - count: number - sort?: string - isLocal?: boolean - search?: string - searchAccount?: string - searchVideo?: string -}) { - const { url, token, start, count, sort, isLocal, search, searchAccount, searchVideo } = options - const path = '/api/v1/videos/comments' - - const query = { - start, - count, - sort: sort || '-createdAt' - } - - if (isLocal !== undefined) Object.assign(query, { isLocal }) - if (search !== undefined) Object.assign(query, { search }) - if (searchAccount !== undefined) Object.assign(query, { searchAccount }) - if (searchVideo !== undefined) Object.assign(query, { searchVideo }) - - return makeGetRequest({ - url, - path, - token, - query, - statusCodeExpected: HttpStatusCode.OK_200 - }) -} - -function getVideoCommentThreads (url: string, videoId: number | string, start: number, count: number, sort?: string, token?: string) { - const path = '/api/v1/videos/' + videoId + '/comment-threads' - - const req = request(url) - .get(path) - .query({ start: start }) - .query({ count: count }) - - if (sort) req.query({ sort }) - if (token) req.set('Authorization', 'Bearer ' + token) - - return req.set('Accept', 'application/json') - .expect(HttpStatusCode.OK_200) - .expect('Content-Type', /json/) -} - -function getVideoThreadComments (url: string, videoId: number | string, threadId: number, token?: string) { - const path = '/api/v1/videos/' + videoId + '/comment-threads/' + threadId - - const req = request(url) - .get(path) - .set('Accept', 'application/json') - - if (token) req.set('Authorization', 'Bearer ' + token) - - return req.expect(HttpStatusCode.OK_200) - .expect('Content-Type', /json/) -} - -function addVideoCommentThread ( - url: string, - token: string, - videoId: number | string, - text: string, - expectedStatus = HttpStatusCode.OK_200 -) { - const path = '/api/v1/videos/' + videoId + '/comment-threads' - - return request(url) - .post(path) - .send({ text }) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - .expect(expectedStatus) -} - -function addVideoCommentReply ( - url: string, - token: string, - videoId: number | string, - inReplyToCommentId: number, - text: string, - expectedStatus = HttpStatusCode.OK_200 -) { - const path = '/api/v1/videos/' + videoId + '/comments/' + inReplyToCommentId - - return request(url) - .post(path) - .send({ text }) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - .expect(expectedStatus) -} - -async function findCommentId (url: string, videoId: number | string, text: string) { - const res = await getVideoCommentThreads(url, videoId, 0, 25, '-createdAt') - - return res.body.data.find(c => c.text === text).id as number -} - -function deleteVideoComment ( - url: string, - token: string, - videoId: number | string, - commentId: number, - statusCodeExpected = HttpStatusCode.NO_CONTENT_204 -) { - const path = '/api/v1/videos/' + videoId + '/comments/' + commentId - - return makeDeleteRequest({ - url, - path, - token, - statusCodeExpected - }) -} - -// --------------------------------------------------------------------------- - -export { - getVideoCommentThreads, - getAdminVideoComments, - getVideoThreadComments, - addVideoCommentThread, - addVideoCommentReply, - findCommentId, - deleteVideoComment -} diff --git a/shared/extra-utils/videos/video-history.ts b/shared/extra-utils/videos/video-history.ts deleted file mode 100644 index b989e14dc..000000000 --- a/shared/extra-utils/videos/video-history.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' - -function userWatchVideo ( - url: string, - token: string, - videoId: number | string, - currentTime: number, - statusCodeExpected = HttpStatusCode.NO_CONTENT_204 -) { - const path = '/api/v1/videos/' + videoId + '/watching' - const fields = { currentTime } - - return makePutBodyRequest({ url, path, token, fields, statusCodeExpected }) -} - -function listMyVideosHistory (url: string, token: string, search?: string) { - const path = '/api/v1/users/me/history/videos' - - return makeGetRequest({ - url, - path, - token, - query: { - search - }, - statusCodeExpected: HttpStatusCode.OK_200 - }) -} - -function removeMyVideosHistory (url: string, token: string, beforeDate?: string) { - const path = '/api/v1/users/me/history/videos/remove' - - return makePostBodyRequest({ - url, - path, - token, - fields: beforeDate ? { beforeDate } : {}, - statusCodeExpected: HttpStatusCode.NO_CONTENT_204 - }) -} - -// --------------------------------------------------------------------------- - -export { - userWatchVideo, - listMyVideosHistory, - removeMyVideosHistory -} diff --git a/shared/extra-utils/videos/video-imports.ts b/shared/extra-utils/videos/video-imports.ts deleted file mode 100644 index 81c0163cb..000000000 --- a/shared/extra-utils/videos/video-imports.ts +++ /dev/null @@ -1,90 +0,0 @@ - -import { VideoImportCreate } from '../../models/videos' -import { makeGetRequest, makeUploadRequest } from '../requests/requests' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' - -function getYoutubeVideoUrl () { - return 'https://www.youtube.com/watch?v=msX3jv1XdvM' -} - -function getYoutubeHDRVideoUrl () { - /** - * The video is used to check format-selection correctness wrt. HDR, - * which brings its own set of oddities outside of a MediaSource. - * FIXME: refactor once HDR is supported at playback - * - * The video needs to have the following format_ids: - * (which you can check by using `youtube-dl -F`): - * - 303 (1080p webm vp9) - * - 299 (1080p mp4 avc1) - * - 335 (1080p webm vp9.2 HDR) - * - * 15 jan. 2021: TEST VIDEO NOT CURRENTLY PROVIDING - * - 400 (1080p mp4 av01) - * - 315 (2160p webm vp9 HDR) - * - 337 (2160p webm vp9.2 HDR) - * - 401 (2160p mp4 av01 HDR) - */ - return 'https://www.youtube.com/watch?v=qR5vOXbZsI4' -} - -function getMagnetURI () { - // eslint-disable-next-line max-len - return 'magnet:?xs=https%3A%2F%2Fpeertube2.cpy.re%2Fstatic%2Ftorrents%2Fb209ca00-c8bb-4b2b-b421-1ede169f3dbc-720.torrent&xt=urn:btih:0f498834733e8057ed5c6f2ee2b4efd8d84a76ee&dn=super+peertube2+video&tr=wss%3A%2F%2Fpeertube2.cpy.re%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fpeertube2.cpy.re%2Ftracker%2Fannounce&ws=https%3A%2F%2Fpeertube2.cpy.re%2Fstatic%2Fwebseed%2Fb209ca00-c8bb-4b2b-b421-1ede169f3dbc-720.mp4' -} - -function getBadVideoUrl () { - return 'https://download.cpy.re/peertube/bad_video.mp4' -} - -function getGoodVideoUrl () { - return 'https://download.cpy.re/peertube/good_video.mp4' -} - -function importVideo ( - url: string, - token: string, - attributes: VideoImportCreate & { torrentfile?: string }, - statusCodeExpected = HttpStatusCode.OK_200 -) { - const path = '/api/v1/videos/imports' - - let attaches: any = {} - if (attributes.torrentfile) attaches = { torrentfile: attributes.torrentfile } - - return makeUploadRequest({ - url, - path, - token, - attaches, - fields: attributes, - statusCodeExpected - }) -} - -function getMyVideoImports (url: string, token: string, sort?: string) { - const path = '/api/v1/users/me/videos/imports' - - const query = {} - if (sort) query['sort'] = sort - - return makeGetRequest({ - url, - query, - path, - token, - statusCodeExpected: HttpStatusCode.OK_200 - }) -} - -// --------------------------------------------------------------------------- - -export { - getBadVideoUrl, - getYoutubeVideoUrl, - getYoutubeHDRVideoUrl, - importVideo, - getMagnetURI, - getMyVideoImports, - getGoodVideoUrl -} diff --git a/shared/extra-utils/videos/video-playlists.ts b/shared/extra-utils/videos/video-playlists.ts deleted file mode 100644 index c6f799e5d..000000000 --- a/shared/extra-utils/videos/video-playlists.ts +++ /dev/null @@ -1,320 +0,0 @@ -import { makeDeleteRequest, makeGetRequest, makePostBodyRequest, makePutBodyRequest, makeUploadRequest } from '../requests/requests' -import { VideoPlaylistCreate } from '../../models/videos/playlist/video-playlist-create.model' -import { omit } from 'lodash' -import { VideoPlaylistUpdate } from '../../models/videos/playlist/video-playlist-update.model' -import { VideoPlaylistElementCreate } from '../../models/videos/playlist/video-playlist-element-create.model' -import { VideoPlaylistElementUpdate } from '../../models/videos/playlist/video-playlist-element-update.model' -import { videoUUIDToId } from './videos' -import { join } from 'path' -import { root } from '..' -import { readdir } from 'fs-extra' -import { expect } from 'chai' -import { VideoPlaylistType } from '../../models/videos/playlist/video-playlist-type.model' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' - -function getVideoPlaylistsList (url: string, start: number, count: number, sort?: string) { - const path = '/api/v1/video-playlists' - - const query = { - start, - count, - sort - } - - return makeGetRequest({ - url, - path, - query, - statusCodeExpected: HttpStatusCode.OK_200 - }) -} - -function getVideoChannelPlaylistsList (url: string, videoChannelName: string, start: number, count: number, sort?: string) { - const path = '/api/v1/video-channels/' + videoChannelName + '/video-playlists' - - const query = { - start, - count, - sort - } - - return makeGetRequest({ - url, - path, - query, - statusCodeExpected: HttpStatusCode.OK_200 - }) -} - -function getAccountPlaylistsList (url: string, accountName: string, start: number, count: number, sort?: string, search?: string) { - const path = '/api/v1/accounts/' + accountName + '/video-playlists' - - const query = { - start, - count, - sort, - search - } - - return makeGetRequest({ - url, - path, - query, - statusCodeExpected: HttpStatusCode.OK_200 - }) -} - -function getAccountPlaylistsListWithToken ( - url: string, - token: string, - accountName: string, - start: number, - count: number, - playlistType?: VideoPlaylistType, - sort?: string -) { - const path = '/api/v1/accounts/' + accountName + '/video-playlists' - - const query = { - start, - count, - playlistType, - sort - } - - return makeGetRequest({ - url, - token, - path, - query, - statusCodeExpected: HttpStatusCode.OK_200 - }) -} - -function getVideoPlaylist (url: string, playlistId: number | string, statusCodeExpected = HttpStatusCode.OK_200) { - const path = '/api/v1/video-playlists/' + playlistId - - return makeGetRequest({ - url, - path, - statusCodeExpected - }) -} - -function getVideoPlaylistWithToken (url: string, token: string, playlistId: number | string, statusCodeExpected = HttpStatusCode.OK_200) { - const path = '/api/v1/video-playlists/' + playlistId - - return makeGetRequest({ - url, - token, - path, - statusCodeExpected - }) -} - -function deleteVideoPlaylist (url: string, token: string, playlistId: number | string, statusCodeExpected = HttpStatusCode.NO_CONTENT_204) { - const path = '/api/v1/video-playlists/' + playlistId - - return makeDeleteRequest({ - url, - path, - token, - statusCodeExpected - }) -} - -function createVideoPlaylist (options: { - url: string - token: string - playlistAttrs: VideoPlaylistCreate - expectedStatus?: number -}) { - const path = '/api/v1/video-playlists' - - const fields = omit(options.playlistAttrs, 'thumbnailfile') - - const attaches = options.playlistAttrs.thumbnailfile - ? { thumbnailfile: options.playlistAttrs.thumbnailfile } - : {} - - return makeUploadRequest({ - method: 'POST', - url: options.url, - path, - token: options.token, - fields, - attaches, - statusCodeExpected: options.expectedStatus || HttpStatusCode.OK_200 - }) -} - -function updateVideoPlaylist (options: { - url: string - token: string - playlistAttrs: VideoPlaylistUpdate - playlistId: number | string - expectedStatus?: number -}) { - const path = '/api/v1/video-playlists/' + options.playlistId - - const fields = omit(options.playlistAttrs, 'thumbnailfile') - - const attaches = options.playlistAttrs.thumbnailfile - ? { thumbnailfile: options.playlistAttrs.thumbnailfile } - : {} - - return makeUploadRequest({ - method: 'PUT', - url: options.url, - path, - token: options.token, - fields, - attaches, - statusCodeExpected: options.expectedStatus || HttpStatusCode.NO_CONTENT_204 - }) -} - -async function addVideoInPlaylist (options: { - url: string - token: string - playlistId: number | string - elementAttrs: VideoPlaylistElementCreate | { videoId: string } - expectedStatus?: number -}) { - options.elementAttrs.videoId = await videoUUIDToId(options.url, options.elementAttrs.videoId) - - const path = '/api/v1/video-playlists/' + options.playlistId + '/videos' - - return makePostBodyRequest({ - url: options.url, - path, - token: options.token, - fields: options.elementAttrs, - statusCodeExpected: options.expectedStatus || HttpStatusCode.OK_200 - }) -} - -function updateVideoPlaylistElement (options: { - url: string - token: string - playlistId: number | string - playlistElementId: number | string - elementAttrs: VideoPlaylistElementUpdate - expectedStatus?: number -}) { - const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/' + options.playlistElementId - - return makePutBodyRequest({ - url: options.url, - path, - token: options.token, - fields: options.elementAttrs, - statusCodeExpected: options.expectedStatus || HttpStatusCode.NO_CONTENT_204 - }) -} - -function removeVideoFromPlaylist (options: { - url: string - token: string - playlistId: number | string - playlistElementId: number - expectedStatus?: number -}) { - const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/' + options.playlistElementId - - return makeDeleteRequest({ - url: options.url, - path, - token: options.token, - statusCodeExpected: options.expectedStatus || HttpStatusCode.NO_CONTENT_204 - }) -} - -function reorderVideosPlaylist (options: { - url: string - token: string - playlistId: number | string - elementAttrs: { - startPosition: number - insertAfterPosition: number - reorderLength?: number - } - expectedStatus?: number -}) { - const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/reorder' - - return makePostBodyRequest({ - url: options.url, - path, - token: options.token, - fields: options.elementAttrs, - statusCodeExpected: options.expectedStatus || HttpStatusCode.NO_CONTENT_204 - }) -} - -async function checkPlaylistFilesWereRemoved ( - playlistUUID: string, - internalServerNumber: number, - directories = [ 'thumbnails' ] -) { - const testDirectory = 'test' + internalServerNumber - - for (const directory of directories) { - const directoryPath = join(root(), testDirectory, directory) - - const files = await readdir(directoryPath) - for (const file of files) { - expect(file).to.not.contain(playlistUUID) - } - } -} - -function getVideoPlaylistPrivacies (url: string) { - const path = '/api/v1/video-playlists/privacies' - - return makeGetRequest({ - url, - path, - statusCodeExpected: HttpStatusCode.OK_200 - }) -} - -function doVideosExistInMyPlaylist (url: string, token: string, videoIds: number[]) { - const path = '/api/v1/users/me/video-playlists/videos-exist' - - return makeGetRequest({ - url, - token, - path, - query: { videoIds }, - statusCodeExpected: HttpStatusCode.OK_200 - }) -} - -// --------------------------------------------------------------------------- - -export { - getVideoPlaylistPrivacies, - - getVideoPlaylistsList, - getVideoChannelPlaylistsList, - getAccountPlaylistsList, - getAccountPlaylistsListWithToken, - - getVideoPlaylist, - getVideoPlaylistWithToken, - - createVideoPlaylist, - updateVideoPlaylist, - deleteVideoPlaylist, - - addVideoInPlaylist, - updateVideoPlaylistElement, - removeVideoFromPlaylist, - - reorderVideosPlaylist, - - checkPlaylistFilesWereRemoved, - - doVideosExistInMyPlaylist -} diff --git a/shared/extra-utils/videos/video-streaming-playlists.ts b/shared/extra-utils/videos/video-streaming-playlists.ts deleted file mode 100644 index 99c2e1880..000000000 --- a/shared/extra-utils/videos/video-streaming-playlists.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { makeRawRequest } from '../requests/requests' -import { sha256 } from '../../../server/helpers/core-utils' -import { VideoStreamingPlaylist } from '../../models/videos/video-streaming-playlist.model' -import { expect } from 'chai' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' - -function getPlaylist (url: string, statusCodeExpected = HttpStatusCode.OK_200) { - return makeRawRequest(url, statusCodeExpected) -} - -function getSegment (url: string, statusCodeExpected = HttpStatusCode.OK_200, range?: string) { - return makeRawRequest(url, statusCodeExpected, range) -} - -function getSegmentSha256 (url: string, statusCodeExpected = HttpStatusCode.OK_200) { - return makeRawRequest(url, statusCodeExpected) -} - -async function checkSegmentHash ( - baseUrlPlaylist: string, - baseUrlSegment: string, - videoUUID: string, - resolution: number, - hlsPlaylist: VideoStreamingPlaylist -) { - const res = await getPlaylist(`${baseUrlPlaylist}/${videoUUID}/${resolution}.m3u8`) - const playlist = res.text - - const videoName = `${videoUUID}-${resolution}-fragmented.mp4` - - const matches = /#EXT-X-BYTERANGE:(\d+)@(\d+)/.exec(playlist) - - const length = parseInt(matches[1], 10) - const offset = parseInt(matches[2], 10) - const range = `${offset}-${offset + length - 1}` - - const res2 = await getSegment(`${baseUrlSegment}/${videoUUID}/${videoName}`, HttpStatusCode.PARTIAL_CONTENT_206, `bytes=${range}`) - - const resSha = await getSegmentSha256(hlsPlaylist.segmentsSha256Url) - - const sha256Server = resSha.body[videoName][range] - expect(sha256(res2.body)).to.equal(sha256Server) -} - -async function checkLiveSegmentHash ( - baseUrlSegment: string, - videoUUID: string, - segmentName: string, - hlsPlaylist: VideoStreamingPlaylist -) { - const res2 = await getSegment(`${baseUrlSegment}/${videoUUID}/${segmentName}`) - - const resSha = await getSegmentSha256(hlsPlaylist.segmentsSha256Url) - - const sha256Server = resSha.body[segmentName] - expect(sha256(res2.body)).to.equal(sha256Server) -} - -async function checkResolutionsInMasterPlaylist (playlistUrl: string, resolutions: number[]) { - const res = await getPlaylist(playlistUrl) - - const masterPlaylist = res.text - - for (const resolution of resolutions) { - const reg = new RegExp( - '#EXT-X-STREAM-INF:BANDWIDTH=\\d+,RESOLUTION=\\d+x' + resolution + ',(FRAME-RATE=\\d+,)?CODECS="avc1.64001f,mp4a.40.2"' - ) - - expect(masterPlaylist).to.match(reg) - } -} - -// --------------------------------------------------------------------------- - -export { - getPlaylist, - getSegment, - checkResolutionsInMasterPlaylist, - getSegmentSha256, - checkLiveSegmentHash, - checkSegmentHash -} diff --git a/shared/extra-utils/videos/videos-command.ts b/shared/extra-utils/videos/videos-command.ts new file mode 100644 index 000000000..33725bfdc --- /dev/null +++ b/shared/extra-utils/videos/videos-command.ts @@ -0,0 +1,599 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */ + +import { expect } from 'chai' +import { createReadStream, stat } from 'fs-extra' +import got, { Response as GotResponse } from 'got' +import { omit } from 'lodash' +import validator from 'validator' +import { buildUUID } from '@server/helpers/uuid' +import { loadLanguages } from '@server/initializers/constants' +import { pick } from '@shared/core-utils' +import { + HttpStatusCode, + ResultList, + UserVideoRateType, + Video, + VideoCreate, + VideoCreateResult, + VideoDetails, + VideoFileMetadata, + VideoPrivacy, + VideosCommonQuery, + VideosWithSearchCommonQuery +} from '@shared/models' +import { buildAbsoluteFixturePath, wait } from '../miscs' +import { unwrapBody } from '../requests' +import { PeerTubeServer, waitJobs } from '../server' +import { AbstractCommand, OverrideCommandOptions } from '../shared' + +export type VideoEdit = Partial> & { + fixture?: string + thumbnailfile?: string + previewfile?: string +} + +export class VideosCommand extends AbstractCommand { + + constructor (server: PeerTubeServer) { + super(server) + + loadLanguages() + } + + getCategories (options: OverrideCommandOptions = {}) { + const path = '/api/v1/videos/categories' + + return this.getRequestBody<{ [id: number]: string }>({ + ...options, + path, + + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + getLicences (options: OverrideCommandOptions = {}) { + const path = '/api/v1/videos/licences' + + return this.getRequestBody<{ [id: number]: string }>({ + ...options, + path, + + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + getLanguages (options: OverrideCommandOptions = {}) { + const path = '/api/v1/videos/languages' + + return this.getRequestBody<{ [id: string]: string }>({ + ...options, + path, + + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + getPrivacies (options: OverrideCommandOptions = {}) { + const path = '/api/v1/videos/privacies' + + return this.getRequestBody<{ [id in VideoPrivacy]: string }>({ + ...options, + path, + + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + // --------------------------------------------------------------------------- + + getDescription (options: OverrideCommandOptions & { + descriptionPath: string + }) { + return this.getRequestBody<{ description: string }>({ + ...options, + path: options.descriptionPath, + + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + getFileMetadata (options: OverrideCommandOptions & { + url: string + }) { + return unwrapBody(this.getRawRequest({ + ...options, + + url: options.url, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + })) + } + + // --------------------------------------------------------------------------- + + view (options: OverrideCommandOptions & { + id: number | string + xForwardedFor?: string + }) { + const { id, xForwardedFor } = options + const path = '/api/v1/videos/' + id + '/views' + + return this.postBodyRequest({ + ...options, + + path, + xForwardedFor, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + rate (options: OverrideCommandOptions & { + id: number | string + rating: UserVideoRateType + }) { + const { id, rating } = options + const path = '/api/v1/videos/' + id + '/rate' + + return this.putBodyRequest({ + ...options, + + path, + fields: { rating }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + // --------------------------------------------------------------------------- + + get (options: OverrideCommandOptions & { + id: number | string + }) { + const path = '/api/v1/videos/' + options.id + + return this.getRequestBody({ + ...options, + + path, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + getWithToken (options: OverrideCommandOptions & { + id: number | string + }) { + return this.get({ + ...options, + + token: this.buildCommonRequestToken({ ...options, implicitToken: true }) + }) + } + + async getId (options: OverrideCommandOptions & { + uuid: number | string + }) { + const { uuid } = options + + if (validator.isUUID('' + uuid) === false) return uuid as number + + const { id } = await this.get({ ...options, id: uuid }) + + return id + } + + // --------------------------------------------------------------------------- + + listMyVideos (options: OverrideCommandOptions & { + start?: number + count?: number + sort?: string + search?: string + isLive?: boolean + } = {}) { + const path = '/api/v1/users/me/videos' + + return this.getRequestBody>({ + ...options, + + path, + query: pick(options, [ 'start', 'count', 'sort', 'search', 'isLive' ]), + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + // --------------------------------------------------------------------------- + + list (options: OverrideCommandOptions & VideosCommonQuery = {}) { + const path = '/api/v1/videos' + + const query = this.buildListQuery(options) + + return this.getRequestBody>({ + ...options, + + path, + query: { sort: 'name', ...query }, + implicitToken: false, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + listWithToken (options: OverrideCommandOptions & VideosCommonQuery = {}) { + return this.list({ + ...options, + + token: this.buildCommonRequestToken({ ...options, implicitToken: true }) + }) + } + + listByAccount (options: OverrideCommandOptions & VideosWithSearchCommonQuery & { + handle: string + }) { + const { handle, search } = options + const path = '/api/v1/accounts/' + handle + '/videos' + + return this.getRequestBody>({ + ...options, + + path, + query: { search, ...this.buildListQuery(options) }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + listByChannel (options: OverrideCommandOptions & VideosWithSearchCommonQuery & { + handle: string + }) { + const { handle } = options + const path = '/api/v1/video-channels/' + handle + '/videos' + + return this.getRequestBody>({ + ...options, + + path, + query: this.buildListQuery(options), + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + + // --------------------------------------------------------------------------- + + async find (options: OverrideCommandOptions & { + name: string + }) { + const { data } = await this.list(options) + + return data.find(v => v.name === options.name) + } + + // --------------------------------------------------------------------------- + + update (options: OverrideCommandOptions & { + id: number | string + attributes?: VideoEdit + }) { + const { id, attributes = {} } = options + const path = '/api/v1/videos/' + id + + // Upload request + if (attributes.thumbnailfile || attributes.previewfile) { + const attaches: any = {} + if (attributes.thumbnailfile) attaches.thumbnailfile = attributes.thumbnailfile + if (attributes.previewfile) attaches.previewfile = attributes.previewfile + + return this.putUploadRequest({ + ...options, + + path, + fields: options.attributes, + attaches: { + thumbnailfile: attributes.thumbnailfile, + previewfile: attributes.previewfile + }, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + return this.putBodyRequest({ + ...options, + + path, + fields: options.attributes, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + } + + remove (options: OverrideCommandOptions & { + id: number | string + }) { + const path = '/api/v1/videos/' + options.id + + return unwrapBody(this.deleteRequest({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 + })) + } + + async removeAll () { + const { data } = await this.list() + + for (const v of data) { + await this.remove({ id: v.id }) + } + } + + // --------------------------------------------------------------------------- + + async upload (options: OverrideCommandOptions & { + attributes?: VideoEdit + mode?: 'legacy' | 'resumable' // default legacy + } = {}) { + const { mode = 'legacy' } = options + let defaultChannelId = 1 + + try { + const { videoChannels } = await this.server.users.getMyInfo({ token: options.token }) + defaultChannelId = videoChannels[0].id + } catch (e) { /* empty */ } + + // Override default attributes + const attributes = { + name: 'my super video', + category: 5, + licence: 4, + language: 'zh', + channelId: defaultChannelId, + nsfw: true, + waitTranscoding: false, + description: 'my super description', + support: 'my super support text', + tags: [ 'tag' ], + privacy: VideoPrivacy.PUBLIC, + commentsEnabled: true, + downloadEnabled: true, + fixture: 'video_short.webm', + + ...options.attributes + } + + const created = mode === 'legacy' + ? await this.buildLegacyUpload({ ...options, attributes }) + : await this.buildResumeUpload({ ...options, attributes }) + + // Wait torrent generation + const expectedStatus = this.buildExpectedStatus({ ...options, defaultExpectedStatus: HttpStatusCode.OK_200 }) + if (expectedStatus === HttpStatusCode.OK_200) { + let video: VideoDetails + + do { + video = await this.getWithToken({ ...options, id: created.uuid }) + + await wait(50) + } while (!video.files[0].torrentUrl) + } + + return created + } + + async buildLegacyUpload (options: OverrideCommandOptions & { + attributes: VideoEdit + }): Promise { + const path = '/api/v1/videos/upload' + + return unwrapBody<{ video: VideoCreateResult }>(this.postUploadRequest({ + ...options, + + path, + fields: this.buildUploadFields(options.attributes), + attaches: this.buildUploadAttaches(options.attributes), + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + })).then(body => body.video || body as any) + } + + async buildResumeUpload (options: OverrideCommandOptions & { + attributes: VideoEdit + }): Promise { + const { attributes, expectedStatus } = options + + let size = 0 + let videoFilePath: string + let mimetype = 'video/mp4' + + if (attributes.fixture) { + videoFilePath = buildAbsoluteFixturePath(attributes.fixture) + size = (await stat(videoFilePath)).size + + if (videoFilePath.endsWith('.mkv')) { + mimetype = 'video/x-matroska' + } else if (videoFilePath.endsWith('.webm')) { + mimetype = 'video/webm' + } + } + + // Do not check status automatically, we'll check it manually + const initializeSessionRes = await this.prepareResumableUpload({ ...options, expectedStatus: null, attributes, size, mimetype }) + const initStatus = initializeSessionRes.status + + if (videoFilePath && initStatus === HttpStatusCode.CREATED_201) { + const locationHeader = initializeSessionRes.header['location'] + expect(locationHeader).to.not.be.undefined + + const pathUploadId = locationHeader.split('?')[1] + + const result = await this.sendResumableChunks({ ...options, pathUploadId, videoFilePath, size }) + + return result.body?.video || result.body as any + } + + const expectedInitStatus = expectedStatus === HttpStatusCode.OK_200 + ? HttpStatusCode.CREATED_201 + : expectedStatus + + expect(initStatus).to.equal(expectedInitStatus) + + return initializeSessionRes.body.video || initializeSessionRes.body + } + + async prepareResumableUpload (options: OverrideCommandOptions & { + attributes: VideoEdit + size: number + mimetype: string + }) { + const { attributes, size, mimetype } = options + + const path = '/api/v1/videos/upload-resumable' + + return this.postUploadRequest({ + ...options, + + path, + headers: { + 'X-Upload-Content-Type': mimetype, + 'X-Upload-Content-Length': size.toString() + }, + fields: { filename: attributes.fixture, ...this.buildUploadFields(options.attributes) }, + // Fixture will be sent later + attaches: this.buildUploadAttaches(omit(options.attributes, 'fixture')), + implicitToken: true, + + defaultExpectedStatus: null + }) + } + + sendResumableChunks (options: OverrideCommandOptions & { + pathUploadId: string + videoFilePath: string + size: number + contentLength?: number + contentRangeBuilder?: (start: number, chunk: any) => string + }) { + const { pathUploadId, videoFilePath, size, contentLength, contentRangeBuilder, expectedStatus = HttpStatusCode.OK_200 } = options + + const path = '/api/v1/videos/upload-resumable' + let start = 0 + + const token = this.buildCommonRequestToken({ ...options, implicitToken: true }) + const url = this.server.url + + const readable = createReadStream(videoFilePath, { highWaterMark: 8 * 1024 }) + return new Promise>((resolve, reject) => { + readable.on('data', async function onData (chunk) { + readable.pause() + + const headers = { + 'Authorization': 'Bearer ' + token, + 'Content-Type': 'application/octet-stream', + 'Content-Range': contentRangeBuilder + ? contentRangeBuilder(start, chunk) + : `bytes ${start}-${start + chunk.length - 1}/${size}`, + 'Content-Length': contentLength ? contentLength + '' : chunk.length + '' + } + + const res = await got<{ video: VideoCreateResult }>({ + url, + method: 'put', + headers, + path: path + '?' + pathUploadId, + body: chunk, + responseType: 'json', + throwHttpErrors: false + }) + + start += chunk.length + + if (res.statusCode === expectedStatus) { + return resolve(res) + } + + if (res.statusCode !== HttpStatusCode.PERMANENT_REDIRECT_308) { + readable.off('data', onData) + return reject(new Error('Incorrect transient behaviour sending intermediary chunks')) + } + + readable.resume() + }) + }) + } + + quickUpload (options: OverrideCommandOptions & { + name: string + nsfw?: boolean + privacy?: VideoPrivacy + fixture?: string + }) { + const attributes: VideoEdit = { name: options.name } + if (options.nsfw) attributes.nsfw = options.nsfw + if (options.privacy) attributes.privacy = options.privacy + if (options.fixture) attributes.fixture = options.fixture + + return this.upload({ ...options, attributes }) + } + + async randomUpload (options: OverrideCommandOptions & { + wait?: boolean // default true + additionalParams?: VideoEdit & { prefixName?: string } + } = {}) { + const { wait = true, additionalParams } = options + const prefixName = additionalParams?.prefixName || '' + const name = prefixName + buildUUID() + + const attributes = { name, ...additionalParams } + + const result = await this.upload({ ...options, attributes }) + + if (wait) await waitJobs([ this.server ]) + + return { ...result, name } + } + + // --------------------------------------------------------------------------- + + private buildListQuery (options: VideosCommonQuery) { + return pick(options, [ + 'start', + 'count', + 'sort', + 'nsfw', + 'isLive', + 'categoryOneOf', + 'licenceOneOf', + 'languageOneOf', + 'tagsOneOf', + 'tagsAllOf', + 'filter', + 'skipCount' + ]) + } + + private buildUploadFields (attributes: VideoEdit) { + return omit(attributes, [ 'fixture', 'thumbnailfile', 'previewfile' ]) + } + + private buildUploadAttaches (attributes: VideoEdit) { + const attaches: { [ name: string ]: string } = {} + + for (const key of [ 'thumbnailfile', 'previewfile' ]) { + if (attributes[key]) attaches[key] = buildAbsoluteFixturePath(attributes[key]) + } + + if (attributes.fixture) attaches.videofile = buildAbsoluteFixturePath(attributes.fixture) + + return attaches + } +} diff --git a/shared/extra-utils/videos/videos.ts b/shared/extra-utils/videos/videos.ts index 469ea4d63..a1d2ba0fc 100644 --- a/shared/extra-utils/videos/videos.ts +++ b/shared/extra-utils/videos/videos.ts @@ -1,646 +1,92 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */ import { expect } from 'chai' -import { createReadStream, pathExists, readdir, readFile, stat } from 'fs-extra' -import got, { Response as GotResponse } from 'got/dist/source' -import * as parseTorrent from 'parse-torrent' -import { join } from 'path' -import * as request from 'supertest' -import validator from 'validator' +import { pathExists, readdir } from 'fs-extra' +import { basename, join } from 'path' import { getLowercaseExtension } from '@server/helpers/core-utils' -import { buildUUID } from '@server/helpers/uuid' -import { HttpStatusCode } from '@shared/core-utils' -import { VideosCommonQuery } from '@shared/models' -import { loadLanguages, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../server/initializers/constants' -import { VideoDetails, VideoPrivacy } from '../../models/videos' -import { - buildAbsoluteFixturePath, - buildServerDirectory, - dateIsValid, - immutableAssign, - testImage, - wait, - webtorrentAdd -} from '../miscs/miscs' -import { makeGetRequest, makePutBodyRequest, makeRawRequest, makeUploadRequest } from '../requests/requests' -import { waitJobs } from '../server/jobs' -import { ServerInfo } from '../server/servers' -import { getMyUserInformation } from '../users/users' - -loadLanguages() - -type VideoAttributes = { - name?: string - category?: number - licence?: number - language?: string - nsfw?: boolean - commentsEnabled?: boolean - downloadEnabled?: boolean - waitTranscoding?: boolean - description?: string - originallyPublishedAt?: string - tags?: string[] - channelId?: number - privacy?: VideoPrivacy - fixture?: string - support?: string - thumbnailfile?: string - previewfile?: string - scheduleUpdate?: { - updateAt: string - privacy?: VideoPrivacy - } -} - -function getVideoCategories (url: string) { - const path = '/api/v1/videos/categories' - - return makeGetRequest({ - url, - path, - statusCodeExpected: HttpStatusCode.OK_200 - }) -} - -function getVideoLicences (url: string) { - const path = '/api/v1/videos/licences' - - return makeGetRequest({ - url, - path, - statusCodeExpected: HttpStatusCode.OK_200 - }) -} - -function getVideoLanguages (url: string) { - const path = '/api/v1/videos/languages' - - return makeGetRequest({ - url, - path, - statusCodeExpected: HttpStatusCode.OK_200 - }) -} - -function getVideoPrivacies (url: string) { - const path = '/api/v1/videos/privacies' - - return makeGetRequest({ - url, - path, - statusCodeExpected: HttpStatusCode.OK_200 - }) -} - -function getVideo (url: string, id: number | string, expectedStatus = HttpStatusCode.OK_200) { - const path = '/api/v1/videos/' + id - - return request(url) - .get(path) - .set('Accept', 'application/json') - .expect(expectedStatus) -} +import { uuidRegex } from '@shared/core-utils' +import { HttpStatusCode, VideoCaption, VideoDetails } from '@shared/models' +import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../server/initializers/constants' +import { dateIsValid, testImage, webtorrentAdd } from '../miscs' +import { makeRawRequest } from '../requests/requests' +import { waitJobs } from '../server' +import { PeerTubeServer } from '../server/server' +import { VideoEdit } from './videos-command' + +async function checkVideoFilesWereRemoved (options: { + server: PeerTubeServer + video: VideoDetails + captions?: VideoCaption[] + onlyVideoFiles?: boolean // default false +}) { + const { video, server, captions = [], onlyVideoFiles = false } = options -async function getVideoIdFromUUID (url: string, uuid: string) { - const res = await getVideo(url, uuid) + const webtorrentFiles = video.files || [] + const hlsFiles = video.streamingPlaylists[0]?.files || [] - return res.body.id -} + const thumbnailName = basename(video.thumbnailPath) + const previewName = basename(video.previewPath) -function getVideoFileMetadataUrl (url: string) { - return request(url) - .get('/') - .set('Accept', 'application/json') - .expect(HttpStatusCode.OK_200) - .expect('Content-Type', /json/) -} + const torrentNames = webtorrentFiles.concat(hlsFiles).map(f => basename(f.torrentUrl)) -function viewVideo (url: string, id: number | string, expectedStatus = HttpStatusCode.NO_CONTENT_204, xForwardedFor?: string) { - const path = '/api/v1/videos/' + id + '/views' + const captionNames = captions.map(c => basename(c.captionPath)) - const req = request(url) - .post(path) - .set('Accept', 'application/json') + const webtorrentFilenames = webtorrentFiles.map(f => basename(f.fileUrl)) + const hlsFilenames = hlsFiles.map(f => basename(f.fileUrl)) - if (xForwardedFor) { - req.set('X-Forwarded-For', xForwardedFor) + let directories: { [ directory: string ]: string[] } = { + videos: webtorrentFilenames, + redundancy: webtorrentFilenames, + [join('playlists', 'hls')]: hlsFilenames, + [join('redundancy', 'hls')]: hlsFilenames } - return req.expect(expectedStatus) -} - -function getVideoWithToken (url: string, token: string, id: number | string, expectedStatus = HttpStatusCode.OK_200) { - const path = '/api/v1/videos/' + id - - return request(url) - .get(path) - .set('Authorization', 'Bearer ' + token) - .set('Accept', 'application/json') - .expect(expectedStatus) -} - -function getVideoDescription (url: string, descriptionPath: string) { - return request(url) - .get(descriptionPath) - .set('Accept', 'application/json') - .expect(HttpStatusCode.OK_200) - .expect('Content-Type', /json/) -} - -function getVideosList (url: string) { - const path = '/api/v1/videos' - - return request(url) - .get(path) - .query({ sort: 'name' }) - .set('Accept', 'application/json') - .expect(HttpStatusCode.OK_200) - .expect('Content-Type', /json/) -} - -function getVideosListWithToken (url: string, token: string, query: { nsfw?: boolean } = {}) { - const path = '/api/v1/videos' - - return request(url) - .get(path) - .set('Authorization', 'Bearer ' + token) - .query(immutableAssign(query, { sort: 'name' })) - .set('Accept', 'application/json') - .expect(HttpStatusCode.OK_200) - .expect('Content-Type', /json/) -} - -function getLocalVideos (url: string) { - const path = '/api/v1/videos' - - return request(url) - .get(path) - .query({ sort: 'name', filter: 'local' }) - .set('Accept', 'application/json') - .expect(HttpStatusCode.OK_200) - .expect('Content-Type', /json/) -} - -function getMyVideos (url: string, accessToken: string, start: number, count: number, sort?: string, search?: string) { - const path = '/api/v1/users/me/videos' - - const req = request(url) - .get(path) - .query({ start: start }) - .query({ count: count }) - .query({ search: search }) - - if (sort) req.query({ sort }) - - return req.set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(HttpStatusCode.OK_200) - .expect('Content-Type', /json/) -} - -function getMyVideosWithFilter (url: string, accessToken: string, query: { isLive?: boolean }) { - const path = '/api/v1/users/me/videos' - - return makeGetRequest({ - url, - path, - token: accessToken, - query, - statusCodeExpected: HttpStatusCode.OK_200 - }) -} - -function getAccountVideos ( - url: string, - accessToken: string, - accountName: string, - start: number, - count: number, - sort?: string, - query: { - nsfw?: boolean - search?: string - } = {} -) { - const path = '/api/v1/accounts/' + accountName + '/videos' - - return makeGetRequest({ - url, - path, - query: immutableAssign(query, { - start, - count, - sort - }), - token: accessToken, - statusCodeExpected: HttpStatusCode.OK_200 - }) -} - -function getVideoChannelVideos ( - url: string, - accessToken: string, - videoChannelName: string, - start: number, - count: number, - sort?: string, - query: { nsfw?: boolean } = {} -) { - const path = '/api/v1/video-channels/' + videoChannelName + '/videos' - - return makeGetRequest({ - url, - path, - query: immutableAssign(query, { - start, - count, - sort - }), - token: accessToken, - statusCodeExpected: HttpStatusCode.OK_200 - }) -} - -function getPlaylistVideos ( - url: string, - accessToken: string, - playlistId: number | string, - start: number, - count: number, - query: { nsfw?: boolean } = {} -) { - const path = '/api/v1/video-playlists/' + playlistId + '/videos' - - return makeGetRequest({ - url, - path, - query: immutableAssign(query, { - start, - count - }), - token: accessToken, - statusCodeExpected: HttpStatusCode.OK_200 - }) -} - -function getVideosListPagination (url: string, start: number, count: number, sort?: string, skipCount?: boolean) { - const path = '/api/v1/videos' - - const req = request(url) - .get(path) - .query({ start: start }) - .query({ count: count }) - - if (sort) req.query({ sort }) - if (skipCount) req.query({ skipCount }) - - return req.set('Accept', 'application/json') - .expect(HttpStatusCode.OK_200) - .expect('Content-Type', /json/) -} - -function getVideosListSort (url: string, sort: string) { - const path = '/api/v1/videos' - - return request(url) - .get(path) - .query({ sort: sort }) - .set('Accept', 'application/json') - .expect(HttpStatusCode.OK_200) - .expect('Content-Type', /json/) -} - -function getVideosWithFilters (url: string, query: VideosCommonQuery) { - const path = '/api/v1/videos' - - return request(url) - .get(path) - .query(query) - .set('Accept', 'application/json') - .expect(HttpStatusCode.OK_200) - .expect('Content-Type', /json/) -} - -function removeVideo (url: string, token: string, id: number | string, expectedStatus = HttpStatusCode.NO_CONTENT_204) { - const path = '/api/v1/videos' - - return request(url) - .delete(path + '/' + id) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - .expect(expectedStatus) -} + if (onlyVideoFiles !== true) { + directories = { + ...directories, -async function removeAllVideos (server: ServerInfo) { - const resVideos = await getVideosList(server.url) - - for (const v of resVideos.body.data) { - await removeVideo(server.url, server.accessToken, v.id) + thumbnails: [ thumbnailName ], + previews: [ previewName ], + torrents: torrentNames, + captions: captionNames + } } -} -async function checkVideoFilesWereRemoved ( - videoUUID: string, - serverNumber: number, - directories = [ - 'redundancy', - 'videos', - 'thumbnails', - 'torrents', - 'previews', - 'captions', - join('playlists', 'hls'), - join('redundancy', 'hls') - ] -) { - for (const directory of directories) { - const directoryPath = buildServerDirectory({ internalServerNumber: serverNumber }, directory) + for (const directory of Object.keys(directories)) { + const directoryPath = server.servers.buildDirectory(directory) const directoryExists = await pathExists(directoryPath) if (directoryExists === false) continue - const files = await readdir(directoryPath) - for (const file of files) { - expect(file, `File ${file} should not exist in ${directoryPath}`).to.not.contain(videoUUID) + const existingFiles = await readdir(directoryPath) + for (const existingFile of existingFiles) { + for (const shouldNotExist of directories[directory]) { + expect(existingFile, `File ${existingFile} should not exist in ${directoryPath}`).to.not.contain(shouldNotExist) + } } } } -async function uploadVideo ( - url: string, - accessToken: string, - videoAttributesArg: VideoAttributes, - specialStatus = HttpStatusCode.OK_200, - mode: 'legacy' | 'resumable' = 'legacy' -) { - let defaultChannelId = '1' - - try { - const res = await getMyUserInformation(url, accessToken) - defaultChannelId = res.body.videoChannels[0].id - } catch (e) { /* empty */ } - - // Override default attributes - const attributes = Object.assign({ - name: 'my super video', - category: 5, - licence: 4, - language: 'zh', - channelId: defaultChannelId, - nsfw: true, - waitTranscoding: false, - description: 'my super description', - support: 'my super support text', - tags: [ 'tag' ], - privacy: VideoPrivacy.PUBLIC, - commentsEnabled: true, - downloadEnabled: true, - fixture: 'video_short.webm' - }, videoAttributesArg) - - const res = mode === 'legacy' - ? await buildLegacyUpload(url, accessToken, attributes, specialStatus) - : await buildResumeUpload(url, accessToken, attributes, specialStatus) - - // Wait torrent generation - if (specialStatus === HttpStatusCode.OK_200) { - let video: VideoDetails - do { - const resVideo = await getVideoWithToken(url, accessToken, res.body.video.uuid) - video = resVideo.body - - await wait(50) - } while (!video.files[0].torrentUrl) +async function saveVideoInServers (servers: PeerTubeServer[], uuid: string) { + for (const server of servers) { + server.store.videoDetails = await server.videos.get({ id: uuid }) } - - return res } function checkUploadVideoParam ( - url: string, + server: PeerTubeServer, token: string, - attributes: Partial, - specialStatus = HttpStatusCode.OK_200, + attributes: Partial, + expectedStatus = HttpStatusCode.OK_200, mode: 'legacy' | 'resumable' = 'legacy' ) { return mode === 'legacy' - ? buildLegacyUpload(url, token, attributes, specialStatus) - : buildResumeUpload(url, token, attributes, specialStatus) -} - -async function buildLegacyUpload (url: string, token: string, attributes: VideoAttributes, specialStatus = HttpStatusCode.OK_200) { - const path = '/api/v1/videos/upload' - const req = request(url) - .post(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - - buildUploadReq(req, attributes) - - if (attributes.fixture !== undefined) { - req.attach('videofile', buildAbsoluteFixturePath(attributes.fixture)) - } - - return req.expect(specialStatus) -} - -async function buildResumeUpload (url: string, token: string, attributes: VideoAttributes, specialStatus = HttpStatusCode.OK_200) { - let size = 0 - let videoFilePath: string - let mimetype = 'video/mp4' - - if (attributes.fixture) { - videoFilePath = buildAbsoluteFixturePath(attributes.fixture) - size = (await stat(videoFilePath)).size - - if (videoFilePath.endsWith('.mkv')) { - mimetype = 'video/x-matroska' - } else if (videoFilePath.endsWith('.webm')) { - mimetype = 'video/webm' - } - } - - const initializeSessionRes = await prepareResumableUpload({ url, token, attributes, size, mimetype }) - const initStatus = initializeSessionRes.status - - if (videoFilePath && initStatus === HttpStatusCode.CREATED_201) { - const locationHeader = initializeSessionRes.header['location'] - expect(locationHeader).to.not.be.undefined - - const pathUploadId = locationHeader.split('?')[1] - - return sendResumableChunks({ url, token, pathUploadId, videoFilePath, size, specialStatus }) - } - - const expectedInitStatus = specialStatus === HttpStatusCode.OK_200 - ? HttpStatusCode.CREATED_201 - : specialStatus - - expect(initStatus).to.equal(expectedInitStatus) - - return initializeSessionRes -} - -async function prepareResumableUpload (options: { - url: string - token: string - attributes: VideoAttributes - size: number - mimetype: string -}) { - const { url, token, attributes, size, mimetype } = options - - const path = '/api/v1/videos/upload-resumable' - - const req = request(url) - .post(path) - .set('Authorization', 'Bearer ' + token) - .set('X-Upload-Content-Type', mimetype) - .set('X-Upload-Content-Length', size.toString()) - - buildUploadReq(req, attributes) - - if (attributes.fixture) { - req.field('filename', attributes.fixture) - } - - return req -} - -function sendResumableChunks (options: { - url: string - token: string - pathUploadId: string - videoFilePath: string - size: number - specialStatus?: HttpStatusCode - contentLength?: number - contentRangeBuilder?: (start: number, chunk: any) => string -}) { - const { url, token, pathUploadId, videoFilePath, size, specialStatus, contentLength, contentRangeBuilder } = options - - const expectedStatus = specialStatus || HttpStatusCode.OK_200 - - const path = '/api/v1/videos/upload-resumable' - let start = 0 - - const readable = createReadStream(videoFilePath, { highWaterMark: 8 * 1024 }) - return new Promise((resolve, reject) => { - readable.on('data', async function onData (chunk) { - readable.pause() - - const headers = { - 'Authorization': 'Bearer ' + token, - 'Content-Type': 'application/octet-stream', - 'Content-Range': contentRangeBuilder - ? contentRangeBuilder(start, chunk) - : `bytes ${start}-${start + chunk.length - 1}/${size}`, - 'Content-Length': contentLength ? contentLength + '' : chunk.length + '' - } - - const res = await got({ - url, - method: 'put', - headers, - path: path + '?' + pathUploadId, - body: chunk, - responseType: 'json', - throwHttpErrors: false - }) - - start += chunk.length - - if (res.statusCode === expectedStatus) { - return resolve(res) - } - - if (res.statusCode !== HttpStatusCode.PERMANENT_REDIRECT_308) { - readable.off('data', onData) - return reject(new Error('Incorrect transient behaviour sending intermediary chunks')) - } - - readable.resume() - }) - }) -} - -function updateVideo ( - url: string, - accessToken: string, - id: number | string, - attributes: VideoAttributes, - statusCodeExpected = HttpStatusCode.NO_CONTENT_204 -) { - const path = '/api/v1/videos/' + id - const body = {} - - if (attributes.name) body['name'] = attributes.name - if (attributes.category) body['category'] = attributes.category - if (attributes.licence) body['licence'] = attributes.licence - if (attributes.language) body['language'] = attributes.language - if (attributes.nsfw !== undefined) body['nsfw'] = JSON.stringify(attributes.nsfw) - if (attributes.commentsEnabled !== undefined) body['commentsEnabled'] = JSON.stringify(attributes.commentsEnabled) - if (attributes.downloadEnabled !== undefined) body['downloadEnabled'] = JSON.stringify(attributes.downloadEnabled) - if (attributes.originallyPublishedAt !== undefined) body['originallyPublishedAt'] = attributes.originallyPublishedAt - if (attributes.description) body['description'] = attributes.description - if (attributes.tags) body['tags'] = attributes.tags - if (attributes.privacy) body['privacy'] = attributes.privacy - if (attributes.channelId) body['channelId'] = attributes.channelId - if (attributes.scheduleUpdate) body['scheduleUpdate'] = attributes.scheduleUpdate - - // Upload request - if (attributes.thumbnailfile || attributes.previewfile) { - const attaches: any = {} - if (attributes.thumbnailfile) attaches.thumbnailfile = attributes.thumbnailfile - if (attributes.previewfile) attaches.previewfile = attributes.previewfile - - return makeUploadRequest({ - url, - method: 'PUT', - path, - token: accessToken, - fields: body, - attaches, - statusCodeExpected - }) - } - - return makePutBodyRequest({ - url, - path, - fields: body, - token: accessToken, - statusCodeExpected - }) -} - -function rateVideo (url: string, accessToken: string, id: number | string, rating: string, specialStatus = HttpStatusCode.NO_CONTENT_204) { - const path = '/api/v1/videos/' + id + '/rate' - - return request(url) - .put(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .send({ rating }) - .expect(specialStatus) -} - -function parseTorrentVideo (server: ServerInfo, videoUUID: string, resolution: number) { - return new Promise((res, rej) => { - const torrentName = videoUUID + '-' + resolution + '.torrent' - const torrentPath = buildServerDirectory(server, join('torrents', torrentName)) - - readFile(torrentPath, (err, data) => { - if (err) return rej(err) - - return res(parseTorrent(data)) - }) - }) + ? server.videos.buildLegacyUpload({ token, attributes, expectedStatus }) + : server.videos.buildResumeUpload({ token, attributes, expectedStatus }) } async function completeVideoCheck ( - url: string, + server: PeerTubeServer, video: any, attributes: { name: string @@ -682,7 +128,7 @@ async function completeVideoCheck ( if (!attributes.likes) attributes.likes = 0 if (!attributes.dislikes) attributes.dislikes = 0 - const host = new URL(url).host + const host = new URL(server.url).host const originHost = attributes.account.host expect(video.name).to.equal(attributes.name) @@ -719,8 +165,7 @@ async function completeVideoCheck ( expect(video.originallyPublishedAt).to.be.null } - const res = await getVideo(url, video.uuid) - const videoDetails: VideoDetails = res.body + const videoDetails = await server.videos.get({ id: video.uuid }) expect(videoDetails.files).to.have.lengthOf(attributes.files.length) expect(videoDetails.tags).to.deep.equal(attributes.tags) @@ -745,18 +190,16 @@ async function completeVideoCheck ( expect(file.magnetUri).to.have.lengthOf.above(2) - expect(file.torrentDownloadUrl).to.equal(`http://${host}/download/torrents/${videoDetails.uuid}-${file.resolution.id}.torrent`) - expect(file.torrentUrl).to.equal(`http://${host}/lazy-static/torrents/${videoDetails.uuid}-${file.resolution.id}.torrent`) + expect(file.torrentDownloadUrl).to.match(new RegExp(`http://${host}/download/torrents/${uuidRegex}-${file.resolution.id}.torrent`)) + expect(file.torrentUrl).to.match(new RegExp(`http://${host}/lazy-static/torrents/${uuidRegex}-${file.resolution.id}.torrent`)) - expect(file.fileUrl).to.equal(`http://${originHost}/static/webseed/${videoDetails.uuid}-${file.resolution.id}${extension}`) - expect(file.fileDownloadUrl).to.equal(`http://${originHost}/download/videos/${videoDetails.uuid}-${file.resolution.id}${extension}`) + expect(file.fileUrl).to.match(new RegExp(`http://${originHost}/static/webseed/${uuidRegex}-${file.resolution.id}${extension}`)) + expect(file.fileDownloadUrl).to.match(new RegExp(`http://${originHost}/download/videos/${uuidRegex}-${file.resolution.id}${extension}`)) await Promise.all([ makeRawRequest(file.torrentUrl, 200), makeRawRequest(file.torrentDownloadUrl, 200), - makeRawRequest(file.metadataUrl, 200), - // Backward compatibility - makeRawRequest(`http://${originHost}/static/torrents/${videoDetails.uuid}-${file.resolution.id}.torrent`, 200) + makeRawRequest(file.metadataUrl, 200) ]) expect(file.resolution.id).to.equal(attributeFile.resolution) @@ -776,149 +219,34 @@ async function completeVideoCheck ( } expect(videoDetails.thumbnailPath).to.exist - await testImage(url, attributes.thumbnailfile || attributes.fixture, videoDetails.thumbnailPath) + await testImage(server.url, attributes.thumbnailfile || attributes.fixture, videoDetails.thumbnailPath) if (attributes.previewfile) { expect(videoDetails.previewPath).to.exist - await testImage(url, attributes.previewfile, videoDetails.previewPath) + await testImage(server.url, attributes.previewfile, videoDetails.previewPath) } } -async function videoUUIDToId (url: string, id: number | string) { - if (validator.isUUID('' + id) === false) return id - - const res = await getVideo(url, id) - return res.body.id -} - -async function uploadVideoAndGetId (options: { - server: ServerInfo - videoName: string - nsfw?: boolean - privacy?: VideoPrivacy - token?: string - fixture?: string -}) { - const videoAttrs: any = { name: options.videoName } - if (options.nsfw) videoAttrs.nsfw = options.nsfw - if (options.privacy) videoAttrs.privacy = options.privacy - if (options.fixture) videoAttrs.fixture = options.fixture - - const res = await uploadVideo(options.server.url, options.token || options.server.accessToken, videoAttrs) - - return res.body.video as { id: number, uuid: string, shortUUID: string } -} - -async function getLocalIdByUUID (url: string, uuid: string) { - const res = await getVideo(url, uuid) - - return res.body.id -} - // serverNumber starts from 1 -async function uploadRandomVideoOnServers (servers: ServerInfo[], serverNumber: number, additionalParams: any = {}) { +async function uploadRandomVideoOnServers ( + servers: PeerTubeServer[], + serverNumber: number, + additionalParams?: VideoEdit & { prefixName?: string } +) { const server = servers.find(s => s.serverNumber === serverNumber) - const res = await uploadRandomVideo(server, false, additionalParams) + const res = await server.videos.randomUpload({ wait: false, additionalParams }) await waitJobs(servers) return res } -async function uploadRandomVideo (server: ServerInfo, wait = true, additionalParams: any = {}) { - const prefixName = additionalParams.prefixName || '' - const name = prefixName + buildUUID() - - const data = Object.assign({ name }, additionalParams) - const res = await uploadVideo(server.url, server.accessToken, data) - - if (wait) await waitJobs([ server ]) - - return { uuid: res.body.video.uuid, name } -} - // --------------------------------------------------------------------------- export { - getVideoDescription, - getVideoCategories, - uploadRandomVideo, - getVideoLicences, - videoUUIDToId, - getVideoPrivacies, - getVideoLanguages, - getMyVideos, - getAccountVideos, - getVideoChannelVideos, - getVideo, - getVideoFileMetadataUrl, - getVideoWithToken, - getVideosList, - removeAllVideos, checkUploadVideoParam, - getVideosListPagination, - getVideosListSort, - removeVideo, - getVideosListWithToken, - uploadVideo, - sendResumableChunks, - getVideosWithFilters, - uploadRandomVideoOnServers, - updateVideo, - rateVideo, - viewVideo, - parseTorrentVideo, - getLocalVideos, completeVideoCheck, + uploadRandomVideoOnServers, checkVideoFilesWereRemoved, - getPlaylistVideos, - getMyVideosWithFilter, - uploadVideoAndGetId, - getLocalIdByUUID, - getVideoIdFromUUID, - prepareResumableUpload -} - -// --------------------------------------------------------------------------- - -function buildUploadReq (req: request.Test, attributes: VideoAttributes) { - - for (const key of [ 'name', 'support', 'channelId', 'description', 'originallyPublishedAt' ]) { - if (attributes[key] !== undefined) { - req.field(key, attributes[key]) - } - } - - for (const key of [ 'nsfw', 'commentsEnabled', 'downloadEnabled', 'waitTranscoding' ]) { - if (attributes[key] !== undefined) { - req.field(key, JSON.stringify(attributes[key])) - } - } - - for (const key of [ 'language', 'privacy', 'category', 'licence' ]) { - if (attributes[key] !== undefined) { - req.field(key, attributes[key].toString()) - } - } - - const tags = attributes.tags || [] - for (let i = 0; i < tags.length; i++) { - req.field('tags[' + i + ']', attributes.tags[i]) - } - - for (const key of [ 'thumbnailfile', 'previewfile' ]) { - if (attributes[key] !== undefined) { - req.attach(key, buildAbsoluteFixturePath(attributes[key])) - } - } - - if (attributes.scheduleUpdate) { - if (attributes.scheduleUpdate.updateAt) { - req.field('scheduleUpdate[updateAt]', attributes.scheduleUpdate.updateAt) - } - - if (attributes.scheduleUpdate.privacy) { - req.field('scheduleUpdate[privacy]', attributes.scheduleUpdate.privacy) - } - } + saveVideoInServers } diff --git a/shared/core-utils/miscs/http-error-codes.ts b/shared/models/http/http-error-codes.ts similarity index 100% rename from shared/core-utils/miscs/http-error-codes.ts rename to shared/models/http/http-error-codes.ts diff --git a/shared/core-utils/miscs/http-methods.ts b/shared/models/http/http-methods.ts similarity index 100% rename from shared/core-utils/miscs/http-methods.ts rename to shared/models/http/http-methods.ts diff --git a/shared/models/http/index.ts b/shared/models/http/index.ts new file mode 100644 index 000000000..ec991afe0 --- /dev/null +++ b/shared/models/http/index.ts @@ -0,0 +1,2 @@ +export * from './http-error-codes' +export * from './http-methods' diff --git a/shared/models/index.ts b/shared/models/index.ts index 5c2bc480e..78723d830 100644 --- a/shared/models/index.ts +++ b/shared/models/index.ts @@ -4,6 +4,7 @@ export * from './bulk' export * from './common' export * from './custom-markup' export * from './feeds' +export * from './http' export * from './joinpeertube' export * from './moderation' export * from './overviews' diff --git a/shared/models/plugins/server/managers/plugin-playlist-privacy-manager.model.ts b/shared/models/plugins/server/managers/plugin-playlist-privacy-manager.model.ts index 4703c0a8b..35247c1e3 100644 --- a/shared/models/plugins/server/managers/plugin-playlist-privacy-manager.model.ts +++ b/shared/models/plugins/server/managers/plugin-playlist-privacy-manager.model.ts @@ -1,8 +1,12 @@ import { VideoPlaylistPrivacy } from '../../../videos/playlist/video-playlist-privacy.model' +import { ConstantManager } from '../plugin-constant-manager.model' -export interface PluginPlaylistPrivacyManager { - // PUBLIC = 1, - // UNLISTED = 2, - // PRIVATE = 3 +export interface PluginPlaylistPrivacyManager extends ConstantManager { + /** + * PUBLIC = 1, + * UNLISTED = 2, + * PRIVATE = 3 + * @deprecated use `deleteConstant` instead + */ deletePlaylistPrivacy: (privacyKey: VideoPlaylistPrivacy) => boolean } diff --git a/shared/models/plugins/server/managers/plugin-video-category-manager.model.ts b/shared/models/plugins/server/managers/plugin-video-category-manager.model.ts index 201bfa979..cf3d828fe 100644 --- a/shared/models/plugins/server/managers/plugin-video-category-manager.model.ts +++ b/shared/models/plugins/server/managers/plugin-video-category-manager.model.ts @@ -1,5 +1,13 @@ -export interface PluginVideoCategoryManager { +import { ConstantManager } from '../plugin-constant-manager.model' + +export interface PluginVideoCategoryManager extends ConstantManager { + /** + * @deprecated use `addConstant` instead + */ addCategory: (categoryKey: number, categoryLabel: string) => boolean + /** + * @deprecated use `deleteConstant` instead + */ deleteCategory: (categoryKey: number) => boolean } diff --git a/shared/models/plugins/server/managers/plugin-video-language-manager.model.ts b/shared/models/plugins/server/managers/plugin-video-language-manager.model.ts index 3fd577a79..69fc8e503 100644 --- a/shared/models/plugins/server/managers/plugin-video-language-manager.model.ts +++ b/shared/models/plugins/server/managers/plugin-video-language-manager.model.ts @@ -1,5 +1,13 @@ -export interface PluginVideoLanguageManager { +import { ConstantManager } from '../plugin-constant-manager.model' + +export interface PluginVideoLanguageManager extends ConstantManager { + /** + * @deprecated use `addConstant` instead + */ addLanguage: (languageKey: string, languageLabel: string) => boolean + /** + * @deprecated use `deleteConstant` instead + */ deleteLanguage: (languageKey: string) => boolean } diff --git a/shared/models/plugins/server/managers/plugin-video-licence-manager.model.ts b/shared/models/plugins/server/managers/plugin-video-licence-manager.model.ts index 82a634d3a..6efeadd7d 100644 --- a/shared/models/plugins/server/managers/plugin-video-licence-manager.model.ts +++ b/shared/models/plugins/server/managers/plugin-video-licence-manager.model.ts @@ -1,5 +1,13 @@ -export interface PluginVideoLicenceManager { +import { ConstantManager } from '../plugin-constant-manager.model' + +export interface PluginVideoLicenceManager extends ConstantManager { + /** + * @deprecated use `addLicence` instead + */ addLicence: (licenceKey: number, licenceLabel: string) => boolean + /** + * @deprecated use `deleteLicence` instead + */ deleteLicence: (licenceKey: number) => boolean } diff --git a/shared/models/plugins/server/managers/plugin-video-privacy-manager.model.ts b/shared/models/plugins/server/managers/plugin-video-privacy-manager.model.ts index 7717115e3..a237037db 100644 --- a/shared/models/plugins/server/managers/plugin-video-privacy-manager.model.ts +++ b/shared/models/plugins/server/managers/plugin-video-privacy-manager.model.ts @@ -1,9 +1,13 @@ import { VideoPrivacy } from '../../../videos/video-privacy.enum' +import { ConstantManager } from '../plugin-constant-manager.model' -export interface PluginVideoPrivacyManager { - // PUBLIC = 1 - // UNLISTED = 2 - // PRIVATE = 3 - // INTERNAL = 4 +export interface PluginVideoPrivacyManager extends ConstantManager { + /** + * PUBLIC = 1, + * UNLISTED = 2, + * PRIVATE = 3 + * INTERNAL = 4 + * @deprecated use `deleteConstant` instead + */ deletePrivacy: (privacyKey: VideoPrivacy) => boolean } diff --git a/shared/models/plugins/server/plugin-constant-manager.model.ts b/shared/models/plugins/server/plugin-constant-manager.model.ts new file mode 100644 index 000000000..4de3ce38f --- /dev/null +++ b/shared/models/plugins/server/plugin-constant-manager.model.ts @@ -0,0 +1,7 @@ +export interface ConstantManager { + addConstant: (key: K, label: string) => boolean + deleteConstant: (key: K) => boolean + getConstantValue: (key: K) => string + getConstants: () => Record + resetConstants: () => void +} diff --git a/shared/models/plugins/server/server-hook.model.ts b/shared/models/plugins/server/server-hook.model.ts index 5f29534b5..562c6eb12 100644 --- a/shared/models/plugins/server/server-hook.model.ts +++ b/shared/models/plugins/server/server-hook.model.ts @@ -18,6 +18,10 @@ export const serverFilterHookObject = { 'filter:api.user.me.videos.list.params': true, 'filter:api.user.me.videos.list.result': true, + // Filter params/result used to list overview videos for the REST API + 'filter:api.overviews.videos.list.params': true, + 'filter:api.overviews.videos.list.result': true, + // Filter params/results to search videos/channels in the DB or on the remote index 'filter:api.search.videos.local.list.params': true, 'filter:api.search.videos.local.list.result': true, diff --git a/shared/models/search/video-channels-search-query.model.ts b/shared/models/search/video-channels-search-query.model.ts index 8f93c4bd5..b68a1e80b 100644 --- a/shared/models/search/video-channels-search-query.model.ts +++ b/shared/models/search/video-channels-search-query.model.ts @@ -1,9 +1,18 @@ import { SearchTargetQuery } from './search-target-query.model' export interface VideoChannelsSearchQuery extends SearchTargetQuery { - search: string + search?: string start?: number count?: number sort?: string + + host?: string + handles?: string[] +} + +export interface VideoChannelsSearchQueryAfterSanitize extends VideoChannelsSearchQuery { + start: number + count: number + sort: string } diff --git a/shared/models/search/video-playlists-search-query.model.ts b/shared/models/search/video-playlists-search-query.model.ts index 31f05218e..d9027eb5b 100644 --- a/shared/models/search/video-playlists-search-query.model.ts +++ b/shared/models/search/video-playlists-search-query.model.ts @@ -1,9 +1,20 @@ import { SearchTargetQuery } from './search-target-query.model' export interface VideoPlaylistsSearchQuery extends SearchTargetQuery { - search: string + search?: string start?: number count?: number sort?: string + + host?: string + + // UUIDs or short UUIDs + uuids?: string[] +} + +export interface VideoPlaylistsSearchQueryAfterSanitize extends VideoPlaylistsSearchQuery { + start: number + count: number + sort: string } diff --git a/shared/models/search/videos-common-query.model.ts b/shared/models/search/videos-common-query.model.ts index bd02489ea..2f2e9a934 100644 --- a/shared/models/search/videos-common-query.model.ts +++ b/shared/models/search/videos-common-query.model.ts @@ -21,6 +21,14 @@ export interface VideosCommonQuery { tagsAllOf?: string[] filter?: VideoFilter + + skipCount?: boolean +} + +export interface VideosCommonQueryAfterSanitize extends VideosCommonQuery { + start: number + count: number + sort: string } export interface VideosWithSearchCommonQuery extends VideosCommonQuery { diff --git a/shared/models/search/videos-search-query.model.ts b/shared/models/search/videos-search-query.model.ts index 406f6cab2..a5436879d 100644 --- a/shared/models/search/videos-search-query.model.ts +++ b/shared/models/search/videos-search-query.model.ts @@ -4,6 +4,8 @@ import { VideosCommonQuery } from './videos-common-query.model' export interface VideosSearchQuery extends SearchTargetQuery, VideosCommonQuery { search?: string + host?: string + startDate?: string // ISO 8601 endDate?: string // ISO 8601 @@ -12,4 +14,13 @@ export interface VideosSearchQuery extends SearchTargetQuery, VideosCommonQuery durationMin?: number // seconds durationMax?: number // seconds + + // UUIDs or short UUIDs + uuids?: string[] +} + +export interface VideosSearchQueryAfterSanitize extends VideosSearchQuery { + start: number + count: number + sort: string } diff --git a/shared/models/server/debug.model.ts b/shared/models/server/debug.model.ts index 7ceff9137..2ecabdeca 100644 --- a/shared/models/server/debug.model.ts +++ b/shared/models/server/debug.model.ts @@ -1,5 +1,6 @@ export interface Debug { ip: string + activityPubMessagesWaiting: number } export interface SendDebugCommand { diff --git a/shared/models/server/index.ts b/shared/models/server/index.ts index 06bf5c599..0f7646c7a 100644 --- a/shared/models/server/index.ts +++ b/shared/models/server/index.ts @@ -10,4 +10,5 @@ export * from './peertube-problem-document.model' export * from './server-config.model' export * from './server-debug.model' export * from './server-error-code.enum' +export * from './server-follow-create.model' export * from './server-stats.model' diff --git a/shared/models/server/peertube-problem-document.model.ts b/shared/models/server/peertube-problem-document.model.ts index e391d5aad..8dd96f7a3 100644 --- a/shared/models/server/peertube-problem-document.model.ts +++ b/shared/models/server/peertube-problem-document.model.ts @@ -1,4 +1,4 @@ -import { HttpStatusCode } from '../../core-utils' +import { HttpStatusCode } from '../../models' import { OAuth2ErrorCode, ServerErrorCode } from './server-error-code.enum' export interface PeerTubeProblemDocumentData { diff --git a/shared/models/server/server-follow-create.model.ts b/shared/models/server/server-follow-create.model.ts new file mode 100644 index 000000000..3f90c7d6f --- /dev/null +++ b/shared/models/server/server-follow-create.model.ts @@ -0,0 +1,4 @@ +export interface ServerFollowCreate { + hosts?: string[] + handles?: string[] +} diff --git a/shared/models/users/index.ts b/shared/models/users/index.ts index a9d578054..b61a8cd40 100644 --- a/shared/models/users/index.ts +++ b/shared/models/users/index.ts @@ -1,3 +1,4 @@ +export * from './user-create-result.model' export * from './user-create.model' export * from './user-flag.model' export * from './user-login.model' diff --git a/shared/models/users/user-create-result.model.ts b/shared/models/users/user-create-result.model.ts new file mode 100644 index 000000000..835b241ed --- /dev/null +++ b/shared/models/users/user-create-result.model.ts @@ -0,0 +1,7 @@ +export interface UserCreateResult { + id: number + + account: { + id: number + } +} diff --git a/shared/models/users/user-notification.model.ts b/shared/models/users/user-notification.model.ts index 8b33e3fbd..5820589fe 100644 --- a/shared/models/users/user-notification.model.ts +++ b/shared/models/users/user-notification.model.ts @@ -36,6 +36,7 @@ export const enum UserNotificationType { export interface VideoInfo { id: number uuid: string + shortUUID: string name: string } @@ -82,11 +83,7 @@ export interface UserNotification { comment?: { threadId: number - video: { - id: number - uuid: string - name: string - } + video: VideoInfo } account?: ActorInfo diff --git a/shared/models/videos/channel/index.ts b/shared/models/videos/channel/index.ts index 9dbaa42da..6cdabffbd 100644 --- a/shared/models/videos/channel/index.ts +++ b/shared/models/videos/channel/index.ts @@ -1,3 +1,4 @@ +export * from './video-channel-create-result.model' export * from './video-channel-create.model' export * from './video-channel-update.model' export * from './video-channel.model' diff --git a/shared/models/videos/channel/video-channel-create-result.model.ts b/shared/models/videos/channel/video-channel-create-result.model.ts new file mode 100644 index 000000000..e3d7aeb4c --- /dev/null +++ b/shared/models/videos/channel/video-channel-create-result.model.ts @@ -0,0 +1,3 @@ +export interface VideoChannelCreateResult { + id: number +} diff --git a/shared/models/videos/comment/index.ts b/shared/models/videos/comment/index.ts index 7b9261a36..80c6c0724 100644 --- a/shared/models/videos/comment/index.ts +++ b/shared/models/videos/comment/index.ts @@ -1 +1,2 @@ +export * from './video-comment-create.model' export * from './video-comment.model' diff --git a/shared/models/videos/comment/video-comment-create.model.ts b/shared/models/videos/comment/video-comment-create.model.ts new file mode 100644 index 000000000..1f0135405 --- /dev/null +++ b/shared/models/videos/comment/video-comment-create.model.ts @@ -0,0 +1,3 @@ +export interface VideoCommentCreate { + text: string +} diff --git a/shared/models/videos/comment/video-comment.model.ts b/shared/models/videos/comment/video-comment.model.ts index 79c0e4c0a..737cfe098 100644 --- a/shared/models/videos/comment/video-comment.model.ts +++ b/shared/models/videos/comment/video-comment.model.ts @@ -1,3 +1,4 @@ +import { ResultList } from '../../common' import { Account } from '../../actors' export interface VideoComment { @@ -36,11 +37,9 @@ export interface VideoCommentAdmin { } } +export type VideoCommentThreads = ResultList & { totalNotDeletedComments: number } + export interface VideoCommentThreadTree { comment: VideoComment children: VideoCommentThreadTree[] } - -export interface VideoCommentCreate { - text: string -} diff --git a/shared/models/videos/playlist/index.ts b/shared/models/videos/playlist/index.ts index f11a4bd28..a9e8ce496 100644 --- a/shared/models/videos/playlist/index.ts +++ b/shared/models/videos/playlist/index.ts @@ -1,6 +1,7 @@ export * from './video-exist-in-playlist.model' export * from './video-playlist-create-result.model' export * from './video-playlist-create.model' +export * from './video-playlist-element-create-result.model' export * from './video-playlist-element-create.model' export * from './video-playlist-element-update.model' export * from './video-playlist-element.model' diff --git a/shared/models/videos/playlist/video-playlist-element-create-result.model.ts b/shared/models/videos/playlist/video-playlist-element-create-result.model.ts new file mode 100644 index 000000000..dc475e7d8 --- /dev/null +++ b/shared/models/videos/playlist/video-playlist-element-create-result.model.ts @@ -0,0 +1,3 @@ +export interface VideoPlaylistElementCreateResult { + id: number +} diff --git a/shared/models/videos/video-update.model.ts b/shared/models/videos/video-update.model.ts index e21ccae04..86653b959 100644 --- a/shared/models/videos/video-update.model.ts +++ b/shared/models/videos/video-update.model.ts @@ -1,5 +1,6 @@ import { VideoPrivacy } from './video-privacy.enum' import { VideoScheduleUpdate } from './video-schedule-update.model' + export interface VideoUpdate { name?: string category?: number diff --git a/support/doc/api/openapi.yaml b/support/doc/api/openapi.yaml index 99a725ead..76e78fe53 100644 --- a/support/doc/api/openapi.yaml +++ b/support/doc/api/openapi.yaml @@ -716,7 +716,7 @@ paths: - admin tags: - Instance Follows - summary: Follow a list of servers + summary: Follow a list of actors (PeerTube instance, channel or account) responses: '204': description: successful operation @@ -734,28 +734,32 @@ paths: type: string format: hostname uniqueItems: true + handles: + type: array + items: + type: string + uniqueItems: true - '/server/following/{host}': + '/server/following/{hostOrHandle}': delete: - summary: Unfollow a server + summary: Unfollow an actor (PeerTube instance, channel or account) security: - OAuth2: - admin tags: - Instance Follows parameters: - - name: host + - name: hostOrHandle in: path required: true - description: The host to unfollow + description: The hostOrHandle to unfollow schema: type: string - format: hostname responses: '204': description: successful operation '404': - description: host not found + description: host or handle not found /users: post: diff --git a/support/doc/dependencies.md b/support/doc/dependencies.md index 8ea0c047d..d6c084cd7 100644 --- a/support/doc/dependencies.md +++ b/support/doc/dependencies.md @@ -151,6 +151,51 @@ sudo systemctl enable --now redis sudo systemctl enable --now postgresql ``` +## Rocky Linux 8.4 + +1. Pull the latest updates: +``` +sudo dnf update -y +``` + +2. Install NodeJS 12.x (why 12 and not 14? Not sure...): +``` +sudo dnf module install -y nodejs:12 +``` + +3. Install yarn: +``` +sudo npm install --global yarn +``` + +4. Install or compile ffmpeg (if you want to compile... enjoy): +``` +sudo dnf install -y epel-release +sudo dnf --enablerepo=powertools install -y SDL2 SDL2-devel +sudo dnf install -y --nogpgcheck https://download1.rpmfusion.org/free/el/rpmfusion-free-release-8.noarch.rpm https://download1.rpmfusion.org/nonfree/el/rpmfusion-nonfree-release-8.noarch.rpm +sudo dnf install -y ffmpeg +sudo dnf update -y +``` + +5. Install PostgreSQL and Python3 and other stuff: +``` +sudo dnf install -y nginx postgresql postgresql-server postgresql-contrib openssl gcc-c++ make wget redis git python3 +sudo ln -s /usr/bin/python3 /usr/bin/python +sudo PGSETUP_INITDB_OPTIONS='--auth-host=md5' postgresql-setup --initdb --unit postgresql +sudo systemctl enable --now redis +sudo systemctl enable --now postgresql +``` + +6. Configure the peertube user: +``` +sudo useradd -m -d /var/www/peertube -s /bin/bash -p peertube peertube +``` + +7. Unknown missing steps: +- Steps missing here... these were adapted from the CentOS 8 steps which abruptly ended. +- /var/www/peertube does not exist yet (expected? done in future steps? documentation?). +- Nothing about Certbot, NGINX, Firewall settings, and etc. +- Hopefully someone can suggest what is missing here with some hints so I can add it? ## Fedora diff --git a/support/doc/plugins/guide.md b/support/doc/plugins/guide.md index 568c0662f..85aaf9f02 100644 --- a/support/doc/plugins/guide.md +++ b/support/doc/plugins/guide.md @@ -234,21 +234,29 @@ function register ({ #### Update video constants -You can add/delete video categories, licences or languages using the appropriate managers: +You can add/delete video categories, licences or languages using the appropriate constant managers: ```js -function register (...) { - videoLanguageManager.addLanguage('al_bhed', 'Al Bhed') - videoLanguageManager.deleteLanguage('fr') +function register ({ + videoLanguageManager, + videoCategoryManager, + videoLicenceManager, + videoPrivacyManager, + playlistPrivacyManager +}) { + videoLanguageManager.addConstant('al_bhed', 'Al Bhed') + videoLanguageManager.deleteConstant('fr') - videoCategoryManager.addCategory(42, 'Best category') - videoCategoryManager.deleteCategory(1) // Music + videoCategoryManager.addConstant(42, 'Best category') + videoCategoryManager.deleteConstant(1) // Music + videoCategoryManager.resetConstants() // Reset to initial categories + videoCategoryManager.getConstants() // Retrieve all category constants - videoLicenceManager.addLicence(42, 'Best licence') - videoLicenceManager.deleteLicence(7) // Public domain + videoLicenceManager.addConstant(42, 'Best licence') + videoLicenceManager.deleteConstant(7) // Public domain - videoPrivacyManager.deletePrivacy(2) // Remove Unlisted video privacy - playlistPrivacyManager.deletePlaylistPrivacy(3) // Remove Private video playlist privacy + videoPrivacyManager.deleteConstant(2) // Remove Unlisted video privacy + playlistPrivacyManager.deleteConstant(3) // Remove Private video playlist privacy } ``` diff --git a/tsconfig.json b/tsconfig.json index d305722c4..32e4a42e4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,7 @@ "emitDecoratorMetadata": true, "importHelpers": true, "removeComments": true, + "strictBindCallApply": true, "outDir": "./dist", "lib": [ "dom", diff --git a/yarn.lock b/yarn.lock index f68741038..68f17d414 100644 --- a/yarn.lock +++ b/yarn.lock @@ -63,10 +63,10 @@ dependencies: "@babel/highlight" "^7.14.5" -"@babel/helper-validator-identifier@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz#d0f0e277c512e0c938277faa85a3968c9a44c0e8" - integrity sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg== +"@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.8": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.8.tgz#32be33a756f29e278a0d644fa08a2c9e0f88a34c" + integrity sha512-ZGy6/XQjllhYQrNw/3zfWRwZCTVSiBLZ9DHVZxn9n2gip/7ab8mv2TWlKPIBk26RwedCBoWdjLmn+t9na2Gcow== "@babel/highlight@^7.10.4", "@babel/highlight@^7.14.5": version "7.14.5" @@ -78,23 +78,23 @@ js-tokens "^4.0.0" "@babel/parser@^7.6.0", "@babel/parser@^7.9.6": - version "7.14.7" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.7.tgz#6099720c8839ca865a2637e6c85852ead0bdb595" - integrity sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA== + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.8.tgz#66fd41666b2d7b840bd5ace7f7416d5ac60208d4" + integrity sha512-syoCQFOoo/fzkWDeM0dLEZi5xqurb5vuyzwIMNZRNun+N/9A4cUZeQaE7dTrB8jGaKuJRBtEOajtnmw0I5hvvA== "@babel/runtime@^7.7.2": - version "7.14.6" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.6.tgz#535203bc0892efc7dec60bdc27b2ecf6e409062d" - integrity sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg== + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.8.tgz#7119a56f421018852694290b9f9148097391b446" + integrity sha512-twj3L8Og5SaCRCErB4x4ajbvBIVV77CGeFglHpeg5WC5FF8TZzBWXtTJ4MqaD9QszLYTtr+IsaAL2rEUevb+eg== dependencies: regenerator-runtime "^0.13.4" "@babel/types@^7.6.1", "@babel/types@^7.9.6": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.14.5.tgz#3bb997ba829a2104cedb20689c4a5b8121d383ff" - integrity sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg== + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.14.8.tgz#38109de8fcadc06415fbd9b74df0065d4d41c728" + integrity sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q== dependencies: - "@babel/helper-validator-identifier" "^7.14.5" + "@babel/helper-validator-identifier" "^7.14.8" to-fast-properties "^2.0.0" "@dabh/diagnostics@^2.0.2": @@ -107,18 +107,18 @@ kuler "^2.0.0" "@digitalbazaar/http-client@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@digitalbazaar/http-client/-/http-client-1.1.0.tgz#cac383b24ace04b18b919deab773462b03d3d7b0" - integrity sha512-ks7hqa6hm9NyULdbm9qL6TRS8rADzBw8R0lETvUgvdNXu9H62XG2YqoKRDThtfgWzWxLwRJ3Z2o4ev81dZZbyQ== + version "1.2.0" + resolved "https://registry.yarnpkg.com/@digitalbazaar/http-client/-/http-client-1.2.0.tgz#1ea3661e77000a15bd892a294f20dc6cc5d1c93b" + integrity sha512-W9KQQ5pUJcaR0I4c2HPJC0a7kRbZApIorZgPnEDwMBgj16iQzutGLrCXYaZOmxqVLVNqqlQ4aUJh+HBQZy4W6Q== dependencies: esm "^3.2.22" ky "^0.25.1" ky-universal "^0.8.2" -"@eslint/eslintrc@^0.4.2": - version "0.4.2" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.2.tgz#f63d0ef06f5c0c57d76c4ab5f63d3835c51b0179" - integrity sha512-8nmGq/4ycLpIwzvhI4tNDmQztZ8sp+hI7cyG8i1nQDhkAbRzHpXPidRAHlNvCZQpJTKw5ItIpMw9RSToGF00mg== +"@eslint/eslintrc@^0.4.3": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" + integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw== dependencies: ajv "^6.12.4" debug "^4.1.1" @@ -131,9 +131,9 @@ strip-json-comments "^3.1.1" "@hapi/boom@^9.1.2": - version "9.1.2" - resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-9.1.2.tgz#48bd41d67437164a2d636e3b5bc954f8c8dc5e38" - integrity sha512-uJEJtiNHzKw80JpngDGBCGAmWjBtzxDCz17A9NO2zCi8LLBlb5Frpq4pXwyN+2JQMod4pKz5BALwyneCgDg89Q== + version "9.1.3" + resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-9.1.3.tgz#22cad56e39b7a4819161a99b1db19eaaa9b6cc6e" + integrity sha512-RlrGyZ603hE/eRTZtTltocRm50HHmrmL3kGOP0SQ9MasazlW1mt/fkv4C5P/6rnpFXjwld/POFX1C8tMZE3ldg== dependencies: "@hapi/hoek" "9.x.x" @@ -142,6 +142,20 @@ resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.0.tgz#f3933a44e365864f4dad5db94158106d511e8131" integrity sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug== +"@humanwhocodes/config-array@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" + integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg== + dependencies: + "@humanwhocodes/object-schema" "^1.2.0" + debug "^4.1.1" + minimatch "^3.0.4" + +"@humanwhocodes/object-schema@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz#87de7af9c231826fdd68ac7258f77c429e0e5fcf" + integrity sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w== + "@jimp/bmp@^0.16.1": version "0.16.1" resolved "https://registry.yarnpkg.com/@jimp/bmp/-/bmp-0.16.1.tgz#6e2da655b2ba22e721df0795423f34e92ef13768" @@ -515,9 +529,9 @@ integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== "@nodelib/fs.walk@^1.2.3": - version "1.2.7" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.7.tgz#94c23db18ee4653e129abd26fb06f870ac9e1ee2" - integrity sha512-BTIhocbPBSrRmHxOAJFtR18oLhxTtAFDAvL8hY1S3iU8k+E60W/YFs4jrixGzQjMpF4qPXxIQHcjVD9dz1C2QA== + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== dependencies: "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" @@ -532,9 +546,9 @@ node-fetch "^2.6.1" "@openapitools/openapi-generator-cli@^2.1.4": - version "2.3.5" - resolved "https://registry.yarnpkg.com/@openapitools/openapi-generator-cli/-/openapi-generator-cli-2.3.5.tgz#20f3974879ae22beb18a0f5a3685cabacf380cef" - integrity sha512-b9dX47j3+g08qM/EMg/Ftw2qBOpfhKB31xyPJ7+kBvGvcoNoMed3aPyojv1iWNfU1KlJvp6k9zJvViOND0ckGg== + version "2.3.7" + resolved "https://registry.yarnpkg.com/@openapitools/openapi-generator-cli/-/openapi-generator-cli-2.3.7.tgz#5aaf9d178545874828db7c7c84a6f9a5b8ea00a4" + integrity sha512-4B93yPXop44fhAw6CT0eciaA0dRbPw5W7p2ZZ+HZ1uk4UD50zgaTAa9pnrsnCDnNtw2cvbZM0uCOf8xQyUy8Vg== dependencies: "@nestjs/common" "7.6.18" "@nestjs/core" "7.6.18" @@ -546,10 +560,10 @@ console.table "0.10.0" fs-extra "10.0.0" glob "7.1.6" - inquirer "8.1.1" + inquirer "8.1.2" lodash "4.17.21" reflect-metadata "0.1.13" - rxjs "7.1.0" + rxjs "7.2.0" tslib "1.13.0" "@sindresorhus/is@^0.14.0": @@ -570,9 +584,9 @@ defer-to-connect "^1.0.1" "@szmarczak/http-timer@^4.0.5": - version "4.0.5" - resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.5.tgz#bfbd50211e9dfa51ba07da58a14cdfd333205152" - integrity sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ== + version "4.0.6" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" + integrity sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w== dependencies: defer-to-connect "^2.0.0" @@ -592,26 +606,19 @@ integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== "@tsconfig/node16@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.1.tgz#a6ca6a9a0ff366af433f42f5f0e124794ff6b8f1" - integrity sha512-FTgBI767POY/lKNDNbIzgAX6miIDBs6NTCbdlDb8TrWovHsSvaVIZDlTqym29C6UqhzwcJx4CYr+AlrMywA0cA== - -"@types/apicache@^1.2.0": - version "1.2.2" - resolved "https://registry.yarnpkg.com/@types/apicache/-/apicache-1.2.2.tgz#b820659b1d95e66ec0e71dcd0317e9d30f0c154b" - integrity sha512-+rjhMdbx7m7pKnNiQCSiKE21mGmMzsfCU871h5BCj4guhAj423j61Dq0Yrr9CnLiDwUhSKtkXWudr9SQE0/IoA== - dependencies: - "@types/redis" "*" + version "1.0.2" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" + integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== "@types/async-lock@^1.1.0": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@types/async-lock/-/async-lock-1.1.2.tgz#cbc26a34b11b83b28f7783a843c393b443ef8bef" - integrity sha512-j9n4bb6RhgFIydBe0+kpjnBPYumDaDyU8zvbWykyVMkku+c2CSu31MZkLeaBfqIwU+XCxlDpYDfyMQRkM0AkeQ== + version "1.1.3" + resolved "https://registry.yarnpkg.com/@types/async-lock/-/async-lock-1.1.3.tgz#0d86017cf87abbcb941c55360e533d37a3f23b3d" + integrity sha512-UpeDcjGKsYEQMeqEbfESm8OWJI305I7b9KE4ji3aBjoKWyN5CTdn8izcA1FM1DVDne30R5fNEnIy89vZw5LXJQ== "@types/async@^3.0.0": - version "3.2.6" - resolved "https://registry.yarnpkg.com/@types/async/-/async-3.2.6.tgz#1d49339846c6aa0b0a8a08303c21176f1b64dc4f" - integrity sha512-ZkrXnZLC1mc4b9QLKaSrsxV4oxTRs10OI2kgSApT8G0v1jrmqppSHUVQ15kLorzsFBTjvf7OKF4kAibuuNQ+xA== + version "3.2.7" + resolved "https://registry.yarnpkg.com/@types/async/-/async-3.2.7.tgz#f784478440d313941e7b12c2e4db53b0ed55637b" + integrity sha512-a+MBBfOTs3ShFMlbH9qsRVFkjIUunEtxrBT0gxRx1cntjKRg2WApuGmNYzHkwKaIhMi3SMbKktaD/rLObQMwIw== "@types/bcrypt@^5.0.0": version "5.0.0" @@ -628,34 +635,34 @@ "@types/node" "*" "@types/bluebird@^3.5.33": - version "3.5.35" - resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.35.tgz#3964c48372bf62d60616d8673dd77a9719ebac9b" - integrity sha512-2WeeXK7BuQo7yPI4WGOBum90SzF/f8rqlvpaXx4rjeTmNssGRDHWf7fgDUH90xMB3sUOu716fUK5d+OVx0+ncQ== + version "3.5.36" + resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.36.tgz#00d9301d4dc35c2f6465a8aec634bb533674c652" + integrity sha512-HBNx4lhkxN7bx6P0++W8E289foSu8kO8GCk2unhuVggO+cE7rh9DhZUyPhUxNRG9m+5B5BTKxZQ5ZP92x/mx9Q== "@types/body-parser@*", "@types/body-parser@^1.16.3": - version "1.19.0" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f" - integrity sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ== + version "1.19.1" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.1.tgz#0c0174c42a7d017b818303d4b5d969cb0b75929c" + integrity sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg== dependencies: "@types/connect" "*" "@types/node" "*" "@types/bull@^3.15.0": - version "3.15.1" - resolved "https://registry.yarnpkg.com/@types/bull/-/bull-3.15.1.tgz#3c3fd665b43ef383ca95a91b8d1448461fae0898" - integrity sha512-thZyjxikoyuDa/ptZEqtTEPUjwlDenkpPigpIyad1X5UMp7U0fXTLiDHJjZ/5yXmVPuWx0cXFXj3drmva/UJRA== + version "3.15.2" + resolved "https://registry.yarnpkg.com/@types/bull/-/bull-3.15.2.tgz#b824b0b4fc8d1d9294a20973f6ceedcba1a7f3e8" + integrity sha512-uMQ7u/4GxY2bSTMd4P2yLkyqu3GoKbwTCDkMHJJ2g9OkiMq0Vxw+C7lF4w+oNkwZzZ2k4Kw76Ncxjd6GMnc+CA== dependencies: "@types/ioredis" "*" "@types/bytes@^3.0.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@types/bytes/-/bytes-3.1.0.tgz#835a3e4aea3b4d7604aca216a78de372bff3ecc3" - integrity sha512-5YG1AiIC8HPPXRvYAIa7ehK3YMAwd0DWiPCtpuL9sgKceWLyWsVtLRA+lT4NkoanDNF9slwQ66lPizWDpgRlWA== + version "3.1.1" + resolved "https://registry.yarnpkg.com/@types/bytes/-/bytes-3.1.1.tgz#67a876422e660dc4c10a27f3e5bcfbd5455f01d0" + integrity sha512-lOGyCnw+2JVPKU3wIV0srU0NyALwTBJlVSx5DfMQOFuuohA8y9S8orImpuIQikZ0uIQ8gehrRjxgQC1rLRi11w== "@types/cacheable-request@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.1.tgz#5d22f3dded1fd3a84c0bbeb5039a7419c2c91976" - integrity sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ== + version "6.0.2" + resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.2.tgz#c324da0197de0a98a2312156536ae262429ff6b9" + integrity sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA== dependencies: "@types/http-cache-semantics" "*" "@types/keyv" "*" @@ -682,62 +689,62 @@ "@types/chai" "*" "@types/chai@*", "@types/chai@^4.0.4": - version "4.2.19" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.19.tgz#80f286b515897413c7a35bdda069cc80f2344233" - integrity sha512-jRJgpRBuY+7izT7/WNXP/LsMO9YonsstuL+xuvycDyESpoDoIAsMd7suwpB4h9oEWB+ZlPTqJJ8EHomzNhwTPQ== + version "4.2.21" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.21.tgz#9f35a5643129df132cf3b5c1ec64046ea1af0650" + integrity sha512-yd+9qKmJxm496BOV9CMNaey8TWsikaZOwMRwPHQIjcOJM9oV+fi9ZMNw3JsVnbEEbo2gRTDnGEBv8pjyn67hNg== "@types/component-emitter@^1.2.10": version "1.2.10" resolved "https://registry.yarnpkg.com/@types/component-emitter/-/component-emitter-1.2.10.tgz#ef5b1589b9f16544642e473db5ea5639107ef3ea" integrity sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg== -"@types/config@^0.0.38": - version "0.0.38" - resolved "https://registry.yarnpkg.com/@types/config/-/config-0.0.38.tgz#ca30679b21b5b297299467e3a3f1c8e2e64b9170" - integrity sha512-z2WizAfIFgSv8SQfQ8c0LlbDAcK47D/o93XW6bxZ9t3bs4fmmfAPjk1nhAIBTG84PBBCHfSPM+8g7vhLdbFokg== +"@types/config@^0.0.39": + version "0.0.39" + resolved "https://registry.yarnpkg.com/@types/config/-/config-0.0.39.tgz#aad18ceb9439329adc3d4c6b91a908a72c715612" + integrity sha512-EBHj9lSIyw62vwqCwkeJXjiV6C2m2o+RJZlRWLkHduGYiNBoMXcY6AhSLqjQQ+uPdrPYrOMYvVa41zjo00LbFQ== "@types/connect@*": - version "3.4.34" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.34.tgz#170a40223a6d666006d93ca128af2beb1d9b1901" - integrity sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ== + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== dependencies: "@types/node" "*" "@types/cookie@^0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.0.tgz#14f854c0f93d326e39da6e3b6f34f7d37513d108" - integrity sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg== + version "0.4.1" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" + integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== "@types/cookiejar@*": version "2.1.2" resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.2.tgz#66ad9331f63fe8a3d3d9d8c6e3906dd10f6446e8" integrity sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog== -"@types/cors@^2.8.8": - version "2.8.10" - resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.10.tgz#61cc8469849e5bcdd0c7044122265c39cec10cf4" - integrity sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ== +"@types/cors@^2.8.10": + version "2.8.12" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" + integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== "@types/express-rate-limit@^5.0.0": - version "5.1.2" - resolved "https://registry.yarnpkg.com/@types/express-rate-limit/-/express-rate-limit-5.1.2.tgz#51f030ce722fe298269f85378b49a34837a1d2ca" - integrity sha512-loN1dcr0WEKsbVtXNQKDef4Fmh25prfy+gESrwTDofIhAt17ngTxrsDiEZxLemmfHH279x206CdUB9XHXS9E6Q== + version "5.1.3" + resolved "https://registry.yarnpkg.com/@types/express-rate-limit/-/express-rate-limit-5.1.3.tgz#79f2ca40d90455a5798da6f8e06d8a3d35f4a1d6" + integrity sha512-H+TYy3K53uPU2TqPGFYaiWc2xJV6+bIFkDd/Ma2/h67Pa6ARk9kWE0p/K9OH1Okm0et9Sfm66fmXoAxsH2PHXg== dependencies: "@types/express" "*" "@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.18": - version "4.17.21" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.21.tgz#a427278e106bca77b83ad85221eae709a3414d42" - integrity sha512-gwCiEZqW6f7EoR8TTEfalyEhb1zA5jQJnRngr97+3pzMaO1RKoI1w2bw07TK72renMUVWcWS5mLI6rk1NqN0nA== + version "4.17.24" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz#ea41f93bf7e0d59cd5a76665068ed6aab6815c07" + integrity sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA== dependencies: "@types/node" "*" "@types/qs" "*" "@types/range-parser" "*" "@types/express@*": - version "4.17.12" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.12.tgz#4bc1bf3cd0cfe6d3f6f2853648b40db7d54de350" - integrity sha512-pTYas6FrP15B1Oa0bkN5tQMNqOcVXa9j4FTFtO8DWI9kppKib+6NJtfTOOLcwxuuYvcX2+dVG6et1SxW/Kc17Q== + version "4.17.13" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" + integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== dependencies: "@types/body-parser" "*" "@types/express-serve-static-core" "^4.17.18" @@ -755,76 +762,71 @@ "@types/serve-static" "*" "@types/fluent-ffmpeg@^2.1.16": - version "2.1.17" - resolved "https://registry.yarnpkg.com/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.17.tgz#6958dda400fe1b33c21f3683db76905cb210d053" - integrity sha512-/bdvjKw/mtBHlJ2370d04nt4CsWqU5MrwS/NtO96V01jxitJ4+iq8OFNcqc5CegeV3TQOK3uueK02kvRK+zjUg== + version "2.1.18" + resolved "https://registry.yarnpkg.com/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.18.tgz#72246c2f8c0f0a590aefc1cdbe13736c73f22a81" + integrity sha512-LTteOx3RUmnPlKkvhvW9aGOHdJYyEtIiRBVbYVO/zPU65ZYQelbPJ+zBBT+IXup7doMvxVstX7NMoUTWKZOhww== dependencies: "@types/node" "*" "@types/fs-extra@^9.0.1": - version "9.0.11" - resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.11.tgz#8cc99e103499eab9f347dbc6ca4e99fb8d2c2b87" - integrity sha512-mZsifGG4QeQ7hlkhO56u7zt/ycBgGxSVsFI/6lGTU34VtwkiqrrSDgw0+ygs8kFGWcXnFQWMrzF2h7TtDFNixA== + version "9.0.12" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.12.tgz#9b8f27973df8a7a3920e8461517ebf8a7d4fdfaf" + integrity sha512-I+bsBr67CurCGnSenZZ7v94gd3tc3+Aj2taxMT4yu4ABLuOgOjeFxX3dokG24ztSRg5tnT00sL8BszO7gSMoIw== dependencies: "@types/node" "*" "@types/http-cache-semantics@*": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz#9140779736aa2655635ee756e2467d787cfe8a2a" - integrity sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A== + version "4.0.1" + resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz#0ea7b61496902b95890dc4c3a116b60cb8dae812" + integrity sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ== "@types/ioredis@*": - version "4.26.4" - resolved "https://registry.yarnpkg.com/@types/ioredis/-/ioredis-4.26.4.tgz#a2b1ed51ddd2c707d7eaac5017cc34a0fe51558a" - integrity sha512-QFbjNq7EnOGw6d1gZZt2h26OFXjx7z+eqEnbCHSrDI1OOLEgOHMKdtIajJbuCr9uO+X9kQQRe7Lz6uxqxl5XKg== + version "4.26.6" + resolved "https://registry.yarnpkg.com/@types/ioredis/-/ioredis-4.26.6.tgz#7e332d6d24f12d79a1099834ccfa0c169ef667ed" + integrity sha512-Q9ydXL/5Mot751i7WLCm9OGTj5jlW3XBdkdEW21SkXZ8Y03srbkluFGbM3q8c+vzPW30JOLJ+NsZWHoly0+13A== dependencies: "@types/node" "*" "@types/json-schema@^7.0.6", "@types/json-schema@^7.0.7": - version "7.0.7" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" - integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== - -"@types/json5@^0.0.29": - version "0.0.29" - resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" - integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= + version "7.0.8" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.8.tgz#edf1bf1dbf4e04413ca8e5b17b3b7d7d54b59818" + integrity sha512-YSBPTLTVm2e2OoQIDYx8HaeWJ5tTToLH67kXR7zYNGupXMEHa2++G8k+DczX2cFVgalypqtyZIcU19AFcmOpmg== "@types/keyv@*": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.1.tgz#e45a45324fca9dab716ab1230ee249c9fb52cfa7" - integrity sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw== + version "3.1.2" + resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.2.tgz#5d97bb65526c20b6e0845f6b0d2ade4f28604ee5" + integrity sha512-/FvAK2p4jQOaJ6CGDHJTqZcUtbZe820qIeTg7o0Shg7drB4JHeL+V/dhSaly7NXx6u8eSee+r7coT+yuJEvDLg== dependencies: "@types/node" "*" "@types/lodash@^4.14.64": - version "4.14.170" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.170.tgz#0d67711d4bf7f4ca5147e9091b847479b87925d6" - integrity sha512-bpcvu/MKHHeYX+qeEN8GE7DIravODWdACVA1ctevD8CN24RhPZIKMn9ntfAsrvLfSX3cR5RrBKAbYm9bGs0A+Q== + version "4.14.171" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.171.tgz#f01b3a5fe3499e34b622c362a46a609fdb23573b" + integrity sha512-7eQ2xYLLI/LsicL2nejW9Wyko3lcpN6O/z0ZLHrEQsg280zIdCv1t/0m6UtBjUHokCGBQ3gYTbHzDkZ1xOBwwg== "@types/lru-cache@^5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-5.1.0.tgz#57f228f2b80c046b4a1bd5cac031f81f207f4f03" - integrity sha512-RaE0B+14ToE4l6UqdarKPnXwVDuigfFv+5j9Dze/Nqr23yyuqdNvzcZi3xB+3Agvi5R4EOgAksfv3lXX4vBt9w== + version "5.1.1" + resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-5.1.1.tgz#c48c2e27b65d2a153b19bfc1a317e30872e01eef" + integrity sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw== "@types/magnet-uri@*", "@types/magnet-uri@^5.1.1": - version "5.1.2" - resolved "https://registry.yarnpkg.com/@types/magnet-uri/-/magnet-uri-5.1.2.tgz#7860417399d52ddc0be1021d570b4ac93ffc133e" - integrity sha512-bXFPXskwHoEYP6t8rq4nWchOlbUzXkyhnfCVZmq+zb25R5pWkasw7BmTIqDKQ6RAQmq89jll1v23yLa/SvPfAw== + version "5.1.3" + resolved "https://registry.yarnpkg.com/@types/magnet-uri/-/magnet-uri-5.1.3.tgz#cdf974721012bd758c0f559cabcad7bab87f9008" + integrity sha512-FvJN1yYdLhvU6zWJ2YnWQ2GnpFLsA8bt+85WY0tLh6ehzGNrvBorjlcc53/zY43r/IKn+ctFs1nt7andwGnQCQ== dependencies: "@types/node" "*" -"@types/maildev@^0.0.2": - version "0.0.2" - resolved "https://registry.yarnpkg.com/@types/maildev/-/maildev-0.0.2.tgz#936f21d66d2c38fafdd653d5bee8b642eb1dab89" - integrity sha512-ITMKrdajIgqe5lz0BrU2xFB3yN4gX/+a2vemuPfgURlcxLn7V5i9AuzGQl2wiH2cg7zcZBqh8EHX+z3ufF7AUA== +"@types/maildev@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@types/maildev/-/maildev-0.0.3.tgz#8a7e3cc640d5388d86bcd11f6c18e40926244b87" + integrity sha512-fY5WoW3zsW686UFKf5ISIWiolaYoo+kbFE/B1rOHNJ768gdyIgu3Wol02vwanq2gFQO420iTUDaZ7ZpQbjZ57Q== dependencies: "@types/node" "*" "@types/memoizee@^0.4.2": - version "0.4.5" - resolved "https://registry.yarnpkg.com/@types/memoizee/-/memoizee-0.4.5.tgz#cb4e7031decf698c52c4f57c348180b0385aa7da" - integrity sha512-+ZzZZ3+0a7/ajBPeAAD4+LxrBsCat0EFZQtO3o0rwpIeLmDmSaM8KF/oYPuFxeUFAMiHIHFcGucFnY/8S4Hszg== + version "0.4.6" + resolved "https://registry.yarnpkg.com/@types/memoizee/-/memoizee-0.4.6.tgz#a4ba7a3ea61fa45be916f148763bec2ca38c34cf" + integrity sha512-qJezGqoi3pW9Pset2w1Gfv8jATvmHHHnpO9Dq8x8pJGyYIpiUZJqRU0NM7xenmN0AcXEe7vqshI8H98KeFLYcg== "@types/mime@^1": version "1.3.2" @@ -832,109 +834,114 @@ integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== "@types/minimatch@^3.0.3": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.4.tgz#f0ec25dbf2f0e4b18647313ac031134ca5b24b21" - integrity sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA== + version "3.0.5" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" + integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== "@types/mkdirp@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-1.0.1.tgz#0930b948914a78587de35458b86c907b6e98bbf6" - integrity sha512-HkGSK7CGAXncr8Qn/0VqNtExEE+PHMWb+qlR1faHMao7ng6P3tAaoWWBMdva0gL5h4zprjIO89GJOLXsMcDm1Q== + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-1.0.2.tgz#8d0bad7aa793abe551860be1f7ae7f3198c16666" + integrity sha512-o0K1tSO0Dx5X6xlU5F1D6625FawhC3dU3iqr25lluNv/+/QIVH8RLNEiVokgIZo+mz+87w/3Mkg/VvQS+J51fQ== dependencies: "@types/node" "*" "@types/mocha@^8.0.3": - version "8.2.2" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.2.2.tgz#91daa226eb8c2ff261e6a8cbf8c7304641e095e0" - integrity sha512-Lwh0lzzqT5Pqh6z61P3c3P5nm6fzQK/MMHl9UKeneAeInVflBSz1O2EkX6gM6xfJd7FBXBY5purtLx7fUiZ7Hw== + version "8.2.3" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.2.3.tgz#bbeb55fbc73f28ea6de601fbfa4613f58d785323" + integrity sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw== "@types/morgan@^1.7.32": - version "1.9.2" - resolved "https://registry.yarnpkg.com/@types/morgan/-/morgan-1.9.2.tgz#450f958a4d3fb0694a3ba012b09c8106f9a2885e" - integrity sha512-edtGMEdit146JwwIeyQeHHg9yID4WSolQPxpEorHmN3KuytuCHyn2ELNr5Uxy8SerniFbbkmgKMrGM933am5BQ== + version "1.9.3" + resolved "https://registry.yarnpkg.com/@types/morgan/-/morgan-1.9.3.tgz#ae04180dff02c437312bc0cfb1e2960086b2f540" + integrity sha512-BiLcfVqGBZCyNCnCH3F4o2GmDLrpy0HeBVnNlyZG4fo88ZiE9SoiBe3C+2ezuwbjlEyT+PDZ17//TAlRxAn75Q== dependencies: "@types/node" "*" "@types/multer@^1.3.3": - version "1.4.6" - resolved "https://registry.yarnpkg.com/@types/multer/-/multer-1.4.6.tgz#411950b7a99ba0de6ee8f6e3713f4628980cdc73" - integrity sha512-F4EZ+KRrzdiSm3jSFj1GVUlw3zWXus5nXYBbrQW/0MGIUv9YHw1dM0cJOxq++v2+Gl4IBdSDvQ3YCORLdyCU+Q== + version "1.4.7" + resolved "https://registry.yarnpkg.com/@types/multer/-/multer-1.4.7.tgz#89cf03547c28c7bbcc726f029e2a76a7232cc79e" + integrity sha512-/SNsDidUFCvqqcWDwxv2feww/yqhNeTRL5CVoL3jU4Goc4kKEL10T7Eye65ZqPNi4HRx8sAEX59pV1aEH7drNA== dependencies: "@types/express" "*" -"@types/node@*", "@types/node@>=10.0.0", "@types/node@^15.0.1": - version "15.12.4" - resolved "https://registry.yarnpkg.com/@types/node/-/node-15.12.4.tgz#e1cf817d70a1e118e81922c4ff6683ce9d422e26" - integrity sha512-zrNj1+yqYF4WskCMOHwN+w9iuD12+dGm0rQ35HLl9/Ouuq52cEtd0CH9qMgrdNmi5ejC1/V7vKEXYubB+65DkA== +"@types/node@*", "@types/node@>=10.0.0": + version "16.4.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.4.0.tgz#2c219eaa3b8d1e4d04f4dd6e40bc68c7467d5272" + integrity sha512-HrJuE7Mlqcjj+00JqMWpZ3tY8w7EUd+S0U3L1+PQSWiXZbOgyQDvi+ogoUxaHApPJq5diKxYBQwA3iIlNcPqOg== "@types/node@^14.14.31": - version "14.17.4" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.4.tgz#218712242446fc868d0e007af29a4408c7765bc0" - integrity sha512-8kQ3+wKGRNN0ghtEn7EGps/B8CzuBz1nXZEIGGLP2GnwbqYn4dbTs7k+VKLTq1HvZLRCIDtN3Snx1Ege8B7L5A== + version "14.17.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.5.tgz#b59daf6a7ffa461b5648456ca59050ba8e40ed54" + integrity sha512-bjqH2cX/O33jXT/UmReo2pM7DIJREPMnarixbQ57DOOzzFaI6D2+IcwaJQaJpv0M1E9TIhPCYVxrkcityLjlqA== + +"@types/node@^15.0.1": + version "15.14.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-15.14.2.tgz#7af8ab20156586f076f4760bc1b3c5ddfffd1ff2" + integrity sha512-dvMUE/m2LbXPwlvVuzCyslTEtQ2ZwuuFClDrOQ6mp2CenCg971719PTILZ4I6bTP27xfFFc+o7x2TkLuun/MPw== "@types/nodemailer@^6.2.0": - version "6.4.2" - resolved "https://registry.yarnpkg.com/@types/nodemailer/-/nodemailer-6.4.2.tgz#d8ee254c969e6ad83fb9a0a0df3a817406a3fa3b" - integrity sha512-yhsqg5Xbr8aWdwjFS3QjkniW5/tLpWXtOYQcJdo9qE3DolBxsKzgRCQrteaMY0hos8MklJNSEsMqDpZynGzMNg== + version "6.4.4" + resolved "https://registry.yarnpkg.com/@types/nodemailer/-/nodemailer-6.4.4.tgz#c265f7e7a51df587597b3a49a023acaf0c741f4b" + integrity sha512-Ksw4t7iliXeYGvIQcSIgWQ5BLuC/mljIEbjf615svhZL10PE9t+ei8O9gDaD3FPCasUJn9KTLwz2JFJyiiyuqw== dependencies: "@types/node" "*" "@types/normalize-package-data@^2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" - integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== + version "2.4.1" + resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" + integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== "@types/oauth2-server@^3.0.8": - version "3.0.12" - resolved "https://registry.yarnpkg.com/@types/oauth2-server/-/oauth2-server-3.0.12.tgz#3c404055c2c88068a3ee8f5e5a0c5a12bca8c365" - integrity sha512-biRndg8t05UxbW1Aqe9kqDbzoi3wSgKITQxtLKR2eK0SwWTgF6AS32IyriFX6qf8KZWhruViVat7MuFfuAUrZQ== + version "3.0.13" + resolved "https://registry.yarnpkg.com/@types/oauth2-server/-/oauth2-server-3.0.13.tgz#e93baf99a923ffbb9ef09dea9978ee63d706b96c" + integrity sha512-thC6D0vgqUh2LFeOV7AzuWaAjZfciFmnh2tM10Nw4ZllMnTP7jw8PYMY6ti7PHAkwp4Cz9YFZs2LFwlXlA87Bw== dependencies: "@types/express" "*" "@types/parse-torrent-file@*": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@types/parse-torrent-file/-/parse-torrent-file-4.0.2.tgz#40c96fc075aec256514807c6c381d11d9035bd9e" - integrity sha512-EzdzpcN0sStQ35sUV2SChTJErLsbotsxZ/RYeR9gf3zXKlPLKaA7aIAoS/nuLRvfxH8mbrWQmXSw76alKecSdg== + version "4.0.3" + resolved "https://registry.yarnpkg.com/@types/parse-torrent-file/-/parse-torrent-file-4.0.3.tgz#045b023426d168e0253c932cb782b231b1ee2d62" + integrity sha512-dFkPnJPKiFWiGX+HXmyTVt2js3k0d9dThmUxX8nfGC22hbyZ5BTmetsEl45sQhHLcFo43njVrIKMXM3F1ahXRw== dependencies: "@types/node" "*" "@types/parse-torrent@*": - version "5.8.3" - resolved "https://registry.yarnpkg.com/@types/parse-torrent/-/parse-torrent-5.8.3.tgz#ff4e987d09ad27ccc1c8893b3a2c6a31a3bc4042" - integrity sha512-c0xAjnpov+Xk/2HTtpaBm0tukNIAoZoxrqgTDwSaIu6IVCynY+2YD9zcQNk2P6H4atcXzD78/LI2CQzLlMmAJg== + version "5.8.4" + resolved "https://registry.yarnpkg.com/@types/parse-torrent/-/parse-torrent-5.8.4.tgz#c095834a9a815507c59014a79517ad403e4329d0" + integrity sha512-FdKs5yN5iYO5Cu9gVz1Zl30CbZe6HTsqloWmCf+LfbImgSzlsUkov2+npQWCQSQ3zi/a2G5C824K0UpZ2sRufA== dependencies: "@types/magnet-uri" "*" "@types/node" "*" "@types/parse-torrent-file" "*" "@types/pem@^1.9.3": - version "1.9.5" - resolved "https://registry.yarnpkg.com/@types/pem/-/pem-1.9.5.tgz#cd5548b5e0acb4b41a9e21067e9fcd8c57089c99" - integrity sha512-C0txxEw8B7DCoD85Ko7SEvzUogNd5VDJ5/YBG8XUcacsOGqxr5Oo4g3OUAfdEDUbhXanwUoVh/ZkMFw77FGPQQ== + version "1.9.6" + resolved "https://registry.yarnpkg.com/@types/pem/-/pem-1.9.6.tgz#c3686832e935947fdd9d848dec3b8fe830068de7" + integrity sha512-IC67SxacM9fxEi/w7hf98dTun83OwUMeLMo1NS2gE0wdM9MHeg73iH/Pp9nB02OUCQ7Zb2UuKE/IpFCmQw9jxw== dependencies: "@types/node" "*" "@types/qs@*": - version "6.9.6" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.6.tgz#df9c3c8b31a247ec315e6996566be3171df4b3b1" - integrity sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA== + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== "@types/range-parser@*": - version "1.2.3" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" - integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== -"@types/redis@*", "@types/redis@^2.8.5": - version "2.8.30" - resolved "https://registry.yarnpkg.com/@types/redis/-/redis-2.8.30.tgz#2b63ce9ff93959355d8a1f6d7a45e483b5fe0299" - integrity sha512-4D3XwfIc671FSNXNruE/wmf6jWL7QYtyAhiWXJDkY41F4atMnOol4584oP4WqnW3uHe8d+Jn+wDLuQaxbfMgXQ== +"@types/redis@^2.8.5": + version "2.8.31" + resolved "https://registry.yarnpkg.com/@types/redis/-/redis-2.8.31.tgz#c11c1b269fec132ac2ec9eb891edf72fc549149e" + integrity sha512-daWrrTDYaa5iSDFbgzZ9gOOzyp2AJmYK59OlG/2KGBgYWF3lfs8GDKm1c//tik5Uc93hDD36O+qLPvzDolChbA== dependencies: "@types/node" "*" "@types/request@^2.0.3": - version "2.48.5" - resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.5.tgz#019b8536b402069f6d11bee1b2c03e7f232937a0" - integrity sha512-/LO7xRVnL3DxJ1WkPGDQrp4VTV1reX9RkC85mJ+Qzykj2Bdw+mG15aAfDahc76HtknjzE16SX/Yddn6MxVbmGQ== + version "2.48.6" + resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.6.tgz#2300e7fc443108f79efa90e3bdf34c6d60fa89d8" + integrity sha512-vrZaV3Ij7j/l/3hz6OttZFtpRCu7zlq7XgkYHJP6FwVEAZkGQ095WqyJV08/GlW9eyXKVcp/xmtruHm8eHpw1g== dependencies: "@types/caseless" "*" "@types/node" "*" @@ -949,31 +956,31 @@ "@types/node" "*" "@types/sax@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@types/sax/-/sax-1.2.1.tgz#e0248be936ece791a82db1a57f3fb5f7c87e8172" - integrity sha512-dqYdvN7Sbw8QT/0Ci5rhjE4/iCMJEM0Y9rHpCu+gGXD9Lwbz28t6HI2yegsB6BoV1sShRMU6lAmAcgRjmFy7LA== + version "1.2.3" + resolved "https://registry.yarnpkg.com/@types/sax/-/sax-1.2.3.tgz#b630ac1403ebd7812e0bf9a10de9bf5077afb348" + integrity sha512-+QSw6Tqvs/KQpZX8DvIl3hZSjNFLW/OqE5nlyHXtTwODaJvioN2rOWpBNEWZp2HZUFhOh+VohmJku/WxEXU2XA== dependencies: "@types/node" "*" "@types/serve-static@*": - version "1.13.9" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.9.tgz#aacf28a85a05ee29a11fb7c3ead935ac56f33e4e" - integrity sha512-ZFqF6qa48XsPdjXV5Gsz0Zqmux2PerNd3a/ktL45mHpa19cuMi/cL8tcxdAx497yRh+QtYPuofjT9oWw9P7nkA== + version "1.13.10" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9" + integrity sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ== dependencies: "@types/mime" "^1" "@types/node" "*" "@types/simple-peer@*": - version "9.11.0" - resolved "https://registry.yarnpkg.com/@types/simple-peer/-/simple-peer-9.11.0.tgz#72f369cb2bebd0023b265aa726d352bf744f9f4b" - integrity sha512-HjIGo3D5I2tdtl2FpngcDYHgqlDxZuZqmQn7f0TQrpYI4ZbuHbBg9THoiDjzKjCzZsHrG5Ag53m5O/cBYfjQWA== + version "9.11.1" + resolved "https://registry.yarnpkg.com/@types/simple-peer/-/simple-peer-9.11.1.tgz#bef6ff1e75178d83438e33aa6a4df2fd98fded1d" + integrity sha512-Pzqbau/WlivSXdRC0He2Wz/ANj2wbi4gzJrtysZz93jvOyI2jo/ibMjUe6AvPllFl/UO6QXT/A0Rcp44bDQB5A== dependencies: "@types/node" "*" "@types/superagent@*": - version "4.1.11" - resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-4.1.11.tgz#4822bc64a82a0f579261a77097dbca276556c20e" - integrity sha512-cZkWBXZI+jESnUTp8RDGBmk1Zn2MkScP4V5bjD7DyqB7L0WNWpblh4KX5K/6aTqxFZMhfo1bhi2cwoAEDVBBJw== + version "4.1.12" + resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-4.1.12.tgz#fad68c6712936892ad24cf94f2f7a07cc749fd0f" + integrity sha512-1GQvD6sySQPD6p9EopDFI3f5OogdICl1sU/2ij3Esobz/RtL9fWZZDPmsuv7eiy5ya+XNiPAxUcI3HIUTJa+3A== dependencies: "@types/cookiejar" "*" "@types/node" "*" @@ -986,24 +993,24 @@ "@types/superagent" "*" "@types/tough-cookie@*": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.0.tgz#fef1904e4668b6e5ecee60c52cc6a078ffa6697d" - integrity sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A== + version "4.0.1" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.1.tgz#8f80dd965ad81f3e1bc26d6f5c727e132721ff40" + integrity sha512-Y0K95ThC3esLEYD6ZuqNek29lNX2EM1qxV8y2FTLUB0ff5wWrk7az+mLrnNFUnaXcgKye22+sFBRXOgpPILZNg== "@types/tv4@*": - version "1.2.30" - resolved "https://registry.yarnpkg.com/@types/tv4/-/tv4-1.2.30.tgz#3159a6fd5ac90c42d27aecfb20a6a150fb565668" - integrity sha512-Uj68uOn6T94IQsEGxLRrFiAqYsP4AUaXiYWrm+DR9/PNtIxuXaq/oHwBbGVR0uXHox4UZMKxCuf+z5M40MpoBw== + version "1.2.31" + resolved "https://registry.yarnpkg.com/@types/tv4/-/tv4-1.2.31.tgz#b33f3e6f082782a90f1fc5f414ad8722db8c9baa" + integrity sha512-P97XU07fcpauSw3/fE2Q7eF6bHl4oHhwkikjnM7zlQLENrdC2rZuHSdNlMBhnW82NyBEsVJHII1Jk3d/MtQsQQ== "@types/validator@^13.0.0": - version "13.1.4" - resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.1.4.tgz#d2e3c27523ce1b5d9dc13d16cbce65dc4db2adbe" - integrity sha512-19C02B8mr53HufY7S+HO/EHBD7a/R22IwEwyqiHaR19iwL37dN3o0M8RianVInfSSqP7InVSg/o0mUATM4JWsQ== + version "13.6.3" + resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.6.3.tgz#31ca2e997bf13a0fffca30a25747d5b9f7dbb7de" + integrity sha512-fWG42pMJOL4jKsDDZZREnXLjc3UE0R8LOJfARWYg6U966rxDT7TYejYzLnUF5cvSObGg34nd0+H2wHHU5Omdfw== "@types/webtorrent@^0.109.0": - version "0.109.0" - resolved "https://registry.yarnpkg.com/@types/webtorrent/-/webtorrent-0.109.0.tgz#dd377691caf360317738f67fa0c3bce48623df57" - integrity sha512-c6EgbuFRZqhM4TMnloRuLAcR45j/Qn0kQ6CKWMppXXHfaQpspB1ZeeYx2Bpc22MAgCc3pjlAakTRS2h15stW2A== + version "0.109.1" + resolved "https://registry.yarnpkg.com/@types/webtorrent/-/webtorrent-0.109.1.tgz#6ca843c3a6d442459b558ec25ce290437f204900" + integrity sha512-yH0F7Ma9VI7Y1y02ZIOkDlS9WDoTFwesUGUFxjDEE6OFLlSqIKxgdHY72cigr7JHCwDm6uNQnCq+twz0SO6cTw== dependencies: "@types/bittorrent-protocol" "*" "@types/node" "*" @@ -1011,79 +1018,79 @@ "@types/simple-peer" "*" "@types/ws@^7.2.1": - version "7.4.5" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.5.tgz#8ff0f7efcd8fea19f51f9dd66cb8b498d172a752" - integrity sha512-8mbDgtc8xpxDDem5Gwj76stBDJX35KQ3YBoayxlqUQcL5BZUthiqP/VQ4PQnLHqM4PmlbyO74t98eJpURO+gPA== + version "7.4.7" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702" + integrity sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww== dependencies: "@types/node" "*" "@typescript-eslint/eslint-plugin@^4.8.1": - version "4.28.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.0.tgz#1a66f03b264844387beb7dc85e1f1d403bd1803f" - integrity sha512-KcF6p3zWhf1f8xO84tuBailV5cN92vhS+VT7UJsPzGBm9VnQqfI9AsiMUFUCYHTYPg1uCCo+HyiDnpDuvkAMfQ== + version "4.28.4" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.4.tgz#e73c8cabbf3f08dee0e1bda65ed4e622ae8f8921" + integrity sha512-s1oY4RmYDlWMlcV0kKPBaADn46JirZzvvH7c2CtAqxCY96S538JRBAzt83RrfkDheV/+G/vWNK0zek+8TB3Gmw== dependencies: - "@typescript-eslint/experimental-utils" "4.28.0" - "@typescript-eslint/scope-manager" "4.28.0" + "@typescript-eslint/experimental-utils" "4.28.4" + "@typescript-eslint/scope-manager" "4.28.4" debug "^4.3.1" functional-red-black-tree "^1.0.1" regexpp "^3.1.0" semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/experimental-utils@4.28.0": - version "4.28.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.0.tgz#13167ed991320684bdc23588135ae62115b30ee0" - integrity sha512-9XD9s7mt3QWMk82GoyUpc/Ji03vz4T5AYlHF9DcoFNfJ/y3UAclRsfGiE2gLfXtyC+JRA3trR7cR296TEb1oiQ== +"@typescript-eslint/experimental-utils@4.28.4": + version "4.28.4" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.4.tgz#9c70c35ebed087a5c70fb0ecd90979547b7fec96" + integrity sha512-OglKWOQRWTCoqMSy6pm/kpinEIgdcXYceIcH3EKWUl4S8xhFtN34GQRaAvTIZB9DD94rW7d/U7tUg3SYeDFNHA== dependencies: "@types/json-schema" "^7.0.7" - "@typescript-eslint/scope-manager" "4.28.0" - "@typescript-eslint/types" "4.28.0" - "@typescript-eslint/typescript-estree" "4.28.0" + "@typescript-eslint/scope-manager" "4.28.4" + "@typescript-eslint/types" "4.28.4" + "@typescript-eslint/typescript-estree" "4.28.4" eslint-scope "^5.1.1" eslint-utils "^3.0.0" "@typescript-eslint/parser@^4.0.0": - version "4.28.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.28.0.tgz#2404c16751a28616ef3abab77c8e51d680a12caa" - integrity sha512-7x4D22oPY8fDaOCvkuXtYYTQ6mTMmkivwEzS+7iml9F9VkHGbbZ3x4fHRwxAb5KeuSkLqfnYjs46tGx2Nour4A== + version "4.28.4" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.28.4.tgz#bc462dc2779afeefdcf49082516afdc3e7b96fab" + integrity sha512-4i0jq3C6n+og7/uCHiE6q5ssw87zVdpUj1k6VlVYMonE3ILdFApEzTWgppSRG4kVNB/5jxnH+gTeKLMNfUelQA== dependencies: - "@typescript-eslint/scope-manager" "4.28.0" - "@typescript-eslint/types" "4.28.0" - "@typescript-eslint/typescript-estree" "4.28.0" + "@typescript-eslint/scope-manager" "4.28.4" + "@typescript-eslint/types" "4.28.4" + "@typescript-eslint/typescript-estree" "4.28.4" debug "^4.3.1" -"@typescript-eslint/scope-manager@4.28.0": - version "4.28.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.28.0.tgz#6a3009d2ab64a30fc8a1e257a1a320067f36a0ce" - integrity sha512-eCALCeScs5P/EYjwo6se9bdjtrh8ByWjtHzOkC4Tia6QQWtQr3PHovxh3TdYTuFcurkYI4rmFsRFpucADIkseg== +"@typescript-eslint/scope-manager@4.28.4": + version "4.28.4" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.28.4.tgz#bdbce9b6a644e34f767bd68bc17bb14353b9fe7f" + integrity sha512-ZJBNs4usViOmlyFMt9X9l+X0WAFcDH7EdSArGqpldXu7aeZxDAuAzHiMAeI+JpSefY2INHrXeqnha39FVqXb8w== dependencies: - "@typescript-eslint/types" "4.28.0" - "@typescript-eslint/visitor-keys" "4.28.0" + "@typescript-eslint/types" "4.28.4" + "@typescript-eslint/visitor-keys" "4.28.4" -"@typescript-eslint/types@4.28.0": - version "4.28.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.28.0.tgz#a33504e1ce7ac51fc39035f5fe6f15079d4dafb0" - integrity sha512-p16xMNKKoiJCVZY5PW/AfILw2xe1LfruTcfAKBj3a+wgNYP5I9ZEKNDOItoRt53p4EiPV6iRSICy8EPanG9ZVA== +"@typescript-eslint/types@4.28.4": + version "4.28.4" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.28.4.tgz#41acbd79b5816b7c0dd7530a43d97d020d3aeb42" + integrity sha512-3eap4QWxGqkYuEmVebUGULMskR6Cuoc/Wii0oSOddleP4EGx1tjLnZQ0ZP33YRoMDCs5O3j56RBV4g14T4jvww== -"@typescript-eslint/typescript-estree@4.28.0": - version "4.28.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.0.tgz#e66d4e5aa2ede66fec8af434898fe61af10c71cf" - integrity sha512-m19UQTRtxMzKAm8QxfKpvh6OwQSXaW1CdZPoCaQuLwAq7VZMNuhJmZR4g5281s2ECt658sldnJfdpSZZaxUGMQ== +"@typescript-eslint/typescript-estree@4.28.4": + version "4.28.4" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.4.tgz#252e6863278dc0727244be9e371eb35241c46d00" + integrity sha512-z7d8HK8XvCRyN2SNp+OXC2iZaF+O2BTquGhEYLKLx5k6p0r05ureUtgEfo5f6anLkhCxdHtCf6rPM1p4efHYDQ== dependencies: - "@typescript-eslint/types" "4.28.0" - "@typescript-eslint/visitor-keys" "4.28.0" + "@typescript-eslint/types" "4.28.4" + "@typescript-eslint/visitor-keys" "4.28.4" debug "^4.3.1" globby "^11.0.3" is-glob "^4.0.1" semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/visitor-keys@4.28.0": - version "4.28.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.0.tgz#255c67c966ec294104169a6939d96f91c8a89434" - integrity sha512-PjJyTWwrlrvM5jazxYF5ZPs/nl0kHDZMVbuIcbpawVXaDPelp3+S9zpOz5RmVUfS/fD5l5+ZXNKnWhNYjPzCvw== +"@typescript-eslint/visitor-keys@4.28.4": + version "4.28.4" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.4.tgz#92dacfefccd6751cbb0a964f06683bfd72d0c4d3" + integrity sha512-NIAXAdbz1XdOuzqkJHjNKXKj8QQ4cv5cxR/g0uQhCYf/6//XrmfpaYsM7PnBcNbfvTDLUkqQ5TPNm1sozDdTWg== dependencies: - "@typescript-eslint/types" "4.28.0" + "@typescript-eslint/types" "4.28.4" eslint-visitor-keys "^2.0.0" "@ungap/promise-all-settled@1.1.2": @@ -1092,9 +1099,9 @@ integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== "@uploadx/core@^4.4.0": - version "4.4.0" - resolved "https://registry.yarnpkg.com/@uploadx/core/-/core-4.4.0.tgz#27ea2b0d28125e81a6bdd65637dc5c7829306cc7" - integrity sha512-dU0oDURYR5RvuAzf63EL9e/fCY4OOQKOs237UTbZDulbRbiyxwEZR+IpRYYr3hKRjjij03EF/Y5j54VGkebAKg== + version "4.4.2" + resolved "https://registry.yarnpkg.com/@uploadx/core/-/core-4.4.2.tgz#13220a449e3ab23ed324e4beaea04dd56e538b10" + integrity sha512-wI9iWjuT+FDV/IjTj55xPs30MwiIMg4buoZlBnTiPAkgyxpwXi9F+6Zchjy2oE5Lfd/9enp0sQz/0RMfqVimAg== dependencies: bytes "^3.1.0" debug "^4.3.1" @@ -1121,9 +1128,9 @@ accepts@~1.3.4, accepts@~1.3.7: negotiator "0.6.2" acorn-jsx@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" - integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn@^7.1.1, acorn@^7.4.0: version "7.4.1" @@ -1163,9 +1170,9 @@ ajv@^6.10.0, ajv@^6.12.4: uri-js "^4.2.2" ajv@^8.0.1: - version "8.6.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.6.0.tgz#60cc45d9c46a477d80d92c48076d972c342e5720" - integrity sha512-cnUG4NSBiM4YFBxgZIj/In3/6KX+rQ2l2YPRVcvAMQGWEPKuXoPIhxzwqh31jA3IPbI4qEOp/5ILI4ynioXsGQ== + version "8.6.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.6.2.tgz#2fb45e0e5fcbc0813326c1c3da535d1881bb0571" + integrity sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" @@ -1235,7 +1242,7 @@ any-promise@^1.3.0: resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= -anymatch@~3.1.1, anymatch@~3.1.2: +anymatch@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== @@ -1243,11 +1250,6 @@ anymatch@~3.1.1, anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" -apicache@1.6.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/apicache/-/apicache-1.6.2.tgz#a0a3d51024fa2814c4ace7e9e7053ebcb0920ee6" - integrity sha512-3z5e+1E2qwZoqzFVgdx5l9nGhSG0kHv3v2G170vnJSz5uj4mCLVZfRw0o37aWwV8pTPXSkB8OBZz3TIur4H26g== - append-field@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56" @@ -1408,9 +1410,9 @@ asynckit@^0.4.0: integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= autocannon@^7.0.4: - version "7.3.0" - resolved "https://registry.yarnpkg.com/autocannon/-/autocannon-7.3.0.tgz#8e52bb3f07926b573dcf401d3fe365393fbab808" - integrity sha512-RuyTU8fQE1FC6BDgslofLCeI4y9y9RRnnZtvoQ+onwQl+B2rsiGpcCi84/Si0rq3VRkkMFnmPulY3z59zYhX9g== + version "7.4.0" + resolved "https://registry.yarnpkg.com/autocannon/-/autocannon-7.4.0.tgz#7e3ea188501d60162b7a0167c1d9bf90db870c2e" + integrity sha512-X0g/nkJ7oHkfn/B+LUzz9jPjG4nD48tPnu2E24m7LA7p8+Mvd/Clrb+FnHmPgI7pszXPRtzUYWz6QrSyMiEY6w== dependencies: chalk "^4.1.0" char-spinner "^1.0.1" @@ -1575,10 +1577,10 @@ bitfield@^4.0.0: resolved "https://registry.yarnpkg.com/bitfield/-/bitfield-4.0.0.tgz#3094123c870030dc6198a283d779639bd2a8e256" integrity sha512-jtuSG9CQr5yoHFuvhgf50+DH8Aezl3C/mMSfqdG4DqP7Kqe34uBUtCEHPN9oWaldTm96/i7y5e778SnM5ES4rw== -bittorrent-dht@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/bittorrent-dht/-/bittorrent-dht-10.0.0.tgz#01de59bb03ed86a8847cb533134925d236d7f565" - integrity sha512-mrM18HMabvd3n/hQa4PYe942nWvBsJCBQb5PfT9kUJLlspNPGiulZYSCgWs7+XarS7nufYrGEp07f9eKTKIrgw== +bittorrent-dht@^10.0.0, bittorrent-dht@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/bittorrent-dht/-/bittorrent-dht-10.0.1.tgz#ff2efe77cdb4d72c819f46b42a162f42ca233793" + integrity sha512-aR0vAgm+SgLiwTCEtNgeuqtT2deg+E/xHCTb7iryikvLbqbR58oFHbNYX4CM6EzyNGSKfcdBKp1gWI5Gcn2Aaw== dependencies: bencode "^2.0.0" debug "^4.1.1" @@ -1603,14 +1605,13 @@ bittorrent-peerid@^1.3.3: resolved "https://registry.yarnpkg.com/bittorrent-peerid/-/bittorrent-peerid-1.3.3.tgz#b8dc79e421f8136d2ffd0b163a18e9d70da09949" integrity sha512-tSh9HdQgwyEAfo1jzoGEis6o/zs4CcdRTchG93XVl5jct+DCAN90M5MVUV76k2vJ9Xg3GAzLB5NLsY/vnVTh6w== -bittorrent-protocol@^3.3.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/bittorrent-protocol/-/bittorrent-protocol-3.4.1.tgz#b481d09dbf910fa7fcca5f06a7c1c4246151d4d1" - integrity sha512-3qBW4ZZrUZKN7HzHbX4+kbiphrTNeraMp3i9n3wobicysjibAV8SBDY+sGiBN4SgXV6WvEW4kyRPIjoSqW+khw== +bittorrent-protocol@^3.4.2: + version "3.4.2" + resolved "https://registry.yarnpkg.com/bittorrent-protocol/-/bittorrent-protocol-3.4.2.tgz#e5194d1acd30273ac02bb272c208977716d0394b" + integrity sha512-a7ueJzmCImWIXfKrJ+dT6mgqi5+LFByAXoMXhV/cYt/y8kplaC8N9ZWfpiTidJY4H2o1GTsyMy73o62a/rZ0Ow== dependencies: bencode "^2.0.1" bitfield "^4.0.0" - buffer-xor "^2.0.2" debug "^4.3.1" randombytes "^2.1.0" rc4 "^0.1.5" @@ -1620,9 +1621,9 @@ bittorrent-protocol@^3.3.1: unordered-array-remove "^1.0.2" bittorrent-tracker@^9.0.0: - version "9.17.2" - resolved "https://registry.yarnpkg.com/bittorrent-tracker/-/bittorrent-tracker-9.17.2.tgz#1afb02d3d2fb474c13389c45e8a2b6919bff40bd" - integrity sha512-hXjed0OnB16da+ScJUZnrAZbf9gMgSLKqh5rJebtYnTRgN4o1mX0DOPH3Nf5RFCs935ibhSmZN5nwbkh+3MdEA== + version "9.17.4" + resolved "https://registry.yarnpkg.com/bittorrent-tracker/-/bittorrent-tracker-9.17.4.tgz#663f51064a924e945cb6ca19a0c293aca258128b" + integrity sha512-ykhdVQHtLfn4DYSJUQD/zFAbP8YwnF6nGlj2SBnCY4xkW5bhwXPeFZUhryAtdITl0qNL/FpmFOamBZfxIwkbxg== dependencies: bencode "^2.0.1" bittorrent-peerid "^1.3.3" @@ -1793,13 +1794,6 @@ buffer-writer@2.0.0: resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" integrity sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw== -buffer-xor@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-2.0.2.tgz#34f7c64f04c777a1f8aac5e661273bb9dd320289" - integrity sha512-eHslX0bin3GB+Lx2p7lEYRShRewuNZL3fUl4qlVJGGiwoPGftmt8JQgk2Y9Ji5/01TnVDo33E5b5O3vUB1HdqQ== - dependencies: - safe-buffer "^5.1.1" - buffer@^5.2.0, buffer@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" @@ -1824,9 +1818,9 @@ bufferutil@^4.0.3: node-gyp-build "^4.2.0" bull@^3.4.2: - version "3.22.9" - resolved "https://registry.yarnpkg.com/bull/-/bull-3.22.9.tgz#9d86493e1bb4afeb7e6e259c45141185bd78111d" - integrity sha512-waVaXkjS1+aPxZpmqcKKQX1KeWx7uZXcg9bhe+y5xx/3k8Xu0vqUL1FxMUfp0f3O4KSAHk1EDlD5DGXxBlFDFQ== + version "3.26.0" + resolved "https://registry.yarnpkg.com/bull/-/bull-3.26.0.tgz#c6198cf4f3a2fa5f3044cbe462b452c77a3df94f" + integrity sha512-W1ohwMBApLW9dhKHEwgzr8YnpScTOGC9KtKP2DrvjnWTQFWbaEnKlrDHKp3SJwvAB0C3jDsO579O/Hys/UmAiQ== dependencies: cron-parser "^2.13.0" debuglog "^1.0.0" @@ -1852,6 +1846,14 @@ bytes@3.1.0, bytes@^3.0.0, bytes@^3.1.0: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== +cache-chunk-store@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/cache-chunk-store/-/cache-chunk-store-3.2.2.tgz#19bb55d61252cd2174da4686548d52bc2dd44120" + integrity sha512-2lJdWbgHFFxcSth9s2wpId3CR3v1YC63KjP4T9WhpW7LWlY7Hiiei3QwwqzkWqlJTfR8lSy9F5kRQECeyj+yQA== + dependencies: + lru "^3.1.0" + queue-microtask "^1.2.3" + cacheable-lookup@^5.0.3: version "5.0.4" resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" @@ -2043,22 +2045,7 @@ cheerio@^1.0.0-rc.3: parse5-htmlparser2-tree-adapter "^6.0.1" tslib "^2.2.0" -chokidar@3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" - integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== - dependencies: - anymatch "~3.1.1" - braces "~3.0.2" - glob-parent "~5.1.0" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.5.0" - optionalDependencies: - fsevents "~2.3.1" - -chokidar@^3.2.2, chokidar@^3.4.2: +chokidar@3.5.2, chokidar@^3.2.2, chokidar@^3.4.2: version "3.5.2" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== @@ -2235,9 +2222,9 @@ color-name@^1.0.0, color-name@~1.1.4: integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== color-string@^1.5.2: - version "1.5.5" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.5.tgz#65474a8f0e7439625f3d27a6a19d89fc45223014" - integrity sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg== + version "1.6.0" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.6.0.tgz#c3915f61fe267672cb7e1e064c9d692219f6c312" + integrity sha512-c/hGS+kRWJutUBEngKKmk4iH3sD59MBkoxVapS/0wgpCz2u7XsNloxknyvBhzwEs1IbV36D9PwqLPJ2DTu3vMA== dependencies: color-name "^1.0.0" simple-swizzle "^0.2.2" @@ -2607,9 +2594,9 @@ dateformat@^3.0.3: integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== dayjs@^1.10.4: - version "1.10.5" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.5.tgz#5600df4548fc2453b3f163ebb2abbe965ccfb986" - integrity sha512-BUFis41ikLz+65iH6LHQCDm4YPMj5r1YFLdupPIyM4SGcXMmtiLQ7U37i+hGS8urIuqe7I/ou3IS1jVc4nbN4g== + version "1.10.6" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.6.tgz#288b2aa82f2d8418a6c9d4df5898c0737ad02a63" + integrity sha512-AztC/IOW4L1Q41A86phW5Thhcrco3xuAA+YX/BLpLWWjRcTj5TOt/QImBLmCKlrF7u7k47arTnOyL6GnbG8Hvw== debug@2.6.9, debug@^2.2.0, debug@^2.6.9: version "2.6.9" @@ -2618,7 +2605,14 @@ debug@2.6.9, debug@^2.2.0, debug@^2.6.9: dependencies: ms "2.0.0" -debug@4, debug@4.3.1, debug@^4.0.0, debug@^4.0.1, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1, debug@~4.3.1: +debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1, debug@^4.3.2, debug@~4.3.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== + dependencies: + ms "2.1.2" + +debug@4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== @@ -2978,7 +2972,7 @@ engine.io-client@~3.3.1: xmlhttprequest-ssl "~1.5.4" yeast "0.1.2" -engine.io-client@~5.1.1: +engine.io-client@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-5.1.2.tgz#27108da9b39ae03262443d945caf2caa3655c4cb" integrity sha512-blRrgXIE0A/eurWXRzvfCLG7uUFJqfTGFsyJzXSK71srMMGJ2VraBLg8Mdw28uUxSpVicepBN9X7asqpD1mZcQ== @@ -3023,7 +3017,7 @@ engine.io@~3.3.1: engine.io-parser "~2.1.0" ws "~6.1.0" -engine.io@~5.1.0: +engine.io@~5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-5.1.1.tgz#a1f97e51ddf10cbd4db8b5ff4b165aad3760cdd3" integrity sha512-aMWot7H5aC8L4/T8qMYbLdvKlZOdJTH54FxfdFunTGvhMx1BHkJOntWArsVfgAZVwAO9LC2sryPWRcEeUzCe5w== @@ -3280,12 +3274,13 @@ eslint-visitor-keys@^2.0.0: integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== eslint@^7.2.0: - version "7.29.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.29.0.tgz#ee2a7648f2e729485e4d0bd6383ec1deabc8b3c0" - integrity sha512-82G/JToB9qIy/ArBzIWG9xvvwL3R86AlCjtGw+A29OMZDqhTybz/MByORSukGxeI+YPCR4coYyITKk8BFH9nDA== + version "7.31.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.31.0.tgz#f972b539424bf2604907a970860732c5d99d3aca" + integrity sha512-vafgJpSh2ia8tnTkNUkwxGmnumgckLh5aAbLa1xRmIn9+owi8qBNGKL+B881kNKNTy7FFqTEkpNkUvmw0n6PkA== dependencies: "@babel/code-frame" "7.12.11" - "@eslint/eslintrc" "^0.4.2" + "@eslint/eslintrc" "^0.4.3" + "@humanwhocodes/config-array" "^0.5.0" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" @@ -3424,9 +3419,9 @@ exif-parser@^0.1.12: integrity sha1-WKnS1ywCwfbwKg70qRZicrd2CSI= express-rate-limit@^5.0.0: - version "5.2.6" - resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-5.2.6.tgz#b454e1be8a252081bda58460e0a25bf43ee0f7b0" - integrity sha512-nE96xaxGfxiS5jP3tD3kIW1Jg9yQgX0rXCs3rCkZtmbWHEGyotwaezkLj7bnB41Z0uaOLM8W4AX6qHao4IZ2YA== + version "5.3.0" + resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-5.3.0.tgz#e7b9d3c2e09ece6e0406a869b2ce00d03fe48aea" + integrity sha512-qJhfEgCnmteSeZAeuOKQ2WEIFTX5ajrzE0xS6gCOBCoRQcU+xEzQmgYQQTpzCcqUAAzTEtu4YEih4pnLfvNtew== express-validator@^6.4.0: version "6.12.0" @@ -3514,16 +3509,15 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== fast-glob@^3.1.1: - version "3.2.5" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.5.tgz#7939af2a656de79a4f1901903ee8adcaa7cb9661" - integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg== + version "3.2.7" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" + integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.0" + glob-parent "^5.1.2" merge2 "^1.3.0" - micromatch "^4.0.2" - picomatch "^2.2.1" + micromatch "^4.0.4" fast-json-stable-stringify@^2.0.0: version "2.1.0" @@ -3535,20 +3529,25 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= -fast-safe-stringify@2.0.7, fast-safe-stringify@^2.0.4, fast-safe-stringify@^2.0.7: +fast-safe-stringify@2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== +fast-safe-stringify@^2.0.4, fast-safe-stringify@^2.0.7: + version "2.0.8" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.8.tgz#dc2af48c46cf712b683e849b2bbd446b32de936f" + integrity sha512-lXatBjf3WPjmWD6DpIZxkeSsCOwqI0maYMpgDlx8g4U2qi4lbjA9oH/HD2a87G+KfsUmo5WbJFmqBZlPxtptag== + fast-xml-parser@^3.19.0: version "3.19.0" resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-3.19.0.tgz#cb637ec3f3999f51406dd8ff0e6fc4d83e520d01" integrity sha512-4pXwmBplsCPv8FOY1WRakF970TjNGnGnfbOnLqjlYvMiF1SR3yOHyxMR/YCXpPTOspNF5gwudqktIP4VsWkvBg== fastq@^1.6.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.0.tgz#bb9fb955a07130a918eb63c1f5161cc32a5d0858" - integrity sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g== + version "1.11.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.1.tgz#5d8175aae17db61947f8b162cfc7f63264d22807" + integrity sha512-HOnr8Mc60eNYl1gzwp6r5RoUyAn5/glBolUzP/Ez6IFVPMPirxn/9phgL6zhOtaTy7ISwPvQ+wT+hfcRZh/bzw== dependencies: reusify "^1.0.4" @@ -3653,9 +3652,9 @@ flat@^5.0.0, flat@^5.0.2: integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== flatted@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469" - integrity sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA== + version "3.2.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.1.tgz#bbef080d95fca6709362c73044a1634f7c6e7d05" + integrity sha512-OMQjaErSFHmHqZe+PSidH5n8j3O0F2DdnVh8JB4j4eUQ2k6KvB0qGfrKIhapvez5JerBbmWkaLYUYWISaESoXg== fluent-ffmpeg@^2.1.0: version "2.1.2" @@ -3762,7 +3761,7 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@~2.3.1, fsevents@~2.3.2: +fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -3866,7 +3865,7 @@ gifwrap@^0.9.2: image-q "^1.1.1" omggif "^1.0.10" -glob-parent@^5.1.0, glob-parent@^5.1.2, glob-parent@~5.1.0, glob-parent@~5.1.2: +glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -3913,9 +3912,9 @@ global@~4.4.0: process "^0.11.10" globals@^13.6.0, globals@^13.9.0: - version "13.9.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.9.0.tgz#4bf2bf635b334a173fb1daf7c5e6b218ecdc06cb" - integrity sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA== + version "13.10.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.10.0.tgz#60ba56c3ac2ca845cfbf4faeca727ad9dd204676" + integrity sha512-piHC3blgLGFjvOuMmWZX60f+na1lXFDhQXBf1UYp2fXPXqvEUbOhNwi6BsQ0bQishwedgnjkwv1d9zKf+MWw3g== dependencies: type-fest "^0.20.2" @@ -4198,11 +4197,11 @@ human-signals@^2.1.0: integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== hyperid@^2.0.3: - version "2.1.0" - resolved "https://registry.yarnpkg.com/hyperid/-/hyperid-2.1.0.tgz#2f5ed7537e87e8fddd344710a610be501b3d2da6" - integrity sha512-cSakhxbUsaIuqjfvvcUuvl/Fl342J65xgLLYrYxSSr9qmJ/EJK+S8crS6mIlQd/a7i+Pe4D0MgSrtZPLze+aCw== + version "2.3.1" + resolved "https://registry.yarnpkg.com/hyperid/-/hyperid-2.3.1.tgz#70cc2c917b6367c9f7307718be243bc28b258353" + integrity sha512-mIbI7Ymn6MCdODaW1/6wdf5lvvXzmPsARN4zTLakMmcziBOuP4PxCBJvHF6kbAIHX6H4vAELx/pDmt0j6Th5RQ== dependencies: - uuid "^3.4.0" + uuid "^8.3.2" uuid-parse "^1.1.0" i18n-locales@^0.0.5: @@ -4336,10 +4335,10 @@ ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== -inquirer@8.1.1: - version "8.1.1" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.1.1.tgz#7c53d94c6d03011c7bb2a947f0dca3b98246c26a" - integrity sha512-hUDjc3vBkh/uk1gPfMAD/7Z188Q8cvTGl0nxwaCdwSbzFh6ZKkZh+s2ozVxbE5G9ZNRyeY0+lgbAIOUFsFf98w== +inquirer@8.1.2: + version "8.1.2" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.1.2.tgz#65b204d2cd7fb63400edd925dfe428bafd422e3d" + integrity sha512-DHLKJwLPNgkfwNmsuEUKSejJFbkv0FMO9SMiQbjI3n5NQuCrSIBqP66ggqyz2a6t2qEolKrMjhQ3+W/xXgUQ+Q== dependencies: ansi-escapes "^4.2.1" chalk "^4.1.1" @@ -4351,7 +4350,7 @@ inquirer@8.1.1: mute-stream "0.0.8" ora "^5.3.0" run-async "^2.4.0" - rxjs "^6.6.6" + rxjs "^7.2.0" string-width "^4.1.0" strip-ansi "^6.0.0" through "^2.3.6" @@ -4468,9 +4467,9 @@ is-cidr@^4.0.0: cidr-regex "^3.1.1" is-core-module@^2.2.0, is-core-module@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.4.0.tgz#8e9fc8e15027b011418026e98f0e6f4d86305cc1" - integrity sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A== + version "2.5.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.5.0.tgz#f754843617c70bfd29b7bd87327400cda5c18491" + integrity sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg== dependencies: has "^1.0.3" @@ -4785,14 +4784,7 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= -json5@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" - integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== - dependencies: - minimist "^1.2.0" - -json5@^2.1.1: +json5@^2.1.1, json5@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== @@ -5252,9 +5244,9 @@ markdown-it-emoji@^2.0.0: integrity sha512-39j7/9vP/CPCKbEI44oV8yoPJTpvfeReTn/COgRhSpNrjWF3PfP/JUxxB0hxV6ynOY8KH8Y8aX9NMDdo6z+6YQ== markdown-it@^12.0.4: - version "12.0.6" - resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.0.6.tgz#adcc8e5fe020af292ccbdf161fe84f1961516138" - integrity sha512-qv3sVLl4lMT96LLtR7xeRJX11OUFjsaD5oVat2/SNBIb21bJXwal2+SklcRbTwGwqWpWH/HRtYavOoJE+seL8w== + version "12.1.0" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.1.0.tgz#7ad572caddd336bd27a68d20e86bac1fafe8fb20" + integrity sha512-7temG6IFOOxfU0SgzhqR+vr2diuMhyO5uUIEZ3C5NbXhqC9uFUHoU41USYuDFoZRsaY7BEIEei874Z20VMLF6A== dependencies: argparse "^2.0.1" entities "~2.1.0" @@ -5268,9 +5260,9 @@ marked-man@^0.7.0: integrity sha512-zxK5E4jbuARALc+fIUAanM2njVGnrd9YvKrqoDHUg2XwNLJijo39EzMIg59LecHBHsIHNtPqepqnJp4SmL/EVg== marked@^2.0.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/marked/-/marked-2.1.2.tgz#59579e17b02443312caa1509994d5a0b18ae38e1" - integrity sha512-ueJhIvklJJw04qxQbGIAu63EXwwOCYc7yKMBjgagTM4rjC5QtWyqSNgW7jCosV1/Km/1TUfs5qEpAqcGG0Mo5g== + version "2.1.3" + resolved "https://registry.yarnpkg.com/marked/-/marked-2.1.3.tgz#bd017cef6431724fd4b27e0657f5ceb14bff3753" + integrity sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA== math-interval-parser@^2.0.1: version "2.0.1" @@ -5385,7 +5377,7 @@ methods@^1.1.2, methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= -micromatch@^4.0.2: +micromatch@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== @@ -5495,14 +5487,14 @@ mkdirp@^1.0.3, mkdirp@~1.0.4: integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== mocha@^9.0.0: - version "9.0.1" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.0.1.tgz#01e66b7af0012330c0a38c4b6eaa6d92b8a81bf9" - integrity sha512-9zwsavlRO+5csZu6iRtl3GHImAbhERoDsZwdRkdJ/bE+eVplmoxNKE901ZJ9LdSchYBjSCPbjKc5XvcAri2ylw== + version "9.0.2" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.0.2.tgz#e84849b61f406a680ced85af76425f6f3108d1a0" + integrity sha512-FpspiWU+UT9Sixx/wKimvnpkeW0mh6ROAKkIaPokj3xZgxeRhcna/k5X57jJghEr8X+Cgu/Vegf8zCX5ugSuTA== dependencies: "@ungap/promise-all-settled" "1.1.2" ansi-colors "4.1.1" browser-stdout "1.3.1" - chokidar "3.5.1" + chokidar "3.5.2" debug "4.3.1" diff "5.0.0" escape-string-regexp "4.0.0" @@ -5515,12 +5507,12 @@ mocha@^9.0.0: minimatch "3.0.4" ms "2.1.3" nanoid "3.1.23" - serialize-javascript "5.0.1" + serialize-javascript "6.0.0" strip-json-comments "3.1.1" supports-color "8.1.1" which "2.0.2" wide-align "1.1.3" - workerpool "6.1.4" + workerpool "6.1.5" yargs "16.2.0" yargs-parser "20.2.4" yargs-unparser "2.0.0" @@ -5711,15 +5703,16 @@ node-gyp-build@^4.2.0: integrity sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg== node-media-server@^2.1.4: - version "2.2.8" - resolved "https://registry.yarnpkg.com/node-media-server/-/node-media-server-2.2.8.tgz#586569690733ff76309f8ff50cdf302f30207009" - integrity sha512-sLusJjFVJ3mWeDMHnkyx1gitPjsMcWbhzlXT7wC8gRRjP0HKmY3d8wGLjl0s+JVPB6ruwSZw0mAwntOwrOCnRw== + version "2.3.8" + resolved "https://registry.yarnpkg.com/node-media-server/-/node-media-server-2.3.8.tgz#05ad4d1ea9372d4dd5f7b72fb5f1c00da44ce78b" + integrity sha512-IWji6X4RQHoiAu0mTojtQ7dznrdsHRahXG51d8um0bjFKkmtJHN3W9oA2fVtFtDrAYMC5bG9GvGDO2qdZtgzww== dependencies: basic-auth-connect "^1.0.0" chalk "^2.4.2" dateformat "^3.0.3" express "^4.16.4" lodash ">=4.17.13" + minimist "^1.2.5" mkdirp "1.0.3" ws "^7.4.6" @@ -5739,14 +5732,14 @@ nodemailer@^3.1.1: integrity sha1-/r+sy0vSc2eEc6MJxstLSi88SOM= nodemailer@^6.0.0, nodemailer@^6.5.0, nodemailer@^6.6.0: - version "6.6.2" - resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.6.2.tgz#e184c9ed5bee245a3e0bcabc7255866385757114" - integrity sha512-YSzu7TLbI+bsjCis/TZlAXBoM4y93HhlIgo0P5oiA2ua9Z4k+E2Fod//ybIzdJxOlXGRcHIh/WaeCBehvxZb/Q== + version "6.6.3" + resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.6.3.tgz#31fb53dd4d8ae16fc088a65cb9ffa8d928a69b48" + integrity sha512-faZFufgTMrphYoDjvyVpbpJcYzwyFnbAMmQtj1lVBYAUSm3SOy2fIdd9+Mr4UxPosBa0JRw9bJoIwQn+nswiew== nodemon@^2.0.1: - version "2.0.7" - resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.7.tgz#6f030a0a0ebe3ea1ba2a38f71bf9bab4841ced32" - integrity sha512-XHzK69Awgnec9UzHr1kc8EomQh4sjTQ8oRf8TsGrSmHDx9/UmiGG9E/mM3BuTfNeFwdNBvrqQq/RHL0xIeyFOA== + version "2.0.12" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.12.tgz#5dae4e162b617b91f1873b3bfea215dd71e144d5" + integrity sha512-egCTmNZdObdBxUBw6ZNwvZ/xzk24CKRs5K6d+5zbmrMr7rOpPmfPeF6OxM3DDpaRx331CQRFEktn+wrFFfBSOA== dependencies: chokidar "^3.2.2" debug "^3.2.6" @@ -5862,9 +5855,9 @@ object-hash@2.1.1: integrity sha512-VOJmgmS+7wvXf8CjbQmimtCnEx3IAoLxI3fp2fbWehxrWBcAQFbk+vcwb6vzR0VZv/eNCJ/27j151ZTwqW/JeQ== object-inspect@^1.10.3, object-inspect@^1.9.0: - version "1.10.3" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.10.3.tgz#c2aa7d2d09f50c99375704f7a0adf24c5782d369" - integrity sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw== + version "1.11.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" + integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" @@ -6394,9 +6387,9 @@ pngjs@^3.0.0, pngjs@^3.3.3: integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w== postcss@^8.0.2: - version "8.3.5" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.5.tgz#982216b113412bc20a86289e91eb994952a5b709" - integrity sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA== + version "8.3.6" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.6.tgz#2730dd76a97969f37f53b9a6096197be311cc4ea" + integrity sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A== dependencies: colorette "^1.2.2" nanoid "^3.1.23" @@ -6847,13 +6840,6 @@ readable-wrap@^1.0.0: dependencies: readable-stream "^1.1.13-1" -readdirp@~3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" - integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ== - dependencies: - picomatch "^2.2.1" - readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -6962,9 +6948,9 @@ require-main-filename@^2.0.0: integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== resolve-alpn@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.1.2.tgz#30b60cfbb0c0b8dc897940fe13fe255afcdd4d28" - integrity sha512-8OyfzhAtA32LVUsJSke3auIyINcwdh5l3cvYKdKO0nvsYSKuiLfTM5i78PJswFPT8y6cPW+L1v6/hE95chcpDA== + version "1.2.0" + resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.0.tgz#058bb0888d1cd4d12474e9a4b6eb17bdd5addc44" + integrity sha512-e4FNQs+9cINYMO5NMFc6kOUCdohjqFPSgMuwuZAOUWqrfWsen+Yjy5qZFkV5K7VO7tFSLKcUL97olkED7sCBHA== resolve-from@^4.0.0: version "4.0.0" @@ -7066,14 +7052,14 @@ rusha@^0.8.13: resolved "https://registry.yarnpkg.com/rusha/-/rusha-0.8.14.tgz#a977d0de9428406138b7bb90d3de5dcd024e2f68" integrity sha512-cLgakCUf6PedEu15t8kbsjnwIFFR2D4RfL+W3iWFJ4iac7z4B0ZI8fxy4R3J956kAI68HclCFGL8MPoUVC3qVA== -rxjs@7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.1.0.tgz#94202d27b19305ef7b1a4f330277b2065df7039e" - integrity sha512-gCFO5iHIbRPwznl6hAYuwNFld8W4S2shtSJIqG27ReWXo9IWrCyEICxUA+6vJHwSR/OakoenC4QsDxq50tzYmw== +rxjs@7.2.0, rxjs@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.2.0.tgz#5cd12409639e9514a71c9f5f9192b2c4ae94de31" + integrity sha512-aX8w9OpKrQmiPKfT1bqETtUr9JygIz6GZ+gql8v7CijClsP0laoFUdKzxFAoWuRdSlOdU2+crss+cMf+cqMTnw== dependencies: tslib "~2.1.0" -rxjs@^6.6.3, rxjs@^6.6.6: +rxjs@^6.6.3: version "6.6.7" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== @@ -7199,10 +7185,10 @@ sequelize@6.6.2: validator "^10.11.0" wkx "^0.5.0" -serialize-javascript@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" - integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== +serialize-javascript@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" + integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== dependencies: randombytes "^2.1.0" @@ -7385,7 +7371,7 @@ socket.io-adapter@~1.1.0: resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz#ab3f0d6f66b8fc7fca3959ab5991f82221789be9" integrity sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g== -socket.io-adapter@~2.3.0: +socket.io-adapter@~2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.3.1.tgz#a442720cb09a4823cfb81287dda1f9b52d4ccdb2" integrity sha512-8cVkRxI8Nt2wadkY6u60Y4rpW3ejA1rxgcK2JuyIhmF+RMNpTy1QRtkHIDUOf3B4HlQwakMsWbKftMv/71VMmw== @@ -7411,15 +7397,15 @@ socket.io-client@2.2.0: to-array "0.1.4" socket.io-client@^4.0.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.1.2.tgz#95ad7113318ea01fba0860237b96d71e1b1fd2eb" - integrity sha512-RDpWJP4DQT1XeexmeDyDkm0vrFc0+bUsHDKiVGaNISJvJonhQQOMqV9Vwfg0ZpPJ27LCdan7iqTI92FRSOkFWQ== + version "4.1.3" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.1.3.tgz#236daa642a9f229932e00b7221e843bf74232a62" + integrity sha512-hISFn6PDpgDifVUiNklLHVPTMv1LAk8poHArfIUdXa+gKgbr0MZbAlquDFqCqsF30yBqa+jg42wgos2FK50BHA== dependencies: "@types/component-emitter" "^1.2.10" backo2 "~1.0.2" component-emitter "~1.3.0" debug "~4.3.1" - engine.io-client "~5.1.1" + engine.io-client "~5.1.2" parseuri "0.0.6" socket.io-parser "~4.0.4" @@ -7432,7 +7418,7 @@ socket.io-parser@~3.3.0: debug "~3.1.0" isarray "2.0.1" -socket.io-parser@~4.0.3, socket.io-parser@~4.0.4: +socket.io-parser@~4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.0.4.tgz#9ea21b0d61508d18196ef04a2c6b9ab630f4c2b0" integrity sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g== @@ -7454,19 +7440,19 @@ socket.io@2.2.0: socket.io-parser "~3.3.0" socket.io@^4.0.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.1.2.tgz#f90f9002a8d550efe2aa1d320deebb9a45b83233" - integrity sha512-xK0SD1C7hFrh9+bYoYCdVt+ncixkSLKtNLCax5aEy1o3r5PaO5yQhVb97exIe67cE7lAK+EpyMytXWTWmyZY8w== + version "4.1.3" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.1.3.tgz#d114328ef27ab31b889611792959c3fa6d502500" + integrity sha512-tLkaY13RcO4nIRh1K2hT5iuotfTaIQw7cVIe0FUykN3SuQi0cm7ALxuyT5/CtDswOMWUzMGTibxYNx/gU7In+Q== dependencies: "@types/cookie" "^0.4.0" - "@types/cors" "^2.8.8" + "@types/cors" "^2.8.10" "@types/node" ">=10.0.0" accepts "~1.3.4" base64id "~2.0.0" debug "~4.3.1" - engine.io "~5.1.0" - socket.io-adapter "~2.3.0" - socket.io-parser "~4.0.3" + engine.io "~5.1.1" + socket.io-adapter "~2.3.1" + socket.io-parser "~4.0.4" source-map-js@^0.6.2: version "0.6.2" @@ -7809,9 +7795,9 @@ superagent@^6.1.0: semver "^7.3.2" supertest@^6.0.1: - version "6.1.3" - resolved "https://registry.yarnpkg.com/supertest/-/supertest-6.1.3.tgz#3f49ea964514c206c334073e8dc4e70519c7403f" - integrity sha512-v2NVRyP73XDewKb65adz+yug1XMtmvij63qIWHZzSX8tp6wiq6xBLUy4SUAd2NII6wIipOmHT/FD9eicpJwdgQ== + version "6.1.4" + resolved "https://registry.yarnpkg.com/supertest/-/supertest-6.1.4.tgz#ea8953343e0ca316e80e975b39340934f754eb06" + integrity sha512-giC9Zm+Bf1CZP09ciPdUyl+XlMAu6rbch79KYiYKOGcbK2R1wH8h+APul1i/3wN6RF1XfWOIF+8X1ga+7SBrug== dependencies: methods "^1.1.2" superagent "^6.1.0" @@ -8063,10 +8049,10 @@ triple-beam@^1.2.0, triple-beam@^1.3.0: resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== -ts-node@10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.0.0.tgz#05f10b9a716b0b624129ad44f0ea05dac84ba3be" - integrity sha512-ROWeOIUvfFbPZkoDis0L/55Fk+6gFQNZwwKPLinacRl6tsxstTF1DbAcLKkovwnpKMVvOMHP1TIbnwXwtLg1gg== +ts-node@10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.1.0.tgz#e656d8ad3b61106938a867f69c39a8ba6efc966e" + integrity sha512-6szn3+J9WyG2hE+5W8e0ruZrzyk1uFLYye6IGMBadnOzDh8aP7t8CbFpsfCiEx2+wMixAhjFt7lOZC4+l+WbEA== dependencies: "@tsconfig/node10" "^1.0.7" "@tsconfig/node12" "^1.0.7" @@ -8080,12 +8066,11 @@ ts-node@10.0.0: yn "3.1.1" tsconfig-paths@^3.9.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" - integrity sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw== + version "3.10.1" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.10.1.tgz#79ae67a68c15289fdf5c51cb74f397522d795ed7" + integrity sha512-rETidPDgCpltxF7MjBZlAFPUHv5aHH2MymyPvh+vEyWAED4Eb/WeMbsnD/JDr4OKPOA1TssDHgIcpTN5Kh0p6Q== dependencies: - "@types/json5" "^0.0.29" - json5 "^1.0.1" + json5 "^2.2.0" minimist "^1.2.0" strip-bom "^3.0.0" @@ -8194,9 +8179,9 @@ typedarray@^0.0.6: integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= typescript@^4.0.5: - version "4.3.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.4.tgz#3f85b986945bcf31071decdd96cf8bfa65f9dcbc" - integrity sha512-uauPG7XZn9F/mo+7MrsRjyvbxFpzemRjKEZXS4AK83oP2KKOJPvb+9cO/gmnv8arWZvhnjVOXz7B49m1l0e9Ew== + version "4.3.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4" + integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA== uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.6" @@ -8322,7 +8307,7 @@ ut_metadata@^3.5.2: debug "^4.2.0" simple-sha1 "^3.0.1" -ut_pex@^3.0.0: +ut_pex@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/ut_pex/-/ut_pex-3.0.1.tgz#fb8b6e066f8f6f6de3e6b3e28e7d18e697be5854" integrity sha512-t1MHIDHSISgOJcmq8UM6Qv9/hRQYVaUvzqSNnXa5ATDbS9hXfhBpyBo2HcSyJtwPSHsmMtNui8G6yKirwJ8vow== @@ -8378,10 +8363,10 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= -utp-native@^2.4.0: - version "2.5.1" - resolved "https://registry.yarnpkg.com/utp-native/-/utp-native-2.5.1.tgz#445a3fcf23db0a841a48c4e353f8900ea852b3e8" - integrity sha512-wbrJwR8DZx8N9s1ffTcMuBK7tMBQ9tvKpIL+mWHrDvGUYfV7ivroEGFTXUr4meqy/PVbUdMfURSoBbwuGtt/YQ== +utp-native@^2.5.3: + version "2.5.3" + resolved "https://registry.yarnpkg.com/utp-native/-/utp-native-2.5.3.tgz#7c04c2a8c2858716555a77d10adb9819e3119b25" + integrity sha512-sWTrWYXPhhWJh+cS2baPzhaZc89zwlWCfwSthUjGhLkZztyPhcQllo+XVVCbNGi7dhyRlxkWxN4NKU6FbA9Y8w== dependencies: napi-macros "^2.0.0" node-gyp-build "^4.2.0" @@ -8407,11 +8392,6 @@ uuid@8.3.2, uuid@^8.1.0, uuid@^8.3.0, uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -uuid@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - v8-compile-cache@^2.0.3: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" @@ -8503,19 +8483,20 @@ webfinger.js@^2.6.6: xhr2 "^0.1.4" webtorrent@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/webtorrent/-/webtorrent-1.0.2.tgz#acd0ae3bedcfa8cb732043489ff506d07d90140a" - integrity sha512-uv9fq5md/98JyeDDyziy1H28Wc/idO80AKv+9pQ4XK0WNNjdE3FMtCKfrvU2VNS1PdAOrA6sFuUq2x0mV7k7WQ== + version "1.2.5" + resolved "https://registry.yarnpkg.com/webtorrent/-/webtorrent-1.2.5.tgz#edea45c53b98787a472381ff91d41c9164b8ac51" + integrity sha512-EvtAQ3rK4c7Kf4ZGxYOGvi8Jih8qsZka1IgNB8T5Vxw5UzSNG1nxTVNNTXL0jFhQUMsyRwIOkTgd7ZkJY6bqsw== dependencies: addr-to-ip-port "^1.5.1" bitfield "^4.0.0" - bittorrent-dht "^10.0.0" - bittorrent-protocol "^3.3.1" + bittorrent-dht "^10.0.1" + bittorrent-protocol "^3.4.2" + cache-chunk-store "^3.2.2" chrome-net "^3.3.4" chunk-store-stream "^4.3.0" cpus "^1.0.3" create-torrent "^4.7.0" - debug "^4.3.1" + debug "^4.3.2" end-of-stream "^1.4.4" escape-html "^1.0.3" fs-chunk-store "^2.0.3" @@ -8549,9 +8530,9 @@ webtorrent@^1.0.0: torrent-piece "^2.0.1" unordered-array-remove "^1.0.2" ut_metadata "^3.5.2" - ut_pex "^3.0.0" + ut_pex "^3.0.1" optionalDependencies: - utp-native "^2.4.0" + utp-native "^2.5.3" which-boxed-primitive@^1.0.2: version "1.0.2" @@ -8659,10 +8640,10 @@ word-wrap@^1.2.3: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== -workerpool@6.1.4: - version "6.1.4" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.1.4.tgz#6a972b6df82e38d50248ee2820aa98e2d0ad3090" - integrity sha512-jGWPzsUqzkow8HoAvqaPWTUPCrlPJaJ5tY8Iz7n1uCz3tTp6s3CDG0FF1NsX42WNlkRSW6Mr+CDZGnNoSsKa7g== +workerpool@6.1.5: + version "6.1.5" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.1.5.tgz#0f7cf076b6215fd7e1da903ff6f22ddd1886b581" + integrity sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw== wrap-ansi@^6.2.0: version "6.2.0" @@ -8698,9 +8679,9 @@ write-file-atomic@^3.0.0: typedarray-to-buffer "^3.1.5" ws@^7.0.0, ws@^7.4.2, ws@^7.4.5, ws@^7.4.6: - version "7.5.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.0.tgz#0033bafea031fb9df041b2026fc72a571ca44691" - integrity sha512-6ezXvzOZupqKj4jUqbQ9tXuJNo+BR2gU8fFRk3XCP3e0G6WT414u5ELe6Y0vtp7kmSJ3F7YWObSNr1ESsgi4vw== + version "7.5.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74" + integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg== ws@~6.1.0: version "6.1.4"