mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-09 07:01:27 -07:00
[2025] unstable
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
LEVEL_REQUIRED_XP,
|
||||
LEVEL_REWARDS,
|
||||
MAX_LEVEL,
|
||||
OUTFIT_SCHEMA_DDL,
|
||||
PROGRESSION_SCHEMA_DDL,
|
||||
RELATIONSHIP_SCHEMA_DDL,
|
||||
ROOM_SCHEMA_DDL,
|
||||
@@ -108,6 +109,9 @@ beforeAll(async () => {
|
||||
// Relationships table (owned by the api worker) — friendship endpoints use it.
|
||||
for (const stmt of RELATIONSHIP_SCHEMA_DDL) await env.DB.prepare(stmt).run()
|
||||
|
||||
// Outfit table (owned by the econ worker) — /outfits/me reads and writes slot 0.
|
||||
for (const stmt of OUTFIT_SCHEMA_DDL) await env.DB.prepare(stmt).run()
|
||||
|
||||
// Inventions table (owned by the api worker) — invention save/mine use it.
|
||||
for (const stmt of INVENTIONS_SCHEMA_DDL) await env.DB.prepare(stmt).run()
|
||||
|
||||
@@ -269,23 +273,45 @@ describe('public endpoints', () => {
|
||||
expect(words[0]).toEqual({ Id: 1, Difficulty: 0, EN_US: 'David Bowie' })
|
||||
})
|
||||
|
||||
test('GET /api/PlayerReporting/v1/moderationBlockDetails reports "not blocked"', async () => {
|
||||
const res = await exports.default.fetch(
|
||||
`${ORIGIN}/api/PlayerReporting/v1/moderationBlockDetails`
|
||||
)
|
||||
expect(res.status).toBe(200)
|
||||
// ReportCategory -1 = no category (0 is a real one), and Message is null.
|
||||
expect(await res.json()).toEqual({
|
||||
ReportCategory: -1,
|
||||
Duration: 0,
|
||||
GameSessionId: 0,
|
||||
IsBan: false,
|
||||
IsHostKick: false,
|
||||
IsVoiceModAutoban: false,
|
||||
Message: null,
|
||||
PlayerIdReporter: null,
|
||||
TimeoutStartedAt: null,
|
||||
// The client POSTs this with no body, despite it being a pure read; the route answers
|
||||
// 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(
|
||||
`${ORIGIN}/api/PlayerReporting/v1/moderationBlockDetails`,
|
||||
{ method }
|
||||
)
|
||||
expect(res.status).toBe(200)
|
||||
// ReportCategory -1 = Unknown (0 is a real category). Message is null, not the
|
||||
// reference stub's empty string — the client tells "no message" from a blank one.
|
||||
expect(await res.json()).toEqual({
|
||||
ReportCategory: -1,
|
||||
Duration: 0,
|
||||
GameSessionId: 0,
|
||||
IsBan: false,
|
||||
IsHostKick: false,
|
||||
IsVoiceModAutoban: false,
|
||||
Message: null,
|
||||
PlayerIdReporter: null,
|
||||
TimeoutStartedAt: null,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
test('POST /api/PlayerReporting/v1/referee says the caller is not one', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/PlayerReporting/v1/referee`, {
|
||||
method: 'POST',
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
// A bare boolean, not an envelope or a list.
|
||||
expect(await res.json()).toBe(false)
|
||||
})
|
||||
|
||||
test('GET /api/referee/files has no cases', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/referee/files`)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual([])
|
||||
})
|
||||
|
||||
// Unauthenticated by design — the client posts this before it has an account, so
|
||||
@@ -462,6 +488,128 @@ describe('public endpoints', () => {
|
||||
expect(await res.json()).toEqual({ Results: [], TotalResults: 0 })
|
||||
})
|
||||
|
||||
test('POST /api/customAvatarItems/GetCustomAvatarItemCurrentSavesForLegacyAvatarItems returns an empty map', async () => {
|
||||
const res = await exports.default.fetch(
|
||||
`${ORIGIN}/api/customAvatarItems/GetCustomAvatarItemCurrentSavesForLegacyAvatarItems`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ AvatarItemIds: [1, 2, 3] }),
|
||||
}
|
||||
)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual({ customAvatarItemSavesByAvatarItemDesc: {} })
|
||||
})
|
||||
|
||||
test('GET /outfits/me 401s without a token, serves the empty envelope for a new player', async () => {
|
||||
const anon = await exports.default.fetch(`${ORIGIN}/outfits/me`)
|
||||
expect(anon.status).toBe(401)
|
||||
// Account 77 never saves an outfit, so it keeps getting the new-account envelope.
|
||||
const res = await exports.default.fetch(`${ORIGIN}/outfits/me`, { headers: await bearer('77') })
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual({
|
||||
LegacyData: {
|
||||
SelectionsV1: null,
|
||||
SelectionsV2: null,
|
||||
FaceFeatures: null,
|
||||
SkinColor: null,
|
||||
HairColor: null,
|
||||
},
|
||||
Selections: [],
|
||||
DataVersion: 9,
|
||||
CustomizationSettings: null,
|
||||
ThumbnailFileName: null,
|
||||
Name: null,
|
||||
Accessibility: 0,
|
||||
Slot: 0,
|
||||
})
|
||||
})
|
||||
|
||||
test('PUT /outfits/me saves into slot 0; GET reads it back verbatim', async () => {
|
||||
// The client's own payload, trimmed to one selection: the point is that the heavy
|
||||
// JSON-in-a-string fields survive the round trip as strings, unparsed.
|
||||
const outfit = {
|
||||
DataVersion: 2,
|
||||
LegacyData: {
|
||||
SelectionsV1: '193a3bf9-abc0-4d78-8d63-92046908b1c5,,0',
|
||||
SelectionsV2:
|
||||
'{"selections":[{"PrefabGuid":"193a3bf9-abc0-4d78-8d63-92046908b1c5","CombinationGuid":"","BodyPart":0}]}',
|
||||
FaceFeatures: '{"ver":7,"eyeId":"Aeu0yxJXG0qCOLZW5Tcu7A","hideEars":false}',
|
||||
SkinColor: 'Dc6StLFk60u5iUTrb3_C3w',
|
||||
HairColor: 'UAT0OaWEkUG-mWDIyiX1Kg',
|
||||
},
|
||||
CustomizationSettings: '{"AvatarVersion":2,"AvatarBodyType":0}',
|
||||
Selections: [],
|
||||
Slot: 0,
|
||||
Name: null,
|
||||
Accessibility: 1,
|
||||
ThumbnailFileName: null,
|
||||
}
|
||||
|
||||
const anon = await exports.default.fetch(`${ORIGIN}/outfits/me`, {
|
||||
method: 'PUT',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify(outfit),
|
||||
})
|
||||
expect(anon.status).toBe(401)
|
||||
|
||||
const res = await exports.default.fetch(`${ORIGIN}/outfits/me`, {
|
||||
method: 'PUT',
|
||||
headers: { ...(await bearer()), 'content-type': 'application/json' },
|
||||
body: JSON.stringify(outfit),
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual(outfit)
|
||||
|
||||
// The read serves it back byte-for-byte — the JSON-in-a-string fields are still
|
||||
// strings, not re-encoded objects.
|
||||
const read = await exports.default.fetch(`${ORIGIN}/outfits/me`, { headers: await bearer() })
|
||||
expect(await read.json()).toEqual(outfit)
|
||||
|
||||
// Re-saving overwrites slot 0 rather than adding a second row.
|
||||
const changed = { ...outfit, LegacyData: { ...outfit.LegacyData, SkinColor: 'changed' } }
|
||||
await exports.default.fetch(`${ORIGIN}/outfits/me`, {
|
||||
method: 'PUT',
|
||||
headers: { ...(await bearer()), 'content-type': 'application/json' },
|
||||
body: JSON.stringify(changed),
|
||||
})
|
||||
const reread = await exports.default.fetch(`${ORIGIN}/outfits/me`, { headers: await bearer() })
|
||||
expect(await reread.json()).toEqual(changed)
|
||||
const rows = await env.DB.prepare(
|
||||
'SELECT COUNT(*) AS n FROM outfit WHERE account_id = 42'
|
||||
).first<{ n: number }>()
|
||||
expect(rows?.n).toBe(1)
|
||||
|
||||
// A save naming another slot does not touch what the caller is wearing.
|
||||
await exports.default.fetch(`${ORIGIN}/outfits/me`, {
|
||||
method: 'PUT',
|
||||
headers: { ...(await bearer()), 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ ...changed, Slot: 3, Name: 'slot three' }),
|
||||
})
|
||||
const worn = await exports.default.fetch(`${ORIGIN}/outfits/me`, { headers: await bearer() })
|
||||
expect(((await worn.json()) as { Name: string | null }).Name).toBe(null)
|
||||
})
|
||||
|
||||
test('GET /outfits/me/saved 401s without a token, returns [] with one', async () => {
|
||||
const anon = await exports.default.fetch(`${ORIGIN}/outfits/me/saved`)
|
||||
expect(anon.status).toBe(401)
|
||||
// Empty even for account 42, which saved an outfit through PUT /outfits/me above.
|
||||
const res = await exports.default.fetch(`${ORIGIN}/outfits/me/saved`, {
|
||||
headers: await bearer(),
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual([])
|
||||
})
|
||||
|
||||
test('PUT /outfits/me 400s on an unparseable body', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/outfits/me`, {
|
||||
method: 'PUT',
|
||||
headers: { ...(await bearer()), 'content-type': 'application/json' },
|
||||
body: 'not json',
|
||||
})
|
||||
expect(res.status).toBe(400)
|
||||
})
|
||||
|
||||
test('GET /api/rooms/v1/filters returns an object with filter arrays', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/rooms/v1/filters`)
|
||||
expect(res.status).toBe(200)
|
||||
@@ -3650,6 +3798,7 @@ describe('openapi', () => {
|
||||
'GET /api/players/v1/progression/{id}',
|
||||
'GET /api/players/v2/progression/bulk',
|
||||
'GET /api/quickPlay/v1/getandclear',
|
||||
'GET /api/referee/files',
|
||||
'GET /api/relationships/mutualfriends',
|
||||
'GET /api/relationships/v1/favorite',
|
||||
'GET /api/relationships/v1/ignore',
|
||||
@@ -3667,11 +3816,16 @@ describe('openapi', () => {
|
||||
'GET /api/rooms/v1/filters',
|
||||
'GET /api/versioncheck/islandedversions',
|
||||
'GET /api/versioncheck/v4',
|
||||
'GET /outfits/me',
|
||||
'GET /outfits/me/saved',
|
||||
'GET /voice/config',
|
||||
'POST /api/PlayerReporting/v1/deviceId',
|
||||
'POST /api/PlayerReporting/v1/hile',
|
||||
'POST /api/PlayerReporting/v1/moderationBlockDetails',
|
||||
'POST /api/PlayerReporting/v1/referee',
|
||||
'POST /api/PlayerReporting/v3/create',
|
||||
'POST /api/avatar/v2/gifts/generate',
|
||||
'POST /api/customAvatarItems/GetCustomAvatarItemCurrentSavesForLegacyAvatarItems',
|
||||
'POST /api/gamesight/event',
|
||||
'POST /api/images/v1/cheer',
|
||||
'POST /api/images/v4/uploadsaved',
|
||||
@@ -3705,6 +3859,7 @@ describe('openapi', () => {
|
||||
'POST /api/sanitize/v1',
|
||||
'POST /api/sanitize/v1/isPure',
|
||||
'POST /api/v1/progression/bulk',
|
||||
'PUT /outfits/me',
|
||||
])
|
||||
|
||||
// Every operation carries a summary — an undescribed one renders as a bare path.
|
||||
|
||||
Reference in New Issue
Block a user