Files
star-erp/tests/Feature/Integration/ProductSearchApiTest.php

195 lines
6.4 KiB
PHP

<?php
namespace Tests\Feature\Integration;
use Tests\TestCase;
use App\Modules\Core\Models\Tenant;
use App\Modules\Core\Models\User;
use App\Modules\Inventory\Models\Product;
use App\Modules\Inventory\Models\Category;
use App\Modules\Inventory\Models\Unit;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Stancl\Tenancy\Facades\Tenancy;
use Illuminate\Support\Str;
class ProductSearchApiTest extends TestCase
{
protected $tenant;
protected $user;
protected $domain;
protected function setUp(): void
{
parent::setUp();
Artisan::call('migrate:fresh');
$this->domain = 'search-test-' . Str::random(8) . '.erp.local';
$tenantId = 'test_tenant_s_' . Str::random(8);
tenancy()->central(function () use ($tenantId) {
$this->tenant = Tenant::create([
'id' => $tenantId,
'name' => 'Search Test Tenant',
]);
$this->tenant->domains()->create(['domain' => $this->domain]);
});
tenancy()->initialize($this->tenant);
Artisan::call('tenants:migrate');
$this->user = User::factory()->create(['name' => 'Search Admin']);
// 建立測試資料
$cat1 = Category::firstOrCreate(['name' => '飲品'], ['code' => 'CAT-DRINK']);
$cat2 = Category::firstOrCreate(['name' => '食品'], ['code' => 'CAT-FOOD']);
$unit = Unit::firstOrCreate(['name' => '瓶'], ['code' => 'BO']);
Product::create([
'name' => '可口可樂',
'code' => 'COKE-001',
'barcode' => '4710001',
'price' => 25,
'category_id' => $cat1->id,
'base_unit_id' => $unit->id,
'is_active' => true,
]);
$pepsi = Product::create([
'name' => '百事可樂',
'code' => 'PEPSI-001',
'barcode' => '4710002',
'price' => 23,
'category_id' => $cat1->id,
'base_unit_id' => $unit->id,
'is_active' => true,
]);
DB::table('products')->where('id', $pepsi->id)->update(['updated_at' => now()->subDay()]);
Product::create([
'name' => '漢堡',
'code' => 'BURGER-001',
'barcode' => '4710003',
'price' => 50,
'category_id' => $cat2->id,
'base_unit_id' => $unit->id,
'is_active' => true,
]);
Product::create([
'name' => '停用商品',
'code' => 'INACTIVE-001',
'is_active' => false,
'category_id' => $cat1->id,
'base_unit_id' => $unit->id,
]);
tenancy()->end();
}
protected function tearDown(): void
{
if ($this->tenant) {
$this->tenant->delete();
}
parent::tearDown();
}
public function test_can_search_all_active_products()
{
\Laravel\Sanctum\Sanctum::actingAs($this->user, ['*']);
$response = $this->withHeaders([
'X-Tenant-Domain' => $this->domain,
'Accept' => 'application/json',
])->getJson('/api/v1/integration/products');
$response->assertStatus(200)
->assertJsonPath('status', 'success')
->assertJsonCount(3, 'data'); // 3 active products
}
public function test_can_filter_by_category()
{
\Laravel\Sanctum\Sanctum::actingAs($this->user, ['*']);
$response = $this->withHeaders([
'X-Tenant-Domain' => $this->domain,
'Accept' => 'application/json',
])->getJson('/api/v1/integration/products?category=食品');
$response->assertStatus(200)
->assertJsonCount(1, 'data')
->assertJsonPath('data.0.name', '漢堡');
}
public function test_can_filter_by_updated_after()
{
\Laravel\Sanctum\Sanctum::actingAs($this->user, ['*']);
// 搜尋今天更新的商品 (百事可樂是昨天更新的,應該被過濾掉)
$timeStr = now()->startOfDay()->format('Y-m-d H:i:s');
$response = $this->withHeaders([
'X-Tenant-Domain' => $this->domain,
'Accept' => 'application/json',
])->getJson("/api/v1/integration/products?updated_after={$timeStr}");
$response->assertStatus(200)
->assertJsonCount(2, 'data'); // 可口可樂, 漢堡 (百事可樂是昨天)
}
public function test_pagination_works()
{
\Laravel\Sanctum\Sanctum::actingAs($this->user, ['*']);
$response = $this->withHeaders([
'X-Tenant-Domain' => $this->domain,
'Accept' => 'application/json',
])->getJson('/api/v1/integration/products?per_page=2');
$response->assertStatus(200)
->assertJsonCount(2, 'data')
->assertJsonPath('meta.per_page', 2)
->assertJsonPath('meta.total', 3);
}
public function test_can_filter_by_precision_params()
{
\Laravel\Sanctum\Sanctum::actingAs($this->user, ['*']);
// 1. By Barcode
$response = $this->withHeaders([
'X-Tenant-Domain' => $this->domain,
'Accept' => 'application/json',
])->getJson('/api/v1/integration/products?barcode=4710001');
$response->assertStatus(200)->assertJsonCount(1, 'data')->assertJsonPath('data.0.name', '可口可樂');
// 2. By Code
$response = $this->withHeaders([
'X-Tenant-Domain' => $this->domain,
'Accept' => 'application/json',
])->getJson('/api/v1/integration/products?code=PEPSI-001');
$response->assertStatus(200)->assertJsonCount(1, 'data')->assertJsonPath('data.0.name', '百事可樂');
// 3. By product_id (ERP ID)
$productId = Product::where('code', 'BURGER-001')->value('id');
$response = $this->withHeaders([
'X-Tenant-Domain' => $this->domain,
'Accept' => 'application/json',
])->getJson("/api/v1/integration/products?product_id={$productId}");
$response->assertStatus(200)->assertJsonCount(1, 'data')->assertJsonPath('data.0.name', '漢堡');
}
public function test_is_protected_by_auth()
{
// 不帶 Token
$response = $this->withHeaders([
'X-Tenant-Domain' => $this->domain,
'Accept' => 'application/json',
])->getJson('/api/v1/integration/products');
$response->assertStatus(401);
}
}