*/ private array $ctx; protected function setUp(): void { parent::setUp(); Functions\when('wp_unslash')->alias(static fn ($v) => $v); Functions\when('sanitize_text_field')->alias(static fn ($v) => $v); Functions\when('sanitize_email')->alias(static fn ($v) => $v); Functions\when('current_time')->justReturn('2024-01-01 00:00:00'); $invites = Mockery::mock(InviteRepository::class); $policies = Mockery::mock(PolicyRepository::class); $policies->shouldReceive('findForScope')->andReturn([])->byDefault(); $this->ctx = [ 'invites' => $invites, 'policies' => $policies, 'mailer' => Mockery::mock(RegistrationMailer::class), 'settings' => Mockery::mock(StudioSettings::class), ]; $this->ctx['page'] = new RegistrationPage( $invites, $policies, Mockery::mock(PolicyVersionRepository::class), Mockery::mock(AcceptanceRepository::class), $this->ctx['settings'], $this->ctx['mailer'], ); $_POST = []; } protected function tearDown(): void { $_POST = []; $_GET = []; $_REQUEST = []; parent::tearDown(); } /** Stub everything render() needs on a logged-out GET request. */ private function stubRenderContext(): void { Functions\when('is_user_logged_in')->justReturn(false); Functions\when('sanitize_key')->alias(static fn ($v) => strtolower((string) $v)); Functions\when('wp_login_url')->justReturn('http://home.test/wp-login.php'); Functions\when('wp_nonce_field')->justReturn(''); $this->ctx['settings']->shouldReceive('openRegistrationEnabled')->andReturn(true); } private function submit(?Invite $invite, bool $open): string { $method = new \ReflectionMethod(RegistrationPage::class, 'handleSubmit'); return (string) $method->invoke($this->ctx['page'], $invite, $open); } public function testInviteBranchCreatesAndLogsInTheStudent(): void { $_POST = [ 'password' => 'password123', 'display_name' => 'Ada' ]; Functions\when('email_exists')->justReturn(false); Functions\when('wp_insert_user')->justReturn(42); Functions\when('is_wp_error')->justReturn(false); $this->ctx['invites']->shouldReceive('markAccepted')->once(); Functions\expect('wp_set_current_user')->once()->with(42); Functions\expect('wp_set_auth_cookie')->once()->with(42); $invite = new Invite(email: 'a@b.test', token: 'hash'); self::assertSame('invite', $this->submit($invite, false)); } public function testOpenBranchCreatesPendingWithoutLoginAndEmails(): void { $_POST = [ 'password' => 'password123', 'display_name' => 'Ada', 'email' => 'new@b.test' ]; Functions\when('is_email')->justReturn(true); Functions\when('email_exists')->justReturn(false); Functions\when('wp_insert_user')->justReturn(42); Functions\when('is_wp_error')->justReturn(false); // markPending internals Functions\when('wp_generate_password')->justReturn('rawtok'); Functions\when('update_user_meta')->justReturn(true); // confirmUrl internals Functions\when('get_option')->justReturn(0); Functions\when('home_url')->justReturn('http://home.test/'); Functions\when('add_query_arg')->alias(static fn (string $k, string $v, string $u): string => $u . '?' . $k . '=' . $v); $user = Mockery::mock(\WP_User::class); Functions\when('get_user_by')->justReturn($user); $this->ctx['mailer']->shouldReceive('sendConfirmation')->once()->with($user, Mockery::type('string')); // No invite acceptance and no auto-login in the open branch. $this->ctx['invites']->shouldReceive('markAccepted')->never(); Functions\expect('wp_set_auth_cookie')->never(); self::assertSame('confirm', $this->submit(null, true)); } public function testClosedModeWithoutInviteReturnsError(): void { $result = $this->submit(null, false); self::assertNotSame('invite', $result); self::assertNotSame('confirm', $result); self::assertNotSame('', $result); } public function testConfirmedEmailShowsSignInLinkInsteadOfForm(): void { $_GET = ['us_confirmed' => '1']; $this->stubRenderContext(); $html = $this->ctx['page']->render([]); self::assertStringContainsString('us-success', $html); self::assertStringContainsString('http://home.test/wp-login.php', $html); self::assertStringNotContainsString('