This commit is contained in:
126
tests/Support/ApiTestCase.php
Normal file
126
tests/Support/ApiTestCase.php
Normal file
@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
|
||||
namespace Tests\Support;
|
||||
|
||||
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
use QdbHttpResponse;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
* In-process API tests via qdb_dispatch_api_request().
|
||||
|
||||
*
|
||||
|
||||
* Error envelopes:
|
||||
|
||||
* - Handlers: { ok: false, error: { code, message } } — use assertError()
|
||||
|
||||
* - Auth middleware (require_valid_token, require_role): { error: "message" } — use assertLegacyAuthError()
|
||||
|
||||
*/
|
||||
|
||||
abstract class ApiTestCase extends TestCase
|
||||
|
||||
{
|
||||
|
||||
protected ?string $bearerToken = null;
|
||||
|
||||
|
||||
|
||||
protected function setUp(): void
|
||||
|
||||
{
|
||||
|
||||
parent::setUp();
|
||||
|
||||
DatabaseFixture::forTests()->reset();
|
||||
|
||||
$this->bearerToken = null;
|
||||
|
||||
unset($GLOBALS['__TEST_JSON_BODY']);
|
||||
|
||||
$_GET = [];
|
||||
|
||||
$_POST = [];
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
* @return array{status: int, body: array<string, mixed>, raw: string}
|
||||
|
||||
*/
|
||||
|
||||
protected function api(
|
||||
|
||||
string $method,
|
||||
|
||||
string $route,
|
||||
|
||||
?array $jsonBody = null,
|
||||
|
||||
?string $token = null,
|
||||
|
||||
array $query = [],
|
||||
|
||||
): array {
|
||||
|
||||
$_GET = $query;
|
||||
|
||||
$_POST = [];
|
||||
|
||||
$_SERVER['REQUEST_METHOD'] = strtoupper($method);
|
||||
|
||||
$_SERVER['REQUEST_URI'] = '/api/' . ltrim($route, '/');
|
||||
|
||||
$_SERVER['SCRIPT_NAME'] = '/api/index.php';
|
||||
|
||||
$_SERVER['HTTP_AUTHORIZATION'] = ($token ?? $this->bearerToken)
|
||||
|
||||
? 'Bearer ' . ($token ?? $this->bearerToken)
|
||||
|
||||
: '';
|
||||
|
||||
|
||||
|
||||
if ($jsonBody !== null) {
|
||||
|
||||
$GLOBALS['__TEST_JSON_BODY'] = json_encode($jsonBody, JSON_THROW_ON_ERROR);
|
||||
|
||||
} else {
|
||||
|
||||
unset($GLOBALS['__TEST_JSON_BODY']);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
ob_start();
|
||||
|
||||
$status = 200;
|
||||
|
||||
$body = [];
|
||||
|
||||
|
||||
|
||||
try {
|
||||
|
||||
require_once dirname(__DIR__, 2) . '/api/dispatch.php';
|
||||
|
||||
qdb_dispatch_api_request();
|
||||
|
||||
} catch (QdbHttpResponse $e) {
|
||||
|
||||
186
tests/Support/DatabaseFixture.php
Normal file
186
tests/Support/DatabaseFixture.php
Normal file
@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Support;
|
||||
|
||||
use PDO;
|
||||
|
||||
final class DatabaseFixture
|
||||
{
|
||||
public const PASSWORD = 'testpass123';
|
||||
|
||||
public const ADMIN_USERNAME = 'testadmin';
|
||||
public const SUPERVISOR1_USERNAME = 'testsuper1';
|
||||
public const SUPERVISOR2_USERNAME = 'testsuper2';
|
||||
public const COACH1_USERNAME = 'testcoach1';
|
||||
public const COACH2_USERNAME = 'testcoach2';
|
||||
public const MUST_CHANGE_USERNAME = 'testmustchange';
|
||||
|
||||
public const ADMIN_ID = 'admin001';
|
||||
public const SUPERVISOR1_ID = 'sup001';
|
||||
public const SUPERVISOR2_ID = 'sup002';
|
||||
public const COACH1_ID = 'coach001';
|
||||
public const COACH2_ID = 'coach002';
|
||||
|
||||
public const CLIENT_COACH1 = 'CLIENT-001';
|
||||
public const CLIENT_COACH1B = 'CLIENT-002';
|
||||
public const CLIENT_COACH2 = 'CLIENT-OTHER';
|
||||
|
||||
public const QUESTIONNAIRE_ID = 'questionnaire_test_demo';
|
||||
|
||||
public function __construct(
|
||||
private readonly string $uploadsDir,
|
||||
private readonly string $schemaPath,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function forTests(): self
|
||||
{
|
||||
return new self(
|
||||
__DIR__ . '/../var/uploads',
|
||||
__DIR__ . '/../fixtures/schema.sql',
|
||||
);
|
||||
}
|
||||
|
||||
public static function forE2E(string $projectRoot): self
|
||||
{
|
||||
return new self(
|
||||
$projectRoot . '/uploads',
|
||||
__DIR__ . '/../fixtures/schema.sql',
|
||||
);
|
||||
}
|
||||
|
||||
public function reset(): void
|
||||
{
|
||||
if (!is_dir($this->uploadsDir)) {
|
||||
mkdir($this->uploadsDir, 0755, true);
|
||||
}
|
||||
|
||||
$dbFile = $this->uploadsDir . '/questionnaire_database';
|
||||
$lockFile = $this->uploadsDir . '/.qdb_lock';
|
||||
if (is_file($dbFile)) {
|
||||
unlink($dbFile);
|
||||
}
|
||||
if (is_file($lockFile)) {
|
||||
unlink($lockFile);
|
||||
}
|
||||
|
||||
if (!defined('QDB_PATH')) {
|
||||
define('QDB_PATH', $dbFile);
|
||||
}
|
||||
if (!defined('QDB_LOCK')) {
|
||||
define('QDB_LOCK', $lockFile);
|
||||
}
|
||||
if (!defined('QDB_SCHEMA')) {
|
||||
define('QDB_SCHEMA', $this->schemaPath);
|
||||
}
|
||||
|
||||
require_once dirname(__DIR__, 2) . '/common.php';
|
||||
require_once dirname(__DIR__, 2) . '/db_init.php';
|
||||
|
||||
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
||||
$this->seed($pdo);
|
||||
qdb_save($tmpDb, $lockFp);
|
||||
}
|
||||
|
||||
private function seed(PDO $pdo): void
|
||||
{
|
||||
$now = time();
|
||||
$hash = password_hash(self::PASSWORD, PASSWORD_DEFAULT);
|
||||
$mustChangeHash = password_hash(self::PASSWORD, PASSWORD_DEFAULT);
|
||||
|
||||
$pdo->exec('DELETE FROM session');
|
||||
$pdo->exec('DELETE FROM client_answer');
|
||||
$pdo->exec('DELETE FROM completed_questionnaire');
|
||||
$pdo->exec('DELETE FROM answer_option_translation');
|
||||
$pdo->exec('DELETE FROM question_translation');
|
||||
$pdo->exec('DELETE FROM string_translation');
|
||||
$pdo->exec('DELETE FROM answer_option');
|
||||
$pdo->exec('DELETE FROM question');
|
||||
$pdo->exec('DELETE FROM questionnaire');
|
||||
$pdo->exec('DELETE FROM client');
|
||||
$pdo->exec('DELETE FROM coach');
|
||||
$pdo->exec('DELETE FROM supervisor');
|
||||
$pdo->exec('DELETE FROM admin');
|
||||
$pdo->exec('DELETE FROM users');
|
||||
$pdo->exec('DELETE FROM language');
|
||||
|
||||
$insUser = $pdo->prepare(
|
||||
'INSERT INTO users (userID, username, passwordHash, role, entityID, mustChangePassword, createdAt)
|
||||
VALUES (:uid, :u, :h, :role, :eid, :mcp, :ca)'
|
||||
);
|
||||
|
||||
$insUser->execute([
|
||||
':uid' => 'user_admin', ':u' => self::ADMIN_USERNAME, ':h' => $hash,
|
||||
':role' => 'admin', ':eid' => self::ADMIN_ID, ':mcp' => 0, ':ca' => $now,
|
||||
]);
|
||||
$insUser->execute([
|
||||
':uid' => 'user_sup1', ':u' => self::SUPERVISOR1_USERNAME, ':h' => $hash,
|
||||
':role' => 'supervisor', ':eid' => self::SUPERVISOR1_ID, ':mcp' => 0, ':ca' => $now,
|
||||
]);
|
||||
$insUser->execute([
|
||||
':uid' => 'user_sup2', ':u' => self::SUPERVISOR2_USERNAME, ':h' => $hash,
|
||||
':role' => 'supervisor', ':eid' => self::SUPERVISOR2_ID, ':mcp' => 0, ':ca' => $now,
|
||||
]);
|
||||
$insUser->execute([
|
||||
':uid' => 'user_coach1', ':u' => self::COACH1_USERNAME, ':h' => $hash,
|
||||
':role' => 'coach', ':eid' => self::COACH1_ID, ':mcp' => 0, ':ca' => $now,
|
||||
]);
|
||||
$insUser->execute([
|
||||
':uid' => 'user_coach2', ':u' => self::COACH2_USERNAME, ':h' => $hash,
|
||||
':role' => 'coach', ':eid' => self::COACH2_ID, ':mcp' => 0, ':ca' => $now,
|
||||
]);
|
||||
$insUser->execute([
|
||||
':uid' => 'user_mustchange', ':u' => self::MUST_CHANGE_USERNAME, ':h' => $mustChangeHash,
|
||||
':role' => 'coach', ':eid' => self::COACH1_ID, ':mcp' => 1, ':ca' => $now,
|
||||
]);
|
||||
|
||||
$pdo->prepare('INSERT INTO admin (adminID, username, location) VALUES (?, ?, ?)')
|
||||
->execute([self::ADMIN_ID, self::ADMIN_USERNAME, 'Test']);
|
||||
$pdo->prepare('INSERT INTO supervisor (supervisorID, username, location) VALUES (?, ?, ?)')
|
||||
->execute([self::SUPERVISOR1_ID, self::SUPERVISOR1_USERNAME, 'Loc1']);
|
||||
$pdo->prepare('INSERT INTO supervisor (supervisorID, username, location) VALUES (?, ?, ?)')
|
||||
->execute([self::SUPERVISOR2_ID, self::SUPERVISOR2_USERNAME, 'Loc2']);
|
||||
$pdo->prepare('INSERT INTO coach (coachID, supervisorID, username) VALUES (?, ?, ?)')
|
||||
->execute([self::COACH1_ID, self::SUPERVISOR1_ID, self::COACH1_USERNAME]);
|
||||
$pdo->prepare('INSERT INTO coach (coachID, supervisorID, username) VALUES (?, ?, ?)')
|
||||
->execute([self::COACH2_ID, self::SUPERVISOR2_ID, self::COACH2_USERNAME]);
|
||||
|
||||
$pdo->prepare('INSERT INTO client (clientCode, coachID) VALUES (?, ?)')
|
||||
->execute([self::CLIENT_COACH1, self::COACH1_ID]);
|
||||
$pdo->prepare('INSERT INTO client (clientCode, coachID) VALUES (?, ?)')
|
||||
->execute([self::CLIENT_COACH1B, self::COACH1_ID]);
|
||||
$pdo->prepare('INSERT INTO client (clientCode, coachID) VALUES (?, ?)')
|
||||
->execute([self::CLIENT_COACH2, self::COACH2_ID]);
|
||||
|
||||
$pdo->prepare(
|
||||
'INSERT INTO questionnaire (questionnaireID, name, version, state, orderIndex, showPoints, conditionJson)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)'
|
||||
)->execute([
|
||||
self::QUESTIONNAIRE_ID,
|
||||
'Test Demo',
|
||||
'1.0',
|
||||
'active',
|
||||
0,
|
||||
1,
|
||||
'{}',
|
||||
]);
|
||||
|
||||
$qid = self::QUESTIONNAIRE_ID . '__q1';
|
||||
$pdo->prepare(
|
||||
'INSERT INTO question (questionID, questionnaireID, defaultText, type, orderIndex, isRequired, configJson)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)'
|
||||
)->execute([$qid, self::QUESTIONNAIRE_ID, 'consent_instruction', 'radio_question', 0, 1, '{}']);
|
||||
|
||||
$pdo->prepare(
|
||||
'INSERT INTO answer_option (answerOptionID, questionID, defaultText, points, orderIndex, nextQuestionId)
|
||||
VALUES (?, ?, ?, ?, ?, ?)'
|
||||
)->execute(['ao1', $qid, 'consent_signed', 5, 0, '']);
|
||||
|
||||
$pdo->prepare(
|
||||
'INSERT INTO answer_option (answerOptionID, questionID, defaultText, points, orderIndex, nextQuestionId)
|
||||
VALUES (?, ?, ?, ?, ?, ?)'
|
||||
)->execute(['ao2', $qid, 'consent_not_signed', 0, 1, '']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user