Files
fotospiel-app/tests/Feature/Ai/AiEditingDataModelTest.php
Codex Agent 36bed12ff9
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled
feat: implement AI styling foundation and billing scope rework
2026-02-06 20:01:58 +01:00

148 lines
4.9 KiB
PHP

<?php
namespace Tests\Feature\Ai;
use App\Models\AiEditOutput;
use App\Models\AiEditRequest;
use App\Models\AiProviderRun;
use App\Models\AiStyle;
use App\Models\AiUsageLedger;
use App\Models\Photo;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Schema;
use Tests\TestCase;
class AiEditingDataModelTest extends TestCase
{
use RefreshDatabase;
public function test_ai_tables_exist_with_expected_key_columns(): void
{
$this->assertTrue(Schema::hasTable('ai_styles'));
$this->assertTrue(Schema::hasTable('ai_edit_requests'));
$this->assertTrue(Schema::hasTable('ai_edit_outputs'));
$this->assertTrue(Schema::hasTable('ai_provider_runs'));
$this->assertTrue(Schema::hasTable('ai_usage_ledgers'));
foreach ([
'key',
'provider',
'provider_model',
'requires_source_image',
'is_premium',
] as $column) {
$this->assertTrue(Schema::hasColumn('ai_styles', $column));
}
foreach ([
'tenant_id',
'event_id',
'photo_id',
'style_id',
'provider',
'idempotency_key',
'safety_state',
'status',
] as $column) {
$this->assertTrue(Schema::hasColumn('ai_edit_requests', $column));
}
foreach ([
'request_id',
'provider',
'provider_task_id',
'request_payload',
'response_payload',
'cost_usd',
] as $column) {
$this->assertTrue(Schema::hasColumn('ai_provider_runs', $column));
}
}
public function test_ai_edit_flow_records_request_run_output_and_usage_with_relations(): void
{
$photo = Photo::factory()->create();
$event = $photo->event;
$tenant = $event->tenant;
$user = User::factory()->create(['tenant_id' => $tenant->id]);
$style = AiStyle::query()->create([
'key' => 'bg-colosseum',
'name' => 'Colosseum Background',
'provider' => 'runware',
'provider_model' => 'runware-default',
'requires_source_image' => true,
'is_active' => true,
]);
$request = AiEditRequest::query()->create([
'tenant_id' => $tenant->id,
'event_id' => $event->id,
'photo_id' => $photo->id,
'style_id' => $style->id,
'requested_by_user_id' => $user->id,
'provider' => 'runware',
'provider_model' => 'runware-default',
'status' => AiEditRequest::STATUS_QUEUED,
'safety_state' => 'pending',
'prompt' => 'Replace background with Colosseum in Rome.',
'idempotency_key' => 'req-'.$photo->id.'-1',
'queued_at' => now(),
'metadata' => ['source' => 'guest_pwa'],
]);
$run = AiProviderRun::query()->create([
'request_id' => $request->id,
'provider' => 'runware',
'attempt' => 1,
'provider_task_id' => 'task-123',
'status' => AiProviderRun::STATUS_RUNNING,
'request_payload' => ['positivePrompt' => 'Rome Colosseum'],
'response_payload' => ['status' => 'processing'],
'started_at' => now(),
]);
$output = AiEditOutput::query()->create([
'request_id' => $request->id,
'storage_disk' => 'public',
'storage_path' => 'ai/outputs/final.jpg',
'mime_type' => 'image/jpeg',
'width' => 1024,
'height' => 1024,
'is_primary' => true,
'generated_at' => now(),
'metadata' => ['variant' => 'v1'],
]);
$ledger = AiUsageLedger::query()->create([
'tenant_id' => $tenant->id,
'event_id' => $event->id,
'request_id' => $request->id,
'entry_type' => AiUsageLedger::TYPE_DEBIT,
'quantity' => 1,
'unit_cost_usd' => 0.015,
'amount_usd' => 0.015,
'recorded_at' => now(),
'metadata' => ['provider' => 'runware'],
]);
$request->refresh();
$run->refresh();
$output->refresh();
$ledger->refresh();
$this->assertSame($style->id, $request->style?->id);
$this->assertSame($tenant->id, $request->tenant?->id);
$this->assertSame($event->id, $request->event?->id);
$this->assertSame($photo->id, $request->photo?->id);
$this->assertSame(1, $request->providerRuns()->count());
$this->assertSame(1, $request->outputs()->count());
$this->assertSame(1, $request->usageLedgers()->count());
$this->assertIsArray($run->request_payload);
$this->assertIsArray($run->response_payload);
$this->assertIsArray($ledger->metadata);
$this->assertGreaterThan(0, (float) $ledger->amount_usd);
}
}