283 lines
11 KiB
PHP
283 lines
11 KiB
PHP
<?php
|
|
|
|
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;
|
|
use Tests\TestCase;
|
|
|
|
class SettingsApiTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
public function test_board_settings_can_be_read_and_mutated(): void
|
|
{
|
|
[$user, $project] = $this->projectContext();
|
|
|
|
$this->withApplicationKey($user)
|
|
->getJson(route('api.projects.settings.board', $project))
|
|
->assertOk()
|
|
->assertJsonPath('data.backlogTicketStatuses.0.name', 'Backlog');
|
|
|
|
$columnId = $this->withApplicationKey($user)
|
|
->postJson(route('api.projects.settings.board.columns.store', $project), [
|
|
'name' => 'In Review',
|
|
])
|
|
->assertCreated()
|
|
->assertJsonPath('data.name', 'In Review')
|
|
->json('data.id');
|
|
|
|
$this->withApplicationKey($user)
|
|
->patchJson(route('api.projects.settings.board.columns.update', [$project, $columnId]), [
|
|
'name' => 'Review',
|
|
'position' => 1,
|
|
])
|
|
->assertOk()
|
|
->assertJsonPath('data.name', 'Review');
|
|
|
|
$statusId = $this->withApplicationKey($user)
|
|
->postJson(route('api.projects.settings.board.statuses.store', $project), [
|
|
'board_column_id' => $columnId,
|
|
'name' => 'Ready',
|
|
])
|
|
->assertCreated()
|
|
->assertJsonPath('data.name', 'Ready')
|
|
->json('data.id');
|
|
$replacementStatusId = $this->withApplicationKey($user)
|
|
->postJson(route('api.projects.settings.board.statuses.store', $project), [
|
|
'board_column_id' => $columnId,
|
|
'name' => 'QA',
|
|
])
|
|
->assertCreated()
|
|
->json('data.id');
|
|
|
|
$backlogStatus = $project->ticketStatuses()->whereNull('board_column_id')->firstOrFail();
|
|
|
|
$this->withApplicationKey($user)
|
|
->patchJson(route('api.projects.settings.board.statuses.transitions.update', [$project, $backlogStatus]), [
|
|
'allowed_ticket_status_ids' => [$statusId],
|
|
])
|
|
->assertOk()
|
|
->assertJsonPath('data.allowed_target_statuses.0.id', $statusId);
|
|
|
|
$this->withApplicationKey($user)
|
|
->deleteJson(route('api.projects.settings.board.statuses.destroy', [$project, $statusId]))
|
|
->assertNoContent();
|
|
|
|
$this->assertDatabaseHas('ticket_statuses', [
|
|
'id' => $replacementStatusId,
|
|
]);
|
|
|
|
$this->withApplicationKey($user)
|
|
->deleteJson(route('api.projects.settings.board.columns.destroy', [$project, $columnId]))
|
|
->assertNoContent();
|
|
}
|
|
|
|
public function test_metadata_settings_can_be_read_and_mutated(): void
|
|
{
|
|
[$user, $project] = $this->projectContext();
|
|
|
|
$this->withApplicationKey($user)
|
|
->getJson(route('api.projects.settings.metadata', $project))
|
|
->assertOk()
|
|
->assertJsonPath('data.ticketTypes.0.name', 'Task');
|
|
|
|
$this->withApplicationKey($user)
|
|
->patchJson(route('api.projects.settings.metadata.update', $project), [
|
|
'show_author' => false,
|
|
'show_story_points' => false,
|
|
])
|
|
->assertOk()
|
|
->assertJsonPath('data.show_author', false);
|
|
|
|
$typeId = $this->withApplicationKey($user)
|
|
->postJson(route('api.projects.settings.metadata.types.store', $project), [
|
|
'is_parent' => true,
|
|
'name' => 'Epic',
|
|
])
|
|
->assertCreated()
|
|
->assertJsonPath('data.name', 'Epic')
|
|
->json('data.id');
|
|
|
|
$this->withApplicationKey($user)
|
|
->patchJson(route('api.projects.settings.metadata.types.update', [$project, $typeId]), [
|
|
'name' => 'Initiative',
|
|
'position' => 1,
|
|
])
|
|
->assertOk()
|
|
->assertJsonPath('data.name', 'Initiative');
|
|
|
|
$priorityId = $this->withApplicationKey($user)
|
|
->postJson(route('api.projects.settings.metadata.priorities.store', $project), [
|
|
'name' => 'Urgent',
|
|
])
|
|
->assertCreated()
|
|
->json('data.id');
|
|
|
|
$fieldId = $this->withApplicationKey($user)
|
|
->postJson(route('api.projects.settings.metadata.fields.store', $project), [
|
|
'field_type' => 'number',
|
|
'label' => 'Risk',
|
|
])
|
|
->assertCreated()
|
|
->json('data.id');
|
|
|
|
$linkTypeId = $this->withApplicationKey($user)
|
|
->postJson(route('api.projects.settings.metadata.link-types.store', $project), [
|
|
'name' => 'Relates to',
|
|
'counterpart_name' => 'Relates to',
|
|
])
|
|
->assertCreated()
|
|
->json('data.id');
|
|
|
|
$this->withApplicationKey($user)
|
|
->deleteJson(route('api.projects.settings.metadata.fields.destroy', [$project, $fieldId]))
|
|
->assertNoContent();
|
|
|
|
$this->withApplicationKey($user)
|
|
->deleteJson(route('api.projects.settings.metadata.link-types.destroy', [$project, $linkTypeId]))
|
|
->assertNoContent();
|
|
|
|
$this->withApplicationKey($user)
|
|
->deleteJson(route('api.projects.settings.metadata.priorities.destroy', [$project, $priorityId]))
|
|
->assertNoContent();
|
|
|
|
$this->withApplicationKey($user)
|
|
->deleteJson(route('api.projects.settings.metadata.types.destroy', [$project, $typeId]))
|
|
->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']);
|
|
$hiddenUser = 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',
|
|
]);
|
|
|
|
$expectedUserIds = [$user->id, $viewer->id, $teamViewer->id];
|
|
|
|
$backlogResponse = $this->withApplicationKey($user)
|
|
->getJson(route('api.projects.backlog', $project))
|
|
->assertOk()
|
|
->assertJsonCount(3, 'data.users');
|
|
|
|
$backlogUserIds = collect($backlogResponse->json('data.users'))
|
|
->pluck('id')
|
|
->all();
|
|
|
|
$this->assertEqualsCanonicalizing($expectedUserIds, $backlogUserIds);
|
|
$this->assertNotContains($hiddenUser->id, $backlogUserIds);
|
|
|
|
$ticketResponse = $this->withApplicationKey($user)
|
|
->getJson(route('api.projects.tickets.show', [$project, $ticket]))
|
|
->assertOk()
|
|
->assertJsonCount(3, 'data.users');
|
|
|
|
$ticketUserIds = collect($ticketResponse->json('data.users'))
|
|
->pluck('id')
|
|
->all();
|
|
|
|
$this->assertEqualsCanonicalizing($expectedUserIds, $ticketUserIds);
|
|
$this->assertNotContains($hiddenUser->id, $ticketUserIds);
|
|
}
|
|
|
|
public function test_project_repositories_and_user_git_connections_are_available(): void
|
|
{
|
|
Http::preventStrayRequests();
|
|
Http::fake([
|
|
'https://git.example.test/api/v1/user' => Http::response([
|
|
'id' => 101,
|
|
'login' => 'api-user',
|
|
'full_name' => 'API User',
|
|
]),
|
|
'https://git.example.test/api/v1/user/repos*' => Http::response([
|
|
[
|
|
'id' => 501,
|
|
'name' => 'kanbangit',
|
|
'full_name' => 'core-team/kanbangit',
|
|
'html_url' => 'https://git.example.test/core-team/kanbangit',
|
|
'owner' => [
|
|
'login' => 'core-team',
|
|
],
|
|
'private' => true,
|
|
],
|
|
]),
|
|
]);
|
|
|
|
[$user, $project] = $this->projectContext();
|
|
|
|
$gitConnectionId = $this->withApplicationKey($user)
|
|
->postJson(route('api.git-connections.store'), [
|
|
'api_token' => 'secret-token',
|
|
'base_url' => 'https://git.example.test/api/v1',
|
|
'provider' => 'gitea',
|
|
])
|
|
->assertCreated()
|
|
->assertJsonPath('data.account_login', 'api-user')
|
|
->json('data.id');
|
|
|
|
$this->withApplicationKey($user)
|
|
->getJson(route('api.git-connections.index'))
|
|
->assertOk()
|
|
->assertJsonPath('data.0.id', $gitConnectionId);
|
|
|
|
$repositoryConnectionId = $this->withApplicationKey($user)
|
|
->postJson(route('api.projects.settings.repositories.store', $project), [
|
|
'provider_repository_id' => '501',
|
|
'user_git_connection_id' => $gitConnectionId,
|
|
])
|
|
->assertCreated()
|
|
->assertJsonPath('data.repository_full_name', 'core-team/kanbangit')
|
|
->json('data.id');
|
|
|
|
$this->withApplicationKey($user)
|
|
->getJson(route('api.projects.settings.repositories', $project))
|
|
->assertOk()
|
|
->assertJsonPath('data.repositoryConnections.0.id', $repositoryConnectionId)
|
|
->assertJsonPath('data.availableRepositorySources.0.repositories.0.full_name', 'core-team/kanbangit');
|
|
|
|
$this->withApplicationKey($user)
|
|
->deleteJson(route('api.projects.settings.repositories.destroy', [$project, $repositoryConnectionId]))
|
|
->assertNoContent();
|
|
|
|
$this->withApplicationKey($user)
|
|
->deleteJson(route('api.git-connections.destroy', $gitConnectionId))
|
|
->assertNoContent();
|
|
}
|
|
|
|
/**
|
|
* @return array{0: User, 1: Project}
|
|
*/
|
|
private function projectContext(): array
|
|
{
|
|
$user = User::factory()->create();
|
|
$project = Project::factory()->create([
|
|
'key' => 'CORE',
|
|
]);
|
|
|
|
app(InitializeProjectTicketMetadata::class)->handle($project);
|
|
$this->syncProjectPermissions($project, $user, ['edit', 'view']);
|
|
|
|
return [$user, $project];
|
|
}
|
|
}
|