feat(lobby): F8-04b hierarchical sidebar + paid-tier gate for create-game
Reshape the lobby UI from a single Overview into a two-level sidebar (games · profile · DEV synthetic-reports) with four games sub-panels (active-past · recruitment · invitations · private-games). Move the `create new game` button into the private-games panel, merge the applications section into recruitment cards as status chips, and add DEV-only synthetic-report loader as a top-level screen. Add a paid-tier gate at backend `lobby.game.create`: free callers get `403 forbidden` before the lobby service is invoked. The UI hides the private-games sub-panel + create button on free tier (DEV affordances flag overrides). Update every integration test that creates a game to use a new `testenv.PromoteToPaid` helper; add a new `TestLobbyFlow_FreeUserCreateGameForbidden`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
// Unit tests for `decodeAccountView` — F8-04b adds an `entitlement`
|
||||
// projection on the TS Account, sourced from the FBS
|
||||
// `EntitlementSnapshot.is_paid` field. The decode must default to
|
||||
// `false` when the snapshot is absent, never throw on null.
|
||||
|
||||
import { Builder, ByteBuffer } from "flatbuffers";
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import { decodeAccountView } from "../src/api/account";
|
||||
import {
|
||||
AccountView,
|
||||
EntitlementSnapshot,
|
||||
} from "../src/proto/galaxy/fbs/user";
|
||||
|
||||
function buildAccountView(opts: {
|
||||
isPaid?: boolean;
|
||||
includeEntitlement: boolean;
|
||||
}): AccountView {
|
||||
const builder = new Builder(256);
|
||||
const userIdOff = builder.createString("user-1");
|
||||
const emailOff = builder.createString("user@example.com");
|
||||
const userNameOff = builder.createString("Player-1");
|
||||
const displayNameOff = builder.createString("Display");
|
||||
const langOff = builder.createString("en-US");
|
||||
const tzOff = builder.createString("UTC");
|
||||
const countryOff = builder.createString("US");
|
||||
|
||||
let entitlementOff = 0;
|
||||
if (opts.includeEntitlement) {
|
||||
const planOff = builder.createString("free");
|
||||
const sourceOff = builder.createString("default");
|
||||
const reasonOff = builder.createString("init");
|
||||
EntitlementSnapshot.startEntitlementSnapshot(builder);
|
||||
EntitlementSnapshot.addPlanCode(builder, planOff);
|
||||
EntitlementSnapshot.addIsPaid(builder, opts.isPaid ?? false);
|
||||
EntitlementSnapshot.addSource(builder, sourceOff);
|
||||
EntitlementSnapshot.addReasonCode(builder, reasonOff);
|
||||
entitlementOff = EntitlementSnapshot.endEntitlementSnapshot(builder);
|
||||
}
|
||||
|
||||
AccountView.startAccountView(builder);
|
||||
AccountView.addUserId(builder, userIdOff);
|
||||
AccountView.addEmail(builder, emailOff);
|
||||
AccountView.addUserName(builder, userNameOff);
|
||||
AccountView.addDisplayName(builder, displayNameOff);
|
||||
AccountView.addPreferredLanguage(builder, langOff);
|
||||
AccountView.addTimeZone(builder, tzOff);
|
||||
AccountView.addDeclaredCountry(builder, countryOff);
|
||||
if (entitlementOff !== 0) {
|
||||
AccountView.addEntitlement(builder, entitlementOff);
|
||||
}
|
||||
const viewOff = AccountView.endAccountView(builder);
|
||||
builder.finish(viewOff);
|
||||
|
||||
return AccountView.getRootAsAccountView(new ByteBuffer(builder.asUint8Array()));
|
||||
}
|
||||
|
||||
describe("decodeAccountView", () => {
|
||||
test("extracts entitlement.isPaid=true from FBS EntitlementSnapshot", () => {
|
||||
const view = buildAccountView({ includeEntitlement: true, isPaid: true });
|
||||
const account = decodeAccountView(view);
|
||||
expect(account.entitlement.isPaid).toBe(true);
|
||||
});
|
||||
|
||||
test("extracts entitlement.isPaid=false from FBS EntitlementSnapshot", () => {
|
||||
const view = buildAccountView({ includeEntitlement: true, isPaid: false });
|
||||
const account = decodeAccountView(view);
|
||||
expect(account.entitlement.isPaid).toBe(false);
|
||||
});
|
||||
|
||||
test("defaults entitlement.isPaid to false when snapshot is absent", () => {
|
||||
const view = buildAccountView({ includeEntitlement: false });
|
||||
const account = decodeAccountView(view);
|
||||
expect(account.entitlement.isPaid).toBe(false);
|
||||
});
|
||||
|
||||
test("populates other Account fields verbatim", () => {
|
||||
const view = buildAccountView({ includeEntitlement: true, isPaid: true });
|
||||
const account = decodeAccountView(view);
|
||||
expect(account.userId).toBe("user-1");
|
||||
expect(account.email).toBe("user@example.com");
|
||||
expect(account.userName).toBe("Player-1");
|
||||
expect(account.displayName).toBe("Display");
|
||||
expect(account.preferredLanguage).toBe("en-US");
|
||||
expect(account.timeZone).toBe("UTC");
|
||||
expect(account.declaredCountry).toBe("US");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user