mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-20 15:48:33 +02:00
Caveat 2 (premium sub had no data source) -> RESOLVED: - src/services/subscriptionService.js: resolves a premium tier by npub. Two sources, both optional (absent => null => unchanged): PREMIUM_NPUBS operator allowlist (npub1/hex[:tier]) works TODAY with zero backend dependency, and an optional SMIRK_PREMIUM_STATUS_URL HTTP endpoint for full automation. Cached. - identityService.entitlementsForUser populates user.subscription_tier from it (by smirk_public_key) before the snapshot, so an active sub unlocks cosmetics. (The Smirk backend's /premium/status is self-only, so a by-npub service lookup is the one remaining backend addition for full automation — documented.) Caveat 1 (client signing was bare scaffold) -> HARDENED: - html/js/smirkChatSign.js now does the Nostr-scope-grant retry (sign first; on NOT_AUTHORIZED, getNostrPublicKey() once + retry), same as SmirkAuth — so a returning user signs with one approval instead of silently falling back. Still feature-detected + graceful. A real Smirk wallet click is the last confirm. Docs updated (MONETIZATION.md, CHAT_AND_NOSTR.md). Tests: +6 (allowlist hex/npub1, HTTP active/inactive+cache, identityService unlock, non-premium unchanged). Suite 57 suites / 413 tests green.
70 lines
3.6 KiB
JavaScript
70 lines
3.6 KiB
JavaScript
/**
|
|
* Premium-subscription -> cosmetic-tier data source (decaveat: entitlements now HAS a source).
|
|
*/
|
|
const SubscriptionService = require('../src/services/subscriptionService');
|
|
const IdentityService = require('../src/network/identityService');
|
|
const { loadNostrTools } = require('../src/utils/nostrLoader');
|
|
|
|
const tools = loadNostrTools();
|
|
const sk = tools.generateSecretKey();
|
|
const pubHex = tools.getPublicKey(sk);
|
|
const npub = tools.nip19.npubEncode(pubHex);
|
|
|
|
describe('SubscriptionService', () => {
|
|
test('no source configured -> null (inert, unchanged behavior)', async () => {
|
|
const svc = new SubscriptionService({ env: {} });
|
|
expect(svc.enabled).toBe(false);
|
|
expect(await svc.tierForNpub(pubHex)).toBeNull();
|
|
});
|
|
|
|
test('PREMIUM_NPUBS allowlist grants the tier — hex and npub1 both match the stored pubkey', async () => {
|
|
const byHex = new SubscriptionService({ env: { PREMIUM_NPUBS: pubHex } });
|
|
expect(await byHex.tierForNpub(pubHex)).toBe('premium'); // default tier
|
|
|
|
const byNpub = new SubscriptionService({ env: { PREMIUM_NPUBS: `${npub}:operator` } });
|
|
expect(await byNpub.tierForNpub(pubHex)).toBe('operator'); // decoded npub1 matches the hex pubkey
|
|
expect(await byNpub.tierForNpub('cc'.repeat(32))).toBeNull(); // someone else
|
|
});
|
|
|
|
test('HTTP source is queried when configured and caches the result', async () => {
|
|
const fetchImpl = jest.fn().mockResolvedValue({ ok: true, json: async () => ({ active: true, tier: 'premium' }) });
|
|
const svc = new SubscriptionService({ env: { SMIRK_PREMIUM_STATUS_URL: 'https://api.example/premium' }, fetchImpl, now: () => 1000 });
|
|
expect(await svc.tierForNpub(pubHex)).toBe('premium');
|
|
expect(await svc.tierForNpub(pubHex)).toBe('premium'); // cached
|
|
expect(fetchImpl).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test('HTTP inactive subscription -> null', async () => {
|
|
const fetchImpl = jest.fn().mockResolvedValue({ ok: true, json: async () => ({ active: false }) });
|
|
const svc = new SubscriptionService({ env: { SMIRK_PREMIUM_STATUS_URL: 'https://api.example/premium' }, fetchImpl });
|
|
expect(await svc.tierForNpub(pubHex)).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('IdentityService wires the subscription tier into entitlements', () => {
|
|
test('an allowlisted npub unlocks premium cosmetics with no purchase', async () => {
|
|
const db = { query: jest.fn().mockResolvedValue({ rows: [] }) }; // no pack grants
|
|
const identity = new IdentityService({
|
|
db,
|
|
catalogService: { getCatalog: async () => undefined }, // -> DEFAULT_CATALOG
|
|
subscriptionService: new SubscriptionService({ env: { PREMIUM_NPUBS: pubHex } })
|
|
});
|
|
const ent = await identity.entitlementsForUser({ id: 7, smirk_public_key: pubHex, total_credits_purchased: 0 });
|
|
expect(ent.level).toBe('premium');
|
|
expect(ent.tier).toBe(2);
|
|
expect(ent.packs['generated-skins']).toBe(true); // tier-1 pack unlocked by the sub
|
|
});
|
|
|
|
test('a non-premium user is unchanged (tier 0)', async () => {
|
|
const db = { query: jest.fn().mockResolvedValue({ rows: [] }) };
|
|
const identity = new IdentityService({
|
|
db,
|
|
catalogService: { getCatalog: async () => undefined },
|
|
subscriptionService: new SubscriptionService({ env: {} })
|
|
});
|
|
const ent = await identity.entitlementsForUser({ id: 8, smirk_public_key: 'dd'.repeat(32), total_credits_purchased: 0 });
|
|
expect(ent.tier).toBe(0);
|
|
expect(ent.packs['generated-skins']).toBe(false);
|
|
});
|
|
});
|