From 3999d7584e147730afb02bb1800b7d44ef5c4759 Mon Sep 17 00:00:00 2001 From: Yaros Date: Fri, 24 May 2024 13:17:26 +0200 Subject: [PATCH] Added unit tests for the API & GitHub Actions --- .github/workflows/dart_test.yml | 29 +++ CHANGELOG.md | 4 + README.md | 7 +- lib/src/api/departures.dart | 43 ++--- lib/src/api/route_line.dart | 20 ++- lib/src/api/stations.dart | 8 +- pubspec.yaml | 2 +- test/api/departures_test.dart | 256 +++++++++++++++++++++++++++ test/api/route_line_test.dart | 195 ++++++++++++++++++++ test/api/stations_test.dart | 132 ++++++++++++++ test/realtime/bus_position_test.dart | 27 +++ test/realtime/bus_stopped_test.dart | 54 ++++++ test/realtime/station_info_test.dart | 101 +++++++++++ 13 files changed, 845 insertions(+), 33 deletions(-) create mode 100644 .github/workflows/dart_test.yml create mode 100644 test/api/departures_test.dart create mode 100644 test/api/route_line_test.dart create mode 100644 test/api/stations_test.dart create mode 100644 test/realtime/bus_position_test.dart create mode 100644 test/realtime/bus_stopped_test.dart create mode 100644 test/realtime/station_info_test.dart diff --git a/.github/workflows/dart_test.yml b/.github/workflows/dart_test.yml new file mode 100644 index 0000000..b4d3550 --- /dev/null +++ b/.github/workflows/dart_test.yml @@ -0,0 +1,29 @@ +name: Dart Test and Analysis + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: dart-lang/setup-dart@v1 + + - name: Format code + run: dart format --set-exit-if-changed . + + - name: Get dependencies + run: dart pub get + + - name: Run tests + run: dart test + + - name: Analyze code + run: dart analyze diff --git a/CHANGELOG.md b/CHANGELOG.md index d0d2a83..30be506 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## 0.5.7 + +- Added unit tests for the API + ## 0.5.6 - Added Connection Close message type to realtime websocket API diff --git a/README.md b/README.md index 3bd3615..a7acb14 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # TIB API - + +TIB Logo ## Features @@ -23,7 +24,7 @@ Install the package by adding it to your `pubspec.yaml` file: ```yaml dependencies: - tib_api: ^0.5.6 + tib_api: ^0.5.7 ``` ## Usage @@ -107,8 +108,8 @@ await RouteLine.getPdfTimetable('A42'); Full example can be found in the [example.dart](example/tib_api_example.dart) ## Facing Issues? + TIB's website is pretty unstable, and changes can occur. I'm trying my best to make this API as bug-free as possible, which can't be said about their website. - diff --git a/lib/src/api/departures.dart b/lib/src/api/departures.dart index f5d7e4d..2479f12 100644 --- a/lib/src/api/departures.dart +++ b/lib/src/api/departures.dart @@ -5,6 +5,7 @@ import 'dart:typed_data'; import 'package:http/http.dart'; class Departures { + static Client httpClient = Client(); List? departures; Departures({this.departures}); @@ -21,27 +22,27 @@ class Departures { {required int stationCode, required int numberOfDepartures}) async { Uri url = Uri.parse( 'http://tib.org/o/manager/stop-code/$stationCode/departures/ctmr4?res=$numberOfDepartures'); - // try { - Uint8List responseBytes = await get(url).then((value) => value.bodyBytes); - List departures = []; - for (var response in json.decode(utf8.decode(responseBytes))) { - Departure responseDeparture = Departure.fromJson(response); - departures.add(responseDeparture); - } + try { + Uint8List responseBytes = + await httpClient.get(url).then((value) => value.bodyBytes); + List departures = []; + for (var response in json.decode(utf8.decode(responseBytes))) { + Departure responseDeparture = Departure.fromJson(response); + departures.add(responseDeparture); + } - if (departures.isEmpty) { + if (departures.isEmpty) { + throw Exception( + "No departures found. 😕 Please check that the station code is correct."); + } else { + return departures; + } + } on FormatException { + throw FormatException("The station code is invalid. 😶"); + } catch (e) { throw Exception( - "No departures found. 😕 Please check that the station code is correct."); - } else { - return departures; + "There was an error fetching the departures. 😕 Please try again later."); } - // } on FormatException { - // throw FormatException("The station code is invalid. 😶"); - // } catch (e) { - // print(e); - // throw Exception( - // "There was an error fetching the departures. 😕 Please try again later."); - // } } } @@ -82,7 +83,7 @@ class RealTrip { /// Converts a [RealTrip] object to a JSON map. static String toJson(RealTrip realTrip) { return jsonEncode({ - 'aet': realTrip.estimatedArrival.toString(), + 'aet': realTrip.estimatedArrival?.toIso8601String(), 'lastCoords': {'lat': realTrip.lat, 'lng': realTrip.long}, 'id': realTrip.id }); @@ -143,8 +144,8 @@ class Departure { /// returns a map representing the JSON data. static Map toJson(Departure departure) { return { - 'dt': departure.departureTime.toString(), - 'aet': departure.estimatedArrival.toString(), + 'dt': departure.departureTime.toIso8601String(), + 'aet': departure.estimatedArrival.toIso8601String(), 'snam': departure.name, 'trip_id': departure.tripId, 'realTrip': departure.realTrip != null diff --git a/lib/src/api/route_line.dart b/lib/src/api/route_line.dart index 9e50f5d..8219b34 100644 --- a/lib/src/api/route_line.dart +++ b/lib/src/api/route_line.dart @@ -21,10 +21,13 @@ class RouteLine { LineType type; List? sublines; + static Client httpClient = Client(); + static Future> getAllLines() async { Uri url = Uri.parse("https://ws.tib.org/sictmws-rest/lines/ctmr4"); try { - Uint8List responseBytes = await get(url).then((value) => value.bodyBytes); + Uint8List responseBytes = + await httpClient.get(url).then((value) => value.bodyBytes); List lines = []; for (Map line in json.decode(utf8.decode(responseBytes))["linesInfo"]) { @@ -42,7 +45,8 @@ class RouteLine { Uri url = Uri.parse("https://ws.tib.org/sictmws-rest/lines/ctmr4/$lineCode"); try { - Uint8List responseBytes = await get(url).then((value) => value.bodyBytes); + Uint8List responseBytes = + await httpClient.get(url).then((value) => value.bodyBytes); return RouteLine.fromJson(json.decode(utf8.decode(responseBytes))); } on FormatException { throw FormatException("Something went wrong. 😶"); @@ -51,10 +55,11 @@ class RouteLine { static Future getPdfTimetable(String lineCode) async { try { - final parser = await Chaleno().load( - "https://www.tib.org/es/lineas-y-horarios/autobus/-/linia/$lineCode"); + final response = await httpClient.get(Uri.parse( + "https://www.tib.org/es/lineas-y-horarios/autobus/-/linia/$lineCode")); + final parser = Parser(response.body); Result div = - parser!.getElementsByClassName('ctm-line-schedule-link').first; + parser.getElementsByClassName('ctm-line-schedule-link').first; String? href = div.querySelector('a')!.href; return href != null ? Uri.parse(href) : null; } catch (e) { @@ -225,6 +230,8 @@ class Subline { /// The coordinates are represented by a [LatLng] object. /// The [subline] parameter is the subline of the route path. class RoutePath { + static Client httpClient = Client(); + Subline subline; List> paths; @@ -237,7 +244,8 @@ class RoutePath { Uri url = Uri.parse( "https://ws.tib.org/sictmws-rest/lines/ctmr4/${subline.parentLine.code}/kmz/${subline.code}"); try { - Uint8List responseBytes = await get(url).then((value) => value.bodyBytes); + Uint8List responseBytes = + await httpClient.get(url).then((value) => value.bodyBytes); return RoutePath.fromKmz(utf8.decode(responseBytes), subline); } on FormatException { throw FormatException("Something went wrong. 😶"); diff --git a/lib/src/api/stations.dart b/lib/src/api/stations.dart index f2814ae..c402719 100644 --- a/lib/src/api/stations.dart +++ b/lib/src/api/stations.dart @@ -8,6 +8,8 @@ import 'package:tib_api/src/api/route_line.dart'; /// A station has a code, an ID, a latitude, a longitude, a name, and a reference. /// The reference is optional. class Station { + static Client httpClient = Client(); + int code; int id; double lat; @@ -63,7 +65,8 @@ class Station { Uri url = Uri.parse("https://ws.tib.org/sictmws-rest/stops/ctmr4/$stationCode"); try { - Uint8List responseBytes = await get(url).then((value) => value.bodyBytes); + Uint8List responseBytes = + await httpClient.get(url).then((value) => value.bodyBytes); List lines = []; for (Map line in json.decode(utf8.decode(responseBytes))["lines"]) { @@ -88,7 +91,8 @@ class Station { Uri.parse("https://ws.tib.org/sictmws-rest/stops/ctmr4?res=$count"); try { - Uint8List responseBytes = await get(url).then((value) => value.bodyBytes); + Uint8List responseBytes = + await httpClient.get(url).then((value) => value.bodyBytes); List stations = []; for (Map station diff --git a/pubspec.yaml b/pubspec.yaml index 51676e6..56434d9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: tib_api description: A reverse engineered API for the TIB (Transportes de las Islas Baleares) public transport service. -version: 0.5.6 +version: 0.5.7 repository: https://github.com/YarosMallorca/tib_api issue_tracker: https://github.com/YarosMallorca/tib_api/issues diff --git a/test/api/departures_test.dart b/test/api/departures_test.dart new file mode 100644 index 0000000..79d7d33 --- /dev/null +++ b/test/api/departures_test.dart @@ -0,0 +1,256 @@ +import 'dart:convert'; +import 'package:test/test.dart'; +import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; +import 'package:tib_api/tib_api.dart'; + +void main() { + group('Departures.getDepartures', () { + test('returns list of departures when the API call is successful', + () async { + final mockHttpClient = MockClient((request) async { + return http.Response( + jsonEncode([ + { + "dt": "1970-01-01T10:12:00", + "aet": "2024-05-24T11:39:00", + "snam": "Departure 1", + "trip_id": 558719, + "lineColor": "401", + "dem": false, + "lcod": "401", + "etn": "Manacor", + "dtn": null, + "et": "1970-01-01T11:55:00" + }, + { + "dt": "1970-01-01T11:20:00", + "aet": "2024-05-24T11:42:00", + "snam": "Departure 2", + "trip_id": 554303, + "lineColor": "424", + "dem": false, + "lcod": "424", + "etn": "Cala Rajada", + "dtn": null, + "et": "1970-01-01T12:45:00" + } + ]), + 200); + }); + + Departures.httpClient = mockHttpClient; + + final departures = await Departures.getDepartures( + stationCode: 123, numberOfDepartures: 2); + + expect(departures, isA>()); + expect(departures.length, 2); + expect(departures[0].name, 'Departure 1'); + }); + + test('throws FormatException when the station code is invalid', () async { + final mockHttpClient = MockClient((request) async { + throw FormatException("Invalid format"); + }); + + Departures.httpClient = mockHttpClient; + + expect( + () async => await Departures.getDepartures( + stationCode: 123, numberOfDepartures: 2), + throwsA(isA())); + }); + + test('throws Exception when there are no departures found', () async { + final mockHttpClient = MockClient((request) async { + return http.Response('[]', 200); + }); + + Departures.httpClient = mockHttpClient; + + expect( + () async => await Departures.getDepartures( + stationCode: 123, numberOfDepartures: 2), + throwsA(isA())); + }); + }); + + group('RealTrip', () { + test('fromJson creates a RealTrip object from JSON', () { + final jsonMap = { + 'aet': '2024-05-24T08:30:00Z', + 'lastCoords': {'lat': 40.712776, 'lng': -74.005974}, + 'id': '1' + }; + + final realTrip = RealTrip.fromJson(jsonMap); + + expect(realTrip.estimatedArrival, DateTime.parse('2024-05-24T08:30:00Z')); + expect(realTrip.lat, 40.712776); + expect(realTrip.long, -74.005974); + expect(realTrip.id, 1); + }); + + test('fromJson handles null estimatedArrival', () { + final jsonMap = { + 'aet': null, + 'lastCoords': {'lat': 40.712776, 'lng': -74.005974}, + 'id': '2' + }; + + final realTrip = RealTrip.fromJson(jsonMap); + + expect(realTrip.estimatedArrival, isNull); + expect(realTrip.lat, 40.712776); + expect(realTrip.long, -74.005974); + expect(realTrip.id, 2); + }); + + test('toJson converts a RealTrip object back to JSON', () { + final realTrip = RealTrip( + estimatedArrival: DateTime.parse('2024-05-24T08:30:00Z'), + lat: 40.712776, + long: -74.005974, + id: 1); + + final jsonString = RealTrip.toJson(realTrip); + final jsonMap = jsonDecode(jsonString); + + expect(jsonMap['aet'], '2024-05-24T08:30:00.000Z'); + expect(jsonMap['lastCoords']['lat'], 40.712776); + expect(jsonMap['lastCoords']['lng'], -74.005974); + expect(jsonMap['id'], 1); + }); + + test('toJson handles null estimatedArrival', () { + final realTrip = RealTrip( + estimatedArrival: null, lat: 40.712776, long: -74.005974, id: 2); + + final jsonString = RealTrip.toJson(realTrip); + final jsonMap = jsonDecode(jsonString); + + expect(jsonMap['aet'], null); + expect(jsonMap['lastCoords']['lat'], 40.712776); + expect(jsonMap['lastCoords']['lng'], -74.005974); + expect(jsonMap['id'], 2); + }); + }); + + group('Departure', () { + test('fromJson creates a Departure object from JSON', () { + final jsonMap = { + 'dt': '2024-05-24T08:30:00Z', + 'aet': '2024-05-24T09:00:00Z', + 'snam': 'Bus 42', + 'trip_id': 1, + 'realTrip': { + 'aet': '2024-05-24T09:00:00Z', + 'lastCoords': {'lat': 40.712776, 'lng': -74.005974}, + 'id': '1' + }, + 'dem': true, + 'lcod': 'B42', + 'etn': 'Central Station', + 'et': 'Main Street' + }; + + final departure = Departure.fromJson(jsonMap); + + expect(departure.departureTime, DateTime.parse('2024-05-24T08:30:00Z')); + expect( + departure.estimatedArrival, DateTime.parse('2024-05-24T09:00:00Z')); + expect(departure.name, 'Bus 42'); + expect(departure.tripId, 1); + expect(departure.realTrip, isA()); + expect(departure.delayed, true); + expect(departure.lineCode, 'B42'); + expect(departure.destination, 'Central Station'); + expect(departure.departureStop, 'Main Street'); + }); + + test('fromJson handles null optional fields', () { + final jsonMap = { + 'dt': '2024-05-24T08:30:00Z', + 'aet': '2024-05-24T09:00:00Z', + 'snam': 'Bus 42', + 'trip_id': 1, + 'realTrip': null, + 'dem': false, + 'lcod': 'B42', + 'etn': null, + 'et': null + }; + + final departure = Departure.fromJson(jsonMap); + + expect(departure.departureTime, DateTime.parse('2024-05-24T08:30:00Z')); + expect( + departure.estimatedArrival, DateTime.parse('2024-05-24T09:00:00Z')); + expect(departure.name, 'Bus 42'); + expect(departure.tripId, 1); + expect(departure.realTrip, isNull); + expect(departure.delayed, false); + expect(departure.lineCode, 'B42'); + expect(departure.destination, isNull); + expect(departure.departureStop, isNull); + }); + + test('toJson converts a Departure object to JSON', () { + final realTrip = RealTrip( + estimatedArrival: DateTime.parse('2024-05-24T09:00:00Z'), + lat: 40.712776, + long: -74.005974, + id: 1); + final departure = Departure( + departureTime: DateTime.parse('2024-05-24T08:30:00Z'), + estimatedArrival: DateTime.parse('2024-05-24T09:00:00Z'), + name: 'Bus 42', + tripId: 1, + realTrip: realTrip, + delayed: true, + lineCode: 'B42', + destination: 'Central Station', + departureStop: 'Main Street'); + + final jsonMap = Departure.toJson(departure); + + expect(jsonMap['dt'], '2024-05-24T08:30:00.000Z'); + expect(jsonMap['aet'], '2024-05-24T09:00:00.000Z'); + expect(jsonMap['snam'], 'Bus 42'); + expect(jsonMap['trip_id'], 1); + expect(jsonMap['realTrip'], isA()); + expect( + jsonDecode(jsonMap['realTrip'])['aet'], '2024-05-24T09:00:00.000Z'); + expect(jsonMap['dem'], true); + expect(jsonMap['lcod'], 'B42'); + expect(jsonMap['etn'], 'Central Station'); + expect(jsonMap['et'], 'Main Street'); + }); + + test('toJson handles null optional fields', () { + final departure = Departure( + departureTime: DateTime.parse('2024-05-24T08:30:00Z'), + estimatedArrival: DateTime.parse('2024-05-24T09:00:00Z'), + name: 'Bus 42', + tripId: 1, + realTrip: null, + delayed: false, + lineCode: 'B42', + destination: null, + departureStop: null); + + final jsonMap = Departure.toJson(departure); + + expect(jsonMap['dt'], '2024-05-24T08:30:00.000Z'); + expect(jsonMap['aet'], '2024-05-24T09:00:00.000Z'); + expect(jsonMap['snam'], 'Bus 42'); + expect(jsonMap['trip_id'], 1); + expect(jsonMap['realTrip'], isNull); + expect(jsonMap['dem'], false); + expect(jsonMap['lcod'], 'B42'); + expect(jsonMap['etn'], isNull); + expect(jsonMap['et'], isNull); + }); + }); +} diff --git a/test/api/route_line_test.dart b/test/api/route_line_test.dart new file mode 100644 index 0000000..d9afe9e --- /dev/null +++ b/test/api/route_line_test.dart @@ -0,0 +1,195 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; +import 'package:test/test.dart'; +import 'package:tib_api/tib_api.dart'; + +void main() { + group('RouteLine', () { + test('fromJson creates a RouteLine object from JSON', () { + final jsonMap = { + "act": true, + "cod": "401", + "color": "#A5CFBA", + "dem": false, + "festius": [ + {"dat": "2024-08-15", "nam": "Assumpció de la Mare de Déu 2024"}, + {"dat": "2024-10-12", "nam": "Festa nacional 2024"}, + {"dat": "2024-11-01", "nam": "Tots Sants 2024"}, + {"dat": "2024-12-06", "nam": "Dia de la Constitució 2024"}, + {"dat": "2024-12-25", "nam": "Dia de Nadal 2024"} + ], + "id": 3066, + "ini": "2020-12-09", + "nam": "Cala Millor - Palma", + "notices": [], + "sec": "400", + "sessions": [ + { + "busTypeId": "I15", + "cur": true, + "end": "1972-12-31", + "ini": "1972-01-01", + "nam": "Cala Millor - Palma" + } + ], + "sublines": [ + { + "cod": "L401-1", + "desc": "", + "dir": "Anada", + "distance": 75554, + "id": 456, + "lineid": 3066, + "main": true, + "nam": "Cala Millor - Palma", + "stops": [ + { + "cod": "51032", + "id": 86, + "lat": 39.601162, + "lon": 3.381568, + "nam": "Cala Millor centre", + "parent": "Cala Millor" + }, + ], + "towns": [ + {"dis": 0, "id": 11432, "nam": "Cala Millor"}, + {"dis": 4, "id": 11431, "nam": "sa Coma"}, + ], + "vis": true + }, + { + "cod": "L401-12", + "desc": "", + "dir": "Tornada", + "distance": 65913, + "id": 668, + "lineid": 3066, + "main": false, + "nam": "Palma - Coves del Drac, exprés", + "stops": [ + { + "cod": "40036", + "id": 984, + "lat": 39.576298, + "lon": 2.6541128, + "nam": "Estació Intermodal", + "parent": "Palma" + }, + ], + "towns": [ + {"dis": 0, "id": 11167, "nam": "Palma"}, + {"dis": 1, "id": 11427, "nam": "Portocristo"} + ], + "vis": false, + }, + ], + "summ": false, + "typ": 3, + "zoneTransport": [ + {"id": 5} + ] + }; + + final routeLine = RouteLine.fromJson(jsonMap); + + expect(routeLine.active, true); + expect(routeLine.code, '401'); + expect(routeLine.id, 3066); + expect(routeLine.name, 'Cala Millor - Palma'); + expect(routeLine.color, 0xFFA5CFBA); + expect(routeLine.type, LineType.bus); + expect(routeLine.sublines, isNotNull); + expect(routeLine.sublines!.length, 2); + expect(routeLine.sublines![0].code, 'L401-1'); + }); + + test('fromJson handles null sublines', () { + final jsonMap = { + 'act': true, + 'cod': 'B42', + 'id': 1, + 'nam': 'Bus 42', + 'color': '#FF0000', + 'typ': 3, + 'sublines': null + }; + + final routeLine = RouteLine.fromJson(jsonMap); + + expect(routeLine.active, true); + expect(routeLine.code, 'B42'); + expect(routeLine.id, 1); + expect(routeLine.name, 'Bus 42'); + expect(routeLine.color, 0xFFFF0000); + expect(routeLine.type, LineType.bus); + expect(routeLine.sublines, isNull); + }); + + test('getAllLines fetches all route lines', () async { + final mockClient = MockClient((request) async { + final responsePayload = json.encode({ + "linesInfo": [ + { + "act": true, + "cod": "B42", + "id": 1, + "nam": "Bus 42", + "color": "#FF0000", + "typ": 3, + "sublines": [] + } + ] + }); + return http.Response(responsePayload, 200); + }); + + RouteLine.httpClient = mockClient; + + final lines = await RouteLine.getAllLines(); + + expect(lines, isNotEmpty); + expect(lines[0].code, 'B42'); + }); + + test('getLine fetches a specific route line', () async { + final mockClient = MockClient((request) async { + final responsePayload = json.encode({ + "act": true, + "cod": "B42", + "id": 1, + "nam": "Bus 42", + "color": "#FF0000", + "typ": 3, + "sublines": [] + }); + return http.Response(responsePayload, 200); + }); + + RouteLine.httpClient = mockClient; + + final line = await RouteLine.getLine('B42'); + + expect(line.code, 'B42'); + }); + + test('getPdfTimetable fetches the PDF timetable URL', () async { + final mockClient = MockClient((request) async { + final htmlResponse = ''' + + '''; + return http.Response(htmlResponse, 200); + }); + + RouteLine.httpClient = mockClient; + + final pdfUri = await RouteLine.getPdfTimetable('B42'); + + expect(pdfUri, isNotNull); + expect(pdfUri.toString(), 'https://www.tib.org/pdftimetable.pdf'); + }); + }); +} diff --git a/test/api/stations_test.dart b/test/api/stations_test.dart new file mode 100644 index 0000000..2bab812 --- /dev/null +++ b/test/api/stations_test.dart @@ -0,0 +1,132 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; +import 'package:test/test.dart'; +import 'package:tib_api/src/api/route_line.dart'; +import 'package:tib_api/src/api/stations.dart'; + +void main() { + group('Station', () { + // Test fromJson and toJson methods + test('fromJson and toJson should work correctly', () { + final stationJson = { + 'cod': '123', + 'id': 456, + 'lat': 37.7749, + 'lon': -122.4194, + 'nam': 'Sample Station', + 'ref': 'Reference' + }; + + final station = Station.fromJson(stationJson); + expect(station.code, 123); + expect(station.id, 456); + expect(station.lat, 37.7749); + expect(station.long, -122.4194); + expect(station.name, 'Sample Station'); + expect(station.ref, 'Reference'); + + final json = Station.toJson(station); + expect(json, stationJson); + }); + + // Test getLines method for successful response + test('getLines should return a list of RouteLines for a valid station code', + () async { + final mockClient = MockClient((request) async { + final linesResponse = jsonEncode({ + 'lines': [ + {'cod': '1'}, + {'cod': '2'} + ] + }); + + return http.Response(linesResponse, 200); + }); + + final mockRouteLineClient = MockClient((request) async { + final routeLineResponse = jsonEncode({ + 'act': true, + 'cod': '1', + 'id': 1, + 'nam': 'Route Line 1', + 'color': '#FFFFFF', + 'typ': 1, + }); + + return http.Response(routeLineResponse, 200); + }); + + RouteLine.httpClient = mockRouteLineClient; + Station.httpClient = mockClient; + + final lines = await Station.getLines(123); + expect(lines.length, 2); + expect(lines[0].name, 'Route Line 1'); + }); + + // Test getLines method for invalid station code + test('getLines should throw FormatException for invalid station code', + () async { + final mockClient = MockClient((request) async { + return http.Response('Invalid station code', 400); + }); + + Station.httpClient = mockClient; + + expect( + () async => await Station.getLines(999), + throwsA(isA()), + ); + }); + + // Test getAllStations method for successful response + test('getAllStations should return a list of Stations', () async { + final mockClient = MockClient((request) async { + final stationsResponse = jsonEncode({ + 'stopsInfo': [ + { + 'cod': '123', + 'id': 456, + 'lat': 37.7749, + 'lon': -122.4194, + 'nam': 'Station 1', + 'ref': 'Ref 1' + }, + { + 'cod': '789', + 'id': 101, + 'lat': 37.7750, + 'lon': -122.4195, + 'nam': 'Station 2', + 'ref': 'Ref 2' + } + ] + }); + + return http.Response(stationsResponse, 200); + }); + + Station.httpClient = mockClient; + + final stations = await Station.getAllStations(count: 2); + expect(stations.length, 2); + expect(stations[0].name, 'Station 1'); + expect(stations[1].name, 'Station 2'); + }); + + // Test getAllStations method for a failure scenario + test('getAllStations should throw an exception on failure', () async { + final mockClient = MockClient((request) async { + return http.Response('Error fetching stations', 500); + }); + + Station.httpClient = mockClient; + + expect( + () async => await Station.getAllStations(), + throwsA(isA()), + ); + }); + }); +} diff --git a/test/realtime/bus_position_test.dart b/test/realtime/bus_position_test.dart new file mode 100644 index 0000000..8e0d3e5 --- /dev/null +++ b/test/realtime/bus_position_test.dart @@ -0,0 +1,27 @@ +import 'package:test/test.dart'; +import 'package:tib_api/tib_api.dart'; + +void main() { + group('BusPosition', () { + test('fromJson should correctly parse JSON data', () { + final json = { + 'lat': 42.1234, // Sample latitude + 'lng': -71.5678, // Sample longitude + 'vel': 35.5, // Sample speed + 'upd': '2024-05-25T08:30:00Z' // Sample timestamp + }; + + final busPosition = BusPosition.fromJson(json); + + // Test attributes + expect(busPosition.lat, 42.1234); + expect(busPosition.long, -71.5678); + expect(busPosition.speed, 35.5); + expect(busPosition.timestamp.year, 2024); + expect(busPosition.timestamp.month, 5); + expect(busPosition.timestamp.day, 25); + expect(busPosition.timestamp.hour, 8); + expect(busPosition.timestamp.minute, 30); + }); + }); +} diff --git a/test/realtime/bus_stopped_test.dart b/test/realtime/bus_stopped_test.dart new file mode 100644 index 0000000..535ec66 --- /dev/null +++ b/test/realtime/bus_stopped_test.dart @@ -0,0 +1,54 @@ +import 'package:test/test.dart'; +import 'package:tib_api/tib_api.dart'; + +void main() { + group('BusStopped', () { + test('fromJson should correctly parse JSON data', () { + final json = { + 'upd': '2024-05-25T08:30:00Z', // Sample timestamp + 'lat': 42.1234, // Sample latitude + 'lng': -71.5678, // Sample longitude + 'vel': 35.5, // Sample speed + 'del': 5, // Sample delay + 'pass': 20, // Sample passengers + 'stop_nam': 'Sample Stop', // Sample stop name + 'arr_t': '0830', // Sample scheduled time (HHmm format) + 'arr_rt': '2024-05-25T08:32:00Z', // Sample actual time + 'stp_rt': '2024-05-25T08:33:00Z', // Sample stop time + 'dep_rt': '2024-05-25T08:35:00Z' // Sample leave time + }; + + final busStopped = BusStopped.fromJson(json); + + // Test attributes + expect(busStopped.timestamp.year, 2024); + expect(busStopped.timestamp.month, 5); + expect(busStopped.timestamp.day, 25); + expect(busStopped.timestamp.hour, 8); + expect(busStopped.timestamp.minute, 30); + expect(busStopped.lat, 42.1234); + expect(busStopped.long, -71.5678); + expect(busStopped.speed, 35.5); + expect(busStopped.delay, 5); + expect(busStopped.passangers, 20); + expect(busStopped.stopName, 'Sample Stop'); + expect(busStopped.scheduledTime.hour, 8); + expect(busStopped.scheduledTime.minute, 30); + expect(busStopped.actualTime.year, 2024); + expect(busStopped.actualTime.month, 5); + expect(busStopped.actualTime.day, 25); + expect(busStopped.actualTime.hour, 8); + expect(busStopped.actualTime.minute, 32); + expect(busStopped.stopTime!.year, 2024); + expect(busStopped.stopTime!.month, 5); + expect(busStopped.stopTime!.day, 25); + expect(busStopped.stopTime!.hour, 8); + expect(busStopped.stopTime!.minute, 33); + expect(busStopped.leaveTime!.year, 2024); + expect(busStopped.leaveTime!.month, 5); + expect(busStopped.leaveTime!.day, 25); + expect(busStopped.leaveTime!.hour, 8); + expect(busStopped.leaveTime!.minute, 35); + }); + }); +} diff --git a/test/realtime/station_info_test.dart b/test/realtime/station_info_test.dart new file mode 100644 index 0000000..b8c5d63 --- /dev/null +++ b/test/realtime/station_info_test.dart @@ -0,0 +1,101 @@ +import 'package:test/test.dart'; +import 'package:tib_api/tib_api.dart'; + +void main() { + group('RouteStationInfo', () { + test('fromJson should correctly parse JSON data with multiple stops', () { + final json = { + 'bus': {'pas': 10, 'cap': 50}, // Sample passengers JSON data + 'stops': [ + { + 'stop_id': 1, + 'stop_nam': 'Station A', + 'arr_t': '0830', // Scheduled arrival time in HHMM format + 'esta_dist': 2.5, // Estimated distance + 'esta_time': '2024-05-25T08:35:00Z' // Estimated arrival time + }, + { + 'stop_id': 2, + 'stop_nam': 'Station B', + 'arr_t': '0900', // Scheduled arrival time in HHMM format + 'esta_dist': 4.2, // Estimated distance + 'esta_time': '2024-05-25T09:05:00Z' // Estimated arrival time + }, + ] + }; + + final routeStationInfo = RouteStationInfo.fromJson(json); + + // Test passengers + expect(routeStationInfo.passangers.inBus, 10); + expect(routeStationInfo.passangers.totalCapacity, 50); + + // Test stops + expect(routeStationInfo.stops.length, 2); + final stationOnRoute1 = routeStationInfo.stops[0]; + expect(stationOnRoute1.stopId, 1); + expect(stationOnRoute1.stopName, 'Station A'); + expect(stationOnRoute1.scheduledArrival.hour, 8); + expect(stationOnRoute1.scheduledArrival.minute, 30); + expect(stationOnRoute1.estimatedDistance, 2.5); + expect(stationOnRoute1.estimatedArrival.year, 2024); + expect(stationOnRoute1.estimatedArrival.month, 5); + expect(stationOnRoute1.estimatedArrival.day, 25); + expect(stationOnRoute1.estimatedArrival.hour, 8); + expect(stationOnRoute1.estimatedArrival.minute, 35); + + final stationOnRoute2 = routeStationInfo.stops[1]; + expect(stationOnRoute2.stopId, 2); + expect(stationOnRoute2.stopName, 'Station B'); + expect(stationOnRoute2.scheduledArrival.hour, 9); + expect(stationOnRoute2.scheduledArrival.minute, 0); + expect(stationOnRoute2.estimatedDistance, 4.2); + expect(stationOnRoute2.estimatedArrival.year, 2024); + expect(stationOnRoute2.estimatedArrival.month, 5); + expect(stationOnRoute2.estimatedArrival.day, 25); + expect(stationOnRoute2.estimatedArrival.hour, 9); + expect(stationOnRoute2.estimatedArrival.minute, 5); + }); + + test('fromJson should handle empty stops array', () { + final json = { + 'bus': {'pas': 10, 'cap': 50}, // Sample passengers JSON data + 'stops': [] // Empty stops array + }; + + final routeStationInfo = RouteStationInfo.fromJson(json); + + // Test passengers + expect(routeStationInfo.passangers.inBus, 10); + expect(routeStationInfo.passangers.totalCapacity, 50); + + // Test stops + expect(routeStationInfo.stops, isEmpty); + }); + }); + + group('StationOnRoute', () { + test('fromJson should correctly parse JSON data', () { + final json = { + 'stop_id': 1, + 'stop_nam': 'Station A', + 'arr_t': '0830', // Scheduled arrival time in HHMM format + 'esta_dist': 2.5, // Estimated distance + 'esta_time': '2024-05-25T08:35:00Z' // Estimated arrival time + }; + + final stationOnRoute = StationOnRoute.fromJson(json); + + expect(stationOnRoute.stopId, 1); + expect(stationOnRoute.stopName, 'Station A'); + expect(stationOnRoute.scheduledArrival.hour, 8); + expect(stationOnRoute.scheduledArrival.minute, 30); + expect(stationOnRoute.estimatedDistance, 2.5); + expect(stationOnRoute.estimatedArrival.year, 2024); + expect(stationOnRoute.estimatedArrival.month, 5); + expect(stationOnRoute.estimatedArrival.day, 25); + expect(stationOnRoute.estimatedArrival.hour, 8); + expect(stationOnRoute.estimatedArrival.minute, 35); + }); + }); +}