mirror of
https://github.com/snipe/snipe-it.git
synced 2026-08-18 11:15:42 +00:00
Fixed #19334 - added pagination to user’s assets API endpoint
This commit is contained in:
@ -747,9 +747,13 @@ class UsersController extends Controller
|
||||
$assets = $assets->InModelList($model_ids);
|
||||
}
|
||||
|
||||
$assets = $assets->get();
|
||||
$total = $assets->count();
|
||||
$offset = ($request->input('offset') > $total) ? $total : app('api_offset_value');
|
||||
$limit = app('api_limit_value');
|
||||
|
||||
return (new AssetsTransformer)->transformAssets($assets, $assets->count(), $request);
|
||||
$assets = $assets->skip($offset)->take($limit)->get();
|
||||
|
||||
return (new AssetsTransformer)->transformAssets($assets, $total, $request);
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found', compact('id'))));
|
||||
@ -812,15 +816,22 @@ class UsersController extends Controller
|
||||
*
|
||||
* @param $userId
|
||||
*/
|
||||
public function accessories($id): array
|
||||
public function accessories(Request $request, $id): array
|
||||
{
|
||||
$this->authorize('view', User::class);
|
||||
$user = User::findOrFail($id);
|
||||
$this->authorize('view', $user);
|
||||
$this->authorize('view', Accessory::class);
|
||||
$accessories = $user->accessories;
|
||||
|
||||
return (new AccessoriesTransformer)->transformAccessories($accessories, $accessories->count());
|
||||
$accessories = $user->accessories();
|
||||
|
||||
$total = $accessories->count();
|
||||
$offset = ($request->input('offset') > $total) ? $total : app('api_offset_value');
|
||||
$limit = app('api_limit_value');
|
||||
|
||||
$accessories = $accessories->skip($offset)->take($limit)->get();
|
||||
|
||||
return (new AccessoriesTransformer)->transformAccessories($accessories, $total);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -832,15 +843,21 @@ class UsersController extends Controller
|
||||
*
|
||||
* @param $userId
|
||||
*/
|
||||
public function licenses($id): JsonResponse|array
|
||||
public function licenses(Request $request, $id): JsonResponse|array
|
||||
{
|
||||
$this->authorize('view', User::class);
|
||||
$this->authorize('view', License::class);
|
||||
|
||||
if ($user = User::where('id', $id)->withTrashed()->first()) {
|
||||
$licenses = $user->licenses()->get();
|
||||
$licenses = $user->licenses();
|
||||
|
||||
return (new LicensesTransformer)->transformLicenses($licenses, $licenses->count());
|
||||
$total = $licenses->count();
|
||||
$offset = ($request->input('offset') > $total) ? $total : app('api_offset_value');
|
||||
$limit = app('api_limit_value');
|
||||
|
||||
$licenses = $licenses->skip($offset)->take($limit)->get();
|
||||
|
||||
return (new LicensesTransformer)->transformLicenses($licenses, $total);
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found', compact('id'))));
|
||||
@ -915,14 +932,20 @@ class UsersController extends Controller
|
||||
*
|
||||
* @author [Godfrey Martinez] [<gmartinez@grokability.com>]
|
||||
*/
|
||||
public function eulas(User $user, ActionlogsTransformer $transformer)
|
||||
public function eulas(Request $request, User $user, ActionlogsTransformer $transformer)
|
||||
{
|
||||
$this->authorize('view', $user);
|
||||
|
||||
$eulas = $user->eulas;
|
||||
$eulas = $user->eulas();
|
||||
|
||||
$total = $eulas->count();
|
||||
$offset = ($request->input('offset') > $total) ? $total : app('api_offset_value');
|
||||
$limit = app('api_limit_value');
|
||||
|
||||
$eulas = $eulas->skip($offset)->take($limit)->get();
|
||||
|
||||
return response()->json(
|
||||
$transformer->transformActionlogs($eulas, $eulas->count())
|
||||
$transformer->transformActionlogs($eulas, $total)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
185
tests/Feature/Users/Api/UserItemsPaginationTest.php
Normal file
185
tests/Feature/Users/Api/UserItemsPaginationTest.php
Normal file
@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Users\Api;
|
||||
|
||||
use App\Models\Accessory;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use App\Models\License;
|
||||
use App\Models\LicenseSeat;
|
||||
use App\Models\User;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* Regression coverage for GitHub #19334: /users/{id}/assets, /accessories,
|
||||
* /licenses, and /eulas emitted pagination metadata but returned every row,
|
||||
* ignoring ?limit and ?page. Each test seeds N rows for the target user and a
|
||||
* few extra rows for a bystander user (so we also catch a "return everything
|
||||
* across all users" regression), then asserts total, rows count, per_page,
|
||||
* current_page, and next/prev URL against a specific ?limit/?page query.
|
||||
*/
|
||||
class UserItemsPaginationTest extends TestCase
|
||||
{
|
||||
public function test_assets_endpoint_paginates_rows_and_reports_accurate_metadata(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$bystander = User::factory()->create();
|
||||
|
||||
Asset::factory()->count(7)->assignedToUser($user)->create();
|
||||
Asset::factory()->count(3)->assignedToUser($bystander)->create();
|
||||
|
||||
$response = $this->actingAsForApi(User::factory()->viewUsers()->viewAssets()->create())
|
||||
->getJson(route('api.users.assetlist', ['user' => $user->id, 'limit' => 3, 'page' => 2]))
|
||||
->assertOk()
|
||||
->json();
|
||||
|
||||
$this->assertSame(7, $response['total'], 'total should reflect the users assigned assets only');
|
||||
$this->assertCount(3, $response['rows'], 'rows should be paginated to per_page');
|
||||
$this->assertSame(3, $response['per_page']);
|
||||
$this->assertSame(2, $response['current_page']);
|
||||
$this->assertSame(3, $response['total_pages']);
|
||||
$this->assertNotNull($response['next_page_url']);
|
||||
$this->assertNotNull($response['prev_page_url']);
|
||||
}
|
||||
|
||||
public function test_assets_endpoint_last_page_returns_remainder_and_no_next_url(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
Asset::factory()->count(7)->assignedToUser($user)->create();
|
||||
|
||||
$response = $this->actingAsForApi(User::factory()->viewUsers()->viewAssets()->create())
|
||||
->getJson(route('api.users.assetlist', ['user' => $user->id, 'limit' => 3, 'page' => 3]))
|
||||
->assertOk()
|
||||
->json();
|
||||
|
||||
$this->assertSame(7, $response['total']);
|
||||
$this->assertCount(1, $response['rows']);
|
||||
$this->assertSame(3, $response['current_page']);
|
||||
$this->assertNull($response['next_page_url']);
|
||||
$this->assertNotNull($response['prev_page_url']);
|
||||
}
|
||||
|
||||
public function test_accessories_endpoint_paginates_rows_and_reports_accurate_metadata(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$bystander = User::factory()->create();
|
||||
|
||||
Accessory::factory()->count(5)->checkedOutToUser($user)->create();
|
||||
Accessory::factory()->count(2)->checkedOutToUser($bystander)->create();
|
||||
|
||||
$response = $this->actingAsForApi(User::factory()->viewUsers()->viewAccessories()->create())
|
||||
->getJson(route('api.users.accessorieslist', ['user' => $user->id, 'limit' => 2, 'page' => 2]))
|
||||
->assertOk()
|
||||
->json();
|
||||
|
||||
$this->assertSame(5, $response['total']);
|
||||
$this->assertCount(2, $response['rows']);
|
||||
$this->assertSame(2, $response['per_page']);
|
||||
$this->assertSame(2, $response['current_page']);
|
||||
$this->assertSame(3, $response['total_pages']);
|
||||
$this->assertNotNull($response['next_page_url']);
|
||||
$this->assertNotNull($response['prev_page_url']);
|
||||
}
|
||||
|
||||
public function test_licenses_endpoint_paginates_rows_and_reports_accurate_metadata(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$bystander = User::factory()->create();
|
||||
|
||||
$userLicenses = License::factory()->count(6)->create();
|
||||
foreach ($userLicenses as $license) {
|
||||
LicenseSeat::factory()->for($license)->assignedToUser($user)->create();
|
||||
}
|
||||
|
||||
$bystanderLicense = License::factory()->create();
|
||||
LicenseSeat::factory()->for($bystanderLicense)->assignedToUser($bystander)->create();
|
||||
|
||||
$response = $this->actingAsForApi(User::factory()->viewUsers()->viewLicenses()->create())
|
||||
->getJson(route('api.users.licenselist', ['user' => $user->id, 'limit' => 2, 'page' => 2]))
|
||||
->assertOk()
|
||||
->json();
|
||||
|
||||
$this->assertSame(6, $response['total']);
|
||||
$this->assertCount(2, $response['rows']);
|
||||
$this->assertSame(2, $response['per_page']);
|
||||
$this->assertSame(2, $response['current_page']);
|
||||
$this->assertSame(3, $response['total_pages']);
|
||||
$this->assertNotNull($response['next_page_url']);
|
||||
$this->assertNotNull($response['prev_page_url']);
|
||||
}
|
||||
|
||||
public function test_eulas_endpoint_paginates_rows_and_reports_accurate_metadata(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$bystander = User::factory()->create();
|
||||
|
||||
// eulas() filters on target_type=User, action_type=accepted, and
|
||||
// NOT NULL filename + accept_signature. Bystander rows and rows
|
||||
// missing either field must be excluded from the total.
|
||||
Actionlog::factory()->count(4)->create([
|
||||
'target_type' => User::class,
|
||||
'target_id' => $user->id,
|
||||
'item_type' => Asset::class,
|
||||
'action_type' => 'accepted',
|
||||
'filename' => 'eula.pdf',
|
||||
'accept_signature' => 'sig',
|
||||
]);
|
||||
Actionlog::factory()->create([
|
||||
'target_type' => User::class,
|
||||
'target_id' => $bystander->id,
|
||||
'item_type' => Asset::class,
|
||||
'action_type' => 'accepted',
|
||||
'filename' => 'eula.pdf',
|
||||
'accept_signature' => 'sig',
|
||||
]);
|
||||
|
||||
$response = $this->actingAsForApi(User::factory()->viewUsers()->create())
|
||||
->getJson(route('api.user.eulas', ['user' => $user->id, 'limit' => 2, 'page' => 2]))
|
||||
->assertOk()
|
||||
->json();
|
||||
|
||||
$this->assertSame(4, $response['total']);
|
||||
$this->assertCount(2, $response['rows']);
|
||||
$this->assertSame(2, $response['per_page']);
|
||||
$this->assertSame(2, $response['current_page']);
|
||||
$this->assertSame(2, $response['total_pages']);
|
||||
$this->assertNull($response['next_page_url']);
|
||||
$this->assertNotNull($response['prev_page_url']);
|
||||
}
|
||||
|
||||
public function test_out_of_range_page_returns_empty_rows_but_keeps_total(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
Asset::factory()->count(2)->assignedToUser($user)->create();
|
||||
|
||||
$response = $this->actingAsForApi(User::factory()->viewUsers()->viewAssets()->create())
|
||||
->getJson(route('api.users.assetlist', ['user' => $user->id, 'limit' => 10, 'page' => 99]))
|
||||
->assertOk()
|
||||
->json();
|
||||
|
||||
$this->assertSame(2, $response['total']);
|
||||
$this->assertCount(0, $response['rows']);
|
||||
}
|
||||
|
||||
public function test_default_no_pagination_params_still_returns_paged_response(): void
|
||||
{
|
||||
// Without ?limit or ?page, the middleware falls back to config
|
||||
// app.max_results (150 in this app). The rows should include every
|
||||
// matching item up to that ceiling, with current_page=1 and no prev
|
||||
// URL. Guards against a regression where "no page param" bypassed
|
||||
// the skip/take entirely.
|
||||
$user = User::factory()->create();
|
||||
Asset::factory()->count(4)->assignedToUser($user)->create();
|
||||
|
||||
$response = $this->actingAsForApi(User::factory()->viewUsers()->viewAssets()->create())
|
||||
->getJson(route('api.users.assetlist', ['user' => $user->id]))
|
||||
->assertOk()
|
||||
->json();
|
||||
|
||||
$this->assertSame(4, $response['total']);
|
||||
$this->assertCount(4, $response['rows']);
|
||||
$this->assertSame(1, $response['current_page']);
|
||||
$this->assertNull($response['prev_page_url']);
|
||||
$this->assertNull($response['next_page_url']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user