308 lines
9.9 KiB
Dart
308 lines
9.9 KiB
Dart
import 'dart:convert';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:pad_scanner/services/api_service.dart';
|
|
|
|
void main() {
|
|
group('ApiService.registerLocation', () {
|
|
test('returns ok on 200', () async {
|
|
final mockClient = _MockClient((request) async {
|
|
return http.Response(
|
|
jsonEncode({
|
|
'zongpai_no': '26B1',
|
|
'location_code': 'A01-02-03',
|
|
'created_at': '2026-05-11T10:00:00',
|
|
}),
|
|
200,
|
|
headers: {'content-type': 'application/json; charset=utf-8'},
|
|
);
|
|
});
|
|
final svc = ApiService(client: mockClient);
|
|
final result = await svc.registerLocation(
|
|
baseUrl: 'http://localhost',
|
|
zongpaiNo: '26B1',
|
|
locationCode: 'A01-02-03',
|
|
);
|
|
expect(result.success, isTrue);
|
|
expect(result.isDuplicate, isFalse);
|
|
expect(result.isOffShelfSuccess, isFalse);
|
|
});
|
|
|
|
test('returns off-shelf success on 200 with previous_location', () async {
|
|
final mockClient = _MockClient((request) async {
|
|
return http.Response(
|
|
jsonEncode({
|
|
'zongpai_no': '26B1',
|
|
'location_code': 'TRANS-01',
|
|
'previous_location': 'A01-02-03',
|
|
'created_at': '2026-05-13T10:00:00',
|
|
}),
|
|
200,
|
|
headers: {'content-type': 'application/json; charset=utf-8'},
|
|
);
|
|
});
|
|
final svc = ApiService(client: mockClient);
|
|
final result = await svc.registerLocation(
|
|
baseUrl: 'http://localhost',
|
|
zongpaiNo: '26B1',
|
|
locationCode: 'TRANS-01',
|
|
);
|
|
expect(result.success, isTrue);
|
|
expect(result.isOffShelfSuccess, isTrue);
|
|
expect(result.isDuplicate, isFalse);
|
|
});
|
|
|
|
test(
|
|
'returns duplicate on 409 with location_code and registered_at',
|
|
() async {
|
|
final mockClient = _MockClient((request) async {
|
|
return http.Response(
|
|
jsonEncode({
|
|
'error_code': 'DUPLICATE_LOCATION',
|
|
'message': '该总排号已存在货位记录',
|
|
'location_code': 'A01-02-03',
|
|
'registered_at': '2026-05-11T10:00:00',
|
|
}),
|
|
409,
|
|
headers: {'content-type': 'application/json; charset=utf-8'},
|
|
);
|
|
});
|
|
final svc = ApiService(client: mockClient);
|
|
final result = await svc.registerLocation(
|
|
baseUrl: 'http://localhost',
|
|
zongpaiNo: '26B1',
|
|
locationCode: 'A02-01-01',
|
|
);
|
|
expect(result.success, isFalse);
|
|
expect(result.isDuplicate, isTrue);
|
|
expect(result.duplicateInfo?['location_code'], 'A01-02-03');
|
|
expect(result.duplicateInfo?['registered_at'], '2026-05-11T10:00:00');
|
|
},
|
|
);
|
|
|
|
test('returns already off shelf on 409 ALREADY_OFF_SHELF', () async {
|
|
final mockClient = _MockClient((request) async {
|
|
return http.Response(
|
|
jsonEncode({
|
|
'error_code': 'ALREADY_OFF_SHELF',
|
|
'message': '该总排号已下架至转运区域,不可重新上架',
|
|
'location_code': 'TRANS-01',
|
|
'registered_at': '2026-05-13T10:00:00',
|
|
}),
|
|
409,
|
|
headers: {'content-type': 'application/json; charset=utf-8'},
|
|
);
|
|
});
|
|
final svc = ApiService(client: mockClient);
|
|
final result = await svc.registerLocation(
|
|
baseUrl: 'http://localhost',
|
|
zongpaiNo: '26B1',
|
|
locationCode: 'A02-01-01',
|
|
);
|
|
expect(result.success, isFalse);
|
|
expect(result.isDuplicate, isFalse);
|
|
expect(result.isAlreadyOffShelf, isTrue);
|
|
expect(result.errorMessage, '该总排号已下架至转运区域,不可重新上架');
|
|
expect(result.conflictInfo?['location_code'], 'TRANS-01');
|
|
});
|
|
|
|
test('returns error on 400 with message', () async {
|
|
final mockClient = _MockClient((request) async {
|
|
return http.Response(
|
|
jsonEncode({
|
|
'error_code': 'INVALID_ZONGPAI',
|
|
'message': 'INVALID_ZONGPAI',
|
|
}),
|
|
400,
|
|
headers: {'content-type': 'application/json; charset=utf-8'},
|
|
);
|
|
});
|
|
final svc = ApiService(client: mockClient);
|
|
final result = await svc.registerLocation(
|
|
baseUrl: 'http://localhost',
|
|
zongpaiNo: 'INVALID',
|
|
locationCode: 'A01-02-03',
|
|
);
|
|
expect(result.success, isFalse);
|
|
expect(result.isDuplicate, isFalse);
|
|
expect(result.errorMessage, 'INVALID_ZONGPAI');
|
|
});
|
|
|
|
test('returns error on network failure', () async {
|
|
final mockClient = _MockClient((request) async {
|
|
throw Exception('Connection refused');
|
|
});
|
|
final svc = ApiService(client: mockClient, timeout: Duration(seconds: 1));
|
|
final result = await svc.registerLocation(
|
|
baseUrl: 'http://localhost',
|
|
zongpaiNo: '26B1',
|
|
locationCode: 'A01-02-03',
|
|
);
|
|
expect(result.success, isFalse);
|
|
expect(result.errorMessage, '网络异常,请检查网络连接');
|
|
});
|
|
});
|
|
|
|
group('ApiService boxing APIs', () {
|
|
test('fetchBoxInfo parses current boxes and box item ids', () async {
|
|
final mockClient = _MockClient((request) async {
|
|
return http.Response(
|
|
jsonEncode({
|
|
'zongpai_no': '26BW0011',
|
|
'paichan_no': 'W00009',
|
|
'work_order_no': '6-1(7)',
|
|
'quantity': 80,
|
|
'current_zongpai_boxes': [
|
|
{'box_item_id': 101, 'box_no': 3, 'quantity': 30},
|
|
],
|
|
'existing_boxes': [
|
|
{
|
|
'box_no': 3,
|
|
'items': [
|
|
{
|
|
'box_item_id': 101,
|
|
'zongpai_no': '26BW0011',
|
|
'work_order_no': '6-1(7)',
|
|
'quantity': 30,
|
|
'total_quantity': 80,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
'max_box_no': 3,
|
|
'suggested_box_no': 4,
|
|
}),
|
|
200,
|
|
headers: {'content-type': 'application/json; charset=utf-8'},
|
|
);
|
|
});
|
|
|
|
final svc = ApiService(client: mockClient);
|
|
final result = await svc.fetchBoxInfo(
|
|
baseUrl: 'http://localhost',
|
|
zongpaiNo: '26BW0011',
|
|
);
|
|
|
|
expect(result.success, isTrue);
|
|
expect(result.currentZongpaiBoxes.single.boxItemId, 101);
|
|
expect(result.existingBoxes.single.items.single.boxItemId, 101);
|
|
expect(result.existingBoxes.single.items.single.totalQuantity, 80);
|
|
expect(result.suggestedBoxNo, 4);
|
|
});
|
|
|
|
test('saveBoxRecord parses zongpai number from success response', () async {
|
|
final mockClient = _MockClient((request) async {
|
|
return http.Response(
|
|
jsonEncode({
|
|
'box_item_id': 102,
|
|
'paichan_no': 'W00009',
|
|
'box_no': 4,
|
|
'zongpai_no': '26BW0012',
|
|
'quantity': 34,
|
|
'created_at': '2026-05-13T10:00:00',
|
|
}),
|
|
200,
|
|
headers: {'content-type': 'application/json; charset=utf-8'},
|
|
);
|
|
});
|
|
|
|
final svc = ApiService(client: mockClient);
|
|
final result = await svc.saveBoxRecord(
|
|
baseUrl: 'http://localhost',
|
|
zongpaiNo: '26BW0012',
|
|
boxNo: 4,
|
|
quantity: 34,
|
|
boxMode: 'many-to-one',
|
|
);
|
|
|
|
expect(result.success, isTrue);
|
|
expect(result.boxItemId, 102);
|
|
expect(result.zongpaiNo, '26BW0012');
|
|
});
|
|
|
|
test('updateBoxRecord sends many-to-one box mode', () async {
|
|
late Map<String, dynamic> body;
|
|
final mockClient = _MockClient((request) async {
|
|
final req = request as http.Request;
|
|
body = jsonDecode(req.body) as Map<String, dynamic>;
|
|
return http.Response(
|
|
jsonEncode({
|
|
'box_item_id': 102,
|
|
'paichan_no': 'W00009',
|
|
'box_no': 4,
|
|
'zongpai_no': '26BW0012',
|
|
'quantity': 20,
|
|
'updated_at': '2026-05-13T10:00:00',
|
|
}),
|
|
200,
|
|
headers: {'content-type': 'application/json; charset=utf-8'},
|
|
);
|
|
});
|
|
|
|
final svc = ApiService(client: mockClient);
|
|
final result = await svc.updateBoxRecord(
|
|
baseUrl: 'http://localhost',
|
|
boxItemId: 102,
|
|
boxNo: 4,
|
|
quantity: 20,
|
|
boxMode: 'many-to-one',
|
|
);
|
|
|
|
expect(result.success, isTrue);
|
|
expect(body['box_mode'], 'many-to-one');
|
|
});
|
|
|
|
test('deleteBoxRecord handles success and not found', () async {
|
|
var calls = 0;
|
|
final mockClient = _MockClient((request) async {
|
|
calls += 1;
|
|
if (calls == 1) {
|
|
return http.Response(
|
|
jsonEncode({'box_item_id': 102, 'deleted': true}),
|
|
200,
|
|
headers: {'content-type': 'application/json; charset=utf-8'},
|
|
);
|
|
}
|
|
return http.Response(
|
|
jsonEncode({
|
|
'error_code': 'BOX_ITEM_NOT_FOUND',
|
|
'message': '指定装箱明细不存在',
|
|
}),
|
|
404,
|
|
headers: {'content-type': 'application/json; charset=utf-8'},
|
|
);
|
|
});
|
|
|
|
final svc = ApiService(client: mockClient);
|
|
final ok = await svc.deleteBoxRecord(
|
|
baseUrl: 'http://localhost',
|
|
boxItemId: 102,
|
|
);
|
|
final missing = await svc.deleteBoxRecord(
|
|
baseUrl: 'http://localhost',
|
|
boxItemId: 999,
|
|
);
|
|
|
|
expect(ok.success, isTrue);
|
|
expect(missing.success, isFalse);
|
|
expect(missing.errorMessage, '指定装箱明细不存在');
|
|
});
|
|
});
|
|
}
|
|
|
|
class _MockClient extends http.BaseClient {
|
|
final Future<http.Response> Function(http.BaseRequest) _handler;
|
|
_MockClient(this._handler);
|
|
|
|
@override
|
|
Future<http.StreamedResponse> send(http.BaseRequest request) async {
|
|
final response = await _handler(request);
|
|
return http.StreamedResponse(
|
|
http.ByteStream.fromBytes(response.bodyBytes),
|
|
response.statusCode,
|
|
headers: response.headers,
|
|
reasonPhrase: response.reasonPhrase,
|
|
);
|
|
}
|
|
}
|