diff --git a/.github/workflows/SA-codeql.yml b/.github/workflows/SA-codeql.yml index acf65d5c16..05efd9118d 100644 --- a/.github/workflows/SA-codeql.yml +++ b/.github/workflows/SA-codeql.yml @@ -26,7 +26,7 @@ jobs: language: [ 'javascript' ] steps: - name: Checkout repository - uses: actions/checkout@v3.3.0 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/codacy-analysis.yml b/.github/workflows/codacy-analysis.yml index 758838307d..3184042c62 100644 --- a/.github/workflows/codacy-analysis.yml +++ b/.github/workflows/codacy-analysis.yml @@ -32,7 +32,7 @@ jobs: steps: # Checkout the repository to the GitHub Actions runner - name: Checkout code - uses: actions/checkout@v3.3.0 + uses: actions/checkout@v4 # Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis - name: Run Codacy Analysis CLI diff --git a/.github/workflows/crowdin-upload.yml b/.github/workflows/crowdin-upload.yml index c986accf37..3255934534 100644 --- a/.github/workflows/crowdin-upload.yml +++ b/.github/workflows/crowdin-upload.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Crowdin push uses: crowdin/github-action@v1 diff --git a/.github/workflows/docker-alpine.yml b/.github/workflows/docker-alpine.yml index 0a5c28ee53..1a52f849a7 100644 --- a/.github/workflows/docker-alpine.yml +++ b/.github/workflows/docker-alpine.yml @@ -41,7 +41,7 @@ jobs: steps: # https://github.com/actions/checkout - name: Checkout codebase - uses: actions/checkout@v3.3.0 + uses: actions/checkout@v4 # https://github.com/docker/setup-buildx-action - name: Setup Docker Buildx diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 5aa2758e79..7600c9f769 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -41,7 +41,7 @@ jobs: steps: # https://github.com/actions/checkout - name: Checkout codebase - uses: actions/checkout@v3.3.0 + uses: actions/checkout@v4 # https://github.com/docker/setup-buildx-action - name: Setup Docker Buildx diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a3ab759441..a82946161d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -37,7 +37,7 @@ jobs: php-version: "${{ matrix.php-version }}" coverage: none - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Get Composer Cache Directory id: composer-cache diff --git a/app/Console/Commands/LdapSync.php b/app/Console/Commands/LdapSync.php index c72de8cb72..29b9eced34 100755 --- a/app/Console/Commands/LdapSync.php +++ b/app/Console/Commands/LdapSync.php @@ -180,10 +180,6 @@ class LdapSync extends Command } } - /* Create user account entries in Snipe-IT */ - $tmp_pass = substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 20); - $pass = bcrypt($tmp_pass); - $manager_cache = []; if($ldap_default_group != null) { @@ -229,7 +225,7 @@ class LdapSync extends Command } else { // Creating a new user. $user = new User; - $user->password = $pass; + $user->password = $user->noPassword(); $user->activated = 1; // newly created users can log in by default, unless AD's UAC is in use, or an active flag is set (below) $item['createorupdate'] = 'created'; } diff --git a/app/Helpers/Helper.php b/app/Helpers/Helper.php index 680c7acfd6..c2ccda6c84 100644 --- a/app/Helpers/Helper.php +++ b/app/Helpers/Helper.php @@ -2,6 +2,8 @@ namespace App\Helpers; use App\Models\Accessory; +use App\Models\Asset; +use App\Models\AssetModel; use App\Models\Component; use App\Models\Consumable; use App\Models\CustomField; @@ -643,6 +645,7 @@ class Helper $consumables = Consumable::withCount('consumableAssignments as consumable_assignments_count')->whereNotNull('min_amt')->get(); $accessories = Accessory::withCount('users as users_count')->whereNotNull('min_amt')->get(); $components = Component::whereNotNull('min_amt')->get(); + $asset_models = AssetModel::where('min_amt', '>', 0)->get(); $avail_consumables = 0; $items_array = []; @@ -705,6 +708,28 @@ class Helper } } + foreach ($asset_models as $asset_model){ + + $asset = new Asset(); + $total_owned = $asset->where('model_id', '=', $asset_model->id)->count(); + $avail = $asset->where('model_id', '=', $asset_model->id)->whereNull('assigned_to')->count(); + + if ($avail < ($asset_model->min_amt)+ \App\Models\Setting::getSettings()->alert_threshold) { + if ($avail > 0) { + $percent = number_format((($avail / $total_owned) * 100), 0); + } else { + $percent = 100; + } + $items_array[$all_count]['id'] = $asset_model->id; + $items_array[$all_count]['name'] = $asset_model->name; + $items_array[$all_count]['type'] = 'models'; + $items_array[$all_count]['percent'] = $percent; + $items_array[$all_count]['remaining'] = $avail; + $items_array[$all_count]['min_amt'] = $asset_model->min_amt; + $all_count++; + } + } + return $items_array; } diff --git a/app/Http/Controllers/Api/AssetModelsController.php b/app/Http/Controllers/Api/AssetModelsController.php index cf13d24fc4..c1c46538db 100644 --- a/app/Http/Controllers/Api/AssetModelsController.php +++ b/app/Http/Controllers/Api/AssetModelsController.php @@ -38,6 +38,7 @@ class AssetModelsController extends Controller 'image', 'name', 'model_number', + 'min_amt', 'eol', 'notes', 'created_at', @@ -52,6 +53,7 @@ class AssetModelsController extends Controller 'models.image', 'models.name', 'model_number', + 'min_amt', 'eol', 'requestable', 'models.notes', diff --git a/app/Http/Controllers/Api/DepartmentsController.php b/app/Http/Controllers/Api/DepartmentsController.php index ef988af597..d152d0a507 100644 --- a/app/Http/Controllers/Api/DepartmentsController.php +++ b/app/Http/Controllers/Api/DepartmentsController.php @@ -27,7 +27,7 @@ class DepartmentsController extends Controller $this->authorize('view', Department::class); $allowed_columns = ['id', 'name', 'image', 'users_count']; - $departments = Company::scopeCompanyables(Department::select( + $departments = Department::select( 'departments.id', 'departments.name', 'departments.phone', @@ -37,8 +37,8 @@ class DepartmentsController extends Controller 'departments.manager_id', 'departments.created_at', 'departments.updated_at', - 'departments.image'), - "company_id", "departments")->with('users')->with('location')->with('manager')->with('company')->withCount('users as users_count'); + 'departments.image' + )->with('users')->with('location')->with('manager')->with('company')->withCount('users as users_count'); if ($request->filled('search')) { $departments = $departments->TextSearch($request->input('search')); diff --git a/app/Http/Controllers/Api/UsersController.php b/app/Http/Controllers/Api/UsersController.php index 3b76317327..64d942fb9d 100644 --- a/app/Http/Controllers/Api/UsersController.php +++ b/app/Http/Controllers/Api/UsersController.php @@ -363,8 +363,12 @@ class UsersController extends Controller $user->permissions = $permissions_array; } - $tmp_pass = substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 40); - $user->password = bcrypt($request->get('password', $tmp_pass)); + // + if ($request->filled('password')) { + $user->password = bcrypt($request->get('password')); + } else { + $user->password = $user->noPassword(); + } app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar'); diff --git a/app/Http/Controllers/AssetModelsController.php b/app/Http/Controllers/AssetModelsController.php index 7b1f3c49ba..3bb72413b0 100755 --- a/app/Http/Controllers/AssetModelsController.php +++ b/app/Http/Controllers/AssetModelsController.php @@ -76,6 +76,7 @@ class AssetModelsController extends Controller $model->depreciation_id = $request->input('depreciation_id'); $model->name = $request->input('name'); $model->model_number = $request->input('model_number'); + $model->min_amt = $request->input('min_amt'); $model->manufacturer_id = $request->input('manufacturer_id'); $model->category_id = $request->input('category_id'); $model->notes = $request->input('notes'); @@ -153,6 +154,7 @@ class AssetModelsController extends Controller $model->eol = $request->input('eol'); $model->name = $request->input('name'); $model->model_number = $request->input('model_number'); + $model->min_amt = $request->input('min_amt'); $model->manufacturer_id = $request->input('manufacturer_id'); $model->category_id = $request->input('category_id'); $model->notes = $request->input('notes'); diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 011184881c..319ebd0418 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -191,9 +191,11 @@ class LoginController extends Controller $ldap_attr = Ldap::parseAndMapLdapAttributes($ldap_user); + $user->password = $user->noPassword(); if (Setting::getSettings()->ldap_pw_sync=='1') { $user->password = bcrypt($request->input('password')); } + $user->email = $ldap_attr['email']; $user->first_name = $ldap_attr['firstname']; $user->last_name = $ldap_attr['lastname']; //FIXME (or TODO?) - do we need to map additional fields that we now support? E.g. country, phone, etc. diff --git a/app/Http/Controllers/Licenses/LicenseCheckinController.php b/app/Http/Controllers/Licenses/LicenseCheckinController.php index 50e20c7985..53c9a2f611 100644 --- a/app/Http/Controllers/Licenses/LicenseCheckinController.php +++ b/app/Http/Controllers/Licenses/LicenseCheckinController.php @@ -128,6 +128,13 @@ class LicenseCheckinController extends Controller $license = License::findOrFail($licenseId); $this->authorize('checkin', $license); + if (! $license->reassignable) { + // Not allowed to checkin + Session::flash('error', 'License not reassignable.'); + + return redirect()->back()->withInput(); + } + $licenseSeatsByUser = LicenseSeat::where('license_id', '=', $licenseId) ->whereNotNull('assigned_to') ->with('user') diff --git a/app/Http/Controllers/ReportsController.php b/app/Http/Controllers/ReportsController.php index 6bb7aa35cf..bacc27f423 100644 --- a/app/Http/Controllers/ReportsController.php +++ b/app/Http/Controllers/ReportsController.php @@ -1021,7 +1021,12 @@ class ReportsController extends Controller $assetsForReport = $acceptances ->filter(function ($acceptance) { - return $acceptance->checkoutable_type == 'App\Models\Asset' && $acceptance->checkoutable->checkedOutToUser(); + $acceptance_checkoutable_flag = false; + if ($acceptance->checkoutable){ + $acceptance_checkoutable_flag = $acceptance->checkoutable->checkedOutToUser(); + } + + return $acceptance->checkoutable_type == 'App\Models\Asset' && $acceptance_checkoutable_flag; }) ->map(function($acceptance) { return ['assetItem' => $acceptance->checkoutable, 'acceptance' => $acceptance]; diff --git a/app/Http/Transformers/ActionlogsTransformer.php b/app/Http/Transformers/ActionlogsTransformer.php index 4ab7ae02ee..ecabf086e8 100644 --- a/app/Http/Transformers/ActionlogsTransformer.php +++ b/app/Http/Transformers/ActionlogsTransformer.php @@ -68,8 +68,8 @@ class ActionlogsTransformer } } - $clean_meta = $this->changedInfo($clean_meta); } + $clean_meta= $this->changedInfo($clean_meta); } $file_url = ''; @@ -227,4 +227,4 @@ class ActionlogsTransformer -} +} \ No newline at end of file diff --git a/app/Http/Transformers/AssetModelsTransformer.php b/app/Http/Transformers/AssetModelsTransformer.php index f3a658c872..ff0a13048b 100644 --- a/app/Http/Transformers/AssetModelsTransformer.php +++ b/app/Http/Transformers/AssetModelsTransformer.php @@ -47,6 +47,7 @@ class AssetModelsTransformer ] : null, 'image' => ($assetmodel->image != '') ? Storage::disk('public')->url('models/'.e($assetmodel->image)) : null, 'model_number' => e($assetmodel->model_number), + 'min_amt' => ($assetmodel->min_amt) ? (int) $assetmodel->min_amt : null, 'depreciation' => ($assetmodel->depreciation) ? [ 'id' => (int) $assetmodel->depreciation->id, 'name'=> e($assetmodel->depreciation->name), diff --git a/app/Listeners/CheckoutableListener.php b/app/Listeners/CheckoutableListener.php index 09cb3ae8f2..d9680f82a1 100644 --- a/app/Listeners/CheckoutableListener.php +++ b/app/Listeners/CheckoutableListener.php @@ -79,13 +79,15 @@ class CheckoutableListener /** * Send the appropriate notification */ - $acceptances = CheckoutAcceptance::where('checkoutable_id', $event->checkoutable->id) - ->where('assigned_to_id', $event->checkedOutTo->id) - ->get(); + if ($event->checkedOutTo && $event->checkoutable){ + $acceptances = CheckoutAcceptance::where('checkoutable_id', $event->checkoutable->id) + ->where('assigned_to_id', $event->checkedOutTo->id) + ->get(); - foreach($acceptances as $acceptance){ - if($acceptance->isPending()){ - $acceptance->delete(); + foreach($acceptances as $acceptance){ + if($acceptance->isPending()){ + $acceptance->delete(); + } } } @@ -142,9 +144,11 @@ class CheckoutableListener $notifiables = collect(); /** - * Notify the user who checked out the item + * Notify who checked out the item as long as the model can route notifications */ - $notifiables->push($event->checkedOutTo); + if (method_exists($event->checkedOutTo, 'routeNotificationFor')) { + $notifiables->push($event->checkedOutTo); + } /** * Notify Admin users if the settings is activated diff --git a/app/Models/AssetModel.php b/app/Models/AssetModel.php index aed1b925ef..e0b056fc9f 100755 --- a/app/Models/AssetModel.php +++ b/app/Models/AssetModel.php @@ -29,6 +29,7 @@ class AssetModel extends SnipeModel protected $rules = [ 'name' => 'required|min:1|max:255', 'model_number' => 'max:255|nullable', + 'min_amt' => 'integer|min:0|nullable', 'category_id' => 'required|integer|exists:categories,id', 'manufacturer_id' => 'integer|exists:manufacturers,id|nullable', 'eol' => 'integer:min:0|max:240|nullable', @@ -65,6 +66,7 @@ class AssetModel extends SnipeModel 'fieldset_id', 'image', 'manufacturer_id', + 'min_amt', 'model_number', 'name', 'notes', diff --git a/app/Models/Department.php b/app/Models/Department.php index 90fde79df4..62755d2aa0 100644 --- a/app/Models/Department.php +++ b/app/Models/Department.php @@ -9,6 +9,7 @@ use Watson\Validating\ValidatingTrait; class Department extends SnipeModel { + use CompanyableTrait; use HasFactory; /** diff --git a/app/Models/Ldap.php b/app/Models/Ldap.php index 4eb496a2ab..ae1f163dda 100644 --- a/app/Models/Ldap.php +++ b/app/Models/Ldap.php @@ -252,13 +252,10 @@ class Ldap extends Model $user->last_name = $item['lastname']; $user->username = $item['username']; $user->email = $item['email']; + $user->password = $user->noPassword(); if (Setting::getSettings()->ldap_pw_sync == '1') { - $user->password = bcrypt($password); - } else { - $pass = substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 25); - $user->password = bcrypt($pass); } $user->activated = 1; @@ -268,7 +265,7 @@ class Ldap extends Model if ($user->save()) { return $user; } else { - LOG::debug('Could not create user.'.$user->getErrors()); + \Log::debug('Could not create user.'.$user->getErrors()); throw new Exception('Could not create user: '.$user->getErrors()); } } diff --git a/app/Models/SCIMUser.php b/app/Models/SCIMUser.php index 71bd9169ae..fcf34c0d5d 100644 --- a/app/Models/SCIMUser.php +++ b/app/Models/SCIMUser.php @@ -9,8 +9,7 @@ class SCIMUser extends User protected $throwValidationExceptions = true; // we want model-level validation to fully THROW, not just return false public function __construct(array $attributes = []) { - $attributes['password'] = "*NO PASSWORD*"; - // $attributes['activated'] = 1; + $attributes['password'] = $this->noPassword(); parent::__construct($attributes); } } \ No newline at end of file diff --git a/app/Models/User.php b/app/Models/User.php index 70c9fc44ae..8011f94ff4 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -456,6 +456,22 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo return $this->belongsToMany(Asset::class, 'checkout_requests', 'user_id', 'requestable_id')->whereNull('canceled_at'); } + /** + * Set a common string when the user has been imported/synced from: + * + * - LDAP without password syncing + * - SCIM + * - CSV import where no password was provided + * + * @author A. Gianotto + * @since [v6.2.0] + * @return string + */ + public function noPassword() + { + return "*** NO PASSWORD ***"; + } + /** * Query builder scope to return NOT-deleted users diff --git a/app/Presenters/AssetModelPresenter.php b/app/Presenters/AssetModelPresenter.php index 8da192ebb2..e661b1ab80 100644 --- a/app/Presenters/AssetModelPresenter.php +++ b/app/Presenters/AssetModelPresenter.php @@ -65,6 +65,14 @@ class AssetModelPresenter extends Presenter 'title' => trans('admin/models/table.modelnumber'), 'visible' => true, ], + [ + 'field' => 'min_amt', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('mail.min_QTY'), + 'visible' => true, + ], [ 'field' => 'assets_count', 'searchable' => false, diff --git a/database/migrations/2023_08_21_181742_add_min_amt_to_models_table.php b/database/migrations/2023_08_21_181742_add_min_amt_to_models_table.php new file mode 100644 index 0000000000..5f0bb85f9b --- /dev/null +++ b/database/migrations/2023_08_21_181742_add_min_amt_to_models_table.php @@ -0,0 +1,32 @@ +integer('min_amt')->after('model_number')->default(null);; + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('models', function (Blueprint $table) { + $table->dropColumn('min_amt'); + }); + } +} diff --git a/database/migrations/2023_09_13_200913_fix_asset_model_min_qty_nullability.php b/database/migrations/2023_09_13_200913_fix_asset_model_min_qty_nullability.php new file mode 100644 index 0000000000..c3b3d1e0d7 --- /dev/null +++ b/database/migrations/2023_09_13_200913_fix_asset_model_min_qty_nullability.php @@ -0,0 +1,32 @@ +integer('min_amt')->nullable()->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('models', function (Blueprint $table) { + $table->integer('min_amt')->nullable(false)->change(); + }); + } +} diff --git a/resources/assets/js/snipeit.js b/resources/assets/js/snipeit.js index 70e4f80a8f..a834e22ba0 100755 --- a/resources/assets/js/snipeit.js +++ b/resources/assets/js/snipeit.js @@ -454,12 +454,18 @@ $(document).ready(function () { $('#assigned_location').hide(); $('.notification-callout').fadeOut(); + $('[name="assigned_location"]').val('').trigger('change.select2'); + $('[name="assigned_user"]').val('').trigger('change.select2'); + } else if (assignto_type == 'location') { $('#current_assets_box').fadeOut(); $('#assigned_asset').hide(); $('#assigned_user').hide(); $('#assigned_location').show(); $('.notification-callout').fadeOut(); + + $('[name="assigned_asset"]').val('').trigger('change.select2'); + $('[name="assigned_user"]').val('').trigger('change.select2'); } else { $('#assigned_asset').hide(); @@ -470,6 +476,8 @@ $(document).ready(function () { } $('.notification-callout').fadeIn(); + $('[name="assigned_asset"]').val('').trigger('change.select2'); + $('[name="assigned_location"]').val('').trigger('change.select2'); } }); }); diff --git a/resources/lang/en/admin/licenses/general.php b/resources/lang/en/admin/licenses/general.php index 0187d076a3..b2766d063e 100644 --- a/resources/lang/en/admin/licenses/general.php +++ b/resources/lang/en/admin/licenses/general.php @@ -26,6 +26,7 @@ return array( 'modal' => 'This will action checkin one seat. | This action will checkin all :checkedout_seats_count seats for this license.', 'enabled_tooltip' => 'Checkin ALL seats for this license from both users and assets', 'disabled_tooltip' => 'This is disabled because there are no seats currently checked out', + 'disabled_tooltip_reassignable' => 'This is disabled because the License is not reassignable', 'success' => 'License successfully checked in! | All licenses were successfully checked in!', 'log_msg' => 'Checked in via bulk license checkout in license GUI', ], diff --git a/resources/views/licenses/view.blade.php b/resources/views/licenses/view.blade.php index f25076e714..f35a9e21c1 100755 --- a/resources/views/licenses/view.blade.php +++ b/resources/views/licenses/view.blade.php @@ -582,17 +582,23 @@ @endcan @can('checkin', $license) - - @if (($license->seats - $license->availCount()->count()) > 0 ) - - {{ trans('admin/licenses/general.bulk.checkin_all.button') }} - - @else + + @if (($license->seats - $license->availCount()->count()) <= 0 ) {{ trans('admin/licenses/general.bulk.checkin_all.button') }} - + + @elseif (! $license->reassignable) + + + {{ trans('admin/licenses/general.bulk.checkin_all.button') }} + + + @else + + {{ trans('admin/licenses/general.bulk.checkin_all.button') }} + @endif @endcan diff --git a/resources/views/models/edit.blade.php b/resources/views/models/edit.blade.php index 6145ef094b..14a7e839e1 100755 --- a/resources/views/models/edit.blade.php +++ b/resources/views/models/edit.blade.php @@ -15,6 +15,7 @@ @include ('partials.forms.edit.manufacturer-select', ['translated_name' => trans('general.manufacturer'), 'fieldname' => 'manufacturer_id']) @include ('partials.forms.edit.model_number') @include ('partials.forms.edit.depreciation') +@include ('partials.forms.edit.minimum_quantity') diff --git a/tests/Feature/Api/Departments/DepartmentIndexTest.php b/tests/Feature/Api/Departments/DepartmentIndexTest.php new file mode 100644 index 0000000000..1a3884308f --- /dev/null +++ b/tests/Feature/Api/Departments/DepartmentIndexTest.php @@ -0,0 +1,94 @@ +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); + } +} diff --git a/tests/Feature/Api/Users/UsersUpdateTest.php b/tests/Feature/Api/Users/UsersUpdateTest.php index 9337080c42..953a671cf1 100644 --- a/tests/Feature/Api/Users/UsersUpdateTest.php +++ b/tests/Feature/Api/Users/UsersUpdateTest.php @@ -56,32 +56,32 @@ class UsersUpdateTest extends TestCase ->assertOk(); $user->refresh(); - $this->assertEquals('Mabel', $user->first_name); - $this->assertEquals('Mora', $user->last_name); - $this->assertEquals('mabel', $user->username); - $this->assertTrue(Hash::check('super-secret', $user->password)); - $this->assertEquals('mabel@onlymurderspod.com', $user->email); - $this->assertArrayHasKey('a.new.permission', $user->decodePermissions()); - $this->assertTrue($user->activated); - $this->assertEquals('619-555-5555', $user->phone); - $this->assertEquals('Host', $user->jobtitle); - $this->assertTrue($user->manager->is($manager)); - $this->assertEquals('1111', $user->employee_num); - $this->assertEquals('Pretty good artist', $user->notes); - $this->assertTrue($user->company->is($company)); - $this->assertTrue($user->department->is($department)); - $this->assertTrue($user->location->is($location)); - $this->assertEquals(1, $user->remote); - $this->assertTrue($user->groups->contains($groupA)); - $this->assertTrue($user->vip); - $this->assertEquals('2021-08-01', $user->start_date); - $this->assertEquals('2025-12-31', $user->end_date); + $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)); - $this->assertTrue($user->groups->contains($groupB)); + $this->assertTrue($user->groups->contains($groupA), 'Not part of expected group'); + $this->assertTrue($user->groups->contains($groupB), 'Not part of expected group'); } }