192 lines
9.2 KiB
PHP
192 lines
9.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\PricingTier;
|
|
use Database\Seeders\PricingTierSeeder;
|
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
uses(DatabaseTransactions::class);
|
|
|
|
beforeEach(function () {
|
|
$this->seed(PricingTierSeeder::class);
|
|
});
|
|
|
|
it('GET /api/admin/pricing-tiers returns active + scheduled sets', function () {
|
|
$response = $this->getJson('/api/admin/pricing-tiers');
|
|
$response->assertOk();
|
|
expect($response->json('data.active'))->toHaveCount(7);
|
|
});
|
|
|
|
it('POST creates 7 new tiers with auto effective_from = 1st of next month', function () {
|
|
$payload = ['tiers' => [
|
|
['tier_no' => 1, 'leads_in_tier' => 50, 'price_rub' => '600.00'],
|
|
['tier_no' => 2, 'leads_in_tier' => 150, 'price_rub' => '550.00'],
|
|
['tier_no' => 3, 'leads_in_tier' => 300, 'price_rub' => '500.00'],
|
|
['tier_no' => 4, 'leads_in_tier' => 700, 'price_rub' => '450.00'],
|
|
['tier_no' => 5, 'leads_in_tier' => 1500, 'price_rub' => '400.00'],
|
|
['tier_no' => 6, 'leads_in_tier' => 3000, 'price_rub' => '350.00'],
|
|
['tier_no' => 7, 'leads_in_tier' => null, 'price_rub' => '300.00'],
|
|
]];
|
|
$this->postJson('/api/admin/pricing-tiers', $payload)->assertCreated();
|
|
|
|
$expectedDate = now('Europe/Moscow')->startOfMonth()->addMonth()->toDateString();
|
|
$newTiers = PricingTier::where('effective_from', $expectedDate)->get();
|
|
expect($newTiers)->toHaveCount(7);
|
|
expect($newTiers->where('tier_no', 1)->first()->price_per_lead_kopecks)->toBe(60000);
|
|
expect($newTiers->where('tier_no', 7)->first()->leads_in_tier)->toBeNull();
|
|
});
|
|
|
|
it('POST validates: exactly 7 rows required', function () {
|
|
$this->postJson('/api/admin/pricing-tiers', ['tiers' => [
|
|
['tier_no' => 1, 'leads_in_tier' => 50, 'price_rub' => '600.00'],
|
|
]])->assertStatus(422);
|
|
});
|
|
|
|
it('POST validates: tier_no must be unique 1..7', function () {
|
|
$this->postJson('/api/admin/pricing-tiers', ['tiers' => [
|
|
['tier_no' => 1, 'leads_in_tier' => 50, 'price_rub' => '600.00'],
|
|
['tier_no' => 1, 'leads_in_tier' => 150, 'price_rub' => '550.00'],
|
|
['tier_no' => 3, 'leads_in_tier' => 300, 'price_rub' => '500.00'],
|
|
['tier_no' => 4, 'leads_in_tier' => 700, 'price_rub' => '450.00'],
|
|
['tier_no' => 5, 'leads_in_tier' => 1500, 'price_rub' => '400.00'],
|
|
['tier_no' => 6, 'leads_in_tier' => 3000, 'price_rub' => '350.00'],
|
|
['tier_no' => 7, 'leads_in_tier' => null, 'price_rub' => '300.00'],
|
|
]])->assertStatus(422);
|
|
});
|
|
|
|
it('POST validates: tier 7 leads_in_tier must be null', function () {
|
|
$this->postJson('/api/admin/pricing-tiers', ['tiers' => [
|
|
['tier_no' => 1, 'leads_in_tier' => 50, 'price_rub' => '600.00'],
|
|
['tier_no' => 2, 'leads_in_tier' => 150, 'price_rub' => '550.00'],
|
|
['tier_no' => 3, 'leads_in_tier' => 300, 'price_rub' => '500.00'],
|
|
['tier_no' => 4, 'leads_in_tier' => 700, 'price_rub' => '450.00'],
|
|
['tier_no' => 5, 'leads_in_tier' => 1500, 'price_rub' => '400.00'],
|
|
['tier_no' => 6, 'leads_in_tier' => 3000, 'price_rub' => '350.00'],
|
|
['tier_no' => 7, 'leads_in_tier' => 99999, 'price_rub' => '300.00'],
|
|
]])->assertStatus(422);
|
|
});
|
|
|
|
it('POST validates: price_rub >= 0', function () {
|
|
$this->postJson('/api/admin/pricing-tiers', ['tiers' => [
|
|
['tier_no' => 1, 'leads_in_tier' => 50, 'price_rub' => '-1.00'],
|
|
['tier_no' => 2, 'leads_in_tier' => 150, 'price_rub' => '550.00'],
|
|
['tier_no' => 3, 'leads_in_tier' => 300, 'price_rub' => '500.00'],
|
|
['tier_no' => 4, 'leads_in_tier' => 700, 'price_rub' => '450.00'],
|
|
['tier_no' => 5, 'leads_in_tier' => 1500, 'price_rub' => '400.00'],
|
|
['tier_no' => 6, 'leads_in_tier' => 3000, 'price_rub' => '350.00'],
|
|
['tier_no' => 7, 'leads_in_tier' => null, 'price_rub' => '300.00'],
|
|
]])->assertStatus(422);
|
|
});
|
|
|
|
it('DELETE /scheduled/{effective_from} removes future tiers only', function () {
|
|
$futureDate = now('Europe/Moscow')->addMonth()->startOfMonth()->toDateString();
|
|
PricingTier::factory()->count(7)->sequence(fn ($s) => ['tier_no' => $s->index + 1])
|
|
->create(['effective_from' => $futureDate, 'is_active' => true]);
|
|
|
|
$this->deleteJson("/api/admin/pricing-tiers/scheduled/{$futureDate}")->assertOk();
|
|
|
|
expect(PricingTier::where('effective_from', $futureDate)->count())->toBe(0);
|
|
expect(PricingTier::where('effective_from', '1970-01-01')->count())->toBe(7);
|
|
});
|
|
|
|
it('store accepts a custom effective_from date', function (): void {
|
|
$custom = Carbon::now('Europe/Moscow')->addMonths(3)->toDateString();
|
|
|
|
$response = $this->postJson('/api/admin/pricing-tiers', [
|
|
'tiers' => [
|
|
['tier_no' => 1, 'leads_in_tier' => 50, 'price_rub' => '600.00'],
|
|
['tier_no' => 2, 'leads_in_tier' => 150, 'price_rub' => '550.00'],
|
|
['tier_no' => 3, 'leads_in_tier' => 300, 'price_rub' => '500.00'],
|
|
['tier_no' => 4, 'leads_in_tier' => 700, 'price_rub' => '450.00'],
|
|
['tier_no' => 5, 'leads_in_tier' => 1500, 'price_rub' => '400.00'],
|
|
['tier_no' => 6, 'leads_in_tier' => 3000, 'price_rub' => '350.00'],
|
|
['tier_no' => 7, 'leads_in_tier' => null, 'price_rub' => '300.00'],
|
|
],
|
|
'effective_from' => $custom,
|
|
]);
|
|
|
|
$response->assertCreated()->assertJson(['effective_from' => $custom]);
|
|
expect(PricingTier::where('effective_from', $custom)->count())->toBe(7);
|
|
});
|
|
|
|
it('store rejects effective_from равную сегодня', function (): void {
|
|
$today = Carbon::now('Europe/Moscow')->toDateString();
|
|
|
|
$this->postJson('/api/admin/pricing-tiers', [
|
|
'tiers' => [
|
|
['tier_no' => 1, 'leads_in_tier' => 50, 'price_rub' => '600.00'],
|
|
['tier_no' => 2, 'leads_in_tier' => 150, 'price_rub' => '550.00'],
|
|
['tier_no' => 3, 'leads_in_tier' => 300, 'price_rub' => '500.00'],
|
|
['tier_no' => 4, 'leads_in_tier' => 700, 'price_rub' => '450.00'],
|
|
['tier_no' => 5, 'leads_in_tier' => 1500, 'price_rub' => '400.00'],
|
|
['tier_no' => 6, 'leads_in_tier' => 3000, 'price_rub' => '350.00'],
|
|
['tier_no' => 7, 'leads_in_tier' => null, 'price_rub' => '300.00'],
|
|
],
|
|
'effective_from' => $today,
|
|
])->assertStatus(422);
|
|
});
|
|
|
|
it('store rejects effective_from in the past', function (): void {
|
|
$past = Carbon::now('Europe/Moscow')->subDay()->toDateString();
|
|
|
|
$this->postJson('/api/admin/pricing-tiers', [
|
|
'tiers' => [
|
|
['tier_no' => 1, 'leads_in_tier' => 50, 'price_rub' => '600.00'],
|
|
['tier_no' => 2, 'leads_in_tier' => 150, 'price_rub' => '550.00'],
|
|
['tier_no' => 3, 'leads_in_tier' => 300, 'price_rub' => '500.00'],
|
|
['tier_no' => 4, 'leads_in_tier' => 700, 'price_rub' => '450.00'],
|
|
['tier_no' => 5, 'leads_in_tier' => 1500, 'price_rub' => '400.00'],
|
|
['tier_no' => 6, 'leads_in_tier' => 3000, 'price_rub' => '350.00'],
|
|
['tier_no' => 7, 'leads_in_tier' => null, 'price_rub' => '300.00'],
|
|
],
|
|
'effective_from' => $past,
|
|
])->assertStatus(422);
|
|
});
|
|
|
|
it('writes audit-trail row in saas_admin_audit_log on POST', function () {
|
|
$this->postJson('/api/admin/pricing-tiers', ['tiers' => [
|
|
['tier_no' => 1, 'leads_in_tier' => 50, 'price_rub' => '600.00'],
|
|
['tier_no' => 2, 'leads_in_tier' => 150, 'price_rub' => '550.00'],
|
|
['tier_no' => 3, 'leads_in_tier' => 300, 'price_rub' => '500.00'],
|
|
['tier_no' => 4, 'leads_in_tier' => 700, 'price_rub' => '450.00'],
|
|
['tier_no' => 5, 'leads_in_tier' => 1500, 'price_rub' => '400.00'],
|
|
['tier_no' => 6, 'leads_in_tier' => 3000, 'price_rub' => '350.00'],
|
|
['tier_no' => 7, 'leads_in_tier' => null, 'price_rub' => '300.00'],
|
|
]])->assertCreated();
|
|
|
|
$log = DB::table('saas_admin_audit_log')
|
|
->where('action', 'pricing_tiers.create_scheduled')->first();
|
|
expect($log)->not->toBeNull();
|
|
});
|
|
|
|
test('AdminPricingTiers::store сохраняет цену 10.10 ₽ как ровно 1010 kopecks (без float-drift)', function () {
|
|
$tiers = [];
|
|
for ($i = 1; $i <= 6; $i++) {
|
|
$tiers[] = ['tier_no' => $i, 'leads_in_tier' => 50, 'price_rub' => '10.10'];
|
|
}
|
|
$tiers[] = ['tier_no' => 7, 'leads_in_tier' => null, 'price_rub' => '10.10'];
|
|
|
|
$resp = $this->postJson('/api/admin/pricing-tiers', ['tiers' => $tiers]);
|
|
$resp->assertCreated();
|
|
|
|
$expectedDate = now('Europe/Moscow')->startOfMonth()->addMonth()->toDateString();
|
|
foreach (PricingTier::query()->where('effective_from', $expectedDate)->orderBy('tier_no')->get() as $row) {
|
|
expect((int) $row->price_per_lead_kopecks)->toBe(1010);
|
|
}
|
|
});
|
|
|
|
test('AdminPricingTiers::store отклоняет некорректный price_rub (например "10.123" — три знака после точки)', function () {
|
|
$tiers = [];
|
|
for ($i = 1; $i <= 6; $i++) {
|
|
$tiers[] = ['tier_no' => $i, 'leads_in_tier' => 50, 'price_rub' => '10.10'];
|
|
}
|
|
$tiers[] = ['tier_no' => 7, 'leads_in_tier' => null, 'price_rub' => '10.123'];
|
|
|
|
$resp = $this->postJson('/api/admin/pricing-tiers', ['tiers' => $tiers]);
|
|
$resp->assertStatus(422);
|
|
$resp->assertJsonValidationErrors(['tiers.6.price_rub']);
|
|
});
|