Files
IronKanban/tests/Support/IntegrationTestCase.php
T
Keith Solomon 812e5c2f2a feature: Initial MVP
2026-04-05 16:20:39 -05:00

201 lines
5.3 KiB
PHP

<?php
// phpcs:disable PEAR.Commenting.FileComment,PEAR.Commenting.ClassComment
/**
* Shared integration test support.
*/
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.PrivateNoUnderscore
// phpcs:disable PEAR.NamingConventions.ValidVariableName.PrivateNoUnderscore
declare(strict_types=1);
namespace Tests\Support;
use IronKanban\Support\HttpHalt;
use IronKanban\Support\FrontMatter;
use PHPUnit\Framework\TestCase;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
/**
* Provides an isolated markdown project workspace for integration tests.
*/
abstract class IntegrationTestCase extends TestCase
{
/**
* Temporary application root for the current test.
*
* @var string
*/
protected string $tempRoot;
/**
* Temporary project storage root for the current test.
*
* @var string
*/
protected string $projectRoot;
/**
* Saved application config to restore after the test.
*
* @var array<string, mixed>
*/
private array $_originalAppConfig;
/**
* Repository root used to resolve actual application scripts.
*
* @var string
*/
private string $_repoRoot;
/**
* Prepare an isolated workspace for each test.
*
* @return void
*/
protected function setUp(): void
{
parent::setUp();
global $appConfig;
$this->_originalAppConfig = is_array($appConfig) ? $appConfig : [];
$this->_repoRoot = (string) ($this->_originalAppConfig['base_path'] ?? dirname(__DIR__, 2));
$this->tempRoot = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'ikb-tests-' . bin2hex(random_bytes(6));
$this->projectRoot = $this->tempRoot . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . 'projects';
mkdir($this->projectRoot, 0777, true);
$appConfig['base_path'] = $this->tempRoot;
$appConfig['project_root'] = $this->projectRoot;
}
/**
* Restore the original workspace after the test.
*
* @return void
*/
protected function tearDown(): void
{
global $appConfig;
$appConfig = $this->_originalAppConfig;
$this->_removeDirectory($this->tempRoot);
parent::tearDown();
}
/**
* Create a markdown-backed project for tests.
*
* @param string $projectId Project identifier.
* @param string $title Project title.
* @param string $body Project body content.
*
* @return string
*/
protected function createProject(string $projectId = 'demo-project', string $title = 'Demo Project', string $body = 'Project body'): string
{
$projectPath = $this->projectRoot . DIRECTORY_SEPARATOR . $projectId;
mkdir($projectPath, 0777, true);
file_put_contents(
$projectPath . DIRECTORY_SEPARATOR . 'index.md',
FrontMatter::dump(
[
'type' => 'project',
'id' => $projectId,
'title' => $title,
],
$body
)
);
return $projectId;
}
/**
* Invoke an API endpoint script with request globals.
*
* @param string $script Relative script path from repo root.
* @param string $method Request method.
* @param array<string, mixed> $get Query parameters.
* @param array<string, mixed> $post Form payload.
* @param array<string, string> $server Additional server variables.
*
* @return array{status:int, payload:array<string, mixed>, raw:string}
*/
protected function callApi(
string $script,
string $method = 'GET',
array $get = [],
array $post = [],
array $server = []
): array {
$_GET = $get;
$_POST = $post;
$_SERVER = array_merge(
[
'REQUEST_METHOD' => strtoupper($method),
],
$server
);
http_response_code(200);
if (function_exists('header_remove')) {
header_remove();
}
ob_start();
try {
include $this->_repoRoot . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $script);
} catch (HttpHalt $halt) {
}
$raw = (string) ob_get_clean();
$decoded = json_decode($raw, true);
self::assertIsArray($decoded, 'Expected a JSON object response from ' . $script . '.');
return [
'status' => http_response_code(),
'payload' => $decoded,
'raw' => $raw,
];
}
/**
* Recursively remove a directory tree.
*
* @param string $path Directory path.
*
* @return void
*/
private function _removeDirectory(string $path): void
{
if ($path === '' || !is_dir($path)) {
return;
}
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($iterator as $item) {
if ($item->isDir()) {
rmdir($item->getPathname());
continue;
}
unlink($item->getPathname());
}
rmdir($path);
}
}