import { EventEmitter } from 'node:events'; import type { IncomingMessage, ServerResponse } from 'node:http'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { handleWorksBillingRoutes, projectBillingBalance, projectBillingSummary, projectPointHistory, projectRechargeOrder } from '@electron/api/routes/works-billing'; import { getRendererCapability, RENDERER_CAPABILITY_HEADER, rotateRendererCapability } from '@electron/api/renderer-capability'; import { billingSummary, permanentBalance, rechargeOrder } from '../fixtures/permanent-points'; const net = vi.hoisted(() => vi.fn()); const token = vi.hoisted(() => vi.fn()); const current = vi.hoisted(() => vi.fn()); vi.mock('@electron/utils/proxy-fetch', () => ({ proxyAwareFetch: net })); vi.mock('@electron/services/works-square-session', () => ({ getValidWorksSquareAccessToken: token, getWorksSquareAccountBinding: () => ({ accountKey: 'main-account', epoch: 1 }), isCurrentWorksSquareAccountBinding: current, })); async function call(path: string, method = 'GET', body?: unknown, capability = true) { const req = Object.assign(new EventEmitter(), { method, headers: capability ? { [RENDERER_CAPABILITY_HEADER]: getRendererCapability() } : {}, async *[Symbol.asyncIterator]() { if (body !== undefined) yield Buffer.from(JSON.stringify(body)); }, }) as IncomingMessage; let responseBody = ''; const res = { statusCode: 0, setHeader: vi.fn(), end: (value: string) => { responseBody = value; } } as unknown as ServerResponse; await handleWorksBillingRoutes(req, res, new URL(`http://localhost/api/works/billing${path}`)); return { status: res.statusCode, body: JSON.parse(responseBody) }; } function reply(value: unknown, status = 200) { net.mockResolvedValueOnce(new Response(JSON.stringify(value), { status })); } beforeEach(() => { net.mockReset(); token.mockReset().mockResolvedValue('main-only-secret'); current.mockReset().mockReturnValue(true); rotateRendererCapability(); }); describe('permanent points Main contract', () => { it('keeps exact own youth balance and strips legacy/internal fields', () => { expect(projectBillingBalance({ ...permanentBalance({ can_recharge: false }), plan_name: 'old', wallet_owner_id: 'private' })).toEqual(permanentBalance({ can_recharge: false })); }); it('redacts exact shared values even when upstream accidentally includes them', () => { expect(projectBillingBalance(permanentBalance({ family_shared: true, entitlement_source: 'shared_group', shared_available: false }))).toEqual({ total: null, used: null, reserved: null, total_remaining: null, entitlement_source: 'shared_group', family_shared: true, shared_available: false, can_recharge: false, expires_at: null }); }); it('rejects an old or expiring balance instead of displaying a made-up zero', () => { expect(() => projectBillingBalance({ plan_code: 'experience', total_remaining: '10.00' })).toThrow(); expect(() => projectBillingBalance({ ...permanentBalance(), expires_at: '2099-01-01' })).toThrow(); }); it.each(['self', 'shared_group'] as const)('keeps the payer’s own exact balance with shared funding source %s', (source) => { const owner = permanentBalance({ entitlement_source: source, family_shared: true, shared_available: null }); expect(projectBillingBalance(owner)).toEqual(owner); }); it('checks the fixed recharge ratio and accepts a separate own wallet during funding failure', () => { const summary = billingSummary({ points: null, funding_error: 'private upstream trace' }); expect(projectBillingSummary(summary).own_points.total_remaining).toBe('75.00'); expect(projectBillingSummary(summary).funding_error).not.toContain('trace'); expect(() => projectBillingSummary({ ...summary, recharge_products: [{ ...summary.recharge_products[0], point_amount: '600.00' }] })).toThrow(); }); it('freezes order amount, discards payment secrets and never calls a quarantined payment credited', () => { const payload = { ...rechargeOrder({ status: 'succeeded', manual_review_required: true }), provider_payload: { codeUrl: 'weixin://wxpay/test', paySign: 'private' }, failure_reason: 'private error' }; expect(projectRechargeOrder(payload)).toMatchObject({ status: 'manual_review', qr_payload: null, amount_cents: 1000, point_amount: '500.00' }); expect(projectRechargeOrder(payload)).not.toHaveProperty('provider_payload'); expect(projectRechargeOrder(rechargeOrder(), { codeUrl: 'javascript:alert(1)' }).qr_payload).toBeNull(); }); it('preserves negative usage and held amounts in ledger history', () => { const item = { id: 'usage', kind: 'usage', description: 'chat', status: 'settled', points: '-1.25', reserved_points: '0.00', created_at: '2026-09-22T00:00:00Z' }; expect(projectPointHistory({ items: [item], has_more: true })).toEqual({ items: [item], has_more: true }); }); it('gets the current Main credential and does not require or forward Renderer credentials', async () => { reply(billingSummary()); const result = await call('/me'); expect(result.status).toBe(200); expect(result.body.data).toEqual(billingSummary()); expect(net.mock.calls[0][0]).toBe('https://square.nianxx.cn/api/billing/me'); expect(net.mock.calls[0][1].headers).toEqual({ Authorization: 'Bearer main-only-secret' }); }); it('uses the same idempotency header for an explicit retry and sends only product identity', async () => { const response = { order: rechargeOrder(), provider_payload: { codeUrl: 'weixin://wxpay/test', paySign: 'secret' } }; reply(response); reply(response); for (let i = 0; i < 2; i++) expect((await call('/recharge/orders', 'POST', { productId: 'recharge-10', requestId: 'same-intent', ownerId: 'forged', amount: 1 })).status).toBe(200); expect(net.mock.calls.map(([, init]) => init.headers['Idempotency-Key'])).toEqual(['makelore:same-intent', 'makelore:same-intent']); expect(JSON.parse(net.mock.calls[0][1].body)).toEqual({ product_id: 'recharge-10' }); }); it('never issues recharge without the existing Renderer capability', async () => { expect((await call('/recharge/orders', 'POST', { productId: 'p', requestId: 'r' }, false)).status).toBe(403); expect(net).not.toHaveBeenCalled(); }); it('bounds page size and forwards exact offset without payer input', async () => { reply({ items: [], has_more: false }); expect((await call('/points/transactions?offset=20&limit=999&owner=other')).status).toBe(200); expect(net.mock.calls[0][0]).toBe('https://square.nianxx.cn/api/billing/points/transactions?limit=20&offset=20'); }); it('distinguishes definitive provider rejection from an unknown network result', async () => { reply({ detail: { outcome: 'definitive_rejection', message: 'secret trace' } }, 502); expect((await call('/recharge/orders', 'POST', { productId: 'p', requestId: 'r' })).body).toMatchObject({ commandOutcome: 'definitive_failure' }); net.mockRejectedValueOnce(new Error('secret trace')); const unknown = await call('/recharge/orders', 'POST', { productId: 'p', requestId: 'r' }); expect(unknown.body.commandOutcome).toBe('unknown'); expect(JSON.stringify(unknown)).not.toContain('secret trace'); }); it('blocks account changes before dispatch and discards a late response after switch', async () => { current.mockReturnValue(false); expect((await call('/me')).status).toBe(401); expect(net).not.toHaveBeenCalled(); current.mockReturnValueOnce(true).mockReturnValueOnce(false); reply(billingSummary()); const result = await call('/me'); expect(result.status).toBe(409); expect(result.body.data).toBeUndefined(); }); it.each(['/plans', '/reset-cards', '/subscriptions', '/reset-card-orders'])('retires %s without contacting payment services', async (path) => { expect((await call(path)).status).toBe(410); expect(net).not.toHaveBeenCalled(); }); });