Files
wownerogue/test/identityService.test.js
jwinterm 1b59d2f927 feat(catalog): graduated cosmetic ladder + per-session test-unlock
Step 1 — real tiered unlocks (was flat: any credit unlocked skins+iso+3D):
- entitlements DEFAULT_CATALOG + migration 025 graduate the ladder — unlock by
  lifetime credit spend OR subscription tier, whichever first:
    Fancy (generated-skins)       1 credit  / supporter (tier 1)
    Iso   (iso-dungeon)          10 credits / premium   (tier 2)
    3D    (kenney-3d-characters) 25 credits / operator  (tier 3)
- Migration 025 UPDATEs the already-seeded 024 rows (idempotent).
- Tests updated to assert the ladder (1cr -> Fancy only; 10 -> +Iso; 25 -> all).

Step 2 — test the premium modes without paying:
- renderModes: ?unlock=1 (sticky via localStorage) flips RENDER_MODE_TEST_UNLOCKS
  for THAT browser only, so Fancy/Iso/3D are all clickable; ?unlock=0 turns it off.

Suite 57 suites / 414 tests green.
2026-07-11 07:24:26 -04:00

103 lines
4.2 KiB
JavaScript

const IdentityService = require('../src/network/identityService');
function makeSocket(id = 'sockA') {
return { id, emit: jest.fn() };
}
function makeDb({ user, grants = [] } = {}) {
const state = {
user: {
id: 1,
socket_id: 'sockA',
credits: 0,
total_credits_purchased: 0,
premium_level: 'free',
appearance: {
avatar: 'char-ranger',
tint: 'teal',
equipment: { body: 'mail', head: 'hood', shield: 'round', weapon: 'bow' },
colors: { base: 'teal', skin: 'warm', hair: 'silver', body: 'gold', head: 'none', shield: 'rose', weapon: 'none' }
},
...user
},
grants
};
const db = {
state,
query: jest.fn(async (text, params = []) => {
if (/SELECT pack_id/i.test(text)) return { rows: state.grants };
if (/UPDATE users[\s\S]*SET appearance/i.test(text)) {
state.user = { ...state.user, appearance: JSON.parse(params[0]) };
return { rows: [state.user] };
}
return { rows: [] };
})
};
return db;
}
describe('IdentityService', () => {
test('loads stored appearance and entitlements for a socket user', async () => {
const db = makeDb({ user: { total_credits_purchased: 5, credits: 2 } });
const sessionManager = { sessions: new Map([['sockA', db.state.user]]) };
const gameModeManager = { getOrCreateUser: jest.fn(async () => db.state.user), db };
const svc = new IdentityService({ db, gameModeManager, sessionManager });
const snapshot = await svc.identityForSocket(makeSocket());
expect(snapshot.appearance).toEqual(db.state.user.appearance);
expect(snapshot.entitlements.premium).toBe(true);
expect(snapshot.entitlements.packs['generated-skins']).toBe(true); // 5 credits >= threshold 1
expect(snapshot.entitlements.packs['iso-dungeon']).toBe(false); // < threshold 10
});
test('saves normalized appearance and refreshes the session cache', async () => {
const db = makeDb({ user: { total_credits_purchased: 0 } });
const sessionManager = { sessions: new Map([['sockA', db.state.user]]) };
const gameModeManager = { getOrCreateUser: jest.fn(async () => db.state.user), db };
const svc = new IdentityService({ db, gameModeManager, sessionManager });
const snapshot = await svc.saveAppearanceForSocket(makeSocket(), {
avatar: 'char-ranger',
tint: 'bad',
equipment: { body: 'mail', head: 'bad', shield: 'round', weapon: 'laser' },
colors: { base: 'violet', skin: 'bad', hair: 'black', body: 'gold', head: 'bad', shield: 'rose', weapon: 'bad' }
});
expect(snapshot.appearance).toEqual({
avatar: 'char-ranger',
tint: 'violet',
equipment: { body: 'mail', head: 'none', shield: 'round', weapon: 'none' },
colors: { base: 'violet', skin: 'natural', hair: 'black', body: 'gold', head: 'none', shield: 'rose', weapon: 'none' }
});
expect(db.state.user.appearance).toEqual(snapshot.appearance);
expect(sessionManager.sessions.get('sockA').appearance).toEqual(snapshot.appearance);
});
test('falls back unauthorized premium appearance before saving', async () => {
const db = makeDb({ user: { total_credits_purchased: 0 } });
const gameModeManager = { getOrCreateUser: jest.fn(async () => db.state.user), db };
const svc = new IdentityService({ db, gameModeManager });
const snapshot = await svc.saveAppearanceForSocket(makeSocket(), { avatar: 'monero-knight' });
expect(snapshot.appearance.avatar).toBe('default');
expect(db.state.user.appearance.avatar).toBe('default');
});
test('allows an explicitly granted pack appearance', async () => {
const db = makeDb({
user: { total_credits_purchased: 0 },
grants: [{ pack_id: 'generated-skins' }]
});
const gameModeManager = { getOrCreateUser: jest.fn(async () => db.state.user), db };
const svc = new IdentityService({ db, gameModeManager });
const snapshot = await svc.saveAppearanceForSocket(makeSocket(), { avatar: 'monero-knight' });
expect(snapshot.appearance.avatar).toBe('monero-knight');
expect(snapshot.entitlements.packs['generated-skins']).toBe(true);
expect(snapshot.entitlements.packs['kenney-3d-characters']).toBe(false);
});
});