Merge branch 'main' into codex/v0.5-webhook-notification/06-notification-tests-and-release-hardening
# Conflicts: # tests/Feature/Mcp/KanbanGitMcpTest.php
This commit is contained in:
@@ -77,4 +77,37 @@ class AssignedTicketsApiTest extends TestCase
|
||||
->assertJsonCount(1, 'data.1.tickets')
|
||||
->assertJsonCount(2, 'data');
|
||||
}
|
||||
|
||||
public function test_assignee_metadata_is_hidden_when_no_visible_assigned_tickets_exist(): void
|
||||
{
|
||||
$viewer = User::factory()->create();
|
||||
$assignee = User::factory()->create([
|
||||
'name' => 'Hidden User',
|
||||
]);
|
||||
|
||||
$visibleProject = Project::factory()->create([
|
||||
'key' => 'VIS',
|
||||
'name' => 'Visible project',
|
||||
]);
|
||||
$hiddenProject = Project::factory()->create([
|
||||
'key' => 'HID',
|
||||
'name' => 'Hidden project',
|
||||
]);
|
||||
|
||||
$this->syncProjectPermissions($visibleProject, $viewer, ['view']);
|
||||
|
||||
Ticket::factory()->create([
|
||||
'assignee_user_id' => $assignee->id,
|
||||
'project_id' => $hiddenProject->id,
|
||||
'sequence_number' => 1,
|
||||
'ticket_key' => 'HID-1',
|
||||
'title' => 'Hidden ticket',
|
||||
]);
|
||||
|
||||
$this->withApplicationKey($viewer)
|
||||
->getJson(route('api.users.assigned-tickets', $assignee))
|
||||
->assertOk()
|
||||
->assertJsonPath('data', [])
|
||||
->assertJsonPath('meta.assignee', null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,5 +83,13 @@ class OpenApiDocumentationTest extends TestCase
|
||||
$this->assertArrayHasKey('UserGitConnectionRequest', $document['components']['schemas']);
|
||||
$this->assertArrayHasKey('AssignedTicketsPayloadEnvelope', $document['components']['schemas']);
|
||||
$this->assertArrayHasKey('AssignedTicketsProjectGroup', $document['components']['schemas']);
|
||||
|
||||
$assigneeSchema = $document['components']['schemas']['AssignedTicketsPayloadEnvelope']['properties']['meta']['properties']['assignee'] ?? null;
|
||||
|
||||
$this->assertIsArray($assigneeSchema);
|
||||
$this->assertSame([
|
||||
['$ref' => '#/components/schemas/User'],
|
||||
['type' => 'null'],
|
||||
], $assigneeSchema['anyOf'] ?? null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Tests\Feature\Api;
|
||||
|
||||
use App\Actions\ProjectTicketMetadata\InitializeProjectTicketMetadata;
|
||||
use App\Models\Project;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
@@ -149,6 +150,45 @@ class SettingsApiTest extends TestCase
|
||||
->assertNoContent();
|
||||
}
|
||||
|
||||
public function test_project_payloads_only_return_users_who_can_view_the_project(): void
|
||||
{
|
||||
[$user, $project] = $this->projectContext();
|
||||
$viewer = User::factory()->create(['name' => 'Visible User']);
|
||||
User::factory()->create(['name' => 'Hidden User']);
|
||||
$teamViewer = User::factory()->create(['name' => 'Team Viewer']);
|
||||
$team = Team::factory()->create();
|
||||
|
||||
$this->syncProjectPermissions($project, $viewer, ['view']);
|
||||
$team->users()->attach($teamViewer);
|
||||
$this->syncProjectPermissions($project, $team, ['view']);
|
||||
|
||||
$ticket = $project->tickets()->firstOrCreate([
|
||||
'sequence_number' => 1,
|
||||
], [
|
||||
'ticket_key' => 'CORE-1',
|
||||
'ticket_priority_id' => $project->ticketPriorities()->firstOrFail()->id,
|
||||
'ticket_status_id' => $project->ticketStatuses()->firstOrFail()->id,
|
||||
'ticket_type_id' => $project->ticketTypes()->where('is_parent', false)->firstOrFail()->id,
|
||||
'title' => 'Scoped users ticket',
|
||||
]);
|
||||
|
||||
$this->withApplicationKey($user)
|
||||
->getJson(route('api.projects.backlog', $project))
|
||||
->assertOk()
|
||||
->assertJsonPath('data.users.0.name', $user->name)
|
||||
->assertJsonPath('data.users.1.name', 'Team Viewer')
|
||||
->assertJsonPath('data.users.2.name', 'Visible User')
|
||||
->assertJsonCount(3, 'data.users');
|
||||
|
||||
$this->withApplicationKey($user)
|
||||
->getJson(route('api.projects.tickets.show', [$project, $ticket]))
|
||||
->assertOk()
|
||||
->assertJsonPath('data.users.0.name', $user->name)
|
||||
->assertJsonPath('data.users.1.name', 'Team Viewer')
|
||||
->assertJsonPath('data.users.2.name', 'Visible User')
|
||||
->assertJsonCount(3, 'data.users');
|
||||
}
|
||||
|
||||
public function test_project_repositories_and_user_git_connections_are_available(): void
|
||||
{
|
||||
Http::preventStrayRequests();
|
||||
|
||||
@@ -85,6 +85,77 @@ class TicketApiTest extends TestCase
|
||||
->assertJsonPath('data.ticket.story_points', 5);
|
||||
}
|
||||
|
||||
public function test_ticket_update_hides_author_for_non_admins_when_author_visibility_is_disabled(): void
|
||||
{
|
||||
$writer = User::factory()->create();
|
||||
$author = User::factory()->create([
|
||||
'name' => 'Author User',
|
||||
]);
|
||||
$project = Project::factory()->create([
|
||||
'key' => 'CORE',
|
||||
'show_author' => false,
|
||||
]);
|
||||
|
||||
app(InitializeProjectTicketMetadata::class)->handle($project);
|
||||
|
||||
$priority = $project->ticketPriorities()->firstOrFail();
|
||||
$status = $project->ticketStatuses()->firstOrFail();
|
||||
$type = $project->ticketTypes()->where('is_parent', false)->firstOrFail();
|
||||
|
||||
$ticket = Ticket::factory()->create([
|
||||
'author_user_id' => $author->id,
|
||||
'project_id' => $project->id,
|
||||
'sequence_number' => 1,
|
||||
'ticket_key' => 'CORE-1',
|
||||
'ticket_priority_id' => $priority->id,
|
||||
'ticket_status_id' => $status->id,
|
||||
'ticket_type_id' => $type->id,
|
||||
'title' => 'API ticket',
|
||||
]);
|
||||
|
||||
$this->syncProjectPermissions($project, $writer, ['write', 'view']);
|
||||
|
||||
$this->withApplicationKey($writer)
|
||||
->patchJson(route('api.projects.tickets.update', [$project, $ticket]), [
|
||||
'acceptance_criteria' => $ticket->acceptance_criteria,
|
||||
'blocked_comment' => $ticket->blocked_comment,
|
||||
'description' => $ticket->description,
|
||||
'is_blocked' => $ticket->is_blocked,
|
||||
'parent_ticket_id' => $ticket->parent_ticket_id,
|
||||
'story_points' => $ticket->story_points,
|
||||
'ticket_priority_id' => $priority->id,
|
||||
'ticket_type_id' => $type->id,
|
||||
'title' => 'Updated API ticket',
|
||||
])
|
||||
->assertOk()
|
||||
->assertJsonPath('data.author', null)
|
||||
->assertJsonPath('data.author_user_id', null);
|
||||
}
|
||||
|
||||
public function test_ticket_options_only_return_project_visible_users(): void
|
||||
{
|
||||
[$user, $project] = $this->projectContext();
|
||||
$projectViewer = User::factory()->create();
|
||||
$outsider = User::factory()->create();
|
||||
|
||||
$this->syncProjectPermissions($project, $projectViewer, ['view']);
|
||||
|
||||
$response = $this->withApplicationKey($user)
|
||||
->getJson(route('api.projects.ticket-options', [
|
||||
'project' => $project,
|
||||
'source' => 'backlog',
|
||||
]))
|
||||
->assertOk();
|
||||
|
||||
$userIds = collect($response->json('data.users'))
|
||||
->pluck('id')
|
||||
->all();
|
||||
|
||||
$this->assertContains($user->id, $userIds);
|
||||
$this->assertContains($projectViewer->id, $userIds);
|
||||
$this->assertNotContains($outsider->id, $userIds);
|
||||
}
|
||||
|
||||
public function test_tickets_can_return_status_targets_and_move_statuses(): void
|
||||
{
|
||||
[$user, $project] = $this->projectContext();
|
||||
|
||||
@@ -407,6 +407,15 @@ class KanbanGitMcpTest extends TestCase
|
||||
$this->assertDatabaseCount('ticket_worklog_entries', 0);
|
||||
}
|
||||
|
||||
public function test_list_tickets_by_status_requires_a_status_selector(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
KanbanGitServer::actingAs($user)
|
||||
->tool(ListTicketsByStatus::class)
|
||||
->assertHasErrors(['Provide ticket_status_id, status_slug, or status_name.']);
|
||||
}
|
||||
|
||||
public function test_ambiguous_status_name_returns_mcp_error(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
@@ -89,6 +89,9 @@ class DeliverNotificationActionTest extends TestCase
|
||||
&& $request->hasHeader('Content-Type', 'application/json')
|
||||
&& $request->data() === [
|
||||
'content' => 'CORE-42 moved from Backlog to Doing by Erica Example: https://kanbangit.test/projects/core/tickets/42 ($unknown)',
|
||||
'allowed_mentions' => [
|
||||
'parse' => [],
|
||||
],
|
||||
];
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user