api/tests/Feature/GenerationCreditsTest.php

231 lines
7.5 KiB
PHP

<?php
use App\Enums\GenerationCreditTransactionType;
use App\Enums\RewardedAdSessionStatus;
use App\Models\GenerationCreditBalance;
use App\Models\GenerationCreditTransaction;
use App\Models\RewardedAdSession;
use App\Models\User;
use App\Services\AdMobServerSideVerificationVerifier;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\Request;
use Laravel\Sanctum\Sanctum;
uses(RefreshDatabase::class);
beforeEach(function (): void {
config()->set('admob.rewarded.android_ad_unit_id', 'ca-app-pub-test/android-rewarded');
config()->set('admob.rewarded.ios_ad_unit_id', 'ca-app-pub-test/ios-rewarded');
config()->set('admob.rewarded.reward_amount', 1);
config()->set('admob.rewarded.reward_item', 'generation_credit');
$this->app->instance(
AdMobServerSideVerificationVerifier::class,
new class extends AdMobServerSideVerificationVerifier
{
public function verify(Request $request): void
{
//
}
},
);
});
it('returns the generation credit status', function () {
$user = User::factory()->create();
GenerationCreditBalance::factory()->create([
'user_id' => $user->id,
'balance' => 2,
]);
Sanctum::actingAs($user);
$this
->getJson('/api/me/generation-credits')
->assertOk()
->assertJsonPath('data.balance', 2)
->assertJsonPath('data.canWatchRewardedAd', true)
->assertJsonPath('data.rewardedAdConfigured', true);
});
it('creates a rewarded ad session for the current day', function () {
$user = User::factory()->create();
Sanctum::actingAs($user);
$response = $this
->postJson('/api/me/rewarded-ad-sessions', [
'platform' => 'android',
])
->assertCreated()
->assertJsonPath('data.adUnitId', 'ca-app-pub-test/android-rewarded')
->assertJsonPath('data.userId', $user->id);
$session = RewardedAdSession::query()->sole();
expect($session->custom_data)->toBe($response->json('data.customData'))
->and($session->status)->toBe(RewardedAdSessionStatus::CREATED)
->and($session->reward_date->toDateString())->toBe(now('UTC')->toDateString());
});
it('reuses an active rewarded ad session', function () {
$user = User::factory()->create();
Sanctum::actingAs($user);
$firstSessionId = $this
->postJson('/api/me/rewarded-ad-sessions', [
'platform' => 'ios',
])
->assertCreated()
->json('data.id');
$this
->postJson('/api/me/rewarded-ad-sessions', [
'platform' => 'ios',
])
->assertOk()
->assertJsonPath('data.id', $firstSessionId);
expect(RewardedAdSession::query()->count())->toBe(1);
});
it('grants one generation credit from a verified rewarded ad callback', function () {
$user = User::factory()->create();
Sanctum::actingAs($user);
$sessionPayload = $this
->postJson('/api/me/rewarded-ad-sessions', [
'platform' => 'android',
])
->assertCreated()
->json('data');
$this
->get('/api/admob/rewarded-ads/callback?'.http_build_query([
'ad_network' => '5450213213286189855',
'ad_unit' => 'ca-app-pub-test/android-rewarded',
'custom_data' => $sessionPayload['customData'],
'reward_amount' => 1,
'reward_item' => 'generation_credit',
'timestamp' => now()->getTimestampMs(),
'transaction_id' => 'reward-transaction-1',
'user_id' => $user->id,
]))
->assertOk()
->assertSee('ok');
$transaction = GenerationCreditTransaction::query()->sole();
$session = RewardedAdSession::query()->sole();
expect($transaction->type)->toBe(GenerationCreditTransactionType::AD_REWARD)
->and($transaction->amount)->toBe(1)
->and($transaction->daily_reward_key)->toBe('ad:'.$session->reward_date->toDateString())
->and($session->status)->toBe(RewardedAdSessionStatus::REWARDED)
->and(GenerationCreditBalance::query()->whereKey($user->id)->value('balance'))->toBe(1);
});
it('accepts the numeric ad unit id returned by admob callbacks', function () {
$user = User::factory()->create();
Sanctum::actingAs($user);
$sessionPayload = $this
->postJson('/api/me/rewarded-ad-sessions', [
'platform' => 'android',
])
->assertCreated()
->json('data');
$this
->get('/api/admob/rewarded-ads/callback?'.http_build_query([
'ad_unit' => 'android-rewarded',
'custom_data' => $sessionPayload['customData'],
'reward_amount' => 1,
'reward_item' => 'generation_credit',
'transaction_id' => 'reward-transaction-numeric-ad-unit',
'user_id' => $user->id,
]))
->assertOk();
expect(GenerationCreditBalance::query()->whereKey($user->id)->value('balance'))->toBe(1);
});
it('rejects unexpected rewarded ad units', function () {
$user = User::factory()->create();
Sanctum::actingAs($user);
$sessionPayload = $this
->postJson('/api/me/rewarded-ad-sessions', [
'platform' => 'android',
])
->assertCreated()
->json('data');
$this
->get('/api/admob/rewarded-ads/callback?'.http_build_query([
'ad_unit' => 'unexpected-ad-unit',
'custom_data' => $sessionPayload['customData'],
'reward_amount' => 1,
'reward_item' => 'generation_credit',
'transaction_id' => 'reward-transaction-unexpected-ad-unit',
'user_id' => $user->id,
]))
->assertBadRequest();
expect(GenerationCreditBalance::query()->whereKey($user->id)->value('balance'))->toBeNull();
});
it('does not grant more than one rewarded ad credit per day', function () {
$user = User::factory()->create();
Sanctum::actingAs($user);
$firstSession = RewardedAdSession::factory()->create([
'user_id' => $user->id,
'reward_date' => now('UTC')->toDateString(),
]);
$secondSession = RewardedAdSession::factory()->create([
'user_id' => $user->id,
'reward_date' => now('UTC')->toDateString(),
]);
foreach ([$firstSession, $secondSession] as $index => $session) {
$this
->get('/api/admob/rewarded-ads/callback?'.http_build_query([
'ad_unit' => 'ca-app-pub-test/android-rewarded',
'custom_data' => $session->custom_data,
'reward_amount' => 1,
'reward_item' => 'generation_credit',
'transaction_id' => 'reward-transaction-'.$index,
'user_id' => $user->id,
]))
->assertOk();
}
expect(GenerationCreditTransaction::query()->where('type', GenerationCreditTransactionType::AD_REWARD)->count())->toBe(1)
->and(GenerationCreditBalance::query()->whereKey($user->id)->value('balance'))->toBe(1)
->and($secondSession->refresh()->status)->toBe(RewardedAdSessionStatus::REJECTED);
});
it('prevents new rewarded ad sessions after today reward is claimed', function () {
$user = User::factory()->create();
GenerationCreditTransaction::factory()->create([
'user_id' => $user->id,
'daily_reward_key' => 'ad:'.now('UTC')->toDateString(),
]);
Sanctum::actingAs($user);
$this
->postJson('/api/me/rewarded-ad-sessions', [
'platform' => 'android',
])
->assertUnprocessable()
->assertJsonPath('message', __('api.generation_credits.rewarded_ad_unavailable'));
});