desktop reads everything by itself: one background read scheduler (4 per store, starts spaced per account, App Store Connect X-Rate-Limit slows it below 20 % and pauses at 100 left, backoff with jitter on 429 / RESOURCE_EXHAUSTED, visible app first, one read per key), per-method TTLs over the saved reads, Settings auto-read (on) and interval (15 min, paused while hidden), Refresh all, a refresh icon and 'read 3 min ago' / 'couldn't refresh' per card, writes invalidate, status line with progress and quota; quota reaches the window through ToolResult.quota (cli/lib/quota.ts); Health top issues open with versions, sample stack (health --issue / --signature, read health_issue), Open in Play Console, Copy for AI and likely-cause hints (cli/lib/crash-hints.ts); tests/scheduler.test.ts, tests/quota-crash-hints.test.ts, boot check auto-read case
This commit is contained in:
parent
89256f1e80
commit
0fb4ce0001
46 changed files with 1531 additions and 141 deletions
61
tests/quota-crash-hints.test.ts
Normal file
61
tests/quota-crash-hints.test.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// 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();
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue