Files
go-sip/internal/dispatcher/current_sip_runtime_test.go
T

92 lines
3.4 KiB
Go

package dispatcher
import (
"context"
"encoding/json"
"errors"
"log/slog"
"net/http"
"net/http/httptest"
"strings"
"sync/atomic"
"testing"
"git.ipao.vip/rogee/go-sip/internal/configread"
)
func TestCurrentSIPNotificationPersistsBarrierAndWaitsForLoadedFullSnapshot(t *testing.T) {
executor, _, _, s := newCurrentExecuteFixture(t)
approved := currentPolicySnapshot(t).SIP
changed := approved
changed.Revision = 9
newSIP, err := json.Marshal(changed)
if err != nil {
t.Fatal(err)
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var body []byte
switch r.URL.Path {
case "/internal/v1/dispatcher/sip":
body = newSIP
case "/internal/v1/dispatcher/tasks":
if r.URL.Query().Get("after") == "" {
body = currentConfigExample(t, "task-discovery-page")
} else {
body = currentConfigExample(t, "task-discovery-end")
}
case "/internal/v1/dispatcher/ai-providers":
body = currentConfigExample(t, "config-read-providers")
case "/internal/v1/dispatcher/task/task-asr":
body = currentConfigExample(t, "config-read-task-asr")
case "/internal/v1/dispatcher/tenant/1001/quota":
body = currentConfigExample(t, "config-read-quota")
default:
t.Errorf("unexpected HTTP path %s", r.URL.Path)
w.WriteHeader(http.StatusNotFound)
return
}
_, _ = w.Write(body)
}))
defer server.Close()
client, err := configread.NewClient(server.URL, executor.DispatcherID, "test-secret", server.Client())
if err != nil {
t.Fatal(err)
}
var loaded atomic.Bool
verify := func(_ context.Context, sip configread.CurrentSIP) error {
if sip.Revision != 9 || !loaded.Load() {
return errors.New("Agent and Asterisk have not applied SIP revision 9")
}
return nil
}
runtime := &CurrentRuntime{Bootstrap: CurrentBootstrap{DispatcherID: executor.DispatcherID, Client: client, Store: s, VerifySIP: verify}, Logger: slog.Default()}
follower := &CurrentDiscoveryFollower{DispatcherID: executor.DispatcherID, Client: client, Store: s, ApprovedSIP: approved, VerifySIP: verify, Cursor: "opaque-end-token"}
body := []byte(strings.Replace(string(currentConfigExample(t, "mq-sip-change")), `"revision":8`, `"revision":9`, 1))
if err := runtime.handleControl(context.Background(), "", body); err != nil {
t.Fatal(err)
}
if admitted, err := s.CanAdmit(executor.DispatcherID, 1001, "task-asr"); err != nil || admitted {
t.Fatalf("SIP notification did not close admission: %v %v", admitted, err)
}
if applied, pending, err := s.SIPState(executor.DispatcherID); err != nil || applied != 8 || pending != 9 {
t.Fatalf("notification not durable before ACK: %d %d %v", applied, pending, err)
}
if err := runtime.refreshSIP(context.Background(), follower); err != nil {
t.Fatal(err)
}
if admitted, err := s.CanAdmit(executor.DispatcherID, 1001, "task-asr"); err != nil || admitted {
t.Fatalf("unloaded SIP reopened admission: %v %v", admitted, err)
}
loaded.Store(true)
if err := runtime.refreshSIP(context.Background(), follower); err != nil {
t.Fatal(err)
}
if admitted, err := s.CanAdmit(executor.DispatcherID, 1001, "task-asr"); err != nil || !admitted {
t.Fatalf("verified SIP did not reopen: %v %v", admitted, err)
}
if follower.ApprovedSIP.Revision != 9 || follower.Cursor != "opaque-end-token" {
t.Fatalf("reloaded SIP/cursor not bound: rev=%d cursor=%q", follower.ApprovedSIP.Revision, follower.Cursor)
}
}