diff --git a/src/database/migrations/2022_07_08_100000_fix_group_sku_name.php b/src/database/migrations/2022_07_08_100000_fix_group_sku_name.php new file mode 100644 index 00000000..b044f93e --- /dev/null +++ b/src/database/migrations/2022_07_08_100000_fix_group_sku_name.php @@ -0,0 +1,32 @@ +get()->each(function ($sku) { + $sku->name = 'Distribution list'; + $sku->description = 'Mail distribution list'; + $sku->save(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + \App\Sku::where('title', 'group')->get()->each(function ($sku) { + $sku->name = 'Group'; + $sku->description = 'Distribution list'; + $sku->save(); + }); + } +}; diff --git a/src/database/seeds/local/SkuSeeder.php b/src/database/seeds/local/SkuSeeder.php index 0645b15a..57f325a9 100644 --- a/src/database/seeds/local/SkuSeeder.php +++ b/src/database/seeds/local/SkuSeeder.php @@ -1,250 +1,250 @@ 'mailbox', 'name' => 'User Mailbox', 'description' => 'Just a mailbox', 'cost' => 500, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Mailbox', 'active' => true, ], [ 'title' => 'domain', 'name' => 'Hosted Domain', 'description' => 'Somewhere to place a mailbox', 'cost' => 100, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Domain', 'active' => false, ], [ 'title' => 'domain-registration', 'name' => 'Domain Registration', 'description' => 'Register a domain with us', 'cost' => 101, 'period' => 'yearly', 'handler_class' => 'App\Handlers\DomainRegistration', 'active' => false, ], [ 'title' => 'domain-hosting', 'name' => 'External Domain', 'description' => 'Host a domain that is externally registered', 'cost' => 100, 'units_free' => 1, 'period' => 'monthly', 'handler_class' => 'App\Handlers\DomainHosting', 'active' => true, ], [ 'title' => 'domain-relay', 'name' => 'Domain Relay', 'description' => 'A domain you host at home, for which we relay email', 'cost' => 103, 'period' => 'monthly', 'handler_class' => 'App\Handlers\DomainRelay', 'active' => false, ], [ 'title' => 'storage', 'name' => 'Storage Quota', 'description' => 'Some wiggle room', 'cost' => 25, 'units_free' => 5, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Storage', 'active' => true, ], [ 'title' => 'groupware', 'name' => 'Groupware Features', 'description' => 'Groupware functions like Calendar, Tasks, Notes, etc.', 'cost' => 490, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Groupware', 'active' => true, ], [ 'title' => 'resource', 'name' => 'Resource', 'description' => 'Reservation taker', 'cost' => 101, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Resource', 'active' => true, ], [ 'title' => 'shared-folder', 'name' => 'Shared Folder', 'description' => 'A shared folder', 'cost' => 89, 'period' => 'monthly', 'handler_class' => 'App\Handlers\SharedFolder', 'active' => true, ], [ 'title' => '2fa', 'name' => '2-Factor Authentication', 'description' => 'Two factor authentication for webmail and administration panel', 'cost' => 0, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Auth2F', 'active' => true, ], [ 'title' => 'activesync', 'name' => 'Activesync', 'description' => 'Mobile synchronization', 'cost' => 0, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Activesync', 'active' => true, ], [ 'title' => 'beta', 'name' => 'Private Beta (invitation only)', 'description' => 'Access to the private beta program features', 'cost' => 0, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Beta', 'active' => false, ], [ 'title' => 'group', - 'name' => 'Group', - 'description' => 'Distribution list', + 'name' => 'Distribution list', + 'description' => 'Mail distribution list', 'cost' => 0, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Group', 'active' => true, ], [ 'title' => 'group-room', 'name' => 'Group conference room', 'description' => 'Shareable audio & video conference room', 'cost' => 0, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\GroupRoom', 'active' => true, ], [ 'title' => 'room', 'name' => 'Standard conference room', 'description' => 'Audio & video conference room', 'cost' => 0, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Room', 'active' => true, ], ]; foreach ($skus as $sku) { // Check existence because migration might have added this already if (!Sku::where('title', $sku['title'])->where('tenant_id', \config('app.tenant_id'))->first()) { Sku::create($sku); } } $skus = [ [ 'title' => 'mailbox', 'name' => 'User Mailbox', 'description' => 'Just a mailbox', 'cost' => 500, 'fee' => 333, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Mailbox', 'active' => true, ], [ 'title' => 'storage', 'name' => 'Storage Quota', 'description' => 'Some wiggle room', 'cost' => 25, 'fee' => 16, 'units_free' => 5, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Storage', 'active' => true, ], [ 'title' => 'domain-hosting', 'name' => 'External Domain', 'description' => 'Host a domain that is externally registered', 'cost' => 100, 'fee' => 66, 'units_free' => 1, 'period' => 'monthly', 'handler_class' => 'App\Handlers\DomainHosting', 'active' => true, ], [ 'title' => 'groupware', 'name' => 'Groupware Features', 'description' => 'Groupware functions like Calendar, Tasks, Notes, etc.', 'cost' => 490, 'fee' => 327, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Groupware', 'active' => true, ], [ 'title' => '2fa', 'name' => '2-Factor Authentication', 'description' => 'Two factor authentication for webmail and administration panel', 'cost' => 0, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Auth2F', 'active' => true, ], [ 'title' => 'activesync', 'name' => 'Activesync', 'description' => 'Mobile synchronization', 'cost' => 0, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Activesync', 'active' => true, ], ]; // for tenants that are not the configured tenant id $tenants = \App\Tenant::where('id', '!=', \config('app.tenant_id'))->get(); foreach ($tenants as $tenant) { foreach ($skus as $sku) { $sku = Sku::create($sku); $sku->tenant_id = $tenant->id; $sku->save(); } } } } diff --git a/src/database/seeds/production/SkuSeeder.php b/src/database/seeds/production/SkuSeeder.php index 99dbb501..c18b77aa 100644 --- a/src/database/seeds/production/SkuSeeder.php +++ b/src/database/seeds/production/SkuSeeder.php @@ -1,172 +1,172 @@ 'mailbox', 'name' => 'User Mailbox', 'description' => 'Just a mailbox', 'cost' => 444, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Mailbox', 'active' => true, ], [ 'title' => 'domain', 'name' => 'Hosted Domain', 'description' => 'Somewhere to place a mailbox', 'cost' => 100, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Domain', 'active' => false, ], [ 'title' => 'domain-registration', 'name' => 'Domain Registration', 'description' => 'Register a domain with us', 'cost' => 101, 'period' => 'yearly', 'handler_class' => 'App\Handlers\DomainRegistration', 'active' => false, ], [ 'title' => 'domain-hosting', 'name' => 'External Domain', 'description' => 'Host a domain that is externally registered', 'cost' => 100, 'units_free' => 1, 'period' => 'monthly', 'handler_class' => 'App\Handlers\DomainHosting', 'active' => true, ], [ 'title' => 'domain-relay', 'name' => 'Domain Relay', 'description' => 'A domain you host at home, for which we relay email', 'cost' => 103, 'period' => 'monthly', 'handler_class' => 'App\Handlers\DomainRelay', 'active' => false, ], [ 'title' => 'storage', 'name' => 'Storage Quota', 'description' => 'Some wiggle room', 'cost' => 50, 'units_free' => 2, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Storage', 'active' => true, ], [ 'title' => 'groupware', 'name' => 'Groupware Features', 'description' => 'Groupware functions like Calendar, Tasks, Notes, etc.', 'cost' => 555, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Groupware', 'active' => true, ], [ 'title' => 'resource', 'name' => 'Resource', 'description' => 'Reservation taker', 'cost' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Resource', 'active' => true, ], [ 'title' => 'shared-folder', 'name' => 'Shared Folder', 'description' => 'A shared folder', 'cost' => 89, 'period' => 'monthly', 'handler_class' => 'App\Handlers\SharedFolder', 'active' => false, ], [ 'title' => '2fa', 'name' => '2-Factor Authentication', 'description' => 'Two factor authentication for webmail and administration panel', 'cost' => 0, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Auth2F', 'active' => true, ], [ 'title' => 'activesync', 'name' => 'Activesync', 'description' => 'Mobile synchronization', 'cost' => 100, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Activesync', 'active' => true, ], [ 'title' => 'beta', 'name' => 'Private Beta (invitation only)', 'description' => 'Access to the private beta program subscriptions', 'cost' => 0, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Beta', 'active' => false, ], [ 'title' => 'group', - 'name' => 'Group', - 'description' => 'Distribution list', + 'name' => 'Distribution list', + 'description' => 'Mail distribution list', 'cost' => 0, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Group', 'active' => true, ], [ 'title' => 'group-room', 'name' => 'Group conference room', 'description' => 'Shareable audio & video conference room', 'cost' => 0, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\GroupRoom', 'active' => true, ], [ 'title' => 'room', 'name' => 'Standard conference room', 'description' => 'Audio & video conference room', 'cost' => 0, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Room', 'active' => true, ], ]; foreach ($skus as $sku) { // Check existence because migration might have added this already if (!Sku::where('title', $sku['title'])->first()) { Sku::create($sku); } } } } diff --git a/src/tests/Browser/DistlistTest.php b/src/tests/Browser/DistlistTest.php index 171d121e..6a255142 100644 --- a/src/tests/Browser/DistlistTest.php +++ b/src/tests/Browser/DistlistTest.php @@ -1,338 +1,338 @@ deleteTestGroup('group-test@kolab.org'); $this->clearBetaEntitlements(); } /** * {@inheritDoc} */ public function tearDown(): void { $this->deleteTestGroup('group-test@kolab.org'); $this->clearBetaEntitlements(); parent::tearDown(); } /** * Test distlist info page (unauthenticated) */ public function testInfoUnauth(): void { // Test that the page requires authentication $this->browse(function (Browser $browser) { $browser->visit('/distlist/abc')->on(new Home()); }); } /** * Test distlist list page (unauthenticated) */ public function testListUnauth(): void { // Test that the page requires authentication $this->browse(function (Browser $browser) { $browser->visit('/distlists')->on(new Home()); }); } /** * Test distlist list page */ public function testList(): void { // Log on the user $this->browse(function (Browser $browser) { $browser->visit(new Home()) ->submitLogon('john@kolab.org', 'simple123', true) ->on(new Dashboard()) ->assertMissing('@links .link-distlists'); }); // Test that Distribution lists page is not accessible without the 'beta' entitlement $this->browse(function (Browser $browser) { $browser->visit('/distlists') ->assertErrorPage(403); }); // Create a single group, add beta entitlement $john = $this->getTestUser('john@kolab.org'); $this->addBetaEntitlement($john); $group = $this->getTestGroup('group-test@kolab.org', ['name' => 'Test Group']); $group->assignToWallet($john->wallets->first()); // Test distribution lists page $this->browse(function (Browser $browser) { $browser->visit(new Dashboard()) ->assertSeeIn('@links .link-distlists', 'Distribution lists') ->click('@links .link-distlists') ->on(new DistlistList()) ->whenAvailable('@table', function (Browser $browser) { $browser->waitFor('tbody tr') ->assertSeeIn('thead tr th:nth-child(1)', 'Name') ->assertSeeIn('thead tr th:nth-child(2)', 'Email') ->assertElementsCount('tbody tr', 1) ->assertSeeIn('tbody tr:nth-child(1) td:nth-child(1) a', 'Test Group') ->assertText('tbody tr:nth-child(1) td:nth-child(1) svg.text-danger title', 'Not Ready') ->assertSeeIn('tbody tr:nth-child(1) td:nth-child(2) a', 'group-test@kolab.org') ->assertMissing('tfoot'); }); }); } /** * Test distlist creation/editing/deleting * * @depends testList */ public function testCreateUpdateDelete(): void { // Test that the page is not available accessible without the 'beta' entitlement $this->browse(function (Browser $browser) { $browser->visit('/distlist/new') ->assertErrorPage(403); }); // Add beta+distlist entitlements $john = $this->getTestUser('john@kolab.org'); $this->addBetaEntitlement($john); $this->browse(function (Browser $browser) { // Create a group $browser->visit(new DistlistList()) ->assertSeeIn('button.distlist-new', 'Create list') ->click('button.distlist-new') ->on(new DistlistInfo()) ->assertSeeIn('#distlist-info .card-title', 'New distribution list') ->assertSeeIn('@nav #tab-general', 'General') ->assertMissing('@nav #tab-settings') ->with('@general', function (Browser $browser) { // Assert form content $browser->assertMissing('#status') ->assertFocused('#name') ->assertSeeIn('div.row:nth-child(1) label', 'Name') ->assertValue('div.row:nth-child(1) input[type=text]', '') ->assertSeeIn('div.row:nth-child(2) label', 'Email') ->assertValue('div.row:nth-child(2) input[type=text]', '') ->assertSeeIn('div.row:nth-child(3) label', 'Recipients') ->assertVisible('div.row:nth-child(3) .list-input') ->with(new ListInput('#members'), function (Browser $browser) { $browser->assertListInputValue([]) ->assertValue('@input', ''); }) ->assertSeeIn('div.row:nth-child(4) label', 'Subscriptions') ->with('@skus', function ($browser) { $browser->assertElementsCount('tbody tr', 1) - ->assertSeeIn('tbody tr:nth-child(1) td.name', 'Group') + ->assertSeeIn('tbody tr:nth-child(1) td.name', 'Distribution list') ->assertSeeIn('tbody tr:nth-child(1) td.price', '0,00 CHF/month') ->assertChecked('tbody tr:nth-child(1) td.selection input') ->assertDisabled('tbody tr:nth-child(1) td.selection input') ->assertTip( 'tbody tr:nth-child(1) td.buttons button', - 'Distribution list' + 'Mail distribution list' ); }) ->assertSeeIn('button[type=submit]', 'Submit'); }) // Test error conditions ->type('#name', str_repeat('A', 192)) ->type('#email', 'group-test@kolabnow.com') ->click('@general button[type=submit]') ->waitFor('#members + .invalid-feedback') ->assertSeeIn('#email + .invalid-feedback', 'The specified domain is not available.') ->assertSeeIn('#name + .invalid-feedback', 'The name may not be greater than 191 characters.') ->assertSeeIn('#members + .invalid-feedback', 'At least one recipient is required.') ->assertFocused('#name') ->assertToast(Toast::TYPE_ERROR, 'Form validation error') // Test successful group creation ->type('#name', 'Test Group') ->type('#email', 'group-test@kolab.org') ->with(new ListInput('#members'), function (Browser $browser) { $browser->addListEntry('test1@gmail.com') ->addListEntry('test2@gmail.com'); }) ->click('@general button[type=submit]') ->assertToast(Toast::TYPE_SUCCESS, 'Distribution list created successfully.') ->on(new DistlistList()) ->assertElementsCount('@table tbody tr', 1); // Test group update $browser->click('@table tr:nth-child(1) td:first-child a') ->on(new DistlistInfo()) ->assertSeeIn('#distlist-info .card-title', 'Distribution list') ->with('@general', function (Browser $browser) { // Assert form content $browser->assertFocused('#name') ->assertSeeIn('div.row:nth-child(1) label', 'Status') ->assertSeeIn('div.row:nth-child(1) span.text-danger', 'Not Ready') ->assertSeeIn('div.row:nth-child(2) label', 'Name') ->assertValue('div.row:nth-child(2) input[type=text]', 'Test Group') ->assertSeeIn('div.row:nth-child(3) label', 'Email') ->assertValue('div.row:nth-child(3) input[type=text]:disabled', 'group-test@kolab.org') ->assertSeeIn('div.row:nth-child(4) label', 'Recipients') ->assertVisible('div.row:nth-child(4) .list-input') ->with(new ListInput('#members'), function (Browser $browser) { $browser->assertListInputValue(['test1@gmail.com', 'test2@gmail.com']) ->assertValue('@input', ''); }) ->assertSeeIn('div.row:nth-child(5) label', 'Subscriptions') ->with('@skus', function ($browser) { $browser->assertElementsCount('tbody tr', 1) - ->assertSeeIn('tbody tr:nth-child(1) td.name', 'Group') + ->assertSeeIn('tbody tr:nth-child(1) td.name', 'Distribution list') ->assertSeeIn('tbody tr:nth-child(1) td.price', '0,00 CHF/month') ->assertChecked('tbody tr:nth-child(1) td.selection input') ->assertDisabled('tbody tr:nth-child(1) td.selection input') ->assertTip( 'tbody tr:nth-child(1) td.buttons button', - 'Distribution list' + 'Mail distribution list' ); }) ->assertSeeIn('button[type=submit]', 'Submit'); }) // Test error handling ->with(new ListInput('#members'), function (Browser $browser) { $browser->addListEntry('invalid address'); }) ->click('@general button[type=submit]') ->waitFor('#members + .invalid-feedback') ->assertSeeIn('#members + .invalid-feedback', 'The specified email address is invalid.') ->assertVisible('#members .input-group:nth-child(4) input.is-invalid') ->assertToast(Toast::TYPE_ERROR, 'Form validation error') // Test successful update ->with(new ListInput('#members'), function (Browser $browser) { $browser->removeListEntry(3)->removeListEntry(2); }) ->click('@general button[type=submit]') ->assertToast(Toast::TYPE_SUCCESS, 'Distribution list updated successfully.') ->assertMissing('.invalid-feedback') ->on(new DistlistList()) ->assertElementsCount('@table tbody tr', 1); $group = Group::where('email', 'group-test@kolab.org')->first(); $this->assertSame(['test1@gmail.com'], $group->members); // Test group deletion $browser->click('@table tr:nth-child(1) td:first-child a') ->on(new DistlistInfo()) ->assertSeeIn('button.button-delete', 'Delete list') ->click('button.button-delete') ->assertToast(Toast::TYPE_SUCCESS, 'Distribution list deleted successfully.') ->on(new DistlistList()) ->assertElementsCount('@table tbody tr', 0) ->assertVisible('@table tfoot'); $this->assertNull(Group::where('email', 'group-test@kolab.org')->first()); }); } /** * Test distribution list status * * @depends testList */ public function testStatus(): void { $john = $this->getTestUser('john@kolab.org'); $this->addBetaEntitlement($john, 'beta'); $group = $this->getTestGroup('group-test@kolab.org'); $group->assignToWallet($john->wallets->first()); $group->status = Group::STATUS_NEW | Group::STATUS_ACTIVE; $group->save(); $this->assertFalse($group->isLdapReady()); $this->browse(function ($browser) use ($group) { // Test auto-refresh $browser->visit('/distlist/' . $group->id) ->on(new DistlistInfo()) ->with(new Status(), function ($browser) { $browser->assertSeeIn('@body', 'We are preparing the distribution list') ->assertProgress(83, 'Creating a distribution list...', 'pending') ->assertMissing('@refresh-button') ->assertMissing('@refresh-text') ->assertMissing('#status-link') ->assertMissing('#status-verify'); }); $group->status |= Group::STATUS_LDAP_READY; $group->save(); // Test Verify button $browser->waitUntilMissing('@status', 10); }); // TODO: Test all group statuses on the list } /** * Test distribution list settings */ public function testSettings(): void { $john = $this->getTestUser('john@kolab.org'); $this->addBetaEntitlement($john); $group = $this->getTestGroup('group-test@kolab.org'); $group->assignToWallet($john->wallets->first()); $group->status = Group::STATUS_NEW | Group::STATUS_ACTIVE; $group->save(); $this->browse(function ($browser) use ($group) { // Test auto-refresh $browser->visit('/distlist/' . $group->id) ->on(new DistlistInfo()) ->assertSeeIn('@nav #tab-general', 'General') ->assertSeeIn('@nav #tab-settings', 'Settings') ->click('@nav #tab-settings') ->with('@settings form', function (Browser $browser) { // Assert form content $browser->assertSeeIn('div.row:nth-child(1) label', 'Sender Access List') ->assertVisible('div.row:nth-child(1) .list-input') ->with(new ListInput('#sender-policy'), function (Browser $browser) { $browser->assertListInputValue([]) ->assertValue('@input', ''); }) ->assertSeeIn('button[type=submit]', 'Submit'); }) // Test error handling ->with(new ListInput('#sender-policy'), function (Browser $browser) { $browser->addListEntry('test.com'); }) ->click('@settings button[type=submit]') ->assertToast(Toast::TYPE_SUCCESS, 'Distribution list settings updated successfully.') ->assertMissing('.invalid-feedback') ->refresh() ->on(new DistlistInfo()) ->click('@nav #tab-settings') ->with('@settings form', function (Browser $browser) { $browser->with(new ListInput('#sender-policy'), function (Browser $browser) { $browser->assertListInputValue(['test.com']) ->assertValue('@input', ''); }); }); }); } }