Begin unit tests

This commit is contained in:
Chocobozzz 2017-12-22 12:10:40 +01:00
parent bf1f650817
commit d3ea897591
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
9 changed files with 252 additions and 42 deletions

View File

@ -78,9 +78,9 @@ function addVideoCommentThread (req: express.Request, res: express.Response) {
return sequelizeTypescript.transaction(async t => { return sequelizeTypescript.transaction(async t => {
return createVideoComment({ return createVideoComment({
text: videoCommentInfo.text, text: videoCommentInfo.text,
inReplyToComment: null, inReplyToCommentId: null,
video: res.locals.video, video: res.locals.video,
actorId: res.locals.oauth.token.User.Account.Actor.id accountId: res.locals.oauth.token.User.Account.id
}, t) }, t)
}) })
} }
@ -106,9 +106,9 @@ function addVideoCommentReply (req: express.Request, res: express.Response, next
return sequelizeTypescript.transaction(async t => { return sequelizeTypescript.transaction(async t => {
return createVideoComment({ return createVideoComment({
text: videoCommentInfo.text, text: videoCommentInfo.text,
inReplyToComment: res.locals.videoComment.id, inReplyToCommentId: res.locals.videoComment.id,
video: res.locals.video, video: res.locals.video,
actorId: res.locals.oauth.token.User.Account.Actor.id accountId: res.locals.oauth.token.User.Account.id
}, t) }, t)
}) })
} }

View File

@ -267,7 +267,7 @@ function createVideoComment (byActor: ActorModel, activity: ActivityCreate) {
originCommentId: null, originCommentId: null,
inReplyToComment: null, inReplyToComment: null,
videoId: video.id, videoId: video.id,
actorId: byActor.id accountId: byAccount.id
}, { transaction: t }) }, { transaction: t })
} }
@ -281,7 +281,7 @@ function createVideoComment (byActor: ActorModel, activity: ActivityCreate) {
originCommentId, originCommentId,
inReplyToCommentId: inReplyToComment.id, inReplyToCommentId: inReplyToComment.id,
videoId: inReplyToComment.videoId, videoId: inReplyToComment.videoId,
actorId: byActor.id accountId: byAccount.id
}, { transaction: t }) }, { transaction: t })
}) })
} }

View File

@ -1,19 +1,20 @@
import * as Sequelize from 'sequelize' import * as Sequelize from 'sequelize'
import { ResultList } from '../../shared/models' import { ResultList } from '../../shared/models'
import { VideoCommentThread } from '../../shared/models/videos/video-comment.model' import { VideoCommentThreadTree } from '../../shared/models/videos/video-comment.model'
import { VideoModel } from '../models/video/video' import { VideoModel } from '../models/video/video'
import { VideoCommentModel } from '../models/video/video-comment' import { VideoCommentModel } from '../models/video/video-comment'
import { getVideoCommentActivityPubUrl } from './activitypub' import { getVideoCommentActivityPubUrl } from './activitypub'
async function createVideoComment (obj: { async function createVideoComment (obj: {
text: string, text: string,
inReplyToComment: number, inReplyToCommentId: number,
video: VideoModel video: VideoModel
actorId: number accountId: number
}, t: Sequelize.Transaction) { }, t: Sequelize.Transaction) {
let originCommentId: number = null let originCommentId: number = null
if (obj.inReplyToComment) {
const repliedComment = await VideoCommentModel.loadById(obj.inReplyToComment) if (obj.inReplyToCommentId) {
const repliedComment = await VideoCommentModel.loadById(obj.inReplyToCommentId)
if (!repliedComment) throw new Error('Unknown replied comment.') if (!repliedComment) throw new Error('Unknown replied comment.')
originCommentId = repliedComment.originCommentId || repliedComment.id originCommentId = repliedComment.originCommentId || repliedComment.id
@ -22,22 +23,23 @@ async function createVideoComment (obj: {
const comment = await VideoCommentModel.create({ const comment = await VideoCommentModel.create({
text: obj.text, text: obj.text,
originCommentId, originCommentId,
inReplyToComment: obj.inReplyToComment, inReplyToCommentId: obj.inReplyToCommentId,
videoId: obj.video.id, videoId: obj.video.id,
actorId: obj.actorId accountId: obj.accountId,
}, { transaction: t }) url: 'fake url'
}, { transaction: t, validate: false })
comment.set('url', getVideoCommentActivityPubUrl(obj.video, comment)) comment.set('url', getVideoCommentActivityPubUrl(obj.video, comment))
return comment.save({ transaction: t }) return comment.save({ transaction: t })
} }
function buildFormattedCommentTree (resultList: ResultList<VideoCommentModel>): VideoCommentThread { function buildFormattedCommentTree (resultList: ResultList<VideoCommentModel>): VideoCommentThreadTree {
// Comments are sorted by id ASC // Comments are sorted by id ASC
const comments = resultList.data const comments = resultList.data
const comment = comments.shift() const comment = comments.shift()
const thread: VideoCommentThread = { const thread: VideoCommentThreadTree = {
comment: comment.toFormattedJSON(), comment: comment.toFormattedJSON(),
children: [] children: []
} }
@ -48,7 +50,7 @@ function buildFormattedCommentTree (resultList: ResultList<VideoCommentModel>):
while (comments.length !== 0) { while (comments.length !== 0) {
const childComment = comments.shift() const childComment = comments.shift()
const childCommentThread: VideoCommentThread = { const childCommentThread: VideoCommentThreadTree = {
comment: childComment.toFormattedJSON(), comment: childComment.toFormattedJSON(),
children: [] children: []
} }

View File

@ -4,6 +4,7 @@ import { logger } from '../../helpers'
import { isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc' import { isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc'
import { isValidVideoCommentText } from '../../helpers/custom-validators/video-comments' import { isValidVideoCommentText } from '../../helpers/custom-validators/video-comments'
import { isVideoExist } from '../../helpers/custom-validators/videos' import { isVideoExist } from '../../helpers/custom-validators/videos'
import { VideoModel } from '../../models/video/video'
import { VideoCommentModel } from '../../models/video/video-comment' import { VideoCommentModel } from '../../models/video/video-comment'
import { areValidationErrors } from './utils' import { areValidationErrors } from './utils'
@ -11,7 +12,7 @@ const listVideoCommentThreadsValidator = [
param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => { async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking blacklistRemove parameters.', { parameters: req.params }) logger.debug('Checking listVideoCommentThreads parameters.', { parameters: req.params })
if (areValidationErrors(req, res)) return if (areValidationErrors(req, res)) return
if (!await isVideoExist(req.params.videoId, res)) return if (!await isVideoExist(req.params.videoId, res)) return
@ -25,11 +26,11 @@ const listVideoThreadCommentsValidator = [
param('threadId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid threadId'), param('threadId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid threadId'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => { async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking blacklistRemove parameters.', { parameters: req.params }) logger.debug('Checking listVideoThreadComments parameters.', { parameters: req.params })
if (areValidationErrors(req, res)) return if (areValidationErrors(req, res)) return
if (!await isVideoExist(req.params.videoId, res)) return if (!await isVideoExist(req.params.videoId, res)) return
if (!await isVideoCommentThreadExist(req.params.threadId, req.params.videoId, res)) return if (!await isVideoCommentThreadExist(req.params.threadId, res.locals.video, res)) return
return next() return next()
} }
@ -40,7 +41,7 @@ const addVideoCommentThreadValidator = [
body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'), body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => { async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking blacklistRemove parameters.', { parameters: req.params }) logger.debug('Checking addVideoCommentThread parameters.', { parameters: req.params })
if (areValidationErrors(req, res)) return if (areValidationErrors(req, res)) return
if (!await isVideoExist(req.params.videoId, res)) return if (!await isVideoExist(req.params.videoId, res)) return
@ -55,11 +56,11 @@ const addVideoCommentReplyValidator = [
body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'), body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => { async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking blacklistRemove parameters.', { parameters: req.params }) logger.debug('Checking addVideoCommentReply parameters.', { parameters: req.params })
if (areValidationErrors(req, res)) return if (areValidationErrors(req, res)) return
if (!await isVideoExist(req.params.videoId, res)) return if (!await isVideoExist(req.params.videoId, res)) return
if (!await isVideoCommentExist(req.params.commentId, req.params.videoId, res)) return if (!await isVideoCommentExist(req.params.commentId, res.locals.video, res)) return
return next() return next()
} }
@ -76,7 +77,7 @@ export {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
async function isVideoCommentThreadExist (id: number, videoId: number, res: express.Response) { async function isVideoCommentThreadExist (id: number, video: VideoModel, res: express.Response) {
const videoComment = await VideoCommentModel.loadById(id) const videoComment = await VideoCommentModel.loadById(id)
if (!videoComment) { if (!videoComment) {
@ -87,7 +88,7 @@ async function isVideoCommentThreadExist (id: number, videoId: number, res: expr
return false return false
} }
if (videoComment.videoId !== videoId) { if (videoComment.videoId !== video.id) {
res.status(400) res.status(400)
.json({ error: 'Video comment is associated to this video.' }) .json({ error: 'Video comment is associated to this video.' })
.end() .end()
@ -107,7 +108,7 @@ async function isVideoCommentThreadExist (id: number, videoId: number, res: expr
return true return true
} }
async function isVideoCommentExist (id: number, videoId: number, res: express.Response) { async function isVideoCommentExist (id: number, video: VideoModel, res: express.Response) {
const videoComment = await VideoCommentModel.loadById(id) const videoComment = await VideoCommentModel.loadById(id)
if (!videoComment) { if (!videoComment) {
@ -118,7 +119,7 @@ async function isVideoCommentExist (id: number, videoId: number, res: express.Re
return false return false
} }
if (videoComment.videoId !== videoId) { if (videoComment.videoId !== video.id) {
res.status(400) res.status(400)
.json({ error: 'Video comment is associated to this video.' }) .json({ error: 'Video comment is associated to this video.' })
.end() .end()

View File

@ -6,18 +6,18 @@ import {
import { VideoComment } from '../../../shared/models/videos/video-comment.model' import { VideoComment } from '../../../shared/models/videos/video-comment.model'
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub' import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub'
import { CONSTRAINTS_FIELDS } from '../../initializers' import { CONSTRAINTS_FIELDS } from '../../initializers'
import { ActorModel } from '../activitypub/actor' import { AccountModel } from '../account/account'
import { getSort, throwIfNotValid } from '../utils' import { getSort, throwIfNotValid } from '../utils'
import { VideoModel } from './video' import { VideoModel } from './video'
enum ScopeNames { enum ScopeNames {
WITH_ACTOR = 'WITH_ACTOR' WITH_ACCOUNT = 'WITH_ACCOUNT'
} }
@Scopes({ @Scopes({
[ScopeNames.WITH_ACTOR]: { [ScopeNames.WITH_ACCOUNT]: {
include: [ include: [
() => ActorModel () => AccountModel
] ]
} }
}) })
@ -84,17 +84,17 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
}) })
Video: VideoModel Video: VideoModel
@ForeignKey(() => ActorModel) @ForeignKey(() => AccountModel)
@Column @Column
actorId: number accountId: number
@BelongsTo(() => ActorModel, { @BelongsTo(() => AccountModel, {
foreignKey: { foreignKey: {
allowNull: false allowNull: false
}, },
onDelete: 'CASCADE' onDelete: 'CASCADE'
}) })
Actor: ActorModel Account: AccountModel
@AfterDestroy @AfterDestroy
static sendDeleteIfOwned (instance: VideoCommentModel) { static sendDeleteIfOwned (instance: VideoCommentModel) {
@ -132,12 +132,13 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
limit: count, limit: count,
order: [ getSort(sort) ], order: [ getSort(sort) ],
where: { where: {
videoId videoId,
inReplyToCommentId: null
} }
} }
return VideoCommentModel return VideoCommentModel
.scope([ ScopeNames.WITH_ACTOR ]) .scope([ ScopeNames.WITH_ACCOUNT ])
.findAndCountAll(query) .findAndCountAll(query)
.then(({ rows, count }) => { .then(({ rows, count }) => {
return { total: count, data: rows } return { total: count, data: rows }
@ -146,7 +147,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
static listThreadCommentsForApi (videoId: number, threadId: number) { static listThreadCommentsForApi (videoId: number, threadId: number) {
const query = { const query = {
order: [ 'id', 'ASC' ], order: [ [ 'id', 'ASC' ] ],
where: { where: {
videoId, videoId,
[ Sequelize.Op.or ]: [ [ Sequelize.Op.or ]: [
@ -157,7 +158,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
} }
return VideoCommentModel return VideoCommentModel
.scope([ ScopeNames.WITH_ACTOR ]) .scope([ ScopeNames.WITH_ACCOUNT ])
.findAndCountAll(query) .findAndCountAll(query)
.then(({ rows, count }) => { .then(({ rows, count }) => {
return { total: count, data: rows } return { total: count, data: rows }
@ -173,7 +174,10 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
inReplyToCommentId: this.inReplyToCommentId, inReplyToCommentId: this.inReplyToCommentId,
videoId: this.videoId, videoId: this.videoId,
createdAt: this.createdAt, createdAt: this.createdAt,
updatedAt: this.updatedAt updatedAt: this.updatedAt,
account: {
name: this.Account.name
}
} as VideoComment } as VideoComment
} }
} }

View File

@ -4,3 +4,4 @@ import './video-transcoder'
import './multiple-servers' import './multiple-servers'
import './follows' import './follows'
import './jobs' import './jobs'
import './video-comments'

View File

@ -0,0 +1,135 @@
/* tslint:disable:no-unused-expression */
import * as chai from 'chai'
import 'mocha'
import { VideoComment, VideoCommentThreadTree } from '../../../shared/models/videos/video-comment.model'
import { dateIsValid, flushTests, killallServers, runServer, ServerInfo, setAccessTokensToServers, uploadVideo } from '../utils'
import { addVideoCommentReply, addVideoCommentThread, getVideoCommentThreads, getVideoThreadComments } from '../utils/video-comments'
const expect = chai.expect
describe('Test a video comments', function () {
let server: ServerInfo
let videoId
let videoUUID
let threadId
before(async function () {
this.timeout(10000)
await flushTests()
server = await runServer(1)
await setAccessTokensToServers([ server ])
const res = await uploadVideo(server.url, server.accessToken, {})
videoUUID = res.body.video.uuid
videoId = res.body.video.id
})
it('Should not have threads on this video', async function () {
const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5)
expect(res.body.total).to.equal(0)
expect(res.body.data).to.be.an('array')
expect(res.body.data).to.have.lengthOf(0)
})
it('Should create a thread in this video', async function () {
const text = 'my super first comment'
await addVideoCommentThread(server.url, server.accessToken, videoUUID, text)
})
it('Should list threads of this video', async function () {
const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5)
expect(res.body.total).to.equal(1)
expect(res.body.data).to.be.an('array')
expect(res.body.data).to.have.lengthOf(1)
const comment: VideoComment = res.body.data[0]
expect(comment.inReplyToCommentId).to.be.null
expect(comment.text).equal('my super first comment')
expect(comment.videoId).to.equal(videoId)
expect(comment.id).to.equal(comment.threadId)
expect(comment.account.name).to.equal('root')
expect(dateIsValid(comment.createdAt as string)).to.be.true
expect(dateIsValid(comment.updatedAt as string)).to.be.true
threadId = comment.threadId
})
it('Should get all the thread created', async function () {
const res = await getVideoThreadComments(server.url, videoUUID, threadId)
const rootComment = res.body.comment
expect(rootComment.inReplyToCommentId).to.be.null
expect(rootComment.text).equal('my super first comment')
expect(rootComment.videoId).to.equal(videoId)
expect(dateIsValid(rootComment.createdAt as string)).to.be.true
expect(dateIsValid(rootComment.updatedAt as string)).to.be.true
})
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 text2 = 'my super answer to answer of thread 1'
await addVideoCommentReply(server.url, server.accessToken, videoId, childCommentId, text2)
const text3 = 'my second answer to thread 1'
await addVideoCommentReply(server.url, server.accessToken, videoId, threadId, text3)
})
it('Should get correctly the replies', async function () {
const res = await getVideoThreadComments(server.url, videoUUID, threadId)
const tree: VideoCommentThreadTree = res.body
expect(tree.comment.text).equal('my super first comment')
expect(tree.children).to.have.lengthOf(2)
const firstChild = tree.children[0]
expect(firstChild.comment.text).to.equal('my super answer to thread 1')
expect(firstChild.children).to.have.lengthOf(1)
const childOfFirstChild = firstChild.children[0]
expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1')
expect(childOfFirstChild.children).to.have.lengthOf(0)
const secondChild = tree.children[1]
expect(secondChild.comment.text).to.equal('my second answer to thread 1')
expect(secondChild.children).to.have.lengthOf(0)
})
it('Should create other threads', async function () {
const text1 = 'super thread 2'
await addVideoCommentThread(server.url, server.accessToken, videoUUID, text1)
const text2 = 'super thread 3'
await addVideoCommentThread(server.url, server.accessToken, videoUUID, 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.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[1].text).to.equal('super thread 2')
expect(res.body.data[2].text).to.equal('super thread 3')
})
after(async function () {
killallServers([ server ])
// Keep the logs if the test failed
if (this['ok']) {
await flushTests()
}
})
})

View File

@ -0,0 +1,64 @@
import * as request from 'supertest'
function getVideoCommentThreads (url: string, videoId: number, start: number, count: number, sort?: 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 })
return req.set('Accept', 'application/json')
.expect(200)
.expect('Content-Type', /json/)
}
function getVideoThreadComments (url: string, videoId: number, threadId: number) {
const path = '/api/v1/videos/' + videoId + '/comment-threads/' + threadId
return request(url)
.get(path)
.set('Accept', 'application/json')
.expect(200)
.expect('Content-Type', /json/)
}
function addVideoCommentThread (url: string, token: string, videoId: number, text: string, expectedStatus = 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,
inReplyToCommentId: number,
text: string,
expectedStatus = 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)
}
// ---------------------------------------------------------------------------
export {
getVideoCommentThreads,
getVideoThreadComments,
addVideoCommentThread,
addVideoCommentReply
}

View File

@ -7,11 +7,14 @@ export interface VideoComment {
videoId: number videoId: number
createdAt: Date | string createdAt: Date | string
updatedAt: Date | string updatedAt: Date | string
account: {
name: string
}
} }
export interface VideoCommentThread { export interface VideoCommentThreadTree {
comment: VideoComment comment: VideoComment
children: VideoCommentThread[] children: VideoCommentThreadTree[]
} }
export interface VideoCommentCreate { export interface VideoCommentCreate {