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.

378 lines
14 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
namespace Tests\Feature;
use App\Models\AdminUser;
use App\Models\Application;
use App\Models\AudienceRegistration;
use App\Models\AudienceSession;
use App\Models\Competition;
use App\Models\CompetitionPortalLocale;
use App\Models\FormSchemaDefinition;
use App\Models\SmsVerification;
use App\Models\User;
use App\Support\PortalLocale;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Laravel\Sanctum\Sanctum;
use Tests\TestCase;
class PortalLocaleSignupTest extends TestCase
{
use RefreshDatabase;
public function test_unsubmitted_login_follows_requested_lang_instead_of_locking(): void
{
$competition = $this->publishedCompetition();
$this->issueSms('13800138111', '123456');
$this->postJson('/api/auth/sms/login', [
'mobile' => '13800138111',
'code' => '123456',
'competition_slug' => $competition->slug,
'lang' => 'en',
])->assertOk()
->assertJsonPath('signup_locale', PortalLocale::EN)
->assertJsonPath('signup_locale_locked', false);
$this->assertDatabaseHas('applications', [
'competition_id' => $competition->id,
'signup_locale' => PortalLocale::EN,
]);
$this->issueSms('13800138111', '654321');
$this->postJson('/api/auth/sms/login', [
'mobile' => '13800138111',
'code' => '654321',
'competition_slug' => $competition->slug,
])->assertOk()
->assertJsonPath('signup_locale', PortalLocale::ZH)
->assertJsonPath('signup_locale_locked', false);
$this->assertDatabaseHas('applications', [
'competition_id' => $competition->id,
'signup_locale' => PortalLocale::ZH,
]);
}
public function test_chinese_and_english_submissions_store_the_same_degree_code(): void
{
$competition = $this->publishedCompetition();
$zhUser = User::query()->create(['mobile' => '13800138121']);
$enUser = User::query()->create(['mobile' => '13800138122']);
$zhApp = Application::query()->create([
'user_id' => $zhUser->id,
'competition_id' => $competition->id,
'status' => 'draft',
'signup_locale' => PortalLocale::ZH,
]);
$enApp = Application::query()->create([
'user_id' => $enUser->id,
'competition_id' => $competition->id,
'status' => 'draft',
'signup_locale' => PortalLocale::EN,
]);
Sanctum::actingAs($zhUser);
$this->postJson('/api/applications/current/save?competition_slug='.$competition->slug, [
'degree' => 'bachelor',
'project_name' => '中文项目',
])->assertOk()
->assertJsonPath('degree', 'bachelor');
Sanctum::actingAs($enUser);
$this->postJson('/api/applications/current/save?competition_slug='.$competition->slug, [
'degree' => 'bachelor',
'project_name' => 'English project',
])->assertOk()
->assertJsonPath('degree', 'bachelor');
$this->assertSame('bachelor', $zhApp->fresh()->degree);
$this->assertSame('bachelor', $enApp->fresh()->degree);
Sanctum::actingAs(AdminUser::query()->create([
'username' => 'locale_admin',
'password_hash' => 'unused',
'name' => '管理员',
'status' => 'active',
]));
$zhApp->forceFill(['status' => 'submitted', 'submitted_at' => now()])->save();
$enApp->forceFill(['status' => 'submitted', 'submitted_at' => now()])->save();
$list = $this->getJson("/api/v1/admin/competitions/{$competition->id}/applications?status=submitted")
->assertOk()
->json('data');
$byId = collect($list)->keyBy('id');
$this->assertSame('bachelor', $byId[$zhApp->id]['degree']);
$this->assertSame('本科', $byId[$zhApp->id]['degree_label']);
$this->assertSame('中文填报', $byId[$zhApp->id]['signup_locale_label']);
$this->assertSame('bachelor', $byId[$enApp->id]['degree']);
$this->assertSame('Bachelor’s Degree', $byId[$enApp->id]['degree_label']);
$this->assertSame('英文填报', $byId[$enApp->id]['signup_locale_label']);
$enOnly = $this->getJson("/api/v1/admin/competitions/{$competition->id}/applications?status=submitted&signup_locale=en")
->assertOk()
->json('data');
$this->assertSame([$enApp->id], collect($enOnly)->pluck('id')->all());
}
public function test_audience_unsubmitted_login_follows_requested_lang(): void
{
$competition = $this->publishedCompetition();
$this->issueSms('13800138131', '111111', SmsVerification::SCENE_AUDIENCE_LOGIN);
$this->postJson('/api/auth/sms/audience/login', [
'mobile' => '13800138131',
'code' => '111111',
'competition_slug' => $competition->slug,
'lang' => 'en',
])->assertOk()
->assertJsonPath('signup_locale', PortalLocale::EN)
->assertJsonPath('signup_locale_locked', false);
$this->issueSms('13800138131', '222222', SmsVerification::SCENE_AUDIENCE_LOGIN);
$this->postJson('/api/auth/sms/audience/login', [
'mobile' => '13800138131',
'code' => '222222',
'competition_slug' => $competition->slug,
])->assertOk()
->assertJsonPath('signup_locale', PortalLocale::ZH)
->assertJsonPath('signup_locale_locked', false);
}
public function test_audience_registration_locks_locale_on_later_login(): void
{
$competition = $this->publishedCompetition();
$user = User::query()->create(['mobile' => '13800138132']);
$session = AudienceSession::query()->create([
'competition_id' => $competition->id,
'code' => 'locale-session',
'name' => '测试场次',
'event_date_text' => '2026年9月18日',
'address' => '上海市',
'capacity' => 10,
'signup_start_at' => now()->subDay(),
'signup_end_at' => now()->addDay(),
'is_final' => false,
'sort' => 1,
'enabled' => true,
]);
AudienceRegistration::query()->create([
'competition_id' => $competition->id,
'audience_session_id' => $session->id,
'user_id' => $user->id,
'name' => '已报名观众',
'phone' => '13800138132',
'company' => '朗业',
'purpose' => 'networking',
'registered_at' => now(),
]);
CompetitionPortalLocale::query()->create([
'user_id' => $user->id,
'competition_id' => $competition->id,
'portal' => PortalLocale::PORTAL_AUDIENCE,
'locale' => PortalLocale::EN,
]);
$this->issueSms('13800138132', '333333', SmsVerification::SCENE_AUDIENCE_LOGIN);
$this->postJson('/api/auth/sms/audience/login', [
'mobile' => '13800138132',
'code' => '333333',
'competition_slug' => $competition->slug,
])->assertOk()
->assertJsonPath('signup_locale', PortalLocale::EN)
->assertJsonPath('signup_locale_locked', true);
}
public function test_draft_application_follows_english_login_link(): void
{
$competition = $this->publishedCompetition();
$user = User::query()->create(['mobile' => '13800138141']);
Application::query()->create([
'user_id' => $user->id,
'competition_id' => $competition->id,
'status' => 'draft',
'signup_locale' => PortalLocale::ZH,
]);
$this->issueSms('13800138141', '333333');
$this->postJson('/api/auth/sms/login', [
'mobile' => '13800138141',
'code' => '333333',
'competition_slug' => $competition->slug,
'lang' => 'en',
])->assertOk()
->assertJsonPath('signup_locale', PortalLocale::EN)
->assertJsonPath('signup_locale_locked', false);
$this->assertDatabaseHas('applications', [
'user_id' => $user->id,
'competition_id' => $competition->id,
'signup_locale' => PortalLocale::EN,
]);
}
public function test_submitted_application_stays_on_submit_locale(): void
{
$competition = $this->publishedCompetition();
$user = User::query()->create(['mobile' => '13800138142']);
Application::query()->create([
'user_id' => $user->id,
'competition_id' => $competition->id,
'status' => 'submitted',
'submitted_at' => now(),
'signup_locale' => PortalLocale::ZH,
]);
CompetitionPortalLocale::query()->create([
'user_id' => $user->id,
'competition_id' => $competition->id,
'portal' => PortalLocale::PORTAL_PARTICIPANT,
'locale' => PortalLocale::ZH,
]);
$this->issueSms('13800138142', '444444');
$this->postJson('/api/auth/sms/login', [
'mobile' => '13800138142',
'code' => '444444',
'competition_slug' => $competition->slug,
'lang' => 'en',
])->assertOk()
->assertJsonPath('signup_locale', PortalLocale::ZH)
->assertJsonPath('signup_locale_locked', true);
}
public function test_draft_save_follows_request_lang(): void
{
$competition = $this->publishedCompetition();
$user = User::query()->create(['mobile' => '13800138143']);
Application::query()->create([
'user_id' => $user->id,
'competition_id' => $competition->id,
'status' => 'draft',
'signup_locale' => PortalLocale::ZH,
]);
Sanctum::actingAs($user);
$this->postJson('/api/applications/current/save?competition_slug='.$competition->slug.'&lang=en', [
'project_name' => 'English draft',
])->assertOk()
->assertJsonPath('signup_locale', PortalLocale::EN)
->assertJsonPath('signup_locale_locked', false);
}
public function test_admin_list_filters_schema_codes_and_shows_locale_labels(): void
{
$competition = $this->publishedCompetition();
$schema = FormSchemaDefinition::query()->create([
'competition_id' => $competition->id,
'purpose' => FormSchemaDefinition::PURPOSE_SIGNUP,
'name' => '双语报名表',
'version' => 1,
'is_published' => true,
'schema_json' => [
[
'key' => 'degree',
'type' => 'select',
'label' => '最高学历',
'list' => true,
'filter' => true,
'options' => [
['value' => 'bachelor', 'label' => '本科', 'label_i18n' => ['en' => "Bachelor’s Degree"]],
],
],
[
'key' => 'industry',
'type' => 'select',
'label' => '行业',
'list' => true,
'filter' => true,
'options' => [
['value' => 'ai', 'label' => '人工智能', 'label_i18n' => ['en' => 'Artificial Intelligence']],
],
],
],
]);
$competition->update(['form_schema_id' => $schema->id]);
$zhUser = User::query()->create(['mobile' => '13800138151']);
$enUser = User::query()->create(['mobile' => '13800138152']);
$zhApp = Application::query()->create([
'user_id' => $zhUser->id,
'competition_id' => $competition->id,
'status' => 'submitted',
'submitted_at' => now(),
'signup_locale' => PortalLocale::ZH,
'degree' => 'bachelor',
'answers_json' => ['industry' => 'ai'],
]);
$enApp = Application::query()->create([
'user_id' => $enUser->id,
'competition_id' => $competition->id,
'status' => 'draft',
'signup_locale' => PortalLocale::EN,
'degree' => 'bachelor',
]);
Sanctum::actingAs($enUser);
$this->postJson('/api/applications/current/save?competition_slug='.$competition->slug, [
'industry' => 'ai',
])->assertOk()
->assertJsonPath('industry', 'ai');
$this->assertSame('ai', $enApp->fresh()->answers_json['industry'] ?? null);
$enApp->forceFill(['status' => 'submitted', 'submitted_at' => now()])->save();
Sanctum::actingAs(AdminUser::query()->create([
'username' => 'schema_locale_admin',
'password_hash' => 'unused',
'name' => '管理员',
'status' => 'active',
]));
$list = $this->getJson("/api/v1/admin/competitions/{$competition->id}/applications?status=submitted")
->assertOk()
->assertJsonPath('schema_list_fields.1.key', 'industry')
->json('data');
$byId = collect($list)->keyBy('id');
$this->assertSame('人工智能', $byId[$zhApp->id]['schema_display']['industry']);
$this->assertSame('Artificial Intelligence', $byId[$enApp->id]['schema_display']['industry']);
$degreeOnly = $this->getJson("/api/v1/admin/competitions/{$competition->id}/applications?status=submitted&degree=bachelor")
->assertOk()
->json('data');
$this->assertCount(2, $degreeOnly);
$industryOnly = $this->getJson("/api/v1/admin/competitions/{$competition->id}/applications?status=submitted&industry=ai")
->assertOk()
->json('data');
$this->assertCount(2, $industryOnly);
}
private function publishedCompetition(): Competition
{
return Competition::query()->create([
'slug' => 'locale-event',
'name' => '双语测试赛',
'published' => true,
'status' => 'signup_open',
]);
}
private function issueSms(
string $mobile,
string $code,
string $scene = SmsVerification::SCENE_PARTICIPANT_LOGIN
): void
{
SmsVerification::query()->create([
'scene' => $scene,
'mobile' => $mobile,
'code' => $code,
'provider' => SmsVerification::PROVIDER_DISABLED,
'status' => SmsVerification::STATUS_SENT,
'expires_at' => now()->addMinutes(5),
]);
}
}