Skip to content

Commit

Permalink
feat: improvements and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Rezrazi committed Nov 18, 2022
1 parent 8e60259 commit a2b7073
Show file tree
Hide file tree
Showing 14 changed files with 268 additions and 30 deletions.
55 changes: 54 additions & 1 deletion config/mail-scheduler.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,62 @@
declare(strict_types=1);

return [

/*
|--------------------------------------------------------------------------
| Max attempts
|--------------------------------------------------------------------------
|
| Can be used to define the maximum number of attempts to send an email.
| Each time an email fails to send, the number of attempts is incremented.
|
| default: 3
*/
'max_attempts' => (int) env('MAIL_SCHEDULER_MAX_ATTEMPTS', 3),

/*
|--------------------------------------------------------------------------
| Batch size
|--------------------------------------------------------------------------
|
| Can be used to define the number of scheduled emails to send in a batch.
|
| default: 100
*/
'batch_size' => (int) env('MAIL_SCHEDULER_BATCH_SIZE', 100),

/*
|--------------------------------------------------------------------------
| Auto schedule
|--------------------------------------------------------------------------
|
| By setting this option to true, the package will auto register into the
| Laravel scheduler.
|
| default: true
*/
'auto_schedule' => (bool) env('MAIL_SCHEDULER_AUTO_SCHEDULE', true),

/*
|--------------------------------------------------------------------------
| Auto schedule CRON expression
|--------------------------------------------------------------------------
|
| If auto schedule is enabled, this CRON expression will be used to schedule
| the command to run.
|
| default: every five minutes
*/
'schedule_cron' => env('MAIL_SCHEDULER_SCHEDULE_CRON', '*/5 * * * *'),

/*
|--------------------------------------------------------------------------
| Table name
|--------------------------------------------------------------------------
|
| Can be used to define a custom table name for the scheduled emails models.
|
| default: scheduled_emails
*/
'table_name' => env('MAIL_SCHEDULER_TABLE_NAME', 'scheduled_emails'),

];
11 changes: 4 additions & 7 deletions database/migrations/create_scheduled_emails_table.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,19 @@
{
public function up(): void
{
Schema::create('scheduled_emails', function (Blueprint $table) {
Schema::create(config('mail-scheduler.table_name'), function (Blueprint $table) {
$table->id();

$table->json('recipients');
$table->longText('mailable');
$table->string('status')->index();
$table->unsignedInteger('attempts')->default(0);
$table->string('error')->nullable();
$table->text('stacktrace')->nullable();
$table->dateTime('attempted_at')->nullable();
$table->unsignedBigInteger('source_id')->nullable();
$table->string('source_type')->nullable();
$table->nullableMorphs('source');
$table->timestamp('attempted_at')->nullable();
$table->timestamps();

$table->index(['status', 'attempts', 'created_at']);

$table->timestamps();
});
}
};
6 changes: 2 additions & 4 deletions phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,15 @@
<testsuites>
<testsuite name="Mail scheduler Test suite">
<directory suffix="Test.php">./tests/Feature</directory>
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
</testsuites>
<php>
<env name="APP_ENV" value="testing"/>
<env name="APP_KEY" value="base64:IiOzvjsOE18pPH+96mKNbE7ilEwkBg4pdayiOZrbRrs="/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="DB_CONNECTION" value="mysql"/>
<env name="DB_DATABASE" value="laravel_mail_scheduler"/>
<env name="DB_USERNAME" value="root"/>
<env name="DB_PASSWORD" value=""/>
<env name="DB_CONNECTION" value="testing"/>
<env name="MAIL_MAILER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/>
Expand Down
17 changes: 4 additions & 13 deletions src/Casts/SerializedMailable.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ class SerializedMailable implements CastsAttributes
*/
public function get($model, string $key, $value, array $attributes): ?Mailable
{
if (!filled($value)) {
return null;
}

return tap(unserialize($value), function ($mailable) {
if (!$mailable instanceof Mailable) {
throw NotAMailable::instanceOf($mailable::class);
Expand All @@ -47,16 +43,11 @@ public function get($model, string $key, $value, array $attributes): ?Mailable
*/
public function set($model, string $key, $value, array $attributes): ?string
{
if (!filled($value)) {
return null;
if (!$value instanceof Mailable) {
throw NotAMailable::instanceOf($value::class);
}

return tap($value, function ($mailable) {
if (!$mailable instanceof Mailable) {
throw NotAMailable::instanceOf($mailable::class);
}

return serialize($mailable);
});
// todo encrypt with ShouldBeEncrypted
return serialize($value);
}
}
6 changes: 3 additions & 3 deletions src/Casts/SerializedObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ class SerializedObject implements CastsAttributes
* @param array $attributes
* @return mixed
*/
public function get($model, string $key, $value, array $attributes): mixed
public function get($model, string $key, $value, array $attributes): array
{
return filled($value) ? unserialize($value) : null;
return filled($value) ? unserialize($value) : [];
}

/**
Expand All @@ -33,6 +33,6 @@ public function get($model, string $key, $value, array $attributes): mixed
*/
public function set($model, string $key, $value, array $attributes): ?string
{
return filled($value) ? serialize($value) : null;
return filled($value) ? serialize($value) : serialize([]);
}
}
15 changes: 15 additions & 0 deletions src/MailSchedulerServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Oneduo\MailScheduler;

use Illuminate\Console\Scheduling\Schedule;
use Oneduo\MailScheduler\Console\Commands\SendEmails;
use Spatie\LaravelPackageTools\Package;
use Spatie\LaravelPackageTools\PackageServiceProvider;

Expand All @@ -15,6 +17,19 @@ public function configurePackage(Package $package): void
->name('laravel-mail-scheduler')
->hasConfigFile()
->hasMigration('create_scheduled_emails_table')
->hasCommands(SendEmails::class)
->runsMigrations();
}

public function boot()
{
parent::boot();

if (config('mail-scheduler.auto_schedule') === true) {
$schedule = app(Schedule::class);

$schedule->command(SendEmails::class)
->cron(config('mail-scheduler.schedule_cron'));
}
}
}
12 changes: 12 additions & 0 deletions src/Models/ScheduledEmail.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Oneduo\MailScheduler\Models;

use Illuminate\Contracts\Mail\Mailable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Oneduo\MailScheduler\Casts\SerializedMailable;
Expand Down Expand Up @@ -70,4 +71,15 @@ public static function serializeWithAttributes(array $attributes): array
], $attributes)
))->jsonSerialize();
}

public static function fromMailable(Mailable $mailable, array $recipients, ?Model $source = null): static
{
return static::query()->create([
'mailable' => $mailable,
'recipients' => $recipients,
'status' => EmailStatus::PENDING,
'source_id' => $source?->getKey(),
'source_type' => $source?->getMorphClass(),
]);
}
}
34 changes: 34 additions & 0 deletions tests/Feature/SendEmailsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

use Illuminate\Support\Facades\Mail;
use Oneduo\MailScheduler\Console\Commands\SendEmails;
use Oneduo\MailScheduler\Enums\EmailStatus;
use Oneduo\MailScheduler\Models\ScheduledEmail;
use Oneduo\MailScheduler\Tests\Support\TestMailable;
use function Pest\Laravel\artisan;

it('runs the command and sends the scheduled emails', function () {
Mail::fake();

collect(range(1, 10))
->each(fn() => ScheduledEmail::fromMailable(mailable(), recipients()));

artisan(SendEmails::class)->assertOk();

Mail::assertSent(TestMailable::class);
});

it('fails to send scheduled emails without recipients', function () {
Mail::fake();

collect(range(1, 10))
->each(fn() => ScheduledEmail::fromMailable(mailable(), []));

artisan(SendEmails::class)->assertOk();

$statuses = ScheduledEmail::query()->pluck('status');

expect($statuses->filter(fn(EmailStatus $status) => $status !== EmailStatus::ERROR))->toBeEmpty();
});
18 changes: 17 additions & 1 deletion tests/Pest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@

declare(strict_types=1);

use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
use Illuminate\Mail\Mailable;
use Oneduo\MailScheduler\Tests\Support\TestMailable;
use Oneduo\MailScheduler\Tests\TestCase;

uses(TestCase::class)->in('Feature');
uses(TestCase::class)->in(__DIR__);
uses(LazilyRefreshDatabase::class)->in(__DIR__);

function mailable(): Mailable
{
return new TestMailable();
}

function recipients(): array
{
return [
...collect(range(1, 10))->map(fn () => fake()->email),
];
}
27 changes: 27 additions & 0 deletions tests/Support/TestMailable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace Oneduo\MailScheduler\Tests\Support;

use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;

class TestMailable extends Mailable implements ShouldBeEncrypted
{
public function envelope(): Envelope
{
return new Envelope(
subject: 'Test subject',
);
}

public function content(): Content
{
return new Content(
text: 'Test content',
);
}
}
11 changes: 11 additions & 0 deletions tests/Support/TestModel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace Oneduo\MailScheduler\Tests\Support;

use Illuminate\Database\Eloquent\Model;

class TestModel extends Model
{
}
3 changes: 2 additions & 1 deletion tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@

namespace Oneduo\MailScheduler\Tests;

use Oneduo\MailScheduler\MailSchedulerServiceProvider;
use Orchestra\Testbench\TestCase as Orchestra;

class TestCase extends Orchestra
{
protected function getPackageProviders($app): array
{
return [

MailSchedulerServiceProvider::class,
];
}
}
Empty file added tests/Unit/.gitkeep
Empty file.
Loading

0 comments on commit a2b7073

Please sign in to comment.