diff --git a/api/README.md b/api/README.md index 6ed7f45d08..93a8ffc92e 100644 --- a/api/README.md +++ b/api/README.md @@ -55,6 +55,6 @@ docker compose down && docker compose up ``` or ```shell -docker-compsoe stop php; docker compose rm php; docker compose up +docker compose stop api; docker compose rm api; docker compose up ``` if you don't want to restart the frontend. diff --git a/api/fixtures/activities.yml b/api/fixtures/activities.yml index 56bdfdc228..50b1214432 100644 --- a/api/fixtures/activities.yml +++ b/api/fixtures/activities.yml @@ -24,3 +24,9 @@ App\Entity\Activity: location: rootContentNode: '@columnLayout1campPrototype' category: '@category1campPrototype' + activity1campShared: + camp: '@campShared' + title: + location: + rootContentNode: '@columnLayout1campShared' + category: '@category1campShared' diff --git a/api/fixtures/activity-progress-labels.yml b/api/fixtures/activity-progress-labels.yml index 3b38e04ff4..5b118ed622 100644 --- a/api/fixtures/activity-progress-labels.yml +++ b/api/fixtures/activity-progress-labels.yml @@ -35,4 +35,12 @@ App\Entity\ActivityProgressLabel: camp: '@campPrototype' position: 2 title: 'Überprüft' + activityProgressLabel1campShared: + camp: '@campShared' + position: 0 + title: 'In Planung' + activityProgressLabel2campShared: + camp: '@campShared' + position: 1 + title: 'Geplant' \ No newline at end of file diff --git a/api/fixtures/activityResponsibles.yml b/api/fixtures/activityResponsibles.yml index bb724b82e4..88f819be67 100644 --- a/api/fixtures/activityResponsibles.yml +++ b/api/fixtures/activityResponsibles.yml @@ -11,3 +11,6 @@ App\Entity\ActivityResponsible: activityResponsible1campPrototype: campCollaboration: '@campCollaboration1campPrototype' activity: '@activity1campPrototype' + activityResponsible1campShared: + campCollaboration: '@campCollaboration1campShared' + activity: '@activity1campShared' diff --git a/api/fixtures/campCollaborations.yml b/api/fixtures/campCollaborations.yml index f345312b04..010f3f1294 100644 --- a/api/fixtures/campCollaborations.yml +++ b/api/fixtures/campCollaborations.yml @@ -77,3 +77,27 @@ App\Entity\CampCollaboration: camp: '@campPrototype' status: established role: manager + campCollaboration1campShared: + user: '@user4unrelated' + camp: '@campShared' + status: established + role: manager + campCollaboration2invitedCampShared: + user: '@user6invited' + camp: '@campShared' + inviteKey: "myInviteKeyCampShared" + inviteKeyHash: "sl3hC12VkIUzT89mMggYyoMmFup=" + status: invited + role: manager + campCollaboration3inactiveCampShared: + user: '@user5inactive' + camp: '@campShared' + status: inactive + role: manager + campCollaboration4invitedCampShared: + camp: '@campShared' + inviteKey: "myInviteKeyCampShared" + inviteKeyHash: "sl3hC12VkIUzT89mMggYyoMmFuq=" + inviteEmail: "e.mail@test.com" + status: invited + role: manager diff --git a/api/fixtures/camps.yml b/api/fixtures/camps.yml index aa99082f3d..403852be4d 100644 --- a/api/fixtures/camps.yml +++ b/api/fixtures/camps.yml @@ -10,6 +10,9 @@ App\Entity\Camp: owner: '@user1manager' creator: '@user2member' isPrototype: false + isShared: false + sharedBy: null + sharedSince: null campPrototypeId: null camp2: shortTitle: Camp2 @@ -22,6 +25,9 @@ App\Entity\Camp: owner: '@admin' creator: '@user4unrelated' isPrototype: false + isShared: false + sharedBy: null + sharedSince: null campPrototypeId: null campUnrelated: shortTitle: CampUnrelated @@ -34,6 +40,9 @@ App\Entity\Camp: owner: '@admin' creator: '@admin' isPrototype: false + isShared: false + sharedBy: null + sharedSince: null campPrototypeId: null campPrototype: shortTitle: CampPrototype @@ -46,4 +55,22 @@ App\Entity\Camp: owner: '@admin' creator: '@admin' isPrototype: true + isShared: false + sharedBy: null + sharedSince: null + campPrototypeId: null + campShared: + shortTitle: CampPrototype + title: + motto: + addressName: + addressStreet: + addressZipcode: + addressCity: + owner: '@admin' + creator: '@admin' + isPrototype: false + isShared: true + sharedBy: '@admin' + sharedSince: '<(new DateTime("2025-09-03 12:00:00"))>' campPrototypeId: null diff --git a/api/fixtures/categories.yml b/api/fixtures/categories.yml index c66effe26f..110ca17fd5 100644 --- a/api/fixtures/categories.yml +++ b/api/fixtures/categories.yml @@ -43,3 +43,10 @@ App\Entity\Category: color: '#4DBB52' numberingStyle: 1 rootContentNode: '@columnLayout2campPrototype' + category1campShared: + camp: '@campShared' + short: LS + name: Lagersport + color: '#4DBB52' + numberingStyle: 1 + rootContentNode: '@columnLayout2campShared' diff --git a/api/fixtures/checklistItems.yml b/api/fixtures/checklistItems.yml index f7730badd4..f3ad90419b 100644 --- a/api/fixtures/checklistItems.yml +++ b/api/fixtures/checklistItems.yml @@ -19,6 +19,9 @@ App\Entity\ChecklistItem: checklistItemUnrelated_1_1: checklist: '@checklist1campUnrelated' text: 'CampUnrelated_List1_Item1' - checklistItemPrototype_1_1: + checklistItemCampPrototype_1_1: checklist: '@checklist1campPrototype' text: 'CampPrototype_List1_Item1' + checklistItemCampShared_1_1: + checklist: '@checklist1campShared' + text: 'CampShared_List1_Item1' diff --git a/api/fixtures/checklistNodes.yml b/api/fixtures/checklistNodes.yml index a6af5b9eeb..3f4924e72d 100644 --- a/api/fixtures/checklistNodes.yml +++ b/api/fixtures/checklistNodes.yml @@ -21,4 +21,18 @@ App\Entity\ContentNode\ChecklistNode: slot: '1' position: 5 instanceName: + contentType: '@contentTypeChecklist' + checklistNodeCampPrototype: + root: '@columnLayout1campPrototype' + parent: '@columnLayout1campPrototype' + slot: '1' + position: 5 + instanceName: + contentType: '@contentTypeChecklist' + checklistNodeCampShared: + root: '@columnLayout1campShared' + parent: '@columnLayout1campShared' + slot: '1' + position: 5 + instanceName: contentType: '@contentTypeChecklist' \ No newline at end of file diff --git a/api/fixtures/checklists.yml b/api/fixtures/checklists.yml index 466a38e012..e9746627a2 100644 --- a/api/fixtures/checklists.yml +++ b/api/fixtures/checklists.yml @@ -18,3 +18,6 @@ App\Entity\Checklist: camp: null isPrototype: true name: 'J+S Ausbildungsziele' + checklist1campShared: + camp: '@campShared' + name: 'PBS Ausbildungsziele' diff --git a/api/fixtures/columnLayouts.yml b/api/fixtures/columnLayouts.yml index 2634d8e232..c5e1a7a65f 100644 --- a/api/fixtures/columnLayouts.yml +++ b/api/fixtures/columnLayouts.yml @@ -87,6 +87,14 @@ App\Entity\ContentNode\ColumnLayout: data: { columns: [{ slot: '1', width: 12 }] } instanceName: contentType: '@contentTypeColumnLayout' + columnLayout3campPrototype: + root: '@columnLayout1campPrototype' + parent: '@columnLayout1campPrototype' + slot: '1' + position: 10 + data: { columns: [{ slot: '1', width: 12 }] } + instanceName: + contentType: '@contentTypeColumnLayout' columnLayoutWithResponsiveLayout: root: '@self' parent: null @@ -95,3 +103,27 @@ App\Entity\ContentNode\ColumnLayout: data: { columns: [{ slot: '1', width: 12 }] } instanceName: contentType: '@contentTypeColumnLayout' + columnLayout1campShared: + root: '@self' + parent: null + slot: null + position: 0 + data: { columns: [{ slot: '1', width: 12 }] } + instanceName: + contentType: '@contentTypeColumnLayout' + columnLayout2campShared: + root: '@self' + parent: null + slot: null + position: 0 + data: { columns: [{ slot: '1', width: 12 }] } + instanceName: + contentType: '@contentTypeColumnLayout' + columnLayout3campShared: + root: '@columnLayout1campShared' + parent: '@columnLayout1campShared' + slot: '1' + position: 10 + data: { columns: [{ slot: '1', width: 12 }] } + instanceName: + contentType: '@contentTypeColumnLayout' diff --git a/api/fixtures/comments.yml b/api/fixtures/comments.yml index 9efe8106a0..58bbda89d8 100644 --- a/api/fixtures/comments.yml +++ b/api/fixtures/comments.yml @@ -16,4 +16,16 @@ App\Entity\Comment: activity: '@activity2' textHtml: author: '@user1manager' + orphanDescription: null + comment1campPrototype: + camp: '@campPrototype' + activity: '@activity1campPrototype' + textHtml: + author: '@user4unrelated' + orphanDescription: null + comment1campShared: + camp: '@campShared' + activity: '@activity1campShared' + textHtml: + author: '@user4unrelated' orphanDescription: null \ No newline at end of file diff --git a/api/fixtures/dayResponsibles.yml b/api/fixtures/dayResponsibles.yml index 889d90f0cb..8e18b0b8a5 100644 --- a/api/fixtures/dayResponsibles.yml +++ b/api/fixtures/dayResponsibles.yml @@ -23,3 +23,6 @@ App\Entity\DayResponsible: dayResponsible1day1period1campPrototype: campCollaboration: '@campCollaboration1campPrototype' day: '@day1period1campPrototype' + dayResponsible1day1period1campShared: + campCollaboration: '@campCollaboration1campShared' + day: '@day1period1campShared' diff --git a/api/fixtures/days.yml b/api/fixtures/days.yml index 6fd522c39a..f6138a7e42 100644 --- a/api/fixtures/days.yml +++ b/api/fixtures/days.yml @@ -29,3 +29,12 @@ App\Entity\Day: day1period1campPrototype: period: '@period1campPrototype' dayOffset: 0 + day1period1campShared: + period: '@period1campShared' + dayOffset: 0 + day2period1campShared: + period: '@period1campShared' + dayOffset: 1 + day3period1campShared: + period: '@period1campShared' + dayOffset: 2 diff --git a/api/fixtures/materialItems.yml b/api/fixtures/materialItems.yml index 410aa632a4..369e22b973 100644 --- a/api/fixtures/materialItems.yml +++ b/api/fixtures/materialItems.yml @@ -39,3 +39,11 @@ App\Entity\MaterialItem: article: Lagerapotheke quantity: 1 unit: null + materialItem1period1campShared: + camp: '@campShared' + materialList: '@materialList1campShared' + period: '@period1campShared' + materialNode: null + article: Umhang + quantity: 1 + unit: null diff --git a/api/fixtures/materialLists.yml b/api/fixtures/materialLists.yml index e4e9f515f1..1ad476bb35 100644 --- a/api/fixtures/materialLists.yml +++ b/api/fixtures/materialLists.yml @@ -20,3 +20,9 @@ App\Entity\MaterialList: materialList1campPrototype: camp: '@campPrototype' name: Einkaufsliste + materialList1campShared: + camp: '@campShared' + name: Pfadiheim + materialList2campShared: + camp: '@campShared' + campCollaboration: '@campCollaboration1campShared' diff --git a/api/fixtures/materialNodes.yml b/api/fixtures/materialNodes.yml index 84131cd0a2..60c6ecbedf 100644 --- a/api/fixtures/materialNodes.yml +++ b/api/fixtures/materialNodes.yml @@ -13,3 +13,17 @@ App\Entity\ContentNode\MaterialNode: position: 2 instanceName: contentType: '@contentTypeMaterial' + materialNodeCampPrototype: + root: '@columnLayout1campPrototype' + parent: '@columnLayout1campPrototype' + slot: '1' + position: 2 + instanceName: + contentType: '@contentTypeMaterial' + materialNodeCampShared: + root: '@columnLayout1campShared' + parent: '@columnLayout1campShared' + slot: '1' + position: 2 + instanceName: + contentType: '@contentTypeMaterial' diff --git a/api/fixtures/multiSelects.yml b/api/fixtures/multiSelects.yml index eb2d608548..20efeba1f2 100644 --- a/api/fixtures/multiSelects.yml +++ b/api/fixtures/multiSelects.yml @@ -7,3 +7,19 @@ App\Entity\ContentNode\MultiSelect: instanceName: contentType: '@contentTypeMultiSelect' data: { options: { 'key1': { 'checked': true } } } + multiSelectCampPrototype: + root: '@columnLayout1campPrototype' + parent: '@columnLayout1campPrototype' + slot: '1' + position: 3 + instanceName: + contentType: '@contentTypeMultiSelect' + data: { options: { 'key1': { 'checked': true } } } + multiSelectCampShared: + root: '@columnLayout1campShared' + parent: '@columnLayout1campShared' + slot: '1' + position: 3 + instanceName: + contentType: '@contentTypeMultiSelect' + data: { options: { 'key1': { 'checked': true } } } diff --git a/api/fixtures/periods.yml b/api/fixtures/periods.yml index c3e4f73e83..0e0f2049a5 100644 --- a/api/fixtures/periods.yml +++ b/api/fixtures/periods.yml @@ -24,3 +24,8 @@ App\Entity\Period: description: Hauptlager start: '<(new DateTime("2021-01-01"))>' end: '<(new DateTime("2021-01-01"))>' + period1campShared: + camp: '@campShared' + description: Hauptlager + start: '<(new DateTime("2025-06-07"))>' + end: '<(new DateTime("2025-06-09"))>' diff --git a/api/fixtures/responsiveLayouts.yml b/api/fixtures/responsiveLayouts.yml index 1a1f05883d..e5b2edc656 100644 --- a/api/fixtures/responsiveLayouts.yml +++ b/api/fixtures/responsiveLayouts.yml @@ -7,3 +7,19 @@ App\Entity\ContentNode\ResponsiveLayout: data: { items: [{ slot: 'main' }, {slot: 'aside-top' }, {slot: 'aside-bottom'}] } instanceName: 'responsiveLayoutCampUnrelated' contentType: '@contentTypeResponsiveLayout' + responsiveLayoutCampPrototype: + root: '@columnLayout1campPrototype' + parent: '@columnLayout1campPrototype' + slot: '1' + position: 0 + data: { items: [{ slot: 'main' }, {slot: 'aside-top' }, {slot: 'aside-bottom'}] } + instanceName: 'responsiveLayoutCampPrototype' + contentType: '@contentTypeResponsiveLayout' + responsiveLayoutCampShared: + root: '@columnLayout1campShared' + parent: '@columnLayout1campShared' + slot: '1' + position: 0 + data: { items: [{ slot: 'main' }, {slot: 'aside-top' }, {slot: 'aside-bottom'}] } + instanceName: 'responsiveLayoutCampShared' + contentType: '@contentTypeResponsiveLayout' diff --git a/api/fixtures/scheduleEntries.yml b/api/fixtures/scheduleEntries.yml index bf1c70e3db..7a295f1e86 100644 --- a/api/fixtures/scheduleEntries.yml +++ b/api/fixtures/scheduleEntries.yml @@ -24,3 +24,13 @@ App\Entity\ScheduleEntry: activity: '@activity1campPrototype' startOffset: 720 endOffset: 780 + scheduleEntry1period1campShared: + period: '@period1campShared' + activity: '@activity1campShared' + startOffset: 660 + endOffset: 720 + scheduleEntry2period1campShared: + period: '@period1campShared' + activity: '@activity1campShared' + startOffset: 720 + endOffset: 780 diff --git a/api/fixtures/singleTexts.yml b/api/fixtures/singleTexts.yml index 36574aca7c..b2a2194c5c 100644 --- a/api/fixtures/singleTexts.yml +++ b/api/fixtures/singleTexts.yml @@ -7,3 +7,19 @@ App\Entity\ContentNode\SingleText: instanceName: contentType: '@contentTypeNotes' data: { html: } + singleTextCampPrototype: + root: '@columnLayout1campPrototype' + parent: '@columnLayout1campPrototype' + slot: '1' + position: 1 + instanceName: + contentType: '@contentTypeNotes' + data: { html: } + singleTextCampShared: + root: '@columnLayout1campShared' + parent: '@columnLayout1campShared' + slot: '1' + position: 1 + instanceName: + contentType: '@contentTypeNotes' + data: { html: } diff --git a/api/fixtures/storyboards.yml b/api/fixtures/storyboards.yml index 6408da8bfa..0e9ff596bf 100644 --- a/api/fixtures/storyboards.yml +++ b/api/fixtures/storyboards.yml @@ -19,3 +19,43 @@ App\Entity\ContentNode\Storyboard: }, }, } + storyboardCampPrototype: + root: '@columnLayout1campPrototype' + parent: '@columnLayout1campPrototype' + slot: '1' + position: 4 + instanceName: + contentType: '@contentTypeStoryboard' + data: + { + sections: + { + 'aaaaaaaa-3ccd-4d9a-ab40-dddddddddddd': + { + column1: , + column2Html: , + column3: , + position: 0, + }, + }, + } + storyboardCampShared: + root: '@columnLayout1campShared' + parent: '@columnLayout1campShared' + slot: '1' + position: 4 + instanceName: + contentType: '@contentTypeStoryboard' + data: + { + sections: + { + 'cccccccc-3ccd-4d9a-ab40-aaaaaaaaaaaa': + { + column1: , + column2Html: , + column3: , + position: 0, + }, + }, + } diff --git a/api/migrations/schema/Version20250821113132.php b/api/migrations/schema/Version20250821113132.php new file mode 100644 index 0000000000..99bffa3490 --- /dev/null +++ b/api/migrations/schema/Version20250821113132.php @@ -0,0 +1,50 @@ +addSql('ALTER TABLE camp ADD isShared BOOLEAN DEFAULT FALSE NOT NULL'); + $this->addSql('CREATE INDEX IDX_C1944230D2E4FE61 ON camp (isShared)'); + $this->addSql( + <<<'EOF' + CREATE OR REPLACE VIEW public.view_user_camps + AS + SELECT CONCAT(u.id, c.id) id, u.id userid, c.id campid + from camp c, "user" u + where c.isprototype = TRUE or c.isshared = TRUE + union all + select cc.id, cc.userid, cc.campid + from camp_collaboration cc + where cc.status = 'established' + EOF + ); + } + + public function down(Schema $schema): void { + $this->addSql( + <<<'EOF' + CREATE OR REPLACE VIEW public.view_user_camps + AS + SELECT CONCAT(u.id, c.id) id, u.id userid, c.id campid + from camp c, "user" u + where c.isprototype = TRUE + union all + select cc.id, cc.userid, cc.campid + from camp_collaboration cc + where cc.status = 'established' + EOF + ); + $this->addSql('DROP INDEX IDX_C1944230D2E4FE61'); + $this->addSql('ALTER TABLE camp DROP isShared'); + } +} diff --git a/api/migrations/schema/Version20250903125914.php b/api/migrations/schema/Version20250903125914.php new file mode 100644 index 0000000000..d0122be5c2 --- /dev/null +++ b/api/migrations/schema/Version20250903125914.php @@ -0,0 +1,33 @@ +addSql('ALTER TABLE camp ADD sharedSince TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL'); + $this->addSql('ALTER TABLE camp ADD sharedById VARCHAR(16) DEFAULT NULL'); + $this->addSql('ALTER TABLE camp ADD CONSTRAINT FK_C19442304B2BC976 FOREIGN KEY (sharedById) REFERENCES "user" (id) NOT DEFERRABLE'); + $this->addSql('CREATE INDEX IDX_C19442304B2BC976 ON camp (sharedById)'); + } + + public function down(Schema $schema): void { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE camp DROP CONSTRAINT FK_C19442304B2BC976'); + $this->addSql('DROP INDEX IDX_C19442304B2BC976'); + $this->addSql('ALTER TABLE camp DROP sharedSince'); + $this->addSql('ALTER TABLE camp DROP sharedById'); + } +} diff --git a/api/src/Doctrine/Filter/CampCollaboratorFilter.php b/api/src/Doctrine/Filter/CampCollaboratorFilter.php new file mode 100644 index 0000000000..cc23257575 --- /dev/null +++ b/api/src/Doctrine/Filter/CampCollaboratorFilter.php @@ -0,0 +1,75 @@ + [ + 'property' => self::QUERY_PARAM_NAME, + 'type' => Type::BUILTIN_TYPE_STRING, + 'required' => false, + ]]; + } + + protected function filterProperty( + string $property, + $value, + QueryBuilder $queryBuilder, + QueryNameGeneratorInterface $queryNameGenerator, + string $resourceClass, + ?Operation $operation = null, + array $context = [] + ): void { + if (!is_a($resourceClass, BelongsToCampInterface::class, true)) { + throw new \Exception("CampCollaboratorFilter can only be applied to entities which implement BelongsToCampInterface (received: {$resourceClass})."); + } + + if (self::QUERY_PARAM_NAME !== $property) { + return; + } + + $campAlias = Camp::class === $resourceClass + ? $queryBuilder->getRootAliases()[0] + : QueryBuilderHelper::findOrAddInnerRootJoinAlias($queryBuilder, $queryNameGenerator, 'camp'); + + // load user from query parameter value + $collaborator = $this->iriConverter->getResourceFromIri($value); + + $campsQry = $queryBuilder->getEntityManager()->createQueryBuilder(); + $campsQry->select('identity(cc.camp)'); + $campsQry->from(CampCollaboration::class, 'cc'); + $campsQry->andWhere('cc.user = :collaborator'); + $campsQry->andWhere('cc.status = :status_established'); + + $queryBuilder->andWhere($queryBuilder->expr()->in($campAlias, $campsQry->getDQL())); + $queryBuilder->setParameter('collaborator', $collaborator); + $queryBuilder->setParameter('status_established', CampCollaboration::STATUS_ESTABLISHED); + } +} diff --git a/api/src/Doctrine/Filter/ContentNodeCampFilter.php b/api/src/Doctrine/Filter/ContentNodeCampFilter.php index a21e282019..7263a7bb7e 100644 --- a/api/src/Doctrine/Filter/ContentNodeCampFilter.php +++ b/api/src/Doctrine/Filter/ContentNodeCampFilter.php @@ -8,7 +8,6 @@ use ApiPlatform\Metadata\Operation; use App\Entity\Activity; use App\Entity\ContentNode; -use App\Repository\FiltersByCampCollaboration; use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ManagerRegistry; use Psr\Log\LoggerInterface; @@ -16,8 +15,6 @@ use Symfony\Component\TypeInfo\Type; final class ContentNodeCampFilter extends AbstractFilter { - use FiltersByCampCollaboration; - public const CAMP_QUERY_NAME = 'camp'; public function __construct( diff --git a/api/src/Doctrine/Filter/ContentNodePeriodFilter.php b/api/src/Doctrine/Filter/ContentNodePeriodFilter.php index d9a21a6424..7f3ba0a474 100644 --- a/api/src/Doctrine/Filter/ContentNodePeriodFilter.php +++ b/api/src/Doctrine/Filter/ContentNodePeriodFilter.php @@ -8,7 +8,6 @@ use ApiPlatform\Metadata\Operation; use App\Entity\Activity; use App\Entity\ContentNode; -use App\Repository\FiltersByCampCollaboration; use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ManagerRegistry; @@ -17,8 +16,6 @@ use Symfony\Component\TypeInfo\Type; final class ContentNodePeriodFilter extends AbstractFilter { - use FiltersByCampCollaboration; - public const PERIOD_QUERY_NAME = 'period'; public function __construct( diff --git a/api/src/Entity/Activity.php b/api/src/Entity/Activity.php index 9178ada51c..ac83ec6346 100644 --- a/api/src/Entity/Activity.php +++ b/api/src/Entity/Activity.php @@ -32,7 +32,9 @@ operations: [ new Get( normalizationContext: self::ITEM_NORMALIZATION_CONTEXT, - security: 'is_granted("CAMP_COLLABORATOR", object) or is_granted("CAMP_IS_PROTOTYPE", object)' + security: 'is_granted("CAMP_COLLABORATOR", object) or + is_granted("CAMP_IS_SHARED", object) or + is_granted("CAMP_IS_PROTOTYPE", object)' ), new Patch( normalizationContext: self::ITEM_NORMALIZATION_CONTEXT, @@ -53,7 +55,9 @@ 'campId' => new Link( toProperty: 'camp', fromClass: Camp::class, - security: 'is_granted("CAMP_COLLABORATOR", camp) or is_granted("CAMP_IS_PROTOTYPE", camp)' + security: 'is_granted("CAMP_COLLABORATOR", camp) or + is_granted("CAMP_IS_SHARED", camp) or + is_granted("CAMP_IS_PROTOTYPE", camp)' ), ], normalizationContext: self::COLLECTION_NORMALIZATION_CONTEXT, diff --git a/api/src/Entity/ActivityProgressLabel.php b/api/src/Entity/ActivityProgressLabel.php index 0f687f381b..76fdb943a8 100644 --- a/api/src/Entity/ActivityProgressLabel.php +++ b/api/src/Entity/ActivityProgressLabel.php @@ -29,7 +29,9 @@ operations: [ new Get( normalizationContext: self::ITEM_NORMALIZATION_CONTEXT, - security: 'is_granted("CAMP_COLLABORATOR", object) or is_granted("CAMP_IS_PROTOTYPE", object)' + security: 'is_granted("CAMP_COLLABORATOR", object) or + is_granted("CAMP_IS_SHARED", object) or + is_granted("CAMP_IS_PROTOTYPE", object)' ), new Patch( normalizationContext: self::ITEM_NORMALIZATION_CONTEXT, @@ -50,7 +52,9 @@ 'campId' => new Link( toProperty: 'camp', fromClass: Camp::class, - security: 'is_granted("CAMP_COLLABORATOR", camp) or is_granted("CAMP_IS_PROTOTYPE", camp)' + security: 'is_granted("CAMP_COLLABORATOR", camp) or + is_granted("CAMP_IS_SHARED", camp) or + is_granted("CAMP_IS_PROTOTYPE", camp)' ), ], security: 'is_fully_authenticated()', diff --git a/api/src/Entity/ActivityResponsible.php b/api/src/Entity/ActivityResponsible.php index 33ede527ed..5b30504d26 100644 --- a/api/src/Entity/ActivityResponsible.php +++ b/api/src/Entity/ActivityResponsible.php @@ -23,7 +23,7 @@ #[ApiResource( operations: [ new Get( - security: 'is_granted("CAMP_COLLABORATOR", object) or is_granted("CAMP_IS_PROTOTYPE", object)' + security: 'is_granted("CAMP_COLLABORATOR", object) or is_granted("CAMP_IS_SHARED", object) or is_granted("CAMP_IS_PROTOTYPE", object)' ), new Delete( security: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object)' diff --git a/api/src/Entity/Camp.php b/api/src/Entity/Camp.php index b8725cd65f..bd604a195f 100644 --- a/api/src/Entity/Camp.php +++ b/api/src/Entity/Camp.php @@ -11,11 +11,13 @@ use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; +use App\Doctrine\Filter\CampCollaboratorFilter; use App\InputFilter; use App\Repository\CampRepository; use App\Serializer\Normalizer\RelatedCollectionLink; use App\State\CampCreateProcessor; use App\State\CampRemoveProcessor; +use App\State\CampUpdateProcessor; use App\Util\EntityMap; use App\Validator\AssertContainsAtLeastOneManager; use Doctrine\Common\Collections\ArrayCollection; @@ -32,10 +34,13 @@ #[ApiResource( operations: [ new Get( - security: 'is_granted("CAMP_COLLABORATOR", object) or is_granted("CAMP_IS_PROTOTYPE", object)', + security: 'is_granted("CAMP_COLLABORATOR", object) or + is_granted("CAMP_IS_SHARED", object) or + is_granted("CAMP_IS_PROTOTYPE", object)', normalizationContext: self::ITEM_NORMALIZATION_CONTEXT, ), new Patch( + processor: CampUpdateProcessor::class, security: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object)', denormalizationContext: ['groups' => ['write', 'update']], normalizationContext: self::ITEM_NORMALIZATION_CONTEXT, @@ -60,8 +65,11 @@ normalizationContext: ['groups' => ['read']] )] #[ApiFilter(filterClass: SearchFilter::class, properties: ['isPrototype'])] +#[ApiFilter(filterClass: CampCollaboratorFilter::class)] #[ORM\Entity(repositoryClass: CampRepository::class)] #[ORM\Index(columns: ['isPrototype'])] +#[ORM\Index(columns: ['isShared'])] +#[ORM\Index(columns: ['updateTime'])] // TODO unclear why this is necessary, but doctrine forgot about this index from BaseEntity... class Camp extends BaseEntity implements BelongsToCampInterface, CopyFromPrototypeInterface { public const ITEM_NORMALIZATION_CONTEXT = [ 'groups' => ['read', 'Camp:Periods', 'Period:Days', 'Camp:CampCollaborations', 'CampCollaboration:User'], @@ -181,6 +189,35 @@ class Camp extends BaseEntity implements BelongsToCampInterface, CopyFromPrototy #[Groups(['create'])] public ?Camp $campPrototype = null; + /** + * Whether the programme of this camp is publicly available to anyone (including + * personal data such as camp collaborations, personal material lists, + * responsibilities and comments). + */ + #[Assert\Type('bool')] + #[ApiProperty(example: true)] + #[Groups(['read', 'write'])] + #[ORM\Column(type: 'boolean', nullable: false, options: ['default' => false])] + public bool $isShared = false; + + /** + * Date and time when the camp was last set to be shared publicly. + */ + #[ApiProperty(example: '2025-10-01T00:00:00+00:00', openapiContext: ['format' => 'date-time'])] + #[Groups(['read'])] + #[ORM\Column(type: 'datetime', nullable: true)] + public ?\DateTimeInterface $sharedSince = null; + + /** + * The person who last set the camp to be shared publicly. + */ + #[Assert\DisableAutoMapping] + #[ApiProperty(writable: false)] + #[Groups(['read'])] + #[ORM\ManyToOne(targetEntity: User::class)] + #[ORM\JoinColumn(nullable: true)] + public ?User $sharedBy = null; + /** * Whether this camp may serve as a template for creating other camps. */ diff --git a/api/src/Entity/CampCollaboration.php b/api/src/Entity/CampCollaboration.php index e51ee0af97..6d9b502eec 100644 --- a/api/src/Entity/CampCollaboration.php +++ b/api/src/Entity/CampCollaboration.php @@ -35,7 +35,7 @@ operations: [ new Get( normalizationContext: self::ITEM_NORMALIZATION_CONTEXT, - security: 'is_granted("CAMP_COLLABORATOR", object) or is_granted("CAMP_IS_PROTOTYPE", object)' + security: 'is_granted("CAMP_COLLABORATOR", object) or is_granted("CAMP_IS_SHARED", object) or is_granted("CAMP_IS_PROTOTYPE", object)' ), new Patch( processor: CampCollaborationUpdateProcessor::class, @@ -67,7 +67,9 @@ 'campId' => new Link( toProperty: 'camp', fromClass: Camp::class, - security: 'is_granted("CAMP_COLLABORATOR", camp) or is_granted("CAMP_IS_PROTOTYPE", camp)' + security: 'is_granted("CAMP_COLLABORATOR", camp) or + is_granted("CAMP_IS_SHARED", camp) or + is_granted("CAMP_IS_PROTOTYPE", camp)' ), ], security: 'is_fully_authenticated()', diff --git a/api/src/Entity/Category.php b/api/src/Entity/Category.php index b2100a5ee7..771b189a72 100644 --- a/api/src/Entity/Category.php +++ b/api/src/Entity/Category.php @@ -35,7 +35,9 @@ operations: [ new Get( normalizationContext: self::ITEM_NORMALIZATION_CONTEXT, - security: 'is_granted("CAMP_COLLABORATOR", object) or is_granted("CAMP_IS_PROTOTYPE", object)' + security: 'is_granted("CAMP_COLLABORATOR", object) or + is_granted("CAMP_IS_SHARED", object) or + is_granted("CAMP_IS_PROTOTYPE", object)' ), new Patch( denormalizationContext: ['groups' => ['write', 'update']], @@ -63,7 +65,9 @@ 'campId' => new Link( fromClass: Camp::class, toProperty: 'camp', - security: 'is_granted("CAMP_COLLABORATOR", camp) or is_granted("CAMP_IS_PROTOTYPE", camp)' + security: 'is_granted("CAMP_COLLABORATOR", camp) or + is_granted("CAMP_IS_SHARED", camp) or + is_granted("CAMP_IS_PROTOTYPE", camp)' ), ], extraProperties: [ diff --git a/api/src/Entity/Checklist.php b/api/src/Entity/Checklist.php index bca408e6e0..3dcb87c200 100644 --- a/api/src/Entity/Checklist.php +++ b/api/src/Entity/Checklist.php @@ -31,6 +31,7 @@ new Get( security: 'is_granted("CHECKLIST_IS_PROTOTYPE", object) or is_granted("CAMP_IS_PROTOTYPE", object) or + is_granted("CAMP_IS_SHARED", object) or is_granted("CAMP_COLLABORATOR", object) ' ), @@ -62,7 +63,9 @@ 'campId' => new Link( toProperty: 'camp', fromClass: Camp::class, - security: 'is_granted("CAMP_COLLABORATOR", camp) or is_granted("CAMP_IS_PROTOTYPE", camp)' + security: 'is_granted("CAMP_COLLABORATOR", camp) or + is_granted("CAMP_IS_SHARED", camp) or + is_granted("CAMP_IS_PROTOTYPE", camp)' ), ], extraProperties: [ diff --git a/api/src/Entity/ChecklistItem.php b/api/src/Entity/ChecklistItem.php index 954ea10675..251c6dce5c 100644 --- a/api/src/Entity/ChecklistItem.php +++ b/api/src/Entity/ChecklistItem.php @@ -32,8 +32,9 @@ #[ApiResource( operations: [ new Get( - security: 'is_granted("CHECKLIST_IS_PROTOTYPE", object) or - is_granted("CAMP_IS_PROTOTYPE", object) or + security: 'is_granted("CHECKLIST_IS_PROTOTYPE", object) or + is_granted("CAMP_IS_PROTOTYPE", object) or + is_granted("CAMP_IS_SHARED", object) or is_granted("CAMP_COLLABORATOR", object) ' ), @@ -63,8 +64,9 @@ 'checklistId' => new Link( fromClass: Checklist::class, toProperty: 'checklist', - security: 'is_granted("CHECKLIST_IS_PROTOTYPE", checklist) or - is_granted("CAMP_IS_PROTOTYPE", checklist) or + security: 'is_granted("CHECKLIST_IS_PROTOTYPE", checklist) or + is_granted("CAMP_IS_PROTOTYPE", checklist) or + is_granted("CAMP_IS_SHARED", checklist) or is_granted("CAMP_COLLABORATOR", checklist)' ), ], diff --git a/api/src/Entity/Comment.php b/api/src/Entity/Comment.php index 34fee75ff4..2e54e037d5 100644 --- a/api/src/Entity/Comment.php +++ b/api/src/Entity/Comment.php @@ -26,7 +26,10 @@ #[ApiResource( operations: [ new Get( - security: 'is_granted("CAMP_COLLABORATOR", object) or object.author === user', + security: 'is_granted("CAMP_COLLABORATOR", object) or + is_granted("CAMP_IS_SHARED", object) or + is_granted("CAMP_IS_PROTOTYPE", object) or + object.author === user', ), new Delete( security: 'object.author === user', @@ -45,7 +48,9 @@ 'activityId' => new Link( toProperty: 'activity', fromClass: Activity::class, - security: 'is_granted("CAMP_COLLABORATOR", activity)', + security: 'is_granted("CAMP_COLLABORATOR", activity) or + is_granted("CAMP_IS_SHARED", activity) or + is_granted("CAMP_IS_PROTOTYPE", activity)', ), ], security: 'is_fully_authenticated()', diff --git a/api/src/Entity/ContentNode.php b/api/src/Entity/ContentNode.php index 5c80a12c08..f31c888a0f 100644 --- a/api/src/Entity/ContentNode.php +++ b/api/src/Entity/ContentNode.php @@ -78,7 +78,7 @@ abstract class ContentNode extends BaseEntity implements BelongsToCampInterface, #[AssertAttachedToRoot(groups: ['update'])] #[Assert\Type( type: SupportsContentNodeChildren::class, - message: 'This parent does not support children, only content_nodes of type column_layout support children.' + message: 'This parent does not support children, only content_nodes of type column_layout or responsive_layout support children.' )] #[ApiProperty(example: '/content_nodes/1a2b3c4d')] #[Gedmo\SortableGroup] diff --git a/api/src/Entity/ContentNode/ChecklistNode.php b/api/src/Entity/ContentNode/ChecklistNode.php index e7f3a8d58e..d46a4063e2 100644 --- a/api/src/Entity/ContentNode/ChecklistNode.php +++ b/api/src/Entity/ContentNode/ChecklistNode.php @@ -23,7 +23,9 @@ #[ApiResource( operations: [ new Get( - security: 'is_granted("CAMP_COLLABORATOR", object) or is_granted("CAMP_IS_PROTOTYPE", object)' + security: 'is_granted("CAMP_COLLABORATOR", object) or + is_granted("CAMP_IS_SHARED", object) or + is_granted("CAMP_IS_PROTOTYPE", object)' ), new Patch( processor: ChecklistNodePersistProcessor::class, diff --git a/api/src/Entity/ContentNode/ColumnLayout.php b/api/src/Entity/ContentNode/ColumnLayout.php index a2ce982d57..88cce2b3eb 100644 --- a/api/src/Entity/ContentNode/ColumnLayout.php +++ b/api/src/Entity/ContentNode/ColumnLayout.php @@ -25,7 +25,9 @@ #[ApiResource( operations: [ new Get( - security: 'is_granted("CAMP_COLLABORATOR", object) or is_granted("CAMP_IS_PROTOTYPE", object)' + security: 'is_granted("CAMP_COLLABORATOR", object) or + is_granted("CAMP_IS_SHARED", object) or + is_granted("CAMP_IS_PROTOTYPE", object)' ), new Patch( processor: ContentNodePersistProcessor::class, diff --git a/api/src/Entity/ContentNode/MaterialNode.php b/api/src/Entity/ContentNode/MaterialNode.php index 541773fdf5..03252d1b5c 100644 --- a/api/src/Entity/ContentNode/MaterialNode.php +++ b/api/src/Entity/ContentNode/MaterialNode.php @@ -23,7 +23,9 @@ #[ApiResource( operations: [ new Get( - security: 'is_granted("CAMP_COLLABORATOR", object) or is_granted("CAMP_IS_PROTOTYPE", object)' + security: 'is_granted("CAMP_COLLABORATOR", object) or + is_granted("CAMP_IS_SHARED", object) or + is_granted("CAMP_IS_PROTOTYPE", object)' ), new Patch( processor: ContentNodePersistProcessor::class, diff --git a/api/src/Entity/ContentNode/MultiSelect.php b/api/src/Entity/ContentNode/MultiSelect.php index 0913476eb8..60cf19d4bc 100644 --- a/api/src/Entity/ContentNode/MultiSelect.php +++ b/api/src/Entity/ContentNode/MultiSelect.php @@ -21,7 +21,9 @@ #[ApiResource( operations: [ new Get( - security: 'is_granted("CAMP_COLLABORATOR", object) or is_granted("CAMP_IS_PROTOTYPE", object)' + security: 'is_granted("CAMP_COLLABORATOR", object) or + is_granted("CAMP_IS_SHARED", object) or + is_granted("CAMP_IS_PROTOTYPE", object)' ), new Patch( processor: ContentNodePersistProcessor::class, diff --git a/api/src/Entity/ContentNode/ResponsiveLayout.php b/api/src/Entity/ContentNode/ResponsiveLayout.php index d57061c5ad..89f2e8988f 100644 --- a/api/src/Entity/ContentNode/ResponsiveLayout.php +++ b/api/src/Entity/ContentNode/ResponsiveLayout.php @@ -24,7 +24,9 @@ #[ApiResource( operations: [ new Get( - security: 'is_granted("CAMP_COLLABORATOR", object) or is_granted("CAMP_IS_PROTOTYPE", object)' + security: 'is_granted("CAMP_COLLABORATOR", object) or + is_granted("CAMP_IS_SHARED", object) or + is_granted("CAMP_IS_PROTOTYPE", object)' ), new Patch( processor: ContentNodePersistProcessor::class, diff --git a/api/src/Entity/ContentNode/SingleText.php b/api/src/Entity/ContentNode/SingleText.php index 2d9f41ae21..674fa89cfe 100644 --- a/api/src/Entity/ContentNode/SingleText.php +++ b/api/src/Entity/ContentNode/SingleText.php @@ -20,7 +20,9 @@ #[ApiResource( operations: [ new Get( - security: 'is_granted("CAMP_COLLABORATOR", object) or is_granted("CAMP_IS_PROTOTYPE", object)' + security: 'is_granted("CAMP_COLLABORATOR", object) or + is_granted("CAMP_IS_SHARED", object) or + is_granted("CAMP_IS_PROTOTYPE", object)' ), new Patch( processor: SingleTextPersistProcessor::class, diff --git a/api/src/Entity/ContentNode/Storyboard.php b/api/src/Entity/ContentNode/Storyboard.php index 9d97acf115..e577a08912 100644 --- a/api/src/Entity/ContentNode/Storyboard.php +++ b/api/src/Entity/ContentNode/Storyboard.php @@ -20,7 +20,9 @@ #[ApiResource( operations: [ new Get( - security: 'is_granted("CAMP_COLLABORATOR", object) or is_granted("CAMP_IS_PROTOTYPE", object)' + security: 'is_granted("CAMP_COLLABORATOR", object) or + is_granted("CAMP_IS_SHARED", object) or + is_granted("CAMP_IS_PROTOTYPE", object)' ), new Patch( processor: StoryboardPersistProcessor::class, diff --git a/api/src/Entity/Day.php b/api/src/Entity/Day.php index 0d36eb85a3..7877604803 100644 --- a/api/src/Entity/Day.php +++ b/api/src/Entity/Day.php @@ -28,7 +28,9 @@ operations: [ new Get( normalizationContext: self::ITEM_NORMALIZATION_CONTEXT, - security: 'is_granted("CAMP_COLLABORATOR", object) or is_granted("CAMP_IS_PROTOTYPE", object)' + security: 'is_granted("CAMP_COLLABORATOR", object) or + is_granted("CAMP_IS_SHARED", object) or + is_granted("CAMP_IS_PROTOTYPE", object)' ), new GetCollection( normalizationContext: self::COLLECTION_NORMALIZATION_CONTEXT, @@ -40,7 +42,9 @@ 'periodId' => new Link( toProperty: 'period', fromClass: Period::class, - security: 'is_granted("CAMP_COLLABORATOR", period) or is_granted("CAMP_IS_PROTOTYPE", period)' + security: 'is_granted("CAMP_COLLABORATOR", period) or + is_granted("CAMP_IS_SHARED", period) or + is_granted("CAMP_IS_PROTOTYPE", period)' ), ], normalizationContext: self::COLLECTION_NORMALIZATION_CONTEXT, diff --git a/api/src/Entity/DayResponsible.php b/api/src/Entity/DayResponsible.php index 1ad82f21b6..8b1d024d8f 100644 --- a/api/src/Entity/DayResponsible.php +++ b/api/src/Entity/DayResponsible.php @@ -24,7 +24,9 @@ #[ApiResource( operations: [ new Get( - security: 'is_granted("CAMP_COLLABORATOR", object) or is_granted("CAMP_IS_PROTOTYPE", object)' + security: 'is_granted("CAMP_COLLABORATOR", object) or + is_granted("CAMP_IS_SHARED", object) or + is_granted("CAMP_IS_PROTOTYPE", object)' ), new Delete( security: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object)' @@ -38,7 +40,9 @@ 'dayId' => new Link( toProperty: 'day', fromClass: Day::class, - security: 'is_granted("CAMP_COLLABORATOR", day) or is_granted("CAMP_IS_PROTOTYPE", day)' + security: 'is_granted("CAMP_COLLABORATOR", day) or + is_granted("CAMP_IS_SHARED", day) or + is_granted("CAMP_IS_PROTOTYPE", day)' ), ], extraProperties: [ diff --git a/api/src/Entity/MaterialItem.php b/api/src/Entity/MaterialItem.php index c5e979b166..737bd41d59 100644 --- a/api/src/Entity/MaterialItem.php +++ b/api/src/Entity/MaterialItem.php @@ -32,7 +32,9 @@ #[ApiResource( operations: [ new Get( - security: 'is_granted("CAMP_COLLABORATOR", object) or is_granted("CAMP_IS_PROTOTYPE", object)' + security: 'is_granted("CAMP_COLLABORATOR", object) or + is_granted("CAMP_IS_SHARED", object) or + is_granted("CAMP_IS_PROTOTYPE", object)' ), new Patch( validationContext: ['groups' => MaterialItemUpdateGroupSequence::class], diff --git a/api/src/Entity/MaterialList.php b/api/src/Entity/MaterialList.php index 3900cd6050..2d6982c15e 100644 --- a/api/src/Entity/MaterialList.php +++ b/api/src/Entity/MaterialList.php @@ -28,7 +28,9 @@ #[ApiResource( operations: [ new Get( - security: 'is_granted("CAMP_COLLABORATOR", object) or is_granted("CAMP_IS_PROTOTYPE", object)' + security: 'is_granted("CAMP_COLLABORATOR", object) or + is_granted("CAMP_IS_SHARED", object) or + is_granted("CAMP_IS_PROTOTYPE", object)' ), new Patch( security: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object)' diff --git a/api/src/Entity/Period.php b/api/src/Entity/Period.php index ce631785fa..3dc019376a 100644 --- a/api/src/Entity/Period.php +++ b/api/src/Entity/Period.php @@ -11,6 +11,7 @@ use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; +use App\Doctrine\Filter\CampCollaboratorFilter; use App\InputFilter; use App\Repository\PeriodRepository; use App\Serializer\Normalizer\RelatedCollectionLink; @@ -34,7 +35,9 @@ #[ApiResource( operations: [ new Get( - security: 'is_granted("CAMP_COLLABORATOR", object) or is_granted("CAMP_IS_PROTOTYPE", object)', + security: 'is_granted("CAMP_COLLABORATOR", object) or + is_granted("CAMP_IS_SHARED", object) or + is_granted("CAMP_IS_PROTOTYPE", object)', normalizationContext: self::ITEM_NORMALIZATION_CONTEXT, ), new Patch( @@ -60,6 +63,7 @@ order: ['start'] )] #[ApiFilter(filterClass: SearchFilter::class, properties: ['camp'])] +#[ApiFilter(filterClass: CampCollaboratorFilter::class)] #[ORM\Entity(repositoryClass: PeriodRepository::class)] class Period extends BaseEntity implements BelongsToCampInterface { public const ITEM_NORMALIZATION_CONTEXT = [ diff --git a/api/src/Entity/ScheduleEntry.php b/api/src/Entity/ScheduleEntry.php index 32a180d961..ad2e7ec95d 100644 --- a/api/src/Entity/ScheduleEntry.php +++ b/api/src/Entity/ScheduleEntry.php @@ -31,7 +31,9 @@ operations: [ new Get( normalizationContext: self::ITEM_NORMALIZATION_CONTEXT, - security: 'is_granted("CAMP_COLLABORATOR", object) or is_granted("CAMP_IS_PROTOTYPE", object)' + security: 'is_granted("CAMP_COLLABORATOR", object) or + is_granted("CAMP_IS_SHARED", object) or + is_granted("CAMP_IS_PROTOTYPE", object)' ), new Patch( normalizationContext: self::ITEM_NORMALIZATION_CONTEXT, @@ -51,7 +53,9 @@ 'periodId' => new Link( toProperty: 'period', fromClass: Period::class, - security: 'is_granted("CAMP_COLLABORATOR", period) or is_granted("CAMP_IS_PROTOTYPE", period)' + security: 'is_granted("CAMP_COLLABORATOR", period) or + is_granted("CAMP_IS_SHARED", period) or + is_granted("CAMP_IS_PROTOTYPE", period)' ), ], security: 'is_fully_authenticated()', diff --git a/api/src/Security/Voter/CampIsSharedVoter.php b/api/src/Security/Voter/CampIsSharedVoter.php new file mode 100644 index 0000000000..9738f9011f --- /dev/null +++ b/api/src/Security/Voter/CampIsSharedVoter.php @@ -0,0 +1,44 @@ + + */ +class CampIsSharedVoter extends Voter { + use GetCampFromContentNodeTrait; + + public function __construct( + private readonly EntityManagerInterface $em, + private readonly ResponseTagger $responseTagger + ) {} + + protected function supports($attribute, $subject): bool { + return 'CAMP_IS_SHARED' === $attribute + && ($subject instanceof BelongsToCampInterface || $subject instanceof BelongsToContentNodeTreeInterface); + } + + protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool { + $camp = $this->getCampFromInterface($subject, $this->em); + + if (null === $camp) { + return false; + } + + if ($camp->isShared) { + $this->responseTagger->addTags([$camp->getId()]); + + return true; + } + + return false; + } +} diff --git a/api/src/State/CampUpdateProcessor.php b/api/src/State/CampUpdateProcessor.php new file mode 100644 index 0000000000..e13921db33 --- /dev/null +++ b/api/src/State/CampUpdateProcessor.php @@ -0,0 +1,45 @@ + + */ +class CampUpdateProcessor extends AbstractPersistProcessor { + public function __construct( + ProcessorInterface $decorated, + private Security $security, + ) { + $sharingChangeListener = PropertyChangeListener::of( + extractProperty: fn (Camp $data) => $data->isShared, + beforeAction: fn (Camp $data) => $this->onBeforeSharingStatusChange($data), + ); + + parent::__construct( + $decorated, + propertyChangeListeners: [ + $sharingChangeListener, + ] + ); + } + + public function onBeforeSharingStatusChange(Camp $data): Camp { + if ($data->isShared) { + $data->sharedSince = new \DateTime('now', new \DateTimeZone('UTC')); + + /** @var User $user */ + $user = $this->security->getUser(); + $data->sharedBy = $user; + } + + return $data; + } +} diff --git a/api/tests/Api/Activities/CreateActivityTest.php b/api/tests/Api/Activities/CreateActivityTest.php index 6b34d582b4..be753bd2e4 100644 --- a/api/tests/Api/Activities/CreateActivityTest.php +++ b/api/tests/Api/Activities/CreateActivityTest.php @@ -87,6 +87,88 @@ public function testCreateActivityIsAllowedForManager() { public function testCreateActivityInCampPrototypeIsDeniedForUnrelatedUser() { static::createClientWithCredentials()->request('POST', '/activities', ['json' => $this->getExampleWritePayload([ 'category' => $this->getIriFor('category1campPrototype'), + 'scheduleEntries' => [ + [ + 'period' => $this->getIriFor('period1campPrototype'), + 'start' => '2023-05-01T15:00:00+00:00', + 'end' => '2023-05-01T16:00:00+00:00', + ], + ], + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testCreateActivityInSharedCampIsDeniedForUnrelatedUser() { + static::createClientWithCredentials()->request('POST', '/activities', ['json' => $this->getExampleWritePayload([ + 'category' => $this->getIriFor('category1campShared'), + 'scheduleEntries' => [ + [ + 'period' => $this->getIriFor('period1campShared'), + 'start' => '2023-05-01T15:00:00+00:00', + 'end' => '2023-05-01T16:00:00+00:00', + ], + ], + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testCreateActivityInSharedCampIsAllowedForManager() { + static::createClientWithCredentials(['email' => static::getFixture('user4unrelated')->getEmail()]) + ->request('POST', '/activities', ['json' => $this->getExampleWritePayload([ + 'category' => $this->getIriFor('category1campShared'), + 'scheduleEntries' => [ + [ + 'period' => $this->getIriFor('period1campShared'), + 'start' => '2025-06-07T15:00:00+00:00', + 'end' => '2025-06-07T16:00:00+00:00', + ], + ], + ])]) + ; + + $this->assertResponseStatusCodeSame(201); + $this->assertJsonContains($this->getExampleReadPayload()); + } + + public function testCreateActivityInSharedCampIsDeniedForInactiveUser() { + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()])->request('POST', '/activities', ['json' => $this->getExampleWritePayload([ + 'category' => $this->getIriFor('category1campShared'), + 'scheduleEntries' => [ + [ + 'period' => $this->getIriFor('period1campShared'), + 'start' => '2023-05-01T15:00:00+00:00', + 'end' => '2023-05-01T16:00:00+00:00', + ], + ], + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testCreateActivityInSharedCampIsDeniedForInvitedUser() { + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()])->request('POST', '/activities', ['json' => $this->getExampleWritePayload([ + 'category' => $this->getIriFor('category1campShared'), + 'scheduleEntries' => [ + [ + 'period' => $this->getIriFor('period1campShared'), + 'start' => '2023-05-01T15:00:00+00:00', + 'end' => '2023-05-01T16:00:00+00:00', + ], + ], ])]); $this->assertResponseStatusCodeSame(403); diff --git a/api/tests/Api/Activities/DeleteActivityTest.php b/api/tests/Api/Activities/DeleteActivityTest.php index 04637d8087..a7f7887e89 100644 --- a/api/tests/Api/Activities/DeleteActivityTest.php +++ b/api/tests/Api/Activities/DeleteActivityTest.php @@ -85,6 +85,47 @@ public function testDeleteActivityFromCampPrototypeIsDeniedForUnrelatedUser() { ]); } + public function testDeleteActivityFromSharedCampIsDeniedForUnrelatedUser() { + $activity = static::getFixture('activity1campShared'); + static::createClientWithCredentials()->request('DELETE', '/activities/'.$activity->getId()); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testDeleteActivityFromSharedCampIsDeniedForInactiveUser() { + $activity = static::getFixture('activity1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()])->request('DELETE', '/activities/'.$activity->getId()); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testDeleteActivityFromSharedCampIsDeniedForInvitedUser() { + $activity = static::getFixture('activity1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()])->request('DELETE', '/activities/'.$activity->getId()); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testDeleteActivityFromSharedCampIsAllowedForManager() { + $activity = static::getFixture('activity1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user4unrelated']->getEmail()])->request('DELETE', '/activities/'.$activity->getId()); + + $this->assertResponseStatusCodeSame(204); + $this->assertNull($this->getEntityManager()->getRepository(Activity::class)->find($activity->getId())); + } + public function testDeleteActivityAlsoDeletesContentNodes() { $client = static::createClientWithCredentials(); // Disable resetting the database between the two requests diff --git a/api/tests/Api/Activities/ListActivitiesTest.php b/api/tests/Api/Activities/ListActivitiesTest.php index 8832f968d9..1226b68a08 100644 --- a/api/tests/Api/Activities/ListActivitiesTest.php +++ b/api/tests/Api/Activities/ListActivitiesTest.php @@ -24,7 +24,7 @@ public function testListActivitiesIsAllowedForLoggedInUserButFiltered() { $response = static::createClientWithCredentials()->request('GET', '/activities'); $this->assertResponseStatusCodeSame(200); $this->assertJsonContains([ - 'totalItems' => 4, + 'totalItems' => 5, '_links' => [ 'items' => [], ], @@ -37,6 +37,7 @@ public function testListActivitiesIsAllowedForLoggedInUserButFiltered() { ['href' => $this->getIriFor('activity2')], ['href' => $this->getIriFor('activity1camp2')], ['href' => $this->getIriFor('activity1campPrototype')], + ['href' => $this->getIriFor('activity1campShared')], ], $response->toArray()['_links']['items']); } diff --git a/api/tests/Api/Activities/ReadActivityTest.php b/api/tests/Api/Activities/ReadActivityTest.php index a401574e42..7a2bdfde71 100644 --- a/api/tests/Api/Activities/ReadActivityTest.php +++ b/api/tests/Api/Activities/ReadActivityTest.php @@ -132,4 +132,72 @@ public function testGetSingleActivityFromCampPrototypeIsAllowedForUnrelatedUser( ], ]); } + + public function testGetSingleActivityFromSharedCampIsAllowedForUnrelatedUser() { + /** @var Activity $activity */ + $activity = static::getFixture('activity1campShared'); + static::createClientWithCredentials()->request('GET', '/activities/'.$activity->getId()); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $activity->getId(), + 'title' => $activity->title, + 'location' => $activity->location, + '_links' => [ + 'rootContentNode' => ['href' => $this->getIriFor('columnLayout1campShared')], + 'category' => ['href' => $this->getIriFor('category1campShared')], + 'camp' => ['href' => $this->getIriFor('campShared')], + ], + ]); + } + + public function testGetSingleActivityFromSharedCampIsAllowedForInactiveUser() { + /** @var Activity $activity */ + $activity = static::getFixture('activity1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()])->request('GET', '/activities/'.$activity->getId()); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $activity->getId(), + 'title' => $activity->title, + 'location' => $activity->location, + '_links' => [ + 'rootContentNode' => ['href' => $this->getIriFor('columnLayout1campShared')], + 'category' => ['href' => $this->getIriFor('category1campShared')], + 'camp' => ['href' => $this->getIriFor('campShared')], + ], + ]); + } + + public function testGetSingleActivityFromSharedCampIsAllowedForInvitedUser() { + /** @var Activity $activity */ + $activity = static::getFixture('activity1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()])->request('GET', '/activities/'.$activity->getId()); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $activity->getId(), + 'title' => $activity->title, + 'location' => $activity->location, + '_links' => [ + 'rootContentNode' => ['href' => $this->getIriFor('columnLayout1campShared')], + 'category' => ['href' => $this->getIriFor('category1campShared')], + 'camp' => ['href' => $this->getIriFor('campShared')], + ], + ]); + } + + public function testGetSingleActivityFromSharedCampIsAllowedForManager() { + /** @var Activity $activity */ + $activity = static::getFixture('activity1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user4unrelated']->getEmail()])->request('GET', '/activities/'.$activity->getId()); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $activity->getId(), + 'title' => $activity->title, + 'location' => $activity->location, + '_links' => [ + 'rootContentNode' => ['href' => $this->getIriFor('columnLayout1campShared')], + 'category' => ['href' => $this->getIriFor('category1campShared')], + 'camp' => ['href' => $this->getIriFor('campShared')], + ], + ]); + } } diff --git a/api/tests/Api/Activities/UpdateActivityTest.php b/api/tests/Api/Activities/UpdateActivityTest.php index cd9ac0cc70..08fdf1e713 100644 --- a/api/tests/Api/Activities/UpdateActivityTest.php +++ b/api/tests/Api/Activities/UpdateActivityTest.php @@ -120,6 +120,61 @@ public function testPatchActivityFromCampPrototypeIsDeniedForUnrelatedUser() { ]); } + public function testPatchActivityFromSharedCampIsDeniedForUnrelatedUser() { + $activity = static::getFixture('activity1campShared'); + static::createClientWithCredentials()->request('PATCH', '/activities/'.$activity->getId(), ['json' => [ + 'title' => 'Hello World', + 'location' => 'Stoos', + 'category' => $this->getIriFor('category2'), + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testPatchActivityFromSharedCampIsDeniedForInactiveUser() { + $activity = static::getFixture('activity1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()])->request('PATCH', '/activities/'.$activity->getId(), ['json' => [ + 'title' => 'Hello World', + 'location' => 'Stoos', + 'category' => $this->getIriFor('category2'), + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testPatchActivityFromSharedCampIsDeniedForInvitedUser() { + $activity = static::getFixture('activity1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()])->request('PATCH', '/activities/'.$activity->getId(), ['json' => [ + 'title' => 'Hello World', + 'location' => 'Stoos', + 'category' => $this->getIriFor('category2'), + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testPatchActivityFromSharedCampIsAllowedForManager() { + $activity = static::getFixture('activity1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user4unrelated']->getEmail()])->request('PATCH', '/activities/'.$activity->getId(), ['json' => [ + 'title' => 'Hello World', + 'location' => 'Stoos', + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'title' => 'Hello World', + 'location' => 'Stoos', + ]); + } + public function testPatchActivityValidatesCategoryFromSameCamp() { $activity = static::getFixture('activity1'); static::createClientWithCredentials()->request('PATCH', '/activities/'.$activity->getId(), ['json' => [ diff --git a/api/tests/Api/ActivityProgressLabel/CreateActivityProgressLabelTest.php b/api/tests/Api/ActivityProgressLabels/CreateActivityProgressLabelTest.php similarity index 79% rename from api/tests/Api/ActivityProgressLabel/CreateActivityProgressLabelTest.php rename to api/tests/Api/ActivityProgressLabels/CreateActivityProgressLabelTest.php index 80158d7179..413127b09c 100644 --- a/api/tests/Api/ActivityProgressLabel/CreateActivityProgressLabelTest.php +++ b/api/tests/Api/ActivityProgressLabels/CreateActivityProgressLabelTest.php @@ -1,6 +1,6 @@ assertJsonContains($this->getExampleReadPayload(['position' => 2])); } + public function testCreateActivityProgressLabelInCampPrototypeIsDeniedForUnrelatedUser() { + static::createClientWithCredentials()->request('POST', '/activity_progress_labels', ['json' => $this->getExampleWritePayload([ + 'camp' => $this->getIriFor('campPrototype'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testCreateActivityProgressLabelInSharedCampIsDeniedForUnrelatedUser() { + static::createClientWithCredentials()->request('POST', '/activity_progress_labels', ['json' => $this->getExampleWritePayload([ + 'camp' => $this->getIriFor('campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testCreateActivityProgressLabelInSharedCampIsDeniedForInactiveUser() { + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()])->request('POST', '/activity_progress_labels', ['json' => $this->getExampleWritePayload([ + 'camp' => $this->getIriFor('campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testCreateActivityProgressLabelInSharedCampIsDeniedForInvitedUser() { + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()])->request('POST', '/activity_progress_labels', ['json' => $this->getExampleWritePayload([ + 'camp' => $this->getIriFor('campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + public function testCreateActivityProgressLabelValidatesMissingCamp() { static::createClientWithCredentials() ->request('POST', '/activity_progress_labels', ['json' => $this->getExampleWritePayload([], ['camp'])]) diff --git a/api/tests/Api/ActivityProgressLabel/DeleteActivityProgressLabelTest.php b/api/tests/Api/ActivityProgressLabels/DeleteActivityProgressLabelTest.php similarity index 64% rename from api/tests/Api/ActivityProgressLabel/DeleteActivityProgressLabelTest.php rename to api/tests/Api/ActivityProgressLabels/DeleteActivityProgressLabelTest.php index baacad814d..2c0cb56c51 100644 --- a/api/tests/Api/ActivityProgressLabel/DeleteActivityProgressLabelTest.php +++ b/api/tests/Api/ActivityProgressLabels/DeleteActivityProgressLabelTest.php @@ -1,6 +1,6 @@ assertResponseStatusCodeSame(204); $this->assertNull($this->getEntityManager()->getRepository(ActivityProgressLabel::class)->find($activityProgressLabel->getId())); } + + public function testDeleteActivityProgressLabelFromCampPrototypeIsDeniedForUnrelatedUser() { + $activityProgressLabel = static::getFixture('activityProgressLabel1campPrototype'); + static::createClientWithCredentials()->request('DELETE', '/activity_progress_labels/'.$activityProgressLabel->getId()); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testDeleteActivityProgressLabelFromSharedCampIsDeniedForUnrelatedUser() { + $activityProgressLabel = static::getFixture('activityProgressLabel1campShared'); + static::createClientWithCredentials()->request('DELETE', '/activity_progress_labels/'.$activityProgressLabel->getId()); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testDeleteActivityProgressLabelFromSharedCampIsDeniedForInactiveUser() { + $activityProgressLabel = static::getFixture('activityProgressLabel1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()])->request('DELETE', '/activity_progress_labels/'.$activityProgressLabel->getId()); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testDeleteActivityProgressLabelFromSharedCampIsDeniedForInvitedUser() { + $activityProgressLabel = static::getFixture('activityProgressLabel1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()])->request('DELETE', '/activity_progress_labels/'.$activityProgressLabel->getId()); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } } diff --git a/api/tests/Api/ActivityProgressLabel/ListActivityProgressLabelTest.php b/api/tests/Api/ActivityProgressLabels/ListActivityProgressLabelsTest.php similarity index 93% rename from api/tests/Api/ActivityProgressLabel/ListActivityProgressLabelTest.php rename to api/tests/Api/ActivityProgressLabels/ListActivityProgressLabelsTest.php index cbca16443c..489d42e2ee 100644 --- a/api/tests/Api/ActivityProgressLabel/ListActivityProgressLabelTest.php +++ b/api/tests/Api/ActivityProgressLabels/ListActivityProgressLabelsTest.php @@ -1,13 +1,13 @@ request('GET', '/activity_progress_labels') @@ -28,7 +28,7 @@ public function testListActivityProgressLabelsIsAllowedForLoggedInUserButFiltere ; $this->assertResponseStatusCodeSame(200); $this->assertJsonContains([ - 'totalItems' => 7, + 'totalItems' => 9, '_links' => [ 'items' => [], ], @@ -44,6 +44,8 @@ public function testListActivityProgressLabelsIsAllowedForLoggedInUserButFiltere ['href' => $this->getIriFor('activityProgressLabel1campPrototype')], ['href' => $this->getIriFor('activityProgressLabel2campPrototype')], ['href' => $this->getIriFor('activityProgressLabel3campPrototype')], + ['href' => $this->getIriFor('activityProgressLabel1campShared')], + ['href' => $this->getIriFor('activityProgressLabel2campShared')], ], $response->toArray()['_links']['items']); } diff --git a/api/tests/Api/ActivityProgressLabel/ReadActivityProgressLabelTest.php b/api/tests/Api/ActivityProgressLabels/ReadActivityProgressLabelTest.php similarity index 58% rename from api/tests/Api/ActivityProgressLabel/ReadActivityProgressLabelTest.php rename to api/tests/Api/ActivityProgressLabels/ReadActivityProgressLabelTest.php index 1dc0ba352d..14377800d5 100644 --- a/api/tests/Api/ActivityProgressLabel/ReadActivityProgressLabelTest.php +++ b/api/tests/Api/ActivityProgressLabels/ReadActivityProgressLabelTest.php @@ -1,6 +1,6 @@ request('GET', '/activity_progress_labels/'.$activityProgressLabel->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $activityProgressLabel->getId(), + '_links' => [ + 'camp' => ['href' => $this->getIriFor('campPrototype')], + ], + ]); + } + + public function testGetSingleActivityProgressLabelFromSharedCampIsAllowedForUnrelatedUser() { + /** @var ActivityProgressLabel $activityProgressLabel */ + $activityProgressLabel = static::getFixture('activityProgressLabel1campShared'); + static::createClientWithCredentials() + ->request('GET', '/activity_progress_labels/'.$activityProgressLabel->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $activityProgressLabel->getId(), + '_links' => [ + 'camp' => ['href' => $this->getIriFor('campShared')], + ], + ]); + } + + public function testGetSingleActivityProgressLabelFromSharedCampIsAllowedForInactiveUser() { + /** @var ActivityProgressLabel $activityProgressLabel */ + $activityProgressLabel = static::getFixture('activityProgressLabel1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('GET', '/activity_progress_labels/'.$activityProgressLabel->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $activityProgressLabel->getId(), + '_links' => [ + 'camp' => ['href' => $this->getIriFor('campShared')], + ], + ]); + } + + public function testGetSingleActivityProgressLabelFromSharedCampIsAllowedForInvitedUser() { + /** @var ActivityProgressLabel $activityProgressLabel */ + $activityProgressLabel = static::getFixture('activityProgressLabel1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('GET', '/activity_progress_labels/'.$activityProgressLabel->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $activityProgressLabel->getId(), + '_links' => [ + 'camp' => ['href' => $this->getIriFor('campShared')], + ], + ]); + } } diff --git a/api/tests/Api/ActivityProgressLabel/UpdateActivityProgressLabelTest.php b/api/tests/Api/ActivityProgressLabels/UpdateActivityProgressLabelTest.php similarity index 75% rename from api/tests/Api/ActivityProgressLabel/UpdateActivityProgressLabelTest.php rename to api/tests/Api/ActivityProgressLabels/UpdateActivityProgressLabelTest.php index 0eff9f5fd4..1bd9e00d3d 100644 --- a/api/tests/Api/ActivityProgressLabel/UpdateActivityProgressLabelTest.php +++ b/api/tests/Api/ActivityProgressLabels/UpdateActivityProgressLabelTest.php @@ -1,6 +1,6 @@ request('PATCH', '/activity_progress_labels/'.$activityProgressLabel->getId(), ['json' => [ + 'position' => 1, + 'title' => 'NewTitle', + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]) + ; + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testPatchActivityProgressLabelFromSharedCampIsDeniedForUnrelatedUser() { + /** @var ActivityProgressLabel $activityProgressLabel */ + $activityProgressLabel = static::getFixture('activityProgressLabel1campShared'); + static::createClientWithCredentials() + ->request('PATCH', '/activity_progress_labels/'.$activityProgressLabel->getId(), ['json' => [ + 'position' => 1, + 'title' => 'NewTitle', + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]) + ; + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testPatchActivityProgressLabelFromSharedCampIsDeniedForInactiveUser() { + /** @var ActivityProgressLabel $activityProgressLabel */ + $activityProgressLabel = static::getFixture('activityProgressLabel1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('PATCH', '/activity_progress_labels/'.$activityProgressLabel->getId(), ['json' => [ + 'position' => 1, + 'title' => 'NewTitle', + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]) + ; + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testPatchActivityProgressLabelFromSharedCampIsDeniedForInvitedUser() { + /** @var ActivityProgressLabel $activityProgressLabel */ + $activityProgressLabel = static::getFixture('activityProgressLabel1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('PATCH', '/activity_progress_labels/'.$activityProgressLabel->getId(), ['json' => [ + 'position' => 1, + 'title' => 'NewTitle', + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]) + ; + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + public function testPatchActivityProgressLabelDisallowsChangingCamp() { /** @var ActivityProgressLabel $activityProgressLabel */ $activityProgressLabel = static::getFixture('activityProgressLabel1'); diff --git a/api/tests/Api/ActivityResponsibles/CreateActivityResponsibleTest.php b/api/tests/Api/ActivityResponsibles/CreateActivityResponsibleTest.php index 42f4273055..dc95258860 100644 --- a/api/tests/Api/ActivityResponsibles/CreateActivityResponsibleTest.php +++ b/api/tests/Api/ActivityResponsibles/CreateActivityResponsibleTest.php @@ -85,6 +85,46 @@ public function testCreateActivityResponsibleIsAllowedForManager() { public function testCreateActivityResponsibleInCampPrototypeIsDeniedForUnrelatedUser() { static::createClientWithCredentials()->request('POST', '/activity_responsibles', ['json' => $this->getExampleWritePayload([ 'activity' => $this->getIriFor('activity1campPrototype'), + 'campCollaboration' => $this->getIriFor('campCollaboration1campPrototype'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testCreateActivityResponsibleInSharedCampIsDeniedForUnrelatedUser() { + static::createClientWithCredentials()->request('POST', '/activity_responsibles', ['json' => $this->getExampleWritePayload([ + 'activity' => $this->getIriFor('activity1campShared'), + 'campCollaboration' => $this->getIriFor('campCollaboration1campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testCreateActivityResponsibleInSharedCampIsDeniedForInactiveUser() { + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()])->request('POST', '/activity_responsibles', ['json' => $this->getExampleWritePayload([ + 'activity' => $this->getIriFor('activity1campShared'), + 'campCollaboration' => $this->getIriFor('campCollaboration1campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testCreateActivityResponsibleInSharedCampIsDeniedForInvitedUser() { + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()])->request('POST', '/activity_responsibles', ['json' => $this->getExampleWritePayload([ + 'activity' => $this->getIriFor('activity1campShared'), + 'campCollaboration' => $this->getIriFor('campCollaboration1campShared'), ])]); $this->assertResponseStatusCodeSame(403); diff --git a/api/tests/Api/ActivityResponsibles/DeleteActivityResponsibleTest.php b/api/tests/Api/ActivityResponsibles/DeleteActivityResponsibleTest.php index d884e11678..839aec7a9e 100644 --- a/api/tests/Api/ActivityResponsibles/DeleteActivityResponsibleTest.php +++ b/api/tests/Api/ActivityResponsibles/DeleteActivityResponsibleTest.php @@ -84,4 +84,37 @@ public function testDeleteActivityResponsibleFromCampPrototypeIsDeniedForUnrelat 'detail' => 'Access Denied.', ]); } + + public function testDeleteActivityResponsibleFromSharedCampIsDeniedForUnrelatedUser() { + $activityResponsible = static::getFixture('activityResponsible1campShared'); + static::createClientWithCredentials()->request('DELETE', '/activity_responsibles/'.$activityResponsible->getId()); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testDeleteActivityResponsibleFromSharedCampIsDeniedForInactiveUser() { + $activityResponsible = static::getFixture('activityResponsible1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()])->request('DELETE', '/activity_responsibles/'.$activityResponsible->getId()); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testDeleteActivityResponsibleFromSharedCampIsDeniedForInvitedUser() { + $activityResponsible = static::getFixture('activityResponsible1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()])->request('DELETE', '/activity_responsibles/'.$activityResponsible->getId()); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } } diff --git a/api/tests/Api/ActivityResponsibles/ListActivityResponsiblesTest.php b/api/tests/Api/ActivityResponsibles/ListActivityResponsiblesTest.php index fa27818ca1..2ddea298ba 100644 --- a/api/tests/Api/ActivityResponsibles/ListActivityResponsiblesTest.php +++ b/api/tests/Api/ActivityResponsibles/ListActivityResponsiblesTest.php @@ -24,7 +24,7 @@ public function testListActivityResponsiblesIsAllowedForLoggedInUserButFiltered( $response = static::createClientWithCredentials()->request('GET', '/activity_responsibles'); $this->assertResponseStatusCodeSame(200); $this->assertJsonContains([ - 'totalItems' => 3, + 'totalItems' => 4, '_links' => [ 'items' => [], ], @@ -36,6 +36,7 @@ public function testListActivityResponsiblesIsAllowedForLoggedInUserButFiltered( ['href' => $this->getIriFor('activityResponsible1')], ['href' => $this->getIriFor('activityResponsible2')], ['href' => $this->getIriFor('activityResponsible1campPrototype')], + ['href' => $this->getIriFor('activityResponsible1campShared')], ], $response->toArray()['_links']['items']); } @@ -93,6 +94,46 @@ public function testListActivityResponsiblesFilteredByActivityInCampPrototypeIsA ], $response->toArray()['_links']['items']); } + public function testListActivityResponsiblesFilteredByActivityInSharedCampIsAllowedForUnrelatedUser() { + $activity = static::getFixture('activity1campShared'); + $response = static::createClientWithCredentials()->request('GET', '/activity_responsibles?activity=%2Factivities%2F'.$activity->getId()); + + $this->assertResponseStatusCodeSame(200); + + $this->assertJsonContains(['totalItems' => 1]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('activityResponsible1campShared')], + ], $response->toArray()['_links']['items']); + } + + public function testListActivityResponsiblesFilteredByActivityInSharedCampIsAllowedForInactiveUser() { + $activity = static::getFixture('activity1campShared'); + $response = static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('GET', '/activity_responsibles?activity=%2Factivities%2F'.$activity->getId()) + ; + + $this->assertResponseStatusCodeSame(200); + + $this->assertJsonContains(['totalItems' => 1]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('activityResponsible1campShared')], + ], $response->toArray()['_links']['items']); + } + + public function testListActivityResponsiblesFilteredByActivityInSharedCampIsAllowedForInvitedUser() { + $activity = static::getFixture('activity1campShared'); + $response = static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('GET', '/activity_responsibles?activity=%2Factivities%2F'.$activity->getId()) + ; + + $this->assertResponseStatusCodeSame(200); + + $this->assertJsonContains(['totalItems' => 1]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('activityResponsible1campShared')], + ], $response->toArray()['_links']['items']); + } + public function testListActivityResponsiblesFilteredByCampIsAllowedForCollaborator() { $camp = static::getFixture('camp1'); $response = static::createClientWithCredentials()->request('GET', '/activity_responsibles?activity.camp=%2Fcamps%2F'.$camp->getId()); diff --git a/api/tests/Api/ActivityResponsibles/ReadActivityResponsibleTest.php b/api/tests/Api/ActivityResponsibles/ReadActivityResponsibleTest.php index 973247de45..f05a9070a0 100644 --- a/api/tests/Api/ActivityResponsibles/ReadActivityResponsibleTest.php +++ b/api/tests/Api/ActivityResponsibles/ReadActivityResponsibleTest.php @@ -105,4 +105,50 @@ public function testGetSingleActivityResponsibleFromCampPrototypeIsAllowedForUnr ], ]); } + + public function testGetSingleActivityResponsibleFromSharedCampIsAllowedForUnrelatedUser() { + /** @var ActivityResponsible $activityResponsible */ + $activityResponsible = static::getFixture('activityResponsible1campShared'); + static::createClientWithCredentials()->request('GET', '/activity_responsibles/'.$activityResponsible->getId()); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $activityResponsible->getId(), + '_links' => [ + 'activity' => ['href' => $this->getIriFor('activity1campShared')], + 'campCollaboration' => ['href' => $this->getIriFor('campCollaboration1campShared')], + ], + ]); + } + + public function testGetSingleActivityResponsibleFromSharedCampIsAllowedForInactiveUser() { + /** @var ActivityResponsible $activityResponsible */ + $activityResponsible = static::getFixture('activityResponsible1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('GET', '/activity_responsibles/'.$activityResponsible->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $activityResponsible->getId(), + '_links' => [ + 'activity' => ['href' => $this->getIriFor('activity1campShared')], + 'campCollaboration' => ['href' => $this->getIriFor('campCollaboration1campShared')], + ], + ]); + } + + public function testGetSingleActivityResponsibleFromSharedCampIsAllowedForInvitedUser() { + /** @var ActivityResponsible $activityResponsible */ + $activityResponsible = static::getFixture('activityResponsible1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('GET', '/activity_responsibles/'.$activityResponsible->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $activityResponsible->getId(), + '_links' => [ + 'activity' => ['href' => $this->getIriFor('activity1campShared')], + 'campCollaboration' => ['href' => $this->getIriFor('campCollaboration1campShared')], + ], + ]); + } } diff --git a/api/tests/Api/CampCollaborations/CreateCampCollaborationTest.php b/api/tests/Api/CampCollaborations/CreateCampCollaborationTest.php index 8d50727c53..be0ea4827b 100644 --- a/api/tests/Api/CampCollaborations/CreateCampCollaborationTest.php +++ b/api/tests/Api/CampCollaborations/CreateCampCollaborationTest.php @@ -198,6 +198,42 @@ public function testCreateCampCollaborationInCampPrototypeIsDeniedForUnrelatedUs ]); } + public function testCreateCampCollaborationInSharedCampIsDeniedForUnrelatedUser() { + static::createClientWithCredentials()->request('POST', '/camp_collaborations', ['json' => $this->getExampleWritePayload([ + 'camp' => $this->getIriFor('campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testCreateCampCollaborationInSharedCampIsDeniedForInactiveUser() { + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()])->request('POST', '/camp_collaborations', ['json' => $this->getExampleWritePayload([ + 'camp' => $this->getIriFor('campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testCreateCampCollaborationInSharedCampIsDeniedForInvitedUser() { + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()])->request('POST', '/camp_collaborations', ['json' => $this->getExampleWritePayload([ + 'camp' => $this->getIriFor('campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + public function testCreateCampCollaborationValidatesIfInviteEmailIsBlank() { static::createClientWithCredentials()->request('POST', '/camp_collaborations', ['json' => $this->getExampleWritePayload([ 'inviteEmail' => '', diff --git a/api/tests/Api/CampCollaborations/DeleteCampCollaborationTest.php b/api/tests/Api/CampCollaborations/DeleteCampCollaborationTest.php index 1c6b2668e2..47e4ea28eb 100644 --- a/api/tests/Api/CampCollaborations/DeleteCampCollaborationTest.php +++ b/api/tests/Api/CampCollaborations/DeleteCampCollaborationTest.php @@ -90,4 +90,37 @@ public function testDeleteCampCollaborationFromCampPrototypeIsDeniedForUnrelated 'detail' => 'Access Denied.', ]); } + + public function testDeleteCampCollaborationFromSharedCampIsDeniedForUnrelatedUser() { + $campCollaboration = static::getFixture('campCollaboration1campShared'); + static::createClientWithCredentials()->request('DELETE', '/camp_collaborations/'.$campCollaboration->getId()); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testDeleteCampCollaborationFromSharedCampIsDeniedForInactiveUser() { + $campCollaboration = static::getFixture('campCollaboration1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()])->request('DELETE', '/camp_collaborations/'.$campCollaboration->getId()); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testDeleteCampCollaborationFromSharedCampIsDeniedForInvitedUser() { + $campCollaboration = static::getFixture('campCollaboration1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()])->request('DELETE', '/camp_collaborations/'.$campCollaboration->getId()); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } } diff --git a/api/tests/Api/CampCollaborations/ListCampCollaborationsTest.php b/api/tests/Api/CampCollaborations/ListCampCollaborationsTest.php index 17a2d064b1..8c77c4ff6a 100644 --- a/api/tests/Api/CampCollaborations/ListCampCollaborationsTest.php +++ b/api/tests/Api/CampCollaborations/ListCampCollaborationsTest.php @@ -24,7 +24,7 @@ public function testListCampCollaborationsIsAllowedForLoggedInUserButFiltered() $response = static::createClientWithCredentials()->request('GET', '/camp_collaborations'); $this->assertResponseStatusCodeSame(200); $this->assertJsonContains([ - 'totalItems' => 12, + 'totalItems' => 16, '_links' => [ 'items' => [], ], @@ -45,6 +45,10 @@ public function testListCampCollaborationsIsAllowedForLoggedInUserButFiltered() ['href' => $this->getIriFor('campCollaboration3camp2guest')], ['href' => $this->getIriFor('campCollaboration4camp2member')], ['href' => $this->getIriFor('campCollaboration1campPrototype')], + ['href' => $this->getIriFor('campCollaboration1campShared')], + ['href' => $this->getIriFor('campCollaboration2invitedCampShared')], + ['href' => $this->getIriFor('campCollaboration3inactiveCampShared')], + ['href' => $this->getIriFor('campCollaboration4invitedCampShared')], ], $response->toArray()['_links']['items']); } diff --git a/api/tests/Api/CampCollaborations/ReadCampCollaborationTest.php b/api/tests/Api/CampCollaborations/ReadCampCollaborationTest.php index be08e7dfa0..a5be68a99c 100644 --- a/api/tests/Api/CampCollaborations/ReadCampCollaborationTest.php +++ b/api/tests/Api/CampCollaborations/ReadCampCollaborationTest.php @@ -117,4 +117,59 @@ public function testGetSingleCampCollaborationFromCampPrototypeIsAllowedForUnrel ], ]); } + + public function testGetSingleCampCollaborationFromSharedCampIsAllowedForUnrelatedUser() { + /** @var CampCollaboration $campCollaboration */ + $campCollaboration = static::getFixture('campCollaboration1campShared'); + static::createClientWithCredentials()->request('GET', '/camp_collaborations/'.$campCollaboration->getId()); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $campCollaboration->getId(), + 'role' => $campCollaboration->role, + 'status' => $campCollaboration->status, + 'inviteEmail' => null, + '_links' => [ + 'camp' => ['href' => $this->getIriFor('campShared')], + 'user' => ['href' => $this->getIriFor('user4unrelated')], + ], + ]); + } + + public function testGetSingleCampCollaborationFromSharedCampIsAllowedForInactiveUser() { + /** @var CampCollaboration $campCollaboration */ + $campCollaboration = static::getFixture('campCollaboration1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('GET', '/camp_collaborations/'.$campCollaboration->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $campCollaboration->getId(), + 'role' => $campCollaboration->role, + 'status' => $campCollaboration->status, + 'inviteEmail' => null, + '_links' => [ + 'camp' => ['href' => $this->getIriFor('campShared')], + 'user' => ['href' => $this->getIriFor('user4unrelated')], + ], + ]); + } + + public function testGetSingleCampCollaborationFromSharedCampIsAllowedForInvitedUser() { + /** @var CampCollaboration $campCollaboration */ + $campCollaboration = static::getFixture('campCollaboration1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('GET', '/camp_collaborations/'.$campCollaboration->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $campCollaboration->getId(), + 'role' => $campCollaboration->role, + 'status' => $campCollaboration->status, + 'inviteEmail' => null, + '_links' => [ + 'camp' => ['href' => $this->getIriFor('campShared')], + 'user' => ['href' => $this->getIriFor('user4unrelated')], + ], + ]); + } } diff --git a/api/tests/Api/CampCollaborations/ResendInvitationCampCollaborationTest.php b/api/tests/Api/CampCollaborations/ResendInvitationCampCollaborationTest.php index 1dcb4d8871..868923fae5 100644 --- a/api/tests/Api/CampCollaborations/ResendInvitationCampCollaborationTest.php +++ b/api/tests/Api/CampCollaborations/ResendInvitationCampCollaborationTest.php @@ -272,4 +272,67 @@ public function testResendInvitationFailsWhenCampCollaborationIsNotFound() { $this->assertResponseStatusCodeSame(404); } + + /** + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + * @throws RedirectionExceptionInterface + * @throws ClientExceptionInterface + */ + public function testResendInvitationInSharedCampIsDeniedForUnrelatedUser() { + /** @var CampCollaboration $campCollaboration */ + $campCollaboration = static::getFixture('campCollaboration2invitedCampShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user1manager']->getEmail()])->request( + 'PATCH', + '/camp_collaborations/'.$campCollaboration->getId().'/'.self::RESEND_INVITATION, + [ + 'json' => [], + 'headers' => ['Content-Type' => 'application/merge-patch+json'], + ] + ); + + $this->assertResponseStatusCodeSame(403); + } + + /** + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + * @throws RedirectionExceptionInterface + * @throws ClientExceptionInterface + */ + public function testResendInvitationInSharedCampIsDeniedForInactiveUser() { + /** @var CampCollaboration $campCollaboration */ + $campCollaboration = static::getFixture('campCollaboration2invitedCampShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()])->request( + 'PATCH', + '/camp_collaborations/'.$campCollaboration->getId().'/'.self::RESEND_INVITATION, + [ + 'json' => [], + 'headers' => ['Content-Type' => 'application/merge-patch+json'], + ] + ); + + $this->assertResponseStatusCodeSame(403); + } + + /** + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + * @throws RedirectionExceptionInterface + * @throws ClientExceptionInterface + */ + public function testResendInvitationInSharedCampIsDeniedForInvitedUser() { + /** @var CampCollaboration $campCollaboration */ + $campCollaboration = static::getFixture('campCollaboration4invitedCampShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()])->request( + 'PATCH', + '/camp_collaborations/'.$campCollaboration->getId().'/'.self::RESEND_INVITATION, + [ + 'json' => [], + 'headers' => ['Content-Type' => 'application/merge-patch+json'], + ] + ); + + $this->assertResponseStatusCodeSame(403); + } } diff --git a/api/tests/Api/CampCollaborations/UpdateCampCollaborationTest.php b/api/tests/Api/CampCollaborations/UpdateCampCollaborationTest.php index a14dd59571..1fd0f0f208 100644 --- a/api/tests/Api/CampCollaborations/UpdateCampCollaborationTest.php +++ b/api/tests/Api/CampCollaborations/UpdateCampCollaborationTest.php @@ -228,6 +228,45 @@ public function testPatchCampCollaborationInCampPrototypeIsDeniedForUnrelatedUse ]); } + public function testPatchCampCollaborationInSharedCampIsDeniedForUnrelatedUser() { + $campCollaboration = static::getFixture('campCollaboration1campShared'); + static::createClientWithCredentials()->request('PATCH', '/camp_collaborations/'.$campCollaboration->getId(), ['json' => [ + 'status' => 'inactive', + 'role' => 'guest', + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testPatchCampCollaborationInSharedCampIsDeniedForInactiveUser() { + $campCollaboration = static::getFixture('campCollaboration1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()])->request('PATCH', '/camp_collaborations/'.$campCollaboration->getId(), ['json' => [ + 'status' => 'inactive', + 'role' => 'guest', + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testPatchCampCollaborationInSharedCampIsDeniedForInvitedUser() { + $campCollaboration = static::getFixture('campCollaboration1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()])->request('PATCH', '/camp_collaborations/'.$campCollaboration->getId(), ['json' => [ + 'status' => 'inactive', + 'role' => 'guest', + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + public function testPatchCampCollaborationDisallowsChangingInviteEmail() { $campCollaboration = static::getFixture('campCollaboration1manager'); static::createClientWithCredentials()->request('PATCH', '/camp_collaborations/'.$campCollaboration->getId(), ['json' => [ diff --git a/api/tests/Api/Camps/CreateCampTest.php b/api/tests/Api/Camps/CreateCampTest.php index 93c7a96211..9500db9e53 100644 --- a/api/tests/Api/Camps/CreateCampTest.php +++ b/api/tests/Api/Camps/CreateCampTest.php @@ -682,6 +682,24 @@ public function testCreateCampFromPrototype() { $this->assertCount(3, $camp->progressLabels); } + public function testCreateCampFromSharedCamp() { + /** @var Camp $campPrototype */ + $campShared = self::getFixture('campShared'); + + $response = static::createClientWithCredentials()->request('POST', '/camps', ['json' => $this->getExampleWritePayload([ + 'campPrototype' => $this->getIriFor($campShared), + ])]); + + $this->assertResponseStatusCodeSame(201); + + $camp = $this->getEntityManager()->getRepository(Camp::class)->find($response->toArray()['id']); + $this->assertEquals($campShared->getId(), $camp->campPrototypeId); + $this->assertCount(1, $camp->categories); + $this->assertCount(2, $camp->materialLists); + $this->assertCount(1, $camp->checklists); + $this->assertCount(2, $camp->progressLabels); + } + public function testCopiesCampSettingsFromNonPrototypeCamp() { /** @var Camp $otherCamp */ $otherCamp = self::getFixture('camp1'); @@ -815,7 +833,7 @@ public function getExampleReadPayload($attributes = [], $except = []) { Camp::class, Get::class, $attributes, - ['periods'], + ['periods', 'sharedBy', 'sharedSince'], $except ); } diff --git a/api/tests/Api/Camps/DeleteCampTest.php b/api/tests/Api/Camps/DeleteCampTest.php index bae5a19555..6a0fc3dc69 100644 --- a/api/tests/Api/Camps/DeleteCampTest.php +++ b/api/tests/Api/Camps/DeleteCampTest.php @@ -70,6 +70,40 @@ public function testDeletePrototypeCampIsDeniedForUnrelatedUser() { ]); } + public function testDeleteSharedCampIsDeniedForUnrelatedUser() { + $camp = static::getFixture('campShared'); + static::createClientWithCredentials()->request('DELETE', '/camps/'.$camp->getId()); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testDeleteSharedCampIsDeniedForInactiveUser() { + $camp = static::getFixture('campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('DELETE', '/camps/'.$camp->getId()) + ; + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testDeleteSharedCampIsDeniedForInvitedUser() { + $camp = static::getFixture('campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('DELETE', '/camps/'.$camp->getId()) + ; + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + public function testDeleteCampAlsoDeletesContentNodes() { $client = static::createClientWithCredentials(); // Disable resetting the database between the two requests diff --git a/api/tests/Api/Camps/ListCampsTest.php b/api/tests/Api/Camps/ListCampsTest.php index 2b2e4dda79..8cb64a210e 100644 --- a/api/tests/Api/Camps/ListCampsTest.php +++ b/api/tests/Api/Camps/ListCampsTest.php @@ -21,7 +21,7 @@ public function testListCampsIsAllowedForLoggedInUserButFiltered() { $response = static::createClientWithCredentials()->request('GET', '/camps'); $this->assertResponseStatusCodeSame(200); $this->assertJsonContains([ - 'totalItems' => 3, + 'totalItems' => 4, '_links' => [ 'items' => [], ], @@ -33,6 +33,7 @@ public function testListCampsIsAllowedForLoggedInUserButFiltered() { ['href' => $this->getIriFor('camp1')], ['href' => $this->getIriFor('camp2')], ['href' => $this->getIriFor('campPrototype')], + ['href' => $this->getIriFor('campShared')], ], $response->toArray()['_links']['items']); } @@ -53,13 +54,65 @@ public function testListPrototypeCampsOnly() { ], $response->toArray()['_links']['items']); } + public function testListCampsFilteredByCurrentUser() { + $user = static::$fixtures['user1manager']; + $response = static::createClientWithCredentials(['email' => $user->getEmail()])->request('GET', '/camps?campCollaborator=/users/'.$user->getId()); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 2, + '_links' => [ + 'items' => [], + ], + '_embedded' => [ + 'items' => [], + ], + ]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('camp1')], + ['href' => $this->getIriFor('camp2')], + ], $response->toArray()['_links']['items']); + } + + public function testListCampsFilteredByOtherCollaboratorIsAllowedButFiltered() { + $user = static::$fixtures['user1manager']; + $user2 = static::$fixtures['user8memberOnlyInCamp2']; + $response = static::createClientWithCredentials(['email' => $user2->getEmail()])->request('GET', '/camps?campCollaborator=/users/'.$user->getId()); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 1, + '_links' => [ + 'items' => [], + ], + '_embedded' => [ + 'items' => [], + ], + ]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('camp2')], + ], $response->toArray()['_links']['items']); + } + + public function testListCampsFilteredByUnrelatedUserIsAllowedButFiltered() { + $user = static::$fixtures['user1manager']; + $user2 = static::$fixtures['user4unrelated']; + static::createClientWithCredentials(['email' => $user2->getEmail()])->request('GET', '/camps?campCollaborator=/users/'.$user->getId()); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 0, + '_links' => [], + '_embedded' => [ + 'items' => [], + ], + ]); + } + public function testListCampsDoesNotShowCampToInactiveCollaborator() { $response = static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) ->request('GET', '/camps') ; $this->assertResponseStatusCodeSame(200); $this->assertJsonContains([ - 'totalItems' => 1, + 'totalItems' => 2, '_links' => [ 'items' => [], ], @@ -69,6 +122,7 @@ public function testListCampsDoesNotShowCampToInactiveCollaborator() { ]); $this->assertEqualsCanonicalizing([ ['href' => $this->getIriFor('campPrototype')], + ['href' => $this->getIriFor('campShared')], ], $response->toArray()['_links']['items']); } } diff --git a/api/tests/Api/Camps/ReadCampTest.php b/api/tests/Api/Camps/ReadCampTest.php index 94cb2a1d89..79cad5a8f0 100644 --- a/api/tests/Api/Camps/ReadCampTest.php +++ b/api/tests/Api/Camps/ReadCampTest.php @@ -148,6 +148,77 @@ public function testGetSinglePrototypeCampIsAllowedForUnrelatedUser() { 'addressZipcode' => $camp->addressZipcode, 'addressCity' => $camp->addressCity, 'isPrototype' => true, + 'isShared' => false, + '_links' => [ + 'creator' => [], + ], + ]); + } + + public function testGetSingleSharedCampIsAllowedForUnrelatedUser() { + /** @var Camp $camp */ + $camp = static::getFixture('campShared'); + static::createClientWithCredentials()->request('GET', '/camps/'.$camp->getId()); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $camp->getId(), + 'shortTitle' => $camp->shortTitle, + 'title' => $camp->title, + 'motto' => $camp->motto, + 'addressName' => $camp->addressName, + 'addressStreet' => $camp->addressStreet, + 'addressZipcode' => $camp->addressZipcode, + 'addressCity' => $camp->addressCity, + 'isPrototype' => false, + 'isShared' => true, + '_links' => [ + 'creator' => [], + ], + ]); + } + + public function testGetSingleSharedCampIsAllowedForInactiveUser() { + /** @var Camp $camp */ + $camp = static::getFixture('campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('GET', '/camps/'.$camp->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $camp->getId(), + 'shortTitle' => $camp->shortTitle, + 'title' => $camp->title, + 'motto' => $camp->motto, + 'addressName' => $camp->addressName, + 'addressStreet' => $camp->addressStreet, + 'addressZipcode' => $camp->addressZipcode, + 'addressCity' => $camp->addressCity, + 'isPrototype' => false, + 'isShared' => true, + '_links' => [ + 'creator' => [], + ], + ]); + } + + public function testGetSingleSharedCampIsAllowedForInvitedUser() { + /** @var Camp $camp */ + $camp = static::getFixture('campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('GET', '/camps/'.$camp->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $camp->getId(), + 'shortTitle' => $camp->shortTitle, + 'title' => $camp->title, + 'motto' => $camp->motto, + 'addressName' => $camp->addressName, + 'addressStreet' => $camp->addressStreet, + 'addressZipcode' => $camp->addressZipcode, + 'addressCity' => $camp->addressCity, + 'isPrototype' => false, + 'isShared' => true, '_links' => [ 'creator' => [], ], diff --git a/api/tests/Api/Camps/UpdateCampTest.php b/api/tests/Api/Camps/UpdateCampTest.php index 9ccdebd592..dde41ec60c 100644 --- a/api/tests/Api/Camps/UpdateCampTest.php +++ b/api/tests/Api/Camps/UpdateCampTest.php @@ -2,6 +2,7 @@ namespace App\Tests\Api\Camps; +use App\Entity\Camp; use App\Tests\Api\ECampApiTestCase; /** @@ -98,6 +99,42 @@ public function testPatchPrototypeCampIsDeniedForUnrelatedUser() { ]); } + public function testPatchSharedCampIsDeniedForUnrelatedUser() { + $camp = static::getFixture('campShared'); + static::createClientWithCredentials()->request('PATCH', '/camps/'.$camp->getId(), ['json' => [ + 'title' => 'Hello World', + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testPatchSharedCampIsDeniedForInactiveUser() { + $camp = static::getFixture('campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()])->request('PATCH', '/camps/'.$camp->getId(), ['json' => [ + 'title' => 'Hello World', + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testPatchSharedCampIsDeniedForInvitedUser() { + $camp = static::getFixture('campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()])->request('PATCH', '/camps/'.$camp->getId(), ['json' => [ + 'title' => 'Hello World', + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + public function testPatchCampDisallowsEditingPeriods() { $camp = static::getFixture('camp1'); static::createClientWithCredentials()->request('PATCH', '/camps/'.$camp->getId(), ['json' => [ @@ -545,4 +582,48 @@ public function testPatchCampValidatesLongAddressCity() { ], ]); } + + public function testPatchCampEnablesSharedAndSetsMetadata() { + /** @var Camp $camp */ + $camp = static::getFixture('camp1'); + // Precondition before the test + $this->assertNull($camp->sharedSince); + + static::createClientWithCredentials()->request('PATCH', '/camps/'.$camp->getId(), ['json' => [ + 'isShared' => true, + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'isShared' => true, + '_links' => [ + 'sharedBy' => ['href' => $this->getIriFor('user1manager')], + ], + ]); + $camp = $this->getEntityManager()->getRepository(Camp::class)->find($camp->getId()); + $this->assertNotNull($camp->sharedSince); + $shortlyAgo = new \DateTime(); + $shortlyAgo->sub(new \DateInterval('PT30S')); + $this->assertGreaterThan($shortlyAgo, $camp->sharedSince); + } + + public function testPatchCampDisablesSharedAndDoesNotChangeMetadata() { + /** @var Camp $camp */ + $camp = static::getFixture('campShared'); + + static::createClientWithCredentials(['email' => static::getFixture('user4unrelated')->getEmail()]) + ->request('PATCH', '/camps/'.$camp->getId(), ['json' => [ + 'isShared' => false, + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]) + ; + + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'isShared' => false, + 'sharedSince' => '2025-09-03T12:00:00+00:00', + '_links' => [ + 'sharedBy' => ['href' => $this->getIriFor('admin')], + ], + ]); + } } diff --git a/api/tests/Api/Categories/CreateCategoryTest.php b/api/tests/Api/Categories/CreateCategoryTest.php index 4f0161d917..e45904e9d0 100644 --- a/api/tests/Api/Categories/CreateCategoryTest.php +++ b/api/tests/Api/Categories/CreateCategoryTest.php @@ -94,6 +94,42 @@ public function testCreateCategoryInCampPrototypeIsDeniedForUnrelatedUser() { ]); } + public function testCreateCategoryInSharedCampIsDeniedForUnrelatedUser() { + static::createClientWithCredentials()->request('POST', '/categories', ['json' => $this->getExampleWritePayload([ + 'camp' => $this->getIriFor('campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testCreateCategoryInSharedCampIsDeniedForInactiveUser() { + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()])->request('POST', '/categories', ['json' => $this->getExampleWritePayload([ + 'camp' => $this->getIriFor('campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testCreateCategoryInSharedCampIsDeniedForInvitedUser() { + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()])->request('POST', '/categories', ['json' => $this->getExampleWritePayload([ + 'camp' => $this->getIriFor('campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + public function testCreateCategoryCreatesNewColumnLayoutAsRootContentNode() { static::createClientWithCredentials()->request('POST', '/categories', ['json' => $this->getExampleWritePayload()]); @@ -106,13 +142,6 @@ public function testCreateCategoryCreatesNewColumnLayoutAsRootContentNode() { ]]); } - public function testCreateCampDoesntExposeCampPrototypeId() { - $response = static::createClientWithCredentials()->request('POST', '/categories', ['json' => $this->getExampleWritePayload([], ['preferredContentTypes'])]); - - $this->assertResponseStatusCodeSame(201); - $this->assertArrayNotHasKey('campPrototypeId', $response->toArray()); - } - public function testCreateCategoryValidatesMissingCamp() { static::createClientWithCredentials()->request('POST', '/categories', ['json' => $this->getExampleWritePayload([], ['camp'])]); diff --git a/api/tests/Api/Categories/DeleteCategoryTest.php b/api/tests/Api/Categories/DeleteCategoryTest.php index 267da567e4..9fb6ad351f 100644 --- a/api/tests/Api/Categories/DeleteCategoryTest.php +++ b/api/tests/Api/Categories/DeleteCategoryTest.php @@ -85,6 +85,39 @@ public function testDeleteCategoryFromCampPrototypeIsDeniedForUnrelatedUser() { ]); } + public function testDeleteCategoryFromSharedCampIsDeniedForUnrelatedUser() { + $category = static::getFixture('category1campShared'); + static::createClientWithCredentials()->request('DELETE', '/categories/'.$category->getId()); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testDeleteCategoryFromSharedCampIsDeniedForInactiveUser() { + $category = static::getFixture('category1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()])->request('DELETE', '/categories/'.$category->getId()); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testDeleteCategoryFromSharedCampIsDeniedForInvitedUser() { + $category = static::getFixture('category1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()])->request('DELETE', '/categories/'.$category->getId()); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + public function testDeleteCategoryValidatesThatCategoryHasNoActivities() { $category = static::getFixture('category1'); static::createClientWithCredentials() diff --git a/api/tests/Api/Categories/ListCategoriesTest.php b/api/tests/Api/Categories/ListCategoriesTest.php index 6b1b144c25..a2ef82899c 100644 --- a/api/tests/Api/Categories/ListCategoriesTest.php +++ b/api/tests/Api/Categories/ListCategoriesTest.php @@ -24,7 +24,7 @@ public function testListCategoriesIsAllowedForLoggedInUserButFiltered() { $response = static::createClientWithCredentials()->request('GET', '/categories'); $this->assertResponseStatusCodeSame(200); $this->assertJsonContains([ - 'totalItems' => 5, + 'totalItems' => 6, '_links' => [ 'items' => [], ], @@ -38,6 +38,7 @@ public function testListCategoriesIsAllowedForLoggedInUserButFiltered() { ['href' => $this->getIriFor('categoryWithNoActivities')], ['href' => $this->getIriFor('category1camp2')], ['href' => $this->getIriFor('category1campPrototype')], + ['href' => $this->getIriFor('category1campShared')], ], $response->toArray()['_links']['items']); } @@ -97,6 +98,46 @@ public function testListCategoriesFilteredByCampPrototypeIsAllowedForUnrelatedUs ], $response->toArray()['_links']['items']); } + public function testListCategoriesFilteredBySharedCampIsAllowedForUnrelatedUser() { + $camp = static::getFixture('campShared'); + $response = static::createClientWithCredentials()->request('GET', '/categories?camp=%2Fcamps%2F'.$camp->getId()); + + $this->assertResponseStatusCodeSame(200); + + $this->assertJsonContains(['totalItems' => 1]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('category1campShared')], + ], $response->toArray()['_links']['items']); + } + + public function testListCategoriesFilteredBySharedCampIsAllowedForInactiveUser() { + $camp = static::getFixture('campShared'); + $response = static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('GET', '/categories?camp=%2Fcamps%2F'.$camp->getId()) + ; + + $this->assertResponseStatusCodeSame(200); + + $this->assertJsonContains(['totalItems' => 1]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('category1campShared')], + ], $response->toArray()['_links']['items']); + } + + public function testListCategoriesFilteredBySharedCampIsAllowedForInvitedUser() { + $camp = static::getFixture('campShared'); + $response = static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('GET', '/categories?camp=%2Fcamps%2F'.$camp->getId()) + ; + + $this->assertResponseStatusCodeSame(200); + + $this->assertJsonContains(['totalItems' => 1]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('category1campShared')], + ], $response->toArray()['_links']['items']); + } + public function testListCategoriesAsCampSubresourceIsAllowedForCollaborator() { $camp = static::getFixture('camp1'); $response = static::createClientWithCredentials()->request('GET', '/camps/'.$camp->getId().'/categories'); diff --git a/api/tests/Api/Categories/ReadCategoryTest.php b/api/tests/Api/Categories/ReadCategoryTest.php index 2bd99c0639..4cf787f158 100644 --- a/api/tests/Api/Categories/ReadCategoryTest.php +++ b/api/tests/Api/Categories/ReadCategoryTest.php @@ -124,7 +124,64 @@ public function testGetSingleCategoryFromCampPrototypeIsAllowedForUnrelatedUser( '_links' => [ 'camp' => ['href' => $this->getIriFor('campPrototype')], 'rootContentNode' => ['href' => $this->getIriFor('columnLayout2campPrototype')], - // 'contentNodes' => ['href' => '/content_nodes?owner=%2Fcategories%2F'.$category->getId()], + ], + ]); + } + + public function testGetSingleCategoryFromSharedCampIsAllowedForUnrelatedUser() { + /** @var Category $category */ + $category = static::getFixture('category1campShared'); + static::createClientWithCredentials()->request('GET', '/categories/'.$category->getId()); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $category->getId(), + 'short' => $category->short, + 'name' => $category->name, + 'color' => $category->color, + 'numberingStyle' => $category->numberingStyle, + '_links' => [ + 'camp' => ['href' => $this->getIriFor('campShared')], + 'rootContentNode' => ['href' => $this->getIriFor('columnLayout2campShared')], + ], + ]); + } + + public function testGetSingleCategoryFromSharedCampIsAllowedForInactiveUser() { + /** @var Category $category */ + $category = static::getFixture('category1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('GET', '/categories/'.$category->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $category->getId(), + 'short' => $category->short, + 'name' => $category->name, + 'color' => $category->color, + 'numberingStyle' => $category->numberingStyle, + '_links' => [ + 'camp' => ['href' => $this->getIriFor('campShared')], + 'rootContentNode' => ['href' => $this->getIriFor('columnLayout2campShared')], + ], + ]); + } + + public function testGetSingleCategoryFromSharedCampIsAllowedForInvitedUser() { + /** @var Category $category */ + $category = static::getFixture('category1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('GET', '/categories/'.$category->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $category->getId(), + 'short' => $category->short, + 'name' => $category->name, + 'color' => $category->color, + 'numberingStyle' => $category->numberingStyle, + '_links' => [ + 'camp' => ['href' => $this->getIriFor('campShared')], + 'rootContentNode' => ['href' => $this->getIriFor('columnLayout2campShared')], ], ]); } diff --git a/api/tests/Api/Categories/UpdateCategoryTest.php b/api/tests/Api/Categories/UpdateCategoryTest.php index 4a7853985e..5e5ee62202 100644 --- a/api/tests/Api/Categories/UpdateCategoryTest.php +++ b/api/tests/Api/Categories/UpdateCategoryTest.php @@ -163,6 +163,63 @@ public function testPatchCategoryInCampPrototypeIsDeniedForUnrelatedUser() { ]); } + public function testPatchCategoryInSharedCampIsDeniedForUnrelatedUser() { + $category = static::getFixture('category1campShared'); + static::createClientWithCredentials()->request('PATCH', '/categories/'.$category->getId(), ['json' => [ + 'short' => 'LP', + 'name' => 'Lagerprogramm', + 'color' => '#FFFF00', + 'numberingStyle' => 'I', + 'preferredContentTypes' => [ + $this->getIriFor('contentTypeColumnLayout'), + $this->getIriFor('contentTypeSafetyConsiderations'), + ], + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testPatchCategoryInSharedCampIsDeniedForInactiveUser() { + $category = static::getFixture('category1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()])->request('PATCH', '/categories/'.$category->getId(), ['json' => [ + 'short' => 'LP', + 'name' => 'Lagerprogramm', + 'color' => '#FFFF00', + 'numberingStyle' => 'I', + 'preferredContentTypes' => [ + $this->getIriFor('contentTypeColumnLayout'), + $this->getIriFor('contentTypeSafetyConsiderations'), + ], + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testPatchCategoryInSharedCampIsDeniedForInvitedUser() { + $category = static::getFixture('category1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()])->request('PATCH', '/categories/'.$category->getId(), ['json' => [ + 'short' => 'LP', + 'name' => 'Lagerprogramm', + 'color' => '#FFFF00', + 'numberingStyle' => 'I', + 'preferredContentTypes' => [ + $this->getIriFor('contentTypeColumnLayout'), + $this->getIriFor('contentTypeSafetyConsiderations'), + ], + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + public function testPatchCategoryDisallowsChangingCamp() { $category = static::getFixture('category1'); static::createClientWithCredentials()->request('PATCH', '/categories/'.$category->getId(), ['json' => [ diff --git a/api/tests/Api/ChecklistItems/CreateChecklistItemTest.php b/api/tests/Api/ChecklistItems/CreateChecklistItemTest.php index 37796a451a..5aa5e1b4cc 100644 --- a/api/tests/Api/ChecklistItems/CreateChecklistItemTest.php +++ b/api/tests/Api/ChecklistItems/CreateChecklistItemTest.php @@ -88,6 +88,42 @@ public function testCreateChecklistItemInCampPrototypeIsDeniedForUnrelatedUser() ]); } + public function testCreateChecklistItemInSharedCampIsDeniedForUnrelatedUser() { + static::createClientWithCredentials()->request('POST', '/checklist_items', ['json' => $this->getExampleWritePayload([ + 'checklist' => $this->getIriFor('checklist1campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testCreateChecklistItemInSharedCampIsDeniedForInactiveUser() { + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()])->request('POST', '/checklist_items', ['json' => $this->getExampleWritePayload([ + 'checklist' => $this->getIriFor('checklist1campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testCreateChecklistItemInSharedCampIsDeniedForInvitedUser() { + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()])->request('POST', '/checklist_items', ['json' => $this->getExampleWritePayload([ + 'checklist' => $this->getIriFor('checklist1campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + public function testCreateChecklistItemValidatesMissingChecklist() { static::createClientWithCredentials()->request('POST', '/checklist_items', ['json' => $this->getExampleWritePayload([], ['checklist'])]); diff --git a/api/tests/Api/ChecklistItems/DeleteChecklistItemTest.php b/api/tests/Api/ChecklistItems/DeleteChecklistItemTest.php index 8a22280004..fb05eb159d 100644 --- a/api/tests/Api/ChecklistItems/DeleteChecklistItemTest.php +++ b/api/tests/Api/ChecklistItems/DeleteChecklistItemTest.php @@ -75,7 +75,7 @@ public function testDeleteChecklistItemIsAllowedForManager() { } public function testDeleteChecklistItemFromCampPrototypeIsDeniedForUnrelatedUser() { - $checklistItem = static::getFixture('checklistItemPrototype_1_1'); + $checklistItem = static::getFixture('checklistItemCampPrototype_1_1'); static::createClientWithCredentials()->request('DELETE', '/checklist_items/'.$checklistItem->getId()); $this->assertResponseStatusCodeSame(403); @@ -85,6 +85,43 @@ public function testDeleteChecklistItemFromCampPrototypeIsDeniedForUnrelatedUser ]); } + public function testDeleteChecklistItemFromSharedCampIsDeniedForUnrelatedUser() { + $checklistItem = static::getFixture('checklistItemCampShared_1_1'); + static::createClientWithCredentials()->request('DELETE', '/checklist_items/'.$checklistItem->getId()); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testDeleteChecklistItemFromSharedCampIsDeniedForInactiveUser() { + $checklistItem = static::getFixture('checklistItemCampShared_1_1'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('DELETE', '/checklist_items/'.$checklistItem->getId()) + ; + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testDeleteChecklistItemFromSharedCampIsDeniedForInvitedUser() { + $checklistItem = static::getFixture('checklistItemCampShared_1_1'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('DELETE', '/checklist_items/'.$checklistItem->getId()) + ; + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + public function testDeleteChecklistItemIsDeniedWhenUsedInChecklistNode() { $checklistItem = static::getFixture('checklistItem1_1_1'); static::createClientWithCredentials()->request('DELETE', '/checklist_items/'.$checklistItem->getId()); diff --git a/api/tests/Api/ChecklistItems/ListChecklistItemTest.php b/api/tests/Api/ChecklistItems/ListChecklistItemsTest.php similarity index 94% rename from api/tests/Api/ChecklistItems/ListChecklistItemTest.php rename to api/tests/Api/ChecklistItems/ListChecklistItemsTest.php index 45b015c454..6cb45ecd5d 100644 --- a/api/tests/Api/ChecklistItems/ListChecklistItemTest.php +++ b/api/tests/Api/ChecklistItems/ListChecklistItemsTest.php @@ -7,7 +7,7 @@ /** * @internal */ -class ListChecklistItemTest extends ECampApiTestCase { +class ListChecklistItemsTest extends ECampApiTestCase { public function testListChecklistItemsIsDeniedForAnonymousUser() { static::createBasicClient()->request('GET', '/checklist_items'); $this->assertResponseStatusCodeSame(401); @@ -24,7 +24,7 @@ public function testListChecklistItemsIsAllowedForLoggedInUserButFiltered() { $response = static::createClientWithCredentials()->request('GET', '/checklist_items'); $this->assertResponseStatusCodeSame(200); $this->assertJsonContains([ - 'totalItems' => 6, + 'totalItems' => 7, '_links' => [ 'items' => [], ], @@ -38,7 +38,8 @@ public function testListChecklistItemsIsAllowedForLoggedInUserButFiltered() { ['href' => $this->getIriFor('checklistItem1_1_2_3')], ['href' => $this->getIriFor('checklistItem1_1_2_3_4')], ['href' => $this->getIriFor('checklistItem2_1_1')], - ['href' => $this->getIriFor('checklistItemPrototype_1_1')], + ['href' => $this->getIriFor('checklistItemCampPrototype_1_1')], + ['href' => $this->getIriFor('checklistItemCampShared_1_1')], ], $response->toArray()['_links']['items']); } @@ -95,7 +96,7 @@ public function testListChecklistItemsFilteredByChecklistPrototypeIsAllowedForUn $this->assertJsonContains(['totalItems' => 1]); $this->assertEqualsCanonicalizing([ - ['href' => $this->getIriFor('checklistItemPrototype_1_1')], + ['href' => $this->getIriFor('checklistItemCampPrototype_1_1')], ], $response->toArray()['_links']['items']); } diff --git a/api/tests/Api/ChecklistItems/ReadChecklistItemTest.php b/api/tests/Api/ChecklistItems/ReadChecklistItemTest.php index 9e362711a8..08d70e8db2 100644 --- a/api/tests/Api/ChecklistItems/ReadChecklistItemTest.php +++ b/api/tests/Api/ChecklistItems/ReadChecklistItemTest.php @@ -94,7 +94,7 @@ public function testGetSingleChecklistItemIsAllowedForManager() { public function testGetSingleChecklistItemFromCampPrototypeIsAllowedForUnrelatedUser() { /** @var ChecklistItem $checklistItem */ - $checklistItem = static::getFixture('checklistItemPrototype_1_1'); + $checklistItem = static::getFixture('checklistItemCampPrototype_1_1'); static::createClientWithCredentials()->request('GET', '/checklist_items/'.$checklistItem->getId()); $this->assertResponseStatusCodeSame(200); $this->assertJsonContains([ @@ -105,4 +105,50 @@ public function testGetSingleChecklistItemFromCampPrototypeIsAllowedForUnrelated ], ]); } + + public function testGetSingleChecklistItemFromSharedCampIsAllowedForUnrelatedUser() { + /** @var ChecklistItem $checklistItem */ + $checklistItem = static::getFixture('checklistItemCampShared_1_1'); + static::createClientWithCredentials()->request('GET', '/checklist_items/'.$checklistItem->getId()); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $checklistItem->getId(), + 'text' => $checklistItem->text, + '_links' => [ + 'checklist' => ['href' => $this->getIriFor('checklist1campShared')], + ], + ]); + } + + public function testGetSingleChecklistItemFromSharedCampIsAllowedForInactiveUser() { + /** @var ChecklistItem $checklistItem */ + $checklistItem = static::getFixture('checklistItemCampShared_1_1'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('GET', '/checklist_items/'.$checklistItem->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $checklistItem->getId(), + 'text' => $checklistItem->text, + '_links' => [ + 'checklist' => ['href' => $this->getIriFor('checklist1campShared')], + ], + ]); + } + + public function testGetSingleChecklistItemFromSharedCampIsAllowedForInvitedUser() { + /** @var ChecklistItem $checklistItem */ + $checklistItem = static::getFixture('checklistItemCampShared_1_1'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('GET', '/checklist_items/'.$checklistItem->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $checklistItem->getId(), + 'text' => $checklistItem->text, + '_links' => [ + 'checklist' => ['href' => $this->getIriFor('checklist1campShared')], + ], + ]); + } } diff --git a/api/tests/Api/ChecklistItems/UpdateChecklistItemTest.php b/api/tests/Api/ChecklistItems/UpdateChecklistItemTest.php index b778266490..3dc2ff8c50 100644 --- a/api/tests/Api/ChecklistItems/UpdateChecklistItemTest.php +++ b/api/tests/Api/ChecklistItems/UpdateChecklistItemTest.php @@ -87,7 +87,7 @@ public function testPatchChecklistItemIsAllowedForManager() { } public function testPatchChecklistItemInCampPrototypeIsDeniedForUnrelatedUser() { - $checklistItem = static::getFixture('checklistItemPrototype_1_1'); + $checklistItem = static::getFixture('checklistItemCampPrototype_1_1'); static::createClientWithCredentials()->request('PATCH', '/checklist_items/'.$checklistItem->getId(), ['json' => [ 'text' => 'Ziel 2', ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); @@ -98,6 +98,42 @@ public function testPatchChecklistItemInCampPrototypeIsDeniedForUnrelatedUser() ]); } + public function testPatchChecklistItemInSharedCampIsDeniedForUnrelatedUser() { + $checklistItem = static::getFixture('checklistItemCampShared_1_1'); + static::createClientWithCredentials()->request('PATCH', '/checklist_items/'.$checklistItem->getId(), ['json' => [ + 'text' => 'Ziel 2', + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testPatchChecklistItemInSharedCampIsDeniedForInactiveUser() { + $checklistItem = static::getFixture('checklistItemCampShared_1_1'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()])->request('PATCH', '/checklist_items/'.$checklistItem->getId(), ['json' => [ + 'text' => 'Ziel 2', + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testPatchChecklistItemInSharedCampIsDeniedForInvitedUser() { + $checklistItem = static::getFixture('checklistItemCampShared_1_1'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()])->request('PATCH', '/checklist_items/'.$checklistItem->getId(), ['json' => [ + 'text' => 'Ziel 2', + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + public function testPatchChecklistItemDisallowsChangingChecklist() { $checklistItem = static::getFixture('checklistItem1_1_1'); static::createClientWithCredentials()->request('PATCH', '/checklist_items/'.$checklistItem->getId(), ['json' => [ diff --git a/api/tests/Api/Checklists/CreateChecklistTest.php b/api/tests/Api/Checklists/CreateChecklistTest.php index c34f93dc46..b7e531bbac 100644 --- a/api/tests/Api/Checklists/CreateChecklistTest.php +++ b/api/tests/Api/Checklists/CreateChecklistTest.php @@ -144,6 +144,42 @@ public function testCreateChecklistInCampPrototypeIsDeniedForUnrelatedUser() { ]); } + public function testCreateChecklistInSharedCampIsDeniedForUnrelatedUser() { + static::createClientWithCredentials()->request('POST', '/checklists', ['json' => $this->getExampleWritePayload([ + 'camp' => $this->getIriFor('campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testCreateChecklistInSharedCampIsDeniedForInactiveUser() { + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()])->request('POST', '/checklists', ['json' => $this->getExampleWritePayload([ + 'camp' => $this->getIriFor('campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testCreateChecklistInSharedCampIsDeniedForInvitedUser() { + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()])->request('POST', '/checklists', ['json' => $this->getExampleWritePayload([ + 'camp' => $this->getIriFor('campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + public function testCreateChecklistValidatesMissingCamp() { static::createClientWithCredentials()->request('POST', '/checklists', ['json' => $this->getExampleWritePayload([], ['camp'])]); diff --git a/api/tests/Api/Checklists/DeleteChecklistTest.php b/api/tests/Api/Checklists/DeleteChecklistTest.php index 52da974bf5..34d9c08b5d 100644 --- a/api/tests/Api/Checklists/DeleteChecklistTest.php +++ b/api/tests/Api/Checklists/DeleteChecklistTest.php @@ -57,9 +57,9 @@ public function testDeleteChecklistIsDeniedForAnonymousUser() { } public function testDeleteChecklistIsDeniedForUnrelatedUser() { - $Checklist = static::getFixture('checklist2WithNoItems'); + $checklist = static::getFixture('checklist2WithNoItems'); static::createClientWithCredentials(['email' => static::$fixtures['user4unrelated']->getEmail()]) - ->request('DELETE', '/checklists/'.$Checklist->getId()) + ->request('DELETE', '/checklists/'.$checklist->getId()) ; $this->assertResponseStatusCodeSame(404); @@ -70,9 +70,9 @@ public function testDeleteChecklistIsDeniedForUnrelatedUser() { } public function testDeleteChecklistIsDeniedForInactiveCollaborator() { - $Checklist = static::getFixture('checklist2WithNoItems'); + $checklist = static::getFixture('checklist2WithNoItems'); static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) - ->request('DELETE', '/checklists/'.$Checklist->getId()) + ->request('DELETE', '/checklists/'.$checklist->getId()) ; $this->assertResponseStatusCodeSame(404); @@ -83,9 +83,9 @@ public function testDeleteChecklistIsDeniedForInactiveCollaborator() { } public function testDeleteChecklistIsDeniedForGuest() { - $Checklist = static::getFixture('checklist2WithNoItems'); + $checklist = static::getFixture('checklist2WithNoItems'); static::createClientWithCredentials(['email' => static::$fixtures['user3guest']->getEmail()]) - ->request('DELETE', '/checklists/'.$Checklist->getId()) + ->request('DELETE', '/checklists/'.$checklist->getId()) ; $this->assertResponseStatusCodeSame(403); @@ -96,24 +96,61 @@ public function testDeleteChecklistIsDeniedForGuest() { } public function testDeleteChecklistIsAllowedForMember() { - $Checklist = static::getFixture('checklist2WithNoItems'); + $checklist = static::getFixture('checklist2WithNoItems'); static::createClientWithCredentials(['email' => static::$fixtures['user2member']->getEmail()]) - ->request('DELETE', '/checklists/'.$Checklist->getId()) + ->request('DELETE', '/checklists/'.$checklist->getId()) ; $this->assertResponseStatusCodeSame(204); - $this->assertNull($this->getEntityManager()->getRepository(Checklist::class)->find($Checklist->getId())); + $this->assertNull($this->getEntityManager()->getRepository(Checklist::class)->find($checklist->getId())); } public function testDeleteChecklistIsAllowedForManager() { - $Checklist = static::getFixture('checklist2WithNoItems'); - static::createClientWithCredentials()->request('DELETE', '/checklists/'.$Checklist->getId()); + $checklist = static::getFixture('checklist2WithNoItems'); + static::createClientWithCredentials()->request('DELETE', '/checklists/'.$checklist->getId()); $this->assertResponseStatusCodeSame(204); - $this->assertNull($this->getEntityManager()->getRepository(Checklist::class)->find($Checklist->getId())); + $this->assertNull($this->getEntityManager()->getRepository(Checklist::class)->find($checklist->getId())); } public function testDeleteChecklistFromCampPrototypeIsDeniedForUnrelatedUser() { - $Checklist = static::getFixture('checklist1campPrototype'); - static::createClientWithCredentials()->request('DELETE', '/checklists/'.$Checklist->getId()); + $checklist = static::getFixture('checklist1campPrototype'); + static::createClientWithCredentials()->request('DELETE', '/checklists/'.$checklist->getId()); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testDeleteChecklistFromSharedCampIsDeniedForUnrelatedUser() { + $checklist = static::getFixture('checklist1campShared'); + static::createClientWithCredentials()->request('DELETE', '/checklists/'.$checklist->getId()); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testDeleteChecklistFromSharedCampIsDeniedForInactiveUser() { + $checklist = static::getFixture('checklist1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('DELETE', '/checklists/'.$checklist->getId()) + ; + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testDeleteChecklistFromSharedCampIsDeniedForInvitedUser() { + $checklist = static::getFixture('checklist1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('DELETE', '/checklists/'.$checklist->getId()) + ; $this->assertResponseStatusCodeSame(403); $this->assertJsonContains([ @@ -122,9 +159,9 @@ public function testDeleteChecklistFromCampPrototypeIsDeniedForUnrelatedUser() { ]); } - public function testDeleteChecklisIsDeniedWhenUsedInChecklistNode() { - $Checklist = static::getFixture('checklist1'); - static::createClientWithCredentials()->request('DELETE', '/checklists/'.$Checklist->getId()); + public function testDeleteChecklistIsDeniedWhenUsedInChecklistNode() { + $checklist = static::getFixture('checklist1'); + static::createClientWithCredentials()->request('DELETE', '/checklists/'.$checklist->getId()); $this->assertResponseStatusCodeSame(422); $this->assertJsonContains([ 'title' => 'An error occurred', diff --git a/api/tests/Api/Checklists/ListChecklistTest.php b/api/tests/Api/Checklists/ListChecklistsTest.php similarity index 71% rename from api/tests/Api/Checklists/ListChecklistTest.php rename to api/tests/Api/Checklists/ListChecklistsTest.php index c5f016d3e8..92672b05f4 100644 --- a/api/tests/Api/Checklists/ListChecklistTest.php +++ b/api/tests/Api/Checklists/ListChecklistsTest.php @@ -7,7 +7,7 @@ /** * @internal */ -class ListChecklistTest extends ECampApiTestCase { +class ListChecklistsTest extends ECampApiTestCase { public function testListChecklistsIsDeniedForAnonymousUser() { static::createBasicClient()->request('GET', '/checklists'); $this->assertResponseStatusCodeSame(401); @@ -24,7 +24,7 @@ public function testListChecklistsIsAllowedForLoggedInUserButFiltered() { $response = static::createClientWithCredentials()->request('GET', '/checklists'); $this->assertResponseStatusCodeSame(200); $this->assertJsonContains([ - 'totalItems' => 5, + 'totalItems' => 6, '_links' => [ 'items' => [], ], @@ -38,6 +38,7 @@ public function testListChecklistsIsAllowedForLoggedInUserButFiltered() { ['href' => $this->getIriFor('checklist2WithNoItems')], ['href' => $this->getIriFor('checklist1camp2')], ['href' => $this->getIriFor('checklist1campPrototype')], + ['href' => $this->getIriFor('checklist1campShared')], ], $response->toArray()['_links']['items']); } @@ -96,6 +97,46 @@ public function testListChecklistsFilteredByCampPrototypeIsAllowedForUnrelatedUs ], $response->toArray()['_links']['items']); } + public function testListChecklistsFilteredBySharedCampIsAllowedForUnrelatedUser() { + $camp = static::getFixture('campShared'); + $response = static::createClientWithCredentials()->request('GET', '/checklists?camp=%2Fcamps%2F'.$camp->getId()); + + $this->assertResponseStatusCodeSame(200); + + $this->assertJsonContains(['totalItems' => 1]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('checklist1campShared')], + ], $response->toArray()['_links']['items']); + } + + public function testListChecklistsFilteredBySharedCampIsAllowedForInactiveUser() { + $camp = static::getFixture('campShared'); + $response = static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('GET', '/checklists?camp=%2Fcamps%2F'.$camp->getId()) + ; + + $this->assertResponseStatusCodeSame(200); + + $this->assertJsonContains(['totalItems' => 1]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('checklist1campShared')], + ], $response->toArray()['_links']['items']); + } + + public function testListChecklistsFilteredBySharedCampIsAllowedForInvitedUser() { + $camp = static::getFixture('campShared'); + $response = static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('GET', '/checklists?camp=%2Fcamps%2F'.$camp->getId()) + ; + + $this->assertResponseStatusCodeSame(200); + + $this->assertJsonContains(['totalItems' => 1]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('checklist1campShared')], + ], $response->toArray()['_links']['items']); + } + public function testListChecklistsAsCampSubresourceIsAllowedForCollaborator() { $camp = static::getFixture('camp1'); $response = static::createClientWithCredentials()->request('GET', '/camps/'.$camp->getId().'/checklists'); diff --git a/api/tests/Api/Checklists/ReadChecklistTest.php b/api/tests/Api/Checklists/ReadChecklistTest.php index 1857746c1c..58c644ee3f 100644 --- a/api/tests/Api/Checklists/ReadChecklistTest.php +++ b/api/tests/Api/Checklists/ReadChecklistTest.php @@ -105,4 +105,50 @@ public function testGetSingleChecklistFromCampPrototypeIsAllowedForUnrelatedUser ], ]); } + + public function testGetSingleChecklistFromSharedCampIsAllowedForUnrelatedUser() { + /** @var Checklist $checklist */ + $checklist = static::getFixture('checklist1campShared'); + static::createClientWithCredentials()->request('GET', '/checklists/'.$checklist->getId()); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $checklist->getId(), + 'name' => $checklist->name, + '_links' => [ + 'camp' => ['href' => $this->getIriFor('campShared')], + ], + ]); + } + + public function testGetSingleChecklistFromSharedCampIsAllowedForInactiveUser() { + /** @var Checklist $checklist */ + $checklist = static::getFixture('checklist1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('GET', '/checklists/'.$checklist->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $checklist->getId(), + 'name' => $checklist->name, + '_links' => [ + 'camp' => ['href' => $this->getIriFor('campShared')], + ], + ]); + } + + public function testGetSingleChecklistFromSharedCampIsAllowedForInvitedUser() { + /** @var Checklist $checklist */ + $checklist = static::getFixture('checklist1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('GET', '/checklists/'.$checklist->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $checklist->getId(), + 'name' => $checklist->name, + '_links' => [ + 'camp' => ['href' => $this->getIriFor('campShared')], + ], + ]); + } } diff --git a/api/tests/Api/Checklists/UpdateChecklistTest.php b/api/tests/Api/Checklists/UpdateChecklistTest.php index f02c7ae0e3..165b2c1d7b 100644 --- a/api/tests/Api/Checklists/UpdateChecklistTest.php +++ b/api/tests/Api/Checklists/UpdateChecklistTest.php @@ -142,6 +142,42 @@ public function testPatchChecklistInCampPrototypeIsDeniedForUnrelatedUser() { ]); } + public function testPatchChecklistInSharedCampIsDeniedForUnrelatedUser() { + $checklist = static::getFixture('checklist1campShared'); + static::createClientWithCredentials()->request('PATCH', '/checklists/'.$checklist->getId(), ['json' => [ + 'name' => 'ChecklistName', + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testPatchChecklistInSharedCampIsDeniedForInactiveUser() { + $checklist = static::getFixture('checklist1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()])->request('PATCH', '/checklists/'.$checklist->getId(), ['json' => [ + 'name' => 'ChecklistName', + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testPatchChecklistInSharedCampIsDeniedForInvitedUser() { + $checklist = static::getFixture('checklist1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()])->request('PATCH', '/checklists/'.$checklist->getId(), ['json' => [ + 'name' => 'ChecklistName', + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + public function testPatchChecklistDisallowsChangingCamp() { $checklist = static::getFixture('checklist1'); static::createClientWithCredentials()->request('PATCH', '/checklists/'.$checklist->getId(), ['json' => [ diff --git a/api/tests/Api/Comments/CreateCommentTest.php b/api/tests/Api/Comments/CreateCommentTest.php index 9e2f665b80..ca5d55ac8d 100644 --- a/api/tests/Api/Comments/CreateCommentTest.php +++ b/api/tests/Api/Comments/CreateCommentTest.php @@ -57,6 +57,58 @@ public function testCreateCommentIsAllowedForManager() { $this->assertJsonContains($this->getExampleReadPayload()); } + public function testCreateCommentInCampPrototypeIsDeniedForUnrelatedUser() { + static::createClientWithCredentials()->request('POST', '/comments', ['json' => $this->getExampleWritePayload([ + 'camp' => $this->getIriFor('campPrototype'), + 'activity' => $this->getIriFor('activity1campPrototype'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testCreateCommentInSharedCampIsDeniedForUnrelatedUser() { + static::createClientWithCredentials()->request('POST', '/comments', ['json' => $this->getExampleWritePayload([ + 'camp' => $this->getIriFor('campShared'), + 'activity' => $this->getIriFor('activity1campPrototype'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testCreateCommentInSharedCampIsDeniedForInactiveUser() { + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()])->request('POST', '/comments', ['json' => $this->getExampleWritePayload([ + 'camp' => $this->getIriFor('campShared'), + 'activity' => $this->getIriFor('activity1campPrototype'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testCreateCommentInSharedCampIsDeniedForInvitedUser() { + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()])->request('POST', '/comments', ['json' => $this->getExampleWritePayload([ + 'camp' => $this->getIriFor('campShared'), + 'activity' => $this->getIriFor('activity1campPrototype'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + public function testCreateCommentValidatesMissingText() { static::createClientWithCredentials()->request('POST', '/comments', ['json' => $this->getExampleWritePayload([], ['textHtml'])]); diff --git a/api/tests/Api/Comments/DeleteCommentTest.php b/api/tests/Api/Comments/DeleteCommentTest.php index c8fed8e2eb..c5d2eaa418 100644 --- a/api/tests/Api/Comments/DeleteCommentTest.php +++ b/api/tests/Api/Comments/DeleteCommentTest.php @@ -53,4 +53,14 @@ public function testDeleteCommentIsAllowedForAuthor() { $this->assertResponseStatusCodeSame(204); $this->assertNull($this->getEntityManager()->getRepository(Comment::class)->find($comment->getId())); } + + public function testDeleteCommentIsAllowedForAuthorEvenWhenAuthorIsNotCampCollaboratorInCampAnymore() { + $comment = static::getFixture('comment2'); + static::createClientWithCredentials(['email' => static::$fixtures['user4unrelated']->getEmail()]) + ->request('DELETE', '/comments/'.$comment->getId()) + ; + + $this->assertResponseStatusCodeSame(204); + $this->assertNull($this->getEntityManager()->getRepository(Comment::class)->find($comment->getId())); + } } diff --git a/api/tests/Api/Comments/ListCommentsTest.php b/api/tests/Api/Comments/ListCommentsTest.php index 18ea92eb15..7f658c1f7e 100644 --- a/api/tests/Api/Comments/ListCommentsTest.php +++ b/api/tests/Api/Comments/ListCommentsTest.php @@ -25,11 +25,18 @@ public function testListCommentsIsAllowedForLoggedInUser() { $this->assertResponseStatusCodeSame(200); $this->assertJsonContains([ - 'totalItems' => 3, + 'totalItems' => 5, '_embedded' => [ 'items' => [], ], ]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('comment1')], + ['href' => $this->getIriFor('comment2')], + ['href' => $this->getIriFor('comment3')], + ['href' => $this->getIriFor('comment1campPrototype')], + ['href' => $this->getIriFor('comment1campShared')], + ], $response->toArray()['_links']['items']); } public function testListCommentsSortyByCreateTime() { @@ -51,14 +58,14 @@ public function testListCommentsSortyByCreateTime() { $response = $client->request('GET', '/comments'); $items = $response->toArray()['_embedded']['items']; - $this->assertCount(4, $items); + $this->assertCount(6, $items); $this->assertGreaterThanOrEqual($items[0]['createTime'], $items[3]['createTime']); - $this->assertEquals($items[3]['createTime'], $lastComment['createTime']); + $this->assertEquals($items[5]['createTime'], $lastComment['createTime']); } public function testListCommentsFilteredByActivity() { $activity = static::getFixture('activity1'); - $response = static::createClientWithCredentials()->request('GET', '/comments?activity='.$this->getIriFor($activity)); + static::createClientWithCredentials()->request('GET', '/comments?activity='.$this->getIriFor($activity)); $this->assertResponseStatusCodeSame(200); $this->assertJsonContains([ @@ -68,7 +75,7 @@ public function testListCommentsFilteredByActivity() { public function testListCommentsActivitySubresource() { $activity = static::getFixture('activity1'); - $response = static::createClientWithCredentials()->request('GET', $this->getIriFor($activity).'/comments'); + static::createClientWithCredentials()->request('GET', $this->getIriFor($activity).'/comments'); $this->assertResponseStatusCodeSame(200); $this->assertJsonContains([ @@ -78,7 +85,7 @@ public function testListCommentsActivitySubresource() { public function testListCommentsActivitySubresourceIsDeniedForUnrelatedUser() { $activity = static::getFixture('activity1'); - $response = static::createClientWithCredentials(['email' => static::$fixtures['user4unrelated']->getEmail()])->request('GET', $this->getIriFor($activity).'/comments'); + static::createClientWithCredentials(['email' => static::$fixtures['user4unrelated']->getEmail()])->request('GET', $this->getIriFor($activity).'/comments'); $this->assertResponseStatusCodeSame(404); $this->assertJsonContains([ @@ -86,4 +93,48 @@ public function testListCommentsActivitySubresourceIsDeniedForUnrelatedUser() { 'detail' => 'Relation for link security not found.', ]); } + + public function testListCommentsActivitySubresourceInCampPrototypeIsAllowedForUnrelatedUser() { + $activity = static::getFixture('activity1campPrototype'); + static::createClientWithCredentials()->request('GET', $this->getIriFor($activity).'/comments'); + + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 1, + ]); + } + + public function testListCommentsActivitySubresourceInSharedCampIsAllowedForUnrelatedUser() { + $activity = static::getFixture('activity1campShared'); + static::createClientWithCredentials()->request('GET', $this->getIriFor($activity).'/comments'); + + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 1, + ]); + } + + public function testListCommentsActivitySubresourceInSharedCampIsAllowedForInactiveUser() { + $activity = static::getFixture('activity1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('GET', $this->getIriFor($activity).'/comments') + ; + + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 1, + ]); + } + + public function testListCommentsActivitySubresourceInSharedCampIsAllowedForInvitedUser() { + $activity = static::getFixture('activity1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('GET', $this->getIriFor($activity).'/comments') + ; + + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 1, + ]); + } } diff --git a/api/tests/Api/Comments/ReadCommentTest.php b/api/tests/Api/Comments/ReadCommentTest.php index 6a26bae308..678b489cdf 100644 --- a/api/tests/Api/Comments/ReadCommentTest.php +++ b/api/tests/Api/Comments/ReadCommentTest.php @@ -59,4 +59,56 @@ public function testGetSingleCommentIsDeniedForUnrelatedUser() { 'detail' => 'Not Found', ]); } + + public function testGetSingleCommentInCampPrototypeIsAllowedForUnrelatedUser() { + $comment = static::getFixture('comment1campPrototype'); + static::createClientWithCredentials() + ->request('GET', '/comments/'.$comment->getId()) + ; + + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $comment->getId(), + 'textHtml' => $comment->textHtml, + ]); + } + + public function testGetSingleCommentInSharedCampIsAllowedForUnrelatedUser() { + $comment = static::getFixture('comment1campShared'); + static::createClientWithCredentials() + ->request('GET', '/comments/'.$comment->getId()) + ; + + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $comment->getId(), + 'textHtml' => $comment->textHtml, + ]); + } + + public function testGetSingleCommentInSharedCampIsAllowedForInactiveUser() { + $comment = static::getFixture('comment1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('GET', '/comments/'.$comment->getId()) + ; + + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $comment->getId(), + 'textHtml' => $comment->textHtml, + ]); + } + + public function testGetSingleCommentInSharedCampIsAllowedForInvitedUser() { + $comment = static::getFixture('comment1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('GET', '/comments/'.$comment->getId()) + ; + + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $comment->getId(), + 'textHtml' => $comment->textHtml, + ]); + } } diff --git a/api/tests/Api/Comments/UpdateCommentTest.php b/api/tests/Api/Comments/UpdateCommentTest.php new file mode 100644 index 0000000000..e8014976ec --- /dev/null +++ b/api/tests/Api/Comments/UpdateCommentTest.php @@ -0,0 +1,17 @@ +request('PATCH', '/comments/'.$comment->getId(), ['json' => [], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + + $this->assertResponseStatusCodeSame(405); // method not allowed + } +} diff --git a/api/tests/Api/ContentNodes/ChecklistNode/DeleteChecklistNodeTest.php b/api/tests/Api/ContentNodes/ChecklistNode/DeleteChecklistNodeTest.php index 963a52a82b..5089124bcb 100644 --- a/api/tests/Api/ContentNodes/ChecklistNode/DeleteChecklistNodeTest.php +++ b/api/tests/Api/ContentNodes/ChecklistNode/DeleteChecklistNodeTest.php @@ -13,5 +13,7 @@ public function setUp(): void { $this->endpoint = '/content_node/checklist_nodes'; $this->defaultEntity = static::getFixture('checklistNode3'); + $this->campPrototypeEntity = static::getFixture('checklistNodeCampPrototype'); + $this->sharedCampEntity = static::getFixture('checklistNodeCampShared'); } } diff --git a/api/tests/Api/ContentNodes/ChecklistNode/ListChecklistNodeTest.php b/api/tests/Api/ContentNodes/ChecklistNode/ListChecklistNodeTest.php index 723700944e..19c5134890 100644 --- a/api/tests/Api/ContentNodes/ChecklistNode/ListChecklistNodeTest.php +++ b/api/tests/Api/ContentNodes/ChecklistNode/ListChecklistNodeTest.php @@ -21,5 +21,10 @@ public function setUp(): void { $this->contentNodesCampUnrelated = [ $this->getIriFor('checklistNodeCampUnrelated'), ]; + + $this->contentNodesPublicCamps = [ + $this->getIriFor('checklistNodeCampPrototype'), + $this->getIriFor('checklistNodeCampShared'), + ]; } } diff --git a/api/tests/Api/ContentNodes/ChecklistNode/ReadChecklistNodeTest.php b/api/tests/Api/ContentNodes/ChecklistNode/ReadChecklistNodeTest.php index f68349061a..8f3a806524 100644 --- a/api/tests/Api/ContentNodes/ChecklistNode/ReadChecklistNodeTest.php +++ b/api/tests/Api/ContentNodes/ChecklistNode/ReadChecklistNodeTest.php @@ -13,5 +13,7 @@ public function setUp(): void { $this->endpoint = '/content_node/checklist_nodes'; $this->defaultEntity = static::getFixture('checklistNode3'); + $this->campPrototypeEntity = static::getFixture('checklistNodeCampPrototype'); + $this->sharedCampEntity = static::getFixture('checklistNodeCampShared'); } } diff --git a/api/tests/Api/ContentNodes/ChecklistNode/UpdateChecklistNodeTest.php b/api/tests/Api/ContentNodes/ChecklistNode/UpdateChecklistNodeTest.php index d60b1fa319..59d0acd2b0 100644 --- a/api/tests/Api/ContentNodes/ChecklistNode/UpdateChecklistNodeTest.php +++ b/api/tests/Api/ContentNodes/ChecklistNode/UpdateChecklistNodeTest.php @@ -14,6 +14,8 @@ public function setUp(): void { $this->endpoint = '/content_node/checklist_nodes'; $this->defaultEntity = static::getFixture('checklistNode1'); + $this->campPrototypeEntity = static::getFixture('checklistNodeCampPrototype'); + $this->sharedCampEntity = static::getFixture('checklistNodeCampShared'); } public function testAddChecklistItemIsDeniedForGuest() { @@ -100,6 +102,66 @@ public function testRemoveChecklistItemForManager() { $this->assertNotContains($checklistItem, $checklistNode->getChecklistItems()); } + public function testRemoveChecklistItemInCampPrototypeIsDeniedForUnrelatedUser() { + $entity = static::getFixture('checklistNodeCampPrototype'); + $checklistItemId = static::getFixture('checklistItemCampPrototype_1_1')->getId(); + static::createClientWithCredentials() + ->request('PATCH', $this->endpoint.'/'.$entity->getId(), ['json' => [ + 'removeChecklistItemIds' => [$checklistItemId], + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]) + ; + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testRemoveChecklistItemInSharedCampIsDeniedForUnrelatedUser() { + $entity = static::getFixture('checklistNodeCampShared'); + $checklistItemId = static::getFixture('checklistItemCampShared_1_1')->getId(); + static::createClientWithCredentials() + ->request('PATCH', $this->endpoint.'/'.$entity->getId(), ['json' => [ + 'removeChecklistItemIds' => [$checklistItemId], + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]) + ; + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testRemoveChecklistItemInSharedCampIsDeniedForInactiveUser() { + $entity = static::getFixture('checklistNodeCampShared'); + $checklistItemId = static::getFixture('checklistItemCampShared_1_1')->getId(); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('PATCH', $this->endpoint.'/'.$entity->getId(), ['json' => [ + 'removeChecklistItemIds' => [$checklistItemId], + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]) + ; + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testRemoveChecklistItemInSharedCampIsDeniedForInvitedUser() { + $entity = static::getFixture('checklistNodeCampShared'); + $checklistItemId = static::getFixture('checklistItemCampShared_1_1')->getId(); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('PATCH', $this->endpoint.'/'.$entity->getId(), ['json' => [ + 'removeChecklistItemIds' => [$checklistItemId], + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]) + ; + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + public function testAddChecklistItemOfOtherCampIsDenied() { $checklistItemId = static::getFixture('checklistItem2_1_1')->getId(); static::createClientWithCredentials(['email' => static::getFixture('user2member')->getEmail()]) diff --git a/api/tests/Api/ContentNodes/ColumnLayout/DeleteColumnLayoutTest.php b/api/tests/Api/ContentNodes/ColumnLayout/DeleteColumnLayoutTest.php index 9359c819d0..45d49b8a94 100644 --- a/api/tests/Api/ContentNodes/ColumnLayout/DeleteColumnLayoutTest.php +++ b/api/tests/Api/ContentNodes/ColumnLayout/DeleteColumnLayoutTest.php @@ -13,6 +13,8 @@ public function setUp(): void { $this->endpoint = '/content_node/column_layouts'; $this->defaultEntity = static::getFixture('columnLayoutChild1'); + $this->campPrototypeEntity = static::getFixture('columnLayout3campPrototype'); + $this->sharedCampEntity = static::getFixture('columnLayout3campShared'); } public function testDeleteColumnLayoutAlsoDeletesChildren() { diff --git a/api/tests/Api/ContentNodes/ColumnLayout/ListColumnLayoutTest.php b/api/tests/Api/ContentNodes/ColumnLayout/ListColumnLayoutTest.php index 09eed8e3a1..dfcdd0e1e8 100644 --- a/api/tests/Api/ContentNodes/ColumnLayout/ListColumnLayoutTest.php +++ b/api/tests/Api/ContentNodes/ColumnLayout/ListColumnLayoutTest.php @@ -30,9 +30,13 @@ public function setUp(): void { $this->getIriFor('columnLayout2campUnrelated'), ]; - $this->contentNodesCampPrototypes = [ + $this->contentNodesPublicCamps = [ $this->getIriFor('columnLayout1campPrototype'), $this->getIriFor('columnLayout2campPrototype'), + $this->getIriFor('columnLayout3campPrototype'), + $this->getIriFor('columnLayout1campShared'), + $this->getIriFor('columnLayout2campShared'), + $this->getIriFor('columnLayout3campShared'), ]; } } diff --git a/api/tests/Api/ContentNodes/ColumnLayout/ReadColumnLayoutTest.php b/api/tests/Api/ContentNodes/ColumnLayout/ReadColumnLayoutTest.php index ff4ac4fd6c..14ad4fb44e 100644 --- a/api/tests/Api/ContentNodes/ColumnLayout/ReadColumnLayoutTest.php +++ b/api/tests/Api/ContentNodes/ColumnLayout/ReadColumnLayoutTest.php @@ -14,6 +14,8 @@ public function setUp(): void { $this->endpoint = '/content_node/column_layouts'; $this->defaultEntity = static::getFixture('columnLayoutChild1'); + $this->campPrototypeEntity = static::getFixture('columnLayout3campPrototype'); + $this->sharedCampEntity = static::getFixture('columnLayout3campShared'); } public function testGetColumnLayout() { diff --git a/api/tests/Api/ContentNodes/ColumnLayout/UpdateColumnLayoutTest.php b/api/tests/Api/ContentNodes/ColumnLayout/UpdateColumnLayoutTest.php index 4c465298ef..2581c3299e 100644 --- a/api/tests/Api/ContentNodes/ColumnLayout/UpdateColumnLayoutTest.php +++ b/api/tests/Api/ContentNodes/ColumnLayout/UpdateColumnLayoutTest.php @@ -13,6 +13,8 @@ public function setUp(): void { $this->endpoint = '/content_node/column_layouts'; $this->defaultEntity = static::getFixture('columnLayoutChild1'); + $this->campPrototypeEntity = static::getFixture('columnLayout3campPrototype'); + $this->sharedCampEntity = static::getFixture('columnLayout3campShared'); } public function testPatchColumnLayoutAcceptsValidJson() { diff --git a/api/tests/Api/ContentNodes/ContentNode/ListContentNodesTest.php b/api/tests/Api/ContentNodes/ContentNode/ListContentNodesTest.php index 7d3084eba9..9e864e0022 100644 --- a/api/tests/Api/ContentNodes/ContentNode/ListContentNodesTest.php +++ b/api/tests/Api/ContentNodes/ContentNode/ListContentNodesTest.php @@ -23,7 +23,7 @@ public function testListContentNodesIsAllowedForLoggedInUserButFiltered() { $response = static::createClientWithCredentials()->request('GET', '/content_nodes'); $this->assertResponseStatusCodeSame(200); $this->assertJsonContains([ - 'totalItems' => 23, + 'totalItems' => 39, '_links' => [ 'items' => [], ], @@ -43,8 +43,15 @@ public function testListContentNodesIsAllowedForLoggedInUserButFiltered() { ['href' => $this->getIriFor('columnLayout5')], ['href' => $this->getIriFor('columnLayout1camp2')], ['href' => $this->getIriFor('columnLayout2camp2')], + ['href' => $this->getIriFor('checklistNodeCampPrototype')], ['href' => $this->getIriFor('columnLayout1campPrototype')], ['href' => $this->getIriFor('columnLayout2campPrototype')], + ['href' => $this->getIriFor('columnLayout3campPrototype')], + ['href' => $this->getIriFor('materialNodeCampPrototype')], + ['href' => $this->getIriFor('multiSelectCampPrototype')], + ['href' => $this->getIriFor('responsiveLayoutCampPrototype')], + ['href' => $this->getIriFor('singleTextCampPrototype')], + ['href' => $this->getIriFor('storyboardCampPrototype')], ['href' => $this->getIriFor('singleText1')], ['href' => $this->getIriFor('singleText2')], ['href' => $this->getIriFor('safetyConsiderations1')], @@ -55,6 +62,15 @@ public function testListContentNodesIsAllowedForLoggedInUserButFiltered() { ['href' => $this->getIriFor('multiSelect1')], ['href' => $this->getIriFor('multiSelect2')], ['href' => $this->getIriFor('responsiveLayout1')], + ['href' => $this->getIriFor('checklistNodeCampShared')], + ['href' => $this->getIriFor('columnLayout1campShared')], + ['href' => $this->getIriFor('columnLayout2campShared')], + ['href' => $this->getIriFor('columnLayout3campShared')], + ['href' => $this->getIriFor('materialNodeCampShared')], + ['href' => $this->getIriFor('multiSelectCampShared')], + ['href' => $this->getIriFor('responsiveLayoutCampShared')], + ['href' => $this->getIriFor('singleTextCampShared')], + ['href' => $this->getIriFor('storyboardCampShared')], ], $response->toArray()['_links']['items']); } @@ -117,12 +133,12 @@ public function testListContentNodesFilteredByPeriodIsDeniedForInactiveCollabora ]); } - public function testListContentNodesFilteredByPeriodInCampPrototypeIsAllowedForCollaborator() { + public function testListContentNodesFilteredByPeriodInCampPrototypeIsAllowedForUnrelatedUser() { $period = static::getFixture('period1campPrototype'); $response = static::createClientWithCredentials()->request('GET', '/content_nodes?period=%2Fperiods%2F'.$period->getId()); $this->assertResponseStatusCodeSame(200); $this->assertJsonContains([ - 'totalItems' => 1, + 'totalItems' => 8, '_links' => [ 'items' => [], ], @@ -131,7 +147,93 @@ public function testListContentNodesFilteredByPeriodInCampPrototypeIsAllowedForC ], ]); $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('checklistNodeCampPrototype')], ['href' => $this->getIriFor('columnLayout1campPrototype')], + ['href' => $this->getIriFor('columnLayout3campPrototype')], + ['href' => $this->getIriFor('materialNodeCampPrototype')], + ['href' => $this->getIriFor('multiSelectCampPrototype')], + ['href' => $this->getIriFor('responsiveLayoutCampPrototype')], + ['href' => $this->getIriFor('singleTextCampPrototype')], + ['href' => $this->getIriFor('storyboardCampPrototype')], + ], $response->toArray()['_links']['items']); + } + + public function testListContentNodesFilteredByPeriodInSharedCampIsAllowedForUnrelatedUser() { + $period = static::getFixture('period1campShared'); + $response = static::createClientWithCredentials()->request('GET', '/content_nodes?period=%2Fperiods%2F'.$period->getId()); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 8, + '_links' => [ + 'items' => [], + ], + '_embedded' => [ + 'items' => [], + ], + ]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('checklistNodeCampShared')], + ['href' => $this->getIriFor('columnLayout1campShared')], + ['href' => $this->getIriFor('columnLayout3campShared')], + ['href' => $this->getIriFor('materialNodeCampShared')], + ['href' => $this->getIriFor('multiSelectCampShared')], + ['href' => $this->getIriFor('responsiveLayoutCampShared')], + ['href' => $this->getIriFor('singleTextCampShared')], + ['href' => $this->getIriFor('storyboardCampShared')], + ], $response->toArray()['_links']['items']); + } + + public function testListContentNodesFilteredByPeriodInSharedCampIsAllowedForInactiveUser() { + $period = static::getFixture('period1campShared'); + $response = static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('GET', '/content_nodes?period=%2Fperiods%2F'.$period->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 8, + '_links' => [ + 'items' => [], + ], + '_embedded' => [ + 'items' => [], + ], + ]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('checklistNodeCampShared')], + ['href' => $this->getIriFor('columnLayout1campShared')], + ['href' => $this->getIriFor('columnLayout3campShared')], + ['href' => $this->getIriFor('materialNodeCampShared')], + ['href' => $this->getIriFor('multiSelectCampShared')], + ['href' => $this->getIriFor('responsiveLayoutCampShared')], + ['href' => $this->getIriFor('singleTextCampShared')], + ['href' => $this->getIriFor('storyboardCampShared')], + ], $response->toArray()['_links']['items']); + } + + public function testListContentNodesFilteredByPeriodInSharedCampIsAllowedForInvitedUser() { + $period = static::getFixture('period1campShared'); + $response = static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('GET', '/content_nodes?period=%2Fperiods%2F'.$period->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 8, + '_links' => [ + 'items' => [], + ], + '_embedded' => [ + 'items' => [], + ], + ]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('checklistNodeCampShared')], + ['href' => $this->getIriFor('columnLayout1campShared')], + ['href' => $this->getIriFor('columnLayout3campShared')], + ['href' => $this->getIriFor('materialNodeCampShared')], + ['href' => $this->getIriFor('multiSelectCampShared')], + ['href' => $this->getIriFor('responsiveLayoutCampShared')], + ['href' => $this->getIriFor('singleTextCampShared')], + ['href' => $this->getIriFor('storyboardCampShared')], ], $response->toArray()['_links']['items']); } diff --git a/api/tests/Api/ContentNodes/CreateContentNodeTestCase.php b/api/tests/Api/ContentNodes/CreateContentNodeTestCase.php index 06dc8b3c30..951b84ca0d 100644 --- a/api/tests/Api/ContentNodes/CreateContentNodeTestCase.php +++ b/api/tests/Api/ContentNodes/CreateContentNodeTestCase.php @@ -91,6 +91,34 @@ public function testCreateIsAllowedForManager() { $this->assertJsonContains($this->getExampleReadPayload($newContentNode), true); } + public function testCreateInCampPrototypeIsDeniedForUnrelatedUser() { + $this->create(payload: $this->getExampleWritePayload([ + 'parent' => $this->getIriFor(static::$fixtures['columnLayout1campPrototype']), + ]), user: static::$fixtures['user1manager']); + $this->assertResponseStatusCodeSame(403); + } + + public function testCreateInSharedCampIsDeniedForUnrelatedUser() { + $this->create(payload: $this->getExampleWritePayload([ + 'parent' => $this->getIriFor(static::$fixtures['columnLayout1campShared']), + ]), user: static::$fixtures['user1manager']); + $this->assertResponseStatusCodeSame(403); + } + + public function testCreateInSharedCampIsDeniedForInactiveUser() { + $this->create(payload: $this->getExampleWritePayload([ + 'parent' => $this->getIriFor(static::$fixtures['columnLayout1campShared']), + ]), user: static::$fixtures['user5inactive']); + $this->assertResponseStatusCodeSame(403); + } + + public function testCreateInSharedCampIsDeniedForInvitedUser() { + $this->create(payload: $this->getExampleWritePayload([ + 'parent' => $this->getIriFor(static::$fixtures['columnLayout1campShared']), + ]), user: static::$fixtures['user6invited']); + $this->assertResponseStatusCodeSame(403); + } + #[DataProvider('getContentNodesWhichCannotHaveChildren')] public function testCreateRejectsParentsWhichDontSupportChildren(string $idOfParentFixture) { $this->defaultParent = static::getFixture($idOfParentFixture); @@ -101,7 +129,7 @@ public function testCreateRejectsParentsWhichDontSupportChildren(string $idOfPar 'violations' => [ 0 => [ 'propertyPath' => 'parent', - 'message' => 'This parent does not support children, only content_nodes of type column_layout support children.', + 'message' => 'This parent does not support children, only content_nodes of type column_layout or responsive_layout support children.', ], ], ]); diff --git a/api/tests/Api/ContentNodes/DeleteContentNodeTestCase.php b/api/tests/Api/ContentNodes/DeleteContentNodeTestCase.php index a3f772191c..c2c32d53da 100644 --- a/api/tests/Api/ContentNodes/DeleteContentNodeTestCase.php +++ b/api/tests/Api/ContentNodes/DeleteContentNodeTestCase.php @@ -2,6 +2,7 @@ namespace App\Tests\Api\ContentNodes; +use App\Entity\BaseEntity; use App\Entity\ContentNode; use App\Tests\Api\ECampApiTestCase; @@ -13,6 +14,9 @@ * @internal */ abstract class DeleteContentNodeTestCase extends ECampApiTestCase { + protected BaseEntity $campPrototypeEntity; + protected BaseEntity $sharedCampEntity; + public function setUp(): void { parent::setUp(); } @@ -63,4 +67,28 @@ public function testDeleteIsAllowedForManager() { $this->assertResponseStatusCodeSame(204); $this->assertEntityWasRemoved(); } + + public function testDeleteInCampPrototypeIsDeniedForUnrelatedUser() { + $this->delete($this->campPrototypeEntity); + $this->assertResponseStatusCodeSame(403); + $this->assertEntityStillExists(); + } + + public function testDeleteInSharedCampIsDeniedForUnrelatedUser() { + $this->delete($this->sharedCampEntity); + $this->assertResponseStatusCodeSame(403); + $this->assertEntityStillExists(); + } + + public function testDeleteInSharedCampIsDeniedForInactiveUser() { + $this->delete($this->sharedCampEntity, user: static::$fixtures['user5inactive']); + $this->assertResponseStatusCodeSame(403); + $this->assertEntityStillExists(); + } + + public function testDeleteInSharedCampIsDeniedForInvitedUser() { + $this->delete($this->sharedCampEntity, user: static::$fixtures['user6invited']); + $this->assertResponseStatusCodeSame(403); + $this->assertEntityStillExists(); + } } diff --git a/api/tests/Api/ContentNodes/ListContentNodeTestCase.php b/api/tests/Api/ContentNodes/ListContentNodeTestCase.php index 30fd4be594..c5ed59eaea 100644 --- a/api/tests/Api/ContentNodes/ListContentNodeTestCase.php +++ b/api/tests/Api/ContentNodes/ListContentNodeTestCase.php @@ -20,7 +20,7 @@ abstract class ListContentNodeTestCase extends ECampApiTestCase { protected array $contentNodesCampUnrelated = []; // content nodes visible for everyone - protected array $contentNodesCampPrototypes = []; + protected array $contentNodesPublicCamps = []; public function setUp(): void { parent::setUp(); @@ -38,36 +38,36 @@ public function testListForAnonymousUser() { public function testListForInvitedCollaborator() { $response = $this->list(user: static::$fixtures['user6invited']); $this->assertResponseStatusCodeSame(200); - $this->assertJsonContainsItems($response, $this->contentNodesCampPrototypes); + $this->assertJsonContainsItems($response, $this->contentNodesPublicCamps); } public function testListForInactiveCollaborator() { $response = $this->list(user: static::$fixtures['user5inactive']); $this->assertResponseStatusCodeSame(200); - $this->assertJsonContainsItems($response, $this->contentNodesCampPrototypes); + $this->assertJsonContainsItems($response, $this->contentNodesPublicCamps); } public function testListForUnrelatedUser() { $response = $this->list(user: static::$fixtures['user4unrelated']); $this->assertResponseStatusCodeSame(200); - $this->assertJsonContainsItems($response, array_merge($this->contentNodesCampUnrelated, $this->contentNodesCampPrototypes)); + $this->assertJsonContainsItems($response, array_merge($this->contentNodesCampUnrelated, $this->contentNodesPublicCamps)); } public function testListForGuest() { $response = $this->list(user: static::$fixtures['user3guest']); $this->assertResponseStatusCodeSame(200); - $this->assertJsonContainsItems($response, array_merge($this->contentNodesCamp1and2, $this->contentNodesCampPrototypes)); + $this->assertJsonContainsItems($response, array_merge($this->contentNodesCamp1and2, $this->contentNodesPublicCamps)); } public function testListForMember() { $response = $this->list(user: static::$fixtures['user2member']); $this->assertResponseStatusCodeSame(200); - $this->assertJsonContainsItems($response, array_merge($this->contentNodesCamp1and2, $this->contentNodesCampPrototypes)); + $this->assertJsonContainsItems($response, array_merge($this->contentNodesCamp1and2, $this->contentNodesPublicCamps)); } public function testListForManager() { $response = $this->list(user: static::$fixtures['user1manager']); $this->assertResponseStatusCodeSame(200); - $this->assertJsonContainsItems($response, array_merge($this->contentNodesCamp1and2, $this->contentNodesCampPrototypes)); + $this->assertJsonContainsItems($response, array_merge($this->contentNodesCamp1and2, $this->contentNodesPublicCamps)); } } diff --git a/api/tests/Api/ContentNodes/MaterialNode/DeleteMaterialNodeTest.php b/api/tests/Api/ContentNodes/MaterialNode/DeleteMaterialNodeTest.php index e88d27e4af..54700284a8 100644 --- a/api/tests/Api/ContentNodes/MaterialNode/DeleteMaterialNodeTest.php +++ b/api/tests/Api/ContentNodes/MaterialNode/DeleteMaterialNodeTest.php @@ -13,5 +13,7 @@ public function setUp(): void { $this->endpoint = '/content_node/material_nodes'; $this->defaultEntity = static::getFixture('materialNode1'); + $this->campPrototypeEntity = static::getFixture('materialNodeCampPrototype'); + $this->sharedCampEntity = static::getFixture('materialNodeCampShared'); } } diff --git a/api/tests/Api/ContentNodes/MaterialNode/ListMaterialNodeTest.php b/api/tests/Api/ContentNodes/MaterialNode/ListMaterialNodeTest.php index e74f7bef3d..9ae6881b8c 100644 --- a/api/tests/Api/ContentNodes/MaterialNode/ListMaterialNodeTest.php +++ b/api/tests/Api/ContentNodes/MaterialNode/ListMaterialNodeTest.php @@ -21,5 +21,10 @@ public function setUp(): void { $this->contentNodesCampUnrelated = [ $this->getIriFor('materialNodeCampUnrelated'), ]; + + $this->contentNodesPublicCamps = [ + $this->getIriFor('materialNodeCampPrototype'), + $this->getIriFor('materialNodeCampShared'), + ]; } } diff --git a/api/tests/Api/ContentNodes/MaterialNode/ReadMaterialNodeTest.php b/api/tests/Api/ContentNodes/MaterialNode/ReadMaterialNodeTest.php index 82c222b4f5..99a56e61eb 100644 --- a/api/tests/Api/ContentNodes/MaterialNode/ReadMaterialNodeTest.php +++ b/api/tests/Api/ContentNodes/MaterialNode/ReadMaterialNodeTest.php @@ -15,6 +15,8 @@ public function setUp(): void { $this->endpoint = '/content_node/material_nodes'; $this->defaultEntity = static::getFixture('materialNode1'); + $this->campPrototypeEntity = static::getFixture('materialNodeCampPrototype'); + $this->sharedCampEntity = static::getFixture('materialNodeCampShared'); } public function testGetMaterialNode() { diff --git a/api/tests/Api/ContentNodes/MaterialNode/UpdateMaterialNodeTest.php b/api/tests/Api/ContentNodes/MaterialNode/UpdateMaterialNodeTest.php index 4ee6b87feb..f23e5c5fa6 100644 --- a/api/tests/Api/ContentNodes/MaterialNode/UpdateMaterialNodeTest.php +++ b/api/tests/Api/ContentNodes/MaterialNode/UpdateMaterialNodeTest.php @@ -13,5 +13,7 @@ public function setUp(): void { $this->endpoint = '/content_node/material_nodes'; $this->defaultEntity = static::getFixture('materialNode1'); + $this->campPrototypeEntity = static::getFixture('materialNodeCampPrototype'); + $this->sharedCampEntity = static::getFixture('materialNodeCampShared'); } } diff --git a/api/tests/Api/ContentNodes/MultiSelect/DeleteMultiSelectTest.php b/api/tests/Api/ContentNodes/MultiSelect/DeleteMultiSelectTest.php index 16f0254fff..ccdec1b75a 100644 --- a/api/tests/Api/ContentNodes/MultiSelect/DeleteMultiSelectTest.php +++ b/api/tests/Api/ContentNodes/MultiSelect/DeleteMultiSelectTest.php @@ -13,5 +13,7 @@ public function setUp(): void { $this->endpoint = '/content_node/multi_selects'; $this->defaultEntity = static::getFixture('multiSelect1'); + $this->campPrototypeEntity = static::getFixture('multiSelectCampPrototype'); + $this->sharedCampEntity = static::getFixture('multiSelectCampShared'); } } diff --git a/api/tests/Api/ContentNodes/MultiSelect/ListMultiSelectTest.php b/api/tests/Api/ContentNodes/MultiSelect/ListMultiSelectTest.php index 24a10ec6bc..4a3c508447 100644 --- a/api/tests/Api/ContentNodes/MultiSelect/ListMultiSelectTest.php +++ b/api/tests/Api/ContentNodes/MultiSelect/ListMultiSelectTest.php @@ -21,5 +21,10 @@ public function setUp(): void { $this->contentNodesCampUnrelated = [ $this->getIriFor('multiSelectCampUnrelated'), ]; + + $this->contentNodesPublicCamps = [ + $this->getIriFor('multiSelectCampPrototype'), + $this->getIriFor('multiSelectCampShared'), + ]; } } diff --git a/api/tests/Api/ContentNodes/MultiSelect/ReadMultiSelectTest.php b/api/tests/Api/ContentNodes/MultiSelect/ReadMultiSelectTest.php index 9e90d1ae19..a0781fd9a4 100644 --- a/api/tests/Api/ContentNodes/MultiSelect/ReadMultiSelectTest.php +++ b/api/tests/Api/ContentNodes/MultiSelect/ReadMultiSelectTest.php @@ -14,6 +14,8 @@ public function setUp(): void { $this->endpoint = '/content_node/multi_selects'; $this->defaultEntity = static::getFixture('multiSelect1'); + $this->campPrototypeEntity = static::getFixture('multiSelectCampPrototype'); + $this->sharedCampEntity = static::getFixture('multiSelectCampShared'); } public function testGetMultiSelect() { diff --git a/api/tests/Api/ContentNodes/MultiSelect/UpdateMultiSelectTest.php b/api/tests/Api/ContentNodes/MultiSelect/UpdateMultiSelectTest.php index 9e9d46f756..87d89a8cf8 100644 --- a/api/tests/Api/ContentNodes/MultiSelect/UpdateMultiSelectTest.php +++ b/api/tests/Api/ContentNodes/MultiSelect/UpdateMultiSelectTest.php @@ -13,6 +13,8 @@ public function setUp(): void { $this->endpoint = '/content_node/multi_selects'; $this->defaultEntity = static::getFixture('multiSelect1'); + $this->campPrototypeEntity = static::getFixture('multiSelectCampPrototype'); + $this->sharedCampEntity = static::getFixture('multiSelectCampShared'); } public function testPatchMultiSelectAcceptsValidJson() { diff --git a/api/tests/Api/ContentNodes/ReadContentNodeTestCase.php b/api/tests/Api/ContentNodes/ReadContentNodeTestCase.php index b54532f1cd..3300307e44 100644 --- a/api/tests/Api/ContentNodes/ReadContentNodeTestCase.php +++ b/api/tests/Api/ContentNodes/ReadContentNodeTestCase.php @@ -2,6 +2,7 @@ namespace App\Tests\Api\ContentNodes; +use App\Entity\BaseEntity; use App\Entity\ContentNode; use App\Tests\Api\ECampApiTestCase; @@ -13,6 +14,9 @@ * @internal */ abstract class ReadContentNodeTestCase extends ECampApiTestCase { + protected BaseEntity $campPrototypeEntity; + protected BaseEntity $sharedCampEntity; + public function setUp(): void { parent::setUp(); } @@ -70,4 +74,84 @@ public function testGetIsAllowedForManager() { ], ]); } + + public function testGetInCampPrototypeIsAllowedForUnrelatedUser() { + $this->get($this->campPrototypeEntity); + $this->assertResponseStatusCodeSame(200); + + /** @var ContentNode $contentNode */ + $contentNode = $this->campPrototypeEntity; + + $this->assertJsonContains([ + 'id' => $contentNode->getId(), + 'instanceName' => $contentNode->instanceName, + 'slot' => $contentNode->slot, + 'position' => $contentNode->position, + 'contentTypeName' => $contentNode->getContentTypeName(), + + '_links' => [ + 'parent' => ['href' => $this->getIriFor($contentNode->parent)], + ], + ]); + } + + public function testGetInSharedCampIsAllowedForUnrelatedUser() { + $this->get($this->sharedCampEntity); + $this->assertResponseStatusCodeSame(200); + + /** @var ContentNode $contentNode */ + $contentNode = $this->sharedCampEntity; + + $this->assertJsonContains([ + 'id' => $contentNode->getId(), + 'instanceName' => $contentNode->instanceName, + 'slot' => $contentNode->slot, + 'position' => $contentNode->position, + 'contentTypeName' => $contentNode->getContentTypeName(), + + '_links' => [ + 'parent' => ['href' => $this->getIriFor($contentNode->parent)], + ], + ]); + } + + public function testGetInSharedCampIsAllowedForInactiveUser() { + $this->get($this->sharedCampEntity, user: static::$fixtures['user5inactive']); + $this->assertResponseStatusCodeSame(200); + + /** @var ContentNode $contentNode */ + $contentNode = $this->sharedCampEntity; + + $this->assertJsonContains([ + 'id' => $contentNode->getId(), + 'instanceName' => $contentNode->instanceName, + 'slot' => $contentNode->slot, + 'position' => $contentNode->position, + 'contentTypeName' => $contentNode->getContentTypeName(), + + '_links' => [ + 'parent' => ['href' => $this->getIriFor($contentNode->parent)], + ], + ]); + } + + public function testGetInSharedCampIsAllowedForInvitedUser() { + $this->get($this->sharedCampEntity, user: static::$fixtures['user6invited']); + $this->assertResponseStatusCodeSame(200); + + /** @var ContentNode $contentNode */ + $contentNode = $this->sharedCampEntity; + + $this->assertJsonContains([ + 'id' => $contentNode->getId(), + 'instanceName' => $contentNode->instanceName, + 'slot' => $contentNode->slot, + 'position' => $contentNode->position, + 'contentTypeName' => $contentNode->getContentTypeName(), + + '_links' => [ + 'parent' => ['href' => $this->getIriFor($contentNode->parent)], + ], + ]); + } } diff --git a/api/tests/Api/ContentNodes/ResponsiveLayout/DeleteResponsiveLayoutTest.php b/api/tests/Api/ContentNodes/ResponsiveLayout/DeleteResponsiveLayoutTest.php index 0fd342b68e..336e7b5b29 100644 --- a/api/tests/Api/ContentNodes/ResponsiveLayout/DeleteResponsiveLayoutTest.php +++ b/api/tests/Api/ContentNodes/ResponsiveLayout/DeleteResponsiveLayoutTest.php @@ -13,5 +13,7 @@ public function setUp(): void { $this->endpoint = '/content_node/responsive_layouts'; $this->defaultEntity = static::getFixture('responsiveLayout1'); + $this->campPrototypeEntity = static::getFixture('responsiveLayoutCampPrototype'); + $this->sharedCampEntity = static::getFixture('responsiveLayoutCampShared'); } } diff --git a/api/tests/Api/ContentNodes/ResponsiveLayout/ListResponsiveLayoutTest.php b/api/tests/Api/ContentNodes/ResponsiveLayout/ListResponsiveLayoutTest.php index 57a4e5dc66..2c6e26c435 100644 --- a/api/tests/Api/ContentNodes/ResponsiveLayout/ListResponsiveLayoutTest.php +++ b/api/tests/Api/ContentNodes/ResponsiveLayout/ListResponsiveLayoutTest.php @@ -20,5 +20,10 @@ public function setUp(): void { $this->contentNodesCampUnrelated = [ $this->getIriFor('responsiveLayoutCampUnrelated'), ]; + + $this->contentNodesPublicCamps = [ + $this->getIriFor('responsiveLayoutCampPrototype'), + $this->getIriFor('responsiveLayoutCampShared'), + ]; } } diff --git a/api/tests/Api/ContentNodes/ResponsiveLayout/ReadResponsiveLayoutTest.php b/api/tests/Api/ContentNodes/ResponsiveLayout/ReadResponsiveLayoutTest.php index 03002df6ca..b5986404e3 100644 --- a/api/tests/Api/ContentNodes/ResponsiveLayout/ReadResponsiveLayoutTest.php +++ b/api/tests/Api/ContentNodes/ResponsiveLayout/ReadResponsiveLayoutTest.php @@ -14,6 +14,8 @@ public function setUp(): void { $this->endpoint = '/content_node/responsive_layouts'; $this->defaultEntity = static::getFixture('responsiveLayout1'); + $this->campPrototypeEntity = static::getFixture('responsiveLayoutCampPrototype'); + $this->sharedCampEntity = static::getFixture('responsiveLayoutCampShared'); } public function testGetResponsiveLayout() { diff --git a/api/tests/Api/ContentNodes/ResponsiveLayout/UpdateResponsiveLayoutTest.php b/api/tests/Api/ContentNodes/ResponsiveLayout/UpdateResponsiveLayoutTest.php index 925956c26e..5ea57fa5b6 100644 --- a/api/tests/Api/ContentNodes/ResponsiveLayout/UpdateResponsiveLayoutTest.php +++ b/api/tests/Api/ContentNodes/ResponsiveLayout/UpdateResponsiveLayoutTest.php @@ -13,6 +13,8 @@ public function setUp(): void { $this->endpoint = '/content_node/responsive_layouts'; $this->defaultEntity = static::getFixture('responsiveLayout1'); + $this->campPrototypeEntity = static::getFixture('responsiveLayoutCampPrototype'); + $this->sharedCampEntity = static::getFixture('responsiveLayoutCampShared'); } public function testPatchResponsiveLayoutAcceptsValidJson() { diff --git a/api/tests/Api/ContentNodes/RootColumnLayout/CreateRootColumnLayoutTest.php b/api/tests/Api/ContentNodes/RootColumnLayout/CreateRootColumnLayoutTest.php index dd74aa8e86..213500543d 100644 --- a/api/tests/Api/ContentNodes/RootColumnLayout/CreateRootColumnLayoutTest.php +++ b/api/tests/Api/ContentNodes/RootColumnLayout/CreateRootColumnLayoutTest.php @@ -2,21 +2,28 @@ namespace App\Tests\Api\ContentNodes\RootColumnLayout; +use App\Entity\ContentNode; use App\Entity\ContentNode\ColumnLayout; -use App\Tests\Api\ContentNodes\CreateContentNodeTestCase; +use App\Entity\ContentType; +use App\Tests\Api\ECampApiTestCase; /** * Tests for creating a root column layout. * * @internal */ -class CreateRootColumnLayoutTest extends CreateContentNodeTestCase { +class CreateRootColumnLayoutTest extends ECampApiTestCase { + protected ContentType $defaultContentType; + + protected ContentNode $defaultParent; + public function setUp(): void { parent::setUp(); $this->endpoint = '/content_node/column_layouts'; $this->entityClass = ColumnLayout::class; $this->defaultContentType = static::getFixture('contentTypeColumnLayout'); + $this->defaultParent = static::getFixture('columnLayout1'); } public function testCreateColumnLayoutSetsRootToParentsRoot() { @@ -77,6 +84,9 @@ public function getExampleWritePayload($attributes = [], $except = []) { return parent::getExampleWritePayload( array_merge( [ + 'parent' => $this->getIriFor($this->defaultParent), + 'contentType' => $this->getIriFor($this->defaultContentType), + 'position' => 10, 'data' => [ 'columns' => [['slot' => '1', 'width' => 12]], ], diff --git a/api/tests/Api/ContentNodes/RootColumnLayout/DeleteRootColumnLayoutTest.php b/api/tests/Api/ContentNodes/RootColumnLayout/DeleteRootColumnLayoutTest.php index 8aae195c86..1451099d61 100644 --- a/api/tests/Api/ContentNodes/RootColumnLayout/DeleteRootColumnLayoutTest.php +++ b/api/tests/Api/ContentNodes/RootColumnLayout/DeleteRootColumnLayoutTest.php @@ -2,19 +2,19 @@ namespace App\Tests\Api\ContentNodes\RootColumnLayout; -use App\Tests\Api\ContentNodes\DeleteContentNodeTestCase; +use App\Tests\Api\ECampApiTestCase; /** * Tests for deleting a root column layout. * * @internal */ -class DeleteRootColumnLayoutTest extends DeleteContentNodeTestCase { +class DeleteRootColumnLayoutTest extends ECampApiTestCase { public function setUp(): void { parent::setUp(); $this->endpoint = '/content_node/column_layouts'; - $this->defaultEntity = static::getFixture('columnLayoutChild1'); + $this->defaultEntity = static::getFixture('columnLayout1'); } public function testDeleteColumnLayoutIsNotAllowedWhenColumnLayoutIsRoot() { diff --git a/api/tests/Api/ContentNodes/RootColumnLayout/UpdateRootColumnLayoutTest.php b/api/tests/Api/ContentNodes/RootColumnLayout/UpdateRootColumnLayoutTest.php index 80f18a63bd..5a232b45f6 100644 --- a/api/tests/Api/ContentNodes/RootColumnLayout/UpdateRootColumnLayoutTest.php +++ b/api/tests/Api/ContentNodes/RootColumnLayout/UpdateRootColumnLayoutTest.php @@ -2,14 +2,14 @@ namespace App\Tests\Api\ContentNodes\RootColumnLayout; -use App\Tests\Api\ContentNodes\UpdateContentNodeTestCase; +use App\Tests\Api\ECampApiTestCase; /** * Testing functionality of ContentNode with the root. * * @internal */ -class UpdateRootColumnLayoutTest extends UpdateContentNodeTestCase { +class UpdateRootColumnLayoutTest extends ECampApiTestCase { public function setUp(): void { parent::setUp(); diff --git a/api/tests/Api/ContentNodes/SingleText/DeleteSingleTextTest.php b/api/tests/Api/ContentNodes/SingleText/DeleteSingleTextTest.php index d9b1b37141..bbbd5558b1 100644 --- a/api/tests/Api/ContentNodes/SingleText/DeleteSingleTextTest.php +++ b/api/tests/Api/ContentNodes/SingleText/DeleteSingleTextTest.php @@ -13,5 +13,7 @@ public function setUp(): void { $this->endpoint = '/content_node/single_texts'; $this->defaultEntity = static::getFixture('singleText1'); + $this->campPrototypeEntity = static::getFixture('singleTextCampPrototype'); + $this->sharedCampEntity = static::getFixture('singleTextCampShared'); } } diff --git a/api/tests/Api/ContentNodes/SingleText/ListSingleTextTest.php b/api/tests/Api/ContentNodes/SingleText/ListSingleTextTest.php index 2a8ab5b05f..695fa657b7 100644 --- a/api/tests/Api/ContentNodes/SingleText/ListSingleTextTest.php +++ b/api/tests/Api/ContentNodes/SingleText/ListSingleTextTest.php @@ -22,5 +22,10 @@ public function setUp(): void { $this->contentNodesCampUnrelated = [ $this->getIriFor('singleTextCampUnrelated'), ]; + + $this->contentNodesPublicCamps = [ + $this->getIriFor('singleTextCampPrototype'), + $this->getIriFor('singleTextCampShared'), + ]; } } diff --git a/api/tests/Api/ContentNodes/SingleText/ReadSingleTextTest.php b/api/tests/Api/ContentNodes/SingleText/ReadSingleTextTest.php index bcbc307245..30386e075d 100644 --- a/api/tests/Api/ContentNodes/SingleText/ReadSingleTextTest.php +++ b/api/tests/Api/ContentNodes/SingleText/ReadSingleTextTest.php @@ -14,6 +14,8 @@ public function setUp(): void { $this->endpoint = '/content_node/single_texts'; $this->defaultEntity = static::getFixture('singleText1'); + $this->campPrototypeEntity = static::getFixture('singleTextCampPrototype'); + $this->sharedCampEntity = static::getFixture('singleTextCampShared'); } public function testGetSingleText() { diff --git a/api/tests/Api/ContentNodes/SingleText/UpdateSingleTextTest.php b/api/tests/Api/ContentNodes/SingleText/UpdateSingleTextTest.php index 0140028f23..0696287a4e 100644 --- a/api/tests/Api/ContentNodes/SingleText/UpdateSingleTextTest.php +++ b/api/tests/Api/ContentNodes/SingleText/UpdateSingleTextTest.php @@ -13,6 +13,8 @@ public function setUp(): void { $this->endpoint = '/content_node/single_texts'; $this->defaultEntity = static::getFixture('singleText1'); + $this->campPrototypeEntity = static::getFixture('singleTextCampPrototype'); + $this->sharedCampEntity = static::getFixture('singleTextCampShared'); } public function testPatchText() { diff --git a/api/tests/Api/ContentNodes/Storyboard/DeleteStoryboardTest.php b/api/tests/Api/ContentNodes/Storyboard/DeleteStoryboardTest.php index 225f19f23d..a5a7d80f45 100644 --- a/api/tests/Api/ContentNodes/Storyboard/DeleteStoryboardTest.php +++ b/api/tests/Api/ContentNodes/Storyboard/DeleteStoryboardTest.php @@ -13,5 +13,7 @@ public function setUp(): void { $this->endpoint = '/content_node/storyboards'; $this->defaultEntity = static::getFixture('storyboard1'); + $this->campPrototypeEntity = static::getFixture('storyboardCampPrototype'); + $this->sharedCampEntity = static::getFixture('storyboardCampShared'); } } diff --git a/api/tests/Api/ContentNodes/Storyboard/ListStoryboardTest.php b/api/tests/Api/ContentNodes/Storyboard/ListStoryboardTest.php index 1fd061074a..23cfa4d512 100644 --- a/api/tests/Api/ContentNodes/Storyboard/ListStoryboardTest.php +++ b/api/tests/Api/ContentNodes/Storyboard/ListStoryboardTest.php @@ -21,5 +21,10 @@ public function setUp(): void { $this->contentNodesCampUnrelated = [ $this->getIriFor('storyboardCampUnrelated'), ]; + + $this->contentNodesPublicCamps = [ + $this->getIriFor('storyboardCampPrototype'), + $this->getIriFor('storyboardCampShared'), + ]; } } diff --git a/api/tests/Api/ContentNodes/Storyboard/ReadStoryboardTest.php b/api/tests/Api/ContentNodes/Storyboard/ReadStoryboardTest.php index 017edcd96d..0d0accfa39 100644 --- a/api/tests/Api/ContentNodes/Storyboard/ReadStoryboardTest.php +++ b/api/tests/Api/ContentNodes/Storyboard/ReadStoryboardTest.php @@ -14,6 +14,8 @@ public function setUp(): void { $this->endpoint = '/content_node/storyboards'; $this->defaultEntity = static::getFixture('storyboard1'); + $this->campPrototypeEntity = static::getFixture('storyboardCampPrototype'); + $this->sharedCampEntity = static::getFixture('storyboardCampShared'); } public function testGetStoryboard() { diff --git a/api/tests/Api/ContentNodes/Storyboard/UpdateStoryboardTest.php b/api/tests/Api/ContentNodes/Storyboard/UpdateStoryboardTest.php index ddb705449b..4211916a80 100644 --- a/api/tests/Api/ContentNodes/Storyboard/UpdateStoryboardTest.php +++ b/api/tests/Api/ContentNodes/Storyboard/UpdateStoryboardTest.php @@ -13,6 +13,8 @@ public function setUp(): void { $this->endpoint = '/content_node/storyboards'; $this->defaultEntity = static::getFixture('storyboard1'); + $this->campPrototypeEntity = static::getFixture('storyboardCampPrototype'); + $this->sharedCampEntity = static::getFixture('storyboardCampShared'); } public function testPatchStoryboardAddSection() { diff --git a/api/tests/Api/ContentNodes/UpdateContentNodeTestCase.php b/api/tests/Api/ContentNodes/UpdateContentNodeTestCase.php index 94b8514aa6..f84ec05f75 100644 --- a/api/tests/Api/ContentNodes/UpdateContentNodeTestCase.php +++ b/api/tests/Api/ContentNodes/UpdateContentNodeTestCase.php @@ -2,6 +2,7 @@ namespace App\Tests\Api\ContentNodes; +use App\Entity\BaseEntity; use App\Entity\ContentNode; use App\Entity\ContentNode\ColumnLayout; use App\Entity\ContentNode\ResponsiveLayout; @@ -16,6 +17,9 @@ * @internal */ abstract class UpdateContentNodeTestCase extends ECampApiTestCase { + protected BaseEntity $campPrototypeEntity; + protected BaseEntity $sharedCampEntity; + public function setUp(): void { parent::setUp(); } @@ -59,6 +63,26 @@ public function testPatchIsAllowedForManager() { $this->assertResponseStatusCodeSame(200); } + public function testPatchInCampPrototypeIsDeniedForUnrelatedUser() { + $this->patch($this->campPrototypeEntity); + $this->assertResponseStatusCodeSame(403); + } + + public function testPatchInSharedCampIsDeniedForUnrelatedUser() { + $this->patch($this->sharedCampEntity); + $this->assertResponseStatusCodeSame(403); + } + + public function testPatchInSharedCampIsDeniedForInactiveUser() { + $this->patch($this->sharedCampEntity, user: static::$fixtures['user5inactive']); + $this->assertResponseStatusCodeSame(403); + } + + public function testPatchInSharedCampIsDeniedForInvitedUser() { + $this->patch($this->sharedCampEntity, user: static::$fixtures['user6invited']); + $this->assertResponseStatusCodeSame(403); + } + #[DataProvider('getContentNodesWhichCannotHaveChildren')] public function testPatchRejectsParentsWhichDontSupportChildren(string $idOfParentFixture) { $parentIri = static::getIriFor($idOfParentFixture); @@ -69,7 +93,7 @@ public function testPatchRejectsParentsWhichDontSupportChildren(string $idOfPare 'violations' => [ 0 => [ 'propertyPath' => 'parent', - 'message' => 'This parent does not support children, only content_nodes of type column_layout support children.', + 'message' => 'This parent does not support children, only content_nodes of type column_layout or responsive_layout support children.', ], ], ]); diff --git a/api/tests/Api/DayResponsibles/CreateDayResponsibleTest.php b/api/tests/Api/DayResponsibles/CreateDayResponsibleTest.php index 7ba50c18ce..28d4a4ceb4 100644 --- a/api/tests/Api/DayResponsibles/CreateDayResponsibleTest.php +++ b/api/tests/Api/DayResponsibles/CreateDayResponsibleTest.php @@ -95,6 +95,45 @@ public function testCreateDayResponsibleInCampPrototypeIsDeniedForUnrelatedUser( ]); } + public function testCreateDayResponsibleInSharedCampIsDeniedForUnrelatedUser() { + static::createClientWithCredentials()->request('POST', '/day_responsibles', ['json' => $this->getExampleWritePayload([ + 'day' => $this->getIriFor('day1period1campShared'), + 'campCollaboration' => $this->getIriFor('campCollaboration1campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testCreateDayResponsibleInSharedCampIsDeniedForInactiveUser() { + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()])->request('POST', '/day_responsibles', ['json' => $this->getExampleWritePayload([ + 'day' => $this->getIriFor('day1period1campShared'), + 'campCollaboration' => $this->getIriFor('campCollaboration1campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testCreateDayResponsibleInSharedCampIsDeniedForInvitedUser() { + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()])->request('POST', '/day_responsibles', ['json' => $this->getExampleWritePayload([ + 'day' => $this->getIriFor('day1period1campShared'), + 'campCollaboration' => $this->getIriFor('campCollaboration1campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + public function testCreateDayResponsibleValidatesMissingDay() { static::createClientWithCredentials()->request('POST', '/day_responsibles', ['json' => $this->getExampleWritePayload([], ['day'])]); diff --git a/api/tests/Api/DayResponsibles/DeleteDayResponsibleTest.php b/api/tests/Api/DayResponsibles/DeleteDayResponsibleTest.php index 2b1e3bcf3b..d8013976ef 100644 --- a/api/tests/Api/DayResponsibles/DeleteDayResponsibleTest.php +++ b/api/tests/Api/DayResponsibles/DeleteDayResponsibleTest.php @@ -84,4 +84,41 @@ public function testDeleteDayResponsibleInCampPrototypeIsDeniedForUnrelatedUser( 'detail' => 'Access Denied.', ]); } + + public function testDeleteDayResponsibleInSharedCampIsDeniedForUnrelatedUser() { + $dayResponsible = static::getFixture('dayResponsible1day1period1campShared'); + static::createClientWithCredentials()->request('DELETE', '/day_responsibles/'.$dayResponsible->getId()); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testDeleteDayResponsibleInSharedCampIsDeniedForInactiveUser() { + $dayResponsible = static::getFixture('dayResponsible1day1period1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('DELETE', '/day_responsibles/'.$dayResponsible->getId()) + ; + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testDeleteDayResponsibleInSharedCampIsDeniedForInvitedUser() { + $dayResponsible = static::getFixture('dayResponsible1day1period1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('DELETE', '/day_responsibles/'.$dayResponsible->getId()) + ; + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } } diff --git a/api/tests/Api/DayResponsibles/ListDayResponsiblesTest.php b/api/tests/Api/DayResponsibles/ListDayResponsiblesTest.php index 1902d9fce9..107a28aaad 100644 --- a/api/tests/Api/DayResponsibles/ListDayResponsiblesTest.php +++ b/api/tests/Api/DayResponsibles/ListDayResponsiblesTest.php @@ -23,7 +23,7 @@ public function testListDayResponsiblesIsAllowedForLoggedInUserButFiltered() { $response = static::createClientWithCredentials()->request('GET', '/day_responsibles'); $this->assertJsonContains([ - 'totalItems' => 7, + 'totalItems' => 8, '_links' => [ 'items' => [], ], @@ -39,6 +39,7 @@ public function testListDayResponsiblesIsAllowedForLoggedInUserButFiltered() { ['href' => $this->getIriFor('dayResponsible1day3period2')], ['href' => $this->getIriFor('dayResponsible2day3period2')], ['href' => $this->getIriFor('dayResponsible1day1period1campPrototype')], + ['href' => $this->getIriFor('dayResponsible1day1period1campShared')], ], $response->toArray()['_links']['items']); } @@ -102,6 +103,64 @@ public function testListDayResponsiblesFilteredByDayInCampPrototypeIsAllowedForU ], $response->toArray()['_links']['items']); } + public function testListDayResponsiblesFilteredByDayInSharedCampIsAllowedForUnrelatedUser() { + $day = static::getFixture('day1period1campShared'); + $response = static::createClientWithCredentials()->request('GET', '/day_responsibles?day=%2Fdays%2F'.$day->getId()); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 1, + '_links' => [ + 'items' => [], + ], + '_embedded' => [ + 'items' => [], + ], + ]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('dayResponsible1day1period1campShared')], + ], $response->toArray()['_links']['items']); + } + + public function testListDayResponsiblesFilteredByDayInSharedCampIsAllowedForInactiveUser() { + $day = static::getFixture('day1period1campShared'); + $response = static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('GET', '/day_responsibles?day=%2Fdays%2F'.$day->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 1, + '_links' => [ + 'items' => [], + ], + '_embedded' => [ + 'items' => [], + ], + ]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('dayResponsible1day1period1campShared')], + ], $response->toArray()['_links']['items']); + } + + public function testListDayResponsiblesFilteredByDayInSharedCampIsAllowedForInvitedUser() { + $day = static::getFixture('day1period1campShared'); + $response = static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('GET', '/day_responsibles?day=%2Fdays%2F'.$day->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 1, + '_links' => [ + 'items' => [], + ], + '_embedded' => [ + 'items' => [], + ], + ]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('dayResponsible1day1period1campShared')], + ], $response->toArray()['_links']['items']); + } + public function testListDayResponsiblesAsDaySubresourceIsAllowedForCollaborator() { $day = static::getFixture('day1period1'); $response = static::createClientWithCredentials()->request('GET', '/days/'.$day->getId().'/day_responsibles'); diff --git a/api/tests/Api/DayResponsibles/ReadDayResponsibleTest.php b/api/tests/Api/DayResponsibles/ReadDayResponsibleTest.php index c608726546..4c64dc57b4 100644 --- a/api/tests/Api/DayResponsibles/ReadDayResponsibleTest.php +++ b/api/tests/Api/DayResponsibles/ReadDayResponsibleTest.php @@ -105,4 +105,50 @@ public function testGetSingleDayResponsibleInCampPrototypeIsAllowedForUnrelatedU ], ]); } + + public function testGetSingleDayResponsibleInSharedCampIsAllowedForUnrelatedUser() { + /** @var DayResponsible $dayResponsible */ + $dayResponsible = static::getFixture('dayResponsible1day1period1campShared'); + static::createClientWithCredentials()->request('GET', '/day_responsibles/'.$dayResponsible->getId()); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $dayResponsible->getId(), + '_links' => [ + 'day' => ['href' => $this->getIriFor('day1period1campShared')], + 'campCollaboration' => ['href' => $this->getIriFor('campCollaboration1campShared')], + ], + ]); + } + + public function testGetSingleDayResponsibleInSharedCampIsAllowedForInactiveUser() { + /** @var DayResponsible $dayResponsible */ + $dayResponsible = static::getFixture('dayResponsible1day1period1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('GET', '/day_responsibles/'.$dayResponsible->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $dayResponsible->getId(), + '_links' => [ + 'day' => ['href' => $this->getIriFor('day1period1campShared')], + 'campCollaboration' => ['href' => $this->getIriFor('campCollaboration1campShared')], + ], + ]); + } + + public function testGetSingleDayResponsibleInSharedCampIsAllowedForInvitedUser() { + /** @var DayResponsible $dayResponsible */ + $dayResponsible = static::getFixture('dayResponsible1day1period1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('GET', '/day_responsibles/'.$dayResponsible->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $dayResponsible->getId(), + '_links' => [ + 'day' => ['href' => $this->getIriFor('day1period1campShared')], + 'campCollaboration' => ['href' => $this->getIriFor('campCollaboration1campShared')], + ], + ]); + } } diff --git a/api/tests/Api/Days/ListDaysTest.php b/api/tests/Api/Days/ListDaysTest.php index 32fbe83558..ea0610dfa4 100644 --- a/api/tests/Api/Days/ListDaysTest.php +++ b/api/tests/Api/Days/ListDaysTest.php @@ -24,7 +24,7 @@ public function testListDaysIsAllowedForLoggedInUserButFiltered() { $response = static::createClientWithCredentials()->request('GET', '/days'); $this->assertResponseStatusCodeSame(200); $this->assertJsonContains([ - 'totalItems' => 9, + 'totalItems' => 12, '_links' => [ 'items' => [], ], @@ -42,6 +42,9 @@ public function testListDaysIsAllowedForLoggedInUserButFiltered() { ['href' => $this->getIriFor('day1period1camp2')], ['href' => $this->getIriFor('day2period1camp2')], ['href' => $this->getIriFor('day1period1campPrototype')], + ['href' => $this->getIriFor('day1period1campShared')], + ['href' => $this->getIriFor('day2period1campShared')], + ['href' => $this->getIriFor('day3period1campShared')], ], $response->toArray()['_links']['items']); } @@ -102,10 +105,13 @@ public function testListDaysOrdersByDate() { ['href' => $this->getIriFor('day1period1')], ['href' => $this->getIriFor('day2period1')], ['href' => $this->getIriFor('day3period1')], + ['href' => $this->getIriFor('day1period1campShared')], + ['href' => $this->getIriFor('day2period1campShared')], + ['href' => $this->getIriFor('day3period1campShared')], ], $response->toArray()['_links']['items']); } - public function testListDaysFilteredByPeriodInCampPrototypeIsAllowedForCollaborator() { + public function testListDaysFilteredByPeriodInCampPrototypeIsAllowedForUnrelatedUser() { $period = static::getFixture('period1campPrototype'); $response = static::createClientWithCredentials()->request('GET', '/days?period=%2Fperiods%2F'.$period->getId()); $this->assertResponseStatusCodeSame(200); @@ -123,6 +129,70 @@ public function testListDaysFilteredByPeriodInCampPrototypeIsAllowedForCollabora ], $response->toArray()['_links']['items']); } + public function testListDaysFilteredByPeriodInSharedCampIsAllowedForUnrelatedUser() { + $period = static::getFixture('period1campShared'); + $response = static::createClientWithCredentials()->request('GET', '/days?period=%2Fperiods%2F'.$period->getId()); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 3, + '_links' => [ + 'items' => [], + ], + '_embedded' => [ + 'items' => [], + ], + ]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('day1period1campShared')], + ['href' => $this->getIriFor('day2period1campShared')], + ['href' => $this->getIriFor('day3period1campShared')], + ], $response->toArray()['_links']['items']); + } + + public function testListDaysFilteredByPeriodInSharedCampIsAllowedForInactiveUser() { + $period = static::getFixture('period1campShared'); + $response = static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('GET', '/days?period=%2Fperiods%2F'.$period->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 3, + '_links' => [ + 'items' => [], + ], + '_embedded' => [ + 'items' => [], + ], + ]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('day1period1campShared')], + ['href' => $this->getIriFor('day2period1campShared')], + ['href' => $this->getIriFor('day3period1campShared')], + ], $response->toArray()['_links']['items']); + } + + public function testListDaysFilteredByPeriodInSharedCampIsAllowedForInvitedUser() { + $period = static::getFixture('period1campShared'); + $response = static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('GET', '/days?period=%2Fperiods%2F'.$period->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 3, + '_links' => [ + 'items' => [], + ], + '_embedded' => [ + 'items' => [], + ], + ]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('day1period1campShared')], + ['href' => $this->getIriFor('day2period1campShared')], + ['href' => $this->getIriFor('day3period1campShared')], + ], $response->toArray()['_links']['items']); + } + public function testListDaysAsPeriodSubresourceIsAllowedForCollaborator() { $period = static::getFixture('period1'); $response = static::createClientWithCredentials()->request('GET', '/periods/'.$period->getId().'/days'); diff --git a/api/tests/Api/Days/ReadDayTest.php b/api/tests/Api/Days/ReadDayTest.php index 306334cdbb..dad0250a88 100644 --- a/api/tests/Api/Days/ReadDayTest.php +++ b/api/tests/Api/Days/ReadDayTest.php @@ -126,6 +126,67 @@ public function testGetSingleDayFromCampPrototypeIsAllowedForUnrelatedUser() { ]); } + public function testGetSingleDayFromSharedCampIsAllowedForUnrelatedUser() { + /** @var Day $day */ + $day = static::getFixture('day1period1campShared'); + $start = $day->getStart()->format(\DateTime::W3C); + $end = $day->getEnd()->format(\DateTime::W3C); + static::createClientWithCredentials()->request('GET', '/days/'.$day->getId()); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $day->getId(), + 'dayOffset' => $day->dayOffset, + 'number' => $day->getDayNumber(), + '_links' => [ + 'period' => ['href' => $this->getIriFor('period1campShared')], + 'scheduleEntries' => ['href' => '/schedule_entries?period=%2Fperiods%2F'.$day->period->getId().'&start%5Bstrictly_before%5D='.urlencode($end).'&end%5Bafter%5D='.urlencode($start)], + 'dayResponsibles' => ['href' => '/days/'.$day->getId().'/day_responsibles'], + ], + ]); + } + + public function testGetSingleDayFromSharedCampIsAllowedForInactiveUser() { + /** @var Day $day */ + $day = static::getFixture('day1period1campShared'); + $start = $day->getStart()->format(\DateTime::W3C); + $end = $day->getEnd()->format(\DateTime::W3C); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('GET', '/days/'.$day->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $day->getId(), + 'dayOffset' => $day->dayOffset, + 'number' => $day->getDayNumber(), + '_links' => [ + 'period' => ['href' => $this->getIriFor('period1campShared')], + 'scheduleEntries' => ['href' => '/schedule_entries?period=%2Fperiods%2F'.$day->period->getId().'&start%5Bstrictly_before%5D='.urlencode($end).'&end%5Bafter%5D='.urlencode($start)], + 'dayResponsibles' => ['href' => '/days/'.$day->getId().'/day_responsibles'], + ], + ]); + } + + public function testGetSingleDayFromSharedCampIsAllowedForInvitedUser() { + /** @var Day $day */ + $day = static::getFixture('day1period1campShared'); + $start = $day->getStart()->format(\DateTime::W3C); + $end = $day->getEnd()->format(\DateTime::W3C); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('GET', '/days/'.$day->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $day->getId(), + 'dayOffset' => $day->dayOffset, + 'number' => $day->getDayNumber(), + '_links' => [ + 'period' => ['href' => $this->getIriFor('period1campShared')], + 'scheduleEntries' => ['href' => '/schedule_entries?period=%2Fperiods%2F'.$day->period->getId().'&start%5Bstrictly_before%5D='.urlencode($end).'&end%5Bafter%5D='.urlencode($start)], + 'dayResponsibles' => ['href' => '/days/'.$day->getId().'/day_responsibles'], + ], + ]); + } + public function testDatesFormatProperlyInTimezoneAheadOfUTC() { // given date_default_timezone_set('Asia/Singapore'); diff --git a/api/tests/Api/MaterialItems/CreateMaterialItemTest.php b/api/tests/Api/MaterialItems/CreateMaterialItemTest.php index bbfc05ec9d..259858f516 100644 --- a/api/tests/Api/MaterialItems/CreateMaterialItemTest.php +++ b/api/tests/Api/MaterialItems/CreateMaterialItemTest.php @@ -107,6 +107,45 @@ public function testCreateMaterialItemInCampPrototypeIsDeniedForUnrelatedUser() ]); } + public function testCreateMaterialItemInSharedCampIsDeniedForUnrelatedUser() { + static::createClientWithCredentials()->request('POST', '/material_items', ['json' => $this->getExampleWritePayload([ + 'materialList' => $this->getIriFor('materialList1campShared'), + 'period' => $this->getIriFor('period1campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testCreateMaterialItemInSharedCampIsDeniedForInactiveUser() { + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()])->request('POST', '/material_items', ['json' => $this->getExampleWritePayload([ + 'materialList' => $this->getIriFor('materialList1campShared'), + 'period' => $this->getIriFor('period1campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testCreateMaterialItemInSharedCampIsDeniedForInvitedUser() { + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()])->request('POST', '/material_items', ['json' => $this->getExampleWritePayload([ + 'materialList' => $this->getIriFor('materialList1campShared'), + 'period' => $this->getIriFor('period1campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + public function testCreateMaterialItemWithMaterialNodeInsteadOfPeriodIsPossible() { static::createClientWithCredentials()->request('POST', '/material_items', ['json' => $this->getExampleWritePayload([ 'materialNode' => $this->getIriFor('materialNode1'), diff --git a/api/tests/Api/MaterialItems/DeleteMaterialItemTest.php b/api/tests/Api/MaterialItems/DeleteMaterialItemTest.php index 549d9c8ce0..fcee678dd9 100644 --- a/api/tests/Api/MaterialItems/DeleteMaterialItemTest.php +++ b/api/tests/Api/MaterialItems/DeleteMaterialItemTest.php @@ -84,4 +84,41 @@ public function testDeleteMaterialItemFromCampPrototypeIsDeniedForUnrelatedUser( 'detail' => 'Access Denied.', ]); } + + public function testDeleteMaterialItemFromSharedCampIsDeniedForUnrelatedUser() { + $materialItem = static::getFixture('materialItem1period1campShared'); + static::createClientWithCredentials()->request('DELETE', '/material_items/'.$materialItem->getId()); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testDeleteMaterialItemFromSharedCampIsDeniedForInactiveUser() { + $materialItem = static::getFixture('materialItem1period1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('DELETE', '/material_items/'.$materialItem->getId()) + ; + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testDeleteMaterialItemFromSharedCampIsDeniedForInvitedUser() { + $materialItem = static::getFixture('materialItem1period1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('DELETE', '/material_items/'.$materialItem->getId()) + ; + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } } diff --git a/api/tests/Api/MaterialItems/ListMaterialItemsTest.php b/api/tests/Api/MaterialItems/ListMaterialItemsTest.php index 85cb43d60b..9e86bb326b 100644 --- a/api/tests/Api/MaterialItems/ListMaterialItemsTest.php +++ b/api/tests/Api/MaterialItems/ListMaterialItemsTest.php @@ -24,7 +24,7 @@ public function testListMaterialItemsIsAllowedForLoggedInUserButFiltered() { $response = static::createClientWithCredentials()->request('GET', '/material_items'); $this->assertResponseStatusCodeSame(200); $this->assertJsonContains([ - 'totalItems' => 4, + 'totalItems' => 5, '_links' => [ 'items' => [], ], @@ -37,6 +37,7 @@ public function testListMaterialItemsIsAllowedForLoggedInUserButFiltered() { ['href' => $this->getIriFor('materialItem1period1')], ['href' => $this->getIriFor('materialItem1period1camp2')], ['href' => $this->getIriFor('materialItem1period1campPrototype')], + ['href' => $this->getIriFor('materialItem1period1campShared')], ], $response->toArray()['_links']['items']); } @@ -100,6 +101,61 @@ public function testListMaterialItemsFilteredByMaterialListInCampPrototypeIsAllo ], $response->toArray()['_links']['items']); } + public function testListMaterialItemsFilteredByMaterialListInSharedCampIsAllowedForUnrelatedUser() { + $materialList = static::getFixture('materialList1campShared'); + $response = static::createClientWithCredentials()->request('GET', '/material_items?materialList=%2Fmaterial_lists%2F'.$materialList->getId()); + $this->assertJsonContains([ + 'totalItems' => 1, + '_links' => [ + 'items' => [], + ], + '_embedded' => [ + 'items' => [], + ], + ]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('materialItem1period1campShared')], + ], $response->toArray()['_links']['items']); + } + + public function testListMaterialItemsFilteredByMaterialListInSharedCampIsAllowedForInactiveUser() { + $materialList = static::getFixture('materialList1campShared'); + $response = static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('GET', '/material_items?materialList=%2Fmaterial_lists%2F'.$materialList->getId()) + ; + $this->assertJsonContains([ + 'totalItems' => 1, + '_links' => [ + 'items' => [], + ], + '_embedded' => [ + 'items' => [], + ], + ]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('materialItem1period1campShared')], + ], $response->toArray()['_links']['items']); + } + + public function testListMaterialItemsFilteredByMaterialListInSharedCampIsAllowedForInvitedUser() { + $materialList = static::getFixture('materialList1campShared'); + $response = static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('GET', '/material_items?materialList=%2Fmaterial_lists%2F'.$materialList->getId()) + ; + $this->assertJsonContains([ + 'totalItems' => 1, + '_links' => [ + 'items' => [], + ], + '_embedded' => [ + 'items' => [], + ], + ]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('materialItem1period1campShared')], + ], $response->toArray()['_links']['items']); + } + public function testListMaterialItemsFilteredByPeriodIsAllowedForCollaborator() { $period = static::getFixture('period1'); $response = static::createClientWithCredentials()->request('GET', '/material_items?period=%2Fperiods%2F'.$period->getId()); @@ -147,6 +203,20 @@ public function testListMaterialItemsFilteredByPeriodIsDeniedForInactiveCollabor ]); } + public function testListMaterialItemsFilteredByPeriodIsDeniedForInvitedCollaborator() { + $period = static::getFixture('period1'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('GET', '/material_items?period=%2Fperiods%2F'.$period->getId()) + ; + + $this->assertResponseStatusCodeSame(400); + + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Item not found for "'.$this->getIriFor('period1').'".', + ]); + } + public function testListMaterialItemsFilteredByPeriodInCampPrototypeIsAllowedForUnrelatedUser() { $period = static::getFixture('period1campPrototype'); $response = static::createClientWithCredentials()->request('GET', '/material_items?period=%2Fperiods%2F'.$period->getId()); @@ -164,4 +234,62 @@ public function testListMaterialItemsFilteredByPeriodInCampPrototypeIsAllowedFor ['href' => $this->getIriFor('materialItem1period1campPrototype')], ], $response->toArray()['_links']['items']); } + + public function testListMaterialItemsFilteredByPeriodInSharedCampIsAllowedForUnrelatedUser() { + $period = static::getFixture('period1campShared'); + $response = static::createClientWithCredentials()->request('GET', '/material_items?period=%2Fperiods%2F'.$period->getId()); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 1, + '_links' => [ + 'items' => [], + ], + '_embedded' => [ + 'items' => [], + ], + ]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('materialItem1period1campShared')], + ], $response->toArray()['_links']['items']); + } + + public function testListMaterialItemsFilteredByPeriodInSharedCampIsAllowedForInactiveUser() { + $period = static::getFixture('period1campShared'); + $response = static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('GET', '/material_items?period=%2Fperiods%2F'.$period->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 1, + '_links' => [ + 'items' => [], + ], + '_embedded' => [ + 'items' => [], + ], + ]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('materialItem1period1campShared')], + ], $response->toArray()['_links']['items']); + } + + public function testListMaterialItemsFilteredByPeriodInSharedCampIsAllowedForInvitedUser() { + $period = static::getFixture('period1campShared'); + $response = static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('GET', '/material_items?period=%2Fperiods%2F'.$period->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 1, + '_links' => [ + 'items' => [], + ], + '_embedded' => [ + 'items' => [], + ], + ]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('materialItem1period1campShared')], + ], $response->toArray()['_links']['items']); + } } diff --git a/api/tests/Api/MaterialItems/ReadMaterialItemTest.php b/api/tests/Api/MaterialItems/ReadMaterialItemTest.php index 075df971b2..681f5a2aef 100644 --- a/api/tests/Api/MaterialItems/ReadMaterialItemTest.php +++ b/api/tests/Api/MaterialItems/ReadMaterialItemTest.php @@ -121,4 +121,62 @@ public function testGetSingleMaterialItemInCampPrototypeIsAllowedForUnrelatedUse ], ]); } + + public function testGetSingleMaterialItemInSharedCampIsAllowedForUnrelatedUser() { + /** @var MaterialItem $materialItem */ + $materialItem = static::getFixture('materialItem1period1campShared'); + static::createClientWithCredentials()->request('GET', '/material_items/'.$materialItem->getId()); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $materialItem->getId(), + 'quantity' => (int) $materialItem->quantity, + 'unit' => $materialItem->unit, + 'article' => $materialItem->article, + '_links' => [ + 'period' => ['href' => $this->getIriFor('period1campShared')], + 'materialList' => ['href' => $this->getIriFor('materialList1campShared')], + 'materialNode' => null, + ], + ]); + } + + public function testGetSingleMaterialItemInSharedCampIsAllowedForInactiveUser() { + /** @var MaterialItem $materialItem */ + $materialItem = static::getFixture('materialItem1period1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('GET', '/material_items/'.$materialItem->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $materialItem->getId(), + 'quantity' => (int) $materialItem->quantity, + 'unit' => $materialItem->unit, + 'article' => $materialItem->article, + '_links' => [ + 'period' => ['href' => $this->getIriFor('period1campShared')], + 'materialList' => ['href' => $this->getIriFor('materialList1campShared')], + 'materialNode' => null, + ], + ]); + } + + public function testGetSingleMaterialItemInSharedCampIsAllowedForInvitedUser() { + /** @var MaterialItem $materialItem */ + $materialItem = static::getFixture('materialItem1period1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('GET', '/material_items/'.$materialItem->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $materialItem->getId(), + 'quantity' => (int) $materialItem->quantity, + 'unit' => $materialItem->unit, + 'article' => $materialItem->article, + '_links' => [ + 'period' => ['href' => $this->getIriFor('period1campShared')], + 'materialList' => ['href' => $this->getIriFor('materialList1campShared')], + 'materialNode' => null, + ], + ]); + } } diff --git a/api/tests/Api/MaterialItems/UpdateMaterialItemTest.php b/api/tests/Api/MaterialItems/UpdateMaterialItemTest.php index c5d300b392..eb0e33819d 100644 --- a/api/tests/Api/MaterialItems/UpdateMaterialItemTest.php +++ b/api/tests/Api/MaterialItems/UpdateMaterialItemTest.php @@ -145,6 +145,51 @@ public function testPatchMaterialItemInCampPrototypeIsDeniedForUnrelatedUser() { ]); } + public function testPatchMaterialItemInSharedCampIsDeniedForUnrelatedUser() { + $materialItem = static::getFixture('materialItem1period1campShared'); + static::createClientWithCredentials()->request('PATCH', '/material_items/'.$materialItem->getId(), ['json' => [ + 'materialNode' => null, + 'article' => 'Mehl', + 'quantity' => 1500, + 'unit' => 'g', + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testPatchMaterialItemInSharedCampIsDeniedForInactiveUser() { + $materialItem = static::getFixture('materialItem1period1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()])->request('PATCH', '/material_items/'.$materialItem->getId(), ['json' => [ + 'materialNode' => null, + 'article' => 'Mehl', + 'quantity' => 1500, + 'unit' => 'g', + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testPatchMaterialItemInSharedCampIsDeniedForInvitedUser() { + $materialItem = static::getFixture('materialItem1period1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()])->request('PATCH', '/material_items/'.$materialItem->getId(), ['json' => [ + 'materialNode' => null, + 'article' => 'Mehl', + 'quantity' => 1500, + 'unit' => 'g', + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + public function testPatchMaterialItemValidatesMissingMaterialList() { $materialItem = static::getFixture('materialItem1'); static::createClientWithCredentials()->request('PATCH', '/material_items/'.$materialItem->getId(), ['json' => [ diff --git a/api/tests/Api/MaterialLists/CreateMaterialListTest.php b/api/tests/Api/MaterialLists/CreateMaterialListTest.php index b9e4f8ce52..b27ef14902 100644 --- a/api/tests/Api/MaterialLists/CreateMaterialListTest.php +++ b/api/tests/Api/MaterialLists/CreateMaterialListTest.php @@ -91,6 +91,42 @@ public function testCreateMaterialListInCampPrototypeIsDeniedForUnrelatedUser() ]); } + public function testCreateMaterialListInSharedCampIsDeniedForUnrelatedUser() { + static::createClientWithCredentials()->request('POST', '/material_lists', ['json' => $this->getExampleWritePayload([ + 'camp' => $this->getIriFor('campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testCreateMaterialListInSharedCampIsDeniedForInactiveUser() { + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()])->request('POST', '/material_lists', ['json' => $this->getExampleWritePayload([ + 'camp' => $this->getIriFor('campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testCreateMaterialListInSharedCampIsDeniedForInvitedUser() { + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()])->request('POST', '/material_lists', ['json' => $this->getExampleWritePayload([ + 'camp' => $this->getIriFor('campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + public function testCreateMaterialListValidatesMissingCamp() { static::createClientWithCredentials()->request('POST', '/material_lists', ['json' => $this->getExampleWritePayload([], ['camp'])]); diff --git a/api/tests/Api/MaterialLists/DeleteMaterialListTest.php b/api/tests/Api/MaterialLists/DeleteMaterialListTest.php index 779dfbb708..2eedddd932 100644 --- a/api/tests/Api/MaterialLists/DeleteMaterialListTest.php +++ b/api/tests/Api/MaterialLists/DeleteMaterialListTest.php @@ -85,6 +85,43 @@ public function testDeleteMaterialListFromCampPrototypeIsDeniedForUnrelatedUser( ]); } + public function testDeleteMaterialListFromSharedCampIsDeniedForUnrelatedUser() { + $materialList = static::getFixture('materialList1campShared'); + static::createClientWithCredentials()->request('DELETE', '/material_lists/'.$materialList->getId()); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testDeleteMaterialListFromSharedCampIsDeniedForInactiveUser() { + $materialList = static::getFixture('materialList1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('DELETE', '/material_lists/'.$materialList->getId()) + ; + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testDeleteMaterialListFromSharedCampIsDeniedForInvitedUser() { + $materialList = static::getFixture('materialList1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('DELETE', '/material_lists/'.$materialList->getId()) + ; + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + public function testDeleteMaterialListValidatesThatListHasNoItems() { $materialList = static::getFixture('materialList1'); static::createClientWithCredentials()->request('DELETE', '/material_lists/'.$materialList->getId()); diff --git a/api/tests/Api/MaterialLists/ListMaterialListsTest.php b/api/tests/Api/MaterialLists/ListMaterialListsTest.php index c47c74c43e..a0b6de0c9d 100644 --- a/api/tests/Api/MaterialLists/ListMaterialListsTest.php +++ b/api/tests/Api/MaterialLists/ListMaterialListsTest.php @@ -24,7 +24,7 @@ public function testListMaterialListsIsAllowedForLoggedInUserButFiltered() { $response = static::createClientWithCredentials()->request('GET', '/material_lists'); $this->assertResponseStatusCodeSame(200); $this->assertJsonContains([ - 'totalItems' => 6, + 'totalItems' => 8, '_links' => [ 'items' => [], ], @@ -39,6 +39,8 @@ public function testListMaterialListsIsAllowedForLoggedInUserButFiltered() { ['href' => $this->getIriFor('materialList4Member')], ['href' => $this->getIriFor('materialList1camp2')], ['href' => $this->getIriFor('materialList1campPrototype')], + ['href' => $this->getIriFor('materialList1campShared')], + ['href' => $this->getIriFor('materialList2campShared')], ], $response->toArray()['_links']['items']); } @@ -104,4 +106,65 @@ public function testListMaterialListsFilteredByCampPrototypeIsAllowedForUnrelate ['href' => $this->getIriFor('materialList1campPrototype')], ], $response->toArray()['_links']['items']); } + + public function testListMaterialListsFilteredByCampIsAllowedForUnrelatedUser() { + $camp = static::getFixture('campShared'); + $response = static::createClientWithCredentials()->request('GET', '/material_lists?camp=%2Fcamps%2F'.$camp->getId()); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 2, + '_links' => [ + 'items' => [], + ], + '_embedded' => [ + 'items' => [], + ], + ]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('materialList1campShared')], + ['href' => $this->getIriFor('materialList2campShared')], + ], $response->toArray()['_links']['items']); + } + + public function testListMaterialListsFilteredByCampIsAllowedForInactiveUser() { + $camp = static::getFixture('campShared'); + $response = static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('GET', '/material_lists?camp=%2Fcamps%2F'.$camp->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 2, + '_links' => [ + 'items' => [], + ], + '_embedded' => [ + 'items' => [], + ], + ]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('materialList1campShared')], + ['href' => $this->getIriFor('materialList2campShared')], + ], $response->toArray()['_links']['items']); + } + + public function testListMaterialListsFilteredByCampIsAllowedForInvitedUser() { + $camp = static::getFixture('campShared'); + $response = static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('GET', '/material_lists?camp=%2Fcamps%2F'.$camp->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 2, + '_links' => [ + 'items' => [], + ], + '_embedded' => [ + 'items' => [], + ], + ]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('materialList1campShared')], + ['href' => $this->getIriFor('materialList2campShared')], + ], $response->toArray()['_links']['items']); + } } diff --git a/api/tests/Api/MaterialLists/ReadMaterialListTest.php b/api/tests/Api/MaterialLists/ReadMaterialListTest.php index 4b5251b7ee..2c2d20ac50 100644 --- a/api/tests/Api/MaterialLists/ReadMaterialListTest.php +++ b/api/tests/Api/MaterialLists/ReadMaterialListTest.php @@ -109,4 +109,53 @@ public function testGetSingleMaterialListFromCampPrototypeIsAllowedForUnrelatedU ], ]); } + + public function testGetSingleMaterialListFromSharedCampIsAllowedForUnrelatedUser() { + /** @var MaterialList $materialList */ + $materialList = static::getFixture('materialList1campShared'); + static::createClientWithCredentials()->request('GET', '/material_lists/'.$materialList->getId()); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $materialList->getId(), + 'name' => $materialList->name, + '_links' => [ + 'camp' => ['href' => $this->getIriFor('campShared')], + 'materialItems' => ['href' => '/material_items?materialList=%2Fmaterial_lists%2F'.$materialList->getId()], + ], + ]); + } + + public function testGetSingleMaterialListFromSharedCampIsAllowedForInactiveUser() { + /** @var MaterialList $materialList */ + $materialList = static::getFixture('materialList1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('GET', '/material_lists/'.$materialList->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $materialList->getId(), + 'name' => $materialList->name, + '_links' => [ + 'camp' => ['href' => $this->getIriFor('campShared')], + 'materialItems' => ['href' => '/material_items?materialList=%2Fmaterial_lists%2F'.$materialList->getId()], + ], + ]); + } + + public function testGetSingleMaterialListFromSharedCampIsAllowedForInvitedUser() { + /** @var MaterialList $materialList */ + $materialList = static::getFixture('materialList1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('GET', '/material_lists/'.$materialList->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $materialList->getId(), + 'name' => $materialList->name, + '_links' => [ + 'camp' => ['href' => $this->getIriFor('campShared')], + 'materialItems' => ['href' => '/material_items?materialList=%2Fmaterial_lists%2F'.$materialList->getId()], + ], + ]); + } } diff --git a/api/tests/Api/MaterialLists/UpdateMaterialListTest.php b/api/tests/Api/MaterialLists/UpdateMaterialListTest.php index e2979b0811..40fba6e85a 100644 --- a/api/tests/Api/MaterialLists/UpdateMaterialListTest.php +++ b/api/tests/Api/MaterialLists/UpdateMaterialListTest.php @@ -109,6 +109,42 @@ public function testPatchMaterialListInCampPrototypeIsDeniedForUnrelatedUser() { ]); } + public function testPatchMaterialListInSharedCampIsDeniedForUnrelatedUser() { + $materialList = static::getFixture('materialList1campShared'); + static::createClientWithCredentials()->request('PATCH', '/material_lists/'.$materialList->getId(), ['json' => [ + 'name' => 'Something', + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testPatchMaterialListInSharedCampIsDeniedForInactiveUser() { + $materialList = static::getFixture('materialList1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()])->request('PATCH', '/material_lists/'.$materialList->getId(), ['json' => [ + 'name' => 'Something', + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testPatchMaterialListInSharedCampIsDeniedForInvitedUser() { + $materialList = static::getFixture('materialList1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()])->request('PATCH', '/material_lists/'.$materialList->getId(), ['json' => [ + 'name' => 'Something', + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + public function testPatchMaterialListDisallowsChangingCamp() { $materialList = static::getFixture('materialList1'); static::createClientWithCredentials()->request('PATCH', '/material_lists/'.$materialList->getId(), ['json' => [ diff --git a/api/tests/Api/Periods/CreatePeriodTest.php b/api/tests/Api/Periods/CreatePeriodTest.php index e6cbb4dc4e..1cf5da4b9c 100644 --- a/api/tests/Api/Periods/CreatePeriodTest.php +++ b/api/tests/Api/Periods/CreatePeriodTest.php @@ -79,7 +79,7 @@ public function testCreatePeriodIsAllowedForManager() { $this->assertJsonContains($this->getExampleReadPayload()); } - public function testCreatePeriodInCampPrototypeIsDeniedForUnrelateduser() { + public function testCreatePeriodInCampPrototypeIsDeniedForUnrelatedUser() { static::createClientWithCredentials()->request('POST', '/periods', ['json' => $this->getExampleWritePayload([ 'camp' => $this->getIriFor('campPrototype'), ])]); @@ -91,6 +91,42 @@ public function testCreatePeriodInCampPrototypeIsDeniedForUnrelateduser() { ]); } + public function testCreatePeriodInSharedCampIsDeniedForUnrelatedUser() { + static::createClientWithCredentials()->request('POST', '/periods', ['json' => $this->getExampleWritePayload([ + 'camp' => $this->getIriFor('campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testCreatePeriodInSharedCampIsDeniedForInactiveUser() { + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()])->request('POST', '/periods', ['json' => $this->getExampleWritePayload([ + 'camp' => $this->getIriFor('campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testCreatePeriodInSharedCampIsDeniedForInvitedUser() { + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()])->request('POST', '/periods', ['json' => $this->getExampleWritePayload([ + 'camp' => $this->getIriFor('campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + public function testCreatePeriodValidatesMissingCamp() { static::createClientWithCredentials()->request('POST', '/periods', ['json' => $this->getExampleWritePayload([], ['camp'])]); diff --git a/api/tests/Api/Periods/DeletePeriodTest.php b/api/tests/Api/Periods/DeletePeriodTest.php index cc3da45536..54398f3161 100644 --- a/api/tests/Api/Periods/DeletePeriodTest.php +++ b/api/tests/Api/Periods/DeletePeriodTest.php @@ -85,6 +85,43 @@ public function testDeletePeriodFromCampPrototypeIsDeniedForUnrelatedUser() { ]); } + public function testDeletePeriodFromSharedCampIsDeniedForUnrelatedUser() { + $period = static::getFixture('period1campShared'); + static::createClientWithCredentials()->request('DELETE', '/periods/'.$period->getId()); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testDeletePeriodFromSharedCampIsDeniedForInactiveUser() { + $period = static::getFixture('period1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('DELETE', '/periods/'.$period->getId()) + ; + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testDeletePeriodFromSharedCampIsDeniedForInvitedUser() { + $period = static::getFixture('period1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('DELETE', '/periods/'.$period->getId()) + ; + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + public function testDeleteLastPeriodIsRejected() { $period = static::getFixture('period1camp2'); static::createClientWithCredentials() diff --git a/api/tests/Api/Periods/ListPeriodsTest.php b/api/tests/Api/Periods/ListPeriodsTest.php index 78320400cd..239c03f7ce 100644 --- a/api/tests/Api/Periods/ListPeriodsTest.php +++ b/api/tests/Api/Periods/ListPeriodsTest.php @@ -24,7 +24,7 @@ public function testListPeriodsIsAllowedForLoggedInUserButFiltered() { $response = static::createClientWithCredentials()->request('GET', '/periods'); $this->assertResponseStatusCodeSame(200); $this->assertJsonContains([ - 'totalItems' => 4, + 'totalItems' => 5, '_links' => [ 'items' => [], ], @@ -37,6 +37,7 @@ public function testListPeriodsIsAllowedForLoggedInUserButFiltered() { ['href' => $this->getIriFor('period2')], ['href' => $this->getIriFor('period1camp2')], ['href' => $this->getIriFor('period1campPrototype')], + ['href' => $this->getIriFor('period1campShared')], ], $response->toArray()['_links']['items']); } @@ -94,4 +95,44 @@ public function testListPeriodsFilteredByCampPrototypeIsAllowedForUnrelatedUser( ['href' => $this->getIriFor('period1campPrototype')], ], $response->toArray()['_links']['items']); } + + public function testListPeriodsFilteredBySharedCampIsAllowedForUnrelatedUser() { + $camp = static::getFixture('campShared'); + $response = static::createClientWithCredentials()->request('GET', '/periods?camp=%2Fcamps%2F'.$camp->getId()); + + $this->assertResponseStatusCodeSame(200); + + $this->assertJsonContains(['totalItems' => 1]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('period1campShared')], + ], $response->toArray()['_links']['items']); + } + + public function testListPeriodsFilteredBySharedCampIsAllowedForInactiveUser() { + $camp = static::getFixture('campShared'); + $response = static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('GET', '/periods?camp=%2Fcamps%2F'.$camp->getId()) + ; + + $this->assertResponseStatusCodeSame(200); + + $this->assertJsonContains(['totalItems' => 1]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('period1campShared')], + ], $response->toArray()['_links']['items']); + } + + public function testListPeriodsFilteredBySharedCampIsAllowedForInvitedUser() { + $camp = static::getFixture('campShared'); + $response = static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('GET', '/periods?camp=%2Fcamps%2F'.$camp->getId()) + ; + + $this->assertResponseStatusCodeSame(200); + + $this->assertJsonContains(['totalItems' => 1]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('period1campShared')], + ], $response->toArray()['_links']['items']); + } } diff --git a/api/tests/Api/Periods/ReadPeriodTest.php b/api/tests/Api/Periods/ReadPeriodTest.php index fd32d9febc..a2a32a4c78 100644 --- a/api/tests/Api/Periods/ReadPeriodTest.php +++ b/api/tests/Api/Periods/ReadPeriodTest.php @@ -131,4 +131,71 @@ public function testGetSinglePeriodFromCampPrototypeIsAllowedForUnrelatedUser() ], ]); } + + public function testGetSinglePeriodFromSharedCampIsAllowedForUnrelatedUser() { + /** @var Period $period */ + $period = static::getFixture('period1campShared'); + + // Precondition: no collaborations on the camp. + // This is to make sure a left join from camp to collaborations is used. + $this->assertEmpty($period->camp->collaborations); + + static::createClientWithCredentials()->request('GET', '/periods/'.$period->getId()); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $period->getId(), + 'description' => $period->description, + 'start' => $period->start->format('Y-m-d'), + 'end' => $period->end->format('Y-m-d'), + '_links' => [ + 'camp' => ['href' => $this->getIriFor('campShared')], + ], + ]); + } + + public function testGetSinglePeriodFromSharedCampIsAllowedForInactiveUser() { + /** @var Period $period */ + $period = static::getFixture('period1campShared'); + + // Precondition: no collaborations on the camp. + // This is to make sure a left join from camp to collaborations is used. + $this->assertEmpty($period->camp->collaborations); + + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('GET', '/periods/'.$period->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $period->getId(), + 'description' => $period->description, + 'start' => $period->start->format('Y-m-d'), + 'end' => $period->end->format('Y-m-d'), + '_links' => [ + 'camp' => ['href' => $this->getIriFor('campShared')], + ], + ]); + } + + public function testGetSinglePeriodFromSharedCampIsAllowedForInvitedUser() { + /** @var Period $period */ + $period = static::getFixture('period1campShared'); + + // Precondition: no collaborations on the camp. + // This is to make sure a left join from camp to collaborations is used. + $this->assertEmpty($period->camp->collaborations); + + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('GET', '/periods/'.$period->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $period->getId(), + 'description' => $period->description, + 'start' => $period->start->format('Y-m-d'), + 'end' => $period->end->format('Y-m-d'), + '_links' => [ + 'camp' => ['href' => $this->getIriFor('campShared')], + ], + ]); + } } diff --git a/api/tests/Api/Periods/UpdatePeriodTest.php b/api/tests/Api/Periods/UpdatePeriodTest.php index de58d13c37..4f36a550fa 100644 --- a/api/tests/Api/Periods/UpdatePeriodTest.php +++ b/api/tests/Api/Periods/UpdatePeriodTest.php @@ -123,6 +123,48 @@ public function testPatchPeriodInCampPrototypeIsDeniedForUnrelatedUser() { ]); } + public function testPatchPeriodInSharedCampIsDeniedForUnrelatedUser() { + $period = static::getFixture('period1campShared'); + static::createClientWithCredentials()->request('PATCH', '/periods/'.$period->getId(), ['json' => [ + 'description' => 'Vorweekend', + 'start' => '2023-01-01', + 'end' => '2023-01-02', + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testPatchPeriodInSharedCampIsDeniedForInactiveUser() { + $period = static::getFixture('period1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()])->request('PATCH', '/periods/'.$period->getId(), ['json' => [ + 'description' => 'Vorweekend', + 'start' => '2023-01-01', + 'end' => '2023-01-02', + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testPatchPeriodInSharedCampIsDeniedForInvitedUser() { + $period = static::getFixture('period1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()])->request('PATCH', '/periods/'.$period->getId(), ['json' => [ + 'description' => 'Vorweekend', + 'start' => '2023-01-01', + 'end' => '2023-01-02', + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + public function testPatchPeriodDisallowsChangingCamp() { $period = static::getFixture('period1'); static::createClientWithCredentials()->request('PATCH', '/periods/'.$period->getId(), ['json' => [ diff --git a/api/tests/Api/PersonalInvitations/ListPersonalInvitationsTest.php b/api/tests/Api/PersonalInvitations/ListPersonalInvitationsTest.php index 11b538a5a0..ac8e082f1d 100644 --- a/api/tests/Api/PersonalInvitations/ListPersonalInvitationsTest.php +++ b/api/tests/Api/PersonalInvitations/ListPersonalInvitationsTest.php @@ -25,11 +25,13 @@ public function testListPersonalInvitationsIsAllowedForLoggedInUserButFiltered() $client->request('GET', '/personal_invitations'); $this->assertResponseStatusCodeSame(200); $invitation = static::getFixture('campCollaboration6invitedWithUser'); + $invitation2 = static::getFixture('campCollaboration2invitedCampShared'); $this->assertJsonContains([ - 'totalItems' => 1, + 'totalItems' => 2, '_links' => [ 'items' => [ ['href' => "/personal_invitations/{$invitation->getId()}"], + ['href' => "/personal_invitations/{$invitation2->getId()}"], ], ], '_embedded' => [ diff --git a/api/tests/Api/ScheduleEntries/CreateScheduleEntryTest.php b/api/tests/Api/ScheduleEntries/CreateScheduleEntryTest.php index 432c9b3051..542b04372b 100644 --- a/api/tests/Api/ScheduleEntries/CreateScheduleEntryTest.php +++ b/api/tests/Api/ScheduleEntries/CreateScheduleEntryTest.php @@ -95,6 +95,45 @@ public function testCreateScheduleEntryInCampPrototypeIsDeniedForUnrelatedUser() ]); } + public function testCreateScheduleEntryInSharedCampIsDeniedForUnrelatedUser() { + static::createClientWithCredentials()->request('POST', '/schedule_entries', ['json' => $this->getExampleWritePayload([ + 'period' => $this->getIriFor('period1campShared'), + 'activity' => $this->getIriFor('activity1campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testCreateScheduleEntryInSharedCampIsDeniedForInactiveUser() { + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()])->request('POST', '/schedule_entries', ['json' => $this->getExampleWritePayload([ + 'period' => $this->getIriFor('period1campShared'), + 'activity' => $this->getIriFor('activity1campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testCreateScheduleEntryInSharedCampIsDeniedForInvitedUser() { + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()])->request('POST', '/schedule_entries', ['json' => $this->getExampleWritePayload([ + 'period' => $this->getIriFor('period1campShared'), + 'activity' => $this->getIriFor('activity1campShared'), + ])]); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + public function testCreateScheduleEntryValidatesMissingPeriod() { static::createClientWithCredentials()->request('POST', '/schedule_entries', ['json' => $this->getExampleWritePayload([], ['period'])]); diff --git a/api/tests/Api/ScheduleEntries/DeleteScheduleEntryTest.php b/api/tests/Api/ScheduleEntries/DeleteScheduleEntryTest.php index 625d258036..95d2c67ab2 100644 --- a/api/tests/Api/ScheduleEntries/DeleteScheduleEntryTest.php +++ b/api/tests/Api/ScheduleEntries/DeleteScheduleEntryTest.php @@ -85,6 +85,43 @@ public function testDeleteScheduleEntryFromCampPrototypeIsDeniedForUnrelatedUser ]); } + public function testDeleteScheduleEntryFromSharedCampIsDeniedForUnrelatedUser() { + $scheduleEntry = static::getFixture('scheduleEntry1period1campShared'); + static::createClientWithCredentials()->request('DELETE', '/schedule_entries/'.$scheduleEntry->getId()); + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testDeleteScheduleEntryFromSharedCampIsDeniedForInactiveUser() { + $scheduleEntry = static::getFixture('scheduleEntry1period1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('DELETE', '/schedule_entries/'.$scheduleEntry->getId()) + ; + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testDeleteScheduleEntryFromSharedCampIsDeniedForInvitedUser() { + $scheduleEntry = static::getFixture('scheduleEntry1period1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('DELETE', '/schedule_entries/'.$scheduleEntry->getId()) + ; + + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + public function testDeleteScheduleEntryIsDeniedForLastScheduleEntryOfActivity() { $scheduleEntry = static::getFixture('scheduleEntry1period1camp1'); static::createClientWithCredentials()->request('DELETE', '/schedule_entries/'.$scheduleEntry->getId()); diff --git a/api/tests/Api/ScheduleEntries/ListScheduleEntriesTest.php b/api/tests/Api/ScheduleEntries/ListScheduleEntriesTest.php index 5ab84c3cc3..a830d21925 100644 --- a/api/tests/Api/ScheduleEntries/ListScheduleEntriesTest.php +++ b/api/tests/Api/ScheduleEntries/ListScheduleEntriesTest.php @@ -25,7 +25,7 @@ public function testListScheduleEntriesIsAllowedForLoggedInUserButFiltered() { $response = static::createClientWithCredentials()->request('GET', '/schedule_entries'); $this->assertResponseStatusCodeSame(200); $this->assertJsonContains([ - 'totalItems' => 6, + 'totalItems' => 8, '_links' => [ 'items' => [], ], @@ -40,6 +40,8 @@ public function testListScheduleEntriesIsAllowedForLoggedInUserButFiltered() { ['href' => $this->getIriFor('scheduleEntry1period1camp2')], ['href' => $this->getIriFor('scheduleEntry1period1campPrototype')], ['href' => $this->getIriFor('scheduleEntry2period1campPrototype')], + ['href' => $this->getIriFor('scheduleEntry1period1campShared')], + ['href' => $this->getIriFor('scheduleEntry2period1campShared')], ], $response->toArray()['_links']['items']); } @@ -106,6 +108,67 @@ public function testListScheduleEntriesFilteredByPeriodInCampPrototypeIsAllowedF ], $response->toArray()['_links']['items']); } + public function testListScheduleEntriesFilteredByPeriodInSharedCampIsAllowedForUnrelatedUser() { + $period = static::getFixture('period1campShared'); + $response = static::createClientWithCredentials()->request('GET', '/schedule_entries?period=%2Fperiods%2F'.$period->getId()); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 2, + '_links' => [ + 'items' => [], + ], + '_embedded' => [ + 'items' => [], + ], + ]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('scheduleEntry1period1campShared')], + ['href' => $this->getIriFor('scheduleEntry2period1campShared')], + ], $response->toArray()['_links']['items']); + } + + public function testListScheduleEntriesFilteredByPeriodInSharedCampIsAllowedForInactiveUser() { + $period = static::getFixture('period1campShared'); + $response = static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('GET', '/schedule_entries?period=%2Fperiods%2F'.$period->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 2, + '_links' => [ + 'items' => [], + ], + '_embedded' => [ + 'items' => [], + ], + ]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('scheduleEntry1period1campShared')], + ['href' => $this->getIriFor('scheduleEntry2period1campShared')], + ], $response->toArray()['_links']['items']); + } + + public function testListScheduleEntriesFilteredByPeriodInSharedCampIsAllowedForInvitedUser() { + $period = static::getFixture('period1campShared'); + $response = static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('GET', '/schedule_entries?period=%2Fperiods%2F'.$period->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 2, + '_links' => [ + 'items' => [], + ], + '_embedded' => [ + 'items' => [], + ], + ]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('scheduleEntry1period1campShared')], + ['href' => $this->getIriFor('scheduleEntry2period1campShared')], + ], $response->toArray()['_links']['items']); + } + public function testListScheduleEntriesFilteredByActivityIsAllowedForCollaborator() { $activity = static::getFixture('activity1'); $response = static::createClientWithCredentials()->request('GET', '/schedule_entries?activity=%2Factivities%2F'.$activity->getId()); @@ -168,6 +231,67 @@ public function testListScheduleEntriesFilteredByActivityInCampPrototypeIsAllowe ], $response->toArray()['_links']['items']); } + public function testListScheduleEntriesFilteredByActivityInSharedCampIsAllowedForUnrelatedUser() { + $activity = static::getFixture('activity1campShared'); + $response = static::createClientWithCredentials()->request('GET', '/schedule_entries?activity=%2Factivities%2F'.$activity->getId()); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 2, + '_links' => [ + 'items' => [], + ], + '_embedded' => [ + 'items' => [], + ], + ]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('scheduleEntry1period1campShared')], + ['href' => $this->getIriFor('scheduleEntry2period1campShared')], + ], $response->toArray()['_links']['items']); + } + + public function testListScheduleEntriesFilteredByActivityInSharedCampIsAllowedForInactiveUser() { + $activity = static::getFixture('activity1campShared'); + $response = static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('GET', '/schedule_entries?activity=%2Factivities%2F'.$activity->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 2, + '_links' => [ + 'items' => [], + ], + '_embedded' => [ + 'items' => [], + ], + ]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('scheduleEntry1period1campShared')], + ['href' => $this->getIriFor('scheduleEntry2period1campShared')], + ], $response->toArray()['_links']['items']); + } + + public function testListScheduleEntriesFilteredByActivityInSharedCampIsAllowedForInvitedUser() { + $activity = static::getFixture('activity1campShared'); + $response = static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('GET', '/schedule_entries?activity=%2Factivities%2F'.$activity->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 2, + '_links' => [ + 'items' => [], + ], + '_embedded' => [ + 'items' => [], + ], + ]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('scheduleEntry1period1campShared')], + ['href' => $this->getIriFor('scheduleEntry2period1campShared')], + ], $response->toArray()['_links']['items']); + } + public function testListScheduleEntriesFilteredByStartBeforeIsAllowedForCollaborator() { /** @var ScheduleEntry $scheduleEntry */ $scheduleEntry = static::getFixture('scheduleEntry2period1campPrototype'); @@ -213,7 +337,7 @@ public function testListScheduleEntriesFilteredByStartAfterIsAllowedForCollabora $response = static::createClientWithCredentials()->request('GET', '/schedule_entries?start[after]='.urlencode($scheduleEntry->getStart()->format(\DateTime::W3C))); $this->assertResponseStatusCodeSame(200); $this->assertJsonContains([ - 'totalItems' => 4, + 'totalItems' => 6, '_links' => [ 'items' => [], ], @@ -226,6 +350,8 @@ public function testListScheduleEntriesFilteredByStartAfterIsAllowedForCollabora ['href' => $this->getIriFor('scheduleEntry2')], ['href' => $this->getIriFor('scheduleEntry1period1camp2')], ['href' => $this->getIriFor('scheduleEntry1period1camp1')], + ['href' => $this->getIriFor('scheduleEntry1period1campShared')], + ['href' => $this->getIriFor('scheduleEntry2period1campShared')], ], $response->toArray()['_links']['items']); } @@ -235,7 +361,7 @@ public function testListScheduleEntriesFilteredByStartStrictlyAfterIsAllowedForC $response = static::createClientWithCredentials()->request('GET', '/schedule_entries?start[strictly_after]='.urlencode($scheduleEntry->getStart()->format(\DateTime::W3C))); $this->assertResponseStatusCodeSame(200); $this->assertJsonContains([ - 'totalItems' => 3, + 'totalItems' => 5, '_links' => [ 'items' => [], ], @@ -247,6 +373,8 @@ public function testListScheduleEntriesFilteredByStartStrictlyAfterIsAllowedForC ['href' => $this->getIriFor('scheduleEntry1')], ['href' => $this->getIriFor('scheduleEntry2')], ['href' => $this->getIriFor('scheduleEntry1period1camp1')], + ['href' => $this->getIriFor('scheduleEntry1period1campShared')], + ['href' => $this->getIriFor('scheduleEntry2period1campShared')], ], $response->toArray()['_links']['items']); } @@ -254,7 +382,7 @@ public function testListScheduleEntriesFilteredByInvalidStartDoesntFilter() { $response = static::createClientWithCredentials()->request('GET', '/schedule_entries?start[after]=when-I-was-young'); $this->assertResponseStatusCodeSame(200); $this->assertJsonContains([ - 'totalItems' => 6, + 'totalItems' => 8, '_links' => [ 'items' => [], ], @@ -269,6 +397,8 @@ public function testListScheduleEntriesFilteredByInvalidStartDoesntFilter() { ['href' => $this->getIriFor('scheduleEntry1period1camp2')], ['href' => $this->getIriFor('scheduleEntry1period1campPrototype')], ['href' => $this->getIriFor('scheduleEntry2period1campPrototype')], + ['href' => $this->getIriFor('scheduleEntry1period1campShared')], + ['href' => $this->getIriFor('scheduleEntry2period1campShared')], ], $response->toArray()['_links']['items']); } @@ -317,7 +447,7 @@ public function testListScheduleEntriesFilteredByEndAfterIsAllowedForCollaborato $response = static::createClientWithCredentials()->request('GET', '/schedule_entries?end[after]='.urlencode($scheduleEntry->getEnd()->format(\DateTime::W3C))); $this->assertResponseStatusCodeSame(200); $this->assertJsonContains([ - 'totalItems' => 4, + 'totalItems' => 6, '_links' => [ 'items' => [], ], @@ -330,6 +460,8 @@ public function testListScheduleEntriesFilteredByEndAfterIsAllowedForCollaborato ['href' => $this->getIriFor('scheduleEntry2')], ['href' => $this->getIriFor('scheduleEntry1period1camp2')], ['href' => $this->getIriFor('scheduleEntry1period1camp1')], + ['href' => $this->getIriFor('scheduleEntry1period1campShared')], + ['href' => $this->getIriFor('scheduleEntry2period1campShared')], ], $response->toArray()['_links']['items']); } @@ -339,7 +471,7 @@ public function testListScheduleEntriesFilteredByEndStrictlyAfterIsAllowedForCol $response = static::createClientWithCredentials()->request('GET', '/schedule_entries?end[strictly_after]='.urlencode($scheduleEntry->getEnd()->format(\DateTime::W3C))); $this->assertResponseStatusCodeSame(200); $this->assertJsonContains([ - 'totalItems' => 3, + 'totalItems' => 5, '_links' => [ 'items' => [], ], @@ -351,6 +483,8 @@ public function testListScheduleEntriesFilteredByEndStrictlyAfterIsAllowedForCol ['href' => $this->getIriFor('scheduleEntry1')], ['href' => $this->getIriFor('scheduleEntry2')], ['href' => $this->getIriFor('scheduleEntry1period1camp1')], + ['href' => $this->getIriFor('scheduleEntry1period1campShared')], + ['href' => $this->getIriFor('scheduleEntry2period1campShared')], ], $response->toArray()['_links']['items']); } @@ -358,7 +492,7 @@ public function testListScheduleEntriesFilteredByInvalidEndDoesntFilter() { $response = static::createClientWithCredentials()->request('GET', '/schedule_entries?end[before]=when-I-was-young'); $this->assertResponseStatusCodeSame(200); $this->assertJsonContains([ - 'totalItems' => 6, + 'totalItems' => 8, '_links' => [ 'items' => [], ], @@ -373,6 +507,8 @@ public function testListScheduleEntriesFilteredByInvalidEndDoesntFilter() { ['href' => $this->getIriFor('scheduleEntry1period1camp2')], ['href' => $this->getIriFor('scheduleEntry1period1campPrototype')], ['href' => $this->getIriFor('scheduleEntry2period1campPrototype')], + ['href' => $this->getIriFor('scheduleEntry1period1campShared')], + ['href' => $this->getIriFor('scheduleEntry2period1campShared')], ], $response->toArray()['_links']['items']); } diff --git a/api/tests/Api/ScheduleEntries/ReadScheduleEntryTest.php b/api/tests/Api/ScheduleEntries/ReadScheduleEntryTest.php index db6f231306..1b7363f026 100644 --- a/api/tests/Api/ScheduleEntries/ReadScheduleEntryTest.php +++ b/api/tests/Api/ScheduleEntries/ReadScheduleEntryTest.php @@ -142,4 +142,77 @@ public function testGetSingleScheduleEntryInCampPrototypeIsAllowedForUnrelatedUs ], ]); } + + public function testGetSingleScheduleEntryInSharedCampIsAllowedForUnrelatedUser() { + /** @var ScheduleEntry $scheduleEntry */ + $scheduleEntry = static::getFixture('scheduleEntry1period1campShared'); + + static::createClientWithCredentials()->request('GET', '/schedule_entries/'.$scheduleEntry->getId()); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $scheduleEntry->getId(), + 'left' => 0, + 'width' => 1, + 'dayNumber' => 1, + 'scheduleEntryNumber' => 1, + 'number' => '1.1', + 'start' => '2025-06-07T11:00:00+00:00', + 'end' => '2025-06-07T12:00:00+00:00', + '_links' => [ + 'activity' => ['href' => $this->getIriFor('activity1campShared')], + 'period' => ['href' => $this->getIriFor('period1campShared')], + 'day' => ['href' => $this->getIriFor('day1period1campShared')], + ], + ]); + } + + public function testGetSingleScheduleEntryInSharedCampIsAllowedForInactiveUser() { + /** @var ScheduleEntry $scheduleEntry */ + $scheduleEntry = static::getFixture('scheduleEntry1period1campShared'); + + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('GET', '/schedule_entries/'.$scheduleEntry->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $scheduleEntry->getId(), + 'left' => 0, + 'width' => 1, + 'dayNumber' => 1, + 'scheduleEntryNumber' => 1, + 'number' => '1.1', + 'start' => '2025-06-07T11:00:00+00:00', + 'end' => '2025-06-07T12:00:00+00:00', + '_links' => [ + 'activity' => ['href' => $this->getIriFor('activity1campShared')], + 'period' => ['href' => $this->getIriFor('period1campShared')], + 'day' => ['href' => $this->getIriFor('day1period1campShared')], + ], + ]); + } + + public function testGetSingleScheduleEntryInSharedCampIsAllowedForInvitedUser() { + /** @var ScheduleEntry $scheduleEntry */ + $scheduleEntry = static::getFixture('scheduleEntry1period1campShared'); + + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()]) + ->request('GET', '/schedule_entries/'.$scheduleEntry->getId()) + ; + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $scheduleEntry->getId(), + 'left' => 0, + 'width' => 1, + 'dayNumber' => 1, + 'scheduleEntryNumber' => 1, + 'number' => '1.1', + 'start' => '2025-06-07T11:00:00+00:00', + 'end' => '2025-06-07T12:00:00+00:00', + '_links' => [ + 'activity' => ['href' => $this->getIriFor('activity1campShared')], + 'period' => ['href' => $this->getIriFor('period1campShared')], + 'day' => ['href' => $this->getIriFor('day1period1campShared')], + ], + ]); + } } diff --git a/api/tests/Api/ScheduleEntries/UpdateScheduleEntryTest.php b/api/tests/Api/ScheduleEntries/UpdateScheduleEntryTest.php index a3747db90a..e576b8c74f 100644 --- a/api/tests/Api/ScheduleEntries/UpdateScheduleEntryTest.php +++ b/api/tests/Api/ScheduleEntries/UpdateScheduleEntryTest.php @@ -142,6 +142,54 @@ public function testPatchScheduleEntryInCampPrototypeIsDeniedForUnrelatedUser() ]); } + public function testPatchScheduleEntryInSharedCampIsDeniedForUnrelatedUser() { + $scheduleEntry = static::getFixture('scheduleEntry1period1campShared'); + static::createClientWithCredentials()->request('PATCH', '/schedule_entries/'.$scheduleEntry->getId(), ['json' => [ + 'period' => $this->getIriFor('period2'), + 'start' => '2023-04-15T00:10:00+00:00', + 'end' => '2023-04-15T00:40:00+00:00', + 'left' => 0.3, + 'width' => 0.7, + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testPatchScheduleEntryInSharedCampIsDeniedForInactiveUser() { + $scheduleEntry = static::getFixture('scheduleEntry1period1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()])->request('PATCH', '/schedule_entries/'.$scheduleEntry->getId(), ['json' => [ + 'period' => $this->getIriFor('period2'), + 'start' => '2023-04-15T00:10:00+00:00', + 'end' => '2023-04-15T00:40:00+00:00', + 'left' => 0.3, + 'width' => 0.7, + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + + public function testPatchScheduleEntryInSharedCampIsDeniedForInvitedUser() { + $scheduleEntry = static::getFixture('scheduleEntry1period1campShared'); + static::createClientWithCredentials(['email' => static::$fixtures['user6invited']->getEmail()])->request('PATCH', '/schedule_entries/'.$scheduleEntry->getId(), ['json' => [ + 'period' => $this->getIriFor('period2'), + 'start' => '2023-04-15T00:10:00+00:00', + 'end' => '2023-04-15T00:40:00+00:00', + 'left' => 0.3, + 'width' => 0.7, + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(403); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Access Denied.', + ]); + } + public function testPatchScheduleEntryDisallowsChangingActivity() { $scheduleEntry = static::getFixture('scheduleEntry1'); static::createClientWithCredentials()->request('PATCH', '/schedule_entries/'.$scheduleEntry->getId(), ['json' => [ diff --git a/api/tests/Api/SnapshotTests/EndpointPerformanceTest.php b/api/tests/Api/SnapshotTests/EndpointPerformanceTest.php index 346c626aeb..443ffb5909 100644 --- a/api/tests/Api/SnapshotTests/EndpointPerformanceTest.php +++ b/api/tests/Api/SnapshotTests/EndpointPerformanceTest.php @@ -197,7 +197,7 @@ protected function getSnapshotId(): string { private static function getContentNodeEndpointQueryCountRanges(): array { return [ - '/content_nodes' => [10, 12], + '/content_nodes' => [13, 15], '/content_node/column_layouts' => [6, 6], '/content_node/column_layouts/item' => [9, 9], '/content_node/checklist_nodes' => [6, 7], diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set activities__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set activities__1.json index 617f4c0ba1..0c06ddbfc2 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set activities__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set activities__1.json @@ -265,6 +265,107 @@ "location": "escaped_value", "title": "escaped_value" }, + { + "_embedded": { + "activityResponsibles": [ + { + "_links": { + "activity": { + "href": "escaped_value" + }, + "campCollaboration": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "id": "escaped_value" + } + ], + "progressLabel": "escaped_value", + "scheduleEntries": [ + { + "_links": { + "activity": { + "href": "escaped_value" + }, + "day": { + "href": "escaped_value" + }, + "period": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "dayNumber": "escaped_value", + "end": "escaped_value", + "id": "escaped_value", + "left": "escaped_value", + "number": "escaped_value", + "scheduleEntryNumber": "escaped_value", + "start": "escaped_value", + "width": "escaped_value" + }, + { + "_links": { + "activity": { + "href": "escaped_value" + }, + "day": { + "href": "escaped_value" + }, + "period": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "dayNumber": "escaped_value", + "end": "escaped_value", + "id": "escaped_value", + "left": "escaped_value", + "number": "escaped_value", + "scheduleEntryNumber": "escaped_value", + "start": "escaped_value", + "width": "escaped_value" + } + ] + }, + "_links": { + "activityResponsibles": { + "href": "escaped_value" + }, + "camp": { + "href": "escaped_value" + }, + "category": { + "href": "escaped_value" + }, + "comments": { + "href": "escaped_value" + }, + "contentNodes": { + "href": "escaped_value" + }, + "progressLabel": "escaped_value", + "rootContentNode": { + "href": "escaped_value" + }, + "scheduleEntries": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "id": "escaped_value", + "location": "escaped_value", + "title": "escaped_value" + }, { "_embedded": { "activityResponsibles": [ @@ -369,6 +470,9 @@ { "href": "escaped_value" }, + { + "href": "escaped_value" + }, { "href": "escaped_value" } diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set activity_progress_labels__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set activity_progress_labels__1.json index 48b07e3bcd..0239103598 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set activity_progress_labels__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set activity_progress_labels__1.json @@ -79,6 +79,32 @@ "position": "escaped_value", "title": "escaped_value" }, + { + "_links": { + "camp": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "id": "escaped_value", + "position": "escaped_value", + "title": "escaped_value" + }, + { + "_links": { + "camp": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "id": "escaped_value", + "position": "escaped_value", + "title": "escaped_value" + }, { "_links": { "camp": { @@ -114,6 +140,12 @@ { "href": "escaped_value" }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, { "href": "escaped_value" } diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set activity_responsibles__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set activity_responsibles__1.json index 9706fa85fe..29b2355861 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set activity_responsibles__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set activity_responsibles__1.json @@ -29,6 +29,20 @@ }, "id": "escaped_value" }, + { + "_links": { + "activity": { + "href": "escaped_value" + }, + "campCollaboration": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "id": "escaped_value" + }, { "_links": { "activity": { @@ -53,6 +67,9 @@ { "href": "escaped_value" }, + { + "href": "escaped_value" + }, { "href": "escaped_value" } diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set camp_collaborations__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set camp_collaborations__1.json index a1216ca249..303187f300 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set camp_collaborations__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set camp_collaborations__1.json @@ -34,7 +34,8 @@ }, "self": { "href": "escaped_value" - } + }, + "sharedBy": "escaped_value" }, "addressCity": "escaped_value", "addressName": "escaped_value", @@ -45,10 +46,12 @@ "courseNumber": "escaped_value", "id": "escaped_value", "isPrototype": "escaped_value", + "isShared": "escaped_value", "kind": "escaped_value", "motto": "escaped_value", "organizer": "escaped_value", "printYSLogoOnPicasso": "escaped_value", + "sharedSince": "escaped_value", "shortTitle": "escaped_value", "title": "escaped_value", "trainingAdvisorName": "escaped_value" @@ -104,7 +107,8 @@ }, "self": { "href": "escaped_value" - } + }, + "sharedBy": "escaped_value" }, "addressCity": "escaped_value", "addressName": "escaped_value", @@ -115,10 +119,12 @@ "courseNumber": "escaped_value", "id": "escaped_value", "isPrototype": "escaped_value", + "isShared": "escaped_value", "kind": "escaped_value", "motto": "escaped_value", "organizer": "escaped_value", "printYSLogoOnPicasso": "escaped_value", + "sharedSince": "escaped_value", "shortTitle": "escaped_value", "title": "escaped_value", "trainingAdvisorName": "escaped_value" @@ -189,7 +195,8 @@ }, "self": { "href": "escaped_value" - } + }, + "sharedBy": "escaped_value" }, "addressCity": "escaped_value", "addressName": "escaped_value", @@ -200,10 +207,12 @@ "courseNumber": "escaped_value", "id": "escaped_value", "isPrototype": "escaped_value", + "isShared": "escaped_value", "kind": "escaped_value", "motto": "escaped_value", "organizer": "escaped_value", "printYSLogoOnPicasso": "escaped_value", + "sharedSince": "escaped_value", "shortTitle": "escaped_value", "title": "escaped_value", "trainingAdvisorName": "escaped_value" @@ -274,7 +283,8 @@ }, "self": { "href": "escaped_value" - } + }, + "sharedBy": "escaped_value" }, "addressCity": "escaped_value", "addressName": "escaped_value", @@ -285,10 +295,12 @@ "courseNumber": "escaped_value", "id": "escaped_value", "isPrototype": "escaped_value", + "isShared": "escaped_value", "kind": "escaped_value", "motto": "escaped_value", "organizer": "escaped_value", "printYSLogoOnPicasso": "escaped_value", + "sharedSince": "escaped_value", "shortTitle": "escaped_value", "title": "escaped_value", "trainingAdvisorName": "escaped_value" @@ -359,7 +371,8 @@ }, "self": { "href": "escaped_value" - } + }, + "sharedBy": "escaped_value" }, "addressCity": "escaped_value", "addressName": "escaped_value", @@ -370,10 +383,12 @@ "courseNumber": "escaped_value", "id": "escaped_value", "isPrototype": "escaped_value", + "isShared": "escaped_value", "kind": "escaped_value", "motto": "escaped_value", "organizer": "escaped_value", "printYSLogoOnPicasso": "escaped_value", + "sharedSince": "escaped_value", "shortTitle": "escaped_value", "title": "escaped_value", "trainingAdvisorName": "escaped_value" @@ -444,7 +459,8 @@ }, "self": { "href": "escaped_value" - } + }, + "sharedBy": "escaped_value" }, "addressCity": "escaped_value", "addressName": "escaped_value", @@ -455,10 +471,12 @@ "courseNumber": "escaped_value", "id": "escaped_value", "isPrototype": "escaped_value", + "isShared": "escaped_value", "kind": "escaped_value", "motto": "escaped_value", "organizer": "escaped_value", "printYSLogoOnPicasso": "escaped_value", + "sharedSince": "escaped_value", "shortTitle": "escaped_value", "title": "escaped_value", "trainingAdvisorName": "escaped_value" @@ -527,10 +545,99 @@ "progressLabels": { "href": "escaped_value" }, + "self": { + "href": "escaped_value" + }, + "sharedBy": "escaped_value" + }, + "addressCity": "escaped_value", + "addressName": "escaped_value", + "addressStreet": "escaped_value", + "addressZipcode": "escaped_value", + "coachName": "escaped_value", + "courseKind": "escaped_value", + "courseNumber": "escaped_value", + "id": "escaped_value", + "isPrototype": "escaped_value", + "isShared": "escaped_value", + "kind": "escaped_value", + "motto": "escaped_value", + "organizer": "escaped_value", + "printYSLogoOnPicasso": "escaped_value", + "sharedSince": "escaped_value", + "shortTitle": "escaped_value", + "title": "escaped_value", + "trainingAdvisorName": "escaped_value" + }, + "user": { + "_links": { + "profile": { + "href": "escaped_value" + }, "self": { "href": "escaped_value" } }, + "abbreviation": "escaped_value", + "color": "escaped_value", + "displayName": "escaped_value", + "id": "escaped_value" + } + }, + "_links": { + "camp": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + }, + "user": { + "href": "escaped_value" + } + }, + "abbreviation": "escaped_value", + "color": "escaped_value", + "id": "escaped_value", + "inviteEmail": "escaped_value", + "role": "escaped_value", + "status": "escaped_value" + }, + { + "_embedded": { + "camp": { + "_links": { + "activities": { + "href": "escaped_value" + }, + "campCollaborations": { + "href": "escaped_value" + }, + "categories": { + "href": "escaped_value" + }, + "checklists": { + "href": "escaped_value" + }, + "creator": { + "href": "escaped_value" + }, + "materialLists": { + "href": "escaped_value" + }, + "periods": { + "href": "escaped_value" + }, + "profiles": { + "href": "escaped_value" + }, + "progressLabels": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + }, + "sharedBy": "escaped_value" + }, "addressCity": "escaped_value", "addressName": "escaped_value", "addressStreet": "escaped_value", @@ -540,10 +647,12 @@ "courseNumber": "escaped_value", "id": "escaped_value", "isPrototype": "escaped_value", + "isShared": "escaped_value", "kind": "escaped_value", "motto": "escaped_value", "organizer": "escaped_value", "printYSLogoOnPicasso": "escaped_value", + "sharedSince": "escaped_value", "shortTitle": "escaped_value", "title": "escaped_value", "trainingAdvisorName": "escaped_value" @@ -612,10 +721,99 @@ "progressLabels": { "href": "escaped_value" }, + "self": { + "href": "escaped_value" + }, + "sharedBy": "escaped_value" + }, + "addressCity": "escaped_value", + "addressName": "escaped_value", + "addressStreet": "escaped_value", + "addressZipcode": "escaped_value", + "coachName": "escaped_value", + "courseKind": "escaped_value", + "courseNumber": "escaped_value", + "id": "escaped_value", + "isPrototype": "escaped_value", + "isShared": "escaped_value", + "kind": "escaped_value", + "motto": "escaped_value", + "organizer": "escaped_value", + "printYSLogoOnPicasso": "escaped_value", + "sharedSince": "escaped_value", + "shortTitle": "escaped_value", + "title": "escaped_value", + "trainingAdvisorName": "escaped_value" + }, + "user": { + "_links": { + "profile": { + "href": "escaped_value" + }, "self": { "href": "escaped_value" } }, + "abbreviation": "escaped_value", + "color": "escaped_value", + "displayName": "escaped_value", + "id": "escaped_value" + } + }, + "_links": { + "camp": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + }, + "user": { + "href": "escaped_value" + } + }, + "abbreviation": "escaped_value", + "color": "escaped_value", + "id": "escaped_value", + "inviteEmail": "escaped_value", + "role": "escaped_value", + "status": "escaped_value" + }, + { + "_embedded": { + "camp": { + "_links": { + "activities": { + "href": "escaped_value" + }, + "campCollaborations": { + "href": "escaped_value" + }, + "categories": { + "href": "escaped_value" + }, + "checklists": { + "href": "escaped_value" + }, + "creator": { + "href": "escaped_value" + }, + "materialLists": { + "href": "escaped_value" + }, + "periods": { + "href": "escaped_value" + }, + "profiles": { + "href": "escaped_value" + }, + "progressLabels": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + }, + "sharedBy": "escaped_value" + }, "addressCity": "escaped_value", "addressName": "escaped_value", "addressStreet": "escaped_value", @@ -625,10 +823,12 @@ "courseNumber": "escaped_value", "id": "escaped_value", "isPrototype": "escaped_value", + "isShared": "escaped_value", "kind": "escaped_value", "motto": "escaped_value", "organizer": "escaped_value", "printYSLogoOnPicasso": "escaped_value", + "sharedSince": "escaped_value", "shortTitle": "escaped_value", "title": "escaped_value", "trainingAdvisorName": "escaped_value" @@ -697,10 +897,99 @@ "progressLabels": { "href": "escaped_value" }, + "self": { + "href": "escaped_value" + }, + "sharedBy": "escaped_value" + }, + "addressCity": "escaped_value", + "addressName": "escaped_value", + "addressStreet": "escaped_value", + "addressZipcode": "escaped_value", + "coachName": "escaped_value", + "courseKind": "escaped_value", + "courseNumber": "escaped_value", + "id": "escaped_value", + "isPrototype": "escaped_value", + "isShared": "escaped_value", + "kind": "escaped_value", + "motto": "escaped_value", + "organizer": "escaped_value", + "printYSLogoOnPicasso": "escaped_value", + "sharedSince": "escaped_value", + "shortTitle": "escaped_value", + "title": "escaped_value", + "trainingAdvisorName": "escaped_value" + }, + "user": { + "_links": { + "profile": { + "href": "escaped_value" + }, "self": { "href": "escaped_value" } }, + "abbreviation": "escaped_value", + "color": "escaped_value", + "displayName": "escaped_value", + "id": "escaped_value" + } + }, + "_links": { + "camp": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + }, + "user": { + "href": "escaped_value" + } + }, + "abbreviation": "escaped_value", + "color": "escaped_value", + "id": "escaped_value", + "inviteEmail": "escaped_value", + "role": "escaped_value", + "status": "escaped_value" + }, + { + "_embedded": { + "camp": { + "_links": { + "activities": { + "href": "escaped_value" + }, + "campCollaborations": { + "href": "escaped_value" + }, + "categories": { + "href": "escaped_value" + }, + "checklists": { + "href": "escaped_value" + }, + "creator": { + "href": "escaped_value" + }, + "materialLists": { + "href": "escaped_value" + }, + "periods": { + "href": "escaped_value" + }, + "profiles": { + "href": "escaped_value" + }, + "progressLabels": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + }, + "sharedBy": "escaped_value" + }, "addressCity": "escaped_value", "addressName": "escaped_value", "addressStreet": "escaped_value", @@ -710,10 +999,12 @@ "courseNumber": "escaped_value", "id": "escaped_value", "isPrototype": "escaped_value", + "isShared": "escaped_value", "kind": "escaped_value", "motto": "escaped_value", "organizer": "escaped_value", "printYSLogoOnPicasso": "escaped_value", + "sharedSince": "escaped_value", "shortTitle": "escaped_value", "title": "escaped_value", "trainingAdvisorName": "escaped_value" @@ -784,6 +1075,84 @@ }, "self": { "href": "escaped_value" + }, + "sharedBy": { + "href": "escaped_value" + } + }, + "addressCity": "escaped_value", + "addressName": "escaped_value", + "addressStreet": "escaped_value", + "addressZipcode": "escaped_value", + "coachName": "escaped_value", + "courseKind": "escaped_value", + "courseNumber": "escaped_value", + "id": "escaped_value", + "isPrototype": "escaped_value", + "isShared": "escaped_value", + "kind": "escaped_value", + "motto": "escaped_value", + "organizer": "escaped_value", + "printYSLogoOnPicasso": "escaped_value", + "sharedSince": "escaped_value", + "shortTitle": "escaped_value", + "title": "escaped_value", + "trainingAdvisorName": "escaped_value" + }, + "user": "escaped_value" + }, + "_links": { + "camp": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + }, + "user": "escaped_value" + }, + "abbreviation": "escaped_value", + "color": "escaped_value", + "id": "escaped_value", + "inviteEmail": "escaped_value", + "role": "escaped_value", + "status": "escaped_value" + }, + { + "_embedded": { + "camp": { + "_links": { + "activities": { + "href": "escaped_value" + }, + "campCollaborations": { + "href": "escaped_value" + }, + "categories": { + "href": "escaped_value" + }, + "checklists": { + "href": "escaped_value" + }, + "creator": { + "href": "escaped_value" + }, + "materialLists": { + "href": "escaped_value" + }, + "periods": { + "href": "escaped_value" + }, + "profiles": { + "href": "escaped_value" + }, + "progressLabels": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + }, + "sharedBy": { + "href": "escaped_value" } }, "addressCity": "escaped_value", @@ -795,10 +1164,12 @@ "courseNumber": "escaped_value", "id": "escaped_value", "isPrototype": "escaped_value", + "isShared": "escaped_value", "kind": "escaped_value", "motto": "escaped_value", "organizer": "escaped_value", "printYSLogoOnPicasso": "escaped_value", + "sharedSince": "escaped_value", "shortTitle": "escaped_value", "title": "escaped_value", "trainingAdvisorName": "escaped_value" @@ -869,6 +1240,9 @@ }, "self": { "href": "escaped_value" + }, + "sharedBy": { + "href": "escaped_value" } }, "addressCity": "escaped_value", @@ -880,10 +1254,12 @@ "courseNumber": "escaped_value", "id": "escaped_value", "isPrototype": "escaped_value", + "isShared": "escaped_value", "kind": "escaped_value", "motto": "escaped_value", "organizer": "escaped_value", "printYSLogoOnPicasso": "escaped_value", + "sharedSince": "escaped_value", "shortTitle": "escaped_value", "title": "escaped_value", "trainingAdvisorName": "escaped_value" @@ -954,6 +1330,9 @@ }, "self": { "href": "escaped_value" + }, + "sharedBy": { + "href": "escaped_value" } }, "addressCity": "escaped_value", @@ -965,10 +1344,12 @@ "courseNumber": "escaped_value", "id": "escaped_value", "isPrototype": "escaped_value", + "isShared": "escaped_value", "kind": "escaped_value", "motto": "escaped_value", "organizer": "escaped_value", "printYSLogoOnPicasso": "escaped_value", + "sharedSince": "escaped_value", "shortTitle": "escaped_value", "title": "escaped_value", "trainingAdvisorName": "escaped_value" @@ -1043,6 +1424,18 @@ { "href": "escaped_value" }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, { "href": "escaped_value" } diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set camps__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set camps__1.json index 83063e878c..b085579fcb 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set camps__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set camps__1.json @@ -32,7 +32,8 @@ }, "self": { "href": "escaped_value" - } + }, + "sharedBy": "escaped_value" }, "addressCity": "escaped_value", "addressName": "escaped_value", @@ -43,10 +44,12 @@ "courseNumber": "escaped_value", "id": "escaped_value", "isPrototype": "escaped_value", + "isShared": "escaped_value", "kind": "escaped_value", "motto": "escaped_value", "organizer": "escaped_value", "printYSLogoOnPicasso": "escaped_value", + "sharedSince": "escaped_value", "shortTitle": "escaped_value", "title": "escaped_value", "trainingAdvisorName": "escaped_value" @@ -82,7 +85,8 @@ }, "self": { "href": "escaped_value" - } + }, + "sharedBy": "escaped_value" }, "addressCity": "escaped_value", "addressName": "escaped_value", @@ -93,10 +97,12 @@ "courseNumber": "escaped_value", "id": "escaped_value", "isPrototype": "escaped_value", + "isShared": "escaped_value", "kind": "escaped_value", "motto": "escaped_value", "organizer": "escaped_value", "printYSLogoOnPicasso": "escaped_value", + "sharedSince": "escaped_value", "shortTitle": "escaped_value", "title": "escaped_value", "trainingAdvisorName": "escaped_value" @@ -132,6 +138,62 @@ }, "self": { "href": "escaped_value" + }, + "sharedBy": "escaped_value" + }, + "addressCity": "escaped_value", + "addressName": "escaped_value", + "addressStreet": "escaped_value", + "addressZipcode": "escaped_value", + "coachName": "escaped_value", + "courseKind": "escaped_value", + "courseNumber": "escaped_value", + "id": "escaped_value", + "isPrototype": "escaped_value", + "isShared": "escaped_value", + "kind": "escaped_value", + "motto": "escaped_value", + "organizer": "escaped_value", + "printYSLogoOnPicasso": "escaped_value", + "sharedSince": "escaped_value", + "shortTitle": "escaped_value", + "title": "escaped_value", + "trainingAdvisorName": "escaped_value" + }, + { + "_links": { + "activities": { + "href": "escaped_value" + }, + "campCollaborations": { + "href": "escaped_value" + }, + "categories": { + "href": "escaped_value" + }, + "checklists": { + "href": "escaped_value" + }, + "creator": { + "href": "escaped_value" + }, + "materialLists": { + "href": "escaped_value" + }, + "periods": { + "href": "escaped_value" + }, + "profiles": { + "href": "escaped_value" + }, + "progressLabels": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + }, + "sharedBy": { + "href": "escaped_value" } }, "addressCity": "escaped_value", @@ -143,10 +205,12 @@ "courseNumber": "escaped_value", "id": "escaped_value", "isPrototype": "escaped_value", + "isShared": "escaped_value", "kind": "escaped_value", "motto": "escaped_value", "organizer": "escaped_value", "printYSLogoOnPicasso": "escaped_value", + "sharedSince": "escaped_value", "shortTitle": "escaped_value", "title": "escaped_value", "trainingAdvisorName": "escaped_value" @@ -161,6 +225,9 @@ { "href": "escaped_value" }, + { + "href": "escaped_value" + }, { "href": "escaped_value" } diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set categories__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set categories__1.json index d6df9880fe..feafd02a6f 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set categories__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set categories__1.json @@ -97,6 +97,30 @@ "numberingStyle": "escaped_value", "short": "escaped_value" }, + { + "_links": { + "camp": { + "href": "escaped_value" + }, + "contentNodes": { + "href": "escaped_value" + }, + "preferredContentTypes": { + "href": "escaped_value" + }, + "rootContentNode": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "color": "escaped_value", + "id": "escaped_value", + "name": "escaped_value", + "numberingStyle": "escaped_value", + "short": "escaped_value" + }, { "_links": { "camp": { @@ -137,6 +161,9 @@ { "href": "escaped_value" }, + { + "href": "escaped_value" + }, { "href": "escaped_value" } diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set checklist_items__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set checklist_items__1.json index cbfeee7a1a..064b4607a3 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set checklist_items__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set checklist_items__1.json @@ -33,6 +33,22 @@ "position": "escaped_value", "text": "escaped_value" }, + { + "_links": { + "checklist": { + "href": "escaped_value" + }, + "checklistNodes": [], + "children": [], + "parent": "escaped_value", + "self": { + "href": "escaped_value" + } + }, + "id": "escaped_value", + "position": "escaped_value", + "text": "escaped_value" + }, { "_links": { "checklist": { @@ -132,6 +148,9 @@ { "href": "escaped_value" }, + { + "href": "escaped_value" + }, { "href": "escaped_value" } diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set checklists__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set checklists__1.json index 56845d37f9..f3690a4126 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set checklists__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set checklists__1.json @@ -63,6 +63,22 @@ "isPrototype": "escaped_value", "name": "escaped_value" }, + { + "_links": { + "camp": { + "href": "escaped_value" + }, + "checklistItems": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "id": "escaped_value", + "isPrototype": "escaped_value", + "name": "escaped_value" + }, { "_links": { "camp": { @@ -95,6 +111,9 @@ { "href": "escaped_value" }, + { + "href": "escaped_value" + }, { "href": "escaped_value" } diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set comments__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set comments__1.json index 310fc61d40..b8955adcfb 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set comments__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set comments__1.json @@ -41,6 +41,46 @@ "orphanDescription": "escaped_value", "textHtml": "escaped_value" }, + { + "_links": { + "activity": { + "href": "escaped_value" + }, + "author": { + "href": "escaped_value" + }, + "camp": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "createTime": "escaped_value", + "id": "escaped_value", + "orphanDescription": "escaped_value", + "textHtml": "escaped_value" + }, + { + "_links": { + "activity": { + "href": "escaped_value" + }, + "author": { + "href": "escaped_value" + }, + "camp": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "createTime": "escaped_value", + "id": "escaped_value", + "orphanDescription": "escaped_value", + "textHtml": "escaped_value" + }, { "_links": { "activity": { @@ -71,6 +111,12 @@ { "href": "escaped_value" }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, { "href": "escaped_value" } diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_nodechecklist_nodes__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_nodechecklist_nodes__1.json index d5fd4f31c9..141ae7bb3c 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_nodechecklist_nodes__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_nodechecklist_nodes__1.json @@ -1,6 +1,58 @@ { "_embedded": { "items": [ + { + "_links": { + "checklistItems": { + "href": "escaped_value" + }, + "children": [], + "contentType": { + "href": "escaped_value" + }, + "parent": { + "href": "escaped_value" + }, + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": "escaped_value", + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, + { + "_links": { + "checklistItems": { + "href": "escaped_value" + }, + "children": [], + "contentType": { + "href": "escaped_value" + }, + "parent": { + "href": "escaped_value" + }, + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": "escaped_value", + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, { "_links": { "checklistItems": { @@ -57,6 +109,12 @@ }, "_links": { "items": [ + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, { "href": "escaped_value" }, diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_nodecolumn_layouts__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_nodecolumn_layouts__1.json index 9e9859e619..8a47173c97 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_nodecolumn_layouts__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_nodecolumn_layouts__1.json @@ -171,6 +171,166 @@ "position": "escaped_value", "slot": "escaped_value" }, + { + "_links": { + "children": [], + "contentType": { + "href": "escaped_value" + }, + "parent": { + "href": "escaped_value" + }, + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": { + "columns": [ + { + "slot": "escaped_value", + "width": "escaped_value" + } + ] + }, + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, + { + "_links": { + "children": [], + "contentType": { + "href": "escaped_value" + }, + "parent": { + "href": "escaped_value" + }, + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": { + "columns": [ + { + "slot": "escaped_value", + "width": "escaped_value" + } + ] + }, + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, + { + "_links": { + "children": [ + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + } + ], + "contentType": { + "href": "escaped_value" + }, + "parent": "escaped_value", + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": { + "columns": [ + { + "slot": "escaped_value", + "width": "escaped_value" + } + ] + }, + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, + { + "_links": { + "children": [ + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + } + ], + "contentType": { + "href": "escaped_value" + }, + "parent": "escaped_value", + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": { + "columns": [ + { + "slot": "escaped_value", + "width": "escaped_value" + } + ] + }, + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, { "_links": { "children": [ @@ -380,6 +540,18 @@ { "href": "escaped_value" }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, { "href": "escaped_value" } diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_nodematerial_nodes__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_nodematerial_nodes__1.json index eb28f3fab4..2e5cfdffc5 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_nodematerial_nodes__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_nodematerial_nodes__1.json @@ -1,6 +1,64 @@ { "_embedded": { "items": [ + { + "_embedded": { + "materialItems": [] + }, + "_links": { + "children": [], + "contentType": { + "href": "escaped_value" + }, + "materialItems": { + "href": "escaped_value" + }, + "parent": { + "href": "escaped_value" + }, + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": "escaped_value", + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, + { + "_embedded": { + "materialItems": [] + }, + "_links": { + "children": [], + "contentType": { + "href": "escaped_value" + }, + "materialItems": { + "href": "escaped_value" + }, + "parent": { + "href": "escaped_value" + }, + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": "escaped_value", + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, { "_embedded": { "materialItems": [] @@ -85,6 +143,12 @@ }, "_links": { "items": [ + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, { "href": "escaped_value" }, diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_nodemulti_selects__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_nodemulti_selects__1.json index 37229cdd64..9be8feffa6 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_nodemulti_selects__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_nodemulti_selects__1.json @@ -33,6 +33,64 @@ "position": "escaped_value", "slot": "escaped_value" }, + { + "_links": { + "children": [], + "contentType": { + "href": "escaped_value" + }, + "parent": { + "href": "escaped_value" + }, + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": { + "options": { + "key1": { + "checked": "escaped_value" + } + } + }, + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, + { + "_links": { + "children": [], + "contentType": { + "href": "escaped_value" + }, + "parent": { + "href": "escaped_value" + }, + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": { + "options": { + "key1": { + "checked": "escaped_value" + } + } + }, + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, { "_links": { "children": [], @@ -66,6 +124,12 @@ }, "_links": { "items": [ + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, { "href": "escaped_value" }, diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_noderesponsive_layouts__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_noderesponsive_layouts__1.json index 488bbf0f70..40133d4821 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_noderesponsive_layouts__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_noderesponsive_layouts__1.json @@ -1,6 +1,76 @@ { "_embedded": { "items": [ + { + "_links": { + "children": [], + "contentType": { + "href": "escaped_value" + }, + "parent": { + "href": "escaped_value" + }, + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": { + "items": [ + { + "slot": "escaped_value" + }, + { + "slot": "escaped_value" + }, + { + "slot": "escaped_value" + } + ] + }, + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, + { + "_links": { + "children": [], + "contentType": { + "href": "escaped_value" + }, + "parent": { + "href": "escaped_value" + }, + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": { + "items": [ + { + "slot": "escaped_value" + }, + { + "slot": "escaped_value" + }, + { + "slot": "escaped_value" + } + ] + }, + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, { "_links": { "children": [ @@ -59,6 +129,12 @@ }, "_links": { "items": [ + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, { "href": "escaped_value" } diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_nodes__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_nodes__1.json index 535611f336..e30cc25c26 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_nodes__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_nodes__1.json @@ -1,6 +1,64 @@ { "_embedded": { "items": [ + { + "_embedded": { + "materialItems": [] + }, + "_links": { + "children": [], + "contentType": { + "href": "escaped_value" + }, + "materialItems": { + "href": "escaped_value" + }, + "parent": { + "href": "escaped_value" + }, + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": "escaped_value", + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, + { + "_embedded": { + "materialItems": [] + }, + "_links": { + "children": [], + "contentType": { + "href": "escaped_value" + }, + "materialItems": { + "href": "escaped_value" + }, + "parent": { + "href": "escaped_value" + }, + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": "escaped_value", + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, { "_embedded": { "materialItems": [] @@ -133,6 +191,58 @@ "position": "escaped_value", "slot": "escaped_value" }, + { + "_links": { + "checklistItems": { + "href": "escaped_value" + }, + "children": [], + "contentType": { + "href": "escaped_value" + }, + "parent": { + "href": "escaped_value" + }, + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": "escaped_value", + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, + { + "_links": { + "checklistItems": { + "href": "escaped_value" + }, + "children": [], + "contentType": { + "href": "escaped_value" + }, + "parent": { + "href": "escaped_value" + }, + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": "escaped_value", + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, { "_links": { "children": [], @@ -321,7 +431,12 @@ }, "contentTypeName": "escaped_value", "data": { - "html": "escaped_value" + "columns": [ + { + "slot": "escaped_value", + "width": "escaped_value" + } + ] }, "id": "escaped_value", "instanceName": "escaped_value", @@ -346,7 +461,12 @@ }, "contentTypeName": "escaped_value", "data": { - "html": "escaped_value" + "columns": [ + { + "slot": "escaped_value", + "width": "escaped_value" + } + ] }, "id": "escaped_value", "instanceName": "escaped_value", @@ -396,14 +516,7 @@ }, "contentTypeName": "escaped_value", "data": { - "options": { - "key1": { - "checked": "escaped_value" - }, - "key2": { - "checked": "escaped_value" - } - } + "html": "escaped_value" }, "id": "escaped_value", "instanceName": "escaped_value", @@ -428,11 +541,7 @@ }, "contentTypeName": "escaped_value", "data": { - "options": { - "key1": { - "checked": "escaped_value" - } - } + "html": "escaped_value" }, "id": "escaped_value", "instanceName": "escaped_value", @@ -457,20 +566,7 @@ }, "contentTypeName": "escaped_value", "data": { - "sections": { - "ab9740f6-61a4-4cae-b574-a73aeb7c5ea0": { - "column1": "escaped_value", - "column2Html": "escaped_value", - "column3": "escaped_value", - "position": "escaped_value" - }, - "cb26d76a-9e3b-43f0-a7c4-0f2ad0ea8029": { - "column1": "escaped_value", - "column2Html": "escaped_value", - "column3": "escaped_value", - "position": "escaped_value" - } - } + "html": "escaped_value" }, "id": "escaped_value", "instanceName": "escaped_value", @@ -495,14 +591,7 @@ }, "contentTypeName": "escaped_value", "data": { - "sections": { - "ea6f1e5d-0ef7-454f-8ae0-1d9b3bfd1177": { - "column1": "escaped_value", - "column2Html": "escaped_value", - "column3": "escaped_value", - "position": "escaped_value" - } - } + "html": "escaped_value" }, "id": "escaped_value", "instanceName": "escaped_value", @@ -511,26 +600,7 @@ }, { "_links": { - "children": [ - { - "href": "escaped_value" - }, - { - "href": "escaped_value" - }, - { - "href": "escaped_value" - }, - { - "href": "escaped_value" - }, - { - "href": "escaped_value" - }, - { - "href": "escaped_value" - } - ], + "children": [], "contentType": { "href": "escaped_value" }, @@ -565,17 +635,7 @@ }, { "_links": { - "children": [ - { - "href": "escaped_value" - }, - { - "href": "escaped_value" - }, - { - "href": "escaped_value" - } - ], + "children": [], "contentType": { "href": "escaped_value" }, @@ -591,14 +651,15 @@ }, "contentTypeName": "escaped_value", "data": { - "columns": [ + "items": [ { - "slot": "escaped_value", - "width": "escaped_value" + "slot": "escaped_value" }, { - "slot": "escaped_value", - "width": "escaped_value" + "slot": "escaped_value" + }, + { + "slot": "escaped_value" } ] }, @@ -609,18 +670,13 @@ }, { "_links": { - "children": [ - { - "href": "escaped_value" - }, - { - "href": "escaped_value" - } - ], + "children": [], "contentType": { "href": "escaped_value" }, - "parent": "escaped_value", + "parent": { + "href": "escaped_value" + }, "root": { "href": "escaped_value" }, @@ -630,12 +686,14 @@ }, "contentTypeName": "escaped_value", "data": { - "columns": [ - { - "slot": "escaped_value", - "width": "escaped_value" + "options": { + "key1": { + "checked": "escaped_value" + }, + "key2": { + "checked": "escaped_value" } - ] + } }, "id": "escaped_value", "instanceName": "escaped_value", @@ -644,15 +702,13 @@ }, { "_links": { - "children": [ - { - "href": "escaped_value" - } - ], + "children": [], "contentType": { "href": "escaped_value" }, - "parent": "escaped_value", + "parent": { + "href": "escaped_value" + }, "root": { "href": "escaped_value" }, @@ -662,12 +718,11 @@ }, "contentTypeName": "escaped_value", "data": { - "columns": [ - { - "slot": "escaped_value", - "width": "escaped_value" + "options": { + "key1": { + "checked": "escaped_value" } - ] + } }, "id": "escaped_value", "instanceName": "escaped_value", @@ -676,11 +731,468 @@ }, { "_links": { - "children": [ - { - "href": "escaped_value" - } - ], + "children": [], + "contentType": { + "href": "escaped_value" + }, + "parent": { + "href": "escaped_value" + }, + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": { + "options": { + "key1": { + "checked": "escaped_value" + } + } + }, + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, + { + "_links": { + "children": [], + "contentType": { + "href": "escaped_value" + }, + "parent": { + "href": "escaped_value" + }, + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": { + "options": { + "key1": { + "checked": "escaped_value" + } + } + }, + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, + { + "_links": { + "children": [], + "contentType": { + "href": "escaped_value" + }, + "parent": { + "href": "escaped_value" + }, + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": { + "sections": { + "aaaaaaaa-3ccd-4d9a-ab40-dddddddddddd": { + "column1": "escaped_value", + "column2Html": "escaped_value", + "column3": "escaped_value", + "position": "escaped_value" + } + } + }, + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, + { + "_links": { + "children": [], + "contentType": { + "href": "escaped_value" + }, + "parent": { + "href": "escaped_value" + }, + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": { + "sections": { + "ab9740f6-61a4-4cae-b574-a73aeb7c5ea0": { + "column1": "escaped_value", + "column2Html": "escaped_value", + "column3": "escaped_value", + "position": "escaped_value" + }, + "cb26d76a-9e3b-43f0-a7c4-0f2ad0ea8029": { + "column1": "escaped_value", + "column2Html": "escaped_value", + "column3": "escaped_value", + "position": "escaped_value" + } + } + }, + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, + { + "_links": { + "children": [], + "contentType": { + "href": "escaped_value" + }, + "parent": { + "href": "escaped_value" + }, + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": { + "sections": { + "cccccccc-3ccd-4d9a-ab40-aaaaaaaaaaaa": { + "column1": "escaped_value", + "column2Html": "escaped_value", + "column3": "escaped_value", + "position": "escaped_value" + } + } + }, + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, + { + "_links": { + "children": [], + "contentType": { + "href": "escaped_value" + }, + "parent": { + "href": "escaped_value" + }, + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": { + "sections": { + "ea6f1e5d-0ef7-454f-8ae0-1d9b3bfd1177": { + "column1": "escaped_value", + "column2Html": "escaped_value", + "column3": "escaped_value", + "position": "escaped_value" + } + } + }, + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, + { + "_links": { + "children": [ + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + } + ], + "contentType": { + "href": "escaped_value" + }, + "parent": "escaped_value", + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": { + "columns": [ + { + "slot": "escaped_value", + "width": "escaped_value" + } + ] + }, + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, + { + "_links": { + "children": [ + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + } + ], + "contentType": { + "href": "escaped_value" + }, + "parent": "escaped_value", + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": { + "columns": [ + { + "slot": "escaped_value", + "width": "escaped_value" + } + ] + }, + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, + { + "_links": { + "children": [ + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + } + ], + "contentType": { + "href": "escaped_value" + }, + "parent": { + "href": "escaped_value" + }, + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": { + "items": [ + { + "slot": "escaped_value" + }, + { + "slot": "escaped_value" + }, + { + "slot": "escaped_value" + } + ] + }, + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, + { + "_links": { + "children": [ + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + } + ], + "contentType": { + "href": "escaped_value" + }, + "parent": { + "href": "escaped_value" + }, + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": { + "columns": [ + { + "slot": "escaped_value", + "width": "escaped_value" + }, + { + "slot": "escaped_value", + "width": "escaped_value" + } + ] + }, + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, + { + "_links": { + "children": [ + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + } + ], + "contentType": { + "href": "escaped_value" + }, + "parent": "escaped_value", + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": { + "columns": [ + { + "slot": "escaped_value", + "width": "escaped_value" + } + ] + }, + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, + { + "_links": { + "children": [ + { + "href": "escaped_value" + } + ], + "contentType": { + "href": "escaped_value" + }, + "parent": "escaped_value", + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": { + "columns": [ + { + "slot": "escaped_value", + "width": "escaped_value" + } + ] + }, + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, + { + "_links": { + "children": [ + { + "href": "escaped_value" + } + ], "contentType": { "href": "escaped_value" }, @@ -808,6 +1320,54 @@ { "href": "escaped_value" }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, { "href": "escaped_value" } diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_nodesingle_texts__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_nodesingle_texts__1.json index bf8cec03ae..c4e334ac47 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_nodesingle_texts__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_nodesingle_texts__1.json @@ -51,6 +51,56 @@ "position": "escaped_value", "slot": "escaped_value" }, + { + "_links": { + "children": [], + "contentType": { + "href": "escaped_value" + }, + "parent": { + "href": "escaped_value" + }, + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": { + "html": "escaped_value" + }, + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, + { + "_links": { + "children": [], + "contentType": { + "href": "escaped_value" + }, + "parent": { + "href": "escaped_value" + }, + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": { + "html": "escaped_value" + }, + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, { "_links": { "children": [], @@ -86,6 +136,12 @@ { "href": "escaped_value" }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, { "href": "escaped_value" } diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_nodestoryboards__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_nodestoryboards__1.json index 9277347569..805a2afe5f 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_nodestoryboards__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set content_nodestoryboards__1.json @@ -1,6 +1,38 @@ { "_embedded": { "items": [ + { + "_links": { + "children": [], + "contentType": { + "href": "escaped_value" + }, + "parent": { + "href": "escaped_value" + }, + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": { + "sections": { + "aaaaaaaa-3ccd-4d9a-ab40-dddddddddddd": { + "column1": "escaped_value", + "column2Html": "escaped_value", + "column3": "escaped_value", + "position": "escaped_value" + } + } + }, + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, { "_links": { "children": [], @@ -39,6 +71,38 @@ "position": "escaped_value", "slot": "escaped_value" }, + { + "_links": { + "children": [], + "contentType": { + "href": "escaped_value" + }, + "parent": { + "href": "escaped_value" + }, + "root": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "contentTypeName": "escaped_value", + "data": { + "sections": { + "cccccccc-3ccd-4d9a-ab40-aaaaaaaaaaaa": { + "column1": "escaped_value", + "column2Html": "escaped_value", + "column3": "escaped_value", + "position": "escaped_value" + } + } + }, + "id": "escaped_value", + "instanceName": "escaped_value", + "position": "escaped_value", + "slot": "escaped_value" + }, { "_links": { "children": [], @@ -75,6 +139,12 @@ }, "_links": { "items": [ + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, { "href": "escaped_value" }, diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set day_responsibles__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set day_responsibles__1.json index dbfd2fd22a..a2c8866701 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set day_responsibles__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set day_responsibles__1.json @@ -85,6 +85,20 @@ }, "id": "escaped_value" }, + { + "_links": { + "campCollaboration": { + "href": "escaped_value" + }, + "day": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "id": "escaped_value" + }, { "_links": { "campCollaboration": { @@ -121,6 +135,9 @@ { "href": "escaped_value" }, + { + "href": "escaped_value" + }, { "href": "escaped_value" } diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set days__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set days__1.json index edc951efc6..6cb6866d1b 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set days__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set days__1.json @@ -73,6 +73,54 @@ "number": "escaped_value", "start": "escaped_value" }, + { + "_embedded": { + "dayResponsibles": [] + }, + "_links": { + "dayResponsibles": { + "href": "escaped_value" + }, + "period": { + "href": "escaped_value" + }, + "scheduleEntries": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "dayOffset": "escaped_value", + "end": "escaped_value", + "id": "escaped_value", + "number": "escaped_value", + "start": "escaped_value" + }, + { + "_embedded": { + "dayResponsibles": [] + }, + "_links": { + "dayResponsibles": { + "href": "escaped_value" + }, + "period": { + "href": "escaped_value" + }, + "scheduleEntries": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "dayOffset": "escaped_value", + "end": "escaped_value", + "id": "escaped_value", + "number": "escaped_value", + "start": "escaped_value" + }, { "_embedded": { "dayResponsibles": [ @@ -282,6 +330,45 @@ "number": "escaped_value", "start": "escaped_value" }, + { + "_embedded": { + "dayResponsibles": [ + { + "_links": { + "campCollaboration": { + "href": "escaped_value" + }, + "day": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "id": "escaped_value" + } + ] + }, + "_links": { + "dayResponsibles": { + "href": "escaped_value" + }, + "period": { + "href": "escaped_value" + }, + "scheduleEntries": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "dayOffset": "escaped_value", + "end": "escaped_value", + "id": "escaped_value", + "number": "escaped_value", + "start": "escaped_value" + }, { "_embedded": { "dayResponsibles": [ @@ -349,6 +436,15 @@ { "href": "escaped_value" }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, { "href": "escaped_value" } diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set material_items__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set material_items__1.json index 1223d1b54a..c70f48e62b 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set material_items__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set material_items__1.json @@ -64,6 +64,27 @@ "quantity": "escaped_value", "unit": "escaped_value" }, + { + "_links": { + "camp": { + "href": "escaped_value" + }, + "materialList": { + "href": "escaped_value" + }, + "materialNode": "escaped_value", + "period": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "article": "escaped_value", + "id": "escaped_value", + "quantity": "escaped_value", + "unit": "escaped_value" + }, { "_links": { "camp": { @@ -98,6 +119,9 @@ { "href": "escaped_value" }, + { + "href": "escaped_value" + }, { "href": "escaped_value" } diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set material_lists__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set material_lists__1.json index 2f708a0cc1..e98471a823 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set material_lists__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set material_lists__1.json @@ -69,6 +69,42 @@ "itemCount": "escaped_value", "name": "escaped_value" }, + { + "_links": { + "camp": { + "href": "escaped_value" + }, + "campCollaboration": "escaped_value", + "materialItems": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "id": "escaped_value", + "itemCount": "escaped_value", + "name": "escaped_value" + }, + { + "_links": { + "camp": { + "href": "escaped_value" + }, + "campCollaboration": { + "href": "escaped_value" + }, + "materialItems": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "id": "escaped_value", + "itemCount": "escaped_value", + "name": "escaped_value" + }, { "_links": { "camp": { @@ -126,6 +162,12 @@ { "href": "escaped_value" }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, { "href": "escaped_value" } diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set periods__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set periods__1.json index ab451a3ebc..c5c0d4c6de 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set periods__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set periods__1.json @@ -88,6 +88,35 @@ "id": "escaped_value", "start": "escaped_value" }, + { + "_links": { + "camp": { + "href": "escaped_value" + }, + "contentNodes": { + "href": "escaped_value" + }, + "dayResponsibles": { + "href": "escaped_value" + }, + "days": { + "href": "escaped_value" + }, + "materialItems": { + "href": "escaped_value" + }, + "scheduleEntries": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "description": "escaped_value", + "end": "escaped_value", + "id": "escaped_value", + "start": "escaped_value" + }, { "_links": { "camp": { @@ -130,6 +159,9 @@ { "href": "escaped_value" }, + { + "href": "escaped_value" + }, { "href": "escaped_value" } diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set schedule_entries__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set schedule_entries__1.json index 1702dba6ea..9e752ba15c 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set schedule_entries__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set schedule_entries__1.json @@ -121,6 +121,54 @@ "start": "escaped_value", "width": "escaped_value" }, + { + "_links": { + "activity": { + "href": "escaped_value" + }, + "day": { + "href": "escaped_value" + }, + "period": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "dayNumber": "escaped_value", + "end": "escaped_value", + "id": "escaped_value", + "left": "escaped_value", + "number": "escaped_value", + "scheduleEntryNumber": "escaped_value", + "start": "escaped_value", + "width": "escaped_value" + }, + { + "_links": { + "activity": { + "href": "escaped_value" + }, + "day": { + "href": "escaped_value" + }, + "period": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "dayNumber": "escaped_value", + "end": "escaped_value", + "id": "escaped_value", + "left": "escaped_value", + "number": "escaped_value", + "scheduleEntryNumber": "escaped_value", + "start": "escaped_value", + "width": "escaped_value" + }, { "_links": { "activity": { @@ -164,6 +212,12 @@ { "href": "escaped_value" }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, { "href": "escaped_value" } diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set camps__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set camps__1.json index c70a471535..55aa547072 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set camps__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set camps__1.json @@ -422,7 +422,8 @@ }, "self": { "href": "escaped_value" - } + }, + "sharedBy": "escaped_value" }, "addressCity": "escaped_value", "addressName": "escaped_value", @@ -433,10 +434,12 @@ "courseNumber": "escaped_value", "id": "escaped_value", "isPrototype": "escaped_value", + "isShared": "escaped_value", "kind": "escaped_value", "motto": "escaped_value", "organizer": "escaped_value", "printYSLogoOnPicasso": "escaped_value", + "sharedSince": "escaped_value", "shortTitle": "escaped_value", "title": "escaped_value", "trainingAdvisorName": "escaped_value" diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set periods__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set periods__1.json index 6928d2306b..d71f38fc9a 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set periods__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set periods__1.json @@ -31,7 +31,8 @@ }, "self": { "href": "escaped_value" - } + }, + "sharedBy": "escaped_value" }, "addressCity": "escaped_value", "addressName": "escaped_value", @@ -42,10 +43,12 @@ "courseNumber": "escaped_value", "id": "escaped_value", "isPrototype": "escaped_value", + "isShared": "escaped_value", "kind": "escaped_value", "motto": "escaped_value", "organizer": "escaped_value", "printYSLogoOnPicasso": "escaped_value", + "sharedSince": "escaped_value", "shortTitle": "escaped_value", "title": "escaped_value", "trainingAdvisorName": "escaped_value" diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testOpenApiSpecMatchesSnapshot__1.yml b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testOpenApiSpecMatchesSnapshot__1.yml index 2efd81fd0f..f139a6d1ab 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testOpenApiSpecMatchesSnapshot__1.yml +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testOpenApiSpecMatchesSnapshot__1.yml @@ -2496,6 +2496,14 @@ components: example: true readOnly: true type: boolean + isShared: + default: false + description: |- + Whether the programme of this camp is publicly available to anyone (including + personal data such as camp collaborations, personal material lists, + responsibilities and comments). + example: true + type: boolean kind: description: 'Rough categorization of the camp (house, tent, traveling, summer, autumn).' example: Zeltlager @@ -2560,6 +2568,21 @@ components: format: iri-reference readOnly: true type: string + sharedBy: + description: 'The person who last set the camp to be shared publicly.' + example: 'https://example.com/' + format: iri-reference + readOnly: true + type: + - 'null' + - string + sharedSince: + description: 'Date and time when the camp was last set to be shared publicly.' + example: '2025-10-01T00:00:00+00:00' + format: date-time + type: + - 'null' + - string shortTitle: description: |- An optional short title for the camp. Can be used in the UI where space is tight. If @@ -2698,6 +2721,14 @@ components: example: true readOnly: true type: boolean + isShared: + default: false + description: |- + Whether the programme of this camp is publicly available to anyone (including + personal data such as camp collaborations, personal material lists, + responsibilities and comments). + example: true + type: boolean kind: description: 'Rough categorization of the camp (house, tent, traveling, summer, autumn).' example: Zeltlager @@ -2755,6 +2786,21 @@ components: format: iri-reference readOnly: true type: string + sharedBy: + description: 'The person who last set the camp to be shared publicly.' + example: 'https://example.com/' + format: iri-reference + readOnly: true + type: + - 'null' + - string + sharedSince: + description: 'Date and time when the camp was last set to be shared publicly.' + example: '2025-10-01T00:00:00+00:00' + format: date-time + type: + - 'null' + - string shortTitle: description: |- An optional short title for the camp. Can be used in the UI where space is tight. If @@ -2889,6 +2935,14 @@ components: example: true readOnly: true type: boolean + isShared: + default: false + description: |- + Whether the programme of this camp is publicly available to anyone (including + personal data such as camp collaborations, personal material lists, + responsibilities and comments). + example: true + type: boolean kind: description: 'Rough categorization of the camp (house, tent, traveling, summer, autumn).' example: Zeltlager @@ -2953,6 +3007,21 @@ components: format: iri-reference readOnly: true type: string + sharedBy: + description: 'The person who last set the camp to be shared publicly.' + example: 'https://example.com/' + format: iri-reference + readOnly: true + type: + - 'null' + - string + sharedSince: + description: 'Date and time when the camp was last set to be shared publicly.' + example: '2025-10-01T00:00:00+00:00' + format: date-time + type: + - 'null' + - string shortTitle: description: |- An optional short title for the camp. Can be used in the UI where space is tight. If @@ -3087,6 +3156,14 @@ components: example: true readOnly: true type: boolean + isShared: + default: false + description: |- + Whether the programme of this camp is publicly available to anyone (including + personal data such as camp collaborations, personal material lists, + responsibilities and comments). + example: true + type: boolean kind: description: 'Rough categorization of the camp (house, tent, traveling, summer, autumn).' example: Zeltlager @@ -3151,6 +3228,21 @@ components: format: iri-reference readOnly: true type: string + sharedBy: + description: 'The person who last set the camp to be shared publicly.' + example: 'https://example.com/' + format: iri-reference + readOnly: true + type: + - 'null' + - string + sharedSince: + description: 'Date and time when the camp was last set to be shared publicly.' + example: '2025-10-01T00:00:00+00:00' + format: date-time + type: + - 'null' + - string shortTitle: description: |- An optional short title for the camp. Can be used in the UI where space is tight. If @@ -3249,6 +3341,14 @@ components: type: - 'null' - string + isShared: + default: false + description: |- + Whether the programme of this camp is publicly available to anyone (including + personal data such as camp collaborations, personal material lists, + responsibilities and comments). + example: true + type: boolean kind: description: 'Rough categorization of the camp (house, tent, traveling, summer, autumn).' example: Zeltlager @@ -3370,6 +3470,14 @@ components: type: - 'null' - string + isShared: + default: false + description: |- + Whether the programme of this camp is publicly available to anyone (including + personal data such as camp collaborations, personal material lists, + responsibilities and comments). + example: true + type: boolean kind: description: 'Rough categorization of the camp (house, tent, traveling, summer, autumn).' example: Zeltlager @@ -3489,6 +3597,14 @@ components: example: true readOnly: true type: boolean + isShared: + default: false + description: |- + Whether the programme of this camp is publicly available to anyone (including + personal data such as camp collaborations, personal material lists, + responsibilities and comments). + example: true + type: boolean kind: description: 'Rough categorization of the camp (house, tent, traveling, summer, autumn).' example: Zeltlager @@ -3509,6 +3625,12 @@ components: description: 'Whether the Y+S logo should be printed on the picasso of this camp.' example: true type: boolean + sharedSince: + description: 'Date and time when the camp was last set to be shared publicly.' + example: '2025-10-01T00:00:00+00:00' + format: date-time + readOnly: true + type: ['null', string] shortTitle: description: |- An optional short title for the camp. Can be used in the UI where space is tight. If @@ -3553,6 +3675,8 @@ components: properties: { data: { items: { properties: { id: { format: iri-reference, type: string }, type: { type: string } }, type: object }, type: array } } progressLabels: properties: { data: { items: { properties: { id: { format: iri-reference, type: string }, type: { type: string } }, type: object }, type: array } } + sharedBy: + properties: { data: { properties: { id: { format: iri-reference, type: string }, type: { type: string } }, type: object } } required: - activities - campCollaborations @@ -3590,6 +3714,8 @@ components: $ref: '#/components/schemas/CampCollaboration.jsonapi' - $ref: '#/components/schemas/CampCollaboration.jsonapi' + - + $ref: '#/components/schemas/CampCollaboration.jsonapi' readOnly: true type: array periods: @@ -3717,6 +3843,14 @@ components: example: true readOnly: true type: boolean + isShared: + default: false + description: |- + Whether the programme of this camp is publicly available to anyone (including + personal data such as camp collaborations, personal material lists, + responsibilities and comments). + example: true + type: boolean kind: description: 'Rough categorization of the camp (house, tent, traveling, summer, autumn).' example: Zeltlager @@ -3781,6 +3915,21 @@ components: format: iri-reference readOnly: true type: string + sharedBy: + description: 'The person who last set the camp to be shared publicly.' + example: 'https://example.com/' + format: iri-reference + readOnly: true + type: + - 'null' + - string + sharedSince: + description: 'Date and time when the camp was last set to be shared publicly.' + example: '2025-10-01T00:00:00+00:00' + format: date-time + type: + - 'null' + - string shortTitle: description: |- An optional short title for the camp. Can be used in the UI where space is tight. If @@ -3928,6 +4077,14 @@ components: example: true readOnly: true type: boolean + isShared: + default: false + description: |- + Whether the programme of this camp is publicly available to anyone (including + personal data such as camp collaborations, personal material lists, + responsibilities and comments). + example: true + type: boolean kind: description: 'Rough categorization of the camp (house, tent, traveling, summer, autumn).' example: Zeltlager @@ -3985,6 +4142,21 @@ components: format: iri-reference readOnly: true type: string + sharedBy: + description: 'The person who last set the camp to be shared publicly.' + example: 'https://example.com/' + format: iri-reference + readOnly: true + type: + - 'null' + - string + sharedSince: + description: 'Date and time when the camp was last set to be shared publicly.' + example: '2025-10-01T00:00:00+00:00' + format: date-time + type: + - 'null' + - string shortTitle: description: |- An optional short title for the camp. Can be used in the UI where space is tight. If @@ -4128,6 +4300,14 @@ components: example: true readOnly: true type: boolean + isShared: + default: false + description: |- + Whether the programme of this camp is publicly available to anyone (including + personal data such as camp collaborations, personal material lists, + responsibilities and comments). + example: true + type: boolean kind: description: 'Rough categorization of the camp (house, tent, traveling, summer, autumn).' example: Zeltlager @@ -4192,6 +4372,21 @@ components: format: iri-reference readOnly: true type: string + sharedBy: + description: 'The person who last set the camp to be shared publicly.' + example: 'https://example.com/' + format: iri-reference + readOnly: true + type: + - 'null' + - string + sharedSince: + description: 'Date and time when the camp was last set to be shared publicly.' + example: '2025-10-01T00:00:00+00:00' + format: date-time + type: + - 'null' + - string shortTitle: description: |- An optional short title for the camp. Can be used in the UI where space is tight. If @@ -4335,6 +4530,14 @@ components: example: true readOnly: true type: boolean + isShared: + default: false + description: |- + Whether the programme of this camp is publicly available to anyone (including + personal data such as camp collaborations, personal material lists, + responsibilities and comments). + example: true + type: boolean kind: description: 'Rough categorization of the camp (house, tent, traveling, summer, autumn).' example: Zeltlager @@ -4399,6 +4602,21 @@ components: format: iri-reference readOnly: true type: string + sharedBy: + description: 'The person who last set the camp to be shared publicly.' + example: 'https://example.com/' + format: iri-reference + readOnly: true + type: + - 'null' + - string + sharedSince: + description: 'Date and time when the camp was last set to be shared publicly.' + example: '2025-10-01T00:00:00+00:00' + format: date-time + type: + - 'null' + - string shortTitle: description: |- An optional short title for the camp. Can be used in the UI where space is tight. If @@ -4506,6 +4724,14 @@ components: type: - 'null' - string + isShared: + default: false + description: |- + Whether the programme of this camp is publicly available to anyone (including + personal data such as camp collaborations, personal material lists, + responsibilities and comments). + example: true + type: boolean kind: description: 'Rough categorization of the camp (house, tent, traveling, summer, autumn).' example: Zeltlager @@ -4696,6 +4922,14 @@ components: example: true readOnly: true type: boolean + isShared: + default: false + description: |- + Whether the programme of this camp is publicly available to anyone (including + personal data such as camp collaborations, personal material lists, + responsibilities and comments). + example: true + type: boolean kind: description: 'Rough categorization of the camp (house, tent, traveling, summer, autumn).' example: Zeltlager @@ -4760,6 +4994,21 @@ components: format: iri-reference readOnly: true type: string + sharedBy: + description: 'The person who last set the camp to be shared publicly.' + example: 'https://example.com/' + format: iri-reference + readOnly: true + type: + - 'null' + - string + sharedSince: + description: 'Date and time when the camp was last set to be shared publicly.' + example: '2025-10-01T00:00:00+00:00' + format: date-time + type: + - 'null' + - string shortTitle: description: |- An optional short title for the camp. Can be used in the UI where space is tight. If @@ -4921,6 +5170,14 @@ components: example: true readOnly: true type: boolean + isShared: + default: false + description: |- + Whether the programme of this camp is publicly available to anyone (including + personal data such as camp collaborations, personal material lists, + responsibilities and comments). + example: true + type: boolean kind: description: 'Rough categorization of the camp (house, tent, traveling, summer, autumn).' example: Zeltlager @@ -4978,6 +5235,21 @@ components: format: iri-reference readOnly: true type: string + sharedBy: + description: 'The person who last set the camp to be shared publicly.' + example: 'https://example.com/' + format: iri-reference + readOnly: true + type: + - 'null' + - string + sharedSince: + description: 'Date and time when the camp was last set to be shared publicly.' + example: '2025-10-01T00:00:00+00:00' + format: date-time + type: + - 'null' + - string shortTitle: description: |- An optional short title for the camp. Can be used in the UI where space is tight. If @@ -5135,6 +5407,14 @@ components: example: true readOnly: true type: boolean + isShared: + default: false + description: |- + Whether the programme of this camp is publicly available to anyone (including + personal data such as camp collaborations, personal material lists, + responsibilities and comments). + example: true + type: boolean kind: description: 'Rough categorization of the camp (house, tent, traveling, summer, autumn).' example: Zeltlager @@ -5199,6 +5479,21 @@ components: format: iri-reference readOnly: true type: string + sharedBy: + description: 'The person who last set the camp to be shared publicly.' + example: 'https://example.com/' + format: iri-reference + readOnly: true + type: + - 'null' + - string + sharedSince: + description: 'Date and time when the camp was last set to be shared publicly.' + example: '2025-10-01T00:00:00+00:00' + format: date-time + type: + - 'null' + - string shortTitle: description: |- An optional short title for the camp. Can be used in the UI where space is tight. If @@ -5356,6 +5651,14 @@ components: example: true readOnly: true type: boolean + isShared: + default: false + description: |- + Whether the programme of this camp is publicly available to anyone (including + personal data such as camp collaborations, personal material lists, + responsibilities and comments). + example: true + type: boolean kind: description: 'Rough categorization of the camp (house, tent, traveling, summer, autumn).' example: Zeltlager @@ -5420,6 +5723,21 @@ components: format: iri-reference readOnly: true type: string + sharedBy: + description: 'The person who last set the camp to be shared publicly.' + example: 'https://example.com/' + format: iri-reference + readOnly: true + type: + - 'null' + - string + sharedSince: + description: 'Date and time when the camp was last set to be shared publicly.' + example: '2025-10-01T00:00:00+00:00' + format: date-time + type: + - 'null' + - string shortTitle: description: |- An optional short title for the camp. Can be used in the UI where space is tight. If @@ -5518,6 +5836,14 @@ components: type: - 'null' - string + isShared: + default: false + description: |- + Whether the programme of this camp is publicly available to anyone (including + personal data such as camp collaborations, personal material lists, + responsibilities and comments). + example: true + type: boolean kind: description: 'Rough categorization of the camp (house, tent, traveling, summer, autumn).' example: Zeltlager @@ -25372,6 +25698,18 @@ paths: description: 'Retrieves the collection of Camp resources.' operationId: api_camps_get_collection parameters: + - + allowEmptyValue: false + allowReserved: false + deprecated: false + description: '' + explode: false + in: query + name: campCollaborator + required: false + schema: + type: string + style: form - allowEmptyValue: false allowReserved: false @@ -32999,6 +33337,18 @@ paths: schema: type: string style: form + - + allowEmptyValue: false + allowReserved: false + deprecated: false + description: '' + explode: false + in: query + name: campCollaborator + required: false + schema: + type: string + style: form - allowEmptyValue: false allowReserved: false diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testRootEndpointMatchesSnapshot__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testRootEndpointMatchesSnapshot__1.json index 5e13e3838c..aa2504bb6a 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testRootEndpointMatchesSnapshot__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testRootEndpointMatchesSnapshot__1.json @@ -17,7 +17,7 @@ "templated": true }, "camps": { - "href": "/camps{/id}{?isPrototype,isPrototype[]}", + "href": "/camps{/id}{?isPrototype,isPrototype[],campCollaborator}", "templated": true }, "categories": { @@ -100,7 +100,7 @@ "templated": true }, "periods": { - "href": "/periods{/id}{?camp,camp[]}", + "href": "/periods{/id}{?camp,camp[],campCollaborator}", "templated": true }, "personalInvitations": { diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testSubResourceUrlMatchesSnapshot with data set camps_campId_camp_collaborations__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testSubResourceUrlMatchesSnapshot with data set camps_campId_camp_collaborations__1.json index 1ca97abbb8..39ad25ab23 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testSubResourceUrlMatchesSnapshot with data set camps_campId_camp_collaborations__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testSubResourceUrlMatchesSnapshot with data set camps_campId_camp_collaborations__1.json @@ -34,7 +34,8 @@ }, "self": { "href": "escaped_value" - } + }, + "sharedBy": "escaped_value" }, "addressCity": "escaped_value", "addressName": "escaped_value", @@ -45,10 +46,12 @@ "courseNumber": "escaped_value", "id": "escaped_value", "isPrototype": "escaped_value", + "isShared": "escaped_value", "kind": "escaped_value", "motto": "escaped_value", "organizer": "escaped_value", "printYSLogoOnPicasso": "escaped_value", + "sharedSince": "escaped_value", "shortTitle": "escaped_value", "title": "escaped_value", "trainingAdvisorName": "escaped_value" @@ -104,7 +107,8 @@ }, "self": { "href": "escaped_value" - } + }, + "sharedBy": "escaped_value" }, "addressCity": "escaped_value", "addressName": "escaped_value", @@ -115,10 +119,12 @@ "courseNumber": "escaped_value", "id": "escaped_value", "isPrototype": "escaped_value", + "isShared": "escaped_value", "kind": "escaped_value", "motto": "escaped_value", "organizer": "escaped_value", "printYSLogoOnPicasso": "escaped_value", + "sharedSince": "escaped_value", "shortTitle": "escaped_value", "title": "escaped_value", "trainingAdvisorName": "escaped_value" @@ -189,7 +195,8 @@ }, "self": { "href": "escaped_value" - } + }, + "sharedBy": "escaped_value" }, "addressCity": "escaped_value", "addressName": "escaped_value", @@ -200,10 +207,12 @@ "courseNumber": "escaped_value", "id": "escaped_value", "isPrototype": "escaped_value", + "isShared": "escaped_value", "kind": "escaped_value", "motto": "escaped_value", "organizer": "escaped_value", "printYSLogoOnPicasso": "escaped_value", + "sharedSince": "escaped_value", "shortTitle": "escaped_value", "title": "escaped_value", "trainingAdvisorName": "escaped_value" @@ -274,7 +283,8 @@ }, "self": { "href": "escaped_value" - } + }, + "sharedBy": "escaped_value" }, "addressCity": "escaped_value", "addressName": "escaped_value", @@ -285,10 +295,12 @@ "courseNumber": "escaped_value", "id": "escaped_value", "isPrototype": "escaped_value", + "isShared": "escaped_value", "kind": "escaped_value", "motto": "escaped_value", "organizer": "escaped_value", "printYSLogoOnPicasso": "escaped_value", + "sharedSince": "escaped_value", "shortTitle": "escaped_value", "title": "escaped_value", "trainingAdvisorName": "escaped_value" @@ -359,7 +371,8 @@ }, "self": { "href": "escaped_value" - } + }, + "sharedBy": "escaped_value" }, "addressCity": "escaped_value", "addressName": "escaped_value", @@ -370,10 +383,12 @@ "courseNumber": "escaped_value", "id": "escaped_value", "isPrototype": "escaped_value", + "isShared": "escaped_value", "kind": "escaped_value", "motto": "escaped_value", "organizer": "escaped_value", "printYSLogoOnPicasso": "escaped_value", + "sharedSince": "escaped_value", "shortTitle": "escaped_value", "title": "escaped_value", "trainingAdvisorName": "escaped_value" @@ -444,7 +459,8 @@ }, "self": { "href": "escaped_value" - } + }, + "sharedBy": "escaped_value" }, "addressCity": "escaped_value", "addressName": "escaped_value", @@ -455,10 +471,12 @@ "courseNumber": "escaped_value", "id": "escaped_value", "isPrototype": "escaped_value", + "isShared": "escaped_value", "kind": "escaped_value", "motto": "escaped_value", "organizer": "escaped_value", "printYSLogoOnPicasso": "escaped_value", + "sharedSince": "escaped_value", "shortTitle": "escaped_value", "title": "escaped_value", "trainingAdvisorName": "escaped_value" diff --git a/api/tests/Api/SnapshotTests/__snapshots__/test_EndpointPerformanceTest__testPerformanceDidNotChangeForStableEndpoints__1.yml b/api/tests/Api/SnapshotTests/__snapshots__/test_EndpointPerformanceTest__testPerformanceDidNotChangeForStableEndpoints__1.yml index b38bfc80be..74c9029712 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/test_EndpointPerformanceTest__testPerformanceDidNotChangeForStableEndpoints__1.yml +++ b/api/tests/Api/SnapshotTests/__snapshots__/test_EndpointPerformanceTest__testPerformanceDidNotChangeForStableEndpoints__1.yml @@ -1,12 +1,12 @@ -/activities: 17 +/activities: 20 /activities/item: 35 /activity_progress_labels: 6 /activity_progress_labels/item: 7 /activity_responsibles: 6 /activity_responsibles/item: 8 -/camps: 15 +/camps: 18 /camps/item: 19 -/camp_collaborations: 16 +/camp_collaborations: 19 /camp_collaborations/item: 7 /categories: 6 /categories/item: 11 @@ -18,19 +18,19 @@ /comments/item: 7 /content_types: 6 /content_types/item: 6 -/days: 26 +/days: 32 /days/item: 12 /day_responsibles: 6 /day_responsibles/item: 9 /material_items: 7 /material_items/item: 8 -/material_lists: 7 +/material_lists: 8 /material_lists/item: 7 /periods: 6 /periods/item: 12 /profiles: 6 /profiles/item: 6 -/schedule_entries: 23 +/schedule_entries: 28 /schedule_entries/item: 17 /users/item: 6 '/activities?camp=': 11 diff --git a/api/tests/Doctrine/Filter/CampCollaboratorFilterTest.php b/api/tests/Doctrine/Filter/CampCollaboratorFilterTest.php new file mode 100644 index 0000000000..b9094ff512 --- /dev/null +++ b/api/tests/Doctrine/Filter/CampCollaboratorFilterTest.php @@ -0,0 +1,155 @@ +managerRegistryMock = $this->createMock(ManagerRegistry::class); + $entityManagerMock = $this->createMock(EntityManager::class); + $this->queryBuilderMock = $this->createMock(QueryBuilder::class); + $this->queryNameGeneratorInterfaceMock = $this->createMock(QueryNameGeneratorInterface::class); + $this->iriConverterMock = $this->createMock(IriConverterInterface::class); + + $entityManagerMock + ->method('createQueryBuilder') + ->willReturn($this->queryBuilderMock) + ; + + $this->queryBuilderMock + ->method('select')->willReturnSelf() + ; + + $this->queryBuilderMock + ->method('from')->willReturnSelf() + ; + + $this->queryBuilderMock + ->method('join')->willReturnSelf() + ; + + $this->queryBuilderMock + ->method('andWhere')->willReturnSelf() + ; + + $this->queryBuilderMock + ->method('getRootAliases') + ->willReturn(['o']) + ; + + $this->queryBuilderMock + ->method('getDQLPart') + ->willReturn([]) + ; + + $this->queryBuilderMock + ->method('getEntityManager') + ->willReturn($entityManagerMock) + ; + + $this->queryNameGeneratorInterfaceMock + ->method('generateJoinAlias') + ->willReturnCallback(fn (string $field): string => $field.'_j1') + ; + } + + public function testGetDescription() { + // given + $filter = new CampCollaboratorFilter($this->iriConverterMock, $this->managerRegistryMock); + + // when + $description = $filter->getDescription('Dummy'); + + // then + $this->assertEquals([ + 'campCollaborator' => [ + 'property' => 'campCollaborator', + 'type' => 'string', + 'required' => false, + ], + ], $description); + } + + public function testFailsForResouceClassNotImplementingBelongsToCampInterface() { + // given + $filter = new CampCollaboratorFilter($this->iriConverterMock, $this->managerRegistryMock); + + // then + $this->expectException(\Exception::class); + $this->expectExceptionMessage('CampCollaboratorFilter can only be applied to entities which implement BelongsToCampInterface (received: App\Entity\User).'); + + // when + $filter->apply($this->queryBuilderMock, $this->queryNameGeneratorInterfaceMock, User::class, null, ['filters' => [ + 'campCollaborator' => '/users/123', + ]]); + } + + public function testDoesNothingForPropertiesOtherThanCampCollaborator() { + // given + $filter = new CampCollaboratorFilter($this->iriConverterMock, $this->managerRegistryMock); + + // then + $this->queryBuilderMock + ->expects($this->never()) + ->method('getRootAliases') + ; + + $this->queryBuilderMock + ->expects($this->never()) + ->method('join') + ; + + $this->queryBuilderMock + ->expects($this->never()) + ->method('andWhere') + ; + + // when + $filter->apply($this->queryBuilderMock, $this->queryNameGeneratorInterfaceMock, ContentNode::class, null, ['filters' => [ + 'dummyProperty' => 'abc', + ]]); + } + + public function testAddsFilterForPeriodProperty() { + // given + $filter = new CampCollaboratorFilter($this->iriConverterMock, $this->managerRegistryMock); + $collaborator = new User(); + + // then + $this->iriConverterMock + ->expects($this->once()) + ->method('getResourceFromIri') + ->with('/users/123') + ->willReturn($collaborator) + ; + + $this->queryBuilderMock + ->expects($this->exactly(2)) + ->method('setParameter') + ; + + // when + $filter->apply($this->queryBuilderMock, $this->queryNameGeneratorInterfaceMock, ContentNode::class, null, ['filters' => [ + 'campCollaborator' => '/users/123', + ]]); + } +} diff --git a/api/tests/Security/Voter/CampIsSharedVoterTest.php b/api/tests/Security/Voter/CampIsSharedVoterTest.php new file mode 100644 index 0000000000..0bc55306cf --- /dev/null +++ b/api/tests/Security/Voter/CampIsSharedVoterTest.php @@ -0,0 +1,147 @@ +token = $this->createMock(TokenInterface::class); + $this->em = $this->createMock(EntityManagerInterface::class); + $this->responseTagger = $this->createMock(ResponseTagger::class); + $this->voter = new CampIsSharedVoter($this->em, $this->responseTagger); + } + + public function testDoesntVoteWhenAttributeWrong() { + // given + + // when + $result = $this->voter->vote($this->token, new Period(), ['CAMP_IS_SOMETHING_ELSE']); + + // then + $this->assertSame(VoterInterface::ACCESS_ABSTAIN, $result); + } + + public function testDoesntVoteWhenSubjectDoesNotBelongToCamp() { + // given + + // when + $result = $this->voter->vote($this->token, new CampIsSharedVoterTestDummy(), ['CAMP_IS_SHARED']); + + // then + $this->assertSame(VoterInterface::ACCESS_ABSTAIN, $result); + } + + public function testDoesntVoteWhenSubjectIsNull() { + // given + + // when + $result = $this->voter->vote($this->token, null, ['CAMP_IS_SHARED']); + + // then + $this->assertSame(VoterInterface::ACCESS_ABSTAIN, $result); + } + + public function testDeniesAccessWhenGetCampYieldsNull() { + // given + $this->token->method('getUser')->willReturn(new User()); + $subject = $this->createMock(Period::class); + $subject->method('getCamp')->willReturn(null); + + // when + $result = $this->voter->vote($this->token, $subject, ['CAMP_IS_SHARED']); + + // then + $this->assertSame(VoterInterface::ACCESS_DENIED, $result); + } + + public function testDeniesAccessWhenCampIsntShared() { + // given + $user = $this->createMock(User::class); + $user->method('getId')->willReturn('idFromTest'); + $this->token->method('getUser')->willReturn($user); + $camp = new Camp(); + $camp->isShared = false; + $subject = $this->createMock(Period::class); + $subject->method('getCamp')->willReturn($camp); + + // when + $result = $this->voter->vote($this->token, $subject, ['CAMP_IS_SHARED']); + + // then + $this->assertSame(VoterInterface::ACCESS_DENIED, $result); + } + + public function testGrantsAccessViaBelongsToCampInterface() { + // given + $user = $this->createMock(User::class); + $user->method('getId')->willReturn('idFromTest'); + $this->token->method('getUser')->willReturn($user); + $camp = new Camp(); + $camp->isShared = true; + $subject = $this->createMock(Period::class); + $subject->method('getCamp')->willReturn($camp); + + $this->responseTagger->expects($this->once())->method('addTags')->with([$camp->getId()]); + + // when + $result = $this->voter->vote($this->token, $subject, ['CAMP_IS_SHARED']); + + // then + $this->assertSame(VoterInterface::ACCESS_GRANTED, $result); + } + + public function testGrantsAccessViaBelongsToContentNodeTreeInterface() { + // given + $user = $this->createMock(User::class); + $user->method('getId')->willReturn('idFromTest'); + $this->token->method('getUser')->willReturn($user); + $camp = new Camp(); + $camp->isShared = true; + $activity = $this->createMock(Activity::class); + $activity->method('getCamp')->willReturn($camp); + $root = $this->createMock(ColumnLayout::class); + $subject = $this->createMock(ContentNodeTreeDummy3::class); + $subject->method('getRoot')->willReturn($root); + $repository = $this->createMock(EntityRepository::class); + $this->em->method('getRepository')->willReturn($repository); + $repository->method('findOneBy')->willReturn($activity); + + // when + $result = $this->voter->vote($this->token, $subject, ['CAMP_IS_SHARED']); + + // then + $this->assertSame(VoterInterface::ACCESS_GRANTED, $result); + } +} + +class CampIsSharedVoterTestDummy extends BaseEntity {} + +class ContentNodeTreeDummy3 implements BelongsToContentNodeTreeInterface { + public function getRoot(): ?ColumnLayout { + return null; + } +} diff --git a/common/helpers/__tests__/dateHelperUTCFormatted.spec.js b/common/helpers/__tests__/dateHelperUTCFormatted.spec.js index 7d645be2b3..ea937383fe 100644 --- a/common/helpers/__tests__/dateHelperUTCFormatted.spec.js +++ b/common/helpers/__tests__/dateHelperUTCFormatted.spec.js @@ -1,6 +1,10 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from 'vitest' import { + dateLong, dateRange, + dateShort, + hourLong, + hourShort, rangeLongEnd, rangeShort, timeDurationShort, @@ -95,3 +99,15 @@ describe('dateRange', () => { ).toEqual('Tu 1.1. - We 01/02/2019') }) }) + +describe.each([ + ['dateShort', dateShort, 'Tu 1.1.'], + ['dateLong', dateLong, 'Tu 01/01/2019'], + ['hourShort', hourShort, '20:00'], + ['hourLong', hourLong, '20:00']])('%s', (name, func, expected) => { + it('should format dateTime', () => { + expect( + func('2019-01-01T20:00:00.000Z', tcMock) + ).toEqual(expected) + }) +}) diff --git a/common/helpers/__tests__/userDisplayName.spec.js b/common/helpers/__tests__/userDisplayName.spec.js index 72a7a429c4..6f7e736966 100644 --- a/common/helpers/__tests__/userDisplayName.spec.js +++ b/common/helpers/__tests__/userDisplayName.spec.js @@ -7,6 +7,8 @@ describe('userDisplayName', () => { [{ displayName: 'test' }, 'test'], [{ displayName: 'test', _meta: {} }, 'test'], [{ _meta: { loading: true } }, ''], + [null, ''], + [undefined, ''], ])('maps %p to %p', (input, expected) => { expect(userDisplayName(input)).toEqual(expected) }) diff --git a/common/helpers/userDisplayName.js b/common/helpers/userDisplayName.js index 7f44be53ce..ed064a0fd1 100644 --- a/common/helpers/userDisplayName.js +++ b/common/helpers/userDisplayName.js @@ -2,5 +2,5 @@ * Returns a display name for a user */ export default function (user) { - return user.displayName || '' + return user?.displayName || '' } diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 1677c7d7de..71fd3afb85 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -9,7 +9,9 @@ - + + +

{{ $tc('global.info.offline.title') }} {{ $tc('global.info.offline.description') }} @@ -149,7 +151,7 @@ export default { diff --git a/frontend/src/components/campAdmin/CampPeriods.vue b/frontend/src/components/campAdmin/CampPeriods.vue index e20e1d1f94..0fe563e7fb 100644 --- a/frontend/src/components/campAdmin/CampPeriods.vue +++ b/frontend/src/components/campAdmin/CampPeriods.vue @@ -4,7 +4,7 @@ Displays all periods of a single camp and allows to edit them & create new ones