import 'package:dio/dio.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:http_mock_adapter/http_mock_adapter.dart'; import 'package:jiu_client/core/api/api_client.dart'; import 'package:jiu_client/core/exceptions.dart'; import 'package:jiu_client/repositories/warehouse_repository.dart'; const _baseUrl = 'http://localhost:8080/api/v1'; class _TestApiClient extends ApiClient { final Dio _testDio; _TestApiClient(this._testDio) : super(token: 'test-token'); @override Future get(String path, {Map? params}) => _testDio.get(path, queryParameters: params); @override Future post(String path, {dynamic data}) => _testDio.post(path, data: data); @override Future put(String path, {dynamic data}) => _testDio.put(path, data: data); @override Future delete(String path) => _testDio.delete(path); } void main() { late Dio dio; late DioAdapter adapter; late WarehouseRepository repo; setUp(() { dio = Dio(BaseOptions(baseUrl: _baseUrl)); adapter = DioAdapter(dio: dio, matcher: const FullHttpRequestMatcher()); repo = WarehouseRepository(_TestApiClient(dio)); }); // --------------------------------------------------------------------------- // list() // --------------------------------------------------------------------------- group('WarehouseRepository.list()', () { test('returns list of warehouses on 200', () async { adapter.onGet( '/warehouses', (server) => server.reply(200, { 'data': [ {'id': 1, 'name': '主仓库', 'location': '一楼', 'is_default': true}, {'id': 2, 'name': '冷藏仓', 'location': '地下室', 'is_default': false}, ] }), ); final list = await repo.list(); expect(list.length, 2); expect(list.first.name, '主仓库'); expect(list.first.isDefault, true); expect(list[1].name, '冷藏仓'); expect(list[1].isDefault, false); }); test('returns empty list without error when data is empty', () async { adapter.onGet( '/warehouses', (server) => server.reply(200, {'data': []}), ); final list = await repo.list(); expect(list, isEmpty); }); test('401 response throws AppException with status 401', () async { adapter.onGet( '/warehouses', (server) => server.reply(401, {'error': 'unauthorized'}), ); expect( () => repo.list(), throwsA( predicate((e) => e.statusCode == 401), ), ); }); test('400 response throws AppException with server error message', () async { adapter.onGet( '/warehouses', (server) => server.reply(400, {'error': 'bad request'}), ); expect( () => repo.list(), throwsA( predicate( (e) => e.message == 'bad request' && e.statusCode == 400, ), ), ); }); test('network error throws AppException with fallback message', () async { final badDio = Dio(BaseOptions( baseUrl: 'http://localhost:19999', connectTimeout: const Duration(milliseconds: 100), )); final badRepo = WarehouseRepository(_TestApiClient(badDio)); try { await badRepo.list(); fail('Expected AppException'); } on AppException catch (e) { expect(e.message, isNotEmpty); } }); }); // --------------------------------------------------------------------------- // create() // --------------------------------------------------------------------------- group('WarehouseRepository.create()', () { test('returns created Warehouse on 201', () async { final payload = {'name': '新仓库', 'location': '二楼', 'is_default': false}; adapter.onPost( '/warehouses', (server) => server.reply(201, { 'data': { 'id': 3, 'name': '新仓库', 'location': '二楼', 'is_default': false, } }), data: payload, ); final warehouse = await repo.create(payload); expect(warehouse.id, 3); expect(warehouse.name, '新仓库'); expect(warehouse.isDefault, false); }); test('400 response throws AppException', () async { adapter.onPost( '/warehouses', (server) => server.reply(400, {'error': 'name is required'}), data: {'name': '', 'is_default': false}, ); expect( () => repo.create({'name': '', 'is_default': false}), throwsA(isA()), ); }); }); // --------------------------------------------------------------------------- // update() // --------------------------------------------------------------------------- group('WarehouseRepository.update()', () { test('returns updated Warehouse on 200', () async { final payload = {'name': '主仓库(改)', 'is_default': true}; adapter.onPut( '/warehouses/1', (server) => server.reply(200, { 'data': { 'id': 1, 'name': '主仓库(改)', 'location': '一楼', 'is_default': true, } }), data: payload, ); final warehouse = await repo.update(1, payload); expect(warehouse.id, 1); expect(warehouse.name, '主仓库(改)'); }); test('404 throws AppException', () async { adapter.onPut( '/warehouses/999', (server) => server.reply(404, {'error': 'not found'}), data: {'name': 'X'}, ); expect( () => repo.update(999, {'name': 'X'}), throwsA( predicate((e) => e.statusCode == 404), ), ); }); }); // --------------------------------------------------------------------------- // delete() // --------------------------------------------------------------------------- group('WarehouseRepository.delete()', () { test('completes without error on 200', () async { adapter.onDelete( '/warehouses/1', (server) => server.reply(200, {'message': 'deleted'}), ); await expectLater(repo.delete(1), completes); }); test('404 response throws AppException', () async { adapter.onDelete( '/warehouses/999', (server) => server.reply(404, {'error': 'not found'}), ); expect( () => repo.delete(999), throwsA(isA()), ); }); }); }