Files
oott/frontend/test/api/devices_api_test.dart
T
rzuastiandClaude Opus 4.8 844d7d189c Add "go to last page" and page count to notifications and devices lists
The list endpoints now return a total count alongside the page so the
front-end can show how many pages exist and offer a last-page jump.

Backend: add count(is_new) and count_devices(...) (sharing a WHERE-builder
with list_devices so page and count can't drift), wrap both list responses
in {items, total_count} structs, and register them with utoipa.

Front-end: parse the wrapper shape (dropping the fetch-one-extra trick),
add a Last-page button and a responsive "Page X of Y" / "X / Y" label to
the shared PaginationBar, and track the total in both lists. Notifications
re-sync the count on every fetch and decrement it locally on mark-read/
unread removals so the count stays accurate without a re-fetch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 21:09:25 -04:00

185 lines
4.6 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:frontend/model/device_type.dart';
import 'package:frontend/utils/oott_api.dart';
import 'package:http_mock_adapter/http_mock_adapter.dart';
import '../helpers/backend_test_harness.dart';
import '../helpers/fixtures.dart';
void main() {
late DioAdapter adapter;
setUp(() async {
adapter = await setUpBackendForTest();
});
test('getDevice fetches and decodes a single device', () async {
adapter.onGet(
'/devices/aa:bb:cc:dd:ee:ff',
(server) => server.reply(200, deviceJson(owner: 'carol')),
);
final device = await BackendAPI.instance.getDevice('aa:bb:cc:dd:ee:ff');
expect(device.owner, 'carol');
});
test('getDeviceSummary GETs /devices/summary', () async {
adapter.onGet(
'/devices/summary',
(server) => server.reply(200, deviceSummaryJson(totalRegistered: 7)),
);
final summary = await BackendAPI.instance.getDeviceSummary();
expect(summary.totalRegistered, 7);
});
test(
'listDevices sends filter + pagination params and reports the total',
() async {
adapter.onGet(
'/devices',
(server) => server.reply(
200,
pagedListJson([
deviceJson(macAddress: '00:00:00:00:00:01'),
deviceJson(macAddress: '00:00:00:00:00:02'),
], totalCount: 12),
),
queryParameters: {
'is_registered': false,
'device_type': 'laptop',
'sort_by': 'last_seen',
'sort_order': 'asc',
'page_offset': 0,
'page_limit': 2,
},
);
final result = await BackendAPI.instance.listDevices(
isRegistered: false,
deviceType: DeviceType.laptop,
sortBy: 'last_seen',
sortAscending: true,
page: 0,
perPage: 2,
);
expect(result.items, hasLength(2));
expect(result.totalCount, 12);
},
);
test(
'listDevices reports the total when a single page is returned',
() async {
adapter.onGet(
'/devices',
(server) => server.reply(200, pagedListJson([deviceJson()])),
);
final result = await BackendAPI.instance.listDevices(perPage: 2);
expect(result.items, hasLength(1));
expect(result.totalCount, 1);
},
);
test('listDevices maps the unknown device type to an empty filter', () async {
adapter.onGet(
'/devices',
(server) => server.reply(200, pagedListJson([])),
queryParameters: {'device_type': '', 'page_offset': 0, 'page_limit': 10},
);
final result = await BackendAPI.instance.listDevices(
deviceType: DeviceType.unknown,
);
expect(result.items, isEmpty);
});
test('registerDevice PUTs the device payload', () async {
adapter.onPut(
'/devices',
(server) => server.reply(200, null),
data: {
'mac_address': 'aa:bb:cc:dd:ee:ff',
'owner': 'dave',
'device_type': 'phone',
'name': 'Dave Phone',
},
);
await BackendAPI.instance.registerDevice(
'aa:bb:cc:dd:ee:ff',
'dave',
'phone',
name: 'Dave Phone',
);
});
test('updateDevice PUTs to the device path', () async {
adapter.onPut(
'/devices/aa:bb:cc:dd:ee:ff',
(server) => server.reply(200, null),
data: {
'owner': 'erin',
'device_type': 'tv',
'vendor': 'Globex',
'name': null,
},
);
await BackendAPI.instance.updateDevice(
'aa:bb:cc:dd:ee:ff',
'erin',
'tv',
'Globex',
);
});
test('forgetDevice DELETEs the device path', () async {
adapter.onDelete(
'/devices/aa:bb:cc:dd:ee:ff',
(server) => server.reply(200, null),
);
await BackendAPI.instance.forgetDevice('aa:bb:cc:dd:ee:ff');
});
test('getDeviceEvents decodes a list of events', () async {
adapter.onGet(
'/devices/aa:bb:cc:dd:ee:ff/events',
(server) => server.reply(200, [
deviceEventJson(id: 1, scanner: 'Arp'),
deviceEventJson(id: 2, scanner: 'Mdns'),
]),
);
final events = await BackendAPI.instance.getDeviceEvents(
'aa:bb:cc:dd:ee:ff',
);
expect(events, hasLength(2));
expect(events.first.scannerLabel, 'ARP');
});
test('getDeviceEvents sends created_from when provided', () async {
final from = DateTime.utc(2026, 5, 1, 0, 0, 0);
adapter.onGet(
'/devices/aa:bb:cc:dd:ee:ff/events',
(server) => server.reply(200, <dynamic>[]),
queryParameters: {'created_from': from.toIso8601String()},
);
final events = await BackendAPI.instance.getDeviceEvents(
'aa:bb:cc:dd:ee:ff',
createdFrom: from,
);
expect(events, isEmpty);
});
}