update moderationblockdetails to work on both clients for now

This commit is contained in:
Devin Zuczek
2026-07-29 12:47:17 -04:00
parent 5d70329067
commit a69f2a5dac
3 changed files with 43 additions and 32 deletions
+3 -2
View File
@@ -391,10 +391,11 @@ export const SubscriptionResponse = z.object({
// ---- Moderation ------------------------------------------------------------ // ---- Moderation ------------------------------------------------------------
/** /**
* `GET /api/PlayerReporting/v1/moderationBlockDetails` — always the "not blocked" * `GET|POST /api/PlayerReporting/v1/moderationBlockDetails` — always the "not blocked"
* answer (no ban storage yet), mirroring the reference server's stub * answer (no ban storage yet), mirroring the reference server's stub
* `ReturnModerationBlockDetails()`. `ReportCategory` is `Unknown` (-1) rather than 0, * `ReturnModerationBlockDetails()`. `ReportCategory` is `Unknown` (-1) rather than 0,
* which is a real category, and `Message` is the empty string the reference sends. * which is a real category, and `Message` is null — the client distinguishes "no
* message" from a blank one, so we send null where the reference sends an empty string.
* `IsVoiceModAutoban`/`TimeoutStartedAt` are on the DTO but unset by that stub, so * `IsVoiceModAutoban`/`TimeoutStartedAt` are on the DTO but unset by that stub, so
* they carry their C# defaults (false / null). * they carry their C# defaults (false / null).
*/ */
+14 -9
View File
@@ -16,22 +16,27 @@ import type { App } from '../context'
export const moderationRoutes = new Hono<App>({ strict: false }) export const moderationRoutes = new Hono<App>({ strict: false })
// Whether the caller is currently blocked (banned / timed out / host-kicked). No ban // Whether the caller is currently blocked (banned / timed out / host-kicked). No ban
// storage yet, so this is always the "not blocked" answer — the reference server's // storage yet, so this is always the "not blocked" answer — the reference server's
// stub `ReturnModerationBlockDetails()` verbatim. `ReportCategory` is `Unknown` (-1), // stub `ReturnModerationBlockDetails()`. `ReportCategory` is `Unknown` (-1), not 0,
// not 0, which is a real category, and `Message` is the empty string that stub sends. // which is a real category. `Message` is null rather than the empty string that stub
// sends: the client distinguishes "no message" from a blank one.
// `IsVoiceModAutoban`/`TimeoutStartedAt` are on the DTO but left unset there, so they // `IsVoiceModAutoban`/`TimeoutStartedAt` are on the DTO but left unset there, so they
// go out with their C# defaults. // go out with their C# defaults.
// POST with no body the client's actual call, despite this being a pure read. // POST with no body is the client's actual call, despite this being a pure read; it
.post( // answers GET too, so the path is reachable either way.
.on(
['GET', 'POST'],
'/api/PlayerReporting/v1/moderationBlockDetails', '/api/PlayerReporting/v1/moderationBlockDetails',
describeRoute({ describeRoute({
tags: ['Moderation'], tags: ['Moderation'],
summary: 'Whether the caller is blocked', summary: 'Whether the caller is blocked',
description: description:
'Ban / timeout / host-kick state for the caller. There is no ban storage yet, so ' + 'Ban / timeout / host-kick state for the caller. There is no ban storage yet, so ' +
'this is always the “not blocked” answer, matching the reference servers stub: ' + 'this is always the “not blocked” answer, following the reference servers stub: ' +
'`ReportCategory` is `Unknown` (-1) rather than 0, which is a real category, and ' + '`ReportCategory` is `Unknown` (-1) rather than 0, which is a real category. ' +
'`Message` is an empty string. `IsVoiceModAutoban` and `TimeoutStartedAt` are on ' + '`Message` is null rather than the empty string that stub sends — the client ' +
'the DTO but unset by that stub, so they carry their defaults.', 'distinguishes “no message” from a blank one. `IsVoiceModAutoban` and ' +
'`TimeoutStartedAt` are on the DTO but unset by that stub, so they carry their ' +
'defaults.',
responses: { 200: json(ModerationBlockDetails, 'Always “not blocked”') }, responses: { 200: json(ModerationBlockDetails, 'Always “not blocked”') },
}), }),
(c) => (c) =>
@@ -42,7 +47,7 @@ export const moderationRoutes = new Hono<App>({ strict: false })
IsBan: false, IsBan: false,
IsHostKick: false, IsHostKick: false,
IsVoiceModAutoban: false, IsVoiceModAutoban: false,
Message: '', Message: null,
PlayerIdReporter: null, PlayerIdReporter: null,
TimeoutStartedAt: null, TimeoutStartedAt: null,
}) })
+12 -7
View File
@@ -233,15 +233,18 @@ describe('public endpoints', () => {
expect(await res.json()).toEqual([]) expect(await res.json()).toEqual([])
}) })
test('POST /api/PlayerReporting/v1/moderationBlockDetails reports "not blocked"', async () => { // The client POSTs this with no body, despite it being a pure read; the route answers
// The client POSTs this with no body, despite it being a pure read. // GET as well, and both methods serve the same body.
test.each(['GET', 'POST'])(
'%s /api/PlayerReporting/v1/moderationBlockDetails reports "not blocked"',
async (method) => {
const res = await exports.default.fetch( const res = await exports.default.fetch(
`${ORIGIN}/api/PlayerReporting/v1/moderationBlockDetails`, `${ORIGIN}/api/PlayerReporting/v1/moderationBlockDetails`,
{ method: 'POST' } { method }
) )
expect(res.status).toBe(200) expect(res.status).toBe(200)
// The reference server's stub verbatim: ReportCategory -1 = Unknown (0 is a real // ReportCategory -1 = Unknown (0 is a real category). Message is null, not the
// category) and an empty-string Message. // reference stub's empty string — the client tells "no message" from a blank one.
expect(await res.json()).toEqual({ expect(await res.json()).toEqual({
ReportCategory: -1, ReportCategory: -1,
Duration: 0, Duration: 0,
@@ -249,11 +252,12 @@ describe('public endpoints', () => {
IsBan: false, IsBan: false,
IsHostKick: false, IsHostKick: false,
IsVoiceModAutoban: false, IsVoiceModAutoban: false,
Message: '', Message: null,
PlayerIdReporter: null, PlayerIdReporter: null,
TimeoutStartedAt: null, TimeoutStartedAt: null,
}) })
}) }
)
// Unauthenticated by design — the client posts this before it has an account, so // Unauthenticated by design — the client posts this before it has an account, so
// there's no bearer token to check and nothing to attribute the id to. // there's no bearer token to check and nothing to attribute the id to.
@@ -1899,6 +1903,7 @@ describe('openapi', () => {
) )
expect([...documented].sort()).toEqual([ expect([...documented].sort()).toEqual([
'DELETE /api/images/v1/deletesaved', 'DELETE /api/images/v1/deletesaved',
'GET /api/PlayerReporting/v1/moderationBlockDetails',
'GET /api/PlayerReporting/v1/voteToKickReasons', 'GET /api/PlayerReporting/v1/voteToKickReasons',
'GET /api/activities/charades/v1/words/{activity}', 'GET /api/activities/charades/v1/words/{activity}',
'GET /api/announcement/v1/get', 'GET /api/announcement/v1/get',