feat: add job maintenance dashboard and automated stale job expiration system
This commit is contained in:
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user