Bind strict Dispatcher Mock environment to current SaaS read client
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.ipao.vip/rogee/go-sip/internal/config"
|
||||
"git.ipao.vip/rogee/go-sip/internal/configread"
|
||||
)
|
||||
|
||||
// dispatcherConfigurationClient binds the deployment-owned Dispatcher identity
|
||||
// to the sole read-only SaaS configuration client. It opens no external resource.
|
||||
func dispatcherConfigurationClient(mode string, httpClient *http.Client) (config.DispatcherEnvironment, *configread.Client, error) {
|
||||
settings, err := config.LoadDispatcherEnvironment(mode)
|
||||
if err != nil {
|
||||
return config.DispatcherEnvironment{}, nil, err
|
||||
}
|
||||
reader, err := configread.NewClient(settings.SaaSBaseURL, settings.DispatcherID, settings.SecretKey, httpClient)
|
||||
if err != nil {
|
||||
return config.DispatcherEnvironment{}, nil, err
|
||||
}
|
||||
return settings, reader, nil
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDispatcherConfigurationClientReadsApprovedSIPWithExplicitIdentity(t *testing.T) {
|
||||
const dispatcherID = "c046b893-8628-4589-ae50-619d049248a6"
|
||||
const secret = "synthetic-placeholder-not-a-credential"
|
||||
fixture, err := os.ReadFile(filepath.Join("..", "..", "contracts", "local", "examples", "config-read-sip.json"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var requests atomic.Int32
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
requests.Add(1)
|
||||
if r.Method != http.MethodGet || r.URL.Path != "/internal/v1/dispatcher/sip" || r.Header.Get("X-DISPATCHER-id") != dispatcherID || r.Header.Get("X-DISPATCHER-SECRET-KEY") != secret {
|
||||
t.Errorf("unexpected SaaS read method, path or Dispatcher headers: %s %s", r.Method, r.URL.Path)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write(fixture)
|
||||
}))
|
||||
defer server.Close()
|
||||
path := filepath.Join(t.TempDir(), "dispatcher.sqlite")
|
||||
t.Setenv("DISPATCHER_ID", dispatcherID)
|
||||
t.Setenv("DISPATCHER_SECRET_KEY", secret)
|
||||
t.Setenv("SAAS_BASE_URL", server.URL)
|
||||
t.Setenv("RABBITMQ_URL", "amqp://127.0.0.1:5672/%2Fmock")
|
||||
t.Setenv("DISPATCHER_SQLITE_PATH", path)
|
||||
|
||||
settings, reader, err := dispatcherConfigurationClient("mock", server.Client())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if settings.DispatcherID != dispatcherID || settings.SQLitePath != path {
|
||||
t.Fatal("dispatcher startup changed approved deployment identity or SQLite path")
|
||||
}
|
||||
sip, err := reader.ReadCurrentSIP(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if sip.DispatcherID != dispatcherID || sip.Revision != 8 || requests.Load() != 1 {
|
||||
t.Fatalf("wrong approved SIP snapshot or HTTP request count: revision=%d requests=%d", sip.Revision, requests.Load())
|
||||
}
|
||||
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
||||
t.Fatalf("HTTP client assembly opened SQLite: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDispatcherConfigurationClientNeverUsesLegacyHTTPSetting(t *testing.T) {
|
||||
t.Setenv("DISPATCHER_ID", "c046b893-8628-4589-ae50-619d049248a6")
|
||||
t.Setenv("DISPATCHER_SECRET_KEY", "synthetic-placeholder-not-a-credential")
|
||||
t.Setenv("SAAS_BASE_URL", "")
|
||||
t.Setenv("DISPATCHER_CONFIG_READ_BASE_URL", "http://127.0.0.1:8080")
|
||||
t.Setenv("RABBITMQ_URL", "amqp://127.0.0.1:5672/%2Fmock")
|
||||
t.Setenv("DISPATCHER_SQLITE_PATH", filepath.Join(t.TempDir(), "dispatcher.sqlite"))
|
||||
if _, _, err := dispatcherConfigurationClient("mock", nil); err == nil || !strings.Contains(err.Error(), "SAAS_BASE_URL") {
|
||||
t.Fatalf("legacy HTTP source silently became a fallback: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@
|
||||
## P03:HTTP 读取分批改造(未整体签收)
|
||||
|
||||
- `contract.ValidateCurrent` 与 `configread` 按当前 Schema 读取 SIP、provider、task、quota 和 cursor 任务发现;严格检查数字 tenant_id、本 D 归属及不可变配置。provider 凭据原值只保留在内存快照,不写日志;Agent 参数中的显式 0/false 保真;无旧 Schema/旧配置回退。
|
||||
- CLI 组装辅助 `dispatcherConfigurationClient` 将严格 Mock 环境预检与当前 HTTP Client 绑定;本机 HTTP 隔离测试实际读取 SIP,核对固定 `/internal/v1/dispatcher/sip`、D 身份/密钥 Header、revision 与不打开 SQLite;旧 `DISPATCHER_CONFIG_READ_BASE_URL` 不可充当缺失的当前地址。`go test ./cmd/sip-go-agent -run '^TestDispatcherConfigurationClient' -count=1` 通过;主 `dispatcher` 命令尚未调用该辅助,不能声称启动已切换。
|
||||
- `store.OpenCurrent` 新建数字租户 SQLite 状态;旧表、旧版当前布局、残缺布局均在写入前拒绝并保留原记录;不执行旧数据迁移或自动清理。启动时完整发现同一快照一次提交,分页增量逐页持久提交后才推进**内存** cursor;失败关闭准入,重启重新取完整快照。HTTP 的旧 running 不能解除 MQ 暂停/终止,同 revision 异内容及跨任务 SIP/租户额度冲突拒绝。
|
||||
- `CurrentBootstrap` 先关闭准入,核验 SIP 全量与 Agent/Asterisk 已加载 revision、读取任务和 provider/额度,再排空 MQ 控制积压,最后依据已验证 SIP revision 开准入;有更新的持久 SIP 通知时保持关闭但控制与结果处理仍可继续。`CurrentDiscoveryFollower` 逐页绑定任务快照;HTTP 错误、失效或授权不一致只失败,不回退旧读取。**目前只在隔离运行组件中调用,尚未接入 `cmd/sip-go-agent/main.go`;provider 向真实 Agent 交付及真实加载尚待 P05/P07。**
|
||||
- TDD 与回归:`go test ./internal/configread ./internal/tenant ./internal/store ./internal/dispatcher -count=1`、`bash scripts/check-current-contracts.sh`、已提交 `a0118e3` 的干净归档测试通过;旧布局行/表原样保留由 `TestCurrentStoreRefusesPreviousCurrentLayoutBeforeModifyingDatabase` 覆盖。
|
||||
|
||||
Reference in New Issue
Block a user