feat(admin): require a resolution and fix panel layout and copy (#3335)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-09 01:40:34 +02:00
1 parent 4db13b0375
commit 6534ee4300
57 files changed
+1186 -255

No files matched your search

@@ -31,6 +31,9 @@ export function mapGuildToAdminResponse(guild: Guild, ownerUser?: User | null):
banner: guild.bannerHash,
member_count: guild.memberCount,
nsfw_level: guild.nsfwLevel,
nsfw: guild.nsfw,
content_warning_level: guild.contentWarningLevel as 0 | 1,
content_warning_text: guild.contentWarningText,
};
}
@@ -48,6 +51,9 @@ export function mapGuildsToAdminResponse(guilds: Array<Guild>, ownerMap?: Map<st
banner: guild.bannerHash,
member_count: guild.memberCount,
nsfw_level: guild.nsfwLevel,
nsfw: guild.nsfw,
content_warning_level: guild.contentWarningLevel as 0 | 1,
content_warning_text: guild.contentWarningText,
};
}),
};
@@ -60,6 +60,9 @@ export class AdminGuildLookupService {
verification_level: guild.verificationLevel,
mfa_level: guild.mfaLevel,
nsfw_level: guild.nsfwLevel,
nsfw: guild.nsfw,
content_warning_level: guild.contentWarningLevel as 0 | 1,
content_warning_text: guild.contentWarningText,
explicit_content_filter: guild.explicitContentFilter,
default_message_notifications: guild.defaultMessageNotifications,
afk_channel_id: guild.afkChannelId?.toString() ?? null,
@@ -76,6 +79,9 @@ export class AdminGuildLookupService {
position: c.position,
parent_id: c.parentId?.toString() ?? null,
nsfw: c.isNsfw,
nsfw_override: c.nsfwOverride,
content_warning_level: c.contentWarningLevel as 0 | 1,
content_warning_text: c.contentWarningText,
url: c.url,
})),
roles: roles.map((r) => ({
@@ -16,6 +16,19 @@ interface AdminGuildDetail {
} | null;
}
interface AdminGuildWarningDetail {
guild: {
nsfw: boolean;
content_warning_level: number;
content_warning_text: string | null;
channels: Array<{
nsfw_override: boolean | null;
content_warning_level: number;
content_warning_text: string | null;
}>;
} | null;
}
interface AdminGuildUpdate {
guild: {
id: string;
@@ -98,6 +111,30 @@ describe('Admin guild routes', () => {
'owner_id',
]);
});
test('GET /admin/guilds/{guild_id} returns the adult content and content warning state', async () => {
const admin = await createTestAccount(harness);
await setUserACLs(harness, admin, ['admin:authenticate', 'guild:lookup', 'guild:update:settings']);
const guild = await createGuild(harness, admin.token, `Warning Lookup Guild ${Date.now()}`);
const before = await createBuilder<AdminGuildWarningDetail>(harness, `${admin.token}`)
.get(`/admin/guilds/${guild.id}`)
.expect(HTTP_STATUS.OK)
.execute();
expect(before.guild).toMatchObject({nsfw: false, content_warning_level: 0, content_warning_text: null});
expect(before.guild?.channels.length).toBeGreaterThan(0);
for (const channel of before.guild?.channels ?? []) {
expect(channel).toMatchObject({nsfw_override: null, content_warning_level: 0, content_warning_text: null});
}
await createBuilder(harness, `${admin.token}`)
.patch(`/admin/guilds/${guild.id}`)
.body({nsfw: true, content_warning_level: 1, content_warning_text: 'Graphic content'})
.expect(HTTP_STATUS.OK)
.execute();
const after = await createBuilder<AdminGuildWarningDetail>(harness, `${admin.token}`)
.get(`/admin/guilds/${guild.id}`)
.expect(HTTP_STATUS.OK)
.execute();
expect(after.guild).toMatchObject({nsfw: true, content_warning_level: 1, content_warning_text: 'Graphic content'});
});
test('PATCH /admin/guilds/{guild_id} requires the ACL selected by every supplied field', async () => {
const admin = await createTestAccount(harness);
await setUserACLs(harness, admin, ['admin:authenticate', 'guild:update:settings']);
@@ -223,6 +223,36 @@ export const ReportAdminAuditCases: ReadonlyArray<AdminAuditCoverageCase> = [
};
},
},
...(['actioned', 'no_violation', 'duplicate'] as const).map(
(resolution): AdminAuditCoverageCase => ({
method: 'PATCH',
route: '/admin/reports/:report_id',
name: `resolution ${resolution}`,
async prepare(context) {
const {reportId} = await fileUserReport(context);
return {
request: {
path: `/admin/reports/${reportId}`,
body: {status: 'resolved', resolution, notify_reporter: false},
},
expected: {
action: 'resolve_report',
targetType: 'report',
targetId: reportId,
metadata: {
report_id: reportId,
report_type: '1',
notify_reporter: 'false',
reporter_dm_sent: 'true',
reporter_email_sent: 'true',
reporter_dsa_email_sent: 'false',
resolution,
},
},
};
},
}),
),
{
method: 'POST',
route: '/admin/reports/:report_id/legal-hold',
@@ -146,6 +146,22 @@ describe('moderation activity events', () => {
expect(resolved?.meta.ip).toBeNull();
});
test.each(['actioned', 'no_violation', 'duplicate'] as const)(
'a staff resolve with %s publishes that outcome even when the account is under enforcement',
async (resolution) => {
const {reported, reportId} = await fileReport();
await createBuilder(harness, admin.token)
.put(`/admin/users/${reported.userId}/ban`)
.body({duration_hours: 24, notify_user: false})
.expect(HTTP_STATUS.OK)
.execute();
await resolve(reportId, {resolution});
expect(publisher.of('report_resolved').map((event) => event.data)).toEqual([
expect.objectContaining({report_id: reportId, outcome: resolution, resolved_by: 'staff'}),
]);
},
);
test('without a chosen outcome the reported account state decides between actioned and unspecified', async () => {
const first = await fileReport();
await resolve(first.reportId, {});