mirror of
https://github.com/snipe/snipe-it.git
synced 2026-08-18 11:15:42 +00:00
206 lines
8.3 KiB
PHP
206 lines
8.3 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Users\Api;
|
|
|
|
use App\Models\Asset;
|
|
use App\Models\Company;
|
|
use App\Models\Location;
|
|
use App\Models\Maintenance;
|
|
use App\Models\User;
|
|
use Illuminate\Testing\Fluent\AssertableJson;
|
|
use Tests\TestCase;
|
|
|
|
class IndexUsersTest extends TestCase
|
|
{
|
|
public function test_requires_permission()
|
|
{
|
|
$this->actingAsForApi(User::factory()->create())
|
|
->getJson(route('api.users.index'))
|
|
->assertForbidden();
|
|
}
|
|
|
|
public function test_returns_managed_users_count_correctly()
|
|
{
|
|
$manager = User::factory()->create(['first_name' => 'Manages Users']);
|
|
User::factory()->create(['first_name' => 'Does Not Manage Users']);
|
|
|
|
User::factory()->create(['manager_id' => $manager->id]);
|
|
User::factory()->create(['manager_id' => $manager->id]);
|
|
|
|
$response = $this->actingAsForApi(User::factory()->viewUsers()->create())
|
|
->getJson(route('api.users.index', [
|
|
'manages_users_count' => 2,
|
|
]))
|
|
->assertOk();
|
|
|
|
$response->assertJson(function (AssertableJson $json) {
|
|
$json->has('rows', 1)
|
|
->where('rows.0.first_name', 'Manages Users')
|
|
->etc();
|
|
});
|
|
}
|
|
|
|
public function test_returns_managed_locations_count_correctly()
|
|
{
|
|
$manager = User::factory()->create(['first_name' => 'Manages Locations']);
|
|
User::factory()->create(['first_name' => 'Does Not Manage Locations']);
|
|
|
|
Location::factory()->create(['manager_id' => $manager->id]);
|
|
Location::factory()->create(['manager_id' => $manager->id]);
|
|
|
|
$response = $this->actingAsForApi(User::factory()->viewUsers()->create())
|
|
->getJson(route('api.users.index', [
|
|
'manages_locations_count' => 2,
|
|
]))
|
|
->assertOk();
|
|
|
|
$response->assertJson(function (AssertableJson $json) {
|
|
$json->has('rows', 1)
|
|
->where('rows.0.first_name', 'Manages Locations')
|
|
->etc();
|
|
});
|
|
}
|
|
|
|
public function test_returns_assigned_maintenances_count_correctly()
|
|
{
|
|
// The withCount/has filter both target the new assignedMaintenances
|
|
// morphMany. checked_out_to_* on a maintenance is populated by
|
|
// MaintenanceObserver::creating() from the asset's assigned_to —
|
|
// so seed via "asset checked out to user, then maintenance on
|
|
// that asset" rather than passing checked_out_to_* directly.
|
|
$busy = User::factory()->create(['first_name' => 'Busy Breaker']);
|
|
User::factory()->create(['first_name' => 'Quiet User']);
|
|
|
|
$busyAsset = Asset::factory()->assignedToUser($busy)->create();
|
|
Maintenance::factory()->create(['asset_id' => $busyAsset->id]);
|
|
Maintenance::factory()->create([
|
|
'asset_id' => $busyAsset->id,
|
|
'completed_at' => now(),
|
|
]);
|
|
|
|
$response = $this->actingAsForApi(User::factory()->viewUsers()->create())
|
|
->getJson(route('api.users.index', [
|
|
'assigned_maintenances_count' => 2,
|
|
]))
|
|
->assertOk();
|
|
|
|
$response->assertJson(function (AssertableJson $json) {
|
|
$json->has('rows', 1)
|
|
->where('rows.0.first_name', 'Busy Breaker')
|
|
->where('rows.0.assigned_maintenances_count', 2)
|
|
->etc();
|
|
});
|
|
}
|
|
|
|
public function test_can_sort_users_index_by_assigned_maintenances_count()
|
|
{
|
|
$most = User::factory()->create();
|
|
$some = User::factory()->create();
|
|
|
|
$mostAsset = Asset::factory()->assignedToUser($most)->create();
|
|
Maintenance::factory()->count(3)->create(['asset_id' => $mostAsset->id]);
|
|
|
|
$someAsset = Asset::factory()->assignedToUser($some)->create();
|
|
Maintenance::factory()->create(['asset_id' => $someAsset->id]);
|
|
|
|
$response = $this->actingAsForApi(User::factory()->viewUsers()->create())
|
|
->getJson(route('api.users.index', [
|
|
'sort' => 'assigned_maintenances_count',
|
|
'order' => 'desc',
|
|
]))
|
|
->assertOk();
|
|
|
|
// Filter to just the two users we created with maintenances; everyone
|
|
// else seeded by factories has count=0 and their relative order is
|
|
// not what we're testing here. Position of $most must precede $some.
|
|
$orderedIds = collect($response->json('rows'))->pluck('id');
|
|
$mostPos = $orderedIds->search($most->id);
|
|
$somePos = $orderedIds->search($some->id);
|
|
|
|
$this->assertNotFalse($mostPos, 'User with 3 maintenances should appear in the result');
|
|
$this->assertNotFalse($somePos, 'User with 1 maintenance should appear in the result');
|
|
$this->assertLessThan(
|
|
$somePos,
|
|
$mostPos,
|
|
'User with 3 maintenances must sort before user with 1 when ordering desc by assigned_maintenances_count',
|
|
);
|
|
}
|
|
|
|
public function test_gracefully_handles_malformed_filter()
|
|
{
|
|
$this->actingAsForApi(User::factory()->viewUsers()->create())
|
|
->getJson(route('api.users.index', [
|
|
// filter should be a json encoded array and not a string
|
|
'filter' => 'email:an-email-address@example.com',
|
|
]))
|
|
->assertStatusMessageIs('error')
|
|
->assertJson(function (AssertableJson $json) {
|
|
$json->has('messages.filter')->etc();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* FMCS + floaters on: a company-scoped, non-superuser caller sees
|
|
* their own pivot companies AND null-company (floater) users, but
|
|
* NOT users pivoted to a different company.
|
|
*
|
|
* Regression pin for support ticket 56305. Root cause was that
|
|
* `orWhereDoesntHave('companies')` in the floater branch had the
|
|
* companies-table CompanyableScope applied recursively to its
|
|
* subquery, filtering the JOIN to the caller's own companies. A user
|
|
* pivoted only to OUT-OF-SCOPE companies then looked pivot-less and
|
|
* slipped through as an apparent floater. Fix reads the company_user
|
|
* pivot directly. See the same pin at tests/Unit/CompanyScopingTest.php.
|
|
*/
|
|
public function test_users_index_hides_other_companies_users_but_includes_floaters_for_company_scoped_caller()
|
|
{
|
|
[$companyA, $companyB] = Company::factory()->count(2)->create();
|
|
|
|
$companyACaller = $companyA->users()->save(User::factory()->viewUsers()->make());
|
|
$companyAPeer = $companyA->users()->save(User::factory()->make(['first_name' => 'PeerInA']));
|
|
$companyBUser = $companyB->users()->save(User::factory()->make(['first_name' => 'UserInB']));
|
|
$floater = User::factory()->withoutCompany()->create(['first_name' => 'FloaterUser']);
|
|
|
|
$this->settings->enableFloaterMode();
|
|
|
|
$rows = $this->actingAsForApi($companyACaller)
|
|
->getJson(route('api.users.index'))
|
|
->assertOk()
|
|
->json('rows');
|
|
|
|
$visibleIds = collect($rows)->pluck('id')->all();
|
|
|
|
$this->assertContains($companyACaller->id, $visibleIds);
|
|
$this->assertContains($companyAPeer->id, $visibleIds);
|
|
$this->assertContains($floater->id, $visibleIds, 'Floater user should be visible per docs.');
|
|
$this->assertNotContains($companyBUser->id, $visibleIds, 'Company B user leaked to a company A caller.');
|
|
}
|
|
|
|
public function test_returns_result_via_filter()
|
|
{
|
|
|
|
User::factory()->count(3)->create(['first_name' => 'Awesome', 'last_name' => 'Admin', 'email' => 'awesome@example.org']);
|
|
$this->actingAsForApi(User::factory()->viewUsers()->create())
|
|
->getJson(route('api.users.index', [
|
|
'filter' => '{"first_name":"Awesome","last_name":"Admin","email":"awesome@example.org"}',
|
|
]))
|
|
->assertOk()
|
|
->assertJsonStructure([
|
|
'total',
|
|
'rows',
|
|
])
|
|
->assertJson(fn (AssertableJson $json) => $json->has('rows', 3)->etc());
|
|
|
|
$this->actingAsForApi(User::factory()->viewUsers()->create())
|
|
->getJson(route('api.users.index', [
|
|
'filter' => '{"first_name":"Not Awesome"}',
|
|
]))
|
|
->assertOk()
|
|
->assertJsonStructure([
|
|
'total',
|
|
'rows',
|
|
])
|
|
->assertJson(fn (AssertableJson $json) => $json->has('rows', 0)->etc());
|
|
}
|
|
}
|