323 lines
10 KiB
PHP
323 lines
10 KiB
PHP
<?php
|
|
// phpcs:disable PEAR.Commenting.FileComment,PEAR.Commenting.ClassComment
|
|
|
|
/**
|
|
* API integration tests.
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Api;
|
|
|
|
use IronKanban\Service\BoardService;
|
|
use Tests\Support\IntegrationTestCase;
|
|
|
|
/**
|
|
* Covers JSON endpoint behavior and request validation.
|
|
*/
|
|
final class BoardApiTest extends IntegrationTestCase
|
|
{
|
|
/**
|
|
* Ensure the board-state endpoint returns project and board data.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testBoardStateEndpointReturnsProjectState(): void
|
|
{
|
|
$projectId = $this->createProject();
|
|
|
|
$response = $this->callApi(
|
|
'public/api/board-state.php',
|
|
'GET',
|
|
['project' => $projectId]
|
|
);
|
|
|
|
self::assertSame(200, $response['status']);
|
|
self::assertSame('Demo Project', $response['payload']['project']['title']);
|
|
self::assertContains('trash', array_column($response['payload']['columns'], 'id'));
|
|
}
|
|
|
|
/**
|
|
* Ensure state-changing endpoints reject missing CSRF tokens.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testCreateTaskEndpointRejectsMissingCsrfToken(): void
|
|
{
|
|
$projectId = $this->createProject();
|
|
|
|
$response = $this->callApi(
|
|
'public/api/create-task.php',
|
|
'POST',
|
|
[],
|
|
[
|
|
'projectId' => $projectId,
|
|
'title' => 'Write docs',
|
|
'column' => 'backlog',
|
|
]
|
|
);
|
|
|
|
self::assertSame(403, $response['status']);
|
|
self::assertFalse($response['payload']['success']);
|
|
self::assertSame('invalid_csrf', $response['payload']['error']);
|
|
}
|
|
|
|
/**
|
|
* Ensure create-task returns a successful JSON state payload.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testCreateTaskEndpointCreatesTaskWithValidCsrfToken(): void
|
|
{
|
|
$projectId = $this->createProject();
|
|
$token = csrf_token();
|
|
|
|
$response = $this->callApi(
|
|
'public/api/create-task.php',
|
|
'POST',
|
|
[],
|
|
[
|
|
'projectId' => $projectId,
|
|
'title' => 'Write docs',
|
|
'column' => 'backlog',
|
|
'body' => 'Add the release checklist.',
|
|
'_token' => $token,
|
|
],
|
|
['HTTP_X_CSRF_TOKEN' => $token]
|
|
);
|
|
|
|
self::assertSame(200, $response['status']);
|
|
self::assertTrue($response['payload']['success']);
|
|
self::assertSame('Write docs', $response['payload']['state']['task']['title']);
|
|
self::assertSame('backlog', $response['payload']['state']['task']['column']);
|
|
self::assertCount(1, $response['payload']['state']['tasks']);
|
|
}
|
|
|
|
/**
|
|
* Ensure stale revisions return a conflict response.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testCreateTaskEndpointReturnsConflictForStaleRevision(): void
|
|
{
|
|
$projectId = $this->createProject();
|
|
$service = new BoardService();
|
|
$current = $service->getBoardState($projectId);
|
|
$token = csrf_token();
|
|
|
|
$service->createTask($projectId, 'Existing task', 'backlog');
|
|
|
|
$response = $this->callApi(
|
|
'public/api/create-task.php',
|
|
'POST',
|
|
[],
|
|
[
|
|
'projectId' => $projectId,
|
|
'title' => 'Conflicting task',
|
|
'column' => 'ready',
|
|
'revision' => (string) $current['revision'],
|
|
'_token' => $token,
|
|
],
|
|
['HTTP_X_CSRF_TOKEN' => $token]
|
|
);
|
|
|
|
self::assertSame(409, $response['status']);
|
|
self::assertFalse($response['payload']['success']);
|
|
self::assertSame('conflict', $response['payload']['error']);
|
|
}
|
|
|
|
/**
|
|
* Ensure move-task updates the task column and preserves a success response.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testMoveTaskEndpointMovesTaskToRequestedColumn(): void
|
|
{
|
|
$projectId = $this->createProject();
|
|
$service = new BoardService();
|
|
$created = $service->createTask($projectId, 'Move me', 'backlog');
|
|
$token = csrf_token();
|
|
|
|
$response = $this->callApi(
|
|
'public/api/move-task.php',
|
|
'POST',
|
|
[],
|
|
[
|
|
'projectId' => $projectId,
|
|
'taskId' => $created['task']['id'],
|
|
'column' => 'done',
|
|
'index' => 0,
|
|
'revision' => (string) $created['revision'],
|
|
'_token' => $token,
|
|
],
|
|
['HTTP_X_CSRF_TOKEN' => $token]
|
|
);
|
|
|
|
self::assertSame(200, $response['status']);
|
|
self::assertTrue($response['payload']['success']);
|
|
self::assertSame('done', $response['payload']['state']['tasks'][0]['column']);
|
|
}
|
|
|
|
/**
|
|
* Ensure delete-task trashes a task by default.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testDeleteTaskEndpointMovesTaskToTrash(): void
|
|
{
|
|
$projectId = $this->createProject();
|
|
$service = new BoardService();
|
|
$created = $service->createTask($projectId, 'Archive me', 'review');
|
|
$token = csrf_token();
|
|
|
|
$response = $this->callApi(
|
|
'public/api/delete-task.php',
|
|
'POST',
|
|
[],
|
|
[
|
|
'projectId' => $projectId,
|
|
'taskId' => $created['task']['id'],
|
|
'revision' => (string) $created['revision'],
|
|
'_token' => $token,
|
|
],
|
|
['HTTP_X_CSRF_TOKEN' => $token]
|
|
);
|
|
|
|
self::assertSame(200, $response['status']);
|
|
self::assertTrue($response['payload']['success']);
|
|
self::assertSame('trash', $response['payload']['state']['tasks'][0]['column']);
|
|
}
|
|
|
|
/**
|
|
* Ensure save-note creates a note and returns it in the response state.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testSaveNoteEndpointCreatesNote(): void
|
|
{
|
|
$projectId = $this->createProject();
|
|
$service = new BoardService();
|
|
$state = $service->getBoardState($projectId);
|
|
$token = csrf_token();
|
|
|
|
$response = $this->callApi(
|
|
'public/api/save-note.php',
|
|
'POST',
|
|
[],
|
|
[
|
|
'projectId' => $projectId,
|
|
'title' => 'Sprint Notes',
|
|
'body' => 'Capture follow-up items.',
|
|
'revision' => (string) $state['revision'],
|
|
'_token' => $token,
|
|
],
|
|
['HTTP_X_CSRF_TOKEN' => $token]
|
|
);
|
|
|
|
self::assertSame(200, $response['status']);
|
|
self::assertTrue($response['payload']['success']);
|
|
self::assertSame('Sprint Notes', $response['payload']['state']['note']['title']);
|
|
self::assertCount(1, $response['payload']['state']['notes']);
|
|
}
|
|
|
|
/**
|
|
* Ensure create-column appends a new column to the board.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testCreateColumnEndpointCreatesNewColumn(): void
|
|
{
|
|
$projectId = $this->createProject();
|
|
$service = new BoardService();
|
|
$state = $service->getBoardState($projectId);
|
|
$token = csrf_token();
|
|
|
|
$response = $this->callApi(
|
|
'public/api/create-column.php',
|
|
'POST',
|
|
[],
|
|
[
|
|
'projectId' => $projectId,
|
|
'label' => 'Blocked',
|
|
'revision' => (string) $state['revision'],
|
|
'_token' => $token,
|
|
],
|
|
['HTTP_X_CSRF_TOKEN' => $token]
|
|
);
|
|
|
|
self::assertSame(200, $response['status']);
|
|
self::assertTrue($response['payload']['success']);
|
|
self::assertContains('blocked', array_column($response['payload']['state']['columns'], 'id'));
|
|
}
|
|
|
|
/**
|
|
* Ensure save-task updates persisted task fields.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testSaveTaskEndpointUpdatesTaskFields(): void
|
|
{
|
|
$projectId = $this->createProject();
|
|
$service = new BoardService();
|
|
$created = $service->createTask($projectId, 'Initial title', 'backlog');
|
|
$token = csrf_token();
|
|
|
|
$response = $this->callApi(
|
|
'public/api/save-task.php',
|
|
'POST',
|
|
[],
|
|
[
|
|
'projectId' => $projectId,
|
|
'taskId' => $created['task']['id'],
|
|
'title' => 'Updated title',
|
|
'body' => 'Updated body',
|
|
'priority' => 'urgent',
|
|
'completed' => true,
|
|
'revision' => (string) $created['revision'],
|
|
'_token' => $token,
|
|
],
|
|
['HTTP_X_CSRF_TOKEN' => $token]
|
|
);
|
|
|
|
self::assertSame(200, $response['status']);
|
|
self::assertTrue($response['payload']['success']);
|
|
self::assertSame('Updated title', $response['payload']['state']['tasks'][0]['title']);
|
|
self::assertSame("Updated body\n", $response['payload']['state']['tasks'][0]['body']);
|
|
self::assertSame('urgent', $response['payload']['state']['tasks'][0]['priority']);
|
|
self::assertTrue($response['payload']['state']['tasks'][0]['completed']);
|
|
}
|
|
|
|
/**
|
|
* Ensure update-board accepts column replacements and keeps trash available.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testUpdateBoardEndpointReplacesColumnDefinitions(): void
|
|
{
|
|
$projectId = $this->createProject();
|
|
$service = new BoardService();
|
|
$state = $service->getBoardState($projectId);
|
|
$token = csrf_token();
|
|
|
|
$response = $this->callApi(
|
|
'public/api/update-board.php',
|
|
'POST',
|
|
[],
|
|
[
|
|
'projectId' => $projectId,
|
|
'columns' => [
|
|
['id' => 'ideas', 'label' => 'Ideas'],
|
|
['id' => 'doing', 'label' => 'Doing'],
|
|
],
|
|
'revision' => (string) $state['revision'],
|
|
'_token' => $token,
|
|
],
|
|
['HTTP_X_CSRF_TOKEN' => $token]
|
|
);
|
|
|
|
self::assertSame(200, $response['status']);
|
|
self::assertTrue($response['payload']['success']);
|
|
self::assertSame(['ideas', 'doing', 'trash'], array_column($response['payload']['state']['columns'], 'id'));
|
|
}
|
|
}
|