Fuehre HTTP-Status-Reporter fuer den Agenten ein
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
parent
3fd6ed7432
commit
0c7f0b5b13
2 changed files with 185 additions and 0 deletions
101
player/agent/internal/statusreporter/reporter.go
Normal file
101
player/agent/internal/statusreporter/reporter.go
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
package statusreporter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Snapshot struct {
|
||||
Status string
|
||||
ScreenID string
|
||||
ServerBaseURL string
|
||||
MQTTBroker string
|
||||
HeartbeatEverySeconds int
|
||||
StartedAt time.Time
|
||||
LastHeartbeatAt time.Time
|
||||
}
|
||||
|
||||
type statusPayload struct {
|
||||
ScreenID string `json:"screen_id"`
|
||||
Timestamp string `json:"ts"`
|
||||
Status string `json:"status"`
|
||||
ServerURL string `json:"server_url"`
|
||||
MQTTBroker string `json:"mqtt_broker"`
|
||||
HeartbeatEverySeconds int `json:"heartbeat_every_seconds"`
|
||||
StartedAt string `json:"started_at,omitempty"`
|
||||
LastHeartbeatAt string `json:"last_heartbeat_at,omitempty"`
|
||||
}
|
||||
|
||||
type Reporter struct {
|
||||
baseURL string
|
||||
client *http.Client
|
||||
now func() time.Time
|
||||
}
|
||||
|
||||
func New(baseURL string, client *http.Client, now func() time.Time) *Reporter {
|
||||
if client == nil {
|
||||
client = &http.Client{Timeout: 5 * time.Second}
|
||||
}
|
||||
|
||||
if now == nil {
|
||||
now = time.Now
|
||||
}
|
||||
|
||||
return &Reporter{
|
||||
baseURL: strings.TrimRight(baseURL, "/"),
|
||||
client: client,
|
||||
now: now,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Reporter) Send(ctx context.Context, snapshot Snapshot) error {
|
||||
payload := buildPayload(snapshot, r.now())
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal status payload: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, r.baseURL+"/api/v1/player/status", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return fmt.Errorf("build status request: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := r.client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("send status request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("unexpected status response: %s", resp.Status)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildPayload(snapshot Snapshot, now time.Time) statusPayload {
|
||||
payload := statusPayload{
|
||||
ScreenID: snapshot.ScreenID,
|
||||
Timestamp: now.Format(time.RFC3339),
|
||||
Status: snapshot.Status,
|
||||
ServerURL: snapshot.ServerBaseURL,
|
||||
MQTTBroker: snapshot.MQTTBroker,
|
||||
HeartbeatEverySeconds: snapshot.HeartbeatEverySeconds,
|
||||
}
|
||||
|
||||
if !snapshot.StartedAt.IsZero() {
|
||||
payload.StartedAt = snapshot.StartedAt.Format(time.RFC3339)
|
||||
}
|
||||
|
||||
if !snapshot.LastHeartbeatAt.IsZero() {
|
||||
payload.LastHeartbeatAt = snapshot.LastHeartbeatAt.Format(time.RFC3339)
|
||||
}
|
||||
|
||||
return payload
|
||||
}
|
||||
84
player/agent/internal/statusreporter/reporter_test.go
Normal file
84
player/agent/internal/statusreporter/reporter_test.go
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
package statusreporter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestBuildPayloadFromSnapshot(t *testing.T) {
|
||||
startedAt := time.Date(2026, 3, 22, 15, 59, 30, 0, time.UTC)
|
||||
lastHeartbeatAt := time.Date(2026, 3, 22, 16, 0, 0, 0, time.UTC)
|
||||
snapshot := Snapshot{
|
||||
Status: "running",
|
||||
ScreenID: "info01-dev",
|
||||
ServerBaseURL: "http://127.0.0.1:8080",
|
||||
MQTTBroker: "tcp://127.0.0.1:1883",
|
||||
HeartbeatEverySeconds: 30,
|
||||
StartedAt: startedAt,
|
||||
LastHeartbeatAt: lastHeartbeatAt,
|
||||
}
|
||||
|
||||
payload := buildPayload(snapshot, lastHeartbeatAt)
|
||||
|
||||
if got, want := payload.ScreenID, "info01-dev"; got != want {
|
||||
t.Fatalf("ScreenID = %q, want %q", got, want)
|
||||
}
|
||||
|
||||
if got, want := payload.Timestamp, lastHeartbeatAt.Format(time.RFC3339); got != want {
|
||||
t.Fatalf("Timestamp = %q, want %q", got, want)
|
||||
}
|
||||
|
||||
if got, want := payload.StartedAt, startedAt.Format(time.RFC3339); got != want {
|
||||
t.Fatalf("StartedAt = %q, want %q", got, want)
|
||||
}
|
||||
|
||||
if got, want := payload.LastHeartbeatAt, lastHeartbeatAt.Format(time.RFC3339); got != want {
|
||||
t.Fatalf("LastHeartbeatAt = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReporterSendStatus(t *testing.T) {
|
||||
var received statusPayload
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if got, want := r.Method, http.MethodPost; got != want {
|
||||
t.Fatalf("method = %s, want %s", got, want)
|
||||
}
|
||||
|
||||
if got, want := r.URL.Path, "/api/v1/player/status"; got != want {
|
||||
t.Fatalf("path = %s, want %s", got, want)
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&received); err != nil {
|
||||
t.Fatalf("Decode() error = %v", err)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"status":"accepted"}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
reporter := New(server.URL, server.Client(), func() time.Time {
|
||||
return time.Date(2026, 3, 22, 16, 0, 0, 0, time.UTC)
|
||||
})
|
||||
|
||||
err := reporter.Send(context.Background(), Snapshot{
|
||||
Status: "running",
|
||||
ScreenID: "info01-dev",
|
||||
ServerBaseURL: "http://127.0.0.1:8080",
|
||||
MQTTBroker: "tcp://127.0.0.1:1883",
|
||||
HeartbeatEverySeconds: 30,
|
||||
StartedAt: time.Date(2026, 3, 22, 15, 59, 30, 0, time.UTC),
|
||||
LastHeartbeatAt: time.Date(2026, 3, 22, 15, 59, 55, 0, time.UTC),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Send() error = %v", err)
|
||||
}
|
||||
|
||||
if got, want := received.ScreenID, "info01-dev"; got != want {
|
||||
t.Fatalf("received.ScreenID = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue