feat: surface device and scene load errors
This commit is contained in:
@@ -6,28 +6,77 @@ import 'package:ignis_app/providers/providers.dart';
|
||||
import 'package:ignis_app/services/api_client.dart';
|
||||
|
||||
class FakeIgnisApi extends IgnisApi {
|
||||
Object? devicesData;
|
||||
Object? scenesData;
|
||||
Object? tasksData;
|
||||
Object? statsData;
|
||||
Object? eventLogData;
|
||||
Object? apiKeysData;
|
||||
Object? devicesError;
|
||||
Object? scenesError;
|
||||
Object? tasksError;
|
||||
Object? statsError;
|
||||
Object? eventLogError;
|
||||
Object? apiKeysError;
|
||||
Object? controlGroupError;
|
||||
Object? cancelTaskError;
|
||||
Object? revokeApiKeyError;
|
||||
String? controlledGroupId;
|
||||
Map<String, dynamic>? controlGroupParams;
|
||||
int? requestedDays;
|
||||
int? requestedLimit;
|
||||
String? cancelledJobId;
|
||||
String? revokedApiKey;
|
||||
|
||||
FakeIgnisApi({
|
||||
this.devicesData,
|
||||
this.scenesData,
|
||||
this.tasksData,
|
||||
this.statsData,
|
||||
this.eventLogData,
|
||||
this.apiKeysData,
|
||||
});
|
||||
|
||||
@override
|
||||
Future<Response> getDevices() async {
|
||||
final error = devicesError;
|
||||
if (error != null) throw error;
|
||||
return Response(
|
||||
requestOptions: RequestOptions(path: '/devices'),
|
||||
data: devicesData,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response> getScenes() async {
|
||||
final error = scenesError;
|
||||
if (error != null) throw error;
|
||||
return Response(
|
||||
requestOptions: RequestOptions(path: '/devices/scenes'),
|
||||
data: scenesData,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response> getGroups() async {
|
||||
return Response(
|
||||
requestOptions: RequestOptions(path: '/devices/groups'),
|
||||
data: <Object>[],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response> controlGroup(String id, Map<String, dynamic> params) async {
|
||||
controlledGroupId = id;
|
||||
controlGroupParams = params;
|
||||
final error = controlGroupError;
|
||||
if (error != null) throw error;
|
||||
return Response(
|
||||
requestOptions: RequestOptions(path: '/control/group/$id'),
|
||||
data: <String, dynamic>{'ok': true},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response> getTasks() async {
|
||||
final error = tasksError;
|
||||
@@ -120,6 +169,57 @@ void main() {
|
||||
expect(api.requestedDays, 14);
|
||||
});
|
||||
|
||||
test('devices load exposes data state', () async {
|
||||
final api = FakeIgnisApi(
|
||||
devicesData: {
|
||||
'devices': [
|
||||
{'mac': 'AA:BB', 'name': 'Kitchen bulb'},
|
||||
],
|
||||
},
|
||||
);
|
||||
final container = containerWith(api);
|
||||
|
||||
await container.read(devicesProvider.notifier).load();
|
||||
|
||||
final state = container.read(devicesProvider);
|
||||
expect(state.status, LoadStatus.data);
|
||||
expect(state.data, hasLength(1));
|
||||
});
|
||||
|
||||
test('devices load exposes empty state', () async {
|
||||
final api = FakeIgnisApi(devicesData: {'devices': <Object>[]});
|
||||
final container = containerWith(api);
|
||||
|
||||
await container.read(devicesProvider.notifier).load();
|
||||
|
||||
final state = container.read(devicesProvider);
|
||||
expect(state.status, LoadStatus.empty);
|
||||
expect(state.data, isEmpty);
|
||||
});
|
||||
|
||||
test('devices load error exposes message', () async {
|
||||
final api = FakeIgnisApi(
|
||||
devicesData: [
|
||||
{'mac': 'AA:BB'},
|
||||
],
|
||||
);
|
||||
final container = containerWith(api);
|
||||
|
||||
await container.read(devicesProvider.notifier).load();
|
||||
api.devicesError = DioException(
|
||||
requestOptions: RequestOptions(path: '/devices'),
|
||||
type: DioExceptionType.connectionError,
|
||||
message: 'No route to host',
|
||||
);
|
||||
|
||||
await container.read(devicesProvider.notifier).load();
|
||||
|
||||
final state = container.read(devicesProvider);
|
||||
expect(state.status, LoadStatus.error);
|
||||
expect(state.data, hasLength(1));
|
||||
expect(state.errorMessage, contains('Backend недоступен'));
|
||||
});
|
||||
|
||||
test('tasks load exposes data state', () async {
|
||||
final api = FakeIgnisApi(
|
||||
tasksData: {
|
||||
@@ -200,6 +300,48 @@ void main() {
|
||||
expect(api.requestedLimit, 50);
|
||||
});
|
||||
|
||||
test('scenes load accepts id-name map and exposes data state', () async {
|
||||
final api = FakeIgnisApi(scenesData: {'party': 'Party', 'relax': 'Relax'});
|
||||
final container = containerWith(api);
|
||||
|
||||
await container.read(scenesProvider.notifier).load();
|
||||
|
||||
final state = container.read(scenesProvider);
|
||||
expect(state.status, LoadStatus.data);
|
||||
expect(state.data, hasLength(2));
|
||||
expect(state.data.first, containsPair('id', 'party'));
|
||||
});
|
||||
|
||||
test('scenes load exposes empty state', () async {
|
||||
final api = FakeIgnisApi(scenesData: {'scenes': <Object>[]});
|
||||
final container = containerWith(api);
|
||||
|
||||
await container.read(scenesProvider.notifier).load();
|
||||
|
||||
final state = container.read(scenesProvider);
|
||||
expect(state.status, LoadStatus.empty);
|
||||
expect(state.data, isEmpty);
|
||||
});
|
||||
|
||||
test('scenes load error exposes message', () async {
|
||||
final api = FakeIgnisApi(scenesData: ['Party']);
|
||||
final container = containerWith(api);
|
||||
|
||||
await container.read(scenesProvider.notifier).load();
|
||||
api.scenesError = DioException(
|
||||
requestOptions: RequestOptions(path: '/devices/scenes'),
|
||||
type: DioExceptionType.connectionError,
|
||||
message: 'No route to host',
|
||||
);
|
||||
|
||||
await container.read(scenesProvider.notifier).load();
|
||||
|
||||
final state = container.read(scenesProvider);
|
||||
expect(state.status, LoadStatus.error);
|
||||
expect(state.data, hasLength(1));
|
||||
expect(state.errorMessage, contains('Backend недоступен'));
|
||||
});
|
||||
|
||||
test('api keys load exposes data state', () async {
|
||||
final api = FakeIgnisApi(
|
||||
apiKeysData: {
|
||||
@@ -315,4 +457,25 @@ void main() {
|
||||
);
|
||||
expect(api.revokedApiKey, 'secret');
|
||||
});
|
||||
|
||||
test('set scene error is not swallowed', () async {
|
||||
final api = FakeIgnisApi();
|
||||
final container = containerWith(api);
|
||||
final error = DioException(
|
||||
requestOptions: RequestOptions(path: '/control/group/kitchen'),
|
||||
type: DioExceptionType.badResponse,
|
||||
response: Response(
|
||||
requestOptions: RequestOptions(path: '/control/group/kitchen'),
|
||||
statusCode: 500,
|
||||
),
|
||||
);
|
||||
api.controlGroupError = error;
|
||||
|
||||
await expectLater(
|
||||
container.read(groupsProvider.notifier).setScene('kitchen', 'party'),
|
||||
throwsA(same(error)),
|
||||
);
|
||||
expect(api.controlledGroupId, 'kitchen');
|
||||
expect(api.controlGroupParams, containsPair('scene', 'party'));
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user