Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added tests around asset checkout #14755

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
984cc7a
Scaffold tests around asset checkout via web
marcusmoore Mar 20, 2024
393dc51
Add assertions around the event dispatch
marcusmoore Mar 20, 2024
530291f
Implement some tests
marcusmoore Mar 20, 2024
b368acf
Implement test case
marcusmoore Mar 21, 2024
deaba46
Merge branch 'develop' into chore/sc-25103/add-tests-around-asset-che…
marcusmoore Apr 10, 2024
6f53f2a
Finish implementing test case
marcusmoore Apr 10, 2024
e652527
Add simple test for LogListener
marcusmoore Apr 10, 2024
37ebf18
Organization
marcusmoore Apr 10, 2024
7dbf8a8
Add tests for asset and location check out
marcusmoore Apr 10, 2024
72eda1e
Improve naming
marcusmoore Apr 10, 2024
d371d14
Implement test
marcusmoore Apr 10, 2024
6d57242
Add validation around dates
marcusmoore Apr 10, 2024
4434de6
Add test case
marcusmoore Apr 10, 2024
b63962e
Add additional assertions
marcusmoore Apr 10, 2024
6b6e186
Account for null asset in factory state
marcusmoore Apr 10, 2024
bbb2cdc
Add note
marcusmoore Apr 10, 2024
6666a78
Organization
marcusmoore Apr 10, 2024
f6ad275
Clean ups
marcusmoore Apr 10, 2024
70934e5
Remove unneeded comment
marcusmoore Apr 10, 2024
7c2fae7
Scaffold api test cases
marcusmoore Apr 11, 2024
f28a82d
Implement some tests, scaffold others
marcusmoore Apr 11, 2024
fa50167
Implement test
marcusmoore Apr 11, 2024
5567a1e
Formatting
marcusmoore Apr 11, 2024
1935a4a
Improve scenario naming
marcusmoore May 22, 2024
5d36899
Fix assertion
marcusmoore May 22, 2024
8ca882d
Complete a scenario
marcusmoore May 22, 2024
c7fa2c0
Add scenario
marcusmoore May 22, 2024
1fe22e4
Re-order scenarios
marcusmoore May 22, 2024
f16c79b
Improve event assertions
marcusmoore May 22, 2024
6d10425
Remove todo
marcusmoore May 22, 2024
8d74a97
Merge branch 'develop' into chore/sc-25103/add-tests-around-asset-che…
marcusmoore May 22, 2024
a3389a3
Update test name and add todo
marcusmoore May 23, 2024
67c4fa2
Improve event assertions
marcusmoore May 23, 2024
482ebfb
Implement test
marcusmoore May 23, 2024
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
6 changes: 0 additions & 6 deletions app/Http/Controllers/Api/AssetsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -851,7 +851,6 @@ public function checkout(AssetCheckoutRequest $request, $asset_id)
'asset_tag' => $asset->asset_tag,
];


// This item is checked out to a location
if (request('checkout_to_type') == 'location') {
$target = Location::find(request('assigned_location'));
Expand All @@ -878,13 +877,10 @@ public function checkout(AssetCheckoutRequest $request, $asset_id)
$asset->status_id = $request->get('status_id');
}


if (! isset($target)) {
return response()->json(Helper::formatStandardApiResponse('error', $error_payload, 'Checkout target for asset '.e($asset->asset_tag).' is invalid - '.$error_payload['target_type'].' does not exist.'));
}



$checkout_at = request('checkout_at', date('Y-m-d H:i:s'));
$expected_checkin = request('expected_checkin', null);
$note = request('note', null);
Expand All @@ -900,8 +896,6 @@ public function checkout(AssetCheckoutRequest $request, $asset_id)
// $asset->location_id = $target->rtd_location_id;
// }



if ($asset->checkOut($target, Auth::user(), $checkout_at, $expected_checkin, $note, $asset_name, $asset->location_id)) {
return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.success')));
}
Expand Down
8 changes: 8 additions & 0 deletions app/Http/Requests/AssetCheckoutRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ public function rules()
'assigned_location' => 'required_without_all:assigned_user,assigned_asset',
'status_id' => 'exists:status_labels,id,deployable,1',
'checkout_to_type' => 'required|in:asset,location,user',
'checkout_at' => [
'nullable',
'date',
],
'expected_checkin' => [
'nullable',
'date'
],
];

return $rules;
Expand Down
10 changes: 10 additions & 0 deletions database/factories/LicenseSeatFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Database\Factories;

use App\Models\Asset;
use App\Models\License;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
Expand All @@ -15,6 +16,15 @@ public function definition()
];
}

public function assignedToAsset(Asset $asset = null)
{
return $this->state(function () use ($asset) {
return [
'asset_id' => $asset->id ?? Asset::factory(),
];
});
}

public function assignedToUser(User $user = null)
{
return $this->state(function () use ($user) {
Expand Down
5 changes: 5 additions & 0 deletions database/factories/StatuslabelFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ public function rtd()
});
}

public function readyToDeploy()
{
return $this->rtd();
}

public function pending()
{
return $this->state(function () {
Expand Down
213 changes: 213 additions & 0 deletions tests/Feature/Api/Assets/AssetCheckoutTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
<?php

namespace Tests\Feature\Api\Assets;

use App\Events\CheckoutableCheckedOut;
use App\Models\Asset;
use App\Models\Location;
use App\Models\Statuslabel;
use App\Models\User;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Event;
use Tests\TestCase;

class AssetCheckoutTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();

Event::fake([CheckoutableCheckedOut::class]);
}

public function testCheckingOutAssetRequiresCorrectPermission()
{
$this->actingAsForApi(User::factory()->create())
->postJson(route('api.asset.checkout', Asset::factory()->create()), [
'checkout_to_type' => 'user',
'assigned_user' => User::factory()->create()->id,
])
->assertForbidden();
}

public function testNonExistentAssetCannotBeCheckedOut()
{
$this->actingAsForApi(User::factory()->checkoutAssets()->create())
->postJson(route('api.asset.checkout', 1000), [
'checkout_to_type' => 'user',
'assigned_user' => User::factory()->create()->id,
])
->assertStatusMessageIs('error');
}

public function testAssetNotAvailableForCheckoutCannotBeCheckedOut()
{
$assetAlreadyCheckedOut = Asset::factory()->assignedToUser()->create();

$this->actingAsForApi(User::factory()->checkoutAssets()->create())
->postJson(route('api.asset.checkout', $assetAlreadyCheckedOut), [
'checkout_to_type' => 'user',
'assigned_user' => User::factory()->create()->id,
])
->assertStatusMessageIs('error');
}

public function testAssetCannotBeCheckedOutToItself()
{
$asset = Asset::factory()->create();

$this->actingAsForApi(User::factory()->checkoutAssets()->create())
->postJson(route('api.asset.checkout', $asset), [
'checkout_to_type' => 'asset',
'assigned_asset' => $asset->id,
])
->assertStatusMessageIs('error');
}

public function testValidationWhenCheckingOutAsset()
{
$this->actingAsForApi(User::factory()->checkoutAssets()->create())
->postJson(route('api.asset.checkout', Asset::factory()->create()), [])
->assertStatusMessageIs('error');

Event::assertNotDispatched(CheckoutableCheckedOut::class);
}

public function testCannotCheckoutAcrossCompaniesWhenFullCompanySupportEnabled()
{
$this->markTestIncomplete('This is not implemented');
}

/**
* This data provider contains checkout targets along with the
* asset's expected location after the checkout process.
*/
public function checkoutTargets(): array
{
return [
'Checkout to User' => [
function () {
$userLocation = Location::factory()->create();
$user = User::factory()->for($userLocation)->create();

return [
'checkout_type' => 'user',
'target' => $user,
'expected_location' => $userLocation,
];
}
],
'Checkout to User without location set' => [
function () {
$userLocation = Location::factory()->create();
$user = User::factory()->for($userLocation)->create(['location_id' => null]);

return [
'checkout_type' => 'user',
'target' => $user,
'expected_location' => null,
];
}
],
'Checkout to Asset with location set' => [
function () {
$rtdLocation = Location::factory()->create();
$location = Location::factory()->create();
$asset = Asset::factory()->for($location)->for($rtdLocation, 'defaultLoc')->create();

return [
'checkout_type' => 'asset',
'target' => $asset,
'expected_location' => $location,
];
}
],
'Checkout to Asset without location set' => [
function () {
$rtdLocation = Location::factory()->create();
$asset = Asset::factory()->for($rtdLocation, 'defaultLoc')->create(['location_id' => null]);

return [
'checkout_type' => 'asset',
'target' => $asset,
'expected_location' => null,
];
}
],
'Checkout to Location' => [
function () {
$location = Location::factory()->create();

return [
'checkout_type' => 'location',
'target' => $location,
'expected_location' => $location,
];
}
],
];
}

/** @dataProvider checkoutTargets */
public function testAssetCanBeCheckedOut($data)
{
['checkout_type' => $type, 'target' => $target, 'expected_location' => $expectedLocation] = $data();

$newStatus = Statuslabel::factory()->readyToDeploy()->create();
$asset = Asset::factory()->forLocation()->create();
$admin = User::factory()->checkoutAssets()->create();

$this->actingAsForApi($admin)
->postJson(route('api.asset.checkout', $asset), [
'checkout_to_type' => $type,
'assigned_'.$type => $target->id,
'status_id' => $newStatus->id,
'checkout_at' => '2024-04-01',
'expected_checkin' => '2024-04-08',
'name' => 'Changed Name',
'note' => 'Here is a cool note!',
])
->assertOk();

$asset->refresh();
$this->assertTrue($asset->assignedTo()->is($target));
$this->assertEquals('Changed Name', $asset->name);
$this->assertTrue($asset->assetstatus->is($newStatus));
$this->assertEquals('2024-04-01 00:00:00', $asset->last_checkout);
$this->assertEquals('2024-04-08 00:00:00', (string) $asset->expected_checkin);

$expectedLocation
? $this->assertTrue($asset->location->is($expectedLocation))
: $this->assertNull($asset->location);

Event::assertDispatched(CheckoutableCheckedOut::class, 1);
Event::assertDispatched(function (CheckoutableCheckedOut $event) use ($admin, $asset, $target) {
$this->assertTrue($event->checkoutable->is($asset));
$this->assertTrue($event->checkedOutTo->is($target));
$this->assertTrue($event->checkedOutBy->is($admin));
$this->assertEquals('Here is a cool note!', $event->note);

return true;
});
}

public function testLicenseSeatsAreAssignedToUserUponCheckout()
{
$this->markTestIncomplete('This is not implemented');
}

public function testLastCheckoutUsesCurrentDateIfNotProvided()
{
$asset = Asset::factory()->create(['last_checkout' => now()->subMonth()]);

$this->actingAsForApi(User::factory()->checkoutAssets()->create())
->postJson(route('api.asset.checkout', $asset), [
'checkout_to_type' => 'user',
'assigned_user' => User::factory()->create()->id,
]);

$asset->refresh();

$this->assertTrue(Carbon::parse($asset->last_checkout)->diffInSeconds(now()) < 2);
}
}
Loading
Loading