output:
Chunk ID: 3d5e83
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1938
Output:
<?php

use App\Enums\FinancialStatus;
use App\Enums\FulfillmentStatus;
use App\Enums\PaymentStatus;
use App\Models\Fulfillment;
use App\Models\InventoryItem;
use App\Models\Order;
use App\Models\OrderLine;
use App\Models\Payment;
use App\Models\Product;
use App\Models\ProductVariant;
use App\Models\Store;
use App\Models\User;
use App\Services\WebhookService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    $this->withoutVite();
    $this->seed(DatabaseSeeder::class);
});

function adminOrderApiStore(): Store
{
    return Store::query()->where('handle', 'acme-fashion')->firstOrFail();
}

function adminOrderApiUser(): User
{
    return User::query()->where('email', 'admin@acme.test')->firstOrFail();
}

/**
 * @param  list<string>  $abilities
 * @return array{token: \App\Models\PersonalAccessToken, plain_text: string}
 */
function adminOrderApiToken(Store $store, array $abilities): array
{
    return app(WebhookService::class)->createApiToken($store, 'Order integration', $abilities);
}

/**
 * @return array{0: Order, 1: OrderLine, 2: ProductVariant}
 */
function adminOrderApiOrder(Store $store, array $orderAttributes = [], int $quantity = 2, int $unitPrice = 2500): array
{
    $product = Product::factory()
        ->withDefaultVariant($unitPrice)
        ->create(['store_id' => $store->getKey()]);
    $variant = ProductVariant::withoutGlobalScopes()
        ->where('product_id', $product->getKey())
        ->firstOrFail();

    InventoryItem::withoutGlobalScopes()
        ->where('variant_id', $variant->getKey())
        ->update([
            'quantity_on_hand' => 10,
            'quantity_reserved' => 0,
        ]);

    $total = $quantity * $unitPrice;
    $order = Order::factory()->paid()->create(array_merge([
        'store_id' => $store->getKey(),
        'subtotal_amount' => $total,
        'shipping_amount' => 0,
        'tax_amount' => 0,
        'total_amount' => $total,
        'email' => 'buyer@example.test',
    ], $orderAttributes));
    $line = OrderLine::factory()->create([
        'order_id' => $order->getKey(),
        'product_id' => $product->getKey(),
        'variant_id' => $variant->getKey(),
        'title_snapshot' => 'Admin API Product',
        'sku_snapshot' => 'ADMIN-API-001',
        'quantity' => $quantity,
        'unit_price_amount' => $unitPrice,
        'total_amount' => $total,
    ]);

    Payment::factory()->create([
        'order_id' => $order->getKey(),
        'status' => $order->financial_status === FinancialStatus::Pending ? PaymentStatus::Pending : PaymentStatus::Captured,
        'amount' => $total,
        'currency' => $order->currency,
    ]);

    return [$order, $line, $variant];
}

test('admin order api lists and shows store scoped orders', function (): void {
    $store = adminOrderApiStore();
    [$order] = adminOrderApiOrder($store, [
        'order_number' => '#8001',
        'email' => 'alpha@example.test',
    ]);
    $otherStore = Store::factory()->create();
    adminOrderApiOrder($otherStore, [
        'order_number' => '#9001',
        'email' => 'other@example.test',
    ]);

    $this->getJson("/api/admin/v1/stores/{$store->getKey()}/orders")
        ->assertUnauthorized();

    $this->actingAs(adminOrderApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders?query=alpha")
        ->assertOk()
        ->assertJsonPath('data.0.order_number', '#8001')
        ->assertJsonMissing(['order_number' => '#9001']);

    $this->actingAs(adminOrderApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}")
        ->assertOk()
        ->assertJsonPath('data.order_number', '#8001')
        ->assertJsonPath('data.lines.0.title', 'Admin API Product');
});

test('admin order api creates refunds and fulfillments', function (): void {
    $store = adminOrderApiStore();
    [$order, $line] = adminOrderApiOrder($store, quantity: 2, unitPrice: 2500);
    $user = adminOrderApiUser();

    $this->actingAs($user)
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}/refunds", [
            'amount' => 1000,
            'reason' => 'Customer return',
        ])
        ->assertCreated()
        ->assertJsonPath('data.amount', 1000)
        ->assertJsonPath('data.status', 'processed');

    expect($order->refresh()->financial_status)->toBe(FinancialStatus::PartiallyRefunded);

    $this->actingAs($user)
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}/fulfillments", [
            'line_items' => [
                ['order_line_id' => $line->getKey(), 'quantity' => 1],
            ],
            'tracking_company' => 'DHL',
            'tracking_number' => 'DHL123',
        ])
        ->assertCreated()
        ->assertJsonPath('data.status', 'pending')
        ->assertJsonPath('data.lines.0.quantity', 1);

    $fulfillment = Fulfillment::query()->where('order_id', $order->getKey())->firstOrFail();

    expect($order->refresh()->fulfillment_status)->toBe(FulfillmentStatus::Partial)
        ->and($fulfillment->tracking_number)->toBe('DHL123');
});

test('admin order api rejects orders outside the requested store', function (): void {
    $store = adminOrderApiStore();
    $otherStore = Store::factory()->create();
    [$otherOrder] = adminOrderApiOrder($otherStore, [
        'order_number' => '#9001',
    ]);

    $this->actingAs(adminOrderApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$otherOrder->getKey()}")
        ->assertNotFound();
});

test('admin order api accepts scoped bearer tokens', function (): void {
    $store = adminOrderApiStore();
    adminOrderApiOrder($store, [
        'order_number' => '#8101',
        'email' => 'token@example.test',
    ]);
    $result = adminOrderApiToken($store, ['read-orders']);

    $this->withToken($result['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders?query=token")
        ->assertOk()
        ->assertJsonPath('data.0.order_number', '#8101');

    expect($result['token']->refresh()->last_used_at)->not->toBeNull();
});

test('admin order api enforces token store scope and abilities', function (): void {
    $store = adminOrderApiStore();
    [$order] = adminOrderApiOrder($store);
    $readOnly = adminOrderApiToken($store, ['read-orders']);
    $writeToken = adminOrderApiToken($store, ['write-orders']);
    $otherStore = Store::factory()->create();
    $otherStoreToken = adminOrderApiToken($otherStore, ['read-orders']);

    $this->withToken($readOnly['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}/refunds", [
            'amount' => 500,
            'reason' => 'Read-only token',
        ])
        ->assertForbidden();

    $this->withToken($otherStoreToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders")
        ->assertForbidden();

    $this->withToken($writeToken['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}/refunds", [
            'amount' => 500,
            'reason' => 'Write token',
        ])
        ->assertCreated()
        ->assertJsonPath('data.amount', 500);
});

test('admin order api rejects expired bearer tokens', function (): void {
    $store = adminOrderApiStore();
    adminOrderApiOrder($store);
    $result = adminOrderApiToken($store, ['read-orders']);
    $result['token']->forceFill(['expires_at' => now()->subMinute()])->save();

    $this->withToken($result['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders")
        ->assertUnauthorized();
});


output:
Chunk ID: 507110
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1471
Output:
<?php

use App\Models\DataExport;
use App\Models\Fulfillment;
use App\Models\Order;
use App\Models\Store;
use App\Models\User;
use App\Services\WebhookService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    $this->withoutVite();
    $this->seed(DatabaseSeeder::class);
});

function adminOrderExportApiStore(): Store
{
    return Store::query()->where('handle', 'acme-fashion')->firstOrFail();
}

function adminOrderExportApiUser(): User
{
    return User::query()->where('email', 'admin@acme.test')->firstOrFail();
}

/**
 * @param  list<string>  $abilities
 * @return array{token: \App\Models\PersonalAccessToken, plain_text: string}
 */
function adminOrderExportApiToken(Store $store, array $abilities): array
{
    return app(WebhookService::class)->createApiToken($store, 'Order export integration', $abilities);
}

function adminOrderExportApiOrder(Store $store, array $attributes = []): Order
{
    return Order::factory()->paid()->create([
        'store_id' => $store->getKey(),
        'email' => 'export@example.test',
        'subtotal_amount' => 2500,
        'discount_amount' => 100,
        'shipping_amount' => 500,
        'tax_amount' => 0,
        'total_amount' => 2900,
        'currency' => $store->default_currency,
        ...$attributes,
    ]);
}

test('admin order export api creates completed csv exports', function (): void {
    Storage::fake('local');

    $store = adminOrderExportApiStore();
    $matching = adminOrderExportApiOrder($store, [
        'order_number' => '#EX1001',
        'created_at' => now()->subDays(2),
    ]);
    Fulfillment::factory()->shipped()->create([
        'order_id' => $matching->getKey(),
        'tracking_number' => 'TRACK-1001',
    ]);
    adminOrderExportApiOrder($store, [
        'order_number' => '#EX2001',
        'status' => 'pending',
        'financial_status' => 'pending',
        'created_at' => now()->subDays(2),
    ]);
    adminOrderExportApiOrder(Store::factory()->create(), [
        'order_number' => '#EX3001',
        'created_at' => now()->subDays(2),
    ]);

    $createResponse = $this->actingAs(adminOrderExportApiUser())
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/exports/orders", [
            'format' => 'csv',
            'filters' => [
                'status' => 'paid',
                'created_after' => now()->subDays(3)->toIso8601String(),
                'created_before' => now()->subDay()->toIso8601String(),
            ],
        ])
        ->assertAccepted()
        ->assertJsonPath('status', 'completed');

    $export = DataExport::query()->findOrFail($createResponse->json('export_id'));

    Storage::disk('local')->assertExists($export->storage_key);

    $showResponse = $this->actingAs(adminOrderExportApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/exports/{$export->getKey()}")
        ->assertOk()
        ->assertJsonPath('data.status', 'completed')
        ->assertJsonPath('data.format', 'csv')
        ->assertJsonPath('data.row_count', 1)
        ->assertJsonPath('data.download_expires_at', $export->download_expires_at?->toIso8601String());

    $csv = rawurldecode(Str::after((string) $showResponse->json('data.download_url'), ','));

    expect($csv)->toContain('order_number,created_at,status')
        ->and($csv)->toContain('#EX1001')
        ->and($csv)->toContain('TRACK-1001')
        ->and($csv)->not->toContain('#EX2001')
        ->and($csv)->not->toContain('#EX3001');
});

test('admin order export api enforces token abilities and store scope', function (): void {
    Storage::fake('local');

    $store = adminOrderExportApiStore();
    adminOrderExportApiOrder($store, ['order_number' => '#TOKEN-EXPORT']);
    $readToken = adminOrderExportApiToken($store, ['read-orders']);
    $wrongAbilityToken = adminOrderExportApiToken($store, ['read-products']);
    $otherStoreToken = adminOrderExportApiToken(Store::factory()->create(), ['read-orders']);

    $this->withToken($wrongAbilityToken['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/exports/orders", [
            'format' => 'csv',
        ])
        ->assertForbidden();

    $createResponse = $this->withToken($readToken['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/exports/orders", [
            'format' => 'csv',
        ])
        ->assertAccepted();

    $export = DataExport::query()->findOrFail($createResponse->json('export_id'));

    $this->withToken($readToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/exports/{$export->getKey()}")
        ->assertOk();

    $this->withToken($otherStoreToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/exports/{$export->getKey()}")
        ->assertForbidden();

    $otherStoreExport = DataExport::factory()->create([
        'store_id' => Store::factory()->create()->getKey(),
    ]);

    $this->actingAs(adminOrderExportApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/exports/{$otherStoreExport->getKey()}")
        ->assertNotFound();
});

test('admin order export api validates format and filters', function (): void {
    $store = adminOrderExportApiStore();

    $this->actingAs(adminOrderExportApiUser())
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/exports/orders", [
            'format' => 'xlsx',
            'filters' => [
                'status' => 'lost',
                'created_after' => now()->toIso8601String(),
                'created_before' => now()->subDay()->toIso8601String(),
            ],
        ])
        ->assertUnprocessable()
        ->assertJsonValidationErrors(['format', 'filters.status', 'filters.created_before']);
});


status: completed

input:
*** Begin Patch
*** Update File: tests/Feature/Api/AdminCollectionApiTest.php
@@
     ]);
     $user = adminCollectionApiUser();
+    $readToken = adminApiBearerToken($store, ['read-collections'], $user);
+    $writeToken = adminApiBearerToken($store, ['write-collections'], $user);

-    $this->actingAs($user)
+    $this->withToken($readToken)
         ->getJson("/api/admin/v1/stores/{$store->getKey()}/collections?query=New")
@@
-    $createResponse = $this->actingAs($user)
+    $createResponse = $this->withToken($writeToken)
         ->postJson("/api/admin/v1/stores/{$store->getKey()}/collections", [
@@
-    $this->actingAs($user)
+    $this->withToken($writeToken)
         ->putJson("/api/admin/v1/stores/{$store->getKey()}/collections/{$collection->getKey()}", [
@@
-    $this->actingAs($user)
+    $this->withToken($writeToken)
         ->deleteJson("/api/admin/v1/stores/{$store->getKey()}/collections/{$collection->getKey()}")
@@
-    $this->actingAs(adminCollectionApiUser())
+    $this->withToken(adminApiBearerToken($store, ['write-collections'], adminCollectionApiUser()))
         ->postJson("/api/admin/v1/stores/{$store->getKey()}/collections", [
*** End Patch


stdout:
Success. Updated the following files:
M tests/Feature/Api/AdminCollectionApiTest.php

type: update

unified_diff:
@@ -47,4 +47,6 @@
     $user = adminCollectionApiUser();
+    $readToken = adminApiBearerToken($store, ['read-collections'], $user);
+    $writeToken = adminApiBearerToken($store, ['write-collections'], $user);

-    $this->actingAs($user)
+    $this->withToken($readToken)
         ->getJson("/api/admin/v1/stores/{$store->getKey()}/collections?query=New")
@@ -53,3 +55,3 @@

-    $createResponse = $this->actingAs($user)
+    $createResponse = $this->withToken($writeToken)
         ->postJson("/api/admin/v1/stores/{$store->getKey()}/collections", [
@@ -75,3 +77,3 @@

-    $this->actingAs($user)
+    $this->withToken($writeToken)
         ->putJson("/api/admin/v1/stores/{$store->getKey()}/collections/{$collection->getKey()}", [
@@ -90,3 +92,3 @@

-    $this->actingAs($user)
+    $this->withToken($writeToken)
         ->deleteJson("/api/admin/v1/stores/{$store->getKey()}/collections/{$collection->getKey()}")
@@ -137,3 +139,3 @@

-    $this->actingAs(adminCollectionApiUser())
+    $this->withToken(adminApiBearerToken($store, ['write-collections'], adminCollectionApiUser()))
         ->postJson("/api/admin/v1/stores/{$store->getKey()}/collections", [

status: completed


output: {"output":"Success. Updated the following files:\nM tests/Feature/Api/AdminCollectionApiTest.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


status: completed

input:
*** Begin Patch
*** Update File: tests/Feature/Api/AdminDiscountApiTest.php
@@
     $collection = Collection::factory()->create(['store_id' => $store->getKey()]);
     $user = adminDiscountApiUser();
+    $readToken = adminApiBearerToken($store, ['read-discounts'], $user);
+    $writeToken = adminApiBearerToken($store, ['write-discounts'], $user);

-    $this->actingAs($user)
+    $this->withToken($readToken)
         ->getJson("/api/admin/v1/stores/{$store->getKey()}/discounts?type=code&status=active")
@@
-    $createResponse = $this->actingAs($user)
+    $createResponse = $this->withToken($writeToken)
         ->postJson("/api/admin/v1/stores/{$store->getKey()}/discounts", [
@@
-    $this->actingAs($user)
+    $this->withToken($writeToken)
         ->putJson("/api/admin/v1/stores/{$store->getKey()}/discounts/{$discount->getKey()}", [
@@
-    $this->actingAs($user)
+    $this->withToken($writeToken)
         ->deleteJson("/api/admin/v1/stores/{$store->getKey()}/discounts/{$discount->getKey()}")
@@
-    $this->actingAs(adminDiscountApiUser())
+    $this->withToken(adminApiBearerToken($store, ['write-discounts'], adminDiscountApiUser()))
         ->postJson("/api/admin/v1/stores/{$store->getKey()}/discounts", [
*** End Patch


stdout:
Success. Updated the following files:
M tests/Feature/Api/AdminDiscountApiTest.php

unified_diff:
@@ -43,4 +43,6 @@
     $user = adminDiscountApiUser();
+    $readToken = adminApiBearerToken($store, ['read-discounts'], $user);
+    $writeToken = adminApiBearerToken($store, ['write-discounts'], $user);

-    $this->actingAs($user)
+    $this->withToken($readToken)
         ->getJson("/api/admin/v1/stores/{$store->getKey()}/discounts?type=code&status=active")
@@ -48,3 +50,3 @@

-    $createResponse = $this->actingAs($user)
+    $createResponse = $this->withToken($writeToken)
         ->postJson("/api/admin/v1/stores/{$store->getKey()}/discounts", [
@@ -75,3 +77,3 @@

-    $this->actingAs($user)
+    $this->withToken($writeToken)
         ->putJson("/api/admin/v1/stores/{$store->getKey()}/discounts/{$discount->getKey()}", [
@@ -87,3 +89,3 @@

-    $this->actingAs($user)
+    $this->withToken($writeToken)
         ->deleteJson("/api/admin/v1/stores/{$store->getKey()}/discounts/{$discount->getKey()}")
@@ -134,3 +136,3 @@

-    $this->actingAs(adminDiscountApiUser())
+    $this->withToken(adminApiBearerToken($store, ['write-discounts'], adminDiscountApiUser()))
         ->postJson("/api/admin/v1/stores/{$store->getKey()}/discounts", [


output: {"output":"Success. Updated the following files:\nM tests/Feature/Api/AdminDiscountApiTest.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


status: completed

input:
*** Begin Patch
*** Update File: tests/Feature/Api/AdminOrderApiTest.php
@@
     adminOrderApiOrder($otherStore, [
         'order_number' => '#9001',
         'email' => 'other@example.test',
     ]);
+    $readToken = adminApiBearerToken($store, ['read-orders'], adminOrderApiUser());

     $this->getJson("/api/admin/v1/stores/{$store->getKey()}/orders")
         ->assertUnauthorized();

-    $this->actingAs(adminOrderApiUser())
+    $this->withToken($readToken)
         ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders?query=alpha")
@@
-    $this->actingAs(adminOrderApiUser())
+    $this->withToken($readToken)
         ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}")
@@
     $store = adminOrderApiStore();
     [$order, $line] = adminOrderApiOrder($store, quantity: 2, unitPrice: 2500);
     $user = adminOrderApiUser();
+    $writeToken = adminApiBearerToken($store, ['write-orders'], $user);

-    $this->actingAs($user)
+    $this->withToken($writeToken)
         ->postJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}/refunds", [
@@
-    $this->actingAs($user)
+    $this->withToken($writeToken)
         ->postJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}/fulfillments", [
@@
-    $this->actingAs(adminOrderApiUser())
+    $this->withToken(adminApiBearerToken($store, ['read-orders'], adminOrderApiUser()))
         ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$otherOrder->getKey()}")
*** End Patch


stdout:
Success. Updated the following files:
M tests/Feature/Api/AdminOrderApiTest.php

unified_diff:
@@ -104,2 +104,3 @@
     ]);
+    $readToken = adminApiBearerToken($store, ['read-orders'], adminOrderApiUser());

@@ -108,3 +109,3 @@

-    $this->actingAs(adminOrderApiUser())
+    $this->withToken($readToken)
         ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders?query=alpha")
@@ -114,3 +115,3 @@

-    $this->actingAs(adminOrderApiUser())
+    $this->withToken($readToken)
         ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}")
@@ -125,4 +126,5 @@
     $user = adminOrderApiUser();
+    $writeToken = adminApiBearerToken($store, ['write-orders'], $user);

-    $this->actingAs($user)
+    $this->withToken($writeToken)
         ->postJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}/refunds", [
@@ -137,3 +139,3 @@

-    $this->actingAs($user)
+    $this->withToken($writeToken)
         ->postJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}/fulfillments", [
@@ -162,3 +164,3 @@

-    $this->actingAs(adminOrderApiUser())
+    $this->withToken(adminApiBearerToken($store, ['read-orders'], adminOrderApiUser()))
         ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$otherOrder->getKey()}")


output: {"output":"Success. Updated the following files:\nM tests/Feature/Api/AdminOrderApiTest.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


status: completed

input:
*** Begin Patch
*** Update File: tests/Feature/Api/AdminOrderExportApiTest.php
@@
     adminOrderExportApiOrder(Store::factory()->create(), [
         'order_number' => '#EX3001',
         'created_at' => now()->subDays(2),
     ]);
+    $readToken = adminApiBearerToken($store, ['read-orders'], adminOrderExportApiUser());

-    $createResponse = $this->actingAs(adminOrderExportApiUser())
+    $createResponse = $this->withToken($readToken)
         ->postJson("/api/admin/v1/stores/{$store->getKey()}/exports/orders", [
@@
-    $showResponse = $this->actingAs(adminOrderExportApiUser())
+    $showResponse = $this->withToken($readToken)
         ->getJson("/api/admin/v1/stores/{$store->getKey()}/exports/{$export->getKey()}")
@@
-    $this->actingAs(adminOrderExportApiUser())
+    $this->withToken(adminApiBearerToken($store, ['read-orders'], adminOrderExportApiUser()))
         ->getJson("/api/admin/v1/stores/{$store->getKey()}/exports/{$otherStoreExport->getKey()}")
@@
-    $this->actingAs(adminOrderExportApiUser())
+    $this->withToken(adminApiBearerToken($store, ['read-orders'], adminOrderExportApiUser()))
         ->postJson("/api/admin/v1/stores/{$store->getKey()}/exports/orders", [
*** End Patch


stdout:
Success. Updated the following files:
M tests/Feature/Api/AdminOrderExportApiTest.php

unified_diff:
@@ -76,4 +76,5 @@
     ]);
+    $readToken = adminApiBearerToken($store, ['read-orders'], adminOrderExportApiUser());

-    $createResponse = $this->actingAs(adminOrderExportApiUser())
+    $createResponse = $this->withToken($readToken)
         ->postJson("/api/admin/v1/stores/{$store->getKey()}/exports/orders", [
@@ -93,3 +94,3 @@

-    $showResponse = $this->actingAs(adminOrderExportApiUser())
+    $showResponse = $this->withToken($readToken)
         ->getJson("/api/admin/v1/stores/{$store->getKey()}/exports/{$export->getKey()}")
@@ -145,3 +146,3 @@

-    $this->actingAs(adminOrderExportApiUser())
+    $this->withToken(adminApiBearerToken($store, ['read-orders'], adminOrderExportApiUser()))
         ->getJson("/api/admin/v1/stores/{$store->getKey()}/exports/{$otherStoreExport->getKey()}")
@@ -153,3 +154,3 @@

-    $this->actingAs(adminOrderExportApiUser())
+    $this->withToken(adminApiBearerToken($store, ['read-orders'], adminOrderExportApiUser()))
         ->postJson("/api/admin/v1/stores/{$store->getKey()}/exports/orders", [


output: {"output":"Success. Updated the following files:\nM tests/Feature/Api/AdminOrderExportApiTest.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


cmd: sed -n '1,220p' tests/Feature/Api/AdminPageApiTest.php


cmd: sed -n '1,220p' tests/Feature/Api/AdminSearchIndexApiTest.php


cmd: sed -n '1,220p' tests/Feature/Api/AdminAnalyticsSummaryApiTest.php


cmd: sed -n '1,240p' tests/Feature/Api/AdminStoreSettingsApiTest.php


command: sed -n '1,220p' tests/Feature/Api/AdminPageApiTest.php

AdminPageApiTest.php

type: read

aggregated_output:
<?php

use App\Models\Page;
use App\Models\Store;
use App\Models\User;
use App\Services\WebhookService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    $this->withoutVite();
    $this->seed(DatabaseSeeder::class);
});

function adminPageApiStore(): Store
{
    return Store::query()->where('handle', 'acme-fashion')->firstOrFail();
}

function adminPageApiUser(): User
{
    return User::query()->where('email', 'admin@acme.test')->firstOrFail();
}

/**
 * @param  list<string>  $abilities
 * @return array{token: \App\Models\PersonalAccessToken, plain_text: string}
 */
function adminPageApiToken(Store $store, array $abilities): array
{
    return app(WebhookService::class)->createApiToken($store, 'Content integration', $abilities);
}

test('admin page api lists creates updates and deletes pages', function (): void {
    $store = adminPageApiStore();
    $otherStore = Store::factory()->create();
    Page::factory()->published()->create([
        'store_id' => $store->getKey(),
        'title' => 'API Visible Page',
        'handle' => 'api-visible-page',
    ]);
    Page::factory()->published()->create([
        'store_id' => $otherStore->getKey(),
        'title' => 'Other Store Page',
        'handle' => 'other-store-page',
    ]);
    $user = adminPageApiUser();

    $this->actingAs($user)
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/pages?status=published&query=API")
        ->assertOk()
        ->assertJsonFragment(['title' => 'API Visible Page'])
        ->assertJsonMissing(['title' => 'Other Store Page']);

    $createResponse = $this->actingAs($user)
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/pages", [
            'title' => 'Shipping Policy API',
            'body_html' => '
Shipping Policy

We ship worldwide.
',
            'status' => 'published',
        ])
        ->assertCreated()
        ->assertJsonPath('data.title', 'Shipping Policy API')
        ->assertJsonPath('data.handle', 'shipping-policy-api')
        ->assertJsonPath('data.status', 'published');

    $page = Page::withoutGlobalScopes()->findOrFail($createResponse->json('data.id'));

    expect($page->published_at)->not->toBeNull();

    $this->actingAs($user)
        ->putJson("/api/admin/v1/stores/{$store->getKey()}/pages/{$page->getKey()}", [
            'handle' => 'API Shipping Policy Updated',
            'body_html' => '
Updated policy.
',
            'status' => 'draft',
        ])
        ->assertOk()
        ->assertJsonPath('data.handle', 'api-shipping-policy-updated')
        ->assertJsonPath('data.body_html', '
Updated policy.
')
        ->assertJsonPath('data.status', 'draft');

    $this->actingAs($user)
        ->deleteJson("/api/admin/v1/stores/{$store->getKey()}/pages/{$page->getKey()}")
        ->assertOk()
        ->assertJsonPath('message', 'Page deleted');

    expect(Page::withoutGlobalScopes()->whereKey($page->getKey())->exists())->toBeFalse();
});

test('admin page api enforces token abilities and store scope', function (): void {
    $store = adminPageApiStore();
    $otherStore = Store::factory()->create();
    $page = Page::factory()->published()->create([
        'store_id' => $store->getKey(),
        'title' => 'Token Content Page',
        'handle' => 'token-content-page',
    ]);
    $readToken = adminPageApiToken($store, ['read-content']);
    $writeToken = adminPageApiToken($store, ['write-content']);
    $otherStoreToken = adminPageApiToken($otherStore, ['read-content']);

    $this->withToken($readToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/pages?query=Token")
        ->assertOk()
        ->assertJsonPath('data.0.title', 'Token Content Page');

    $this->withToken($readToken['plain_text'])
        ->deleteJson("/api/admin/v1/stores/{$store->getKey()}/pages/{$page->getKey()}")
        ->assertForbidden();

    $this->withToken($otherStoreToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/pages")
        ->assertForbidden();

    $this->withToken($writeToken['plain_text'])
        ->deleteJson("/api/admin/v1/stores/{$store->getKey()}/pages/{$page->getKey()}")
        ->assertOk();
});

test('admin page api validates normalized handles and published dates', function (): void {
    $store = adminPageApiStore();
    $existing = Page::factory()->create([
        'store_id' => $store->getKey(),
        'handle' => 'existing-api-page',
    ]);

    $this->actingAs(adminPageApiUser())
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/pages", [
            'title' => 'Invalid API Page',
            'handle' => str_replace('-', ' ', $existing->handle),
            'status' => 'published',
        ])
        ->assertUnprocessable()
        ->assertJsonValidationErrors(['handle']);

    $this->actingAs(adminPageApiUser())
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/pages", [
            'title' => 'Invalid API Date',
            'status' => 'published',
            'published_at' => 'not-a-date',
        ])
        ->assertUnprocessable()
        ->assertJsonValidationErrors(['published_at']);
});


command: sed -n '1,220p' tests/Feature/Api/AdminSearchIndexApiTest.php

aggregated_output:
<?php

use App\Models\Product;
use App\Models\SearchSettings;
use App\Models\Store;
use App\Models\User;
use App\Services\SearchService;
use App\Services\WebhookService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    $this->withoutVite();
    $this->seed(DatabaseSeeder::class);
});

function adminSearchIndexApiStore(): Store
{
    return Store::query()->where('handle', 'acme-fashion')->firstOrFail();
}

function adminSearchIndexApiUser(): User
{
    return User::query()->where('email', 'admin@acme.test')->firstOrFail();
}

/**
 * @param  list<string>  $abilities
 * @return array{token: \App\Models\PersonalAccessToken, plain_text: string}
 */
function adminSearchIndexApiToken(Store $store, array $abilities): array
{
    return app(WebhookService::class)->createApiToken($store, 'Search integration', $abilities);
}

test('admin search index api reports status and rebuilds stale documents', function (): void {
    $store = adminSearchIndexApiStore();
    $product = Product::factory()
        ->for($store)
        ->withDefaultVariant(2999)
        ->create([
            'title' => 'API Reindex Linen Jacket',
            'handle' => 'api-reindex-linen-jacket',
        ]);

    DB::table('products_fts')->where('product_id', $product->getKey())->delete();

    expect(app(SearchService::class)->search($store, 'api reindex linen', [], 12)->total())->toBe(0);

    $this->actingAs(adminSearchIndexApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/search/status")
        ->assertOk()
        ->assertJsonPath('data.index_status', 'stale')
        ->assertJsonPath('data.pending_updates', 1);

    $this->actingAs(adminSearchIndexApiUser())
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/search/reindex")
        ->assertAccepted()
        ->assertJsonPath('status', 'completed')
        ->assertJsonPath('documents_count', Product::withoutGlobalScopes()->where('store_id', $store->getKey())->count());

    expect(app(SearchService::class)->search($store, 'api reindex linen', [], 12)->total())->toBe(1)
        ->and(SearchSettings::withoutGlobalScopes()->where('store_id', $store->getKey())->firstOrFail()->updated_at)->not->toBeNull();

    $this->actingAs(adminSearchIndexApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/search/status")
        ->assertOk()
        ->assertJsonPath('data.index_status', 'ready')
        ->assertJsonPath('data.pending_updates', 0);
});

test('admin search index api enforces token abilities and store scope', function (): void {
    $store = adminSearchIndexApiStore();
    $otherStore = Store::factory()->create();
    $readToken = adminSearchIndexApiToken($store, ['read-settings']);
    $writeToken = adminSearchIndexApiToken($store, ['write-settings']);
    $otherStoreToken = adminSearchIndexApiToken($otherStore, ['read-settings']);

    $this->withToken($readToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/search/status")
        ->assertOk();

    $this->withToken($readToken['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/search/reindex")
        ->assertForbidden();

    $this->withToken($otherStoreToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/search/status")
        ->assertForbidden();

    $this->withToken($writeToken['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/search/reindex")
        ->assertAccepted();
});


output:
Chunk ID: f34341
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1309
Output:
<?php

use App\Models\Page;
use App\Models\Store;
use App\Models\User;
use App\Services\WebhookService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    $this->withoutVite();
    $this->seed(DatabaseSeeder::class);
});

function adminPageApiStore(): Store
{
    return Store::query()->where('handle', 'acme-fashion')->firstOrFail();
}

function adminPageApiUser(): User
{
    return User::query()->where('email', 'admin@acme.test')->firstOrFail();
}

/**
 * @param  list<string>  $abilities
 * @return array{token: \App\Models\PersonalAccessToken, plain_text: string}
 */
function adminPageApiToken(Store $store, array $abilities): array
{
    return app(WebhookService::class)->createApiToken($store, 'Content integration', $abilities);
}

test('admin page api lists creates updates and deletes pages', function (): void {
    $store = adminPageApiStore();
    $otherStore = Store::factory()->create();
    Page::factory()->published()->create([
        'store_id' => $store->getKey(),
        'title' => 'API Visible Page',
        'handle' => 'api-visible-page',
    ]);
    Page::factory()->published()->create([
        'store_id' => $otherStore->getKey(),
        'title' => 'Other Store Page',
        'handle' => 'other-store-page',
    ]);
    $user = adminPageApiUser();

    $this->actingAs($user)
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/pages?status=published&query=API")
        ->assertOk()
        ->assertJsonFragment(['title' => 'API Visible Page'])
        ->assertJsonMissing(['title' => 'Other Store Page']);

    $createResponse = $this->actingAs($user)
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/pages", [
            'title' => 'Shipping Policy API',
            'body_html' => '
Shipping Policy

We ship worldwide.
',
            'status' => 'published',
        ])
        ->assertCreated()
        ->assertJsonPath('data.title', 'Shipping Policy API')
        ->assertJsonPath('data.handle', 'shipping-policy-api')
        ->assertJsonPath('data.status', 'published');

    $page = Page::withoutGlobalScopes()->findOrFail($createResponse->json('data.id'));

    expect($page->published_at)->not->toBeNull();

    $this->actingAs($user)
        ->putJson("/api/admin/v1/stores/{$store->getKey()}/pages/{$page->getKey()}", [
            'handle' => 'API Shipping Policy Updated',
            'body_html' => '
Updated policy.
',
            'status' => 'draft',
        ])
        ->assertOk()
        ->assertJsonPath('data.handle', 'api-shipping-policy-updated')
        ->assertJsonPath('data.body_html', '
Updated policy.
')
        ->assertJsonPath('data.status', 'draft');

    $this->actingAs($user)
        ->deleteJson("/api/admin/v1/stores/{$store->getKey()}/pages/{$page->getKey()}")
        ->assertOk()
        ->assertJsonPath('message', 'Page deleted');

    expect(Page::withoutGlobalScopes()->whereKey($page->getKey())->exists())->toBeFalse();
});

test('admin page api enforces token abilities and store scope', function (): void {
    $store = adminPageApiStore();
    $otherStore = Store::factory()->create();
    $page = Page::factory()->published()->create([
        'store_id' => $store->getKey(),
        'title' => 'Token Content Page',
        'handle' => 'token-content-page',
    ]);
    $readToken = adminPageApiToken($store, ['read-content']);
    $writeToken = adminPageApiToken($store, ['write-content']);
    $otherStoreToken = adminPageApiToken($otherStore, ['read-content']);

    $this->withToken($readToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/pages?query=Token")
        ->assertOk()
        ->assertJsonPath('data.0.title', 'Token Content Page');

    $this->withToken($readToken['plain_text'])
        ->deleteJson("/api/admin/v1/stores/{$store->getKey()}/pages/{$page->getKey()}")
        ->assertForbidden();

    $this->withToken($otherStoreToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/pages")
        ->assertForbidden();

    $this->withToken($writeToken['plain_text'])
        ->deleteJson("/api/admin/v1/stores/{$store->getKey()}/pages/{$page->getKey()}")
        ->assertOk();
});

test('admin page api validates normalized handles and published dates', function (): void {
    $store = adminPageApiStore();
    $existing = Page::factory()->create([
        'store_id' => $store->getKey(),
        'handle' => 'existing-api-page',
    ]);

    $this->actingAs(adminPageApiUser())
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/pages", [
            'title' => 'Invalid API Page',
            'handle' => str_replace('-', ' ', $existing->handle),
            'status' => 'published',
        ])
        ->assertUnprocessable()
        ->assertJsonValidationErrors(['handle']);

    $this->actingAs(adminPageApiUser())
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/pages", [
            'title' => 'Invalid API Date',
            'status' => 'published',
            'published_at' => 'not-a-date',
        ])
        ->assertUnprocessable()
        ->assertJsonValidationErrors(['published_at']);
});


output:
Chunk ID: e0ddd1
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 901
Output:
<?php

use App\Models\Product;
use App\Models\SearchSettings;
use App\Models\Store;
use App\Models\User;
use App\Services\SearchService;
use App\Services\WebhookService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    $this->withoutVite();
    $this->seed(DatabaseSeeder::class);
});

function adminSearchIndexApiStore(): Store
{
    return Store::query()->where('handle', 'acme-fashion')->firstOrFail();
}

function adminSearchIndexApiUser(): User
{
    return User::query()->where('email', 'admin@acme.test')->firstOrFail();
}

/**
 * @param  list<string>  $abilities
 * @return array{token: \App\Models\PersonalAccessToken, plain_text: string}
 */
function adminSearchIndexApiToken(Store $store, array $abilities): array
{
    return app(WebhookService::class)->createApiToken($store, 'Search integration', $abilities);
}

test('admin search index api reports status and rebuilds stale documents', function (): void {
    $store = adminSearchIndexApiStore();
    $product = Product::factory()
        ->for($store)
        ->withDefaultVariant(2999)
        ->create([
            'title' => 'API Reindex Linen Jacket',
            'handle' => 'api-reindex-linen-jacket',
        ]);

    DB::table('products_fts')->where('product_id', $product->getKey())->delete();

    expect(app(SearchService::class)->search($store, 'api reindex linen', [], 12)->total())->toBe(0);

    $this->actingAs(adminSearchIndexApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/search/status")
        ->assertOk()
        ->assertJsonPath('data.index_status', 'stale')
        ->assertJsonPath('data.pending_updates', 1);

    $this->actingAs(adminSearchIndexApiUser())
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/search/reindex")
        ->assertAccepted()
        ->assertJsonPath('status', 'completed')
        ->assertJsonPath('documents_count', Product::withoutGlobalScopes()->where('store_id', $store->getKey())->count());

    expect(app(SearchService::class)->search($store, 'api reindex linen', [], 12)->total())->toBe(1)
        ->and(SearchSettings::withoutGlobalScopes()->where('store_id', $store->getKey())->firstOrFail()->updated_at)->not->toBeNull();

    $this->actingAs(adminSearchIndexApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/search/status")
        ->assertOk()
        ->assertJsonPath('data.index_status', 'ready')
        ->assertJsonPath('data.pending_updates', 0);
});

test('admin search index api enforces token abilities and store scope', function (): void {
    $store = adminSearchIndexApiStore();
    $otherStore = Store::factory()->create();
    $readToken = adminSearchIndexApiToken($store, ['read-settings']);
    $writeToken = adminSearchIndexApiToken($store, ['write-settings']);
    $otherStoreToken = adminSearchIndexApiToken($otherStore, ['read-settings']);

    $this->withToken($readToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/search/status")
        ->assertOk();

    $this->withToken($readToken['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/search/reindex")
        ->assertForbidden();

    $this->withToken($otherStoreToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/search/status")
        ->assertForbidden();

    $this->withToken($writeToken['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/search/reindex")
        ->assertAccepted();
});


command: sed -n '1,220p' tests/Feature/Api/AdminAnalyticsSummaryApiTest.php

aggregated_output:
<?php

use App\Models\Order;
use App\Models\OrderLine;
use App\Models\Product;
use App\Models\Store;
use App\Models\User;
use App\Services\WebhookService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    $this->withoutVite();
    $this->seed(DatabaseSeeder::class);
});

function adminAnalyticsSummaryApiStore(): Store
{
    $store = Store::factory()->create();
    $user = adminAnalyticsSummaryApiUser();

    DB::table('store_users')->updateOrInsert(
        [
            'store_id' => $store->getKey(),
            'user_id' => $user->getKey(),
        ],
        [
            'role' => 'owner',
            'created_at' => now(),
        ],
    );

    return $store;
}

function adminAnalyticsSummaryApiUser(): User
{
    return User::query()->where('email', 'admin@acme.test')->firstOrFail();
}

/**
 * @param  list<string>  $abilities
 * @return array{token: \App\Models\PersonalAccessToken, plain_text: string}
 */
function adminAnalyticsSummaryApiToken(Store $store, array $abilities): array
{
    return app(WebhookService::class)->createApiToken($store, 'Analytics integration', $abilities);
}

test('admin analytics summary api returns totals daily rows and top products', function (): void {
    $store = adminAnalyticsSummaryApiStore();
    $from = now()->subDay()->toDateString();
    $to = now()->toDateString();

    DB::table('analytics_daily')
        ->where('store_id', $store->getKey())
        ->whereBetween('date', [$from, $to])
        ->delete();

    DB::table('analytics_daily')->insert([
        [
            'store_id' => $store->getKey(),
            'date' => $from,
            'orders_count' => 2,
            'revenue_amount' => 2000,
            'aov_amount' => 1000,
            'visits_count' => 10,
            'add_to_cart_count' => 4,
            'checkout_started_count' => 3,
            'checkout_completed_count' => 2,
        ],
        [
            'store_id' => $store->getKey(),
            'date' => $to,
            'orders_count' => 3,
            'revenue_amount' => 9000,
            'aov_amount' => 3000,
            'visits_count' => 20,
            'add_to_cart_count' => 6,
            'checkout_started_count' => 7,
            'checkout_completed_count' => 3,
        ],
    ]);

    $product = Product::factory()
        ->for($store)
        ->withDefaultVariant(2000)
        ->create(['title' => 'Analytics API Jacket']);
    $order = Order::factory()->paid()->create([
        'store_id' => $store->getKey(),
        'currency' => $store->default_currency,
        'placed_at' => Carbon::parse($from)->addHours(12),
    ]);
    OrderLine::factory()->create([
        'order_id' => $order->getKey(),
        'product_id' => $product->getKey(),
        'title_snapshot' => $product->title,
        'quantity' => 4,
        'total_amount' => 8000,
    ]);

    $this->actingAs(adminAnalyticsSummaryApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/analytics/summary?from={$from}&to={$to}")
        ->assertOk()
        ->assertJsonPath('data.period.from', $from)
        ->assertJsonPath('data.period.to', $to)
        ->assertJsonPath('data.summary.orders_count', 5)
        ->assertJsonPath('data.summary.revenue_amount', 11000)
        ->assertJsonPath('data.summary.aov_amount', 2200)
        ->assertJsonPath('data.summary.visits_count', 30)
        ->assertJsonPath('data.summary.add_to_cart_count', 10)
        ->assertJsonPath('data.summary.checkout_started_count', 10)
        ->assertJsonPath('data.summary.conversion_rate', 0.1667)
        ->assertJsonPath('data.summary.currency', $store->default_currency)
        ->assertJsonPath('data.daily.0.date', $from)
        ->assertJsonPath('data.daily.1.date', $to)
        ->assertJsonPath('data.top_products.0.product_id', $product->getKey())
        ->assertJsonPath('data.top_products.0.title', 'Analytics API Jacket')
        ->assertJsonPath('data.top_products.0.units_sold', 4)
        ->assertJsonPath('data.top_products.0.revenue_amount', 8000);
});

test('admin analytics summary api enforces token abilities and store scope', function (): void {
    $store = adminAnalyticsSummaryApiStore();
    $otherStore = Store::factory()->create();
    $from = now()->subDay()->toDateString();
    $to = now()->toDateString();
    $readToken = adminAnalyticsSummaryApiToken($store, ['read-analytics']);
    $wrongAbilityToken = adminAnalyticsSummaryApiToken($store, ['read-settings']);
    $otherStoreToken = adminAnalyticsSummaryApiToken($otherStore, ['read-analytics']);

    $this->withToken($readToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/analytics/summary?from={$from}&to={$to}")
        ->assertOk();

    $this->withToken($wrongAbilityToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/analytics/summary?from={$from}&to={$to}")
        ->assertForbidden();

    $this->withToken($otherStoreToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/analytics/summary?from={$from}&to={$to}")
        ->assertForbidden();
});

test('admin analytics summary api validates date ranges', function (): void {
    $store = adminAnalyticsSummaryApiStore();

    $this->actingAs(adminAnalyticsSummaryApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/analytics/summary?from=2026-02-10&to=2026-02-01")
        ->assertUnprocessable()
        ->assertJsonValidationErrors(['to']);

    $this->actingAs(adminAnalyticsSummaryApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/analytics/summary?from=2025-01-01&to=2026-02-01")
        ->assertUnprocessable()
        ->assertJsonValidationErrors(['to']);
});


output:
Chunk ID: 9d5fae
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1461
Output:
<?php

use App\Models\Order;
use App\Models\OrderLine;
use App\Models\Product;
use App\Models\Store;
use App\Models\User;
use App\Services\WebhookService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    $this->withoutVite();
    $this->seed(DatabaseSeeder::class);
});

function adminAnalyticsSummaryApiStore(): Store
{
    $store = Store::factory()->create();
    $user = adminAnalyticsSummaryApiUser();

    DB::table('store_users')->updateOrInsert(
        [
            'store_id' => $store->getKey(),
            'user_id' => $user->getKey(),
        ],
        [
            'role' => 'owner',
            'created_at' => now(),
        ],
    );

    return $store;
}

function adminAnalyticsSummaryApiUser(): User
{
    return User::query()->where('email', 'admin@acme.test')->firstOrFail();
}

/**
 * @param  list<string>  $abilities
 * @return array{token: \App\Models\PersonalAccessToken, plain_text: string}
 */
function adminAnalyticsSummaryApiToken(Store $store, array $abilities): array
{
    return app(WebhookService::class)->createApiToken($store, 'Analytics integration', $abilities);
}

test('admin analytics summary api returns totals daily rows and top products', function (): void {
    $store = adminAnalyticsSummaryApiStore();
    $from = now()->subDay()->toDateString();
    $to = now()->toDateString();

    DB::table('analytics_daily')
        ->where('store_id', $store->getKey())
        ->whereBetween('date', [$from, $to])
        ->delete();

    DB::table('analytics_daily')->insert([
        [
            'store_id' => $store->getKey(),
            'date' => $from,
            'orders_count' => 2,
            'revenue_amount' => 2000,
            'aov_amount' => 1000,
            'visits_count' => 10,
            'add_to_cart_count' => 4,
            'checkout_started_count' => 3,
            'checkout_completed_count' => 2,
        ],
        [
            'store_id' => $store->getKey(),
            'date' => $to,
            'orders_count' => 3,
            'revenue_amount' => 9000,
            'aov_amount' => 3000,
            'visits_count' => 20,
            'add_to_cart_count' => 6,
            'checkout_started_count' => 7,
            'checkout_completed_count' => 3,
        ],
    ]);

    $product = Product::factory()
        ->for($store)
        ->withDefaultVariant(2000)
        ->create(['title' => 'Analytics API Jacket']);
    $order = Order::factory()->paid()->create([
        'store_id' => $store->getKey(),
        'currency' => $store->default_currency,
        'placed_at' => Carbon::parse($from)->addHours(12),
    ]);
    OrderLine::factory()->create([
        'order_id' => $order->getKey(),
        'product_id' => $product->getKey(),
        'title_snapshot' => $product->title,
        'quantity' => 4,
        'total_amount' => 8000,
    ]);

    $this->actingAs(adminAnalyticsSummaryApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/analytics/summary?from={$from}&to={$to}")
        ->assertOk()
        ->assertJsonPath('data.period.from', $from)
        ->assertJsonPath('data.period.to', $to)
        ->assertJsonPath('data.summary.orders_count', 5)
        ->assertJsonPath('data.summary.revenue_amount', 11000)
        ->assertJsonPath('data.summary.aov_amount', 2200)
        ->assertJsonPath('data.summary.visits_count', 30)
        ->assertJsonPath('data.summary.add_to_cart_count', 10)
        ->assertJsonPath('data.summary.checkout_started_count', 10)
        ->assertJsonPath('data.summary.conversion_rate', 0.1667)
        ->assertJsonPath('data.summary.currency', $store->default_currency)
        ->assertJsonPath('data.daily.0.date', $from)
        ->assertJsonPath('data.daily.1.date', $to)
        ->assertJsonPath('data.top_products.0.product_id', $product->getKey())
        ->assertJsonPath('data.top_products.0.title', 'Analytics API Jacket')
        ->assertJsonPath('data.top_products.0.units_sold', 4)
        ->assertJsonPath('data.top_products.0.revenue_amount', 8000);
});

test('admin analytics summary api enforces token abilities and store scope', function (): void {
    $store = adminAnalyticsSummaryApiStore();
    $otherStore = Store::factory()->create();
    $from = now()->subDay()->toDateString();
    $to = now()->toDateString();
    $readToken = adminAnalyticsSummaryApiToken($store, ['read-analytics']);
    $wrongAbilityToken = adminAnalyticsSummaryApiToken($store, ['read-settings']);
    $otherStoreToken = adminAnalyticsSummaryApiToken($otherStore, ['read-analytics']);

    $this->withToken($readToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/analytics/summary?from={$from}&to={$to}")
        ->assertOk();

    $this->withToken($wrongAbilityToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/analytics/summary?from={$from}&to={$to}")
        ->assertForbidden();

    $this->withToken($otherStoreToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/analytics/summary?from={$from}&to={$to}")
        ->assertForbidden();
});

test('admin analytics summary api validates date ranges', function (): void {
    $store = adminAnalyticsSummaryApiStore();

    $this->actingAs(adminAnalyticsSummaryApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/analytics/summary?from=2026-02-10&to=2026-02-01")
        ->assertUnprocessable()
        ->assertJsonValidationErrors(['to']);

    $this->actingAs(adminAnalyticsSummaryApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/analytics/summary?from=2025-01-01&to=2026-02-01")
        ->assertUnprocessable()
        ->assertJsonValidationErrors(['to']);
});


command: sed -n '1,240p' tests/Feature/Api/AdminStoreSettingsApiTest.php

aggregated_output:
<?php

use App\Models\Store;
use App\Models\StoreSettings;
use App\Models\User;
use App\Services\WebhookService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    $this->withoutVite();
    $this->seed(DatabaseSeeder::class);
});

function adminStoreSettingsApiStore(): Store
{
    return Store::query()->where('handle', 'acme-fashion')->firstOrFail();
}

function adminStoreSettingsApiUser(): User
{
    return User::query()->where('email', 'admin@acme.test')->firstOrFail();
}

/**
 * @param  list<string>  $abilities
 * @return array{token: \App\Models\PersonalAccessToken, plain_text: string}
 */
function adminStoreSettingsApiToken(Store $store, array $abilities): array
{
    return app(WebhookService::class)->createApiToken($store, 'Store settings integration', $abilities);
}

test('admin store settings api shows and updates general settings', function (): void {
    $store = adminStoreSettingsApiStore();
    $user = adminStoreSettingsApiUser();

    $this->actingAs($user)
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/settings")
        ->assertOk()
        ->assertJsonPath('data.name', 'Acme Fashion')
        ->assertJsonPath('data.default_currency', 'EUR')
        ->assertJsonPath('data.settings_json.announcement.enabled', true)
        ->assertJsonPath('data.domains.0.is_primary', true);

    $this->actingAs($user)
        ->putJson("/api/admin/v1/stores/{$store->getKey()}/settings", [
            'name' => 'Acme API Store',
            'default_currency' => 'USD',
            'default_locale' => 'de',
            'timezone' => 'Europe/Paris',
            'settings_json' => [
                'announcement' => [
                    'enabled' => false,
                    'text' => 'API announcement',
                ],
                'checkout' => [
                    'terms_required' => true,
                    'terms_url' => 'https://example.test/terms',
                ],
                'bank_transfer_cancel_days' => 10,
            ],
        ])
        ->assertOk()
        ->assertJsonPath('data.name', 'Acme API Store')
        ->assertJsonPath('data.default_currency', 'USD')
        ->assertJsonPath('data.default_locale', 'de')
        ->assertJsonPath('data.timezone', 'Europe/Paris')
        ->assertJsonPath('data.settings_json.announcement.enabled', false)
        ->assertJsonPath('data.settings_json.checkout.terms_required', true)
        ->assertJsonPath('data.settings_json.notifications.sender_email', 'no-reply@shop.test');

    $settings = StoreSettings::query()->whereKey($store->getKey())->firstOrFail();

    expect($store->refresh()->name)->toBe('Acme API Store')
        ->and($store->default_currency)->toBe('USD')
        ->and($settings->settings_json['announcement']['text'])->toBe('API announcement')
        ->and($settings->settings_json['checkout']['terms_url'])->toBe('https://example.test/terms')
        ->and($settings->settings_json['notifications']['sender_email'])->toBe('no-reply@shop.test');
});

test('admin store settings api enforces token abilities and store scope', function (): void {
    $store = adminStoreSettingsApiStore();
    $otherStore = Store::factory()->create();
    $readToken = adminStoreSettingsApiToken($store, ['read-settings']);
    $writeToken = adminStoreSettingsApiToken($store, ['write-settings']);
    $otherStoreToken = adminStoreSettingsApiToken($otherStore, ['read-settings']);

    $this->withToken($readToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/settings")
        ->assertOk();

    $this->withToken($readToken['plain_text'])
        ->putJson("/api/admin/v1/stores/{$store->getKey()}/settings", [
            'name' => 'Read Only API Store',
        ])
        ->assertForbidden();

    $this->withToken($otherStoreToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/settings")
        ->assertForbidden();

    $this->withToken($writeToken['plain_text'])
        ->putJson("/api/admin/v1/stores/{$store->getKey()}/settings", [
            'settings_json' => [
                'notifications' => [
                    'low_stock_threshold' => 12,
                ],
            ],
        ])
        ->assertOk()
        ->assertJsonPath('data.settings_json.notifications.low_stock_threshold', 12);
});

test('admin store settings api validates defaults and settings shape', function (): void {
    $store = adminStoreSettingsApiStore();

    $this->actingAs(adminStoreSettingsApiUser())
        ->putJson("/api/admin/v1/stores/{$store->getKey()}/settings", [
            'default_currency' => 'BTC',
            'default_locale' => 'es',
            'timezone' => 'Mars/Olympus',
        ])
        ->assertUnprocessable()
        ->assertJsonValidationErrors(['default_currency', 'default_locale', 'timezone']);

    $this->actingAs(adminStoreSettingsApiUser())
        ->putJson("/api/admin/v1/stores/{$store->getKey()}/settings", [
            'settings_json' => ['invalid-list-value'],
        ])
        ->assertUnprocessable()
        ->assertJsonValidationErrors(['settings_json']);

    $this->actingAs(adminStoreSettingsApiUser())
        ->putJson("/api/admin/v1/stores/{$store->getKey()}/settings", [
            'settings_json' => [
                'checkout' => [
                    'terms_url' => 'not-a-url',
                    'payment_hold_hours' => 0,
                ],
            ],
        ])
        ->assertUnprocessable()
        ->assertJsonValidationErrors(['settings_json.checkout.terms_url', 'settings_json.checkout.payment_hold_hours']);
});


output:
Chunk ID: 971e56
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1419
Output:
<?php

use App\Models\Store;
use App\Models\StoreSettings;
use App\Models\User;
use App\Services\WebhookService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    $this->withoutVite();
    $this->seed(DatabaseSeeder::class);
});

function adminStoreSettingsApiStore(): Store
{
    return Store::query()->where('handle', 'acme-fashion')->firstOrFail();
}

function adminStoreSettingsApiUser(): User
{
    return User::query()->where('email', 'admin@acme.test')->firstOrFail();
}

/**
 * @param  list<string>  $abilities
 * @return array{token: \App\Models\PersonalAccessToken, plain_text: string}
 */
function adminStoreSettingsApiToken(Store $store, array $abilities): array
{
    return app(WebhookService::class)->createApiToken($store, 'Store settings integration', $abilities);
}

test('admin store settings api shows and updates general settings', function (): void {
    $store = adminStoreSettingsApiStore();
    $user = adminStoreSettingsApiUser();

    $this->actingAs($user)
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/settings")
        ->assertOk()
        ->assertJsonPath('data.name', 'Acme Fashion')
        ->assertJsonPath('data.default_currency', 'EUR')
        ->assertJsonPath('data.settings_json.announcement.enabled', true)
        ->assertJsonPath('data.domains.0.is_primary', true);

    $this->actingAs($user)
        ->putJson("/api/admin/v1/stores/{$store->getKey()}/settings", [
            'name' => 'Acme API Store',
            'default_currency' => 'USD',
            'default_locale' => 'de',
            'timezone' => 'Europe/Paris',
            'settings_json' => [
                'announcement' => [
                    'enabled' => false,
                    'text' => 'API announcement',
                ],
                'checkout' => [
                    'terms_required' => true,
                    'terms_url' => 'https://example.test/terms',
                ],
                'bank_transfer_cancel_days' => 10,
            ],
        ])
        ->assertOk()
        ->assertJsonPath('data.name', 'Acme API Store')
        ->assertJsonPath('data.default_currency', 'USD')
        ->assertJsonPath('data.default_locale', 'de')
        ->assertJsonPath('data.timezone', 'Europe/Paris')
        ->assertJsonPath('data.settings_json.announcement.enabled', false)
        ->assertJsonPath('data.settings_json.checkout.terms_required', true)
        ->assertJsonPath('data.settings_json.notifications.sender_email', 'no-reply@shop.test');

    $settings = StoreSettings::query()->whereKey($store->getKey())->firstOrFail();

    expect($store->refresh()->name)->toBe('Acme API Store')
        ->and($store->default_currency)->toBe('USD')
        ->and($settings->settings_json['announcement']['text'])->toBe('API announcement')
        ->and($settings->settings_json['checkout']['terms_url'])->toBe('https://example.test/terms')
        ->and($settings->settings_json['notifications']['sender_email'])->toBe('no-reply@shop.test');
});

test('admin store settings api enforces token abilities and store scope', function (): void {
    $store = adminStoreSettingsApiStore();
    $otherStore = Store::factory()->create();
    $readToken = adminStoreSettingsApiToken($store, ['read-settings']);
    $writeToken = adminStoreSettingsApiToken($store, ['write-settings']);
    $otherStoreToken = adminStoreSettingsApiToken($otherStore, ['read-settings']);

    $this->withToken($readToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/settings")
        ->assertOk();

    $this->withToken($readToken['plain_text'])
        ->putJson("/api/admin/v1/stores/{$store->getKey()}/settings", [
            'name' => 'Read Only API Store',
        ])
        ->assertForbidden();

    $this->withToken($otherStoreToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/settings")
        ->assertForbidden();

    $this->withToken($writeToken['plain_text'])
        ->putJson("/api/admin/v1/stores/{$store->getKey()}/settings", [
            'settings_json' => [
                'notifications' => [
                    'low_stock_threshold' => 12,
                ],
            ],
        ])
        ->assertOk()
        ->assertJsonPath('data.settings_json.notifications.low_stock_threshold', 12);
});

test('admin store settings api validates defaults and settings shape', function (): void {
    $store = adminStoreSettingsApiStore();

    $this->actingAs(adminStoreSettingsApiUser())
        ->putJson("/api/admin/v1/stores/{$store->getKey()}/settings", [
            'default_currency' => 'BTC',
            'default_locale' => 'es',
            'timezone' => 'Mars/Olympus',
        ])
        ->assertUnprocessable()
        ->assertJsonValidationErrors(['default_currency', 'default_locale', 'timezone']);

    $this->actingAs(adminStoreSettingsApiUser())
        ->putJson("/api/admin/v1/stores/{$store->getKey()}/settings", [
            'settings_json' => ['invalid-list-value'],
        ])
        ->assertUnprocessable()
        ->assertJsonValidationErrors(['settings_json']);

    $this->actingAs(adminStoreSettingsApiUser())
        ->putJson("/api/admin/v1/stores/{$store->getKey()}/settings", [
            'settings_json' => [
                'checkout' => [
                    'terms_url' => 'not-a-url',
                    'payment_hold_hours' => 0,
                ],
            ],
        ])
        ->assertUnprocessable()
        ->assertJsonValidationErrors(['settings_json.checkout.terms_url', 'settings_json.checkout.payment_hold_hours']);
});


status: completed

input:
*** Begin Patch
*** Update File: tests/Feature/Api/AdminPageApiTest.php
@@
     ]);
     $user = adminPageApiUser();
+    $readToken = adminApiBearerToken($store, ['read-content'], $user);
+    $writeToken = adminApiBearerToken($store, ['write-content'], $user);

-    $this->actingAs($user)
+    $this->withToken($readToken)
         ->getJson("/api/admin/v1/stores/{$store->getKey()}/pages?status=published&query=API")
@@
-    $createResponse = $this->actingAs($user)
+    $createResponse = $this->withToken($writeToken)
         ->postJson("/api/admin/v1/stores/{$store->getKey()}/pages", [
@@
-    $this->actingAs($user)
+    $this->withToken($writeToken)
         ->putJson("/api/admin/v1/stores/{$store->getKey()}/pages/{$page->getKey()}", [
@@
-    $this->actingAs($user)
+    $this->withToken($writeToken)
         ->deleteJson("/api/admin/v1/stores/{$store->getKey()}/pages/{$page->getKey()}")
@@
-    $this->actingAs(adminPageApiUser())
+    $this->withToken(adminApiBearerToken($store, ['write-content'], adminPageApiUser()))
         ->postJson("/api/admin/v1/stores/{$store->getKey()}/pages", [
@@
-    $this->actingAs(adminPageApiUser())
+    $this->withToken(adminApiBearerToken($store, ['write-content'], adminPageApiUser()))
         ->postJson("/api/admin/v1/stores/{$store->getKey()}/pages", [
*** Update File: tests/Feature/Api/AdminSearchIndexApiTest.php
@@
-    $this->actingAs(adminSearchIndexApiUser())
+    $this->withToken(adminApiBearerToken($store, ['read-settings'], adminSearchIndexApiUser()))
         ->getJson("/api/admin/v1/stores/{$store->getKey()}/search/status")
@@
-    $this->actingAs(adminSearchIndexApiUser())
+    $this->withToken(adminApiBearerToken($store, ['write-settings'], adminSearchIndexApiUser()))
         ->postJson("/api/admin/v1/stores/{$store->getKey()}/search/reindex")
@@
-    $this->actingAs(adminSearchIndexApiUser())
+    $this->withToken(adminApiBearerToken($store, ['read-settings'], adminSearchIndexApiUser()))
         ->getJson("/api/admin/v1/stores/{$store->getKey()}/search/status")
*** Update File: tests/Feature/Api/AdminAnalyticsSummaryApiTest.php
@@
-    $this->actingAs(adminAnalyticsSummaryApiUser())
+    $this->withToken(adminApiBearerToken($store, ['read-analytics'], adminAnalyticsSummaryApiUser()))
         ->getJson("/api/admin/v1/stores/{$store->getKey()}/analytics/summary?from={$from}&to={$to}")
@@
-    $this->actingAs(adminAnalyticsSummaryApiUser())
+    $this->withToken(adminApiBearerToken($store, ['read-analytics'], adminAnalyticsSummaryApiUser()))
         ->getJson("/api/admin/v1/stores/{$store->getKey()}/analytics/summary?from=2026-02-10&to=2026-02-01")
@@
-    $this->actingAs(adminAnalyticsSummaryApiUser())
+    $this->withToken(adminApiBearerToken($store, ['read-analytics'], adminAnalyticsSummaryApiUser()))
         ->getJson("/api/admin/v1/stores/{$store->getKey()}/analytics/summary?from=2025-01-01&to=2026-02-01")
*** Update File: tests/Feature/Api/AdminStoreSettingsApiTest.php
@@
     $store = adminStoreSettingsApiStore();
     $user = adminStoreSettingsApiUser();
+    $readToken = adminApiBearerToken($store, ['read-settings'], $user);
+    $writeToken = adminApiBearerToken($store, ['write-settings'], $user);

-    $this->actingAs($user)
+    $this->withToken($readToken)
         ->getJson("/api/admin/v1/stores/{$store->getKey()}/settings")
@@
-    $this->actingAs($user)
+    $this->withToken($writeToken)
         ->putJson("/api/admin/v1/stores/{$store->getKey()}/settings", [
@@
-    $this->actingAs(adminStoreSettingsApiUser())
+    $this->withToken(adminApiBearerToken($store, ['write-settings'], adminStoreSettingsApiUser()))
         ->putJson("/api/admin/v1/stores/{$store->getKey()}/settings", [
@@
-    $this->actingAs(adminStoreSettingsApiUser())
+    $this->withToken(adminApiBearerToken($store, ['write-settings'], adminStoreSettingsApiUser()))
         ->putJson("/api/admin/v1/stores/{$store->getKey()}/settings", [
@@
-    $this->actingAs(adminStoreSettingsApiUser())
+    $this->withToken(adminApiBearerToken($store, ['write-settings'], adminStoreSettingsApiUser()))
         ->putJson("/api/admin/v1/stores/{$store->getKey()}/settings", [
*** End Patch


stdout:
Success. Updated the following files:
M tests/Feature/Api/AdminPageApiTest.php
M tests/Feature/Api/AdminSearchIndexApiTest.php
M tests/Feature/Api/AdminAnalyticsSummaryApiTest.php
M tests/Feature/Api/AdminStoreSettingsApiTest.php

unified_diff:
@@ -38,4 +38,6 @@
     $user = adminStoreSettingsApiUser();
+    $readToken = adminApiBearerToken($store, ['read-settings'], $user);
+    $writeToken = adminApiBearerToken($store, ['write-settings'], $user);

-    $this->actingAs($user)
+    $this->withToken($readToken)
         ->getJson("/api/admin/v1/stores/{$store->getKey()}/settings")
@@ -47,3 +49,3 @@

-    $this->actingAs($user)
+    $this->withToken($writeToken)
         ->putJson("/api/admin/v1/stores/{$store->getKey()}/settings", [
@@ -119,3 +121,3 @@

-    $this->actingAs(adminStoreSettingsApiUser())
+    $this->withToken(adminApiBearerToken($store, ['write-settings'], adminStoreSettingsApiUser()))
         ->putJson("/api/admin/v1/stores/{$store->getKey()}/settings", [
@@ -128,3 +130,3 @@

-    $this->actingAs(adminStoreSettingsApiUser())
+    $this->withToken(adminApiBearerToken($store, ['write-settings'], adminStoreSettingsApiUser()))
         ->putJson("/api/admin/v1/stores/{$store->getKey()}/settings", [
@@ -135,3 +137,3 @@

-    $this->actingAs(adminStoreSettingsApiUser())
+    $this->withToken(adminApiBearerToken($store, ['write-settings'], adminStoreSettingsApiUser()))
         ->putJson("/api/admin/v1/stores/{$store->getKey()}/settings", [

unified_diff:
@@ -105,3 +105,3 @@

-    $this->actingAs(adminAnalyticsSummaryApiUser())
+    $this->withToken(adminApiBearerToken($store, ['read-analytics'], adminAnalyticsSummaryApiUser()))
         ->getJson("/api/admin/v1/stores/{$store->getKey()}/analytics/summary?from={$from}&to={$to}")
@@ -151,3 +151,3 @@

-    $this->actingAs(adminAnalyticsSummaryApiUser())
+    $this->withToken(adminApiBearerToken($store, ['read-analytics'], adminAnalyticsSummaryApiUser()))
         ->getJson("/api/admin/v1/stores/{$store->getKey()}/analytics/summary?from=2026-02-10&to=2026-02-01")
@@ -156,3 +156,3 @@

-    $this->actingAs(adminAnalyticsSummaryApiUser())
+    $this->withToken(adminApiBearerToken($store, ['read-analytics'], adminAnalyticsSummaryApiUser()))
         ->getJson("/api/admin/v1/stores/{$store->getKey()}/analytics/summary?from=2025-01-01&to=2026-02-01")

unified_diff:
@@ -49,4 +49,6 @@
     $user = adminPageApiUser();
+    $readToken = adminApiBearerToken($store, ['read-content'], $user);
+    $writeToken = adminApiBearerToken($store, ['write-content'], $user);

-    $this->actingAs($user)
+    $this->withToken($readToken)
         ->getJson("/api/admin/v1/stores/{$store->getKey()}/pages?status=published&query=API")
@@ -56,3 +58,3 @@

-    $createResponse = $this->actingAs($user)
+    $createResponse = $this->withToken($writeToken)
         ->postJson("/api/admin/v1/stores/{$store->getKey()}/pages", [
@@ -71,3 +73,3 @@

-    $this->actingAs($user)
+    $this->withToken($writeToken)
         ->putJson("/api/admin/v1/stores/{$store->getKey()}/pages/{$page->getKey()}", [
@@ -82,3 +84,3 @@

-    $this->actingAs($user)
+    $this->withToken($writeToken)
         ->deleteJson("/api/admin/v1/stores/{$store->getKey()}/pages/{$page->getKey()}")
@@ -127,3 +129,3 @@

-    $this->actingAs(adminPageApiUser())
+    $this->withToken(adminApiBearerToken($store, ['write-content'], adminPageApiUser()))
         ->postJson("/api/admin/v1/stores/{$store->getKey()}/pages", [
@@ -136,3 +138,3 @@

-    $this->actingAs(adminPageApiUser())
+    $this->withToken(adminApiBearerToken($store, ['write-content'], adminPageApiUser()))
         ->postJson("/api/admin/v1/stores/{$store->getKey()}/pages", [

unified_diff:
@@ -52,3 +52,3 @@

-    $this->actingAs(adminSearchIndexApiUser())
+    $this->withToken(adminApiBearerToken($store, ['read-settings'], adminSearchIndexApiUser()))
         ->getJson("/api/admin/v1/stores/{$store->getKey()}/search/status")
@@ -58,3 +58,3 @@

-    $this->actingAs(adminSearchIndexApiUser())
+    $this->withToken(adminApiBearerToken($store, ['write-settings'], adminSearchIndexApiUser()))
         ->postJson("/api/admin/v1/stores/{$store->getKey()}/search/reindex")
@@ -67,3 +67,3 @@

-    $this->actingAs(adminSearchIndexApiUser())
+    $this->withToken(adminApiBearerToken($store, ['read-settings'], adminSearchIndexApiUser()))
         ->getJson("/api/admin/v1/stores/{$store->getKey()}/search/status")


output: {"output":"Success. Updated the following files:\nM tests/Feature/Api/AdminPageApiTest.php\nM tests/Feature/Api/AdminSearchIndexApiTest.php\nM tests/Feature/Api/AdminAnalyticsSummaryApiTest.php\nM tests/Feature/Api/AdminStoreSettingsApiTest.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


cmd: sed -n '1,240p' tests/Feature/Api/AdminShippingSettingsApiTest.php


cmd: sed -n '1,200p' tests/Feature/Api/AdminTaxSettingsApiTest.php


cmd: sed -n '1,280p' tests/Feature/Api/AdminThemeApiTest.php


command: sed -n '1,240p' tests/Feature/Api/AdminShippingSettingsApiTest.php

aggregated_output:
<?php

use App\Enums\ShippingRateType;
use App\Models\ShippingRate;
use App\Models\ShippingZone;
use App\Models\Store;
use App\Models\User;
use App\Services\WebhookService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    $this->withoutVite();
    $this->seed(DatabaseSeeder::class);
});

function adminShippingSettingsApiStore(): Store
{
    return Store::query()->where('handle', 'acme-fashion')->firstOrFail();
}

function adminShippingSettingsApiUser(): User
{
    return User::query()->where('email', 'admin@acme.test')->firstOrFail();
}

/**
 * @param  list<string>  $abilities
 * @return array{token: \App\Models\PersonalAccessToken, plain_text: string}
 */
function adminShippingSettingsApiToken(Store $store, array $abilities): array
{
    return app(WebhookService::class)->createApiToken($store, 'Shipping settings integration', $abilities);
}

test('admin shipping settings api lists creates updates zones and adds rates', function (): void {
    $store = adminShippingSettingsApiStore();
    $user = adminShippingSettingsApiUser();

    $this->actingAs($user)
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/shipping/zones")
        ->assertOk()
        ->assertJsonPath('data.0.name', 'Domestic')
        ->assertJsonPath('data.0.rates.0.config_json.currency', $store->default_currency);

    $createResponse = $this->actingAs($user)
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/shipping/zones", [
            'name' => 'Nordics API',
            'countries_json' => ['se', 'no'],
            'regions_json' => ['stockholm'],
        ])
        ->assertCreated()
        ->assertJsonPath('data.name', 'Nordics API')
        ->assertJsonPath('data.countries_json.0', 'SE')
        ->assertJsonPath('data.regions_json.0', 'STOCKHOLM');

    $zone = ShippingZone::withoutGlobalScopes()->findOrFail($createResponse->json('data.id'));

    $this->actingAs($user)
        ->putJson("/api/admin/v1/stores/{$store->getKey()}/shipping/zones/{$zone->getKey()}", [
            'name' => 'Nordics and Baltics API',
            'countries_json' => ['SE', 'DK'],
            'regions_json' => [],
        ])
        ->assertOk()
        ->assertJsonPath('data.name', 'Nordics and Baltics API')
        ->assertJsonPath('data.countries_json.1', 'DK');

    $rateResponse = $this->actingAs($user)
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/shipping/zones/{$zone->getKey()}/rates", [
            'name' => 'API Express',
            'type' => 'flat',
            'config_json' => [
                'price_amount' => 1200,
                'currency' => $store->default_currency,
            ],
            'is_active' => true,
        ])
        ->assertCreated()
        ->assertJsonPath('data.name', 'API Express')
        ->assertJsonPath('data.type', 'flat')
        ->assertJsonPath('data.config_json.price_amount', 1200);

    $rate = ShippingRate::withoutGlobalScopes()->findOrFail($rateResponse->json('data.id'));

    expect($zone->refresh()->countries_json)->toBe(['SE', 'DK'])
        ->and($rate->type)->toBe(ShippingRateType::Flat)
        ->and($rate->config_json['amount'])->toBe(1200);
});

test('admin shipping settings api enforces token abilities and store scope', function (): void {
    $store = adminShippingSettingsApiStore();
    $otherStore = Store::factory()->create();
    $zone = ShippingZone::withoutGlobalScopes()->where('store_id', $store->getKey())->firstOrFail();
    $readToken = adminShippingSettingsApiToken($store, ['read-settings']);
    $writeToken = adminShippingSettingsApiToken($store, ['write-settings']);
    $otherStoreToken = adminShippingSettingsApiToken($otherStore, ['read-settings']);

    $this->withToken($readToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/shipping/zones")
        ->assertOk();

    $this->withToken($readToken['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/shipping/zones", [
            'name' => 'Read Only Zone',
            'countries_json' => ['FI'],
        ])
        ->assertForbidden();

    $this->withToken($otherStoreToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/shipping/zones")
        ->assertForbidden();

    $this->withToken($writeToken['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/shipping/zones/{$zone->getKey()}/rates", [
            'name' => 'Token Rate',
            'type' => 'carrier',
            'config_json' => [
                'price_amount' => 999,
            ],
        ])
        ->assertCreated();
});

test('admin shipping settings api validates countries overlaps and rate types', function (): void {
    $store = adminShippingSettingsApiStore();
    $zone = ShippingZone::withoutGlobalScopes()->where('store_id', $store->getKey())->firstOrFail();

    $this->actingAs(adminShippingSettingsApiUser())
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/shipping/zones", [
            'name' => 'Overlap API',
            'countries_json' => ['DE'],
        ])
        ->assertUnprocessable()
        ->assertJsonValidationErrors(['countries_json']);

    $this->actingAs(adminShippingSettingsApiUser())
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/shipping/zones/{$zone->getKey()}/rates", [
            'name' => 'Invalid Rate',
            'type' => 'rocket',
            'config_json' => [],
        ])
        ->assertUnprocessable()
        ->assertJsonValidationErrors(['type']);
});


output:
Chunk ID: 1b29e0
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1398
Output:
<?php

use App\Enums\ShippingRateType;
use App\Models\ShippingRate;
use App\Models\ShippingZone;
use App\Models\Store;
use App\Models\User;
use App\Services\WebhookService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    $this->withoutVite();
    $this->seed(DatabaseSeeder::class);
});

function adminShippingSettingsApiStore(): Store
{
    return Store::query()->where('handle', 'acme-fashion')->firstOrFail();
}

function adminShippingSettingsApiUser(): User
{
    return User::query()->where('email', 'admin@acme.test')->firstOrFail();
}

/**
 * @param  list<string>  $abilities
 * @return array{token: \App\Models\PersonalAccessToken, plain_text: string}
 */
function adminShippingSettingsApiToken(Store $store, array $abilities): array
{
    return app(WebhookService::class)->createApiToken($store, 'Shipping settings integration', $abilities);
}

test('admin shipping settings api lists creates updates zones and adds rates', function (): void {
    $store = adminShippingSettingsApiStore();
    $user = adminShippingSettingsApiUser();

    $this->actingAs($user)
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/shipping/zones")
        ->assertOk()
        ->assertJsonPath('data.0.name', 'Domestic')
        ->assertJsonPath('data.0.rates.0.config_json.currency', $store->default_currency);

    $createResponse = $this->actingAs($user)
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/shipping/zones", [
            'name' => 'Nordics API',
            'countries_json' => ['se', 'no'],
            'regions_json' => ['stockholm'],
        ])
        ->assertCreated()
        ->assertJsonPath('data.name', 'Nordics API')
        ->assertJsonPath('data.countries_json.0', 'SE')
        ->assertJsonPath('data.regions_json.0', 'STOCKHOLM');

    $zone = ShippingZone::withoutGlobalScopes()->findOrFail($createResponse->json('data.id'));

    $this->actingAs($user)
        ->putJson("/api/admin/v1/stores/{$store->getKey()}/shipping/zones/{$zone->getKey()}", [
            'name' => 'Nordics and Baltics API',
            'countries_json' => ['SE', 'DK'],
            'regions_json' => [],
        ])
        ->assertOk()
        ->assertJsonPath('data.name', 'Nordics and Baltics API')
        ->assertJsonPath('data.countries_json.1', 'DK');

    $rateResponse = $this->actingAs($user)
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/shipping/zones/{$zone->getKey()}/rates", [
            'name' => 'API Express',
            'type' => 'flat',
            'config_json' => [
                'price_amount' => 1200,
                'currency' => $store->default_currency,
            ],
            'is_active' => true,
        ])
        ->assertCreated()
        ->assertJsonPath('data.name', 'API Express')
        ->assertJsonPath('data.type', 'flat')
        ->assertJsonPath('data.config_json.price_amount', 1200);

    $rate = ShippingRate::withoutGlobalScopes()->findOrFail($rateResponse->json('data.id'));

    expect($zone->refresh()->countries_json)->toBe(['SE', 'DK'])
        ->and($rate->type)->toBe(ShippingRateType::Flat)
        ->and($rate->config_json['amount'])->toBe(1200);
});

test('admin shipping settings api enforces token abilities and store scope', function (): void {
    $store = adminShippingSettingsApiStore();
    $otherStore = Store::factory()->create();
    $zone = ShippingZone::withoutGlobalScopes()->where('store_id', $store->getKey())->firstOrFail();
    $readToken = adminShippingSettingsApiToken($store, ['read-settings']);
    $writeToken = adminShippingSettingsApiToken($store, ['write-settings']);
    $otherStoreToken = adminShippingSettingsApiToken($otherStore, ['read-settings']);

    $this->withToken($readToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/shipping/zones")
        ->assertOk();

    $this->withToken($readToken['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/shipping/zones", [
            'name' => 'Read Only Zone',
            'countries_json' => ['FI'],
        ])
        ->assertForbidden();

    $this->withToken($otherStoreToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/shipping/zones")
        ->assertForbidden();

    $this->withToken($writeToken['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/shipping/zones/{$zone->getKey()}/rates", [
            'name' => 'Token Rate',
            'type' => 'carrier',
            'config_json' => [
                'price_amount' => 999,
            ],
        ])
        ->assertCreated();
});

test('admin shipping settings api validates countries overlaps and rate types', function (): void {
    $store = adminShippingSettingsApiStore();
    $zone = ShippingZone::withoutGlobalScopes()->where('store_id', $store->getKey())->firstOrFail();

    $this->actingAs(adminShippingSettingsApiUser())
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/shipping/zones", [
            'name' => 'Overlap API',
            'countries_json' => ['DE'],
        ])
        ->assertUnprocessable()
        ->assertJsonValidationErrors(['countries_json']);

    $this->actingAs(adminShippingSettingsApiUser())
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/shipping/zones/{$zone->getKey()}/rates", [
            'name' => 'Invalid Rate',
            'type' => 'rocket',
            'config_json' => [],
        ])
        ->assertUnprocessable()
        ->assertJsonValidationErrors(['type']);
});


command: sed -n '1,200p' tests/Feature/Api/AdminTaxSettingsApiTest.php

aggregated_output:
<?php

use App\Enums\TaxMode;
use App\Models\Store;
use App\Models\TaxSettings;
use App\Models\User;
use App\Services\WebhookService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    $this->withoutVite();
    $this->seed(DatabaseSeeder::class);
});

function adminTaxSettingsApiStore(): Store
{
    return Store::query()->where('handle', 'acme-fashion')->firstOrFail();
}

function adminTaxSettingsApiUser(): User
{
    return User::query()->where('email', 'admin@acme.test')->firstOrFail();
}

/**
 * @param  list<string>  $abilities
 * @return array{token: \App\Models\PersonalAccessToken, plain_text: string}
 */
function adminTaxSettingsApiToken(Store $store, array $abilities): array
{
    return app(WebhookService::class)->createApiToken($store, 'Tax settings integration', $abilities);
}

test('admin tax settings api shows and updates manual settings', function (): void {
    $store = adminTaxSettingsApiStore();
    $user = adminTaxSettingsApiUser();

    $this->actingAs($user)
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/tax/settings")
        ->assertOk()
        ->assertJsonPath('data.store_id', $store->getKey())
        ->assertJsonPath('data.mode', 'manual')
        ->assertJsonPath('data.config_json.default_tax_rate', 1900);

    $this->actingAs($user)
        ->putJson("/api/admin/v1/stores/{$store->getKey()}/tax/settings", [
            'mode' => 'manual',
            'provider' => 'none',
            'prices_include_tax' => true,
            'config_json' => [
                'default_tax_rate' => 2100,
                'tax_rates' => [
                    [
                        'country_code' => 'de',
                        'rate' => 2100,
                        'name' => 'VAT',
                        'shipping_taxed' => true,
                    ],
                ],
            ],
        ])
        ->assertOk()
        ->assertJsonPath('data.prices_include_tax', true)
        ->assertJsonPath('data.config_json.default_tax_rate', 2100)
        ->assertJsonPath('data.config_json.tax_rates.0.country_code', 'DE');

    $settings = TaxSettings::withoutGlobalScopes()->whereKey($store->getKey())->firstOrFail();

    expect($settings->mode)->toBe(TaxMode::Manual)
        ->and($settings->provider)->toBe('none')
        ->and($settings->prices_include_tax)->toBeTrue()
        ->and($settings->config_json['default_rate_bps'])->toBe(2100)
        ->and($settings->config_json['rates'][0]['country'])->toBe('DE');
});

test('admin tax settings api enforces token abilities and store scope', function (): void {
    $store = adminTaxSettingsApiStore();
    $otherStore = Store::factory()->create();
    $readToken = adminTaxSettingsApiToken($store, ['read-settings']);
    $writeToken = adminTaxSettingsApiToken($store, ['write-settings']);
    $otherStoreToken = adminTaxSettingsApiToken($otherStore, ['read-settings']);

    $this->withToken($readToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/tax/settings")
        ->assertOk();

    $this->withToken($readToken['plain_text'])
        ->putJson("/api/admin/v1/stores/{$store->getKey()}/tax/settings", [
            'mode' => 'manual',
            'provider' => 'none',
            'prices_include_tax' => false,
            'config_json' => [],
        ])
        ->assertForbidden();

    $this->withToken($otherStoreToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/tax/settings")
        ->assertForbidden();

    $this->withToken($writeToken['plain_text'])
        ->putJson("/api/admin/v1/stores/{$store->getKey()}/tax/settings", [
            'mode' => 'provider',
            'provider' => 'stripe_tax',
            'prices_include_tax' => false,
            'config_json' => [
                'stripe_tax_settings_id' => 'txr_api',
            ],
        ])
        ->assertOk()
        ->assertJsonPath('data.mode', 'provider')
        ->assertJsonPath('data.provider', 'stripe_tax');
});

test('admin tax settings api validates provider and rate payloads', function (): void {
    $store = adminTaxSettingsApiStore();

    $this->actingAs(adminTaxSettingsApiUser())
        ->putJson("/api/admin/v1/stores/{$store->getKey()}/tax/settings", [
            'mode' => 'provider',
            'prices_include_tax' => false,
            'config_json' => [],
        ])
        ->assertUnprocessable()
        ->assertJsonValidationErrors(['provider']);

    $this->actingAs(adminTaxSettingsApiUser())
        ->putJson("/api/admin/v1/stores/{$store->getKey()}/tax/settings", [
            'mode' => 'manual',
            'provider' => 'none',
            'prices_include_tax' => false,
            'config_json' => [
                'tax_rates' => [
                    [
                        'country_code' => 'DEU',
                        'rate' => 12000,
                        'name' => 'VAT',
                    ],
                ],
            ],
        ])
        ->assertUnprocessable()
        ->assertJsonValidationErrors(['config_json.tax_rates.0.country_code', 'config_json.tax_rates.0.rate']);
});


output:
Chunk ID: f3434a
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1301
Output:
<?php

use App\Enums\TaxMode;
use App\Models\Store;
use App\Models\TaxSettings;
use App\Models\User;
use App\Services\WebhookService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    $this->withoutVite();
    $this->seed(DatabaseSeeder::class);
});

function adminTaxSettingsApiStore(): Store
{
    return Store::query()->where('handle', 'acme-fashion')->firstOrFail();
}

function adminTaxSettingsApiUser(): User
{
    return User::query()->where('email', 'admin@acme.test')->firstOrFail();
}

/**
 * @param  list<string>  $abilities
 * @return array{token: \App\Models\PersonalAccessToken, plain_text: string}
 */
function adminTaxSettingsApiToken(Store $store, array $abilities): array
{
    return app(WebhookService::class)->createApiToken($store, 'Tax settings integration', $abilities);
}

test('admin tax settings api shows and updates manual settings', function (): void {
    $store = adminTaxSettingsApiStore();
    $user = adminTaxSettingsApiUser();

    $this->actingAs($user)
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/tax/settings")
        ->assertOk()
        ->assertJsonPath('data.store_id', $store->getKey())
        ->assertJsonPath('data.mode', 'manual')
        ->assertJsonPath('data.config_json.default_tax_rate', 1900);

    $this->actingAs($user)
        ->putJson("/api/admin/v1/stores/{$store->getKey()}/tax/settings", [
            'mode' => 'manual',
            'provider' => 'none',
            'prices_include_tax' => true,
            'config_json' => [
                'default_tax_rate' => 2100,
                'tax_rates' => [
                    [
                        'country_code' => 'de',
                        'rate' => 2100,
                        'name' => 'VAT',
                        'shipping_taxed' => true,
                    ],
                ],
            ],
        ])
        ->assertOk()
        ->assertJsonPath('data.prices_include_tax', true)
        ->assertJsonPath('data.config_json.default_tax_rate', 2100)
        ->assertJsonPath('data.config_json.tax_rates.0.country_code', 'DE');

    $settings = TaxSettings::withoutGlobalScopes()->whereKey($store->getKey())->firstOrFail();

    expect($settings->mode)->toBe(TaxMode::Manual)
        ->and($settings->provider)->toBe('none')
        ->and($settings->prices_include_tax)->toBeTrue()
        ->and($settings->config_json['default_rate_bps'])->toBe(2100)
        ->and($settings->config_json['rates'][0]['country'])->toBe('DE');
});

test('admin tax settings api enforces token abilities and store scope', function (): void {
    $store = adminTaxSettingsApiStore();
    $otherStore = Store::factory()->create();
    $readToken = adminTaxSettingsApiToken($store, ['read-settings']);
    $writeToken = adminTaxSettingsApiToken($store, ['write-settings']);
    $otherStoreToken = adminTaxSettingsApiToken($otherStore, ['read-settings']);

    $this->withToken($readToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/tax/settings")
        ->assertOk();

    $this->withToken($readToken['plain_text'])
        ->putJson("/api/admin/v1/stores/{$store->getKey()}/tax/settings", [
            'mode' => 'manual',
            'provider' => 'none',
            'prices_include_tax' => false,
            'config_json' => [],
        ])
        ->assertForbidden();

    $this->withToken($otherStoreToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/tax/settings")
        ->assertForbidden();

    $this->withToken($writeToken['plain_text'])
        ->putJson("/api/admin/v1/stores/{$store->getKey()}/tax/settings", [
            'mode' => 'provider',
            'provider' => 'stripe_tax',
            'prices_include_tax' => false,
            'config_json' => [
                'stripe_tax_settings_id' => 'txr_api',
            ],
        ])
        ->assertOk()
        ->assertJsonPath('data.mode', 'provider')
        ->assertJsonPath('data.provider', 'stripe_tax');
});

test('admin tax settings api validates provider and rate payloads', function (): void {
    $store = adminTaxSettingsApiStore();

    $this->actingAs(adminTaxSettingsApiUser())
        ->putJson("/api/admin/v1/stores/{$store->getKey()}/tax/settings", [
            'mode' => 'provider',
            'prices_include_tax' => false,
            'config_json' => [],
        ])
        ->assertUnprocessable()
        ->assertJsonValidationErrors(['provider']);

    $this->actingAs(adminTaxSettingsApiUser())
        ->putJson("/api/admin/v1/stores/{$store->getKey()}/tax/settings", [
            'mode' => 'manual',
            'provider' => 'none',
            'prices_include_tax' => false,
            'config_json' => [
                'tax_rates' => [
                    [
                        'country_code' => 'DEU',
                        'rate' => 12000,
                        'name' => 'VAT',
                    ],
                ],
            ],
        ])
        ->assertUnprocessable()
        ->assertJsonValidationErrors(['config_json.tax_rates.0.country_code', 'config_json.tax_rates.0.rate']);
});