From 32fe667019fafc5b2fe8d1279d2b8619b5943676 Mon Sep 17 00:00:00 2001 From: fearlessalan Date: Sat, 15 Aug 2026 05:29:44 +0200 Subject: [PATCH 1/2] feat: implement welcome email service and integrate into user registration flow with associated tests --- app/Mail/WelcomeUserMail.php | 44 ++++++++++++++++++ app/Services/AuthService.php | 6 +++ app/Services/MailService.php | 21 +++++++++ resources/views/emails/welcome.blade.php | 12 +++++ .../Feature/Api/V1/ProfileControllerTest.php | 28 +++++++++++ tests/Unit/Services/AuthServiceTest.php | 10 +++- tests/Unit/Services/MailServiceTest.php | 46 +++++++++++++++++++ 7 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 app/Mail/WelcomeUserMail.php create mode 100644 app/Services/MailService.php create mode 100644 resources/views/emails/welcome.blade.php create mode 100644 tests/Feature/Api/V1/ProfileControllerTest.php create mode 100644 tests/Unit/Services/MailServiceTest.php diff --git a/app/Mail/WelcomeUserMail.php b/app/Mail/WelcomeUserMail.php new file mode 100644 index 0000000..dad82d3 --- /dev/null +++ b/app/Mail/WelcomeUserMail.php @@ -0,0 +1,44 @@ +afterCommit(); + } + + public function envelope(): Envelope + { + return new Envelope( + subject: 'Bienvenue sur CDTM', + ); + } + + public function content(): Content + { + return new Content( + markdown: 'emails.welcome', + with: [ + 'name' => $this->user->name, + ], + ); + } + + public function attachments(): array + { + return []; + } +} diff --git a/app/Services/AuthService.php b/app/Services/AuthService.php index d8f7ab5..6ef1d00 100644 --- a/app/Services/AuthService.php +++ b/app/Services/AuthService.php @@ -8,11 +8,15 @@ use Illuminate\Support\Str; use Illuminate\Support\Facades\Cache; use App\Models\User; +use App\Services\MailService; use App\Exceptions\InvalidOAuthCodeException; use Illuminate\Database\Eloquent\ModelNotFoundException; class AuthService { + public function __construct( + protected MailService $mailService + ) {} /** * Redirect to LinkedIn and authenticate the user with LinkedIn using OAuth. * @@ -99,6 +103,8 @@ private function createUser(SocialiteUser $linkedInUser): User 'account_status' => 'PENDING_VALIDATION', ]); + $this->mailService->sendWelcomeEmail($user); + return $user; }); } diff --git a/app/Services/MailService.php b/app/Services/MailService.php new file mode 100644 index 0000000..3a28df9 --- /dev/null +++ b/app/Services/MailService.php @@ -0,0 +1,21 @@ +email)->send(new WelcomeUserMail($user)); + } +} diff --git a/resources/views/emails/welcome.blade.php b/resources/views/emails/welcome.blade.php new file mode 100644 index 0000000..70588fb --- /dev/null +++ b/resources/views/emails/welcome.blade.php @@ -0,0 +1,12 @@ + +# Bienvenue, {{ $name }} ! + +Merci de vous être inscrit sur la plateforme CDTM. Nous sommes ravis de vous compter parmi nous. + + +Accéder à mon espace + + +Cordialement,
+L'équipe {{ config('app.name') }} +
diff --git a/tests/Feature/Api/V1/ProfileControllerTest.php b/tests/Feature/Api/V1/ProfileControllerTest.php new file mode 100644 index 0000000..11c1293 --- /dev/null +++ b/tests/Feature/Api/V1/ProfileControllerTest.php @@ -0,0 +1,28 @@ +has(Profile::factory())->create(['role' => UserRole::Admin]); + $profile = $user->profile; + + $response = $this->actingAs($user)->patchJson("/api/v1/profiles/{$profile->id}", [ + 'name' => 'New Name', + ]); + + $response->assertStatus(200); + } + + +} diff --git a/tests/Unit/Services/AuthServiceTest.php b/tests/Unit/Services/AuthServiceTest.php index c53bd4a..b1ad7ae 100644 --- a/tests/Unit/Services/AuthServiceTest.php +++ b/tests/Unit/Services/AuthServiceTest.php @@ -3,12 +3,14 @@ namespace Tests\Unit\Services; use App\Exceptions\InvalidOAuthCodeException; +use App\Mail\WelcomeUserMail; use App\Models\Profile; use App\Models\User; use App\Services\AuthService; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Mail; use Laravel\Socialite\Facades\Socialite; use Laravel\Socialite\Two\User as SocialiteUser; use Mockery; @@ -23,7 +25,7 @@ class AuthServiceTest extends TestCase protected function setUp(): void { parent::setUp(); - $this->service = new AuthService(); + $this->service = $this->app->make(AuthService::class); } private function fakeSocialiteUser( @@ -60,6 +62,8 @@ public function test_linked_id_authenticate_returns_the_socialite_user(): void public function test_creates_a_new_user_and_profile_when_no_match_exists(): void { + Mail::fake(); + $linkedInUser = $this->fakeSocialiteUser('li-999', 'newperson@example.com'); $result = $this->service->signUpOrLogin($linkedInUser); @@ -83,6 +87,10 @@ public function test_creates_a_new_user_and_profile_when_no_match_exists(): void $this->assertNotNull($cached); $this->assertEquals($user->id, $cached['user_id']); $this->assertNotEmpty($cached['token']); + + Mail::assertQueued(WelcomeUserMail::class, function (WelcomeUserMail $mail) use ($user) { + return $mail->user->id === $user->id && $mail->hasTo($user->email); + }); } public function test_links_linkedin_id_to_an_existing_user_matched_by_email(): void diff --git a/tests/Unit/Services/MailServiceTest.php b/tests/Unit/Services/MailServiceTest.php new file mode 100644 index 0000000..20ec926 --- /dev/null +++ b/tests/Unit/Services/MailServiceTest.php @@ -0,0 +1,46 @@ +create([ + 'email' => 'testwelcome@example.com', + ]); + + $mailService = new MailService(); + $mailService->sendWelcomeEmail($user); + + Mail::assertQueued(WelcomeUserMail::class, function (WelcomeUserMail $mail) use ($user) { + return $mail->user->id === $user->id && $mail->hasTo('testwelcome@example.com'); + }); + } + + public function test_welcome_user_mail_properties(): void + { + $user = User::factory()->create([ + 'name' => 'Alice', + 'email' => 'alice@example.com', + ]); + + $mailable = new WelcomeUserMail($user); + + $this->assertEquals('Bienvenue sur CDTM', $mailable->envelope()->subject); + $this->assertEquals('emails.welcome', $mailable->content()->markdown); + $this->assertEquals(['name' => 'Alice'], $mailable->content()->with); + $this->assertEmpty($mailable->attachments()); + } +} From 1797de338b4861fb66f23fa14d880ac0884ad40f Mon Sep 17 00:00:00 2001 From: fearlessalan Date: Sat, 15 Aug 2026 06:06:31 +0200 Subject: [PATCH 2/2] chore: update league/commonmark to v2.10.0 and add type hinting to WelcomeUserMail attachments --- app/Mail/WelcomeUserMail.php | 3 +++ composer.lock | 14 +++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app/Mail/WelcomeUserMail.php b/app/Mail/WelcomeUserMail.php index dad82d3..0d00290 100644 --- a/app/Mail/WelcomeUserMail.php +++ b/app/Mail/WelcomeUserMail.php @@ -37,6 +37,9 @@ public function content(): Content ); } + /** + * @return array + */ public function attachments(): array { return []; diff --git a/composer.lock b/composer.lock index 91eb373..5db12b8 100644 --- a/composer.lock +++ b/composer.lock @@ -1756,16 +1756,16 @@ }, { "name": "league/commonmark", - "version": "2.8.3", + "version": "2.10.0", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "1902f60f984235023acbe03db6ad614a37b3c3e7" + "reference": "d2d1aa8b35e072966c89bc0c66cf926e56767dc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/1902f60f984235023acbe03db6ad614a37b3c3e7", - "reference": "1902f60f984235023acbe03db6ad614a37b3c3e7", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d2d1aa8b35e072966c89bc0c66cf926e56767dc4", + "reference": "d2d1aa8b35e072966c89bc0c66cf926e56767dc4", "shasum": "" }, "require": { @@ -1802,7 +1802,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.9-dev" + "dev-main": "2.11-dev" } }, "autoload": { @@ -1859,7 +1859,7 @@ "type": "tidelift" } ], - "time": "2026-07-12T15:29:16+00:00" + "time": "2026-08-11T16:06:25+00:00" }, { "name": "league/config", @@ -11235,5 +11235,5 @@ "php": "^8.3" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" }