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

137 lines
4.7 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
use App\Models\ActivityLog;
use App\Models\Deal;
use App\Models\Project;
use App\Models\Tenant;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
/**
* Тесты POST /api/deals/transition — bulk status-update для DealsView bulk-actions.
*
* Покрывает: validation (422 на missing/неизвестный slug), RLS+app-фильтр
* (чужие сделки НЕ обновляются), ActivityLog event=deal.status_changed,
* 401 без auth, NO-OP не пишет audit entry, partial update (несколько id
* принадлежат tenant'у, один — нет → updated < requested).
*/
uses(DatabaseTransactions::class);
beforeEach(function () {
$this->tenant = Tenant::factory()->create();
$this->otherTenant = Tenant::factory()->create();
$this->user = User::factory()->for($this->tenant)->create();
$this->actingAs($this->user);
DB::statement('SET app.current_tenant_id = '.$this->tenant->id);
$this->project = Project::factory()->for($this->tenant)->create();
});
test('POST /api/deals/transition — 422 без обязательных полей', function () {
$this->postJson('/api/deals/transition', [])->assertStatus(422);
});
test('POST /api/deals/transition — 401 без auth', function () {
auth()->logout();
$this->postJson('/api/deals/transition', ['ids' => [1], 'status' => 'new'])->assertStatus(401);
});
test('POST /api/deals/transition — 422 на неизвестный status slug', function () {
$deal = Deal::factory()->for($this->tenant)->for($this->project)->create(['status' => 'new']);
$r = $this->postJson('/api/deals/transition', [
'ids' => [$deal->id],
'status' => 'not_a_real_slug',
]);
$r->assertStatus(422);
expect($r->json('errors.status.0'))->toContain('lead_statuses');
// Сделка осталась в исходном статусе.
$deal->refresh();
expect($deal->status)->toBe('new');
});
test('POST /api/deals/transition — обновляет статус и пишет ActivityLog', function () {
$deals = Deal::factory()->count(3)->for($this->tenant)->for($this->project)->create(['status' => 'new']);
$r = $this->postJson('/api/deals/transition', [
'ids' => $deals->pluck('id')->all(),
'status' => 'won',
]);
$r->assertStatus(200)->assertJson([
'updated' => 3,
'requested' => 3,
'status' => 'won',
]);
foreach ($deals as $d) {
$d->refresh();
expect($d->status)->toBe('won');
}
$activity = ActivityLog::where('tenant_id', $this->tenant->id)
->where('event', ActivityLog::EVENT_DEAL_STATUS_CHANGED)
->get();
expect($activity)->toHaveCount(3);
expect($activity->first()->context)->toMatchArray([
'from' => 'new',
'to' => 'won',
'source' => 'bulk',
]);
});
test('POST /api/deals/transition — NO-OP не пишет ActivityLog', function () {
$deal = Deal::factory()->for($this->tenant)->for($this->project)->create(['status' => 'won']);
$r = $this->postJson('/api/deals/transition', [
'ids' => [$deal->id],
'status' => 'won',
]);
$r->assertStatus(200)->assertJson(['updated' => 0, 'requested' => 1]);
expect(ActivityLog::where('deal_id', $deal->id)
->where('event', ActivityLog::EVENT_DEAL_STATUS_CHANGED)
->count())->toBe(0);
});
test('POST /api/deals/transition — defense-in-depth не апдейтит чужие сделки', function () {
$own = Deal::factory()->for($this->tenant)->for($this->project)->create(['status' => 'new']);
DB::statement('SET app.current_tenant_id = '.$this->otherTenant->id);
$foreignProject = Project::factory()->for($this->otherTenant)->create();
$foreign = Deal::factory()->for($this->otherTenant)->for($foreignProject)->create(['status' => 'new']);
// Передаём оба id — чужой не должен обновиться.
$r = $this->postJson('/api/deals/transition', [
'ids' => [$own->id, $foreign->id],
'status' => 'won',
]);
$r->assertStatus(200)->assertJson([
'updated' => 1,
'requested' => 2,
]);
DB::statement('SET app.current_tenant_id = '.$this->tenant->id);
$own->refresh();
expect($own->status)->toBe('won');
DB::statement('SET app.current_tenant_id = '.$this->otherTenant->id);
$foreign->refresh();
expect($foreign->status)->toBe('new');
});
test('POST /api/deals/transition — 422 если ids пустой массив', function () {
$this->postJson('/api/deals/transition', [
'ids' => [],
'status' => 'won',
])->assertStatus(422);
});