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°ree=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), ]); } }