diff --git a/src/app/Http/Controllers/API/V4/UsersController.php b/src/app/Http/Controllers/API/V4/UsersController.php index 826005c0..9535eb4b 100644 --- a/src/app/Http/Controllers/API/V4/UsersController.php +++ b/src/app/Http/Controllers/API/V4/UsersController.php @@ -1,681 +1,685 @@ errorResponse(404); } // User can't remove himself until he's the controller if (!$this->guard()->user()->canDelete($user)) { return $this->errorResponse(403); } $user->delete(); return response()->json([ 'status' => 'success', 'message' => __('app.user-delete-success'), ]); } /** * Listing of users. * * The user-entitlements billed to the current user wallet(s) * * @return \Illuminate\Http\JsonResponse */ public function index() { $user = $this->guard()->user(); $result = $user->users()->orderBy('email')->get()->map(function ($user) { $data = $user->toArray(); $data = array_merge($data, self::userStatuses($user)); return $data; }); return response()->json($result); } /** * Display information on the user account specified by $id. * * @param int $id The account to show information for. * * @return \Illuminate\Http\JsonResponse */ public function show($id) { $user = User::find($id); if (empty($user)) { return $this->errorResponse(404); } if (!$this->guard()->user()->canRead($user)) { return $this->errorResponse(403); } $response = $this->userResponse($user); // Simplified Entitlement/SKU information, // TODO: I agree this format may need to be extended in future $response['skus'] = []; foreach ($user->entitlements as $ent) { $sku = $ent->sku; $response['skus'][$sku->id] = [ // 'cost' => $ent->cost, 'count' => isset($response['skus'][$sku->id]) ? $response['skus'][$sku->id]['count'] + 1 : 1, ]; } return response()->json($response); } /** * Fetch user status (and reload setup process) * * @param int $id User identifier * * @return \Illuminate\Http\JsonResponse */ public function status($id) { $user = User::find($id); if (empty($user)) { return $this->errorResponse(404); } if (!$this->guard()->user()->canRead($user)) { return $this->errorResponse(403); } $response = self::statusInfo($user); if (!empty(request()->input('refresh'))) { $updated = false; $last_step = 'none'; foreach ($response['process'] as $idx => $step) { $last_step = $step['label']; if (!$step['state']) { if (!$this->execProcessStep($user, $step['label'])) { break; } $updated = true; } } if ($updated) { $response = self::statusInfo($user); } $success = $response['isReady']; $suffix = $success ? 'success' : 'error-' . $last_step; $response['status'] = $success ? 'success' : 'error'; $response['message'] = \trans('app.process-' . $suffix); } $response = array_merge($response, self::userStatuses($user)); return response()->json($response); } /** * User status (extended) information * * @param \App\User $user User object * * @return array Status information */ public static function statusInfo(User $user): array { $process = []; $steps = [ 'user-new' => true, 'user-ldap-ready' => $user->isLdapReady(), 'user-imap-ready' => $user->isImapReady(), ]; // Create a process check list foreach ($steps as $step_name => $state) { $step = [ 'label' => $step_name, 'title' => \trans("app.process-{$step_name}"), 'state' => $state, ]; $process[] = $step; } list ($local, $domain) = explode('@', $user->email); $domain = Domain::where('namespace', $domain)->first(); // If that is not a public domain, add domain specific steps if ($domain && !$domain->isPublic()) { $domain_status = DomainsController::statusInfo($domain); $process = array_merge($process, $domain_status['process']); } $all = count($process); $checked = count(array_filter($process, function ($v) { return $v['state']; })); $state = $all === $checked ? 'done' : 'running'; // After 180 seconds assume the process is in failed state, // this should unlock the Refresh button in the UI if ($all !== $checked && $user->created_at->diffInSeconds(Carbon::now()) > 180) { $state = 'failed'; } // Check if the user is a controller of his wallet $isController = $user->canDelete($user); $hasCustomDomain = $user->wallet()->entitlements() ->where('entitleable_type', Domain::class) ->count() > 0; return [ // TODO: This will change when we enable all users to create domains 'enableDomains' => $isController && $hasCustomDomain, 'enableUsers' => $isController, 'enableWallets' => $isController, 'process' => $process, 'processState' => $state, 'isReady' => $all === $checked, ]; } /** * Create a new user record. * * @param \Illuminate\Http\Request $request The API request. * * @return \Illuminate\Http\JsonResponse The response */ public function store(Request $request) { $current_user = $this->guard()->user(); $owner = $current_user->wallet()->owner; if ($owner->id != $current_user->id) { return $this->errorResponse(403); } if ($error_response = $this->validateUserRequest($request, null, $settings)) { return $error_response; } if (empty($request->package) || !($package = \App\Package::find($request->package))) { $errors = ['package' => \trans('validation.packagerequired')]; return response()->json(['status' => 'error', 'errors' => $errors], 422); } if ($package->isDomain()) { $errors = ['package' => \trans('validation.packageinvalid')]; return response()->json(['status' => 'error', 'errors' => $errors], 422); } DB::beginTransaction(); // Create user record $user = User::create([ 'email' => $request->email, 'password' => $request->password, ]); $owner->assignPackage($package, $user); if (!empty($settings)) { $user->setSettings($settings); } if (!empty($request->aliases)) { $user->setAliases($request->aliases); } DB::commit(); return response()->json([ 'status' => 'success', 'message' => __('app.user-create-success'), ]); } /** * Update user data. * * @param \Illuminate\Http\Request $request The API request. * @param string $id User identifier * * @return \Illuminate\Http\JsonResponse The response */ public function update(Request $request, $id) { $user = User::find($id); if (empty($user)) { return $this->errorResponse(404); } $current_user = $this->guard()->user(); // TODO: Decide what attributes a user can change on his own profile if (!$current_user->canUpdate($user)) { return $this->errorResponse(403); } if ($error_response = $this->validateUserRequest($request, $user, $settings)) { return $error_response; } // Entitlements, only controller can do that if ($request->skus !== null && !$current_user->canDelete($user)) { return $this->errorResponse(422, "You have no permission to change entitlements"); } DB::beginTransaction(); $this->updateEntitlements($user, $request->skus); if (!empty($settings)) { $user->setSettings($settings); } if (!empty($request->password)) { $user->password = $request->password; $user->save(); } if (isset($request->aliases)) { $user->setAliases($request->aliases); } // TODO: Make sure that UserUpdate job is created in case of entitlements update // and no password change. So, for example quota change is applied to LDAP // TODO: Review use of $user->save() in the above context DB::commit(); return response()->json([ 'status' => 'success', 'message' => __('app.user-update-success'), ]); } /** * Get the guard to be used during authentication. * * @return \Illuminate\Contracts\Auth\Guard */ public function guard() { return Auth::guard(); } /** * Update user entitlements. * * @param \App\User $user The user * @param array $rSkus List of SKU IDs requested for the user in the form [id=>qty] */ protected function updateEntitlements(User $user, $rSkus) { if (!is_array($rSkus)) { return; } // list of skus, [id=>obj] $skus = Sku::all()->mapWithKeys( function ($sku) { return [$sku->id => $sku]; } ); // existing entitlement's SKUs $eSkus = []; $user->entitlements()->groupBy('sku_id') ->selectRaw('count(*) as total, sku_id')->each( function ($e) use (&$eSkus) { $eSkus[$e->sku_id] = $e->total; } ); foreach ($skus as $skuID => $sku) { $e = array_key_exists($skuID, $eSkus) ? $eSkus[$skuID] : 0; $r = array_key_exists($skuID, $rSkus) ? $rSkus[$skuID] : 0; if ($sku->handler_class == \App\Handlers\Mailbox::class) { if ($r != 1) { throw new \Exception("Invalid quantity of mailboxes"); } } if ($e > $r) { // remove those entitled more than existing $user->removeSku($sku, ($e - $r)); } elseif ($e < $r) { // add those requested more than entitled $user->assignSku($sku, ($r - $e)); } } } /** * Create a response data array for specified user. * * @param \App\User $user User object * * @return array Response data */ public static function userResponse(User $user): array { $response = $user->toArray(); // Settings $response['settings'] = []; foreach ($user->settings()->whereIn('key', self::USER_SETTINGS)->get() as $item) { $response['settings'][$item->key] = $item->value; } // Aliases $response['aliases'] = []; foreach ($user->aliases as $item) { $response['aliases'][] = $item->alias; } // Status info $response['statusInfo'] = self::statusInfo($user); $response = array_merge($response, self::userStatuses($user)); // Add more info to the wallet object output $map_func = function ($wallet) use ($user) { $result = $wallet->toArray(); if ($wallet->discount) { $result['discount'] = $wallet->discount->discount; $result['discount_description'] = $wallet->discount->description; } if ($wallet->user_id != $user->id) { $result['user_email'] = $wallet->owner->email; } $provider = \App\Providers\PaymentProvider::factory($wallet); $result['provider'] = $provider->name(); return $result; }; // Information about wallets and accounts for access checks $response['wallets'] = $user->wallets->map($map_func)->toArray(); $response['accounts'] = $user->accounts->map($map_func)->toArray(); $response['wallet'] = $map_func($user->wallet()); return $response; } /** * Prepare user statuses for the UI * * @param \App\User $user User object * * @return array Statuses array */ protected static function userStatuses(User $user): array { return [ 'isImapReady' => $user->isImapReady(), 'isLdapReady' => $user->isLdapReady(), 'isSuspended' => $user->isSuspended(), 'isActive' => $user->isActive(), 'isDeleted' => $user->isDeleted() || $user->trashed(), ]; } /** * Validate user input * * @param \Illuminate\Http\Request $request The API request. * @param \App\User|null $user User identifier * @param array $settings User settings (from the request) * * @return \Illuminate\Http\JsonResponse|null The error response on error */ protected function validateUserRequest(Request $request, $user, &$settings = []) { $rules = [ 'external_email' => 'nullable|email', 'phone' => 'string|nullable|max:64|regex:/^[0-9+() -]+$/', 'first_name' => 'string|nullable|max:128', 'last_name' => 'string|nullable|max:128', 'organization' => 'string|nullable|max:512', 'billing_address' => 'string|nullable|max:1024', 'country' => 'string|nullable|alpha|size:2', 'currency' => 'string|nullable|alpha|size:3', 'aliases' => 'array|nullable', ]; if (empty($user) || !empty($request->password) || !empty($request->password_confirmation)) { $rules['password'] = 'required|min:4|max:2048|confirmed'; } $errors = []; // Validate input $v = Validator::make($request->all(), $rules); if ($v->fails()) { $errors = $v->errors()->toArray(); } $controller = $user ? $user->wallet()->owner : $this->guard()->user(); // For new user validate email address if (empty($user)) { $email = $request->email; if (empty($email)) { $errors['email'] = \trans('validation.required', ['attribute' => 'email']); } elseif ($error = self::validateEmail($email, $controller, false)) { $errors['email'] = $error; } } // Validate aliases input if (isset($request->aliases)) { $aliases = []; $existing_aliases = $user ? $user->aliases()->get()->pluck('alias')->toArray() : []; foreach ($request->aliases as $idx => $alias) { if (is_string($alias) && !empty($alias)) { // Alias cannot be the same as the email address (new user) if (!empty($email) && Str::lower($alias) == Str::lower($email)) { continue; } // validate new aliases if ( !in_array($alias, $existing_aliases) && ($error = self::validateEmail($alias, $controller, true)) ) { if (!isset($errors['aliases'])) { $errors['aliases'] = []; } $errors['aliases'][$idx] = $error; continue; } $aliases[] = $alias; } } $request->aliases = $aliases; } if (!empty($errors)) { return response()->json(['status' => 'error', 'errors' => $errors], 422); } // Update user settings $settings = $request->only(array_keys($rules)); unset($settings['password'], $settings['aliases'], $settings['email']); return null; } /** * Execute (synchronously) specified step in a user setup process. * * @param \App\User $user User object * @param string $step Step identifier (as in self::statusInfo()) * * @return bool True if the execution succeeded, False otherwise */ public static function execProcessStep(User $user, string $step): bool { try { if (strpos($step, 'domain-') === 0) { list ($local, $domain) = explode('@', $user->email); $domain = Domain::where('namespace', $domain)->first(); return DomainsController::execProcessStep($domain, $step); } switch ($step) { case 'user-ldap-ready': // User not in LDAP, create it $job = new \App\Jobs\User\CreateJob($user->id); $job->handle(); - return $user->fresh()->isLdapReady(); + $user->refresh(); + + return $user->isLdapReady(); case 'user-imap-ready': // User not in IMAP? Verify again $job = new \App\Jobs\User\VerifyJob($user->id); $job->handle(); - return $user->fresh()->isImapReady(); + $user->refresh(); + + return $user->isImapReady(); } } catch (\Exception $e) { \Log::error($e); } return false; } /** * Email address (login or alias) validation * * @param string $email Email address * @param \App\User $user The account owner * @param bool $is_alias The email is an alias * * @return string Error message on validation error */ public static function validateEmail( string $email, \App\User $user, bool $is_alias = false ): ?string { $attribute = $is_alias ? 'alias' : 'email'; if (strpos($email, '@') === false) { return \trans('validation.entryinvalid', ['attribute' => $attribute]); } list($login, $domain) = explode('@', Str::lower($email)); if (strlen($login) === 0 || strlen($domain) === 0) { return \trans('validation.entryinvalid', ['attribute' => $attribute]); } // Check if domain exists $domain = Domain::where('namespace', $domain)->first(); if (empty($domain)) { return \trans('validation.domaininvalid'); } // Validate login part alone $v = Validator::make( [$attribute => $login], [$attribute => ['required', new UserEmailLocal(!$domain->isPublic())]] ); if ($v->fails()) { return $v->errors()->toArray()[$attribute][0]; } // Check if it is one of domains available to the user $domains = \collect($user->domains())->pluck('namespace')->all(); if (!in_array($domain->namespace, $domains)) { return \trans('validation.entryexists', ['attribute' => 'domain']); } // Check if a user/alias with specified address already exists // Allow assigning the same alias to a user in the same group account, // but only for non-public domains // Allow an alias in a custom domain to an address that was a user before if ($exists = User::emailExists($email, true, $alias_exists, $is_alias && !$domain->isPublic())) { if ( !$is_alias || !$alias_exists || $domain->isPublic() || $exists->wallet()->user_id != $user->id ) { return \trans('validation.entryexists', ['attribute' => $attribute]); } } return null; } } diff --git a/src/database/seeds/local/UserSeeder.php b/src/database/seeds/local/UserSeeder.php index 9d3df7ee..938cae7a 100644 --- a/src/database/seeds/local/UserSeeder.php +++ b/src/database/seeds/local/UserSeeder.php @@ -1,142 +1,142 @@ 'kolab.org', 'status' => Domain::STATUS_NEW + Domain::STATUS_ACTIVE + Domain::STATUS_CONFIRMED + Domain::STATUS_VERIFIED, 'type' => Domain::TYPE_EXTERNAL ] ); $john = User::create( [ 'email' => 'john@kolab.org', 'password' => 'simple123', ] ); $john->setSettings( [ 'first_name' => 'John', 'last_name' => 'Doe', 'currency' => 'USD', 'country' => 'US', 'billing_address' => "601 13th Street NW\nSuite 900 South\nWashington, DC 20005", 'external_email' => 'john.doe.external@gmail.com', 'organization' => 'Kolab Developers', 'phone' => '+1 509-248-1111', ] ); $john->setAliases(['john.doe@kolab.org']); $wallet = $john->wallets->first(); $package_domain = \App\Package::where('title', 'domain-hosting')->first(); $package_kolab = \App\Package::where('title', 'kolab')->first(); $package_lite = \App\Package::where('title', 'lite')->first(); $domain->assignPackage($package_domain, $john); $john->assignPackage($package_kolab); $jack = User::create( [ 'email' => 'jack@kolab.org', 'password' => 'simple123', ] ); $jack->setSettings( [ 'first_name' => 'Jack', 'last_name' => 'Daniels', 'currency' => 'USD', 'country' => 'US' ] ); $jack->setAliases(['jack.daniels@kolab.org']); $john->assignPackage($package_kolab, $jack); foreach ($john->entitlements as $entitlement) { $entitlement->created_at = Carbon::now()->subMonthsWithoutOverflow(1); $entitlement->updated_at = Carbon::now()->subMonthsWithoutOverflow(1); $entitlement->save(); } $ned = User::create( [ 'email' => 'ned@kolab.org', 'password' => 'simple123', ] ); $ned->setSettings( [ 'first_name' => 'Edward', 'last_name' => 'Flanders', 'currency' => 'USD', 'country' => 'US' ] ); $john->assignPackage($package_kolab, $ned); $ned->assignSku(\App\Sku::where('title', 'activesync')->first(), 1); // Ned is a controller on Jack's wallet $john->wallets()->first()->addController($ned); // Ned is also our 2FA test user $sku2fa = Sku::firstOrCreate(['title' => '2fa']); $ned->assignSku($sku2fa); SecondFactor::seed('ned@kolab.org'); $joe = User::create( [ 'email' => 'joe@kolab.org', 'password' => 'simple123', ] ); $john->assignPackage($package_lite, $joe); $joe->setAliases(['joe.monster@kolab.org']); - factory(User::class, 10)->create(); + // factory(User::class, 10)->create(); $jeroen = User::create( [ 'email' => 'jeroen@jeroen.jeroen', 'password' => 'jeroen', ] ); $jeroen->role = 'admin'; $jeroen->save(); } } diff --git a/src/tests/Browser/DomainTest.php b/src/tests/Browser/DomainTest.php index 2462458a..2322bc5f 100644 --- a/src/tests/Browser/DomainTest.php +++ b/src/tests/Browser/DomainTest.php @@ -1,163 +1,158 @@ browse(function ($browser) { $browser->visit('/domain/123')->on(new Home()); }); } /** * Test domain info page (non-existing domain id) */ public function testDomainInfo404(): void { $this->browse(function ($browser) { // FIXME: I couldn't make loginAs() method working // Note: Here we're also testing that unauthenticated request // is passed to logon form and then "redirected" to the requested page $browser->visit('/domain/123') ->on(new Home()) ->submitLogon('john@kolab.org', 'simple123') ->assertErrorPage(404); }); } /** * Test domain info page (existing domain) * * @depends testDomainInfo404 */ public function testDomainInfo(): void { $this->browse(function ($browser) { // Unconfirmed domain $domain = Domain::where('namespace', 'kolab.org')->first(); if ($domain->isConfirmed()) { $domain->status ^= Domain::STATUS_CONFIRMED; $domain->save(); } $browser->visit('/domain/' . $domain->id) ->on(new DomainInfo()) ->whenAvailable('@verify', function ($browser) use ($domain) { $browser->assertSeeIn('pre', $domain->namespace) ->assertSeeIn('pre', $domain->hash()) ->click('button') - ->assertToast(Toast::TYPE_ERROR, 'Domain ownership verification failed.'); - - // Make sure the domain is confirmed now - $domain->status |= Domain::STATUS_CONFIRMED; - $domain->save(); - - $browser->click('button') ->assertToast(Toast::TYPE_SUCCESS, 'Domain verified successfully.'); + + // TODO: Test scenario when a domain confirmation failed }) ->whenAvailable('@config', function ($browser) use ($domain) { $browser->assertSeeIn('pre', $domain->namespace); }) ->assertMissing('@verify'); // Check that confirmed domain page contains only the config box $browser->visit('/domain/' . $domain->id) ->on(new DomainInfo()) ->assertMissing('@verify') ->assertPresent('@config'); }); } /** * Test domains list page (unauthenticated) */ public function testDomainListUnauth(): void { // Test that the page requires authentication $this->browse(function ($browser) { $browser->visit('/logout') ->visit('/domains') ->on(new Home()); }); } /** * Test domains list page * * @depends testDomainListUnauth */ public function testDomainList(): void { $this->browse(function ($browser) { // Login the user $browser->visit('/login') ->on(new Home()) ->submitLogon('john@kolab.org', 'simple123', true) // On dashboard click the "Domains" link ->on(new Dashboard()) ->assertSeeIn('@links a.link-domains', 'Domains') ->click('@links a.link-domains') // On Domains List page click the domain entry ->on(new DomainList()) ->waitFor('@table tbody tr') ->assertVisible('@table tbody tr:first-child td:first-child svg.fa-globe.text-success') ->assertText('@table tbody tr:first-child td:first-child svg title', 'Active') ->assertSeeIn('@table tbody tr:first-child td:first-child', 'kolab.org') ->assertMissing('@table tfoot') ->click('@table tbody tr:first-child td:first-child a') // On Domain Info page verify that's the clicked domain ->on(new DomainInfo()) ->whenAvailable('@config', function ($browser) { $browser->assertSeeIn('pre', 'kolab.org'); }); }); // TODO: Test domains list acting as Ned (John's "delegatee") } /** * Test domains list page (user with no domains) */ public function testDomainListEmpty(): void { $this->browse(function ($browser) { // Login the user $browser->visit('/login') ->on(new Home()) ->submitLogon('jack@kolab.org', 'simple123', true) ->on(new Dashboard()) ->assertVisible('@links a.link-profile') ->assertMissing('@links a.link-domains') ->assertMissing('@links a.link-users') ->assertMissing('@links a.link-wallet'); /* // On dashboard click the "Domains" link ->assertSeeIn('@links a.link-domains', 'Domains') ->click('@links a.link-domains') // On Domains List page click the domain entry ->on(new DomainList()) ->assertMissing('@table tbody') ->assertSeeIn('tfoot td', 'There are no domains in this account.'); */ }); } } diff --git a/src/tests/Functional/Methods/DomainTest.php b/src/tests/Functional/Methods/DomainTest.php index 341d55f7..b52ea0b3 100644 --- a/src/tests/Functional/Methods/DomainTest.php +++ b/src/tests/Functional/Methods/DomainTest.php @@ -1,113 +1,115 @@ domain = $this->getTestDomain( 'test.domain', [ 'status' => \App\Domain::STATUS_CONFIRMED | \App\Domain::STATUS_VERIFIED, 'type' => \App\Domain::TYPE_EXTERNAL ] ); } public function tearDown(): void { $this->deleteTestDomain('test.domain'); parent::tearDown(); } /** * Verify we can suspend an active domain. */ public function testSuspendForActiveDomain() { Queue::fake(); $this->domain->status |= \App\Domain::STATUS_ACTIVE; $this->assertFalse($this->domain->isSuspended()); $this->assertTrue($this->domain->isActive()); $this->domain->suspend(); $this->assertTrue($this->domain->isSuspended()); $this->assertFalse($this->domain->isActive()); } /** * Verify we can unsuspend a suspended domain */ public function testUnsuspendForSuspendedDomain() { Queue::fake(); $this->domain->status |= \App\Domain::STATUS_SUSPENDED; $this->assertTrue($this->domain->isSuspended()); $this->assertFalse($this->domain->isActive()); $this->domain->unsuspend(); $this->assertFalse($this->domain->isSuspended()); $this->assertTrue($this->domain->isActive()); } /** * Verify we can unsuspend a suspended domain that wasn't confirmed */ public function testUnsuspendForSuspendedUnconfirmedDomain() { Queue::fake(); $this->domain->status = \App\Domain::STATUS_NEW | \App\Domain::STATUS_SUSPENDED; $this->assertTrue($this->domain->isNew()); $this->assertTrue($this->domain->isSuspended()); $this->assertFalse($this->domain->isActive()); $this->assertFalse($this->domain->isConfirmed()); $this->assertFalse($this->domain->isVerified()); $this->domain->unsuspend(); $this->assertTrue($this->domain->isNew()); $this->assertFalse($this->domain->isSuspended()); $this->assertFalse($this->domain->isActive()); $this->assertFalse($this->domain->isConfirmed()); $this->assertFalse($this->domain->isVerified()); } /** * Verify we can unsuspend a suspended domain that was verified but not confirmed */ public function testUnsuspendForSuspendedVerifiedUnconfirmedDomain() { Queue::fake(); $this->domain->status = \App\Domain::STATUS_NEW | \App\Domain::STATUS_SUSPENDED | \App\Domain::STATUS_VERIFIED; $this->assertTrue($this->domain->isNew()); $this->assertTrue($this->domain->isSuspended()); $this->assertFalse($this->domain->isActive()); $this->assertFalse($this->domain->isConfirmed()); $this->assertTrue($this->domain->isVerified()); $this->domain->unsuspend(); $this->assertTrue($this->domain->isNew()); $this->assertFalse($this->domain->isSuspended()); $this->assertFalse($this->domain->isActive()); $this->assertFalse($this->domain->isConfirmed()); $this->assertTrue($this->domain->isVerified()); } } diff --git a/src/tests/Unit/Methods/DomainTest.php b/src/tests/Unit/Methods/DomainTest.php index 5300bc40..0452aba1 100644 --- a/src/tests/Unit/Methods/DomainTest.php +++ b/src/tests/Unit/Methods/DomainTest.php @@ -1,159 +1,161 @@ domain = new \App\Domain(); } /** * Test lower-casing namespace attribute. */ public function testSetNamespaceAttributeLowercases() { $this->domain = new \App\Domain(); $this->domain->namespace = 'UPPERCASE'; - $this->assertTrue($this->domain->namespace === 'uppercase'); + $this->assertTrue($this->domain->namespace === 'uppercase'); // @phpstan-ignore-line } /** * Test setting the status to something invalid */ public function testSetStatusAttributeInvalid() { $this->expectException(\Exception::class); $this->domain->status = 123456; } /** * Test public domain. */ public function testSetStatusAttributeOnPublicDomain() { $this->domain->{'type'} = \App\Domain::TYPE_PUBLIC; $this->domain->status = 115; $this->assertTrue($this->domain->status == 115); } /** * Test status mutations */ public function testSetStatusAttributeActiveMakesForNotNew() { $this->domain->status = \App\Domain::STATUS_NEW; $this->assertTrue($this->domain->isNew()); $this->assertFalse($this->domain->isActive()); $this->domain->status |= \App\Domain::STATUS_ACTIVE; $this->assertFalse($this->domain->isNew()); $this->assertTrue($this->domain->isActive()); } /** * Verify setting confirmed sets verified. */ public function testSetStatusAttributeConfirmedMakesForVerfied() { $this->domain->status = \App\Domain::STATUS_CONFIRMED; $this->assertTrue($this->domain->isConfirmed()); $this->assertTrue($this->domain->isVerified()); } /** * Verify setting confirmed sets active. */ public function testSetStatusAttributeConfirmedMakesForActive() { $this->domain->status = \App\Domain::STATUS_CONFIRMED; $this->assertTrue($this->domain->isConfirmed()); $this->assertTrue($this->domain->isActive()); } /** * Verify setting deleted drops active. */ public function testSetStatusAttributeDeletedVoidsActive() { $this->domain->status = \App\Domain::STATUS_ACTIVE; $this->assertTrue($this->domain->isActive()); $this->assertFalse($this->domain->isNew()); $this->assertFalse($this->domain->isDeleted()); $this->domain->status |= \App\Domain::STATUS_DELETED; $this->assertFalse($this->domain->isActive()); $this->assertFalse($this->domain->isNew()); $this->assertTrue($this->domain->isDeleted()); } /** * Verify setting suspended drops active. */ public function testSetStatusAttributeSuspendedVoidsActive() { $this->domain->status = \App\Domain::STATUS_ACTIVE; $this->assertTrue($this->domain->isActive()); $this->assertFalse($this->domain->isSuspended()); $this->domain->status |= \App\Domain::STATUS_SUSPENDED; $this->assertFalse($this->domain->isActive()); $this->assertTrue($this->domain->isSuspended()); } /** * Verify we can suspend a suspended domain without disaster. * * This doesn't change anything to trigger a save. */ public function testSuspendForSuspendedDomain() { $this->domain->status = \App\Domain::STATUS_ACTIVE; $this->domain->status |= \App\Domain::STATUS_SUSPENDED; $this->assertTrue($this->domain->isSuspended()); $this->assertFalse($this->domain->isActive()); $this->domain->suspend(); $this->assertTrue($this->domain->isSuspended()); $this->assertFalse($this->domain->isActive()); } /** * Verify we can unsuspend an active (unsuspended) domain * * This doesn't change anything to trigger a save. */ public function testUnsuspendForActiveDomain() { $this->domain->status = \App\Domain::STATUS_ACTIVE; $this->assertFalse($this->domain->isSuspended()); $this->assertTrue($this->domain->isActive()); $this->domain->unsuspend(); $this->assertFalse($this->domain->isSuspended()); $this->assertTrue($this->domain->isActive()); } }