201 lines
5.3 KiB
PHP
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);
|
|
}
|
|
}
|