test(issues): phase 5 — feature tests for Issue and Sprint models and actions
IssueTest covers: model CRUD, status transitions, slug generation, sprint association, comments, label management, scopes, MCP context, and all Issue Action classes with validation. SprintTest covers: model lifecycle, progress calculation, scopes, MCP context, and all Sprint Action classes with validation. Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
5a0b126f51
commit
12a22aa892
2 changed files with 654 additions and 0 deletions
368
src/php/tests/Feature/IssueTest.php
Normal file
368
src/php/tests/Feature/IssueTest.php
Normal file
|
|
@ -0,0 +1,368 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Agentic\Tests\Feature;
|
||||
|
||||
use Core\Mod\Agentic\Actions\Issue\AddIssueComment;
|
||||
use Core\Mod\Agentic\Actions\Issue\ArchiveIssue;
|
||||
use Core\Mod\Agentic\Actions\Issue\CreateIssue;
|
||||
use Core\Mod\Agentic\Actions\Issue\GetIssue;
|
||||
use Core\Mod\Agentic\Actions\Issue\ListIssues;
|
||||
use Core\Mod\Agentic\Actions\Issue\UpdateIssue;
|
||||
use Core\Mod\Agentic\Models\Issue;
|
||||
use Core\Mod\Agentic\Models\IssueComment;
|
||||
use Core\Mod\Agentic\Models\Sprint;
|
||||
use Core\Tenant\Models\Workspace;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class IssueTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private Workspace $workspace;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->workspace = Workspace::factory()->create();
|
||||
}
|
||||
|
||||
// -- Model tests --
|
||||
|
||||
public function test_issue_can_be_created(): void
|
||||
{
|
||||
$issue = Issue::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'slug' => 'test-issue',
|
||||
'title' => 'Test Issue',
|
||||
'type' => Issue::TYPE_BUG,
|
||||
'status' => Issue::STATUS_OPEN,
|
||||
'priority' => Issue::PRIORITY_HIGH,
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('issues', [
|
||||
'id' => $issue->id,
|
||||
'slug' => 'test-issue',
|
||||
'type' => 'bug',
|
||||
'priority' => 'high',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_issue_has_correct_default_status(): void
|
||||
{
|
||||
$issue = Issue::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'slug' => 'defaults-test',
|
||||
'title' => 'Defaults',
|
||||
]);
|
||||
|
||||
$this->assertEquals(Issue::STATUS_OPEN, $issue->fresh()->status);
|
||||
}
|
||||
|
||||
public function test_issue_can_be_closed(): void
|
||||
{
|
||||
$issue = Issue::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'slug' => 'close-test',
|
||||
'title' => 'Close Me',
|
||||
'status' => Issue::STATUS_OPEN,
|
||||
]);
|
||||
|
||||
$issue->close();
|
||||
|
||||
$fresh = $issue->fresh();
|
||||
$this->assertEquals(Issue::STATUS_CLOSED, $fresh->status);
|
||||
$this->assertNotNull($fresh->closed_at);
|
||||
}
|
||||
|
||||
public function test_issue_can_be_reopened(): void
|
||||
{
|
||||
$issue = Issue::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'slug' => 'reopen-test',
|
||||
'title' => 'Reopen Me',
|
||||
'status' => Issue::STATUS_CLOSED,
|
||||
'closed_at' => now(),
|
||||
]);
|
||||
|
||||
$issue->reopen();
|
||||
|
||||
$fresh = $issue->fresh();
|
||||
$this->assertEquals(Issue::STATUS_OPEN, $fresh->status);
|
||||
$this->assertNull($fresh->closed_at);
|
||||
}
|
||||
|
||||
public function test_issue_can_be_archived_with_reason(): void
|
||||
{
|
||||
$issue = Issue::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'slug' => 'archive-test',
|
||||
'title' => 'Archive Me',
|
||||
]);
|
||||
|
||||
$issue->archive('Duplicate');
|
||||
|
||||
$fresh = $issue->fresh();
|
||||
$this->assertEquals(Issue::STATUS_CLOSED, $fresh->status);
|
||||
$this->assertNotNull($fresh->archived_at);
|
||||
$this->assertEquals('Duplicate', $fresh->metadata['archive_reason']);
|
||||
}
|
||||
|
||||
public function test_issue_generates_unique_slugs(): void
|
||||
{
|
||||
Issue::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'slug' => 'my-issue',
|
||||
'title' => 'My Issue',
|
||||
]);
|
||||
|
||||
$slug = Issue::generateSlug('My Issue');
|
||||
|
||||
$this->assertEquals('my-issue-1', $slug);
|
||||
}
|
||||
|
||||
public function test_issue_belongs_to_sprint(): void
|
||||
{
|
||||
$sprint = Sprint::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'slug' => 'sprint-1',
|
||||
'title' => 'Sprint 1',
|
||||
]);
|
||||
|
||||
$issue = Issue::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'sprint_id' => $sprint->id,
|
||||
'slug' => 'sprint-issue',
|
||||
'title' => 'Sprint Issue',
|
||||
]);
|
||||
|
||||
$this->assertEquals($sprint->id, $issue->sprint->id);
|
||||
}
|
||||
|
||||
public function test_issue_has_comments(): void
|
||||
{
|
||||
$issue = Issue::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'slug' => 'commented',
|
||||
'title' => 'Has Comments',
|
||||
]);
|
||||
|
||||
IssueComment::create([
|
||||
'issue_id' => $issue->id,
|
||||
'author' => 'claude',
|
||||
'body' => 'Investigating.',
|
||||
]);
|
||||
|
||||
$this->assertCount(1, $issue->fresh()->comments);
|
||||
}
|
||||
|
||||
public function test_issue_label_management(): void
|
||||
{
|
||||
$issue = Issue::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'slug' => 'label-test',
|
||||
'title' => 'Labels',
|
||||
'labels' => [],
|
||||
]);
|
||||
|
||||
$issue->addLabel('agentic');
|
||||
$this->assertContains('agentic', $issue->fresh()->labels);
|
||||
|
||||
$issue->addLabel('agentic'); // duplicate — should not add
|
||||
$this->assertCount(1, $issue->fresh()->labels);
|
||||
|
||||
$issue->removeLabel('agentic');
|
||||
$this->assertEmpty($issue->fresh()->labels);
|
||||
}
|
||||
|
||||
public function test_issue_scopes(): void
|
||||
{
|
||||
Issue::create(['workspace_id' => $this->workspace->id, 'slug' => 'open-1', 'title' => 'A', 'status' => Issue::STATUS_OPEN]);
|
||||
Issue::create(['workspace_id' => $this->workspace->id, 'slug' => 'ip-1', 'title' => 'B', 'status' => Issue::STATUS_IN_PROGRESS]);
|
||||
Issue::create(['workspace_id' => $this->workspace->id, 'slug' => 'closed-1', 'title' => 'C', 'status' => Issue::STATUS_CLOSED]);
|
||||
|
||||
$this->assertCount(1, Issue::open()->get());
|
||||
$this->assertCount(1, Issue::inProgress()->get());
|
||||
$this->assertCount(1, Issue::closed()->get());
|
||||
$this->assertCount(2, Issue::notClosed()->get());
|
||||
}
|
||||
|
||||
public function test_issue_to_mcp_context(): void
|
||||
{
|
||||
$issue = Issue::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'slug' => 'mcp-test',
|
||||
'title' => 'MCP Context',
|
||||
'type' => Issue::TYPE_FEATURE,
|
||||
]);
|
||||
|
||||
$context = $issue->toMcpContext();
|
||||
|
||||
$this->assertIsArray($context);
|
||||
$this->assertEquals('mcp-test', $context['slug']);
|
||||
$this->assertEquals('feature', $context['type']);
|
||||
$this->assertArrayHasKey('status', $context);
|
||||
$this->assertArrayHasKey('priority', $context);
|
||||
$this->assertArrayHasKey('labels', $context);
|
||||
}
|
||||
|
||||
// -- Action tests --
|
||||
|
||||
public function test_create_issue_action(): void
|
||||
{
|
||||
$issue = CreateIssue::run([
|
||||
'title' => 'New Bug',
|
||||
'type' => 'bug',
|
||||
'priority' => 'high',
|
||||
'labels' => ['agentic'],
|
||||
], $this->workspace->id);
|
||||
|
||||
$this->assertInstanceOf(Issue::class, $issue);
|
||||
$this->assertEquals('New Bug', $issue->title);
|
||||
$this->assertEquals('bug', $issue->type);
|
||||
$this->assertEquals('high', $issue->priority);
|
||||
$this->assertEquals(Issue::STATUS_OPEN, $issue->status);
|
||||
}
|
||||
|
||||
public function test_create_issue_action_validates_title(): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
CreateIssue::run(['title' => ''], $this->workspace->id);
|
||||
}
|
||||
|
||||
public function test_create_issue_action_validates_type(): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
CreateIssue::run([
|
||||
'title' => 'Bad Type',
|
||||
'type' => 'invalid',
|
||||
], $this->workspace->id);
|
||||
}
|
||||
|
||||
public function test_get_issue_action(): void
|
||||
{
|
||||
$created = Issue::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'slug' => 'get-test',
|
||||
'title' => 'Get Me',
|
||||
]);
|
||||
|
||||
$found = GetIssue::run('get-test', $this->workspace->id);
|
||||
|
||||
$this->assertEquals($created->id, $found->id);
|
||||
}
|
||||
|
||||
public function test_get_issue_action_throws_for_missing(): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
GetIssue::run('nonexistent', $this->workspace->id);
|
||||
}
|
||||
|
||||
public function test_list_issues_action(): void
|
||||
{
|
||||
Issue::create(['workspace_id' => $this->workspace->id, 'slug' => 'list-1', 'title' => 'A', 'status' => Issue::STATUS_OPEN]);
|
||||
Issue::create(['workspace_id' => $this->workspace->id, 'slug' => 'list-2', 'title' => 'B', 'status' => Issue::STATUS_CLOSED]);
|
||||
|
||||
$all = ListIssues::run($this->workspace->id, includeClosed: true);
|
||||
$this->assertCount(2, $all);
|
||||
|
||||
$open = ListIssues::run($this->workspace->id);
|
||||
$this->assertCount(1, $open);
|
||||
}
|
||||
|
||||
public function test_list_issues_filters_by_type(): void
|
||||
{
|
||||
Issue::create(['workspace_id' => $this->workspace->id, 'slug' => 'bug-1', 'title' => 'Bug', 'type' => Issue::TYPE_BUG]);
|
||||
Issue::create(['workspace_id' => $this->workspace->id, 'slug' => 'feat-1', 'title' => 'Feature', 'type' => Issue::TYPE_FEATURE]);
|
||||
|
||||
$bugs = ListIssues::run($this->workspace->id, type: 'bug');
|
||||
$this->assertCount(1, $bugs);
|
||||
$this->assertEquals('bug', $bugs->first()->type);
|
||||
}
|
||||
|
||||
public function test_update_issue_action(): void
|
||||
{
|
||||
Issue::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'slug' => 'update-test',
|
||||
'title' => 'Update Me',
|
||||
'status' => Issue::STATUS_OPEN,
|
||||
]);
|
||||
|
||||
$updated = UpdateIssue::run('update-test', [
|
||||
'status' => Issue::STATUS_IN_PROGRESS,
|
||||
'priority' => Issue::PRIORITY_URGENT,
|
||||
], $this->workspace->id);
|
||||
|
||||
$this->assertEquals(Issue::STATUS_IN_PROGRESS, $updated->status);
|
||||
$this->assertEquals(Issue::PRIORITY_URGENT, $updated->priority);
|
||||
}
|
||||
|
||||
public function test_update_issue_sets_closed_at_when_closing(): void
|
||||
{
|
||||
Issue::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'slug' => 'close-via-update',
|
||||
'title' => 'Close Me',
|
||||
'status' => Issue::STATUS_OPEN,
|
||||
]);
|
||||
|
||||
$updated = UpdateIssue::run('close-via-update', [
|
||||
'status' => Issue::STATUS_CLOSED,
|
||||
], $this->workspace->id);
|
||||
|
||||
$this->assertNotNull($updated->closed_at);
|
||||
}
|
||||
|
||||
public function test_archive_issue_action(): void
|
||||
{
|
||||
Issue::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'slug' => 'archive-action',
|
||||
'title' => 'Archive Me',
|
||||
]);
|
||||
|
||||
$archived = ArchiveIssue::run('archive-action', $this->workspace->id, 'Not needed');
|
||||
|
||||
$this->assertNotNull($archived->archived_at);
|
||||
$this->assertEquals(Issue::STATUS_CLOSED, $archived->status);
|
||||
}
|
||||
|
||||
public function test_add_issue_comment_action(): void
|
||||
{
|
||||
Issue::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'slug' => 'comment-action',
|
||||
'title' => 'Comment On Me',
|
||||
]);
|
||||
|
||||
$comment = AddIssueComment::run(
|
||||
'comment-action',
|
||||
$this->workspace->id,
|
||||
'gemini',
|
||||
'Found the root cause.',
|
||||
);
|
||||
|
||||
$this->assertInstanceOf(IssueComment::class, $comment);
|
||||
$this->assertEquals('gemini', $comment->author);
|
||||
$this->assertEquals('Found the root cause.', $comment->body);
|
||||
}
|
||||
|
||||
public function test_add_comment_validates_empty_body(): void
|
||||
{
|
||||
Issue::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'slug' => 'empty-comment',
|
||||
'title' => 'Empty Comment',
|
||||
]);
|
||||
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
AddIssueComment::run('empty-comment', $this->workspace->id, 'claude', '');
|
||||
}
|
||||
}
|
||||
286
src/php/tests/Feature/SprintTest.php
Normal file
286
src/php/tests/Feature/SprintTest.php
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Agentic\Tests\Feature;
|
||||
|
||||
use Core\Mod\Agentic\Actions\Sprint\ArchiveSprint;
|
||||
use Core\Mod\Agentic\Actions\Sprint\CreateSprint;
|
||||
use Core\Mod\Agentic\Actions\Sprint\GetSprint;
|
||||
use Core\Mod\Agentic\Actions\Sprint\ListSprints;
|
||||
use Core\Mod\Agentic\Actions\Sprint\UpdateSprint;
|
||||
use Core\Mod\Agentic\Models\Issue;
|
||||
use Core\Mod\Agentic\Models\Sprint;
|
||||
use Core\Tenant\Models\Workspace;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class SprintTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private Workspace $workspace;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->workspace = Workspace::factory()->create();
|
||||
}
|
||||
|
||||
// -- Model tests --
|
||||
|
||||
public function test_sprint_can_be_created(): void
|
||||
{
|
||||
$sprint = Sprint::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'slug' => 'sprint-1',
|
||||
'title' => 'Sprint 1',
|
||||
'goal' => 'Ship MVP',
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('sprints', [
|
||||
'id' => $sprint->id,
|
||||
'slug' => 'sprint-1',
|
||||
'goal' => 'Ship MVP',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_sprint_has_default_planning_status(): void
|
||||
{
|
||||
$sprint = Sprint::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'slug' => 'defaults',
|
||||
'title' => 'Defaults',
|
||||
]);
|
||||
|
||||
$this->assertEquals(Sprint::STATUS_PLANNING, $sprint->fresh()->status);
|
||||
}
|
||||
|
||||
public function test_sprint_can_be_activated(): void
|
||||
{
|
||||
$sprint = Sprint::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'slug' => 'activate-test',
|
||||
'title' => 'Activate',
|
||||
'status' => Sprint::STATUS_PLANNING,
|
||||
]);
|
||||
|
||||
$sprint->activate();
|
||||
|
||||
$fresh = $sprint->fresh();
|
||||
$this->assertEquals(Sprint::STATUS_ACTIVE, $fresh->status);
|
||||
$this->assertNotNull($fresh->started_at);
|
||||
}
|
||||
|
||||
public function test_sprint_can_be_completed(): void
|
||||
{
|
||||
$sprint = Sprint::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'slug' => 'complete-test',
|
||||
'title' => 'Complete',
|
||||
'status' => Sprint::STATUS_ACTIVE,
|
||||
]);
|
||||
|
||||
$sprint->complete();
|
||||
|
||||
$fresh = $sprint->fresh();
|
||||
$this->assertEquals(Sprint::STATUS_COMPLETED, $fresh->status);
|
||||
$this->assertNotNull($fresh->ended_at);
|
||||
}
|
||||
|
||||
public function test_sprint_can_be_cancelled_with_reason(): void
|
||||
{
|
||||
$sprint = Sprint::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'slug' => 'cancel-test',
|
||||
'title' => 'Cancel',
|
||||
]);
|
||||
|
||||
$sprint->cancel('Scope changed');
|
||||
|
||||
$fresh = $sprint->fresh();
|
||||
$this->assertEquals(Sprint::STATUS_CANCELLED, $fresh->status);
|
||||
$this->assertNotNull($fresh->ended_at);
|
||||
$this->assertNotNull($fresh->archived_at);
|
||||
$this->assertEquals('Scope changed', $fresh->metadata['cancel_reason']);
|
||||
}
|
||||
|
||||
public function test_sprint_generates_unique_slugs(): void
|
||||
{
|
||||
Sprint::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'slug' => 'sprint-1',
|
||||
'title' => 'Sprint 1',
|
||||
]);
|
||||
|
||||
$slug = Sprint::generateSlug('Sprint 1');
|
||||
|
||||
$this->assertEquals('sprint-1-1', $slug);
|
||||
}
|
||||
|
||||
public function test_sprint_has_issues(): void
|
||||
{
|
||||
$sprint = Sprint::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'slug' => 'with-issues',
|
||||
'title' => 'Has Issues',
|
||||
]);
|
||||
|
||||
Issue::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'sprint_id' => $sprint->id,
|
||||
'slug' => 'issue-1',
|
||||
'title' => 'First',
|
||||
]);
|
||||
|
||||
$this->assertCount(1, $sprint->fresh()->issues);
|
||||
}
|
||||
|
||||
public function test_sprint_calculates_progress(): void
|
||||
{
|
||||
$sprint = Sprint::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'slug' => 'progress-test',
|
||||
'title' => 'Progress',
|
||||
]);
|
||||
|
||||
Issue::create(['workspace_id' => $this->workspace->id, 'sprint_id' => $sprint->id, 'slug' => 'p-1', 'title' => 'A', 'status' => Issue::STATUS_OPEN]);
|
||||
Issue::create(['workspace_id' => $this->workspace->id, 'sprint_id' => $sprint->id, 'slug' => 'p-2', 'title' => 'B', 'status' => Issue::STATUS_IN_PROGRESS]);
|
||||
Issue::create(['workspace_id' => $this->workspace->id, 'sprint_id' => $sprint->id, 'slug' => 'p-3', 'title' => 'C', 'status' => Issue::STATUS_CLOSED]);
|
||||
Issue::create(['workspace_id' => $this->workspace->id, 'sprint_id' => $sprint->id, 'slug' => 'p-4', 'title' => 'D', 'status' => Issue::STATUS_CLOSED]);
|
||||
|
||||
$progress = $sprint->getProgress();
|
||||
|
||||
$this->assertEquals(4, $progress['total']);
|
||||
$this->assertEquals(2, $progress['closed']);
|
||||
$this->assertEquals(1, $progress['in_progress']);
|
||||
$this->assertEquals(1, $progress['open']);
|
||||
$this->assertEquals(50, $progress['percentage']);
|
||||
}
|
||||
|
||||
public function test_sprint_scopes(): void
|
||||
{
|
||||
Sprint::create(['workspace_id' => $this->workspace->id, 'slug' => 's-1', 'title' => 'A', 'status' => Sprint::STATUS_PLANNING]);
|
||||
Sprint::create(['workspace_id' => $this->workspace->id, 'slug' => 's-2', 'title' => 'B', 'status' => Sprint::STATUS_ACTIVE]);
|
||||
Sprint::create(['workspace_id' => $this->workspace->id, 'slug' => 's-3', 'title' => 'C', 'status' => Sprint::STATUS_CANCELLED]);
|
||||
|
||||
$this->assertCount(1, Sprint::active()->get());
|
||||
$this->assertCount(1, Sprint::planning()->get());
|
||||
$this->assertCount(2, Sprint::notCancelled()->get());
|
||||
}
|
||||
|
||||
public function test_sprint_to_mcp_context(): void
|
||||
{
|
||||
$sprint = Sprint::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'slug' => 'mcp-test',
|
||||
'title' => 'MCP Context',
|
||||
'goal' => 'Test goal',
|
||||
]);
|
||||
|
||||
$context = $sprint->toMcpContext();
|
||||
|
||||
$this->assertIsArray($context);
|
||||
$this->assertEquals('mcp-test', $context['slug']);
|
||||
$this->assertEquals('Test goal', $context['goal']);
|
||||
$this->assertArrayHasKey('status', $context);
|
||||
$this->assertArrayHasKey('progress', $context);
|
||||
}
|
||||
|
||||
// -- Action tests --
|
||||
|
||||
public function test_create_sprint_action(): void
|
||||
{
|
||||
$sprint = CreateSprint::run([
|
||||
'title' => 'New Sprint',
|
||||
'goal' => 'Deliver features',
|
||||
], $this->workspace->id);
|
||||
|
||||
$this->assertInstanceOf(Sprint::class, $sprint);
|
||||
$this->assertEquals('New Sprint', $sprint->title);
|
||||
$this->assertEquals(Sprint::STATUS_PLANNING, $sprint->status);
|
||||
}
|
||||
|
||||
public function test_create_sprint_validates_title(): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
CreateSprint::run(['title' => ''], $this->workspace->id);
|
||||
}
|
||||
|
||||
public function test_get_sprint_action(): void
|
||||
{
|
||||
$created = Sprint::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'slug' => 'get-test',
|
||||
'title' => 'Get Me',
|
||||
]);
|
||||
|
||||
$found = GetSprint::run('get-test', $this->workspace->id);
|
||||
|
||||
$this->assertEquals($created->id, $found->id);
|
||||
}
|
||||
|
||||
public function test_get_sprint_throws_for_missing(): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
GetSprint::run('nonexistent', $this->workspace->id);
|
||||
}
|
||||
|
||||
public function test_list_sprints_action(): void
|
||||
{
|
||||
Sprint::create(['workspace_id' => $this->workspace->id, 'slug' => 'ls-1', 'title' => 'A', 'status' => Sprint::STATUS_ACTIVE]);
|
||||
Sprint::create(['workspace_id' => $this->workspace->id, 'slug' => 'ls-2', 'title' => 'B', 'status' => Sprint::STATUS_CANCELLED]);
|
||||
|
||||
$all = ListSprints::run($this->workspace->id, includeCancelled: true);
|
||||
$this->assertCount(2, $all);
|
||||
|
||||
$notCancelled = ListSprints::run($this->workspace->id);
|
||||
$this->assertCount(1, $notCancelled);
|
||||
}
|
||||
|
||||
public function test_update_sprint_action(): void
|
||||
{
|
||||
Sprint::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'slug' => 'update-test',
|
||||
'title' => 'Update Me',
|
||||
'status' => Sprint::STATUS_PLANNING,
|
||||
]);
|
||||
|
||||
$updated = UpdateSprint::run('update-test', [
|
||||
'status' => Sprint::STATUS_ACTIVE,
|
||||
], $this->workspace->id);
|
||||
|
||||
$this->assertEquals(Sprint::STATUS_ACTIVE, $updated->status);
|
||||
$this->assertNotNull($updated->started_at);
|
||||
}
|
||||
|
||||
public function test_update_sprint_validates_status(): void
|
||||
{
|
||||
Sprint::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'slug' => 'bad-status',
|
||||
'title' => 'Bad',
|
||||
]);
|
||||
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
UpdateSprint::run('bad-status', ['status' => 'invalid'], $this->workspace->id);
|
||||
}
|
||||
|
||||
public function test_archive_sprint_action(): void
|
||||
{
|
||||
Sprint::create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'slug' => 'archive-test',
|
||||
'title' => 'Archive Me',
|
||||
]);
|
||||
|
||||
$archived = ArchiveSprint::run('archive-test', $this->workspace->id, 'Done');
|
||||
|
||||
$this->assertEquals(Sprint::STATUS_CANCELLED, $archived->status);
|
||||
$this->assertNotNull($archived->archived_at);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue