diff --git a/app/Http/Controllers/Api/AssetsController.php b/app/Http/Controllers/Api/AssetsController.php index bfe071a6b550..755a50ccac96 100644 --- a/app/Http/Controllers/Api/AssetsController.php +++ b/app/Http/Controllers/Api/AssetsController.php @@ -1290,9 +1290,19 @@ public function requestable(Request $request): JsonResponse | array public function assignedAssets(Request $request, Asset $asset) : JsonResponse | array { + $this->authorize('view', Asset::class); + $this->authorize('view', $asset); + + $query = Asset::where([ + 'assigned_to' => $asset->id, + 'assigned_type' => Asset::class, + ]); + + $total = $query->count(); + + $assets = $query->applyOffsetAndLimit($total)->get(); - return []; - // to do + return (new AssetsTransformer)->transformAssets($assets, $total); } public function assignedAccessories(Request $request, Asset $asset) : JsonResponse | array diff --git a/app/Models/SnipeModel.php b/app/Models/SnipeModel.php index f5a5a51cc04c..6a4bdfee2c17 100644 --- a/app/Models/SnipeModel.php +++ b/app/Models/SnipeModel.php @@ -3,8 +3,10 @@ namespace App\Models; use App\Helpers\Helper; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Facades\Request; class SnipeModel extends Model { @@ -156,6 +158,20 @@ public function setStatusIdAttribute($value) $this->attributes['status_id'] = $value; } + /** + * Applies offset (from request) and limit to query. + * + * @param Builder $query + * @param int $total + * @return void + */ + public function scopeApplyOffsetAndLimit(Builder $query, int $total) + { + $offset = (Request::input('offset') > $total) ? $total : app('api_offset_value'); + $limit = app('api_limit_value'); + + $query->skip($offset)->take($limit); + } protected function displayName(): Attribute { diff --git a/tests/Feature/Assets/Api/AssignedAssetsTest.php b/tests/Feature/Assets/Api/AssignedAssetsTest.php new file mode 100644 index 000000000000..93d9bbbfb04a --- /dev/null +++ b/tests/Feature/Assets/Api/AssignedAssetsTest.php @@ -0,0 +1,84 @@ +actingAsForApi(User::factory()->create()) + ->getJson(route('api.assets.assigned_assets', Asset::factory()->create())) + ->assertForbidden(); + } + + public function test_adheres_to_company_scoping() + { + $this->settings->enableMultipleFullCompanySupport(); + + [$companyA, $companyB] = Company::factory()->count(2)->create(); + + $asset = Asset::factory()->for($companyA)->create(); + + $user = User::factory()->for($companyB)->viewAssets()->create(); + + $this->actingAsForApi($user) + ->getJson(route('api.assets.assigned_assets', $asset)) + ->assertOk() + ->assertStatusMessageIs('error') + ->assertMessagesAre('Asset not found'); + } + + public function test_can_get_assets_assigned_to_specific_asset() + { + $unassociatedAsset = Asset::factory()->create(); + + $asset = Asset::factory()->hasAssignedAssets(2)->create(); + + $assetsAssignedToAsset = Asset::where([ + 'assigned_to' => $asset->id, + 'assigned_type' => Asset::class, + ])->get(); + + $this->actingAsForApi(User::factory()->viewAssets()->create()) + ->getJson(route('api.assets.assigned_assets', $asset)) + ->assertOk() + ->assertResponseContainsInRows($assetsAssignedToAsset, 'serial') + ->assertResponseDoesNotContainInRows($unassociatedAsset, 'serial') + ->assertJson(function (AssertableJson $json) { + $json->where('total', 2) + ->count('rows', 2) + ->etc(); + }); + } + + public function test_adheres_to_offset_and_limit() + { + $asset = Asset::factory()->hasAssignedAssets(2)->create(); + + $assetsAssignedToAsset = Asset::where([ + 'assigned_to' => $asset->id, + 'assigned_type' => Asset::class, + ])->get(); + + $this->actingAsForApi(User::factory()->viewAssets()->create()) + ->getJson(route('api.assets.assigned_assets', [ + 'asset' => $asset, + 'offset' => 1, + 'limit' => 1, + ])) + ->assertOk() + ->assertResponseDoesNotContainInRows($assetsAssignedToAsset->first(), 'serial') + ->assertResponseContainsInRows($assetsAssignedToAsset->last(), 'serial') + ->assertJson(function (AssertableJson $json) { + $json->where('total', 2) + ->count('rows', 1) + ->etc(); + }); + } +} diff --git a/tests/Support/CustomTestMacros.php b/tests/Support/CustomTestMacros.php index 956235f461a4..be9a527f4c5c 100644 --- a/tests/Support/CustomTestMacros.php +++ b/tests/Support/CustomTestMacros.php @@ -21,13 +21,19 @@ protected function registerCustomMacros() TestResponse::macro( 'assertResponseContainsInRows', - function (Model $model, string $property = 'name') use ($guardAgainstNullProperty) { - $guardAgainstNullProperty($model, $property); + function (iterable|Model $models, string $property = 'name') use ($guardAgainstNullProperty) { + if ($models instanceof Model) { + $models = [$models]; + } - Assert::assertTrue( - collect($this['rows'])->pluck($property)->contains(e($model->{$property})), - "Response did not contain the expected value: {$model->{$property}}" - ); + foreach ($models as $model) { + $guardAgainstNullProperty($model, $property); + + Assert::assertTrue( + collect($this['rows'])->pluck($property)->contains(e($model->{$property})), + "Response did not contain the expected value: {$model->{$property}}" + ); + } return $this; }