This commit is contained in:
211
tests/Unit/ScoringTest.php
Normal file
211
tests/Unit/ScoringTest.php
Normal file
@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use PDO;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class ScoringTest extends TestCase
|
||||
{
|
||||
private PDO $pdo;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
require_once dirname(__DIR__, 2) . '/common.php';
|
||||
require_once dirname(__DIR__, 2) . '/lib/scoring.php';
|
||||
|
||||
$this->pdo = new PDO('sqlite::memory:');
|
||||
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
$this->pdo->exec('PRAGMA foreign_keys = ON');
|
||||
$schema = file_get_contents(dirname(__DIR__, 2) . '/schema.sql');
|
||||
$this->pdo->exec($schema);
|
||||
}
|
||||
|
||||
public function testBandForTotal(): void
|
||||
{
|
||||
$legacy = ['greenMax' => 12, 'yellowMax' => 36];
|
||||
$this->assertSame('green', qdb_band_for_total(0, $legacy));
|
||||
$this->assertSame('green', qdb_band_for_total(12, $legacy));
|
||||
$this->assertSame('yellow', qdb_band_for_total(13, $legacy));
|
||||
$this->assertSame('yellow', qdb_band_for_total(36, $legacy));
|
||||
$this->assertSame('red', qdb_band_for_total(37, $legacy));
|
||||
|
||||
$custom = [
|
||||
'greenMin' => 5,
|
||||
'greenMax' => 15,
|
||||
'yellowMin' => 16,
|
||||
'yellowMax' => 30,
|
||||
'redMin' => 31,
|
||||
];
|
||||
$this->assertSame('green', qdb_band_for_total(5, $custom));
|
||||
$this->assertSame('green', qdb_band_for_total(15, $custom));
|
||||
$this->assertSame('yellow', qdb_band_for_total(16, $custom));
|
||||
$this->assertSame('red', qdb_band_for_total(31, $custom));
|
||||
}
|
||||
|
||||
public function testValidateScoringBands(): void
|
||||
{
|
||||
$this->assertNull(qdb_validate_scoring_bands(qdb_normalize_scoring_bands(['greenMax' => 12, 'yellowMax' => 36])));
|
||||
$this->assertNotNull(qdb_validate_scoring_bands([
|
||||
'greenMin' => 0, 'greenMax' => 20,
|
||||
'yellowMin' => 15, 'yellowMax' => 36,
|
||||
'redMin' => 37,
|
||||
]));
|
||||
}
|
||||
|
||||
public function testGlassScoreRulesFromSymptoms(): void
|
||||
{
|
||||
$rules = qdb_score_rules_from_glass_symptoms([
|
||||
['key' => 'pain', 'scoreLevels' => ['never_glass' => 0, 'extreme_glass' => 5]],
|
||||
]);
|
||||
$this->assertCount(5, $rules);
|
||||
$painExtreme = array_values(array_filter(
|
||||
$rules,
|
||||
fn ($r) => $r['scopeKey'] === 'pain' && $r['levelKey'] === 'extreme_glass'
|
||||
));
|
||||
$this->assertSame(5, $painExtreme[0]['points'] ?? null);
|
||||
}
|
||||
|
||||
private function seedCoachChain(): void
|
||||
{
|
||||
$this->pdo->exec("INSERT INTO supervisor (supervisorID, username) VALUES ('sup1', 'sup')");
|
||||
$this->pdo->exec("INSERT INTO coach (coachID, supervisorID, username) VALUES ('coach1', 'sup1', 'coach')");
|
||||
}
|
||||
|
||||
public function testRadioAndGlassScoring(): void
|
||||
{
|
||||
$this->seedCoachChain();
|
||||
$qnID = 'qn_test';
|
||||
$this->pdo->exec("INSERT INTO questionnaire (questionnaireID, name, version, state, orderIndex, showPoints, conditionJson, categoryKey)
|
||||
VALUES ('$qnID', 'Test', '1', 'active', 0, 0, '{}', '')");
|
||||
|
||||
$radioQ = "{$qnID}__radio";
|
||||
$this->pdo->exec("INSERT INTO question (questionID, questionnaireID, defaultText, type, orderIndex, isRequired, configJson)
|
||||
VALUES ('$radioQ', '$qnID', 'Pick one', 'radio_question', 0, 1, '{}')");
|
||||
$this->pdo->exec("INSERT INTO answer_option (answerOptionID, questionID, defaultText, points, orderIndex, nextQuestionId)
|
||||
VALUES ('ao1', '$radioQ', 'yes', 3, 0, '')");
|
||||
|
||||
$glassQ = "{$qnID}__glass";
|
||||
$this->pdo->exec("INSERT INTO question (questionID, questionnaireID, defaultText, type, orderIndex, isRequired, configJson)
|
||||
VALUES ('$glassQ', '$qnID', 'Symptoms', 'glass_scale_question', 1, 1, '{\"symptoms\":[\"pain\"]}')");
|
||||
qdb_sync_question_score_rules($this->pdo, $glassQ, [
|
||||
['scopeKey' => 'pain', 'levelKey' => 'moderate_glass', 'points' => 2],
|
||||
]);
|
||||
|
||||
$client = 'CLIENT-001';
|
||||
$this->pdo->exec("INSERT INTO client (clientCode, coachID) VALUES ('$client', 'coach1')");
|
||||
$this->pdo->exec("INSERT INTO client_answer (clientCode, questionID, answerOptionID, freeTextValue, numericValue)
|
||||
VALUES ('$client', '$radioQ', 'ao1', NULL, NULL)");
|
||||
$this->pdo->exec("INSERT INTO client_answer (clientCode, questionID, answerOptionID, freeTextValue, numericValue)
|
||||
VALUES ('$client', '$glassQ', NULL, '{\"pain\":\"moderate_glass\"}', NULL)");
|
||||
|
||||
$score = qdb_compute_questionnaire_score($this->pdo, $client, $qnID);
|
||||
$this->assertSame(5, $score);
|
||||
}
|
||||
|
||||
public function testIncompleteProfileProducesNoResult(): void
|
||||
{
|
||||
$this->seedCoachChain();
|
||||
$qnA = 'qn_a';
|
||||
$qnB = 'qn_b';
|
||||
foreach ([$qnA, $qnB] as $id) {
|
||||
$this->pdo->exec("INSERT INTO questionnaire (questionnaireID, name, version, state, orderIndex, showPoints, conditionJson, categoryKey)
|
||||
VALUES ('$id', '$id', '1', 'active', 0, 0, '{}', '')");
|
||||
}
|
||||
$profileID = 'profile1';
|
||||
$this->pdo->exec("INSERT INTO scoring_profile (profileID, name, description, isActive,
|
||||
greenMin, greenMax, yellowMin, yellowMax, redMin, createdAt, updatedAt)
|
||||
VALUES ('$profileID', 'Combo', '', 1, 0, 10, 11, 20, 21, 0, 0)");
|
||||
$this->pdo->exec("INSERT INTO scoring_profile_questionnaire (profileID, questionnaireID, weight, orderIndex)
|
||||
VALUES ('$profileID', '$qnA', 1.0, 0), ('$profileID', '$qnB', 1.0, 1)");
|
||||
|
||||
$client = 'CLIENT-002';
|
||||
$this->pdo->exec("INSERT INTO client (clientCode, coachID) VALUES ('$client', 'coach1')");
|
||||
$this->pdo->exec("INSERT INTO completed_questionnaire (clientCode, questionnaireID, status, sumPoints, completedAt)
|
||||
VALUES ('$client', '$qnA', 'completed', 5, 1)");
|
||||
|
||||
qdb_recompute_profile_scores_for_client($this->pdo, $client);
|
||||
|
||||
$stmt = $this->pdo->prepare('SELECT COUNT(*) FROM client_scoring_profile_result WHERE clientCode = :cc');
|
||||
$stmt->execute([':cc' => $client]);
|
||||
$this->assertSame(0, (int)$stmt->fetchColumn());
|
||||
}
|
||||
|
||||
public function testCoachScoringReviewPreservesComputedBand(): void
|
||||
{
|
||||
$this->seedCoachChain();
|
||||
$qnID = 'qn_review';
|
||||
$this->pdo->exec("INSERT INTO questionnaire (questionnaireID, name, version, state, orderIndex, showPoints, conditionJson, categoryKey)
|
||||
VALUES ('$qnID', 'Review', '1', 'active', 0, 0, '{}', '')");
|
||||
$profileID = 'profile_review';
|
||||
$this->pdo->exec("INSERT INTO scoring_profile (profileID, name, description, isActive,
|
||||
greenMin, greenMax, yellowMin, yellowMax, redMin, createdAt, updatedAt)
|
||||
VALUES ('$profileID', 'Review profile', '', 1, 0, 10, 11, 20, 21, 0, 0)");
|
||||
$this->pdo->exec("INSERT INTO scoring_profile_questionnaire (profileID, questionnaireID, weight, orderIndex)
|
||||
VALUES ('$profileID', '$qnID', 1.0, 0)");
|
||||
|
||||
$client = 'CLIENT-REVIEW';
|
||||
$this->pdo->exec("INSERT INTO client (clientCode, coachID) VALUES ('$client', 'coach1')");
|
||||
$this->pdo->exec("INSERT INTO completed_questionnaire (clientCode, questionnaireID, status, sumPoints, completedAt)
|
||||
VALUES ('$client', '$qnID', 'completed', 15, 1)");
|
||||
|
||||
qdb_recompute_profile_scores_for_client($this->pdo, $client);
|
||||
|
||||
$row = $this->pdo->query(
|
||||
"SELECT band, coachBand FROM client_scoring_profile_result WHERE clientCode = '$client'"
|
||||
)->fetch(PDO::FETCH_ASSOC);
|
||||
$this->assertSame('yellow', $row['band']);
|
||||
$this->assertSame('', $row['coachBand']);
|
||||
|
||||
qdb_set_coach_scoring_band($this->pdo, $client, $profileID, 'green', 'user1');
|
||||
|
||||
$row = $this->pdo->query(
|
||||
"SELECT band, coachBand FROM client_scoring_profile_result WHERE clientCode = '$client'"
|
||||
)->fetch(PDO::FETCH_ASSOC);
|
||||
$this->assertSame('yellow', $row['band']);
|
||||
$this->assertSame('green', $row['coachBand']);
|
||||
|
||||
qdb_recompute_profile_scores_for_client($this->pdo, $client);
|
||||
$row = $this->pdo->query(
|
||||
"SELECT band, coachBand FROM client_scoring_profile_result WHERE clientCode = '$client'"
|
||||
)->fetch(PDO::FETCH_ASSOC);
|
||||
$this->assertSame('yellow', $row['band']);
|
||||
$this->assertSame('green', $row['coachBand']);
|
||||
}
|
||||
|
||||
public function testSaveCoachReviewFromAppBeforeServerRecompute(): void
|
||||
{
|
||||
$this->seedCoachChain();
|
||||
$qnID = 'qn_pre';
|
||||
$this->pdo->exec("INSERT INTO questionnaire (questionnaireID, name, version, state, orderIndex, showPoints, conditionJson, categoryKey)
|
||||
VALUES ('$qnID', 'Pre', '1', 'active', 0, 0, '{}', '')");
|
||||
$profileID = 'profile_pre';
|
||||
$this->pdo->exec("INSERT INTO scoring_profile (profileID, name, description, isActive,
|
||||
greenMin, greenMax, yellowMin, yellowMax, redMin, createdAt, updatedAt)
|
||||
VALUES ('$profileID', 'Pre profile', '', 1, 0, 10, 11, 20, 21, 0, 0)");
|
||||
$this->pdo->exec("INSERT INTO scoring_profile_questionnaire (profileID, questionnaireID, weight, orderIndex)
|
||||
VALUES ('$profileID', '$qnID', 1.0, 0)");
|
||||
|
||||
$client = 'CLIENT-PRE';
|
||||
$this->pdo->exec("INSERT INTO client (clientCode, coachID) VALUES ('$client', 'coach1')");
|
||||
|
||||
qdb_save_coach_scoring_review(
|
||||
$this->pdo,
|
||||
$client,
|
||||
$profileID,
|
||||
'yellow',
|
||||
'user1',
|
||||
15.0,
|
||||
'yellow'
|
||||
);
|
||||
|
||||
$row = $this->pdo->query(
|
||||
"SELECT band, coachBand, weightedTotal FROM client_scoring_profile_result WHERE clientCode = '$client'"
|
||||
)->fetch(PDO::FETCH_ASSOC);
|
||||
$this->assertSame('yellow', $row['band']);
|
||||
$this->assertSame('yellow', $row['coachBand']);
|
||||
$this->assertSame(15.0, (float)$row['weightedTotal']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user