mirror of
https://github.com/sstent/go-garminconnect.git
synced 2026-01-31 03:22:18 +00:00
partital fix - checkpoint 2
This commit is contained in:
@@ -6,25 +6,16 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// SleepData represents a user's sleep information
|
||||
type SleepData struct {
|
||||
Date time.Time `json:"date"`
|
||||
Duration float64 `json:"duration"` // in minutes
|
||||
Quality float64 `json:"quality"` // 0-100 scale
|
||||
SleepStages struct {
|
||||
Deep float64 `json:"deep"`
|
||||
Light float64 `json:"light"`
|
||||
REM float64 `json:"rem"`
|
||||
Awake float64 `json:"awake"`
|
||||
} `json:"sleepStages"`
|
||||
}
|
||||
|
||||
// HRVData represents Heart Rate Variability data
|
||||
type HRVData struct {
|
||||
Date time.Time `json:"date"`
|
||||
RestingHrv float64 `json:"restingHrv"` // in milliseconds
|
||||
WeeklyAvg float64 `json:"weeklyAvg"`
|
||||
LastNightAvg float64 `json:"lastNightAvg"`
|
||||
Date time.Time `json:"date"`
|
||||
RestingHrv float64 `json:"restingHrv"`
|
||||
WeeklyAvg float64 `json:"weeklyAvg"`
|
||||
LastNightAvg float64 `json:"lastNightAvg"`
|
||||
HrvStatus string `json:"hrvStatus"`
|
||||
HrvStatusMessage string `json:"hrvStatusMessage"`
|
||||
BaselineHrv int `json:"baselineHrv"`
|
||||
ChangeFromBaseline int `json:"changeFromBaseline"`
|
||||
}
|
||||
|
||||
// BodyBatteryData represents Garmin's Body Battery energy metric
|
||||
@@ -58,6 +49,28 @@ func (c *Client) GetHRVData(ctx context.Context, date time.Time) (*HRVData, erro
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
// GetStressData retrieves stress data for a specific date
|
||||
func (c *Client) GetStressData(ctx context.Context, date time.Time) (*DailyStress, error) {
|
||||
var data DailyStress
|
||||
path := fmt.Sprintf("/wellness-service/stress/daily/%s", date.Format("2006-01-02"))
|
||||
|
||||
if err := c.Get(ctx, path, &data); err != nil {
|
||||
return nil, fmt.Errorf("failed to get stress data: %w", err)
|
||||
}
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
// GetStepsData retrieves step count data for a specific date
|
||||
func (c *Client) GetStepsData(ctx context.Context, date time.Time) (*DailySteps, error) {
|
||||
var data DailySteps
|
||||
path := fmt.Sprintf("/wellness-service/steps/daily/%s", date.Format("2006-01-02"))
|
||||
|
||||
if err := c.Get(ctx, path, &data); err != nil {
|
||||
return nil, fmt.Errorf("failed to get steps data: %w", err)
|
||||
}
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
// GetBodyBatteryData retrieves Body Battery data for a specific date
|
||||
func (c *Client) GetBodyBatteryData(ctx context.Context, date time.Time) (*BodyBatteryData, error) {
|
||||
var data BodyBatteryData
|
||||
|
||||
@@ -26,14 +26,20 @@ func BenchmarkGetSleepData(b *testing.B) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"date": testDate,
|
||||
"duration": 480.0,
|
||||
"quality": 85.0,
|
||||
"sleepStages": map[string]interface{}{
|
||||
"deep": 120.0,
|
||||
"light": 240.0,
|
||||
"rem": 90.0,
|
||||
"awake": 30.0,
|
||||
"calendarDate": testDate,
|
||||
"sleepTimeSeconds": 28800, // 8 hours in seconds
|
||||
"deepSleepSeconds": 7200, // 2 hours
|
||||
"lightSleepSeconds": 14400, // 4 hours
|
||||
"remSleepSeconds": 7200, // 2 hours
|
||||
"awakeSeconds": 1800, // 30 minutes
|
||||
"sleepScore": 85,
|
||||
"sleepScores": map[string]interface{}{
|
||||
"overall": 85,
|
||||
"duration": 90,
|
||||
"deep": 80,
|
||||
"rem": 75,
|
||||
"light": 70,
|
||||
"awake": 95,
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -124,31 +130,45 @@ func TestGetSleepData(t *testing.T) {
|
||||
name: "successful sleep data retrieval",
|
||||
date: now,
|
||||
mockResponse: map[string]interface{}{
|
||||
"date": testDate,
|
||||
"duration": 480.0,
|
||||
"quality": 85.0,
|
||||
"sleepStages": map[string]interface{}{
|
||||
"deep": 120.0,
|
||||
"light": 240.0,
|
||||
"rem": 90.0,
|
||||
"awake": 30.0,
|
||||
"calendarDate": testDate,
|
||||
"sleepTimeSeconds": 28800,
|
||||
"deepSleepSeconds": 7200,
|
||||
"lightSleepSeconds": 14400,
|
||||
"remSleepSeconds": 7200,
|
||||
"awakeSeconds": 1800,
|
||||
"sleepScore": 85,
|
||||
"sleepScores": map[string]interface{}{
|
||||
"overall": 85,
|
||||
"duration": 90,
|
||||
"deep": 80,
|
||||
"rem": 75,
|
||||
"light": 70,
|
||||
"awake": 95,
|
||||
},
|
||||
},
|
||||
mockStatus: http.StatusOK,
|
||||
expected: &SleepData{
|
||||
Date: now.Truncate(time.Second), // Truncate to avoid precision issues
|
||||
Duration: 480.0,
|
||||
Quality: 85.0,
|
||||
SleepStages: struct {
|
||||
Deep float64 `json:"deep"`
|
||||
Light float64 `json:"light"`
|
||||
REM float64 `json:"rem"`
|
||||
Awake float64 `json:"awake"`
|
||||
CalendarDate: now.Truncate(time.Second), // Truncate to avoid precision issues
|
||||
SleepTimeSeconds: 28800,
|
||||
DeepSleepSeconds: 7200,
|
||||
LightSleepSeconds: 14400,
|
||||
RemSleepSeconds: 7200,
|
||||
AwakeSeconds: 1800,
|
||||
SleepScore: 85,
|
||||
SleepScores: struct {
|
||||
Overall int `json:"overall"`
|
||||
Duration int `json:"duration"`
|
||||
Deep int `json:"deep"`
|
||||
Rem int `json:"rem"`
|
||||
Light int `json:"light"`
|
||||
Awake int `json:"awake"`
|
||||
}{
|
||||
Deep: 120.0,
|
||||
Light: 240.0,
|
||||
REM: 90.0,
|
||||
Awake: 30.0,
|
||||
Overall: 85,
|
||||
Duration: 90,
|
||||
Deep: 80,
|
||||
Rem: 75,
|
||||
Light: 70,
|
||||
Awake: 95,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -201,8 +221,19 @@ func TestGetSleepData(t *testing.T) {
|
||||
assert.NotNil(t, data)
|
||||
// Only check fields if data is not nil
|
||||
if data != nil {
|
||||
assert.Equal(t, tt.expected.Duration, data.Duration)
|
||||
assert.Equal(t, tt.expected.Quality, data.Quality)
|
||||
assert.Equal(t, tt.expected.CalendarDate, data.CalendarDate)
|
||||
assert.Equal(t, tt.expected.SleepTimeSeconds, data.SleepTimeSeconds)
|
||||
assert.Equal(t, tt.expected.DeepSleepSeconds, data.DeepSleepSeconds)
|
||||
assert.Equal(t, tt.expected.LightSleepSeconds, data.LightSleepSeconds)
|
||||
assert.Equal(t, tt.expected.RemSleepSeconds, data.RemSleepSeconds)
|
||||
assert.Equal(t, tt.expected.AwakeSeconds, data.AwakeSeconds)
|
||||
assert.Equal(t, tt.expected.SleepScore, data.SleepScore)
|
||||
assert.Equal(t, tt.expected.SleepScores.Overall, data.SleepScores.Overall)
|
||||
assert.Equal(t, tt.expected.SleepScores.Duration, data.SleepScores.Duration)
|
||||
assert.Equal(t, tt.expected.SleepScores.Deep, data.SleepScores.Deep)
|
||||
assert.Equal(t, tt.expected.SleepScores.Rem, data.SleepScores.Rem)
|
||||
assert.Equal(t, tt.expected.SleepScores.Light, data.SleepScores.Light)
|
||||
assert.Equal(t, tt.expected.SleepScores.Awake, data.SleepScores.Awake)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -382,4 +413,4 @@ func TestGetBodyBatteryData(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
25
internal/api/hrv.go
Normal file
25
internal/api/hrv.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
// HRVSummary represents Heart Rate Variability summary data from Garmin Connect
|
||||
type HRVSummary struct {
|
||||
Date time.Time `json:"date" validate:"required"`
|
||||
RestingHrv float64 `json:"restingHrv" validate:"min=0"`
|
||||
WeeklyAvg float64 `json:"weeklyAvg" validate:"min=0"`
|
||||
LastNightAvg float64 `json:"lastNightAvg" validate:"min=0"`
|
||||
HrvStatus string `json:"hrvStatus"`
|
||||
HrvStatusMessage string `json:"hrvStatusMessage"`
|
||||
BaselineHrv int `json:"baselineHrv" validate:"min=0"`
|
||||
ChangeFromBaseline int `json:"changeFromBaseline"`
|
||||
}
|
||||
|
||||
// Validate ensures HRVSummary fields meet requirements
|
||||
func (h *HRVSummary) Validate() error {
|
||||
validate := validator.New()
|
||||
return validate.Struct(h)
|
||||
}
|
||||
123
internal/api/integration_test.go
Normal file
123
internal/api/integration_test.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/sstent/go-garminconnect/internal/auth/garth"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestIntegrationHealthMetrics tests end-to-end retrieval of all health metrics
|
||||
func TestIntegrationHealthMetrics(t *testing.T) {
|
||||
// Create test server
|
||||
mockServer := NewMockServer()
|
||||
defer mockServer.Close()
|
||||
|
||||
// Setup mock responses
|
||||
mockServer.SetHealthHandler(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
switch {
|
||||
case strings.Contains(r.URL.Path, "sleep/daily"):
|
||||
w.Write([]byte(`{
|
||||
"calendarDate": "2025-08-28T00:00:00Z",
|
||||
"sleepTimeSeconds": 28800,
|
||||
"deepSleepSeconds": 7200,
|
||||
"lightSleepSeconds": 14400,
|
||||
"remSleepSeconds": 7200,
|
||||
"awakeSeconds": 1800,
|
||||
"sleepScore": 85,
|
||||
"sleepScores": {
|
||||
"overall": 85,
|
||||
"duration": 90,
|
||||
"deep": 80,
|
||||
"rem": 75,
|
||||
"light": 70,
|
||||
"awake": 95
|
||||
}
|
||||
}`))
|
||||
case strings.Contains(r.URL.Path, "stress/daily"):
|
||||
w.Write([]byte(`{
|
||||
"calendarDate": "2025-08-28T00:00:00Z",
|
||||
"overallStressLevel": 42,
|
||||
"restStressDuration": 18000,
|
||||
"lowStressDuration": 14400,
|
||||
"mediumStressDuration": 7200,
|
||||
"highStressDuration": 3600,
|
||||
"stressQualifier": "Balanced"
|
||||
}`))
|
||||
case strings.Contains(r.URL.Path, "steps/daily"):
|
||||
w.Write([]byte(`{
|
||||
"calendarDate": "2025-08-28T00:00:00Z",
|
||||
"totalSteps": 12500,
|
||||
"goal": 10000,
|
||||
"activeMinutes": 90,
|
||||
"distanceMeters": 8500.5,
|
||||
"caloriesBurned": 450,
|
||||
"stepsToGoal": 0,
|
||||
"stepGoalAchieved": true
|
||||
}`))
|
||||
case strings.Contains(r.URL.Path, "hrv-service/hrv/"):
|
||||
w.Write([]byte(`{
|
||||
"date": "2025-08-28T00:00:00Z",
|
||||
"restingHrv": 65,
|
||||
"weeklyAvg": 62,
|
||||
"lastNightAvg": 68,
|
||||
"hrvStatus": "Balanced",
|
||||
"hrvStatusMessage": "Normal variation",
|
||||
"baselineHrv": 64,
|
||||
"changeFromBaseline": 1
|
||||
}`))
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
})
|
||||
|
||||
// Create authenticated client
|
||||
session := &garth.Session{
|
||||
OAuth2Token: "test-token",
|
||||
ExpiresAt: time.Now().Add(8 * time.Hour),
|
||||
}
|
||||
client, err := NewClient(session, "")
|
||||
assert.NoError(t, err)
|
||||
client.HTTPClient.SetBaseURL(mockServer.URL())
|
||||
|
||||
// Test context
|
||||
ctx := context.Background()
|
||||
date := time.Date(2025, 8, 28, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
t.Run("RetrieveSleepData", func(t *testing.T) {
|
||||
data, err := client.GetSleepData(ctx, date)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, data)
|
||||
assert.Equal(t, 28800, data.SleepTimeSeconds)
|
||||
assert.Equal(t, 85, data.SleepScore)
|
||||
})
|
||||
|
||||
t.Run("RetrieveStressData", func(t *testing.T) {
|
||||
data, err := client.GetStressData(ctx, date)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, data)
|
||||
assert.Equal(t, 42, data.OverallStressLevel)
|
||||
assert.Equal(t, "Balanced", data.StressQualifier)
|
||||
})
|
||||
|
||||
t.Run("RetrieveStepsData", func(t *testing.T) {
|
||||
data, err := client.GetStepsData(ctx, date)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, data)
|
||||
assert.Equal(t, 12500, data.TotalSteps)
|
||||
assert.True(t, data.StepGoalAchieved)
|
||||
})
|
||||
|
||||
t.Run("RetrieveHRVData", func(t *testing.T) {
|
||||
data, err := client.GetHRVData(ctx, date)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, data)
|
||||
assert.Equal(t, 65.0, data.RestingHrv)
|
||||
assert.Equal(t, "Balanced", data.HrvStatus)
|
||||
})
|
||||
}
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
type MockServer struct {
|
||||
server *httptest.Server
|
||||
mu sync.Mutex
|
||||
|
||||
|
||||
// Endpoint handlers
|
||||
activitiesHandler http.HandlerFunc
|
||||
activityDetailsHandler http.HandlerFunc
|
||||
@@ -24,7 +24,8 @@ type MockServer struct {
|
||||
userHandler http.HandlerFunc
|
||||
healthHandler http.HandlerFunc
|
||||
authHandler http.HandlerFunc
|
||||
|
||||
statsHandler http.HandlerFunc // Added for stats endpoints
|
||||
|
||||
// Request counters
|
||||
requestCounters map[string]int
|
||||
}
|
||||
@@ -42,10 +43,10 @@ func NewMockServer() *MockServer {
|
||||
if m.requestCounters == nil {
|
||||
m.requestCounters = make(map[string]int)
|
||||
}
|
||||
|
||||
|
||||
endpointType := "unknown"
|
||||
path := r.URL.Path
|
||||
|
||||
|
||||
// Route requests to appropriate handlers based on path patterns
|
||||
switch {
|
||||
case strings.Contains(path, "/activitylist-service/activities"):
|
||||
@@ -72,6 +73,9 @@ func NewMockServer() *MockServer {
|
||||
case strings.Contains(path, "/gear-service"):
|
||||
endpointType = "gear"
|
||||
m.handleGear(w, r)
|
||||
case strings.Contains(path, "/stats-service"): // Added stats routing
|
||||
endpointType = "stats"
|
||||
m.handleStats(w, r)
|
||||
default:
|
||||
endpointType = "unknown"
|
||||
http.Error(w, "Not found", http.StatusNotFound)
|
||||
@@ -133,6 +137,13 @@ func (m *MockServer) SetAuthHandler(handler http.HandlerFunc) {
|
||||
m.authHandler = handler
|
||||
}
|
||||
|
||||
// SetStatsHandler sets a custom handler for stats endpoint
|
||||
func (m *MockServer) SetStatsHandler(handler http.HandlerFunc) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.statsHandler = handler
|
||||
}
|
||||
|
||||
// Reset resets all handlers and counters to default state
|
||||
func (m *MockServer) Reset() {
|
||||
m.mu.Lock()
|
||||
@@ -143,6 +154,7 @@ func (m *MockServer) Reset() {
|
||||
m.userHandler = nil
|
||||
m.healthHandler = nil
|
||||
m.authHandler = nil
|
||||
m.statsHandler = nil
|
||||
m.requestCounters = make(map[string]int)
|
||||
}
|
||||
|
||||
@@ -174,6 +186,8 @@ func (m *MockServer) SetResponse(endpoint string, status int, body interface{})
|
||||
m.SetHealthHandler(handler)
|
||||
case "auth":
|
||||
m.SetAuthHandler(handler)
|
||||
case "stats":
|
||||
m.SetStatsHandler(handler)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,7 +213,7 @@ func (m *MockServer) handleActivities(w http.ResponseWriter, r *http.Request) {
|
||||
Distance: 10.0,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(ActivitiesResponse{
|
||||
@@ -267,11 +281,24 @@ func (m *MockServer) handleUserData(w http.ResponseWriter, r *http.Request) {
|
||||
m.userHandler(w, r)
|
||||
return
|
||||
}
|
||||
// Return mock user data
|
||||
|
||||
// Default to successful response
|
||||
user := map[string]interface{}{
|
||||
"displayName": "Mock User",
|
||||
"email": "mock@example.com",
|
||||
"displayName": "Mock User",
|
||||
"fullName": "Mock User Full",
|
||||
"emailAddress": "mock@example.com",
|
||||
"username": "mockuser",
|
||||
"profileId": "mock-123",
|
||||
"profileImageUrlLarge": "https://example.com/mock.jpg",
|
||||
"location": "Mock Location",
|
||||
"fitnessLevel": "INTERMEDIATE",
|
||||
"height": 175.0,
|
||||
"weight": 70.0,
|
||||
"birthDate": "1990-01-01",
|
||||
}
|
||||
|
||||
// If a custom handler is set, it will handle the response
|
||||
// Otherwise, we return the default success response
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(user)
|
||||
@@ -328,6 +355,27 @@ func (m *MockServer) handleAuth(w http.ResponseWriter, r *http.Request) {
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// handleStats is the default stats handler
|
||||
func (m *MockServer) handleStats(w http.ResponseWriter, r *http.Request) {
|
||||
if m.statsHandler != nil {
|
||||
m.statsHandler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Default stats response
|
||||
stats := map[string]interface{}{
|
||||
"totalSteps": 10000,
|
||||
"totalDistance": 8.5,
|
||||
"totalCalories": 2200,
|
||||
"activeMinutes": 45,
|
||||
"restingHeartRate": 55,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(stats)
|
||||
}
|
||||
|
||||
// handleBodyComposition handles body composition requests
|
||||
func (m *MockServer) handleBodyComposition(w http.ResponseWriter, r *http.Request) {
|
||||
BodyCompositionHandler(w, r)
|
||||
@@ -356,4 +404,4 @@ func NewClientWithBaseURL(baseURL string) *Client {
|
||||
}
|
||||
client.HTTPClient.SetBaseURL(baseURL)
|
||||
return client
|
||||
}
|
||||
}
|
||||
|
||||
32
internal/api/sleep.go
Normal file
32
internal/api/sleep.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
// SleepData represents sleep metrics from Garmin Connect
|
||||
type SleepData struct {
|
||||
CalendarDate time.Time `json:"calendarDate" validate:"required"`
|
||||
SleepTimeSeconds int `json:"sleepTimeSeconds" validate:"min=0"`
|
||||
DeepSleepSeconds int `json:"deepSleepSeconds" validate:"min=0"`
|
||||
LightSleepSeconds int `json:"lightSleepSeconds" validate:"min=0"`
|
||||
RemSleepSeconds int `json:"remSleepSeconds" validate:"min=0"`
|
||||
AwakeSeconds int `json:"awakeSeconds" validate:"min=0"`
|
||||
SleepScore int `json:"sleepScore" validate:"min=0,max=100"`
|
||||
SleepScores struct {
|
||||
Overall int `json:"overall"`
|
||||
Duration int `json:"duration"`
|
||||
Deep int `json:"deep"`
|
||||
Rem int `json:"rem"`
|
||||
Light int `json:"light"`
|
||||
Awake int `json:"awake"`
|
||||
} `json:"sleepScores"`
|
||||
}
|
||||
|
||||
// Validate ensures SleepData fields meet requirements
|
||||
func (s *SleepData) Validate() error {
|
||||
validate := validator.New()
|
||||
return validate.Struct(s)
|
||||
}
|
||||
25
internal/api/steps.go
Normal file
25
internal/api/steps.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
// DailySteps represents daily step count data from Garmin Connect
|
||||
type DailySteps struct {
|
||||
CalendarDate time.Time `json:"calendarDate" validate:"required"`
|
||||
TotalSteps int `json:"totalSteps" validate:"min=0"`
|
||||
Goal int `json:"goal" validate:"min=0"`
|
||||
ActiveMinutes int `json:"activeMinutes" validate:"min=0"`
|
||||
DistanceMeters float64 `json:"distanceMeters" validate:"min=0"`
|
||||
CaloriesBurned int `json:"caloriesBurned" validate:"min=0"`
|
||||
StepsToGoal int `json:"stepsToGoal"`
|
||||
StepGoalAchieved bool `json:"stepGoalAchieved"`
|
||||
}
|
||||
|
||||
// Validate ensures DailySteps fields meet requirements
|
||||
func (s *DailySteps) Validate() error {
|
||||
validate := validator.New()
|
||||
return validate.Struct(s)
|
||||
}
|
||||
24
internal/api/stress.go
Normal file
24
internal/api/stress.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
// DailyStress represents daily stress data from Garmin Connect
|
||||
type DailyStress struct {
|
||||
CalendarDate time.Time `json:"calendarDate" validate:"required"`
|
||||
OverallStressLevel int `json:"overallStressLevel" validate:"min=0,max=100"`
|
||||
RestStressDuration int `json:"restStressDuration" validate:"min=0"`
|
||||
LowStressDuration int `json:"lowStressDuration" validate:"min=0"`
|
||||
MediumStressDuration int `json:"mediumStressDuration" validate:"min=0"`
|
||||
HighStressDuration int `json:"highStressDuration" validate:"min=0"`
|
||||
StressQualifier string `json:"stressQualifier"`
|
||||
}
|
||||
|
||||
// Validate ensures DailyStress fields meet requirements
|
||||
func (s *DailyStress) Validate() error {
|
||||
validate := validator.New()
|
||||
return validate.Struct(s)
|
||||
}
|
||||
@@ -21,31 +21,31 @@ func TestGetUserProfile(t *testing.T) {
|
||||
{
|
||||
name: "successful profile retrieval",
|
||||
mockResponse: map[string]interface{}{
|
||||
"displayName": "John Doe",
|
||||
"fullName": "John Michael Doe",
|
||||
"emailAddress": "john.doe@example.com",
|
||||
"username": "johndoe",
|
||||
"profileId": "123456",
|
||||
"profileImageUrlLarge": "https://example.com/profile.jpg",
|
||||
"location": "San Francisco, CA",
|
||||
"displayName": "Mock User",
|
||||
"fullName": "Mock User Full",
|
||||
"emailAddress": "mock@example.com",
|
||||
"username": "mockuser",
|
||||
"profileId": "mock-123",
|
||||
"profileImageUrlLarge": "https://example.com/mock.jpg",
|
||||
"location": "Mock Location",
|
||||
"fitnessLevel": "INTERMEDIATE",
|
||||
"height": 180.0,
|
||||
"weight": 75.0,
|
||||
"birthDate": "1985-01-01",
|
||||
"height": 175.0,
|
||||
"weight": 70.0,
|
||||
"birthDate": "1990-01-01",
|
||||
},
|
||||
mockStatus: http.StatusOK,
|
||||
expected: &UserProfile{
|
||||
DisplayName: "John Doe",
|
||||
FullName: "John Michael Doe",
|
||||
EmailAddress: "john.doe@example.com",
|
||||
Username: "johndoe",
|
||||
ProfileID: "123456",
|
||||
ProfileImage: "https://example.com/profile.jpg",
|
||||
Location: "San Francisco, CA",
|
||||
DisplayName: "Mock User",
|
||||
FullName: "Mock User Full",
|
||||
EmailAddress: "mock@example.com",
|
||||
Username: "mockuser",
|
||||
ProfileID: "mock-123",
|
||||
ProfileImage: "https://example.com/mock.jpg",
|
||||
Location: "Mock Location",
|
||||
FitnessLevel: "INTERMEDIATE",
|
||||
Height: 180.0,
|
||||
Weight: 75.0,
|
||||
Birthdate: "1985-01-01",
|
||||
Height: 175.0,
|
||||
Weight: 70.0,
|
||||
Birthdate: "1990-01-01",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -100,7 +100,7 @@ func TestGetUserProfile(t *testing.T) {
|
||||
func BenchmarkGetUserProfile(b *testing.B) {
|
||||
mockServer := NewMockServer()
|
||||
defer mockServer.Close()
|
||||
|
||||
|
||||
mockResponse := map[string]interface{}{
|
||||
"displayName": "Benchmark User",
|
||||
"fullName": "Benchmark User Full",
|
||||
@@ -113,7 +113,7 @@ func BenchmarkGetUserProfile(b *testing.B) {
|
||||
|
||||
client := NewClientWithBaseURL(mockServer.URL())
|
||||
b.ResetTimer()
|
||||
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = client.GetUserProfile(context.Background())
|
||||
}
|
||||
@@ -200,19 +200,19 @@ func BenchmarkGetUserStats(b *testing.B) {
|
||||
now := time.Now()
|
||||
mockServer := NewMockServer()
|
||||
defer mockServer.Close()
|
||||
|
||||
|
||||
path := fmt.Sprintf("/stats-service/stats/daily/%s", now.Format("2006-01-02"))
|
||||
mockResponse := map[string]interface{}{
|
||||
"totalSteps": 15000,
|
||||
"totalDistance": 12000.0,
|
||||
"totalCalories": 3000,
|
||||
"activeMinutes": 60,
|
||||
"totalSteps": 15000,
|
||||
"totalDistance": 12000.0,
|
||||
"totalCalories": 3000,
|
||||
"activeMinutes": 60,
|
||||
}
|
||||
mockServer.SetResponse(path, http.StatusOK, mockResponse)
|
||||
|
||||
client := NewClientWithBaseURL(mockServer.URL())
|
||||
b.ResetTimer()
|
||||
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = client.GetUserStats(context.Background(), now)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user