Skip to content

Commit

Permalink
Optimization method FormsService::canSubmit
Browse files Browse the repository at this point in the history
Signed-off-by: ailkiv <[email protected]>
  • Loading branch information
AIlkiv committed Jul 10, 2024
1 parent f6c1b8a commit a0b26df
Show file tree
Hide file tree
Showing 8 changed files with 234 additions and 80 deletions.
2 changes: 1 addition & 1 deletion lib/Controller/ApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -1161,7 +1161,7 @@ public function insertSubmission(int $formId, array $answers, string $shareHash

// Ensure the form is unique if needed.
// If we can not submit anymore then the submission must be unique
if (!$this->formsService->canSubmit($form) && !$this->submissionService->isUniqueSubmission($submission)) {
if (!$this->formsService->canSubmit($form) && $this->submissionMapper->hasMultipleFormSubmissionsByUser($form, $submission->getUserId())) {
$this->submissionMapper->delete($submission);
throw new OCSForbiddenException('Already submitted');
}
Expand Down
49 changes: 28 additions & 21 deletions lib/Db/SubmissionMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,36 +86,40 @@ public function findById(int $id): Submission {
}

/**
* @param int $formId
* @throws DoesNotExistException if not found
* @return array
* Сhecks if there are multiple form submissions by user
* @param Form $form of the form to count submissions
* @param string $userId ID of the user to count submissions
*/
public function findParticipantsByForm(int $formId): array {
$qb = $this->db->getQueryBuilder();

$qb->select('user_id')
->from($this->getTableName())
->where(
$qb->expr()->eq('form_id', $qb->createNamedParameter($formId, IQueryBuilder::PARAM_INT))
);

$submissionEntities = $this->findEntities($qb);
public function hasMultipleFormSubmissionsByUser(Form $form, string $userId): bool {
return $this->countSubmissionsWithFilters($form->getId(), $userId, 2) >= 2;
}

// From array of submissionEntities produce array of userIds.
$userIds = array_map(function ($submissionEntity) {
return $submissionEntity->getUserId();
}, $submissionEntities);
/**
* Сhecks if there are form submissions by user
* @param Form $form of the form to count submissions
* @param string $userId ID of the user to count submissions
*/
public function hasFormSubmissionsByUser(Form $form, string $userId): bool {
return (bool)$this->countSubmissionsWithFilters($form->getId(), $userId, 1);
}

return $userIds;
/**
* Count submissions by form
* @param int $formId ID of the form to count submissions
* @throws \Exception
*/
public function countSubmissions(int $formId): int {
return $this->countSubmissionsWithFilters($formId, null, -1);
}

/**
* Count submissions by form and optionally also by userId
* @param int $formId ID of the form to count submissions for
* Count submissions by form with optional filters
* @param int $formId ID of the form to count submissions
* @param string|null $userId optionally limit submissions to the one of that user
* @param int $limit allows to limit the query selection. If -1, the restriction is ignored
* @throws \Exception
*/
public function countSubmissions(int $formId, ?string $userId = null): int {
protected function countSubmissionsWithFilters(int $formId, ?string $userId = null, int $limit = -1): int {

Check warning on line 122 in lib/Db/SubmissionMapper.php

View check run for this annotation

Codecov / codecov/patch

lib/Db/SubmissionMapper.php#L122

Added line #L122 was not covered by tests
$qb = $this->db->getQueryBuilder();

$query = $qb->select($qb->func()->count('*', 'num_submissions'))
Expand All @@ -124,6 +128,9 @@ public function countSubmissions(int $formId, ?string $userId = null): int {
if (!is_null($userId)) {
$query->andWhere($qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
}
if ($limit !== -1) {
$query->setMaxResults($limit);

Check warning on line 132 in lib/Db/SubmissionMapper.php

View check run for this annotation

Codecov / codecov/patch

lib/Db/SubmissionMapper.php#L131-L132

Added lines #L131 - L132 were not covered by tests
}

$result = $query->executeQuery();
$row = $result->fetch();
Expand Down
52 changes: 52 additions & 0 deletions lib/Migration/Version040300Date20240708163401.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2024 Andrii Ilkiv <[email protected]>
*
* @author Andrii Ilkiv <[email protected]>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\Forms\Migration;

use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;

class Version040300Date20240708163401 extends SimpleMigrationStep {
/**
* @param IOutput $output
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
* @return null|ISchemaWrapper
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {

Check warning on line 41 in lib/Migration/Version040300Date20240708163401.php

View check run for this annotation

Codecov / codecov/patch

lib/Migration/Version040300Date20240708163401.php#L41

Added line #L41 was not covered by tests
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();

Check warning on line 43 in lib/Migration/Version040300Date20240708163401.php

View check run for this annotation

Codecov / codecov/patch

lib/Migration/Version040300Date20240708163401.php#L43

Added line #L43 was not covered by tests

$table = $schema->getTable('forms_v2_submissions');
if (!$table->hasIndex('forms_submissions_form_user')) {
$table->addIndex(['form_id', 'user_id'], 'forms_submissions_form_user');

Check warning on line 47 in lib/Migration/Version040300Date20240708163401.php

View check run for this annotation

Codecov / codecov/patch

lib/Migration/Version040300Date20240708163401.php#L45-L47

Added lines #L45 - L47 were not covered by tests
}

return $schema;

Check warning on line 50 in lib/Migration/Version040300Date20240708163401.php

View check run for this annotation

Codecov / codecov/patch

lib/Migration/Version040300Date20240708163401.php#L50

Added line #L50 was not covered by tests
}
}
12 changes: 5 additions & 7 deletions lib/Service/FormsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -335,13 +335,11 @@ public function canSubmit(Form $form): bool {
}

// Refuse access, if SubmitMultiple is not set and user already has taken part.
if (!$form->getSubmitMultiple()) {
$participants = $this->submissionMapper->findParticipantsByForm($form->getId());
foreach ($participants as $participant) {
if ($participant === $this->currentUser->getUID()) {
return false;
}
}
if (
!$form->getSubmitMultiple() &&
$this->submissionMapper->hasFormSubmissionsByUser($form, $this->currentUser->getUID())
) {
return false;
}

return true;
Expand Down
7 changes: 0 additions & 7 deletions lib/Service/SubmissionService.php
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,6 @@ public function getSubmissions(int $formId): array {
}
}

/**
* Validate the new submission is unique
*/
public function isUniqueSubmission(Submission $newSubmission): bool {
return $this->submissionMapper->countSubmissions($newSubmission->getFormId(), $newSubmission->getUserId()) === 1;
}

/**
* Export Submissions to Cloud-Filesystem
*
Expand Down
139 changes: 139 additions & 0 deletions tests/Unit/Db/SubmissionMapperTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<?php

declare(strict_types=1);
/**
* @copyright Copyright (c) 2024 Abdrii Ilkiv <[email protected]>
*
* @author Abdrii Ilkiv <[email protected]>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Forms\Tests\Unit\Db;

use OCA\Forms\Db\Form;
use OCA\Forms\Db\SubmissionMapper;

use Test\TestCase;

class SubmissionMapperTest extends TestCase {

private SubmissionMapper $mockSubmissionMapper;


public function setUp(): void {
parent::setUp();

$this->mockSubmissionMapper = $this->getMockBuilder(SubmissionMapper::class)
->disableOriginalConstructor()
->setMethods(['countSubmissionsWithFilters'])
->getMock();
}

/**
* @dataProvider dataHasMultipleFormSubmissionsByUser
*/
public function testHasMultipleFormSubmissionsByUser(int $numberOfSubmissions, bool $expected) {
$this->mockSubmissionMapper->expects($this->once())
->method('countSubmissionsWithFilters')
->will($this->returnValue($numberOfSubmissions));

$form = new Form();
$form->setId(1);

$this->assertEquals($expected, $this->mockSubmissionMapper->hasMultipleFormSubmissionsByUser($form, 'user1'));
}

public function dataHasMultipleFormSubmissionsByUser() {
return [
[
'numberOfSubmissions' => 0,
'expected' => false,
],
[
'numberOfSubmissions' => 1,
'expected' => false,
],
[
'numberOfSubmissions' => 2,
'expected' => true,
],
[
'numberOfSubmissions' => 3,
'expected' => true,
],
];
}

/**
* @dataProvider dataHasFormSubmissionsByUser
*/
public function testHasFormSubmissionsByUser(int $numberOfSubmissions, bool $expected) {
$this->mockSubmissionMapper->expects($this->once())
->method('countSubmissionsWithFilters')
->will($this->returnValue($numberOfSubmissions));

$form = new Form();
$form->setId(1);

$this->assertEquals($expected, $this->mockSubmissionMapper->hasFormSubmissionsByUser($form, 'user1'));
}

public function dataHasFormSubmissionsByUser() {
return [
[
'numberOfSubmissions' => 0,
'expected' => false,
],
[
'numberOfSubmissions' => 1,
'expected' => true,
],
[
'numberOfSubmissions' => 2,
'expected' => true,
],
];
}

/**
* @dataProvider dataCountSubmissions
*/
public function testCountSubmissions(int $numberOfSubmissions, int $expected) {
$this->mockSubmissionMapper->expects($this->once())
->method('countSubmissionsWithFilters')
->will($this->returnValue($numberOfSubmissions));

$this->assertEquals($expected, $this->mockSubmissionMapper->countSubmissions(1));
}

public function dataCountSubmissions() {
return [
[
'numberOfSubmissions' => 0,
'expected' => 0,
],
[
'numberOfSubmissions' => 1,
'expected' => 1,
],
[
'numberOfSubmissions' => 20,
'expected' => 20,
],
];
}
}
18 changes: 9 additions & 9 deletions tests/Unit/Service/FormsServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -802,25 +802,25 @@ public function dataCanSubmit() {
'allowFormOwner' => [
'ownerId' => 'currentUser',
'submitMultiple' => false,
'participantsArray' => ['currentUser'],
'hasFormSubmissionsByUser' => true,
'expected' => true
],
'submitMultipleGood' => [
'ownerId' => 'someUser',
'submitMultiple' => false,
'participantsArray' => ['notCurrentUser'],
'hasFormSubmissionsByUser' => false,
'expected' => true
],
'submitMultipleNotGood' => [
'ownerId' => 'someUser',
'submitMultiple' => false,
'participantsArray' => ['notCurrentUser', 'currentUser'],
'hasFormSubmissionsByUser' => true,
'expected' => false
],
'submitMultiple' => [
'ownerId' => 'someUser',
'submitMultiple' => true,
'participantsArray' => ['currentUser'],
'hasFormSubmissionsByUser' => true,
'expected' => true
]
];
Expand All @@ -830,10 +830,10 @@ public function dataCanSubmit() {
*
* @param string $ownerId
* @param bool $submitMultiple
* @param array $participantsArray
* @param bool $hasFormSubmissionsByUser
* @param bool $expected
*/
public function testCanSubmit(string $ownerId, bool $submitMultiple, array $participantsArray, bool $expected) {
public function testCanSubmit(string $ownerId, bool $submitMultiple, bool $hasFormSubmissionsByUser, bool $expected) {
$form = new Form();
$form->setId(42);
$form->setAccess([
Expand All @@ -844,9 +844,9 @@ public function testCanSubmit(string $ownerId, bool $submitMultiple, array $part
$form->setSubmitMultiple($submitMultiple);

$this->submissionMapper->expects($this->any())
->method('findParticipantsByForm')
->with(42)
->willReturn($participantsArray);
->method('hasFormSubmissionsByUser')
->with($form, 'currentUser')
->willReturn($hasFormSubmissionsByUser);

$this->assertEquals($expected, $this->formsService->canSubmit($form));
}
Expand Down
Loading

0 comments on commit a0b26df

Please sign in to comment.