Files
xianyan/scripts/favorite_sync_test.dart
Developer 228095f80a chore: 完成v6.7.0版本迭代更新
本次更新涵盖多个功能模块的优化与新增:
1. 新增Wi-Fi直连配对方式与协作画布模块
2. 完成设备管理重命名API与前端适配
3. 优化日签卡片空数据保护与UI细节
4. 新增剪贴板工具与每日运势会话
5. 修复应用锁恢复、语音消息录制等已知问题
6. 完善文件传输统计与配对逻辑
7. 更新安卓权限配置与iOS隐私描述
8. 新增自动化测试脚本与文档
9. 清理旧版审计报告与测试文件
2026-05-14 05:35:18 +08:00

543 lines
17 KiB
Dart

// ============================================================
// 闲言APP — 收藏同步接口测试脚本
// 创建时间: 2026-05-14
// 更新时间: 2026-05-14
// 作用: 测试收藏系统HTTP接口
// - 收藏列表获取 (Feed API + UserCenter API)
// - 收藏添加/删除
// - Feed API收藏操作
// - 同步逻辑验证
// - API基础URL: https://tools.wktyl.com
// 上次更新: 初始版本
// 运行: dart run Scripts/favorite_sync_test.dart [token]
// ============================================================
import 'dart:convert';
import 'dart:io';
const String kApiBase = 'https://tools.wktyl.com';
const String kUserCenterBase = '/api/user_center';
const String kFeedBase = '/api/feed';
const Duration kTimeout = Duration(seconds: 15);
int _passCount = 0;
int _failCount = 0;
void _result(String name, bool pass, {String? detail}) {
final icon = pass ? '' : '';
final status = pass ? 'PASS' : 'FAIL';
_passCount += pass ? 1 : 0;
_failCount += pass ? 0 : 1;
print('$icon [$status] $name${detail != null ? '$detail' : ''}');
}
Future<Map<String, dynamic>?> _httpGet(
String path, {
Map<String, dynamic>? queryParameters,
String? token,
}) async {
try {
final uri = Uri.parse('$kApiBase$path').replace(
queryParameters: queryParameters?.map(
(k, v) => MapEntry(k, v.toString()),
),
);
final client = HttpClient();
client.connectionTimeout = kTimeout;
final request = await client.getUrl(uri);
request.headers.set('Accept', 'application/json');
request.headers.set('Content-Type', 'application/x-www-form-urlencoded');
if (token != null && token.isNotEmpty) {
request.headers.set('Authorization', 'Bearer $token');
}
final response = await request.close();
final body = await response.transform(utf8.decoder).join();
client.close();
return jsonDecode(body) as Map<String, dynamic>;
} catch (e) {
print(' ⚠️ HTTP GET $path 失败: $e');
return null;
}
}
Future<Map<String, dynamic>?> _httpPost(
String path, {
Map<String, dynamic>? data,
String? token,
}) async {
try {
final uri = Uri.parse('$kApiBase$path');
final client = HttpClient();
client.connectionTimeout = kTimeout;
final request = await client.postUrl(uri);
request.headers.set('Accept', 'application/json');
request.headers.set('Content-Type', 'application/x-www-form-urlencoded');
if (token != null && token.isNotEmpty) {
request.headers.set('Authorization', 'Bearer $token');
}
if (data != null && data.isNotEmpty) {
final formData = data.entries
.map((e) =>
'${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value.toString())}')
.join('&');
request.write(formData);
}
final response = await request.close();
final body = await response.transform(utf8.decoder).join();
client.close();
return jsonDecode(body) as Map<String, dynamic>;
} catch (e) {
print(' ⚠️ HTTP POST $path 失败: $e');
return null;
}
}
String? _testToken;
Future<void> main(List<String> args) async {
print('╔══════════════════════════════════════════════════════════╗');
print('║ 闲言APP — 收藏同步接口测试 ║');
print('║ API Base: $kApiBase');
print('╚══════════════════════════════════════════════════════════╝\n');
if (args.isNotEmpty) {
_testToken = args[0];
print('🔑 使用提供的Token: ${_testToken!.substring(0, 10)}...\n');
} else {
print('⚠️ 未提供Token, 部分需要登录的接口可能返回401');
print(' 用法: dart run Scripts/favorite_sync_test.dart <token>\n');
}
await testFeedFavoriteList();
await testUserCenterFavoriteList();
await testFavoriteAddAndRemove();
await testFeedActionFavorite();
await testFavoriteSyncLogic();
print('\n╔══════════════════════════════════════════════════════════╗');
print('║ 测试结果汇总 ║');
print('╠══════════════════════════════════════════════════════════╣');
print('║ ✅ 通过: $_passCount');
print('║ ❌ 失败: $_failCount');
print('║ 📊 总计: ${_passCount + _failCount}');
print('╚══════════════════════════════════════════════════════════╝');
exit(_failCount > 0 ? 1 : 0);
}
Future<void> testFeedFavoriteList() async {
print('\n━━━ 1. Feed API 收藏列表获取 ━━━');
final result = await _httpGet(
'$kFeedBase/favorites',
queryParameters: {'page': '1', 'limit': '10'},
token: _testToken,
);
_result(
'Feed收藏列表接口响应',
result != null,
detail: result != null ? 'code=${result['code']}' : '请求失败',
);
if (result != null) {
final code = result['code'] as int? ?? 0;
_result(
'接口返回成功(code=1)',
code == 1,
detail: 'code=$code, msg=${result['msg']}',
);
if (code == 1) {
final data = result['data'] as Map<String, dynamic>? ?? {};
final list = data['list'] as List<dynamic>? ?? [];
final total = data['total'] as int? ?? 0;
final page = data['page'] as int? ?? 1;
_result(
'收藏列表非空',
list.isNotEmpty || total == 0,
detail: 'total=$total, page=$page, list.length=${list.length}',
);
if (list.isNotEmpty) {
final firstItem = list.first as Map<String, dynamic>;
final hasRequiredFields = firstItem.containsKey('id') &&
firstItem.containsKey('feed_type') &&
firstItem.containsKey('content');
_result(
'收藏项包含必要字段(id/feed_type/content)',
hasRequiredFields,
detail: 'keys=${firstItem.keys.join(', ')}',
);
final isFavorited = firstItem['is_favorited'] as bool? ?? false;
_result(
'收藏项标记为已收藏',
isFavorited,
detail: 'is_favorited=$isFavorited',
);
}
}
}
}
Future<void> testUserCenterFavoriteList() async {
print('\n━━━ 2. UserCenter API 收藏列表获取 ━━━');
final result = await _httpPost(
'$kUserCenterBase/favorite',
data: {
'action': 'list',
'target_type': 'article',
'page': '1',
'limit': '10',
},
token: _testToken,
);
_result(
'UserCenter收藏列表接口响应',
result != null,
detail: result != null ? 'code=${result['code']}' : '请求失败',
);
if (result != null) {
final code = result['code'] as int? ?? 0;
_result(
'接口返回成功(code=1)',
code == 1,
detail: 'code=$code, msg=${result['msg']}',
);
if (code == 1) {
final data = result['data'] as Map<String, dynamic>? ?? {};
final list = data['list'] as List<dynamic>? ?? [];
final total = data['total'] as int? ?? 0;
_result(
'UserCenter收藏列表数据',
true,
detail: 'total=$total, list.length=${list.length}',
);
if (list.isNotEmpty) {
final firstItem = list.first as Map<String, dynamic>;
final hasRequiredFields = firstItem.containsKey('id') &&
firstItem.containsKey('target_type') &&
firstItem.containsKey('target_id');
_result(
'收藏项包含必要字段(id/target_type/target_id)',
hasRequiredFields,
detail: 'keys=${firstItem.keys.join(', ')}',
);
}
}
}
}
Future<void> testFavoriteAddAndRemove() async {
print('\n━━━ 3. 收藏添加/删除测试 ━━━');
if (_testToken == null) {
_result('收藏添加/删除(需登录)', false, detail: '未提供Token, 跳过');
return;
}
final feedListResult = await _httpGet(
'$kFeedBase/list',
queryParameters: {'channel': 'all', 'limit': '5'},
);
int? testFeedId;
String? testFeedType;
if (feedListResult != null && feedListResult['code'] == 1) {
final data = feedListResult['data'] as Map<String, dynamic>? ?? {};
final list = data['list'] as List<dynamic>? ?? [];
if (list.isNotEmpty) {
final firstItem = list.first as Map<String, dynamic>;
testFeedId = firstItem['id'] as int?;
testFeedType = firstItem['feed_type'] as String? ?? 'feed';
}
}
if (testFeedId == null) {
_result('获取测试用Feed项', false, detail: '无法获取Feed列表中的项目');
return;
}
_result('获取测试用Feed项', true, detail: 'id=$testFeedId, type=$testFeedType');
final checkResult = await _httpPost(
'$kUserCenterBase/favorite',
data: {
'action': 'check',
'target_type': testFeedType,
'target_id': '$testFeedId',
},
token: _testToken,
);
bool wasFavorited = false;
if (checkResult != null && checkResult['code'] == 1) {
final data = checkResult['data'] as Map<String, dynamic>? ?? {};
wasFavorited = data['is_favorited'] as bool? ?? false;
_result('收藏状态检查', true, detail: 'is_favorited=$wasFavorited');
} else {
_result(
'收藏状态检查',
false,
detail: '接口返回code=${checkResult?['code']}',
);
}
final addAction = wasFavorited ? 'remove' : 'add';
final addResult = await _httpPost(
'$kUserCenterBase/favorite',
data: {
'action': addAction,
'target_type': testFeedType,
'target_id': '$testFeedId',
},
token: _testToken,
);
_result(
'收藏${addAction == 'add' ? '添加' : '移除'}操作',
addResult != null && addResult['code'] == 1,
detail: addResult != null
? 'code=${addResult['code']}, msg=${addResult['msg']}'
: '请求失败',
);
final verifyResult = await _httpPost(
'$kUserCenterBase/favorite',
data: {
'action': 'check',
'target_type': testFeedType,
'target_id': '$testFeedId',
},
token: _testToken,
);
if (verifyResult != null && verifyResult['code'] == 1) {
final data = verifyResult['data'] as Map<String, dynamic>? ?? {};
final nowFavorited = data['is_favorited'] as bool? ?? false;
final expectedFavorited = !wasFavorited;
_result(
'收藏状态已变更',
nowFavorited == expectedFavorited,
detail: '操作前=$wasFavorited, 操作后=$nowFavorited, 期望=$expectedFavorited',
);
}
final restoreAction = wasFavorited ? 'add' : 'remove';
final restoreResult = await _httpPost(
'$kUserCenterBase/favorite',
data: {
'action': restoreAction,
'target_type': testFeedType,
'target_id': '$testFeedId',
},
token: _testToken,
);
_result(
'恢复原始收藏状态',
restoreResult != null && restoreResult['code'] == 1,
detail: '已恢复为${wasFavorited ? "已收藏" : "未收藏"}',
);
}
Future<void> testFeedActionFavorite() async {
print('\n━━━ 4. Feed API 收藏操作测试 ━━━');
if (_testToken == null) {
_result('Feed API收藏操作(需登录)', false, detail: '未提供Token, 跳过');
return;
}
final feedListResult = await _httpGet(
'$kFeedBase/list',
queryParameters: {'channel': 'all', 'limit': '3'},
);
int? testFeedId;
String? testFeedType;
if (feedListResult != null && feedListResult['code'] == 1) {
final data = feedListResult['data'] as Map<String, dynamic>? ?? {};
final list = data['list'] as List<dynamic>? ?? [];
if (list.isNotEmpty) {
final item = list.last as Map<String, dynamic>;
testFeedId = item['id'] as int?;
testFeedType = item['feed_type'] as String? ?? 'feed';
}
}
if (testFeedId == null) {
_result('获取测试用Feed项', false, detail: '无法获取Feed列表中的项目');
return;
}
_result('获取测试用Feed项', true, detail: 'id=$testFeedId, type=$testFeedType');
final favoriteResult = await _httpPost(
'$kFeedBase/action',
data: {
'action': 'favorite',
'feed_type': testFeedType,
'feed_id': '$testFeedId',
},
token: _testToken,
);
_result(
'Feed API收藏操作',
favoriteResult != null && favoriteResult['code'] == 1,
detail: favoriteResult != null
? 'code=${favoriteResult['code']}, msg=${favoriteResult['msg']}'
: '请求失败',
);
await Future<void>.delayed(const Duration(seconds: 1));
final unfavoriteResult = await _httpPost(
'$kFeedBase/action',
data: {
'action': 'unfavorite',
'feed_type': testFeedType,
'feed_id': '$testFeedId',
},
token: _testToken,
);
_result(
'Feed API取消收藏操作',
unfavoriteResult != null && unfavoriteResult['code'] == 1,
detail: unfavoriteResult != null
? 'code=${unfavoriteResult['code']}, msg=${unfavoriteResult['msg']}'
: '请求失败',
);
}
Future<void> testFavoriteSyncLogic() async {
print('\n━━━ 5. 同步逻辑验证 ━━━');
if (_testToken == null) {
_result('同步逻辑验证(需登录)', false, detail: '未提供Token, 跳过');
return;
}
final feedFavResult = await _httpGet(
'$kFeedBase/favorites',
queryParameters: {'page': '1', 'limit': '20'},
token: _testToken,
);
int feedFavCount = 0;
List<int> feedFavIds = [];
if (feedFavResult != null && feedFavResult['code'] == 1) {
final data = feedFavResult['data'] as Map<String, dynamic>? ?? {};
final list = data['list'] as List<dynamic>? ?? [];
feedFavCount = data['total'] as int? ?? list.length;
feedFavIds = list
.map<int>((e) => (e as Map<String, dynamic>)['id'] as int? ?? 0)
.where((id) => id > 0)
.toList();
}
_result(
'Feed API收藏列表可获取',
feedFavResult != null,
detail: 'total=$feedFavCount, 本页${feedFavIds.length}',
);
final ucFavResult = await _httpPost(
'$kUserCenterBase/favorite',
data: {'action': 'list', 'target_type': 'article', 'page': '1', 'limit': '20'},
token: _testToken,
);
int ucFavCount = 0;
List<int> ucFavIds = [];
if (ucFavResult != null && ucFavResult['code'] == 1) {
final data = ucFavResult['data'] as Map<String, dynamic>? ?? {};
final list = data['list'] as List<dynamic>? ?? [];
ucFavCount = data['total'] as int? ?? list.length;
ucFavIds = list
.map<int>(
(e) => (e as Map<String, dynamic>)['target_id'] as int? ?? 0)
.where((id) => id > 0)
.toList();
}
_result(
'UserCenter API收藏列表可获取',
ucFavResult != null,
detail: 'total=$ucFavCount, 本页${ucFavIds.length}',
);
final countResult = await _httpPost(
'$kUserCenterBase/favorite',
data: {'action': 'count'},
token: _testToken,
);
if (countResult != null && countResult['code'] == 1) {
final data = countResult['data'] as Map<String, dynamic>? ?? {};
final counts = data['counts'] as Map<String, dynamic>? ?? {};
_result(
'收藏统计接口可用',
true,
detail: 'counts=$counts',
);
} else {
_result(
'收藏统计接口',
false,
detail: 'code=${countResult?['code']}, msg=${countResult?['msg']}',
);
}
final groupsResult = await _httpPost(
'$kUserCenterBase/favorite',
data: {'action': 'groups'},
token: _testToken,
);
if (groupsResult != null && groupsResult['code'] == 1) {
final data = groupsResult['data'] as Map<String, dynamic>? ?? {};
final groups = data['groups'] as List<dynamic>? ?? [];
_result(
'收藏分组接口可用',
true,
detail: 'groups=${groups.join(', ')}',
);
} else {
_result(
'收藏分组接口',
false,
detail: 'code=${groupsResult?['code']}, msg=${groupsResult?['msg']}',
);
}
if (feedFavIds.isNotEmpty && ucFavIds.isNotEmpty) {
final overlapIds = feedFavIds.toSet().intersection(ucFavIds.toSet());
_result(
'两个API收藏数据有交集(同步一致性)',
overlapIds.isNotEmpty,
detail: 'Feed API ${feedFavIds.length}条, UserCenter API ${ucFavIds.length}条, 交集${overlapIds.length}',
);
} else {
_result(
'同步一致性验证',
true,
detail: '至少一个API收藏列表为空, 无法比较交集',
);
}
}