MWSE/internal/bridge/bridge_test.go

156 lines
3.9 KiB
Go

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")
}
}