You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

184 lines
6.3 KiB

1 month ago
<?php
namespace Tests\Feature;
use App\Models\AdminUser;
use App\Models\AudienceRegistration;
use App\Models\AudienceSession;
use App\Models\Competition;
use App\Models\CompetitionAdmin;
use App\Models\User;
use App\Support\BizDateTime;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Laravel\Sanctum\Sanctum;
use Tests\TestCase;
class CompetitionAdminAccessTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
Carbon::setTestNow(Carbon::parse('2026-08-18 13:00:00', BizDateTime::TIMEZONE));
}
protected function tearDown(): void
{
Carbon::setTestNow();
parent::tearDown();
}
public function test_platform_admin_can_login_via_manage_portal(): void
{
$competition = $this->publishedCompetition();
$this->platformAdmin('admin', 'Admin123456');
$this->postJson('/api/v1/manage/auth/login', [
'username' => 'admin',
'password' => 'Admin123456',
'competition_slug' => $competition->slug,
])->assertOk()
->assertJsonPath('kind', 'platform')
->assertJsonPath('competition.slug', $competition->slug);
}
public function test_competition_admin_can_login_only_to_own_event(): void
{
$competition = $this->publishedCompetition();
$other = $this->publishedCompetition('other-2026');
$this->competitionAdmin($competition, 'hsxt_admin', 'Pass123456');
$this->postJson('/api/v1/manage/auth/login', [
'username' => 'hsxt_admin',
'password' => 'Pass123456',
'competition_slug' => $competition->slug,
])->assertOk()
->assertJsonPath('kind', 'competition');
$this->postJson('/api/v1/manage/auth/login', [
'username' => 'hsxt_admin',
'password' => 'Pass123456',
'competition_slug' => $other->slug,
])->assertUnprocessable();
}
public function test_competition_admin_cannot_access_platform_admin_users(): void
{
$competition = $this->publishedCompetition();
Sanctum::actingAs($this->competitionAdmin($competition));
$this->getJson('/api/v1/admin/admin-users')->assertForbidden();
$this->getJson('/api/v1/admin/competitions')->assertForbidden();
}
public function test_competition_admin_can_manage_own_audience_but_not_other_event(): void
{
$competition = $this->publishedCompetition();
$other = $this->publishedCompetition('other-2026');
Sanctum::actingAs($this->competitionAdmin($competition));
$this->getJson("/api/v1/admin/competitions/{$competition->id}/audience-registrations")
->assertOk();
$this->getJson("/api/v1/admin/competitions/{$other->id}/audience-registrations")
->assertForbidden();
}
public function test_platform_admin_can_crud_competition_admins(): void
{
Sanctum::actingAs($this->platformAdmin());
$competition = $this->publishedCompetition();
$this->postJson("/api/v1/admin/competitions/{$competition->id}/admins", [
'username' => 'event_admin',
'name' => '赛事管理员',
'password' => 'Pass123456',
'status' => 'active',
])->assertCreated()
->assertJsonPath('username', 'event_admin');
$this->assertDatabaseHas('competition_admins', [
'competition_id' => $competition->id,
'username' => 'event_admin',
]);
}
public function test_competition_admin_can_checkin(): void
{
$competition = $this->publishedCompetition();
$session = $this->openSession($competition);
$user = User::query()->create(['mobile' => '13800138200']);
$registration = AudienceRegistration::query()->create([
'competition_id' => $competition->id,
'audience_session_id' => $session->id,
'user_id' => $user->id,
'name' => '现场观众',
'phone' => '13800138200',
'purpose' => '项目交流',
'registered_at' => now(),
]);
Sanctum::actingAs($this->competitionAdmin($competition));
$this->postJson("/api/v1/admin/competitions/{$competition->id}/audience-checkin", [
'ticket_code' => $registration->ticket_code,
])->assertOk()
->assertJsonPath('already_checked_in', false);
$this->assertNotNull($registration->fresh()->checked_in_by_competition_admin_id);
}
private function publishedCompetition(string $slug = 'husu-2026'): Competition
{
return Competition::query()->create([
'slug' => $slug,
'name' => '沪苏协同新兴产业科创大赛',
'published' => true,
'status' => 'signup_open',
]);
}
/**
* @param array<string, mixed> $overrides
*/
private function openSession(Competition $competition, array $overrides = []): AudienceSession
{
return AudienceSession::query()->create(array_merge([
'competition_id' => $competition->id,
'code' => 'shanghai-preliminary',
'name' => '上海预赛路演',
'event_date_text' => '2026年9月18日星期五',
'address' => '上海',
'capacity' => 300,
'signup_start_at' => Carbon::parse('2026-08-10 00:00:00', BizDateTime::TIMEZONE),
'signup_end_at' => Carbon::parse('2026-09-10 23:59:59', BizDateTime::TIMEZONE),
'is_final' => false,
'sort' => 1,
'enabled' => true,
], $overrides));
}
private function platformAdmin(string $username = 'platform', string $password = 'Admin123456'): AdminUser
{
return AdminUser::query()->create([
'username' => $username,
'password_hash' => $password,
'name' => '平台管理员',
'status' => 'active',
]);
}
private function competitionAdmin(Competition $competition, string $username = 'event_admin', string $password = 'Pass123456'): CompetitionAdmin
{
return CompetitionAdmin::query()->create([
'competition_id' => $competition->id,
'username' => $username,
'password_hash' => $password,
'name' => '赛事管理员',
'status' => 'active',
]);
}
}