Align task schedule optional fields with shared schema

This commit is contained in:
2026-10-09 11:38:40 +08:00
parent ef1c625119
commit 4872deb08e
7 changed files with 304 additions and 59 deletions
@@ -210,6 +210,9 @@ func TestDispatcherCommandStartsWithIsolatedMQHTTPAndAgent(t *testing.T) {
taskSchedule := taskConfig["schedule"].(map[string]any)
taskSchedule["starts_at"] = "2000-01-01T00:00:00+08:00"
taskSchedule["weekly_windows"] = weekly
// Exercise the current contract through HTTP, persistence, MQ and mTLS.
delete(taskSchedule, "ends_at")
delete(taskSchedule, "excluded_dates")
if examples["config-read-task-asr.json"], err = json.Marshal(taskConfig); err != nil {
t.Fatal(err)
}
+59
View File
@@ -0,0 +1,59 @@
package contracts
import (
"encoding/json"
"testing"
)
func TestTaskScheduleOptionalFieldsSchema(t *testing.T) {
schema, err := CompileCurrent("http-task-detail.schema.json")
if err != nil {
t.Fatal(err)
}
for _, mode := range []string{"asr", "full"} {
fixture, err := Files.ReadFile("schema/examples/config-read-task-" + mode + ".json")
if err != nil {
t.Fatal(err)
}
for _, tc := range []struct {
name string
omit []string
set map[string]any
valid bool
}{
{name: "explicit null end and empty exclusions", valid: true},
{name: "omitted end", omit: []string{"ends_at"}, valid: true},
{name: "omitted exclusions", omit: []string{"excluded_dates"}, valid: true},
{name: "both omitted", omit: []string{"ends_at", "excluded_dates"}, valid: true},
{name: "explicit end and exclusions", set: map[string]any{"ends_at": "2026-12-31T20:00:00+08:00", "excluded_dates": []any{"2026-10-01"}}, valid: true},
{name: "missing start", omit: []string{"starts_at"}},
{name: "missing time zone", omit: []string{"time_zone"}},
{name: "missing weekly windows", omit: []string{"weekly_windows"}},
{name: "null exclusions", set: map[string]any{"excluded_dates": nil}},
{name: "wrong exclusions type", set: map[string]any{"excluded_dates": "2026-10-01"}},
{name: "invalid excluded date", set: map[string]any{"excluded_dates": []any{"2026-02-30"}}},
{name: "duplicate exclusions", set: map[string]any{"excluded_dates": []any{"2026-10-01", "2026-10-01"}}},
{name: "invalid end", set: map[string]any{"ends_at": "not-a-time"}},
{name: "empty end", set: map[string]any{"ends_at": ""}},
{name: "numeric end", set: map[string]any{"ends_at": 123}},
{name: "unknown schedule property", set: map[string]any{"unknown": true}},
} {
t.Run(mode+"/"+tc.name, func(t *testing.T) {
var doc map[string]any
if err := json.Unmarshal(fixture, &doc); err != nil {
t.Fatal(err)
}
schedule := doc["schedule"].(map[string]any)
for _, field := range tc.omit {
delete(schedule, field)
}
for field, value := range tc.set {
schedule[field] = value
}
if err := schema.Validate(doc); (err == nil) != tc.valid {
t.Fatalf("valid=%t, want %t: %v", err == nil, tc.valid, err)
}
})
}
}
}
+6
View File
@@ -83,3 +83,9 @@ Mock、hash 或离线验证不替代 SaaS、供应商或生产签收。
AI 连接的 HTTP 与 WebSocket 地址按调用用途分别使用,缺失时明确拒绝、不互相回退。字段定义仍只在共享仓库维护。
本轮本地验证与外部边界见 [`evidence/schema-task-trunk-concurrency-20261008.md`](evidence/schema-task-trunk-concurrency-20261008.md)。没有部署、真实拨号或真实 AI 请求。
## 2026-10-09 本地适配
共享版本 `adc49ace34a0cdfd21c4de5a20dc6dfc1ab9b9df` 的任务计划可选字段变更已用于本项目合同引用;现有调度行为与新合同一致,无需改写业务逻辑。合同正反例、配置读取、选线边界及省略字段的隔离端到端验证已补齐。
本轮差异、验证结果及 A01–A12/K01–K16 对照见 [`evidence/schema-optional-task-schedule-20261009.md`](evidence/schema-optional-task-schedule-20261009.md)。仅暂存了用户明确批准的合同指针,没有提交、推送、部署、真实拨号或真实服务请求。
@@ -0,0 +1,91 @@
# 任务计划可选字段:本地合同适配与验证(2026-10-09)
## 基线与唯一差异
- 起始主仓库 `main`:`6e028d4`,更新前工作区干净,已执行远程同步,未发生主仓库快进。
- 验证时主仓库另有独立提交 `ef1c625`,仅调整 `AGENTS.md` 和独立 sip-call 部署证据,与本次修改文件无交叠;该提交及文件原样保留,本次未创建提交。
- 共享合同:`b502ad2` → `adc49ace34a0cdfd21c4de5a20dc6dfc1ab9b9df`;`make contracts-update` 成功。
- 唯一变化:`http-task-detail.schema.json` 的任务计划不再要求提供 `ends_at`、`excluded_dates`。属性类型、其他必填项、任务/线路时段、授权、并发及消息规则均未改变。
- 定义仍仅由 [`contracts/schema/`](../../contracts/schema/README.md) 提供,本项目没有修改共享仓库文件或添加平行 Schema。
- 用户追加批准:**仅暂存 `contracts/schema` 指针**,以满足现有合同验收要求;其他改动未暂存,不提交、不推送。此前未暂存指针时 `make check` 明确失败,未更改或绕过检查规则。
## 差异对应处理
| 变更或影响 | 处理结果与依据 |
| --- | --- |
| 两个字段分别省略或同时省略 | `contracts/task_schedule_schema_test.go` 分别核验 ASR-only 与完整 AI 任务;显式 null 结束时间和空排除日期继续有效。 |
| HTTP 读取与冻结快照 | `TestReadTaskSnapshot` 覆盖两种任务的四种字段组合,核验原始响应原样保存,不补结束时间或排除日期。 |
| 实际选线与通话期限 | `TestSelectTrunkOptionalTaskSchedule` 核验省略字段后的准入与期限;开始时间、每周窗口、显式结束时间及排除日期仍生效,SIP 加载和线路并发仍约束执行。 |
| 非法输入与边界 | 缺失其他必填项、非法结束时间、错误日期、重复日期、错误类型和未知字段仍拒绝;结束早于开始、到达结束边界等由现有调度测试核验。 |
| 隔离完整链路 | `TestDispatcherCommandStartsWithIsolatedMQHTTPAndAgent` 的模拟 SaaS 任务同时省略两个字段,经配置读取、持久化、RabbitMQ、双向 TLS Agent 和模拟 HTTPS 对象上传实际取得最终结果。 |
| 业务实现 | `internal/callwindow/policy.go` 已将未提供结束时间视为无额外结束限制、未提供排除日期视为无排除日期;读取层原样保留计划,Dispatcher 已调用该逻辑。无需修改运行逻辑,不新增兼容、兜底、默认值或数据迁移。 |
| 文档 | `docs/contracts.md` 增加本次适配入口;既有日期证据保留历史事实,不把旧合同路径或旧覆盖率当作本次依据。 |
## 测试先失败再通过
新增 `TestTaskScheduleOptionalFieldsSchema` 后,在独立临时测试目录中仅用旧提交的任务 Schema 执行测试:ASR-only 与完整 AI 的“省略结束时间”“省略排除日期”“同时省略”六项均因缺失必填属性失败。
同一临时测试目录换成已更新的任务 Schema 后,该测试全部通过。真实工作区的共享合同未回退、未被临时改写;临时目录仅用于负例验证,完成后删除,不构成运行时备用合同。
## 本地验证结果
| 检查 | 结果 |
| --- | --- |
| `go test ./contracts ./internal/configread ./internal/callwindow ./internal/dispatcher -count=1` | PASS |
| `make check` | PASS:格式、Proto 生成及 7 文件清单、合同来源/离线引用/历史来源、`go mod verify`、`go vet ./...`、`go test -race ./...`、构建及实际隔离 MQ 测试。 |
| `make coverage` | PASS:排除生成文件后的业务语句覆盖率 **70.6%**,达到 ≥65%。 |
| `make release-check-local` | PASS:当前合同固定提交与本地制品/打包检查;`source_dirty=true`、`production_approval=false`。 |
本次实际运行并通过的隔离 MQ 测试:
- `TestBrokerSharedResultQueueAndNoConfigure`
- `TestRuntimeIsolatedControlBacklogExecuteAndSharedResult`
- `TestDispatcherCommandStartsWithIsolatedMQHTTPAndAgent`
- `TestSaaSMockPublishesOneCommandOnProvisionedRabbitMQ`
- `TestSaaSMockAwaitsExactResultBeforeStoppingCapture`
本地发布检查报告制品 SHA-256:`025018369e0f2e3d50aecae3edd35e9dcf21e7f601e8200c1acdfefb50b1590b`。此检查制品和临时打包产物由检查脚本删除,未安装或部署。
## A01–A12 本次对照
| 项目 | 受影响范围与本次依据 |
| --- | --- |
| A01 合同一致性 | 直接影响;新 Schema 正反例及 `TestCurrentContractExamples` 通过,固定共享提交,无重复定义。 |
| A02 代次名称 | 不适用新增变更:仅消费字段可选性变化,未引入新运行入口、协议代次或旧路径。 |
| A03 固定通信名称 | 无业务变更;`TestCurrentContractTopology` 与实际隔离 MQ 回归通过。 |
| A04 来源与生成一致性 | 直接影响合同提交;`make check` 的共享来源、引用及 Proto 检查通过。 |
| A05 配置读取与失效 | 直接影响;`TestReadTaskSnapshot` 的八种组合通过;`TestReadTaskDoesNotReuseConfigAfterHTTPFailure` 随完整回归通过,无旧快照回退。 |
| A06 队列归属与路由 | 无业务变更;实际 `TestBrokerSharedResultQueueAndNoConfigure` 通过,未赋予 Dispatcher 建队列权限。 |
| A07 控制与发现竞态 | 无业务变更;`TestControlStopCannotResumeOrDispatchPending`、`TestSIPNotificationPersistsBarrierAndWaitsForLoadedFullSnapshot` 等随完整回归通过。 |
| A08 执行与 AI | 直接影响任务计划;新选线测试与省略字段的隔离完整链路通过。AI 连接、参数及对话行为未改。 |
| A09 结果与录音 | 无业务变更;省略字段的完整链路取得最终结果;`TestFinalResultNeedsConfirmedEndAndRemainsExactlyOne` 等随完整回归通过。 |
| A10 数据保护与恢复 | 无业务变更;`TestRecordingRecoveryUnknownPUTNeverRetriesAfterRestart` 等随完整回归通过;未改业务表、恢复文件或未知占用。 |
| A11 测试与构建 | 直接影响;新合同测试先失败再通过;完整本地验收、70.6% 覆盖率、发布检查通过。 |
| A12 交付边界 | 仅本地改动与验证,未部署或调用真实业务服务,未签收生产。 |
## K01–K16 本次对照
| 项目 | 受影响范围与本次依据 |
| --- | --- |
| K01 队列拓扑与所有权 | 无业务变更;实际隔离 MQ 与 `TestCurrentContractTopology` 通过。 |
| K02 归属与事件身份 | 无业务变更;`TestCurrentContractExamples`、原身份持久执行测试随完整回归通过。 |
| K03 分页发现 | 无业务变更;`TestReadAllTasksContinuesAfterShortPageUntilEmptyPage` 随完整回归通过。 |
| K04 停止不可恢复 | 无业务变更;`TestControlStopCannotResumeOrDispatchPending` 随完整回归通过。 |
| K05 暂停控制 | 无业务变更;`TestControlPauseDefaultHangupAckAfterAgentDispatch` 随完整回归通过。 |
| K06 派发与通话事实 | 无业务变更;`TestExecuteWaitsForRulesThenDispatchesOriginalIdentity` 随完整回归通过;隔离完整链路取得原事件最终结果。 |
| K07 SIP 加载屏障 | 无业务变更;新选线测试确认未加载时不放行;SIP 通知持久屏障测试随完整回归通过。 |
| K08 AI 授权绑定 | 无业务变更;`TestBindCurrentRejectsUnauthorizedProviderBeforeCall` 随完整回归通过,未发真实 AI 请求。 |
| K09 关键词结束 | 无业务变更;`TestKeywordHangupOnlyFinalUserLiteralAndOrderedGroups` 随完整回归通过。 |
| K10 上传恢复 | 无业务变更;`TestRecordingRecoveryExpiresAfterExactly48HoursWithoutResult` 随完整回归通过;未更改恢复窗口。 |
| K11 SIP 事实 | 无业务变更;`TestFinalResultPayloadRejectsMissingOrInventedCallFacts` 随完整回归通过,未新增或伪造真实 SIP 证据。 |
| K12 呼出规则 | 直接影响;`TestSelectTrunkOptionalTaskSchedule` 核验省略字段、开始/结束边界、每周时段、排除日期及并发/加载门槛;缺省不等于绕过其他规则。 |
| K13 单号拒绝 | 无业务变更;`TestExecuteRejectsInvalidCalleeWithoutStoppingTask` 随完整回归通过。 |
| K14 通话结果与未知执行 | 无业务变更;`TestFinalResultPayloadNoAnswerHasNoFabricatedTranscriptOrAsset` 随完整回归通过,未知占用未清除。 |
| K15 录音生成失败 | 无业务变更;`TestRecordingDeliveryCaptureFailureDescribesFailureWithoutInventingUpload` 随完整回归通过。 |
| K16 双文件保存失败 | 无业务变更;`TestRecordingDeliveryRecoveryDiskFailureNeverReportsUploaded` 随完整回归通过。 |
## 未验证范围
没有真实 SaaS/management 的配置签收、真实运营商 SIP 接通、Asterisk 实际加载、真实 ASR/LLM/TTS、OSS、主机重启、容量或生产验收。本次 RabbitMQ、HTTP/HTTPS、双向 TLS、上传与通话均为本机隔离测试,不授权或证明任何真实拨号。
未操作测试机,未读取私有配置,未清理或迁移既有数据,未修改其他项目。共享仓库本次仅同步读取,未替 SaaS 开发人员确认应用已同步或已完成联调。
+83 -58
View File
@@ -37,68 +37,93 @@ func approvedSIP(t *testing.T) SIP {
func TestReadTaskSnapshot(t *testing.T) {
for _, mode := range []string{"asr", "full"} {
t.Run(mode, func(t *testing.T) {
responses := map[string][]byte{
"/internal/v1/dispatcher/sip": example(t, "config-read-sip"),
"/internal/v1/dispatcher/ai-providers": example(t, "config-read-providers"),
"/internal/v1/dispatcher/task/task-" + mode: []byte(strings.Replace(string(example(t, "config-read-task-"+mode)), `"temperature":0.7`, `"temperature":0`, 1)),
"/internal/v1/dispatcher/tenant/1001/quota": example(t, "config-read-quota"),
}
calls := 0
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
calls++
if r.Method != http.MethodGet || r.Header.Get("X-DISPATCHER-ID") != "c046b893-8628-4589-ae50-619d049248a6" || r.Header.Get("X-DISPATCHER-SECRET-KEY") != "test-secret" {
t.Errorf("unexpected method/headers: %s", r.URL.Path)
w.WriteHeader(http.StatusBadRequest)
return
for _, tc := range []struct {
name string
omit []string
}{
{"explicit schedule fields", nil},
{"omitted end", []string{"ends_at"}},
{"omitted exclusions", []string{"excluded_dates"}},
{"both omitted", []string{"ends_at", "excluded_dates"}},
} {
t.Run(mode+"/"+tc.name, func(t *testing.T) {
var task map[string]any
if err := json.Unmarshal(example(t, "config-read-task-"+mode), &task); err != nil {
t.Fatal(err)
}
body, ok := responses[r.URL.Path]
if !ok {
t.Errorf("unexpected path %s", r.URL.Path)
w.WriteHeader(http.StatusNotFound)
return
for _, field := range tc.omit {
delete(task["schedule"].(map[string]any), field)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(body)
}))
defer server.Close()
client, err := NewClient(server.URL, "c046b893-8628-4589-ae50-619d049248a6", "test-secret", server.Client())
if err != nil {
t.Fatal(err)
}
providers, err := client.ReadProviders(context.Background())
if err != nil {
t.Fatal(err)
}
snapshot, err := client.ReadTask(context.Background(), "task-"+mode, 1001, approvedSIP(t), providers)
if err != nil {
t.Fatal(err)
}
if calls != 3 || snapshot.SIP.Revision != 8 || snapshot.Task.TenantID != 1001 || snapshot.Quota.TenantID != 1001 {
t.Fatalf("unexpected request count or identity: calls=%d snapshot=%+v", calls, snapshot)
}
if got := snapshot.Providers["volcengine"].Credential; got != "sk-******" {
t.Fatalf("credential not passed unchanged: %q", got)
}
if mode == "asr" && (snapshot.Task.Agent.Mode != "asr_only" || strings.Contains(string(snapshot.Task.Agent.Raw), `"llm"`)) {
t.Fatalf("ASR-only Agent picked up an LLM: %s", snapshot.Task.Agent.Raw)
}
if mode == "full" {
var agent struct {
LLM struct {
Params struct {
Temperature *float64 `json:"temperature"`
} `json:"params"`
} `json:"llm"`
Conversation struct {
AllowInterrupt *bool `json:"allow_interrupt"`
} `json:"conversation"`
taskBody, err := json.Marshal(task)
if err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(snapshot.Task.Agent.Raw, &agent); err != nil || agent.LLM.Params.Temperature == nil || *agent.LLM.Params.Temperature != 0 || agent.Conversation.AllowInterrupt == nil || *agent.Conversation.AllowInterrupt {
t.Fatalf("explicit zero/false lost: %v, %s", err, snapshot.Task.Agent.Raw)
taskBody = []byte(strings.Replace(string(taskBody), `"temperature":0.7`, `"temperature":0`, 1))
responses := map[string][]byte{
"/internal/v1/dispatcher/sip": example(t, "config-read-sip"),
"/internal/v1/dispatcher/ai-providers": example(t, "config-read-providers"),
"/internal/v1/dispatcher/task/task-" + mode: taskBody,
"/internal/v1/dispatcher/tenant/1001/quota": example(t, "config-read-quota"),
}
}
})
calls := 0
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
calls++
if r.Method != http.MethodGet || r.Header.Get("X-DISPATCHER-ID") != "c046b893-8628-4589-ae50-619d049248a6" || r.Header.Get("X-DISPATCHER-SECRET-KEY") != "test-secret" {
t.Errorf("unexpected method/headers: %s", r.URL.Path)
w.WriteHeader(http.StatusBadRequest)
return
}
body, ok := responses[r.URL.Path]
if !ok {
t.Errorf("unexpected path %s", r.URL.Path)
w.WriteHeader(http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(body)
}))
defer server.Close()
client, err := NewClient(server.URL, "c046b893-8628-4589-ae50-619d049248a6", "test-secret", server.Client())
if err != nil {
t.Fatal(err)
}
providers, err := client.ReadProviders(context.Background())
if err != nil {
t.Fatal(err)
}
snapshot, err := client.ReadTask(context.Background(), "task-"+mode, 1001, approvedSIP(t), providers)
if err != nil {
t.Fatal(err)
}
if string(snapshot.Task.Raw) != string(taskBody) {
t.Fatal("task snapshot changed the HTTP response or inserted omitted schedule fields")
}
if calls != 3 || snapshot.SIP.Revision != 8 || snapshot.Task.TenantID != 1001 || snapshot.Quota.TenantID != 1001 {
t.Fatalf("unexpected request count or identity: calls=%d snapshot=%+v", calls, snapshot)
}
if got := snapshot.Providers["volcengine"].Credential; got != "sk-******" {
t.Fatalf("credential not passed unchanged: %q", got)
}
if mode == "asr" && (snapshot.Task.Agent.Mode != "asr_only" || strings.Contains(string(snapshot.Task.Agent.Raw), `"llm"`)) {
t.Fatalf("ASR-only Agent picked up an LLM: %s", snapshot.Task.Agent.Raw)
}
if mode == "full" {
var agent struct {
LLM struct {
Params struct {
Temperature *float64 `json:"temperature"`
} `json:"params"`
} `json:"llm"`
Conversation struct {
AllowInterrupt *bool `json:"allow_interrupt"`
} `json:"conversation"`
}
if err := json.Unmarshal(snapshot.Task.Agent.Raw, &agent); err != nil || agent.LLM.Params.Temperature == nil || *agent.LLM.Params.Temperature != 0 || agent.Conversation.AllowInterrupt == nil || *agent.Conversation.AllowInterrupt {
t.Fatalf("explicit zero/false lost: %v, %s", err, snapshot.Task.Agent.Raw)
}
}
})
}
}
}
+61
View File
@@ -71,6 +71,67 @@ func TestSelectTrunkSaaSEventCalleeScheduleAndCaller(t *testing.T) {
}
}
func TestSelectTrunkOptionalTaskSchedule(t *testing.T) {
for _, tc := range []struct {
name string
set map[string]any
at time.Time
deadline time.Time
wantErr error
}{
{name: "both omitted", at: monday(9, 30), deadline: monday(9, 32)},
{name: "null end", set: map[string]any{"ends_at": nil}, at: monday(9, 30), deadline: monday(9, 32)},
{name: "empty exclusions", set: map[string]any{"excluded_dates": []string{}}, at: monday(9, 30), deadline: monday(9, 32)},
{name: "weekly end still bounds call", at: monday(19, 59), deadline: monday(20, 0)},
{name: "weekly end remains exclusive", at: monday(20, 0), wantErr: ErrRuleWait},
{name: "before start", set: map[string]any{"starts_at": "2026-09-21T10:00:00+08:00"}, at: monday(9, 30), wantErr: ErrRuleWait},
{name: "at start", set: map[string]any{"starts_at": "2026-09-21T09:30:00+08:00"}, at: monday(9, 30), deadline: monday(9, 32)},
{name: "explicit end bounds call", set: map[string]any{"ends_at": "2026-09-21T09:31:00+08:00"}, at: monday(9, 30), deadline: monday(9, 31)},
{name: "at explicit end", set: map[string]any{"ends_at": "2026-09-21T09:30:00+08:00"}, at: monday(9, 30), wantErr: ErrRuleWait},
{name: "explicit excluded date", set: map[string]any{"excluded_dates": []string{"2026-09-21"}}, at: monday(9, 30), wantErr: ErrRuleWait},
{name: "other date not excluded", set: map[string]any{"excluded_dates": []string{"2026-09-22"}}, at: monday(9, 30), deadline: monday(9, 32)},
{name: "invalid end", set: map[string]any{"ends_at": "not-a-time"}, at: monday(9, 30), wantErr: ErrRuleInvalid},
{name: "end before start", set: map[string]any{"ends_at": "2026-09-20T20:00:00+08:00"}, at: monday(9, 30), wantErr: ErrRuleInvalid},
{name: "invalid excluded date", set: map[string]any{"excluded_dates": []string{"2026-02-30"}}, at: monday(9, 30), wantErr: ErrRuleInvalid},
{name: "duplicate excluded dates", set: map[string]any{"excluded_dates": []string{"2026-09-22", "2026-09-22"}}, at: monday(9, 30), wantErr: ErrRuleInvalid},
} {
t.Run(tc.name, func(t *testing.T) {
snapshot := policySnapshot(t)
var schedule map[string]any
if err := json.Unmarshal(snapshot.Task.Schedule, &schedule); err != nil {
t.Fatal(err)
}
delete(schedule, "ends_at")
delete(schedule, "excluded_dates")
for field, value := range tc.set {
schedule[field] = value
}
var err error
snapshot.Task.Schedule, err = json.Marshal(schedule)
if err != nil {
t.Fatal(err)
}
loaded := map[string]int64{"trunk-mock": 8}
selected, err := SelectTrunk(snapshot, "15003164745", tc.at, nil, nil, loaded)
if tc.wantErr != nil {
if !errors.Is(err, tc.wantErr) {
t.Fatalf("schedule must be rejected with %v, got %v", tc.wantErr, err)
}
return
}
if err != nil || !selected.Deadline.Equal(tc.deadline) {
t.Fatalf("deadline=%v, want %v, err=%v", selected.Deadline, tc.deadline, err)
}
if _, err := SelectTrunk(snapshot, "15003164745", tc.at, nil, nil, nil); !errors.Is(err, ErrRuleWait) {
t.Fatalf("omitted schedule fields bypassed SIP loading: %v", err)
}
if _, err := SelectTrunk(snapshot, "15003164745", tc.at, nil, map[string]int64{"trunk-mock": 2}, loaded); !errors.Is(err, ErrRuleWait) {
t.Fatalf("omitted schedule fields bypassed task-line concurrency: %v", err)
}
})
}
}
func TestSelectTrunkUsesEachLinesOwnCaller(t *testing.T) {
snapshot := policySnapshot(t)
var trunks []trunkConfig