/** * @dataProvider provideAccountLink * @param StatusValue $preTest * @param array $primaryResponses * @param array $managerResponses */ public function testAccountLink(StatusValue $preTest, array $primaryResponses, array $managerResponses) { $user = \User::newFromName('UTSysop'); $this->initializeManager(); // Set up lots of mocks... $req = $this->getMockForAbstractClass(AuthenticationRequest::class); $req->primary = $primaryResponses; $mocks = []; foreach (['pre', 'primary'] as $key) { $class = ucfirst($key) . 'AuthenticationProvider'; $mocks[$key] = $this->getMockForAbstractClass("MediaWiki\\Auth\\{$class}", [], "Mock{$class}"); $mocks[$key]->expects($this->any())->method('getUniqueId')->will($this->returnValue($key)); for ($i = 2; $i <= 3; $i++) { $mocks[$key . $i] = $this->getMockForAbstractClass("MediaWiki\\Auth\\{$class}", [], "Mock{$class}"); $mocks[$key . $i]->expects($this->any())->method('getUniqueId')->will($this->returnValue($key . $i)); } } $mocks['pre']->expects($this->any())->method('testForAccountLink')->will($this->returnCallback(function ($u) use($user, $preTest) { $this->assertSame($user->getId(), $u->getId()); $this->assertSame($user->getName(), $u->getName()); return $preTest; })); $mocks['pre2']->expects($this->atMost(1))->method('testForAccountLink')->will($this->returnValue(StatusValue::newGood())); $mocks['primary']->expects($this->any())->method('accountCreationType')->will($this->returnValue(PrimaryAuthenticationProvider::TYPE_LINK)); $ct = count($req->primary); $callback = $this->returnCallback(function ($u, $reqs) use($user, $req) { $this->assertSame($user->getId(), $u->getId()); $this->assertSame($user->getName(), $u->getName()); $foundReq = false; foreach ($reqs as $r) { $this->assertSame($user->getName(), $r->username); $foundReq = $foundReq || get_class($r) === get_class($req); } $this->assertTrue($foundReq, '$reqs contains $req'); return array_shift($req->primary); }); $mocks['primary']->expects($this->exactly(min(1, $ct)))->method('beginPrimaryAccountLink')->will($callback); $mocks['primary']->expects($this->exactly(max(0, $ct - 1)))->method('continuePrimaryAccountLink')->will($callback); $abstain = AuthenticationResponse::newAbstain(); $mocks['primary2']->expects($this->any())->method('accountCreationType')->will($this->returnValue(PrimaryAuthenticationProvider::TYPE_LINK)); $mocks['primary2']->expects($this->atMost(1))->method('beginPrimaryAccountLink')->will($this->returnValue($abstain)); $mocks['primary2']->expects($this->never())->method('continuePrimaryAccountLink'); $mocks['primary3']->expects($this->any())->method('accountCreationType')->will($this->returnValue(PrimaryAuthenticationProvider::TYPE_CREATE)); $mocks['primary3']->expects($this->never())->method('beginPrimaryAccountLink'); $mocks['primary3']->expects($this->never())->method('continuePrimaryAccountLink'); $this->preauthMocks = [$mocks['pre'], $mocks['pre2']]; $this->primaryauthMocks = [$mocks['primary3'], $mocks['primary2'], $mocks['primary']]; $this->logger = new \TestLogger(true, function ($message, $level) { return $level === LogLevel::DEBUG ? null : $message; }); $this->initializeManager(true); $constraint = \PHPUnit_Framework_Assert::logicalOr($this->equalTo(AuthenticationResponse::PASS), $this->equalTo(AuthenticationResponse::FAIL)); $providers = array_merge($this->preauthMocks, $this->primaryauthMocks); foreach ($providers as $p) { $p->postCalled = false; $p->expects($this->atMost(1))->method('postAccountLink')->willReturnCallback(function ($user, $response) use($constraint, $p) { $this->assertInstanceOf('User', $user); $this->assertSame('UTSysop', $user->getName()); $this->assertInstanceOf(AuthenticationResponse::class, $response); $this->assertThat($response->status, $constraint); $p->postCalled = $response->status; }); } $first = true; $created = false; $expectLog = []; foreach ($managerResponses as $i => $response) { if ($response instanceof AuthenticationResponse && $response->status === AuthenticationResponse::PASS) { $expectLog[] = [LogLevel::INFO, 'Account linked to {user} by primary']; } $ex = null; try { if ($first) { $ret = $this->manager->beginAccountLink($user, [$req], 'http://localhost/'); } else { $ret = $this->manager->continueAccountLink([$req]); } if ($response instanceof \Exception) { $this->fail('Expected exception not thrown', "Response {$i}"); } } catch (\Exception $ex) { if (!$response instanceof \Exception) { throw $ex; } $this->assertEquals($response->getMessage(), $ex->getMessage(), "Response {$i}, exception"); $this->assertNull($this->request->getSession()->getSecret('AuthManager::accountLinkState'), "Response {$i}, exception, session state"); return; } $this->assertSame('http://localhost/', $req->returnToUrl); $ret->message = $this->message($ret->message); $this->assertEquals($response, $ret, "Response {$i}, response"); if ($response->status === AuthenticationResponse::PASS || $response->status === AuthenticationResponse::FAIL) { $this->assertNull($this->request->getSession()->getSecret('AuthManager::accountLinkState'), "Response {$i}, session state"); foreach ($providers as $p) { $this->assertSame($response->status, $p->postCalled, "Response {$i}, post-auth callback called"); } } else { $this->assertNotNull($this->request->getSession()->getSecret('AuthManager::accountLinkState'), "Response {$i}, session state"); $this->assertEquals($ret->neededRequests, $this->manager->getAuthenticationRequests(AuthManager::ACTION_LINK_CONTINUE), "Response {$i}, continuation check"); foreach ($providers as $p) { $this->assertFalse($p->postCalled, "Response {$i}, post-auth callback not called"); } } $first = false; } $this->assertSame($expectLog, $this->logger->getBuffer()); }