From de9257ba6591c2022b560f7027e83547fe74f8a5 Mon Sep 17 00:00:00 2001 From: Julien Dessaux Date: Tue, 7 Sep 2021 15:44:44 +0200 Subject: Added navitia code to fetch train stops names --- internal/webui/root_test.go | 5 + pkg/navitia_api_client/client.go | 1 + pkg/navitia_api_client/client_test.go | 25 ++ pkg/navitia_api_client/departures.go | 1 + .../test_data/4-train-stops-page-0.json | 264 +++++++++++++++++++++ .../test_data/4-train-stops-page-1.json | 239 +++++++++++++++++++ .../test_data/4-train-stops-page-2.json | 239 +++++++++++++++++++ .../test_data/4-train-stops.json | 264 +++++++++++++++++++++ pkg/navitia_api_client/train_stops.go | 70 ++++++ pkg/navitia_api_client/train_stops_test.go | 106 +++++++++ 10 files changed, 1214 insertions(+) create mode 100644 pkg/navitia_api_client/test_data/4-train-stops-page-0.json create mode 100644 pkg/navitia_api_client/test_data/4-train-stops-page-1.json create mode 100644 pkg/navitia_api_client/test_data/4-train-stops-page-2.json create mode 100644 pkg/navitia_api_client/test_data/4-train-stops.json create mode 100644 pkg/navitia_api_client/train_stops.go create mode 100644 pkg/navitia_api_client/train_stops_test.go diff --git a/internal/webui/root_test.go b/internal/webui/root_test.go index cd2da0f..945f04b 100644 --- a/internal/webui/root_test.go +++ b/internal/webui/root_test.go @@ -12,6 +12,7 @@ import ( type NavitiaMockClient struct { departures []model.Departure + trainStops []model.TrainStop err error } @@ -19,6 +20,10 @@ func (c *NavitiaMockClient) GetDepartures(trainStop string) (departures []model. return c.departures, c.err } +func (c *NavitiaMockClient) GetTrainStops() (trainStops []model.TrainStop, err error) { + return c.trainStops, c.err +} + func TestRootHandler(t *testing.T) { // test environment setup dbEnv, err := database.InitDB("sqlite3", "file::memory:?_foreign_keys=on") diff --git a/pkg/navitia_api_client/client.go b/pkg/navitia_api_client/client.go index 71b82fe..ccd7198 100644 --- a/pkg/navitia_api_client/client.go +++ b/pkg/navitia_api_client/client.go @@ -11,6 +11,7 @@ import ( type Client interface { GetDepartures(trainStop string) (departures []model.Departure, err error) + GetTrainStops() (trainStops []model.TrainStop, err error) } type NavitiaClient struct { diff --git a/pkg/navitia_api_client/client_test.go b/pkg/navitia_api_client/client_test.go index 009712d..d63db9e 100644 --- a/pkg/navitia_api_client/client_test.go +++ b/pkg/navitia_api_client/client_test.go @@ -27,3 +27,28 @@ func newTestClientFromFilename(t *testing.T, filename string) (*NavitiaClient, * })) return newTestClient(ts), ts } + +type testClientCase struct { + urlPath string + filename string +} + +func newTestClientFromFilenames(t *testing.T, tcs []testClientCase) (*NavitiaClient, *httptest.Server) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + found := false + for i := 0; i < len(tcs) && !found; i++ { + if r.URL.Path+"?"+r.URL.RawQuery == tcs[i].urlPath { + page, err := ioutil.ReadFile(tcs[i].filename) + if err != nil { + t.Fatalf("Could not open test file : %s", err) + } + w.Write(page) + found = true + } + } + if !found { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + } + })) + return newTestClient(ts), ts +} diff --git a/pkg/navitia_api_client/departures.go b/pkg/navitia_api_client/departures.go index 50ebe17..1932767 100644 --- a/pkg/navitia_api_client/departures.go +++ b/pkg/navitia_api_client/departures.go @@ -70,6 +70,7 @@ func (c *NavitiaClient) GetDepartures(trainStop string) (departures []model.Depa return nil, newJsonDecodeError("GetDepartures "+trainStop, err) } // TODO test for no json error + // TODO handle pagination for i := 0; i < len(data.Departures); i++ { t, err := time.Parse("20060102T150405", data.Departures[i].StopDateTime.ArrivalDateTime) if err != nil { diff --git a/pkg/navitia_api_client/test_data/4-train-stops-page-0.json b/pkg/navitia_api_client/test_data/4-train-stops-page-0.json new file mode 100644 index 0000000..5b91260 --- /dev/null +++ b/pkg/navitia_api_client/test_data/4-train-stops-page-0.json @@ -0,0 +1,264 @@ +{ + "pagination": { + "start_page": 0, + "items_on_page": 4, + "items_per_page": 4, + "total_result": 12 + }, + "links": [ + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}", + "type": "stop_areas", + "rel": "stop_areas", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}/route_schedules", + "type": "route_schedules", + "rel": "route_schedules", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}/stop_schedules", + "type": "stop_schedules", + "rel": "stop_schedules", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}/arrivals", + "type": "arrivals", + "rel": "arrivals", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}/departures", + "type": "departures", + "rel": "departures", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}/places_nearby", + "type": "places_nearby", + "rel": "places_nearby", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}/journeys", + "type": "journey", + "rel": "journeys", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}/traffic_reports", + "type": "disruption", + "rel": "disruptions", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas?count=4&start_page=1", + "type": "next", + "templated": false + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas?count=4&start_page=1280", + "type": "last", + "templated": false + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas?count=4", + "type": "first", + "templated": false + } + ], + "disruptions": [], + "feed_publishers": [ + { + "url": "", + "id": "sncf", + "license": "Private (unspecified)", + "name": "SNCF PIV Production" + }, + { + "url": "", + "id": "SNCF:sncf-piv", + "license": "Private (unspecified)", + "name": "SNCF PIV Production" + } + ], + "context": { + "timezone": "Europe/Paris", + "current_datetime": "20210906T224334" + }, + "stop_areas": [ + { + "codes": [ + { + "type": "secondary_id", + "value": "SNCF:87144758" + }, + { + "type": "secondary_id", + "value": "SNCF:87407536" + }, + { + "type": "secondary_id", + "value": "SNCF:87641241" + }, + { + "type": "secondary_id", + "value": "SNCF:87714089" + }, + { + "type": "secondary_id", + "value": "SNCF:87757005" + }, + { + "type": "secondary_id", + "value": "SNCF:87781567" + }, + { + "type": "source", + "value": "87144758" + }, + { + "type": "source", + "value": "87407536" + }, + { + "type": "source", + "value": "87420802" + }, + { + "type": "source", + "value": "87641241" + }, + { + "type": "source", + "value": "87714089" + }, + { + "type": "source", + "value": "87757005" + }, + { + "type": "source", + "value": "87781567" + } + ], + "name": "", + "links": [], + "coord": { + "lat": "0", + "lon": "0" + }, + "label": "", + "timezone": "Europe/Paris", + "id": "stop_area:SNCF:87420802" + }, + { + "codes": [ + { + "type": "source", + "value": "87313759" + }, + { + "type": "uic", + "value": "87313759" + } + ], + "name": "Abancourt", + "links": [], + "coord": { + "lat": "49.685602", + "lon": "1.774351" + }, + "label": "Abancourt (Abancourt)", + "administrative_regions": [ + { + "insee": "60001", + "name": "Abancourt", + "level": 8, + "coord": { + "lat": "49.6977145", + "lon": "1.7646826" + }, + "label": "Abancourt (60220)", + "id": "admin:fr:60001", + "zip_code": "60220" + } + ], + "timezone": "Europe/Paris", + "id": "stop_area:SNCF:87313759" + }, + { + "codes": [ + { + "type": "source", + "value": "87481614" + }, + { + "type": "uic", + "value": "87481614" + } + ], + "name": "Abbaretz", + "links": [], + "coord": { + "lat": "47.555241", + "lon": "-1.524289" + }, + "label": "Abbaretz (Abbaretz)", + "administrative_regions": [ + { + "insee": "44001", + "name": "Abbaretz", + "level": 8, + "coord": { + "lat": "47.5525545", + "lon": "-1.5322775" + }, + "label": "Abbaretz (44170)", + "id": "admin:fr:44001", + "zip_code": "44170" + } + ], + "timezone": "Europe/Paris", + "id": "stop_area:SNCF:87481614" + }, + { + "codes": [ + { + "type": "source", + "value": "87317362" + }, + { + "type": "uic", + "value": "87317362" + } + ], + "name": "Abbeville", + "links": [], + "coord": { + "lat": "50.102216", + "lon": "1.824487" + }, + "label": "Abbeville (Abbeville)", + "administrative_regions": [ + { + "insee": "80001", + "name": "Abbeville", + "level": 8, + "coord": { + "lat": "50.1060835", + "lon": "1.8337029" + }, + "label": "Abbeville (80100)", + "id": "admin:fr:80001", + "zip_code": "80100" + } + ], + "timezone": "Europe/Paris", + "id": "stop_area:SNCF:87317362" + } + ] +} diff --git a/pkg/navitia_api_client/test_data/4-train-stops-page-1.json b/pkg/navitia_api_client/test_data/4-train-stops-page-1.json new file mode 100644 index 0000000..c57b85b --- /dev/null +++ b/pkg/navitia_api_client/test_data/4-train-stops-page-1.json @@ -0,0 +1,239 @@ +{ + "pagination": { + "start_page": 1, + "items_on_page": 4, + "items_per_page": 4, + "total_result": 12 + }, + "links": [ + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}", + "type": "stop_areas", + "rel": "stop_areas", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}/route_schedules", + "type": "route_schedules", + "rel": "route_schedules", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}/stop_schedules", + "type": "stop_schedules", + "rel": "stop_schedules", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}/arrivals", + "type": "arrivals", + "rel": "arrivals", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}/departures", + "type": "departures", + "rel": "departures", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}/places_nearby", + "type": "places_nearby", + "rel": "places_nearby", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}/journeys", + "type": "journey", + "rel": "journeys", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}/traffic_reports", + "type": "disruption", + "rel": "disruptions", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas?count=4&start_page=0", + "type": "previous", + "templated": false + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas?count=4&start_page=2", + "type": "next", + "templated": false + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas?count=4&start_page=1280", + "type": "last", + "templated": false + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas?count=4", + "type": "first", + "templated": false + } + ], + "disruptions": [], + "feed_publishers": [ + { + "url": "", + "id": "sncf", + "license": "Private (unspecified)", + "name": "SNCF PIV Production" + }, + { + "url": "", + "id": "SNCF:sncf-piv", + "license": "Private (unspecified)", + "name": "SNCF PIV Production" + } + ], + "context": { + "timezone": "Europe/Paris", + "current_datetime": "20210906T212057" + }, + "stop_areas": [ + { + "codes": [ + { + "type": "source", + "value": "87545269" + }, + { + "type": "uic", + "value": "87545269" + } + ], + "name": "Ablon", + "links": [], + "coord": { + "lat": "48.725443", + "lon": "2.419213" + }, + "label": "Ablon (Ablon-sur-Seine)", + "administrative_regions": [ + { + "insee": "94001", + "name": "Ablon-sur-Seine", + "level": 8, + "coord": { + "lat": "48.7247582", + "lon": "2.421509" + }, + "label": "Ablon-sur-Seine (94480)", + "id": "admin:fr:94001", + "zip_code": "94480" + } + ], + "timezone": "Europe/Paris", + "id": "stop_area:SNCF:87545269" + }, + { + "codes": [ + { + "type": "source", + "value": "87590588" + }, + { + "type": "uic", + "value": "87590588" + } + ], + "name": "Ablon Noctilien", + "links": [], + "coord": { + "lat": "48.72551", + "lon": "2.419155" + }, + "label": "Ablon Noctilien (Ablon-sur-Seine)", + "administrative_regions": [ + { + "insee": "94001", + "name": "Ablon-sur-Seine", + "level": 8, + "coord": { + "lat": "48.7247582", + "lon": "2.421509" + }, + "label": "Ablon-sur-Seine (94480)", + "id": "admin:fr:94001", + "zip_code": "94480" + } + ], + "timezone": "Europe/Paris", + "id": "stop_area:SNCF:87590588" + }, + { + "codes": [ + { + "type": "source", + "value": "87191403" + }, + { + "type": "uic", + "value": "87191403" + } + ], + "name": "Aboncourt Mairie", + "links": [], + "coord": { + "lat": "49.261076", + "lon": "6.346603" + }, + "label": "Aboncourt Mairie (Aboncourt)", + "administrative_regions": [ + { + "insee": "57001", + "name": "Aboncourt", + "level": 8, + "coord": { + "lat": "49.2602817", + "lon": "6.3463759" + }, + "label": "Aboncourt (57920)", + "id": "admin:fr:57001", + "zip_code": "57920" + } + ], + "timezone": "Europe/Paris", + "id": "stop_area:SNCF:87191403" + }, + { + "codes": [ + { + "type": "source", + "value": "87721944" + }, + { + "type": "uic", + "value": "87721944" + } + ], + "name": "Abrest Mairie", + "links": [], + "coord": { + "lat": "46.098868", + "lon": "3.444791" + }, + "label": "Abrest Mairie (Abrest)", + "administrative_regions": [ + { + "insee": "3001", + "name": "Abrest", + "level": 8, + "coord": { + "lat": "46.0980139", + "lon": "3.4450434" + }, + "label": "Abrest (03200)", + "id": "admin:fr:3001", + "zip_code": "03200" + } + ], + "timezone": "Europe/Paris", + "id": "stop_area:SNCF:87721944" + } + ] +} diff --git a/pkg/navitia_api_client/test_data/4-train-stops-page-2.json b/pkg/navitia_api_client/test_data/4-train-stops-page-2.json new file mode 100644 index 0000000..5d0952c --- /dev/null +++ b/pkg/navitia_api_client/test_data/4-train-stops-page-2.json @@ -0,0 +1,239 @@ +{ + "pagination": { + "start_page": 2, + "items_on_page": 4, + "items_per_page": 4, + "total_result": 12 + }, + "links": [ + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}", + "type": "stop_areas", + "rel": "stop_areas", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}/route_schedules", + "type": "route_schedules", + "rel": "route_schedules", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}/stop_schedules", + "type": "stop_schedules", + "rel": "stop_schedules", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}/arrivals", + "type": "arrivals", + "rel": "arrivals", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}/departures", + "type": "departures", + "rel": "departures", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}/places_nearby", + "type": "places_nearby", + "rel": "places_nearby", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}/journeys", + "type": "journey", + "rel": "journeys", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}/traffic_reports", + "type": "disruption", + "rel": "disruptions", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas?count=4&start_page=1", + "type": "previous", + "templated": false + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas?count=4&start_page=3", + "type": "next", + "templated": false + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas?count=4&start_page=1280", + "type": "last", + "templated": false + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas?count=4", + "type": "first", + "templated": false + } + ], + "disruptions": [], + "feed_publishers": [ + { + "url": "", + "id": "sncf", + "license": "Private (unspecified)", + "name": "SNCF PIV Production" + }, + { + "url": "", + "id": "SNCF:sncf-piv", + "license": "Private (unspecified)", + "name": "SNCF PIV Production" + } + ], + "context": { + "timezone": "Europe/Paris", + "current_datetime": "20210906T212107" + }, + "stop_areas": [ + { + "codes": [ + { + "type": "source", + "value": "87677252" + }, + { + "type": "uic", + "value": "87677252" + } + ], + "name": "Accolay Le Pont", + "links": [], + "coord": { + "lat": "47.663798", + "lon": "3.709584" + }, + "label": "Accolay Le Pont (Deux Rivières)", + "administrative_regions": [ + { + "insee": "89130", + "name": "Deux Rivières", + "level": 8, + "coord": { + "lat": "47.6839847", + "lon": "3.6898895" + }, + "label": "Deux Rivières", + "id": "admin:fr:89130", + "zip_code": "" + } + ], + "timezone": "Europe/Paris", + "id": "stop_area:SNCF:87677252" + }, + { + "codes": [ + { + "type": "source", + "value": "87386052" + }, + { + "type": "uic", + "value": "87386052" + } + ], + "name": "Achères Grand Cormier", + "links": [], + "coord": { + "lat": "48.955187", + "lon": "2.091962" + }, + "label": "Achères Grand Cormier (Saint-Germain-en-Laye)", + "administrative_regions": [ + { + "insee": "78551", + "name": "Saint-Germain-en-Laye", + "level": 8, + "coord": { + "lat": "48.8990413", + "lon": "2.0942792" + }, + "label": "Saint-Germain-en-Laye", + "id": "admin:fr:78551", + "zip_code": "" + } + ], + "timezone": "Europe/Paris", + "id": "stop_area:SNCF:87386052" + }, + { + "codes": [ + { + "type": "source", + "value": "87612978" + }, + { + "type": "uic", + "value": "87612978" + } + ], + "name": "Acheres Grand Cormier Noctilien", + "links": [], + "coord": { + "lat": "48.955143", + "lon": "2.091769" + }, + "label": "Acheres Grand Cormier Noctilien (Saint-Germain-en-Laye)", + "administrative_regions": [ + { + "insee": "78551", + "name": "Saint-Germain-en-Laye", + "level": 8, + "coord": { + "lat": "48.8990413", + "lon": "2.0942792" + }, + "label": "Saint-Germain-en-Laye", + "id": "admin:fr:78551", + "zip_code": "" + } + ], + "timezone": "Europe/Paris", + "id": "stop_area:SNCF:87612978" + }, + { + "codes": [ + { + "type": "source", + "value": "87381657" + }, + { + "type": "uic", + "value": "87381657" + } + ], + "name": "Achères Ville", + "links": [], + "coord": { + "lat": "48.97011", + "lon": "2.07739" + }, + "label": "Achères Ville (Achères)", + "administrative_regions": [ + { + "insee": "78005", + "name": "Achères", + "level": 8, + "coord": { + "lat": "48.9606321", + "lon": "2.0698106" + }, + "label": "Achères (78260)", + "id": "admin:fr:78005", + "zip_code": "78260" + } + ], + "timezone": "Europe/Paris", + "id": "stop_area:SNCF:87381657" + } + ] +} diff --git a/pkg/navitia_api_client/test_data/4-train-stops.json b/pkg/navitia_api_client/test_data/4-train-stops.json new file mode 100644 index 0000000..3c5a7a4 --- /dev/null +++ b/pkg/navitia_api_client/test_data/4-train-stops.json @@ -0,0 +1,264 @@ +{ + "pagination": { + "start_page": 0, + "items_on_page": 4, + "items_per_page": 4, + "total_result": 4 + }, + "links": [ + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}", + "type": "stop_areas", + "rel": "stop_areas", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}/route_schedules", + "type": "route_schedules", + "rel": "route_schedules", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}/stop_schedules", + "type": "stop_schedules", + "rel": "stop_schedules", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}/arrivals", + "type": "arrivals", + "rel": "arrivals", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}/departures", + "type": "departures", + "rel": "departures", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}/places_nearby", + "type": "places_nearby", + "rel": "places_nearby", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}/journeys", + "type": "journey", + "rel": "journeys", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas/{stop_areas.id}/traffic_reports", + "type": "disruption", + "rel": "disruptions", + "templated": true + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas?count=4&start_page=1", + "type": "next", + "templated": false + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas?count=4&start_page=1280", + "type": "last", + "templated": false + }, + { + "href": "https://api.sncf.com/v1/coverage/sncf/stop_areas?count=4", + "type": "first", + "templated": false + } + ], + "disruptions": [], + "feed_publishers": [ + { + "url": "", + "id": "sncf", + "license": "Private (unspecified)", + "name": "SNCF PIV Production" + }, + { + "url": "", + "id": "SNCF:sncf-piv", + "license": "Private (unspecified)", + "name": "SNCF PIV Production" + } + ], + "context": { + "timezone": "Europe/Paris", + "current_datetime": "20210906T213433" + }, + "stop_areas": [ + { + "codes": [ + { + "type": "secondary_id", + "value": "SNCF:87144758" + }, + { + "type": "secondary_id", + "value": "SNCF:87407536" + }, + { + "type": "secondary_id", + "value": "SNCF:87641241" + }, + { + "type": "secondary_id", + "value": "SNCF:87714089" + }, + { + "type": "secondary_id", + "value": "SNCF:87757005" + }, + { + "type": "secondary_id", + "value": "SNCF:87781567" + }, + { + "type": "source", + "value": "87144758" + }, + { + "type": "source", + "value": "87407536" + }, + { + "type": "source", + "value": "87420802" + }, + { + "type": "source", + "value": "87641241" + }, + { + "type": "source", + "value": "87714089" + }, + { + "type": "source", + "value": "87757005" + }, + { + "type": "source", + "value": "87781567" + } + ], + "name": "", + "links": [], + "coord": { + "lat": "0", + "lon": "0" + }, + "label": "", + "timezone": "Europe/Paris", + "id": "stop_area:SNCF:87420802" + }, + { + "codes": [ + { + "type": "source", + "value": "87313759" + }, + { + "type": "uic", + "value": "87313759" + } + ], + "name": "Abancourt", + "links": [], + "coord": { + "lat": "49.685602", + "lon": "1.774351" + }, + "label": "Abancourt (Abancourt)", + "administrative_regions": [ + { + "insee": "60001", + "name": "Abancourt", + "level": 8, + "coord": { + "lat": "49.6977145", + "lon": "1.7646826" + }, + "label": "Abancourt (60220)", + "id": "admin:fr:60001", + "zip_code": "60220" + } + ], + "timezone": "Europe/Paris", + "id": "stop_area:SNCF:87313759" + }, + { + "codes": [ + { + "type": "source", + "value": "87481614" + }, + { + "type": "uic", + "value": "87481614" + } + ], + "name": "Abbaretz", + "links": [], + "coord": { + "lat": "47.555241", + "lon": "-1.524289" + }, + "label": "Abbaretz (Abbaretz)", + "administrative_regions": [ + { + "insee": "44001", + "name": "Abbaretz", + "level": 8, + "coord": { + "lat": "47.5525545", + "lon": "-1.5322775" + }, + "label": "Abbaretz (44170)", + "id": "admin:fr:44001", + "zip_code": "44170" + } + ], + "timezone": "Europe/Paris", + "id": "stop_area:SNCF:87481614" + }, + { + "codes": [ + { + "type": "source", + "value": "87317362" + }, + { + "type": "uic", + "value": "87317362" + } + ], + "name": "Abbeville", + "links": [], + "coord": { + "lat": "50.102216", + "lon": "1.824487" + }, + "label": "Abbeville (Abbeville)", + "administrative_regions": [ + { + "insee": "80001", + "name": "Abbeville", + "level": 8, + "coord": { + "lat": "50.1060835", + "lon": "1.8337029" + }, + "label": "Abbeville (80100)", + "id": "admin:fr:80001", + "zip_code": "80100" + } + ], + "timezone": "Europe/Paris", + "id": "stop_area:SNCF:87317362" + } + ] +} diff --git a/pkg/navitia_api_client/train_stops.go b/pkg/navitia_api_client/train_stops.go new file mode 100644 index 0000000..c8a8ff2 --- /dev/null +++ b/pkg/navitia_api_client/train_stops.go @@ -0,0 +1,70 @@ +package navitia_api_client + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + + "git.adyxax.org/adyxax/trains/pkg/model" +) + +type TrainStopsResponse struct { + Pagination struct { + StartPage int `json:"start_page"` + ItemsOnPage int `json:"items_on_page"` + ItemsPerPage int `json:"items_per_page"` + TotalResult int `json:"total_result"` + } `json:"pagination"` + StopAreas []struct { + Name string `json:"name"` + ID string `json:"id"` + Codes []interface{} `json:"codes"` + Links []interface{} `json:"links"` + Coord interface{} `json:"coord"` + Label interface{} `json:"label"` + Timezone interface{} `json:"timezone"` + AdministrativeRegion interface{} `json:"administrative_regions"` + } `json:"stop_areas"` + Links []interface{} `json:"links"` + Disruptions []interface{} `json:"disruptions"` + FeedPublishers []interface{} `json:"feed_publishers"` + Context interface{} `json:"context"` +} + +func (c *NavitiaClient) GetTrainStops() (trainStops []model.TrainStop, err error) { + return getTrainStopsPage(c, 0) +} + +func getTrainStopsPage(c *NavitiaClient, i int) (trainStops []model.TrainStop, err error) { + request := fmt.Sprintf("%s/coverage/sncf/stop_areas?count=1000&start_page=%d", c.baseURL, i) + req, err := http.NewRequest("GET", request, nil) + if err != nil { + return nil, newHttpClientError("http.NewRequest error", err) + } + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, newHttpClientError("httpClient.Do error", err) + } + defer resp.Body.Close() + if resp.StatusCode == http.StatusOK { + var data TrainStopsResponse + if err = json.NewDecoder(resp.Body).Decode(&data); err != nil { + return nil, newJsonDecodeError("GetTrainStops ", err) + } + for i := 0; i < len(data.StopAreas); i++ { + trainStops = append(trainStops, model.TrainStop{data.StopAreas[i].ID, data.StopAreas[i].Name}) + } + if data.Pagination.ItemsOnPage+data.Pagination.ItemsPerPage*data.Pagination.StartPage < data.Pagination.TotalResult { + log.Printf("pagination %d\n", i) + tss, err := getTrainStopsPage(c, i+1) + if err != nil { + return nil, err + } + trainStops = append(trainStops, tss...) + } + } else { + err = newApiError(resp.StatusCode, "GetTrainStops") + } + return +} diff --git a/pkg/navitia_api_client/train_stops_test.go b/pkg/navitia_api_client/train_stops_test.go new file mode 100644 index 0000000..93fc43f --- /dev/null +++ b/pkg/navitia_api_client/train_stops_test.go @@ -0,0 +1,106 @@ +package navitia_api_client + +import ( + "net/http" + "net/http/httptest" + "reflect" + "testing" + + "git.adyxax.org/adyxax/trains/pkg/model" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGetTrainStops(t *testing.T) { + // Simple Test cases + testCases := []struct { + name string + inputNewCLient string + expected []model.TrainStop + expectedError interface{} + }{ + {"invalid characters in token should fail", "}", nil, &HttpClientError{}}, + {"unreachable server should fail", "https://", nil, &HttpClientError{}}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + client := NewClient(tc.inputNewCLient) + valid, err := client.GetTrainStops() + if tc.expectedError != nil { + require.Error(t, err) + assert.Equalf(t, reflect.TypeOf(err), reflect.TypeOf(tc.expectedError), "Invalid error type. Got %s but expected %s", reflect.TypeOf(err), reflect.TypeOf(tc.expectedError)) + assert.Equal(t, tc.expected, valid) + } else { + require.NoError(t, err) + assert.Equal(t, tc.expected, valid) + } + }) + } + // Test cases with a filename + testCasesFilename := []struct { + name string + inputFilename string + expected []model.TrainStop + expectedError interface{} + }{ + {"invalid json should fail", "test_data/invalid.json", nil, &JsonDecodeError{}}, + } + for _, tc := range testCasesFilename { + t.Run(tc.name, func(t *testing.T) { + client, ts := newTestClientFromFilename(t, tc.inputFilename) + defer ts.Close() + valid, err := client.GetTrainStops() + if tc.expectedError != nil { + require.Error(t, err) + assert.Equalf(t, reflect.TypeOf(err), reflect.TypeOf(tc.expectedError), "Invalid error type. Got %s but expected %s", reflect.TypeOf(err), reflect.TypeOf(tc.expectedError)) + assert.Equal(t, tc.expected, valid) + } else { + require.NoError(t, err) + assert.Equal(t, tc.expected, valid) + } + }) + } + // http error + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + })) + client := newTestClient(ts) + _, err := client.GetTrainStops() + if err == nil { + t.Fatalf("404 should raise an error") + } + // normal working request + client, ts = newTestClientFromFilename(t, "test_data/4-train-stops.json") + defer ts.Close() + trainStops, err := client.GetTrainStops() + if err != nil { + t.Fatalf("could not get train stops : %s", err) + } + if len(trainStops) != 4 { + t.Fatalf("did not decode train stops properly, got %d train stops when expected 4", len(trainStops)) + } + // normal request in multiple pages + client, ts = newTestClientFromFilenames(t, []testClientCase{ + testClientCase{"/coverage/sncf/stop_areas?count=1000&start_page=0", "test_data/4-train-stops-page-0.json"}, + testClientCase{"/coverage/sncf/stop_areas?count=1000&start_page=1", "test_data/4-train-stops-page-1.json"}, + testClientCase{"/coverage/sncf/stop_areas?count=1000&start_page=2", "test_data/4-train-stops-page-2.json"}, + }) + defer ts.Close() + trainStops, err = client.GetTrainStops() + if err != nil { + t.Fatalf("could not get train stops : %+v", err) + } + if len(trainStops) != 12 { + t.Fatalf("did not decode train stops properly, got %d train stops when expected 4", len(trainStops)) + } + // failing request in multiple pages with last one missing + client, ts = newTestClientFromFilenames(t, []testClientCase{ + testClientCase{"/coverage/sncf/stop_areas?count=1000&start_page=0", "test_data/4-train-stops-page-0.json"}, + testClientCase{"/coverage/sncf/stop_areas?count=1000&start_page=1", "test_data/4-train-stops-page-1.json"}, + }) + defer ts.Close() + trainStops, err = client.GetTrainStops() + if err == nil { + t.Fatalf("should not be able to get train stops : %+v", trainStops) + } +} -- cgit v1.2.3