Files
fotospiel-app/tests/Unit/Services/AiBudgetGuardServiceTest.php
Codex Agent 1d2242fb4d
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled
feat(ai): finalize AI magic edits epic rollout and operations
2026-02-06 22:41:51 +01:00

127 lines
4.3 KiB
PHP

<?php
namespace Tests\Unit\Services;
use App\Models\AiUsageLedger;
use App\Models\Event;
use App\Models\User;
use App\Services\AiEditing\AiBudgetGuardService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Tests\TestCase;
class AiBudgetGuardServiceTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
Cache::flush();
}
public function test_it_allows_requests_when_spend_is_below_caps(): void
{
$event = Event::factory()->create(['status' => 'published']);
$settings = (array) ($event->tenant->settings ?? []);
data_set($settings, 'ai_editing.budget.soft_cap_usd', 10.0);
data_set($settings, 'ai_editing.budget.hard_cap_usd', 20.0);
$event->tenant->update(['settings' => $settings]);
AiUsageLedger::query()->create([
'tenant_id' => $event->tenant_id,
'event_id' => $event->id,
'entry_type' => AiUsageLedger::TYPE_DEBIT,
'quantity' => 1,
'unit_cost_usd' => 3.0,
'amount_usd' => 3.0,
'currency' => 'USD',
'recorded_at' => now(),
]);
$decision = app(AiBudgetGuardService::class)->evaluateForEvent($event->fresh('tenant'));
$this->assertTrue($decision['allowed']);
$this->assertFalse($decision['budget']['soft_reached']);
$this->assertFalse($decision['budget']['hard_reached']);
$this->assertSame(3.0, $decision['budget']['current_spend_usd']);
}
public function test_it_blocks_requests_when_hard_cap_is_reached_without_override(): void
{
$event = Event::factory()->create(['status' => 'published']);
$owner = User::factory()->create();
$event->tenant->update(['user_id' => $owner->id]);
$settings = (array) ($event->tenant->settings ?? []);
data_set($settings, 'ai_editing.budget.hard_cap_usd', 5.0);
$event->tenant->update(['settings' => $settings]);
AiUsageLedger::query()->create([
'tenant_id' => $event->tenant_id,
'event_id' => $event->id,
'entry_type' => AiUsageLedger::TYPE_DEBIT,
'quantity' => 1,
'unit_cost_usd' => 5.0,
'amount_usd' => 5.0,
'currency' => 'USD',
'recorded_at' => now(),
]);
$decision = app(AiBudgetGuardService::class)->evaluateForEvent($event->fresh('tenant'));
$this->assertFalse($decision['allowed']);
$this->assertSame('budget_hard_cap_reached', $decision['reason_code']);
$this->assertTrue($decision['budget']['hard_reached']);
$this->assertFalse($decision['budget']['override_active']);
$this->assertDatabaseHas('tenant_notification_logs', [
'tenant_id' => $event->tenant_id,
'type' => 'ai_budget_hard_cap',
'channel' => 'system',
'status' => 'sent',
]);
$this->assertDatabaseHas('tenant_notification_receipts', [
'tenant_id' => $event->tenant_id,
'user_id' => $owner->id,
'status' => 'delivered',
]);
}
public function test_it_throttles_soft_cap_notifications_with_cooldown(): void
{
$event = Event::factory()->create(['status' => 'published']);
$settings = (array) ($event->tenant->settings ?? []);
data_set($settings, 'ai_editing.budget.soft_cap_usd', 2.0);
data_set($settings, 'ai_editing.budget.hard_cap_usd', 100.0);
$event->tenant->update(['settings' => $settings]);
AiUsageLedger::query()->create([
'tenant_id' => $event->tenant_id,
'event_id' => $event->id,
'entry_type' => AiUsageLedger::TYPE_DEBIT,
'quantity' => 1,
'unit_cost_usd' => 3.0,
'amount_usd' => 3.0,
'currency' => 'USD',
'recorded_at' => now(),
]);
$service = app(AiBudgetGuardService::class);
$service->evaluateForEvent($event->fresh('tenant'));
$service->evaluateForEvent($event->fresh('tenant'));
$this->assertSame(
1,
\App\Models\TenantNotificationLog::query()
->where('tenant_id', $event->tenant_id)
->where('type', 'ai_budget_soft_cap')
->count()
);
}
}