Skip to content

Commit 7201da7

Browse files
committed
Add publishable test stubs for common application scenarios
Two stubs are published via `vendor:publish --tag=subscriber-tests`: - UnsubscribeRouteTest — covers GET and POST unsubscribe routes, RFC 8058 body validation, and tampered-URL rejection - SubscribableNotificationTest — covers subscription gating (subscribed sends, unsubscribed dropped) and view data presence in the mail message Each stub uses Subscriber::fake() and is annotated with TODO markers where the user substitutes their own model or notification class. README Testing section updated with a scaffold install command and a table describing what each stub covers. https://claude.ai/code/session_01R4pAjWwGY8xKspsdU8xnsy
1 parent 36fc888 commit 7201da7

4 files changed

Lines changed: 240 additions & 0 deletions

File tree

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,25 @@ This creates `resources/views/vendor/subscriber/html.blade.php` and `text.blade.
313313

314314
## Testing
315315

316+
### Scaffolding your tests
317+
318+
Publish ready-to-customise test stubs into your application's `tests/Feature/` directory:
319+
320+
```bash
321+
php artisan vendor:publish --tag=subscriber-tests
322+
```
323+
324+
This creates two files:
325+
326+
| File | What it covers |
327+
|------|----------------|
328+
| `tests/Feature/UnsubscribeRouteTest.php` | GET and POST unsubscribe routes, RFC 8058 body validation, tampered-URL rejection |
329+
| `tests/Feature/SubscribableNotificationTest.php` | Subscription gating (subscribed sends, unsubscribed dropped), view data presence |
330+
331+
Each file is annotated with `// TODO:` markers wherever you need to substitute your own model or notification class. The stubs use `Subscriber::fake()` so no real database handlers need to be configured.
332+
333+
### Using the fake
334+
316335
Use `Subscriber::fake()` in your tests to swap in a fake implementation and make assertions without needing real handlers configured:
317336

318337
```php

src/SubscribableServiceProvider.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ public function boot(): void
2222
$this->publishes([
2323
__DIR__.'/../stubs/SubscribableServiceProvider.stub' => app_path('Providers/SubscribableServiceProvider.php'),
2424
], 'subscriber-provider');
25+
26+
$this->publishes([
27+
__DIR__.'/../stubs/tests/UnsubscribeRouteTest.stub' => base_path('tests/Feature/UnsubscribeRouteTest.php'),
28+
__DIR__.'/../stubs/tests/SubscribableNotificationTest.stub' => base_path('tests/Feature/SubscribableNotificationTest.php'),
29+
], 'subscriber-tests');
2530
}
2631

2732
Event::listen(NotificationSending::class, function (NotificationSending $event) {
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<?php
2+
3+
namespace Tests\Feature;
4+
5+
use Illuminate\Foundation\Testing\RefreshDatabase;
6+
use Illuminate\Support\Facades\Notification;
7+
use Tests\TestCase;
8+
use YlsIdeas\SubscribableNotifications\Facades\Subscriber;
9+
use YlsIdeas\SubscribableNotifications\Messages\SubscribableMailMessage;
10+
11+
// TODO: Import your notifiable model and notification class:
12+
// use App\Models\User;
13+
// use App\Notifications\YourNotification;
14+
15+
class SubscribableNotificationTest extends TestCase
16+
{
17+
use RefreshDatabase;
18+
19+
/**
20+
* Verify that a subscribed user receives the notification.
21+
*
22+
* Requires:
23+
* - Your notifiable model implements CanUnsubscribe and
24+
* CheckSubscriptionStatusBeforeSendingNotifications.
25+
* - Your notification implements CheckNotifiableSubscriptionStatus
26+
* and returns true from checkMailSubscriptionStatus().
27+
*/
28+
public function test_subscribed_user_receives_notification(): void
29+
{
30+
Notification::fake();
31+
Subscriber::fake()->alwaysSubscribed();
32+
33+
// TODO: Replace with your notifiable model factory
34+
$user = \App\Models\User::factory()->create();
35+
36+
// TODO: Replace with your notification class
37+
$user->notify(new \App\Notifications\YourNotification());
38+
39+
// TODO: Replace with your notification class
40+
Notification::assertSentTo($user, \App\Notifications\YourNotification::class);
41+
}
42+
43+
/**
44+
* Verify that a globally unsubscribed user does not receive the notification.
45+
*
46+
* Requires the same model and notification setup as the test above.
47+
*/
48+
public function test_globally_unsubscribed_user_does_not_receive_notification(): void
49+
{
50+
Notification::fake();
51+
Subscriber::fake()->alwaysUnsubscribed();
52+
53+
// TODO: Replace with your notifiable model factory
54+
$user = \App\Models\User::factory()->create();
55+
56+
// TODO: Replace with your notification class
57+
$user->notify(new \App\Notifications\YourNotification());
58+
59+
// TODO: Replace with your notification class
60+
Notification::assertNotSentTo($user, \App\Notifications\YourNotification::class);
61+
}
62+
63+
/**
64+
* Verify that a user unsubscribed from a specific list does not receive
65+
* notifications that target that list.
66+
*
67+
* Requires your notification to implement AppliesToMailingList in addition
68+
* to CheckNotifiableSubscriptionStatus.
69+
*/
70+
public function test_user_unsubscribed_from_mailing_list_does_not_receive_notification(): void
71+
{
72+
Notification::fake();
73+
74+
// alwaysUnsubscribed() makes both the global and per-list checks return false.
75+
// To test only per-list gating, configure a real handler in your service provider
76+
// that returns false only for the specific list.
77+
Subscriber::fake()->alwaysUnsubscribed();
78+
79+
// TODO: Replace with your notifiable model factory
80+
$user = \App\Models\User::factory()->create();
81+
82+
// TODO: Replace with a notification that implements AppliesToMailingList
83+
$user->notify(new \App\Notifications\YourNotification());
84+
85+
// TODO: Replace with your notification class
86+
Notification::assertNotSentTo($user, \App\Notifications\YourNotification::class);
87+
}
88+
89+
/**
90+
* Verify the mail message includes the expected unsubscribe view data.
91+
*
92+
* Tests the output of your notification's toMail() method directly, without
93+
* going through the notification channel pipeline.
94+
*/
95+
public function test_notification_email_includes_unsubscribe_links(): void
96+
{
97+
// TODO: Replace with your notifiable model factory
98+
$user = \App\Models\User::factory()->create();
99+
100+
// TODO: Replace with your notification class
101+
$notification = new \App\Notifications\YourNotification();
102+
103+
/** @var SubscribableMailMessage $message */
104+
$message = $notification->toMail($user);
105+
106+
$this->assertInstanceOf(SubscribableMailMessage::class, $message);
107+
$this->assertArrayHasKey('unsubscribeLinkForAll', $message->viewData);
108+
}
109+
110+
/**
111+
* Verify the mail message includes a per-list unsubscribe link when the
112+
* notification implements AppliesToMailingList.
113+
*/
114+
public function test_mailing_list_notification_includes_list_specific_unsubscribe_link(): void
115+
{
116+
// TODO: Replace with your notifiable model factory
117+
$user = \App\Models\User::factory()->create();
118+
119+
// TODO: Replace with a notification that implements AppliesToMailingList
120+
$notification = new \App\Notifications\YourNotification();
121+
122+
/** @var SubscribableMailMessage $message */
123+
$message = $notification->toMail($user);
124+
125+
$this->assertInstanceOf(SubscribableMailMessage::class, $message);
126+
$this->assertArrayHasKey('unsubscribeLink', $message->viewData);
127+
$this->assertArrayHasKey('unsubscribeLinkForAll', $message->viewData);
128+
}
129+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php
2+
3+
namespace Tests\Feature;
4+
5+
use Illuminate\Foundation\Testing\RefreshDatabase;
6+
use Tests\TestCase;
7+
use YlsIdeas\SubscribableNotifications\Facades\Subscriber;
8+
9+
// TODO: Import your notifiable model:
10+
// use App\Models\User;
11+
12+
class UnsubscribeRouteTest extends TestCase
13+
{
14+
use RefreshDatabase;
15+
16+
public function test_user_can_unsubscribe_from_all_mailing_lists(): void
17+
{
18+
$fake = Subscriber::fake();
19+
20+
// TODO: Replace with your notifiable model factory
21+
$user = \App\Models\User::factory()->create();
22+
23+
$this->get($user->unsubscribeLink())->assertSuccessful();
24+
25+
$fake->assertUnsubscribedFromAll($user);
26+
}
27+
28+
public function test_user_can_unsubscribe_from_a_specific_mailing_list(): void
29+
{
30+
$fake = Subscriber::fake();
31+
32+
// TODO: Replace with your notifiable model factory
33+
$user = \App\Models\User::factory()->create();
34+
35+
// TODO: Replace with a real mailing list identifier used by your application
36+
$this->get($user->unsubscribeLink('your-mailing-list'))->assertSuccessful();
37+
38+
$fake->assertUnsubscribedFromMailingList($user, 'your-mailing-list');
39+
}
40+
41+
public function test_rfc8058_one_click_post_unsubscribes_user(): void
42+
{
43+
$fake = Subscriber::fake();
44+
45+
// TODO: Replace with your notifiable model factory
46+
$user = \App\Models\User::factory()->create();
47+
48+
$this->post($user->unsubscribeLink(), ['List-Unsubscribe' => 'One-Click'])
49+
->assertNoContent();
50+
51+
$fake->assertUnsubscribedFromAll($user);
52+
}
53+
54+
public function test_rfc8058_one_click_post_unsubscribes_user_from_mailing_list(): void
55+
{
56+
$fake = Subscriber::fake();
57+
58+
// TODO: Replace with your notifiable model factory
59+
$user = \App\Models\User::factory()->create();
60+
61+
// TODO: Replace with a real mailing list identifier used by your application
62+
$this->post($user->unsubscribeLink('your-mailing-list'), ['List-Unsubscribe' => 'One-Click'])
63+
->assertNoContent();
64+
65+
$fake->assertUnsubscribedFromMailingList($user, 'your-mailing-list');
66+
}
67+
68+
public function test_post_without_rfc8058_body_is_rejected(): void
69+
{
70+
Subscriber::fake();
71+
72+
// TODO: Replace with your notifiable model factory
73+
$user = \App\Models\User::factory()->create();
74+
75+
$this->post($user->unsubscribeLink())->assertBadRequest();
76+
}
77+
78+
public function test_tampered_unsubscribe_url_is_rejected(): void
79+
{
80+
Subscriber::fake();
81+
82+
// TODO: Replace with your notifiable model factory
83+
$user = \App\Models\User::factory()->create();
84+
85+
$this->get($user->unsubscribeLink() . '&tampered=1')->assertForbidden();
86+
}
87+
}

0 commit comments

Comments
 (0)