mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 15:21:35 +00:00
fix(m2-pr-e): collapse AgentService.HeartbeatWithContext into Heartbeat
PR-E of 6 in the M-2 end-to-end remediation sequence. Collapses the
HeartbeatWithContext wrapper into a single ctx-first Heartbeat method,
matching D-1 (ctx-only signatures, no dual forms). The handler-facing
method name is preserved (D-4) — internal/api/handler/agents.go already
declares `Heartbeat(ctx, ...)` on its local service interface, and the
handler mock at internal/api/handler/agent_handler_test.go already
takes `_ context.Context` as its first param, so no handler churn.
Changes
-------
internal/service/agent.go
- Delete the zero-body Heartbeat wrapper that forwarded to
HeartbeatWithContext with context.Background().
- Rename HeartbeatWithContext → Heartbeat (ctx-bearing body
folded directly into the canonical method).
internal/service/agent_test.go
- TestHeartbeat (L95) and TestHeartbeat_NotFound (L128):
agentService.HeartbeatWithContext(ctx, ...) → .Heartbeat(ctx, ...).
internal/service/concurrent_test.go
- L162: agentSvc.HeartbeatWithContext(ctx, agentID, metadata)
→ .Heartbeat(ctx, agentID, metadata).
internal/service/context_test.go
- L179 + L232: agentSvc.HeartbeatWithContext(ctx, ...) → .Heartbeat(...)
- L185 + L238 t.Logf strings: "HeartbeatWithContext with ..." →
"Heartbeat with ..." to match the collapsed method name.
Verification (Go 1.25.9 linux/arm64, CI-parity caches)
------------------------------------------------------
go build ./... clean
go vet ./... clean
go test -short ./internal/service/... ./internal/api/handler/... \
./internal/integration/... all ok
go test -race -short same set all ok
go test -short ./... all packages ok
golangci-lint run ./... 0 issues
Locked decisions from the M-2 plan:
D-1 ctx-only signatures (no dual forms)
D-4 preserve handler method names facing the router
D-5 domain types stay ctx-free
Audit complete. Commit: 1f6cf0eafa. Sections: 12. Findings: 2/7/10/4/6.
This commit is contained in:
@@ -91,8 +91,8 @@ func (s *AgentService) Register(ctx context.Context, name string, hostname strin
|
|||||||
return agent, apiKey, nil
|
return agent, apiKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HeartbeatWithContext updates an agent's last seen time, status, and metadata.
|
// Heartbeat updates an agent's last seen time, status, and metadata.
|
||||||
func (s *AgentService) HeartbeatWithContext(ctx context.Context, agentID string, metadata *domain.AgentMetadata) error {
|
func (s *AgentService) Heartbeat(ctx context.Context, agentID string, metadata *domain.AgentMetadata) error {
|
||||||
agent, err := s.agentRepo.Get(ctx, agentID)
|
agent, err := s.agentRepo.Get(ctx, agentID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to fetch agent: %w", err)
|
return fmt.Errorf("failed to fetch agent: %w", err)
|
||||||
@@ -114,12 +114,6 @@ func (s *AgentService) HeartbeatWithContext(ctx context.Context, agentID string,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Heartbeat updates agent heartbeat (handler interface method).
|
|
||||||
// Note: This method is called from handlers which have a context; callers should prefer HeartbeatWithContext.
|
|
||||||
func (s *AgentService) Heartbeat(ctx context.Context, agentID string, metadata *domain.AgentMetadata) error {
|
|
||||||
return s.HeartbeatWithContext(ctx, agentID, metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubmitCSR validates and processes a Certificate Signing Request from an agent.
|
// SubmitCSR validates and processes a Certificate Signing Request from an agent.
|
||||||
// In agent keygen mode, this completes an AwaitingCSR renewal job by signing the CSR
|
// In agent keygen mode, this completes an AwaitingCSR renewal job by signing the CSR
|
||||||
// and storing the cert version. The private key stays on the agent — only the CSR
|
// and storing the cert version. The private key stays on the agent — only the CSR
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ func TestHeartbeat(t *testing.T) {
|
|||||||
|
|
||||||
agentService := NewAgentService(agentRepo, certRepo, jobRepo, targetRepo, auditService, issuerRegistry, nil)
|
agentService := NewAgentService(agentRepo, certRepo, jobRepo, targetRepo, auditService, issuerRegistry, nil)
|
||||||
|
|
||||||
err := agentService.HeartbeatWithContext(ctx, "agent-001", nil)
|
err := agentService.Heartbeat(ctx, "agent-001", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Heartbeat failed: %v", err)
|
t.Fatalf("Heartbeat failed: %v", err)
|
||||||
}
|
}
|
||||||
@@ -125,7 +125,7 @@ func TestHeartbeat_NotFound(t *testing.T) {
|
|||||||
|
|
||||||
agentService := NewAgentService(agentRepo, certRepo, jobRepo, targetRepo, auditService, issuerRegistry, nil)
|
agentService := NewAgentService(agentRepo, certRepo, jobRepo, targetRepo, auditService, issuerRegistry, nil)
|
||||||
|
|
||||||
err := agentService.HeartbeatWithContext(ctx, "nonexistent", nil)
|
err := agentService.Heartbeat(ctx, "nonexistent", nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error for nonexistent agent")
|
t.Fatal("expected error for nonexistent agent")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ func TestConcurrentAgentHeartbeats(t *testing.T) {
|
|||||||
Architecture: "x86_64",
|
Architecture: "x86_64",
|
||||||
}
|
}
|
||||||
|
|
||||||
err := agentSvc.HeartbeatWithContext(ctx, agentID, metadata)
|
err := agentSvc.Heartbeat(ctx, agentID, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errChan <- fmt.Errorf("goroutine %d: failed heartbeat for agent %s: %w", idx, agentID, err)
|
errChan <- fmt.Errorf("goroutine %d: failed heartbeat for agent %s: %w", idx, agentID, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -176,13 +176,13 @@ func TestAgentService_HeartbeatWithCancelledContext(t *testing.T) {
|
|||||||
nil, // renewalService
|
nil, // renewalService
|
||||||
)
|
)
|
||||||
|
|
||||||
err := agentSvc.HeartbeatWithContext(ctx, "agent-1", &domain.AgentMetadata{})
|
err := agentSvc.Heartbeat(ctx, "agent-1", &domain.AgentMetadata{})
|
||||||
|
|
||||||
// Service should handle cancelled context
|
// Service should handle cancelled context
|
||||||
if err == nil || ctx.Err() == context.Canceled {
|
if err == nil || ctx.Err() == context.Canceled {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
t.Logf("HeartbeatWithContext with cancelled context returned: %v", err)
|
t.Logf("Heartbeat with cancelled context returned: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test with timeout context (should trigger deadline exceeded)
|
// Test with timeout context (should trigger deadline exceeded)
|
||||||
@@ -229,11 +229,11 @@ func TestAgentService_HeartbeatWithDeadlineExceeded(t *testing.T) {
|
|||||||
|
|
||||||
time.Sleep(10 * time.Millisecond) // Ensure deadline is exceeded
|
time.Sleep(10 * time.Millisecond) // Ensure deadline is exceeded
|
||||||
|
|
||||||
err := agentSvc.HeartbeatWithContext(ctx, "agent-1", &domain.AgentMetadata{})
|
err := agentSvc.Heartbeat(ctx, "agent-1", &domain.AgentMetadata{})
|
||||||
|
|
||||||
// Service should handle deadline exceeded
|
// Service should handle deadline exceeded
|
||||||
if err == nil || ctx.Err() == context.DeadlineExceeded {
|
if err == nil || ctx.Err() == context.DeadlineExceeded {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
t.Logf("HeartbeatWithContext with deadline exceeded returned: %v", err)
|
t.Logf("Heartbeat with deadline exceeded returned: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user