{{ domain.namespace }}
Domain DNS verification sample:
{{ domain.dns.join("\n") }}
Domain DNS configuration sample:
{{ domain.config.join("\n") }}
diff --git a/src/resources/vue/Admin/User.vue b/src/resources/vue/Admin/User.vue
index 1fd4a604..85084245 100644
--- a/src/resources/vue/Admin/User.vue
+++ b/src/resources/vue/Admin/User.vue
@@ -1,656 +1,694 @@
{{ user.email }}
Account balance {{ $root.price(wallet.balance) }}
Transactions
Email address |
{{ alias }} |
This user has no email aliases. |
Subscription |
Price |
{{ sku.name }} |
{{ sku.price }} |
This user has no subscriptions. |
¹ applied discount: {{ discount }}% - {{ discount_description }}
Name |
{{ domain.namespace }}
|
There are no domains in this account. |
Primary Email |
{{ item.email }}
{{ item.email }}
|
There are no users in this account. |
+
+
+
+
+
+
+ Email address |
+
+
+
+
+
+
+ {{ list.email }}
+ |
+
+
+
+
+ There are no distribution lists in this account. |
+
+
+
+
+
+
This will remove 2-Factor Authentication entitlement as well
as the user-configured factors.
Please, make sure to confirm the user identity properly.
diff --git a/src/routes/api.php b/src/routes/api.php
index 32210bce..0357a28b 100644
--- a/src/routes/api.php
+++ b/src/routes/api.php
@@ -1,170 +1,175 @@
'api',
'prefix' => $prefix . 'api/auth'
],
function ($router) {
Route::post('login', 'API\AuthController@login');
Route::group(
['middleware' => 'auth:api'],
function ($router) {
Route::get('info', 'API\AuthController@info');
Route::post('logout', 'API\AuthController@logout');
Route::post('refresh', 'API\AuthController@refresh');
}
);
}
);
Route::group(
[
'domain' => \config('app.domain'),
'middleware' => 'api',
'prefix' => $prefix . 'api/auth'
],
function ($router) {
Route::post('password-reset/init', 'API\PasswordResetController@init');
Route::post('password-reset/verify', 'API\PasswordResetController@verify');
Route::post('password-reset', 'API\PasswordResetController@reset');
Route::get('signup/plans', 'API\SignupController@plans');
Route::post('signup/init', 'API\SignupController@init');
Route::post('signup/verify', 'API\SignupController@verify');
Route::post('signup', 'API\SignupController@signup');
}
);
Route::group(
[
'domain' => \config('app.domain'),
'middleware' => 'auth:api',
'prefix' => $prefix . 'api/v4'
],
function () {
Route::apiResource('domains', API\V4\DomainsController::class);
Route::get('domains/{id}/confirm', 'API\V4\DomainsController@confirm');
Route::get('domains/{id}/status', 'API\V4\DomainsController@status');
Route::apiResource('groups', API\V4\GroupsController::class);
Route::get('groups/{id}/status', 'API\V4\GroupsController@status');
Route::apiResource('entitlements', API\V4\EntitlementsController::class);
Route::apiResource('packages', API\V4\PackagesController::class);
Route::apiResource('skus', API\V4\SkusController::class);
Route::apiResource('users', API\V4\UsersController::class);
Route::get('users/{id}/skus', 'API\V4\SkusController@userSkus');
Route::get('users/{id}/status', 'API\V4\UsersController@status');
Route::apiResource('wallets', API\V4\WalletsController::class);
Route::get('wallets/{id}/transactions', 'API\V4\WalletsController@transactions');
Route::get('wallets/{id}/receipts', 'API\V4\WalletsController@receipts');
Route::get('wallets/{id}/receipts/{receipt}', 'API\V4\WalletsController@receiptDownload');
Route::post('payments', 'API\V4\PaymentsController@store');
//Route::delete('payments', 'API\V4\PaymentsController@cancel');
Route::get('payments/mandate', 'API\V4\PaymentsController@mandate');
Route::post('payments/mandate', 'API\V4\PaymentsController@mandateCreate');
Route::put('payments/mandate', 'API\V4\PaymentsController@mandateUpdate');
Route::delete('payments/mandate', 'API\V4\PaymentsController@mandateDelete');
Route::get('payments/methods', 'API\V4\PaymentsController@paymentMethods');
Route::get('payments/pending', 'API\V4\PaymentsController@payments');
Route::get('payments/has-pending', 'API\V4\PaymentsController@hasPayments');
Route::get('openvidu/rooms', 'API\V4\OpenViduController@index');
Route::post('openvidu/rooms/{id}/close', 'API\V4\OpenViduController@closeRoom');
Route::post('openvidu/rooms/{id}/config', 'API\V4\OpenViduController@setRoomConfig');
// FIXME: I'm not sure about this one, should we use DELETE request maybe?
Route::post('openvidu/rooms/{id}/connections/{conn}/dismiss', 'API\V4\OpenViduController@dismissConnection');
Route::put('openvidu/rooms/{id}/connections/{conn}', 'API\V4\OpenViduController@updateConnection');
Route::post('openvidu/rooms/{id}/request/{reqid}/accept', 'API\V4\OpenViduController@acceptJoinRequest');
Route::post('openvidu/rooms/{id}/request/{reqid}/deny', 'API\V4\OpenViduController@denyJoinRequest');
}
);
// Note: In Laravel 7.x we could just use withoutMiddleware() instead of a separate group
Route::group(
[
'domain' => \config('app.domain'),
'prefix' => $prefix . 'api/v4'
],
function () {
Route::post('openvidu/rooms/{id}', 'API\V4\OpenViduController@joinRoom');
Route::post('openvidu/rooms/{id}/connections', 'API\V4\OpenViduController@createConnection');
// FIXME: I'm not sure about this one, should we use DELETE request maybe?
Route::post('openvidu/rooms/{id}/connections/{conn}/dismiss', 'API\V4\OpenViduController@dismissConnection');
Route::put('openvidu/rooms/{id}/connections/{conn}', 'API\V4\OpenViduController@updateConnection');
Route::post('openvidu/rooms/{id}/request/{reqid}/accept', 'API\V4\OpenViduController@acceptJoinRequest');
Route::post('openvidu/rooms/{id}/request/{reqid}/deny', 'API\V4\OpenViduController@denyJoinRequest');
}
);
Route::group(
[
'domain' => \config('app.domain'),
'middleware' => 'api',
'prefix' => $prefix . 'api/v4'
],
function ($router) {
Route::post('support/request', 'API\V4\SupportController@request');
}
);
Route::group(
[
'domain' => \config('app.domain'),
'prefix' => $prefix . 'api/webhooks',
],
function () {
Route::post('payment/{provider}', 'API\V4\PaymentsController@webhook');
Route::post('meet/openvidu', 'API\V4\OpenViduController@webhook');
}
);
Route::group(
[
'domain' => 'admin.' . \config('app.domain'),
'middleware' => ['auth:api', 'admin'],
'prefix' => $prefix . 'api/v4',
],
function () {
Route::apiResource('domains', API\V4\Admin\DomainsController::class);
Route::get('domains/{id}/confirm', 'API\V4\Admin\DomainsController@confirm');
Route::post('domains/{id}/suspend', 'API\V4\Admin\DomainsController@suspend');
Route::post('domains/{id}/unsuspend', 'API\V4\Admin\DomainsController@unsuspend');
Route::apiResource('entitlements', API\V4\Admin\EntitlementsController::class);
+
+ Route::apiResource('groups', API\V4\Admin\GroupsController::class);
+ Route::post('groups/{id}/suspend', 'API\V4\Admin\GroupsController@suspend');
+ Route::post('groups/{id}/unsuspend', 'API\V4\Admin\GroupsController@unsuspend');
+
Route::apiResource('packages', API\V4\Admin\PackagesController::class);
Route::apiResource('skus', API\V4\Admin\SkusController::class);
Route::apiResource('users', API\V4\Admin\UsersController::class);
Route::post('users/{id}/reset2FA', 'API\V4\Admin\UsersController@reset2FA');
Route::get('users/{id}/skus', 'API\V4\Admin\SkusController@userSkus');
Route::post('users/{id}/suspend', 'API\V4\Admin\UsersController@suspend');
Route::post('users/{id}/unsuspend', 'API\V4\Admin\UsersController@unsuspend');
Route::apiResource('wallets', API\V4\Admin\WalletsController::class);
Route::post('wallets/{id}/one-off', 'API\V4\Admin\WalletsController@oneOff');
Route::get('wallets/{id}/transactions', 'API\V4\Admin\WalletsController@transactions');
Route::apiResource('discounts', API\V4\Admin\DiscountsController::class);
Route::get('stats/chart/{chart}', 'API\V4\Admin\StatsController@chart');
}
);
diff --git a/src/tests/Browser/Admin/DistlistTest.php b/src/tests/Browser/Admin/DistlistTest.php
new file mode 100644
index 00000000..d89bd4e6
--- /dev/null
+++ b/src/tests/Browser/Admin/DistlistTest.php
@@ -0,0 +1,128 @@
+deleteTestGroup('group-test@kolab.org');
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function tearDown(): void
+ {
+ $this->deleteTestGroup('group-test@kolab.org');
+
+ parent::tearDown();
+ }
+
+ /**
+ * Test distlist info page (unauthenticated)
+ */
+ public function testDistlistUnauth(): void
+ {
+ // Test that the page requires authentication
+ $this->browse(function (Browser $browser) {
+ $user = $this->getTestUser('john@kolab.org');
+ $group = $this->getTestGroup('group-test@kolab.org');
+ $group->assignToWallet($user->wallets->first());
+
+ $browser->visit('/distlist/' . $group->id)->on(new Home());
+ });
+ }
+
+ /**
+ * Test distribution list info page
+ */
+ public function testInfo(): void
+ {
+ Queue::fake();
+
+ $this->browse(function (Browser $browser) {
+ $user = $this->getTestUser('john@kolab.org');
+ $group = $this->getTestGroup('group-test@kolab.org');
+ $group->assignToWallet($user->wallets->first());
+ $group->members = ['test1@gmail.com', 'test2@gmail.com'];
+ $group->save();
+
+ $distlist_page = new DistlistPage($group->id);
+ $user_page = new UserPage($user->id);
+
+ // Goto the distlist page
+ $browser->visit(new Home())
+ ->submitLogon('jeroen@jeroen.jeroen', 'jeroen', true)
+ ->on(new Dashboard())
+ ->visit($user_page)
+ ->on($user_page)
+ ->click('@nav #tab-distlists')
+ ->pause(1000)
+ ->click('@user-distlists table tbody tr:first-child td a')
+ ->on($distlist_page)
+ ->assertSeeIn('@distlist-info .card-title', $group->email)
+ ->with('@distlist-info form', function (Browser $browser) use ($group) {
+ $browser->assertElementsCount('.row', 3)
+ ->assertSeeIn('.row:nth-child(1) label', 'ID (Created at)')
+ ->assertSeeIn('.row:nth-child(1) #distlistid', "{$group->id} ({$group->created_at})")
+ ->assertSeeIn('.row:nth-child(2) label', 'Status')
+ ->assertSeeIn('.row:nth-child(2) #status.text-danger', 'Not Ready')
+ ->assertSeeIn('.row:nth-child(3) label', 'Recipients')
+ ->assertSeeIn('.row:nth-child(3) #members', $group->members[0])
+ ->assertSeeIn('.row:nth-child(3) #members', $group->members[1]);
+ });
+
+ // Test invalid group identifier
+ $browser->visit('/distlist/abc')->assertErrorPage(404);
+ });
+ }
+
+ /**
+ * Test suspending/unsuspending a distribution list
+ *
+ * @depends testInfo
+ */
+ public function testSuspendAndUnsuspend(): void
+ {
+ Queue::fake();
+
+ $this->browse(function (Browser $browser) {
+ $user = $this->getTestUser('john@kolab.org');
+ $group = $this->getTestGroup('group-test@kolab.org');
+ $group->assignToWallet($user->wallets->first());
+ $group->status = Group::STATUS_ACTIVE | Group::STATUS_LDAP_READY;
+ $group->save();
+
+ $browser->visit(new DistlistPage($group->id))
+ ->assertVisible('@distlist-info #button-suspend')
+ ->assertMissing('@distlist-info #button-unsuspend')
+ ->assertSeeIn('@distlist-info #status.text-success', 'Active')
+ ->click('@distlist-info #button-suspend')
+ ->assertToast(Toast::TYPE_SUCCESS, 'Distribution list suspended successfully.')
+ ->assertSeeIn('@distlist-info #status.text-warning', 'Suspended')
+ ->assertMissing('@distlist-info #button-suspend')
+ ->click('@distlist-info #button-unsuspend')
+ ->assertToast(Toast::TYPE_SUCCESS, 'Distribution list unsuspended successfully.')
+ ->assertSeeIn('@distlist-info #status.text-success', 'Active')
+ ->assertVisible('@distlist-info #button-suspend')
+ ->assertMissing('@distlist-info #button-unsuspend');
+ });
+ }
+}
diff --git a/src/tests/Browser/Admin/UserTest.php b/src/tests/Browser/Admin/UserTest.php
index ab632794..35068244 100644
--- a/src/tests/Browser/Admin/UserTest.php
+++ b/src/tests/Browser/Admin/UserTest.php
@@ -1,470 +1,498 @@
getTestUser('john@kolab.org');
$john->setSettings([
'phone' => '+48123123123',
'external_email' => 'john.doe.external@gmail.com',
]);
if ($john->isSuspended()) {
User::where('email', $john->email)->update(['status' => $john->status - User::STATUS_SUSPENDED]);
}
$wallet = $john->wallets()->first();
$wallet->discount()->dissociate();
$wallet->save();
Entitlement::where('cost', '>=', 5000)->delete();
-
+ $this->deleteTestGroup('group-test@kolab.org');
$this->clearMeetEntitlements();
}
/**
* {@inheritDoc}
*/
public function tearDown(): void
{
$john = $this->getTestUser('john@kolab.org');
$john->setSettings([
'phone' => null,
'external_email' => 'john.doe.external@gmail.com',
]);
if ($john->isSuspended()) {
User::where('email', $john->email)->update(['status' => $john->status - User::STATUS_SUSPENDED]);
}
$wallet = $john->wallets()->first();
$wallet->discount()->dissociate();
$wallet->save();
Entitlement::where('cost', '>=', 5000)->delete();
-
+ $this->deleteTestGroup('group-test@kolab.org');
$this->clearMeetEntitlements();
parent::tearDown();
}
/**
* Test user info page (unauthenticated)
*/
public function testUserUnauth(): void
{
// Test that the page requires authentication
$this->browse(function (Browser $browser) {
$jack = $this->getTestUser('jack@kolab.org');
$browser->visit('/user/' . $jack->id)->on(new Home());
});
}
/**
* Test user info page
*/
public function testUserInfo(): void
{
$this->browse(function (Browser $browser) {
$jack = $this->getTestUser('jack@kolab.org');
$page = new UserPage($jack->id);
$browser->visit(new Home())
->submitLogon('jeroen@jeroen.jeroen', 'jeroen', true)
->on(new Dashboard())
->visit($page)
->on($page);
// Assert main info box content
$browser->assertSeeIn('@user-info .card-title', $jack->email)
->with('@user-info form', function (Browser $browser) use ($jack) {
$browser->assertElementsCount('.row', 7)
->assertSeeIn('.row:nth-child(1) label', 'Managed by')
->assertSeeIn('.row:nth-child(1) #manager a', 'john@kolab.org')
->assertSeeIn('.row:nth-child(2) label', 'ID (Created at)')
->assertSeeIn('.row:nth-child(2) #userid', "{$jack->id} ({$jack->created_at})")
->assertSeeIn('.row:nth-child(3) label', 'Status')
->assertSeeIn('.row:nth-child(3) #status span.text-success', 'Active')
->assertSeeIn('.row:nth-child(4) label', 'First name')
->assertSeeIn('.row:nth-child(4) #first_name', 'Jack')
->assertSeeIn('.row:nth-child(5) label', 'Last name')
->assertSeeIn('.row:nth-child(5) #last_name', 'Daniels')
->assertSeeIn('.row:nth-child(6) label', 'External email')
->assertMissing('.row:nth-child(6) #external_email a')
->assertSeeIn('.row:nth-child(7) label', 'Country')
->assertSeeIn('.row:nth-child(7) #country', 'United States');
});
// Some tabs are loaded in background, wait a second
$browser->pause(500)
- ->assertElementsCount('@nav a', 5);
+ ->assertElementsCount('@nav a', 6);
// Note: Finances tab is tested in UserFinancesTest.php
$browser->assertSeeIn('@nav #tab-finances', 'Finances');
// Assert Aliases tab
$browser->assertSeeIn('@nav #tab-aliases', 'Aliases (1)')
->click('@nav #tab-aliases')
->whenAvailable('@user-aliases', function (Browser $browser) {
$browser->assertElementsCount('table tbody tr', 1)
->assertSeeIn('table tbody tr:first-child td:first-child', 'jack.daniels@kolab.org')
->assertMissing('table tfoot');
});
// Assert Subscriptions tab
$browser->assertSeeIn('@nav #tab-subscriptions', 'Subscriptions (3)')
->click('@nav #tab-subscriptions')
->with('@user-subscriptions', function (Browser $browser) {
$browser->assertElementsCount('table tbody tr', 3)
->assertSeeIn('table tbody tr:nth-child(1) td:first-child', 'User Mailbox')
->assertSeeIn('table tbody tr:nth-child(1) td:last-child', '4,44 CHF')
->assertSeeIn('table tbody tr:nth-child(2) td:first-child', 'Storage Quota 2 GB')
->assertSeeIn('table tbody tr:nth-child(2) td:last-child', '0,00 CHF')
->assertSeeIn('table tbody tr:nth-child(3) td:first-child', 'Groupware Features')
->assertSeeIn('table tbody tr:nth-child(3) td:last-child', '5,55 CHF')
->assertMissing('table tfoot')
->assertMissing('#reset2fa');
});
// Assert Domains tab
$browser->assertSeeIn('@nav #tab-domains', 'Domains (0)')
->click('@nav #tab-domains')
->with('@user-domains', function (Browser $browser) {
$browser->assertElementsCount('table tbody tr', 0)
->assertSeeIn('table tfoot tr td', 'There are no domains in this account.');
});
// Assert Users tab
$browser->assertSeeIn('@nav #tab-users', 'Users (0)')
->click('@nav #tab-users')
->with('@user-users', function (Browser $browser) {
$browser->assertElementsCount('table tbody tr', 0)
->assertSeeIn('table tfoot tr td', 'There are no users in this account.');
});
+
+ // Assert Distribution lists tab
+ $browser->assertSeeIn('@nav #tab-distlists', 'Distribution lists (0)')
+ ->click('@nav #tab-distlists')
+ ->with('@user-distlists', function (Browser $browser) {
+ $browser->assertElementsCount('table tbody tr', 0)
+ ->assertSeeIn('table tfoot tr td', 'There are no distribution lists in this account.');
+ });
});
}
/**
* Test user info page (continue)
*
* @depends testUserInfo
*/
public function testUserInfo2(): void
{
$this->browse(function (Browser $browser) {
$john = $this->getTestUser('john@kolab.org');
$page = new UserPage($john->id);
$discount = Discount::where('code', 'TEST')->first();
$wallet = $john->wallet();
$wallet->discount()->associate($discount);
$wallet->debit(2010);
$wallet->save();
+ $group = $this->getTestGroup('group-test@kolab.org');
+ $group->assignToWallet($john->wallets->first());
// Click the managed-by link on Jack's page
$browser->click('@user-info #manager a')
->on($page);
// Assert main info box content
$browser->assertSeeIn('@user-info .card-title', $john->email)
->with('@user-info form', function (Browser $browser) use ($john) {
$ext_email = $john->getSetting('external_email');
$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')
->assertSeeIn('.row:nth-child(2) #status span.text-success', 'Active')
->assertSeeIn('.row:nth-child(3) label', 'First name')
->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', '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');
});
// Some tabs are loaded in background, wait a second
$browser->pause(500)
- ->assertElementsCount('@nav a', 5);
+ ->assertElementsCount('@nav a', 6);
// Note: Finances tab is tested in UserFinancesTest.php
$browser->assertSeeIn('@nav #tab-finances', 'Finances');
// Assert Aliases tab
$browser->assertSeeIn('@nav #tab-aliases', 'Aliases (1)')
->click('@nav #tab-aliases')
->whenAvailable('@user-aliases', function (Browser $browser) {
$browser->assertElementsCount('table tbody tr', 1)
->assertSeeIn('table tbody tr:first-child td:first-child', 'john.doe@kolab.org')
->assertMissing('table tfoot');
});
// Assert Subscriptions tab
$browser->assertSeeIn('@nav #tab-subscriptions', 'Subscriptions (3)')
->click('@nav #tab-subscriptions')
->with('@user-subscriptions', function (Browser $browser) {
$browser->assertElementsCount('table tbody tr', 3)
->assertSeeIn('table tbody tr:nth-child(1) td:first-child', 'User Mailbox')
->assertSeeIn('table tbody tr:nth-child(1) td:last-child', '3,99 CHF/month¹')
->assertSeeIn('table tbody tr:nth-child(2) td:first-child', 'Storage Quota 2 GB')
->assertSeeIn('table tbody tr:nth-child(2) td:last-child', '0,00 CHF/month¹')
->assertSeeIn('table tbody tr:nth-child(3) td:first-child', 'Groupware Features')
->assertSeeIn('table tbody tr:nth-child(3) td:last-child', '4,99 CHF/month¹')
->assertMissing('table tfoot')
->assertSeeIn('table + .hint', '¹ applied discount: 10% - Test voucher');
});
// Assert Domains tab
$browser->assertSeeIn('@nav #tab-domains', 'Domains (1)')
->click('@nav #tab-domains')
->with('@user-domains table', function (Browser $browser) {
$browser->assertElementsCount('tbody tr', 1)
->assertSeeIn('tbody tr:nth-child(1) td:first-child a', 'kolab.org')
->assertVisible('tbody tr:nth-child(1) td:first-child svg.text-success')
->assertMissing('tfoot');
});
+ // Assert Distribution lists tab
+ $browser->assertSeeIn('@nav #tab-distlists', 'Distribution lists (1)')
+ ->click('@nav #tab-distlists')
+ ->with('@user-distlists table', function (Browser $browser) {
+ $browser->assertElementsCount('tbody tr', 1)
+ ->assertSeeIn('tbody tr:nth-child(1) td:first-child a', 'group-test@kolab.org')
+ ->assertVisible('tbody tr:nth-child(1) td:first-child svg.text-danger')
+ ->assertMissing('tfoot');
+ });
+
// Assert Users tab
$browser->assertSeeIn('@nav #tab-users', 'Users (4)')
->click('@nav #tab-users')
->with('@user-users table', function (Browser $browser) {
$browser->assertElementsCount('tbody tr', 4)
->assertSeeIn('tbody tr:nth-child(1) td:first-child a', 'jack@kolab.org')
->assertVisible('tbody tr:nth-child(1) td:first-child svg.text-success')
->assertSeeIn('tbody tr:nth-child(2) td:first-child a', 'joe@kolab.org')
->assertVisible('tbody tr:nth-child(2) td:first-child svg.text-success')
->assertSeeIn('tbody tr:nth-child(3) td:first-child span', 'john@kolab.org')
->assertVisible('tbody tr:nth-child(3) td:first-child svg.text-success')
->assertSeeIn('tbody tr:nth-child(4) td:first-child a', 'ned@kolab.org')
->assertVisible('tbody tr:nth-child(4) td:first-child svg.text-success')
->assertMissing('tfoot');
});
});
// Now we go to Ned's info page, he's a controller on John's wallet
$this->browse(function (Browser $browser) {
$ned = $this->getTestUser('ned@kolab.org');
$beta_sku = Sku::where('title', 'beta')->first();
$storage_sku = Sku::where('title', 'storage')->first();
$wallet = $ned->wallet();
// Add an extra storage and beta entitlement with different prices
Entitlement::create([
'wallet_id' => $wallet->id,
'sku_id' => $beta_sku->id,
'cost' => 5010,
'entitleable_id' => $ned->id,
'entitleable_type' => User::class
]);
Entitlement::create([
'wallet_id' => $wallet->id,
'sku_id' => $storage_sku->id,
'cost' => 5000,
'entitleable_id' => $ned->id,
'entitleable_type' => User::class
]);
$page = new UserPage($ned->id);
$browser->click('@user-users tbody tr:nth-child(4) td:first-child a')
->on($page);
// Assert main info box content
$browser->assertSeeIn('@user-info .card-title', $ned->email)
->with('@user-info form', function (Browser $browser) use ($ned) {
$browser->assertSeeIn('.row:nth-child(2) label', 'ID (Created at)')
->assertSeeIn('.row:nth-child(2) #userid', "{$ned->id} ({$ned->created_at})");
});
// Some tabs are loaded in background, wait a second
$browser->pause(500)
- ->assertElementsCount('@nav a', 5);
+ ->assertElementsCount('@nav a', 6);
// Note: Finances tab is tested in UserFinancesTest.php
$browser->assertSeeIn('@nav #tab-finances', 'Finances');
// Assert Aliases tab
$browser->assertSeeIn('@nav #tab-aliases', 'Aliases (0)')
->click('@nav #tab-aliases')
->whenAvailable('@user-aliases', function (Browser $browser) {
$browser->assertElementsCount('table tbody tr', 0)
->assertSeeIn('table tfoot tr td', 'This user has no email aliases.');
});
// Assert Subscriptions tab, we expect John's discount here
$browser->assertSeeIn('@nav #tab-subscriptions', 'Subscriptions (6)')
->click('@nav #tab-subscriptions')
->with('@user-subscriptions', function (Browser $browser) {
$browser->assertElementsCount('table tbody tr', 6)
->assertSeeIn('table tbody tr:nth-child(1) td:first-child', 'User Mailbox')
->assertSeeIn('table tbody tr:nth-child(1) td:last-child', '3,99 CHF/month¹')
->assertSeeIn('table tbody tr:nth-child(2) td:first-child', 'Storage Quota 3 GB')
->assertSeeIn('table tbody tr:nth-child(2) td:last-child', '45,00 CHF/month¹')
->assertSeeIn('table tbody tr:nth-child(3) td:first-child', 'Groupware Features')
->assertSeeIn('table tbody tr:nth-child(3) td:last-child', '4,99 CHF/month¹')
->assertSeeIn('table tbody tr:nth-child(4) td:first-child', 'Activesync')
->assertSeeIn('table tbody tr:nth-child(4) td:last-child', '0,90 CHF/month¹')
->assertSeeIn('table tbody tr:nth-child(5) td:first-child', '2-Factor Authentication')
->assertSeeIn('table tbody tr:nth-child(5) td:last-child', '0,00 CHF/month¹')
->assertSeeIn('table tbody tr:nth-child(6) td:first-child', 'Private Beta (invitation only)')
->assertSeeIn('table tbody tr:nth-child(6) td:last-child', '45,09 CHF/month¹')
->assertMissing('table tfoot')
->assertSeeIn('table + .hint', '¹ applied discount: 10% - Test voucher')
->assertSeeIn('#reset2fa', 'Reset 2-Factor Auth');
});
// We don't expect John's domains here
$browser->assertSeeIn('@nav #tab-domains', 'Domains (0)')
->click('@nav #tab-domains')
->with('@user-domains', function (Browser $browser) {
$browser->assertElementsCount('table tbody tr', 0)
->assertSeeIn('table tfoot tr td', 'There are no domains in this account.');
});
// We don't expect John's users here
$browser->assertSeeIn('@nav #tab-users', 'Users (0)')
->click('@nav #tab-users')
->with('@user-users', function (Browser $browser) {
$browser->assertElementsCount('table tbody tr', 0)
->assertSeeIn('table tfoot tr td', 'There are no users in this account.');
});
+
+ // We don't expect John's distribution lists here
+ $browser->assertSeeIn('@nav #tab-distlists', 'Distribution lists (0)')
+ ->click('@nav #tab-distlists')
+ ->with('@user-distlists', function (Browser $browser) {
+ $browser->assertElementsCount('table tbody tr', 0)
+ ->assertSeeIn('table tfoot tr td', 'There are no distribution lists in this account.');
+ });
});
}
/**
* Test editing an external email
*
* @depends testUserInfo2
*/
public function testExternalEmail(): void
{
$this->browse(function (Browser $browser) {
$john = $this->getTestUser('john@kolab.org');
$browser->visit(new UserPage($john->id))
->waitFor('@user-info #external_email button')
->click('@user-info #external_email button')
// Test dialog content, and closing it with Cancel button
->with(new Dialog('#email-dialog'), function (Browser $browser) {
$browser->assertSeeIn('@title', 'External email')
->assertFocused('@body input')
->assertValue('@body input', 'john.doe.external@gmail.com')
->assertSeeIn('@button-cancel', 'Cancel')
->assertSeeIn('@button-action', 'Submit')
->click('@button-cancel');
})
->assertMissing('#email-dialog')
->click('@user-info #external_email button')
// Test email validation error handling, and email update
->with(new Dialog('#email-dialog'), function (Browser $browser) {
$browser->type('@body input', 'test')
->click('@button-action')
->waitFor('@body input.is-invalid')
->assertSeeIn(
'@body input + .invalid-feedback',
'The external email must be a valid email address.'
)
->assertToast(Toast::TYPE_ERROR, 'Form validation error')
->type('@body input', 'test@test.com')
->click('@button-action');
})
->assertToast(Toast::TYPE_SUCCESS, 'User data updated successfully.')
->assertSeeIn('@user-info #external_email a', 'test@test.com')
->click('@user-info #external_email button')
->with(new Dialog('#email-dialog'), function (Browser $browser) {
$browser->assertValue('@body input', 'test@test.com')
->assertMissing('@body input.is-invalid')
->assertMissing('@body input + .invalid-feedback')
->click('@button-cancel');
})
->assertSeeIn('@user-info #external_email a', 'test@test.com');
// $john->getSetting() may not work here as it uses internal cache
// read the value form database
$current_ext_email = $john->settings()->where('key', 'external_email')->first()->value;
$this->assertSame('test@test.com', $current_ext_email);
});
}
/**
* Test suspending/unsuspending the user
*/
public function testSuspendAndUnsuspend(): void
{
$this->browse(function (Browser $browser) {
$john = $this->getTestUser('john@kolab.org');
$browser->visit(new UserPage($john->id))
->assertVisible('@user-info #button-suspend')
->assertMissing('@user-info #button-unsuspend')
->click('@user-info #button-suspend')
->assertToast(Toast::TYPE_SUCCESS, 'User suspended successfully.')
->assertSeeIn('@user-info #status span.text-warning', 'Suspended')
->assertMissing('@user-info #button-suspend')
->click('@user-info #button-unsuspend')
->assertToast(Toast::TYPE_SUCCESS, 'User unsuspended successfully.')
->assertSeeIn('@user-info #status span.text-success', 'Active')
->assertVisible('@user-info #button-suspend')
->assertMissing('@user-info #button-unsuspend');
});
}
/**
* Test resetting 2FA for the user
*/
public function testReset2FA(): void
{
$this->browse(function (Browser $browser) {
$this->deleteTestUser('userstest1@kolabnow.com');
$user = $this->getTestUser('userstest1@kolabnow.com');
$sku2fa = Sku::firstOrCreate(['title' => '2fa']);
$user->assignSku($sku2fa);
SecondFactor::seed('userstest1@kolabnow.com');
$browser->visit(new UserPage($user->id))
->click('@nav #tab-subscriptions')
->with('@user-subscriptions', function (Browser $browser) use ($sku2fa) {
$browser->waitFor('#reset2fa')
->assertVisible('#sku' . $sku2fa->id);
})
->assertSeeIn('@nav #tab-subscriptions', 'Subscriptions (1)')
->click('#reset2fa')
->with(new Dialog('#reset-2fa-dialog'), function (Browser $browser) {
$browser->assertSeeIn('@title', '2-Factor Authentication Reset')
->assertSeeIn('@button-cancel', 'Cancel')
->assertSeeIn('@button-action', 'Reset')
->click('@button-action');
})
->assertToast(Toast::TYPE_SUCCESS, '2-Factor authentication reset successfully.')
->assertMissing('#sku' . $sku2fa->id)
->assertSeeIn('@nav #tab-subscriptions', 'Subscriptions (0)');
});
}
}
diff --git a/src/tests/Browser/Pages/Admin/User.php b/src/tests/Browser/Pages/Admin/Distlist.php
similarity index 54%
copy from src/tests/Browser/Pages/Admin/User.php
copy to src/tests/Browser/Pages/Admin/Distlist.php
index d8e0e555..1742c455 100644
--- a/src/tests/Browser/Pages/Admin/User.php
+++ b/src/tests/Browser/Pages/Admin/Distlist.php
@@ -1,63 +1,57 @@
userid = $userid;
+ $this->listid = $listid;
}
/**
* Get the URL for the page.
*
* @return string
*/
public function url(): string
{
- return '/user/' . $this->userid;
+ return '/distlist/' . $this->listid;
}
/**
* Assert that the browser is on the page.
*
* @param \Laravel\Dusk\Browser $browser The browser object
*
* @return void
*/
public function assert($browser): void
{
$browser->waitForLocation($this->url())
- ->waitUntilMissing('@app .app-loader')
- ->waitFor('@user-info');
+ ->waitFor('@distlist-info');
}
/**
* Get the element shortcuts for the page.
*
* @return array
*/
public function elements(): array
{
return [
'@app' => '#app',
- '@user-info' => '#user-info',
- '@nav' => 'ul.nav-tabs',
- '@user-finances' => '#user-finances',
- '@user-aliases' => '#user-aliases',
- '@user-subscriptions' => '#user-subscriptions',
- '@user-domains' => '#user-domains',
- '@user-users' => '#user-users',
+ '@distlist-info' => '#distlist-info',
+ '@distlist-config' => '#distlist-config',
];
}
}
diff --git a/src/tests/Browser/Pages/Admin/User.php b/src/tests/Browser/Pages/Admin/User.php
index d8e0e555..120c67bd 100644
--- a/src/tests/Browser/Pages/Admin/User.php
+++ b/src/tests/Browser/Pages/Admin/User.php
@@ -1,63 +1,64 @@
userid = $userid;
}
/**
* Get the URL for the page.
*
* @return string
*/
public function url(): string
{
return '/user/' . $this->userid;
}
/**
* Assert that the browser is on the page.
*
* @param \Laravel\Dusk\Browser $browser The browser object
*
* @return void
*/
public function assert($browser): void
{
$browser->waitForLocation($this->url())
->waitUntilMissing('@app .app-loader')
->waitFor('@user-info');
}
/**
* Get the element shortcuts for the page.
*
* @return array
*/
public function elements(): array
{
return [
'@app' => '#app',
'@user-info' => '#user-info',
'@nav' => 'ul.nav-tabs',
'@user-finances' => '#user-finances',
'@user-aliases' => '#user-aliases',
'@user-subscriptions' => '#user-subscriptions',
+ '@user-distlists' => '#user-distlists',
'@user-domains' => '#user-domains',
'@user-users' => '#user-users',
];
}
}
diff --git a/src/tests/Feature/Controller/Admin/GroupsTest.php b/src/tests/Feature/Controller/Admin/GroupsTest.php
new file mode 100644
index 00000000..2e886d16
--- /dev/null
+++ b/src/tests/Feature/Controller/Admin/GroupsTest.php
@@ -0,0 +1,184 @@
+deleteTestGroup('group-test@kolab.org');
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function tearDown(): void
+ {
+ $this->deleteTestGroup('group-test@kolab.org');
+
+ parent::tearDown();
+ }
+
+ /**
+ * Test groups searching (/api/v4/groups)
+ */
+ public function testIndex(): void
+ {
+ $user = $this->getTestUser('john@kolab.org');
+ $admin = $this->getTestUser('jeroen@jeroen.jeroen');
+ $group = $this->getTestGroup('group-test@kolab.org');
+ $group->assignToWallet($user->wallets->first());
+
+ // Non-admin user
+ $response = $this->actingAs($user)->get("api/v4/groups");
+ $response->assertStatus(403);
+
+ // Search with no search criteria
+ $response = $this->actingAs($admin)->get("api/v4/groups");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame(0, $json['count']);
+ $this->assertSame([], $json['list']);
+
+ // Search with no matches expected
+ $response = $this->actingAs($admin)->get("api/v4/groups?search=john@kolab.org");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame(0, $json['count']);
+ $this->assertSame([], $json['list']);
+
+ // Search by email
+ $response = $this->actingAs($admin)->get("api/v4/groups?search={$group->email}");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame(1, $json['count']);
+ $this->assertCount(1, $json['list']);
+ $this->assertSame($group->email, $json['list'][0]['email']);
+
+ // Search by owner
+ $response = $this->actingAs($admin)->get("api/v4/groups?owner={$user->id}");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame(1, $json['count']);
+ $this->assertCount(1, $json['list']);
+ $this->assertSame($group->email, $json['list'][0]['email']);
+
+ // Search by owner (Ned is a controller on John's wallets,
+ // here we expect only domains assigned to Ned's wallet(s))
+ $ned = $this->getTestUser('ned@kolab.org');
+ $response = $this->actingAs($admin)->get("api/v4/groups?owner={$ned->id}");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame(0, $json['count']);
+ $this->assertCount(0, $json['list']);
+ }
+
+ /**
+ * Test group creating (POST /api/v4/groups)
+ */
+ public function testStore(): void
+ {
+ $user = $this->getTestUser('john@kolab.org');
+ $admin = $this->getTestUser('jeroen@jeroen.jeroen');
+
+ // Test unauthorized access to admin API
+ $response = $this->actingAs($user)->post("/api/v4/groups", []);
+ $response->assertStatus(403);
+
+ // Admin can't create groups
+ $response = $this->actingAs($admin)->post("/api/v4/groups", []);
+ $response->assertStatus(404);
+ }
+
+ /**
+ * Test group suspending (POST /api/v4/groups/
/suspend)
+ */
+ public function testSuspend(): void
+ {
+ Queue::fake(); // disable jobs
+
+ $user = $this->getTestUser('john@kolab.org');
+ $admin = $this->getTestUser('jeroen@jeroen.jeroen');
+ $group = $this->getTestGroup('group-test@kolab.org');
+ $group->assignToWallet($user->wallets->first());
+
+ // Test unauthorized access to admin API
+ $response = $this->actingAs($user)->post("/api/v4/groups/{$group->id}/suspend", []);
+ $response->assertStatus(403);
+
+ // Test non-existing group ID
+ $response = $this->actingAs($admin)->post("/api/v4/groups/abc/suspend", []);
+ $response->assertStatus(404);
+
+ $this->assertFalse($group->fresh()->isSuspended());
+
+ // Test suspending the group
+ $response = $this->actingAs($admin)->post("/api/v4/groups/{$group->id}/suspend", []);
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame('success', $json['status']);
+ $this->assertSame("Distribution list suspended successfully.", $json['message']);
+ $this->assertCount(2, $json);
+
+ $this->assertTrue($group->fresh()->isSuspended());
+ }
+
+ /**
+ * Test user un-suspending (POST /api/v4/users//unsuspend)
+ */
+ public function testUnsuspend(): void
+ {
+ Queue::fake(); // disable jobs
+
+ $user = $this->getTestUser('john@kolab.org');
+ $admin = $this->getTestUser('jeroen@jeroen.jeroen');
+ $group = $this->getTestGroup('group-test@kolab.org');
+ $group->assignToWallet($user->wallets->first());
+ $group->status |= Group::STATUS_SUSPENDED;
+ $group->save();
+
+ // Test unauthorized access to admin API
+ $response = $this->actingAs($user)->post("/api/v4/groups/{$group->id}/unsuspend", []);
+ $response->assertStatus(403);
+
+ // Invalid group ID
+ $response = $this->actingAs($admin)->post("/api/v4/groups/abc/unsuspend", []);
+ $response->assertStatus(404);
+
+ $this->assertTrue($group->fresh()->isSuspended());
+
+ // Test suspending the group
+ $response = $this->actingAs($admin)->post("/api/v4/groups/{$group->id}/unsuspend", []);
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame('success', $json['status']);
+ $this->assertSame("Distribution list unsuspended successfully.", $json['message']);
+ $this->assertCount(2, $json);
+
+ $this->assertFalse($group->fresh()->isSuspended());
+ }
+}
diff --git a/src/tests/Feature/Controller/Admin/UsersTest.php b/src/tests/Feature/Controller/Admin/UsersTest.php
index 9070c0a0..ad995165 100644
--- a/src/tests/Feature/Controller/Admin/UsersTest.php
+++ b/src/tests/Feature/Controller/Admin/UsersTest.php
@@ -1,341 +1,355 @@
deleteTestUser('UsersControllerTest1@userscontroller.com');
$this->deleteTestUser('test@testsearch.com');
$this->deleteTestDomain('testsearch.com');
+ $this->deleteTestGroup('group-test@kolab.org');
$jack = $this->getTestUser('jack@kolab.org');
$jack->setSetting('external_email', null);
}
/**
* {@inheritDoc}
*/
public function tearDown(): void
{
$this->deleteTestUser('UsersControllerTest1@userscontroller.com');
$this->deleteTestUser('test@testsearch.com');
$this->deleteTestDomain('testsearch.com');
$jack = $this->getTestUser('jack@kolab.org');
$jack->setSetting('external_email', null);
parent::tearDown();
}
/**
* Test users searching (/api/v4/users)
*/
public function testIndex(): void
{
$user = $this->getTestUser('john@kolab.org');
$admin = $this->getTestUser('jeroen@jeroen.jeroen');
+ $group = $this->getTestGroup('group-test@kolab.org');
+ $group->assignToWallet($user->wallets->first());
// Non-admin user
$response = $this->actingAs($user)->get("api/v4/users");
$response->assertStatus(403);
// Search with no search criteria
$response = $this->actingAs($admin)->get("api/v4/users");
$response->assertStatus(200);
$json = $response->json();
$this->assertSame(0, $json['count']);
$this->assertSame([], $json['list']);
// Search with no matches expected
$response = $this->actingAs($admin)->get("api/v4/users?search=abcd1234efgh5678");
$response->assertStatus(200);
$json = $response->json();
$this->assertSame(0, $json['count']);
$this->assertSame([], $json['list']);
// Search by domain
$response = $this->actingAs($admin)->get("api/v4/users?search=kolab.org");
$response->assertStatus(200);
$json = $response->json();
$this->assertSame(1, $json['count']);
$this->assertCount(1, $json['list']);
$this->assertSame($user->id, $json['list'][0]['id']);
$this->assertSame($user->email, $json['list'][0]['email']);
// Search by user ID
$response = $this->actingAs($admin)->get("api/v4/users?search={$user->id}");
$response->assertStatus(200);
$json = $response->json();
$this->assertSame(1, $json['count']);
$this->assertCount(1, $json['list']);
$this->assertSame($user->id, $json['list'][0]['id']);
$this->assertSame($user->email, $json['list'][0]['email']);
// Search by email (primary)
$response = $this->actingAs($admin)->get("api/v4/users?search=john@kolab.org");
$response->assertStatus(200);
$json = $response->json();
$this->assertSame(1, $json['count']);
$this->assertCount(1, $json['list']);
$this->assertSame($user->id, $json['list'][0]['id']);
$this->assertSame($user->email, $json['list'][0]['email']);
// Search by email (alias)
$response = $this->actingAs($admin)->get("api/v4/users?search=john.doe@kolab.org");
$response->assertStatus(200);
$json = $response->json();
$this->assertSame(1, $json['count']);
$this->assertCount(1, $json['list']);
$this->assertSame($user->id, $json['list'][0]['id']);
$this->assertSame($user->email, $json['list'][0]['email']);
// Search by email (external), expect two users in a result
$jack = $this->getTestUser('jack@kolab.org');
$jack->setSetting('external_email', 'john.doe.external@gmail.com');
$response = $this->actingAs($admin)->get("api/v4/users?search=john.doe.external@gmail.com");
$response->assertStatus(200);
$json = $response->json();
$this->assertSame(2, $json['count']);
$this->assertCount(2, $json['list']);
$emails = array_column($json['list'], 'email');
$this->assertContains($user->email, $emails);
$this->assertContains($jack->email, $emails);
// Search by owner
$response = $this->actingAs($admin)->get("api/v4/users?owner={$user->id}");
$response->assertStatus(200);
$json = $response->json();
$this->assertSame(4, $json['count']);
$this->assertCount(4, $json['list']);
// Search by owner (Ned is a controller on John's wallets,
// here we expect only users assigned to Ned's wallet(s))
$ned = $this->getTestUser('ned@kolab.org');
$response = $this->actingAs($admin)->get("api/v4/users?owner={$ned->id}");
$response->assertStatus(200);
$json = $response->json();
$this->assertSame(0, $json['count']);
$this->assertCount(0, $json['list']);
+ // Search by distribution list email
+ $response = $this->actingAs($admin)->get("api/v4/users?search=group-test@kolab.org");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame(1, $json['count']);
+ $this->assertCount(1, $json['list']);
+ $this->assertSame($user->id, $json['list'][0]['id']);
+ $this->assertSame($user->email, $json['list'][0]['email']);
+
// Deleted users/domains
$domain = $this->getTestDomain('testsearch.com', ['type' => \App\Domain::TYPE_EXTERNAL]);
$user = $this->getTestUser('test@testsearch.com');
$plan = \App\Plan::where('title', 'group')->first();
$user->assignPlan($plan, $domain);
$user->setAliases(['alias@testsearch.com']);
Queue::fake();
$user->delete();
$response = $this->actingAs($admin)->get("api/v4/users?search=test@testsearch.com");
$response->assertStatus(200);
$json = $response->json();
$this->assertSame(1, $json['count']);
$this->assertCount(1, $json['list']);
$this->assertSame($user->id, $json['list'][0]['id']);
$this->assertSame($user->email, $json['list'][0]['email']);
$this->assertTrue($json['list'][0]['isDeleted']);
$response = $this->actingAs($admin)->get("api/v4/users?search=alias@testsearch.com");
$response->assertStatus(200);
$json = $response->json();
$this->assertSame(1, $json['count']);
$this->assertCount(1, $json['list']);
$this->assertSame($user->id, $json['list'][0]['id']);
$this->assertSame($user->email, $json['list'][0]['email']);
$this->assertTrue($json['list'][0]['isDeleted']);
$response = $this->actingAs($admin)->get("api/v4/users?search=testsearch.com");
$response->assertStatus(200);
$json = $response->json();
$this->assertSame(1, $json['count']);
$this->assertCount(1, $json['list']);
$this->assertSame($user->id, $json['list'][0]['id']);
$this->assertSame($user->email, $json['list'][0]['email']);
$this->assertTrue($json['list'][0]['isDeleted']);
}
/**
* Test reseting 2FA (POST /api/v4/users//reset2FA)
*/
public function testReset2FA(): void
{
$user = $this->getTestUser('UsersControllerTest1@userscontroller.com');
$admin = $this->getTestUser('jeroen@jeroen.jeroen');
$sku2fa = Sku::firstOrCreate(['title' => '2fa']);
$user->assignSku($sku2fa);
SecondFactor::seed('userscontrollertest1@userscontroller.com');
// Test unauthorized access to admin API
$response = $this->actingAs($user)->post("/api/v4/users/{$user->id}/reset2FA", []);
$response->assertStatus(403);
$entitlements = $user->fresh()->entitlements()->where('sku_id', $sku2fa->id)->get();
$this->assertCount(1, $entitlements);
$sf = new SecondFactor($user);
$this->assertCount(1, $sf->factors());
// Test reseting 2FA
$response = $this->actingAs($admin)->post("/api/v4/users/{$user->id}/reset2FA", []);
$response->assertStatus(200);
$json = $response->json();
$this->assertSame('success', $json['status']);
$this->assertSame("2-Factor authentication reset successfully.", $json['message']);
$this->assertCount(2, $json);
$entitlements = $user->fresh()->entitlements()->where('sku_id', $sku2fa->id)->get();
$this->assertCount(0, $entitlements);
$sf = new SecondFactor($user);
$this->assertCount(0, $sf->factors());
}
/**
* Test user suspending (POST /api/v4/users//suspend)
*/
public function testSuspend(): void
{
Queue::fake(); // disable jobs
$user = $this->getTestUser('UsersControllerTest1@userscontroller.com');
$admin = $this->getTestUser('jeroen@jeroen.jeroen');
// Test unauthorized access to admin API
$response = $this->actingAs($user)->post("/api/v4/users/{$user->id}/suspend", []);
$response->assertStatus(403);
$this->assertFalse($user->isSuspended());
// Test suspending the user
$response = $this->actingAs($admin)->post("/api/v4/users/{$user->id}/suspend", []);
$response->assertStatus(200);
$json = $response->json();
$this->assertSame('success', $json['status']);
$this->assertSame("User suspended successfully.", $json['message']);
$this->assertCount(2, $json);
$this->assertTrue($user->fresh()->isSuspended());
}
/**
* Test user un-suspending (POST /api/v4/users//unsuspend)
*/
public function testUnsuspend(): void
{
Queue::fake(); // disable jobs
$user = $this->getTestUser('UsersControllerTest1@userscontroller.com');
$admin = $this->getTestUser('jeroen@jeroen.jeroen');
// Test unauthorized access to admin API
$response = $this->actingAs($user)->post("/api/v4/users/{$user->id}/unsuspend", []);
$response->assertStatus(403);
$this->assertFalse($user->isSuspended());
$user->suspend();
$this->assertTrue($user->isSuspended());
// Test suspending the user
$response = $this->actingAs($admin)->post("/api/v4/users/{$user->id}/unsuspend", []);
$response->assertStatus(200);
$json = $response->json();
$this->assertSame('success', $json['status']);
$this->assertSame("User unsuspended successfully.", $json['message']);
$this->assertCount(2, $json);
$this->assertFalse($user->fresh()->isSuspended());
}
/**
* Test user update (PUT /api/v4/users/)
*/
public function testUpdate(): void
{
$user = $this->getTestUser('UsersControllerTest1@userscontroller.com');
$admin = $this->getTestUser('jeroen@jeroen.jeroen');
// Test unauthorized access to admin API
$response = $this->actingAs($user)->put("/api/v4/users/{$user->id}", []);
$response->assertStatus(403);
// Test updatig the user data (empty data)
$response = $this->actingAs($admin)->put("/api/v4/users/{$user->id}", []);
$response->assertStatus(200);
$json = $response->json();
$this->assertSame('success', $json['status']);
$this->assertSame("User data updated successfully.", $json['message']);
$this->assertCount(2, $json);
// Test error handling
$post = ['external_email' => 'aaa'];
$response = $this->actingAs($admin)->put("/api/v4/users/{$user->id}", $post);
$response->assertStatus(422);
$json = $response->json();
$this->assertSame('error', $json['status']);
$this->assertSame("The external email must be a valid email address.", $json['errors']['external_email'][0]);
$this->assertCount(2, $json);
// Test real update
$post = ['external_email' => 'modified@test.com'];
$response = $this->actingAs($admin)->put("/api/v4/users/{$user->id}", $post);
$response->assertStatus(200);
$json = $response->json();
$this->assertSame('success', $json['status']);
$this->assertSame("User data updated successfully.", $json['message']);
$this->assertCount(2, $json);
$this->assertSame('modified@test.com', $user->getSetting('external_email'));
}
}
diff --git a/src/tests/Feature/Jobs/Group/UpdateTest.php b/src/tests/Feature/Jobs/Group/UpdateTest.php
index 723cbb68..14145bba 100644
--- a/src/tests/Feature/Jobs/Group/UpdateTest.php
+++ b/src/tests/Feature/Jobs/Group/UpdateTest.php
@@ -1,49 +1,80 @@
deleteTestGroup('group@kolab.org');
}
public function tearDown(): void
{
$this->deleteTestGroup('group@kolab.org');
parent::tearDown();
}
/**
* Test job handle
*
* @group ldap
*/
public function testHandle(): void
{
+ Queue::fake();
+
+ // Test non-existing group ID
+ $job = new \App\Jobs\Group\UpdateJob(123);
+ $job->handle();
+
+ $this->assertTrue($job->hasFailed());
+ $this->assertSame("Group 123 could not be found in the database.", $job->failureMessage);
+
+ // Create the group
$group = $this->getTestGroup('group@kolab.org', ['members' => []]);
+ LDAP::createGroup($group);
+
+ // Test if group properties (members) actually changed in LDAP
+ $group->members = ['test1@gmail.com'];
+ $group->status |= Group::STATUS_LDAP_READY;
+ $group->save();
$job = new \App\Jobs\Group\UpdateJob($group->id);
$job->handle();
- // TODO: Test if group properties (members) actually changed in LDAP
- $this->assertTrue(true);
+ $ldapGroup = LDAP::getGroup($group->email);
+ $root_dn = \config('ldap.hosted.root_dn');
- // Test non-existing group ID
- $job = new \App\Jobs\Group\UpdateJob(123);
+ $this->assertSame('uid=test1@gmail.com,ou=People,ou=kolab.org,' . $root_dn, $ldapGroup['uniquemember']);
+
+ // Test that suspended group is removed from LDAP
+ $group->suspend();
+
+ $job = new \App\Jobs\Group\UpdateJob($group->id);
$job->handle();
- $this->assertTrue($job->hasFailed());
- $this->assertSame("Group 123 could not be found in the database.", $job->failureMessage);
+ $this->assertNull(LDAP::getGroup($group->email));
+
+ // Test that unsuspended group is added back to LDAP
+ $group->unsuspend();
+
+ $job = new \App\Jobs\Group\UpdateJob($group->id);
+ $job->handle();
+
+ $ldapGroup = LDAP::getGroup($group->email);
+ $this->assertSame($group->email, $ldapGroup['mail']);
+ $this->assertSame('uid=test1@gmail.com,ou=People,ou=kolab.org,' . $root_dn, $ldapGroup['uniquemember']);
}
}