Files
CFDivePlatform/tests/Feature/ProviderOfferCrudTest.php
T
a620906209 49a4b89655
Run Tests / test (pull_request) Successful in 1m51s
test(feature): 補齊五個核心業務流程 Feature test——Offer/Schedule CRUD、Admin 管理、通知觸發、預約列表
ProviderOfferCrudTest(7 案例):store/update/destroy 正常流程 + 所有權邊界(403)+ 未認證(401)
ProviderScheduleCrudTest(7 案例):store/update/destroy + 容量不變式(422)+ destroy 同步取消 active bookings
AdminUserManagementTest(5 案例):列表角色過濾 + toggle-active/toggle-verified DB 副作用 + 非 admin 403
NotificationTriggerTest(5 案例):5 條直接觸發路徑,收件者各不同(BookingCreated/CancelledByMember → provider;其餘 → member)
BookingListTest(4 案例):三角色資料隔離邊界 + 未認證 401

全套件 215 passed / 529 assertions,零 regression。
同步新增 openspec/changes/feature-test-coverage/ 與 openspec/specs/feature-test-coverage/spec.md。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 18:23:11 +08:00

144 lines
4.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace Tests\Feature;
use App\Models\DivingOffer;
use App\Models\ProviderProfile;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
/**
* Provider 課程 CRUDcoach-offers-api 規格)。
*
* 所有權驗證是核心不變式:provider_id 決定哪位教練可操作,
* 未加檢查會讓任何已登入教練刪改他人課程。
*/
class ProviderOfferCrudTest extends TestCase
{
use RefreshDatabase;
private function makeProvider(): User
{
$provider = User::factory()->create(['role' => 'provider']);
ProviderProfile::create(['user_id' => $provider->id, 'is_verified' => true]);
return $provider;
}
private function makeOffer(User $provider): DivingOffer
{
return DivingOffer::create([
'provider_id' => $provider->id,
'title' => 'Test Dive Course',
'location' => 'Kenting',
'price' => 3000,
'region' => '南部',
'rating' => 0,
'reviews' => 0,
]);
}
// ── store ────────────────────────────────────────────────
public function test_provider_creates_offer_successfully(): void
{
$provider = $this->makeProvider();
$response = $this->actingAs($provider)->postJson('/api/provider/offers', [
'title' => 'New Dive Course',
'location' => 'Kenting',
'price' => 5000,
'region' => '南部',
]);
$response->assertStatus(201)
->assertJsonPath('status', true)
->assertJsonPath('data.provider_id', $provider->id);
$this->assertDatabaseHas('diving_offers', [
'title' => 'New Dive Course',
'provider_id' => $provider->id,
]);
}
public function test_create_offer_missing_required_field_returns_422(): void
{
$provider = $this->makeProvider();
$this->actingAs($provider)->postJson('/api/provider/offers', [
// title is missing
'location' => 'Kenting',
'price' => 5000,
'region' => '南部',
])->assertStatus(422);
}
public function test_unauthenticated_request_is_rejected(): void
{
$this->postJson('/api/provider/offers', [
'title' => 'New Dive Course',
'location' => 'Kenting',
'price' => 5000,
'region' => '南部',
])->assertStatus(401);
}
// ── update ───────────────────────────────────────────────
public function test_provider_updates_own_offer(): void
{
$provider = $this->makeProvider();
$offer = $this->makeOffer($provider);
$this->actingAs($provider)->putJson("/api/provider/offers/{$offer->id}", [
'title' => 'Updated Title',
'price' => 9999,
])->assertOk()->assertJsonPath('status', true);
$this->assertDatabaseHas('diving_offers', [
'id' => $offer->id,
'title' => 'Updated Title',
'price' => 9999,
]);
}
public function test_provider_cannot_update_others_offer(): void
{
$ownerProvider = $this->makeProvider();
$otherProvider = $this->makeProvider();
$offer = $this->makeOffer($ownerProvider);
$this->actingAs($otherProvider)
->putJson("/api/provider/offers/{$offer->id}", ['title' => 'Hacked Title'])
->assertStatus(403);
}
// ── destroy ──────────────────────────────────────────────
public function test_provider_deletes_own_offer(): void
{
$provider = $this->makeProvider();
$offer = $this->makeOffer($provider);
$this->actingAs($provider)
->deleteJson("/api/provider/offers/{$offer->id}")
->assertOk()
->assertJsonPath('status', true);
$this->assertDatabaseMissing('diving_offers', ['id' => $offer->id]);
}
public function test_provider_cannot_delete_others_offer(): void
{
$ownerProvider = $this->makeProvider();
$otherProvider = $this->makeProvider();
$offer = $this->makeOffer($ownerProvider);
$this->actingAs($otherProvider)
->deleteJson("/api/provider/offers/{$offer->id}")
->assertStatus(403);
$this->assertDatabaseHas('diving_offers', ['id' => $offer->id]);
}
}