Merge branch 'develop' into snipeit_v7_laravel10

This commit is contained in:
Brady Wetherington 2023-08-31 13:07:06 +01:00
commit 3500217f94
26 changed files with 299 additions and 55 deletions

73
.github/workflows/tests.yml vendored Normal file
View file

@ -0,0 +1,73 @@
name: Tests
on:
push:
branches:
- master
- develop
pull_request:
jobs:
tests:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:5.7
env:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: snipeit
ports:
- 33306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
strategy:
fail-fast: false
matrix:
php-version:
- "7.4"
- "8.0"
- "8.1.1"
name: PHP ${{ matrix.php-version }}
steps:
- uses: shivammathur/setup-php@v2
with:
php-version: "${{ matrix.php-version }}"
coverage: none
- uses: actions/checkout@v3
- name: Get Composer Cache Directory
id: composer-cache
run: |
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-composer-
- name: Copy .env
run: |
cp -v .env.testing.example .env
cp -v .env.testing.example .env.testing
- name: Install Dependencies
run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
- name: Generate key
run: php artisan key:generate
- name: Directory Permissions
run: chmod -R 777 storage bootstrap/cache
- name: Execute tests (Unit and Feature tests) via PHPUnit
env:
DB_CONNECTION: mysql
DB_DATABASE: snipeit
DB_PORT: ${{ job.services.mysql.ports[3306] }}
DB_USERNAME: root
run: php artisan test --parallel

View file

@ -75,7 +75,6 @@ class UsersController extends Controller
])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy',) ])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy',)
->withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count'); ->withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count');
$users = Company::scopeCompanyables($users);
if ($request->filled('activated')) { if ($request->filled('activated')) {
@ -272,6 +271,8 @@ class UsersController extends Controller
$users = $users->withTrashed(); $users = $users->withTrashed();
} }
$users = Company::scopeCompanyables($users);
$total = $users->count(); $total = $users->count();
$users = $users->skip($offset)->take($limit)->get(); $users = $users->skip($offset)->take($limit)->get();

View file

@ -286,6 +286,7 @@ class AssetModelsController extends Controller
return view('models/edit') return view('models/edit')
->with('depreciation_list', Helper::depreciationList()) ->with('depreciation_list', Helper::depreciationList())
->with('item', $model) ->with('item', $model)
->with('model_id', $model_to_clone->id)
->with('clone_model', $model_to_clone); ->with('clone_model', $model_to_clone);
} }

View file

@ -82,7 +82,6 @@ class Asset extends Depreciable
'location_id' => 'integer', 'location_id' => 'integer',
'rtd_company_id' => 'integer', 'rtd_company_id' => 'integer',
'supplier_id' => 'integer', 'supplier_id' => 'integer',
'byod' => 'boolean',
'created_at' => 'datetime', 'created_at' => 'datetime',
'updated_at' => 'datetime', 'updated_at' => 'datetime',
'deleted_at' => 'datetime', 'deleted_at' => 'datetime',
@ -105,6 +104,7 @@ class Asset extends Depreciable
'purchase_cost' => 'numeric|nullable|gte:0', 'purchase_cost' => 'numeric|nullable|gte:0',
'supplier_id' => 'exists:suppliers,id|nullable', 'supplier_id' => 'exists:suppliers,id|nullable',
'asset_eol_date' => 'date|max:10|min:10|nullable', 'asset_eol_date' => 'date|max:10|min:10|nullable',
'byod' => 'boolean',
]; ];
/** /**

View file

@ -69,15 +69,12 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
]; ];
protected $casts = [ protected $casts = [
'activated' => 'boolean',
'manager_id' => 'integer', 'manager_id' => 'integer',
'location_id' => 'integer', 'location_id' => 'integer',
'company_id' => 'integer', 'company_id' => 'integer',
'vip' => 'boolean',
'created_at' => 'datetime', 'created_at' => 'datetime',
'updated_at' => 'datetime', 'updated_at' => 'datetime',
'deleted_at' => 'datetime', 'deleted_at' => 'datetime',
'autoassign_licenses' => 'boolean',
]; ];
/** /**
@ -103,6 +100,9 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
'state' => 'min:2|max:191|nullable', 'state' => 'min:2|max:191|nullable',
'country' => 'min:2|max:191|nullable', 'country' => 'min:2|max:191|nullable',
'zip' => 'max:10|nullable', 'zip' => 'max:10|nullable',
'vip' => 'boolean',
'remote' => 'boolean',
'activated' => 'boolean',
]; ];
/** /**

View file

@ -69,7 +69,7 @@ return [
'mysql' => [ 'mysql' => [
'driver' => 'mysql', 'driver' => 'mysql',
'host' => env('DB_HOST', 'localhost'), 'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', '3306'), 'port' => env('DB_PORT', 3306),
'database' => env('DB_DATABASE', 'forge'), 'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'), 'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''), 'password' => env('DB_PASSWORD', ''),

View file

@ -1,10 +1,10 @@
<?php <?php
return array ( return array (
'app_version' => 'v6.1.2', 'app_version' => 'v6.2.0-pre',
'full_app_version' => 'v6.1.2 - build 10938-g32747cafd', 'full_app_version' => 'v6.2.0-pre - build 11391-g319cb2305',
'build_version' => '10938', 'build_version' => '11391',
'prerelease_version' => '', 'prerelease_version' => '',
'hash_version' => 'g32747cafd', 'hash_version' => 'g319cb2305',
'full_hash' => 'v6.1.2-89-g32747cafd', 'full_hash' => 'v6.2.0-pre-451-g319cb2305',
'branch' => 'develop', 'branch' => 'develop',
); );

20
package-lock.json generated
View file

@ -5,7 +5,7 @@
"packages": { "packages": {
"": { "": {
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^6.4.0", "@fortawesome/fontawesome-free": "^6.4.2",
"acorn": "^8.9.0", "acorn": "^8.9.0",
"acorn-import-assertions": "^1.9.0", "acorn-import-assertions": "^1.9.0",
"admin-lte": "^2.4.18", "admin-lte": "^2.4.18",
@ -26,7 +26,7 @@
"jquery-ui-bundle": "^1.12.1", "jquery-ui-bundle": "^1.12.1",
"jquery.iframe-transport": "^1.0.0", "jquery.iframe-transport": "^1.0.0",
"jspdf-autotable": "^3.5.30", "jspdf-autotable": "^3.5.30",
"less": "^4.1.2", "less": "^4.2.0",
"less-loader": "^6.0", "less-loader": "^6.0",
"list.js": "^1.5.0", "list.js": "^1.5.0",
"morris.js": "github:morrisjs/morris.js", "morris.js": "github:morrisjs/morris.js",
@ -1810,9 +1810,10 @@
} }
}, },
"node_modules/@fortawesome/fontawesome-free": { "node_modules/@fortawesome/fontawesome-free": {
"version": "6.4.0", "version": "6.4.2",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.4.2.tgz",
"integrity": "sha512-m5cPn3e2+FDCOgi1mz0RexTUvvQibBebOUlUlW0+YrMjDTPkiJ6VTKukA1GRsvRw+12KyJndNjj0O4AgTxm2Pg==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
} }
@ -2620,9 +2621,9 @@
} }
}, },
"node_modules/alpinejs": { "node_modules/alpinejs": {
"version": "3.12.3", "version": "3.13.0",
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.12.3.tgz", "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.0.tgz",
"integrity": "sha512-fLz2dfYQ3xCk7Ip8LiIpV2W+9brUyex2TAE7Z0BCvZdUDklJE+n+a8gCgLWzfZ0GzZNZu7HUP8Z0z6Xbm6fsSA==", "integrity": "sha512-7FYR1Yz3evIjlJD1mZ3SYWSw+jlOmQGeQ1QiSufSQ6J84XMQFkzxm6OobiZ928SfqhGdoIp2SsABNsS4rXMMJw==",
"dependencies": { "dependencies": {
"@vue/reactivity": "~3.1.1" "@vue/reactivity": "~3.1.1"
} }
@ -7457,8 +7458,9 @@
} }
}, },
"node_modules/less": { "node_modules/less": {
"version": "4.1.3", "version": "4.2.0",
"license": "Apache-2.0", "resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz",
"integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==",
"dependencies": { "dependencies": {
"copy-anything": "^2.0.1", "copy-anything": "^2.0.1",
"parse-node-version": "^1.0.1", "parse-node-version": "^1.0.1",

View file

@ -24,7 +24,7 @@
"vue-template-compiler": "2.4.4" "vue-template-compiler": "2.4.4"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^6.4.0", "@fortawesome/fontawesome-free": "^6.4.2",
"acorn": "^8.9.0", "acorn": "^8.9.0",
"acorn-import-assertions": "^1.9.0", "acorn-import-assertions": "^1.9.0",
"admin-lte": "^2.4.18", "admin-lte": "^2.4.18",
@ -45,7 +45,7 @@
"jquery-ui-bundle": "^1.12.1", "jquery-ui-bundle": "^1.12.1",
"jquery.iframe-transport": "^1.0.0", "jquery.iframe-transport": "^1.0.0",
"jspdf-autotable": "^3.5.30", "jspdf-autotable": "^3.5.30",
"less": "^4.1.2", "less": "^4.2.0",
"less-loader": "^6.0", "less-loader": "^6.0",
"list.js": "^1.5.0", "list.js": "^1.5.0",
"morris.js": "github:morrisjs/morris.js", "morris.js": "github:morrisjs/morris.js",

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -18,22 +18,22 @@
"/css/dist/skins/skin-green.css": "/css/dist/skins/skin-green.css?id=b48f4d8af0e1ca5621c161e93951109f", "/css/dist/skins/skin-green.css": "/css/dist/skins/skin-green.css?id=b48f4d8af0e1ca5621c161e93951109f",
"/css/dist/skins/skin-contrast.css": "/css/dist/skins/skin-contrast.css?id=f0fbbb0ac729ea092578fb05ca615460", "/css/dist/skins/skin-contrast.css": "/css/dist/skins/skin-contrast.css?id=f0fbbb0ac729ea092578fb05ca615460",
"/css/dist/skins/skin-red.css": "/css/dist/skins/skin-red.css?id=b9a74ec0cd68f83e7480d5ae39919beb", "/css/dist/skins/skin-red.css": "/css/dist/skins/skin-red.css?id=b9a74ec0cd68f83e7480d5ae39919beb",
"/css/dist/all.css": "/css/dist/all.css?id=c5f8982eb14ba2c7e0c0fec60519afc6", "/css/dist/all.css": "/css/dist/all.css?id=829410b57e28448fef5c57beda0861e3",
"/css/dist/signature-pad.css": "/css/dist/signature-pad.css?id=6a89d3cd901305e66ced1cf5f13147f7", "/css/dist/signature-pad.css": "/css/dist/signature-pad.css?id=6a89d3cd901305e66ced1cf5f13147f7",
"/css/dist/signature-pad.min.css": "/css/dist/signature-pad.min.css?id=6a89d3cd901305e66ced1cf5f13147f7", "/css/dist/signature-pad.min.css": "/css/dist/signature-pad.min.css?id=6a89d3cd901305e66ced1cf5f13147f7",
"/css/webfonts/fa-brands-400.ttf": "/css/webfonts/fa-brands-400.ttf?id=e2e2b1797606a266ed55549f5bb5a179", "/css/webfonts/fa-brands-400.ttf": "/css/webfonts/fa-brands-400.ttf?id=a656b2d865fe379d8851757e8e4001ef",
"/css/webfonts/fa-brands-400.woff2": "/css/webfonts/fa-brands-400.woff2?id=fe912d1c4a7e0e1db87a64eb7e54c945", "/css/webfonts/fa-brands-400.woff2": "/css/webfonts/fa-brands-400.woff2?id=a6761c03f9c021aeb9754283755b660e",
"/css/webfonts/fa-regular-400.ttf": "/css/webfonts/fa-regular-400.ttf?id=31a6b5ecfc8d018d0e3a294f0c80e9e9", "/css/webfonts/fa-regular-400.ttf": "/css/webfonts/fa-regular-400.ttf?id=75a44676b684c784508452a53867e8da",
"/css/webfonts/fa-regular-400.woff2": "/css/webfonts/fa-regular-400.woff2?id=bf8eabe300a00a3adb0293596987abc4", "/css/webfonts/fa-regular-400.woff2": "/css/webfonts/fa-regular-400.woff2?id=c612044da7f7b81ab8f2cadf94964208",
"/css/webfonts/fa-solid-900.ttf": "/css/webfonts/fa-solid-900.ttf?id=cd687455c6d6c058e2e9f84f409e2965", "/css/webfonts/fa-solid-900.ttf": "/css/webfonts/fa-solid-900.ttf?id=3867ccc7ab681d3c27d1e97a792b46d3",
"/css/webfonts/fa-solid-900.woff2": "/css/webfonts/fa-solid-900.woff2?id=eea38615e7b5dbbaf88c263f2230cc32", "/css/webfonts/fa-solid-900.woff2": "/css/webfonts/fa-solid-900.woff2?id=7f63d634454e771396bce3e09dfcdbc5",
"/css/webfonts/fa-v4compatibility.ttf": "/css/webfonts/fa-v4compatibility.ttf?id=6ebbf5afc34f54463abc2b81ca637364", "/css/webfonts/fa-v4compatibility.ttf": "/css/webfonts/fa-v4compatibility.ttf?id=70ad875b2378eb850254f01dec991ade",
"/css/webfonts/fa-v4compatibility.woff2": "/css/webfonts/fa-v4compatibility.woff2?id=67b8a78b7e80e805cfa4ee0421895ba4", "/css/webfonts/fa-v4compatibility.woff2": "/css/webfonts/fa-v4compatibility.woff2?id=d36941873b661076f146b0221f13497d",
"/css/dist/bootstrap-table.css": "/css/dist/bootstrap-table.css?id=281bcfe26549412d128f695234961081", "/css/dist/bootstrap-table.css": "/css/dist/bootstrap-table.css?id=281bcfe26549412d128f695234961081",
"/js/build/vendor.js": "/js/build/vendor.js?id=8ac1d250496313e93744790e5138305d", "/js/build/vendor.js": "/js/build/vendor.js?id=8ac1d250496313e93744790e5138305d",
"/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=df78f0c4cc93c29c02a41144590f6350", "/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=df78f0c4cc93c29c02a41144590f6350",
"/js/dist/all.js": "/js/dist/all.js?id=b487452bd6bef7fa6201586415d383f2", "/js/dist/all.js": "/js/dist/all.js?id=b487452bd6bef7fa6201586415d383f2",
"/js/dist/all-defer.js": "/js/dist/all-defer.js?id=7829a391ab2f89926465398bebb7df8d", "/js/dist/all-defer.js": "/js/dist/all-defer.js?id=07e52318da2cdf3171c4d88113f25fb6",
"/css/dist/skins/skin-green.min.css": "/css/dist/skins/skin-green.min.css?id=b48f4d8af0e1ca5621c161e93951109f", "/css/dist/skins/skin-green.min.css": "/css/dist/skins/skin-green.min.css?id=b48f4d8af0e1ca5621c161e93951109f",
"/css/dist/skins/skin-green-dark.min.css": "/css/dist/skins/skin-green-dark.min.css?id=44f9320d0739f419c9246f7f39395b02", "/css/dist/skins/skin-green-dark.min.css": "/css/dist/skins/skin-green-dark.min.css?id=44f9320d0739f419c9246f7f39395b02",
"/css/dist/skins/skin-black.min.css": "/css/dist/skins/skin-black.min.css?id=1f33ca3d860461c1127ec465ab3ebb6b", "/css/dist/skins/skin-black.min.css": "/css/dist/skins/skin-black.min.css?id=1f33ca3d860461c1127ec465ab3ebb6b",

View file

@ -15,15 +15,15 @@
<div class="modal-body"> <div class="modal-body">
<div class="row"> <div class="row">
<div class="col-md-3"> <div class="col-md-12">
<label class="btn btn-default"> <label class="btn btn-default btn-block">
{{ trans('button.select_file') }} {{ trans('button.select_files') }}
<input type="file" name="file[]" multiple="true" class="js-uploadFile" id="uploadFile" data-maxsize="{{ Helper::file_upload_max_size() }}" accept="image/*,.csv,.zip,.rar,.doc,.docx,.xls,.xlsx,.xml,.lic,.xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel,text/plain,.pdf,application/rtf,application/json" style="display:none" required> <input type="file" name="file[]" multiple="true" class="js-uploadFile" id="uploadFile" data-maxsize="{{ Helper::file_upload_max_size() }}" accept="image/*,.csv,.zip,.rar,.doc,.docx,.xls,.xlsx,.xml,.lic,.xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel,text/plain,.pdf,application/rtf,application/json" style="display:none" required>
</label> </label>
</div> </div>
<div class="col-md-9"> <div class="col-md-12">
<span id="uploadFile-info"></span> <span id="uploadFile-info"></span>
</div> </div>
<div class="col-md-12"> <div class="col-md-12">

View file

@ -34,7 +34,8 @@
</div> </div>
<!-- Custom Fieldset --> <!-- Custom Fieldset -->
@livewire('custom-field-set-default-values-for-model',["model_id" => $item->id]) <!-- If $item->id is null we are cloning the model and we need the $model_id variable -->
@livewire('custom-field-set-default-values-for-model',["model_id" => $item->id ?? $model_id ?? null ])
@include ('partials.forms.edit.notes') @include ('partials.forms.edit.notes')
@include ('partials.forms.edit.requestable', ['requestable_text' => trans('admin/models/general.requestable')]) @include ('partials.forms.edit.requestable', ['requestable_text' => trans('admin/models/general.requestable')])

View file

@ -18,6 +18,15 @@
.checkbox label { .checkbox label {
padding-right: 40px; padding-right: 40px;
} }
/*
Don't make the password field *look* readonly - this is for usability, so admins don't think they can't edit this field.
*/
.form-control[readonly] {
background-color: white;
color: #555555;
cursor:text;
}
</style> </style>
@if ((!function_exists('ldap_connect')) || (!function_exists('ldap_set_option')) || (!function_exists('ldap_bind'))) @if ((!function_exists('ldap_connect')) || (!function_exists('ldap_set_option')) || (!function_exists('ldap_bind')))
@ -34,10 +43,12 @@
@endif @endif
{{ Form::open(['method' => 'POST', 'files' => false, 'autocomplete' => 'false', 'class' => 'form-horizontal', 'role' => 'form']) }} {{ Form::open(['method' => 'POST', 'files' => false, 'autocomplete' => 'off', 'class' => 'form-horizontal', 'role' => 'form']) }}
<!-- CSRF Token --> <!-- CSRF Token -->
{{csrf_field()}} {{csrf_field()}}
<input type="hidden" name="username" value="{{ Request::old('username', $user->username) }}">
<!-- this is a hack to prevent Chrome from trying to autocomplete fields --> <!-- this is a hack to prevent Chrome from trying to autocomplete fields -->
<input type="text" name="prevent_autofill" id="prevent_autofill" value="" style="display:none;" /> <input type="text" name="prevent_autofill" id="prevent_autofill" value="" style="display:none;" />
<input type="password" name="password_fake" id="password_fake" value="" style="display:none;" /> <input type="password" name="password_fake" id="password_fake" value="" style="display:none;" />
@ -54,7 +65,6 @@
</div> </div>
<div class="box-body"> <div class="box-body">
<div class="col-md-11 col-md-offset-1"> <div class="col-md-11 col-md-offset-1">
<!-- Enable LDAP --> <!-- Enable LDAP -->
@ -230,7 +240,7 @@
{{ Form::label('ldap_uname', trans('admin/settings/general.ldap_uname')) }} {{ Form::label('ldap_uname', trans('admin/settings/general.ldap_uname')) }}
</div> </div>
<div class="col-md-8"> <div class="col-md-8">
{{ Form::text('ldap_uname', Request::old('ldap_uname', $setting->ldap_uname), ['class' => 'form-control','placeholder' => trans('general.example') .'binduser@example.com', $setting->demoMode]) }} {{ Form::text('ldap_uname', Request::old('ldap_uname', $setting->ldap_uname), ['class' => 'form-control','autocomplete' => 'off', 'placeholder' => trans('general.example') .'binduser@example.com', $setting->demoMode]) }}
{!! $errors->first('ldap_uname', '<span class="alert-msg" aria-hidden="true">:message</span>') !!} {!! $errors->first('ldap_uname', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
@if (config('app.lock_passwords')===true) @if (config('app.lock_passwords')===true)
<p class="text-warning"><i class="fas fa-lock" aria-hidden="true"></i> {{ trans('general.feature_disabled') }}</p> <p class="text-warning"><i class="fas fa-lock" aria-hidden="true"></i> {{ trans('general.feature_disabled') }}</p>
@ -244,7 +254,7 @@
{{ Form::label('ldap_pword', trans('admin/settings/general.ldap_pword')) }} {{ Form::label('ldap_pword', trans('admin/settings/general.ldap_pword')) }}
</div> </div>
<div class="col-md-8"> <div class="col-md-8">
{{ Form::password('ldap_pword', ['class' => 'form-control','placeholder' => trans('general.example') .' binduserpassword', $setting->demoMode]) }} {{ Form::password('ldap_pword', ['class' => 'form-control', 'autocomplete' => 'off', 'onfocus' => "this.removeAttribute('readonly');", $setting->demoMode, ' readonly']) }}
{!! $errors->first('ldap_pword', '<span class="alert-msg" aria-hidden="true">:message</span>') !!} {!! $errors->first('ldap_pword', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
@if (config('app.lock_passwords')===true) @if (config('app.lock_passwords')===true)
<p class="text-warning"><i class="fas fa-lock" aria-hidden="true"></i> {{ trans('general.feature_disabled') }}</p> <p class="text-warning"><i class="fas fa-lock" aria-hidden="true"></i> {{ trans('general.feature_disabled') }}</p>
@ -538,7 +548,7 @@
<input type="text" name="ldaptest_user" id="ldaptest_user" class="form-control" placeholder="LDAP username"> <input type="text" name="ldaptest_user" id="ldaptest_user" class="form-control" placeholder="LDAP username">
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<input type="password" name="ldaptest_password" id="ldaptest_password" class="form-control" placeholder="LDAP password"> <input type="password" name="ldaptest_password" id="ldaptest_password" class="form-control" placeholder="LDAP password" autocomplete="off" readonly onfocus="this.removeAttribute('readonly');">
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<a class="btn btn-default btn-sm" id="ldaptestlogin" style="margin-right: 10px;">{{ trans('admin/settings/general.ldap_test') }}</a> <a class="btn btn-default btn-sm" id="ldaptestlogin" style="margin-right: 10px;">{{ trans('admin/settings/general.ldap_test') }}</a>

View file

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

View file

@ -2,6 +2,7 @@
namespace Tests\Feature\Api\Users; namespace Tests\Feature\Api\Users;
use App\Models\Company;
use App\Models\User; use App\Models\User;
use Laravel\Passport\Passport; use Laravel\Passport\Passport;
use Tests\Support\InteractsWithSettings; use Tests\Support\InteractsWithSettings;
@ -83,4 +84,67 @@ class UsersSearchTest extends TestCase
'Expected deleted user does not appear in results' 'Expected deleted user does not appear in results'
); );
} }
public function testUsersScopedToCompanyWhenMultipleFullCompanySupportEnabled()
{
$this->settings->enableMultipleFullCompanySupport();
$companyA = Company::factory()
->has(User::factory(['first_name' => 'Company A', 'last_name' => 'User']))
->create();
Company::factory()
->has(User::factory(['first_name' => 'Company B', 'last_name' => 'User']))
->create();
$response = $this->actingAsForApi(User::factory()->for($companyA)->viewUsers()->create())
->getJson(route('api.users.index'))
->assertOk();
$results = collect($response->json('rows'));
$this->assertTrue(
$results->pluck('name')->contains(fn($text) => str_contains($text, 'Company A')),
'User index does not contain expected user'
);
$this->assertFalse(
$results->pluck('name')->contains(fn($text) => str_contains($text, 'Company B')),
'User index contains unexpected user from another company'
);
}
public function testUsersScopedToCompanyDuringSearchWhenMultipleFullCompanySupportEnabled()
{
$this->settings->enableMultipleFullCompanySupport();
$companyA = Company::factory()
->has(User::factory(['first_name' => 'Company A', 'last_name' => 'User']))
->create();
Company::factory()
->has(User::factory(['first_name' => 'Company B', 'last_name' => 'User']))
->create();
$response = $this->actingAsForApi(User::factory()->for($companyA)->viewUsers()->create())
->getJson(route('api.users.index', [
'deleted' => 'false',
'company_id' => null,
'search' => 'user',
'order' => 'asc',
'offset' => '0',
'limit' => '20',
]))
->assertOk();
$results = collect($response->json('rows'));
$this->assertTrue(
$results->pluck('name')->contains(fn($text) => str_contains($text, 'Company A')),
'User index does not contain expected user'
);
$this->assertFalse(
$results->pluck('name')->contains(fn($text) => str_contains($text, 'Company B')),
'User index contains unexpected user from another company'
);
}
} }

View file

@ -10,10 +10,10 @@ class UpdateUserTest extends TestCase
{ {
use InteractsWithSettings; use InteractsWithSettings;
public function testUsersCanBeActivated() public function testUsersCanBeActivatedWithNumber()
{ {
$admin = User::factory()->superuser()->create(); $admin = User::factory()->superuser()->create();
$user = User::factory()->create(['activated' => false]); $user = User::factory()->create(['activated' => 0]);
$this->actingAs($admin) $this->actingAs($admin)
->put(route('users.update', $user), [ ->put(route('users.update', $user), [
@ -22,10 +22,25 @@ class UpdateUserTest extends TestCase
'activated' => 1, 'activated' => 1,
]); ]);
$this->assertTrue($user->refresh()->activated); $this->assertEquals(1, $user->refresh()->activated);
} }
public function testUsersCanBeDeactivated() public function testUsersCanBeActivatedWithBooleanTrue()
{
$admin = User::factory()->superuser()->create();
$user = User::factory()->create(['activated' => false]);
$this->actingAs($admin)
->put(route('users.update', $user), [
'first_name' => $user->first_name,
'username' => $user->username,
'activated' => true,
]);
$this->assertEquals(1, $user->refresh()->activated);
}
public function testUsersCanBeDeactivatedWithNumber()
{ {
$admin = User::factory()->superuser()->create(); $admin = User::factory()->superuser()->create();
$user = User::factory()->create(['activated' => true]); $user = User::factory()->create(['activated' => true]);
@ -34,12 +49,25 @@ class UpdateUserTest extends TestCase
->put(route('users.update', $user), [ ->put(route('users.update', $user), [
'first_name' => $user->first_name, 'first_name' => $user->first_name,
'username' => $user->username, 'username' => $user->username,
// checkboxes that are not checked are 'activated' => 0,
// not included in the request payload
// 'activated' => 0,
]); ]);
$this->assertFalse($user->refresh()->activated); $this->assertEquals(0, $user->refresh()->activated);
}
public function testUsersCanBeDeactivatedWithBooleanFalse()
{
$admin = User::factory()->superuser()->create();
$user = User::factory()->create(['activated' => true]);
$this->actingAs($admin)
->put(route('users.update', $user), [
'first_name' => $user->first_name,
'username' => $user->username,
'activated' => false,
]);
$this->assertEquals(0, $user->refresh()->activated);
} }
public function testUsersUpdatingThemselvesDoNotDeactivateTheirAccount() public function testUsersUpdatingThemselvesDoNotDeactivateTheirAccount()
@ -50,12 +78,8 @@ class UpdateUserTest extends TestCase
->put(route('users.update', $admin), [ ->put(route('users.update', $admin), [
'first_name' => $admin->first_name, 'first_name' => $admin->first_name,
'username' => $admin->username, 'username' => $admin->username,
// checkboxes that are disabled are not
// included in the request payload
// even if they are checked
// 'activated' => 0,
]); ]);
$this->assertTrue($admin->refresh()->activated); $this->assertEquals(1, $admin->refresh()->activated);
} }
} }