Merge pull request 'Add template context to API and MCP project payloads' (#96) from koda/kan-8-add-project-template-to-mcp-api into epic/kan-3
linter / quality (pull_request) Successful in 1m30s
tests / ci (8.4) (pull_request) Failing after 1m47s

Reviewed-on: OpenSource/KanbanGit#96
This commit was merged in pull request #96.
This commit is contained in:
2026-06-23 22:15:15 +00:00
5 changed files with 165 additions and 1 deletions
+70
View File
@@ -113,6 +113,76 @@ class ProjectApiTest extends TestCase
$this->assertSame(['Spike'], $project->ticketTypes()->pluck('name')->all());
}
public function test_project_api_exposes_template_context_and_overrides(): void
{
$user = User::factory()->create();
$project = Project::factory()->create([
'key' => 'TMP1',
'name' => 'Template Driven Project',
'show_author' => false,
'show_story_points' => false,
]);
$template = ProjectTemplate::factory()->create([
'name' => 'Workflow Template',
'slug' => 'workflow-template',
'copy_areas' => [
DuplicateProjectConfiguration::COPY_PROJECT_SETTINGS,
DuplicateProjectConfiguration::COPY_TICKET_TYPES,
],
'configuration_snapshot' => [
DuplicateProjectConfiguration::COPY_PROJECT_SETTINGS => [
'show_author' => true,
'show_story_points' => true,
],
DuplicateProjectConfiguration::COPY_TICKET_TYPES => [
[
'name' => 'Bug',
'position' => 1,
'is_parent' => false,
],
[
'name' => 'Feature',
'position' => 2,
'is_parent' => false,
],
],
],
]);
$project->update([
'project_template_id' => $template->id,
]);
$project->ticketTypes()->delete();
TicketType::factory()->create([
'project_id' => $project->id,
'name' => 'Bug',
'position' => 1,
'is_parent' => false,
]);
TicketType::factory()->create([
'project_id' => $project->id,
'name' => 'Feature',
'position' => 2,
'is_parent' => false,
]);
$this->syncProjectPermissions($project, $user, ['view']);
$this->withApplicationKey($user)
->getJson(route('api.projects.show', $project))
->assertOk()
->assertJsonPath('data.project_template.id', $template->id)
->assertJsonPath('data.project_template.slug', 'workflow-template')
->assertJsonPath('data.project_template.configuration_snapshot.project_settings.show_author', true)
->assertJsonPath('data.template_state.inherits_from_template', true)
->assertJsonPath('data.template_state.template_id', $template->id)
->assertJsonPath('data.template_state.template_configuration.project_settings.show_author', true)
->assertJsonPath('data.template_state.effective_configuration.ticket_types.0.name', 'Bug')
->assertJsonPath('data.template_state.inherited_configuration.ticket_types.1.name', 'Feature')
->assertJsonPath('data.template_state.overridden_configuration.project_settings.show_author', false)
->assertJsonPath('data.template_state.overridden_areas.0.value', DuplicateProjectConfiguration::COPY_PROJECT_SETTINGS);
}
public function test_users_can_duplicate_projects_via_api(): void
{
$user = User::factory()->create();
+63
View File
@@ -4,6 +4,7 @@ namespace Tests\Feature\Mcp;
use App\Actions\Notifications\DeliverNotificationAction;
use App\Actions\ProjectTicketMetadata\InitializeProjectTicketMetadata;
use App\Actions\Projects\DuplicateProjectConfiguration;
use App\Enums\NotificationDeliveryStatus;
use App\Mcp\Servers\KanbanGitServer;
use App\Mcp\Tools\GetTicket;
@@ -15,8 +16,10 @@ use App\Mcp\Tools\UpdateTicketDescription;
use App\Models\BoardColumn;
use App\Models\NotificationAction;
use App\Models\Project;
use App\Models\ProjectTemplate;
use App\Models\Ticket;
use App\Models\TicketStatus;
use App\Models\TicketType;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue;
@@ -207,6 +210,66 @@ class KanbanGitMcpTest extends TestCase
->etc());
}
public function test_get_ticket_exposes_project_template_context_and_overrides(): void
{
$user = User::factory()->create();
$project = $this->project('CORE', 'Core project');
$template = ProjectTemplate::factory()->create([
'name' => 'Agent Workflow Template',
'slug' => 'agent-workflow-template',
'copy_areas' => [
DuplicateProjectConfiguration::COPY_PROJECT_SETTINGS,
DuplicateProjectConfiguration::COPY_TICKET_TYPES,
],
'configuration_snapshot' => [
DuplicateProjectConfiguration::COPY_PROJECT_SETTINGS => [
'show_author' => true,
'show_story_points' => true,
],
DuplicateProjectConfiguration::COPY_TICKET_TYPES => [
[
'name' => 'Bug',
'position' => 1,
'is_parent' => false,
],
],
],
]);
$project->update([
'project_template_id' => $template->id,
'show_author' => false,
'show_story_points' => false,
]);
$project->ticketTypes()->delete();
TicketType::factory()->create([
'project_id' => $project->id,
'name' => 'Bug',
'position' => 1,
'is_parent' => false,
]);
$status = $project->ticketStatuses()->firstOrFail();
$ticket = $this->ticket($project, $status, [
'ticket_key' => 'CORE-1',
'title' => 'Template aware ticket',
]);
$this->syncProjectPermissions($project, $user, ['view']);
KanbanGitServer::actingAs($user)
->tool(GetTicket::class, [
'ticket_id' => $ticket->id,
])
->assertOk()
->assertStructuredContent(fn (AssertableJson $json) => $json
->where('data.project.project_template.id', $template->id)
->where('data.project.project_template.slug', 'agent-workflow-template')
->where('data.project.template_state.inherits_from_template', true)
->where('data.project.template_state.template_configuration.project_settings.show_author', true)
->where('data.project.template_state.overridden_configuration.project_settings.show_author', false)
->where('data.project.template_state.effective_configuration.ticket_types.0.name', 'Bug')
->etc());
}
public function test_description_update_requires_write_permission_and_preserves_unrelated_fields(): void
{
$user = User::factory()->create();