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); } }