summaryrefslogtreecommitdiff
path: root/web/server_test.go
diff options
context:
space:
mode:
authorMartin Kagamino Lehoux <martin@lehoux.net>2026-08-05 11:21:55 +0200
committerMartin Kagamino Lehoux <martin@lehoux.net>2026-08-05 11:21:55 +0200
commitbef31e1d35fe88f2698cf0e3191f26a79623f66f (patch)
tree8d8e10b2a58fc4ca557f0312cfda60adfc308c30 /web/server_test.go
parent69f317d8cb51b911732712a9fcc8ac49563326c6 (diff)
feat: Add web ride library with Strava sync
Diffstat (limited to 'web/server_test.go')
-rw-r--r--web/server_test.go133
1 files changed, 133 insertions, 0 deletions
diff --git a/web/server_test.go b/web/server_test.go
new file mode 100644
index 0000000..8d3f653
--- /dev/null
+++ b/web/server_test.go
@@ -0,0 +1,133 @@
+package web
+
+import (
+ "database/sql"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/martinlehoux/biking_home/rides"
+ _ "github.com/mattn/go-sqlite3"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func newWebTestServer(t *testing.T) (*Server, *sql.DB) {
+ t.Helper()
+ db, err := sql.Open("sqlite3", ":memory:")
+ require.NoError(t, err)
+ t.Cleanup(func() { db.Close() })
+ _, err = db.Exec(`
+ create table rides (
+ id integer primary key,
+ external_id text unique not null,
+ gpx_path text not null,
+ name text not null,
+ type text not null,
+ start_date text not null,
+ distance_m real not null,
+ moving_time_s integer not null,
+ elapsed_time_s integer not null,
+ total_elevation_gain_m real not null,
+ average_speed_mps real not null,
+ created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
+ updated_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
+ )
+ `)
+ require.NoError(t, err)
+ envPath := filepath.Join(t.TempDir(), ".env")
+ require.NoError(t, os.WriteFile(envPath, []byte("STRAVA_CLIENT_ID=123\nSTRAVA_CLIENT_SECRET=secret\n"), 0o600))
+ return NewServer(db, envPath, t.TempDir(), "http://localhost:8080"), db
+}
+
+func TestHandlerRendersRidesPage(t *testing.T) {
+ server, db := newWebTestServer(t)
+ require.NoError(t, rides.Save(db, rides.Ride{
+ ExternalID: "strava:1234",
+ GPXPath: "missing.gpx",
+ Name: "Long Ride",
+ Type: "Ride",
+ StartDate: time.Date(2026, 8, 1, 7, 0, 0, 0, time.UTC),
+ DistanceM: 20_000,
+ }))
+ require.NoError(t, rides.Save(db, rides.Ride{
+ ExternalID: "strava:5678",
+ GPXPath: "short.gpx",
+ Name: "Short Ride",
+ Type: "Ride",
+ StartDate: time.Date(2026, 8, 2, 7, 0, 0, 0, time.UTC),
+ DistanceM: 9_999,
+ }))
+ req := httptest.NewRequest(http.MethodGet, "/", nil)
+ response := httptest.NewRecorder()
+
+ server.Handler().ServeHTTP(response, req)
+
+ assert.Equal(t, http.StatusOK, response.Code)
+ assert.Contains(t, response.Body.String(), "All rides")
+ assert.Contains(t, response.Body.String(), "Long Ride")
+ assert.NotContains(t, response.Body.String(), "Short Ride")
+ assert.Contains(t, response.Body.String(), "Cotacol")
+ assert.Contains(t, response.Body.String(), "Cotacol / 100 km")
+}
+
+func TestSyncPageRequestsAuthorization(t *testing.T) {
+ server, _ := newWebTestServer(t)
+ req := httptest.NewRequest(http.MethodGet, "/sync", nil)
+ response := httptest.NewRecorder()
+
+ server.Handler().ServeHTTP(response, req)
+
+ assert.Equal(t, http.StatusOK, response.Code)
+ assert.Contains(t, response.Body.String(), "Authorize Strava")
+}
+
+func TestSyncRedirectsToOAuthWhenUnauthenticated(t *testing.T) {
+ server, _ := newWebTestServer(t)
+ form := url.Values{"from": {"2026-08-01"}, "to": {"2026-08-04"}}
+ req := httptest.NewRequest(http.MethodPost, "/sync", strings.NewReader(form.Encode()))
+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+ response := httptest.NewRecorder()
+
+ server.Handler().ServeHTTP(response, req)
+
+ assert.Equal(t, http.StatusFound, response.Code)
+ location, err := url.Parse(response.Header().Get("Location"))
+ require.NoError(t, err)
+ assert.Equal(t, "/strava/login", location.Path)
+ assert.Equal(t, "/sync?from=2026-08-01&to=2026-08-04", location.Query().Get("return_to"))
+}
+
+func TestStravaLoginRedirectsToAuthorize(t *testing.T) {
+ server, _ := newWebTestServer(t)
+ req := httptest.NewRequest(http.MethodGet, "/strava/login?return_to=/sync", nil)
+ response := httptest.NewRecorder()
+
+ server.Handler().ServeHTTP(response, req)
+
+ assert.Equal(t, http.StatusFound, response.Code)
+ location, err := url.Parse(response.Header().Get("Location"))
+ require.NoError(t, err)
+ assert.Equal(t, "www.strava.com", location.Host)
+ assert.Equal(t, "/oauth/authorize", location.Path)
+ assert.Equal(t, "activity:read_all", location.Query().Get("scope"))
+ assert.Equal(t, "http://localhost:8080/strava/callback", location.Query().Get("redirect_uri"))
+ assert.NotEmpty(t, location.Query().Get("state"))
+}
+
+func TestParseDateRangeMakesEndInclusive(t *testing.T) {
+ from, to, err := parseDateRange("2026-08-01", "2026-08-04")
+ require.NoError(t, err)
+ assert.Equal(t, "2026-08-01T00:00:00Z", from.Format("2006-01-02T15:04:05Z07:00"))
+ assert.Equal(t, "2026-08-05T00:00:00Z", to.Format("2006-01-02T15:04:05Z07:00"))
+}
+
+func TestFormatCotacolPer100Km(t *testing.T) {
+ assert.Equal(t, "20.0", formatCotacolPer100Km(2, 10_000))
+ assert.Equal(t, "-", formatCotacolPer100Km(2, 0))
+}