iop/apps/edge/internal/bootstrap/runtime_test_support_test.go
toki 01dc2ef78b refactor: readability baseline 및 테스트 구조 개선
- agent-readable-repository-refactor 완료: 기존 테스트 파일 아카이브 이동
- 새 테스트 파일 추가 (edge, node, client, config, readability)
- readability_audit 스크립트 및 baseline 추가
- roadmap/SDD 문서 갱신
- agent-client/pi/extensions/openai-sampling-parameters 추가
2026-07-17 16:02:12 +09:00

192 lines
4.6 KiB
Go

package bootstrap
import (
"encoding/json"
"fmt"
"net"
"net/http"
"os"
"path/filepath"
"sort"
"testing"
"iop/packages/go/config"
)
func newTestConfig() *config.EdgeConfig {
return &config.EdgeConfig{
Server: config.EdgeServerConf{Listen: "127.0.0.1:0"},
Logging: config.LoggingConf{Level: "error"},
Metrics: config.MetricsConf{Port: 0},
Nodes: []config.NodeDefinition{
{ID: "node-1", Alias: "alias-1", Token: "tok-1"},
},
}
}
func serveNormalizedLogPath() string {
if exe, err := os.Executable(); err == nil {
return filepath.Join(filepath.Dir(exe), "logs", "edge.log")
}
if wd, err := os.Getwd(); err == nil {
return filepath.Join(wd, "logs", "edge.log")
}
return "logs/edge.log"
}
func serveNormalizedArtifactDir(dir string) string {
if dir == "" {
dir = "artifacts"
}
if filepath.IsAbs(dir) {
return dir
}
bd := binaryDirForTest()
return filepath.Join(bd, dir)
}
func binaryDirForTest() string {
if exe, err := os.Executable(); err == nil {
return filepath.Dir(exe)
}
if wd, err := os.Getwd(); err == nil {
return wd
}
return "."
}
// loadServeNormalizedConfig loads an Edge config and applies the same
// normalization iop-edge serve performs, so refresh classification compares
// effective values.
func loadServeNormalizedConfig(t *testing.T, path string) *config.EdgeConfig {
t.Helper()
cfg, err := config.LoadEdge(path)
if err != nil {
t.Fatalf("load config %s: %v", path, err)
}
cfg.Logging.Path = serveNormalizedLogPath()
cfg.Bootstrap.ArtifactDir = serveNormalizedArtifactDir(cfg.Bootstrap.ArtifactDir)
return cfg
}
// providerPoolConfigYAML builds an Edge config with one provider-pool model and
// one node provider. The extra parameter lets callers inject a candidate
// variation (e.g. a different capacity or an invalid adapter block).
func providerPoolConfigYAML(serverAddr, openAIAddr, providerBlock string) string {
return fmt.Sprintf(`
server:
listen: %q
bootstrap:
listen: "0.0.0.0:18080"
artifact_dir: "artifacts"
logging:
level: "error"
refresh:
enabled: true
listen: "127.0.0.1:0"
openai:
enabled: true
listen: %q
adapter: "openai_compat"
target: ""
a2a:
listen: "0.0.0.0:8081"
metrics:
port: 0
models:
- id: "qwen3.6:35b"
display_name: "Qwen"
providers:
prov-a: "served-qwen"
nodes:
- id: "node-1"
alias: "n1"
token: "tok-1"
adapters:
%s
providers:
- id: "prov-a"
type: "vllm"
category: "api"
adapter: "vllm-gpu"
models: ["served-qwen"]
health: "available"
capacity: 2
max_queue: 4
queue_timeout_ms: 5000
`, serverAddr, openAIAddr, providerBlock)
}
const validAdapterBlock = ` openai_compat_instances:
- name: "vllm-gpu"
enabled: true
provider: "vllm"
endpoint: "http://127.0.0.1:8000/v1"`
// invalidAdapterBlock enables an openai_compat instance but leaves endpoint
// empty, which ValidateEdgeConfig rejects.
const invalidAdapterBlock = ` openai_compat_instances:
- name: "vllm-gpu"
enabled: true
provider: "vllm"
endpoint: ""`
// modelsResponse represents the JSON response from /v1/models.
type modelsResponse struct {
Object string `json:"object"`
Data []modelItem `json:"data"`
}
// modelItem represents a single model entry in the /v1/models response.
type modelItem struct {
ID string `json:"id"`
Object string `json:"object"`
Created int64 `json:"created"`
OwnedBy string `json:"owned_by"`
}
// modelIDs returns the sorted list of model IDs from /v1/models.
// This avoids flaky timestamp comparisons on the `created` field.
func modelIDs(t *testing.T, client *http.Client, openAIAddr string) []string {
t.Helper()
resp, err := client.Get("http://" + openAIAddr + "/v1/models")
if err != nil {
t.Fatalf("GET /v1/models: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("GET /v1/models status=%d", resp.StatusCode)
}
var body modelsResponse
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
t.Fatalf("decode /v1/models body: %v", err)
}
ids := make([]string, 0, len(body.Data))
for _, m := range body.Data {
ids = append(ids, m.ID)
}
sort.Strings(ids)
return ids
}
func containsString(s []string, want string) bool {
for _, v := range s {
if v == want {
return true
}
}
return false
}
func freeTCPAddr(t *testing.T) string {
t.Helper()
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("listen free tcp addr: %v", err)
}
addr := ln.Addr().String()
if err := ln.Close(); err != nil {
t.Fatalf("close free tcp addr listener: %v", err)
}
return addr
}