package history import ( "errors" "os" "strings" "testing" ) var errBoom = errors.New("boom") func writeFile(t *testing.T, path, content string) { t.Helper() if err := os.WriteFile(path, []byte(content), 0o644); err != nil { t.Fatal(err) } } func TestParseTockConfig(t *testing.T) { cfg := []byte("backend: sqlite\nsqlite:\n path: /Users/x/.tock.db\nlanguage: eng\n") b, p := parseTockConfig(cfg) if b != "sqlite" || p != "/Users/x/.tock.db" { t.Fatalf("got backend=%q path=%q", b, p) } b2, _ := parseTockConfig([]byte("backend: file\n")) if b2 != "file" { t.Errorf("backend=%q want file", b2) } } func TestParseLines(t *testing.T) { got := parseLines([]byte("ARCHER\n Development \n\nDoctor\n")) want := []string{"ARCHER", "Development", "Doctor"} if len(got) != 3 || got[0] != want[0] || got[1] != want[1] || got[2] != want[2] { t.Fatalf("got %v want %v", got, want) } } func TestDedupeEntries(t *testing.T) { raw := []lastEntry{ {Project: "ARCHER", Description: "a", Tags: []string{"x", "y"}}, {Project: "ARCHER", Description: "b", Tags: []string{"x"}}, {Project: "", Description: "", Tags: nil}, } h := dedupeEntries(raw) if len(h.Projects) != 1 || h.Projects[0] != "ARCHER" { t.Errorf("projects=%v", h.Projects) } if len(h.Descriptions) != 2 { t.Errorf("descriptions=%v", h.Descriptions) } if len(h.Tags) != 2 || h.Tags[0] != "x" || h.Tags[1] != "y" { t.Errorf("tags=%v", h.Tags) } } // fakeRunner routes by the command name (sqlite3 vs tock) and a query substring. type fakeRunner struct { sqlite map[string][]byte last []byte sqlErr error tockErr error } func (f fakeRunner) run(name string, args ...string) ([]byte, error) { if name == "sqlite3" { if f.sqlErr != nil { return nil, f.sqlErr } q := args[len(args)-1] for sub, out := range f.sqlite { if strings.Contains(q, sub) { return out, nil } } return nil, nil } // tock last --json if f.tockErr != nil { return nil, f.tockErr } return f.last, nil } func TestLoadWithSqlite(t *testing.T) { dir := t.TempDir() cfgPath := dir + "/tock.yaml" writeFile(t, cfgPath, "backend: sqlite\nsqlite:\n path: "+dir+"/x.db\n") writeFile(t, dir+"/x.db", "") // must exist for the DB-present check r := fakeRunner{sqlite: map[string][]byte{ "FROM activities WHERE project": []byte("ARCHER\nDoctor\n"), "FROM activities WHERE description": []byte("standup\nemails\n"), "json_each": []byte("admin\nmeeting\n"), }} h := loadWith(r, cfgPath) if len(h.Projects) != 2 || h.Projects[0] != "ARCHER" { t.Errorf("projects=%v", h.Projects) } if len(h.Descriptions) != 2 || len(h.Tags) != 2 { t.Errorf("desc=%v tags=%v", h.Descriptions, h.Tags) } } func TestLoadWithFallbackToTockLast(t *testing.T) { dir := t.TempDir() cfgPath := dir + "/tock.yaml" writeFile(t, cfgPath, "backend: file\n") r := fakeRunner{last: []byte(`[{"project":"ARCHER","description":"a","tags":["x"]},{"project":"ARCHER","description":"b"}]`)} h := loadWith(r, cfgPath) if len(h.Projects) != 1 || len(h.Descriptions) != 2 || len(h.Tags) != 1 { t.Errorf("fallback wrong: %+v", h) } } func TestLoadWithTotalFailureIsEmpty(t *testing.T) { dir := t.TempDir() cfgPath := dir + "/tock.yaml" writeFile(t, cfgPath, "backend: file\n") r := fakeRunner{tockErr: errBoom} h := loadWith(r, cfgPath) if len(h.Projects) != 0 || len(h.Descriptions) != 0 || len(h.Tags) != 0 { t.Errorf("expected empty, got %+v", h) } }