Files
native/src/lib/__tests__/install-update.test.ts
T
Linus Rath 23af324d79 fix: verify app updates without a second slow progress bar
Since releases ship an `.apk.sha256` file, every in-app update checks
the downloaded APK against it. The check read the 40 MB file in base64
chunks and hashed it in JS, which took longer than the download, and it
reset the progress bar to 0 under the "Downloading" label, so the update
looked like it downloaded twice, the second time slowly.

The file is now read and hashed natively through expo-crypto (the JS
hasher remains as a fallback), and the bar stays full under a
"Verifying…" label while the check runs.
2026-10-02 15:02:10 +02:00

50 lines
1.7 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { createHash } from 'node:crypto';
const files = new Map<string, Uint8Array>();
const digest = vi.fn();
vi.mock('react-native', () => ({ Platform: { OS: 'android' } }));
vi.mock('expo-file-system', () => ({
File: class {
constructor(private uri: string) {}
async bytes() { return files.get(this.uri) ?? new Uint8Array(); }
},
Paths: { cache: 'file:///cache' },
}));
vi.mock('expo-file-system/legacy', () => ({}));
vi.mock('expo-intent-launcher', () => ({}));
vi.mock('expo-crypto', () => ({
CryptoDigestAlgorithm: { SHA256: 'SHA-256' },
digest: (...args: unknown[]) => digest(...args),
}));
import { hashFile } from '../install-update';
const sha256Hex = (bytes: Uint8Array) => createHash('sha256').update(bytes).digest('hex');
describe('hashFile', () => {
const apk = new TextEncoder().encode('not really an apk');
beforeEach(() => {
files.clear();
files.set('file:///cache/app.apk', apk);
digest.mockReset();
});
it('hashes the file natively in one call', async () => {
digest.mockImplementation(async (_alg: string, data: Uint8Array) => {
const out = createHash('sha256').update(data).digest();
return out.buffer.slice(out.byteOffset, out.byteOffset + out.byteLength);
});
expect(await hashFile('file:///cache/app.apk')).toBe(sha256Hex(apk));
expect(digest).toHaveBeenCalledTimes(1);
expect(digest).toHaveBeenCalledWith('SHA-256', apk);
});
it('falls back to the JS hasher when the native digest fails', async () => {
digest.mockRejectedValue(new Error('unavailable'));
expect(await hashFile('file:///cache/app.apk')).toBe(sha256Hex(apk));
});
});