app->make(GenerationService::class); } public function test_image_generation_records_actual_cost_and_downloads_file(): void { Storage::fake('public'); Http::fake([ 'https://api.runware.ai/v1' => Http::response(['data' => [[ 'taskType' => 'imageInference', 'taskUUID' => '11111111-1111-1111-1111-111111111111', 'imageURL' => 'https://cdn.example.com/out.jpg', 'cost' => 0.01, ]]]), 'https://cdn.example.com/out.jpg' => Http::response('FAKEBYTES'), ]); RunwareModel::create([ 'modality' => 'image', 'air_id' => 'a:cheap@1', 'label' => 'Cheap', 'unit' => 'per_image', 'price_usd' => 0.01, 'active' => true, 'is_default' => true, ]); $generation = $this->service()->createImage('a cat wearing a hat'); $this->assertSame('completed', $generation->status); $this->assertEquals(0.01, $generation->cost_usd); $this->assertNotNull($generation->cost_eur); $this->assertFalse($generation->cost_is_estimated); Storage::disk('public')->assertExists($generation->output_path); } public function test_failed_api_call_marks_generation_failed_without_cost(): void { Http::fake([ 'https://api.runware.ai/v1' => Http::response(['errors' => [['message' => 'boom']]], 400), ]); RunwareModel::create([ 'modality' => 'image', 'air_id' => 'a:cheap@1', 'label' => 'Cheap', 'unit' => 'per_image', 'price_usd' => 0.01, 'active' => true, 'is_default' => true, ]); $this->expectException(\RuntimeException::class); try { $this->service()->createImage('a cat wearing a hat'); } finally { $generation = Generation::first(); $this->assertSame('failed', $generation->status); $this->assertNull($generation->cost_usd); } } public function test_budget_cap_blocks_generation_that_would_exceed_it(): void { config(['runware.monthly_cap_eur' => 0.001]); RunwareModel::create([ 'modality' => 'image', 'air_id' => 'a:pricey@1', 'label' => 'Pricey', 'unit' => 'per_image', 'price_usd' => 10, 'active' => true, 'is_default' => true, ]); $this->expectException(BudgetExceededException::class); $this->service()->createImage('this should never call the API'); Http::assertNothingSent(); } public function test_text_generation_falls_back_to_estimated_cost_when_usage_cost_missing(): void { Http::fake([ 'https://api.runware.ai/v1/chat/completions' => Http::response([ 'choices' => [['message' => ['content' => 'Hello there']]], 'usage' => ['total_tokens' => 100], ]), ]); RunwareModel::create([ 'modality' => 'text', 'air_id' => 't:cheap@1', 'label' => 'Cheap', 'unit' => 'per_million_tokens', 'price_usd' => 1, 'active' => true, 'is_default' => true, ]); $generation = $this->service()->createText('say hello'); $this->assertSame('completed', $generation->status); $this->assertSame('Hello there', $generation->output_text); $this->assertTrue($generation->cost_is_estimated); $this->assertEqualsWithDelta(0.0001, $generation->cost_usd, 0.00001); } }