package bridge import ( "encoding/json" "net/http" "net/http/httptest" "testing" "time" "git.saqut.com/saqut/mwse/internal/notify" ) // ---- Inbox --------------------------------------------------------------- func TestInboxPushDrain(t *testing.T) { inbox := NewInbox(0) inbox.Push("a", "hello") inbox.Push("b", 42) msgs := inbox.Drain() if len(msgs) != 2 { t.Fatalf("drain = %d, want 2", len(msgs)) } if msgs[0].From != "a" || msgs[1].From != "b" { t.Fatalf("from order wrong: %v", msgs) } // Drain again should return nil (empty). if got := inbox.Drain(); got != nil { t.Fatalf("second drain = %v, want nil", got) } } func TestInboxCapDropsOldest(t *testing.T) { inbox := NewInbox(3) for i := range 5 { inbox.Push("x", i) } if inbox.Len() != 3 { t.Fatalf("len = %d, want 3 (capped)", inbox.Len()) } msgs := inbox.Drain() // Should contain the last 3 items: 2, 3, 4. if msgs[0].Pack.(int) != 2 { t.Fatalf("oldest surviving item = %v, want 2", msgs[0].Pack) } } func TestInboxLen(t *testing.T) { inbox := NewInbox(0) if inbox.Len() != 0 { t.Fatalf("initial len = %d, want 0", inbox.Len()) } inbox.Push("a", nil) if inbox.Len() != 1 { t.Fatalf("after push len = %d, want 1", inbox.Len()) } } // ---- HTTPApprover -------------------------------------------------------- func TestHTTPApproverApproves(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var body map[string]any json.NewDecoder(r.Body).Decode(&body) if body["id"] == nil { http.Error(w, "missing id", 400) return } json.NewEncoder(w).Encode(map[string]any{"approve": true}) })) defer srv.Close() a := NewHTTPApprover(srv.URL, 2*time.Second) if !a.Approve("client-1", map[string]any{"ip": "1.2.3.4"}) { t.Fatal("expected approve=true") } } func TestHTTPApproverRejects(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(map[string]any{"approve": false}) })) defer srv.Close() a := NewHTTPApprover(srv.URL, 2*time.Second) if a.Approve("client-2", nil) { t.Fatal("expected approve=false") } } func TestHTTPApproverFailClosed(t *testing.T) { // Unreachable URL must deny (fail-closed). a := NewHTTPApprover("http://127.0.0.1:1", 100*time.Millisecond) if a.Approve("x", nil) { t.Fatal("expected fail-closed denial for unreachable server") } } func TestHTTPApproverNon200Denies(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Error(w, "forbidden", http.StatusForbidden) })) defer srv.Close() a := NewHTTPApprover(srv.URL, 2*time.Second) if a.Approve("c", nil) { t.Fatal("expected denial on non-200") } } // ---- HTTPTrigger --------------------------------------------------------- func TestHTTPTriggerPosts(t *testing.T) { received := make(chan map[string]any, 1) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var body map[string]any json.NewDecoder(r.Body).Decode(&body) received <- body })) defer srv.Close() trigger := NewHTTPTrigger(srv.URL, 2*time.Second) n := notify.Notification{ Trace: "tr1", From: "alice", To: "bob", Reply: map[string]any{"ok": true}, } trigger.NotifyReplied(n) select { case body := <-received: if body["trace"] != "tr1" || body["from"] != "alice" { t.Fatalf("received body = %v", body) } case <-time.After(2 * time.Second): t.Fatal("trigger post not received within 2s") } } func TestHTTPTriggerBestEffortOnError(t *testing.T) { // An unreachable trigger must not panic or block. trigger := NewHTTPTrigger("http://127.0.0.1:1", 100*time.Millisecond) done := make(chan struct{}) go func() { trigger.NotifyReplied(notify.Notification{Trace: "x"}) close(done) }() select { case <-done: case <-time.After(2 * time.Second): t.Fatal("trigger blocked on error instead of returning quickly") } }