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