diff --git a/apps/aixue/assets/config/bootstrap.json b/apps/aixue/assets/config/bootstrap.json index 4032efd..ac4db1e 100644 --- a/apps/aixue/assets/config/bootstrap.json +++ b/apps/aixue/assets/config/bootstrap.json @@ -3,5 +3,6 @@ "preferredOrientations": [ "portraitUp", "portraitDown" - ] + ], + "upgradeConfigUrl": "https://umsapi.23544.com/api/biz/version/get-latest-config?appId=787154824761413&environment=1" } diff --git a/flavors/aixue.yaml b/flavors/aixue.yaml index ffae98e..3fb89a0 100644 --- a/flavors/aixue.yaml +++ b/flavors/aixue.yaml @@ -3,7 +3,7 @@ application_id: "com.yuanxuan.aixue" app_key: "aixue_prod" default_url: "http://xszy.lzzneng.com/login.html" bootstrap_config_url: "" -upgrade_config_url: "" +upgrade_config_url: "https://umsapi.23544.com/api/biz/version/get-latest-config?appId=787154824761413&environment=1" preferred_orientations: - "portraitUp" - "portraitDown" diff --git a/packages/web_shell_core/lib/core_app.dart b/packages/web_shell_core/lib/core_app.dart index 4b43e9b..d677457 100644 --- a/packages/web_shell_core/lib/core_app.dart +++ b/packages/web_shell_core/lib/core_app.dart @@ -18,6 +18,7 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter_android/webview_flutter_android.dart'; +import 'package:yx_app_upgrade_flutter/app_upgrade_plugin.dart'; import 'package:yx_app_upgrade_flutter/yx_app_upgrade_flutter.dart'; export 'package:yx_app_upgrade_flutter/yx_app_upgrade_flutter.dart' diff --git a/packages/web_shell_core/lib/src/services/upgrade_service.dart b/packages/web_shell_core/lib/src/services/upgrade_service.dart index effd54e..f232801 100644 --- a/packages/web_shell_core/lib/src/services/upgrade_service.dart +++ b/packages/web_shell_core/lib/src/services/upgrade_service.dart @@ -1,7 +1,7 @@ part of '../../core_app.dart'; -/// 远程升级配置模型。 -class ShellUpgradeConfig { +/// 升级明细配置,对应升级接口中的 `upgrade` 字段。 +class ShellUpgradeReleaseConfig { final String? versionName; final int? version; final int? isForce; @@ -9,7 +9,7 @@ class ShellUpgradeConfig { final String? filePath; final int? fileSize; - ShellUpgradeConfig({ + const ShellUpgradeReleaseConfig({ this.versionName, this.version, this.isForce, @@ -18,24 +18,241 @@ class ShellUpgradeConfig { this.fileSize, }); + factory ShellUpgradeReleaseConfig.fromJson(Map json) { + return ShellUpgradeReleaseConfig( + versionName: _readShellUpgradeString(json['versionName']), + version: _readShellUpgradeInt(json['version']), + isForce: _readShellUpgradeInt(json['isForce']), + remark: _readShellUpgradeString(json['remark']), + filePath: _readShellUpgradeString(json['filePath']), + fileSize: _readShellUpgradeInt(json['fileSize']), + ); + } + + static ShellUpgradeReleaseConfig? fromDynamic(dynamic value) { + if (value == null) { + return null; + } + if (value is Map) { + return ShellUpgradeReleaseConfig.fromJson(value); + } + if (value is Map) { + return ShellUpgradeReleaseConfig.fromJson( + Map.from(value), + ); + } + if (value is! String) { + return null; + } + + final trimmed = value.trim(); + if (trimmed.isEmpty) { + return null; + } + + try { + final decoded = jsonDecode(trimmed); + if (decoded is Map) { + return ShellUpgradeReleaseConfig.fromJson(decoded); + } + if (decoded is Map) { + return ShellUpgradeReleaseConfig.fromJson( + Map.from(decoded), + ); + } + } catch (e) { + debugPrint('解析 WebShell 升级配置 upgrade 字段异常: $e'); + } + return null; + } +} + +/// 远程升级配置模型,对应顶层升级 JSON。 +class ShellUpgradeConfig { + final bool? success; + final String? responseCode; + final String? msg; + final int? id; + final int? upgradeFileId; + final int? installFileId; + final bool? isLatest; + final int? appId; + final String? releaseCode; + final String? releaseTime; + final bool? releaseStatus; + final String? upgradeOssUrl; + final bool? isAllowRollback; + final String? rawConfig; + final String? configDescribe; + final int? describeType; + final int? environment; + final String? createdTime; + final ShellUpgradeReleaseConfig? upgrade; + final ShellBootstrapConfig? config; + final String? installOssUrl; + final String? describe; + + const ShellUpgradeConfig({ + this.success, + this.responseCode, + this.msg, + this.id, + this.upgradeFileId, + this.installFileId, + this.isLatest, + this.appId, + this.releaseCode, + this.releaseTime, + this.releaseStatus, + this.upgradeOssUrl, + this.isAllowRollback, + this.rawConfig, + this.configDescribe, + this.describe, + this.describeType, + this.environment, + this.createdTime, + this.installOssUrl, + this.upgrade, + this.config, + }); + + String? get versionName => upgrade?.versionName; + + int? get version => upgrade?.version; + + int? get isForce => upgrade?.isForce; + + String? get remark => describe ?? upgrade?.remark; + + String? get filePath => + _readShellUpgradeString(installOssUrl ?? upgrade?.filePath); + + int? get fileSize => upgrade?.fileSize; + + bool get hasStructuredPayload => upgrade != null || config != null; + + bool shouldOfferUpgrade({required int localVersion}) { + final remoteVersion = upgrade?.version; + if (remoteVersion == null) { + return false; + } + return remoteVersion > localVersion; + } + factory ShellUpgradeConfig.fromJson(Map json) { + final data = json['data']; + final dataMap = data is Map + ? data + : data is Map + ? Map.from(data) + : const {}; + final rawConfig = _readShellUpgradeString(dataMap['config']); + final parsedConfig = _readShellUpgradeEmbeddedConfig(rawConfig); + final rawBootstrapConfig = parsedConfig?['config']; + final bootstrapConfig = rawBootstrapConfig is Map + ? ShellBootstrapConfig.fromJson(rawBootstrapConfig) + : rawBootstrapConfig is Map + ? ShellBootstrapConfig.fromJson( + Map.from(rawBootstrapConfig), + ) + : null; + return ShellUpgradeConfig( - versionName: json['versionName']?.toString(), - version: json['version'] as int?, - isForce: (json['isForce'] ?? json['isforce']) as int?, - remark: json['remark']?.toString(), - filePath: json['filePath']?.toString(), - fileSize: json['fileSize'] as int?, + success: _readShellUpgradeBool(json['success']), + responseCode: _readShellUpgradeString(json['code']), + msg: _readShellUpgradeString(json['msg']), + id: _readShellUpgradeInt(dataMap['id']), + upgradeFileId: _readShellUpgradeInt(dataMap['upgradeFileId']), + installFileId: _readShellUpgradeInt(dataMap['installFileId']), + isLatest: _readShellUpgradeBool(dataMap['isLatest']), + appId: _readShellUpgradeInt(dataMap['appId']), + releaseCode: _readShellUpgradeString(dataMap['code']), + releaseTime: _readShellUpgradeString(dataMap['releaseTime']), + releaseStatus: _readShellUpgradeBool(dataMap['releaseStatus']), + upgradeOssUrl: _readShellUpgradeString(dataMap['upgradeOssUrl']), + installOssUrl: _readShellUpgradeString(dataMap['installOssUrl']), + isAllowRollback: _readShellUpgradeBool(dataMap['isAllowRollback']), + rawConfig: rawConfig, + upgrade: ShellUpgradeReleaseConfig.fromDynamic(parsedConfig?['upgrade']), + config: bootstrapConfig, + configDescribe: _readShellUpgradeString(dataMap['configDescribe']), + describe: _readShellUpgradeString(dataMap['describe']), + describeType: _readShellUpgradeInt(dataMap['describeType']), + environment: _readShellUpgradeInt(dataMap['environment']), + createdTime: _readShellUpgradeString(dataMap['createdTime']), ); } } -/// 管理壳应用的升级逻辑,依赖 `yx_app_upgrade_flutter`。 +String? _readShellUpgradeString(dynamic value) { + final normalized = value?.toString().trim(); + if (normalized == null || normalized.isEmpty) { + return null; + } + return normalized; +} + +int? _readShellUpgradeInt(dynamic value) { + if (value is int) { + return value; + } + if (value is num) { + return value.toInt(); + } + final normalized = value?.toString().trim(); + if (normalized == null || normalized.isEmpty) { + return null; + } + return int.tryParse(normalized); +} + +bool? _readShellUpgradeBool(dynamic value) { + if (value is bool) { + return value; + } + if (value is num) { + return value != 0; + } + final normalized = value?.toString().trim().toLowerCase(); + if (normalized == null || normalized.isEmpty) { + return null; + } + if (normalized == 'true' || normalized == '1') { + return true; + } + if (normalized == 'false' || normalized == '0') { + return false; + } + return null; +} + +Map? _readShellUpgradeEmbeddedConfig(String? value) { + if (value == null || value.isEmpty) { + return null; + } + + try { + final decoded = jsonDecode(value); + if (decoded is Map) { + return decoded; + } + if (decoded is Map) { + return Map.from(decoded); + } + } catch (e) { + debugPrint('解析 WebShell 升级配置 data.config 异常: $e'); + } + return null; +} + +/// 管理壳应用的升级逻辑,依赖 yx_app_upgrade_flutter。 class ShellUpgradeService { static final ShellUpgradeService instance = ShellUpgradeService._(); ShellUpgradeService._(); String? _configUrl; + Future Function()? _localBuildNumberResolver; /// 注入升级配置地址。 void setupConfigUrl(String? configUrl) { @@ -63,7 +280,8 @@ class ShellUpgradeService { return; } - UpgradeAuxiliaryUtils.instance.initiateVersionCheck( // coverage:ignore-line + UpgradeAuxiliaryUtils.instance.initiateVersionCheck( + // coverage:ignore-line context, showNoUpdateToast: showNoUpdateToast, future: _createVersionResolver(remoteConfig), // coverage:ignore-line @@ -74,10 +292,46 @@ class ShellUpgradeService { ShellUpgradeConfig remoteConfig, ) { return (int upType) async { - return _convertToAppUpgradeVersion(remoteConfig); + final upgradeVersion = _convertToAppUpgradeVersion(remoteConfig); + if (upgradeVersion == null) { + return null; + } + + final localBuildNumber = await _resolveLocalBuildNumber(); + if (localBuildNumber == null) { + debugPrint('获取 WebShell 本地版本号失败,回退到升级插件内部版本比较'); + return upgradeVersion; + } + if (!remoteConfig.shouldOfferUpgrade(localVersion: localBuildNumber)) { + debugPrint( + 'WebShell 远端版本(${remoteConfig.version}) <= 本地版本($localBuildNumber),跳过升级提示', + ); + return null; + } + return upgradeVersion; }; } + Future _resolveLocalBuildNumber() async { + final resolver = _localBuildNumberResolver; + if (resolver != null) { + return resolver(); + } + + try { + final appInfo = await AppUpgradePlugin().getAppInfo(); + return int.tryParse((appInfo['buildNumber'] ?? '').trim()); + } catch (e) { + debugPrint('获取 WebShell 本地版本号异常: $e'); + return null; + } + } + + @visibleForTesting + void debugSetLocalBuildNumberResolver(Future Function()? resolver) { + _localBuildNumberResolver = resolver; + } + Future _fetchConfig(String url) async { try { final uri = Uri.tryParse(url); @@ -102,10 +356,14 @@ class ShellUpgradeService { try { final dynamic jsonMap = jsonDecode(content); if (jsonMap is Map) { - final data = jsonMap['data'] is Map - ? jsonMap['data'] as Map - : jsonMap; - return ShellUpgradeConfig.fromJson(data); + final config = ShellUpgradeConfig.fromJson(jsonMap); + return config.hasStructuredPayload ? config : null; + } + if (jsonMap is Map) { + final config = ShellUpgradeConfig.fromJson( + Map.from(jsonMap), + ); + return config.hasStructuredPayload ? config : null; } } catch (e) { debugPrint('解析 WebShell 升级配置异常: $e'); diff --git a/packages/web_shell_core/lib/src/testing/test_hooks.dart b/packages/web_shell_core/lib/src/testing/test_hooks.dart index d5368ef..1067c77 100644 --- a/packages/web_shell_core/lib/src/testing/test_hooks.dart +++ b/packages/web_shell_core/lib/src/testing/test_hooks.dart @@ -76,6 +76,11 @@ class ShellCoreTestHooks { return ShellUpgradeService.instance._createVersionResolver(config)(upType); } + /// 为测试注入本地构建号解析逻辑。 + void setLocalBuildNumberResolver(Future Function()? resolver) { + ShellUpgradeService.instance.debugSetLocalBuildNumberResolver(resolver); + } + /// 为测试设置升级配置地址。 void setupUpgradeConfigUrl(String? url) { ShellUpgradeService.instance.setupConfigUrl(url); diff --git a/packages/web_shell_core/test/web_shell_core_test.dart b/packages/web_shell_core/test/web_shell_core_test.dart index 269d99a..271e59a 100644 --- a/packages/web_shell_core/test/web_shell_core_test.dart +++ b/packages/web_shell_core/test/web_shell_core_test.dart @@ -47,8 +47,9 @@ Future _startJsonServer( ); request.response.write(body); } catch (error) { - request.response.statusCode = - error is int ? error : HttpStatus.internalServerError; + request.response.statusCode = error is int + ? error + : HttpStatus.internalServerError; } finally { await request.response.close(); } @@ -1254,7 +1255,10 @@ void main() { ); expect(config, isNotNull); - expect(config!.bootstrapConfigUrl, ' https://example.com/bootstrap.json '); + expect( + config!.bootstrapConfigUrl, + ' https://example.com/bootstrap.json ', + ); expect(config.upgradeConfigUrl, ' https://example.com/upgrade.json '); expect( config.preferredOrientations, @@ -1267,15 +1271,19 @@ void main() { test('启动配置解析支持空数组、非法对象与非法 JSON', () { expect( - shellCoreTestHooks.parseBootstrapConfigString( - '{"preferredOrientations":[]}', - )!.preferredOrientations, + shellCoreTestHooks + .parseBootstrapConfigString( + '{"preferredOrientations":[]}', + )! + .preferredOrientations, [], ); expect( - shellCoreTestHooks.parseBootstrapConfigString( - '{"preferredOrientations":"portraitUp"}', - )!.preferredOrientations, + shellCoreTestHooks + .parseBootstrapConfigString( + '{"preferredOrientations":"portraitUp"}', + )! + .preferredOrientations, isNull, ); expect(shellCoreTestHooks.parseBootstrapConfigString('['), isNull); @@ -1319,9 +1327,12 @@ void main() { await unavailableSocket.close(); final successUri = await _startJsonServer( - (_) async => '{"data":{"initialUrl":"https://remote.example.com","upgradeConfigUrl":"https://remote.example.com/upgrade.json"}}', + (_) async => + '{"data":{"initialUrl":"https://remote.example.com","upgradeConfigUrl":"https://remote.example.com/upgrade.json"}}', + ); + final success = await _runWithRealHttpClient( + () => shellCoreTestHooks.fetchBootstrapConfig(successUri.toString()), ); - final success = await _runWithRealHttpClient(() => shellCoreTestHooks.fetchBootstrapConfig(successUri.toString())); expect(success?.initialUrl, 'https://remote.example.com'); expect( success?.upgradeConfigUrl, @@ -1341,7 +1352,9 @@ void main() { expect(invalidUri?.initialUrl, 'https://remote.example.com'); final notFoundUri = await _startJsonServer((_) async => throw 404); - final notFound = await _runWithRealHttpClient(() => shellCoreTestHooks.fetchBootstrapConfig(notFoundUri.toString())); + final notFound = await _runWithRealHttpClient( + () => shellCoreTestHooks.fetchBootstrapConfig(notFoundUri.toString()), + ); expect(notFound?.initialUrl, 'https://remote.example.com'); final failed = await _runWithRealHttpClient( @@ -1355,7 +1368,9 @@ void main() { test('读取启动配置缓存异常时返回 null', () async { final originalStore = SharedPreferencesStorePlatform.instance; SharedPreferencesStorePlatform.instance = _ThrowingPreferencesStore(); - addTearDown(() => SharedPreferencesStorePlatform.instance = originalStore); + addTearDown( + () => SharedPreferencesStorePlatform.instance = originalStore, + ); expect(await shellCoreTestHooks.loadCachedBootstrapConfig(), isNull); }); @@ -1943,41 +1958,118 @@ void main() { await tester.pumpAndSettle(); }); - test('升级配置解析支持 data、平铺、isforce 和非法 JSON', () { - final wrapped = shellCoreTestHooks.parseUpgradeConfigString( - '{"data":{"versionName":"1.0.1","version":101,"isforce":1,"remark":"fix","filePath":"https://example.com/app.apk","fileSize":2048}}', - ); - final flat = shellCoreTestHooks.parseUpgradeConfigString( - '{"versionName":"1.0.2","version":102}', + test('升级配置解析仅支持新版接口响应', () { + final wrappedContent = jsonEncode({ + 'success': true, + 'code': null, + 'msg': null, + 'data': { + 'id': 787171620319301, + 'upgradeFileId': 0, + 'installFileId': 787171620307013, + 'isLatest': true, + 'appId': 787154824761413, + 'code': '1.0.1', + 'releaseTime': '2026-03-24 09:51:23', + 'releaseStatus': true, + 'upgradeOssUrl': '', + 'installOssUrl': + 'https://umsapi.23544.com/api/biz/Action/download-app?appId=787154824761413&environment=DevelopmentEnvironment&fileType=install&versionCode=1.0.1', + 'isAllowRollback': true, + 'config': jsonEncode({ + 'upgrade': { + 'versionName': '1.0.0', + 'version': 100, + 'isForce': 0, + 'remark': '1. 修复已知问题\n2. 测试升级弹窗功能是否正常加载', + 'filePath': + 'https://gitee.com/mr_koi/static_host/raw/master/app-release.apk', + 'fileSize': 30000, + }, + 'config': { + 'initialUrl': 'http://xszy.lzzneng.com/login.html', + 'preferredOrientations': ['portraitUp', 'portraitDown'], + }, + }), + 'configDescribe': '', + 'describe': '首次更新', + 'describeType': 1, + 'environment': 1, + 'createdTime': '2026-03-24 09:49:32.581', + }, + }); + final structured = shellCoreTestHooks.parseUpgradeConfigString( + wrappedContent, ); - expect(wrapped, isNotNull); - expect(wrapped!.versionName, '1.0.1'); - expect(wrapped.isForce, 1); - expect(flat?.version, 102); - expect(shellCoreTestHooks.parseUpgradeConfigString('{'), isNull); - }); - - test('升级配置转换为 AppUpgradeVersion 时处理缺省值', () { + expect(structured, isNotNull); + expect(structured!.success, isTrue); + expect(structured.id, 787171620319301); + expect(structured.installFileId, 787171620307013); + expect(structured.isLatest, isTrue); + expect(structured.appId, 787154824761413); + expect(structured.releaseCode, '1.0.1'); + expect(structured.releaseStatus, isTrue); + expect(structured.isAllowRollback, isTrue); + expect(structured.describe, '首次更新'); + expect(structured.describeType, 1); + expect(structured.environment, 1); + expect(structured.createdTime, '2026-03-24 09:49:32.581'); + expect(structured.upgrade, isNotNull); + expect(structured.versionName, '1.0.0'); + expect(structured.version, 100); + expect(structured.isForce, 0); + expect(structured.remark, contains('修复已知问题')); expect( - shellCoreTestHooks.convertUpgradeConfig( - ShellUpgradeConfig(versionName: '1.0.0'), + structured.filePath, + 'https://umsapi.23544.com/api/biz/Action/download-app?appId=787154824761413&environment=DevelopmentEnvironment&fileType=install&versionCode=1.0.1', + ); + expect(structured.fileSize, 30000); + expect(structured.config, isNotNull); + expect( + structured.config?.initialUrl, + 'http://xszy.lzzneng.com/login.html', + ); + expect( + structured.config?.preferredOrientations, + [ + DeviceOrientation.portraitUp, + DeviceOrientation.portraitDown, + ], + ); + + expect( + shellCoreTestHooks.parseUpgradeConfigString( + '{"versionName":"1.0.2","version":102}', ), isNull, ); + expect(shellCoreTestHooks.parseUpgradeConfigString('{'), isNull); + }); + + test('升级配置转换为 AppUpgradeVersion 时仅支持顶层 upgrade', () { expect( - shellCoreTestHooks.convertUpgradeConfig(ShellUpgradeConfig(version: 1)), + shellCoreTestHooks.convertUpgradeConfig(const ShellUpgradeConfig()), + isNull, + ); + expect( + shellCoreTestHooks.convertUpgradeConfig( + const ShellUpgradeConfig( + upgrade: ShellUpgradeReleaseConfig(version: 1), + ), + ), isNull, ); final converted = shellCoreTestHooks.convertUpgradeConfig( - ShellUpgradeConfig( - versionName: '1.2.3', - version: 123, - isForce: 1, - remark: 'important update', - filePath: '', - fileSize: 2048, + const ShellUpgradeConfig( + upgrade: ShellUpgradeReleaseConfig( + versionName: '1.2.3', + version: 123, + isForce: 1, + remark: 'important update', + fileSize: 2048, + ), ), ); @@ -1992,29 +2084,74 @@ void main() { expect(converted.supportedMethods, hasLength(3)); final convertedWithPath = shellCoreTestHooks.convertUpgradeConfig( - ShellUpgradeConfig( - versionName: '2.0.0', - version: 200, - filePath: 'https://example.com/app.apk', + const ShellUpgradeConfig( + upgrade: ShellUpgradeReleaseConfig( + versionName: '2.0.0', + version: 200, + filePath: 'https://example.com/app.apk', + ), ), ); expect(convertedWithPath?.downloadUrl, 'https://example.com/app.apk'); expect(convertedWithPath?.appStoreUrl, 'https://example.com/app.apk'); + + final convertedFromStructuredConfig = shellCoreTestHooks + .convertUpgradeConfig( + const ShellUpgradeConfig( + upgrade: ShellUpgradeReleaseConfig( + versionName: '3.0.0', + version: 300, + isForce: 0, + remark: '1. 修复已知问题\n2. 测试升级弹窗功能是否正常加载', + filePath: 'https://download.example.com/app.apk', + fileSize: 25000, + ), + ), + ); + expect(convertedFromStructuredConfig, isNotNull); + expect(convertedFromStructuredConfig!.versionName, '3.0.0'); + expect(convertedFromStructuredConfig.versionBuildNumber, 300); + expect(convertedFromStructuredConfig.isForce, isFalse); + expect(convertedFromStructuredConfig.updateContent, contains('修复已知问题')); + expect( + convertedFromStructuredConfig.downloadUrl, + 'https://download.example.com/app.apk', + ); + expect( + convertedFromStructuredConfig.appStoreUrl, + 'https://download.example.com/app.apk', + ); + expect(convertedFromStructuredConfig.apkSize, 25000 * 1024); }); test('获取升级配置支持成功、非法地址、非 200 和异常', () async { final successUri = await _startJsonServer( - (_) async => '{"versionName":"2.0.0","version":200}', + (_) async => jsonEncode({ + 'success': true, + 'data': { + 'installOssUrl': 'https://example.com/app.apk', + 'config': jsonEncode({ + 'upgrade': { + 'versionName': '2.0.0', + 'version': 200, + }, + }), + }, + }), ); - final success = await _runWithRealHttpClient(() => shellCoreTestHooks.fetchUpgradeConfig(successUri.toString())); + final success = await _runWithRealHttpClient( + () => shellCoreTestHooks.fetchUpgradeConfig(successUri.toString()), + ); expect(success?.versionName, '2.0.0'); expect(await shellCoreTestHooks.fetchUpgradeConfig('%%%'), isNull); final notFoundUri = await _startJsonServer((_) async => throw 404); expect( - await _runWithRealHttpClient(() => shellCoreTestHooks.fetchUpgradeConfig(notFoundUri.toString())), + await _runWithRealHttpClient( + () => shellCoreTestHooks.fetchUpgradeConfig(notFoundUri.toString()), + ), isNull, ); @@ -2037,7 +2174,8 @@ void main() { test('applyBootstrapConfig 会用远程启动配置覆盖本地配置', () async { final bootstrapUri = await _startJsonServer( - (_) async => '{"data":{"initialUrl":"https://remote.example.com/home","preferredOrientations":["portraitDown"],"upgradeConfigUrl":" https://remote.example.com/upgrade.json "}}', + (_) async => + '{"data":{"initialUrl":"https://remote.example.com/home","preferredOrientations":["portraitDown"],"upgradeConfigUrl":" https://remote.example.com/upgrade.json "}}', ); shellCoreTestHooks.initializeEnvironment( const ShellEnvironment( @@ -2096,8 +2234,7 @@ void main() { assetContents['assets/config/fallback_bootstrap.json'] = jsonEncode( { 'initialUrl': 'https://asset.example.com/start', - 'bootstrapConfigUrl': - 'http://127.0.0.1:$unavailablePort/config.json', + 'bootstrapConfigUrl': 'http://127.0.0.1:$unavailablePort/config.json', 'upgradeConfigUrl': ' https://asset.example.com/upgrade.json ', }, ); @@ -2134,7 +2271,9 @@ void main() { splashImage: MemoryImage(Uint8List.fromList(kTransparentImage)), ), ); - addTearDown(() => shellCoreTestHooks.initializeEnvironment(_testEnvironment)); + addTearDown( + () => shellCoreTestHooks.initializeEnvironment(_testEnvironment), + ); await tester.pumpWidget( const MaterialApp( @@ -2213,18 +2352,53 @@ void main() { expect(find.text('长提示'), findsNothing); }); - test('升级配置异步解析闭包可返回版本信息', () async { + test('升级配置异步解析闭包按本地版本决定是否返回升级信息', () async { + addTearDown(() => shellCoreTestHooks.setLocalBuildNumberResolver(null)); + + shellCoreTestHooks.setLocalBuildNumberResolver(() async => 200); final version = await shellCoreTestHooks.resolveUpgradeConfig( - ShellUpgradeConfig( - versionName: '3.0.0', - version: 300, - filePath: 'https://example.com/app.apk', + const ShellUpgradeConfig( + upgrade: ShellUpgradeReleaseConfig( + versionName: '3.0.0', + version: 300, + filePath: 'https://example.com/app.apk', + ), ), ); expect(version?.versionName, '3.0.0'); expect(version?.versionBuildNumber, 300); expect(version?.downloadUrl, 'https://example.com/app.apk'); + + shellCoreTestHooks.setLocalBuildNumberResolver(() async => 300); + final sameVersion = await shellCoreTestHooks.resolveUpgradeConfig( + const ShellUpgradeConfig( + upgrade: ShellUpgradeReleaseConfig( + versionName: '3.0.0', + version: 300, + filePath: 'https://example.com/app.apk', + ), + ), + ); + expect(sameVersion, isNull); + + shellCoreTestHooks.setLocalBuildNumberResolver(() async => null); + final fallbackVersion = await shellCoreTestHooks.resolveUpgradeConfig( + const ShellUpgradeConfig( + upgrade: ShellUpgradeReleaseConfig( + versionName: '3.0.0', + version: 300, + filePath: 'https://example.com/app.apk', + ), + ), + ); + expect(fallbackVersion?.versionName, '3.0.0'); + expect(fallbackVersion?.versionBuildNumber, 300); + + final hiddenVersion = await shellCoreTestHooks.resolveUpgradeConfig( + const ShellUpgradeConfig(), + ); + expect(hiddenVersion, isNull); }); }); }