.cursor/skills/golang-tests/SKILL.md
Тестирование на Go — API-тесты (muonsoft/api-testing), unit-тесты. Arrange–Act–Assert. Используй при написании тестов для internal/ и cmd/.
npx skillsauth add strider2038/knowledge-db golang-testsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Цель: покрыть тестами API-handlers, internal/kb, internal/ingestion.
Пакеты apitest и assertjson:
func TestGetNode_WhenNotFound_Expect404(t *testing.T) {
t.Parallel()
// Arrange
handler := setupTestHandler() // или NewTestHandler(container)
// Act
resp := apitest.HandleGET(t, handler, "/api/nodes/missing/path")
// Assert
resp.IsNotFound()
}
assertjson.Node — вариадический синтаксис: Node("key", 0, "nested"), не legacy /key/0/nested.
internal/<pkg>/mocks или *_test.gomap по ключуerrors.Errorf, не errors.New для анонимныхTest<Entity>_<Action>_When<Condition>_Expect<Result>
Примеры: TestGetNode_WhenValidPath_ExpectOK, TestValidate_WhenMissingContent_ExpectError.
Все проверки в тестах — через testify (require и assert):
require — обязательные проверки, останавливают тест при провале (require.NoError, require.Error, require.NotEmpty)assert — сравнения, продолжают тест (assert.Equal, assert.Empty, assert.True, assert.False)// Ошибки
require.NoError(t, err) // ожидаем успех
require.Error(t, err) // ожидаем ошибку
assert.ErrorIs(t, err, ErrNotFound) // ошибка должна быть target
// Сравнения
assert.Equal(t, expected, actual)
assert.Empty(t, slice) // len == 0
assert.NotEmpty(t, slice)
assert.True(t, cond)
assert.False(t, cond)
assert.NotNil(t, ptr)
assert.Contains(t, slice, element) // slice содержит element
require — когда дальнейшие проверки бессмысленны (нет данных, паника и т.п.)assert — для обычных проверок значенийВ тестовом коде не использовать panic — даже в хелперах и setup. При ошибках вызывать tb.Fatalf:
func buildTestData(tb testing.TB, input string) *Response {
tb.Helper()
data, err := json.Marshal(input)
if err != nil {
tb.Fatalf("marshal: %v", err)
}
// ...
}
testing.TB (работает с *testing.T и *testing.B)tb.Helper() в начале хелпера — корректный stack trace при паденииtb.Fatalf останавливает тест с понятным сообщениемДля тестов с файловой структурой используй afero вместо t.TempDir() и os.WriteFile:
NewStore(afero.NewOsFs())NewStore(afero.NewMemMapFs()) — in-memory, без диска, быстрееafero.Fs (через Store или NewStore(fs)).fs := afero.NewMemMapFs(), заполняешь через afero.WriteFile, afero.MkdirAll.Store с этим fs.func seedMemFS(files map[string]string) (*Store, string) {
fs := afero.NewMemMapFs()
basePath := "/"
for path, content := range files {
fullPath := filepath.Join(basePath, path)
_ = fs.MkdirAll(filepath.Dir(fullPath), 0755)
_ = afero.WriteFile(fs, fullPath, []byte(content), 0644)
}
return NewStore(fs), basePath
}
Пример: store, base := seedMemFS(map[string]string{"topic/node1/node1.md": "---\nkeywords: []\n---\n"}).
Для MemMapFs используй абсолютные пути (/ как base). filepath.Join с / даёт корректные пути.
development
Knowledge base layout and node format for knowledge-db. Use when creating or editing KB markdown files. Root path placeholder {{DATA_PATH}}.
development
Frontend web/ (React, TypeScript, Vite). Используй при работе с web/src/**/*.tsx, web/src/**/*.ts.
tools
Регистрация фоновых процессов через pior/runnable. Используй при добавлении Telegram bot и других воркеров в kb-server.
testing
Verify implementation matches change artifacts. Use when the user wants to validate that implementation is complete, correct, and coherent before archiving.