|
|
|
|
|
<?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',
|
|
|
|
|
|
]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|