Skip to content

Commit f684712

Browse files
refactor(schedules): replace deleteJobsByRepo with prepareRepoDelete, add lightweight ID query, remove dead mock
1 parent 6fc7ec6 commit f684712

7 files changed

Lines changed: 42 additions & 26 deletions

File tree

backend/src/db/schedules.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,12 @@ export function listScheduleJobsByRepo(db: Database, repoId: number): ScheduleJo
116116
return rows.map(rowToScheduleJob)
117117
}
118118

119+
export function listScheduleJobIdsByRepo(db: Database, repoId: number): number[] {
120+
const stmt = db.prepare('SELECT id FROM schedule_jobs WHERE repo_id = ? ORDER BY created_at DESC')
121+
const rows = stmt.all(repoId) as Array<{ id: number }>
122+
return rows.map((row) => row.id)
123+
}
124+
119125
export function listEnabledScheduleJobs(db: Database): ScheduleJob[] {
120126
const stmt = db.prepare('SELECT * FROM schedule_jobs WHERE enabled = 1 ORDER BY id ASC')
121127
const rows = stmt.all() as ScheduleJobRow[]
@@ -206,13 +212,6 @@ export function deleteScheduleJob(db: Database, repoId: number, jobId: number):
206212
return result.changes > 0
207213
}
208214

209-
export function deleteScheduleJobsByRepo(db: Database, repoId: number): number {
210-
db.prepare('DELETE FROM schedule_runs WHERE repo_id = ?').run(repoId)
211-
const stmt = db.prepare('DELETE FROM schedule_jobs WHERE repo_id = ?')
212-
const result = stmt.run(repoId)
213-
return result.changes
214-
}
215-
216215
export function cleanupOrphanedSchedules(db: Database): { orphanedJobs: number; orphanedRuns: number } {
217216
const runStmt = db.prepare(`
218217
DELETE FROM schedule_runs

backend/src/routes/repos.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ function createTestApp(db: Database, openCodeClient: OpenCodeClient = createStub
3737
listSchedules: () => [],
3838
updateSchedule: () => {},
3939
deleteSchedule: () => {},
40+
prepareRepoDelete: () => {},
4041
} as any
4142
app.route('/repos', createRepoRoutes(db, stubGitAuthService, scheduleService, openCodeClient))
4243
return app

backend/src/routes/repos.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -273,10 +273,7 @@ app.get('/', async (c) => {
273273
return c.json({ error: 'Repo not found' }, 404)
274274
}
275275

276-
// Unregister and delete all schedule jobs/runs for this repo first,
277-
// so the ScheduleRunner doesn't hold stale cron timers and the DB
278-
// cascade (or explicit delete) cleans up schedule data cleanly.
279-
scheduleService.deleteJobsByRepo(id)
276+
scheduleService.prepareRepoDelete(id)
280277

281278
await repoService.deleteRepoFiles(database, id)
282279

backend/src/services/schedules.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ import {
1414
createScheduleJob,
1515
createScheduleRun,
1616
deleteScheduleJob,
17-
deleteScheduleJobsByRepo,
1817
getScheduleJobById,
1918
getRunningScheduleRunByJob,
2019
getScheduleRunById,
2120
listAllScheduleJobsWithRepos,
2221
listAllScheduleRuns,
2322
listEnabledScheduleJobs,
23+
listScheduleJobIdsByRepo,
2424
listScheduleJobsByRepo,
2525
listRunningScheduleRuns,
2626
listScheduleRunsByJob,
@@ -446,18 +446,11 @@ export class ScheduleService {
446446
this.onJobChange?.(null, jobId)
447447
}
448448

449-
/**
450-
* Deletes all schedule jobs (and their runs) for a repo and notifies the
451-
* ScheduleRunner so in-memory cron timers are removed. This should be called
452-
* **before** the repo row itself is deleted.
453-
*/
454-
deleteJobsByRepo(repoId: number): void {
455-
// Notify the runner for each job so in-memory timers are cleaned up.
456-
const jobs = listScheduleJobsByRepo(this.db, repoId)
457-
for (const job of jobs) {
458-
this.onJobChange?.(null, job.id)
449+
prepareRepoDelete(repoId: number): void {
450+
const jobIds = listScheduleJobIdsByRepo(this.db, repoId)
451+
for (const jobId of jobIds) {
452+
this.onJobChange?.(null, jobId)
459453
}
460-
deleteScheduleJobsByRepo(this.db, repoId)
461454
}
462455

463456
/**

backend/test/db/schedules.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,19 @@ describe('schedule database queries', () => {
7272
})
7373
})
7474

75+
it('lists schedule job ids without loading full job rows', () => {
76+
const stmt = {
77+
all: vi.fn().mockReturnValue([{ id: 7 }, { id: 8 }]),
78+
}
79+
mockDb.prepare.mockReturnValue(stmt)
80+
81+
const jobIds = schedulesDb.listScheduleJobIdsByRepo(mockDb, 42)
82+
83+
expect(mockDb.prepare).toHaveBeenCalledWith('SELECT id FROM schedule_jobs WHERE repo_id = ? ORDER BY created_at DESC')
84+
expect(stmt.all).toHaveBeenCalledWith(42)
85+
expect(jobIds).toEqual([7, 8])
86+
})
87+
7588
it('creates a schedule job and reloads the inserted row', () => {
7689
const insertStmt = {
7790
run: vi.fn().mockReturnValue({ lastInsertRowid: 7 }),

backend/test/services/schedules.test.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ const mocks = vi.hoisted(() => ({
66
createScheduleJob: vi.fn(),
77
createScheduleRun: vi.fn(),
88
deleteScheduleJob: vi.fn(),
9-
deleteScheduleJobsByRepo: vi.fn(),
109
cleanupOrphanedSchedules: vi.fn(),
1110
getScheduleJobById: vi.fn(),
1211
getRunningScheduleRunByJob: vi.fn(),
1312
getScheduleRunById: vi.fn(),
1413
listEnabledScheduleJobs: vi.fn(),
1514
listRunningScheduleRuns: vi.fn(),
15+
listScheduleJobIdsByRepo: vi.fn(),
1616
listScheduleJobsByRepo: vi.fn(),
1717
listScheduleRunsByJob: vi.fn(),
1818
updateScheduleJob: vi.fn(),
@@ -37,13 +37,13 @@ vi.mock('../../src/db/schedules', () => ({
3737
createScheduleJob: mocks.createScheduleJob,
3838
createScheduleRun: mocks.createScheduleRun,
3939
deleteScheduleJob: mocks.deleteScheduleJob,
40-
deleteScheduleJobsByRepo: mocks.deleteScheduleJobsByRepo,
4140
cleanupOrphanedSchedules: mocks.cleanupOrphanedSchedules,
4241
getScheduleJobById: mocks.getScheduleJobById,
4342
getRunningScheduleRunByJob: mocks.getRunningScheduleRunByJob,
4443
getScheduleRunById: mocks.getScheduleRunById,
4544
listEnabledScheduleJobs: mocks.listEnabledScheduleJobs,
4645
listRunningScheduleRuns: mocks.listRunningScheduleRuns,
46+
listScheduleJobIdsByRepo: mocks.listScheduleJobIdsByRepo,
4747
listScheduleJobsByRepo: mocks.listScheduleJobsByRepo,
4848
listScheduleRunsByJob: mocks.listScheduleRunsByJob,
4949
updateScheduleJob: mocks.updateScheduleJob,
@@ -728,6 +728,20 @@ describe('ScheduleService', () => {
728728
expect(() => service.getRun(42, 7, 5)).toThrow('Run not found')
729729
})
730730

731+
it('prepares repo deletion by unregistering repo jobs without deleting records', () => {
732+
const service = new ScheduleService({} as never, createOpenCodeClientStub())
733+
const onJobChange = vi.fn()
734+
service.setJobChangeHandler(onJobChange)
735+
mocks.listScheduleJobIdsByRepo.mockReturnValue([7, 8])
736+
737+
service.prepareRepoDelete(42)
738+
739+
expect(mocks.listScheduleJobIdsByRepo).toHaveBeenCalledWith(expect.anything(), 42)
740+
expect(onJobChange).toHaveBeenCalledWith(null, 7)
741+
expect(onJobChange).toHaveBeenCalledWith(null, 8)
742+
expect(onJobChange).toHaveBeenCalledTimes(2)
743+
})
744+
731745
it('cancels by finalizing the run when the assistant already completed', async () => {
732746
const service = new ScheduleService({} as never, createOpenCodeClientStub())
733747
const runningRun: ScheduleRun = {

frontend/src/pages/__tests__/Schedules.test.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ function createMockScheduleUrlState(overrides: Record<string, unknown> = {}) {
9090
openImportTemplate: vi.fn(),
9191
closeDialog: vi.fn(),
9292
closePromptDialog: vi.fn(),
93-
selectJob: vi.fn(),
9493
selectRun: vi.fn(),
9594
selectJobAndView: vi.fn(),
9695
selectJobAndCloseDialog: vi.fn(),

0 commit comments

Comments
 (0)