Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions webapp/src/Controller/API/AbstractApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ public function __construct(
protected readonly EventLogService $eventLogService
) {}

/**
* Whether to filter out contests that haven't started yet in calls to getQueryBuilder
* without an explicit value set for $filterBeforeContest.
* Override in subclasses to change this behavior.
*/
protected function shouldFilterBeforeContest(): bool
{
return true;
}

/**
* Get the query builder used for getting contests.
*
Expand Down
24 changes: 16 additions & 8 deletions webapp/src/Controller/API/ContestController.php
Original file line number Diff line number Diff line change
Expand Up @@ -356,24 +356,24 @@ public function setProblemsetAction(Request $request, string $cid, ValidatorInte
public function problemsetAction(Request $request, string $cid): Response
{
/** @var Contest|null $contest */
$contest = $this->getQueryBuilder($request)
$contest = $this->getQueryBuilder($request, filterBeforeContest: true)
->andWhere(sprintf('%s = :id', $this->getIdField()))
->setParameter('id', $cid)
->getQuery()
->getOneOrNullResult();

if ($contest === null) {
throw new NotFoundHttpException(sprintf('Object with ID \'%s\' not found', $cid));
}

$hasAccess = $this->dj->checkrole('jury') ||
$this->dj->checkrole('api_reader') ||
$contest?->getFreezeData()->started();
$contest->getFreezeData()->started();

if (!$hasAccess) {
throw new AccessDeniedHttpException();
}

if ($contest === null) {
throw new NotFoundHttpException(sprintf('Object with ID \'%s\' not found', $cid));
}

if (!$contest->getContestProblemsetType()) {
throw new NotFoundHttpException(sprintf('Contest with ID \'%s\' has no problemset text', $cid));
}
Expand Down Expand Up @@ -950,10 +950,18 @@ public function samplesDataZipAction(Request $request): Response
return $this->dj->getSamplesZipForContest($contest);
}

protected function getQueryBuilder(Request $request, bool $filterBeforeContest = true): QueryBuilder
protected function shouldFilterBeforeContest(): bool
{
return false;
}

protected function getQueryBuilder(Request $request, ?bool $filterBeforeContest = null): QueryBuilder
{
try {
return $this->getContestQueryBuilder($request->query->getBoolean('onlyActive', true), $filterBeforeContest);
return $this->getContestQueryBuilder(
$request->query->getBoolean('onlyActive', true),
$filterBeforeContest ?? $this->shouldFilterBeforeContest()
);
} catch (TypeError) {
throw new BadRequestHttpException('\'onlyActive\' must be a boolean.');
}
Expand Down
45 changes: 45 additions & 0 deletions webapp/tests/Unit/Controller/API/ContestControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Tests\Unit\Controller\API;

use App\DataFixtures\Test\DemoPreStartContestFixture;
use App\Entity\Contest;

class ContestControllerTest extends BaseTestCase
Expand All @@ -26,4 +27,48 @@ class ContestControllerTest extends BaseTestCase
protected array $expectedAbsent = ['4242', 'nonexistent'];

protected ?string $objectClassForExternalId = Contest::class;

/**
* Test that a contest that is activated but not yet started is visible in the list action for a team user.
*/
public function testListShowsActivatedButNotStartedContest(): void
{
$this->loadFixture(DemoPreStartContestFixture::class);

$url = $this->helperGetEndpointURL($this->apiEndpoint);
// Use 'demo' user which has team role
$objects = $this->verifyApiJsonResponse('GET', $url, 200, 'demo');

self::assertIsArray($objects);
self::assertNotEmpty($objects, 'Contest list should not be empty');

// Find the demo contest in the response
$foundContest = null;
foreach ($objects as $contest) {
if ($contest['shortname'] === 'demo') {
$foundContest = $contest;
break;
}
}

self::assertNotNull($foundContest, 'Demo contest should be visible after activation even before start');
self::assertSame('Demo contest', $foundContest['formal_name']);
}

/**
* Test that a contest that is activated but not yet started is visible in the single action for a team user.
*/
public function testSingleShowsActivatedButNotStartedContest(): void
{
$this->loadFixture(DemoPreStartContestFixture::class);

$contestId = $this->resolveEntityId(Contest::class, '1');
$url = $this->helperGetEndpointURL($this->apiEndpoint, $contestId);
// Use 'demo' user which has team role
$contest = $this->verifyApiJsonResponse('GET', $url, 200, 'demo');

self::assertIsArray($contest);
self::assertSame('Demo contest', $contest['formal_name']);
self::assertSame('demo', $contest['shortname']);
}
}
Loading