diff --git a/README.md b/README.md index 153e107..d31e15e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ -⚠️ Work in Progress -Note: This package is currently under development and not ready for production use. +# ⚠️ Work in Progress +#### Note: This package is currently under development and not ready for production use. +-- + <p align="center"> <img height="100%" src="assets/cover.png" alt="Laravel Package Skeleton Logo"/> @@ -31,7 +33,14 @@ Fasti::schedule($job, '2024-12-31 23:59:59'); // Schedule a job for New Year's E composer require a-bashtannik/fasti ``` -Fasti is built with developers in mind. +Add the tick command to your `console.php` file: + +```php +use Bashtannik\Fasti\Console\Commands\FastiTickCommand; + +Schedule::command(FastiTickCommand::class)->everyMinute(); + +``` ## Usage @@ -83,11 +92,12 @@ Fasti includes a lightweight Eloquent model that stores essential job informatio use \Bashtannik\Fasti\Facades\Fasti; -Fasti::all(); // Retrieve all scheduled tasks -Fasti::scheduled($at); // Retrieve all tasks scheduled at a specific time -Fasti::cancel($id); // Cancel a scheduled task -Fasti::cancelled(); // Retrieve all canceled tasks -Fasti::find($id); // Retrieve a scheduled task by ID +Fasti::all(); +Fasti::schedule($job, $at); +Fasti::scheduled($at); // List all jobs scheduled for a specific time +Fasti::cancel($job); +Fasti::cancelled(); // List all cancelled jobs +Fasti::find($id); // Or use the Eloquent model directly @@ -104,15 +114,30 @@ Test your scheduled jobs with ease using Fasti's `Fasti::fake()` method and buil ```php use \Bashtannik\Fasti\Facades\Fasti; -Fasti::fake(); +// If you are using custom model or repository, avoid using this method and test real storage instead. + +Fasti::fake(); + +// Test if the expected job is scheduled Fasti::assertScheduled($job); -Fasti::assertNotScheduled($job); -Fasti::assertScheduledAt(); -Fasti::assertNotScheduledAt(); +// Or by job class name + +Fasti::assertScheduled(SendGreetingEmail::class); -Fasti::assertCancelled() +// Add custom assertion by using callback + +Fasti::assertScheduled(function ($job) { + return $job->type === 'send_greeting_email'; +}); + +// Many other assertions are available + +Fasti::assertNotScheduled($job); +Fasti::assertScheduledAt($job, '2024-12-31 23:59:59'); +Fasti::assertNotScheduledAt($job, '2024-12-31 23:59:59'); +Fasti::assertCancelled($job); ``` @@ -120,7 +145,7 @@ Fasti operates as a scheduling layer for your Laravel jobs, focusing solely on w Instead, when the scheduled time arrives, Fasti simply hands off the job to Laravel's default `Bus`. -It means you are free to use well-known Bus assertion methods shipped with Laravel. +It means you are free to use well-known Bus assertion methods shipped with Laravel when the job is dispatched to the queue. ```php @@ -149,7 +174,7 @@ class MyOwnModel extends Model implements SchedulableJob ``` -Create your own repository that implements the `FastiRepository` interface and bind it in your app service provider `register()` method. +Create your own repository that implements the `FastiScheduledJobsRepository` interface and bind it in your app service provider `register()` method. ```php use Bashtannik\Fasti\Repositories\FastiRepository; @@ -159,20 +184,31 @@ $this->app->bind( FastiScheduledJobsRepository::class, MyOwnRepository::class ); -``` - -Or use more context-aware approach and switch your repository on the fly. -```php -use \Bashtannik\Fasti\Facades\Fasti; +// Or if you just need to switch the model -$repository = new MyOwnRepository( - user: $user, - company: $company, +$this->app->bind( + FastiScheduledJobsRepository::class, + function () { + $repository = new FastiEloquentRepository; + $respository::$model = MyOwnModel::class; + + return $repository; + } ); +``` -Fasti::setRepository($repository); +### Hint: store human-friendly job type name + +When using `FastiEloquentRepository` repository, by default it stores class name in the `type` field. +However, you can define your own set of human-friendly names in your AppServiceProvider like you do with morph-many relationships. + +```php +use Bashtannik\Fasti\Repositories\FastiEloquentRepository; +FastiEloquentRepository::enforceTypeMapping([ + 'fake_job' => FakeJob::class, +]); ``` -You are done, now Fasti will use your custom repository to manage scheduled jobs. +This field doesn't affect the job execution but can be useful for debugging or logging purposes. diff --git a/assets/cover1.png b/assets/cover1.png new file mode 100644 index 0000000..6d4ef88 Binary files /dev/null and b/assets/cover1.png differ diff --git a/composer.json b/composer.json index 282f321..f37e2c1 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,7 @@ { "name": "a-bashtannik/fasti", "description": "Laravel task scheduler with calendar-based management.", + "version": "dev-main", "keywords": [ "laravel", "package", @@ -43,6 +44,16 @@ "Bashtannik\\Fasti\\Tests\\": "tests/" } }, + "extra": { + "laravel": { + "providers": [ + "Bashtannik\\Fasti\\Providers\\FastiServiceProvider" + ], + "aliases": { + "Fasti": "Bashtannik\\Fasti\\Facades\\Fasti" + } + } + }, "require": { "php": "^8.2" }, diff --git a/database/migrations/2024_09_28_000000_create_scheduled_jobs_table.php b/database/migrations/2024_09_28_000000_create_scheduled_jobs_table.php index 2aeced1..117f83f 100644 --- a/database/migrations/2024_09_28_000000_create_scheduled_jobs_table.php +++ b/database/migrations/2024_09_28_000000_create_scheduled_jobs_table.php @@ -10,6 +10,7 @@ public function up(): void { Schema::create('scheduled_jobs', function (Blueprint $table) { $table->id(); + $table->string('type'); $table->longText('payload'); $table->dateTime('scheduled_at'); $table->dateTime('cancelled_at')->nullable(); @@ -18,6 +19,6 @@ public function up(): void public function down(): void { - Schema::dropIfExists('scheduled_tasks'); + Schema::dropIfExists('scheduled_jobs'); } }; diff --git a/phpunit.xml b/phpunit.xml index 5cec5dc..28cb2fd 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -12,7 +12,7 @@ </testsuites> <source> <include> - <directory suffix=".php">./app</directory> + <directory suffix=".php">./src</directory> </include> </source> </phpunit> diff --git a/src/Console/Commands/FastiListCommand.php b/src/Console/Commands/FastiListCommand.php index c32568a..34d1f02 100644 --- a/src/Console/Commands/FastiListCommand.php +++ b/src/Console/Commands/FastiListCommand.php @@ -9,29 +9,29 @@ class FastiListCommand extends Command { - protected $signature = 'fasti:list --all'; + protected $signature = 'fasti:list'; protected $description = 'List all scheduled jobs, add --all to include cancelled jobs.'; public function handle(FastiService $fasti): void { - $all = $this->option('all'); - - $jobs = $all - ? $fasti->all() - : $fasti->scheduled(now()); + $jobs = $fasti->all(); table( array_values([ 'ID', + 'Type', 'Scheduled at', - 'Canceled at', + 'Cancelled at', ]), - $jobs->only([ - 'id', - 'scheduled_at', - 'canceled_at', - ])->toArray() + $jobs->map(function ($job) { + return [ + 'id' => $job->id, + 'type' => $job->type, + 'scheduled_at' => $job->scheduled_at, + 'cancelled_at' => $job->cancelled_at ?? '-', + ]; + })->toArray() ); } } diff --git a/src/Contracts/JobDispatcher.php b/src/Contracts/JobDispatcher.php new file mode 100644 index 0000000..d34fb16 --- /dev/null +++ b/src/Contracts/JobDispatcher.php @@ -0,0 +1,12 @@ +<?php + +declare(strict_types=1); + +namespace Bashtannik\Fasti\Contracts; + +interface JobDispatcher +{ + public function dispatch(mixed $job): mixed; + + public function dispatchSync(mixed $job): mixed; +} diff --git a/src/Contracts/SchedulableJob.php b/src/Contracts/SchedulableJob.php index a5e7d1c..cd9d4a9 100644 --- a/src/Contracts/SchedulableJob.php +++ b/src/Contracts/SchedulableJob.php @@ -10,6 +10,7 @@ * A job that can be scheduled and canceled. * * @property int|string $id + * @property string $type * @property string $payload * @property CarbonInterface $scheduled_at * @property CarbonInterface|null $cancelled_at diff --git a/src/Events/JobCancelled.php b/src/Events/JobCancelled.php new file mode 100644 index 0000000..f842c01 --- /dev/null +++ b/src/Events/JobCancelled.php @@ -0,0 +1,12 @@ +<?php + +declare(strict_types=1); + +namespace Bashtannik\Fasti\Events; + +use Bashtannik\Fasti\Contracts\SchedulableJob; + +class JobCancelled +{ + public function __construct(public SchedulableJob $job) {} +} diff --git a/src/Events/JobDispatched.php b/src/Events/JobDispatched.php new file mode 100644 index 0000000..4100f7c --- /dev/null +++ b/src/Events/JobDispatched.php @@ -0,0 +1,12 @@ +<?php + +declare(strict_types=1); + +namespace Bashtannik\Fasti\Events; + +use Bashtannik\Fasti\Contracts\SchedulableJob; + +class JobDispatched +{ + public function __construct(public SchedulableJob $job) {} +} diff --git a/src/Events/JobScheduled.php b/src/Events/JobScheduled.php new file mode 100644 index 0000000..90c4eba --- /dev/null +++ b/src/Events/JobScheduled.php @@ -0,0 +1,12 @@ +<?php + +declare(strict_types=1); + +namespace Bashtannik\Fasti\Events; + +use Bashtannik\Fasti\Contracts\SchedulableJob; + +class JobScheduled +{ + public function __construct(public SchedulableJob $job) {} +} diff --git a/src/Facades/Fasti.php b/src/Facades/Fasti.php index 5d16627..989f9d1 100644 --- a/src/Facades/Fasti.php +++ b/src/Facades/Fasti.php @@ -6,6 +6,7 @@ use Bashtannik\Fasti\Contracts\SchedulableJob; use Bashtannik\Fasti\Repositories\FastiArrayRepository; +use Bashtannik\Fasti\Services\BusJobDispatcher; use Bashtannik\Fasti\Services\FastiService; use Carbon\CarbonInterface; use Closure; @@ -16,9 +17,9 @@ /** * @method static Collection<int, SchedulableJob> all() - * @method static int|string schedule(object $job, DateTimeInterface|CarbonInterface $at) - * @method static Collection<int, SchedulableJob> scheduled(DateTimeInterface|CarbonInterface $at) - * @method static void cancel(int|string $id) + * @method static SchedulableJob schedule(object $job, DateTimeInterface|CarbonInterface $at) + * @method static Collection<int, SchedulableJob> scheduled(DateTimeInterface|CarbonInterface|string $at) + * @method static void cancel(int|string|SchedulableJob $id) * @method static Collection<int, SchedulableJob> cancelled() * @method static SchedulableJob find(int|string $id) * @method static void dispatch(int|string $id) @@ -30,7 +31,7 @@ public static function fake(): void { $fakeRepository = new FastiArrayRepository; - $fake = new FastiService($fakeRepository); + $fake = new FastiService($fakeRepository, new BusJobDispatcher); static::swap($fake); } @@ -40,64 +41,79 @@ protected static function getFacadeAccessor(): string return 'fasti'; } - public static function assertScheduled(object $job, ?Closure $closure = null): void + public static function assertScheduled(object|string $job, ?Closure $closure = null): void { $scheduled = static::getFacadeRoot()->all(); PHPUnit::assertTrue( - $scheduled->contains(fn (SchedulableJob $scheduledJob): bool => $scheduledJob->payload === serialize($job) - && (! $closure instanceof \Closure || $closure($scheduledJob)) - ), + $scheduled->contains( + function (SchedulableJob $scheduledJob) use ($job, $closure): bool { + $isSame = is_string($job) ? unserialize($scheduledJob->payload)::class === $job : $scheduledJob->payload === serialize($job); + + return $isSame && (! $closure instanceof \Closure || $closure($scheduledJob)); + }), 'The job was not scheduled.' ); } - public static function assertNotScheduled(object $job, ?Closure $closure = null): void + public static function assertNotScheduled(object|string $job, ?Closure $closure = null): void { $scheduled = static::getFacadeRoot()->all(); PHPUnit::assertFalse( - $scheduled->contains(fn (SchedulableJob $scheduledJob): bool => $scheduledJob->payload === serialize($job) - && (! $closure instanceof \Closure || $closure($scheduledJob)) - ), - 'The job was scheduled.' + $scheduled->contains( + function (SchedulableJob $scheduledJob) use ($job, $closure): bool { + $isSame = is_string($job) ? unserialize($scheduledJob->payload)::class === $job : $scheduledJob->payload === serialize($job); + + return $isSame && (! $closure instanceof \Closure || $closure($scheduledJob)); + }), + 'The job was not scheduled.' ); } - public static function assertScheduledAt(object $job, DateTimeInterface|CarbonInterface $at, ?Closure $closure = null): void + public static function assertScheduledAt(object|string $job, DateTimeInterface|CarbonInterface|string $at, ?Closure $closure = null): void { $scheduled = static::getFacadeRoot()->all(); PHPUnit::assertTrue( - $scheduled->contains(fn (SchedulableJob $scheduledJob): bool => $scheduledJob->payload === serialize($job) - && $scheduledJob->scheduled_at->isSameMinute($at) - && (! $closure instanceof \Closure || $closure($scheduledJob)) - ), + $scheduled->contains(function (SchedulableJob $scheduledJob) use ($job, $closure, $at): bool { + $isSame = is_string($job) ? unserialize($scheduledJob->payload)::class === $job : $scheduledJob->payload === serialize($job); + + return $isSame + && $scheduledJob->scheduled_at->isSameMinute($at) + && (! $closure instanceof \Closure || $closure($scheduledJob)); + }), 'The job was not scheduled at the specified time.' ); } - public static function assertNotScheduledAt(object $job, DateTimeInterface|CarbonInterface $at, ?Closure $closure = null): void + public static function assertNotScheduledAt(object|string $job, DateTimeInterface|CarbonInterface|string $at, ?Closure $closure = null): void { $scheduled = static::getFacadeRoot()->all(); PHPUnit::assertFalse( - $scheduled->contains(fn (SchedulableJob $scheduledJob): bool => $scheduledJob->payload === serialize($job) - && $scheduledJob->scheduled_at->isSameMinute($at) - && (! $closure instanceof \Closure || $closure($scheduledJob)) - ), + $scheduled->contains(function (SchedulableJob $scheduledJob) use ($job, $closure, $at): bool { + $isSame = is_string($job) ? unserialize($scheduledJob->payload)::class === $job : $scheduledJob->payload === serialize($job); + + return $isSame + && $scheduledJob->scheduled_at->isSameMinute($at) + && (! $closure instanceof \Closure || $closure($scheduledJob)); + }), 'The job was scheduled at the specified time.' ); } - public static function assertCancelled(object $job, ?Closure $closure = null): void + public static function assertCancelled(object|string $job, ?Closure $closure = null): void { $canceled = static::getFacadeRoot()->cancelled(); PHPUnit::assertTrue( - $canceled->contains(fn (SchedulableJob $canceledJob): bool => $canceledJob->payload === serialize($job) - && (! $closure instanceof \Closure || $closure($canceledJob)) - ), + $canceled->contains( + function (SchedulableJob $scheduledJob) use ($job, $closure): bool { + $isSame = is_string($job) ? unserialize($scheduledJob->payload)::class === $job : $scheduledJob->payload === serialize($job); + + return $isSame && (! $closure instanceof \Closure || $closure($scheduledJob)); + }), 'The job was not canceled.' ); } diff --git a/src/Models/ScheduledJob.php b/src/Models/ScheduledJob.php index 4bed25d..57bf312 100644 --- a/src/Models/ScheduledJob.php +++ b/src/Models/ScheduledJob.php @@ -8,6 +8,7 @@ /** * @property int $id + * @property string $type * @property string $payload * @property CarbonImmutable|null $cancelled_at * @property CarbonImmutable $scheduled_at @@ -18,6 +19,8 @@ class ScheduledJob extends Model implements SchedulableJob protected $guarded = []; + protected $hidden = ['payload']; + /** * @return array<string,string> */ diff --git a/src/Providers/FastiServiceProvider.php b/src/Providers/FastiServiceProvider.php index 545c526..b561767 100644 --- a/src/Providers/FastiServiceProvider.php +++ b/src/Providers/FastiServiceProvider.php @@ -2,10 +2,14 @@ namespace Bashtannik\Fasti\Providers; +use Bashtannik\Fasti\Console\Commands\FastiListCommand; use Bashtannik\Fasti\Console\Commands\FastiTickCommand; +use Bashtannik\Fasti\Contracts\JobDispatcher; use Bashtannik\Fasti\Repositories\FastiEloquentRepository; use Bashtannik\Fasti\Repositories\FastiScheduledJobsRepository; +use Bashtannik\Fasti\Services\BusJobDispatcher; use Bashtannik\Fasti\Services\FastiService; +use Illuminate\Foundation\Console\AboutCommand; use Illuminate\Support\ServiceProvider; class FastiServiceProvider extends ServiceProvider @@ -21,13 +25,24 @@ public function register(): void FastiService::class, 'fasti' ); + + $this->app->when(FastiService::class) + ->needs(JobDispatcher::class) + ->give(BusJobDispatcher::class); } public function boot(): void { + AboutCommand::add('Fasti', fn (): array => ['Version' => '1.0.0']); + if ($this->app->runningInConsole()) { $this->commands([ FastiTickCommand::class, + FastiListCommand::class, + ]); + + $this->publishesMigrations([ + __DIR__.'/../../database/migrations' => database_path('migrations'), ]); } } diff --git a/src/Repositories/FastiArrayRepository.php b/src/Repositories/FastiArrayRepository.php index 4a29cb4..e86c8bf 100644 --- a/src/Repositories/FastiArrayRepository.php +++ b/src/Repositories/FastiArrayRepository.php @@ -23,16 +23,19 @@ public function all(): Collection return collect($this->jobsToFake); } - public function store(object $job, DateTimeInterface|CarbonInterface $dateTime): int|string + public function store(object $job, DateTimeInterface|CarbonInterface $dateTime): SchedulableJob { - $this->jobsToFake[] = new GenericScheduledJob( + $scheduledJob = new GenericScheduledJob( id: count($this->jobsToFake), + type: $job::class, payload: serialize($job), scheduled_at: Carbon::instance($dateTime), cancelled_at: null, ); - return count($this->jobsToFake) - 1; + $this->jobsToFake[] = $scheduledJob; + + return $scheduledJob; } public function scheduled(DateTimeInterface|CarbonInterface $at): Collection @@ -42,9 +45,13 @@ public function scheduled(DateTimeInterface|CarbonInterface $at): Collection ); } - public function cancel(int|string $id): void + public function cancel(int|string|SchedulableJob $id): SchedulableJob { - $this->jobsToFake[$id]->cancelled_at = now(); + $key = $id instanceof SchedulableJob ? $id->id : $id; + + $this->jobsToFake[$key]->cancelled_at = now(); + + return $this->jobsToFake[$key]; } public function cancelled(): Collection diff --git a/src/Repositories/FastiEloquentRepository.php b/src/Repositories/FastiEloquentRepository.php index 15f41d8..2f6cdd5 100644 --- a/src/Repositories/FastiEloquentRepository.php +++ b/src/Repositories/FastiEloquentRepository.php @@ -15,26 +15,46 @@ class FastiEloquentRepository implements FastiScheduledJobsRepository { + public static string $model = ScheduledJob::class; + + /** + * @var array<string, class-string> + */ + public static array $morphMap = []; + public function all(): Collection { - return ScheduledJob::all(); + return static::$model::all(); } - public function store(object $job, DateTimeInterface|CarbonInterface $dateTime): int|string + /** + * @param array<string, class-string> $morphMap + */ + public static function enforceTypeMap(array $morphMap): void { - $task = new ScheduledJob; + static::$morphMap = $morphMap; + } - if ($job instanceof ShouldBeEncrypted) { - $task->payload = encrypt(serialize(clone $job)); + public function store(object $job, DateTimeInterface|CarbonInterface $dateTime): SchedulableJob + { + $scheduledJob = new static::$model; + + if (count(self::$morphMap) && isset(array_flip(self::$morphMap)[$job::class])) { + $scheduledJob->type = array_flip(self::$morphMap)[$job::class]; } else { - $task->payload = serialize(clone $job); + $scheduledJob->type = $job::class; } - $task->scheduled_at = CarbonImmutable::instance($dateTime); + if ($job instanceof ShouldBeEncrypted) { + $scheduledJob->payload = encrypt(serialize(clone $job)); + } else { + $scheduledJob->payload = serialize(clone $job); + } - $task->save(); + $scheduledJob->scheduled_at = CarbonImmutable::instance($dateTime); + $scheduledJob->save(); - return $task->id; + return $scheduledJob; } public function scheduled(DateTimeInterface|CarbonInterface $at): Collection @@ -42,26 +62,28 @@ public function scheduled(DateTimeInterface|CarbonInterface $at): Collection $from = Carbon::instance($at)->startOf('minute'); $to = Carbon::instance($at)->endOf('minute'); - return ScheduledJob::query() + return static::$model::query() ->whereNull('cancelled_at') ->whereBetween('scheduled_at', [$from, $to])->get(); } - public function cancel(string|int $id): void + public function cancel(string|int|SchedulableJob $id): SchedulableJob { - $job = ScheduledJob::query()->findOrFail($id); + $scheduledJob = static::$model::query()->findOrFail($id instanceof SchedulableJob ? $id->id : $id); + + $scheduledJob->cancelled_at = now()->toImmutable(); + $scheduledJob->save(); - $job->cancelled_at = now()->toImmutable(); - $job->save(); + return $scheduledJob; } public function cancelled(): Collection { - return ScheduledJob::query()->whereNotNull('cancelled_at')->get(); + return static::$model::query()->whereNotNull('cancelled_at')->get(); } public function find(int|string $id): SchedulableJob { - return ScheduledJob::query()->findOrFail($id); + return static::$model::query()->findOrFail($id); } } diff --git a/src/Repositories/FastiScheduledJobsRepository.php b/src/Repositories/FastiScheduledJobsRepository.php index 6f372b0..a348839 100644 --- a/src/Repositories/FastiScheduledJobsRepository.php +++ b/src/Repositories/FastiScheduledJobsRepository.php @@ -16,14 +16,14 @@ interface FastiScheduledJobsRepository */ public function all(): Collection; - public function store(object $job, DateTimeInterface|CarbonInterface $dateTime): int|string; + public function store(object $job, DateTimeInterface|CarbonInterface $dateTime): SchedulableJob; /** * @return Collection<int, SchedulableJob>|Collection<string, SchedulableJob> */ public function scheduled(DateTimeInterface|CarbonInterface $at): Collection; - public function cancel(int|string $id): void; + public function cancel(int|string|SchedulableJob $id): SchedulableJob; /** * @return Collection<int, SchedulableJob>|Collection<string, SchedulableJob> diff --git a/src/Repositories/GenericScheduledJob.php b/src/Repositories/GenericScheduledJob.php index ba03948..0cd8267 100644 --- a/src/Repositories/GenericScheduledJob.php +++ b/src/Repositories/GenericScheduledJob.php @@ -7,12 +7,42 @@ use Bashtannik\Fasti\Contracts\SchedulableJob; use Carbon\CarbonInterface; +/** + * @codeCoverageIgnore + */ class GenericScheduledJob implements SchedulableJob { + /** + * @var array<string, mixed> + */ + private array $properties = []; + public function __construct( public int|string $id, + public string $type, public string $payload, public CarbonInterface $scheduled_at, public ?CarbonInterface $cancelled_at, ) {} + + public function __set(string $name, mixed $value): void + { + if (! property_exists($this, $name)) { + $this->properties[$name] = $value; + } + } + + public function __get(string $name): mixed + { + if (property_exists($this, $name)) { + return $this->$name; + } + + return $this->properties[$name] ?? null; + } + + public function __call(string $name, mixed $arguments): void + { + // Just silently ignores the call + } } diff --git a/src/Services/BusJobDispatcher.php b/src/Services/BusJobDispatcher.php new file mode 100644 index 0000000..2c27683 --- /dev/null +++ b/src/Services/BusJobDispatcher.php @@ -0,0 +1,21 @@ +<?php + +declare(strict_types=1); + +namespace Bashtannik\Fasti\Services; + +use Bashtannik\Fasti\Contracts\JobDispatcher; +use Illuminate\Support\Facades\Bus; + +class BusJobDispatcher implements JobDispatcher +{ + public function dispatch(mixed $job): mixed + { + return Bus::dispatch($job); + } + + public function dispatchSync(mixed $job): mixed + { + return Bus::dispatchSync($job); + } +} diff --git a/src/Services/FastiService.php b/src/Services/FastiService.php index 44939df..bccefce 100644 --- a/src/Services/FastiService.php +++ b/src/Services/FastiService.php @@ -4,19 +4,26 @@ namespace Bashtannik\Fasti\Services; -use Bashtannik\Fasti\Models\ScheduledJob; +use Bashtannik\Fasti\Contracts\JobDispatcher; +use Bashtannik\Fasti\Contracts\SchedulableJob; +use Bashtannik\Fasti\Events\JobCancelled; +use Bashtannik\Fasti\Events\JobDispatched; +use Bashtannik\Fasti\Events\JobScheduled; use Bashtannik\Fasti\Repositories\FastiScheduledJobsRepository; use Carbon\CarbonInterface; use DateTimeInterface; use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Support\Carbon; use Illuminate\Support\Collection; -use Illuminate\Support\Facades\Bus; +use Illuminate\Support\Facades\Event; use RuntimeException; -use stdClass; class FastiService { - public function __construct(protected FastiScheduledJobsRepository $repository) {} + public function __construct( + protected FastiScheduledJobsRepository $repository, + protected JobDispatcher $dispatcher, + ) {} /** * Retrieve the repository. @@ -26,22 +33,10 @@ public function getRepository(): FastiScheduledJobsRepository return $this->repository; } - /** - * Set the repository. - * - * @return $this - */ - public function setRepository(FastiScheduledJobsRepository $repository): static - { - $this->repository = $repository; - - return $this; - } - /** * Retrieve all scheduled jobs. * - * @return Collection<int, ScheduledJob>|Collection<int|string, array{id: int|string, payload: stdClass, scheduled_at: CarbonInterface, canceled_at: CarbonInterface|null}> + * @return Collection<int, SchedulableJob> */ public function all(): Collection { @@ -51,13 +46,22 @@ public function all(): Collection /** * Schedule a job to run at a specific time. */ - public function schedule(object $job, DateTimeInterface|CarbonInterface $at): int|string + public function schedule(object $job, DateTimeInterface|CarbonInterface|string $at): SchedulableJob { - return $this->repository->store($job, $at); + if (is_string($at)) { + $at = Carbon::parse($at); + } + + $scheduledJob = $this->repository->store($job, $at); + + Event::dispatch(new JobScheduled($scheduledJob)); + + return $scheduledJob; } /** * List all scheduled jobs for a specific minute. + * @return Collection<int, SchedulableJob> */ public function scheduled(DateTimeInterface|CarbonInterface $at): Collection { @@ -67,22 +71,25 @@ public function scheduled(DateTimeInterface|CarbonInterface $at): Collection /** * Cancel a scheduled job. */ - public function cancel(int|string $id): void + public function cancel(int|string|SchedulableJob $id): void { - $this->repository->cancel($id); + $scheduledJob = $this->repository->cancel($id); + + Event::dispatch(new JobCancelled($scheduledJob)); } /** * List all canceled jobs. + * @return Collection<int, SchedulableJob> */ public function cancelled(): Collection { return $this->repository->cancelled(); } - public function dispatch(int|string $id): void + public function dispatch(int|string|SchedulableJob $id): void { - $scheduledJob = $this->repository->find($id); + $scheduledJob = $id instanceof SchedulableJob ? $id : $this->repository->find($id); if (! str_starts_with((string) $scheduledJob->payload, 'O:')) { $data = decrypt($scheduledJob->payload); @@ -96,10 +103,12 @@ public function dispatch(int|string $id): void $instance = unserialize($scheduledJob->payload); } + Event::dispatch(new JobDispatched($scheduledJob)); + if ($instance instanceof ShouldQueue) { - Bus::dispatch($instance); + $this->dispatcher->dispatch($instance); } else { - Bus::dispatchSync($instance); + $this->dispatcher->dispatchSync($instance); } } } diff --git a/tests/Unit/FastiFacadeTest.php b/tests/Unit/FastiFacadeTest.php index 58278b4..00a19fc 100644 --- a/tests/Unit/FastiFacadeTest.php +++ b/tests/Unit/FastiFacadeTest.php @@ -7,6 +7,7 @@ use Bashtannik\Fasti\Facades\Fasti; use Bashtannik\Fasti\Repositories\FastiArrayRepository; use Bashtannik\Fasti\Services\FastiService; +use Bashtannik\Fasti\Tests\Fake\FakeEncryptedJob; use Bashtannik\Fasti\Tests\Fake\FakeJob; use Bashtannik\Fasti\Tests\TestCase; use DateTime; @@ -41,6 +42,8 @@ public function test_can_assert_scheduled_state(): void $notScheduledJob = new FakeJob(2); + $notScheduledEncryptedJob = new FakeEncryptedJob; // Other instance + $dateTime = new DateTime('now'); // act @@ -51,8 +54,17 @@ public function test_can_assert_scheduled_state(): void Fasti::assertScheduled($job); Fasti::assertScheduledAt($job, $dateTime); + Fasti::assertScheduledAt($job, $dateTime->format('Y-m-d H:i:s')); Fasti::assertNotScheduled($notScheduledJob); Fasti::assertNotScheduledAt($job, new DateTime('tomorrow')); + Fasti::assertNotScheduledAt($job, (new DateTime('tomorrow'))->format('Y-m-d H:i:s')); + + Fasti::assertScheduled($job::class); + Fasti::assertScheduledAt($job::class, $dateTime); + Fasti::assertScheduledAt($job::class, $dateTime->format('Y-m-d H:i:s')); + Fasti::assertNotScheduled($notScheduledEncryptedJob::class); // Other instance + Fasti::assertNotScheduledAt($job::class, new DateTime('tomorrow')); + Fasti::assertNotScheduledAt($job::class, (new DateTime('tomorrow'))->format('Y-m-d H:i:s')); } public function test_can_assert_canceled_state(): void diff --git a/tests/Unit/FastiServiceArrayRepositoryTest.php b/tests/Unit/FastiServiceArrayRepositoryTest.php index f1c83d4..a43139a 100644 --- a/tests/Unit/FastiServiceArrayRepositoryTest.php +++ b/tests/Unit/FastiServiceArrayRepositoryTest.php @@ -5,6 +5,7 @@ namespace Bashtannik\Fasti\Tests\Unit; use Bashtannik\Fasti\Repositories\FastiArrayRepository; +use Bashtannik\Fasti\Services\BusJobDispatcher; use Bashtannik\Fasti\Services\FastiService; use Bashtannik\Fasti\Tests\Fake\FakeEncryptedJob; use Bashtannik\Fasti\Tests\Fake\FakeJob; @@ -22,10 +23,10 @@ protected function setUp(): void { parent::setUp(); - self::$fasti = new FastiService(new FastiArrayRepository); + self::$fasti = new FastiService(new FastiArrayRepository, new BusJobDispatcher); } - public function test_can_schedule_job_date_time(): void + public function test_can_schedule_job_at_date_time(): void { // arrange @@ -35,17 +36,17 @@ public function test_can_schedule_job_date_time(): void // act - $id = self::$fasti->schedule($job, $dateTime); + $scheduledJob = self::$fasti->schedule($job, $dateTime); // assert - $scheduledJob = self::$fasti->getRepository()->find($id); + $scheduledJob = self::$fasti->getRepository()->find($scheduledJob->id); $this->assertEquals(serialize($job), $scheduledJob->payload, 'The job should be the one we scheduled.'); $this->assertEquals($dateTime->format('Y-m-d H:i:s'), $scheduledJob->scheduled_at->format('Y-m-d H:i:s'), 'The scheduled date should be the one we scheduled.'); } - public function test_can_schedule_job_date_time_immutable(): void + public function test_can_schedule_job_at_date_time_immutable(): void { // arrange @@ -55,17 +56,17 @@ public function test_can_schedule_job_date_time_immutable(): void // act - $id = self::$fasti->schedule($job, $dateTime); + $scheduledJob = self::$fasti->schedule($job, $dateTime); // assert - $scheduledJob = self::$fasti->getRepository()->find($id); + $scheduledJob = self::$fasti->getRepository()->find($scheduledJob->id); $this->assertEquals(serialize($job), $scheduledJob->payload, 'The job should be the one we scheduled.'); $this->assertEquals($dateTime->format('Y-m-d H:i:s'), $scheduledJob->scheduled_at->format('Y-m-d H:i:s'), 'The scheduled date should be the one we scheduled.'); } - public function test_can_schedule_job_carbon(): void + public function test_can_schedule_job_at_carbon(): void { // arrange @@ -75,17 +76,17 @@ public function test_can_schedule_job_carbon(): void // act - $id = self::$fasti->schedule($job, $dateTime); + $scheduledJob = self::$fasti->schedule($job, $dateTime); // assert - $scheduledJob = self::$fasti->getRepository()->find($id); + $scheduledJob = self::$fasti->getRepository()->find($scheduledJob->id); $this->assertEquals(serialize($job), $scheduledJob->payload, 'The job should be the one we scheduled.'); $this->assertEquals($dateTime->format('Y-m-d H:i:s'), $scheduledJob->scheduled_at->format('Y-m-d H:i:s'), 'The scheduled date should be the one we scheduled.'); } - public function test_can_schedule_job_carbon_immutable(): void + public function test_can_schedule_job_at_carbon_immutable(): void { // arrange @@ -95,11 +96,31 @@ public function test_can_schedule_job_carbon_immutable(): void // act - $id = self::$fasti->schedule($job, $dateTime); + $scheduledJob = self::$fasti->schedule($job, $dateTime); // assert - $scheduledJob = self::$fasti->getRepository()->find($id); + $scheduledJob = self::$fasti->getRepository()->find($scheduledJob->id); + + $this->assertEquals(serialize($job), $scheduledJob->payload, 'The job should be the one we scheduled.'); + $this->assertEquals($dateTime->format('Y-m-d H:i:s'), $scheduledJob->scheduled_at->format('Y-m-d H:i:s'), 'The scheduled date should be the one we scheduled.'); + } + + public function test_can_schedule_job_at_string(): void + { + // arrange + + $job = new FakeJob; + + $dateTime = now(); + + // act + + $scheduledJob = self::$fasti->schedule($job, $dateTime->toDateTimeString()); + + // assert + + $scheduledJob = self::$fasti->getRepository()->find($scheduledJob->id); $this->assertEquals(serialize($job), $scheduledJob->payload, 'The job should be the one we scheduled.'); $this->assertEquals($dateTime->format('Y-m-d H:i:s'), $scheduledJob->scheduled_at->format('Y-m-d H:i:s'), 'The scheduled date should be the one we scheduled.'); @@ -115,11 +136,11 @@ public function test_can_schedule_encrypted_job(): void // act - $id = self::$fasti->schedule($job, $dateTime); + $scheduledJob = self::$fasti->schedule($job, $dateTime); // assert - $scheduledJob = self::$fasti->getRepository()->find($id); + $scheduledJob = self::$fasti->getRepository()->find($scheduledJob->id); $this->assertEquals(serialize($job), $scheduledJob->payload, 'The job should be the one we scheduled.'); $this->assertEquals($dateTime->format('Y-m-d H:i:s'), $scheduledJob->scheduled_at->format('Y-m-d H:i:s'), 'The scheduled date should be the one we scheduled.'); @@ -134,7 +155,7 @@ public function test_can_get_scheduled_jobs(): void $dateTime = new DateTime('now'); - $id = self::$fasti->schedule($job, $dateTime); + $scheduledJob = self::$fasti->schedule($job, $dateTime); $cancelledId = self::$fasti->schedule($scheduledJob, $dateTime); self::$fasti->cancel($cancelledId); @@ -146,7 +167,7 @@ public function test_can_get_scheduled_jobs(): void // assert $this->assertCount(1, $jobs, 'Only the scheduled job should be returned.'); - $this->assertEquals($id, $jobs->first()->id, 'The scheduled job should be the one we scheduled.'); + $this->assertEquals($scheduledJob, $jobs->first(), 'The scheduled job should be the one we scheduled.'); } public function test_can_cancel_scheduled_job(): void @@ -157,15 +178,15 @@ public function test_can_cancel_scheduled_job(): void $dateTime = new DateTime('now'); - $id = self::$fasti->schedule($job, $dateTime); + $scheduledJob = self::$fasti->schedule($job, $dateTime); // act - self::$fasti->cancel($id); + self::$fasti->cancel($scheduledJob); // assert - $job = self::$fasti->getRepository()->find($id); + $job = self::$fasti->getRepository()->find($scheduledJob->id); $this->assertNotNull($job->cancelled_at, 'The job should be canceled.'); } @@ -181,8 +202,8 @@ public function test_can_get_cancelled_jobs(): void self::$fasti->schedule($job, $dateTime); - $id = self::$fasti->schedule($canceledJob, $dateTime); - self::$fasti->cancel($id); + $scheduledJob = self::$fasti->schedule($canceledJob, $dateTime); + self::$fasti->cancel($scheduledJob); // act @@ -191,7 +212,7 @@ public function test_can_get_cancelled_jobs(): void // assert $this->assertCount(1, $jobs, 'Only the canceled job should be returned.'); - $this->assertEquals($id, $jobs->first()->id, 'The canceled job should be the one we canceled.'); + $this->assertEquals($scheduledJob, $jobs->first(), 'The canceled job should be the one we canceled.'); } public function test_can_get_all_jobs(): void @@ -205,8 +226,8 @@ public function test_can_get_all_jobs(): void self::$fasti->schedule($job, $dateTime); - $id = self::$fasti->schedule($canceledJob, $dateTime); - self::$fasti->cancel($id); + $scheduledJob = self::$fasti->schedule($canceledJob, $dateTime); + self::$fasti->cancel($scheduledJob); // act @@ -227,11 +248,11 @@ public function test_can_dispatch_sync_job(): void $dateTime = new DateTime('now'); - $id = self::$fasti->schedule($job, $dateTime); + $scheduledJob = self::$fasti->schedule($job, $dateTime); // act - self::$fasti->dispatch($id); + self::$fasti->dispatch($scheduledJob); // assert @@ -248,11 +269,11 @@ public function test_can_dispatch_queued_job(): void $dateTime = new DateTime('now'); - $id = self::$fasti->schedule($job, $dateTime); + $scheduledJob = self::$fasti->schedule($job, $dateTime); // act - self::$fasti->dispatch($id); + self::$fasti->dispatch($scheduledJob); // assert @@ -269,11 +290,11 @@ public function test_can_dispatch_encrypted_job(): void $dateTime = new DateTime('now'); - $id = self::$fasti->schedule($job, $dateTime); + $scheduledJob = self::$fasti->schedule($job, $dateTime); // act - self::$fasti->dispatch($id); + self::$fasti->dispatch($scheduledJob); // assert diff --git a/tests/Unit/FastiServiceEloquentRepositoryTest.php b/tests/Unit/FastiServiceEloquentRepositoryTest.php index 2e35f65..0300d72 100644 --- a/tests/Unit/FastiServiceEloquentRepositoryTest.php +++ b/tests/Unit/FastiServiceEloquentRepositoryTest.php @@ -2,7 +2,12 @@ namespace Bashtannik\Fasti\Tests\Unit; +use Bashtannik\Fasti\Events\JobCancelled; +use Bashtannik\Fasti\Events\JobDispatched; +use Bashtannik\Fasti\Events\JobScheduled; +use Bashtannik\Fasti\Models\ScheduledJob; use Bashtannik\Fasti\Repositories\FastiEloquentRepository; +use Bashtannik\Fasti\Services\BusJobDispatcher; use Bashtannik\Fasti\Services\FastiService; use Bashtannik\Fasti\Tests\Fake\FakeEncryptedJob; use Bashtannik\Fasti\Tests\Fake\FakeJob; @@ -11,6 +16,7 @@ use DateTime; use DateTimeImmutable; use Illuminate\Support\Facades\Bus; +use Illuminate\Support\Facades\Event; class FastiServiceEloquentRepositoryTest extends TestCase { @@ -20,106 +26,196 @@ protected function setUp(): void { parent::setUp(); - self::$fasti = new FastiService(new FastiEloquentRepository); + self::$fasti = new FastiService(new FastiEloquentRepository, new BusJobDispatcher); } - public function test_can_schedule_job_date_time(): void + public function test_can_schedule_job_at_date_time(): void { // arrange + Event::fake(JobScheduled::class); + $job = new FakeJob; $dateTime = new DateTime('now'); // act - self::$fasti->schedule($job, $dateTime); + $scheduledJob = self::$fasti->schedule($job, $dateTime); // assert + $this->assertInstanceOf(ScheduledJob::class, $scheduledJob); + $this->assertDatabaseHas('scheduled_jobs', [ 'payload' => serialize($job), + 'type' => FakeJob::class, 'scheduled_at' => $dateTime->format('Y-m-d H:i:s'), ]); + + Event::assertDispatched(JobScheduled::class); } - public function test_can_schedule_job_date_time_immutable(): void + public function test_can_schedule_job_at_date_time_immutable(): void { // arrange + Event::fake(JobScheduled::class); + $job = new FakeJob; $dateTime = new DateTimeImmutable('now'); // act - self::$fasti->schedule($job, $dateTime); + $scheduledJob = self::$fasti->schedule($job, $dateTime); // assert + $this->assertInstanceOf(ScheduledJob::class, $scheduledJob); + $this->assertDatabaseHas('scheduled_jobs', [ 'payload' => serialize($job), 'scheduled_at' => $dateTime->format('Y-m-d H:i:s'), ]); + + Event::assertDispatched(JobScheduled::class); } - public function test_can_schedule_job_carbon(): void + public function test_can_schedule_job_at_carbon(): void { // arrange + Event::fake(JobScheduled::class); + $job = new FakeJob; $dateTime = now(); // act - self::$fasti->schedule($job, $dateTime); + $scheduledJob = self::$fasti->schedule($job, $dateTime); // assert + $this->assertInstanceOf(ScheduledJob::class, $scheduledJob); + $this->assertDatabaseHas('scheduled_jobs', [ 'payload' => serialize($job), 'scheduled_at' => $dateTime->format('Y-m-d H:i:s'), ]); + + Event::assertDispatched(JobScheduled::class); } - public function test_can_schedule_job_carbon_immutable(): void + public function test_can_schedule_job_at_carbon_immutable(): void { // arrange + Event::fake(JobScheduled::class); + + $job = new FakeJob; + + $dateTime = now()->toImmutable(); + + // act + + $scheduledJob = self::$fasti->schedule($job, $dateTime); + + // assert + + $this->assertInstanceOf(ScheduledJob::class, $scheduledJob); + + // assert + + $this->assertDatabaseHas('scheduled_jobs', [ + 'payload' => serialize($job), + 'scheduled_at' => $dateTime->format('Y-m-d H:i:s'), + ]); + + Event::assertDispatched(JobScheduled::class); + } + + public function test_can_schedule_job_at_string(): void + { + // arrange + + Event::fake(JobScheduled::class); + $job = new FakeJob; $dateTime = now(); // act - self::$fasti->schedule($job, $dateTime); + $scheduledJob = self::$fasti->schedule($job, $dateTime); // assert + $this->assertInstanceOf(ScheduledJob::class, $scheduledJob); + $this->assertDatabaseHas('scheduled_jobs', [ 'payload' => serialize($job), 'scheduled_at' => $dateTime->format('Y-m-d H:i:s'), ]); + + Event::assertDispatched(JobScheduled::class); } public function test_can_schedule_encrypted_job(): void { // arrange + Event::fake(JobScheduled::class); + $job = new FakeEncryptedJob; $dateTime = new DateTime('now'); // act - self::$fasti->schedule($job, $dateTime); + $scheduledJob = self::$fasti->schedule($job, $dateTime); // assert + $this->assertInstanceOf(ScheduledJob::class, $scheduledJob); + $first = self::$fasti->getRepository()->all()->first(); $this->assertEquals(serialize($job), decrypt($first->payload), 'The job should be encrypted.'); + + Event::assertDispatched(JobScheduled::class); + } + + public function test_can_schedule_mapped_type_job(): void + { + // arrange + + Event::fake(JobScheduled::class); + + $job = new FakeJob; + + $dateTime = new DateTime('now'); + + FastiEloquentRepository::enforceTypeMap([ + 'fake_job' => FakeJob::class, + ]); + + // act + + $scheduledJob = self::$fasti->schedule($job, $dateTime); + + // assert + + $this->assertInstanceOf(ScheduledJob::class, $scheduledJob); + + $this->assertDatabaseHas('scheduled_jobs', [ + 'payload' => serialize($job), + 'type' => 'fake_job', + 'scheduled_at' => $dateTime->format('Y-m-d H:i:s'), + ]); + + Event::assertDispatched(JobScheduled::class); } public function test_can_get_scheduled_jobs(): void @@ -127,14 +223,14 @@ public function test_can_get_scheduled_jobs(): void // arrange $job = new FakeJob; - $scheduledJob = new FakeJob; + $job2 = new FakeJob; $dateTime = new DateTime('now'); - $id = self::$fasti->schedule($job, $dateTime); + $scheduledJob = self::$fasti->schedule($job, $dateTime); + $scheduledJob2 = self::$fasti->schedule($job2, $dateTime); - $cancelledId = self::$fasti->schedule($scheduledJob, $dateTime); - self::$fasti->cancel($cancelledId); + self::$fasti->cancel($scheduledJob2->id); // act @@ -143,29 +239,33 @@ public function test_can_get_scheduled_jobs(): void // assert $this->assertCount(1, $jobs, 'Both jobs should be returned.'); - $this->assertEquals($id, $jobs->first()->id, 'The scheduled job should be the one we scheduled.'); + $this->assertEquals($scheduledJob->id, $jobs->first()->id, 'The scheduled job should be the one we scheduled.'); } public function test_can_cancel_scheduled_job(): void { // arrange + Event::fake(JobCancelled::class); + $job = new FakeJob; $dateTime = new DateTime('now'); - $id = self::$fasti->schedule($job, $dateTime); + $scheduledJob = self::$fasti->schedule($job, $dateTime); // act - self::$fasti->cancel($id); + self::$fasti->cancel($scheduledJob->id); // assert $this->assertDatabaseHas('scheduled_jobs', [ - 'id' => $id, + 'id' => $scheduledJob->id, 'cancelled_at' => now()->format('Y-m-d H:i:s'), ]); + + Event::assertDispatched(JobCancelled::class); } public function test_can_get_cancelled_jobs(): void @@ -173,14 +273,14 @@ public function test_can_get_cancelled_jobs(): void // arrange $job = new FakeJob; - $canceledJob = new FakeJob; + $job2 = new FakeJob; $dateTime = new DateTime('now'); self::$fasti->schedule($job, $dateTime); - $id = self::$fasti->schedule($canceledJob, $dateTime); - self::$fasti->cancel($id); + $scheduledJob = self::$fasti->schedule($job2, $dateTime); + self::$fasti->cancel($scheduledJob->id); // act @@ -189,7 +289,7 @@ public function test_can_get_cancelled_jobs(): void // assert $this->assertCount(1, $jobs, 'Only the canceled job should be returned.'); - $this->assertEquals($id, $jobs->first()->id, 'The canceled job should be the one we canceled.'); + $this->assertEquals($scheduledJob->id, $jobs->first()->id, 'The canceled job should be the one we canceled.'); } public function test_can_get_all_jobs(): void @@ -197,14 +297,14 @@ public function test_can_get_all_jobs(): void // arrange $job = new FakeJob; - $canceledJob = new FakeJob; + $job2 = new FakeJob; $dateTime = new DateTime('now'); self::$fasti->schedule($job, $dateTime); - $id = self::$fasti->schedule($canceledJob, $dateTime); - self::$fasti->cancel($id); + $scheduledJob = self::$fasti->schedule($job2, $dateTime); + self::$fasti->cancel($scheduledJob->id); // act @@ -220,20 +320,22 @@ public function test_can_dispatch_sync_job(): void // arrange Bus::fake(FakeJob::class); + Event::fake(JobDispatched::class); $job = new FakeJob; $dateTime = new DateTime('now'); - $id = self::$fasti->schedule($job, $dateTime); + $scheduledJob = self::$fasti->schedule($job, $dateTime); // act - self::$fasti->dispatch($id); + self::$fasti->dispatch($scheduledJob->id); // assert Bus::assertDispatchedSync(FakeJob::class); + Event::fake(JobDispatched::class); } public function test_can_dispatch_queued_job(): void @@ -241,20 +343,22 @@ public function test_can_dispatch_queued_job(): void // arrange Bus::fake(FakeQueuedJob::class); + Event::fake(JobDispatched::class); $job = new FakeQueuedJob; $dateTime = new DateTime('now'); - $id = self::$fasti->schedule($job, $dateTime); + $scheduledJob = self::$fasti->schedule($job, $dateTime); // act - self::$fasti->dispatch($id); + self::$fasti->dispatch($scheduledJob->id); // assert Bus::assertDispatched(FakeQueuedJob::class); + Event::assertDispatched(JobDispatched::class); } public function test_can_dispatch_encrypted_job(): void @@ -262,19 +366,21 @@ public function test_can_dispatch_encrypted_job(): void // arrange Bus::fake(FakeEncryptedJob::class); + Event::fake(JobDispatched::class); $job = new FakeEncryptedJob; $dateTime = new DateTime('now'); - $id = self::$fasti->schedule($job, $dateTime); + $scheduledJob = self::$fasti->schedule($job, $dateTime); // act - self::$fasti->dispatch($id); + self::$fasti->dispatch($scheduledJob->id); // assert Bus::assertDispatchedSync(FakeEncryptedJob::class); + Event::assertDispatched(JobDispatched::class); } }