From 3c5e31b25a53268b413bc1e511b7486a2a1c80b9 Mon Sep 17 00:00:00 2001 From: Julien Dessaux Date: Wed, 8 Sep 2021 15:23:50 +0200 Subject: Renamed TrainStop to simply Stop --- internal/webui/root_test.go | 4 +- internal/webui/webui.go | 6 +- pkg/database/migrations.go | 2 +- pkg/database/stop.go | 47 +++++++++++ pkg/database/stop_test.go | 130 +++++++++++++++++++++++++++++ pkg/database/train_stop.go | 47 ----------- pkg/database/train_stop_test.go | 130 ----------------------------- pkg/model/stop.go | 6 ++ pkg/model/train_stop.go | 6 -- pkg/navitia_api_client/client.go | 2 +- pkg/navitia_api_client/stops.go | 70 ++++++++++++++++ pkg/navitia_api_client/stops_test.go | 108 ++++++++++++++++++++++++ pkg/navitia_api_client/train_stops.go | 70 ---------------- pkg/navitia_api_client/train_stops_test.go | 108 ------------------------ 14 files changed, 368 insertions(+), 368 deletions(-) create mode 100644 pkg/database/stop.go create mode 100644 pkg/database/stop_test.go delete mode 100644 pkg/database/train_stop.go delete mode 100644 pkg/database/train_stop_test.go create mode 100644 pkg/model/stop.go delete mode 100644 pkg/model/train_stop.go create mode 100644 pkg/navitia_api_client/stops.go create mode 100644 pkg/navitia_api_client/stops_test.go delete mode 100644 pkg/navitia_api_client/train_stops.go delete 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 945f04b..b86bb1c 100644 --- a/internal/webui/root_test.go +++ b/internal/webui/root_test.go @@ -12,7 +12,7 @@ import ( type NavitiaMockClient struct { departures []model.Departure - trainStops []model.TrainStop + trainStops []model.Stop err error } @@ -20,7 +20,7 @@ func (c *NavitiaMockClient) GetDepartures(trainStop string) (departures []model. return c.departures, c.err } -func (c *NavitiaMockClient) GetTrainStops() (trainStops []model.TrainStop, err error) { +func (c *NavitiaMockClient) GetStops() (trainStops []model.Stop, err error) { return c.trainStops, c.err } diff --git a/internal/webui/webui.go b/internal/webui/webui.go index 1a251f4..fa59f68 100644 --- a/internal/webui/webui.go +++ b/internal/webui/webui.go @@ -19,11 +19,11 @@ func Run(c *config.Config, dbEnv *database.DBEnv) { http.Handle("/login", handler{&e, loginHandler}) http.Handle("/static/", http.FileServer(http.FS(staticFS))) - if i, err := dbEnv.CountTrainStops(); err == nil && i == 0 { + if i, err := dbEnv.CountStops(); err == nil && i == 0 { log.Printf("No trains stops data found, updating...") - if trainStops, err := e.navitia.GetTrainStops(); err == nil { + if trainStops, err := e.navitia.GetStops(); err == nil { log.Printf("Updated trains stops data from navitia api, got %d results", len(trainStops)) - if err = dbEnv.ReplaceAndImportTrainStops(trainStops); err != nil { + if err = dbEnv.ReplaceAndImportStops(trainStops); err != nil { if dberr, ok := err.(*database.QueryError); ok { log.Printf("%+v", dberr.Unwrap()) } diff --git a/pkg/database/migrations.go b/pkg/database/migrations.go index ada638a..c2f35dc 100644 --- a/pkg/database/migrations.go +++ b/pkg/database/migrations.go @@ -23,7 +23,7 @@ var allMigrations = []func(tx *sql.Tx) error{ created_at DATE DEFAULT (datetime('now')), FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); - CREATE TABLE train_stops ( + CREATE TABLE stops ( id TEXT PRIMARY KEY, name TEXT NOT NULL );` diff --git a/pkg/database/stop.go b/pkg/database/stop.go new file mode 100644 index 0000000..519b9aa --- /dev/null +++ b/pkg/database/stop.go @@ -0,0 +1,47 @@ +package database + +import ( + "git.adyxax.org/adyxax/trains/pkg/model" +) + +func (env *DBEnv) CountStops() (i int, err error) { + query := `SELECT count(*) from stops;` + err = env.db.QueryRow(query).Scan(&i) + if err != nil { + return 0, newQueryError("Could not run database query: most likely the schema is corrupted", err) + } + return +} + +func (env *DBEnv) ReplaceAndImportStops(trainStops []model.Stop) error { + pre_query := `DELETE FROM stops;` + query := ` + INSERT INTO stops + (id, name) + VALUES + ($1, $2);` + tx, err := env.db.Begin() + if err != nil { + return newTransactionError("Could not Begin()", err) + } + _, err = tx.Exec(pre_query) + if err != nil { + tx.Rollback() + return newQueryError("Could not run database query: most likely the schema is corrupted", err) + } + for i := 0; i < len(trainStops); i++ { + _, err = tx.Exec( + query, + trainStops[i].Id, + trainStops[i].Name, + ) + if err != nil { + tx.Rollback() + return newQueryError("Could not run database query", err) + } + } + if err := tx.Commit(); err != nil { + return newTransactionError("Could not commit transaction", err) + } + return nil +} diff --git a/pkg/database/stop_test.go b/pkg/database/stop_test.go new file mode 100644 index 0000000..7329ac7 --- /dev/null +++ b/pkg/database/stop_test.go @@ -0,0 +1,130 @@ +package database + +import ( + "reflect" + "testing" + + "git.adyxax.org/adyxax/trains/pkg/model" + "github.com/DATA-DOG/go-sqlmock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCountStops(t *testing.T) { + trainStops := []model.Stop{ + model.Stop{Id: "id1", Name: "name1"}, + model.Stop{Id: "id2", Name: "name2"}, + } + // test db setup + db, err := InitDB("sqlite3", "file::memory:?_foreign_keys=on") + require.NoError(t, err) + // check sql error + i, err := db.CountStops() + require.Error(t, err) + assert.Equalf(t, reflect.TypeOf(err), reflect.TypeOf(&QueryError{}), "Invalid error type. Got %s but expected %s", reflect.TypeOf(err), reflect.TypeOf(&QueryError{})) + // normal check + err = db.Migrate() + require.NoError(t, err) + err = db.ReplaceAndImportStops(trainStops) + i, err = db.CountStops() + require.NoError(t, err) + assert.Equal(t, i, len(trainStops)) +} + +func TestReplaceAndImportStops(t *testing.T) { + // test db setup + db, err := InitDB("sqlite3", "file::memory:?_foreign_keys=on") + require.NoError(t, err) + err = db.Migrate() + require.NoError(t, err) + // datasets + data1 := []model.Stop{ + model.Stop{Id: "first", Name: "firstName"}, + model.Stop{Id: "second", Name: "secondName"}, + } + data2 := []model.Stop{ + model.Stop{Id: "first", Name: "firstTest"}, + model.Stop{Id: "secondTest", Name: "secondTest"}, + model.Stop{Id: "thirdTest", Name: "thirdTest"}, + } + testCases := []struct { + name string + input []model.Stop + expectedError interface{} + }{ + {"Normal insert", data1, nil}, + {"Normal insert overwrite", data2, nil}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := db.ReplaceAndImportStops(tc.input) + 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)) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestReplaceAndImportStopsWithSQLMock(t *testing.T) { + // datasets + data1 := []model.Stop{ + model.Stop{Id: "first", Name: "firstName"}, + model.Stop{Id: "second", Name: "secondName"}, + } + // Transaction begin error + dbBeginError, _, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + defer dbBeginError.Close() + // Query error cannot delete from + dbCannotDeleteFrom, mockCannotDeleteFrom, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + defer dbCannotDeleteFrom.Close() + mockCannotDeleteFrom.ExpectBegin() + // Transaction commit error + dbCannotInsertError, mockCannotInsertError, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + defer dbCannotInsertError.Close() + mockCannotInsertError.ExpectBegin() + mockCannotInsertError.ExpectExec(`DELETE FROM`).WillReturnResult(sqlmock.NewResult(1, 1)) + // Transaction commit error + dbCommitError, mockCommitError, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + defer dbCommitError.Close() + mockCommitError.ExpectBegin() + mockCommitError.ExpectExec(`DELETE FROM`).WillReturnResult(sqlmock.NewResult(1, 1)) + mockCommitError.ExpectExec(`INSERT INTO`).WillReturnResult(sqlmock.NewResult(1, 1)) + mockCommitError.ExpectExec(`INSERT INTO`).WillReturnResult(sqlmock.NewResult(1, 1)) + // Test cases + testCases := []struct { + name string + db *DBEnv + expectedError interface{} + }{ + {"begin transaction error", &DBEnv{db: dbBeginError}, &TransactionError{}}, + {"query error cannot delete from", &DBEnv{db: dbCannotDeleteFrom}, &QueryError{}}, + {"query error cannot insert into", &DBEnv{db: dbCannotInsertError}, &QueryError{}}, + {"commit transaction error", &DBEnv{db: dbCommitError}, &TransactionError{}}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.db.ReplaceAndImportStops(data1) + 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)) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/pkg/database/train_stop.go b/pkg/database/train_stop.go deleted file mode 100644 index ee7be00..0000000 --- a/pkg/database/train_stop.go +++ /dev/null @@ -1,47 +0,0 @@ -package database - -import ( - "git.adyxax.org/adyxax/trains/pkg/model" -) - -func (env *DBEnv) CountTrainStops() (i int, err error) { - query := `SELECT count(*) from train_stops;` - err = env.db.QueryRow(query).Scan(&i) - if err != nil { - return 0, newQueryError("Could not run database query: most likely the schema is corrupted", err) - } - return -} - -func (env *DBEnv) ReplaceAndImportTrainStops(trainStops []model.TrainStop) error { - pre_query := `DELETE FROM train_stops;` - query := ` - INSERT INTO train_stops - (id, name) - VALUES - ($1, $2);` - tx, err := env.db.Begin() - if err != nil { - return newTransactionError("Could not Begin()", err) - } - _, err = tx.Exec(pre_query) - if err != nil { - tx.Rollback() - return newQueryError("Could not run database query: most likely the schema is corrupted", err) - } - for i := 0; i < len(trainStops); i++ { - _, err = tx.Exec( - query, - trainStops[i].Id, - trainStops[i].Name, - ) - if err != nil { - tx.Rollback() - return newQueryError("Could not run database query", err) - } - } - if err := tx.Commit(); err != nil { - return newTransactionError("Could not commit transaction", err) - } - return nil -} diff --git a/pkg/database/train_stop_test.go b/pkg/database/train_stop_test.go deleted file mode 100644 index b3f9459..0000000 --- a/pkg/database/train_stop_test.go +++ /dev/null @@ -1,130 +0,0 @@ -package database - -import ( - "reflect" - "testing" - - "git.adyxax.org/adyxax/trains/pkg/model" - "github.com/DATA-DOG/go-sqlmock" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestCountTrainStops(t *testing.T) { - trainStops := []model.TrainStop{ - model.TrainStop{Id: "id1", Name: "name1"}, - model.TrainStop{Id: "id2", Name: "name2"}, - } - // test db setup - db, err := InitDB("sqlite3", "file::memory:?_foreign_keys=on") - require.NoError(t, err) - // check sql error - i, err := db.CountTrainStops() - require.Error(t, err) - assert.Equalf(t, reflect.TypeOf(err), reflect.TypeOf(&QueryError{}), "Invalid error type. Got %s but expected %s", reflect.TypeOf(err), reflect.TypeOf(&QueryError{})) - // normal check - err = db.Migrate() - require.NoError(t, err) - err = db.ReplaceAndImportTrainStops(trainStops) - i, err = db.CountTrainStops() - require.NoError(t, err) - assert.Equal(t, i, len(trainStops)) -} - -func TestReplaceAndImportTrainStops(t *testing.T) { - // test db setup - db, err := InitDB("sqlite3", "file::memory:?_foreign_keys=on") - require.NoError(t, err) - err = db.Migrate() - require.NoError(t, err) - // datasets - data1 := []model.TrainStop{ - model.TrainStop{Id: "first", Name: "firstName"}, - model.TrainStop{Id: "second", Name: "secondName"}, - } - data2 := []model.TrainStop{ - model.TrainStop{Id: "first", Name: "firstTest"}, - model.TrainStop{Id: "secondTest", Name: "secondTest"}, - model.TrainStop{Id: "thirdTest", Name: "thirdTest"}, - } - testCases := []struct { - name string - input []model.TrainStop - expectedError interface{} - }{ - {"Normal insert", data1, nil}, - {"Normal insert overwrite", data2, nil}, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - err := db.ReplaceAndImportTrainStops(tc.input) - 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)) - } else { - require.NoError(t, err) - } - }) - } -} - -func TestReplaceAndImportTrainStopsWithSQLMock(t *testing.T) { - // datasets - data1 := []model.TrainStop{ - model.TrainStop{Id: "first", Name: "firstName"}, - model.TrainStop{Id: "second", Name: "secondName"}, - } - // Transaction begin error - dbBeginError, _, err := sqlmock.New() - if err != nil { - t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) - } - defer dbBeginError.Close() - // Query error cannot delete from - dbCannotDeleteFrom, mockCannotDeleteFrom, err := sqlmock.New() - if err != nil { - t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) - } - defer dbCannotDeleteFrom.Close() - mockCannotDeleteFrom.ExpectBegin() - // Transaction commit error - dbCannotInsertError, mockCannotInsertError, err := sqlmock.New() - if err != nil { - t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) - } - defer dbCannotInsertError.Close() - mockCannotInsertError.ExpectBegin() - mockCannotInsertError.ExpectExec(`DELETE FROM`).WillReturnResult(sqlmock.NewResult(1, 1)) - // Transaction commit error - dbCommitError, mockCommitError, err := sqlmock.New() - if err != nil { - t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) - } - defer dbCommitError.Close() - mockCommitError.ExpectBegin() - mockCommitError.ExpectExec(`DELETE FROM`).WillReturnResult(sqlmock.NewResult(1, 1)) - mockCommitError.ExpectExec(`INSERT INTO`).WillReturnResult(sqlmock.NewResult(1, 1)) - mockCommitError.ExpectExec(`INSERT INTO`).WillReturnResult(sqlmock.NewResult(1, 1)) - // Test cases - testCases := []struct { - name string - db *DBEnv - expectedError interface{} - }{ - {"begin transaction error", &DBEnv{db: dbBeginError}, &TransactionError{}}, - {"query error cannot delete from", &DBEnv{db: dbCannotDeleteFrom}, &QueryError{}}, - {"query error cannot insert into", &DBEnv{db: dbCannotInsertError}, &QueryError{}}, - {"commit transaction error", &DBEnv{db: dbCommitError}, &TransactionError{}}, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - err := tc.db.ReplaceAndImportTrainStops(data1) - 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)) - } else { - require.NoError(t, err) - } - }) - } -} diff --git a/pkg/model/stop.go b/pkg/model/stop.go new file mode 100644 index 0000000..d6522e3 --- /dev/null +++ b/pkg/model/stop.go @@ -0,0 +1,6 @@ +package model + +type Stop struct { + Id string + Name string +} diff --git a/pkg/model/train_stop.go b/pkg/model/train_stop.go deleted file mode 100644 index e5b1f6e..0000000 --- a/pkg/model/train_stop.go +++ /dev/null @@ -1,6 +0,0 @@ -package model - -type TrainStop struct { - Id string - Name string -} diff --git a/pkg/navitia_api_client/client.go b/pkg/navitia_api_client/client.go index ccd7198..2ca0e5d 100644 --- a/pkg/navitia_api_client/client.go +++ b/pkg/navitia_api_client/client.go @@ -11,7 +11,7 @@ import ( type Client interface { GetDepartures(trainStop string) (departures []model.Departure, err error) - GetTrainStops() (trainStops []model.TrainStop, err error) + GetStops() (trainStops []model.Stop, err error) } type NavitiaClient struct { diff --git a/pkg/navitia_api_client/stops.go b/pkg/navitia_api_client/stops.go new file mode 100644 index 0000000..f0cbcf8 --- /dev/null +++ b/pkg/navitia_api_client/stops.go @@ -0,0 +1,70 @@ +package navitia_api_client + +import ( + "encoding/json" + "fmt" + "net/http" + + "git.adyxax.org/adyxax/trains/pkg/model" +) + +type StopsResponse 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 string `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) GetStops() (trainStops []model.Stop, err error) { + return getStopsPage(c, 0) +} + +func getStopsPage(c *NavitiaClient, i int) (trainStops []model.Stop, 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 StopsResponse + if err = json.NewDecoder(resp.Body).Decode(&data); err != nil { + return nil, newJsonDecodeError("GetStops ", err) + } + for i := 0; i < len(data.StopAreas); i++ { + if data.StopAreas[i].Label != "" { + trainStops = append(trainStops, model.Stop{data.StopAreas[i].ID, data.StopAreas[i].Label}) + } + } + if data.Pagination.ItemsOnPage+data.Pagination.ItemsPerPage*data.Pagination.StartPage < data.Pagination.TotalResult { + tss, err := getStopsPage(c, i+1) + if err != nil { + return nil, err + } + trainStops = append(trainStops, tss...) + } + } else { + err = newApiError(resp.StatusCode, "GetStops") + } + return +} diff --git a/pkg/navitia_api_client/stops_test.go b/pkg/navitia_api_client/stops_test.go new file mode 100644 index 0000000..0b94765 --- /dev/null +++ b/pkg/navitia_api_client/stops_test.go @@ -0,0 +1,108 @@ +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 TestGetStops(t *testing.T) { + // Simple Test cases + testCases := []struct { + name string + inputNewCLient string + expected []model.Stop + 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.GetStops() + 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.Stop + 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.GetStops() + 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.GetStops() + 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.GetStops() + if err != nil { + t.Fatalf("could not get train stops : %s", err) + } + // 4 records but one is empty (navitia api quirk) + if len(trainStops) != 3 { + 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.GetStops() + if err != nil { + t.Fatalf("could not get train stops : %+v", err) + } + // 12 records but one is empty (navitia api quirk) + if len(trainStops) != 11 { + 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.GetStops() + if err == nil { + t.Fatalf("should not be able to get train stops : %+v", trainStops) + } +} diff --git a/pkg/navitia_api_client/train_stops.go b/pkg/navitia_api_client/train_stops.go deleted file mode 100644 index a31ccf7..0000000 --- a/pkg/navitia_api_client/train_stops.go +++ /dev/null @@ -1,70 +0,0 @@ -package navitia_api_client - -import ( - "encoding/json" - "fmt" - "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 string `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++ { - if data.StopAreas[i].Label != "" { - trainStops = append(trainStops, model.TrainStop{data.StopAreas[i].ID, data.StopAreas[i].Label}) - } - } - if data.Pagination.ItemsOnPage+data.Pagination.ItemsPerPage*data.Pagination.StartPage < data.Pagination.TotalResult { - 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 deleted file mode 100644 index 9e1c982..0000000 --- a/pkg/navitia_api_client/train_stops_test.go +++ /dev/null @@ -1,108 +0,0 @@ -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) - } - // 4 records but one is empty (navitia api quirk) - if len(trainStops) != 3 { - 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) - } - // 12 records but one is empty (navitia api quirk) - if len(trainStops) != 11 { - 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