Files
kitchen/scripts/test_kitchen_api.dart
2026-04-17 07:00:26 +08:00

353 lines
9.9 KiB
Dart
Raw 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.
// 2026-04-17 | test_kitchen_api.dart | 点餐助手API接口测试 | 验证kitchen.php CRUD + SSE
// 运行: dart run scripts/test_kitchen_api.dart
import 'dart:convert';
import 'dart:io';
const String baseUrl = 'https://eat.wktyl.com/api/kitchen';
String? createdOrderId;
String? createdOrderNo;
int _curlCounter = 0;
Future<String> _curl(
String method,
String url, {
String? body,
Map<String, String>? headers,
}) async {
_curlCounter++;
final tmpFile = File(
'${Directory.systemTemp.path}/kitchen_test_$_curlCounter.json',
);
final args = [
'-sS',
'-X',
method,
'--max-time',
'15',
'--compressed',
'-o',
tmpFile.path,
];
if (headers != null) {
headers.forEach((k, v) {
args.addAll(['-H', '$k: $v']);
});
}
if (body != null) {
args.addAll([
'-H',
'Content-Type: application/json; charset=utf-8',
'-d',
body,
]);
}
args.add(url);
final result = await Process.run('curl.exe', args);
final stderr = (result.stderr as String).trim();
if (!tmpFile.existsSync()) {
throw Exception('curl无输出: $stderr');
}
final bytes = tmpFile.readAsBytesSync();
try {
tmpFile.deleteSync();
} catch (_) {}
if (bytes.isEmpty && stderr.isNotEmpty) {
throw Exception('curl error: $stderr');
}
final output = utf8.decode(bytes, allowMalformed: true);
return _extractJson(output);
}
String _extractJson(String raw) {
var s = raw.trim();
final jsonStart = s.indexOf('{');
if (jsonStart > 0) {
s = s.substring(jsonStart);
}
final jsonEnd = s.lastIndexOf('}');
if (jsonEnd >= 0 && jsonEnd < s.length - 1) {
s = s.substring(0, jsonEnd + 1);
}
return s;
}
Future<void> main() async {
print('╔══════════════════════════════════════════════════╗');
print('║ 🍽️ 点餐助手 API 接口测试 ║');
print('║ 目标: $baseUrl');
print('╚══════════════════════════════════════════════════╝\n');
print('━━━ 1. 接口首页 (index) ━━━');
await testGet({'act': 'index'});
print('\n━━━ 2. CORS预检 (OPTIONS) ━━━');
await testOptions();
print('\n━━━ 3. 创建点单 (POST JSON body) ━━━');
await testCreateOrder();
if (createdOrderId == null) {
print('\n❌ 创建点单失败,后续测试跳过');
return;
}
print('\n━━━ 4. 获取点单 (GET ?act=get&id=xxx) ━━━');
await testGet({'act': 'get', 'id': createdOrderId!});
print('\n━━━ 5. 更新点单 (POST ?act=update) ━━━');
await testUpdateOrder();
print('\n━━━ 6. 点单列表 (GET ?act=list) ━━━');
await testGet({'act': 'list', 'page': '1', 'limit': '5'});
print('\n━━━ 7. 统计信息 (GET ?act=stats) ━━━');
await testGet({'act': 'stats'});
print('\n━━━ 8. SSE连接测试 ━━━');
await testSSE();
print('\n━━━ 9. 清理过期数据 (GET ?act=cleanup&days=999) ━━━');
await testGet({'act': 'cleanup', 'days': '999'});
print('\n━━━ 10. 删除点单 (GET ?act=delete&id=xxx) ━━━');
await testGet({'act': 'delete', 'id': createdOrderId!});
print('\n━━━ 11. 确认删除 - 再次获取 ━━━');
await testGet({'act': 'get', 'id': createdOrderId!});
print('\n╔══════════════════════════════════════════════════╗');
print('║ ✅ 全部测试完成 ║');
print('╚══════════════════════════════════════════════════╝');
}
Future<void> testGet(Map<String, String> params) async {
final qs = params.entries
.map((e) => '${e.key}=${Uri.encodeComponent(e.value)}')
.join('&');
final url = '$baseUrl/kitchen.php?$qs';
print(' GET $url');
try {
final output = await _curl(
'GET',
url,
headers: {'Accept': 'application/json'},
);
_printResult(output);
} catch (e) {
print(' ❌ 请求失败: $e');
}
}
Future<void> testOptions() async {
final url = '$baseUrl/kitchen.php';
print(' OPTIONS $url');
try {
final args = [
'-sS',
'-X',
'OPTIONS',
'-o',
'NUL',
'-w',
'%{http_code}\\n%header{Access-Control-Allow-Origin}\\n%header{Access-Control-Allow-Methods}',
'--max-time',
'15',
url,
];
final result = await Process.run('curl.exe', args);
final output = (result.stdout as String).trim();
final lines = output.split('\n');
final statusCode = lines.isNotEmpty ? lines[0].trim() : '???';
print(' 状态码: $statusCode');
if (lines.length > 1) print(' Allow-Origin: ${lines[1].trim()}');
if (lines.length > 2) print(' Allow-Methods: ${lines[2].trim()}');
if (statusCode == '204') {
print(' ✅ CORS预检通过');
} else {
print(' ⚠️ 预期204实际$statusCode');
}
} catch (e) {
print(' ❌ 请求失败: $e');
}
}
Future<void> testCreateOrder() async {
final orderData = {
'type': 0,
'status': 1,
'tableNo': 'A1',
'note': '接口测试订单,可删除',
'items': [
{
'id': 'item_1',
'name': '红烧肉',
'source': 0,
'quantity': 1,
'price': 38.0,
'ingredients': '五花肉、酱油、冰糖',
'note': null,
},
{
'id': 'item_2',
'name': '番茄炒蛋',
'source': 2,
'quantity': 2,
'price': 18.0,
'ingredients': null,
'note': '少盐',
},
],
};
final url = '$baseUrl/kitchen.php?act=create';
print(' POST $url');
try {
final output = await _curl(
'POST',
url,
body: jsonEncode(orderData),
headers: {'Accept': 'application/json'},
);
print(' Raw: ${output.length > 500 ? output.substring(0, 500) : output}');
final result = _parseResult(output);
if (result['code'] == 200 && result['data'] != null) {
final data = result['data'] as Map<String, dynamic>;
createdOrderId = data['id']?.toString();
createdOrderNo = data['orderNo']?.toString();
print(' ✅ 创建成功! id=$createdOrderId, orderNo=$createdOrderNo');
} else {
print(' ❌ 创建失败: ${result['message']}');
}
} catch (e) {
print(' ❌ 请求失败: $e');
}
}
Future<void> testUpdateOrder() async {
final updateData = {
'id': createdOrderId,
'status': 2,
'note': '接口测试 - 已更新',
'items': [
{
'id': 'item_1',
'name': '红烧肉',
'source': 0,
'quantity': 2,
'price': 38.0,
'ingredients': '五花肉、酱油、冰糖',
'note': '多放糖',
},
],
};
final url = '$baseUrl/kitchen.php?act=update';
print(' POST $url');
try {
final output = await _curl(
'POST',
url,
body: jsonEncode(updateData),
headers: {'Accept': 'application/json'},
);
_printResult(output);
} catch (e) {
print(' ❌ 请求失败: $e');
}
}
Future<void> testSSE() async {
final url = '$baseUrl/kitchen_sse.php?order_id=${createdOrderId ?? "test"}';
print(' GET (SSE) $url');
try {
_curlCounter++;
final tmpFile = File(
'${Directory.systemTemp.path}/kitchen_sse_test_$_curlCounter.txt',
);
final args = [
'-sS',
'-N',
'--max-time',
'6',
'--compressed',
'-o',
tmpFile.path,
'-H',
'Accept: text/event-stream',
'-H',
'Cache-Control: no-cache',
url,
];
await Process.run('curl.exe', args);
if (!tmpFile.existsSync()) {
print(' ⚠️ SSE无输出');
return;
}
final bytes = tmpFile.readAsBytesSync();
try {
tmpFile.deleteSync();
} catch (_) {}
final output = utf8.decode(bytes, allowMalformed: true).trim();
if (output.isEmpty) {
print(' ⚠️ SSE无输出');
return;
}
final lines = output.split('\n').where((l) => l.trim().isNotEmpty).toList();
print(' 收到 ${lines.length} 行SSE数据:');
for (var i = 0; i < lines.length && i < 10; i++) {
print(' 📡 ${lines[i]}');
}
final hasConnected = lines.any((l) => l.contains('connected'));
final hasHeartbeat = lines.any((l) => l.contains('heartbeat'));
final hasOrderUpdate = lines.any((l) => l.contains('order_update'));
if (hasConnected) print(' ✅ SSE连接事件已收到');
if (hasHeartbeat) print(' ✅ SSE心跳事件已收到');
if (hasOrderUpdate) print(' ✅ SSE订单更新事件已收到');
if (!hasConnected && !hasHeartbeat) print(' ⚠️ 未收到标准SSE事件');
} catch (e) {
print(' ❌ SSE请求失败: $e');
}
}
Map<String, dynamic> _parseResult(String body) {
try {
return jsonDecode(body) as Map<String, dynamic>;
} catch (e) {
return {'code': -1, 'message': 'JSON解析失败', 'raw': body};
}
}
void _printResult(String body) {
try {
final json = jsonDecode(body) as Map<String, dynamic>;
final code = json['code'];
final message = json['message'];
if (code == 200) {
print(' ✅ code: $code | $message');
final data = json['data'];
if (data != null) {
final dataStr = jsonEncode(data);
if (dataStr.length > 300) {
print(' data: ${dataStr.substring(0, 300)}...');
} else {
print(' data: $dataStr');
}
}
} else {
print(' ⚠️ code: $code | $message');
final data = json['data'];
if (data != null) print(' data: ${jsonEncode(data)}');
}
} catch (e) {
if (body.length > 300) {
print(' ${body.substring(0, 300)}...');
} else {
print(' $body');
}
}
}