@@ -353,4 +353,172 @@ public function test_sync_handles_mixed_scenarios_with_deletions(): void
353353 'site_id ' => null ,
354354 ]);
355355 }
356+
357+ public function test_sync_normalizes_frequency_with_extra_spaces (): void
358+ {
359+ // Create a cronjob with normal spacing
360+ $ existingCronJob = CronJob::factory ()->create ([
361+ 'server_id ' => $ this ->server ->id ,
362+ 'user ' => 'root ' ,
363+ 'command ' => '/usr/bin/backup.sh ' ,
364+ 'frequency ' => '5 15 * * * ' ,
365+ 'status ' => CronjobStatus::READY ,
366+ 'site_id ' => null ,
367+ ]);
368+
369+ // Mock SSH to return the same cronjob with extra spaces
370+ SSH ::fake ('5 15 * * * /usr/bin/backup.sh ' );
371+
372+ $ this ->actingAs ($ this ->user )
373+ ->post (route ('cronjobs.sync ' , $ this ->server ))
374+ ->assertRedirect ()
375+ ->assertSessionHas ('success ' , 'Cron jobs synced successfully. ' );
376+
377+ // Should not create duplicate, existing cronjob should remain
378+ $ cronJobs = CronJob::where ('server_id ' , $ this ->server ->id )
379+ ->where ('command ' , '/usr/bin/backup.sh ' )
380+ ->where ('site_id ' , null )
381+ ->get ();
382+
383+ // Should only have the one existing cronjob for each user (root + vito = 2 total)
384+ $ this ->assertCount (2 , $ cronJobs );
385+
386+ // The original cronjob should still be ready
387+ $ existingCronJob ->refresh ();
388+ $ this ->assertEquals (CronjobStatus::READY , $ existingCronJob ->status );
389+ }
390+
391+ public function test_sync_recognizes_site_level_cronjobs (): void
392+ {
393+ // Create a site-level cronjob with the same command as what will be on the server
394+ $ siteCronJob = CronJob::factory ()->create ([
395+ 'server_id ' => $ this ->server ->id ,
396+ 'user ' => 'root ' ,
397+ 'command ' => '/usr/bin/backup.sh ' ,
398+ 'frequency ' => '5 15 * * * ' ,
399+ 'status ' => CronjobStatus::READY ,
400+ 'site_id ' => $ this ->site ->id ,
401+ ]);
402+
403+ // Mock SSH to return a cronjob with the same frequency and command
404+ SSH ::fake ('5 15 * * * /usr/bin/backup.sh ' );
405+
406+ $ countBefore = CronJob::where ('server_id ' , $ this ->server ->id )
407+ ->where ('command ' , '/usr/bin/backup.sh ' )
408+ ->count ();
409+
410+ $ this ->actingAs ($ this ->user )
411+ ->post (route ('cronjobs.sync ' , $ this ->server ))
412+ ->assertRedirect ()
413+ ->assertSessionHas ('success ' , 'Cron jobs synced successfully. ' );
414+
415+ $ countAfter = CronJob::where ('server_id ' , $ this ->server ->id )
416+ ->where ('command ' , '/usr/bin/backup.sh ' )
417+ ->count ();
418+
419+ // Before fix: would create duplicate with site_id = null
420+ // After fix: recognizes site-level cronjob and doesn't duplicate it, only creates for vito user
421+ // countBefore = 1 (site-level), countAfter should be 2 (site-level + vito user)
422+ $ this ->assertEquals ($ countBefore + 1 , $ countAfter );
423+
424+ // The site-level cronjob should remain unchanged
425+ $ siteCronJob ->refresh ();
426+ $ this ->assertEquals ($ this ->site ->id , $ siteCronJob ->site_id );
427+ $ this ->assertEquals (CronjobStatus::READY , $ siteCronJob ->status );
428+ }
429+
430+ public function test_sync_handles_frequency_with_mixed_spacing_in_db (): void
431+ {
432+ // Create a cronjob with extra spaces in the frequency (simulating old data)
433+ $ existingCronJob = CronJob::factory ()->create ([
434+ 'server_id ' => $ this ->server ->id ,
435+ 'user ' => 'root ' ,
436+ 'command ' => '/usr/bin/backup.sh ' ,
437+ 'frequency ' => '5 15 * * * ' , // Double spaces
438+ 'status ' => CronjobStatus::READY ,
439+ 'site_id ' => null ,
440+ ]);
441+
442+ // Mock SSH to return the same cronjob with normalized spacing
443+ SSH ::fake ('5 15 * * * /usr/bin/backup.sh ' );
444+
445+ $ this ->actingAs ($ this ->user )
446+ ->post (route ('cronjobs.sync ' , $ this ->server ))
447+ ->assertRedirect ()
448+ ->assertSessionHas ('success ' , 'Cron jobs synced successfully. ' );
449+
450+ // Should not create duplicate
451+ $ cronJobs = CronJob::where ('server_id ' , $ this ->server ->id )
452+ ->where ('command ' , '/usr/bin/backup.sh ' )
453+ ->where ('site_id ' , null )
454+ ->get ();
455+
456+ // Should only have the one existing cronjob for each user (root + vito = 2 total)
457+ $ this ->assertCount (2 , $ cronJobs );
458+
459+ // The original cronjob should still be ready
460+ $ existingCronJob ->refresh ();
461+ $ this ->assertEquals (CronjobStatus::READY , $ existingCronJob ->status );
462+ }
463+
464+ public function test_sync_ignores_crontab_documentation_comments (): void
465+ {
466+ // Mock SSH to return crontab with documentation comments (like the default crontab header)
467+ $ crontabWithComments = '# Edit this file to introduce tasks to be run by cron.
468+ #
469+ # Each task to run has to be defined through a single line
470+ # m h dom mon dow command
471+ #
472+ 0 2 * * * /usr/bin/backup.sh ' ;
473+
474+ SSH ::fake ($ crontabWithComments );
475+
476+ $ this ->actingAs ($ this ->user )
477+ ->post (route ('cronjobs.sync ' , $ this ->server ))
478+ ->assertRedirect ()
479+ ->assertSessionHas ('success ' , 'Cron jobs synced successfully. ' );
480+
481+ // Should only create cronjobs for the actual cron line, not the documentation comments
482+ $ cronJobs = CronJob::where ('server_id ' , $ this ->server ->id )->get ();
483+
484+ // Should have 2 cronjobs (1 for root, 1 for vito), not 6 (which would include the comment lines)
485+ $ this ->assertCount (2 , $ cronJobs );
486+
487+ // Both should have the actual backup command
488+ $ this ->assertTrue ($ cronJobs ->every (fn ($ cronJob ) => $ cronJob ->command === '/usr/bin/backup.sh ' ));
489+ }
490+
491+ public function test_sync_normalizes_command_with_extra_spaces (): void
492+ {
493+ // Create a cronjob with normal spacing in command
494+ $ existingCronJob = CronJob::factory ()->create ([
495+ 'server_id ' => $ this ->server ->id ,
496+ 'user ' => 'root ' ,
497+ 'command ' => 'ls -la ' ,
498+ 'frequency ' => '* * * * * ' ,
499+ 'status ' => CronjobStatus::READY ,
500+ 'site_id ' => null ,
501+ ]);
502+
503+ // Mock SSH to return the same cronjob with extra spaces in command
504+ SSH ::fake ('* * * * * ls -la ' );
505+
506+ $ this ->actingAs ($ this ->user )
507+ ->post (route ('cronjobs.sync ' , $ this ->server ))
508+ ->assertRedirect ()
509+ ->assertSessionHas ('success ' , 'Cron jobs synced successfully. ' );
510+
511+ // Should not create duplicate, existing cronjob should remain
512+ $ cronJobs = CronJob::where ('server_id ' , $ this ->server ->id )
513+ ->where ('site_id ' , null )
514+ ->get ();
515+
516+ // Should only have the one existing cronjob for each user (root + vito = 2 total)
517+ $ this ->assertCount (2 , $ cronJobs );
518+
519+ // The original cronjob should still be ready
520+ $ existingCronJob ->refresh ();
521+ $ this ->assertEquals (CronjobStatus::READY , $ existingCronJob ->status );
522+ $ this ->assertEquals ('ls -la ' , $ existingCronJob ->command );
523+ }
356524}
0 commit comments