Refactor widget tests for H5 line generation and validation

- Simplified tests for H5 line creation, focusing on single line generation with a numeric 16-digit label.
- Updated assertions to validate the generated H5 line's label and URI structure.
- Removed redundant tests related to default H5 line URLs and local access, consolidating functionality into fewer tests.
- Added new tests to ensure correct URL generation from configured fallback order and query parameter handling.
- Enhanced test coverage for restoring cached H5 line URLs and validating their structure.
This commit is contained in:
Booker
2026-06-06 03:48:20 +07:00
parent 4b652a090d
commit 15d767100a
4 changed files with 381 additions and 1073 deletions

View File

@@ -12,175 +12,52 @@ class H5Line {
Uri get uri => Uri.parse(url);
}
enum Environment {
development,
staging,
production,
}
class AppConfig {
static const String appName = '本地打包';
static const String appLogo = '';
static const String clientConfigDevice = 'h5';
static const String clientConfigSerialNo = 'h5-domain';
static const String _dartDefinedH5LineUrls =
String.fromEnvironment('H5_LINE_URLS');
static const String _dartDefinedClientConfigQueryUrl =
String.fromEnvironment('CLIENT_CONFIG_QUERY_URL');
// Bootstrap only: used before /client_config/query returns dynamic H5 domains.
static const String _bootstrapH5LineUrl = String.fromEnvironment(
'BOOTSTRAP_H5_LINE_URL',
defaultValue: 'https://h5-test.imharry.work/');
static const Environment currentEnvironment = Environment.production;
// Compatibility for the packaging service's legacy domain rewrite step.
static final Map<Environment, List<String>> _environmentHosts = {
Environment.production: [
_bootstrapH5LineUrl,
],
};
static final Random _wildcardRandom = Random.secure();
static List<H5Line> get h5Lines {
final configuredUrls = _dartDefinedH5LineUrls.trim().isEmpty
? _normalizedUniqueUrls(defaultH5LineUrlsForBaseUri(Uri.base))
: _normalizedUniqueUrls(_dartDefinedH5LineUrls.split(','));
final urls = configuredUrls.isEmpty
? _normalizedUniqueUrls(const [_bootstrapH5LineUrl])
: configuredUrls;
static const String flutterShellMarkerQueryKey = 'flutter_shell';
static const String flutterShellUrlQueryKey = 'flutter_shell_url';
static const List<String> wildcardH5DomainTemplates = [
'*.albzyuxq.vip',
'*.zdyswvnt.vip',
'*.gxifpxcr.vip',
];
return [
for (var index = 0; index < urls.length; index += 1)
H5Line(label: '线路${index + 1}', url: urls[index]),
];
}
static final Random _random = Random.secure();
static Map<String, String> get clientConfigQueryPayload => const {
'device': clientConfigDevice,
'serialNo': clientConfigSerialNo,
};
static H5Line get h5Line => createRandomH5Line();
static List<Uri> get clientConfigQueryUris {
final dartDefinedUrl = _dartDefinedClientConfigQueryUrl.trim();
if (dartDefinedUrl.isNotEmpty) {
return [Uri.parse(dartDefinedUrl)];
}
final lines = h5Lines;
if (lines.isEmpty) {
return const [];
}
return clientConfigQueryUrisForHomeUri(lines.first.uri);
}
static List<String> defaultH5LineUrlsForBaseUri(Uri baseUri) {
final runtimeHomeUrl = _runtimeHomeUrlFromBaseUri(baseUri);
if (runtimeHomeUrl != null) {
return [runtimeHomeUrl];
}
final environmentHosts = _environmentHosts[currentEnvironment];
if (environmentHosts == null || environmentHosts.isEmpty) {
return const [_bootstrapH5LineUrl];
}
return environmentHosts;
}
static List<Uri> clientConfigQueryUrisForHomeUri(Uri homeUri) {
return [
_originUriWithPath(homeUri, '/client_config/query'),
_originUriWithPath(homeUri, '/api/user/client_config/query'),
];
}
static List<H5Line> h5LinesFromResourceUrl(
String resourceUrl, {
static H5Line createRandomH5Line({
int attempt = 0,
String Function()? wildcardFactory,
}) {
final urls = _normalizedUniqueUrls(
splitResourceUrlLines(resourceUrl).map(
(value) => _replaceWildcardHost(
value,
wildcardFactory: wildcardFactory,
),
final template = wildcardH5DomainTemplates[
attempt.abs() % wildcardH5DomainTemplates.length];
return h5LineFromUrl(
_buildFlutterShellHomeUrl(
_replaceWildcardHost(template, wildcardFactory: wildcardFactory),
),
);
return [
for (var index = 0; index < urls.length; index += 1)
H5Line(label: '线路${index + 1}', url: urls[index]),
];
}
static List<String> splitResourceUrlLines(String resourceUrl) {
if (resourceUrl.trim().isEmpty) {
return const [];
}
final lines = <String>[];
final seen = <String>{};
for (final value
in resourceUrl.split(RegExp(r'\\r\\n|\\n|\\r|\\t|[\r\n\t]+'))) {
final line = value.trim();
if (line.isEmpty || seen.contains(line)) {
continue;
}
lines.add(line);
seen.add(line);
}
return List.unmodifiable(lines);
static H5Line h5LineFromUrl(String value) {
return H5Line(label: '线路1', url: _normalizeHomeUrl(value));
}
static bool isLoginPageUrl(String? url) {
if (url == null || url.isEmpty) {
return false;
}
final uri = Uri.tryParse(url);
if (uri == null) {
return false;
}
return _isLoginPath(uri.path) || _isLoginPath(uri.fragment);
static String originForUrl(String value) {
final uri = Uri.parse(value);
return '${uri.scheme}://${uri.authority}';
}
static bool _isLoginPath(String path) {
final normalized = path.trim();
if (normalized.isEmpty) {
return false;
}
final pathOnly = normalized.split('?').first.split('#').first;
final segments = pathOnly.split('/').where((segment) => segment.isNotEmpty);
if (segments.isEmpty) {
return false;
}
return segments.last == 'login';
}
static List<String> _normalizedUniqueUrls(Iterable<String> values) {
final urls = <String>[];
final seen = <String>{};
for (final value in values) {
final normalized = _tryNormalizeHomeUrl(value);
if (normalized == null || seen.contains(normalized)) {
continue;
}
urls.add(normalized);
seen.add(normalized);
}
return List.unmodifiable(urls);
}
static String? _tryNormalizeHomeUrl(String value) {
try {
return _normalizeHomeUrl(value);
} catch (_) {
return null;
}
static String _buildFlutterShellHomeUrl(String host) {
final normalized = _normalizeHomeUrl(host);
final uri = Uri.parse(normalized);
final origin = originForUrl(normalized);
final queryParameters = Map<String, String>.from(uri.queryParameters)
..[flutterShellMarkerQueryKey] = '1'
..[flutterShellUrlQueryKey] = origin;
return uri.replace(queryParameters: queryParameters).toString();
}
static String _normalizeHomeUrl(String host) {
@@ -198,69 +75,6 @@ class AppConfig {
return uri.replace(path: path).toString();
}
static String? _runtimeHomeUrlFromBaseUri(Uri baseUri) {
if (_isLocalRuntimeUri(baseUri)) {
return null;
}
return _originUriWithPath(baseUri, '/').toString();
}
static Uri _originUriWithPath(Uri uri, String path) {
final normalizedPath = path.startsWith('/') ? path : '/$path';
return Uri.parse('${uri.scheme}://${uri.authority}$normalizedPath');
}
static bool _isLocalRuntimeUri(Uri uri) {
if (uri.scheme != 'http' && uri.scheme != 'https') {
return true;
}
final host = _normalizeHostForCompare(uri.host);
if (host.isEmpty) {
return true;
}
if (host == 'localhost' || host.endsWith('.localhost') || host == '::1') {
return true;
}
final ipv4Parts = _tryParseIpv4Address(host);
if (ipv4Parts == null) {
return false;
}
return ipv4Parts[0] == 127 || ipv4Parts.every((part) => part == 0);
}
static String _normalizeHostForCompare(String host) {
final normalized = host.trim().toLowerCase();
if (normalized.endsWith('.')) {
return normalized.substring(0, normalized.length - 1);
}
return normalized;
}
static List<int>? _tryParseIpv4Address(String host) {
final parts = host.split('.');
if (parts.length != 4) {
return null;
}
final octets = <int>[];
for (final part in parts) {
if (part.isEmpty || !RegExp(r'^\d+$').hasMatch(part)) {
return null;
}
final value = int.tryParse(part);
if (value == null || value < 0 || value > 255) {
return null;
}
octets.add(value);
}
return octets;
}
static String _replaceWildcardHost(
String value, {
String Function()? wildcardFactory,
@@ -276,12 +90,10 @@ class AppConfig {
}
static String _randomWildcardLabel() {
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
return String.fromCharCodes(
List<int>.generate(
16,
(_) => chars.codeUnitAt(_wildcardRandom.nextInt(chars.length)),
),
);
final digits = StringBuffer(_random.nextInt(9) + 1);
for (var index = 1; index < 16; index += 1) {
digits.write(_random.nextInt(10));
}
return digits.toString();
}
}

File diff suppressed because it is too large Load Diff