diff --git a/app/Http/Controllers/Api/AssetMaintenancesController.php b/app/Http/Controllers/Api/AssetMaintenancesController.php index ac247a8873..e12dc826a9 100644 --- a/app/Http/Controllers/Api/AssetMaintenancesController.php +++ b/app/Http/Controllers/Api/AssetMaintenancesController.php @@ -188,10 +188,6 @@ class AssetMaintenancesController extends Controller // Check if the asset maintenance exists $assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId); - if (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) { - return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot delete a maintenance for that asset')); - } - $assetMaintenance->delete(); return response()->json(Helper::formatStandardApiResponse('success', $assetMaintenance, trans('admin/asset_maintenances/message.delete.success'))); diff --git a/app/Http/Controllers/Api/LicensesController.php b/app/Http/Controllers/Api/LicensesController.php index 0dae68dbb7..61f04bbd0b 100644 --- a/app/Http/Controllers/Api/LicensesController.php +++ b/app/Http/Controllers/Api/LicensesController.php @@ -220,7 +220,6 @@ class LicensesController extends Controller */ public function destroy($id) : JsonResponse { - // $license = License::findOrFail($id); $this->authorize('delete', $license); diff --git a/app/Models/PredefinedKit.php b/app/Models/PredefinedKit.php index 1bf6cb098b..f9464e12c7 100644 --- a/app/Models/PredefinedKit.php +++ b/app/Models/PredefinedKit.php @@ -4,6 +4,7 @@ namespace App\Models; use App\Models\Traits\Searchable; use App\Presenters\Presentable; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Validation\Rule; use Watson\Validating\ValidatingTrait; @@ -16,6 +17,7 @@ use Watson\Validating\ValidatingTrait; class PredefinedKit extends SnipeModel { protected $presenter = \App\Presenters\PredefinedKitPresenter::class; + use HasFactory; use Presentable; protected $table = 'kits'; diff --git a/database/factories/PredefinedKitFactory.php b/database/factories/PredefinedKitFactory.php new file mode 100644 index 0000000000..32e192655f --- /dev/null +++ b/database/factories/PredefinedKitFactory.php @@ -0,0 +1,23 @@ + + */ +class PredefinedKitFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'name' => $this->faker->words(3, true), + ]; + } +} diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index 746d88a589..b375142196 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -141,6 +141,11 @@ class UserFactory extends Factory return $this->appendPermission(['assets.view.requestable' => '1']); } + public function deleteAssetModels() + { + return $this->appendPermission(['models.delete' => '1']); + } + public function viewAccessories() { return $this->appendPermission(['accessories.view' => '1']); @@ -201,6 +206,11 @@ class UserFactory extends Factory return $this->appendPermission(['consumables.checkout' => '1']); } + public function deleteDepartments() + { + return $this->appendPermission(['departments.delete' => '1']); + } + public function viewDepartments() { return $this->appendPermission(['departments.view' => '1']); @@ -241,11 +251,6 @@ class UserFactory extends Factory return $this->appendPermission(['components.view' => '1']); } - public function createCompanies() - { - return $this->appendPermission(['companies.create' => '1']); - } - public function createComponents() { return $this->appendPermission(['components.create' => '1']); @@ -271,6 +276,16 @@ class UserFactory extends Factory return $this->appendPermission(['components.checkout' => '1']); } + public function createCompanies() + { + return $this->appendPermission(['companies.create' => '1']); + } + + public function deleteCompanies() + { + return $this->appendPermission(['companies.delete' => '1']); + } + public function viewUsers() { return $this->appendPermission(['users.view' => '1']); @@ -291,6 +306,16 @@ class UserFactory extends Factory return $this->appendPermission(['users.delete' => '1']); } + public function deleteCategories() + { + return $this->appendPermission(['categories.delete' => '1']); + } + + public function deleteLocations() + { + return $this->appendPermission(['locations.delete' => '1']); + } + public function canEditOwnLocation() { return $this->appendPermission(['self.edit_location' => '1']); @@ -306,6 +331,41 @@ class UserFactory extends Factory return $this->appendPermission(['import' => '1']); } + public function deleteCustomFields() + { + return $this->appendPermission(['customfields.delete' => '1']); + } + + public function deleteCustomFieldsets() + { + return $this->appendPermission(['customfields.delete' => '1']); + } + + public function deleteDepreciations() + { + return $this->appendPermission(['depreciations.delete' => '1']); + } + + public function deleteManufacturers() + { + return $this->appendPermission(['manufacturers.delete' => '1']); + } + + public function deletePredefinedKits() + { + return $this->appendPermission(['kits.delete' => '1']); + } + + public function deleteStatusLabels() + { + return $this->appendPermission(['statuslabels.delete' => '1']); + } + + public function deleteSuppliers() + { + return $this->appendPermission(['suppliers.delete' => '1']); + } + private function appendPermission(array $permission) { return $this->state(function ($currentState) use ($permission) { diff --git a/resources/macros/macros.php b/resources/macros/macros.php index 1b2c127a4f..db7b1d08b4 100644 --- a/resources/macros/macros.php +++ b/resources/macros/macros.php @@ -40,18 +40,18 @@ Form::macro('countries', function ($name = 'country', $selected = null, $class = foreach ($countries_array as $abbr => $country) { - // We have to handle it this way to handle deprecication warnings since you can't strtoupper on null + // We have to handle it this way to handle deprecation warnings since you can't strtoupper on null if ($abbr!='') { $abbr = strtoupper($abbr); } // Loop through the countries configured in the localization file - $select .= ' '; + $select .= ' '; } // If the country value doesn't exist in the array, add it as a new option and select it so we don't drop that data - if (!in_array($selected, $countries_array)) { + if (!array_key_exists($selected, $countries_array)) { $select .= ' '; } diff --git a/resources/views/hardware/edit.blade.php b/resources/views/hardware/edit.blade.php index 4c14b2ea02..abba2cdb9b 100755 --- a/resources/views/hardware/edit.blade.php +++ b/resources/views/hardware/edit.blade.php @@ -301,7 +301,7 @@ e.preventDefault(); - var auto_tag = $("#asset_tag").val().replace(/[^\d]/g, ''); + var auto_tag = $("#asset_tag").val().replace(/^{{ preg_quote(App\Models\Setting::getSettings()->auto_increment_prefix) }}/g, ''); var box_html = ''; const zeroPad = (num, places) => String(num).padStart(places, '0'); diff --git a/tests/Concerns/TestsFullMultipleCompaniesSupport.php b/tests/Concerns/TestsFullMultipleCompaniesSupport.php new file mode 100644 index 0000000000..a52d27bd22 --- /dev/null +++ b/tests/Concerns/TestsFullMultipleCompaniesSupport.php @@ -0,0 +1,8 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.accessories.destroy', $accessory)) + ->assertForbidden(); + + $this->assertNotSoftDeleted($accessory); + } + + public function testAdheresToFullMultipleCompaniesSupportScoping() + { + [$companyA, $companyB] = Company::factory()->count(2)->create(); + + $accessoryA = Accessory::factory()->for($companyA)->create(); + $accessoryB = Accessory::factory()->for($companyB)->create(); + $accessoryC = Accessory::factory()->for($companyB)->create(); + + $superUser = $companyA->users()->save(User::factory()->superuser()->make()); + $userInCompanyA = $companyA->users()->save(User::factory()->deleteAccessories()->make()); + $userInCompanyB = $companyB->users()->save(User::factory()->deleteAccessories()->make()); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAsForApi($userInCompanyA) + ->deleteJson(route('api.accessories.destroy', $accessoryB)) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($userInCompanyB) + ->deleteJson(route('api.accessories.destroy', $accessoryA)) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($superUser) + ->deleteJson(route('api.accessories.destroy', $accessoryC)) + ->assertStatusMessageIs('success'); + + $this->assertNotSoftDeleted($accessoryA); + $this->assertNotSoftDeleted($accessoryB); + $this->assertSoftDeleted($accessoryC); + } + + public function testCannotDeleteAccessoryThatHasCheckouts() + { + $accessory = Accessory::factory()->checkedOutToUser()->create(); + + $this->actingAsForApi(User::factory()->deleteAccessories()->create()) + ->deleteJson(route('api.accessories.destroy', $accessory)) + ->assertStatusMessageIs('error'); + + $this->assertNotSoftDeleted($accessory); + } + + public function testCanDeleteAccessory() + { + $accessory = Accessory::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteAccessories()->create()) + ->deleteJson(route('api.accessories.destroy', $accessory)) + ->assertStatusMessageIs('success'); + + $this->assertSoftDeleted($accessory); + } +} diff --git a/tests/Feature/Accessories/Api/DeleteAccessoryTest.php b/tests/Feature/Accessories/Api/DeleteAccessoryTest.php deleted file mode 100644 index d1d732e754..0000000000 --- a/tests/Feature/Accessories/Api/DeleteAccessoryTest.php +++ /dev/null @@ -1,19 +0,0 @@ -create(); - - $this->actingAsForApi(User::factory()->create()) - ->deleteJson(route('api.accessories.destroy', $accessory)) - ->assertForbidden(); - } -} diff --git a/tests/Feature/AssetMaintenances/Api/DeleteAssetMaintenancesTest.php b/tests/Feature/AssetMaintenances/Api/DeleteAssetMaintenancesTest.php new file mode 100644 index 0000000000..8a0189bc5b --- /dev/null +++ b/tests/Feature/AssetMaintenances/Api/DeleteAssetMaintenancesTest.php @@ -0,0 +1,70 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.maintenances.destroy', $assetMaintenance)) + ->assertForbidden(); + + $this->assertNotSoftDeleted($assetMaintenance); + } + + public function testAdheresToFullMultipleCompaniesSupportScoping() + { + [$companyA, $companyB] = Company::factory()->count(2)->create(); + + $assetMaintenanceA = AssetMaintenance::factory()->create(); + $assetMaintenanceB = AssetMaintenance::factory()->create(); + $assetMaintenanceC = AssetMaintenance::factory()->create(); + + $assetMaintenanceA->asset->update(['company_id' => $companyA->id]); + $assetMaintenanceB->asset->update(['company_id' => $companyB->id]); + $assetMaintenanceC->asset->update(['company_id' => $companyB->id]); + + $superUser = $companyA->users()->save(User::factory()->superuser()->make()); + $userInCompanyA = $companyA->users()->save(User::factory()->editAssets()->make()); + $userInCompanyB = $companyB->users()->save(User::factory()->editAssets()->make()); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAsForApi($userInCompanyA) + ->deleteJson(route('api.maintenances.destroy', $assetMaintenanceB)) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($userInCompanyB) + ->deleteJson(route('api.maintenances.destroy', $assetMaintenanceA)) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($superUser) + ->deleteJson(route('api.maintenances.destroy', $assetMaintenanceC)) + ->assertStatusMessageIs('success'); + + $this->assertNotSoftDeleted($assetMaintenanceA); + $this->assertNotSoftDeleted($assetMaintenanceB); + $this->assertSoftDeleted($assetMaintenanceC); + } + + public function testCanDeleteAssetMaintenance() + { + $assetMaintenance = AssetMaintenance::factory()->create(); + + $this->actingAsForApi(User::factory()->editAssets()->create()) + ->deleteJson(route('api.maintenances.destroy', $assetMaintenance)) + ->assertStatusMessageIs('success'); + + $this->assertSoftDeleted($assetMaintenance); + } +} diff --git a/tests/Feature/AssetModels/Api/DeleteAssetModelsTest.php b/tests/Feature/AssetModels/Api/DeleteAssetModelsTest.php new file mode 100644 index 0000000000..a079788651 --- /dev/null +++ b/tests/Feature/AssetModels/Api/DeleteAssetModelsTest.php @@ -0,0 +1,45 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.models.destroy', $assetModel)) + ->assertForbidden(); + + $this->assertNotSoftDeleted($assetModel); + } + + public function testCannotDeleteAssetModelThatStillHasAssociatedAssets() + { + $assetModel = Asset::factory()->create()->model; + + $this->actingAsForApi(User::factory()->deleteAssetModels()->create()) + ->deleteJson(route('api.models.destroy', $assetModel)) + ->assertStatusMessageIs('error'); + + $this->assertNotSoftDeleted($assetModel); + } + + public function testCanDeleteAssetModel() + { + $assetModel = AssetModel::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteAssetModels()->create()) + ->deleteJson(route('api.models.destroy', $assetModel)) + ->assertStatusMessageIs('success'); + + $this->assertSoftDeleted($assetModel); + } +} diff --git a/tests/Feature/Assets/Api/DeleteAssetsTest.php b/tests/Feature/Assets/Api/DeleteAssetsTest.php new file mode 100644 index 0000000000..5a017e0f5d --- /dev/null +++ b/tests/Feature/Assets/Api/DeleteAssetsTest.php @@ -0,0 +1,71 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.assets.destroy', $asset)) + ->assertForbidden(); + + $this->assertNotSoftDeleted($asset); + } + + public function testAdheresToFullMultipleCompaniesSupportScoping() + { + [$companyA, $companyB] = Company::factory()->count(2)->create(); + + $assetA = Asset::factory()->for($companyA)->create(); + $assetB = Asset::factory()->for($companyB)->create(); + $assetC = Asset::factory()->for($companyB)->create(); + + $superUser = $companyA->users()->save(User::factory()->superuser()->make()); + $userInCompanyA = $companyA->users()->save(User::factory()->deleteAssets()->make()); + $userInCompanyB = $companyB->users()->save(User::factory()->deleteAssets()->make()); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAsForApi($userInCompanyA) + ->deleteJson(route('api.assets.destroy', $assetB)) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($userInCompanyB) + ->deleteJson(route('api.assets.destroy', $assetA)) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($superUser) + ->deleteJson(route('api.assets.destroy', $assetC)) + ->assertStatusMessageIs('success'); + + $this->assertNotSoftDeleted($assetA); + $this->assertNotSoftDeleted($assetB); + $this->assertSoftDeleted($assetC); + } + + public function testCannotDeleteAssetThatIsCheckedOut() + { + $this->markTestSkipped('This behavior is not functioning yet.'); + } + + public function testCanDeleteAsset() + { + $asset = Asset::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteAssets()->create()) + ->deleteJson(route('api.assets.destroy', $asset)) + ->assertStatusMessageIs('success'); + + $this->assertSoftDeleted($asset); + } +} diff --git a/tests/Feature/Categories/Api/DeleteCategoriesTest.php b/tests/Feature/Categories/Api/DeleteCategoriesTest.php new file mode 100644 index 0000000000..eb9b73b050 --- /dev/null +++ b/tests/Feature/Categories/Api/DeleteCategoriesTest.php @@ -0,0 +1,46 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.categories.destroy', $category)) + ->assertForbidden(); + + $this->assertNotSoftDeleted($category); + } + + public function testCannotDeleteCategoryThatStillHasAssociatedItems() + { + $asset = Asset::factory()->create(); + $category = $asset->model->category; + + $this->actingAsForApi(User::factory()->deleteCategories()->create()) + ->deleteJson(route('api.categories.destroy', $category)) + ->assertStatusMessageIs('error'); + + $this->assertNotSoftDeleted($category); + } + + public function testCanDeleteCategory() + { + $category = Category::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteCategories()->create()) + ->deleteJson(route('api.categories.destroy', $category)) + ->assertStatusMessageIs('success'); + + $this->assertSoftDeleted($category); + } +} diff --git a/tests/Feature/Companies/Api/DeleteCompaniesTest.php b/tests/Feature/Companies/Api/DeleteCompaniesTest.php new file mode 100644 index 0000000000..3dcdb4fd21 --- /dev/null +++ b/tests/Feature/Companies/Api/DeleteCompaniesTest.php @@ -0,0 +1,56 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.companies.destroy', $company)) + ->assertForbidden(); + + $this->assertDatabaseHas('companies', ['id' => $company->id]); + } + + public function testCannotDeleteCompanyThatHasAssociatedItems() + { + $companyWithAssets = Company::factory()->hasAssets()->create(); + $companyWithAccessories = Company::factory()->hasAccessories()->create(); + $companyWithConsumables = Company::factory()->hasConsumables()->create(); + $companyWithComponents = Company::factory()->hasComponents()->create(); + $companyWithUsers = Company::factory()->hasUsers()->create(); + + $actor = $this->actingAsForApi(User::factory()->deleteCompanies()->create()); + + $actor->deleteJson(route('api.companies.destroy', $companyWithAssets))->assertStatusMessageIs('error'); + $actor->deleteJson(route('api.companies.destroy', $companyWithAccessories))->assertStatusMessageIs('error'); + $actor->deleteJson(route('api.companies.destroy', $companyWithConsumables))->assertStatusMessageIs('error'); + $actor->deleteJson(route('api.companies.destroy', $companyWithComponents))->assertStatusMessageIs('error'); + $actor->deleteJson(route('api.companies.destroy', $companyWithUsers))->assertStatusMessageIs('error'); + + $this->assertDatabaseHas('companies', ['id' => $companyWithAssets->id]); + $this->assertDatabaseHas('companies', ['id' => $companyWithAccessories->id]); + $this->assertDatabaseHas('companies', ['id' => $companyWithConsumables->id]); + $this->assertDatabaseHas('companies', ['id' => $companyWithComponents->id]); + $this->assertDatabaseHas('companies', ['id' => $companyWithUsers->id]); + } + + public function testCanDeleteCompany() + { + $company = Company::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteCompanies()->create()) + ->deleteJson(route('api.companies.destroy', $company)) + ->assertStatusMessageIs('success'); + + $this->assertDatabaseMissing('companies', ['id' => $company->id]); + } +} diff --git a/tests/Feature/Components/Api/DeleteComponentsTest.php b/tests/Feature/Components/Api/DeleteComponentsTest.php new file mode 100644 index 0000000000..e95fe34559 --- /dev/null +++ b/tests/Feature/Components/Api/DeleteComponentsTest.php @@ -0,0 +1,66 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.components.destroy', $component)) + ->assertForbidden(); + + $this->assertNotSoftDeleted($component); + } + + public function testAdheresToFullMultipleCompaniesSupportScoping() + { + [$companyA, $companyB] = Company::factory()->count(2)->create(); + + $componentA = Component::factory()->for($companyA)->create(); + $componentB = Component::factory()->for($companyB)->create(); + $componentC = Component::factory()->for($companyB)->create(); + + $superUser = $companyA->users()->save(User::factory()->superuser()->make()); + $userInCompanyA = $companyA->users()->save(User::factory()->deleteComponents()->make()); + $userInCompanyB = $companyB->users()->save(User::factory()->deleteComponents()->make()); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAsForApi($userInCompanyA) + ->deleteJson(route('api.components.destroy', $componentB)) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($userInCompanyB) + ->deleteJson(route('api.components.destroy', $componentA)) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($superUser) + ->deleteJson(route('api.components.destroy', $componentC)) + ->assertStatusMessageIs('success'); + + $this->assertNotSoftDeleted($componentA); + $this->assertNotSoftDeleted($componentB); + $this->assertSoftDeleted($componentC); + } + + public function testCanDeleteComponents() + { + $component = Component::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteComponents()->create()) + ->deleteJson(route('api.components.destroy', $component)) + ->assertStatusMessageIs('success'); + + $this->assertSoftDeleted($component); + } +} diff --git a/tests/Feature/Consumables/Api/DeleteConsumablesTest.php b/tests/Feature/Consumables/Api/DeleteConsumablesTest.php new file mode 100644 index 0000000000..1ab91e07a0 --- /dev/null +++ b/tests/Feature/Consumables/Api/DeleteConsumablesTest.php @@ -0,0 +1,66 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.consumables.destroy', $consumable)) + ->assertForbidden(); + + $this->assertNotSoftDeleted($consumable); + } + + public function testAdheresToFullMultipleCompaniesSupportScoping() + { + [$companyA, $companyB] = Company::factory()->count(2)->create(); + + $consumableA = Consumable::factory()->for($companyA)->create(); + $consumableB = Consumable::factory()->for($companyB)->create(); + $consumableC = Consumable::factory()->for($companyB)->create(); + + $superUser = $companyA->users()->save(User::factory()->superuser()->make()); + $userInCompanyA = $companyA->users()->save(User::factory()->deleteConsumables()->make()); + $userInCompanyB = $companyB->users()->save(User::factory()->deleteConsumables()->make()); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAsForApi($userInCompanyA) + ->deleteJson(route('api.consumables.destroy', $consumableB)) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($userInCompanyB) + ->deleteJson(route('api.consumables.destroy', $consumableA)) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($superUser) + ->deleteJson(route('api.consumables.destroy', $consumableC)) + ->assertStatusMessageIs('success'); + + $this->assertNotSoftDeleted($consumableA); + $this->assertNotSoftDeleted($consumableB); + $this->assertSoftDeleted($consumableC); + } + + public function testCanDeleteConsumables() + { + $consumable = Consumable::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteConsumables()->create()) + ->deleteJson(route('api.consumables.destroy', $consumable)) + ->assertStatusMessageIs('success'); + + $this->assertSoftDeleted($consumable); + } +} diff --git a/tests/Feature/CustomFields/Api/DeleteCustomFieldsTest.php b/tests/Feature/CustomFields/Api/DeleteCustomFieldsTest.php new file mode 100644 index 0000000000..ab40591e90 --- /dev/null +++ b/tests/Feature/CustomFields/Api/DeleteCustomFieldsTest.php @@ -0,0 +1,54 @@ +markIncompleteIfMySQL('Custom Fields tests do not work on MySQL'); + + $customField = CustomField::factory()->create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.customfields.destroy', $customField)) + ->assertForbidden(); + + $this->assertDatabaseHas('custom_fields', ['id' => $customField->id]); + } + + public function testCustomFieldsCannotBeDeletedIfTheyHaveAssociatedFieldsets() + { + $this->markIncompleteIfMySQL('Custom Fields tests do not work on MySQL'); + + $customField = CustomField::factory()->create(); + $customFieldset = CustomFieldset::factory()->create(); + + $customField->fieldset()->attach($customFieldset, ['order' => 1, 'required' => 'false']); + + $this->actingAsForApi(User::factory()->deleteCustomFields()->create()) + ->deleteJson(route('api.customfields.destroy', $customField)) + ->assertStatusMessageIs('error'); + + $this->assertDatabaseHas('custom_fields', ['id' => $customField->id]); + } + + public function testCustomFieldsCanBeDeleted() + { + $this->markIncompleteIfMySQL('Custom Fields tests do not work on MySQL'); + + $customField = CustomField::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteCustomFields()->create()) + ->deleteJson(route('api.customfields.destroy', $customField)) + ->assertStatusMessageIs('success'); + + $this->assertDatabaseMissing('custom_fields', ['id' => $customField->id]); + } +} diff --git a/tests/Feature/CustomFieldsets/Api/DeleteCustomFieldsetsTest.php b/tests/Feature/CustomFieldsets/Api/DeleteCustomFieldsetsTest.php new file mode 100644 index 0000000000..c464323653 --- /dev/null +++ b/tests/Feature/CustomFieldsets/Api/DeleteCustomFieldsetsTest.php @@ -0,0 +1,67 @@ +markIncompleteIfMySQL('Custom Fields tests do not work on MySQL'); + + $customFieldset = CustomFieldset::factory()->create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.fieldsets.destroy', $customFieldset)) + ->assertForbidden(); + + $this->assertDatabaseHas('custom_fieldsets', ['id' => $customFieldset->id]); + } + + public function testCannotDeleteCustomFieldsetWithAssociatedFields() + { + $this->markIncompleteIfMySQL('Custom Fields tests do not work on MySQL'); + + $customField = CustomField::factory()->create(); + $customFieldset = CustomFieldset::factory()->create(); + + $customField->fieldset()->attach($customFieldset, ['order' => 1, 'required' => 'false']); + + $this->actingAsForApi(User::factory()->deleteCustomFieldsets()->create()) + ->deleteJson(route('api.fieldsets.destroy', $customFieldset)) + ->assertStatusMessageIs('error'); + + $this->assertDatabaseHas('custom_fieldsets', ['id' => $customFieldset->id]); + } + + public function testCannotDeleteCustomFieldsetWithAssociatedModels() + { + $this->markIncompleteIfMySQL('Custom Fields tests do not work on MySQL'); + + $customFieldset = CustomFieldset::factory()->hasModels()->create(); + + $this->actingAsForApi(User::factory()->deleteCustomFieldsets()->create()) + ->deleteJson(route('api.fieldsets.destroy', $customFieldset)) + ->assertStatusMessageIs('error'); + + $this->assertDatabaseHas('custom_fieldsets', ['id' => $customFieldset->id]); + } + + public function testCanDeleteCustomFieldsets() + { + $this->markIncompleteIfMySQL('Custom Fields tests do not work on MySQL'); + + $customFieldset = CustomFieldset::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteCustomFieldsets()->create()) + ->deleteJson(route('api.fieldsets.destroy', $customFieldset)) + ->assertStatusMessageIs('success'); + + $this->assertDatabaseMissing('custom_fieldsets', ['id' => $customFieldset->id]); + } +} diff --git a/tests/Feature/Departments/Api/DeleteDepartmentsTest.php b/tests/Feature/Departments/Api/DeleteDepartmentsTest.php new file mode 100644 index 0000000000..cf59b81510 --- /dev/null +++ b/tests/Feature/Departments/Api/DeleteDepartmentsTest.php @@ -0,0 +1,77 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.departments.destroy', $department)) + ->assertForbidden(); + + $this->assertDatabaseHas('departments', ['id' => $department->id]); + } + + public function testAdheresToFullMultipleCompaniesSupportScoping() + { + [$companyA, $companyB] = Company::factory()->count(2)->create(); + + $departmentA = Department::factory()->for($companyA)->create(); + $departmentB = Department::factory()->for($companyB)->create(); + $departmentC = Department::factory()->for($companyB)->create(); + + $superUser = $companyA->users()->save(User::factory()->superuser()->make()); + $userInCompanyA = $companyA->users()->save(User::factory()->deleteDepartments()->make()); + $userInCompanyB = $companyB->users()->save(User::factory()->deleteDepartments()->make()); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAsForApi($userInCompanyA) + ->deleteJson(route('api.departments.destroy', $departmentB)) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($userInCompanyB) + ->deleteJson(route('api.departments.destroy', $departmentA)) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($superUser) + ->deleteJson(route('api.departments.destroy', $departmentC)) + ->assertStatusMessageIs('success'); + + $this->assertDatabaseHas('departments', ['id' => $departmentA->id]); + $this->assertDatabaseHas('departments', ['id' => $departmentB->id]); + $this->assertDatabaseMissing('departments', ['id' => $departmentC->id]); + } + + public function testCannotDeleteDepartmentThatStillHasUsers() + { + $department = Department::factory()->hasUsers()->create(); + + $this->actingAsForApi(User::factory()->deleteDepartments()->create()) + ->deleteJson(route('api.departments.destroy', $department)) + ->assertStatusMessageIs('error'); + + $this->assertDatabaseHas('departments', ['id' => $department->id]); + } + + public function testCanDeleteDepartment() + { + $department = Department::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteDepartments()->create()) + ->deleteJson(route('api.departments.destroy', $department)) + ->assertStatusMessageIs('success'); + + $this->assertDatabaseMissing('departments', ['id' => $department->id]); + } +} diff --git a/tests/Feature/Depreciations/Api/DeleteDepreciationsTest.php b/tests/Feature/Depreciations/Api/DeleteDepreciationsTest.php new file mode 100644 index 0000000000..d1b32079cc --- /dev/null +++ b/tests/Feature/Depreciations/Api/DeleteDepreciationsTest.php @@ -0,0 +1,44 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.depreciations.destroy', $depreciation)) + ->assertForbidden(); + + $this->assertDatabaseHas('depreciations', ['id' => $depreciation->id]); + } + + public function testCannotDeleteDepreciationThatHasAssociatedModels() + { + $depreciation = Depreciation::factory()->hasModels()->create(); + + $this->actingAsForApi(User::factory()->deleteDepreciations()->create()) + ->deleteJson(route('api.depreciations.destroy', $depreciation)) + ->assertStatusMessageIs('error'); + + $this->assertDatabaseHas('depreciations', ['id' => $depreciation->id]); + } + + public function testCanDeleteDepreciation() + { + $depreciation = Depreciation::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteDepreciations()->create()) + ->deleteJson(route('api.depreciations.destroy', $depreciation)) + ->assertStatusMessageIs('success'); + + $this->assertDatabaseMissing('depreciations', ['id' => $depreciation->id]); + } +} diff --git a/tests/Feature/Groups/Api/DeleteGroupsTest.php b/tests/Feature/Groups/Api/DeleteGroupsTest.php new file mode 100644 index 0000000000..8259cec84c --- /dev/null +++ b/tests/Feature/Groups/Api/DeleteGroupsTest.php @@ -0,0 +1,34 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.groups.destroy', $group)) + ->assertForbidden(); + + $this->assertDatabaseHas('permission_groups', ['id' => $group->id]); + } + + public function testCanDeleteGroup() + { + $group = Group::factory()->create(); + + // only super admins can delete groups + $this->actingAsForApi(User::factory()->superuser()->create()) + ->deleteJson(route('api.groups.destroy', $group)) + ->assertStatusMessageIs('success'); + + $this->assertDatabaseMissing('permission_groups', ['id' => $group->id]); + } +} diff --git a/tests/Feature/Licenses/Api/DeleteLicensesTest.php b/tests/Feature/Licenses/Api/DeleteLicensesTest.php new file mode 100644 index 0000000000..7db8e588d3 --- /dev/null +++ b/tests/Feature/Licenses/Api/DeleteLicensesTest.php @@ -0,0 +1,90 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.licenses.destroy', $license)) + ->assertForbidden(); + + $this->assertNotSoftDeleted($license); + } + + public function testAdheresToFullMultipleCompaniesSupportScoping() + { + [$companyA, $companyB] = Company::factory()->count(2)->create(); + + $licenseA = License::factory()->for($companyA)->create(); + $licenseB = License::factory()->for($companyB)->create(); + $licenseC = License::factory()->for($companyB)->create(); + + $superUser = $companyA->users()->save(User::factory()->superuser()->make()); + $userInCompanyA = $companyA->users()->save(User::factory()->deleteLicenses()->make()); + $userInCompanyB = $companyB->users()->save(User::factory()->deleteLicenses()->make()); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAsForApi($userInCompanyA) + ->deleteJson(route('api.licenses.destroy', $licenseB)) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($userInCompanyB) + ->deleteJson(route('api.licenses.destroy', $licenseA)) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($superUser) + ->deleteJson(route('api.licenses.destroy', $licenseC)) + ->assertStatusMessageIs('success'); + + $this->assertNotSoftDeleted($licenseA); + $this->assertNotSoftDeleted($licenseB); + $this->assertSoftDeleted($licenseC); + } + + public function testLicenseCannotBeDeletedIfStillAssigned() + { + $license = License::factory()->create(['seats' => 2]); + $license->freeSeat()->update(['assigned_to' => User::factory()->create()->id]); + + $this->actingAsForApi(User::factory()->deleteLicenses()->create()) + ->deleteJson(route('api.licenses.destroy', $license)) + ->assertStatusMessageIs('error'); + + $this->assertNotSoftDeleted($license); + } + + public function testCanDeleteLicense() + { + $license = License::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteLicenses()->create()) + ->deleteJson(route('api.licenses.destroy', $license)) + ->assertStatusMessageIs('success'); + + $this->assertSoftDeleted($license); + } + + public function testLicenseSeatsAreDeletedWhenLicenseIsDeleted() + { + $license = License::factory()->create(['seats' => 2]); + + $this->assertTrue($license->fresh()->licenseseats->isNotEmpty(), 'License seats not created like expected'); + + $this->actingAsForApi(User::factory()->deleteLicenses()->create()) + ->deleteJson(route('api.licenses.destroy', $license)); + + $this->assertTrue($license->fresh()->licenseseats->isEmpty()); + } +} diff --git a/tests/Feature/Locations/Api/DeleteLocationsTest.php b/tests/Feature/Locations/Api/DeleteLocationsTest.php index 270582c901..796b9a1977 100644 --- a/tests/Feature/Locations/Api/DeleteLocationsTest.php +++ b/tests/Feature/Locations/Api/DeleteLocationsTest.php @@ -5,10 +5,21 @@ namespace Tests\Feature\Locations\Api; use App\Models\Asset; use App\Models\Location; use App\Models\User; +use Tests\Concerns\TestsPermissionsRequirement; use Tests\TestCase; -class DeleteLocationsTest extends TestCase +class DeleteLocationsTest extends TestCase implements TestsPermissionsRequirement { + public function testRequiresPermission() + { + $location = Location::factory()->create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.locations.destroy', $location)) + ->assertForbidden(); + + $this->assertNotSoftDeleted($location); + } public function testErrorReturnedViaApiIfLocationDoesNotExist() { @@ -90,4 +101,15 @@ class DeleteLocationsTest extends TestCase ->json(); } + public function testCanDeleteLocation() + { + $location = Location::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteLocations()->create()) + ->deleteJson(route('api.locations.destroy', $location->id)) + ->assertOk() + ->assertStatusMessageIs('success'); + + $this->assertSoftDeleted($location); + } } diff --git a/tests/Feature/Manufacturers/Api/DeleteManufacturersTest.php b/tests/Feature/Manufacturers/Api/DeleteManufacturersTest.php new file mode 100644 index 0000000000..a5fbb5ee0a --- /dev/null +++ b/tests/Feature/Manufacturers/Api/DeleteManufacturersTest.php @@ -0,0 +1,64 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.manufacturers.destroy', $manufacturer)) + ->assertForbidden(); + + $this->assertNotSoftDeleted($manufacturer); + } + + public function testCannotDeleteManufacturerWithAssociatedData() + { + $manufacturerWithAccessories = Manufacturer::factory()->hasAccessories()->create(); + $manufacturerWithConsumables = Manufacturer::factory()->hasConsumables()->create(); + $manufacturerWithLicenses = Manufacturer::factory()->hasLicenses()->create(); + + $manufacturerWithAssets = Manufacturer::factory()->hasAssets()->create(); + $model = AssetModel::factory()->create(['manufacturer_id' => $manufacturerWithAssets->id]); + Asset::factory()->create(['model_id' => $model->id]); + + $this->assertGreaterThan(0, $manufacturerWithAccessories->accessories->count(), 'Precondition failed: Manufacturer has no accessories'); + $this->assertGreaterThan(0, $manufacturerWithAssets->assets->count(), 'Precondition failed: Manufacturer has no assets'); + $this->assertGreaterThan(0, $manufacturerWithConsumables->consumables->count(), 'Precondition failed: Manufacturer has no consumables'); + $this->assertGreaterThan(0, $manufacturerWithLicenses->licenses->count(), 'Precondition failed: Manufacturer has no licenses'); + + $actor = $this->actingAsForApi(User::factory()->deleteManufacturers()->create()); + + $actor->deleteJson(route('api.manufacturers.destroy', $manufacturerWithAccessories))->assertStatusMessageIs('error'); + $actor->deleteJson(route('api.manufacturers.destroy', $manufacturerWithAssets))->assertStatusMessageIs('error'); + $actor->deleteJson(route('api.manufacturers.destroy', $manufacturerWithConsumables))->assertStatusMessageIs('error'); + $actor->deleteJson(route('api.manufacturers.destroy', $manufacturerWithLicenses))->assertStatusMessageIs('error'); + + $this->assertNotSoftDeleted($manufacturerWithAssets); + $this->assertNotSoftDeleted($manufacturerWithAccessories); + $this->assertNotSoftDeleted($manufacturerWithConsumables); + $this->assertNotSoftDeleted($manufacturerWithLicenses); + } + + public function testCanDeleteManufacturer() + { + $manufacturer = Manufacturer::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteManufacturers()->create()) + ->deleteJson(route('api.manufacturers.destroy', $manufacturer)) + ->assertOk() + ->assertStatusMessageIs('success'); + + $this->assertSoftDeleted($manufacturer); + } +} diff --git a/tests/Feature/PredefinedKits/Api/DeletePredefinedKitsTest.php b/tests/Feature/PredefinedKits/Api/DeletePredefinedKitsTest.php new file mode 100644 index 0000000000..b9ff6164a7 --- /dev/null +++ b/tests/Feature/PredefinedKits/Api/DeletePredefinedKitsTest.php @@ -0,0 +1,59 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.kits.destroy', $predefinedKit)) + ->assertForbidden(); + + $this->assertDatabaseHas('kits', ['id' => $predefinedKit->id]); + } + + public function testCanDeletePredefinedKits() + { + $predefinedKit = PredefinedKit::factory()->create(); + + $this->actingAsForApi(User::factory()->deletePredefinedKits()->create()) + ->deleteJson(route('api.kits.destroy', $predefinedKit)) + ->assertOk() + ->assertStatusMessageIs('success'); + + $this->assertDatabaseMissing('kits', ['id' => $predefinedKit->id]); + } + + public function testAssociatedDataDetachedWhenPredefinedKitDeleted() + { + $predefinedKit = PredefinedKit::factory() + ->hasAccessories() + ->hasConsumables() + ->hasLicenses() + ->hasModels() + ->create(); + + $this->assertGreaterThan(0, $predefinedKit->accessories->count(), 'Precondition failed: PredefinedKit has no accessories'); + $this->assertGreaterThan(0, $predefinedKit->consumables->count(), 'Precondition failed: PredefinedKit has no consumables'); + $this->assertGreaterThan(0, $predefinedKit->licenses->count(), 'Precondition failed: PredefinedKit has no licenses'); + $this->assertGreaterThan(0, $predefinedKit->models->count(), 'Precondition failed: PredefinedKit has no models'); + + $this->actingAsForApi(User::factory()->deletePredefinedKits()->create()) + ->deleteJson(route('api.kits.destroy', $predefinedKit)) + ->assertStatusMessageIs('success'); + + $this->assertEquals(0, DB::table('kits_accessories')->where('kit_id', $predefinedKit->id)->count()); + $this->assertEquals(0, DB::table('kits_consumables')->where('kit_id', $predefinedKit->id)->count()); + $this->assertEquals(0, DB::table('kits_licenses')->where('kit_id', $predefinedKit->id)->count()); + $this->assertEquals(0, DB::table('kits_models')->where('kit_id', $predefinedKit->id)->count()); + } +} diff --git a/tests/Feature/StatusLabels/Api/DeleteStatusLabelsTest.php b/tests/Feature/StatusLabels/Api/DeleteStatusLabelsTest.php new file mode 100644 index 0000000000..04728c7c41 --- /dev/null +++ b/tests/Feature/StatusLabels/Api/DeleteStatusLabelsTest.php @@ -0,0 +1,47 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.statuslabels.destroy', $statusLabel)) + ->assertForbidden(); + + $this->assertNotSoftDeleted($statusLabel); + } + + public function testCannotDeleteStatusLabelWhileStillAssociatedToAssets() + { + $statusLabel = Statuslabel::factory()->hasAssets()->create(); + + $this->assertGreaterThan(0, $statusLabel->assets->count(), 'Precondition failed: StatusLabel has no assets'); + + $this->actingAsForApi(User::factory()->deleteStatusLabels()->create()) + ->deleteJson(route('api.statuslabels.destroy', $statusLabel)) + ->assertStatusMessageIs('error'); + + $this->assertNotSoftDeleted($statusLabel); + } + + public function testCanDeleteStatusLabel() + { + $statusLabel = Statuslabel::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteStatusLabels()->create()) + ->deleteJson(route('api.statuslabels.destroy', $statusLabel)) + ->assertOk() + ->assertStatusMessageIs('success'); + + $this->assertSoftDeleted($statusLabel); + } +} diff --git a/tests/Feature/Suppliers/Api/DeleteSuppliersTest.php b/tests/Feature/Suppliers/Api/DeleteSuppliersTest.php new file mode 100644 index 0000000000..7da8197bde --- /dev/null +++ b/tests/Feature/Suppliers/Api/DeleteSuppliersTest.php @@ -0,0 +1,52 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.suppliers.destroy', $supplier)) + ->assertForbidden(); + + $this->assertNotSoftDeleted($supplier); + } + + public function testCannotDeleteSupplierWithDataStillAssociated() + { + $supplierWithAsset = Supplier::factory()->hasAssets()->create(); + $supplierWithAssetMaintenance = Supplier::factory()->has(AssetMaintenance::factory(), 'asset_maintenances')->create(); + $supplierWithLicense = Supplier::factory()->hasLicenses()->create(); + + $actor = $this->actingAsForApi(User::factory()->deleteSuppliers()->create()); + + $actor->deleteJson(route('api.suppliers.destroy', $supplierWithAsset))->assertStatusMessageIs('error'); + $actor->deleteJson(route('api.suppliers.destroy', $supplierWithAssetMaintenance))->assertStatusMessageIs('error'); + $actor->deleteJson(route('api.suppliers.destroy', $supplierWithLicense))->assertStatusMessageIs('error'); + + $this->assertNotSoftDeleted($supplierWithAsset); + $this->assertNotSoftDeleted($supplierWithAssetMaintenance); + $this->assertNotSoftDeleted($supplierWithLicense); + } + + public function testCanDeleteSupplier() + { + $supplier = Supplier::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteSuppliers()->create()) + ->deleteJson(route('api.suppliers.destroy', $supplier)) + ->assertOk() + ->assertStatusMessageIs('success'); + + $this->assertSoftDeleted($supplier); + } +} diff --git a/tests/Feature/Users/Api/DeleteUserTest.php b/tests/Feature/Users/Api/DeleteUsersTest.php similarity index 85% rename from tests/Feature/Users/Api/DeleteUserTest.php rename to tests/Feature/Users/Api/DeleteUsersTest.php index 49625daac3..9677e5f7d4 100644 --- a/tests/Feature/Users/Api/DeleteUserTest.php +++ b/tests/Feature/Users/Api/DeleteUsersTest.php @@ -6,11 +6,22 @@ use App\Models\Company; use App\Models\LicenseSeat; use App\Models\Location; use App\Models\User; +use Tests\Concerns\TestsFullMultipleCompaniesSupport; +use Tests\Concerns\TestsPermissionsRequirement; use Tests\TestCase; -class DeleteUserTest extends TestCase +class DeleteUsersTest extends TestCase implements TestsFullMultipleCompaniesSupport, TestsPermissionsRequirement { + public function testRequiresPermission() + { + $user = User::factory()->create(); + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.users.destroy', $user)) + ->assertForbidden(); + + $this->assertNotSoftDeleted($user); + } public function testErrorReturnedViaApiIfUserDoesNotExist() { @@ -33,7 +44,6 @@ class DeleteUserTest extends TestCase ->json(); } - public function testDisallowUserDeletionViaApiIfStillManagingPeople() { $manager = User::factory()->create(); @@ -78,26 +88,19 @@ class DeleteUserTest extends TestCase ->json(); } - public function testDeniedPermissionsForDeletingUserViaApi() + public function testUsersCannotDeleteThemselves() { - $this->actingAsForApi(User::factory()->create()) - ->deleteJson(route('api.users.destroy', User::factory()->create())) - ->assertStatus(403) - ->json(); - } - - public function testSuccessPermissionsForDeletingUserViaApi() - { - $this->actingAsForApi(User::factory()->deleteUsers()->create()) - ->deleteJson(route('api.users.destroy', User::factory()->create())) + $user = User::factory()->deleteUsers()->create(); + $this->actingAsForApi($user) + ->deleteJson(route('api.users.destroy', $user)) ->assertOk() ->assertStatus(200) - ->assertStatusMessageIs('success') + ->assertStatusMessageIs('error') ->json(); + } - - public function testPermissionsForDeletingIfNotInSameCompanyAndNotSuperadmin() + public function testAdheresToFullMultipleCompaniesSupportScoping() { $this->settings->enableMultipleFullCompanySupport(); @@ -136,20 +139,17 @@ class DeleteUserTest extends TestCase $userFromA->refresh(); $this->assertNotNull($userFromA->deleted_at); - } - public function testUsersCannotDeleteThemselves() + public function testCanDeleteUser() { - $user = User::factory()->deleteUsers()->create(); - $this->actingAsForApi($user) + $user = User::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteUsers()->create()) ->deleteJson(route('api.users.destroy', $user)) ->assertOk() - ->assertStatus(200) - ->assertStatusMessageIs('error') - ->json(); + ->assertStatusMessageIs('success'); + $this->assertSoftDeleted($user); } - - }