From 68e70a745b2010cd0199864a2addd60d8f99c732 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 4 Nov 2020 14:16:57 +0100 Subject: Add save replay live tests --- server/tests/api/live/index.ts | 2 + server/tests/api/live/live-constraints.ts | 199 +++++++++++++++++++ server/tests/api/live/live-save-replay.ts | 307 ++++++++++++++++++++++++++++++ server/tests/api/live/live.ts | 213 ++++----------------- 4 files changed, 550 insertions(+), 171 deletions(-) create mode 100644 server/tests/api/live/live-constraints.ts create mode 100644 server/tests/api/live/live-save-replay.ts (limited to 'server/tests/api/live') diff --git a/server/tests/api/live/index.ts b/server/tests/api/live/index.ts index 280daf423..ee77af286 100644 --- a/server/tests/api/live/index.ts +++ b/server/tests/api/live/index.ts @@ -1 +1,3 @@ +export * from './live-constraints' +export * from './live-save-replay' export * from './live' diff --git a/server/tests/api/live/live-constraints.ts b/server/tests/api/live/live-constraints.ts new file mode 100644 index 000000000..23c8e3b0a --- /dev/null +++ b/server/tests/api/live/live-constraints.ts @@ -0,0 +1,199 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ + +import 'mocha' +import * as chai from 'chai' +import { User, VideoDetails, VideoPrivacy } from '@shared/models' +import { + checkLiveCleanup, + cleanupTests, + createLive, + createUser, + doubleFollow, + flushAndRunMultipleServers, + getMyUserInformation, + getVideo, + runAndTestFfmpegStreamError, + ServerInfo, + setAccessTokensToServers, + setDefaultVideoChannel, + updateCustomSubConfig, + updateUser, + userLogin, + wait, + waitJobs +} from '../../../../shared/extra-utils' + +const expect = chai.expect + +describe('Test live constraints', function () { + let servers: ServerInfo[] = [] + let userId: number + let userAccessToken: string + let userChannelId: number + + async function createLiveWrapper (saveReplay: boolean) { + const liveAttributes = { + name: 'user live', + channelId: userChannelId, + privacy: VideoPrivacy.PUBLIC, + saveReplay + } + + const res = await createLive(servers[0].url, userAccessToken, liveAttributes) + return res.body.video.uuid as string + } + + async function checkSaveReplay (videoId: string, resolutions = [ 720 ]) { + for (const server of servers) { + const res = await getVideo(server.url, videoId) + + const video: VideoDetails = res.body + expect(video.isLive).to.be.false + expect(video.duration).to.be.greaterThan(0) + } + + await checkLiveCleanup(servers[0], videoId, resolutions) + } + + before(async function () { + this.timeout(120000) + + servers = await flushAndRunMultipleServers(2) + + // Get the access tokens + await setAccessTokensToServers(servers) + await setDefaultVideoChannel(servers) + + await updateCustomSubConfig(servers[0].url, servers[0].accessToken, { + live: { + enabled: true, + allowReplay: true, + transcoding: { + enabled: false + } + } + }) + + { + const user = { username: 'user1', password: 'superpassword' } + 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 resMe = await getMyUserInformation(servers[0].url, userAccessToken) + userChannelId = (resMe.body as User).videoChannels[0].id + + await updateUser({ + url: servers[0].url, + userId, + accessToken: servers[0].accessToken, + videoQuota: 1, + videoQuotaDaily: -1 + }) + } + + // Server 1 and server 2 follow each other + await doubleFollow(servers[0], servers[1]) + }) + + it('Should not have size limit if save replay is disabled', async function () { + this.timeout(60000) + + const userVideoLiveoId = await createLiveWrapper(false) + await runAndTestFfmpegStreamError(servers[0].url, userAccessToken, userVideoLiveoId, false) + }) + + it('Should have size limit depending on user global quota if save replay is enabled', async function () { + this.timeout(60000) + + // Wait for user quota memoize cache invalidation + await wait(5000) + + const userVideoLiveoId = await createLiveWrapper(true) + await runAndTestFfmpegStreamError(servers[0].url, userAccessToken, userVideoLiveoId, true) + + await waitJobs(servers) + + await checkSaveReplay(userVideoLiveoId) + }) + + it('Should have size limit depending on user daily quota if save replay is enabled', async function () { + this.timeout(60000) + + // Wait for user quota memoize cache invalidation + await wait(5000) + + await updateUser({ + url: servers[0].url, + userId, + accessToken: servers[0].accessToken, + videoQuota: -1, + videoQuotaDaily: 1 + }) + + const userVideoLiveoId = await createLiveWrapper(true) + await runAndTestFfmpegStreamError(servers[0].url, userAccessToken, userVideoLiveoId, true) + + await waitJobs(servers) + + await checkSaveReplay(userVideoLiveoId) + }) + + it('Should succeed without quota limit', async function () { + this.timeout(60000) + + // Wait for user quota memoize cache invalidation + await wait(5000) + + await updateUser({ + url: servers[0].url, + userId, + accessToken: servers[0].accessToken, + videoQuota: 10 * 1000 * 1000, + videoQuotaDaily: -1 + }) + + const userVideoLiveoId = await createLiveWrapper(true) + await runAndTestFfmpegStreamError(servers[0].url, userAccessToken, userVideoLiveoId, false) + }) + + it('Should have max duration limit', async function () { + this.timeout(30000) + + await updateCustomSubConfig(servers[0].url, servers[0].accessToken, { + live: { + enabled: true, + allowReplay: true, + maxDuration: 1, + transcoding: { + enabled: true, + resolutions: { + '240p': true, + '360p': true, + '480p': true, + '720p': true, + '1080p': true, + '2160p': true + } + } + } + }) + + const userVideoLiveoId = await createLiveWrapper(true) + await runAndTestFfmpegStreamError(servers[0].url, userAccessToken, userVideoLiveoId, true) + + await waitJobs(servers) + + await checkSaveReplay(userVideoLiveoId, [ 720, 480, 360, 240 ]) + }) + + after(async function () { + await cleanupTests(servers) + }) +}) diff --git a/server/tests/api/live/live-save-replay.ts b/server/tests/api/live/live-save-replay.ts new file mode 100644 index 000000000..3ffa0c093 --- /dev/null +++ b/server/tests/api/live/live-save-replay.ts @@ -0,0 +1,307 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ + +import 'mocha' +import * as chai from 'chai' +import { FfmpegCommand } from 'fluent-ffmpeg' +import { LiveVideoCreate, VideoDetails, VideoPrivacy, VideoState } from '@shared/models' +import { + addVideoToBlacklist, + checkLiveCleanup, + cleanupTests, + createLive, + doubleFollow, + flushAndRunMultipleServers, + getVideo, + getVideosList, + removeVideo, + sendRTMPStreamInVideo, + ServerInfo, + setAccessTokensToServers, + setDefaultVideoChannel, + stopFfmpeg, + testFfmpegStreamError, + updateCustomSubConfig, + updateVideo, + waitJobs, + waitUntilLiveStarts +} from '../../../../shared/extra-utils' + +const expect = chai.expect + +describe('Save replay setting', function () { + let servers: ServerInfo[] = [] + let liveVideoUUID: string + let ffmpegCommand: FfmpegCommand + + async function createLiveWrapper (saveReplay: boolean) { + if (liveVideoUUID) { + try { + await removeVideo(servers[0].url, servers[0].accessToken, liveVideoUUID) + await waitJobs(servers) + } catch {} + } + + const attributes: LiveVideoCreate = { + channelId: servers[0].videoChannel.id, + privacy: VideoPrivacy.PUBLIC, + name: 'my super live', + saveReplay + } + + const res = await createLive(servers[0].url, servers[0].accessToken, attributes) + return res.body.video.uuid + } + + async function checkVideosExist (videoId: string, existsInList: boolean, getStatus?: number) { + for (const server of servers) { + const length = existsInList ? 1 : 0 + + const resVideos = await getVideosList(server.url) + expect(resVideos.body.data).to.have.lengthOf(length) + expect(resVideos.body.total).to.equal(length) + + if (getStatus) { + await getVideo(server.url, videoId, getStatus) + } + } + } + + async function checkVideoState (videoId: string, state: VideoState) { + for (const server of servers) { + const res = await getVideo(server.url, videoId) + expect((res.body as VideoDetails).state.id).to.equal(state) + } + } + + before(async function () { + this.timeout(120000) + + servers = await flushAndRunMultipleServers(2) + + // Get the access tokens + await setAccessTokensToServers(servers) + await setDefaultVideoChannel(servers) + + // Server 1 and server 2 follow each other + await doubleFollow(servers[0], servers[1]) + + await updateCustomSubConfig(servers[0].url, servers[0].accessToken, { + live: { + enabled: true, + allowReplay: true, + maxDuration: null, + transcoding: { + enabled: false, + resolutions: { + '240p': true, + '360p': true, + '480p': true, + '720p': true, + '1080p': true, + '2160p': true + } + } + } + }) + }) + + describe('With save replay disabled', function () { + + before(async function () { + this.timeout(10000) + }) + + it('Should correctly create and federate the "waiting for stream" live', async function () { + this.timeout(20000) + + liveVideoUUID = await createLiveWrapper(false) + + await waitJobs(servers) + + await checkVideosExist(liveVideoUUID, false, 200) + await checkVideoState(liveVideoUUID, VideoState.WAITING_FOR_LIVE) + }) + + it('Should correctly have updated the live and federated it when streaming in the live', async function () { + this.timeout(20000) + + ffmpegCommand = await sendRTMPStreamInVideo(servers[0].url, servers[0].accessToken, liveVideoUUID) + await waitUntilLiveStarts(servers[0].url, servers[0].accessToken, liveVideoUUID) + + await waitJobs(servers) + + await checkVideosExist(liveVideoUUID, true, 200) + await checkVideoState(liveVideoUUID, VideoState.PUBLISHED) + }) + + it('Should correctly delete the video files after the stream ended', async function () { + this.timeout(30000) + + await stopFfmpeg(ffmpegCommand) + + await waitJobs(servers) + + // Live still exist, but cannot be played anymore + await checkVideosExist(liveVideoUUID, false, 200) + await checkVideoState(liveVideoUUID, VideoState.LIVE_ENDED) + + // No resolutions saved since we did not save replay + await checkLiveCleanup(servers[0], liveVideoUUID, []) + }) + + it('Should correctly terminate the stream on blacklist and delete the live', async function () { + this.timeout(40000) + + liveVideoUUID = await createLiveWrapper(false) + + ffmpegCommand = await sendRTMPStreamInVideo(servers[0].url, servers[0].accessToken, liveVideoUUID) + await waitUntilLiveStarts(servers[0].url, servers[0].accessToken, liveVideoUUID) + + await waitJobs(servers) + await checkVideosExist(liveVideoUUID, true, 200) + + await Promise.all([ + addVideoToBlacklist(servers[0].url, servers[0].accessToken, liveVideoUUID, 'bad live', true), + testFfmpegStreamError(ffmpegCommand, true) + ]) + + await waitJobs(servers) + + await checkVideosExist(liveVideoUUID, false) + + await getVideo(servers[0].url, liveVideoUUID, 401) + await getVideo(servers[1].url, liveVideoUUID, 404) + + await checkLiveCleanup(servers[0], liveVideoUUID, []) + }) + + it('Should correctly terminate the stream on delete and delete the video', async function () { + this.timeout(40000) + + liveVideoUUID = await createLiveWrapper(false) + + ffmpegCommand = await sendRTMPStreamInVideo(servers[0].url, servers[0].accessToken, liveVideoUUID) + await waitUntilLiveStarts(servers[0].url, servers[0].accessToken, liveVideoUUID) + + await waitJobs(servers) + await checkVideosExist(liveVideoUUID, true, 200) + + await Promise.all([ + testFfmpegStreamError(ffmpegCommand, true), + removeVideo(servers[0].url, servers[0].accessToken, liveVideoUUID) + ]) + + await waitJobs(servers) + + await checkVideosExist(liveVideoUUID, false, 404) + await checkLiveCleanup(servers[0], liveVideoUUID, []) + }) + }) + + describe('With save replay enabled', function () { + + it('Should correctly create and federate the "waiting for stream" live', async function () { + this.timeout(20000) + + liveVideoUUID = await createLiveWrapper(true) + + await waitJobs(servers) + + await checkVideosExist(liveVideoUUID, false, 200) + await checkVideoState(liveVideoUUID, VideoState.WAITING_FOR_LIVE) + }) + + it('Should correctly have updated the live and federated it when streaming in the live', async function () { + this.timeout(20000) + + ffmpegCommand = await sendRTMPStreamInVideo(servers[0].url, servers[0].accessToken, liveVideoUUID) + await waitUntilLiveStarts(servers[0].url, servers[0].accessToken, liveVideoUUID) + + await waitJobs(servers) + + await checkVideosExist(liveVideoUUID, true, 200) + await checkVideoState(liveVideoUUID, VideoState.PUBLISHED) + }) + + it('Should correctly have saved the live and federated it after the streaming', async function () { + this.timeout(30000) + + await stopFfmpeg(ffmpegCommand) + + await waitJobs(servers) + + // Live has been transcoded + await checkVideosExist(liveVideoUUID, true, 200) + await checkVideoState(liveVideoUUID, VideoState.PUBLISHED) + }) + + it('Should update the saved live and correctly federate the updated attributes', async function () { + this.timeout(30000) + + await updateVideo(servers[0].url, servers[0].accessToken, liveVideoUUID, { name: 'video updated' }) + await waitJobs(servers) + + for (const server of servers) { + const res = await getVideo(server.url, liveVideoUUID) + expect(res.body.name).to.equal('video updated') + expect(res.body.isLive).to.be.false + } + }) + + it('Should have cleaned up the live files', async function () { + await checkLiveCleanup(servers[0], liveVideoUUID, [ 720 ]) + }) + + it('Should correctly terminate the stream on blacklist and blacklist the saved replay video', async function () { + this.timeout(40000) + + liveVideoUUID = await createLiveWrapper(true) + + ffmpegCommand = await sendRTMPStreamInVideo(servers[0].url, servers[0].accessToken, liveVideoUUID) + await waitUntilLiveStarts(servers[0].url, servers[0].accessToken, liveVideoUUID) + + await waitJobs(servers) + await checkVideosExist(liveVideoUUID, true, 200) + + await Promise.all([ + addVideoToBlacklist(servers[0].url, servers[0].accessToken, liveVideoUUID, 'bad live', true), + testFfmpegStreamError(ffmpegCommand, true) + ]) + + await waitJobs(servers) + + await checkVideosExist(liveVideoUUID, false) + + await getVideo(servers[0].url, liveVideoUUID, 401) + await getVideo(servers[1].url, liveVideoUUID, 404) + + await checkLiveCleanup(servers[0], liveVideoUUID, [ 720 ]) + }) + + it('Should correctly terminate the stream on delete and delete the video', async function () { + this.timeout(40000) + + liveVideoUUID = await createLiveWrapper(true) + + ffmpegCommand = await sendRTMPStreamInVideo(servers[0].url, servers[0].accessToken, liveVideoUUID) + await waitUntilLiveStarts(servers[0].url, servers[0].accessToken, liveVideoUUID) + + await waitJobs(servers) + await checkVideosExist(liveVideoUUID, true, 200) + + await Promise.all([ + removeVideo(servers[0].url, servers[0].accessToken, liveVideoUUID), + testFfmpegStreamError(ffmpegCommand, true) + ]) + + await waitJobs(servers) + + await checkVideosExist(liveVideoUUID, false, 404) + await checkLiveCleanup(servers[0], liveVideoUUID, []) + }) + }) + + after(async function () { + await cleanupTests(servers) + }) +}) diff --git a/server/tests/api/live/live.ts b/server/tests/api/live/live.ts index f351e9650..f7ccb453d 100644 --- a/server/tests/api/live/live.ts +++ b/server/tests/api/live/live.ts @@ -4,6 +4,7 @@ import 'mocha' import * as chai from 'chai' import { LiveVideo, LiveVideoCreate, User, VideoDetails, VideoPrivacy } from '@shared/models' import { + addVideoToBlacklist, cleanupTests, createLive, createUser, @@ -15,6 +16,7 @@ import { getVideosList, makeRawRequest, removeVideo, + sendRTMPStream, ServerInfo, setAccessTokensToServers, setDefaultVideoChannel, @@ -22,9 +24,7 @@ import { testImage, updateCustomSubConfig, updateLive, - updateUser, userLogin, - wait, waitJobs } from '../../../../shared/extra-utils' @@ -32,7 +32,6 @@ const expect = chai.expect describe('Test live', function () { let servers: ServerInfo[] = [] - let liveVideoUUID: string let userId: number let userAccessToken: string let userChannelId: number @@ -49,7 +48,10 @@ describe('Test live', function () { await updateCustomSubConfig(servers[0].url, servers[0].accessToken, { live: { enabled: true, - allowReplay: true + allowReplay: true, + transcoding: { + enabled: false + } } }) @@ -74,6 +76,7 @@ describe('Test live', function () { }) describe('Live creation, update and delete', function () { + let liveVideoUUID: string it('Should create a live with the appropriate parameters', async function () { this.timeout(20000) @@ -220,206 +223,74 @@ describe('Test live', function () { }) }) - describe('Test live constraints', function () { + describe('Stream checks', function () { + let liveVideo: LiveVideo & VideoDetails + let rtmpUrl: string + + before(function () { + rtmpUrl = 'rtmp://' + servers[0].hostname + ':1936' + }) - async function createLiveWrapper (saveReplay: boolean) { + async function createLiveWrapper () { const liveAttributes = { name: 'user live', channelId: userChannelId, privacy: VideoPrivacy.PUBLIC, - saveReplay + saveReplay: false } const res = await createLive(servers[0].url, userAccessToken, liveAttributes) - return res.body.video.uuid as string - } - - before(async function () { - await updateCustomSubConfig(servers[0].url, servers[0].accessToken, { - live: { - enabled: true, - allowReplay: true - } - }) - - await updateUser({ - url: servers[0].url, - userId, - accessToken: servers[0].accessToken, - videoQuota: 1, - videoQuotaDaily: -1 - }) - }) + const uuid = res.body.video.uuid - it('Should not have size limit if save replay is disabled', async function () { - this.timeout(30000) + const resLive = await getLive(servers[0].url, servers[0].accessToken, uuid) + const resVideo = await getVideo(servers[0].url, uuid) - const userVideoLiveoId = await createLiveWrapper(false) - await testFfmpegStreamError(servers[0].url, userAccessToken, userVideoLiveoId, false) - }) + return Object.assign(resVideo.body, resLive.body) as LiveVideo & VideoDetails + } - it('Should have size limit depending on user global quota if save replay is enabled', async function () { + it('Should not allow a stream without the appropriate path', async function () { this.timeout(30000) - const userVideoLiveoId = await createLiveWrapper(true) - await testFfmpegStreamError(servers[0].url, userAccessToken, userVideoLiveoId, true) - - await waitJobs(servers) - - for (const server of servers) { - const res = await getVideo(server.url, userVideoLiveoId) + liveVideo = await createLiveWrapper() - const video: VideoDetails = res.body - expect(video.isLive).to.be.false - expect(video.duration).to.be.greaterThan(0) - } - - // TODO: check stream correctly saved + cleaned + const command = sendRTMPStream(rtmpUrl + '/bad-live', liveVideo.streamKey) + await testFfmpegStreamError(command, true) }) - it('Should have size limit depending on user daily quota if save replay is enabled', async function () { + it('Should not allow a stream without the appropriate stream key', async function () { this.timeout(30000) - await updateUser({ - url: servers[0].url, - userId, - accessToken: servers[0].accessToken, - videoQuota: -1, - videoQuotaDaily: 1 - }) - - const userVideoLiveoId = await createLiveWrapper(true) - await testFfmpegStreamError(servers[0].url, userAccessToken, userVideoLiveoId, true) - - // TODO: check stream correctly saved + cleaned + const command = sendRTMPStream(rtmpUrl + '/live', 'bad-stream-key') + await testFfmpegStreamError(command, true) }) - it('Should succeed without quota limit', async function () { + it('Should succeed with the correct params', async function () { this.timeout(30000) - // Wait for user quota memoize cache invalidation - await wait(5000) - - await updateUser({ - url: servers[0].url, - userId, - accessToken: servers[0].accessToken, - videoQuota: 10 * 1000 * 1000, - videoQuotaDaily: -1 - }) - - const userVideoLiveoId = await createLiveWrapper(true) - await testFfmpegStreamError(servers[0].url, userAccessToken, userVideoLiveoId, false) + const command = sendRTMPStream(rtmpUrl + '/live', liveVideo.streamKey) + await testFfmpegStreamError(command, false) }) - it('Should have max duration limit', async function () { + it('Should not allow a stream on a live that was blacklisted', async function () { this.timeout(30000) - await updateCustomSubConfig(servers[0].url, servers[0].accessToken, { - live: { - enabled: true, - allowReplay: true, - maxDuration: 1 - } - }) - - const userVideoLiveoId = await createLiveWrapper(true) - await testFfmpegStreamError(servers[0].url, userAccessToken, userVideoLiveoId, true) - - // TODO: check stream correctly saved + cleaned - }) - }) - - describe('With save replay disabled', function () { + liveVideo = await createLiveWrapper() - it('Should correctly create and federate the "waiting for stream" live', async function () { + await addVideoToBlacklist(servers[0].url, servers[0].accessToken, liveVideo.uuid) + const command = sendRTMPStream(rtmpUrl + '/live', liveVideo.streamKey) + await testFfmpegStreamError(command, true) }) - it('Should correctly have updated the live and federated it when streaming in the live', async function () { - - }) - - it('Should correctly delete the video and the live after the stream ended', async function () { - // Wait 10 seconds - // get video 404 - // get video federation 404 - - // check cleanup - }) - - it('Should correctly terminate the stream on blacklist and delete the live', async function () { - // Wait 10 seconds - // get video 404 - // get video federation 404 - - // check cleanup - }) - - it('Should correctly terminate the stream on delete and delete the video', async function () { - // Wait 10 seconds - // get video 404 - // get video federation 404 - - // check cleanup - }) - }) - - describe('With save replay enabled', function () { - - it('Should correctly create and federate the "waiting for stream" live', async function () { - - }) - - it('Should correctly have updated the live and federated it when streaming in the live', async function () { - - }) - - it('Should correctly have saved the live and federated it after the streaming', async function () { - - }) - - it('Should update the saved live and correctly federate the updated attributes', async function () { - - }) - - it('Should have cleaned up the live files', async function () { - - }) - - it('Should correctly terminate the stream on blacklist and blacklist the saved replay video', async function () { - // Wait 10 seconds - // get video -> blacklisted - // get video federation -> blacklisted - - // check cleanup live files quand meme - }) - - it('Should correctly terminate the stream on delete and delete the video', async function () { - // Wait 10 seconds - // get video 404 - // get video federation 404 - - // check cleanup - }) - }) - - describe('Stream checks', function () { - - it('Should not allow a stream without the appropriate path', async function () { - - }) - - it('Should not allow a stream without the appropriate stream key', async function () { - - }) - - it('Should not allow a stream on a live that was blacklisted', async function () { + it('Should not allow a stream on a live that was deleted', async function () { + this.timeout(30000) - }) + liveVideo = await createLiveWrapper() - it('Should not allow a stream on a live that was deleted', async function () { + await removeVideo(servers[0].url, servers[0].accessToken, liveVideo.uuid) + const command = sendRTMPStream(rtmpUrl + '/live', liveVideo.streamKey) + await testFfmpegStreamError(command, true) }) }) -- cgit v1.2.3