Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Add support for project CI/CD job token scope endpoints
* Add support for merge request resource label event endpoints
* Add support for merge request and merge request note award emoji endpoints
* Add support for merge request dependency endpoints
* Add support for `MergeRequests::remove`
* Add support for `MergeRequests::addToMergeTrain`
* Correct merge request API parameter handling
Expand Down
52 changes: 52 additions & 0 deletions src/Api/MergeRequests.php
Original file line number Diff line number Diff line change
Expand Up @@ -939,6 +939,58 @@ public function deleteLevelRule(int|string $project_id, int $mr_iid, int $approv
return $this->delete($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid).'/approval_rules/'.self::encodePath($approval_rule_id)));
}

public function dependencies(int|string $project_id, int $mr_iid): mixed
{
return $this->get($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid).'/blocks'));
}

public function showDependency(int|string $project_id, int $mr_iid, int $block_id): mixed
{
return $this->get($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid).'/blocks/'.self::encodePath($block_id)));
}

/**
* @param array $parameters {
*
* @var int $blocking_merge_request_id global ID of the blocking merge request
* @var int $blocking_merge_request_iid IID of the blocking merge request
* @var int|string $blocking_project_id project containing the blocking merge request
* }
*/
public function createDependency(int|string $project_id, int $mr_iid, array $parameters): mixed
{
$resolver = new OptionsResolver();
$resolver->setDefined('blocking_merge_request_id')
->setAllowedTypes('blocking_merge_request_id', 'int')
;
$resolver->setDefined('blocking_merge_request_iid')
->setAllowedTypes('blocking_merge_request_iid', 'int')
;
$resolver->setDefined('blocking_project_id')
->setAllowedTypes('blocking_project_id', ['int', 'string'])
;

$parameters = $resolver->resolve($parameters);
$hasBlockingId = isset($parameters['blocking_merge_request_id']);
$hasBlockingIid = isset($parameters['blocking_merge_request_iid']);

if ($hasBlockingId === $hasBlockingIid) {
throw new InvalidOptionsException('Exactly one of "blocking_merge_request_id" or "blocking_merge_request_iid" must be provided.');
}

return $this->post($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid).'/blocks'), $parameters);
}

public function deleteDependency(int|string $project_id, int $mr_iid, int $block_id): mixed
{
return $this->delete($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid).'/blocks/'.self::encodePath($block_id)));
}

public function blockedMergeRequests(int|string $project_id, int $mr_iid): mixed
{
return $this->get($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid).'/blockees'));
}

private static function isIntegerArray(array $value): bool
{
return \count($value) === \count(\array_filter($value, 'is_int'));
Expand Down
150 changes: 150 additions & 0 deletions tests/Api/MergeRequestsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1328,6 +1328,156 @@ public function shoudDeleteLevelRule(): void
$this->assertEquals($expectedValue, $api->deleteLevelRule(1, 2, 3));
}

#[Test]
public function shouldGetDependencies(): void
{
$expectedArray = [
['id' => 1, 'project_id' => 1, 'blocking_merge_request' => ['id' => 3], 'blocked_merge_request' => ['id' => 2]],
['id' => 2, 'project_id' => 1, 'blocking_merge_request' => ['id' => 4], 'blocked_merge_request' => ['id' => 2]],
];

$api = $this->getApiMock();
$api->expects($this->once())
->method('get')
->with('projects/1/merge_requests/2/blocks')
->willReturn($expectedArray)
;

$this->assertEquals($expectedArray, $api->dependencies(1, 2));
}

#[Test]
public function shouldGetDependenciesForStringProjectPath(): void
{
$expectedArray = [
['id' => 1, 'project_id' => 1, 'blocking_merge_request' => ['id' => 3], 'blocked_merge_request' => ['id' => 2]],
];

$api = $this->getApiMock();
$api->expects($this->once())
->method('get')
->with('projects/group%2Fproject/merge_requests/2/blocks')
->willReturn($expectedArray)
;

$this->assertEquals($expectedArray, $api->dependencies('group/project', 2));
}

#[Test]
public function shouldShowDependency(): void
{
$expectedArray = ['id' => 3, 'project_id' => 1, 'blocking_merge_request' => ['id' => 4], 'blocked_merge_request' => ['id' => 2]];

$api = $this->getApiMock();
$api->expects($this->once())
->method('get')
->with('projects/1/merge_requests/2/blocks/3')
->willReturn($expectedArray)
;

$this->assertEquals($expectedArray, $api->showDependency(1, 2, 3));
}

#[Test]
public function shouldCreateDependencyWithBlockingMergeRequestId(): void
{
$expectedArray = ['id' => 1, 'project_id' => 1, 'blocking_merge_request' => ['id' => 3], 'blocked_merge_request' => ['id' => 2]];

$api = $this->getApiMock();
$api->expects($this->once())
->method('post')
->with('projects/1/merge_requests/2/blocks', ['blocking_merge_request_id' => 3])
->willReturn($expectedArray)
;

$this->assertEquals($expectedArray, $api->createDependency(1, 2, ['blocking_merge_request_id' => 3]));
}

#[Test]
public function shouldCreateDependencyWithBlockingMergeRequestIid(): void
{
$expectedArray = ['id' => 1, 'project_id' => 1, 'blocking_merge_request' => ['iid' => 3], 'blocked_merge_request' => ['iid' => 2]];

$api = $this->getApiMock();
$api->expects($this->once())
->method('post')
->with('projects/1/merge_requests/2/blocks', ['blocking_merge_request_iid' => 3])
->willReturn($expectedArray)
;

$this->assertEquals($expectedArray, $api->createDependency(1, 2, ['blocking_merge_request_iid' => 3]));
}

#[Test]
public function shouldCreateDependencyWithBlockingProjectId(): void
{
$expectedArray = ['id' => 1, 'project_id' => 1, 'blocking_merge_request' => ['iid' => 3], 'blocked_merge_request' => ['iid' => 2]];
$parameters = ['blocking_merge_request_iid' => 3, 'blocking_project_id' => 'group/project'];

$api = $this->getApiMock();
$api->expects($this->once())
->method('post')
->with('projects/1/merge_requests/2/blocks', $parameters)
->willReturn($expectedArray)
;

$this->assertEquals($expectedArray, $api->createDependency(1, 2, $parameters));
}

#[Test]
public function shouldRejectDependencyWithoutBlockingMergeRequest(): void
{
$this->expectException(\Symfony\Component\OptionsResolver\Exception\InvalidOptionsException::class);
$this->expectExceptionMessage('Exactly one of "blocking_merge_request_id" or "blocking_merge_request_iid" must be provided.');

$this->getApiMock()->createDependency(1, 2, []);
}

#[Test]
public function shouldRejectDependencyWithMultipleBlockingMergeRequests(): void
{
$this->expectException(\Symfony\Component\OptionsResolver\Exception\InvalidOptionsException::class);
$this->expectExceptionMessage('Exactly one of "blocking_merge_request_id" or "blocking_merge_request_iid" must be provided.');

$this->getApiMock()->createDependency(1, 2, [
'blocking_merge_request_id' => 3,
'blocking_merge_request_iid' => 4,
]);
}

#[Test]
public function shouldDeleteDependency(): void
{
$expectedValue = true;

$api = $this->getApiMock();
$api->expects($this->once())
->method('delete')
->with('projects/1/merge_requests/2/blocks/3')
->willReturn($expectedValue)
;

$this->assertEquals($expectedValue, $api->deleteDependency(1, 2, 3));
}

#[Test]
public function shouldGetBlockedMergeRequests(): void
{
$expectedArray = [
['id' => 1, 'project_id' => 1, 'blocking_merge_request' => ['id' => 2], 'blocked_merge_request' => ['id' => 3]],
['id' => 2, 'project_id' => 1, 'blocking_merge_request' => ['id' => 2], 'blocked_merge_request' => ['id' => 4]],
];

$api = $this->getApiMock();
$api->expects($this->once())
->method('get')
->with('projects/1/merge_requests/2/blockees')
->willReturn($expectedArray)
;

$this->assertEquals($expectedArray, $api->blockedMergeRequests(1, 2));
}

protected function getMultipleMergeRequestsData(): array
{
return [
Expand Down