aboutsummaryrefslogtreecommitdiff
path: root/pkg/navitia_api_client
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/navitia_api_client')
-rw-r--r--pkg/navitia_api_client/client.go1
-rw-r--r--pkg/navitia_api_client/client_test.go25
-rw-r--r--pkg/navitia_api_client/departures.go1
-rw-r--r--pkg/navitia_api_client/test_data/4-train-stops-page-0.json264
-rw-r--r--pkg/navitia_api_client/test_data/4-train-stops-page-1.json239
-rw-r--r--pkg/navitia_api_client/test_data/4-train-stops-page-2.json239
-rw-r--r--pkg/navitia_api_client/test_data/4-train-stops.json264
-rw-r--r--pkg/navitia_api_client/train_stops.go70
-rw-r--r--pkg/navitia_api_client/train_stops_test.go106
9 files changed, 1209 insertions, 0 deletions
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)
+ }
+}