Files
isaaccladandCursor 085645fd0f
Deploy Ladill Woo Manager / deploy (push) Successful in 24s
Add WooCommerce-style featured image and product gallery flow.
Sync full image sets through the plugin, with upload/URL support in the product editor and thumbnails in the list.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 00:27:11 +00:00

355 lines
12 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\User;
use App\Models\WooOrder;
use App\Models\WooStore;
use App\Services\Woo\InstallTokenService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class WooWebTest extends TestCase
{
use RefreshDatabase;
public function test_guest_is_redirected_to_sso(): void
{
$this->get(route('woo.dashboard'))
->assertRedirect(route('sso.connect', ['redirect' => route('woo.dashboard')]));
}
public function test_authenticated_user_sees_dashboard(): void
{
$user = User::factory()->create();
$this->actingAs($user)
->get(route('woo.dashboard'))
->assertOk()
->assertSee('Woo Manager');
}
public function test_store_activation_exchanges_install_token(): void
{
$user = User::factory()->create();
$store = WooStore::create([
'user_id' => $user->id,
'site_url' => 'https://shop.example.com',
'site_name' => 'Example Shop',
'status' => WooStore::STATUS_PENDING,
]);
$token = app(InstallTokenService::class)->issue($store)['token'];
$response = $this->postJson('/api/v1/stores/activate', [
'install_token' => $token,
'site_url' => 'https://shop.example.com',
]);
$response->assertOk()
->assertJsonPath('store_id', $store->public_id)
->assertJsonStructure(['webhook_url', 'webhook_secret', 'plugin_token']);
$this->assertSame(WooStore::STATUS_ACTIVE, $store->fresh()->status);
}
public function test_webhook_ingests_order(): void
{
$user = User::factory()->create();
$store = WooStore::create([
'user_id' => $user->id,
'site_url' => 'https://shop.example.com',
'status' => WooStore::STATUS_ACTIVE,
'connected_at' => now(),
'plugin_token_hash' => hash('sha256', 'test-token'),
]);
$payload = [
'id' => 42,
'number' => '1042',
'status' => 'processing',
'currency' => 'GHS',
'total' => '120.50',
'billing' => [
'first_name' => 'Ama',
'last_name' => 'Mensah',
'email' => 'ama@example.com',
'phone' => '0244123456',
],
'line_items' => [
['name' => 'T-shirt', 'quantity' => 2, 'total' => '120.50', 'sku' => 'TS-1'],
],
'needs_payment' => false,
'date_modified_gmt' => now()->utc()->format('Y-m-d H:i:s'),
];
$body = json_encode($payload, JSON_THROW_ON_ERROR);
$signature = base64_encode(hash_hmac('sha256', $body, (string) $store->webhook_secret, true));
$this->call(
'POST',
route('woo.webhooks.woocommerce', $store->public_id),
[],
[],
[],
[
'CONTENT_TYPE' => 'application/json',
'HTTP_X_WC_WEBHOOK_SIGNATURE' => $signature,
'HTTP_X_WC_WEBHOOK_TOPIC' => 'order.updated',
],
$body,
)->assertOk();
$order = WooOrder::query()->where('external_order_id', 42)->first();
$this->assertNotNull($order);
$this->assertSame('ama@example.com', $order->customer_email);
$this->assertSame(12050, $order->total_minor);
}
public function test_merchant_can_update_fulfillment_status(): void
{
$user = User::factory()->create();
$store = WooStore::create([
'user_id' => $user->id,
'site_url' => 'https://shop.example.com',
'status' => WooStore::STATUS_ACTIVE,
]);
$order = WooOrder::create([
'woo_store_id' => $store->id,
'external_order_id' => 7,
'order_number' => '1007',
'line_items' => [['name' => 'Item', 'quantity' => 1, 'total_minor' => 1000]],
'currency' => 'GHS',
'total_minor' => 1000,
]);
$this->actingAs($user)
->patch(route('woo.orders.update-status', $order), [
'fulfillment_status' => WooOrder::FULFILLMENT_PREPARING,
])
->assertRedirect();
$this->assertSame(WooOrder::FULFILLMENT_PREPARING, $order->fresh()->fulfillment_status);
}
public function test_product_webhook_ingests_product(): void
{
$user = User::factory()->create();
$store = WooStore::create([
'user_id' => $user->id,
'site_url' => 'https://shop.example.com',
'status' => WooStore::STATUS_ACTIVE,
'connected_at' => now(),
]);
$payload = [
'id' => 101,
'name' => 'Hoodie',
'slug' => 'hoodie',
'sku' => 'HD-1',
'status' => 'publish',
'regular_price' => '150.00',
'categories' => [['id' => 12, 'name' => 'Apparel']],
];
$body = json_encode($payload, JSON_THROW_ON_ERROR);
$signature = base64_encode(hash_hmac('sha256', $body, (string) $store->webhook_secret, true));
$this->call(
'POST',
route('woo.webhooks.woocommerce', $store->public_id),
[],
[],
[],
[
'CONTENT_TYPE' => 'application/json',
'HTTP_X_WC_WEBHOOK_SIGNATURE' => $signature,
'HTTP_X_WC_WEBHOOK_TOPIC' => 'product.updated',
],
$body,
)->assertOk();
$this->assertDatabaseHas('woo_products', [
'woo_store_id' => $store->id,
'external_id' => 101,
'name' => 'Hoodie',
'sku' => 'HD-1',
]);
}
public function test_category_webhook_ingests_category(): void
{
$user = User::factory()->create();
$store = WooStore::create([
'user_id' => $user->id,
'site_url' => 'https://shop.example.com',
'status' => WooStore::STATUS_ACTIVE,
'connected_at' => now(),
]);
$payload = [
'id' => 12,
'name' => 'Apparel',
'slug' => 'apparel',
'parent' => 0,
'count' => 4,
];
$body = json_encode($payload, JSON_THROW_ON_ERROR);
$signature = base64_encode(hash_hmac('sha256', $body, (string) $store->webhook_secret, true));
$this->call(
'POST',
route('woo.webhooks.woocommerce', $store->public_id),
[],
[],
[],
[
'CONTENT_TYPE' => 'application/json',
'HTTP_X_WC_WEBHOOK_SIGNATURE' => $signature,
'HTTP_X_WC_WEBHOOK_TOPIC' => 'product_category.created',
],
$body,
)->assertOk();
$this->assertDatabaseHas('woo_categories', [
'woo_store_id' => $store->id,
'external_id' => 12,
'name' => 'Apparel',
]);
}
public function test_authenticated_user_can_view_settings(): void
{
$user = User::factory()->create();
$this->actingAs($user)
->get(route('woo.settings'))
->assertOk()
->assertSee('Settings')
->assertSee('Manage connected stores');
}
public function test_store_activation_dispatches_bootstrap_sync(): void
{
\Illuminate\Support\Facades\Bus::fake();
$user = User::factory()->create();
$store = WooStore::create([
'user_id' => $user->id,
'site_url' => 'https://shop.example.com',
'status' => WooStore::STATUS_PENDING,
]);
$token = app(InstallTokenService::class)->issue($store)['token'];
$this->postJson('/api/v1/stores/activate', [
'install_token' => $token,
'site_url' => 'https://shop.example.com',
])->assertOk();
\Illuminate\Support\Facades\Bus::assertDispatched(\App\Jobs\StoreBootstrapSync::class, fn ($job) => $job->storeId === $store->id);
}
public function test_webhook_accepts_post_without_csrf_token(): void
{
$user = User::factory()->create();
$store = WooStore::create([
'user_id' => $user->id,
'site_url' => 'https://shop.example.com',
'status' => WooStore::STATUS_ACTIVE,
'connected_at' => now(),
]);
$payload = ['id' => 99, 'number' => '1099', 'status' => 'processing', 'currency' => 'GHS', 'total' => '10.00', 'billing' => ['email' => 'a@b.com'], 'line_items' => [['name' => 'Item', 'quantity' => 1, 'total' => '10.00']], 'needs_payment' => false];
$body = json_encode($payload, JSON_THROW_ON_ERROR);
$signature = base64_encode(hash_hmac('sha256', $body, (string) $store->webhook_secret, true));
$this->withMiddleware(\Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class)
->call(
'POST',
route('woo.webhooks.woocommerce', $store->public_id),
[],
[],
[],
[
'CONTENT_TYPE' => 'application/json',
'HTTP_X_WC_WEBHOOK_SIGNATURE' => $signature,
'HTTP_X_WC_WEBHOOK_TOPIC' => 'order.updated',
],
$body,
)->assertOk();
$this->assertDatabaseHas('woo_orders', ['external_order_id' => 99]);
}
public function test_product_webhook_ingests_gallery_images(): void
{
$user = User::factory()->create();
$store = WooStore::create([
'user_id' => $user->id,
'site_url' => 'https://shop.example.com',
'status' => WooStore::STATUS_ACTIVE,
'connected_at' => now(),
]);
$payload = [
'id' => 102,
'name' => 'Gallery Hoodie',
'slug' => 'gallery-hoodie',
'status' => 'publish',
'regular_price' => '150.00',
'images' => [
['id' => 1, 'src' => 'https://shop.example.com/featured.jpg', 'alt' => 'Front', 'position' => 0],
['id' => 2, 'src' => 'https://shop.example.com/gallery-1.jpg', 'alt' => 'Side', 'position' => 1],
['id' => 3, 'src' => 'https://shop.example.com/gallery-2.jpg', 'alt' => 'Back', 'position' => 2],
],
];
$body = json_encode($payload, JSON_THROW_ON_ERROR);
$signature = base64_encode(hash_hmac('sha256', $body, (string) $store->webhook_secret, true));
$this->call(
'POST',
route('woo.webhooks.woocommerce', $store->public_id),
[],
[],
[],
[
'CONTENT_TYPE' => 'application/json',
'HTTP_X_WC_WEBHOOK_SIGNATURE' => $signature,
'HTTP_X_WC_WEBHOOK_TOPIC' => 'product.updated',
],
$body,
)->assertOk();
$product = \App\Models\WooProduct::query()->where('external_id', 102)->first();
$this->assertNotNull($product);
$this->assertSame('https://shop.example.com/featured.jpg', $product->featuredImage()['src'] ?? null);
$this->assertCount(2, $product->galleryImages());
}
public function test_merchant_can_view_products_page(): void
{
$user = User::factory()->create();
$store = WooStore::create([
'user_id' => $user->id,
'site_url' => 'https://shop.example.com',
'status' => WooStore::STATUS_ACTIVE,
]);
\App\Models\WooProduct::create([
'woo_store_id' => $store->id,
'external_id' => 1,
'name' => 'Sample Tee',
'slug' => 'sample-tee',
'status' => 'publish',
]);
$this->actingAs($user)
->get(route('woo.products.index'))
->assertOk()
->assertSee('Sample Tee');
}
}