195 lines
6.4 KiB
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);
|
|
}
|
|
}
|