mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-02 20:30:10 +00:00
Add job descriptions and stale-name renames
Show human-friendly per-job descriptions on the Admin Jobs page (JOB_DESCRIPTIONS) and remove the old "About Scheduled Jobs" info box. Add STALE_NAME_REWRITES and renameStaleJobNames() in SchedulerService to automatically rewrite legacy exact-literal job names (e.g. "Plex Library Scan") to neutral defaults on startup; updates are type-gated and use updateMany with exact matches so admin-customized names are not touched. Log successful renames and swallow rename errors so startup remains idempotent. Tests and documentation were updated to reflect the new UI text and to cover rename behavior.
This commit is contained in:
@@ -43,7 +43,7 @@ describe('AdminJobsPage', () => {
|
||||
{
|
||||
id: 'job-1',
|
||||
name: 'Library Scan',
|
||||
type: 'scan_plex',
|
||||
type: 'plex_library_scan',
|
||||
schedule: '0 * * * *',
|
||||
enabled: true,
|
||||
lastRun: null,
|
||||
@@ -56,6 +56,11 @@ describe('AdminJobsPage', () => {
|
||||
render(<AdminJobsPage />);
|
||||
|
||||
expect((await screen.findAllByText('Library Scan'))[0]).toBeInTheDocument();
|
||||
expect(
|
||||
(await screen.findAllByText('Scans your full media library to detect newly added audiobooks.'))[0]
|
||||
).toBeInTheDocument();
|
||||
expect(screen.queryByText('plex_library_scan')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('About Scheduled Jobs')).not.toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getAllByRole('button', { name: /Trigger Now/i })[0]);
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Trigger Job' }));
|
||||
|
||||
@@ -504,4 +504,45 @@ describe('SchedulerService', () => {
|
||||
|
||||
await expect(service.triggerJobNow('job-10')).rejects.toThrow('Audiobookshelf is not configured');
|
||||
});
|
||||
|
||||
it('rewrites stale literal job names with type-gated updateMany on startup', async () => {
|
||||
prismaMock.scheduledJob.findFirst.mockResolvedValue({ id: 'existing' });
|
||||
prismaMock.scheduledJob.updateMany.mockResolvedValue({ count: 1 });
|
||||
prismaMock.scheduledJob.findMany
|
||||
.mockResolvedValueOnce([]) // cleanupDeprecatedJobs
|
||||
.mockResolvedValueOnce([]) // scheduleAllJobs
|
||||
.mockResolvedValue([]); // triggerOverdueJobs
|
||||
|
||||
const { SchedulerService } = await import('@/lib/services/scheduler.service');
|
||||
const service = new SchedulerService();
|
||||
await service.start();
|
||||
|
||||
const updateManyCalls = prismaMock.scheduledJob.updateMany.mock.calls;
|
||||
expect(updateManyCalls).toHaveLength(2);
|
||||
|
||||
// Type-gating safety: each WHERE must match BOTH name AND type exact-equals.
|
||||
expect(updateManyCalls[0][0]).toEqual({
|
||||
where: { name: 'Plex Library Scan', type: 'plex_library_scan' },
|
||||
data: { name: 'Library Scan' },
|
||||
});
|
||||
expect(updateManyCalls[1][0]).toEqual({
|
||||
where: { name: 'Plex Recently Added Check', type: 'plex_recently_added_check' },
|
||||
data: { name: 'Recently Added Check' },
|
||||
});
|
||||
});
|
||||
|
||||
it('swallows rename errors and continues startup (idempotent)', async () => {
|
||||
prismaMock.scheduledJob.findFirst.mockResolvedValue({ id: 'existing' });
|
||||
prismaMock.scheduledJob.updateMany.mockRejectedValue(new Error('db blip'));
|
||||
prismaMock.scheduledJob.findMany
|
||||
.mockResolvedValueOnce([]) // cleanupDeprecatedJobs
|
||||
.mockResolvedValueOnce([]) // scheduleAllJobs
|
||||
.mockResolvedValue([]); // triggerOverdueJobs
|
||||
|
||||
const { SchedulerService } = await import('@/lib/services/scheduler.service');
|
||||
const service = new SchedulerService();
|
||||
|
||||
// Must not throw — rename failure is non-fatal.
|
||||
await expect(service.start()).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user