import 'package:flutter_test/flutter_test.dart'; /// 守住 `Api.*` 返回值的解包口径。 /// /// 背景:`DioManager.request` 结尾是 `return response.data;` —— 返回的是**data 本身**, /// 不是 Response。全仓既有调用方都直接下标取字段(`response['devices']`)。 /// /// 早先 MemoryService._data 写成了 `resp.data`。因为 request 的返回类型是 dynamic, /// **编译和 dart analyze 都不报错**,而且后端没部署时返回 null,正好走空分支看不出来; /// 一旦后端真的返回成功,每一次调用都会抛 NoSuchMethodError。 /// /// 这个用例把两种形状都过一遍,任何人再改回 `.data` 都会在这里红。 void main() { group('Api 返回值解包', () { // 与 MemoryService._data 同一份逻辑(那个是私有方法,这里复刻一份守住口径) Map? unwrap(dynamic resp) { if (resp == null) return null; if (resp is Map) return Map.from(resp); return null; } test('成功:直接拿到 data Map,能取到字段', () { final resp = { 'items': [ {'id': 1, 'title': '开会'} ], 'total': 1, }; final data = unwrap(resp); expect(data, isNotNull); expect(data!['total'], 1); expect((data['items'] as List).length, 1); }); test('成功但没数据:AuthInterceptor 给的是 {} 而不是 null', () { final data = unwrap({}); // {} 表示「成功但没数据」,必须是非 null —— 与业务错误的 null 含义相反 expect(data, isNotNull); expect(data, isEmpty); }); test('业务错误:AuthInterceptor 把 data 置 null 并 resolve(不抛)', () { expect(unwrap(null), isNull); }); test('拿到 Response 这类对象(口径写错时的形状)一律当无数据,不能崩', () { // 万一有人把 Response 传进来,宁可返回 null 也不要抛 expect(unwrap(Object()), isNull); expect(unwrap('unexpected string'), isNull); expect(unwrap(42), isNull); }); }); }