Files
zzet--gortex/internal/resolver/module_attribution_test.go
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

191 lines
6.0 KiB
Go

package resolver
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zzet/gortex/internal/graph"
)
// seedFile adds a KindFile node with the given language to the
// graph; tests use it to drive the language-aware attribution pass.
func seedFile(g graph.Store, fileID, language string) {
g.AddNode(&graph.Node{
ID: fileID, Kind: graph.KindFile, Name: fileID,
FilePath: fileID, Language: language,
})
}
// seedExternalImport drops in an EdgeImports edge that's already
// landed at an `external::*` target — the post-pass inputs we want
// to exercise.
func seedExternalImport(g graph.Store, fileID, importPath string) *graph.Edge {
e := &graph.Edge{
From: fileID,
To: "external::" + importPath,
Kind: graph.EdgeImports,
FilePath: fileID,
Line: 1,
}
g.AddEdge(e)
return e
}
func TestAttributeNonGo_PythonPyPI(t *testing.T) {
g := graph.New()
seedFile(g, "app/main.py", "python")
e := seedExternalImport(g, "app/main.py", "requests")
r := New(g)
r.attributeNonGoModuleImports()
assert.Equal(t, "module::pypi:requests", e.To, "import edge should retarget onto KindModule")
mod := g.GetNode("module::pypi:requests")
require.NotNil(t, mod, "KindModule node should be materialised")
assert.Equal(t, graph.KindModule, mod.Kind)
assert.Equal(t, "pypi", mod.Meta["ecosystem"])
depEdges := outEdgesOfKind(g, "app/main.py", graph.EdgeDependsOnModule)
require.Len(t, depEdges, 1, "exactly one EdgeDependsOnModule per (file, module) pair")
assert.Equal(t, "module::pypi:requests", depEdges[0].To)
}
func TestAttributeNonGo_PythonStdlibCollapsesToTopLevel(t *testing.T) {
g := graph.New()
seedFile(g, "app/main.py", "python")
// `os.path` should attribute to `os` (the top-level module),
// not to `os.path` — a `KindModule` is the package, not the
// dotted path.
e := seedExternalImport(g, "app/main.py", "os.path")
r := New(g)
r.attributeNonGoModuleImports()
assert.Equal(t, "module::python:stdlib::os", e.To)
mod := g.GetNode("module::python:stdlib::os")
require.NotNil(t, mod)
assert.Equal(t, "python:stdlib", mod.Meta["ecosystem"])
assert.Equal(t, "stdlib", mod.Meta["role"])
// import_path keeps the original dotted form so consumers can
// recover the sub-module reference.
assert.Equal(t, "os.path", mod.Meta["import_path"])
}
func TestAttributeNonGo_PythonRelativeImportsSkipped(t *testing.T) {
g := graph.New()
seedFile(g, "app/main.py", "python")
// `from . import foo` lands as `external::.` — we can't
// attribute relative imports without package-layout context,
// so the pass leaves them alone.
e := seedExternalImport(g, "app/main.py", ".foo.bar")
r := New(g)
r.attributeNonGoModuleImports()
assert.Equal(t, "external::.foo.bar", e.To, "relative import must remain unchanged")
depEdges := outEdgesOfKind(g, "app/main.py", graph.EdgeDependsOnModule)
assert.Empty(t, depEdges)
}
func TestAttributeNonGo_DartPackageURI(t *testing.T) {
g := graph.New()
seedFile(g, "lib/main.dart", "dart")
e := seedExternalImport(g, "lib/main.dart", "package:flutter/material.dart")
r := New(g)
r.attributeNonGoModuleImports()
assert.Equal(t, "module::pub:flutter", e.To, "package: URI maps to its top-level pub package")
mod := g.GetNode("module::pub:flutter")
require.NotNil(t, mod)
assert.Equal(t, "pub", mod.Meta["ecosystem"])
}
func TestAttributeNonGo_DartCoreLibraryStdlib(t *testing.T) {
g := graph.New()
seedFile(g, "lib/main.dart", "dart")
e := seedExternalImport(g, "lib/main.dart", "dart:async")
r := New(g)
r.attributeNonGoModuleImports()
assert.Equal(t, "module::dart:stdlib::async", e.To)
mod := g.GetNode("module::dart:stdlib::async")
require.NotNil(t, mod)
assert.Equal(t, "stdlib", mod.Meta["role"])
}
func TestAttributeNonGo_DartRelativeImportsSkipped(t *testing.T) {
g := graph.New()
seedFile(g, "lib/main.dart", "dart")
e := seedExternalImport(g, "lib/main.dart", "models/user.dart")
r := New(g)
r.attributeNonGoModuleImports()
assert.Equal(t, "external::models/user.dart", e.To,
"relative-path Dart imports stay external until layout-aware resolution lands")
}
func TestAttributeNonGo_OtherLanguagesUntouched(t *testing.T) {
g := graph.New()
// Go imports are handled by the dep-module bridge plus
// goanalysis; this pass must not interfere with them.
seedFile(g, "main.go", "go")
e := seedExternalImport(g, "main.go", "github.com/foo/bar")
r := New(g)
r.attributeNonGoModuleImports()
assert.Equal(t, "external::github.com/foo/bar", e.To)
}
func TestAttributeNonGo_DeduplicatesAcrossManyImports(t *testing.T) {
g := graph.New()
seedFile(g, "app/a.py", "python")
// Three Python files all importing two distinct sub-modules
// of `numpy`. The pass must yield exactly one
// EdgeDependsOnModule per (file, module) pair, and a single
// shared `module::pypi:numpy` node.
for _, p := range []string{"numpy", "numpy.linalg", "numpy.random"} {
seedExternalImport(g, "app/a.py", p)
}
r := New(g)
r.attributeNonGoModuleImports()
mod := g.GetNode("module::pypi:numpy")
require.NotNil(t, mod)
deps := outEdgesOfKind(g, "app/a.py", graph.EdgeDependsOnModule)
require.Len(t, deps, 1, "three sub-imports should collapse to one EdgeDependsOnModule")
assert.Equal(t, "module::pypi:numpy", deps[0].To)
}
func TestAttributeNonGo_IdempotentOnSecondPass(t *testing.T) {
g := graph.New()
seedFile(g, "app/main.py", "python")
seedExternalImport(g, "app/main.py", "requests")
r := New(g)
r.attributeNonGoModuleImports()
r.attributeNonGoModuleImports() // run again — must not duplicate.
deps := outEdgesOfKind(g, "app/main.py", graph.EdgeDependsOnModule)
require.Len(t, deps, 1, "second pass must not duplicate EdgeDependsOnModule")
}
// outEdgesOfKind is a small filter over Graph.GetOutEdges for the
// assertions above; declared here to keep the test file self-
// contained.
func outEdgesOfKind(g graph.Store, fileID string, kind graph.EdgeKind) []*graph.Edge {
var out []*graph.Edge
for _, e := range g.GetOutEdges(fileID) {
if e.Kind == kind {
out = append(out, e)
}
}
return out
}