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 }