feat: subscriptions
CI / 🧪 Tests Laravel (push) Successful in 2m48s
CI / 🐳 Build & Push Image (push) Successful in 3m0s

This commit is contained in:
2026-08-07 11:53:46 +02:00
parent 99cb0e63e6
commit 2ece4b6e3e
51 changed files with 1489 additions and 254 deletions
+13 -21
View File
@@ -3,11 +3,9 @@
use App\Ai\Agents\MealImageAnalyzer;
use App\Enums\AiUsageStatus;
use App\Enums\AiUsageType;
use App\Enums\CreditLedgerEntryType;
use App\Enums\IngredientUnit;
use App\Enums\MealPostType;
use App\Models\AiUsage;
use App\Models\CreditLedgerEntry;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
@@ -28,7 +26,9 @@ function fakeMealAnalysisImage(): UploadedFile
}
it('analyzes a meal image into a draft', function () {
$user = User::factory()->create();
$user = User::factory()->create([
'trial_ends_at' => now()->addDay(),
]);
Sanctum::actingAs($user);
@@ -66,7 +66,6 @@ it('analyzes a meal image into a draft', function () {
$response
->assertOk()
->assertJsonPath('credits.balance', 2)
->assertJsonPath('data.title', 'Bowl saumon avocat')
->assertJsonPath('data.type', MealPostType::LUNCH->value)
->assertJsonPath('data.calories', 612)
@@ -110,18 +109,12 @@ it('analyzes a meal image into a draft', function () {
],
]);
$user->refresh();
$ledgerEntry = CreditLedgerEntry::query()->sole();
expect($user->ai_credits_balance)->toBe(2)
->and($ledgerEntry->user_id)->toBe($user->id)
->and($ledgerEntry->type)->toBe(CreditLedgerEntryType::USAGE)
->and($ledgerEntry->credits)->toBe(-1)
->and($ledgerEntry->balance_after)->toBe(2);
});
it('records a failed meal image analysis attempt', function () {
$user = User::factory()->create();
$user = User::factory()->create([
'trial_ends_at' => now()->addDay(),
]);
Sanctum::actingAs($user);
Exceptions::fake();
@@ -152,12 +145,11 @@ it('records a failed meal image analysis attempt', function () {
->and($usage->output)->toBeNull()
->and($usage->error_message)->toContain('Provider unavailable');
expect($user->fresh()->ai_credits_balance)->toBe(3);
});
it('requires available credits to analyze a meal image', function () {
it('requires a trial or subscription to analyze a meal image', function () {
$user = User::factory()->create([
'ai_credits_balance' => 0,
'trial_ends_at' => now()->subDay(),
]);
Sanctum::actingAs($user);
@@ -174,11 +166,9 @@ it('requires available credits to analyze a meal image', function () {
'image' => fakeMealAnalysisImage(),
])
->assertStatus(402)
->assertJsonPath('message', __('api.meal_image_analysis.insufficient_credits'))
->assertJsonPath('credits.balance', 0);
->assertJsonPath('message', __('api.meal_image_analysis.subscription_required'));
expect(AiUsage::query()->count())->toBe(0)
->and(CreditLedgerEntry::query()->count())->toBe(0);
expect(AiUsage::query()->count())->toBe(0);
});
it('requires authentication to analyze a meal image', function () {
@@ -191,7 +181,9 @@ it('requires authentication to analyze a meal image', function () {
});
it('validates the analyzed image', function () {
Sanctum::actingAs(User::factory()->create());
Sanctum::actingAs(User::factory()->create([
'trial_ends_at' => now()->addDay(),
]));
$this
->postJson('/api/meal-posts/analyze-image')