storebound/tests/quota-crash-hints.test.ts

61 lines
4.7 KiB
TypeScript

// Store quota lines for the desktop's read scheduler (cli/lib/quota.ts), crash "likely cause" hints
// (cli/lib/crash-hints.ts) and the sample report parsers of `health --issue / --signature` (cli/lib/health.ts).
import { describe, expect, test } from 'bun:test';
import { crashHint } from '../cli/lib/crash-hints';
import { parseDiagnosticLogs, parseErrorReports, parseIssues, reportsQuery } from '../cli/lib/health';
import { mergeQuota, parseAscRateLimit } from '../cli/lib/quota';
describe('quota', () => {
test('X-Rate-Limit header', () => {
expect(parseAscRateLimit('user-hour-lim:3600;user-hour-rem:3412;')).toEqual({ limit: 3600, remaining: 3412 });
expect(parseAscRateLimit('user-hour-rem:12;user-hour-lim:3500')).toEqual({ limit: 3500, remaining: 12 });
expect(parseAscRateLimit(null)).toBeNull();
expect(parseAscRateLimit('something else')).toBeNull();
});
test('merging the lines of one run keeps the latest count and any throttling', () => {
const a = mergeQuota(undefined, { store: 'apple', limit: 3600, remaining: 3500 });
const b = mergeQuota(a, { store: 'apple', throttled: true });
expect(b).toEqual({ store: 'apple', limit: 3600, remaining: 3500, throttled: true });
expect(mergeQuota(b, { store: 'apple', limit: 3600, remaining: 3490 })).toEqual({ store: 'apple', limit: 3600, remaining: 3490, throttled: true });
});
});
describe('crash hints', () => {
test('Capacitor getPermissionStates NPE', () => {
const h = crashHint('java.lang.NullPointerException', 'com.getcapacitor.Bridge.getPermissionStates');
expect(h?.id).toBe('capacitor-permission-states-npe');
expect(h?.fix).toContain('App.getState()');
expect(crashHint('java.lang.NullPointerException: Attempt to invoke virtual method', null, '\tat com.getcapacitor.Bridge.getPermissionStates(Bridge.java:1234)')?.id).toBe('capacitor-permission-states-npe');
});
test('no hint for other issues', () => {
expect(crashHint('java.lang.NullPointerException', 'com.example.Main.onCreate')).toBeNull();
expect(crashHint('java.lang.IllegalStateException', 'com.getcapacitor.Bridge.getPermissionStates')).toBeNull();
expect(crashHint(undefined, null)).toBeNull();
});
test('the issue list carries the hint and the detail fields', () => {
const [i] = parseIssues({ errorIssues: [{ name: 'apps/com.example/errorIssues/abc123', type: 'CRASH', cause: 'java.lang.NullPointerException', location: 'com.getcapacitor.Bridge.getPermissionStates',
errorReportCount: '9', distinctUsers: '7', firstAppVersion: { versionCode: '20' }, lastAppVersion: { versionCode: '24' }, firstOsVersion: { apiLevel: '29' }, lastOsVersion: { apiLevel: '35' }, lastErrorReportTime: '2026-10-12T10:00:00Z', issueUri: 'https://play.google.com/console/x' }] });
expect(i).toMatchObject({ id: 'abc123', firstVersion: '20', lastVersion: '24', firstOs: '29', lastOs: '35', lastSeen: '2026-10-12T10:00:00Z', uri: 'https://play.google.com/console/x' });
expect(i!.hint?.id).toBe('capacitor-permission-states-npe');
});
});
describe('sample reports', () => {
test('errorReports:search query and answer', () => {
const q = new URLSearchParams(reportsQuery({ year: 2026, month: 10, day: 12 }, 28, 'abc123'));
expect(q.get('filter')).toBe('errorIssueId = abc123');
expect(q.get('pageSize')).toBe('1');
expect(q.get('orderBy')).toBeNull();
expect(() => reportsQuery({ year: 2026, month: 10, day: 12 }, 28, 'a b')).toThrow();
const s = parseErrorReports({ errorReports: [{ reportText: 'java.lang.NullPointerException\n\tat com.getcapacitor.Bridge.getPermissionStates(Bridge.java:1)\n\n', eventTime: '2026-10-12T10:00:00Z', type: 'CRASH',
appVersion: { versionCode: '24' }, osVersion: { apiLevel: '34' }, deviceModel: { marketingName: 'Galaxy A12' } }] });
expect(s).toEqual({ store: 'play', stack: ['java.lang.NullPointerException', '\tat com.getcapacitor.Bridge.getPermissionStates(Bridge.java:1)'], time: '2026-10-12T10:00:00Z', version: '24', os: 'API 34', device: 'Galaxy A12', type: 'CRASH' });
expect(parseErrorReports({})).toBeNull();
});
test('App Store diagnostic logs: the first call stack, top down', () => {
const s = parseDiagnosticLogs({ productData: [{ diagnosticLogs: [{ diagnosticMetaData: { appVersion: '2.3.0', buildVersion: '48', osVersion: 'iOS 18.6', deviceType: 'iPhone15,2' },
callStackTree: [{ callStacks: [{ callStackRootFrames: [{ binaryName: 'App', symbolName: 'main', offsetIntoSymbol: 4, subFrames: [{ binaryName: 'App', symbolName: 'Combo.update' }] }] }] }] }] }] });
expect(s).toEqual({ store: 'apple', stack: ['App main + 4', 'App Combo.update'], version: '2.3.0 (48)', os: 'iOS 18.6', device: 'iPhone15,2' });
expect(parseDiagnosticLogs({ productData: [] })).toBeNull();
});
});