Community程式設計與開發github.com

Taldres/laravel-waitlist

Write feature tests for an application's taldres/laravel-waitlist integration: get the plain tokens from events, assert the confirmation and other mails, test the signup, confirm and unsubscribe flows in PHP and over the package's HTTP routes, and bypass rate limits and bot checks in tests.

laravel-waitlist 是什麼?

laravel-waitlist is a Claude Code agent skill that write feature tests for an application's taldres/laravel-waitlist integration: get the plain tokens from events, assert the confirmation and other mails, test the signup, confirm and unsubscribe flows in PHP and over the package's HTTP routes, and bypass rate limits and bot checks in tests.

相容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Taldres/laravel-waitlist/tree/HEAD/resources/boost/skills/laravel-waitlist-testing

Installed? Explore more 程式設計與開發 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

Laravel Waitlist: testing the integration

Use this skill when writing or fixing tests in an application that uses taldres/laravel-waitlist: its forms, listeners, mails and pages, not the package itself.

Primary Goal

  • test the application's own code around the waitlist through the package's public API, with real tokens and real state changes

Workflow

1. Setup

  • The package migrations are published into database/migrations, so RefreshDatabase creates the tables.
  • APP_KEY must be set in the test environment: addresses are encrypted and looked up with keys derived from it.
  • Events implement ShouldDispatchAfterCommit; with RefreshDatabase they still dispatch as usual.
  • Package routes are registered at boot. To test them, set WAITLIST_ROUTES_ENABLED=true in phpunit.xml or .env.testing; config()->set() inside a test is too late. An empty WAITLIST_ROUTES_ENABLED= is a config error; routes that are off answer 404.
  • The app's WaitlistServiceProvider boots in tests, so its lists, fields and pages are there. Waitlist::define() inside a test replaces that project for that test only; the next test boots the app again with the provider's definitions.
  • Set other package config by key from the enum, not a string: config()->set(ConfigKey::DoubleOptIn->value, false) (Taldres\Waitlist\Enums\ConfigKey; the defaults live in the package's config/waitlist.php).
  • A setup mistake throws InvalidConfigurationException, never a WaitlistException; assert it, do not catch it in app code. So does a config value that does not read, naming the key: null says never or off, and a number standing for "forever" is refused (a link may not end after 2038-01-19, a retention period may not reach back before 1970).

2. Post back what the form showed

Wording can be one text or one per locale; post back version and locale as served:

use Taldres\Waitlist\Facades\Waitlist;
use Taldres\Waitlist\Support\PurposeWording;

$purposes = collect(Waitlist::purposes('beta'))
    ->filter(fn (PurposeWording $wording) => $wording->required)   // add optional ones as the test needs
    ->mapWithKeys(fn (PurposeWording $wording) => [$wording->purpose => ['version' => $wording->version, 'locale' => $wording->locale]])
    ->all();

$result = Waitlist::for('beta')->add('[email protected]', $purposes);

3. Get the plain tokens

The plain confirm token exists only in the EntrySubscribed payload, on a list with double opt-in. Unsubscribe and manage tokens also come from Waitlist::unsubscribeToken($entry)->token and Waitlist::manageLink($entry)->token.

use Illuminate\Support\Facades\Event;
use Taldres\Waitlist\Enums\EntryStatus;
use Taldres\Waitlist\Events\EntrySubscribed;

Event::fake([EntrySubscribed::class]);

Waitlist::for('beta')->add('[email protected]', $purposes);

$event = Event::dispatched(EntrySubscribed::class)->first()[0];

$entry = Waitlist::confirm($event->confirmToken);
expect($entry->status)->toBe(EntryStatus::Confirmed);   // or $this->assertSame(...)

To keep the real listeners running (e.g. the mail listener) and still capture a token, listen instead of faking:

$token = null;
Event::listen(function (EntrySubscribed $event) use (&$token) {
    $token = $event->confirmToken;
});

$event->unsubscribeToken works the same for unsubscribe tests, ManageLinkRequested::$manageToken for the preference page.

On a list with ->doubleOptIn(false) (or with waitlist.double_opt_in.enabled off), EntrySubscribed fires with requiresConfirmation false and confirmToken null, then EntryConfirmed; the entry is confirmed already. A signup held back by max_pending_per_address fires no EntrySubscribed at all ($result->outcome is confirmation_deferred).

4. Assert the mails

use Illuminate\Support\Facades\Mail;

Mail::fake();

Waitlist::for('beta')->add('[email protected]', $purposes);

Mail::assertSent(ConfirmWaitlistMail::class, fn ($mail) => $mail->hasTo('[email protected]'));

Queued listeners run inline with QUEUE_CONNECTION=sync (Laravel's test default).

5. HTTP routes

use Illuminate\Routing\Middleware\ThrottleRequests;

$this->withoutMiddleware(ThrottleRequests::class);   // many signups from one test IP
Event::fake([EntrySubscribed::class]);

$wording = $this->getJson('/waitlist/purposes?list=beta')->assertOk()->json('data.0');

$this->postJson('/waitlist', [
    'email' => '[email protected]',
    'list' => 'beta',
    'purposes' => [$wording['purpose'] => ['version' => $wording['version'], 'locale' => $wording['locale']]],
])->assertStatus(202);

$token = Event::dispatched(EntrySubscribed::class)->first()[0]->confirmToken;

$this->postJson("/waitlist/confirm/{$token}")->assertOk()->assertJsonPath('data.status', 'confirmed');
$this->getJson('/waitlist/confirm/unknown')->assertNotFound()->assertJsonPath('error', 'invalid_token');

The package's own refusals name an error (invalid_token, expired_token, unknown_list, not_subscribed, list_unavailable). Assert it: a 404 without one is a wrong URL, routes that are off or the useWaitlist gate, and would pass assertNotFound() for an unknown token.

Only the HTTP signup applies a project's fields; add() trusts its caller. Define them in the test to assert the 422:

use Taldres\Waitlist\Definitions\ProjectDefinition;

Waitlist::define(function (ProjectDefinition $project): void {
    $project->purpose('waitlist', ['v1' => 'Email me.']);
    $project->list('teams', purpose: 'waitlist')->fields(['contact_phone' => ['required']]);
});

$this->postJson('/waitlist', [/* email, list, purposes */])->assertJsonValidationErrors('metadata.contact_phone');

Use postJson()/getJson(): without Accept: application/json, the pages in the project's urls() turn answers into redirects.

6. Bot checks and erasure

use Taldres\Waitlist\Events\EntryForgotten;

Waitlist::verifySpamUsing(fn () => true);    // bypass Turnstile & co. in tests
Waitlist::verifySpamUsing(fn () => false);   // assert the 422 "Spam check failed."
Waitlist::verifySpamUsing(null);             // back to the configured protector

Event::fake([EntryForgotten::class]);
Waitlist::allProjects()->forget('[email protected]');
Event::assertDispatched(EntryForgotten::class, fn (EntryForgotten $event) => $event->email === '[email protected]');

Rules, References, and Templates

  • Assert state through the public API: $entry->status, hasConsentFor(), Waitlist::for($list)->snapshot(), recipients()->count().
  • Outcome of a signup: $result->outcome (SubscribeOutcome: started, confirmation_resent, resend_suppressed, already_confirmed, resubscribed, confirmation_deferred).
  • Over HTTP, signups answer 202 for new and known addresses alike; assert state, not the response body.

Examples

  • "Test that signing up sends the confirmation mail": steps 2 and 4.
  • "Test the confirm page": steps 3 and 5, or call the controller route with the captured token.
  • "Test that the newsletter only goes to people who agreed": subscribe two addresses, one with the optional purpose, confirm both, assert Waitlist::recipients('newsletter')->count() === 1.

Anti-patterns

  • Creating WaitlistEntry, subscriptions or consents with factories or create(); go through add() and the token methods, or events and invariants are skipped.
  • Reading tokens from the database columns; use the events or Waitlist::unsubscribeToken() and Waitlist::manageLink().
  • Enabling routes with config()->set() inside a test.
  • Faking EntrySubscribed in a test that asserts the confirmation mail.
  • Using arrow functions to capture a token: they capture by value.

Individual skills in this repo

This repo contains 13 individual skills — each has its own dedicated page.

Taldres/laravel-waitlist

Use this skill when reviewing Laravel package compatibility across composer constraints, PHP versions, Laravel versions, Testbench versions, dependency stability lanes, Windows CI, or matrix-sensitive code and workflow changes.

Taldres/laravel-waitlist

Use this skill when creating or updating the bundled Laravel Boost skill under resources/boost/skills from the package implementation and package documentation. Trigger after public APIs, commands, config, routes, views, publish tags, README content, or examples change.

Taldres/laravel-waitlist

Use this skill when preparing Laravel package releases: CHANGELOG.md updates, generated release notes, GitHub release workflows, version checks, tags, release validation, or release automation changes. Never publish autonomously.

Taldres/laravel-waitlist

Use this skill when adding Laravel package capabilities or wiring them through the service provider: commands, migrations, routes, config merges, views, translations, assets, middleware, publish tags, workbench files, or console-only behavior.

Taldres/laravel-waitlist

Use this skill when writing, editing, fixing, or reviewing package tests with Pest 4/5 and Orchestra Testbench, including TDD, feature tests, unit tests, type coverage, arch tests, workbench behavior, commands, routes, config, migrations, and publishable resources.

Taldres/laravel-waitlist

Integrate taldres/laravel-waitlist in a Laravel application: define purposes, lists and fields, build the signup form, send the confirmation mail from events, add a preference page, and keep consent, retention and encryption intact.

Taldres/laravel-waitlist

Build the pages of a taldres/laravel-waitlist integration: the signup form with the registered wording, and the confirm, unsubscribe and preference pages, either in Laravel controllers (Blade, Livewire, Inertia) or in an SPA or static site over the package's JSON API, including CORS and bot protection.

Taldres/laravel-waitlist

Turn a taldres/laravel-waitlist waitlist into a launch: invite people in batches in signup order, let invited addresses register, announce the launch to everyone who agreed, and erase the list once its purpose is fulfilled.

Taldres/laravel-waitlist

Send the mails of a taldres/laravel-waitlist integration: the double opt-in confirmation, the preference page link, a welcome mail, launch mails and newsletters to the people who agreed, with the right unsubscribe links, one-click headers and a sender per project.

Taldres/laravel-waitlist

Operate a taldres/laravel-waitlist installation: answer access and erasure requests, withdraw a purpose on request, apply retention, erase a list once its purpose is fulfilled, rotate APP_KEY with waitlist:rekey, export a list, and check the setup before going live.

Taldres/laravel-waitlist

Run the waitlists of several products in one Laravel app with taldres/laravel-waitlist projects: define projects, scope facade calls with project(), resolve the project of HTTP signups, send mail per project, handle requests across all projects, or keep projects in a database for a central waitlist API.

Taldres/laravel-waitlist

Keep a newsletter tool such as Brevo, Mailchimp or Mailcoach in step with a taldres/laravel-waitlist waitlist: add confirmed contacts per purpose, remove them on withdrawal and erasure, and carry unsubscribes made at the provider back into the waitlist via a webhook.

Taldres/laravel-waitlist

Build waitlist statistics with taldres/laravel-waitlist: signups and confirmations per day for charts, totals and confirmation rates for a period, the confirmed count on any past day, and how many people are on a list right now, for dashboards, admin pages and API endpoints.

相關技能