mirror of
https://github.com/sstent/go-garminconnect.git
synced 2026-01-26 00:52:43 +00:00
327 lines
8.6 KiB
Go
327 lines
8.6 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/sstent/go-garminconnect/internal/auth/garth"
|
|
)
|
|
|
|
// MockServer simulates the Garmin Connect API
|
|
type MockServer struct {
|
|
server *httptest.Server
|
|
mu sync.Mutex
|
|
|
|
// Endpoint handlers
|
|
activitiesHandler http.HandlerFunc
|
|
activityDetailsHandler http.HandlerFunc
|
|
uploadHandler http.HandlerFunc
|
|
userHandler http.HandlerFunc
|
|
healthHandler http.HandlerFunc
|
|
authHandler http.HandlerFunc
|
|
|
|
// Request counters
|
|
requestCounters map[string]int
|
|
}
|
|
|
|
// NewMockServer creates a new mock Garmin Connect server
|
|
func NewMockServer() *MockServer {
|
|
m := &MockServer{
|
|
requestCounters: make(map[string]int),
|
|
}
|
|
m.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
// Track request count
|
|
if m.requestCounters == nil {
|
|
m.requestCounters = make(map[string]int)
|
|
}
|
|
endpointType := "unknown"
|
|
path := r.URL.Path
|
|
switch {
|
|
case strings.HasPrefix(path, "/activitylist-service/activities/search") || path == "/activitylist-service/activities":
|
|
endpointType = "activities"
|
|
m.handleActivities(w, r)
|
|
case strings.HasPrefix(path, "/activity-service/activities") || path == "/activity-service/activities":
|
|
endpointType = "activities"
|
|
m.handleActivities(w, r)
|
|
case strings.HasPrefix(path, "/activity-service/activity/"):
|
|
endpointType = "activityDetails"
|
|
m.handleActivityDetails(w, r)
|
|
case strings.HasPrefix(path, "/upload-service/upload") || path == "/upload-service/upload":
|
|
endpointType = "upload"
|
|
m.handleUpload(w, r)
|
|
case strings.HasPrefix(path, "/user-service/user") || path == "/user-service/user":
|
|
endpointType = "user"
|
|
m.handleUserData(w, r)
|
|
case strings.HasPrefix(path, "/health-service") || path == "/health-service":
|
|
endpointType = "health"
|
|
m.handleHealthData(w, r)
|
|
case strings.HasPrefix(path, "/auth") || path == "/auth":
|
|
endpointType = "auth"
|
|
m.handleAuth(w, r)
|
|
default:
|
|
endpointType = "unknown"
|
|
http.Error(w, "Not found", http.StatusNotFound)
|
|
}
|
|
m.requestCounters[endpointType]++
|
|
}))
|
|
return m
|
|
}
|
|
|
|
// URL returns the base URL of the mock server
|
|
func (m *MockServer) URL() string {
|
|
return m.server.URL
|
|
}
|
|
|
|
// Close shuts down the mock server
|
|
func (m *MockServer) Close() {
|
|
m.server.Close()
|
|
}
|
|
|
|
// SetActivitiesHandler sets a custom handler for activities endpoint
|
|
func (m *MockServer) SetActivitiesHandler(handler http.HandlerFunc) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.activitiesHandler = handler
|
|
}
|
|
|
|
// SetUploadHandler sets a custom handler for upload endpoint
|
|
func (m *MockServer) SetUploadHandler(handler http.HandlerFunc) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.uploadHandler = handler
|
|
}
|
|
|
|
// SetActivityDetailsHandler sets a custom handler for activity details endpoint
|
|
func (m *MockServer) SetActivityDetailsHandler(handler http.HandlerFunc) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.activityDetailsHandler = handler
|
|
}
|
|
|
|
// SetUserHandler sets a custom handler for user endpoint
|
|
func (m *MockServer) SetUserHandler(handler http.HandlerFunc) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.userHandler = handler
|
|
}
|
|
|
|
// SetHealthHandler sets a custom handler for health endpoint
|
|
func (m *MockServer) SetHealthHandler(handler http.HandlerFunc) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.healthHandler = handler
|
|
}
|
|
|
|
// SetAuthHandler sets a custom handler for auth endpoint
|
|
func (m *MockServer) SetAuthHandler(handler http.HandlerFunc) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.authHandler = handler
|
|
}
|
|
|
|
// Reset resets all handlers and counters to default state
|
|
func (m *MockServer) Reset() {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.activitiesHandler = nil
|
|
m.activityDetailsHandler = nil
|
|
m.uploadHandler = nil
|
|
m.userHandler = nil
|
|
m.healthHandler = nil
|
|
m.authHandler = nil
|
|
m.requestCounters = make(map[string]int)
|
|
}
|
|
|
|
// RequestCount returns the number of requests made to a specific endpoint
|
|
func (m *MockServer) RequestCount(endpoint string) int {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
return m.requestCounters[endpoint]
|
|
}
|
|
|
|
// SetResponse sets a standardized response for a specific endpoint
|
|
func (m *MockServer) SetResponse(endpoint string, status int, body interface{}) {
|
|
handler := func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(status)
|
|
json.NewEncoder(w).Encode(body)
|
|
}
|
|
|
|
switch endpoint {
|
|
case "activities":
|
|
m.SetActivitiesHandler(handler)
|
|
case "activityDetails":
|
|
m.SetActivityDetailsHandler(handler)
|
|
case "upload":
|
|
m.SetUploadHandler(handler)
|
|
case "user":
|
|
m.SetUserHandler(handler)
|
|
case "health":
|
|
m.SetHealthHandler(handler)
|
|
case "auth":
|
|
m.SetAuthHandler(handler)
|
|
}
|
|
}
|
|
|
|
// SetErrorResponse configures an error response for a specific endpoint
|
|
func (m *MockServer) SetErrorResponse(endpoint string, status int, message string) {
|
|
m.SetResponse(endpoint, status, map[string]string{"error": message})
|
|
}
|
|
|
|
// Default handler implementations would follow for each endpoint
|
|
// ...
|
|
|
|
// handleActivities is the default activities endpoint handler
|
|
func (m *MockServer) handleActivities(w http.ResponseWriter, r *http.Request) {
|
|
if m.activitiesHandler != nil {
|
|
m.activitiesHandler(w, r)
|
|
return
|
|
}
|
|
// Default implementation
|
|
activities := []ActivityResponse{
|
|
{
|
|
ActivityID: 1,
|
|
Name: "Morning Run",
|
|
StartTime: garminTime{time.Now().Add(-24 * time.Hour)},
|
|
Duration: 3600,
|
|
Distance: 10.0,
|
|
},
|
|
}
|
|
json.NewEncoder(w).Encode(ActivitiesResponse{
|
|
Activities: activities,
|
|
Pagination: Pagination{
|
|
Page: 1,
|
|
PageSize: 10,
|
|
TotalCount: 1,
|
|
},
|
|
})
|
|
}
|
|
|
|
// handleActivityDetails is the default activity details endpoint handler
|
|
func (m *MockServer) handleActivityDetails(w http.ResponseWriter, r *http.Request) {
|
|
if m.activityDetailsHandler != nil {
|
|
m.activityDetailsHandler(w, r)
|
|
return
|
|
}
|
|
// Extract activity ID from path
|
|
pathParts := strings.Split(r.URL.Path, "/")
|
|
activityID, err := strconv.ParseInt(pathParts[len(pathParts)-1], 10, 64)
|
|
if err != nil {
|
|
http.Error(w, "Invalid activity ID", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
activity := ActivityDetailResponse{
|
|
ActivityResponse: ActivityResponse{
|
|
ActivityID: activityID,
|
|
Name: "Mock Activity",
|
|
Type: "RUNNING",
|
|
StartTime: garminTime{time.Now().Add(-24 * time.Hour)},
|
|
Duration: 3600,
|
|
Distance: 10.0,
|
|
},
|
|
Calories: 500,
|
|
AverageHR: 150,
|
|
MaxHR: 170,
|
|
ElevationGain: 100,
|
|
}
|
|
|
|
json.NewEncoder(w).Encode(activity)
|
|
}
|
|
|
|
// handleUpload is the default activity upload handler
|
|
func (m *MockServer) handleUpload(w http.ResponseWriter, r *http.Request) {
|
|
if m.uploadHandler != nil {
|
|
m.uploadHandler(w, r)
|
|
return
|
|
}
|
|
// Simulate successful upload
|
|
response := map[string]interface{}{
|
|
"activityId": 12345,
|
|
}
|
|
w.WriteHeader(http.StatusCreated)
|
|
json.NewEncoder(w).Encode(response)
|
|
}
|
|
|
|
// handleUserData is the default user data handler
|
|
func (m *MockServer) handleUserData(w http.ResponseWriter, r *http.Request) {
|
|
if m.userHandler != nil {
|
|
m.userHandler(w, r)
|
|
return
|
|
}
|
|
// Return mock user data
|
|
user := map[string]interface{}{
|
|
"displayName": "Mock User",
|
|
"email": "mock@example.com",
|
|
}
|
|
json.NewEncoder(w).Encode(user)
|
|
}
|
|
|
|
// handleHealthData is the default health data handler
|
|
func (m *MockServer) handleHealthData(w http.ResponseWriter, r *http.Request) {
|
|
if m.healthHandler != nil {
|
|
m.healthHandler(w, r)
|
|
return
|
|
}
|
|
// Return mock health data
|
|
data := map[string]interface{}{
|
|
"bodyBattery": 90,
|
|
"stress": 35,
|
|
"sleep": map[string]interface{}{
|
|
"duration": 480,
|
|
"quality": 85,
|
|
},
|
|
}
|
|
json.NewEncoder(w).Encode(data)
|
|
}
|
|
|
|
// handleAuth is the default authentication handler
|
|
func (m *MockServer) handleAuth(w http.ResponseWriter, r *http.Request) {
|
|
if m.authHandler != nil {
|
|
m.authHandler(w, r)
|
|
return
|
|
}
|
|
|
|
// Handle session refresh requests
|
|
if strings.Contains(r.URL.Path, "/refresh") {
|
|
// Validate refresh token and return new access token
|
|
response := map[string]interface{}{
|
|
"oauth2_token": "new-mock-token",
|
|
"expires_in": 3600,
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(response)
|
|
return
|
|
}
|
|
|
|
// Simulate successful authentication
|
|
response := map[string]interface{}{
|
|
"oauth2_token": "mock-access-token",
|
|
"refresh_token": "mock-refresh-token",
|
|
"expires_in": 3600,
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(response)
|
|
}
|
|
|
|
// NewClientWithBaseURL creates a test client that uses the mock server's URL
|
|
func NewClientWithBaseURL(baseURL string) *Client {
|
|
session := &garth.Session{
|
|
OAuth2Token: "mock-token",
|
|
ExpiresAt: time.Now().Add(8 * time.Hour),
|
|
}
|
|
client, err := NewClient(session, "")
|
|
if err != nil {
|
|
panic("failed to create test client: " + err.Error())
|
|
}
|
|
client.HTTPClient.SetBaseURL(baseURL)
|
|
return client
|
|
}
|