Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117906121
D976.1775389593.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
33 KB
Referenced Files
None
Subscribers
None
D976.1775389593.diff
View Options
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
@@ -327,8 +327,6 @@
return ['domain' => 'validation.domaininvalid'];
}
- // TODO: DNS registration check - maybe after signup?
-
// Check if domain is already registered with us
if (Domain::where('namespace', $domain)->first()) {
return ['domain' => 'validation.domainexists'];
@@ -336,7 +334,6 @@
}
// Check if user with specified login already exists
- // TODO: Aliases
$email = $login . '@' . $domain;
if (User::findByEmail($email)) {
return ['login' => 'validation.loginexists'];
diff --git a/src/app/Http/Controllers/API/UsersController.php b/src/app/Http/Controllers/API/UsersController.php
--- a/src/app/Http/Controllers/API/UsersController.php
+++ b/src/app/Http/Controllers/API/UsersController.php
@@ -75,18 +75,7 @@
public function info()
{
$user = $this->guard()->user();
- $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) {
- $response['settings'][$item->key] = $item->value;
- }
-
- // Status info
- $response['statusInfo'] = self::statusInfo($user);
+ $response = $this->userResponse($user);
return response()->json($response);
}
@@ -112,7 +101,6 @@
return response()->json(['status' => 'error', 'errors' => $v->errors()], 422);
}
-
$credentials = $request->only('email', 'password');
if ($token = $this->guard()->attempt($credentials)) {
@@ -181,10 +169,12 @@
$user = User::find($id);
if (empty($user)) {
- return $this->errorResponse(404);
+ return $this->errorResponse(404);
}
- return response()->json($user);
+ $response = $this->userResponse($user);
+
+ return response()->json($response);
}
/**
@@ -216,7 +206,7 @@
$domain = Domain::where('namespace', $domain)->first();
// If that is not a public domain, add domain specific steps
- if (!$domain->isPublic()) {
+ if ($domain && !$domain->isPublic()) {
$steps['domain-new'] = true;
$steps['domain-ldap-ready'] = 'isLdapReady';
$steps['domain-verified'] = 'isVerified';
@@ -286,6 +276,7 @@
'billing_address' => 'string|nullable|max:1024',
'country' => 'string|nullable|alpha|size:2',
'currency' => 'string|nullable|alpha|size:3',
+ 'aliases' => 'array|nullable',
];
if (!empty($request->password) || !empty($request->password_confirmation)) {
@@ -299,9 +290,26 @@
return response()->json(['status' => 'error', 'errors' => $v->errors()], 422);
}
+ // Validate aliases input
+ if (isset($request->aliases)) {
+ $aliases = [];
+
+ if (!empty($request->aliases)) {
+ $domains = $user->domains();
+ }
+
+ foreach ($request->aliases as $idx => $alias) {
+ if (is_string($alias) && !empty($alias)) {
+ // TODO: validation
+
+ $aliases[] = $alias;
+ }
+ }
+ }
+
// Update user settings
$settings = $request->only(array_keys($rules));
- unset($settings['password']);
+ unset($settings['password'], $settings['aliases']);
if (!empty($settings)) {
$user->setSettings($settings);
@@ -313,6 +321,11 @@
$user->save();
}
+ // Update aliases
+ if (!empty($aliases)) {
+ $user->setAliases($aliases);
+ }
+
return response()->json([
'status' => 'success',
'message' => __('app.user-update-success'),
@@ -345,4 +358,35 @@
return $current_user->id == $user_id;
}
+
+ /**
+ * Create a response data array for specified user.
+ *
+ * @param \App\User $user User object
+ *
+ * @return array Response data
+ */
+ protected function userResponse(User $user): array
+ {
+ $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) {
+ $response['settings'][$item->key] = $item->value;
+ }
+
+ // Aliases
+ $response['aliases'] = [];
+ foreach ($user->aliases as $item) {
+ $response['aliases'][] = $item->alias;
+ }
+
+ // Status info
+ $response['statusInfo'] = self::statusInfo($user);
+
+ return $response;
+ }
}
diff --git a/src/app/Observers/UserAliasObserver.php b/src/app/Observers/UserAliasObserver.php
new file mode 100644
--- /dev/null
+++ b/src/app/Observers/UserAliasObserver.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace App\Observers;
+
+use App\User;
+use App\UserAlias;
+
+class UserAliasObserver
+{
+ /**
+ * Handle the "creating" event on an alias
+ *
+ * Ensures that there's no user with specified email.
+ *
+ * @param \App\UserAlias $alias The user email alias
+ *
+ * @return bool|null
+ */
+ public function creating(UserAlias $alias)
+ {
+ $alias->alias = \strtolower($alias->alias);
+
+ if (User::where('email', $alias->alias)->first()) {
+ \Log::error("Failed creating alias {$alias->alias}. User exists.");
+ return false;
+ }
+ }
+
+ /**
+ * Handle the user alias "created" event.
+ *
+ * @param \App\UserAlias $alias User email alias
+ *
+ * @return void
+ */
+ public function created(UserAlias $alias)
+ {
+ \App\Jobs\UserUpdate::dispatch($alias->user);
+ }
+
+ /**
+ * Handle the user setting "updated" event.
+ *
+ * @param \App\UserAlias $alias User email alias
+ *
+ * @return void
+ */
+ public function updated(UserAlias $alias)
+ {
+ \App\Jobs\UserUpdate::dispatch($alias->user);
+ }
+
+ /**
+ * Handle the user setting "deleted" event.
+ *
+ * @param \App\UserAlias $alias User email alias
+ *
+ * @return void
+ */
+ public function deleted(UserAlias $alias)
+ {
+ \App\Jobs\UserUpdate::dispatch($alias->user);
+ }
+
+ /**
+ * Handle the user alias "restored" event.
+ *
+ * @param \App\UserAlias $alias User email alias
+ *
+ * @return void
+ */
+ public function restored(UserAlias $alias)
+ {
+ // not used
+ }
+
+ /**
+ * Handle the user alias "force deleted" event.
+ *
+ * @param \App\UserAlias $alias User email alias
+ *
+ * @return void
+ */
+ public function forceDeleted(UserAlias $alias)
+ {
+ // not used
+ }
+}
diff --git a/src/app/Providers/AppServiceProvider.php b/src/app/Providers/AppServiceProvider.php
--- a/src/app/Providers/AppServiceProvider.php
+++ b/src/app/Providers/AppServiceProvider.php
@@ -32,6 +32,7 @@
\App\SignupCode::observe(\App\Observers\SignupCodeObserver::class);
\App\Sku::observe(\App\Observers\SkuObserver::class);
\App\User::observe(\App\Observers\UserObserver::class);
+ \App\UserAlias::observe(\App\Observers\UserAliasObserver::class);
\App\UserSetting::observe(\App\Observers\UserSettingObserver::class);
\App\VerificationCode::observe(\App\Observers\VerificationCodeObserver::class);
\App\Wallet::observe(\App\Observers\WalletObserver::class);
diff --git a/src/app/Traits/UserAliasesTrait.php b/src/app/Traits/UserAliasesTrait.php
new file mode 100644
--- /dev/null
+++ b/src/app/Traits/UserAliasesTrait.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace App\Traits;
+
+use App\UserAlias;
+use Illuminate\Support\Facades\Cache;
+
+trait UserAliasesTrait
+{
+ /**
+ * A helper to update user aliases list.
+ *
+ * Example Usage:
+ *
+ * ```php
+ * $user = User::firstOrCreate(['email' => 'some@other.erg']);
+ * $user->setAliases(['alias1@other.org', 'alias2@other.org']);
+ * ```
+ *
+ * @param array $data An array of email addresses
+ *
+ * @return void
+ */
+ public function setAliases(array $aliases): void
+ {
+ $existing_aliases = $this->aliases()->get()->map(function ($alias) {
+ return $alias->alias;
+ })->toArray();
+
+ $aliases = array_map('strtolower', $aliases);
+ $aliases = array_unique($aliases);
+
+ foreach (array_diff($aliases, $existing_aliases) as $alias) {
+ $this->aliases()->create(['alias' => $alias]);
+ }
+
+ foreach (array_diff($existing_aliases, $aliases) as $alias) {
+ $this->aliases()->where('alias', $alias)->delete();
+ }
+ }
+}
diff --git a/src/app/User.php b/src/app/User.php
--- a/src/app/User.php
+++ b/src/app/User.php
@@ -2,24 +2,30 @@
namespace App;
+use App\UserAlias;
+use App\Traits\UserAliasesTrait;
+use App\Traits\UserSettingsTrait;
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;
use Tymon\JWTAuth\Contracts\JWTSubject;
-use App\Traits\UserSettingsTrait;
/**
* The eloquent definition of a User.
*
- * @property integer $id
- * @property integer $status
+ * @property string $email
+ * @property int $id
+ * @property string $name
+ * @property string $password
+ * @property int $status
*/
class User extends Authenticatable implements JWTSubject
{
use Notifiable;
use NullableFields;
+ use UserAliasesTrait;
use UserSettingsTrait;
use SoftDeletes;
@@ -95,6 +101,16 @@
);
}
+ /**
+ * Email aliases of this user.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function aliases()
+ {
+ return $this->hasMany('App\UserAlias', 'user_id');
+ }
+
/**
* Assign a package to a user. The user should not have any existing entitlements.
*
@@ -216,7 +232,7 @@
*
* @param string $email Email address
*
- * @return \App\User User model object
+ * @return \App\User User model object if found
*/
public static function findByEmail(string $email): ?User
{
@@ -224,11 +240,23 @@
return null;
}
+ $email = \strtolower($email);
+
$user = self::where('email', $email)->first();
- // TODO: Aliases, External email
+ if ($user) {
+ return $user;
+ }
- return $user;
+ $alias = UserAlias::where('alias', $email)->first();
+
+ if ($alias) {
+ return $alias->user;
+ }
+
+ // TODO: External email
+
+ return null;
}
public function getJWTIdentifier()
diff --git a/src/app/UserAlias.php b/src/app/UserAlias.php
new file mode 100644
--- /dev/null
+++ b/src/app/UserAlias.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace App;
+
+use Illuminate\Database\Eloquent\Model;
+
+/**
+ * A email address alias for a User.
+ *
+ * @property string $alias
+ * @property int $id
+ * @property int $user_id
+ */
+class UserAlias extends Model
+{
+ protected $fillable = [
+ 'user_id', 'alias'
+ ];
+
+ /**
+ * The user to which this alias belongs.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function user()
+ {
+ return $this->belongsTo(
+ '\App\User',
+ 'user_id', /* local */
+ 'id' /* remote */
+ );
+ }
+}
diff --git a/src/database/migrations/2020_02_26_000000_create_user_aliases_table.php b/src/database/migrations/2020_02_26_000000_create_user_aliases_table.php
new file mode 100644
--- /dev/null
+++ b/src/database/migrations/2020_02_26_000000_create_user_aliases_table.php
@@ -0,0 +1,39 @@
+<?php
+
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Migrations\Migration;
+
+class CreateUserAliasesTable extends Migration
+{
+ /**
+ * Run the migrations.
+ *
+ * @return void
+ */
+ public function up()
+ {
+ Schema::create(
+ 'user_aliases',
+ function (Blueprint $table) {
+ $table->bigIncrements('id');
+ $table->bigInteger('user_id');
+ $table->string('alias')->unique();
+ $table->timestamps();
+
+ $table->foreign('user_id')->references('id')->on('users')
+ ->onDelete('cascade')->onUpdate('cascade');
+ }
+ );
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_aliases');
+ }
+}
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
@@ -45,6 +45,8 @@
]
);
+ $john->setAliases(['john.doe@kolab.org']);
+
$user_wallets = $john->wallets()->get();
$package_domain = \App\Package::where('title', 'domain-hosting')->first();
@@ -71,6 +73,8 @@
]
);
+ $jack->setAliases(['jack.daniels@kolab.org']);
+
$john->assignPackage($package_kolab, $jack);
factory(User::class, 10)->create();
diff --git a/src/resources/vue/components/User/Info.vue b/src/resources/vue/components/User/Info.vue
--- a/src/resources/vue/components/User/Info.vue
+++ b/src/resources/vue/components/User/Info.vue
@@ -4,6 +4,47 @@
<div class="card-body">
<div class="card-title">User account</div>
<div class="card-text">
+ <form @submit.prevent="submit">
+ <div class="form-group row">
+ <label for="first_name" class="col-sm-4 col-form-label">First name</label>
+ <div class="col-sm-8">
+ <input type="text" class="form-control" id="first_name" v-model="user.first_name">
+ </div>
+ </div>
+ <div class="form-group row">
+ <label for="last_name" class="col-sm-4 col-form-label">Last name</label>
+ <div class="col-sm-8">
+ <input type="text" class="form-control" id="last_name" v-model="user.last_name">
+ </div>
+ </div>
+ <div class="form-group row">
+ <label for="email" class="col-sm-4 col-form-label">Email</label>
+ <div class="col-sm-8">
+ <input type="text" class="form-control" id="email" disabled v-model="user.email">
+ </div>
+ </div>
+ <!--
+ <div class="form-group row">
+ <label for="alias" class="col-sm-4 col-form-label">Email aliases</label>
+ <div class="col-sm-8">
+ <input type="text" class="form-control" id="alias" v-model="user.alias">
+ </div>
+ </div>
+ -->
+ <div class="form-group row">
+ <label for="password" class="col-sm-4 col-form-label">Password</label>
+ <div class="col-sm-8">
+ <input type="password" class="form-control" id="password" v-model="user.password">
+ </div>
+ </div>
+ <div class="form-group row">
+ <label for="password_confirmaton" class="col-sm-4 col-form-label">Confirm password</label>
+ <div class="col-sm-8">
+ <input type="password" class="form-control" id="password_confirmation" v-model="user.password_confirmation">
+ </div>
+ </div>
+ <button class="btn btn-primary" type="submit">Submit</button>
+ </form>
</div>
</div>
</div>
@@ -15,7 +56,7 @@
data() {
return {
user_id: null,
- user: null
+ user: {}
}
},
created() {
@@ -23,11 +64,31 @@
axios.get('/api/v4/users/' + this.user_id)
.then(response => {
this.user = response.data
+ this.user.first_name = response.data.settings.first_name
+ this.user.last_name = response.data.settings.last_name
})
.catch(this.$root.errorHandler)
} else {
this.$root.errorPage(404)
}
+ },
+ mounted() {
+ $('#first_name').focus()
+ },
+ methods: {
+ submit() {
+ this.$root.clearFormValidation($('#user-info form'))
+
+ axios.put('/api/v4/users/' + this.user_id, this.user)
+ .then(response => {
+ delete this.user.password
+ delete this.user.password_confirm
+
+ if (response.data.status == 'success') {
+ this.$toastr('success', response.data.message)
+ }
+ })
+ }
}
}
</script>
diff --git a/src/tests/Browser/Pages/UserInfo.php b/src/tests/Browser/Pages/UserInfo.php
new file mode 100644
--- /dev/null
+++ b/src/tests/Browser/Pages/UserInfo.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Tests\Browser\Pages;
+
+use Laravel\Dusk\Browser;
+use Laravel\Dusk\Page;
+
+class UserInfo extends Page
+{
+ /**
+ * Get the URL for the page.
+ *
+ * @return string
+ */
+ public function url(): string
+ {
+ return '';
+ }
+
+ /**
+ * Assert that the browser is on the page.
+ *
+ * @param \Laravel\Dusk\Browser $browser
+ *
+ * @return void
+ */
+ public function assert(Browser $browser)
+ {
+ $browser->waitFor('@form')
+ ->assertSeeIn('#user-info .card-title', 'User account');
+ }
+
+ /**
+ * Get the element shortcuts for the page.
+ *
+ * @return array
+ */
+ public function elements(): array
+ {
+ return [
+ '@app' => '#app',
+ '@form' => '#user-info form',
+ ];
+ }
+}
diff --git a/src/tests/Browser/Pages/UserList.php b/src/tests/Browser/Pages/UserList.php
new file mode 100644
--- /dev/null
+++ b/src/tests/Browser/Pages/UserList.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Tests\Browser\Pages;
+
+use Laravel\Dusk\Browser;
+use Laravel\Dusk\Page;
+
+class UserList extends Page
+{
+ /**
+ * Get the URL for the page.
+ *
+ * @return string
+ */
+ public function url(): string
+ {
+ return '/users';
+ }
+
+ /**
+ * Assert that the browser is on the page.
+ *
+ * @param \Laravel\Dusk\Browser $browser
+ *
+ * @return void
+ */
+ public function assert(Browser $browser)
+ {
+ $browser->assertPathIs($this->url())
+ ->waitUntilMissing('@app .app-loader')
+ ->assertSeeIn('#user-list .card-title', 'User Accounts');
+ }
+
+ /**
+ * Get the element shortcuts for the page.
+ *
+ * @return array
+ */
+ public function elements(): array
+ {
+ return [
+ '@app' => '#app',
+ '@table' => '#user-list table',
+ ];
+ }
+}
diff --git a/src/tests/Browser/UsersTest.php b/src/tests/Browser/UsersTest.php
new file mode 100644
--- /dev/null
+++ b/src/tests/Browser/UsersTest.php
@@ -0,0 +1,142 @@
+<?php
+
+namespace Tests\Browser;
+
+use App\User;
+use Tests\Browser\Components\Toast;
+use Tests\Browser\Pages\Dashboard;
+use Tests\Browser\Pages\Home;
+use Tests\Browser\Pages\UserInfo;
+use Tests\Browser\Pages\UserList;
+use Tests\DuskTestCase;
+use Laravel\Dusk\Browser;
+use Illuminate\Foundation\Testing\DatabaseMigrations;
+
+class UsersTest extends DuskTestCase
+{
+ private $profile = [
+ 'first_name' => 'John',
+ 'last_name' => 'Doe',
+ ];
+
+ /**
+ * {@inheritDoc}
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ User::where('email', 'john@kolab.org')->first()->setSettings($this->profile);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function tearDown(): void
+ {
+ User::where('email', 'john@kolab.org')->first()->setSettings($this->profile);
+
+ parent::tearDown();
+ }
+
+ /**
+ * Test user info page (unauthenticated)
+ */
+ public function testInfoUnauth(): void
+ {
+ // Test that the page requires authentication
+ $this->browse(function (Browser $browser) {
+ $user = User::where('email', 'john@kolab.org')->first();
+
+ $browser->visit('/user/' . $user->id)->on(new Home());
+ });
+ }
+
+ /**
+ * Test users list page (unauthenticated)
+ */
+ public function testListUnauth(): void
+ {
+ // Test that the page requires authentication
+ $this->browse(function (Browser $browser) {
+ $browser->visit('/users')->on(new Home());
+ });
+ }
+
+ /**
+ * Test users list page
+ */
+ public function testList(): void
+ {
+ // Test that the page requires authentication
+ $this->browse(function (Browser $browser) {
+ $browser->visit(new Home())
+ ->submitLogon('john@kolab.org', 'simple123', true)
+ ->on(new Dashboard())
+ ->assertSeeIn('@links .link-users', 'User accounts')
+ ->click('@links .link-users')
+ ->on(new UserList())
+ ->whenAvailable('@table', function ($browser) {
+ $this->assertCount(1, $browser->elements('tbody tr'));
+ $browser->assertSeeIn('tbody tr td a', 'john@kolab.org');
+ });
+ });
+ }
+
+ /**
+ * Test profile page
+ *
+ * @depends testList
+ */
+ public function testInfo(): void
+ {
+ $this->browse(function (Browser $browser) {
+ $browser->on(new UserList())
+ ->click('@table tr:first-child a')
+ ->on(new UserInfo())
+ ->whenAvailable('@form', function (Browser $browser) {
+ // Assert form content
+ $browser->assertFocused('div.row:nth-child(1) input')
+ ->assertSeeIn('div.row:nth-child(1) label', 'First name')
+ ->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', 'Email')
+ ->assertValue('div.row:nth-child(3) input[type=text]', 'john@kolab.org')
+// ->assertDisabled('div.row:nth-child(3) input')
+ ->assertSeeIn('div.row:nth-child(4) label', 'Password')
+ ->assertValue('div.row:nth-child(4) input[type=password]', '')
+ ->assertSeeIn('div.row:nth-child(5) label', 'Confirm password')
+ ->assertValue('div.row:nth-child(5) input[type=password]', '')
+ ->assertSeeIn('button[type=submit]', 'Submit');
+
+ // Clear some fields and submit
+ $browser->type('#first_name', '')
+ ->type('#last_name', '')
+ ->click('button[type=submit]');
+ })
+ ->with(new Toast(Toast::TYPE_SUCCESS), function (Browser $browser) {
+ $browser->assertToastTitle('')
+ ->assertToastMessage('User data updated successfully')
+ ->closeToast();
+ });
+
+ // Test error handling
+ $browser->with('@form', function (Browser $browser) {
+ $browser->type('#password', 'aaaaaa')
+ ->type('#password_confirmation', '')
+ ->click('button[type=submit]')
+ ->waitFor('#password + .invalid-feedback')
+ ->assertSeeIn('#password + .invalid-feedback', 'The password confirmation does not match.')
+ ->assertFocused('#password');
+ })
+ ->with(new Toast(Toast::TYPE_ERROR), function (Browser $browser) {
+ $browser->assertToastTitle('Error')
+ ->assertToastMessage('Form validation error')
+ ->closeToast();
+ });
+
+ // TODO: Test password change
+ });
+ }
+}
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
@@ -55,8 +55,10 @@
$this->assertEquals($user->email, $json['email']);
$this->assertEquals(User::STATUS_NEW, $json['status']);
$this->assertTrue(is_array($json['statusInfo']));
- $this->assertEquals($user->getSetting('country'), $json['settings']['country']);
- $this->assertEquals($user->getSetting('currency'), $json['settings']['currency']);
+ $this->assertTrue(is_array($json['settings']));
+ $this->assertTrue(is_array($json['aliases']));
+
+ // Note: Details of the content are tested in testUserResponse()
}
public function testIndex(): void
@@ -207,6 +209,29 @@
$this->assertSame('deleted', $result['status']);
}
+ /**
+ * Test user data response used in show and info actions
+ */
+ public function testUserResponse(): void
+ {
+ $user = $this->getTestUser('john@kolab.org');
+
+ $result = $this->invokeMethod(new UsersController(), 'userResponse', [$user]);
+
+ $this->assertEquals($user->id, $result['id']);
+ $this->assertEquals($user->email, $result['email']);
+ $this->assertEquals(1, $result['status']);
+ $this->assertTrue(is_array($result['statusInfo']));
+
+ $this->assertTrue(is_array($result['aliases']));
+ $this->assertCount(1, $result['aliases']);
+ $this->assertSame('john.doe@kolab.org', $result['aliases'][0]);
+
+ $this->assertTrue(is_array($result['settings']));
+ $this->assertSame('US', $result['settings']['country']);
+ $this->assertSame('USD', $result['settings']['currency']);
+ }
+
/**
* Test fetching user data/profile (GET /api/v4/users/<user-id>)
*/
@@ -217,8 +242,14 @@
// Test getting profile of self
$response = $this->actingAs($userA, 'api')->get("/api/v4/users/{$userA->id}");
+ $json = $response->json();
+
$response->assertStatus(200);
- $response->assertJson(['id' => $userA->id]);
+ $this->assertEquals($userA->id, $json['id']);
+ $this->assertEquals($userA->email, $json['email']);
+ $this->assertTrue(is_array($json['statusInfo']));
+ $this->assertTrue(is_array($json['settings']));
+ $this->assertTrue(is_array($json['aliases']));
// Test unauthorized access to a profile of other user
$user = $this->getTestUser('jack@kolab.org');
@@ -250,7 +281,7 @@
$response = $this->actingAs($userB)->get("/api/v4/users/{$userA->id}", []);
$response->assertStatus(403);
- // Test updating of self
+ // Test updating of self (empty request)
$response = $this->actingAs($userA)->put("/api/v4/users/{$userA->id}", []);
$response->assertStatus(200);
@@ -283,6 +314,7 @@
'billing_address' => 'billing',
'country' => 'CH',
'currency' => 'CHF',
+ 'aliases' => ['useralias1@userscontroller.com', 'useralias2@userscontroller.com']
];
$response = $this->actingAs($userA)->put("/api/v4/users/{$userA->id}", $post);
@@ -294,10 +326,14 @@
$this->assertSame("User data updated successfully", $json['message']);
$this->assertCount(2, $json);
$this->assertTrue($userA->password != $userA->fresh()->password);
- unset($post['password'], $post['password_confirmation']);
+ unset($post['password'], $post['password_confirmation'], $post['aliases']);
foreach ($post as $key => $value) {
$this->assertSame($value, $userA->getSetting($key));
}
+ $aliases = $userA->aliases()->get();
+ $this->assertCount(2, $aliases);
+ $this->assertSame('useralias1@userscontroller.com', $aliases[0]->alias);
+ $this->assertSame('useralias2@userscontroller.com', $aliases[1]->alias);
// Test unsetting values
$post = [
@@ -308,6 +344,7 @@
'billing_address' => '',
'country' => '',
'currency' => '',
+ 'aliases' => ['useralias2@userscontroller.com']
];
$response = $this->actingAs($userA)->put("/api/v4/users/{$userA->id}", $post);
@@ -318,10 +355,15 @@
$this->assertSame('success', $json['status']);
$this->assertSame("User data updated successfully", $json['message']);
$this->assertCount(2, $json);
+ unset($post['aliases']);
foreach ($post as $key => $value) {
$this->assertNull($userA->getSetting($key));
}
+ $aliases = $userA->aliases()->get();
+ $this->assertCount(1, $aliases);
+ $this->assertSame('useralias2@userscontroller.com', $aliases[0]->alias);
+ // TODO: Test error on aliases with invalid/non-existing/other-user's domain
// TODO: Test authorized update of other user
$this->markTestIncomplete();
}
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
@@ -28,6 +28,22 @@
parent::tearDown();
}
+ /**
+ * Tests for User::assignPackage()
+ */
+ public function testAssignPackage(): void
+ {
+ $this->markTestIncomplete();
+ }
+
+ /**
+ * Tests for User::assignPlan()
+ */
+ public function testAssignPlan(): void
+ {
+ $this->markTestIncomplete();
+ }
+
/**
* Verify user creation process
*/
@@ -158,7 +174,61 @@
$this->assertInstanceOf(User::class, $result);
$this->assertSame($user->id, $result->id);
- // TODO: Make sure searching is case-insensitive
- // TODO: Alias, eternal email
+ // Use an alias
+ $result = User::findByEmail('john.doe@kolab.org');
+ $this->assertInstanceOf(User::class, $result);
+ $this->assertSame($user->id, $result->id);
+
+ // TODO: searching by external email (setting)
+ $this->markTestIncomplete();
+ }
+
+ /**
+ * Tests for UserAliasesTrait::setAliases()
+ */
+ public function testSetAliases(): void
+ {
+ Queue::fake();
+
+ $user = $this->getTestUser('UserAccountA@UserAccount.com');
+
+ $this->assertCount(0, $user->aliases->all());
+
+ // Add an alias
+ $user->setAliases(['UserAlias@UserAccount.com']);
+
+ $aliases = $user->aliases()->get();
+ $this->assertCount(1, $aliases);
+ $this->assertSame('useralias@useraccount.com', $aliases[0]['alias']);
+
+ // Add another alias
+ $user->setAliases(['UserAlias@UserAccount.com', 'UserAlias2@UserAccount.com']);
+
+ $aliases = $user->aliases()->orderBy('alias')->get();
+ $this->assertCount(2, $aliases);
+ $this->assertSame('useralias2@useraccount.com', $aliases[0]->alias);
+ $this->assertSame('useralias@useraccount.com', $aliases[1]->alias);
+
+ // Remove an alias
+ $user->setAliases(['UserAlias@UserAccount.com']);
+
+ $aliases = $user->aliases()->get();
+ $this->assertCount(1, $aliases);
+ $this->assertSame('useralias@useraccount.com', $aliases[0]['alias']);
+
+ // Remove all aliases
+ $user->setAliases([]);
+
+ $this->assertCount(0, $user->aliases()->get());
+
+ // TODO: Test that the changes are propagated to ldap
+ }
+
+ /**
+ * Tests for UserSettingsTrait::setSettings()
+ */
+ public function testSetSettings(): void
+ {
+ $this->markTestIncomplete();
}
}
diff --git a/src/tests/TestCase.php b/src/tests/TestCase.php
--- a/src/tests/TestCase.php
+++ b/src/tests/TestCase.php
@@ -73,4 +73,22 @@
return $property->getValue($object);
}
+
+ /**
+ * Call protected/private method of a class.
+ *
+ * @param object $object Instantiated object that we will run method on.
+ * @param string $methodName Method name to call
+ * @param array $parameters Array of parameters to pass into method.
+ *
+ * @return mixed Method return.
+ */
+ public function invokeMethod($object, $methodName, array $parameters = array())
+ {
+ $reflection = new \ReflectionClass(get_class($object));
+ $method = $reflection->getMethod($methodName);
+ $method->setAccessible(true);
+
+ return $method->invokeArgs($object, $parameters);
+ }
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Apr 5, 11:46 AM (1 d, 10 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18833353
Default Alt Text
D976.1775389593.diff (33 KB)
Attached To
Mode
D976: Group: Additional user (Bifrost#T249344)
Attached
Detach File
Event Timeline