feat: add job maintenance dashboard and automated stale job expiration system

This commit is contained in:
2026-08-22 01:51:43 -07:00
parent 367929e7d0
commit 7ec91f384f
12 changed files with 633 additions and 56 deletions
+115
View File
@@ -182,3 +182,118 @@ func TestRemoveTargetFromYAML(t *testing.T) {
}
}
func TestTouchJobLastSeenAndMaintenance(t *testing.T) {
tempDir := t.TempDir()
dbPath := filepath.Join(tempDir, "test_maintenance.db")
database, err := InitDB(dbPath)
if err != nil {
t.Fatalf("failed to init db: %v", err)
}
defer func() { _ = database.Close() }()
// Insert jobs
job1, _, err := database.InsertRawJob("canon-1", nil, "Job 1", "Google", "Seattle", "https://google.com/1", "Software Engineer Go")
if err != nil || job1.ID == 0 {
t.Fatalf("failed to insert job 1: %v", err)
}
job2, _, err := database.InsertRawJob("canon-2", nil, "Job 2", "Apple", "Seattle", "https://apple.com/2", "Software Engineer Swift")
if err != nil {
t.Fatalf("failed to insert job 2: %v", err)
}
job3, _, err := database.InsertRawJob("canon-3", nil, "Job 3", "Meta", "Seattle", "https://meta.com/3", "Software Engineer React")
if err != nil {
t.Fatalf("failed to insert job 3: %v", err)
}
// 1. Touch last seen for canon-1
if err := database.TouchJobLastSeen("canon-1"); err != nil {
t.Fatalf("TouchJobLastSeen failed: %v", err)
}
// Make job2 and job3 artificially stale (20 days and 40 days old)
_, err = database.Exec(`UPDATE raw_jobs SET last_seen_at = datetime('now', '-20 days') WHERE id = ?`, job2.ID)
if err != nil {
t.Fatalf("failed to update last_seen_at: %v", err)
}
_, err = database.Exec(`UPDATE raw_jobs SET last_seen_at = datetime('now', '-40 days') WHERE id = ?`, job3.ID)
if err != nil {
t.Fatalf("failed to update last_seen_at: %v", err)
}
// Protect job3 in active pipeline (APPLIED)
_, _ = database.CreateOrUpdateApplication(job3.ID, "APPLIED", "Applied to Meta")
// 2. Enqueue expire tasks (> 14 days) with pipeline protection
queuedExpireCount, err := database.EnqueueExpireTasks(14, true)
if err != nil {
t.Fatalf("EnqueueExpireTasks failed: %v", err)
}
// job2 is stale and unprotected -> should be queued
// job3 is stale but protected (APPLIED) -> should NOT be queued
// job1 is fresh -> should NOT be queued
if queuedExpireCount != 1 {
t.Errorf("expected 1 job queued for expire, got %d", queuedExpireCount)
}
// 3. Directly expire job2
if err := database.ExpireJob(job2.ID, "Test expiration"); err != nil {
t.Fatalf("ExpireJob failed: %v", err)
}
// Verify job2 status in DB
j2Detail, err := database.GetJobByID(job2.ID)
if err != nil {
t.Fatalf("GetJobByID failed: %v", err)
}
if j2Detail.App == nil || j2Detail.App.Status != "EXPIRED" {
t.Errorf("expected job2 to have EXPIRED application, got %v", j2Detail.App)
}
// Verify job3 still APPLIED
j3Detail, err := database.GetJobByID(job3.ID)
if err != nil {
t.Fatalf("GetJobByID failed: %v", err)
}
if j3Detail.App == nil || j3Detail.App.Status != "APPLIED" {
t.Errorf("expected job3 to remain APPLIED, got %v", j3Detail.App)
}
// 4. Stats check
stats, err := database.GetJobMaintenanceStats()
if err != nil {
t.Fatalf("GetJobMaintenanceStats failed: %v", err)
}
if stats["total_jobs"] != 3 {
t.Errorf("expected total_jobs=3, got %d", stats["total_jobs"])
}
if stats["active_7d"] != 1 {
t.Errorf("expected active_7d=1, got %d", stats["active_7d"])
}
if stats["stale_7d"] != 2 {
t.Errorf("expected stale_7d=2, got %d", stats["stale_7d"])
}
if stats["expired_count"] != 1 {
t.Errorf("expected expired_count=1, got %d", stats["expired_count"])
}
}
func TestDevDBMigration(t *testing.T) {
devDBPath := "../../data/db/dev/hireme.db"
if _, err := os.Stat(devDBPath); os.IsNotExist(err) {
return
}
database, err := InitDB(devDBPath)
if err != nil {
t.Fatalf("InitDB dev db failed: %v", err)
}
defer func() { _ = database.Close() }()
jobs, err := database.GetJobsWithAnalysis()
if err != nil {
t.Fatalf("GetJobsWithAnalysis on dev db failed: %v", err)
}
t.Logf("Successfully verified %d jobs from dev db", len(jobs))
}