Running tests¶
go test ./... # all packages
go test ./internal/srs # single package
go test -v ./internal/store # verbose output
go test -run TestPickNext # specific test
In-memory SQLite¶
All tests that need a database use db.OpenMemory():
func TestSomething(t *testing.T) {
testDB, err := db.OpenMemory()
if err != nil {
t.Fatal(err)
}
defer testDB.Close()
// testDB has all migrations applied and is empty
}
Each test gets a fresh, isolated database. No file cleanup required.
Test patterns¶
Pure function tests (internal/srs)¶
SM-2 logic has no dependencies — use table-driven tests:
func TestUpdateSRS(t *testing.T) {
tests := []struct {
name string
state ProblemSRS
result string
duration int
difficulty string
wantReps int
wantInterval int
}{
{"first success", defaultState, "success", 8, "easy", 1, 1},
{"second success", afterFirstSuccess, "success", 8, "easy", 2, 6},
{"failure resets", afterSecondSuccess, "fail", 0, "easy", 0, 1},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := UpdateSRS(tc.state, tc.result, tc.duration, tc.difficulty)
// assert ...
})
}
}
Store / integration tests (internal/store)¶
Seed the in-memory DB and assert on query results:
func TestPickNext(t *testing.T) {
db, _ := db.OpenMemory()
defer db.Close()
store.InsertProblem(db, "leetcode", "two-sum", "Two Sum", "easy", nil)
// set next_review_date to past to make it due
db.Exec(`UPDATE problem_srs SET next_review_date = '2020-01-01'`)
p, _, isDue, err := store.PickNext(db, store.PracticeFilter{})
if err != nil || p == nil {
t.Fatal("expected a problem")
}
if !isDue {
t.Error("expected isDue=true")
}
}
Parser tests (internal/importer, internal/platform)¶
Use os.CreateTemp for file-based parsers, table-driven for URL parsing:
func TestParseURL(t *testing.T) {
tests := []struct {
url string
platform string
slug string
wantErr bool
}{
{"https://leetcode.com/problems/two-sum/", "leetcode", "two-sum", false},
{"https://neetcode.io/problems/two-sum/question", "neetcode", "two-sum", false},
{"https://unknown.com/problems/foo", "", "", true},
}
for _, tc := range tests {
t.Run(tc.url, func(t *testing.T) {
plat, slug, err := platform.ParseURL(tc.url)
// assert ...
})
}
}
What not to test automatically¶
Scraper and monitor tests require a real browser and live problem sites. Don't write automated tests for these. Instead:
- Add a
//go:build manualbuild tag to any test that needs a browser - Document manual verification steps as a comment in the test file:
//go:build manual
// To run: go test -tags manual -v ./internal/scraper
// Requires Chrome and a valid NeetCode session in the browser profile.
CI conventions¶
- All tests in
go test ./...must pass with no build tags - No network calls in regular tests
- No file system side effects outside of
t.TempDir()oros.CreateTemp