mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
fix(api): correct report targets and ticket handling (#2504)
This commit is contained in:
@@ -24,6 +24,7 @@ import {ReportBannedError} from '@fluxer/errors/src/domains/moderation/ReportBan
|
|||||||
import {UnknownReportError} from '@fluxer/errors/src/domains/moderation/UnknownReportError';
|
import {UnknownReportError} from '@fluxer/errors/src/domains/moderation/UnknownReportError';
|
||||||
import {UnknownUserError} from '@fluxer/errors/src/domains/user/UnknownUserError';
|
import {UnknownUserError} from '@fluxer/errors/src/domains/user/UnknownUserError';
|
||||||
import type {DsaReportRequest} from '@fluxer/schema/src/domains/report/ReportSchemas';
|
import type {DsaReportRequest} from '@fluxer/schema/src/domains/report/ReportSchemas';
|
||||||
|
import {SnowflakeType} from '@fluxer/schema/src/primitives/SchemaPrimitives';
|
||||||
import {snowflakeToDate} from '@fluxer/snowflake/src/Snowflake';
|
import {snowflakeToDate} from '@fluxer/snowflake/src/Snowflake';
|
||||||
import type {IEmailService} from '@pkgs/email/src/IEmailService';
|
import type {IEmailService} from '@pkgs/email/src/IEmailService';
|
||||||
import type {IRateLimitService} from '@pkgs/rate_limit/src/IRateLimitService';
|
import type {IRateLimitService} from '@pkgs/rate_limit/src/IRateLimitService';
|
||||||
@@ -170,7 +171,6 @@ export class ReportService {
|
|||||||
reported_guild_invite_code: null,
|
reported_guild_invite_code: null,
|
||||||
...contentWarningSnapshot,
|
...contentWarningSnapshot,
|
||||||
};
|
};
|
||||||
await this.consumeMessageReportRateLimits({reporter, channel, message});
|
|
||||||
let duplicateReservationCreated = false;
|
let duplicateReservationCreated = false;
|
||||||
if (reporter.id) {
|
if (reporter.id) {
|
||||||
duplicateReservationCreated = await this.reportRepository.reserveMessageReportByReporter({
|
duplicateReservationCreated = await this.reportRepository.reserveMessageReportByReporter({
|
||||||
@@ -185,6 +185,7 @@ export class ReportService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
await this.consumeMessageReportRateLimits({reporter, channel, message});
|
||||||
const report = await this.reportRepository.createReport(reportData);
|
const report = await this.reportRepository.createReport(reportData);
|
||||||
if (this.reportSearchService && 'indexReport' in this.reportSearchService) {
|
if (this.reportSearchService && 'indexReport' in this.reportSearchService) {
|
||||||
await this.reportSearchService.indexReport(report).catch((error) => {
|
await this.reportSearchService.indexReport(report).catch((error) => {
|
||||||
@@ -221,6 +222,9 @@ export class ReportService {
|
|||||||
}
|
}
|
||||||
const reportId = createReportID(await this.snowflakeService.generate());
|
const reportId = createReportID(await this.snowflakeService.generate());
|
||||||
const guild = guildId ? await this.guildRepository.findUnique(guildId) : null;
|
const guild = guildId ? await this.guildRepository.findUnique(guildId) : null;
|
||||||
|
if (guildId && !guild) {
|
||||||
|
throw new UnknownGuildError();
|
||||||
|
}
|
||||||
const contentWarningSnapshot = await this.buildContentWarningSnapshot(guild, null);
|
const contentWarningSnapshot = await this.buildContentWarningSnapshot(guild, null);
|
||||||
const reportData: IARSubmissionRow = {
|
const reportData: IARSubmissionRow = {
|
||||||
report_id: reportId,
|
report_id: reportId,
|
||||||
@@ -372,7 +376,7 @@ export class ReportService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async createDsaReport(report: DsaReportRequest): Promise<IARSubmission> {
|
async createDsaReport(report: DsaReportRequest): Promise<IARSubmission> {
|
||||||
const ticket = await this.consumeDsaTicket(report.ticket);
|
const ticket = await this.readDsaTicket(report.ticket);
|
||||||
const reporterMeta: ReporterMetadata = {
|
const reporterMeta: ReporterMetadata = {
|
||||||
id: null,
|
id: null,
|
||||||
email: ticket.email_lower,
|
email: ticket.email_lower,
|
||||||
@@ -385,6 +389,7 @@ export class ReportService {
|
|||||||
const reportId = createReportID(await this.snowflakeService.generate());
|
const reportId = createReportID(await this.snowflakeService.generate());
|
||||||
const reportRow = await this.buildDsaReportRow(reportId, report, reporterMeta);
|
const reportRow = await this.buildDsaReportRow(reportId, report, reporterMeta);
|
||||||
await this.ensureReportRateLimit(this.createReportRateLimitIdentifier(reporterKey), REPORT_RATE_LIMIT_MAX, true);
|
await this.ensureReportRateLimit(this.createReportRateLimitIdentifier(reporterKey), REPORT_RATE_LIMIT_MAX, true);
|
||||||
|
await this.reportRepository.deleteDsaTicket(report.ticket);
|
||||||
const createdReport = await this.reportRepository.createReport(reportRow);
|
const createdReport = await this.reportRepository.createReport(reportRow);
|
||||||
if (this.reportSearchService && 'indexReport' in this.reportSearchService) {
|
if (this.reportSearchService && 'indexReport' in this.reportSearchService) {
|
||||||
await this.reportSearchService.indexReport(createdReport).catch((error) => {
|
await this.reportSearchService.indexReport(createdReport).catch((error) => {
|
||||||
@@ -706,12 +711,11 @@ export class ReportService {
|
|||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async consumeDsaTicket(ticket: string): Promise<DSAReportTicketRow> {
|
private async readDsaTicket(ticket: string): Promise<DSAReportTicketRow> {
|
||||||
const ticketRow = await this.reportRepository.getDsaTicket(ticket);
|
const ticketRow = await this.reportRepository.getDsaTicket(ticket);
|
||||||
if (!ticketRow || ticketRow.expires_at.getTime() < Date.now()) {
|
if (!ticketRow || ticketRow.expires_at.getTime() < Date.now()) {
|
||||||
throw new InvalidDsaTicketError();
|
throw new InvalidDsaTicketError();
|
||||||
}
|
}
|
||||||
await this.reportRepository.deleteDsaTicket(ticket);
|
|
||||||
return ticketRow;
|
return ticketRow;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -762,11 +766,14 @@ export class ReportService {
|
|||||||
if (segments.length < 4 || segments[0] !== 'channels') {
|
if (segments.length < 4 || segments[0] !== 'channels') {
|
||||||
throw new UnknownMessageError();
|
throw new UnknownMessageError();
|
||||||
}
|
}
|
||||||
const channelIdSegment = segments[2];
|
const channelId = SnowflakeType.safeParse(segments[2]);
|
||||||
const messageIdSegment = segments[3];
|
const messageId = SnowflakeType.safeParse(segments[3]);
|
||||||
|
if (!channelId.success || !messageId.success) {
|
||||||
|
throw new UnknownMessageError();
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
channelId: createChannelID(BigInt(channelIdSegment)),
|
channelId: createChannelID(channelId.data),
|
||||||
messageId: createMessageID(BigInt(messageIdSegment)),
|
messageId: createMessageID(messageId.data),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import {
|
|||||||
import {ensureSessionStarted} from '../../message/tests/MessageTestUtils';
|
import {ensureSessionStarted} from '../../message/tests/MessageTestUtils';
|
||||||
import {ReadStateRepository} from '../../read_state/ReadStateRepository';
|
import {ReadStateRepository} from '../../read_state/ReadStateRepository';
|
||||||
import {type ApiTestHarness, createApiTestHarness} from '../../test/ApiTestHarness';
|
import {type ApiTestHarness, createApiTestHarness} from '../../test/ApiTestHarness';
|
||||||
import {HTTP_STATUS} from '../../test/TestConstants';
|
import {HTTP_STATUS, TEST_IDS} from '../../test/TestConstants';
|
||||||
import {createBuilder, createBuilderWithoutAuth} from '../../test/TestRequestBuilder';
|
import {createBuilder, createBuilderWithoutAuth} from '../../test/TestRequestBuilder';
|
||||||
import {ReportRepository} from '../ReportRepository';
|
import {ReportRepository} from '../ReportRepository';
|
||||||
|
|
||||||
@@ -129,6 +129,19 @@ describe('Content Reporting', () => {
|
|||||||
.execute();
|
.execute();
|
||||||
expect(result.report_id).toBeTruthy();
|
expect(result.report_id).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
test('should reject a user report naming an unknown guild', async () => {
|
||||||
|
const reporter = await createTestAccount(harness);
|
||||||
|
const targetUser = await createTestAccount(harness);
|
||||||
|
await createBuilder(harness, reporter.token)
|
||||||
|
.post('/reports/user')
|
||||||
|
.body({
|
||||||
|
user_id: targetUser.userId,
|
||||||
|
category: 'harassment',
|
||||||
|
guild_id: TEST_IDS.NONEXISTENT_GUILD,
|
||||||
|
})
|
||||||
|
.expect(HTTP_STATUS.NOT_FOUND, APIErrorCodes.UNKNOWN_GUILD)
|
||||||
|
.execute();
|
||||||
|
});
|
||||||
test('should reject report with invalid category', async () => {
|
test('should reject report with invalid category', async () => {
|
||||||
const reporter = await createTestAccount(harness);
|
const reporter = await createTestAccount(harness);
|
||||||
const targetUser = await createTestAccount(harness);
|
const targetUser = await createTestAccount(harness);
|
||||||
@@ -440,6 +453,45 @@ describe('Content Reporting', () => {
|
|||||||
.execute();
|
.execute();
|
||||||
expect(await countReports()).toBe(3);
|
expect(await countReports()).toBe(3);
|
||||||
});
|
});
|
||||||
|
test('a duplicate message report does not spend the reporter allowance', async () => {
|
||||||
|
const {owner, members, guild} = await setupTestGuildWithMembers(harness, 1);
|
||||||
|
const targetUser = members[0];
|
||||||
|
const channel = await getChannel(harness, owner.token, guild.system_channel_id!);
|
||||||
|
const [firstMessage, secondMessage] = await Promise.all([
|
||||||
|
sendChannelMessage(harness, targetUser.token, channel.id, 'Duplicate allowance target'),
|
||||||
|
sendChannelMessage(harness, targetUser.token, channel.id, 'Duplicate allowance second target'),
|
||||||
|
]);
|
||||||
|
await createBuilder<ReportResponse>(harness, owner.token)
|
||||||
|
.post('/reports/message')
|
||||||
|
.body({
|
||||||
|
channel_id: channel.id,
|
||||||
|
message_id: firstMessage.id,
|
||||||
|
category: 'harassment',
|
||||||
|
})
|
||||||
|
.expect(HTTP_STATUS.OK)
|
||||||
|
.execute();
|
||||||
|
for (let attempt = 0; attempt < 5; attempt += 1) {
|
||||||
|
await createBuilder(harness, owner.token)
|
||||||
|
.post('/reports/message')
|
||||||
|
.body({
|
||||||
|
channel_id: channel.id,
|
||||||
|
message_id: firstMessage.id,
|
||||||
|
category: 'spam',
|
||||||
|
})
|
||||||
|
.expect(HTTP_STATUS.CONFLICT, APIErrorCodes.CONFLICT)
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
await createBuilder<ReportResponse>(harness, owner.token)
|
||||||
|
.post('/reports/message')
|
||||||
|
.body({
|
||||||
|
channel_id: channel.id,
|
||||||
|
message_id: secondMessage.id,
|
||||||
|
category: 'harassment',
|
||||||
|
})
|
||||||
|
.expect(HTTP_STATUS.OK)
|
||||||
|
.execute();
|
||||||
|
expect(await countReports()).toBe(2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
describe('Report Guild', () => {
|
describe('Report Guild', () => {
|
||||||
async function setupMemberAndGuild(): Promise<{
|
async function setupMemberAndGuild(): Promise<{
|
||||||
@@ -817,6 +869,47 @@ describe('Content Reporting', () => {
|
|||||||
expect(result.report_id).toBeTruthy();
|
expect(result.report_id).toBeTruthy();
|
||||||
expect(result.status).toBe('pending');
|
expect(result.status).toBe('pending');
|
||||||
});
|
});
|
||||||
|
test('a DSA notice that fails target resolution keeps its ticket', async () => {
|
||||||
|
await clearTestEmails(harness);
|
||||||
|
const email = createUniqueEmail('dsa-reporter');
|
||||||
|
const targetUser = await createTestAccount(harness);
|
||||||
|
await createBuilderWithoutAuth(harness).post('/reports/dsa/email/send').body({email}).execute();
|
||||||
|
const emails = await listTestEmails(harness);
|
||||||
|
const dsaEmail = findLastTestEmail(emails, 'dsa_report_verification');
|
||||||
|
const code = dsaEmail!.metadata.code;
|
||||||
|
const verifyResponse = await createBuilder<{
|
||||||
|
ticket: string;
|
||||||
|
}>(harness, '')
|
||||||
|
.post('/reports/dsa/email/verify')
|
||||||
|
.body({email, code})
|
||||||
|
.expect(HTTP_STATUS.OK)
|
||||||
|
.execute();
|
||||||
|
await createBuilderWithoutAuth(harness)
|
||||||
|
.post('/reports/dsa')
|
||||||
|
.body({
|
||||||
|
ticket: verifyResponse.ticket,
|
||||||
|
report_type: 'user',
|
||||||
|
category: 'harassment',
|
||||||
|
user_id: TEST_IDS.NONEXISTENT_USER,
|
||||||
|
reporter_full_legal_name: 'John Doe',
|
||||||
|
reporter_country_of_residence: 'DE',
|
||||||
|
})
|
||||||
|
.expect(HTTP_STATUS.NOT_FOUND, APIErrorCodes.UNKNOWN_USER)
|
||||||
|
.execute();
|
||||||
|
const result = await createBuilder<ReportResponse>(harness, '')
|
||||||
|
.post('/reports/dsa')
|
||||||
|
.body({
|
||||||
|
ticket: verifyResponse.ticket,
|
||||||
|
report_type: 'user',
|
||||||
|
category: 'harassment',
|
||||||
|
user_id: targetUser.userId,
|
||||||
|
reporter_full_legal_name: 'John Doe',
|
||||||
|
reporter_country_of_residence: 'DE',
|
||||||
|
})
|
||||||
|
.expect(HTTP_STATUS.OK)
|
||||||
|
.execute();
|
||||||
|
expect(result.report_id).toBeTruthy();
|
||||||
|
});
|
||||||
test('should create DSA guild report with valid ticket', async () => {
|
test('should create DSA guild report with valid ticket', async () => {
|
||||||
await clearTestEmails(harness);
|
await clearTestEmails(harness);
|
||||||
const email = createUniqueEmail('dsa-reporter');
|
const email = createUniqueEmail('dsa-reporter');
|
||||||
@@ -848,6 +941,60 @@ describe('Content Reporting', () => {
|
|||||||
expect(result.report_id).toBeTruthy();
|
expect(result.report_id).toBeTruthy();
|
||||||
expect(result.status).toBe('pending');
|
expect(result.status).toBe('pending');
|
||||||
});
|
});
|
||||||
|
test('should reject DSA message report with a non-numeric message link segment', async () => {
|
||||||
|
await clearTestEmails(harness);
|
||||||
|
const email = createUniqueEmail('dsa-reporter');
|
||||||
|
await createBuilderWithoutAuth(harness).post('/reports/dsa/email/send').body({email}).execute();
|
||||||
|
const emails = await listTestEmails(harness);
|
||||||
|
const dsaEmail = findLastTestEmail(emails, 'dsa_report_verification');
|
||||||
|
const code = dsaEmail!.metadata.code;
|
||||||
|
const verifyResponse = await createBuilder<{
|
||||||
|
ticket: string;
|
||||||
|
}>(harness, '')
|
||||||
|
.post('/reports/dsa/email/verify')
|
||||||
|
.body({email, code})
|
||||||
|
.expect(HTTP_STATUS.OK)
|
||||||
|
.execute();
|
||||||
|
await createBuilderWithoutAuth(harness)
|
||||||
|
.post('/reports/dsa')
|
||||||
|
.body({
|
||||||
|
ticket: verifyResponse.ticket,
|
||||||
|
report_type: 'message',
|
||||||
|
category: 'harassment',
|
||||||
|
message_link: 'https://fluxer.test/channels/1/abc/def',
|
||||||
|
reporter_full_legal_name: 'John Doe',
|
||||||
|
reporter_country_of_residence: 'DE',
|
||||||
|
})
|
||||||
|
.expect(HTTP_STATUS.NOT_FOUND, APIErrorCodes.UNKNOWN_MESSAGE)
|
||||||
|
.execute();
|
||||||
|
});
|
||||||
|
test('should reject DSA message report with an out-of-range message link segment', async () => {
|
||||||
|
await clearTestEmails(harness);
|
||||||
|
const email = createUniqueEmail('dsa-reporter');
|
||||||
|
await createBuilderWithoutAuth(harness).post('/reports/dsa/email/send').body({email}).execute();
|
||||||
|
const emails = await listTestEmails(harness);
|
||||||
|
const dsaEmail = findLastTestEmail(emails, 'dsa_report_verification');
|
||||||
|
const code = dsaEmail!.metadata.code;
|
||||||
|
const verifyResponse = await createBuilder<{
|
||||||
|
ticket: string;
|
||||||
|
}>(harness, '')
|
||||||
|
.post('/reports/dsa/email/verify')
|
||||||
|
.body({email, code})
|
||||||
|
.expect(HTTP_STATUS.OK)
|
||||||
|
.execute();
|
||||||
|
await createBuilderWithoutAuth(harness)
|
||||||
|
.post('/reports/dsa')
|
||||||
|
.body({
|
||||||
|
ticket: verifyResponse.ticket,
|
||||||
|
report_type: 'message',
|
||||||
|
category: 'harassment',
|
||||||
|
message_link: 'https://fluxer.test/channels/1/99999999999999999999/1',
|
||||||
|
reporter_full_legal_name: 'John Doe',
|
||||||
|
reporter_country_of_residence: 'DE',
|
||||||
|
})
|
||||||
|
.expect(HTTP_STATUS.NOT_FOUND, APIErrorCodes.UNKNOWN_MESSAGE)
|
||||||
|
.execute();
|
||||||
|
});
|
||||||
test('should reject DSA report with invalid ticket', async () => {
|
test('should reject DSA report with invalid ticket', async () => {
|
||||||
const targetUser = await createTestAccount(harness);
|
const targetUser = await createTestAccount(harness);
|
||||||
await createBuilderWithoutAuth(harness)
|
await createBuilderWithoutAuth(harness)
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export class SearchService {
|
|||||||
const {channel_id, channel_ids, context_channel_id, context_guild_id, ...searchParams} = data;
|
const {channel_id, channel_ids, context_channel_id, context_guild_id, ...searchParams} = data;
|
||||||
const contextChannelId = context_channel_id ? createChannelID(context_channel_id) : null;
|
const contextChannelId = context_channel_id ? createChannelID(context_channel_id) : null;
|
||||||
const contextGuildId = context_guild_id ? createGuildID(context_guild_id) : null;
|
const contextGuildId = context_guild_id ? createGuildID(context_guild_id) : null;
|
||||||
const channelIds = channel_ids?.map((id) => createChannelID(id)) ?? [];
|
const channelIds = (channel_ids ?? channel_id)?.map((id) => createChannelID(id)) ?? [];
|
||||||
const scope = searchParams.scope ?? 'current';
|
const scope = searchParams.scope ?? 'current';
|
||||||
let result: MessageSearchResponse;
|
let result: MessageSearchResponse;
|
||||||
switch (scope) {
|
switch (scope) {
|
||||||
|
|||||||
@@ -12,7 +12,12 @@ import {
|
|||||||
createGuild,
|
createGuild,
|
||||||
getChannel,
|
getChannel,
|
||||||
} from '../../guild/tests/GuildTestUtils';
|
} from '../../guild/tests/GuildTestUtils';
|
||||||
import {markChannelAsIndexed, pinMessage, sendMessage} from '../../message/tests/MessageTestUtils';
|
import {
|
||||||
|
markChannelAsIndexed,
|
||||||
|
markGuildChannelsAsIndexed,
|
||||||
|
pinMessage,
|
||||||
|
sendMessage,
|
||||||
|
} from '../../message/tests/MessageTestUtils';
|
||||||
import {type ApiTestHarness, createApiTestHarness} from '../../test/ApiTestHarness';
|
import {type ApiTestHarness, createApiTestHarness} from '../../test/ApiTestHarness';
|
||||||
import {HTTP_STATUS} from '../../test/TestConstants';
|
import {HTTP_STATUS} from '../../test/TestConstants';
|
||||||
import {createBuilder} from '../../test/TestRequestBuilder';
|
import {createBuilder} from '../../test/TestRequestBuilder';
|
||||||
@@ -292,6 +297,55 @@ describe('Message Search Filters', () => {
|
|||||||
expect(result.messages.every((m) => m.channel_id !== channel3.id)).toBe(true);
|
expect(result.messages.every((m) => m.channel_id !== channel3.id)).toBe(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
test('channel_id narrows scope like channel_ids', async () => {
|
||||||
|
const account = await createTestAccount(harness);
|
||||||
|
const guild = await createGuild(harness, account.token, 'Channel Id Alias Guild');
|
||||||
|
const channel1 = await getChannel(harness, account.token, guild.system_channel_id!);
|
||||||
|
const channel2 = await createChannel(harness, account.token, guild.id, 'alias-second-channel');
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const baseContent = `channel-id-alias-${timestamp}`;
|
||||||
|
await sendMessage(harness, account.token, channel1.id, `${baseContent} channel1 msg`);
|
||||||
|
await sendMessage(harness, account.token, channel2.id, `${baseContent} channel2 msg`);
|
||||||
|
await markGuildChannelsAsIndexed(harness, account.token, guild.id);
|
||||||
|
const result = await createBuilder<MessageSearchResponse>(harness, account.token)
|
||||||
|
.post('/search/messages')
|
||||||
|
.body({
|
||||||
|
content: baseContent,
|
||||||
|
scope: 'all_guilds',
|
||||||
|
channel_id: [channel1.id],
|
||||||
|
})
|
||||||
|
.expect(HTTP_STATUS.OK)
|
||||||
|
.execute();
|
||||||
|
if (!isSearchResult(result)) {
|
||||||
|
expect.fail('Expected search result but got indexing response');
|
||||||
|
}
|
||||||
|
expect(result.messages.length).toBeGreaterThan(0);
|
||||||
|
for (const msg of result.messages) {
|
||||||
|
expect(msg.channel_id).toBe(channel1.id);
|
||||||
|
}
|
||||||
|
expect(result.messages.every((m) => m.channel_id !== channel2.id)).toBe(true);
|
||||||
|
});
|
||||||
|
test('channel_id outside the resolved scope returns 403', async () => {
|
||||||
|
const account = await createTestAccount(harness);
|
||||||
|
const outsider = await createTestAccount(harness);
|
||||||
|
const guild = await createGuild(harness, account.token, 'Channel Id Scope Guild');
|
||||||
|
const outsideGuild = await createGuild(harness, outsider.token, 'Channel Id Outside Guild');
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const baseContent = `channel-id-scope-${timestamp}`;
|
||||||
|
await sendMessage(harness, account.token, guild.system_channel_id!, `${baseContent} own msg`);
|
||||||
|
await sendMessage(harness, outsider.token, outsideGuild.system_channel_id!, `${baseContent} outside msg`);
|
||||||
|
await markGuildChannelsAsIndexed(harness, account.token, guild.id);
|
||||||
|
await markGuildChannelsAsIndexed(harness, outsider.token, outsideGuild.id);
|
||||||
|
await createBuilder<MessageSearchResponse>(harness, account.token)
|
||||||
|
.post('/search/messages')
|
||||||
|
.body({
|
||||||
|
content: baseContent,
|
||||||
|
scope: 'all_guilds',
|
||||||
|
channel_id: [outsideGuild.system_channel_id!],
|
||||||
|
})
|
||||||
|
.expect(HTTP_STATUS.FORBIDDEN, 'MISSING_PERMISSIONS')
|
||||||
|
.execute();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
describe('Content Type Filtering (has/exclude_has)', () => {
|
describe('Content Type Filtering (has/exclude_has)', () => {
|
||||||
test('has: link finds messages with links', async () => {
|
test('has: link finds messages with links', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user