feat: support updating Garmin credentials at runtime
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -24,6 +24,12 @@ type Client interface {
|
|||||||
Authenticate(ctx context.Context) (AuthResult, error)
|
Authenticate(ctx context.Context) (AuthResult, error)
|
||||||
// CompleteMFA submits an MFA code for a login started by Authenticate.
|
// CompleteMFA submits an MFA code for a login started by Authenticate.
|
||||||
CompleteMFA(ctx context.Context, code string) (AuthResult, error)
|
CompleteMFA(ctx context.Context, code string) (AuthResult, error)
|
||||||
|
// UpdateCredentials replaces the Garmin email/password used to spawn
|
||||||
|
// the subprocess, and terminates any already-running subprocess (which
|
||||||
|
// would otherwise still be authenticated under the old credentials).
|
||||||
|
// The next call that needs the subprocess spawns a fresh one with the
|
||||||
|
// new credentials.
|
||||||
|
UpdateCredentials(email, password string)
|
||||||
// GetActivities lists activities between start and end (YYYY-MM-DD).
|
// GetActivities lists activities between start and end (YYYY-MM-DD).
|
||||||
GetActivities(ctx context.Context, startDate, endDate string, limit int) ([]Activity, error)
|
GetActivities(ctx context.Context, startDate, endDate string, limit int) ([]Activity, error)
|
||||||
// GetActivitySplits fetches lap/split summaries for one activity.
|
// GetActivitySplits fetches lap/split summaries for one activity.
|
||||||
@@ -97,6 +103,23 @@ func (c *mcpClient) ensureStarted(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateCredentials implements Client.
|
||||||
|
func (c *mcpClient) UpdateCredentials(email, password string) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
c.cfg.GarminEmail = email
|
||||||
|
c.cfg.GarminPassword = password
|
||||||
|
|
||||||
|
if c.started {
|
||||||
|
if c.inner != nil {
|
||||||
|
c.inner.Close()
|
||||||
|
}
|
||||||
|
c.inner = nil
|
||||||
|
c.started = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// drainStderr forwards the subprocess's debug/log output so it isn't
|
// drainStderr forwards the subprocess's debug/log output so it isn't
|
||||||
// silently dropped (mcp-garmin logs auth/rate-limit diagnostics there).
|
// silently dropped (mcp-garmin logs auth/rate-limit diagnostics there).
|
||||||
func drainStderr(stdio *transport.Stdio) {
|
func drainStderr(stdio *transport.Stdio) {
|
||||||
|
|||||||
@@ -20,3 +20,20 @@ func TestParseAuthResult(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMcpClient_UpdateCredentials_ResetsStartedState(t *testing.T) {
|
||||||
|
c := &mcpClient{cfg: Config{GarminEmail: "old@example.com", GarminPassword: "old"}}
|
||||||
|
c.started = true // simulate an already-spawned subprocess
|
||||||
|
|
||||||
|
c.UpdateCredentials("new@example.com", "new")
|
||||||
|
|
||||||
|
if c.cfg.GarminEmail != "new@example.com" || c.cfg.GarminPassword != "new" {
|
||||||
|
t.Errorf("cfg after update = %+v, want new@example.com/new", c.cfg)
|
||||||
|
}
|
||||||
|
if c.started {
|
||||||
|
t.Error("started should be reset to false so the next call respawns the subprocess")
|
||||||
|
}
|
||||||
|
if c.inner != nil {
|
||||||
|
t.Error("inner should be cleared so ensureStarted spawns a fresh client")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ type Client struct {
|
|||||||
authResultCursor int
|
authResultCursor int
|
||||||
ClosedCalled bool
|
ClosedCalled bool
|
||||||
GetActivitiesCalls int
|
GetActivitiesCalls int
|
||||||
|
LastEmail string
|
||||||
|
LastPassword string
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ garmin.Client = (*Client)(nil)
|
var _ garmin.Client = (*Client)(nil)
|
||||||
@@ -70,6 +72,11 @@ func (c *Client) GetActivityDetails(ctx context.Context, activityID int64) (garm
|
|||||||
return c.Details[activityID], nil
|
return c.Details[activityID], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) UpdateCredentials(email, password string) {
|
||||||
|
c.LastEmail = email
|
||||||
|
c.LastPassword = password
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) Close() error {
|
func (c *Client) Close() error {
|
||||||
c.ClosedCalled = true
|
c.ClosedCalled = true
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
Reference in New Issue
Block a user