Files
barometer-server/tests/Unit/ScoringTest.php
tom.hempel f1caa9e681
Some checks failed
PHPUnit / test (push) Has been cancelled
initial prototype based on nat-as-server
2026-06-29 12:39:55 +02:00

212 lines
9.6 KiB
PHP

<?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']);
}
}