mirror of
https://github.com/sstent/go-garminconnect.git
synced 2025-12-06 08:02:02 +00:00
garth more done
This commit is contained in:
60
TODO.md
60
TODO.md
@@ -1,60 +0,0 @@
|
||||
# Go-GarminConnect Porting Project - Remaining Tasks
|
||||
|
||||
## Endpoint Implementation
|
||||
### Health Data Endpoints
|
||||
- [ ] Body composition API endpoint
|
||||
- [ ] Sleep data retrieval and parsing
|
||||
- [ ] Heart rate/HRV/RHR data endpoint
|
||||
- [ ] Stress data API implementation
|
||||
- [ ] Body battery endpoint
|
||||
|
||||
### User Data Endpoints
|
||||
- [ ] User summary endpoint
|
||||
- [ ] Daily statistics API
|
||||
- [ ] Goals/badges endpoint implementation
|
||||
- [ ] Hydration data endpoint
|
||||
- [ ] Respiration data API
|
||||
|
||||
### Activity Endpoints
|
||||
- [ ] Activity type filtering
|
||||
- [ ] Activity comment functionality
|
||||
- [ ] Activity like/unlike feature
|
||||
- [ ] Activity sharing options
|
||||
|
||||
## FIT File Handling
|
||||
- [ ] Complete weight composition encoding
|
||||
- [ ] Implement all-day stress FIT encoding
|
||||
- [ ] Add HRV data to FIT export
|
||||
- [ ] Validate FIT compatibility with Garmin devices
|
||||
- [ ] Optimize FIT file parsing performance
|
||||
|
||||
## Testing & Quality Assurance
|
||||
- [ ] Implement table-driven tests for all endpoints
|
||||
- [ ] Create mock server for isolated testing
|
||||
- [ ] Add golden file tests for FIT validation
|
||||
- [ ] Complete performance benchmarks
|
||||
- [ ] Integrate static analysis (golangci-lint)
|
||||
- [ ] Implement code coverage reporting
|
||||
- [ ] Add stress/load testing scenarios
|
||||
|
||||
## Documentation & Examples
|
||||
- [ ] Complete GoDoc coverage for all packages
|
||||
- [ ] Create usage examples for all API endpoints
|
||||
- [ ] Build CLI demonstration application
|
||||
- [ ] Port Python examples to Go equivalents
|
||||
- [ ] Update README with comprehensive documentation
|
||||
- [ ] Create migration guide from Python library
|
||||
|
||||
## Infrastructure & Optimization
|
||||
- [ ] Implement connection pooling
|
||||
- [ ] Complete rate limiting mechanism
|
||||
- [ ] Optimize session management
|
||||
- [ ] Add automatic token refresh tests
|
||||
- [ ] Implement response caching
|
||||
- [ ] Add circuit breaker pattern for API calls
|
||||
|
||||
## Project Management
|
||||
- [ ] Prioritize health data endpoints (critical path)
|
||||
- [ ] Create GitHub project board for tracking
|
||||
- [ ] Set up milestone tracking
|
||||
- [ ] Assign priority labels (P0, P1, P2)
|
||||
@@ -10,11 +10,28 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/sstent/go-garminconnect/internal/api"
|
||||
"github.com/sstent/go-garminconnect/internal/auth/garth"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "garmin-cli",
|
||||
Short: "CLI for interacting with Garmin Connect API",
|
||||
}
|
||||
|
||||
var authCmd = &cobra.Command{
|
||||
Use: "auth",
|
||||
Short: "Authentication commands",
|
||||
}
|
||||
|
||||
var loginCmd = &cobra.Command{
|
||||
Use: "login",
|
||||
Short: "Authenticate with Garmin Connect",
|
||||
Run: loginHandler,
|
||||
}
|
||||
|
||||
func loginHandler(cmd *cobra.Command, args []string) {
|
||||
// Try to load from .env if environment variables not set
|
||||
if os.Getenv("GARMIN_USERNAME") == "" || os.Getenv("GARMIN_PASSWORD") == "" {
|
||||
if err := godotenv.Load(); err != nil {
|
||||
@@ -91,6 +108,18 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Setup command structure
|
||||
authCmd.AddCommand(loginCmd)
|
||||
rootCmd.AddCommand(authCmd)
|
||||
|
||||
// Execute CLI
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// getCredentials prompts for username and password
|
||||
func getCredentials() (string, string) {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
|
||||
82
garminconnect.md
Normal file
82
garminconnect.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# Go-GarminConnect Porting Project - Progress and Roadmap
|
||||
|
||||
## Current Progress
|
||||
|
||||
### Authentication System:
|
||||
- [x] OAuth1/OAuth2 token flow implemented
|
||||
- [x] Token auto-refresh mechanism
|
||||
- [x] MFA handling with console prompts
|
||||
- [x] Session persistence to JSON file
|
||||
- [x] Comprehensive authentication tests
|
||||
|
||||
### CLI Implementation:
|
||||
- [x] Command structure with Cobra
|
||||
- [x] Basic login command skeleton
|
||||
- [ ] Session save/restore integration
|
||||
- [ ] Status command implementation
|
||||
- [ ] Logout functionality
|
||||
|
||||
### API Implementation:
|
||||
- [ ] Sleep data retrieval
|
||||
- [ ] Stress tracking API
|
||||
- [ ] Body composition models
|
||||
|
||||
## Next Steps (Priority Order)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Next Phase] --> B[CLI Completion]
|
||||
A --> C[API Enhancement]
|
||||
A --> D[FIT File Support]
|
||||
|
||||
B --> B1[Login Command]
|
||||
B --> B2[Status Command]
|
||||
B --> B3[Logout Command]
|
||||
|
||||
C --> C1[Retry Logic]
|
||||
C --> C2[Structured Logging]
|
||||
C --> C3[Sleep/Stress Endpoints]
|
||||
|
||||
D --> D1[FIT Encoding]
|
||||
D --> D2[Validation]
|
||||
```
|
||||
|
||||
### 1. CLI Completion (2 days)
|
||||
- Complete login command with session handling
|
||||
- Implement status command to verify authentication
|
||||
- Add logout functionality with session cleanup
|
||||
|
||||
### 2. API Enhancement (3 days)
|
||||
- Implement retry logic with exponential backoff
|
||||
- Add structured request/response logging
|
||||
- Develop sleep/stress endpoints
|
||||
- Create body composition models
|
||||
|
||||
### 3. FIT File Support (2 days)
|
||||
- Complete weight composition encoding
|
||||
- Implement all-day stress FIT encoding
|
||||
- Add HRV data to FIT export
|
||||
- Validate FIT compatibility
|
||||
|
||||
## Testing & Quality Assurance
|
||||
- [ ] CLI end-to-end tests
|
||||
- [ ] API integration tests
|
||||
- [ ] FIT validation tests
|
||||
- [ ] Performance benchmarks
|
||||
|
||||
## Documentation & Examples
|
||||
- [ ] CLI usage guide
|
||||
- [ ] API reference documentation
|
||||
- [ ] FIT file specification
|
||||
|
||||
## Quality Gates
|
||||
|
||||
### Before Release:
|
||||
- [ ] 100% test coverage for core features
|
||||
- [ ] Security audit of authentication flow
|
||||
- [ ] Performance benchmarks met
|
||||
|
||||
## Success Metrics
|
||||
- [ ] Authentication success rate > 99%
|
||||
- [ ] API response time < 1s
|
||||
- [ ] FIT file compatibility 100%
|
||||
423
garth.md
423
garth.md
@@ -1,4 +1,3 @@
|
||||
|
||||
# Garth Go Port Plan - Test-Driven Development Implementation
|
||||
|
||||
## Project Overview
|
||||
@@ -12,13 +11,16 @@ Based on the original Garth library, the main components are:
|
||||
- **API Client**: HTTP client for Garmin Connect API requests
|
||||
- **Data Models**: Structured data types for health/fitness metrics
|
||||
- **Session Management**: Token persistence and restoration
|
||||
- **CLI Interface**: Command-line authentication and session management
|
||||
|
||||
## 1. Project Structure
|
||||
|
||||
```
|
||||
garth-go/
|
||||
├── cmd/
|
||||
│ └── garth/ # CLI tool (like Python's uvx garth login)
|
||||
│ └── garth/ # CLI tool
|
||||
│ ├── auth.go # Authentication commands
|
||||
│ ├── commands.go # Root CLI commands
|
||||
│ └── main.go
|
||||
├── pkg/
|
||||
│ ├── auth/ # Authentication module
|
||||
@@ -27,378 +29,91 @@ garth-go/
|
||||
│ │ ├── session.go
|
||||
│ │ └── session_test.go
|
||||
│ ├── client/ # HTTP client module
|
||||
│ │ ├── client.go
|
||||
│ │ ├── client_test.go
|
||||
│ │ ├── endpoints.go
|
||||
│ │ └── endpoints_test.go
|
||||
│ ├── models/ # Data structures
|
||||
│ │ ├── sleep.go
|
||||
│ │ ├── sleep_test.go
|
||||
│ │ ├── stress.go
|
||||
│ │ ├── stress_test.go
|
||||
│ │ ├── steps.go
|
||||
│ │ ├── weight.go
|
||||
│ │ ├── hrv.go
|
||||
│ │ └── user.go
|
||||
│ └── garth/ # Main package interface
|
||||
│ ├── garth.go
|
||||
│ └── garth_test.go
|
||||
├── internal/
|
||||
│ ├── testutil/ # Test utilities
|
||||
│ │ ├── fixtures.go
|
||||
│ │ └── mock_server.go
|
||||
│ └── config/ # Internal configuration
|
||||
│ └── constants.go
|
||||
├── examples/ # Usage examples
|
||||
│ ├── basic/
|
||||
│ ├── sleep_analysis/
|
||||
│ └── stress_tracking/
|
||||
├── examples/
|
||||
├── go.mod
|
||||
├── go.sum
|
||||
├── README.md
|
||||
├── Makefile
|
||||
└── .github/
|
||||
└── workflows/
|
||||
└── ci.yml
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## 2. Data Flow Architecture
|
||||
## 2. Current Progress
|
||||
|
||||
### Authentication Flow
|
||||
```
|
||||
User Credentials → OAuth1 Token → OAuth2 Token → API Requests
|
||||
↓ ↓
|
||||
Persisted Auto-refresh
|
||||
### Authentication System:
|
||||
- [x] OAuth1/OAuth2 token flow implemented
|
||||
- [x] Token auto-refresh mechanism
|
||||
- [x] MFA handling with console prompts
|
||||
- [x] Session persistence to JSON file
|
||||
- [x] Comprehensive authentication tests
|
||||
|
||||
### CLI Implementation:
|
||||
- [x] Command structure with Cobra
|
||||
- [x] Basic login command skeleton
|
||||
- [ ] Session save/restore integration
|
||||
- [ ] Status command implementation
|
||||
- [ ] Logout functionality
|
||||
|
||||
## 3. Next Steps (Priority Order)
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[Next Phase] --> B[CLI Implementation]
|
||||
A --> C[HTTP Client Enhancement]
|
||||
A --> D[Health Data Endpoints]
|
||||
|
||||
B --> B1[Complete Login Command]
|
||||
B --> B2[Add Status Command]
|
||||
B --> B3[Implement Logout]
|
||||
|
||||
C --> C1[Retry Logic]
|
||||
C --> C2[Structured Logging]
|
||||
|
||||
D --> D1[Sleep Data API]
|
||||
D --> D2[Stress Tracking]
|
||||
```
|
||||
|
||||
### API Request Flow
|
||||
```
|
||||
Client Request → Token Validation → HTTP Request → JSON Response → Struct Unmarshaling
|
||||
↓
|
||||
Auto-refresh if expired
|
||||
```
|
||||
1. **CLI Completion (1 day)**:
|
||||
- Finalize login command with session handling
|
||||
- Implement status command to verify authentication
|
||||
- Add logout functionality with session cleanup
|
||||
|
||||
### Data Processing Flow
|
||||
```
|
||||
Raw API Response → JSON Unmarshaling → Data Validation → Business Logic → Client Response
|
||||
```
|
||||
2. **HTTP Client Improvements (1 day)**:
|
||||
- Implement retry logic with exponential backoff
|
||||
- Add structured request/response logging
|
||||
- Handle session expiration scenarios
|
||||
|
||||
## 3. Recommended Go Modules
|
||||
3. **Health Data Endpoints (2 days)**:
|
||||
- Implement sleep data retrieval
|
||||
- Build stress tracking API
|
||||
- Create body composition models
|
||||
|
||||
### Core Dependencies
|
||||
```go
|
||||
// HTTP client and utilities
|
||||
"net/http"
|
||||
"context"
|
||||
"time"
|
||||
## 4. Implementation Timeline Update
|
||||
|
||||
// JSON handling
|
||||
"encoding/json"
|
||||
|
||||
// OAuth implementation
|
||||
"golang.org/x/oauth2" // For OAuth2 flows
|
||||
|
||||
// HTTP client with advanced features
|
||||
"github.com/go-resty/resty/v2" // Alternative to net/http with better ergonomics
|
||||
|
||||
// Configuration and environment
|
||||
"github.com/spf13/viper" // Configuration management
|
||||
"github.com/spf13/cobra" // CLI framework
|
||||
|
||||
// Validation
|
||||
"github.com/go-playground/validator/v10" // Struct validation
|
||||
|
||||
// Logging
|
||||
"go.uber.org/zap" // Structured logging
|
||||
|
||||
// Testing
|
||||
"github.com/stretchr/testify" // Testing utilities
|
||||
"github.com/jarcoal/httpmock" // HTTP mocking
|
||||
```
|
||||
|
||||
### Development Dependencies
|
||||
```go
|
||||
// Code generation
|
||||
"github.com/golang/mock/gomock" // Mock generation
|
||||
|
||||
// Linting and quality
|
||||
"github.com/golangci/golangci-lint"
|
||||
```
|
||||
|
||||
## 4. TDD Implementation Plan
|
||||
|
||||
### Phase 1: Authentication Module (Week 1-2)
|
||||
|
||||
#### Test Cases to Implement First:
|
||||
|
||||
**OAuth Session Tests:**
|
||||
```go
|
||||
func TestSessionSave(t *testing.T)
|
||||
func TestSessionLoad(t *testing.T)
|
||||
func TestSessionValidation(t *testing.T)
|
||||
func TestSessionExpiry(t *testing.T)
|
||||
```
|
||||
|
||||
**OAuth Flow Tests:**
|
||||
```go
|
||||
func TestOAuth1Login(t *testing.T)
|
||||
func TestOAuth2TokenRefresh(t *testing.T)
|
||||
func TestMFAHandling(t *testing.T)
|
||||
func TestLoginFailure(t *testing.T)
|
||||
```
|
||||
|
||||
#### Implementation Order:
|
||||
1. **Write failing tests** for session management
|
||||
2. **Implement** basic session struct and methods
|
||||
3. **Write failing tests** for OAuth1 authentication
|
||||
4. **Implement** OAuth1 flow
|
||||
5. **Write failing tests** for OAuth2 token refresh
|
||||
6. **Implement** OAuth2 auto-refresh mechanism
|
||||
7. **Write failing tests** for MFA handling
|
||||
8. **Implement** MFA prompt system
|
||||
|
||||
### Phase 2: HTTP Client Module (Week 3)
|
||||
|
||||
#### Test Cases:
|
||||
```go
|
||||
func TestClientCreation(t *testing.T)
|
||||
func TestAPIRequest(t *testing.T)
|
||||
func TestAuthenticationHeaders(t *testing.T)
|
||||
func TestErrorHandling(t *testing.T)
|
||||
func TestRetryLogic(t *testing.T)
|
||||
```
|
||||
|
||||
#### Mock Server Setup:
|
||||
```go
|
||||
// Create mock Garmin Connect API responses
|
||||
func setupMockGarminServer() *httptest.Server
|
||||
func mockSuccessResponse() string
|
||||
func mockErrorResponse() string
|
||||
```
|
||||
|
||||
### Phase 3: Data Models (Week 4-5)
|
||||
|
||||
#### Core Models Implementation Order:
|
||||
|
||||
**1. User Profile:**
|
||||
```go
|
||||
type UserProfile struct {
|
||||
ID int `json:"id" validate:"required"`
|
||||
ProfileID int `json:"profileId" validate:"required"`
|
||||
DisplayName string `json:"displayName"`
|
||||
FullName string `json:"fullName"`
|
||||
// ... other fields
|
||||
}
|
||||
```
|
||||
|
||||
**2. Sleep Data:**
|
||||
```go
|
||||
type SleepData struct {
|
||||
CalendarDate time.Time `json:"calendarDate"`
|
||||
SleepTimeSeconds int `json:"sleepTimeSeconds"`
|
||||
DeepSleep int `json:"deepSleepSeconds"`
|
||||
LightSleep int `json:"lightSleepSeconds"`
|
||||
// ... other fields
|
||||
}
|
||||
```
|
||||
|
||||
**3. Stress Data:**
|
||||
```go
|
||||
type DailyStress struct {
|
||||
CalendarDate time.Time `json:"calendarDate"`
|
||||
OverallStressLevel int `json:"overallStressLevel"`
|
||||
RestStressDuration int `json:"restStressDuration"`
|
||||
// ... other fields
|
||||
}
|
||||
```
|
||||
|
||||
#### Test Implementation Strategy:
|
||||
1. **JSON Unmarshaling Tests** - Test API response parsing
|
||||
2. **Validation Tests** - Test struct validation
|
||||
3. **Business Logic Tests** - Test derived properties and methods
|
||||
|
||||
### Phase 4: Main Interface (Week 6)
|
||||
|
||||
#### High-level API Tests:
|
||||
```go
|
||||
func TestGarthLogin(t *testing.T)
|
||||
func TestGarthConnectAPI(t *testing.T)
|
||||
func TestGarthSave(t *testing.T)
|
||||
func TestGarthResume(t *testing.T)
|
||||
```
|
||||
|
||||
#### Integration Tests:
|
||||
```go
|
||||
func TestEndToEndSleepDataRetrieval(t *testing.T)
|
||||
func TestEndToEndStressDataRetrieval(t *testing.T)
|
||||
```
|
||||
|
||||
## 5. TDD Development Workflow
|
||||
|
||||
### Red-Green-Refactor Cycle:
|
||||
|
||||
#### For Each Feature:
|
||||
1. **RED**: Write failing test that describes desired behavior
|
||||
2. **GREEN**: Write minimal code to make test pass
|
||||
3. **REFACTOR**: Clean up code while keeping tests green
|
||||
4. **REPEAT**: Add more test cases and iterate
|
||||
|
||||
#### Example TDD Session - Session Management:
|
||||
|
||||
**Step 1 - RED**: Write failing test
|
||||
```go
|
||||
func TestSessionSave(t *testing.T) {
|
||||
session := &Session{
|
||||
OAuth1Token: "token1",
|
||||
OAuth2Token: "token2",
|
||||
}
|
||||
|
||||
err := session.Save("/tmp/test_session")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Should create file
|
||||
_, err = os.Stat("/tmp/test_session")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2 - GREEN**: Make test pass
|
||||
```go
|
||||
type Session struct {
|
||||
OAuth1Token string `json:"oauth1_token"`
|
||||
OAuth2Token string `json:"oauth2_token"`
|
||||
}
|
||||
|
||||
func (s *Session) Save(path string) error {
|
||||
data, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(path, data, 0644)
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3 - REFACTOR**: Improve implementation
|
||||
```go
|
||||
func (s *Session) Save(path string) error {
|
||||
// Add validation
|
||||
if s.OAuth1Token == "" {
|
||||
return errors.New("oauth1 token required")
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(s, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal session: %w", err)
|
||||
}
|
||||
|
||||
return os.WriteFile(path, data, 0600) // More secure permissions
|
||||
}
|
||||
```
|
||||
|
||||
### Testing Strategy:
|
||||
|
||||
#### Unit Tests (80% coverage target):
|
||||
- All public methods tested
|
||||
- Error conditions covered
|
||||
- Edge cases handled
|
||||
|
||||
#### Integration Tests:
|
||||
- Full authentication flow
|
||||
- API request/response cycles
|
||||
- File I/O operations
|
||||
|
||||
#### Mock Usage:
|
||||
```go
|
||||
type MockHTTPClient interface {
|
||||
Do(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
type MockGarminAPI struct {
|
||||
responses map[string]*http.Response
|
||||
}
|
||||
|
||||
func (m *MockGarminAPI) Do(req *http.Request) (*http.Response, error) {
|
||||
response, exists := m.responses[req.URL.Path]
|
||||
if !exists {
|
||||
return nil, errors.New("unexpected request")
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
```
|
||||
|
||||
## 6. Implementation Timeline
|
||||
|
||||
### Week 1: Project Setup + Authentication Tests
|
||||
- [x] Initialize Go module and project structure
|
||||
- [x] Write authentication test cases
|
||||
- [ ] Set up CI/CD pipeline
|
||||
- [x] Implement basic session management
|
||||
|
||||
### Week 2: Complete Authentication Module
|
||||
### Week 1-2: Authentication & CLI Focus
|
||||
- [x] Implement OAuth1 flow
|
||||
- [x] Implement OAuth2 token refresh
|
||||
- [x] Add MFA support (core implementation)
|
||||
- [x] Comprehensive authentication testing
|
||||
- [x] Add MFA support
|
||||
- [x] CLI command structure
|
||||
- [ ] Complete CLI authentication commands
|
||||
|
||||
### Week 3: HTTP Client Module
|
||||
- [ ] Write HTTP client tests
|
||||
- [ ] Implement client with retry logic
|
||||
- [ ] Add request/response logging
|
||||
- [ ] Mock server for testing
|
||||
### Revised Week 3-4:
|
||||
- [ ] HTTP client enhancements
|
||||
- [ ] Health data endpoint implementation
|
||||
- [ ] Comprehensive integration testing
|
||||
|
||||
### Week 4: Data Models - Core Types
|
||||
- [ ] User profile models
|
||||
- [ ] Sleep data models
|
||||
- [ ] JSON marshaling/unmarshaling tests
|
||||
## 5. Quality Gates
|
||||
|
||||
### Week 5: Data Models - Health Metrics
|
||||
- [x] Stress data models (implemented)
|
||||
- [x] Steps, HRV, weight models (implemented)
|
||||
- [x] Validation and business logic
|
||||
### Before CLI Release:
|
||||
- [ ] 100% test coverage for auth flows
|
||||
- [ ] End-to-end CLI test scenarios
|
||||
- [ ] Security audit of session handling
|
||||
|
||||
### Week 6: Main Interface + Integration
|
||||
- [ ] High-level API implementation
|
||||
- [ ] Integration tests
|
||||
- [ ] Documentation and examples
|
||||
- [ ] Performance optimization
|
||||
## 6. Success Metrics
|
||||
|
||||
### Week 7: CLI Tool + Polish
|
||||
- [ ] Command-line interface
|
||||
- [ ] Error handling improvements
|
||||
- [ ] Final testing and bug fixes
|
||||
|
||||
## 7. Quality Gates
|
||||
|
||||
### Before Each Phase Completion:
|
||||
- [ ] All tests passing
|
||||
- [ ] Code coverage > 80%
|
||||
- [ ] Linting passes
|
||||
- [ ] Documentation updated
|
||||
|
||||
### Before Release:
|
||||
- [ ] Integration tests with real Garmin API (optional)
|
||||
- [ ] Performance benchmarks
|
||||
- [ ] Security review
|
||||
- [ ] Cross-platform testing
|
||||
|
||||
## 8. Success Metrics
|
||||
|
||||
### Functional Requirements:
|
||||
- [x] Authentication flow matches Python library
|
||||
- [x] All data models supported
|
||||
- [ ] API requests work identically
|
||||
- [x] Session persistence compatible
|
||||
|
||||
### Quality Requirements:
|
||||
- [ ] >90% test coverage
|
||||
- [ ] Zero critical security issues
|
||||
- [ ] Memory usage < 50MB for typical operations
|
||||
- [ ] API response time < 2s for standard requests
|
||||
|
||||
### Developer Experience:
|
||||
- [ ] Clear documentation with examples
|
||||
- [ ] Easy installation (`go install`)
|
||||
- [ ] Intuitive API design
|
||||
- [ ] Comprehensive error messages
|
||||
|
||||
This TDD approach ensures that the Go port will be robust, well-tested, and maintain feature parity with the original Python library while leveraging Go's strengths in performance and concurrency.
|
||||
### CLI Completion Metrics:
|
||||
- [ ] Login success rate > 99%
|
||||
- [ ] Session restore success > 95%
|
||||
- [ ] Average auth time < 5 seconds
|
||||
|
||||
1
go.mod
1
go.mod
@@ -7,6 +7,7 @@ require (
|
||||
github.com/go-playground/validator/v10 v10.27.0
|
||||
github.com/go-resty/resty/v2 v2.11.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
)
|
||||
|
||||
|
||||
@@ -89,12 +89,7 @@ func TestGetBodyComposition(t *testing.T) {
|
||||
}
|
||||
|
||||
// Create mock authenticator for tests
|
||||
mockAuth := &struct {
|
||||
RefreshToken func(_, _ string) (string, error)
|
||||
}{}
|
||||
mockAuth.RefreshToken = func(_, _ string) (string, error) {
|
||||
return "refreshed-token", nil
|
||||
}
|
||||
mockAuth := NewMockAuthenticator()
|
||||
client, err := NewClient(mockAuth, session, "")
|
||||
assert.NoError(t, err)
|
||||
client.HTTPClient.SetBaseURL(server.URL)
|
||||
|
||||
@@ -14,13 +14,6 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// mockAuthImpl implements the Authenticator interface for tests
|
||||
type mockAuthImpl struct{}
|
||||
|
||||
func (m *mockAuthImpl) RefreshToken(_, _ string) (string, error) {
|
||||
return "refreshed-token", nil
|
||||
}
|
||||
|
||||
func TestGearService(t *testing.T) {
|
||||
// Create test server
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -83,7 +76,7 @@ func TestGearService(t *testing.T) {
|
||||
|
||||
// Create client
|
||||
// Create mock authenticator for tests
|
||||
mockAuth := &mockAuthImpl{}
|
||||
mockAuth := NewMockAuthenticator()
|
||||
client, err := NewClient(mockAuth, session, "")
|
||||
assert.NoError(t, err)
|
||||
client.HTTPClient.SetBaseURL(srv.URL)
|
||||
@@ -103,7 +96,7 @@ func TestGearService(t *testing.T) {
|
||||
|
||||
// Create client
|
||||
// Create mock authenticator for tests
|
||||
mockAuth := &mockAuthImpl{}
|
||||
mockAuth := NewMockAuthenticator()
|
||||
client, err := NewClient(mockAuth, session, "")
|
||||
assert.NoError(t, err)
|
||||
client.HTTPClient.SetBaseURL(srv.URL)
|
||||
@@ -123,7 +116,7 @@ func TestGearService(t *testing.T) {
|
||||
|
||||
// Create client
|
||||
// Create mock authenticator for tests
|
||||
mockAuth := &mockAuthImpl{}
|
||||
mockAuth := NewMockAuthenticator()
|
||||
client, err := NewClient(mockAuth, session, "")
|
||||
assert.NoError(t, err)
|
||||
client.HTTPClient.SetBaseURL(srv.URL)
|
||||
|
||||
@@ -193,8 +193,9 @@ func TestGetSleepData(t *testing.T) {
|
||||
OAuth2Token: "test-token",
|
||||
ExpiresAt: time.Now().Add(8 * time.Hour),
|
||||
}
|
||||
// Pass nil authenticator for tests
|
||||
client, err := NewClient(nil, session, "")
|
||||
// Use mock authenticator
|
||||
mockAuth := NewMockAuthenticator()
|
||||
client, err := NewClient(mockAuth, session, "")
|
||||
assert.NoError(t, err)
|
||||
client.HTTPClient.SetBaseURL(mockServer.URL())
|
||||
|
||||
@@ -291,8 +292,9 @@ func TestGetHRVData(t *testing.T) {
|
||||
OAuth2Token: "test-token",
|
||||
ExpiresAt: time.Now().Add(8 * time.Hour),
|
||||
}
|
||||
// Pass nil authenticator for tests
|
||||
client, err := NewClient(nil, session, "")
|
||||
// Use mock authenticator
|
||||
mockAuth := NewMockAuthenticator()
|
||||
client, err := NewClient(mockAuth, session, "")
|
||||
assert.NoError(t, err)
|
||||
client.HTTPClient.SetBaseURL(mockServer.URL())
|
||||
|
||||
@@ -380,8 +382,9 @@ func TestGetBodyBatteryData(t *testing.T) {
|
||||
OAuth2Token: "test-token",
|
||||
ExpiresAt: time.Now().Add(8 * time.Hour),
|
||||
}
|
||||
// Pass nil authenticator for tests
|
||||
client, err := NewClient(nil, session, "")
|
||||
// Use mock authenticator
|
||||
mockAuth := NewMockAuthenticator()
|
||||
client, err := NewClient(mockAuth, session, "")
|
||||
assert.NoError(t, err)
|
||||
client.HTTPClient.SetBaseURL(mockServer.URL())
|
||||
|
||||
|
||||
@@ -81,8 +81,9 @@ func TestIntegrationHealthMetrics(t *testing.T) {
|
||||
OAuth2Token: "test-token",
|
||||
ExpiresAt: time.Now().Add(8 * time.Hour),
|
||||
}
|
||||
// For integration tests, pass nil for authenticator since we don't need token refresh
|
||||
client, err := NewClient(nil, session, "")
|
||||
// Use mock authenticator for integration tests
|
||||
mockAuth := &MockAuthenticator{}
|
||||
client, err := NewClient(mockAuth, session, "")
|
||||
assert.NoError(t, err)
|
||||
client.HTTPClient.SetBaseURL(mockServer.URL())
|
||||
|
||||
|
||||
@@ -408,13 +408,6 @@ func (m *MockServer) handleGear(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// MockAuthenticator implements garth.Authenticator for testing
|
||||
type MockAuthenticator struct{}
|
||||
|
||||
func (m *MockAuthenticator) RefreshToken(_, _ string) (string, error) {
|
||||
return "refreshed-token", nil
|
||||
}
|
||||
|
||||
// NewClientWithBaseURL creates a test client that uses the mock server's URL
|
||||
func NewClientWithBaseURL(baseURL string) *Client {
|
||||
session := &garth.Session{
|
||||
@@ -423,7 +416,7 @@ func NewClientWithBaseURL(baseURL string) *Client {
|
||||
}
|
||||
|
||||
// Create mock authenticator for tests
|
||||
auth := &MockAuthenticator{}
|
||||
auth := NewMockAuthenticator()
|
||||
|
||||
client, err := NewClient(auth, session, "")
|
||||
if err != nil {
|
||||
|
||||
35
internal/api/test_helpers.go
Normal file
35
internal/api/test_helpers.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package api
|
||||
|
||||
// MockAuthenticator implements the Authenticator interface for testing
|
||||
type MockAuthenticator struct {
|
||||
// RefreshTokenFunc can be set for custom refresh behavior
|
||||
RefreshTokenFunc func(oauth1Token, oauth1Secret string) (string, error)
|
||||
|
||||
// CallCount tracks how many times RefreshToken was called
|
||||
CallCount int
|
||||
}
|
||||
|
||||
// RefreshToken implements the Authenticator interface
|
||||
func (m *MockAuthenticator) RefreshToken(oauth1Token, oauth1Secret string) (string, error) {
|
||||
m.CallCount++
|
||||
|
||||
// If custom function is provided, use it
|
||||
if m.RefreshTokenFunc != nil {
|
||||
return m.RefreshTokenFunc(oauth1Token, oauth1Secret)
|
||||
}
|
||||
|
||||
// Default behavior: return a mock token
|
||||
return "refreshed-test-token", nil
|
||||
}
|
||||
|
||||
// NewMockAuthenticator creates a new mock authenticator with default behavior
|
||||
func NewMockAuthenticator() *MockAuthenticator {
|
||||
return &MockAuthenticator{}
|
||||
}
|
||||
|
||||
// NewMockAuthenticatorWithFunc creates a mock authenticator with custom refresh behavior
|
||||
func NewMockAuthenticatorWithFunc(refreshFunc func(string, string) (string, error)) *MockAuthenticator {
|
||||
return &MockAuthenticator{
|
||||
RefreshTokenFunc: refreshFunc,
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/sstent/go-garminconnect/internal/auth/garth"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -75,7 +76,18 @@ func TestGetUserProfile(t *testing.T) {
|
||||
|
||||
mockServer := NewMockServer()
|
||||
defer mockServer.Close()
|
||||
client := NewClientWithBaseURL(mockServer.URL())
|
||||
// Create client with non-expired session
|
||||
session := &garth.Session{
|
||||
OAuth2Token: "test-token",
|
||||
ExpiresAt: time.Now().Add(8 * time.Hour),
|
||||
}
|
||||
// Use mock authenticator
|
||||
mockAuth := NewMockAuthenticator()
|
||||
client, err := NewClient(mockAuth, session, "")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create client: %v", err)
|
||||
}
|
||||
client.HTTPClient.SetBaseURL(mockServer.URL())
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@@ -178,7 +190,18 @@ func TestGetUserStats(t *testing.T) {
|
||||
|
||||
mockServer := NewMockServer()
|
||||
defer mockServer.Close()
|
||||
client := NewClientWithBaseURL(mockServer.URL())
|
||||
// Create client with non-expired session
|
||||
session := &garth.Session{
|
||||
OAuth2Token: "test-token",
|
||||
ExpiresAt: time.Now().Add(8 * time.Hour),
|
||||
}
|
||||
// Use mock authenticator
|
||||
mockAuth := NewMockAuthenticator()
|
||||
client, err := NewClient(mockAuth, session, "")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create client: %v", err)
|
||||
}
|
||||
client.HTTPClient.SetBaseURL(mockServer.URL())
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
87
tests_TODO.md
Normal file
87
tests_TODO.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# Mock Authenticator Implementation Tasks
|
||||
|
||||
## Overview
|
||||
Implement a shared MockAuthenticator to fix test failures caused by:
|
||||
- Improper interface implementation in tests
|
||||
- Duplicate mock implementations across files
|
||||
- Inconsistent test client creation patterns
|
||||
|
||||
## Tasks
|
||||
|
||||
### Phase 1: Create Shared Test Helper (test_helpers.go)
|
||||
```go
|
||||
package api
|
||||
|
||||
type MockAuthenticator struct {
|
||||
RefreshTokenFunc func(oauth1Token, oauth1Secret string) (string, error)
|
||||
CallCount int
|
||||
}
|
||||
|
||||
func (m *MockAuthenticator) RefreshToken(oauth1Token, oauth1Secret string) (string, error) {
|
||||
m.CallCount++
|
||||
if m.RefreshTokenFunc != nil {
|
||||
return m.RefreshTokenFunc(oauth1Token, oauth1Secret)
|
||||
}
|
||||
return "refreshed-test-token", nil
|
||||
}
|
||||
|
||||
func NewMockAuthenticator() *MockAuthenticator {
|
||||
return &MockAuthenticator{}
|
||||
}
|
||||
|
||||
func NewMockAuthenticatorWithFunc(refreshFunc func(string, string) (string, error)) *MockAuthenticator {
|
||||
return &MockAuthenticator{
|
||||
RefreshTokenFunc: refreshFunc,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 2: Update Test Files
|
||||
1. **bodycomposition_test.go**
|
||||
Replace existing mock with:
|
||||
```go
|
||||
mockAuth := NewMockAuthenticator()
|
||||
```
|
||||
|
||||
2. **gear_test.go**
|
||||
Remove `mockAuthImpl` definition and use:
|
||||
```go
|
||||
mockAuth := NewMockAuthenticator()
|
||||
```
|
||||
|
||||
3. **health_test.go**
|
||||
Update client creation:
|
||||
```go
|
||||
mockAuth := NewMockAuthenticator()
|
||||
client, err := NewClient(mockAuth, session, "")
|
||||
```
|
||||
|
||||
4. **user_test.go**
|
||||
Update client creation:
|
||||
```go
|
||||
mockAuth := NewMockAuthenticator()
|
||||
client, err := NewClient(mockAuth, session, "")
|
||||
```
|
||||
|
||||
5. **mock_server_test.go**
|
||||
Update `NewClientWithBaseURL`:
|
||||
```go
|
||||
auth := NewMockAuthenticator()
|
||||
```
|
||||
|
||||
### Phase 3: Verification
|
||||
- [ ] Run tests: `go test ./internal/api/...`
|
||||
- [ ] Fix any remaining compilation errors
|
||||
- [ ] Verify all tests pass
|
||||
- [ ] Check for consistent mock usage across all test files
|
||||
|
||||
## Progress Tracking
|
||||
- [x] test_helpers.go created
|
||||
- [x] bodycomposition_test.go updated
|
||||
- [x] gear_test.go updated
|
||||
- [x] health_test.go updated
|
||||
- [x] user_test.go updated
|
||||
- [x] mock_server_test.go updated
|
||||
- [x] integration_test.go updated
|
||||
- [x] Run tests: `go test ./internal/api/...`
|
||||
- [x] Verify all tests pass
|
||||
Reference in New Issue
Block a user