Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions app/Http/Controllers/Api/AssetsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use App\Models\AccessoryCheckout;
use App\Models\CheckoutAcceptance;
use App\Models\LicenseSeat;
use App\Rules\FlatArray;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Crypt;
Expand All @@ -34,8 +35,6 @@
use Illuminate\Support\Facades\Route;
use App\View\Label;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;


/**
* This class controls all actions related to assets for
Expand All @@ -57,7 +56,12 @@ class AssetsController extends Controller
*/
public function index(Request $request, $action = null, $upcoming_status = null) : JsonResponse | array
{

$request->validate([
'filter' => [
'json',
new FlatArray,
],
]);

// This handles the legacy audit endpoints :(
if ($action == 'audit') {
Expand Down
32 changes: 32 additions & 0 deletions app/Rules/FlatArray.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;

/**
* Validates array is only one level deep.
* Passes: ['a' => 'b']
* Fails: ['a' => ['b', 'c']]
*/
class FlatArray implements ValidationRule
{
/**
* Run the validation rule.
*
* @param \Closure(string, ?string=): \Illuminate\Translation\PotentiallyTranslatedString $fail
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if (is_string($value)) {
$value = json_decode($value, true);
}

foreach ($value as $arrayValue) {
if (is_array($arrayValue)) {
$fail(":attribute cannot contain a nested data.");
}
}
}
}
42 changes: 42 additions & 0 deletions tests/Feature/Assets/Api/AssetIndexTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Testing\Fluent\AssertableJson;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;

class AssetIndexTest extends TestCase
Expand Down Expand Up @@ -126,6 +127,47 @@ public function testAssetApiIndexReturnsDueOrOverdueForExpectedCheckin()
->assertJson(fn(AssertableJson $json) => $json->has('rows', 5)->etc());
}

public static function filterValues()
{
return [
['filter%5Bassigned_to%5D'],
['filter[assigned_to][not]=null'],
['filter={%22assigned_to%22:{%22$ne%22:null}}'],
['filter=%5B%22a%22%20%3D%3E%20%22b%22%5D'],
];
}

/**
* [RB-17904]
* [RB-19910]
*/
#[DataProvider('filterValues')]
public function test_handles_non_string_filter($filterString)
{
$this->actingAsForApi(User::factory()->superuser()->create())
->getJson(route('api.assets.index') . '?' . $filterString)
->assertOk()
->assertStatusMessageIs('error')
->assertMessagesContains('filter');
}

public function test_can_filter_results()
{
Asset::factory()->create(['purchase_date' => '2025-07-01', 'order_number' => '123']);
Asset::factory()->create(['purchase_date' => '2025-07-01', 'order_number' => '123']);
Asset::factory()->create(['purchase_date' => '2025-07-01', 'order_number' => '456']);

$this->actingAsForApi(User::factory()->superuser()->create())
->getJson(route('api.assets.index', [
'filter' => json_encode([
'order_number' => '123',
'purchase_date' => '2025-07-01',
]),
]))
->assertOk()
->assertJson(fn(AssertableJson $json) => $json->has('rows', 2)->etc());
}

public function testAssetApiIndexAdheresToCompanyScoping()
{
[$companyA, $companyB] = Company::factory()->count(2)->create();
Expand Down
Loading