iop/apps/edge/internal/configrefresh/workspace_classify_test.go
toki dc9a9a8c59 feat(agent): 단일 요청 Agent 실행 경계를 구현한다
승인된 execution preset을 Edge 조정 경계와 Node workspace/tool 실행 경계로 연결해 단일 요청 수명주기와 관측 계약을 일관되게 처리한다.
2026-08-07 07:03:55 +09:00

155 lines
5.9 KiB
Go

package configrefresh
import (
"testing"
"iop/packages/go/config"
)
// makeTestConfig builds a minimal EdgeConfig with the given workspaces for
// classification testing. It is not a valid full config for LoadEdge; it is
// used only as in-memory current/candidate pairs for Classify.
func makeTestConfig(workspaces []config.WorkspaceDefinition) *config.EdgeConfig {
return &config.EdgeConfig{
Edge: config.EdgeInfo{ID: "edge-test", Name: "edge-test"},
Server: config.EdgeServerConf{Listen: "0.0.0.0:9090"},
Bootstrap: config.EdgeBootstrapConf{
Listen: "0.0.0.0:18080",
ArtifactDir: "artifacts",
},
OpenAI: config.EdgeOpenAIConf{Listen: "0.0.0.0:18081", Adapter: "ollama"},
Logging: config.LoggingConf{Level: "info"},
Metrics: config.MetricsConf{Port: 19092},
Refresh: config.EdgeRefreshConf{Enabled: false, Listen: "127.0.0.1:19093"},
LongContextThresholdTokens: 100000,
Nodes: []config.NodeDefinition{
{
ID: "node-ws-test",
Alias: "ws-test-node",
Token: "token-ws-test",
Providers: []config.NodeProviderConf{
{
ID: "prov-a",
Type: "ollama",
Category: config.CategoryLocalInference,
Models: []string{"model-a"},
Capacity: 2,
},
},
Workspaces: workspaces,
},
},
}
}
func TestClassifyWorkspaceRootChangeRequiresRestart(t *testing.T) {
current := makeTestConfig([]config.WorkspaceDefinition{
{Ref: "ws-root-change", Platform: "darwin", Root: "/Users/operator/projects/old", Operations: []config.WorkspaceOperation{config.WorkspaceOpRead}},
})
candidate := makeTestConfig([]config.WorkspaceDefinition{
{Ref: "ws-root-change", Platform: "darwin", Root: "/Users/operator/projects/new", Operations: []config.WorkspaceOperation{config.WorkspaceOpRead}},
})
result := Classify(current, candidate)
if result.Status != StatusRestartRequired {
t.Fatalf("expected restart_required for root change, got %s", result.Status)
}
found := false
for _, c := range result.Changes {
if c.Class == StatusRestartRequired && containsStr(c.Path, "workspaces") {
found = true
}
}
if !found {
t.Fatalf("expected restart_required change on nodes[].workspaces, got %v", result.Changes)
}
}
func TestClassifyWorkspaceCapabilityChangeRequiresRestart(t *testing.T) {
current := makeTestConfig([]config.WorkspaceDefinition{
{Ref: "ws-cap-change", Platform: "darwin", Root: "/Users/operator/projects/test", Operations: []config.WorkspaceOperation{config.WorkspaceOpRead}},
})
candidate := makeTestConfig([]config.WorkspaceDefinition{
{Ref: "ws-cap-change", Platform: "darwin", Root: "/Users/operator/projects/test", Operations: []config.WorkspaceOperation{config.WorkspaceOpRead, config.WorkspaceOpWrite}},
})
result := Classify(current, candidate)
if result.Status != StatusRestartRequired {
t.Fatalf("expected restart_required for capability change, got %s", result.Status)
}
}
func TestClassifyWorkspaceAdditionRequiresRestart(t *testing.T) {
current := makeTestConfig(nil)
candidate := makeTestConfig([]config.WorkspaceDefinition{
{Ref: "ws-new", Platform: "darwin", Root: "/Users/operator/projects/new", Operations: []config.WorkspaceOperation{config.WorkspaceOpRead}},
})
result := Classify(current, candidate)
if result.Status != StatusRestartRequired {
t.Fatalf("expected restart_required for workspace addition, got %s", result.Status)
}
}
func TestClassifyWorkspaceRemovalRequiresRestart(t *testing.T) {
current := makeTestConfig([]config.WorkspaceDefinition{
{Ref: "ws-to-remove", Platform: "darwin", Root: "/Users/operator/projects/test", Operations: []config.WorkspaceOperation{config.WorkspaceOpRead}},
})
candidate := makeTestConfig(nil)
result := Classify(current, candidate)
if result.Status != StatusRestartRequired {
t.Fatalf("expected restart_required for workspace removal, got %s", result.Status)
}
}
func TestClassifyWorkspaceNoChangeIsApplied(t *testing.T) {
ws := []config.WorkspaceDefinition{
{Ref: "ws-no-change", Platform: "darwin", Root: "/Users/operator/projects/test", Operations: []config.WorkspaceOperation{config.WorkspaceOpRead}},
}
current := makeTestConfig(ws)
candidate := makeTestConfig(ws)
result := Classify(current, candidate)
if result.Status != StatusApplied {
t.Fatalf("expected applied for no workspace change, got %s", result.Status)
}
}
func TestClassifyWorkspaceEnvironmentAllowlistChangeRequiresRestart(t *testing.T) {
current := makeTestConfig([]config.WorkspaceDefinition{
{Ref: "ws-env-change", Platform: "darwin", Root: "/Users/operator/projects/test", Operations: []config.WorkspaceOperation{config.WorkspaceOpRead}, EnvironmentAllowlist: []string{"PATH"}},
})
candidate := makeTestConfig([]config.WorkspaceDefinition{
{Ref: "ws-env-change", Platform: "darwin", Root: "/Users/operator/projects/test", Operations: []config.WorkspaceOperation{config.WorkspaceOpRead}, EnvironmentAllowlist: []string{"PATH", "HOME"}},
})
result := Classify(current, candidate)
if result.Status != StatusRestartRequired {
t.Fatalf("expected restart_required for environment allowlist change, got %s", result.Status)
}
}
func TestClassifyWorkspaceLimitsChangeRequiresRestart(t *testing.T) {
current := makeTestConfig([]config.WorkspaceDefinition{
{Ref: "ws-limits-change", Platform: "darwin", Root: "/Users/operator/projects/test", Operations: []config.WorkspaceOperation{config.WorkspaceOpRead}, MaxReadBytes: 1024},
})
candidate := makeTestConfig([]config.WorkspaceDefinition{
{Ref: "ws-limits-change", Platform: "darwin", Root: "/Users/operator/projects/test", Operations: []config.WorkspaceOperation{config.WorkspaceOpRead}, MaxReadBytes: 2048},
})
result := Classify(current, candidate)
if result.Status != StatusRestartRequired {
t.Fatalf("expected restart_required for limits change, got %s", result.Status)
}
}
// containsStr reports whether s contains substr.
func containsStr(s, substr string) bool {
for i := 0; i+len(substr) <= len(s); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}