Public source validation / validate (push) Failing after 3m8s
106 lines
4.3 KiB
Go
106 lines
4.3 KiB
Go
package unraid
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/itworx/pulse/internal/container"
|
|
)
|
|
|
|
func TestContainerSourceAcceptsBoundedOperationalHeadroomAndRejectsOverflow(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name string
|
|
count int
|
|
wantErr bool
|
|
}{
|
|
{name: "bounded headroom", count: container.DefaultMaxContainers},
|
|
{name: "overflow", count: container.DefaultMaxContainers + 1, wantErr: true},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
items := make([]map[string]any, 0, test.count)
|
|
for index := 0; index < test.count; index++ {
|
|
items = append(items, map[string]any{"id": fmt.Sprintf("id-%03d", index), "names": []string{fmt.Sprintf("/container-%03d", index)}, "state": "RUNNING", "status": "Up 1 hour", "autoStart": true})
|
|
}
|
|
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
_ = json.NewEncoder(w).Encode(map[string]any{"data": map[string]any{"docker": map[string]any{"containers": items}}})
|
|
}))
|
|
defer server.Close()
|
|
client, err := New(server.URL, "test-key", server.Client())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
snapshot, err := (ContainerSource{Client: client}).Snapshot(context.Background())
|
|
if test.wantErr {
|
|
if err == nil {
|
|
t.Fatal("expected oversized response rejection")
|
|
}
|
|
return
|
|
}
|
|
if err != nil || len(snapshot.Containers) != test.count {
|
|
t.Fatalf("containers=%d err=%v", len(snapshot.Containers), err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestContainerSourceNormalizesDocumentedReadOnlyContainerFields(t *testing.T) {
|
|
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
_, _ = w.Write([]byte(`{"data":{"docker":{"containers":[{"id":"two","names":["/zeta"],"state":"EXITED","status":"Exited (0) 2 days ago","autoStart":false},{"id":"one","names":"/alpha","state":"RUNNING","status":"Up 34 hours (healthy)","autoStart":true},{"id":"three","names":"/beta","state":"RUNNING","status":"Up 34 hours","autoStart":true},{"id":"four","names":"/gamma","state":"EXITED","status":"Restarting (137) 11 seconds ago","autoStart":false}]}}}`))
|
|
}))
|
|
defer server.Close()
|
|
client, err := New(server.URL, "test-key", server.Client())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
now := time.Date(2026, 8, 10, 2, 0, 0, 0, time.UTC)
|
|
snapshot, err := (ContainerSource{Client: client, Now: func() time.Time { return now }}).Snapshot(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(snapshot.Containers) != 4 || snapshot.Containers[0].Name != "zeta" || !snapshot.Containers[0].IntentionalStop {
|
|
t.Fatalf("unexpected normalized snapshot: %+v", snapshot)
|
|
}
|
|
if snapshot.Containers[0].State != "exited" || snapshot.Containers[0].Health != "unknown" || snapshot.Containers[1].Health != "healthy" || snapshot.Containers[2].Health != "unknown" || snapshot.Containers[3].State != "restarting" || snapshot.Containers[3].IntentionalStop {
|
|
t.Fatalf("runtime status was mistaken for health: %+v", snapshot.Containers)
|
|
}
|
|
if !snapshot.ObservedAt.Equal(now) || snapshot.Source.Type != "unraid" {
|
|
t.Fatalf("unexpected source timestamps: %+v", snapshot)
|
|
}
|
|
}
|
|
|
|
func TestContainerSourceRejectsOversizedOrInvalidPayload(t *testing.T) {
|
|
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
_, _ = w.Write([]byte(`{"data":{"docker":{"containers":[{"id":"","names":[],"state":"running","status":"healthy","autoStart":true}]}}}`))
|
|
}))
|
|
defer server.Close()
|
|
client, err := New(server.URL, "test-key", server.Client())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := (ContainerSource{Client: client}).Snapshot(context.Background()); err == nil {
|
|
t.Fatal("expected invalid identity rejection")
|
|
}
|
|
}
|
|
|
|
func TestContainerStatusClassification(t *testing.T) {
|
|
healthCases := map[string]string{
|
|
"healthy": "healthy", "Up 34 hours (healthy)": "healthy",
|
|
"unhealthy": "unhealthy", "Up 2 minutes (unhealthy)": "unhealthy",
|
|
"starting": "starting", "Up 3 seconds (health: starting)": "starting",
|
|
"Up 34 hours": "unknown", "Exited (0) 2 days ago": "unknown",
|
|
}
|
|
for input, want := range healthCases {
|
|
if got := containerHealth(input); got != want {
|
|
t.Fatalf("health %q = %q, want %q", input, got, want)
|
|
}
|
|
}
|
|
if got := containerRuntimeState("EXITED", "Restarting (137) 11 seconds ago"); got != "restarting" {
|
|
t.Fatalf("runtime = %q, want restarting", got)
|
|
}
|
|
}
|