diff --git a/app/helpers/Recodex/RecodexApiHelper.php b/app/helpers/Recodex/RecodexApiHelper.php index e83da5b..8ae944e 100644 --- a/app/helpers/Recodex/RecodexApiHelper.php +++ b/app/helpers/Recodex/RecodexApiHelper.php @@ -4,6 +4,7 @@ use App\Exceptions\ConfigException; use App\Exceptions\RecodexApiException; +use App\Exceptions\InvalidAccessTokenException; use App\Model\Entity\SisScheduleEvent; use App\Model\Entity\User; use Nette; @@ -129,10 +130,16 @@ private function prepareOptions(array $query = [], $body = null, array $headers * Decode and verify JSON body. * @return array|string|int|bool|null decoded JSON response * @throws RecodexApiException + * @throws InvalidAccessTokenException */ private function processJsonBody($response) { $code = $response->getStatusCode(); + if ($code === 401) { // unauthorized, token is probably invalid + Debugger::log("HTTP request to ReCodEx API failed (response $code).", Debugger::DEBUG); + throw new InvalidAccessTokenException("Unauthorized request to ReCodEx API. Token is probably invalid."); + } + if ($code !== 200) { Debugger::log("HTTP request to ReCodEx API failed (response $code).", Debugger::DEBUG); throw new RecodexApiException("HTTP request failed (response $code)."); @@ -154,17 +161,42 @@ private function processJsonBody($response) return $body['payload'] ?? null; } + /** + * Perform an HTTP request and return decoded JSON response. + * @param string $method HTTP method + * @param string $url suffix for the base URL + * @param array $options for GuzzleHttp request + * @return array|string|int|bool|null decoded JSON response + * @throws RecodexApiException + * @throws InvalidAccessTokenException + */ + private function request(string $method, string $url, array $options) + { + try { + $response = $this->client->request($method, $url, $options); + } catch (GuzzleHttp\Exception\ClientException $e) { + if ($e->hasResponse()) { + return $this->processJsonBody($e->getResponse()); + } + throw new RecodexApiException("HTTP request to ReCodEx API failed: " . $e->getMessage(), $e); + } catch (GuzzleHttp\Exception\GuzzleException $e) { + throw new RecodexApiException("HTTP request to ReCodEx API failed: " . $e->getMessage(), $e); + } + return $this->processJsonBody($response); + } + /** * Perform a GET request and return decoded JSON response. * @param string $url suffix for the base URL * @param array $params to be encoded in URL query * @param array $headers initial HTTP headers * @return array|string|int|bool|null decoded JSON response + * @throws RecodexApiException + * @throws InvalidAccessTokenException */ private function get(string $url, array $params = [], array $headers = []) { - $response = $this->client->get($url, $this->prepareOptions($params, null, $headers)); - return $this->processJsonBody($response); + return $this->request('GET', $url, $this->prepareOptions($params, null, $headers)); } /** @@ -174,11 +206,12 @@ private function get(string $url, array $params = [], array $headers = []) * @param string|array|null $body (array is encoded as JSON) * @param array $headers initial HTTP headers * @return array|string|int|bool|null decoded JSON response + * @throws RecodexApiException + * @throws InvalidAccessTokenException */ private function post(string $url, array $params = [], $body = null, array $headers = []) { - $response = $this->client->post($url, $this->prepareOptions($params, $body, $headers)); - return $this->processJsonBody($response); + return $this->request('POST', $url, $this->prepareOptions($params, $body, $headers)); } /** @@ -187,11 +220,12 @@ private function post(string $url, array $params = [], $body = null, array $head * @param array $params to be encoded in URL query * @param array $headers initial HTTP headers * @return array|string|int|bool|null decoded JSON response + * @throws RecodexApiException + * @throws InvalidAccessTokenException */ private function delete(string $url, array $params = [], array $headers = []) { - $response = $this->client->delete($url, $this->prepareOptions($params, null, $headers)); - return $this->processJsonBody($response); + return $this->request('DELETE', $url, $this->prepareOptions($params, null, $headers)); } /** @@ -225,13 +259,14 @@ public function getTempTokenInstance(string $token): string /** * Complete the authentication process. Use tmp token to fetch full-token and user info. * The tmp token is expected to be set as the auth token already. - * @return array|null ['accessToken' => string, 'user' => RecodexUser] on success + * @return array ['accessToken' => string, 'user' => RecodexUser] on success */ - public function getTokenAndUser(): ?array + public function getTokenAndUser(): array { Debugger::log('ReCodEx::getTokenAndUser()', Debugger::DEBUG); $body = $this->post('extensions/' . $this->extensionId); if (!is_array($body) || empty($body['accessToken']) || empty($body['user'])) { + Debugger::log($body, Debugger::DEBUG); throw new RecodexApiException("Unexpected ReCodEx API response from extension token endpoint."); } @@ -240,6 +275,24 @@ public function getTokenAndUser(): ?array return $body; } + /** + * Refresh the token using ReCodEx API. The current token is expected to be set as the auth token already. + * @return array ['accessToken' => string, 'user' => RecodexUser] on success (same as getTokenAndUser()) + */ + public function refreshToken(): array + { + Debugger::log('ReCodEx::refreshToken()', Debugger::DEBUG); + $body = $this->post('login/refresh'); + if (!is_array($body) || empty($body['accessToken']) || empty($body['user'])) { + Debugger::log($body, Debugger::DEBUG); + throw new RecodexApiException("Unexpected ReCodEx API response from token refresh endpoint."); + } + + // wrap the user into a structure + $body['user'] = new RecodexUser($body['user'], $this); + return $body; + } + /** * Retrieve user data. * @param string $id diff --git a/app/presenters/LoginPresenter.php b/app/presenters/LoginPresenter.php index 624da55..472acc8 100644 --- a/app/presenters/LoginPresenter.php +++ b/app/presenters/LoginPresenter.php @@ -2,9 +2,13 @@ namespace App\Presenters; +use App\Exceptions\BadRequestException; use App\Exceptions\ForbiddenRequestException; use App\Exceptions\InvalidAccessTokenException; +use App\Exceptions\NotFoundException; +use App\Exceptions\RecodexApiException; use App\Exceptions\WrongCredentialsException; +use App\Model\Entity\User; use App\Model\Repository\Users; use App\Helpers\RecodexApiHelper; use App\Helpers\RecodexUser; @@ -42,6 +46,33 @@ class LoginPresenter extends BasePresenter */ public $roles; + /** + * Split the ReCodEx API token (save it to DB and suffix to the newly generated token), + * generate a new token for our frontend and send the response. + * @param User $user The user to log in + * @param string $token The token from ReCodEx API to split and save + */ + private function finalizeLogin(User $user, string $token): void + { + // part of the token is stored in the database, suffix goes into our token (payload) + $tokenSuffix = $user->setRecodexToken($token); + $this->users->persist($user); + + // generate our token for our frontend + $token = $this->accessManager->issueToken( + $user, + null, // no effective role override + [TokenScope::MASTER, TokenScope::REFRESH], + null, // default expiration + ['suffix' => $tokenSuffix] + ); + + $this->sendSuccessResponse([ + "accessToken" => $token, + "user" => $user, + ]); + } + /** * Log in using temp token from ReCodEx. * @POST @@ -55,7 +86,11 @@ public function actionDefault() { $req = $this->getRequest(); $tempToken = $req->getPost("token"); - $instanceId = $this->recodexApi->getTempTokenInstance($tempToken); + try { + $instanceId = $this->recodexApi->getTempTokenInstance($tempToken); + } catch (RecodexApiException $e) { + throw new BadRequestException("Invalid token from ReCodEx API", $e); + } // Call ReCodEx API and get full token + user info using the temporary token $this->recodexApi->setAuthToken($tempToken); @@ -72,23 +107,7 @@ public function actionDefault() } $user->updatedNow(); - // part of the token is stored in the database, suffix goes into our token (payload) - $tokenSuffix = $user->setRecodexToken($recodexResponse['accessToken']); - $this->users->persist($user); - - // generate our token for our frontend - $token = $this->accessManager->issueToken( - $user, - null, // no effective role override - [TokenScope::MASTER, TokenScope::REFRESH], - null, // default expiration - ['suffix' => $tokenSuffix] - ); - - $this->sendSuccessResponse([ - "accessToken" => $token, - "user" => $user, - ]); + $this->finalizeLogin($user, $recodexResponse['accessToken']); } /** @@ -104,23 +123,38 @@ public function checkRefresh() } /** - * Refresh the access token of current user + * Refresh the access token of current user (as well as the ReCodEx API token). * @GET * @LoggedIn + * @throws AuthenticationException * @throws ForbiddenRequestException + * @throws NotFoundException + * @throws InvalidAccessTokenException */ public function actionRefresh() { - $token = $this->getAccessToken(); - + // We need to inject the token manually here (this class is not derived from BasePresenterWithApi) $user = $this->getCurrentUser(); - $this->users->flush(); + $prefix = $user->getRecodexToken(); + $suffix = $this->getAccessToken()->getPayloadOrDefault('suffix', null); - $this->sendSuccessResponse( - [ - "accessToken" => $this->accessManager->issueRefreshedToken($token), - "user" => $user, - ] - ); + if (!$prefix || !$suffix) { + throw new ForbiddenRequestException("Cannot refresh token - user does not have a ReCodEx token."); + } + + // Call ReCodEx API to refresh the token + $this->recodexApi->setAuthToken($prefix . $suffix); + $recodexResponse = $this->recodexApi->refreshToken(); + /** @var RecodexUser */ + $recodexUser = $recodexResponse['user']; + + // Update the user entity if the token uses the same identity as the user + // (token may use identity override, in which case we do not want to update the user) + if ($recodexUser->getId() === $user->getId()) { + $recodexUser->updateUser($user); + $user->updatedNow(); + } + + $this->finalizeLogin($user, $recodexResponse['accessToken']); } } diff --git a/app/security/AccessManager.php b/app/security/AccessManager.php index 894520e..0316a0b 100644 --- a/app/security/AccessManager.php +++ b/app/security/AccessManager.php @@ -108,9 +108,9 @@ public function getUser(AccessToken $token): User */ public function issueToken( User $user, - string $effectiveRole = null, + ?string $effectiveRole = null, array $scopes = [], - int $exp = null, + ?int $exp = null, array $payload = [] ) { if ($exp === null) { @@ -155,7 +155,7 @@ public static function getGivenAccessToken(IRequest $request) { $accessToken = $request->getQuery("access_token"); if ($accessToken !== null && Strings::length($accessToken) > 0) { - return $accessToken; // the token specified in the URL is prefered + return $accessToken; // the token specified in the URL is preferred } // if the token is not in the URL, try to find the "Authorization" header with the bearer token diff --git a/tests/Presenters/GroupsPresenter.phpt b/tests/Presenters/GroupsPresenter.phpt index 7462e15..7a6553f 100644 --- a/tests/Presenters/GroupsPresenter.phpt +++ b/tests/Presenters/GroupsPresenter.phpt @@ -126,7 +126,7 @@ class TestGroupsPresenter extends Tester\TestCase public function testListGroupsAll() { PresenterTestHelper::loginDefaultAdmin($this->container); - $this->client->shouldReceive("get")->with('group-attributes', Mockery::any()) + $this->client->shouldReceive("request")->with('GET', 'group-attributes', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -166,7 +166,7 @@ class TestGroupsPresenter extends Tester\TestCase public function testListGroupsStudent() { PresenterTestHelper::login($this->container, PresenterTestHelper::STUDENT1_LOGIN); - $this->client->shouldReceive("get")->with('group-attributes', Mockery::any()) + $this->client->shouldReceive("request")->with('GET', 'group-attributes', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -202,7 +202,7 @@ class TestGroupsPresenter extends Tester\TestCase public function testListGroupsTeacher() { PresenterTestHelper::login($this->container, PresenterTestHelper::TEACHER1_LOGIN); - $this->client->shouldReceive("get")->with('group-attributes', Mockery::any()) + $this->client->shouldReceive("request")->with('GET', 'group-attributes', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -239,7 +239,7 @@ class TestGroupsPresenter extends Tester\TestCase $event = $this->presenter->sisEvents->findOneBy(['sisId' => 'gl1p']); Assert::notNull($event); - $this->client->shouldReceive("get")->with('group-attributes', Mockery::any()) + $this->client->shouldReceive("request")->with('GET', 'group-attributes', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -251,7 +251,7 @@ class TestGroupsPresenter extends Tester\TestCase ] ]))); - $this->client->shouldReceive("post")->with('group-attributes/g1', Mockery::any()) + $this->client->shouldReceive("request")->with('POST', 'group-attributes/g1', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -274,7 +274,7 @@ class TestGroupsPresenter extends Tester\TestCase $event = $this->presenter->sisEvents->findOneBy(['sisId' => 'gl1p']); Assert::notNull($event); - $this->client->shouldReceive("get")->with('group-attributes', Mockery::any()) + $this->client->shouldReceive("request")->with('GET', 'group-attributes', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -285,7 +285,7 @@ class TestGroupsPresenter extends Tester\TestCase ] ]))); - $this->client->shouldReceive("post")->with('group-attributes/t1', Mockery::any()) + $this->client->shouldReceive("request")->with('POST', 'group-attributes/t1', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -324,7 +324,7 @@ class TestGroupsPresenter extends Tester\TestCase $event = $this->presenter->sisEvents->findOneBy(['sisId' => 'gl1p']); Assert::notNull($event); - $this->client->shouldReceive("get")->with('group-attributes', Mockery::any()) + $this->client->shouldReceive("request")->with('GET', 'group-attributes', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -352,7 +352,7 @@ class TestGroupsPresenter extends Tester\TestCase $event = $this->presenter->sisEvents->findOneBy(['sisId' => 'gl1p']); Assert::notNull($event); - $this->client->shouldReceive("get")->with('group-attributes', Mockery::any()) + $this->client->shouldReceive("request")->with('GET', 'group-attributes', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -380,7 +380,7 @@ class TestGroupsPresenter extends Tester\TestCase $event = $this->presenter->sisEvents->findOneBy(['sisId' => 'gl1p']); Assert::notNull($event); - $this->client->shouldReceive("get")->with('group-attributes', Mockery::any()) + $this->client->shouldReceive("request")->with('GET', 'group-attributes', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -408,7 +408,7 @@ class TestGroupsPresenter extends Tester\TestCase $event = $this->presenter->sisEvents->findOneBy(['sisId' => 'gl1p']); Assert::notNull($event); - $this->client->shouldReceive("get")->with('group-attributes', Mockery::any()) + $this->client->shouldReceive("request")->with('GET', 'group-attributes', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -436,7 +436,7 @@ class TestGroupsPresenter extends Tester\TestCase $event = $this->presenter->sisEvents->findOneBy(['sisId' => 'gl1p']); Assert::notNull($event); - $this->client->shouldReceive("get")->with('group-attributes', Mockery::any()) + $this->client->shouldReceive("request")->with('GET', 'group-attributes', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -463,7 +463,7 @@ class TestGroupsPresenter extends Tester\TestCase $event = $this->presenter->sisEvents->findOneBy(['sisId' => 'gl1p']); Assert::notNull($event); - $this->client->shouldReceive("get")->with('group-attributes', Mockery::any()) + $this->client->shouldReceive("request")->with('GET', 'group-attributes', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -474,7 +474,7 @@ class TestGroupsPresenter extends Tester\TestCase ] ]))); - $this->client->shouldReceive("delete")->with('group-attributes/g1', Mockery::any()) + $this->client->shouldReceive("request")->with('DELETE', 'group-attributes/g1', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -497,7 +497,7 @@ class TestGroupsPresenter extends Tester\TestCase $event = $this->presenter->sisEvents->findOneBy(['sisId' => 'gl1p']); Assert::notNull($event); - $this->client->shouldReceive("get")->with('group-attributes', Mockery::any()) + $this->client->shouldReceive("request")->with('GET', 'group-attributes', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -507,7 +507,7 @@ class TestGroupsPresenter extends Tester\TestCase ] ]))); - $this->client->shouldReceive("delete")->with('group-attributes/g1', Mockery::any()) + $this->client->shouldReceive("request")->with('DELETE', 'group-attributes/g1', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -546,7 +546,7 @@ class TestGroupsPresenter extends Tester\TestCase $event = $this->presenter->sisEvents->findOneBy(['sisId' => 'gl1p']); Assert::notNull($event); - $this->client->shouldReceive("get")->with('group-attributes', Mockery::any()) + $this->client->shouldReceive("request")->with('GET', 'group-attributes', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -572,7 +572,7 @@ class TestGroupsPresenter extends Tester\TestCase $event = $this->presenter->sisEvents->findOneBy(['sisId' => 'gl1p']); Assert::notNull($event); - $this->client->shouldReceive("get")->with('group-attributes', Mockery::any()) + $this->client->shouldReceive("request")->with('GET', 'group-attributes', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -599,7 +599,7 @@ class TestGroupsPresenter extends Tester\TestCase $event = $this->presenter->sisEvents->findOneBy(['sisId' => 'gl1p']); Assert::notNull($event); - $this->client->shouldReceive("get")->with('group-attributes', Mockery::any()) + $this->client->shouldReceive("request")->with('GET', 'group-attributes', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -609,7 +609,7 @@ class TestGroupsPresenter extends Tester\TestCase ] ]))); - $this->client->shouldReceive("post")->with("groups/g1/students/$studentId", Mockery::any()) + $this->client->shouldReceive("request")->with('POST', "groups/g1/students/$studentId", Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -630,7 +630,7 @@ class TestGroupsPresenter extends Tester\TestCase { PresenterTestHelper::login($this->container, PresenterTestHelper::STUDENT1_LOGIN); - $this->client->shouldReceive("get")->with('group-attributes', Mockery::any()) + $this->client->shouldReceive("request")->with('GET', 'group-attributes', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -656,7 +656,7 @@ class TestGroupsPresenter extends Tester\TestCase $event = $this->presenter->sisEvents->findOneBy(['sisId' => 'gl1p']); Assert::notNull($event); - $this->client->shouldReceive("get")->with('group-attributes', Mockery::any()) + $this->client->shouldReceive("request")->with('GET', 'group-attributes', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -684,7 +684,7 @@ class TestGroupsPresenter extends Tester\TestCase $event = $this->presenter->sisEvents->findOneBy(['sisId' => 'gl1p']); Assert::notNull($event); - $this->client->shouldReceive("get")->with('group-attributes', Mockery::any()) + $this->client->shouldReceive("request")->with('GET', 'group-attributes', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -695,7 +695,7 @@ class TestGroupsPresenter extends Tester\TestCase ] ]))); - $this->client->shouldReceive("post")->with('groups', Mockery::on(function ($arg) use ($user, $event) { + $this->client->shouldReceive("request")->with('POST', 'groups', Mockery::on(function ($arg) use ($user, $event) { Assert::type('array', $arg); Assert::type('array', $arg['json'] ?? null); $body = $arg['json']; @@ -723,14 +723,14 @@ class TestGroupsPresenter extends Tester\TestCase 'payload' => ['id' => 'g1'] ]))); - $this->client->shouldReceive("post")->with('groups/g1/members/' . $user->getId(), Mockery::any()) + $this->client->shouldReceive("request")->with('POST', 'groups/g1/members/' . $user->getId(), Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, 'payload' => "OK" ]))); - $this->client->shouldReceive("post")->with('group-attributes/g1', Mockery::any()) + $this->client->shouldReceive("request")->with('POST', 'group-attributes/g1', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -753,7 +753,7 @@ class TestGroupsPresenter extends Tester\TestCase $event = $this->presenter->sisEvents->findOneBy(['sisId' => 'gl1p']); Assert::notNull($event); - $this->client->shouldReceive("get")->with('group-attributes', Mockery::any()) + $this->client->shouldReceive("request")->with('GET', 'group-attributes', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -781,7 +781,7 @@ class TestGroupsPresenter extends Tester\TestCase $event = $this->presenter->sisEvents->findOneBy(['sisId' => 'gl1p']); Assert::notNull($event); - $this->client->shouldReceive("get")->with('group-attributes', Mockery::any()) + $this->client->shouldReceive("request")->with('GET', 'group-attributes', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -816,7 +816,7 @@ class TestGroupsPresenter extends Tester\TestCase 'en' => ['name' => 'Group', 'description' => 'Group description'], ]; - $this->client->shouldReceive("get")->with('group-attributes', Mockery::any()) + $this->client->shouldReceive("request")->with('GET', 'group-attributes', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -826,7 +826,7 @@ class TestGroupsPresenter extends Tester\TestCase ] ]))); - $this->client->shouldReceive("post")->with('groups', Mockery::on(function ($arg) use ($user, $texts) { + $this->client->shouldReceive("request")->with('POST', 'groups', Mockery::on(function ($arg) use ($user, $texts) { Assert::type('array', $arg); Assert::type('array', $arg['json'] ?? null); $body = $arg['json']; @@ -854,7 +854,7 @@ class TestGroupsPresenter extends Tester\TestCase 'payload' => ['id' => 'g1'] ]))); - $this->client->shouldReceive("post")->with('group-attributes/g1', Mockery::any()) + $this->client->shouldReceive("request")->with('POST', 'group-attributes/g1', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -885,7 +885,7 @@ class TestGroupsPresenter extends Tester\TestCase 'en' => ['name' => 'Group', 'description' => 'Group description'], ]; - $this->client->shouldReceive("get")->with('group-attributes', Mockery::any()) + $this->client->shouldReceive("request")->with('GET', 'group-attributes', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -919,7 +919,7 @@ class TestGroupsPresenter extends Tester\TestCase 'en' => ['name' => 'Group', 'description' => 'Group description'], ]; - $this->client->shouldReceive("get")->with('group-attributes', Mockery::any()) + $this->client->shouldReceive("request")->with('GET', 'group-attributes', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -945,7 +945,7 @@ class TestGroupsPresenter extends Tester\TestCase { PresenterTestHelper::loginDefaultAdmin($this->container); - $this->client->shouldReceive("get")->with('group-attributes', Mockery::any()) + $this->client->shouldReceive("request")->with('GET', 'group-attributes', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -954,7 +954,7 @@ class TestGroupsPresenter extends Tester\TestCase ] ]))); - $this->client->shouldReceive("post")->with('group-attributes/g1', Mockery::on(function ($arg) { + $this->client->shouldReceive("request")->with('POST', 'group-attributes/g1', Mockery::on(function ($arg) { Assert::type('array', $arg); Assert::type('array', $arg['json'] ?? null); $body = $arg['json']; @@ -984,7 +984,7 @@ class TestGroupsPresenter extends Tester\TestCase { PresenterTestHelper::loginDefaultAdmin($this->container); - $this->client->shouldReceive("get")->with('group-attributes', Mockery::any()) + $this->client->shouldReceive("request")->with('GET', 'group-attributes', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -1008,7 +1008,7 @@ class TestGroupsPresenter extends Tester\TestCase { PresenterTestHelper::loginDefaultAdmin($this->container); - $this->client->shouldReceive("get")->with('group-attributes', Mockery::any()) + $this->client->shouldReceive("request")->with('GET', 'group-attributes', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -1032,7 +1032,7 @@ class TestGroupsPresenter extends Tester\TestCase { PresenterTestHelper::loginDefaultAdmin($this->container); - $this->client->shouldReceive("get")->with('group-attributes', Mockery::any()) + $this->client->shouldReceive("request")->with('GET', 'group-attributes', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -1041,7 +1041,7 @@ class TestGroupsPresenter extends Tester\TestCase ] ]))); - $this->client->shouldReceive("delete")->with('group-attributes/g1', Mockery::on(function ($arg) { + $this->client->shouldReceive("request")->with('DELETE', 'group-attributes/g1', Mockery::on(function ($arg) { Assert::type('array', $arg); Assert::type('array', $arg['query'] ?? null); $query = $arg['query']; @@ -1071,7 +1071,7 @@ class TestGroupsPresenter extends Tester\TestCase { PresenterTestHelper::loginDefaultAdmin($this->container); - $this->client->shouldReceive("get")->with('group-attributes', Mockery::any()) + $this->client->shouldReceive("request")->with('GET', 'group-attributes', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -1095,7 +1095,7 @@ class TestGroupsPresenter extends Tester\TestCase { PresenterTestHelper::loginDefaultAdmin($this->container); - $this->client->shouldReceive("get")->with('group-attributes', Mockery::any()) + $this->client->shouldReceive("request")->with('GET', 'group-attributes', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200, @@ -1119,7 +1119,7 @@ class TestGroupsPresenter extends Tester\TestCase { PresenterTestHelper::loginDefaultAdmin($this->container); - $this->client->shouldReceive("post")->with('groups/g1/archived', Mockery::any()) + $this->client->shouldReceive("request")->with('POST', 'groups/g1/archived', Mockery::any()) ->andReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'success' => true, 'code' => 200,