package state import ( "os" "path/filepath" "testing" "time" ) func TestMarkAndQuery(t *testing.T) { s, err := New(t.TempDir()) if err != nil { t.Fatal(err) } const repo = "unkin/teabot" if s.IssueProcessed(repo, 1) { t.Error("fresh store should not report issue 1 processed") } s.MarkIssue(repo, 1) if !s.IssueProcessed(repo, 1) { t.Error("issue 1 should be processed after MarkIssue") } if !s.ActedOnIssue(repo, 1) { t.Error("MarkIssue should also record acted-on") } if s.ActedOnPull(repo, 1) { t.Error("issue mark must not set acted-on-pull") } s.MarkPull(repo, 2) if !s.PullProcessed(repo, 2) || !s.ActedOnPull(repo, 2) { t.Error("pull 2 should be processed and acted-on") } s.MarkComment(repo, 99) if !s.CommentProcessed(repo, 99) { t.Error("comment 99 should be processed") } if s.CommentProcessed(repo, 100) { t.Error("comment 100 was never marked") } } func TestPersistenceRoundTrip(t *testing.T) { dir := t.TempDir() s1, err := New(dir) if err != nil { t.Fatal(err) } const repo = "a/b" s1.MarkIssue(repo, 10) s1.MarkPull(repo, 11) s1.MarkComment(repo, 12) s1.MarkSeeded(repo) now := time.Now().Truncate(time.Second) s1.SetLastPoll(repo, now) if err := s1.Save(); err != nil { t.Fatalf("Save: %v", err) } // A fresh Store loaded from the same dir must see the persisted state. s2, err := New(dir) if err != nil { t.Fatal(err) } if !s2.IssueProcessed(repo, 10) || !s2.PullProcessed(repo, 11) || !s2.CommentProcessed(repo, 12) { t.Error("processed sets did not survive reload") } if !s2.ActedOnIssue(repo, 10) || !s2.ActedOnPull(repo, 11) { t.Error("acted-on sets did not survive reload") } if !s2.Seeded(repo) { t.Error("seeded flag did not survive reload") } if !s2.LastPoll(repo).Equal(now) { t.Errorf("LastPoll = %v, want %v", s2.LastPoll(repo), now) } } func TestSaveIsAtomicFile(t *testing.T) { dir := t.TempDir() s, err := New(dir) if err != nil { t.Fatal(err) } s.MarkIssue("a/b", 1) if err := s.Save(); err != nil { t.Fatal(err) } if _, err := os.Stat(filepath.Join(dir, StateFileName)); err != nil { t.Errorf("state file missing after Save: %v", err) } // No leftover temp file. if _, err := os.Stat(filepath.Join(dir, StateFileName+".tmp")); !os.IsNotExist(err) { t.Error("temp file should not remain after atomic rename") } } func TestSeededIndependentPerRepo(t *testing.T) { s, err := New(t.TempDir()) if err != nil { t.Fatal(err) } s.MarkSeeded("a/b") if s.Seeded("c/d") { t.Error("seeding a/b must not seed c/d") } if !s.Seeded("a/b") { t.Error("a/b should be seeded") } } func TestLoadCorruptStateFails(t *testing.T) { dir := t.TempDir() if err := os.WriteFile(filepath.Join(dir, StateFileName), []byte("{not json"), 0o644); err != nil { t.Fatal(err) } if _, err := New(dir); err == nil { t.Error("expected error loading corrupt state file") } }