516 lines
15 KiB
PHP
516 lines
15 KiB
PHP
<?php
|
|||
|
|
|
||
|
|
use App\Models\Idea;
|
||
|
|
use App\Models\Project;
|
||
|
|
use App\Models\Task;
|
||
|
|
use App\Models\TaskComment;
|
||
|
|
|
||
|
|
// ==================== PROJECTS API TESTS ====================
|
||
|
|
|
||
|
|
// Projects - Index
|
||
|
|
test('it lists all projects', function () {
|
||
|
|
Project::factory()->count(3)->create();
|
||
|
|
|
||
|
|
$response = $this->getJson('/api/projects');
|
||
|
|
|
||
|
|
$response->assertOk()->assertJsonCount(3, 'data');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('it filters projects by status', function () {
|
||
|
|
Project::factory()->create(['status' => 'active']);
|
||
|
|
Project::factory()->create(['status' => 'completed']);
|
||
|
|
Project::factory()->create(['status' => 'archived']);
|
||
|
|
|
||
|
|
$response = $this->getJson('/api/projects?status=active');
|
||
|
|
|
||
|
|
$response->assertOk()
|
||
|
|
->assertJsonCount(1, 'data')
|
||
|
|
->assertJsonPath('data.0.status', 'active');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('it filters projects by name', function () {
|
||
|
|
Project::factory()->create(['name' => 'Alpha Project']);
|
||
|
|
Project::factory()->create(['name' => 'Beta Project']);
|
||
|
|
Project::factory()->create(['name' => 'Gamma Project']);
|
||
|
|
|
||
|
|
$response = $this->getJson('/api/projects?name=Alpha');
|
||
|
|
|
||
|
|
$response->assertOk()
|
||
|
|
->assertJsonCount(1, 'data')
|
||
|
|
->assertJsonPath('data.0.name', 'Alpha Project');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('it includes tasks when requested', function () {
|
||
|
|
$project = Project::factory()->create();
|
||
|
|
Task::factory()->count(2)->create(['project_id' => $project->id]);
|
||
|
|
|
||
|
|
$response = $this->getJson('/api/projects?with_tasks=1');
|
||
|
|
|
||
|
|
$response->assertOk()
|
||
|
|
->assertJsonPath('data.0.task_count', 2);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('it shows a single project', function () {
|
||
|
|
$project = Project::factory()->create(['name' => 'Test Project']);
|
||
|
|
|
||
|
|
$response = $this->getJson("/api/projects/{$project->id}");
|
||
|
|
|
||
|
|
$response->assertOk()->assertJsonPath('data.name', 'Test Project');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('it returns 404 for a missing project', function () {
|
||
|
|
$this->getJson('/api/projects/999')->assertNotFound();
|
||
|
|
});
|
||
|
|
|
||
|
|
// Projects - Store
|
||
|
|
test('it creates a project', function () {
|
||
|
|
$data = [
|
||
|
|
'name' => 'New Project',
|
||
|
|
'description' => 'A new test project',
|
||
|
|
'status' => 'planned',
|
||
|
|
'due_at' => now()->addDays(30)->toDateTimeString(),
|
||
|
|
];
|
||
|
|
|
||
|
|
$response = $this->postJson('/api/projects', $data);
|
||
|
|
|
||
|
|
$response->assertCreated()
|
||
|
|
->assertJsonPath('data.name', 'New Project')
|
||
|
|
->assertJsonPath('data.status', 'planned');
|
||
|
|
|
||
|
|
$this->assertDatabaseHas('projects', ['name' => 'New Project']);
|
||
|
|
});
|
||
|
|
|
||
|
|
// Projects - Update
|
||
|
|
test('it updates a project', function () {
|
||
|
|
$project = Project::factory()->create(['name' => 'Original Name']);
|
||
|
|
|
||
|
|
$data = [
|
||
|
|
'name' => 'Updated Name',
|
||
|
|
'description' => 'Updated description',
|
||
|
|
'status' => 'active',
|
||
|
|
];
|
||
|
|
|
||
|
|
$response = $this->putJson("/api/projects/{$project->id}", $data);
|
||
|
|
|
||
|
|
$response->assertOk()
|
||
|
|
->assertJsonPath('data.name', 'Updated Name')
|
||
|
|
->assertJsonPath('data.description', 'Updated description');
|
||
|
|
|
||
|
|
$this->assertDatabaseHas('projects', ['name' => 'Updated Name']);
|
||
|
|
});
|
||
|
|
|
||
|
|
// Projects - Destroy
|
||
|
|
test('it deletes a project', function () {
|
||
|
|
$project = Project::factory()->create();
|
||
|
|
|
||
|
|
$response = $this->deleteJson("/api/projects/{$project->id}");
|
||
|
|
|
||
|
|
$response->assertNoContent();
|
||
|
|
|
||
|
|
$this->assertSoftDeleted($project);
|
||
|
|
});
|
||
|
|
|
||
|
|
// Projects - Tasks Endpoint
|
||
|
|
test('it lists tasks for a project', function () {
|
||
|
|
$project = Project::factory()->create();
|
||
|
|
Task::factory()->count(3)->create(['project_id' => $project->id]);
|
||
|
|
Task::factory()->count(2)->create(); // Tasks not in this project
|
||
|
|
|
||
|
|
$response = $this->getJson("/api/projects/{$project->id}/tasks");
|
||
|
|
|
||
|
|
$response->assertOk()->assertJsonCount(3, 'data');
|
||
|
|
});
|
||
|
|
|
||
|
|
// ==================== TASKS API TESTS ====================
|
||
|
|
|
||
|
|
// Tasks - Index
|
||
|
|
test('it lists all tasks', function () {
|
||
|
|
Task::factory()->count(5)->create();
|
||
|
|
|
||
|
|
$response = $this->getJson('/api/tasks');
|
||
|
|
|
||
|
|
$response->assertOk()->assertJsonCount(5, 'data');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('it filters tasks by status', function () {
|
||
|
|
Task::factory()->create(['status' => 'todo']);
|
||
|
|
Task::factory()->create(['status' => 'in_progress']);
|
||
|
|
Task::factory()->create(['status' => 'done']);
|
||
|
|
|
||
|
|
$response = $this->getJson('/api/tasks?status=in_progress');
|
||
|
|
|
||
|
|
$response->assertOk()
|
||
|
|
->assertJsonCount(1, 'data')
|
||
|
|
->assertJsonPath('data.0.status', 'in_progress');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('it filters tasks by priority', function () {
|
||
|
|
Task::factory()->create(['priority' => 5]);
|
||
|
|
Task::factory()->create(['priority' => 3]);
|
||
|
|
Task::factory()->create(['priority' => 5]);
|
||
|
|
|
||
|
|
$response = $this->getJson('/api/tasks?priority=5');
|
||
|
|
|
||
|
|
$response->assertOk()->assertJsonCount(2, 'data');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('it includes comments when requested', function () {
|
||
|
|
$task = Task::factory()->create();
|
||
|
|
TaskComment::factory()->count(2)->create(['task_id' => $task->id]);
|
||
|
|
|
||
|
|
$response = $this->getJson('/api/tasks?with_comments=1');
|
||
|
|
|
||
|
|
$response->assertOk()
|
||
|
|
->assertJsonPath('data.0.comment_count', 2);
|
||
|
|
});
|
||
|
|
|
||
|
|
// Tasks - Show
|
||
|
|
test('it shows a single task', function () {
|
||
|
|
$task = Task::factory()->create(['title' => 'Test Task']);
|
||
|
|
|
||
|
|
$response = $this->getJson("/api/tasks/{$task->id}");
|
||
|
|
|
||
|
|
$response->assertOk()->assertJsonPath('data.title', 'Test Task');
|
||
|
|
});
|
||
|
|
|
||
|
|
// Tasks - Store
|
||
|
|
test('it creates a task', function () {
|
||
|
|
$project = Project::factory()->create();
|
||
|
|
|
||
|
|
$data = [
|
||
|
|
'title' => 'New Task',
|
||
|
|
'description' => 'A new test task',
|
||
|
|
'status' => 'todo',
|
||
|
|
'project_id' => $project->id,
|
||
|
|
'priority' => 5,
|
||
|
|
'due_at' => now()->addDays(7)->toDateTimeString(),
|
||
|
|
'estimated_hours' => 4,
|
||
|
|
];
|
||
|
|
|
||
|
|
$response = $this->postJson('/api/tasks', $data);
|
||
|
|
|
||
|
|
$response->assertCreated()
|
||
|
|
->assertJsonPath('data.title', 'New Task')
|
||
|
|
->assertJsonPath('data.priority', 5);
|
||
|
|
|
||
|
|
$this->assertDatabaseHas('tasks', ['title' => 'New Task']);
|
||
|
|
});
|
||
|
|
|
||
|
|
// Tasks - Update
|
||
|
|
test('it updates a task', function () {
|
||
|
|
$task = Task::factory()->create(['title' => 'Original Title']);
|
||
|
|
|
||
|
|
$data = [
|
||
|
|
'title' => 'Updated Title',
|
||
|
|
'status' => 'in_progress',
|
||
|
|
'priority' => 8,
|
||
|
|
];
|
||
|
|
|
||
|
|
$response = $this->putJson("/api/tasks/{$task->id}", $data);
|
||
|
|
|
||
|
|
$response->assertOk()
|
||
|
|
->assertJsonPath('data.title', 'Updated Title')
|
||
|
|
->assertJsonPath('data.status', 'in_progress');
|
||
|
|
|
||
|
|
$this->assertDatabaseHas('tasks', ['title' => 'Updated Title']);
|
||
|
|
});
|
||
|
|
|
||
|
|
// Tasks - Destroy
|
||
|
|
test('it deletes a task', function () {
|
||
|
|
$task = Task::factory()->create();
|
||
|
|
|
||
|
|
$response = $this->deleteJson("/api/tasks/{$task->id}");
|
||
|
|
|
||
|
|
$response->assertNoContent();
|
||
|
|
|
||
|
|
$this->assertSoftDeleted($task);
|
||
|
|
});
|
||
|
|
|
||
|
|
// Tasks - Comments Endpoint
|
||
|
|
test('it lists comments for a task', function () {
|
||
|
|
$task = Task::factory()->create();
|
||
|
|
TaskComment::factory()->count(3)->create(['task_id' => $task->id]);
|
||
|
|
|
||
|
|
$response = $this->getJson("/api/tasks/{$task->id}/comments");
|
||
|
|
|
||
|
|
$response->assertOk()->assertJsonCount(3, 'data');
|
||
|
|
});
|
||
|
|
|
||
|
|
// Tasks - Change Status
|
||
|
|
test('it changes task status', function () {
|
||
|
|
$task = Task::factory()->create(['status' => 'todo']);
|
||
|
|
|
||
|
|
$response = $this->postJson("/api/tasks/{$task->id}/status", [
|
||
|
|
'status' => 'in_progress',
|
||
|
|
]);
|
||
|
|
|
||
|
|
$response->assertOk();
|
||
|
|
|
||
|
|
$task->refresh();
|
||
|
|
$this->assertEquals('in_progress', $task->status);
|
||
|
|
});
|
||
|
|
|
||
|
|
// ==================== TASK COMMENTS API TESTS ====================
|
||
|
|
|
||
|
|
// Task Comments - Index
|
||
|
|
test('it lists all task comments for a task', function () {
|
||
|
|
$task = Task::factory()->create();
|
||
|
|
TaskComment::factory()->count(4)->create(['task_id' => $task->id]);
|
||
|
|
|
||
|
|
$response = $this->getJson("/api/tasks/{$task->id}/comments");
|
||
|
|
|
||
|
|
$response->assertOk()->assertJsonCount(4, 'data');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('it shows a single task comment', function () {
|
||
|
|
$comment = TaskComment::factory()->create(['body' => 'Test Comment']);
|
||
|
|
|
||
|
|
$response = $this->getJson("/api/comments/{$comment->id}");
|
||
|
|
|
||
|
|
$response->assertOk()->assertJsonPath('data.body', 'Test Comment');
|
||
|
|
});
|
||
|
|
|
||
|
|
// Task Comments - Store
|
||
|
|
test('it creates a task comment', function () {
|
||
|
|
$task = Task::factory()->create();
|
||
|
|
|
||
|
|
$data = [
|
||
|
|
'body' => 'New comment from AI',
|
||
|
|
'author_type' => 'ai',
|
||
|
|
'author_name' => 'AI Assistant',
|
||
|
|
];
|
||
|
|
|
||
|
|
$response = $this->postJson("/api/tasks/{$task->id}/comments", $data);
|
||
|
|
|
||
|
|
$response->assertCreated()
|
||
|
|
->assertJsonPath('data.body', 'New comment from AI')
|
||
|
|
->assertJsonPath('data.author_type', 'ai');
|
||
|
|
|
||
|
|
$this->assertDatabaseHas('task_comments', ['body' => 'New comment from AI']);
|
||
|
|
});
|
||
|
|
|
||
|
|
// Task Comments - Update
|
||
|
|
test('it updates a task comment', function () {
|
||
|
|
$comment = TaskComment::factory()->create(['body' => 'Original Comment']);
|
||
|
|
|
||
|
|
$data = [
|
||
|
|
'body' => 'Updated Comment',
|
||
|
|
];
|
||
|
|
|
||
|
|
$response = $this->putJson("/api/comments/{$comment->id}", $data);
|
||
|
|
|
||
|
|
$response->assertOk()
|
||
|
|
->assertJsonPath('data.body', 'Updated Comment');
|
||
|
|
|
||
|
|
$this->assertDatabaseHas('task_comments', ['body' => 'Updated Comment']);
|
||
|
|
});
|
||
|
|
|
||
|
|
// Task Comments - Destroy
|
||
|
|
test('it deletes a task comment', function () {
|
||
|
|
$comment = TaskComment::factory()->create();
|
||
|
|
|
||
|
|
$response = $this->deleteJson("/api/comments/{$comment->id}");
|
||
|
|
|
||
|
|
$response->assertNoContent();
|
||
|
|
|
||
|
|
$this->assertSoftDeleted($comment);
|
||
|
|
});
|
||
|
|
|
||
|
|
// ==================== IDEAS API TESTS ====================
|
||
|
|
|
||
|
|
// Ideas - Index
|
||
|
|
test('it lists all ideas', function () {
|
||
|
|
Idea::factory()->count(5)->create();
|
||
|
|
|
||
|
|
$response = $this->getJson('/api/ideas');
|
||
|
|
|
||
|
|
$response->assertOk()->assertJsonCount(5, 'data');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('it filters ideas by status', function () {
|
||
|
|
Idea::factory()->create(['status' => 'new']);
|
||
|
|
Idea::factory()->create(['status' => 'considered']);
|
||
|
|
Idea::factory()->create(['status' => 'planned']);
|
||
|
|
|
||
|
|
$response = $this->getJson('/api/ideas?status=considered');
|
||
|
|
|
||
|
|
$response->assertOk()
|
||
|
|
->assertJsonCount(1, 'data')
|
||
|
|
->assertJsonPath('data.0.status', 'considered');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('it filters ideas by category', function () {
|
||
|
|
Idea::factory()->feature()->create();
|
||
|
|
Idea::factory()->improvement()->create();
|
||
|
|
Idea::factory()->bugfix()->create();
|
||
|
|
|
||
|
|
$response = $this->getJson('/api/ideas?category=feature');
|
||
|
|
|
||
|
|
$response->assertOk()
|
||
|
|
->assertJsonCount(1, 'data')
|
||
|
|
->assertJsonPath('data.0.category', 'feature');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('it filters unlinked ideas', function () {
|
||
|
|
Idea::factory()->create(['project_id' => null]);
|
||
|
|
Idea::factory()->withProject()->create();
|
||
|
|
|
||
|
|
$response = $this->getJson('/api/ideas?unlinked=1');
|
||
|
|
|
||
|
|
$response->assertOk()->assertJsonCount(1, 'data');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('it filters ideas by project', function () {
|
||
|
|
$project = Project::factory()->create();
|
||
|
|
Idea::factory()->count(2)->withProject($project)->create();
|
||
|
|
Idea::factory()->count(3)->create();
|
||
|
|
|
||
|
|
$response = $this->getJson("/api/ideas?project_id={$project->id}");
|
||
|
|
|
||
|
|
$response->assertOk()->assertJsonCount(2, 'data');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('it searches ideas', function () {
|
||
|
|
Idea::factory()->create(['title' => 'AI Integration']);
|
||
|
|
Idea::factory()->create(['title' => 'Database Optimization']);
|
||
|
|
|
||
|
|
$response = $this->getJson('/api/ideas?search=AI');
|
||
|
|
|
||
|
|
$response->assertOk()
|
||
|
|
->assertJsonCount(1, 'data')
|
||
|
|
->assertJsonPath('data.0.title', 'AI Integration');
|
||
|
|
});
|
||
|
|
|
||
|
|
// Ideas - Show
|
||
|
|
test('it shows a single idea', function () {
|
||
|
|
$idea = Idea::factory()->create(['title' => 'Test Idea']);
|
||
|
|
|
||
|
|
$response = $this->getJson("/api/ideas/{$idea->id}");
|
||
|
|
|
||
|
|
$response->assertOk()->assertJsonPath('data.title', 'Test Idea');
|
||
|
|
});
|
||
|
|
|
||
|
|
// Ideas - Store
|
||
|
|
test('it creates an idea', function () {
|
||
|
|
$data = [
|
||
|
|
'title' => 'New Idea',
|
||
|
|
'description' => 'A great new idea',
|
||
|
|
'status' => 'new',
|
||
|
|
'category' => 'feature',
|
||
|
|
'priority' => 5,
|
||
|
|
'tags' => 'ai,automation,innovation',
|
||
|
|
];
|
||
|
|
|
||
|
|
$response = $this->postJson('/api/ideas', $data);
|
||
|
|
|
||
|
|
$response->assertCreated()
|
||
|
|
->assertJsonPath('data.title', 'New Idea')
|
||
|
|
->assertJsonPath('data.category', 'feature');
|
||
|
|
|
||
|
|
$this->assertDatabaseHas('ideas', ['title' => 'New Idea']);
|
||
|
|
});
|
||
|
|
|
||
|
|
// Ideas - Update
|
||
|
|
test('it updates an idea', function () {
|
||
|
|
$idea = Idea::factory()->create(['title' => 'Original Title']);
|
||
|
|
|
||
|
|
$data = [
|
||
|
|
'title' => 'Updated Title',
|
||
|
|
'status' => 'considered',
|
||
|
|
'category' => 'improvement',
|
||
|
|
];
|
||
|
|
|
||
|
|
$response = $this->putJson("/api/ideas/{$idea->id}", $data);
|
||
|
|
|
||
|
|
$response->assertOk()
|
||
|
|
->assertJsonPath('data.title', 'Updated Title')
|
||
|
|
->assertJsonPath('data.status', 'considered');
|
||
|
|
|
||
|
|
$this->assertDatabaseHas('ideas', ['title' => 'Updated Title']);
|
||
|
|
});
|
||
|
|
|
||
|
|
// Ideas - Destroy
|
||
|
|
test('it deletes an idea', function () {
|
||
|
|
$idea = Idea::factory()->create();
|
||
|
|
|
||
|
|
$response = $this->deleteJson("/api/ideas/{$idea->id}");
|
||
|
|
|
||
|
|
$response->assertNoContent();
|
||
|
|
|
||
|
|
$this->assertSoftDeleted($idea);
|
||
|
|
});
|
||
|
|
|
||
|
|
// Ideas - Change Status
|
||
|
|
test('it changes idea status', function () {
|
||
|
|
$idea = Idea::factory()->create(['status' => 'new']);
|
||
|
|
|
||
|
|
$response = $this->postJson("/api/ideas/{$idea->id}/status", [
|
||
|
|
'status' => 'considered',
|
||
|
|
]);
|
||
|
|
|
||
|
|
$response->assertOk();
|
||
|
|
|
||
|
|
$idea->refresh();
|
||
|
|
$this->assertEquals('considered', $idea->status);
|
||
|
|
});
|
||
|
|
|
||
|
|
// Ideas - Link to Project
|
||
|
|
test('it links idea to project', function () {
|
||
|
|
$idea = Idea::factory()->create(['project_id' => null]);
|
||
|
|
$project = Project::factory()->create();
|
||
|
|
|
||
|
|
$response = $this->postJson("/api/ideas/{$idea->id}/link-project", [
|
||
|
|
'project_id' => $project->id,
|
||
|
|
]);
|
||
|
|
|
||
|
|
$response->assertOk();
|
||
|
|
|
||
|
|
$idea->refresh();
|
||
|
|
$this->assertEquals($project->id, $idea->project_id);
|
||
|
|
});
|
||
|
|
|
||
|
|
// Ideas - Unlink from Project
|
||
|
|
test('it unlinks idea from project', function () {
|
||
|
|
$project = Project::factory()->create();
|
||
|
|
$idea = Idea::factory()->withProject($project)->create();
|
||
|
|
|
||
|
|
$response = $this->postJson("/api/ideas/{$idea->id}/unlink-project");
|
||
|
|
|
||
|
|
$response->assertOk();
|
||
|
|
|
||
|
|
$idea->refresh();
|
||
|
|
$this->assertNull($idea->project_id);
|
||
|
|
});
|
||
|
|
|
||
|
|
// ==================== DATA FACTORIES TESTS ====================
|
||
|
|
|
||
|
|
// Test that factories work correctly
|
||
|
|
test('project factory creates valid projects', function () {
|
||
|
|
$project = Project::factory()->create();
|
||
|
|
|
||
|
|
$this->assertNotNull($project->name);
|
||
|
|
$this->assertContains($project->status, Project::STATUSES);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('task factory creates valid tasks', function () {
|
||
|
|
$task = Task::factory()->create();
|
||
|
|
|
||
|
|
$this->assertNotNull($task->title);
|
||
|
|
$this->assertContains($task->status, Task::STATUSES);
|
||
|
|
$this->assertNotNull($task->project_id);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('task comment factory creates valid comments', function () {
|
||
|
|
$comment = TaskComment::factory()->create();
|
||
|
|
|
||
|
|
$this->assertNotNull($comment->body);
|
||
|
|
$this->assertContains($comment->author_type, TaskComment::AUTHOR_TYPES);
|
||
|
|
$this->assertNotNull($comment->task_id);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('idea factory creates valid ideas', function () {
|
||
|
|
$idea = Idea::factory()->create();
|
||
|
|
|
||
|
|
$this->assertNotNull($idea->title);
|
||
|
|
$this->assertContains($idea->status, Idea::STATUSES);
|
||
|
|
});
|