ajout app

This commit is contained in:
2024-04-17 20:22:30 +02:00
parent cc017cfc5e
commit f9d05a2fd3
8025 changed files with 729805 additions and 0 deletions

View File

@ -0,0 +1,22 @@
<?php
namespace Tests;
use Illuminate\Contracts\Console\Kernel;
trait CreatesApplication
{
/**
* Creates the application.
*
* @return \Illuminate\Foundation\Application
*/
public function createApplication()
{
$app = require __DIR__.'/../bootstrap/app.php';
$app->make(Kernel::class)->bootstrap();
return $app;
}
}

View File

@ -0,0 +1,93 @@
<?php
namespace Tests\Feature\Api\Accessories;
use App\Models\Accessory;
use App\Models\Actionlog;
use App\Models\User;
use App\Notifications\CheckoutAccessoryNotification;
use Illuminate\Support\Facades\Notification;
use Tests\TestCase;
class AccessoryCheckoutTest extends TestCase
{
public function testCheckingOutAccessoryRequiresCorrectPermission()
{
$this->actingAsForApi(User::factory()->create())
->postJson(route('api.accessories.checkout', Accessory::factory()->create()))
->assertForbidden();
}
public function testValidationWhenCheckingOutAccessory()
{
$this->actingAsForApi(User::factory()->checkoutAccessories()->create())
->postJson(route('api.accessories.checkout', Accessory::factory()->create()), [
// missing assigned_to
])
->assertStatusMessageIs('error');
}
public function testAccessoryMustBeAvailableWhenCheckingOut()
{
$this->actingAsForApi(User::factory()->checkoutAccessories()->create())
->postJson(route('api.accessories.checkout', Accessory::factory()->withoutItemsRemaining()->create()), [
'assigned_to' => User::factory()->create()->id,
])
->assertStatusMessageIs('error');
}
public function testAccessoryCanBeCheckedOut()
{
$accessory = Accessory::factory()->create();
$user = User::factory()->create();
$this->actingAsForApi(User::factory()->checkoutAccessories()->create())
->postJson(route('api.accessories.checkout', $accessory), [
'assigned_to' => $user->id,
]);
$this->assertTrue($accessory->users->contains($user));
}
public function testUserSentNotificationUponCheckout()
{
Notification::fake();
$accessory = Accessory::factory()->requiringAcceptance()->create();
$user = User::factory()->create();
$this->actingAsForApi(User::factory()->checkoutAccessories()->create())
->postJson(route('api.accessories.checkout', $accessory), [
'assigned_to' => $user->id,
]);
Notification::assertSentTo($user, CheckoutAccessoryNotification::class);
}
public function testActionLogCreatedUponCheckout()
{
$accessory = Accessory::factory()->create();
$actor = User::factory()->checkoutAccessories()->create();
$user = User::factory()->create();
$this->actingAsForApi($actor)
->postJson(route('api.accessories.checkout', $accessory), [
'assigned_to' => $user->id,
'note' => 'oh hi there',
]);
$this->assertEquals(
1,
Actionlog::where([
'action_type' => 'checkout',
'target_id' => $user->id,
'target_type' => User::class,
'item_id' => $accessory->id,
'item_type' => Accessory::class,
'user_id' => $actor->id,
'note' => 'oh hi there',
])->count(),
'Log entry either does not exist or there are more than expected'
);
}
}

View File

@ -0,0 +1,162 @@
<?php
namespace Tests\Feature\Api\Assets;
use App\Events\CheckoutableCheckedIn;
use App\Models\Asset;
use App\Models\CheckoutAcceptance;
use App\Models\LicenseSeat;
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 AssetCheckinTest extends TestCase
{
public function testCheckingInAssetRequiresCorrectPermission()
{
$this->actingAsForApi(User::factory()->create())
->postJson(route('api.asset.checkin', Asset::factory()->assignedToUser()->create()))
->assertForbidden();
}
public function testCannotCheckInNonExistentAsset()
{
$this->actingAsForApi(User::factory()->checkinAssets()->create())
->postJson(route('api.asset.checkin', ['id' => 'does-not-exist']))
->assertStatusMessageIs('error');
}
public function testCannotCheckInAssetThatIsNotCheckedOut()
{
$this->actingAsForApi(User::factory()->checkinAssets()->create())
->postJson(route('api.asset.checkin', Asset::factory()->create()->id))
->assertStatusMessageIs('error');
}
public function testAssetCanBeCheckedIn()
{
Event::fake([CheckoutableCheckedIn::class]);
$user = User::factory()->create();
$location = Location::factory()->create();
$status = Statuslabel::factory()->create();
$asset = Asset::factory()->assignedToUser($user)->create([
'expected_checkin' => now()->addDay(),
'last_checkin' => null,
'accepted' => 'accepted',
]);
$this->assertTrue($asset->assignedTo->is($user));
$currentTimestamp = now();
$this->actingAsForApi(User::factory()->checkinAssets()->create())
->postJson(route('api.asset.checkin', $asset), [
'name' => 'Changed Name',
'status_id' => $status->id,
'location_id' => $location->id,
])
->assertOk();
$this->assertNull($asset->refresh()->assignedTo);
$this->assertNull($asset->expected_checkin);
$this->assertNull($asset->assignedTo);
$this->assertNull($asset->assigned_type);
$this->assertNull($asset->accepted);
$this->assertEquals('Changed Name', $asset->name);
$this->assertEquals($status->id, $asset->status_id);
$this->assertTrue($asset->location()->is($location));
Event::assertDispatched(function (CheckoutableCheckedIn $event) use ($currentTimestamp) {
// this could be better mocked but is ok for now.
return Carbon::parse($event->action_date)->diffInSeconds($currentTimestamp) < 2;
}, 1);
}
public function testLocationIsSetToRTDLocationByDefaultUponCheckin()
{
$rtdLocation = Location::factory()->create();
$asset = Asset::factory()->assignedToUser()->create([
'location_id' => Location::factory()->create()->id,
'rtd_location_id' => $rtdLocation->id,
]);
$this->actingAsForApi(User::factory()->checkinAssets()->create())
->postJson(route('api.asset.checkin', $asset->id));
$this->assertTrue($asset->refresh()->location()->is($rtdLocation));
}
public function testDefaultLocationCanBeUpdatedUponCheckin()
{
$location = Location::factory()->create();
$asset = Asset::factory()->assignedToUser()->create();
$this->actingAsForApi(User::factory()->checkinAssets()->create())
->postJson(route('api.asset.checkin', $asset), [
'location_id' => $location->id,
'update_default_location' => true,
]);
$this->assertTrue($asset->refresh()->defaultLoc()->is($location));
}
public function testAssetsLicenseSeatsAreClearedUponCheckin()
{
$asset = Asset::factory()->assignedToUser()->create();
LicenseSeat::factory()->assignedToUser()->for($asset)->create();
$this->assertNotNull($asset->licenseseats->first()->assigned_to);
$this->actingAsForApi(User::factory()->checkinAssets()->create())
->postJson(route('api.asset.checkin', $asset));
$this->assertNull($asset->refresh()->licenseseats->first()->assigned_to);
}
public function testLegacyLocationValuesSetToZeroAreUpdated()
{
$asset = Asset::factory()->canBeInvalidUponCreation()->assignedToUser()->create([
'rtd_location_id' => 0,
'location_id' => 0,
]);
$this->actingAsForApi(User::factory()->checkinAssets()->create())
->postJson(route('api.asset.checkin', $asset));
$this->assertNull($asset->refresh()->rtd_location_id);
$this->assertEquals($asset->location_id, $asset->rtd_location_id);
}
public function testPendingCheckoutAcceptancesAreClearedUponCheckin()
{
$asset = Asset::factory()->assignedToUser()->create();
$acceptance = CheckoutAcceptance::factory()->for($asset, 'checkoutable')->pending()->create();
$this->actingAsForApi(User::factory()->checkinAssets()->create())
->postJson(route('api.asset.checkin', $asset));
$this->assertFalse($acceptance->exists(), 'Acceptance was not deleted');
}
public function testCheckinTimeAndActionLogNoteCanBeSet()
{
Event::fake();
$this->actingAsForApi(User::factory()->checkinAssets()->create())
->postJson(route('api.asset.checkin', Asset::factory()->assignedToUser()->create()), [
// time is appended to the provided date in controller
'checkin_at' => '2023-01-02',
'note' => 'hi there',
]);
Event::assertDispatched(function (CheckoutableCheckedIn $event) {
return Carbon::parse('2023-01-02')->isSameDay(Carbon::parse($event->action_date))
&& $event->note === 'hi there';
}, 1);
}
}

View File

@ -0,0 +1,78 @@
<?php
namespace Tests\Feature\Api\Assets;
use App\Models\Asset;
use App\Models\Company;
use App\Models\User;
use Illuminate\Testing\Fluent\AssertableJson;
use Tests\TestCase;
class AssetIndexTest extends TestCase
{
public function testAssetIndexReturnsExpectedAssets()
{
Asset::factory()->count(3)->create();
$this->actingAsForApi(User::factory()->superuser()->create())
->getJson(
route('api.assets.index', [
'sort' => 'name',
'order' => 'asc',
'offset' => '0',
'limit' => '20',
]))
->assertOk()
->assertJsonStructure([
'total',
'rows',
])
->assertJson(fn(AssertableJson $json) => $json->has('rows', 3)->etc());
}
public function testAssetIndexAdheresToCompanyScoping()
{
[$companyA, $companyB] = Company::factory()->count(2)->create();
$assetA = Asset::factory()->for($companyA)->create();
$assetB = Asset::factory()->for($companyB)->create();
$superUser = $companyA->users()->save(User::factory()->superuser()->make());
$userInCompanyA = $companyA->users()->save(User::factory()->viewAssets()->make());
$userInCompanyB = $companyB->users()->save(User::factory()->viewAssets()->make());
$this->settings->disableMultipleFullCompanySupport();
$this->actingAsForApi($superUser)
->getJson(route('api.assets.index'))
->assertResponseContainsInRows($assetA, 'asset_tag')
->assertResponseContainsInRows($assetB, 'asset_tag');
$this->actingAsForApi($userInCompanyA)
->getJson(route('api.assets.index'))
->assertResponseContainsInRows($assetA, 'asset_tag')
->assertResponseContainsInRows($assetB, 'asset_tag');
$this->actingAsForApi($userInCompanyB)
->getJson(route('api.assets.index'))
->assertResponseContainsInRows($assetA, 'asset_tag')
->assertResponseContainsInRows($assetB, 'asset_tag');
$this->settings->enableMultipleFullCompanySupport();
$this->actingAsForApi($superUser)
->getJson(route('api.assets.index'))
->assertResponseContainsInRows($assetA, 'asset_tag')
->assertResponseContainsInRows($assetB, 'asset_tag');
$this->actingAsForApi($userInCompanyA)
->getJson(route('api.assets.index'))
->assertResponseContainsInRows($assetA, 'asset_tag')
->assertResponseDoesNotContainInRows($assetB, 'asset_tag');
$this->actingAsForApi($userInCompanyB)
->getJson(route('api.assets.index'))
->assertResponseDoesNotContainInRows($assetA, 'asset_tag')
->assertResponseContainsInRows($assetB, 'asset_tag');
}
}

View File

@ -0,0 +1,482 @@
<?php
namespace Tests\Feature\Api\Assets;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Company;
use App\Models\Location;
use App\Models\Statuslabel;
use App\Models\Supplier;
use App\Models\User;
use Illuminate\Testing\Fluent\AssertableJson;
use Tests\TestCase;
class AssetStoreTest extends TestCase
{
public function testRequiresPermissionToCreateAsset()
{
$this->actingAsForApi(User::factory()->create())
->postJson(route('api.assets.store'))
->assertForbidden();
}
public function testAllAssetAttributesAreStored()
{
$company = Company::factory()->create();
$location = Location::factory()->create();
$model = AssetModel::factory()->create();
$rtdLocation = Location::factory()->create();
$status = Statuslabel::factory()->create();
$supplier = Supplier::factory()->create();
$user = User::factory()->createAssets()->create();
$userAssigned = User::factory()->create();
$response = $this->actingAsForApi($user)
->postJson(route('api.assets.store'), [
'asset_eol_date' => '2024-06-02',
'asset_tag' => 'random_string',
'assigned_user' => $userAssigned->id,
'company_id' => $company->id,
'last_audit_date' => '2023-09-03',
'location_id' => $location->id,
'model_id' => $model->id,
'name' => 'A New Asset',
'notes' => 'Some notes',
'order_number' => '5678',
'purchase_cost' => '123.45',
'purchase_date' => '2023-09-02',
'requestable' => true,
'rtd_location_id' => $rtdLocation->id,
'serial' => '1234567890',
'status_id' => $status->id,
'supplier_id' => $supplier->id,
'warranty_months' => 10,
])
->assertOk()
->assertStatusMessageIs('success')
->json();
$asset = Asset::find($response['payload']['id']);
$this->assertTrue($asset->adminuser->is($user));
$this->assertEquals('2024-06-02', $asset->asset_eol_date);
$this->assertEquals('random_string', $asset->asset_tag);
$this->assertEquals($userAssigned->id, $asset->assigned_to);
$this->assertTrue($asset->company->is($company));
$this->assertEquals('2023-09-03 00:00:00', $asset->last_audit_date->format('Y-m-d H:i:s'));
$this->assertTrue($asset->location->is($location));
$this->assertTrue($asset->model->is($model));
$this->assertEquals('A New Asset', $asset->name);
$this->assertEquals('Some notes', $asset->notes);
$this->assertEquals('5678', $asset->order_number);
$this->assertEquals('123.45', $asset->purchase_cost);
$this->assertTrue($asset->purchase_date->is('2023-09-02'));
$this->assertEquals('1', $asset->requestable);
$this->assertTrue($asset->defaultLoc->is($rtdLocation));
$this->assertEquals('1234567890', $asset->serial);
$this->assertTrue($asset->assetstatus->is($status));
$this->assertTrue($asset->supplier->is($supplier));
$this->assertEquals(10, $asset->warranty_months);
}
public function testSetsLastAuditDateToMidnightOfProvidedDate()
{
$response = $this->actingAsForApi(User::factory()->superuser()->create())
->postJson(route('api.assets.store'), [
'last_audit_date' => '2023-09-03 12:23:45',
'asset_tag' => '1234',
'model_id' => AssetModel::factory()->create()->id,
'status_id' => Statuslabel::factory()->create()->id,
])
->assertOk()
->assertStatusMessageIs('success');
$asset = Asset::find($response['payload']['id']);
$this->assertEquals('00:00:00', $asset->last_audit_date->format('H:i:s'));
}
public function testLastAuditDateCanBeNull()
{
$response = $this->actingAsForApi(User::factory()->superuser()->create())
->postJson(route('api.assets.store'), [
// 'last_audit_date' => '2023-09-03 12:23:45',
'asset_tag' => '1234',
'model_id' => AssetModel::factory()->create()->id,
'status_id' => Statuslabel::factory()->create()->id,
])
->assertOk()
->assertStatusMessageIs('success');
$asset = Asset::find($response['payload']['id']);
$this->assertNull($asset->last_audit_date);
}
public function testNonDateUsedForLastAuditDateReturnsValidationError()
{
$response = $this->actingAsForApi(User::factory()->superuser()->create())
->postJson(route('api.assets.store'), [
'last_audit_date' => 'this-is-not-valid',
'asset_tag' => '1234',
'model_id' => AssetModel::factory()->create()->id,
'status_id' => Statuslabel::factory()->create()->id,
])
->assertStatusMessageIs('error');
$this->assertNotNull($response->json('messages.last_audit_date'));
}
public function testArchivedDepreciateAndPhysicalCanBeNull()
{
$model = AssetModel::factory()->ipadModel()->create();
$status = Statuslabel::factory()->create();
$this->settings->enableAutoIncrement();
$response = $this->actingAsForApi(User::factory()->superuser()->create())
->postJson(route('api.assets.store'), [
'model_id' => $model->id,
'status_id' => $status->id,
'archive' => null,
'depreciate' => null,
'physical' => null
])
->assertOk()
->assertStatusMessageIs('success')
->json();
$asset = Asset::find($response['payload']['id']);
$this->assertEquals(0, $asset->archived);
$this->assertEquals(1, $asset->physical);
$this->assertEquals(0, $asset->depreciate);
}
public function testArchivedDepreciateAndPhysicalCanBeEmpty()
{
$model = AssetModel::factory()->ipadModel()->create();
$status = Statuslabel::factory()->create();
$this->settings->enableAutoIncrement();
$response = $this->actingAsForApi(User::factory()->superuser()->create())
->postJson(route('api.assets.store'), [
'model_id' => $model->id,
'status_id' => $status->id,
'archive' => '',
'depreciate' => '',
'physical' => ''
])
->assertOk()
->assertStatusMessageIs('success')
->json();
$asset = Asset::find($response['payload']['id']);
$this->assertEquals(0, $asset->archived);
$this->assertEquals(1, $asset->physical);
$this->assertEquals(0, $asset->depreciate);
}
public function testAssetEolDateIsCalculatedIfPurchaseDateSet()
{
$model = AssetModel::factory()->mbp13Model()->create();
$status = Statuslabel::factory()->create();
$this->settings->enableAutoIncrement();
$response = $this->actingAsForApi(User::factory()->superuser()->create())
->postJson(route('api.assets.store'), [
'model_id' => $model->id,
'purchase_date' => '2021-01-01',
'status_id' => $status->id,
])
->assertOk()
->assertStatusMessageIs('success')
->json();
$asset = Asset::find($response['payload']['id']);
$this->assertEquals('2024-01-01', $asset->asset_eol_date);
}
public function testAssetEolDateIsNotCalculatedIfPurchaseDateNotSet()
{
$model = AssetModel::factory()->mbp13Model()->create();
$status = Statuslabel::factory()->create();
$this->settings->enableAutoIncrement();
$response = $this->actingAsForApi(User::factory()->superuser()->create())
->postJson(route('api.assets.store'), [
'model_id' => $model->id,
'status_id' => $status->id,
])
->assertOk()
->assertStatusMessageIs('success')
->json();
$asset = Asset::find($response['payload']['id']);
$this->assertNull($asset->asset_eol_date);
}
public function testAssetEolExplicitIsSetIfAssetEolDateIsExplicitlySet()
{
$model = AssetModel::factory()->mbp13Model()->create();
$status = Statuslabel::factory()->create();
$this->settings->enableAutoIncrement();
$response = $this->actingAsForApi(User::factory()->superuser()->create())
->postJson(route('api.assets.store'), [
'model_id' => $model->id,
'asset_eol_date' => '2025-01-01',
'status_id' => $status->id,
])
->assertOk()
->assertStatusMessageIs('success')
->json();
$asset = Asset::find($response['payload']['id']);
$this->assertEquals('2025-01-01', $asset->asset_eol_date);
$this->assertTrue($asset->eol_explicit);
}
public function testAssetGetsAssetTagWithAutoIncrement()
{
$model = AssetModel::factory()->create();
$status = Statuslabel::factory()->create();
$this->settings->enableAutoIncrement();
$response = $this->actingAsForApi(User::factory()->superuser()->create())
->postJson(route('api.assets.store'), [
'model_id' => $model->id,
'status_id' => $status->id,
])
->assertOk()
->assertStatusMessageIs('success')
->json();
$asset = Asset::find($response['payload']['id']);
$this->assertNotNull($asset->asset_tag);
}
public function testAssetCreationFailsWithNoAssetTagOrAutoIncrement()
{
$model = AssetModel::factory()->create();
$status = Statuslabel::factory()->create();
$this->settings->disableAutoIncrement();
$this->actingAsForApi(User::factory()->superuser()->create())
->postJson(route('api.assets.store'), [
'model_id' => $model->id,
'status_id' => $status->id,
])
->assertOk()
->assertStatusMessageIs('error');
}
public function testUniqueSerialNumbersIsEnforcedWhenEnabled()
{
$model = AssetModel::factory()->create();
$status = Statuslabel::factory()->create();
$serial = '1234567890';
$this->settings->enableAutoIncrement();
$this->settings->enableUniqueSerialNumbers();
$this->actingAsForApi(User::factory()->superuser()->create())
->postJson(route('api.assets.store'), [
'model_id' => $model->id,
'status_id' => $status->id,
'serial' => $serial,
])
->assertOk()
->assertStatusMessageIs('success');
$this->actingAsForApi(User::factory()->superuser()->create())
->postJson(route('api.assets.store'), [
'model_id' => $model->id,
'status_id' => $status->id,
'serial' => $serial,
])
->assertOk()
->assertStatusMessageIs('error');
}
public function testUniqueSerialNumbersIsNotEnforcedWhenDisabled()
{
$model = AssetModel::factory()->create();
$status = Statuslabel::factory()->create();
$serial = '1234567890';
$this->settings->enableAutoIncrement();
$this->settings->disableUniqueSerialNumbers();
$this->actingAsForApi(User::factory()->superuser()->create())
->postJson(route('api.assets.store'), [
'model_id' => $model->id,
'status_id' => $status->id,
'serial' => $serial,
])
->assertOk()
->assertStatusMessageIs('success');
$this->actingAsForApi(User::factory()->superuser()->create())
->postJson(route('api.assets.store'), [
'model_id' => $model->id,
'status_id' => $status->id,
'serial' => $serial,
])
->assertOk()
->assertStatusMessageIs('success');
}
public function testAssetTagsMustBeUniqueWhenUndeleted()
{
$model = AssetModel::factory()->create();
$status = Statuslabel::factory()->create();
$asset_tag = '1234567890';
$this->settings->disableAutoIncrement();
$this->actingAsForApi(User::factory()->superuser()->create())
->postJson(route('api.assets.store'), [
'asset_tag' => $asset_tag,
'model_id' => $model->id,
'status_id' => $status->id,
])
->assertOk()
->assertStatusMessageIs('success');
$this->actingAsForApi(User::factory()->superuser()->create())
->postJson(route('api.assets.store'), [
'asset_tag' => $asset_tag,
'model_id' => $model->id,
'status_id' => $status->id,
])
->assertOk()
->assertStatusMessageIs('error');
}
public function testAssetTagsCanBeDuplicatedIfDeleted()
{
$model = AssetModel::factory()->create();
$status = Statuslabel::factory()->create();
$asset_tag = '1234567890';
$this->settings->disableAutoIncrement();
$response = $this->actingAsForApi(User::factory()->superuser()->create())
->postJson(route('api.assets.store'), [
'asset_tag' => $asset_tag,
'model_id' => $model->id,
'status_id' => $status->id,
])
->assertOk()
->assertStatusMessageIs('success')
->json();
Asset::find($response['payload']['id'])->delete();
$this->actingAsForApi(User::factory()->superuser()->create())
->postJson(route('api.assets.store'), [
'asset_tag' => $asset_tag,
'model_id' => $model->id,
'status_id' => $status->id,
])
->assertOk()
->assertStatusMessageIs('success');
}
public function testAnAssetCanBeCheckedOutToUserOnStore()
{
$model = AssetModel::factory()->create();
$status = Statuslabel::factory()->create();
$user = User::factory()->createAssets()->create();
$userAssigned = User::factory()->create();
$this->settings->enableAutoIncrement();
$response = $this->actingAsForApi($user)
->postJson(route('api.assets.store'), [
'assigned_user' => $userAssigned->id,
'model_id' => $model->id,
'status_id' => $status->id,
])
->assertOk()
->assertStatusMessageIs('success')
->json();
$asset = Asset::find($response['payload']['id']);
$this->assertTrue($asset->adminuser->is($user));
$this->assertTrue($asset->checkedOutToUser());
$this->assertTrue($asset->assignedTo->is($userAssigned));
}
public function testAnAssetCanBeCheckedOutToLocationOnStore()
{
$model = AssetModel::factory()->create();
$status = Statuslabel::factory()->create();
$location = Location::factory()->create();
$user = User::factory()->createAssets()->create();
$this->settings->enableAutoIncrement();
$response = $this->actingAsForApi($user)
->postJson(route('api.assets.store'), [
'assigned_location' => $location->id,
'model_id' => $model->id,
'status_id' => $status->id,
])
->assertOk()
->assertStatusMessageIs('success')
->json();
$asset = Asset::find($response['payload']['id']);
$this->assertTrue($asset->adminuser->is($user));
$this->assertTrue($asset->checkedOutToLocation());
$this->assertTrue($asset->location->is($location));
}
public function testAnAssetCanBeCheckedOutToAssetOnStore()
{
$model = AssetModel::factory()->create();
$status = Statuslabel::factory()->create();
$asset = Asset::factory()->create();
$user = User::factory()->createAssets()->create();
$this->settings->enableAutoIncrement();
$response = $this->actingAsForApi($user)
->postJson(route('api.assets.store'), [
'assigned_asset' => $asset->id,
'model_id' => $model->id,
'status_id' => $status->id,
])
->assertOk()
->assertStatusMessageIs('success')
->json();
$apiAsset = Asset::find($response['payload']['id']);
$this->assertTrue($apiAsset->adminuser->is($user));
$this->assertTrue($apiAsset->checkedOutToAsset());
// I think this makes sense, but open to a sanity check
$this->assertTrue($asset->assignedAssets()->find($response['payload']['id'])->is($apiAsset));
}
public function testCompanyIdNeedsToBeInteger()
{
$this->actingAsForApi(User::factory()->createAssets()->create())
->postJson(route('api.assets.store'), [
'company_id' => [1],
])
->assertStatusMessageIs('error')
->assertJson(function (AssertableJson $json) {
$json->has('messages.company_id')->etc();
});
}
}

View File

@ -0,0 +1,73 @@
<?php
namespace Tests\Feature\Api\Assets;
use App\Models\Asset;
use App\Models\Company;
use App\Models\User;
use Tests\TestCase;
class AssetsForSelectListTest extends TestCase
{
public function testAssetsCanBeSearchedForByAssetTag()
{
Asset::factory()->create(['asset_tag' => '0001']);
Asset::factory()->create(['asset_tag' => '0002']);
$response = $this->actingAsForApi(User::factory()->create())
->getJson(route('assets.selectlist', ['search' => '000']))
->assertOk();
$results = collect($response->json('results'));
$this->assertEquals(2, $results->count());
$this->assertTrue($results->pluck('text')->contains(fn($text) => str_contains($text, '0001')));
$this->assertTrue($results->pluck('text')->contains(fn($text) => str_contains($text, '0002')));
}
public function testAssetsAreScopedToCompanyWhenMultipleCompanySupportEnabled()
{
[$companyA, $companyB] = Company::factory()->count(2)->create();
$assetA = Asset::factory()->for($companyA)->create(['asset_tag' => '0001']);
$assetB = Asset::factory()->for($companyB)->create(['asset_tag' => '0002']);
$superUser = $companyA->users()->save(User::factory()->superuser()->make());
$userInCompanyA = $companyA->users()->save(User::factory()->viewAssets()->make());
$userInCompanyB = $companyB->users()->save(User::factory()->viewAssets()->make());
$this->settings->disableMultipleFullCompanySupport();
$this->actingAsForApi($superUser)
->getJson(route('assets.selectlist', ['search' => '000']))
->assertResponseContainsInResults($assetA)
->assertResponseContainsInResults($assetB);
$this->actingAsForApi($userInCompanyA)
->getJson(route('assets.selectlist', ['search' => '000']))
->assertResponseContainsInResults($assetA)
->assertResponseContainsInResults($assetB);
$this->actingAsForApi($userInCompanyB)
->getJson(route('assets.selectlist', ['search' => '000']))
->assertResponseContainsInResults($assetA)
->assertResponseContainsInResults($assetB);
$this->settings->enableMultipleFullCompanySupport();
$this->actingAsForApi($superUser)
->getJson(route('assets.selectlist', ['search' => '000']))
->assertResponseContainsInResults($assetA)
->assertResponseContainsInResults($assetB);
$this->actingAsForApi($userInCompanyA)
->getJson(route('assets.selectlist', ['search' => '000']))
->assertResponseContainsInResults($assetA)
->assertResponseDoesNotContainInResults($assetB);
$this->actingAsForApi($userInCompanyB)
->getJson(route('assets.selectlist', ['search' => '000']))
->assertResponseDoesNotContainInResults($assetA)
->assertResponseContainsInResults($assetB);
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace Tests\Feature\Api\Assets;
use App\Models\Asset;
use App\Models\Company;
use App\Models\User;
use Tests\TestCase;
class RequestableAssetsTest extends TestCase
{
public function testViewingRequestableAssetsRequiresCorrectPermission()
{
$this->actingAsForApi(User::factory()->create())
->getJson(route('api.assets.requestable'))
->assertForbidden();
}
public function testReturnsRequestableAssets()
{
$requestableAsset = Asset::factory()->requestable()->create(['asset_tag' => 'requestable']);
$nonRequestableAsset = Asset::factory()->nonrequestable()->create(['asset_tag' => 'non-requestable']);
$this->actingAsForApi(User::factory()->viewRequestableAssets()->create())
->getJson(route('api.assets.requestable'))
->assertOk()
->assertResponseContainsInRows($requestableAsset, 'asset_tag')
->assertResponseDoesNotContainInRows($nonRequestableAsset, 'asset_tag');
}
public function testRequestableAssetsAreScopedToCompanyWhenMultipleCompanySupportEnabled()
{
[$companyA, $companyB] = Company::factory()->count(2)->create();
$assetA = Asset::factory()->requestable()->for($companyA)->create(['asset_tag' => '0001']);
$assetB = Asset::factory()->requestable()->for($companyB)->create(['asset_tag' => '0002']);
$superUser = $companyA->users()->save(User::factory()->superuser()->make());
$userInCompanyA = $companyA->users()->save(User::factory()->viewRequestableAssets()->make());
$userInCompanyB = $companyB->users()->save(User::factory()->viewRequestableAssets()->make());
$this->settings->disableMultipleFullCompanySupport();
$this->actingAsForApi($superUser)
->getJson(route('api.assets.requestable'))
->assertResponseContainsInRows($assetA, 'asset_tag')
->assertResponseContainsInRows($assetB, 'asset_tag');
$this->actingAsForApi($userInCompanyA)
->getJson(route('api.assets.requestable'))
->assertResponseContainsInRows($assetA, 'asset_tag')
->assertResponseContainsInRows($assetB, 'asset_tag');
$this->actingAsForApi($userInCompanyB)
->getJson(route('api.assets.requestable'))
->assertResponseContainsInRows($assetA, 'asset_tag')
->assertResponseContainsInRows($assetB, 'asset_tag');
$this->settings->enableMultipleFullCompanySupport();
$this->actingAsForApi($superUser)
->getJson(route('api.assets.requestable'))
->assertResponseContainsInRows($assetA, 'asset_tag')
->assertResponseContainsInRows($assetB, 'asset_tag');
$this->actingAsForApi($userInCompanyA)
->getJson(route('api.assets.requestable'))
->assertResponseContainsInRows($assetA, 'asset_tag')
->assertResponseDoesNotContainInRows($assetB, 'asset_tag');
$this->actingAsForApi($userInCompanyB)
->getJson(route('api.assets.requestable'))
->assertResponseDoesNotContainInRows($assetA, 'asset_tag')
->assertResponseContainsInRows($assetB, 'asset_tag');
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace Tests\Feature\Api\Components;
use App\Models\Company;
use App\Models\Component;
use App\Models\User;
use Tests\TestCase;
class ComponentIndexTest extends TestCase
{
public function testComponentIndexAdheresToCompanyScoping()
{
[$companyA, $companyB] = Company::factory()->count(2)->create();
$componentA = Component::factory()->for($companyA)->create();
$componentB = Component::factory()->for($companyB)->create();
$superUser = $companyA->users()->save(User::factory()->superuser()->make());
$userInCompanyA = $companyA->users()->save(User::factory()->viewComponents()->make());
$userInCompanyB = $companyB->users()->save(User::factory()->viewComponents()->make());
$this->settings->disableMultipleFullCompanySupport();
$this->actingAsForApi($superUser)
->getJson(route('api.components.index'))
->assertResponseContainsInRows($componentA)
->assertResponseContainsInRows($componentB);
$this->actingAsForApi($userInCompanyA)
->getJson(route('api.components.index'))
->assertResponseContainsInRows($componentA)
->assertResponseContainsInRows($componentB);
$this->actingAsForApi($userInCompanyB)
->getJson(route('api.components.index'))
->assertResponseContainsInRows($componentA)
->assertResponseContainsInRows($componentB);
$this->settings->enableMultipleFullCompanySupport();
$this->actingAsForApi($superUser)
->getJson(route('api.components.index'))
->assertResponseContainsInRows($componentA)
->assertResponseContainsInRows($componentB);
$this->actingAsForApi($userInCompanyA)
->getJson(route('api.components.index'))
->assertResponseContainsInRows($componentA)
->assertResponseDoesNotContainInRows($componentB);
$this->actingAsForApi($userInCompanyB)
->getJson(route('api.components.index'))
->assertResponseDoesNotContainInRows($componentA)
->assertResponseContainsInRows($componentB);
}
}

View File

@ -0,0 +1,93 @@
<?php
namespace Tests\Feature\Api\Consumables;
use App\Models\Actionlog;
use App\Models\Consumable;
use App\Models\User;
use App\Notifications\CheckoutConsumableNotification;
use Illuminate\Support\Facades\Notification;
use Tests\TestCase;
class ConsumableCheckoutTest extends TestCase
{
public function testCheckingOutConsumableRequiresCorrectPermission()
{
$this->actingAsForApi(User::factory()->create())
->postJson(route('api.consumables.checkout', Consumable::factory()->create()))
->assertForbidden();
}
public function testValidationWhenCheckingOutConsumable()
{
$this->actingAsForApi(User::factory()->checkoutConsumables()->create())
->postJson(route('api.consumables.checkout', Consumable::factory()->create()), [
// missing assigned_to
])
->assertStatusMessageIs('error');
}
public function testConsumableMustBeAvailableWhenCheckingOut()
{
$this->actingAsForApi(User::factory()->checkoutConsumables()->create())
->postJson(route('api.consumables.checkout', Consumable::factory()->withoutItemsRemaining()->create()), [
'assigned_to' => User::factory()->create()->id,
])
->assertStatusMessageIs('error');
}
public function testConsumableCanBeCheckedOut()
{
$consumable = Consumable::factory()->create();
$user = User::factory()->create();
$this->actingAsForApi(User::factory()->checkoutConsumables()->create())
->postJson(route('api.consumables.checkout', $consumable), [
'assigned_to' => $user->id,
]);
$this->assertTrue($user->consumables->contains($consumable));
}
public function testUserSentNotificationUponCheckout()
{
Notification::fake();
$consumable = Consumable::factory()->requiringAcceptance()->create();
$user = User::factory()->create();
$this->actingAsForApi(User::factory()->checkoutConsumables()->create())
->postJson(route('api.consumables.checkout', $consumable), [
'assigned_to' => $user->id,
]);
Notification::assertSentTo($user, CheckoutConsumableNotification::class);
}
public function testActionLogCreatedUponCheckout()
{$consumable = Consumable::factory()->create();
$actor = User::factory()->checkoutConsumables()->create();
$user = User::factory()->create();
$this->actingAsForApi($actor)
->postJson(route('api.consumables.checkout', $consumable), [
'assigned_to' => $user->id,
'note' => 'oh hi there',
]);
$this->assertEquals(
1,
Actionlog::where([
'action_type' => 'checkout',
'target_id' => $user->id,
'target_type' => User::class,
'item_id' => $consumable->id,
'item_type' => Consumable::class,
'user_id' => $actor->id,
'note' => 'oh hi there',
])->count(),
'Log entry either does not exist or there are more than expected'
);
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace Tests\Feature\Api\Consumables;
use App\Models\Company;
use App\Models\Consumable;
use App\Models\User;
use Tests\TestCase;
class ConsumablesIndexTest extends TestCase
{
public function testConsumableIndexAdheresToCompanyScoping()
{
[$companyA, $companyB] = Company::factory()->count(2)->create();
$consumableA = Consumable::factory()->for($companyA)->create();
$consumableB = Consumable::factory()->for($companyB)->create();
$superUser = $companyA->users()->save(User::factory()->superuser()->make());
$userInCompanyA = $companyA->users()->save(User::factory()->viewConsumables()->make());
$userInCompanyB = $companyB->users()->save(User::factory()->viewConsumables()->make());
$this->settings->disableMultipleFullCompanySupport();
$this->actingAsForApi($superUser)
->getJson(route('api.consumables.index'))
->assertResponseContainsInRows($consumableA)
->assertResponseContainsInRows($consumableB);
$this->actingAsForApi($userInCompanyA)
->getJson(route('api.consumables.index'))
->assertResponseContainsInRows($consumableA)
->assertResponseContainsInRows($consumableB);
$this->actingAsForApi($userInCompanyB)
->getJson(route('api.consumables.index'))
->assertResponseContainsInRows($consumableA)
->assertResponseContainsInRows($consumableB);
$this->settings->enableMultipleFullCompanySupport();
$this->actingAsForApi($superUser)
->getJson(route('api.consumables.index'))
->assertResponseContainsInRows($consumableA)
->assertResponseContainsInRows($consumableB);
$this->actingAsForApi($userInCompanyA)
->getJson(route('api.consumables.index'))
->assertResponseContainsInRows($consumableA)
->assertResponseDoesNotContainInRows($consumableB);
$this->actingAsForApi($userInCompanyB)
->getJson(route('api.consumables.index'))
->assertResponseDoesNotContainInRows($consumableA)
->assertResponseContainsInRows($consumableB);
}
}

View File

@ -0,0 +1,90 @@
<?php
namespace Tests\Feature\Api\Departments;
use App\Models\Company;
use App\Models\Department;
use App\Models\User;
use Illuminate\Testing\Fluent\AssertableJson;
use Tests\TestCase;
class DepartmentIndexTest extends TestCase
{
public function testViewingDepartmentIndexRequiresAuthentication()
{
$this->getJson(route('api.departments.index'))->assertRedirect();
}
public function testViewingDepartmentIndexRequiresPermission()
{
$this->actingAsForApi(User::factory()->create())
->getJson(route('api.departments.index'))
->assertForbidden();
}
public function testDepartmentIndexReturnsExpectedDepartments()
{
Department::factory()->count(3)->create();
$this->actingAsForApi(User::factory()->superuser()->create())
->getJson(
route('api.departments.index', [
'sort' => 'name',
'order' => 'asc',
'offset' => '0',
'limit' => '20',
]))
->assertOk()
->assertJsonStructure([
'total',
'rows',
])
->assertJson(fn(AssertableJson $json) => $json->has('rows', 3)->etc());
}
public function testDepartmentIndexAdheresToCompanyScoping()
{
[$companyA, $companyB] = Company::factory()->count(2)->create();
$departmentA = Department::factory()->for($companyA)->create();
$departmentB = Department::factory()->for($companyB)->create();
$superUser = $companyA->users()->save(User::factory()->superuser()->make());
$userInCompanyA = $companyA->users()->save(User::factory()->viewDepartments()->make());
$userInCompanyB = $companyB->users()->save(User::factory()->viewDepartments()->make());
$this->settings->disableMultipleFullCompanySupport();
$this->actingAsForApi($superUser)
->getJson(route('api.departments.index'))
->assertResponseContainsInRows($departmentA)
->assertResponseContainsInRows($departmentB);
$this->actingAsForApi($userInCompanyA)
->getJson(route('api.departments.index'))
->assertResponseContainsInRows($departmentA)
->assertResponseContainsInRows($departmentB);
$this->actingAsForApi($userInCompanyB)
->getJson(route('api.departments.index'))
->assertResponseContainsInRows($departmentA)
->assertResponseContainsInRows($departmentB);
$this->settings->enableMultipleFullCompanySupport();
$this->actingAsForApi($superUser)
->getJson(route('api.departments.index'))
->assertResponseContainsInRows($departmentA)
->assertResponseContainsInRows($departmentB);
$this->actingAsForApi($userInCompanyA)
->getJson(route('api.departments.index'))
->assertResponseContainsInRows($departmentA)
->assertResponseDoesNotContainInRows($departmentB);
$this->actingAsForApi($userInCompanyB)
->getJson(route('api.departments.index'))
->assertResponseDoesNotContainInRows($departmentA)
->assertResponseContainsInRows($departmentB);
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace Tests\Feature\Api\Groups;
use App\Models\Group;
use App\Models\User;
use Tests\TestCase;
class GroupStoreTest extends TestCase
{
public function testStoringGroupRequiresSuperAdminPermission()
{
$this->actingAsForApi(User::factory()->create())
->postJson(route('api.groups.store'))
->assertForbidden();
}
public function testCanStoreGroup()
{
$this->actingAsForApi(User::factory()->superuser()->create())
->postJson(route('api.groups.store'), [
'name' => 'My Awesome Group',
'permissions' => [
'admin' => '1',
'import' => '1',
'reports.view' => '0',
],
])
->assertOk();
$group = Group::where('name', 'My Awesome Group')->first();
$this->assertNotNull($group);
$this->assertEquals('1', $group->decodePermissions()['admin']);
$this->assertEquals('1', $group->decodePermissions()['import']);
$this->assertEquals('0', $group->decodePermissions()['reports.view']);
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace Tests\Feature\Api\Licenses;
use App\Models\Company;
use App\Models\License;
use App\Models\User;
use Tests\TestCase;
class LicensesIndexTest extends TestCase
{
public function testLicensesIndexAdheresToCompanyScoping()
{
[$companyA, $companyB] = Company::factory()->count(2)->create();
$licenseA = License::factory()->for($companyA)->create();
$licenseB = License::factory()->for($companyB)->create();
$superUser = $companyA->users()->save(User::factory()->superuser()->make());
$userInCompanyA = $companyA->users()->save(User::factory()->viewLicenses()->make());
$userInCompanyB = $companyB->users()->save(User::factory()->viewLicenses()->make());
$this->settings->disableMultipleFullCompanySupport();
$this->actingAsForApi($superUser)
->getJson(route('api.licenses.index'))
->assertResponseContainsInRows($licenseA)
->assertResponseContainsInRows($licenseB);
$this->actingAsForApi($userInCompanyA)
->getJson(route('api.licenses.index'))
->assertResponseContainsInRows($licenseA)
->assertResponseContainsInRows($licenseB);
$this->actingAsForApi($userInCompanyB)
->getJson(route('api.licenses.index'))
->assertResponseContainsInRows($licenseA)
->assertResponseContainsInRows($licenseB);
$this->settings->enableMultipleFullCompanySupport();
$this->actingAsForApi($superUser)
->getJson(route('api.licenses.index'))
->assertResponseContainsInRows($licenseA)
->assertResponseContainsInRows($licenseB);
$this->actingAsForApi($userInCompanyA)
->getJson(route('api.licenses.index'))
->assertResponseContainsInRows($licenseA)
->assertResponseDoesNotContainInRows($licenseB);
$this->actingAsForApi($userInCompanyB)
->getJson(route('api.licenses.index'))
->assertResponseDoesNotContainInRows($licenseA)
->assertResponseContainsInRows($licenseB);
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace Tests\Feature\Api\Locations;
use App\Models\Location;
use App\Models\User;
use Illuminate\Testing\Fluent\AssertableJson;
use Tests\TestCase;
class LocationsForSelectListTest extends TestCase
{
public function testGettingLocationListRequiresProperPermission()
{
$this->actingAsForApi(User::factory()->create())
->getJson(route('api.locations.selectlist'))
->assertForbidden();
}
public function testLocationsReturned()
{
Location::factory()->create();
// see the where the "view.selectlists" is defined in the AuthServiceProvider
// for info on why "createUsers()" is used here.
$this->actingAsForApi(User::factory()->createUsers()->create())
->getJson(route('api.locations.selectlist'))
->assertOk()
->assertJsonStructure([
'results',
'pagination',
'total_count',
'page',
'page_count',
])
->assertJson(fn(AssertableJson $json) => $json->has('results', 1)->etc());
}
public function testLocationsAreReturnedWhenUserIsUpdatingTheirProfileAndHasPermissionToUpdateLocation()
{
$this->actingAsForApi(User::factory()->canEditOwnLocation()->create())
->withHeader('referer', route('profile'))
->getJson(route('api.locations.selectlist'))
->assertOk();
}
}

View File

@ -0,0 +1,61 @@
<?php
namespace Tests\Feature\Api\Users;
use App\Models\User;
use Tests\TestCase;
class UpdateUserApiTest extends TestCase
{
public function testApiUsersCanBeActivatedWithNumber()
{
$admin = User::factory()->superuser()->create();
$user = User::factory()->create(['activated' => 0]);
$this->actingAsForApi($admin)
->patch(route('api.users.update', $user), [
'activated' => 1,
]);
$this->assertEquals(1, $user->refresh()->activated);
}
public function testApiUsersCanBeActivatedWithBooleanTrue()
{
$admin = User::factory()->superuser()->create();
$user = User::factory()->create(['activated' => false]);
$this->actingAsForApi($admin)
->patch(route('api.users.update', $user), [
'activated' => true,
]);
$this->assertEquals(1, $user->refresh()->activated);
}
public function testApiUsersCanBeDeactivatedWithNumber()
{
$admin = User::factory()->superuser()->create();
$user = User::factory()->create(['activated' => true]);
$this->actingAsForApi($admin)
->patch(route('api.users.update', $user), [
'activated' => 0,
]);
$this->assertEquals(0, $user->refresh()->activated);
}
public function testApiUsersCanBeDeactivatedWithBooleanFalse()
{
$admin = User::factory()->superuser()->create();
$user = User::factory()->create(['activated' => true]);
$this->actingAsForApi($admin)
->patch(route('api.users.update', $user), [
'activated' => false,
]);
$this->assertEquals(0, $user->refresh()->activated);
}
}

View File

@ -0,0 +1,97 @@
<?php
namespace Tests\Feature\Api\Users;
use App\Models\Company;
use App\Models\User;
use Illuminate\Testing\Fluent\AssertableJson;
use Laravel\Passport\Passport;
use Tests\TestCase;
class UsersForSelectListTest extends TestCase
{
public function testUsersAreReturned()
{
$users = User::factory()->superuser()->count(3)->create();
Passport::actingAs($users->first());
$this->getJson(route('api.users.selectlist'))
->assertOk()
->assertJsonStructure([
'results',
'pagination',
'total_count',
'page',
'page_count',
])
->assertJson(fn(AssertableJson $json) => $json->has('results', 3)->etc());
}
public function testUsersCanBeSearchedByFirstAndLastName()
{
User::factory()->create(['first_name' => 'Luke', 'last_name' => 'Skywalker']);
Passport::actingAs(User::factory()->create());
$response = $this->getJson(route('api.users.selectlist', ['search' => 'luke sky']))->assertOk();
$results = collect($response->json('results'));
$this->assertEquals(1, $results->count());
$this->assertTrue($results->pluck('text')->contains(fn($text) => str_contains($text, 'Luke')));
}
public function testUsersScopedToCompanyWhenMultipleFullCompanySupportEnabled()
{
$this->settings->enableMultipleFullCompanySupport();
$jedi = Company::factory()->has(User::factory()->count(3)->sequence(
['first_name' => 'Luke', 'last_name' => 'Skywalker', 'username' => 'lskywalker'],
['first_name' => 'Obi-Wan', 'last_name' => 'Kenobi', 'username' => 'okenobi'],
['first_name' => 'Anakin', 'last_name' => 'Skywalker', 'username' => 'askywalker'],
))->create();
$sith = Company::factory()
->has(User::factory()->state(['first_name' => 'Darth', 'last_name' => 'Vader', 'username' => 'dvader']))
->create();
Passport::actingAs($jedi->users->first());
$response = $this->getJson(route('api.users.selectlist'))->assertOk();
$results = collect($response->json('results'));
$this->assertEquals(3, $results->count());
$this->assertTrue(
$results->pluck('text')->contains(fn($text) => str_contains($text, 'Luke'))
);
$this->assertFalse(
$results->pluck('text')->contains(fn($text) => str_contains($text, 'Darth'))
);
}
public function testUsersScopedToCompanyDuringSearchWhenMultipleFullCompanySupportEnabled()
{
$this->settings->enableMultipleFullCompanySupport();
$jedi = Company::factory()->has(User::factory()->count(3)->sequence(
['first_name' => 'Luke', 'last_name' => 'Skywalker', 'username' => 'lskywalker'],
['first_name' => 'Obi-Wan', 'last_name' => 'Kenobi', 'username' => 'okenobi'],
['first_name' => 'Anakin', 'last_name' => 'Skywalker', 'username' => 'askywalker'],
))->create();
Company::factory()
->has(User::factory()->state(['first_name' => 'Darth', 'last_name' => 'Vader', 'username' => 'dvader']))
->create();
Passport::actingAs($jedi->users->first());
$response = $this->getJson(route('api.users.selectlist', ['search' => 'a']))->assertOk();
$results = collect($response->json('results'));
$this->assertEquals(3, $results->count());
$this->assertTrue($results->pluck('text')->contains(fn($text) => str_contains($text, 'Luke')));
$this->assertTrue($results->pluck('text')->contains(fn($text) => str_contains($text, 'Anakin')));
$response = $this->getJson(route('api.users.selectlist', ['search' => 'v']))->assertOk();
$this->assertEquals(0, collect($response->json('results'))->count());
}
}

View File

@ -0,0 +1,147 @@
<?php
namespace Tests\Feature\Api\Users;
use App\Models\Company;
use App\Models\User;
use Laravel\Passport\Passport;
use Tests\TestCase;
class UsersSearchTest extends TestCase
{
public function testCanSearchByUserFirstAndLastName()
{
User::factory()->create(['first_name' => 'Luke', 'last_name' => 'Skywalker']);
User::factory()->create(['first_name' => 'Darth', 'last_name' => 'Vader']);
Passport::actingAs(User::factory()->viewUsers()->create());
$response = $this->getJson(route('api.users.index', ['search' => 'luke sky']))->assertOk();
$results = collect($response->json('rows'));
$this->assertEquals(1, $results->count());
$this->assertTrue($results->pluck('name')->contains(fn($text) => str_contains($text, 'Luke')));
$this->assertFalse($results->pluck('name')->contains(fn($text) => str_contains($text, 'Darth')));
}
public function testResultsWhenSearchingForActiveUsers()
{
User::factory()->create(['first_name' => 'Active', 'last_name' => 'User']);
User::factory()->create(['first_name' => 'Deleted', 'last_name' => 'User'])->delete();
$response = $this->actingAsForApi(User::factory()->viewUsers()->create())
->getJson(route('api.users.index', [
'deleted' => 'false',
'company_id' => '',
'search' => 'user',
'order' => 'asc',
'offset' => '0',
'limit' => '20',
]))
->assertOk();
$firstNames = collect($response->json('rows'))->pluck('first_name');
$this->assertTrue(
$firstNames->contains('Active'),
'Expected user does not appear in results'
);
$this->assertFalse(
$firstNames->contains('Deleted'),
'Unexpected deleted user appears in results'
);
}
public function testResultsWhenSearchingForDeletedUsers()
{
User::factory()->create(['first_name' => 'Active', 'last_name' => 'User']);
User::factory()->create(['first_name' => 'Deleted', 'last_name' => 'User'])->delete();
$response = $this->actingAsForApi(User::factory()->viewUsers()->create())
->getJson(route('api.users.index', [
'deleted' => 'true',
'company_id' => '',
'search' => 'user',
'order' => 'asc',
'offset' => '0',
'limit' => '20',
]))
->assertOk();
$firstNames = collect($response->json('rows'))->pluck('first_name');
$this->assertFalse(
$firstNames->contains('Active'),
'Unexpected active user appears in results'
);
$this->assertTrue(
$firstNames->contains('Deleted'),
'Expected deleted user does not appear in results'
);
}
public function testUsersScopedToCompanyWhenMultipleFullCompanySupportEnabled()
{
$this->settings->enableMultipleFullCompanySupport();
$companyA = Company::factory()
->has(User::factory(['first_name' => 'Company A', 'last_name' => 'User']))
->create();
Company::factory()
->has(User::factory(['first_name' => 'Company B', 'last_name' => 'User']))
->create();
$response = $this->actingAsForApi(User::factory()->for($companyA)->viewUsers()->create())
->getJson(route('api.users.index'))
->assertOk();
$results = collect($response->json('rows'));
$this->assertTrue(
$results->pluck('name')->contains(fn($text) => str_contains($text, 'Company A')),
'User index does not contain expected user'
);
$this->assertFalse(
$results->pluck('name')->contains(fn($text) => str_contains($text, 'Company B')),
'User index contains unexpected user from another company'
);
}
public function testUsersScopedToCompanyDuringSearchWhenMultipleFullCompanySupportEnabled()
{
$this->settings->enableMultipleFullCompanySupport();
$companyA = Company::factory()
->has(User::factory(['first_name' => 'Company A', 'last_name' => 'User']))
->create();
Company::factory()
->has(User::factory(['first_name' => 'Company B', 'last_name' => 'User']))
->create();
$response = $this->actingAsForApi(User::factory()->for($companyA)->viewUsers()->create())
->getJson(route('api.users.index', [
'deleted' => 'false',
'company_id' => null,
'search' => 'user',
'order' => 'asc',
'offset' => '0',
'limit' => '20',
]))
->assertOk();
$results = collect($response->json('rows'));
$this->assertTrue(
$results->pluck('name')->contains(fn($text) => str_contains($text, 'Company A')),
'User index does not contain expected user'
);
$this->assertFalse(
$results->pluck('name')->contains(fn($text) => str_contains($text, 'Company B')),
'User index contains unexpected user from another company'
);
}
}

View File

@ -0,0 +1,84 @@
<?php
namespace Tests\Feature\Api\Users;
use App\Models\Company;
use App\Models\Department;
use App\Models\Group;
use App\Models\Location;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Tests\TestCase;
class UsersUpdateTest extends TestCase
{
public function testCanUpdateUserViaPatch()
{
$admin = User::factory()->superuser()->create();
$manager = User::factory()->create();
$company = Company::factory()->create();
$department = Department::factory()->create();
$location = Location::factory()->create();
[$groupA, $groupB] = Group::factory()->count(2)->create();
$user = User::factory()->create([
'activated' => false,
'remote' => false,
'vip' => false,
]);
$this->actingAsForApi($admin)
->patchJson(route('api.users.update', $user), [
'first_name' => 'Mabel',
'last_name' => 'Mora',
'username' => 'mabel',
'password' => 'super-secret',
'email' => 'mabel@onlymurderspod.com',
'permissions' => '{"a.new.permission":"1"}',
'activated' => true,
'phone' => '619-555-5555',
'jobtitle' => 'Host',
'manager_id' => $manager->id,
'employee_num' => '1111',
'notes' => 'Pretty good artist',
'company_id' => $company->id,
'department_id' => $department->id,
'location_id' => $location->id,
'remote' => true,
'groups' => $groupA->id,
'vip' => true,
'start_date' => '2021-08-01',
'end_date' => '2025-12-31',
])
->assertOk();
$user->refresh();
$this->assertEquals('Mabel', $user->first_name, 'First name was not updated');
$this->assertEquals('Mora', $user->last_name, 'Last name was not updated');
$this->assertEquals('mabel', $user->username, 'Username was not updated');
$this->assertTrue(Hash::check('super-secret', $user->password), 'Password was not updated');
$this->assertEquals('mabel@onlymurderspod.com', $user->email, 'Email was not updated');
$this->assertArrayHasKey('a.new.permission', $user->decodePermissions(), 'Permissions were not updated');
$this->assertTrue((bool)$user->activated, 'User not marked as activated');
$this->assertEquals('619-555-5555', $user->phone, 'Phone was not updated');
$this->assertEquals('Host', $user->jobtitle, 'Job title was not updated');
$this->assertTrue($user->manager->is($manager), 'Manager was not updated');
$this->assertEquals('1111', $user->employee_num, 'Employee number was not updated');
$this->assertEquals('Pretty good artist', $user->notes, 'Notes was not updated');
$this->assertTrue($user->company->is($company), 'Company was not updated');
$this->assertTrue($user->department->is($department), 'Department was not updated');
$this->assertTrue($user->location->is($location), 'Location was not updated');
$this->assertEquals(1, $user->remote, 'Remote was not updated');
$this->assertTrue($user->groups->contains($groupA), 'Groups were not updated');
$this->assertEquals(1, $user->vip, 'VIP was not updated');
$this->assertEquals('2021-08-01', $user->start_date, 'Start date was not updated');
$this->assertEquals('2025-12-31', $user->end_date, 'End date was not updated');
// `groups` can be an id or array or ids
$this->patch(route('api.users.update', $user), ['groups' => [$groupA->id, $groupB->id]]);
$user->refresh();
$this->assertTrue($user->groups->contains($groupA), 'Not part of expected group');
$this->assertTrue($user->groups->contains($groupB), 'Not part of expected group');
}
}

View File

@ -0,0 +1,88 @@
<?php
namespace Tests\Feature\Checkins;
use App\Events\CheckoutableCheckedIn;
use App\Models\Accessory;
use App\Models\User;
use App\Notifications\CheckinAccessoryNotification;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Notification;
use Tests\TestCase;
class AccessoryCheckinTest extends TestCase
{
public function testCheckingInAccessoryRequiresCorrectPermission()
{
$accessory = Accessory::factory()->checkedOutToUser()->create();
$this->actingAs(User::factory()->create())
->post(route('accessories.checkin.store', $accessory->users->first()->pivot->id))
->assertForbidden();
}
public function testAccessoryCanBeCheckedIn()
{
Event::fake([CheckoutableCheckedIn::class]);
$user = User::factory()->create();
$accessory = Accessory::factory()->checkedOutToUser($user)->create();
$this->assertTrue($accessory->users->contains($user));
$this->actingAs(User::factory()->checkinAccessories()->create())
->post(route('accessories.checkin.store', $accessory->users->first()->pivot->id));
$this->assertFalse($accessory->fresh()->users->contains($user));
Event::assertDispatched(CheckoutableCheckedIn::class, 1);
}
public function testEmailSentToUserIfSettingEnabled()
{
Notification::fake();
$user = User::factory()->create();
$accessory = Accessory::factory()->checkedOutToUser($user)->create();
$accessory->category->update(['checkin_email' => true]);
event(new CheckoutableCheckedIn(
$accessory,
$user,
User::factory()->checkinAccessories()->create(),
'',
));
Notification::assertSentTo(
[$user],
function (CheckinAccessoryNotification $notification, $channels) {
return in_array('mail', $channels);
},
);
}
public function testEmailNotSentToUserIfSettingDisabled()
{
Notification::fake();
$user = User::factory()->create();
$accessory = Accessory::factory()->checkedOutToUser($user)->create();
$accessory->category->update(['checkin_email' => false]);
event(new CheckoutableCheckedIn(
$accessory,
$user,
User::factory()->checkinAccessories()->create(),
'',
));
Notification::assertNotSentTo(
[$user],
function (CheckinAccessoryNotification $notification, $channels) {
return in_array('mail', $channels);
},
);
}
}

View File

@ -0,0 +1,163 @@
<?php
namespace Tests\Feature\Checkins;
use App\Events\CheckoutableCheckedIn;
use App\Models\Asset;
use App\Models\CheckoutAcceptance;
use App\Models\LicenseSeat;
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 AssetCheckinTest extends TestCase
{
public function testCheckingInAssetRequiresCorrectPermission()
{
$this->actingAs(User::factory()->create())
->post(route('hardware.checkin.store', [
'assetId' => Asset::factory()->assignedToUser()->create()->id,
]))
->assertForbidden();
}
public function testCannotCheckInAssetThatIsNotCheckedOut()
{
$this->actingAs(User::factory()->checkinAssets()->create())
->post(route('hardware.checkin.store', ['assetId' => Asset::factory()->create()->id]))
->assertSessionHas('error')
->assertRedirect(route('hardware.index'));
}
public function testAssetCanBeCheckedIn()
{
Event::fake([CheckoutableCheckedIn::class]);
$user = User::factory()->create();
$location = Location::factory()->create();
$status = Statuslabel::first() ?? Statuslabel::factory()->create();
$asset = Asset::factory()->assignedToUser($user)->create([
'expected_checkin' => now()->addDay(),
'last_checkin' => null,
'accepted' => 'accepted',
]);
$this->assertTrue($asset->assignedTo->is($user));
$currentTimestamp = now();
$this->actingAs(User::factory()->checkinAssets()->create())
->post(
route('hardware.checkin.store', ['assetId' => $asset->id, 'backto' => 'user']),
[
'name' => 'Changed Name',
'status_id' => $status->id,
'location_id' => $location->id,
],
)
->assertRedirect(route('users.show', $user));
$this->assertNull($asset->refresh()->assignedTo);
$this->assertNull($asset->expected_checkin);
$this->assertNotNull($asset->last_checkin);
$this->assertNull($asset->assignedTo);
$this->assertNull($asset->assigned_type);
$this->assertNull($asset->accepted);
$this->assertEquals('Changed Name', $asset->name);
$this->assertEquals($status->id, $asset->status_id);
$this->assertTrue($asset->location()->is($location));
Event::assertDispatched(function (CheckoutableCheckedIn $event) use ($currentTimestamp) {
// this could be better mocked but is ok for now.
return Carbon::parse($event->action_date)->diffInSeconds($currentTimestamp) < 2;
}, 1);
}
public function testLocationIsSetToRTDLocationByDefaultUponCheckin()
{
$rtdLocation = Location::factory()->create();
$asset = Asset::factory()->assignedToUser()->create([
'location_id' => Location::factory()->create()->id,
'rtd_location_id' => $rtdLocation->id,
]);
$this->actingAs(User::factory()->checkinAssets()->create())
->post(route('hardware.checkin.store', ['assetId' => $asset->id]));
$this->assertTrue($asset->refresh()->location()->is($rtdLocation));
}
public function testDefaultLocationCanBeUpdatedUponCheckin()
{
$location = Location::factory()->create();
$asset = Asset::factory()->assignedToUser()->create();
$this->actingAs(User::factory()->checkinAssets()->create())
->post(route('hardware.checkin.store', ['assetId' => $asset->id]), [
'location_id' => $location->id,
'update_default_location' => 0
]);
$this->assertTrue($asset->refresh()->defaultLoc()->is($location));
}
public function testAssetsLicenseSeatsAreClearedUponCheckin()
{
$asset = Asset::factory()->assignedToUser()->create();
LicenseSeat::factory()->assignedToUser()->for($asset)->create();
$this->assertNotNull($asset->licenseseats->first()->assigned_to);
$this->actingAs(User::factory()->checkinAssets()->create())
->post(route('hardware.checkin.store', ['assetId' => $asset->id]));
$this->assertNull($asset->refresh()->licenseseats->first()->assigned_to);
}
public function testLegacyLocationValuesSetToZeroAreUpdated()
{
$asset = Asset::factory()->canBeInvalidUponCreation()->assignedToUser()->create([
'rtd_location_id' => 0,
'location_id' => 0,
]);
$this->actingAs(User::factory()->checkinAssets()->create())
->post(route('hardware.checkin.store', ['assetId' => $asset->id]));
$this->assertNull($asset->refresh()->rtd_location_id);
$this->assertEquals($asset->location_id, $asset->rtd_location_id);
}
public function testPendingCheckoutAcceptancesAreClearedUponCheckin()
{
$asset = Asset::factory()->assignedToUser()->create();
$acceptance = CheckoutAcceptance::factory()->for($asset, 'checkoutable')->pending()->create();
$this->actingAs(User::factory()->checkinAssets()->create())
->post(route('hardware.checkin.store', ['assetId' => $asset->id]));
$this->assertFalse($acceptance->exists(), 'Acceptance was not deleted');
}
public function testCheckinTimeAndActionLogNoteCanBeSet()
{
Event::fake([CheckoutableCheckedIn::class]);
$this->actingAs(User::factory()->checkinAssets()->create())
->post(route(
'hardware.checkin.store',
['assetId' => Asset::factory()->assignedToUser()->create()->id]
), [
'checkin_at' => '2023-01-02',
'note' => 'hello'
]);
Event::assertDispatched(function (CheckoutableCheckedIn $event) {
return $event->action_date === '2023-01-02' && $event->note === 'hello';
}, 1);
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace Tests\Feature\CheckoutAcceptances;
use App\Models\Accessory;
use App\Models\CheckoutAcceptance;
use App\Notifications\AcceptanceAssetAcceptedNotification;
use App\Notifications\AcceptanceAssetDeclinedNotification;
use Notification;
use Tests\TestCase;
class AccessoryAcceptanceTest extends TestCase
{
/**
* This can be absorbed into a bigger test
*/
public function testUsersNameIsIncludedInAccessoryAcceptedNotification()
{
Notification::fake();
$this->settings->enableAlertEmail();
$acceptance = CheckoutAcceptance::factory()
->pending()
->for(Accessory::factory()->appleMouse(), 'checkoutable')
->create();
$this->actingAs($acceptance->assignedTo)
->post(route('account.store-acceptance', $acceptance), ['asset_acceptance' => 'accepted'])
->assertSessionHasNoErrors();
$this->assertNotNull($acceptance->fresh()->accepted_at);
Notification::assertSentTo(
$acceptance,
function (AcceptanceAssetAcceptedNotification $notification) use ($acceptance) {
$this->assertStringContainsString(
$acceptance->assignedTo->present()->fullName,
$notification->toMail()->render()
);
return true;
}
);
}
/**
* This can be absorbed into a bigger test
*/
public function testUsersNameIsIncludedInAccessoryDeclinedNotification()
{
Notification::fake();
$this->settings->enableAlertEmail();
$acceptance = CheckoutAcceptance::factory()
->pending()
->for(Accessory::factory()->appleMouse(), 'checkoutable')
->create();
$this->actingAs($acceptance->assignedTo)
->post(route('account.store-acceptance', $acceptance), ['asset_acceptance' => 'declined'])
->assertSessionHasNoErrors();
$this->assertNotNull($acceptance->fresh()->declined_at);
Notification::assertSentTo(
$acceptance,
function (AcceptanceAssetDeclinedNotification $notification) use ($acceptance) {
$this->assertStringContainsString(
$acceptance->assignedTo->present()->fullName,
$notification->toMail($acceptance)->render()
);
return true;
}
);
}
}

View File

@ -0,0 +1,93 @@
<?php
namespace Tests\Feature\Checkouts;
use App\Models\Accessory;
use App\Models\Actionlog;
use App\Models\User;
use App\Notifications\CheckoutAccessoryNotification;
use Illuminate\Support\Facades\Notification;
use Tests\TestCase;
class AccessoryCheckoutTest extends TestCase
{
public function testCheckingOutAccessoryRequiresCorrectPermission()
{
$this->actingAs(User::factory()->create())
->post(route('accessories.checkout.store', Accessory::factory()->create()))
->assertForbidden();
}
public function testValidationWhenCheckingOutAccessory()
{
$this->actingAs(User::factory()->checkoutAccessories()->create())
->post(route('accessories.checkout.store', Accessory::factory()->create()), [
// missing assigned_to
])
->assertSessionHas('error');
}
public function testAccessoryMustBeAvailableWhenCheckingOut()
{
$this->actingAs(User::factory()->checkoutAccessories()->create())
->post(route('accessories.checkout.store', Accessory::factory()->withoutItemsRemaining()->create()), [
'assigned_to' => User::factory()->create()->id,
])
->assertSessionHas('error');
}
public function testAccessoryCanBeCheckedOut()
{
$accessory = Accessory::factory()->create();
$user = User::factory()->create();
$this->actingAs(User::factory()->checkoutAccessories()->create())
->post(route('accessories.checkout.store', $accessory), [
'assigned_to' => $user->id,
]);
$this->assertTrue($accessory->users->contains($user));
}
public function testUserSentNotificationUponCheckout()
{
Notification::fake();
$accessory = Accessory::factory()->requiringAcceptance()->create();
$user = User::factory()->create();
$this->actingAs(User::factory()->checkoutAccessories()->create())
->post(route('accessories.checkout.store', $accessory), [
'assigned_to' => $user->id,
]);
Notification::assertSentTo($user, CheckoutAccessoryNotification::class);
}
public function testActionLogCreatedUponCheckout()
{
$accessory = Accessory::factory()->create();
$actor = User::factory()->checkoutAccessories()->create();
$user = User::factory()->create();
$this->actingAs($actor)
->post(route('accessories.checkout.store', $accessory), [
'assigned_to' => $user->id,
'note' => 'oh hi there',
]);
$this->assertEquals(
1,
Actionlog::where([
'action_type' => 'checkout',
'target_id' => $user->id,
'target_type' => User::class,
'item_id' => $accessory->id,
'item_type' => Accessory::class,
'user_id' => $actor->id,
'note' => 'oh hi there',
])->count(),
'Log entry either does not exist or there are more than expected'
);
}
}

View File

@ -0,0 +1,93 @@
<?php
namespace Tests\Feature\Checkouts;
use App\Models\Actionlog;
use App\Models\Consumable;
use App\Models\User;
use App\Notifications\CheckoutConsumableNotification;
use Illuminate\Support\Facades\Notification;
use Tests\TestCase;
class ConsumableCheckoutTest extends TestCase
{
public function testCheckingOutConsumableRequiresCorrectPermission()
{
$this->actingAs(User::factory()->create())
->post(route('consumables.checkout.store', Consumable::factory()->create()))
->assertForbidden();
}
public function testValidationWhenCheckingOutConsumable()
{
$this->actingAs(User::factory()->checkoutConsumables()->create())
->post(route('consumables.checkout.store', Consumable::factory()->create()), [
// missing assigned_to
])
->assertSessionHas('error');
}
public function testConsumableMustBeAvailableWhenCheckingOut()
{
$this->actingAs(User::factory()->checkoutConsumables()->create())
->post(route('consumables.checkout.store', Consumable::factory()->withoutItemsRemaining()->create()), [
'assigned_to' => User::factory()->create()->id,
])
->assertSessionHas('error');
}
public function testConsumableCanBeCheckedOut()
{
$consumable = Consumable::factory()->create();
$user = User::factory()->create();
$this->actingAs(User::factory()->checkoutConsumables()->create())
->post(route('consumables.checkout.store', $consumable), [
'assigned_to' => $user->id,
]);
$this->assertTrue($user->consumables->contains($consumable));
}
public function testUserSentNotificationUponCheckout()
{
Notification::fake();
$consumable = Consumable::factory()->create();
$user = User::factory()->create();
$this->actingAs(User::factory()->checkoutConsumables()->create())
->post(route('consumables.checkout.store', $consumable), [
'assigned_to' => $user->id,
]);
Notification::assertSentTo($user, CheckoutConsumableNotification::class);
}
public function testActionLogCreatedUponCheckout()
{
$consumable = Consumable::factory()->create();
$actor = User::factory()->checkoutConsumables()->create();
$user = User::factory()->create();
$this->actingAs($actor)
->post(route('consumables.checkout.store', $consumable), [
'assigned_to' => $user->id,
'note' => 'oh hi there',
]);
$this->assertEquals(
1,
Actionlog::where([
'action_type' => 'checkout',
'target_id' => $user->id,
'target_type' => User::class,
'item_id' => $consumable->id,
'item_type' => Consumable::class,
'user_id' => $actor->id,
'note' => 'oh hi there',
])->count(),
'Log entry either does not exist or there are more than expected'
);
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace Tests\Feature\Checkouts;
use App\Models\Asset;
use App\Models\License;
use App\Models\LicenseSeat;
use App\Models\User;
use Tests\TestCase;
class LicenseCheckoutTest extends TestCase
{
public function testNotesAreStoredInActionLogOnCheckoutToAsset()
{
$admin = User::factory()->superuser()->create();
$asset = Asset::factory()->create();
$licenseSeat = LicenseSeat::factory()->create();
$this->actingAs($admin)
->post("/licenses/{$licenseSeat->license->id}/checkout", [
'checkout_to_type' => 'asset',
'assigned_to' => null,
'asset_id' => $asset->id,
'notes' => 'oh hi there',
]);
$this->assertDatabaseHas('action_logs', [
'action_type' => 'checkout',
'target_id' => $asset->id,
'target_type' => Asset::class,
'item_id' => $licenseSeat->license->id,
'item_type' => License::class,
'note' => 'oh hi there',
]);
}
public function testNotesAreStoredInActionLogOnCheckoutToUser()
{
$admin = User::factory()->superuser()->create();
$licenseSeat = LicenseSeat::factory()->create();
$this->actingAs($admin)
->post("/licenses/{$licenseSeat->license->id}/checkout", [
'checkout_to_type' => 'user',
'assigned_to' => $admin->id,
'asset_id' => null,
'notes' => 'oh hi there',
]);
$this->assertDatabaseHas('action_logs', [
'action_type' => 'checkout',
'target_id' => $admin->id,
'target_type' => User::class,
'item_id' => $licenseSeat->license->id,
'item_type' => License::class,
'note' => 'oh hi there',
]);
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace Tests\Feature;
use App\Models\User;
use Tests\TestCase;
class DashboardTest extends TestCase
{
public function testUsersWithoutAdminAccessAreRedirected()
{
$this->actingAs(User::factory()->create())
->get(route('home'))
->assertRedirect(route('view-assets'));
}
}

View File

@ -0,0 +1,105 @@
<?php
namespace Tests\Feature\Livewire;
use App\Http\Livewire\CategoryEditForm;
use Livewire\Livewire;
use Tests\TestCase;
class CategoryEditFormTest extends TestCase
{
public function testTheComponentCanRender()
{
Livewire::test(CategoryEditForm::class)->assertStatus(200);
}
public function testSendEmailCheckboxIsCheckedOnLoadWhenSendEmailIsExistingSetting()
{
Livewire::test(CategoryEditForm::class, [
'sendCheckInEmail' => true,
'eulaText' => '',
'useDefaultEula' => false,
])->assertSet('sendCheckInEmail', true);
}
public function testSendEmailCheckboxIsCheckedOnLoadWhenCategoryEulaSet()
{
Livewire::test(CategoryEditForm::class, [
'sendCheckInEmail' => false,
'eulaText' => 'Some Content',
'useDefaultEula' => false,
])->assertSet('sendCheckInEmail', true);
}
public function testSendEmailCheckboxIsCheckedOnLoadWhenUsingDefaultEula()
{
Livewire::test(CategoryEditForm::class, [
'sendCheckInEmail' => false,
'eulaText' => '',
'useDefaultEula' => true,
])->assertSet('sendCheckInEmail', true);
}
public function testSendEmailCheckBoxIsUncheckedOnLoadWhenSendEmailIsFalseNoCategoryEulaSetAndNotUsingDefaultEula()
{
Livewire::test(CategoryEditForm::class, [
'sendCheckInEmail' => false,
'eulaText' => '',
'useDefaultEula' => false,
])->assertSet('sendCheckInEmail', false);
}
public function testSendEmailCheckboxIsCheckedWhenCategoryEulaEntered()
{
Livewire::test(CategoryEditForm::class, [
'sendCheckInEmail' => false,
'useDefaultEula' => false,
])->assertSet('sendCheckInEmail', false)
->set('eulaText', 'Some Content')
->assertSet('sendCheckInEmail', true);
}
public function testSendEmailCheckboxCheckedAndDisabledAndEulaTextDisabledWhenUseDefaultEulaSelected()
{
Livewire::test(CategoryEditForm::class, [
'sendCheckInEmail' => false,
'useDefaultEula' => false,
])->assertSet('sendCheckInEmail', false)
->set('useDefaultEula', true)
->assertSet('sendCheckInEmail', true)
->assertSet('eulaTextDisabled', true)
->assertSet('sendCheckInEmailDisabled', true);
}
public function testSendEmailCheckboxEnabledAndSetToOriginalValueWhenNoCategoryEulaAndNotUsingGlobalEula()
{
Livewire::test(CategoryEditForm::class, [
'eulaText' => 'Some Content',
'sendCheckInEmail' => false,
'useDefaultEula' => true,
])
->set('useDefaultEula', false)
->set('eulaText', '')
->assertSet('sendCheckInEmail', false)
->assertSet('sendCheckInEmailDisabled', false);
Livewire::test(CategoryEditForm::class, [
'eulaText' => 'Some Content',
'sendCheckInEmail' => true,
'useDefaultEula' => true,
])
->set('useDefaultEula', false)
->set('eulaText', '')
->assertSet('sendCheckInEmail', true)
->assertSet('sendCheckInEmailDisabled', false);
}
public function testEulaFieldEnabledOnLoadWhenNotUsingDefaultEula()
{
Livewire::test(CategoryEditForm::class, [
'sendCheckInEmail' => false,
'eulaText' => '',
'useDefaultEula' => false,
])->assertSet('eulaTextDisabled', false);
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace Tests\Feature\Notifications\Email;
use App\Events\CheckoutableCheckedIn;
use App\Models\Asset;
use App\Models\User;
use App\Notifications\CheckinAssetNotification;
use Illuminate\Support\Facades\Notification;
use Tests\TestCase;
/**
* @group notifications
*/
class EmailNotificationsUponCheckinTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
Notification::fake();
}
public function testCheckInEmailSentToUserIfSettingEnabled()
{
$user = User::factory()->create();
$asset = Asset::factory()->assignedToUser($user)->create();
$asset->model->category->update(['checkin_email' => true]);
$this->fireCheckInEvent($asset, $user);
Notification::assertSentTo(
$user,
function (CheckinAssetNotification $notification, $channels) {
return in_array('mail', $channels);
},
);
}
public function testCheckInEmailNotSentToUserIfSettingDisabled()
{
$user = User::factory()->create();
$asset = Asset::factory()->assignedToUser($user)->create();
$asset->model->category->update(['checkin_email' => false]);
$this->fireCheckInEvent($asset, $user);
Notification::assertNotSentTo(
$user,
function (CheckinAssetNotification $notification, $channels) {
return in_array('mail', $channels);
}
);
}
private function fireCheckInEvent($asset, $user): void
{
event(new CheckoutableCheckedIn(
$asset,
$user,
User::factory()->checkinAssets()->create(),
''
));
}
}

View File

@ -0,0 +1,145 @@
<?php
namespace Tests\Feature\Notifications\Webhooks;
use App\Events\CheckoutableCheckedIn;
use App\Models\Accessory;
use App\Models\Asset;
use App\Models\Component;
use App\Models\LicenseSeat;
use App\Models\Location;
use App\Models\User;
use App\Notifications\CheckinAccessoryNotification;
use App\Notifications\CheckinAssetNotification;
use App\Notifications\CheckinLicenseSeatNotification;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Notification;
use Tests\TestCase;
/**
* @group notifications
*/
class SlackNotificationsUponCheckinTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
Notification::fake();
}
public function assetCheckInTargets(): array
{
return [
'Asset checked out to user' => [fn() => User::factory()->create()],
'Asset checked out to asset' => [fn() => Asset::factory()->laptopMbp()->create()],
'Asset checked out to location' => [fn() => Location::factory()->create()],
];
}
public function licenseCheckInTargets(): array
{
return [
'License checked out to user' => [fn() => User::factory()->create()],
'License checked out to asset' => [fn() => Asset::factory()->laptopMbp()->create()],
];
}
public function testAccessoryCheckinSendsSlackNotificationWhenSettingEnabled()
{
$this->settings->enableSlackWebhook();
$this->fireCheckInEvent(
Accessory::factory()->create(),
User::factory()->create(),
);
$this->assertSlackNotificationSent(CheckinAccessoryNotification::class);
}
public function testAccessoryCheckinDoesNotSendSlackNotificationWhenSettingDisabled()
{
$this->settings->disableSlackWebhook();
$this->fireCheckInEvent(
Accessory::factory()->create(),
User::factory()->create(),
);
$this->assertNoSlackNotificationSent(CheckinAccessoryNotification::class);
}
/** @dataProvider assetCheckInTargets */
public function testAssetCheckinSendsSlackNotificationWhenSettingEnabled($checkoutTarget)
{
$this->settings->enableSlackWebhook();
$this->fireCheckInEvent(
Asset::factory()->create(),
$checkoutTarget(),
);
$this->assertSlackNotificationSent(CheckinAssetNotification::class);
}
/** @dataProvider assetCheckInTargets */
public function testAssetCheckinDoesNotSendSlackNotificationWhenSettingDisabled($checkoutTarget)
{
$this->settings->disableSlackWebhook();
$this->fireCheckInEvent(
Asset::factory()->create(),
$checkoutTarget(),
);
$this->assertNoSlackNotificationSent(CheckinAssetNotification::class);
}
public function testComponentCheckinDoesNotSendSlackNotification()
{
$this->settings->enableSlackWebhook();
$this->fireCheckInEvent(
Component::factory()->create(),
Asset::factory()->laptopMbp()->create(),
);
Notification::assertNothingSent();
}
/** @dataProvider licenseCheckInTargets */
public function testLicenseCheckinSendsSlackNotificationWhenSettingEnabled($checkoutTarget)
{
$this->settings->enableSlackWebhook();
$this->fireCheckInEvent(
LicenseSeat::factory()->create(),
$checkoutTarget(),
);
$this->assertSlackNotificationSent(CheckinLicenseSeatNotification::class);
}
/** @dataProvider licenseCheckInTargets */
public function testLicenseCheckinDoesNotSendSlackNotificationWhenSettingDisabled($checkoutTarget)
{
$this->settings->disableSlackWebhook();
$this->fireCheckInEvent(
LicenseSeat::factory()->create(),
$checkoutTarget(),
);
$this->assertNoSlackNotificationSent(CheckinLicenseSeatNotification::class);
}
private function fireCheckInEvent(Model $checkoutable, Model $target)
{
event(new CheckoutableCheckedIn(
$checkoutable,
$target,
User::factory()->superuser()->create(),
''
));
}
}

View File

@ -0,0 +1,171 @@
<?php
namespace Tests\Feature\Notifications\Webhooks;
use App\Events\CheckoutableCheckedOut;
use App\Models\Accessory;
use App\Models\Asset;
use App\Models\Component;
use App\Models\Consumable;
use App\Models\LicenseSeat;
use App\Models\Location;
use App\Models\User;
use App\Notifications\CheckoutAccessoryNotification;
use App\Notifications\CheckoutAssetNotification;
use App\Notifications\CheckoutConsumableNotification;
use App\Notifications\CheckoutLicenseSeatNotification;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Notification;
use Tests\TestCase;
/**
* @group notifications
*/
class SlackNotificationsUponCheckoutTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
Notification::fake();
}
public function assetCheckoutTargets(): array
{
return [
'Asset checked out to user' => [fn() => User::factory()->create()],
'Asset checked out to asset' => [fn() => Asset::factory()->laptopMbp()->create()],
'Asset checked out to location' => [fn() => Location::factory()->create()],
];
}
public function licenseCheckoutTargets(): array
{
return [
'License checked out to user' => [fn() => User::factory()->create()],
'License checked out to asset' => [fn() => Asset::factory()->laptopMbp()->create()],
];
}
public function testAccessoryCheckoutSendsSlackNotificationWhenSettingEnabled()
{
$this->settings->enableSlackWebhook();
$this->fireCheckOutEvent(
Accessory::factory()->create(),
User::factory()->create(),
);
$this->assertSlackNotificationSent(CheckoutAccessoryNotification::class);
}
public function testAccessoryCheckoutDoesNotSendSlackNotificationWhenSettingDisabled()
{
$this->settings->disableSlackWebhook();
$this->fireCheckOutEvent(
Accessory::factory()->create(),
User::factory()->create(),
);
$this->assertNoSlackNotificationSent(CheckoutAccessoryNotification::class);
}
/** @dataProvider assetCheckoutTargets */
public function testAssetCheckoutSendsSlackNotificationWhenSettingEnabled($checkoutTarget)
{
$this->settings->enableSlackWebhook();
$this->fireCheckOutEvent(
Asset::factory()->create(),
$checkoutTarget(),
);
$this->assertSlackNotificationSent(CheckoutAssetNotification::class);
}
/** @dataProvider assetCheckoutTargets */
public function testAssetCheckoutDoesNotSendSlackNotificationWhenSettingDisabled($checkoutTarget)
{
$this->settings->disableSlackWebhook();
$this->fireCheckOutEvent(
Asset::factory()->create(),
$checkoutTarget(),
);
$this->assertNoSlackNotificationSent(CheckoutAssetNotification::class);
}
public function testComponentCheckoutDoesNotSendSlackNotification()
{
$this->settings->enableSlackWebhook();
$this->fireCheckOutEvent(
Component::factory()->create(),
Asset::factory()->laptopMbp()->create(),
);
Notification::assertNothingSent();
}
public function testConsumableCheckoutSendsSlackNotificationWhenSettingEnabled()
{
$this->settings->enableSlackWebhook();
$this->fireCheckOutEvent(
Consumable::factory()->create(),
User::factory()->create(),
);
$this->assertSlackNotificationSent(CheckoutConsumableNotification::class);
}
public function testConsumableCheckoutDoesNotSendSlackNotificationWhenSettingDisabled()
{
$this->settings->disableSlackWebhook();
$this->fireCheckOutEvent(
Consumable::factory()->create(),
User::factory()->create(),
);
$this->assertNoSlackNotificationSent(CheckoutConsumableNotification::class);
}
/** @dataProvider licenseCheckoutTargets */
public function testLicenseCheckoutSendsSlackNotificationWhenSettingEnabled($checkoutTarget)
{
$this->settings->enableSlackWebhook();
$this->fireCheckOutEvent(
LicenseSeat::factory()->create(),
$checkoutTarget(),
);
$this->assertSlackNotificationSent(CheckoutLicenseSeatNotification::class);
}
/** @dataProvider licenseCheckoutTargets */
public function testLicenseCheckoutDoesNotSendSlackNotificationWhenSettingDisabled($checkoutTarget)
{
$this->settings->disableSlackWebhook();
$this->fireCheckOutEvent(
LicenseSeat::factory()->create(),
$checkoutTarget(),
);
$this->assertNoSlackNotificationSent(CheckoutLicenseSeatNotification::class);
}
private function fireCheckOutEvent(Model $checkoutable, Model $target)
{
event(new CheckoutableCheckedOut(
$checkoutable,
$target,
User::factory()->superuser()->create(),
'',
));
}
}

View File

@ -0,0 +1,132 @@
<?php
namespace Tests\Feature\Reports;
use App\Models\Asset;
use App\Models\Company;
use App\Models\User;
use Illuminate\Testing\TestResponse;
use League\Csv\Reader;
use PHPUnit\Framework\Assert;
use Tests\TestCase;
class CustomReportTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
TestResponse::macro(
'assertSeeTextInStreamedResponse',
function (string $needle) {
Assert::assertTrue(
collect(Reader::createFromString($this->streamedContent())->getRecords())
->pluck(0)
->contains($needle)
);
return $this;
}
);
TestResponse::macro(
'assertDontSeeTextInStreamedResponse',
function (string $needle) {
Assert::assertFalse(
collect(Reader::createFromString($this->streamedContent())->getRecords())
->pluck(0)
->contains($needle)
);
return $this;
}
);
}
public function testCustomAssetReport()
{
Asset::factory()->create(['name' => 'Asset A']);
Asset::factory()->create(['name' => 'Asset B']);
$this->actingAs(User::factory()->canViewReports()->create())
->post('reports/custom', [
'asset_name' => '1',
'asset_tag' => '1',
'serial' => '1',
])->assertOk()
->assertHeader('content-type', 'text/csv; charset=UTF-8')
->assertSeeTextInStreamedResponse('Asset A')
->assertSeeTextInStreamedResponse('Asset B');
}
public function testCustomAssetReportAdheresToCompanyScoping()
{
[$companyA, $companyB] = Company::factory()->count(2)->create();
Asset::factory()->for($companyA)->create(['name' => 'Asset A']);
Asset::factory()->for($companyB)->create(['name' => 'Asset B']);
$superUser = $companyA->users()->save(User::factory()->superuser()->make());
$userInCompanyA = $companyA->users()->save(User::factory()->canViewReports()->make());
$userInCompanyB = $companyB->users()->save(User::factory()->canViewReports()->make());
$this->settings->disableMultipleFullCompanySupport();
$this->actingAs($superUser)
->post('reports/custom', ['asset_name' => '1', 'asset_tag' => '1', 'serial' => '1'])
->assertSeeTextInStreamedResponse('Asset A')
->assertSeeTextInStreamedResponse('Asset B');
$this->actingAs($userInCompanyA)
->post('reports/custom', ['asset_name' => '1', 'asset_tag' => '1', 'serial' => '1'])
->assertSeeTextInStreamedResponse('Asset A')
->assertSeeTextInStreamedResponse('Asset B');
$this->actingAs($userInCompanyB)
->post('reports/custom', ['asset_name' => '1', 'asset_tag' => '1', 'serial' => '1'])
->assertSeeTextInStreamedResponse('Asset A')
->assertSeeTextInStreamedResponse('Asset B');
$this->settings->enableMultipleFullCompanySupport();
$this->actingAs($superUser)
->post('reports/custom', ['asset_name' => '1', 'asset_tag' => '1', 'serial' => '1'])
->assertSeeTextInStreamedResponse('Asset A')
->assertSeeTextInStreamedResponse('Asset B');
$this->actingAs($userInCompanyA)
->post('reports/custom', ['asset_name' => '1', 'asset_tag' => '1', 'serial' => '1'])
->assertSeeTextInStreamedResponse('Asset A')
->assertDontSeeTextInStreamedResponse('Asset B');
$this->actingAs($userInCompanyB)
->post('reports/custom', ['asset_name' => '1', 'asset_tag' => '1', 'serial' => '1'])
->assertDontSeeTextInStreamedResponse('Asset A')
->assertSeeTextInStreamedResponse('Asset B');
}
public function testCanLimitAssetsByLastCheckIn()
{
Asset::factory()->create(['name' => 'Asset A', 'last_checkin' => '2023-08-01']);
Asset::factory()->create(['name' => 'Asset B', 'last_checkin' => '2023-08-02']);
Asset::factory()->create(['name' => 'Asset C', 'last_checkin' => '2023-08-03']);
Asset::factory()->create(['name' => 'Asset D', 'last_checkin' => '2023-08-04']);
Asset::factory()->create(['name' => 'Asset E', 'last_checkin' => '2023-08-05']);
$this->actingAs(User::factory()->canViewReports()->create())
->post('reports/custom', [
'asset_name' => '1',
'asset_tag' => '1',
'serial' => '1',
'checkin_date' => '1',
'checkin_date_start' => '2023-08-02',
'checkin_date_end' => '2023-08-04',
])->assertOk()
->assertHeader('content-type', 'text/csv; charset=UTF-8')
->assertDontSeeTextInStreamedResponse('Asset A')
->assertSeeTextInStreamedResponse('Asset B')
->assertSeeTextInStreamedResponse('Asset C')
->assertSeeTextInStreamedResponse('Asset D')
->assertDontSeeTextInStreamedResponse('Asset E');
}
}

View File

@ -0,0 +1,82 @@
<?php
namespace Tests\Feature\Users;
use App\Models\User;
use Tests\TestCase;
class UpdateUserTest extends TestCase
{
public function testUsersCanBeActivatedWithNumber()
{
$admin = User::factory()->superuser()->create();
$user = User::factory()->create(['activated' => 0]);
$this->actingAs($admin)
->put(route('users.update', $user), [
'first_name' => $user->first_name,
'username' => $user->username,
'activated' => 1,
]);
$this->assertEquals(1, $user->refresh()->activated);
}
public function testUsersCanBeActivatedWithBooleanTrue()
{
$admin = User::factory()->superuser()->create();
$user = User::factory()->create(['activated' => false]);
$this->actingAs($admin)
->put(route('users.update', $user), [
'first_name' => $user->first_name,
'username' => $user->username,
'activated' => true,
]);
$this->assertEquals(1, $user->refresh()->activated);
}
public function testUsersCanBeDeactivatedWithNumber()
{
$admin = User::factory()->superuser()->create();
$user = User::factory()->create(['activated' => true]);
$this->actingAs($admin)
->put(route('users.update', $user), [
'first_name' => $user->first_name,
'username' => $user->username,
'activated' => 0,
]);
$this->assertEquals(0, $user->refresh()->activated);
}
public function testUsersCanBeDeactivatedWithBooleanFalse()
{
$admin = User::factory()->superuser()->create();
$user = User::factory()->create(['activated' => true]);
$this->actingAs($admin)
->put(route('users.update', $user), [
'first_name' => $user->first_name,
'username' => $user->username,
'activated' => false,
]);
$this->assertEquals(0, $user->refresh()->activated);
}
public function testUsersUpdatingThemselvesDoNotDeactivateTheirAccount()
{
$admin = User::factory()->superuser()->create(['activated' => true]);
$this->actingAs($admin)
->put(route('users.update', $admin), [
'first_name' => $admin->first_name,
'username' => $admin->username,
]);
$this->assertEquals(1, $admin->refresh()->activated);
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace Tests\Support;
use App\Models\Setting;
use Illuminate\Notifications\AnonymousNotifiable;
use Illuminate\Support\Facades\Notification;
trait AssertsAgainstSlackNotifications
{
public function assertSlackNotificationSent(string $notificationClass)
{
Notification::assertSentTo(
new AnonymousNotifiable,
$notificationClass,
function ($notification, $channels, $notifiable) {
return $notifiable->routes['slack'] === Setting::getSettings()->webhook_endpoint;
}
);
}
public function assertNoSlackNotificationSent(string $notificationClass)
{
Notification::assertNotSentTo(new AnonymousNotifiable, $notificationClass);
}
}

View File

@ -0,0 +1,91 @@
<?php
namespace Tests\Support;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Testing\TestResponse;
use PHPUnit\Framework\Assert;
use RuntimeException;
trait CustomTestMacros
{
protected function registerCustomMacros()
{
$guardAgainstNullProperty = function (Model $model, string $property) {
if (is_null($model->{$property})) {
throw new RuntimeException(
"The property ({$property}) either does not exist or is null on the model which isn't helpful for comparison."
);
}
};
TestResponse::macro(
'assertResponseContainsInRows',
function (Model $model, string $property = 'name') use ($guardAgainstNullProperty) {
$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;
}
);
TestResponse::macro(
'assertResponseDoesNotContainInRows',
function (Model $model, string $property = 'name') use ($guardAgainstNullProperty) {
$guardAgainstNullProperty($model, $property);
Assert::assertFalse(
collect($this['rows'])->pluck($property)->contains(e($model->{$property})),
"Response contained unexpected value: {$model->{$property}}"
);
return $this;
}
);
TestResponse::macro(
'assertResponseContainsInResults',
function (Model $model, string $property = 'id') use ($guardAgainstNullProperty) {
$guardAgainstNullProperty($model, $property);
Assert::assertTrue(
collect($this->json('results'))->pluck('id')->contains(e($model->{$property})),
"Response did not contain the expected value: {$model->{$property}}"
);
return $this;
}
);
TestResponse::macro(
'assertResponseDoesNotContainInResults',
function (Model $model, string $property = 'id') use ($guardAgainstNullProperty) {
$guardAgainstNullProperty($model, $property);
Assert::assertFalse(
collect($this->json('results'))->pluck('id')->contains(e($model->{$property})),
"Response contained unexpected value: {$model->{$property}}"
);
return $this;
}
);
TestResponse::macro(
'assertStatusMessageIs',
function (string $message) {
Assert::assertEquals(
$message,
$this['status'],
"Response status message was not {$message}"
);
return $this;
}
);
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Tests\Support;
use App\Models\Setting;
trait InitializesSettings
{
protected Settings $settings;
public function initializeSettings()
{
$this->settings = Settings::initialize();
$this->beforeApplicationDestroyed(fn() => Setting::$_cache = null);
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace Tests\Support;
use Illuminate\Contracts\Auth\Authenticatable;
use Laravel\Passport\Passport;
trait InteractsWithAuthentication
{
protected function actingAsForApi(Authenticatable $user)
{
Passport::actingAs($user);
return $this;
}
}

View File

@ -0,0 +1,140 @@
<?php
namespace Tests\Support;
use App\Models\Setting;
use Illuminate\Support\Facades\Crypt;
class Settings
{
private Setting $setting;
private function __construct()
{
$this->setting = Setting::factory()->create();
}
public static function initialize(): Settings
{
return new self();
}
public function enableAlertEmail(string $email = 'notifications@afcrichmond.com'): Settings
{
return $this->update(['alert_email' => $email]);
}
public function disableAlertEmail(): Settings
{
return $this->update(['alert_email' => null]);
}
public function enableMultipleFullCompanySupport(): Settings
{
return $this->update(['full_multiple_companies_support' => 1]);
}
public function disableMultipleFullCompanySupport(): Settings
{
return $this->update(['full_multiple_companies_support' => 0]);
}
public function enableSlackWebhook(): Settings
{
return $this->update([
'webhook_selected' => 'slack',
'webhook_botname' => 'SnipeBot5000',
'webhook_endpoint' => 'https://hooks.slack.com/services/NZ59/Q446/672N',
'webhook_channel' => '#it',
]);
}
public function disableSlackWebhook(): Settings
{
return $this->update([
'webhook_selected' => '',
'webhook_botname' => '',
'webhook_endpoint' => '',
'webhook_channel' => '',
]);
}
public function enableAutoIncrement(): Settings
{
return $this->update([
'auto_increment_assets' => 1,
'auto_increment_prefix' => 'ABCD',
'next_auto_tag_base' => 123,
'zerofill_count' => 5
]);
}
public function disableAutoIncrement(): Settings
{
return $this->update([
'auto_increment_assets' => 0,
'auto_increment_prefix' => 0,
'next_auto_tag_base' => 0,
'zerofill_count' => 0
]);
}
public function enableUniqueSerialNumbers(): Settings
{
return $this->update(['unique_serial' => 1]);
}
public function disableUniqueSerialNumbers(): Settings
{
return $this->update(['unique_serial' => 0]);
}
public function enableLdap(): Settings
{
return $this->update([
'ldap_enabled' => 1,
'ldap_server' => 'ldaps://ldap.example.com',
'ldap_uname' => 'fake_username',
'ldap_pword' => Crypt::encrypt("fake_password"),
'ldap_basedn' => 'CN=Users,DC=ad,DC=example,Dc=com'
]);
}
public function enableAnonymousLdap(): Settings
{
return $this->update([
'ldap_enabled' => 1,
'ldap_server' => 'ldaps://ldap.example.com',
// 'ldap_uname' => 'fake_username',
'ldap_pword' => Crypt::encrypt("fake_password"),
'ldap_basedn' => 'CN=Users,DC=ad,DC=example,Dc=com'
]);
}
public function enableBadPasswordLdap(): Settings
{
return $this->update([
'ldap_enabled' => 1,
'ldap_server' => 'ldaps://ldap.example.com',
'ldap_uname' => 'fake_username',
'ldap_pword' => "badly_encrypted_password!",
'ldap_basedn' => 'CN=Users,DC=ad,DC=example,Dc=com'
]);
}
/**
* @param array $attributes Attributes to modify in the application's settings.
*/
public function set(array $attributes): Settings
{
return $this->update($attributes);
}
private function update(array $attributes): Settings
{
Setting::unguarded(fn() => $this->setting->update($attributes));
Setting::$_cache = null;
return $this;
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace Tests;
use App\Http\Middleware\SecurityHeaders;
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use RuntimeException;
use Tests\Support\AssertsAgainstSlackNotifications;
use Tests\Support\CustomTestMacros;
use Tests\Support\InteractsWithAuthentication;
use Tests\Support\InitializesSettings;
abstract class TestCase extends BaseTestCase
{
use AssertsAgainstSlackNotifications;
use CreatesApplication;
use CustomTestMacros;
use InteractsWithAuthentication;
use InitializesSettings;
use LazilyRefreshDatabase;
private array $globallyDisabledMiddleware = [
SecurityHeaders::class,
];
protected function setUp(): void
{
$this->guardAgainstMissingEnv();
parent::setUp();
$this->registerCustomMacros();
$this->withoutMiddleware($this->globallyDisabledMiddleware);
$this->initializeSettings();
}
private function guardAgainstMissingEnv(): void
{
if (!file_exists(realpath(__DIR__ . '/../') . '/.env.testing')) {
throw new RuntimeException(
'.env.testing file does not exist. Aborting to avoid wiping your local database.'
);
}
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace Tests\Unit;
use App\Models\Accessory;
use App\Models\Manufacturer;
use App\Models\Location;
use App\Models\Category;
use App\Models\Company;
use Tests\TestCase;
class AccessoryTest extends TestCase
{
public function testAnAccessoryBelongsToACompany()
{
$accessory = Accessory::factory()
->create(
[
'company_id' =>
Company::factory()->create()->id]);
$this->assertInstanceOf(Company::class, $accessory->company);
}
public function testAnAccessoryHasALocation()
{
$accessory = Accessory::factory()
->create(
[
'location_id' => Location::factory()->create()->id
]);
$this->assertInstanceOf(Location::class, $accessory->location);
}
public function testAnAccessoryBelongsToACategory()
{
$accessory = Accessory::factory()->appleBtKeyboard()
->create(
[
'category_id' =>
Category::factory()->create(
[
'category_type' => 'accessory'
]
)->id]);
$this->assertInstanceOf(Category::class, $accessory->category);
$this->assertEquals('accessory', $accessory->category->category_type);
}
public function testAnAccessoryHasAManufacturer()
{
$accessory = Accessory::factory()->appleBtKeyboard()->create(
[
'category_id' => Category::factory()->create(),
'manufacturer_id' => Manufacturer::factory()->apple()->create()
]);
$this->assertInstanceOf(Manufacturer::class, $accessory->manufacturer);
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace Tests\Unit;
use App\Models\AssetMaintenance;
use Tests\TestCase;
class AssetMaintenanceTest extends TestCase
{
public function testZerosOutWarrantyIfBlank()
{
$c = new AssetMaintenance;
$c->is_warranty = '';
$this->assertTrue($c->is_warranty === 0);
$c->is_warranty = '4';
$this->assertTrue($c->is_warranty == 4);
}
public function testSetsCostsAppropriately()
{
$c = new AssetMaintenance();
$c->cost = '0.00';
$this->assertTrue($c->cost === null);
$c->cost = '9.54';
$this->assertTrue($c->cost === 9.54);
$c->cost = '9.50';
$this->assertTrue($c->cost === 9.5);
}
public function testNullsOutNotesIfBlank()
{
$c = new AssetMaintenance;
$c->notes = '';
$this->assertTrue($c->notes === null);
$c->notes = 'This is a long note';
$this->assertTrue($c->notes === 'This is a long note');
}
public function testNullsOutCompletionDateIfBlankOrInvalid()
{
$c = new AssetMaintenance;
$c->completion_date = '';
$this->assertTrue($c->completion_date === null);
$c->completion_date = '0000-00-00';
$this->assertTrue($c->completion_date === null);
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace Tests\Unit;
use App\Models\Asset;
use App\Models\Category;
use App\Models\AssetModel;
use Tests\TestCase;
class AssetModelTest extends TestCase
{
public function testAnAssetModelContainsAssets()
{
$category = Category::factory()->create([
'category_type' => 'asset'
]);
$model = AssetModel::factory()->create([
'category_id' => $category->id,
]);
$asset = Asset::factory()->create([
'model_id' => $model->id
]);
$this->assertEquals(1, $model->assets()->count());
}
}

View File

@ -0,0 +1,159 @@
<?php
namespace Tests\Unit;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Category;
use Carbon\Carbon;
use Tests\TestCase;
class AssetTest extends TestCase
{
public function testAutoIncrement()
{
$this->settings->enableAutoIncrement();
$a = Asset::factory()->create(['asset_tag' => Asset::autoincrement_asset() ]);
$b = Asset::factory()->create(['asset_tag' => Asset::autoincrement_asset() ]);
$this->assertModelExists($a);
$this->assertModelExists($b);
}
public function testAutoIncrementCollision()
{
$this->settings->enableAutoIncrement();
// we have to do this by hand to 'simulate' two web pages being open at the same time
$a = Asset::factory()->make(['asset_tag' => Asset::autoincrement_asset() ]);
$b = Asset::factory()->make(['asset_tag' => Asset::autoincrement_asset() ]);
$this->assertTrue($a->save());
$this->assertFalse($b->save());
}
public function testAutoIncrementDouble()
{
// make one asset with the autoincrement *ONE* higher than the next auto-increment
// make sure you can then still make another
$this->settings->enableAutoIncrement();
$gap_number = Asset::autoincrement_asset(1);
$final_number = Asset::autoincrement_asset(2);
$a = Asset::factory()->make(['asset_tag' => $gap_number]); //make an asset with an ID that is one *over* the next increment
$b = Asset::factory()->make(['asset_tag' => Asset::autoincrement_asset()]); //but also make one with one that is *at* the next increment
$this->assertTrue($a->save());
$this->assertTrue($b->save());
//and ensure a final asset ends up at *two* over what would've been the next increment at the start
$c = Asset::factory()->make(['asset_tag' => Asset::autoincrement_asset()]);
$this->assertTrue($c->save());
$this->assertEquals($c->asset_tag, $final_number);
}
public function testAutoIncrementGapAndBackfill()
{
// make one asset 3 higher than the next auto-increment
// manually make one that's 1 lower than that
// make sure the next one is one higher than the 3 higher one.
$this->settings->enableAutoIncrement();
$big_gap = Asset::autoincrement_asset(3);
$final_result = Asset::autoincrement_asset(4);
$backfill_one = Asset::autoincrement_asset(0);
$backfill_two = Asset::autoincrement_asset(1);
$backfill_three = Asset::autoincrement_asset(2);
$a = Asset::factory()->create(['asset_tag' => $big_gap]);
$this->assertModelExists($a);
$b = Asset::factory()->create(['asset_tag' => $backfill_one]);
$this->assertModelExists($b);
$c = Asset::factory()->create(['asset_tag' => $backfill_two]);
$this->assertModelExists($c);
$d = Asset::factory()->create(['asset_tag' => $backfill_three]);
$this->assertModelExists($d);
$final = Asset::factory()->create(['asset_tag' => Asset::autoincrement_asset()]);
$this->assertModelExists($final);
$this->assertEquals($final->asset_tag, $final_result);
}
public function testPrefixlessAutoincrementBackfill()
{
// TODO: COPYPASTA FROM above, is there a way to still run this test but not have it be so duplicative?
$this->settings->enableAutoIncrement()->set(['auto_increment_prefix' => '']);
$big_gap = Asset::autoincrement_asset(3);
$final_result = Asset::autoincrement_asset(4);
$backfill_one = Asset::autoincrement_asset(0);
$backfill_two = Asset::autoincrement_asset(1);
$backfill_three = Asset::autoincrement_asset(2);
$a = Asset::factory()->create(['asset_tag' => $big_gap]);
$this->assertModelExists($a);
$b = Asset::factory()->create(['asset_tag' => $backfill_one]);
$this->assertModelExists($b);
$c = Asset::factory()->create(['asset_tag' => $backfill_two]);
$this->assertModelExists($c);
$d = Asset::factory()->create(['asset_tag' => $backfill_three]);
$this->assertModelExists($d);
$final = Asset::factory()->create(['asset_tag' => Asset::autoincrement_asset()]);
$this->assertModelExists($final);
$this->assertEquals($final->asset_tag, $final_result);
}
public function testUnzerofilledPrefixlessAutoincrementBackfill()
{
// TODO: COPYPASTA FROM above (AGAIN), is there a way to still run this test but not have it be so duplicative?
$this->settings->enableAutoIncrement()->set(['auto_increment_prefix' => '','zerofill_count' => 0]);
$big_gap = Asset::autoincrement_asset(3);
$final_result = Asset::autoincrement_asset(4);
$backfill_one = Asset::autoincrement_asset(0);
$backfill_two = Asset::autoincrement_asset(1);
$backfill_three = Asset::autoincrement_asset(2);
$a = Asset::factory()->create(['asset_tag' => $big_gap]);
$this->assertModelExists($a);
$b = Asset::factory()->create(['asset_tag' => $backfill_one]);
$this->assertModelExists($b);
$c = Asset::factory()->create(['asset_tag' => $backfill_two]);
$this->assertModelExists($c);
$d = Asset::factory()->create(['asset_tag' => $backfill_three]);
$this->assertModelExists($d);
$final = Asset::factory()->create(['asset_tag' => Asset::autoincrement_asset()]);
$this->assertModelExists($final);
$this->assertEquals($final->asset_tag, $final_result);
}
public function testWarrantyExpiresAttribute()
{
$asset = Asset::factory()
->create(
[
'model_id' => AssetModel::factory()
->create(
[
'category_id' => Category::factory()->assetLaptopCategory()->create()->id
]
)->id,
'warranty_months' => 24,
'purchase_date' => Carbon::createFromDate(2017, 1, 1)->hour(0)->minute(0)->second(0)
]);
$this->assertEquals(Carbon::createFromDate(2017, 1, 1)->format('Y-m-d'), $asset->purchase_date->format('Y-m-d'));
$this->assertEquals(Carbon::createFromDate(2019, 1, 1)->format('Y-m-d'), $asset->warranty_expires->format('Y-m-d'));
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace Tests\Unit;
use App\Models\Category;
use App\Models\AssetModel;
use App\Models\Asset;
use Tests\TestCase;
class CategoryTest extends TestCase
{
public function testFailsEmptyValidation()
{
// An Asset requires a name, a qty, and a category_id.
$a = Category::create();
$this->assertFalse($a->isValid());
$fields = [
'name' => 'name',
'category_type' => 'category type',
];
$errors = $a->getErrors();
foreach ($fields as $field => $fieldTitle) {
$this->assertEquals($errors->get($field)[0], "The ${fieldTitle} field is required.");
}
}
public function testACategoryCanHaveAssets()
{
$category = Category::factory()->assetDesktopCategory()->create();
// Generate 5 models via factory
$models = AssetModel::factory()
->mbp13Model()
->count(5)
->create(
[
'category_id' => $category->id
]
);
// Loop through the models and create 2 assets in each model
$models->each(function ($model) {
//dd($model);
$asset = Asset::factory()
->count(2)
->create(
[
'model_id' => $model->id,
]
);
//dd($asset);
});
$this->assertCount(5, $category->models);
$this->assertCount(5, $category->models);
$this->assertEquals(10, $category->itemCount());
}
}

View File

@ -0,0 +1,166 @@
<?php
namespace Tests\Unit;
use App\Models\Accessory;
use App\Models\Asset;
use App\Models\AssetMaintenance;
use App\Models\Company;
use App\Models\Component;
use App\Models\Consumable;
use App\Models\License;
use App\Models\LicenseSeat;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
use Tests\TestCase;
class CompanyScopingTest extends TestCase
{
public function models(): array
{
return [
'Accessories' => [Accessory::class],
'Assets' => [Asset::class],
'Components' => [Component::class],
'Consumables' => [Consumable::class],
'Licenses' => [License::class],
];
}
/** @dataProvider models */
public function testCompanyScoping($model)
{
[$companyA, $companyB] = Company::factory()->count(2)->create();
$modelA = $model::factory()->for($companyA)->create();
$modelB = $model::factory()->for($companyB)->create();
$superUser = $companyA->users()->save(User::factory()->superuser()->make());
$userInCompanyA = $companyA->users()->save(User::factory()->make());
$userInCompanyB = $companyB->users()->save(User::factory()->make());
$this->settings->disableMultipleFullCompanySupport();
$this->actingAs($superUser);
$this->assertCanSee($modelA);
$this->assertCanSee($modelB);
$this->actingAs($userInCompanyA);
$this->assertCanSee($modelA);
$this->assertCanSee($modelB);
$this->actingAs($userInCompanyB);
$this->assertCanSee($modelA);
$this->assertCanSee($modelB);
$this->settings->enableMultipleFullCompanySupport();
$this->actingAs($superUser);
$this->assertCanSee($modelA);
$this->assertCanSee($modelB);
$this->actingAs($userInCompanyA);
$this->assertCanSee($modelA);
$this->assertCannotSee($modelB);
$this->actingAs($userInCompanyB);
$this->assertCannotSee($modelA);
$this->assertCanSee($modelB);
}
public function testAssetMaintenanceCompanyScoping()
{
[$companyA, $companyB] = Company::factory()->count(2)->create();
$assetMaintenanceForCompanyA = AssetMaintenance::factory()->for(Asset::factory()->for($companyA))->create();
$assetMaintenanceForCompanyB = AssetMaintenance::factory()->for(Asset::factory()->for($companyB))->create();
$superUser = $companyA->users()->save(User::factory()->superuser()->make());
$userInCompanyA = $companyA->users()->save(User::factory()->make());
$userInCompanyB = $companyB->users()->save(User::factory()->make());
$this->settings->disableMultipleFullCompanySupport();
$this->actingAs($superUser);
$this->assertCanSee($assetMaintenanceForCompanyA);
$this->assertCanSee($assetMaintenanceForCompanyB);
$this->actingAs($userInCompanyA);
$this->assertCanSee($assetMaintenanceForCompanyA);
$this->assertCanSee($assetMaintenanceForCompanyB);
$this->actingAs($userInCompanyB);
$this->assertCanSee($assetMaintenanceForCompanyA);
$this->assertCanSee($assetMaintenanceForCompanyB);
$this->settings->enableMultipleFullCompanySupport();
$this->actingAs($superUser);
$this->assertCanSee($assetMaintenanceForCompanyA);
$this->assertCanSee($assetMaintenanceForCompanyB);
$this->actingAs($userInCompanyA);
$this->assertCanSee($assetMaintenanceForCompanyA);
$this->assertCannotSee($assetMaintenanceForCompanyB);
$this->actingAs($userInCompanyB);
$this->assertCannotSee($assetMaintenanceForCompanyA);
$this->assertCanSee($assetMaintenanceForCompanyB);
}
public function testLicenseSeatCompanyScoping()
{
[$companyA, $companyB] = Company::factory()->count(2)->create();
$licenseSeatA = LicenseSeat::factory()->for(Asset::factory()->for($companyA))->create();
$licenseSeatB = LicenseSeat::factory()->for(Asset::factory()->for($companyB))->create();
$superUser = $companyA->users()->save(User::factory()->superuser()->make());
$userInCompanyA = $companyA->users()->save(User::factory()->make());
$userInCompanyB = $companyB->users()->save(User::factory()->make());
$this->settings->disableMultipleFullCompanySupport();
$this->actingAs($superUser);
$this->assertCanSee($licenseSeatA);
$this->assertCanSee($licenseSeatB);
$this->actingAs($userInCompanyA);
$this->assertCanSee($licenseSeatA);
$this->assertCanSee($licenseSeatB);
$this->actingAs($userInCompanyB);
$this->assertCanSee($licenseSeatA);
$this->assertCanSee($licenseSeatB);
$this->settings->enableMultipleFullCompanySupport();
$this->actingAs($superUser);
$this->assertCanSee($licenseSeatA);
$this->assertCanSee($licenseSeatB);
$this->actingAs($userInCompanyA);
$this->assertCanSee($licenseSeatA);
$this->assertCannotSee($licenseSeatB);
$this->actingAs($userInCompanyB);
$this->assertCannotSee($licenseSeatA);
$this->assertCanSee($licenseSeatB);
}
private function assertCanSee(Model $model)
{
$this->assertTrue(
get_class($model)::all()->contains($model),
'User was not able to see expected model'
);
}
private function assertCannotSee(Model $model)
{
$this->assertFalse(
get_class($model)::all()->contains($model),
'User was able to see model from a different company'
);
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace Tests\Unit;
use App\Models\Category;
use App\Models\Company;
use App\Models\Component;
use App\Models\Location;
use Tests\TestCase;
class ComponentTest extends TestCase
{
public function testAComponentBelongsToACompany()
{
$component = Component::factory()
->create(
[
'company_id' => Company::factory()->create()->id
]
);
$this->assertInstanceOf(Company::class, $component->company);
}
public function testAComponentHasALocation()
{
$component = Component::factory()
->create(['location_id' => Location::factory()->create()->id]);
$this->assertInstanceOf(Location::class, $component->location);
}
public function testAComponentBelongsToACategory()
{
$component = Component::factory()->ramCrucial4()
->create(
[
'category_id' =>
Category::factory()->create(
[
'category_type' => 'component'
]
)->id]);
$this->assertInstanceOf(Category::class, $component->category);
$this->assertEquals('component', $component->category->category_type);
}
}

View File

@ -0,0 +1,111 @@
<?php
namespace Tests\Unit;
use App\Models\CustomField;
use Tests\TestCase;
/*
* Test strings for db column names gathered from
* http://www.omniglot.com/language/phrases/hovercraft.htm
*/
class CustomFieldTest extends TestCase
{
public function testFormat()
{
$customfield = CustomField::factory()->make(['format' => 'IP']);
$this->assertEquals($customfield->getAttributes()['format'], CustomField::PREDEFINED_FORMATS['IP']); //this seems undocumented...
$this->assertEquals($customfield->format, 'IP');
}
public function testDbNameAscii()
{
$customfield = new CustomField();
$customfield->name = 'My hovercraft is full of eels';
$customfield->id = 1337;
$this->assertEquals($customfield->convertUnicodeDbSlug(), '_snipeit_my_hovercraft_is_full_of_eels_1337');
}
// Western Europe
public function testDbNameLatin()
{
$customfield = new CustomField();
$customfield->name = 'My hovercraft is full of eels';
$customfield->id = 1337;
$this->assertEquals($customfield->convertUnicodeDbSlug(), '_snipeit_my_hovercraft_is_full_of_eels_1337');
}
// Asian
public function testDbNameChinese()
{
$customfield = new CustomField();
$customfield->name = '我的氣墊船裝滿了鱔魚';
$customfield->id = 1337;
if (function_exists('transliterator_transliterate')) {
$this->assertEquals($customfield->convertUnicodeDbSlug(), '_snipeit_wo_de_qi_dian_chuan_zhuang_man_le_shan_yu_1337');
} else {
$this->assertEquals($customfield->convertUnicodeDbSlug(), '_snipeit_aecsae0ase1eaeaeoees_1337');
}
}
public function testDbNameJapanese()
{
$customfield = new CustomField();
$customfield->name = '私のホバークラフトは鰻でいっぱいです';
$customfield->id = 1337;
if (function_exists('transliterator_transliterate')) {
$this->assertEquals($customfield->convertUnicodeDbSlug(), '_snipeit_sinohohakurafutoha_manteihhaitesu_1337');
} else {
$this->assertEquals($customfield->convertUnicodeDbSlug(), '_snipeit_caafafafaafcafafae0aaaaaaa_1337');
}
}
public function testDbNameKorean()
{
$customfield = new CustomField();
$customfield->name = '내 호버크라프트는 장어로 가득 차 있어요';
$customfield->id = 1337;
if (function_exists('transliterator_transliterate')) {
$this->assertEquals($customfield->convertUnicodeDbSlug(), '_snipeit_nae_hobeokeulapeuteuneun_jang_eolo_gadeug_1337');
} else {
$this->assertEquals($customfield->convertUnicodeDbSlug(), '_snipeit_e_ie2ieiises_izieoe_e0e_i0_iziis_1337');
}
}
// Nordic languages
public function testDbNameNonLatinEuro()
{
$customfield = new CustomField();
$customfield->name = 'Mój poduszkowiec jest pełen węgorzy';
$customfield->id = 1337;
if (function_exists('transliterator_transliterate')) {
$this->assertEquals($customfield->convertUnicodeDbSlug(), '_snipeit_moj_poduszkowiec_jest_pelen_wegorzy_1337');
} else {
$this->assertEquals($customfield->convertUnicodeDbSlug(), '_snipeit_ma3j_poduszkowiec_jest_peaen_waegorzy_1337');
}
}
//
public function testDbNameTurkish()
{
$customfield = new CustomField();
$customfield->name = 'Hoverkraftım yılan balığı dolu';
$customfield->id = 1337;
if (function_exists('transliterator_transliterate')) {
$this->assertEquals($customfield->convertUnicodeDbSlug(), '_snipeit_hoverkraftim_yilan_baligi_dolu_1337');
} else {
$this->assertEquals($customfield->convertUnicodeDbSlug(), '_snipeit_hoverkraftaem_yaelan_balaeaeyae_dolu_1337');
}
}
public function testDbNameArabic()
{
$customfield = new CustomField();
$customfield->name = 'حَوّامتي مُمْتِلئة بِأَنْقَلَيْسون';
$customfield->id = 1337;
if (function_exists('transliterator_transliterate')) {
$this->assertEquals($customfield->convertUnicodeDbSlug(), '_snipeit_hwamty_mmtlyt_banqlyswn_1337');
} else {
$this->assertEquals($customfield->convertUnicodeDbSlug(), '_snipeit_ouzuuouoaus_uuuuoauuooc_ououzuuuuzuuzusuo_1337');
}
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace Tests\Unit;
use App\Models\Depreciation;
use App\Models\Category;
use App\Models\License;
use App\Models\AssetModel;
use Tests\TestCase;
class DepreciationTest extends TestCase
{
public function testADepreciationHasModels()
{
$depreciation = Depreciation::factory()->create();
AssetModel::factory()
->mbp13Model()
->count(5)
->create(
[
'category_id' => Category::factory()->assetLaptopCategory()->create(),
'depreciation_id' => $depreciation->id
]);
$this->assertEquals(5, $depreciation->models->count());
}
public function testADepreciationHasLicenses()
{
$depreciation = Depreciation::factory()->create();
License::factory()
->count(5)
->photoshop()
->create(
[
'category_id' => Category::factory()->licenseGraphicsCategory()->create(),
'depreciation_id' => $depreciation->id
]);
$this->assertEquals(5, $depreciation->licenses()->count());
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace Tests\Unit\Helpers;
use App\Helpers\Helper;
use Tests\TestCase;
class HelperTest extends TestCase
{
public function testDefaultChartColorsMethodHandlesHighValues()
{
$this->assertIsString(Helper::defaultChartColors(1000));
}
public function testDefaultChartColorsMethodHandlesNegativeNumbers()
{
$this->assertIsString(Helper::defaultChartColors(-1));
}
}

View File

@ -0,0 +1,210 @@
<?php
namespace Tests\Unit;
use App\Models\Ldap;
use Tests\TestCase;
/**
* @group ldap
*/
class LdapTest extends TestCase
{
use \phpmock\phpunit\PHPMock;
public function testConnect()
{
$this->settings->enableLdap();
$ldap_connect = $this->getFunctionMock("App\\Models", "ldap_connect");
$ldap_connect->expects($this->once())->willReturn('hello');
$ldap_set_option = $this->getFunctionMock("App\\Models", "ldap_set_option");
$ldap_set_option->expects($this->exactly(3));
$blah = Ldap::connectToLdap();
$this->assertEquals('hello',$blah,"LDAP_connect should return 'hello'");
}
// other test cases - with/without client-side certs?
// with/without LDAP version 3?
// with/without ignore cert validation?
// test (and mock) ldap_start_tls() ?
public function testBindAdmin()
{
$this->settings->enableLdap();
$this->getFunctionMock("App\\Models", "ldap_bind")->expects($this->once())->willReturn(true);
$this->assertNull(Ldap::bindAdminToLdap("dummy"));
}
public function testBindBad()
{
$this->settings->enableLdap();
$this->getFunctionMock("App\\Models", "ldap_bind")->expects($this->once())->willReturn(false);
$this->getFunctionMock("App\\Models","ldap_error")->expects($this->once())->willReturn("exception");
$this->expectExceptionMessage("Could not bind to LDAP:");
$this->assertNull(Ldap::bindAdminToLdap("dummy"));
}
// other test cases - test donked password?
public function testAnonymousBind()
{
//todo - would be nice to introspect somehow to make sure the right parameters were passed?
$this->settings->enableAnonymousLdap();
$this->getFunctionMock("App\\Models", "ldap_bind")->expects($this->once())->willReturn(true);
$this->assertNull(Ldap::bindAdminToLdap("dummy"));
}
public function testBadAnonymousBind()
{
$this->settings->enableAnonymousLdap();
$this->getFunctionMock("App\\Models", "ldap_bind")->expects($this->once())->willReturn(false);
$this->getFunctionMock("App\\Models","ldap_error")->expects($this->once())->willReturn("exception");
$this->expectExceptionMessage("Could not bind to LDAP:");
$this->assertNull(Ldap::bindAdminToLdap("dummy"));
}
public function testBadEncryptedPassword()
{
$this->settings->enableBadPasswordLdap();
$this->expectExceptionMessage("Your app key has changed");
$this->assertNull(Ldap::bindAdminToLdap("dummy"));
}
public function testFindAndBind()
{
$this->settings->enableLdap();
$ldap_connect = $this->getFunctionMock("App\\Models", "ldap_connect");
$ldap_connect->expects($this->once())->willReturn('hello');
$ldap_set_option = $this->getFunctionMock("App\\Models", "ldap_set_option");
$ldap_set_option->expects($this->exactly(3));
$this->getFunctionMock("App\\Models", "ldap_bind")->expects($this->once())->willReturn(true);
$this->getFunctionMock("App\\Models", "ldap_search")->expects($this->once())->willReturn(true);
$this->getFunctionMock("App\\Models", "ldap_first_entry")->expects($this->once())->willReturn(true);
$this->getFunctionMock("App\\Models", "ldap_get_attributes")->expects($this->once())->willReturn(
[
"count" => 1,
0 => [
'sn' => 'Surname',
'firstName' => 'FirstName'
]
]
);
$results = Ldap::findAndBindUserLdap("username","password");
$this->assertEqualsCanonicalizing(["count" =>1,0 =>['sn' => 'Surname','firstname' => 'FirstName']],$results);
}
public function testFindAndBindBadPassword()
{
$this->settings->enableLdap();
$ldap_connect = $this->getFunctionMock("App\\Models", "ldap_connect");
$ldap_connect->expects($this->once())->willReturn('hello');
$ldap_set_option = $this->getFunctionMock("App\\Models", "ldap_set_option");
$ldap_set_option->expects($this->exactly(3));
// note - we return FALSE first, to simulate a bad-bind, then TRUE the second time to simulate a successful admin bind
$this->getFunctionMock("App\\Models", "ldap_bind")->expects($this->exactly(2))->willReturn(false, true);
// $this->getFunctionMock("App\\Models","ldap_error")->expects($this->once())->willReturn("exception");
// $this->expectExceptionMessage("exception");
$results = Ldap::findAndBindUserLdap("username","password");
$this->assertFalse($results);
}
public function testFindAndBindCannotFindSelf()
{
$this->settings->enableLdap();
$ldap_connect = $this->getFunctionMock("App\\Models", "ldap_connect");
$ldap_connect->expects($this->once())->willReturn('hello');
$ldap_set_option = $this->getFunctionMock("App\\Models", "ldap_set_option");
$ldap_set_option->expects($this->exactly(3));
$this->getFunctionMock("App\\Models", "ldap_bind")->expects($this->once())->willReturn(true);
$this->getFunctionMock("App\\Models", "ldap_search")->expects($this->once())->willReturn(false);
$this->expectExceptionMessage("Could not search LDAP:");
$results = Ldap::findAndBindUserLdap("username","password");
$this->assertFalse($results);
}
//maybe should do an AD test as well?
public function testFindLdapUsers()
{
$this->settings->enableLdap();
$ldap_connect = $this->getFunctionMock("App\\Models", "ldap_connect");
$ldap_connect->expects($this->once())->willReturn('hello');
$ldap_set_option = $this->getFunctionMock("App\\Models", "ldap_set_option");
$ldap_set_option->expects($this->exactly(3));
$this->getFunctionMock("App\\Models", "ldap_bind")->expects($this->once())->willReturn(true);
$this->getFunctionMock("App\\Models", "ldap_search")->expects($this->once())->willReturn(["stuff"]);
$this->getFunctionMock("App\\Models", "ldap_parse_result")->expects($this->once())->willReturn(true);
$this->getFunctionMock("App\\Models", "ldap_get_entries")->expects($this->once())->willReturn(["count" => 1]);
$results = Ldap::findLdapUsers();
$this->assertEqualsCanonicalizing(["count" => 1], $results);
}
public function testFindLdapUsersPaginated()
{
$this->settings->enableLdap();
$ldap_connect = $this->getFunctionMock("App\\Models", "ldap_connect");
$ldap_connect->expects($this->once())->willReturn('hello');
$ldap_set_option = $this->getFunctionMock("App\\Models", "ldap_set_option");
$ldap_set_option->expects($this->exactly(3));
$this->getFunctionMock("App\\Models", "ldap_bind")->expects($this->once())->willReturn(true);
$this->getFunctionMock("App\\Models", "ldap_search")->expects($this->exactly(2))->willReturn(["stuff"]);
$this->getFunctionMock("App\\Models", "ldap_parse_result")->expects($this->exactly(2))->willReturnCallback(
function ($ldapconn, $search_results, $errcode , $matcheddn , $errmsg , $referrals, &$controls) {
static $count = 0;
if($count == 0) {
$count++;
$controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'] = "cookie";
return ["count" => 1];
} else {
$controls = [];
return ["count" => 1];
}
}
);
$this->getFunctionMock("App\\Models", "ldap_get_entries")->expects($this->exactly(2))->willReturn(["count" => 1]);
$results = Ldap::findLdapUsers();
$this->assertEqualsCanonicalizing(["count" => 2], $results);
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace Tests\Unit;
use App\Models\Location;
use Tests\TestCase;
class LocationTest extends TestCase
{
public function testPassesIfNotSelfParent()
{
$a = Location::factory()->make([
'name' => 'Test Location',
'id' => 1,
'parent_id' => Location::factory()->create(['id' => 10])->id,
]);
$this->assertTrue($a->isValid());
}
public function testFailsIfSelfParent()
{
$a = Location::factory()->make([
'name' => 'Test Location',
'id' => 1,
'parent_id' => 1,
]);
$this->assertFalse($a->isValid());
$this->assertStringContainsString(trans('validation.non_circular', ['attribute' => 'parent id']), $a->getErrors());
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace Tests\Unit\Models\Company;
use App\Models\Company;
use App\Models\User;
use Tests\TestCase;
class CompanyTest extends TestCase
{
public function testACompanyCanHaveUsers()
{
$company = Company::factory()->create();
$user = User::factory()
->create(
[
'company_id'=> $company->id
]
);
$this->assertCount(1, $company->users);
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace Tests\Unit\Models\Company;
use App\Models\Company;
use App\Models\User;
use Tests\TestCase;
class GetIdForCurrentUserTest extends TestCase
{
public function testReturnsProvidedValueWhenFullCompanySupportDisabled()
{
$this->settings->disableMultipleFullCompanySupport();
$this->actingAs(User::factory()->create());
$this->assertEquals(1000, Company::getIdForCurrentUser(1000));
}
public function testReturnsProvidedValueForSuperUsersWhenFullCompanySupportEnabled()
{
$this->settings->enableMultipleFullCompanySupport();
$this->actingAs(User::factory()->superuser()->create());
$this->assertEquals(2000, Company::getIdForCurrentUser(2000));
}
public function testReturnsNonSuperUsersCompanyIdWhenFullCompanySupportEnabled()
{
$this->settings->enableMultipleFullCompanySupport();
$this->actingAs(User::factory()->forCompany(['id' => 2000])->create());
$this->assertEquals(2000, Company::getIdForCurrentUser(1000));
}
public function testReturnsProvidedValueForNonSuperUserWithoutCompanyIdWhenFullCompanySupportEnabled()
{
$this->settings->enableMultipleFullCompanySupport();
$this->actingAs(User::factory()->create(['company_id' => null]));
$this->assertEquals(1000, Company::getIdForCurrentUser(1000));
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace Tests\Unit\Models\Labels;
use App\Models\Asset;
use App\Models\Labels\FieldOption;
use App\Models\User;
use Tests\TestCase;
class FieldOptionTest extends TestCase
{
public function testItDisplaysAssignedToProperly()
{
// "assignedTo" is a "special" value that can be used in the new label engine
$fieldOption = FieldOption::fromString('Assigned To=assignedTo');
$asset = Asset::factory()
->assignedToUser(User::factory()->create(['first_name' => 'Luke', 'last_name' => 'Skywalker']))
->create();
$this->assertEquals('Luke Skywalker', $fieldOption->getValue($asset));
// If the "assignedTo" relationship was eager loaded then the way to get the
// relationship changes from $asset->assignedTo to $asset->assigned.
$this->assertEquals('Luke Skywalker', $fieldOption->getValue(Asset::with('assignedTo')->find($asset->id)));
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace Tests\Unit;
use App\Models\User;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Category;
use Carbon\Carbon;
use App\Notifications\CheckoutAssetNotification;
use Illuminate\Support\Facades\Notification;
use Tests\TestCase;
class NotificationTest extends TestCase
{
public function testAUserIsEmailedIfTheyCheckoutAnAssetWithEULA()
{
$admin = User::factory()->superuser()->create();
$user = User::factory()->create();
$asset = Asset::factory()
->create(
[
'model_id' => AssetModel::factory()
->create(
[
'category_id' => Category::factory()->assetLaptopCategory()->create()->id
]
)->id,
'warranty_months' => 24,
'purchase_date' => Carbon::createFromDate(2017, 1, 1)->hour(0)->minute(0)->second(0)->format('Y-m-d')
]);
Notification::fake();
$asset->checkOut($user, $admin->id);
Notification::assertSentTo($user, CheckoutAssetNotification::class);
}
}

View File

@ -0,0 +1,73 @@
<?php
namespace Tests\Unit;
use App\Models\SnipeModel;
use Tests\TestCase;
class SnipeModelTest extends TestCase
{
public function testSetsPurchaseDatesAppropriately()
{
$c = new SnipeModel;
$c->purchase_date = '';
$this->assertTrue($c->purchase_date === null);
$c->purchase_date = '2016-03-25 12:35:50';
$this->assertTrue($c->purchase_date === '2016-03-25 12:35:50');
}
public function testSetsPurchaseCostsAppropriately()
{
$c = new SnipeModel;
$c->purchase_cost = '0.00';
$this->assertTrue($c->purchase_cost === null);
$c->purchase_cost = '9.54';
$this->assertTrue($c->purchase_cost === 9.54);
$c->purchase_cost = '9.50';
$this->assertTrue($c->purchase_cost === 9.5);
}
public function testNullsBlankLocationIdsButNotOthers()
{
$c = new SnipeModel;
$c->location_id = '';
$this->assertTrue($c->location_id === null);
$c->location_id = '5';
$this->assertTrue($c->location_id == 5);
}
public function testNullsBlankCategoriesButNotOthers()
{
$c = new SnipeModel;
$c->category_id = '';
$this->assertTrue($c->category_id === null);
$c->category_id = '1';
$this->assertTrue($c->category_id == 1);
}
public function testNullsBlankSuppliersButNotOthers()
{
$c = new SnipeModel;
$c->supplier_id = '';
$this->assertTrue($c->supplier_id === null);
$c->supplier_id = '4';
$this->assertTrue($c->supplier_id == 4);
}
public function testNullsBlankDepreciationsButNotOthers()
{
$c = new SnipeModel;
$c->depreciation_id = '';
$this->assertTrue($c->depreciation_id === null);
$c->depreciation_id = '4';
$this->assertTrue($c->depreciation_id == 4);
}
public function testNullsBlankManufacturersButNotOthers()
{
$c = new SnipeModel;
$c->manufacturer_id = '';
$this->assertTrue($c->manufacturer_id === null);
$c->manufacturer_id = '4';
$this->assertTrue($c->manufacturer_id == 4);
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace Tests\Unit;
use App\Models\Statuslabel;
use Tests\TestCase;
class StatuslabelTest extends TestCase
{
public function testRTDStatuslabelAdd()
{
$statuslabel = Statuslabel::factory()->rtd()->create();
$this->assertModelExists($statuslabel);
}
public function testPendingStatuslabelAdd()
{
$statuslabel = Statuslabel::factory()->pending()->create();
$this->assertModelExists($statuslabel);
}
public function testArchivedStatuslabelAdd()
{
$statuslabel = Statuslabel::factory()->archived()->create();
$this->assertModelExists($statuslabel);
}
public function testOutForRepairStatuslabelAdd()
{
$statuslabel = Statuslabel::factory()->outForRepair()->create();
$this->assertModelExists($statuslabel);
}
public function testBrokenStatuslabelAdd()
{
$statuslabel = Statuslabel::factory()->broken()->create();
$this->assertModelExists($statuslabel);
}
public function testLostStatuslabelAdd()
{
$statuslabel = Statuslabel::factory()->lost()->create();
$this->assertModelExists($statuslabel);
}
}

View File

@ -0,0 +1,98 @@
<?php
namespace Tests\Unit;
use App\Models\User;
use Tests\TestCase;
class UserTest extends TestCase
{
public function testFirstNameSplit()
{
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
$expected_firstname = 'Natalia';
$expected_lastname = "Allanovna Romanova-O'Shostakova";
$user = User::generateFormattedNameFromFullName($fullname, 'firstname');
$this->assertEquals($expected_firstname, $user['first_name']);
$this->assertEquals($expected_lastname, $user['last_name']);
}
public function testFirstName()
{
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
$expected_username = 'natalia';
$user = User::generateFormattedNameFromFullName($fullname, 'firstname');
$this->assertEquals($expected_username, $user['username']);
}
public function testFirstNameDotLastName()
{
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
$expected_username = 'natalia.allanovna-romanova-oshostakova';
$user = User::generateFormattedNameFromFullName($fullname, 'firstname.lastname');
$this->assertEquals($expected_username, $user['username']);
}
public function testLastNameFirstInitial()
{
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
$expected_username = 'allanovna-romanova-oshostakovan';
$user = User::generateFormattedNameFromFullName($fullname, 'lastnamefirstinitial');
$this->assertEquals($expected_username, $user['username']);
}
public function testFirstInitialLastName()
{
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
$expected_username = 'nallanovna-romanova-oshostakova';
$user = User::generateFormattedNameFromFullName($fullname, 'filastname');
$this->assertEquals($expected_username, $user['username']);
}
public function testFirstInitialUnderscoreLastName()
{
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
$expected_username = 'nallanovna-romanova-oshostakova';
$user = User::generateFormattedNameFromFullName($fullname, 'firstinitial_lastname');
$this->assertEquals($expected_username, $user['username']);
}
public function testSingleName()
{
$fullname = 'Natalia';
$expected_username = 'natalia';
$user = User::generateFormattedNameFromFullName($fullname, 'firstname_lastname',);
$this->assertEquals($expected_username, $user['username']);
}
public function firstInitialDotLastname()
{
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
$expected_username = 'n.allanovnaromanovaoshostakova';
$user = User::generateFormattedNameFromFullName($fullname, 'firstinitial.lastname');
$this->assertEquals($expected_username, $user['username']);
}
public function lastNameUnderscoreFirstInitial()
{
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
$expected_username = 'allanovnaromanovaoshostakova_n';
$user = User::generateFormattedNameFromFullName($fullname, 'lastname_firstinitial');
$this->assertEquals($expected_username, $user['username']);
}
public function firstNameLastName()
{
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
$expected_username = 'nataliaallanovnaromanovaoshostakova';
$user = User::generateFormattedNameFromFullName($fullname, 'firstnamelastname');
$this->assertEquals($expected_username, $user['username']);
}
public function firstNameLastInitial()
{
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
$expected_username = 'nataliaa';
$user = User::generateFormattedNameFromFullName($fullname, 'firstnamelastinitial');
$this->assertEquals($expected_username, $user['username']);
}
}