sed -n '1,260p' app/Http/Middleware/AuthenticateAdminApi.php

<?php

namespace App\Http\Middleware;

use App\Enums\StoreUserRole;
use App\Models\PersonalAccessToken;
use App\Models\Store;
use App\Models\User;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class AuthenticateAdminApi
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next, string ...$abilities): Response
    {
        $store = $this->storeFromRoute($request);

        app()->instance('current_store', $store);

        $user = $request->user();
        $token = $request->attributes->get('sanctum_personal_access_token');

        if (! $user instanceof User || ! $token instanceof PersonalAccessToken) {
            return response()->json(['message' => 'Unauthenticated.'], 401);
        }

        if (! $this->userCanAccessStore($user, $store, $abilities)) {
            return response()->json(['message' => 'Forbidden.'], 403);
        }

        if (! $this->hasAbilities($token, $abilities)) {
            return response()->json(['message' => 'Forbidden.'], 403);
        }

        $token->forceFill(['last_used_at' => now()])->save();

        return $next($request);
    }

    private function storeFromRoute(Request $request): Store
    {
        $store = $request->route('store');

        if ($store instanceof Store) {
            return $store;
        }

        return Store::query()->findOrFail($store);
    }

    /**
     * @param  list<string>  $abilities
     */
    private function hasAbilities(PersonalAccessToken $token, array $abilities): bool
    {
        if ($abilities === []) {
            return true;
        }

        foreach ($abilities as $ability) {
            if (! $token->can($ability)) {
                return false;
            }
        }

        return true;
    }

    /**
     * @param  list<string>  $abilities
     */
    private function userCanAccessStore(User $user, Store $store, array $abilities): bool
    {
        $role = $user->roleForStore($store);

        if (! $role instanceof StoreUserRole) {
            return false;
        }

        if ($abilities === []) {
            return true;
        }

        foreach ($abilities as $ability) {
            if (! $this->roleAllowsAbility($role, $ability)) {
                return false;
            }
        }

        return true;
    }

    private function roleAllowsAbility(StoreUserRole $role, string $ability): bool
    {
        return match ($ability) {
            'read-products',
            'read-orders',
            'read-customers',
            'read-collections',
            'read-discounts' => true,
            'write-products',
            'write-collections',
            'write-discounts',
            'write-content' => in_array($role, [StoreUserRole::Owner, StoreUserRole::Admin, StoreUserRole::Staff], true),
            'write-orders' => in_array($role, [StoreUserRole::Owner, StoreUserRole::Admin, StoreUserRole::Staff], true),
            'read-settings',
            'write-settings',
            'write-themes',
            'read-analytics',
            'manage-platform' => in_array($role, [StoreUserRole::Owner, StoreUserRole::Admin], true),
            default => false,
        };
    }
}


Output:
Chunk ID: 7c5b88
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 840
Output:
<?php

namespace App\Http\Middleware;

use App\Enums\StoreUserRole;
use App\Models\PersonalAccessToken;
use App\Models\Store;
use App\Models\User;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class AuthenticateAdminApi
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next, string ...$abilities): Response
    {
        $store = $this->storeFromRoute($request);

        app()->instance('current_store', $store);

        $user = $request->user();
        $token = $request->attributes->get('sanctum_personal_access_token');

        if (! $user instanceof User || ! $token instanceof PersonalAccessToken) {
            return response()->json(['message' => 'Unauthenticated.'], 401);
        }

        if (! $this->userCanAccessStore($user, $store, $abilities)) {
            return response()->json(['message' => 'Forbidden.'], 403);
        }

        if (! $this->hasAbilities($token, $abilities)) {
            return response()->json(['message' => 'Forbidden.'], 403);
        }

        $token->forceFill(['last_used_at' => now()])->save();

        return $next($request);
    }

    private function storeFromRoute(Request $request): Store
    {
        $store = $request->route('store');

        if ($store instanceof Store) {
            return $store;
        }

        return Store::query()->findOrFail($store);
    }

    /**
     * @param  list<string>  $abilities
     */
    private function hasAbilities(PersonalAccessToken $token, array $abilities): bool
    {
        if ($abilities === []) {
            return true;
        }

        foreach ($abilities as $ability) {
            if (! $token->can($ability)) {
                return false;
            }
        }

        return true;
    }

    /**
     * @param  list<string>  $abilities
     */
    private function userCanAccessStore(User $user, Store $store, array $abilities): bool
    {
        $role = $user->roleForStore($store);

        if (! $role instanceof StoreUserRole) {
            return false;
        }

        if ($abilities === []) {
            return true;
        }

        foreach ($abilities as $ability) {
            if (! $this->roleAllowsAbility($role, $ability)) {
                return false;
            }
        }

        return true;
    }

    private function roleAllowsAbility(StoreUserRole $role, string $ability): bool
    {
        return match ($ability) {
            'read-products',
            'read-orders',
            'read-customers',
            'read-collections',
            'read-discounts' => true,
            'write-products',
            'write-collections',
            'write-discounts',
            'write-content' => in_array($role, [StoreUserRole::Owner, StoreUserRole::Admin, StoreUserRole::Staff], true),
            'write-orders' => in_array($role, [StoreUserRole::Owner, StoreUserRole::Admin, StoreUserRole::Staff], true),
            'read-settings',
            'write-settings',
            'write-themes',
            'read-analytics',
            'manage-platform' => in_array($role, [StoreUserRole::Owner, StoreUserRole::Admin], true),
            default => false,
        };
    }
}


Output:
Chunk ID: d20e26
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 308
Output:
<?php

namespace App\Http\Middleware;

use App\Enums\StoreUserRole;
use App\Models\PersonalAccessToken;
use App\Models\User;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class AuthenticatePlatformApi
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        $user = $request->user();
        $token = $request->attributes->get('sanctum_personal_access_token');

        if (! $user instanceof User || ! $token instanceof PersonalAccessToken) {
            return response()->json(['message' => 'Unauthenticated.'], 401);
        }

        if (! $this->userCanManagePlatform($user) || ! $token->can('manage-platform')) {
            return response()->json(['message' => 'Forbidden.'], 403);
        }

        $token->forceFill(['last_used_at' => now()])->save();

        return $next($request);
    }

    private function userCanManagePlatform(User $user): bool
    {
        return $user->stores()
            ->wherePivot('role', StoreUserRole::Owner->value)
            ->exists();
    }

}


sed -n '1,220p' tests/Feature/Api/AdminCatalogApiTest.php

AdminCatalogApiTest.php

<?php

use App\Enums\MediaStatus;
use App\Enums\ProductStatus;
use App\Models\Collection as ProductCollection;
use App\Models\Customer;
use App\Models\CustomerAddress;
use App\Models\Order;
use App\Models\Product;
use App\Models\ProductMedia;
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 adminCatalogApiStore(): Store
{
    return Store::query()->where('handle', 'acme-fashion')->firstOrFail();
}

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

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

test('admin product api lists and shows store scoped products', function (): void {
    $store = adminCatalogApiStore();
    $product = Product::factory()
        ->withDefaultVariant(3299)
        ->create([
            'store_id' => $store->getKey(),
            'title' => 'Admin API Jacket',
            'handle' => 'admin-api-jacket',
            'vendor' => 'Catalog Test Vendor',
            'tags' => ['outerwear', 'api'],
        ]);
    $variant = ProductVariant::withoutGlobalScopes()
        ->where('product_id', $product->getKey())
        ->firstOrFail();
    Product::factory()
        ->withDefaultVariant()
        ->create([
            'store_id' => Store::factory()->create()->getKey(),
            'title' => 'Other Store Jacket',
        ]);

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

    $this->actingAs(adminCatalogApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/products?query={$variant->sku}&sort=title_asc")
        ->assertOk()
        ->assertJsonPath('data.0.title', 'Admin API Jacket')
        ->assertJsonPath('data.0.variants_count', 1)
        ->assertJsonPath('data.0.total_inventory', 50)
        ->assertJsonMissing(['title' => 'Other Store Jacket']);

    $this->actingAs(adminCatalogApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/products/{$product->getKey()}")
        ->assertOk()
        ->assertJsonPath('data.title', 'Admin API Jacket')
        ->assertJsonPath('data.variants.0.price_amount', 3299)
        ->assertJsonPath('data.variants.0.inventory.quantity_on_hand', 50);
});

test('admin product api creates updates and archives products', function (): void {
    $store = adminCatalogApiStore();
    $collection = ProductCollection::factory()->create([
        'store_id' => $store->getKey(),
        'title' => 'API Summer',
        'handle' => 'api-summer',
    ]);
    $user = adminCatalogApiUser();

    $createResponse = $this->actingAs($user)
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/products", [
            'title' => 'API Cotton T-Shirt',
            'handle' => 'api-cotton-t-shirt',
            'description_html' => '
Soft cotton tee.
',
            'vendor' => 'API Apparel',
            'product_type' => 'Shirts',
            'status' => 'active',
            'tags' => ['organic', 'cotton'],
            'options' => [
                ['name' => 'Color', 'position' => 1],
                ['name' => 'Size', 'position' => 2],
            ],
            'variants' => [
                [
                    'sku' => 'API-TEE-BLU-S',
                    'price_amount' => 2500,
                    'compare_at_amount' => 3000,
                    'is_default' => true,
                    'position' => 1,
                    'option_values' => [
                        ['option_name' => 'Color', 'value' => 'Blue'],
                        ['option_name' => 'Size', 'value' => 'Small'],
                    ],
                    'inventory' => [
                        'quantity_on_hand' => 12,
                        'policy' => 'deny',
                    ],
                ],
                [
                    'sku' => 'API-TEE-BLU-M',
                    'price_amount' => 2600,
                    'is_default' => false,
                    'position' => 2,
                    'option_values' => [
                        ['option_name' => 'Color', 'value' => 'Blue'],
                        ['option_name' => 'Size', 'value' => 'Medium'],
                    ],
                    'inventory' => [
                        'quantity_on_hand' => 8,
                        'policy' => 'continue',
                    ],
                ],
            ],
            'collections' => [$collection->getKey()],
        ])
        ->assertCreated()
        ->assertJsonPath('data.title', 'API Cotton T-Shirt')
        ->assertJsonPath('data.status', 'active')
        ->assertJsonPath('data.variants.0.inventory.quantity_on_hand', 12)
        ->assertJsonPath('data.collections.0.handle', 'api-summer');

    $product = Product::withoutGlobalScopes()
        ->where('store_id', $store->getKey())
        ->where('handle', 'api-cotton-t-shirt')
        ->firstOrFail();
    $defaultVariant = ProductVariant::withoutGlobalScopes()
        ->where('product_id', $product->getKey())
        ->where('is_default', true)
        ->firstOrFail();
    $removedVariant = ProductVariant::withoutGlobalScopes()
        ->where('product_id', $product->getKey())
        ->where('sku', 'API-TEE-BLU-M')
        ->firstOrFail();

    expect($product->status)->toBe(ProductStatus::Active)
        ->and($product->options()->count())->toBe(2)
        ->and($product->variants()->count())->toBe(2);

    $this->actingAs($user)
        ->putJson("/api/admin/v1/stores/{$store->getKey()}/products/{$product->getKey()}", [
            'title' => 'API Cotton T-Shirt Updated',
            'tags' => ['organic', 'bestseller'],
            'variants' => [
                [
                    'id' => $defaultVariant->getKey(),
                    'sku' => 'API-TEE-BLU-S-UPDATED',
                    'price_amount' => 2700,
                    'is_default' => true,
                    'position' => 1,
                    'option_values' => [
                        ['option_name' => 'Color', 'value' => 'Blue'],
                        ['option_name' => 'Size', 'value' => 'Small'],
                    ],
                    'inventory' => [
                        'quantity_on_hand' => 15,
                    ],
                ],
            ],
        ])
        ->assertOk()
        ->assertJsonPath('data.title', 'API Cotton T-Shirt Updated')
        ->assertJsonPath('data.variants.0.sku', 'API-TEE-BLU-S-UPDATED')
        ->assertJsonPath('data.variants.0.inventory.quantity_on_hand', 15);

    $this->assertModelMissing($removedVariant);

    $this->actingAs($user)
        ->deleteJson("/api/admin/v1/stores/{$store->getKey()}/products/{$product->getKey()}")
        ->assertOk()
        ->assertJsonPath('data.status', 'archived');

    expect($product->refresh()->status)->toBe(ProductStatus::Archived);
});

test('admin product api presigns media uploads', function (): void {
    $store = adminCatalogApiStore();
    $product = Product::factory()->withDefaultVariant()->create([
        'store_id' => $store->getKey(),
        'title' => 'API Media Product',
    ]);

    $this->actingAs(adminCatalogApiUser())
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/products/{$product->getKey()}/media/presign-upload", [
            'filename' => 'front.jpg',
            'content_type' => 'image/jpeg',
            'byte_size' => 245000,
        ])
        ->assertCreated()
        ->assertJsonPath('method', 'PUT')
        ->assertJsonPath('headers.Content-Type', 'image/jpeg')
        ->assertJsonPath('storage_key', fn (string $storageKey): bool => str_starts_with($storageKey, "media/originals/{$product->getKey()}/"))
        ->assertJsonPath('upload_url', fn (string $uploadUrl): bool => $uploadUrl !== '');

    $media = ProductMedia::withoutGlobalScopes()
        ->where('product_id', $product->getKey())
        ->firstOrFail();


Output:
Chunk ID: 02ac50
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 2088
Output:
<?php

use App\Enums\MediaStatus;
use App\Enums\ProductStatus;
use App\Models\Collection as ProductCollection;
use App\Models\Customer;
use App\Models\CustomerAddress;
use App\Models\Order;
use App\Models\Product;
use App\Models\ProductMedia;
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 adminCatalogApiStore(): Store
{
    return Store::query()->where('handle', 'acme-fashion')->firstOrFail();
}

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

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

test('admin product api lists and shows store scoped products', function (): void {
    $store = adminCatalogApiStore();
    $product = Product::factory()
        ->withDefaultVariant(3299)
        ->create([
            'store_id' => $store->getKey(),
            'title' => 'Admin API Jacket',
            'handle' => 'admin-api-jacket',
            'vendor' => 'Catalog Test Vendor',
            'tags' => ['outerwear', 'api'],
        ]);
    $variant = ProductVariant::withoutGlobalScopes()
        ->where('product_id', $product->getKey())
        ->firstOrFail();
    Product::factory()
        ->withDefaultVariant()
        ->create([
            'store_id' => Store::factory()->create()->getKey(),
            'title' => 'Other Store Jacket',
        ]);

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

    $this->actingAs(adminCatalogApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/products?query={$variant->sku}&sort=title_asc")
        ->assertOk()
        ->assertJsonPath('data.0.title', 'Admin API Jacket')
        ->assertJsonPath('data.0.variants_count', 1)
        ->assertJsonPath('data.0.total_inventory', 50)
        ->assertJsonMissing(['title' => 'Other Store Jacket']);

    $this->actingAs(adminCatalogApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/products/{$product->getKey()}")
        ->assertOk()
        ->assertJsonPath('data.title', 'Admin API Jacket')
        ->assertJsonPath('data.variants.0.price_amount', 3299)
        ->assertJsonPath('data.variants.0.inventory.quantity_on_hand', 50);
});

test('admin product api creates updates and archives products', function (): void {
    $store = adminCatalogApiStore();
    $collection = ProductCollection::factory()->create([
        'store_id' => $store->getKey(),
        'title' => 'API Summer',
        'handle' => 'api-summer',
    ]);
    $user = adminCatalogApiUser();

    $createResponse = $this->actingAs($user)
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/products", [
            'title' => 'API Cotton T-Shirt',
            'handle' => 'api-cotton-t-shirt',
            'description_html' => '
Soft cotton tee.
',
            'vendor' => 'API Apparel',
            'product_type' => 'Shirts',
            'status' => 'active',
            'tags' => ['organic', 'cotton'],
            'options' => [
                ['name' => 'Color', 'position' => 1],
                ['name' => 'Size', 'position' => 2],
            ],
            'variants' => [
                [
                    'sku' => 'API-TEE-BLU-S',
                    'price_amount' => 2500,
                    'compare_at_amount' => 3000,
                    'is_default' => true,
                    'position' => 1,
                    'option_values' => [
                        ['option_name' => 'Color', 'value' => 'Blue'],
                        ['option_name' => 'Size', 'value' => 'Small'],
                    ],
                    'inventory' => [
                        'quantity_on_hand' => 12,
                        'policy' => 'deny',
                    ],
                ],
                [
                    'sku' => 'API-TEE-BLU-M',
                    'price_amount' => 2600,
                    'is_default' => false,
                    'position' => 2,
                    'option_values' => [
                        ['option_name' => 'Color', 'value' => 'Blue'],
                        ['option_name' => 'Size', 'value' => 'Medium'],
                    ],
                    'inventory' => [
                        'quantity_on_hand' => 8,
                        'policy' => 'continue',
                    ],
                ],
            ],
            'collections' => [$collection->getKey()],
        ])
        ->assertCreated()
        ->assertJsonPath('data.title', 'API Cotton T-Shirt')
        ->assertJsonPath('data.status', 'active')
        ->assertJsonPath('data.variants.0.inventory.quantity_on_hand', 12)
        ->assertJsonPath('data.collections.0.handle', 'api-summer');

    $product = Product::withoutGlobalScopes()
        ->where('store_id', $store->getKey())
        ->where('handle', 'api-cotton-t-shirt')
        ->firstOrFail();
    $defaultVariant = ProductVariant::withoutGlobalScopes()
        ->where('product_id', $product->getKey())
        ->where('is_default', true)
        ->firstOrFail();
    $removedVariant = ProductVariant::withoutGlobalScopes()
        ->where('product_id', $product->getKey())
        ->where('sku', 'API-TEE-BLU-M')
        ->firstOrFail();

    expect($product->status)->toBe(ProductStatus::Active)
        ->and($product->options()->count())->toBe(2)
        ->and($product->variants()->count())->toBe(2);

    $this->actingAs($user)
        ->putJson("/api/admin/v1/stores/{$store->getKey()}/products/{$product->getKey()}", [
            'title' => 'API Cotton T-Shirt Updated',
            'tags' => ['organic', 'bestseller'],
            'variants' => [
                [
                    'id' => $defaultVariant->getKey(),
                    'sku' => 'API-TEE-BLU-S-UPDATED',
                    'price_amount' => 2700,
                    'is_default' => true,
                    'position' => 1,
                    'option_values' => [
                        ['option_name' => 'Color', 'value' => 'Blue'],
                        ['option_name' => 'Size', 'value' => 'Small'],
                    ],
                    'inventory' => [
                        'quantity_on_hand' => 15,
                    ],
                ],
            ],
        ])
        ->assertOk()
        ->assertJsonPath('data.title', 'API Cotton T-Shirt Updated')
        ->assertJsonPath('data.variants.0.sku', 'API-TEE-BLU-S-UPDATED')
        ->assertJsonPath('data.variants.0.inventory.quantity_on_hand', 15);

    $this->assertModelMissing($removedVariant);

    $this->actingAs($user)
        ->deleteJson("/api/admin/v1/stores/{$store->getKey()}/products/{$product->getKey()}")
        ->assertOk()
        ->assertJsonPath('data.status', 'archived');

    expect($product->refresh()->status)->toBe(ProductStatus::Archived);
});

test('admin product api presigns media uploads', function (): void {
    $store = adminCatalogApiStore();
    $product = Product::factory()->withDefaultVariant()->create([
        'store_id' => $store->getKey(),
        'title' => 'API Media Product',
    ]);

    $this->actingAs(adminCatalogApiUser())
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/products/{$product->getKey()}/media/presign-upload", [
            'filename' => 'front.jpg',
            'content_type' => 'image/jpeg',
            'byte_size' => 245000,
        ])
        ->assertCreated()
        ->assertJsonPath('method', 'PUT')
        ->assertJsonPath('headers.Content-Type', 'image/jpeg')
        ->assertJsonPath('storage_key', fn (string $storageKey): bool => str_starts_with($storageKey, "media/originals/{$product->getKey()}/"))
        ->assertJsonPath('upload_url', fn (string $uploadUrl): bool => $uploadUrl !== '');

    $media = ProductMedia::withoutGlobalScopes()
        ->where('product_id', $product->getKey())
        ->firstOrFail();


sed -n '1,220p' tests/Feature/Api/AdminPlatformApiTest.php

AdminPlatformApiTest.php

<?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;
use Illuminate\Support\Facades\DB;

uses(RefreshDatabase::class);

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

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

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

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

test('platform api creates organizations and stores for owner sessions', function (): void {
    $user = adminPlatformApiUser();

    $organizationResponse = $this->actingAs($user)
        ->postJson('/api/admin/v1/platform/organizations', [
            'name' => 'Platform API Org',
            'billing_email' => 'billing@platform-api.test',
        ])
        ->assertCreated()
        ->assertJsonPath('data.name', 'Platform API Org')
        ->assertJsonPath('data.billing_email', 'billing@platform-api.test');

    $organizationId = $organizationResponse->json('data.id');

    $this->actingAs($user)
        ->postJson('/api/admin/v1/platform/stores', [
            'organization_id' => $organizationId,
            'name' => 'Platform API Store',
            'handle' => 'platform-api-store',
            'default_currency' => 'EUR',
            'default_locale' => 'en',
            'timezone' => 'Europe/Berlin',
        ])
        ->assertCreated()
        ->assertJsonPath('data.name', 'Platform API Store')
        ->assertJsonPath('data.handle', 'platform-api-store')
        ->assertJsonPath('data.status', 'active');

    $store = Store::query()->where('handle', 'platform-api-store')->firstOrFail();

    expect(StoreSettings::query()->whereKey($store->getKey())->exists())->toBeTrue()
        ->and(DB::table('store_users')
            ->where('store_id', $store->getKey())
            ->where('user_id', $user->getKey())
            ->where('role', 'owner')
            ->exists())->toBeTrue();
});

test('platform api rejects non owner sessions and tokens without manage platform ability', function (): void {
    $store = adminPlatformApiStore();
    $staff = User::factory()->create([
        'email' => 'platform-staff@example.test',
    ]);
    DB::table('store_users')->insert([
        'store_id' => $store->getKey(),
        'user_id' => $staff->getKey(),
        'role' => 'staff',
        'created_at' => now(),
    ]);
    $readToken = adminPlatformApiToken($store, ['read-products']);

    $this->actingAs($staff)
        ->postJson('/api/admin/v1/platform/organizations', [
            'name' => 'Forbidden Org',
            'billing_email' => 'forbidden@example.test',
        ])
        ->assertForbidden();

    $this->withToken($readToken['plain_text'])
        ->postJson('/api/admin/v1/platform/organizations', [
            'name' => 'Forbidden Token Org',
            'billing_email' => 'forbidden-token@example.test',
        ])
        ->assertForbidden();
});

test('platform api accepts manage platform bearer tokens', function (): void {
    $store = adminPlatformApiStore();
    $token = adminPlatformApiToken($store, ['manage-platform']);

    $this->withToken($token['plain_text'])
        ->postJson('/api/admin/v1/platform/organizations', [
            'name' => 'Token Platform Org',
            'billing_email' => 'token-platform@example.test',
        ])
        ->assertCreated()
        ->assertJsonPath('data.name', 'Token Platform Org');

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

test('store me and invite endpoints expose membership and enforce role or ability', function (): void {
    $store = adminPlatformApiStore();
    $user = adminPlatformApiUser();
    $staff = User::factory()->create([
        'email' => 'store-staff@example.test',
    ]);
    DB::table('store_users')->insert([
        'store_id' => $store->getKey(),
        'user_id' => $staff->getKey(),
        'role' => 'staff',
        'created_at' => now(),
    ]);
    $readToken = adminPlatformApiToken($store, ['read-products']);
    $manageToken = adminPlatformApiToken($store, ['manage-platform']);

    $this->withToken($readToken['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/invites", [
            'email' => 'new-staff@example.test',
            'role' => 'staff',
        ])
        ->assertForbidden();

    $this->withToken($manageToken['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/invites", [
            'email' => 'new-staff@example.test',
            'role' => 'staff',
        ])
        ->assertCreated()
        ->assertJsonPath('data.email', 'new-staff@example.test')
        ->assertJsonPath('data.role', 'staff');

    $this->actingAs($user)
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/me")
        ->assertOk()
        ->assertJsonPath('data.email', 'admin@acme.test')
        ->assertJsonPath('data.role', 'owner')
        ->assertJsonPath('data.permissions.1', 'read-products');

    $this->actingAs($staff)
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/invites", [
            'email' => 'new-staff@example.test',
            'role' => 'staff',
        ])
        ->assertForbidden();

    $this->actingAs($user)
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/invites", [
            'email' => 'admin@acme.test',
            'role' => 'admin',
        ])
        ->assertStatus(409);
});


Output:
Chunk ID: 7f0401
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1469
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;
use Illuminate\Support\Facades\DB;

uses(RefreshDatabase::class);

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

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

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

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

test('platform api creates organizations and stores for owner sessions', function (): void {
    $user = adminPlatformApiUser();

    $organizationResponse = $this->actingAs($user)
        ->postJson('/api/admin/v1/platform/organizations', [
            'name' => 'Platform API Org',
            'billing_email' => 'billing@platform-api.test',
        ])
        ->assertCreated()
        ->assertJsonPath('data.name', 'Platform API Org')
        ->assertJsonPath('data.billing_email', 'billing@platform-api.test');

    $organizationId = $organizationResponse->json('data.id');

    $this->actingAs($user)
        ->postJson('/api/admin/v1/platform/stores', [
            'organization_id' => $organizationId,
            'name' => 'Platform API Store',
            'handle' => 'platform-api-store',
            'default_currency' => 'EUR',
            'default_locale' => 'en',
            'timezone' => 'Europe/Berlin',
        ])
        ->assertCreated()
        ->assertJsonPath('data.name', 'Platform API Store')
        ->assertJsonPath('data.handle', 'platform-api-store')
        ->assertJsonPath('data.status', 'active');

    $store = Store::query()->where('handle', 'platform-api-store')->firstOrFail();

    expect(StoreSettings::query()->whereKey($store->getKey())->exists())->toBeTrue()
        ->and(DB::table('store_users')
            ->where('store_id', $store->getKey())
            ->where('user_id', $user->getKey())
            ->where('role', 'owner')
            ->exists())->toBeTrue();
});

test('platform api rejects non owner sessions and tokens without manage platform ability', function (): void {
    $store = adminPlatformApiStore();
    $staff = User::factory()->create([
        'email' => 'platform-staff@example.test',
    ]);
    DB::table('store_users')->insert([
        'store_id' => $store->getKey(),
        'user_id' => $staff->getKey(),
        'role' => 'staff',
        'created_at' => now(),
    ]);
    $readToken = adminPlatformApiToken($store, ['read-products']);

    $this->actingAs($staff)
        ->postJson('/api/admin/v1/platform/organizations', [
            'name' => 'Forbidden Org',
            'billing_email' => 'forbidden@example.test',
        ])
        ->assertForbidden();

    $this->withToken($readToken['plain_text'])
        ->postJson('/api/admin/v1/platform/organizations', [
            'name' => 'Forbidden Token Org',
            'billing_email' => 'forbidden-token@example.test',
        ])
        ->assertForbidden();
});

test('platform api accepts manage platform bearer tokens', function (): void {
    $store = adminPlatformApiStore();
    $token = adminPlatformApiToken($store, ['manage-platform']);

    $this->withToken($token['plain_text'])
        ->postJson('/api/admin/v1/platform/organizations', [
            'name' => 'Token Platform Org',
            'billing_email' => 'token-platform@example.test',
        ])
        ->assertCreated()
        ->assertJsonPath('data.name', 'Token Platform Org');

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

test('store me and invite endpoints expose membership and enforce role or ability', function (): void {
    $store = adminPlatformApiStore();
    $user = adminPlatformApiUser();
    $staff = User::factory()->create([
        'email' => 'store-staff@example.test',
    ]);
    DB::table('store_users')->insert([
        'store_id' => $store->getKey(),
        'user_id' => $staff->getKey(),
        'role' => 'staff',
        'created_at' => now(),
    ]);
    $readToken = adminPlatformApiToken($store, ['read-products']);
    $manageToken = adminPlatformApiToken($store, ['manage-platform']);

    $this->withToken($readToken['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/invites", [
            'email' => 'new-staff@example.test',
            'role' => 'staff',
        ])
        ->assertForbidden();

    $this->withToken($manageToken['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/invites", [
            'email' => 'new-staff@example.test',
            'role' => 'staff',
        ])
        ->assertCreated()
        ->assertJsonPath('data.email', 'new-staff@example.test')
        ->assertJsonPath('data.role', 'staff');

    $this->actingAs($user)
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/me")
        ->assertOk()
        ->assertJsonPath('data.email', 'admin@acme.test')
        ->assertJsonPath('data.role', 'owner')
        ->assertJsonPath('data.permissions.1', 'read-products');

    $this->actingAs($staff)
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/invites", [
            'email' => 'new-staff@example.test',
            'role' => 'staff',
        ])
        ->assertForbidden();

    $this->actingAs($user)
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/invites", [
            'email' => 'admin@acme.test',
            'role' => 'admin',
        ])
        ->assertStatus(409);
});


sed -n '1,260p' tests/Feature/Api/AdminApiHelpers.php

AdminApiHelpers.php

sed: tests/Feature/Api/AdminApiHelpers.php: No such file or directory


Output:
Chunk ID: 959a89
Wall time: 0.0000 seconds
Process exited with code 1
Original token count: 18
Output:
sed: tests/Feature/Api/AdminApiHelpers.php: No such file or directory


sed -n '1,240p' app/Providers/AppServiceProvider.php

AppServiceProvider.php

<?php

namespace App\Providers;

use App\Auth\CustomerUserProvider;
use App\Contracts\PaymentProvider;
use App\Events\CheckoutCompleted;
use App\Events\FulfillmentCreated;
use App\Events\FulfillmentDelivered;
use App\Events\FulfillmentShipped;
use App\Events\OrderCancelled;
use App\Events\OrderCreated;
use App\Events\OrderPaid;
use App\Events\OrderRefunded;
use App\Events\ProductCreated;
use App\Events\ProductDeleted;
use App\Events\ProductUpdated;
use App\Http\Middleware\CheckStoreRole;
use App\Http\Middleware\EnsureUserEmailIsVerified;
use App\Http\Middleware\ResolveStore;
use App\Listeners\DispatchWebhooks;
use App\Models\PersonalAccessToken;
use App\Models\Product;
use App\Models\Store;
use App\Models\User;
use App\Observers\ProductObserver;
use App\Services\AnalyticsService;
use App\Services\NavigationService;
use App\Services\Payments\MockPaymentProvider;
use App\Services\SearchService;
use App\Services\ThemeSettingsService;
use App\Services\WebhookService;
use Carbon\CarbonImmutable;
use Illuminate\Auth\Middleware\Authenticate;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Rules\Password;
use Illuminate\View\View as ViewInstance;
use Livewire\Livewire;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        $this->app->bind(PaymentProvider::class, MockPaymentProvider::class);

        Auth::provider('store_scoped_eloquent', function ($app, array $config): CustomerUserProvider {
            return new CustomerUserProvider($app['hash'], $config['model']);
        });

        Auth::viaRequest('sanctum-compatible', function (Request $request): ?User {
            app()->forgetInstance('sanctum_personal_access_token');

            $plainTextToken = $request->bearerToken();

            if (! is_string($plainTextToken) || $plainTextToken === '') {
                return null;
            }

            $token = PersonalAccessToken::query()
                ->with('tokenable')
                ->where('token', hash('sha256', $this->plainTokenForHashing($plainTextToken)))
                ->first();

            if (! $token instanceof PersonalAccessToken || $token->isExpired() || ! $token->tokenable instanceof User) {
                return null;
            }

            $request->attributes->set('sanctum_personal_access_token', $token);
            app()->instance('sanctum_personal_access_token', $token);

            return $token->tokenable;
        });

        $this->app->singleton(ThemeSettingsService::class);
        $this->app->singleton(NavigationService::class);
        $this->app->singleton(SearchService::class);
        $this->app->singleton(AnalyticsService::class);
        $this->app->singleton(WebhookService::class);
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        $this->configureDefaults();
        $this->configureEventListeners();
        $this->configureLivewireMiddleware();
        $this->configureStorefrontViewData();
    }

    /**
     * Configure default behaviors for production-ready applications.
     */
    protected function configureDefaults(): void
    {
        Date::use(CarbonImmutable::class);

        DB::prohibitDestructiveCommands(
            app()->isProduction(),
        );

        Password::defaults(fn (): ?Password => app()->isProduction()
            ? Password::min(12)
                ->mixedCase()
                ->letters()
                ->numbers()
                ->symbols()
                ->uncompromised()
            : null
        );

        RateLimiter::for('api.storefront', function (Request $request): Limit {
            return Limit::perMinute(120)->by($request->ip());
        });

        RateLimiter::for('checkout', function (Request $request): Limit {
            $sessionId = $request->hasSession() ? $request->session()->getId() : null;

            return Limit::perMinute(10)->by($sessionId ?: $request->ip());
        });

        RateLimiter::for('search', function (Request $request): Limit {
            return Limit::perMinute(30)->by($request->ip());
        });

        RateLimiter::for('analytics', function (Request $request): Limit {
            return Limit::perMinute(60)->by($request->ip());
        });

        RateLimiter::for('webhooks', function (Request $request): Limit {
            return Limit::perMinute(100)->by($request->ip());
        });

        Product::observe(ProductObserver::class);

        Authenticate::redirectUsing(function (Request $request): string {
            if ($request->is('admin*')) {
                return route('admin.login');
            }

            if ($request->is('account*')) {
                return route('account.login');
            }

            return route('login');
        });
    }

    protected function configureStorefrontViewData(): void
    {
        View::composer('layouts.storefront', function (ViewInstance $view): void {
            $store = app()->bound('current_store') ? app('current_store') : null;

            if (! $store instanceof Store) {
                $view->with([
                    'themeSettings' => [],
                    'mainNavigation' => [],
                    'footerNavigation' => [],
                ]);

                return;
            }

            $themeSettings = app(ThemeSettingsService::class)->forStore($store);
            $navigation = app(NavigationService::class);

            $view->with([
                'themeSettings' => $themeSettings,
                'mainNavigation' => $navigation->forHandle($store, data_get($themeSettings, 'header.main_menu', 'main-menu')),
                'footerNavigation' => $navigation->forHandle($store, data_get($themeSettings, 'footer.menu', 'footer-menu')),
            ]);
        });
    }

    protected function configureEventListeners(): void
    {
        foreach ([
            OrderCreated::class,
            OrderPaid::class,
            OrderCancelled::class,
            OrderRefunded::class,
            CheckoutCompleted::class,
            FulfillmentCreated::class,
            FulfillmentShipped::class,
            FulfillmentDelivered::class,
            ProductCreated::class,
            ProductUpdated::class,
            ProductDeleted::class,
        ] as $event) {
            Event::listen($event, DispatchWebhooks::class);
        }
    }

    protected function configureLivewireMiddleware(): void
    {
        Livewire::addPersistentMiddleware([
            EnsureUserEmailIsVerified::class,
            ResolveStore::class,
            CheckStoreRole::class,
        ]);
    }

    private function plainTokenForHashing(string $plainTextToken): string
    {
        if (str_contains($plainTextToken, '|')) {
            return (string) str($plainTextToken)->after('|');
        }

        return $plainTextToken;
    }
}


Output:
Chunk ID: 5ab3c5
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1810
Output:
<?php

namespace App\Providers;

use App\Auth\CustomerUserProvider;
use App\Contracts\PaymentProvider;
use App\Events\CheckoutCompleted;
use App\Events\FulfillmentCreated;
use App\Events\FulfillmentDelivered;
use App\Events\FulfillmentShipped;
use App\Events\OrderCancelled;
use App\Events\OrderCreated;
use App\Events\OrderPaid;
use App\Events\OrderRefunded;
use App\Events\ProductCreated;
use App\Events\ProductDeleted;
use App\Events\ProductUpdated;
use App\Http\Middleware\CheckStoreRole;
use App\Http\Middleware\EnsureUserEmailIsVerified;
use App\Http\Middleware\ResolveStore;
use App\Listeners\DispatchWebhooks;
use App\Models\PersonalAccessToken;
use App\Models\Product;
use App\Models\Store;
use App\Models\User;
use App\Observers\ProductObserver;
use App\Services\AnalyticsService;
use App\Services\NavigationService;
use App\Services\Payments\MockPaymentProvider;
use App\Services\SearchService;
use App\Services\ThemeSettingsService;
use App\Services\WebhookService;
use Carbon\CarbonImmutable;
use Illuminate\Auth\Middleware\Authenticate;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Rules\Password;
use Illuminate\View\View as ViewInstance;
use Livewire\Livewire;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        $this->app->bind(PaymentProvider::class, MockPaymentProvider::class);

        Auth::provider('store_scoped_eloquent', function ($app, array $config): CustomerUserProvider {
            return new CustomerUserProvider($app['hash'], $config['model']);
        });

        Auth::viaRequest('sanctum-compatible', function (Request $request): ?User {
            app()->forgetInstance('sanctum_personal_access_token');

            $plainTextToken = $request->bearerToken();

            if (! is_string($plainTextToken) || $plainTextToken === '') {
                return null;
            }

            $token = PersonalAccessToken::query()
                ->with('tokenable')
                ->where('token', hash('sha256', $this->plainTokenForHashing($plainTextToken)))
                ->first();

            if (! $token instanceof PersonalAccessToken || $token->isExpired() || ! $token->tokenable instanceof User) {
                return null;
            }

            $request->attributes->set('sanctum_personal_access_token', $token);
            app()->instance('sanctum_personal_access_token', $token);

            return $token->tokenable;
        });

        $this->app->singleton(ThemeSettingsService::class);
        $this->app->singleton(NavigationService::class);
        $this->app->singleton(SearchService::class);
        $this->app->singleton(AnalyticsService::class);
        $this->app->singleton(WebhookService::class);
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        $this->configureDefaults();
        $this->configureEventListeners();
        $this->configureLivewireMiddleware();
        $this->configureStorefrontViewData();
    }

    /**
     * Configure default behaviors for production-ready applications.
     */
    protected function configureDefaults(): void
    {
        Date::use(CarbonImmutable::class);

        DB::prohibitDestructiveCommands(
            app()->isProduction(),
        );

        Password::defaults(fn (): ?Password => app()->isProduction()
            ? Password::min(12)
                ->mixedCase()
                ->letters()
                ->numbers()
                ->symbols()
                ->uncompromised()
            : null
        );

        RateLimiter::for('api.storefront', function (Request $request): Limit {
            return Limit::perMinute(120)->by($request->ip());
        });

        RateLimiter::for('checkout', function (Request $request): Limit {
            $sessionId = $request->hasSession() ? $request->session()->getId() : null;

            return Limit::perMinute(10)->by($sessionId ?: $request->ip());
        });

        RateLimiter::for('search', function (Request $request): Limit {
            return Limit::perMinute(30)->by($request->ip());
        });

        RateLimiter::for('analytics', function (Request $request): Limit {
            return Limit::perMinute(60)->by($request->ip());
        });

        RateLimiter::for('webhooks', function (Request $request): Limit {
            return Limit::perMinute(100)->by($request->ip());
        });

        Product::observe(ProductObserver::class);

        Authenticate::redirectUsing(function (Request $request): string {
            if ($request->is('admin*')) {
                return route('admin.login');
            }

            if ($request->is('account*')) {
                return route('account.login');
            }

            return route('login');
        });
    }

    protected function configureStorefrontViewData(): void
    {
        View::composer('layouts.storefront', function (ViewInstance $view): void {
            $store = app()->bound('current_store') ? app('current_store') : null;

            if (! $store instanceof Store) {
                $view->with([
                    'themeSettings' => [],
                    'mainNavigation' => [],
                    'footerNavigation' => [],
                ]);

                return;
            }

            $themeSettings = app(ThemeSettingsService::class)->forStore($store);
            $navigation = app(NavigationService::class);

            $view->with([
                'themeSettings' => $themeSettings,
                'mainNavigation' => $navigation->forHandle($store, data_get($themeSettings, 'header.main_menu', 'main-menu')),
                'footerNavigation' => $navigation->forHandle($store, data_get($themeSettings, 'footer.menu', 'footer-menu')),
            ]);
        });
    }

    protected function configureEventListeners(): void
    {
        foreach ([
            OrderCreated::class,
            OrderPaid::class,
            OrderCancelled::class,
            OrderRefunded::class,
            CheckoutCompleted::class,
            FulfillmentCreated::class,
            FulfillmentShipped::class,
            FulfillmentDelivered::class,
            ProductCreated::class,
            ProductUpdated::class,
            ProductDeleted::class,
        ] as $event) {
            Event::listen($event, DispatchWebhooks::class);
        }
    }

    protected function configureLivewireMiddleware(): void
    {
        Livewire::addPersistentMiddleware([
            EnsureUserEmailIsVerified::class,
            ResolveStore::class,
            CheckStoreRole::class,
        ]);
    }

    private function plainTokenForHashing(string $plainTextToken): string
    {
        if (str_contains($plainTextToken, '|')) {
            return (string) str($plainTextToken)->after('|');
        }

        return $plainTextToken;
    }
}


sed -n '220,520p' tests/Feature/Api/AdminCatalogApiTest.php


rg -n "actingAs\(|withToken\(|adminCatalogApiToken|adminPlatformApiToken|OauthToken|personal_access_tokens|oauth_tokens" tests/Feature/Api app tests/Feature/Admin tests/Feature/Checkout


sed -n '1,260p' app/Http/Requests/Api/Admin/StoreProductRequest.php


sed -n '1,260p' app/Http/Requests/Api/Admin/UpdateProductRequest.php


rg -n "class .*Request|function authorize" app/Http/Requests/Api/Admin app/Http/Controllers/Api/Admin/V1


/bin/zsh
-lc
sed -n '1,300p' routes/api.php

<?php

use App\Http\Controllers\Api\Admin\V1\AnalyticsSummaryController as AdminAnalyticsSummaryController;
use App\Http\Controllers\Api\Admin\V1\CollectionController as AdminCollectionController;
use App\Http\Controllers\Api\Admin\V1\CustomerController as AdminCustomerController;
use App\Http\Controllers\Api\Admin\V1\DiscountController as AdminDiscountController;
use App\Http\Controllers\Api\Admin\V1\OrderController as AdminOrderController;
use App\Http\Controllers\Api\Admin\V1\OrderExportController as AdminOrderExportController;
use App\Http\Controllers\Api\Admin\V1\OrderFulfillmentController as AdminOrderFulfillmentController;
use App\Http\Controllers\Api\Admin\V1\OrderRefundController as AdminOrderRefundController;
use App\Http\Controllers\Api\Admin\V1\PageController as AdminPageController;
use App\Http\Controllers\Api\Admin\V1\PlatformOrganizationController as AdminPlatformOrganizationController;
use App\Http\Controllers\Api\Admin\V1\PlatformStoreController as AdminPlatformStoreController;
use App\Http\Controllers\Api\Admin\V1\ProductController as AdminProductController;
use App\Http\Controllers\Api\Admin\V1\SearchIndexController as AdminSearchIndexController;
use App\Http\Controllers\Api\Admin\V1\ShippingRateController as AdminShippingRateController;
use App\Http\Controllers\Api\Admin\V1\ShippingZoneController as AdminShippingZoneController;
use App\Http\Controllers\Api\Admin\V1\StoreInviteController as AdminStoreInviteController;
use App\Http\Controllers\Api\Admin\V1\StoreMembershipController as AdminStoreMembershipController;
use App\Http\Controllers\Api\Admin\V1\StoreSettingsController as AdminStoreSettingsController;
use App\Http\Controllers\Api\Admin\V1\TaxSettingsController as AdminTaxSettingsController;
use App\Http\Controllers\Api\Admin\V1\ThemeController as AdminThemeController;
use App\Http\Controllers\Api\Admin\V1\ThemeSettingsController as AdminThemeSettingsController;
use App\Http\Controllers\Api\Apps\V1\DeferredEndpointController as DeferredAppEndpointController;
use App\Http\Controllers\Api\Storefront\V1\AnalyticsEventController as StorefrontAnalyticsEventController;
use App\Http\Controllers\Api\Storefront\V1\CartController;
use App\Http\Controllers\Api\Storefront\V1\CartLineController;
use App\Http\Controllers\Api\Storefront\V1\CheckoutController;
use App\Http\Controllers\Api\Storefront\V1\OrderController as StorefrontOrderController;
use App\Http\Controllers\Api\Storefront\V1\SearchController as StorefrontSearchController;
use Illuminate\Support\Facades\Route;

Route::middleware('store.resolve')
    ->prefix('storefront/v1')
    ->name('api.storefront.v1.')
    ->group(function (): void {
        Route::middleware('throttle:api.storefront')->group(function (): void {
            Route::post('carts', [CartController::class, 'store'])->name('carts.store');
            Route::get('carts/{cart}', [CartController::class, 'show'])->name('carts.show');
            Route::post('carts/{cart}/lines', [CartLineController::class, 'store'])->name('carts.lines.store');
            Route::put('carts/{cart}/lines/{cartLine}', [CartLineController::class, 'update'])->name('carts.lines.update');
            Route::delete('carts/{cart}/lines/{cartLine}', [CartLineController::class, 'destroy'])->name('carts.lines.destroy');
        });

        Route::middleware('throttle:search')->group(function (): void {
            Route::get('search', [StorefrontSearchController::class, 'index'])->name('search.index');
            Route::get('search/suggest', [StorefrontSearchController::class, 'suggest'])->name('search.suggest');
        });

        Route::middleware('throttle:analytics')->group(function (): void {
            Route::post('analytics/events', [StorefrontAnalyticsEventController::class, 'store'])->name('analytics.events.store');
        });

        Route::middleware('throttle:checkout')->group(function (): void {
            Route::post('checkouts', [CheckoutController::class, 'store'])->name('checkouts.store');
            Route::get('checkouts/{checkout}', [CheckoutController::class, 'show'])->name('checkouts.show');
            Route::put('checkouts/{checkout}/address', [CheckoutController::class, 'address'])->name('checkouts.address');
            Route::put('checkouts/{checkout}/shipping-method', [CheckoutController::class, 'shippingMethod'])->name('checkouts.shipping-method');
            Route::post('checkouts/{checkout}/apply-discount', [CheckoutController::class, 'applyDiscount'])->name('checkouts.apply-discount');
            Route::delete('checkouts/{checkout}/discount', [CheckoutController::class, 'destroyDiscount'])->name('checkouts.discount.destroy');
            Route::put('checkouts/{checkout}/payment-method', [CheckoutController::class, 'paymentMethod'])->name('checkouts.payment-method');
            Route::post('checkouts/{checkout}/pay', [CheckoutController::class, 'pay'])->name('checkouts.pay');
            Route::get('orders/{orderNumber}', [StorefrontOrderController::class, 'sh…2468 chars truncated…pi:write-collections')->group(function (): void {
            Route::post('collections', [AdminCollectionController::class, 'store'])->name('collections.store');
            Route::put('collections/{collection}', [AdminCollectionController::class, 'update'])->name('collections.update');
            Route::delete('collections/{collection}', [AdminCollectionController::class, 'destroy'])->name('collections.destroy');
        });

        Route::middleware('admin.api:read-discounts')->group(function (): void {
            Route::get('discounts', [AdminDiscountController::class, 'index'])->name('discounts.index');
        });

        Route::middleware('admin.api:write-discounts')->group(function (): void {
            Route::post('discounts', [AdminDiscountController::class, 'store'])->name('discounts.store');
            Route::put('discounts/{discount}', [AdminDiscountController::class, 'update'])->name('discounts.update');
            Route::delete('discounts/{discount}', [AdminDiscountController::class, 'destroy'])->name('discounts.destroy');
        });

        Route::middleware('admin.api:read-content')->group(function (): void {
            Route::get('pages', [AdminPageController::class, 'index'])->name('pages.index');
        });

        Route::middleware('admin.api:write-content')->group(function (): void {
            Route::post('pages', [AdminPageController::class, 'store'])->name('pages.store');
            Route::put('pages/{page}', [AdminPageController::class, 'update'])->name('pages.update');
            Route::delete('pages/{page}', [AdminPageController::class, 'destroy'])->name('pages.destroy');
        });

        Route::middleware('admin.api:read-settings')->group(function (): void {
            Route::get('search/status', [AdminSearchIndexController::class, 'status'])->name('search.status');
            Route::get('settings', [AdminStoreSettingsController::class, 'show'])->name('settings.show');
            Route::get('shipping/zones', [AdminShippingZoneController::class, 'index'])->name('shipping.zones.index');
            Route::get('tax/settings', [AdminTaxSettingsController::class, 'show'])->name('tax.settings.show');
        });

        Route::middleware('admin.api:write-settings')->group(function (): void {
            Route::post('search/reindex', [AdminSearchIndexController::class, 'reindex'])->name('search.reindex');
            Route::put('settings', [AdminStoreSettingsController::class, 'update'])->name('settings.update');
            Route::post('shipping/zones', [AdminShippingZoneController::class, 'store'])->name('shipping.zones.store');
            Route::put('shipping/zones/{shippingZone}', [AdminShippingZoneController::class, 'update'])->name('shipping.zones.update');
            Route::post('shipping/zones/{shippingZone}/rates', [AdminShippingRateController::class, 'store'])->name('shipping.zones.rates.store');
            Route::put('tax/settings', [AdminTaxSettingsController::class, 'update'])->name('tax.settings.update');
        });

        Route::middleware('admin.api:write-themes')->group(function (): void {
            Route::post('themes', [AdminThemeController::class, 'store'])->name('themes.store');
            Route::post('themes/{theme}/publish', [AdminThemeController::class, 'publish'])->name('themes.publish');
            Route::put('themes/{theme}/settings', [AdminThemeSettingsController::class, 'update'])->name('themes.settings.update');
        });

        Route::middleware('admin.api:read-analytics')->group(function (): void {
            Route::get('analytics/summary', [AdminAnalyticsSummaryController::class, 'show'])->name('analytics.summary');
        });

        Route::middleware('admin.api:read-orders')->group(function (): void {
            Route::post('exports/orders', [AdminOrderExportController::class, 'store'])->name('exports.orders.store');
            Route::get('exports/{dataExport}', [AdminOrderExportController::class, 'show'])->name('exports.show');
            Route::get('orders', [AdminOrderController::class, 'index'])->name('orders.index');
            Route::get('orders/{order}', [AdminOrderController::class, 'show'])->name('orders.show');
        });

        Route::middleware('admin.api:write-orders')->group(function (): void {
            Route::post('orders/{order}/fulfillments', [AdminOrderFulfillmentController::class, 'store'])->name('orders.fulfillments.store');
            Route::post('orders/{order}/refunds', [AdminOrderRefundController::class, 'store'])->name('orders.refunds.store');
        });
    });

Route::middleware('throttle:60,1')
    ->prefix('apps/v1/stores/{store}')
    ->name('api.apps.v1.')
    ->group(function (): void {
        Route::get('products', DeferredAppEndpointController::class)->name('products.index');
        Route::get('orders', DeferredAppEndpointController::class)->name('orders.index');
        Route::get('customers', DeferredAppEndpointController::class)->name('customers.index');
    });


sed -n '220,520p' tests/Feature/Api/AdminCatalogApiTest.php

    expect($media->status)->toBe(MediaStatus::Processing)
        ->and($media->mime_type)->toBe('image/jpeg')
        ->and($media->byte_size)->toBe(245000)
        ->and($media->position)->toBe(0);
});

test('admin customer api lists and shows store scoped customers', function (): void {
    $store = adminCatalogApiStore();
    $customer = Customer::factory()->create([
        'store_id' => $store->getKey(),
        'email' => 'customer-api@example.test',
        'name' => 'Customer API',
        'marketing_opt_in' => true,
    ]);
    CustomerAddress::factory()->default()->create(['customer_id' => $customer->getKey()]);
    Order::factory()->paid()->forCustomer($customer)->create([
        'store_id' => $store->getKey(),
        'order_number' => '#CA-1001',
        'total_amount' => 4400,
    ]);
    Customer::factory()->create([
        'store_id' => Store::factory()->create()->getKey(),
        'email' => 'other-customer@example.test',
    ]);

    $this->actingAs(adminCatalogApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/customers?query=customer-api")
        ->assertOk()
        ->assertJsonPath('data.0.email', 'customer-api@example.test')
        ->assertJsonPath('data.0.orders_count', 1)
        ->assertJsonPath('data.0.total_spent_amount', 4400)
        ->assertJsonMissing(['email' => 'other-customer@example.test']);

    $this->actingAs(adminCatalogApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/customers/{$customer->getKey()}")
        ->assertOk()
        ->assertJsonPath('data.email', 'customer-api@example.test')
        ->assertJsonPath('data.addresses.0.is_default', true)
        ->assertJsonPath('data.orders.0.order_number', '#CA-1001');
});

test('admin catalog api accepts scoped tokens and enforces abilities', function (): void {
    $store = adminCatalogApiStore();
    Product::factory()->withDefaultVariant()->create([
        'store_id' => $store->getKey(),
        'title' => 'Token Visible Product',
    ]);
    Customer::factory()->create([
        'store_id' => $store->getKey(),
        'email' => 'token-customer@example.test',
    ]);
    $productToken = adminCatalogApiToken($store, ['read-products']);
    $writeProductToken = adminCatalogApiToken($store, ['write-products']);
    $customerToken = adminCatalogApiToken($store, ['read-customers']);

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

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

    $this->withToken($productToken['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/products", [
            'title' => 'Token Created Product',
            'variants' => [
                [
                    'sku' => 'TOKEN-CREATED-001',
                    'price_amount' => 1999,
                    'is_default' => true,
                ],
            ],
        ])
        ->assertForbidden();

    $this->withToken($writeProductToken['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/products", [
            'title' => 'Token Created Product',
            'variants' => [
                [
                    'sku' => 'TOKEN-CREATED-001',
                    'price_amount' => 1999,
                    'is_default' => true,
                ],
            ],
        ])
        ->assertCreated()
        ->assertJsonPath('data.title', 'Token Created Product');

    $this->withToken($customerToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/customers?query=token-customer")
        ->assertOk()
        ->assertJsonPath('data.0.email', 'token-customer@example.test');

    expect($productToken['token']->refresh()->last_used_at)->not->toBeNull()
        ->and($writeProductToken['token']->refresh()->last_used_at)->not->toBeNull()
        ->and($customerToken['token']->refresh()->last_used_at)->not->toBeNull();
});

test('admin catalog api rejects resources outside the requested store', function (): void {
    $store = adminCatalogApiStore();
    $otherStore = Store::factory()->create();
    $otherProduct = Product::factory()->withDefaultVariant()->create(['store_id' => $otherStore->getKey()]);
    $otherCustomer = Customer::factory()->create(['store_id' => $otherStore->getKey()]);

    $this->actingAs(adminCatalogApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/products/{$otherProduct->getKey()}")
        ->assertNotFound();

    $this->actingAs(adminCatalogApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/customers/{$otherCustomer->getKey()}")
        ->assertNotFound();
});

test('deferred oauth and app ecosystem routes return not implemented', function (): void {
    $store = adminCatalogApiStore();

    $this->actingAs(adminCatalogApiUser())
        ->getJson('/oauth/authorize?client_id=test&redirect_uri=https://example.test/callback&response_type=code&scope=read-products&state=state')
        ->assertStatus(501)
        ->assertJsonPath('message', 'OAuth app ecosystem endpoints are deferred for initial implementation.');

    $this->postJson('/oauth/token', [
        'grant_type' => 'authorization_code',
        'client_id' => 'test',
        'client_secret' => 'secret',
        'redirect_uri' => 'https://example.test/callback',
        'code' => 'code',
    ])
        ->assertStatus(501)
        ->assertJsonPath('message', 'OAuth app ecosystem endpoints are deferred for initial implementation.');

    $this->getJson("/api/apps/v1/stores/{$store->getKey()}/products")
        ->assertStatus(501)
        ->assertJsonPath('message', 'App API endpoints are deferred for initial implementation.');
});


sed -n '1,260p' app/Http/Requests/Api/Admin/StoreProductRequest.php

sed: app/Http/Requests/Api/Admin/StoreProductRequest.php: No such file or directory


sed -n '1,260p' app/Http/Requests/Api/Admin/UpdateProductRequest.php

sed: app/Http/Requests/Api/Admin/UpdateProductRequest.php: No such file or directory


rg -n "class .*Request|function authorize" app/Http/Requests/Api/Admin app/Http/Controllers/Api/Admin/V1

rg -n 'class .*Request|function authorize' app/Http/Requests/Api/Admin app/Http/Controllers/Api/Admin/V1

Question: class .*Request|function authorize

app/Http/Controllers/Api/Admin/V1/PageController.php:97:    private function authorizeStore(Request $request, Store $store): void
app/Http/Requests/Api/Admin/V1/StoreProductRequest.php:12:class StoreProductRequest extends FormRequest
app/Http/Requests/Api/Admin/V1/StoreProductRequest.php:14:    public function authorize(): bool
app/Http/Controllers/Api/Admin/V1/ThemeController.php:65:    private function authorizeStore(Request $request, Store $store): void
app/Http/Controllers/Api/Admin/V1/OrderRefundController.php:43:    private function authorizeStore(Request $request, Store $store): void
app/Http/Controllers/Api/Admin/V1/ThemeSettingsController.php:44:    private function authorizeStore(Request $request, Store $store): void
app/Http/Controllers/Api/Admin/V1/ShippingZoneController.php:58:    private function authorizeStore(Request $request, Store $store): void
app/Http/Controllers/Api/Admin/V1/StoreSettingsController.php:88:    private function authorizeStore(Request $request, Store $store): void
app/Http/Controllers/Api/Admin/V1/TaxSettingsController.php:57:    private function authorizeStore(Request $request, Store $store): void
app/Http/Requests/Api/Admin/V1/CreateProductMediaUploadRequest.php:9:class CreateProductMediaUploadRequest extends FormRequest
app/Http/Requests/Api/Admin/V1/CreateProductMediaUploadRequest.php:11:    public function authorize(): bool
app/Http/Requests/Api/Admin/V1/CreateOrderRefundRequest.php:7:class CreateOrderRefundRequest extends FormRequest
app/Http/Requests/Api/Admin/V1/CreateOrderRefundRequest.php:9:    public function authorize(): bool
app/Http/Controllers/Api/Admin/V1/DiscountController.php:75:    private function authorizeStore(Request $request, Store $store): void
app/Http/Controllers/Api/Admin/V1/OrderFulfillmentController.php:37:    private function authorizeStore(Request $request, Store $store): void
app/Http/Controllers/Api/Admin/V1/ProductController.php:219:    private function authorizeStore(Request $request, Store $store): void
app/Http/Requests/Api/Admin/V1/StoreInviteRequest.php:8:class StoreInviteRequest extends FormRequest
app/Http/Requests/Api/Admin/V1/StoreInviteRequest.php:10:    public function authorize(): bool
app/Http/Requests/Api/Admin/V1/CreateOrderFulfillmentRequest.php:7:class CreateOrderFulfillmentRequest extends FormRequest
app/Http/Requests/Api/Admin/V1/CreateOrderFulfillmentRequest.php:9:    public function authorize(): bool
app/Http/Requests/Api/Admin/V1/StorePlatformStoreRequest.php:8:class StorePlatformStoreRequest extends FormRequest
app/Http/Requests/Api/Admin/V1/StorePlatformStoreRequest.php:10:    public function authorize(): bool
app/Http/Controllers/Api/Admin/V1/CustomerController.php:54:    private function authorizeStore(Request $request, Store $store): void
app/Http/Controllers/Api/Admin/V1/OrderController.php:60:    private function authorizeStore(Request $request, Store $store): void
app/Http/Requests/Api/Admin/V1/StorePlatformOrganizationRequest.php:7:class StorePlatformOrganizationRequest extends FormRequest
app/Http/Requests/Api/Admin/V1/StorePlatformOrganizationRequest.php:9:    public function authorize(): bool
app/Http/Controllers/Api/Admin/V1/OrderExportController.php:48:    private function authorizeStore(Request $request, Store $store): void
app/Http/Controllers/Api/Admin/V1/StoreInviteController.php:40:    private function authorizeInvite(Request $request, Store $store): void
app/Http/Requests/Api/Admin/V1/UpdateProductRequest.php:13:class UpdateProductRequest extends FormRequest
app/Http/Requests/Api/Admin/V1/UpdateProductRequest.php:15:    public function authorize(): bool
app/Http/Controllers/Api/Admin/V1/ShippingRateController.php:52:    private function authorizeStore(Request $request, Store $store): void
app/Http/Controllers/Api/Admin/V1/AnalyticsSummaryController.php:64:    private function authorizeStore(Request $request, Store $store): void
app/Http/Controllers/Api/Admin/V1/SearchIndexController.php:66:    private function authorizeStore(Request $request, Store $store): void
app/Http/Controllers/Api/Admin/V1/CollectionController.php:103:    private function authorizeStore(Request $request, Store $store): void


rg -n "actingAs\(|withToken\(|adminCatalogApiToken|adminPlatformApiToken|OauthToken|personal_access_tokens|oauth_tokens" tests/Feature/Api app tests/Feature/Admin tests/Feature/Checkout

rg -n "actingAs\\(|withToken\\(|adminCatalogApiToken|adminPlatformApiToken|OauthToken|personal_access_tokens|oauth_tokens" tests/Feature/Api app tests/Feature/Admin tests/Feature/Checkout

Question: actingAs\(|withToken\(|adminCatalogApiToken|adminPlatformApiToken|OauthToken|personal_access_tokens|oauth_tokens

tests/Feature/Admin/ProductMediaManagementTest.php:42:    Livewire::actingAs($user)
tests/Feature/Admin/ProductMediaManagementTest.php:77:    Livewire::actingAs($user)
tests/Feature/Admin/ProductMediaManagementTest.php:112:    Livewire::actingAs($user)
tests/Feature/Admin/ProductMediaManagementTest.php:147:    $component = Livewire::actingAs($user)
tests/Feature/Admin/AppsDevelopersTest.php:6:use App\Models\OauthToken;
tests/Feature/Admin/AppsDevelopersTest.php:45:    $this->actingAs(appsDevelopersUser(StoreUserRole::Admin))
tests/Feature/Admin/AppsDevelopersTest.php:53:    $this->actingAs(appsDevelopersUser(StoreUserRole::Staff))
tests/Feature/Admin/AppsDevelopersTest.php:63:    $initialTokenCount = OauthToken::query()
tests/Feature/Admin/AppsDevelopersTest.php:67:    Livewire::actingAs($user)
tests/Feature/Admin/AppsDevelopersTest.php:81:    expect(OauthToken::query()
tests/Feature/Admin/CustomerManagementTest.php:71:    $this->actingAs(adminCustomerManagementUser())
tests/Feature/Admin/CustomerManagementTest.php:79:    $this->actingAs(adminCustomerManagementUser())
tests/Feature/Admin/CustomerManagementTest.php:87:    $this->actingAs(adminCustomerManagementUser())
tests/Feature/Admin/CustomerManagementTest.php:99:    Livewire::actingAs($user)
tests/Feature/Admin/CustomerManagementTest.php:120:    Livewire::actingAs($user)
tests/Feature/Admin/CustomerManagementTest.php:138:    Livewire::actingAs($user)
tests/Feature/Admin/CustomerManagementTest.php:161:    Livewire::actingAs(adminCustomerManagementUser())
tests/Feature/Admin/SettingsManagementTest.php:59:        $this->actingAs($owner)
tests/Feature/Admin/SettingsManagementTest.php:64:        $this->actingAs($staff)
tests/Feature/Admin/SettingsManagementTest.php:75:    Livewire::actingAs($user)
tests/Feature/Admin/SettingsManagementTest.php:105:    Livewire::actingAs($user)
tests/Feature/Admin/SettingsManagementTest.php:117:    Livewire::actingAs($user)
tests/Feature/Admin/SettingsManagementTest.php:143:    Livewire::actingAs($user)
tests/Feature/Admin/SettingsManagementTest.php:176:    Livewire::actingAs($user)
tests/Feature/Admin/SettingsManagementTest.php:207:    Livewire::actingAs($user)
tests/Feature/Admin/SettingsManagementTest.php:224:    Livewire::actingAs($user)
tests/Feature/Admin/DiscountManagementTest.php:76:    $this->actingAs(adminDiscountManagementUser())
tests/Feature/Admin/DiscountManagementTest.php:85:    $this->actingAs(adminDiscountManagementUser())
tests/Feature/Admin/DiscountManagementTest.php:91:    $this->actingAs(adminDiscountManagementUser())
tests/Feature/Admin/DiscountManagementTest.php:130:    Livewire::actingAs($user)
tests/Feature/Admin/DiscountManagementTest.php:157:    Livewire::actingAs($user)
tests/Feature/Admin/DiscountManagementTest.php:194:    $this->actingAs($supportUser)
tests/Feature/Admin/DiscountManagementTest.php:199:    $this->actingAs($supportUser)
tests/Feature/Admin/DiscountManagementTest.php:204:    $this->actingAs($unverifiedUser)
tests/Feature/Admin/DiscountManagementTest.php:209:    Livewire::actingAs($supportUser)
tests/Feature/Admin/DiscountManagementTest.php:222:    Livewire::actingAs($user)
tests/Feature/Admin/DiscountManagementTest.php:230:    Livewire::actingAs($user)
tests/Feature/Admin/DiscountManagementTest.php:243:    Livewire::actingAs($user)
tests/Feature/Admin/DiscountManagementTest.php:254:    Livewire::actingAs($user)
tests/Feature/Admin/DiscountManagementTest.php:266:    Livewire::actingAs($user)
tests/Feature/Admin/DiscountManagementTest.php:290:    Livewire::actingAs($user)
tests/Feature/Admin/DiscountManagementTest.php:303:    Livewire::actingAs($user)
tests/Feature/Admin/ContentManagementTest.php:66:    $this->actingAs($user)
tests/Feature/Admin/ContentManagementTest.php:73:    $this->actingAs($user)
tests/Feature/Admin/ContentManagementTest.php:79:    $this->actingAs($user)
tests/Feature/Admin/ContentManagementTest.php:85:    $this->actingAs($user)
tests/Feature/Admin/ContentManagementTest.php:91:    $this->actingAs($user)
tests/Feature/Admin/ContentManagementTest.php:102:    Livewire::actingAs($user)
tests/Feature/Admin/ContentManagementTest.php:119:    Livewire::actingAs($user)
tests/Feature/Admin/ContentManagementTest.php:125:    Livewire::actingAs($user)
tests/Feature/Admin/ContentManagementTest.php:135:    Livewire::actingAs($user)
tests/Feature/Admin/ContentManagementTest.php:151:    $component = Livewire::actingAs($user)
tests/Feature/Admin/ContentManagementTest.php:206:    Livewire::actingAs($user)
tests/Feature/Admin/ContentManagementTest.php:221:    Livewire::actingAs($user)
tests/Feature/Admin/ContentManagementTest.php:242:    Livewire::actingAs($user)
tests/Feature/Admin/ContentManagementTest.php:266:    Livewire::actingAs($support)
tests/Feature/Admin/ContentManagementTest.php:270:    Livewire::actingAs($staff)
tests/Feature/Admin/ContentManagementTest.php:274:    Livewire::actingAs($staff)
tests/Feature/Admin/ContentManagementTest.php:278:    Livewire::actingAs(adminCon…10656 chars truncated…oreToken['plain_text'])
tests/Feature/Api/AdminDiscountApiTest.php:120:    $this->withToken($writeToken['plain_text'])
tests/Feature/Api/AdminDiscountApiTest.php:135:    $this->actingAs(adminDiscountApiUser())
tests/Feature/Api/AdminThemeApiTest.php:34: * @return array{token: \App\Models\OauthToken, plain_text: string}
tests/Feature/Api/AdminThemeApiTest.php:108:    $response = $this->actingAs($user)
tests/Feature/Api/AdminThemeApiTest.php:132:    $this->actingAs(adminThemeApiUser())
tests/Feature/Api/AdminThemeApiTest.php:145:    $this->actingAs(adminThemeApiUser())
tests/Feature/Api/AdminThemeApiTest.php:164:    $this->withToken($readToken['plain_text'])
tests/Feature/Api/AdminThemeApiTest.php:168:    $this->withToken($otherStoreToken['plain_text'])
tests/Feature/Api/AdminThemeApiTest.php:172:    $this->withToken($writeToken['plain_text'])
tests/Feature/Api/AdminThemeApiTest.php:191:    $this->actingAs(adminThemeApiUser())
tests/Feature/Api/AdminThemeApiTest.php:200:    $this->actingAs(adminThemeApiUser())
tests/Feature/Api/AdminThemeApiTest.php:207:    $this->actingAs(adminThemeApiUser())
app/Models/AppInstallation.php:55:     * @return HasMany<OauthToken, $this>
app/Models/AppInstallation.php:59:        return $this->hasMany(OauthToken::class, 'installation_id');
tests/Feature/Api/AdminOrderExportApiTest.php:33: * @return array{token: \App\Models\OauthToken, plain_text: string}
tests/Feature/Api/AdminOrderExportApiTest.php:78:    $createResponse = $this->actingAs(adminOrderExportApiUser())
tests/Feature/Api/AdminOrderExportApiTest.php:94:    $showResponse = $this->actingAs(adminOrderExportApiUser())
tests/Feature/Api/AdminOrderExportApiTest.php:120:    $this->withToken($wrongAbilityToken['plain_text'])
tests/Feature/Api/AdminOrderExportApiTest.php:126:    $createResponse = $this->withToken($readToken['plain_text'])
tests/Feature/Api/AdminOrderExportApiTest.php:134:    $this->withToken($readToken['plain_text'])
tests/Feature/Api/AdminOrderExportApiTest.php:138:    $this->withToken($otherStoreToken['plain_text'])
tests/Feature/Api/AdminOrderExportApiTest.php:146:    $this->actingAs(adminOrderExportApiUser())
tests/Feature/Api/AdminOrderExportApiTest.php:154:    $this->actingAs(adminOrderExportApiUser())
tests/Feature/Api/AdminOrderApiTest.php:38: * @return array{token: \App\Models\OauthToken, plain_text: string}
tests/Feature/Api/AdminOrderApiTest.php:109:    $this->actingAs(adminOrderApiUser())
tests/Feature/Api/AdminOrderApiTest.php:115:    $this->actingAs(adminOrderApiUser())
tests/Feature/Api/AdminOrderApiTest.php:127:    $this->actingAs($user)
tests/Feature/Api/AdminOrderApiTest.php:138:    $this->actingAs($user)
tests/Feature/Api/AdminOrderApiTest.php:163:    $this->actingAs(adminOrderApiUser())
tests/Feature/Api/AdminOrderApiTest.php:176:    $this->withToken($result['plain_text'])
tests/Feature/Api/AdminOrderApiTest.php:192:    $this->withToken($readOnly['plain_text'])
tests/Feature/Api/AdminOrderApiTest.php:199:    $this->withToken($otherStoreToken['plain_text'])
tests/Feature/Api/AdminOrderApiTest.php:203:    $this->withToken($writeToken['plain_text'])
tests/Feature/Api/AdminOrderApiTest.php:218:    $this->withToken($result['plain_text'])
tests/Feature/Api/AdminPageApiTest.php:29: * @return array{token: \App\Models\OauthToken, plain_text: string}
tests/Feature/Api/AdminPageApiTest.php:51:    $this->actingAs($user)
tests/Feature/Api/AdminPageApiTest.php:57:    $createResponse = $this->actingAs($user)
tests/Feature/Api/AdminPageApiTest.php:72:    $this->actingAs($user)
tests/Feature/Api/AdminPageApiTest.php:83:    $this->actingAs($user)
tests/Feature/Api/AdminPageApiTest.php:103:    $this->withToken($readToken['plain_text'])
tests/Feature/Api/AdminPageApiTest.php:108:    $this->withToken($readToken['plain_text'])
tests/Feature/Api/AdminPageApiTest.php:112:    $this->withToken($otherStoreToken['plain_text'])
tests/Feature/Api/AdminPageApiTest.php:116:    $this->withToken($writeToken['plain_text'])
tests/Feature/Api/AdminPageApiTest.php:128:    $this->actingAs(adminPageApiUser())
tests/Feature/Api/AdminPageApiTest.php:137:    $this->actingAs(adminPageApiUser())
tests/Feature/Api/AdminTaxSettingsApiTest.php:30: * @return array{token: \App\Models\OauthToken, plain_text: string}
tests/Feature/Api/AdminTaxSettingsApiTest.php:41:    $this->actingAs($user)
tests/Feature/Api/AdminTaxSettingsApiTest.php:48:    $this->actingAs($user)
tests/Feature/Api/AdminTaxSettingsApiTest.php:86:    $this->withToken($readToken['plain_text'])
tests/Feature/Api/AdminTaxSettingsApiTest.php:90:    $this->withToken($readToken['plain_text'])
tests/Feature/Api/AdminTaxSettingsApiTest.php:99:    $this->withToken($otherStoreToken['plain_text'])
tests/Feature/Api/AdminTaxSettingsApiTest.php:103:    $this->withToken($writeToken['plain_text'])
tests/Feature/Api/AdminTaxSettingsApiTest.php:120:    $this->actingAs(adminTaxSettingsApiUser())
tests/Feature/Api/AdminTaxSettingsApiTest.php:129:    $this->actingAs(adminTaxSettingsApiUser())


Output:
Chunk ID: 3b2ae7
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1477
Output:

    expect($media->status)->toBe(MediaStatus::Processing)
        ->and($media->mime_type)->toBe('image/jpeg')
        ->and($media->byte_size)->toBe(245000)
        ->and($media->position)->toBe(0);
});

test('admin customer api lists and shows store scoped customers', function (): void {
    $store = adminCatalogApiStore();
    $customer = Customer::factory()->create([
        'store_id' => $store->getKey(),
        'email' => 'customer-api@example.test',
        'name' => 'Customer API',
        'marketing_opt_in' => true,
    ]);
    CustomerAddress::factory()->default()->create(['customer_id' => $customer->getKey()]);
    Order::factory()->paid()->forCustomer($customer)->create([
        'store_id' => $store->getKey(),
        'order_number' => '#CA-1001',
        'total_amount' => 4400,
    ]);
    Customer::factory()->create([
        'store_id' => Store::factory()->create()->getKey(),
        'email' => 'other-customer@example.test',
    ]);

    $this->actingAs(adminCatalogApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/customers?query=customer-api")
        ->assertOk()
        ->assertJsonPath('data.0.email', 'customer-api@example.test')
        ->assertJsonPath('data.0.orders_count', 1)
        ->assertJsonPath('data.0.total_spent_amount', 4400)
        ->assertJsonMissing(['email' => 'other-customer@example.test']);

    $this->actingAs(adminCatalogApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/customers/{$customer->getKey()}")
        ->assertOk()
        ->assertJsonPath('data.email', 'customer-api@example.test')
        ->assertJsonPath('data.addresses.0.is_default', true)
        ->assertJsonPath('data.orders.0.order_number', '#CA-1001');
});

test('admin catalog api accepts scoped tokens and enforces abilities', function (): void {
    $store = adminCatalogApiStore();
    Product::factory()->withDefaultVariant()->create([
        'store_id' => $store->getKey(),
        'title' => 'Token Visible Product',
    ]);
    Customer::factory()->create([
        'store_id' => $store->getKey(),
        'email' => 'token-customer@example.test',
    ]);
    $productToken = adminCatalogApiToken($store, ['read-products']);
    $writeProductToken = adminCatalogApiToken($store, ['write-products']);
    $customerToken = adminCatalogApiToken($store, ['read-customers']);

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

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

    $this->withToken($productToken['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/products", [
            'title' => 'Token Created Product',
            'variants' => [
                [
                    'sku' => 'TOKEN-CREATED-001',
                    'price_amount' => 1999,
                    'is_default' => true,
                ],
            ],
        ])
        ->assertForbidden();

    $this->withToken($writeProductToken['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/products", [
            'title' => 'Token Created Product',
            'variants' => [
                [
                    'sku' => 'TOKEN-CREATED-001',
                    'price_amount' => 1999,
                    'is_default' => true,
                ],
            ],
        ])
        ->assertCreated()
        ->assertJsonPath('data.title', 'Token Created Product');

    $this->withToken($customerToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/customers?query=token-customer")
        ->assertOk()
        ->assertJsonPath('data.0.email', 'token-customer@example.test');

    expect($productToken['token']->refresh()->last_used_at)->not->toBeNull()
        ->and($writeProductToken['token']->refresh()->last_used_at)->not->toBeNull()
        ->and($customerToken['token']->refresh()->last_used_at)->not->toBeNull();
});

test('admin catalog api rejects resources outside the requested store', function (): void {
    $store = adminCatalogApiStore();
    $otherStore = Store::factory()->create();
    $otherProduct = Product::factory()->withDefaultVariant()->create(['store_id' => $otherStore->getKey()]);
    $otherCustomer = Customer::factory()->create(['store_id' => $otherStore->getKey()]);

    $this->actingAs(adminCatalogApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/products/{$otherProduct->getKey()}")
        ->assertNotFound();

    $this->actingAs(adminCatalogApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/customers/{$otherCustomer->getKey()}")
        ->assertNotFound();
});

test('deferred oauth and app ecosystem routes return not implemented', function (): void {
    $store = adminCatalogApiStore();

    $this->actingAs(adminCatalogApiUser())
        ->getJson('/oauth/authorize?client_id=test&redirect_uri=https://example.test/callback&response_type=code&scope=read-products&state=state')
        ->assertStatus(501)
        ->assertJsonPath('message', 'OAuth app ecosystem endpoints are deferred for initial implementation.');

    $this->postJson('/oauth/token', [
        'grant_type' => 'authorization_code',
        'client_id' => 'test',
        'client_secret' => 'secret',
        'redirect_uri' => 'https://example.test/callback',
        'code' => 'code',
    ])
        ->assertStatus(501)
        ->assertJsonPath('message', 'OAuth app ecosystem endpoints are deferred for initial implementation.');

    $this->getJson("/api/apps/v1/stores/{$store->getKey()}/products")
        ->assertStatus(501)
        ->assertJsonPath('message', 'App API endpoints are deferred for initial implementation.');
});