Files
portal/app/tests/Feature/DealCreateTest.php
T

196 lines
6.7 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
use App\Models\ActivityLog;
use App\Models\Deal;
use App\Models\Project;
use App\Models\SupplierLeadCost;
use App\Models\Tenant;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
uses(DatabaseTransactions::class);
beforeEach(function () {
$this->tenant = Tenant::factory()->create([
'balance_leads' => 100,
]);
$this->user = User::factory()->for($this->tenant)->create();
$this->actingAs($this->user);
});
test('POST /api/deals создаёт сделку с manual source + project firstOrCreate', function () {
$r = $this->postJson('/api/deals', [
'project_name' => 'Окна Москва',
'phone' => '+7 (999) 123-45-67',
'contact_name' => 'Тест Тестов',
'status' => 'new',
]);
$r->assertStatus(201);
expect($r->json('deal.id'))->toBeInt();
expect($r->json('deal.tenant_id'))->toBe($this->tenant->id);
expect($r->json('deal.phone'))->toBe('+7 (999) 123-45-67');
expect($r->json('deal.status'))->toBe('new');
$dealId = $r->json('deal.id');
DB::statement('SET app.current_tenant_id = '.$this->tenant->id);
$deal = Deal::query()->where('id', $dealId)->first();
expect($deal)->not->toBeNull();
expect($deal->source_crm_id)->toBeNull(); // manual
expect($deal->contact_name)->toBe('Тест Тестов');
// Project создан с type='manual'
$project = Project::find($r->json('deal.project_id'));
expect($project->name)->toBe('Окна Москва');
expect($project->type)->toBe('manual');
});
test('POST /api/deals использует существующий project (не дублирует)', function () {
DB::statement('SET app.current_tenant_id = '.$this->tenant->id);
$existing = Project::create([
'tenant_id' => $this->tenant->id,
'name' => 'Натяжные потолки',
'type' => 'webhook',
]);
$r = $this->postJson('/api/deals', [
'project_name' => 'Натяжные потолки',
'phone' => '+7 (999) 000-00-00',
]);
$r->assertStatus(201);
expect($r->json('deal.project_id'))->toBe($existing->id);
// Проверяем что НЕТ нового project'а с таким же name
$count = Project::where('tenant_id', $this->tenant->id)
->where('name', 'Натяжные потолки')
->count();
expect($count)->toBe(1);
});
test('POST /api/deals пишет ActivityLog с context.source=manual', function () {
$r = $this->postJson('/api/deals', [
'project_name' => 'X',
'phone' => '+7 (999) 000-00-00',
]);
$r->assertStatus(201);
DB::statement('SET app.current_tenant_id = '.$this->tenant->id);
$log = ActivityLog::where('deal_id', $r->json('deal.id'))->first();
expect($log)->not->toBeNull();
expect($log->event)->toBe(ActivityLog::EVENT_DEAL_CREATED);
expect($log->context)->toBe(['source' => 'manual']);
});
test('POST /api/deals 422 без обязательных полей', function () {
$r = $this->postJson('/api/deals', []);
$r->assertStatus(422);
expect($r->json('errors'))->toHaveKeys(['project_name', 'phone']);
});
test('POST /api/deals 401 без auth', function () {
auth()->logout();
$r = $this->postJson('/api/deals', [
'project_name' => 'X',
'phone' => '+7 (999) 000-00-00',
]);
$r->assertStatus(401);
});
test('POST /api/deals дефолтный status = new если не передан', function () {
$r = $this->postJson('/api/deals', [
'project_name' => 'X',
'phone' => '+7 (999) 000-00-00',
]);
$r->assertStatus(201);
expect($r->json('deal.status'))->toBe('new');
});
test('POST /api/deals с manager_id → assigned_at = NOW()', function () {
DB::statement('SET app.current_tenant_id = '.$this->tenant->id);
$manager = User::factory()->for($this->tenant)->create(['is_active' => true]);
$r = $this->postJson('/api/deals', [
'project_name' => 'X',
'phone' => '+7 (999) 000-00-00',
'manager_id' => $manager->id,
]);
$r->assertStatus(201);
$deal = Deal::where('id', $r->json('deal.id'))->first();
expect($deal->manager_id)->toBe($manager->id);
expect($deal->assigned_at)->not->toBeNull();
});
test('POST /api/deals manual НЕ списывает баланс tenant\'а', function () {
$balanceBefore = $this->tenant->balance_leads;
$this->postJson('/api/deals', [
'project_name' => 'X',
'phone' => '+7 (999) 000-00-00',
])->assertStatus(201);
$this->tenant->refresh();
expect($this->tenant->balance_leads)->toBe($balanceBefore);
});
test('POST /api/deals manual создаёт SupplierLeadCost если у проекта есть активный supplier', function () {
// Создаём supplier + проект + project_suppliers связку.
$supplierId = DB::table('suppliers')->insertGetId([
'code' => 'test_b1_'.bin2hex(random_bytes(3)),
'name' => 'Test Supplier',
'accepts_types' => '{"websites","calls"}',
'cost_rub' => '15.00',
'channel' => 'sites',
'is_active' => true,
'sort_order' => 1,
'quality_score' => 1.00,
'created_at' => now(),
]);
DB::statement('SET app.current_tenant_id = '.$this->tenant->id);
$project = Project::create([
'tenant_id' => $this->tenant->id,
'name' => 'WithSupplier',
'type' => 'manual',
]);
DB::table('project_suppliers')->insert([
'project_id' => $project->id,
'supplier_id' => $supplierId,
'is_active' => true,
'created_at' => now(),
]);
$r = $this->postJson('/api/deals', [
'project_name' => 'WithSupplier',
'phone' => '+7 (999) 000-00-00',
]);
$r->assertStatus(201);
// SupplierLeadCost создан со snapshot cost_rub
$cost = SupplierLeadCost::query()
->where('deal_id', $r->json('deal.id'))
->first();
expect($cost)->not->toBeNull();
expect($cost->supplier_id)->toBe((int) $supplierId);
expect((string) $cost->cost_rub)->toBe('15.00');
expect($cost->supplier_lead_id)->toBeNull(); // manual: нет внешнего id
});
test('POST /api/deals manual БЕЗ supplier'."'а у проекта — без SupplierLeadCost (graceful skip)", function () {
$r = $this->postJson('/api/deals', [
'project_name' => 'NoSupplier',
'phone' => '+7 (999) 000-00-00',
]);
$r->assertStatus(201);
DB::statement('SET app.current_tenant_id = '.$this->tenant->id);
$cost = SupplierLeadCost::query()
->where('deal_id', $r->json('deal.id'))
->count();
expect($cost)->toBe(0);
});