- Add mock resource activation for G06 review followup alignment - Expand edgevalidate tests and refactor validation logic - Update node mapper with improved handling - Add integration and server test fixes - Update Go config with new options - Fix E2E scripts (cli-workspace, lemonade, ollama, vllm)
988 lines
27 KiB
Go
988 lines
27 KiB
Go
package edgevalidate
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"iop/packages/go/config"
|
|
)
|
|
|
|
func TestValidateEdgeConfigProviderAdapterReferences(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
node config.NodeDefinition
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "valid exact openai compat instance",
|
|
node: config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Adapters: config.AdaptersConf{
|
|
OpenAICompatInstances: []config.OpenAICompatInstanceConf{
|
|
{Name: "vllm-gpu", Enabled: true, Endpoint: "http://127.0.0.1:8000/v1"},
|
|
},
|
|
},
|
|
Providers: []config.NodeProviderConf{
|
|
{ID: "prov-vllm", Type: "vllm", Category: config.CategoryAPI, Adapter: "vllm-gpu"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "valid single type route",
|
|
node: config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Adapters: config.AdaptersConf{
|
|
OpenAICompatInstances: []config.OpenAICompatInstanceConf{
|
|
{Name: "vllm-gpu", Enabled: true, Endpoint: "http://127.0.0.1:8000/v1"},
|
|
},
|
|
},
|
|
Providers: []config.NodeProviderConf{
|
|
{ID: "prov-vllm", Type: "vllm", Category: config.CategoryAPI, Adapter: "openai_compat"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "missing adapter rejected",
|
|
node: config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Adapters: config.AdaptersConf{
|
|
CLI: config.CLIConf{Enabled: true},
|
|
},
|
|
Providers: []config.NodeProviderConf{
|
|
{ID: "prov-vllm", Type: "vllm", Category: config.CategoryAPI, Adapter: "missing"},
|
|
},
|
|
},
|
|
wantErr: "does not resolve",
|
|
},
|
|
{
|
|
name: "disabled instance rejected",
|
|
node: config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Adapters: config.AdaptersConf{
|
|
CLI: config.CLIConf{Enabled: true},
|
|
OpenAICompatInstances: []config.OpenAICompatInstanceConf{
|
|
{Name: "vllm-gpu", Enabled: false, Endpoint: "http://127.0.0.1:8000/v1"},
|
|
},
|
|
},
|
|
Providers: []config.NodeProviderConf{
|
|
{ID: "prov-vllm", Type: "vllm", Category: config.CategoryAPI, Adapter: "vllm-gpu"},
|
|
},
|
|
},
|
|
wantErr: "does not resolve",
|
|
},
|
|
{
|
|
name: "ambiguous type route rejected",
|
|
node: config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Adapters: config.AdaptersConf{
|
|
OpenAICompatInstances: []config.OpenAICompatInstanceConf{
|
|
{Name: "vllm-a", Enabled: true, Endpoint: "http://127.0.0.1:8000/v1"},
|
|
{Name: "vllm-b", Enabled: true, Endpoint: "http://127.0.0.1:8001/v1"},
|
|
},
|
|
},
|
|
Providers: []config.NodeProviderConf{
|
|
{ID: "prov-vllm", Type: "vllm", Category: config.CategoryAPI, Adapter: "openai_compat"},
|
|
},
|
|
},
|
|
wantErr: "ambiguous",
|
|
},
|
|
{
|
|
name: "cli resource requires enabled cli",
|
|
node: config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Adapters: config.AdaptersConf{
|
|
OllamaInstances: []config.OllamaInstanceConf{
|
|
{Name: "ollama-local", Enabled: true, BaseURL: "http://127.0.0.1:11434"},
|
|
},
|
|
},
|
|
Providers: []config.NodeProviderConf{
|
|
{ID: "cli-codex", Type: "cli", Category: config.CategoryCLI, Adapter: "cli"},
|
|
},
|
|
},
|
|
wantErr: "category cli requires enabled cli adapter",
|
|
},
|
|
{
|
|
name: "valid cli resource",
|
|
node: config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Adapters: config.AdaptersConf{
|
|
CLI: config.CLIConf{Enabled: true},
|
|
},
|
|
Providers: []config.NodeProviderConf{
|
|
{ID: "cli-codex", Type: "cli", Category: config.CategoryCLI, Adapter: "cli"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
err := ValidateEdgeConfig(&config.EdgeConfig{Nodes: []config.NodeDefinition{tc.node}})
|
|
if tc.wantErr == "" {
|
|
if err != nil {
|
|
t.Fatalf("ValidateEdgeConfig: %v", err)
|
|
}
|
|
return
|
|
}
|
|
if err == nil {
|
|
t.Fatalf("expected error containing %q", tc.wantErr)
|
|
}
|
|
if !strings.Contains(err.Error(), tc.wantErr) {
|
|
t.Fatalf("expected error containing %q, got %v", tc.wantErr, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
func TestValidateEdgeConfigProviderAdapterEmptyRejected(t *testing.T) {
|
|
cfg := &config.EdgeConfig{
|
|
Nodes: []config.NodeDefinition{
|
|
{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Adapters: config.AdaptersConf{
|
|
CLI: config.CLIConf{Enabled: true},
|
|
},
|
|
Providers: []config.NodeProviderConf{
|
|
{ID: "prov-empty", Type: "cli", Category: config.CategoryCLI, Adapter: " "},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
err := ValidateEdgeConfig(cfg)
|
|
if err == nil {
|
|
t.Fatal("expected empty adapter error")
|
|
}
|
|
if !strings.Contains(err.Error(), "adapter must not be empty") {
|
|
t.Fatalf("expected adapter empty error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateEdgeConfig_ProviderFirstWithoutAdapterAllowed(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
node config.NodeDefinition
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "valid adapter-less provider-first openai_compat",
|
|
node: config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Providers: []config.NodeProviderConf{
|
|
{
|
|
ID: "prov-openai",
|
|
Type: "openai_compat",
|
|
Category: config.CategoryAPI,
|
|
Endpoint: "http://127.0.0.1:8000/v1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "valid adapter-less provider-first ollama",
|
|
node: config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Providers: []config.NodeProviderConf{
|
|
{
|
|
ID: "prov-ollama",
|
|
Type: "ollama",
|
|
Category: config.CategoryLocalInference,
|
|
BaseURL: "http://127.0.0.1:11434",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "valid adapter-less provider-first cli",
|
|
node: config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Providers: []config.NodeProviderConf{
|
|
{
|
|
ID: "prov-cli",
|
|
Type: "cli",
|
|
Category: config.CategoryCLI,
|
|
Command: "echo",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "invalid adapter-less provider-first openai_compat (missing endpoint)",
|
|
node: config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Providers: []config.NodeProviderConf{
|
|
{
|
|
ID: "prov-openai",
|
|
Type: "openai_compat",
|
|
Category: config.CategoryAPI,
|
|
},
|
|
},
|
|
},
|
|
wantErr: "adapter must not be empty",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
err := ValidateEdgeConfig(&config.EdgeConfig{Nodes: []config.NodeDefinition{tc.node}})
|
|
if tc.wantErr == "" {
|
|
if err != nil {
|
|
t.Fatalf("ValidateEdgeConfig: %v", err)
|
|
}
|
|
return
|
|
}
|
|
if err == nil {
|
|
t.Fatalf("expected error containing %q", tc.wantErr)
|
|
}
|
|
if !strings.Contains(err.Error(), tc.wantErr) {
|
|
t.Fatalf("expected error containing %q, got %v", tc.wantErr, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateEdgeConfig_ProviderFirstRejectsMissingTypeFields(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
node config.NodeDefinition
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "missing endpoint for openai_compat",
|
|
node: config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Providers: []config.NodeProviderConf{
|
|
{
|
|
ID: "prov-openai",
|
|
Type: "openai_compat",
|
|
Category: config.CategoryAPI,
|
|
},
|
|
},
|
|
},
|
|
wantErr: "adapter must not be empty",
|
|
},
|
|
{
|
|
name: "missing base_url for ollama",
|
|
node: config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Providers: []config.NodeProviderConf{
|
|
{
|
|
ID: "prov-ollama",
|
|
Type: "ollama",
|
|
Category: config.CategoryLocalInference,
|
|
},
|
|
},
|
|
},
|
|
wantErr: "adapter must not be empty",
|
|
},
|
|
{
|
|
name: "missing command for cli",
|
|
node: config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Providers: []config.NodeProviderConf{
|
|
{
|
|
ID: "prov-cli",
|
|
Type: "cli",
|
|
Category: config.CategoryCLI,
|
|
},
|
|
},
|
|
},
|
|
wantErr: "adapter must not be empty",
|
|
},
|
|
{
|
|
name: "openai_compat with base_url only",
|
|
node: config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Providers: []config.NodeProviderConf{
|
|
{
|
|
ID: "prov-openai",
|
|
Type: "openai_compat",
|
|
Category: config.CategoryAPI,
|
|
BaseURL: "http://127.0.0.1:11434",
|
|
},
|
|
},
|
|
},
|
|
wantErr: "openai_compat provider endpoint must not be empty",
|
|
},
|
|
{
|
|
name: "ollama with command only",
|
|
node: config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Providers: []config.NodeProviderConf{
|
|
{
|
|
ID: "prov-ollama",
|
|
Type: "ollama",
|
|
Category: config.CategoryLocalInference,
|
|
Command: "echo",
|
|
},
|
|
},
|
|
},
|
|
wantErr: "ollama provider base_url must not be empty",
|
|
},
|
|
{
|
|
name: "cli with endpoint only",
|
|
node: config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Providers: []config.NodeProviderConf{
|
|
{
|
|
ID: "prov-cli",
|
|
Type: "cli",
|
|
Category: config.CategoryCLI,
|
|
Endpoint: "http://127.0.0.1:8000/v1",
|
|
},
|
|
},
|
|
},
|
|
wantErr: "cli provider command must not be empty",
|
|
},
|
|
{
|
|
name: "whitespace type",
|
|
node: config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Providers: []config.NodeProviderConf{
|
|
{
|
|
ID: "prov-whitespace",
|
|
Type: " ",
|
|
Category: config.CategoryAPI,
|
|
Endpoint: "http://127.0.0.1:8000/v1",
|
|
},
|
|
},
|
|
},
|
|
wantErr: "type must not be empty",
|
|
},
|
|
{
|
|
name: "openai_compat with wrong-field base_url",
|
|
node: config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Providers: []config.NodeProviderConf{
|
|
{
|
|
ID: "prov-openai",
|
|
Type: "openai_compat",
|
|
Category: config.CategoryAPI,
|
|
Endpoint: "http://127.0.0.1:8000/v1",
|
|
BaseURL: "http://127.0.0.1:11434",
|
|
},
|
|
},
|
|
},
|
|
wantErr: "openai_compat provider must not set base_url or command",
|
|
},
|
|
{
|
|
name: "supported aliases vllm valid",
|
|
node: config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Providers: []config.NodeProviderConf{
|
|
{
|
|
ID: "prov-vllm",
|
|
Type: "vllm",
|
|
Category: config.CategoryAPI,
|
|
Endpoint: "http://127.0.0.1:8000/v1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "supported aliases lemonade valid",
|
|
node: config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Providers: []config.NodeProviderConf{
|
|
{
|
|
ID: "prov-lemonade",
|
|
Type: "lemonade",
|
|
Category: config.CategoryAPI,
|
|
Endpoint: "http://127.0.0.1:8000/v1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "supported aliases openai_api valid",
|
|
node: config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Providers: []config.NodeProviderConf{
|
|
{
|
|
ID: "prov-openai-api",
|
|
Type: "openai_api",
|
|
Category: config.CategoryAPI,
|
|
Endpoint: "http://127.0.0.1:8000/v1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "supported aliases sglang valid",
|
|
node: config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Providers: []config.NodeProviderConf{
|
|
{
|
|
ID: "prov-sglang",
|
|
Type: "sglang",
|
|
Category: config.CategoryAPI,
|
|
Endpoint: "http://127.0.0.1:8000/v1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "unknown provider type rejected",
|
|
node: config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Providers: []config.NodeProviderConf{
|
|
{
|
|
ID: "prov-unknown",
|
|
Type: "unknown_provider",
|
|
Category: config.CategoryAPI,
|
|
Endpoint: "http://127.0.0.1:8000/v1",
|
|
},
|
|
},
|
|
},
|
|
wantErr: "unknown provider type",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
err := ValidateEdgeConfig(&config.EdgeConfig{Nodes: []config.NodeDefinition{tc.node}})
|
|
if tc.wantErr == "" {
|
|
if err != nil {
|
|
t.Fatalf("ValidateEdgeConfig: %v", err)
|
|
}
|
|
return
|
|
}
|
|
if err == nil {
|
|
t.Fatalf("expected error containing %q", tc.wantErr)
|
|
}
|
|
if !strings.Contains(err.Error(), tc.wantErr) {
|
|
t.Fatalf("expected error containing %q, got %v", tc.wantErr, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// COMPAT-1: ValidateEdgeConfig edgevalidate entrypoint also rejects mixed config conflicts.
|
|
func TestValidateEdgeConfig_ProviderLegacyConflictViaValidate(t *testing.T) {
|
|
node := config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Adapters: config.AdaptersConf{
|
|
OllamaInstances: []config.OllamaInstanceConf{
|
|
{Name: "ollama", Enabled: true, BaseURL: "http://127.0.0.1:11434"},
|
|
},
|
|
},
|
|
Providers: []config.NodeProviderConf{
|
|
{
|
|
ID: "ollama",
|
|
Type: "ollama",
|
|
Category: config.CategoryLocalInference,
|
|
BaseURL: "http://other-host:11434",
|
|
Models: []string{"llama3:8b"},
|
|
},
|
|
},
|
|
}
|
|
|
|
err := ValidateEdgeConfig(&config.EdgeConfig{Nodes: []config.NodeDefinition{node}})
|
|
if err == nil {
|
|
t.Fatal("expected conflict error from ValidateEdgeConfig")
|
|
}
|
|
if !strings.Contains(err.Error(), "conflicts with adapters") {
|
|
t.Fatalf("expected error containing 'conflicts with adapters', got %v", err)
|
|
}
|
|
}
|
|
|
|
// REVIEW_COMPAT-1: ValidateEdgeConfig capacity mismatch rejects same-key provider-first/legacy
|
|
// when provider capacity is 0 and legacy capacity > 0.
|
|
func TestValidateEdgeConfig_ProviderLegacyCapacityMismatchViaValidate(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
adapterName string
|
|
providerID string
|
|
providerType string
|
|
providerCap int
|
|
legacyCap int
|
|
legacyOpts func(*config.AdaptersConf)
|
|
providerOpts func(*config.NodeProviderConf)
|
|
}{
|
|
{
|
|
name: "ollama provider capacity 0 vs legacy capacity 2",
|
|
adapterName: "ollama",
|
|
providerID: "ollama",
|
|
providerType: "ollama",
|
|
providerCap: 0,
|
|
legacyCap: 2,
|
|
legacyOpts: func(a *config.AdaptersConf) {
|
|
a.OllamaInstances = []config.OllamaInstanceConf{
|
|
{Name: "ollama", Enabled: true, BaseURL: "http://127.0.0.1:11434", Capacity: 2},
|
|
}
|
|
},
|
|
providerOpts: func(p *config.NodeProviderConf) {
|
|
p.BaseURL = "http://127.0.0.1:11434"
|
|
p.Models = []string{"llama3:8b"}
|
|
},
|
|
},
|
|
{
|
|
name: "vllm provider capacity 0 vs legacy capacity 4",
|
|
adapterName: "vllm-gpu",
|
|
providerID: "vllm-gpu",
|
|
providerType: "vllm",
|
|
providerCap: 0,
|
|
legacyCap: 4,
|
|
legacyOpts: func(a *config.AdaptersConf) {
|
|
a.OpenAICompatInstances = []config.OpenAICompatInstanceConf{
|
|
{Name: "vllm-gpu", Enabled: true, Endpoint: "http://127.0.0.1:8000/v1", Capacity: 4},
|
|
}
|
|
},
|
|
providerOpts: func(p *config.NodeProviderConf) {
|
|
p.Endpoint = "http://127.0.0.1:8000/v1"
|
|
p.Models = []string{"model-a"}
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
node := config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Adapters: config.AdaptersConf{},
|
|
Providers: []config.NodeProviderConf{
|
|
{
|
|
ID: tc.providerID,
|
|
Type: tc.providerType,
|
|
Category: config.CategoryLocalInference,
|
|
Capacity: tc.providerCap,
|
|
},
|
|
},
|
|
}
|
|
tc.legacyOpts(&node.Adapters)
|
|
tc.providerOpts(&node.Providers[0])
|
|
|
|
err := ValidateEdgeConfig(&config.EdgeConfig{Nodes: []config.NodeDefinition{node}})
|
|
if err == nil {
|
|
t.Fatal("expected capacity mismatch error from ValidateEdgeConfig")
|
|
}
|
|
if !strings.Contains(err.Error(), "conflicts with adapters") {
|
|
t.Fatalf("expected error containing 'conflicts with adapters', got %v", err)
|
|
}
|
|
if !strings.Contains(err.Error(), "capacity mismatch") {
|
|
t.Fatalf("expected error containing 'capacity mismatch', got %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateEdgeConfig_DisabledProviderUnknownTypeRejected(t *testing.T) {
|
|
disabled := false
|
|
tests := []struct {
|
|
name string
|
|
node config.NodeDefinition
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "disabled provider with unknown type is rejected",
|
|
node: config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Adapters: config.AdaptersConf{
|
|
CLI: config.CLIConf{Enabled: true},
|
|
},
|
|
Providers: []config.NodeProviderConf{
|
|
// enabled provider so the node passes the "at least one adapter" check
|
|
{ID: "prov-active", Type: "cli", Category: config.CategoryCLI, Adapter: "cli"},
|
|
// disabled provider with unknown type must be rejected
|
|
{ID: "prov-disabled", Type: "unknown_provider", Category: config.CategoryAPI, Enabled: &disabled},
|
|
},
|
|
},
|
|
wantErr: "unknown provider type",
|
|
},
|
|
{
|
|
name: "disabled provider with valid type and missing adapter passes",
|
|
node: config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Adapters: config.AdaptersConf{
|
|
CLI: config.CLIConf{Enabled: true},
|
|
},
|
|
Providers: []config.NodeProviderConf{
|
|
{ID: "prov-active", Type: "cli", Category: config.CategoryCLI, Adapter: "cli"},
|
|
// disabled provider: valid type, missing adapter reference must be skipped
|
|
{ID: "prov-disabled", Type: "openai_compat", Category: config.CategoryAPI, Adapter: "ghost-adapter", Enabled: &disabled},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
err := ValidateEdgeConfig(&config.EdgeConfig{Nodes: []config.NodeDefinition{tc.node}})
|
|
if tc.wantErr == "" {
|
|
if err != nil {
|
|
t.Fatalf("ValidateEdgeConfig: %v", err)
|
|
}
|
|
return
|
|
}
|
|
if err == nil {
|
|
t.Fatalf("expected error containing %q", tc.wantErr)
|
|
}
|
|
if !strings.Contains(err.Error(), tc.wantErr) {
|
|
t.Fatalf("expected error containing %q, got %v", tc.wantErr, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateEdgeConfig_DisabledProviderSkipsAdapterRefCheck(t *testing.T) {
|
|
disabled := false
|
|
node := config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Adapters: config.AdaptersConf{
|
|
VllmInstances: []config.VllmInstanceConf{
|
|
{Name: "vllm-gpu", Enabled: true, Endpoint: "http://127.0.0.1:8000/v1"},
|
|
},
|
|
},
|
|
Providers: []config.NodeProviderConf{
|
|
{ID: "prov-active", Type: "vllm", Category: config.CategoryAPI, Adapter: "vllm-gpu", Models: []string{"model-a"}, Capacity: 2},
|
|
// disabled: adapter "ghost-adapter" doesn't exist but must not error
|
|
{ID: "prov-ghost", Type: "vllm", Category: config.CategoryAPI, Adapter: "ghost-adapter", Models: []string{"model-b"}, Enabled: &disabled},
|
|
},
|
|
}
|
|
|
|
if err := ValidateEdgeConfig(&config.EdgeConfig{Nodes: []config.NodeDefinition{node}}); err != nil {
|
|
t.Fatalf("disabled provider with missing adapter should pass validation, got: %v", err)
|
|
}
|
|
}
|
|
|
|
// REVIEW_MOCK: mock-only node passes "at least one adapter" when explicitly enabled.
|
|
func TestValidateEdgeConfig_MockOnlyNode(t *testing.T) {
|
|
node := config.NodeDefinition{
|
|
ID: "node-mock",
|
|
Token: "tok-mock",
|
|
Adapters: config.AdaptersConf{
|
|
Mock: config.MockConf{Enabled: true},
|
|
},
|
|
}
|
|
|
|
err := ValidateEdgeConfig(&config.EdgeConfig{Nodes: []config.NodeDefinition{node}})
|
|
if err != nil {
|
|
t.Fatalf("mock-only node with explicit enabled should pass, got: %v", err)
|
|
}
|
|
}
|
|
|
|
// REVIEW_MOCK: node with only mock config disabled should fail "at least one adapter".
|
|
func TestValidateEdgeConfig_MockDisabledFails(t *testing.T) {
|
|
node := config.NodeDefinition{
|
|
ID: "node-mock",
|
|
Token: "tok-mock",
|
|
Adapters: config.AdaptersConf{
|
|
Mock: config.MockConf{Enabled: false},
|
|
},
|
|
}
|
|
|
|
err := ValidateEdgeConfig(&config.EdgeConfig{Nodes: []config.NodeDefinition{node}})
|
|
if err == nil {
|
|
t.Fatal("expected 'at least one adapter' error for mock-disabled node")
|
|
}
|
|
if !strings.Contains(err.Error(), "at least one adapter") {
|
|
t.Fatalf("expected error mentioning 'at least one adapter', got: %v", err)
|
|
}
|
|
}
|
|
|
|
// REVIEW_REVIEW_MOCK-2: console.adapter references mock without any mock-enabled node fails.
|
|
func TestValidateEdgeConfig_MockSurfaceConsoleNoMockNode(t *testing.T) {
|
|
node := config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Adapters: config.AdaptersConf{
|
|
OllamaInstances: []config.OllamaInstanceConf{
|
|
{Name: "ollama", Enabled: true, BaseURL: "http://127.0.0.1:11434"},
|
|
},
|
|
},
|
|
}
|
|
cfg := &config.EdgeConfig{
|
|
Nodes: []config.NodeDefinition{node},
|
|
Console: config.EdgeConsoleConf{Adapter: "mock"},
|
|
}
|
|
err := ValidateEdgeConfig(cfg)
|
|
if err == nil {
|
|
t.Fatal("expected error for console.adapter: mock without mock-enabled node")
|
|
}
|
|
if !strings.Contains(err.Error(), "mock") || !strings.Contains(err.Error(), "explicit mock") {
|
|
t.Fatalf("expected error mentioning mock, got: %v", err)
|
|
}
|
|
}
|
|
|
|
// REVIEW_REVIEW_MOCK-2: console.adapter references mock with mock-enabled node succeeds.
|
|
func TestValidateEdgeConfig_MockSurfaceConsoleWithMockNode(t *testing.T) {
|
|
node := config.NodeDefinition{
|
|
ID: "node-mock",
|
|
Token: "tok",
|
|
Adapters: config.AdaptersConf{
|
|
Mock: config.MockConf{Enabled: true},
|
|
},
|
|
}
|
|
cfg := &config.EdgeConfig{
|
|
Nodes: []config.NodeDefinition{node},
|
|
Console: config.EdgeConsoleConf{Adapter: "mock"},
|
|
}
|
|
if err := ValidateEdgeConfig(cfg); err != nil {
|
|
t.Fatalf("expected success, got: %v", err)
|
|
}
|
|
}
|
|
|
|
// REVIEW_REVIEW_MOCK-2: openai.model_routes[].adapter references mock with node ref to mock-enabled node succeeds.
|
|
func TestValidateEdgeConfig_MockSurfaceOpenAIRouteWithMockNode(t *testing.T) {
|
|
node := config.NodeDefinition{
|
|
ID: "node-mock",
|
|
Token: "tok",
|
|
Adapters: config.AdaptersConf{
|
|
Mock: config.MockConf{Enabled: true},
|
|
},
|
|
}
|
|
cfg := &config.EdgeConfig{
|
|
Nodes: []config.NodeDefinition{node},
|
|
OpenAI: config.EdgeOpenAIConf{
|
|
Enabled: true,
|
|
ModelRoutes: []config.OpenAIRouteEntry{
|
|
{
|
|
Model: "mock-model",
|
|
NodeRef: "node-mock",
|
|
Adapter: "mock",
|
|
Target: "mock-target",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
if err := ValidateEdgeConfig(cfg); err != nil {
|
|
t.Fatalf("expected success, got: %v", err)
|
|
}
|
|
}
|
|
|
|
// REVIEW_REVIEW_MOCK-2: openai.model_routes[].adapter references mock with node ref to non-mock node fails.
|
|
func TestValidateEdgeConfig_MockSurfaceOpenAIRouteNoMockNode(t *testing.T) {
|
|
node := config.NodeDefinition{
|
|
ID: "node-ollama",
|
|
Token: "tok",
|
|
Adapters: config.AdaptersConf{
|
|
OllamaInstances: []config.OllamaInstanceConf{
|
|
{Name: "ollama", Enabled: true, BaseURL: "http://127.0.0.1:11434"},
|
|
},
|
|
},
|
|
}
|
|
cfg := &config.EdgeConfig{
|
|
Nodes: []config.NodeDefinition{node},
|
|
OpenAI: config.EdgeOpenAIConf{
|
|
Enabled: true,
|
|
ModelRoutes: []config.OpenAIRouteEntry{
|
|
{
|
|
Model: "mock-model",
|
|
NodeRef: "node-ollama",
|
|
Adapter: "mock",
|
|
Target: "mock-target",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
err := ValidateEdgeConfig(cfg)
|
|
if err == nil {
|
|
t.Fatal("expected error for openai.model_routes[].adapter: mock with non-mock node ref")
|
|
}
|
|
if !strings.Contains(err.Error(), "mock") || !strings.Contains(err.Error(), "explicit mock") {
|
|
t.Fatalf("expected error mentioning mock, got: %v", err)
|
|
}
|
|
}
|
|
|
|
// REVIEW_REVIEW_MOCK-2: a2a.adapter references mock with mock-enabled node ref succeeds.
|
|
func TestValidateEdgeConfig_MockSurfaceA2AWithMockNode(t *testing.T) {
|
|
node := config.NodeDefinition{
|
|
ID: "node-mock",
|
|
Token: "tok",
|
|
Adapters: config.AdaptersConf{
|
|
Mock: config.MockConf{Enabled: true},
|
|
},
|
|
}
|
|
cfg := &config.EdgeConfig{
|
|
Nodes: []config.NodeDefinition{node},
|
|
A2A: config.EdgeA2AConf{
|
|
Enabled: true,
|
|
NodeRef: "node-mock",
|
|
Adapter: "mock",
|
|
},
|
|
}
|
|
if err := ValidateEdgeConfig(cfg); err != nil {
|
|
t.Fatalf("expected success, got: %v", err)
|
|
}
|
|
}
|
|
|
|
// REVIEW_REVIEW_MOCK-2: a2a.adapter references mock without mock-enabled node fails.
|
|
func TestValidateEdgeConfig_MockSurfaceA2ANoMockNode(t *testing.T) {
|
|
node := config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Adapters: config.AdaptersConf{
|
|
OllamaInstances: []config.OllamaInstanceConf{
|
|
{Name: "ollama", Enabled: true, BaseURL: "http://127.0.0.1:11434"},
|
|
},
|
|
},
|
|
}
|
|
cfg := &config.EdgeConfig{
|
|
Nodes: []config.NodeDefinition{node},
|
|
A2A: config.EdgeA2AConf{
|
|
Enabled: true,
|
|
NodeRef: "node-1",
|
|
Adapter: "mock",
|
|
},
|
|
}
|
|
err := ValidateEdgeConfig(cfg)
|
|
if err == nil {
|
|
t.Fatal("expected error for a2a.adapter: mock without mock-enabled node")
|
|
}
|
|
if !strings.Contains(err.Error(), "mock") || !strings.Contains(err.Error(), "explicit mock") {
|
|
t.Fatalf("expected error mentioning mock, got: %v", err)
|
|
}
|
|
}
|
|
|
|
// REVIEW_REVIEW_MOCK-2: openai top-level adapter references mock with node ref to mock-enabled node succeeds.
|
|
func TestValidateEdgeConfig_MockSurfaceOpenAITopLevelWithMockNode(t *testing.T) {
|
|
node := config.NodeDefinition{
|
|
ID: "node-mock",
|
|
Token: "tok",
|
|
Adapters: config.AdaptersConf{
|
|
Mock: config.MockConf{Enabled: true},
|
|
},
|
|
}
|
|
cfg := &config.EdgeConfig{
|
|
Nodes: []config.NodeDefinition{node},
|
|
OpenAI: config.EdgeOpenAIConf{
|
|
Enabled: true,
|
|
NodeRef: "node-mock",
|
|
Adapter: "mock",
|
|
Target: "mock-target",
|
|
Models: []string{"mock-model"},
|
|
SessionID: "test",
|
|
},
|
|
}
|
|
if err := ValidateEdgeConfig(cfg); err != nil {
|
|
t.Fatalf("expected success, got: %v", err)
|
|
}
|
|
}
|
|
|
|
// REVIEW_REVIEW_MOCK-2: openai top-level adapter references mock without node ref but hasAnyMock succeeds.
|
|
func TestValidateEdgeConfig_MockSurfaceOpenAITopLevelNoNodeRefWithHasAnyMock(t *testing.T) {
|
|
node := config.NodeDefinition{
|
|
ID: "node-mock",
|
|
Token: "tok",
|
|
Adapters: config.AdaptersConf{
|
|
Mock: config.MockConf{Enabled: true},
|
|
},
|
|
}
|
|
cfg := &config.EdgeConfig{
|
|
Nodes: []config.NodeDefinition{node},
|
|
OpenAI: config.EdgeOpenAIConf{
|
|
Enabled: true,
|
|
Adapter: "mock",
|
|
Target: "mock-target",
|
|
Models: []string{"mock-model"},
|
|
SessionID: "test",
|
|
},
|
|
}
|
|
if err := ValidateEdgeConfig(cfg); err != nil {
|
|
t.Fatalf("expected success, got: %v", err)
|
|
}
|
|
}
|
|
|
|
// REVIEW_REVIEW_MOCK-2: openai top-level adapter references mock with alias-based node ref succeeds.
|
|
func TestValidateEdgeConfig_MockSurfaceOpenAITopLevelAliasRef(t *testing.T) {
|
|
node := config.NodeDefinition{
|
|
ID: "node-mock",
|
|
Alias: "mock-alias",
|
|
Token: "tok",
|
|
Adapters: config.AdaptersConf{
|
|
Mock: config.MockConf{Enabled: true},
|
|
},
|
|
}
|
|
cfg := &config.EdgeConfig{
|
|
Nodes: []config.NodeDefinition{node},
|
|
OpenAI: config.EdgeOpenAIConf{
|
|
Enabled: true,
|
|
NodeRef: "mock-alias",
|
|
Adapter: "mock",
|
|
Target: "mock-target",
|
|
Models: []string{"mock-model"},
|
|
SessionID: "test",
|
|
},
|
|
}
|
|
if err := ValidateEdgeConfig(cfg); err != nil {
|
|
t.Fatalf("expected success with alias ref, got: %v", err)
|
|
}
|
|
}
|
|
|
|
// REVIEW_REVIEW_MOCK-2: a2a adapter references mock without node ref but hasAnyMock succeeds.
|
|
func TestValidateEdgeConfig_MockSurfaceA2ANoNodeRefWithHasAnyMock(t *testing.T) {
|
|
node := config.NodeDefinition{
|
|
ID: "node-mock",
|
|
Token: "tok",
|
|
Adapters: config.AdaptersConf{
|
|
Mock: config.MockConf{Enabled: true},
|
|
},
|
|
}
|
|
cfg := &config.EdgeConfig{
|
|
Nodes: []config.NodeDefinition{node},
|
|
A2A: config.EdgeA2AConf{
|
|
Enabled: true,
|
|
Adapter: "mock",
|
|
},
|
|
}
|
|
if err := ValidateEdgeConfig(cfg); err != nil {
|
|
t.Fatalf("expected success, got: %v", err)
|
|
}
|
|
}
|
|
|
|
// REVIEW_REVIEW_MOCK-2: openai top-level adapter references mock without node ref and no mock node fails.
|
|
func TestValidateEdgeConfig_MockSurfaceOpenAITopLevelNoNodeRefNoMock(t *testing.T) {
|
|
node := config.NodeDefinition{
|
|
ID: "node-1",
|
|
Token: "tok",
|
|
Adapters: config.AdaptersConf{
|
|
OllamaInstances: []config.OllamaInstanceConf{
|
|
{Name: "ollama", Enabled: true, BaseURL: "http://127.0.0.1:11434"},
|
|
},
|
|
},
|
|
}
|
|
cfg := &config.EdgeConfig{
|
|
Nodes: []config.NodeDefinition{node},
|
|
OpenAI: config.EdgeOpenAIConf{
|
|
Enabled: true,
|
|
Adapter: "mock",
|
|
Target: "mock-target",
|
|
Models: []string{"mock-model"},
|
|
SessionID: "test",
|
|
},
|
|
}
|
|
err := ValidateEdgeConfig(cfg)
|
|
if err == nil {
|
|
t.Fatal("expected error for openai top-level adapter: mock with no node ref and no mock-enabled node")
|
|
}
|
|
if !strings.Contains(err.Error(), "mock") || !strings.Contains(err.Error(), "explicit mock") {
|
|
t.Fatalf("expected error mentioning mock, got: %v", err)
|
|
}
|
|
}
|