create(); $dynamic = Dynamic::factory()->create(); $dynamic->participants()->attach($owner->id, ['role' => 'owner']); $ledger = Ledger::factory()->create(['dynamic_id' => $dynamic->id, 'score' => 100]); $this->actingAs($owner); $response = $this->post(route('dynamics.ledgers.mutations.store', [$dynamic, $ledger]), [ 'amount' => 15, 'description' => 'Direct point reward', ]); $response->assertRedirect(route('dynamics.ledgers.show', [$dynamic, $ledger])); $mutation = Mutation::firstWhere('description', 'Direct point reward'); expect($mutation)->not->toBeNull(); expect($mutation->status)->toBe('approved'); // Score should be updated immediately $ledger->refresh(); expect($ledger->score)->toBe(115); // Verify chat messages (should NOT say "approved" or "Approved") $mutationChatMessages = $mutation->chat->messages; expect($mutationChatMessages)->toHaveCount(1); expect($mutationChatMessages->first()->user_id)->toBeNull(); expect($mutationChatMessages->first()->content)->toBe("Entry was created by id}>."); $dynamicChatMessages = $dynamic->chat->messages; expect($dynamicChatMessages)->toHaveCount(1); expect($dynamicChatMessages->first()->user_id)->toBeNull(); expect($dynamicChatMessages->first()->content)->toBe("id}> added entry \"Direct point reward\" for +15 points on \"{$ledger->name}\" ledger."); }); test('non-owner participant creates a suggestion which defaults to pending and says suggested', function () { $owner = User::factory()->create(); $participant = User::factory()->create(); $dynamic = Dynamic::factory()->create(); $dynamic->participants()->attach($owner->id, ['role' => 'owner']); $dynamic->participants()->attach($participant->id, ['role' => 'participant']); $ledger = Ledger::factory()->create(['dynamic_id' => $dynamic->id, 'score' => 100]); $this->actingAs($participant); $response = $this->post(route('dynamics.ledgers.mutations.store', [$dynamic, $ledger]), [ 'amount' => 10, 'description' => 'Suggested point reward', ]); $response->assertRedirect(route('dynamics.ledgers.show', [$dynamic, $ledger])); $mutation = Mutation::firstWhere('description', 'Suggested point reward'); expect($mutation)->not->toBeNull(); expect($mutation->status)->toBe('pending'); // Score should NOT be updated immediately $ledger->refresh(); expect($ledger->score)->toBe(100); // Verify chat messages $mutationChatMessages = $mutation->chat->messages; expect($mutationChatMessages)->toHaveCount(1); expect($mutationChatMessages->first()->user_id)->toBeNull(); expect($mutationChatMessages->first()->content)->toBe("Suggestion was created by id}>."); $dynamicChatMessages = $dynamic->chat->messages; expect($dynamicChatMessages)->toHaveCount(1); expect($dynamicChatMessages->first()->user_id)->toBeNull(); expect($dynamicChatMessages->first()->content)->toBe("id}> suggested \"Suggested point reward\" for +10 points on \"{$ledger->name}\" ledger."); }); test('owner can approve a pending suggestion and it is updated and logged', function () { $owner = User::factory()->create(); $participant = User::factory()->create(); $dynamic = Dynamic::factory()->create(); $dynamic->participants()->attach($owner->id, ['role' => 'owner']); $dynamic->participants()->attach($participant->id, ['role' => 'participant']); $ledger = Ledger::factory()->create(['dynamic_id' => $dynamic->id, 'score' => 100]); // Create a pending mutation for the participant $mutation = Mutation::factory()->create([ 'ledger_id' => $ledger->id, 'user_id' => $participant->id, 'amount' => 20, 'description' => 'Polished dungeon floors', 'status' => 'pending', ]); $this->actingAs($owner); $response = $this->put(route('dynamics.ledgers.mutations.update', [$dynamic, $ledger, $mutation]), [ 'status' => 'approved', ]); $response->assertRedirect(); $mutation->refresh(); expect($mutation->status)->toBe('approved'); $ledger->refresh(); expect($ledger->score)->toBe(120); // Verify system logs (should have the manual approval log now) $mutationChatMessages = $mutation->chat->messages; // Note: one from boot created (empty or via seeder, but in our factory it starts with 0 messages if not manually logged, // actually our model booted hook creates the chat but doesn't log on boot, the update method creates 1 message) expect($mutationChatMessages->last()->user_id)->toBeNull(); expect($mutationChatMessages->last()->content)->toBe("Suggestion was APPROVED by id}>."); $dynamicChatMessages = $dynamic->chat->messages; expect($dynamicChatMessages->last()->user_id)->toBeNull(); expect($dynamicChatMessages->last()->content)->toBe("id}> APPROVED the suggestion \"Polished dungeon floors\" for +20 points on \"{$ledger->name}\" ledger."); }); test('creating a mutation with 0 points fails validation', function () { $owner = User::factory()->create(); $dynamic = Dynamic::factory()->create(); $dynamic->participants()->attach($owner->id, ['role' => 'owner']); $ledger = Ledger::factory()->create(['dynamic_id' => $dynamic->id]); $this->actingAs($owner); $response = $this->post(route('dynamics.ledgers.mutations.store', [$dynamic, $ledger]), [ 'amount' => 0, 'description' => 'Zero point spam', ]); $response->assertSessionHasErrors(['amount']); expect(Mutation::where('description', 'Zero point spam')->exists())->toBeFalse(); }); test('creating a mutation with more than 1000 points fails validation', function () { $owner = User::factory()->create(); $dynamic = Dynamic::factory()->create(); $dynamic->participants()->attach($owner->id, ['role' => 'owner']); $ledger = Ledger::factory()->create(['dynamic_id' => $dynamic->id]); $this->actingAs($owner); $response = $this->post(route('dynamics.ledgers.mutations.store', [$dynamic, $ledger]), [ 'amount' => 1001, 'description' => 'Abusive positive point reward', ]); $response->assertSessionHasErrors(['amount']); expect(Mutation::where('description', 'Abusive positive point reward')->exists())->toBeFalse(); }); test('creating a mutation with less than -1000 points fails validation', function () { $owner = User::factory()->create(); $dynamic = Dynamic::factory()->create(); $dynamic->participants()->attach($owner->id, ['role' => 'owner']); $ledger = Ledger::factory()->create(['dynamic_id' => $dynamic->id]); $this->actingAs($owner); $response = $this->post(route('dynamics.ledgers.mutations.store', [$dynamic, $ledger]), [ 'amount' => -1001, 'description' => 'Abusive negative point demerit', ]); $response->assertSessionHasErrors(['amount']); expect(Mutation::where('description', 'Abusive negative point demerit')->exists())->toBeFalse(); });