mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-09 07:01:27 -07:00
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:
@@ -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 caller’s 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('')
|
||||
})
|
||||
Reference in New Issue
Block a user