mirror of
https://github.com/djdevin/recflare.git
synced 2026-10-25 23:18:51 -07:00
[2026] fix a few wire shapes
This commit is contained in:
@@ -297,6 +297,28 @@ export const progressionRoutes = new Hono<App>({ strict: false })
|
||||
}),
|
||||
async (c) => c.json(await getReputation(c.env.DB, Number.parseInt(c.req.param('id'), 10)))
|
||||
)
|
||||
// How much XP the caller has earned today — what the client measures against its daily
|
||||
// XP cap. A BARE INTEGER (the client decodes a plain `int`), not a progression record.
|
||||
//
|
||||
// Registered BEFORE `/progression/:id` below, which otherwise takes `xpEarnedToday` as
|
||||
// an account id and answers `{ PlayerId: null, Level: 1, XP: 0 }` — the client's decoder
|
||||
// threw on that (`expected:'Number Token', actual:'{'`).
|
||||
//
|
||||
// STUB: always 0. Nothing here records when XP was earned, only the running total. No
|
||||
// auth while the answer is the same for everybody; it will need the caller once it is
|
||||
// real, since the path names no player.
|
||||
.get(
|
||||
'/api/players/v1/progression/xpEarnedToday',
|
||||
describeRoute({
|
||||
tags: ['Progression'],
|
||||
summary: 'XP the caller earned today (stub)',
|
||||
description:
|
||||
'The XP earned so far today, as a bare integer. Always `0` — XP is banked as a ' +
|
||||
'running total and nothing records when it was earned.',
|
||||
responses: { 200: json(BareInteger, 'A bare `0`') },
|
||||
}),
|
||||
(c) => c.json(0)
|
||||
)
|
||||
.get(
|
||||
'/api/players/v1/progression/:id',
|
||||
describeRoute({
|
||||
|
||||
@@ -2081,6 +2081,14 @@ describe('public endpoints', () => {
|
||||
expect(res.status).toBe(400)
|
||||
})
|
||||
|
||||
test('GET /api/players/v1/progression/xpEarnedToday is a bare 0', async () => {
|
||||
// A plain int on the client. It must not fall through to `/progression/:id`, which
|
||||
// would read the path segment as an account id and answer a progression OBJECT.
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/players/v1/progression/xpEarnedToday`)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toBe(0)
|
||||
})
|
||||
|
||||
test('GET /api/progressionEvents/active is a bare 18 (no auth)', async () => {
|
||||
// A hardcoded stub: the id of an archived event (0 would mean "no active event").
|
||||
// A 404 would stall the client's load instead.
|
||||
@@ -10208,6 +10216,7 @@ describe('openapi', () => {
|
||||
'GET /api/playerevents/v1/{eventId}/responses',
|
||||
'GET /api/playerevents/v2/{eventId}',
|
||||
'GET /api/players/v1/playerPhotoTaggingSetting',
|
||||
'GET /api/players/v1/progression/xpEarnedToday',
|
||||
'GET /api/players/v1/progression/{id}',
|
||||
'GET /api/players/v2/progression/bulk',
|
||||
'GET /api/progressionEvents/active',
|
||||
|
||||
@@ -48,12 +48,13 @@ const app = new Hono<App>()
|
||||
return c.json([])
|
||||
})
|
||||
|
||||
// Sampling configuration, asked for once per session (`?sessionId=<guid>`). An empty
|
||||
// object carries no per-event overrides, which the client reads as "sample everything
|
||||
// at the built-in default rates". Since the events are discarded anyway, the rate it
|
||||
// picks makes no difference here.
|
||||
// Sampling configuration, asked for once per session (`?sessionId=<guid>`). A BARE ARRAY
|
||||
// of `{ EventType, ShouldBeSampled }` entries (the client decodes a `List<EventDTO>`),
|
||||
// e.g. `[{ "EventType": "player_join", "ShouldBeSampled": true }]` — not an object: the
|
||||
// `{}` this once served made the client's decoder throw. Empty says "sample nothing",
|
||||
// which is the right answer since the events are discarded anyway.
|
||||
.get('/sampling', async (c) => {
|
||||
return c.json({})
|
||||
return c.json([])
|
||||
})
|
||||
|
||||
export default app
|
||||
|
||||
@@ -49,7 +49,7 @@ it('acknowledges an unreadable body on both event paths', async () => {
|
||||
it('serves an empty sampling configuration', async () => {
|
||||
const res = await SELF.fetch(`${ORIGIN}/sampling?sessionId=e9a899ce-ce46-447d-bb22-11e82ed68f8d`)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual({})
|
||||
expect(await res.json()).toEqual([])
|
||||
})
|
||||
|
||||
// The client always sends `sessionId`, but nothing here reads it, so a missing one must
|
||||
@@ -57,5 +57,5 @@ it('serves an empty sampling configuration', async () => {
|
||||
it('serves the sampling configuration without a sessionId', async () => {
|
||||
const res = await SELF.fetch(`${ORIGIN}/sampling`)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual({})
|
||||
expect(await res.json()).toEqual([])
|
||||
})
|
||||
|
||||
@@ -96,14 +96,24 @@ const app = new Hono<App>()
|
||||
})
|
||||
})
|
||||
|
||||
// The caller's CRM configuration — the campaign/messaging settings the client fetches on
|
||||
// startup before it will ask about anything else here. An object, not an envelope.
|
||||
// The caller's CRM configuration — which CRM (Braze, Rengage) and push service the
|
||||
// client is assigned, fetched on startup before it will ask about anything else here.
|
||||
//
|
||||
// STUB: there is no CRM behind this, so the config is EMPTY rather than invented. An
|
||||
// empty object is the honest answer and the client reads it as "nothing configured";
|
||||
// made-up keys would switch on messaging paths that have nothing to serve them. Unlike
|
||||
// `/config/categories` there is no visible text to mark as a stub — nothing here is
|
||||
// rendered — so the marker stays in this comment.
|
||||
// ENVELOPED: the client decodes a `RecNetResultObject<CrmAssignmentDTO>` and reads
|
||||
// `Value` off it itself, so the `{ Value, Success, Error, error_id }` wrapper is on the
|
||||
// wire — this once served a bare `{}`, which leaves `Value` null. Note the casing:
|
||||
// PascalCase `Success`/`Error` beside a snake_case `error_id`.
|
||||
//
|
||||
// `Value` has exactly two keys. `UserConfig.AssignedCrmType` is the `CrmType` enum (None
|
||||
// 0, Braze 1, Rengage 2) and `AllowPush` a bool — both NON-nullable on the client, so
|
||||
// never null. `AssignedPushNotificationService` is a nullable `PushNotificationServices`
|
||||
// (APNS 0, Firebase 1, Mock 2, Braze 3, Rengage 4). `BrazeConfig` is `{ AppId,
|
||||
// SdkEndpoint }` or null.
|
||||
//
|
||||
// STUB: there is no CRM behind this, so the answer is "none assigned" — type 0, no push
|
||||
// service, push off, no Braze config — rather than an invented assignment that would
|
||||
// switch on messaging paths with nothing to serve them. Nothing here is rendered, so
|
||||
// the stub marker stays in this comment.
|
||||
//
|
||||
// Auth-gated because the route is `me`-scoped, like `/preferences` below: the config
|
||||
// belongs to the caller even while the answer is the same for everyone.
|
||||
@@ -111,7 +121,19 @@ const app = new Hono<App>()
|
||||
const accountId = await authedId(c)
|
||||
if (accountId === null) return unauthorized(c)
|
||||
|
||||
return c.json({})
|
||||
return c.json({
|
||||
Value: {
|
||||
UserConfig: {
|
||||
AssignedCrmType: 0,
|
||||
AssignedPushNotificationService: null,
|
||||
AllowPush: false,
|
||||
},
|
||||
BrazeConfig: null,
|
||||
},
|
||||
Success: true,
|
||||
Error: null,
|
||||
error_id: null,
|
||||
})
|
||||
})
|
||||
|
||||
// The caller's own notification preferences — which categories they have muted, by
|
||||
|
||||
@@ -123,9 +123,22 @@ it('serves the caller’s CRM config', async () => {
|
||||
const res = await SELF.fetch(`${ORIGIN}/crm/me/config/v3`, { headers: await bearer('205') })
|
||||
expect(res.status).toBe(200)
|
||||
expect(res.headers.get('content-type')).toContain('application/json')
|
||||
// Empty rather than invented: there is no CRM behind this, and made-up keys would switch
|
||||
// on client messaging paths with nothing to serve them.
|
||||
expect(await res.json()).toEqual({})
|
||||
// A RecNetResultObject envelope — the client reads `Value` off it. "No CRM assigned"
|
||||
// rather than invented: there is no CRM behind this. `AssignedCrmType` and `AllowPush`
|
||||
// are non-nullable on the client, so they must never be null.
|
||||
expect(await res.json()).toEqual({
|
||||
Value: {
|
||||
UserConfig: {
|
||||
AssignedCrmType: 0,
|
||||
AssignedPushNotificationService: null,
|
||||
AllowPush: false,
|
||||
},
|
||||
BrazeConfig: null,
|
||||
},
|
||||
Success: true,
|
||||
Error: null,
|
||||
error_id: null,
|
||||
})
|
||||
})
|
||||
|
||||
it('401s the CRM config read without a bearer token', async () => {
|
||||
|
||||
Reference in New Issue
Block a user