Files
zzet--gortex/internal/indexer/multi_singlerepo_resolve_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

147 lines
5.4 KiB
Go

package indexer
import (
"context"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"github.com/zzet/gortex/internal/config"
"github.com/zzet/gortex/internal/graph"
"github.com/zzet/gortex/internal/search"
)
func indexSingleRepoForTest(t *testing.T) (*MultiIndexer, string) {
t.Helper()
dir := setupRepoDir(t, "myrepo")
tmpCfg := filepath.Join(t.TempDir(), "config.yaml")
gc := &config.GlobalConfig{
Repos: []config.RepoEntry{{Path: dir, Name: "myrepo"}},
}
gc.SetConfigPath(tmpCfg)
require.NoError(t, gc.Save())
cm, err := config.NewConfigManager(tmpCfg)
require.NoError(t, err)
g := graph.New()
mi := NewMultiIndexer(g, newTestRegistry(), search.NewBM25(), cm, zap.NewNop())
_, err = mi.IndexAll()
require.NoError(t, err)
return mi, dir
}
func indexTwoReposForTest(t *testing.T) (*MultiIndexer, string, string) {
t.Helper()
repoA := setupRepoDir(t, "repo-a")
repoB := setupRepoDir(t, "repo-b")
tmpCfg := filepath.Join(t.TempDir(), "config.yaml")
gc := &config.GlobalConfig{
Repos: []config.RepoEntry{
{Path: repoA, Name: "repo-a"},
{Path: repoB, Name: "repo-b"},
},
}
gc.SetConfigPath(tmpCfg)
require.NoError(t, gc.Save())
cm, err := config.NewConfigManager(tmpCfg)
require.NoError(t, err)
g := graph.New()
mi := NewMultiIndexer(g, newTestRegistry(), search.NewBM25(), cm, zap.NewNop())
_, err = mi.IndexAll()
require.NoError(t, err)
return mi, repoA, repoB
}
// Single-repo mode indexes nodes and file paths without a repo prefix
// while registering the repo's metadata under its real prefix. The empty
// prefix must therefore resolve to the lone repo — otherwise every node
// the single-repo indexer mints is unresolvable (no source reads, no
// savings recording, no editing by graph path).
func TestMultiIndexer_RepoRoot_EmptyPrefixResolvesLoneRepo(t *testing.T) {
mi, dir := indexSingleRepoForTest(t)
root, ok := mi.RepoRoot("")
require.True(t, ok, "empty prefix must resolve when exactly one repo is tracked")
assert.Equal(t, dir, root)
// The real prefix keeps working.
root, ok = mi.RepoRoot("myrepo")
require.True(t, ok)
assert.Equal(t, dir, root)
// Unknown prefixes still miss.
_, ok = mi.RepoRoot("nope")
assert.False(t, ok)
}
func TestMultiIndexer_RepoRoot_EmptyPrefixAmbiguousInMultiRepo(t *testing.T) {
mi, _, _ := indexTwoReposForTest(t)
_, ok := mi.RepoRoot("")
assert.False(t, ok, "empty prefix is ambiguous with two tracked repos")
}
func TestMultiIndexer_ResolveFilePath_UnprefixedSingleRepo(t *testing.T) {
mi, dir := indexSingleRepoForTest(t)
// Unprefixed path (the form single-repo nodes carry) anchors to the
// lone repo root.
assert.Equal(t, filepath.Join(dir, "main.go"), mi.ResolveFilePath("main.go"))
// The prefixed form keeps working too.
assert.Equal(t, filepath.Join(dir, "main.go"), mi.ResolveFilePath("myrepo/main.go"))
}
func TestMultiIndexer_ResolveFilePath_UnprefixedMultiRepoStaysEmpty(t *testing.T) {
mi, _, _ := indexTwoReposForTest(t)
assert.Empty(t, mi.ResolveFilePath("main.go"),
"bare path with two tracked repos is ambiguous and must not resolve")
}
// TestMultiIndexer_RunPreEnrichResolve_BindsInboundCrossRepoEdge is the
// warm-restart completeness regression: on a partial restart where only the
// provider repo re-indexed, RunPreEnrichResolve scopes the same-repo master
// resolve to that repo but must still bind an unchanged consumer repo's inbound
// cross-repo reference — before the daemon flips ready. Scoping the cross-repo
// pass to the changed provider's own out-edges left the consumer-owned inbound
// edge unresolved for the whole enrichment window despite a "ready" daemon.
func TestMultiIndexer_RunPreEnrichResolve_BindsInboundCrossRepoEdge(t *testing.T) {
tmpCfg := filepath.Join(t.TempDir(), "config.yaml")
gc := &config.GlobalConfig{}
gc.SetConfigPath(tmpCfg)
require.NoError(t, gc.Save())
cm, err := config.NewConfigManager(tmpCfg)
require.NoError(t, err)
g := graph.New()
// Unchanged consumer repo-a: a call with no local definition (bare
// unresolved), in a shared workspace so the cross-repo boundary is open.
g.AddNode(&graph.Node{ID: "repo-a/a.go::Caller", Kind: graph.KindFunction, Name: "Caller", FilePath: "repo-a/a.go", Language: "go", RepoPrefix: "repo-a", WorkspaceID: "shared"})
// Changed provider repo-b: just added Foo, plus its file node for import
// reachability.
g.AddNode(&graph.Node{ID: "repo-b/b.go::Foo", Kind: graph.KindFunction, Name: "Foo", FilePath: "repo-b/b.go", Language: "go", RepoPrefix: "repo-b", WorkspaceID: "shared"})
g.AddNode(&graph.Node{ID: "repo-b/b.go", Kind: graph.KindFile, Name: "repo-b/b.go", FilePath: "repo-b/b.go", Language: "go", RepoPrefix: "repo-b", WorkspaceID: "shared"})
g.AddEdge(&graph.Edge{From: "repo-a/a.go", To: "repo-b/b.go", Kind: graph.EdgeImports, FilePath: "repo-a/a.go", Line: 1})
inbound := &graph.Edge{From: "repo-a/a.go::Caller", To: "unresolved::Foo", Kind: graph.EdgeCalls, FilePath: "repo-a/a.go", Line: 5}
g.AddEdge(inbound)
mi := NewMultiIndexer(g, newTestRegistry(), search.NewBM25(), cm, zap.NewNop())
// Scoped warm restart: only the provider repo-b re-indexed.
mi.RunPreEnrichResolve(context.Background(), map[string]struct{}{"repo-b": {}})
assert.Equal(t, "repo-b/b.go::Foo", inbound.To,
"pre-enrich resolve must bind the unchanged consumer's inbound edge into the changed provider before ready")
assert.True(t, inbound.CrossRepo)
}