127 lines
4.3 KiB
PHP
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()
|
|
);
|
|
}
|
|
}
|