Merge pull request #4540 from router-for-me/test

refactor(tests): simplify credential concurrency and sentinel test fixtures
This commit is contained in:
Chén Mù
2026-07-24 22:13:38 +08:00
committed by GitHub
7 changed files with 100 additions and 192 deletions
@@ -1,12 +1,7 @@
package config
import (
"bytes"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"testing"
"time"
@@ -14,17 +9,17 @@ import (
)
type credentialConcurrencyFixtureWireConfig struct {
LifecycleConfigRevision int64 `json:"lifecycle-config-revision"`
ObservationBarrierRevision int64 `json:"observation-barrier-revision"`
CPAHeartbeatTimeout time.Duration `json:"cpa-heartbeat-timeout"`
CPACancelBound time.Duration `json:"cpa-cancel-bound"`
ReclaimGrace time.Duration `json:"reclaim-grace"`
CleanupInterval time.Duration `json:"cleanup-interval"`
ReleaseFlushInterval string `json:"release-flush-interval" yaml:"release-flush-interval"`
ReleaseMaxBackoff string `json:"release-max-backoff" yaml:"release-max-backoff"`
BusyRetryMin string `json:"busy-retry-min" yaml:"busy-retry-min"`
BusyRetryMax string `json:"busy-retry-max" yaml:"busy-retry-max"`
MaxLimit int64 `json:"max-limit"`
LifecycleConfigRevision int64
ObservationBarrierRevision int64
CPAHeartbeatTimeout time.Duration
CPACancelBound time.Duration
ReclaimGrace time.Duration
CleanupInterval time.Duration
ReleaseFlushInterval string `yaml:"release-flush-interval"`
ReleaseMaxBackoff string `yaml:"release-max-backoff"`
BusyRetryMin string `yaml:"busy-retry-min"`
BusyRetryMax string `yaml:"busy-retry-max"`
MaxLimit int64
}
type credentialConcurrencyFixtureHotDurations struct {
@@ -58,45 +53,44 @@ func (c credentialConcurrencyFixtureWireConfig) config() (CredentialConcurrencyC
}, nil
}
func TestCredentialConcurrencyLifecycleFixture(t *testing.T) {
raw, errRead := os.ReadFile(filepath.Join("..", "..", "testdata", "credential-concurrency-lifecycle.json"))
if errRead != nil {
t.Fatal(errRead)
}
var fixture struct {
Defaults credentialConcurrencyFixtureWireConfig `json:"defaults"`
Invalid []struct {
NodeHeartbeatTimeout time.Duration `json:"node_heartbeat_timeout"`
Config credentialConcurrencyFixtureWireConfig `json:"config"`
} `json:"invalid"`
}
decoder := json.NewDecoder(bytes.NewReader(raw))
decoder.DisallowUnknownFields()
if errDecode := decoder.Decode(&fixture); errDecode != nil {
t.Fatal(errDecode)
}
if errTrailing := decoder.Decode(&struct{}{}); errTrailing != io.EOF {
t.Fatalf("fixture contains trailing JSON: %v", errTrailing)
func credentialConcurrencyWireFixture(cpaHeartbeatTimeout time.Duration) credentialConcurrencyFixtureWireConfig {
return credentialConcurrencyFixtureWireConfig{
CPAHeartbeatTimeout: cpaHeartbeatTimeout,
CPACancelBound: 5 * time.Second,
ReclaimGrace: 5 * time.Second,
CleanupInterval: 5 * time.Second,
ReleaseFlushInterval: "250ms",
ReleaseMaxBackoff: "2s",
BusyRetryMin: "250ms",
BusyRetryMax: "1s",
MaxLimit: 1_000_000,
}
}
defaults, errConfig := fixture.Defaults.config()
func credentialConcurrencyConfigFixture(cpaHeartbeatTimeout time.Duration) CredentialConcurrencyConfig {
return CredentialConcurrencyConfig{
CPAHeartbeatTimeout: cpaHeartbeatTimeout,
CPACancelBound: 5 * time.Second,
ReclaimGrace: 5 * time.Second,
CleanupInterval: 5 * time.Second,
ReleaseFlushInterval: 250 * time.Millisecond,
ReleaseMaxBackoff: 2 * time.Second,
BusyRetryMin: 250 * time.Millisecond,
BusyRetryMax: time.Second,
MaxLimit: 1_000_000,
}
}
func TestCredentialConcurrencyLifecycleFixture(t *testing.T) {
wireDefaults := credentialConcurrencyWireFixture(3 * time.Second)
wireDefaults.LifecycleConfigRevision = 1
defaults, errConfig := wireDefaults.config()
if errConfig != nil {
t.Fatal(errConfig)
}
expectedDefaults := CredentialConcurrencyConfig{
LifecycleConfigRevision: 1,
ObservationBarrierRevision: 0,
CPAHeartbeatTimeout: 3 * time.Second,
CPACancelBound: 5 * time.Second,
ReclaimGrace: 5 * time.Second,
CleanupInterval: 5 * time.Second,
ReleaseFlushInterval: 250 * time.Millisecond,
ReleaseMaxBackoff: 2 * time.Second,
BusyRetryMin: 250 * time.Millisecond,
BusyRetryMax: time.Second,
MaxLimit: 1_000_000,
}
expectedDefaults := credentialConcurrencyConfigFixture(3 * time.Second)
expectedDefaults.LifecycleConfigRevision = 1
if defaults != expectedDefaults {
t.Fatalf("defaults = %#v, want %#v", defaults, expectedDefaults)
}
@@ -104,44 +98,25 @@ func TestCredentialConcurrencyLifecycleFixture(t *testing.T) {
t.Fatalf("ValidateCredentialConcurrency(defaults) error = %v", errValidate)
}
invalidFixtures := []struct {
NodeHeartbeatTimeout time.Duration
Config credentialConcurrencyFixtureWireConfig
}{
{NodeHeartbeatTimeout: 3 * time.Second, Config: credentialConcurrencyWireFixture(3 * time.Second)},
{NodeHeartbeatTimeout: 20 * time.Second, Config: credentialConcurrencyWireFixture(0)},
}
expectedInvalid := []struct {
nodeHeartbeatTimeout time.Duration
config CredentialConcurrencyConfig
}{
{
nodeHeartbeatTimeout: 3 * time.Second,
config: CredentialConcurrencyConfig{
CPAHeartbeatTimeout: 3 * time.Second,
CPACancelBound: 5 * time.Second,
ReclaimGrace: 5 * time.Second,
CleanupInterval: 5 * time.Second,
ReleaseFlushInterval: 250 * time.Millisecond,
ReleaseMaxBackoff: 2 * time.Second,
BusyRetryMin: 250 * time.Millisecond,
BusyRetryMax: time.Second,
MaxLimit: 1_000_000,
},
},
{
nodeHeartbeatTimeout: 20 * time.Second,
config: CredentialConcurrencyConfig{
CPAHeartbeatTimeout: 0,
CPACancelBound: 5 * time.Second,
ReclaimGrace: 5 * time.Second,
CleanupInterval: 5 * time.Second,
ReleaseFlushInterval: 250 * time.Millisecond,
ReleaseMaxBackoff: 2 * time.Second,
BusyRetryMin: 250 * time.Millisecond,
BusyRetryMax: time.Second,
MaxLimit: 1_000_000,
},
},
{nodeHeartbeatTimeout: 3 * time.Second, config: credentialConcurrencyConfigFixture(3 * time.Second)},
{nodeHeartbeatTimeout: 20 * time.Second, config: credentialConcurrencyConfigFixture(0)},
}
if len(fixture.Invalid) != len(expectedInvalid) {
t.Fatalf("invalid fixture count = %d, want %d", len(fixture.Invalid), len(expectedInvalid))
if len(invalidFixtures) != len(expectedInvalid) {
t.Fatalf("invalid fixture count = %d, want %d", len(invalidFixtures), len(expectedInvalid))
}
for index, expected := range expectedInvalid {
item := fixture.Invalid[index]
item := invalidFixtures[index]
itemConfig, errConfig := item.Config.config()
if errConfig != nil {
t.Fatalf("invalid fixture %d config() error = %v", index, errConfig)
+45 -32
View File
@@ -1,35 +1,48 @@
package test
import (
"encoding/json"
"os"
"path/filepath"
"testing"
import "testing"
type sentinelPayload = map[string]any
var (
claudeCodeToolProgressFixture = sentinelPayload{
"type": "tool_progress",
"tool_use_id": "toolu_123",
"tool_name": "Bash",
"parent_tool_use_id": nil,
"elapsed_time_seconds": 2.5,
"task_id": "task_123",
"uuid": "11111111-1111-4111-8111-111111111111",
"session_id": "sess_123",
}
claudeCodeSessionStateChangedFixture = sentinelPayload{
"type": "system",
"subtype": "session_state_changed",
"state": "requires_action",
"uuid": "22222222-2222-4222-8222-222222222222",
"session_id": "sess_123",
}
claudeCodeToolUseSummaryFixture = sentinelPayload{
"type": "tool_use_summary",
"summary": "Searched in auth/",
"preceding_tool_use_ids": []any{"toolu_1", "toolu_2"},
"uuid": "33333333-3333-4333-8333-333333333333",
"session_id": "sess_123",
}
claudeCodeControlRequestCanUseToolFixture = sentinelPayload{
"type": "control_request",
"request_id": "req_123",
"request": sentinelPayload{
"subtype": "can_use_tool",
"tool_name": "Bash",
"input": sentinelPayload{"command": "npm test"},
"tool_use_id": "toolu_123",
"description": "Running npm test",
},
}
)
type jsonObject = map[string]any
func loadClaudeCodeSentinelFixture(t *testing.T, name string) jsonObject {
t.Helper()
path := filepath.Join("testdata", "claude_code_sentinels", name)
data := mustReadFile(t, path)
var payload jsonObject
if err := json.Unmarshal(data, &payload); err != nil {
t.Fatalf("unmarshal %s: %v", name, err)
}
return payload
}
func mustReadFile(t *testing.T, path string) []byte {
t.Helper()
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read %s: %v", path, err)
}
return data
}
func requireStringField(t *testing.T, obj jsonObject, key string) string {
func requireStringField(t *testing.T, obj sentinelPayload, key string) string {
t.Helper()
value, ok := obj[key].(string)
if !ok || value == "" {
@@ -39,7 +52,7 @@ func requireStringField(t *testing.T, obj jsonObject, key string) string {
}
func TestClaudeCodeSentinel_ToolProgressShape(t *testing.T) {
payload := loadClaudeCodeSentinelFixture(t, "tool_progress.json")
payload := claudeCodeToolProgressFixture
if got := requireStringField(t, payload, "type"); got != "tool_progress" {
t.Fatalf("type = %q, want tool_progress", got)
}
@@ -52,7 +65,7 @@ func TestClaudeCodeSentinel_ToolProgressShape(t *testing.T) {
}
func TestClaudeCodeSentinel_SessionStateShape(t *testing.T) {
payload := loadClaudeCodeSentinelFixture(t, "session_state_changed.json")
payload := claudeCodeSessionStateChangedFixture
if got := requireStringField(t, payload, "type"); got != "system" {
t.Fatalf("type = %q, want system", got)
}
@@ -69,7 +82,7 @@ func TestClaudeCodeSentinel_SessionStateShape(t *testing.T) {
}
func TestClaudeCodeSentinel_ToolUseSummaryShape(t *testing.T) {
payload := loadClaudeCodeSentinelFixture(t, "tool_use_summary.json")
payload := claudeCodeToolUseSummaryFixture
if got := requireStringField(t, payload, "type"); got != "tool_use_summary" {
t.Fatalf("type = %q, want tool_use_summary", got)
}
@@ -86,7 +99,7 @@ func TestClaudeCodeSentinel_ToolUseSummaryShape(t *testing.T) {
}
func TestClaudeCodeSentinel_ControlRequestCanUseToolShape(t *testing.T) {
payload := loadClaudeCodeSentinelFixture(t, "control_request_can_use_tool.json")
payload := claudeCodeControlRequestCanUseToolFixture
if got := requireStringField(t, payload, "type"); got != "control_request" {
t.Fatalf("type = %q, want control_request", got)
}
@@ -1,11 +0,0 @@
{
"type": "control_request",
"request_id": "req_123",
"request": {
"subtype": "can_use_tool",
"tool_name": "Bash",
"input": {"command": "npm test"},
"tool_use_id": "toolu_123",
"description": "Running npm test"
}
}
@@ -1,7 +0,0 @@
{
"type": "system",
"subtype": "session_state_changed",
"state": "requires_action",
"uuid": "22222222-2222-4222-8222-222222222222",
"session_id": "sess_123"
}
-10
View File
@@ -1,10 +0,0 @@
{
"type": "tool_progress",
"tool_use_id": "toolu_123",
"tool_name": "Bash",
"parent_tool_use_id": null,
"elapsed_time_seconds": 2.5,
"task_id": "task_123",
"uuid": "11111111-1111-4111-8111-111111111111",
"session_id": "sess_123"
}
@@ -1,7 +0,0 @@
{
"type": "tool_use_summary",
"summary": "Searched in auth/",
"preceding_tool_use_ids": ["toolu_1", "toolu_2"],
"uuid": "33333333-3333-4333-8333-333333333333",
"session_id": "sess_123"
}
-45
View File
@@ -1,45 +0,0 @@
{
"defaults": {
"lifecycle-config-revision": 1,
"observation-barrier-revision": 0,
"cpa-heartbeat-timeout": 3000000000,
"cpa-cancel-bound": 5000000000,
"reclaim-grace": 5000000000,
"cleanup-interval": 5000000000,
"release-flush-interval": "250ms",
"release-max-backoff": "2s",
"busy-retry-min": "250ms",
"busy-retry-max": "1s",
"max-limit": 1000000
},
"invalid": [
{
"node_heartbeat_timeout": 3000000000,
"config": {
"cpa-heartbeat-timeout": 3000000000,
"cpa-cancel-bound": 5000000000,
"reclaim-grace": 5000000000,
"cleanup-interval": 5000000000,
"release-flush-interval": "250ms",
"release-max-backoff": "2s",
"busy-retry-min": "250ms",
"busy-retry-max": "1s",
"max-limit": 1000000
}
},
{
"node_heartbeat_timeout": 20000000000,
"config": {
"cpa-heartbeat-timeout": 0,
"cpa-cancel-bound": 5000000000,
"reclaim-grace": 5000000000,
"cleanup-interval": 5000000000,
"release-flush-interval": "250ms",
"release-max-backoff": "2s",
"busy-retry-min": "250ms",
"busy-retry-max": "1s",
"max-limit": 1000000
}
}
]
}