🐞 fix: Error cleanup, add test harness

This commit is contained in:
Keith Solomon
2026-03-21 23:21:56 -05:00
parent adabacc48d
commit 8e66d69b9e
1743 changed files with 156124 additions and 69 deletions
+64 -41
View File
@@ -19,10 +19,11 @@ declare(strict_types=1);
namespace IronKanban\Repository;
use IronKanban\Domain\Task;
use IronKanban\Markdown\FrontMatterDocument;
use IronKanban\Markdown\FrontMatterParser;
use IronKanban\Support\FileLock;
use IronKanban\Support\IdGenerator;
use IronKanban\Support\Paths;
use IronKanban\Support\TaskMapper;
use RuntimeException;
/**
@@ -40,10 +41,14 @@ class TaskRepository {
*
* @param Paths $paths The paths helper
* @param FrontMatterParser $frontMatterParser The front matter parser
* @param TaskMapper $taskMapper The task mapper
* @param IdGenerator $idGenerator The ID generator
*/
public function __construct(
private readonly Paths $paths,
private readonly FrontMatterParser $frontMatterParser
private readonly FrontMatterParser $frontMatterParser,
private readonly TaskMapper $taskMapper,
private readonly IdGenerator $idGenerator
) {
}
@@ -69,7 +74,14 @@ class TaskRepository {
continue;
}
$tasks[] = $this->_mapFileToTask($projectId, $filePath);
$fileName = basename($filePath);
$document = $this->frontMatterParser->parseFile($filePath);
$tasks[] = $this->taskMapper->fromDocument(
$projectId,
$fileName,
$document
);
}
usort(
@@ -96,13 +108,13 @@ class TaskRepository {
* @throws RuntimeException If task not found
*/
public function get(string $projectId, string $taskId): Task {
$filePath = $this->paths->getTaskFilePath($projectId, $taskId);
if (!is_file($filePath)) {
throw new RuntimeException("Task not found: {$taskId}");
foreach ($this->getAll($projectId) as $task) {
if ($task->id === $taskId) {
return $task;
}
}
return $this->_mapFileToTask($projectId, $filePath);
throw new RuntimeException("Task not found: {$taskId}");
}
/**
@@ -115,12 +127,13 @@ class TaskRepository {
public function save(Task $task): void {
$this->paths->ensureTasksPath($task->projectId);
$filePath = $this->paths->getTaskFilePath($task->projectId, $task->id);
$filePath = $this->paths->getTaskFilePath($task->projectId, $task->fileName);
$lockPath = $filePath . '.lock';
FileLock::run(
$lockPath, function () use ($task, $filePath): void {
$document = $this->_mapTaskToDocument($task);
$lockPath,
function () use ($task, $filePath): void {
$document = $this->taskMapper->toDocument($task);
$contents = $this->frontMatterParser->dump($document);
$this->_atomicWrite($filePath, $contents);
@@ -139,38 +152,45 @@ class TaskRepository {
*/
public function create(array $data): Task {
$projectId = $this->_requireString($data, 'projectId');
$title = $this->_requireString($data, 'title');
$body = (string) ($data['body'] ?? '');
$column = $this->_normalizeColumn((string) ($data['column'] ?? 'backlog'));
$priority = (string) ($data['priority'] ?? 'normal');
$title = $this->_requireString($data, 'title');
$body = (string) ($data['body'] ?? '');
$column = $this->taskMapper->normalizeColumn(
(string) ($data['column'] ?? 'backlog')
);
$priority = (string) ($data['priority'] ?? 'normal');
$completed = (bool) ($data['completed'] ?? false);
$isActive = (bool) ($data['isActive'] ?? true);
$order = isset($data['order']) ? (int) $data['order'] : 100;
$now = gmdate('c');
$isActive = (bool) ($data['isActive'] ?? true);
$order = isset($data['order']) ? (int) $data['order'] : 100;
$now = gmdate('c');
$taskId = $this->_generateTaskId();
$taskId = $this->idGenerator->generateTaskId();
$fileName = $this->idGenerator->generateTaskFileName($taskId);
$meta = [
'id' => $taskId,
'type' => 'task',
'title' => $title,
'project_id' => $projectId,
'column' => $column,
'order' => $order,
'completed' => $completed,
'priority' => $priority,
'is_active' => $isActive,
'created' => $now,
'updated' => $now,
'tags' => [],
'id' => $taskId,
'type' => 'task',
'title' => $title,
'project_id' => $projectId,
'column' => $column,
'order' => $order,
'completed' => $completed,
'priority' => $priority,
'is_active' => $isActive,
'created' => $now,
'updated' => $now,
'tags' => [],
];
if (isset($data['status'])) {
$meta['status'] = (string) $data['status'];
if (array_key_exists('status', $data)) {
$meta['status'] = $data['status'];
}
if (isset($data['statusNote'])) {
$meta['status_note'] = (string) $data['statusNote'];
if (array_key_exists('statusNote', $data)) {
$meta['status_note'] = $data['statusNote'];
}
if (array_key_exists('section', $data)) {
$meta['section'] = $data['section'];
}
$task = new Task(
@@ -183,6 +203,7 @@ class TaskRepository {
priority: $priority,
isActive: $isActive,
body: $body,
fileName: $fileName,
meta: $meta
);
@@ -202,15 +223,17 @@ class TaskRepository {
* @throws RuntimeException If task not found
*/
public function delete(string $projectId, string $taskId): void {
$filePath = $this->paths->getTaskFilePath($projectId, $taskId);
$task = $this->get($projectId, $taskId);
$filePath = $this->paths->getTaskFilePath($projectId, $task->fileName);
$lockPath = $filePath . '.lock';
if (!is_file($filePath)) {
throw new RuntimeException("Task not found: {$taskId}");
}
FileLock::run(
$lockPath, function () use ($filePath): void {
$lockPath,
function () use ($filePath): void {
if (!is_file($filePath)) {
throw new RuntimeException("Task file not found: {$filePath}");
}
if (!unlink($filePath)) {
throw new RuntimeException(
"Unable to delete task file: {$filePath}"