feat: implement AI styling foundation and billing scope rework
This commit is contained in:
147
tests/Feature/Ai/AiEditingDataModelTest.php
Normal file
147
tests/Feature/Ai/AiEditingDataModelTest.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user