This commit is contained in:
194
tests/Integration/ProgramSessionsTest.php
Normal file
194
tests/Integration/ProgramSessionsTest.php
Normal file
@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Integration;
|
||||
|
||||
use Tests\Support\DatabaseSeeder;
|
||||
use Tests\Support\QdbTestCase;
|
||||
|
||||
final class ProgramSessionsTest extends QdbTestCase
|
||||
{
|
||||
public function testAdminCrudAndMobileFetch(): void
|
||||
{
|
||||
$token = $this->adminToken();
|
||||
$qnId = $this->fixture()->questionnaireId;
|
||||
|
||||
$create = $this->api()->request('POST', 'program_sessions', [
|
||||
'name' => 'Baseline',
|
||||
'description' => 'First visit',
|
||||
'sessionNumber' => 1,
|
||||
'orderIndex' => 0,
|
||||
'isActive' => true,
|
||||
'questionnaires' => [
|
||||
['questionnaireID' => $qnId, 'orderIndex' => 0],
|
||||
],
|
||||
], ['Authorization' => 'Bearer ' . $token]);
|
||||
|
||||
$this->assertApiOk($create);
|
||||
$sessionID = $create['data']['session']['sessionID'] ?? '';
|
||||
$this->assertNotSame('', $sessionID);
|
||||
$this->assertSame('Baseline', $create['data']['session']['name']);
|
||||
$this->assertCount(1, $create['data']['session']['questionnaires']);
|
||||
|
||||
$list = $this->api()->request('GET', 'program_sessions', null, [
|
||||
'Authorization' => 'Bearer ' . $token,
|
||||
]);
|
||||
$this->assertApiOk($list);
|
||||
$this->assertCount(1, $list['data']['sessions']);
|
||||
$this->assertFalse($list['data']['settings']['autoRepeatSessions'] ?? true);
|
||||
|
||||
$update = $this->api()->request('PUT', 'program_sessions', [
|
||||
'sessionID' => $sessionID,
|
||||
'name' => 'Baseline updated',
|
||||
'sessionNumber' => 2,
|
||||
'questionnaires' => [],
|
||||
], ['Authorization' => 'Bearer ' . $token]);
|
||||
$this->assertApiOk($update);
|
||||
$this->assertSame('Baseline updated', $update['data']['session']['name']);
|
||||
$this->assertSame([], $update['data']['session']['questionnaires']);
|
||||
|
||||
$mobile = $this->coachMobileLogin();
|
||||
$appRes = $this->api()->request('GET', 'app_questionnaires', null, [
|
||||
'Authorization' => 'Bearer ' . $mobile['token'],
|
||||
], ['sessions' => '1']);
|
||||
$this->assertApiOk($appRes);
|
||||
$this->assertCount(1, $appRes['data']['sessions']);
|
||||
$this->assertSame($sessionID, $appRes['data']['sessions'][0]['sessionID']);
|
||||
$this->assertArrayHasKey('settings', $appRes['data']);
|
||||
|
||||
$delete = $this->api()->request('DELETE', 'program_sessions', [
|
||||
'sessionID' => $sessionID,
|
||||
], ['Authorization' => 'Bearer ' . $token]);
|
||||
$this->assertApiOk($delete);
|
||||
|
||||
$empty = $this->api()->request('GET', 'program_sessions', null, [
|
||||
'Authorization' => 'Bearer ' . $token,
|
||||
]);
|
||||
$this->assertApiOk($empty);
|
||||
$this->assertSame([], $empty['data']['sessions']);
|
||||
}
|
||||
|
||||
public function testAutoRepeatAdvancesCycleAfterLastSessionCompleted(): void
|
||||
{
|
||||
$adminToken = $this->adminToken();
|
||||
$qnId = $this->fixture()->questionnaireId;
|
||||
$clientCode = $this->fixture()->clientCode;
|
||||
|
||||
$this->assertApiOk($this->api()->request('PATCH', 'program_sessions', [
|
||||
'autoRepeatSessions' => true,
|
||||
], ['Authorization' => 'Bearer ' . $adminToken]));
|
||||
|
||||
$session1 = $this->api()->request('POST', 'program_sessions', [
|
||||
'name' => 'Session 1',
|
||||
'sessionNumber' => 1,
|
||||
'orderIndex' => 0,
|
||||
'isActive' => true,
|
||||
'questionnaires' => [['questionnaireID' => $qnId, 'orderIndex' => 0]],
|
||||
], ['Authorization' => 'Bearer ' . $adminToken]);
|
||||
$this->assertApiOk($session1);
|
||||
|
||||
$session2 = $this->api()->request('POST', 'program_sessions', [
|
||||
'name' => 'Session 2',
|
||||
'sessionNumber' => 2,
|
||||
'orderIndex' => 1,
|
||||
'isActive' => true,
|
||||
'questionnaires' => [['questionnaireID' => $qnId, 'orderIndex' => 0]],
|
||||
], ['Authorization' => 'Bearer ' . $adminToken]);
|
||||
$this->assertApiOk($session2);
|
||||
|
||||
$login = $this->coachMobileLogin();
|
||||
$submit = $this->submitQuestionnaireAs(
|
||||
$login['token'],
|
||||
$qnId,
|
||||
$clientCode,
|
||||
[['questionID' => $this->fixture()->questionShortId, 'answerOptionKey' => 'yes']],
|
||||
);
|
||||
$this->assertApiOk($submit);
|
||||
$this->assertTrue($submit['data']['cycleAdvanced'] ?? false);
|
||||
$this->assertSame(2, $submit['data']['cycleNumber'] ?? 0);
|
||||
|
||||
$clientsRes = $this->api()->withMobileToken($login['token'], 'GET', 'app_questionnaires', null, [
|
||||
'clients' => '1',
|
||||
]);
|
||||
$this->assertApiOk($clientsRes);
|
||||
$plain = qdb_decrypt_sensitive_envelope($clientsRes['data'], $login['token']);
|
||||
$payload = json_decode($plain, true, 512, JSON_THROW_ON_ERROR);
|
||||
$client = null;
|
||||
foreach ($payload['clients'] as $row) {
|
||||
if (($row['clientCode'] ?? '') === $clientCode) {
|
||||
$client = $row;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->assertNotNull($client);
|
||||
$progress = $client['programProgress'];
|
||||
$this->assertSame(2, $progress['cycleNumber']);
|
||||
$this->assertSame(2, $progress['totalSessions']);
|
||||
$this->assertSame(0, $progress['sessionsCompletedInCycle']);
|
||||
$this->assertTrue($progress['autoRepeatSessions']);
|
||||
$this->assertFalse($progress['programComplete']);
|
||||
}
|
||||
|
||||
public function testAutoRepeatDisabledKeepsProgramComplete(): void
|
||||
{
|
||||
$adminToken = $this->adminToken();
|
||||
$qnId = $this->fixture()->questionnaireId;
|
||||
$clientCode = $this->fixture()->clientCode;
|
||||
|
||||
$this->assertApiOk($this->api()->request('PATCH', 'program_sessions', [
|
||||
'autoRepeatSessions' => false,
|
||||
], ['Authorization' => 'Bearer ' . $adminToken]));
|
||||
|
||||
$this->assertApiOk($this->api()->request('POST', 'program_sessions', [
|
||||
'name' => 'Only session',
|
||||
'sessionNumber' => 1,
|
||||
'orderIndex' => 0,
|
||||
'isActive' => true,
|
||||
'questionnaires' => [['questionnaireID' => $qnId, 'orderIndex' => 0]],
|
||||
], ['Authorization' => 'Bearer ' . $adminToken]));
|
||||
|
||||
$login = $this->coachMobileLogin();
|
||||
$submit = $this->submitQuestionnaireAs(
|
||||
$login['token'],
|
||||
$qnId,
|
||||
$clientCode,
|
||||
[['questionID' => $this->fixture()->questionShortId, 'answerOptionKey' => 'yes']],
|
||||
);
|
||||
$this->assertApiOk($submit);
|
||||
$this->assertFalse($submit['data']['cycleAdvanced'] ?? true);
|
||||
$this->assertSame(1, $submit['data']['cycleNumber'] ?? 0);
|
||||
|
||||
$clientsRes = $this->api()->withMobileToken($login['token'], 'GET', 'app_questionnaires', null, [
|
||||
'clients' => '1',
|
||||
]);
|
||||
$this->assertApiOk($clientsRes);
|
||||
$plain = qdb_decrypt_sensitive_envelope($clientsRes['data'], $login['token']);
|
||||
$payload = json_decode($plain, true, 512, JSON_THROW_ON_ERROR);
|
||||
$progress = $payload['clients'][0]['programProgress'];
|
||||
$this->assertTrue($progress['cycleComplete']);
|
||||
$this->assertTrue($progress['programComplete']);
|
||||
$this->assertFalse($progress['autoRepeatSessions']);
|
||||
}
|
||||
|
||||
public function testRejectUnknownQuestionnaire(): void
|
||||
{
|
||||
$token = $this->adminToken();
|
||||
$res = $this->api()->request('POST', 'program_sessions', [
|
||||
'name' => 'Invalid',
|
||||
'questionnaires' => [
|
||||
['questionnaireID' => 'does-not-exist'],
|
||||
],
|
||||
], ['Authorization' => 'Bearer ' . $token]);
|
||||
$this->assertApiError($res, 'INVALID_FIELD', 400);
|
||||
}
|
||||
|
||||
public function testCoachCannotCreateSession(): void
|
||||
{
|
||||
$mobile = $this->coachMobileLogin();
|
||||
$res = $this->api()->request('POST', 'program_sessions', [
|
||||
'name' => 'Blocked',
|
||||
], ['Authorization' => 'Bearer ' . $mobile['token']]);
|
||||
$this->assertApiError($res, 'FORBIDDEN', 403);
|
||||
}
|
||||
}
|
||||
55
tests/Unit/ProgramSessionsTest.php
Normal file
55
tests/Unit/ProgramSessionsTest.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Tests\Support\DatabaseSeeder;
|
||||
use Tests\Support\TestFilesystem;
|
||||
|
||||
final class ProgramSessionsTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
TestFilesystem::resetUploads();
|
||||
if (defined('QDB_PATH') && is_file(QDB_PATH)) {
|
||||
@unlink(QDB_PATH);
|
||||
}
|
||||
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
||||
DatabaseSeeder::seed($pdo);
|
||||
require_once dirname(__DIR__, 2) . '/lib/program_sessions.php';
|
||||
|
||||
$sessionID = bin2hex(random_bytes(8));
|
||||
$now = time();
|
||||
$pdo->prepare(
|
||||
'INSERT INTO program_session
|
||||
(sessionID, name, description, sessionNumber, orderIndex, isActive, createdAt, updatedAt)
|
||||
VALUES (:id, :name, "", 1, 0, 1, :t, :t)'
|
||||
)->execute([':id' => $sessionID, ':name' => 'S1', ':t' => $now]);
|
||||
qdb_sync_program_session_questionnaires($pdo, $sessionID, [
|
||||
['questionnaireID' => 'qn-test', 'orderIndex' => 0],
|
||||
]);
|
||||
qdb_save($tmpDb, $lockFp);
|
||||
}
|
||||
|
||||
public function testAdvanceCycleWhenSessionComplete(): void
|
||||
{
|
||||
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
||||
require_once dirname(__DIR__, 2) . '/lib/program_sessions.php';
|
||||
qdb_program_session_settings_save($pdo, ['autoRepeatSessions' => true]);
|
||||
|
||||
$now = time();
|
||||
$pdo->prepare(
|
||||
"INSERT INTO completed_questionnaire
|
||||
(clientCode, questionnaireID, status, completedAt, sumPoints)
|
||||
VALUES ('CLIENT-TEST-001', 'qn-test', 'completed', :t, 0)"
|
||||
)->execute([':t' => $now]);
|
||||
|
||||
$this->assertTrue(qdb_maybe_advance_program_cycle($pdo, 'CLIENT-TEST-001'));
|
||||
$cycle = qdb_client_program_cycle_get($pdo, 'CLIENT-TEST-001');
|
||||
$this->assertSame(2, $cycle['cycleNumber']);
|
||||
qdb_save($tmpDb, $lockFp);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user