92 lines
3.4 KiB
Go
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)
|
|
}
|
|
}
|