184 lines
7.5 KiB
PHP
184 lines
7.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Integration;
|
|
|
|
use Tests\Support\DatabaseSeeder;
|
|
use Tests\Support\QdbTestCase;
|
|
|
|
final class AppSubmitExtendedTest extends QdbTestCase
|
|
{
|
|
public function testResubmitCreatesSecondSubmissionVersion(): void
|
|
{
|
|
$login = $this->api()->loginMobileAndGetToken(
|
|
DatabaseSeeder::COACH_USERNAME,
|
|
DatabaseSeeder::PASSWORD
|
|
);
|
|
$f = $this->fixture();
|
|
|
|
$this->assertApiOk($this->submitFixtureQuestionnaire());
|
|
$this->assertSame(1, $this->submissionVersionCount($f->clientCode, $f->questionnaireId));
|
|
|
|
$res = $this->submitQuestionnaireAs($login['token'], $f->questionnaireId, $f->clientCode, [
|
|
['questionID' => $f->questionShortId, 'answerOptionKey' => 'yes'],
|
|
]);
|
|
$this->assertApiOk($res);
|
|
$this->assertTrue($res['data']['submitted'] ?? false);
|
|
$this->assertSame(2, $this->submissionVersionCount($f->clientCode, $f->questionnaireId));
|
|
|
|
[$pdo, $tmpDb, $lockFp] = qdb_open(false);
|
|
$stmt = $pdo->prepare(
|
|
'SELECT COUNT(*) FROM client_answer_submission cas
|
|
INNER JOIN questionnaire_submission qs ON qs.submissionID = cas.submissionID
|
|
WHERE qs.clientCode = :cc AND qs.questionnaireID = :qn'
|
|
);
|
|
$stmt->execute([':cc' => $f->clientCode, ':qn' => $f->questionnaireId]);
|
|
$this->assertGreaterThanOrEqual(2, (int)$stmt->fetchColumn());
|
|
qdb_discard($tmpDb, $lockFp);
|
|
}
|
|
|
|
public function testSupervisorCanSubmitForClient(): void
|
|
{
|
|
$login = $this->api()->loginMobileAndGetToken(
|
|
DatabaseSeeder::SUPERVISOR_USERNAME,
|
|
DatabaseSeeder::PASSWORD
|
|
);
|
|
$f = $this->fixture();
|
|
$res = $this->submitQuestionnaireAs($login['token'], $f->questionnaireId, $f->clientCode, [
|
|
['questionID' => $f->questionShortId, 'answerOptionKey' => 'yes'],
|
|
]);
|
|
$this->assertApiOk($res);
|
|
|
|
[$pdo, $tmpDb, $lockFp] = qdb_open(false);
|
|
$stmt = $pdo->prepare(
|
|
'SELECT submittedByRole FROM questionnaire_submission
|
|
WHERE clientCode = :cc AND questionnaireID = :qn ORDER BY version DESC LIMIT 1'
|
|
);
|
|
$stmt->execute([':cc' => $f->clientCode, ':qn' => $f->questionnaireId]);
|
|
$this->assertSame('supervisor', $stmt->fetchColumn());
|
|
qdb_discard($tmpDb, $lockFp);
|
|
}
|
|
|
|
public function testSubmitWithFreeTextAndGlassSymptom(): void
|
|
{
|
|
$login = $this->api()->loginMobileAndGetToken(
|
|
DatabaseSeeder::COACH_USERNAME,
|
|
DatabaseSeeder::PASSWORD
|
|
);
|
|
$f = $this->fixture();
|
|
$res = $this->submitQuestionnaireAs($login['token'], $f->questionnaireId, $f->clientCode, [
|
|
['questionID' => $f->questionShortId, 'answerOptionKey' => 'yes'],
|
|
['questionID' => $f->freeTextShortId, 'freeTextValue' => 'Feeling better'],
|
|
['questionID' => $f->glassSymptomKey, 'freeTextValue' => 'moderate'],
|
|
]);
|
|
$this->assertApiOk($res);
|
|
|
|
[$pdo, $tmpDb, $lockFp] = qdb_open(false);
|
|
$ft = $pdo->prepare(
|
|
'SELECT freeTextValue FROM client_answer WHERE clientCode = :cc AND questionID = :qid'
|
|
);
|
|
$ft->execute([':cc' => $f->clientCode, ':qid' => $f->freeTextQuestionId]);
|
|
$this->assertSame('Feeling better', $ft->fetchColumn());
|
|
|
|
$glass = $pdo->prepare(
|
|
'SELECT freeTextValue FROM client_answer WHERE clientCode = :cc AND questionID = :qid'
|
|
);
|
|
$glass->execute([':cc' => $f->clientCode, ':qid' => $f->glassQuestionId]);
|
|
$json = (string)$glass->fetchColumn();
|
|
$decoded = json_decode($json, true);
|
|
$this->assertIsArray($decoded);
|
|
$this->assertSame('moderate', $decoded[$f->glassSymptomKey] ?? null);
|
|
qdb_discard($tmpDb, $lockFp);
|
|
}
|
|
|
|
public function testCoachLoadsEncryptedAnswersBundle(): void
|
|
{
|
|
$this->submitFixtureQuestionnaire();
|
|
$login = $this->api()->loginMobileAndGetToken(
|
|
DatabaseSeeder::COACH_USERNAME,
|
|
DatabaseSeeder::PASSWORD
|
|
);
|
|
$f = $this->fixture();
|
|
$res = $this->api()->withMobileToken($login['token'], 'GET', 'app_questionnaires', null, [
|
|
'clientCode' => $f->clientCode,
|
|
'answers' => '1',
|
|
]);
|
|
$this->assertApiOk($res);
|
|
$this->assertTrue($res['data']['encrypted'] ?? false);
|
|
$plain = qdb_decrypt_sensitive_envelope($res['data'], $login['token']);
|
|
$payload = json_decode($plain, true, 512, JSON_THROW_ON_ERROR);
|
|
$this->assertSame($f->clientCode, $payload['clientCode']);
|
|
$this->assertNotEmpty($payload['questionnaires']);
|
|
}
|
|
|
|
public function testMultiCheckSubmitAccumulatesPoints(): void
|
|
{
|
|
$login = $this->api()->loginMobileAndGetToken(
|
|
DatabaseSeeder::COACH_USERNAME,
|
|
DatabaseSeeder::PASSWORD
|
|
);
|
|
$f = $this->fixture();
|
|
$res = $this->submitQuestionnaireAs($login['token'], $f->questionnaireId, $f->clientCode, [
|
|
['questionID' => $f->questionShortId, 'answerOptionKey' => 'yes'],
|
|
['questionID' => $f->multiCheckShortId, 'answerOptionKey' => 'opt_a'],
|
|
['questionID' => $f->multiCheckShortId, 'answerOptionKey' => 'opt_b'],
|
|
]);
|
|
$this->assertApiOk($res);
|
|
$this->assertSame(3, $res['data']['sumPoints'] ?? 0);
|
|
}
|
|
|
|
public function testRequiredQuestionMissingFailsValidation(): void
|
|
{
|
|
$login = $this->api()->loginMobileAndGetToken(
|
|
DatabaseSeeder::COACH_USERNAME,
|
|
DatabaseSeeder::PASSWORD
|
|
);
|
|
$f = $this->fixture();
|
|
$res = $this->submitQuestionnaireAs($login['token'], $f->questionnaireId, $f->clientCode, [
|
|
['questionID' => $f->freeTextShortId, 'freeTextValue' => 'skipped consent'],
|
|
]);
|
|
$this->assertApiError($res, 'VALIDATION_FAILED', 400);
|
|
$codes = array_column($res['error']['details']['errors'] ?? [], 'code');
|
|
$this->assertContains('MISSING_ANSWER', $codes);
|
|
}
|
|
|
|
public function testActiveListExcludesDraftQuestionnaire(): void
|
|
{
|
|
$login = $this->api()->loginMobileAndGetToken(
|
|
DatabaseSeeder::COACH_USERNAME,
|
|
DatabaseSeeder::PASSWORD
|
|
);
|
|
$f = $this->fixture();
|
|
$res = $this->api()->withMobileToken($login['token'], 'GET', 'app_questionnaires');
|
|
$this->assertApiOk($res);
|
|
$ids = array_column($res['data'], 'id');
|
|
$this->assertContains($f->questionnaireId, $ids);
|
|
$this->assertNotContains($f->draftQuestionnaireId, $ids);
|
|
}
|
|
|
|
public function testTempTokenCannotSubmitQuestionnaire(): void
|
|
{
|
|
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
|
$f = $this->fixture();
|
|
$pdo->prepare('UPDATE users SET mustChangePassword = 1 WHERE userID = :uid')
|
|
->execute([':uid' => $f->supervisorUserId]);
|
|
qdb_save($tmpDb, $lockFp);
|
|
|
|
$login = $this->api()->loginMobile(
|
|
DatabaseSeeder::SUPERVISOR_USERNAME,
|
|
DatabaseSeeder::PASSWORD
|
|
);
|
|
$this->assertApiOk($login);
|
|
$tempToken = $login['data']['token'];
|
|
$res = $this->api()->encryptedPost($tempToken, 'app_questionnaires', [
|
|
'questionnaireID' => $f->questionnaireId,
|
|
'clientCode' => $f->clientCode,
|
|
'answers' => [
|
|
['questionID' => $f->questionShortId, 'answerOptionKey' => 'yes'],
|
|
],
|
|
]);
|
|
$this->assertApiError($res, 'PASSWORD_CHANGE_REQUIRED', 403);
|
|
}
|
|
}
|