Merge remote-tracking branch 'origin/develop'

This commit is contained in:
snipe 2024-07-23 21:42:12 +01:00
commit b2a69efc9d
16 changed files with 553 additions and 78 deletions

View file

@ -87,6 +87,7 @@ SESSION_LIFETIME=12000
EXPIRE_ON_CLOSE=false EXPIRE_ON_CLOSE=false
ENCRYPT=false ENCRYPT=false
COOKIE_NAME=snipeit_session COOKIE_NAME=snipeit_session
PASSPORT_COOKIE_NAME='snipeit_passport_token'
COOKIE_DOMAIN=null COOKIE_DOMAIN=null
SECURE_COOKIES=false SECURE_COOKIES=false
API_TOKEN_EXPIRATION_YEARS=15 API_TOKEN_EXPIRATION_YEARS=15

View file

@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api;
use App\Events\CheckoutableCheckedIn; use App\Events\CheckoutableCheckedIn;
use App\Http\Requests\StoreAssetRequest; use App\Http\Requests\StoreAssetRequest;
use App\Http\Requests\UpdateAssetRequest;
use App\Http\Traits\MigratesLegacyAssetLocations; use App\Http\Traits\MigratesLegacyAssetLocations;
use App\Models\CheckoutAcceptance; use App\Models\CheckoutAcceptance;
use App\Models\LicenseSeat; use App\Models\LicenseSeat;
@ -651,36 +652,35 @@ class AssetsController extends Controller
* Accepts a POST request to update an asset * Accepts a POST request to update an asset
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param \App\Http\Requests\ImageUploadRequest $request
* @since [v4.0] * @since [v4.0]
*/ */
public function update(ImageUploadRequest $request, $id) : JsonResponse public function update(UpdateAssetRequest $request, Asset $asset): JsonResponse
{ {
$this->authorize('update', Asset::class); $asset->fill($request->validated());
if ($asset = Asset::find($id)) { if ($request->has('model_id')) {
$asset->fill($request->all()); $asset->model()->associate(AssetModel::find($request->validated()['model_id']));
}
if ($request->has('company_id')) {
$asset->company_id = Company::getIdForCurrentUser($request->validated()['company_id']);
}
if ($request->has('rtd_location_id') && !$request->has('location_id')) {
$asset->location_id = $request->validated()['rtd_location_id'];
}
if ($request->input('last_audit_date')) {
$asset->last_audit_date = Carbon::parse($request->input('last_audit_date'))->startOfDay()->format('Y-m-d H:i:s');
}
($request->filled('model_id')) ? /**
$asset->model()->associate(AssetModel::find($request->get('model_id'))) : null; * this is here just legacy reasons. Api\AssetController
($request->filled('rtd_location_id')) ? * used image_source once to allow encoded image uploads.
$asset->location_id = $request->get('rtd_location_id') : ''; */
($request->filled('company_id')) ? if ($request->has('image_source')) {
$asset->company_id = Company::getIdForCurrentUser($request->get('company_id')) : ''; $request->offsetSet('image', $request->offsetGet('image_source'));
}
($request->filled('rtd_location_id')) ? $asset = $request->handleImages($asset);
$asset->location_id = $request->get('rtd_location_id') : null; $model = $asset->model;
/**
* this is here just legacy reasons. Api\AssetController
* used image_source once to allow encoded image uploads.
*/
if ($request->has('image_source')) {
$request->offsetSet('image', $request->offsetGet('image_source'));
}
$asset = $request->handleImages($asset);
$model = AssetModel::find($asset->model_id);
// Update custom fields // Update custom fields
$problems_updating_encrypted_custom_fields = false; $problems_updating_encrypted_custom_fields = false;
@ -706,15 +706,13 @@ class AssetsController extends Controller
} }
} }
} }
if ($asset->save()) { if ($asset->save()) {
if (($request->filled('assigned_user')) && ($target = User::find($request->get('assigned_user')))) { if (($request->filled('assigned_user')) && ($target = User::find($request->get('assigned_user')))) {
$location = $target->location_id; $location = $target->location_id;
} elseif (($request->filled('assigned_asset')) && ($target = Asset::find($request->get('assigned_asset')))) { } elseif (($request->filled('assigned_asset')) && ($target = Asset::find($request->get('assigned_asset')))) {
$location = $target->location_id; $location = $target->location_id;
Asset::where('assigned_type', \App\Models\Asset::class)->where('assigned_to', $id) Asset::where('assigned_type', \App\Models\Asset::class)->where('assigned_to', $asset->id)
->update(['location_id' => $target->location_id]); ->update(['location_id' => $target->location_id]);
} elseif (($request->filled('assigned_location')) && ($target = Location::find($request->get('assigned_location')))) { } elseif (($request->filled('assigned_location')) && ($target = Location::find($request->get('assigned_location')))) {
$location = $target->id; $location = $target->id;
@ -728,17 +726,13 @@ class AssetsController extends Controller
$asset->image = $asset->getImageUrl(); $asset->image = $asset->getImageUrl();
} }
if ($problems_updating_encrypted_custom_fields) { if ($problems_updating_encrypted_custom_fields) {
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.encrypted_warning'))); return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.encrypted_warning')));
} else { } else {
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.success'))); return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.success')));
}
} }
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
} }
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
} }

View file

@ -14,7 +14,6 @@ use App\Models\Asset;
use App\Models\User; use App\Models\User;
use App\Notifications\FirstAdminNotification; use App\Notifications\FirstAdminNotification;
use App\Notifications\MailTest; use App\Notifications\MailTest;
use Illuminate\Http\Client\HttpClientException;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
@ -129,11 +128,11 @@ class SettingsController extends Controller
protected function dotEnvFileIsExposed() : bool protected function dotEnvFileIsExposed() : bool
{ {
try { try {
return Http::timeout(10) return Http::withoutVerifying()->timeout(10)
->accept('*/*') ->accept('*/*')
->get(URL::to('.env')) ->get(URL::to('.env'))
->successful(); ->successful();
} catch (HttpClientException $e) { } catch (\Exception $e) {
Log::debug($e->getMessage()); Log::debug($e->getMessage());
return true; return true;
} }

View file

@ -20,5 +20,5 @@ class EncryptCookies extends BaseEncrypter
* *
* @var bool * @var bool
*/ */
protected static $serialize = true; protected static $serialize = false;
} }

View file

@ -0,0 +1,44 @@
<?php
namespace App\Http\Requests;
use App\Models\Asset;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rule;
class UpdateAssetRequest extends ImageUploadRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return Gate::allows('update', $this->asset);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rules = array_merge(
parent::rules(),
(new Asset)->getRules(),
// this is to overwrite rulesets that include required, and rewrite unique_undeleted
[
'model_id' => ['integer', 'exists:models,id,deleted_at,NULL', 'not_array'],
'status_id' => ['integer', 'exists:status_labels,id'],
'asset_tag' => [
'min:1', 'max:255', 'not_array',
Rule::unique('assets', 'asset_tag')->ignore($this->asset)->withoutTrashed()
],
],
);
return $rules;
}
}

View file

@ -97,35 +97,38 @@ class Asset extends Depreciable
]; ];
protected $rules = [ protected $rules = [
'model_id' => 'required|integer|exists:models,id,deleted_at,NULL|not_array', 'model_id' => ['required', 'integer', 'exists:models,id,deleted_at,NULL', 'not_array'],
'status_id' => 'required|integer|exists:status_labels,id', 'status_id' => ['required', 'integer', 'exists:status_labels,id'],
'asset_tag' => 'required|min:1|max:255|unique_undeleted:assets,asset_tag|not_array', 'asset_tag' => ['required', 'min:1', 'max:255', 'unique_undeleted:assets,asset_tag', 'not_array'],
'name' => 'nullable|max:255', 'name' => ['nullable', 'max:255'],
'company_id' => 'nullable|integer|exists:companies,id', 'company_id' => ['nullable', 'integer', 'exists:companies,id'],
'warranty_months' => 'nullable|numeric|digits_between:0,240', 'warranty_months' => ['nullable', 'numeric', 'digits_between:0,240'],
'last_checkout' => 'nullable|date_format:Y-m-d H:i:s', 'last_checkout' => ['nullable', 'date_format:Y-m-d H:i:s'],
'last_checkin' => 'nullable|date_format:Y-m-d H:i:s', 'last_checkin' => ['nullable', 'date_format:Y-m-d H:i:s'],
'expected_checkin' => 'nullable|date', 'expected_checkin' => ['nullable', 'date'],
'last_audit_date' => 'nullable|date_format:Y-m-d H:i:s', 'last_audit_date' => ['nullable', 'date_format:Y-m-d H:i:s'],
// 'next_audit_date' => 'nullable|date|after:last_audit_date', 'next_audit_date' => ['nullable', 'date'],
'next_audit_date' => 'nullable|date', //'after:last_audit_date'],
'location_id' => 'nullable|exists:locations,id', 'location_id' => ['nullable', 'exists:locations,id'],
'rtd_location_id' => 'nullable|exists:locations,id', 'rtd_location_id' => ['nullable', 'exists:locations,id'],
'purchase_date' => 'nullable|date|date_format:Y-m-d', 'purchase_date' => ['nullable', 'date', 'date_format:Y-m-d'],
'serial' => 'nullable|unique_undeleted:assets,serial', 'serial' => ['nullable', 'unique_undeleted:assets,serial'],
'purchase_cost' => 'nullable|numeric|gte:0', 'purchase_cost' => ['nullable', 'numeric', 'gte:0'],
'supplier_id' => 'nullable|exists:suppliers,id', 'supplier_id' => ['nullable', 'exists:suppliers,id'],
'asset_eol_date' => 'nullable|date', 'asset_eol_date' => ['nullable', 'date'],
'eol_explicit' => 'nullable|boolean', 'eol_explicit' => ['nullable', 'boolean'],
'byod' => 'nullable|boolean', 'byod' => ['nullable', 'boolean'],
'order_number' => 'nullable|string|max:191', 'order_number' => ['nullable', 'string', 'max:191'],
'notes' => 'nullable|string|max:65535', 'notes' => ['nullable', 'string', 'max:65535'],
'assigned_to' => 'nullable|integer', 'assigned_to' => ['nullable', 'integer'],
'requestable' => 'nullable|boolean', 'requestable' => ['nullable', 'boolean'],
'assigned_user' => ['nullable', 'exists:users,id,deleted_at,NULL'],
'assigned_location' => ['nullable', 'exists:locations,id,deleted_at,NULL'],
'assigned_asset' => ['nullable', 'exists:assets,id,deleted_at,NULL']
]; ];
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
* *
* @var array * @var array

View file

@ -87,11 +87,11 @@ class AuthServiceProvider extends ServiceProvider
]); ]);
$this->registerPolicies(); $this->registerPolicies();
//Passport::routes(); //this is no longer required in newer passport versions
Passport::tokensExpireIn(Carbon::now()->addYears(config('passport.expiration_years'))); Passport::tokensExpireIn(Carbon::now()->addYears(config('passport.expiration_years')));
Passport::refreshTokensExpireIn(Carbon::now()->addYears(config('passport.expiration_years'))); Passport::refreshTokensExpireIn(Carbon::now()->addYears(config('passport.expiration_years')));
Passport::personalAccessTokensExpireIn(Carbon::now()->addYears(config('passport.expiration_years'))); Passport::personalAccessTokensExpireIn(Carbon::now()->addYears(config('passport.expiration_years')));
Passport::withCookieSerialization();
Passport::cookie(config('passport.cookie_name'));
/** /**

View file

@ -66,7 +66,6 @@ class ValidationServiceProvider extends ServiceProvider
* `unique_undeleted:table,fieldname` in your rules out of the box * `unique_undeleted:table,fieldname` in your rules out of the box
*/ */
Validator::extend('unique_undeleted', function ($attribute, $value, $parameters, $validator) { Validator::extend('unique_undeleted', function ($attribute, $value, $parameters, $validator) {
if (count($parameters)) { if (count($parameters)) {
// This is a bit of a shim, but serial doesn't have any other rules around it other than that it's nullable // This is a bit of a shim, but serial doesn't have any other rules around it other than that it's nullable

View file

@ -14,4 +14,5 @@ return [
'private_key' => env('PASSPORT_PRIVATE_KEY'), 'private_key' => env('PASSPORT_PRIVATE_KEY'),
'public_key' => env('PASSPORT_PUBLIC_KEY'), 'public_key' => env('PASSPORT_PUBLIC_KEY'),
'expiration_years' => env('API_TOKEN_EXPIRATION_YEARS', 20), 'expiration_years' => env('API_TOKEN_EXPIRATION_YEARS', 20),
'cookie_name' => env('PASSPORT_COOKIE_NAME', 'snipeit_passport_token'),
]; ];

View file

@ -48,6 +48,7 @@ class AssetFactory extends Factory
'assigned_type' => null, 'assigned_type' => null,
'next_audit_date' => null, 'next_audit_date' => null,
'last_checkout' => null, 'last_checkout' => null,
'asset_eol_date' => null
]; ];
} }
@ -354,6 +355,17 @@ class AssetFactory extends Factory
return $this->state(['requestable' => false]); return $this->state(['requestable' => false]);
} }
public function noPurchaseOrEolDate()
{
return $this->afterCreating(function (Asset $asset) {
$asset->update([
'purchase_date' => null,
'asset_eol_date' => null
]);
});
}
public function hasEncryptedCustomField(CustomField $field = null) public function hasEncryptedCustomField(CustomField $field = null)
{ {
return $this->state(function () use ($field) { return $this->state(function () use ($field) {
@ -372,7 +384,6 @@ class AssetFactory extends Factory
}); });
} }
/** /**
* This allows bypassing model level validation if you want to purposefully * This allows bypassing model level validation if you want to purposefully
* create an asset in an invalid state. Validation is turned back on * create an asset in an invalid state. Validation is turned back on

View file

@ -26,6 +26,12 @@ class LocationFactory extends Factory
]; ];
} }
// one of these can eventuall go away - left temporarily for conflict resolution
public function deleted(): self
{
return $this->state(['deleted_at' => $this->faker->dateTime()]);
}
public function deletedLocation() public function deletedLocation()
{ {
return $this->state(function () { return $this->state(function () {

View file

@ -309,4 +309,9 @@ class UserFactory extends Factory
]; ];
}); });
} }
public function deleted(): self
{
return $this->state(['deleted_at' => $this->faker->dateTime()]);
}
} }

View file

@ -26,7 +26,7 @@ return [
'clone' => 'Clone :item_type', 'clone' => 'Clone :item_type',
'edit' => 'Edit :item_type', 'edit' => 'Edit :item_type',
'delete' => 'Delete :item_type', 'delete' => 'Delete :item_type',
'restore' => 'Delete :item_type', 'restore' => 'Restore :item_type',
'create' => 'Create New :item_type', 'create' => 'Create New :item_type',
'checkout' => 'Checkout :item_type', 'checkout' => 'Checkout :item_type',
'checkin' => 'Checkin :item_type', 'checkin' => 'Checkin :item_type',

View file

@ -1,7 +1,6 @@
<?php <?php
use App\Http\Controllers\Api; use App\Http\Controllers\Api;
// use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
@ -571,19 +570,22 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'throttle:api']], functi
'destroy' 'destroy'
] ]
)->name('api.assets.files.destroy'); )->name('api.assets.files.destroy');
}); });
Route::resource('hardware', // pulling this out of resource route group to begin normalizing for route-model binding.
// this would probably keep working with the resource route group, but the general practice is for
// the model name to be the parameter - and i think it's a good differentiation in the code while we convert the others.
Route::patch('/hardware/{asset}', [Api\AssetsController::class, 'update'])->name('api.assets.update');
Route::resource('hardware',
Api\AssetsController::class, Api\AssetsController::class,
['names' => [ ['names' => [
'index' => 'api.assets.index', 'index' => 'api.assets.index',
'show' => 'api.assets.show', 'show' => 'api.assets.show',
'update' => 'api.assets.update',
'store' => 'api.assets.store', 'store' => 'api.assets.store',
'destroy' => 'api.assets.destroy', 'destroy' => 'api.assets.destroy',
], ],
'except' => ['create', 'edit'], 'except' => ['create', 'edit', 'update'],
'parameters' => ['asset' => 'asset_id'], 'parameters' => ['asset' => 'asset_id'],
] ]
); // end assets API routes ); // end assets API routes

View file

@ -3,13 +3,268 @@
namespace Tests\Feature\Assets\Api; namespace Tests\Feature\Assets\Api;
use App\Models\Asset; use App\Models\Asset;
use App\Models\CustomField; 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 App\Models\User;
use App\Models\CustomField;
use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\Crypt;
use Tests\TestCase; use Tests\TestCase;
class UpdateAssetTest extends TestCase class UpdateAssetTest extends TestCase
{ {
public function testThatANonExistentAssetIdReturnsError()
{
$this->actingAsForApi(User::factory()->editAssets()->createAssets()->create())
->patchJson(route('api.assets.update', 123456789))
->assertStatusMessageIs('error');
}
public function testRequiresPermissionToUpdateAsset()
{
$asset = Asset::factory()->create();
$this->actingAsForApi(User::factory()->create())
->patchJson(route('api.assets.update', $asset->id))
->assertForbidden();
}
public function testGivenPermissionUpdateAssetIsAllowed()
{
$asset = Asset::factory()->create();
$this->actingAsForApi(User::factory()->editAssets()->create())
->patchJson(route('api.assets.update', $asset->id), [
'name' => 'test'
])
->assertOk();
}
public function testAllAssetAttributesAreStored()
{
$asset = Asset::factory()->create();
$user = User::factory()->editAssets()->create();
$userAssigned = User::factory()->create();
$company = Company::factory()->create();
$location = Location::factory()->create();
$model = AssetModel::factory()->create();
$rtdLocation = Location::factory()->create();
$status = Statuslabel::factory()->create();
$supplier = Supplier::factory()->create();
$response = $this->actingAsForApi($user)
->patchJson(route('api.assets.update', $asset->id), [
'asset_eol_date' => '2024-06-02',
'asset_tag' => 'random_string',
'assigned_user' => $userAssigned->id,
'company_id' => $company->id,
'last_audit_date' => '2023-09-03 12:23:45',
'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();
$updatedAsset = Asset::find($response['payload']['id']);
$this->assertEquals('2024-06-02', $updatedAsset->asset_eol_date);
$this->assertEquals('random_string', $updatedAsset->asset_tag);
$this->assertEquals($userAssigned->id, $updatedAsset->assigned_to);
$this->assertTrue($updatedAsset->company->is($company));
$this->assertTrue($updatedAsset->location->is($location));
$this->assertTrue($updatedAsset->model->is($model));
$this->assertEquals('A New Asset', $updatedAsset->name);
$this->assertEquals('Some notes', $updatedAsset->notes);
$this->assertEquals('5678', $updatedAsset->order_number);
$this->assertEquals('123.45', $updatedAsset->purchase_cost);
$this->assertTrue($updatedAsset->purchase_date->is('2023-09-02'));
$this->assertEquals('1', $updatedAsset->requestable);
$this->assertTrue($updatedAsset->defaultLoc->is($rtdLocation));
$this->assertEquals('1234567890', $updatedAsset->serial);
$this->assertTrue($updatedAsset->assetstatus->is($status));
$this->assertTrue($updatedAsset->supplier->is($supplier));
$this->assertEquals(10, $updatedAsset->warranty_months);
//$this->assertEquals('2023-09-03 00:00:00', $updatedAsset->last_audit_date->format('Y-m-d H:i:s'));
$this->assertEquals('2023-09-03 00:00:00', $updatedAsset->last_audit_date);
}
public function testAssetEolDateIsCalculatedIfPurchaseDateUpdated()
{
$asset = Asset::factory()->laptopMbp()->noPurchaseOrEolDate()->create();
$this->actingAsForApi(User::factory()->editAssets()->create())
->patchJson((route('api.assets.update', $asset->id)), [
'purchase_date' => '2021-01-01',
])
->assertOk()
->assertStatusMessageIs('success')
->json();
$asset->refresh();
$this->assertEquals('2024-01-01', $asset->asset_eol_date);
}
public function testAssetEolDateIsNotCalculatedIfPurchaseDateNotSet()
{
$asset = Asset::factory()->laptopMbp()->noPurchaseOrEolDate()->create();
$this->actingAsForApi(User::factory()->editAssets()->create())
->patchJson(route('api.assets.update', $asset->id), [
'name' => 'test asset',
'asset_eol_date' => '2022-01-01'
])
->assertOk()
->assertStatusMessageIs('success')
->json();
$asset->refresh();
$this->assertEquals('2022-01-01', $asset->asset_eol_date);
}
public function testAssetEolExplicitIsSetIfAssetEolDateIsExplicitlySet()
{
$asset = Asset::factory()->laptopMbp()->create();
$this->actingAsForApi(User::factory()->editAssets()->create())
->patchJson(route('api.assets.update', $asset->id), [
'asset_eol_date' => '2025-01-01',
])
->assertOk()
->assertStatusMessageIs('success')
->json();
$asset->refresh();
$this->assertEquals('2025-01-01', $asset->asset_eol_date);
$this->assertTrue($asset->eol_explicit);
}
public function testAssetTagCannotUpdateToNullValue()
{
$asset = Asset::factory()->laptopMbp()->create();
$this->actingAsForApi(User::factory()->editAssets()->create())
->patchJson(route('api.assets.update', $asset->id), [
'asset_tag' => null,
])
->assertOk()
->assertStatusMessageIs('error');
}
public function testAssetTagCannotUpdateToEmptyStringValue()
{
$asset = Asset::factory()->laptopMbp()->create();
$this->actingAsForApi(User::factory()->editAssets()->create())
->patchJson(route('api.assets.update', $asset->id), [
'asset_tag' => "",
])
->assertOk()
->assertStatusMessageIs('error');
}
public function testModelIdCannotUpdateToNullValue()
{
$asset = Asset::factory()->laptopMbp()->create();
$this->actingAsForApi(User::factory()->editAssets()->create())
->patchJson(route('api.assets.update', $asset->id), [
'model_id' => null
])
->assertOk()
->assertStatusMessageIs('error');
}
public function testModelIdCannotUpdateToEmptyStringValue()
{
$asset = Asset::factory()->laptopMbp()->create();
$this->actingAsForApi(User::factory()->editAssets()->create())
->patchJson(route('api.assets.update', $asset->id), [
'model_id' => ""
])
->assertOk()
->assertStatusMessageIs('error');
}
public function testStatusIdCannotUpdateToNullValue()
{
$asset = Asset::factory()->laptopMbp()->create();
$this->actingAsForApi(User::factory()->editAssets()->create())
->patchJson(route('api.assets.update', $asset->id), [
'status_id' => null
])
->assertOk()
->assertStatusMessageIs('error');
}
public function testStatusIdCannotUpdateToEmptyStringValue()
{
$asset = Asset::factory()->laptopMbp()->create();
$this->actingAsForApi(User::factory()->editAssets()->create())
->patchJson(route('api.assets.update', $asset->id), [
'status_id' => ""
])
->assertOk()
->assertStatusMessageIs('error');
}
public function testIfRtdLocationIdIsSetWithoutLocationIdAssetReturnsToDefault()
{
$location = Location::factory()->create();
$asset = Asset::factory()->laptopMbp()->create([
'location_id' => $location->id
]);
$rtdLocation = Location::factory()->create();
$this->actingAsForApi(User::factory()->editAssets()->create())
->patchJson(route('api.assets.update', $asset->id), [
'rtd_location_id' => $rtdLocation->id
]);
$asset->refresh();
$this->assertTrue($asset->defaultLoc->is($rtdLocation));
$this->assertTrue($asset->location->is($rtdLocation));
}
public function testIfLocationAndRtdLocationAreSetLocationIdIsLocation()
{
$location = Location::factory()->create();
$asset = Asset::factory()->laptopMbp()->create();
$rtdLocation = Location::factory()->create();
$this->actingAsForApi(User::factory()->editAssets()->create())
->patchJson(route('api.assets.update', $asset->id), [
'rtd_location_id' => $rtdLocation->id,
'location_id' => $location->id
]);
$asset->refresh();
$this->assertTrue($asset->defaultLoc->is($rtdLocation));
$this->assertTrue($asset->location->is($location));
}
public function testEncryptedCustomFieldCanBeUpdated() public function testEncryptedCustomFieldCanBeUpdated()
{ {
$this->markIncompleteIfMySQL('Custom Fields tests do not work on MySQL'); $this->markIncompleteIfMySQL('Custom Fields tests do not work on MySQL');
@ -52,4 +307,151 @@ class UpdateAssetTest extends TestCase
$asset->refresh(); $asset->refresh();
$this->assertEquals("encrypted value should not change", Crypt::decrypt($asset->{$field->db_column_name()})); $this->assertEquals("encrypted value should not change", Crypt::decrypt($asset->{$field->db_column_name()}));
} }
public function testCheckoutToUserOnAssetUpdate()
{
$asset = Asset::factory()->create();
$user = User::factory()->editAssets()->create();
$assigned_user = User::factory()->create();
$response = $this->actingAsForApi($user)
->patchJson(route('api.assets.update', $asset->id), [
'assigned_user' => $assigned_user->id,
])
->assertOk()
->assertStatusMessageIs('success')
->json();
$asset->refresh();
$this->assertEquals($assigned_user->id, $asset->assigned_to);
$this->assertEquals($asset->assigned_type, 'App\Models\User');
}
public function testCheckoutToDeletedUserFailsOnAssetUpdate()
{
$asset = Asset::factory()->create();
$user = User::factory()->editAssets()->create();
$assigned_user = User::factory()->deleted()->create();
$this->actingAsForApi($user)
->patchJson(route('api.assets.update', $asset->id), [
'assigned_user' => $assigned_user->id,
])
->assertOk()
->assertStatusMessageIs('error')
->json();
$asset->refresh();
$this->assertNull($asset->assigned_to);
$this->assertNull($asset->assigned_type);
}
public function testCheckoutToLocationOnAssetUpdate()
{
$asset = Asset::factory()->create();
$user = User::factory()->editAssets()->create();
$assigned_location = Location::factory()->create();
$this->actingAsForApi($user)
->patchJson(route('api.assets.update', $asset->id), [
'assigned_location' => $assigned_location->id,
])
->assertOk()
->assertStatusMessageIs('success')
->json();
$asset->refresh();
$this->assertEquals($assigned_location->id, $asset->assigned_to);
$this->assertEquals($asset->assigned_type, 'App\Models\Location');
}
public function testCheckoutToDeletedLocationFailsOnAssetUpdate()
{
$asset = Asset::factory()->create();
$user = User::factory()->editAssets()->create();
$assigned_location = Location::factory()->deleted()->create();
$this->actingAsForApi($user)
->patchJson(route('api.assets.update', $asset->id), [
'assigned_location' => $assigned_location->id,
])
->assertOk()
->assertStatusMessageIs('error')
->json();
$asset->refresh();
$this->assertNull($asset->assigned_to);
$this->assertNull($asset->assigned_type);
}
public function testCheckoutAssetOnAssetUpdate()
{
$asset = Asset::factory()->create();
$user = User::factory()->editAssets()->create();
$assigned_asset = Asset::factory()->create();
$this->actingAsForApi($user)
->patchJson(route('api.assets.update', $asset->id), [
'assigned_asset' => $assigned_asset->id,
'checkout_to_type' => 'user',
])
->assertOk()
->assertStatusMessageIs('success')
->json();
$asset->refresh();
$this->assertEquals($assigned_asset->id, $asset->assigned_to);
$this->assertEquals($asset->assigned_type, 'App\Models\Asset');
}
public function testCheckoutToDeletedAssetFailsOnAssetUpdate()
{
$asset = Asset::factory()->create();
$user = User::factory()->editAssets()->create();
$assigned_asset = Asset::factory()->deleted()->create();
$this->actingAsForApi($user)
->patchJson(route('api.assets.update', $asset->id), [
'assigned_asset' => $assigned_asset->id,
])
->assertOk()
->assertStatusMessageIs('error')
->json();
$asset->refresh();
$this->assertNull($asset->assigned_to);
$this->assertNull($asset->assigned_type);
}
public function testAssetCannotBeUpdatedByUserInSeparateCompany()
{
$this->settings->enableMultipleFullCompanySupport();
$companyA = Company::factory()->create();
$companyB = Company::factory()->create();
$userA = User::factory()->editAssets()->create([
'company_id' => $companyA->id,
]);
$userB = User::factory()->editAssets()->create([
'company_id' => $companyB->id,
]);
$asset = Asset::factory()->create([
'user_id' => $userA->id,
'company_id' => $companyA->id,
]);
$this->actingAsForApi($userB)
->patchJson(route('api.assets.update', $asset->id), [
'name' => 'test name'
])
->assertStatusMessageIs('error');
$this->actingAsForApi($userA)
->patchJson(route('api.assets.update', $asset->id), [
'name' => 'test name'
])
->assertStatusMessageIs('success');
}
} }

View file

@ -2,6 +2,7 @@
namespace Tests\Feature\Settings; namespace Tests\Feature\Settings;
use App\Http\Controllers\SettingsController;
use Illuminate\Database\Events\QueryExecuted; use Illuminate\Database\Events\QueryExecuted;
use Illuminate\Http\Client\ConnectionException; use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\Request; use Illuminate\Http\Client\Request;
@ -301,4 +302,11 @@ class ShowSetUpPageTest extends TestCase
$this->assertSeeDirectoryPermissionError(false); $this->assertSeeDirectoryPermissionError(false);
} }
public function testInvalidTLSCertsOkWhenCheckingForEnvFile()
{
//set the weird bad SSL cert place - https://self-signed.badssl.com
$this->markTestIncomplete("Not yet sure how to write this test, it requires messing with .env ...");
$this->assertTrue((new SettingsController())->dotEnvFileIsExposed());
}
} }