create([ 'role' => 'moderator', 'username' => 'worldchallenge-' . Str::lower(Str::random(6)), ]); return World::factory()->create(array_merge([ 'created_by_user_id' => $moderator->id, 'status' => World::STATUS_PUBLISHED, 'published_at' => now()->subDay(), 'accepts_submissions' => true, 'participation_mode' => World::PARTICIPATION_MODE_MANUAL_APPROVAL, 'submission_note_enabled' => true, 'community_section_enabled' => true, 'allow_readd_after_removal' => true, 'submission_starts_at' => now()->subDay(), 'submission_ends_at' => now()->addDays(7), ], $attributes)); } function worldUpdatePayload(World $world, array $overrides = []): array { return array_merge([ 'title' => $world->title, 'status' => $world->status, 'type' => $world->type, 'tagline' => $world->tagline, 'summary' => $world->summary, 'description' => $world->description, 'accepts_submissions' => (bool) $world->accepts_submissions, 'participation_mode' => $world->participation_mode, 'submission_note_enabled' => (bool) $world->submission_note_enabled, 'community_section_enabled' => (bool) $world->community_section_enabled, 'allow_readd_after_removal' => (bool) $world->allow_readd_after_removal, 'is_featured' => (bool) $world->is_featured, 'is_active_campaign' => (bool) $world->is_active_campaign, 'is_homepage_featured' => (bool) $world->is_homepage_featured, 'is_recurring' => (bool) $world->is_recurring, 'cta_label' => $world->cta_label, 'cta_url' => $world->cta_url, 'badge_label' => $world->badge_label, 'badge_description' => $world->badge_description, 'badge_url' => $world->badge_url, 'linked_challenge_id' => $world->linked_challenge_id, 'show_linked_challenge_section' => (bool) ($world->show_linked_challenge_section ?? true), 'show_linked_challenge_entries' => (bool) ($world->show_linked_challenge_entries ?? true), 'show_linked_challenge_winners' => (bool) ($world->show_linked_challenge_winners ?? true), 'show_linked_challenge_finalists' => (bool) ($world->show_linked_challenge_finalists ?? true), 'auto_grant_challenge_world_rewards' => (bool) ($world->auto_grant_challenge_world_rewards ?? true), 'challenge_teaser_override' => $world->challenge_teaser_override, 'relations' => [], ], $overrides); } function linkedGroupChallenge(Group $group, User $owner, array $attributes = []): GroupChallenge { return GroupChallenge::query()->create(array_merge([ 'group_id' => $group->id, 'title' => 'Pixel Week Finals', 'slug' => 'pixel-week-finals-' . Str::lower(Str::random(6)), 'summary' => 'Challenge finale.', 'description' => 'Challenge finale description.', 'visibility' => GroupChallenge::VISIBILITY_PUBLIC, 'participation_scope' => GroupChallenge::PARTICIPATION_PUBLIC, 'status' => GroupChallenge::STATUS_ACTIVE, 'start_at' => now()->subDay(), 'end_at' => now()->addDay(), 'created_by_user_id' => $owner->id, 'featured_artwork_id' => null, ], $attributes)); } it('syncs winner rewards from linked challenge outcomes', function (): void { $moderator = User::factory()->create(['role' => 'moderator']); $creator = User::factory()->create(); $groupOwner = User::factory()->create(); $group = Group::factory()->for($groupOwner, 'owner')->create(); $world = challengeLinkedWorld($moderator); $artwork = Artwork::factory()->for($creator)->create([ 'group_id' => $group->id, 'title' => 'Challenge Winner Artwork', 'slug' => 'challenge-winner-artwork', 'artwork_status' => 'published', 'published_at' => now()->subDay(), 'is_public' => true, 'visibility' => Artwork::VISIBILITY_PUBLIC, ]); $submission = WorldSubmission::query()->create([ 'world_id' => $world->id, 'artwork_id' => $artwork->id, 'submitted_by_user_id' => $creator->id, 'status' => WorldSubmission::STATUS_LIVE, 'reviewed_by_user_id' => $moderator->id, 'reviewed_at' => now()->subHour(), ]); $challenge = linkedGroupChallenge($group, $groupOwner); $challenge->artworks()->attach($artwork->id, ['submitted_by_user_id' => $groupOwner->id, 'sort_order' => 0]); $world->worldRelations()->create([ 'section_key' => 'related_programming', 'related_type' => 'challenge', 'related_id' => $challenge->id, 'context_label' => 'Challenge finale', 'sort_order' => 0, 'is_featured' => true, ]); app(GroupChallengeService::class)->update($challenge, $groupOwner, [ 'outcomes' => [[ 'artwork_id' => $artwork->id, 'outcome_type' => 'winner', 'position' => 1, 'sort_order' => 0, 'title_override' => 'Grand Winner', ]], ]); $this->assertDatabaseHas('world_reward_grants', [ 'user_id' => $creator->id, 'world_id' => $world->id, 'artwork_id' => $artwork->id, 'world_submission_id' => $submission->id, 'reward_type' => 'winner', 'grant_source' => 'challenge', ]); }); it('syncs finalist rewards from linked challenge outcomes', function (): void { $moderator = User::factory()->create(['role' => 'moderator']); $creator = User::factory()->create(); $groupOwner = User::factory()->create(); $group = Group::factory()->for($groupOwner, 'owner')->create(); $world = challengeLinkedWorld($moderator); $artwork = Artwork::factory()->for($creator)->create([ 'group_id' => $group->id, 'title' => 'Challenge Finalist Artwork', 'slug' => 'challenge-finalist-artwork', 'artwork_status' => 'published', 'published_at' => now()->subDay(), 'is_public' => true, 'visibility' => Artwork::VISIBILITY_PUBLIC, ]); $submission = WorldSubmission::query()->create([ 'world_id' => $world->id, 'artwork_id' => $artwork->id, 'submitted_by_user_id' => $creator->id, 'status' => WorldSubmission::STATUS_LIVE, 'reviewed_by_user_id' => $moderator->id, 'reviewed_at' => now()->subHour(), ]); $challenge = linkedGroupChallenge($group, $groupOwner); $challenge->artworks()->attach($artwork->id, ['submitted_by_user_id' => $groupOwner->id, 'sort_order' => 0]); $world->worldRelations()->create([ 'section_key' => 'related_programming', 'related_type' => 'challenge', 'related_id' => $challenge->id, 'context_label' => 'Challenge finale', 'sort_order' => 0, 'is_featured' => true, ]); app(GroupChallengeService::class)->update($challenge, $groupOwner, [ 'outcomes' => [[ 'artwork_id' => $artwork->id, 'outcome_type' => 'finalist', 'sort_order' => 0, 'note' => 'Finalist award.', ]], ]); $this->assertDatabaseHas('world_reward_grants', [ 'user_id' => $creator->id, 'world_id' => $world->id, 'artwork_id' => $artwork->id, 'world_submission_id' => $submission->id, 'reward_type' => 'finalist', 'grant_source' => 'challenge', ]); }); it('syncs challenge winner rewards when challenge relations are added to a world', function (): void { $moderator = User::factory()->create(['role' => 'moderator']); $creator = User::factory()->create(); $groupOwner = User::factory()->create(); $group = Group::factory()->for($groupOwner, 'owner')->create(); $world = challengeLinkedWorld($moderator); $artwork = Artwork::factory()->for($creator)->create([ 'group_id' => $group->id, 'title' => 'Relation Sync Artwork', 'slug' => 'relation-sync-artwork', 'artwork_status' => 'published', 'published_at' => now()->subDay(), 'is_public' => true, 'visibility' => Artwork::VISIBILITY_PUBLIC, ]); WorldSubmission::query()->create([ 'world_id' => $world->id, 'artwork_id' => $artwork->id, 'submitted_by_user_id' => $creator->id, 'status' => WorldSubmission::STATUS_LIVE, 'reviewed_by_user_id' => $moderator->id, 'reviewed_at' => now()->subHour(), ]); $challenge = linkedGroupChallenge($group, $groupOwner, [ 'featured_artwork_id' => $artwork->id, ]); app(WorldService::class)->update($world, $moderator, worldUpdatePayload($world, [ 'relations' => [[ 'section_key' => 'related_programming', 'related_type' => 'challenge', 'related_id' => $challenge->id, 'context_label' => 'Challenge finale', 'sort_order' => 0, 'is_featured' => true, ]], ])); $this->assertDatabaseHas('world_reward_grants', [ 'user_id' => $creator->id, 'world_id' => $world->id, 'reward_type' => 'winner', 'grant_source' => 'challenge', ]); }); it('syncs challenge winner rewards when a primary linked challenge is set on a world', function (): void { $moderator = User::factory()->create(['role' => 'moderator']); $creator = User::factory()->create(); $groupOwner = User::factory()->create(); $group = Group::factory()->for($groupOwner, 'owner')->create(); $world = challengeLinkedWorld($moderator); $artwork = Artwork::factory()->for($creator)->create([ 'group_id' => $group->id, 'title' => 'Primary Challenge Sync Artwork', 'slug' => 'primary-challenge-sync-artwork', 'artwork_status' => 'published', 'published_at' => now()->subDay(), 'is_public' => true, 'visibility' => Artwork::VISIBILITY_PUBLIC, ]); WorldSubmission::query()->create([ 'world_id' => $world->id, 'artwork_id' => $artwork->id, 'submitted_by_user_id' => $creator->id, 'status' => WorldSubmission::STATUS_LIVE, 'reviewed_by_user_id' => $moderator->id, 'reviewed_at' => now()->subHour(), ]); $challenge = linkedGroupChallenge($group, $groupOwner, [ 'featured_artwork_id' => $artwork->id, ]); app(WorldService::class)->update($world, $moderator, worldUpdatePayload($world, [ 'linked_challenge_id' => $challenge->id, ])); $this->assertDatabaseHas('world_reward_grants', [ 'user_id' => $creator->id, 'world_id' => $world->id, 'reward_type' => 'winner', 'grant_source' => 'challenge', ]); }); it('revokes challenge-sourced winner rewards when linked challenge winners are cleared', function (): void { $moderator = User::factory()->create(['role' => 'moderator']); $creator = User::factory()->create(); $groupOwner = User::factory()->create(); $group = Group::factory()->for($groupOwner, 'owner')->create(); $world = challengeLinkedWorld($moderator); $artwork = Artwork::factory()->for($creator)->create([ 'group_id' => $group->id, 'title' => 'Revoked Challenge Winner', 'slug' => 'revoked-challenge-winner', 'artwork_status' => 'published', 'published_at' => now()->subDay(), 'is_public' => true, 'visibility' => Artwork::VISIBILITY_PUBLIC, ]); $submission = WorldSubmission::query()->create([ 'world_id' => $world->id, 'artwork_id' => $artwork->id, 'submitted_by_user_id' => $creator->id, 'status' => WorldSubmission::STATUS_LIVE, 'reviewed_by_user_id' => $moderator->id, 'reviewed_at' => now()->subHour(), ]); $challenge = linkedGroupChallenge($group, $groupOwner); $world->worldRelations()->create([ 'section_key' => 'related_programming', 'related_type' => 'challenge', 'related_id' => $challenge->id, 'context_label' => 'Challenge finale', 'sort_order' => 0, 'is_featured' => true, ]); $challengeService = app(GroupChallengeService::class); $challengeService->update($challenge, $groupOwner, ['featured_artwork_id' => $artwork->id]); $this->assertDatabaseHas('world_reward_grants', [ 'user_id' => $creator->id, 'world_id' => $world->id, 'world_submission_id' => $submission->id, 'reward_type' => 'winner', 'grant_source' => 'challenge', ]); $challengeService->update($challenge->fresh(), $groupOwner, ['featured_artwork_id' => null]); $this->assertDatabaseMissing('world_reward_grants', [ 'user_id' => $creator->id, 'world_id' => $world->id, 'reward_type' => 'winner', 'grant_source' => 'challenge', ]); });