diff --git a/src/tests/Feature/Console/Domain/StatusTest.php b/src/tests/Feature/Console/Domain/StatusTest.php index d183e361..b31c8d10 100644 --- a/src/tests/Feature/Console/Domain/StatusTest.php +++ b/src/tests/Feature/Console/Domain/StatusTest.php @@ -1,64 +1,68 @@ deleteTestDomain('domain-delete.com'); } /** * {@inheritDoc} */ public function tearDown(): void { $this->deleteTestDomain('domain-delete.com'); parent::tearDown(); } /** * Test the command */ public function testHandle(): void { Queue::fake(); // Non-existing domain $code = \Artisan::call("domain:status unknown.org"); $output = trim(\Artisan::output()); $this->assertSame(1, $code); $this->assertSame("Domain not found.", $output); // Existing domain $code = \Artisan::call("domain:status kolab.org"); $output = trim(\Artisan::output()); $this->assertSame(0, $code); - $this->assertSame("Status (114): active (2), confirmed (16), verified (32), ldapReady (64)", $output); + if (\config('app.with_ldap')) { + $this->assertSame("Status (114): active (2), confirmed (16), verified (32), ldapReady (64)", $output); + } else { + $this->assertSame("Status (50): active (2), confirmed (16), verified (32)", $output); + } // Test deleted domain $user = $this->getTestUser('john@kolab.org'); $domain = $this->getTestDomain('domain-delete.com', [ 'status' => \App\Domain::STATUS_NEW, 'type' => \App\Domain::TYPE_HOSTED, ]); $package_domain = \App\Package::where('title', 'domain-hosting')->first(); $domain->assignPackage($package_domain, $user); $domain->delete(); $code = \Artisan::call("domain:status {$domain->namespace}"); $output = trim(\Artisan::output()); $this->assertSame(0, $code); $this->assertSame("Status (1): deleted (8)", $output); } } diff --git a/src/tests/Feature/Controller/GroupsTest.php b/src/tests/Feature/Controller/GroupsTest.php index 68696710..1ed0367c 100644 --- a/src/tests/Feature/Controller/GroupsTest.php +++ b/src/tests/Feature/Controller/GroupsTest.php @@ -1,650 +1,658 @@ deleteTestGroup('group-test@kolab.org'); $this->deleteTestGroup('group-test2@kolab.org'); } /** * {@inheritDoc} */ public function tearDown(): void { $this->deleteTestGroup('group-test@kolab.org'); $this->deleteTestGroup('group-test2@kolab.org'); parent::tearDown(); } /** * Test group deleting (DELETE /api/v4/groups/) */ public function testDestroy(): void { // First create some groups to delete $john = $this->getTestUser('john@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); $group = $this->getTestGroup('group-test@kolab.org'); $group->assignToWallet($john->wallets->first()); // Test unauth access $response = $this->delete("api/v4/groups/{$group->id}"); $response->assertStatus(401); // Test non-existing group $response = $this->actingAs($john)->delete("api/v4/groups/abc"); $response->assertStatus(404); // Test access to other user's group $response = $this->actingAs($jack)->delete("api/v4/groups/{$group->id}"); $response->assertStatus(403); $json = $response->json(); $this->assertSame('error', $json['status']); $this->assertSame("Access denied", $json['message']); $this->assertCount(2, $json); // Test removing a group $response = $this->actingAs($john)->delete("api/v4/groups/{$group->id}"); $response->assertStatus(200); $json = $response->json(); $this->assertEquals('success', $json['status']); $this->assertEquals("Distribution list deleted successfully.", $json['message']); } /** * Test groups listing (GET /api/v4/groups) */ public function testIndex(): void { $jack = $this->getTestUser('jack@kolab.org'); $john = $this->getTestUser('john@kolab.org'); $ned = $this->getTestUser('ned@kolab.org'); $group = $this->getTestGroup('group-test@kolab.org'); $group->assignToWallet($john->wallets->first()); // Test unauth access $response = $this->get("api/v4/groups"); $response->assertStatus(401); // Test a user with no groups $response = $this->actingAs($jack)->get("/api/v4/groups"); $response->assertStatus(200); $json = $response->json(); $this->assertCount(4, $json); $this->assertSame(0, $json['count']); $this->assertSame(false, $json['hasMore']); $this->assertSame("0 distribution lists have been found.", $json['message']); $this->assertSame([], $json['list']); // Test a user with a single group $response = $this->actingAs($john)->get("/api/v4/groups"); $response->assertStatus(200); $json = $response->json(); $this->assertCount(4, $json); $this->assertSame(1, $json['count']); $this->assertSame(false, $json['hasMore']); $this->assertSame("1 distribution lists have been found.", $json['message']); $this->assertCount(1, $json['list']); $this->assertSame($group->id, $json['list'][0]['id']); $this->assertSame($group->email, $json['list'][0]['email']); $this->assertSame($group->name, $json['list'][0]['name']); $this->assertArrayHasKey('isDeleted', $json['list'][0]); $this->assertArrayHasKey('isSuspended', $json['list'][0]); $this->assertArrayHasKey('isActive', $json['list'][0]); - $this->assertArrayHasKey('isLdapReady', $json['list'][0]); + if (\config('app.with_ldap')) { + $this->assertArrayHasKey('isLdapReady', $json['list'][0]); + } // Test that another wallet controller has access to groups $response = $this->actingAs($ned)->get("/api/v4/groups"); $response->assertStatus(200); $json = $response->json(); $this->assertCount(4, $json); $this->assertSame(1, $json['count']); $this->assertSame(false, $json['hasMore']); $this->assertSame("1 distribution lists have been found.", $json['message']); $this->assertCount(1, $json['list']); $this->assertSame($group->email, $json['list'][0]['email']); } /** * Test group config update (POST /api/v4/groups//config) */ public function testSetConfig(): void { $john = $this->getTestUser('john@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); $group = $this->getTestGroup('group-test@kolab.org'); $group->assignToWallet($john->wallets->first()); // Test unknown group id $post = ['sender_policy' => []]; $response = $this->actingAs($john)->post("/api/v4/groups/123/config", $post); $json = $response->json(); $response->assertStatus(404); // Test access by user not being a wallet controller $post = ['sender_policy' => []]; $response = $this->actingAs($jack)->post("/api/v4/groups/{$group->id}/config", $post); $json = $response->json(); $response->assertStatus(403); $this->assertSame('error', $json['status']); $this->assertSame("Access denied", $json['message']); $this->assertCount(2, $json); // Test some invalid data $post = ['test' => 1]; $response = $this->actingAs($john)->post("/api/v4/groups/{$group->id}/config", $post); $response->assertStatus(422); $json = $response->json(); $this->assertSame('error', $json['status']); $this->assertCount(2, $json); $this->assertCount(1, $json['errors']); $this->assertSame('The requested configuration parameter is not supported.', $json['errors']['test']); $group->refresh(); $this->assertNull($group->getSetting('test')); $this->assertNull($group->getSetting('sender_policy')); // Test some valid data $post = ['sender_policy' => ['domain.com']]; $response = $this->actingAs($john)->post("/api/v4/groups/{$group->id}/config", $post); $response->assertStatus(200); $json = $response->json(); $this->assertCount(2, $json); $this->assertSame('success', $json['status']); $this->assertSame('Distribution list settings updated successfully.', $json['message']); $this->assertSame(['sender_policy' => $post['sender_policy']], $group->fresh()->getConfig()); // Test input validation $post = ['sender_policy' => [5]]; $response = $this->actingAs($john)->post("/api/v4/groups/{$group->id}/config", $post); $response->assertStatus(422); $json = $response->json(); $this->assertCount(2, $json); $this->assertSame('error', $json['status']); $this->assertCount(1, $json['errors']); $this->assertSame( 'The entry format is invalid. Expected an email, domain, or part of it.', $json['errors']['sender_policy'][0] ); $this->assertSame(['sender_policy' => ['domain.com']], $group->fresh()->getConfig()); } /** * Test fetching group data/profile (GET /api/v4/groups/) */ public function testShow(): void { $jack = $this->getTestUser('jack@kolab.org'); $john = $this->getTestUser('john@kolab.org'); $ned = $this->getTestUser('ned@kolab.org'); $group = $this->getTestGroup('group-test@kolab.org'); $group->assignToWallet($john->wallets->first()); $group->setSetting('sender_policy', '["test"]'); // Test unauthorized access to a profile of other user $response = $this->get("/api/v4/groups/{$group->id}"); $response->assertStatus(401); // Test unauthorized access to a group of another user $response = $this->actingAs($jack)->get("/api/v4/groups/{$group->id}"); $response->assertStatus(403); // John: Group owner - non-existing group $response = $this->actingAs($john)->get("/api/v4/groups/abc"); $response->assertStatus(404); // John: Group owner $response = $this->actingAs($john)->get("/api/v4/groups/{$group->id}"); $response->assertStatus(200); $json = $response->json(); $this->assertSame($group->id, $json['id']); $this->assertSame($group->email, $json['email']); $this->assertSame($group->name, $json['name']); $this->assertSame($group->members, $json['members']); $this->assertTrue(!empty($json['statusInfo'])); $this->assertArrayHasKey('isDeleted', $json); $this->assertArrayHasKey('isSuspended', $json); $this->assertArrayHasKey('isActive', $json); - $this->assertArrayHasKey('isLdapReady', $json); + if (\config('app.with_ldap')) { + $this->assertArrayHasKey('isLdapReady', $json); + } $this->assertSame(['sender_policy' => ['test']], $json['config']); $this->assertCount(1, $json['skus']); } /** * Test fetching SKUs list for a group (GET /groups//skus) */ public function testSkus(): void { Queue::fake(); $john = $this->getTestUser('john@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); $group = $this->getTestGroup('group-test@kolab.org'); $group->assignToWallet($john->wallets->first()); // Unauth access not allowed $response = $this->get("api/v4/groups/{$group->id}/skus"); $response->assertStatus(401); // Unauthorized access not allowed $response = $this->actingAs($jack)->get("api/v4/groups/{$group->id}/skus"); $response->assertStatus(403); $response = $this->actingAs($john)->get("api/v4/groups/{$group->id}/skus"); $response->assertStatus(200); $json = $response->json(); $this->assertCount(1, $json); $this->assertSkuElement('group', $json[0], [ 'prio' => 0, 'type' => 'group', 'handler' => 'Group', 'enabled' => true, 'readonly' => true, ]); } /** * Test fetching group status (GET /api/v4/groups//status) * and forcing setup process update (?refresh=1) + * + * @group ldap */ public function testStatus(): void { Queue::fake(); $john = $this->getTestUser('john@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); $group = $this->getTestGroup('group-test@kolab.org'); $group->assignToWallet($john->wallets->first()); // Test unauthorized access $response = $this->get("/api/v4/groups/abc/status"); $response->assertStatus(401); // Test unauthorized access $response = $this->actingAs($jack)->get("/api/v4/groups/{$group->id}/status"); $response->assertStatus(403); $group->status = Group::STATUS_NEW | Group::STATUS_ACTIVE; $group->save(); // Get group status $response = $this->actingAs($john)->get("/api/v4/groups/{$group->id}/status"); $response->assertStatus(200); $json = $response->json(); $this->assertFalse($json['isLdapReady']); $this->assertFalse($json['isReady']); $this->assertFalse($json['isSuspended']); $this->assertTrue($json['isActive']); $this->assertFalse($json['isDeleted']); $this->assertCount(6, $json['process']); $this->assertSame('distlist-new', $json['process'][0]['label']); $this->assertSame(true, $json['process'][0]['state']); $this->assertSame('distlist-ldap-ready', $json['process'][1]['label']); $this->assertSame(false, $json['process'][1]['state']); $this->assertTrue(empty($json['status'])); $this->assertTrue(empty($json['message'])); // Make sure the domain is confirmed (other test might unset that status) $domain = $this->getTestDomain('kolab.org'); $domain->status |= \App\Domain::STATUS_CONFIRMED; $domain->save(); // Now "reboot" the process and the group $response = $this->actingAs($john)->get("/api/v4/groups/{$group->id}/status?refresh=1"); $response->assertStatus(200); $json = $response->json(); $this->assertTrue($json['isLdapReady']); $this->assertTrue($json['isReady']); $this->assertCount(6, $json['process']); $this->assertSame('distlist-ldap-ready', $json['process'][1]['label']); $this->assertSame(true, $json['process'][1]['state']); $this->assertSame('success', $json['status']); $this->assertSame('Setup process finished successfully.', $json['message']); // Test a case when a domain is not ready $domain->status ^= \App\Domain::STATUS_CONFIRMED; $domain->save(); $response = $this->actingAs($john)->get("/api/v4/groups/{$group->id}/status?refresh=1"); $response->assertStatus(200); $json = $response->json(); $this->assertTrue($json['isLdapReady']); $this->assertTrue($json['isReady']); $this->assertCount(6, $json['process']); $this->assertSame('distlist-ldap-ready', $json['process'][1]['label']); $this->assertSame(true, $json['process'][1]['state']); $this->assertSame('success', $json['status']); $this->assertSame('Setup process finished successfully.', $json['message']); } /** * Test GroupsController::statusInfo() + * + * @group ldap */ public function testStatusInfo(): void { $john = $this->getTestUser('john@kolab.org'); $group = $this->getTestGroup('group-test@kolab.org'); $group->assignToWallet($john->wallets->first()); $group->status = Group::STATUS_NEW | Group::STATUS_ACTIVE; $group->save(); $result = GroupsController::statusInfo($group); $this->assertFalse($result['isDone']); $this->assertCount(6, $result['process']); $this->assertSame('distlist-new', $result['process'][0]['label']); $this->assertSame(true, $result['process'][0]['state']); $this->assertSame('distlist-ldap-ready', $result['process'][1]['label']); $this->assertSame(false, $result['process'][1]['state']); $this->assertSame('running', $result['processState']); $group->created_at = Carbon::now()->subSeconds(181); $group->save(); $result = GroupsController::statusInfo($group); $this->assertSame('failed', $result['processState']); $group->status |= Group::STATUS_LDAP_READY; $group->save(); $result = GroupsController::statusInfo($group); $this->assertTrue($result['isDone']); $this->assertCount(6, $result['process']); $this->assertSame('distlist-new', $result['process'][0]['label']); $this->assertSame(true, $result['process'][0]['state']); $this->assertSame('distlist-ldap-ready', $result['process'][1]['label']); $this->assertSame(true, $result['process'][2]['state']); $this->assertSame('done', $result['processState']); } /** * Test group creation (POST /api/v4/groups) */ public function testStore(): void { Queue::fake(); $jack = $this->getTestUser('jack@kolab.org'); $john = $this->getTestUser('john@kolab.org'); // Test unauth request $response = $this->post("/api/v4/groups", []); $response->assertStatus(401); // Test non-controller user $response = $this->actingAs($jack)->post("/api/v4/groups", []); $response->assertStatus(403); // Test empty request $response = $this->actingAs($john)->post("/api/v4/groups", []); $response->assertStatus(422); $json = $response->json(); $this->assertSame('error', $json['status']); $this->assertSame("The email field is required.", $json['errors']['email']); $this->assertSame("At least one recipient is required.", $json['errors']['members']); $this->assertSame("The name field is required.", $json['errors']['name'][0]); $this->assertCount(2, $json); $this->assertCount(3, $json['errors']); // Test missing members and name $post = ['email' => 'group-test@kolab.org']; $response = $this->actingAs($john)->post("/api/v4/groups", $post); $json = $response->json(); $response->assertStatus(422); $this->assertSame('error', $json['status']); $this->assertSame("At least one recipient is required.", $json['errors']['members']); $this->assertSame("The name field is required.", $json['errors']['name'][0]); $this->assertCount(2, $json); $this->assertCount(2, $json['errors']); // Test invalid email and too long name $post = ['email' => 'invalid', 'name' => str_repeat('A', 192)]; $response = $this->actingAs($john)->post("/api/v4/groups", $post); $response->assertStatus(422); $json = $response->json(); $this->assertSame('error', $json['status']); $this->assertCount(2, $json); $this->assertSame("The specified email is invalid.", $json['errors']['email']); $this->assertSame("The name may not be greater than 191 characters.", $json['errors']['name'][0]); $this->assertCount(3, $json['errors']); // Test successful group creation $post = [ 'name' => 'Test Group', 'email' => 'group-test@kolab.org', 'members' => ['test1@domain.tld', 'test2@domain.tld'] ]; $response = $this->actingAs($john)->post("/api/v4/groups", $post); $json = $response->json(); $response->assertStatus(200); $this->assertSame('success', $json['status']); $this->assertSame("Distribution list created successfully.", $json['message']); $this->assertCount(2, $json); $group = Group::where('email', 'group-test@kolab.org')->first(); $this->assertInstanceOf(Group::class, $group); $this->assertSame($post['email'], $group->email); $this->assertSame($post['members'], $group->members); $this->assertTrue($john->groups()->get()->contains($group)); // Group name must be unique within a domain $post['email'] = 'group-test2@kolab.org'; $post['members'] = ['test1@domain.tld']; $response = $this->actingAs($john)->post("/api/v4/groups", $post); $response->assertStatus(422); $json = $response->json(); $this->assertSame('error', $json['status']); $this->assertCount(2, $json); $this->assertCount(1, $json['errors']); $this->assertSame("The specified name is not available.", $json['errors']['name'][0]); } /** * Test group update (PUT /api/v4/groups/) */ public function testUpdate(): void { Queue::fake(); $jack = $this->getTestUser('jack@kolab.org'); $john = $this->getTestUser('john@kolab.org'); $ned = $this->getTestUser('ned@kolab.org'); $group = $this->getTestGroup('group-test@kolab.org'); $group->assignToWallet($john->wallets->first()); // Test unauthorized update $response = $this->get("/api/v4/groups/{$group->id}", []); $response->assertStatus(401); // Test unauthorized update $response = $this->actingAs($jack)->get("/api/v4/groups/{$group->id}", []); $response->assertStatus(403); // Test updating - missing members $response = $this->actingAs($john)->put("/api/v4/groups/{$group->id}", []); $response->assertStatus(422); $json = $response->json(); $this->assertSame('error', $json['status']); $this->assertSame("At least one recipient is required.", $json['errors']['members']); $this->assertCount(2, $json); // Test some invalid data $post = ['members' => ['test@domain.tld', 'invalid']]; $response = $this->actingAs($john)->put("/api/v4/groups/{$group->id}", $post); $response->assertStatus(422); $json = $response->json(); $this->assertSame('error', $json['status']); $this->assertCount(2, $json); $this->assertSame('The specified email address is invalid.', $json['errors']['members'][1]); // Valid data - members and name changed $post = [ 'name' => 'Test Gr', 'members' => ['member1@test.domain', 'member2@test.domain'] ]; $response = $this->actingAs($john)->put("/api/v4/groups/{$group->id}", $post); $json = $response->json(); $response->assertStatus(200); $this->assertSame('success', $json['status']); $this->assertSame("Distribution list updated successfully.", $json['message']); $this->assertCount(2, $json); $group->refresh(); $this->assertSame($post['name'], $group->name); $this->assertSame($post['members'], $group->members); } /** * Group email address validation. */ public function testValidateGroupEmail(): void { $john = $this->getTestUser('john@kolab.org'); $group = $this->getTestGroup('group-test@kolab.org'); // Invalid email $result = GroupsController::validateGroupEmail('', $john); $this->assertSame("The email field is required.", $result); $result = GroupsController::validateGroupEmail('kolab.org', $john); $this->assertSame("The specified email is invalid.", $result); $result = GroupsController::validateGroupEmail('.@kolab.org', $john); $this->assertSame("The specified email is invalid.", $result); $result = GroupsController::validateGroupEmail('test123456@localhost', $john); $this->assertSame("The specified domain is invalid.", $result); $result = GroupsController::validateGroupEmail('test123456@unknown-domain.org', $john); $this->assertSame("The specified domain is invalid.", $result); // forbidden public domain $result = GroupsController::validateGroupEmail('testtest@kolabnow.com', $john); $this->assertSame("The specified domain is not available.", $result); // existing alias $result = GroupsController::validateGroupEmail('jack.daniels@kolab.org', $john); $this->assertSame("The specified email is not available.", $result); // existing user $result = GroupsController::validateGroupEmail('ned@kolab.org', $john); $this->assertSame("The specified email is not available.", $result); // existing group $result = GroupsController::validateGroupEmail('group-test@kolab.org', $john); $this->assertSame("The specified email is not available.", $result); // valid $result = GroupsController::validateGroupEmail('admin@kolab.org', $john); $this->assertSame(null, $result); } /** * Group member email address validation. */ public function testValidateMemberEmail(): void { $john = $this->getTestUser('john@kolab.org'); // Invalid format $result = GroupsController::validateMemberEmail('kolab.org', $john); $this->assertSame("The specified email address is invalid.", $result); $result = GroupsController::validateMemberEmail('.@kolab.org', $john); $this->assertSame("The specified email address is invalid.", $result); $result = GroupsController::validateMemberEmail('test123456@localhost', $john); $this->assertSame("The specified email address is invalid.", $result); // Test local non-existing user $result = GroupsController::validateMemberEmail('unknown@kolab.org', $john); $this->assertSame("The specified email address does not exist.", $result); // Test local existing user $result = GroupsController::validateMemberEmail('ned@kolab.org', $john); $this->assertSame(null, $result); // Test existing user, but not in the same account $result = GroupsController::validateMemberEmail('jeroen@jeroen.jeroen', $john); $this->assertSame(null, $result); // Valid address $result = GroupsController::validateMemberEmail('test@google.com', $john); $this->assertSame(null, $result); } } diff --git a/src/tests/Feature/Controller/ResourcesTest.php b/src/tests/Feature/Controller/ResourcesTest.php index 04268ec8..c7d8fa0f 100644 --- a/src/tests/Feature/Controller/ResourcesTest.php +++ b/src/tests/Feature/Controller/ResourcesTest.php @@ -1,563 +1,567 @@ deleteTestResource('resource-test@kolab.org'); Resource::where('name', 'Test Resource')->delete(); } /** * {@inheritDoc} */ public function tearDown(): void { $this->deleteTestResource('resource-test@kolab.org'); Resource::where('name', 'Test Resource')->delete(); parent::tearDown(); } /** * Test resource deleting (DELETE /api/v4/resources/) */ public function testDestroy(): void { // First create some groups to delete $john = $this->getTestUser('john@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); $resource = $this->getTestResource('resource-test@kolab.org'); $resource->assignToWallet($john->wallets->first()); // Test unauth access $response = $this->delete("api/v4/resources/{$resource->id}"); $response->assertStatus(401); // Test non-existing resource $response = $this->actingAs($john)->delete("api/v4/resources/abc"); $response->assertStatus(404); // Test access to other user's resource $response = $this->actingAs($jack)->delete("api/v4/resources/{$resource->id}"); $response->assertStatus(403); $json = $response->json(); $this->assertSame('error', $json['status']); $this->assertSame("Access denied", $json['message']); $this->assertCount(2, $json); // Test removing a resource $response = $this->actingAs($john)->delete("api/v4/resources/{$resource->id}"); $response->assertStatus(200); $json = $response->json(); $this->assertEquals('success', $json['status']); $this->assertEquals("Resource deleted successfully.", $json['message']); } /** * Test resources listing (GET /api/v4/resources) */ public function testIndex(): void { $jack = $this->getTestUser('jack@kolab.org'); $john = $this->getTestUser('john@kolab.org'); $ned = $this->getTestUser('ned@kolab.org'); // Test unauth access $response = $this->get("api/v4/resources"); $response->assertStatus(401); // Test a user with no resources $response = $this->actingAs($jack)->get("/api/v4/resources"); $response->assertStatus(200); $json = $response->json(); $this->assertCount(4, $json); $this->assertSame(0, $json['count']); $this->assertSame(false, $json['hasMore']); $this->assertSame("0 resources have been found.", $json['message']); $this->assertSame([], $json['list']); // Test a user with two resources $response = $this->actingAs($john)->get("/api/v4/resources"); $response->assertStatus(200); $json = $response->json(); $resource = Resource::where('name', 'Conference Room #1')->first(); $this->assertCount(4, $json); $this->assertSame(2, $json['count']); $this->assertSame(false, $json['hasMore']); $this->assertSame("2 resources have been found.", $json['message']); $this->assertCount(2, $json['list']); $this->assertSame($resource->id, $json['list'][0]['id']); $this->assertSame($resource->email, $json['list'][0]['email']); $this->assertSame($resource->name, $json['list'][0]['name']); $this->assertArrayHasKey('isDeleted', $json['list'][0]); $this->assertArrayHasKey('isActive', $json['list'][0]); $this->assertArrayHasKey('isImapReady', $json['list'][0]); if (\config('app.with_ldap')) { $this->assertArrayHasKey('isLdapReady', $json['list'][0]); } // Test that another wallet controller has access to resources $response = $this->actingAs($ned)->get("/api/v4/resources"); $response->assertStatus(200); $json = $response->json(); $this->assertCount(4, $json); $this->assertSame(2, $json['count']); $this->assertSame(false, $json['hasMore']); $this->assertSame("2 resources have been found.", $json['message']); $this->assertCount(2, $json['list']); $this->assertSame($resource->email, $json['list'][0]['email']); } /** * Test resource config update (POST /api/v4/resources//config) */ public function testSetConfig(): void { $john = $this->getTestUser('john@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); $resource = $this->getTestResource('resource-test@kolab.org'); $resource->assignToWallet($john->wallets->first()); // Test unknown resource id $post = ['invitation_policy' => 'reject']; $response = $this->actingAs($john)->post("/api/v4/resources/123/config", $post); $json = $response->json(); $response->assertStatus(404); // Test access by user not being a wallet controller $post = ['invitation_policy' => 'reject']; $response = $this->actingAs($jack)->post("/api/v4/resources/{$resource->id}/config", $post); $json = $response->json(); $response->assertStatus(403); $this->assertSame('error', $json['status']); $this->assertSame("Access denied", $json['message']); $this->assertCount(2, $json); // Test some invalid data $post = ['test' => 1]; $response = $this->actingAs($john)->post("/api/v4/resources/{$resource->id}/config", $post); $response->assertStatus(422); $json = $response->json(); $this->assertSame('error', $json['status']); $this->assertCount(2, $json); $this->assertCount(1, $json['errors']); $this->assertSame('The requested configuration parameter is not supported.', $json['errors']['test']); $resource->refresh(); $this->assertNull($resource->getSetting('test')); $this->assertNull($resource->getSetting('invitation_policy')); // Test some valid data $post = ['invitation_policy' => 'reject']; $response = $this->actingAs($john)->post("/api/v4/resources/{$resource->id}/config", $post); $response->assertStatus(200); $json = $response->json(); $this->assertCount(2, $json); $this->assertSame('success', $json['status']); $this->assertSame("Resource settings updated successfully.", $json['message']); $this->assertSame(['invitation_policy' => 'reject'], $resource->fresh()->getConfig()); // Test input validation $post = ['invitation_policy' => 'aaa']; $response = $this->actingAs($john)->post("/api/v4/resources/{$resource->id}/config", $post); $response->assertStatus(422); $json = $response->json(); $this->assertCount(2, $json); $this->assertSame('error', $json['status']); $this->assertCount(1, $json['errors']); $this->assertSame( "The specified invitation policy is invalid.", $json['errors']['invitation_policy'] ); $this->assertSame(['invitation_policy' => 'reject'], $resource->fresh()->getConfig()); } /** * Test fetching resource data/profile (GET /api/v4/resources/) */ public function testShow(): void { $jack = $this->getTestUser('jack@kolab.org'); $john = $this->getTestUser('john@kolab.org'); $ned = $this->getTestUser('ned@kolab.org'); $resource = $this->getTestResource('resource-test@kolab.org'); $resource->assignToWallet($john->wallets->first()); $resource->setSetting('invitation_policy', 'reject'); // Test unauthorized access to a profile of other user $response = $this->get("/api/v4/resources/{$resource->id}"); $response->assertStatus(401); // Test unauthorized access to a resource of another user $response = $this->actingAs($jack)->get("/api/v4/resources/{$resource->id}"); $response->assertStatus(403); // John: Account owner - non-existing resource $response = $this->actingAs($john)->get("/api/v4/resources/abc"); $response->assertStatus(404); // John: Account owner $response = $this->actingAs($john)->get("/api/v4/resources/{$resource->id}"); $response->assertStatus(200); $json = $response->json(); $this->assertSame($resource->id, $json['id']); $this->assertSame($resource->email, $json['email']); $this->assertSame($resource->name, $json['name']); $this->assertTrue(!empty($json['statusInfo'])); $this->assertArrayHasKey('isDeleted', $json); $this->assertArrayHasKey('isActive', $json); $this->assertArrayHasKey('isImapReady', $json); if (\config('app.with_ldap')) { $this->assertArrayHasKey('isLdapReady', $json); } $this->assertSame(['invitation_policy' => 'reject'], $json['config']); $this->assertCount(1, $json['skus']); } /** * Test fetching SKUs list for a resource (GET /resources//skus) */ public function testSkus(): void { Queue::fake(); $john = $this->getTestUser('john@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); $resource = $this->getTestResource('resource-test@kolab.org'); $resource->assignToWallet($john->wallets->first()); // Unauth access not allowed $response = $this->get("api/v4/resources/{$resource->id}/skus"); $response->assertStatus(401); // Unauthorized access not allowed $response = $this->actingAs($jack)->get("api/v4/resources/{$resource->id}/skus"); $response->assertStatus(403); $response = $this->actingAs($john)->get("api/v4/resources/{$resource->id}/skus"); $response->assertStatus(200); $json = $response->json(); $this->assertCount(1, $json); $this->assertSkuElement('resource', $json[0], [ 'prio' => 0, 'type' => 'resource', 'handler' => 'Resource', 'enabled' => true, 'readonly' => true, ]); } /** * Test fetching a resource status (GET /api/v4/resources//status) * and forcing setup process update (?refresh=1) + * + * @group ldap */ public function testStatus(): void { Queue::fake(); $john = $this->getTestUser('john@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); $resource = $this->getTestResource('resource-test@kolab.org'); $resource->assignToWallet($john->wallets->first()); // Test unauthorized access $response = $this->get("/api/v4/resources/abc/status"); $response->assertStatus(401); // Test unauthorized access $response = $this->actingAs($jack)->get("/api/v4/resources/{$resource->id}/status"); $response->assertStatus(403); $resource->status = Resource::STATUS_NEW | Resource::STATUS_ACTIVE; $resource->save(); // Get resource status $response = $this->actingAs($john)->get("/api/v4/resources/{$resource->id}/status"); $response->assertStatus(200); $json = $response->json(); $this->assertFalse($json['isReady']); $this->assertFalse($json['isDeleted']); $this->assertTrue($json['isActive']); $this->assertFalse($json['isImapReady']); $this->assertSame('resource-new', $json['process'][0]['label']); $this->assertSame(true, $json['process'][0]['state']); if (\config('app.with_ldap')) { $this->assertFalse($json['isLdapReady']); $this->assertSame('resource-ldap-ready', $json['process'][1]['label']); $this->assertSame(false, $json['process'][1]['state']); $this->assertSame('resource-imap-ready', $json['process'][2]['label']); $this->assertSame(false, $json['process'][2]['state']); } else { $this->assertSame('resource-imap-ready', $json['process'][1]['label']); $this->assertSame(false, $json['process'][1]['state']); } $this->assertTrue(empty($json['status'])); $this->assertTrue(empty($json['message'])); $this->assertSame('running', $json['processState']); // Make sure the domain is confirmed (other test might unset that status) $domain = $this->getTestDomain('kolab.org'); $domain->status |= \App\Domain::STATUS_CONFIRMED; $domain->save(); $resource->status |= Resource::STATUS_IMAP_READY; $resource->save(); // Now "reboot" the process Queue::fake(); $response = $this->actingAs($john)->get("/api/v4/resources/{$resource->id}/status?refresh=1"); $response->assertStatus(200); $json = $response->json(); $this->assertFalse($json['isReady']); $this->assertTrue($json['isImapReady']); if (\config('app.with_ldap')) { $this->assertFalse($json['isLdapReady']); $this->assertSame('resource-ldap-ready', $json['process'][1]['label']); $this->assertSame(false, $json['process'][1]['state']); $this->assertSame('resource-imap-ready', $json['process'][2]['label']); $this->assertSame(true, $json['process'][2]['state']); } else { $this->assertSame('resource-imap-ready', $json['process'][1]['label']); $this->assertSame(true, $json['process'][1]['state']); } $this->assertSame('success', $json['status']); $this->assertSame('Setup process has been pushed. Please wait.', $json['message']); $this->assertSame('waiting', $json['processState']); Queue::assertPushed(\App\Jobs\Resource\CreateJob::class, 1); // Test a case when a domain is not ready Queue::fake(); $domain->status ^= \App\Domain::STATUS_CONFIRMED; $domain->save(); $response = $this->actingAs($john)->get("/api/v4/resources/{$resource->id}/status?refresh=1"); $response->assertStatus(200); $json = $response->json(); $this->assertFalse($json['isReady']); if (\config('app.with_ldap')) { $this->assertFalse($json['isLdapReady']); $this->assertSame('resource-ldap-ready', $json['process'][1]['label']); $this->assertSame(false, $json['process'][1]['state']); } $this->assertSame('success', $json['status']); $this->assertSame('Setup process has been pushed. Please wait.', $json['message']); $this->assertSame('waiting', $json['processState']); Queue::assertPushed(\App\Jobs\Resource\CreateJob::class, 1); } /** * Test ResourcesController::statusInfo() + * + * @group ldap */ public function testStatusInfo(): void { $john = $this->getTestUser('john@kolab.org'); $resource = $this->getTestResource('resource-test@kolab.org'); $resource->assignToWallet($john->wallets->first()); $resource->status = Resource::STATUS_NEW | Resource::STATUS_ACTIVE; $resource->save(); $domain = $this->getTestDomain('kolab.org'); $domain->status |= \App\Domain::STATUS_CONFIRMED; $domain->save(); $result = ResourcesController::statusInfo($resource); $this->assertFalse($result['isDone']); $this->assertSame('resource-new', $result['process'][0]['label']); $this->assertSame(true, $result['process'][0]['state']); if (\config('app.with_ldap')) { $this->assertSame('resource-ldap-ready', $result['process'][1]['label']); $this->assertSame(false, $result['process'][1]['state']); $this->assertSame('resource-imap-ready', $result['process'][2]['label']); $this->assertSame(false, $result['process'][2]['state']); } else { $this->assertSame('resource-imap-ready', $result['process'][1]['label']); $this->assertSame(false, $result['process'][1]['state']); } $this->assertSame('running', $result['processState']); $resource->created_at = Carbon::now()->subSeconds(181); $resource->save(); $result = ResourcesController::statusInfo($resource); $this->assertSame('failed', $result['processState']); $resource->status |= Resource::STATUS_LDAP_READY | Resource::STATUS_IMAP_READY; $resource->save(); $result = ResourcesController::statusInfo($resource); $this->assertTrue($result['isDone']); $this->assertSame('resource-new', $result['process'][0]['label']); $this->assertSame(true, $result['process'][0]['state']); if (\config('app.with_ldap')) { $this->assertSame('resource-ldap-ready', $result['process'][1]['label']); $this->assertSame(true, $result['process'][1]['state']); $this->assertSame('resource-imap-ready', $result['process'][2]['label']); $this->assertSame(true, $result['process'][2]['state']); } else { $this->assertSame('resource-imap-ready', $result['process'][1]['label']); $this->assertSame(true, $result['process'][1]['state']); } $this->assertSame('done', $result['processState']); } /** * Test resource creation (POST /api/v4/resources) */ public function testStore(): void { Queue::fake(); $jack = $this->getTestUser('jack@kolab.org'); $john = $this->getTestUser('john@kolab.org'); // Test unauth request $response = $this->post("/api/v4/resources", []); $response->assertStatus(401); // Test non-controller user $response = $this->actingAs($jack)->post("/api/v4/resources", []); $response->assertStatus(403); // Test empty request $response = $this->actingAs($john)->post("/api/v4/resources", []); $response->assertStatus(422); $json = $response->json(); $this->assertSame('error', $json['status']); $this->assertSame("The name field is required.", $json['errors']['name'][0]); $this->assertCount(2, $json); $this->assertCount(1, $json['errors']); // Test too long name $post = ['domain' => 'kolab.org', 'name' => str_repeat('A', 192)]; $response = $this->actingAs($john)->post("/api/v4/resources", $post); $response->assertStatus(422); $json = $response->json(); $this->assertSame('error', $json['status']); $this->assertCount(2, $json); $this->assertSame("The name may not be greater than 191 characters.", $json['errors']['name'][0]); $this->assertCount(1, $json['errors']); // Test successful resource creation $post['name'] = 'Test Resource'; $response = $this->actingAs($john)->post("/api/v4/resources", $post); $json = $response->json(); $response->assertStatus(200); $this->assertSame('success', $json['status']); $this->assertSame("Resource created successfully.", $json['message']); $this->assertCount(2, $json); $resource = Resource::where('name', $post['name'])->first(); $this->assertInstanceOf(Resource::class, $resource); $this->assertTrue($john->resources()->get()->contains($resource)); // Resource name must be unique within a domain $response = $this->actingAs($john)->post("/api/v4/resources", $post); $response->assertStatus(422); $json = $response->json(); $this->assertSame('error', $json['status']); $this->assertCount(2, $json); $this->assertCount(1, $json['errors']); $this->assertSame("The specified name is not available.", $json['errors']['name'][0]); } /** * Test resource update (PUT /api/v4/resources/) */ public function testUpdate(): void { Queue::fake(); $jack = $this->getTestUser('jack@kolab.org'); $john = $this->getTestUser('john@kolab.org'); $ned = $this->getTestUser('ned@kolab.org'); $resource = $this->getTestResource('resource-test@kolab.org'); $resource->assignToWallet($john->wallets->first()); // Test unauthorized update $response = $this->get("/api/v4/resources/{$resource->id}", []); $response->assertStatus(401); // Test unauthorized update $response = $this->actingAs($jack)->get("/api/v4/resources/{$resource->id}", []); $response->assertStatus(403); // Name change $post = [ 'name' => 'Test Res', ]; $response = $this->actingAs($john)->put("/api/v4/resources/{$resource->id}", $post); $json = $response->json(); $response->assertStatus(200); $this->assertSame('success', $json['status']); $this->assertSame("Resource updated successfully.", $json['message']); $this->assertCount(2, $json); $resource->refresh(); $this->assertSame($post['name'], $resource->name); } } diff --git a/src/tests/Feature/Controller/SharedFoldersTest.php b/src/tests/Feature/Controller/SharedFoldersTest.php index 75ebdb60..388d370b 100644 --- a/src/tests/Feature/Controller/SharedFoldersTest.php +++ b/src/tests/Feature/Controller/SharedFoldersTest.php @@ -1,643 +1,645 @@ deleteTestSharedFolder('folder-test@kolab.org'); SharedFolder::where('name', 'like', 'Test_Folder')->forceDelete(); } /** * {@inheritDoc} */ public function tearDown(): void { $this->deleteTestSharedFolder('folder-test@kolab.org'); SharedFolder::where('name', 'like', 'Test_Folder')->forceDelete(); parent::tearDown(); } /** * Test resource deleting (DELETE /api/v4/resources/) */ public function testDestroy(): void { $john = $this->getTestUser('john@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); $folder = $this->getTestSharedFolder('folder-test@kolab.org'); $folder->assignToWallet($john->wallets->first()); // Test unauth access $response = $this->delete("api/v4/shared-folders/{$folder->id}"); $response->assertStatus(401); // Test non-existing folder $response = $this->actingAs($john)->delete("api/v4/shared-folders/abc"); $response->assertStatus(404); // Test access to other user's folder $response = $this->actingAs($jack)->delete("api/v4/shared-folders/{$folder->id}"); $response->assertStatus(403); $json = $response->json(); $this->assertSame('error', $json['status']); $this->assertSame("Access denied", $json['message']); $this->assertCount(2, $json); // Test removing a folder $response = $this->actingAs($john)->delete("api/v4/shared-folders/{$folder->id}"); $response->assertStatus(200); $json = $response->json(); $this->assertEquals('success', $json['status']); $this->assertEquals("Shared folder deleted successfully.", $json['message']); } /** * Test shared folders listing (GET /api/v4/shared-folders) */ public function testIndex(): void { $jack = $this->getTestUser('jack@kolab.org'); $john = $this->getTestUser('john@kolab.org'); $ned = $this->getTestUser('ned@kolab.org'); // Test unauth access $response = $this->get("api/v4/shared-folders"); $response->assertStatus(401); // Test a user with no shared folders $response = $this->actingAs($jack)->get("/api/v4/shared-folders"); $response->assertStatus(200); $json = $response->json(); $this->assertCount(4, $json); $this->assertSame(0, $json['count']); $this->assertSame(false, $json['hasMore']); $this->assertSame("0 shared folders have been found.", $json['message']); $this->assertSame([], $json['list']); // Test a user with two shared folders $response = $this->actingAs($john)->get("/api/v4/shared-folders"); $response->assertStatus(200); $json = $response->json(); $folder = SharedFolder::where('name', 'Calendar')->first(); $this->assertCount(4, $json); $this->assertSame(2, $json['count']); $this->assertSame(false, $json['hasMore']); $this->assertSame("2 shared folders have been found.", $json['message']); $this->assertCount(2, $json['list']); $this->assertSame($folder->id, $json['list'][0]['id']); $this->assertSame($folder->email, $json['list'][0]['email']); $this->assertSame($folder->name, $json['list'][0]['name']); $this->assertSame($folder->type, $json['list'][0]['type']); $this->assertArrayHasKey('isDeleted', $json['list'][0]); $this->assertArrayHasKey('isActive', $json['list'][0]); $this->assertArrayHasKey('isImapReady', $json['list'][0]); if (\config('app.with_ldap')) { $this->assertArrayHasKey('isLdapReady', $json['list'][0]); } // Test that another wallet controller has access to shared folders $response = $this->actingAs($ned)->get("/api/v4/shared-folders"); $response->assertStatus(200); $json = $response->json(); $this->assertCount(4, $json); $this->assertSame(2, $json['count']); $this->assertSame(false, $json['hasMore']); $this->assertSame("2 shared folders have been found.", $json['message']); $this->assertCount(2, $json['list']); $this->assertSame($folder->email, $json['list'][0]['email']); } /** * Test shared folder config update (POST /api/v4/shared-folders//config) */ public function testSetConfig(): void { $john = $this->getTestUser('john@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); $folder = $this->getTestSharedFolder('folder-test@kolab.org'); $folder->assignToWallet($john->wallets->first()); // Test unknown resource id $post = ['acl' => ['john@kolab.org, full']]; $response = $this->actingAs($john)->post("/api/v4/shared-folders/123/config", $post); $json = $response->json(); $response->assertStatus(404); // Test access by user not being a wallet controller $response = $this->actingAs($jack)->post("/api/v4/shared-folders/{$folder->id}/config", $post); $json = $response->json(); $response->assertStatus(403); $this->assertSame('error', $json['status']); $this->assertSame("Access denied", $json['message']); $this->assertCount(2, $json); // Test some invalid data $post = ['test' => 1]; $response = $this->actingAs($john)->post("/api/v4/shared-folders/{$folder->id}/config", $post); $response->assertStatus(422); $json = $response->json(); $this->assertSame('error', $json['status']); $this->assertCount(2, $json); $this->assertCount(1, $json['errors']); $this->assertSame('The requested configuration parameter is not supported.', $json['errors']['test']); $folder->refresh(); $this->assertNull($folder->getSetting('test')); $this->assertNull($folder->getSetting('acl')); // Test some valid data $post = ['acl' => ['john@kolab.org, full']]; $response = $this->actingAs($john)->post("/api/v4/shared-folders/{$folder->id}/config", $post); $response->assertStatus(200); $json = $response->json(); $this->assertCount(2, $json); $this->assertSame('success', $json['status']); $this->assertSame("Shared folder settings updated successfully.", $json['message']); $this->assertSame(['acl' => $post['acl']], $folder->fresh()->getConfig()); // Test input validation $post = ['acl' => ['john@kolab.org, full', 'test, full']]; $response = $this->actingAs($john)->post("/api/v4/shared-folders/{$folder->id}/config", $post); $response->assertStatus(422); $json = $response->json(); $this->assertCount(2, $json); $this->assertSame('error', $json['status']); $this->assertCount(1, $json['errors']); $this->assertCount(1, $json['errors']['acl']); $this->assertSame( "The specified email address is invalid.", $json['errors']['acl'][1] ); $this->assertSame(['acl' => ['john@kolab.org, full']], $folder->fresh()->getConfig()); } /** * Test fetching shared folder data/profile (GET /api/v4/shared-folders/) */ public function testShow(): void { $jack = $this->getTestUser('jack@kolab.org'); $john = $this->getTestUser('john@kolab.org'); $ned = $this->getTestUser('ned@kolab.org'); $folder = $this->getTestSharedFolder('folder-test@kolab.org'); $folder->assignToWallet($john->wallets->first()); $folder->setSetting('acl', '["anyone, full"]'); $folder->setAliases(['folder-alias@kolab.org']); // Test unauthenticated access $response = $this->get("/api/v4/shared-folders/{$folder->id}"); $response->assertStatus(401); // Test unauthorized access to a shared folder of another user $response = $this->actingAs($jack)->get("/api/v4/shared-folders/{$folder->id}"); $response->assertStatus(403); // John: Account owner - non-existing folder $response = $this->actingAs($john)->get("/api/v4/shared-folders/abc"); $response->assertStatus(404); // John: Account owner $response = $this->actingAs($john)->get("/api/v4/shared-folders/{$folder->id}"); $response->assertStatus(200); $json = $response->json(); $this->assertSame($folder->id, $json['id']); $this->assertSame($folder->email, $json['email']); $this->assertSame($folder->name, $json['name']); $this->assertSame($folder->type, $json['type']); $this->assertSame(['folder-alias@kolab.org'], $json['aliases']); $this->assertTrue(!empty($json['statusInfo'])); $this->assertArrayHasKey('isDeleted', $json); $this->assertArrayHasKey('isActive', $json); $this->assertArrayHasKey('isImapReady', $json); if (\config('app.with_ldap')) { $this->assertArrayHasKey('isLdapReady', $json); } $this->assertSame(['acl' => ['anyone, full']], $json['config']); $this->assertCount(1, $json['skus']); } /** * Test fetching SKUs list for a shared folder (GET /shared-folders//skus) */ public function testSkus(): void { Queue::fake(); $john = $this->getTestUser('john@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); $folder = $this->getTestSharedFolder('folder-test@kolab.org'); $folder->assignToWallet($john->wallets->first()); // Unauth access not allowed $response = $this->get("api/v4/shared-folders/{$folder->id}/skus"); $response->assertStatus(401); // Unauthorized access not allowed $response = $this->actingAs($jack)->get("api/v4/shared-folders/{$folder->id}/skus"); $response->assertStatus(403); // Non-existing folder $response = $this->actingAs($john)->get("api/v4/shared-folders/non-existing/skus"); $response->assertStatus(404); $response = $this->actingAs($john)->get("api/v4/shared-folders/{$folder->id}/skus"); $response->assertStatus(200); $json = $response->json(); $this->assertCount(1, $json); $this->assertSkuElement('shared-folder', $json[0], [ 'prio' => 0, 'type' => 'sharedFolder', 'handler' => 'SharedFolder', 'enabled' => true, 'readonly' => true, ]); } /** * Test fetching a shared folder status (GET /api/v4/shared-folders//status) * and forcing setup process update (?refresh=1) + * + * @group ldap */ public function testStatus(): void { Queue::fake(); $john = $this->getTestUser('john@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); $folder = $this->getTestSharedFolder('folder-test@kolab.org'); $folder->assignToWallet($john->wallets->first()); // Test unauthorized access $response = $this->get("/api/v4/shared-folders/abc/status"); $response->assertStatus(401); // Test unauthorized access $response = $this->actingAs($jack)->get("/api/v4/shared-folders/{$folder->id}/status"); $response->assertStatus(403); $folder->status = SharedFolder::STATUS_NEW | SharedFolder::STATUS_ACTIVE; $folder->save(); // Get resource status $response = $this->actingAs($john)->get("/api/v4/shared-folders/{$folder->id}/status"); $response->assertStatus(200); $json = $response->json(); $this->assertFalse($json['isImapReady']); $this->assertFalse($json['isReady']); $this->assertFalse($json['isDeleted']); $this->assertTrue($json['isActive']); $this->assertSame('running', $json['processState']); $this->assertTrue(empty($json['status'])); $this->assertTrue(empty($json['message'])); $this->assertSame('shared-folder-new', $json['process'][0]['label']); $this->assertSame(true, $json['process'][0]['state']); if (\config('app.with_ldap')) { $this->assertFalse($json['isLdapReady']); $this->assertSame('shared-folder-ldap-ready', $json['process'][1]['label']); $this->assertSame(false, $json['process'][1]['state']); } // Make sure the domain is confirmed (other test might unset that status) $domain = $this->getTestDomain('kolab.org'); $domain->status |= \App\Domain::STATUS_CONFIRMED; $domain->save(); $folder->status |= SharedFolder::STATUS_IMAP_READY; $folder->save(); // Now "reboot" the process Queue::fake(); $response = $this->actingAs($john)->get("/api/v4/shared-folders/{$folder->id}/status?refresh=1"); $response->assertStatus(200); $json = $response->json(); $this->assertTrue($json['isImapReady']); $this->assertFalse($json['isReady']); if (\config('app.with_ldap')) { $this->assertFalse($json['isLdapReady']); $this->assertSame('shared-folder-ldap-ready', $json['process'][1]['label']); $this->assertSame(false, $json['process'][1]['state']); $this->assertSame('shared-folder-imap-ready', $json['process'][2]['label']); $this->assertSame(true, $json['process'][2]['state']); } else { $this->assertSame('shared-folder-imap-ready', $json['process'][1]['label']); $this->assertSame(true, $json['process'][1]['state']); } $this->assertSame('success', $json['status']); $this->assertSame('Setup process has been pushed. Please wait.', $json['message']); $this->assertSame('waiting', $json['processState']); Queue::assertPushed(\App\Jobs\SharedFolder\CreateJob::class, 1); // Test a case when a domain is not ready Queue::fake(); $domain->status ^= \App\Domain::STATUS_CONFIRMED; $domain->save(); $response = $this->actingAs($john)->get("/api/v4/shared-folders/{$folder->id}/status?refresh=1"); $response->assertStatus(200); $json = $response->json(); $this->assertTrue($json['isImapReady']); $this->assertFalse($json['isReady']); if (\config('app.with_ldap')) { $this->assertFalse($json['isLdapReady']); $this->assertSame('shared-folder-ldap-ready', $json['process'][1]['label']); $this->assertSame(false, $json['process'][1]['state']); } else { $this->assertSame('shared-folder-imap-ready', $json['process'][1]['label']); $this->assertSame(true, $json['process'][1]['state']); } $this->assertSame('success', $json['status']); $this->assertSame('Setup process has been pushed. Please wait.', $json['message']); $this->assertSame('waiting', $json['processState']); Queue::assertPushed(\App\Jobs\SharedFolder\CreateJob::class, 1); } /** * Test SharedFoldersController::statusInfo() */ public function testStatusInfo(): void { $john = $this->getTestUser('john@kolab.org'); $folder = $this->getTestSharedFolder('folder-test@kolab.org'); $folder->assignToWallet($john->wallets->first()); $folder->status = SharedFolder::STATUS_NEW | SharedFolder::STATUS_ACTIVE; $folder->save(); $domain = $this->getTestDomain('kolab.org'); $domain->status |= \App\Domain::STATUS_CONFIRMED; $domain->save(); $result = SharedFoldersController::statusInfo($folder); $this->assertFalse($result['isDone']); $this->assertSame('running', $result['processState']); $this->assertSame('shared-folder-new', $result['process'][0]['label']); $this->assertSame(true, $result['process'][0]['state']); if (\config('app.with_ldap')) { $this->assertSame('shared-folder-ldap-ready', $result['process'][1]['label']); $this->assertSame(false, $result['process'][1]['state']); } $folder->created_at = Carbon::now()->subSeconds(181); $folder->save(); $result = SharedFoldersController::statusInfo($folder); $this->assertSame('failed', $result['processState']); $folder->status |= SharedFolder::STATUS_LDAP_READY | SharedFolder::STATUS_IMAP_READY; $folder->save(); $result = SharedFoldersController::statusInfo($folder); $this->assertTrue($result['isDone']); $this->assertSame('done', $result['processState']); $this->assertSame('shared-folder-new', $result['process'][0]['label']); $this->assertSame(true, $result['process'][0]['state']); if (\config('app.with_ldap')) { $this->assertSame('shared-folder-ldap-ready', $result['process'][1]['label']); $this->assertSame(true, $result['process'][1]['state']); $this->assertSame('shared-folder-imap-ready', $result['process'][2]['label']); $this->assertSame(true, $result['process'][2]['state']); } else { $this->assertSame('shared-folder-imap-ready', $result['process'][1]['label']); $this->assertSame(true, $result['process'][1]['state']); } } /** * Test shared folder creation (POST /api/v4/shared-folders) */ public function testStore(): void { Queue::fake(); $jack = $this->getTestUser('jack@kolab.org'); $john = $this->getTestUser('john@kolab.org'); // Test unauth request $response = $this->post("/api/v4/shared-folders", []); $response->assertStatus(401); // Test non-controller user $response = $this->actingAs($jack)->post("/api/v4/shared-folders", []); $response->assertStatus(403); // Test empty request $response = $this->actingAs($john)->post("/api/v4/shared-folders", []); $response->assertStatus(422); $json = $response->json(); $this->assertSame('error', $json['status']); $this->assertSame("The name field is required.", $json['errors']['name'][0]); $this->assertSame("The type field is required.", $json['errors']['type'][0]); $this->assertCount(2, $json); $this->assertCount(2, $json['errors']); // Test too long name, invalid alias domain $post = [ 'domain' => 'kolab.org', 'name' => str_repeat('A', 192), 'type' => 'unknown', 'aliases' => ['folder-alias@unknown.org'], ]; $response = $this->actingAs($john)->post("/api/v4/shared-folders", $post); $response->assertStatus(422); $json = $response->json(); $this->assertSame('error', $json['status']); $this->assertCount(2, $json); $this->assertSame(["The name may not be greater than 191 characters."], $json['errors']['name']); $this->assertSame(["The specified type is invalid."], $json['errors']['type']); $this->assertSame(["The specified domain is invalid."], $json['errors']['aliases']); $this->assertCount(3, $json['errors']); // Test successful folder creation $post['name'] = 'Test Folder'; $post['type'] = 'event'; $post['aliases'] = []; $response = $this->actingAs($john)->post("/api/v4/shared-folders", $post); $json = $response->json(); $response->assertStatus(200); $this->assertSame('success', $json['status']); $this->assertSame("Shared folder created successfully.", $json['message']); $this->assertCount(2, $json); $folder = SharedFolder::where('name', $post['name'])->first(); $this->assertInstanceOf(SharedFolder::class, $folder); $this->assertSame($post['type'], $folder->type); $this->assertTrue($john->sharedFolders()->get()->contains($folder)); $this->assertSame([], $folder->aliases()->pluck('alias')->all()); // Shared folder name must be unique within a domain $post['type'] = 'mail'; $response = $this->actingAs($john)->post("/api/v4/shared-folders", $post); $response->assertStatus(422); $json = $response->json(); $this->assertSame('error', $json['status']); $this->assertCount(2, $json); $this->assertCount(1, $json['errors']); $this->assertSame("The specified name is not available.", $json['errors']['name'][0]); $folder->forceDelete(); // Test successful folder creation with aliases $post['name'] = 'Test Folder'; $post['type'] = 'mail'; $post['aliases'] = ['folder-alias@kolab.org']; $response = $this->actingAs($john)->post("/api/v4/shared-folders", $post); $json = $response->json(); $response->assertStatus(200); $folder = SharedFolder::where('name', $post['name'])->first(); $this->assertSame(['folder-alias@kolab.org'], $folder->aliases()->pluck('alias')->all()); $folder->forceDelete(); // Test handling subfolders and lmtp alias email $post['name'] = 'Test/Folder'; $post['type'] = 'mail'; $post['aliases'] = ['shared+shared/Test/Folder@kolab.org']; $response = $this->actingAs($john)->post("/api/v4/shared-folders", $post); $json = $response->json(); $response->assertStatus(200); $folder = SharedFolder::where('name', $post['name'])->first(); $this->assertSame(['shared+shared/Test/Folder@kolab.org'], $folder->aliases()->pluck('alias')->all()); } /** * Test shared folder update (PUT /api/v4/shared-folders/getTestUser('jack@kolab.org'); $john = $this->getTestUser('john@kolab.org'); $ned = $this->getTestUser('ned@kolab.org'); $folder = $this->getTestSharedFolder('folder-test@kolab.org'); $folder->assignToWallet($john->wallets->first()); // Test unauthorized update $response = $this->get("/api/v4/shared-folders/{$folder->id}", []); $response->assertStatus(401); // Test unauthorized update $response = $this->actingAs($jack)->get("/api/v4/shared-folders/{$folder->id}", []); $response->assertStatus(403); // Name change $post = [ 'name' => 'Test Res', ]; $response = $this->actingAs($john)->put("/api/v4/shared-folders/{$folder->id}", $post); $json = $response->json(); $response->assertStatus(200); $this->assertSame('success', $json['status']); $this->assertSame("Shared folder updated successfully.", $json['message']); $this->assertCount(2, $json); $folder->refresh(); $this->assertSame($post['name'], $folder->name); // Aliases with error $post['aliases'] = ['folder-alias1@kolab.org', 'folder-alias2@unknown.com']; $response = $this->actingAs($john)->put("/api/v4/shared-folders/{$folder->id}", $post); $json = $response->json(); $response->assertStatus(422); $this->assertSame('error', $json['status']); $this->assertCount(2, $json); $this->assertCount(1, $json['errors']); $this->assertCount(1, $json['errors']['aliases']); $this->assertSame("The specified domain is invalid.", $json['errors']['aliases'][1]); $this->assertSame([], $folder->aliases()->pluck('alias')->all()); // Aliases with success expected $post['aliases'] = ['folder-alias1@kolab.org', 'folder-alias2@kolab.org']; $response = $this->actingAs($john)->put("/api/v4/shared-folders/{$folder->id}", $post); $json = $response->json(); $response->assertStatus(200); $this->assertSame('success', $json['status']); $this->assertSame("Shared folder updated successfully.", $json['message']); $this->assertCount(2, $json); $this->assertSame($post['aliases'], $folder->aliases()->pluck('alias')->all()); // All aliases removal $post['aliases'] = []; $response = $this->actingAs($john)->put("/api/v4/shared-folders/{$folder->id}", $post); $response->assertStatus(200); $this->assertSame($post['aliases'], $folder->aliases()->pluck('alias')->all()); } } diff --git a/src/tests/Feature/Controller/UsersTest.php b/src/tests/Feature/Controller/UsersTest.php index eb03ae08..1c578aee 100644 --- a/src/tests/Feature/Controller/UsersTest.php +++ b/src/tests/Feature/Controller/UsersTest.php @@ -1,1731 +1,1734 @@ clearBetaEntitlements(); $this->deleteTestUser('jane@kolabnow.com'); $this->deleteTestUser('UsersControllerTest1@userscontroller.com'); $this->deleteTestUser('UsersControllerTest2@userscontroller.com'); $this->deleteTestUser('UsersControllerTest3@userscontroller.com'); $this->deleteTestUser('UserEntitlement2A@UserEntitlement.com'); $this->deleteTestUser('john2.doe2@kolab.org'); $this->deleteTestUser('deleted@kolab.org'); $this->deleteTestUser('deleted@kolabnow.com'); $this->deleteTestDomain('userscontroller.com'); $this->deleteTestGroup('group-test@kolabnow.com'); $this->deleteTestGroup('group-test@kolab.org'); $this->deleteTestSharedFolder('folder-test@kolabnow.com'); $this->deleteTestResource('resource-test@kolabnow.com'); Sku::where('title', 'test')->delete(); $user = $this->getTestUser('john@kolab.org'); $wallet = $user->wallets()->first(); $wallet->discount()->dissociate(); $wallet->settings()->whereIn('key', ['mollie_id', 'stripe_id'])->delete(); $wallet->save(); $user->settings()->whereIn('key', ['greylist_enabled', 'guam_enabled'])->delete(); $user->status |= User::STATUS_IMAP_READY | User::STATUS_LDAP_READY | User::STATUS_ACTIVE; $user->save(); Plan::withEnvTenantContext()->where('title', 'individual')->update(['mode' => 'email']); $user->setSettings(['plan_id' => null]); } /** * {@inheritDoc} */ public function tearDown(): void { $this->clearBetaEntitlements(); $this->deleteTestUser('jane@kolabnow.com'); $this->deleteTestUser('UsersControllerTest1@userscontroller.com'); $this->deleteTestUser('UsersControllerTest2@userscontroller.com'); $this->deleteTestUser('UsersControllerTest3@userscontroller.com'); $this->deleteTestUser('UserEntitlement2A@UserEntitlement.com'); $this->deleteTestUser('john2.doe2@kolab.org'); $this->deleteTestUser('deleted@kolab.org'); $this->deleteTestUser('deleted@kolabnow.com'); $this->deleteTestDomain('userscontroller.com'); $this->deleteTestGroup('group-test@kolabnow.com'); $this->deleteTestGroup('group-test@kolab.org'); $this->deleteTestSharedFolder('folder-test@kolabnow.com'); $this->deleteTestResource('resource-test@kolabnow.com'); Sku::where('title', 'test')->delete(); $user = $this->getTestUser('john@kolab.org'); $wallet = $user->wallets()->first(); $wallet->discount()->dissociate(); $wallet->settings()->whereIn('key', ['mollie_id', 'stripe_id'])->delete(); $wallet->save(); $user->settings()->whereIn('key', ['greylist_enabled', 'guam_enabled'])->delete(); $user->status |= User::STATUS_IMAP_READY | User::STATUS_LDAP_READY | User::STATUS_ACTIVE; $user->save(); Plan::withEnvTenantContext()->where('title', 'individual')->update(['mode' => 'email']); $user->setSettings(['plan_id' => null]); parent::tearDown(); } /** * Test user deleting (DELETE /api/v4/users/) */ public function testDestroy(): void { // First create some users/accounts to delete $package_kolab = \App\Package::where('title', 'kolab')->first(); $package_domain = \App\Package::where('title', 'domain-hosting')->first(); $john = $this->getTestUser('john@kolab.org'); $user1 = $this->getTestUser('UsersControllerTest1@userscontroller.com'); $user2 = $this->getTestUser('UsersControllerTest2@userscontroller.com'); $user3 = $this->getTestUser('UsersControllerTest3@userscontroller.com'); $domain = $this->getTestDomain('userscontroller.com', [ 'status' => Domain::STATUS_NEW, 'type' => Domain::TYPE_PUBLIC, ]); $user1->assignPackage($package_kolab); $domain->assignPackage($package_domain, $user1); $user1->assignPackage($package_kolab, $user2); $user1->assignPackage($package_kolab, $user3); // Test unauth access $response = $this->delete("api/v4/users/{$user2->id}"); $response->assertStatus(401); // Test access to other user/account $response = $this->actingAs($john)->delete("api/v4/users/{$user2->id}"); $response->assertStatus(403); $response = $this->actingAs($john)->delete("api/v4/users/{$user1->id}"); $response->assertStatus(403); $json = $response->json(); $this->assertSame('error', $json['status']); $this->assertSame("Access denied", $json['message']); $this->assertCount(2, $json); // Test that non-controller cannot remove himself $response = $this->actingAs($user3)->delete("api/v4/users/{$user3->id}"); $response->assertStatus(403); // Test removing a non-controller user $response = $this->actingAs($user1)->delete("api/v4/users/{$user3->id}"); $response->assertStatus(200); $json = $response->json(); $this->assertEquals('success', $json['status']); $this->assertEquals('User deleted successfully.', $json['message']); // Test removing self (an account with users) $response = $this->actingAs($user1)->delete("api/v4/users/{$user1->id}"); $response->assertStatus(200); $json = $response->json(); $this->assertEquals('success', $json['status']); $this->assertEquals('User deleted successfully.', $json['message']); } /** * Test user deleting (DELETE /api/v4/users/) */ public function testDestroyByController(): void { // Create an account with additional controller - $user2 $package_kolab = \App\Package::where('title', 'kolab')->first(); $package_domain = \App\Package::where('title', 'domain-hosting')->first(); $user1 = $this->getTestUser('UsersControllerTest1@userscontroller.com'); $user2 = $this->getTestUser('UsersControllerTest2@userscontroller.com'); $user3 = $this->getTestUser('UsersControllerTest3@userscontroller.com'); $domain = $this->getTestDomain('userscontroller.com', [ 'status' => Domain::STATUS_NEW, 'type' => Domain::TYPE_PUBLIC, ]); $user1->assignPackage($package_kolab); $domain->assignPackage($package_domain, $user1); $user1->assignPackage($package_kolab, $user2); $user1->assignPackage($package_kolab, $user3); $user1->wallets()->first()->addController($user2); // 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 none of those // However, this is not 0-regression scenario as we // do not fully support additional controllers. //$response = $this->actingAs($user2)->delete("api/v4/users/{$user2->id}"); //$response->assertStatus(403); $response = $this->actingAs($user2)->delete("api/v4/users/{$user3->id}"); $response->assertStatus(200); $response = $this->actingAs($user2)->delete("api/v4/users/{$user1->id}"); $response->assertStatus(200); // Note: More detailed assertions in testDestroy() above $this->assertTrue($user1->fresh()->trashed()); $this->assertTrue($user2->fresh()->trashed()); $this->assertTrue($user3->fresh()->trashed()); } /** * Test user listing (GET /api/v4/users) */ public function testIndex(): void { // Test unauth access $response = $this->get("api/v4/users"); $response->assertStatus(401); $jack = $this->getTestUser('jack@kolab.org'); $joe = $this->getTestUser('joe@kolab.org'); $john = $this->getTestUser('john@kolab.org'); $ned = $this->getTestUser('ned@kolab.org'); $response = $this->actingAs($jack)->get("/api/v4/users"); $response->assertStatus(200); $json = $response->json(); $this->assertSame(false, $json['hasMore']); $this->assertSame(0, $json['count']); $this->assertCount(0, $json['list']); $response = $this->actingAs($john)->get("/api/v4/users"); $response->assertStatus(200); $json = $response->json(); $this->assertSame(false, $json['hasMore']); $this->assertSame(4, $json['count']); $this->assertCount(4, $json['list']); $this->assertSame($jack->email, $json['list'][0]['email']); $this->assertSame($joe->email, $json['list'][1]['email']); $this->assertSame($john->email, $json['list'][2]['email']); $this->assertSame($ned->email, $json['list'][3]['email']); // Values below are tested by Unit tests $this->assertArrayHasKey('isDeleted', $json['list'][0]); $this->assertArrayHasKey('isDegraded', $json['list'][0]); $this->assertArrayHasKey('isAccountDegraded', $json['list'][0]); $this->assertArrayHasKey('isSuspended', $json['list'][0]); $this->assertArrayHasKey('isActive', $json['list'][0]); $this->assertArrayHasKey('isReady', $json['list'][0]); $this->assertArrayHasKey('isImapReady', $json['list'][0]); if (\config('app.with_ldap')) { $this->assertArrayHasKey('isLdapReady', $json['list'][0]); } else { $this->assertArrayNotHasKey('isLdapReady', $json['list'][0]); } $response = $this->actingAs($ned)->get("/api/v4/users"); $response->assertStatus(200); $json = $response->json(); $this->assertSame(false, $json['hasMore']); $this->assertSame(4, $json['count']); $this->assertCount(4, $json['list']); $this->assertSame($jack->email, $json['list'][0]['email']); $this->assertSame($joe->email, $json['list'][1]['email']); $this->assertSame($john->email, $json['list'][2]['email']); $this->assertSame($ned->email, $json['list'][3]['email']); // Search by user email $response = $this->actingAs($john)->get("/api/v4/users?search=jack@k"); $response->assertStatus(200); $json = $response->json(); $this->assertSame(false, $json['hasMore']); $this->assertSame(1, $json['count']); $this->assertCount(1, $json['list']); $this->assertSame($jack->email, $json['list'][0]['email']); // Search by alias $response = $this->actingAs($john)->get("/api/v4/users?search=monster"); $response->assertStatus(200); $json = $response->json(); $this->assertSame(false, $json['hasMore']); $this->assertSame(1, $json['count']); $this->assertCount(1, $json['list']); $this->assertSame($joe->email, $json['list'][0]['email']); // Search by name $response = $this->actingAs($john)->get("/api/v4/users?search=land"); $response->assertStatus(200); $json = $response->json(); $this->assertSame(false, $json['hasMore']); $this->assertSame(1, $json['count']); $this->assertCount(1, $json['list']); $this->assertSame($ned->email, $json['list'][0]['email']); // TODO: Test paging } /** * Test fetching user data/profile (GET /api/v4/users/) */ public function testShow(): void { $userA = $this->getTestUser('UserEntitlement2A@UserEntitlement.com'); // Test getting profile of self $response = $this->actingAs($userA)->get("/api/v4/users/{$userA->id}"); $json = $response->json(); $response->assertStatus(200); $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($json['config']['greylist_enabled']); $this->assertFalse($json['config']['guam_enabled']); $this->assertSame([], $json['skus']); $this->assertSame([], $json['aliases']); // Values below are tested by Unit tests $this->assertArrayHasKey('isDeleted', $json); $this->assertArrayHasKey('isDegraded', $json); $this->assertArrayHasKey('isAccountDegraded', $json); $this->assertArrayHasKey('isSuspended', $json); $this->assertArrayHasKey('isActive', $json); $this->assertArrayHasKey('isReady', $json); $this->assertArrayHasKey('isImapReady', $json); if (\config('app.with_ldap')) { $this->assertArrayHasKey('isLdapReady', $json); } else { $this->assertArrayNotHasKey('isLdapReady', $json); } $john = $this->getTestUser('john@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); $ned = $this->getTestUser('ned@kolab.org'); // Test unauthorized access to a profile of other user $response = $this->actingAs($jack)->get("/api/v4/users/{$userA->id}"); $response->assertStatus(403); // Test authorized access to a profile of other user // Ned: Additional account controller $response = $this->actingAs($ned)->get("/api/v4/users/{$john->id}"); $response->assertStatus(200); $json = $response->json(); $this->assertSame(['john.doe@kolab.org'], $json['aliases']); $response = $this->actingAs($ned)->get("/api/v4/users/{$jack->id}"); $response->assertStatus(200); // John: Account owner $response = $this->actingAs($john)->get("/api/v4/users/{$jack->id}"); $response->assertStatus(200); $response = $this->actingAs($john)->get("/api/v4/users/{$ned->id}"); $response->assertStatus(200); $json = $response->json(); $storage_sku = Sku::withEnvTenantContext()->where('title', 'storage')->first(); $groupware_sku = Sku::withEnvTenantContext()->where('title', 'groupware')->first(); $mailbox_sku = Sku::withEnvTenantContext()->where('title', 'mailbox')->first(); $secondfactor_sku = Sku::withEnvTenantContext()->where('title', '2fa')->first(); $this->assertCount(5, $json['skus']); $this->assertSame(5, $json['skus'][$storage_sku->id]['count']); $this->assertSame([0,0,0,0,0], $json['skus'][$storage_sku->id]['costs']); $this->assertSame(1, $json['skus'][$groupware_sku->id]['count']); $this->assertSame([490], $json['skus'][$groupware_sku->id]['costs']); $this->assertSame(1, $json['skus'][$mailbox_sku->id]['count']); $this->assertSame([500], $json['skus'][$mailbox_sku->id]['costs']); $this->assertSame(1, $json['skus'][$secondfactor_sku->id]['count']); $this->assertSame([0], $json['skus'][$secondfactor_sku->id]['costs']); $this->assertSame([], $json['aliases']); } /** * Test fetching SKUs list for a user (GET /users//skus) */ public function testSkus(): void { $user = $this->getTestUser('john@kolab.org'); // Unauth access not allowed $response = $this->get("api/v4/users/{$user->id}/skus"); $response->assertStatus(401); // Create an sku for another tenant, to make sure it is not included in the result $nsku = Sku::create([ 'title' => 'test', 'name' => 'Test', 'description' => '', 'active' => true, 'cost' => 100, 'handler_class' => 'Mailbox', ]); $tenant = Tenant::whereNotIn('id', [\config('app.tenant_id')])->first(); $nsku->tenant_id = $tenant->id; $nsku->save(); $response = $this->actingAs($user)->get("api/v4/users/{$user->id}/skus"); $response->assertStatus(200); $json = $response->json(); $this->assertCount(5, $json); $this->assertSkuElement('mailbox', $json[0], [ 'prio' => 100, 'type' => 'user', 'handler' => 'Mailbox', 'enabled' => true, 'readonly' => true, ]); $this->assertSkuElement('storage', $json[1], [ 'prio' => 90, 'type' => 'user', 'handler' => 'Storage', 'enabled' => true, 'readonly' => true, 'range' => [ 'min' => 5, 'max' => 100, 'unit' => 'GB', ] ]); $this->assertSkuElement('groupware', $json[2], [ 'prio' => 80, 'type' => 'user', 'handler' => 'Groupware', 'enabled' => false, 'readonly' => false, ]); $this->assertSkuElement('activesync', $json[3], [ 'prio' => 70, 'type' => 'user', 'handler' => 'Activesync', 'enabled' => false, 'readonly' => false, 'required' => ['Groupware'], ]); $this->assertSkuElement('2fa', $json[4], [ 'prio' => 60, 'type' => 'user', 'handler' => 'Auth2F', 'enabled' => false, 'readonly' => false, 'forbidden' => ['Activesync'], ]); // Test inclusion of beta SKUs $sku = Sku::withEnvTenantContext()->where('title', 'beta')->first(); $user->assignSku($sku); $response = $this->actingAs($user)->get("api/v4/users/{$user->id}/skus"); $response->assertStatus(200); $json = $response->json(); $this->assertCount(6, $json); $this->assertSkuElement('beta', $json[5], [ 'prio' => 10, 'type' => 'user', 'handler' => 'Beta', 'enabled' => false, 'readonly' => false, ]); } /** * Test fetching user status (GET /api/v4/users//status) * and forcing setup process update (?refresh=1) * * @group imap * @group dns */ public function testStatus(): void { Queue::fake(); $john = $this->getTestUser('john@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); // Test unauthorized access $response = $this->actingAs($jack)->get("/api/v4/users/{$john->id}/status"); $response->assertStatus(403); $john->status &= ~User::STATUS_IMAP_READY; $john->status &= ~User::STATUS_LDAP_READY; $john->save(); // Get user status $response = $this->actingAs($john)->get("/api/v4/users/{$john->id}/status"); $response->assertStatus(200); $json = $response->json(); $this->assertFalse($json['isReady']); $this->assertFalse($json['isImapReady']); $this->assertTrue(empty($json['status'])); $this->assertTrue(empty($json['message'])); if (\config('app.with_ldap')) { $this->assertFalse($json['isLdapReady']); $this->assertSame('user-ldap-ready', $json['process'][1]['label']); $this->assertFalse($json['process'][1]['state']); $this->assertSame('user-imap-ready', $json['process'][2]['label']); $this->assertFalse($json['process'][2]['state']); } else { $this->assertArrayNotHasKey('isLdapReady', $json); $this->assertSame('user-imap-ready', $json['process'][1]['label']); $this->assertFalse($json['process'][1]['state']); } // Make sure the domain is confirmed (other test might unset that status) $domain = $this->getTestDomain('kolab.org'); $domain->status |= Domain::STATUS_CONFIRMED; $domain->save(); // Now "reboot" the process Queue::fake(); $response = $this->actingAs($john)->get("/api/v4/users/{$john->id}/status?refresh=1"); $response->assertStatus(200); $json = $response->json(); $this->assertFalse($json['isImapReady']); $this->assertFalse($json['isReady']); $this->assertSame('success', $json['status']); $this->assertSame('Setup process has been pushed. Please wait.', $json['message']); if (\config('app.with_ldap')) { $this->assertFalse($json['isLdapReady']); $this->assertSame('user-ldap-ready', $json['process'][1]['label']); $this->assertSame(false, $json['process'][1]['state']); $this->assertSame('user-imap-ready', $json['process'][2]['label']); $this->assertSame(false, $json['process'][2]['state']); } else { $this->assertSame('user-imap-ready', $json['process'][1]['label']); $this->assertSame(false, $json['process'][1]['state']); } Queue::assertPushed(\App\Jobs\User\CreateJob::class, 1); } /** * Test UsersController::statusInfo() */ public function testStatusInfo(): void { $user = $this->getTestUser('UsersControllerTest1@userscontroller.com'); $domain = $this->getTestDomain('userscontroller.com', [ 'status' => Domain::STATUS_NEW, 'type' => Domain::TYPE_PUBLIC, ]); $user->created_at = Carbon::now(); $user->status = User::STATUS_NEW; $user->save(); $result = UsersController::statusInfo($user); $this->assertFalse($result['isDone']); $this->assertSame([], $result['skus']); - $this->assertCount(3, $result['process']); - $this->assertSame('user-new', $result['process'][0]['label']); - $this->assertSame(true, $result['process'][0]['state']); if (\config('app.with_ldap')) { + $this->assertCount(3, $result['process']); $this->assertSame('user-ldap-ready', $result['process'][1]['label']); $this->assertSame(false, $result['process'][1]['state']); $this->assertSame('user-imap-ready', $result['process'][2]['label']); $this->assertSame(false, $result['process'][2]['state']); } else { + $this->assertCount(2, $result['process']); $this->assertSame('user-imap-ready', $result['process'][1]['label']); $this->assertSame(false, $result['process'][1]['state']); } + $this->assertSame('user-new', $result['process'][0]['label']); + $this->assertSame(true, $result['process'][0]['state']); $this->assertSame('running', $result['processState']); $this->assertTrue($result['enableRooms']); $this->assertFalse($result['enableBeta']); $user->created_at = Carbon::now()->subSeconds(181); $user->save(); $result = UsersController::statusInfo($user); $this->assertSame('failed', $result['processState']); $user->status |= User::STATUS_LDAP_READY | User::STATUS_IMAP_READY; $user->save(); $result = UsersController::statusInfo($user); $this->assertTrue($result['isDone']); - $this->assertCount(3, $result['process']); $this->assertSame('done', $result['processState']); - $this->assertSame('user-new', $result['process'][0]['label']); - $this->assertSame(true, $result['process'][0]['state']); if (\config('app.with_ldap')) { + $this->assertCount(3, $result['process']); $this->assertSame('user-ldap-ready', $result['process'][1]['label']); $this->assertSame(true, $result['process'][1]['state']); $this->assertSame('user-imap-ready', $result['process'][2]['label']); $this->assertSame(true, $result['process'][2]['state']); } else { + $this->assertCount(2, $result['process']); $this->assertSame('user-imap-ready', $result['process'][1]['label']); $this->assertSame(true, $result['process'][1]['state']); } + $this->assertSame('user-new', $result['process'][0]['label']); + $this->assertSame(true, $result['process'][0]['state']); $domain->status |= Domain::STATUS_VERIFIED; $domain->type = Domain::TYPE_EXTERNAL; $domain->save(); $result = UsersController::statusInfo($user); $this->assertFalse($result['isDone']); $this->assertSame([], $result['skus']); - $this->assertCount(7, $result['process']); - $this->assertSame('user-new', $result['process'][0]['label']); - $this->assertSame(true, $result['process'][0]['state']); if (\config('app.with_ldap')) { + $this->assertCount(7, $result['process']); $this->assertSame('user-ldap-ready', $result['process'][1]['label']); $this->assertSame(true, $result['process'][1]['state']); $this->assertSame('user-imap-ready', $result['process'][2]['label']); $this->assertSame(true, $result['process'][2]['state']); $this->assertSame('domain-new', $result['process'][3]['label']); $this->assertSame(true, $result['process'][3]['state']); $this->assertSame('domain-ldap-ready', $result['process'][4]['label']); $this->assertSame(false, $result['process'][4]['state']); $this->assertSame('domain-verified', $result['process'][5]['label']); $this->assertSame(true, $result['process'][5]['state']); $this->assertSame('domain-confirmed', $result['process'][6]['label']); $this->assertSame(false, $result['process'][6]['state']); } else { + $this->assertCount(5, $result['process']); $this->assertSame('user-imap-ready', $result['process'][1]['label']); $this->assertSame(true, $result['process'][1]['state']); $this->assertSame('domain-new', $result['process'][2]['label']); $this->assertSame(true, $result['process'][2]['state']); $this->assertSame('domain-verified', $result['process'][3]['label']); $this->assertSame(true, $result['process'][3]['state']); $this->assertSame('domain-confirmed', $result['process'][4]['label']); $this->assertSame(false, $result['process'][4]['state']); } + $this->assertSame('user-new', $result['process'][0]['label']); + $this->assertSame(true, $result['process'][0]['state']); // Test 'skus' property $user->assignSku(Sku::withEnvTenantContext()->where('title', 'beta')->first()); $result = UsersController::statusInfo($user); $this->assertSame(['beta'], $result['skus']); $this->assertTrue($result['enableBeta']); $user->assignSku(Sku::withEnvTenantContext()->where('title', 'groupware')->first()); $result = UsersController::statusInfo($user); $this->assertSame(['beta', 'groupware'], $result['skus']); // Degraded user $user->status |= User::STATUS_DEGRADED; $user->save(); $result = UsersController::statusInfo($user); $this->assertTrue($result['enableBeta']); $this->assertFalse($result['enableRooms']); // User in a tenant without 'room' SKU $user->status = User::STATUS_LDAP_READY | User::STATUS_IMAP_READY | User::STATUS_ACTIVE; $user->tenant_id = Tenant::where('title', 'Sample Tenant')->first()->id; $user->save(); $result = UsersController::statusInfo($user); $this->assertTrue($result['enableBeta']); $this->assertFalse($result['enableRooms']); } /** * Test user config update (POST /api/v4/users//config) */ public function testSetConfig(): void { $jack = $this->getTestUser('jack@kolab.org'); $john = $this->getTestUser('john@kolab.org'); $john->setSetting('greylist_enabled', null); $john->setSetting('guam_enabled', null); $john->setSetting('password_policy', null); $john->setSetting('max_password_age', null); // Test unknown user id $post = ['greylist_enabled' => 1]; $response = $this->actingAs($john)->post("/api/v4/users/123/config", $post); $json = $response->json(); $response->assertStatus(404); // Test access by user not being a wallet controller (controller's config change) $post = ['greylist_enabled' => 1]; $response = $this->actingAs($jack)->post("/api/v4/users/{$john->id}/config", $post); $json = $response->json(); $response->assertStatus(403); $this->assertSame('error', $json['status']); $this->assertSame("Access denied", $json['message']); $this->assertCount(2, $json); // Test access by user not being a wallet controller (self config change) $response = $this->actingAs($jack)->post("/api/v4/users/{$jack->id}/config", $post); $response->assertStatus(403); // Test some invalid data $post = ['grey' => 1, 'password_policy' => 'min:1,max:255']; $response = $this->actingAs($john)->post("/api/v4/users/{$john->id}/config", $post); $response->assertStatus(422); $json = $response->json(); $this->assertSame('error', $json['status']); $this->assertCount(2, $json); $this->assertCount(2, $json['errors']); $this->assertSame("The requested configuration parameter is not supported.", $json['errors']['grey']); $this->assertSame("Minimum password length cannot be less than 6.", $json['errors']['password_policy']); $this->assertNull($john->fresh()->getSetting('greylist_enabled')); // Test some valid data $post = [ 'greylist_enabled' => 1, 'guam_enabled' => 1, 'password_policy' => 'min:10,max:255,upper,lower,digit,special', 'max_password_age' => 6, ]; $response = $this->actingAs($john)->post("/api/v4/users/{$john->id}/config", $post); $response->assertStatus(200); $json = $response->json(); $this->assertCount(2, $json); $this->assertSame('success', $json['status']); $this->assertSame('User settings updated successfully.', $json['message']); $this->assertSame('true', $john->getSetting('greylist_enabled')); $this->assertSame('true', $john->getSetting('guam_enabled')); $this->assertSame('min:10,max:255,upper,lower,digit,special', $john->getSetting('password_policy')); $this->assertSame('6', $john->getSetting('max_password_age')); // Test some valid data, acting as another account controller $ned = $this->getTestUser('ned@kolab.org'); $post = ['greylist_enabled' => 0, 'guam_enabled' => 0, 'password_policy' => 'min:10,max:255,upper,last:1']; $response = $this->actingAs($ned)->post("/api/v4/users/{$john->id}/config", $post); $response->assertStatus(200); $json = $response->json(); $this->assertCount(2, $json); $this->assertSame('success', $json['status']); $this->assertSame('User settings updated successfully.', $json['message']); $this->assertSame('false', $john->fresh()->getSetting('greylist_enabled')); $this->assertSame(null, $john->fresh()->getSetting('guam_enabled')); $this->assertSame('min:10,max:255,upper,last:1', $john->fresh()->getSetting('password_policy')); } /** * Test user creation (POST /api/v4/users) */ public function testStore(): void { Queue::fake(); $jack = $this->getTestUser('jack@kolab.org'); $john = $this->getTestUser('john@kolab.org'); $john->setSetting('password_policy', 'min:8,max:100,digit'); $deleted_priv = $this->getTestUser('deleted@kolab.org'); $deleted_priv->delete(); // Test empty request $response = $this->actingAs($john)->post("/api/v4/users", []); $response->assertStatus(422); $json = $response->json(); $this->assertSame('error', $json['status']); $this->assertSame("The email field is required.", $json['errors']['email']); $this->assertSame("The password field is required.", $json['errors']['password'][0]); $this->assertCount(2, $json); // Test access by user not being a wallet controller $post = ['first_name' => 'Test']; $response = $this->actingAs($jack)->post("/api/v4/users", $post); $json = $response->json(); $response->assertStatus(403); $this->assertSame('error', $json['status']); $this->assertSame("Access denied", $json['message']); $this->assertCount(2, $json); // Test some invalid data $post = ['password' => '12345678', 'email' => 'invalid']; $response = $this->actingAs($john)->post("/api/v4/users", $post); $response->assertStatus(422); $json = $response->json(); $this->assertSame('error', $json['status']); $this->assertCount(2, $json); $this->assertSame('The password confirmation does not match.', $json['errors']['password'][0]); $this->assertSame('The specified email is invalid.', $json['errors']['email']); // Test existing user email $post = [ 'password' => 'simple123', 'password_confirmation' => 'simple123', 'first_name' => 'John2', 'last_name' => 'Doe2', 'email' => 'jack.daniels@kolab.org', ]; $response = $this->actingAs($john)->post("/api/v4/users", $post); $response->assertStatus(422); $json = $response->json(); $this->assertSame('error', $json['status']); $this->assertCount(2, $json); $this->assertSame('The specified email is not available.', $json['errors']['email']); $package_kolab = \App\Package::withEnvTenantContext()->where('title', 'kolab')->first(); $package_domain = \App\Package::withEnvTenantContext()->where('title', 'domain-hosting')->first(); $post = [ 'password' => 'simple123', 'password_confirmation' => 'simple123', 'first_name' => 'John2', 'last_name' => 'Doe2', 'email' => 'john2.doe2@kolab.org', 'organization' => 'TestOrg', 'aliases' => ['useralias1@kolab.org', 'deleted@kolab.org'], ]; // Missing package $response = $this->actingAs($john)->post("/api/v4/users", $post); $json = $response->json(); $response->assertStatus(422); $this->assertSame('error', $json['status']); $this->assertSame("Package is required.", $json['errors']['package']); $this->assertCount(2, $json); // Invalid package $post['package'] = $package_domain->id; $response = $this->actingAs($john)->post("/api/v4/users", $post); $json = $response->json(); $response->assertStatus(422); $this->assertSame('error', $json['status']); $this->assertSame("Invalid package selected.", $json['errors']['package']); $this->assertCount(2, $json); // Test password policy checking $post['package'] = $package_kolab->id; $post['password'] = 'password'; $response = $this->actingAs($john)->post("/api/v4/users", $post); $json = $response->json(); $response->assertStatus(422); $this->assertSame('error', $json['status']); $this->assertSame("The password confirmation does not match.", $json['errors']['password'][0]); $this->assertSame("Specified password does not comply with the policy.", $json['errors']['password'][1]); $this->assertCount(2, $json); // Test password confirmation $post['password_confirmation'] = 'password'; $response = $this->actingAs($john)->post("/api/v4/users", $post); $json = $response->json(); $response->assertStatus(422); $this->assertSame('error', $json['status']); $this->assertSame("Specified password does not comply with the policy.", $json['errors']['password'][0]); $this->assertCount(2, $json); // Test full and valid data $post['password'] = 'password123'; $post['password_confirmation'] = 'password123'; $response = $this->actingAs($john)->post("/api/v4/users", $post); $json = $response->json(); $response->assertStatus(200); $this->assertSame('success', $json['status']); $this->assertSame("User created successfully.", $json['message']); $this->assertCount(2, $json); $user = User::where('email', 'john2.doe2@kolab.org')->first(); $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')); $this->assertFalse($user->isRestricted()); /** @var \App\UserAlias[] $aliases */ $aliases = $user->aliases()->orderBy('alias')->get(); $this->assertCount(2, $aliases); $this->assertSame('deleted@kolab.org', $aliases[0]->alias); $this->assertSame('useralias1@kolab.org', $aliases[1]->alias); // Assert the new user entitlements $this->assertEntitlements($user, ['groupware', 'mailbox', 'storage', 'storage', 'storage', 'storage', 'storage']); // Assert the wallet to which the new user should be assigned to $wallet = $user->wallet(); $this->assertSame($john->wallets->first()->id, $wallet->id); // Attempt to create a user previously deleted $user->delete(); $post['package'] = $package_kolab->id; $post['aliases'] = []; $response = $this->actingAs($john)->post("/api/v4/users", $post); $json = $response->json(); $response->assertStatus(200); $this->assertSame('success', $json['status']); $this->assertSame("User created successfully.", $json['message']); $this->assertCount(2, $json); $user = User::where('email', 'john2.doe2@kolab.org')->first(); $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')); $this->assertCount(0, $user->aliases()->get()); $this->assertEntitlements($user, ['groupware', 'mailbox', 'storage', 'storage', 'storage', 'storage', 'storage']); // Test password reset link "mode" $code = new \App\VerificationCode(['mode' => 'password-reset', 'active' => false]); $john->verificationcodes()->save($code); $post = [ 'first_name' => 'John2', 'last_name' => 'Doe2', 'email' => 'deleted@kolab.org', 'organization' => '', 'aliases' => [], 'passwordLinkCode' => $code->short_code . '-' . $code->code, 'package' => $package_kolab->id, ]; $response = $this->actingAs($john)->post("/api/v4/users", $post); $json = $response->json(); $response->assertStatus(200); $this->assertSame('success', $json['status']); $this->assertSame("User created successfully.", $json['message']); $this->assertCount(2, $json); $user = $this->getTestUser('deleted@kolab.org'); $code->refresh(); $this->assertSame($user->id, $code->user_id); $this->assertTrue($code->active); $this->assertTrue(is_string($user->password) && strlen($user->password) >= 60); // Test acting as account controller not owner, which is not yet supported $john->wallets->first()->addController($user); $response = $this->actingAs($user)->post("/api/v4/users", []); $response->assertStatus(403); // Test that creating a user in a restricted account creates a restricted user $package_domain = Package::withEnvTenantContext()->where('title', 'domain-hosting')->first(); $owner = $this->getTestUser('UsersControllerTest1@userscontroller.com'); $domain = $this->getTestDomain( 'userscontroller.com', ['status' => Domain::STATUS_NEW, 'type' => Domain::TYPE_EXTERNAL] ); $domain->assignPackage($package_domain, $owner); $owner->restrict(); $post = [ 'password' => 'simple123', 'password_confirmation' => 'simple123', 'email' => 'UsersControllerTest2@userscontroller.com', 'package' => $package_kolab->id, ]; $response = $this->actingAs($owner)->post("/api/v4/users", $post); $response->assertStatus(200); $user = User::where('email', 'UsersControllerTest1@userscontroller.com')->first(); $this->assertTrue($user->isRestricted()); } /** * Test user update (PUT /api/v4/users/) */ public function testUpdate(): void { $userA = $this->getTestUser('UsersControllerTest1@userscontroller.com'); $userA->setSetting('password_policy', 'min:8,digit'); $jack = $this->getTestUser('jack@kolab.org'); $john = $this->getTestUser('john@kolab.org'); $ned = $this->getTestUser('ned@kolab.org'); $domain = $this->getTestDomain( 'userscontroller.com', ['status' => Domain::STATUS_NEW, 'type' => Domain::TYPE_EXTERNAL] ); // Test unauthorized update of other user profile $response = $this->actingAs($jack)->get("/api/v4/users/{$userA->id}", []); $response->assertStatus(403); // Test authorized update of account owner by account controller $response = $this->actingAs($ned)->get("/api/v4/users/{$john->id}", []); $response->assertStatus(200); // Test updating of self (empty request) $response = $this->actingAs($userA)->put("/api/v4/users/{$userA->id}", []); $response->assertStatus(200); $json = $response->json(); $this->assertSame('success', $json['status']); $this->assertSame("User data updated successfully.", $json['message']); $this->assertTrue(!empty($json['statusInfo'])); $this->assertCount(3, $json); // Test some invalid data $post = ['password' => '1234567', 'currency' => 'invalid']; $response = $this->actingAs($userA)->put("/api/v4/users/{$userA->id}", $post); $response->assertStatus(422); $json = $response->json(); $this->assertSame('error', $json['status']); $this->assertCount(2, $json); $this->assertSame("The password confirmation does not match.", $json['errors']['password'][0]); $this->assertSame("Specified password does not comply with the policy.", $json['errors']['password'][1]); $this->assertSame("The currency must be 3 characters.", $json['errors']['currency'][0]); // Test full profile update including password $post = [ 'password' => 'simple123', 'password_confirmation' => 'simple123', 'first_name' => 'John2', 'last_name' => 'Doe2', 'organization' => 'TestOrg', 'phone' => '+123 123 123', 'external_email' => 'external@gmail.com', 'billing_address' => 'billing', 'country' => 'CH', 'currency' => 'CHF', 'aliases' => ['useralias1@' . \config('app.domain'), 'useralias2@' . \config('app.domain')] ]; $response = $this->actingAs($userA)->put("/api/v4/users/{$userA->id}", $post); $json = $response->json(); $response->assertStatus(200); $this->assertSame('success', $json['status']); $this->assertSame("User data updated successfully.", $json['message']); $this->assertTrue(!empty($json['statusInfo'])); $this->assertCount(3, $json); $this->assertTrue($userA->password != $userA->fresh()->password); unset($post['password'], $post['password_confirmation'], $post['aliases']); foreach ($post as $key => $value) { $this->assertSame($value, $userA->getSetting($key)); } $aliases = $userA->aliases()->orderBy('alias')->get(); $this->assertCount(2, $aliases); $this->assertSame('useralias1@' . \config('app.domain'), $aliases[0]->alias); $this->assertSame('useralias2@' . \config('app.domain'), $aliases[1]->alias); // Test unsetting values $post = [ 'first_name' => '', 'last_name' => '', 'organization' => '', 'phone' => '', 'external_email' => '', 'billing_address' => '', 'country' => '', 'currency' => '', 'aliases' => ['useralias2@' . \config('app.domain')] ]; $response = $this->actingAs($userA)->put("/api/v4/users/{$userA->id}", $post); $json = $response->json(); $response->assertStatus(200); $this->assertSame('success', $json['status']); $this->assertSame("User data updated successfully.", $json['message']); $this->assertTrue(!empty($json['statusInfo'])); $this->assertCount(3, $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@' . \config('app.domain'), $aliases[0]->alias); // Test error on some invalid aliases missing password confirmation $post = [ 'password' => 'simple123', 'aliases' => [ 'useralias2@' . \config('app.domain'), 'useralias1@kolab.org', '@kolab.org', ] ]; $response = $this->actingAs($userA)->put("/api/v4/users/{$userA->id}", $post); $json = $response->json(); $response->assertStatus(422); $this->assertSame('error', $json['status']); $this->assertCount(2, $json['errors']); $this->assertCount(2, $json['errors']['aliases']); $this->assertSame("The specified domain is not available.", $json['errors']['aliases'][1]); $this->assertSame("The specified alias is invalid.", $json['errors']['aliases'][2]); $this->assertSame("The password confirmation does not match.", $json['errors']['password'][0]); // Test authorized update of other user $response = $this->actingAs($ned)->put("/api/v4/users/{$jack->id}", []); $response->assertStatus(200); $json = $response->json(); $this->assertTrue(empty($json['statusInfo'])); // TODO: Test error on aliases with invalid/non-existing/other-user's domain // Create entitlements and additional user for following tests $owner = $this->getTestUser('UsersControllerTest1@userscontroller.com'); $user = $this->getTestUser('UsersControllerTest2@userscontroller.com'); $package_domain = Package::withEnvTenantContext()->where('title', 'domain-hosting')->first(); $package_kolab = Package::withEnvTenantContext()->where('title', 'kolab')->first(); $package_lite = Package::withEnvTenantContext()->where('title', 'lite')->first(); $sku_mailbox = Sku::withEnvTenantContext()->where('title', 'mailbox')->first(); $sku_storage = Sku::withEnvTenantContext()->where('title', 'storage')->first(); $sku_groupware = Sku::withEnvTenantContext()->where('title', 'groupware')->first(); $domain = $this->getTestDomain( 'userscontroller.com', [ 'status' => Domain::STATUS_NEW, 'type' => Domain::TYPE_EXTERNAL, ] ); $domain->assignPackage($package_domain, $owner); $owner->assignPackage($package_kolab); $owner->assignPackage($package_lite, $user); // Non-controller cannot update his own entitlements, nor aliases $post = ['skus' => []]; $response = $this->actingAs($user)->put("/api/v4/users/{$user->id}", $post); $response->assertStatus(403); $post = ['aliases' => []]; $response = $this->actingAs($user)->put("/api/v4/users/{$user->id}", $post); $response->assertStatus(403); // Test updating entitlements $post = [ 'skus' => [ $sku_mailbox->id => 1, $sku_storage->id => 6, $sku_groupware->id => 1, ], ]; $response = $this->actingAs($owner)->put("/api/v4/users/{$user->id}", $post); $response->assertStatus(200); $json = $response->json(); $storage_cost = $user->entitlements() ->where('sku_id', $sku_storage->id) ->orderBy('cost') ->pluck('cost')->all(); $this->assertEntitlements( $user, ['groupware', 'mailbox', 'storage', 'storage', 'storage', 'storage', 'storage', 'storage'] ); $this->assertSame([0, 0, 0, 0, 0, 25], $storage_cost); $this->assertTrue(empty($json['statusInfo'])); // Test password reset link "mode" $code = new \App\VerificationCode(['mode' => 'password-reset', 'active' => false]); $owner->verificationcodes()->save($code); $post = ['passwordLinkCode' => $code->short_code . '-' . $code->code]; $response = $this->actingAs($owner)->put("/api/v4/users/{$user->id}", $post); $json = $response->json(); $response->assertStatus(200); $code->refresh(); $this->assertSame($user->id, $code->user_id); $this->assertTrue($code->active); $this->assertSame($user->password, $user->fresh()->password); } /** * Test UsersController::updateEntitlements() */ public function testUpdateEntitlements(): void { $jane = $this->getTestUser('jane@kolabnow.com'); $kolab = Package::withEnvTenantContext()->where('title', 'kolab')->first(); $storage = Sku::withEnvTenantContext()->where('title', 'storage')->first(); $activesync = Sku::withEnvTenantContext()->where('title', 'activesync')->first(); $groupware = Sku::withEnvTenantContext()->where('title', 'groupware')->first(); $mailbox = Sku::withEnvTenantContext()->where('title', 'mailbox')->first(); // standard package, 1 mailbox, 1 groupware, 5 storage $jane->assignPackage($kolab); // add 2 storage, 1 activesync $post = [ 'skus' => [ $mailbox->id => 1, $groupware->id => 1, $storage->id => 7, $activesync->id => 1 ] ]; $response = $this->actingAs($jane)->put("/api/v4/users/{$jane->id}", $post); $response->assertStatus(200); $this->assertEntitlements( $jane, [ 'activesync', 'groupware', 'mailbox', 'storage', 'storage', 'storage', 'storage', 'storage', 'storage', 'storage' ] ); // add 2 storage, remove 1 activesync $post = [ 'skus' => [ $mailbox->id => 1, $groupware->id => 1, $storage->id => 9, $activesync->id => 0 ] ]; $response = $this->actingAs($jane)->put("/api/v4/users/{$jane->id}", $post); $response->assertStatus(200); $this->assertEntitlements( $jane, [ 'groupware', 'mailbox', 'storage', 'storage', 'storage', 'storage', 'storage', 'storage', 'storage', 'storage', 'storage' ] ); // add mailbox $post = [ 'skus' => [ $mailbox->id => 2, $groupware->id => 1, $storage->id => 9, $activesync->id => 0 ] ]; $response = $this->actingAs($jane)->put("/api/v4/users/{$jane->id}", $post); $response->assertStatus(500); $this->assertEntitlements( $jane, [ 'groupware', 'mailbox', 'storage', 'storage', 'storage', 'storage', 'storage', 'storage', 'storage', 'storage', 'storage' ] ); // remove mailbox $post = [ 'skus' => [ $mailbox->id => 0, $groupware->id => 1, $storage->id => 9, $activesync->id => 0 ] ]; $response = $this->actingAs($jane)->put("/api/v4/users/{$jane->id}", $post); $response->assertStatus(500); $this->assertEntitlements( $jane, [ 'groupware', 'mailbox', 'storage', 'storage', 'storage', 'storage', 'storage', 'storage', 'storage', 'storage', 'storage' ] ); // less than free storage $post = [ 'skus' => [ $mailbox->id => 1, $groupware->id => 1, $storage->id => 1, $activesync->id => 0 ] ]; $response = $this->actingAs($jane)->put("/api/v4/users/{$jane->id}", $post); $response->assertStatus(200); $this->assertEntitlements( $jane, [ 'groupware', 'mailbox', 'storage', 'storage', 'storage', 'storage', 'storage' ] ); } /** * Test user data response used in show and info actions */ public function testUserResponse(): void { $provider = \config('services.payment_provider') ?: 'mollie'; $john = $this->getTestUser('john@kolab.org'); $wallet = $john->wallets()->first(); $wallet->setSettings(['mollie_id' => null, 'stripe_id' => null]); $wallet->owner->setSettings(['plan_id' => null]); $result = $this->invokeMethod(new UsersController(), 'userResponse', [$john]); $this->assertEquals($john->id, $result['id']); $this->assertEquals($john->email, $result['email']); $this->assertEquals($john->status, $result['status']); $this->assertTrue(is_array($result['statusInfo'])); $this->assertTrue(is_array($result['settings'])); $this->assertSame('US', $result['settings']['country']); $this->assertSame('USD', $result['settings']['currency']); $this->assertTrue(is_array($result['accounts'])); $this->assertTrue(is_array($result['wallets'])); $this->assertCount(0, $result['accounts']); $this->assertCount(1, $result['wallets']); $this->assertSame($wallet->id, $result['wallet']['id']); $this->assertArrayNotHasKey('discount', $result['wallet']); $this->assertFalse($result['isLocked']); $this->assertTrue($result['statusInfo']['enableDomains']); $this->assertTrue($result['statusInfo']['enableWallets']); $this->assertTrue($result['statusInfo']['enableWalletMandates']); $this->assertTrue($result['statusInfo']['enableWalletPayments']); $this->assertTrue($result['statusInfo']['enableUsers']); $this->assertTrue($result['statusInfo']['enableSettings']); $this->assertTrue($result['statusInfo']['enableDistlists']); $this->assertTrue($result['statusInfo']['enableFolders']); // Ned is John's wallet controller $plan = Plan::withEnvTenantContext()->where('title', 'individual')->first(); $plan->mode = Plan::MODE_MANDATE; $plan->save(); $wallet->owner->setSettings(['plan_id' => $plan->id]); $ned = $this->getTestUser('ned@kolab.org'); $ned_wallet = $ned->wallets()->first(); $result = $this->invokeMethod(new UsersController(), 'userResponse', [$ned]); $this->assertEquals($ned->id, $result['id']); $this->assertEquals($ned->email, $result['email']); $this->assertTrue(is_array($result['accounts'])); $this->assertTrue(is_array($result['wallets'])); $this->assertCount(1, $result['accounts']); $this->assertCount(1, $result['wallets']); $this->assertSame($wallet->id, $result['wallet']['id']); $this->assertSame($wallet->id, $result['accounts'][0]['id']); $this->assertSame($ned_wallet->id, $result['wallets'][0]['id']); $this->assertSame($provider, $result['wallet']['provider']); $this->assertSame($provider, $result['wallets'][0]['provider']); $this->assertFalse($result['isLocked']); $this->assertTrue($result['statusInfo']['enableDomains']); $this->assertTrue($result['statusInfo']['enableWallets']); $this->assertTrue($result['statusInfo']['enableWalletMandates']); $this->assertFalse($result['statusInfo']['enableWalletPayments']); $this->assertTrue($result['statusInfo']['enableUsers']); $this->assertTrue($result['statusInfo']['enableSettings']); $this->assertTrue($result['statusInfo']['enableDistlists']); $this->assertTrue($result['statusInfo']['enableFolders']); // Test discount in a response $discount = Discount::where('code', 'TEST')->first(); $wallet->discount()->associate($discount); $wallet->save(); $mod_provider = $provider == 'mollie' ? 'stripe' : 'mollie'; $wallet->setSetting($mod_provider . '_id', 123); $john->refresh(); $result = $this->invokeMethod(new UsersController(), 'userResponse', [$john]); $this->assertEquals($john->id, $result['id']); $this->assertSame($discount->id, $result['wallet']['discount_id']); $this->assertSame($discount->discount, $result['wallet']['discount']); $this->assertSame($discount->description, $result['wallet']['discount_description']); $this->assertSame($mod_provider, $result['wallet']['provider']); $this->assertSame($discount->id, $result['wallets'][0]['discount_id']); $this->assertSame($discount->discount, $result['wallets'][0]['discount']); $this->assertSame($discount->description, $result['wallets'][0]['discount_description']); $this->assertSame($mod_provider, $result['wallets'][0]['provider']); $this->assertFalse($result['isLocked']); // Jack is not a John's wallet controller $jack = $this->getTestUser('jack@kolab.org'); $result = $this->invokeMethod(new UsersController(), 'userResponse', [$jack]); $this->assertFalse($result['statusInfo']['enableDomains']); $this->assertFalse($result['statusInfo']['enableWallets']); $this->assertFalse($result['statusInfo']['enableWalletMandates']); $this->assertFalse($result['statusInfo']['enableWalletPayments']); $this->assertFalse($result['statusInfo']['enableUsers']); $this->assertFalse($result['statusInfo']['enableSettings']); $this->assertFalse($result['statusInfo']['enableDistlists']); $this->assertFalse($result['statusInfo']['enableFolders']); $this->assertFalse($result['isLocked']); // Test locked user $john->status &= ~User::STATUS_ACTIVE; $john->save(); $result = $this->invokeMethod(new UsersController(), 'userResponse', [$john]); $this->assertTrue($result['isLocked']); } /** * User email address validation. * * Note: Technically these include unit tests, but let's keep it here for now. * FIXME: Shall we do a http request for each case? */ public function testValidateEmail(): void { Queue::fake(); $public_domains = Domain::getPublicDomains(); $domain = reset($public_domains); $john = $this->getTestUser('john@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); $user = $this->getTestUser('UsersControllerTest1@userscontroller.com'); $folder = $this->getTestSharedFolder('folder-event@kolab.org'); $folder->setAliases(['folder-alias1@kolab.org']); $folder_del = $this->getTestSharedFolder('folder-test@kolabnow.com'); $folder_del->setAliases(['folder-alias2@kolabnow.com']); $folder_del->delete(); $pub_group = $this->getTestGroup('group-test@kolabnow.com'); $pub_group->delete(); $priv_group = $this->getTestGroup('group-test@kolab.org'); $resource = $this->getTestResource('resource-test@kolabnow.com'); $resource->delete(); $cases = [ // valid (user domain) ["admin@kolab.org", $john, null], // valid (public domain) ["test.test@$domain", $john, null], // Invalid format ["$domain", $john, 'The specified email is invalid.'], [".@$domain", $john, 'The specified email is invalid.'], ["test123456@localhost", $john, 'The specified domain is invalid.'], ["test123456@unknown-domain.org", $john, 'The specified domain is invalid.'], ["$domain", $john, 'The specified email is invalid.'], [".@$domain", $john, 'The specified email is invalid.'], // forbidden local part on public domains ["admin@$domain", $john, 'The specified email is not available.'], ["administrator@$domain", $john, 'The specified email is not available.'], // forbidden (other user's domain) ["testtest@kolab.org", $user, 'The specified domain is not available.'], // existing alias of other user ["jack.daniels@kolab.org", $john, 'The specified email is not available.'], // An existing shared folder or folder alias ["folder-event@kolab.org", $john, 'The specified email is not available.'], ["folder-alias1@kolab.org", $john, 'The specified email is not available.'], // A soft-deleted shared folder or folder alias ["folder-test@kolabnow.com", $john, 'The specified email is not available.'], ["folder-alias2@kolabnow.com", $john, 'The specified email is not available.'], // A group ["group-test@kolab.org", $john, 'The specified email is not available.'], // A soft-deleted group ["group-test@kolabnow.com", $john, 'The specified email is not available.'], // A resource ["resource-test1@kolab.org", $john, 'The specified email is not available.'], // A soft-deleted resource ["resource-test@kolabnow.com", $john, 'The specified email is not available.'], ]; foreach ($cases as $idx => $case) { list($email, $user, $expected) = $case; $deleted = null; $result = UsersController::validateEmail($email, $user, $deleted); $this->assertSame($expected, $result, "Case {$email}"); $this->assertNull($deleted, "Case {$email}"); } } /** * User email validation - tests for $deleted argument * * Note: Technically these include unit tests, but let's keep it here for now. * FIXME: Shall we do a http request for each case? */ public function testValidateEmailDeleted(): void { Queue::fake(); $john = $this->getTestUser('john@kolab.org'); $deleted_priv = $this->getTestUser('deleted@kolab.org'); $deleted_priv->delete(); $deleted_pub = $this->getTestUser('deleted@kolabnow.com'); $deleted_pub->delete(); $result = UsersController::validateEmail('deleted@kolab.org', $john, $deleted); $this->assertSame(null, $result); $this->assertSame($deleted_priv->id, $deleted->id); $result = UsersController::validateEmail('deleted@kolabnow.com', $john, $deleted); $this->assertSame('The specified email is not available.', $result); $this->assertSame(null, $deleted); $result = UsersController::validateEmail('jack@kolab.org', $john, $deleted); $this->assertSame('The specified email is not available.', $result); $this->assertSame(null, $deleted); $pub_group = $this->getTestGroup('group-test@kolabnow.com'); $priv_group = $this->getTestGroup('group-test@kolab.org'); // A group in a public domain, existing $result = UsersController::validateEmail($pub_group->email, $john, $deleted); $this->assertSame('The specified email is not available.', $result); $this->assertNull($deleted); $pub_group->delete(); // A group in a public domain, deleted $result = UsersController::validateEmail($pub_group->email, $john, $deleted); $this->assertSame('The specified email is not available.', $result); $this->assertNull($deleted); // A group in a private domain, existing $result = UsersController::validateEmail($priv_group->email, $john, $deleted); $this->assertSame('The specified email is not available.', $result); $this->assertNull($deleted); $priv_group->delete(); // A group in a private domain, deleted $result = UsersController::validateEmail($priv_group->email, $john, $deleted); $this->assertSame(null, $result); $this->assertSame($priv_group->id, $deleted->id); // TODO: Test the same with a resource and shared folder } /** * User email alias validation. * * Note: Technically these include unit tests, but let's keep it here for now. * FIXME: Shall we do a http request for each case? */ public function testValidateAlias(): void { Queue::fake(); $public_domains = Domain::getPublicDomains(); $domain = reset($public_domains); $john = $this->getTestUser('john@kolab.org'); $user = $this->getTestUser('UsersControllerTest1@userscontroller.com'); $deleted_priv = $this->getTestUser('deleted@kolab.org'); $deleted_priv->setAliases(['deleted-alias@kolab.org']); $deleted_priv->delete(); $deleted_pub = $this->getTestUser('deleted@kolabnow.com'); $deleted_pub->setAliases(['deleted-alias@kolabnow.com']); $deleted_pub->delete(); $folder = $this->getTestSharedFolder('folder-event@kolab.org'); $folder->setAliases(['folder-alias1@kolab.org']); $folder_del = $this->getTestSharedFolder('folder-test@kolabnow.com'); $folder_del->setAliases(['folder-alias2@kolabnow.com']); $folder_del->delete(); $group_priv = $this->getTestGroup('group-test@kolab.org'); $group = $this->getTestGroup('group-test@kolabnow.com'); $group->delete(); $resource = $this->getTestResource('resource-test@kolabnow.com'); $resource->delete(); $cases = [ // Invalid format ["$domain", $john, 'The specified alias is invalid.'], [".@$domain", $john, 'The specified alias is invalid.'], ["test123456@localhost", $john, 'The specified domain is invalid.'], ["test123456@unknown-domain.org", $john, 'The specified domain is invalid.'], ["$domain", $john, 'The specified alias is invalid.'], [".@$domain", $john, 'The specified alias is invalid.'], // forbidden local part on public domains ["admin@$domain", $john, 'The specified alias is not available.'], ["administrator@$domain", $john, 'The specified alias is not available.'], // forbidden (other user's domain) ["testtest@kolab.org", $user, 'The specified domain is not available.'], // existing alias of other user, to be an alias, user in the same group account ["jack.daniels@kolab.org", $john, null], // existing user ["jack@kolab.org", $john, 'The specified alias is not available.'], // valid (user domain) ["admin@kolab.org", $john, null], // valid (public domain) ["test.test@$domain", $john, null], // An alias that was a user email before is allowed, but only for custom domains ["deleted@kolab.org", $john, null], ["deleted-alias@kolab.org", $john, null], ["deleted@kolabnow.com", $john, 'The specified alias is not available.'], ["deleted-alias@kolabnow.com", $john, 'The specified alias is not available.'], // An existing shared folder or folder alias ["folder-event@kolab.org", $john, 'The specified alias is not available.'], ["folder-alias1@kolab.org", $john, null], // A soft-deleted shared folder or folder alias ["folder-test@kolabnow.com", $john, 'The specified alias is not available.'], ["folder-alias2@kolabnow.com", $john, 'The specified alias is not available.'], // A group with the same email address exists ["group-test@kolab.org", $john, 'The specified alias is not available.'], // A soft-deleted group ["group-test@kolabnow.com", $john, 'The specified alias is not available.'], // A resource ["resource-test1@kolab.org", $john, 'The specified alias is not available.'], // A soft-deleted resource ["resource-test@kolabnow.com", $john, 'The specified alias is not available.'], ]; foreach ($cases as $idx => $case) { list($alias, $user, $expected) = $case; $result = UsersController::validateAlias($alias, $user); $this->assertSame($expected, $result, "Case {$alias}"); } } } diff --git a/src/tests/Feature/GroupTest.php b/src/tests/Feature/GroupTest.php index cbbd2c35..748c55af 100644 --- a/src/tests/Feature/GroupTest.php +++ b/src/tests/Feature/GroupTest.php @@ -1,425 +1,450 @@ deleteTestUser('user-test@kolabnow.com'); $this->deleteTestGroup('group-test@kolabnow.com'); } public function tearDown(): void { $this->deleteTestUser('user-test@kolabnow.com'); $this->deleteTestGroup('group-test@kolabnow.com'); parent::tearDown(); } /** * Tests for Group::assignToWallet() */ public function testAssignToWallet(): void { $user = $this->getTestUser('user-test@kolabnow.com'); $group = $this->getTestGroup('group-test@kolabnow.com'); $result = $group->assignToWallet($user->wallets->first()); $this->assertSame($group, $result); $this->assertSame(1, $group->entitlements()->count()); // Can't be done twice on the same group $this->expectException(\Exception::class); $result->assignToWallet($user->wallets->first()); } /** * Test Group::getConfig() and setConfig() methods */ public function testConfigTrait(): void { $group = $this->getTestGroup('group-test@kolabnow.com'); $group->setSetting('sender_policy', '["test","-"]'); $this->assertSame(['sender_policy' => ['test']], $group->getConfig()); $result = $group->setConfig(['sender_policy' => [], 'unknown' => false]); $this->assertSame(['sender_policy' => []], $group->getConfig()); $this->assertSame('[]', $group->getSetting('sender_policy')); $this->assertSame(['unknown' => "The requested configuration parameter is not supported."], $result); $result = $group->setConfig(['sender_policy' => ['test']]); $this->assertSame(['sender_policy' => ['test']], $group->getConfig()); $this->assertSame('["test","-"]', $group->getSetting('sender_policy')); $this->assertSame([], $result); } /** * Test creating a group */ public function testCreate(): void { Queue::fake(); $group = Group::create(['email' => 'GROUP-test@kolabnow.com']); $this->assertSame('group-test@kolabnow.com', $group->email); $this->assertSame('group-test', $group->name); $this->assertMatchesRegularExpression('/^[0-9]{1,20}$/', $group->id); $this->assertSame([], $group->members); $this->assertTrue($group->isNew()); $this->assertFalse($group->isActive()); Queue::assertPushed( \App\Jobs\Group\CreateJob::class, function ($job) use ($group) { $groupEmail = TestCase::getObjectProperty($job, 'groupEmail'); $groupId = TestCase::getObjectProperty($job, 'groupId'); return $groupEmail === $group->email && $groupId === $group->id; } ); } /** * Test group deletion and force-deletion */ public function testDelete(): void { Queue::fake(); $user = $this->getTestUser('user-test@kolabnow.com'); $group = $this->getTestGroup('group-test@kolabnow.com'); $group->assignToWallet($user->wallets->first()); $entitlements = \App\Entitlement::where('entitleable_id', $group->id); $this->assertSame(1, $entitlements->count()); $group->delete(); $this->assertTrue($group->fresh()->trashed()); $this->assertSame(0, $entitlements->count()); $this->assertSame(1, $entitlements->withTrashed()->count()); $group->forceDelete(); $this->assertSame(0, $entitlements->withTrashed()->count()); $this->assertCount(0, Group::withTrashed()->where('id', $group->id)->get()); Queue::assertPushed(\App\Jobs\Group\DeleteJob::class, 1); Queue::assertPushed( \App\Jobs\Group\DeleteJob::class, function ($job) use ($group) { $groupEmail = TestCase::getObjectProperty($job, 'groupEmail'); $groupId = TestCase::getObjectProperty($job, 'groupId'); return $groupEmail === $group->email && $groupId === $group->id; } ); } /** * Test eventlog on group deletion */ public function testDeleteAndEventLog(): void { Queue::fake(); $group = $this->getTestGroup('group-test@kolabnow.com'); EventLog::createFor($group, EventLog::TYPE_SUSPENDED, 'test'); $group->delete(); $this->assertCount(1, EventLog::where('object_id', $group->id)->where('object_type', Group::class)->get()); $group->forceDelete(); $this->assertCount(0, EventLog::where('object_id', $group->id)->where('object_type', Group::class)->get()); } /** * Tests for Group::emailExists() */ public function testEmailExists(): void { Queue::fake(); $group = $this->getTestGroup('group-test@kolabnow.com'); $this->assertFalse(Group::emailExists('unknown@domain.tld')); $this->assertTrue(Group::emailExists($group->email)); $result = Group::emailExists($group->email, true); $this->assertSame($result->id, $group->id); $group->delete(); $this->assertTrue(Group::emailExists($group->email)); $result = Group::emailExists($group->email, true); $this->assertSame($result->id, $group->id); } /* * Test group restoring */ public function testRestore(): void { Queue::fake(); $user = $this->getTestUser('user-test@kolabnow.com'); $group = $this->getTestGroup('group-test@kolabnow.com', [ 'status' => Group::STATUS_ACTIVE | Group::STATUS_LDAP_READY | Group::STATUS_SUSPENDED, ]); $group->assignToWallet($user->wallets->first()); $entitlements = \App\Entitlement::where('entitleable_id', $group->id); $this->assertTrue($group->isSuspended()); - $this->assertTrue($group->isLdapReady()); + if (\config('app.with_ldap')) { + $this->assertTrue($group->isLdapReady()); + } $this->assertTrue($group->isActive()); $this->assertSame(1, $entitlements->count()); $group->delete(); $this->assertTrue($group->fresh()->trashed()); $this->assertSame(0, $entitlements->count()); $this->assertSame(1, $entitlements->withTrashed()->count()); Queue::fake(); $group->restore(); $group->refresh(); $this->assertFalse($group->trashed()); $this->assertFalse($group->isDeleted()); $this->assertFalse($group->isSuspended()); - $this->assertFalse($group->isLdapReady()); + if (\config('app.with_ldap')) { + $this->assertFalse($group->isLdapReady()); + } $this->assertFalse($group->isActive()); $this->assertTrue($group->isNew()); $this->assertSame(1, $entitlements->count()); $entitlements->get()->each(function ($ent) { $this->assertTrue($ent->updated_at->greaterThan(\Carbon\Carbon::now()->subSeconds(5))); }); Queue::assertPushed(\App\Jobs\Group\CreateJob::class, 1); Queue::assertPushed( \App\Jobs\Group\CreateJob::class, function ($job) use ($group) { $groupEmail = TestCase::getObjectProperty($job, 'groupEmail'); $groupId = TestCase::getObjectProperty($job, 'groupId'); return $groupEmail === $group->email && $groupId === $group->id; } ); } /** * Tests for GroupSettingsTrait functionality and GroupSettingObserver */ public function testSettings(): void { Queue::fake(); Queue::assertNothingPushed(); $group = $this->getTestGroup('group-test@kolabnow.com'); Queue::assertPushed(\App\Jobs\Group\UpdateJob::class, 0); // Add a setting $group->setSetting('unknown', 'test'); Queue::assertPushed(\App\Jobs\Group\UpdateJob::class, 0); // Add a setting that is synced to LDAP $group->setSetting('sender_policy', '[]'); - Queue::assertPushed(\App\Jobs\Group\UpdateJob::class, 1); + if (\config('app.with_ldap')) { + Queue::assertPushed(\App\Jobs\Group\UpdateJob::class, 1); + } // Note: We test both current group as well as fresh group object // to make sure cache works as expected $this->assertSame('test', $group->getSetting('unknown')); $this->assertSame('[]', $group->fresh()->getSetting('sender_policy')); Queue::fake(); // Update a setting $group->setSetting('unknown', 'test1'); Queue::assertPushed(\App\Jobs\Group\UpdateJob::class, 0); // Update a setting that is synced to LDAP $group->setSetting('sender_policy', '["-"]'); - Queue::assertPushed(\App\Jobs\Group\UpdateJob::class, 1); + if (\config('app.with_ldap')) { + Queue::assertPushed(\App\Jobs\Group\UpdateJob::class, 1); + } $this->assertSame('test1', $group->getSetting('unknown')); $this->assertSame('["-"]', $group->fresh()->getSetting('sender_policy')); Queue::fake(); // Delete a setting (null) $group->setSetting('unknown', null); Queue::assertPushed(\App\Jobs\Group\UpdateJob::class, 0); // Delete a setting that is synced to LDAP $group->setSetting('sender_policy', null); - Queue::assertPushed(\App\Jobs\Group\UpdateJob::class, 1); + if (\config('app.with_ldap')) { + Queue::assertPushed(\App\Jobs\Group\UpdateJob::class, 1); + } $this->assertSame(null, $group->getSetting('unknown')); $this->assertSame(null, $group->fresh()->getSetting('sender_policy')); } /** * Test group status assignment and is*() methods */ public function testStatus(): void { $group = new Group(); $this->assertSame(false, $group->isNew()); $this->assertSame(false, $group->isActive()); $this->assertSame(false, $group->isDeleted()); - $this->assertSame(false, $group->isLdapReady()); + if (\config('app.with_ldap')) { + $this->assertSame(false, $group->isLdapReady()); + } $this->assertSame(false, $group->isSuspended()); $group->status = Group::STATUS_NEW; $this->assertSame(true, $group->isNew()); $this->assertSame(false, $group->isActive()); $this->assertSame(false, $group->isDeleted()); - $this->assertSame(false, $group->isLdapReady()); + if (\config('app.with_ldap')) { + $this->assertSame(false, $group->isLdapReady()); + } $this->assertSame(false, $group->isSuspended()); $group->status |= Group::STATUS_ACTIVE; $this->assertSame(true, $group->isNew()); $this->assertSame(true, $group->isActive()); $this->assertSame(false, $group->isDeleted()); - $this->assertSame(false, $group->isLdapReady()); + if (\config('app.with_ldap')) { + $this->assertSame(false, $group->isLdapReady()); + } $this->assertSame(false, $group->isSuspended()); - $group->status |= Group::STATUS_LDAP_READY; + if (\config('app.with_ldap')) { + $group->status |= Group::STATUS_LDAP_READY; + } $this->assertSame(true, $group->isNew()); $this->assertSame(true, $group->isActive()); $this->assertSame(false, $group->isDeleted()); - $this->assertSame(true, $group->isLdapReady()); + + if (\config('app.with_ldap')) { + $this->assertSame(true, $group->isLdapReady()); + } $this->assertSame(false, $group->isSuspended()); $group->status |= Group::STATUS_DELETED; $this->assertSame(true, $group->isNew()); $this->assertSame(true, $group->isActive()); $this->assertSame(true, $group->isDeleted()); - $this->assertSame(true, $group->isLdapReady()); + if (\config('app.with_ldap')) { + $this->assertSame(true, $group->isLdapReady()); + } $this->assertSame(false, $group->isSuspended()); $group->status |= Group::STATUS_SUSPENDED; $this->assertSame(true, $group->isNew()); $this->assertSame(true, $group->isActive()); $this->assertSame(true, $group->isDeleted()); - $this->assertSame(true, $group->isLdapReady()); + if (\config('app.with_ldap')) { + $this->assertSame(true, $group->isLdapReady()); + } $this->assertSame(true, $group->isSuspended()); // Unknown status value $this->expectException(\Exception::class); $group->status = 111; } /** * Tests for Group::suspend() */ public function testSuspend(): void { Queue::fake(); $group = $this->getTestGroup('group-test@kolabnow.com'); $group->suspend(); $this->assertTrue($group->isSuspended()); Queue::assertPushed(\App\Jobs\Group\UpdateJob::class, 1); Queue::assertPushed( \App\Jobs\Group\UpdateJob::class, function ($job) use ($group) { $groupEmail = TestCase::getObjectProperty($job, 'groupEmail'); $groupId = TestCase::getObjectProperty($job, 'groupId'); return $groupEmail === $group->email && $groupId === $group->id; } ); } /** * Test updating a group */ public function testUpdate(): void { Queue::fake(); $group = $this->getTestGroup('group-test@kolabnow.com'); $group->status |= Group::STATUS_DELETED; $group->save(); Queue::assertPushed(\App\Jobs\Group\UpdateJob::class, 1); Queue::assertPushed( \App\Jobs\Group\UpdateJob::class, function ($job) use ($group) { $groupEmail = TestCase::getObjectProperty($job, 'groupEmail'); $groupId = TestCase::getObjectProperty($job, 'groupId'); return $groupEmail === $group->email && $groupId === $group->id; } ); } /** * Tests for Group::unsuspend() */ public function testUnsuspend(): void { Queue::fake(); $group = $this->getTestGroup('group-test@kolabnow.com'); $group->status = Group::STATUS_SUSPENDED; $group->unsuspend(); $this->assertFalse($group->isSuspended()); Queue::assertPushed(\App\Jobs\Group\UpdateJob::class, 1); Queue::assertPushed( \App\Jobs\Group\UpdateJob::class, function ($job) use ($group) { $groupEmail = TestCase::getObjectProperty($job, 'groupEmail'); $groupId = TestCase::getObjectProperty($job, 'groupId'); return $groupEmail === $group->email && $groupId === $group->id; } ); } } diff --git a/src/tests/Feature/UserTest.php b/src/tests/Feature/UserTest.php index 483c4104..f67bb950 100644 --- a/src/tests/Feature/UserTest.php +++ b/src/tests/Feature/UserTest.php @@ -1,1578 +1,1588 @@ deleteTestUser('user-test@' . \config('app.domain')); $this->deleteTestUser('UserAccountA@UserAccount.com'); $this->deleteTestUser('UserAccountB@UserAccount.com'); $this->deleteTestUser('UserAccountC@UserAccount.com'); $this->deleteTestGroup('test-group@UserAccount.com'); $this->deleteTestResource('test-resource@UserAccount.com'); $this->deleteTestSharedFolder('test-folder@UserAccount.com'); $this->deleteTestDomain('UserAccount.com'); $this->deleteTestDomain('UserAccountAdd.com'); Package::where('title', 'test-package')->delete(); } /** * {@inheritDoc} */ public function tearDown(): void { \App\TenantSetting::truncate(); Package::where('title', 'test-package')->delete(); $this->deleteTestUser('user-test@' . \config('app.domain')); $this->deleteTestUser('UserAccountA@UserAccount.com'); $this->deleteTestUser('UserAccountB@UserAccount.com'); $this->deleteTestUser('UserAccountC@UserAccount.com'); $this->deleteTestGroup('test-group@UserAccount.com'); $this->deleteTestResource('test-resource@UserAccount.com'); $this->deleteTestSharedFolder('test-folder@UserAccount.com'); $this->deleteTestDomain('UserAccount.com'); $this->deleteTestDomain('UserAccountAdd.com'); parent::tearDown(); } /** * Tests for User::assignPackage() */ public function testAssignPackage(): void { $user = $this->getTestUser('user-test@' . \config('app.domain')); $wallet = $user->wallets()->first(); $skuGroupware = Sku::withEnvTenantContext()->where('title', 'groupware')->first(); // cost: 490 $skuMailbox = Sku::withEnvTenantContext()->where('title', 'mailbox')->first(); // cost: 500 $skuStorage = Sku::withEnvTenantContext()->where('title', 'storage')->first(); // cost: 25 $package = Package::create([ 'title' => 'test-package', 'name' => 'Test Account', 'description' => 'Test account.', 'discount_rate' => 0, ]); // WARNING: saveMany() sets package_skus.cost = skus.cost $package->skus()->saveMany([ $skuMailbox, $skuGroupware, $skuStorage ]); $package->skus()->updateExistingPivot($skuStorage, ['qty' => 2, 'cost' => null], false); $package->skus()->updateExistingPivot($skuMailbox, ['cost' => null], false); $package->skus()->updateExistingPivot($skuGroupware, ['cost' => 100], false); $user->assignPackage($package); $this->assertCount(4, $user->entitlements()->get()); // mailbox + groupware + 2 x storage $entitlement = $wallet->entitlements()->where('sku_id', $skuMailbox->id)->first(); $this->assertSame($skuMailbox->id, $entitlement->sku->id); $this->assertSame($wallet->id, $entitlement->wallet->id); $this->assertEquals($user->id, $entitlement->entitleable_id); $this->assertTrue($entitlement->entitleable instanceof \App\User); $this->assertSame($skuMailbox->cost, $entitlement->cost); $entitlement = $wallet->entitlements()->where('sku_id', $skuGroupware->id)->first(); $this->assertSame($skuGroupware->id, $entitlement->sku->id); $this->assertSame($wallet->id, $entitlement->wallet->id); $this->assertEquals($user->id, $entitlement->entitleable_id); $this->assertTrue($entitlement->entitleable instanceof \App\User); $this->assertSame(100, $entitlement->cost); $entitlement = $wallet->entitlements()->where('sku_id', $skuStorage->id)->first(); $this->assertSame($skuStorage->id, $entitlement->sku->id); $this->assertSame($wallet->id, $entitlement->wallet->id); $this->assertEquals($user->id, $entitlement->entitleable_id); $this->assertTrue($entitlement->entitleable instanceof \App\User); $this->assertSame(0, $entitlement->cost); } /** * Tests for User::assignPlan() */ public function testAssignPlan(): void { $this->markTestIncomplete(); } /** * Tests for User::assignSku() */ public function testAssignSku(): void { $user = $this->getTestUser('user-test@' . \config('app.domain')); $wallet = $user->wallets()->first(); $skuStorage = Sku::withEnvTenantContext()->where('title', 'storage')->first(); $skuMailbox = Sku::withEnvTenantContext()->where('title', 'mailbox')->first(); $user->assignSku($skuMailbox); $this->assertCount(1, $user->entitlements()->get()); $entitlement = $wallet->entitlements()->where('sku_id', $skuMailbox->id)->first(); $this->assertSame($skuMailbox->id, $entitlement->sku->id); $this->assertSame($wallet->id, $entitlement->wallet->id); $this->assertEquals($user->id, $entitlement->entitleable_id); $this->assertTrue($entitlement->entitleable instanceof \App\User); $this->assertSame($skuMailbox->cost, $entitlement->cost); // Test units_free handling for ($x = 0; $x < 5; $x++) { $user->assignSku($skuStorage); } $entitlements = $user->entitlements()->where('sku_id', $skuStorage->id) ->where('cost', 0) ->get(); $this->assertCount(5, $entitlements); $user->assignSku($skuStorage); $entitlements = $user->entitlements()->where('sku_id', $skuStorage->id) ->where('cost', $skuStorage->cost) ->get(); $this->assertCount(1, $entitlements); } /** * Verify a wallet assigned a controller is among the accounts of the assignee. */ public function testAccounts(): void { $userA = $this->getTestUser('UserAccountA@UserAccount.com'); $userB = $this->getTestUser('UserAccountB@UserAccount.com'); $this->assertTrue($userA->wallets()->count() == 1); $userA->wallets()->each( function ($wallet) use ($userB) { $wallet->addController($userB); } ); $this->assertTrue($userB->accounts()->get()[0]->id === $userA->wallets()->get()[0]->id); } /** * Test User::canDelete() method */ public function testCanDelete(): void { $john = $this->getTestUser('john@kolab.org'); $ned = $this->getTestUser('ned@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); $reseller1 = $this->getTestUser('reseller@' . \config('app.domain')); $admin = $this->getTestUser('jeroen@jeroen.jeroen'); $domain = $this->getTestDomain('kolab.org'); // Admin $this->assertTrue($admin->canDelete($admin)); $this->assertFalse($admin->canDelete($john)); $this->assertFalse($admin->canDelete($jack)); $this->assertFalse($admin->canDelete($reseller1)); $this->assertFalse($admin->canDelete($domain)); $this->assertFalse($admin->canDelete($domain->wallet())); // Reseller - kolabnow $this->assertFalse($reseller1->canDelete($john)); $this->assertFalse($reseller1->canDelete($jack)); $this->assertTrue($reseller1->canDelete($reseller1)); $this->assertFalse($reseller1->canDelete($domain)); $this->assertFalse($reseller1->canDelete($domain->wallet())); $this->assertFalse($reseller1->canDelete($admin)); // Normal user - account owner $this->assertTrue($john->canDelete($john)); $this->assertTrue($john->canDelete($ned)); $this->assertTrue($john->canDelete($jack)); $this->assertTrue($john->canDelete($domain)); $this->assertFalse($john->canDelete($domain->wallet())); $this->assertFalse($john->canDelete($reseller1)); $this->assertFalse($john->canDelete($admin)); // Normal user - a non-owner and non-controller $this->assertFalse($jack->canDelete($jack)); $this->assertFalse($jack->canDelete($john)); $this->assertFalse($jack->canDelete($domain)); $this->assertFalse($jack->canDelete($domain->wallet())); $this->assertFalse($jack->canDelete($reseller1)); $this->assertFalse($jack->canDelete($admin)); // Normal user - John's wallet controller $this->assertTrue($ned->canDelete($ned)); $this->assertTrue($ned->canDelete($john)); $this->assertTrue($ned->canDelete($jack)); $this->assertTrue($ned->canDelete($domain)); $this->assertFalse($ned->canDelete($domain->wallet())); $this->assertFalse($ned->canDelete($reseller1)); $this->assertFalse($ned->canDelete($admin)); } /** * Test User::canRead() method */ public function testCanRead(): void { $john = $this->getTestUser('john@kolab.org'); $ned = $this->getTestUser('ned@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); $reseller1 = $this->getTestUser('reseller@' . \config('app.domain')); $reseller2 = $this->getTestUser('reseller@sample-tenant.dev-local'); $admin = $this->getTestUser('jeroen@jeroen.jeroen'); $domain = $this->getTestDomain('kolab.org'); // Admin $this->assertTrue($admin->canRead($admin)); $this->assertTrue($admin->canRead($john)); $this->assertTrue($admin->canRead($jack)); $this->assertTrue($admin->canRead($reseller1)); $this->assertTrue($admin->canRead($reseller2)); $this->assertTrue($admin->canRead($domain)); $this->assertTrue($admin->canRead($domain->wallet())); // Reseller - kolabnow $this->assertTrue($reseller1->canRead($john)); $this->assertTrue($reseller1->canRead($jack)); $this->assertTrue($reseller1->canRead($reseller1)); $this->assertTrue($reseller1->canRead($domain)); $this->assertTrue($reseller1->canRead($domain->wallet())); $this->assertFalse($reseller1->canRead($reseller2)); $this->assertFalse($reseller1->canRead($admin)); // Reseller - different tenant $this->assertTrue($reseller2->canRead($reseller2)); $this->assertFalse($reseller2->canRead($john)); $this->assertFalse($reseller2->canRead($jack)); $this->assertFalse($reseller2->canRead($reseller1)); $this->assertFalse($reseller2->canRead($domain)); $this->assertFalse($reseller2->canRead($domain->wallet())); $this->assertFalse($reseller2->canRead($admin)); // Normal user - account owner $this->assertTrue($john->canRead($john)); $this->assertTrue($john->canRead($ned)); $this->assertTrue($john->canRead($jack)); $this->assertTrue($john->canRead($domain)); $this->assertTrue($john->canRead($domain->wallet())); $this->assertFalse($john->canRead($reseller1)); $this->assertFalse($john->canRead($reseller2)); $this->assertFalse($john->canRead($admin)); // Normal user - a non-owner and non-controller $this->assertTrue($jack->canRead($jack)); $this->assertFalse($jack->canRead($john)); $this->assertFalse($jack->canRead($domain)); $this->assertFalse($jack->canRead($domain->wallet())); $this->assertFalse($jack->canRead($reseller1)); $this->assertFalse($jack->canRead($reseller2)); $this->assertFalse($jack->canRead($admin)); // Normal user - John's wallet controller $this->assertTrue($ned->canRead($ned)); $this->assertTrue($ned->canRead($john)); $this->assertTrue($ned->canRead($jack)); $this->assertTrue($ned->canRead($domain)); $this->assertTrue($ned->canRead($domain->wallet())); $this->assertFalse($ned->canRead($reseller1)); $this->assertFalse($ned->canRead($reseller2)); $this->assertFalse($ned->canRead($admin)); } /** * Test User::canUpdate() method */ public function testCanUpdate(): void { $john = $this->getTestUser('john@kolab.org'); $ned = $this->getTestUser('ned@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); $reseller1 = $this->getTestUser('reseller@' . \config('app.domain')); $reseller2 = $this->getTestUser('reseller@sample-tenant.dev-local'); $admin = $this->getTestUser('jeroen@jeroen.jeroen'); $domain = $this->getTestDomain('kolab.org'); // Admin $this->assertTrue($admin->canUpdate($admin)); $this->assertTrue($admin->canUpdate($john)); $this->assertTrue($admin->canUpdate($jack)); $this->assertTrue($admin->canUpdate($reseller1)); $this->assertTrue($admin->canUpdate($reseller2)); $this->assertTrue($admin->canUpdate($domain)); $this->assertTrue($admin->canUpdate($domain->wallet())); // Reseller - kolabnow $this->assertTrue($reseller1->canUpdate($john)); $this->assertTrue($reseller1->canUpdate($jack)); $this->assertTrue($reseller1->canUpdate($reseller1)); $this->assertTrue($reseller1->canUpdate($domain)); $this->assertTrue($reseller1->canUpdate($domain->wallet())); $this->assertFalse($reseller1->canUpdate($reseller2)); $this->assertFalse($reseller1->canUpdate($admin)); // Reseller - different tenant $this->assertTrue($reseller2->canUpdate($reseller2)); $this->assertFalse($reseller2->canUpdate($john)); $this->assertFalse($reseller2->canUpdate($jack)); $this->assertFalse($reseller2->canUpdate($reseller1)); $this->assertFalse($reseller2->canUpdate($domain)); $this->assertFalse($reseller2->canUpdate($domain->wallet())); $this->assertFalse($reseller2->canUpdate($admin)); // Normal user - account owner $this->assertTrue($john->canUpdate($john)); $this->assertTrue($john->canUpdate($ned)); $this->assertTrue($john->canUpdate($jack)); $this->assertTrue($john->canUpdate($domain)); $this->assertFalse($john->canUpdate($domain->wallet())); $this->assertFalse($john->canUpdate($reseller1)); $this->assertFalse($john->canUpdate($reseller2)); $this->assertFalse($john->canUpdate($admin)); // Normal user - a non-owner and non-controller $this->assertTrue($jack->canUpdate($jack)); $this->assertFalse($jack->canUpdate($john)); $this->assertFalse($jack->canUpdate($domain)); $this->assertFalse($jack->canUpdate($domain->wallet())); $this->assertFalse($jack->canUpdate($reseller1)); $this->assertFalse($jack->canUpdate($reseller2)); $this->assertFalse($jack->canUpdate($admin)); // Normal user - John's wallet controller $this->assertTrue($ned->canUpdate($ned)); $this->assertTrue($ned->canUpdate($john)); $this->assertTrue($ned->canUpdate($jack)); $this->assertTrue($ned->canUpdate($domain)); $this->assertFalse($ned->canUpdate($domain->wallet())); $this->assertFalse($ned->canUpdate($reseller1)); $this->assertFalse($ned->canUpdate($reseller2)); $this->assertFalse($ned->canUpdate($admin)); } /** * Test user created/creating/updated observers */ public function testCreateAndUpdate(): void { Queue::fake(); $domain = \config('app.domain'); \App\Tenant::find(\config('app.tenant_id'))->setSetting('pgp.enable', 0); $user = User::create([ 'email' => 'USER-test@' . \strtoupper($domain), 'password' => 'test', ]); $result = User::where('email', "user-test@$domain")->first(); $this->assertSame("user-test@$domain", $result->email); $this->assertSame($user->id, $result->id); $this->assertSame(User::STATUS_NEW, $result->status); $this->assertSame(0, $user->passwords()->count()); Queue::assertPushed(\App\Jobs\User\CreateJob::class, 1); Queue::assertPushed(\App\Jobs\PGP\KeyCreateJob::class, 0); Queue::assertPushed( \App\Jobs\User\CreateJob::class, function ($job) use ($user) { $userEmail = TestCase::getObjectProperty($job, 'userEmail'); $userId = TestCase::getObjectProperty($job, 'userId'); return $userEmail === $user->email && $userId === $user->id; } ); // Test invoking KeyCreateJob $this->deleteTestUser("user-test@$domain"); \App\Tenant::find(\config('app.tenant_id'))->setSetting('pgp.enable', 1); $user = User::create(['email' => "user-test@$domain", 'password' => 'test']); Queue::assertPushed(\App\Jobs\PGP\KeyCreateJob::class, 1); Queue::assertPushed( \App\Jobs\PGP\KeyCreateJob::class, function ($job) use ($user) { $userEmail = TestCase::getObjectProperty($job, 'userEmail'); $userId = TestCase::getObjectProperty($job, 'userId'); return $userEmail === $user->email && $userId === $user->id; } ); // Update the user, test the password change $user->setSetting('password_expiration_warning', '2020-10-10 10:10:10'); $oldPassword = $user->password; $user->password = 'test123'; $user->save(); $this->assertNotEquals($oldPassword, $user->password); $this->assertSame(0, $user->passwords()->count()); $this->assertNull($user->getSetting('password_expiration_warning')); $this->assertMatchesRegularExpression( '/^' . now()->format('Y-m-d') . ' [0-9]{2}:[0-9]{2}:[0-9]{2}$/', $user->getSetting('password_update') ); Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 1); Queue::assertPushed( \App\Jobs\User\UpdateJob::class, function ($job) use ($user) { $userEmail = TestCase::getObjectProperty($job, 'userEmail'); $userId = TestCase::getObjectProperty($job, 'userId'); return $userEmail === $user->email && $userId === $user->id; } ); // Update the user, test the password history $user->setSetting('password_policy', 'last:3'); $oldPassword = $user->password; $user->password = 'test1234'; $user->save(); $this->assertSame(1, $user->passwords()->count()); $this->assertSame($oldPassword, $user->passwords()->first()->password); $user->password = 'test12345'; $user->save(); $oldPassword = $user->password; $user->password = 'test123456'; $user->save(); $this->assertSame(2, $user->passwords()->count()); $this->assertSame($oldPassword, $user->passwords()->latest()->first()->password); } /** * Tests for User::domains() */ public function testDomains(): void { $user = $this->getTestUser('john@kolab.org'); $domain = $this->getTestDomain('useraccount.com', [ 'status' => Domain::STATUS_NEW | Domain::STATUS_ACTIVE, 'type' => Domain::TYPE_PUBLIC, ]); $domains = $user->domains()->pluck('namespace')->all(); $this->assertContains($domain->namespace, $domains); $this->assertContains('kolab.org', $domains); // Jack is not the wallet controller, so for him the list should not // include John's domains, kolab.org specifically $user = $this->getTestUser('jack@kolab.org'); $domains = $user->domains()->pluck('namespace')->all(); $this->assertContains($domain->namespace, $domains); $this->assertNotContains('kolab.org', $domains); // Public domains of other tenants should not be returned $tenant = \App\Tenant::where('id', '!=', \config('app.tenant_id'))->first(); $domain->tenant_id = $tenant->id; $domain->save(); $domains = $user->domains()->pluck('namespace')->all(); $this->assertNotContains($domain->namespace, $domains); } /** * Test User::getConfig() and setConfig() methods */ public function testConfigTrait(): void { $user = $this->getTestUser('UserAccountA@UserAccount.com'); $user->setSetting('greylist_enabled', null); $user->setSetting('guam_enabled', null); $user->setSetting('password_policy', null); $user->setSetting('max_password_age', null); $user->setSetting('limit_geo', null); // greylist_enabled $this->assertSame(true, $user->getConfig()['greylist_enabled']); $result = $user->setConfig(['greylist_enabled' => false, 'unknown' => false]); $this->assertSame(['unknown' => "The requested configuration parameter is not supported."], $result); $this->assertSame(false, $user->getConfig()['greylist_enabled']); $this->assertSame('false', $user->getSetting('greylist_enabled')); $result = $user->setConfig(['greylist_enabled' => true]); $this->assertSame([], $result); $this->assertSame(true, $user->getConfig()['greylist_enabled']); $this->assertSame('true', $user->getSetting('greylist_enabled')); // guam_enabled $this->assertSame(false, $user->getConfig()['guam_enabled']); $result = $user->setConfig(['guam_enabled' => false]); $this->assertSame([], $result); $this->assertSame(false, $user->getConfig()['guam_enabled']); $this->assertSame(null, $user->getSetting('guam_enabled')); $result = $user->setConfig(['guam_enabled' => true]); $this->assertSame([], $result); $this->assertSame(true, $user->getConfig()['guam_enabled']); $this->assertSame('true', $user->getSetting('guam_enabled')); // max_apssword_age $this->assertSame(null, $user->getConfig()['max_password_age']); $result = $user->setConfig(['max_password_age' => -1]); $this->assertSame([], $result); $this->assertSame(null, $user->getConfig()['max_password_age']); $this->assertSame(null, $user->getSetting('max_password_age')); $result = $user->setConfig(['max_password_age' => 12]); $this->assertSame([], $result); $this->assertSame('12', $user->getConfig()['max_password_age']); $this->assertSame('12', $user->getSetting('max_password_age')); // password_policy $result = $user->setConfig(['password_policy' => true]); $this->assertSame(['password_policy' => "Specified password policy is invalid."], $result); $this->assertSame(null, $user->getConfig()['password_policy']); $this->assertSame(null, $user->getSetting('password_policy')); $result = $user->setConfig(['password_policy' => 'min:-1']); $this->assertSame(['password_policy' => "Specified password policy is invalid."], $result); $result = $user->setConfig(['password_policy' => 'min:-1']); $this->assertSame(['password_policy' => "Specified password policy is invalid."], $result); $result = $user->setConfig(['password_policy' => 'min:10,unknown']); $this->assertSame(['password_policy' => "Specified password policy is invalid."], $result); \config(['app.password_policy' => 'min:5,max:100']); $result = $user->setConfig(['password_policy' => 'min:4,max:255']); $this->assertSame(['password_policy' => "Minimum password length cannot be less than 5."], $result); \config(['app.password_policy' => 'min:5,max:100']); $result = $user->setConfig(['password_policy' => 'min:10,max:255']); $this->assertSame(['password_policy' => "Maximum password length cannot be more than 100."], $result); \config(['app.password_policy' => 'min:5,max:255']); $result = $user->setConfig(['password_policy' => 'min:10,max:255']); $this->assertSame([], $result); $this->assertSame('min:10,max:255', $user->getConfig()['password_policy']); $this->assertSame('min:10,max:255', $user->getSetting('password_policy')); // limit_geo $this->assertSame([], $user->getConfig()['limit_geo']); $result = $user->setConfig(['limit_geo' => '']); $err = "Specified configuration is invalid. Expected a list of two-letter country codes."; $this->assertSame(['limit_geo' => $err], $result); $this->assertSame(null, $user->getSetting('limit_geo')); $result = $user->setConfig(['limit_geo' => ['usa']]); $this->assertSame(['limit_geo' => $err], $result); $this->assertSame(null, $user->getSetting('limit_geo')); $result = $user->setConfig(['limit_geo' => []]); $this->assertSame([], $result); $this->assertSame(null, $user->getSetting('limit_geo')); $result = $user->setConfig(['limit_geo' => ['US', 'ru']]); $this->assertSame([], $result); $this->assertSame(['US', 'RU'], $user->getConfig()['limit_geo']); $this->assertSame('["US","RU"]', $user->getSetting('limit_geo')); } /** * Test user account degradation and un-degradation */ public function testDegradeAndUndegrade(): void { Queue::fake(); // Test an account with users, domain $userA = $this->getTestUser('UserAccountA@UserAccount.com'); $userB = $this->getTestUser('UserAccountB@UserAccount.com'); $package_kolab = \App\Package::withEnvTenantContext()->where('title', 'kolab')->first(); $package_domain = \App\Package::withEnvTenantContext()->where('title', 'domain-hosting')->first(); $domain = $this->getTestDomain('UserAccount.com', [ 'status' => Domain::STATUS_NEW, 'type' => Domain::TYPE_HOSTED, ]); $userA->assignPackage($package_kolab); $domain->assignPackage($package_domain, $userA); $userA->assignPackage($package_kolab, $userB); $entitlementsA = \App\Entitlement::where('entitleable_id', $userA->id); $entitlementsB = \App\Entitlement::where('entitleable_id', $userB->id); $entitlementsDomain = \App\Entitlement::where('entitleable_id', $domain->id); $yesterday = Carbon::now()->subDays(1); $this->backdateEntitlements($entitlementsA->get(), $yesterday, Carbon::now()->subMonthsWithoutOverflow(1)); $this->backdateEntitlements($entitlementsB->get(), $yesterday, Carbon::now()->subMonthsWithoutOverflow(1)); $wallet = $userA->wallets->first(); $this->assertSame(7, $entitlementsA->count()); $this->assertSame(7, $entitlementsB->count()); $this->assertSame(7, $entitlementsA->whereDate('updated_at', $yesterday->toDateString())->count()); $this->assertSame(7, $entitlementsB->whereDate('updated_at', $yesterday->toDateString())->count()); $this->assertSame(0, $wallet->balance); Queue::fake(); // reset queue state // Degrade the account/wallet owner $userA->degrade(); $entitlementsA = \App\Entitlement::where('entitleable_id', $userA->id); $entitlementsB = \App\Entitlement::where('entitleable_id', $userB->id); $this->assertTrue($userA->fresh()->isDegraded()); $this->assertTrue($userA->fresh()->isDegraded(true)); $this->assertFalse($userB->fresh()->isDegraded()); $this->assertTrue($userB->fresh()->isDegraded(true)); $balance = $wallet->fresh()->balance; $this->assertTrue($balance < 0); $this->assertSame(7, $entitlementsA->whereDate('updated_at', Carbon::now()->toDateString())->count()); $this->assertSame(7, $entitlementsB->whereDate('updated_at', Carbon::now()->toDateString())->count()); // Expect one update job for every user // @phpstan-ignore-next-line $userIds = Queue::pushed(\App\Jobs\User\UpdateJob::class)->map(function ($job) { return TestCase::getObjectProperty($job, 'userId'); })->all(); $this->assertSame([$userA->id, $userB->id], $userIds); // Un-Degrade the account/wallet owner $entitlementsA = \App\Entitlement::where('entitleable_id', $userA->id); $entitlementsB = \App\Entitlement::where('entitleable_id', $userB->id); $yesterday = Carbon::now()->subDays(1); $this->backdateEntitlements($entitlementsA->get(), $yesterday, Carbon::now()->subMonthsWithoutOverflow(1)); $this->backdateEntitlements($entitlementsB->get(), $yesterday, Carbon::now()->subMonthsWithoutOverflow(1)); Queue::fake(); // reset queue state $userA->undegrade(); $this->assertFalse($userA->fresh()->isDegraded()); $this->assertFalse($userA->fresh()->isDegraded(true)); $this->assertFalse($userB->fresh()->isDegraded()); $this->assertFalse($userB->fresh()->isDegraded(true)); // Expect no balance change, degraded account entitlements are free $this->assertSame($balance, $wallet->fresh()->balance); $this->assertSame(7, $entitlementsA->whereDate('updated_at', Carbon::now()->toDateString())->count()); $this->assertSame(7, $entitlementsB->whereDate('updated_at', Carbon::now()->toDateString())->count()); // Expect one update job for every user // @phpstan-ignore-next-line $userIds = Queue::pushed(\App\Jobs\User\UpdateJob::class)->map(function ($job) { return TestCase::getObjectProperty($job, 'userId'); })->all(); $this->assertSame([$userA->id, $userB->id], $userIds); } /** * Test user deletion */ public function testDelete(): void { Queue::fake(); $user = $this->getTestUser('user-test@' . \config('app.domain')); $package = \App\Package::withEnvTenantContext()->where('title', 'kolab')->first(); $user->assignPackage($package); $id = $user->id; $this->assertCount(7, $user->entitlements()->get()); $user->delete(); $this->assertCount(0, $user->entitlements()->get()); $this->assertTrue($user->fresh()->trashed()); $this->assertFalse($user->fresh()->isDeleted()); // Delete the user for real $job = new \App\Jobs\User\DeleteJob($id); $job->handle(); $this->assertTrue(User::withTrashed()->where('id', $id)->first()->isDeleted()); $user->forceDelete(); $this->assertCount(0, User::withTrashed()->where('id', $id)->get()); // Test an account with users, domain, and group, and resource $userA = $this->getTestUser('UserAccountA@UserAccount.com'); $userB = $this->getTestUser('UserAccountB@UserAccount.com'); $userC = $this->getTestUser('UserAccountC@UserAccount.com'); $package_kolab = \App\Package::withEnvTenantContext()->where('title', 'kolab')->first(); $package_domain = \App\Package::withEnvTenantContext()->where('title', 'domain-hosting')->first(); $domain = $this->getTestDomain('UserAccount.com', [ 'status' => Domain::STATUS_NEW, 'type' => Domain::TYPE_HOSTED, ]); $userA->assignPackage($package_kolab); $domain->assignPackage($package_domain, $userA); $userA->assignPackage($package_kolab, $userB); $userA->assignPackage($package_kolab, $userC); $group = $this->getTestGroup('test-group@UserAccount.com'); $group->assignToWallet($userA->wallets->first()); $resource = $this->getTestResource('test-resource@UserAccount.com', ['name' => 'test']); $resource->assignToWallet($userA->wallets->first()); $folder = $this->getTestSharedFolder('test-folder@UserAccount.com', ['name' => 'test']); $folder->assignToWallet($userA->wallets->first()); $entitlementsA = \App\Entitlement::where('entitleable_id', $userA->id); $entitlementsB = \App\Entitlement::where('entitleable_id', $userB->id); $entitlementsC = \App\Entitlement::where('entitleable_id', $userC->id); $entitlementsDomain = \App\Entitlement::where('entitleable_id', $domain->id); $entitlementsGroup = \App\Entitlement::where('entitleable_id', $group->id); $entitlementsResource = \App\Entitlement::where('entitleable_id', $resource->id); $entitlementsFolder = \App\Entitlement::where('entitleable_id', $folder->id); $this->assertSame(7, $entitlementsA->count()); $this->assertSame(7, $entitlementsB->count()); $this->assertSame(7, $entitlementsC->count()); $this->assertSame(1, $entitlementsDomain->count()); $this->assertSame(1, $entitlementsGroup->count()); $this->assertSame(1, $entitlementsResource->count()); $this->assertSame(1, $entitlementsFolder->count()); // Delete non-controller user $userC->delete(); $this->assertTrue($userC->fresh()->trashed()); $this->assertFalse($userC->fresh()->isDeleted()); $this->assertSame(0, $entitlementsC->count()); // Delete the controller (and expect "sub"-users to be deleted too) $userA->delete(); $this->assertSame(0, $entitlementsA->count()); $this->assertSame(0, $entitlementsB->count()); $this->assertSame(0, $entitlementsDomain->count()); $this->assertSame(0, $entitlementsGroup->count()); $this->assertSame(0, $entitlementsResource->count()); $this->assertSame(0, $entitlementsFolder->count()); $this->assertSame(7, $entitlementsA->withTrashed()->count()); $this->assertSame(7, $entitlementsB->withTrashed()->count()); $this->assertSame(7, $entitlementsC->withTrashed()->count()); $this->assertSame(1, $entitlementsDomain->withTrashed()->count()); $this->assertSame(1, $entitlementsGroup->withTrashed()->count()); $this->assertSame(1, $entitlementsResource->withTrashed()->count()); $this->assertSame(1, $entitlementsFolder->withTrashed()->count()); $this->assertTrue($userA->fresh()->trashed()); $this->assertTrue($userB->fresh()->trashed()); $this->assertTrue($domain->fresh()->trashed()); $this->assertTrue($group->fresh()->trashed()); $this->assertTrue($resource->fresh()->trashed()); $this->assertTrue($folder->fresh()->trashed()); $this->assertFalse($userA->isDeleted()); $this->assertFalse($userB->isDeleted()); $this->assertFalse($domain->isDeleted()); $this->assertFalse($group->isDeleted()); $this->assertFalse($resource->isDeleted()); $this->assertFalse($folder->isDeleted()); $userA->forceDelete(); $all_entitlements = \App\Entitlement::where('wallet_id', $userA->wallets->first()->id); $transactions = \App\Transaction::where('object_id', $userA->wallets->first()->id); $this->assertSame(0, $all_entitlements->withTrashed()->count()); $this->assertSame(0, $transactions->count()); $this->assertCount(0, User::withTrashed()->where('id', $userA->id)->get()); $this->assertCount(0, User::withTrashed()->where('id', $userB->id)->get()); $this->assertCount(0, User::withTrashed()->where('id', $userC->id)->get()); $this->assertCount(0, Domain::withTrashed()->where('id', $domain->id)->get()); $this->assertCount(0, Group::withTrashed()->where('id', $group->id)->get()); $this->assertCount(0, \App\Resource::withTrashed()->where('id', $resource->id)->get()); $this->assertCount(0, \App\SharedFolder::withTrashed()->where('id', $folder->id)->get()); } /** * Test eventlog on user deletion */ public function testDeleteAndEventLog(): void { Queue::fake(); $user = $this->getTestUser('user-test@' . \config('app.domain')); EventLog::createFor($user, EventLog::TYPE_SUSPENDED, 'test'); $user->delete(); $this->assertCount(1, EventLog::where('object_id', $user->id)->where('object_type', User::class)->get()); $user->forceDelete(); $this->assertCount(0, EventLog::where('object_id', $user->id)->where('object_type', User::class)->get()); } /** * Test user deletion vs. group membership */ public function testDeleteAndGroups(): void { Queue::fake(); $package_kolab = \App\Package::withEnvTenantContext()->where('title', 'kolab')->first(); $userA = $this->getTestUser('UserAccountA@UserAccount.com'); $userB = $this->getTestUser('UserAccountB@UserAccount.com'); $userA->assignPackage($package_kolab, $userB); $group = $this->getTestGroup('test-group@UserAccount.com'); $group->members = ['test@gmail.com', $userB->email]; $group->assignToWallet($userA->wallets->first()); $group->save(); Queue::assertPushed(\App\Jobs\Group\UpdateJob::class, 1); $userGroups = $userA->groups()->get(); $this->assertSame(1, $userGroups->count()); $this->assertSame($group->id, $userGroups->first()->id); $userB->delete(); $this->assertSame(['test@gmail.com'], $group->fresh()->members); // Twice, one for save() and one for delete() above Queue::assertPushed(\App\Jobs\Group\UpdateJob::class, 2); } /** * Test handling negative balance on user deletion */ public function testDeleteWithNegativeBalance(): void { $user = $this->getTestUser('user-test@' . \config('app.domain')); $wallet = $user->wallets()->first(); $wallet->balance = -1000; $wallet->save(); $reseller_wallet = $user->tenant->wallet(); $reseller_wallet->balance = 0; $reseller_wallet->save(); \App\Transaction::where('object_id', $reseller_wallet->id)->where('object_type', \App\Wallet::class)->delete(); $user->delete(); $reseller_transactions = \App\Transaction::where('object_id', $reseller_wallet->id) ->where('object_type', \App\Wallet::class)->get(); $this->assertSame(-1000, $reseller_wallet->fresh()->balance); $this->assertCount(1, $reseller_transactions); $trans = $reseller_transactions[0]; $this->assertSame("Deleted user {$user->email}", $trans->description); $this->assertSame(-1000, $trans->amount); $this->assertSame(\App\Transaction::WALLET_DEBIT, $trans->type); } /** * Test handling positive balance on user deletion */ public function testDeleteWithPositiveBalance(): void { $user = $this->getTestUser('user-test@' . \config('app.domain')); $wallet = $user->wallets()->first(); $wallet->balance = 1000; $wallet->save(); $reseller_wallet = $user->tenant->wallet(); $reseller_wallet->balance = 0; $reseller_wallet->save(); $user->delete(); $this->assertSame(0, $reseller_wallet->fresh()->balance); } /** * Test user deletion with PGP/WOAT enabled */ public function testDeleteWithPGP(): void { Queue::fake(); // Test with PGP disabled $user = $this->getTestUser('user-test@' . \config('app.domain')); $user->tenant->setSetting('pgp.enable', 0); $user->delete(); Queue::assertPushed(\App\Jobs\PGP\KeyDeleteJob::class, 0); // Test with PGP enabled $this->deleteTestUser('user-test@' . \config('app.domain')); $user = $this->getTestUser('user-test@' . \config('app.domain')); $user->tenant->setSetting('pgp.enable', 1); $user->delete(); $user->tenant->setSetting('pgp.enable', 0); Queue::assertPushed(\App\Jobs\PGP\KeyDeleteJob::class, 1); Queue::assertPushed( \App\Jobs\PGP\KeyDeleteJob::class, function ($job) use ($user) { $userId = TestCase::getObjectProperty($job, 'userId'); $userEmail = TestCase::getObjectProperty($job, 'userEmail'); return $userId == $user->id && $userEmail === $user->email; } ); } /** * Test user deletion vs. rooms */ public function testDeleteWithRooms(): void { $this->markTestIncomplete(); } /** * Tests for User::aliasExists() */ public function testAliasExists(): void { $this->assertTrue(User::aliasExists('jack.daniels@kolab.org')); $this->assertFalse(User::aliasExists('j.daniels@kolab.org')); $this->assertFalse(User::aliasExists('john@kolab.org')); } /** * Tests for User::emailExists() */ public function testEmailExists(): void { $this->assertFalse(User::emailExists('jack.daniels@kolab.org')); $this->assertFalse(User::emailExists('j.daniels@kolab.org')); $this->assertTrue(User::emailExists('john@kolab.org')); $user = User::emailExists('john@kolab.org', true); $this->assertSame('john@kolab.org', $user->email); } /** * Tests for User::findByEmail() */ public function testFindByEmail(): void { $user = $this->getTestUser('john@kolab.org'); $result = User::findByEmail('john'); $this->assertNull($result); $result = User::findByEmail('non-existing@email.com'); $this->assertNull($result); $result = User::findByEmail('john@kolab.org'); $this->assertInstanceOf(User::class, $result); $this->assertSame($user->id, $result->id); // Use an alias $result = User::findByEmail('john.doe@kolab.org'); $this->assertInstanceOf(User::class, $result); $this->assertSame($user->id, $result->id); Queue::fake(); // A case where two users have the same alias $ned = $this->getTestUser('ned@kolab.org'); $ned->setAliases(['joe.monster@kolab.org']); $result = User::findByEmail('joe.monster@kolab.org'); $this->assertNull($result); $ned->setAliases([]); // TODO: searching by external email (setting) $this->markTestIncomplete(); } /** * Test User::hasSku() and countEntitlementsBySku() methods */ public function testHasSku(): void { $john = $this->getTestUser('john@kolab.org'); $this->assertTrue($john->hasSku('mailbox')); $this->assertTrue($john->hasSku('storage')); $this->assertFalse($john->hasSku('beta')); $this->assertFalse($john->hasSku('unknown')); $this->assertSame(0, $john->countEntitlementsBySku('unknown')); $this->assertSame(0, $john->countEntitlementsBySku('2fa')); $this->assertSame(1, $john->countEntitlementsBySku('mailbox')); $this->assertSame(5, $john->countEntitlementsBySku('storage')); } /** * Test User::name() */ public function testName(): void { Queue::fake(); $user = $this->getTestUser('user-test@' . \config('app.domain')); $this->assertSame('', $user->name()); $this->assertSame($user->tenant->title . ' User', $user->name(true)); $user->setSetting('first_name', 'First'); $this->assertSame('First', $user->name()); $this->assertSame('First', $user->name(true)); $user->setSetting('last_name', 'Last'); $this->assertSame('First Last', $user->name()); $this->assertSame('First Last', $user->name(true)); } /** * Test resources() method */ public function testResources(): void { $john = $this->getTestUser('john@kolab.org'); $ned = $this->getTestUser('ned@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); $resources = $john->resources()->orderBy('email')->get(); $this->assertSame(2, $resources->count()); $this->assertSame('resource-test1@kolab.org', $resources[0]->email); $this->assertSame('resource-test2@kolab.org', $resources[1]->email); $resources = $ned->resources()->orderBy('email')->get(); $this->assertSame(2, $resources->count()); $this->assertSame('resource-test1@kolab.org', $resources[0]->email); $this->assertSame('resource-test2@kolab.org', $resources[1]->email); $resources = $jack->resources()->get(); $this->assertSame(0, $resources->count()); } /** * Test sharedFolders() method */ public function testSharedFolders(): void { $john = $this->getTestUser('john@kolab.org'); $ned = $this->getTestUser('ned@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); $folders = $john->sharedFolders()->orderBy('email')->get(); $this->assertSame(2, $folders->count()); $this->assertSame('folder-contact@kolab.org', $folders[0]->email); $this->assertSame('folder-event@kolab.org', $folders[1]->email); $folders = $ned->sharedFolders()->orderBy('email')->get(); $this->assertSame(2, $folders->count()); $this->assertSame('folder-contact@kolab.org', $folders[0]->email); $this->assertSame('folder-event@kolab.org', $folders[1]->email); $folders = $jack->sharedFolders()->get(); $this->assertSame(0, $folders->count()); } /** * Test user restoring */ public function testRestore(): void { Queue::fake(); // Test an account with users and domain $userA = $this->getTestUser('UserAccountA@UserAccount.com', [ 'status' => User::STATUS_LDAP_READY | User::STATUS_IMAP_READY | User::STATUS_SUSPENDED, ]); $userB = $this->getTestUser('UserAccountB@UserAccount.com'); $package_kolab = \App\Package::withEnvTenantContext()->where('title', 'kolab')->first(); $package_domain = \App\Package::withEnvTenantContext()->where('title', 'domain-hosting')->first(); $domainA = $this->getTestDomain('UserAccount.com', [ 'status' => Domain::STATUS_NEW, 'type' => Domain::TYPE_HOSTED, ]); $domainB = $this->getTestDomain('UserAccountAdd.com', [ 'status' => Domain::STATUS_NEW, 'type' => Domain::TYPE_HOSTED, ]); $userA->assignPackage($package_kolab); $domainA->assignPackage($package_domain, $userA); $domainB->assignPackage($package_domain, $userA); $userA->assignPackage($package_kolab, $userB); $storage_sku = \App\Sku::withEnvTenantContext()->where('title', 'storage')->first(); $now = \Carbon\Carbon::now(); $wallet_id = $userA->wallets->first()->id; // add an extra storage entitlement $ent1 = \App\Entitlement::create([ 'wallet_id' => $wallet_id, 'sku_id' => $storage_sku->id, 'cost' => 0, 'entitleable_id' => $userA->id, 'entitleable_type' => User::class, ]); $entitlementsA = \App\Entitlement::where('entitleable_id', $userA->id); $entitlementsB = \App\Entitlement::where('entitleable_id', $userB->id); $entitlementsDomain = \App\Entitlement::where('entitleable_id', $domainA->id); // First delete the user $userA->delete(); $this->assertSame(0, $entitlementsA->count()); $this->assertSame(0, $entitlementsB->count()); $this->assertSame(0, $entitlementsDomain->count()); $this->assertTrue($userA->fresh()->trashed()); $this->assertTrue($userB->fresh()->trashed()); $this->assertTrue($domainA->fresh()->trashed()); $this->assertTrue($domainB->fresh()->trashed()); $this->assertFalse($userA->isDeleted()); $this->assertFalse($userB->isDeleted()); $this->assertFalse($domainA->isDeleted()); // Backdate one storage entitlement (it's not expected to be restored) \App\Entitlement::withTrashed()->where('id', $ent1->id) ->update(['deleted_at' => $now->copy()->subMinutes(2)]); // Backdate entitlements to assert that they were restored with proper updated_at timestamp \App\Entitlement::withTrashed()->where('wallet_id', $wallet_id) ->update(['updated_at' => $now->subMinutes(10)]); Queue::fake(); // Then restore it $userA->restore(); $userA->refresh(); $this->assertFalse($userA->trashed()); $this->assertFalse($userA->isDeleted()); $this->assertFalse($userA->isSuspended()); $this->assertFalse($userA->isLdapReady()); $this->assertFalse($userA->isImapReady()); $this->assertFalse($userA->isActive()); $this->assertTrue($userA->isNew()); $this->assertTrue($userB->fresh()->trashed()); $this->assertTrue($domainB->fresh()->trashed()); $this->assertFalse($domainA->fresh()->trashed()); // Assert entitlements $this->assertSame(7, $entitlementsA->count()); // mailbox + groupware + 5 x storage $this->assertTrue($ent1->fresh()->trashed()); $entitlementsA->get()->each(function ($ent) { $this->assertTrue($ent->updated_at->greaterThan(\Carbon\Carbon::now()->subSeconds(5))); }); // We expect only CreateJob + UpdateJob pair for both user and domain. // Because how Illuminate/Database/Eloquent/SoftDeletes::restore() method // is implemented we cannot skip the UpdateJob in any way. // I don't want to overwrite this method, the extra job shouldn't do any harm. $this->assertCount(4, Queue::pushedJobs()); // @phpstan-ignore-line Queue::assertPushed(\App\Jobs\Domain\UpdateJob::class, 1); Queue::assertPushed(\App\Jobs\Domain\CreateJob::class, 1); Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 1); Queue::assertPushed(\App\Jobs\User\CreateJob::class, 1); Queue::assertPushed( \App\Jobs\User\CreateJob::class, function ($job) use ($userA) { return $userA->id === TestCase::getObjectProperty($job, 'userId'); } ); } /** * Test user account restrict() and unrestrict() */ public function testRestrictAndUnrestrict(): void { Queue::fake(); // Test an account with users, domain $user = $this->getTestUser('UserAccountA@UserAccount.com'); $userB = $this->getTestUser('UserAccountB@UserAccount.com'); $package_kolab = \App\Package::withEnvTenantContext()->where('title', 'kolab')->first(); $package_domain = \App\Package::withEnvTenantContext()->where('title', 'domain-hosting')->first(); $domain = $this->getTestDomain('UserAccount.com', [ 'status' => Domain::STATUS_NEW, 'type' => Domain::TYPE_HOSTED, ]); $user->assignPackage($package_kolab); $domain->assignPackage($package_domain, $user); $user->assignPackage($package_kolab, $userB); $this->assertFalse($user->isRestricted()); $this->assertFalse($userB->isRestricted()); $user->restrict(); $this->assertTrue($user->fresh()->isRestricted()); $this->assertFalse($userB->fresh()->isRestricted()); Queue::assertPushed( \App\Jobs\User\UpdateJob::class, function ($job) use ($user) { return TestCase::getObjectProperty($job, 'userId') == $user->id; } ); $userB->restrict(); $this->assertTrue($userB->fresh()->isRestricted()); Queue::fake(); // reset queue state $user->refresh(); $user->unrestrict(); $this->assertFalse($user->fresh()->isRestricted()); $this->assertTrue($userB->fresh()->isRestricted()); Queue::assertPushed( \App\Jobs\User\UpdateJob::class, function ($job) use ($user) { return TestCase::getObjectProperty($job, 'userId') == $user->id; } ); Queue::fake(); // reset queue state $user->unrestrict(true); $this->assertFalse($user->fresh()->isRestricted()); $this->assertFalse($userB->fresh()->isRestricted()); Queue::assertPushed( \App\Jobs\User\UpdateJob::class, function ($job) use ($userB) { return TestCase::getObjectProperty($job, 'userId') == $userB->id; } ); } /** * Tests for AliasesTrait::setAliases() */ public function testSetAliases(): void { Queue::fake(); Queue::assertNothingPushed(); $user = $this->getTestUser('UserAccountA@UserAccount.com'); $domain = $this->getTestDomain('UserAccount.com', [ 'status' => Domain::STATUS_NEW, 'type' => Domain::TYPE_HOSTED, ]); $this->assertCount(0, $user->aliases->all()); $user->tenant->setSetting('pgp.enable', 1); // Add an alias $user->setAliases(['UserAlias1@UserAccount.com']); Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 1); Queue::assertPushed(\App\Jobs\PGP\KeyCreateJob::class, 1); $user->tenant->setSetting('pgp.enable', 0); $aliases = $user->aliases()->get(); $this->assertCount(1, $aliases); $this->assertSame('useralias1@useraccount.com', $aliases[0]['alias']); // Add another alias $user->setAliases(['UserAlias1@UserAccount.com', 'UserAlias2@UserAccount.com']); Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 2); Queue::assertPushed(\App\Jobs\PGP\KeyCreateJob::class, 1); $aliases = $user->aliases()->orderBy('alias')->get(); $this->assertCount(2, $aliases); $this->assertSame('useralias1@useraccount.com', $aliases[0]->alias); $this->assertSame('useralias2@useraccount.com', $aliases[1]->alias); $user->tenant->setSetting('pgp.enable', 1); // Remove an alias $user->setAliases(['UserAlias1@UserAccount.com']); $user->tenant->setSetting('pgp.enable', 0); Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 3); Queue::assertPushed(\App\Jobs\PGP\KeyDeleteJob::class, 1); Queue::assertPushed( \App\Jobs\PGP\KeyDeleteJob::class, function ($job) use ($user) { $userId = TestCase::getObjectProperty($job, 'userId'); $userEmail = TestCase::getObjectProperty($job, 'userEmail'); return $userId == $user->id && $userEmail === 'useralias2@useraccount.com'; } ); $aliases = $user->aliases()->get(); $this->assertCount(1, $aliases); $this->assertSame('useralias1@useraccount.com', $aliases[0]['alias']); // Remove all aliases $user->setAliases([]); Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 4); $this->assertCount(0, $user->aliases()->get()); } /** * Tests for suspendAccount() */ public function testSuspendAccount(): void { $user = $this->getTestUser('UserAccountA@UserAccount.com'); $wallet = $user->wallets()->first(); // No entitlements, expect the wallet owner to be suspended anyway $user->suspendAccount(); $this->assertTrue($user->fresh()->isSuspended()); // Add entitlements and more suspendable objects into the wallet $user->unsuspend(); $mailbox_sku = Sku::withEnvTenantContext()->where('title', 'mailbox')->first(); $domain_sku = Sku::withEnvTenantContext()->where('title', 'domain-hosting')->first(); $group_sku = Sku::withEnvTenantContext()->where('title', 'group')->first(); $resource_sku = Sku::withEnvTenantContext()->where('title', 'resource')->first(); $userB = $this->getTestUser('UserAccountB@UserAccount.com'); $userB->assignSku($mailbox_sku, 1, $wallet); $domain = $this->getTestDomain('UserAccount.com', ['type' => \App\Domain::TYPE_PUBLIC]); $domain->assignSku($domain_sku, 1, $wallet); $group = $this->getTestGroup('test-group@UserAccount.com'); $group->assignSku($group_sku, 1, $wallet); $resource = $this->getTestResource('test-resource@UserAccount.com'); $resource->assignSku($resource_sku, 1, $wallet); $this->assertFalse($user->isSuspended()); $this->assertFalse($userB->isSuspended()); $this->assertFalse($domain->isSuspended()); $this->assertFalse($group->isSuspended()); $this->assertFalse($resource->isSuspended()); $user->suspendAccount(); $this->assertTrue($user->fresh()->isSuspended()); $this->assertTrue($userB->fresh()->isSuspended()); $this->assertTrue($domain->fresh()->isSuspended()); $this->assertTrue($group->fresh()->isSuspended()); $this->assertFalse($resource->fresh()->isSuspended()); } /** * Tests for UserSettingsTrait::setSettings() and getSetting() and getSettings() */ public function testUserSettings(): void { Queue::fake(); Queue::assertNothingPushed(); $user = $this->getTestUser('UserAccountA@UserAccount.com'); Queue::assertPushed(\App\Jobs\User\UpdateJob::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\User\UpdateJob::class, 1); + if (\config('app.with_ldap')) { + Queue::assertPushed(\App\Jobs\User\UpdateJob::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\User\UpdateJob::class, 2); + if (\config('app.with_ldap')) { + Queue::assertPushed(\App\Jobs\User\UpdateJob::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\User\UpdateJob::class, 3); + if (\config('app.with_ldap')) { + Queue::assertPushed(\App\Jobs\User\UpdateJob::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\User\UpdateJob::class, 5); + if (\config('app.with_ldap')) { + Queue::assertPushed(\App\Jobs\User\UpdateJob::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\User\UpdateJob::class, 7); + if (\config('app.with_ldap')) { + Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 7); + } // 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); // Test getSettings() method $this->assertSame( [ 'first_name' => 'Firstname2', 'last_name' => 'Lastname2', 'unknown' => null, ], $user->getSettings(['first_name', 'last_name', 'unknown']) ); } /** * Tests for User::users() */ public function testUsers(): void { $jack = $this->getTestUser('jack@kolab.org'); $joe = $this->getTestUser('joe@kolab.org'); $john = $this->getTestUser('john@kolab.org'); $ned = $this->getTestUser('ned@kolab.org'); $wallet = $john->wallets()->first(); $users = $john->users()->orderBy('email')->get(); $this->assertCount(4, $users); $this->assertEquals($jack->id, $users[0]->id); $this->assertEquals($joe->id, $users[1]->id); $this->assertEquals($john->id, $users[2]->id); $this->assertEquals($ned->id, $users[3]->id); $users = $jack->users()->orderBy('email')->get(); $this->assertCount(0, $users); $users = $ned->users()->orderBy('email')->get(); $this->assertCount(4, $users); } /** * Tests for User::walletOwner() (from EntitleableTrait) */ public function testWalletOwner(): void { $jack = $this->getTestUser('jack@kolab.org'); $john = $this->getTestUser('john@kolab.org'); $ned = $this->getTestUser('ned@kolab.org'); $this->assertSame($john->id, $john->walletOwner()->id); $this->assertSame($john->id, $jack->walletOwner()->id); $this->assertSame($john->id, $ned->walletOwner()->id); // User with no entitlements $user = $this->getTestUser('UserAccountA@UserAccount.com'); $this->assertSame($user->id, $user->walletOwner()->id); } /** * Tests for User::wallets() */ public function testWallets(): void { $john = $this->getTestUser('john@kolab.org'); $ned = $this->getTestUser('ned@kolab.org'); $this->assertSame(1, $john->wallets()->count()); $this->assertCount(1, $john->wallets); $this->assertInstanceOf(\App\Wallet::class, $john->wallets->first()); $this->assertSame(1, $ned->wallets()->count()); $this->assertCount(1, $ned->wallets); $this->assertInstanceOf(\App\Wallet::class, $ned->wallets->first()); } } diff --git a/src/tests/Unit/DomainTest.php b/src/tests/Unit/DomainTest.php index 439467af..e3b3034c 100644 --- a/src/tests/Unit/DomainTest.php +++ b/src/tests/Unit/DomainTest.php @@ -1,138 +1,140 @@ 'test.com', 'status' => \array_sum($domainStatuses), 'type' => Domain::TYPE_EXTERNAL ] ); $domainStatuses = []; foreach ($statuses as $status) { if ($domain->status & $status) { $domainStatuses[] = $status; } } $this->assertSame($domain->status, \array_sum($domainStatuses)); // either one is true, but not both $this->assertSame( $domain->isNew() === in_array(Domain::STATUS_NEW, $domainStatuses), $domain->isActive() === in_array(Domain::STATUS_ACTIVE, $domainStatuses) ); $this->assertTrue( $domain->isNew() === in_array(Domain::STATUS_NEW, $domainStatuses) ); $this->assertTrue( $domain->isActive() === in_array(Domain::STATUS_ACTIVE, $domainStatuses) ); $this->assertTrue( $domain->isConfirmed() === in_array(Domain::STATUS_CONFIRMED, $domainStatuses) ); $this->assertTrue( $domain->isSuspended() === in_array(Domain::STATUS_SUSPENDED, $domainStatuses) ); $this->assertTrue( $domain->isDeleted() === in_array(Domain::STATUS_DELETED, $domainStatuses) ); - $this->assertTrue( - $domain->isLdapReady() === in_array(Domain::STATUS_LDAP_READY, $domainStatuses) - ); + if (\config('app.with_ldap')) { + $this->assertTrue( + $domain->isLdapReady() === in_array(Domain::STATUS_LDAP_READY, $domainStatuses) + ); + } $this->assertTrue( $domain->isVerified() === in_array(Domain::STATUS_VERIFIED, $domainStatuses) ); } } /** * Test basic Domain funtionality */ public function testDomainType(): void { $types = [ Domain::TYPE_PUBLIC, Domain::TYPE_HOSTED, Domain::TYPE_EXTERNAL, ]; $domains = \Tests\Utils::powerSet($types); foreach ($domains as $domain_types) { $domain = new Domain( [ 'namespace' => 'test.com', 'status' => Domain::STATUS_NEW, 'type' => \array_sum($domain_types), ] ); $this->assertTrue($domain->isPublic() === in_array(Domain::TYPE_PUBLIC, $domain_types)); $this->assertTrue($domain->isHosted() === in_array(Domain::TYPE_HOSTED, $domain_types)); $this->assertTrue($domain->isExternal() === in_array(Domain::TYPE_EXTERNAL, $domain_types)); } } /** * Test domain hash generation */ public function testHash(): void { $domain = new Domain([ 'namespace' => 'test.com', 'status' => Domain::STATUS_NEW, ]); $hash_code = $domain->hash(); $this->assertMatchesRegularExpression('/^[a-f0-9]{32}$/', $hash_code); $hash_text = $domain->hash(Domain::HASH_TEXT); $this->assertMatchesRegularExpression('/^kolab-verify=[a-f0-9]{32}$/', $hash_text); $this->assertSame($hash_code, str_replace('kolab-verify=', '', $hash_text)); $hash_cname = $domain->hash(Domain::HASH_CNAME); $this->assertSame('kolab-verify', $hash_cname); $hash_code2 = $domain->hash(Domain::HASH_CODE); $this->assertSame($hash_code, $hash_code2); } }