text:
### Page
- Page URL: http://shop.test:8000/products/classic-cotton-t-shirt
- Page Title: Acme Fashion
### Snapshot
```yaml
- generic [active] [ref=f6e1]:
  - link "Skip to content" [ref=f6e2] [cursor=pointer]:
    - /url: "#main-content"
  - generic [ref=f6e3]: Free shipping on orders over €50
  - banner [ref=f6e4]:
    - navigation "Main navigation" [ref=f6e5]:
      - link "Acme Fashion" [ref=f6e6] [cursor=pointer]:
        - /url: http://shop.test:8000
      - generic [ref=f6e7]:
        - link "Collections" [ref=f6e8] [cursor=pointer]:
          - /url: http://shop.test:8000/collections
        - link "New Arrivals" [ref=f6e9] [cursor=pointer]:
          - /url: http://shop.test:8000/collections/new-arrivals
        - link "About" [ref=f6e10] [cursor=pointer]:
          - /url: http://shop.test:8000/pages/about
        - link "Search" [ref=f6e11] [cursor=pointer]:
          - /url: http://shop.test:8000/search
      - generic [ref=f6e12]:
        - link "Account" [ref=f6e13] [cursor=pointer]:
          - /url: http://shop.test:8000/account/login
        - link "Cart" [ref=f6e14] [cursor=pointer]:
          - /url: http://shop.test:8000/cart
  - main [ref=f6e15]:
    - generic [ref=f6e16]:
      - generic [ref=f6e17]: ⌂
      - generic [ref=f6e20]:
        - paragraph [ref=f6e21]: Acme
        - heading "Classic Cotton T-Shirt" [level=1] [ref=f6e22]
        - paragraph [ref=f6e23]: €24.99
        - paragraph [ref=f6e24]: Designed for comfortable everyday wear.
        - group "Size" [ref=f6e25]:
          - generic [ref=f6e27]:
            - button "S" [ref=f6e28]
            - button "M" [ref=f6e29]
            - button "L" [ref=f6e30]
            - button "XL" [ref=f6e31]
        - group "Color" [ref=f6e32]:
          - generic [ref=f6e34]:
            - button "Black" [ref=f6e35]
            - button "White" [ref=f6e36]
            - button "Navy" [ref=f6e37]
        - generic [ref=f6e38]:
          - generic [ref=f6e39]: Quantity
          - spinbutton "Quantity" [ref=f6e40]: "1"
          - button "Add to cart" [ref=f6e41]
  - contentinfo [ref=f6e42]:
    - generic [ref=f6e43]:
      - generic [ref=f6e44]:
        - paragraph [ref=f6e45]: Acme Fashion
        - paragraph [ref=f6e46]: Thoughtful everyday pieces, made to last.
      - generic [ref=f6e47]:
        - paragraph [ref=f6e48]: Shop
        - link "Collections" [ref=f6e49] [cursor=pointer]:
          - /url: http://shop.test:8000/collections
      - generic [ref=f6e50]:
        - paragraph [ref=f6e51]: Help
        - link "Your account" [ref=f6e52] [cursor=pointer]:
          - /url: http://shop.test:8000/account
      - generic [ref=f6e53]:
        - paragraph [ref=f6e54]: Stay in the loop
        - paragraph [ref=f6e55]: Subscribe for exclusive offers and updates.
    - generic [ref=f6e56]: © 2026 Acme Fashion. All rights reserved.
```


text:
Script completed
Wall time 0.1 seconds
Output:

text: {"content":[{"type":"text","text":"### Ran Playwright code\n```js\nawait page.goto('http://shop.test:8000/products/classic-cotton-t-shirt');\n```\n### Page\n- Page URL: http://shop.test:8000/products/classic-cotton-t-shirt\n- Page Title: Acme Fashion\n### Snapshot\n- [Snapshot](.playwright-mcp/page-2026-08-20T21-50-31-555Z.yml)\n### Events\n- New console entries: .playwright-mcp/console-2026-08-20T21-50-31-472Z.log#L1"}]}

text: {"content":[{"type":"text","text":"### Page\n- Page URL: http://shop.test:8000/products/classic-cotton-t-shirt\n- Page Title: Acme Fashion\n### Snapshot\n```yaml\n- generic [active] [ref=f6e1]:\n  - link \"Skip to content\" [ref=f6e2] [cursor=pointer]:\n    - /url: \"#main-content\"\n  - generic [ref=f6e3]: Free shipping on orders over €50\n  - banner [ref=f6e4]:\n    - navigation \"Main navigation\" [ref=f6e5]:\n      - link \"Acme Fashion\" [ref=f6e6] [cursor=pointer]:\n        - /url: http://shop.test:8000\n      - generic [ref=f6e7]:\n        - link \"Collections\" [ref=f6e8] [cursor=pointer]:\n          - /url: http://shop.test:8000/collections\n        - link \"New Arrivals\" [ref=f6e9] [cursor=pointer]:\n          - /url: http://shop.test:8000/collections/new-arrivals\n        - link \"About\" [ref=f6e10] [cursor=pointer]:\n          - /url: http://shop.test:8000/pages/about\n        - link \"Search\" [ref=f6e11] [cursor=pointer]:\n          - /url: http://shop.test:8000/search\n      - generic [ref=f6e12]:\n        - link \"Account\" [ref=f6e13] [cursor=pointer]:\n          - /url: http://shop.test:8000/account/login\n        - link \"Cart\" [ref=f6e14] [cursor=pointer]:\n          - /url: http://shop.test:8000/cart\n  - main [ref=f6e15]:\n    - generic [ref=f6e16]:\n      - generic [ref=f6e17]: ⌂\n      - generic [ref=f6e20]:\n        - paragraph [ref=f6e21]: Acme\n        - heading \"Classic Cotton T-Shirt\" [level=1] [ref=f6e22]\n        - paragraph [ref=f6e23]: €24.99\n        - paragraph [ref=f6e24]: Designed for comfortable everyday wear.\n        - group \"Size\" [ref=f6e25]:\n          - generic [ref=f6e27]:\n            - button \"S\" [ref=f6e28]\n            - button \"M\" [ref=f6e29]\n            - button \"L\" [ref=f6e30]\n            - button \"XL\" [ref=f6e31]\n        - group \"Color\" [ref=f6e32]:\n          - generic [ref=f6e34]:\n            - button \"Black\" [ref=f6e35]\n            - button \"White\" [ref=f6e36]\n            - button \"Navy\" [ref=f6e37]\n        - generic [ref=f6e38]:\n          - generic [ref=f6e39]: Quantity\n          - spinbutton \"Quantity\" [ref=f6e40]: \"1\"\n          - button \"Add to cart\" [ref=f6e41]\n  - contentinfo [ref=f6e42]:\n    - generic [ref=f6e43]:\n      - generic [ref=f6e44]:\n        - paragraph [ref=f6e45]: Acme Fashion\n        - paragraph [ref=f6e46]: Thoughtful everyday pieces, made to last.\n      - generic [ref=f6e47]:\n        - paragraph [ref=f6e48]: Shop\n        - link \"Collections\" [ref=f6e49] [cursor=pointer]:\n          - /url: http://shop.test:8000/collections\n      - generic [ref=f6e50]:\n        - paragraph [ref=f6e51]: Help\n        - link \"Your account\" [ref=f6e52] [cursor=pointer]:\n          - /url: http://shop.test:8000/account\n      - generic [ref=f6e53]:\n        - paragraph [ref=f6e54]: Stay in the loop\n        - paragraph [ref=f6e55]: Subscribe for exclusive offers and updates.\n    - generic [ref=f6e56]: © 2026 Acme Fashion. All rights reserved.\n```"}]}


exec

status: completed

input:
const click = await tools.mcp__playwright__browser_click({target:"f6e41",element:"Add to cart button"});
text(click);
const snap = await tools.mcp__playwright__browser_snapshot({});
text(snap);


type: McpToolCall

server: playwright

element: Add to cart button

status: completed

type: text

text:
### Ran Playwright code
```js
await page.getByRole('button', { name: 'Add to cart' }).click();
```
### Page
- Page URL: http://shop.test:8000/products/classic-cotton-t-shirt
- Page Title: Acme Fashion
### Snapshot
- [Snapshot](.playwright-mcp/page-2026-08-20T21-50-35-264Z.yml)


text:
### Page
- Page URL: http://shop.test:8000/products/classic-cotton-t-shirt
- Page Title: Acme Fashion
### Snapshot
```yaml
- generic [ref=f6e1]:
  - link "Skip to content" [ref=f6e2] [cursor=pointer]:
    - /url: "#main-content"
  - generic [ref=f6e3]: Free shipping on orders over €50
  - banner [ref=f6e4]:
    - navigation "Main navigation" [ref=f6e5]:
      - link "Acme Fashion" [ref=f6e6] [cursor=pointer]:
        - /url: http://shop.test:8000
      - generic [ref=f6e7]:
        - link "Collections" [ref=f6e8] [cursor=pointer]:
          - /url: http://shop.test:8000/collections
        - link "New Arrivals" [ref=f6e9] [cursor=pointer]:
          - /url: http://shop.test:8000/collections/new-arrivals
        - link "About" [ref=f6e10] [cursor=pointer]:
          - /url: http://shop.test:8000/pages/about
        - link "Search" [ref=f6e11] [cursor=pointer]:
          - /url: http://shop.test:8000/search
      - generic [ref=f6e12]:
        - link "Account" [ref=f6e13] [cursor=pointer]:
          - /url: http://shop.test:8000/account/login
        - link "Cart" [ref=f6e14] [cursor=pointer]:
          - /url: http://shop.test:8000/cart
  - main [ref=f6e15]:
    - generic [ref=f6e16]:
      - generic [ref=f6e17]: ⌂
      - generic [ref=f6e20]:
        - paragraph [ref=f6e21]: Acme
        - heading "Classic Cotton T-Shirt" [level=1] [ref=f6e22]
        - paragraph [ref=f6e23]: €24.99
        - paragraph [ref=f6e24]: Designed for comfortable everyday wear.
        - group "Size" [ref=f6e25]:
          - generic [ref=f6e27]:
            - button "S" [ref=f6e28]
            - button "M" [ref=f6e29]
            - button "L" [ref=f6e30]
            - button "XL" [ref=f6e31]
        - group "Color" [ref=f6e32]:
          - generic [ref=f6e34]:
            - button "Black" [ref=f6e35]
            - button "White" [ref=f6e36]
            - button "Navy" [ref=f6e37]
        - generic [ref=f6e38]:
          - generic [ref=f6e39]: Quantity
          - spinbutton "Quantity" [ref=f6e40]: "1"
          - button "Add to cart" [active] [ref=f6e41]
        - status [ref=f6e57]: Added to cart
  - contentinfo [ref=f6e42]:
    - generic [ref=f6e43]:
      - generic [ref=f6e44]:
        - paragraph [ref=f6e45]: Acme Fashion
        - paragraph [ref=f6e46]: Thoughtful everyday pieces, made to last.
      - generic [ref=f6e47]:
        - paragraph [ref=f6e48]: Shop
        - link "Collections" [ref=f6e49] [cursor=pointer]:
          - /url: http://shop.test:8000/collections
      - generic [ref=f6e50]:
        - paragraph [ref=f6e51]: Help
        - link "Your account" [ref=f6e52] [cursor=pointer]:
          - /url: http://shop.test:8000/account
      - generic [ref=f6e53]:
        - paragraph [ref=f6e54]: Stay in the loop
        - paragraph [ref=f6e55]: Subscribe for exclusive offers and updates.
    - generic [ref=f6e56]: © 2026 Acme Fashion. All rights reserved.
```


text:
Script completed
Wall time 1.1 seconds
Output:

text: {"content":[{"type":"text","text":"### Ran Playwright code\n```js\nawait page.getByRole('button', { name: 'Add to cart' }).click();\n```\n### Page\n- Page URL: http://shop.test:8000/products/classic-cotton-t-shirt\n- Page Title: Acme Fashion\n### Snapshot\n- [Snapshot](.playwright-mcp/page-2026-08-20T21-50-35-264Z.yml)"}]}

text: {"content":[{"type":"text","text":"### Page\n- Page URL: http://shop.test:8000/products/classic-cotton-t-shirt\n- Page Title: Acme Fashion\n### Snapshot\n```yaml\n- generic [ref=f6e1]:\n  - link \"Skip to content\" [ref=f6e2] [cursor=pointer]:\n    - /url: \"#main-content\"\n  - generic [ref=f6e3]: Free shipping on orders over €50\n  - banner [ref=f6e4]:\n    - navigation \"Main navigation\" [ref=f6e5]:\n      - link \"Acme Fashion\" [ref=f6e6] [cursor=pointer]:\n        - /url: http://shop.test:8000\n      - generic [ref=f6e7]:\n        - link \"Collections\" [ref=f6e8] [cursor=pointer]:\n          - /url: http://shop.test:8000/collections\n        - link \"New Arrivals\" [ref=f6e9] [cursor=pointer]:\n          - /url: http://shop.test:8000/collections/new-arrivals\n        - link \"About\" [ref=f6e10] [cursor=pointer]:\n          - /url: http://shop.test:8000/pages/about\n        - link \"Search\" [ref=f6e11] [cursor=pointer]:\n          - /url: http://shop.test:8000/search\n      - generic [ref=f6e12]:\n        - link \"Account\" [ref=f6e13] [cursor=pointer]:\n          - /url: http://shop.test:8000/account/login\n        - link \"Cart\" [ref=f6e14] [cursor=pointer]:\n          - /url: http://shop.test:8000/cart\n  - main [ref=f6e15]:\n    - generic [ref=f6e16]:\n      - generic [ref=f6e17]: ⌂\n      - generic [ref=f6e20]:\n        - paragraph [ref=f6e21]: Acme\n        - heading \"Classic Cotton T-Shirt\" [level=1] [ref=f6e22]\n        - paragraph [ref=f6e23]: €24.99\n        - paragraph [ref=f6e24]: Designed for comfortable everyday wear.\n        - group \"Size\" [ref=f6e25]:\n          - generic [ref=f6e27]:\n            - button \"S\" [ref=f6e28]\n            - button \"M\" [ref=f6e29]\n            - button \"L\" [ref=f6e30]\n            - button \"XL\" [ref=f6e31]\n        - group \"Color\" [ref=f6e32]:\n          - generic [ref=f6e34]:\n            - button \"Black\" [ref=f6e35]\n            - button \"White\" [ref=f6e36]\n            - button \"Navy\" [ref=f6e37]\n        - generic [ref=f6e38]:\n          - generic [ref=f6e39]: Quantity\n          - spinbutton \"Quantity\" [ref=f6e40]: \"1\"\n          - button \"Add to cart\" [active] [ref=f6e41]\n        - status [ref=f6e57]: Added to cart\n  - contentinfo [ref=f6e42]:\n    - generic [ref=f6e43]:\n      - generic [ref=f6e44]:\n        - paragraph [ref=f6e45]: Acme Fashion\n        - paragraph [ref=f6e46]: Thoughtful everyday pieces, made to last.\n      - generic [ref=f6e47]:\n        - paragraph [ref=f6e48]: Shop\n        - link \"Collections\" [ref=f6e49] [cursor=pointer]:\n          - /url: http://shop.test:8000/collections\n      - generic [ref=f6e50]:\n        - paragraph [ref=f6e51]: Help\n        - link \"Your account\" [ref=f6e52] [cursor=pointer]:\n          - /url: http://shop.test:8000/account\n      - generic [ref=f6e53]:\n        - paragraph [ref=f6e54]: Stay in the loop\n        - paragraph [ref=f6e55]: Subscribe for exclusive offers and updates.\n    - generic [ref=f6e56]: © 2026 Acme Fashion. All rights reserved.\n```"}]}


status: completed

input:
const c = await tools.mcp__playwright__browser_console_messages({level:"error",all:false});
text(c);


level: error

text:
### Result
Total messages: 1 (Errors: 0, Warnings: 0)
Returning 0 messages for level "error"


text:
Script completed
Wall time 0.0 seconds
Output:

text: {"content":[{"type":"text","text":"### Result\nTotal messages: 1 (Errors: 0, Warnings: 0)\nReturning 0 messages for level \"error\"\n"}]}


type: AgentMessage

type: Text

text: The Livewire blocker is fixed and verified in Playwright: the product page now adds the item and renders “Added to cart” with no console errors. I’m moving on to the full automated suite, then I’ll exercise checkout, authentication, and the admin surface in the browser.

phase: commentary


type: message

role: assistant


status: completed

input:
const r = await tools.exec_command({cmd:"php artisan test","workdir":"/Users/wesner/Herd/shop","yield_time_ms":1000,"max_output_tokens":12000});
text(JSON.stringify(r));


type: CommandExecution

command:
/bin/zsh
-lc
php artisan test

type: unknown

cmd: php artisan test

status: failed

stdout:
   PASS  Tests\Unit\ExampleTest
  ✓ that true is true

   PASS  Tests\Unit\Policies\StorePolicyTest
  ✓ product permissions are based on the users role in the current stor… 0.11s
  ✓ store deletion is restricted to owners                               0.01s

   PASS  Tests\Unit\PricingEngineTest
  ✓ tax calculations use integer minor-unit arithmetic
  ✓ discount calculations cap fixed discounts and allocate percentages
  ✓ free shipping discounts do not reduce merchandise totals
  ✓ mock payment provider supports the documented magic cards and deferred transfers

   PASS  Tests\Unit\Tenancy\BelongsToStoreTest
  ✓ tenant models are created and queried for the current store only     0.01s
  ✓ tenant queries return no rows when no current store is resolved

   FAIL  Tests\Feature\Auth\AuthenticationTest
  ⨯ login screen can be rendered                                         0.01s
  ⨯ users can authenticate using the login screen                        0.01s
  ⨯ users can not authenticate with invalid password                     0.01s
  ⨯ users with two factor enabled are redirected to two factor challeng… 0.01s
  ⨯ users can logout                                                     0.01s

   FAIL  Tests\Feature\Auth\EmailVerificationTest
  ⨯ email verification screen can be rendered                            0.01s
  ⨯ email can be verified                                                0.01s
  ✓ email is not verified with invalid hash
  ⨯ already verified user visiting verification link is redirected with… 0.01s

   FAIL  Tests\Feature\Auth\PasswordConfirmationTest
  ⨯ confirm password screen can be rendered                              0.01s

   FAIL  Tests\Feature\Auth\PasswordResetTest
  ⨯ reset password link screen can be rendered
  ⨯ reset password link can be requested                                 0.01s
  ⨯ reset password screen can be rendered
  ⨯ password can be reset with valid token                               0.01s

   FAIL  Tests\Feature\Auth\RegistrationTest
  ⨯ registration screen can be rendered                                  0.01s
  ⨯ new users can register

   FAIL  Tests\Feature\Auth\TwoFactorChallengeTest
  ⨯ two factor challenge redirects to login when not authenticated       0.01s
  ⨯ two factor challenge can be rendered                                 0.01s

   PASS  Tests\Feature\CommerceFlowTest
  ✓ guest cart and card checkout create a paid order                     0.05s
  ✓ declined payments release the reservation and do not create an orde… 0.04s
  ✓ stale cart versions return a conflict response                       0.03s
  ✓ bank transfer keeps inventory reserved until admin confirmation      0.04s

   FAIL  Tests\Feature\DashboardTest
  ⨯ guests are redirected to the login page                              0.01s
  ⨯ authenticated users can visit the dashboard                          0.01s

   PASS  Tests\Feature\ExampleTest
  ✓ it returns a successful response                                     0.01s

   PASS  Tests\Feature\SearchAnalyticsWebhookTest
  ✓ search is tenant scoped, paginated, and logs the query               0.03s
  ✓ product changes are synchronized to the FTS index and autocomplete   0.03s
  ✓ analytics events aggregate idempotently into daily metrics           0.03s
  ✓ webhooks are signed and delivered with platform headers              0.03s

   PASS  Tests\Feature\Settings\PasswordUpdateTest
  ✓ password can be updated                                              0.05s
  ✓ correct password must be provided to update password                 0.03s

   FAIL  Tests\Feature\Settings\ProfileUpdateTest
  ⨯ profile page is displayed                                            0.01s
  ✓ profile information can be updated                                   0.03s
  ✓ email verification status is unchanged when email address is unchan… 0.03s
  ✓ user can delete their account                                        0.01s
  ✓ correct password must be provided to delete account                  0.02s

   FAIL  Tests\Feature\Settings\TwoFactorAuthenticationTest
  ⨯ two factor settings page can be rendered                             0.01s
  ⨯ two factor settings page requires password confirmation when enable… 0.01s
  ⨯ two factor settings page returns forbidden response when two factor… 0.01s
  ✓ two factor authentication disabled when confirmation abandoned betw… 0.02s

   FAIL  Tests\Feature\Tenancy\ResolveStoreTest
  ✓ a storefront request resolves and shares the store by hostname       0.01s
  ✓ a storefront hostname is cached as a store id                        0.01s
  ✓ unknown storefront hosts return not found
  ✓ suspended storefronts return service unavailable                     0.01s
  ⨯ admin requests resolve only the session store the user belongs to    0.05s
  ⨯ admin requests reject a session store without membership             0.04s
  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Auth\AuthenticationTest > login screen can be rende…
  Expected response status code [200] but received 404.
Failed asserting that 404 is identical to 200.

  at tests/Feature/Auth/AuthenticationTest.php:11
      7▕
      8▕ test('login screen can be rendered', function () {
      9▕     $response = $this->get(route('login'));
     10▕
  ➜  11▕     $response->assertOk();
     12▕ });
     13▕
     14▕ test('users can authenticate using the login screen', function () {
     15▕     $user = User::factory()->create();

  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Auth\AuthenticationTest > users can authenticate us…
  Expected response status code [201, 301, 302, 303, 307, 308] but received 404.
Failed asserting that false is true.

  at tests/Feature/Auth/AuthenticationTest.php:24
     20▕     ]);
     21▕
     22▕     $response
     23▕         ->assertSessionHasNoErrors()
  ➜  24▕         ->assertRedirect(route('dashboard', absolute: false));
     25▕
     26▕     $this->assertAuthenticated();
     27▕ });
     28▕

  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Auth\AuthenticationTest > users can not authenticat…
  Session is missing expected key [errors].
Failed asserting that false is true.

  at tests/Feature/Auth/AuthenticationTest.php:37
     33▕         'email' => $user->email,
     34▕         'password' => 'wrong-password',
     35▕     ]);
     36▕
  ➜  37▕     $response->assertSessionHasErrorsIn('email');
     38▕
     39▕     $this->assertGuest();
     40▕ });
     41▕

  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Auth\AuthenticationTest > users with two factor ena…
  Expected response status code [201, 301, 302, 303, 307, 308] but received 404.
Failed asserting that false is true.

  at tests/Feature/Auth/AuthenticationTest.php:58
     54▕         'email' => $user->email,
     55▕         'password' => 'password',
     56▕     ]);
     57▕
  ➜  58▕     $response->assertRedirect(route('two-factor.login'));
     59▕     $this->assertGuest();
     60▕ });
     61▕
     62▕ test('users can logout', function () {

  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Auth\AuthenticationTest > users can logout
  Expected response status code [201, 301, 302, 303, 307, 308] but received 404.
Failed asserting that false is true.

  at tests/Feature/Auth/AuthenticationTest.php:67
     63▕     $user = User::factory()->create();
     64▕
     65▕     $response = $this->actingAs($user)->post(route('logout'));
     66▕
  ➜  67▕     $response->assertRedirect(route('home'));
     68▕     $this->assertGuest();
     69▕ });

  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Auth\EmailVerificationTest > email verification scr…
  Expected response status code [200] but received 404.
Failed asserting that 404 is identical to 200.

  at tests/Feature/Auth/EmailVerificationTest.php:15
     11▕     $user = User::factory()->unverified()->create();
     12▕
     13▕     $response = $this->actingAs($user)->get(route('verification.notice'));
     14▕
  ➜  15▕     $response->assertOk();
     16▕ });
     17▕
     18▕ test('email can be verified', function () {
     19▕     $user = User::factory()->unverified()->create();

  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Auth\EmailVerificationTest > email can be verified
  The expected [Illuminate\Auth\Events\Verified] event was not dispatched.
Failed asserting that false is true.

  at vendor/laravel/framework/src/Illuminate/Support/Testing/Fakes/EventFake.php:144
    140▕         if (is_int($callback)) {
    141▕             return $this->assertDispatchedTimes($event, $callback);
    142▕         }
    143▕
  ➜ 144▕         PHPUnit::assertTrue(
    145▕             $this->dispatched($event, $callback)->count() > 0,
    146▕             "The expected [{$event}] event was not dispatched."
    147▕         );
    148▕     }

      +2 vendor frames
  3   tests/Feature/Auth/EmailVerificationTest.php:31

  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Auth\EmailVerificationTest > already verified user…
  Expected response status code [201, 301, 302, 303, 307, 308] but received 404.
Failed asserting that false is true.

  at tests/Feature/Auth/EmailVerificationTest.php:65
     61▕         ['id' => $user->id, 'hash' => sha1($user->email)]
     62▕     );
     63▕
     64▕     $this->actingAs($user)->get($verificationUrl)
  ➜  65▕         ->assertRedirect(route('dashboard', absolute: false).'?verified=1');
     66▕
     67▕     expect($user->fresh()->hasVerifiedEmail())->toBeTrue();
     68▕     Event::assertNotDispatched(Verified::class);
     69▕ });

  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Auth\PasswordConfirmationTest > confirm password sc…
  Expected response status code [200] but received 404.
Failed asserting that 404 is identical to 200.

  at tests/Feature/Auth/PasswordConfirmationTest.php:12
      8▕     $user = User::factory()->create();
      9▕
     10▕     $response = $this->actingAs($user)->get(route('password.confirm'));
     11▕
  ➜  12▕     $response->assertOk();
     13▕ });

  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Auth\PasswordResetTest > reset password link screen…
  Expected response status code [200] but received 404.
Failed asserting that 404 is identical to 200.

  at tests/Feature/Auth/PasswordResetTest.php:12
      8▕
      9▕ test('reset password link screen can be rendered', function () {
     10▕     $response = $this->get(route('password.request'));
     11▕
  ➜  12▕     $response->assertOk();
     13▕ });
     14▕
     15▕ test('reset password link can be requested', function () {
     16▕     Notification::fake();

  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Auth\PasswordResetTest > reset password link can be…
  The expected [Illuminate\Auth\Notifications\ResetPassword] notification was not sent.
Failed asserting that false is true.

  at vendor/laravel/framework/src/Illuminate/Support/Testing/Fakes/NotificationFake.php:89
     85▕         if (is_numeric($callback)) {
     86▕             return $this->assertSentToTimes($notifiable, $notification, $callback);
     87▕         }
     88▕
  ➜  89▕         PHPUnit::assertTrue(
     90▕             $this->sent($notifiable, $notification, $callback)->count() > 0,
     91▕             "The expected [{$notification}] notification was not sent."
     92▕         );
     93▕     }

      +2 vendor frames
  3   tests/Feature/Auth/PasswordResetTest.php:22

  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Auth\PasswordResetTest > reset password screen can…
  The expected [Illuminate\Auth\Notifications\ResetPassword] notification was not sent.
Failed asserting that false is true.

  at vendor/laravel/framework/src/Illuminate/Support/Testing/Fakes/NotificationFake.php:89
     85▕         if (is_numeric($callback)) {
     86▕             return $this->assertSentToTimes($notifiable, $notification, $callback);
     87▕         }
     88▕
  ➜  89▕         PHPUnit::assertTrue(
     90▕             $this->sent($notifiable, $notification, $callback)->count() > 0,
     91▕             "The expected [{$notification}] notification was not sent."
     92▕         );
     93▕     }

      +2 vendor frames
  3   tests/Feature/Auth/PasswordResetTest.php:32

  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Auth\PasswordResetTest > password can be reset with…
  The expected [Illuminate\Auth\Notifications\ResetPassword] notification was not sent.
Failed asserting that false is true.

  at vendor/laravel/framework/src/Illuminate/Support/Testing/Fakes/NotificationFake.php:89
     85▕         if (is_numeric($callback)) {
     86▕             return $this->assertSentToTimes($notifiable, $notification, $callback);
     87▕         }
     88▕
  ➜  89▕         PHPUnit::assertTrue(
     90▕             $this->sent($notifiable, $notification, $callback)->count() > 0,
     91▕             "The expected [{$notification}] notification was not sent."
     92▕         );
     93▕     }

      +2 vendor frames
  3   tests/Feature/Auth/PasswordResetTest.php:47

  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Auth\RegistrationTest > registration screen can be…
  Expected response status code [200] but received 404.
Failed asserting that 404 is identical to 200.

  at tests/Feature/Auth/RegistrationTest.php:8
      4▕
      5▕ test('registration screen can be rendered', function () {
      6▕     $response = $this->get(route('register'));
      7▕
  ➜   8▕     $response->assertOk();
      9▕ });
     10▕
     11▕ test('new users can register', function () {
     12▕     $response = $this->post(route('register.store'), [

  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Auth\RegistrationTest > new users can register
  Expected response status code [201, 301, 302, 303, 307, 308] but received 404.
Failed asserting that false is true.

  at tests/Feature/Auth/RegistrationTest.php:20
     16▕         'password_confirmation' => 'password',
     17▕     ]);
     18▕
     19▕     $response->assertSessionHasNoErrors()
  ➜  20▕         ->assertRedirect(route('dashboard', absolute: false));
     21▕
     22▕     $this->assertAuthenticated();
     23▕ });

  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Auth\TwoFactorChallengeTest > two factor challenge…
  Expected response status code [201, 301, 302, 303, 307, 308] but received 404.
Failed asserting that false is true.

  at tests/Feature/Auth/TwoFactorChallengeTest.php:15
     11▕     }
     12▕
     13▕     $response = $this->get(route('two-factor.login'));
     14▕
  ➜  15▕     $response->assertRedirect(route('login'));
     16▕ });
     17▕
     18▕ test('two factor challenge can be rendered', function () {
     19▕     if (! Features::canManageTwoFactorAuthentication()) {

  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Auth\TwoFactorChallengeTest > two factor challenge…
  Expected response status code [201, 301, 302, 303, 307, 308] but received 404.
Failed asserting that false is true.

  at tests/Feature/Auth/TwoFactorChallengeTest.php:33
     29▕
     30▕     $this->post(route('login.store'), [
     31▕         'email' => $user->email,
     32▕         'password' => 'password',
  ➜  33▕     ])->assertRedirect(route('two-factor.login'));
     34▕ });

  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\DashboardTest > guests are redirected to the login…
  Expected response status code [201, 301, 302, 303, 307, 308] but received 404.
Failed asserting that false is true.

  at tests/Feature/DashboardTest.php:9
      5▕ uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);
      6▕
      7▕ test('guests are redirected to the login page', function () {
      8▕     $response = $this->get(route('dashboard'));
  ➜   9▕     $response->assertRedirect(route('login'));
     10▕ });
     11▕
     12▕ test('authenticated users can visit the dashboard', function () {
     13▕     $user = User::factory()->create();

  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\DashboardTest > authenticated users can visit the d…
  Expected response status code [200] but received 404.
Failed asserting that 404 is identical to 200.

  at tests/Feature/DashboardTest.php:17
     13▕     $user = User::factory()->create();
     14▕     $this->actingAs($user);
     15▕
     16▕     $response = $this->get(route('dashboard'));
  ➜  17▕     $response->assertOk();
     18▕ });

  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Settings\ProfileUpdateTest > profile page is displa…
  Expected response status code [200] but received 404.
Failed asserting that 404 is identical to 200.

  at tests/Feature/Settings/ProfileUpdateTest.php:12
      8▕
      9▕ test('profile page is displayed', function () {
     10▕     $this->actingAs($user = User::factory()->create());
     11▕
  ➜  12▕     $this->get('/settings/profile')->assertOk();
     13▕ });
     14▕
     15▕ test('profile information can be updated', function () {
     16▕     $user = User::factory()->create();

  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Settings\TwoFactorAuthenticationTest > two factor s…
  Expected response status code [200] but received 404.
Failed asserting that 404 is identical to 200.

  at tests/Feature/Settings/TwoFactorAuthenticationTest.php:26
     22▕
     23▕     $this->actingAs($user)
     24▕         ->withSession(['auth.password_confirmed_at' => time()])
     25▕         ->get(route('two-factor.show'))
  ➜  26▕         ->assertOk()
     27▕         ->assertSee('Two Factor Authentication')
     28▕         ->assertSee('Disabled');
     29▕ });
     30▕

  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Settings\TwoFactorAuthenticationTest > two factor s…
  Expected response status code [201, 301, 302, 303, 307, 308] but received 404.
Failed asserting that false is true.

  at tests/Feature/Settings/TwoFactorAuthenticationTest.php:37
     33▕
     34▕     $response = $this->actingAs($user)
     35▕         ->get(route('two-factor.show'));
     36▕
  ➜  37▕     $response->assertRedirect(route('password.confirm'));
     38▕ });
     39▕
     40▕ test('two factor settings page returns forbidden response when two factor is disabled', function () {
     41▕     config(['fortify.features' => []]);

  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Settings\TwoFactorAuthenticationTest > two factor s…
  Expected response status code [403] but received 404.
Failed asserting that 404 is identical to 403.

  at tests/Feature/Settings/TwoFactorAuthenticationTest.php:49
     45▕     $response = $this->actingAs($user)
     46▕         ->withSession(['auth.password_confirmed_at' => time()])
     47▕         ->get(route('two-factor.show'));
     48▕
  ➜  49▕     $response->assertForbidden();
     50▕ });
     51▕
     52▕ test('two factor authentication disabled when confirmation abandoned between requests', function () {
     53▕     $user = User::factory()->create();

  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Tenancy\ResolveStoreTest > admin requests resolve o…
  Expected response status code [>=200, <300] but received 500.
Failed asserting that false is true.

The following exception occurred during the last request:

RuntimeException: Session store not set on request. in /Users/wesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Http/Request.php:610
Stack trace:
#0 /Users/wesner/Herd/shop/app/Http/Middleware/ResolveStore.php(74): Illuminate\Http\Request->session()
#1 /Users/wesner/Herd/shop/app/Http/Middleware/ResolveStore.php(33): App\Http\Middleware\ResolveStore->resolveAdminStore(Object(Illuminate\Http\Request))
#2 /Users/wesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): App\Http\Middleware\ResolveStore->handle(Object(Illuminate\Http\Request), Object(Closure))
#3 /Users/wesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\Pipeline\Pipeline->{closure:{closure:Illuminate\Pipeline\Pipeline::carry():194}:195}(Object(Illuminate\Http\Request))
#4 /Users/wesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\Pipeline\Pipeline->then(Object(Closure))
#5 /Users/wesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\Foundation\Http\Kernel->sendRequestThroughRouter(Object(Illuminate\Http\Request))
#6 /Users/wesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php(607): Illuminate\Foundation\Http\Kernel->handle(Object(Illuminate\Http\Request))
#7 /Users/wesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php(368): Illuminate\Foundation\Testing\TestCase->call('GET', '/admin/tenant-r...', Array, Array, Array, Array)
#8 /Users/wesner/Herd/shop/tests/Feature/Tenancy/ResolveStoreTest.php(90): Illuminate\Foundation\Testing\TestCase->get('/admin/tenant-r...')
#9 /Users/wesner/Herd/shop/vendor/pestphp/pest/src/Factories/TestCaseMethodFactory.php(168): P\Tests\Feature\Tenancy\ResolveStoreTest->{closure:/Users/wesner/Herd/shop/tests/Feature/Tenancy/ResolveStoreTest.php:82}()
#10 [internal function]: P\Tests\Feature\Tenancy\ResolveStoreTest->{closure:Pest\Factories\TestCaseMethodFactory::getClosure():158}()
#11 /Users/wesner/Herd/shop/vendor/pestphp/pest/src/Concerns/Testable.php(429): call_user_func_array(Object(Closure), Array)
#12 /Users/wesner/Herd/shop/vendor/pestphp/pest/src/Support/ExceptionTrace.php(26): P\Tests\Feature\Tenancy\ResolveStoreTest->{closure:Pest\Concerns\Testable::__callClosure():429}()
#13 /Users/wesner/Herd/shop/vendor/pestphp/pest/src/Concerns/Testable.php(429): Pest\Support\ExceptionTrace::ensure(Object(Closure))
#14 /Users/wesner/Herd/shop/vendor/pestphp/pest/src/Concerns/Testable.php(331): P\Tests\Feature\Tenancy\ResolveStoreTest->__callClosure(Object(Closure), Array)
#15 /Users/wesner/Herd/shop/vendor/pestphp/pest/src/Factories/TestCaseFactory.php(169) : eval()'d code(53): P\Tests\Feature\Tenancy\ResolveStoreTest->__runTest(Object(Closure))
#16 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestCase.php(1332): P\Tests\Feature\Tenancy\ResolveStoreTest->__pest_evaluable_admin_requests_resolve_only_the_session_store_the_user_belongs_to()
#17 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestCase.php(519): PHPUnit\Framework\TestCase->runTest()
#18 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestRunner/TestRunner.php(99): PHPUnit\Framework\TestCase->runBare()
#19 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestCase.php(359): PHPUnit\Framework\TestRunner->run(Object(P\Tests\Feature\Tenancy\ResolveStoreTest))
#20 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestSuite.php(374): PHPUnit\Framework\TestCase->run()
#21 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestSuite.php(374): PHPUnit\Framework\TestSuite->run()
#22 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestSuite.php(374): PHPUnit\Framework\TestSuite->run()
#23 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/TextUI/TestRunner.php(64): PHPUnit\Framework\TestSuite->run()
#24 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/TextUI/Application.php(229): PHPUnit\TextUI\TestRunner->run(Object(PHPUnit\TextUI\Configuration\Configuration), Object(PHPUnit\Runner\ResultCache\DefaultResultCache), Object(PHPUnit\Framework\TestSuite))
#25 /Users/wesner/Herd/shop/vendor/pestphp/pest/src/Kernel.php(103): PHPUnit\TextUI\Application->run(Array)
#26 /Users/wesner/Herd/shop/vendor/pestphp/pest/bin/pest(184): Pest\Kernel->handle(Array, Array)
#27 /Users/wesner/Herd/shop/vendor/pestphp/pest/bin/pest(192): {closure:/Users/wesner/Herd/shop/vendor/pestphp/pest/bin/pest:18}()
#28 {main}

----------------------------------------------------------------------------------

Session store not set on request.

  at tests/Feature/Tenancy/ResolveStoreTest.php:91
     87▕
     88▕     $this->actingAs($user)
     89▕         ->withSession(['current_store_id' => $store->getKey()])
     90▕         ->get('/admin/tenant-resolution-test')
  ➜  91▕         ->assertSuccessful()
     92▕         ->assertJson(['store_id' => $store->getKey()]);
     93▕ });
     94▕
     95▕ test('admin requests reject a session store without membership', function () {

  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Tenancy\ResolveStoreTest > admin requests reject a…
  Expected response status code [403] but received 500.
Failed asserting that 500 is identical to 403.

The following exception occurred during the last request:

RuntimeException: Session store not set on request. in /Users/wesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Http/Request.php:610
Stack trace:
#0 /Users/wesner/Herd/shop/app/Http/Middleware/ResolveStore.php(74): Illuminate\Http\Request->session()
#1 /Users/wesner/Herd/shop/app/Http/Middleware/ResolveStore.php(33): App\Http\Middleware\ResolveStore->resolveAdminStore(Object(Illuminate\Http\Request))
#2 /Users/wesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): App\Http\Middleware\ResolveStore->handle(Object(Illuminate\Http\Request), Object(Closure))
#3 /Users/wesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\Pipeline\Pipeline->{closure:{closure:Illuminate\Pipeline\Pipeline::carry():194}:195}(Object(Illuminate\Http\Request))
#4 /Users/wesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\Pipeline\Pipeline->then(Object(Closure))
#5 /Users/wesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\Foundation\Http\Kernel->sendRequestThroughRouter(Object(Illuminate\Http\Request))
#6 /Users/wesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php(607): Illuminate\Foundation\Http\Kernel->handle(Object(Illuminate\Http\Request))
#7 /Users/wesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php(368): Illuminate\Foundation\Testing\TestCase->call('GET', '/admin/tenant-r...', Array, Array, Array, Array)
#8 /Users/wesner/Herd/shop/tests/Feature/Tenancy/ResolveStoreTest.php(101): Illuminate\Foundation\Testing\TestCase->get('/admin/tenant-r...')
#9 /Users/wesner/Herd/shop/vendor/pestphp/pest/src/Factories/TestCaseMethodFactory.php(168): P\Tests\Feature\Tenancy\ResolveStoreTest->{closure:/Users/wesner/Herd/shop/tests/Feature/Tenancy/ResolveStoreTest.php:95}()
#10 [internal function]: P\Tests\Feature\Tenancy\ResolveStoreTest->{closure:Pest\Factories\TestCaseMethodFactory::getClosure():158}()
#11 /Users/wesner/Herd/shop/vendor/pestphp/pest/src/Concerns/Testable.php(429): call_user_func_array(Object(Closure), Array)
#12 /Users/wesner/Herd/shop/vendor/pestphp/pest/src/Support/ExceptionTrace.php(26): P\Tests\Feature\Tenancy\ResolveStoreTest->{closure:Pest\Concerns\Testable::__callClosure():429}()
#13 /Users/wesner/Herd/shop/vendor/pestphp/pest/src/Concerns/Testable.php(429): Pest\Support\ExceptionTrace::ensure(Object(Closure))
#14 /Users/wesner/Herd/shop/vendor/pestphp/pest/src/Concerns/Testable.php(331): P\Tests\Feature\Tenancy\ResolveStoreTest->__callClosure(Object(Closure), Array)
#15 /Users/wesner/Herd/shop/vendor/pestphp/pest/src/Factories/TestCaseFactory.php(169) : eval()'d code(62): P\Tests\Feature\Tenancy\ResolveStoreTest->__runTest(Object(Closure))
#16 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestCase.php(1332): P\Tests\Feature\Tenancy\ResolveStoreTest->__pest_evaluable_admin_requests_reject_a_session_store_without_membership()
#17 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestCase.php(519): PHPUnit\Framework\TestCase->runTest()
#18 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestRunner/TestRunner.php(99): PHPUnit\Framework\TestCase->runBare()
#19 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestCase.php(359): PHPUnit\Framework\TestRunner->run(Object(P\Tests\Feature\Tenancy\ResolveStoreTest))
#20 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestSuite.php(374): PHPUnit\Framework\TestCase->run()
#21 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestSuite.php(374): PHPUnit\Framework\TestSuite->run()
#22 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestSuite.php(374): PHPUnit\Framework\TestSuite->run()
#23 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/TextUI/TestRunner.php(64): PHPUnit\Framework\TestSuite->run()
#24 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/TextUI/Application.php(229): PHPUnit\TextUI\TestRunner->run(Object(PHPUnit\TextUI\Configuration\Configuration), Object(PHPUnit\Runner\ResultCache\DefaultResultCache), Object(PHPUnit\Framework\TestSuite))
#25 /Users/wesner/Herd/shop/vendor/pestphp/pest/src/Kernel.php(103): PHPUnit\TextUI\Application->run(Array)
#26 /Users/wesner/Herd/shop/vendor/pestphp/pest/bin/pest(184): Pest\Kernel->handle(Array, Array)
#27 /Users/wesner/Herd/shop/vendor/pestphp/pest/bin/pest(192): {closure:/Users/wesner/Herd/shop/vendor/pestphp/pest/bin/pest:18}()
#28 {main}

----------------------------------------------------------------------------------

Session store not set on request.

  at tests/Feature/Tenancy/ResolveStoreTest.php:102
     98▕
     99▕     $this->actingAs($user)
    100▕         ->withSession(['current_store_id' => $store->getKey()])
    101▕         ->get('/admin/tenant-resolution-test')
  ➜ 102▕         ->assertForbidden();
    103▕ });
    104▕

  Tests:    25 failed, 30 passed (116 assertions)
  Duration: 0.88s


text:
Script completed
Wall time 1.1 seconds
Output:

text: {"chunk_id":"c978f1","wall_time_seconds":0.911793917,"exit_code":1,"original_token_count":8898,"output":"\n   PASS  Tests\\Unit\\ExampleTest\n  ✓ that true is true\n\n   PASS  Tests\\Unit\\Policies\\StorePolicyTest\n  ✓ product permissions are based on the users role in the current stor… 0.11s  \n  ✓ store deletion is restricted to owners                               0.01s  \n\n   PASS  Tests\\Unit\\PricingEngineTest\n  ✓ tax calculations use integer minor-unit arithmetic\n  ✓ discount calculations cap fixed discounts and allocate percentages\n  ✓ free shipping discounts do not reduce merchandise totals\n  ✓ mock payment provider supports the documented magic cards and deferred transfers\n\n   PASS  Tests\\Unit\\Tenancy\\BelongsToStoreTest\n  ✓ tenant models are created and queried for the current store only     0.01s  \n  ✓ tenant queries return no rows when no current store is resolved\n\n   FAIL  Tests\\Feature\\Auth\\AuthenticationTest\n  ⨯ login screen can be rendered                                         0.01s  \n  ⨯ users can authenticate using the login screen                        0.01s  \n  ⨯ users can not authenticate with invalid password                     0.01s  \n  ⨯ users with two factor enabled are redirected to two factor challeng… 0.01s  \n  ⨯ users can logout                                                     0.01s  \n\n   FAIL  Tests\\Feature\\Auth\\EmailVerificationTest\n  ⨯ email verification screen can be rendered                            0.01s  \n  ⨯ email can be verified                                                0.01s  \n  ✓ email is not verified with invalid hash\n  ⨯ already verified user visiting verification link is redirected with… 0.01s  \n\n   FAIL  Tests\\Feature\\Auth\\PasswordConfirmationTest\n  ⨯ confirm password screen can be rendered                              0.01s  \n\n   FAIL  Tests\\Feature\\Auth\\PasswordResetTest\n  ⨯ reset password link screen can be rendered\n  ⨯ reset password link can be requested                                 0.01s  \n  ⨯ reset password screen can be rendered\n  ⨯ password can be reset with valid token                               0.01s  \n\n   FAIL  Tests\\Feature\\Auth\\RegistrationTest\n  ⨯ registration screen can be rendered                                  0.01s  \n  ⨯ new users can register\n\n   FAIL  Tests\\Feature\\Auth\\TwoFactorChallengeTest\n  ⨯ two factor challenge redirects to login when not authenticated       0.01s  \n  ⨯ two factor challenge can be rendered                                 0.01s  \n\n   PASS  Tests\\Feature\\CommerceFlowTest\n  ✓ guest cart and card checkout create a paid order                     0.05s  \n  ✓ declined payments release the reservation and do not create an orde… 0.04s  \n  ✓ stale cart versions return a conflict response                       0.03s  \n  ✓ bank transfer keeps inventory reserved until admin confirmation      0.04s  \n\n   FAIL  Tests\\Feature\\DashboardTest\n  ⨯ guests are redirected to the login page                              0.01s  \n  ⨯ authenticated users can visit the dashboard                          0.01s  \n\n   PASS  Tests\\Feature\\ExampleTest\n  ✓ it returns a successful response                                     0.01s  \n\n   PASS  Tests\\Feature\\SearchAnalyticsWebhookTest\n  ✓ search is tenant scoped, paginated, and logs the query               0.03s  \n  ✓ product changes are synchronized to the FTS index and autocomplete   0.03s  \n  ✓ analytics events aggregate idempotently into daily metrics           0.03s  \n  ✓ webhooks are signed and delivered with platform headers              0.03s  \n\n   PASS  Tests\\Feature\\Settings\\PasswordUpdateTest\n  ✓ password can be updated                                              0.05s  \n  ✓ correct password must be provided to update password                 0.03s  \n\n   FAIL  Tests\\Feature\\Settings\\ProfileUpdateTest\n  ⨯ profile page is displayed                                            0.01s  \n  ✓ profile information can be updated                                   0.03s  \n  ✓ email verification status is unchanged when email address is unchan… 0.03s  \n  ✓ user can delete their account                                        0.01s  \n  ✓ correct password must be provided to delete account                  0.02s  \n\n   FAIL  Tests\\Feature\\Settings\\TwoFactorAuthenticationTest\n  ⨯ two factor settings page can be rendered                             0.01s  \n  ⨯ two factor settings page requires password confirmation when enable… 0.01s  \n  ⨯ two factor settings page returns forbidden response when two factor… 0.01s  \n  ✓ two factor authentication disabled when confirmation abandoned betw… 0.02s  \n\n   FAIL  Tests\\Feature\\Tenancy\\ResolveStoreTest\n  ✓ a storefront request resolves and shares the store by hostname       0.01s  \n  ✓ a storefront hostname is cached as a store id                        0.01s  \n  ✓ unknown storefront hosts return not found\n  ✓ suspended storefronts return service unavailable                     0.01s  \n  ⨯ admin requests resolve only the session store the user belongs to    0.05s  \n  ⨯ admin requests reject a session store without membership             0.04s  \n  ────────────────────────────────────────────────────────────────────────────  \n   FAILED  Tests\\Feature\\Auth\\AuthenticationTest > login screen can be rende…   \n  Expected response status code [200] but received 404.\nFailed asserting that 404 is identical to 200.\n\n  at tests/Feature/Auth/AuthenticationTest.php:11\n      7▕ \n      8▕ test('login screen can be rendered', function () {\n      9▕     $response = $this->get(route('login'));\n     10▕ \n  ➜  11▕     $response->assertOk();\n     12▕ });\n     13▕ \n     14▕ test('users can authenticate using the login screen', function () {\n     15▕     $user = User::factory()->create();\n\n  ────────────────────────────────────────────────────────────────────────────  \n   FAILED  Tests\\Feature\\Auth\\AuthenticationTest > users can authenticate us…   \n  Expected response status code [201, 301, 302, 303, 307, 308] but received 404.\nFailed asserting that false is true.\n\n  at tests/Feature/Auth/AuthenticationTest.php:24\n     20▕     ]);\n     21▕ \n     22▕     $response\n     23▕         ->assertSessionHasNoErrors()\n  ➜  24▕         ->assertRedirect(route('dashboard', absolute: false));\n     25▕ \n     26▕     $this->assertAuthenticated();\n     27▕ });\n     28▕\n\n  ────────────────────────────────────────────────────────────────────────────  \n   FAILED  Tests\\Feature\\Auth\\AuthenticationTest > users can not authenticat…   \n  Session is missing expected key [errors].\nFailed asserting that false is true.\n\n  at tests/Feature/Auth/AuthenticationTest.php:37\n     33▕         'email' => $user->email,\n     34▕         'password' => 'wrong-password',\n     35▕     ]);\n     36▕ \n  ➜  37▕     $response->assertSessionHasErrorsIn('email');\n     38▕ \n     39▕     $this->assertGuest();\n     40▕ });\n     41▕\n\n  ────────────────────────────────────────────────────────────────────────────  \n   FAILED  Tests\\Feature\\Auth\\AuthenticationTest > users with two factor ena…   \n  Expected response status code [201, 301, 302, 303, 307, 308] but received 404.\nFailed asserting that false is true.\n\n  at tests/Feature/Auth/AuthenticationTest.php:58\n     54▕         'email' => $user->email,\n     55▕         'password' => 'password',\n     56▕     ]);\n     57▕ \n  ➜  58▕     $response->assertRedirect(route('two-factor.login'));\n     59▕     $this->assertGuest();\n     60▕ });\n     61▕ \n     62▕ test('users can logout', function () {\n\n  ────────────────────────────────────────────────────────────────────────────  \n   FAILED  Tests\\Feature\\Auth\\AuthenticationTest > users can logout             \n  Expected response status code [201, 301, 302, 303, 307, 308] but received 404.\nFailed asserting that false is true.\n\n  at tests/Feature/Auth/AuthenticationTest.php:67\n     63▕     $user = User::factory()->create();\n     64▕ \n     65▕     $response = $this->actingAs($user)->post(route('logout'));\n     66▕ \n  ➜  67▕     $response->assertRedirect(route('home'));\n     68▕     $this->assertGuest();\n     69▕ });\n\n  ────────────────────────────────────────────────────────────────────────────  \n   FAILED  Tests\\Feature\\Auth\\EmailVerificationTest > email verification scr…   \n  Expected response status code [200] but received 404.\nFailed asserting that 404 is identical to 200.\n\n  at tests/Feature/Auth/EmailVerificationTest.php:15\n     11▕     $user = User::factory()->unverified()->create();\n     12▕ \n     13▕     $response = $this->actingAs($user)->get(route('verification.notice'));\n     14▕ \n  ➜  15▕     $response->assertOk();\n     16▕ });\n     17▕ \n     18▕ test('email can be verified', function () {\n     19▕     $user = User::factory()->unverified()->create();\n\n  ────────────────────────────────────────────────────────────────────────────  \n   FAILED  Tests\\Feature\\Auth\\EmailVerificationTest > email can be verified     \n  The expected [Illuminate\\Auth\\Events\\Verified] event was not dispatched.\nFailed asserting that false is true.\n\n  at vendor/laravel/framework/src/Illuminate/Support/Testing/Fakes/EventFake.php:144\n    140▕         if (is_int($callback)) {\n    141▕             return $this->assertDispatchedTimes($event, $callback);\n    142▕         }\n    143▕ \n  ➜ 144▕         PHPUnit::assertTrue(\n    145▕             $this->dispatched($event, $callback)->count() > 0,\n    146▕             \"The expected [{$event}] event was not dispatched.\"\n    147▕         );\n    148▕     }\n\n      \u001b[2m+2 vendor frames \u001b[22m\n  3   tests/Feature/Auth/EmailVerificationTest.php:31\n\n  ────────────────────────────────────────────────────────────────────────────  \n   FAILED  Tests\\Feature\\Auth\\EmailVerificationTest > already verified user…    \n  Expected response status code [201, 301, 302, 303, 307, 308] but received 404.\nFailed asserting that false is true.\n\n  at tests/Feature/Auth/EmailVerificationTest.php:65\n     61▕         ['id' => $user->id, 'hash' => sha1($user->email)]\n     62▕     );\n     63▕ \n     64▕     $this->actingAs($user)->get($verificationUrl)\n  ➜  65▕         ->assertRedirect(route('dashboard', absolute: false).'?verified=1');\n     66▕ \n     67▕     expect($user->fresh()->hasVerifiedEmail())->toBeTrue();\n     68▕     Event::assertNotDispatched(Verified::class);\n     69▕ });\n\n  ────────────────────────────────────────────────────────────────────────────  \n   FAILED  Tests\\Feature\\Auth\\PasswordConfirmationTest > confirm password sc…   \n  Expected response status code [200] but received 404.\nFailed asserting that 404 is identical to 200.\n\n  at tests/Feature/Auth/PasswordConfirmationTest.php:12\n      8▕     $user = User::factory()->create();\n      9▕ \n     10▕     $response = $this->actingAs($user)->get(route('password.confirm'));\n     11▕ \n  ➜  12▕     $response->assertOk();\n     13▕ });\n\n  ────────────────────────────────────────────────────────────────────────────  \n   FAILED  Tests\\Feature\\Auth\\PasswordResetTest > reset password link screen…   \n  Expected response status code [200] but received 404.\nFailed asserting that 404 is identical to 200.\n\n  at tests/Feature/Auth/PasswordResetTest.php:12\n      8▕ \n      9▕ test('reset password link screen can be rendered', function () {\n     10▕     $response = $this->get(route('password.request'));\n     11▕ \n  ➜  12▕     $response->assertOk();\n     13▕ });\n     14▕ \n     15▕ test('reset password link can be requested', function () {\n     16▕     Notification::fake();\n\n  ────────────────────────────────────────────────────────────────────────────  \n   FAILED  Tests\\Feature\\Auth\\PasswordResetTest > reset password link can be…   \n  The expected [Illuminate\\Auth\\Notifications\\ResetPassword] notification was not sent.\nFailed asserting that false is true.\n\n  at vendor/laravel/framework/src/Illuminate/Support/Testing/Fakes/NotificationFake.php:89\n     85▕         if (is_numeric($callback)) {\n     86▕             return $this->assertSentToTimes($notifiable, $notification, $callback);\n     87▕         }\n     88▕ \n  ➜  89▕         PHPUnit::assertTrue(\n     90▕             $this->sent($notifiable, $notification, $callback)->count() > 0,\n     91▕             \"The expected [{$notification}] notification was not sent.\"\n     92▕         );\n     93▕     }\n\n      \u001b[2m+2 vendor frames \u001b[22m\n  3   tests/Feature/Auth/PasswordResetTest.php:22\n\n  ────────────────────────────────────────────────────────────────────────────  \n   FAILED  Tests\\Feature\\Auth\\PasswordResetTest > reset password screen can…    \n  The expected [Illuminate\\Auth\\Notifications\\ResetPassword] notification was not sent.\nFailed asserting that false is true.\n\n  at vendor/laravel/framework/src/Illuminate/Support/Testing/Fakes/NotificationFake.php:89\n     85▕         if (is_numeric($callback)) {\n     86▕             return $this->assertSentToTimes($notifiable, $notification, $callback);\n     87▕         }\n     88▕ \n  ➜  89▕         PHPUnit::assertTrue(\n     90▕             $this->sent($notifiable, $notification, $callback)->count() > 0,\n     91▕             \"The expected [{$notification}] notification was not sent.\"\n     92▕         );\n     93▕     }\n\n      \u001b[2m+2 vendor frames \u001b[22m\n  3   tests/Feature/Auth/PasswordResetTest.php:32\n\n  ────────────────────────────────────────────────────────────────────────────  \n   FAILED  Tests\\Feature\\Auth\\PasswordResetTest > password can be reset with…   \n  The expected [Illuminate\\Auth\\Notifications\\ResetPassword] notification was not sent.\nFailed asserting that false is true.\n\n  at vendor/laravel/framework/src/Illuminate/Support/Testing/Fakes/NotificationFake.php:89\n     85▕         if (is_numeric($callback)) {\n     86▕             return $this->assertSentToTimes($notifiable, $notification, $callback);\n     87▕         }\n     88▕ \n  ➜  89▕         PHPUnit::assertTrue(\n     90▕             $this->sent($notifiable, $notification, $callback)->count() > 0,\n     91▕             \"The expected [{$notification}] notification was not sent.\"\n     92▕         );\n     93▕     }\n\n      \u001b[2m+2 vendor frames \u001b[22m\n  3   tests/Feature/Auth/PasswordResetTest.php:47\n\n  ────────────────────────────────────────────────────────────────────────────  \n   FAILED  Tests\\Feature\\Auth\\RegistrationTest > registration screen can be…    \n  Expected response status code [200] but received 404.\nFailed asserting that 404 is identical to 200.\n\n  at tests/Feature/Auth/RegistrationTest.php:8\n      4▕ \n      5▕ test('registration screen can be rendered', function () {\n      6▕     $response = $this->get(route('register'));\n      7▕ \n  ➜   8▕     $response->assertOk();\n      9▕ });\n     10▕ \n     11▕ test('new users can register', function () {\n     12▕     $response = $this->post(route('register.store'), [\n\n  ────────────────────────────────────────────────────────────────────────────  \n   FAILED  Tests\\Feature\\Auth\\RegistrationTest > new users can register         \n  Expected response status code [201, 301, 302, 303, 307, 308] but received 404.\nFailed asserting that false is true.\n\n  at tests/Feature/Auth/RegistrationTest.php:20\n     16▕         'password_confirmation' => 'password',\n     17▕     ]);\n     18▕ \n     19▕     $response->assertSessionHasNoErrors()\n  ➜  20▕         ->assertRedirect(route('dashboard', absolute: false));\n     21▕ \n     22▕     $this->assertAuthenticated();\n     23▕ });\n\n  ────────────────────────────────────────────────────────────────────────────  \n   FAILED  Tests\\Feature\\Auth\\TwoFactorChallengeTest > two factor challenge…    \n  Expected response status code [201, 301, 302, 303, 307, 308] but received 404.\nFailed asserting that false is true.\n\n  at tests/Feature/Auth/TwoFactorChallengeTest.php:15\n     11▕     }\n     12▕ \n     13▕     $response = $this->get(route('two-factor.login'));\n     14▕ \n  ➜  15▕     $response->assertRedirect(route('login'));\n     16▕ });\n     17▕ \n     18▕ test('two factor challenge can be rendered', function () {\n     19▕     if (! Features::canManageTwoFactorAuthentication()) {\n\n  ────────────────────────────────────────────────────────────────────────────  \n   FAILED  Tests\\Feature\\Auth\\TwoFactorChallengeTest > two factor challenge…    \n  Expected response status code [201, 301, 302, 303, 307, 308] but received 404.\nFailed asserting that false is true.\n\n  at tests/Feature/Auth/TwoFactorChallengeTest.php:33\n     29▕ \n     30▕     $this->post(route('login.store'), [\n     31▕         'email' => $user->email,\n     32▕         'password' => 'password',\n  ➜  33▕     ])->assertRedirect(route('two-factor.login'));\n     34▕ });\n\n  ────────────────────────────────────────────────────────────────────────────  \n   FAILED  Tests\\Feature\\DashboardTest > guests are redirected to the login…    \n  Expected response status code [201, 301, 302, 303, 307, 308] but received 404.\nFailed asserting that false is true.\n\n  at tests/Feature/DashboardTest.php:9\n      5▕ uses(\\Illuminate\\Foundation\\Testing\\RefreshDatabase::class);\n      6▕ \n      7▕ test('guests are redirected to the login page', function () {\n      8▕     $response = $this->get(route('dashboard'));\n  ➜   9▕     $response->assertRedirect(route('login'));\n     10▕ });\n     11▕ \n     12▕ test('authenticated users can visit the dashboard', function () {\n     13▕     $user = User::factory()->create();\n\n  ────────────────────────────────────────────────────────────────────────────  \n   FAILED  Tests\\Feature\\DashboardTest > authenticated users can visit the d…   \n  Expected response status code [200] but received 404.\nFailed asserting that 404 is identical to 200.\n\n  at tests/Feature/DashboardTest.php:17\n     13▕     $user = User::factory()->create();\n     14▕     $this->actingAs($user);\n     15▕ \n     16▕     $response = $this->get(route('dashboard'));\n  ➜  17▕     $response->assertOk();\n     18▕ });\n\n  ────────────────────────────────────────────────────────────────────────────  \n   FAILED  Tests\\Feature\\Settings\\ProfileUpdateTest > profile page is displa…   \n  Expected response status code [200] but received 404.\nFailed asserting that 404 is identical to 200.\n\n  at tests/Feature/Settings/ProfileUpdateTest.php:12\n      8▕ \n      9▕ test('profile page is displayed', function () {\n     10▕     $this->actingAs($user = User::factory()->create());\n     11▕ \n  ➜  12▕     $this->get('/settings/profile')->assertOk();\n     13▕ });\n     14▕ \n     15▕ test('profile information can be updated', function () {\n     16▕     $user = User::factory()->create();\n\n  ────────────────────────────────────────────────────────────────────────────  \n   FAILED  Tests\\Feature\\Settings\\TwoFactorAuthenticationTest > two factor s…   \n  Expected response status code [200] but received 404.\nFailed asserting that 404 is identical to 200.\n\n  at tests/Feature/Settings/TwoFactorAuthenticationTest.php:26\n     22▕ \n     23▕     $this->actingAs($user)\n     24▕         ->withSession(['auth.password_confirmed_at' => time()])\n     25▕         ->get(route('two-factor.show'))\n  ➜  26▕         ->assertOk()\n     27▕         ->assertSee('Two Factor Authentication')\n     28▕         ->assertSee('Disabled');\n     29▕ });\n     30▕\n\n  ────────────────────────────────────────────────────────────────────────────  \n   FAILED  Tests\\Feature\\Settings\\TwoFactorAuthenticationTest > two factor s…   \n  Expected response status code [201, 301, 302, 303, 307, 308] but received 404.\nFailed asserting that false is true.\n\n  at tests/Feature/Settings/TwoFactorAuthenticationTest.php:37\n     33▕ \n     34▕     $response = $this->actingAs($user)\n     35▕         ->get(route('two-factor.show'));\n     36▕ \n  ➜  37▕     $response->assertRedirect(route('password.confirm'));\n     38▕ });\n     39▕ \n     40▕ test('two factor settings page returns forbidden response when two factor is disabled', function () {\n     41▕     config(['fortify.features' => []]);\n\n  ────────────────────────────────────────────────────────────────────────────  \n   FAILED  Tests\\Feature\\Settings\\TwoFactorAuthenticationTest > two factor s…   \n  Expected response status code [403] but received 404.\nFailed asserting that 404 is identical to 403.\n\n  at tests/Feature/Settings/TwoFactorAuthenticationTest.php:49\n     45▕     $response = $this->actingAs($user)\n     46▕         ->withSession(['auth.password_confirmed_at' => time()])\n     47▕         ->get(route('two-factor.show'));\n     48▕ \n  ➜  49▕     $response->assertForbidden();\n     50▕ });\n     51▕ \n     52▕ test('two factor authentication disabled when confirmation abandoned between requests', function () {\n     53▕     $user = User::factory()->create();\n\n  ────────────────────────────────────────────────────────────────────────────  \n   FAILED  Tests\\Feature\\Tenancy\\ResolveStoreTest > admin requests resolve o…   \n  Expected response status code [>=200, <300] but received 500.\nFailed asserting that false is true.\n\nThe following exception occurred during the last request:\n\nRuntimeException: Session store not set on request. in /Users/wesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Http/Request.php:610\nStack trace:\n#0 /Users/wesner/Herd/shop/app/Http/Middleware/ResolveStore.php(74): Illuminate\\Http\\Request->session()\n#1 /Users/wesner/Herd/shop/app/Http/Middleware/ResolveStore.php(33): App\\Http\\Middleware\\ResolveStore->resolveAdminStore(Object(Illuminate\\Http\\Request))\n#2 /Users/wesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): App\\Http\\Middleware\\ResolveStore->handle(Object(Illuminate\\Http\\Request), Object(Closure))\n#3 /Users/wesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}(Object(Illuminate\\Http\\Request))\n#4 /Users/wesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))\n#5 /Users/wesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))\n#6 /Users/wesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php(607): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))\n#7 /Users/wesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php(368): Illuminate\\Foundation\\Testing\\TestCase->call('GET', '/admin/tenant-r...', Array, Array, Array, Array)\n#8 /Users/wesner/Herd/shop/tests/Feature/Tenancy/ResolveStoreTest.php(90): Illuminate\\Foundation\\Testing\\TestCase->get('/admin/tenant-r...')\n#9 /Users/wesner/Herd/shop/vendor/pestphp/pest/src/Factories/TestCaseMethodFactory.php(168): P\\Tests\\Feature\\Tenancy\\ResolveStoreTest->{closure:/Users/wesner/Herd/shop/tests/Feature/Tenancy/ResolveStoreTest.php:82}()\n#10 [internal function]: P\\Tests\\Feature\\Tenancy\\ResolveStoreTest->{closure:Pest\\Factories\\TestCaseMethodFactory::getClosure():158}()\n#11 /Users/wesner/Herd/shop/vendor/pestphp/pest/src/Concerns/Testable.php(429): call_user_func_array(Object(Closure), Array)\n#12 /Users/wesner/Herd/shop/vendor/pestphp/pest/src/Support/ExceptionTrace.php(26): P\\Tests\\Feature\\Tenancy\\ResolveStoreTest->{closure:Pest\\Concerns\\Testable::__callClosure():429}()\n#13 /Users/wesner/Herd/shop/vendor/pestphp/pest/src/Concerns/Testable.php(429): Pest\\Support\\ExceptionTrace::ensure(Object(Closure))\n#14 /Users/wesner/Herd/shop/vendor/pestphp/pest/src/Concerns/Testable.php(331): P\\Tests\\Feature\\Tenancy\\ResolveStoreTest->__callClosure(Object(Closure), Array)\n#15 /Users/wesner/Herd/shop/vendor/pestphp/pest/src/Factories/TestCaseFactory.php(169) : eval()'d code(53): P\\Tests\\Feature\\Tenancy\\ResolveStoreTest->__runTest(Object(Closure))\n#16 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestCase.php(1332): P\\Tests\\Feature\\Tenancy\\ResolveStoreTest->__pest_evaluable_admin_requests_resolve_only_the_session_store_the_user_belongs_to()\n#17 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestCase.php(519): PHPUnit\\Framework\\TestCase->runTest()\n#18 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestRunner/TestRunner.php(99): PHPUnit\\Framework\\TestCase->runBare()\n#19 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestCase.php(359): PHPUnit\\Framework\\TestRunner->run(Object(P\\Tests\\Feature\\Tenancy\\ResolveStoreTest))\n#20 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestSuite.php(374): PHPUnit\\Framework\\TestCase->run()\n#21 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestSuite.php(374): PHPUnit\\Framework\\TestSuite->run()\n#22 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestSuite.php(374): PHPUnit\\Framework\\TestSuite->run()\n#23 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/TextUI/TestRunner.php(64): PHPUnit\\Framework\\TestSuite->run()\n#24 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/TextUI/Application.php(229): PHPUnit\\TextUI\\TestRunner->run(Object(PHPUnit\\TextUI\\Configuration\\Configuration), Object(PHPUnit\\Runner\\ResultCache\\DefaultResultCache), Object(PHPUnit\\Framework\\TestSuite))\n#25 /Users/wesner/Herd/shop/vendor/pestphp/pest/src/Kernel.php(103): PHPUnit\\TextUI\\Application->run(Array)\n#26 /Users/wesner/Herd/shop/vendor/pestphp/pest/bin/pest(184): Pest\\Kernel->handle(Array, Array)\n#27 /Users/wesner/Herd/shop/vendor/pestphp/pest/bin/pest(192): {closure:/Users/wesner/Herd/shop/vendor/pestphp/pest/bin/pest:18}()\n#28 {main}\n\n----------------------------------------------------------------------------------\n\nSession store not set on request.\n\n  at tests/Feature/Tenancy/ResolveStoreTest.php:91\n     87▕ \n     88▕     $this->actingAs($user)\n     89▕         ->withSession(['current_store_id' => $store->getKey()])\n     90▕         ->get('/admin/tenant-resolution-test')\n  ➜  91▕         ->assertSuccessful()\n     92▕         ->assertJson(['store_id' => $store->getKey()]);\n     93▕ });\n     94▕ \n     95▕ test('admin requests reject a session store without membership', function () {\n\n  ────────────────────────────────────────────────────────────────────────────  \n   FAILED  Tests\\Feature\\Tenancy\\ResolveStoreTest > admin requests reject a…    \n  Expected response status code [403] but received 500.\nFailed asserting that 500 is identical to 403.\n\nThe following exception occurred during the last request:\n\nRuntimeException: Session store not set on request. in /Users/wesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Http/Request.php:610\nStack trace:\n#0 /Users/wesner/Herd/shop/app/Http/Middleware/ResolveStore.php(74): Illuminate\\Http\\Request->session()\n#1 /Users/wesner/Herd/shop/app/Http/Middleware/ResolveStore.php(33): App\\Http\\Middleware\\ResolveStore->resolveAdminStore(Object(Illuminate\\Http\\Request))\n#2 /Users/wesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): App\\Http\\Middleware\\ResolveStore->handle(Object(Illuminate\\Http\\Request), Object(Closure))\n#3 /Users/wesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():194}:195}(Object(Illuminate\\Http\\Request))\n#4 /Users/wesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))\n#5 /Users/wesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))\n#6 /Users/wesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php(607): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))\n#7 /Users/wesner/Herd/shop/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php(368): Illuminate\\Foundation\\Testing\\TestCase->call('GET', '/admin/tenant-r...', Array, Array, Array, Array)\n#8 /Users/wesner/Herd/shop/tests/Feature/Tenancy/ResolveStoreTest.php(101): Illuminate\\Foundation\\Testing\\TestCase->get('/admin/tenant-r...')\n#9 /Users/wesner/Herd/shop/vendor/pestphp/pest/src/Factories/TestCaseMethodFactory.php(168): P\\Tests\\Feature\\Tenancy\\ResolveStoreTest->{closure:/Users/wesner/Herd/shop/tests/Feature/Tenancy/ResolveStoreTest.php:95}()\n#10 [internal function]: P\\Tests\\Feature\\Tenancy\\ResolveStoreTest->{closure:Pest\\Factories\\TestCaseMethodFactory::getClosure():158}()\n#11 /Users/wesner/Herd/shop/vendor/pestphp/pest/src/Concerns/Testable.php(429): call_user_func_array(Object(Closure), Array)\n#12 /Users/wesner/Herd/shop/vendor/pestphp/pest/src/Support/ExceptionTrace.php(26): P\\Tests\\Feature\\Tenancy\\ResolveStoreTest->{closure:Pest\\Concerns\\Testable::__callClosure():429}()\n#13 /Users/wesner/Herd/shop/vendor/pestphp/pest/src/Concerns/Testable.php(429): Pest\\Support\\ExceptionTrace::ensure(Object(Closure))\n#14 /Users/wesner/Herd/shop/vendor/pestphp/pest/src/Concerns/Testable.php(331): P\\Tests\\Feature\\Tenancy\\ResolveStoreTest->__callClosure(Object(Closure), Array)\n#15 /Users/wesner/Herd/shop/vendor/pestphp/pest/src/Factories/TestCaseFactory.php(169) : eval()'d code(62): P\\Tests\\Feature\\Tenancy\\ResolveStoreTest->__runTest(Object(Closure))\n#16 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestCase.php(1332): P\\Tests\\Feature\\Tenancy\\ResolveStoreTest->__pest_evaluable_admin_requests_reject_a_session_store_without_membership()\n#17 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestCase.php(519): PHPUnit\\Framework\\TestCase->runTest()\n#18 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestRunner/TestRunner.php(99): PHPUnit\\Framework\\TestCase->runBare()\n#19 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestCase.php(359): PHPUnit\\Framework\\TestRunner->run(Object(P\\Tests\\Feature\\Tenancy\\ResolveStoreTest))\n#20 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestSuite.php(374): PHPUnit\\Framework\\TestCase->run()\n#21 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestSuite.php(374): PHPUnit\\Framework\\TestSuite->run()\n#22 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/Framework/TestSuite.php(374): PHPUnit\\Framework\\TestSuite->run()\n#23 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/TextUI/TestRunner.php(64): PHPUnit\\Framework\\TestSuite->run()\n#24 /Users/wesner/Herd/shop/vendor/phpunit/phpunit/src/TextUI/Application.php(229): PHPUnit\\TextUI\\TestRunner->run(Object(PHPUnit\\TextUI\\Configuration\\Configuration), Object(PHPUnit\\Runner\\ResultCache\\DefaultResultCache), Object(PHPUnit\\Framework\\TestSuite))\n#25 /Users/wesner/Herd/shop/vendor/pestphp/pest/src/Kernel.php(103): PHPUnit\\TextUI\\Application->run(Array)\n#26 /Users/wesner/Herd/shop/vendor/pestphp/pest/bin/pest(184): Pest\\Kernel->handle(Array, Array)\n#27 /Users/wesner/Herd/shop/vendor/pestphp/pest/bin/pest(192): {closure:/Users/wesner/Herd/shop/vendor/pestphp/pest/bin/pest:18}()\n#28 {main}\n\n----------------------------------------------------------------------------------\n\nSession store not set on request.\n\n  at tests/Feature/Tenancy/ResolveStoreTest.php:102\n     98▕ \n     99▕     $this->actingAs($user)\n    100▕         ->withSession(['current_store_id' => $store->getKey()])\n    101▕         ->get('/admin/tenant-resolution-test')\n  ➜ 102▕         ->assertForbidden();\n    103▕ });\n    104▕\n\n\n  Tests:    25 failed, 30 passed (116 assertions)\n  Duration: 0.88s\n\n"}


status: completed

input:
const r = await tools.exec_command({cmd:"sed -n '1,220p' bootstrap/app.php && sed -n '1,220p' app/Http/Middleware/ResolveStore.php && sed -n '1,180p' routes/web.php && sed -n '1,160p' tests/Feature/Tenancy/ResolveStoreTest.php","workdir":"/Users/wesner/Herd/shop","yield_time_ms":10000,"max_output_tokens":22000});
text(r.output);


command: sed -n '1,220p' bootstrap/app.php && sed -n '1,220p' app/Http/Middleware/ResolveStore.php && sed -n '1,180p' routes/web.php && sed -n '1,160p' tests/Feature/Tenancy/ResolveStoreTest.php

app.php

type: read

cmd: sed -n '1,220p' bootstrap/app.php

web.php

cmd: sed -n '1,180p' routes/web.php

ResolveStoreTest.php

stdout:
<?php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware): void {
        $middleware->prepend(App\Http\Middleware\ResolveStore::class);

        $middleware->group('storefront', [
            'store.resolve:storefront',
        ]);

        $middleware->group('admin', [
            'store.resolve:admin',
        ]);

        $middleware->alias([
            'store.resolve' => App\Http\Middleware\ResolveStore::class,
            'role.check' => App\Http\Middleware\EnsureStoreRole::class,
        ]);
    })
    ->withExceptions(function (Exceptions $exceptions): void {
        //
    })->create();
<?php

namespace App\Http\Middleware;

use App\Enums\StoreDomainType;
use App\Enums\StoreStatus;
use App\Models\Store;
use App\Models\StoreDomain;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;

class ResolveStore
{
    public function handle(Request $request, Closure $next, string $context = 'storefront'): Response
    {
        if (! Schema::hasTable('stores')) {
            return $next($request);
        }

        if ($this->isPublicAdminAuthRequest($request)) {
            return $next($request);
        }
        $context = $context === 'storefront' && $this->isAdminRequest($request)
            ? 'admin'
            : $context;

        $store = $context === 'admin'
            ? $this->resolveAdminStore($request)
            : $this->resolveStorefrontStore($request);

        abort_unless($store instanceof Store, $context === 'admin' ? 403 : 404);

        if ($store->status === StoreStatus::Suspended) {
            if ($context === 'storefront') {
                abort(503, 'This store is currently unavailable.');
            }

            if (! in_array($request->method(), ['GET', 'HEAD', 'OPTIONS'], true)) {
                abort(403);
            }
        }

        $binding = (string) config('tenancy.binding', 'current_store');
        app()->instance($binding, $store);
        View::share((string) config('tenancy.view_share', 'currentStore'), $store);

        return $next($request);
    }

    private function resolveStorefrontStore(Request $request): ?Store
    {
        $hostname = Str::lower(rtrim(trim($request->getHost()), '.'));
        $cacheKey = config('tenancy.cache_prefix', 'store-domains').':'.$hostname;

        $storeId = Cache::remember(
            $cacheKey,
            (int) config('tenancy.store_cache_ttl', 300),
            fn (): ?int => StoreDomain::query()
                ->where('hostname', $hostname)
                ->where('type', config('tenancy.storefront_domain_type', StoreDomainType::Storefront->value))
                ->value('store_id'),
        );

        return $storeId === null ? null : Store::query()->find($storeId);
    }

    private function resolveAdminStore(Request $request): ?Store
    {
        $storeId = $request->session()->get(config('tenancy.admin_session_key', 'current_store_id'));
        $user = $request->user('web') ?? $request->user();

        if ($storeId === null || $user === null) {
            return null;
        }

        return $user->stores()->whereKey($storeId)->first();
    }

    private function isAdminRequest(Request $request): bool
    {
        $prefix = trim((string) config('tenancy.admin_path_prefix', 'admin'), '/');

        if ($request->is($prefix, $prefix.'/*') || $request->routeIs($prefix.'.*')) {
            return true;
        }

        return $request->is('livewire/update') && str_contains((string) $request->headers->get('referer'), '/admin');
    }

    private function isPublicAdminAuthRequest(Request $request): bool
    {
        return $request->is('admin/login', 'admin/forgot-password', 'admin/reset-password/*');
    }
}
<?php

use App\Livewire\Admin\Auth\ForgotPassword as AdminForgotPassword;
use App\Livewire\Admin\Auth\Login as AdminLogin;
use App\Livewire\Admin\Auth\ResetPassword as AdminResetPassword;
use App\Livewire\Admin\Customers\Index as AdminCustomersIndex;
use App\Livewire\Admin\Customers\Show as AdminCustomersShow;
use App\Livewire\Admin\Dashboard as AdminDashboard;
use App\Livewire\Admin\Discounts\Form as AdminDiscountForm;
use App\Livewire\Admin\Discounts\Index as AdminDiscountsIndex;
use App\Livewire\Admin\Orders\Index as AdminOrdersIndex;
use App\Livewire\Admin\Orders\Show as AdminOrdersShow;
use App\Livewire\Admin\Products\Form as AdminProductForm;
use App\Livewire\Admin\Products\Index as AdminProductsIndex;
use App\Livewire\Admin\Section as AdminSection;
use App\Livewire\Admin\Settings\General as AdminSettingsGeneral;
use App\Livewire\Admin\Settings\Shipping as AdminSettingsShipping;
use App\Livewire\Admin\Settings\Taxes as AdminSettingsTaxes;
use App\Livewire\Storefront\Account\Addresses\Index as AccountAddresses;
use App\Livewire\Storefront\Account\Auth\ForgotPassword as CustomerForgotPassword;
use App\Livewire\Storefront\Account\Auth\Login as CustomerLogin;
use App\Livewire\Storefront\Account\Auth\Register as CustomerRegister;
use App\Livewire\Storefront\Account\Auth\ResetPassword as CustomerResetPassword;
use App\Livewire\Storefront\Account\Dashboard as AccountDashboard;
use App\Livewire\Storefront\Account\Orders\Index as AccountOrders;
use App\Livewire\Storefront\Account\Orders\Show as AccountOrderShow;
use App\Livewire\Storefront\Cart\Show as CartShow;
use App\Livewire\Storefront\Checkout\Confirmation as CheckoutConfirmation;
use App\Livewire\Storefront\Checkout\Show as CheckoutShow;
use App\Livewire\Storefront\Collections\Index as CollectionsIndex;
use App\Livewire\Storefront\Collections\Show as CollectionShow;
use App\Livewire\Storefront\Home;
use App\Livewire\Storefront\Pages\Show as PageShow;
use App\Livewire\Storefront\Products\Show as ProductShow;
use App\Livewire\Storefront\Search\Index as SearchIndex;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;

Route::view('dashboard', 'dashboard')
    ->middleware(['auth', 'verified'])
    ->name('dashboard');

require __DIR__.'/settings.php';

Route::get('/favicon.ico', fn (): \Symfony\Component\HttpFoundation\Response => response('', 204))->name('favicon');

Route::middleware('store.resolve')->group(function (): void {
    Route::livewire('/', Home::class)->name('home');
    Route::livewire('/collections', CollectionsIndex::class)->name('collections.index');
    Route::livewire('/collections/{handle}', CollectionShow::class)->name('collection.show');
    Route::livewire('/products/{handle}', ProductShow::class)->name('product.show');
    Route::livewire('/cart', CartShow::class)->name('cart.show');
    Route::livewire('/search', SearchIndex::class)->name('search');
    Route::livewire('/pages/{handle}', PageShow::class)->name('page.show');
    Route::livewire('/checkout/{checkoutId}', CheckoutShow::class)->name('checkout.show');
    Route::livewire('/checkout/{checkoutId}/confirmation', CheckoutConfirmation::class)->name('checkout.confirmation');
    Route::livewire('/account/login', CustomerLogin::class)->middleware('throttle:login')->name('account.login');
    Route::livewire('/account/register', CustomerRegister::class)->name('account.register');
    Route::livewire('/forgot-password', CustomerForgotPassword::class)->name('password.request');
    Route::livewire('/reset-password/{token}', CustomerResetPassword::class)->name('password.reset');
    Route::post('/account/logout', function (): \Illuminate\Http\RedirectResponse {
        Auth::guard('customer')->logout();
        request()->session()->invalidate();
        request()->session()->regenerateToken();

        return redirect()->route('home');
    })->name('account.logout');

    Route::middleware('auth:customer')->group(function (): void {
        Route::livewire('/account', AccountDashboard::class)->name('account.dashboard');
        Route::livewire('/account/orders', AccountOrders::class)->name('account.orders');
        Route::livewire('/account/orders/{orderNumber}', AccountOrderShow::class)->name('account.order.show');
        Route::livewire('/account/addresses', AccountAddresses::class)->name('account.addresses');
    });
});

Route::livewire('/admin/login', AdminLogin::class)->middleware('throttle:login')->name('admin.login');
Route::livewire('/admin/forgot-password', AdminForgotPassword::class)->middleware('throttle:login')->name('admin.password.request');
Route::livewire('/admin/reset-password/{token}', AdminResetPassword::class)->name('admin.password.reset');
Route::post('/admin/logout', function (): \Illuminate\Http\RedirectResponse {
    Auth::guard('web')->logout();
    request()->session()->invalidate();
    request()->session()->regenerateToken();

    return redirect()->route('admin.login');
})->name('admin.logout');

Route::prefix('admin')->middleware(['auth', 'verified', 'store.resolve', 'role.check:owner,admin,staff,support'])->group(function (): void {
    Route::livewire('/', AdminDashboard::class)->name('admin.dashboard');
    Route::livewire('/products', AdminProductsIndex::class)->name('admin.products.index');
    Route::livewire('/products/create', AdminProductForm::class)->middleware('role.check:owner,admin,staff')->name('admin.products.create');
    Route::livewire('/products/{product}/edit', AdminProductForm::class)->middleware('role.check:owner,admin,staff')->name('admin.products.edit');
    Route::livewire('/orders', AdminOrdersIndex::class)->name('admin.orders.index');
    Route::livewire('/orders/{order}', AdminOrdersShow::class)->name('admin.orders.show');
    Route::livewire('/customers', AdminCustomersIndex::class)->name('admin.customers.index');
    Route::livewire('/customers/{customer}', AdminCustomersShow::class)->name('admin.customers.show');
    Route::livewire('/discounts', AdminDiscountsIndex::class)->name('admin.discounts.index');
    Route::livewire('/discounts/create', AdminDiscountForm::class)->middleware('role.check:owner,admin,staff')->name('admin.discounts.create');
    Route::livewire('/discounts/{discount}/edit', AdminDiscountForm::class)->middleware('role.check:owner,admin,staff')->name('admin.discounts.edit');
    Route::livewire('/settings', AdminSettingsGeneral::class)->middleware('role.check:owner,admin')->name('admin.settings');
    Route::livewire('/settings/shipping', AdminSettingsShipping::class)->middleware('role.check:owner,admin')->name('admin.settings.shipping');
    Route::livewire('/settings/taxes', AdminSettingsTaxes::class)->middleware('role.check:owner,admin')->name('admin.settings.taxes');
    Route::livewire('/inventory', AdminSection::class)->name('admin.inventory');
    Route::livewire('/collections', AdminSection::class)->name('admin.collections');
    Route::livewire('/collections/create', AdminSection::class)->middleware('role.check:owner,admin,staff')->name('admin.collections.create');
    Route::livewire('/collections/{collection}/edit', AdminSection::class)->middleware('role.check:owner,admin,staff')->name('admin.collections.edit');
    Route::livewire('/themes', AdminSection::class)->name('admin.themes');
    Route::livewire('/themes/{theme}/editor', AdminSection::class)->name('admin.themes.editor');
    Route::livewire('/pages', AdminSection::class)->name('admin.pages');
    Route::livewire('/pages/create', AdminSection::class)->middleware('role.check:owner,admin')->name('admin.pages.create');
    Route::livewire('/pages/{page}/edit', AdminSection::class)->middleware('role.check:owner,admin')->name('admin.pages.edit');
    Route::livewire('/navigation', AdminSection::class)->name('admin.navigation');
    Route::livewire('/apps', AdminSection::class)->name('admin.apps');
    Route::livewire('/apps/{installation}', AdminSection::class)->name('admin.apps.show');
    Route::livewire('/developers', AdminSection::class)->name('admin.developers');
    Route::livewire('/analytics', AdminSection::class)->name('admin.analytics');
    Route::livewire('/search/settings', AdminSection::class)->name('admin.search.settings');
});
<?php

use App\Enums\StoreDomainType;
use App\Enums\StoreStatus;
use App\Enums\StoreUserRole;
use App\Models\Store;
use App\Models\StoreDomain;
use App\Models\User;
use Illuminate\Support\Facades\Route;

uses(\Illuminate\Foundation\Testing\LazilyRefreshDatabase::class);

beforeEach(function (): void {
    config(['cache.default' => 'array']);

    Route::middleware(['web', 'storefront'])->get('/tenant-resolution-test', function () {
        return response()->json([
            'store_id' => app('current_store')->getKey(),
            'view_store_id' => view()->shared('currentStore')->getKey(),
        ]);
    });

    Route::middleware(['web', 'admin'])->get('/admin/tenant-resolution-test', function () {
        return response()->json(['store_id' => app('current_store')->getKey()]);
    });
});

afterEach(function (): void {
    app()->forgetInstance('current_store');
});

test('a storefront request resolves and shares the store by hostname', function () {
    $store = Store::factory()->create();

    StoreDomain::factory()->create([
        'store_id' => $store->getKey(),
        'hostname' => 'fashion.example.test',
        'type' => StoreDomainType::Storefront,
    ]);

    $this->get('http://fashion.example.test/tenant-resolution-test')
        ->assertSuccessful()
        ->assertJson([
            'store_id' => $store->getKey(),
            'view_store_id' => $store->getKey(),
        ]);
});

test('a storefront hostname is cached as a store id', function () {
    $store = Store::factory()->create();

    $domain = StoreDomain::factory()->create([
        'store_id' => $store->getKey(),
        'hostname' => 'cached.example.test',
    ]);

    $this->get('http://cached.example.test/tenant-resolution-test')->assertSuccessful();

    $domain->delete();

    $this->get('http://cached.example.test/tenant-resolution-test')
        ->assertSuccessful()
        ->assertJson(['store_id' => $store->getKey()]);
});

test('unknown storefront hosts return not found', function () {
    $this->get('http://missing.example.test/tenant-resolution-test')->assertNotFound();
});

test('suspended storefronts return service unavailable', function () {
    $store = Store::factory()->create(['status' => StoreStatus::Suspended]);

    StoreDomain::factory()->create([
        'store_id' => $store->getKey(),
        'hostname' => 'suspended.example.test',
    ]);

    $this->get('http://suspended.example.test/tenant-resolution-test')
        ->assertServiceUnavailable();
});

test('admin requests resolve only the session store the user belongs to', function () {
    $store = Store::factory()->create();
    $user = User::factory()->create();

    $store->users()->attach($user, ['role' => StoreUserRole::Owner]);

    $this->actingAs($user)
        ->withSession(['current_store_id' => $store->getKey()])
        ->get('/admin/tenant-resolution-test')
        ->assertSuccessful()
        ->assertJson(['store_id' => $store->getKey()]);
});

test('admin requests reject a session store without membership', function () {
    $store = Store::factory()->create();
    $user = User::factory()->create();

    $this->actingAs($user)
        ->withSession(['current_store_id' => $store->getKey()])
        ->get('/admin/tenant-resolution-test')
        ->assertForbidden();
});