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
147 lines
5.8 KiB
Go
147 lines
5.8 KiB
Go
package resolver
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/zzet/gortex/internal/graph"
|
|
)
|
|
|
|
// addDepNode is a tiny helper to materialise a dep::<module> contract
|
|
// node the way GoModExtractor + commitInlinedContractToGraph would.
|
|
func addDepNode(t *testing.T, g graph.Store, repoPrefix, modulePath string) {
|
|
t.Helper()
|
|
g.AddNode(&graph.Node{
|
|
ID: "dep::" + modulePath,
|
|
Kind: graph.KindContract,
|
|
Name: "dep::" + modulePath,
|
|
FilePath: repoPrefix + "/go.mod",
|
|
Language: "contract",
|
|
RepoPrefix: repoPrefix,
|
|
})
|
|
}
|
|
|
|
// Sub-package import: importing a path under a declared module's
|
|
// directory should resolve to the dep::<module> contract node.
|
|
// This is the original bug — internal/parser/tsitter/sql/sql.go
|
|
// imports "github.com/gortexhq/tree-sitter-sql/bindings/go" and
|
|
// dep::github.com/gortexhq/tree-sitter-sql had zero incoming edges.
|
|
func TestResolveAll_DepBridge_SubPackageImport(t *testing.T) {
|
|
g := graph.New()
|
|
g.AddNode(&graph.Node{ID: "repo/internal/x.go", Kind: graph.KindFile, Name: "x.go", FilePath: "repo/internal/x.go", Language: "go", RepoPrefix: "repo"})
|
|
addDepNode(t, g, "repo", "github.com/foo/bar")
|
|
|
|
importEdge := &graph.Edge{
|
|
From: "repo/internal/x.go",
|
|
To: "unresolved::import::github.com/foo/bar/sub/pkg",
|
|
Kind: graph.EdgeImports,
|
|
FilePath: "repo/internal/x.go",
|
|
Line: 3,
|
|
}
|
|
g.AddEdge(importEdge)
|
|
|
|
r := New(g)
|
|
stats := r.ResolveAll()
|
|
|
|
assert.Equal(t, 1, stats.Resolved)
|
|
assert.Equal(t, "dep::github.com/foo/bar", importEdge.To)
|
|
}
|
|
|
|
// Bare import equal to the module path also resolves to the dep node.
|
|
func TestResolveAll_DepBridge_BareModuleImport(t *testing.T) {
|
|
g := graph.New()
|
|
g.AddNode(&graph.Node{ID: "repo/x.go", Kind: graph.KindFile, Name: "x.go", FilePath: "repo/x.go", Language: "go", RepoPrefix: "repo"})
|
|
addDepNode(t, g, "repo", "github.com/foo/bar")
|
|
|
|
e := &graph.Edge{From: "repo/x.go", To: "unresolved::import::github.com/foo/bar", Kind: graph.EdgeImports, FilePath: "repo/x.go", Line: 3}
|
|
g.AddEdge(e)
|
|
|
|
r := New(g)
|
|
stats := r.ResolveAll()
|
|
|
|
assert.Equal(t, 1, stats.Resolved)
|
|
assert.Equal(t, "dep::github.com/foo/bar", e.To)
|
|
}
|
|
|
|
// When a parent module and a nested module are both declared, the
|
|
// longer (more specific) module path must win — otherwise importing
|
|
// "github.com/aws/aws-sdk-go-v2/service/s3/types" would attribute to
|
|
// the parent aws-sdk-go-v2 dep instead of the s3 sub-module dep.
|
|
func TestResolveAll_DepBridge_LongestPrefixWins(t *testing.T) {
|
|
g := graph.New()
|
|
g.AddNode(&graph.Node{ID: "repo/x.go", Kind: graph.KindFile, Name: "x.go", FilePath: "repo/x.go", Language: "go", RepoPrefix: "repo"})
|
|
addDepNode(t, g, "repo", "github.com/aws/aws-sdk-go-v2")
|
|
addDepNode(t, g, "repo", "github.com/aws/aws-sdk-go-v2/service/s3")
|
|
|
|
e := &graph.Edge{From: "repo/x.go", To: "unresolved::import::github.com/aws/aws-sdk-go-v2/service/s3/types", Kind: graph.EdgeImports, FilePath: "repo/x.go", Line: 3}
|
|
g.AddEdge(e)
|
|
|
|
r := New(g)
|
|
stats := r.ResolveAll()
|
|
|
|
assert.Equal(t, 1, stats.Resolved)
|
|
assert.Equal(t, "dep::github.com/aws/aws-sdk-go-v2/service/s3", e.To)
|
|
}
|
|
|
|
// A module path is a prefix only when the next character is '/' or
|
|
// the strings are equal — `foo/bar` must not satisfy import `foo/barbaz`.
|
|
func TestResolveAll_DepBridge_NoFalsePositivePathComponent(t *testing.T) {
|
|
g := graph.New()
|
|
g.AddNode(&graph.Node{ID: "repo/x.go", Kind: graph.KindFile, Name: "x.go", FilePath: "repo/x.go", Language: "go", RepoPrefix: "repo"})
|
|
addDepNode(t, g, "repo", "github.com/foo/bar")
|
|
|
|
e := &graph.Edge{From: "repo/x.go", To: "unresolved::import::github.com/foo/barbaz", Kind: graph.EdgeImports, FilePath: "repo/x.go", Line: 3}
|
|
g.AddEdge(e)
|
|
|
|
r := New(g)
|
|
stats := r.ResolveAll()
|
|
|
|
// Should fall through to external::, not match dep::.../bar.
|
|
assert.Equal(t, 0, stats.Resolved)
|
|
assert.Equal(t, 1, stats.External)
|
|
assert.Equal(t, "external::github.com/foo/barbaz", e.To)
|
|
}
|
|
|
|
// A dep declared by repo A's go.mod must not satisfy an import in
|
|
// repo B even if the module path matches — each go.mod scopes its
|
|
// own dep nodes.
|
|
func TestResolveAll_DepBridge_RepoScoped(t *testing.T) {
|
|
g := graph.New()
|
|
// File lives in repoB; dep node lives under repoA.
|
|
g.AddNode(&graph.Node{ID: "repoB/x.go", Kind: graph.KindFile, Name: "x.go", FilePath: "repoB/x.go", Language: "go", RepoPrefix: "repoB"})
|
|
addDepNode(t, g, "repoA", "github.com/foo/bar")
|
|
|
|
e := &graph.Edge{From: "repoB/x.go", To: "unresolved::import::github.com/foo/bar", Kind: graph.EdgeImports, FilePath: "repoB/x.go", Line: 3}
|
|
g.AddEdge(e)
|
|
|
|
r := New(g)
|
|
stats := r.ResolveAll()
|
|
|
|
assert.Equal(t, 0, stats.Resolved)
|
|
assert.Equal(t, 1, stats.External)
|
|
assert.Equal(t, "external::github.com/foo/bar", e.To)
|
|
}
|
|
|
|
// Same coverage on the cross-repo resolver: caller in repoB with a
|
|
// dep declared by repoA must not bridge; caller in the dep's own
|
|
// repo must.
|
|
func TestCrossRepoResolveAll_DepBridge(t *testing.T) {
|
|
g := graph.New()
|
|
g.AddNode(&graph.Node{ID: "repoA/x.go", Kind: graph.KindFile, Name: "x.go", FilePath: "repoA/x.go", Language: "go", RepoPrefix: "repoA"})
|
|
g.AddNode(&graph.Node{ID: "repoB/y.go", Kind: graph.KindFile, Name: "y.go", FilePath: "repoB/y.go", Language: "go", RepoPrefix: "repoB"})
|
|
addDepNode(t, g, "repoA", "github.com/foo/bar")
|
|
|
|
bridged := &graph.Edge{From: "repoA/x.go", To: "unresolved::import::github.com/foo/bar/sub", Kind: graph.EdgeImports, FilePath: "repoA/x.go", Line: 3}
|
|
g.AddEdge(bridged)
|
|
stranded := &graph.Edge{From: "repoB/y.go", To: "unresolved::import::github.com/foo/bar/sub", Kind: graph.EdgeImports, FilePath: "repoB/y.go", Line: 3}
|
|
g.AddEdge(stranded)
|
|
|
|
cr := NewCrossRepo(g)
|
|
stats := cr.ResolveAll()
|
|
|
|
require.NotNil(t, stats)
|
|
assert.Equal(t, "dep::github.com/foo/bar", bridged.To, "caller in dep's own repo bridges")
|
|
assert.Equal(t, "external::github.com/foo/bar/sub", stranded.To, "caller in foreign repo stays external")
|
|
}
|