Ţéšţ Šţŕàţéĝý
Éṽéŕý ƒöŕḿàţ, ţööļ àñđ ƒļöŵ îñ ñéöķàþî îš ţéšţéđ àţ îţš îñţéŕƒàçé, àñđ à
ƒöŕḿàţ'š ŕöüñđţŕîþ ţéšţ îš ţĥé ƃàŕ îţ ḿüšţ þàšš. Çöþý ţĥé þàţţéŕñš ƃéļöŵ ƒöŕ à
ñéŵ ţéšţ, þîçķ ţĥé ƃüîļđ ţàĝ ţĥàţ ḿàţçĥéš îţš šçöþé, àñđ àđđ à ĝàţé çöñƒöŕḿàñçé
çàšé ŵĥéñ ýöü çĥàñĝé ŵĥàţ kapi check ŕéþöŕţš.
Þŕîñçîþļéš
- Éṽéŕý ƒöŕḿàţ àñđ ţööļ ĥàš ţéšţš. Ñö ƒöŕḿàţ ŕéàđéŕ/ŵŕîţéŕ öŕ ţööļ šĥîþš ŵîţĥöüţ ţéšţš.
- Ŕöüñđţŕîþ îš ţĥé ĝöļđ šţàñđàŕđ. Ƒöŕ ƒöŕḿàţš: ŕéàđ, ŵŕîţé, çöḿþàŕé ŵîţĥ öŕîĝîñàļ.
- Þöŕţ Öķàþî ţéšţ đàţà. Üšé Öķàþî'š ţéšţ ŕéšöüŕçé ƒîļéš àš ţĥé šöüŕçé öƒ ţŕüţĥ.
- Ţàƃļé-đŕîṽéñ ţéšţš. Ĝö'š ţàƃļé-đŕîṽéñ þàţţéŕñ ƒöŕ çöṽéŕîñĝ ḿüļţîþļé îñþüţš.
- Ţéšţ àţ ţĥé îñţéŕƒàçé ƃöüñđàŕý. Ţéšţ àĝàîñšţ
DataFormatReader/Toolîñţéŕƒàçéš, ñöţ îñţéŕñàļš. - Đéţéŕḿîñîšţîç ÀÎ ţéšţš. ÀÎ ţööļš üšé ḿöçķ þŕöṽîđéŕš îñ ÇÎ; ŕéàļ þŕöṽîđéŕš îñ ḿàñüàļ îñţéĝŕàţîöñ ţéšţš.
Ţéšţ Šţŕüçţüŕé
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,integration"): go ķééþš öñļý ţĥé ļàšţ -tags öççüŕŕéñçé.
Ţéšţ Ţàĝš
| Ţàĝ | Þüŕþöšé | Çöḿḿàñđ |
|---|---|---|
| (ñöñé) | Üñîţ ţéšţš öñļý | go test -tags fts5 ./... |
integration | + þļüĝîñ àñđ ƒöŕḿàţ îñţéĝŕàţîöñ | go test -tags "fts5,integration" ./... |
acceptance | + ñàţîṽé-ƒöŕḿàţ çöñšüḿéŕ-ţööļçĥàîñ ţéšţš | go test -tags "fts5,acceptance" ./... |
e2e | + éñđ-ţö-éñđ ţéšţš | go test -tags "fts5,e2e" ./... |
Ţĥé ĥéàđ-ţö-ĥéàđ çöḿþàŕîšöñ öƒ ţĥé Ĝö þöŕţš àĝàîñšţ ţĥé Öķàþî Ƒŕàḿéŵöŕķ îš à
šéþàŕàţé šüîţé îñ ţĥé öķàþî-ƃŕîđĝé ŕéþöšîţöŕý
(parity/). Îţ çöḿþîļéš àĝàîñšţ à ŕéļéàšéđ ñéöķàþî ḿöđüļé àñđ ŕüñš öñ à
šçĥéđüļé ţĥéŕé; šéé À-02.
Ĝàţé Çöñƒöŕḿàñçé Šüîţé
Ţĥé ĝàţé çöñƒöŕḿàñçé šüîţé ĥöļđš ţĥé ƃüîļţ kapi ƃîñàŕý ţö ŵĥàţ ţĥé ĝàţé
çļàîḿš. Éàçĥ çàšé šţàţéš ŵĥîçĥ ƒîñđîñĝš kapi check ŕéþöŕţš àñđ ŵĥîçĥ îţ
ļéàṽéš àļöñé, ŵĥàţ kapi fix ŵŕîţéš, ŵĥàţ kapi context <path> ñàḿéš àḿöñĝ à
ƒîļé'š ŕüļéš, àñđ ŵĥàţ ţĥé Çļàüđé Çöđé þļüĝîñ'š éđîţ àñđ Šţöþ ĥööķš đéçîđé
àƒţéŕ àñ éđîţ. Ŕüñ îţ ŵîţĥ:
make gate-conformance
Ţĥé ţàŕĝéţ ŕüñš TestGateConformance îñ kapi/e2e/, šö îţ üšéš ţĥé ƃîñàŕý àñđ
ţĥé îšöļàţîöñ éñṽîŕöñḿéñţ ţĥàţ þàçķàĝé'š TestMain šéţš üþ, àñđ ĝîṽéš éàçĥ çàšé
à ŵöŕķšþàçé öƒ îţš öŵñ. Îţ þŕîñţš à ḿàţŕîẋ ŵîţĥ öñé ļîñé þéŕ çàšé àñđ šüŕƒàçé
(check, fix, context, hook): pass, fail, expected-fail ŵîţĥ ţĥé
ŕéàšöñ, öŕ not covered.
Éàçĥ çàšé îš à đîŕéçţöŕý üñđéŕ kapi/e2e/gate/testdata/<case>/. Ţĥé đîŕéçţöŕý
îš à šḿàļļ þŕöĵéçţ: à kapi.yaml, îţš çöñţéñţ ƒîļéš, àñđ, ƒöŕ à çàšé ţĥàţ
ñééđš ŕüļéš, à voice.yaml üñđéŕ .kapi/. Ţĥàţ ƒîļé îš ţĥé àŕţîƒàçţ à
kapi store import šţéþ ŕéàđš îñţö ţĥé þŕöĵéçţ'š šţöŕé: îţš terms: îñţö ţĥé
ţéŕḿš šţöŕé àñđ îţš þàţţéŕñš îñţö ţĥé ṽöîçé þŕöƒîļé. Ƃéšîđé ţĥéḿ, expect.yaml
ļîšţš šţàĝéš. Éàçĥ šţàĝé ŕüñš îţš steps àñđ ţĥéñ çĥéçķš îţš éẋþéçţàţîöñš:
description: A kept name rule fails a check on each rejected form.
stages:
- name: kept
steps:
- actor: agent
kapi: [context, note, --term, Sign in, --instead-of, Log in]
- keep: Write Sign in, not Log in, Sign-in, Signin or SignIn.
files:
- path: docs/guide.md
check:
failing: 1
report:
- {rule: terms.vocabulary, block: "welcome/p", quote: Log in, fails: true}
absent:
- {quote: Sign in}
fix:
failing: 0
after: |
# Welcome
Sign in with your account.
context:
names: ['Sign in, not "Log in"']
hook:
edit: {path: docs/guide.md, append: "\nLog in again.\n"}
blocks: true
names: [Forbidden term "Log in" found]
log: {failing: 1, caught: 0}
À šţéþ îš à kapi çöḿḿàñđ ļîñé (ŕüñ îñ ţĥé þŕöĵéçţ, àš ţĥé þéŕšöñ öŕ àš àñ
agent), à keep öŕ narrow öƒ à ŕüļé ñàḿéđ ƃý ţĥé šéñţéñçé kapi rules
þŕîñţš, öŕ à write ţö à ƒîļé. À fix ŵîţĥ ñö after àššéŕţš ţĥàţ ţĥé ƒîļé
ķééþš îţš ƃýţéš; args: [] ŕüñš kapi fix öṽéŕ ţĥé ŵĥöļé þŕöĵéçţ, àñđ
unchanged ñàḿéš öţĥéŕ ƒîļéš ŵĥöšé ƃýţéš ḿüšţ ñöţ ḿöṽé. À ĥööķ ŕüñš ţĥé Šţöþ
ĥööķ üñļéšš îţ šéţš event: edit, ŵĥîçĥ ŕüñš ţĥé ÞöšţŢööļÜšé ĥööķ öñ ţĥé
éđîţ'š ƒîļé; outside: true ŵŕîţéš ţĥàţ ƒîļé öüţšîđé ţĥé þŕöĵéçţ. À ĥööķ'š
log ŕéàđš
kapi context log --session àƒţéŕ ţĥé ŕüñ: failing îš ţĥé çöüñţ ţĥé ŕüñ
ŕéçöŕđéđ, àñđ caught îš ĥöŵ ḿàñý ƒîñđîñĝš ţĥé šéššîöñ'š ŕüñš ĥàṽé çàüĝĥţ
šö ƒàŕ.
Ţö àđđ à çàšé, çöþý ţĥé ñéàŕéšţ đîŕéçţöŕý, çĥàñĝé ţĥé þŕöĵéçţ àñđ ţĥé
éẋþéçţàţîöñš, àñđ ŕüñ make gate-conformance. À ƒàîļîñĝ éẋþéçţàţîöñ þŕîñţš ţĥé
ƒüļļ kapi check --json ŕéþöŕţ, öŕ ţĥé ƒîẋ öüţþüţ àñđ ƃöţĥ ṽéŕšîöñš öƒ ţĥé
ƒîļé. Ƒîñđ ţĥé ƃļöçķ îđš àñđ ǫüöţéđ ŵöŕđš ţĥéŕé.
Àñ éẋþéçţàţîöñ ţĥé çüŕŕéñţ çöđé đöéš ñöţ ḿééţ çàŕŕîéš xfail: <reason>. Îţ îš
ļîšţéđ àš expected-fail ŵîţĥ ţĥàţ ŕéàšöñ àñđ đöéš ñöţ ƒàîļ ţĥé ŕüñ. Ŵĥéñ ţĥé
ƃéĥàṽîöüŕ ļàñđš, ţĥé éẋþéçţàţîöñ þàššéš, ţĥé ŕüñ ƒàîļš àñđ ñàḿéš ţĥé xfail ţö
ŕéḿöṽé. Ñéṽéŕ ḿàŕķ àñ éẋþéçţàţîöñ àš šķîþþéđ îñšţéàđ.
Ţĥé ƒîẋţüŕéš çöñţàîñ ƃàññéđ ŵöŕđš öñ þüŕþöšé. Ţĥé ŕéþöšîţöŕý'š öŵñ kapi.yaml
éẋçļüđéš **/testdata/**, šö kapi check öñ ţĥé ŕéþöšîţöŕý ñéṽéŕ ŕéàđš ţĥéḿ.
ÇÎ
ĜîţĤüƃ Àçţîöñš ŕüñš ţĥé ţéšţš öñ éṽéŕý þüšĥ àñđ þüļļ ŕéǫüéšţ
(.github/workflows/ci.yml).
Ŕéļàţéđ
- À-01: Ţéšţîñĝ àñđ đöçüḿéñţàţîöñ: ţĥé ţéšţ ļàýéŕš àñđ ŵĥàţ éàçĥ þŕöţéçţš.
- À-02: Þàŕîţý: ţĥé çöḿþàŕîšöñ àĝàîñšţ ţĥé Öķàþî Ƒŕàḿéŵöŕķ.
- Îḿþļéḿéñţîñĝ à Ƒöŕḿàţ: éẋţŕàçţîöñ àñđ ŕöüñđţŕîþ ţéšţš ƒöŕ à ñéŵ ƒöŕḿàţ.
- Ţööļ Àüţĥöŕîñĝ ŵîţĥ Þàŕàḿéţéŕ Šçĥéḿàš: ƃüîļđîñĝ ţĥé ţööļš ţĥéšé ţéšţš đŕîṽé.