Files
portal/app/tests/Feature/Api/ProjectBulkActionsTest.php
T
2026-05-12 14:55:45 +03:00

175 lines
6.1 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\Project;
use App\Models\Tenant;
use App\Models\User;
it('accepts update_regions action with add/remove bitmask', function () {
$tenant = Tenant::factory()->create();
$user = User::factory()->for($tenant)->create();
$p = Project::factory()->for($tenant)->create(['region_mask' => 1]);
$this->actingAs($user)
->postJson('/api/projects/bulk', [
'action' => 'update_regions',
'ids' => [$p->id],
'add' => 6, // биты 2+4 = Северо-Западный + Южный
'remove' => 1, // бит 1 = Центральный
])
->assertOk()
->assertJsonStructure(['updated', 'skipped', 'warnings']);
});
it('rejects unknown action', function () {
$user = User::factory()->create();
$this->actingAs($user)
->postJson('/api/projects/bulk', [
'action' => 'nuke_everything',
'ids' => [1],
])
->assertStatus(422)
->assertJsonValidationErrors(['action']);
});
it('rejects update_limit with both delta and replace', function () {
$user = User::factory()->create();
$this->actingAs($user)
->postJson('/api/projects/bulk', [
'action' => 'update_limit',
'ids' => [1],
'delta' => 50,
'replace' => 500,
])
->assertStatus(422);
});
it('rejects empty ids without scope', function () {
$user = User::factory()->create();
$this->actingAs($user)
->postJson('/api/projects/bulk', [
'action' => 'pause',
])
->assertStatus(422);
});
it('accepts empty scope.filter as valid scope (all projects)', function () {
$tenant = Tenant::factory()->create();
$user = User::factory()->for($tenant)->create();
$this->actingAs($user)
->postJson('/api/projects/bulk', [
'action' => 'pause',
'scope' => ['filter' => []],
])
->assertOk();
});
it('applies update_regions add and remove correctly', function () {
$tenant = Tenant::factory()->create();
$user = User::factory()->for($tenant)->create();
$p1 = Project::factory()->for($tenant)->create(['region_mask' => 3]); // 1+2
$p2 = Project::factory()->for($tenant)->create(['region_mask' => 5]); // 1+4
$this->actingAs($user)
->postJson('/api/projects/bulk', [
'action' => 'update_regions',
'ids' => [$p1->id, $p2->id],
'add' => 16, // 16 = Приволжский
'remove' => 1, // 1 = Центральный
])
->assertOk()
->assertJson(['updated' => 2, 'skipped' => [], 'warnings' => []]);
expect($p1->fresh()->region_mask)->toBe((3 | 16) & ~1); // = 18
expect($p2->fresh()->region_mask)->toBe((5 | 16) & ~1); // = 20
});
it('applies update_days add and remove correctly', function () {
$tenant = Tenant::factory()->create();
$user = User::factory()->for($tenant)->create();
$p = Project::factory()->for($tenant)->create(['delivery_days_mask' => 31]); // Пн-Пт
$this->actingAs($user)
->postJson('/api/projects/bulk', [
'action' => 'update_days',
'ids' => [$p->id],
'add' => 96, // 32+64 = Сб+Вс
'remove' => 1, // Пн
])
->assertOk()
->assertJson(['updated' => 1, 'skipped' => [], 'warnings' => []]);
expect($p->fresh()->delivery_days_mask)->toBe((31 | 96) & ~1); // = 126
});
it('applies update_limit delta to all projects', function () {
$tenant = Tenant::factory()->create();
$user = User::factory()->for($tenant)->create();
$p1 = Project::factory()->for($tenant)->create(['daily_limit_target' => 100, 'delivered_today' => 0]);
$p2 = Project::factory()->for($tenant)->create(['daily_limit_target' => 200, 'delivered_today' => 0]);
$this->actingAs($user)
->postJson('/api/projects/bulk', [
'action' => 'update_limit',
'ids' => [$p1->id, $p2->id],
'delta' => 50,
])
->assertOk()
->assertJson(['updated' => 2, 'skipped' => [], 'warnings' => []]);
expect($p1->fresh()->daily_limit_target)->toBe(150);
expect($p2->fresh()->daily_limit_target)->toBe(250);
});
it('skips projects when limit delta would drop below delivered_today', function () {
$tenant = Tenant::factory()->create();
$user = User::factory()->for($tenant)->create();
$p1 = Project::factory()->for($tenant)->create(['daily_limit_target' => 100, 'delivered_today' => 80]);
$p2 = Project::factory()->for($tenant)->create(['daily_limit_target' => 50, 'delivered_today' => 30]);
$this->actingAs($user)
->postJson('/api/projects/bulk', [
'action' => 'update_limit',
'ids' => [$p1->id, $p2->id],
'delta' => -40, // p1: 100-40=60 < 80 → SKIP; p2: 50-40=10 < 30 → SKIP
])
->assertOk()
->assertJson([
'updated' => 0,
'skipped' => [
['id' => $p1->id, 'reason' => 'below_delivered_today'],
['id' => $p2->id, 'reason' => 'below_delivered_today'],
],
]);
expect($p1->fresh()->daily_limit_target)->toBe(100); // unchanged
expect($p2->fresh()->daily_limit_target)->toBe(50);
});
it('applies update_limit replace with skip for conflicts', function () {
$tenant = Tenant::factory()->create();
$user = User::factory()->for($tenant)->create();
$p1 = Project::factory()->for($tenant)->create(['daily_limit_target' => 100, 'delivered_today' => 30]);
$p2 = Project::factory()->for($tenant)->create(['daily_limit_target' => 200, 'delivered_today' => 150]);
$this->actingAs($user)
->postJson('/api/projects/bulk', [
'action' => 'update_limit',
'ids' => [$p1->id, $p2->id],
'replace' => 100, // p1: ok (100>=30); p2: 100<150 → skip
])
->assertOk()
->assertJson([
'updated' => 1,
'skipped' => [['id' => $p2->id, 'reason' => 'below_delivered_today']],
]);
expect($p1->fresh()->daily_limit_target)->toBe(100);
expect($p2->fresh()->daily_limit_target)->toBe(200);
});