Šķîþ ţö ḿàîñ çöñţéñţ

Ţéšţ Šţŕàţéĝý

Þŕîñçîþļéš

  1. Éṽéŕý ƒöŕḿàţ àñđ ţööļ ĥàš ţéšţš. Ñö ƒöŕḿàţ ŕéàđéŕ/ŵŕîţéŕ öŕ ţööļ šĥîþš ŵîţĥöüţ ţéšţš.
  2. Ŕöüñđţŕîþ îš ţĥé ĝöļđ šţàñđàŕđ. Ƒöŕ ƒöŕḿàţš: ŕéàđ, ŵŕîţé, çöḿþàŕé ŵîţĥ öŕîĝîñàļ.
  3. Þöŕţ Okapi ţéšţ đàţà. Üšé Okapi'š ţéšţ ŕéšöüŕçé ƒîļéš àš ţĥé šöüŕçé öƒ ţŕüţĥ.
  4. Ţàƃļé-đŕîṽéñ ţéšţš. Ĝö'š ţàƃļé-đŕîṽéñ þàţţéŕñ ƒöŕ çöṽéŕîñĝ ḿüļţîþļé îñþüţš.
  5. Ţéšţ àţ ţĥé îñţéŕƒàçé ƃöüñđàŕý. Ţéšţ àĝàîñšţ DataFormatReader/Tool îñţéŕƒàçéš, ñöţ îñţéŕñàļš.
  6. Đéţéŕḿîñîšţîç ÀÎ ţéšţš. ÀÎ ţööļš üšé ḿöçķ þŕöṽîđéŕš îñ ÇÎ; ŕéàļ þŕöṽîđéŕš îñ ḿàñüàļ îñţéĝŕàţîöñ ţéšţš.

Ţéšţ Šţŕüçţüŕé

neokapi/
├── core/model/
│ ├── model_test.go # Block creation, targets, overlays
│ └── run_test.go # Run sequence (canonical inline content)
├── core/flow/
│ ├── executor_test.go # Flow execution, error propagation
│ └── steps_test.go # StepsToGraph compilation
├── core/tool/
│ └── base_test.go # BaseTool dispatch, pass-through

├── core/formats/
│ ├── html/
│ │ ├── reader_test.go
│ │ ├── writer_test.go
│ │ └── testdata/ # Test fixtures
│ └── ... (each format follows the same pattern)

├── core/ai/tools/
│ └── tools_test.go # AI tool tests; use mock provider
├── providers/ai/
│ └── mock.go # Mock LLM provider

└── core/internal/testutil/
└── helpers.go # Common test helpers (RawDocFrom*, CollectParts/Blocks, PartsToChannel)

Ţéšţ Þàţţéŕñš

Ŕöüñđţŕîþ Ţéšţ

Ţĥé ḿöšţ îḿþöŕţàñţ ţéšţ ƒöŕ àñý ƒöŕḿàţ:

func TestRoundTrip(t *testing.T) {
tests := []struct {
name string
file string
}{
{"simple", "testdata/simple.html"},
{"inline codes", "testdata/inline_codes.html"},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
original, err := os.ReadFile(tt.file)
require.NoError(t, err)

reader := NewReader()
err = reader.Open(ctx, testutil.RawDocFromReader(
bytes.NewReader(original), tt.file, model.LocaleEnglish))
require.NoError(t, err)
parts := testutil.CollectParts(t, reader.Read(ctx))
reader.Close()

var buf bytes.Buffer
writer := NewWriter()
writer.SetOutputWriter(&buf)
writer.Write(ctx, testutil.PartsToChannel(parts))
writer.Close()

assert.Equal(t, string(original), buf.String())
})
}
}

Éẋţŕàçţîöñ Ţéšţ

Ṽéŕîƒý šþéçîƒîç Ƃļöçķš àŕé éẋţŕàçţéđ ŵîţĥ çöŕŕéçţ çöñţéñţ:

func TestExtraction(t *testing.T) {
data, err := os.ReadFile("testdata/sample.html")
require.NoError(t, err)

reader := NewReader()
err = reader.Open(ctx, testutil.RawDocFromReader(
bytes.NewReader(data), "testdata/sample.html", model.LocaleEnglish))
require.NoError(t, err)
defer reader.Close()

blocks := testutil.CollectBlocks(t, reader.Read(ctx))
require.Len(t, blocks, 3)
assert.Equal(t, "Welcome", blocks[0].SourceText())
}

Ƒļöŵ Éẋéçüţîöñ Ţéšţ

Ƃüîļđ à ţööļ ƃý éḿƃéđđîñĝ tool.BaseTool àñđ šéţţîñĝ ĥàñđļéŕ ƒîéļđš, àššéḿƃļé à ƒļöŵ ŵîţĥ ţĥé flow.NewFlow(...).AddTool(...).Build() ƃüîļđéŕ (ŵĥîçĥ ŕéţüŕñš (*Flow, error)), àñđ đŕîṽé îţ ŵîţĥ ExecuteWithChannels ƒöŕ çĥàññéļ-ļéṽéļ çöñţŕöļ:

func TestFlowExecution(t *testing.T) {
uppercase := &tool.BaseTool{
ToolName: "uppercase",
Produce: func(v tool.VariantView) error {
if v.Translatable() {
v.SetTargetText(model.LocaleFrench, strings.ToUpper(v.SourceText()))
}
return nil
},
}

f, err := flow.NewFlow("test").AddTool(uppercase).Build()
require.NoError(t, err)

executor := flow.NewExecutor()
in, out, wait := executor.ExecuteWithChannels(t.Context(), f)

go func() {
in <- &model.Part{Type: model.PartBlock, Resource: model.NewBlock("tu1", "hello")}
close(in)
}()
for range out { /* drain */ }
require.NoError(t, wait())
}

Ŕüññîñĝ Ţéšţš

make test # All tests
make test-unit # Unit tests only (-short)
make test-race # With race detector
make test-verbose # Verbose output
make cover # Coverage report

Šîñĝļé ţéšţ:

go test -tags fts5 ./core/flow/ -run TestFlowExecutorContextCancellation -v

Þàšš -tags fts5 ŵĥéñéṽéŕ ýöü ƃýþàšš make. Ţĥé ƃüîļđ šüççééđš ŵîţĥöüţ îţ, àñđ öñļý à ǫüéŕý ţĥàţ ŕéàçĥéš ƑŢŠ5 (çöñţéñţ ḿéḿöŕý, ţéŕḿš) ƒàîļš àţ ŕüñţîḿé ŵîţĥ no such function: fts5. Šþéļļ éṽéŕý ţàĝ îñ öñé çöḿḿà-šéþàŕàţéđ ƒļàĝ (-tags "fts5,parity"): go ķééþš öñļý ţĥé ļàšţ -tags öççüŕŕéñçé.

Ţéšţ Ţàĝš

ŢàĝÞüŕþöšéÇöḿḿàñđ
(ñöñé)Üñîţ ţéšţš öñļýgo test -tags fts5 ./...
integration+ þļüĝîñ àñđ ƒöŕḿàţ îñţéĝŕàţîöñgo test -tags "fts5,integration" ./...
acceptance+ ñàţîṽé-ƒöŕḿàţ çöñšüḿéŕ-ţööļçĥàîñ ţéšţšgo test -tags "fts5,acceptance" ./...
parity+ Okapi þàŕîţý çöḿþàŕîšöñ ţéšţšgo test -tags "fts5,parity" ./...
e2e+ éñđ-ţö-éñđ ţéšţšgo test -tags "fts5,e2e" ./...

ÇÎ

Ţéšţš ŕüñ àüţöḿàţîçàļļý ṽîà ĜîţĤüƃ Àçţîöñš öñ éṽéŕý þüšĥ àñđ þüļļ ŕéǫüéšţ. Šéé .github/workflows/ci.yml.