CI / Tests (PHP 8.2) (pull_request) Successful in 47s
CI / Tests (PHP 8.1) (pull_request) Successful in 58s
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 2m53s
CI / Coding Standards (pull_request) Successful in 2m55s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m37s
CI / Build Plugin Zip (pull_request) Skipped
The registration page's already-logged-in branch returned a bare sentence with nowhere to go, leaving the visitor to find their own way to their account. The invited-student branch a few lines above already built exactly the link that was missing. Extract that into continueLink() and use it for both logged-in outcomes. There is deliberately still no wp_login_url() fallback: sending someone already signed in to the login screen is the same dead end with extra steps, so with no page configured there is no link. Both messages now carry the us-register-form wrapper and enqueue the plugin stylesheet, which the invite branch emitted markup for but never loaded. Closes #131
585 lines
25 KiB
PHP
585 lines
25 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace Unsupervised\Schedular\Tests\Unit\Auth;
|
|
|
|
use Brain\Monkey\Functions;
|
|
use Mockery;
|
|
use Unsupervised\Schedular\Auth\Invite;
|
|
use Unsupervised\Schedular\Auth\InviteRepository;
|
|
use Unsupervised\Schedular\Auth\RegistrationMailer;
|
|
use Unsupervised\Schedular\Auth\RegistrationPage;
|
|
use Unsupervised\Schedular\GroupClass\GroupAccessRepository;
|
|
use Unsupervised\Schedular\Payment\StudioSettings;
|
|
use Unsupervised\Schedular\Policy\AcceptanceRepository;
|
|
use Unsupervised\Schedular\Policy\Policy;
|
|
use Unsupervised\Schedular\Policy\PolicyRepository;
|
|
use Unsupervised\Schedular\Policy\PolicyVersion;
|
|
use Unsupervised\Schedular\Policy\PolicyVersionRepository;
|
|
use Unsupervised\Schedular\Registration\Answer;
|
|
use Unsupervised\Schedular\Registration\AnswerRepository;
|
|
use Unsupervised\Schedular\Registration\Question;
|
|
use Unsupervised\Schedular\Registration\QuestionRepository;
|
|
use Unsupervised\Schedular\Tests\Unit\TestCase;
|
|
|
|
class RegistrationPageTest extends TestCase
|
|
{
|
|
/** @var array<string, mixed> */
|
|
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_textarea_field')->alias(static fn ($v) => $v);
|
|
Functions\when('sanitize_email')->alias(static fn ($v) => $v);
|
|
Functions\when('absint')->alias(static fn ($v) => (int) $v);
|
|
Functions\when('current_time')->justReturn('2024-01-01 00:00:00');
|
|
Functions\when('wp_enqueue_style')->justReturn(null);
|
|
Functions\when('wp_enqueue_script')->justReturn(null);
|
|
|
|
$invites = Mockery::mock(InviteRepository::class);
|
|
$policies = Mockery::mock(PolicyRepository::class);
|
|
$questions = Mockery::mock(QuestionRepository::class);
|
|
$answers = Mockery::mock(AnswerRepository::class);
|
|
$policies->shouldReceive('findForScope')->andReturn([])->byDefault();
|
|
$questions->shouldReceive('findByScope')->andReturn([])->byDefault();
|
|
$answers->shouldReceive('insert')->andReturn(1)->byDefault();
|
|
|
|
$access = Mockery::mock(GroupAccessRepository::class);
|
|
$access->shouldReceive('linkStudentByEmail')->andReturn(true)->byDefault();
|
|
|
|
$this->ctx = [
|
|
'invites' => $invites,
|
|
'policies' => $policies,
|
|
'questions' => $questions,
|
|
'answers' => $answers,
|
|
'access' => $access,
|
|
'mailer' => Mockery::mock(RegistrationMailer::class),
|
|
'settings' => Mockery::mock(StudioSettings::class),
|
|
];
|
|
|
|
$this->ctx['versions'] = Mockery::mock(PolicyVersionRepository::class);
|
|
$this->ctx['acceptances'] = Mockery::mock(AcceptanceRepository::class);
|
|
|
|
$this->ctx['page'] = new RegistrationPage(
|
|
$invites,
|
|
$policies,
|
|
$this->ctx['versions'],
|
|
$this->ctx['acceptances'],
|
|
$this->ctx['settings'],
|
|
$this->ctx['mailer'],
|
|
$questions,
|
|
$answers,
|
|
$access,
|
|
);
|
|
|
|
$_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: '[email protected]', token: 'hash');
|
|
|
|
self::assertSame('invite', $this->submit($invite, false));
|
|
}
|
|
|
|
public function testInviteAcceptanceLinksClassGrantForTheEmail(): 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\when('wp_set_current_user')->justReturn(null);
|
|
Functions\when('wp_set_auth_cookie')->justReturn(null);
|
|
|
|
// A personal invite tied to a class grant links the new account to it.
|
|
$this->ctx['access']->shouldReceive('linkStudentByEmail')->once()->with('[email protected]', 42)->andReturn(true);
|
|
|
|
$invite = new Invite(email: '[email protected]', token: 'hash', offeringId: 8);
|
|
|
|
self::assertSame('invite', $this->submit($invite, false));
|
|
}
|
|
|
|
public function testOpenBranchCreatesPendingWithoutLoginAndEmails(): void
|
|
{
|
|
$_POST = [ 'password' => 'password123', 'display_name' => 'Ada', 'email' => '[email protected]' ];
|
|
|
|
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 testGroupInviteCreatesPendingAutoApproveAccountEvenWhenClosed(): void
|
|
{
|
|
$_POST = [ 'password' => 'password123', 'display_name' => 'Ada', 'email' => '[email protected]' ];
|
|
|
|
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);
|
|
Functions\when('wp_generate_password')->justReturn('rawtok');
|
|
// 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);
|
|
|
|
// The auto-approve marker must be set alongside the pending metas.
|
|
$metas = [];
|
|
Functions\when('update_user_meta')->alias(static function (int $id, string $key, $value) use (&$metas): bool {
|
|
$metas[$key] = $value;
|
|
return true;
|
|
});
|
|
|
|
$user = Mockery::mock(\WP_User::class);
|
|
Functions\when('get_user_by')->justReturn($user);
|
|
|
|
$this->ctx['mailer']->shouldReceive('sendConfirmation')->once()->with($user, Mockery::type('string'));
|
|
// The link is multi-use: never marked accepted, and no auto-login.
|
|
$this->ctx['invites']->shouldReceive('markAccepted')->never();
|
|
Functions\expect('wp_set_auth_cookie')->never();
|
|
|
|
$invite = new Invite(
|
|
email: '',
|
|
token: 'hash',
|
|
createdAt: '2024-01-01 00:00:00',
|
|
kind: Invite::KIND_GROUP,
|
|
expiresAt: '2024-02-01 23:59:59',
|
|
id: 9
|
|
);
|
|
|
|
// Registration mode is invite-only (open = false): the group link still works.
|
|
self::assertSame('confirm_group', $this->submit($invite, false));
|
|
self::assertSame('1', $metas['us_auto_approve'] ?? null);
|
|
}
|
|
|
|
public function testGroupInviteRendersEditableEmailField(): void
|
|
{
|
|
$_REQUEST = ['us_invite' => 'raw-token'];
|
|
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('');
|
|
// Invite-only mode: only the group link grants access to the form.
|
|
$this->ctx['settings']->shouldReceive('openRegistrationEnabled')->andReturn(false);
|
|
|
|
$invite = new Invite(
|
|
email: '',
|
|
token: 'hash',
|
|
createdAt: '2024-01-01 00:00:00',
|
|
kind: Invite::KIND_GROUP,
|
|
expiresAt: '2024-02-01 23:59:59',
|
|
id: 9
|
|
);
|
|
$this->ctx['invites']->shouldReceive('findByToken')
|
|
->once()
|
|
->with(Invite::hashToken('raw-token'))
|
|
->andReturn($invite);
|
|
|
|
$html = $this->ctx['page']->render([]);
|
|
|
|
self::assertStringContainsString('<form', $html);
|
|
self::assertStringContainsString('name="email"', $html);
|
|
self::assertStringNotContainsString('readonly', $html);
|
|
}
|
|
|
|
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('<form', $html);
|
|
}
|
|
|
|
public function testConfirmedSignInLinkUsesConfiguredLoginPage(): void
|
|
{
|
|
$_GET = ['us_confirmed' => '1'];
|
|
$this->stubRenderContext();
|
|
Functions\expect('get_permalink')->once()->with(7)->andReturn('http://home.test/sign-in/');
|
|
|
|
$html = $this->ctx['page']->render(['login_page_id' => 7]);
|
|
|
|
self::assertStringContainsString('http://home.test/sign-in/', $html);
|
|
self::assertStringNotContainsString('wp-login.php', $html);
|
|
}
|
|
|
|
public function testRegistrationFormRendersWithoutConfirmationFlag(): void
|
|
{
|
|
$this->stubRenderContext();
|
|
|
|
$html = $this->ctx['page']->render([]);
|
|
|
|
self::assertStringContainsString('<form', $html);
|
|
self::assertStringNotContainsString('us-success', $html);
|
|
}
|
|
|
|
public function testExpiredConfirmationStillShowsForm(): void
|
|
{
|
|
$_GET = ['us_confirmed' => 'expired'];
|
|
$this->stubRenderContext();
|
|
|
|
$html = $this->ctx['page']->render([]);
|
|
|
|
self::assertStringContainsString('us-error', $html);
|
|
self::assertStringContainsString('<form', $html);
|
|
}
|
|
|
|
public function testValidInviteRendersEmailPrefilledAndLocked(): void
|
|
{
|
|
$_REQUEST = ['us_invite' => 'raw-token'];
|
|
$this->stubRenderContext();
|
|
|
|
$invite = new Invite(email: '[email protected]', token: 'hash', createdAt: '2024-01-01 00:00:00', id: 9);
|
|
$this->ctx['invites']->shouldReceive('findByToken')
|
|
->once()
|
|
->with(Invite::hashToken('raw-token'))
|
|
->andReturn($invite);
|
|
|
|
$html = $this->ctx['page']->render([]);
|
|
|
|
// The invited address is shown read-only; there is no editable email input.
|
|
self::assertStringContainsString('value="[email protected]" readonly', $html);
|
|
self::assertStringNotContainsString('name="email"', $html);
|
|
}
|
|
|
|
public function testStaleInviteWithOpenRegistrationShowsEditableEmail(): void
|
|
{
|
|
$_REQUEST = ['us_invite' => 'raw-token'];
|
|
$this->stubRenderContext();
|
|
|
|
// Already-redeemed invite: not acceptable, so the open-registration form
|
|
// must collect an email rather than showing the stale locked address.
|
|
$invite = new Invite(
|
|
email: '[email protected]',
|
|
token: 'hash',
|
|
status: Invite::STATUS_ACCEPTED,
|
|
createdAt: '2024-01-01 00:00:00',
|
|
id: 9
|
|
);
|
|
$this->ctx['invites']->shouldReceive('findByToken')->once()->andReturn($invite);
|
|
|
|
$html = $this->ctx['page']->render([]);
|
|
|
|
self::assertStringContainsString('name="email"', $html);
|
|
self::assertStringNotContainsString('[email protected]', $html);
|
|
self::assertStringNotContainsString('readonly', $html);
|
|
}
|
|
|
|
public function testRejectsWhenARequiredPolicyIsUnaccepted(): void
|
|
{
|
|
$_POST = [ 'password' => 'password123', 'display_name' => 'Ada', 'email' => '[email protected]' ];
|
|
|
|
Functions\when('is_email')->justReturn(true);
|
|
|
|
$policy = new Policy(title: 'Terms', slug: 'terms', currentVersionId: 3);
|
|
$version = new PolicyVersion(policyId: 1, versionNumber: 1, status: PolicyVersion::STATUS_PUBLISHED, id: 3);
|
|
|
|
$this->ctx['policies']->shouldReceive('findForScope')->andReturn([ $policy ]);
|
|
$versionRepo = (new \ReflectionProperty(RegistrationPage::class, 'versions'))->getValue($this->ctx['page']);
|
|
$versionRepo->shouldReceive('findById')->with(3)->andReturn($version);
|
|
|
|
$result = $this->submit(null, true);
|
|
|
|
self::assertNotSame('confirm', $result);
|
|
self::assertNotSame('', $result);
|
|
}
|
|
|
|
public function testRejectsWhenARequiredAccountQuestionIsUnanswered(): void
|
|
{
|
|
$_POST = [ 'password' => 'password123', 'display_name' => 'Ada', 'email' => '[email protected]' ];
|
|
|
|
Functions\when('is_email')->justReturn(true);
|
|
|
|
$question = new Question(null, 'Emergency contact', isRequired: true, scope: Question::SCOPE_ACCOUNT, id: 5);
|
|
$this->ctx['questions']->shouldReceive('findByScope')->with(Question::SCOPE_ACCOUNT, Mockery::any())->andReturn([$question]);
|
|
|
|
// A missing required answer must be caught before any account is created.
|
|
Functions\expect('wp_insert_user')->never();
|
|
$this->ctx['answers']->shouldReceive('insert')->never();
|
|
|
|
$result = $this->submit(null, true);
|
|
|
|
self::assertNotSame('confirm', $result);
|
|
self::assertNotSame('', $result);
|
|
}
|
|
|
|
public function testRecordsAccountAnswersOnSuccess(): void
|
|
{
|
|
$_POST = [
|
|
'password' => 'password123',
|
|
'display_name' => 'Ada',
|
|
'us_answers' => [ '5' => 'By a friend' ],
|
|
];
|
|
|
|
Functions\when('email_exists')->justReturn(false);
|
|
Functions\when('wp_insert_user')->justReturn(42);
|
|
Functions\when('is_wp_error')->justReturn(false);
|
|
Functions\expect('wp_set_current_user')->once();
|
|
Functions\expect('wp_set_auth_cookie')->once();
|
|
$this->ctx['invites']->shouldReceive('markAccepted')->once();
|
|
|
|
$question = new Question(null, 'How did you hear about us?', isRequired: true, scope: Question::SCOPE_ACCOUNT, id: 5);
|
|
$this->ctx['questions']->shouldReceive('findByScope')->with(Question::SCOPE_ACCOUNT, Mockery::any())->andReturn([$question]);
|
|
|
|
// The answer is written against the new user (account scope).
|
|
$this->ctx['answers']->shouldReceive('insert')
|
|
->once()
|
|
->with(Mockery::on(static function (Answer $answer): bool {
|
|
return $answer->registrationType === Answer::REG_ACCOUNT
|
|
&& $answer->registrationId === 42
|
|
&& $answer->studentId === 42
|
|
&& $answer->questionId === 5
|
|
&& $answer->answerValue === 'By a friend';
|
|
}))
|
|
->andReturn(1);
|
|
|
|
$invite = new Invite(email: '[email protected]', token: 'hash');
|
|
|
|
self::assertSame('invite', $this->submit($invite, false));
|
|
}
|
|
|
|
public function testMaybeHandleSubmitLogsInInviteAndRedirects(): void
|
|
{
|
|
$_POST = [ 'us_register' => '1', 'password' => 'password123', 'display_name' => 'Ada' ];
|
|
$_REQUEST = [ 'us_invite' => 'raw-token' ];
|
|
|
|
Functions\when('is_user_logged_in')->justReturn(false);
|
|
Functions\when('check_admin_referer')->justReturn(true);
|
|
Functions\when('email_exists')->justReturn(false);
|
|
Functions\when('wp_insert_user')->justReturn(42);
|
|
Functions\when('is_wp_error')->justReturn(false);
|
|
Functions\when('get_permalink')->justReturn('http://home.test/register/');
|
|
Functions\when('add_query_arg')->alias(static fn (string $k, string $v, string $u): string => $u . '?' . $k . '=' . $v);
|
|
|
|
// The cookie must be set here — during template_redirect, before output —
|
|
// which is the whole point of processing the submit outside render().
|
|
Functions\expect('wp_set_current_user')->once()->with(42);
|
|
Functions\expect('wp_set_auth_cookie')->once()->with(42);
|
|
|
|
$invite = new Invite(email: '[email protected]', token: 'hash', createdAt: '2024-01-01 00:00:00', id: 9);
|
|
$this->ctx['invites']->shouldReceive('findByToken')->once()->andReturn($invite);
|
|
$this->ctx['invites']->shouldReceive('markAccepted')->once();
|
|
$this->ctx['settings']->shouldReceive('openRegistrationEnabled')->andReturn(false);
|
|
|
|
$page = Mockery::mock(
|
|
RegistrationPage::class,
|
|
[
|
|
$this->ctx['invites'],
|
|
$this->ctx['policies'],
|
|
$this->ctx['versions'],
|
|
$this->ctx['acceptances'],
|
|
$this->ctx['settings'],
|
|
$this->ctx['mailer'],
|
|
$this->ctx['questions'],
|
|
$this->ctx['answers'],
|
|
$this->ctx['access'],
|
|
]
|
|
)->makePartial()->shouldAllowMockingProtectedMethods();
|
|
|
|
$captured = '';
|
|
$page->shouldReceive('redirect')->once()->with(Mockery::on(static function (string $url) use (&$captured): bool {
|
|
$captured = $url;
|
|
return true;
|
|
}));
|
|
|
|
$page->maybeHandleSubmit();
|
|
|
|
self::assertStringContainsString('us_registered=invite', $captured);
|
|
}
|
|
|
|
public function testMaybeHandleSubmitStoresValidationErrorWithoutRedirecting(): void
|
|
{
|
|
// Too-short password: handleSubmit returns an error and no redirect fires.
|
|
$_POST = [ 'us_register' => '1', 'password' => 'short', 'display_name' => 'Ada' ];
|
|
|
|
Functions\when('is_user_logged_in')->justReturn(false);
|
|
Functions\when('check_admin_referer')->justReturn(true);
|
|
$this->ctx['settings']->shouldReceive('openRegistrationEnabled')->andReturn(true);
|
|
|
|
// A redirect would call exit; reaching the assertion proves none happened.
|
|
$this->ctx['page']->maybeHandleSubmit();
|
|
|
|
$error = (new \ReflectionProperty(RegistrationPage::class, 'submitError'))->getValue($this->ctx['page']);
|
|
self::assertNotSame('', $error);
|
|
}
|
|
|
|
public function testInviteSuccessRedirectShowsLoggedInWelcome(): void
|
|
{
|
|
// After the PRG redirect the student is logged in; the us_registered flag
|
|
// distinguishes a just-completed signup from an already-logged-in visitor.
|
|
$_GET = [ 'us_registered' => 'invite' ];
|
|
Functions\when('is_user_logged_in')->justReturn(true);
|
|
Functions\when('sanitize_key')->alias(static fn ($v) => strtolower((string) $v));
|
|
|
|
$html = $this->ctx['page']->render([]);
|
|
|
|
self::assertStringContainsString('us-success', $html);
|
|
self::assertStringContainsString('now logged in', $html);
|
|
// No page chosen: the sign-in-screen fallback is useless to someone who
|
|
// is already signed in, so no link is offered at all.
|
|
self::assertStringNotContainsString('<a href', $html);
|
|
}
|
|
|
|
public function testInviteSuccessLinksToTheChosenPage(): void
|
|
{
|
|
$_GET = [ 'us_registered' => 'invite' ];
|
|
Functions\when('is_user_logged_in')->justReturn(true);
|
|
Functions\when('sanitize_key')->alias(static fn ($v) => strtolower((string) $v));
|
|
Functions\expect('get_permalink')->once()->with(4)->andReturn('http://home.test/welcome/');
|
|
|
|
$html = $this->ctx['page']->render([ 'loginPageId' => 4 ]);
|
|
|
|
self::assertStringContainsString('now logged in', $html);
|
|
self::assertStringContainsString('href="http://home.test/welcome/"', $html);
|
|
}
|
|
|
|
public function testAlreadyLoggedInVisitorIsLinkedToTheChosenPage(): void
|
|
{
|
|
// No us_registered flag: someone who simply happens to be signed in and
|
|
// lands on the registration page. They still need a way onward.
|
|
Functions\when('is_user_logged_in')->justReturn(true);
|
|
Functions\when('sanitize_key')->alias(static fn ($v) => strtolower((string) $v));
|
|
Functions\expect('get_permalink')->once()->with(4)->andReturn('http://home.test/welcome/');
|
|
|
|
$html = $this->ctx['page']->render([ 'loginPageId' => 4 ]);
|
|
|
|
self::assertStringContainsString('already have an account', $html);
|
|
self::assertStringContainsString('href="http://home.test/welcome/"', $html);
|
|
// Not the just-registered message — that branch needs its own flag.
|
|
self::assertStringNotContainsString('us-success', $html);
|
|
}
|
|
|
|
public function testAlreadyLoggedInVisitorGetsNoLinkWithoutAChosenPage(): void
|
|
{
|
|
Functions\when('is_user_logged_in')->justReturn(true);
|
|
Functions\when('sanitize_key')->alias(static fn ($v) => strtolower((string) $v));
|
|
Functions\when('get_permalink')->justReturn(false);
|
|
|
|
// A deleted page resolves to false, which must not become a broken link.
|
|
$html = $this->ctx['page']->render([ 'loginPageId' => 4 ]);
|
|
|
|
self::assertStringContainsString('already have an account', $html);
|
|
self::assertStringNotContainsString('<a href', $html);
|
|
|
|
self::assertStringNotContainsString('<a href', $this->ctx['page']->render([]));
|
|
}
|
|
|
|
public function testContinueUrlIsNullWithoutAResolvablePage(): void
|
|
{
|
|
Functions\when('get_permalink')->justReturn(false);
|
|
|
|
self::assertNull($this->ctx['page']->continueUrl(0));
|
|
self::assertNull($this->ctx['page']->continueUrl(4));
|
|
}
|
|
|
|
public function testIsRegistrationCompleteOnlyForFinishedStates(): void
|
|
{
|
|
Functions\when('sanitize_key')->alias(static fn ($v) => strtolower((string) $v));
|
|
|
|
// [ query args, logged in, finished ]
|
|
$cases = [
|
|
'invited student, now logged in' => [['us_registered' => 'invite'], true, true],
|
|
'invited student, not logged in' => [['us_registered' => 'invite'], false, false],
|
|
'email confirmed, ready' => [['us_confirmed' => 'ready'], false, true],
|
|
'email confirmed, pending review' => [['us_confirmed' => '1'], false, true],
|
|
'confirmation link expired' => [['us_confirmed' => 'expired'], false, false],
|
|
'awaiting email confirmation' => [['us_registered' => 'confirm'], false, false],
|
|
'group signup awaiting confirm' => [['us_registered' => 'confirm_group'], false, false],
|
|
'plain page view' => [[], false, false],
|
|
];
|
|
|
|
foreach ($cases as $label => [$get, $loggedIn, $expected]) {
|
|
$_GET = $get;
|
|
Functions\when('is_user_logged_in')->justReturn($loggedIn);
|
|
|
|
self::assertSame($expected, $this->ctx['page']->isRegistrationComplete(), $label);
|
|
}
|
|
}
|
|
|
|
public function testInviteOnlyMessageCanBeCustomised(): void
|
|
{
|
|
// Closed registration and no invite → the invitation-only gate shows.
|
|
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(false);
|
|
|
|
$html = $this->ctx['page']->render([ 'inviteOnlyMessage' => 'Ask the front desk for a link.' ]);
|
|
|
|
self::assertStringContainsString('Ask the front desk for a link.', $html);
|
|
self::assertStringNotContainsString('by invitation only', $html);
|
|
}
|
|
}
|