Files
2026-09-28 11:39:19 +08:00

160 lines
5.7 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:typed_data';
import 'package:dio/dio.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:qiangbao_accounting/api/geo_api.dart';
import 'package:qiangbao_accounting/core/network/api_exception.dart';
/// 用一个假的 HTTP 适配器接管请求,按 URL 分发不同的响应。
/// 这样不用真的联网,也能把「百度失败 → Nominatim 兜底」整条链路跑通。
class _FakeAdapter implements HttpClientAdapter {
_FakeAdapter(this.handler);
final Future<ResponseBody> Function(RequestOptions options) handler;
/// 记录每次请求,用来断言请求头和参数
final List<RequestOptions> requests = [];
@override
Future<ResponseBody> fetch(
RequestOptions options,
Stream<Uint8List>? requestStream,
Future<void>? cancelFuture,
) {
requests.add(options);
return handler(options);
}
@override
void close({bool force = false}) {}
}
ResponseBody _json(String body) => ResponseBody.fromString(
body,
200,
headers: {
Headers.contentTypeHeader: ['application/json'],
},
);
bool _isBaidu(RequestOptions o) => o.uri.host.contains('baidu');
bool _isNominatim(RequestOptions o) => o.uri.host.contains('nominatim');
void main() {
GeoApi apiWith(_FakeAdapter adapter) => GeoApi(dio: Dio()..httpClientAdapter = adapter);
group('地址解析:百度优先、Nominatim 兜底', () {
test('百度正常时用百度的结果(国内数据最准)', () async {
final adapter = _FakeAdapter((o) async {
if (_isBaidu(o)) {
return _json(
'{"status":0,"result":{"formatted_address":"云南省昆明市五华区护国街道南屏街112"}}',
);
}
fail('百度成功了就不该再去打 Nominatim');
});
final address = await apiWith(adapter)
.reverseAddress(latitude: 25.041234, longitude: 102.712345);
expect(address, '云南省昆明市五华区护国街道南屏街112');
expect(adapter.requests.length, 1);
});
test('★ 百度配额用完时自动降级到 Nominatim(用户实际撞到的情况)', () async {
final adapter = _FakeAdapter((o) async {
if (_isBaidu(o)) {
return _json('{"status":4,"msg":"AK 配额校验失败"}');
}
return _json(
'{"display_name":"庆云街, 护国街道, 五华区, 昆明市, 云南省, 650011, 中国",'
'"address":{"road":"庆云街","suburb":"护国街道","district":"五华区",'
'"city":"昆明市","state":"云南省","postcode":"650011","country":"中国"}}',
);
});
final address = await apiWith(adapter)
.reverseAddress(latitude: 25.041234, longitude: 102.712345);
// 拼成中文习惯的「大 → 小」,而不是 Nominatim 原生的「小 → 大」
expect(address, '云南省昆明市五华区护国街道庆云街');
expect(adapter.requests.length, 2);
});
test('百度网络异常也会降级(不只是配额问题)', () async {
final adapter = _FakeAdapter((o) async {
if (_isBaidu(o)) {
throw DioException(requestOptions: o, message: 'connection timeout');
}
return _json('{"address":{"state":"云南省","city":"昆明市","district":"五华区"}}');
});
final address = await apiWith(adapter)
.reverseAddress(latitude: 25.0, longitude: 102.7);
expect(address, '云南省昆明市五华区');
});
test('两个都挂时,把百度的原因带出来(而不是一句笼统的失败)', () async {
final adapter = _FakeAdapter((o) async {
if (_isBaidu(o)) return _json('{"status":4,"msg":"AK 配额校验失败"}');
throw DioException(requestOptions: o, message: 'timeout');
});
expect(
() => apiWith(adapter).reverseAddress(latitude: 25.0, longitude: 102.7),
throwsA(
isA<ApiException>().having(
(e) => e.message,
'message',
contains('配额'),
),
),
);
});
test('Nominatim 只给 display_name 时也能拼出地址,且滤掉邮编和国家', () async {
final adapter = _FakeAdapter((o) async {
if (_isBaidu(o)) return _json('{"status":1,"msg":"失败"}');
return _json(
'{"display_name":"庆云街, 护国街道, 五华区, 昆明市, 云南省, 650011, 中国"}',
);
});
final address = await apiWith(adapter)
.reverseAddress(latitude: 25.0, longitude: 102.7);
expect(address, '云南省昆明市五华区护国街道庆云街');
});
test('Nominatim 请求必须带 User-Agent(它的使用政策要求,不带会被拒)', () async {
final adapter = _FakeAdapter((o) async {
if (_isBaidu(o)) return _json('{"status":1,"msg":"失败"}');
return _json('{"address":{"state":"云南省"}}');
});
await apiWith(adapter).reverseAddress(latitude: 25.0, longitude: 102.7);
final nominatim = adapter.requests.firstWhere(_isNominatim);
expect(nominatim.headers['User-Agent'], isNotNull);
expect(nominatim.headers['User-Agent'], contains('Qiangbao'));
});
test('百度请求带 coordtype=wgs84ll(geolocator 给的是 WGS84)', () async {
final adapter = _FakeAdapter((o) async {
if (_isBaidu(o)) {
return _json('{"status":0,"result":{"formatted_address":"昆明"}}');
}
return _json('{}');
});
await apiWith(adapter).reverseAddress(latitude: 25.0, longitude: 102.7);
final baidu = adapter.requests.firstWhere(_isBaidu);
expect(baidu.uri.queryParameters['coordtype'], 'wgs84ll');
expect(baidu.uri.queryParameters['location'], '25.0,102.7');
});
});
}