Skip to content

Commit 48acfaa

Browse files
aksOpsclaude
andcommitted
test(pipeline): end-to-end integration test over markdown corpus
New integration test drives pipeline.New().IndexPath().Finalize() over 5 small markdown files with the mock LLM provider, then asserts: - Document count is exactly 5. - Chunk count is in the 5..50 band. - Embedding count equals chunk count (Phase 2 invariant). - Entity count is in the 2..2*chunks band (mock returns 2 entities per extraction prompt; dedup collapses duplicates). - Relationship count is >=1. - LocalSearch("Apollo program", topK=5) returns >=1 chunk containing "Apollo". Gated by //go:build integration && sqlite_fts5 so the default `go test ./...` path is unaffected. The test-integration CI job picks it up automatically via its existing -tags "sqlite_fts5 integration" invocation. No CI workflow change needed. Local run: 0.05s without -race, 1.07s with -race. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent dc27526 commit 48acfaa

6 files changed

Lines changed: 194 additions & 0 deletions

File tree

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
//go:build integration && sqlite_fts5
2+
3+
package pipeline_test
4+
5+
import (
6+
"context"
7+
"path/filepath"
8+
"strings"
9+
"testing"
10+
"time"
11+
12+
"github.com/RandomCodeSpace/docsiq/internal/config"
13+
"github.com/RandomCodeSpace/docsiq/internal/embedder"
14+
"github.com/RandomCodeSpace/docsiq/internal/llm/mock"
15+
"github.com/RandomCodeSpace/docsiq/internal/pipeline"
16+
"github.com/RandomCodeSpace/docsiq/internal/search"
17+
"github.com/RandomCodeSpace/docsiq/internal/store"
18+
)
19+
20+
// TestPipeline_IndexAndSearch_EndToEnd drives pipeline.New().IndexPath()
21+
// followed by Finalize() against a 5-file markdown corpus using a
22+
// deterministic mock LLM provider, then asserts:
23+
// - SQLite documents, chunks, embeddings row counts are in the
24+
// expected bands,
25+
// - entity / relationship counts reflect mock extraction,
26+
// - a LocalSearch for a known substring returns >=1 hit from the
27+
// correct document.
28+
//
29+
// Runs under the integration build tag so it stays out of the default
30+
// `go test ./...` path; the CI test-integration job runs it with -race.
31+
func TestPipeline_IndexAndSearch_EndToEnd(t *testing.T) {
32+
t.Parallel()
33+
34+
// 1. Temp dir for the SQLite DB (OpenForProject constructs
35+
// <dataDir>/projects/<slug>/docsiq.db for us).
36+
dataDir := t.TempDir()
37+
st, err := store.OpenForProject(dataDir, "itest")
38+
if err != nil {
39+
t.Fatalf("store.OpenForProject: %v", err)
40+
}
41+
t.Cleanup(func() { _ = st.Close() })
42+
43+
// 2. Resolve the corpus relative to this test file; filepath.Abs
44+
// resolves against the test binary's cwd which is the package dir
45+
// (internal/pipeline), so ../../testdata/pipeline is correct.
46+
corpus, err := filepath.Abs(filepath.Join("..", "..", "testdata", "pipeline"))
47+
if err != nil {
48+
t.Fatalf("resolve corpus: %v", err)
49+
}
50+
51+
// 3. Minimal config; values low to keep wall-clock predictable
52+
// under -race.
53+
cfg := &config.Config{
54+
DataDir: dataDir,
55+
DefaultProject: "itest",
56+
LLM: config.LLMConfig{Provider: "none"}, // unused; we inject the mock directly
57+
Indexing: config.IndexingConfig{
58+
BatchSize: 4,
59+
ChunkSize: 512,
60+
ChunkOverlap: 64,
61+
ExtractGraph: true,
62+
ExtractClaims: false,
63+
MaxGleanings: 0,
64+
},
65+
Community: config.CommunityConfig{
66+
MinCommunitySize: 1,
67+
MaxLevels: 2,
68+
},
69+
}
70+
71+
// 4. Mock provider — no network, deterministic.
72+
prov := mock.New(mock.DefaultDims)
73+
pl := pipeline.New(st, prov, cfg)
74+
75+
// 5. Drive the indexer with a 120s deadline; a real deadlock will
76+
// blow past this and fail loud.
77+
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
78+
defer cancel()
79+
80+
if err := pl.IndexPath(ctx, corpus, pipeline.IndexOptions{
81+
Workers: 2,
82+
Verbose: false,
83+
}); err != nil {
84+
t.Fatalf("IndexPath: %v", err)
85+
}
86+
87+
// 6. Run Finalize (Phases 3-4: community detection + summaries).
88+
if err := pl.Finalize(ctx, false); err != nil {
89+
t.Fatalf("Finalize: %v", err)
90+
}
91+
92+
// 7. Row-count assertions — bands, not exact values, so chunker
93+
// re-tuning doesn't break this test.
94+
docCount := countRows(ctx, t, st, `SELECT count(*) FROM documents WHERE is_latest = 1`)
95+
if docCount != 5 {
96+
t.Errorf("document count: want 5, got %d", docCount)
97+
}
98+
99+
chunkCount := countRows(ctx, t, st, `SELECT count(*) FROM chunks`)
100+
if chunkCount < 5 || chunkCount > 50 {
101+
t.Errorf("chunk count: want 5..50, got %d", chunkCount)
102+
}
103+
104+
embCount := countRows(ctx, t, st, `SELECT count(*) FROM embeddings`)
105+
if embCount != chunkCount {
106+
t.Errorf("embedding count: want %d (= chunk count), got %d", chunkCount, embCount)
107+
}
108+
109+
// Mock returns 2 entities per extraction prompt; extractor runs
110+
// at least once per chunk; dedup collapses duplicates. Lower
111+
// bound 2, upper bound 2*chunkCount is a generous band.
112+
entityCount := countRows(ctx, t, st, `SELECT count(*) FROM entities`)
113+
if entityCount < 2 || entityCount > 2*chunkCount {
114+
t.Errorf("entity count: want 2..%d, got %d", 2*chunkCount, entityCount)
115+
}
116+
117+
relCount := countRows(ctx, t, st, `SELECT count(*) FROM relationships`)
118+
if relCount < 1 {
119+
t.Errorf("relationship count: want >=1, got %d", relCount)
120+
}
121+
122+
// 8. Search assertion: "Apollo" appears in alpha.md, beta.md,
123+
// gamma.md (indirectly via missions), and epsilon.md. LocalSearch
124+
// must return >=1 chunk whose content references the corpus.
125+
emb := embedder.New(prov, cfg.Indexing.BatchSize)
126+
if emb == nil {
127+
t.Fatal("embedder.New returned nil for non-nil provider")
128+
}
129+
result, err := search.LocalSearch(ctx, st, emb, nil, "Apollo program", 5, 0)
130+
if err != nil {
131+
t.Fatalf("LocalSearch: %v", err)
132+
}
133+
if len(result.Chunks) == 0 {
134+
t.Fatal("LocalSearch returned 0 chunks; expected >=1")
135+
}
136+
var gotApollo bool
137+
for _, c := range result.Chunks {
138+
if strings.Contains(strings.ToLower(c.Chunk.Content), "apollo") {
139+
gotApollo = true
140+
break
141+
}
142+
}
143+
if !gotApollo {
144+
t.Errorf("LocalSearch returned %d chunks but none mentioned Apollo", len(result.Chunks))
145+
}
146+
}
147+
148+
// countRows runs a `SELECT count(*) ...` and fails the test on error.
149+
func countRows(ctx context.Context, t *testing.T, st *store.Store, q string) int {
150+
t.Helper()
151+
var n int
152+
if err := st.DB().QueryRowContext(ctx, q).Scan(&n); err != nil {
153+
t.Fatalf("countRows %q: %v", q, err)
154+
}
155+
return n
156+
}

testdata/pipeline/alpha.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Alpha Doc
2+
3+
Alpha is the first document in the fixture corpus. It mentions
4+
Project Apollo, which was a spaceflight program run by NASA from 1961
5+
to 1972. Apollo 11 landed humans on the Moon for the first time.
6+
7+
## Background
8+
9+
The program was led by administrator James Webb, named after the
10+
Greek god Apollo. The Saturn V rocket launched every mission.

testdata/pipeline/beta.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Beta Doc
2+
3+
Beta covers the Apollo program's missions in more detail. Apollo 11,
4+
Apollo 12, and Apollo 13 are the most famous flights. Neil Armstrong
5+
was the commander of Apollo 11 and the first human on the Moon.
6+
7+
Apollo 13 suffered an oxygen tank explosion but returned safely.

testdata/pipeline/delta.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Delta Doc
2+
3+
Delta is a short document about unrelated topics. The moon is a
4+
celestial body orbiting Earth. The Earth orbits the Sun. Neither is
5+
particularly relevant to the Apollo program except by coincidence.

testdata/pipeline/epsilon.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Epsilon Doc
2+
3+
Epsilon is the last fixture. It mentions James Webb again — the
4+
James Webb Space Telescope was named after the Apollo-era NASA
5+
administrator.

testdata/pipeline/gamma.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Gamma Doc
2+
3+
Gamma is about the Saturn V rocket. Saturn V was the largest rocket
4+
ever flown successfully. It was designed by Wernher von Braun and his
5+
team at the Marshall Space Flight Center. The rocket had three
6+
stages.
7+
8+
## Notable Flights
9+
10+
All crewed Apollo missions used Saturn V. Skylab was launched on a
11+
modified Saturn V.

0 commit comments

Comments
 (0)