Add new abuses tests

This commit is contained in:
Chocobozzz 2020-07-08 15:51:46 +02:00 committed by Chocobozzz
parent 811cef146c
commit 310b5219b3
29 changed files with 869 additions and 386 deletions

View File

@ -138,8 +138,8 @@
<tr> <tr>
<td colspan="6"> <td colspan="6">
<div class="no-results"> <div class="no-results">
<ng-container *ngIf="search" i18n>No video abuses found matching current filters.</ng-container> <ng-container *ngIf="search" i18n>No abuses found matching current filters.</ng-container>
<ng-container *ngIf="!search" i18n>No video abuses found.</ng-container> <ng-container *ngIf="!search" i18n>No abuses found.</ng-container>
</div> </div>
</td> </td>
</tr> </tr>

View File

@ -118,7 +118,7 @@ export class UserNotification implements UserNotificationServer {
this.commentUrl = [ this.buildVideoUrl(this.comment.video), { threadId: this.comment.threadId } ] this.commentUrl = [ this.buildVideoUrl(this.comment.video), { threadId: this.comment.threadId } ]
break break
case UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS: case UserNotificationType.NEW_ABUSE_FOR_MODERATORS:
this.abuseUrl = '/admin/moderation/abuses/list' this.abuseUrl = '/admin/moderation/abuses/list'
if (this.abuse.video) this.videoUrl = this.buildVideoUrl(this.abuse.video) if (this.abuse.video) this.videoUrl = this.buildVideoUrl(this.abuse.video)

View File

@ -42,7 +42,7 @@
</div> </div>
</ng-container> </ng-container>
<ng-container *ngSwitchCase="UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS"> <ng-container *ngSwitchCase="UserNotificationType.NEW_ABUSE_FOR_MODERATORS">
<my-global-icon iconName="flag" aria-hidden="true"></my-global-icon> <my-global-icon iconName="flag" aria-hidden="true"></my-global-icon>
<div class="message" i18n> <div class="message" i18n>

View File

@ -140,7 +140,6 @@ export class VideoReportComponent extends FormReactive implements OnInit {
const { hasStart, startAt, hasEnd, endAt } = this.form.get('timestamp').value const { hasStart, startAt, hasEnd, endAt } = this.form.get('timestamp').value
this.abuseService.reportVideo({ this.abuseService.reportVideo({
accountId: this.video.account.id,
reason, reason,
predefinedReasons, predefinedReasons,
video: { video: {

View File

@ -100,7 +100,7 @@ async function updateAbuse (req: express.Request, res: express.Response) {
return abuse.save({ transaction: t }) return abuse.save({ transaction: t })
}) })
// Do not send the delete to other instances, we updated OUR copy of this video abuse // Do not send the delete to other instances, we updated OUR copy of this abuse
return res.type('json').status(204).end() return res.type('json').status(204).end()
} }
@ -112,7 +112,7 @@ async function deleteAbuse (req: express.Request, res: express.Response) {
return abuse.destroy({ transaction: t }) return abuse.destroy({ transaction: t })
}) })
// Do not send the delete to other instances, we delete OUR copy of this video abuse // Do not send the delete to other instances, we delete OUR copy of this abuse
return res.type('json').status(204).end() return res.type('json').status(204).end()
} }

View File

@ -50,7 +50,5 @@ async function removeUserHistory (req: express.Request, res: express.Response) {
return UserVideoHistoryModel.removeUserHistoryBefore(user, beforeDate, t) return UserVideoHistoryModel.removeUserHistoryBefore(user, beforeDate, t)
}) })
// Do not send the delete to other instances, we delete OUR copy of this video abuse
return res.type('json').status(204).end() return res.type('json').status(204).end()
} }

View File

@ -3,10 +3,10 @@ import { AbuseFilter, abusePredefinedReasonsMap, AbusePredefinedReasonsString, A
import { ABUSE_STATES, CONSTRAINTS_FIELDS } from '../../initializers/constants' import { ABUSE_STATES, CONSTRAINTS_FIELDS } from '../../initializers/constants'
import { exists, isArray } from './misc' import { exists, isArray } from './misc'
const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.ABUSES const ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.ABUSES
function isAbuseReasonValid (value: string) { function isAbuseReasonValid (value: string) {
return exists(value) && validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.REASON) return exists(value) && validator.isLength(value, ABUSES_CONSTRAINTS_FIELDS.REASON)
} }
function isAbusePredefinedReasonValid (value: AbusePredefinedReasonsString) { function isAbusePredefinedReasonValid (value: AbusePredefinedReasonsString) {
@ -32,7 +32,7 @@ function isAbuseTimestampCoherent (endAt: number, { req }) {
} }
function isAbuseModerationCommentValid (value: string) { function isAbuseModerationCommentValid (value: string) {
return exists(value) && validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.MODERATION_COMMENT) return exists(value) && validator.isLength(value, ABUSES_CONSTRAINTS_FIELDS.MODERATION_COMMENT)
} }
function isAbuseStateValid (value: string) { function isAbuseStateValid (value: string) {

View File

@ -68,7 +68,7 @@ async function doesVideoCommentExist (idArg: number | string, video: MVideoId, r
async function doesCommentIdExist (idArg: number | string, res: express.Response) { async function doesCommentIdExist (idArg: number | string, res: express.Response) {
const id = parseInt(idArg + '', 10) const id = parseInt(idArg + '', 10)
const videoComment = await VideoCommentModel.loadById(id) const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id)
if (!videoComment) { if (!videoComment) {
res.status(404) res.status(404)
@ -77,7 +77,7 @@ async function doesCommentIdExist (idArg: number | string, res: express.Response
return false return false
} }
res.locals.videoComment = videoComment res.locals.videoCommentFull = videoComment
return true return true
} }

View File

@ -30,7 +30,7 @@ async function doesAbuseExist (abuseId: number | string, res: Response) {
if (!abuse) { if (!abuse) {
res.status(404) res.status(404)
.json({ error: 'Video abuse not found' }) .json({ error: 'Abuse not found' })
return false return false
} }

View File

@ -43,12 +43,10 @@ async function up (utils: {
await utils.sequelize.query(` await utils.sequelize.query(`
CREATE TABLE IF NOT EXISTS "commentAbuse" ( CREATE TABLE IF NOT EXISTS "commentAbuse" (
"id" serial, "id" serial,
"deletedComment" jsonb DEFAULT NULL,
"abuseId" integer NOT NULL REFERENCES "abuse" ("id") ON DELETE CASCADE ON UPDATE CASCADE, "abuseId" integer NOT NULL REFERENCES "abuse" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
"videoCommentId" integer REFERENCES "videoComment" ("id") ON DELETE SET NULL ON UPDATE CASCADE, "videoCommentId" integer REFERENCES "videoComment" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
"createdAt" timestamp WITH time zone NOT NULL, "createdAt" timestamp WITH time zone NOT NULL,
"updatedAt" timestamp WITH time zone NOT NULL, "updatedAt" timestamp WITH time zone NOT NULL,
"commentId" integer REFERENCES "videoComment" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
PRIMARY KEY ("id") PRIMARY KEY ("id")
); );
`) `)

View File

@ -325,6 +325,7 @@ class Emailer {
subject: `New comment abuse report from ${reporter}`, subject: `New comment abuse report from ${reporter}`,
locals: { locals: {
commentUrl, commentUrl,
videoName: comment.Video.name,
isLocal: comment.isOwned(), isLocal: comment.isOwned(),
commentCreatedAt: new Date(comment.createdAt).toLocaleString(), commentCreatedAt: new Date(comment.createdAt).toLocaleString(),
reason: abuse.reason, reason: abuse.reason,

View File

@ -7,7 +7,8 @@ block title
block content block content
p p
| #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] received an abuse report for the #{isLocal ? '' : 'remote '}comment " | #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] received an abuse report for the #{isLocal ? '' : 'remote '}comment "
a(href=commentUrl) of #{flaggedAccount} a(href=commentUrl) on video #{videoName}
| of #{flaggedAccount}
| created on #{commentCreatedAt} | created on #{commentCreatedAt}
p The reporter, #{reporter}, cited the following reason(s): p The reporter, #{reporter}, cited the following reason(s):

View File

@ -371,7 +371,7 @@ class Notifier {
async function notificationCreator (user: MUserWithNotificationSetting) { async function notificationCreator (user: MUserWithNotificationSetting) {
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({ const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
type: UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS, type: UserNotificationType.NEW_ABUSE_FOR_MODERATORS,
userId: user.id, userId: user.id,
abuseId: abuse.id abuseId: abuse.id
}) })

View File

@ -128,7 +128,7 @@ const abuseListValidator = [
.custom(exists).withMessage('Should have a valid search'), .custom(exists).withMessage('Should have a valid search'),
query('state') query('state')
.optional() .optional()
.custom(isAbuseStateValid).withMessage('Should have a valid video abuse state'), .custom(isAbuseStateValid).withMessage('Should have a valid abuse state'),
query('videoIs') query('videoIs')
.optional() .optional()
.custom(isAbuseVideoIsValid).withMessage('Should have a valid "video is" attribute'), .custom(isAbuseVideoIsValid).withMessage('Should have a valid "video is" attribute'),

View File

@ -362,8 +362,8 @@ export class AbuseModel extends Model<AbuseModel> {
const countReportsForReporter = this.get('countReportsForReporter') as number const countReportsForReporter = this.get('countReportsForReporter') as number
const countReportsForReportee = this.get('countReportsForReportee') as number const countReportsForReportee = this.get('countReportsForReportee') as number
let video: VideoAbuse let video: VideoAbuse = null
let comment: VideoCommentAbuse let comment: VideoCommentAbuse = null
if (this.VideoAbuse) { if (this.VideoAbuse) {
const abuseModel = this.VideoAbuse const abuseModel = this.VideoAbuse
@ -391,13 +391,13 @@ export class AbuseModel extends Model<AbuseModel> {
if (this.VideoCommentAbuse) { if (this.VideoCommentAbuse) {
const abuseModel = this.VideoCommentAbuse const abuseModel = this.VideoCommentAbuse
const entity = abuseModel.VideoComment || abuseModel.deletedComment const entity = abuseModel.VideoComment
comment = { comment = {
id: entity.id, id: entity.id,
text: entity.text, text: entity.text ?? '',
deleted: !abuseModel.VideoComment, deleted: entity.isDeleted(),
video: { video: {
id: entity.Video.id, id: entity.Video.id,

View File

@ -1,5 +1,4 @@
import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
import { VideoComment } from '@shared/models'
import { VideoCommentModel } from '../video/video-comment' import { VideoCommentModel } from '../video/video-comment'
import { AbuseModel } from './abuse' import { AbuseModel } from './abuse'
@ -22,11 +21,6 @@ export class VideoCommentAbuseModel extends Model<VideoCommentAbuseModel> {
@UpdatedAt @UpdatedAt
updatedAt: Date updatedAt: Date
@AllowNull(true)
@Default(null)
@Column(DataType.JSONB)
deletedComment: VideoComment & { Video: { name: string, id: number, uuid: string }}
@ForeignKey(() => AbuseModel) @ForeignKey(() => AbuseModel)
@Column @Column
abuseId: number abuseId: number

View File

@ -109,7 +109,7 @@ function buildAccountInclude (required: boolean, withActor = false) {
required: true, required: true,
include: [ include: [
{ {
attributes: [ 'uuid' ], attributes: [ 'id', 'name', 'uuid' ],
model: VideoModel.unscoped(), model: VideoModel.unscoped(),
required: true required: true
} }
@ -492,6 +492,8 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
threadId: abuse.VideoCommentAbuse.VideoComment.getThreadId(), threadId: abuse.VideoCommentAbuse.VideoComment.getThreadId(),
video: { video: {
id: abuse.VideoCommentAbuse.VideoComment.Video.id,
name: abuse.VideoCommentAbuse.VideoComment.Video.name,
uuid: abuse.VideoCommentAbuse.VideoComment.Video.uuid uuid: abuse.VideoCommentAbuse.VideoComment.Video.uuid
} }
} : undefined } : undefined

View File

@ -3,7 +3,6 @@ import { uniq } from 'lodash'
import { FindOptions, Op, Order, ScopeOptions, Sequelize, Transaction } from 'sequelize' import { FindOptions, Op, Order, ScopeOptions, Sequelize, Transaction } from 'sequelize'
import { import {
AllowNull, AllowNull,
BeforeDestroy,
BelongsTo, BelongsTo,
Column, Column,
CreatedAt, CreatedAt,
@ -16,7 +15,6 @@ import {
Table, Table,
UpdatedAt UpdatedAt
} from 'sequelize-typescript' } from 'sequelize-typescript'
import { logger } from '@server/helpers/logger'
import { getServerActor } from '@server/models/application/application' import { getServerActor } from '@server/models/application/application'
import { MAccount, MAccountId, MUserAccountId } from '@server/types/models' import { MAccount, MAccountId, MUserAccountId } from '@server/types/models'
import { VideoPrivacy } from '@shared/models' import { VideoPrivacy } from '@shared/models'
@ -242,51 +240,13 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
@HasMany(() => VideoCommentAbuseModel, { @HasMany(() => VideoCommentAbuseModel, {
foreignKey: { foreignKey: {
name: 'commentId', name: 'videoCommentId',
allowNull: true allowNull: true
}, },
onDelete: 'set null' onDelete: 'set null'
}) })
CommentAbuses: VideoCommentAbuseModel[] CommentAbuses: VideoCommentAbuseModel[]
@BeforeDestroy
static async saveEssentialDataToAbuses (instance: VideoCommentModel, options) {
const tasks: Promise<any>[] = []
if (!Array.isArray(instance.CommentAbuses)) {
instance.CommentAbuses = await instance.$get('CommentAbuses')
if (instance.CommentAbuses.length === 0) return undefined
}
if (!instance.Video) {
instance.Video = await instance.$get('Video')
}
logger.info('Saving video comment %s for abuse.', instance.url)
const details = Object.assign(instance.toFormattedJSON(), {
Video: {
id: instance.Video.id,
name: instance.Video.name,
uuid: instance.Video.uuid
}
})
for (const abuse of instance.CommentAbuses) {
abuse.deletedComment = details
tasks.push(abuse.save({ transaction: options.transaction }))
}
Promise.all(tasks)
.catch(err => {
logger.error('Some errors when saving details of comment %s in its abuses before destroy hook.', instance.url, { err })
})
return undefined
}
static loadById (id: number, t?: Transaction): Bluebird<MComment> { static loadById (id: number, t?: Transaction): Bluebird<MComment> {
const query: FindOptions = { const query: FindOptions = {
where: { where: {

View File

@ -21,9 +21,7 @@ import {
checkBadStartPagination checkBadStartPagination
} from '../../../../shared/extra-utils/requests/check-api-params' } from '../../../../shared/extra-utils/requests/check-api-params'
// FIXME: deprecated in 2.3. Remove this controller describe('Test abuses API validators', function () {
describe('Test video abuses API validators', function () {
const basePath = '/api/v1/abuses/' const basePath = '/api/v1/abuses/'
let server: ServerInfo let server: ServerInfo

View File

@ -2,21 +2,30 @@
import 'mocha' import 'mocha'
import * as chai from 'chai' import * as chai from 'chai'
import { Abuse, AbusePredefinedReasonsString, AbuseState } from '@shared/models' import { Abuse, AbuseFilter, AbusePredefinedReasonsString, AbuseState, VideoComment, Account } from '@shared/models'
import { import {
addVideoCommentThread,
cleanupTests, cleanupTests,
createUser, createUser,
deleteVideoAbuse, deleteAbuse,
deleteVideoComment,
flushAndRunMultipleServers, flushAndRunMultipleServers,
getVideoAbusesList, getAbusesList,
getVideoCommentThreads,
getVideoIdFromUUID,
getVideosList, getVideosList,
immutableAssign,
removeVideo, removeVideo,
reportVideoAbuse, reportAbuse,
ServerInfo, ServerInfo,
setAccessTokensToServers, setAccessTokensToServers,
updateVideoAbuse, updateAbuse,
uploadVideo, uploadVideo,
userLogin uploadVideoAndGetId,
userLogin,
getAccount,
removeUser,
generateUserAccessToken
} from '../../../../shared/extra-utils/index' } from '../../../../shared/extra-utils/index'
import { doubleFollow } from '../../../../shared/extra-utils/server/follows' import { doubleFollow } from '../../../../shared/extra-utils/server/follows'
import { waitJobs } from '../../../../shared/extra-utils/server/jobs' import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
@ -31,6 +40,7 @@ const expect = chai.expect
describe('Test abuses', function () { describe('Test abuses', function () {
let servers: ServerInfo[] = [] let servers: ServerInfo[] = []
let abuseServer1: Abuse
let abuseServer2: Abuse let abuseServer2: Abuse
before(async function () { before(async function () {
@ -44,6 +54,12 @@ describe('Test abuses', function () {
// Server 1 and server 2 follow each other // Server 1 and server 2 follow each other
await doubleFollow(servers[0], servers[1]) await doubleFollow(servers[0], servers[1])
})
describe('Video abuses', function () {
before(async function () {
this.timeout(50000)
// Upload some videos on each servers // Upload some videos on each servers
const video1Attributes = { const video1Attributes = {
@ -70,8 +86,8 @@ describe('Test abuses', function () {
servers[1].video = videos.find(video => video.name === 'my super name for server 2') servers[1].video = videos.find(video => video.name === 'my super name for server 2')
}) })
it('Should not have video abuses', async function () { it('Should not have abuses', async function () {
const res = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken })
expect(res.body.total).to.equal(0) expect(res.body.total).to.equal(0)
expect(res.body.data).to.be.an('array') expect(res.body.data).to.be.an('array')
@ -82,14 +98,14 @@ describe('Test abuses', function () {
this.timeout(15000) this.timeout(15000)
const reason = 'my super bad reason' const reason = 'my super bad reason'
await reportVideoAbuse(servers[0].url, servers[0].accessToken, servers[0].video.id, reason) await reportAbuse({ url: servers[0].url, token: servers[0].accessToken, videoId: servers[0].video.id, reason })
// We wait requests propagation, even if the server 1 is not supposed to make a request to server 2 // We wait requests propagation, even if the server 1 is not supposed to make a request to server 2
await waitJobs(servers) await waitJobs(servers)
}) })
it('Should have 1 video abuses on server 1 and 0 on server 2', async function () { it('Should have 1 video abuses on server 1 and 0 on server 2', async function () {
const res1 = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) const res1 = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken })
expect(res1.body.total).to.equal(1) expect(res1.body.total).to.equal(1)
expect(res1.body.data).to.be.an('array') expect(res1.body.data).to.be.an('array')
@ -97,16 +113,25 @@ describe('Test abuses', function () {
const abuse: Abuse = res1.body.data[0] const abuse: Abuse = res1.body.data[0]
expect(abuse.reason).to.equal('my super bad reason') expect(abuse.reason).to.equal('my super bad reason')
expect(abuse.reporterAccount.name).to.equal('root') expect(abuse.reporterAccount.name).to.equal('root')
expect(abuse.reporterAccount.host).to.equal('localhost:' + servers[0].port) expect(abuse.reporterAccount.host).to.equal(servers[0].host)
expect(abuse.video.id).to.equal(servers[0].video.id) expect(abuse.video.id).to.equal(servers[0].video.id)
expect(abuse.video.channel).to.exist expect(abuse.video.channel).to.exist
expect(abuse.count).to.equal(1)
expect(abuse.nth).to.equal(1) expect(abuse.comment).to.be.null
expect(abuse.flaggedAccount.name).to.equal('root')
expect(abuse.flaggedAccount.host).to.equal(servers[0].host)
expect(abuse.video.countReports).to.equal(1)
expect(abuse.video.nthReport).to.equal(1)
expect(abuse.countReportsForReporter).to.equal(1) expect(abuse.countReportsForReporter).to.equal(1)
expect(abuse.countReportsForReportee).to.equal(1) expect(abuse.countReportsForReportee).to.equal(1)
const res2 = await getVideoAbusesList({ url: servers[1].url, token: servers[1].accessToken }) const res2 = await getAbusesList({ url: servers[1].url, token: servers[1].accessToken })
expect(res2.body.total).to.equal(0) expect(res2.body.total).to.equal(0)
expect(res2.body.data).to.be.an('array') expect(res2.body.data).to.be.an('array')
expect(res2.body.data.length).to.equal(0) expect(res2.body.data.length).to.equal(0)
@ -116,86 +141,88 @@ describe('Test abuses', function () {
this.timeout(10000) this.timeout(10000)
const reason = 'my super bad reason 2' const reason = 'my super bad reason 2'
await reportVideoAbuse(servers[0].url, servers[0].accessToken, servers[1].video.id, reason) await reportAbuse({ url: servers[0].url, token: servers[0].accessToken, videoId: servers[1].video.id, reason })
// We wait requests propagation // We wait requests propagation
await waitJobs(servers) await waitJobs(servers)
}) })
it('Should have 2 video abuses on server 1 and 1 on server 2', async function () { it('Should have 2 video abuses on server 1 and 1 on server 2', async function () {
const res1 = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) const res1 = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken })
expect(res1.body.total).to.equal(2) expect(res1.body.total).to.equal(2)
expect(res1.body.data).to.be.an('array')
expect(res1.body.data.length).to.equal(2) expect(res1.body.data.length).to.equal(2)
const abuse1: Abuse = res1.body.data[0] const abuse1: Abuse = res1.body.data[0]
expect(abuse1.reason).to.equal('my super bad reason') expect(abuse1.reason).to.equal('my super bad reason')
expect(abuse1.reporterAccount.name).to.equal('root') expect(abuse1.reporterAccount.name).to.equal('root')
expect(abuse1.reporterAccount.host).to.equal('localhost:' + servers[0].port) expect(abuse1.reporterAccount.host).to.equal(servers[0].host)
expect(abuse1.video.id).to.equal(servers[0].video.id) expect(abuse1.video.id).to.equal(servers[0].video.id)
expect(abuse1.video.countReports).to.equal(1)
expect(abuse1.video.nthReport).to.equal(1)
expect(abuse1.comment).to.be.null
expect(abuse1.flaggedAccount.name).to.equal('root')
expect(abuse1.flaggedAccount.host).to.equal(servers[0].host)
expect(abuse1.state.id).to.equal(AbuseState.PENDING) expect(abuse1.state.id).to.equal(AbuseState.PENDING)
expect(abuse1.state.label).to.equal('Pending') expect(abuse1.state.label).to.equal('Pending')
expect(abuse1.moderationComment).to.be.null expect(abuse1.moderationComment).to.be.null
expect(abuse1.count).to.equal(1)
expect(abuse1.nth).to.equal(1)
const abuse2: Abuse = res1.body.data[1] const abuse2: Abuse = res1.body.data[1]
expect(abuse2.reason).to.equal('my super bad reason 2') expect(abuse2.reason).to.equal('my super bad reason 2')
expect(abuse2.reporterAccount.name).to.equal('root') expect(abuse2.reporterAccount.name).to.equal('root')
expect(abuse2.reporterAccount.host).to.equal('localhost:' + servers[0].port) expect(abuse2.reporterAccount.host).to.equal(servers[0].host)
expect(abuse2.video.id).to.equal(servers[1].video.id) expect(abuse2.video.id).to.equal(servers[1].video.id)
expect(abuse2.comment).to.be.null
expect(abuse2.flaggedAccount.name).to.equal('root')
expect(abuse2.flaggedAccount.host).to.equal(servers[1].host)
expect(abuse2.state.id).to.equal(AbuseState.PENDING) expect(abuse2.state.id).to.equal(AbuseState.PENDING)
expect(abuse2.state.label).to.equal('Pending') expect(abuse2.state.label).to.equal('Pending')
expect(abuse2.moderationComment).to.be.null expect(abuse2.moderationComment).to.be.null
const res2 = await getVideoAbusesList({ url: servers[1].url, token: servers[1].accessToken }) const res2 = await getAbusesList({ url: servers[1].url, token: servers[1].accessToken })
expect(res2.body.total).to.equal(1) expect(res2.body.total).to.equal(1)
expect(res2.body.data).to.be.an('array')
expect(res2.body.data.length).to.equal(1) expect(res2.body.data.length).to.equal(1)
abuseServer2 = res2.body.data[0] abuseServer2 = res2.body.data[0]
expect(abuseServer2.reason).to.equal('my super bad reason 2') expect(abuseServer2.reason).to.equal('my super bad reason 2')
expect(abuseServer2.reporterAccount.name).to.equal('root') expect(abuseServer2.reporterAccount.name).to.equal('root')
expect(abuseServer2.reporterAccount.host).to.equal('localhost:' + servers[0].port) expect(abuseServer2.reporterAccount.host).to.equal(servers[0].host)
expect(abuse2.flaggedAccount.name).to.equal('root')
expect(abuse2.flaggedAccount.host).to.equal(servers[1].host)
expect(abuseServer2.state.id).to.equal(AbuseState.PENDING) expect(abuseServer2.state.id).to.equal(AbuseState.PENDING)
expect(abuseServer2.state.label).to.equal('Pending') expect(abuseServer2.state.label).to.equal('Pending')
expect(abuseServer2.moderationComment).to.be.null expect(abuseServer2.moderationComment).to.be.null
}) })
it('Should update the state of a video abuse', async function () {
const body = { state: AbuseState.REJECTED }
await updateVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id, body)
const res = await getVideoAbusesList({ url: servers[1].url, token: servers[1].accessToken })
expect(res.body.data[0].state.id).to.equal(AbuseState.REJECTED)
})
it('Should add a moderation comment', async function () {
const body = { state: AbuseState.ACCEPTED, moderationComment: 'It is valid' }
await updateVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id, body)
const res = await getVideoAbusesList({ url: servers[1].url, token: servers[1].accessToken })
expect(res.body.data[0].state.id).to.equal(AbuseState.ACCEPTED)
expect(res.body.data[0].moderationComment).to.equal('It is valid')
})
it('Should hide video abuses from blocked accounts', async function () { it('Should hide video abuses from blocked accounts', async function () {
this.timeout(10000) this.timeout(10000)
{ {
await reportVideoAbuse(servers[1].url, servers[1].accessToken, servers[0].video.uuid, 'will mute this') const videoId = await getVideoIdFromUUID(servers[1].url, servers[0].video.uuid)
await reportAbuse({ url: servers[1].url, token: servers[1].accessToken, videoId, reason: 'will mute this' })
await waitJobs(servers) await waitJobs(servers)
const res = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken })
expect(res.body.total).to.equal(3) expect(res.body.total).to.equal(3)
} }
const accountToBlock = 'root@localhost:' + servers[1].port const accountToBlock = 'root@' + servers[1].host
{ {
await addAccountToServerBlocklist(servers[0].url, servers[0].accessToken, accountToBlock) await addAccountToServerBlocklist(servers[0].url, servers[0].accessToken, accountToBlock)
const res = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken })
expect(res.body.total).to.equal(2) expect(res.body.total).to.equal(2)
const abuse = res.body.data.find(a => a.reason === 'will mute this') const abuse = res.body.data.find(a => a.reason === 'will mute this')
@ -205,7 +232,7 @@ describe('Test abuses', function () {
{ {
await removeAccountFromServerBlocklist(servers[0].url, servers[0].accessToken, accountToBlock) await removeAccountFromServerBlocklist(servers[0].url, servers[0].accessToken, accountToBlock)
const res = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken })
expect(res.body.total).to.equal(3) expect(res.body.total).to.equal(3)
} }
}) })
@ -216,7 +243,7 @@ describe('Test abuses', function () {
{ {
await addServerToServerBlocklist(servers[0].url, servers[0].accessToken, servers[1].host) await addServerToServerBlocklist(servers[0].url, servers[0].accessToken, servers[1].host)
const res = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken })
expect(res.body.total).to.equal(2) expect(res.body.total).to.equal(2)
const abuse = res.body.data.find(a => a.reason === 'will mute this') const abuse = res.body.data.find(a => a.reason === 'will mute this')
@ -226,7 +253,7 @@ describe('Test abuses', function () {
{ {
await removeServerFromServerBlocklist(servers[0].url, servers[0].accessToken, serverToBlock) await removeServerFromServerBlocklist(servers[0].url, servers[0].accessToken, serverToBlock)
const res = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken })
expect(res.body.total).to.equal(3) expect(res.body.total).to.equal(3)
} }
}) })
@ -238,12 +265,12 @@ describe('Test abuses', function () {
await waitJobs(servers) await waitJobs(servers)
const res = await getVideoAbusesList({ url: servers[1].url, token: servers[1].accessToken }) const res = await getAbusesList({ url: servers[1].url, token: servers[1].accessToken })
expect(res.body.total).to.equal(2, "wrong number of videos returned") expect(res.body.total).to.equal(2, "wrong number of videos returned")
expect(res.body.data.length).to.equal(2, "wrong number of videos returned") expect(res.body.data).to.have.lengthOf(2, "wrong number of videos returned")
expect(res.body.data[0].id).to.equal(abuseServer2.id, "wrong origin server id for first video")
const abuse: Abuse = res.body.data[0] const abuse: Abuse = res.body.data[0]
expect(abuse.id).to.equal(abuseServer2.id, "wrong origin server id for first video")
expect(abuse.video.id).to.equal(abuseServer2.video.id, "wrong video id") expect(abuse.video.id).to.equal(abuseServer2.video.id, "wrong video id")
expect(abuse.video.channel).to.exist expect(abuse.video.channel).to.exist
expect(abuse.video.deleted).to.be.true expect(abuse.video.deleted).to.be.true
@ -270,24 +297,24 @@ describe('Test abuses', function () {
// resume with the test // resume with the test
const reason3 = 'my super bad reason 3' const reason3 = 'my super bad reason 3'
await reportVideoAbuse(servers[0].url, servers[0].accessToken, video3.id, reason3) await reportAbuse({ url: servers[0].url, token: servers[0].accessToken, videoId: video3.id, reason: reason3 })
const reason4 = 'my super bad reason 4'
await reportVideoAbuse(servers[0].url, userAccessToken, servers[0].video.id, reason4)
const res2 = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) const reason4 = 'my super bad reason 4'
await reportAbuse({ url: servers[0].url, token: userAccessToken, videoId: servers[0].video.id, reason: reason4 })
{ {
for (const abuse of res2.body.data as Abuse[]) { const res2 = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken })
if (abuse.video.id === video3.id) { const abuses = res2.body.data as Abuse[]
expect(abuse.count).to.equal(1, "wrong reports count for video 3")
expect(abuse.nth).to.equal(1, "wrong report position in report list for video 3") const abuseVideo3 = res2.body.data.find(a => a.video.id === video3.id)
expect(abuse.countReportsForReportee).to.equal(1, "wrong reports count for reporter on video 3 abuse") expect(abuseVideo3).to.not.be.undefined
expect(abuse.countReportsForReporter).to.equal(3, "wrong reports count for reportee on video 3 abuse") expect(abuseVideo3.video.countReports).to.equal(1, "wrong reports count for video 3")
} expect(abuseVideo3.video.nthReport).to.equal(1, "wrong report position in report list for video 3")
if (abuse.video.id === servers[0].video.id) { expect(abuseVideo3.countReportsForReportee).to.equal(1, "wrong reports count for reporter on video 3 abuse")
expect(abuse.countReportsForReportee).to.equal(3, "wrong reports count for reporter on video 1 abuse") expect(abuseVideo3.countReportsForReporter).to.equal(3, "wrong reports count for reportee on video 3 abuse")
}
} const abuseServer1 = abuses.find(a => a.video.id === servers[0].video.id)
expect(abuseServer1.countReportsForReportee).to.equal(3, "wrong reports count for reporter on video 1 abuse")
} }
}) })
@ -296,17 +323,17 @@ describe('Test abuses', function () {
const reason5 = 'my super bad reason 5' const reason5 = 'my super bad reason 5'
const predefinedReasons5: AbusePredefinedReasonsString[] = [ 'violentOrRepulsive', 'captions' ] const predefinedReasons5: AbusePredefinedReasonsString[] = [ 'violentOrRepulsive', 'captions' ]
const createdAbuse = (await reportVideoAbuse( const createdAbuse = (await reportAbuse({
servers[0].url, url: servers[0].url,
servers[0].accessToken, token: servers[0].accessToken,
servers[0].video.id, videoId: servers[0].video.id,
reason5, reason: reason5,
predefinedReasons5, predefinedReasons: predefinedReasons5,
1, startAt: 1,
5 endAt: 5
)).body.abuse })).body.abuse
const res = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken })
{ {
const abuse = (res.body.data as Abuse[]).find(a => a.id === createdAbuse.id) const abuse = (res.body.data as Abuse[]).find(a => a.id === createdAbuse.id)
@ -320,25 +347,27 @@ describe('Test abuses', function () {
it('Should delete the video abuse', async function () { it('Should delete the video abuse', async function () {
this.timeout(10000) this.timeout(10000)
await deleteVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id) await deleteAbuse(servers[1].url, servers[1].accessToken, abuseServer2.id)
await waitJobs(servers) await waitJobs(servers)
{ {
const res = await getVideoAbusesList({ url: servers[1].url, token: servers[1].accessToken }) const res = await getAbusesList({ url: servers[1].url, token: servers[1].accessToken })
expect(res.body.total).to.equal(1) expect(res.body.total).to.equal(1)
expect(res.body.data.length).to.equal(1) expect(res.body.data.length).to.equal(1)
expect(res.body.data[0].id).to.not.equal(abuseServer2.id) expect(res.body.data[0].id).to.not.equal(abuseServer2.id)
} }
{ {
const res = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken })
expect(res.body.total).to.equal(6) expect(res.body.total).to.equal(6)
} }
}) })
it('Should list and filter video abuses', async function () { it('Should list and filter video abuses', async function () {
async function list (query: Omit<Parameters<typeof getVideoAbusesList>[0], 'url' | 'token'>) { this.timeout(10000)
async function list (query: Omit<Parameters<typeof getAbusesList>[0], 'url' | 'token'>) {
const options = { const options = {
url: servers[0].url, url: servers[0].url,
token: servers[0].accessToken token: servers[0].accessToken
@ -346,7 +375,7 @@ describe('Test abuses', function () {
Object.assign(options, query) Object.assign(options, query)
const res = await getVideoAbusesList(options) const res = await getAbusesList(options)
return res.body.data as Abuse[] return res.body.data as Abuse[]
} }
@ -377,6 +406,370 @@ describe('Test abuses', function () {
expect(await list({ predefinedReason: 'violentOrRepulsive' })).to.have.lengthOf(1) expect(await list({ predefinedReason: 'violentOrRepulsive' })).to.have.lengthOf(1)
expect(await list({ predefinedReason: 'serverRules' })).to.have.lengthOf(0) expect(await list({ predefinedReason: 'serverRules' })).to.have.lengthOf(0)
}) })
})
describe('Comment abuses', function () {
async function getComment (url: string, videoIdArg: number | string) {
const videoId = typeof videoIdArg === 'string'
? await getVideoIdFromUUID(url, videoIdArg)
: videoIdArg
const res = await getVideoCommentThreads(url, videoId, 0, 5)
return res.body.data[0] as VideoComment
}
before(async function () {
this.timeout(50000)
servers[0].video = await uploadVideoAndGetId({ server: servers[0], videoName: 'server 1' })
servers[1].video = await uploadVideoAndGetId({ server: servers[1], videoName: 'server 2' })
await addVideoCommentThread(servers[0].url, servers[0].accessToken, servers[0].video.id, 'comment server 1')
await addVideoCommentThread(servers[1].url, servers[1].accessToken, servers[1].video.id, 'comment server 2')
await waitJobs(servers)
})
it('Should report abuse on a comment', async function () {
this.timeout(15000)
const comment = await getComment(servers[0].url, servers[0].video.id)
const reason = 'it is a bad comment'
await reportAbuse({ url: servers[0].url, token: servers[0].accessToken, commentId: comment.id, reason })
await waitJobs(servers)
})
it('Should have 1 comment abuse on server 1 and 0 on server 2', async function () {
{
const comment = await getComment(servers[0].url, servers[0].video.id)
const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'comment' })
expect(res.body.total).to.equal(1)
expect(res.body.data).to.have.lengthOf(1)
const abuse: Abuse = res.body.data[0]
expect(abuse.reason).to.equal('it is a bad comment')
expect(abuse.reporterAccount.name).to.equal('root')
expect(abuse.reporterAccount.host).to.equal(servers[0].host)
expect(abuse.video).to.be.null
expect(abuse.comment.deleted).to.be.false
expect(abuse.comment.id).to.equal(comment.id)
expect(abuse.comment.text).to.equal(comment.text)
expect(abuse.comment.video.name).to.equal('server 1')
expect(abuse.comment.video.id).to.equal(servers[0].video.id)
expect(abuse.comment.video.uuid).to.equal(servers[0].video.uuid)
expect(abuse.countReportsForReporter).to.equal(5)
expect(abuse.countReportsForReportee).to.equal(5)
}
{
const res = await getAbusesList({ url: servers[1].url, token: servers[1].accessToken, filter: 'comment' })
expect(res.body.total).to.equal(0)
expect(res.body.data.length).to.equal(0)
}
})
it('Should report abuse on a remote comment', async function () {
this.timeout(10000)
const comment = await getComment(servers[0].url, servers[1].video.uuid)
const reason = 'it is a really bad comment'
await reportAbuse({ url: servers[0].url, token: servers[0].accessToken, commentId: comment.id, reason })
await waitJobs(servers)
})
it('Should have 2 comment abuses on server 1 and 1 on server 2', async function () {
const commentServer2 = await getComment(servers[0].url, servers[1].video.id)
const res1 = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'comment' })
expect(res1.body.total).to.equal(2)
expect(res1.body.data.length).to.equal(2)
const abuse: Abuse = res1.body.data[0]
expect(abuse.reason).to.equal('it is a bad comment')
expect(abuse.countReportsForReporter).to.equal(6)
expect(abuse.countReportsForReportee).to.equal(5)
const abuse2: Abuse = res1.body.data[1]
expect(abuse2.reason).to.equal('it is a really bad comment')
expect(abuse2.reporterAccount.name).to.equal('root')
expect(abuse2.reporterAccount.host).to.equal(servers[0].host)
expect(abuse2.video).to.be.null
expect(abuse2.comment.deleted).to.be.false
expect(abuse2.comment.id).to.equal(commentServer2.id)
expect(abuse2.comment.text).to.equal(commentServer2.text)
expect(abuse2.comment.video.name).to.equal('server 2')
expect(abuse2.comment.video.uuid).to.equal(servers[1].video.uuid)
expect(abuse2.state.id).to.equal(AbuseState.PENDING)
expect(abuse2.state.label).to.equal('Pending')
expect(abuse2.moderationComment).to.be.null
expect(abuse2.countReportsForReporter).to.equal(6)
expect(abuse2.countReportsForReportee).to.equal(2)
const res2 = await getAbusesList({ url: servers[1].url, token: servers[1].accessToken, filter: 'comment' })
expect(res2.body.total).to.equal(1)
expect(res2.body.data.length).to.equal(1)
abuseServer2 = res2.body.data[0]
expect(abuseServer2.reason).to.equal('it is a really bad comment')
expect(abuseServer2.reporterAccount.name).to.equal('root')
expect(abuseServer2.reporterAccount.host).to.equal(servers[0].host)
expect(abuseServer2.state.id).to.equal(AbuseState.PENDING)
expect(abuseServer2.state.label).to.equal('Pending')
expect(abuseServer2.moderationComment).to.be.null
expect(abuseServer2.countReportsForReporter).to.equal(1)
expect(abuseServer2.countReportsForReportee).to.equal(1)
})
it('Should keep the comment abuse when deleting the comment', async function () {
this.timeout(10000)
const commentServer2 = await getComment(servers[0].url, servers[1].video.id)
await deleteVideoComment(servers[0].url, servers[0].accessToken, servers[1].video.uuid, commentServer2.id)
await waitJobs(servers)
const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'comment' })
expect(res.body.total).to.equal(2)
expect(res.body.data).to.have.lengthOf(2)
const abuse = (res.body.data as Abuse[]).find(a => a.comment?.id === commentServer2.id)
expect(abuse).to.not.be.undefined
expect(abuse.comment.text).to.be.empty
expect(abuse.comment.video.name).to.equal('server 2')
expect(abuse.comment.deleted).to.be.true
})
it('Should delete the comment abuse', async function () {
this.timeout(10000)
await deleteAbuse(servers[1].url, servers[1].accessToken, abuseServer2.id)
await waitJobs(servers)
{
const res = await getAbusesList({ url: servers[1].url, token: servers[1].accessToken, filter: 'comment' })
expect(res.body.total).to.equal(0)
expect(res.body.data.length).to.equal(0)
}
{
const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'comment' })
expect(res.body.total).to.equal(2)
}
})
it('Should list and filter video abuses', async function () {
{
const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'comment', searchReportee: 'foo' })
expect(res.body.total).to.equal(0)
}
{
const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'comment', searchReportee: 'ot' })
expect(res.body.total).to.equal(2)
}
{
const baseParams = { url: servers[0].url, token: servers[0].accessToken, filter: 'comment' as AbuseFilter, start: 1, count: 1 }
const res1 = await getAbusesList(immutableAssign(baseParams, { sort: 'createdAt' }))
expect(res1.body.data).to.have.lengthOf(1)
expect(res1.body.data[0].comment.text).to.be.empty
const res2 = await getAbusesList(immutableAssign(baseParams, { sort: '-createdAt' }))
expect(res2.body.data).to.have.lengthOf(1)
expect(res2.body.data[0].comment.text).to.equal('comment server 1')
}
})
})
describe('Account abuses', function () {
async function getAccountFromServer (url: string, name: string, server: ServerInfo) {
const res = await getAccount(url, name + '@' + server.host)
return res.body as Account
}
before(async function () {
this.timeout(50000)
await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: 'user_1', password: 'donald' })
const token = await generateUserAccessToken(servers[1], 'user_2')
await uploadVideo(servers[1].url, token, { name: 'super video' })
await waitJobs(servers)
})
it('Should report abuse on an account', async function () {
this.timeout(15000)
const account = await getAccountFromServer(servers[0].url, 'user_1', servers[0])
const reason = 'it is a bad account'
await reportAbuse({ url: servers[0].url, token: servers[0].accessToken, accountId: account.id, reason })
await waitJobs(servers)
})
it('Should have 1 account abuse on server 1 and 0 on server 2', async function () {
{
const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'account' })
expect(res.body.total).to.equal(1)
expect(res.body.data).to.have.lengthOf(1)
const abuse: Abuse = res.body.data[0]
expect(abuse.reason).to.equal('it is a bad account')
expect(abuse.reporterAccount.name).to.equal('root')
expect(abuse.reporterAccount.host).to.equal(servers[0].host)
expect(abuse.video).to.be.null
expect(abuse.comment).to.be.null
expect(abuse.flaggedAccount.name).to.equal('user_1')
expect(abuse.flaggedAccount.host).to.equal(servers[0].host)
}
{
const res = await getAbusesList({ url: servers[1].url, token: servers[1].accessToken, filter: 'comment' })
expect(res.body.total).to.equal(0)
expect(res.body.data.length).to.equal(0)
}
})
it('Should report abuse on a remote account', async function () {
this.timeout(10000)
const account = await getAccountFromServer(servers[0].url, 'user_2', servers[1])
const reason = 'it is a really bad account'
await reportAbuse({ url: servers[0].url, token: servers[0].accessToken, accountId: account.id, reason })
await waitJobs(servers)
})
it('Should have 2 comment abuses on server 1 and 1 on server 2', async function () {
const res1 = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'account' })
expect(res1.body.total).to.equal(2)
expect(res1.body.data.length).to.equal(2)
const abuse: Abuse = res1.body.data[0]
expect(abuse.reason).to.equal('it is a bad account')
const abuse2: Abuse = res1.body.data[1]
expect(abuse2.reason).to.equal('it is a really bad account')
expect(abuse2.reporterAccount.name).to.equal('root')
expect(abuse2.reporterAccount.host).to.equal(servers[0].host)
expect(abuse2.video).to.be.null
expect(abuse2.comment).to.be.null
expect(abuse2.state.id).to.equal(AbuseState.PENDING)
expect(abuse2.state.label).to.equal('Pending')
expect(abuse2.moderationComment).to.be.null
const res2 = await getAbusesList({ url: servers[1].url, token: servers[1].accessToken, filter: 'account' })
expect(res2.body.total).to.equal(1)
expect(res2.body.data.length).to.equal(1)
abuseServer2 = res2.body.data[0]
expect(abuseServer2.reason).to.equal('it is a really bad account')
expect(abuseServer2.reporterAccount.name).to.equal('root')
expect(abuseServer2.reporterAccount.host).to.equal(servers[0].host)
expect(abuseServer2.state.id).to.equal(AbuseState.PENDING)
expect(abuseServer2.state.label).to.equal('Pending')
expect(abuseServer2.moderationComment).to.be.null
})
it('Should keep the account abuse when deleting the account', async function () {
this.timeout(10000)
const account = await getAccountFromServer(servers[1].url, 'user_2', servers[1])
await removeUser(servers[1].url, account.userId, servers[1].accessToken)
await waitJobs(servers)
const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'account' })
expect(res.body.total).to.equal(2)
expect(res.body.data).to.have.lengthOf(2)
const abuse = (res.body.data as Abuse[]).find(a => a.reason === 'it is a really bad account')
expect(abuse).to.not.be.undefined
})
it('Should delete the account abuse', async function () {
this.timeout(10000)
await deleteAbuse(servers[1].url, servers[1].accessToken, abuseServer2.id)
await waitJobs(servers)
{
const res = await getAbusesList({ url: servers[1].url, token: servers[1].accessToken, filter: 'account' })
expect(res.body.total).to.equal(0)
expect(res.body.data.length).to.equal(0)
}
{
const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'account' })
expect(res.body.total).to.equal(2)
abuseServer1 = res.body.data[0]
}
})
})
describe('Common actions on abuses', function () {
it('Should update the state of an abuse', async function () {
const body = { state: AbuseState.REJECTED }
await updateAbuse(servers[0].url, servers[0].accessToken, abuseServer1.id, body)
const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, id: abuseServer1.id })
expect(res.body.data[0].state.id).to.equal(AbuseState.REJECTED)
})
it('Should add a moderation comment', async function () {
const body = { state: AbuseState.ACCEPTED, moderationComment: 'It is valid' }
await updateAbuse(servers[0].url, servers[0].accessToken, abuseServer1.id, body)
const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, id: abuseServer1.id })
expect(res.body.data[0].state.id).to.equal(AbuseState.ACCEPTED)
expect(res.body.data[0].moderationComment).to.equal('It is valid')
})
})
after(async function () { after(async function () {
await cleanupTests(servers) await cleanupTests(servers)

View File

@ -3,10 +3,16 @@
import 'mocha' import 'mocha'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { import {
addVideoCommentThread,
addVideoToBlacklist, addVideoToBlacklist,
cleanupTests, cleanupTests,
createUser,
follow, follow,
generateUserAccessToken,
getAccount,
getCustomConfig, getCustomConfig,
getVideoCommentThreads,
getVideoIdFromUUID,
immutableAssign, immutableAssign,
MockInstancesIndex, MockInstancesIndex,
registerUser, registerUser,
@ -23,7 +29,9 @@ import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
import { import {
checkAutoInstanceFollowing, checkAutoInstanceFollowing,
CheckerBaseParams, CheckerBaseParams,
checkNewAccountAbuseForModerators,
checkNewBlacklistOnMyVideo, checkNewBlacklistOnMyVideo,
checkNewCommentAbuseForModerators,
checkNewInstanceFollower, checkNewInstanceFollower,
checkNewVideoAbuseForModerators, checkNewVideoAbuseForModerators,
checkNewVideoFromSubscription, checkNewVideoFromSubscription,
@ -91,11 +99,74 @@ describe('Test moderation notifications', function () {
await waitJobs(servers) await waitJobs(servers)
await reportAbuse({ url: servers[1].url, token: servers[1].accessToken, videoId: video.id, reason: 'super reason' }) const videoId = await getVideoIdFromUUID(servers[1].url, video.uuid)
await reportAbuse({ url: servers[1].url, token: servers[1].accessToken, videoId, reason: 'super reason' })
await waitJobs(servers) await waitJobs(servers)
await checkNewVideoAbuseForModerators(baseParams, video.uuid, name, 'presence') await checkNewVideoAbuseForModerators(baseParams, video.uuid, name, 'presence')
}) })
it('Should send a notification to moderators on local comment abuse', async function () {
this.timeout(10000)
const name = 'video for abuse ' + uuidv4()
const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name })
const video = resVideo.body.video
const resComment = await addVideoCommentThread(servers[0].url, userAccessToken, video.id, 'comment abuse ' + uuidv4())
const comment = resComment.body.comment
await reportAbuse({ url: servers[0].url, token: servers[0].accessToken, commentId: comment.id, reason: 'super reason' })
await waitJobs(servers)
await checkNewCommentAbuseForModerators(baseParams, video.uuid, name, 'presence')
})
it('Should send a notification to moderators on remote comment abuse', async function () {
this.timeout(10000)
const name = 'video for abuse ' + uuidv4()
const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name })
const video = resVideo.body.video
await addVideoCommentThread(servers[0].url, userAccessToken, video.id, 'comment abuse ' + uuidv4())
await waitJobs(servers)
const resComments = await getVideoCommentThreads(servers[1].url, video.uuid, 0, 5)
const commentId = resComments.body.data[0].id
await reportAbuse({ url: servers[1].url, token: servers[1].accessToken, commentId, reason: 'super reason' })
await waitJobs(servers)
await checkNewCommentAbuseForModerators(baseParams, video.uuid, name, 'presence')
})
it('Should send a notification to moderators on local account abuse', async function () {
this.timeout(10000)
const username = 'user' + new Date().getTime()
const resUser = await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username, password: 'donald' })
const accountId = resUser.body.user.account.id
await reportAbuse({ url: servers[0].url, token: servers[0].accessToken, accountId, reason: 'super reason' })
await waitJobs(servers)
await checkNewAccountAbuseForModerators(baseParams, username, 'presence')
})
it('Should send a notification to moderators on remote account abuse', async function () {
this.timeout(10000)
const username = 'user' + new Date().getTime()
const tmpToken = await generateUserAccessToken(servers[0], username)
await uploadVideo(servers[0].url, tmpToken, { name: 'super video' })
await waitJobs(servers)
const resAccount = await getAccount(servers[1].url, username + '@' + servers[0].host)
await reportAbuse({ url: servers[1].url, token: servers[1].accessToken, accountId: resAccount.body.id, reason: 'super reason' })
await waitJobs(servers)
await checkNewAccountAbuseForModerators(baseParams, username, 'presence')
})
}) })
describe('Video blacklist on my video', function () { describe('Video blacklist on my video', function () {

View File

@ -180,7 +180,7 @@ describe('Test emails', function () {
}) })
}) })
describe('When creating a video abuse', function () { describe('When creating an abuse', function () {
it('Should send the notification email', async function () { it('Should send the notification email', async function () {
this.timeout(10000) this.timeout(10000)

View File

@ -53,7 +53,7 @@ export module UserNotificationIncludes {
Pick<VideoCommentAbuseModel, 'id'> & Pick<VideoCommentAbuseModel, 'id'> &
PickWith<VideoCommentAbuseModel, 'VideoComment', PickWith<VideoCommentAbuseModel, 'VideoComment',
Pick<VideoCommentModel, 'id' | 'originCommentId' | 'getThreadId'> & Pick<VideoCommentModel, 'id' | 'originCommentId' | 'getThreadId'> &
PickWith<VideoCommentModel, 'Video', Pick<VideoModel, 'uuid'>>> PickWith<VideoCommentModel, 'Video', Pick<VideoModel, 'id' | 'name' | 'uuid'>>>
export type AbuseInclude = export type AbuseInclude =
Pick<AbuseModel, 'id'> & Pick<AbuseModel, 'id'> &

View File

@ -91,7 +91,6 @@ declare module 'express' {
accountVideoRate?: MAccountVideoRateAccountVideo accountVideoRate?: MAccountVideoRateAccountVideo
videoComment?: MComment
videoCommentFull?: MCommentOwnerVideoReply videoCommentFull?: MCommentOwnerVideoReply
videoCommentThread?: MComment videoCommentThread?: MComment

View File

@ -57,10 +57,15 @@ function reportAbuse (options: {
function getAbusesList (options: { function getAbusesList (options: {
url: string url: string
token: string token: string
start?: number
count?: number
sort?: string
id?: number id?: number
predefinedReason?: AbusePredefinedReasonsString predefinedReason?: AbusePredefinedReasonsString
search?: string search?: string
filter?: AbuseFilter, filter?: AbuseFilter
state?: AbuseState state?: AbuseState
videoIs?: AbuseVideoIs videoIs?: AbuseVideoIs
searchReporter?: string searchReporter?: string
@ -71,6 +76,9 @@ function getAbusesList (options: {
const { const {
url, url,
token, token,
start,
count,
sort,
id, id,
predefinedReason, predefinedReason,
search, search,
@ -85,13 +93,15 @@ function getAbusesList (options: {
const path = '/api/v1/abuses' const path = '/api/v1/abuses'
const query = { const query = {
sort: 'createdAt',
id, id,
predefinedReason, predefinedReason,
search, search,
state, state,
filter, filter,
videoIs, videoIs,
start,
count,
sort: sort || 'createdAt',
searchReporter, searchReporter,
searchReportee, searchReportee,
searchVideo, searchVideo,

View File

@ -37,8 +37,8 @@ interface ServerInfo {
video?: { video?: {
id: number id: number
uuid: string uuid: string
name: string name?: string
account: { account?: {
name: string name: string
} }
} }

View File

@ -139,13 +139,17 @@ async function checkNotification (
} }
function checkVideo (video: any, videoName?: string, videoUUID?: string) { function checkVideo (video: any, videoName?: string, videoUUID?: string) {
if (videoName) {
expect(video.name).to.be.a('string') expect(video.name).to.be.a('string')
expect(video.name).to.not.be.empty expect(video.name).to.not.be.empty
if (videoName) expect(video.name).to.equal(videoName) expect(video.name).to.equal(videoName)
}
if (videoUUID) {
expect(video.uuid).to.be.a('string') expect(video.uuid).to.be.a('string')
expect(video.uuid).to.not.be.empty expect(video.uuid).to.not.be.empty
if (videoUUID) expect(video.uuid).to.equal(videoUUID) expect(video.uuid).to.equal(videoUUID)
}
expect(video.id).to.be.a('number') expect(video.id).to.be.a('number')
} }
@ -436,7 +440,7 @@ async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string,
} }
async function checkNewVideoAbuseForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) { async function checkNewVideoAbuseForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
const notificationType = UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS const notificationType = UserNotificationType.NEW_ABUSE_FOR_MODERATORS
function notificationChecker (notification: UserNotification, type: CheckerType) { function notificationChecker (notification: UserNotification, type: CheckerType) {
if (type === 'presence') { if (type === 'presence') {
@ -460,6 +464,56 @@ async function checkNewVideoAbuseForModerators (base: CheckerBaseParams, videoUU
await checkNotification(base, notificationChecker, emailNotificationFinder, type) await checkNotification(base, notificationChecker, emailNotificationFinder, type)
} }
async function checkNewCommentAbuseForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
const notificationType = UserNotificationType.NEW_ABUSE_FOR_MODERATORS
function notificationChecker (notification: UserNotification, type: CheckerType) {
if (type === '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)
} else {
expect(notification).to.satisfy((n: UserNotification) => {
return n === undefined || n.abuse === undefined || n.abuse.comment.video.uuid !== videoUUID
})
}
}
function emailNotificationFinder (email: object) {
const text = email['text']
return text.indexOf(videoUUID) !== -1 && text.indexOf('abuse') !== -1
}
await checkNotification(base, notificationChecker, emailNotificationFinder, type)
}
async function checkNewAccountAbuseForModerators (base: CheckerBaseParams, displayName: string, type: CheckerType) {
const notificationType = UserNotificationType.NEW_ABUSE_FOR_MODERATORS
function notificationChecker (notification: UserNotification, type: CheckerType) {
if (type === 'presence') {
expect(notification).to.not.be.undefined
expect(notification.type).to.equal(notificationType)
expect(notification.abuse.id).to.be.a('number')
expect(notification.abuse.account.displayName).to.equal(displayName)
} else {
expect(notification).to.satisfy((n: UserNotification) => {
return n === undefined || n.abuse === undefined || n.abuse.account.displayName !== displayName
})
}
}
function emailNotificationFinder (email: object) {
const text = email['text']
return text.indexOf(displayName) !== -1 && text.indexOf('abuse') !== -1
}
await checkNotification(base, notificationChecker, emailNotificationFinder, type)
}
async function checkVideoAutoBlacklistForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) { async function checkVideoAutoBlacklistForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
const notificationType = UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS const notificationType = UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS
@ -541,6 +595,9 @@ async function prepareNotificationsTest (serversCount = 3) {
smtp: { smtp: {
hostname: 'localhost', hostname: 'localhost',
port port
},
signup: {
limit: 20
} }
} }
const servers = await flushAndRunMultipleServers(serversCount, overrideConfig) const servers = await flushAndRunMultipleServers(serversCount, overrideConfig)
@ -623,5 +680,7 @@ export {
markAsReadNotifications, markAsReadNotifications,
getLastNotification, getLastNotification,
checkNewInstanceFollower, checkNewInstanceFollower,
prepareNotificationsTest prepareNotificationsTest,
checkNewCommentAbuseForModerators,
checkNewAccountAbuseForModerators
} }

View File

@ -3,7 +3,7 @@ import { FollowState } from '../actors'
export enum UserNotificationType { export enum UserNotificationType {
NEW_VIDEO_FROM_SUBSCRIPTION = 1, NEW_VIDEO_FROM_SUBSCRIPTION = 1,
NEW_COMMENT_ON_MY_VIDEO = 2, NEW_COMMENT_ON_MY_VIDEO = 2,
NEW_VIDEO_ABUSE_FOR_MODERATORS = 3, NEW_ABUSE_FOR_MODERATORS = 3,
BLACKLIST_ON_MY_VIDEO = 4, BLACKLIST_ON_MY_VIDEO = 4,
UNBLACKLIST_ON_MY_VIDEO = 5, UNBLACKLIST_ON_MY_VIDEO = 5,

View File

@ -106,9 +106,9 @@ tags:
Managing plugins installed from a local path or from NPM, or search for new ones. Managing plugins installed from a local path or from NPM, or search for new ones.
externalDocs: externalDocs:
url: https://docs.joinpeertube.org/#/api-plugins url: https://docs.joinpeertube.org/#/api-plugins
- name: Video Abuses - name: Abuses
description: | description: |
Video abuses deal with reports of local or remote videos alike. Abuses deal with reports of local or remote videos/comments/accounts alike.
- name: Video - name: Video
description: | description: |
Operations dealing with listing, uploading, fetching or modifying videos. Operations dealing with listing, uploading, fetching or modifying videos.
@ -166,7 +166,7 @@ x-tagGroups:
- Search - Search
- name: Moderation - name: Moderation
tags: tags:
- Video Abuses - Abuses
- Video Blocks - Video Blocks
- Account Blocks - Account Blocks
- Server Blocks - Server Blocks
@ -1474,13 +1474,13 @@ paths:
/videos/abuse: /videos/abuse:
get: get:
deprecated: true deprecated: true
summary: List video abuses summary: List abuses
security: security:
- OAuth2: - OAuth2:
- admin - admin
- moderator - moderator
tags: tags:
- Video Abuses - Abuses
parameters: parameters:
- name: id - name: id
in: query in: query
@ -1508,7 +1508,7 @@ paths:
type: string type: string
- name: state - name: state
in: query in: query
description: 'The video playlist privacy (Pending = `1`, Rejected = `2`, Accepted = `3`)' description: 'The abuse state (Pending = `1`, Rejected = `2`, Accepted = `3`)'
schema: schema:
type: integer type: integer
enum: enum:
@ -1554,7 +1554,7 @@ paths:
security: security:
- OAuth2: [] - OAuth2: []
tags: tags:
- Video Abuses - Abuses
- Videos - Videos
parameters: parameters:
- $ref: '#/components/parameters/idOrUUID' - $ref: '#/components/parameters/idOrUUID'
@ -1607,7 +1607,7 @@ paths:
- admin - admin
- moderator - moderator
tags: tags:
- Video Abuses - Abuses
parameters: parameters:
- $ref: '#/components/parameters/idOrUUID' - $ref: '#/components/parameters/idOrUUID'
- $ref: '#/components/parameters/abuseId' - $ref: '#/components/parameters/abuseId'
@ -1626,11 +1626,11 @@ paths:
'204': '204':
description: successful operation description: successful operation
'404': '404':
description: video abuse not found description: abuse not found
delete: delete:
deprecated: true deprecated: true
tags: tags:
- Video Abuses - Abuses
summary: Delete an abuse summary: Delete an abuse
security: security:
- OAuth2: - OAuth2:
@ -3320,7 +3320,7 @@ components:
name: abuseId name: abuseId
in: path in: path
required: true required: true
description: Video abuse id description: Abuse id
schema: schema:
type: integer type: integer
captionLanguage: captionLanguage:
@ -5098,7 +5098,7 @@ components:
- `2` NEW_COMMENT_ON_MY_VIDEO - `2` NEW_COMMENT_ON_MY_VIDEO
- `3` NEW_VIDEO_ABUSE_FOR_MODERATORS - `3` NEW_ABUSE_FOR_MODERATORS
- `4` BLACKLIST_ON_MY_VIDEO - `4` BLACKLIST_ON_MY_VIDEO