support for 202507 endpoints (#37)

* [auth][api] accept the 20250424.01 client

* [2025] unstable

* 20250718.0

* correct one this time

* stubs

* more stubs

* more stubs

* [lists] add worker

* [ai] route stubs

* [api] player photo setting

* [econ] add roomEconConfig route

* [infra] update worker generators

* [worker] add cards/moderation/platformnotification workers

* [lists] updates to some endpoints

* [clubs] stub out announcement endpoint, for now

* [econ] stub out season endpoints for now

* [chat] apps/chat stub out party endpoint not sure the shape yet

* [api] stub out statsig and lockeditems

* [doc] new services

* [lists] stub the bulk endpoint

* [datacollection] add placeholder service until we can kill it

* [api] set gifting to lvl5

* update lock

* [cdn] enable cache

* [match] matchmake v2

* [lists] stub some lists

* [ai] stubs

* [rooms] new subroom save endpoint

* [econ] add bulk purchase endpoint

* [discovery] update featured creator to 1 for fun

* [api] add photo settings flag

* [chat] fixup chat permissions (sorta)

* [auth] restrictions endpoint

* [rooms] contributed endpoint

* [api] fix outfit endpoint

* [discovery] attempt to fix store

* [chat] privacy endpoints

* [api] cheered images

* [rooms] add xp endpoint (disbaled)

* [rooms] add xp endpoint (disabled)

* update images-db for cheers

* [rooms] add autocomplete endpoint

* [cdn/img] increase cache ttl for statics

* [api] bulk route for images

* [accounts] add banner image

* [api] add misc missing endpoints

* [discovery] remove AI tab

* [platformnotifications] stub some endpoints

* [lists] add some more lists

* [rooms] additional endpoints

* [chat] stub a few privacy endpoints

* [econ] stub some endpoints

* misc db fixes

* [api] tweak shape for images v6

* [rooms] dont show trending RROs
This commit is contained in:
devin
2026-08-18 23:07:24 -04:00
committed by Devin Zuczek
parent 66c09806f9
commit 178d3b5b0e
162 changed files with 114930 additions and 469 deletions
+100 -9
View File
@@ -376,6 +376,95 @@ describe('thread storage', () => {
})
})
// The party thread is a stub until its real shape is observed off a live client; these
// pin down only what the stub promises — auth, and an object body rather than a 404.
describe('GET /settings/partyinvite', () => {
it('answers the invite-link lifetime as a bare single-key object', async () => {
const res = await SELF.fetch(`${ORIGIN}/settings/partyinvite`, {
headers: await bearer(885001),
})
expect(res.status).toBe(200)
// One key, no `{ success, error, value }` envelope around it.
expect(await res.json()).toEqual({ InviteLinkLifetimeInMinutes: 60 })
})
it('401s without a token', async () => {
const res = await SELF.fetch(`${ORIGIN}/settings/partyinvite`)
expect(res.status).toBe(401)
})
})
describe('GET /thread/party', () => {
it('answers an empty object', async () => {
const res = await SELF.fetch(`${ORIGIN}/thread/party?maxCount=1&mode=0`, {
headers: await bearer(883001),
})
expect(res.status).toBe(200)
expect(await res.json()).toEqual({})
})
it('401s without a token', async () => {
const res = await SELF.fetch(`${ORIGIN}/thread/party?maxCount=1&mode=0`)
expect(res.status).toBe(401)
})
})
describe('GET /thread/chatPrivacySetting', () => {
it('reports Friends for both settings, keyed to the caller', async () => {
const res = await SELF.fetch(`${ORIGIN}/thread/chatPrivacySetting`, {
headers: await bearer(886001),
})
expect(res.status).toBe(200)
// camelCase, and the enum by NUMBER (0 = Friends) — this build has no by-name enum
// formatter, so a string would decode as nothing.
expect(await res.json()).toEqual({
playerId: 886001,
directMessagePrivacySetting: 0,
groupChatPrivacySetting: 0,
})
})
it('reads playerId off the token, not a query param', async () => {
const res = await SELF.fetch(`${ORIGIN}/thread/chatPrivacySetting?playerId=999999`, {
headers: await bearer(886002),
})
expect(((await res.json()) as { playerId: number }).playerId).toBe(886002)
})
it('401s without a token', async () => {
const res = await SELF.fetch(`${ORIGIN}/thread/chatPrivacySetting`)
expect(res.status).toBe(401)
})
})
describe('GET /thread/checkCanSendDirectMessageWithPrivacySetting', () => {
const path = `${ORIGIN}/thread/checkCanSendDirectMessageWithPrivacySetting`
it('always allows the DM, as a bare ChatResult integer', async () => {
const res = await SELF.fetch(`${path}?receivingPlayerId=205`, {
headers: await bearer(884001),
})
expect(res.status).toBe(200)
expect(res.headers.get('content-type')).toContain('application/json')
// The whole body is the ChatResult — 0 is Success. NOT a boolean: the client
// instantiates its response wrapper with the enum, so `true` would decode as nothing.
expect(await res.text()).toBe('0')
})
it('answers the same for any player, and with no player named', async () => {
// `receivingPlayerId` is ignored — nothing here stores a privacy setting to refuse on.
for (const query of ['?receivingPlayerId=205', '?receivingPlayerId=999999', '']) {
const res = await SELF.fetch(`${path}${query}`, { headers: await bearer(884002) })
expect(await res.text()).toBe('0')
}
})
it('401s without a token', async () => {
const res = await SELF.fetch(`${path}?receivingPlayerId=205`)
expect(res.status).toBe(401)
})
})
describe('POST /thread/withmembers', () => {
async function withMembers(caller: number, body: string) {
return SELF.fetch(`${ORIGIN}/thread/withmembers`, {
@@ -760,9 +849,9 @@ describe('ChatMessageReceived push', () => {
const sent = await hub.getByName('global').takeSent()
expect(sent.map((n) => n.playerId).sort((a, b) => a - b)).toEqual([caller, 886002, 886003])
expect(
sent.every((n) => n.notificationType === NotificationType.ChatMessageReceived)
).toBe(true)
expect(sent.every((n) => n.notificationType === NotificationType.ChatMessageReceived)).toBe(
true
)
expect(sent[0]!.data).toEqual({
chatMessageId: chatThread.latestMessage.chatMessageId,
chatThreadId: chatThread.chatThreadId,
@@ -984,9 +1073,7 @@ describe('POST /thread/:id', () => {
it('pushes ChatMessageReceived to every member', async () => {
const hub = env.RECFLARE_NOTIFICATIONS_HUB as unknown as {
getByName(name: string): {
takeSent(): Promise<
Array<{ playerId: number; notificationType: NotificationType }>
>
takeSent(): Promise<Array<{ playerId: number; notificationType: NotificationType }>>
}
}
const caller = 889005
@@ -996,9 +1083,9 @@ describe('POST /thread/:id', () => {
await send(caller, `/thread/${chatThreadId}`)
const sent = await hub.getByName('global').takeSent()
expect(sent.map((n) => n.playerId).sort((a, b) => a - b)).toEqual([caller, 889006])
expect(
sent.every((n) => n.notificationType === NotificationType.ChatMessageReceived)
).toBe(true)
expect(sent.every((n) => n.notificationType === NotificationType.ChatMessageReceived)).toBe(
true
)
})
it('reports invalid arguments for blank contents without storing anything', async () => {
@@ -1301,7 +1388,11 @@ describe('openapi', () => {
expect([...documented].sort()).toEqual([
'DELETE /thread/{id}/leave',
'GET /',
'GET /settings/partyinvite',
'GET /thread',
'GET /thread/chatPrivacySetting',
'GET /thread/checkCanSendDirectMessageWithPrivacySetting',
'GET /thread/party',
'GET /thread/{id}',
'GET /thread/{id}/message',
'POST /thread',