seedReviewerWithRegions(); $this->actingAsReviewer($ctx['reviewer']); $this->getJson('/api/v1/review/applications?competition_slug=hsxt&event_location=suzhou') ->assertOk() ->assertJsonPath('meta.total', 1) ->assertJsonPath('data.0.id', $ctx['shanghai']->id); $ids = collect($this->getJson('/api/v1/review/applications?competition_slug=hsxt')->json('data'))->pluck('id'); $this->assertTrue($ids->contains($ctx['shanghai']->id)); $this->assertFalse($ids->contains($ctx['suzhou']->id)); $this->getJson('/api/v1/review/applications/'.$ctx['suzhou']->id.'?competition_slug=hsxt') ->assertForbidden(); $this->postJson('/api/v1/review/applications/'.$ctx['suzhou']->id.'/score', [ 'competition_slug' => 'hsxt', 'payload' => ['scores' => ['a' => 10, 'b' => 20]], ])->assertForbidden(); } public function test_score_submit_requires_item_scores_and_stores_sum(): void { $ctx = $this->seedReviewerWithRegions(); $this->actingAsReviewer($ctx['reviewer']); $this->postJson('/api/v1/review/applications/'.$ctx['shanghai']->id.'/score', [ 'competition_slug' => 'hsxt', 'payload' => ['line_total' => 88], ])->assertUnprocessable(); $this->postJson('/api/v1/review/applications/'.$ctx['shanghai']->id.'/score', [ 'competition_slug' => 'hsxt', 'payload' => ['scores' => ['a' => 40.01, 'b' => 0]], ])->assertUnprocessable(); $this->postJson('/api/v1/review/applications/'.$ctx['shanghai']->id.'/score', [ 'competition_slug' => 'hsxt', 'payload' => ['scores' => ['a' => 12.5, 'b' => 30]], ])->assertOk() ->assertJsonPath('data.my_review_score.line_total', '42.50') ->assertJsonPath('data.my_review_score.payload_json.scores.a', 12.5) ->assertJsonPath('data.my_review_score.payload_json.line_total', 42.5); } public function test_suzhou_reviewer_sees_chinese_and_english_location_values(): void { $ctx = $this->seedSuzhouReviewerWithMixedLocationLabels(); $this->actingAsReviewer($ctx['reviewer']); $ids = collect($this->getJson('/api/v1/review/applications?competition_slug=hsxt')->json('data')) ->pluck('id') ->all(); $this->assertContains($ctx['zh']->id, $ids); $this->assertContains($ctx['en']->id, $ids); $this->assertContains($ctx['code']->id, $ids); $this->assertNotContains($ctx['shanghai']->id, $ids); $this->getJson('/api/v1/review/applications/'.$ctx['zh']->id.'?competition_slug=hsxt') ->assertOk() ->assertJsonPath('data.id', $ctx['zh']->id); $this->getJson('/api/v1/review/applications/'.$ctx['en']->id.'?competition_slug=hsxt') ->assertOk(); } public function test_suzhou_reviewer_sees_other_track_in_same_region(): void { $ctx = $this->seedSuzhouReviewerWithMixedLocationLabels(); $other = Application::query()->create([ 'user_id' => User::query()->create(['mobile' => '13800138105'])->id, 'competition_id' => $ctx['code']->competition_id, 'status' => 'submitted', 'review_eligible' => true, 'track' => 'other', 'event_location' => 'suzhou', 'project_name' => '其他赛道苏州项目', 'degree' => 'bachelor', 'location_country' => 'cn', 'location_province' => '江苏省', 'location_city' => '苏州市', ]); $this->actingAsReviewer($ctx['reviewer']); $list = $this->getJson('/api/v1/review/applications?competition_slug=hsxt')->assertOk(); $ids = collect($list->json('data'))->pluck('id')->all(); $this->assertContains($other->id, $ids); $row = collect($list->json('data'))->firstWhere('id', $other->id); $this->assertSame('本科', $row['degree']); $this->assertSame('中国 / 江苏省 / 苏州市', $row['location_label']); $this->assertSame('苏州', $row['event_location_label']); $this->getJson('/api/v1/review/applications/'.$other->id.'?competition_slug=hsxt') ->assertOk(); } public function test_english_signup_shows_english_option_labels(): void { $ctx = $this->seedSuzhouReviewerWithMixedLocationLabels(); $en = Application::query()->create([ 'user_id' => User::query()->create(['mobile' => '13800138106'])->id, 'competition_id' => $ctx['code']->competition_id, 'status' => 'submitted', 'review_eligible' => true, 'track' => 'track_a', 'event_location' => 'suzhou', 'project_name' => 'English Suzhou Project', 'signup_locale' => PortalLocale::EN, 'degree' => 'bachelor', 'location_country' => 'cn', 'location_province' => 'Jiangsu', 'location_city' => 'Suzhou', ]); $this->actingAsReviewer($ctx['reviewer']); $list = $this->getJson('/api/v1/review/applications?competition_slug=hsxt')->assertOk(); $row = collect($list->json('data'))->firstWhere('id', $en->id); $this->assertSame('Bachelor’s Degree', $row['degree']); $this->assertSame('China / Jiangsu / Suzhou', $row['location_label']); $this->assertSame('Suzhou', $row['event_location_label']); $this->getJson('/api/v1/review/applications/'.$en->id.'?competition_slug=hsxt') ->assertOk() ->assertJsonPath('data.degree_label', 'Bachelor’s Degree') ->assertJsonPath('data.location_country_label', 'China') ->assertJsonPath('data.event_location_label', 'Suzhou'); } private function actingAsReviewer(Reviewer $reviewer): void { $token = $reviewer->createToken('review')->plainTextToken; $this->withToken($token); } /** * @return array{reviewer: Reviewer, shanghai: Application, suzhou: Application} */ private function seedReviewerWithRegions(): array { $competition = Competition::query()->create([ 'slug' => 'hsxt', 'name' => '沪苏协同新兴产业科创大赛', 'published' => true, 'status' => 'signup_open', ]); CompetitionTrack::query()->create([ 'competition_id' => $competition->id, 'track_code' => 'track_a', 'title' => '赛道A', 'sort' => 1, 'is_enabled' => true, 'scoring_sheet_json' => [ 'version' => 1, 'title' => '测试评分表', 'items' => [ [ 'key' => 'a', 'sort' => 1, 'category' => '通用', 'title' => 'A', 'criteria' => '', 'max_score' => 40, ], [ 'key' => 'b', 'sort' => 2, 'category' => '通用', 'title' => 'B', 'criteria' => '', 'max_score' => 60, ], ], ], ]); $reviewer = Reviewer::query()->create([ 'mobile' => '13900139001', 'username' => 'rev_sh', 'name' => '上海评委', 'status' => 'active', 'password_hash' => 'unused', ]); ReviewerScope::query()->create([ 'reviewer_id' => $reviewer->id, 'competition_id' => $competition->id, 'track_code' => 'track_a', 'event_location' => 'shanghai', ]); $userA = User::query()->create(['mobile' => '13800138001']); $userB = User::query()->create(['mobile' => '13800138002']); $shanghai = Application::query()->create([ 'user_id' => $userA->id, 'competition_id' => $competition->id, 'status' => 'submitted', 'review_eligible' => true, 'track' => 'track_a', 'event_location' => 'shanghai', 'project_name' => '上海项目', ]); $suzhou = Application::query()->create([ 'user_id' => $userB->id, 'competition_id' => $competition->id, 'status' => 'submitted', 'review_eligible' => true, 'track' => 'track_a', 'event_location' => 'suzhou', 'project_name' => '苏州项目', ]); return [ 'reviewer' => $reviewer, 'shanghai' => $shanghai, 'suzhou' => $suzhou, ]; } /** * @return array{reviewer: Reviewer, zh: Application, en: Application, code: Application, shanghai: Application} */ private function seedSuzhouReviewerWithMixedLocationLabels(): array { $competition = Competition::query()->create([ 'slug' => 'hsxt', 'name' => '沪苏协同新兴产业科创大赛', 'published' => true, 'status' => 'signup_open', ]); CompetitionTrack::query()->create([ 'competition_id' => $competition->id, 'track_code' => 'track_a', 'title' => '赛道A', 'sort' => 1, 'is_enabled' => true, ]); $reviewer = Reviewer::query()->create([ 'mobile' => '13900139008', 'username' => 'rev_sz', 'name' => '苏州评委', 'status' => 'active', 'password_hash' => 'unused', ]); ReviewerScope::query()->create([ 'reviewer_id' => $reviewer->id, 'competition_id' => $competition->id, 'track_code' => 'track_a', 'event_location' => 'suzhou', ]); $makeApp = function (string $mobile, string $location, string $name) use ($competition): Application { return Application::query()->create([ 'user_id' => User::query()->create(['mobile' => $mobile])->id, 'competition_id' => $competition->id, 'status' => 'submitted', 'review_eligible' => true, 'track' => 'track_a', 'event_location' => $location, 'project_name' => $name, ]); }; return [ 'reviewer' => $reviewer, 'zh' => $makeApp('13800138101', '苏州', '中文苏州项目'), 'en' => $makeApp('13800138102', 'Suzhou', '英文苏州项目'), 'code' => $makeApp('13800138103', 'suzhou', '编码苏州项目'), 'shanghai' => $makeApp('13800138104', 'shanghai', '上海项目'), ]; } }