diff --git a/src/app/Backends/LDAP.php b/src/app/Backends/LDAP.php --- a/src/app/Backends/LDAP.php +++ b/src/app/Backends/LDAP.php @@ -368,11 +368,9 @@ $entry['givenname'] = $firstName; $entry['sn'] = $lastName; $entry['userpassword'] = $user->password_ldap; - $entry['inetuserstatus'] = $user->status; - + $entry['o'] = $user->getSetting('organization'); $entry['mailquota'] = 0; - $entry['alias'] = $user->aliases->pluck('alias')->toArray(); $roles = []; diff --git a/src/app/Http/Controllers/API/SignupController.php b/src/app/Http/Controllers/API/SignupController.php --- a/src/app/Http/Controllers/API/SignupController.php +++ b/src/app/Http/Controllers/API/SignupController.php @@ -71,26 +71,33 @@ $request->all(), [ 'email' => 'required', - 'name' => 'required|max:512', + 'first_name' => 'max:128', + 'last_name' => 'max:128', 'plan' => 'nullable|alpha_num|max:128', 'voucher' => 'max:32', ] ); - if ($v->fails()) { - return response()->json(['status' => 'error', 'errors' => $v->errors()], 422); - } + $is_phone = false; + $errors = $v->fails() ? $v->errors()->toArray() : []; // Validate user email (or phone) - if ($error = $this->validatePhoneOrEmail($request->email, $is_phone)) { - return response()->json(['status' => 'error', 'errors' => ['email' => $error]], 422); + if (empty($errors['email'])) { + if ($error = $this->validatePhoneOrEmail($request->email, $is_phone)) { + $errors['email'] = $error; + } + } + + if (!empty($errors)) { + return response()->json(['status' => 'error', 'errors' => $errors], 422); } // Generate the verification code $code = SignupCode::create([ 'data' => [ 'email' => $request->email, - 'name' => $request->name, + 'first_name' => $request->first_name, + 'last_name' => $request->last_name, 'plan' => $request->plan, 'voucher' => $request->voucher, ] @@ -151,7 +158,8 @@ return response()->json([ 'status' => 'success', 'email' => $code->data['email'], - 'name' => $code->data['name'], + 'first_name' => $code->data['first_name'], + 'last_name' => $code->data['last_name'], 'voucher' => $code->data['voucher'], 'is_domain' => $has_domain, 'domains' => $has_domain ? [] : Domain::getPublicDomains(), @@ -213,7 +221,6 @@ // Get user name/email from the verification code database $code_data = $v->getData(); - $user_name = $code_data->name; $user_email = $code_data->email; // We allow only ASCII, so we can safely lower-case the email address @@ -224,7 +231,6 @@ // Create user record $user = User::create([ - 'name' => $user_name, 'email' => $login . '@' . $domain, 'password' => $request->password, ]); @@ -248,7 +254,11 @@ $user->assignPlan($plan, $domain); // Save the external email and plan in user settings - $user->setSetting('external_email', $user_email); + $user->setSettings([ + 'external_email' => $user_email, + 'first_name' => $code_data->first_name, + 'last_name' => $code_data->last_name, + ]); // Remove the verification code $this->code->delete(); diff --git a/src/app/Http/Controllers/API/V4/PaymentsController.php b/src/app/Http/Controllers/API/V4/PaymentsController.php --- a/src/app/Http/Controllers/API/V4/PaymentsController.php +++ b/src/app/Http/Controllers/API/V4/PaymentsController.php @@ -47,7 +47,7 @@ if (empty($customer_id)) { $customer = mollie()->customers()->create([ - 'name' => $current_user->name, + 'name' => $current_user->name(), 'email' => $current_user->email, ]); diff --git a/src/app/Http/Controllers/API/V4/UsersController.php b/src/app/Http/Controllers/API/V4/UsersController.php --- a/src/app/Http/Controllers/API/V4/UsersController.php +++ b/src/app/Http/Controllers/API/V4/UsersController.php @@ -16,6 +16,18 @@ class UsersController extends Controller { + // List of user settings keys available for modification in UI + public const USER_SETTINGS = [ + 'billing_address', + 'country', + 'currency', + 'external_email', + 'first_name', + 'last_name', + 'organization', + 'phone', + ]; + /** * Delete a user. * @@ -178,16 +190,10 @@ return response()->json(['status' => 'error', 'errors' => $errors], 422); } - $user_name = !empty($settings['first_name']) ? $settings['first_name'] : ''; - if (!empty($settings['last_name'])) { - $user_name .= ' ' . $settings['last_name']; - } - DB::beginTransaction(); // Create user record $user = User::create([ - 'name' => $user_name, 'email' => $request->email, 'password' => $request->password, ]); @@ -342,10 +348,8 @@ $response = $user->toArray(); // Settings - // TODO: It might be reasonable to limit the list of settings here to these - // that are safe and are used in the UI $response['settings'] = []; - foreach ($user->settings as $item) { + foreach ($user->settings()->whereIn('key', self::USER_SETTINGS)->get() as $item) { $response['settings'][$item->key] = $item->value; } @@ -416,8 +420,9 @@ $rules = [ 'external_email' => 'nullable|email', 'phone' => 'string|nullable|max:64|regex:/^[0-9+() -]+$/', - 'first_name' => 'string|nullable|max:512', - 'last_name' => 'string|nullable|max:512', + 'first_name' => 'string|nullable|max:128', + 'last_name' => 'string|nullable|max:128', + 'organization' => 'string|nullable|max:512', 'billing_address' => 'string|nullable|max:1024', 'country' => 'string|nullable|alpha|size:2', 'currency' => 'string|nullable|alpha|size:3', diff --git a/src/app/Mail/PasswordReset.php b/src/app/Mail/PasswordReset.php --- a/src/app/Mail/PasswordReset.php +++ b/src/app/Mail/PasswordReset.php @@ -49,7 +49,7 @@ 'code' => $this->code->code, 'short_code' => $this->code->short_code, 'link' => sprintf('%s', $href, $href), - 'username' => $this->code->user->name + 'username' => $this->code->user->name(true) ]); return $this; diff --git a/src/app/Observers/UserObserver.php b/src/app/Observers/UserObserver.php --- a/src/app/Observers/UserObserver.php +++ b/src/app/Observers/UserObserver.php @@ -46,17 +46,30 @@ */ public function created(User $user) { - // FIXME: Actual proper settings - $user->setSettings( - [ - 'country' => 'CH', - 'currency' => 'CHF', - 'first_name' => '', - 'last_name' => '', - 'billing_address' => '', - 'organization' => '' - ] - ); + $settings = [ + 'country' => 'CH', + 'currency' => 'CHF', + /* + 'first_name' => '', + 'last_name' => '', + 'billing_address' => '', + 'organization' => '', + 'phone' => '', + 'external_email' => '', + */ + ]; + + foreach ($settings as $key => $value) { + $settings[$key] = [ + 'key' => $key, + 'value' => $value, + 'user_id' => $user->id, + ]; + } + + // Note: Don't use setSettings() here to bypass UserSetting observers + // Note: This is a single multi-insert query + $user->settings()->insert(array_values($settings)); $user->wallets()->create(); diff --git a/src/app/Observers/UserSettingObserver.php b/src/app/Observers/UserSettingObserver.php --- a/src/app/Observers/UserSettingObserver.php +++ b/src/app/Observers/UserSettingObserver.php @@ -9,18 +9,20 @@ /** * Handle the user setting "created" event. * - * @param \App\UserSetting $userSetting + * @param \App\UserSetting $userSetting Settings object + * * @return void */ public function created(UserSetting $userSetting) { - // + \App\Jobs\UserUpdate::dispatch($userSetting->user); } /** * Handle the user setting "updated" event. * - * @param \App\UserSetting $userSetting + * @param \App\UserSetting $userSetting Settings object + * * @return void */ public function updated(UserSetting $userSetting) @@ -31,33 +33,12 @@ /** * Handle the user setting "deleted" event. * - * @param \App\UserSetting $userSetting - * @return void - */ - public function deleted(UserSetting $userSetting) - { - // - } - - /** - * Handle the user setting "restored" event. + * @param \App\UserSetting $userSetting Settings object * - * @param \App\UserSetting $userSetting * @return void */ - public function restored(UserSetting $userSetting) - { - // - } - - /** - * Handle the user setting "force deleted" event. - * - * @param \App\UserSetting $userSetting - * @return void - */ - public function forceDeleted(UserSetting $userSetting) + public function deleted(UserSetting $userSetting) { - // + \App\Jobs\UserUpdate::dispatch($userSetting->user); } } diff --git a/src/app/Traits/UserSettingsTrait.php b/src/app/Traits/UserSettingsTrait.php --- a/src/app/Traits/UserSettingsTrait.php +++ b/src/app/Traits/UserSettingsTrait.php @@ -81,7 +81,9 @@ private function storeSetting(string $key, $value): void { if ($value === null || $value === '') { - UserSetting::where(['user_id' => $this->id, 'key' => $key])->delete(); + if ($setting = UserSetting::where(['user_id' => $this->id, 'key' => $key])->first()) { + $setting->delete(); + } } else { UserSetting::updateOrCreate( ['user_id' => $this->id, 'key' => $key], diff --git a/src/app/User.php b/src/app/User.php --- a/src/app/User.php +++ b/src/app/User.php @@ -8,7 +8,6 @@ use App\Traits\UserSettingsTrait; use App\Wallet; use Illuminate\Notifications\Notifiable; -use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Foundation\Auth\User as Authenticatable; use Iatstuti\Database\Support\NullableFields; @@ -19,7 +18,6 @@ * * @property string $email * @property int $id - * @property string $name * @property string $password * @property int $status */ @@ -55,7 +53,6 @@ * @var array */ protected $fillable = [ - 'name', 'email', 'password', 'password_ldap', @@ -70,25 +67,14 @@ protected $hidden = [ 'password', 'password_ldap', - 'remember_token', 'role' ]; protected $nullable = [ - 'name', 'password', 'password_ldap' ]; - /** - * The attributes that should be cast to native types. - * - * @var array - */ - protected $casts = [ - 'email_verified_at' => 'datetime', - ]; - /** * Any wallets on which this user is a controller. * @@ -456,6 +442,27 @@ return ($this->status & self::STATUS_SUSPENDED) > 0; } + /** + * A shortcut to get the user name. + * + * @param bool $fallback Return " User" if there's no name + * + * @return string Full user name + */ + public function name(bool $fallback = false): string + { + $firstname = $this->getSetting('first_name'); + $lastname = $this->getSetting('last_name'); + + $name = trim($firstname . ' ' . $lastname); + + if (empty($name) && $fallback) { + return \config('app.name') . ' User'; + } + + return $name; + } + /** * Any (additional) properties of this user. * diff --git a/src/composer.json b/src/composer.json --- a/src/composer.json +++ b/src/composer.json @@ -42,7 +42,7 @@ "nunomaduro/collision": "^3.0", "nunomaduro/larastan": "^0.4", "phpstan/phpstan": "0.11.19", - "phpunit/phpunit": "^7.5" + "phpunit/phpunit": "^8" }, "config": { "optimize-autoloader": true, diff --git a/src/database/factories/UserFactory.php b/src/database/factories/UserFactory.php --- a/src/database/factories/UserFactory.php +++ b/src/database/factories/UserFactory.php @@ -20,9 +20,7 @@ User::class, function (Faker $faker) { return [ - 'name' => $faker->name, 'email' => $faker->unique()->safeEmail, - 'email_verified_at' => now(), 'password' => Str::random(64) ]; } diff --git a/src/database/migrations/2014_10_12_000000_create_users_table.php b/src/database/migrations/2014_10_12_000000_create_users_table.php --- a/src/database/migrations/2014_10_12_000000_create_users_table.php +++ b/src/database/migrations/2014_10_12_000000_create_users_table.php @@ -17,13 +17,11 @@ 'users', function (Blueprint $table) { $table->bigInteger('id'); - $table->string('name')->nullable(); $table->string('email')->unique(); - $table->timestamp('email_verified_at')->nullable(); $table->string('password')->nullable(); $table->string('password_ldap')->nullable(); $table->smallinteger('status'); - $table->rememberToken(); +// $table->rememberToken(); $table->timestamps(); $table->primary('id'); diff --git a/src/database/seeds/UserSeeder.php b/src/database/seeds/UserSeeder.php --- a/src/database/seeds/UserSeeder.php +++ b/src/database/seeds/UserSeeder.php @@ -32,10 +32,8 @@ $john = User::create( [ - 'name' => 'John Doe', 'email' => 'john@kolab.org', 'password' => 'simple123', - 'email_verified_at' => now() ] ); @@ -47,6 +45,7 @@ 'country' => 'US', 'billing_address' => "601 13th Street NW\nSuite 900 South\nWashington, DC 20005", 'external_email' => 'john.doe.external@gmail.com', + 'organization' => 'Kolab Developers', 'phone' => '+1 509-248-1111', ] ); @@ -64,10 +63,8 @@ $jack = User::create( [ - 'name' => 'Jack Daniels', 'email' => 'jack@kolab.org', 'password' => 'simple123', - 'email_verified_at' => now() ] ); @@ -92,10 +89,8 @@ $ned = User::create( [ - 'name' => 'Edward Flanders', 'email' => 'ned@kolab.org', 'password' => 'simple123', - 'email_verified_at' => now() ] ); @@ -122,10 +117,8 @@ $joe = User::create( [ - 'name' => 'Joe Sixpack', 'email' => 'joe@kolab.org', 'password' => 'simple123', - 'email_verified_at' => now() ] ); @@ -135,10 +128,8 @@ $jeroen = User::create( [ - 'name' => 'Jeroen van Meeuwen', 'email' => 'jeroen@jeroen.jeroen', 'password' => 'jeroen', - 'email_verified_at' => now() ] ); diff --git a/src/resources/vue/Admin/User.vue b/src/resources/vue/Admin/User.vue --- a/src/resources/vue/Admin/User.vue +++ b/src/resources/vue/Admin/User.vue @@ -41,6 +41,12 @@ {{ user.last_name }} +
+ +
+ {{ user.organization }} +
+
@@ -106,7 +112,7 @@
- +
{{ wallet_discount ? (wallet_discount + '% - ' + wallet_discount_description) : 'none' }} @@ -292,7 +298,7 @@ this.user = response.data - let keys = ['first_name', 'last_name', 'external_email', 'billing_address', 'phone'] + let keys = ['first_name', 'last_name', 'external_email', 'billing_address', 'phone', 'organization'] let country = this.user.settings.country if (country) { diff --git a/src/resources/vue/Signup.vue b/src/resources/vue/Signup.vue --- a/src/resources/vue/Signup.vue +++ b/src/resources/vue/Signup.vue @@ -26,8 +26,10 @@

- - +
+ + +
@@ -101,7 +103,8 @@ data() { return { email: '', - name: '', + first_name: '', + last_name: '', code: '', short_code: '', login: '', @@ -162,7 +165,8 @@ axios.post('/api/auth/signup/init', { email: this.email, - name: this.name, + last_name: this.last_name, + first_name: this.first_name, plan: this.plan, voucher: this.voucher }).then(response => { @@ -184,7 +188,8 @@ }).then(response => { this.displayForm(3, true) // Reset user name/email/plan, we don't have them if user used a verification link - this.name = response.data.name + this.first_name = response.data.first_name + this.last_name = response.data.last_name this.email = response.data.email this.is_domain = response.data.is_domain this.voucher = response.data.voucher diff --git a/src/resources/vue/User/Info.vue b/src/resources/vue/User/Info.vue --- a/src/resources/vue/User/Info.vue +++ b/src/resources/vue/User/Info.vue @@ -24,6 +24,12 @@
+
+ +
+ +
+
@@ -193,6 +199,7 @@ this.user = response.data this.user.first_name = response.data.settings.first_name this.user.last_name = response.data.settings.last_name + this.user.organization = response.data.settings.organization this.discount = this.user.wallet.discount this.discount_description = this.user.wallet.discount_description diff --git a/src/resources/vue/User/Profile.vue b/src/resources/vue/User/Profile.vue --- a/src/resources/vue/User/Profile.vue +++ b/src/resources/vue/User/Profile.vue @@ -17,6 +17,12 @@
+
+ +
+ +
+
diff --git a/src/tests/Browser/Admin/UserTest.php b/src/tests/Browser/Admin/UserTest.php --- a/src/tests/Browser/Admin/UserTest.php +++ b/src/tests/Browser/Admin/UserTest.php @@ -180,7 +180,7 @@ ->with('@user-info form', function (Browser $browser) use ($john) { $ext_email = $john->getSetting('external_email'); - $browser->assertElementsCount('.row', 8) + $browser->assertElementsCount('.row', 9) ->assertSeeIn('.row:nth-child(1) label', 'ID (Created at)') ->assertSeeIn('.row:nth-child(1) #userid', "{$john->id} ({$john->created_at})") ->assertSeeIn('.row:nth-child(2) label', 'Status') @@ -189,15 +189,17 @@ ->assertSeeIn('.row:nth-child(3) #first_name', 'John') ->assertSeeIn('.row:nth-child(4) label', 'Last name') ->assertSeeIn('.row:nth-child(4) #last_name', 'Doe') - ->assertSeeIn('.row:nth-child(5) label', 'Phone') - ->assertSeeIn('.row:nth-child(5) #phone', $john->getSetting('phone')) - ->assertSeeIn('.row:nth-child(6) label', 'External email') - ->assertSeeIn('.row:nth-child(6) #external_email a', $ext_email) - ->assertAttribute('.row:nth-child(6) #external_email a', 'href', "mailto:$ext_email") - ->assertSeeIn('.row:nth-child(7) label', 'Address') - ->assertSeeIn('.row:nth-child(7) #billing_address', $john->getSetting('billing_address')) - ->assertSeeIn('.row:nth-child(8) label', 'Country') - ->assertSeeIn('.row:nth-child(8) #country', 'United States of America'); + ->assertSeeIn('.row:nth-child(5) label', 'Organization') + ->assertSeeIn('.row:nth-child(5) #organization', 'Kolab Developers') + ->assertSeeIn('.row:nth-child(6) label', 'Phone') + ->assertSeeIn('.row:nth-child(6) #phone', $john->getSetting('phone')) + ->assertSeeIn('.row:nth-child(7) label', 'External email') + ->assertSeeIn('.row:nth-child(7) #external_email a', $ext_email) + ->assertAttribute('.row:nth-child(7) #external_email a', 'href', "mailto:$ext_email") + ->assertSeeIn('.row:nth-child(8) label', 'Address') + ->assertSeeIn('.row:nth-child(8) #billing_address', $john->getSetting('billing_address')) + ->assertSeeIn('.row:nth-child(9) label', 'Country') + ->assertSeeIn('.row:nth-child(9) #country', 'United States of America'); }); // Some tabs are loaded in background, wait a second diff --git a/src/tests/Browser/SignupTest.php b/src/tests/Browser/SignupTest.php --- a/src/tests/Browser/SignupTest.php +++ b/src/tests/Browser/SignupTest.php @@ -74,7 +74,8 @@ $code = SignupCode::create([ 'data' => [ 'email' => 'User@example.org', - 'name' => 'User Name', + 'first_name' => 'User', + 'last_name' => 'Name', 'plan' => 'individual', 'voucher' => '', ] @@ -125,7 +126,7 @@ ->assertMissing('@step0') ->assertMissing('@step2') ->assertMissing('@step3') - ->assertFocused('@step1 #signup_name'); + ->assertFocused('@step1 #signup_first_name'); // Click Back button $browser->click('@step1 [type=button]') @@ -142,7 +143,7 @@ ->assertMissing('@step0') ->assertMissing('@step2') ->assertMissing('@step3') - ->assertFocused('@step1 #signup_name'); + ->assertFocused('@step1 #signup_first_name'); // TODO: Test if 'plan' variable is set properly in vue component }); @@ -165,41 +166,44 @@ // Here we expect two text inputs and Back and Continue buttons $browser->with('@step1', function ($step) { - $step->assertVisible('#signup_name') - ->assertFocused('#signup_name') + $step->assertVisible('#signup_last_name') + ->assertVisible('#signup_first_name') + ->assertFocused('#signup_first_name') ->assertVisible('#signup_email') ->assertVisible('[type=button]') ->assertVisible('[type=submit]'); }); // Submit empty form - // Both Step 1 inputs are required, so after pressing Submit - // we expect focus to be moved to the first input + // Email is required, so after pressing Submit + // we expect focus to be moved to the email input $browser->with('@step1', function ($step) { $step->click('[type=submit]'); - $step->assertFocused('#signup_name'); + $step->assertFocused('#signup_email'); }); - // Submit invalid email - // We expect email input to have is-invalid class added, with .invalid-feedback element + // Submit invalid email, and first_name + // We expect both inputs to have is-invalid class added, with .invalid-feedback element $browser->with('@step1', function ($step) use ($browser) { - $step->type('#signup_name', 'Test User') + $step->type('#signup_first_name', str_repeat('a', 250)) ->type('#signup_email', '@test') ->click('[type=submit]') ->waitFor('#signup_email.is-invalid') + ->assertVisible('#signup_first_name.is-invalid') ->assertVisible('#signup_email + .invalid-feedback') + ->assertVisible('#signup_last_name + .invalid-feedback') ->assertToast(Toast::TYPE_ERROR, 'Form validation error'); }); // Submit valid data // We expect error state on email input to be removed, and Step 2 form visible $browser->with('@step1', function ($step) { - $step->type('#signup_name', 'Test User'); - $step->type('#signup_email', 'BrowserSignupTestUser1@kolab.org'); - $step->click('[type=submit]'); - - $step->assertMissing('#signup_email.is-invalid'); - $step->assertMissing('#signup_email + .invalid-feedback'); + $step->type('#signup_first_name', 'Test') + ->type('#signup_last_name', 'User') + ->type('#signup_email', 'BrowserSignupTestUser1@kolab.org') + ->click('[type=submit]') + ->assertMissing('#signup_email.is-invalid') + ->assertMissing('#signup_email + .invalid-feedback'); }); $browser->waitUntilMissing('@step2 #signup_code[value=""]'); @@ -232,14 +236,15 @@ // Test Back button functionality $browser->click('@step2 [type=button]') ->waitFor('@step1') - ->assertFocused('@step1 #signup_name') + ->assertFocused('@step1 #signup_first_name') ->assertMissing('@step2'); // Submit valid Step 1 data (again) $browser->with('@step1', function ($step) { - $step->type('#signup_name', 'Test User'); - $step->type('#signup_email', 'BrowserSignupTestUser1@kolab.org'); - $step->click('[type=submit]'); + $step->type('#signup_first_name', 'User') + ->type('#signup_last_name', 'User') + ->type('#signup_email', 'BrowserSignupTestUser1@kolab.org') + ->click('[type=submit]'); }); $browser->waitFor('@step2'); @@ -389,7 +394,8 @@ // Submit valid data // We expect error state on email input to be removed, and Step 2 form visible $browser->whenAvailable('@step1', function ($step) { - $step->type('#signup_name', 'Test User') + $step->type('#signup_first_name', 'Test') + ->type('#signup_last_name', 'User') ->type('#signup_email', 'BrowserSignupTestUser1@kolab.org') ->click('[type=submit]'); }); @@ -478,7 +484,8 @@ ->waitFor('@step0') ->click('.plan-individual button') ->whenAvailable('@step1', function (Browser $browser) { - $browser->type('#signup_name', 'Test User') + $browser->type('#signup_first_name', 'Test') + ->type('#signup_last_name', 'User') ->type('#signup_email', 'BrowserSignupTestUser1@kolab.org') ->click('[type=submit]'); }) diff --git a/src/tests/Browser/UserProfileTest.php b/src/tests/Browser/UserProfileTest.php --- a/src/tests/Browser/UserProfileTest.php +++ b/src/tests/Browser/UserProfileTest.php @@ -21,6 +21,7 @@ 'billing_address' => "601 13th Street NW\nSuite 900 South\nWashington, DC 20005", 'external_email' => 'john.doe.external@gmail.com', 'phone' => '+1 509-248-1111', + 'organization' => 'Kolab Developers', ]; /** @@ -76,24 +77,27 @@ ->assertValue('div.row:nth-child(1) input[type=text]', $this->profile['first_name']) ->assertSeeIn('div.row:nth-child(2) label', 'Last name') ->assertValue('div.row:nth-child(2) input[type=text]', $this->profile['last_name']) - ->assertSeeIn('div.row:nth-child(3) label', 'Phone') - ->assertValue('div.row:nth-child(3) input[type=text]', $this->profile['phone']) - ->assertSeeIn('div.row:nth-child(4) label', 'External email') - ->assertValue('div.row:nth-child(4) input[type=text]', $this->profile['external_email']) - ->assertSeeIn('div.row:nth-child(5) label', 'Address') - ->assertValue('div.row:nth-child(5) textarea', $this->profile['billing_address']) - ->assertSeeIn('div.row:nth-child(6) label', 'Country') - ->assertValue('div.row:nth-child(6) select', $this->profile['country']) - ->assertSeeIn('div.row:nth-child(7) label', 'Password') - ->assertValue('div.row:nth-child(7) input[type=password]', '') - ->assertSeeIn('div.row:nth-child(8) label', 'Confirm password') + ->assertSeeIn('div.row:nth-child(3) label', 'Organization') + ->assertValue('div.row:nth-child(3) input[type=text]', $this->profile['organization']) + ->assertSeeIn('div.row:nth-child(4) label', 'Phone') + ->assertValue('div.row:nth-child(4) input[type=text]', $this->profile['phone']) + ->assertSeeIn('div.row:nth-child(5) label', 'External email') + ->assertValue('div.row:nth-child(5) input[type=text]', $this->profile['external_email']) + ->assertSeeIn('div.row:nth-child(6) label', 'Address') + ->assertValue('div.row:nth-child(6) textarea', $this->profile['billing_address']) + ->assertSeeIn('div.row:nth-child(7) label', 'Country') + ->assertValue('div.row:nth-child(7) select', $this->profile['country']) + ->assertSeeIn('div.row:nth-child(8) label', 'Password') ->assertValue('div.row:nth-child(8) input[type=password]', '') + ->assertSeeIn('div.row:nth-child(9) label', 'Confirm password') + ->assertValue('div.row:nth-child(9) input[type=password]', '') ->assertSeeIn('button[type=submit]', 'Submit'); // Clear all fields and submit // FIXME: Should any of these fields be required? $browser->type('#first_name', '') ->type('#last_name', '') + ->type('#organization', '') ->type('#phone', '') ->type('#external_email', '') ->type('#billing_address', '') diff --git a/src/tests/Browser/UsersTest.php b/src/tests/Browser/UsersTest.php --- a/src/tests/Browser/UsersTest.php +++ b/src/tests/Browser/UsersTest.php @@ -24,6 +24,7 @@ private $profile = [ 'first_name' => 'John', 'last_name' => 'Doe', + 'organization' => 'Kolab Developers', ]; /** @@ -140,19 +141,21 @@ ->assertValue('div.row:nth-child(2) input[type=text]', $this->profile['first_name']) ->assertSeeIn('div.row:nth-child(3) label', 'Last name') ->assertValue('div.row:nth-child(3) input[type=text]', $this->profile['last_name']) - ->assertSeeIn('div.row:nth-child(4) label', 'Email') - ->assertValue('div.row:nth-child(4) input[type=text]', 'john@kolab.org') - ->assertDisabled('div.row:nth-child(4) input[type=text]') - ->assertSeeIn('div.row:nth-child(5) label', 'Email aliases') - ->assertVisible('div.row:nth-child(5) .list-input') + ->assertSeeIn('div.row:nth-child(4) label', 'Organization') + ->assertValue('div.row:nth-child(4) input[type=text]', $this->profile['organization']) + ->assertSeeIn('div.row:nth-child(5) label', 'Email') + ->assertValue('div.row:nth-child(5) input[type=text]', 'john@kolab.org') + ->assertDisabled('div.row:nth-child(5) input[type=text]') + ->assertSeeIn('div.row:nth-child(6) label', 'Email aliases') + ->assertVisible('div.row:nth-child(6) .list-input') ->with(new ListInput('#aliases'), function (Browser $browser) { $browser->assertListInputValue(['john.doe@kolab.org']) ->assertValue('@input', ''); }) - ->assertSeeIn('div.row:nth-child(6) label', 'Password') - ->assertValue('div.row:nth-child(6) input[type=password]', '') - ->assertSeeIn('div.row:nth-child(7) label', 'Confirm password') + ->assertSeeIn('div.row:nth-child(7) label', 'Password') ->assertValue('div.row:nth-child(7) input[type=password]', '') + ->assertSeeIn('div.row:nth-child(8) label', 'Confirm password') + ->assertValue('div.row:nth-child(8) input[type=password]', '') ->assertSeeIn('button[type=submit]', 'Submit'); // Clear some fields and submit @@ -210,8 +213,8 @@ // Test subscriptions $browser->with('@form', function (Browser $browser) { - $browser->assertSeeIn('div.row:nth-child(8) label', 'Subscriptions') - ->assertVisible('@skus.row:nth-child(8)') + $browser->assertSeeIn('div.row:nth-child(9) label', 'Subscriptions') + ->assertVisible('@skus.row:nth-child(9)') ->with('@skus', function ($browser) { $browser->assertElementsCount('tbody tr', 5) // Mailbox SKU @@ -321,20 +324,22 @@ ->assertValue('div.row:nth-child(1) input[type=text]', '') ->assertSeeIn('div.row:nth-child(2) label', 'Last name') ->assertValue('div.row:nth-child(2) input[type=text]', '') - ->assertSeeIn('div.row:nth-child(3) label', 'Email') + ->assertSeeIn('div.row:nth-child(3) label', 'Organization') ->assertValue('div.row:nth-child(3) input[type=text]', '') - ->assertEnabled('div.row:nth-child(3) input[type=text]') - ->assertSeeIn('div.row:nth-child(4) label', 'Email aliases') - ->assertVisible('div.row:nth-child(4) .list-input') + ->assertSeeIn('div.row:nth-child(4) label', 'Email') + ->assertValue('div.row:nth-child(4) input[type=text]', '') + ->assertEnabled('div.row:nth-child(4) input[type=text]') + ->assertSeeIn('div.row:nth-child(5) label', 'Email aliases') + ->assertVisible('div.row:nth-child(5) .list-input') ->with(new ListInput('#aliases'), function (Browser $browser) { $browser->assertListInputValue([]) ->assertValue('@input', ''); }) - ->assertSeeIn('div.row:nth-child(5) label', 'Password') - ->assertValue('div.row:nth-child(5) input[type=password]', '') - ->assertSeeIn('div.row:nth-child(6) label', 'Confirm password') + ->assertSeeIn('div.row:nth-child(6) label', 'Password') ->assertValue('div.row:nth-child(6) input[type=password]', '') - ->assertSeeIn('div.row:nth-child(7) label', 'Package') + ->assertSeeIn('div.row:nth-child(7) label', 'Confirm password') + ->assertValue('div.row:nth-child(7) input[type=password]', '') + ->assertSeeIn('div.row:nth-child(8) label', 'Package') // assert packages list widget, select "Lite Account" ->with('@packages', function ($browser) { $browser->assertElementsCount('tbody tr', 2) @@ -382,10 +387,13 @@ // Successful account creation $browser->with('@form', function (Browser $browser) { - $browser->with(new ListInput('#aliases'), function (Browser $browser) { - $browser->removeListEntry(1) - ->addListEntry('julia.roberts2@kolab.org'); - }) + $browser->type('#first_name', 'Julia') + ->type('#last_name', 'Roberts') + ->type('#organization', 'Test Org') + ->with(new ListInput('#aliases'), function (Browser $browser) { + $browser->removeListEntry(1) + ->addListEntry('julia.roberts2@kolab.org'); + }) ->click('button[type=submit]'); }) ->assertToast(Toast::TYPE_SUCCESS, 'User created successfully.') @@ -401,6 +409,9 @@ $alias = UserAlias::where('user_id', $julia->id)->where('alias', 'julia.roberts2@kolab.org')->first(); $this->assertTrue(!empty($alias)); $this->assertUserEntitlements($julia, ['mailbox', 'storage', 'storage']); + $this->assertSame('Julia', $julia->getSetting('first_name')); + $this->assertSame('Roberts', $julia->getSetting('last_name')); + $this->assertSame('Test Org', $julia->getSetting('organization')); }); } diff --git a/src/tests/Feature/Backends/LDAPTest.php b/src/tests/Feature/Backends/LDAPTest.php --- a/src/tests/Feature/Backends/LDAPTest.php +++ b/src/tests/Feature/Backends/LDAPTest.php @@ -4,48 +4,151 @@ use App\Backends\LDAP; use App\Domain; +use App\Entitlement; use App\User; +use Illuminate\Support\Facades\Queue; use Tests\TestCase; class LDAPTest extends TestCase { /** - * Test creating a domain record - * - * @group ldap + * {@inheritDoc} */ - public function testCreateDomain(): void + public function setUp(): void { - $this->markTestIncomplete(); + parent::setUp(); + + $this->deleteTestUser('user-ldap-test@' . \config('app.domain')); } /** - * Test creating a user record - * - * @group ldap + * {@inheritDoc} */ - public function testCreateUser(): void + public function tearDown(): void { - $this->markTestIncomplete(); + $this->deleteTestUser('user-ldap-test@' . \config('app.domain')); + + parent::tearDown(); } /** - * Test updating a domain record + * Test creating/updating/deleting a domain record * * @group ldap */ - public function testUpdateDomain(): void + public function testDomain(): void { $this->markTestIncomplete(); } /** - * Test updating a user record + * Test creating/editing/deleting a user record * * @group ldap */ - public function testUpdateUser(): void + public function testUser(): void { - $this->markTestIncomplete(); + Queue::fake(); + + $user = $this->getTestUser('user-ldap-test@' . \config('app.domain')); + + LDAP::createUser($user); + + $ldap_user = LDAP::getUser($user->email); + + $expected = [ + 'objectclass' => [ + 'top', + 'inetorgperson', + 'inetuser', + 'kolabinetorgperson', + 'mailrecipient', + 'person', + 'organizationalPerson', + ], + 'mail' => $user->email, + 'uid' => $user->email, + 'nsroledn' => null, + 'cn' => 'unknown', + 'displayname' => '', + 'givenname' => '', + 'sn' => 'unknown', + 'inetuserstatus' => $user->status, + 'mailquota' => null, + 'o' => '', + 'alias' => null, + ]; + + foreach ($expected as $attr => $value) { + $this->assertEquals($value, isset($ldap_user[$attr]) ? $ldap_user[$attr] : null); + } + + // Add aliases, and change some user settings, and entitlements + $user->setSettings([ + 'first_name' => 'Firstname', + 'last_name' => 'Lastname', + 'organization' => 'Org', + 'country' => 'PL', + ]); + $user->status |= User::STATUS_IMAP_READY; + $user->save(); + $aliases = ['t1-' . $user->email, 't2-' . $user->email]; + $user->setAliases($aliases); + $package_kolab = \App\Package::where('title', 'kolab')->first(); + $user->assignPackage($package_kolab); + + LDAP::updateUser($user->fresh()); + + $expected['alias'] = $aliases; + $expected['o'] = 'Org'; + $expected['displayname'] = 'Lastname, Firstname'; + $expected['givenname'] = 'Firstname'; + $expected['cn'] = 'Firstname Lastname'; + $expected['sn'] = 'Lastname'; + $expected['inetuserstatus'] = $user->status; + $expected['mailquota'] = 2097152; + $expected['nsroledn'] = null; + // TODO: country? dn + + $ldap_user = LDAP::getUser($user->email); + + foreach ($expected as $attr => $value) { + $this->assertEquals($value, isset($ldap_user[$attr]) ? $ldap_user[$attr] : null); + } + + // Update entitlements + $sku_activesync = \App\Sku::where('title', 'activesync')->first(); + $sku_groupware = \App\Sku::where('title', 'groupware')->first(); + $user->assignSku($sku_activesync, 1); + Entitlement::where(['sku_id' => $sku_groupware->id, 'entitleable_id' => $user->id])->delete(); + + LDAP::updateUser($user->fresh()); + + $expected_roles = [ + 'activesync-user', + 'imap-user' + ]; + + $ldap_user = LDAP::getUser($user->email); + + $this->assertCount(2, $ldap_user['nsroledn']); + + $ldap_roles = array_map( + function ($role) { + if (preg_match('/^cn=([a-z0-9-]+)/', $role, $m)) { + return $m[1]; + } else { + return $role; + } + }, + $ldap_user['nsroledn'] + ); + + $this->assertSame($expected_roles, $ldap_roles); + + // Delete the user + LDAP::deleteUser($user); + + $this->assertSame(null, LDAP::getUser($user->email)); } } diff --git a/src/tests/Feature/Controller/SignupTest.php b/src/tests/Feature/Controller/SignupTest.php --- a/src/tests/Feature/Controller/SignupTest.php +++ b/src/tests/Feature/Controller/SignupTest.php @@ -107,13 +107,14 @@ $response->assertStatus(422); $this->assertSame('error', $json['status']); - $this->assertCount(2, $json['errors']); + $this->assertCount(1, $json['errors']); $this->assertArrayHasKey('email', $json['errors']); - $this->assertArrayHasKey('name', $json['errors']); // Data with missing name $data = [ 'email' => 'UsersApiControllerTest1@UsersApiControllerTest.com', + 'first_name' => str_repeat('a', 250), + 'last_name' => str_repeat('a', 250), ]; $response = $this->post('/api/auth/signup/init', $data); @@ -122,13 +123,15 @@ $response->assertStatus(422); $this->assertSame('error', $json['status']); - $this->assertCount(1, $json['errors']); - $this->assertArrayHasKey('name', $json['errors']); + $this->assertCount(2, $json['errors']); + $this->assertArrayHasKey('first_name', $json['errors']); + $this->assertArrayHasKey('last_name', $json['errors']); // Data with invalid email (but not phone number) $data = [ 'email' => '@example.org', - 'name' => 'Signup User', + 'first_name' => 'Signup', + 'last_name' => 'User', ]; $response = $this->post('/api/auth/signup/init', $data); @@ -140,10 +143,9 @@ $this->assertCount(1, $json['errors']); $this->assertArrayHasKey('email', $json['errors']); - // Sanity check on voucher code + // Sanity check on voucher code, last/first name is optional $data = [ 'voucher' => '123456789012345678901234567890123', - 'name' => 'Signup User', 'email' => 'valid@email.com', ]; @@ -173,7 +175,8 @@ $data = [ 'email' => 'testuser@external.com', - 'name' => 'Signup User', + 'first_name' => 'Signup', + 'last_name' => 'User', 'plan' => 'individual', ]; @@ -195,7 +198,8 @@ return $code->code === $json['code'] && $code->data['plan'] === $data['plan'] && $code->data['email'] === $data['email'] - && $code->data['name'] === $data['name']; + && $code->data['first_name'] === $data['first_name'] + && $code->data['last_name'] === $data['last_name']; }); // Try the same with voucher @@ -217,13 +221,15 @@ && $code->data['plan'] === $data['plan'] && $code->data['email'] === $data['email'] && $code->data['voucher'] === $data['voucher'] - && $code->data['name'] === $data['name']; + && $code->data['first_name'] === $data['first_name'] + && $code->data['last_name'] === $data['last_name']; }); return [ 'code' => $json['code'], 'email' => $data['email'], - 'name' => $data['name'], + 'first_name' => $data['first_name'], + 'last_name' => $data['last_name'], 'plan' => $data['plan'], 'voucher' => $data['voucher'] ]; @@ -298,10 +304,11 @@ $json = $response->json(); $response->assertStatus(200); - $this->assertCount(6, $json); + $this->assertCount(7, $json); $this->assertSame('success', $json['status']); $this->assertSame($result['email'], $json['email']); - $this->assertSame($result['name'], $json['name']); + $this->assertSame($result['first_name'], $json['first_name']); + $this->assertSame($result['last_name'], $json['last_name']); $this->assertSame($result['voucher'], $json['voucher']); $this->assertSame(false, $json['is_domain']); $this->assertTrue(is_array($json['domains']) && !empty($json['domains'])); @@ -489,9 +496,10 @@ $this->assertNotEmpty($user); $this->assertSame($identity, $user->email); - $this->assertSame($result['name'], $user->name); - // Check external email in user settings + // Check user settings + $this->assertSame($result['first_name'], $user->getSetting('first_name')); + $this->assertSame($result['last_name'], $user->getSetting('last_name')); $this->assertSame($result['email'], $user->getSetting('external_email')); // Discount @@ -515,7 +523,8 @@ // Initial signup request $user_data = $data = [ 'email' => 'testuser@external.com', - 'name' => 'Signup User', + 'first_name' => 'Signup', + 'last_name' => 'User', 'plan' => 'group', ]; @@ -537,7 +546,8 @@ return $code->code === $json['code'] && $code->data['plan'] === $data['plan'] && $code->data['email'] === $data['email'] - && $code->data['name'] === $data['name']; + && $code->data['first_name'] === $data['first_name'] + && $code->data['last_name'] === $data['last_name']; }); // Verify the code @@ -551,10 +561,11 @@ $result = $response->json(); $response->assertStatus(200); - $this->assertCount(6, $result); + $this->assertCount(7, $result); $this->assertSame('success', $result['status']); $this->assertSame($user_data['email'], $result['email']); - $this->assertSame($user_data['name'], $result['name']); + $this->assertSame($user_data['first_name'], $result['first_name']); + $this->assertSame($user_data['last_name'], $result['last_name']); $this->assertSame(null, $result['voucher']); $this->assertSame(true, $result['is_domain']); $this->assertSame([], $result['domains']); @@ -602,12 +613,13 @@ $user = User::where('email', $login . '@' . $domain)->first(); $this->assertNotEmpty($user); - $this->assertSame($user_data['name'], $user->name); - - // Check domain record - // Check external email in user settings + // Check user settings $this->assertSame($user_data['email'], $user->getSetting('external_email')); + $this->assertSame($user_data['first_name'], $user->getSetting('first_name')); + $this->assertSame($user_data['last_name'], $user->getSetting('last_name')); + + // TODO: Check domain record // TODO: Check SKUs/Plan diff --git a/src/tests/Feature/Controller/UsersTest.php b/src/tests/Feature/Controller/UsersTest.php --- a/src/tests/Feature/Controller/UsersTest.php +++ b/src/tests/Feature/Controller/UsersTest.php @@ -139,7 +139,7 @@ // TODO/FIXME: // For now controller can delete himself, as well as // the whole account he has control to, including the owner - // Probably he should not be able to do either of those + // Probably he should not be able to do none of those // However, this is not 0-regression scenario as we // do not fully support additional controllers. @@ -290,6 +290,7 @@ $this->assertTrue(is_array($result['settings'])); $this->assertSame('US', $result['settings']['country']); $this->assertSame('USD', $result['settings']['currency']); + // TODO: Test all settings $this->assertTrue(is_array($result['accounts'])); $this->assertTrue(is_array($result['wallets'])); @@ -460,6 +461,7 @@ 'first_name' => 'John2', 'last_name' => 'Doe2', 'email' => 'john2.doe2@kolab.org', + 'organization' => 'TestOrg', 'aliases' => ['useralias1@kolab.org', 'useralias2@kolab.org'], ]; @@ -499,6 +501,7 @@ $this->assertInstanceOf(User::class, $user); $this->assertSame('John2', $user->getSetting('first_name')); $this->assertSame('Doe2', $user->getSetting('last_name')); + $this->assertSame('TestOrg', $user->getSetting('organization')); $aliases = $user->aliases()->orderBy('alias')->get(); $this->assertCount(2, $aliases); $this->assertSame('useralias1@kolab.org', $aliases[0]->alias); @@ -575,6 +578,7 @@ 'password_confirmation' => 'simple', 'first_name' => 'John2', 'last_name' => 'Doe2', + 'organization' => 'TestOrg', 'phone' => '+123 123 123', 'external_email' => 'external@gmail.com', 'billing_address' => 'billing', @@ -605,6 +609,7 @@ $post = [ 'first_name' => '', 'last_name' => '', + 'organization' => '', 'phone' => '', 'external_email' => '', 'billing_address' => '', diff --git a/src/tests/Feature/Jobs/SignupVerificationEmailTest.php b/src/tests/Feature/Jobs/SignupVerificationEmailTest.php --- a/src/tests/Feature/Jobs/SignupVerificationEmailTest.php +++ b/src/tests/Feature/Jobs/SignupVerificationEmailTest.php @@ -24,7 +24,8 @@ $this->code = SignupCode::create([ 'data' => [ 'email' => 'SignupVerificationEmailTest1@' . \config('app.domain'), - 'name' => "Test Job" + 'first_name' => "Test", + 'last_name' => "Job" ] ]); } diff --git a/src/tests/Feature/Jobs/UserVerifyTest.php b/src/tests/Feature/Jobs/UserVerifyTest.php --- a/src/tests/Feature/Jobs/UserVerifyTest.php +++ b/src/tests/Feature/Jobs/UserVerifyTest.php @@ -14,12 +14,16 @@ { parent::setUp(); - $this->deleteTestUser('jane@kolabnow.com'); + $ned = $this->getTestUser('ned@kolab.org'); + $ned->status |= User::STATUS_IMAP_READY; + $ned->save(); } public function tearDown(): void { - $this->deleteTestUser('jane@kolabnow.com'); + $ned = $this->getTestUser('ned@kolab.org'); + $ned->status |= User::STATUS_IMAP_READY; + $ned->save(); parent::tearDown(); } @@ -33,17 +37,11 @@ { Queue::fake(); - $user = $this->getTestUser('jane@kolabnow.com'); + $user = $this->getTestUser('ned@kolab.org'); + $user->status ^= User::STATUS_IMAP_READY; + $user->save(); - // This is a valid assertion in a feature, not functional test environment. $this->assertFalse($user->isImapReady()); - $this->assertFalse($user->isLdapReady()); - - $job = new UserCreate($user); - $job->handle(); - - $this->assertFalse($user->isImapReady()); - $this->assertTrue($user->isLdapReady()); for ($i = 0; $i < 10; $i++) { $job = new UserVerify($user); diff --git a/src/tests/Feature/UserTest.php b/src/tests/Feature/UserTest.php --- a/src/tests/Feature/UserTest.php +++ b/src/tests/Feature/UserTest.php @@ -335,11 +335,88 @@ } /** - * Tests for UserSettingsTrait::setSettings() + * Tests for UserSettingsTrait::setSettings() and getSetting() */ - public function testSetSettings(): void + public function testUserSettings(): void { - $this->markTestIncomplete(); + Queue::fake(); + Queue::assertNothingPushed(); + + $user = $this->getTestUser('UserAccountA@UserAccount.com'); + + Queue::assertPushed(\App\Jobs\UserUpdate::class, 0); + + // Test default settings + // Note: Technicly this tests UserObserver::created() behavior + $all_settings = $user->settings()->orderBy('key')->get(); + $this->assertCount(2, $all_settings); + $this->assertSame('country', $all_settings[0]->key); + $this->assertSame('CH', $all_settings[0]->value); + $this->assertSame('currency', $all_settings[1]->key); + $this->assertSame('CHF', $all_settings[1]->value); + + // Add a setting + $user->setSetting('first_name', 'Firstname'); + + Queue::assertPushed(\App\Jobs\UserUpdate::class, 1); + + // Note: We test both current user as well as fresh user object + // to make sure cache works as expected + $this->assertSame('Firstname', $user->getSetting('first_name')); + $this->assertSame('Firstname', $user->fresh()->getSetting('first_name')); + + // Update a setting + $user->setSetting('first_name', 'Firstname1'); + + Queue::assertPushed(\App\Jobs\UserUpdate::class, 2); + + // Note: We test both current user as well as fresh user object + // to make sure cache works as expected + $this->assertSame('Firstname1', $user->getSetting('first_name')); + $this->assertSame('Firstname1', $user->fresh()->getSetting('first_name')); + + // Delete a setting (null) + $user->setSetting('first_name', null); + + Queue::assertPushed(\App\Jobs\UserUpdate::class, 3); + + // Note: We test both current user as well as fresh user object + // to make sure cache works as expected + $this->assertSame(null, $user->getSetting('first_name')); + $this->assertSame(null, $user->fresh()->getSetting('first_name')); + + // Delete a setting (empty string) + $user->setSetting('first_name', 'Firstname1'); + $user->setSetting('first_name', ''); + + Queue::assertPushed(\App\Jobs\UserUpdate::class, 5); + + // Note: We test both current user as well as fresh user object + // to make sure cache works as expected + $this->assertSame(null, $user->getSetting('first_name')); + $this->assertSame(null, $user->fresh()->getSetting('first_name')); + + // Set multiple settings at once + $user->setSettings([ + 'first_name' => 'Firstname2', + 'last_name' => 'Lastname2', + 'country' => null, + ]); + + // TODO: This really should create a single UserUpdate job, not 3 + Queue::assertPushed(\App\Jobs\UserUpdate::class, 8); + + // Note: We test both current user as well as fresh user object + // to make sure cache works as expected + $this->assertSame('Firstname2', $user->getSetting('first_name')); + $this->assertSame('Firstname2', $user->fresh()->getSetting('first_name')); + $this->assertSame('Lastname2', $user->getSetting('last_name')); + $this->assertSame('Lastname2', $user->fresh()->getSetting('last_name')); + $this->assertSame(null, $user->getSetting('country')); + $this->assertSame(null, $user->fresh()->getSetting('country')); + + $all_settings = $user->settings()->orderBy('key')->get(); + $this->assertCount(3, $all_settings); } /** diff --git a/src/tests/TestCase.php b/src/tests/TestCase.php --- a/src/tests/TestCase.php +++ b/src/tests/TestCase.php @@ -26,5 +26,6 @@ // If we wanted to access both user and admin in one test // we can also just call post/get/whatever with full url \config(['app.url' => str_replace('//', '//admin.', \config('app.url'))]); + url()->forceRootUrl(config('app.url')); } } diff --git a/src/tests/Unit/Mail/PasswordResetTest.php b/src/tests/Unit/Mail/PasswordResetTest.php --- a/src/tests/Unit/Mail/PasswordResetTest.php +++ b/src/tests/Unit/Mail/PasswordResetTest.php @@ -35,6 +35,6 @@ $this->assertSame(\config('app.name') . ' Password Reset', $mail->subject); $this->assertStringStartsWith('', $html); $this->assertTrue(strpos($html, $link) > 0); - $this->assertTrue(strpos($html, $code->user->name) > 0); + $this->assertTrue(strpos($html, $code->user->name(true)) > 0); } }