a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
219 lines
5.7 KiB
Go
219 lines
5.7 KiB
Go
package languages
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/zzet/gortex/internal/graph"
|
|
)
|
|
|
|
func TestGoConfigs_ViperGetReadEdge(t *testing.T) {
|
|
src := `package foo
|
|
|
|
import _ "github.com/spf13/viper"
|
|
|
|
type Viper struct{}
|
|
|
|
func (v *Viper) GetString(key string) string { return "" }
|
|
func (v *Viper) GetInt(key string) int { return 0 }
|
|
|
|
func Run(v *Viper) {
|
|
_ = v.GetString("server.port")
|
|
_ = v.GetInt("server.timeout")
|
|
}
|
|
`
|
|
fix := runGoExtract(t, src)
|
|
|
|
keys := fix.nodesByKind[graph.KindConfigKey]
|
|
if len(keys) != 2 {
|
|
t.Fatalf("expected 2 KindConfigKey, got %d: %+v", len(keys), keys)
|
|
}
|
|
|
|
gotIDs := map[string]bool{}
|
|
for _, k := range keys {
|
|
gotIDs[k.ID] = true
|
|
if s, _ := k.Meta["source"].(string); s != "viper" {
|
|
t.Errorf("source = %q", s)
|
|
}
|
|
}
|
|
if !gotIDs["cfg::viper::server.port"] || !gotIDs["cfg::viper::server.timeout"] {
|
|
t.Errorf("missing key IDs: %v", gotIDs)
|
|
}
|
|
|
|
reads := fix.edgesByKind[graph.EdgeReadsConfig]
|
|
if len(reads) != 2 {
|
|
t.Errorf("expected 2 EdgeReadsConfig, got %d", len(reads))
|
|
}
|
|
for _, e := range reads {
|
|
if op, _ := e.Meta["op"].(string); op != "read" {
|
|
t.Errorf("op meta = %q", op)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGoConfigs_SetEmitsWriteEdge(t *testing.T) {
|
|
src := `package foo
|
|
|
|
import _ "github.com/spf13/viper"
|
|
|
|
type Viper struct{}
|
|
|
|
func (v *Viper) Set(key string, val any) {}
|
|
func (v *Viper) SetDefault(key string, val any) {}
|
|
|
|
func Run(v *Viper) {
|
|
v.Set("server.port", 8080)
|
|
v.SetDefault("server.timeout", 30)
|
|
}
|
|
`
|
|
fix := runGoExtract(t, src)
|
|
|
|
writes := fix.edgesByKind[graph.EdgeWritesConfig]
|
|
if len(writes) != 2 {
|
|
t.Fatalf("expected 2 EdgeWritesConfig, got %d", len(writes))
|
|
}
|
|
for _, e := range writes {
|
|
if op, _ := e.Meta["op"].(string); op != "write" {
|
|
t.Errorf("op = %q", op)
|
|
}
|
|
}
|
|
// No reads should fire on this fixture.
|
|
if got := len(fix.edgesByKind[graph.EdgeReadsConfig]); got != 0 {
|
|
t.Errorf("expected 0 reads, got %d", got)
|
|
}
|
|
}
|
|
|
|
func TestGoConfigs_BindEnvEmitsRegister(t *testing.T) {
|
|
src := `package foo
|
|
|
|
import _ "github.com/spf13/viper"
|
|
|
|
type Viper struct{}
|
|
|
|
func (v *Viper) BindEnv(key string) error { return nil }
|
|
|
|
func Run(v *Viper) {
|
|
_ = v.BindEnv("server.port")
|
|
}
|
|
`
|
|
fix := runGoExtract(t, src)
|
|
|
|
// Register operations route through reads-config (the spec
|
|
// classifies BindEnv as "register" but it surfaces as an edge
|
|
// kind that read-side queries pick up).
|
|
reads := fix.edgesByKind[graph.EdgeReadsConfig]
|
|
if len(reads) != 1 {
|
|
t.Fatalf("expected 1 read-style edge for BindEnv, got %d", len(reads))
|
|
}
|
|
if op, _ := reads[0].Meta["op"].(string); op != "register" {
|
|
t.Errorf("BindEnv op meta should be 'register', got %q", op)
|
|
}
|
|
}
|
|
|
|
func TestGoConfigs_DynamicKeySkipped(t *testing.T) {
|
|
src := `package foo
|
|
|
|
import _ "github.com/spf13/viper"
|
|
|
|
type Viper struct{}
|
|
|
|
func (v *Viper) GetString(key string) string { return "" }
|
|
|
|
func Run(v *Viper, dynamic string) {
|
|
_ = v.GetString(dynamic)
|
|
}
|
|
`
|
|
fix := runGoExtract(t, src)
|
|
if got := len(fix.nodesByKind[graph.KindConfigKey]); got != 0 {
|
|
t.Errorf("dynamic key should not produce KindConfigKey, got %d", got)
|
|
}
|
|
}
|
|
|
|
func TestGoConfigs_NonViperGetIgnored(t *testing.T) {
|
|
// `Cache.Get` shares the bare method name 'Get' with viper.Get.
|
|
// Files that don't import viper are gated out so a domain
|
|
// type's `Get` is no longer classified as a viper read. The
|
|
// previous behaviour (acknowledged false positive) silently
|
|
// polluted graph diffs in any file that called `<x>.GetString`
|
|
// against unrelated types (mcp.CallToolRequest, sync.Map, …).
|
|
src := `package foo
|
|
|
|
type Cache struct{}
|
|
|
|
func (c *Cache) Get(key string) any { return nil }
|
|
|
|
func Run(c *Cache) {
|
|
_ = c.Get("user_123")
|
|
}
|
|
`
|
|
fix := runGoExtract(t, src)
|
|
if got := len(fix.nodesByKind[graph.KindConfigKey]); got != 0 {
|
|
t.Errorf("no viper import: Cache.Get should not classify as a viper read, got %d", got)
|
|
}
|
|
}
|
|
|
|
func TestGoConfigs_NonViperGetStringIgnoredWithoutImport(t *testing.T) {
|
|
// Regression pin for the preview_edit false-positive: a
|
|
// non-viper type with a GetString method must not produce a
|
|
// KindConfigKey when the file doesn't import viper. The actual
|
|
// repro that surfaced this was `req.GetString("query")` against
|
|
// an mcp.CallToolRequest in tools_search.go.
|
|
src := `package mcp
|
|
|
|
type Request struct{}
|
|
|
|
func (r Request) GetString(name, def string) string { return def }
|
|
|
|
func Handle(req Request) {
|
|
_ = req.GetString("query", "")
|
|
_ = req.GetString("promote", "true")
|
|
_ = req.GetString("max_results", "10")
|
|
}
|
|
`
|
|
fix := runGoExtract(t, src)
|
|
if got := len(fix.nodesByKind[graph.KindConfigKey]); got != 0 {
|
|
t.Errorf("Request.GetString without viper import must not classify, got %d", got)
|
|
}
|
|
}
|
|
|
|
func TestGoConfigs_UnrelatedTypedGetIgnored(t *testing.T) {
|
|
// `Cache.GetItem` does NOT match because GetItem isn't in the
|
|
// viper allowlist — the strict enumeration is the false-
|
|
// positive guard. This test pins the strictness.
|
|
src := `package foo
|
|
|
|
type Cache struct{}
|
|
|
|
func (c *Cache) GetItem(key string) any { return nil }
|
|
|
|
func Run(c *Cache) {
|
|
_ = c.GetItem("user_123")
|
|
}
|
|
`
|
|
fix := runGoExtract(t, src)
|
|
if got := len(fix.nodesByKind[graph.KindConfigKey]); got != 0 {
|
|
t.Errorf("Cache.GetItem must not match — strict allowlist is the guard, got %d", got)
|
|
}
|
|
}
|
|
|
|
func TestGoConfigs_DuplicateKeyDeduplicates(t *testing.T) {
|
|
src := `package foo
|
|
|
|
import _ "github.com/spf13/viper"
|
|
|
|
type Viper struct{}
|
|
|
|
func (v *Viper) GetString(key string) string { return "" }
|
|
|
|
func A(v *Viper) { _ = v.GetString("server.port") }
|
|
func B(v *Viper) { _ = v.GetString("server.port") }
|
|
`
|
|
fix := runGoExtract(t, src)
|
|
keys := fix.nodesByKind[graph.KindConfigKey]
|
|
if len(keys) != 1 {
|
|
t.Errorf("expected 1 deduped key node, got %d", len(keys))
|
|
}
|
|
if got := len(fix.edgesByKind[graph.EdgeReadsConfig]); got != 2 {
|
|
t.Errorf("expected 2 read edges (one per call site), got %d", got)
|
|
}
|
|
}
|