Files
SkinbaseNova/tests/Feature/Worlds/WorldChallengeRewardSyncTest.php
2026-04-25 08:36:03 +02:00

348 lines
13 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\Artwork;
use App\Models\Group;
use App\Models\GroupChallenge;
use App\Models\User;
use App\Models\World;
use App\Models\WorldSubmission;
use App\Services\GroupChallengeService;
use App\Services\Worlds\WorldService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Str;
uses(RefreshDatabase::class);
function challengeLinkedWorld(?User $moderator = null, array $attributes = []): World
{
$moderator ??= User::factory()->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',
]);
});