From d5019d94340eadc08289afe2e86a6c8c6a58c249 Mon Sep 17 00:00:00 2001 From: Carlo Beltrame Date: Wed, 3 Apr 2024 02:38:12 +0200 Subject: [PATCH] Add tests for accessing personal invitations --- .../Api/Invitations/AcceptInvitationTest.php | 4 +- .../AcceptPersonalInvitationTest.php | 202 +++++++++++ .../DeletePersonalInvitationTest.php | 23 ++ .../FindPersonalInvitationTest.php | 69 ++++ .../PersonalInvitationGraphQLTest.php | 78 +++++ .../RejectPersonalInvitationTest.php | 183 ++++++++++ .../SnapshotTests/EndpointQueryCountTest.php | 1 + .../SnapshotTests/ResponseSnapshotTest.php | 1 + ...est__testOpenApiSpecMatchesSnapshot__1.yml | 329 ++++++++++++++++++ ...t__testRootEndpointMatchesSnapshot__1.json | 4 + .../PersonalInvitationAcceptProcessorTest.php | 82 +++++ .../PersonalInvitationRejectProcessorTest.php | 83 +++++ 12 files changed, 1057 insertions(+), 2 deletions(-) create mode 100644 api/tests/Api/PersonalInvitations/AcceptPersonalInvitationTest.php create mode 100644 api/tests/Api/PersonalInvitations/DeletePersonalInvitationTest.php create mode 100644 api/tests/Api/PersonalInvitations/FindPersonalInvitationTest.php create mode 100644 api/tests/Api/PersonalInvitations/PersonalInvitationGraphQLTest.php create mode 100644 api/tests/Api/PersonalInvitations/RejectPersonalInvitationTest.php create mode 100644 api/tests/State/PersonalInvitationAcceptProcessorTest.php create mode 100644 api/tests/State/PersonalInvitationRejectProcessorTest.php diff --git a/api/tests/Api/Invitations/AcceptInvitationTest.php b/api/tests/Api/Invitations/AcceptInvitationTest.php index 307112e5bcd..6e0d3c57163 100644 --- a/api/tests/Api/Invitations/AcceptInvitationTest.php +++ b/api/tests/Api/Invitations/AcceptInvitationTest.php @@ -13,7 +13,7 @@ use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; use function PHPUnit\Framework\assertThat; -use function PHPUnit\Framework\greaterThanOrEqual; +use function PHPUnit\Framework\lessThanOrEqual; /** * @internal @@ -60,7 +60,7 @@ public function testAcceptInvitationDoesNotHitDBWhenNotLoggedIn() { * SAVEPOINT * RELEASE SAVEPOINT */ - assertThat($collector->getQueryCount(), greaterThanOrEqual(3)); + assertThat($collector->getQueryCount(), lessThanOrEqual(3)); } /** diff --git a/api/tests/Api/PersonalInvitations/AcceptPersonalInvitationTest.php b/api/tests/Api/PersonalInvitations/AcceptPersonalInvitationTest.php new file mode 100644 index 00000000000..7e5d4f8d037 --- /dev/null +++ b/api/tests/Api/PersonalInvitations/AcceptPersonalInvitationTest.php @@ -0,0 +1,202 @@ +request( + 'PATCH', + "/personal_invitations/{$campCollaboration->getId()}/".PersonalInvitation::ACCEPT, + [ + 'json' => [], + 'headers' => ['Content-Type' => 'application/merge-patch+json'], + ] + ); + $this->assertResponseStatusCodeSame(401); + } + + /** + * @throws TransportExceptionInterface + */ + public function testAcceptPersonalInvitationDoesNotHitDBWhenNotLoggedIn() { + /** @var CampCollaboration $campCollaboration */ + $campCollaboration = static::getFixture('campCollaboration6invitedWithUser'); + $client = static::createBasicClient(); + $client->enableProfiler(); + $client->request( + 'PATCH', + "/personal_invitations/{$campCollaboration->getId()}/".PersonalInvitation::ACCEPT, + [ + 'json' => [], + 'headers' => ['Content-Type' => 'application/merge-patch+json'], + ] + ); + + $collector = $client->getProfile()->getCollector('db'); + /* + * 3 is: + * BEGIN TRANSACTION + * SAVEPOINT + * RELEASE SAVEPOINT + */ + assertThat($collector->getQueryCount(), lessThanOrEqual(3)); + } + + /** + * @throws RedirectionExceptionInterface + * @throws DecodingExceptionInterface + * @throws ClientExceptionInterface + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + */ + public function testAcceptPersonalInvitationSuccess() { + /** @var CampCollaboration $campCollaboration */ + $campCollaboration = static::getFixture('campCollaboration6invitedWithUser'); + + /** @var Profile $profile */ + $profile = static::getFixture('profile6invited'); + static::createClientWithCredentials(['email' => $profile->email])->request( + 'PATCH', + "/personal_invitations/{$campCollaboration->getId()}/".PersonalInvitation::ACCEPT, + [ + 'json' => [], + 'headers' => ['Content-Type' => 'application/merge-patch+json'], + ] + ); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + '_links' => [ + 'self' => ['href' => "/personal_invitations/{$campCollaboration->getId()}"], + ], + ]); + } + + /** + * @throws RedirectionExceptionInterface + * @throws DecodingExceptionInterface + * @throws ClientExceptionInterface + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + */ + public function testCannotFindPersonalInvitationAfterSuccessfulAccept() { + /** @var CampCollaboration $campCollaboration */ + $campCollaboration = static::getFixture('campCollaboration6invitedWithUser'); + + /** @var Profile $profile */ + $profile = static::getFixture('profile6invited'); + $client = static::createClientWithCredentials(['email' => $profile->email]); + $client->disableReboot(); + $client->request( + 'PATCH', + "/personal_invitations/{$campCollaboration->getId()}/".PersonalInvitation::ACCEPT, + [ + 'json' => [], + 'headers' => ['Content-Type' => 'application/merge-patch+json'], + ] + ); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + /* + 'id' => $campCollaboration->getId(), + 'campId' => $campCollaboration->camp->getId(), + 'campTitle' => $campCollaboration->camp->title, */ + '_links' => [ + 'self' => ['href' => "/personal_invitations/{$campCollaboration->getId()}"], + ], + ]); + + $client->request('GET', "/personal_invitations/{$campCollaboration->getId()}"); + $this->assertResponseStatusCodeSame(404); + } + + /** + * @throws RedirectionExceptionInterface + * @throws ClientExceptionInterface + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + */ + public function testAcceptPersonalInvitationFailsWithExtraAttribute() { + /** @var CampCollaboration $campCollaboration */ + $campCollaboration = static::getFixture('campCollaboration6invitedWithUser'); + + /** @var Profile $profile */ + $profile = static::getFixture('profile6invited'); + static::createClientWithCredentials(['email' => $profile->email])->request( + 'PATCH', + "/personal_invitations/{$campCollaboration->getId()}/".PersonalInvitation::ACCEPT, + [ + 'json' => [ + 'userAlreadyInCamp' => true, + ], + 'headers' => ['Content-Type' => 'application/merge-patch+json'], + ] + ); + $this->assertResponseStatusCodeSame(400); + } + + /** + * @throws ClientExceptionInterface + * @throws RedirectionExceptionInterface + * @throws ServerExceptionInterface + * @throws TransportExceptionInterface + */ + #[DataProvider('invalidMethods')] + public function testInvalidRequestWhenWrongMethod(string $method) { + /** @var CampCollaboration $campCollaboration */ + $campCollaboration = static::getFixture('campCollaboration6invitedWithUser'); + static::createClientWithCredentials()->request($method, "/personal_invitations/{$campCollaboration->getId()}/".PersonalInvitation::ACCEPT); + $this->assertResponseStatusCodeSame(405); + } + + public static function invalidMethods(): array { + return ['GET' => ['GET'], 'PUT' => ['PUT'], 'POST' => ['POST'], 'DELETE' => ['DELETE'], 'OPTIONS' => ['OPTIONS']]; + } + + /** + * @throws ClientExceptionInterface + * @throws RedirectionExceptionInterface + * @throws ServerExceptionInterface + * @throws TransportExceptionInterface + */ + public function testNotFoundWhenIdDoesNotMatch() { + /** @var Profile $profile */ + $profile = static::getFixture('profile6invited'); + static::createClientWithCredentials(['email' => $profile->email])->request('PATCH', '/personal_invitations/notExisting/'.PersonalInvitation::ACCEPT); + $this->assertResponseStatusCodeSame(404); + } + + /** + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + * @throws RedirectionExceptionInterface + * @throws ClientExceptionInterface + */ + public function testMethodNotAllowedWhenNoId() { + /** @var Profile $profile */ + $profile = static::getFixture('profile6invited'); + static::createClientWithCredentials(['email' => $profile->email])->request('PATCH', '/personal_invitations/'.PersonalInvitation::ACCEPT); + $this->assertResponseStatusCodeSame(405); + } +} diff --git a/api/tests/Api/PersonalInvitations/DeletePersonalInvitationTest.php b/api/tests/Api/PersonalInvitations/DeletePersonalInvitationTest.php new file mode 100644 index 00000000000..e9f03896510 --- /dev/null +++ b/api/tests/Api/PersonalInvitations/DeletePersonalInvitationTest.php @@ -0,0 +1,23 @@ + $profile->email])->request('DELETE', '/invitations/'.$campCollaboration->getId()); + + $this->assertResponseStatusCodeSame(404); + } +} diff --git a/api/tests/Api/PersonalInvitations/FindPersonalInvitationTest.php b/api/tests/Api/PersonalInvitations/FindPersonalInvitationTest.php new file mode 100644 index 00000000000..a9cc836f1f9 --- /dev/null +++ b/api/tests/Api/PersonalInvitations/FindPersonalInvitationTest.php @@ -0,0 +1,69 @@ +request('GET', "/personal_invitations/{$campCollaboration->getId()}"); + $this->assertResponseStatusCodeSame(401); + } + + /** + * @throws RedirectionExceptionInterface + * @throws DecodingExceptionInterface + * @throws ClientExceptionInterface + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + */ + public function testFindOwnPersonalInvitationWhenLoggedIn() { + /** @var CampCollaboration $campCollaboration */ + $campCollaboration = static::getFixture('campCollaboration6invitedWithUser'); + + /** @var Profile $profile */ + $profile = static::getFixture('profile6invited'); + static::createClientWithCredentials(['email' => $profile->email])->request('GET', "/personal_invitations/{$campCollaboration->getId()}"); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $campCollaboration->getId(), + 'campId' => $campCollaboration->camp->getId(), + 'campTitle' => $campCollaboration->camp->title, + '_links' => [ + 'self' => ['href' => "/personal_invitations/{$campCollaboration->getId()}"], + ], + ]); + } + + /** + * @throws RedirectionExceptionInterface + * @throws DecodingExceptionInterface + * @throws ClientExceptionInterface + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + */ + public function testDoesNotFindOtherPersonalInvitationWhenLoggedIn() { + /** @var CampCollaboration $campCollaboration */ + $campCollaboration = static::getFixture('campCollaboration6invitedWithUser'); + static::createClientWithCredentials()->request('GET', "/personal_invitations/{$campCollaboration->getId()}"); + $this->assertResponseStatusCodeSame(404); + } +} diff --git a/api/tests/Api/PersonalInvitations/PersonalInvitationGraphQLTest.php b/api/tests/Api/PersonalInvitations/PersonalInvitationGraphQLTest.php new file mode 100644 index 00000000000..a8e26e1edc2 --- /dev/null +++ b/api/tests/Api/PersonalInvitations/PersonalInvitationGraphQLTest.php @@ -0,0 +1,78 @@ +getId()}\") { + id + campTitle + campId + } + } + "; + + static::createClient()->request('GET', '/graphql?'.http_build_query(['query' => $query])); + + $this->assertResponseStatusCodeSame(401); + } + + /** + * @throws ClientExceptionInterface + * @throws DecodingExceptionInterface + * @throws RedirectionExceptionInterface + * @throws ServerExceptionInterface + * @throws TransportExceptionInterface + */ + public function testFindPersonalInvitationWhenLoggedIn() { + /** @var CampCollaboration $campCollaboration */ + $campCollaboration = static::getFixture('campCollaboration6invitedWithUser'); + $query = " + { + personalInvitation(id: \"personal_invitations/{$campCollaboration->getId()}\") { + id + campTitle + campId + } + } + "; + + /** @var Profile $profile */ + $profile = static::getFixture('profile6invited'); + static::createClientWithCredentials(['email' => $profile->email])->request('GET', '/graphql?'.http_build_query(['query' => $query])); + + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'data' => [ + 'personalInvitation' => [ + 'id' => '/personal_invitations/'.$campCollaboration->getId(), + 'campId' => $campCollaboration->camp->getId(), + 'campTitle' => $campCollaboration->camp->title, + ], + ], + ]); + } +} diff --git a/api/tests/Api/PersonalInvitations/RejectPersonalInvitationTest.php b/api/tests/Api/PersonalInvitations/RejectPersonalInvitationTest.php new file mode 100644 index 00000000000..a69524b719d --- /dev/null +++ b/api/tests/Api/PersonalInvitations/RejectPersonalInvitationTest.php @@ -0,0 +1,183 @@ +request( + 'PATCH', + "/personal_invitations/{$campCollaboration->getId()}/".PersonalInvitation::REJECT, + [ + 'json' => [], + 'headers' => ['Content-Type' => 'application/merge-patch+json'], + ] + ); + $this->assertResponseStatusCodeSame(401); + } + + /** + * @throws RedirectionExceptionInterface + * @throws DecodingExceptionInterface + * @throws ClientExceptionInterface + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + */ + public function testRejectPersonalInvitationSucceedsWhenLoggedIn() { + /** @var CampCollaboration $campCollaboration */ + $campCollaboration = static::getFixture('campCollaboration6invitedWithUser'); + + /** @var Profile $profile */ + $profile = static::getFixture('profile6invited'); + static::createClientWithCredentials(['email' => $profile->email])->request( + 'PATCH', + "/personal_invitations/{$campCollaboration->getId()}/".PersonalInvitation::REJECT, + [ + 'json' => [], + 'headers' => ['Content-Type' => 'application/merge-patch+json'], + ] + ); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + /* + 'id' => $campCollaboration->getId(), + 'campId' => $campCollaboration->camp->getId(), + 'campTitle' => $campCollaboration->camp->title,*/ + '_links' => [ + 'self' => ['href' => "/personal_invitations/{$campCollaboration->getId()}"], + ], + ]); + } + + /** + * @throws ClientExceptionInterface + * @throws DecodingExceptionInterface + * @throws RedirectionExceptionInterface + * @throws ServerExceptionInterface + * @throws TransportExceptionInterface + */ + public function testCannotFindPersonalInvitationAfterSuccessfulReject() { + /** @var CampCollaboration $campCollaboration */ + $campCollaboration = static::getFixture('campCollaboration6invitedWithUser'); + + /** @var Profile $profile */ + $profile = static::getFixture('profile6invited'); + $client = static::createClientWithCredentials(['email' => $profile->email]); + $client->disableReboot(); + $client->request( + 'PATCH', + "/personal_invitations/{$campCollaboration->getId()}/".PersonalInvitation::REJECT, + [ + 'json' => [], + 'headers' => ['Content-Type' => 'application/merge-patch+json'], + ] + ); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + /* + 'id' => $campCollaboration->getId(), + 'campId' => $campCollaboration->camp->getId(), + 'campTitle' => $campCollaboration->camp->title,*/ + '_links' => [ + 'self' => ['href' => "/personal_invitations/{$campCollaboration->getId()}"], + ], + ]); + + $client->request('GET', "/personal_invitations/{$campCollaboration->getId()}"); + $this->assertResponseStatusCodeSame(404); + } + + /** + * @throws RedirectionExceptionInterface + * @throws ClientExceptionInterface + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + */ + public function testRejectPersonalInvitationFailsWithExtraAttribute() { + /** @var CampCollaboration $campCollaboration */ + $campCollaboration = static::getFixture('campCollaboration6invitedWithUser'); + + /** @var Profile $profile */ + $profile = static::getFixture('profile6invited'); + static::createClientWithCredentials(['email' => $profile->email])->request( + 'PATCH', + "/personal_invitations/{$campCollaboration->getId()}/".PersonalInvitation::REJECT, + [ + 'json' => [ + 'userAlreadyInCamp' => true, + ], + 'headers' => ['Content-Type' => 'application/merge-patch+json'], + ] + ); + $this->assertResponseStatusCodeSame(400); + } + + /** + * @throws ClientExceptionInterface + * @throws RedirectionExceptionInterface + * @throws ServerExceptionInterface + * @throws TransportExceptionInterface + */ + #[DataProvider('invalidMethods')] + public function testInvalidRequestWhenWrongMethod(string $method) { + /** @var CampCollaboration $campCollaboration */ + $campCollaboration = static::getFixture('campCollaboration6invitedWithUser'); + + /** @var Profile $profile */ + $profile = static::getFixture('profile6invited'); + static::createClientWithCredentials(['email' => $profile->email])->request($method, "/personal_invitations/{$campCollaboration->getId()}/".PersonalInvitation::REJECT); + $this->assertResponseStatusCodeSame(405); + } + + public static function invalidMethods(): array { + return ['GET' => ['GET'], 'PUT' => ['PUT'], 'POST' => ['POST'], 'DELETE' => ['DELETE'], 'OPTIONS' => ['OPTIONS']]; + } + + /** + * @throws ClientExceptionInterface + * @throws RedirectionExceptionInterface + * @throws ServerExceptionInterface + * @throws TransportExceptionInterface + */ + public function testNotFoundWhenIdDoesNotMatch() { + /** @var Profile $profile */ + $profile = static::getFixture('profile6invited'); + static::createClientWithCredentials(['email' => $profile->email])->request('PATCH', '/personal_invitations/notExisting/'.PersonalInvitation::REJECT); + $this->assertResponseStatusCodeSame(404); + } + + /** + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + * @throws RedirectionExceptionInterface + * @throws ClientExceptionInterface + */ + public function testMethodNotAllowedWhenNoId() { + /** @var Profile $profile */ + $profile = static::getFixture('profile6invited'); + static::createClientWithCredentials(['email' => $profile->email])->request('PATCH', '/personal_invitations/'.PersonalInvitation::REJECT); + $this->assertResponseStatusCodeSame(405); + } +} diff --git a/api/tests/Api/SnapshotTests/EndpointQueryCountTest.php b/api/tests/Api/SnapshotTests/EndpointQueryCountTest.php index 644540f6238..3830d1ae83d 100644 --- a/api/tests/Api/SnapshotTests/EndpointQueryCountTest.php +++ b/api/tests/Api/SnapshotTests/EndpointQueryCountTest.php @@ -182,6 +182,7 @@ private static function getCollectionEndpoints() { '/auth/jubladb' => false, '/auth/reset_password' => false, '/invitations' => false, + '/personal_invitations' => false, default => true }; }); diff --git a/api/tests/Api/SnapshotTests/ResponseSnapshotTest.php b/api/tests/Api/SnapshotTests/ResponseSnapshotTest.php index 26e7b6b6297..84d06368078 100644 --- a/api/tests/Api/SnapshotTests/ResponseSnapshotTest.php +++ b/api/tests/Api/SnapshotTests/ResponseSnapshotTest.php @@ -114,6 +114,7 @@ public static function getCollectionEndpoints() { '/auth/jubladb' => false, '/auth/reset_password' => false, '/invitations' => false, + '/personal_invitations' => false, '/users' => false, default => true }; diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testOpenApiSpecMatchesSnapshot__1.yml b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testOpenApiSpecMatchesSnapshot__1.yml index 87e6a210a3c..b76cfc320ab 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testOpenApiSpecMatchesSnapshot__1.yml +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testOpenApiSpecMatchesSnapshot__1.yml @@ -18423,6 +18423,126 @@ components: - moveScheduleEntries - start type: object + PersonalInvitation-read: + deprecated: false + description: 'An invitation for a person who already has an account to collaborate in a camp.' + properties: + campId: + description: |- + The id of the camp for which this invitation is valid. This is useful for + redirecting the user to the correct place after they accept. + example: 1a2b3c4d + type: string + campTitle: + description: |- + The full title of the camp for which this invitation is valid. This should help + the user to decide whether to accept or reject the invitation. + example: 'Abteilungs-Sommerlager 2022' + type: string + id: + example: 1a2b3c4d + type: string + type: object + PersonalInvitation-write: + deprecated: false + description: 'An invitation for a person who already has an account to collaborate in a camp.' + type: object + PersonalInvitation.jsonapi-read: + deprecated: false + description: 'An invitation for a person who already has an account to collaborate in a camp.' + properties: + campId: + description: |- + The id of the camp for which this invitation is valid. This is useful for + redirecting the user to the correct place after they accept. + example: 1a2b3c4d + type: string + campTitle: + description: |- + The full title of the camp for which this invitation is valid. This should help + the user to decide whether to accept or reject the invitation. + example: 'Abteilungs-Sommerlager 2022' + type: string + id: + example: 1a2b3c4d + type: string + type: object + PersonalInvitation.jsonapi-write: + deprecated: false + description: 'An invitation for a person who already has an account to collaborate in a camp.' + type: object + PersonalInvitation.jsonhal-read: + deprecated: false + description: 'An invitation for a person who already has an account to collaborate in a camp.' + properties: + _links: + properties: + self: + properties: + href: + format: iri-reference + type: string + type: object + type: object + campId: + description: |- + The id of the camp for which this invitation is valid. This is useful for + redirecting the user to the correct place after they accept. + example: 1a2b3c4d + type: string + campTitle: + description: |- + The full title of the camp for which this invitation is valid. This should help + the user to decide whether to accept or reject the invitation. + example: 'Abteilungs-Sommerlager 2022' + type: string + id: + example: 1a2b3c4d + type: string + type: object + PersonalInvitation.jsonld-read: + deprecated: false + description: 'An invitation for a person who already has an account to collaborate in a camp.' + properties: + '@context': + oneOf: + - + additionalProperties: true + properties: + '@vocab': + type: string + hydra: + enum: ['http://www.w3.org/ns/hydra/core#'] + type: string + required: + - '@vocab' + - hydra + type: object + - + type: string + readOnly: true + '@id': + readOnly: true + type: string + '@type': + readOnly: true + type: string + campId: + description: |- + The id of the camp for which this invitation is valid. This is useful for + redirecting the user to the correct place after they accept. + example: 1a2b3c4d + type: string + campTitle: + description: |- + The full title of the camp for which this invitation is valid. This should help + the user to decide whether to accept or reject the invitation. + example: 'Abteilungs-Sommerlager 2022' + type: string + id: + example: 1a2b3c4d + type: string + type: object Profile-read: deprecated: false description: |- @@ -30001,6 +30121,215 @@ paths: summary: 'Updates the Period resource.' tags: - Period + /personal_invitations: + get: + deprecated: false + description: 'Retrieves the collection of PersonalInvitation resources.' + operationId: api_personal_invitations_get_collection + parameters: [] + responses: + 200: + content: + application/hal+json: + schema: + properties: + _embedded: { anyOf: [{ properties: { item: { items: { $ref: '#/components/schemas/PersonalInvitation.jsonhal-read' }, type: array } }, type: object }, { type: object }] } + _links: { properties: { first: { properties: { href: { format: iri-reference, type: string } }, type: object }, last: { properties: { href: { format: iri-reference, type: string } }, type: object }, next: { properties: { href: { format: iri-reference, type: string } }, type: object }, previous: { properties: { href: { format: iri-reference, type: string } }, type: object }, self: { properties: { href: { format: iri-reference, type: string } }, type: object } }, type: object } + itemsPerPage: { minimum: 0, type: integer } + totalItems: { minimum: 0, type: integer } + required: + - _embedded + - _links + type: object + application/json: + schema: + items: + $ref: '#/components/schemas/PersonalInvitation-read' + type: array + application/ld+json: + schema: + properties: + 'hydra:member': { items: { $ref: '#/components/schemas/PersonalInvitation.jsonld-read' }, type: array } + 'hydra:search': { properties: { '@type': { type: string }, 'hydra:mapping': { items: { properties: { '@type': { type: string }, property: { type: ['null', string] }, required: { type: boolean }, variable: { type: string } }, type: object }, type: array }, 'hydra:template': { type: string }, 'hydra:variableRepresentation': { type: string } }, type: object } + 'hydra:totalItems': { minimum: 0, type: integer } + 'hydra:view': { example: { '@id': string, 'hydra:first': string, 'hydra:last': string, 'hydra:next': string, 'hydra:previous': string, type: string }, properties: { '@id': { format: iri-reference, type: string }, '@type': { type: string }, 'hydra:first': { format: iri-reference, type: string }, 'hydra:last': { format: iri-reference, type: string }, 'hydra:next': { format: iri-reference, type: string }, 'hydra:previous': { format: iri-reference, type: string } }, type: object } + required: + - 'hydra:member' + type: object + application/vnd.api+json: + schema: + items: + $ref: '#/components/schemas/PersonalInvitation.jsonapi-read' + type: array + text/html: + schema: + items: + $ref: '#/components/schemas/PersonalInvitation-read' + type: array + description: 'PersonalInvitation collection' + summary: 'Retrieves the collection of PersonalInvitation resources.' + tags: + - PersonalInvitation + parameters: [] + '/personal_invitations/{id}': + get: + deprecated: false + description: 'Retrieves a PersonalInvitation resource.' + operationId: api_personal_invitations_id_get + parameters: + - + allowEmptyValue: false + allowReserved: false + deprecated: false + description: 'PersonalInvitation identifier' + explode: false + in: path + name: id + required: true + schema: + type: string + style: simple + responses: + 200: + content: + application/hal+json: + schema: + $ref: '#/components/schemas/PersonalInvitation.jsonhal-read' + application/json: + schema: + $ref: '#/components/schemas/PersonalInvitation-read' + application/ld+json: + schema: + $ref: '#/components/schemas/PersonalInvitation.jsonld-read' + application/vnd.api+json: + schema: + $ref: '#/components/schemas/PersonalInvitation.jsonapi-read' + text/html: + schema: + $ref: '#/components/schemas/PersonalInvitation-read' + description: 'PersonalInvitation resource' + 404: + description: 'Resource not found' + summary: 'Retrieves a PersonalInvitation resource.' + tags: + - PersonalInvitation + parameters: [] + '/personal_invitations/{id}/accept': + parameters: [] + patch: + deprecated: false + description: 'Updates the PersonalInvitation resource.' + operationId: api_personal_invitations_idaccept_patch + parameters: + - + allowEmptyValue: false + allowReserved: false + deprecated: false + description: 'PersonalInvitation identifier' + explode: false + in: path + name: id + required: true + schema: + type: string + style: simple + requestBody: + content: + application/merge-patch+json: + schema: + $ref: '#/components/schemas/PersonalInvitation-write' + application/vnd.api+json: + schema: + $ref: '#/components/schemas/PersonalInvitation.jsonapi-write' + description: 'The updated PersonalInvitation resource' + required: true + responses: + 200: + content: + application/hal+json: + schema: + $ref: '#/components/schemas/PersonalInvitation.jsonhal-read' + application/json: + schema: + $ref: '#/components/schemas/PersonalInvitation-read' + application/ld+json: + schema: + $ref: '#/components/schemas/PersonalInvitation.jsonld-read' + application/vnd.api+json: + schema: + $ref: '#/components/schemas/PersonalInvitation.jsonapi-read' + text/html: + schema: + $ref: '#/components/schemas/PersonalInvitation-read' + description: 'PersonalInvitation resource updated' + links: [] + 400: + description: 'Invalid input' + 404: + description: 'Resource not found' + 422: + description: 'Unprocessable entity' + summary: 'Accept a personal invitation.' + tags: + - PersonalInvitation + '/personal_invitations/{id}/reject': + parameters: [] + patch: + deprecated: false + description: 'Updates the PersonalInvitation resource.' + operationId: api_personal_invitations_idreject_patch + parameters: + - + allowEmptyValue: false + allowReserved: false + deprecated: false + description: 'PersonalInvitation identifier' + explode: false + in: path + name: id + required: true + schema: + type: string + style: simple + requestBody: + content: + application/merge-patch+json: + schema: + $ref: '#/components/schemas/PersonalInvitation-write' + application/vnd.api+json: + schema: + $ref: '#/components/schemas/PersonalInvitation.jsonapi-write' + description: 'The updated PersonalInvitation resource' + required: true + responses: + 200: + content: + application/hal+json: + schema: + $ref: '#/components/schemas/PersonalInvitation.jsonhal-read' + application/json: + schema: + $ref: '#/components/schemas/PersonalInvitation-read' + application/ld+json: + schema: + $ref: '#/components/schemas/PersonalInvitation.jsonld-read' + application/vnd.api+json: + schema: + $ref: '#/components/schemas/PersonalInvitation.jsonapi-read' + text/html: + schema: + $ref: '#/components/schemas/PersonalInvitation-read' + description: 'PersonalInvitation resource updated' + links: [] + 400: + description: 'Invalid input' + 404: + description: 'Resource not found' + 422: + description: 'Unprocessable entity' + summary: 'Reject a personal invitation.' + tags: + - PersonalInvitation /profiles: get: deprecated: false diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testRootEndpointMatchesSnapshot__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testRootEndpointMatchesSnapshot__1.json index c4aa27d3225..b99465930a0 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testRootEndpointMatchesSnapshot__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testRootEndpointMatchesSnapshot__1.json @@ -87,6 +87,10 @@ "href": "\/periods{\/id}{?camp,camp[]}", "templated": true }, + "personalInvitations": { + "href": "/personal_invitations{/id}{/action}", + "templated": true + }, "profiles": { "href": "\/profiles{\/id}{?user.collaborations.camp,user.collaborations.camp[]}", "templated": true diff --git a/api/tests/State/PersonalInvitationAcceptProcessorTest.php b/api/tests/State/PersonalInvitationAcceptProcessorTest.php new file mode 100644 index 00000000000..3c8f7f12fb2 --- /dev/null +++ b/api/tests/State/PersonalInvitationAcceptProcessorTest.php @@ -0,0 +1,82 @@ +invitation = new PersonalInvitation(self::ID, '', '', '', false); + $this->campCollaboration = new CampCollaboration(); + $this->user = $this->createMock(User::class); + + $this->collaborationRepository = $this->createMock(CampCollaborationRepository::class); + $this->userRepository = $this->createMock(UserRepository::class); + $this->security = $this->createMock(Security::class); + $this->em = $this->createMock(EntityManagerInterface::class); + + $this->processor = new PersonalInvitationAcceptProcessor( + $this->collaborationRepository, + $this->userRepository, + $this->security, + $this->em + ); + } + + public function testUpdatesPersonalInvitationCorrectlyOnAccept() { + $this->user + ->expects(self::once()) + ->method('getUserIdentifier') + ->willReturn('9876abcd') + ; + $this->collaborationRepository + ->expects(self::once()) + ->method('findByUserAndIdAndInvited') + ->with($this->user, self::ID) + ->willReturn($this->campCollaboration) + ; + $this->userRepository + ->expects(self::once()) + ->method('loadUserByIdentifier') + ->with('9876abcd') + ->willReturn($this->user) + ; + $this->security->expects(self::once())->method('getUser')->willReturn($this->user); + $this->em->expects($this->exactly(1))->method('flush'); + + $this->processor->process($this->invitation, new Patch()); + + self::assertThat($this->campCollaboration->status, self::equalTo(CampCollaboration::STATUS_ESTABLISHED)); + self::assertThat($this->campCollaboration->inviteKey, self::isNull()); + self::assertThat($this->campCollaboration->inviteEmail, self::isNull()); + } +} diff --git a/api/tests/State/PersonalInvitationRejectProcessorTest.php b/api/tests/State/PersonalInvitationRejectProcessorTest.php new file mode 100644 index 00000000000..dec52144e79 --- /dev/null +++ b/api/tests/State/PersonalInvitationRejectProcessorTest.php @@ -0,0 +1,83 @@ +invitation = new PersonalInvitation(self::ID, '', '', '', false); + $this->campCollaboration = new CampCollaboration(); + $this->user = $this->createMock(User::class); + + $this->collaborationRepository = $this->createMock(CampCollaborationRepository::class); + $this->userRepository = $this->createMock(UserRepository::class); + $this->security = $this->createMock(Security::class); + $this->em = $this->createMock(EntityManagerInterface::class); + + $this->processor = new PersonalInvitationRejectProcessor( + $this->collaborationRepository, + $this->userRepository, + $this->security, + $this->em + ); + } + + public function testUpdatesPersonalInvitationCorrectlyOnReject() { + $this->user + ->expects(self::once()) + ->method('getUserIdentifier') + ->willReturn('9876abcd') + ; + $this->collaborationRepository + ->expects(self::once()) + ->method('findByUserAndIdAndInvited') + ->with($this->user, self::ID) + ->willReturn($this->campCollaboration) + ; + $this->userRepository + ->expects(self::once()) + ->method('loadUserByIdentifier') + ->with('9876abcd') + ->willReturn($this->user) + ; + $this->security->expects(self::once())->method('getUser')->willReturn($this->user); + $this->em->expects($this->exactly(1))->method('flush'); + + $this->processor->process($this->invitation, new Patch()); + + self::assertThat($this->campCollaboration->status, self::equalTo(CampCollaboration::STATUS_INACTIVE)); + self::assertThat($this->campCollaboration->inviteKey, self::isNull()); + self::assertThat($this->campCollaboration->inviteEmail, self::equalTo($this->campCollaboration->inviteEmail)); + self::assertThat($this->campCollaboration->user, self::equalTo($this->campCollaboration->user)); + } +}