import 'dart:io'; import 'package:crypto/crypto.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:ikar/core/utils/update_checker.dart'; import 'package:ikar/core/utils/update_installer.dart'; void main() { const qlyra = AppUpdateArtifact( name: 'qlyra.apk', url: 'https://example.test/qlyra.apk', flavor: 'ikar', abi: 'universal', sizeBytes: 100, sha256: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', ); const onemeArm64 = AppUpdateArtifact( name: 'oneme-arm64.apk', url: 'https://example.test/oneme-arm64.apk', flavor: 'oneme', abi: 'arm64-v8a', sizeBytes: 100, sha256: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', ); const onemeUniversal = AppUpdateArtifact( name: 'oneme.apk', url: 'https://example.test/oneme.apk', flavor: 'oneme', abi: 'universal', sizeBytes: 100, sha256: 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', ); test('maps package identities to independent Argus slugs', () { expect(UpdateChecker.manifestSlugForPackageName('ru.ikar.app'), 'ikar'); expect(UpdateChecker.manifestSlugForPackageName('ru.oneme.app'), 'oneme'); }); test('selects only an artifact for the installed flavor', () { expect( UpdateInstaller.selectArtifact( [qlyra, onemeArm64, onemeUniversal], 'ru.ikar.app', ['arm64-v8a'], ), same(qlyra), ); expect( UpdateInstaller.selectArtifact( [qlyra, onemeArm64, onemeUniversal], 'ru.oneme.app', ['arm64-v8a'], ), same(onemeArm64), ); }); test('falls back to a universal artifact of the same flavor', () { expect( UpdateInstaller.selectArtifact( [qlyra, onemeUniversal], 'ru.oneme.app', ['x86_64'], ), same(onemeUniversal), ); }); test('does not cross-install another flavor', () { expect( UpdateInstaller.selectArtifact([qlyra], 'ru.oneme.app', ['arm64-v8a']), isNull, ); }); test('verifies artifact size and SHA-256', () async { final directory = await Directory.systemTemp.createTemp('qlyra-update-'); addTearDown(() => directory.delete(recursive: true)); final file = File('${directory.path}/update.apk'); final bytes = [1, 2, 3, 4]; await file.writeAsBytes(bytes); final artifact = AppUpdateArtifact( name: 'update.apk', url: 'https://example.test/update.apk', flavor: 'ikar', abi: 'universal', sizeBytes: bytes.length, sha256: sha256.convert(bytes).toString(), ); await expectLater( UpdateInstaller.verifyArtifact(file, artifact), completes, ); }); test('rejects an artifact with a different digest', () async { final directory = await Directory.systemTemp.createTemp('qlyra-update-'); addTearDown(() => directory.delete(recursive: true)); final file = File('${directory.path}/update.apk'); await file.writeAsBytes([1, 2, 3, 4]); const artifact = AppUpdateArtifact( name: 'update.apk', url: 'https://example.test/update.apk', flavor: 'ikar', abi: 'universal', sizeBytes: 4, sha256: 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', ); await expectLater( UpdateInstaller.verifyArtifact(file, artifact), throwsA(isA()), ); }); }