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']); } }