From 6168503fee48a89b53dff95e56c73e7bffd30484 Mon Sep 17 00:00:00 2001 From: Christian Hartmann Date: Mon, 21 Oct 2024 23:25:49 +0200 Subject: [PATCH] make ApiController endpoints openapi compatible Signed-off-by: Christian Hartmann --- .prettierignore | 4 + lib/Capabilities.php | 1 + lib/Controller/ApiController.php | 364 +- lib/Controller/ConfigController.php | 1 + lib/Controller/PageController.php | 1 + lib/Controller/ShareApiController.php | 76 +- lib/ResponseDefinitions.php | 113 + openapi.json | 3155 +++++++++++++++++ tests/Integration/Api/ApiV3Test.php | 14 +- tests/Unit/Controller/ApiControllerTest.php | 65 +- .../Controller/ShareApiControllerTest.php | 21 +- 11 files changed, 3567 insertions(+), 248 deletions(-) create mode 100644 lib/ResponseDefinitions.php create mode 100644 openapi.json diff --git a/.prettierignore b/.prettierignore index d04025109..e65599c97 100644 --- a/.prettierignore +++ b/.prettierignore @@ -16,7 +16,11 @@ js/ # Handled by transifex l10n/ +# OpenAPI +openapi.json + # PHP lib/ **/*.php +composer.json composer.lock diff --git a/lib/Capabilities.php b/lib/Capabilities.php index 62d739a5b..1d780f842 100644 --- a/lib/Capabilities.php +++ b/lib/Capabilities.php @@ -35,6 +35,7 @@ public function __construct( /** * Provide App Capabilities * @inheritdoc + * @return array{forms: array{version: string, apiVersions: array}} */ public function getCapabilities() { return [ diff --git a/lib/Controller/ApiController.php b/lib/Controller/ApiController.php index a1b3ddfff..9c23264b0 100644 --- a/lib/Controller/ApiController.php +++ b/lib/Controller/ApiController.php @@ -44,6 +44,7 @@ use OCA\Forms\Db\SubmissionMapper; use OCA\Forms\Db\UploadedFile; use OCA\Forms\Db\UploadedFileMapper; +use OCA\Forms\ResponseDefinitions; use OCA\Forms\Service\ConfigService; use OCA\Forms\Service\FormsService; use OCA\Forms\Service\SubmissionService; @@ -73,6 +74,14 @@ use Psr\Log\LoggerInterface; +/** + * @psalm-import-type FormsPartialForm from ResponseDefinitions + * @psalm-import-type FormsForm from ResponseDefinitions + * @psalm-import-type FormsQuestion from ResponseDefinitions + * @psalm-import-type FormsOption from ResponseDefinitions + * @psalm-import-type FormsSubmissions from ResponseDefinitions + * @psalm-import-type FormsUploadedFile from ResponseDefinitions + */ class ApiController extends OCSController { private ?IUser $currentUser; @@ -112,38 +121,43 @@ public function preflightedCors() { // API v3 methods // Forms /** - * Read Form-List of owned forms - * Return only with necessary information for Listing. - * @return DataResponse + * Get all forms available to the user (owned/shared) + * + * @param string $type The type of forms to retrieve. Defaults to `owned`. + * Possible values: + * - `owned`: Forms owned by the user. + * - `shared`: Forms shared with the user. + * @return DataResponse, Http::STATUS_OK, array<>> + * @throws OCSBadRequestException wrong form type supplied */ #[CORS()] #[NoAdminRequired()] #[ApiRoute(verb: 'GET', url: '/api/v3/forms')] public function getForms(string $type = 'owned'): DataResponse { + $result = []; + if ($type === 'owned') { $forms = $this->formMapper->findAllByOwnerId($this->currentUser->getUID()); - $result = []; foreach ($forms as $form) { $result[] = $this->formsService->getPartialFormArray($form); } - return new DataResponse($result); } elseif ($type === 'shared') { $forms = $this->formsService->getSharedForms($this->currentUser); $result = array_values(array_map(fn (Form $form): array => $this->formsService->getPartialFormArray($form), $forms)); - return new DataResponse($result); } else { - throw new OCSBadRequestException(); + throw new OCSBadRequestException('wrong form type supplied'); } + + return new DataResponse($result, Http::STATUS_OK); } /** - * Create a new Form and return the Form to edit. - * Return a cloned Form if the parameter $fromId is set + * Create a new form and return the form + * Return a copy of the form if the parameter $fromId is set * - * @param int $fromId (optional) ID of the Form that should be cloned - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException + * @param ?int $fromId (optional) Id of the form that should be cloned + * @return DataResponse> + * @throws OCSForbiddenException The user is not allowed to create forms */ #[CORS()] #[NoAdminRequired()] @@ -152,7 +166,7 @@ public function newForm(?int $fromId = null): DataResponse { // Check if user is allowed if (!$this->configService->canCreateForms()) { $this->logger->debug('This user is not allowed to create Forms.'); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('This user is not allowed to create Forms.'); } if ($fromId === null) { @@ -209,16 +223,17 @@ public function newForm(?int $fromId = null): DataResponse { } } } - return $this->getForm($form->getId()); + + return new DataResponse($this->formsService->getForm($form), Http::STATUS_CREATED); } /** - * Read all information to edit a Form (form, questions, options, except submissions/answers). + * Read all information to edit a Form (form, questions, options, except submissions/answers) * * @param int $formId Id of the form - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException + * @return DataResponse, Http::STATUS_OK, array<>> + * @throws OCSBadRequestException Could not find form + * @throws OCSForbiddenException User has no permissions to get this form */ #[CORS()] #[NoAdminRequired()] @@ -228,27 +243,28 @@ public function getForm(int $formId): DataResponse { $form = $this->formMapper->findById($formId); } catch (IMapperException $e) { $this->logger->debug('Could not find form'); - throw new OCSBadRequestException(); + throw new OCSBadRequestException('Could not find form'); } if (!$this->formsService->hasUserAccess($form)) { $this->logger->debug('User has no permissions to get this form'); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('User has no permissions to get this form'); } - $formData = $this->formsService->getForm($form); - - return new DataResponse($formData); + return new DataResponse($this->formsService->getForm($form)); } /** - * Writes the given key-value pairs into Database. + * Writes the given key-value pairs into Database * * @param int $formId FormId of form to update - * @param array $keyValuePairs Array of key=>value pairs to update. - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException + * @param array $keyValuePairs Array of key=>value pairs to update. + * @return DataResponse> + * @throws OCSBadRequestException Could not find new form owner + * @throws OCSForbiddenException Empty keyValuePairs provided + * @throws OCSForbiddenException Not allowed to update id, hash, created, fileId or lastUpdated. OwnerId only allowed if no other key provided. + * @throws OCSForbiddenException User is not allowed to modify the form + * @throws OCSNotFoundException Form not found */ #[CORS()] #[NoAdminRequired()] @@ -264,7 +280,7 @@ public function updateForm(int $formId, array $keyValuePairs): DataResponse { // Don't allow empty array if (sizeof($keyValuePairs) === 0) { $this->logger->info('Empty keyValuePairs, will not update.'); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('Empty keyValuePairs, will not update.'); } // Process owner transfer @@ -296,7 +312,7 @@ public function updateForm(int $formId, array $keyValuePairs): DataResponse { isset($keyValuePairs['fileId']) || key_exists('lastUpdated', $keyValuePairs) ) { $this->logger->info('Not allowed to update id, hash, ownerId, created, fileId or lastUpdated'); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('Not allowed to update id, hash, ownerId, created, fileId or lastUpdated'); } // Process file linking @@ -333,9 +349,9 @@ public function updateForm(int $formId, array $keyValuePairs): DataResponse { * Delete a form * * @param int $formId the form id - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException + * @return DataResponse> + * @throws OCSForbiddenException User is not allowed to delete the form + * @throws OCSNotFoundException Form not found */ #[CORS()] #[NoAdminRequired()] @@ -355,10 +371,10 @@ public function deleteForm(int $formId): DataResponse { /** * Read all questions (including options) * - * @param int $formId FormId - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException + * @param int $formId the form id + * @return DataResponse, Http::STATUS_OK, array<>> + * @throws OCSForbiddenException User has no permissions to get this form + * @throws OCSNotFoundException Could not find form */ #[CORS()] #[NoAdminRequired()] @@ -368,12 +384,12 @@ public function getQuestions(int $formId): DataResponse { $form = $this->formMapper->findById($formId); } catch (IMapperException $e) { $this->logger->debug('Could not find form'); - throw new OCSBadRequestException(); + throw new OCSNotFoundException('Could not find form'); } if (!$this->formsService->hasUserAccess($form)) { $this->logger->debug('User has no permissions to get this form'); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('User has no permissions to get this form'); } $questionData = $this->formsService->getQuestions($formId); @@ -386,9 +402,10 @@ public function getQuestions(int $formId): DataResponse { * * @param int $formId FormId * @param int $questionId QuestionId - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException + * @return DataResponse> + * @throws OCSBadRequestException Question doesn\'t belong to given Form + * @throws OCSForbiddenException User has no permissions to get this form + * @throws OCSNotFoundException Could not find form */ #[CORS()] #[NoAdminRequired()] @@ -398,18 +415,18 @@ public function getQuestion(int $formId, int $questionId): DataResponse { $form = $this->formMapper->findById($formId); } catch (IMapperException $e) { $this->logger->debug('Could not find form'); - throw new OCSBadRequestException(); + throw new OCSNotFoundException('Could not find form'); } if (!$this->formsService->hasUserAccess($form)) { $this->logger->debug('User has no permissions to get this form'); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('User has no permissions to get this form'); } $question = $this->formsService->getQuestion($questionId); if ($question['formId'] !== $formId) { - throw new OCSBadRequestException('Question doesn\'t belong to given Form'); + throw new OCSBadRequestException('Question doesn\'t belong to given form'); } return new DataResponse($question); @@ -421,10 +438,14 @@ public function getQuestion(int $formId, int $questionId): DataResponse { * @param int $formId the form id * @param string $type the new question type * @param string $text the new question title - * @param int $fromId (optional) id of the question that should be cloned - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException + * @param ?int $fromId (optional) id of the question that should be cloned + * @return DataResponse> + * @throws OCSBadRequestException Invalid type + * @throws OCSBadRequestException Datetime question type no longer supported + * @throws OCSForbiddenException User has no permissions to get this form + * @throws OCSForbiddenException This form is archived and can not be modified + * @throws OCSNotFoundException Could not find form + * @throws OCSNotFoundException Could not find question */ #[CORS()] #[NoAdminRequired()] @@ -433,7 +454,7 @@ public function newQuestion(int $formId, ?string $type = null, string $text = '' $form = $this->getFormIfAllowed($formId); if ($this->formsService->isFormArchived($form)) { $this->logger->debug('This form is archived and can not be modified'); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('This form is archived and can not be modified'); } if ($fromId === null) { @@ -518,19 +539,26 @@ public function newQuestion(int $formId, ?string $type = null, string $text = '' $this->formMapper->update($form); - return new DataResponse($response); + return new DataResponse($response, Http::STATUS_CREATED); } /** - * Writes the given key-value pairs into Database. - * Key 'order' should only be changed by reorderQuestions() and is not allowed here. + * Writes the given key-value pairs into Database + * Key `order` should only be changed by reorderQuestions() and is not allowed here * * @param int $formId the form id * @param int $questionId id of question to update - * @param array $keyValuePairs Array of key=>value pairs to update. - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException + * @param array $keyValuePairs Array of key=>value pairs to update. + * @return DataResponse> + * @throws OCSBadRequestException Question doesn\'t belong to given Form + * @throws OCSBadRequestException Invalid extraSettings, will not update. + * @throws OCSForbiddenException Empty keyValuePairs, will not update + * @throws OCSForbiddenException Not allowed to update `id` or `formId` + * @throws OCSForbiddenException Please use reorderQuestions() to change order + * @throws OCSForbiddenException This form is archived and can not be modified + * @throws OCSForbiddenException User has no permissions to get this form + * @throws OCSNotFoundException Could not find form + * @throws OCSNotFoundException Could not find question */ #[CORS()] #[NoAdminRequired()] @@ -546,7 +574,7 @@ public function updateQuestion(int $formId, int $questionId, array $keyValuePair $question = $this->questionMapper->findById($questionId); } catch (IMapperException $e) { $this->logger->debug('Could not find question'); - throw new OCSBadRequestException('Could not find question'); + throw new OCSNotFoundException('Could not find question'); } if ($question->getFormId() !== $formId) { @@ -556,19 +584,19 @@ public function updateQuestion(int $formId, int $questionId, array $keyValuePair $form = $this->getFormIfAllowed($formId); if ($this->formsService->isFormArchived($form)) { $this->logger->debug('This form is archived and can not be modified'); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('This form is archived and can not be modified'); } // Don't allow empty array if (sizeof($keyValuePairs) === 0) { $this->logger->info('Empty keyValuePairs, will not update.'); - throw new OCSForbiddenException(); + throw new OCSBacRequestException('This form is archived and can not be modified'); } //Don't allow to change id or formId if (key_exists('id', $keyValuePairs) || key_exists('formId', $keyValuePairs)) { $this->logger->debug('Not allowed to update \'id\' or \'formId\''); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('Not allowed to update \'id\' or \'formId\''); } // Don't allow to reorder here @@ -597,9 +625,12 @@ public function updateQuestion(int $formId, int $questionId, array $keyValuePair * * @param int $formId the form id * @param int $questionId the question id - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException + * @return DataResponse> + * @throws OCSBadRequestException Question doesn\'t belong to given Form + * @throws OCSForbiddenException This form is archived and can not be modified + * @throws OCSForbiddenException User has no permissions to get this form + * @throws OCSNotFoundException Could not find form + * @throws OCSNotFoundException Could not find question */ #[CORS()] #[NoAdminRequired()] @@ -613,7 +644,7 @@ public function deleteQuestion(int $formId, int $questionId): DataResponse { $question = $this->questionMapper->findById($questionId); } catch (IMapperException $e) { $this->logger->debug('Could not find question'); - throw new OCSBadRequestException('Could not find question'); + throw new OCSNotFoundException('Could not find question'); } if ($question->getFormId() !== $formId) { @@ -623,7 +654,7 @@ public function deleteQuestion(int $formId, int $questionId): DataResponse { $form = $this->getFormIfAllowed($formId); if ($this->formsService->isFormArchived($form)) { $this->logger->debug('This form is archived and can not be modified'); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('This form is archived and can not be modified'); } // Store Order of deleted Question @@ -649,13 +680,19 @@ public function deleteQuestion(int $formId, int $questionId): DataResponse { } /** - * Updates the Order of all Questions of a Form. + * Updates the Order of all Questions of a Form * * @param int $formId Id of the form to reorder - * @param Array $newOrder Array of Question-Ids in new order. - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException + * @param array $newOrder Array of Question-Ids in new order. + * @return DataResponse, Http::STATUS_OK, array<>> + * @throws OCSBadRequestException The given array contains duplicates + * @throws OCSBadRequestException The length of the given array does not match the number of stored questions + * @throws OCSBadRequestException Question doesn\'t belong to given Form + * @throws OCSBadRequestException One question has already been marked as deleted + * @throws OCSForbiddenException This form is archived and can not be modified + * @throws OCSForbiddenException User has no permissions to get this form + * @throws OCSNotFoundException Could not find form + * @throws OCSNotFoundException Could not find question */ #[CORS()] #[NoAdminRequired()] @@ -669,7 +706,7 @@ public function reorderQuestions(int $formId, array $newOrder): DataResponse { $form = $this->getFormIfAllowed($formId); if ($this->formsService->isFormArchived($form)) { $this->logger->debug('This form is archived and can not be modified'); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('This form is archived and can not be modified'); } // Check if array contains duplicates @@ -693,27 +730,27 @@ public function reorderQuestions(int $formId, array $newOrder): DataResponse { try { $questions[$arrayKey] = $this->questionMapper->findById($questionId); } catch (IMapperException $e) { - $this->logger->debug('Could not find question. Id: {questionId}', [ + $this->logger->debug('Could not find question {questionId}', [ 'questionId' => $questionId ]); - throw new OCSBadRequestException(); + throw new OCSNotFoundException('Could not find question'); } // Abort if a question is not part of the Form. if ($questions[$arrayKey]->getFormId() !== $formId) { - $this->logger->debug('This Question is not part of the given Form: questionId: {questionId}', [ + $this->logger->debug('This Question is not part of the given form: {questionId}', [ 'questionId' => $questionId ]); - throw new OCSBadRequestException(); + throw new OCSBadRequestException('Question doesn\'t belong to given Form'); } // Abort if a question is already marked as deleted (order==0) $oldOrder = $questions[$arrayKey]->getOrder(); if ($oldOrder === 0) { - $this->logger->debug('This Question has already been marked as deleted: Id: {questionId}', [ + $this->logger->debug('This question has already been marked as deleted: Id: {questionId}', [ 'questionId' => $questions[$arrayKey]->getId() ]); - throw new OCSBadRequestException(); + throw new OCSBadRequestException('One question has already been marked as deleted'); } // Only set order, if it changed. @@ -745,9 +782,12 @@ public function reorderQuestions(int $formId, array $newOrder): DataResponse { * @param int $formId id of the form * @param int $questionId id of the question * @param array $optionTexts the new option text - * @return DataResponse Returns a DataResponse containing the added options - * @throws OCSBadRequestException - * @throws OCSForbiddenException + * @return DataResponse, Http::STATUS_CREATED, array<>> Returns a DataResponse containing the added options + * @throws OCSBadRequestException This question is not part ot the given form + * @throws OCSForbiddenException This form is archived and can not be modified + * @throws OCSForbiddenException Current user has no permission to edit + * @throws OCSNotFoundException Could not find form + * @throws OCSNotFoundException Could not find question */ #[CORS()] #[NoAdminRequired()] @@ -762,21 +802,21 @@ public function newOption(int $formId, int $questionId, array $optionTexts): Dat $form = $this->getFormIfAllowed($formId); if ($this->formsService->isFormArchived($form)) { $this->logger->debug('This form is archived and can not be modified'); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('This form is archived and can not be modified'); } try { $question = $this->questionMapper->findById($questionId); } catch (IMapperException $e) { $this->logger->debug('Could not find question'); - throw new OCSBadRequestException('Could not find question'); + throw new OCSNotFoundException('Could not find question'); } if ($question->getFormId() !== $formId) { - $this->logger->debug('This Question is not part of the given Form: questionId: {questionId}', [ + $this->logger->debug('This question is not part of the given form: questionId: {questionId}', [ 'questionId' => $questionId ]); - throw new OCSBadRequestException(); + throw new OCSBadRequestException('This question is not part ot the given form'); } // Retrieve all options sorted by 'order'. Takes the order of the last array-element and adds one. @@ -808,19 +848,24 @@ public function newOption(int $formId, int $questionId, array $optionTexts): Dat $this->formMapper->update($form); - return new DataResponse($addedOptions); + return new DataResponse($addedOptions, Http::STATUS_CREATED); } /** - * Writes the given key-value pairs into Database. + * Writes the given key-value pairs into Database * * @param int $formId id of form * @param int $questionId id of question * @param int $optionId id of option to update - * @param array $keyValuePairs Array of key=>value pairs to update. - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException + * @param array{key: string, value: mixed} $keyValuePairs Array of key=>value pairs to update. + * @return DataResponse> Returns the id of the updated option + * @throws OCSBadRequestException The given option id doesn't match the question or form + * @throws OCSForbiddenException This form is archived and can not be modified + * @throws OCSForbiddenException Current user has no permission to edit + * @throws OCSForbiddenException Empty keyValuePairs, will not update + * @throws OCSForbiddenException Not allowed to update id or questionId + * @throws OCSNotFoundException Could not find form + * @throws OCSNotFoundException Could not find option or question */ #[CORS()] #[NoAdminRequired()] @@ -836,7 +881,7 @@ public function updateOption(int $formId, int $questionId, int $optionId, array $form = $this->getFormIfAllowed($formId); if ($this->formsService->isFormArchived($form)) { $this->logger->debug('This form is archived and can not be modified'); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('This form is archived and can not be modified'); } try { @@ -844,24 +889,24 @@ public function updateOption(int $formId, int $questionId, int $optionId, array $question = $this->questionMapper->findById($questionId); } catch (IMapperException $e) { $this->logger->debug('Could not find option or question'); - throw new OCSBadRequestException('Could not find option or question'); + throw new OCSNotFoundException('Could not find option or question'); } if ($option->getQuestionId() !== $questionId || $question->getFormId() !== $formId) { $this->logger->debug('The given option id doesn\'t match the question or form.'); - throw new OCSBadRequestException(); + throw new OCSBadRequestException('The given option id doesn\'t match the question or form.'); } // Don't allow empty array if (sizeof($keyValuePairs) === 0) { - $this->logger->info('Empty keyValuePairs, will not update.'); - throw new OCSForbiddenException(); + $this->logger->info('Empty keyValuePairs, will not update'); + throw new OCSForbiddenException('Empty keyValuePairs, will not update'); } //Don't allow to change id or questionId if (key_exists('id', $keyValuePairs) || key_exists('questionId', $keyValuePairs)) { $this->logger->debug('Not allowed to update id or questionId'); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('Not allowed to update id or questionId'); } // Create OptionEntity with given Params & Id. @@ -882,9 +927,12 @@ public function updateOption(int $formId, int $questionId, int $optionId, array * @param int $formId id of form * @param int $questionId id of question * @param int $optionId id of option to update - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException + * @return DataResponse> Returns the id of the deleted option + * @throws OCSBadRequestException The given option id doesn't match the question or form + * @throws OCSForbiddenException This form is archived and can not be modified + * @throws OCSForbiddenException Current user has no permission to edit + * @throws OCSNotFoundException Could not find form + * @throws OCSNotFoundException Could not find question or option */ #[CORS()] #[NoAdminRequired()] @@ -897,20 +945,20 @@ public function deleteOption(int $formId, int $questionId, int $optionId): DataR $form = $this->getFormIfAllowed($formId); if ($this->formsService->isFormArchived($form)) { $this->logger->debug('This form is archived and can not be modified'); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('This form is archived and can not be modified'); } try { $option = $this->optionMapper->findById($optionId); $question = $this->questionMapper->findById($questionId); } catch (IMapperException $e) { - $this->logger->debug('Could not find form, question or option'); - throw new OCSBadRequestException('Could not find form, question or option'); + $this->logger->debug('Could not find option or question'); + throw new OCSBadRequestException('Could not find option or question'); } if ($option->getQuestionId() !== $questionId || $question->getFormId() !== $formId) { $this->logger->debug('The given option id doesn\'t match the question or form.'); - throw new OCSBadRequestException(); + throw new OCSBadRequestException('The given option id doesn\'t match the question or form.'); } $this->optionMapper->delete($option); @@ -932,7 +980,16 @@ public function deleteOption(int $formId, int $questionId, int $optionId): DataR * Reorder options for a given question * @param int $formId id of form * @param int $questionId id of question - * @param Array $newOrder Order to use + * @param array $newOrder Array of option ids in new order. + * @return DataResponse, Http::STATUS_OK, array<>> + * @throws OCSBadRequestException The given question id doesn't match the form + * @throws OCSBadRequestException The given array contains duplicates + * @throws OCSBadRequestException The length of the given array does not match the number of stored options + * @throws OCSBadRequestException This option is not part of the given question + * @throws OCSForbiddenException This form is archived and can not be modified + * @throws OCSForbiddenException Current user has no permission to edit + * @throws OCSNotFoundException Could not find form + * @throws OCSNotFoundException Could not find question */ #[CORS()] #[NoAdminRequired()] @@ -941,19 +998,19 @@ public function reorderOptions(int $formId, int $questionId, array $newOrder) { $form = $this->getFormIfAllowed($formId); if ($this->formsService->isFormArchived($form)) { $this->logger->debug('This form is archived and can not be modified'); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('This form is archived and can not be modified'); } try { $question = $this->questionMapper->findById($questionId); } catch (IMapperException $e) { - $this->logger->debug('Could not find form or question', ['exception' => $e]); - throw new OCSNotFoundException('Could not find form or question'); + $this->logger->debug('Could not find question'); + throw new OCSNotFoundException('Could not find question'); } if ($question->getFormId() !== $formId) { $this->logger->debug('The given question id doesn\'t match the form.'); - throw new OCSBadRequestException(); + throw new OCSBadRequestException('The given question id doesn\'t match the form.'); } // Check if array contains duplicates @@ -980,18 +1037,17 @@ public function reorderOptions(int $formId, int $questionId, array $newOrder) { $this->logger->debug('Could not find option. Id: {optionId}', [ 'optionId' => $optionId ]); - throw new OCSBadRequestException(); + throw new OCSNotFoundException('Could not find option'); } - // Abort if a question is not part of the Form. + // Abort if a option is not part of the question. if ($options[$arrayKey]->getQuestionId() !== $questionId) { - $this->logger->debug('This Option is not part of the given Question: formId: {formId}', [ + $this->logger->debug('This option is not part of the given question: formId: {formId}', [ 'formId' => $formId ]); - throw new OCSBadRequestException(); + throw new OCSBadRequestException('This option is not part of the given question'); } - // Abort if a question is already marked as deleted (order==0) $oldOrder = $options[$arrayKey]->getOrder(); // Only set order, if it changed. @@ -1021,9 +1077,14 @@ public function reorderOptions(int $formId, int $questionId, array $newOrder) { * Get all the submissions of a given form * * @param int $formId of the form - * @return DataResponse|DataDownloadResponse - * @throws OCSNotFoundException - * @throws OCSForbiddenException + * @param ?string $fileFormat the file format that should be used for the download. Defaults to `null` + * Possible values: + * - `csv`: Comma-separated value + * - `ods`: OpenDocument Spreadsheet + * - `xlsx`: Excel Open XML Spreadsheet + * @return DataResponse|DataDownloadResponse> + * @throws OCSNotFoundException Could not find form + * @throws OCSForbiddenException The current user has no permission to get the results for this form */ #[CORS()] #[NoAdminRequired()] @@ -1033,12 +1094,12 @@ public function getSubmissions(int $formId, ?string $fileFormat = null): DataRes $form = $this->formMapper->findById($formId); } catch (IMapperException $e) { $this->logger->debug('Could not find form'); - throw new OCSNotFoundException(); + throw new OCSNotFoundException('Could not find form'); } if (!$this->formsService->canSeeResults($form)) { $this->logger->debug('The current user has no permission to get the results for this form'); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('The current user has no permission to get the results for this form'); } if ($fileFormat !== null) { @@ -1082,9 +1143,9 @@ public function getSubmissions(int $formId, ?string $fileFormat = null): DataRes * Delete all submissions of a specified form * * @param int $formId the form id - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException + * @return DataResponse> + * @throws OCSNotFoundException Could not find form + * @throws OCSForbiddenException This form is not owned by the current user and user has no `results_delete` permission */ #[CORS()] #[NoAdminRequired()] @@ -1098,13 +1159,13 @@ public function deleteAllSubmissions(int $formId): DataResponse { $form = $this->formMapper->findById($formId); } catch (IMapperException $e) { $this->logger->debug('Could not find form'); - throw new OCSBadRequestException(); + throw new OCSNotFoundException('Could not find form'); } // The current user has permissions to remove submissions if (!$this->formsService->canDeleteResults($form)) { $this->logger->debug('This form is not owned by the current user and user has no `results_delete` permission'); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('This form is not owned by the current user and user has no `results_delete` permission'); } // Delete all submissions (incl. Answers) @@ -1118,11 +1179,15 @@ public function deleteAllSubmissions(int $formId): DataResponse { * Process a new submission * * @param int $formId the form id - * @param array $answers [question_id => arrayOfString] + * @param array> $answers [question_id => arrayOfString] * @param string $shareHash public share-hash -> Necessary to submit on public link-shares. - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException + * @return DataResponse, Http::STATUS_CREATED, array<>> + * @throws OCSBadRequestException At least one submitted answer is not valid + * @throws OCSForbiddenException Already submitted + * @throws OCSForbiddenException Not allowed to access this form + * @throws OCSForbiddenException This form is no longer taking answers + * @throws OCSForbiddenException This form is not owned by the current user and user has no `results_delete` permission + * @throws OCSNotFoundException Could not find form */ #[CORS()] #[NoAdminRequired()] @@ -1207,7 +1272,7 @@ public function newSubmission(int $formId, array $answers, string $shareHash = ' } } - return new DataResponse(); + return new DataResponse(null, Http::STATUS_CREATED); } /** @@ -1215,9 +1280,10 @@ public function newSubmission(int $formId, array $answers, string $shareHash = ' * * @param int $formId the form id * @param int $submissionId the submission id - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException + * @return DataResponse> + * @throws OCSBadRequestException Submission doesn't belong to given form + * @throws OCSNotFoundException Could not find form or submission + * @throws OCSForbiddenException This form is not owned by the current user and user has no `results_delete` permission */ #[CORS()] #[NoAdminRequired()] @@ -1232,7 +1298,7 @@ public function deleteSubmission(int $formId, int $submissionId): DataResponse { $form = $this->formMapper->findById($formId); } catch (IMapperException $e) { $this->logger->debug('Could not find form or submission'); - throw new OCSBadRequestException(); + throw new OCSNotFoundException('Could not find form or submission'); } if ($formId !== $submission->getFormId()) { @@ -1243,7 +1309,7 @@ public function deleteSubmission(int $formId, int $submissionId): DataResponse { // The current user has permissions to remove submissions if (!$this->formsService->canDeleteResults($form)) { $this->logger->debug('This form is not owned by the current user and user has no `results_delete` permission'); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('This form is not owned by the current user and user has no `results_delete` permission'); } // Delete submission (incl. Answers) @@ -1259,9 +1325,9 @@ public function deleteSubmission(int $formId, int $submissionId): DataResponse { * @param int $formId of the form * @param string $path The Cloud-Path to export to * @param string $fileFormat File format used for export - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException + * @return DataResponse> + * @throws OCSForbiddenException The current user has no permission to get the results for this form + * @throws OCSNotFoundException Could not find form */ #[CORS()] #[NoAdminRequired()] @@ -1271,12 +1337,12 @@ public function exportSubmissionsToCloud(int $formId, string $path, string $file $form = $this->formMapper->findById($formId); } catch (IMapperException $e) { $this->logger->debug('Could not find form'); - throw new OCSNotFoundException(); + throw new OCSNotFoundException('Could not find form'); } if (!$this->formsService->canSeeResults($form)) { $this->logger->debug('The current user has no permission to get the results for this form'); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('The current user has no permission to get the results for this form'); } $file = $this->submissionService->writeFileToCloud($form, $path, $fileFormat); @@ -1290,7 +1356,15 @@ public function exportSubmissionsToCloud(int $formId, string $path, string $file * @param int $formId id of the form * @param int $questionId id of the question * @param string $shareHash hash of the form share - * @return DataResponse + * @return DataResponse> + * @throws OCSBadRequestException No files provided + * @throws OCSBadRequestException Question doesn't belong to the given form + * @throws OCSBadRequestException Invalid file provided + * @throws OCSBadRequestException Failed to upload the file + * @throws OCSBadRequestException File size exceeds the maximum allowed size + * @throws OCSBadRequestException File type is not allowed + * @throws OCSForbiddenException Already submitted + * @throws OCSNotFoundException Could not find question */ #[CORS()] #[NoAdminRequired()] @@ -1325,7 +1399,7 @@ public function uploadFiles(int $formId, int $questionId, string $shareHash = '' $this->logger->debug('Could not find question with id {questionId}', [ 'questionId' => $questionId ]); - throw new OCSBadRequestException(previous: $e instanceof \Exception ? $e : null); + throw new OCSNotFoundException('Could not find question'); } if ($formId !== $question->getFormId()) { @@ -1486,7 +1560,7 @@ private function loadFormForSubmission(int $formId, string $shareHash): Form { $form = $this->formMapper->findById($formId); } catch (IMapperException $e) { $this->logger->debug('Could not find form'); - throw new OCSBadRequestException(previous: $e instanceof \Exception ? $e : null); + throw new OCSNotFoundException('Could not find form'); } // Does the user have access to the form (Either by logged-in user, or by providing public share-hash.) @@ -1530,12 +1604,12 @@ private function getFormIfAllowed(int $formId): Form { $form = $this->formMapper->findById($formId); } catch (IMapperException $e) { $this->logger->debug('Could not find form'); - throw new OCSNotFoundException(); + throw new OCSNotFoundException('Could not find form'); } if ($form->getOwnerId() !== $this->currentUser->getUID()) { $this->logger->debug('This form is not owned by the current user'); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('This form is not owned by the current user'); } return $form; } diff --git a/lib/Controller/ConfigController.php b/lib/Controller/ConfigController.php index e0e7990ef..147ef0aa8 100644 --- a/lib/Controller/ConfigController.php +++ b/lib/Controller/ConfigController.php @@ -36,6 +36,7 @@ use Psr\Log\LoggerInterface; +#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)] class ConfigController extends ApiController { public function __construct( protected $appName, diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php index 4b1d87673..ca5f05f27 100644 --- a/lib/Controller/PageController.php +++ b/lib/Controller/PageController.php @@ -54,6 +54,7 @@ use OCP\IUserSession; use OCP\Util; +#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)] class PageController extends Controller { private const TEMPLATE_MAIN = 'main'; diff --git a/lib/Controller/ShareApiController.php b/lib/Controller/ShareApiController.php index 088685f5b..806d556f9 100644 --- a/lib/Controller/ShareApiController.php +++ b/lib/Controller/ShareApiController.php @@ -30,19 +30,21 @@ use OCA\Forms\Db\FormMapper; use OCA\Forms\Db\Share; use OCA\Forms\Db\ShareMapper; +use OCA\Forms\ResponseDefinitions; use OCA\Forms\Service\CirclesService; use OCA\Forms\Service\ConfigService; use OCA\Forms\Service\FormsService; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\IMapperException; +use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\ApiRoute; use OCP\AppFramework\Http\Attribute\CORS; use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\OCS\OCSBadRequestException; -use OCP\AppFramework\OCS\OCSException; use OCP\AppFramework\OCS\OCSForbiddenException; +use OCP\AppFramework\OCS\OCSNotFoundException; use OCP\AppFramework\OCSController; use OCP\Files\IRootFolder; use OCP\IGroup; @@ -56,6 +58,9 @@ use OCP\Share\IShare; use Psr\Log\LoggerInterface; +/** + * @psalm-import-type FormsShare from ResponseDefinitions + */ class ShareApiController extends OCSController { private IUser $currentUser; @@ -85,9 +90,23 @@ public function __construct( * @param int $formId The form to share * @param int $shareType Nextcloud-ShareType * @param string $shareWith ID of user/group/... to share with. For Empty shareWith and shareType Link, this will be set as RandomID. - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException + * @param array $permissions the permissions granted on the share, defaults to `submit` + * Possible values: + * - `submit` user can submit + * - `results` user can see the results + * - `results_delete` user can see and delete results + * @return DataResponse, Http::STATUS_CREATED, array<>> + * @throws OCSBadRequestException Invalid shareType + * @throws OCSBadRequestException Invalid permission given + * @throws OCSBadRequestException Invalid user to share with + * @throws OCSBadRequestException Invalid group to share with + * @throws OCSBadRequestException Invalid team to share with + * @throws OCSBadRequestException Unknown shareType + * @throws OCSBadRequestException Share hash exists, please try again + * @throws OCSBadRequestException Teams app is disabled + * @throws OCSForbiddenException Link share not allowed + * @throws OCSForbiddenException This form is not owned by the current user + * @throws OCSNotFoundException Could not find form */ #[CORS()] #[NoAdminRequired()] @@ -109,20 +128,20 @@ public function newShare(int $formId, int $shareType, string $shareWith = '', ar // Block LinkShares if not allowed if ($shareType === IShare::TYPE_LINK && !$this->configService->getAllowPublicLink()) { $this->logger->debug('Link Share not allowed.'); - throw new OCSForbiddenException('Link Share not allowed.'); + throw new OCSForbiddenException('Link share not allowed.'); } try { $form = $this->formMapper->findById($formId); } catch (IMapperException $e) { $this->logger->debug('Could not find form', ['exception' => $e]); - throw new OCSBadRequestException('Could not find form'); + throw new OCSNotFoundException('Could not find form'); } // Check for permission to share form if ($form->getOwnerId() !== $this->currentUser->getUID()) { $this->logger->debug('This form is not owned by the current user'); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('This form is not owned by the current user'); } if (!$this->validatePermissions($permissions, $shareType)) { @@ -157,11 +176,11 @@ public function newShare(int $formId, int $shareType, string $shareWith = '', ar // Check if hash already exists. (Unfortunately not possible here by unique index on db.) try { // Try loading a share to the hash. - $nonex = $this->shareMapper->findPublicShareByHash($shareWith); + $this->shareMapper->findPublicShareByHash($shareWith); // If we come here, a share has been found --> The share hash already exists, thus aborting. - $this->logger->debug('Share Hash already exists.'); - throw new OCSException('Share Hash exists. Please retry.'); + $this->logger->debug('Share hash already exists.'); + throw new OCSBadRequestException('Share hash exists, please retry.'); } catch (DoesNotExistException $e) { // Just continue, this is what we expect to happen (share hash not existing yet). } @@ -170,7 +189,7 @@ public function newShare(int $formId, int $shareType, string $shareWith = '', ar case IShare::TYPE_CIRCLE: if (!$this->circlesService->isCirclesEnabled()) { $this->logger->debug('Teams app is disabled, sharing to teams not possible.'); - throw new OCSException('Teams app is disabled.'); + throw new OCSBadRequestException('Teams app is disabled.'); } $circle = $this->circlesService->getCircle($shareWith); if (is_null($circle)) { @@ -182,7 +201,7 @@ public function newShare(int $formId, int $shareType, string $shareWith = '', ar default: // This passed the check for used shareTypes, but has not been found here. $this->logger->warning('Unknown, but used shareType: {shareType}. Please file an issue on GitHub.', [ 'shareType' => $shareType ]); - throw new OCSException('Unknown shareType.'); + throw new OCSBadRequestException('Unknown shareType.'); } $share = new Share(); @@ -202,7 +221,7 @@ public function newShare(int $formId, int $shareType, string $shareWith = '', ar $shareData = $share->read(); $shareData['displayName'] = $this->formsService->getShareDisplayName($shareData); - return new DataResponse($shareData); + return new DataResponse($shareData, Http::STATUS_CREATED); } /** @@ -210,10 +229,14 @@ public function newShare(int $formId, int $shareType, string $shareWith = '', ar * * @param int $formId of the form * @param int $shareId of the share to update - * @param array $keyValuePairs Array of key=>value pairs to update. - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException + * @param array{key: string, values: mixed} $keyValuePairs Array of key=>value pairs to update. + * @return DataResponse, Http::STATUS_OK, array<>> + * @throws OCSBadRequestException Share doesn't belong to given Form + * @throws OCSBadRequestException Invalid permission given + * @throws OCSForbiddenException This form is not owned by the current user + * @throws OCSForbiddenException Empty keyValuePairs, will not update + * @throws OCSForbiddenException Not allowed to update other properties than permissions + * @throws OCSNotFoundException Could not find share */ #[CORS()] #[NoAdminRequired()] @@ -230,7 +253,7 @@ public function updateShare(int $formId, int $shareId, array $keyValuePairs): Da $form = $this->formMapper->findById($formId); } catch (IMapperException $e) { $this->logger->debug('Could not find share', ['exception' => $e]); - throw new OCSBadRequestException('Could not find share'); + throw new OCSNotFoundException('Could not find share'); } if ($formId !== $formShare->getFormId()) { @@ -240,19 +263,19 @@ public function updateShare(int $formId, int $shareId, array $keyValuePairs): Da if ($form->getOwnerId() !== $this->currentUser->getUID()) { $this->logger->debug('This form is not owned by the current user'); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('This form is not owned by the current user'); } // Don't allow empty array if (sizeof($keyValuePairs) === 0) { $this->logger->info('Empty keyValuePairs, will not update.'); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('Empty keyValuePairs, will not update'); } //Don't allow to change other properties than permissions if (count($keyValuePairs) > 1 || !key_exists('permissions', $keyValuePairs)) { $this->logger->debug('Not allowed to update other properties than permissions'); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('Not allowed to update other properties than permissions'); } if (!$this->validatePermissions($keyValuePairs['permissions'], $formShare->getShareType())) { @@ -302,9 +325,10 @@ public function updateShare(int $formId, int $shareId, array $keyValuePairs): Da * * @param int $formId of the form * @param int $shareId of the share to delete - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException + * @return DataResponse, Http::STATUS_OK, array<>> + * @throws OCSBadRequestException Share doesn't belong to given Form + * @throws OCSForbiddenException This form is not owned by the current user + * @throws OCSNotFoundException Could not find share */ #[CORS()] #[NoAdminRequired()] @@ -320,7 +344,7 @@ public function deleteShare(int $formId, int $shareId): DataResponse { $form = $this->formMapper->findById($formId); } catch (IMapperException $e) { $this->logger->debug('Could not find share', ['exception' => $e]); - throw new OCSBadRequestException('Could not find share'); + throw new OCSNotFoundException('Could not find share'); } if ($formId !== $share->getFormId()) { @@ -330,7 +354,7 @@ public function deleteShare(int $formId, int $shareId): DataResponse { if ($form->getOwnerId() !== $this->currentUser->getUID()) { $this->logger->debug('This form is not owned by the current user'); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('This form is not owned by the current user'); } $this->shareMapper->delete($share); diff --git a/lib/ResponseDefinitions.php b/lib/ResponseDefinitions.php new file mode 100644 index 000000000..e5f0945e8 --- /dev/null +++ b/lib/ResponseDefinitions.php @@ -0,0 +1,113 @@ + + * + * @author Christian Hartmann + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Forms; + +/** + * @psalm-type FormsForm = array{ + * "id": int, + * "hash": string, + * "title": string, + * "description": string, + * "ownerId": string, + * "created": int, + * "access": stdClass, + * "expires": int, + * "isAnonymous": bool, + * "submitMultiple": bool, + * "showExpiration": bool, + * "canSubmit": bool, + * "permissions": array, + * "questions": array, + * "state": int, + * "shares": array, + * "submissions": array, + * } + * + * @psalm-type FormsPartialForm = array{ + * "id": int, + * "hash": string, + * "title": string, + * "expires": int, + * "permissions": array, + * "partial": bool, + * "state": int + * } + * + * @psalm-type FormsQuestion = array{ + * "id": int, + * "formId": int, + * "order": int, + * "type": string, + * "isRequired": bool, + * "text": string, + * "name": string, + * "options": array, + * "accept": array, + * "extraSettings": stdClass + * } + * + * @psalm-type FormsOption = array{ + * "id": int, + * "questionId": int, + * "text": string, + * "order": ?int + * } + * + * @psalm-type FormsSubmissions = { + * "submissions": array, + * "questions": array + * } + * + * @psalm-type FormsSubmission = array{ + * "id": int, + * "formId": int, + * "userId": string, + * "timestamp": int, + * "answers": array, + * "userDisplayName": string + * } + * + * @psalm-type FormsAnswer = array{ + * "id": int, + * "submissionId": int, + * "questionId": int, + * "text": string + * } + * + * @psalm-type FormsUploadedFile = array{ + * "uploadedFileId": int, + * "fileName": string + * } + * + * @psalm-type FormsShare = array{ + * "id": int, + * "formId": int, + * "shareType": int, + * "shareWith": string, + * "permissions": array, + * "displayName": string + * } + */ +class ResponseDefinitions { +} diff --git a/openapi.json b/openapi.json new file mode 100644 index 000000000..0b3547ae1 --- /dev/null +++ b/openapi.json @@ -0,0 +1,3155 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "forms", + "version": "0.0.1", + "description": "📝 Simple surveys and questionnaires, self-hosted", + "license": { + "name": "agpl" + } + }, + "components": { + "securitySchemes": { + "basic_auth": { + "type": "http", + "scheme": "basic" + }, + "bearer_auth": { + "type": "http", + "scheme": "bearer" + } + }, + "schemas": { + "Capabilities": { + "type": "object", + "required": [ + "forms" + ], + "properties": { + "forms": { + "type": "object", + "required": [ + "version", + "apiVersions" + ], + "properties": { + "version": { + "type": "string" + }, + "apiVersions": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + "OCSMeta": { + "type": "object", + "required": [ + "status", + "statuscode" + ], + "properties": { + "status": { + "type": "string" + }, + "statuscode": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "totalitems": { + "type": "string" + }, + "itemsperpage": { + "type": "string" + } + } + } + } + }, + "paths": { + "/ocs/v2.php/apps/forms/api/v3/forms": { + "get": { + "operationId": "api-get-forms", + "summary": "Get all forms available to the user (owned/shared)", + "tags": [ + "api" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "type", + "in": "query", + "description": "The type of forms to retrieve. Defaults to `owned`. Possible values: - `owned`: Forms owned by the user. - `shared`: Forms shared with the user.", + "schema": { + "type": "string", + "default": "owned" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "400": { + "description": "wrong form type supplied", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + }, + "post": { + "operationId": "api-new-form", + "summary": "Create a new form and return the form Return a copy of the form if the parameter $fromId is set", + "tags": [ + "api" + ], + "security": [ + { + "basic_auth": [] + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "fromId": { + "type": "integer", + "format": "int64", + "nullable": true, + "description": "(optional) Id of the form that should be cloned" + } + } + } + } + } + }, + "parameters": [ + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "403": { + "description": "The user is not allowed to create forms", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/forms/api/v3/forms/{formId}": { + "get": { + "operationId": "api-get-form", + "summary": "Read all information to edit a Form (form, questions, options, except submissions/answers)", + "tags": [ + "api" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "formId", + "in": "path", + "description": "Id of the form", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "400": { + "description": "Could not find form", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "403": { + "description": "User has no permissions to get this form", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + }, + "patch": { + "operationId": "api-update-form", + "summary": "Writes the given key-value pairs into Database", + "tags": [ + "api" + ], + "security": [ + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "keyValuePairs" + ], + "properties": { + "keyValuePairs": { + "type": "object", + "description": "Array of key=>value pairs to update.", + "additionalProperties": { + "type": "object" + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "formId", + "in": "path", + "description": "FormId of form to update", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "400": { + "description": "Could not find new form owner", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "403": { + "description": "User is not allowed to modify the form", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "404": { + "description": "Form not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + }, + "delete": { + "operationId": "api-delete-form", + "summary": "Delete a form", + "tags": [ + "api" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "formId", + "in": "path", + "description": "the form id", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "403": { + "description": "User is not allowed to delete the form", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "404": { + "description": "Form not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/forms/api/v3/forms/{formId}/questions": { + "get": { + "operationId": "api-get-questions", + "summary": "Read all questions (including options)", + "tags": [ + "api" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "formId", + "in": "path", + "description": "the form id", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "403": { + "description": "User has no permissions to get this form", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "404": { + "description": "Could not find form", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + }, + "post": { + "operationId": "api-new-question", + "summary": "Add a new question", + "tags": [ + "api" + ], + "security": [ + { + "basic_auth": [] + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "the new question type" + }, + "text": { + "type": "string", + "default": "", + "description": "the new question title" + }, + "fromId": { + "type": "integer", + "format": "int64", + "nullable": true, + "description": "(optional) id of the question that should be cloned" + } + } + } + } + } + }, + "parameters": [ + { + "name": "formId", + "in": "path", + "description": "the form id", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "400": { + "description": "Datetime question type no longer supported", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "403": { + "description": "This form is archived and can not be modified", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "404": { + "description": "Could not find question", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + }, + "patch": { + "operationId": "api-reorder-questions", + "summary": "Updates the Order of all Questions of a Form", + "tags": [ + "api" + ], + "security": [ + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "newOrder" + ], + "properties": { + "newOrder": { + "type": "object", + "description": "Array of Question-Ids in new order.", + "additionalProperties": { + "type": "integer", + "format": "int64" + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "formId", + "in": "path", + "description": "Id of the form to reorder", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "400": { + "description": "One question has already been marked as deleted", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "403": { + "description": "User has no permissions to get this form", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "404": { + "description": "Could not find question", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/forms/api/v3/forms/{formId}/questions/{questionId}": { + "get": { + "operationId": "api-get-question", + "summary": "Read a specific question (including options)", + "tags": [ + "api" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "formId", + "in": "path", + "description": "FormId", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "questionId", + "in": "path", + "description": "QuestionId", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "400": { + "description": "Question doesn\\'t belong to given Form", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "403": { + "description": "User has no permissions to get this form", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "404": { + "description": "Could not find form", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + }, + "patch": { + "operationId": "api-update-question", + "summary": "Writes the given key-value pairs into Database Key `order` should only be changed by reorderQuestions() and is not allowed here", + "tags": [ + "api" + ], + "security": [ + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "keyValuePairs" + ], + "properties": { + "keyValuePairs": { + "type": "object", + "description": "Array of key=>value pairs to update.", + "additionalProperties": { + "type": "object" + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "formId", + "in": "path", + "description": "the form id", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "questionId", + "in": "path", + "description": "id of question to update", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "400": { + "description": "Invalid extraSettings, will not update.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "403": { + "description": "User has no permissions to get this form", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "404": { + "description": "Could not find question", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + }, + "delete": { + "operationId": "api-delete-question", + "summary": "Delete a question", + "tags": [ + "api" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "formId", + "in": "path", + "description": "the form id", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "questionId", + "in": "path", + "description": "the question id", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "400": { + "description": "Question doesn\\'t belong to given Form", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "403": { + "description": "User has no permissions to get this form", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "404": { + "description": "Could not find question", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/forms/api/v3/forms/{formId}/questions/{questionId}/options": { + "post": { + "operationId": "api-new-option", + "summary": "Add a new option to a question", + "tags": [ + "api" + ], + "security": [ + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "optionTexts" + ], + "properties": { + "optionTexts": { + "type": "array", + "description": "the new option text", + "items": { + "type": "string" + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "formId", + "in": "path", + "description": "id of the form", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "questionId", + "in": "path", + "description": "id of the question", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "400": { + "description": "This question is not part ot the given form", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "403": { + "description": "Current user has no permission to edit", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "404": { + "description": "Could not find question", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + }, + "patch": { + "operationId": "api-reorder-options", + "summary": "Reorder options for a given question", + "tags": [ + "api" + ], + "security": [ + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "newOrder" + ], + "properties": { + "newOrder": { + "type": "object", + "description": "Array of option ids in new order.", + "additionalProperties": { + "type": "integer", + "format": "int64" + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "formId", + "in": "path", + "description": "id of form", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "questionId", + "in": "path", + "description": "id of question", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "400": { + "description": "This option is not part of the given question", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "403": { + "description": "Current user has no permission to edit", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "404": { + "description": "Could not find question", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/forms/api/v3/forms/{formId}/questions/{questionId}/options/{optionId}": { + "patch": { + "operationId": "api-update-option", + "summary": "Writes the given key-value pairs into Database", + "tags": [ + "api" + ], + "security": [ + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "keyValuePairs" + ], + "properties": { + "keyValuePairs": { + "type": "object", + "description": "Array of key=>value pairs to update.", + "required": [ + "key", + "value" + ], + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "object" + } + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "formId", + "in": "path", + "description": "id of form", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "questionId", + "in": "path", + "description": "id of question", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "optionId", + "in": "path", + "description": "id of option to update", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "400": { + "description": "The given option id doesn't match the question or form", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "403": { + "description": "Not allowed to update id or questionId", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "404": { + "description": "Could not find option or question", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + }, + "delete": { + "operationId": "api-delete-option", + "summary": "Delete an option", + "tags": [ + "api" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "formId", + "in": "path", + "description": "id of form", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "questionId", + "in": "path", + "description": "id of question", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "optionId", + "in": "path", + "description": "id of option to update", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "400": { + "description": "The given option id doesn't match the question or form", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "403": { + "description": "Current user has no permission to edit", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "404": { + "description": "Could not find question or option", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/forms/api/v3/forms/{formId}/submissions": { + "get": { + "operationId": "api-get-submissions", + "summary": "Get all the submissions of a given form", + "tags": [ + "api" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "formId", + "in": "path", + "description": "of the form", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "fileFormat", + "in": "query", + "description": "the file format that should be used for the download. Defaults to `null` Possible values: - `csv`: Comma-separated value - `ods`: OpenDocument Spreadsheet - `xlsx`: Excel Open XML Spreadsheet", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "404": { + "description": "Could not find form", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "403": { + "description": "The current user has no permission to get the results for this form", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + }, + "delete": { + "operationId": "api-delete-all-submissions", + "summary": "Delete all submissions of a specified form", + "tags": [ + "api" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "formId", + "in": "path", + "description": "the form id", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "404": { + "description": "Could not find form", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "403": { + "description": "This form is not owned by the current user and user has no `results_delete` permission", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + }, + "post": { + "operationId": "api-new-submission", + "summary": "Process a new submission", + "tags": [ + "api" + ], + "security": [ + {}, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "answers" + ], + "properties": { + "answers": { + "type": "object", + "description": "[question_id => arrayOfString]", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "shareHash": { + "type": "string", + "default": "", + "description": "public share-hash -> Necessary to submit on public link-shares." + } + } + } + } + } + }, + "parameters": [ + { + "name": "formId", + "in": "path", + "description": "the form id", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "400": { + "description": "At least one submitted answer is not valid", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "403": { + "description": "This form is not owned by the current user and user has no `results_delete` permission", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "404": { + "description": "Could not find form", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/forms/api/v3/forms/{formId}/submissions/{submissionId}": { + "delete": { + "operationId": "api-delete-submission", + "summary": "Delete a specific submission", + "tags": [ + "api" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "formId", + "in": "path", + "description": "the form id", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "submissionId", + "in": "path", + "description": "the submission id", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "400": { + "description": "Submission doesn't belong to given form", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "404": { + "description": "Could not find form or submission", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "403": { + "description": "This form is not owned by the current user and user has no `results_delete` permission", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/forms/api/v3/forms/{formId}/submissions/export": { + "post": { + "operationId": "api-export-submissions-to-cloud", + "summary": "Export Submissions to the Cloud", + "tags": [ + "api" + ], + "security": [ + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "path", + "fileFormat" + ], + "properties": { + "path": { + "type": "string", + "description": "The Cloud-Path to export to" + }, + "fileFormat": { + "type": "string", + "description": "File format used for export" + } + } + } + } + } + }, + "parameters": [ + { + "name": "formId", + "in": "path", + "description": "of the form", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "403": { + "description": "The current user has no permission to get the results for this form", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "404": { + "description": "Could not find form", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/forms/api/v3/forms/{formId}/submissions/files/{questionId}": { + "post": { + "operationId": "api-upload-files", + "summary": "Uploads a temporary files to the server during form filling", + "tags": [ + "api" + ], + "security": [ + {}, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "shareHash": { + "type": "string", + "default": "", + "description": "hash of the form share" + } + } + } + } + } + }, + "parameters": [ + { + "name": "formId", + "in": "path", + "description": "id of the form", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "questionId", + "in": "path", + "description": "id of the question", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "400": { + "description": "File type is not allowed", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "403": { + "description": "Already submitted", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "404": { + "description": "Could not find question", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/forms/api/v3/forms/{formId}/shares": { + "post": { + "operationId": "share_api-new-share", + "summary": "Add a new share", + "tags": [ + "share_api" + ], + "security": [ + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "shareType" + ], + "properties": { + "shareType": { + "type": "integer", + "format": "int64", + "description": "Nextcloud-ShareType" + }, + "shareWith": { + "type": "string", + "default": "", + "description": "ID of user/group/... to share with. For Empty shareWith and shareType Link, this will be set as RandomID." + }, + "permissions": { + "type": "array", + "default": [], + "description": "the permissions granted on the share, defaults to `submit`\n Possible values:\n - `submit` user can submit\n - `results` user can see the results\n - `results_delete` user can see and delete results", + "items": { + "type": "string" + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "formId", + "in": "path", + "description": "The form to share", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "400": { + "description": "Teams app is disabled", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "403": { + "description": "This form is not owned by the current user", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "404": { + "description": "Could not find form", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/forms/api/v3/forms/{formId}/shares/{shareId}": { + "patch": { + "operationId": "share_api-update-share", + "summary": "Update permissions of a share", + "tags": [ + "share_api" + ], + "security": [ + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "keyValuePairs" + ], + "properties": { + "keyValuePairs": { + "type": "object", + "description": "Array of key=>value pairs to update.", + "required": [ + "key", + "values" + ], + "properties": { + "key": { + "type": "string" + }, + "values": { + "type": "object" + } + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "formId", + "in": "path", + "description": "of the form", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "shareId", + "in": "path", + "description": "of the share to update", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "400": { + "description": "Invalid permission given", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "403": { + "description": "Not allowed to update other properties than permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "404": { + "description": "Could not find share", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + }, + "delete": { + "operationId": "share_api-delete-share", + "summary": "Delete a share", + "tags": [ + "share_api" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "formId", + "in": "path", + "description": "of the form", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "shareId", + "in": "path", + "description": "of the share to delete", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "400": { + "description": "Share doesn't belong to given Form", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "403": { + "description": "This form is not owned by the current user", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "404": { + "description": "Could not find share", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + } + } + }, + "tags": [] +} diff --git a/tests/Integration/Api/ApiV3Test.php b/tests/Integration/Api/ApiV3Test.php index a36079c74..110dcd14e 100644 --- a/tests/Integration/Api/ApiV3Test.php +++ b/tests/Integration/Api/ApiV3Test.php @@ -421,7 +421,7 @@ public function testGetNewForm(array $expected): void { $this->assertEqualsWithDelta(time(), $data['lastUpdated'], 10); unset($data['lastUpdated']); - $this->assertEquals(200, $resp->getStatusCode()); + $this->assertEquals(201, $resp->getStatusCode()); $this->assertEquals($expected, $data); } @@ -623,7 +623,7 @@ public function testCloneForm(array $expected): void { $this->assertTrue(time() - $data['lastUpdated'] < 10); unset($data['lastUpdated']); - $this->assertEquals(200, $resp->getStatusCode()); + $this->assertEquals(201, $resp->getStatusCode()); $this->assertEquals($expected, $data); } @@ -740,7 +740,7 @@ public function testCreateNewQuestion(array $expected): void { unset($data['order']); unset($data['id']); - $this->assertEquals(200, $resp->getStatusCode()); + $this->assertEquals(201, $resp->getStatusCode()); $this->assertEquals($expected, $data); } @@ -860,7 +860,7 @@ public function testCloneQuestion() { $data = $this->OcsResponse2Data($resp); $this->testForms[0]['questions'][] = $data; - $this->assertEquals(200, $resp->getStatusCode()); + $this->assertEquals(201, $resp->getStatusCode()); $this->assertNotEquals($data['id'], $this->testForms[0]['questions'][0]['id']); $copy = $this->testForms[0]['questions'][0]; @@ -903,7 +903,7 @@ public function testCreateNewOption(array $expected): void { unset($data['questionId']); unset($data['id']); - $this->assertEquals(200, $resp->getStatusCode()); + $this->assertEquals(201, $resp->getStatusCode()); $this->assertEquals($expected, $data); } @@ -1001,7 +1001,7 @@ public function testAddShare(array $expected) { // Store for cleanup $this->testForms[0]['shares'][] = $data; - $this->assertEquals(200, $resp->getStatusCode()); + $this->assertEquals(201, $resp->getStatusCode()); $this->assertEquals($this->testForms[0]['id'], $data['formId']); unset($data['formId']); unset($data['id']); @@ -1315,7 +1315,7 @@ public function testNewSubmission() { ]); $data = $this->OcsResponse2Data($resp); - $this->assertEquals(200, $resp->getStatusCode()); + $this->assertEquals(201, $resp->getStatusCode()); // Check stored submissions $resp = $this->http->request('GET', "api/v3/forms/{$this->testForms[0]['id']}/submissions"); diff --git a/tests/Unit/Controller/ApiControllerTest.php b/tests/Unit/Controller/ApiControllerTest.php index fa27ba42d..a5a899944 100644 --- a/tests/Unit/Controller/ApiControllerTest.php +++ b/tests/Unit/Controller/ApiControllerTest.php @@ -63,6 +63,7 @@ function is_uploaded_file(string|bool|null $filename) { use OCA\Forms\Service\FormsService; use OCA\Forms\Service\SubmissionService; use OCA\Forms\Tests\Unit\MockedMapperException; +use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataDownloadResponse; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\OCS\OCSBadRequestException; @@ -406,30 +407,6 @@ public function dataTestCreateNewForm() { * @dataProvider dataTestCreateNewForm() */ public function testCreateNewForm($expectedForm) { - // Create a partial mock, as we only test newForm and not getForm - /** @var ApiController|MockObject */ - $apiController = $this->getMockBuilder(ApiController::class) - ->onlyMethods(['getForm']) - ->setConstructorArgs(['forms', - $this->request, - $this->createUserSession(), - $this->answerMapper, - $this->formMapper, - $this->optionMapper, - $this->questionMapper, - $this->shareMapper, - $this->submissionMapper, - $this->configService, - $this->formsService, - $this->submissionService, - $this->l10n, - $this->logger, - $this->userManager, - $this->storage, - $this->uploadedFileMapper, - $this->mimeTypeDetector, - ])->getMock(); - $this->configService->expects($this->once()) ->method('canCreateForms') ->willReturn(true); @@ -448,11 +425,7 @@ public function testCreateNewForm($expectedForm) { $form->setId(7); return $form; }); - $apiController->expects($this->once()) - ->method('getForm') - ->with(7) - ->willReturn(new DataResponse('succeeded')); - $this->assertEquals(new DataResponse('succeeded'), $apiController->newForm()); + $this->assertEquals(new DataResponse([], Http::STATUS_CREATED), $this->apiController->newForm()); } public function dataCloneForm_exceptions() { @@ -568,35 +541,7 @@ public function testCloneForm($old, $new) { ->with(7) ->willReturn([]); - /** @var ApiController|MockObject */ - $apiController = $this->getMockBuilder(ApiController::class) - ->onlyMethods(['getForm']) - ->setConstructorArgs(['forms', - $this->request, - $this->createUserSession(), - $this->answerMapper, - $this->formMapper, - $this->optionMapper, - $this->questionMapper, - $this->shareMapper, - $this->submissionMapper, - $this->configService, - $this->formsService, - $this->submissionService, - $this->l10n, - $this->logger, - $this->userManager, - $this->storage, - $this->uploadedFileMapper, - $this->mimeTypeDetector, - ]) - ->getMock(); - - $apiController->expects($this->once()) - ->method('getForm') - ->with(14) - ->willReturn(new DataResponse('success')); - $this->assertEquals(new DataResponse('success'), $apiController->newForm(7)); + $this->assertEquals(new DataResponse([], Http::STATUS_CREATED), $this->apiController->newForm(7)); } private function formAccess(bool $hasUserAccess = true, bool $hasFormExpired = false, bool $canSubmit = true) { @@ -826,7 +771,7 @@ public function testNewSubmission_formNotFound() { ->method('findById') ->with(1) ->willThrowException($exception); - $this->expectException(OCSBadRequestException::class); + $this->expectException(OCSNotFoundException::class); $this->apiController->newSubmission(1, [], ''); } @@ -900,7 +845,7 @@ public function testDeleteSubmissionNotFound() { ->with(42) ->willThrowException($exception); - $this->expectException(OCSBadRequestException::class); + $this->expectException(OCSNotFoundException::class); $this->apiController->deleteSubmission(1, 42); } diff --git a/tests/Unit/Controller/ShareApiControllerTest.php b/tests/Unit/Controller/ShareApiControllerTest.php index 588945238..688ae66f2 100644 --- a/tests/Unit/Controller/ShareApiControllerTest.php +++ b/tests/Unit/Controller/ShareApiControllerTest.php @@ -37,10 +37,11 @@ use OCA\Forms\Service\FormsService; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\IMapperException; +use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\OCS\OCSBadRequestException; -use OCP\AppFramework\OCS\OCSException; use OCP\AppFramework\OCS\OCSForbiddenException; +use OCP\AppFramework\OCS\OCSNotFoundException; use OCP\Files\File; use OCP\Files\Folder; use OCP\Files\IRootFolder; @@ -243,7 +244,7 @@ public function testValidNewShare(int $shareType, string $shareWith, array $perm ->willReturn($shareWith . ' DisplayName'); // Share Form '5' to 'user1' of share-type 'user=0' - $expectedResponse = new DataResponse($expected); + $expectedResponse = new DataResponse($expected, Http::STATUS_CREATED); $this->assertEquals($expectedResponse, $this->shareApiController->newShare(5, $shareType, $shareWith, $permissions)); } @@ -326,7 +327,7 @@ public function testNewLinkShare(int $shareType, string $shareWith, array $permi if ($exception === null) { // Share the form. - $expectedResponse = new DataResponse($expected); + $expectedResponse = new DataResponse($expected, Http::STATUS_CREATED); $this->assertEquals($expectedResponse, $this->shareApiController->newShare(5, $shareType, $shareWith, $permissions)); } else { $this->expectException($exception); @@ -363,7 +364,7 @@ public function testNewLinkShare_ExistingHash() { $this->shareMapper->expects($this->never()) ->method('insert'); - $this->expectException(OCSException::class); + $this->expectException(OCSBadRequestException::class); $this->shareApiController->newShare(5, IShare::TYPE_LINK, ''); } @@ -382,7 +383,7 @@ public function testNewLinkShare_PublicLinkNotAllowed() { $this->shareMapper->expects($this->never()) ->method('insert'); - $this->expectException(OCSException::class); + $this->expectException(OCSForbiddenException::class); $this->shareApiController->newShare(5, IShare::TYPE_LINK, ''); } @@ -489,7 +490,7 @@ public function testCirclesShare_circlesAppDisabled() { ->willReturn(false); - $this->expectException(OCSException::class); + $this->expectException(OCSBadRequestException::class); // Share Form '5' to 'noCircle' $this->shareApiController->newShare(5, IShare::TYPE_CIRCLE, 'noCircle'); @@ -505,7 +506,7 @@ public function testShareUnknownForm() { ->will($this->throwException(new DoesNotExistException('Form not found'))); ; - $this->expectException(OCSBadRequestException::class); + $this->expectException(OCSNotFoundException::class); $this->shareApiController->newShare(5, 0, 'user1'); } @@ -564,7 +565,7 @@ public function testDeleteUnknownShare() { ->will($this->throwException(new DoesNotExistException('Share not found'))); ; - $this->expectException(OCSBadRequestException::class); + $this->expectException(OCSNotFoundException::class); $this->shareApiController->deleteShare(1, 8); } @@ -845,7 +846,7 @@ public function testUpdateShare_NotExistingShare() { $this->logger->expects($this->exactly(2)) ->method('debug'); - $this->expectException(OCSBadRequestException::class); + $this->expectException(OCSNotFoundException::class); $this->shareApiController->updateShare(1, 1337, [Constants::PERMISSION_SUBMIT]); } @@ -875,7 +876,7 @@ public function testUpdateShare_NotExistingForm() { $this->logger->expects($this->exactly(2)) ->method('debug'); - $this->expectException(OCSBadRequestException::class); + $this->expectException(OCSNotFoundException::class); $this->shareApiController->updateShare(7331, 1337, [Constants::PERMISSION_SUBMIT]); } }