feat:支持升级功能。
This commit is contained in:
parent
55d035cb9c
commit
24d8fc3a2d
|
|
@ -3,5 +3,6 @@
|
|||
"preferredOrientations": [
|
||||
"portraitUp",
|
||||
"portraitDown"
|
||||
]
|
||||
],
|
||||
"upgradeConfigUrl": "https://umsapi.23544.com/api/biz/version/get-latest-config?appId=787154824761413&environment=1"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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<String, dynamic> 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<String, dynamic>) {
|
||||
return ShellUpgradeReleaseConfig.fromJson(value);
|
||||
}
|
||||
if (value is Map) {
|
||||
return ShellUpgradeReleaseConfig.fromJson(
|
||||
Map<String, dynamic>.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<String, dynamic>) {
|
||||
return ShellUpgradeReleaseConfig.fromJson(decoded);
|
||||
}
|
||||
if (decoded is Map) {
|
||||
return ShellUpgradeReleaseConfig.fromJson(
|
||||
Map<String, dynamic>.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<String, dynamic> json) {
|
||||
final data = json['data'];
|
||||
final dataMap = data is Map<String, dynamic>
|
||||
? data
|
||||
: data is Map
|
||||
? Map<String, dynamic>.from(data)
|
||||
: const <String, dynamic>{};
|
||||
final rawConfig = _readShellUpgradeString(dataMap['config']);
|
||||
final parsedConfig = _readShellUpgradeEmbeddedConfig(rawConfig);
|
||||
final rawBootstrapConfig = parsedConfig?['config'];
|
||||
final bootstrapConfig = rawBootstrapConfig is Map<String, dynamic>
|
||||
? ShellBootstrapConfig.fromJson(rawBootstrapConfig)
|
||||
: rawBootstrapConfig is Map
|
||||
? ShellBootstrapConfig.fromJson(
|
||||
Map<String, dynamic>.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<String, dynamic>? _readShellUpgradeEmbeddedConfig(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
final decoded = jsonDecode(value);
|
||||
if (decoded is Map<String, dynamic>) {
|
||||
return decoded;
|
||||
}
|
||||
if (decoded is Map) {
|
||||
return Map<String, dynamic>.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<int?> 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<int?> _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<int?> Function()? resolver) {
|
||||
_localBuildNumberResolver = resolver;
|
||||
}
|
||||
|
||||
Future<ShellUpgradeConfig?> _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<String, dynamic>) {
|
||||
final data = jsonMap['data'] is Map<String, dynamic>
|
||||
? jsonMap['data'] as Map<String, dynamic>
|
||||
: jsonMap;
|
||||
return ShellUpgradeConfig.fromJson(data);
|
||||
final config = ShellUpgradeConfig.fromJson(jsonMap);
|
||||
return config.hasStructuredPayload ? config : null;
|
||||
}
|
||||
if (jsonMap is Map) {
|
||||
final config = ShellUpgradeConfig.fromJson(
|
||||
Map<String, dynamic>.from(jsonMap),
|
||||
);
|
||||
return config.hasStructuredPayload ? config : null;
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('解析 WebShell 升级配置异常: $e');
|
||||
|
|
|
|||
|
|
@ -76,6 +76,11 @@ class ShellCoreTestHooks {
|
|||
return ShellUpgradeService.instance._createVersionResolver(config)(upType);
|
||||
}
|
||||
|
||||
/// 为测试注入本地构建号解析逻辑。
|
||||
void setLocalBuildNumberResolver(Future<int?> Function()? resolver) {
|
||||
ShellUpgradeService.instance.debugSetLocalBuildNumberResolver(resolver);
|
||||
}
|
||||
|
||||
/// 为测试设置升级配置地址。
|
||||
void setupUpgradeConfigUrl(String? url) {
|
||||
ShellUpgradeService.instance.setupConfigUrl(url);
|
||||
|
|
|
|||
|
|
@ -47,8 +47,9 @@ Future<Uri> _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,
|
||||
<DeviceOrientation>[],
|
||||
);
|
||||
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>[
|
||||
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(
|
||||
<String, Object?>{
|
||||
'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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue