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
@@ -0,0 +1,114 @@
import { adminSecretsStore, env, SELF } from 'cloudflare:test'
import { beforeAll, expect, it } from 'vitest'
import type { Env } from '../../context'
declare module 'cloudflare:test' {
interface ProvidedEnv extends Env {}
}
const ORIGIN = 'https://example.com'
beforeAll(async () => {
// Seed the shared JWT signing key into the local Secrets Store so .get() resolves.
await adminSecretsStore(env.JWT_SECRET).create('test-signing-key')
})
// Mint a token the way the `auth` worker does, signing with the shared test key seeded
// into the JWT_SECRET store.
const TEST_SECRET = 'test-signing-key'
function b64url(input: ArrayBuffer | string): string {
const bytes = typeof input === 'string' ? new TextEncoder().encode(input) : new Uint8Array(input)
let binary = ''
for (const byte of bytes) binary += String.fromCharCode(byte)
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
}
async function bearer(sub = '42'): Promise<Record<string, string>> {
const now = Math.floor(Date.now() / 1000)
const signingInput = `${b64url(JSON.stringify({ alg: 'HS256', typ: 'JWT' }))}.${b64url(
JSON.stringify({ sub, exp: now + 3600 })
)}`
const key = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(TEST_SECRET),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
)
const sig = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(signingInput))
return { Authorization: `Bearer ${signingInput}.${b64url(sig)}` }
}
it('response with hello world', async () => {
const res = await SELF.fetch(ORIGIN)
expect(res.status).toBe(200)
expect(await res.text()).toMatchInlineSnapshot(`"hello, world!"`)
})
it('reports that a player receives gameplay invites', async () => {
const res = await SELF.fetch(`${ORIGIN}/accounts/205/receives/GameplayInvites`, {
headers: await bearer('205'),
})
expect(res.status).toBe(200)
expect(res.headers.get('content-type')).toContain('application/json')
// The whole body is the boolean — not `{ value: true }`, not an envelope.
expect(await res.text()).toBe('true')
})
it('answers the same for any account id, since nothing is stored per player', async () => {
// A caller may ask about someone else; the answer doesn't depend on the id.
const other = await SELF.fetch(`${ORIGIN}/accounts/999999/receives/GameplayInvites`, {
headers: await bearer('205'),
})
expect(await other.text()).toBe('true')
// The id is digits-only, like the reference's `ulong id`.
expect((await SELF.fetch(`${ORIGIN}/accounts/abc/receives/GameplayInvites`)).status).toBe(404)
})
it('401s the gameplay-invites check without a bearer token', async () => {
const res = await SELF.fetch(`${ORIGIN}/accounts/205/receives/GameplayInvites`)
expect(res.status).toBe(401)
expect(await res.text()).toBe('')
})
it('serves the stub notification categories', async () => {
// A bare array of PascalCase categories, and no auth — the list is server-side config
// rather than anything per-player.
const res = await SELF.fetch(`${ORIGIN}/config/categories`)
expect(res.status).toBe(200)
const categories = (await res.json()) as Array<{
CategoryId: number
Importance: number
Name: string
Description: string
IsMuteable: boolean
}>
expect(categories).toHaveLength(1)
expect(categories[0]).toMatchObject({
CategoryId: 2,
Importance: 0,
Name: 'Friends',
IsMuteable: true,
})
// The stub marker is in the DISPLAYED text, so a category that does nothing says so
// in-game rather than looking like a real setting. Keep it there while this is a stub.
expect(categories[0].Description).toContain('STUB')
})
it('serves the callers notification preferences', async () => {
const res = await SELF.fetch(`${ORIGIN}/preferences`, { headers: await bearer('205') })
expect(res.status).toBe(200)
// Nothing stores preferences, so nobody has muted anything. An object, not a bare array —
// the shape has room for the other preferences the reference carries here.
expect(await res.json()).toEqual({ MutedCategories: [] })
})
it('401s the preferences read without a bearer token', async () => {
// Per-player, unlike /config/categories, so this one needs a token.
const res = await SELF.fetch(`${ORIGIN}/preferences`)
expect(res.status).toBe(401)
expect(await res.text()).toBe('')
})