diff --git a/.gitattributes b/.gitattributes index 5a646aae4..67bd3b5ec 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,11 +1,16 @@ * text=auto -/doc export-ignore -/test export-ignore +/tests export-ignore +/vendor-bin export-ignore /.gitattributes export-ignore +/.github export-ignore /.gitignore export-ignore /.styleci.yml export-ignore -/.travis.yml export-ignore +/Makefile export-ignore +/phpstan-baseline.neon export-ignore +/phpstan.neon.dist export-ignore /phpunit.xml.dist export-ignore +/psalm-baseline.xml export-ignore +/psalm.xml export-ignore +/CHANGELOG.md export-ignore /README.md export-ignore -/UPGRADE.md export-ignore diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..590c95931 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,132 @@ +# CONTRIBUTOR COVENANT CODE OF CONDUCT + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +hello@gjcampbell.co.uk. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available +at [https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 000000000..5570c626b --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,31 @@ +# CONTRIBUTION GUIDELINES + +Contributions are **welcome** and will be fully **credited**. + +We accept contributions via pull requests on GitHub. Please review these guidelines before continuing. + +## Guidelines + +* Please follow the [PSR-12 Coding Style Guide](https://www.php-fig.org/psr/psr-12/), enforced by [StyleCI](https://styleci.io/). +* Ensure that the current tests pass, and if you've added something new, add the tests where relevant. +* Send a coherent commit history, making sure each commit in your pull request is meaningful. +* You may need to [rebase](https://git-scm.com/book/en/v2/Git-Branching-Rebasing) to avoid merge conflicts. +* If you are changing or adding to the behaviour or public API, you may need to update the docs. +* Please remember that we follow [Semantic Versioning](https://semver.org/). + +## Running Tests + +First, install the dependencies: + +```bash +$ make install +``` + +Then run the test suite and static analyzers: + +```bash +$ make test +``` + +* The tests will be automatically run by [GitHub Actions](https://github.com/features/actions) against pull requests. +* We also have [StyleCI](https://styleci.io/) set up to automatically fix any code style issues. diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..0c5598bfa --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [GrahamCampbell] diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 000000000..7236d4b14 --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,14 @@ +# SECURITY POLICY + +## Supported Versions + +After each new major release, the previous release will be supported for no +less than 2 years, unless explicitly stated otherwise. This may mean that there +are multiple supported versions at any given time. + +## Reporting a Vulnerability + +If you discover a security vulnerability within this package, please send an +email to Graham Campbell at hello@gjcampbell.co.uk. All security vulnerabilities +will be promptly addressed. Please do not disclose security-related issues +publicly until a fix has been announced. diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 000000000..09ab97f8b --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,8 @@ +daysUntilStale: 120 +daysUntilClose: 30 +staleLabel: stale +markComment: > + This issue has been automatically marked as stale because there has been no + recent activity. It will be closed after 30 days if no further activity + occurs. Thank you for your contributions. +closeComment: false diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml new file mode 100644 index 000000000..b37fe5d04 --- /dev/null +++ b/.github/workflows/static.yml @@ -0,0 +1,74 @@ +name: Static Analysis + +on: + push: + pull_request: + +jobs: + phpstan: + name: PHPStan + runs-on: ubuntu-24.04 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.1' + tools: composer:v2 + coverage: none + env: + update: true + + - name: Install Dependencies + uses: nick-invision/retry@v3 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer update --no-interaction --no-progress + + - name: Install PHPStan + uses: nick-invision/retry@v3 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer bin phpstan update --no-interaction --no-progress + + - name: Execute PHPStan + run: vendor/bin/phpstan analyze --no-progress + + psalm: + name: Psalm + runs-on: ubuntu-24.04 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.1' + tools: composer:v2 + coverage: none + env: + update: true + + - name: Install Dependencies + uses: nick-invision/retry@v3 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer update --no-interaction --no-progress + + - name: Install Psalm + uses: nick-invision/retry@v3 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer bin psalm update --no-interaction --no-progress + + - name: Execute Psalm + run: vendor/bin/psalm.phar --no-progress --output-format=github diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 000000000..f2a04d294 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,47 @@ +name: Tests + +on: + push: + pull_request: + +jobs: + tests: + name: PHP ${{ matrix.php }} + runs-on: ubuntu-24.04 + + strategy: + matrix: + php: ['8.1', '8.2', '8.3', '8.4'] + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + tools: composer:v2 + coverage: none + env: + update: true + + - name: Setup Problem Matchers + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - name: Install Dependencies + uses: nick-invision/retry@v3 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer update --no-interaction --no-progress + + - name: Install PHPUnit + uses: nick-invision/retry@v3 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer bin phpunit update --no-interaction --no-progress + + - name: Execute PHPUnit + run: vendor/bin/phpunit diff --git a/.gitignore b/.gitignore index d1502b087..04626b2fe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ -vendor/ +.phpunit.result.cache composer.lock +phpstan.neon +phpunit.xml +vendor diff --git a/.styleci.yml b/.styleci.yml index 247a09c5d..87da8d093 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -1 +1,21 @@ -preset: psr2 +preset: symfony + +risky: true + +enabled: + - align_phpdoc + - alpha_ordered_imports + - array_indentation + - const_visibility_required + - declare_strict_types + - native_constant_invocation + - native_function_invocation + - phpdoc_order + - void_return + +disabled: + - native_constant_invocation_symfony + - native_function_invocation_symfony + - phpdoc_align + - phpdoc_to_comment + - phpdoc_var_without_name diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a097e0ce1..000000000 --- a/.travis.yml +++ /dev/null @@ -1,24 +0,0 @@ -language: php - -sudo: false - -php: - - 5.6 - - 7.0 - - 7.1 - - 7.2 - -matrix: - # test latest PHP stable version with lowest dependencies and phpstan static analysis - include: - - php: 7.1 - env: COMPOSER_FLAGS="--prefer-lowest" STATIC_ANALYSIS=yes - -before_script: - - travis_retry composer self-update - - travis_retry composer update --no-interaction ${COMPOSER_FLAGS} - - if [ "$STATIC_ANALYSIS" != "" ]; then curl -L https://github.com/phpstan/phpstan/releases/download/0.8/phpstan.phar -o phpstan.phar; fi; - -script: - - vendor/bin/phpunit --verbose --coverage-text - - if [ "$STATIC_ANALYSIS" != "" ]; then php phpstan.phar analyse --level=4 lib; fi; diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..65398fb44 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,330 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [12.0.0] - 2025-02-23 + +* Add PHP 8.4 support +* Drop support for PHP earlier than 8.1 +* Moved various param types to native PHP types + +## [11.14.0] - 2024-03-11 + +* Add support for `php-http/cache-plugin:^2.0` +* Add support for `'approved'` `status` in `Project::events` +* Add support for `name` in `createRelease` and `updateRelease` +* Add support for date filtering to `GroupsMilestones::all()` +* Update `MergeRequests::all` to use millisecond precision for date filters + +## [11.13.0] - 2023-12-03 + +* Add support for `symfony/options-resolver:^7.0` +* Add support for `status` and `environment` in `Deployments::all` +* Add support for `Groups::search`, `Projects::search`, and `Search::all` + +## [11.12.0] - 2023-10-08 + +* Add PHP 8.3 support +* Add `Projects::updateProtectedBranch` and `Projects::updateApprovalsConfiguration` +* Add support for `environment_scope` in `Projects::removeVariable` +* Add support for `filter` in `Projects::variable` +* Add support for `author` in `Repositories::commits` +* Add support for additional parameters in `Projects::labels` and `Groups::labels` + +## [11.11.1] - 2023-10-08 + +* Fixed double encoding of job name in artifacts download + +## [11.11.0] - 2023-07-17 + +* Add support for `author_id` in `Issues::all` +* Add support for `tier` in `Environments::create` +* Add support for `expires_at` in `Groups::addMember` +* Add support for `include_retried` in `Jobs::pipelineBridges` +* Add support for additional parameters in `Projects::deployment` +* Add support for additional parameters in `Projects::forks` +* Add support for `Events::all` +* Add support for `Users::removeUserIdentity` +* Add support for `MergeRequests::showParticipants` + +## [11.10.0] - 2023-04-30 + +* Add support for `Packages::addGenericFile` +* Add support for `Milestones::mergeRequests` +* Add support for `Project::removeTrigger` +* Add support for `Schedules::takeOwnership` and `Schedules::play` +* Add support for `access_level` in `Projects::createProjectAccessToken` +* Add support for `expires_at` in `Projects::addMember` and `Projects::saveMember` +* Add support for `order_by` `version` in `Tags::all` +* Added support for `psr/http-message` v2 + +## [11.9.1] - 2023-04-30 + +* Corrected upload avatar endpoint + +## [11.9.0] - 2023-03-06 + +* Add PHP 8.2 support +* Add support for group and project deploy tokens +* Add source parameter to pipelines API +* Add support for `Jobs::artifactByJobId` +* Add support for `Users::usersStarredProjects` +* Add support for `Groups::issues` +* Add support for `Groups::iterations` +* Add support for `Projects::iterations` +* Add support for `Projects::projectAccessToken` +* Add support for `Projects::pipelineTestReport` +* Add support for `Projects::pipelineTestReportSummary` +* Add support for `allowed_to_create` in `Projects::addProtectedTag` +* Add support for `update_at` order by in `Projects::pipelines` +* Added additional parameters to `Issues::all` +* Added additional parameters to `Issues::group` +* Added the ability to authenticate with a job token + +## [11.8.0] - 2022-04-24 + +* Add support for `reviewer_id` and `wip` params in `MergeRequests::all()` +* Add support for `GroupEpics::issues()` +* Add support for `Projects::pipelineJobs()` and protected tags +* Add support for the confidential filter in `Issues:all()` +* Allow specifying params in `Wiki::showAll()` +* Allow specifying params in `SystemHooks::create()` +* Allow `chmod` action and `execute_filemode` attribute +* Implement group merge requests endpoints +* Implement event endpoints + +[11.8.0]: https://github.com/GitLabPHP/Client/compare/11.7.1...11.8.0 + +## [11.7.1] - 2022-04-24 + +* Fixed `GroupsEpic::all()` method +* Fixed `Projects::createPipeline()` method + +[11.7.1]: https://github.com/GitLabPHP/Client/compare/11.7.0...11.7.1 + +## [11.7.0] - 2022-01-24 + +* Dropped PHP 7.2 and 7.3 support + +[11.7.0]: https://github.com/GitLabPHP/Client/compare/11.6.0...11.7.0 + +## [11.6.0] - 2022-01-23 + +* Added support for for workspace repository permissions +* Added support for `psr/cache:^3.0` + +[11.6.0]: https://github.com/GitLabPHP/Client/compare/11.5.1...11.6.0 + +## [11.5.1] - 2022-01-23 + +* Fixed release API paths + +[11.5.1]: https://github.com/GitLabPHP/Client/compare/11.5.0...11.5.1 + +## [11.5.0] - 2021-12-26 + +* Added support for filtering environments by state +* Added support for approval rules endpoints +* Added support for toggling the activate state of users +* Added support for managing packages +* Added support for filtering projects by topics +* Added support for locked merge requests +* Added support for filtering groups and projects by user +* Added support for removing protected branches +* Added support for `psr/cache:^2.0` +* Added support for `symfony/options-resolver:^6.0` +* Added support for PHP 8.1 + +[11.5.0]: https://github.com/GitLabPHP/Client/compare/11.4.1...11.5.0 + +## [11.4.1] - 2021-12-26 + +* Fixed creating environments +* Fixed double encoding of query parameters when comparing commits + +[11.4.1]: https://github.com/GitLabPHP/Client/compare/11.4.0...11.4.1 + +## [11.4.0] - 2021-03-27 + +* Added parameters to the list of project repository tags +* Added support for the epics endpoints +* Added support for project access tokens +* Added support for reverting commits + +[11.4.0]: https://github.com/GitLabPHP/Client/compare/11.3.0...11.4.0 + +## [11.3.0] - 2021-03-14 + +* Added support for disabling and enabling runners +* Added support for a single inherited members +* Added support for tag search + +[11.3.0]: https://github.com/GitLabPHP/Client/compare/11.2.1...11.3.0 + +## [11.2.1] - 2021-03-14 + +* Fixed commit order validation + +[11.2.1]: https://github.com/GitLabPHP/Client/compare/11.2.0...11.2.1 + +## [11.2.0] - 2021-02-20 + +* Added support for user memberships +* Added support for the following projects parameters: id_after, id_before, last_activity_after, last_activity_before, repository_checksum_failed, repository_storage, wiki_checksum_failed, with_custom_attributes, with_programming_language + +[11.2.0]: https://github.com/GitLabPHP/Client/compare/11.1.0...11.2.0 + +## [11.1.0] - 2021-01-25 + +* Added CI schedule variables endpoints +* Added support for triggering a pipeline +* Added support for the search_namespaces projects parameter +* Added support for order_by and sort deployments parameters + +[11.1.0]: https://github.com/GitLabPHP/Client/compare/11.0.0...11.1.0 + +## [11.0.0] - 2020-12-22 + +* Removed models API +* Dropped support for PHP 7.1 +* Updated to latest labels API +* Made builder class final +* Re-worked pagination +* Client authenticate and setUrl now return void +* Added additional return type enforcement + +[11.0.0]: https://github.com/GitLabPHP/Client/compare/10.4.0...11.0.0 + +## [10.4.0] - 2020-12-22 + +[10.4.0]: https://github.com/GitLabPHP/Client/compare/10.3.1...10.4.0 + +* Add min_access_level option to group search +* Added support for additional issue order clauses +* Added params array to remove user method to support hard_delete + +## [10.3.1] - 2020-12-04 + +* Work around GitLab's API returning bad JSON for some endpoints + +[10.3.1]: https://github.com/GitLabPHP/Client/compare/10.3.0...10.3.1 + +## [10.3.0] - 2020-11-27 + +* Support PHP 8.0 + +[10.3.0]: https://github.com/GitLabPHP/Client/compare/10.2.0...10.3.0 + +## [10.2.0] - 2020-11-09 + +* Added variable_type to addVariable and updateVariable +* Added get pipeline bridget jobs method + +[10.2.0]: https://github.com/GitLabPHP/Client/compare/10.1.2...10.2.0 + +## [10.1.2] - 2020-11-09 + +* Fixed comparing repositories + +[10.1.2]: https://github.com/GitLabPHP/Client/compare/10.1.1...10.1.2 + +## [10.1.1] - 2020-10-26 + +* Fixed phpdoc typo +* Fixed broken query builder + +[10.1.1]: https://github.com/GitLabPHP/Client/compare/10.1.0...10.1.1 + +## [10.1.0] - 2020-10-24 + +* Added method to get protected branches for a project +* Added with_merge_status_recheck option for fetching MRs +* Added commit cherry-pick API +* Added support for optional Note parameters +* Deprecated models API + +[10.1.0]: https://github.com/GitLabPHP/Client/compare/10.0.1...10.1.0 + +## [10.0.1] - 2020-10-24 + +* Fixed using the name of a group as an ID +* Fixed various phpdoc issues +* Reverted query builder changes + +[10.0.1]: https://github.com/GitLabPHP/Client/compare/10.0.0...10.0.1 + +## [10.0.0] - 2020-08-15 + +* Added void return types to void methods + +[10.0.0]: https://github.com/GitLabPHP/Client/compare/10.0.0-RC2...10.0.0 + +## [10.0.0-RC2] - 2020-07-23 + +* Restored 9.x behaviour for empty JSON responses +* Support the issue link link_type parameter + +[10.0.0-RC2]: https://github.com/GitLabPHP/Client/compare/10.0.0-RC1...10.0.0-RC2 + +## [10.0.0-RC1] - 2020-07-22 + +* Removed all deprecated functionality +* Switched to PSR-17 and PSR-18 +* Encode URIs according to RFC 3986 +* Send request bodies as JSON to GitLab +* Redesigned pagination +* Added array types where missing +* Added scalar param types +* Added user events API + +[10.0.0-RC1]: https://github.com/GitLabPHP/Client/compare/9.18.1...10.0.0-RC1 + +## [9.18.1] - 2020-07-22 + +* Fixed error in getHeader function +* Fixed incorrect param type doc + +[9.18.1]: https://github.com/GitLabPHP/Client/compare/9.18.0...9.18.1 + +## [9.18.0] - 2020-07-11 + +* Deprecated all APIs that are deprecated or removed as of GitLab 13.1 +* Deprecated old authentication methods and deprecated not specifying an authentication mode +* Deprecated dynamic property access on the client, `Client::api()`, `Client::create()`, and `Client::getResponseHistory()` +* Deprecated passing a stream factory to the Api classes: get it from the client instance instead +* Soft marked various classes as final and/or internal +* Added support for HTTP caching +* Implement removing award emojis +* Implemented notes APIs +* Extended pipeline APIs +* Extended MR approvals APIs +* Add subscribe/unsubscribe methods to issue API +* Add scope and allow all projects to MR API +* Add method to access project discussions +* Update parameters for repository/commits APIs +* Added delete merged branches API function +* Allow to search and find issues by "assignee_id" +* Updated Issues to support updated_after + +[9.18.0]: https://github.com/GitLabPHP/Client/compare/9.17.1...9.18.0 + +## [9.17.1] - 2020-02-17 + +* Fixed text encoding for `Repositories::createCommit()` +* Corrected lots of phpdoc errors and edges cases + +[9.17.1]: https://github.com/GitLabPHP/Client/compare/9.17.0...9.17.1 + +## [9.17.0] - 2020-02-17 + +* Added support for the wiki APIs +* Implemented `Environments::show()` +* Implemented `Issues::showParticipants()` +* Add method to get issues for a group +* Add forks API call to return all forked projects +* Added users projects request parameters normalization + +[9.17.0]: https://github.com/GitLabPHP/Client/compare/9.16.0...9.17.0 diff --git a/LICENSE b/LICENSE index a30bb7fa5..9cebae09c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ MIT License -Copyright (c) 2018 Matt Humphrey +Copyright (c) 2012-2018 Matt Humphrey +Copyright (c) 2018-2025 Graham Campbell Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -9,13 +10,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..a451bbc12 --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ +install: + @docker run -it -w /data -v ${PWD}:/data:delegated -v ~/.composer:/root/.composer:delegated --entrypoint composer --rm registry.gitlab.com/grahamcampbell/php:8.1-base update + @docker run -it -w /data -v ${PWD}:/data:delegated -v ~/.composer:/root/.composer:delegated --entrypoint composer --rm registry.gitlab.com/grahamcampbell/php:8.1-base bin all update + +phpunit: + @docker run -it -w /data -v ${PWD}:/data:delegated --entrypoint vendor/bin/phpunit --rm registry.gitlab.com/grahamcampbell/php:8.1-cli + +phpstan-analyze: + @docker run -it -w /data -v ${PWD}:/data:delegated --entrypoint vendor/bin/phpstan --rm registry.gitlab.com/grahamcampbell/php:8.1-cli analyze + +phpstan-baseline: + @docker run -it -w /data -v ${PWD}:/data:delegated --entrypoint vendor/bin/phpstan --rm registry.gitlab.com/grahamcampbell/php:8.1-cli analyze --generate-baseline + +psalm-analyze: + @docker run -it -w /data -v ${PWD}:/data:delegated --entrypoint vendor/bin/psalm.phar --rm registry.gitlab.com/grahamcampbell/php:8.1-cli + +psalm-baseline: + @docker run -it -w /data -v ${PWD}:/data:delegated --entrypoint vendor/bin/psalm.phar --rm registry.gitlab.com/grahamcampbell/php:8.1-cli --set-baseline=psalm-baseline.xml + +psalm-show-info: + @docker run -it -w /data -v ${PWD}:/data:delegated --entrypoint vendor/bin/psalm.phar --rm registry.gitlab.com/grahamcampbell/php:8.1-cli --show-info=true + +test: phpunit phpstan-analyze psalm-analyze + +clean: + @rm -rf .phpunit.result.cache composer.lock vendor vendor-bin/*/composer.lock vendor-bin/*/vendor diff --git a/README.md b/README.md index 2316423db..35065ecdc 100644 --- a/README.md +++ b/README.md @@ -1,110 +1,118 @@ -A PHP wrapper to be used with [Gitlab's API](https://github.com/gitlabhq/gitlabhq/tree/master/doc/api). -============== +# GitLab PHP API Client -[![Build Status](https://travis-ci.org/m4tthumphrey/php-gitlab-api.svg?branch=master)](https://travis-ci.org/m4tthumphrey/php-gitlab-api) -[![StyleCI](https://styleci.io/repos/6816335/shield?branch=master)](https://styleci.io/repos/6816335) -[![Total Downloads](https://poser.pugx.org/m4tthumphrey/php-gitlab-api/downloads?format=flat-square)](https://packagist.org/packages/m4tthumphrey/php-gitlab-api) -[![Latest Stable Version](https://poser.pugx.org/m4tthumphrey/php-gitlab-api/version?format=flat-square)](https://packagist.org/packages/m4tthumphrey/php-gitlab-api) -[![Latest Unstable Version](https://poser.pugx.org/m4tthumphrey/php-gitlab-api/v/unstable?format=flat-square)](//packagist.org/packages/m4tthumphrey/php-gitlab-api) +We present a modern [GitLab API v4](https://docs.gitlab.com/ce/api/) client for PHP. -Based on [php-github-api](https://github.com/m4tthumphrey/php-github-api) and code from [KnpLabs](https://github.com/KnpLabs/php-github-api). +![Banner](https://user-images.githubusercontent.com/2829600/86969006-fc2e3b00-c164-11ea-80b7-8750160a65c4.png) -Installation ------------- +

+Build Status +StyleCI Status +Software License +Packagist Downloads +Latest Version +

-Via [composer](https://getcomposer.org) +This is strongly based on [php-github-api](https://github.com/KnpLabs/php-github-api) by [KnpLabs](https://github.com/KnpLabs). With this in mind, we now have **very similar** clients for: + +* [Bitbucket](https://bitbucket.org/) - [bitbucket/client](https://packagist.org/packages/bitbucket/client) by [Graham Campbell](https://github.com/GrahamCampbell). +* [GitHub](https://github.com/) - [knplabs/github-api](https://packagist.org/packages/knplabs/github-api) by [KnpLabs](https://github.com/KnpLabs/php-github-api). +* [GitLab](https://gitlab.com/) - [m4tthumphrey/php-gitlab-api](https://packagist.org/packages/m4tthumphrey/php-gitlab-api) which is this package! + +Check out the [change log](CHANGELOG.md), [releases](https://github.com/GitLabPHP/Client/releases), [security policy](https://github.com/GitLabPHP/Client/security/policy), [license](LICENSE), [code of conduct](.github/CODE_OF_CONDUCT.md), and [contribution guidelines](.github/CONTRIBUTING.md). + + +## Installation + +This version supports [PHP](https://php.net) 8.1-8.4. To get started, simply require the project using [Composer](https://getcomposer.org). You will also need to install packages that "provide" [`psr/http-client-implementation`](https://packagist.org/providers/psr/http-client-implementation) and [`psr/http-factory-implementation`](https://packagist.org/providers/psr/http-factory-implementation). + +### Standard Installation ```bash -composer require m4tthumphrey/php-gitlab-api php-http/guzzle6-adapter +$ composer require "m4tthumphrey/php-gitlab-api:^12.0" "guzzlehttp/guzzle:^7.9.2" ``` -Why `php-http/guzzle6-adapter`? We are decoupled from any HTTP messaging client with help by [HTTPlug](http://httplug.io). +### Framework Integration -You can visit [HTTPlug for library users](http://docs.php-http.org/en/latest/httplug/users.html) to get more information about installing HTTPlug related packages. +#### Laravel: -Versioning ----------- +```bash +$ composer require "graham-campbell/gitlab:^8.0" +``` + +#### Symfony: -Depending on your Gitlab server version, you must choose the right version of this library. -Please refer to the following table to pick the right one. +```bash +$ composer require "zeichen32/gitlabapibundle:^7.0" +``` -|Version|Gitlab API Version|Gitlab Version| -|-------|------------------|--------------| -|9.x | V4 | >= 9.0 | -|8.x | V3 | < 9.5 | +We are decoupled from any HTTP messaging client by using [PSR-7](https://www.php-fig.org/psr/psr-7/), [PSR-17](https://www.php-fig.org/psr/psr-17/), [PSR-18](https://www.php-fig.org/psr/psr-18/), and [HTTPlug](https://httplug.io/). You can visit [HTTPlug for library users](https://docs.php-http.org/en/latest/httplug/users.html) to get more information about installing HTTPlug related packages. The framework integration [graham-campbell/gitlab](https://github.com/GrahamCampbell/Laravel-GitLab) is by [Graham Campbell](https://github.com/GrahamCampbell) and [zeichen32/gitlabapibundle](https://github.com/Zeichen32/GitLabApiBundle) is by [Jens Averkamp](https://github.com/Zeichen32). -General API Usage ------------------ +## General API Usage ```php -$client = \Gitlab\Client::create('http://git.yourdomain.com') - ->authenticate('your_gitlab_token_here', \Gitlab\Client::AUTH_URL_TOKEN) -; +// Token authentication +$client = new Gitlab\Client(); +$client->authenticate('your_http_token', Gitlab\Client::AUTH_HTTP_TOKEN); + +// OAuth2 authentication +$client = new Gitlab\Client(); +$client->authenticate('your_oauth_token', Gitlab\Client::AUTH_OAUTH_TOKEN); + +// An example API call +$project = $client->projects()->create('My Project', [ + 'description' => 'This is a project', + 'issues_enabled' => false, +]); +``` + +### Self-Hosted GitLab + +```php +$client = new Gitlab\Client(); +$client->setUrl('https://git.yourdomain.com'); +$client->authenticate('your_http_token', Gitlab\Client::AUTH_HTTP_TOKEN); +``` -$project = $client->api('projects')->create('My Project', array( - 'description' => 'This is a project', - 'issues_enabled' => false -)); +### Example with Pager +```php +$pager = new Gitlab\ResultPager($client); +$issues = $pager->fetchAll($client->issues(), 'all', [null, ['state' => 'closed']]); ``` -Example with Pager ------------------- +### HTTP Client Builder -to fetch all your closed issue with pagination ( on the gitlab api ) +By providing a `Gitlab\HttpClient\Builder` to the `Gitlab\Client` constructor, you can customize the HTTP client. For example, to customize the user agent: ```php -$client = \Gitlab\Client::create('http://git.yourdomain.com') - ->authenticate('your_gitlab_token_here', \Gitlab\Client::AUTH_URL_TOKEN) -; -$pager = new \Gitlab\ResultPager($client); -$issues = $pager->fetchall($client->api('issues'),'all',[null, ['state' => 'closed']]); +$plugin = new Http\Client\Common\Plugin\HeaderSetPlugin([ + 'User-Agent' => 'Foobar', +]); + +$builder = new Gitlab\HttpClient\Builder(); +$builder->addPlugin($plugin); +$client = new Gitlab\Client($builder); ``` +One can read more about HTTPlug plugins [here](https://docs.php-http.org/en/latest/plugins/introduction.html#how-it-works). Take a look around the [API methods](https://github.com/GitLabPHP/Client/tree/12.0/src/Api), and please feel free to report any bugs, noting our [code of conduct](.github/CODE_OF_CONDUCT.md). -Model Usage ------------ +## Contributing -You can also use the library in an object oriented manner: +We will gladly receive issue reports and review and accept pull requests, in accordance with our [code of conduct](.github/CODE_OF_CONDUCT.md) and [contribution guidelines](.github/CONTRIBUTING.md)! -```php -$client = \Gitlab\Client::create('http://git.yourdomain.com') - ->authenticate('your_gitlab_token_here', \Gitlab\Client::AUTH_URL_TOKEN) -; - -# Creating a new project -$project = \Gitlab\Model\Project::create($client, 'My Project', array( - 'description' => 'This is my project', - 'issues_enabled' => false -)); - -$project->addHook('http://mydomain.com/hook/push/1'); - -# Creating a new issue -$project = new \Gitlab\Model\Project(1, $client); -$issue = $project->createIssue('This does not work.', array( - 'description' => 'This doesn\'t work properly. Please fix.', - 'assignee_id' => 2 -)); - -# Closing that issue -$issue->close(); ``` +$ make install +$ make test +``` + -You get the idea! Take a look around ([API methods](https://github.com/m4tthumphrey/php-gitlab-api/tree/master/lib/Gitlab/Api), -[models](https://github.com/m4tthumphrey/php-gitlab-api/tree/master/lib/Gitlab/Model)) and please feel free to report any bugs. +## Security -Framework Integrations ----------------------- -- **Symfony** - https://github.com/Zeichen32/GitLabApiBundle -- **Laravel** - https://github.com/GrahamCampbell/Laravel-GitLab +If you discover a security vulnerability within this package, please send an email to Graham Campbell at hello@gjcampbell.co.uk. All security vulnerabilities will be promptly addressed. You may view our full security policy [here](https://github.com/GitLabPHP/Client/security/policy). -If you have integrated GitLab into a popular PHP framework, let us know! -Contributing ------------- +## License -There are many parts of Gitlab that I have not added to this as it was originally created for personal use, hence the -lack of tests. Feel free to fork and add new functionality and tests, I'll gladly accept decent pull requests. +GitLab PHP API Client is licensed under [The MIT License (MIT)](LICENSE). diff --git a/UPGRADE.md b/UPGRADE.md deleted file mode 100644 index 9062b5c69..000000000 --- a/UPGRADE.md +++ /dev/null @@ -1,90 +0,0 @@ -# UPGRADE FROM 8.0 to 9.0 - -Since 9.0, lib no longer use buzz 0.7+, instead it has an HTTPlug abstraction layer. - -## `Gitlab\Client` changes - -* The constructor no longer allow to specify base url. Use `setUrl` or `Client::create` instead. -* The default url is set to `https://gitlab.com`. -* The `$options` constructor argument have been removed, the `getOption` and `setOption` methods have been removed. -See [documentation](doc/customize.md) to know how to customize the client timeout and how to use a custom user agent. -* The `setBaseUrl` and `getBaseUrl` methods have been removed. Use `setUrl` instead. -* The `clearHeaders` and `setHeaders` methods have been removed. See [documentation](doc/customize.md) to know how use custom headers. -* The `setHttpClient` method have been removed. Use a `Gitlab\HttpClient\Builder` instead. -* The `getHttpClient` method return type is changed to `Http\Client\Common\HttpMethodsClient`. - -## `Gitlab\Api\AbstractApi` changes - -* The `PER_PAGE` class constant have been removed. - -## `Gitlab\Api\DeployKeys` changes - -* The `all` method now take a single argument which is an associative array of query string parameters. -* The `ORDER_BY` and `SORT` class constants have been removed. - -## `Gitlab\Api\Groups` changes - -* The `visibility_level` parameter have been removed from `create` method. Use `visibility` instead. -* The `all` method now take a single argument which is an associative array of query string parameters. -* The `search` method have been removed. Use `all` method instead. -* The `members` method second and subsequent arguments have been replaced by a single associative array of query string parameters. -* The `projects` method second and subsequent arguments have been replaced by a single associative array of query string parameters. - -## `Gitlab\Api\Issues` changes - -* The second argument of `update`, `remove`, `showComments`, `showComment`, `addComment`, `updateComment`, `removeComment`, - `setTimeEstimate`, `resetTimeEstimate`, `addSpentTime` and `resetSpentTime` methods is now a scoped issue id (iid). -* The `all` method second and subsequent arguments have been replaced by a single associative array of query string parameters. - -## `Gitlab\Api\IssueBoards` changes - -* The `all` method second and subsequent arguments have been replaced by a single associative array of query string parameters. - -## `Gitlab\Api\MergeRequests` changes - -* The `getList`, `getByIid`, `merged`, `opened` and `closed` methods have been removed. Use `all` method instead. -* The `ORDER_BY` and `SORT` class constants have been removed. -* The `all` method now take a single argument which is an associative array of query string parameters. -* The `getNotes` method now take only two arguments, the project id and the merge request iid. - -## `Gitlab\Api\Milestones` changes - -* The `all` method second and subsequent arguments have been replaced by a single associative array of query string parameters. - -## `Gitlab\Api\Projects` changes - -* The `keys`, `key`, `addKey`, `removeKey`, `disableKey` and `enableKey` methods have been removed. -Use the `deployKeys`, `deployKey`, `addDeployKey`, `deleteDeployKey`, `removeDeployKey` and `enableDeployKey` instead. -* The `ORDER_BY` and `SORT` class constants have been removed. -* The `accessible`, `owned` and `search` methods have been removed. Use `all` method instead. -* The `all` method now take a single argument which is an associative array of query string parameters. -* The `trace` method have been removed. Use `Gitlab\Api\Jobs::trace` instead. -* The `builds` method have been removed. Use `Gitlab\Api\Jobs::all` instead. -* The `build` method have been removed. Use `Gitlab\Api\Jobs::show` instead. -* The `events` method second and subsequent arguments have been replaced by a single associative array of query string parameters. -* The `deployments` method second and subsequent arguments have been replaced by a single associative array of query string parameters. - -## `Gitlab\Api\ProjectNamespaces` changes - -* The `search` method have been removed. Use `all` method instead. -* The `all` method now take a single argument which is an associative array of query string parameters. - -## `Gitlab\Api\Repositories` changes - -* The `commitBuilds` method have been removed. Use `Gitlab\Api\Projects::pipelines` instead. -* The `commits` method second and subsequent arguments have been replaced by a single associative array of query string parameters. -* The `commitComments` method third and subsequent arguments have been replaced by a single associative array of query string parameters. - -## `Gitlab\Model\Project` changes - -* The `keys`, `key`, `addKey`, `removeKey`, `disableKey` and `enableKey` methods have been removed. -Use the `deployKeys`, `deployKey`, `addDeployKey`, `deleteDeployKey`, `removeDeployKey` and `enableDeployKey` instead. - -## `Gitlab\Model\Snippet` changes - -* The `expires_at` property have been removed.` - -## `Gitlab\Model\Users` changes - -* The `all` method now take a single argument which is an associative array of query string parameters. -* The `lookup` and `search` methods have been removed. Use `all` method instead. diff --git a/composer.json b/composer.json index 1075af28e..487fb319b 100644 --- a/composer.json +++ b/composer.json @@ -1,50 +1,70 @@ { - "name": "m4tthumphrey/php-gitlab-api", - "type": "library", - "description": "GitLab API client", - "homepage": "https://github.com/m4tthumphrey/php-gitlab-api", - "keywords": ["gitlab", "api"], - "license": "MIT", - "authors": [ - { - "name": "Matt Humphrey", - "homepage": "http://m4tt.io" - }, - { - "name": "KnpLabs Team", - "homepage": "http://knplabs.com" - }, - { - "name": "Thibault Duplessis", - "email": "thibault.duplessis@gmail.com", - "homepage": "http://ornicar.github.com" - } - ], - "require": { - "php": "^5.6 || ^7.0", - "ext-xml": "*", - "php-http/client-common": "^1.6", - "php-http/client-implementation": "^1.0", - "php-http/discovery": "^1.2", - "php-http/httplug": "^1.1", - "php-http/multipart-stream-builder": "^1.0", - "symfony/options-resolver": "^2.6 || ^3.0 || ^4.0" - }, - "require-dev": { - "guzzlehttp/psr7": "^1.2", - "php-http/guzzle6-adapter": "^1.0", - "php-http/mock-client": "^1.0", - "phpunit/phpunit": "^5.0" - }, - "autoload": { - "psr-4": { "Gitlab\\": "lib/Gitlab/" } - }, - "autoload-dev": { - "psr-4": { "Gitlab\\Tests\\": "test/Gitlab/Tests/" } - }, - "extra": { - "branch-alias": { - "dev-master": "9.8.x-dev" - } - } + "name": "m4tthumphrey/php-gitlab-api", + "description": "GitLab API v4 client for PHP", + "keywords": ["gitlab", "api"], + "license": "MIT", + "authors": [ + { + "name": "Fabien Bourigault", + "email": "bourigaultfabien@gmail.com", + "homepage": "https://github.com/fbourigault" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Matt Humphrey", + "email": "matth@windsor-telecom.co.uk", + "homepage": "https://github.com/m4tthumphrey" + }, + { + "name": "Miguel Piedrafita", + "email": "github@miguelpiedrafita.com", + "homepage": "https://github.com/m1guelpf" + } + ], + "require": { + "php": "^8.1", + "ext-json": "*", + "ext-xml": "*", + "php-http/cache-plugin": "^2.0.1", + "php-http/client-common": "^2.7.2", + "php-http/discovery": "^1.20.0", + "php-http/httplug": "^2.4.1", + "php-http/multipart-stream-builder": "^1.4.2", + "psr/cache": "^2.0 || ^3.0", + "psr/http-client-implementation": "^1.0", + "psr/http-factory-implementation": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "symfony/options-resolver": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "guzzlehttp/guzzle": "^7.9.2" + }, + "autoload": { + "psr-4": { + "Gitlab\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Gitlab\\Tests\\": "tests/" + } + }, + "config": { + "preferred-install": "dist", + "allow-plugins": { + "bamarni/composer-bin-plugin": true, + "php-http/discovery": true + } + }, + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + } } diff --git a/doc/customize.md b/doc/customize.md deleted file mode 100644 index 6cb130858..000000000 --- a/doc/customize.md +++ /dev/null @@ -1,28 +0,0 @@ -## Customize `php-gitlab-api` - -### How to set custom headers (including `User-Agent`)? - -By providing a `Gitlab\HttpClient\Builder` to the `Gitlab\Client` constructor, you can customize the HTTP client. - -```php -$plugin = new Http\Client\Common\Plugin\HeaderSetPlugin([ - 'User-Agent' => 'Foobar', -]); - -$builder = new Gitlab\HttpClient\Builder(); -$builder->addPlugin($plugin); - -$client = new Gitlab\Client($builder); -``` -Read more about [HTTPlug plugins here](http://docs.php-http.org/en/latest/plugins/introduction.html#how-it-works). - -### How to customize the HTTP client timeout? -As timeout configuration is not compatible with HTTP client abstraction, you have to create the `Gitlab\Client` with -an explicit HTTP client implementation. - -```php -$httpClient = Http\Adapter\Guzzle6::createWithConfig([ - 'timeout' => 1.0 -]); -$client = Gitlab\Client::createWithHttpClient($httpClient); -``` diff --git a/lib/Gitlab/Api/AbstractApi.php b/lib/Gitlab/Api/AbstractApi.php deleted file mode 100644 index ef9120a20..000000000 --- a/lib/Gitlab/Api/AbstractApi.php +++ /dev/null @@ -1,223 +0,0 @@ - - * @author Matt Humphrey - * @author Radu Topala - */ -abstract class AbstractApi implements ApiInterface -{ - /** - * The client - * - * @var Client - */ - protected $client; - - /** - * @var StreamFactory - */ - private $streamFactory; - - /** - * @param Client $client - * @param StreamFactory|null $streamFactory - */ - public function __construct(Client $client, StreamFactory $streamFactory = null) - { - $this->client = $client; - $this->streamFactory = $streamFactory ?: StreamFactoryDiscovery::find(); - } - - /** - * @return $this - * @codeCoverageIgnore - */ - public function configure() - { - return $this; - } - - /** - * Performs a GET query and returns the response as a PSR-7 response object. - * - * @param string $path - * @param array $parameters - * @param array $requestHeaders - * @return ResponseInterface - */ - protected function getAsResponse($path, array $parameters = array(), $requestHeaders = array()) - { - $path = $this->preparePath($path, $parameters); - - return $this->client->getHttpClient()->get($path, $requestHeaders); - } - - /** - * @param string $path - * @param array $parameters - * @param array $requestHeaders - * @return mixed - */ - protected function get($path, array $parameters = array(), $requestHeaders = array()) - { - return ResponseMediator::getContent($this->getAsResponse($path, $parameters, $requestHeaders)); - } - - /** - * @param string $path - * @param array $parameters - * @param array $requestHeaders - * @param array $files - * @return mixed - */ - protected function post($path, array $parameters = array(), $requestHeaders = array(), array $files = array()) - { - $path = $this->preparePath($path); - - $body = null; - if (empty($files) && !empty($parameters)) { - $body = $this->streamFactory->createStream(QueryStringBuilder::build($parameters)); - $requestHeaders['Content-Type'] = 'application/x-www-form-urlencoded'; - } elseif (!empty($files)) { - $builder = new MultipartStreamBuilder($this->streamFactory); - - foreach ($parameters as $name => $value) { - $builder->addResource($name, $value); - } - - foreach ($files as $name => $file) { - $builder->addResource($name, fopen($file, 'r'), [ - 'headers' => [ - 'Content-Type' => $this->guessContentType($file), - ], - 'filename' => basename($file), - ]); - } - - $body = $builder->build(); - $requestHeaders['Content-Type'] = 'multipart/form-data; boundary='.$builder->getBoundary(); - } - - $response = $this->client->getHttpClient()->post($path, $requestHeaders, $body); - - return ResponseMediator::getContent($response); - } - - /** - * @param string $path - * @param array $parameters - * @param array $requestHeaders - * @return mixed - */ - protected function put($path, array $parameters = array(), $requestHeaders = array()) - { - $path = $this->preparePath($path); - - $body = null; - if (!empty($parameters)) { - $body = $this->streamFactory->createStream(http_build_query($parameters)); - $requestHeaders['Content-Type'] = 'application/x-www-form-urlencoded'; - } - - $response = $this->client->getHttpClient()->put($path, $requestHeaders, $body); - - return ResponseMediator::getContent($response); - } - - /** - * @param string $path - * @param array $parameters - * @param array $requestHeaders - * @return mixed - */ - protected function delete($path, array $parameters = array(), $requestHeaders = array()) - { - $path = $this->preparePath($path, $parameters); - - $response = $this->client->getHttpClient()->delete($path, $requestHeaders); - - return ResponseMediator::getContent($response); - } - - /** - * @param int $id - * @param string $path - * @return string - */ - protected function getProjectPath($id, $path) - { - return 'projects/'.$this->encodePath($id).'/'.$path; - } - - /** - * @param string $path - * @return string - */ - protected function encodePath($path) - { - $path = rawurlencode($path); - - return str_replace('.', '%2E', $path); - } - - /** - * Create a new OptionsResolver with page and per_page options. - * - * @return OptionsResolver - */ - protected function createOptionsResolver() - { - $resolver = new OptionsResolver(); - $resolver->setDefined('page') - ->setAllowedTypes('page', 'int') - ->setAllowedValues('page', function ($value) { - return $value > 0; - }) - ; - $resolver->setDefined('per_page') - ->setAllowedTypes('per_page', 'int') - ->setAllowedValues('per_page', function ($value) { - return $value > 0 && $value <= 100; - }) - ; - - return $resolver; - } - - private function preparePath($path, array $parameters = []) - { - if (count($parameters) > 0) { - $path .= '?'.QueryStringBuilder::build($parameters); - } - - return $path; - } - - /** - * @param $file - * - * @return string - */ - private function guessContentType($file) - { - if (!class_exists(\finfo::class, false)) { - return 'application/octet-stream'; - } - $finfo = new \finfo(FILEINFO_MIME_TYPE); - - return $finfo->file($file); - } -} diff --git a/lib/Gitlab/Api/ApiInterface.php b/lib/Gitlab/Api/ApiInterface.php deleted file mode 100644 index 40c3a5659..000000000 --- a/lib/Gitlab/Api/ApiInterface.php +++ /dev/null @@ -1,11 +0,0 @@ -createOptionsResolver(); - - return $this->get('deploy_keys', $resolver->resolve($parameters)); - } -} diff --git a/lib/Gitlab/Api/Deployments.php b/lib/Gitlab/Api/Deployments.php deleted file mode 100644 index f085e20f2..000000000 --- a/lib/Gitlab/Api/Deployments.php +++ /dev/null @@ -1,25 +0,0 @@ -createOptionsResolver(); - return $this->get($this->getProjectPath($project_id, 'deployments'), $resolver->resolve($parameters)); - } - - /** - * @param int $project_id - * @param string $deployment_id - * @return mixed - */ - public function show($project_id, $deployment_id) - { - return $this->get($this->getProjectPath($project_id, 'deployments/' . $deployment_id)); - } -} diff --git a/lib/Gitlab/Api/Environments.php b/lib/Gitlab/Api/Environments.php deleted file mode 100644 index 48270a633..000000000 --- a/lib/Gitlab/Api/Environments.php +++ /dev/null @@ -1,48 +0,0 @@ -createOptionsResolver(); - return $this->get($this->getProjectPath($project_id, 'environments'), $resolver->resolve($parameters)); - } - - /** - * @param int $project_id - * @param array $parameters ( - * - * @var string $name The name of the environment - * @var string $external_url Place to link to for this environment - * ) - * @return mixed - */ - public function create($project_id, array $parameters = array()) - { - $resolver = new OptionsResolver(); - $resolver->setDefined('name') - ->setRequired('name') - ->setAllowedTypes('name', 'string'); - $resolver->setDefined('external_url') - ->setAllowedTypes('external_url', 'string'); - - return $this->post($this->getProjectPath($project_id, 'environment'), $resolver->resolve($parameters)); - } - - /** - * @param int $project_id - * @param string $environment_id - * @return mixed - */ - public function remove($project_id, $environment_id) - { - return $this->delete($this->getProjectPath($project_id, 'environments/' . $environment_id)); - } -} diff --git a/lib/Gitlab/Api/Groups.php b/lib/Gitlab/Api/Groups.php deleted file mode 100644 index 03807e6c0..000000000 --- a/lib/Gitlab/Api/Groups.php +++ /dev/null @@ -1,218 +0,0 @@ -createOptionsResolver(); - $booleanNormalizer = function (Options $resolver, $value) { - return $value ? 'true' : 'false'; - }; - - $resolver->setDefined('skip_groups') - ->setAllowedTypes('skip_groups', 'array') - ->setAllowedValues('skip_groups', function (array $value) { - return count($value) == count(array_filter($value, 'is_int')); - }) - ; - $resolver->setDefined('all_available') - ->setAllowedTypes('all_available', 'bool') - ->setNormalizer('all_available', $booleanNormalizer) - ; - $resolver->setDefined('search'); - $resolver->setDefined('order_by') - ->setAllowedValues('order_by', ['name', 'path']) - ; - $resolver->setDefined('sort') - ->setAllowedValues('sort', ['asc', 'desc']) - ; - $resolver->setDefined('statistics') - ->setAllowedTypes('statistics', 'bool') - ->setNormalizer('statistics', $booleanNormalizer) - ; - $resolver->setDefined('owned') - ->setAllowedTypes('owned', 'bool') - ->setNormalizer('owned', $booleanNormalizer) - ; - - return $this->get('groups', $resolver->resolve($parameters)); - } - - /** - * @param int $id - * @return mixed - */ - public function show($id) - { - return $this->get('groups/'.$this->encodePath($id)); - } - - /** - * @param string $name - * @param string $path - * @param string $description - * @param string $visibility - * @return mixed - */ - public function create($name, $path, $description = null, $visibility = 'private') - { - return $this->post('groups', array( - 'name' => $name, - 'path' => $path, - 'description' => $description, - 'visibility' => $visibility, - )); - } - - /** - * @param int $id - * @param array $params - * @return mixed - */ - public function update($id, array $params) - { - return $this->put('groups/'.$this->encodePath($id), $params); - } - - /** - * @param int $group_id - * @return mixed - */ - public function remove($group_id) - { - return $this->delete('groups/'.$this->encodePath($group_id)); - } - - /** - * @param int $group_id - * @param int $project_id - * @return mixed - */ - public function transfer($group_id, $project_id) - { - return $this->post('groups/'.$this->encodePath($group_id).'/projects/'.$this->encodePath($project_id)); - } - - /** - * @param int $id - * @param array $parameters ( - * - * @var string $query A query string to search for members. - * ) - * - * @return mixed - */ - public function members($id, array $parameters = []) - { - $resolver = $this->createOptionsResolver(); - $resolver->setDefined('query'); - - return $this->get('groups/'.$this->encodePath($id).'/members', $resolver->resolve($parameters)); - } - - /** - * @param int $group_id - * @param int $user_id - * @param int $access_level - * @return mixed - */ - public function addMember($group_id, $user_id, $access_level) - { - return $this->post('groups/'.$this->encodePath($group_id).'/members', array( - 'user_id' => $user_id, - 'access_level' => $access_level - )); - } - - /** - * @param int $group_id - * @param int $user_id - * @param int $access_level - * @return mixed - */ - public function saveMember($group_id, $user_id, $access_level) - { - return $this->put('groups/'.$this->encodePath($group_id).'/members/'.$this->encodePath($user_id), array( - 'access_level' => $access_level - )); - } - - /** - * @param int $group_id - * @param int $user_id - * @return mixed - */ - public function removeMember($group_id, $user_id) - { - return $this->delete('groups/'.$this->encodePath($group_id).'/members/'.$this->encodePath($user_id)); - } - - /** - * @param $id - * @param array $parameters ( - * - * @var bool $archived Limit by archived status. - * @var string $visibility Limit by visibility public, internal, or private. - * @var string $order_by Return projects ordered by id, name, path, created_at, updated_at, or last_activity_at fields. - * Default is created_at. - * @var string $sort Return projects sorted in asc or desc order. Default is desc. - * @var string $search Return list of authorized projects matching the search criteria. - * @var bool $simple Return only the ID, URL, name, and path of each project. - * @var bool $owned Limit by projects owned by the current user. - * @var bool $starred Limit by projects starred by the current user. - * ) - * - * @return mixed - */ - public function projects($id, array $parameters = []) - { - $resolver = $this->createOptionsResolver(); - $booleanNormalizer = function (Options $resolver, $value) { - return $value ? 'true' : 'false'; - }; - - $resolver->setDefined('archived') - ->setAllowedTypes('archived', 'bool') - ->setNormalizer('archived', $booleanNormalizer) - ; - $resolver->setDefined('visibility') - ->setAllowedValues('visibility', ['public', 'internal', 'private']) - ; - $resolver->setDefined('order_by') - ->setAllowedValues('order_by', ['id', 'name', 'path', 'created_at', 'updated_at', 'last_activity_at']) - ; - $resolver->setDefined('sort') - ->setAllowedValues('sort', ['asc', 'desc']) - ; - $resolver->setDefined('search'); - $resolver->setDefined('simple') - ->setAllowedTypes('simple', 'bool') - ->setNormalizer('simple', $booleanNormalizer) - ; - $resolver->setDefined('owned') - ->setAllowedTypes('owned', 'bool') - ->setNormalizer('owned', $booleanNormalizer) - ; - $resolver->setDefined('starred') - ->setAllowedTypes('starred', 'bool') - ->setNormalizer('starred', $booleanNormalizer) - ; - - return $this->get('groups/'.$this->encodePath($id).'/projects', $resolver->resolve($parameters)); - } -} diff --git a/lib/Gitlab/Api/IssueBoards.php b/lib/Gitlab/Api/IssueBoards.php deleted file mode 100644 index e5d460bc5..000000000 --- a/lib/Gitlab/Api/IssueBoards.php +++ /dev/null @@ -1,88 +0,0 @@ -createOptionsResolver(); - - $path = $project_id === null ? 'boards' : $this->getProjectPath($project_id, 'boards'); - - return $this->get($path, $resolver->resolve($parameters)); - } - - /** - * @param int $project_id - * @param int $board_id - * @return mixed - */ - public function allLists($project_id, $board_id) - { - return $this->get($this->getProjectPath($project_id, 'boards/'.$this->encodePath($board_id).'/lists')); - } - - - /** - * @param int $project_id - * @param int $board_id - * @param int $list_id - * @return mixed - */ - public function showList($project_id, $board_id, $list_id) - { - return $this->get($this->getProjectPath($project_id, 'boards/'.$this->encodePath($board_id).'/lists'.$this->encodePath($list_id))); - } - - /** - * @param int $project_id - * @param int $board_id - * @param int $label_id - * @return mixed - */ - public function createList($project_id, $board_id, $label_id) - { - $params = array( - 'id' => $project_id, - 'board_id' => $board_id, - 'label_id' => $label_id - ); - - return $this->get($this->getProjectPath($project_id, 'boards/'.$this->encodePath($board_id).'/lists'), $params); - } - - /** - * @param int $project_id - * @param int $board_id - * @param int $list_id - * @param int $position - * @return mixed - */ - public function updateList($project_id, $board_id, $list_id, $position) - { - $params = array( - 'id' => $project_id, - 'board_id' => $board_id, - 'list_id' => $list_id, - 'position' => $position - ); - - return $this->put($this->getProjectPath($project_id, 'boards/'.$this->encodePath($board_id).'/lists/'.$this->encodePath($list_id)), $params); - } - - /** - * @param int $project_id - * @param int $board_id - * @param int $list_id - * @return mixed - */ - public function deleteList($project_id, $board_id, $list_id) - { - return $this->delete($this->getProjectPath($project_id, 'boards/'.$this->encodePath($board_id).'/lists/'.$this->encodePath($list_id))); - } -} diff --git a/lib/Gitlab/Api/Issues.php b/lib/Gitlab/Api/Issues.php deleted file mode 100644 index 9fa0f74da..000000000 --- a/lib/Gitlab/Api/Issues.php +++ /dev/null @@ -1,226 +0,0 @@ -createOptionsResolver(); - - $resolver->setDefined('state') - ->setAllowedValues('state', ['opened', 'closed']) - ; - $resolver->setDefined('labels'); - $resolver->setDefined('milestone'); - $resolver->setDefined('iids') - ->setAllowedTypes('iids', 'array') - ->setAllowedValues('iids', function (array $value) { - return count($value) == count(array_filter($value, 'is_int')); - }) - ; - $resolver->setDefined('scope') - ->setAllowedValues('scope', ['created-by-me', 'assigned-to-me', 'all']) - ; - $resolver->setDefined('order_by') - ->setAllowedValues('order_by', ['created_at', 'updated_at']) - ; - $resolver->setDefined('sort') - ->setAllowedValues('sort', ['asc', 'desc']) - ; - $resolver->setDefined('search'); - - $path = $project_id === null ? 'issues' : $this->getProjectPath($project_id, 'issues'); - - return $this->get($path, $resolver->resolve($parameters)); - } - - /** - * @param int $project_id - * @param int $issue_iid - * @return mixed - */ - public function show($project_id, $issue_iid) - { - return $this->get($this->getProjectPath($project_id, 'issues/'.$this->encodePath($issue_iid))); - } - - /** - * @param int $project_id - * @param array $params - * @return mixed - */ - public function create($project_id, array $params) - { - return $this->post($this->getProjectPath($project_id, 'issues'), $params); - } - - /** - * @param int $project_id - * @param int $issue_iid - * @param array $params - * @return mixed - */ - public function update($project_id, $issue_iid, array $params) - { - return $this->put($this->getProjectPath($project_id, 'issues/'.$this->encodePath($issue_iid)), $params); - } - - /** - * @param int $project_id - * @param int $issue_iid - * @return mixed - */ - public function remove($project_id, $issue_iid) - { - return $this->delete($this->getProjectPath($project_id, 'issues/'.$this->encodePath($issue_iid))); - } - - /** - * @param int $project_id - * @param int $issue_iid - * @return mixed - */ - public function showComments($project_id, $issue_iid) - { - return $this->get($this->getProjectPath($project_id, 'issues/'.$this->encodePath($issue_iid)).'/notes'); - } - - /** - * @param int $project_id - * @param int $issue_iid - * @param int $note_id - * @return mixed - */ - public function showComment($project_id, $issue_iid, $note_id) - { - return $this->get($this->getProjectPath($project_id, 'issues/'.$this->encodePath($issue_iid)).'/notes/'.$this->encodePath($note_id)); - } - - /** - * @param int $project_id - * @param int $issue_iid - * @param string|array $body - * @return mixed - */ - public function addComment($project_id, $issue_iid, $body) - { - // backwards compatibility - if (is_array($body)) { - $params = $body; - } else { - $params = array('body' => $body); - } - - return $this->post($this->getProjectPath($project_id, 'issues/'.$this->encodePath($issue_iid).'/notes'), $params); - } - - /** - * @param int $project_id - * @param int $issue_iid - * @param int $note_id - * @param string $body - * @return mixed - */ - public function updateComment($project_id, $issue_iid, $note_id, $body) - { - return $this->put($this->getProjectPath($project_id, 'issues/'.$this->encodePath($issue_iid).'/notes/'.$this->encodePath($note_id)), array( - 'body' => $body - )); - } - - /** - * @param int $project_id - * @param int $issue_iid - * @param int $note_id - * @return mixed - */ - public function removeComment($project_id, $issue_iid, $note_id) - { - return $this->delete($this->getProjectPath($project_id, 'issues/'.$this->encodePath($issue_iid).'/notes/'.$this->encodePath($note_id))); - } - - /** - * @param int $project_id - * @param int $issue_iid - * @param string $duration - */ - public function setTimeEstimate($project_id, $issue_iid, $duration) - { - return $this->post($this->getProjectPath($project_id, 'issues/'.$this->encodePath($issue_iid).'/time_estimate'), array('duration' => $duration)); - } - - /** - * @param int $project_id - * @param int $issue_iid - */ - public function resetTimeEstimate($project_id, $issue_iid) - { - return $this->post($this->getProjectPath($project_id, 'issues/'.$this->encodePath($issue_iid).'/reset_time_estimate')); - } - - /** - * @param int $project_id - * @param int $issue_iid - * @param string $duration - */ - public function addSpentTime($project_id, $issue_iid, $duration) - { - return $this->post($this->getProjectPath($project_id, 'issues/'.$this->encodePath($issue_iid).'/add_spent_time'), array('duration' => $duration)); - } - - /** - * @param int $project_id - * @param int $issue_iid - */ - public function resetSpentTime($project_id, $issue_iid) - { - return $this->post($this->getProjectPath($project_id, 'issues/'.$this->encodePath($issue_iid).'/reset_spent_time')); - } - - /** - * @param int $project_id - * @param int $issue_iid - * @return mixed - */ - public function getTimeStats($project_id, $issue_iid) - { - return $this->get($this->getProjectPath($project_id, 'issues/'.$this->encodePath($issue_iid) .'/time_stats')); - } - - /** - * @param int $project_id - * @param int $issue_iid - * - * @return mixed - */ - public function awardEmoji($project_id, $issue_iid) - { - return $this->get($this->getProjectPath($project_id, 'issues/'.$this->encodePath($issue_iid).'/award_emoji')); - } - - /** - * @param int $project_id - * @param int $issue_iid - * @return mixed - */ - public function closedByMergeRequests($project_id, $issue_iid) - { - return $this->get($this->getProjectPath($project_id, 'issues/'.$this->encodePath($issue_iid)).'/closed_by'); - } -} diff --git a/lib/Gitlab/Api/Jobs.php b/lib/Gitlab/Api/Jobs.php deleted file mode 100644 index 2bec56796..000000000 --- a/lib/Gitlab/Api/Jobs.php +++ /dev/null @@ -1,178 +0,0 @@ -createOptionsResolver(); - - return $this->get("projects/".$this->encodePath($project_id)."/jobs", $resolver->resolve($parameters)); - } - - /** - * @param int|string $project_id - * @param int $pipeline_id - * @param array $parameters ( - * - * @var string|string[] $scope The scope of jobs to show, one or array of: created, pending, running, failed, - * success, canceled, skipped, manual; showing all jobs if none provided. - * ) - * - * @return mixed - */ - public function pipelineJobs($project_id, $pipeline_id, array $parameters = []) - { - $resolver = $this->createOptionsResolver(); - - return $this->get( - $this->getProjectPath($project_id, 'pipelines/').$this->encodePath($pipeline_id)."/jobs", - $resolver->resolve($parameters) - ); - } - - /** - * @param int|string $project_id - * @param int $job_id - * @return mixed - */ - public function show($project_id, $job_id) - { - return $this->get("projects/".$this->encodePath($project_id)."/jobs/".$this->encodePath($job_id)); - } - - /** - * @param int|string $project_id - * @param int $job_id - * @return StreamInterface - */ - public function artifacts($project_id, $job_id) - { - return $this->getAsResponse("projects/".$this->encodePath($project_id)."/jobs/".$this->encodePath($job_id)."/artifacts")->getBody(); - } - - /** - * @param int|string $project_id - * @param string $ref_name - * @param string $job_name - * @return StreamInterface - */ - public function artifactsByRefName($project_id, $ref_name, $job_name) - { - return $this->getAsResponse("projects/".$this->encodePath($project_id)."/jobs/artifacts/".$this->encodePath($ref_name)."/download", array( - 'job' => $job_name - ))->getBody(); - } - - /** - * @param int|string $project_id - * @param int $job_id - * @return string - */ - public function trace($project_id, $job_id) - { - return $this->get("projects/".$this->encodePath($project_id)."/jobs/".$this->encodePath($job_id)."/trace"); - } - - /** - * @param int|string $project_id - * @param int $job_id - * @return mixed - */ - public function cancel($project_id, $job_id) - { - return $this->post("projects/".$this->encodePath($project_id)."/jobs/".$this->encodePath($job_id)."/cancel"); - } - - /** - * @param int|string $project_id - * @param int $job_id - * @return mixed - */ - public function retry($project_id, $job_id) - { - return $this->post("projects/".$this->encodePath($project_id)."/jobs/".$this->encodePath($job_id)."/retry"); - } - - /** - * @param int|string $project_id - * @param int $job_id - * @return mixed - */ - public function erase($project_id, $job_id) - { - return $this->post("projects/".$this->encodePath($project_id)."/jobs/".$this->encodePath($job_id)."/erase"); - } - - /** - * @param int|string $project_id - * @param int $job_id - * @return mixed - */ - public function keepArtifacts($project_id, $job_id) - { - return $this->post("projects/".$this->encodePath($project_id)."/jobs/".$this->encodePath($job_id)."/artifacts/keep"); - } - - /** - * @param int|string $project_id - * @param int $job_id - * @return mixed - */ - public function play($project_id, $job_id) - { - return $this->post("projects/".$this->encodePath($project_id)."/jobs/".$this->encodePath($job_id)."/play"); - } - - /** - * {@inheritdoc} - */ - protected function createOptionsResolver() - { - $allowedScopeValues = [ - self::SCOPE_CANCELED, - self::SCOPE_CREATED, - self::SCOPE_FAILED, - self::SCOPE_MANUAL, - self::SCOPE_PENDING, - self::SCOPE_RUNNING, - self::SCOPE_SKIPPED, - self::SCOPE_SUCCESS, - ]; - - $resolver = parent::createOptionsResolver(); - $resolver->setDefined('scope') - ->setAllowedTypes('scope', ['string', 'array']) - ->setAllowedValues('scope', $allowedScopeValues) - ->addAllowedValues('scope', function ($value) use ($allowedScopeValues) { - return is_array($value) && empty(array_diff($value, $allowedScopeValues)); - }) - ->setNormalizer('scope', function (OptionsResolver $resolver, $value) { - return (array) $value; - }) - ; - - return $resolver; - } -} diff --git a/lib/Gitlab/Api/Keys.php b/lib/Gitlab/Api/Keys.php deleted file mode 100644 index 162c33855..000000000 --- a/lib/Gitlab/Api/Keys.php +++ /dev/null @@ -1,13 +0,0 @@ -get('keys/'.$this->encodePath($id)); - } -} diff --git a/lib/Gitlab/Api/MergeRequests.php b/lib/Gitlab/Api/MergeRequests.php deleted file mode 100644 index d7b4ebb2e..000000000 --- a/lib/Gitlab/Api/MergeRequests.php +++ /dev/null @@ -1,258 +0,0 @@ -createOptionsResolver(); - $datetimeNormalizer = function (Options $resolver, \DateTimeInterface $value) { - return $value->format('c'); - }; - $resolver->setDefined('iids') - ->setAllowedTypes('iids', 'array') - ->setAllowedValues('iids', function (array $value) { - return count($value) == count(array_filter($value, 'is_int')); - }) - ; - $resolver->setDefined('state') - ->setAllowedValues('state', ['all', 'opened', 'merged', 'closed']) - ; - $resolver->setDefined('order_by') - ->setAllowedValues('order_by', ['created_at', 'updated_at']) - ; - $resolver->setDefined('sort') - ->setAllowedValues('sort', ['asc', 'desc']) - ; - $resolver->setDefined('milestone'); - $resolver->setDefined('view') - ->setAllowedValues('view', ['simple']) - ; - $resolver->setDefined('labels'); - $resolver->setDefined('created_after') - ->setAllowedTypes('created_after', \DateTimeInterface::class) - ->setNormalizer('created_after', $datetimeNormalizer) - ; - $resolver->setDefined('created_before') - ->setAllowedTypes('created_before', \DateTimeInterface::class) - ->setNormalizer('created_before', $datetimeNormalizer) - ; - $resolver->setDefined('search'); - - return $this->get($this->getProjectPath($project_id, 'merge_requests'), $resolver->resolve($parameters)); - } - - /** - * @param int $project_id - * @param int $mr_id - * @return mixed - */ - public function show($project_id, $mr_id) - { - return $this->get($this->getProjectPath($project_id, 'merge_requests/'.$this->encodePath($mr_id))); - } - - /** - * @param int $project_id - * @param string $source - * @param string $target - * @param string $title - * @param int $assignee - * @param int $target_project_id - * @param string $description - * @return mixed - */ - public function create($project_id, $source, $target, $title, $assignee = null, $target_project_id = null, $description = null) - { - return $this->post($this->getProjectPath($project_id, 'merge_requests'), array( - 'source_branch' => $source, - 'target_branch' => $target, - 'title' => $title, - 'assignee_id' => $assignee, - 'target_project_id' => $target_project_id, - 'description' => $description - )); - } - - /** - * @param int $project_id - * @param int $mr_id - * @param array $params - * @return mixed - */ - public function update($project_id, $mr_id, array $params) - { - return $this->put($this->getProjectPath($project_id, 'merge_requests/'.$this->encodePath($mr_id)), $params); - } - - /** - * @param int $project_id - * @param int $mr_id - * @param string $message - * @return mixed - */ - public function merge($project_id, $mr_id, $message = null) - { - if (is_array($message)) { - $params = $message; - } else { - $params = array('merge_commit_message' => $message); - } - - return $this->put($this->getProjectPath($project_id, 'merge_requests/'.$this->encodePath($mr_id).'/merge'), $params); - } - - /** - * @param int $project_id - * @param int $mr_id - * - * @return mixed - */ - public function showNotes($project_id, $mr_id) - { - return $this->get($this->getProjectPath($project_id, 'merge_requests/'.$this->encodePath($mr_id).'/notes')); - } - - /** - * @param int $project_id - * @param int $mr_id - * @param string $note - * @return mixed - */ - public function addNote($project_id, $mr_id, $note) - { - return $this->post($this->getProjectPath($project_id, 'merge_requests/'.$this->encodePath($mr_id).'/notes'), array( - 'body' => $note - )); - } - - /** - * @param int $project_id - * @param int $mr_id - * @return mixed - */ - public function showComments($project_id, $mr_id) - { - @trigger_error(sprintf('The %s() method is deprecated since version 9.1 and will be removed in 10.0. Use the showNotes() method instead.', __METHOD__), E_USER_DEPRECATED); - - return $this->showNotes($project_id, $mr_id); - } - - /** - * @param int $project_id - * @param int $mr_id - * @param string $note - * @return mixed - */ - public function addComment($project_id, $mr_id, $note) - { - @trigger_error(sprintf('The %s() method is deprecated since version 9.1 and will be removed in 10.0. Use the addNote() method instead.', __METHOD__), E_USER_DEPRECATED); - - return $this->addNote($project_id, $mr_id, $note); - } - - /** - * @param int $project_id - * @param int $mr_id - * @return mixed - */ - public function changes($project_id, $mr_id) - { - return $this->get($this->getProjectPath($project_id, 'merge_requests/'.$this->encodePath($mr_id).'/changes')); - } - - /** - * @param int $project_id - * @param int $mr_id - * @return mixed - */ - public function commits($project_id, $mr_id) - { - return $this->get($this->getProjectPath($project_id, 'merge_requests/'.$this->encodePath($mr_id).'/commits')); - } - - /** - * @param int $project_id - * @param int $mr_id - * @return mixed - */ - public function closesIssues($project_id, $mr_id) - { - return $this->get($this->getProjectPath($project_id, 'merge_requests/'.$this->encodePath($mr_id).'/closes_issues')); - } - - /** - * @param int $project_id - * @param int $mr_id - * - * @return mixed - */ - public function approvals($project_id, $merge_request_iid) - { - return $this->get($this->getProjectPath($project_id, 'merge_requests/'.$this->encodePath($merge_request_iid).'/approvals')); - } - - /** - * @param int $project_id - * @param int $mr_id - * - * @return mixed - */ - public function approve($project_id, $merge_request_iid) - { - return $this->post($this->getProjectPath($project_id, 'merge_requests/'.$this->encodePath($merge_request_iid).'/approve')); - } - - /** - * @param int $project_id - * @param int $mr_id - * - * @return mixed - */ - public function unapprove($project_id, $merge_request_iid) - { - return $this->post($this->getProjectPath($project_id, 'merge_requests/'.$this->encodePath($merge_request_iid).'/unapprove')); - } - - /** - * @param int $project_id - * @param int $merge_request_iid - * - * @return mixed - */ - public function awardEmoji($project_id, $merge_request_iid) - { - return $this->get($this->getProjectPath($project_id, 'merge_requests/'.$this->encodePath($merge_request_iid).'/award_emoji')); - } -} diff --git a/lib/Gitlab/Api/Milestones.php b/lib/Gitlab/Api/Milestones.php deleted file mode 100644 index 0f9b2d25e..000000000 --- a/lib/Gitlab/Api/Milestones.php +++ /dev/null @@ -1,73 +0,0 @@ -createOptionsResolver(); - $resolver->setDefined('iids') - ->setAllowedTypes('iids', 'array') - ->setAllowedValues('iids', function (array $value) { - return count($value) == count(array_filter($value, 'is_int')); - }) - ; - $resolver->setDefined('state') - ->setAllowedValues('state', ['active', 'closed']) - ; - $resolver->setDefined('search'); - - return $this->get($this->getProjectPath($project_id, 'milestones'), $resolver->resolve($parameters)); - } - - /** - * @param int $project_id - * @param int $milestone_id - * @return mixed - */ - public function show($project_id, $milestone_id) - { - return $this->get($this->getProjectPath($project_id, 'milestones/'.$this->encodePath($milestone_id))); - } - - /** - * @param int $project_id - * @param array $params - * @return mixed - */ - public function create($project_id, array $params) - { - return $this->post($this->getProjectPath($project_id, 'milestones'), $params); - } - - /** - * @param int $project_id - * @param int $milestone_id - * @param array $params - * @return mixed - */ - public function update($project_id, $milestone_id, array $params) - { - return $this->put($this->getProjectPath($project_id, 'milestones/'.$this->encodePath($milestone_id)), $params); - } - - /** - * @param int $project_id - * @param int $milestone_id - * @return mixed - */ - public function issues($project_id, $milestone_id) - { - return $this->get($this->getProjectPath($project_id, 'milestones/'.$this->encodePath($milestone_id).'/issues')); - } -} diff --git a/lib/Gitlab/Api/ProjectNamespaces.php b/lib/Gitlab/Api/ProjectNamespaces.php deleted file mode 100644 index 815d4e083..000000000 --- a/lib/Gitlab/Api/ProjectNamespaces.php +++ /dev/null @@ -1,20 +0,0 @@ -createOptionsResolver(); - $resolver->setDefined('search'); - - return $this->get('namespaces', $resolver->resolve($parameters)); - } -} diff --git a/lib/Gitlab/Api/Projects.php b/lib/Gitlab/Api/Projects.php deleted file mode 100644 index 2337a9cec..000000000 --- a/lib/Gitlab/Api/Projects.php +++ /dev/null @@ -1,669 +0,0 @@ -createOptionsResolver(); - $booleanNormalizer = function (Options $resolver, $value) { - return $value ? 'true' : 'false'; - }; - $resolver->setDefined('archived') - ->setAllowedTypes('archived', 'bool') - ->setNormalizer('archived', $booleanNormalizer) - ; - $resolver->setDefined('visibility') - ->setAllowedValues('visibility', ['public', 'internal', 'private']) - ; - $resolver->setDefined('order_by') - ->setAllowedValues('order_by', ['id', 'name', 'path', 'created_at', 'updated_at', 'last_activity_at']) - ; - $resolver->setDefined('sort') - ->setAllowedValues('sort', ['asc', 'desc']) - ; - $resolver->setDefined('search'); - $resolver->setDefined('simple') - ->setAllowedTypes('simple', 'bool') - ->setNormalizer('simple', $booleanNormalizer) - ; - $resolver->setDefined('owned') - ->setAllowedTypes('owned', 'bool') - ->setNormalizer('owned', $booleanNormalizer) - ; - $resolver->setDefined('membership') - ->setAllowedTypes('membership', 'bool') - ->setNormalizer('membership', $booleanNormalizer) - ; - $resolver->setDefined('starred') - ->setAllowedTypes('starred', 'bool') - ->setNormalizer('starred', $booleanNormalizer) - ; - $resolver->setDefined('statistics') - ->setAllowedTypes('statistics', 'bool') - ->setNormalizer('statistics', $booleanNormalizer) - ; - $resolver->setDefined('with_issues_enabled') - ->setAllowedTypes('with_issues_enabled', 'bool') - ->setNormalizer('with_issues_enabled', $booleanNormalizer) - ; - $resolver->setDefined('with_merge_requests_enabled') - ->setAllowedTypes('with_merge_requests_enabled', 'bool') - ->setNormalizer('with_merge_requests_enabled', $booleanNormalizer) - ; - - return $this->get('projects', $resolver->resolve($parameters)); - } - - /** - * @param int $project_id - * @return mixed - */ - public function show($project_id) - { - return $this->get('projects/'.$this->encodePath($project_id)); - } - - /** - * @param string $name - * @param array $params - * @return mixed - */ - public function create($name, array $params = array()) - { - $params['name'] = $name; - - return $this->post('projects', $params); - } - - /** - * @param int $user_id - * @param string $name - * @param array $params - * @return mixed - */ - public function createForUser($user_id, $name, array $params = array()) - { - $params['name'] = $name; - - return $this->post('projects/user/'.$this->encodePath($user_id), $params); - } - - /** - * @param int $project_id - * @param array $params - * @return mixed - */ - public function update($project_id, array $params) - { - return $this->put('projects/'.$this->encodePath($project_id), $params); - } - - /** - * @param int $project_id - * @return mixed - */ - public function remove($project_id) - { - return $this->delete('projects/'.$this->encodePath($project_id)); - } - - /** - * @param int $project_id - * @return mixed - */ - public function archive($project_id) - { - return $this->post("projects/".$this->encodePath($project_id)."/archive"); - } - - /** - * @param int $project_id - * @return mixed - */ - public function unarchive($project_id) - { - return $this->post("projects/".$this->encodePath($project_id)."/unarchive"); - } - - /** - * @param int $project_id - * @param array $parameters ( - * - * @var string $scope The scope of pipelines, one of: running, pending, finished, branches, tags. - * @var string $status The status of pipelines, one of: running, pending, success, failed, canceled, skipped. - * @var string $ref The ref of pipelines. - * @var bool $yaml_errors Returns pipelines with invalid configurations. - * @var string $name The name of the user who triggered pipelines. - * @var string $username The username of the user who triggered pipelines. - * @var string $order_by Order pipelines by id, status, ref, or user_id (default: id). - * @var string $order Sort pipelines in asc or desc order (default: desc). - * ) - * @return mixed - */ - public function pipelines($project_id, array $parameters = []) - { - $resolver = $this->createOptionsResolver(); - $booleanNormalizer = function (Options $resolver, $value) { - return $value ? 'true' : 'false'; - }; - - $resolver->setDefined('scope') - ->setAllowedValues('scope', ['running', 'pending', 'finished', 'branches', 'tags']) - ; - $resolver->setDefined('status') - ->setAllowedValues('status', ['running', 'pending', 'success', 'failed', 'canceled', 'skipped']) - ; - $resolver->setDefined('ref'); - $resolver->setDefined('yaml_errors') - ->setAllowedTypes('yaml_errors', 'bool') - ->setNormalizer('yaml_errors', $booleanNormalizer) - ; - $resolver->setDefined('name'); - $resolver->setDefined('username'); - $resolver->setDefined('order_by') - ->setAllowedValues('order_by', ['id', 'status', 'ref', 'user_id']) - ; - $resolver->setDefined('sort') - ->setAllowedValues('sort', ['asc', 'desc']) - ; - - return $this->get($this->getProjectPath($project_id, 'pipelines'), $resolver->resolve($parameters)); - } - - /** - * @param int $project_id - * @param int $pipeline_id - * @return mixed - */ - public function pipeline($project_id, $pipeline_id) - { - return $this->get($this->getProjectPath($project_id, 'pipelines/'.$this->encodePath($pipeline_id))); - } - - /** - * @param int $project_id - * @param string $commit_ref - * @return mixed - */ - public function createPipeline($project_id, $commit_ref) - { - return $this->post($this->getProjectPath($project_id, 'pipeline'), array( - 'ref' => $commit_ref)); - } - - /** - * @param int $project_id - * @param int $pipeline_id - * @return mixed - */ - public function retryPipeline($project_id, $pipeline_id) - { - return $this->post($this->getProjectPath($project_id, 'pipelines/'.$this->encodePath($pipeline_id)).'/retry'); - } - - /** - * @param int $project_id - * @param int $pipeline_id - * @return mixed - */ - public function cancelPipeline($project_id, $pipeline_id) - { - return $this->post($this->getProjectPath($project_id, 'pipelines/'.$this->encodePath($pipeline_id)).'/cancel'); - } - - /** - * @param int $project_id - * @param string $username_query - * @return mixed - */ - public function members($project_id, $username_query = null) - { - return $this->get($this->getProjectPath($project_id, 'members'), array( - 'query' => $username_query - )); - } - - /** - * @param int $project_id - * @param int $user_id - * @return mixed - */ - public function member($project_id, $user_id) - { - return $this->get($this->getProjectPath($project_id, 'members/'.$this->encodePath($user_id))); - } - - /** - * @param int $project_id - * @param int $user_id - * @param int $access_level - * @return mixed - */ - public function addMember($project_id, $user_id, $access_level) - { - return $this->post($this->getProjectPath($project_id, 'members'), array( - 'user_id' => $user_id, - 'access_level' => $access_level - )); - } - - /** - * @param int $project_id - * @param int $user_id - * @param int $access_level - * @return mixed - */ - public function saveMember($project_id, $user_id, $access_level) - { - return $this->put($this->getProjectPath($project_id, 'members/'.urldecode($user_id)), array( - 'access_level' => $access_level - )); - } - - /** - * @param int $project_id - * @param int $user_id - * @return mixed - */ - public function removeMember($project_id, $user_id) - { - return $this->delete($this->getProjectPath($project_id, 'members/'.urldecode($user_id))); - } - - /** - * @param int $project_id - * @param array $parameters - * - * @return mixed - */ - public function hooks($project_id, array $parameters = []) - { - $resolver = $this->createOptionsResolver(); - - return $this->get($this->getProjectPath($project_id, 'hooks'), $resolver->resolve($parameters)); - } - - /** - * @param int $project_id - * @param int $hook_id - * @return mixed - */ - public function hook($project_id, $hook_id) - { - return $this->get($this->getProjectPath($project_id, 'hooks/'.$this->encodePath($hook_id))); - } - - /** - * @param int $project_id - * @param string $url - * @param array $params - * @return mixed - */ - public function addHook($project_id, $url, array $params = array()) - { - if (empty($params)) { - $params = array('push_events' => true); - } - - $params['url'] = $url; - - return $this->post($this->getProjectPath($project_id, 'hooks'), $params); - } - - /** - * @param int $project_id - * @param int $hook_id - * @param array $params - * @return mixed - */ - public function updateHook($project_id, $hook_id, array $params) - { - return $this->put($this->getProjectPath($project_id, 'hooks/'.$this->encodePath($hook_id)), $params); - } - - /** - * @param int $project_id - * @param int $hook_id - * @return mixed - */ - public function removeHook($project_id, $hook_id) - { - return $this->delete($this->getProjectPath($project_id, 'hooks/'.$this->encodePath($hook_id))); - } - - /** - * @param int $project_id - * @return mixed - */ - public function deployKeys($project_id) - { - return $this->get($this->getProjectPath($project_id, 'deploy_keys')); - } - - /** - * @param int $project_id - * @param int $key_id - * @return mixed - */ - public function deployKey($project_id, $key_id) - { - return $this->get($this->getProjectPath($project_id, 'deploy_keys/'.$this->encodePath($key_id))); - } - - /** - * @param int $project_id - * @param string $title - * @param string $key - * @param bool $canPush - * @return mixed - */ - public function addDeployKey($project_id, $title, $key, $canPush = false) - { - return $this->post($this->getProjectPath($project_id, 'deploy_keys'), array( - 'title' => $title, - 'key' => $key, - 'can_push' => $canPush - )); - } - - /** - * @param int $project_id - * @param int $key_id - * @return mixed - */ - public function deleteDeployKey($project_id, $key_id) - { - return $this->delete($this->getProjectPath($project_id, 'deploy_keys/'.$this->encodePath($key_id))); - } - - /** - * @param int $project_id - * @param int $key_id - * @return mixed - */ - public function enableDeployKey($project_id, $key_id) - { - return $this->post($this->getProjectPath($project_id, 'deploy_keys/'.$this->encodePath($key_id).'/enable')); - } - - /** - * @param int $project_id - * @param array $parameters ( - * - * @var string $action Include only events of a particular action type. - * @var string $target_type Include only events of a particular target type. - * @var \DateTimeInterface $before Include only events created before a particular date. - * @var \DateTimeInterface $after Include only events created after a particular date. - * @var string $sort Sort events in asc or desc order by created_at. Default is desc. - * ) - * - * @return mixed - */ - public function events($project_id, array $parameters = []) - { - $resolver = $this->createOptionsResolver(); - $datetimeNormalizer = function (Options $resolver, \DateTimeInterface $value) { - return $value->format('Y-m-d'); - }; - - $resolver->setDefined('action') - ->setAllowedValues('action', ['created', 'updated', 'closed', 'reopened', 'pushed', 'commented', 'merged', 'joined', 'left', 'destroyed', 'expired']) - ; - $resolver->setDefined('target_type') - ->setAllowedValues('target_type', ['issue', 'milestone', 'merge_request', 'note', 'project', 'snippet', 'user']) - ; - $resolver->setDefined('before') - ->setAllowedTypes('before', \DateTimeInterface::class) - ->setNormalizer('before', $datetimeNormalizer); - $resolver->setDefined('after') - ->setAllowedTypes('after', \DateTimeInterface::class) - ->setNormalizer('after', $datetimeNormalizer) - ; - $resolver->setDefined('sort') - ->setAllowedValues('sort', ['asc', 'desc']) - ; - - return $this->get($this->getProjectPath($project_id, 'events'), $resolver->resolve($parameters)); - } - - /** - * @param int $project_id - * @return mixed - */ - public function labels($project_id) - { - return $this->get($this->getProjectPath($project_id, 'labels')); - } - - /** - * @param int $project_id - * @param array $params - * @return mixed - */ - public function addLabel($project_id, array $params) - { - return $this->post($this->getProjectPath($project_id, 'labels'), $params); - } - - /** - * @param int $project_id - * @param array $params - * @return mixed - */ - public function updateLabel($project_id, array $params) - { - return $this->put($this->getProjectPath($project_id, 'labels'), $params); - } - - /** - * @param int $project_id - * @param string $name - * @return mixed - */ - public function removeLabel($project_id, $name) - { - return $this->delete($this->getProjectPath($project_id, 'labels'), array( - 'name' => $name - )); - } - - /** - * @param int $project_id - * @param array $params ( - * - * @var string $namespace The ID or path of the namespace that the project will be forked to - * ) - * @return mixed - */ - public function fork($project_id, array $parameters = []) - { - $resolver = new OptionsResolver(); - $resolver->setDefined('namespace'); - - $resolved = $resolver->resolve($parameters); - - return $this->post($this->getProjectPath($project_id, 'fork'), $resolved); - } - - /** - * @param int $project_id - * @param int $forked_project_id - * @return mixed - */ - public function createForkRelation($project_id, $forked_project_id) - { - return $this->post($this->getProjectPath($project_id, 'fork/'.$this->encodePath($forked_project_id))); - } - - /** - * @param int $project_id - * @return mixed - */ - public function removeForkRelation($project_id) - { - return $this->delete($this->getProjectPath($project_id, 'fork')); - } - - /** - * @param int $project_id - * @param string $service_name - * @param array $params - * @return mixed - */ - public function setService($project_id, $service_name, array $params = array()) - { - return $this->put($this->getProjectPath($project_id, 'services/'.$this->encodePath($service_name)), $params); - } - - /** - * @param int $project_id - * @param string $service_name - * @return mixed - */ - public function removeService($project_id, $service_name) - { - return $this->delete($this->getProjectPath($project_id, 'services/'.$this->encodePath($service_name))); - } - - /** - * @param int $project_id - * @return mixed - */ - public function variables($project_id) - { - return $this->get($this->getProjectPath($project_id, 'variables')); - } - - /** - * @param int $project_id - * @param string $key - * @return mixed - */ - public function variable($project_id, $key) - { - return $this->get($this->getProjectPath($project_id, 'variables/'.$this->encodePath($key))); - } - - /** - * @param int $project_id - * @param string $key - * @param string $value - * @param bool $protected - * @param string $environment_scope - * @return mixed - */ - public function addVariable($project_id, $key, $value, $protected = null, $environment_scope = null) - { - $payload = array( - 'key' => $key, - 'value' => $value, - ); - - if ($protected) { - $payload['protected'] = $protected; - } - - if ($environment_scope) { - $payload['environment_scope'] = $environment_scope; - } - - return $this->post($this->getProjectPath($project_id, 'variables'), $payload); - } - - /** - * @param int $project_id - * @param string $key - * @param string $value - * @param bool $protected - * @param string $environment_scope - * @return mixed - */ - public function updateVariable($project_id, $key, $value, $protected = null, $environment_scope = null) - { - $payload = array( - 'value' => $value, - ); - - if ($protected) { - $payload['protected'] = $protected; - } - - if ($environment_scope) { - $payload['environment_scope'] = $environment_scope; - } - - return $this->put($this->getProjectPath($project_id, 'variables/'.$this->encodePath($key)), $payload); - } - - /** - * @param int $project_id - * @param string $key - * @return mixed - */ - public function removeVariable($project_id, $key) - { - return $this->delete($this->getProjectPath($project_id, 'variables/'.$this->encodePath($key))); - } - - /** - * @param int $project_id - * @param string $file - * @return mixed - */ - public function uploadFile($project_id, $file) - { - return $this->post($this->getProjectPath($project_id, 'uploads'), array(), array(), array('file' => $file)); - } - - /** - * @param int $project_id - * @param array $parameters - * - * @return mixed - */ - public function deployments($project_id, array $parameters = []) - { - $resolver = $this->createOptionsResolver(); - - return $this->get($this->getProjectPath($project_id, 'deployments'), $resolver->resolve($parameters)); - } - - /** - * @param int $project_id - * @param int $deployment_id - * @return mixed - */ - public function deployment($project_id, $deployment_id) - { - return $this->get($this->getProjectPath($project_id, 'deployments/'.$this->encodePath($deployment_id))); - } -} diff --git a/lib/Gitlab/Api/Repositories.php b/lib/Gitlab/Api/Repositories.php deleted file mode 100644 index 14313b66e..000000000 --- a/lib/Gitlab/Api/Repositories.php +++ /dev/null @@ -1,451 +0,0 @@ -createOptionsResolver(); - return $this->get($this->getProjectPath($project_id, 'repository/branches'), $resolver->resolve($parameters)); - } - - /** - * @param int $project_id - * @param int $branch_id - * @return mixed - */ - public function branch($project_id, $branch_id) - { - return $this->get($this->getProjectPath($project_id, 'repository/branches/'.$this->encodePath($branch_id))); - } - - /** - * @param int $project_id - * @param string $branch - * @param string $ref - * @return mixed - */ - public function createBranch($project_id, $branch, $ref) - { - return $this->post($this->getProjectPath($project_id, 'repository/branches'), array( - 'branch' => $branch, - 'ref' => $ref - )); - } - - /** - * @param int $project_id - * @param string $branch - * @return mixed - */ - public function deleteBranch($project_id, $branch) - { - return $this->delete($this->getProjectPath($project_id, 'repository/branches/'.$this->encodePath($branch))); - } - - /** - * @param int $project_id - * @param string $branch_name - * @param bool $devPush - * @param bool $devMerge - * @return mixed - */ - public function protectBranch($project_id, $branch_name, $devPush = false, $devMerge = false) - { - return $this->put($this->getProjectPath($project_id, 'repository/branches/'.$this->encodePath($branch_name).'/protect'), array( - 'developers_can_push' => $devPush, - 'developers_can_merge' => $devMerge - )); - } - - /** - * @param int $project_id - * @param string $branch_name - * @return mixed - */ - public function unprotectBranch($project_id, $branch_name) - { - return $this->put($this->getProjectPath($project_id, 'repository/branches/'.$this->encodePath($branch_name).'/unprotect')); - } - - /** - * @param int $project_id - * @param array $parameters - * @return mixed - */ - public function tags($project_id, array $parameters = []) - { - $resolver = $this->createOptionsResolver(); - - return $this->get($this->getProjectPath($project_id, 'repository/tags'), $resolver->resolve($parameters)); - } - - /** - * @param int $project_id - * @param string $name - * @param string $ref - * @param string $message - * @return mixed - */ - public function createTag($project_id, $name, $ref, $message = null) - { - return $this->post($this->getProjectPath($project_id, 'repository/tags'), array( - 'tag_name' => $name, - 'ref' => $ref, - 'message' => $message - )); - } - - /** - * @param int $project_id - * @param string $tag_name - * @param string $description - * - * @return mixed - */ - public function createRelease($project_id, $tag_name, $description) - { - return $this->post($this->getProjectPath($project_id, 'repository/tags/' . $this->encodePath($tag_name) . '/release'), array( - 'id' => $project_id, - 'tag_name' => $tag_name, - 'description' => $description - )); - } - - /** - * @param int $project_id - * @param string $tag_name - * @param string $description - * - * @return mixed - */ - public function updateRelease($project_id, $tag_name, $description) - { - return $this->put($this->getProjectPath($project_id, 'repository/tags/' . $this->encodePath($tag_name) . '/release'), array( - 'id' => $project_id, - 'tag_name' => $tag_name, - 'description' => $description - )); - } - - /** - * @param int $project_id - * @param array $parameters ( - * - * @var string $ref_name The name of a repository branch or tag or if not given the default branch. - * @var \DateTimeInterface $since Only commits after or on this date will be returned. - * @var \DateTimeInterface $until Only commits before or on this date will be returned. - * ) - * - * @return mixed - */ - public function commits($project_id, array $parameters = []) - { - $resolver = $this->createOptionsResolver(); - $datetimeNormalizer = function (Options $options, \DateTimeInterface $value) { - return $value->format('c'); - }; - - $resolver->setDefined('ref_name'); - $resolver->setDefined('since') - ->setAllowedTypes('since', \DateTimeInterface::class) - ->setNormalizer('since', $datetimeNormalizer) - ; - $resolver->setDefined('until') - ->setAllowedTypes('until', \DateTimeInterface::class) - ->setNormalizer('until', $datetimeNormalizer) - ; - - return $this->get($this->getProjectPath($project_id, 'repository/commits'), $resolver->resolve($parameters)); - } - - /** - * @param int $project_id - * @param $sha - * @return mixed - */ - public function commit($project_id, $sha) - { - return $this->get($this->getProjectPath($project_id, 'repository/commits/'.$this->encodePath($sha))); - } - - /** - * @param int $project_id - * @param array $parameters ( - * - * @var string $branch Name of the branch to commit into. To create a new branch, also provide start_branch. - * @var string $commit_message Commit message. - * @var string $start_branch Name of the branch to start the new commit from. - * @var array $actions ( - * - * @var string $action he action to perform, create, delete, move, update. - * @var string $file_path Full path to the file. - * @var string $previous_path Original full path to the file being moved. - * @var string $content File content, required for all except delete. Optional for move. - * @var string $encoding text or base64. text is default. - * ) - * @var string $author_email Specify the commit author's email address. - * @var string $author_name Specify the commit author's name. - * ) - * - * @return mixed - */ - public function createCommit($project_id, array $parameters = []) - { - $resolver = new OptionsResolver(); - $resolver->setDefined('branch') - ->setRequired('branch') - ; - $resolver->setDefined('commit_message') - ->setRequired('commit_message') - ; - $resolver->setDefined('start_branch'); - $resolver->setDefined('actions') - ->setRequired('actions') - ->setAllowedTypes('actions', 'array') - ->setAllowedValues('actions', function (array $actions) { - return !empty($actions); - }) - ->setNormalizer('actions', function (Options $resolver, array $actions) { - $actionsOptionsResolver = new OptionsResolver(); - $actionsOptionsResolver->setDefined('action') - ->setRequired('action') - ->setAllowedValues('action', ['create', 'delete', 'move', 'update']) - ; - $actionsOptionsResolver->setDefined('file_path') - ->setRequired('file_path') - ; - $actionsOptionsResolver->setDefined('previous_path'); - $actionsOptionsResolver->setDefined('content'); - $actionsOptionsResolver->setDefined('encoding') - ->setAllowedValues('encoding', ['test', 'base64']) - ; - - return array_map(function ($action) use ($actionsOptionsResolver) { - return $actionsOptionsResolver->resolve($action); - }, $actions); - }) - ; - $resolver->setDefined('author_email'); - $resolver->setDefined('author_name'); - - return $this->post($this->getProjectPath($project_id, 'repository/commits'), $resolver->resolve($parameters)); - } - - /** - * @param int $project_id - * @param string $sha - * @param array $parameters - * - * @return mixed - */ - public function commitComments($project_id, $sha, array $parameters = []) - { - $resolver = $this->createOptionsResolver(); - - return $this->get( - $this->getProjectPath($project_id, 'repository/commits/'.$this->encodePath($sha).'/comments'), - $resolver->resolve($parameters) - ); - } - - /** - * @param int $project_id - * @param string $sha - * @param string $note - * @param array $params - * @return mixed - */ - public function createCommitComment($project_id, $sha, $note, array $params = array()) - { - $params['note'] = $note; - - return $this->post($this->getProjectPath($project_id, 'repository/commits/'.$this->encodePath($sha).'/comments'), $params); - } - - /** - * @param int $project_id - * @param string $sha - * @param array $params - * @return mixed - */ - public function getCommitBuildStatus($project_id, $sha, array $params = array()) - { - return $this->get($this->getProjectPath($project_id, 'repository/commits/'.$this->encodePath($sha).'/statuses'), $params); - } - - /** - * @param int $project_id - * @param string $sha - * @param string $state - * @param array $params - * @return mixed - */ - public function postCommitBuildStatus($project_id, $sha, $state, array $params = array()) - { - $params['state'] = $state; - - return $this->post($this->getProjectPath($project_id, 'statuses/'.$this->encodePath($sha)), $params); - } - - /** - * @param int $project_id - * @param string $fromShaOrMaster - * @param string $toShaOrMaster - * @return mixed - */ - public function compare($project_id, $fromShaOrMaster, $toShaOrMaster) - { - return $this->get($this->getProjectPath( - $project_id, - 'repository/compare?from='.$this->encodePath($fromShaOrMaster).'&to='.$this->encodePath($toShaOrMaster) - )); - } - - /** - * @param int $project_id - * @param string $sha - * @return string - */ - public function diff($project_id, $sha) - { - return $this->get($this->getProjectPath($project_id, 'repository/commits/'.$this->encodePath($sha).'/diff')); - } - - /** - * @param int $project_id - * @param array $params - * @return mixed - */ - public function tree($project_id, array $params = array()) - { - return $this->get($this->getProjectPath($project_id, 'repository/tree'), $params); - } - - /** - * @param int $project_id - * @param string $sha - * @param string $filepath - * @return mixed - */ - public function blob($project_id, $sha, $filepath) - { - @trigger_error(sprintf('The %s() method is deprecated since version 9.2 and will be removed in 10.0. Use the %s::getRawFile() method instead.', __METHOD__, RepositoryFiles::class), E_USER_DEPRECATED); - - return $this->client->repositoryFiles()->getRawFile($project_id, $filepath, $sha); - } - - /** - * @param int $project_id - * @param string $file_path - * @param string $ref - * @return mixed - */ - public function getFile($project_id, $file_path, $ref) - { - @trigger_error(sprintf('The %s() method is deprecated since version 9.2 and will be removed in 10.0. Use the %s::getFile() method instead.', __METHOD__, RepositoryFiles::class), E_USER_DEPRECATED); - - return $this->client->repositoryFiles()->getFile($project_id, $file_path, $ref); - } - - /** - * @param int $project_id - * @param string $file_path - * @param string $content - * @param string $branch - * @param string $commit_message - * @param string $encoding - * @param string $author_email - * @param string $author_name - * @return mixed - */ - public function createFile($project_id, $file_path, $content, $branch, $commit_message, $encoding = null, $author_email = null, $author_name = null) - { - @trigger_error(sprintf('The %s() method is deprecated since version 9.2 and will be removed in 10.0. Use the %s::createFile() method instead.', __METHOD__, RepositoryFiles::class), E_USER_DEPRECATED); - - return $this->client->repositoryFiles()->createFile($project_id, [ - 'file_path' => $file_path, - 'branch' => $branch, - 'content' => $content, - 'commit_message' => $commit_message, - 'encoding' => $encoding, - 'author_email' => $author_email, - 'author_name' => $author_name, - ]); - } - - /** - * @param int $project_id - * @param string $file_path - * @param string $content - * @param string $branch - * @param string $commit_message - * @param string $encoding - * @param string $author_email - * @param string $author_name - * @return mixed - */ - public function updateFile($project_id, $file_path, $content, $branch, $commit_message, $encoding = null, $author_email = null, $author_name = null) - { - @trigger_error(sprintf('The %s() method is deprecated since version 9.2 and will be removed in 10.0. Use the %s::updateFile() method instead.', __METHOD__, RepositoryFiles::class), E_USER_DEPRECATED); - - return $this->client->repositoryFiles()->updateFile($project_id, [ - 'file_path' => $file_path, - 'branch' => $branch, - 'content' => $content, - 'commit_message' => $commit_message, - 'encoding' => $encoding, - 'author_email' => $author_email, - 'author_name' => $author_name, - ]); - } - - /** - * @param int $project_id - * @param string $file_path - * @param string $branch - * @param string $commit_message - * @param string $author_email - * @param string $author_name - * @return mixed - */ - public function deleteFile($project_id, $file_path, $branch, $commit_message, $author_email = null, $author_name = null) - { - @trigger_error(sprintf('The %s() method is deprecated since version 9.2 and will be removed in 10.0. Use the %s::deleteFile() method instead.', __METHOD__, RepositoryFiles::class), E_USER_DEPRECATED); - - return $this->client->repositoryFiles()->deleteFile($project_id, [ - 'file_path' => $file_path, - 'branch' => $branch, - 'commit_message' => $commit_message, - 'author_email' => $author_email, - 'author_name' => $author_name, - ]); - } - - /** - * @param int $project_id - * @return mixed - */ - public function contributors($project_id) - { - return $this->get($this->getProjectPath($project_id, 'repository/contributors')); - } - - /** - * @param int $project_id - * @param array $params - * @param string $format Options: "tar.gz", "zip", "tar.bz2" and "tar" - * @return mixed - */ - public function archive($project_id, $params = array(), $format = 'tar.gz') - { - return $this->get($this->getProjectPath($project_id, 'repository/archive.'.$format), $params); - } -} diff --git a/lib/Gitlab/Api/RepositoryFiles.php b/lib/Gitlab/Api/RepositoryFiles.php deleted file mode 100644 index bc20c00f0..000000000 --- a/lib/Gitlab/Api/RepositoryFiles.php +++ /dev/null @@ -1,135 +0,0 @@ -get($this->getProjectPath($project_id, 'repository/files/'.$this->encodePath($file_path)), array( - 'ref' => $ref - )); - } - - /** - * @param int $project_id - * @param string $file_path - * @param string $ref - * @return mixed - */ - public function getRawFile($project_id, $file_path, $ref) - { - return $this->get($this->getProjectPath($project_id, 'repository/files/'.$this->encodePath($file_path).'/raw'), array( - 'ref' => $ref, - )); - } - - /** - * @param int $project_id - * @param array $parameters ( - * - * @var string $file_path Url encoded full path to new file. Ex. lib%2Fclass%2Erb. - * @var string $branch Name of the branch. - * @var string $start_branch Name of the branch to start the new commit from. - * @var string $encoding Change encoding to 'base64'. Default is text. - * @var string $author_email Specify the commit author's email address. - * @var string $author_name Specify the commit author's name. - * @var string $content File content. - * @var string $commit_message Commit message. - * ) - * - * @return mixed - */ - public function createFile($project_id, array $parameters = []) - { - $resolver = new OptionsResolver(); - $resolver->setRequired('file_path'); - $resolver->setRequired('branch'); - $resolver->setDefined('start_branch'); - $resolver->setDefined('encoding') - ->setAllowedValues('encoding', ['text', 'base64']) - ; - $resolver->setDefined('author_email'); - $resolver->setDefined('author_name'); - $resolver->setRequired('content'); - $resolver->setRequired('commit_message'); - - $resolved = $resolver->resolve($parameters); - - return $this->post($this->getProjectPath($project_id, 'repository/files/'.$this->encodePath($resolved['file_path'])), $resolved); - } - - /** - * @param int $project_id - * @param array $parameters ( - * - * @var string $file_path Url encoded full path to new file. Ex. lib%2Fclass%2Erb. - * @var string $branch Name of the branch. - * @var string $start_branch Name of the branch to start the new commit from. - * @var string $encoding Change encoding to 'base64'. Default is text. - * @var string $author_email Specify the commit author's email address. - * @var string $author_name Specify the commit author's name. - * @var string $content File content. - * @var string $commit_message Commit message. - * @var string $last_commit_id Last known file commit id. - * ) - * - * @return mixed - */ - public function updateFile($project_id, array $parameters = []) - { - $resolver = new OptionsResolver(); - $resolver->setRequired('file_path'); - $resolver->setRequired('branch'); - $resolver->setDefined('start_branch'); - $resolver->setDefined('encoding') - ->setAllowedValues('encoding', ['text', 'base64']) - ; - $resolver->setDefined('author_email'); - $resolver->setDefined('author_name'); - $resolver->setRequired('content'); - $resolver->setRequired('commit_message'); - $resolver->setDefined('last_commit_id'); - - $resolved = $resolver->resolve($parameters); - - return $this->put($this->getProjectPath($project_id, 'repository/files/'.$this->encodePath($resolved['file_path'])), $resolved); - } - - /** - * @param int $project_id - * @param array $parameters ( - * - * @var string $file_path Url encoded full path to new file. Ex. lib%2Fclass%2Erb. - * @var string $branch Name of the branch. - * @var string $start_branch Name of the branch to start the new commit from. - * @var string $author_email Specify the commit author's email address. - * @var string $author_name Specify the commit author's name. - * @var string $commit_message Commit message. - * ) - * - * @return mixed - */ - public function deleteFile($project_id, array $parameters = []) - { - $resolver = new OptionsResolver(); - $resolver->setRequired('file_path'); - $resolver->setRequired('branch'); - $resolver->setDefined('start_branch'); - $resolver->setDefined('author_email'); - $resolver->setDefined('author_name'); - $resolver->setRequired('commit_message'); - - $resolved = $resolver->resolve($parameters); - - return $this->delete($this->getProjectPath($project_id, 'repository/files/'.$this->encodePath($resolved['file_path'])), $resolved); - } -} diff --git a/lib/Gitlab/Api/Snippets.php b/lib/Gitlab/Api/Snippets.php deleted file mode 100644 index e17c0cba7..000000000 --- a/lib/Gitlab/Api/Snippets.php +++ /dev/null @@ -1,81 +0,0 @@ -get($this->getProjectPath($project_id, 'snippets')); - } - - /** - * @param int $project_id - * @param int $snippet_id - * @return mixed - */ - public function show($project_id, $snippet_id) - { - return $this->get($this->getProjectPath($project_id, 'snippets/'.$this->encodePath($snippet_id))); - } - - /** - * @param int $project_id - * @param string $title - * @param string $filename - * @param string $code - * @return mixed - */ - public function create($project_id, $title, $filename, $code) - { - return $this->post($this->getProjectPath($project_id, 'snippets'), array( - 'title' => $title, - 'file_name' => $filename, - 'code' => $code - )); - } - - /** - * @param int $project_id - * @param int $snippet_id - * @param array $params - * @return mixed - */ - public function update($project_id, $snippet_id, array $params) - { - return $this->put($this->getProjectPath($project_id, 'snippets/'.$this->encodePath($snippet_id)), $params); - } - - /** - * @param int $project_id - * @param int $snippet_id - * @return string - */ - public function content($project_id, $snippet_id) - { - return $this->get($this->getProjectPath($project_id, 'snippets/'.$this->encodePath($snippet_id).'/raw')); - } - - /** - * @param int $project_id - * @param int $snippet_id - * @return mixed - */ - public function remove($project_id, $snippet_id) - { - return $this->delete($this->getProjectPath($project_id, 'snippets/'.$this->encodePath($snippet_id))); - } - - /** - * @param int $project_id - * @param int $snippet_id - * - * @return mixed - */ - public function awardEmoji($project_id, $snippet_id) - { - return $this->get($this->getProjectPath($project_id, 'snippets/'.$this->encodePath($snippet_id).'/award_emoji')); - } -} diff --git a/lib/Gitlab/Api/SystemHooks.php b/lib/Gitlab/Api/SystemHooks.php deleted file mode 100644 index b21fd0908..000000000 --- a/lib/Gitlab/Api/SystemHooks.php +++ /dev/null @@ -1,41 +0,0 @@ -get('hooks'); - } - - /** - * @param string $url - * @return mixed - */ - public function create($url) - { - return $this->post('hooks', array( - 'url' => $url - )); - } - - /** - * @param int $id - * @return mixed - */ - public function test($id) - { - return $this->get('hooks/'.$this->encodePath($id)); - } - - /** - * @param int $id - * @return mixed - */ - public function remove($id) - { - return $this->delete('hooks/'.$this->encodePath($id)); - } -} diff --git a/lib/Gitlab/Api/Tags.php b/lib/Gitlab/Api/Tags.php deleted file mode 100644 index 16aa64a67..000000000 --- a/lib/Gitlab/Api/Tags.php +++ /dev/null @@ -1,43 +0,0 @@ -get($this->getProjectPath($project_id, 'repository/tags')); - } - - /** - * @param int $project_id - * @param string $tag_name - * @return mixed - */ - public function show($project_id, $tag_name) - { - return $this->get($this->getProjectPath($project_id, 'repository/tags/'.$tag_name)); - } - - /** - * @param int $project_id - * @param array $params - * @return mixed - */ - public function create($project_id, array $params = array()) - { - return $this->post($this->getProjectPath($project_id, "repository/tags"), $params); - } - - /** - * @param int $project_id - * @param string $tag_name - * @return mixed - */ - public function remove($project_id, $tag_name) - { - return $this->delete($this->getProjectPath($project_id, 'repository/tags/'.$tag_name)); - } -} diff --git a/lib/Gitlab/Api/Users.php b/lib/Gitlab/Api/Users.php deleted file mode 100644 index fa63e0515..000000000 --- a/lib/Gitlab/Api/Users.php +++ /dev/null @@ -1,256 +0,0 @@ -createOptionsResolver(); - $datetimeNormalizer = function (Options $resolver, \DateTimeInterface $value) { - return $value->format('c'); - }; - - $resolver->setDefined('search'); - $resolver->setDefined('username'); - $resolver->setDefined('external') - ->setAllowedTypes('external', 'bool') - ; - $resolver->setDefined('extern_uid'); - $resolver->setDefined('provider'); - $resolver->setDefined('created_before') - ->setAllowedTypes('created_before', \DateTimeInterface::class) - ->setNormalizer('created_before', $datetimeNormalizer) - ; - $resolver->setDefined('created_after') - ->setAllowedTypes('created_after', \DateTimeInterface::class) - ->setNormalizer('created_after', $datetimeNormalizer) - ; - $resolver->setDefined('active') - ->setAllowedTypes('active', 'bool') - ->setAllowedValues('active', true) - ; - $resolver->setDefined('blocked') - ->setAllowedTypes('blocked', 'bool') - ->setAllowedValues('blocked', true) - ; - - - return $this->get('users', $resolver->resolve($parameters)); - } - - /** - * @param int $id - * @return mixed - */ - public function show($id) - { - return $this->get('users/'.$this->encodePath($id)); - } - - /** - * @return mixed - */ - public function user() - { - return $this->get('user'); - } - - /** - * @param string $email - * @param string $password - * @param array $params - * @return mixed - */ - public function create($email, $password, array $params = array()) - { - $params['email'] = $email; - $params['password'] = $password; - - return $this->post('users', $params); - } - - /** - * @param int $id - * @param array $params - * @return mixed - */ - public function update($id, array $params) - { - return $this->put('users/'.$this->encodePath($id), $params); - } - - /** - * @param int $id - * @return mixed - */ - public function remove($id) - { - return $this->delete('users/'.$this->encodePath($id)); - } - - /** - * @param int $id - * @return mixed - */ - public function block($id) - { - return $this->post('users/'.$this->encodePath($id).'/block'); - } - - /** - * @param int $id - * @return mixed - */ - public function unblock($id) - { - return $this->post('users/'.$this->encodePath($id).'/unblock'); - } - - /** - * @param string $emailOrUsername - * @param string $password - * @return mixed - */ - public function session($emailOrUsername, $password) - { - return $this->post('session', array( - 'login' => $emailOrUsername, - 'email' => $emailOrUsername, - 'password' => $password - )); - } - - /** - * @param string $email - * @param string $password - * @return mixed - */ - public function login($email, $password) - { - return $this->session($email, $password); - } - - /** - * @return mixed - */ - public function me() - { - return $this->get('user'); - } - - /** - * @return mixed - */ - public function keys() - { - return $this->get('user/keys'); - } - - /** - * @param int $id - * @return mixed - */ - public function key($id) - { - return $this->get('user/keys/'.$this->encodePath($id)); - } - - /** - * @param string $title - * @param string $key - * @return mixed - */ - public function createKey($title, $key) - { - return $this->post('user/keys', array( - 'title' => $title, - 'key' => $key - )); - } - - /** - * @param int $id - * @return mixed - */ - public function removeKey($id) - { - return $this->delete('user/keys/'.$this->encodePath($id)); - } - - /** - * @param int $user_id - * @return mixed - */ - public function userKeys($user_id) - { - return $this->get('users/'.$this->encodePath($user_id).'/keys'); - } - - /* - * @param int $user_id - * @param int $key_id - * @return mixed - */ - public function userKey($user_id, $key_id) - { - return $this->get('users/'.$this->encodePath($user_id).'/keys/'.$this->encodePath($key_id)); - } - - /** - * @param int $user_id - * @param string $title - * @param string $key - * @return mixed - */ - public function createKeyForUser($user_id, $title, $key) - { - return $this->post('users/'.$this->encodePath($user_id).'/keys', array( - 'title' => $title, - 'key' => $key - )); - } - - /** - * @param int $user_id - * @param int $key_id - * @return mixed - */ - public function removeUserKey($user_id, $key_id) - { - return $this->delete('users/'.$this->encodePath($user_id).'/keys/'.$this->encodePath($key_id)); - } - - /** - * @return mixed - */ - public function emails() - { - return $this->get('user/emails'); - } - - /** - * @param $id - * @return mixed - */ - public function email($id) - { - return $this->get('user/emails/'.$this->encodePath($id)); - } -} diff --git a/lib/Gitlab/Api/Version.php b/lib/Gitlab/Api/Version.php deleted file mode 100644 index 59a58104b..000000000 --- a/lib/Gitlab/Api/Version.php +++ /dev/null @@ -1,9 +0,0 @@ -get('version'); - } -} diff --git a/lib/Gitlab/Client.php b/lib/Gitlab/Client.php deleted file mode 100644 index a8d593571..000000000 --- a/lib/Gitlab/Client.php +++ /dev/null @@ -1,378 +0,0 @@ - - * - * @property-read \Gitlab\Api\Groups $groups - * @property-read \Gitlab\Api\Issues $issues - * @property-read \Gitlab\Api\Jobs $jobs - * @property-read \Gitlab\Api\MergeRequests $merge_requests - * @property-read \Gitlab\Api\MergeRequests $mr - * @property-read \Gitlab\Api\Milestones $milestones - * @property-read \Gitlab\Api\Milestones $ms - * @property-read \Gitlab\Api\ProjectNamespaces $namespaces - * @property-read \Gitlab\Api\ProjectNamespaces $ns - * @property-read \Gitlab\Api\Projects $projects - * @property-read \Gitlab\Api\Repositories $repositories - * @property-read \Gitlab\Api\Repositories $repo - * @property-read \Gitlab\Api\Snippets $snippets - * @property-read \Gitlab\Api\SystemHooks $hooks - * @property-read \Gitlab\Api\SystemHooks $system_hooks - * @property-read \Gitlab\Api\Users $users - * @property-read \Gitlab\Api\Keys $keys - * @property-read \Gitlab\Api\Tags $tags - * @property-read \Gitlab\Api\Version $version - */ -class Client -{ - /** - * Constant for authentication method. Indicates the default, but deprecated - * login with username and token in URL. - */ - const AUTH_URL_TOKEN = 'url_token'; - - /** - * Constant for authentication method. Indicates the new login method with - * with username and token via HTTP Authentication. - */ - const AUTH_HTTP_TOKEN = 'http_token'; - - /** - * Constant for authentication method. Indicates the OAuth method with a key - * obtain using Gitlab's OAuth provider. - */ - const AUTH_OAUTH_TOKEN = 'oauth_token'; - - /** - * @var History - */ - private $responseHistory; - - /** - * @var Builder - */ - private $httpClientBuilder; - - /** - * Instantiate a new Gitlab client - * - * @param Builder $httpClientBuilder - */ - public function __construct(Builder $httpClientBuilder = null) - { - $this->responseHistory = new History(); - $this->httpClientBuilder = $httpClientBuilder ?: new Builder(); - - $this->httpClientBuilder->addPlugin(new GitlabExceptionThrower()); - $this->httpClientBuilder->addPlugin(new HistoryPlugin($this->responseHistory)); - $this->httpClientBuilder->addPlugin(new ApiVersion()); - $this->httpClientBuilder->addPlugin(new HeaderDefaultsPlugin([ - 'User-Agent' => 'php-gitlab-api (http://github.com/m4tthumphrey/php-gitlab-api)', - ])); - - $this->setUrl('https://gitlab.com'); - } - - /** - * Create a Gitlab\Client using an url. - * - * @param string $url - * - * @return Client - */ - public static function create($url) - { - $client = new self(); - $client->setUrl($url); - - return $client; - } - - /** - * Create a Gitlab\Client using an HttpClient. - * - * @param HttpClient $httpClient - * - * @return Client - */ - public static function createWithHttpClient(HttpClient $httpClient) - { - $builder = new Builder($httpClient); - - return new self($builder); - } - - /** - * @return Api\DeployKeys - */ - public function deployKeys() - { - return new Api\DeployKeys($this); - } - - /** - * @return Api\Groups - */ - public function groups() - { - return new Api\Groups($this); - } - - /** - * @return Api\Issues - */ - public function issues() - { - return new Api\Issues($this); - } - - /** - * @return Api\IssueBoards - */ - public function issueBoards() - { - return new Api\IssueBoards($this); - } - - /** - * @return Api\Jobs - */ - public function jobs() - { - return new Api\Jobs($this); - } - - /** - * @return Api\MergeRequests - */ - public function mergeRequests() - { - return new Api\MergeRequests($this); - } - - /** - * @return Api\Milestones - */ - public function milestones() - { - return new Api\Milestones($this); - } - - /** - * @return Api\ProjectNamespaces - */ - public function namespaces() - { - return new Api\ProjectNamespaces($this); - } - - /** - * @return Api\Projects - */ - public function projects() - { - return new Api\Projects($this); - } - - /** - * @return Api\Repositories - */ - public function repositories() - { - return new Api\Repositories($this); - } - - /** - * @return Api\RepositoryFiles - */ - public function repositoryFiles() - { - return new Api\RepositoryFiles($this); - } - - /** - * @return Api\Snippets - */ - public function snippets() - { - return new Api\Snippets($this); - } - - /** - * @return Api\SystemHooks - */ - public function systemHooks() - { - return new Api\SystemHooks($this); - } - - /** - * @return Api\Users - */ - public function users() - { - return new Api\Users($this); - } - - /** - * @return Api\Keys - */ - public function keys() - { - return new Api\Keys($this); - } - - /** - * @return Api\Tags - */ - public function tags() - { - return new Api\Tags($this); - } - - /** - * @return Api\Version - */ - public function version() - { - return new Api\Version($this); - } - - /** - * @param string $name - * - * @return AbstractApi|mixed - * @throws InvalidArgumentException - */ - public function api($name) - { - switch ($name) { - - case 'deploy_keys': - return $this->deployKeys(); - - case 'groups': - return $this->groups(); - - case 'issues': - return $this->issues(); - - case 'board': - case 'issue_boards': - return $this->issueBoards(); - case 'jobs': - return $this->jobs(); - - case 'mr': - case 'merge_requests': - return $this->mergeRequests(); - - case 'milestones': - case 'ms': - return $this->milestones(); - - case 'namespaces': - case 'ns': - return $this->namespaces(); - - case 'projects': - return $this->projects(); - - case 'repo': - case 'repositories': - return $this->repositories(); - - case 'snippets': - return $this->snippets(); - - case 'hooks': - case 'system_hooks': - return $this->systemHooks(); - - case 'users': - return $this->users(); - - case 'keys': - return $this->keys(); - - case 'tags': - return $this->tags(); - - case 'version': - return $this->version(); - - default: - throw new InvalidArgumentException('Invalid endpoint: "'.$name.'"'); - } - } - - /** - * Authenticate a user for all next requests - * - * @param string $token Gitlab private token - * @param string $authMethod One of the AUTH_* class constants - * @param string $sudo - * @return $this - */ - public function authenticate($token, $authMethod = self::AUTH_URL_TOKEN, $sudo = null) - { - $this->httpClientBuilder->removePlugin(Authentication::class); - $this->httpClientBuilder->addPlugin(new Authentication($authMethod, $token, $sudo)); - - return $this; - } - - /** - * @param string $url - * - * @return $this - */ - public function setUrl($url) - { - $this->httpClientBuilder->removePlugin(AddHostPlugin::class); - $this->httpClientBuilder->addPlugin(new AddHostPlugin(UriFactoryDiscovery::find()->createUri($url))); - - return $this; - } - - /** - * @param string $api - * @return AbstractApi - */ - public function __get($api) - { - return $this->api($api); - } - - /** - * @return HttpMethodsClient - */ - public function getHttpClient() - { - return $this->httpClientBuilder->getHttpClient(); - } - - /** - * @return History - */ - public function getResponseHistory() - { - return $this->responseHistory; - } -} diff --git a/lib/Gitlab/Exception/ErrorException.php b/lib/Gitlab/Exception/ErrorException.php deleted file mode 100644 index 4bca15dc2..000000000 --- a/lib/Gitlab/Exception/ErrorException.php +++ /dev/null @@ -1,10 +0,0 @@ - - */ -class ErrorException extends \ErrorException implements ExceptionInterface -{ -} diff --git a/lib/Gitlab/Exception/ExceptionInterface.php b/lib/Gitlab/Exception/ExceptionInterface.php deleted file mode 100644 index 81403dcf8..000000000 --- a/lib/Gitlab/Exception/ExceptionInterface.php +++ /dev/null @@ -1,12 +0,0 @@ - - */ -interface ExceptionInterface extends Exception -{ -} diff --git a/lib/Gitlab/Exception/InvalidArgumentException.php b/lib/Gitlab/Exception/InvalidArgumentException.php deleted file mode 100644 index f430ab9e5..000000000 --- a/lib/Gitlab/Exception/InvalidArgumentException.php +++ /dev/null @@ -1,10 +0,0 @@ - - */ -class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface -{ -} diff --git a/lib/Gitlab/Exception/MissingArgumentException.php b/lib/Gitlab/Exception/MissingArgumentException.php deleted file mode 100644 index 93a5451e5..000000000 --- a/lib/Gitlab/Exception/MissingArgumentException.php +++ /dev/null @@ -1,18 +0,0 @@ - - */ -class MissingArgumentException extends ErrorException implements ExceptionInterface -{ - public function __construct($required, $code = 0, $previous = null) - { - if (is_string($required)) { - $required = array($required); - } - - parent::__construct(sprintf('One or more of required ("%s") parameters is missing!', implode('", "', $required)), $code, $previous); - } -} diff --git a/lib/Gitlab/Exception/RuntimeException.php b/lib/Gitlab/Exception/RuntimeException.php deleted file mode 100644 index 0a12b500c..000000000 --- a/lib/Gitlab/Exception/RuntimeException.php +++ /dev/null @@ -1,10 +0,0 @@ - - */ -class RuntimeException extends \RuntimeException implements ExceptionInterface -{ -} diff --git a/lib/Gitlab/Exception/ValidationFailedException.php b/lib/Gitlab/Exception/ValidationFailedException.php deleted file mode 100644 index 066b9f855..000000000 --- a/lib/Gitlab/Exception/ValidationFailedException.php +++ /dev/null @@ -1,10 +0,0 @@ - - */ -class ValidationFailedException extends ErrorException implements ExceptionInterface -{ -} diff --git a/lib/Gitlab/HttpClient/Builder.php b/lib/Gitlab/HttpClient/Builder.php deleted file mode 100644 index 8c6f586eb..000000000 --- a/lib/Gitlab/HttpClient/Builder.php +++ /dev/null @@ -1,118 +0,0 @@ - - */ -class Builder -{ - /** - * The object that sends HTTP messages. - * - * @var HttpClient - */ - private $httpClient; - - /** - * A HTTP client with all our plugins. - * - * @var HttpMethodsClient - */ - private $pluginClient; - - /** - * @var MessageFactory - */ - private $requestFactory; - - /** - * @var StreamFactory - */ - private $streamFactory; - - /** - * True if we should create a new Plugin client at next request. - * - * @var bool - */ - private $httpClientModified = true; - - /** - * @var Plugin[] - */ - private $plugins = []; - - /** - * @param HttpClient $httpClient - * @param RequestFactory $requestFactory - * @param StreamFactory $streamFactory - */ - public function __construct( - HttpClient $httpClient = null, - RequestFactory $requestFactory = null, - StreamFactory $streamFactory = null - ) { - $this->httpClient = $httpClient ?: HttpClientDiscovery::find(); - $this->requestFactory = $requestFactory ?: MessageFactoryDiscovery::find(); - $this->streamFactory = $streamFactory ?: StreamFactoryDiscovery::find(); - } - - /** - * @return HttpMethodsClient - */ - public function getHttpClient() - { - if ($this->httpClientModified) { - $this->httpClientModified = false; - - $this->pluginClient = new HttpMethodsClient( - (new PluginClientFactory())->createClient($this->httpClient, $this->plugins), - $this->requestFactory - ); - } - - return $this->pluginClient; - } - - /** - * Add a new plugin to the end of the plugin chain. - * - * @param Plugin $plugin - */ - public function addPlugin(Plugin $plugin) - { - $this->plugins[] = $plugin; - $this->httpClientModified = true; - } - - /** - * Remove a plugin by its fully qualified class name (FQCN). - * - * @param string $fqcn - */ - public function removePlugin($fqcn) - { - foreach ($this->plugins as $idx => $plugin) { - if ($plugin instanceof $fqcn) { - unset($this->plugins[$idx]); - $this->httpClientModified = true; - } - } - } -} diff --git a/lib/Gitlab/HttpClient/Message/QueryStringBuilder.php b/lib/Gitlab/HttpClient/Message/QueryStringBuilder.php deleted file mode 100644 index 03e16a326..000000000 --- a/lib/Gitlab/HttpClient/Message/QueryStringBuilder.php +++ /dev/null @@ -1,76 +0,0 @@ -getBody()->__toString(); - if (strpos($response->getHeaderLine('Content-Type'), 'application/json') === 0) { - $content = json_decode($body, true); - if (JSON_ERROR_NONE === json_last_error()) { - return $content; - } - } - - return $body; - } - - /** - * Extract pagination URIs from Link header. - * - * @param ResponseInterface $response - * - * @return array|null - */ - public static function getPagination(ResponseInterface $response) - { - if (!$response->hasHeader('Link')) { - return null; - } - - $header = self::getHeader($response, 'Link'); - $pagination = array(); - foreach (explode(',', $header) as $link) { - preg_match('/<(.*)>; rel="(.*)"/i', trim($link, ','), $match); - - if (3 === count($match)) { - $pagination[$match[2]] = $match[1]; - } - } - - return $pagination; - } - - - /** - * Get the value for a single header. - * - * @param ResponseInterface $response - * @param string $name - * - * @return string|null - */ - private static function getHeader(ResponseInterface $response, $name) - { - $headers = $response->getHeader($name); - - return array_shift($headers); - } -} diff --git a/lib/Gitlab/HttpClient/Plugin/ApiVersion.php b/lib/Gitlab/HttpClient/Plugin/ApiVersion.php deleted file mode 100644 index 46b20e402..000000000 --- a/lib/Gitlab/HttpClient/Plugin/ApiVersion.php +++ /dev/null @@ -1,29 +0,0 @@ - - */ -class ApiVersion implements Plugin -{ - /** - * {@inheritdoc} - */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) - { - $uri = $request->getUri(); - - if (substr($uri->getPath(), 0, 8) !== '/api/v4/') { - $request = $request->withUri($uri->withPath('/api/v4/'.$uri->getPath())); - } - - return $next($request); - } -} diff --git a/lib/Gitlab/HttpClient/Plugin/Authentication.php b/lib/Gitlab/HttpClient/Plugin/Authentication.php deleted file mode 100644 index aac0acc4a..000000000 --- a/lib/Gitlab/HttpClient/Plugin/Authentication.php +++ /dev/null @@ -1,86 +0,0 @@ - - * @author Fabien Bourigault - */ -class Authentication implements Plugin -{ - /** - * @var string - */ - private $method; - - /** - * @var string - */ - private $token; - - /** - * @var string|null - */ - private $sudo; - - /** - * @param string $method - * @param string $token - * @param string|null $sudo - */ - public function __construct($method, $token, $sudo = null) - { - $this->method = $method; - $this->token = $token; - $this->sudo = $sudo; - } - - /** - * {@inheritdoc} - */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) - { - switch ($this->method) { - case Client::AUTH_HTTP_TOKEN: - $request = $request->withHeader('PRIVATE-TOKEN', $this->token); - if (!is_null($this->sudo)) { - $request = $request->withHeader('SUDO', $this->sudo); - } - break; - - case Client::AUTH_URL_TOKEN: - $uri = $request->getUri(); - $query = $uri->getQuery(); - - $parameters = [ - 'private_token' => $this->token, - ]; - - if (!is_null($this->sudo)) { - $parameters['sudo'] = $this->sudo; - } - - $query .= empty($query) ? '' : '&'; - $query .= utf8_encode(http_build_query($parameters, '', '&')); - - $uri = $uri->withQuery($query); - $request = $request->withUri($uri); - break; - - case Client::AUTH_OAUTH_TOKEN: - $request = $request->withHeader('Authorization', 'Bearer '.$this->token); - if (!is_null($this->sudo)) { - $request = $request->withHeader('SUDO', $this->sudo); - } - break; - } - - return $next($request); - } -} diff --git a/lib/Gitlab/HttpClient/Plugin/GitlabExceptionThrower.php b/lib/Gitlab/HttpClient/Plugin/GitlabExceptionThrower.php deleted file mode 100644 index bb61e3121..000000000 --- a/lib/Gitlab/HttpClient/Plugin/GitlabExceptionThrower.php +++ /dev/null @@ -1,86 +0,0 @@ - - * @author Fabien Bourigault - */ -class GitlabExceptionThrower implements Plugin -{ - /** - * {@inheritdoc} - */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) - { - return $next($request)->then(function (ResponseInterface $response) { - if ($response->getStatusCode() >= 400 && $response->getStatusCode() < 600) { - $content = ResponseMediator::getContent($response); - if (is_array($content) && isset($content['message'])) { - if (400 == $response->getStatusCode()) { - $message = $this->parseMessage($content['message']); - - throw new ErrorException($message, 400); - } - } - - $errorMessage = null; - if (isset($content['error'])) { - $errorMessage = $content['error']; - if (is_array($content['error'])) { - $errorMessage = implode("\n", $content['error']); - } - } elseif (isset($content['message'])) { - $errorMessage = $this->parseMessage($content['message']); - } else { - $errorMessage = $content; - } - - throw new RuntimeException($errorMessage, $response->getStatusCode()); - } - - return $response; - }); - } - - /** - * @param mixed $message - * - * @return string - */ - private function parseMessage($message) - { - $string = $message; - - if (is_array($message)) { - $format = '"%s" %s'; - $errors = array(); - - foreach ($message as $field => $messages) { - if (is_array($messages)) { - $messages = array_unique($messages); - foreach ($messages as $error) { - $errors[] = sprintf($format, $field, $error); - } - } elseif (is_integer($field)) { - $errors[] = $messages; - } else { - $errors[] = sprintf($format, $field, $messages); - } - } - - $string = implode(', ', $errors); - } - - return $string; - } -} diff --git a/lib/Gitlab/HttpClient/Plugin/History.php b/lib/Gitlab/HttpClient/Plugin/History.php deleted file mode 100644 index 8412356fd..000000000 --- a/lib/Gitlab/HttpClient/Plugin/History.php +++ /dev/null @@ -1,44 +0,0 @@ - - */ -class History implements Journal -{ - /** - * @var ResponseInterface - */ - private $lastResponse; - - /** - * @return ResponseInterface|null - */ - public function getLastResponse() - { - return $this->lastResponse; - } - - /** - * {@inheritdoc} - */ - public function addSuccess(RequestInterface $request, ResponseInterface $response) - { - $this->lastResponse = $response; - } - - /** - * {@inheritdoc} - */ - public function addFailure(RequestInterface $request, Exception $exception) - { - } -} diff --git a/lib/Gitlab/Model/AbstractModel.php b/lib/Gitlab/Model/AbstractModel.php deleted file mode 100644 index 7c11d4b21..000000000 --- a/lib/Gitlab/Model/AbstractModel.php +++ /dev/null @@ -1,129 +0,0 @@ -client; - } - - /** - * @param Client $client - * @return $this - */ - public function setClient(Client $client = null) - { - if (null !== $client) { - $this->client = $client; - } - - return $this; - } - - /** - * @param string $api - * @return AbstractApi|mixed - */ - public function api($api) - { - return $this->getClient()->api($api); - } - - /** - * @param array $data - * @return $this - */ - protected function hydrate(array $data = array()) - { - if (!empty($data)) { - foreach ($data as $field => $value) { - $this->setData($field, $value); - } - } - - return $this; - } - - /** - * @param string $field - * @param mixed $value - * @return $this - */ - protected function setData($field, $value) - { - if (in_array($field, static::$properties)) { - $this->data[$field] = $value; - } - - return $this; - } - - /** - * @return array - */ - public function getData() - { - return $this->data; - } - - /** - * @param string $property - * @param mixed $value - * @throws RuntimeException - */ - public function __set($property, $value) - { - throw new RuntimeException('Model properties are immutable'); - } - - /** - * @param string $property - * @return mixed - */ - public function __get($property) - { - if (!in_array($property, static::$properties)) { - throw new RuntimeException(sprintf( - 'Property "%s" does not exist for %s object', - $property, get_called_class() - )); - } - - if (isset($this->data[$property])) { - return $this->data[$property]; - } - - return null; - } - - /** - * @param string $property - * @return bool - */ - public function __isset($property) - { - return isset($this->data[$property]); - } -} diff --git a/lib/Gitlab/Model/Branch.php b/lib/Gitlab/Model/Branch.php deleted file mode 100644 index 8c7ef36b4..000000000 --- a/lib/Gitlab/Model/Branch.php +++ /dev/null @@ -1,146 +0,0 @@ -hydrate($data); - } - - /** - * @param Project $project - * @param string $name - * @param Client $client - */ - public function __construct(Project $project, $name = null, Client $client = null) - { - $this->setClient($client); - $this->setData('project', $project); - $this->setData('name', $name); - } - - /** - * @return Branch - */ - public function show() - { - $data = $this->client->repositories()->branch($this->project->id, $this->name); - - return static::fromArray($this->getClient(), $this->project, $data); - } - - /** - * @param bool $devPush - * @param bool $devMerge - * @return Branch - */ - public function protect($devPush = false, $devMerge = false) - { - $data = $this->client->repositories()->protectBranch($this->project->id, $this->name, $devPush, $devMerge); - - return static::fromArray($this->getClient(), $this->project, $data); - } - - /** - * @return Branch - */ - public function unprotect() - { - $data = $this->client->repositories()->unprotectBranch($this->project->id, $this->name); - - return static::fromArray($this->getClient(), $this->project, $data); - } - - /** - * @return bool - */ - public function delete() - { - $this->client->repositories()->deleteBranch($this->project->id, $this->name); - - return true; - } - - /** - * @param array $parameters - * - * @see Projects::commits for available parameters. - * - * @return Commit[] - */ - public function commits(array $parameters = []) - { - return $this->project->commits($parameters); - } - - /** - * @param string $file_path - * @param string $content - * @param string $commit_message - * @return File - */ - public function createFile($file_path, $content, $commit_message) - { - $data = $this->client->repositories()->createFile($this->project->id, $file_path, $content, $this->name, $commit_message); - - return File::fromArray($this->getClient(), $this->project, $data); - } - - /** - * @param string $file_path - * @param string $content - * @param string $commit_message - * @return File - */ - public function updateFile($file_path, $content, $commit_message) - { - $data = $this->client->repositories()->updateFile($this->project->id, $file_path, $content, $this->name, $commit_message); - - return File::fromArray($this->getClient(), $this->project, $data); - } - - /** - * @param string $file_path - * @param string $commit_message - * @return bool - */ - public function deleteFile($file_path, $commit_message) - { - $this->client->repositories()->deleteFile($this->project->id, $file_path, $this->name, $commit_message); - - return true; - } -} diff --git a/lib/Gitlab/Model/Commit.php b/lib/Gitlab/Model/Commit.php deleted file mode 100644 index 9b0073255..000000000 --- a/lib/Gitlab/Model/Commit.php +++ /dev/null @@ -1,86 +0,0 @@ -hydrate($data); - } - - /** - * @param Project $project - * @param int $id - * @param Client $client - */ - public function __construct(Project $project, $id = null, Client $client = null) - { - $this->setClient($client); - $this->setData('project', $project); - $this->setData('id', $id); - } -} diff --git a/lib/Gitlab/Model/CommitNote.php b/lib/Gitlab/Model/CommitNote.php deleted file mode 100644 index 4b455c7f1..000000000 --- a/lib/Gitlab/Model/CommitNote.php +++ /dev/null @@ -1,50 +0,0 @@ -hydrate($data); - } - - /** - * @param Client $client - */ - public function __construct(Client $client = null) - { - $this->setClient($client); - } -} diff --git a/lib/Gitlab/Model/Comparison.php b/lib/Gitlab/Model/Comparison.php deleted file mode 100644 index 27901829d..000000000 --- a/lib/Gitlab/Model/Comparison.php +++ /dev/null @@ -1,73 +0,0 @@ -hydrate($data); - } - - /** - * @param Project $project - * @param Client $client - */ - public function __construct(Project $project, Client $client = null) - { - $this->setClient($client); - $this->setData('project', $project); - } -} diff --git a/lib/Gitlab/Model/Contributor.php b/lib/Gitlab/Model/Contributor.php deleted file mode 100644 index 658da48bc..000000000 --- a/lib/Gitlab/Model/Contributor.php +++ /dev/null @@ -1,51 +0,0 @@ -hydrate($data); - } - - /** - * @param Project $project - * @param Client $client - */ - public function __construct(Project $project, Client $client = null) - { - $this->setClient($client); - $this->setData('project', $project); - } -} diff --git a/lib/Gitlab/Model/Diff.php b/lib/Gitlab/Model/Diff.php deleted file mode 100644 index 5fd95d5ac..000000000 --- a/lib/Gitlab/Model/Diff.php +++ /dev/null @@ -1,65 +0,0 @@ -hydrate($data); - } - - /** - * @param Project $project - * @param Client $client - */ - public function __construct(Project $project, Client $client = null) - { - $this->setClient($client); - $this->setData('project', $project); - } - - /** - * @return string - */ - public function __toString() - { - return $this->diff; - } -} diff --git a/lib/Gitlab/Model/Event.php b/lib/Gitlab/Model/Event.php deleted file mode 100644 index 83565e174..000000000 --- a/lib/Gitlab/Model/Event.php +++ /dev/null @@ -1,65 +0,0 @@ -hydrate($data); - } - - /** - * @param Project $project - * @param Client $client - */ - public function __construct(Project $project, Client $client = null) - { - $this->setClient($client); - $this->setData('project', $project); - } -} diff --git a/lib/Gitlab/Model/File.php b/lib/Gitlab/Model/File.php deleted file mode 100644 index b2edac127..000000000 --- a/lib/Gitlab/Model/File.php +++ /dev/null @@ -1,47 +0,0 @@ -hydrate($data); - } - - /** - * @param Project $project - * @param string $file_path - * @param Client $client - */ - public function __construct(Project $project, $file_path = null, Client $client = null) - { - $this->setClient($client); - $this->setData('project', $project); - $this->setData('file_path', $file_path); - } -} diff --git a/lib/Gitlab/Model/Group.php b/lib/Gitlab/Model/Group.php deleted file mode 100644 index 48923f182..000000000 --- a/lib/Gitlab/Model/Group.php +++ /dev/null @@ -1,138 +0,0 @@ -hydrate($data); - } - - /** - * @param Client $client - * @param string $name - * @param string $path - * @return Group - */ - public static function create(Client $client, $name, $path) - { - $data = $client->groups()->create($name, $path); - - return static::fromArray($client, $data); - } - - /** - * @param int $id - * @param Client $client - */ - public function __construct($id, Client $client = null) - { - $this->setClient($client); - $this->setData('id', $id); - } - - /** - * @return Group - */ - public function show() - { - $data = $this->client->groups()->show($this->id); - - return Group::fromArray($this->getClient(), $data); - } - - /** - * @param int $project_id - * @return Group - */ - public function transfer($project_id) - { - $data = $this->client->groups()->transfer($this->id, $project_id); - - return Group::fromArray($this->getClient(), $data); - } - - /** - * @return User[] - */ - public function members() - { - $data = $this->client->groups()->members($this->id); - - $members = array(); - foreach ($data as $member) { - $members[] = User::fromArray($this->getClient(), $member); - } - - return $members; - } - - /** - * @param int $user_id - * @param int $access_level - * @return User - */ - public function addMember($user_id, $access_level) - { - $data = $this->client->groups()->addMember($this->id, $user_id, $access_level); - - return User::fromArray($this->getClient(), $data); - } - - /** - * @param int $user_id - * @return bool - */ - public function removeMember($user_id) - { - $this->client->groups()->removeMember($this->id, $user_id); - - return true; - } - - /** - * @return Group - */ - public function projects() - { - $data = $this->client->groups()->projects($this->id); - - return Group::fromArray($this->getClient(), $data); - } -} diff --git a/lib/Gitlab/Model/Hook.php b/lib/Gitlab/Model/Hook.php deleted file mode 100644 index d276982e2..000000000 --- a/lib/Gitlab/Model/Hook.php +++ /dev/null @@ -1,76 +0,0 @@ -hydrate($data); - } - - /** - * @param Client $client - * @param string $url - * @return Hook - */ - public static function create(Client $client, $url) - { - $data = $client->systemHooks()->create($url); - - return static::fromArray($client, $data); - } - - /** - * @param int $id - * @param Client $client - */ - public function __construct($id, Client $client = null) - { - $this->setClient($client); - $this->setData('id', $id); - } - - /** - * @return bool - */ - public function test() - { - $this->client->systemHooks()->test($this->id); - - return true; - } - - /** - * @return bool - */ - public function delete() - { - $this->client->systemHooks()->remove($this->id); - - return true; - } -} diff --git a/lib/Gitlab/Model/Issue.php b/lib/Gitlab/Model/Issue.php deleted file mode 100644 index 5b4724ddd..000000000 --- a/lib/Gitlab/Model/Issue.php +++ /dev/null @@ -1,171 +0,0 @@ -hydrate($data); - } - - /** - * @param Project $project - * @param int $id - * @param Client $client - */ - public function __construct(Project $project, $id = null, Client $client = null) - { - $this->setClient($client); - $this->setData('project', $project); - $this->setData('id', $id); - } - - /** - * @return Issue - */ - public function show() - { - $data = $this->client->issues()->show($this->project->id, $this->iid); - - return static::fromArray($this->getClient(), $this->project, $data); - } - - /** - * @param array $params - * @return Issue - */ - public function update(array $params) - { - $data = $this->client->issues()->update($this->project->id, $this->iid, $params); - - return static::fromArray($this->getClient(), $this->project, $data); - } - - /** - * @param string $comment - * @return Issue - */ - public function close($comment = null) - { - if ($comment) { - $this->addComment($comment); - } - - return $this->update(array( - 'state_event' => 'close' - )); - } - - /** - * @return Issue - */ - public function open() - { - return $this->update(array( - 'state_event' => 'reopen' - )); - } - - /** - * @return Issue - */ - public function reopen() - { - return $this->open(); - } - - /** - * @param string $comment - * @return Note - */ - public function addComment($comment) - { - $data = $this->client->issues()->addComment($this->project->id, $this->iid, array( - 'body' => $comment - )); - - return Note::fromArray($this->getClient(), $this, $data); - } - - /** - * @return Note[] - */ - public function showComments() - { - $notes = array(); - $data = $this->client->issues()->showComments($this->project->id, $this->iid); - - foreach ($data as $note) { - $notes[] = Note::fromArray($this->getClient(), $this, $note); - } - - return $notes; - } - - /** - * @return bool - */ - public function isClosed() - { - if ($this->state == 'closed') { - return true; - } - - return false; - } -} diff --git a/lib/Gitlab/Model/Job.php b/lib/Gitlab/Model/Job.php deleted file mode 100644 index 586e3a311..000000000 --- a/lib/Gitlab/Model/Job.php +++ /dev/null @@ -1,83 +0,0 @@ -hydrate($data); - } - - /** - * @param Project $project - * @param int $id - * @param Client $client - */ - public function __construct(Project $project, $id = null, Client $client = null) - { - $this->setClient($client); - $this->setData('project', $project); - $this->setData('id', $id); - } -} diff --git a/lib/Gitlab/Model/Key.php b/lib/Gitlab/Model/Key.php deleted file mode 100644 index 5e6aebfa5..000000000 --- a/lib/Gitlab/Model/Key.php +++ /dev/null @@ -1,44 +0,0 @@ -hydrate($data); - } - - /** - * @param Client $client - */ - public function __construct(Client $client = null) - { - $this->setClient($client); - } -} diff --git a/lib/Gitlab/Model/Label.php b/lib/Gitlab/Model/Label.php deleted file mode 100644 index 5c8a20919..000000000 --- a/lib/Gitlab/Model/Label.php +++ /dev/null @@ -1,43 +0,0 @@ -hydrate($data); - } - - /** - * @param Project $project - * @param Client $client - */ - public function __construct(Project $project, Client $client = null) - { - $this->setClient($client); - $this->setData('project', $project); - } -} diff --git a/lib/Gitlab/Model/MergeRequest.php b/lib/Gitlab/Model/MergeRequest.php deleted file mode 100644 index 03251f3b0..000000000 --- a/lib/Gitlab/Model/MergeRequest.php +++ /dev/null @@ -1,227 +0,0 @@ -hydrate($data); - } - - /** - * @param Project $project - * @param int $iid - * @param Client $client - */ - public function __construct(Project $project, $iid = null, Client $client = null) - { - $this->setClient($client); - $this->setData('project', $project); - $this->setData('iid', $iid); - } - - /** - * @return MergeRequest - */ - public function show() - { - $data = $this->client->mergeRequests()->show($this->project->id, $this->iid); - - return static::fromArray($this->getClient(), $this->project, $data); - } - - /** - * @param array $params - * @return MergeRequest - */ - public function update(array $params) - { - $data = $this->client->mergeRequests()->update($this->project->id, $this->iid, $params); - - return static::fromArray($this->getClient(), $this->project, $data); - } - - /** - * @param string $comment - * @return MergeRequest - */ - public function close($comment = null) - { - if ($comment) { - $this->addComment($comment); - } - - return $this->update(array( - 'state_event' => 'close' - )); - } - - /** - * @return MergeRequest - */ - public function reopen() - { - return $this->update(array( - 'state_event' => 'reopen' - )); - } - - /** - * @return MergeRequest - */ - public function open() - { - return $this->reopen(); - } - - /** - * @param string $message - * @return MergeRequest - */ - public function merge($message = null) - { - $data = $this->client->mergeRequests()->merge($this->project->id, $this->iid, array( - 'merge_commit_message' => $message - )); - - return static::fromArray($this->getClient(), $this->project, $data); - } - - /** - * @return MergeRequest - */ - public function merged() - { - return $this->update(array( - 'state_event' => 'merge' - )); - } - - /** - * @param string $comment - * @return Note - */ - public function addComment($comment) - { - $data = $this->client->mergeRequests()->addComment($this->project->id, $this->iid, $comment); - - return Note::fromArray($this->getClient(), $this, $data); - } - - /** - * @return Note[] - */ - public function showComments() - { - $notes = array(); - $data = $this->client->mergeRequests()->showComments($this->project->id, $this->iid); - - foreach ($data as $note) { - $notes[] = Note::fromArray($this->getClient(), $this, $note); - } - - return $notes; - } - - /** - * @return bool - */ - public function isClosed() - { - if (in_array($this->state, array('closed', 'merged'))) { - return true; - } - - return false; - } - - /** - * @return MergeRequest - */ - public function changes() - { - $data = $this->client->mergeRequests()->changes($this->project->id, $this->iid); - - return static::fromArray($this->getClient(), $this->project, $data); - } -} diff --git a/lib/Gitlab/Model/Milestone.php b/lib/Gitlab/Model/Milestone.php deleted file mode 100644 index 8b07d38af..000000000 --- a/lib/Gitlab/Model/Milestone.php +++ /dev/null @@ -1,117 +0,0 @@ -hydrate($data); - } - - /** - * @param Project $project - * @param int $id - * @param Client $client - */ - public function __construct(Project $project, $id, Client $client = null) - { - $this->setClient($client); - $this->setData('id', $id); - $this->setData('project', $project); - } - - /** - * @return Milestone - */ - public function show() - { - $data = $this->client->milestones()->show($this->project->id, $this->id); - - return static::fromArray($this->getClient(), $this->project, $data); - } - - /** - * @param array $params - * @return Milestone - */ - public function update(array $params) - { - $data = $this->client->milestones()->update($this->project->id, $this->id, $params); - - return static::fromArray($this->getClient(), $this->project, $data); - } - - /** - * @return Milestone - */ - public function complete() - { - return $this->update(array('closed' => true)); - } - - /** - * @return Milestone - */ - public function incomplete() - { - return $this->update(array('closed' => false)); - } - - /** - * @return Issue[] - */ - public function issues() - { - $data = $this->client->milestones()->issues($this->project->id, $this->id); - - $issues = array(); - foreach ($data as $issue) { - $issues[] = Issue::fromArray($this->getClient(), $this->project, $issue); - } - - return $issues; - } -} diff --git a/lib/Gitlab/Model/Node.php b/lib/Gitlab/Model/Node.php deleted file mode 100644 index bb9451687..000000000 --- a/lib/Gitlab/Model/Node.php +++ /dev/null @@ -1,53 +0,0 @@ -hydrate($data); - } - - /** - * @param Project $project - * @param int $id - * @param Client $client - */ - public function __construct(Project $project, $id = null, Client $client) - { - $this->setClient($client); - $this->setData('project', $project); - $this->setData('id', $id); - } -} diff --git a/lib/Gitlab/Model/Note.php b/lib/Gitlab/Model/Note.php deleted file mode 100644 index c7767792d..000000000 --- a/lib/Gitlab/Model/Note.php +++ /dev/null @@ -1,61 +0,0 @@ -hydrate($data); - } - - /** - * @param Noteable $type - * @param Client $client - */ - public function __construct(Noteable $type, Client $client = null) - { - $this->setClient($client); - $this->setData('parent_type', get_class($type)); - $this->setData('parent', $type); - } -} diff --git a/lib/Gitlab/Model/Noteable.php b/lib/Gitlab/Model/Noteable.php deleted file mode 100644 index 11e2cb650..000000000 --- a/lib/Gitlab/Model/Noteable.php +++ /dev/null @@ -1,36 +0,0 @@ -hydrate($data); - } - - /** - * @param Project $project - * @param int $id - * @param Client $client - */ - public function __construct(Project $project, $id = null, Client $client = null) - { - $this->setClient($client); - $this->setData('project', $project); - $this->setData('id', $id); - } -} diff --git a/lib/Gitlab/Model/Project.php b/lib/Gitlab/Model/Project.php deleted file mode 100644 index e22df239b..000000000 --- a/lib/Gitlab/Model/Project.php +++ /dev/null @@ -1,1186 +0,0 @@ -setClient($client); - - if (isset($data['owner'])) { - $data['owner'] = User::fromArray($client, $data['owner']); - } - - if (isset($data['namespace']) && is_array($data['namespace'])) { - $data['namespace'] = ProjectNamespace::fromArray($client, $data['namespace']); - } - - return $project->hydrate($data); - } - - /** - * @param Client $client - * @param string $name - * @param array $params - * @return Project - */ - public static function create(Client $client, $name, array $params = array()) - { - $data = $client->projects()->create($name, $params); - - return static::fromArray($client, $data); - } - - /** - * @param int $user_id - * @param Client $client - * @param string $name - * @param array $params - * @return Project - */ - public static function createForUser($user_id, Client $client, $name, array $params = array()) - { - $data = $client->projects()->createForUser($user_id, $name, $params); - - return static::fromArray($client, $data); - } - /** - * @param int $id - * @param Client $client - */ - public function __construct($id = null, Client $client = null) - { - $this->setClient($client); - $this->setData('id', $id); - } - - /** - * @return Project - */ - public function show() - { - $data = $this->client->projects()->show($this->id); - - return static::fromArray($this->getClient(), $data); - } - - /** - * @param array $params - * @return Project - */ - public function update(array $params) - { - $data = $this->client->projects()->update($this->id, $params); - - return static::fromArray($this->getClient(), $data); - } - - /** - * @return Project - */ - public function archive() - { - $data = $this->client->projects()->archive($this->id); - - return static::fromArray($this->getClient(), $data); - } - - /** - * @return Project - */ - public function unarchive() - { - $data = $this->client->projects()->unarchive($this->id); - - return static::fromArray($this->getClient(), $data); - } - - /** - * @return bool - */ - public function remove() - { - $this->client->projects()->remove($this->id); - - return true; - } - - /** - * @param string $username_query - * @return User[] - */ - public function members($username_query = null) - { - $data = $this->client->projects()->members($this->id, $username_query); - - $members = array(); - foreach ($data as $member) { - $members[] = User::fromArray($this->getClient(), $member); - } - - return $members; - } - - /** - * @param int $user_id - * @return User - */ - public function member($user_id) - { - $data = $this->client->projects()->member($this->id, $user_id); - - return User::fromArray($this->getClient(), $data); - } - - /** - * @param int $user_id - * @param int $access_level - * @return User - */ - public function addMember($user_id, $access_level) - { - $data = $this->client->projects()->addMember($this->id, $user_id, $access_level); - - return User::fromArray($this->getClient(), $data); - } - - /** - * @param int $user_id - * @param int $access_level - * @return User - */ - public function saveMember($user_id, $access_level) - { - $data = $this->client->projects()->saveMember($this->id, $user_id, $access_level); - - return User::fromArray($this->getClient(), $data); - } - - /** - * @param int $user_id - * @return bool - */ - public function removeMember($user_id) - { - $this->client->projects()->removeMember($this->id, $user_id); - - return true; - } - - /** - * @param array $parameters - * - * @see Projects::hooks() for available parameters. - * - * @return ProjectHook[] - */ - public function hooks(array $parameters = []) - { - $data = $this->client->projects()->hooks($this->id, $parameters); - - $hooks = array(); - foreach ($data as $hook) { - $hooks[] = ProjectHook::fromArray($this->getClient(), $this, $hook); - } - - return $hooks; - } - - /** - * @param int $id - * @return ProjectHook - */ - public function hook($id) - { - $hook = new ProjectHook($this, $id, $this->getClient()); - - return $hook->show(); - } - - /** - * @param string $url - * @param array $events - * @return ProjectHook - */ - public function addHook($url, array $events = array()) - { - $data = $this->client->projects()->addHook($this->id, $url, $events); - - return ProjectHook::fromArray($this->getClient(), $this, $data); - } - - /** - * @param int $hook_id - * @param array $params - * @return mixed - */ - public function updateHook($hook_id, array $params) - { - $hook = new ProjectHook($this, $hook_id, $this->getClient()); - - return $hook->update($params); - } - - /** - * @param int $hook_id - * @return bool - */ - public function removeHook($hook_id) - { - $hook = new ProjectHook($this, $hook_id, $this->getClient()); - - return $hook->delete(); - } - - /** - * @return Key[] - */ - public function deployKeys() - { - $data = $this->client->projects()->deployKeys($this->id); - - $keys = array(); - foreach ($data as $key) { - $keys[] = Key::fromArray($this->getClient(), $key); - } - - return $keys; - } - - /** - * @param int $key_id - * @return Key - */ - public function deployKey($key_id) - { - $data = $this->client->projects()->deployKey($this->id, $key_id); - - return Key::fromArray($this->getClient(), $data); - } - - /** - * @param string $title - * @param string $key - * @param bool $canPush - * @return Key - */ - public function addDeployKey($title, $key, $canPush = false) - { - $data = $this->client->projects()->addDeployKey($this->id, $title, $key, $canPush); - - return Key::fromArray($this->getClient(), $data); - } - - /** - * @param string $key_id - * @return bool - */ - public function deleteDeployKey($key_id) - { - $this->client->projects()->deleteDeployKey($this->id, $key_id); - - return true; - } - - /** - * @param string $key_id - * @return bool - */ - public function enableDeployKey($key_id) - { - $this->client->projects()->enableDeployKey($this->id, $key_id); - - return true; - } - - /** - * @param string $name - * @param string $ref - * @return Branch - */ - public function createBranch($name, $ref) - { - $data = $this->client->repositories()->createBranch($this->id, $name, $ref); - - return Branch::fromArray($this->getClient(), $this, $data); - } - - /** - * @param string $name - * @return bool - */ - public function deleteBranch($name) - { - $this->client->repositories()->deleteBranch($this->id, $name); - - return true; - } - - /** - * @return Branch[] - */ - public function branches() - { - $data = $this->client->repositories()->branches($this->id); - - $branches = array(); - foreach ($data as $branch) { - $branches[] = Branch::fromArray($this->getClient(), $this, $branch); - } - - return $branches; - } - - /** - * @param string $branch_name - * @return Branch - */ - public function branch($branch_name) - { - $branch = new Branch($this, $branch_name); - $branch->setClient($this->getClient()); - - return $branch->show(); - } - - /** - * @param string $branch_name - * @param bool $devPush - * @param bool $devMerge - * @return Branch - */ - public function protectBranch($branch_name, $devPush = false, $devMerge = false) - { - $branch = new Branch($this, $branch_name); - $branch->setClient($this->getClient()); - - return $branch->protect($devPush, $devMerge); - } - - /** - * @param string $branch_name - * @return Branch - */ - public function unprotectBranch($branch_name) - { - $branch = new Branch($this, $branch_name); - $branch->setClient($this->getClient()); - - return $branch->unprotect(); - } - - /** - * @return Tag[] - */ - public function tags() - { - $data = $this->client->repositories()->tags($this->id); - - $tags = array(); - foreach ($data as $tag) { - $tags[] = Tag::fromArray($this->getClient(), $this, $tag); - } - - return $tags; - } - - /** - * @param array $parameters - * - * @see Repositories::commits() for available parameters. - * - * @return Commit[] - */ - public function commits(array $parameters = []) - { - $data = $this->client->repositories()->commits($this->id, $parameters); - - $commits = array(); - foreach ($data as $commit) { - $commits[] = Commit::fromArray($this->getClient(), $this, $commit); - } - - return $commits; - } - - /** - * @param string $sha - * @return Commit - */ - public function commit($sha) - { - $data = $this->client->repositories()->commit($this->id, $sha); - - return Commit::fromArray($this->getClient(), $this, $data); - } - - /** - * @param string $ref - * @param array $parameters - * - * @see Repositories::commitComments() for available parameters. - * - * @return Commit[] - */ - public function commitComments($ref, array $parameters = []) - { - $data = $this->client->repositories()->commitComments($this->id, $ref, $parameters); - - $comments = array(); - foreach ($data as $comment) { - $comments[] = CommitNote::fromArray($this->getClient(), $comment); - } - - return $comments; - } - - /** - * @param string $ref - * @param string $note - * @param array $params - * @return CommitNote - */ - public function createCommitComment($ref, $note, array $params = array()) - { - $data = $this->client->repositories()->createCommitComment($this->id, $ref, $note, $params); - - return CommitNote::fromArray($this->getClient(), $data); - } - - /** - * @param string $sha - * @return string - */ - public function diff($sha) - { - return $this->client->repositories()->diff($this->id, $sha); - } - - /** - * @param string $from - * @param string $to - * @return Comparison - */ - public function compare($from, $to) - { - $data = $this->client->repositories()->compare($this->id, $from, $to); - - return Comparison::fromArray($this->getClient(), $this, $data); - } - - /** - * @param array $params - * @return Node[] - */ - public function tree(array $params = array()) - { - $data = $this->client->repositories()->tree($this->id, $params); - - $tree = array(); - foreach ($data as $node) { - $tree[] = Node::fromArray($this->getClient(), $this, $node); - } - - return $tree; - } - - /** - * @param string $sha - * @param string $filepath - * @return string - */ - public function blob($sha, $filepath) - { - return $this->client->repositories()->blob($this->id, $sha, $filepath); - } - - /** - * @param $sha - * @param $filepath - * - * @return array - */ - public function getFile($sha, $filepath) - { - return $this->client->repositories()->getFile($this->id, $filepath, $sha); - } - - /** - * @param string $file_path - * @param string $content - * @param string $branch_name - * @param string $commit_message - * @param string $author_email - * @param string $author_name - * @return File - */ - public function createFile($file_path, $content, $branch_name, $commit_message, $author_email = null, $author_name = null) - { - $parameters = [ - 'file_path' => $file_path, - 'branch' => $branch_name, - 'content' => $content, - 'commit_message' => $commit_message, - ]; - - if ($author_email !== null) { - $parameters['author_email'] = $author_email; - } - - if ($author_name !== null) { - $parameters['author_name'] = $author_name; - } - - $data = $this->client->repositoryFiles()->createFile($this->id, $parameters); - - return File::fromArray($this->getClient(), $this, $data); - } - - /** - * @param string $file_path - * @param string $content - * @param string $branch_name - * @param string $commit_message - * @param string $author_email - * @param string $author_name - * @return File - */ - public function updateFile($file_path, $content, $branch_name, $commit_message, $author_email = null, $author_name = null) - { - $parameters = [ - 'file_path' => $file_path, - 'branch' => $branch_name, - 'content' => $content, - 'commit_message' => $commit_message, - ]; - - if ($author_email !== null) { - $parameters['author_email'] = $author_email; - } - - if ($author_name !== null) { - $parameters['author_name'] = $author_name; - } - - $data = $this->client->repositoryFiles()->updateFile($this->id, $parameters); - - return File::fromArray($this->getClient(), $this, $data); - } - - /** - * @param string $file_path - * @param string $branch_name - * @param string $commit_message - * @param string $author_email - * @param string $author_name - * @return bool - */ - public function deleteFile($file_path, $branch_name, $commit_message, $author_email = null, $author_name = null) - { - $parameters = [ - 'file_path' => $file_path, - 'branch' => $branch_name, - 'commit_message' => $commit_message, - ]; - - if ($author_email !== null) { - $parameters['author_email'] = $author_email; - } - - if ($author_name !== null) { - $parameters['author_name'] = $author_name; - } - - $this->client->repositoryFiles()->deleteFile($this->id, $parameters); - - return true; - } - - /** - * @param array $parameters - * - * @see Projects::events() for available parameters. - * - * @return Event[] - */ - public function events(array $parameters = []) - { - $data = $this->client->projects()->events($this->id, $parameters); - - $events = array(); - foreach ($data as $event) { - $events[] = Event::fromArray($this->getClient(), $this, $event); - } - - return $events; - } - - /** - * @param array $parameters - * - * @see MergeRequests::all() for available parameters. - * - * @return MergeRequest[] - */ - public function mergeRequests(array $parameters = []) - { - $data = $this->client->mergeRequests()->all($this->id, $parameters); - - $mrs = array(); - foreach ($data as $mr) { - $mrs[] = MergeRequest::fromArray($this->getClient(), $this, $mr); - } - - return $mrs; - } - - /** - * @param int $id - * @return MergeRequest - */ - public function mergeRequest($id) - { - $mr = new MergeRequest($this, $id, $this->getClient()); - - return $mr->show(); - } - - /** - * @param string $source - * @param string $target - * @param string $title - * @param int $assignee - * @param string $description - * @return MergeRequest - */ - public function createMergeRequest($source, $target, $title, $assignee = null, $description = null) - { - $data = $this->client->mergeRequests()->create($this->id, $source, $target, $title, $assignee, $this->id, $description); - - return MergeRequest::fromArray($this->getClient(), $this, $data); - } - - /** - * @param int $id - * @param array $params - * @return MergeRequest - */ - public function updateMergeRequest($id, array $params) - { - $mr = new MergeRequest($this, $id, $this->getClient()); - - return $mr->update($params); - } - - /** - * @param int $id - * @return MergeRequest - */ - public function closeMergeRequest($id) - { - $mr = new MergeRequest($this, $id, $this->getClient()); - - return $mr->close(); - } - - /** - * @param int $id - * @return MergeRequest - */ - public function openMergeRequest($id) - { - $mr = new MergeRequest($this, $id, $this->getClient()); - - return $mr->reopen(); - } - - /** - * @param int $id - * @return MergeRequest - */ - public function mergeMergeRequest($id) - { - $mr = new MergeRequest($this, $id, $this->getClient()); - - return $mr->merge(); - } - - /** - * @param array $parameters - * - * @see Issues::all() for available parameters. - * - * @return Issue[] - */ - public function issues(array $parameters = []) - { - $data = $this->client->issues()->all($this->id, $parameters); - - $issues = array(); - foreach ($data as $issue) { - $issues[] = Issue::fromArray($this->getClient(), $this, $issue); - } - - return $issues; - } - - /** - * @param string $title - * @param array $params - * @return Issue - */ - public function createIssue($title, array $params = array()) - { - $params['title'] = $title; - $data = $this->client->issues()->create($this->id, $params); - - return Issue::fromArray($this->getClient(), $this, $data); - } - - /** - * @param int $id - * @return Issue - */ - public function issue($id) - { - $issue = new Issue($this, $id, $this->getClient()); - - return $issue->show(); - } - - /** - * @param int $id - * @param array $params - * @return Issue - */ - public function updateIssue($id, array $params) - { - $issue = new Issue($this, $id, $this->getClient()); - - return $issue->update($params); - } - - /** - * @param int $id - * @param string $comment - * @return Issue - */ - public function closeIssue($id, $comment = null) - { - $issue = new Issue($this, $id, $this->getClient()); - - return $issue->close($comment); - } - - /** - * @param int $id - * @return Issue - */ - public function openIssue($id) - { - $issue = new Issue($this, $id, $this->getClient()); - - return $issue->open(); - } - - /** - * @param array $parameters - * - * @see Milestones::all() for available parameters. - * - * @return Milestone[] - */ - public function milestones(array $parameters = []) - { - $data = $this->client->milestones()->all($this->id, $parameters); - - $milestones = array(); - foreach ($data as $milestone) { - $milestones[] = Milestone::fromArray($this->getClient(), $this, $milestone); - } - - return $milestones; - } - - /** - * @param string $title - * @param array $params - * @return Milestone - */ - public function createMilestone($title, array $params = array()) - { - $params['title'] = $title; - $data = $this->client->milestones()->create($this->id, $params); - - return Milestone::fromArray($this->getClient(), $this, $data); - } - - /** - * @param int $id - * @return Milestone - */ - public function milestone($id) - { - $milestone = new Milestone($this, $id, $this->getClient()); - - return $milestone->show(); - } - - /** - * @param int $id - * @param array $params - * @return Milestone - */ - public function updateMilestone($id, array $params) - { - $milestone = new Milestone($this, $id, $this->getClient()); - - return $milestone->update($params); - } - - /** - * @param int $id - * @return Issue[] - */ - public function milestoneIssues($id) - { - $milestone = new Milestone($this, $id, $this->getClient()); - - return $milestone->issues(); - } - - /** - * @return Snippet[] - */ - public function snippets() - { - $data = $this->client->snippets()->all($this->id); - - $snippets = array(); - foreach ($data as $snippet) { - $snippets[] = Snippet::fromArray($this->getClient(), $this, $snippet); - } - - return $snippets; - } - - /** - * @param string $title - * @param string $filename - * @param string $code - * @return Snippet - */ - public function createSnippet($title, $filename, $code) - { - $data = $this->client->snippets()->create($this->id, $title, $filename, $code); - - return Snippet::fromArray($this->getClient(), $this, $data); - } - - /** - * @param int $id - * @return Snippet - */ - public function snippet($id) - { - $snippet = new Snippet($this, $id, $this->getClient()); - - return $snippet->show(); - } - - /** - * @param int $id - * @return string - */ - public function snippetContent($id) - { - $snippet = new Snippet($this, $id, $this->getClient()); - - return $snippet->content(); - } - - /** - * @param int $id - * @param array $params - * @return Snippet - */ - public function updateSnippet($id, array $params) - { - $snippet = new Snippet($this, $id, $this->getClient()); - - return $snippet->update($params); - } - - /** - * @param int $id - * @return bool - */ - public function removeSnippet($id) - { - $snippet = new Snippet($this, $id, $this->getClient()); - - return $snippet->remove(); - } - - /** - * @param int $group_id - * @return Group - */ - public function transfer($group_id) - { - $group = new Group($group_id, $this->getClient()); - - return $group->transfer($this->id); - } - - /** - * @param int $id - * @return Project - */ - public function forkTo($id) - { - $data = $this->client->projects()->createForkRelation($id, $this->id); - - return Project::fromArray($this->getClient(), $data); - } - - /** - * @param int $id - * @return Project - */ - public function forkFrom($id) - { - return $this->createForkRelation($id); - } - - /** - * @param int $id - * @return Project - */ - public function createForkRelation($id) - { - $data = $this->client->projects()->createForkRelation($this->id, $id); - - return Project::fromArray($this->getClient(), $data); - } - - /** - * @return bool - */ - public function removeForkRelation() - { - $this->client->projects()->removeForkRelation($this->id); - - return true; - } - - /** - * @param string $service_name - * @param array $params - * @return bool - */ - public function setService($service_name, array $params = array()) - { - $this->client->projects()->setService($this->id, $service_name, $params); - - return true; - } - - /** - * @param string $service_name - * @return bool - */ - public function removeService($service_name) - { - $this->client->projects()->removeService($this->id, $service_name); - - return true; - } - - /** - * @return Label[] - */ - public function labels() - { - $data = $this->client->projects()->labels($this->id); - - $labels = array(); - foreach ($data as $label) { - $labels[] = Label::fromArray($this->getClient(), $this, $label); - } - - return $labels; - } - - /** - * @param string $name - * @param string $color - * @return Label - */ - public function addLabel($name, $color) - { - $data = $this->client->projects()->addLabel($this->id, array( - 'name' => $name, - 'color' => $color - )); - - return Label::fromArray($this->getClient(), $this, $data); - } - - /** - * @param string $name - * @param array $params - * @return Label - */ - public function updateLabel($name, array $params) - { - if (isset($params['name'])) { - $params['new_name'] = $params['name']; - } - - $params['name'] = $name; - - $data = $this->client->projects()->updateLabel($this->id, $params); - - return Label::fromArray($this->getClient(), $this, $data); - } - - /** - * @param string $name - * @return bool - */ - public function removeLabel($name) - { - $this->client->projects()->removeLabel($this->id, $name); - - return true; - } - - /** - * @return array - */ - public function contributors() - { - $data = $this->client->repositories()->contributors($this->id); - - $contributors = array(); - foreach ($data as $contributor) { - $contributors[] = Contributor::fromArray($this->getClient(), $this, $contributor); - } - - return $contributors; - } - - /** - * @param array $scopes - * @return Job[] - */ - public function jobs(array $scopes = []) - { - $data = $this->client->jobs()->all($this->id, $scopes); - - $jobs = array(); - foreach ($data as $job) { - $jobs[] = Job::fromArray($this->getClient(), $this, $job); - } - - return $jobs; - } - - /** - * @param int $pipeline_id - * @param array $scopes - * @return Job[] - */ - public function pipelineJobs($pipeline_id, array $scopes = []) - { - $data = $this->client->jobs()->pipelineJobs($this->id, $pipeline_id, $scopes); - - $jobs = array(); - foreach ($data as $job) { - $jobs[] = Job::fromArray($this->getClient(), $this, $job); - } - - return $jobs; - } - - /** - * @param int $job_id - * @return Job - */ - public function job($job_id) - { - $data = $this->client->jobs()->show($this->id, $job_id); - - return Job::fromArray($this->getClient(), $this, $data); - } -} diff --git a/lib/Gitlab/Model/ProjectHook.php b/lib/Gitlab/Model/ProjectHook.php deleted file mode 100644 index 1789ca68b..000000000 --- a/lib/Gitlab/Model/ProjectHook.php +++ /dev/null @@ -1,102 +0,0 @@ -hydrate($data); - } - - /** - * @param Project $project - * @param int $id - * @param Client $client - */ - public function __construct(Project $project, $id, Client $client = null) - { - $this->setClient($client); - $this->setData('project', $project); - $this->setData('id', $id); - } - - /** - * @return ProjectHook - */ - public function show() - { - $data = $this->client->projects()->hook($this->project->id, $this->id); - - return static::fromArray($this->getClient(), $this->project, $data); - } - - /** - * @return bool - */ - public function delete() - { - $this->client->projects()->removeHook($this->project->id, $this->id); - - return true; - } - - /** - * @return bool - */ - public function remove() - { - return $this->delete(); - } - - /** - * @param array $params - * @return ProjectHook - */ - public function update(array $params) - { - $data = $this->client->projects()->updateHook($this->project->id, $this->id, $params); - - return static::fromArray($this->getClient(), $this->project, $data); - } -} diff --git a/lib/Gitlab/Model/ProjectNamespace.php b/lib/Gitlab/Model/ProjectNamespace.php deleted file mode 100644 index 9e3aee4ed..000000000 --- a/lib/Gitlab/Model/ProjectNamespace.php +++ /dev/null @@ -1,55 +0,0 @@ -setClient($client); - - return $project->hydrate($data); - } - - /** - * @param int $id - * @param Client $client - */ - public function __construct($id = null, Client $client = null) - { - $this->setClient($client); - $this->setData('id', $id); - } -} diff --git a/lib/Gitlab/Model/Session.php b/lib/Gitlab/Model/Session.php deleted file mode 100644 index 7cf4cfe79..000000000 --- a/lib/Gitlab/Model/Session.php +++ /dev/null @@ -1,70 +0,0 @@ -hydrate($data); - } - - /** - * @param Client $client - */ - public function __construct(Client $client = null) - { - $this->setClient($client); - } - - /** - * @return User - */ - public function me() - { - $data = $this->client->users()->user(); - - return User::fromArray($this->getClient(), $data); - } - - /** - * @param string $email - * @param string $password - * @return Session - */ - public function login($email, $password) - { - $data = $this->client->users()->session($email, $password); - - return $this->hydrate($data); - } -} diff --git a/lib/Gitlab/Model/Snippet.php b/lib/Gitlab/Model/Snippet.php deleted file mode 100644 index 1f021bcb4..000000000 --- a/lib/Gitlab/Model/Snippet.php +++ /dev/null @@ -1,98 +0,0 @@ -hydrate($data); - } - - /** - * @param Project $project - * @param int $id - * @param Client $client - */ - public function __construct(Project $project, $id = null, Client $client = null) - { - $this->setClient($client); - $this->setData('project', $project); - $this->setData('id', $id); - } - - /** - * @return Snippet - */ - public function show() - { - $data = $this->client->snippets()->show($this->project->id, $this->id); - - return static::fromArray($this->getClient(), $this->project, $data); - } - - /** - * @param array $params - * @return Snippet - */ - public function update(array $params) - { - $data = $this->client->snippets()->update($this->project->id, $this->id, $params); - - return static::fromArray($this->getClient(), $this->project, $data); - } - - /** - * @return string - */ - public function content() - { - return $this->client->snippets()->content($this->project->id, $this->id); - } - - /** - * @return bool - */ - public function remove() - { - $this->client->snippets()->remove($this->project->id, $this->id); - - return true; - } -} diff --git a/lib/Gitlab/Model/Tag.php b/lib/Gitlab/Model/Tag.php deleted file mode 100644 index 6028b3da5..000000000 --- a/lib/Gitlab/Model/Tag.php +++ /dev/null @@ -1,53 +0,0 @@ -hydrate($data); - } - - /** - * @param Project $project - * @param string $name - * @param Client $client - */ - public function __construct(Project $project, $name = null, Client $client = null) - { - $this->setClient($client); - $this->setData('project', $project); - $this->setData('name', $name); - } -} diff --git a/lib/Gitlab/Model/User.php b/lib/Gitlab/Model/User.php deleted file mode 100644 index ef654756d..000000000 --- a/lib/Gitlab/Model/User.php +++ /dev/null @@ -1,227 +0,0 @@ -hydrate($data); - } - - /** - * @param Client $client - * @param string $email - * @param string $password - * @param array $params - * @return User - */ - public static function create(Client $client, $email, $password, array $params = array()) - { - $data = $client->users()->create($email, $password, $params); - - return static::fromArray($client, $data); - } - - /** - * @param int $id - * @param Client $client - */ - public function __construct($id = null, Client $client = null) - { - $this->setClient($client); - $this->setData('id', $id); - } - - /** - * @return User - */ - public function show() - { - $data = $this->client->users()->show($this->id); - - return static::fromArray($this->getClient(), $data); - } - - /** - * @param array $params - * @return User - */ - public function update(array $params) - { - $data = $this->client->users()->update($this->id, $params); - - return static::fromArray($this->getClient(), $data); - } - - /** - * @return bool - */ - public function remove() - { - $this->client->users()->remove($this->id); - - return true; - } - - /** - * @return bool - */ - public function block() - { - $this->client->users()->block($this->id); - - return true; - } - - /** - * @return bool - */ - public function unblock() - { - $this->client->users()->unblock($this->id); - - return true; - } - - /** - * @return Key[] - */ - public function keys() - { - $data = $this->client->users()->keys(); - - $keys = array(); - foreach ($data as $key) { - $keys[] = Key::fromArray($this->getClient(), $key); - } - - return $keys; - } - - /** - * @param string $title - * @param string $key - * @return Key - */ - public function createKey($title, $key) - { - $data = $this->client->users()->createKey($title, $key); - - return Key::fromArray($this->getClient(), $data); - } - - /** - * @param string $title - * @param string $key - * @return Key - */ - public function createKeyForUser($user_id, $title, $key) - { - $data = $this->client->users()->createKeyForUser($user_id, $title, $key); - - return Key::fromArray($this->getClient(), $data); - } - - /** - * @param int $id - * @return bool - */ - public function removeKey($id) - { - $this->client->users()->removeKey($id); - - return true; - } - - /** - * @param int $group_id - * @param int $access_level - * @return User - */ - public function addToGroup($group_id, $access_level) - { - $group = new Group($group_id, $this->getClient()); - - return $group->addMember($this->id, $access_level); - } - - /** - * @param int $group_id - * @return bool - */ - public function removeFromGroup($group_id) - { - $group = new Group($group_id, $this->getClient()); - - return $group->removeMember($this->id); - } -} diff --git a/lib/Gitlab/ResultPager.php b/lib/Gitlab/ResultPager.php deleted file mode 100644 index b436305b1..000000000 --- a/lib/Gitlab/ResultPager.php +++ /dev/null @@ -1,132 +0,0 @@ -api('someApi'); - * $pager = new \Gitlab\ResultPager($client); - * - * @param \Gitlab\Client $client - * - */ - public function __construct(Client $client) - { - $this->client = $client; - } - - /** - * {@inheritdoc} - */ - public function fetch(ApiInterface $api, $method, array $parameters = array()) - { - return call_user_func_array(array($api, $method), $parameters); - } - - /** - * {@inheritdoc} - */ - public function fetchAll(ApiInterface $api, $method, array $parameters = array()) - { - $result = call_user_func_array(array($api, $method), $parameters); - while ($this->hasNext()) { - $result = array_merge($result, $this->fetchNext()); - } - - return $result; - } - - /** - * {@inheritdoc} - */ - public function hasNext() - { - return $this->has('next'); - } - - /** - * {@inheritdoc} - */ - public function fetchNext() - { - return $this->get('next'); - } - - /** - * {@inheritdoc} - */ - public function hasPrevious() - { - return $this->has('prev'); - } - - /** - * {@inheritdoc} - */ - public function fetchPrevious() - { - return $this->get('prev'); - } - - /** - * {@inheritdoc} - */ - public function fetchFirst() - { - return $this->get('first'); - } - - /** - * {@inheritdoc} - */ - public function fetchLast() - { - return $this->get('last'); - } - - /** - * {@inheritdoc} - */ - protected function has($key) - { - $lastResponse = $this->client->getResponseHistory()->getLastResponse(); - if ($lastResponse == null) { - return false; - } - - $pagination = ResponseMediator::getPagination($lastResponse); - if ($pagination == null) { - return false; - } - - return isset($pagination[$key]); - } - - /** - * {@inheritdoc} - */ - protected function get($key) - { - if (!$this->has($key)) { - return []; - } - - $pagination = ResponseMediator::getPagination($this->client->getResponseHistory()->getLastResponse()); - - return ResponseMediator::getContent($this->client->getHttpClient()->get($pagination[$key])); - } -} diff --git a/lib/Gitlab/ResultPagerInterface.php b/lib/Gitlab/ResultPagerInterface.php deleted file mode 100644 index 0078580c1..000000000 --- a/lib/Gitlab/ResultPagerInterface.php +++ /dev/null @@ -1,68 +0,0 @@ -, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Api/Projects.php + + - + message: '#^Parameter \#1 \$options of method Symfony\\Component\\OptionsResolver\\OptionsResolver\:\:resolve\(\) expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Api/Repositories.php + + - + message: '#^Parameter \#1 \$uri of static method Gitlab\\Api\\AbstractApi\:\:encodePath\(\) expects int\|string, mixed given\.$#' + identifier: argument.type + count: 3 + path: src/Api/RepositoryFiles.php + + - + message: '#^Parameter \#1 \$message of static method Gitlab\\HttpClient\\Message\\ResponseMediator\:\:getMessageAsString\(\) expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/HttpClient/Message/ResponseMediator.php + + - + message: '#^Parameter \#3 \.\.\.\$values of function sprintf expects bool\|float\|int\|string\|null, mixed given\.$#' + identifier: argument.type + count: 2 + path: src/HttpClient/Message/ResponseMediator.php + + - + message: '#^PHPDoc tag @return contains generic type Http\\Promise\\Promise\ but interface Http\\Promise\\Promise is not generic\.$#' + identifier: generics.notGeneric + count: 1 + path: src/HttpClient/Plugin/Authentication.php + + - + message: '#^PHPDoc tag @return contains generic type Http\\Promise\\Promise\ but interface Http\\Promise\\Promise is not generic\.$#' + identifier: generics.notGeneric + count: 1 + path: src/HttpClient/Plugin/ExceptionThrower.php + + - + message: '#^PHPDoc tag @var with type string is not subtype of native type non\-empty\-string\|false\.$#' + identifier: varTag.nativeType + count: 1 + path: src/HttpClient/Util/JsonArray.php + + - + message: '#^Cannot cast mixed to string\.$#' + identifier: cast.string + count: 1 + path: src/HttpClient/Util/QueryStringBuilder.php + + - + message: '#^PHPDoc tag @var with type Closure\(Gitlab\\Api\\AbstractApi\)\: Gitlab\\Api\\AbstractApi is not subtype of type Closure\(Gitlab\\Api\\AbstractApi\)\: Gitlab\\Api\\AbstractApi\.$#' + identifier: varTag.type + count: 1 + path: src/ResultPager.php + + - + message: '#^Variable method call on Gitlab\\Api\\AbstractApi\.$#' + identifier: method.dynamicName + count: 1 + path: src/ResultPager.php + diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 000000000..40f6a875d --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,16 @@ +includes: + - phpstan-baseline.neon + - vendor-bin/phpstan/vendor/phpstan/phpstan-deprecation-rules/rules.neon + - vendor-bin/phpstan/vendor/phpstan/phpstan-strict-rules/rules.neon + +parameters: + level: max + paths: + - src + ignoreErrors: + - '#Only booleans are allowed in an if condition#' + - '#PHPDoc tag \@var above a method has no effect.#' + - '#return type has no value type specified in iterable type array#' + - '#no value type specified in iterable type array#' + - '#expects array\, array given#' + - '#expects array\, array given#' diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 7351de836..b689bdf47 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,25 +1,13 @@ - - - - - ./test/Gitlab/ - - - - - - ./lib/Gitlab/ - - + + + + ./tests + + + + + ./src + + diff --git a/psalm-baseline.xml b/psalm-baseline.xml new file mode 100644 index 000000000..3c19e346b --- /dev/null +++ b/psalm-baseline.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + ]]> + + + + + ]]> + + + + + perPage]]> + + + diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 000000000..3dd74b68c --- /dev/null +++ b/psalm.xml @@ -0,0 +1,18 @@ + + + + + + + + + diff --git a/src/Api/AbstractApi.php b/src/Api/AbstractApi.php new file mode 100644 index 000000000..b0219ac42 --- /dev/null +++ b/src/Api/AbstractApi.php @@ -0,0 +1,370 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +use Gitlab\Client; +use Gitlab\Exception\RuntimeException; +use Gitlab\HttpClient\Message\ResponseMediator; +use Gitlab\HttpClient\Util\JsonArray; +use Gitlab\HttpClient\Util\QueryStringBuilder; +use Http\Message\MultipartStream\MultipartStreamBuilder; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * @author Joseph Bielawski + * @author Matt Humphrey + * @author Radu Topala + */ +abstract class AbstractApi +{ + /** + * The URI prefix. + * + * @var string + */ + private const URI_PREFIX = '/api/v4/'; + + /** + * The access levels for groups and projects + * as defined in the Gitlab::Access module. + * + * @see https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/access.rb + * + * @var array + */ + protected const ACCESS_LEVELS = [0, 10, 20, 30, 40, 50]; + + private readonly Client $client; + + private ?int $perPage; + + public function __construct(Client $client) + { + $this->client = $client; + $this->perPage = null; + } + + /** + * Send a GET request with query params and return the raw response. + * + * @param array $headers + * + * @throws \Http\Client\Exception + */ + protected function getAsResponse(string $uri, array $params = [], array $headers = []): ResponseInterface + { + if (null !== $this->perPage && !isset($params['per_page'])) { + $params['per_page'] = $this->perPage; + } + + return $this->client->getHttpClient()->get(self::prepareUri($uri, $params), $headers); + } + + /** + * @param array $params + * @param array $headers + */ + protected function get(string $uri, array $params = [], array $headers = []): mixed + { + $response = $this->getAsResponse($uri, $params, $headers); + + return ResponseMediator::getContent($response); + } + + /** + * @param array $params + * @param array $headers + * @param array $files + * @param array $uriParams + */ + protected function post(string $uri, array $params = [], array $headers = [], array $files = [], array $uriParams = []): mixed + { + if (0 < \count($files)) { + $builder = $this->createMultipartStreamBuilder($params, $files); + $body = self::prepareMultipartBody($builder); + $headers = self::addMultipartContentType($headers, $builder); + } else { + $body = self::prepareJsonBody($params); + + if (null !== $body) { + $headers = self::addJsonContentType($headers); + } + } + + $response = $this->client->getHttpClient()->post(self::prepareUri($uri, $uriParams), $headers, $body); + + return ResponseMediator::getContent($response); + } + + /** + * @param array $params + * @param array $headers + * @param array $files + */ + protected function put(string $uri, array $params = [], array $headers = [], array $files = []): mixed + { + if (0 < \count($files)) { + $builder = $this->createMultipartStreamBuilder($params, $files); + $body = self::prepareMultipartBody($builder); + $headers = self::addMultipartContentType($headers, $builder); + } else { + $body = self::prepareJsonBody($params); + + if (null !== $body) { + $headers = self::addJsonContentType($headers); + } + } + + $response = $this->client->getHttpClient()->put(self::prepareUri($uri), $headers, $body ?? ''); + + return ResponseMediator::getContent($response); + } + + /** + * @param array $params + * @param array $headers + * @param array $files + */ + protected function patch(string $uri, array $params = [], array $headers = [], array $files = []): mixed + { + if (0 < \count($files)) { + $builder = $this->createMultipartStreamBuilder($params, $files); + $body = self::prepareMultipartBody($builder); + $headers = self::addMultipartContentType($headers, $builder); + } else { + $body = self::prepareJsonBody($params); + + if (null !== $body) { + $headers = self::addJsonContentType($headers); + } + } + + $response = $this->client->getHttpClient()->patch(self::prepareUri($uri), $headers, $body ?? ''); + + return ResponseMediator::getContent($response); + } + + /** + * @param array $headers + * @param array $uriParams + */ + protected function putFile(string $uri, string $file, array $headers = [], array $uriParams = []): mixed + { + $resource = self::tryFopen($file, 'r'); + $body = $this->client->getStreamFactory()->createStreamFromResource($resource); + + if ($body->isReadable()) { + $headers = \array_merge([ResponseMediator::CONTENT_TYPE_HEADER => self::guessFileContentType($file)], $headers); + } + + $response = $this->client->getHttpClient()->put(self::prepareUri($uri, $uriParams), $headers, $body); + + return ResponseMediator::getContent($response); + } + + /** + * @param array $params + * @param array $headers + */ + protected function delete(string $uri, array $params = [], array $headers = []): mixed + { + $body = self::prepareJsonBody($params); + + if (null !== $body) { + $headers = self::addJsonContentType($headers); + } + + $response = $this->client->getHttpClient()->delete(self::prepareUri($uri), $headers, $body ?? ''); + + return ResponseMediator::getContent($response); + } + + protected static function encodePath(int|string $uri): string + { + return \rawurlencode((string) $uri); + } + + protected function getProjectPath(int|string $id, string $uri): string + { + return 'projects/'.self::encodePath($id).'/'.$uri; + } + + /** + * Create a new OptionsResolver with page and per_page options. + */ + protected function createOptionsResolver(): OptionsResolver + { + $resolver = new OptionsResolver(); + $resolver->setDefined('page') + ->setAllowedTypes('page', 'int') + ->setAllowedValues('page', function ($value): bool { + return $value > 0; + }) + ; + $resolver->setDefined('per_page') + ->setAllowedTypes('per_page', 'int') + ->setAllowedValues('per_page', function ($value): bool { + return $value > 0 && $value <= 100; + }) + ; + + return $resolver; + } + + /** + * Prepare the request URI. + */ + private static function prepareUri(string $uri, array $query = []): string + { + $query = \array_filter($query, function ($value): bool { + return null !== $value; + }); + + return \sprintf('%s%s%s', self::URI_PREFIX, $uri, QueryStringBuilder::build($query)); + } + + /** + * Prepare the request URI. + * + * @param array $params + * @param array $files + */ + private function createMultipartStreamBuilder(array $params = [], array $files = []): MultipartStreamBuilder + { + $builder = new MultipartStreamBuilder($this->client->getStreamFactory()); + + foreach ($params as $name => $value) { + $builder->addResource($name, $value); + } + + foreach ($files as $name => $file) { + $builder->addResource($name, self::tryFopen($file, 'r'), [ + 'headers' => [ + ResponseMediator::CONTENT_TYPE_HEADER => self::guessFileContentType($file), + ], + 'filename' => \basename($file), + ]); + } + + return $builder; + } + + /** + * Prepare the request multipart body. + */ + private static function prepareMultipartBody(MultipartStreamBuilder $builder): StreamInterface + { + return $builder->build(); + } + + /** + * Add the multipart content type to the headers if one is not already present. + * + * @param array $headers + * + * @return array + */ + private static function addMultipartContentType(array $headers, MultipartStreamBuilder $builder): array + { + $contentType = \sprintf('%s; boundary=%s', ResponseMediator::MULTIPART_CONTENT_TYPE, $builder->getBoundary()); + + return \array_merge([ResponseMediator::CONTENT_TYPE_HEADER => $contentType], $headers); + } + + /** + * Prepare the request JSON body. + * + * @param array $params + */ + private static function prepareJsonBody(array $params): ?string + { + $params = \array_filter($params, function ($value): bool { + return null !== $value; + }); + + if (0 === \count($params)) { + return null; + } + + return JsonArray::encode($params); + } + + /** + * Add the JSON content type to the headers if one is not already present. + * + * @param array $headers + * + * @return array + */ + private static function addJsonContentType(array $headers): array + { + return \array_merge([ResponseMediator::CONTENT_TYPE_HEADER => ResponseMediator::JSON_CONTENT_TYPE], $headers); + } + + /** + * Safely opens a PHP stream resource using a filename. + * + * When fopen fails, PHP normally raises a warning. This function adds an + * error handler that checks for errors and throws an exception instead. + * + * @param string $filename File to open + * @param string $mode Mode used to open the file + * + * @throws RuntimeException if the file cannot be opened + * + * @return resource + * + * @see https://github.com/guzzle/psr7/blob/1.6.1/src/functions.php#L287-L320 + */ + private static function tryFopen(string $filename, string $mode) + { + $ex = null; + \set_error_handler(function () use ($filename, $mode, &$ex): void { + $ex = new RuntimeException(\sprintf( + 'Unable to open %s using mode %s: %s', + $filename, + $mode, + \func_get_args()[1] + )); + }); + + $handle = \fopen($filename, $mode); + \restore_error_handler(); + + if (null !== $ex) { + throw $ex; + } + + /** @var resource */ + return $handle; + } + + /** + * Guess the content type of the file if possible. + */ + private static function guessFileContentType(string $file): string + { + if (!\class_exists(\finfo::class, false)) { + return ResponseMediator::STREAM_CONTENT_TYPE; + } + + $finfo = new \finfo(\FILEINFO_MIME_TYPE); + $type = $finfo->file($file); + + return false !== $type ? $type : ResponseMediator::STREAM_CONTENT_TYPE; + } +} diff --git a/src/Api/DeployKeys.php b/src/Api/DeployKeys.php new file mode 100644 index 000000000..9259d25c3 --- /dev/null +++ b/src/Api/DeployKeys.php @@ -0,0 +1,25 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +class DeployKeys extends AbstractApi +{ + public function all(array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + + return $this->get('deploy_keys', $resolver->resolve($parameters)); + } +} diff --git a/src/Api/Deployments.php b/src/Api/Deployments.php new file mode 100644 index 000000000..3770bf9df --- /dev/null +++ b/src/Api/Deployments.php @@ -0,0 +1,53 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +class Deployments extends AbstractApi +{ + /** + * @param array $parameters { + * + * @var string $order_by Return deployments ordered by id, iid, created_at, updated_at, + * or ref fields (default is id) + * @var string $sort Return deployments sorted in asc or desc order (default is desc) + * @var string $status Return deployments filtered by status of deployment allowed + * values of status are 'created', 'running', 'success', 'failed', + * 'canceled', 'blocked' + * @var string $environment Return deployments filtered to a particular environment + * } + */ + public function all(int|string $project_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $resolver->setDefined('order_by') + ->setAllowedTypes('order_by', 'string') + ->setAllowedValues('order_by', ['id', 'iid', 'created_at', 'updated_at', 'ref']); + $resolver->setDefined('sort') + ->setAllowedTypes('sort', 'string') + ->setAllowedValues('sort', ['desc', 'asc']); + $resolver->setDefined('status') + ->setAllowedTypes('status', 'string') + ->setAllowedValues('status', ['created', 'running', 'success', 'failed', 'canceled', 'blocked']); + $resolver->setDefined('environment') + ->setAllowedTypes('environment', 'string'); + + return $this->get($this->getProjectPath($project_id, 'deployments'), $resolver->resolve($parameters)); + } + + public function show(int|string $project_id, int $deployment_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'deployments/'.$deployment_id)); + } +} diff --git a/src/Api/Environments.php b/src/Api/Environments.php new file mode 100644 index 000000000..b83693a9a --- /dev/null +++ b/src/Api/Environments.php @@ -0,0 +1,71 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +use Symfony\Component\OptionsResolver\OptionsResolver; + +class Environments extends AbstractApi +{ + public function all(int|string $project_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $resolver->setDefined('name') + ->setAllowedTypes('name', 'string'); + $resolver->setDefined('search') + ->setAllowedTypes('search', 'string'); + $resolver->setDefined('states') + ->setAllowedTypes('states', 'string') + ->setAllowedValues('states', ['available', 'stopped']); + + return $this->get($this->getProjectPath($project_id, 'environments'), $resolver->resolve($parameters)); + } + + /** + * @param array $parameters { + * + * @var string $name The name of the environment + * @var string $external_url Place to link to for this environment + * @var string $tier The tier of the new environment. Allowed values are production, staging, testing, development, and other. + * } + */ + public function create(int|string $project_id, array $parameters = []): mixed + { + $resolver = new OptionsResolver(); + $resolver->setDefined('name') + ->setRequired('name') + ->setAllowedTypes('name', 'string'); + $resolver->setDefined('external_url') + ->setAllowedTypes('external_url', 'string'); + $resolver->setDefined('tier') + ->setAllowedValues('tier', ['production', 'staging', 'testing', 'development', 'other']); + + return $this->post($this->getProjectPath($project_id, 'environments'), $resolver->resolve($parameters)); + } + + public function remove(int|string $project_id, int $environment_id): mixed + { + return $this->delete($this->getProjectPath($project_id, 'environments/'.$environment_id)); + } + + public function stop(int|string $project_id, int $environment_id): mixed + { + return $this->post($this->getProjectPath($project_id, 'environments/'.self::encodePath($environment_id).'/stop')); + } + + public function show(int|string $project_id, int $environment_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'environments/'.self::encodePath($environment_id))); + } +} diff --git a/src/Api/Events.php b/src/Api/Events.php new file mode 100644 index 000000000..74f3d1cd1 --- /dev/null +++ b/src/Api/Events.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +use Symfony\Component\OptionsResolver\Options; + +class Events extends AbstractApi +{ + /** + * @param array $parameters { + * + * @var string $action include only events of a particular action type + * @var string $target_type include only events of a particular target type + * @var \DateTimeInterface $before include only events created before a particular date + * @var \DateTimeInterface $after include only events created after a particular date + * @var string $scope include all events across a user’s projects + * @var string $sort sort events in asc or desc order by created_at + * + * } + */ + public function all(array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $datetimeNormalizer = function (Options $resolver, \DateTimeInterface $value): string { + return $value->format('Y-m-d'); + }; + + $resolver->setDefined('action'); + $resolver->setDefined('target_type'); + $resolver->setDefined('before') + ->setAllowedTypes('before', \DateTimeInterface::class) + ->setNormalizer('before', $datetimeNormalizer) + ; + $resolver->setDefined('after') + ->setAllowedTypes('after', \DateTimeInterface::class) + ->setNormalizer('after', $datetimeNormalizer) + ; + $resolver->setDefined('scope'); + $resolver->setDefined('sort'); + + return $this->get('events', $resolver->resolve($parameters)); + } +} diff --git a/src/Api/Groups.php b/src/Api/Groups.php new file mode 100644 index 000000000..7104d3450 --- /dev/null +++ b/src/Api/Groups.php @@ -0,0 +1,857 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; +use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class Groups extends AbstractApi +{ + /** + * @var string + */ + public const STATE_ALL = 'all'; + + /** + * @var string + */ + public const STATE_MERGED = 'merged'; + + /** + * @var string + */ + public const STATE_OPENED = 'opened'; + + /** + * @var string + */ + public const STATE_CLOSED = 'closed'; + + /** + * @var string + */ + public const STATE_LOCKED = 'locked'; + + /** + * @param array $parameters { + * + * @var int[] $skip_groups skip the group IDs passes + * @var bool $all_available show all the groups you have access to + * @var string $search return list of authorized groups matching the search criteria + * @var string $order_by Order groups by name or path (default is name) + * @var string $sort Order groups in asc or desc order (default is asc) + * @var bool $statistics include group statistics (admins only) + * @var bool $owned limit by groups owned by the current user + * @var int $min_access_level limit by groups in which the current user has at least this access level + * @var bool $top_level_only limit to top level groups, excluding all subgroups + * } + */ + public function all(array $parameters = []): mixed + { + $resolver = $this->getGroupSearchResolver(); + + return $this->get('groups', $resolver->resolve($parameters)); + } + + public function show(int|string $id): mixed + { + return $this->get('groups/'.self::encodePath($id)); + } + + public function create(string $name, string $path, ?string $description = null, string $visibility = 'private', ?bool $lfs_enabled = null, ?bool $request_access_enabled = null, ?int $parent_id = null, ?int $shared_runners_minutes_limit = null): mixed + { + $params = [ + 'name' => $name, + 'path' => $path, + 'description' => $description, + 'visibility' => $visibility, + 'lfs_enabled' => $lfs_enabled, + 'request_access_enabled' => $request_access_enabled, + 'parent_id' => $parent_id, + 'shared_runners_minutes_limit' => $shared_runners_minutes_limit, + ]; + + return $this->post('groups', \array_filter($params, function ($value) { + return null !== $value && (!\is_string($value) || '' !== $value); + })); + } + + public function update(int|string $id, array $params): mixed + { + return $this->put('groups/'.self::encodePath($id), $params); + } + + public function remove(int|string $group_id): mixed + { + return $this->delete('groups/'.self::encodePath($group_id)); + } + + public function transfer(int|string $group_id, int|string $project_id): mixed + { + return $this->post('groups/'.self::encodePath($group_id).'/projects/'.self::encodePath($project_id)); + } + + public function allMembers(int|string $group_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $resolver->setDefined('query'); + $resolver->setDefined('user_ids') + ->setAllowedTypes('user_ids', 'array') + ->setAllowedValues('user_ids', function (array $value) { + return \count($value) === \count(\array_filter($value, 'is_int')); + }) + ; + + return $this->get('groups/'.self::encodePath($group_id).'/members/all', $resolver->resolve($parameters)); + } + + /** + * @param array $parameters { + * + * @var string $query A query string to search for members. + * } + */ + public function members(int|string $group_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $resolver->setDefined('query'); + $resolver->setDefined('user_ids') + ->setAllowedTypes('user_ids', 'array') + ->setAllowedValues('user_ids', function (array $value) { + return \count($value) === \count(\array_filter($value, 'is_int')); + }) + ; + + return $this->get('groups/'.self::encodePath($group_id).'/members', $resolver->resolve($parameters)); + } + + public function member(int|string $group_id, int $user_id): mixed + { + return $this->get('groups/'.self::encodePath($group_id).'/members/'.self::encodePath($user_id)); + } + + public function allMember(int|string $group_id, int $user_id): mixed + { + return $this->get('groups/'.self::encodePath($group_id).'/members/all/'.self::encodePath($user_id)); + } + + public function addMember(int|string $group_id, int $user_id, int $access_level, array $parameters = []): mixed + { + $dateNormalizer = function (OptionsResolver $optionsResolver, \DateTimeInterface $date): string { + return $date->format('Y-m-d'); + }; + + $resolver = $this->createOptionsResolver() + ->setDefined('expires_at') + ->setAllowedTypes('expires_at', \DateTimeInterface::class) + ->setNormalizer('expires_at', $dateNormalizer) + ; + + $parameters = \array_merge([ + 'user_id' => $user_id, + 'access_level' => $access_level, + ], $resolver->resolve($parameters)); + + return $this->post('groups/'.self::encodePath($group_id).'/members', $parameters); + } + + public function saveMember(int|string $group_id, int $user_id, int $access_level): mixed + { + return $this->put('groups/'.self::encodePath($group_id).'/members/'.self::encodePath($user_id), [ + 'access_level' => $access_level, + ]); + } + + /** + * @param array $parameters { + * + * @var int $group_access the access level to grant the group + * @var string $expires_at share expiration date in ISO 8601 format: 2016-09-26 + * } + */ + public function addShare(int|string $group_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + + $datetimeNormalizer = function (OptionsResolver $optionsResolver, \DateTimeInterface $value) { + return $value->format('Y-m-d'); + }; + + $resolver->setRequired('group_id') + ->setAllowedTypes('group_id', 'int'); + + $resolver->setRequired('group_access') + ->setAllowedTypes('group_access', 'int') + ->setAllowedValues('group_access', [0, 10, 20, 30, 40, 50]); + + $resolver->setDefined('expires_at') + ->setAllowedTypes('expires_at', \DateTimeInterface::class) + ->setNormalizer('expires_at', $datetimeNormalizer) + ; + + return $this->post('groups/'.self::encodePath($group_id).'/share', $resolver->resolve($parameters)); + } + + public function removeMember(int|string $group_id, int $user_id): mixed + { + return $this->delete('groups/'.self::encodePath($group_id).'/members/'.self::encodePath($user_id)); + } + + /** + * @param array $parameters { + * + * @var bool $archived limit by archived status + * @var string $visibility limit by visibility public, internal, or private + * @var string $order_by Return projects ordered by id, name, path, created_at, updated_at, or last_activity_at fields. + * Default is created_at. + * @var string $sort Return projects sorted in asc or desc order (default is desc) + * @var string $search return list of authorized projects matching the search criteria + * @var bool $simple return only the ID, URL, name, and path of each project + * @var bool $owned limit by projects owned by the current user + * @var bool $starred limit by projects starred by the current user + * @var bool $with_issues_enabled Limit by projects with issues feature enabled (default is false) + * @var bool $with_merge_requests_enabled Limit by projects with merge requests feature enabled (default is false) + * @var bool $with_shared Include projects shared to this group (default is true) + * @var bool $include_subgroups Include projects in subgroups of this group (default is false) + * @var bool $with_custom_attributes Include custom attributes in response (admins only). + * } + */ + public function projects(int|string $id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $booleanNormalizer = function (Options $resolver, $value): string { + return $value ? 'true' : 'false'; + }; + + $resolver->setDefined('archived') + ->setAllowedTypes('archived', 'bool') + ->setNormalizer('archived', $booleanNormalizer) + ; + $resolver->setDefined('visibility') + ->setAllowedValues('visibility', ['public', 'internal', 'private']) + ; + $resolver->setDefined('order_by') + ->setAllowedValues('order_by', ['id', 'name', 'path', 'created_at', 'updated_at', 'last_activity_at']) + ; + $resolver->setDefined('sort') + ->setAllowedValues('sort', ['asc', 'desc']) + ; + $resolver->setDefined('search'); + $resolver->setDefined('simple') + ->setAllowedTypes('simple', 'bool') + ->setNormalizer('simple', $booleanNormalizer) + ; + $resolver->setDefined('owned') + ->setAllowedTypes('owned', 'bool') + ->setNormalizer('owned', $booleanNormalizer) + ; + $resolver->setDefined('starred') + ->setAllowedTypes('starred', 'bool') + ->setNormalizer('starred', $booleanNormalizer) + ; + $resolver->setDefined('with_issues_enabled') + ->setAllowedTypes('with_issues_enabled', 'bool') + ->setNormalizer('with_issues_enabled', $booleanNormalizer) + ; + $resolver->setDefined('with_merge_requests_enabled') + ->setAllowedTypes('with_merge_requests_enabled', 'bool') + ->setNormalizer('with_merge_requests_enabled', $booleanNormalizer) + ; + $resolver->setDefined('with_shared') + ->setAllowedTypes('with_shared', 'bool') + ->setNormalizer('with_shared', $booleanNormalizer) + ; + $resolver->setDefined('include_subgroups') + ->setAllowedTypes('include_subgroups', 'bool') + ->setNormalizer('include_subgroups', $booleanNormalizer) + ; + $resolver->setDefined('with_custom_attributes') + ->setAllowedTypes('with_custom_attributes', 'bool') + ->setNormalizer('with_custom_attributes', $booleanNormalizer) + ; + + return $this->get('groups/'.self::encodePath($id).'/projects', $resolver->resolve($parameters)); + } + + /** + * @param array $parameters { + * + * @var int[] $skip_groups skip the group IDs passes + * @var bool $all_available show all the groups you have access to + * @var string $search return list of authorized groups matching the search criteria + * @var string $order_by Order groups by name or path (default is name) + * @var string $sort Order groups in asc or desc order (default is asc) + * @var bool $statistics include group statistics (admins only) + * @var bool $owned Limit by groups owned by the current user. + * } + */ + public function subgroups(int|string $group_id, array $parameters = []): mixed + { + $resolver = $this->getSubgroupSearchResolver(); + + return $this->get('groups/'.self::encodePath($group_id).'/subgroups', $resolver->resolve($parameters)); + } + + /** + * @param array $parameters { + * + * @var string $assignee_id Return issues assigned to the given user id. Mutually exclusive with assignee_username. + * None returns unassigned issues. Any returns issues with an assignee. + * @var string $assignee_username Return issues assigned to the given username. Similar to assignee_id and mutually exclusive with assignee_id. + * In GitLab CE, the assignee_username array should only contain a single value. Otherwise, an invalid parameter error is returned. + * @var int $author_id Return issues created by the given user id. Mutually exclusive with author_username. + * Combine with scope=all or scope=assigned_to_me. + * @var string $author_username Return issues created by the given username. Similar to author_id and mutually exclusive with author_id. + * @var bool $confidential Filter confidential or public issues + * @var \DateTimeInterface $created_after Return issues created after the given time (inclusive) + * @var \DateTimeInterface $created_before Return issues created before the given time (inclusive) + * @var int $iteration_id Return issues assigned to the given iteration ID. None returns issues that do not belong to an iteration. Any returns issues that belong to an iteration. Mutually exclusive with iteration_title. + * @var string $iteration_title Return issues assigned to the iteration with the given title. Similar to iteration_id and mutually exclusive with iteration_id. + * @var string $labels Comma-separated list of label names, issues must have all labels to be returned. None lists all issues with no labels. Any lists all issues with at least one label. No+Label (Deprecated) lists all issues with no labels. Predefined names are case-insensitive. + * @var string $milestone The milestone title. None lists all issues with no milestone. Any lists all issues that have an assigned milestone. + * @var string $my_reaction_emoji Return issues reacted by the authenticated user by the given emoji. None returns issues not given a reaction. Any returns issues given at least one reaction. + * @var bool $non_archived Return issues from non archived projects. Default is true. + * @var string $not Return issues that do not match the parameters supplied. Accepts: labels, milestone, author_id, author_username, assignee_id, assignee_username, my_reaction_emoji, search, in + * @var string $order_by Return issues ordered by created_at, updated_at, priority, due_date, relative_position, label_priority, milestone_due, popularity, weight fields. Default is created_at + * @var string $scope Return issues for the given scope: created_by_me, assigned_to_me or all. Defaults to all. + * @var string $search Search group issues against their title and description + * @var string $sort Return issues sorted in asc or desc order. Default is desc + * @var string $state Return all issues or just those that are opened or closed + * @var \DateTimeInterface $updated_after Return issues updated on or after the given time. Expected in ISO 8601 format (2019-03-15T08:00:00Z) + * @var \DateTimeInterface $updated_before Return issues updated on or before the given time. Expected in ISO 8601 format (2019-03-15T08:00:00Z) + * @var int $weight Return issues with the specified weight. None returns issues with no weight assigned. Any returns issues with a weight assigned. + * @var bool $with_labels_details If true, the response returns more details for each label in labels field: :name, :color, :description, :description_html, :text_color. Default is false. + * } + */ + public function issues(int|string $group_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $booleanNormalizer = function (Options $resolver, $value): string { + return $value ? 'true' : 'false'; + }; + $datetimeNormalizer = function (Options $resolver, \DateTimeInterface $value): string { + return $value->format('c'); + }; + + $resolver->setDefined('assignee_id'); + $resolver->setDefined('assignee_username') + ->setAllowedTypes('assignee_username', 'string'); + + $resolver->setDefined('author_id'); + $resolver->setDefined('author_username') + ->setAllowedTypes('author_username', 'string'); + + $resolver->setDefined('confidential') + ->setAllowedTypes('confidential', 'bool') + ->setNormalizer('confidential', $booleanNormalizer); + + $resolver->setDefined('created_after') + ->setAllowedTypes('created_after', \DateTimeInterface::class) + ->setNormalizer('created_after', $datetimeNormalizer); + $resolver->setDefined('created_before') + ->setAllowedTypes('created_before', \DateTimeInterface::class) + ->setNormalizer('created_before', $datetimeNormalizer); + + $resolver->setDefined('updated_after') + ->setAllowedTypes('updated_after', \DateTimeInterface::class) + ->setNormalizer('updated_after', $datetimeNormalizer); + $resolver->setDefined('updated_before') + ->setAllowedTypes('updated_before', \DateTimeInterface::class) + ->setNormalizer('updated_before', $datetimeNormalizer); + + $resolver->setDefined('iteration_id'); + $resolver->setDefined('iteration_title') + ->setAllowedTypes('iteration_title', 'string'); + + $resolver->setDefined('labels') + ->setAllowedTypes('labels', 'string'); + + $resolver->setDefined('milestone') + ->setAllowedTypes('milestone', 'string'); + + $resolver->setDefined('my_reaction_emoji') + ->setAllowedTypes('my_reaction_emoji', 'string'); + + $resolver->setDefined('non_archived') + ->setAllowedTypes('non_archived', 'bool') + ->setNormalizer('non_archived', $booleanNormalizer); + + $resolver->setDefined('not') + ->setAllowedTypes('not', 'string'); + + $resolver->setDefined('order_by') + ->setAllowedValues('order_by', ['created_at', 'updated_at']); + $resolver->setDefined('sort') + ->setAllowedValues('sort', ['asc', 'desc']); + + $resolver->setDefined('scope') + ->setAllowedTypes('scope', 'string'); + + $resolver->setDefined('search') + ->setAllowedTypes('search', 'string'); + + $resolver->setDefined('state') + ->setAllowedValues('state', [self::STATE_ALL, self::STATE_OPENED, self::STATE_CLOSED]); + + $resolver->setDefined('weight'); + + $resolver->setDefined('with_labels_details') + ->setAllowedTypes('with_labels_details', 'bool') + ->setNormalizer('with_labels_details', $booleanNormalizer); + + return $this->get('groups/'.self::encodePath($group_id).'/issues', $resolver->resolve($parameters)); + } + + /** + * @param array $parameters { + * + * @var bool $with_counts Whether or not to include issue and merge request counts. Defaults to false. + * @var bool $include_ancestor_groups Include ancestor groups. Defaults to true. + * @var bool $include_descendant_groups Include descendant groups. Defaults to false. + * @var bool $only_group_labels Toggle to include only group labels or also project labels. Defaults to true. + * @var string $search Keyword to filter labels by. + * } + */ + public function labels(int|string $group_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + + $resolver->setDefined('with_counts') + ->setAllowedTypes('with_counts', 'bool'); + + $resolver->setDefined('include_ancestor_groups') + ->setAllowedTypes('include_ancestor_groups', 'bool'); + + $resolver->setDefined('include_descendant_groups') + ->setAllowedTypes('include_descendant_groups', 'bool'); + + $resolver->setDefined('only_group_labels') + ->setAllowedTypes('only_group_labels', 'bool'); + + $resolver->setDefined('search') + ->setAllowedTypes('search', 'string'); + + return $this->get('groups/'.self::encodePath($group_id).'/labels', $resolver->resolve($parameters)); + } + + public function addLabel(int|string $group_id, array $params): mixed + { + return $this->post('groups/'.self::encodePath($group_id).'/labels', $params); + } + + public function updateLabel(int|string $group_id, int $label_id, array $params): mixed + { + return $this->put('groups/'.self::encodePath($group_id).'/labels/'.self::encodePath($label_id), $params); + } + + public function removeLabel(int|string $group_id, int $label_id): mixed + { + return $this->delete('groups/'.self::encodePath($group_id).'/labels/'.self::encodePath($label_id)); + } + + public function variables(int|string $group_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + + return $this->get('groups/'.self::encodePath($group_id).'/variables', $resolver->resolve($parameters)); + } + + public function variable(int|string $group_id, string $key): mixed + { + return $this->get('groups/'.self::encodePath($group_id).'/variables/'.self::encodePath($key)); + } + + /** + * @param array $parameters { + * + * @var string $masked true or false + * @var string $variable_type env_var (default) or file + * } + */ + public function addVariable(int|string $group_id, string $key, string $value, ?bool $protected = null, array $parameters = []): mixed + { + $payload = [ + 'key' => $key, + 'value' => $value, + ]; + + if ($protected) { + $payload['protected'] = $protected; + } + + if (isset($parameters['masked'])) { + $payload['masked'] = $parameters['masked']; + } + + if (isset($parameters['variable_type'])) { + $payload['variable_type'] = $parameters['variable_type']; + } + + return $this->post('groups/'.self::encodePath($group_id).'/variables', $payload); + } + + public function updateVariable(int|string $group_id, string $key, string $value, ?bool $protected = null): mixed + { + $payload = [ + 'value' => $value, + ]; + + if ($protected) { + $payload['protected'] = $protected; + } + + return $this->put('groups/'.self::encodePath($group_id).'/variables/'.self::encodePath($key), $payload); + } + + public function removeVariable(int|string $group_id, string $key): mixed + { + return $this->delete('groups/'.self::encodePath($group_id).'/variables/'.self::encodePath($key)); + } + + /** + * @param array $parameters { + * + * @var int[] $iids return the request having the given iid + * @var string $state return all merge requests or just those that are opened, closed, or + * merged + * @var string $scope Return merge requests for the given scope: created-by-me, + * assigned-to-me or all (default is created-by-me) + * @var string $order_by return requests ordered by created_at or updated_at fields (default is created_at) + * @var string $sort return requests sorted in asc or desc order (default is desc) + * @var string $milestone return merge requests for a specific milestone + * @var string $view if simple, returns the iid, URL, title, description, and basic state of merge request + * @var string $labels return merge requests matching a comma separated list of labels + * @var \DateTimeInterface $created_after return merge requests created after the given time (inclusive) + * @var \DateTimeInterface $created_before return merge requests created before the given time (inclusive) + * } + */ + public function mergeRequests(int|string $group_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $datetimeNormalizer = function (Options $resolver, \DateTimeInterface $value): string { + return $value->format('c'); + }; + $resolver->setDefined('state') + ->setAllowedValues('state', [self::STATE_ALL, self::STATE_MERGED, self::STATE_OPENED, self::STATE_CLOSED]) + ; + $resolver->setDefined('order_by') + ->setAllowedValues('order_by', ['created_at', 'updated_at']) + ; + $resolver->setDefined('sort') + ->setAllowedValues('sort', ['asc', 'desc']) + ; + $resolver->setDefined('milestone'); + $resolver->setDefined('view') + ->setAllowedValues('view', ['simple']) + ; + $resolver->setDefined('labels'); + $resolver->setDefined('with_labels_details') + ->setAllowedTypes('with_labels_details', 'bool') + ; + + $resolver->setDefined('created_after') + ->setAllowedTypes('created_after', \DateTimeInterface::class) + ->setNormalizer('created_after', $datetimeNormalizer) + ; + $resolver->setDefined('created_before') + ->setAllowedTypes('created_before', \DateTimeInterface::class) + ->setNormalizer('created_before', $datetimeNormalizer) + ; + + $resolver->setDefined('updated_after') + ->setAllowedTypes('updated_after', \DateTimeInterface::class) + ->setNormalizer('updated_after', $datetimeNormalizer) + ; + $resolver->setDefined('updated_before') + ->setAllowedTypes('updated_before', \DateTimeInterface::class) + ->setNormalizer('updated_before', $datetimeNormalizer) + ; + + $resolver->setDefined('scope') + ->setAllowedValues('scope', ['created_by_me', 'assigned_to_me', 'all']) + ; + $resolver->setDefined('author_id') + ->setAllowedTypes('author_id', 'integer'); + $resolver->setDefined('author_username'); + + $resolver->setDefined('assignee_id') + ->setAllowedTypes('assignee_id', 'integer'); + + $resolver->setDefined('approver_ids') + ->setAllowedTypes('approver_ids', 'array') + ->setAllowedValues('approver_ids', function (array $value) { + return \count($value) === \count(\array_filter($value, 'is_int')); + }) + ; + $resolver->setDefined('non_archived') + ->setAllowedTypes('non_archived', 'bool') + ; + $resolver->setDefined('reviewer_id') + ->setAllowedTypes('reviewer_id', 'integer'); + $resolver->setDefined('reviewer_username'); + $resolver->setDefined('my_reaction_emoji'); + + $resolver->setDefined('search'); + $resolver->setDefined('source_branch'); + $resolver->setDefined('target_branch'); + $resolver->setDefined('with_merge_status_recheck') + ->setAllowedTypes('with_merge_status_recheck', 'bool') + ; + $resolver->setDefined('approved_by_ids') + ->setAllowedTypes('approved_by_ids', 'array') + ->setAllowedValues('approved_by_ids', function (array $value) { + return \count($value) === \count(\array_filter($value, 'is_int')); + }) + ; + + return $this->get('groups/'.self::encodePath($group_id).'/merge_requests', $resolver->resolve($parameters)); + } + + /** + * @param array $parameters { + * + * @var string $state Return opened, upcoming, current (previously started), closed, or all iterations. + * Filtering by started state is deprecated starting with 14.1, please use current instead. + * @var string $search return only iterations with a title matching the provided string + * @var bool $include_ancestors Include iterations from parent group and its ancestors. Defaults to true. + * } + */ + public function iterations(int|string $group_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $booleanNormalizer = function (Options $resolver, $value): string { + return $value ? 'true' : 'false'; + }; + + $resolver->setDefined('state') + ->setAllowedValues('state', ['opened', 'upcoming', 'current', 'current (previously started)', 'closed', 'all']) + ; + $resolver->setDefined('include_ancestors') + ->setAllowedTypes('include_ancestors', 'bool') + ->setNormalizer('include_ancestors', $booleanNormalizer) + ->setDefault('include_ancestors', true) + ; + + return $this->get('groups/'.self::encodePath($group_id).'/iterations', $resolver->resolve($parameters)); + } + + /** + * @param array $parameters { + * + * @var bool $exclude_subgroups if the parameter is included as true, packages from projects from subgroups + * are not listed. default is false. + * @var string $order_by the field to use as order. one of created_at (default), name, version, type, + * or project_path. + * @var string $sort the direction of the order, either asc (default) for ascending order + * or desc for descending order + * @var string $package_type filter the returned packages by type. one of conan, maven, npm, pypi, + * composer, nuget, or golang. + * @var string $package_name filter the project packages with a fuzzy search by name + * @var bool $include_versionless when set to true, versionless packages are included in the response + * @var string $status filter the returned packages by status. one of default (default), + * hidden, or processing. + * } + */ + public function packages(int|string $group_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $booleanNormalizer = function (Options $resolver, $value): string { + return $value ? 'true' : 'false'; + }; + + $resolver->setDefined('exclude_subgroups') + ->setAllowedTypes('exclude_subgroups', 'bool') + ->setNormalizer('exclude_subgroups', $booleanNormalizer) + ; + $resolver->setDefined('order_by') + ->setAllowedValues('order_by', ['created_at', 'name', 'version', 'type']) + ; + $resolver->setDefined('sort') + ->setAllowedValues('sort', ['asc', 'desc']) + ; + $resolver->setDefined('package_type') + ->setAllowedValues('package_type', ['conan', 'maven', 'npm', 'pypi', 'composer', 'nuget', 'golang']) + ; + $resolver->setDefined('package_name'); + $resolver->setDefined('include_versionless') + ->setAllowedTypes('include_versionless', 'bool') + ->setNormalizer('include_versionless', $booleanNormalizer) + ; + $resolver->setDefined('status') + ->setAllowedValues('status', ['default', 'hidden', 'processing']) + ; + + return $this->get('groups/'.self::encodePath($group_id).'/packages', $resolver->resolve($parameters)); + } + + private function getGroupSearchResolver(): OptionsResolver + { + $resolver = $this->getSubgroupSearchResolver(); + $booleanNormalizer = function (Options $resolver, $value): string { + return $value ? 'true' : 'false'; + }; + + $resolver->setDefined('top_level_only') + ->setAllowedTypes('top_level_only', 'bool') + ->setNormalizer('top_level_only', $booleanNormalizer) + ; + + return $resolver; + } + + private function getSubgroupSearchResolver(): OptionsResolver + { + $resolver = $this->createOptionsResolver(); + $booleanNormalizer = function (Options $resolver, $value): string { + return $value ? 'true' : 'false'; + }; + + $resolver->setDefined('skip_groups') + ->setAllowedTypes('skip_groups', 'array') + ->setAllowedValues('skip_groups', function (array $value) { + return \count($value) === \count(\array_filter($value, 'is_int')); + }) + ; + $resolver->setDefined('all_available') + ->setAllowedTypes('all_available', 'bool') + ->setNormalizer('all_available', $booleanNormalizer) + ; + $resolver->setDefined('search'); + $resolver->setDefined('order_by') + ->setAllowedValues('order_by', ['name', 'path']) + ; + $resolver->setDefined('sort') + ->setAllowedValues('sort', ['asc', 'desc']) + ; + $resolver->setDefined('statistics') + ->setAllowedTypes('statistics', 'bool') + ->setNormalizer('statistics', $booleanNormalizer) + ; + $resolver->setDefined('owned') + ->setAllowedTypes('owned', 'bool') + ->setNormalizer('owned', $booleanNormalizer) + ; + $resolver->setDefined('min_access_level') + ->setAllowedValues('min_access_level', [null, 10, 20, 30, 40, 50]) + ; + + return $resolver; + } + + public function deployTokens(int|string $group_id, ?bool $active = null): mixed + { + return $this->get('groups/'.self::encodePath($group_id).'/deploy_tokens', (null !== $active) ? ['active' => $active] : []); + } + + /** + * @param array $parameters { + * + * @var string $name the name of the deploy token + * @var \DateTimeInterface $expires_at expiration date for the deploy token, does not expire if no value is provided + * @var string $username the username for the deploy token + * @var array $scopes the scopes, one or many of: read_repository, read_registry, write_registry, read_package_registry, write_package_registry + * } + */ + public function createDeployToken(int|string $group_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $datetimeNormalizer = function (Options $resolver, \DateTimeInterface $value): string { + return $value->format('c'); + }; + + $resolver->define('name') + ->required() + ; + + $resolver->define('scopes') + ->required() + ->allowedTypes('array') + ->allowedValues(function ($scopes) { + $allowed = ['read_repository', 'read_registry', 'write_registry', 'read_package_registry', 'write_package_registry']; + foreach ($scopes as $scope) { + if (!\in_array($scope, $allowed, true)) { + return false; + } + } + + return true; + }) + ; + $resolver->setDefined('username') + ->setAllowedTypes('username', 'string') + ; + + $resolver->setDefined('expires_at') + ->setAllowedTypes('expires_at', \DateTimeInterface::class) + ->setNormalizer('expires_at', $datetimeNormalizer) + ; + + return $this->post('groups/'.self::encodePath($group_id).'/deploy_tokens', $resolver->resolve($parameters)); + } + + public function deleteDeployToken(int|string $group_id, int $token_id): mixed + { + return $this->delete('groups/'.self::encodePath($group_id).'/deploy_tokens/'.self::encodePath($token_id)); + } + + /** + * @param array $parameters { + * + * @var string $scope The scope to search in + * @var string $search The search query + * @var string $state Filter by state. Issues and merge requests are supported; it is ignored for other scopes. + * @var bool $confidential Filter by confidentiality. Issues scope is supported; it is ignored for other scopes. + * @var string $order_by Allowed values are created_at only. If this is not set, the results are either sorted by created_at in descending order for basic search, or by the most relevant documents when using advanced search. + * @var string $sort Return projects sorted in asc or desc order (default is desc) + * } + * + * @throws UndefinedOptionsException If an option name is undefined + * @throws InvalidOptionsException If an option doesn't fulfill the specified validation rules + */ + public function search(int|string $id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $booleanNormalizer = function (Options $resolver, $value): string { + return $value ? 'true' : 'false'; + }; + $resolver->setDefined('confidential') + ->setAllowedTypes('confidential', 'bool') + ->setNormalizer('confidential', $booleanNormalizer); + $scope = [ + 'issues', + 'merge_requests', + 'milestones', + 'projects', + 'users', + 'blobs', + 'commits', + 'notes', + 'wiki_blobs', + ]; + $resolver->setRequired('scope') + ->setAllowedValues('scope', $scope); + $resolver->setRequired('search'); + $resolver->setDefined('order_by') + ->setAllowedValues('order_by', ['created_at']); + $resolver->setDefined('sort') + ->setAllowedValues('sort', ['asc', 'desc']); + $resolver->setDefined('state') + ->setAllowedValues('state', ['opened', 'closed']); + + return $this->get('groups/'.self::encodePath($id).'/search', $resolver->resolve($parameters)); + } +} diff --git a/src/Api/GroupsBoards.php b/src/Api/GroupsBoards.php new file mode 100644 index 000000000..a3fa67421 --- /dev/null +++ b/src/Api/GroupsBoards.php @@ -0,0 +1,80 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +class GroupsBoards extends AbstractApi +{ + public function all(int|string|null $group_id = null, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + + $path = null === $group_id ? 'boards' : 'groups/'.self::encodePath($group_id).'/boards'; + + return $this->get($path, $resolver->resolve($parameters)); + } + + public function show(int|string $group_id, int $board_id): mixed + { + return $this->get('groups/'.self::encodePath($group_id).'/boards/'.self::encodePath($board_id)); + } + + public function create(int|string $group_id, array $params): mixed + { + return $this->post('groups/'.self::encodePath($group_id).'/boards', $params); + } + + public function update(int|string $group_id, int $board_id, array $params): mixed + { + return $this->put('groups/'.self::encodePath($group_id).'/boards/'.self::encodePath($board_id), $params); + } + + public function remove(int|string $group_id, int $board_id): mixed + { + return $this->delete('groups/'.self::encodePath($group_id).'/boards/'.self::encodePath($board_id)); + } + + public function allLists(int|string $group_id, int $board_id): mixed + { + return $this->get('groups/'.self::encodePath($group_id).'/boards/'.self::encodePath($board_id).'/lists'); + } + + public function showList(int|string $group_id, int $board_id, int $list_id): mixed + { + return $this->get('groups/'.self::encodePath($group_id).'/boards/'.self::encodePath($board_id).'/lists/'.self::encodePath($list_id)); + } + + public function createList(int|string $group_id, int $board_id, int $label_id): mixed + { + $params = [ + 'label_id' => $label_id, + ]; + + return $this->post('groups/'.self::encodePath($group_id).'/boards/'.self::encodePath($board_id).'/lists', $params); + } + + public function updateList(int|string $group_id, int $board_id, int $list_id, int $position): mixed + { + $params = [ + 'position' => $position, + ]; + + return $this->put('groups/'.self::encodePath($group_id).'/boards/'.self::encodePath($board_id).'/lists/'.self::encodePath($list_id), $params); + } + + public function deleteList(int|string $group_id, int $board_id, int $list_id): mixed + { + return $this->delete('groups/'.self::encodePath($group_id).'/boards/'.self::encodePath($board_id).'/lists/'.self::encodePath($list_id)); + } +} diff --git a/src/Api/GroupsEpics.php b/src/Api/GroupsEpics.php new file mode 100644 index 000000000..603aef6e0 --- /dev/null +++ b/src/Api/GroupsEpics.php @@ -0,0 +1,83 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +class GroupsEpics extends AbstractApi +{ + /** + * @var string + */ + public const STATE_ALL = 'all'; + + /** + * @var string + */ + public const STATE_OPENED = 'opened'; + + /** + * @var string + */ + public const STATE_CLOSED = 'closed'; + + /** + * @param array $parameters { + * + * @var int[] $iids return only the epics having the given iids + * @var string $state return only active or closed epics + * @var string $search Return only epics with a title or description matching the provided string. + * } + */ + public function all(int|string $group_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $resolver->setDefined('iids') + ->setAllowedTypes('iids', 'array') + ->setAllowedValues('iids', function (array $value) { + return \count($value) === \count(\array_filter($value, 'is_int')); + }) + ; + $resolver->setDefined('state') + ->setAllowedValues('state', [self::STATE_ALL, self::STATE_OPENED, self::STATE_CLOSED]) + ; + $resolver->setDefined('search'); + + return $this->get('groups/'.self::encodePath($group_id).'/epics', $resolver->resolve($parameters)); + } + + public function show(int|string $group_id, int $epic_id): mixed + { + return $this->get('groups/'.self::encodePath($group_id).'/epics/'.self::encodePath($epic_id)); + } + + public function create(int|string $group_id, array $params): mixed + { + return $this->post('groups/'.self::encodePath($group_id).'/epics', $params); + } + + public function update(int|string $group_id, int $epic_id, array $params): mixed + { + return $this->put('groups/'.self::encodePath($group_id).'/epics/'.self::encodePath($epic_id), $params); + } + + public function remove(int|string $group_id, int $epic_id): mixed + { + return $this->delete('groups/'.self::encodePath($group_id).'/epics/'.self::encodePath($epic_id)); + } + + public function issues(int|string $group_id, int $epic_iid): mixed + { + return $this->get('groups/'.self::encodePath($group_id).'/epics/'.self::encodePath($epic_iid).'/issues'); + } +} diff --git a/src/Api/GroupsMilestones.php b/src/Api/GroupsMilestones.php new file mode 100644 index 000000000..50fac6674 --- /dev/null +++ b/src/Api/GroupsMilestones.php @@ -0,0 +1,99 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +use Symfony\Component\OptionsResolver\Options; + +class GroupsMilestones extends AbstractApi +{ + /** + * @var string + */ + public const STATE_ACTIVE = 'active'; + + /** + * @var string + */ + public const STATE_CLOSED = 'closed'; + + /** + * @param array $parameters { + * + * @var int[] $iids return only the milestones having the given iids + * @var string $state return only active or closed milestones + * @var string $search Return only milestones with a title or description matching the provided string + * @var \DateTimeInterface $updated_after Return only milestones updated on or after the given datetime. Expected in ISO 8601 format (2019-03-15T08:00:00Z) + * @var \DateTimeInterface $updated_before Return only milestones updated on or before the given datetime. Expected in ISO 8601 format (2019-03-15T08:00:00Z) + * } + */ + public function all(int|string $group_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $datetimeNormalizer = function (Options $resolver, \DateTimeInterface $value): string { + $utc = (new \DateTimeImmutable($value->format(\DateTimeImmutable::RFC3339_EXTENDED)))->setTimezone(new \DateTimeZone('UTC')); + + return $utc->format('Y-m-d\TH:i:s.v\Z'); + }; + $resolver->setDefined('iids') + ->setAllowedTypes('iids', 'array') + ->setAllowedValues('iids', function (array $value) { + return \count($value) === \count(\array_filter($value, 'is_int')); + }) + ; + $resolver->setDefined('state') + ->setAllowedValues('state', [self::STATE_ACTIVE, self::STATE_CLOSED]) + ; + $resolver->setDefined('search'); + + $resolver->setDefined('updated_after') + ->setAllowedTypes('updated_after', \DateTimeInterface::class) + ->setNormalizer('updated_after', $datetimeNormalizer); + $resolver->setDefined('updated_before') + ->setAllowedTypes('updated_before', \DateTimeInterface::class) + ->setNormalizer('updated_before', $datetimeNormalizer); + + return $this->get('groups/'.self::encodePath($group_id).'/milestones', $resolver->resolve($parameters)); + } + + public function show(int|string $group_id, int $milestone_id): mixed + { + return $this->get('groups/'.self::encodePath($group_id).'/milestones/'.self::encodePath($milestone_id)); + } + + public function create(int|string $group_id, array $params): mixed + { + return $this->post('groups/'.self::encodePath($group_id).'/milestones', $params); + } + + public function update(int|string $group_id, int $milestone_id, array $params): mixed + { + return $this->put('groups/'.self::encodePath($group_id).'/milestones/'.self::encodePath($milestone_id), $params); + } + + public function remove(int|string $group_id, int $milestone_id): mixed + { + return $this->delete('groups/'.self::encodePath($group_id).'/milestones/'.self::encodePath($milestone_id)); + } + + public function issues(int|string $group_id, int $milestone_id): mixed + { + return $this->get('groups/'.self::encodePath($group_id).'/milestones/'.self::encodePath($milestone_id).'/issues'); + } + + public function mergeRequests(int|string $group_id, int $milestone_id): mixed + { + return $this->get('groups/'.self::encodePath($group_id).'/milestones/'.self::encodePath($milestone_id).'/merge_requests'); + } +} diff --git a/src/Api/IssueBoards.php b/src/Api/IssueBoards.php new file mode 100644 index 000000000..369de96f7 --- /dev/null +++ b/src/Api/IssueBoards.php @@ -0,0 +1,80 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +class IssueBoards extends AbstractApi +{ + public function all(int|string|null $project_id = null, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + + $path = null === $project_id ? 'boards' : $this->getProjectPath($project_id, 'boards'); + + return $this->get($path, $resolver->resolve($parameters)); + } + + public function show(int|string $project_id, int $board_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'boards/'.self::encodePath($board_id))); + } + + public function create(int|string $project_id, array $params): mixed + { + return $this->post($this->getProjectPath($project_id, 'boards'), $params); + } + + public function update(int|string $project_id, int $board_id, array $params): mixed + { + return $this->put($this->getProjectPath($project_id, 'boards/'.self::encodePath($board_id)), $params); + } + + public function remove(int|string $project_id, int $board_id): mixed + { + return $this->delete($this->getProjectPath($project_id, 'boards/'.self::encodePath($board_id))); + } + + public function allLists(int|string $project_id, int $board_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'boards/'.self::encodePath($board_id).'/lists')); + } + + public function showList(int|string $project_id, int $board_id, int $list_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'boards/'.self::encodePath($board_id).'/lists/'.self::encodePath($list_id))); + } + + public function createList(int|string $project_id, int $board_id, int $label_id): mixed + { + $params = [ + 'label_id' => $label_id, + ]; + + return $this->post($this->getProjectPath($project_id, 'boards/'.self::encodePath($board_id).'/lists'), $params); + } + + public function updateList(int|string $project_id, int $board_id, int $list_id, int $position): mixed + { + $params = [ + 'position' => $position, + ]; + + return $this->put($this->getProjectPath($project_id, 'boards/'.self::encodePath($board_id).'/lists/'.self::encodePath($list_id)), $params); + } + + public function deleteList(int|string $project_id, int $board_id, int $list_id): mixed + { + return $this->delete($this->getProjectPath($project_id, 'boards/'.self::encodePath($board_id).'/lists/'.self::encodePath($list_id))); + } +} diff --git a/src/Api/IssueLinks.php b/src/Api/IssueLinks.php new file mode 100644 index 000000000..d5c8399f7 --- /dev/null +++ b/src/Api/IssueLinks.php @@ -0,0 +1,48 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +class IssueLinks extends AbstractApi +{ + public function all(int|string $project_id, int $issue_iid): mixed + { + return $this->get($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid)).'/links'); + } + + /** + * @param array $parameters { + * + * @var string $link_type + * } + */ + public function create(int|string $project_id, int $issue_iid, int|string $target_project_id, int $target_issue_iid, array $parameters = []): mixed + { + $parameters['target_project_id'] = $target_project_id; + $parameters['target_issue_iid'] = $target_issue_iid; + + return $this->post($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid).'/links'), $parameters); + } + + /** + * @param array $parameters { + * + * @var string $link_type + * } + */ + public function remove(int|string $project_id, int $issue_iid, int|string $issue_link_id, array $parameters = []): mixed + { + return $this->delete($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid)).'/links/'.self::encodePath($issue_link_id), $parameters); + } +} diff --git a/src/Api/Issues.php b/src/Api/Issues.php new file mode 100644 index 000000000..10f74f261 --- /dev/null +++ b/src/Api/Issues.php @@ -0,0 +1,306 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class Issues extends AbstractApi +{ + /** + * @var string + */ + public const STATE_OPENED = 'opened'; + + /** + * @var string + */ + public const STATE_CLOSED = 'closed'; + + /** + * @param array $parameters { + * + * @var string $state return all issues or just those that are opened or closed + * @var string $labels comma-separated list of label names, issues must have all labels to be returned + * @var bool $with_labels_details if true, response will return more details for each label + * @var string $milestone the milestone title + * @var string $scope return issues for the given scope: created-by-me, assigned-to-me or all (default is created-by-me) + * @var int[] $iids return only the issues having the given iid + * @var string $order_by return requests ordered by created_at or updated_at fields (default is created_at) + * @var string $sort return requests sorted in asc or desc order (default is desc) + * @var bool $confidential filter confidential or public issues + * @var string $search search issues against their title and description + * @var int $author_id filter issues assigned to the specified author user id + * @var int $assignee_id filter issues assigned to the specified assignee user id + * @var int $iteration_id filter issues assigned to the specified iteration id + * @var string $iteration_title filter issues assigned to the specified iteration title + * } + */ + public function all(int|string|null $project_id = null, array $parameters = []): mixed + { + $path = null === $project_id ? 'issues' : $this->getProjectPath($project_id, 'issues'); + + return $this->get($path, $this->createOptionsResolver()->resolve($parameters)); + } + + public function group(int|string $group_id, array $parameters = []): mixed + { + return $this->get( + 'groups/'.self::encodePath($group_id).'/issues', + $this->createOptionsResolver()->resolve($parameters) + ); + } + + public function show(int|string $project_id, int $issue_iid): mixed + { + return $this->get($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid))); + } + + public function create(int|string $project_id, array $params): mixed + { + return $this->post($this->getProjectPath($project_id, 'issues'), $params); + } + + public function update(int|string $project_id, int $issue_iid, array $params): mixed + { + return $this->put($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid)), $params); + } + + public function reorder(int|string $project_id, int $issue_iid, array $params): mixed + { + return $this->put($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid)).'/reorder', $params); + } + + public function move(int|string $project_id, int $issue_iid, int|string $to_project_id): mixed + { + return $this->post($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid)).'/move', [ + 'to_project_id' => $to_project_id, + ]); + } + + public function remove(int|string $project_id, int $issue_iid): mixed + { + return $this->delete($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid))); + } + + public function showNotes(int|string $project_id, int $issue_iid): mixed + { + return $this->get($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid).'/notes')); + } + + public function showNote(int|string $project_id, int $issue_iid, int $note_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid).'/notes/'.self::encodePath($note_id))); + } + + public function addNote(int|string $project_id, int $issue_iid, string $body, array $params = []): mixed + { + $params['body'] = $body; + + return $this->post($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid).'/notes'), $params); + } + + public function updateNote(int|string $project_id, int $issue_iid, int $note_id, string $body, array $params = []): mixed + { + $params['body'] = $body; + + return $this->put($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid).'/notes/'.self::encodePath($note_id)), $params); + } + + public function removeNote(int|string $project_id, int $issue_iid, int $note_id): mixed + { + return $this->delete($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid).'/notes/'.self::encodePath($note_id))); + } + + public function showDiscussions(int|string $project_id, int $issue_iid): mixed + { + return $this->get($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid)).'/discussions'); + } + + public function showDiscussion(int|string $project_id, int $issue_iid, string $discussion_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid)).'/discussions/'.self::encodePath($discussion_id)); + } + + public function addDiscussion(int|string $project_id, int $issue_iid, string $body): mixed + { + return $this->post($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid).'/discussions'), ['body' => $body]); + } + + public function addDiscussionNote(int|string $project_id, int $issue_iid, string $discussion_id, string $body): mixed + { + return $this->post($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid).'/discussions/'.self::encodePath($discussion_id).'/notes'), ['body' => $body]); + } + + public function updateDiscussionNote(int|string $project_id, int $issue_iid, string $discussion_id, int $note_id, string $body): mixed + { + return $this->put($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid).'/discussions/'.self::encodePath($discussion_id).'/notes/'.self::encodePath($note_id)), [ + 'body' => $body, + ]); + } + + public function removeDiscussionNote(int|string $project_id, int $issue_iid, string $discussion_id, int $note_id): mixed + { + return $this->delete($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid).'/discussions/'.self::encodePath($discussion_id).'/notes/'.self::encodePath($note_id))); + } + + public function setTimeEstimate(int|string $project_id, int $issue_iid, string $duration): mixed + { + return $this->post($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid).'/time_estimate'), ['duration' => $duration]); + } + + public function resetTimeEstimate(int|string $project_id, int $issue_iid): mixed + { + return $this->post($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid).'/reset_time_estimate')); + } + + public function addSpentTime(int|string $project_id, int $issue_iid, string $duration): mixed + { + return $this->post($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid).'/add_spent_time'), ['duration' => $duration]); + } + + public function resetSpentTime(int|string $project_id, int $issue_iid): mixed + { + return $this->post($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid).'/reset_spent_time')); + } + + public function getTimeStats(int|string $project_id, int $issue_iid): mixed + { + return $this->get($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid).'/time_stats')); + } + + /** + * Subscribes the authenticated user to an issue to receive notifications. + * If the user is already subscribed to the issue, the status code 304 is returned. + * + * @see https://docs.gitlab.com/ee/api/issues.html#subscribe-to-an-issue + * + * @param int|string $project_id The ID or URL-encoded path of the project owned by the authenticated user + * @param int $issue_iid The internal ID of a project’s issue + */ + public function subscribe(int|string $project_id, int $issue_iid): mixed + { + return $this->post($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid).'/subscribe')); + } + + /** + * Unsubscribes the authenticated user from the issue to not receive notifications from it. + * If the user is not subscribed to the issue, the status code 304 is returned. + * + * @see https://docs.gitlab.com/ee/api/issues.html#unsubscribe-from-an-issue + * + * @param int|string $project_id The ID or URL-encoded path of the project owned by the authenticated user + * @param int $issue_iid The internal ID of a project’s issue + */ + public function unsubscribe(int|string $project_id, int $issue_iid): mixed + { + return $this->post($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid).'/unsubscribe')); + } + + public function awardEmoji(int|string $project_id, int $issue_iid): mixed + { + return $this->get($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid).'/award_emoji')); + } + + public function removeAwardEmoji(int|string $project_id, int $issue_iid, int $award_id): mixed + { + return $this->delete($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid).'/award_emoji/'.self::encodePath($award_id))); + } + + public function closedByMergeRequests(int|string $project_id, int $issue_iid): mixed + { + return $this->get($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid)).'/closed_by'); + } + + public function relatedMergeRequests(int|string $project_id, int $issue_iid): mixed + { + return $this->get($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid).'/related_merge_requests')); + } + + public function showParticipants(int|string $project_id, int $issue_iid): mixed + { + return $this->get($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid)).'/participants'); + } + + public function showResourceLabelEvents(int|string $project_id, int $issue_iid): mixed + { + return $this->get($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid)).'/resource_label_events'); + } + + public function showResourceLabelEvent(int|string $project_id, int $issue_iid, int $resource_label_event_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'issues/'.self::encodePath($issue_iid)).'/resource_label_events/'.self::encodePath($resource_label_event_id)); + } + + protected function createOptionsResolver(): OptionsResolver + { + $resolver = parent::createOptionsResolver(); + $booleanNormalizer = function (Options $resolver, $value): string { + return $value ? 'true' : 'false'; + }; + + $resolver->setDefined('state') + ->setAllowedValues('state', [self::STATE_OPENED, self::STATE_CLOSED]) + ; + $resolver->setDefined('labels'); + $resolver->setDefined('milestone'); + $resolver->setDefined('milestone_id') + ->setAllowedTypes('milestone_id', 'integer'); + $resolver->setDefined('with_labels_details') + ->setAllowedTypes('with_labels_details', 'bool') + ->setNormalizer('with_labels_details', $booleanNormalizer) + ; + $resolver->setDefined('iids') + ->setAllowedTypes('iids', 'array') + ->setAllowedValues('iids', function (array $value) { + return \count($value) === \count(\array_filter($value, 'is_int')); + }) + ; + $resolver->setDefined('scope') + ->setAllowedValues('scope', ['created-by-me', 'assigned-to-me', 'all']) + ; + $resolver->setDefined('order_by') + ->setAllowedValues('order_by', ['created_at', 'updated_at', 'priority', 'due_date', 'relative_position', 'label_priority', 'milestone_due', 'popularity', 'weight']) + ; + $resolver->setDefined('sort') + ->setAllowedValues('sort', ['asc', 'desc']) + ; + $resolver->setDefined('confidential') + ->setAllowedValues('confidential', [false, true]) + ; + $resolver->setDefined('search'); + $resolver->setDefined('created_after'); + $resolver->setDefined('created_before'); + $resolver->setDefined('updated_after'); + $resolver->setDefined('updated_before'); + $resolver->setDefined('author_id') + ->setAllowedTypes('author_id', 'integer') + ; + $resolver->setDefined('assignee_id') + ->setAllowedTypes('assignee_id', 'integer') + ; + $resolver->setDefined('iteration_id') + ->setAllowedTypes('iteration_id', 'integer') + ; + $resolver->setDefined('iteration_title') + ->setAllowedTypes('iteration_title', 'string') + ; + $resolver->setDefined('weight') + ->setAllowedTypes('weight', 'integer') + ; + + return $resolver; + } +} diff --git a/src/Api/IssuesStatistics.php b/src/Api/IssuesStatistics.php new file mode 100644 index 000000000..33e459ef8 --- /dev/null +++ b/src/Api/IssuesStatistics.php @@ -0,0 +1,98 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class IssuesStatistics extends AbstractApi +{ + public function all(array $parameters): mixed + { + return $this->get('issues_statistics', $this->createOptionsResolver()->resolve($parameters)); + } + + public function project(int|string $project_id, array $parameters): mixed + { + return $this->get($this->getProjectPath($project_id, 'issues_statistics'), $this->createOptionsResolver()->resolve($parameters)); + } + + public function group(int|string $group_id, array $parameters): mixed + { + return $this->get('groups/'.self::encodePath($group_id).'/issues_statistics', $this->createOptionsResolver()->resolve($parameters)); + } + + protected function createOptionsResolver(): OptionsResolver + { + $resolver = new OptionsResolver(); + + $resolver->setDefined('milestone') + ->setAllowedTypes('milestone', 'string'); + + $resolver->setDefined('labels') + ->setAllowedTypes('labels', 'string'); + + $resolver->setDefined('scope') + ->setAllowedValues('scope', ['created-by-me', 'assigned-to-me', 'all']); + + $resolver->setDefined('author_id') + ->setAllowedTypes('author_id', 'integer'); + + $resolver->setDefined('author_username') + ->setAllowedTypes('author_username', 'string'); + + $resolver->setDefined('assignee_id') + ->setAllowedTypes('assignee_id', 'integer'); + + $resolver->setDefined('assignee_username') + ->setAllowedTypes('assignee_username', 'string'); + + $resolver->setDefined('my_reaction_emoji') + ->setAllowedTypes('my_reaction_emoji', 'string'); + + $resolver->setDefined('search') + ->setAllowedTypes('search', 'string'); + + $datetimeNormalizer = function (Options $resolver, \DateTimeInterface $value): string { + return $value->format('c'); + }; + + $resolver->setDefined('created_after') + ->setAllowedTypes('created_after', \DateTimeInterface::class) + ->setNormalizer('created_after', $datetimeNormalizer); + + $resolver->setDefined('created_before') + ->setAllowedTypes('created_before', \DateTimeInterface::class) + ->setNormalizer('created_before', $datetimeNormalizer); + + $resolver->setDefined('updated_after') + ->setAllowedTypes('updated_after', \DateTimeInterface::class) + ->setNormalizer('updated_after', $datetimeNormalizer); + + $resolver->setDefined('updated_before') + ->setAllowedTypes('updated_before', \DateTimeInterface::class) + ->setNormalizer('updated_before', $datetimeNormalizer); + + $booleanNormalizer = function (Options $resolver, $value): string { + return $value ? 'true' : 'false'; + }; + + $resolver->setDefined('confidential') + ->setAllowedTypes('confidential', 'bool') + ->setNormalizer('confidential', $booleanNormalizer); + + return $resolver; + } +} diff --git a/src/Api/Jobs.php b/src/Api/Jobs.php new file mode 100644 index 000000000..1850e535e --- /dev/null +++ b/src/Api/Jobs.php @@ -0,0 +1,200 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +use Psr\Http\Message\StreamInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class Jobs extends AbstractApi +{ + /** + * @var string + */ + public const SCOPE_CREATED = 'created'; + + /** + * @var string + */ + public const SCOPE_PENDING = 'pending'; + + /** + * @var string + */ + public const SCOPE_RUNNING = 'running'; + + /** + * @var string + */ + public const SCOPE_FAILED = 'failed'; + + /** + * @var string + */ + public const SCOPE_SUCCESS = 'success'; + + /** + * @var string + */ + public const SCOPE_CANCELED = 'canceled'; + + /** + * @var string + */ + public const SCOPE_SKIPPED = 'skipped'; + + /** + * @var string + */ + public const SCOPE_MANUAL = 'manual'; + + /** + * @param array $parameters { + * + * @var string|string[] $scope The scope of jobs to show, one or array of: created, pending, running, failed, + * success, canceled, skipped, manual; showing all jobs if none provided. + * } + */ + public function all(int|string $project_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + + return $this->get('projects/'.self::encodePath($project_id).'/jobs', $resolver->resolve($parameters)); + } + + /** + * @param array $parameters { + * + * @var string|string[] $scope The scope of jobs to show, one or array of: created, pending, running, failed, + * success, canceled, skipped, manual; showing all jobs if none provided. + * } + */ + public function pipelineJobs(int|string $project_id, int $pipeline_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + + return $this->get( + $this->getProjectPath($project_id, 'pipelines/').self::encodePath($pipeline_id).'/jobs', + $resolver->resolve($parameters) + ); + } + + /** + * @param array $parameters { + * + * @var string|string[] $scope The scope of bridge jobs to show, one or array of: created, pending, running, failed, + * success, canceled, skipped, manual; showing all jobs if none provided + * @var bool $include_retried Include retried jobs in the response. Defaults to false. Introduced in GitLab 13.9. + * } + */ + public function pipelineBridges(int|string $project_id, int $pipeline_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + + return $this->get( + $this->getProjectPath($project_id, 'pipelines/').self::encodePath($pipeline_id).'/bridges', + $resolver->resolve($parameters) + ); + } + + public function show(int|string $project_id, int $job_id): mixed + { + return $this->get('projects/'.self::encodePath($project_id).'/jobs/'.self::encodePath($job_id)); + } + + public function artifacts(int|string $project_id, int $job_id): StreamInterface + { + return $this->getAsResponse('projects/'.self::encodePath($project_id).'/jobs/'.self::encodePath($job_id).'/artifacts')->getBody(); + } + + public function artifactsByRefName(int|string $project_id, string $ref_name, string $job_name): StreamInterface + { + return $this->getAsResponse('projects/'.self::encodePath($project_id).'/jobs/artifacts/'.self::encodePath($ref_name).'/download', [ + 'job' => $job_name, + ])->getBody(); + } + + public function artifactByRefName(int|string $project_id, string $ref_name, string $job_name, string $artifact_path): StreamInterface + { + return $this->getAsResponse('projects/'.self::encodePath($project_id).'/jobs/artifacts/'.self::encodePath($ref_name).'/raw/'.self::encodePath($artifact_path), [ + 'job' => $job_name, + ])->getBody(); + } + + public function artifactByJobId(int|string $project_id, int $job_id, string $artifact_path): StreamInterface + { + return $this->getAsResponse('projects/'.self::encodePath($project_id).'/jobs/'.self::encodePath($job_id).'/artifacts/'.self::encodePath($artifact_path))->getBody(); + } + + public function trace(int|string $project_id, int $job_id): mixed + { + return $this->get('projects/'.self::encodePath($project_id).'/jobs/'.self::encodePath($job_id).'/trace'); + } + + public function cancel(int|string $project_id, int $job_id): mixed + { + return $this->post('projects/'.self::encodePath($project_id).'/jobs/'.self::encodePath($job_id).'/cancel'); + } + + public function retry(int|string $project_id, int $job_id): mixed + { + return $this->post('projects/'.self::encodePath($project_id).'/jobs/'.self::encodePath($job_id).'/retry'); + } + + public function erase(int|string $project_id, int $job_id): mixed + { + return $this->post('projects/'.self::encodePath($project_id).'/jobs/'.self::encodePath($job_id).'/erase'); + } + + public function keepArtifacts(int|string $project_id, int $job_id): mixed + { + return $this->post('projects/'.self::encodePath($project_id).'/jobs/'.self::encodePath($job_id).'/artifacts/keep'); + } + + public function play(int|string $project_id, int $job_id): mixed + { + return $this->post('projects/'.self::encodePath($project_id).'/jobs/'.self::encodePath($job_id).'/play'); + } + + protected function createOptionsResolver(): OptionsResolver + { + $allowedScopeValues = [ + self::SCOPE_CANCELED, + self::SCOPE_CREATED, + self::SCOPE_FAILED, + self::SCOPE_MANUAL, + self::SCOPE_PENDING, + self::SCOPE_RUNNING, + self::SCOPE_SKIPPED, + self::SCOPE_SUCCESS, + ]; + + $resolver = parent::createOptionsResolver(); + $resolver->setDefined('scope') + ->setAllowedTypes('scope', ['string', 'array']) + ->setAllowedValues('scope', $allowedScopeValues) + ->addAllowedValues('scope', function ($value) use ($allowedScopeValues) { + return \is_array($value) && 0 === \count(\array_diff($value, $allowedScopeValues)); + }) + ->setNormalizer('scope', function (OptionsResolver $resolver, $value) { + return (array) $value; + }) + ; + + $resolver->setDefined('include_retried') + ->setAllowedTypes('include_retried', ['bool']); + + return $resolver; + } +} diff --git a/src/Api/Keys.php b/src/Api/Keys.php new file mode 100644 index 000000000..05f4d4d5b --- /dev/null +++ b/src/Api/Keys.php @@ -0,0 +1,23 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +class Keys extends AbstractApi +{ + public function show(int $id): mixed + { + return $this->get('keys/'.self::encodePath($id)); + } +} diff --git a/src/Api/MergeRequests.php b/src/Api/MergeRequests.php new file mode 100644 index 000000000..83d5b7d07 --- /dev/null +++ b/src/Api/MergeRequests.php @@ -0,0 +1,371 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; +use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException; +use Symfony\Component\OptionsResolver\Options; + +class MergeRequests extends AbstractApi +{ + /** + * @var string + */ + public const STATE_ALL = 'all'; + + /** + * @var string + */ + public const STATE_MERGED = 'merged'; + + /** + * @var string + */ + public const STATE_OPENED = 'opened'; + + /** + * @var string + */ + public const STATE_CLOSED = 'closed'; + + /** + * @var string + */ + public const STATE_LOCKED = 'locked'; + + /** + * @param array $parameters { + * + * @var int[] $iids return the request having the given iid + * @var string $state return all merge requests or just those that are opened, closed, or + * merged + * @var string $scope Return merge requests for the given scope: created-by-me, + * assigned-to-me or all (default is created-by-me) + * @var string $order_by return requests ordered by created_at or updated_at fields (default is created_at) + * @var string $sort return requests sorted in asc or desc order (default is desc) + * @var string $milestone return merge requests for a specific milestone + * @var string $view if simple, returns the iid, URL, title, description, and basic state of merge request + * @var string $labels return merge requests matching a comma separated list of labels + * @var \DateTimeInterface $created_after return merge requests created after the given time (inclusive) + * @var \DateTimeInterface $created_before return merge requests created before the given time (inclusive) + * @var int $reviewer_id return merge requests which have the user as a reviewer with the given user id + * @var bool $wip return only draft merge requests (true) or only non-draft merge requests (false) + * } + * + * @throws UndefinedOptionsException if an option name is undefined + * @throws InvalidOptionsException if an option doesn't fulfill the specified validation rules + */ + public function all(int|string|null $project_id = null, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $datetimeNormalizer = function (Options $resolver, \DateTimeInterface $value): string { + $utc = (new \DateTimeImmutable($value->format(\DateTimeImmutable::RFC3339_EXTENDED)))->setTimezone(new \DateTimeZone('UTC')); + + return $utc->format('Y-m-d\TH:i:s.v\Z'); + }; + $resolver->setDefined('iids') + ->setAllowedTypes('iids', 'array') + ->setAllowedValues('iids', function (array $value) { + return \count($value) === \count(\array_filter($value, 'is_int')); + }) + ; + $resolver->setDefined('state') + ->setAllowedValues('state', [self::STATE_ALL, self::STATE_MERGED, self::STATE_OPENED, self::STATE_CLOSED, self::STATE_LOCKED]) + ; + $resolver->setDefined('scope') + ->setAllowedValues('scope', ['created-by-me', 'assigned-to-me', 'all']) + ; + $resolver->setDefined('order_by') + ->setAllowedValues('order_by', ['created_at', 'updated_at']) + ; + $resolver->setDefined('sort') + ->setAllowedValues('sort', ['asc', 'desc']) + ; + $resolver->setDefined('milestone'); + $resolver->setDefined('view') + ->setAllowedValues('view', ['simple']) + ; + $resolver->setDefined('labels'); + $resolver->setDefined('created_after') + ->setAllowedTypes('created_after', \DateTimeInterface::class) + ->setNormalizer('created_after', $datetimeNormalizer) + ; + $resolver->setDefined('created_before') + ->setAllowedTypes('created_before', \DateTimeInterface::class) + ->setNormalizer('created_before', $datetimeNormalizer) + ; + + $resolver->setDefined('updated_after') + ->setAllowedTypes('updated_after', \DateTimeInterface::class) + ->setNormalizer('updated_after', $datetimeNormalizer) + ; + $resolver->setDefined('updated_before') + ->setAllowedTypes('updated_before', \DateTimeInterface::class) + ->setNormalizer('updated_before', $datetimeNormalizer) + ; + + $resolver->setDefined('scope') + ->setAllowedValues('scope', ['created_by_me', 'assigned_to_me', 'all']) + ; + $resolver->setDefined('author_id') + ->setAllowedTypes('author_id', 'integer'); + + $resolver->setDefined('assignee_id') + ->setAllowedTypes('assignee_id', 'integer'); + + $resolver->setDefined('search'); + $resolver->setDefined('source_branch'); + $resolver->setDefined('target_branch'); + $resolver->setDefined('with_merge_status_recheck') + ->setAllowedTypes('with_merge_status_recheck', 'bool') + ; + $resolver->setDefined('approved_by_ids') + ->setAllowedTypes('approved_by_ids', 'array') + ->setAllowedValues('approved_by_ids', function (array $value) { + return \count($value) === \count(\array_filter($value, 'is_int')); + }) + ; + $resolver->setDefined('reviewer_id') + ->setAllowedTypes('reviewer_id', 'integer'); + $resolver->setDefined('wip') + ->setAllowedTypes('wip', 'boolean') + ->addNormalizer('wip', static function ($resolver, $wip) { + return $wip ? 'yes' : 'no'; + }); + + $path = null === $project_id ? 'merge_requests' : $this->getProjectPath($project_id, 'merge_requests'); + + return $this->get($path, $resolver->resolve($parameters)); + } + + /** + * @param array $parameters { + * + * @var bool $include_diverged_commits_count Return the commits behind the target branch + * @var bool $include_rebase_in_progress Return whether a rebase operation is in progress + * } + */ + public function show(int|string $project_id, int $mr_iid, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $resolver->setDefined('include_diverged_commits_count') + ->setAllowedTypes('include_diverged_commits_count', 'bool') + ; + $resolver->setDefined('include_rebase_in_progress') + ->setAllowedTypes('include_rebase_in_progress', 'bool') + ; + + return $this->get($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid)), $resolver->resolve($parameters)); + } + + /** + * @param array $parameters { + * + * @var int $assignee_id the assignee id + * @var int|string $target_project_id the target project id + * @var string $description the description + * } + */ + public function create(int|string $project_id, string $source, string $target, string $title, array $parameters = []): mixed + { + $baseParams = [ + 'source_branch' => $source, + 'target_branch' => $target, + 'title' => $title, + ]; + + return $this->post( + $this->getProjectPath($project_id, 'merge_requests'), + \array_merge($baseParams, $parameters) + ); + } + + public function update(int|string $project_id, int $mr_iid, array $parameters): mixed + { + return $this->put($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid)), $parameters); + } + + public function merge(int|string $project_id, int $mr_iid, array $parameters = []): mixed + { + return $this->put($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid).'/merge'), $parameters); + } + + public function showNotes(int|string $project_id, int $mr_iid): mixed + { + return $this->get($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid).'/notes')); + } + + public function showNote(int|string $project_id, int $mr_iid, int $note_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid).'/notes/'.self::encodePath($note_id))); + } + + public function addNote(int|string $project_id, int $mr_iid, string $body, array $params = []): mixed + { + $params['body'] = $body; + + return $this->post($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid).'/notes'), $params); + } + + public function updateNote(int|string $project_id, int $mr_iid, int $note_id, string $body): mixed + { + return $this->put($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid).'/notes/'.self::encodePath($note_id)), [ + 'body' => $body, + ]); + } + + public function removeNote(int|string $project_id, int $mr_iid, int $note_id): mixed + { + return $this->delete($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid).'/notes/'.self::encodePath($note_id))); + } + + public function showDiscussions(int|string $project_id, int $mr_iid): mixed + { + return $this->get($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid)).'/discussions'); + } + + public function showDiscussion(int|string $project_id, int $mr_iid, string $discussion_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid)).'/discussions/'.self::encodePath($discussion_id)); + } + + public function addDiscussion(int|string $project_id, int $mr_iid, array $params): mixed + { + return $this->post($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid).'/discussions'), $params); + } + + public function resolveDiscussion(int|string $project_id, int $mr_iid, string $discussion_id, bool $resolved = true): mixed + { + return $this->put($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid).'/discussions/'.self::encodePath($discussion_id)), [ + 'resolved' => $resolved, + ]); + } + + public function addDiscussionNote(int|string $project_id, int $mr_iid, string $discussion_id, string $body): mixed + { + return $this->post($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid).'/discussions/'.self::encodePath($discussion_id).'/notes'), ['body' => $body]); + } + + public function updateDiscussionNote(int|string $project_id, int $mr_iid, string $discussion_id, int $note_id, array $params): mixed + { + return $this->put($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid).'/discussions/'.self::encodePath($discussion_id).'/notes/'.self::encodePath($note_id)), $params); + } + + public function removeDiscussionNote(int|string $project_id, int $mr_iid, string $discussion_id, int $note_id): mixed + { + return $this->delete($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid).'/discussions/'.self::encodePath($discussion_id).'/notes/'.self::encodePath($note_id))); + } + + public function showParticipants(int|string $project_id, int $mr_iid): mixed + { + return $this->get($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid)).'/participants'); + } + + public function changes(int|string $project_id, int $mr_iid): mixed + { + return $this->get($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid).'/changes')); + } + + public function commits(int|string $project_id, int $mr_iid): mixed + { + return $this->get($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid).'/commits')); + } + + public function closesIssues(int|string $project_id, int $mr_iid): mixed + { + return $this->get($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid).'/closes_issues')); + } + + public function approvals(int|string $project_id, int $mr_iid): mixed + { + return $this->get($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid).'/approvals')); + } + + public function approve(int|string $project_id, int $mr_iid): mixed + { + return $this->post($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid).'/approve')); + } + + public function unapprove(int|string $project_id, int $mr_iid): mixed + { + return $this->post($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid).'/unapprove')); + } + + public function awardEmoji(int|string $project_id, int $mr_iid): mixed + { + return $this->get($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid).'/award_emoji')); + } + + public function removeAwardEmoji(int|string $project_id, int $mr_iid, int $award_id): mixed + { + return $this->delete($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid).'/award_emoji/'.self::encodePath($award_id))); + } + + public function rebase(int|string $project_id, int $mr_iid, array $params = []): mixed + { + $resolver = $this->createOptionsResolver(); + $resolver->setDefined('skip_ci') + ->setAllowedTypes('skip_ci', 'bool'); + + return $this->put($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid)).'/rebase', $resolver->resolve($params)); + } + + public function approvalState(int|string $project_id, int $mr_iid): mixed + { + return $this->get($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid).'/approval_state')); + } + + public function levelRules(int|string $project_id, int $mr_iid): mixed + { + return $this->get($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid).'/approval_rules')); + } + + /** + * @param array $parameters + */ + public function createLevelRule(int|string $project_id, int $mr_iid, string $name, int $approvals_required, array $parameters = []): mixed + { + $baseParam = [ + 'name' => $name, + 'approvals_required' => $approvals_required, + ]; + + return $this->post( + $this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid).'/approval_rules'), + \array_merge($baseParam, $parameters) + ); + } + + /** + * @param array $parameters + */ + public function updateLevelRule(int|string $project_id, int $mr_iid, int $approval_rule_id, string $name, int $approvals_required, array $parameters = []): mixed + { + $baseParam = [ + 'name' => $name, + 'approvals_required' => $approvals_required, + ]; + + return $this->put( + $this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid).'/approval_rules/'.self::encodePath($approval_rule_id)), + \array_merge($baseParam, $parameters) + ); + } + + public function deleteLevelRule(int|string $project_id, int $mr_iid, int $approval_rule_id): mixed + { + return $this->delete($this->getProjectPath($project_id, 'merge_requests/'.self::encodePath($mr_iid).'/approval_rules/'.self::encodePath($approval_rule_id))); + } +} diff --git a/src/Api/Milestones.php b/src/Api/Milestones.php new file mode 100644 index 000000000..ee0761505 --- /dev/null +++ b/src/Api/Milestones.php @@ -0,0 +1,83 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +class Milestones extends AbstractApi +{ + /** + * @var string + */ + public const STATE_ACTIVE = 'active'; + + /** + * @var string + */ + public const STATE_CLOSED = 'closed'; + + /** + * @param array $parameters { + * + * @var int[] $iids return only the milestones having the given iids + * @var string $state return only active or closed milestones + * @var string $search Return only milestones with a title or description matching the provided string. + * } + */ + public function all(int|string $project_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $resolver->setDefined('iids') + ->setAllowedTypes('iids', 'array') + ->setAllowedValues('iids', function (array $value) { + return \count($value) === \count(\array_filter($value, 'is_int')); + }) + ; + $resolver->setDefined('state') + ->setAllowedValues('state', [self::STATE_ACTIVE, self::STATE_CLOSED]) + ; + $resolver->setDefined('search'); + + return $this->get($this->getProjectPath($project_id, 'milestones'), $resolver->resolve($parameters)); + } + + public function show(int|string $project_id, int $milestone_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'milestones/'.self::encodePath($milestone_id))); + } + + public function create(int|string $project_id, array $params): mixed + { + return $this->post($this->getProjectPath($project_id, 'milestones'), $params); + } + + public function update(int|string $project_id, int $milestone_id, array $params): mixed + { + return $this->put($this->getProjectPath($project_id, 'milestones/'.self::encodePath($milestone_id)), $params); + } + + public function remove(int|string $project_id, int $milestone_id): mixed + { + return $this->delete($this->getProjectPath($project_id, 'milestones/'.self::encodePath($milestone_id))); + } + + public function issues(int|string $project_id, int $milestone_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'milestones/'.self::encodePath($milestone_id).'/issues')); + } + + public function mergeRequests(int|string $project_id, int $milestone_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'milestones/'.self::encodePath($milestone_id).'/merge_requests')); + } +} diff --git a/src/Api/Packages.php b/src/Api/Packages.php new file mode 100644 index 000000000..4698905f0 --- /dev/null +++ b/src/Api/Packages.php @@ -0,0 +1,102 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +use Symfony\Component\OptionsResolver\Options; + +class Packages extends AbstractApi +{ + /** + * @param array $parameters { + * + * @var string $order_by the field to use as order. one of created_at (default), name, + * version, or type + * @var string $sort the direction of the order, either asc (default) for ascending order or + * desc for descending order + * @var string $package_type filter the returned packages by type. one of conan, maven, npm, pypi, + * composer, nuget, or golang. + * @var string $package_name filter the project packages with a fuzzy search by name + * @var bool $include_versionless when set to true, versionless packages are included in the response + * @var string $status filter the returned packages by status. one of default (default), + * hidden, or processing. + * } + */ + public function all(int|string $project_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + + $resolver->setDefined('order_by') + ->setAllowedValues('order_by', ['created_at', 'name', 'version', 'type']) + ; + $resolver->setDefined('sort') + ->setAllowedValues('sort', ['asc', 'desc']) + ; + $resolver->setDefined('package_type') + ->setAllowedValues('package_type', ['conan', 'maven', 'npm', 'pypi', 'composer', 'nuget', 'golang']) + ; + $resolver->setDefined('package_name'); + $resolver->setDefined('include_versionless') + ->setAllowedTypes('include_versionless', 'bool') + ->setNormalizer('include_versionless', function (Options $resolver, $value): string { + return $value ? 'true' : 'false'; + }) + ; + $resolver->setDefined('status') + ->setAllowedValues('status', ['default', 'hidden', 'processing']) + ; + + return $this->get($this->getProjectPath($project_id, 'packages'), $resolver->resolve($parameters)); + } + + public function show(int|string $project_id, int $package_id): mixed + { + return $this->get($this->getPackagePath($project_id, $package_id)); + } + + public function allFiles(int|string $project_id, int $package_id): mixed + { + return $this->get($this->getPackagePath($project_id, $package_id).'/package_files'); + } + + public function remove(int|string $project_id, int $package_id): mixed + { + return $this->delete($this->getPackagePath($project_id, $package_id)); + } + + public function removeFile(int|string $project_id, int $package_id, int $package_file_id): mixed + { + return $this->delete( + $this->getPackagePath($project_id, $package_id).'/package_files/'.self::encodePath($package_file_id) + ); + } + + public function addGenericFile(int|string $project_id, string $package_name, string $package_version, string $file, string $status = 'default'): mixed + { + return $this->putFile( + $this->getProjectPath( + $project_id, + 'packages/generic/'.self::encodePath($package_name).'/'.self::encodePath($package_version).'/'.self::encodePath(\basename($file)) + ), + $file, + [], + ['status' => $status] + ); + } + + private function getPackagePath(int|string $project_id, int $package_id): string + { + return $this->getProjectPath($project_id, 'packages/'.self::encodePath($package_id)); + } +} diff --git a/src/Api/ProjectNamespaces.php b/src/Api/ProjectNamespaces.php new file mode 100644 index 000000000..d35228ba1 --- /dev/null +++ b/src/Api/ProjectNamespaces.php @@ -0,0 +1,37 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +class ProjectNamespaces extends AbstractApi +{ + /** + * @param array $parameters { + * + * @var string $search Returns a list of namespaces the user is authorized to see based on the search criteria. + * } + */ + public function all(array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $resolver->setDefined('search'); + + return $this->get('namespaces', $resolver->resolve($parameters)); + } + + public function show(int|string $namespace_id): mixed + { + return $this->get('namespaces/'.self::encodePath($namespace_id)); + } +} diff --git a/src/Api/Projects.php b/src/Api/Projects.php new file mode 100644 index 000000000..32aa168a7 --- /dev/null +++ b/src/Api/Projects.php @@ -0,0 +1,1283 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; +use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class Projects extends AbstractApi +{ + /** + * @param array $parameters { + * + * @var bool $archived limit by archived status + * @var string $visibility limit by visibility public, internal, or private + * @var string $order_by Return projects ordered by id, name, path, created_at, updated_at, + * last_activity_at, repository_size, storage_size, packages_size or + * wiki_size fields (default is created_at) + * @var string $sort Return projects sorted in asc or desc order (default is desc) + * @var string $search return list of projects matching the search criteria + * @var bool $search_namespaces Include ancestor namespaces when matching search criteria + * @var bool $simple return only the ID, URL, name, and path of each project + * @var bool $owned limit by projects owned by the current user + * @var bool $membership limit by projects that the current user is a member of + * @var bool $starred limit by projects starred by the current user + * @var bool $statistics include project statistics + * @var bool $with_issues_enabled limit by enabled issues feature + * @var bool $with_merge_requests_enabled limit by enabled merge requests feature + * @var int $min_access_level Limit by current user minimal access level + * @var int $id_after Limit by project id's greater than the specified id + * @var int $id_before Limit by project id's less than the specified id + * @var \DateTimeInterface $last_activity_after Limit by last_activity after specified time + * @var \DateTimeInterface $last_activity_before Limit by last_activity before specified time + * @var bool $repository_checksum_failed Limit by failed repository checksum calculation + * @var string $repository_storage Limit by repository storage type + * @var bool $wiki_checksum_failed Limit by failed wiki checksum calculation + * @var bool $with_custom_attributes Include custom attributes in response + * @var string $with_programming_language Limit by programming language + * @var string $topic Limit by topic + * } + * + * @throws UndefinedOptionsException If an option name is undefined + * @throws InvalidOptionsException If an option doesn't fulfill the specified validation rules + */ + public function all(array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $booleanNormalizer = function (Options $resolver, $value): string { + return $value ? 'true' : 'false'; + }; + $datetimeNormalizer = function (Options $resolver, \DateTimeInterface $value): string { + return $value->format('c'); + }; + $resolver->setDefined('archived') + ->setAllowedTypes('archived', 'bool') + ->setNormalizer('archived', $booleanNormalizer) + ; + $resolver->setDefined('visibility') + ->setAllowedValues('visibility', ['public', 'internal', 'private']) + ; + $orderBy = [ + 'id', 'name', 'path', 'created_at', 'updated_at', 'last_activity_at', + 'repository_size', 'storage_size', 'packages_size', 'wiki_size', + ]; + $resolver->setDefined('order_by') + ->setAllowedValues('order_by', $orderBy) + ; + $resolver->setDefined('sort') + ->setAllowedValues('sort', ['asc', 'desc']) + ; + $resolver->setDefined('search'); + $resolver->setDefined('search_namespaces') + ->setAllowedTypes('search_namespaces', 'bool') + ->setNormalizer('search_namespaces', $booleanNormalizer) + ; + $resolver->setDefined('simple') + ->setAllowedTypes('simple', 'bool') + ->setNormalizer('simple', $booleanNormalizer) + ; + $resolver->setDefined('owned') + ->setAllowedTypes('owned', 'bool') + ->setNormalizer('owned', $booleanNormalizer) + ; + $resolver->setDefined('membership') + ->setAllowedTypes('membership', 'bool') + ->setNormalizer('membership', $booleanNormalizer) + ; + $resolver->setDefined('starred') + ->setAllowedTypes('starred', 'bool') + ->setNormalizer('starred', $booleanNormalizer) + ; + $resolver->setDefined('statistics') + ->setAllowedTypes('statistics', 'bool') + ->setNormalizer('statistics', $booleanNormalizer) + ; + $resolver->setDefined('with_issues_enabled') + ->setAllowedTypes('with_issues_enabled', 'bool') + ->setNormalizer('with_issues_enabled', $booleanNormalizer) + ; + $resolver->setDefined('with_merge_requests_enabled') + ->setAllowedTypes('with_merge_requests_enabled', 'bool') + ->setNormalizer('with_merge_requests_enabled', $booleanNormalizer) + ; + $resolver->setDefined('min_access_level') + ->setAllowedValues('min_access_level', [null, 10, 20, 30, 40, 50]) + ; + $resolver->setDefined('id_after') + ->setAllowedTypes('id_after', 'integer') + ; + $resolver->setDefined('id_before') + ->setAllowedTypes('id_before', 'integer') + ; + $resolver->setDefined('last_activity_after') + ->setAllowedTypes('last_activity_after', \DateTimeInterface::class) + ->setNormalizer('last_activity_after', $datetimeNormalizer) + ; + $resolver->setDefined('last_activity_before') + ->setAllowedTypes('last_activity_before', \DateTimeInterface::class) + ->setNormalizer('last_activity_before', $datetimeNormalizer) + ; + $resolver->setDefined('repository_checksum_failed') + ->setAllowedTypes('repository_checksum_failed', 'bool') + ->setNormalizer('repository_checksum_failed', $booleanNormalizer) + ; + $resolver->setDefined('repository_storage'); + $resolver->setDefined('wiki_checksum_failed') + ->setAllowedTypes('wiki_checksum_failed', 'bool') + ->setNormalizer('wiki_checksum_failed', $booleanNormalizer) + ; + $resolver->setDefined('with_custom_attributes') + ->setAllowedTypes('with_custom_attributes', 'bool') + ->setNormalizer('with_custom_attributes', $booleanNormalizer) + ; + $resolver->setDefined('with_programming_language'); + $resolver->setDefined('topic'); + + return $this->get('projects', $resolver->resolve($parameters)); + } + + /** + * @param array $parameters { + * + * @var bool $statistics include project statistics + * @var bool $with_custom_attributes Include project custom attributes. + * } + */ + public function show(int|string $project_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $booleanNormalizer = function (Options $resolver, $value): bool { + return (bool) $value; + }; + $resolver->setDefined('statistics') + ->setAllowedTypes('statistics', 'bool') + ->setNormalizer('statistics', $booleanNormalizer) + ; + $resolver->setDefined('with_custom_attributes') + ->setAllowedTypes('with_custom_attributes', 'bool') + ->setNormalizer('with_custom_attributes', $booleanNormalizer) + ; + + return $this->get('projects/'.self::encodePath($project_id), $resolver->resolve($parameters)); + } + + public function create(string $name, array $parameters = []): mixed + { + $parameters['name'] = $name; + + return $this->post('projects', $parameters); + } + + public function createForUser(int $user_id, string $name, array $parameters = []): mixed + { + $parameters['name'] = $name; + + return $this->post('projects/user/'.self::encodePath($user_id), $parameters); + } + + public function update(int|string $project_id, array $parameters): mixed + { + return $this->put('projects/'.self::encodePath($project_id), $parameters); + } + + public function remove(int|string $project_id): mixed + { + return $this->delete('projects/'.self::encodePath($project_id)); + } + + public function archive(int|string $project_id): mixed + { + return $this->post('projects/'.self::encodePath($project_id).'/archive'); + } + + public function unarchive(int|string $project_id): mixed + { + return $this->post('projects/'.self::encodePath($project_id).'/unarchive'); + } + + public function triggers(int|string $project_id): mixed + { + return $this->get('projects/'.self::encodePath($project_id).'/triggers'); + } + + public function trigger(int|string $project_id, int $trigger_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'triggers/'.self::encodePath($trigger_id))); + } + + public function createTrigger(int|string $project_id, string $description): mixed + { + return $this->post($this->getProjectPath($project_id, 'triggers'), [ + 'description' => $description, + ]); + } + + public function removeTrigger(int|string $project_id, int $trigger_id): mixed + { + return $this->delete($this->getProjectPath($project_id, 'triggers/'.self::encodePath($trigger_id))); + } + + public function triggerPipeline(int|string $project_id, string $ref, string $token, array $variables = []): mixed + { + return $this->post($this->getProjectPath($project_id, 'trigger/pipeline'), [ + 'ref' => $ref, + 'token' => $token, + 'variables' => $variables, + ]); + } + + public function disableRunner(int $project_id, int $runner_id): mixed + { + return $this->delete('projects/'.self::encodePath($project_id).'/runners/'.self::encodePath($runner_id)); + } + + public function enableRunner(int $project_id, int $runner_id): mixed + { + $parameters = [ + 'runner_id' => $runner_id, + ]; + + return $this->post('projects/'.self::encodePath($project_id).'/runners', $parameters); + } + + /** + * @param array $parameters { + * + * @var string $scope the scope of pipelines, one of: running, pending, finished, branches, tags + * @var string $status the status of pipelines, one of: running, pending, success, failed, canceled, skipped + * @var string $ref the ref of pipelines + * @var string $sha the sha of pipelines + * @var bool $yaml_errors returns pipelines with invalid configurations + * @var string $name the name of the user who triggered pipelines + * @var string $username the username of the user who triggered pipelines + * @var string $order_by order pipelines by id, status, ref, updated_at, or user_id (default: id) + * @var string $order sort pipelines in asc or desc order (default: desc) + * @var string $source the source of the pipeline + * } + */ + public function pipelines(int|string $project_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $booleanNormalizer = function (Options $resolver, $value): string { + return $value ? 'true' : 'false'; + }; + $datetimeNormalizer = function (Options $resolver, \DateTimeInterface $value): string { + return $value->format('Y-m-d'); + }; + + $resolver->setDefined('scope') + ->setAllowedValues('scope', ['running', 'pending', 'finished', 'branches', 'tags']) + ; + $resolver->setDefined('status') + ->setAllowedValues('status', ['running', 'pending', 'success', 'failed', 'canceled', 'skipped']) + ; + $resolver->setDefined('ref'); + $resolver->setDefined('sha'); + $resolver->setDefined('yaml_errors') + ->setAllowedTypes('yaml_errors', 'bool') + ->setNormalizer('yaml_errors', $booleanNormalizer) + ; + $resolver->setDefined('name'); + $resolver->setDefined('username'); + $resolver->setDefined('updated_after') + ->setAllowedTypes('updated_after', \DateTimeInterface::class) + ->setNormalizer('updated_after', $datetimeNormalizer) + ; + $resolver->setDefined('updated_before') + ->setAllowedTypes('updated_before', \DateTimeInterface::class) + ->setNormalizer('updated_before', $datetimeNormalizer) + ; + $resolver->setDefined('order_by') + ->setAllowedValues('order_by', ['id', 'status', 'ref', 'updated_at', 'user_id']) + ; + $resolver->setDefined('sort') + ->setAllowedValues('sort', ['asc', 'desc']) + ; + $resolver->setDefined('source') + ->setAllowedValues('source', ['push', 'web', 'trigger', 'schedule', 'api', 'external', 'pipeline', 'chat', 'webide', 'merge_request_event', 'external_pull_request_event', 'parent_pipeline', 'ondemand_dast_scan', 'ondemand_dast_validation']) + ; + + return $this->get($this->getProjectPath($project_id, 'pipelines'), $resolver->resolve($parameters)); + } + + public function pipeline(int|string $project_id, int $pipeline_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'pipelines/'.self::encodePath($pipeline_id))); + } + + public function pipelineJobs(int|string $project_id, int $pipeline_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'pipelines/'.self::encodePath($pipeline_id).'/jobs')); + } + + public function pipelineVariables(int|string $project_id, int $pipeline_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'pipelines/'.self::encodePath($pipeline_id).'/variables')); + } + + public function pipelineTestReport(int|string $project_id, int $pipeline_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'pipelines/'.self::encodePath($pipeline_id).'/test_report')); + } + + public function pipelineTestReportSummary(int|string $project_id, int $pipeline_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'pipelines/'.self::encodePath($pipeline_id).'/test_report_summary')); + } + + /** + * @param array|null $variables { + * + * @var string $key The name of the variable + * @var mixed $value The value of the variable + * @var string $variable_type env_var (default) or file + * } + */ + public function createPipeline(int|string $project_id, string $commit_ref, ?array $variables = null): mixed + { + $parameters = []; + + if (null !== $variables) { + $parameters['variables'] = $variables; + } + + return $this->post($this->getProjectPath($project_id, 'pipeline'), $parameters, [], [], [ + 'ref' => $commit_ref, + ]); + } + + public function retryPipeline(int|string $project_id, int $pipeline_id): mixed + { + return $this->post($this->getProjectPath($project_id, 'pipelines/'.self::encodePath($pipeline_id)).'/retry'); + } + + public function cancelPipeline(int|string $project_id, int $pipeline_id): mixed + { + return $this->post($this->getProjectPath($project_id, 'pipelines/'.self::encodePath($pipeline_id)).'/cancel'); + } + + public function deletePipeline(int|string $project_id, int $pipeline_id): mixed + { + return $this->delete($this->getProjectPath($project_id, 'pipelines/'.self::encodePath($pipeline_id))); + } + + public function allMembers(int|string $project_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $resolver->setDefined('query'); + $resolver->setDefined('user_ids') + ->setAllowedTypes('user_ids', 'array') + ->setAllowedValues('user_ids', function (array $value) { + return \count($value) === \count(\array_filter($value, 'is_int')); + }) + ; + + return $this->get('projects/'.self::encodePath($project_id).'/members/all', $resolver->resolve($parameters)); + } + + /** + * @param array $parameters { + * + * @var string $query The query you want to search members for. + * } + */ + public function members(int|string $project_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + + $resolver->setDefined('query') + ->setAllowedTypes('query', 'string') + ; + $resolver->setDefined('user_ids') + ->setAllowedTypes('user_ids', 'array') + ->setAllowedValues('user_ids', function (array $value) { + return \count($value) === \count(\array_filter($value, 'is_int')); + }) + ; + + return $this->get($this->getProjectPath($project_id, 'members'), $resolver->resolve($parameters)); + } + + public function member(int|string $project_id, int $user_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'members/'.self::encodePath($user_id))); + } + + public function allMember(int|string $project_id, int $user_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'members/all/'.self::encodePath($user_id))); + } + + public function addMember(int|string $project_id, int $user_id, int $access_level, ?string $expires_at = null): mixed + { + $params = [ + 'user_id' => $user_id, + 'access_level' => $access_level, + ]; + if (null !== $expires_at) { + $params['expires_at'] = $expires_at; + } + + return $this->post($this->getProjectPath($project_id, 'members'), $params); + } + + public function saveMember(int|string $project_id, int $user_id, int $access_level, ?string $expires_at = null): mixed + { + $params = [ + 'access_level' => $access_level, + ]; + if (null !== $expires_at) { + $params['expires_at'] = $expires_at; + } + + return $this->put($this->getProjectPath($project_id, 'members/'.self::encodePath($user_id)), $params); + } + + public function removeMember(int|string $project_id, int $user_id): mixed + { + return $this->delete($this->getProjectPath($project_id, 'members/'.self::encodePath($user_id))); + } + + public function hooks(int|string $project_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + + return $this->get($this->getProjectPath($project_id, 'hooks'), $resolver->resolve($parameters)); + } + + public function hook(int|string $project_id, int $hook_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'hooks/'.self::encodePath($hook_id))); + } + + /** + * Get project users. + * + * See https://docs.gitlab.com/ee/api/projects.html#get-project-users for more info. + */ + public function users(int|string $project_id, array $parameters = []): mixed + { + return $this->get($this->getProjectPath($project_id, 'users'), $parameters); + } + + /** + * Get project issues. + * + * See https://docs.gitlab.com/ee/api/issues.html#list-project-issues for more info. + */ + public function issues(int|string $project_id, array $parameters = []): mixed + { + return $this->get($this->getProjectPath($project_id, 'issues'), $parameters); + } + + /** + * Get projects board list. + * + * See https://docs.gitlab.com/ee/api/boards.html for more info. + */ + public function boards(int|string $project_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'boards')); + } + + /** + * @param array $parameters { + * + * @var string $state Return opened, upcoming, current (previously started), closed, or all iterations. + * Filtering by started state is deprecated starting with 14.1, please use current instead. + * @var string $search return only iterations with a title matching the provided string + * @var bool $include_ancestors Include iterations from parent group and its ancestors. Defaults to true. + * } + */ + public function iterations(int|string $project_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $booleanNormalizer = function (Options $resolver, $value): string { + return $value ? 'true' : 'false'; + }; + + $resolver->setDefined('state') + ->setAllowedValues('state', ['opened', 'upcoming', 'current', 'current (previously started)', 'closed', 'all']) + ; + $resolver->setDefined('include_ancestors') + ->setAllowedTypes('include_ancestors', 'bool') + ->setNormalizer('include_ancestors', $booleanNormalizer) + ->setDefault('include_ancestors', true) + ; + + return $this->get('projects/'.self::encodePath($project_id).'/iterations', $resolver->resolve($parameters)); + } + + /** + * Gets a list of all discussion items for a single commit. + * + * Example: + * - https://gitlab.com/gitlab-org/gitlab/-/commit/695c29abcf7dc2eabde8d59869abcea0923ce8fa#note_334686748 + * - https://gitlab.com/api/v4/projects/gitlab-org%2Fgitlab/repository/commits/695c29abcf7dc2eabde8d59869abcea0923ce8fa/discussions + * + * @see https://docs.gitlab.com/ee/api/discussions.html#list-project-commit-discussion-items + */ + public function getRepositoryCommitDiscussions(int|string $project_id, string $commit_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'repository/commits/'.self::encodePath($commit_id)).'/discussions'); + } + + public function addHook(int|string $project_id, string $url, array $parameters = []): mixed + { + if (0 === \count($parameters)) { + $parameters = ['push_events' => true]; + } + + $parameters['url'] = $url; + + return $this->post($this->getProjectPath($project_id, 'hooks'), $parameters); + } + + public function updateHook(int|string $project_id, int $hook_id, array $parameters): mixed + { + return $this->put($this->getProjectPath($project_id, 'hooks/'.self::encodePath($hook_id)), $parameters); + } + + public function removeHook(int|string $project_id, int $hook_id): mixed + { + return $this->delete($this->getProjectPath($project_id, 'hooks/'.self::encodePath($hook_id))); + } + + public function transfer(int|string $project_id, mixed $namespace): mixed + { + return $this->put($this->getProjectPath($project_id, 'transfer'), ['namespace' => $namespace]); + } + + public function deployKeys(int|string $project_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'deploy_keys')); + } + + public function deployKey(int|string $project_id, int $key_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'deploy_keys/'.self::encodePath($key_id))); + } + + public function addDeployKey(int|string $project_id, string $title, string $key, bool $canPush = false): mixed + { + return $this->post($this->getProjectPath($project_id, 'deploy_keys'), [ + 'title' => $title, + 'key' => $key, + 'can_push' => $canPush, + ]); + } + + public function deleteDeployKey(int|string $project_id, int $key_id): mixed + { + return $this->delete($this->getProjectPath($project_id, 'deploy_keys/'.self::encodePath($key_id))); + } + + public function enableDeployKey(int|string $project_id, int $key_id): mixed + { + return $this->post($this->getProjectPath($project_id, 'deploy_keys/'.self::encodePath($key_id).'/enable')); + } + + public function deployTokens(int|string $project_id, ?bool $active = null): mixed + { + return $this->get($this->getProjectPath($project_id, 'deploy_tokens'), (null !== $active) ? ['active' => $active] : []); + } + + /** + * @param array $parameters { + * + * @var string $name the name of the deploy token + * @var \DateTimeInterface $expires_at expiration date for the deploy token, does not expire if no value is provided + * @var string $username the username for the deploy token + * @var array $scopes the scopes, one or many of: read_repository, read_registry, write_registry, read_package_registry, write_package_registry + * } + */ + public function createDeployToken(int|string $project_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $datetimeNormalizer = function (Options $resolver, \DateTimeInterface $value): string { + return $value->format('c'); + }; + + $resolver->define('name') + ->required() + ; + + $resolver->define('scopes') + ->required() + ->allowedTypes('array') + ->allowedValues(function ($scopes) { + $allowed = ['read_repository', 'read_registry', 'write_registry', 'read_package_registry', 'write_package_registry']; + foreach ($scopes as $scope) { + if (!\in_array($scope, $allowed, true)) { + return false; + } + } + + return true; + }) + ; + $resolver->setDefined('username') + ->setAllowedTypes('username', 'string') + ; + + $resolver->setDefined('expires_at') + ->setAllowedTypes('expires_at', \DateTimeInterface::class) + ->setNormalizer('expires_at', $datetimeNormalizer) + ; + + return $this->post($this->getProjectPath($project_id, 'deploy_tokens'), $resolver->resolve($parameters)); + } + + public function deleteDeployToken(int|string $project_id, int $token_id): mixed + { + return $this->delete($this->getProjectPath($project_id, 'deploy_tokens/'.self::encodePath($token_id))); + } + + /** + * @param array $parameters { + * + * @var string $action include only events of a particular action type + * @var string $target_type include only events of a particular target type + * @var \DateTimeInterface $before include only events created before a particular date + * @var \DateTimeInterface $after include only events created after a particular date + * @var string $sort Sort events in asc or desc order by created_at (default is desc) + * } + */ + public function events(int|string $project_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $datetimeNormalizer = function (Options $resolver, \DateTimeInterface $value): string { + return $value->format('Y-m-d'); + }; + + $resolver->setDefined('action') + ->setAllowedValues('action', ['created', 'updated', 'closed', 'reopened', 'pushed', 'commented', 'merged', 'joined', 'left', 'destroyed', 'expired', 'approved']) + ; + $resolver->setDefined('target_type') + ->setAllowedValues('target_type', ['issue', 'milestone', 'merge_request', 'note', 'project', 'snippet', 'user']) + ; + $resolver->setDefined('before') + ->setAllowedTypes('before', \DateTimeInterface::class) + ->setNormalizer('before', $datetimeNormalizer); + $resolver->setDefined('after') + ->setAllowedTypes('after', \DateTimeInterface::class) + ->setNormalizer('after', $datetimeNormalizer) + ; + $resolver->setDefined('sort') + ->setAllowedValues('sort', ['asc', 'desc']) + ; + + return $this->get($this->getProjectPath($project_id, 'events'), $resolver->resolve($parameters)); + } + + /** + * @param array $parameters { + * + * @var bool $with_counts Whether or not to include issue and merge request counts. Defaults to false. + * @var bool $include_ancestor_groups Include ancestor groups. Defaults to true. + * @var string $search Keyword to filter labels by. + * } + */ + public function labels(int|string $project_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + + $resolver->setDefined('with_counts') + ->setAllowedTypes('with_counts', 'bool'); + + $resolver->setDefined('include_ancestor_groups') + ->setAllowedTypes('include_ancestor_groups', 'bool'); + + $resolver->setDefined('search') + ->setAllowedTypes('search', 'string'); + + return $this->get($this->getProjectPath($project_id, 'labels'), $resolver->resolve($parameters)); + } + + public function addLabel(int|string $project_id, array $parameters): mixed + { + return $this->post($this->getProjectPath($project_id, 'labels'), $parameters); + } + + public function updateLabel(int|string $project_id, int $label_id, array $parameters): mixed + { + return $this->put($this->getProjectPath($project_id, 'labels/'.self::encodePath($label_id)), $parameters); + } + + public function removeLabel(int|string $project_id, int $label_id): mixed + { + return $this->delete($this->getProjectPath($project_id, 'labels/'.self::encodePath($label_id))); + } + + /** + * Get languages used in a project with percentage value. + */ + public function languages(int|string $project_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'languages')); + } + + /** + * @param array $parameters { + * + * @var bool $archived Limit by archived status + * @var string $visibility Limit by visibility public, internal, or private + * @var string $order_by Return projects ordered by id, name, path, created_at, updated_at, + * last_activity_at, repository_size, storage_size, packages_size or + * wiki_size fields (default is created_at) + * @var string $sort Return projects sorted in asc or desc order (default is desc) + * @var string $search Return list of projects matching the search criteria + * @var bool $simple Return only the ID, URL, name, and path of each project + * @var bool $owned Limit by projects owned by the current user + * @var bool $membership Limit by projects that the current user is a member of + * @var bool $starred Limit by projects starred by the current user + * @var bool $statistics Include project statistics + * @var bool $with_issues_enabled Limit by enabled issues feature + * @var bool $with_merge_requests_enabled Limit by enabled merge requests feature + * @var int $min_access_level Limit by current user minimal access level + * @var \DateTimeInterface $updated_before limit results to projects last updated before the specified time + * @var \DateTimeInterface $updated_after limit results to projects last updated after the specified time + * @var bool $with_custom_attributes Include custom attributes in response + * } + */ + public function forks(int|string $project_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $booleanNormalizer = function (Options $resolver, $value): string { + return $value ? 'true' : 'false'; + }; + $datetimeNormalizer = function (Options $resolver, \DateTimeInterface $value): string { + return $value->format('c'); + }; + $resolver->setDefined('archived') + ->setAllowedTypes('archived', 'bool') + ->setNormalizer('archived', $booleanNormalizer) + ; + $resolver->setDefined('visibility') + ->setAllowedValues('visibility', ['public', 'internal', 'private']) + ; + $orderBy = [ + 'id', 'name', 'path', 'created_at', 'updated_at', 'last_activity_at', + 'repository_size', 'storage_size', 'packages_size', 'wiki_size', + ]; + $resolver->setDefined('order_by') + ->setAllowedValues('order_by', $orderBy) + ; + $resolver->setDefined('sort') + ->setAllowedValues('sort', ['asc', 'desc']) + ; + $resolver->setDefined('search'); + $resolver->setDefined('simple') + ->setAllowedTypes('simple', 'bool') + ->setNormalizer('simple', $booleanNormalizer) + ; + $resolver->setDefined('owned') + ->setAllowedTypes('owned', 'bool') + ->setNormalizer('owned', $booleanNormalizer) + ; + $resolver->setDefined('membership') + ->setAllowedTypes('membership', 'bool') + ->setNormalizer('membership', $booleanNormalizer) + ; + $resolver->setDefined('starred') + ->setAllowedTypes('starred', 'bool') + ->setNormalizer('starred', $booleanNormalizer) + ; + $resolver->setDefined('statistics') + ->setAllowedTypes('statistics', 'bool') + ->setNormalizer('statistics', $booleanNormalizer) + ; + $resolver->setDefined('with_issues_enabled') + ->setAllowedTypes('with_issues_enabled', 'bool') + ->setNormalizer('with_issues_enabled', $booleanNormalizer) + ; + $resolver->setDefined('with_merge_requests_enabled') + ->setAllowedTypes('with_merge_requests_enabled', 'bool') + ->setNormalizer('with_merge_requests_enabled', $booleanNormalizer) + ; + $resolver->setDefined('min_access_level') + ->setAllowedValues('min_access_level', [null, 10, 20, 30, 40, 50]) + ; + $resolver->setDefined('updated_before') + ->setAllowedTypes('updated_before', \DateTimeInterface::class) + ->setNormalizer('updated_before', $datetimeNormalizer) + ; + $resolver->setDefined('updated_after') + ->setAllowedTypes('updated_after', \DateTimeInterface::class) + ->setNormalizer('updated_after', $datetimeNormalizer) + ; + $resolver->setDefined('with_custom_attributes') + ->setAllowedTypes('with_custom_attributes', 'bool') + ->setNormalizer('with_custom_attributes', $booleanNormalizer) + ; + + return $this->get($this->getProjectPath($project_id, 'forks'), $resolver->resolve($parameters)); + } + + /** + * @param array $parameters { + * + * @var string $namespace The ID or path of the namespace that the project will be forked to + * @var string $path The path of the forked project (optional) + * @var string $name The name of the forked project (optional) + * } + */ + public function fork(int|string $project_id, array $parameters = []): mixed + { + $resolver = new OptionsResolver(); + $resolver->setDefined(['namespace', 'path', 'name']); + + $resolved = $resolver->resolve($parameters); + + return $this->post($this->getProjectPath($project_id, 'fork'), $resolved); + } + + public function createForkRelation(int|string $project_id, int|string $forked_project_id): mixed + { + return $this->post($this->getProjectPath($project_id, 'fork/'.self::encodePath($forked_project_id))); + } + + public function removeForkRelation(int|string $project_id): mixed + { + return $this->delete($this->getProjectPath($project_id, 'fork')); + } + + public function setService(int|string $project_id, string $service_name, array $parameters = []): mixed + { + return $this->put($this->getProjectPath($project_id, 'services/'.self::encodePath($service_name)), $parameters); + } + + public function removeService(int|string $project_id, string $service_name): mixed + { + return $this->delete($this->getProjectPath($project_id, 'services/'.self::encodePath($service_name))); + } + + public function variables(int|string $project_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + + return $this->get($this->getProjectPath($project_id, 'variables'), $resolver->resolve($parameters)); + } + + public function variable(int|string $project_id, string $key, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $resolver->setDefined('filter') + ->setAllowedTypes('filter', 'array'); + + return $this->get($this->getProjectPath($project_id, 'variables/'.self::encodePath($key)), $resolver->resolve($parameters)); + } + + /** + * @param array $parameters { + * + * @var string $variable_type env_var (default) or file + * } + */ + public function addVariable(int|string $project_id, string $key, string $value, ?bool $protected = null, ?string $environment_scope = null, array $parameters = []): mixed + { + $payload = [ + 'key' => $key, + 'value' => $value, + ]; + + if ($protected) { + $payload['protected'] = $protected; + } + + if ($environment_scope) { + $payload['environment_scope'] = $environment_scope; + } + + $payload = \array_merge($parameters, $payload); + + return $this->post($this->getProjectPath($project_id, 'variables'), $payload); + } + + /** + * @param array $parameters { + * + * @var string $variable_type env_var (default) or file + *} + */ + public function updateVariable(int|string $project_id, string $key, string $value, ?bool $protected = null, ?string $environment_scope = null, array $parameters = []): mixed + { + $payload = [ + 'value' => $value, + ]; + + if ($protected) { + $payload['protected'] = $protected; + } + + if ($environment_scope) { + $payload['environment_scope'] = $environment_scope; + } + + $payload = \array_merge($parameters, $payload); + + return $this->put($this->getProjectPath($project_id, 'variables/'.self::encodePath($key)), $payload); + } + + /** + * @param array $parameters { + * + * @var array $filter { + * @var string $environment_scope Use filter[environment_scope] to select the variable with the matching environment_scope attribute. + * } + * } + */ + public function removeVariable(int|string $project_id, string $key, array $parameters = []): mixed + { + $resolver = new OptionsResolver(); + $resolver->setDefined('filter') + ->setAllowedTypes('filter', 'array'); + + return $this->delete($this->getProjectPath($project_id, 'variables/'.self::encodePath($key)), $resolver->resolve($parameters)); + } + + public function uploadFile(int|string $project_id, string $file): mixed + { + return $this->post($this->getProjectPath($project_id, 'uploads'), [], [], ['file' => $file]); + } + + public function uploadAvatar(int|string $project_id, string $file): mixed + { + return $this->put('projects/'.self::encodePath($project_id), [], [], ['avatar' => $file]); + } + + /** + * @see https://docs.gitlab.com/ee/api/deployments.html#list-project-deployments + */ + public function deployments(int|string $project_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + + $datetimeNormalizer = function (Options $resolver, \DateTimeInterface $value): string { + return $value->format('c'); + }; + + $resolver->setDefined('order_by') + ->setAllowedTypes('order_by', 'string') + ->setAllowedValues('order_by', ['id', 'iid', 'created_at', 'updated_at', 'finished_at', 'ref']) + ; + + $resolver->setDefined('sort') + ->setAllowedTypes('sort', 'string') + ->setAllowedValues('sort', ['asc', 'desc']) + ; + + $resolver->setDefined('updated_after') + ->setAllowedTypes('updated_after', \DateTimeInterface::class) + ->setNormalizer('updated_after', $datetimeNormalizer) + ; + + $resolver->setDefined('updated_before') + ->setAllowedTypes('updated_before', \DateTimeInterface::class) + ->setNormalizer('updated_before', $datetimeNormalizer) + ; + + $resolver->setDefined('finished_after') + ->setAllowedTypes('finished_after', \DateTimeInterface::class) + ->setNormalizer('finished_after', $datetimeNormalizer) + ; + + $resolver->setDefined('finished_before') + ->setAllowedTypes('finished_before', \DateTimeInterface::class) + ->setNormalizer('finished_before', $datetimeNormalizer) + ; + + $resolver->setDefined('environment') + ->setAllowedTypes('environment', 'string') + ; + + $resolver->setDefined('status') + ->setAllowedTypes('status', 'string') + ->setAllowedValues('status', ['created', 'running', 'success', 'failed', 'canceled', 'blocked']) + ; + + return $this->get($this->getProjectPath($project_id, 'deployments'), $resolver->resolve($parameters)); + } + + public function deployment(int|string $project_id, int $deployment_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'deployments/'.self::encodePath($deployment_id))); + } + + public function addShare(int|string $project_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + + $datetimeNormalizer = function (OptionsResolver $optionsResolver, \DateTimeInterface $value): string { + return $value->format('Y-m-d'); + }; + + $resolver->setRequired('group_id') + ->setAllowedTypes('group_id', 'int'); + + $resolver->setRequired('group_access') + ->setAllowedTypes('group_access', 'int') + ->setAllowedValues('group_access', self::ACCESS_LEVELS); + + $resolver->setDefined('expires_at') + ->setAllowedTypes('expires_at', \DateTimeInterface::class) + ->setNormalizer('expires_at', $datetimeNormalizer) + ; + + return $this->post($this->getProjectPath($project_id, 'share'), $resolver->resolve($parameters)); + } + + public function removeShare(int|string $project_id, int|string $group_id): mixed + { + return $this->delete($this->getProjectPath($project_id, 'share/'.$group_id)); + } + + public function badges(int|string $project_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'badges')); + } + + public function badge(int|string $project_id, int $badge_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'badges/'.self::encodePath($badge_id))); + } + + public function addBadge(int|string $project_id, array $parameters = []): mixed + { + return $this->post($this->getProjectPath($project_id, 'badges'), $parameters); + } + + public function removeBadge(int|string $project_id, int $badge_id): mixed + { + return $this->delete($this->getProjectPath($project_id, 'badges/'.self::encodePath($badge_id))); + } + + public function updateBadge(int|string $project_id, int $badge_id, array $parameters = []): mixed + { + return $this->put($this->getProjectPath($project_id, 'badges/'.self::encodePath($badge_id)), $parameters); + } + + public function protectedBranches(int|string $project_id, array $parameters = []): mixed + { + return $this->get('projects/'.self::encodePath($project_id).'/protected_branches'); + } + + public function addProtectedBranch(int|string $project_id, array $parameters = []): mixed + { + return $this->post($this->getProjectPath($project_id, 'protected_branches'), $parameters); + } + + public function deleteProtectedBranch(int|string $project_id, string $branch_name): mixed + { + return $this->delete($this->getProjectPath($project_id, 'protected_branches/'.self::encodePath($branch_name))); + } + + public function updateProtectedBranch(int|string $project_id, string $branch_name, array $parameters = []): mixed + { + return $this->patch($this->getProjectPath($project_id, 'protected_branches/'.self::encodePath($branch_name)), $parameters); + } + + public function approvalsConfiguration(int|string $project_id): mixed + { + return $this->get('projects/'.self::encodePath($project_id).'/approvals'); + } + + public function updateApprovalsConfiguration(int|string $project_id, array $parameters = []): mixed + { + return $this->post('projects/'.self::encodePath($project_id).'/approvals', $parameters); + } + + public function approvalsRules(int|string $project_id): mixed + { + return $this->get('projects/'.self::encodePath($project_id).'/approval_rules'); + } + + public function createApprovalsRule(int|string $project_id, array $parameters = []): mixed + { + return $this->post('projects/'.self::encodePath($project_id).'/approval_rules/', $parameters); + } + + public function updateApprovalsRule(int|string $project_id, int $approval_rule_id, array $parameters = []): mixed + { + return $this->put('projects/'.self::encodePath($project_id).'/approval_rules/'.self::encodePath($approval_rule_id), $parameters); + } + + public function deleteApprovalsRule(int|string $project_id, int $approval_rule_id): mixed + { + return $this->delete('projects/'.self::encodePath($project_id).'/approval_rules/'.self::encodePath($approval_rule_id)); + } + + public function deleteAllMergedBranches(int|string $project_id): mixed + { + return $this->delete($this->getProjectPath($project_id, 'repository/merged_branches')); + } + + public function projectAccessTokens(int|string $project_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'access_tokens')); + } + + public function projectAccessToken(int|string $project_id, int|string $token_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'access_tokens/'.self::encodePath($token_id))); + } + + /** + * @param array $parameters { + * + * @var string $name the name of the project access token + * @var array $scopes the scopes, one or many of: api, read_api, read_registry, write_registry, read_repository, write_repository + * @var int $access_level the access level: 10 (Guest), 20 (Reporter), 30 (Developer), 40 (Maintainer), 50 (Owner) + * @var \DateTimeInterface $expires_at the token expires at midnight UTC on that date + * } + */ + public function createProjectAccessToken(int|string $project_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $datetimeNormalizer = function (Options $resolver, \DateTimeInterface $value): string { + return $value->format('Y-m-d'); + }; + + $resolver->define('name') + ->required() + ; + + $resolver->define('scopes') + ->required() + ->allowedTypes('array') + ->allowedValues(function ($scopes) { + $allowed = ['api', 'read_api', 'read_registry', 'write_registry', 'read_repository', 'write_repository']; + foreach ($scopes as $scope) { + if (!\in_array($scope, $allowed, true)) { + return false; + } + } + + return true; + }) + ; + + $resolver->setDefined('access_level') + ->setAllowedTypes('access_level', 'int') + ->setAllowedValues('access_level', [10, 20, 30, 40, 50]) + ; + + $resolver->setDefined('expires_at') + ->setAllowedTypes('expires_at', \DateTimeInterface::class) + ->setNormalizer('expires_at', $datetimeNormalizer) + ; + + return $this->post($this->getProjectPath($project_id, 'access_tokens'), $resolver->resolve($parameters)); + } + + public function deleteProjectAccessToken(int|string $project_id, int|string $token_id): mixed + { + return $this->delete($this->getProjectPath($project_id, 'access_tokens/'.$token_id)); + } + + public function protectedTags(int|string $project_id): mixed + { + return $this->get('projects/'.self::encodePath($project_id).'/protected_tags'); + } + + public function protectedTag(int|string $project_id, string $tag_name): mixed + { + return $this->get('projects/'.self::encodePath($project_id).'/protected_tags/'.self::encodePath($tag_name)); + } + + public function addProtectedTag(int|string $project_id, array $parameters = []): mixed + { + $resolver = new OptionsResolver(); + $resolver->setDefined('name') + ->setAllowedTypes('name', 'string') + ->setRequired('name') + ; + $resolver->setDefined('create_access_level') + ->setAllowedTypes('create_access_level', 'int') + ->setAllowedValues('create_access_level', [0, 30, 40]) + ; + $resolver->setDefined('allowed_to_create') + ->setAllowedTypes('allowed_to_create', 'array') + ->setAllowedValues('allowed_to_create', function (array $value) { + $keys = \array_keys((array) \call_user_func_array('array_merge', $value)); + $diff = \array_diff($keys, ['user_id', 'group_id', 'access_level']); + $values = \array_map(function ($item) { + return \array_values($item)[0] ?? ''; + }, $value); + $integer = \count($values) === \count(\array_filter($values, 'is_int')); + + return \count($value) > 0 && 0 === \count($diff) && $integer; + }) + ; + + return $this->post($this->getProjectPath($project_id, 'protected_tags'), $resolver->resolve($parameters)); + } + + public function deleteProtectedTag(int|string $project_id, string $tag_name): mixed + { + return $this->delete($this->getProjectPath($project_id, 'protected_tags/'.self::encodePath($tag_name))); + } + + /** + * @param array $parameters { + * + * @var string $scope The scope to search in + * @var string $search The search query + * @var string $state Filter by state. Issues and merge requests are supported; it is ignored for other scopes. + * @var string $ref The name of a repository branch or tag to search on. The project’s default branch is used by default. Applicable only for scopes blobs, commits, and wiki_blobs. + * @var bool $confidential Filter by confidentiality. Issues scope is supported; it is ignored for other scopes. + * @var string $order_by Allowed values are created_at only. If this is not set, the results are either sorted by created_at in descending order for basic search, or by the most relevant documents when using advanced search. + * @var string $sort Return projects sorted in asc or desc order (default is desc) + * } + * + * @throws UndefinedOptionsException If an option name is undefined + * @throws InvalidOptionsException If an option doesn't fulfill the specified validation rules + */ + public function search(int|string $id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $booleanNormalizer = function (Options $resolver, $value): string { + return $value ? 'true' : 'false'; + }; + $resolver->setDefined('confidential') + ->setAllowedTypes('confidential', 'bool') + ->setNormalizer('confidential', $booleanNormalizer); + $scope = [ + 'blobs', + 'commits', + 'issues', + 'merge_requests', + 'milestones', + 'notes', + 'users', + 'wiki_blobs', + ]; + $resolver->setRequired('scope') + ->setAllowedValues('scope', $scope); + $resolver->setRequired('search'); + $resolver->setDefined('ref') + ->setAllowedTypes('ref', 'string'); + $resolver->setDefined('order_by') + ->setAllowedValues('order_by', ['created_at']); + $resolver->setDefined('sort') + ->setAllowedValues('sort', ['asc', 'desc']); + $resolver->setDefined('state') + ->setAllowedValues('state', ['opened', 'closed']); + + return $this->get('projects/'.self::encodePath($id).'/search', $resolver->resolve($parameters)); + } +} diff --git a/src/Api/Repositories.php b/src/Api/Repositories.php new file mode 100644 index 000000000..eded16b93 --- /dev/null +++ b/src/Api/Repositories.php @@ -0,0 +1,360 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class Repositories extends AbstractApi +{ + /** + * @var string + */ + public const TYPE_BRANCH = 'branch'; + + /** + * @var string + */ + public const TYPE_TAG = 'tag'; + + /** + * @param array $parameters { + * + * @var string $search + * } + */ + public function branches(int|string $project_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $resolver->setDefined('search') + ->setAllowedTypes('search', 'string'); + + return $this->get($this->getProjectPath($project_id, 'repository/branches'), $resolver->resolve($parameters)); + } + + public function branch(int|string $project_id, string $branch): mixed + { + return $this->get($this->getProjectPath($project_id, 'repository/branches/'.self::encodePath($branch))); + } + + public function createBranch(int|string $project_id, string $branch, string $ref): mixed + { + return $this->post($this->getProjectPath($project_id, 'repository/branches'), [ + 'branch' => $branch, + 'ref' => $ref, + ]); + } + + public function deleteBranch(int|string $project_id, string $branch): mixed + { + return $this->delete($this->getProjectPath($project_id, 'repository/branches/'.self::encodePath($branch))); + } + + public function protectBranch(int|string $project_id, string $branch, bool $devPush = false, bool $devMerge = false): mixed + { + return $this->put($this->getProjectPath($project_id, 'repository/branches/'.self::encodePath($branch).'/protect'), [ + 'developers_can_push' => $devPush, + 'developers_can_merge' => $devMerge, + ]); + } + + public function unprotectBranch(int|string $project_id, string $branch): mixed + { + return $this->put($this->getProjectPath($project_id, 'repository/branches/'.self::encodePath($branch).'/unprotect')); + } + + public function tags(int|string $project_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $resolver->setDefined('search') + ->setAllowedTypes('search', 'string'); + + return $this->get($this->getProjectPath($project_id, 'repository/tags'), $resolver->resolve($parameters)); + } + + public function createTag(int|string $project_id, string $name, string $ref, ?string $message = null): mixed + { + return $this->post($this->getProjectPath($project_id, 'repository/tags'), [ + 'tag_name' => $name, + 'ref' => $ref, + 'message' => $message, + ]); + } + + public function createRelease(int|string $project_id, string $tag_name, string $description, ?string $name = null): mixed + { + return $this->post($this->getProjectPath($project_id, 'releases'), \array_filter([ + 'id' => $project_id, + 'tag_name' => $tag_name, + 'description' => $description, + 'name' => $name, + ], fn ($v) => null !== $v)); + } + + public function updateRelease(int|string $project_id, string $tag_name, string $description, ?string $name = null): mixed + { + return $this->put($this->getProjectPath($project_id, 'releases/'.self::encodePath($tag_name)), \array_filter([ + 'id' => $project_id, + 'tag_name' => $tag_name, + 'description' => $description, + 'name' => $name, + ], fn ($v) => null !== $v)); + } + + public function releases(int|string $project_id): mixed + { + $resolver = $this->createOptionsResolver(); + + return $this->get($this->getProjectPath($project_id, 'releases')); + } + + /** + * @see https://docs.gitlab.com/ee/api/commits.html#list-repository-commits + * + * @param array $parameters { + * + * @var string $ref_name the name of a repository branch or tag or if not given the default branch + * @var \DateTimeInterface $since only commits after or on this date will be returned + * @var \DateTimeInterface $until Only commits before or on this date will be returned. + * } + */ + public function commits(int|string $project_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $datetimeNormalizer = function (Options $options, \DateTimeInterface $value): string { + return $value->format('c'); + }; + $booleanNormalizer = function (Options $resolver, $value): string { + return $value ? 'true' : 'false'; + }; + + $resolver->setDefined('path'); + $resolver->setDefined('ref_name'); + $resolver->setDefined('author'); + $resolver->setDefined('since') + ->setAllowedTypes('since', \DateTimeInterface::class) + ->setNormalizer('since', $datetimeNormalizer) + ; + $resolver->setDefined('until') + ->setAllowedTypes('until', \DateTimeInterface::class) + ->setNormalizer('until', $datetimeNormalizer) + ; + $resolver->setDefined('all') + ->setAllowedTypes('all', 'bool') + ->setNormalizer('all', $booleanNormalizer) + ; + $resolver->setDefined('with_stats') + ->setAllowedTypes('with_stats', 'bool') + ->setNormalizer('with_stats', $booleanNormalizer) + ; + $resolver->setDefined('first_parent') + ->setAllowedTypes('first_parent', 'bool') + ->setNormalizer('first_parent', $booleanNormalizer) + ; + $resolver->setDefined('order') + ->setAllowedValues('order', ['default', 'topo']) + ; + + return $this->get($this->getProjectPath($project_id, 'repository/commits'), $resolver->resolve($parameters)); + } + + public function commit(int|string $project_id, string $sha): mixed + { + return $this->get($this->getProjectPath($project_id, 'repository/commits/'.self::encodePath($sha))); + } + + public function commitRefs(int|string $project_id, string $sha, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + + return $this->get( + $this->getProjectPath($project_id, 'repository/commits/'.self::encodePath($sha).'/refs'), + $resolver->resolve($parameters) + ); + } + + /** + * @param array $parameters { + * + * @var string $branch Name of the branch to commit into. To create a new branch, also provide start_branch. + * @var string $commit_message commit message + * @var string $start_branch name of the branch to start the new commit from + * @var array $actions { + * @var string $action he action to perform, create, delete, move, update + * @var string $file_path full path to the file + * @var string $previous_path original full path to the file being moved + * @var string $content File content, required for all except delete. Optional for move. + * @var string $encoding text or base64. text is default. + * } + * @var string $author_email specify the commit author's email address + * @var string $author_name Specify the commit author's name. + * } + */ + public function createCommit(int|string $project_id, array $parameters = []): mixed + { + $resolver = new OptionsResolver(); + $resolver->setDefined('branch') + ->setRequired('branch') + ; + $resolver->setDefined('commit_message') + ->setRequired('commit_message') + ; + $resolver->setDefined('start_branch'); + $resolver->setDefined('actions') + ->setRequired('actions') + ->setAllowedTypes('actions', 'array') + ->setAllowedValues('actions', function (array $actions) { + return 0 < \count($actions); + }) + ->setNormalizer('actions', function (Options $resolver, array $actions) { + $actionsOptionsResolver = new OptionsResolver(); + $actionsOptionsResolver->setDefined('action') + ->setRequired('action') + ->setAllowedValues('action', ['create', 'delete', 'move', 'update', 'chmod']) + ; + $actionsOptionsResolver->setDefined('file_path') + ->setRequired('file_path') + ; + $actionsOptionsResolver->setDefined('previous_path'); + $actionsOptionsResolver->setDefined('content'); + $actionsOptionsResolver->setDefined('encoding') + ->setAllowedValues('encoding', ['text', 'base64']) + ; + $actionsOptionsResolver->setDefined('execute_filemode') + ->setAllowedValues('execute_filemode', [true, false]) + ; + + return \array_map(function ($action) use ($actionsOptionsResolver) { + return $actionsOptionsResolver->resolve($action); + }, $actions); + }) + ; + $resolver->setDefined('author_email'); + $resolver->setDefined('author_name'); + + return $this->post($this->getProjectPath($project_id, 'repository/commits'), $resolver->resolve($parameters)); + } + + public function revertCommit(int|string $project_id, string $branch, string $sha): mixed + { + return $this->post($this->getProjectPath($project_id, 'repository/commits/'.self::encodePath($sha).'/revert'), [ + 'branch' => $branch, + ]); + } + + public function commitComments(int|string $project_id, string $sha, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + + return $this->get( + $this->getProjectPath($project_id, 'repository/commits/'.self::encodePath($sha).'/comments'), + $resolver->resolve($parameters) + ); + } + + public function createCommitComment(int|string $project_id, string $sha, string $note, array $params = []): mixed + { + $params['note'] = $note; + + return $this->post($this->getProjectPath($project_id, 'repository/commits/'.self::encodePath($sha).'/comments'), $params); + } + + public function getCommitBuildStatus(int|string $project_id, string $sha, array $params = []): mixed + { + return $this->get($this->getProjectPath($project_id, 'repository/commits/'.self::encodePath($sha).'/statuses'), $params); + } + + public function postCommitBuildStatus(int|string $project_id, string $sha, string $state, array $params = []): mixed + { + $params['state'] = $state; + + return $this->post($this->getProjectPath($project_id, 'statuses/'.self::encodePath($sha)), $params); + } + + public function compare(int|string $project_id, string $fromShaOrMaster, string $toShaOrMaster, bool $straight = false, ?string $fromProjectId = null): mixed + { + $params = [ + 'from' => $fromShaOrMaster, + 'to' => $toShaOrMaster, + 'straight' => $straight ? 'true' : 'false', + ]; + + if (null !== $fromProjectId) { + $params['from_project_id'] = self::encodePath($fromProjectId); + } + + return $this->get($this->getProjectPath($project_id, 'repository/compare'), $params); + } + + public function diff(int|string $project_id, string $sha): mixed + { + return $this->get($this->getProjectPath($project_id, 'repository/commits/'.self::encodePath($sha).'/diff')); + } + + public function tree(int|string $project_id, array $params = []): mixed + { + return $this->get($this->getProjectPath($project_id, 'repository/tree'), $params); + } + + public function contributors(int|string $project_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'repository/contributors')); + } + + /** + * @param string $format Options: "tar.gz", "zip", "tar.bz2" and "tar" + */ + public function archive(int|string $project_id, array $params = [], string $format = 'tar.gz'): mixed + { + return $this->get($this->getProjectPath($project_id, 'repository/archive.'.$format), $params); + } + + public function mergeBase(int|string $project_id, array $refs): mixed + { + return $this->get($this->getProjectPath($project_id, 'repository/merge_base'), ['refs' => $refs]); + } + + public function cherryPick(int|string $project_id, string $sha, array $params = []): mixed + { + $resolver = $this->createOptionsResolver(); + $booleanNormalizer = function (Options $resolver, $value): string { + return $value ? 'true' : 'false'; + }; + + $resolver->setDefined('branch') + ->setRequired('branch'); + + $resolver->setDefined('dry_run') + ->setAllowedTypes('dry_run', 'bool') + ->setNormalizer('dry_run', $booleanNormalizer); + + return $this->post($this->getProjectPath($project_id, 'repository/commits/'.self::encodePath($sha).'/cherry_pick'), $params); + } + + protected function createOptionsResolver(): OptionsResolver + { + $allowedTypeValues = [ + self::TYPE_BRANCH, + self::TYPE_TAG, + ]; + + $resolver = parent::createOptionsResolver(); + $resolver->setDefined('type') + ->setAllowedTypes('type', 'string') + ->setAllowedValues('type', $allowedTypeValues); + + return $resolver; + } +} diff --git a/src/Api/RepositoryFiles.php b/src/Api/RepositoryFiles.php new file mode 100644 index 000000000..80f303ad8 --- /dev/null +++ b/src/Api/RepositoryFiles.php @@ -0,0 +1,126 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +use Symfony\Component\OptionsResolver\OptionsResolver; + +class RepositoryFiles extends AbstractApi +{ + public function getFile(int|string $project_id, string $file_path, string $ref): mixed + { + return $this->get($this->getProjectPath($project_id, 'repository/files/'.self::encodePath($file_path)), [ + 'ref' => $ref, + ]); + } + + public function getRawFile(int|string $project_id, string $file_path, string $ref): mixed + { + return $this->get($this->getProjectPath($project_id, 'repository/files/'.self::encodePath($file_path).'/raw'), [ + 'ref' => $ref, + ]); + } + + /** + * @param array $parameters { + * + * @var string $file_path Url encoded full path to new file. Ex. lib%2Fclass%2Erb. + * @var string $branch name of the branch + * @var string $start_branch name of the branch to start the new commit from + * @var string $encoding change encoding to 'base64' (default is text) + * @var string $author_email specify the commit author's email address + * @var string $author_name specify the commit author's name + * @var string $content file content + * @var string $commit_message Commit message. + * } + */ + public function createFile(int|string $project_id, array $parameters = []): mixed + { + $resolver = new OptionsResolver(); + $resolver->setRequired('file_path'); + $resolver->setRequired('branch'); + $resolver->setDefined('start_branch'); + $resolver->setDefined('encoding') + ->setAllowedValues('encoding', ['text', 'base64']) + ; + $resolver->setDefined('author_email'); + $resolver->setDefined('author_name'); + $resolver->setRequired('content'); + $resolver->setRequired('commit_message'); + + $resolved = $resolver->resolve($parameters); + + return $this->post($this->getProjectPath($project_id, 'repository/files/'.self::encodePath($resolved['file_path'])), $resolved); + } + + /** + * @param array $parameters { + * + * @var string $file_path Url encoded full path to new file. Ex. lib%2Fclass%2Erb. + * @var string $branch name of the branch + * @var string $start_branch name of the branch to start the new commit from + * @var string $encoding change encoding to 'base64' (default is text) + * @var string $author_email specify the commit author's email address + * @var string $author_name specify the commit author's name + * @var string $content file content + * @var string $commit_message commit message + * @var string $last_commit_id last known file commit id + * } + */ + public function updateFile(int|string $project_id, array $parameters = []): mixed + { + $resolver = new OptionsResolver(); + $resolver->setRequired('file_path'); + $resolver->setRequired('branch'); + $resolver->setDefined('start_branch'); + $resolver->setDefined('encoding') + ->setAllowedValues('encoding', ['text', 'base64']) + ; + $resolver->setDefined('author_email'); + $resolver->setDefined('author_name'); + $resolver->setRequired('content'); + $resolver->setRequired('commit_message'); + $resolver->setDefined('last_commit_id'); + + $resolved = $resolver->resolve($parameters); + + return $this->put($this->getProjectPath($project_id, 'repository/files/'.self::encodePath($resolved['file_path'])), $resolved); + } + + /** + * @param array $parameters { + * + * @var string $file_path Url encoded full path to new file. Ex. lib%2Fclass%2Erb. + * @var string $branch name of the branch + * @var string $start_branch name of the branch to start the new commit from + * @var string $author_email specify the commit author's email address + * @var string $author_name specify the commit author's name + * @var string $commit_message Commit message. + * } + */ + public function deleteFile(int|string $project_id, array $parameters = []): mixed + { + $resolver = new OptionsResolver(); + $resolver->setRequired('file_path'); + $resolver->setRequired('branch'); + $resolver->setDefined('start_branch'); + $resolver->setDefined('author_email'); + $resolver->setDefined('author_name'); + $resolver->setRequired('commit_message'); + + $resolved = $resolver->resolve($parameters); + + return $this->delete($this->getProjectPath($project_id, 'repository/files/'.self::encodePath($resolved['file_path'])), $resolved); + } +} diff --git a/src/Api/ResourceIterationEvents.php b/src/Api/ResourceIterationEvents.php new file mode 100644 index 000000000..f18edb752 --- /dev/null +++ b/src/Api/ResourceIterationEvents.php @@ -0,0 +1,33 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +class ResourceIterationEvents extends AbstractApi +{ + public function all(int|string $project_id, int $issue_iid): mixed + { + $path = 'issues/'.self::encodePath($issue_iid).'/resource_iteration_events'; + + return $this->get($this->getProjectPath($project_id, $path)); + } + + public function show(int|string $project_id, int $issue_iid, int $resource_iteration_event_id): mixed + { + $path = 'issues/'.self::encodePath($issue_iid).'/resource_iteration_events/'; + $path .= self::encodePath($resource_iteration_event_id); + + return $this->get($this->getProjectPath($project_id, $path)); + } +} diff --git a/src/Api/ResourceLabelEvents.php b/src/Api/ResourceLabelEvents.php new file mode 100644 index 000000000..9ccf65679 --- /dev/null +++ b/src/Api/ResourceLabelEvents.php @@ -0,0 +1,33 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +class ResourceLabelEvents extends AbstractApi +{ + public function all(int|string $project_id, int $issue_iid): mixed + { + $path = 'issues/'.self::encodePath($issue_iid).'/resource_label_events'; + + return $this->get($this->getProjectPath($project_id, $path)); + } + + public function show(int|string $project_id, int $issue_iid, int $resource_label_event_id): mixed + { + $path = 'issues/'.self::encodePath($issue_iid).'/resource_label_events/'; + $path .= self::encodePath($resource_label_event_id); + + return $this->get($this->getProjectPath($project_id, $path)); + } +} diff --git a/src/Api/ResourceMilestoneEvents.php b/src/Api/ResourceMilestoneEvents.php new file mode 100644 index 000000000..be4adb473 --- /dev/null +++ b/src/Api/ResourceMilestoneEvents.php @@ -0,0 +1,33 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +class ResourceMilestoneEvents extends AbstractApi +{ + public function all(int|string $project_id, int $issue_iid): mixed + { + $path = 'issues/'.self::encodePath($issue_iid).'/resource_milestone_events'; + + return $this->get($this->getProjectPath($project_id, $path)); + } + + public function show(int|string $project_id, int $issue_iid, int $resource_milestone_event_id): mixed + { + $path = 'issues/'.self::encodePath($issue_iid).'/resource_milestone_events/'; + $path .= self::encodePath($resource_milestone_event_id); + + return $this->get($this->getProjectPath($project_id, $path)); + } +} diff --git a/src/Api/ResourceStateEvents.php b/src/Api/ResourceStateEvents.php new file mode 100644 index 000000000..be03a6d5b --- /dev/null +++ b/src/Api/ResourceStateEvents.php @@ -0,0 +1,33 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +class ResourceStateEvents extends AbstractApi +{ + public function all(int|string $project_id, int $issue_iid): mixed + { + $path = 'issues/'.self::encodePath($issue_iid).'/resource_state_events'; + + return $this->get($this->getProjectPath($project_id, $path)); + } + + public function show(int|string $project_id, int $issue_iid, int $resource_label_event_id): mixed + { + $path = 'issues/'.self::encodePath($issue_iid).'/resource_state_events/'; + $path .= self::encodePath($resource_label_event_id); + + return $this->get($this->getProjectPath($project_id, $path)); + } +} diff --git a/src/Api/ResourceWeightEvents.php b/src/Api/ResourceWeightEvents.php new file mode 100644 index 000000000..53a7b162f --- /dev/null +++ b/src/Api/ResourceWeightEvents.php @@ -0,0 +1,33 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +class ResourceWeightEvents extends AbstractApi +{ + public function all(int|string $project_id, int $issue_iid): mixed + { + $path = 'issues/'.self::encodePath($issue_iid).'/resource_weight_events'; + + return $this->get($this->getProjectPath($project_id, $path)); + } + + public function show(int|string $project_id, int $issue_iid, int $resource_label_event_id): mixed + { + $path = 'issues/'.self::encodePath($issue_iid).'/resource_weight_events/'; + $path .= self::encodePath($resource_label_event_id); + + return $this->get($this->getProjectPath($project_id, $path)); + } +} diff --git a/src/Api/Schedules.php b/src/Api/Schedules.php new file mode 100644 index 000000000..44b61964a --- /dev/null +++ b/src/Api/Schedules.php @@ -0,0 +1,74 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +class Schedules extends AbstractApi +{ + public function create(int|string $project_id, array $params): mixed + { + return $this->post($this->getProjectPath($project_id, 'pipeline_schedules'), $params); + } + + public function show(int|string $project_id, int $schedule_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'pipeline_schedules/'.self::encodePath($schedule_id))); + } + + public function showAll(int|string $project_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'pipeline_schedules')); + } + + public function update(int|string $project_id, int $schedule_id, array $params): mixed + { + return $this->put($this->getProjectPath($project_id, 'pipeline_schedules/'.self::encodePath($schedule_id)), $params); + } + + public function remove(int|string $project_id, int $schedule_id): mixed + { + return $this->delete($this->getProjectPath($project_id, 'pipeline_schedules/'.self::encodePath($schedule_id))); + } + + public function addVariable(int|string $project_id, int $schedule_id, array $params): mixed + { + $path = 'pipeline_schedules/'.self::encodePath($schedule_id).'/variables'; + + return $this->post($this->getProjectPath($project_id, $path), $params); + } + + public function updateVariable(int|string $project_id, int $schedule_id, string $variable_key, array $params): mixed + { + $path = 'pipeline_schedules/'.self::encodePath($schedule_id).'/variables/'.self::encodePath($variable_key); + + return $this->put($this->getProjectPath($project_id, $path), $params); + } + + public function removeVariable(int|string $project_id, int $schedule_id, string $variable_key): mixed + { + $path = 'pipeline_schedules/'.self::encodePath($schedule_id).'/variables/'.self::encodePath($variable_key); + + return $this->delete($this->getProjectPath($project_id, $path)); + } + + public function takeOwnership(int|string $project_id, int $schedule_id): mixed + { + return $this->post($this->getProjectPath($project_id, 'pipeline_schedules/'.self::encodePath($schedule_id)).'/take_ownership'); + } + + public function play(int|string $project_id, int $schedule_id): mixed + { + return $this->post($this->getProjectPath($project_id, 'pipeline_schedules/'.self::encodePath($schedule_id)).'/play'); + } +} diff --git a/src/Api/Search.php b/src/Api/Search.php new file mode 100644 index 000000000..a7685bd65 --- /dev/null +++ b/src/Api/Search.php @@ -0,0 +1,70 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; +use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException; +use Symfony\Component\OptionsResolver\Options; + +class Search extends AbstractApi +{ + /** + * @param array $parameters { + * + * @var string $scope The scope to search in + * @var string $search The search query + * @var string $state Filter by state. Issues and merge requests are supported; it is ignored for other scopes. + * @var bool $confidential Filter by confidentiality. Issues scope is supported; it is ignored for other scopes. + * @var string $order_by Allowed values are created_at only. If this is not set, the results are either sorted by created_at in descending order for basic search, or by the most relevant documents when using advanced search. + * @var string $sort Return projects sorted in asc or desc order (default is desc) + * } + * + * @throws UndefinedOptionsException If an option name is undefined + * @throws InvalidOptionsException If an option doesn't fulfill the specified validation rules + */ + public function all(array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $booleanNormalizer = function (Options $resolver, $value): string { + return $value ? 'true' : 'false'; + }; + $resolver->setDefined('confidential') + ->setAllowedTypes('confidential', 'bool') + ->setNormalizer('confidential', $booleanNormalizer); + $scope = [ + 'projects', + 'issues', + 'merge_requests', + 'milestones', + 'snippet_titles', + 'wiki_blobs', + 'commits', + 'blobs', + 'notes', + 'users', + ]; + $resolver->setRequired('scope') + ->setAllowedValues('scope', $scope); + $resolver->setRequired('search'); + $resolver->setDefined('order_by') + ->setAllowedValues('order_by', ['created_at']); + $resolver->setDefined('sort') + ->setAllowedValues('sort', ['asc', 'desc']); + $resolver->setDefined('state') + ->setAllowedValues('state', ['opened', 'closed']); + + return $this->get('search', $resolver->resolve($parameters)); + } +} diff --git a/src/Api/Snippets.php b/src/Api/Snippets.php new file mode 100644 index 000000000..d62e174eb --- /dev/null +++ b/src/Api/Snippets.php @@ -0,0 +1,92 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +class Snippets extends AbstractApi +{ + public function all(int|string $project_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'snippets')); + } + + public function show(int|string $project_id, int $snippet_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'snippets/'.self::encodePath($snippet_id))); + } + + public function create(int|string $project_id, string $title, string $filename, string $code, string $visibility): mixed + { + return $this->post($this->getProjectPath($project_id, 'snippets'), [ + 'title' => $title, + 'file_name' => $filename, + 'code' => $code, + 'visibility' => $visibility, + ]); + } + + public function update(int|string $project_id, int $snippet_id, array $params): mixed + { + return $this->put($this->getProjectPath($project_id, 'snippets/'.self::encodePath($snippet_id)), $params); + } + + public function content(int|string $project_id, int $snippet_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'snippets/'.self::encodePath($snippet_id).'/raw')); + } + + public function remove(int|string $project_id, int $snippet_id): mixed + { + return $this->delete($this->getProjectPath($project_id, 'snippets/'.self::encodePath($snippet_id))); + } + + public function showNotes(int|string $project_id, int $snippet_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'snippets/'.self::encodePath($snippet_id).'/notes')); + } + + public function showNote(int|string $project_id, int $snippet_id, int $note_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'snippets/'.self::encodePath($snippet_id).'/notes/'.self::encodePath($note_id))); + } + + public function addNote(int|string $project_id, int $snippet_id, string $body, array $params = []): mixed + { + $params['body'] = $body; + + return $this->post($this->getProjectPath($project_id, 'snippets/'.self::encodePath($snippet_id).'/notes'), $params); + } + + public function updateNote(int|string $project_id, int $snippet_id, int $note_id, string $body): mixed + { + return $this->put($this->getProjectPath($project_id, 'snippets/'.self::encodePath($snippet_id).'/notes/'.self::encodePath($note_id)), [ + 'body' => $body, + ]); + } + + public function removeNote(int|string $project_id, int $snippet_id, int $note_id): mixed + { + return $this->delete($this->getProjectPath($project_id, 'snippets/'.self::encodePath($snippet_id).'/notes/'.self::encodePath($note_id))); + } + + public function awardEmoji(int|string $project_id, int $snippet_id): mixed + { + return $this->get($this->getProjectPath($project_id, 'snippets/'.self::encodePath($snippet_id).'/award_emoji')); + } + + public function removeAwardEmoji(int|string $project_id, int $snippet_id, int $award_id): mixed + { + return $this->delete($this->getProjectPath($project_id, 'snippets/'.self::encodePath($snippet_id).'/award_emoji/'.self::encodePath($award_id))); + } +} diff --git a/src/Api/SystemHooks.php b/src/Api/SystemHooks.php new file mode 100644 index 000000000..f068429db --- /dev/null +++ b/src/Api/SystemHooks.php @@ -0,0 +1,94 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class SystemHooks extends AbstractApi +{ + public function all(): mixed + { + return $this->get('hooks'); + } + + /** + * @param array $parameters { + * + * @var string $token secret token to validate received payloads + * @var bool $push_events when true, the hook fires on push events + * @var bool $tag_push_events when true, the hook fires on new tags being pushed + * @var bool $merge_requests_events trigger hook on merge requests events + * @var bool $repository_update_events trigger hook on repository update events + * @var bool $enable_ssl_verification do SSL verification when triggering the hook + * } + */ + public function create(string $url, array $parameters = []): mixed + { + $parameters = $this->createOptionsResolver()->resolve($parameters); + + $parameters['url'] = $url; + + return $this->post('hooks', $parameters); + } + + public function test(int $id): mixed + { + return $this->get('hooks/'.self::encodePath($id)); + } + + public function remove(int $id): mixed + { + return $this->delete('hooks/'.self::encodePath($id)); + } + + protected function createOptionsResolver(): OptionsResolver + { + $resolver = new OptionsResolver(); + + $resolver->setDefined('token'); + + $booleanNormalizer = function (Options $resolver, $value): string { + return $value ? 'true' : 'false'; + }; + + $resolver->setDefined('push_events') + ->setAllowedTypes('push_events', 'bool') + ->setNormalizer('push_events', $booleanNormalizer) + ; + + $resolver->setDefined('tag_push_events') + ->setAllowedTypes('tag_push_events', 'bool') + ->setNormalizer('tag_push_events', $booleanNormalizer) + ; + + $resolver->setDefined('merge_requests_events') + ->setAllowedTypes('merge_requests_events', 'bool') + ->setNormalizer('merge_requests_events', $booleanNormalizer) + ; + + $resolver->setDefined('repository_update_events') + ->setAllowedTypes('repository_update_events', 'bool') + ->setNormalizer('repository_update_events', $booleanNormalizer) + ; + + $resolver->setDefined('enable_ssl_verification') + ->setAllowedTypes('enable_ssl_verification', 'bool') + ->setNormalizer('enable_ssl_verification', $booleanNormalizer) + ; + + return $resolver; + } +} diff --git a/src/Api/Tags.php b/src/Api/Tags.php new file mode 100644 index 000000000..8085d7638 --- /dev/null +++ b/src/Api/Tags.php @@ -0,0 +1,64 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +class Tags extends AbstractApi +{ + /** + * @param array $parameters { + * + * @var string $order_by Return tags ordered by `name`, `updated` or `version` fields. Default is `updated`. + * @var string $sort Return tags sorted in asc or desc order. Default is desc. + * @var string $search Return list of tags matching the search criteria. You can use `^term` and `term$` to + * find tags that begin and end with term respectively. + * } + */ + public function all(int|string $project_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $resolver->setDefined('order_by') + ->setAllowedValues('order_by', ['name', 'updated', 'version']); + $resolver->setDefined('sort') + ->setAllowedValues('sort', ['asc', 'desc']); + $resolver->setDefined('search'); + + return $this->get($this->getProjectPath($project_id, 'repository/tags'), $resolver->resolve($parameters)); + } + + public function show(int|string $project_id, string $tag_name): mixed + { + return $this->get($this->getProjectPath($project_id, 'repository/tags/'.self::encodePath($tag_name))); + } + + public function create(int|string $project_id, array $params = []): mixed + { + return $this->post($this->getProjectPath($project_id, 'repository/tags'), $params); + } + + public function remove(int|string $project_id, string $tag_name): mixed + { + return $this->delete($this->getProjectPath($project_id, 'repository/tags/'.self::encodePath($tag_name))); + } + + public function createRelease(int|string $project_id, string $tag_name, array $params = []): mixed + { + return $this->post($this->getProjectPath($project_id, 'repository/tags/'.self::encodePath($tag_name).'/release'), $params); + } + + public function updateRelease(int|string $project_id, string $tag_name, array $params = []): mixed + { + return $this->put($this->getProjectPath($project_id, 'repository/tags/'.self::encodePath($tag_name).'/release'), $params); + } +} diff --git a/src/Api/Users.php b/src/Api/Users.php new file mode 100644 index 000000000..01fd1ff3e --- /dev/null +++ b/src/Api/Users.php @@ -0,0 +1,446 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +use Symfony\Component\OptionsResolver\Options; + +class Users extends AbstractApi +{ + /** + * @param array $parameters { + * + * @var string $search search for user by email or username + * @var string $username lookup for user by username + * @var bool $external search for external users only + * @var string $extern_uid lookup for users by external uid + * @var string $provider lookup for users by provider + * @var \DateTimeInterface $created_before return users created before the given time (inclusive) + * @var \DateTimeInterface $created_after return users created after the given time (inclusive) + * @var bool $active Return only active users. It does not support filtering inactive users. + * @var bool $blocked Return only blocked users. It does not support filtering non-blocked users. + * } + */ + public function all(array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $datetimeNormalizer = function (Options $resolver, \DateTimeInterface $value): string { + return $value->format('c'); + }; + + $resolver->setDefined('search'); + $resolver->setDefined('username'); + $resolver->setDefined('external') + ->setAllowedTypes('external', 'bool') + ; + $resolver->setDefined('extern_uid'); + $resolver->setDefined('provider'); + $resolver->setDefined('created_before') + ->setAllowedTypes('created_before', \DateTimeInterface::class) + ->setNormalizer('created_before', $datetimeNormalizer) + ; + $resolver->setDefined('created_after') + ->setAllowedTypes('created_after', \DateTimeInterface::class) + ->setNormalizer('created_after', $datetimeNormalizer) + ; + $resolver->setDefined('active') + ->setAllowedTypes('active', 'bool') + ->setAllowedValues('active', true) + ; + $resolver->setDefined('blocked') + ->setAllowedTypes('blocked', 'bool') + ->setAllowedValues('blocked', true) + ; + + return $this->get('users', $resolver->resolve($parameters)); + } + + public function show(int $id): mixed + { + return $this->get('users/'.self::encodePath($id)); + } + + /** + * @param array $parameters { + * + * @var string $type Filter memberships by type. Can be either Project or Namespace + * } + */ + public function usersMemberships(int $id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $resolver->setDefined('type') + ->setAllowedValues('type', ['Project', 'Namespace']) + ; + + return $this->get('users/'.self::encodePath($id).'/memberships', $resolver->resolve($parameters)); + } + + /** + * @param array $parameters { + * + * @var bool $archived limit by archived status + * @var string $visibility limit by visibility public, internal, or private + * @var string $order_by Return projects ordered by id, name, path, created_at, updated_at, + * or last_activity_at fields (default is created_at) + * @var string $sort Return projects sorted in asc or desc order (default is desc) + * @var string $search return list of projects matching the search criteria + * @var bool $simple return only the ID, URL, name, and path of each project + * @var bool $owned limit by projects owned by the current user + * @var bool $membership limit by projects that the current user is a member of + * @var bool $starred limit by projects starred by the current user + * @var bool $statistics include project statistics + * @var bool $with_issues_enabled limit by enabled issues feature + * @var bool $with_merge_requests_enabled limit by enabled merge requests feature + * @var int $min_access_level Limit by current user minimal access level + * } + */ + public function usersProjects(int $id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $booleanNormalizer = function (Options $resolver, $value): string { + return $value ? 'true' : 'false'; + }; + $resolver->setDefined('archived') + ->setAllowedTypes('archived', 'bool') + ->setNormalizer('archived', $booleanNormalizer) + ; + $resolver->setDefined('visibility') + ->setAllowedValues('visibility', ['public', 'internal', 'private']) + ; + $resolver->setDefined('order_by') + ->setAllowedValues('order_by', ['id', 'name', 'path', 'created_at', 'updated_at', 'last_activity_at']) + ; + $resolver->setDefined('sort') + ->setAllowedValues('sort', ['asc', 'desc']) + ; + $resolver->setDefined('search'); + $resolver->setDefined('simple') + ->setAllowedTypes('simple', 'bool') + ->setNormalizer('simple', $booleanNormalizer) + ; + $resolver->setDefined('owned') + ->setAllowedTypes('owned', 'bool') + ->setNormalizer('owned', $booleanNormalizer) + ; + $resolver->setDefined('membership') + ->setAllowedTypes('membership', 'bool') + ->setNormalizer('membership', $booleanNormalizer) + ; + $resolver->setDefined('starred') + ->setAllowedTypes('starred', 'bool') + ->setNormalizer('starred', $booleanNormalizer) + ; + $resolver->setDefined('statistics') + ->setAllowedTypes('statistics', 'bool') + ->setNormalizer('statistics', $booleanNormalizer) + ; + $resolver->setDefined('with_issues_enabled') + ->setAllowedTypes('with_issues_enabled', 'bool') + ->setNormalizer('with_issues_enabled', $booleanNormalizer) + ; + $resolver->setDefined('with_merge_requests_enabled') + ->setAllowedTypes('with_merge_requests_enabled', 'bool') + ->setNormalizer('with_merge_requests_enabled', $booleanNormalizer) + ; + $resolver->setDefined('min_access_level') + ->setAllowedValues('min_access_level', [null, 10, 20, 30, 40, 50]) + ; + + return $this->get('users/'.self::encodePath($id).'/projects', $resolver->resolve($parameters)); + } + + /** + * @param array $parameters { + * + * @var bool $archived limit by archived status + * @var string $visibility limit by visibility public, internal, or private + * @var string $order_by Return projects ordered by id, name, path, created_at, updated_at, + * or last_activity_at fields (default is created_at) + * @var string $sort Return projects sorted in asc or desc order (default is desc) + * @var string $search return list of projects matching the search criteria + * @var bool $simple return only the ID, URL, name, and path of each project + * @var bool $owned limit by projects owned by the current user + * @var bool $membership limit by projects that the current user is a member of + * @var bool $starred limit by projects starred by the current user + * @var bool $statistics include project statistics + * @var bool $with_issues_enabled limit by enabled issues feature + * @var bool $with_merge_requests_enabled limit by enabled merge requests feature + * @var int $min_access_level Limit by current user minimal access level + * @var bool $with_custom_attributes Include custom attributes in response (administrator only) + * } + */ + public function usersStarredProjects(int $id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $booleanNormalizer = function (Options $resolver, $value): string { + return $value ? 'true' : 'false'; + }; + $resolver->setDefined('archived') + ->setAllowedTypes('archived', 'bool') + ->setNormalizer('archived', $booleanNormalizer) + ; + $resolver->setDefined('visibility') + ->setAllowedValues('visibility', ['public', 'internal', 'private']) + ; + $resolver->setDefined('order_by') + ->setAllowedValues('order_by', ['id', 'name', 'path', 'created_at', 'updated_at', 'last_activity_at']) + ; + $resolver->setDefined('sort') + ->setAllowedValues('sort', ['asc', 'desc']) + ; + $resolver->setDefined('search'); + $resolver->setDefined('simple') + ->setAllowedTypes('simple', 'bool') + ->setNormalizer('simple', $booleanNormalizer) + ; + $resolver->setDefined('owned') + ->setAllowedTypes('owned', 'bool') + ->setNormalizer('owned', $booleanNormalizer) + ; + $resolver->setDefined('membership') + ->setAllowedTypes('membership', 'bool') + ->setNormalizer('membership', $booleanNormalizer) + ; + $resolver->setDefined('starred') + ->setAllowedTypes('starred', 'bool') + ->setNormalizer('starred', $booleanNormalizer) + ; + $resolver->setDefined('statistics') + ->setAllowedTypes('statistics', 'bool') + ->setNormalizer('statistics', $booleanNormalizer) + ; + $resolver->setDefined('with_issues_enabled') + ->setAllowedTypes('with_issues_enabled', 'bool') + ->setNormalizer('with_issues_enabled', $booleanNormalizer) + ; + $resolver->setDefined('with_merge_requests_enabled') + ->setAllowedTypes('with_merge_requests_enabled', 'bool') + ->setNormalizer('with_merge_requests_enabled', $booleanNormalizer) + ; + $resolver->setDefined('min_access_level') + ->setAllowedValues('min_access_level', [null, 10, 20, 30, 40, 50]) + ; + $resolver->setDefined('with_custom_attributes') + ->setAllowedTypes('with_custom_attributes', 'bool') + ->setNormalizer('with_custom_attributes', $booleanNormalizer) + ; + + return $this->get('users/'.self::encodePath($id).'/starred_projects', $resolver->resolve($parameters)); + } + + public function user(): mixed + { + return $this->get('user'); + } + + public function create(string $email, string $password, array $params = []): mixed + { + $params['email'] = $email; + $params['password'] = $password; + + return $this->post('users', $params); + } + + public function update(int $id, array $params, array $files = []): mixed + { + return $this->put('users/'.self::encodePath($id), $params, [], $files); + } + + /** + * @param array $params { + * + * @var bool $hard_delete If true, contributions that would usually be moved to the ghost user are + * deleted instead, as well as groups owned solely by this user. + * } + */ + public function remove(int $id, array $params = []): mixed + { + return $this->delete('users/'.self::encodePath($id), $params); + } + + public function block(int $id): mixed + { + return $this->post('users/'.self::encodePath($id).'/block'); + } + + public function unblock(int $id): mixed + { + return $this->post('users/'.self::encodePath($id).'/unblock'); + } + + public function activate(int $id): mixed + { + return $this->post('users/'.self::encodePath($id).'/activate'); + } + + public function deactivate(int $id): mixed + { + return $this->post('users/'.self::encodePath($id).'/deactivate'); + } + + public function me(): mixed + { + return $this->get('user'); + } + + public function keys(): mixed + { + return $this->get('user/keys'); + } + + public function key(int $id): mixed + { + return $this->get('user/keys/'.self::encodePath($id)); + } + + public function createKey(string $title, string $key): mixed + { + return $this->post('user/keys', [ + 'title' => $title, + 'key' => $key, + ]); + } + + public function removeKey(int $id): mixed + { + return $this->delete('user/keys/'.self::encodePath($id)); + } + + public function userKeys(int $user_id): mixed + { + return $this->get('users/'.self::encodePath($user_id).'/keys'); + } + + public function userKey(int $user_id, int $key_id): mixed + { + return $this->get('users/'.self::encodePath($user_id).'/keys/'.self::encodePath($key_id)); + } + + public function createKeyForUser(int $user_id, string $title, string $key): mixed + { + return $this->post('users/'.self::encodePath($user_id).'/keys', [ + 'title' => $title, + 'key' => $key, + ]); + } + + public function removeUserKey(int $user_id, int $key_id): mixed + { + return $this->delete('users/'.self::encodePath($user_id).'/keys/'.self::encodePath($key_id)); + } + + public function emails(): mixed + { + return $this->get('user/emails'); + } + + public function email(int $id): mixed + { + return $this->get('user/emails/'.self::encodePath($id)); + } + + public function userEmails(int $user_id): mixed + { + return $this->get('users/'.self::encodePath($user_id).'/emails'); + } + + public function createEmailForUser(int $user_id, string $email, bool $skip_confirmation = false): mixed + { + return $this->post('users/'.self::encodePath($user_id).'/emails', [ + 'email' => $email, + 'skip_confirmation' => $skip_confirmation, + ]); + } + + public function removeUserEmail(int $user_id, int $email_id): mixed + { + return $this->delete('users/'.self::encodePath($user_id).'/emails/'.self::encodePath($email_id)); + } + + public function userImpersonationTokens(int $user_id, array $params = []): mixed + { + $resolver = $this->createOptionsResolver(); + + $resolver->setDefined('state') + ->setAllowedValues('state', ['all', 'active', 'inactive']) + ; + + return $this->get('users/'.self::encodePath($user_id).'/impersonation_tokens', $resolver->resolve($params)); + } + + public function userImpersonationToken(int $user_id, int $impersonation_token_id): mixed + { + return $this->get('users/'.self::encodePath($user_id).'/impersonation_tokens/'.self::encodePath($impersonation_token_id)); + } + + public function createImpersonationToken(int $user_id, string $name, array $scopes, ?string $expires_at = null): mixed + { + return $this->post('users/'.self::encodePath($user_id).'/impersonation_tokens', [ + 'name' => $name, + 'scopes' => $scopes, + 'expires_at' => $expires_at, + ]); + } + + public function removeImpersonationToken(int $user_id, int $impersonation_token_id): mixed + { + return $this->delete('users/'.self::encodePath($user_id).'/impersonation_tokens/'.self::encodePath($impersonation_token_id)); + } + + /** + * @param array $parameters { + * + * @var string $action include only events of a particular action type + * @var string $target_type include only events of a particular target type + * @var \DateTimeInterface $before include only events created before a particular date + * @var \DateTimeInterface $after include only events created after a particular date + * @var string $sort Sort events in asc or desc order by created_at (default is desc) + * } + */ + public function events(int $user_id, array $parameters = []): mixed + { + $resolver = $this->createOptionsResolver(); + $datetimeNormalizer = function (Options $resolver, \DateTimeInterface $value): string { + return $value->format('Y-m-d'); + }; + + $resolver->setDefined('action') + ->setAllowedValues('action', ['created', 'updated', 'closed', 'reopened', 'pushed', 'commented', 'merged', 'joined', 'left', 'destroyed', 'expired']) + ; + $resolver->setDefined('target_type') + ->setAllowedValues('target_type', ['issue', 'milestone', 'merge_request', 'note', 'project', 'snippet', 'user']) + ; + $resolver->setDefined('before') + ->setAllowedTypes('before', \DateTimeInterface::class) + ->setNormalizer('before', $datetimeNormalizer); + $resolver->setDefined('after') + ->setAllowedTypes('after', \DateTimeInterface::class) + ->setNormalizer('after', $datetimeNormalizer) + ; + $resolver->setDefined('sort') + ->setAllowedValues('sort', ['asc', 'desc']) + ; + + return $this->get('users/'.self::encodePath($user_id).'/events', $resolver->resolve($parameters)); + } + + /** + * Deletes a user’s authentication identity using the provider name associated with that identity. + */ + public function removeUserIdentity(int $user_id, string $provider): mixed + { + return $this->delete('users/'.self::encodePath($user_id).'/identities/'.self::encodePath($provider)); + } +} diff --git a/src/Api/Version.php b/src/Api/Version.php new file mode 100644 index 000000000..8976a320e --- /dev/null +++ b/src/Api/Version.php @@ -0,0 +1,23 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +class Version extends AbstractApi +{ + public function show(): mixed + { + return $this->get('version'); + } +} diff --git a/src/Api/Wiki.php b/src/Api/Wiki.php new file mode 100644 index 000000000..fbb05551b --- /dev/null +++ b/src/Api/Wiki.php @@ -0,0 +1,59 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Api; + +class Wiki extends AbstractApi +{ + /** + * @param array $params + */ + public function create(int|string $project_id, array $params): mixed + { + return $this->post($this->getProjectPath($project_id, 'wikis'), $params); + } + + public function show(int|string $project_id, string $wiki_slug): mixed + { + return $this->get($this->getProjectPath($project_id, 'wikis/'.self::encodePath($wiki_slug))); + } + + /** + * @param array $params { + * + * @var bool $with_content Include pages' content + * } + */ + public function showAll(int|string $project_id, array $params): mixed + { + $resolver = $this->createOptionsResolver(); + $resolver->setDefined('with_content') + ->setAllowedTypes('with_content', 'bool'); + + return $this->get($this->getProjectPath($project_id, 'wikis'), $resolver->resolve($params)); + } + + /** + * @param array $params + */ + public function update(int|string $project_id, string $wiki_slug, array $params): mixed + { + return $this->put($this->getProjectPath($project_id, 'wikis/'.self::encodePath($wiki_slug)), $params); + } + + public function remove(int|string $project_id, string $wiki_slug): mixed + { + return $this->delete($this->getProjectPath($project_id, 'wikis/'.self::encodePath($wiki_slug))); + } +} diff --git a/src/Client.php b/src/Client.php new file mode 100644 index 000000000..ee4ff8a7b --- /dev/null +++ b/src/Client.php @@ -0,0 +1,349 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab; + +use Gitlab\Api\DeployKeys; +use Gitlab\Api\Deployments; +use Gitlab\Api\Environments; +use Gitlab\Api\Events; +use Gitlab\Api\Groups; +use Gitlab\Api\GroupsBoards; +use Gitlab\Api\GroupsEpics; +use Gitlab\Api\GroupsMilestones; +use Gitlab\Api\IssueBoards; +use Gitlab\Api\IssueLinks; +use Gitlab\Api\Issues; +use Gitlab\Api\IssuesStatistics; +use Gitlab\Api\Jobs; +use Gitlab\Api\Keys; +use Gitlab\Api\MergeRequests; +use Gitlab\Api\Milestones; +use Gitlab\Api\ProjectNamespaces; +use Gitlab\Api\Projects; +use Gitlab\Api\Repositories; +use Gitlab\Api\RepositoryFiles; +use Gitlab\Api\ResourceIterationEvents; +use Gitlab\Api\ResourceLabelEvents; +use Gitlab\Api\ResourceMilestoneEvents; +use Gitlab\Api\ResourceStateEvents; +use Gitlab\Api\ResourceWeightEvents; +use Gitlab\Api\Schedules; +use Gitlab\Api\Search; +use Gitlab\Api\Snippets; +use Gitlab\Api\SystemHooks; +use Gitlab\Api\Tags; +use Gitlab\Api\Users; +use Gitlab\Api\Version; +use Gitlab\Api\Wiki; +use Gitlab\HttpClient\Builder; +use Gitlab\HttpClient\Plugin\Authentication; +use Gitlab\HttpClient\Plugin\ExceptionThrower; +use Gitlab\HttpClient\Plugin\History; +use Http\Client\Common\HttpMethodsClientInterface; +use Http\Client\Common\Plugin\AddHostPlugin; +use Http\Client\Common\Plugin\HeaderDefaultsPlugin; +use Http\Client\Common\Plugin\HistoryPlugin; +use Http\Client\Common\Plugin\RedirectPlugin; +use Psr\Http\Client\ClientInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamFactoryInterface; + +/** + * Simple API wrapper for Gitlab. + * + * @author Matt Humphrey + */ +class Client +{ + /** + * The private token authentication method. + * + * @var string + */ + public const AUTH_HTTP_TOKEN = 'http_token'; + + /** + * The job token authentication method. + * + * @var string + */ + public const AUTH_HTTP_JOB_TOKEN = 'http_job_token'; + + /** + * The OAuth 2 token authentication method. + * + * @var string + */ + public const AUTH_OAUTH_TOKEN = 'oauth_token'; + + /** + * The default base URL. + * + * @var string + */ + private const BASE_URL = 'https://gitlab.com'; + + /** + * The default user agent header. + * + * @var string + */ + private const USER_AGENT = 'gitlab-php-api-client/12.0'; + + private readonly Builder $httpClientBuilder; + private readonly History $responseHistory; + + public function __construct(?Builder $httpClientBuilder = null) + { + $this->httpClientBuilder = $builder = $httpClientBuilder ?? new Builder(); + $this->responseHistory = new History(); + + $builder->addPlugin(new ExceptionThrower()); + $builder->addPlugin(new HistoryPlugin($this->responseHistory)); + $builder->addPlugin(new HeaderDefaultsPlugin([ + 'User-Agent' => self::USER_AGENT, + ])); + $builder->addPlugin(new RedirectPlugin()); + + $this->setUrl(self::BASE_URL); + } + + /** + * Create a Gitlab\Client using an HTTP client. + */ + public static function createWithHttpClient(ClientInterface $httpClient): self + { + $builder = new Builder($httpClient); + + return new self($builder); + } + + public function deployKeys(): DeployKeys + { + return new DeployKeys($this); + } + + public function deployments(): Deployments + { + return new Deployments($this); + } + + public function environments(): Environments + { + return new Environments($this); + } + + public function events(): Events + { + return new Events($this); + } + + public function groups(): Groups + { + return new Groups($this); + } + + public function groupsBoards(): GroupsBoards + { + return new GroupsBoards($this); + } + + public function groupsEpics(): GroupsEpics + { + return new GroupsEpics($this); + } + + public function groupsMilestones(): GroupsMilestones + { + return new GroupsMilestones($this); + } + + public function issueBoards(): IssueBoards + { + return new IssueBoards($this); + } + + public function issueLinks(): IssueLinks + { + return new IssueLinks($this); + } + + public function issues(): Issues + { + return new Issues($this); + } + + public function resourceIterationEvents(): ResourceIterationEvents + { + return new ResourceIterationEvents($this); + } + + public function resourceLabelEvents(): ResourceLabelEvents + { + return new ResourceLabelEvents($this); + } + + public function resourceMilestoneEvents(): ResourceMilestoneEvents + { + return new ResourceMilestoneEvents($this); + } + + public function resourceStateEvents(): ResourceStateEvents + { + return new ResourceStateEvents($this); + } + + public function resourceWeightEvents(): ResourceWeightEvents + { + return new ResourceWeightEvents($this); + } + + public function issuesStatistics(): IssuesStatistics + { + return new IssuesStatistics($this); + } + + public function jobs(): Jobs + { + return new Jobs($this); + } + + public function keys(): Keys + { + return new Keys($this); + } + + public function mergeRequests(): MergeRequests + { + return new MergeRequests($this); + } + + public function milestones(): Milestones + { + return new Milestones($this); + } + + public function namespaces(): ProjectNamespaces + { + return new ProjectNamespaces($this); + } + + public function projects(): Projects + { + return new Projects($this); + } + + public function repositories(): Repositories + { + return new Repositories($this); + } + + public function repositoryFiles(): RepositoryFiles + { + return new RepositoryFiles($this); + } + + public function search(): Search + { + return new Search($this); + } + + public function schedules(): Schedules + { + return new Schedules($this); + } + + public function snippets(): Snippets + { + return new Snippets($this); + } + + public function systemHooks(): SystemHooks + { + return new SystemHooks($this); + } + + public function tags(): Tags + { + return new Tags($this); + } + + public function users(): Users + { + return new Users($this); + } + + public function version(): Version + { + return new Version($this); + } + + public function wiki(): Wiki + { + return new Wiki($this); + } + + /** + * Authenticate a user for all next requests. + * + * @param string $token Gitlab private token + * @param string $authMethod One of the AUTH_* class constants + */ + public function authenticate(string $token, string $authMethod, ?string $sudo = null): void + { + $this->getHttpClientBuilder()->removePlugin(Authentication::class); + $this->getHttpClientBuilder()->addPlugin(new Authentication($authMethod, $token, $sudo)); + } + + public function setUrl(string $url): void + { + $uri = $this->getHttpClientBuilder()->getUriFactory()->createUri($url); + + $this->getHttpClientBuilder()->removePlugin(AddHostPlugin::class); + $this->getHttpClientBuilder()->addPlugin(new AddHostPlugin($uri)); + } + + /** + * Get the last response. + */ + public function getLastResponse(): ?ResponseInterface + { + return $this->responseHistory->getLastResponse(); + } + + /** + * Get the HTTP client. + */ + public function getHttpClient(): HttpMethodsClientInterface + { + return $this->getHttpClientBuilder()->getHttpClient(); + } + + /** + * Get the stream factory. + */ + public function getStreamFactory(): StreamFactoryInterface + { + return $this->getHttpClientBuilder()->getStreamFactory(); + } + + /** + * Get the HTTP client builder. + */ + protected function getHttpClientBuilder(): Builder + { + return $this->httpClientBuilder; + } +} diff --git a/src/Exception/ApiLimitExceededException.php b/src/Exception/ApiLimitExceededException.php new file mode 100644 index 000000000..46cd23b27 --- /dev/null +++ b/src/Exception/ApiLimitExceededException.php @@ -0,0 +1,22 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Exception; + +/** + * @author Graham Campbell + */ +class ApiLimitExceededException extends RuntimeException +{ +} diff --git a/src/Exception/ErrorException.php b/src/Exception/ErrorException.php new file mode 100644 index 000000000..156bb1afd --- /dev/null +++ b/src/Exception/ErrorException.php @@ -0,0 +1,22 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Exception; + +/** + * @author Graham Campbell + */ +class ErrorException extends \ErrorException implements ExceptionInterface +{ +} diff --git a/src/Exception/ExceptionInterface.php b/src/Exception/ExceptionInterface.php new file mode 100644 index 000000000..5779ed0cf --- /dev/null +++ b/src/Exception/ExceptionInterface.php @@ -0,0 +1,24 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Exception; + +use Http\Client\Exception; + +/** + * @author Graham Campbell + */ +interface ExceptionInterface extends Exception +{ +} diff --git a/src/Exception/RuntimeException.php b/src/Exception/RuntimeException.php new file mode 100644 index 000000000..fe3f876c2 --- /dev/null +++ b/src/Exception/RuntimeException.php @@ -0,0 +1,22 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Exception; + +/** + * @author Graham Campbell + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/src/Exception/ValidationFailedException.php b/src/Exception/ValidationFailedException.php new file mode 100644 index 000000000..2c9aa27b2 --- /dev/null +++ b/src/Exception/ValidationFailedException.php @@ -0,0 +1,22 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Exception; + +/** + * @author Graham Campbell + */ +class ValidationFailedException extends ErrorException +{ +} diff --git a/src/HttpClient/Builder.php b/src/HttpClient/Builder.php new file mode 100644 index 000000000..e94248f50 --- /dev/null +++ b/src/HttpClient/Builder.php @@ -0,0 +1,156 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\HttpClient; + +use Http\Client\Common\HttpMethodsClient; +use Http\Client\Common\HttpMethodsClientInterface; +use Http\Client\Common\Plugin; +use Http\Client\Common\Plugin\Cache\Generator\HeaderCacheKeyGenerator; +use Http\Client\Common\Plugin\CachePlugin; +use Http\Client\Common\PluginClientFactory; +use Http\Discovery\Psr17FactoryDiscovery; +use Http\Discovery\Psr18ClientDiscovery; +use Psr\Cache\CacheItemPoolInterface; +use Psr\Http\Client\ClientInterface; +use Psr\Http\Message\RequestFactoryInterface; +use Psr\Http\Message\StreamFactoryInterface; +use Psr\Http\Message\UriFactoryInterface; + +/** + * The HTTP client builder class. + * + * This will allow you to fluently add and remove plugins. + * + * @author Tobias Nyholm + * @author Graham Campbell + */ +final class Builder +{ + private readonly ClientInterface $httpClient; + private readonly RequestFactoryInterface $requestFactory; + private readonly StreamFactoryInterface $streamFactory; + private readonly UriFactoryInterface $uriFactory; + + /** + * @var Plugin[] + */ + private array $plugins = []; + + private ?CachePlugin $cachePlugin; + + private ?HttpMethodsClientInterface $pluginClient; + + public function __construct( + ?ClientInterface $httpClient = null, + ?RequestFactoryInterface $requestFactory = null, + ?StreamFactoryInterface $streamFactory = null, + ?UriFactoryInterface $uriFactory = null + ) { + $this->httpClient = $httpClient ?? Psr18ClientDiscovery::find(); + $this->requestFactory = $requestFactory ?? Psr17FactoryDiscovery::findRequestFactory(); + $this->streamFactory = $streamFactory ?? Psr17FactoryDiscovery::findStreamFactory(); + $this->uriFactory = $uriFactory ?? Psr17FactoryDiscovery::findUriFactory(); + + $this->plugins = []; + $this->cachePlugin = null; + $this->pluginClient = null; + } + + public function getHttpClient(): HttpMethodsClientInterface + { + if (null === $this->pluginClient) { + $plugins = $this->plugins; + if (null !== $this->cachePlugin) { + $plugins[] = $this->cachePlugin; + } + + $this->pluginClient = new HttpMethodsClient( + (new PluginClientFactory())->createClient($this->httpClient, $plugins), + $this->requestFactory, + $this->streamFactory + ); + } + + return $this->pluginClient; + } + + /** + * Get the request factory. + */ + public function getRequestFactory(): RequestFactoryInterface + { + return $this->requestFactory; + } + + /** + * Get the stream factory. + */ + public function getStreamFactory(): StreamFactoryInterface + { + return $this->streamFactory; + } + + /** + * Get the URI factory. + */ + public function getUriFactory(): UriFactoryInterface + { + return $this->uriFactory; + } + + /** + * Add a new plugin to the end of the plugin chain. + */ + public function addPlugin(Plugin $plugin): void + { + $this->plugins[] = $plugin; + $this->pluginClient = null; + } + + /** + * Remove a plugin by its fully qualified class name (FQCN). + */ + public function removePlugin(string $fqcn): void + { + foreach ($this->plugins as $idx => $plugin) { + if ($plugin instanceof $fqcn) { + unset($this->plugins[$idx]); + $this->pluginClient = null; + } + } + } + + /** + * Add a cache plugin to cache responses locally. + */ + public function addCache(CacheItemPoolInterface $cachePool, array $config = []): void + { + if (!isset($config['cache_key_generator'])) { + $config['cache_key_generator'] = new HeaderCacheKeyGenerator(['Authorization', 'Cookie', 'Accept', 'Content-type']); + } + + $this->cachePlugin = CachePlugin::clientCache($cachePool, $this->streamFactory, $config); + $this->pluginClient = null; + } + + /** + * Remove the cache plugin. + */ + public function removeCache(): void + { + $this->cachePlugin = null; + $this->pluginClient = null; + } +} diff --git a/src/HttpClient/Message/ResponseMediator.php b/src/HttpClient/Message/ResponseMediator.php new file mode 100644 index 000000000..ef66fae67 --- /dev/null +++ b/src/HttpClient/Message/ResponseMediator.php @@ -0,0 +1,170 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\HttpClient\Message; + +use Gitlab\Exception\RuntimeException; +use Gitlab\HttpClient\Util\JsonArray; +use Psr\Http\Message\ResponseInterface; + +/** + * Utilities to parse response headers and content. + */ +final class ResponseMediator +{ + /** + * The content type header. + * + * @var string + */ + public const CONTENT_TYPE_HEADER = 'Content-Type'; + + /** + * The JSON content type identifier. + * + * @var string + */ + public const JSON_CONTENT_TYPE = 'application/json'; + + /** + * The octet stream content type identifier. + * + * @var string + */ + public const STREAM_CONTENT_TYPE = 'application/octet-stream'; + + /** + * The multipart form data content type identifier. + * + * @var string + */ + public const MULTIPART_CONTENT_TYPE = 'multipart/form-data'; + + /** + * Return the response body as a string or JSON array if content type is JSON. + */ + public static function getContent(ResponseInterface $response): array|string + { + $body = (string) $response->getBody(); + + if (!\in_array($body, ['', 'null', 'true', 'false'], true) && 0 === \strpos($response->getHeaderLine(self::CONTENT_TYPE_HEADER), self::JSON_CONTENT_TYPE)) { + return JsonArray::decode($body); + } + + return $body; + } + + /** + * Extract pagination URIs from Link header. + * + * @return array + */ + public static function getPagination(ResponseInterface $response): array + { + $header = self::getHeader($response, 'Link'); + + if (null === $header) { + return []; + } + + $pagination = []; + foreach (\explode(',', $header) as $link) { + \preg_match('/<(.*)>; rel="(.*)"/i', \trim($link, ','), $match); + + /** @var string[] $match */ + if (3 === \count($match)) { + $pagination[$match[2]] = $match[1]; + } + } + + return $pagination; + } + + /** + * Get the value for a single header. + */ + private static function getHeader(ResponseInterface $response, string $name): ?string + { + $headers = $response->getHeader($name); + + return \array_shift($headers); + } + + /** + * Get the error message from the response if present. + */ + public static function getErrorMessage(ResponseInterface $response): ?string + { + try { + $content = self::getContent($response); + } catch (RuntimeException $e) { + return null; + } + + if (!\is_array($content)) { + return null; + } + + if (isset($content['message'])) { + $message = $content['message']; + + if (\is_string($message)) { + return $message; + } + + if (\is_array($message)) { + return self::getMessageAsString($content['message']); + } + } + + if (isset($content['error_description'])) { + $error = $content['error_description']; + + if (\is_string($error)) { + return $error; + } + } + + if (isset($content['error'])) { + $error = $content['error']; + + if (\is_string($error)) { + return $error; + } + } + + return null; + } + + private static function getMessageAsString(array $message): string + { + $format = '"%s" %s'; + $errors = []; + + foreach ($message as $field => $messages) { + if (\is_array($messages)) { + $messages = \array_unique($messages); + foreach ($messages as $error) { + $errors[] = \sprintf($format, $field, $error); + } + } elseif (\is_int($field)) { + $errors[] = $messages; + } else { + $errors[] = \sprintf($format, $field, $messages); + } + } + + return \implode(', ', $errors); + } +} diff --git a/src/HttpClient/Plugin/Authentication.php b/src/HttpClient/Plugin/Authentication.php new file mode 100644 index 000000000..148a523f9 --- /dev/null +++ b/src/HttpClient/Plugin/Authentication.php @@ -0,0 +1,92 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\HttpClient\Plugin; + +use Gitlab\Client; +use Gitlab\Exception\RuntimeException; +use Http\Client\Common\Plugin; +use Http\Promise\Promise; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; + +/** + * Add authentication to the request. + * + * @author Tobias Nyholm + * @author Fabien Bourigault + * + * @internal + */ +final class Authentication implements Plugin +{ + /** + * @var array + */ + private readonly array $headers; + + public function __construct(string $method, string $token, ?string $sudo = null) + { + $this->headers = self::buildHeaders($method, $token, $sudo); + } + + /** + * Handle the request and return the response coming from the next callable. + * + * @return Promise + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise + { + foreach ($this->headers as $header => $value) { + $request = $request->withHeader($header, $value); + } + + return $next($request); + } + + /** + * Build the headers to be attached to the request. + * + * @throws RuntimeException + * + * @return array + */ + private static function buildHeaders(string $method, string $token, ?string $sudo = null): array + { + $headers = []; + + switch ($method) { + case Client::AUTH_HTTP_TOKEN: + $headers['PRIVATE-TOKEN'] = $token; + + break; + case Client::AUTH_HTTP_JOB_TOKEN: + $headers['JOB-TOKEN'] = $token; + + break; + case Client::AUTH_OAUTH_TOKEN: + $headers['Authorization'] = \sprintf('Bearer %s', $token); + + break; + default: + throw new RuntimeException(\sprintf('Authentication method "%s" not implemented.', $method)); + } + + if (null !== $sudo) { + $headers['SUDO'] = $sudo; + } + + return $headers; + } +} diff --git a/src/HttpClient/Plugin/ExceptionThrower.php b/src/HttpClient/Plugin/ExceptionThrower.php new file mode 100644 index 000000000..d8d76caa7 --- /dev/null +++ b/src/HttpClient/Plugin/ExceptionThrower.php @@ -0,0 +1,73 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\HttpClient\Plugin; + +use Gitlab\Exception\ApiLimitExceededException; +use Gitlab\Exception\ErrorException; +use Gitlab\Exception\ExceptionInterface; +use Gitlab\Exception\RuntimeException; +use Gitlab\Exception\ValidationFailedException; +use Gitlab\HttpClient\Message\ResponseMediator; +use Http\Client\Common\Plugin; +use Http\Promise\Promise; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; + +/** + * A plugin to remember the last response. + * + * @author Tobias Nyholm + * @author Fabien Bourigault + * + * @internal + */ +final class ExceptionThrower implements Plugin +{ + /** + * Handle the request and return the response coming from the next callable. + * + * @return Promise + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise + { + return $next($request)->then(function (ResponseInterface $response): ResponseInterface { + $status = $response->getStatusCode(); + + if ($status >= 400 && $status < 600) { + throw self::createException($status, ResponseMediator::getErrorMessage($response) ?? $response->getReasonPhrase()); + } + + return $response; + }); + } + + /** + * Create an exception from a status code and error message. + * + * @return ErrorException|RuntimeException + */ + private static function createException(int $status, string $message): ExceptionInterface + { + if (400 === $status || 422 === $status) { + return new ValidationFailedException($message, $status); + } + + if (429 === $status) { + return new ApiLimitExceededException($message, $status); + } + + return new RuntimeException($message, $status); + } +} diff --git a/src/HttpClient/Plugin/History.php b/src/HttpClient/Plugin/History.php new file mode 100644 index 000000000..5e41ca93d --- /dev/null +++ b/src/HttpClient/Plugin/History.php @@ -0,0 +1,60 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\HttpClient\Plugin; + +use Http\Client\Common\Plugin\Journal; +use Psr\Http\Client\ClientExceptionInterface; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; + +/** + * A plugin to remember the last response. + * + * @author Tobias Nyholm + * + * @internal + */ +final class History implements Journal +{ + private ?ResponseInterface $lastResponse; + + public function __construct() + { + $this->lastResponse = null; + } + + /** + * Get the last response. + */ + public function getLastResponse(): ?ResponseInterface + { + return $this->lastResponse; + } + + /** + * Record a successful call. + */ + public function addSuccess(RequestInterface $request, ResponseInterface $response): void + { + $this->lastResponse = $response; + } + + /** + * Record a failed call. + */ + public function addFailure(RequestInterface $request, ClientExceptionInterface $exception): void + { + } +} diff --git a/src/HttpClient/Util/JsonArray.php b/src/HttpClient/Util/JsonArray.php new file mode 100644 index 000000000..59a9c49f1 --- /dev/null +++ b/src/HttpClient/Util/JsonArray.php @@ -0,0 +1,61 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\HttpClient\Util; + +use Gitlab\Exception\RuntimeException; + +/** + * @internal + */ +final class JsonArray +{ + /** + * Decode a JSON string into a PHP array. + * + * @throws RuntimeException + */ + public static function decode(string $json): array + { + /** @var scalar|array|null */ + $data = \json_decode($json, true); + + if (\JSON_ERROR_NONE !== \json_last_error()) { + throw new RuntimeException(\sprintf('json_decode error: %s', \json_last_error_msg())); + } + + if (!\is_array($data)) { + throw new RuntimeException(\sprintf('json_decode error: Expected JSON of type array, %s given.', \get_debug_type($data))); + } + + return $data; + } + + /** + * Encode a PHP array into a JSON string. + * + * @throws RuntimeException + */ + public static function encode(array $value): string + { + $json = \json_encode($value); + + if (\JSON_ERROR_NONE !== \json_last_error()) { + throw new RuntimeException(\sprintf('json_encode error: %s', \json_last_error_msg())); + } + + /** @var string */ + return $json; + } +} diff --git a/src/HttpClient/Util/QueryStringBuilder.php b/src/HttpClient/Util/QueryStringBuilder.php new file mode 100644 index 000000000..462f5ff47 --- /dev/null +++ b/src/HttpClient/Util/QueryStringBuilder.php @@ -0,0 +1,76 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\HttpClient\Util; + +/** + * @internal + */ +final class QueryStringBuilder +{ + /** + * Encode a query as a query string according to RFC 3986. + * + * Indexed arrays are encoded using empty squared brackets ([]) unlike + * `http_build_query`. + */ + public static function build(array $query): string + { + return \sprintf('?%s', \implode('&', \array_map(function ($value, $key): string { + return self::encode($value, $key); + }, $query, \array_keys($query)))); + } + + /** + * Encode a value. + */ + private static function encode(mixed $query, int|string $prefix): string + { + if (!\is_array($query)) { + return self::rawurlencode($prefix).'='.self::rawurlencode($query); + } + + $isList = self::isList($query); + + return \implode('&', \array_map(function ($value, $key) use ($prefix, $isList): string { + $prefix = $isList ? $prefix.'[]' : $prefix.'['.$key.']'; + + return self::encode($value, $prefix); + }, $query, \array_keys($query))); + } + + /** + * Tell if the given array is a list. + */ + private static function isList(array $query): bool + { + if (0 === \count($query) || !isset($query[0])) { + return false; + } + + return \array_keys($query) === \range(0, \count($query) - 1); + } + + /** + * Encode a value like rawurlencode, but return "0" when false is given. + */ + private static function rawurlencode(mixed $value): string + { + if (false === $value) { + return '0'; + } + + return \rawurlencode((string) $value); + } +} diff --git a/src/ResultPager.php b/src/ResultPager.php new file mode 100644 index 000000000..d2ab7ba2e --- /dev/null +++ b/src/ResultPager.php @@ -0,0 +1,211 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab; + +use Closure; +use Generator; +use Gitlab\Api\AbstractApi; +use Gitlab\Exception\RuntimeException; +use Gitlab\HttpClient\Message\ResponseMediator; +use ValueError; + +/** + * This is the result pager class. + * + * @author Ramon de la Fuente + * @author Mitchel Verschoof + * @author Graham Campbell + */ +final class ResultPager implements ResultPagerInterface +{ + /** + * The default number of entries to request per page. + * + * @var int + */ + private const PER_PAGE = 50; + + private readonly Client $client; + + private readonly int $perPage; + + /** + * @var array + */ + private array $pagination; + + public function __construct(Client $client, ?int $perPage = null) + { + if (null !== $perPage && ($perPage < 1 || $perPage > 100)) { + throw new ValueError(\sprintf('%s::__construct(): Argument #2 ($perPage) must be between 1 and 100, or null', self::class)); + } + + $this->client = $client; + $this->perPage = $perPage ?? self::PER_PAGE; + $this->pagination = []; + } + + /** + * Fetch a single result from an api call. + * + * @throws \Http\Client\Exception + */ + public function fetch(AbstractApi $api, string $method, array $parameters = []): array + { + $result = self::bindPerPage($api, $this->perPage)->$method(...$parameters); + + if (!\is_array($result)) { + throw new RuntimeException('Pagination of this endpoint is not supported.'); + } + + $this->postFetch(); + + return $result; + } + + /** + * Fetch all results from an api call. + * + * @throws \Http\Client\Exception + */ + public function fetchAll(AbstractApi $api, string $method, array $parameters = []): array + { + return \iterator_to_array($this->fetchAllLazy($api, $method, $parameters)); + } + + /** + * Lazily fetch all results from an api call. + * + * @throws \Http\Client\Exception + */ + public function fetchAllLazy(AbstractApi $api, string $method, array $parameters = []): Generator + { + /** @var mixed $value */ + foreach ($this->fetch($api, $method, $parameters) as $value) { + yield $value; + } + + while ($this->hasNext()) { + /** @var mixed $value */ + foreach ($this->fetchNext() as $value) { + yield $value; + } + } + } + + /** + * Check to determine the availability of a next page. + */ + public function hasNext(): bool + { + return isset($this->pagination['next']); + } + + /** + * Fetch the next page. + * + * @throws \Http\Client\Exception + */ + public function fetchNext(): array + { + return $this->get('next'); + } + + /** + * Check to determine the availability of a previous page. + */ + public function hasPrevious(): bool + { + return isset($this->pagination['prev']); + } + + /** + * Fetch the previous page. + * + * @throws \Http\Client\Exception + */ + public function fetchPrevious(): array + { + return $this->get('prev'); + } + + /** + * Fetch the first page. + * + * @throws \Http\Client\Exception + */ + public function fetchFirst(): array + { + return $this->get('first'); + } + + /** + * Fetch the last page. + * + * @throws \Http\Client\Exception + */ + public function fetchLast(): array + { + return $this->get('last'); + } + + /** + * Refresh the pagination property. + */ + private function postFetch(): void + { + $response = $this->client->getLastResponse(); + + $this->pagination = null === $response ? [] : ResponseMediator::getPagination($response); + } + + /** + * @throws \Http\Client\Exception + */ + private function get(string $key): array + { + $pagination = $this->pagination[$key] ?? null; + + if (null === $pagination) { + return []; + } + + $result = $this->client->getHttpClient()->get($pagination); + + $content = ResponseMediator::getContent($result); + + if (!\is_array($content)) { + throw new RuntimeException('Pagination of this endpoint is not supported.'); + } + + $this->postFetch(); + + return $content; + } + + private static function bindPerPage(AbstractApi $api, int $perPage): AbstractApi + { + /** @var Closure(AbstractApi): AbstractApi */ + $closure = Closure::bind(static function (AbstractApi $api) use ($perPage): AbstractApi { + $clone = clone $api; + + $clone->perPage = $perPage; + + return $clone; + }, null, AbstractApi::class); + + return $closure($api); + } +} diff --git a/src/ResultPagerInterface.php b/src/ResultPagerInterface.php new file mode 100644 index 000000000..445455e05 --- /dev/null +++ b/src/ResultPagerInterface.php @@ -0,0 +1,87 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab; + +use Generator; +use Gitlab\Api\AbstractApi; + +/** + * This is the result pager interface. + * + * @author Ramon de la Fuente + * @author Mitchel Verschoof + * @author Graham Campbell + */ +interface ResultPagerInterface +{ + /** + * Fetch a single result from an api call. + * + * @throws \Http\Client\Exception + */ + public function fetch(AbstractApi $api, string $method, array $parameters = []): array; + + /** + * Fetch all results from an api call. + * + * @throws \Http\Client\Exception + */ + public function fetchAll(AbstractApi $api, string $method, array $parameters = []): array; + + /** + * Lazily fetch all results from an api call. + * + * @throws \Http\Client\Exception + */ + public function fetchAllLazy(AbstractApi $api, string $method, array $parameters = []): Generator; + + /** + * Check to determine the availability of a next page. + */ + public function hasNext(): bool; + + /** + * Fetch the next page. + * + * @throws \Http\Client\Exception + */ + public function fetchNext(): array; + + /** + * Check to determine the availability of a previous page. + */ + public function hasPrevious(): bool; + + /** + * Fetch the previous page. + * + * @throws \Http\Client\Exception + */ + public function fetchPrevious(): array; + + /** + * Fetch the first page. + * + * @throws \Http\Client\Exception + */ + public function fetchFirst(): array; + + /** + * Fetch the last page. + * + * @throws \Http\Client\Exception + */ + public function fetchLast(): array; +} diff --git a/test/Gitlab/Tests/Api/DeploymentsTest.php b/test/Gitlab/Tests/Api/DeploymentsTest.php deleted file mode 100644 index 3373ef4e1..000000000 --- a/test/Gitlab/Tests/Api/DeploymentsTest.php +++ /dev/null @@ -1,248 +0,0 @@ - '2016-08-11T07:36:40.222Z', - 'deployable' => - array( - 'commit' => - array( - 'author_email' => 'admin@example.com', - 'author_name' => 'Administrator', - 'created_at' => '2016-08-11T09:36:01.000+02:00', - 'id' => '99d03678b90d914dbb1b109132516d71a4a03ea8', - 'message' => 'Merge branch \'new-title\' into \'master\' - -Update README - - - -See merge request !1', - 'short_id' => '99d03678', - 'title' => 'Merge branch \'new-title\' into \'master\' -', - ), - 'coverage' => null, - 'created_at' => '2016-08-11T07:36:27.357Z', - 'finished_at' => '2016-08-11T07:36:39.851Z', - 'id' => 657, - 'name' => 'deploy', - 'ref' => 'master', - 'runner' => null, - 'stage' => 'deploy', - 'started_at' => null, - 'status' => 'success', - 'tag' => false, - 'user' => - array( - 'avatar_url' => 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', - 'bio' => null, - 'created_at' => '2016-08-11T07:09:20.351Z', - 'id' => 1, - 'linkedin' => '', - 'location' => null, - 'name' => 'Administrator', - 'skype' => '', - 'state' => 'active', - 'twitter' => '', - 'username' => 'root', - 'web_url' => 'http://localhost:3000/root', - 'website_url' => '', - ), - ), - 'environment' => - array( - 'external_url' => 'https://about.gitlab.com', - 'id' => 9, - 'name' => 'production', - ), - 'id' => 41, - 'iid' => 1, - 'ref' => 'master', - 'sha' => '99d03678b90d914dbb1b109132516d71a4a03ea8', - 'user' => - array( - 'avatar_url' => 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', - 'id' => 1, - 'name' => 'Administrator', - 'state' => 'active', - 'username' => 'root', - 'web_url' => 'http://localhost:3000/root', - ), - ), - array( - 'created_at' => '2016-08-11T11:32:35.444Z', - 'deployable' => - array( - 'commit' => - array( - 'author_email' => 'admin@example.com', - 'author_name' => 'Administrator', - 'created_at' => '2016-08-11T13:28:26.000+02:00', - 'id' => 'a91957a858320c0e17f3a0eca7cfacbff50ea29a', - 'message' => 'Merge branch \'rename-readme\' into \'master\' - -Rename README - - - -See merge request !2', - 'short_id' => 'a91957a8', - 'title' => 'Merge branch \'rename-readme\' into \'master\' -', - ), - 'coverage' => null, - 'created_at' => '2016-08-11T11:32:24.456Z', - 'finished_at' => '2016-08-11T11:32:35.145Z', - 'id' => 664, - 'name' => 'deploy', - 'ref' => 'master', - 'runner' => null, - 'stage' => 'deploy', - 'started_at' => null, - 'status' => 'success', - 'tag' => false, - 'user' => - array( - 'avatar_url' => 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', - 'bio' => null, - 'created_at' => '2016-08-11T07:09:20.351Z', - 'id' => 1, - 'linkedin' => '', - 'location' => null, - 'name' => 'Administrator', - 'skype' => '', - 'state' => 'active', - 'twitter' => '', - 'username' => 'root', - 'web_url' => 'http://localhost:3000/root', - 'website_url' => '', - ), - ), - 'environment' => - array( - 'external_url' => 'https://about.gitlab.com', - 'id' => 9, - 'name' => 'production', - ), - 'id' => 42, - 'iid' => 2, - 'ref' => 'master', - 'sha' => 'a91957a858320c0e17f3a0eca7cfacbff50ea29a', - 'user' => - array( - 'avatar_url' => 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', - 'id' => 1, - 'name' => 'Administrator', - 'state' => 'active', - 'username' => 'root', - 'web_url' => 'http://localhost:3000/root', - ), - ), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/deployments') - ->will($this->returnValue($expectedArray)); - $this->assertEquals($expectedArray, $api->all(1)); - } - - /** - * @test - */ - public function shouldShowDeployment() - { - $expectedArray = array( - array( - 'created_at' => '2016-08-11T11:32:35.444Z', - 'deployable' => - array( - 'commit' => - array( - 'author_email' => 'admin@example.com', - 'author_name' => 'Administrator', - 'created_at' => '2016-08-11T13:28:26.000+02:00', - 'id' => 'a91957a858320c0e17f3a0eca7cfacbff50ea29a', - 'message' => 'Merge branch \'rename-readme\' into \'master\' - -Rename README - - - -See merge request !2', - 'short_id' => 'a91957a8', - 'title' => 'Merge branch \'rename-readme\' into \'master\' -', - ), - 'coverage' => null, - 'created_at' => '2016-08-11T11:32:24.456Z', - 'finished_at' => '2016-08-11T11:32:35.145Z', - 'id' => 664, - 'name' => 'deploy', - 'ref' => 'master', - 'runner' => null, - 'stage' => 'deploy', - 'started_at' => null, - 'status' => 'success', - 'tag' => false, - 'user' => - array( - 'avatar_url' => 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', - 'bio' => null, - 'created_at' => '2016-08-11T07:09:20.351Z', - 'id' => 1, - 'linkedin' => '', - 'location' => null, - 'name' => 'Administrator', - 'skype' => '', - 'state' => 'active', - 'twitter' => '', - 'username' => 'root', - 'web_url' => 'http://localhost:3000/root', - 'website_url' => '', - ), - ), - 'environment' => - array( - 'external_url' => 'https://about.gitlab.com', - 'id' => 9, - 'name' => 'production', - ), - 'id' => 42, - 'iid' => 2, - 'ref' => 'master', - 'sha' => 'a91957a858320c0e17f3a0eca7cfacbff50ea29a', - 'user' => - array( - 'avatar_url' => 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', - 'id' => 1, - 'name' => 'Administrator', - 'state' => 'active', - 'username' => 'root', - 'web_url' => 'http://localhost:3000/root', - ), - ), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/deployments/42') - ->will($this->returnValue($expectedArray)); - $this->assertEquals($expectedArray, $api->show(1, 42)); - } - - protected function getApiClass() - { - return 'Gitlab\Api\Deployments'; - } -} diff --git a/test/Gitlab/Tests/Api/EnvironmentsTest.php b/test/Gitlab/Tests/Api/EnvironmentsTest.php deleted file mode 100644 index 5715ea315..000000000 --- a/test/Gitlab/Tests/Api/EnvironmentsTest.php +++ /dev/null @@ -1,80 +0,0 @@ - 1, - 'name' => 'review/fix-foo', - 'slug' => 'review-fix-foo-dfjre3', - 'external_url' => 'https://review-fix-foo-dfjre3.example.gitlab.com' - ), - array( - 'id' => 2, - 'name' => 'review/fix-bar', - 'slug' => 'review-fix-bar-dfjre4', - 'external_url' => 'https://review-fix-bar-dfjre4.example.gitlab.com' - ), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/environments') - ->will($this->returnValue($expectedArray)); - $this->assertEquals($expectedArray, $api->all(1)); - } - - /** - * @test - */ - public function shouldCreateEnvironment() - { - $expectedArray = array( - array( - 'id' => 3, - 'name' => 'review/fix-baz', - 'slug' => 'review-fix-baz-dfjre5', - 'external_url' => 'https://review-fix-baz-dfjre5.example.gitlab.com' - ), - ); - - $params = array( - 'name' => 'review/fix-baz', - 'external_url' => 'https://review-fix-baz-dfjre5.example.gitlab.com' - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/environment', $params) - ->will($this->returnValue($expectedArray)); - - $this->assertEquals($expectedArray, $api->create(1, $params)); - } - - /** - * @test - */ - public function shouldRemoveEnvironment() - { - $expectedBool = true; - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('delete') - ->with('projects/1/environments/3') - ->will($this->returnValue($expectedBool)); - $this->assertEquals($expectedBool, $api->remove(1, 3)); - } - - protected function getApiClass() - { - return 'Gitlab\Api\Environments'; - } -} diff --git a/test/Gitlab/Tests/Api/GroupsTest.php b/test/Gitlab/Tests/Api/GroupsTest.php deleted file mode 100644 index 5ba1324ca..000000000 --- a/test/Gitlab/Tests/Api/GroupsTest.php +++ /dev/null @@ -1,264 +0,0 @@ - 1, 'name' => 'A group'), - array('id' => 2, 'name' => 'Another group'), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('groups', array('page' => 1, 'per_page' => 10)) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->all(['page' => 1, 'per_page' => 10])); - } - - /** - * @test - */ - public function shouldGetAllGroupsWithBooleanParam() - { - $expectedArray = array( - array('id' => 1, 'name' => 'A group'), - array('id' => 2, 'name' => 'Another group'), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('groups', ['all_available' => 'false']) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->all(['all_available' => false])); - } - - /** - * @test - */ - public function shouldGetAllGroupProjectsWithBooleanParam() - { - $expectedArray = array( - array('id' => 1, 'name' => 'A group'), - array('id' => 2, 'name' => 'Another group'), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('groups/1/projects', ['archived' => 'false']) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->projects(1, ['archived' => false])); - } - - /** - * @test - */ - public function shouldNotNeedPaginationWhenGettingGroups() - { - $expectedArray = array( - array('id' => 1, 'name' => 'A group'), - array('id' => 2, 'name' => 'Another group'), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('groups', array()) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->all()); - } - - /** - * @test - */ - public function shouldShowGroup() - { - $expectedArray = array('id' => 1, 'name' => 'A group'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('groups/1') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->show(1)); - } - - /** - * @test - */ - public function shouldCreateGroup() - { - $expectedArray = array('id' => 1, 'name' => 'A new group'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('groups', array('name' => 'A new group', 'path' => 'a-new-group', 'description' => null, 'visibility' => 'private')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->create('A new group', 'a-new-group')); - } - - /** - * @test - */ - public function shouldCreateGroupWithDescriptionAndVisibility() - { - $expectedArray = array('id' => 1, 'name' => 'A new group', 'visibility_level' => 2); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('groups', array('name' => 'A new group', 'path' => 'a-new-group', 'description' => 'Description', 'visibility' => 'public')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->create('A new group', 'a-new-group', 'Description', 'public')); - } - - /** - * @test - */ - public function shouldUpdateGroup() - { - $expectedArray = array('id' => 3, 'name' => 'Group name', 'path' => 'group-path'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('put') - ->with('groups/3', array('name' => 'Group name', 'path' => 'group-path')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->update(3, array('name' => 'Group name', 'path' => 'group-path'))); - } - - /** - * @test - */ - public function shouldTransferProjectToGroup() - { - $expectedBool = true; - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('groups/1/projects/2') - ->will($this->returnValue($expectedBool)) - ; - - $this->assertEquals($expectedBool, $api->transfer(1, 2)); - } - - /** - * @test - */ - public function shouldGetMembers() - { - $expectedArray = array( - array('id' => 1, 'name' => 'Matt'), - array('id' => 2, 'name' => 'Bob') - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('groups/1/members') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->members(1)); - } - - /** - * @test - */ - public function shouldAddMember() - { - $expectedArray = array('id' => 1, 'name' => 'Matt'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('groups/1/members', array('user_id' => 2, 'access_level' => 3)) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->addMember(1, 2, 3)); - } - - /** - * @test - */ - public function shouldSaveMember() - { - $expectedArray = array('id' => 1, 'name' => 'Matt'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('put') - ->with('groups/1/members/2', array('access_level' => 4)) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->saveMember(1, 2, 4)); - } - - /** - * @test - */ - public function shouldRemoveMember() - { - $expectedBool = true; - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('delete') - ->with('groups/1/members/2') - ->will($this->returnValue($expectedBool)) - ; - - $this->assertEquals($expectedBool, $api->removeMember(1, 2)); - } - - /** - * @test - */ - public function shouldRemoveGroup() - { - $expectedBool = true; - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('delete') - ->with('groups/1') - ->will($this->returnValue($expectedBool)) - ; - - $this->assertEquals($expectedBool, $api->remove(1)); - } - - protected function getApiClass() - { - return 'Gitlab\Api\Groups'; - } -} diff --git a/test/Gitlab/Tests/Api/IssueBoardsTest.php b/test/Gitlab/Tests/Api/IssueBoardsTest.php deleted file mode 100644 index 40c356d0b..000000000 --- a/test/Gitlab/Tests/Api/IssueBoardsTest.php +++ /dev/null @@ -1,192 +0,0 @@ - 1, 'title' => 'A board'), - array('id' => 2, 'title' => 'Another board'), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('boards', array()) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->all()); - } - // - // /** - // * @test - // */ - // public function shouldGetProjectIssuesWithPagination() - // { - // $expectedArray = array( - // array('id' => 1, 'title' => 'An issue'), - // array('id' => 2, 'title' => 'Another issue'), - // ); - // - // $api = $this->getApiMock(); - // $api->expects($this->once()) - // ->method('get') - // ->with('projects/1/issues', array('page' => 2, 'per_page' => 5)) - // ->will($this->returnValue($expectedArray)) - // ; - // - // $this->assertEquals($expectedArray, $api->all(1, 2, 5)); - // } - // - // /** - // * @test - // */ - // public function shouldGetProjectIssuesWithParams() - // { - // $expectedArray = array( - // array('id' => 1, 'title' => 'An issue'), - // array('id' => 2, 'title' => 'Another issue'), - // ); - // - // $api = $this->getApiMock(); - // $api->expects($this->once()) - // ->method('get') - // ->with('projects/1/issues', array('page' => 2, 'per_page' => 5, 'order_by' => 'created_at', 'sort' => 'desc', 'labels' => 'foo,bar', 'state' => 'open')) - // ->will($this->returnValue($expectedArray)) - // ; - // - // $this->assertEquals($expectedArray, $api->all(1, 2, 5, array('order_by' => 'created_at', 'sort' => 'desc', 'labels' => 'foo,bar', 'state' => 'open'))); - // } - // - // /** - // * @test - // */ - // public function shouldShowIssue() - // { - // $expectedArray = array('id' => 2, 'title' => 'Another issue'); - // - // $api = $this->getApiMock(); - // $api->expects($this->once()) - // ->method('get') - // ->with('projects/1/issues?iid=2') - // ->will($this->returnValue($expectedArray)) - // ; - // - // $this->assertEquals($expectedArray, $api->show(1, 2)); - // } - // - // /** - // * @test - // */ - // public function shouldCreateIssue() - // { - // $expectedArray = array('id' => 3, 'title' => 'A new issue'); - // - // $api = $this->getApiMock(); - // $api->expects($this->once()) - // ->method('post') - // ->with('projects/1/issues', array('title' => 'A new issue', 'labels' => 'foo,bar')) - // ->will($this->returnValue($expectedArray)) - // ; - // - // $this->assertEquals($expectedArray, $api->create(1, array('title' => 'A new issue', 'labels' => 'foo,bar'))); - // } - // - // /** - // * @test - // */ - // public function shouldUpdateIssue() - // { - // $expectedArray = array('id' => 2, 'title' => 'A renamed issue'); - // - // $api = $this->getApiMock(); - // $api->expects($this->once()) - // ->method('put') - // ->with('projects/1/issues/2', array('title' => 'A renamed issue', 'labels' => 'foo')) - // ->will($this->returnValue($expectedArray)) - // ; - // - // $this->assertEquals($expectedArray, $api->update(1, 2, array('title' => 'A renamed issue', 'labels' => 'foo'))); - // } - // - // /** - // * @test - // */ - // public function shouldGetIssueComments() - // { - // $expectedArray = array( - // array('id' => 1, 'body' => 'A comment'), - // array('id' => 2, 'body' => 'Another comment') - // ); - // - // $api = $this->getApiMock(); - // $api->expects($this->once()) - // ->method('get') - // ->with('projects/1/issues/2/notes') - // ->will($this->returnValue($expectedArray)) - // ; - // - // $this->assertEquals($expectedArray, $api->showComments(1, 2)); - // } - // - // /** - // * @test - // */ - // public function shouldGetIssueComment() - // { - // $expectedArray = array('id' => 3, 'body' => 'A new comment'); - // - // $api = $this->getApiMock(); - // $api->expects($this->once()) - // ->method('get') - // ->with('projects/1/issues/2/notes/3') - // ->will($this->returnValue($expectedArray)) - // ; - // - // $this->assertEquals($expectedArray, $api->showComment(1, 2, 3)); - // } - // - // /** - // * @test - // */ - // public function shouldCreateComment() - // { - // $expectedArray = array('id' => 3, 'body' => 'A new comment'); - // - // $api = $this->getApiMock(); - // $api->expects($this->exactly(2)) - // ->method('post') - // ->with('projects/1/issues/2/notes', array('body' => 'A new comment')) - // ->will($this->returnValue($expectedArray)) - // ; - // - // $this->assertEquals($expectedArray, $api->addComment(1, 2, array('body' => 'A new comment'))); - // $this->assertEquals($expectedArray, $api->addComment(1, 2, 'A new comment')); - // } - // - // /** - // * @test - // */ - // public function shouldUpdateComment() - // { - // $expectedArray = array('id' => 3, 'body' => 'An edited comment'); - // - // $api = $this->getApiMock(); - // $api->expects($this->once()) - // ->method('put') - // ->with('projects/1/issues/2/notes/3', array('body' => 'An edited comment')) - // ->will($this->returnValue($expectedArray)) - // ; - // - // $this->assertEquals($expectedArray, $api->updateComment(1, 2, 3, 'An edited comment')); - // } - - protected function getApiClass() - { - return 'Gitlab\Api\IssueBoards'; - } -} diff --git a/test/Gitlab/Tests/Api/IssuesTest.php b/test/Gitlab/Tests/Api/IssuesTest.php deleted file mode 100644 index 666d84ecf..000000000 --- a/test/Gitlab/Tests/Api/IssuesTest.php +++ /dev/null @@ -1,317 +0,0 @@ - 1, 'title' => 'An issue'), - array('id' => 2, 'title' => 'Another issue'), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('issues', array()) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->all()); - } - - /** - * @test - */ - public function shouldGetProjectIssuesWithPagination() - { - $expectedArray = array( - array('id' => 1, 'title' => 'An issue'), - array('id' => 2, 'title' => 'Another issue'), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/issues', array('page' => 2, 'per_page' => 5)) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->all(1, ['page' => 2, 'per_page' => 5])); - } - - /** - * @test - */ - public function shouldGetProjectIssuesWithParams() - { - $expectedArray = array( - array('id' => 1, 'title' => 'An issue'), - array('id' => 2, 'title' => 'Another issue'), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/issues', array('order_by' => 'created_at', 'sort' => 'desc', 'labels' => 'foo,bar', 'state' => 'opened')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->all(1, array('order_by' => 'created_at', 'sort' => 'desc', 'labels' => 'foo,bar', 'state' => 'opened'))); - } - - /** - * @test - */ - public function shouldShowIssue() - { - $expectedArray = array('id' => 2, 'title' => 'Another issue'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/issues/2') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->show(1, 2)); - } - - /** - * @test - */ - public function shouldCreateIssue() - { - $expectedArray = array('id' => 3, 'title' => 'A new issue'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/issues', array('title' => 'A new issue', 'labels' => 'foo,bar')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->create(1, array('title' => 'A new issue', 'labels' => 'foo,bar'))); - } - - /** - * @test - */ - public function shouldUpdateIssue() - { - $expectedArray = array('id' => 2, 'title' => 'A renamed issue'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('put') - ->with('projects/1/issues/2', array('title' => 'A renamed issue', 'labels' => 'foo')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->update(1, 2, array('title' => 'A renamed issue', 'labels' => 'foo'))); - } - - /** - * @test - */ - public function shouldGetIssueComments() - { - $expectedArray = array( - array('id' => 1, 'body' => 'A comment'), - array('id' => 2, 'body' => 'Another comment') - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/issues/2/notes') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->showComments(1, 2)); - } - - /** - * @test - */ - public function shouldGetIssueComment() - { - $expectedArray = array('id' => 3, 'body' => 'A new comment'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/issues/2/notes/3') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->showComment(1, 2, 3)); - } - - /** - * @test - */ - public function shouldCreateComment() - { - $expectedArray = array('id' => 3, 'body' => 'A new comment'); - - $api = $this->getApiMock(); - $api->expects($this->exactly(2)) - ->method('post') - ->with('projects/1/issues/2/notes', array('body' => 'A new comment')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->addComment(1, 2, array('body' => 'A new comment'))); - $this->assertEquals($expectedArray, $api->addComment(1, 2, 'A new comment')); - } - - /** - * @test - */ - public function shouldUpdateComment() - { - $expectedArray = array('id' => 3, 'body' => 'An edited comment'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('put') - ->with('projects/1/issues/2/notes/3', array('body' => 'An edited comment')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->updateComment(1, 2, 3, 'An edited comment')); - } - - /** - * @test - */ - public function shouldSetTimeEstimate() - { - $expectedArray = array('time_estimate' => 14400, 'total_time_spent' => 0, 'human_time_estimate' => '4h', 'human_total_time_spent' => null); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/issues/2/time_estimate', array('duration' => '4h')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->setTimeEstimate(1, 2, '4h')); - } - - /** - * @test - */ - public function shouldResetTimeEstimate() - { - $expectedArray = array('time_estimate' => 0, 'total_time_spent' => 0, 'human_time_estimate' => null, 'human_total_time_spent' => null); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/issues/2/reset_time_estimate') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->resetTimeEstimate(1, 2)); - } - - /** - * @test - */ - public function shouldAddSpentTime() - { - $expectedArray = array('time_estimate' => 0, 'total_time_spent' => 14400, 'human_time_estimate' => null, 'human_total_time_spent' => '4h'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/issues/2/add_spent_time', array('duration' => '4h')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->addSpentTime(1, 2, '4h')); - } - - /** - * @test - */ - public function shouldResetSpentTime() - { - $expectedArray = array('time_estimate' => 0, 'total_time_spent' => 0, 'human_time_estimate' => null, 'human_total_time_spent' => null); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/issues/2/reset_spent_time') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->resetSpentTime(1, 2)); - } - - /** - * @test - */ - public function shouldGetIssueTimeStats() - { - $expectedArray = array('time_estimate' => 14400, 'total_time_spent' => 5400, 'human_time_estimate' => '4h', 'human_total_time_spent' => '1h 30m'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/issues/2/time_stats') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->getTimeStats(1, 2)); - } - - /** - * @test - */ - public function shouldGetIssueAwardEmoji() - { - $expectedArray = array( - array('id' => 1, 'name' => 'sparkles'), - array('id' => 2, 'name' => 'heart_eyes'), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/issues/2/award_emoji') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->awardEmoji(1, 2)); - } - - /** - * @test - */ - public function shouldGetIssueClosedByMergeRequests() - { - $expectedArray = array( - array('id' => 1, 'iid' => '1111', 'title' => 'Just saving the world'), - array('id' => 2, 'iid' => '1112', 'title' => 'Adding new feature to get merge requests that close an issue'), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/issues/2/closed_by') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->closedByMergeRequests(1, 2)); - } - - protected function getApiClass() - { - return 'Gitlab\Api\Issues'; - } -} diff --git a/test/Gitlab/Tests/Api/JobsTest.php b/test/Gitlab/Tests/Api/JobsTest.php deleted file mode 100644 index d8188ea35..000000000 --- a/test/Gitlab/Tests/Api/JobsTest.php +++ /dev/null @@ -1,211 +0,0 @@ - 1, 'name' => 'A job'), - array('id' => 2, 'name' => 'Another job'), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/jobs', array( - 'scope' => ['pending'] - )) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->all(1, ['scope' => Jobs::SCOPE_PENDING])); - } - - /** - * @test - */ - public function shouldGetPipelineJobs() - { - $expectedArray = array( - array('id' => 1, 'name' => 'A job'), - array('id' => 2, 'name' => 'Another job'), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/pipelines/2/jobs', array( - 'scope' => ['pending', 'running'] - )) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->pipelineJobs(1, 2, ['scope' => [Jobs::SCOPE_PENDING, Jobs::SCOPE_RUNNING]])); - } - - /** - * @test - */ - public function shouldGetJob() - { - $expectedArray = array('id' => 3, 'name' => 'A job'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/jobs/3') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->show(1, 3)); - } - - /** - * @test - */ - public function shouldGetArtifacts() - { - $returnedStream = new Response(200, [], 'foobar'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('getAsResponse') - ->with('projects/1/jobs/3/artifacts') - ->will($this->returnValue($returnedStream)) - ; - - $this->assertEquals('foobar', $api->artifacts(1, 3)->getContents()); - } - - /** - * @test - */ - public function shouldGetArtifactsByRefName() - { - $returnedStream = new Response(200, [], 'foobar'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('getAsResponse') - ->with('projects/1/jobs/artifacts/master/download', array( - 'job' => 'job_name' - )) - ->will($this->returnValue($returnedStream)) - ; - - $this->assertEquals('foobar', $api->artifactsByRefName(1, 'master', 'job_name')->getContents()); - } - - /** - * @test - */ - public function shouldGetTrace() - { - $expectedString = "some trace"; - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/jobs/3/trace') - ->will($this->returnValue($expectedString)) - ; - - $this->assertEquals($expectedString, $api->trace(1, 3)); - } - - /** - * @test - */ - public function shouldCancel() - { - $expectedArray = array('id' => 3, 'name' => 'A job'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/jobs/3/cancel') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->cancel(1, 3)); - } - - /** - * @test - */ - public function shouldRetry() - { - $expectedArray = array('id' => 3, 'name' => 'A job'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/jobs/3/retry') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->retry(1, 3)); - } - - /** - * @test - */ - public function shouldErase() - { - $expectedArray = array('id' => 3, 'name' => 'A job'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/jobs/3/erase') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->erase(1, 3)); - } - - /** - * @test - */ - public function shouldKeepArtifacts() - { - $expectedArray = array('id' => 3, 'name' => 'A job'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/jobs/3/artifacts/keep') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->keepArtifacts(1, 3)); - } - - /** - * @test - */ - public function shouldPlay() - { - $expectedArray = array('id' => 3, 'name' => 'A job'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/jobs/3/play') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->play(1, 3)); - } - - protected function getApiClass() - { - return 'Gitlab\Api\Jobs'; - } -} diff --git a/test/Gitlab/Tests/Api/KeysTest.php b/test/Gitlab/Tests/Api/KeysTest.php deleted file mode 100644 index 355e77bdf..000000000 --- a/test/Gitlab/Tests/Api/KeysTest.php +++ /dev/null @@ -1,24 +0,0 @@ - 1, 'title' => 'A key', 'key' => 'ssh-rsa key', 'created_at' => '2016-01-01T01:00:00.000Z'); - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('keys/1') - ->will($this->returnValue($expectedArray)); - - $this->assertEquals($expectedArray, $api->show(1)); - } - - protected function getApiClass() - { - return 'Gitlab\Api\Keys'; - } -} diff --git a/test/Gitlab/Tests/Api/MergeRequestsTest.php b/test/Gitlab/Tests/Api/MergeRequestsTest.php deleted file mode 100644 index 30940b200..000000000 --- a/test/Gitlab/Tests/Api/MergeRequestsTest.php +++ /dev/null @@ -1,329 +0,0 @@ -getMultipleMergeRequestsData(); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/merge_requests', array()) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->all(1)); - } - - /** - * @test - */ - public function shouldGetAllWithParams() - { - $expectedArray = $this->getMultipleMergeRequestsData(); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/merge_requests', ['page' => 2, 'per_page' => 5, 'order_by' => 'updated_at', 'sort' => 'desc']) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->all(1, ['page' => 2, 'per_page' => 5, 'order_by' => 'updated_at', 'sort' => 'desc'])); - } - - /** - * @test - */ - public function shouldGetAllWithDateTimeParams() - { - $expectedArray = $this->getMultipleMergeRequestsData(); - - $createdAfter = new \DateTime('2018-01-01 00:00:00'); - $createdBefore = new \DateTime('2018-01-31 00:00:00'); - - $expectedWithArray = [ - 'created_after' => $createdAfter->format(DATE_ATOM), - 'created_before' => $createdBefore->format(DATE_ATOM), - ]; - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/merge_requests', $expectedWithArray) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals( - $expectedArray, - $api->all(1, ['created_after' => $createdAfter, 'created_before' => $createdBefore]) - ); - } - - /** - * @test - */ - public function shouldShowMergeRequest() - { - $expectedArray = array('id' => 2, 'name' => 'A merge request'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/merge_requests/2') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->show(1, 2)); - } - - /** - * @test - */ - public function shouldCreateMergeRequestWithoutOptionalParams() - { - $expectedArray = array('id' => 3, 'title' => 'Merge Request'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/merge_requests', array( - 'title' => 'Merge Request', - 'target_branch' => 'master', - 'source_branch' => 'develop', - 'description' => null, - 'assignee_id' => null, - 'target_project_id' => null - )) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->create(1, 'develop', 'master', 'Merge Request')); - } - - /** - * @test - */ - public function shouldCreateMergeRequestWithOptionalParams() - { - $expectedArray = array('id' => 3, 'title' => 'Merge Request'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/merge_requests', array( - 'title' => 'Merge Request', - 'target_branch' => 'master', - 'source_branch' => 'develop', - 'description' => 'Some changes', - 'assignee_id' => 6, - 'target_project_id' => 20 - )) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->create(1, 'develop', 'master', 'Merge Request', 6, 20, 'Some changes')); - } - - /** - * @test - */ - public function shouldUpdateMergeRequest() - { - $expectedArray = array('id' => 2, 'title' => 'Updated title'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('put') - ->with('projects/1/merge_requests/2', array('title' => 'Updated title', 'description' => 'No so many changes now', 'state_event' => 'close')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->update(1, 2, array( - 'title' => 'Updated title', - 'description' => 'No so many changes now', - 'state_event' => 'close' - ))); - } - - /** - * @test - */ - public function shouldMergeMergeRequest() - { - $expectedArray = array('id' => 2, 'title' => 'Updated title'); - - $api = $this->getApiMock(); - $api->expects($this->exactly(2)) - ->method('put') - ->with('projects/1/merge_requests/2/merge', array('merge_commit_message' => 'Accepted')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->merge(1, 2, 'Accepted')); - $this->assertEquals($expectedArray, $api->merge(1, 2, array('merge_commit_message' => 'Accepted'))); - } - - /** - * @test - */ - public function shouldGetMergeRequestNotes() - { - $expectedArray = array( - array('id' => 1, 'body' => 'A comment'), - array('id' => 2, 'body' => 'Another comment') - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/merge_requests/2/notes') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->showNotes(1, 2)); - } - - /** - * @test - */ - public function shouldGetMergeRequestChanges() - { - $expectedArray = array('id' => 1, 'title' => 'A merge request'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/merge_requests/2/changes') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->changes(1, 2)); - } - - - /** - * @test - */ - public function shouldGetIssuesClosedByMergeRequest() - { - $expectedArray = array('id' => 1, 'title' => 'A merge request'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/merge_requests/2/closes_issues') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->closesIssues(1, 2)); - } - - /** - * @test - */ - public function shouldGetMergeRequestByIid() - { - $expectedArray = array('id' => 1, 'title' => 'A merge request'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/merge_requests', array('iids' => [2])) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->all(1, ['iids' => [2]])); - } - - /** - * @test - */ - public function shouldApproveMergeRequest() - { - $expectedArray = array('id' => 1, 'title' => 'Approvals API'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/merge_requests/2/approve') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->approve(1, 2)); - } - - /** - * @test - */ - public function shouldUnApproveMergeRequest() - { - $expectedArray = array('id' => 1, 'title' => 'Approvals API'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/merge_requests/2/unapprove') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->unapprove(1, 2)); - } - - /** - * @test - */ - public function shouldGetMergeRequestApprovals() - { - $expectedArray = array('id' => 1, 'title' => 'Approvals API'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/merge_requests', array('iids' => [2])) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->all(1, ['iids' => [2]])); - } - - /** - * @test - */ - public function shouldGetMergeRequestAwardEmoji() - { - $expectedArray = array( - array('id' => 1, 'name' => 'sparkles'), - array('id' => 2, 'name' => 'heart_eyes'), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/merge_requests/2/award_emoji') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->awardEmoji(1, 2)); - } - - protected function getMultipleMergeRequestsData() - { - return array( - array('id' => 1, 'title' => 'A merge request'), - array('id' => 2, 'title' => 'Another merge request') - ); - } - - protected function getApiClass() - { - return 'Gitlab\Api\MergeRequests'; - } -} diff --git a/test/Gitlab/Tests/Api/MilestonesTest.php b/test/Gitlab/Tests/Api/MilestonesTest.php deleted file mode 100644 index 337befa42..000000000 --- a/test/Gitlab/Tests/Api/MilestonesTest.php +++ /dev/null @@ -1,100 +0,0 @@ - 1, 'title' => 'A milestone'), - array('id' => 2, 'title' => 'Another milestone'), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/milestones') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->all(1)); - } - - /** - * @test - */ - public function shouldShowMilestone() - { - $expectedArray = array('id' => 1, 'name' => 'A milestone'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/milestones/2') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->show(1, 2)); - } - - /** - * @test - */ - public function shouldCreateMilestone() - { - $expectedArray = array('id' => 3, 'title' => 'A new milestone'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/milestones', array('description' => 'Some text', 'title' => 'A new milestone')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->create(1, array('description' => 'Some text', 'title' => 'A new milestone'))); - } - - /** - * @test - */ - public function shouldUpdateMilestone() - { - $expectedArray = array('id' => 3, 'title' => 'Updated milestone'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('put') - ->with('projects/1/milestones/3', array('title' => 'Updated milestone', 'due_date' => '2015-04-01', 'state_event' => 'close')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->update(1, 3, array('title' => 'Updated milestone', 'due_date' => '2015-04-01', 'state_event' => 'close'))); - } - - /** - * @test - */ - public function shouldGetMilestonesIssues() - { - $expectedArray = array( - array('id' => 1, 'title' => 'An issue'), - array('id' => 2, 'title' => 'Another issue'), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/milestones/3/issues') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->issues(1, 3)); - } - - protected function getApiClass() - { - return 'Gitlab\Api\Milestones'; - } -} diff --git a/test/Gitlab/Tests/Api/ProjectNamespacesTest.php b/test/Gitlab/Tests/Api/ProjectNamespacesTest.php deleted file mode 100644 index 049027924..000000000 --- a/test/Gitlab/Tests/Api/ProjectNamespacesTest.php +++ /dev/null @@ -1,31 +0,0 @@ - 1, 'name' => 'bespokes'), - array('id' => 2, 'name' => 'internal') - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('namespaces', array()) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->all()); - } - - protected function getApiClass() - { - return 'Gitlab\Api\ProjectNamespaces'; - } -} diff --git a/test/Gitlab/Tests/Api/ProjectsTest.php b/test/Gitlab/Tests/Api/ProjectsTest.php deleted file mode 100644 index 0d9f07397..000000000 --- a/test/Gitlab/Tests/Api/ProjectsTest.php +++ /dev/null @@ -1,1181 +0,0 @@ -getMultipleProjectsData(); - - $api = $this->getMultipleProjectsRequestMock('projects', $expectedArray); - - $this->assertEquals($expectedArray, $api->all()); - } - - /** - * @test - */ - public function shouldGetAllProjectsSortedByName() - { - $expectedArray = $this->getMultipleProjectsData(); - - $api = $this->getMultipleProjectsRequestMock('projects', $expectedArray, ['page' => 1, 'per_page' => 5, 'order_by' => 'name', 'sort' => 'asc']); - - $this->assertEquals($expectedArray, $api->all(['page' => 1, 'per_page' => 5, 'order_by' => 'name', 'sort' => 'asc'])); - } - - /** - * @test - */ - public function shouldNotNeedPaginationWhenGettingProjects() - { - $expectedArray = $this->getMultipleProjectsData(); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->all()); - } - - /** - * @test - */ - public function shouldGetAccessibleProjects() - { - $expectedArray = $this->getMultipleProjectsData(); - - $api = $this->getMultipleProjectsRequestMock('projects', $expectedArray); - - $this->assertEquals($expectedArray, $api->all()); - } - - /** - * @test - */ - public function shouldGetOwnedProjects() - { - $expectedArray = $this->getMultipleProjectsData(); - - $api = $this->getMultipleProjectsRequestMock('projects', $expectedArray, ['owned' => 'true']); - - $this->assertEquals($expectedArray, $api->all(['owned' => true])); - } - - /** - * @test - */ - public function shouldGetNotArchivedProjects() - { - $expectedArray = $this->getMultipleProjectsData(); - - $api = $this->getMultipleProjectsRequestMock('projects', $expectedArray, ['archived' => 'false']); - - $this->assertEquals($expectedArray, $api->all(['archived' => false])); - } - - /** - * @test - */ - public function shouldSearchProjects() - { - $expectedArray = $this->getMultipleProjectsData(); - - $api = $this->getMultipleProjectsRequestMock('projects', $expectedArray, ['search' => 'a project']); - $this->assertEquals($expectedArray, $api->all(['search' => 'a project'])); - } - - /** - * @test - */ - public function shouldShowProject() - { - $expectedArray = array('id' => 1, 'name' => 'Project Name'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->show(1)); - } - - /** - * @test - */ - public function shouldCreateProject() - { - $expectedArray = array('id' => 1, 'name' => 'Project Name'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects', array('name' => 'Project Name', 'issues_enabled' => true)) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->create('Project Name', array( - 'issues_enabled' => true - ))); - } - - /** - * @test - */ - public function shouldUpdateProject() - { - $expectedArray = array('id' => 1, 'name' => 'Updated Name'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('put') - ->with('projects/1', array('name' => 'Updated Name', 'issues_enabled' => true)) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->update(1, array( - 'name' => 'Updated Name', - 'issues_enabled' => true - ))); - } - - /** - * @test - */ - public function shouldArchiveProject() - { - $expectedArray = array('id' => 1, 'archived' => true); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/archive') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->archive(1)); - } - - /** - * @test - */ - public function shouldUnarchiveProject() - { - $expectedArray = array('id' => 1, 'archived' => false); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/unarchive') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->unarchive(1)); - } - - /** - * @test - */ - public function shouldCreateProjectForUser() - { - $expectedArray = array('id' => 1, 'name' => 'Project Name'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/user/1', array('name' => 'Project Name', 'issues_enabled' => true)) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->createForUser(1, 'Project Name', array( - 'issues_enabled' => true - ))); - } - - /** - * @test - */ - public function shouldRemoveProject() - { - $expectedBool = true; - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('delete') - ->with('projects/1') - ->will($this->returnValue($expectedBool)) - ; - - $this->assertEquals($expectedBool, $api->remove(1)); - } - - /** - * @test - */ - public function shouldGetPipelines() - { - $expectedArray = array( - array('id' => 1, 'status' => 'success','ref' => 'new-pipeline'), - array('id' => 2, 'status' => 'failed', 'ref' => 'new-pipeline'), - array('id' => 3, 'status' => 'pending', 'ref'=> 'test-pipeline') - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/pipelines') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->pipelines(1)); - } - - /** - * @test - */ - public function shouldGetPipelinesWithBooleanParam() - { - $expectedArray = array( - array('id' => 1, 'status' => 'success','ref' => 'new-pipeline'), - array('id' => 2, 'status' => 'failed', 'ref' => 'new-pipeline'), - array('id' => 3, 'status' => 'pending', 'ref'=> 'test-pipeline') - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/pipelines', ['yaml_errors' => 'false']) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->pipelines(1, ['yaml_errors' => false])); - } - - /** - * @test - */ - public function shouldGetPipeline() - { - $expectedArray = array( - array('id' => 1, 'status' => 'success','ref' => 'new-pipeline'), - array('id' => 2, 'status' => 'failed', 'ref' => 'new-pipeline'), - array('id' => 3, 'status' => 'pending', 'ref'=> 'test-pipeline') - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/pipelines/3') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->pipeline(1, 3)); - } - - /** - * @test - */ - public function shouldCreatePipeline() - { - $expectedArray = array( - array('id' => 4, 'status' => 'created', 'ref'=> 'test-pipeline') - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/pipeline', array('ref' => 'test-pipeline')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->createPipeline(1, 'test-pipeline')); - } - - /** - * @test - */ - public function shouldRetryPipeline() - { - $expectedArray = array( - array('id' => 5, 'status' => 'pending', 'ref'=> 'test-pipeline') - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/pipelines/4/retry') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->retryPipeline(1, 4)); - } - - /** - * @test - */ - public function shouldCancelPipeline() - { - $expectedArray = array( - array('id' => 6, 'status' => 'cancelled', 'ref'=> 'test-pipeline') - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/pipelines/6/cancel') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->cancelPipeline(1, 6)); - } - - /** - * @test - */ - public function shouldGetMembers() - { - $expectedArray = array( - array('id' => 1, 'name' => 'Matt'), - array('id' => 2, 'name' => 'Bob') - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/members') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->members(1)); - } - - /** - * @test - */ - public function shouldGetMembersWithQuery() - { - $expectedArray = array( - array('id' => 1, 'name' => 'Matt') - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/members', array('query' => 'at')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->members(1, 'at')); - } - - /** - * @test - */ - public function shouldGetMember() - { - $expectedArray = array('id' => 2, 'name' => 'Matt'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/members/2') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->member(1, 2)); - } - - /** - * @test - */ - public function shouldAddMember() - { - $expectedArray = array('id' => 1, 'name' => 'Matt'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/members', array('user_id' => 2, 'access_level' => 3)) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->addMember(1, 2, 3)); - } - - /** - * @test - */ - public function shouldSaveMember() - { - $expectedArray = array('id' => 1, 'name' => 'Matt'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('put') - ->with('projects/1/members/2', array('access_level' => 4)) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->saveMember(1, 2, 4)); - } - - /** - * @test - */ - public function shouldRemoveMember() - { - $expectedBool = true; - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('delete') - ->with('projects/1/members/2') - ->will($this->returnValue($expectedBool)) - ; - - $this->assertEquals($expectedBool, $api->removeMember(1, 2)); - } - - /** - * @test - */ - public function shouldGetHooks() - { - $expectedArray = array( - array('id' => 1, 'name' => 'Test hook'), - array('id' => 2, 'name' => 'Another hook'), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/hooks') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->hooks(1)); - } - - /** - * @test - */ - public function shouldGetHook() - { - $expectedArray = array('id' => 2, 'name' => 'Another hook'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/hooks/2') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->hook(1, 2)); - } - - /** - * @test - */ - public function shouldAddHook() - { - $expectedArray = array('id' => 3, 'name' => 'A new hook', 'url' => 'http://www.example.com'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/hooks', array('url' => 'http://www.example.com', 'push_events' => true, 'issues_events' => true, 'merge_requests_events' => true)) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->addHook(1, 'http://www.example.com', array('push_events' => true, 'issues_events' => true, 'merge_requests_events' => true))); - } - - /** - * @test - */ - public function shouldAddHookWithOnlyUrl() - { - $expectedArray = array('id' => 3, 'name' => 'A new hook', 'url' => 'http://www.example.com'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/hooks', array('url' => 'http://www.example.com', 'push_events' => true)) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->addHook(1, 'http://www.example.com')); - } - - /** - * @test - */ - public function shouldAddHookWithoutPushEvents() - { - $expectedArray = array('id' => 3, 'name' => 'A new hook', 'url' => 'http://www.example.com'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/hooks', array('url' => 'http://www.example.com', 'push_events' => false)) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->addHook(1, 'http://www.example.com', array('push_events' => false))); - } - - /** - * @test - */ - public function shouldUpdateHook() - { - $expectedArray = array('id' => 3, 'name' => 'A new hook', 'url' => 'http://www.example.com'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('put') - ->with('projects/1/hooks/3', array('url' => 'http://www.example-test.com', 'push_events' => false)) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->updateHook(1, 3, array('url' => 'http://www.example-test.com', 'push_events' => false))); - } - - /** - * @test - */ - public function shouldRemoveHook() - { - $expectedBool = true; - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('delete') - ->with('projects/1/hooks/2') - ->will($this->returnValue($expectedBool)) - ; - - $this->assertEquals($expectedBool, $api->removeHook(1, 2)); - } - - /** - * @test - */ - public function shouldGetDeployKeys() - { - $expectedArray = array( - array('id' => 1, 'title' => 'test-key'), - array('id' => 2, 'title' => 'another-key') - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/deploy_keys') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->deployKeys(1)); - } - - /** - * @test - */ - public function shouldGetDeployKey() - { - $expectedArray = array('id' => 2, 'title' => 'another-key'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/deploy_keys/2') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->deployKey(1, 2)); - } - - /** - * @test - */ - public function shouldAddKey() - { - $expectedArray = array('id' => 3, 'title' => 'new-key', 'can_push' => false); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/deploy_keys', array('title' => 'new-key', 'key' => '...', 'can_push' => false)) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->addDeployKey(1, 'new-key', '...')); - } - - /** - * @test - */ - public function shouldAddKeyWithPushOption() - { - $expectedArray = array('id' => 3, 'title' => 'new-key', 'can_push' => true); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/deploy_keys', array('title' => 'new-key', 'key' => '...', 'can_push' => true)) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->addDeployKey(1, 'new-key', '...', true)); - } - - /** - * @test - */ - public function shouldDeleteDeployKey() - { - $expectedBool = true; - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('delete') - ->with('projects/1/deploy_keys/3') - ->will($this->returnValue($expectedBool)) - ; - - $this->assertEquals($expectedBool, $api->deleteDeployKey(1, 3)); - } - - /** - * @test - */ - public function shoudEnableDeployKey() - { - $expectedBool = true; - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/deploy_keys/3/enable') - ->will($this->returnValue($expectedBool)) - ; - - $this->assertEquals($expectedBool, $api->enableDeployKey(1, 3)); - } - - /** - * @test - */ - public function shouldGetEvents() - { - $expectedArray = array( - array('id' => 1, 'title' => 'An event'), - array('id' => 2, 'title' => 'Another event') - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/events', array()) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->events(1)); - } - - /** - * @test - */ - public function shouldGetEventsWithDateTimeParams() - { - $expectedArray = [ - ['id' => 1, 'title' => 'An event'], - ['id' => 2, 'title' => 'Another event'] - ]; - - $after = new \DateTime('2018-01-01 00:00:00'); - $before = new \DateTime('2018-01-31 00:00:00'); - - $expectedWithArray = [ - 'after' => $after->format('Y-m-d'), - 'before' => $before->format('Y-m-d'), - ]; - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/events', $expectedWithArray) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->events(1, ['after' => $after, 'before' => $before])); - } - - /** - * @test - */ - public function shouldGetEventsWithPagination() - { - $expectedArray = array( - array('id' => 1, 'title' => 'An event'), - array('id' => 2, 'title' => 'Another event') - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/events', array( - 'page' => 2, - 'per_page' => 15 - )) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->events(1, ['page' => 2, 'per_page' => 15])); - } - - /** - * @test - */ - public function shouldGetLabels() - { - $expectedArray = array( - array('name' => 'bug', 'color' => '#000000'), - array('name' => 'feature', 'color' => '#ff0000') - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/labels') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->labels(1)); - } - - /** - * @test - */ - public function shouldAddLabel() - { - $expectedArray = array('name' => 'bug', 'color' => '#000000'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/labels', array('name' => 'wont-fix', 'color' => '#ffffff')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->addLabel(1, array('name' => 'wont-fix', 'color' => '#ffffff'))); - } - - /** - * @test - */ - public function shouldUpdateLabel() - { - $expectedArray = array('name' => 'bug', 'color' => '#00ffff'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('put') - ->with('projects/1/labels', array('name' => 'bug', 'new_name' => 'big-bug', 'color' => '#00ffff')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->updateLabel(1, array('name' => 'bug', 'new_name' => 'big-bug', 'color' => '#00ffff'))); - } - - /** - * @test - */ - public function shouldRemoveLabel() - { - $expectedBool = true; - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('delete') - ->with('projects/1/labels', array('name' => 'bug')) - ->will($this->returnValue($expectedBool)) - ; - - $this->assertEquals($expectedBool, $api->removeLabel(1, 'bug')); - } - - /** - * @test - */ - public function shouldCreateForkRelation() - { - $expectedArray = array('project_id' => 1, 'forked_id' => 2); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/fork/2') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->createForkRelation(1, 2)); - } - - /** - * @test - */ - public function shouldRemoveForkRelation() - { - $expectedBool = true; - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('delete') - ->with('projects/2/fork') - ->will($this->returnValue($expectedBool)) - ; - - $this->assertEquals($expectedBool, $api->removeForkRelation(2)); - } - - /** - * @test - */ - public function shouldSetService() - { - $expectedBool = true; - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('put') - ->with('projects/1/services/hipchat', array('param' => 'value')) - ->will($this->returnValue($expectedBool)) - ; - - $this->assertEquals($expectedBool, $api->setService(1, 'hipchat', array('param' => 'value'))); - } - - /** - * @test - */ - public function shouldRemoveService() - { - $expectedBool = true; - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('delete') - ->with('projects/1/services/hipchat') - ->will($this->returnValue($expectedBool)) - ; - - $this->assertEquals($expectedBool, $api->removeService(1, 'hipchat')); - } - - /** - * @test - */ - public function shouldGetVariables() - { - $expectedArray = array( - array('key' => 'ftp_username', 'value' => 'ftp'), - array('key' => 'ftp_password', 'value' => 'somepassword') - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/variables') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->variables(1)); - } - - /** - * @test - */ - public function shouldGetVariable() - { - $expectedArray = array('key' => 'ftp_username', 'value' => 'ftp'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/variables/ftp_username') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->variable(1, 'ftp_username')); - } - - /** - * @test - */ - public function shouldAddVariable() - { - $expectedKey = 'ftp_port'; - $expectedValue = '21'; - - $expectedArray = array( - 'key' => $expectedKey, - 'value' => $expectedValue, - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/variables', $expectedArray) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->addVariable(1, $expectedKey, $expectedValue)); - } - - /** - * @test - */ - public function shouldAddVariableWithProtected() - { - $expectedArray = array( - 'key' => 'DEPLOY_SERVER', - 'value' => 'stage.example.com', - 'protected' => true, - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/variables', $expectedArray) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->addVariable(1, 'DEPLOY_SERVER', 'stage.example.com', true)); - } - - /** - * @test - */ - public function shouldAddVariableWithEnvironment() - { - $expectedArray = array( - 'key' => 'DEPLOY_SERVER', - 'value' => 'stage.example.com', - 'environment_scope' => 'staging', - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/variables', $expectedArray) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->addVariable(1, 'DEPLOY_SERVER', 'stage.example.com', null, 'staging')); - } - - /** - * @test - */ - public function shouldAddVariableWithProtectionAndEnvironment() - { - $expectedArray = array( - 'key' => 'DEPLOY_SERVER', - 'value' => 'stage.example.com', - 'protected' => true, - 'environment_scope' => 'staging', - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/variables', $expectedArray) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->addVariable(1, 'DEPLOY_SERVER', 'stage.example.com', true, 'staging')); - } - - /** - * @test - */ - public function shouldUpdateVariable() - { - $expectedKey = 'ftp_port'; - $expectedValue = '22'; - - $expectedArray = array( - 'key' => 'ftp_port', - 'value' => '22', - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('put') - ->with('projects/1/variables/'.$expectedKey, array('value' => $expectedValue)) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->updateVariable(1, $expectedKey, $expectedValue)); - } - - /** - * @test - */ - public function shouldUpdateVariableWithProtected() - { - $expectedArray = array( - 'key' => 'DEPLOY_SERVER', - 'value' => 'stage.example.com', - 'protected' => true, - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('put') - ->with('projects/1/variables/DEPLOY_SERVER', array('value' => 'stage.example.com', 'protected' => true)) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->updateVariable(1, 'DEPLOY_SERVER', 'stage.example.com', true)); - } - - /** - * @test - */ - public function shouldUpdateVariableWithEnvironment() - { - $expectedArray = array( - 'key' => 'DEPLOY_SERVER', - 'value' => 'stage.example.com', - 'environment_scope' => 'staging', - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('put') - ->with('projects/1/variables/DEPLOY_SERVER', array('value' => 'stage.example.com', 'environment_scope' => 'staging')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->updateVariable(1, 'DEPLOY_SERVER', 'stage.example.com', null, 'staging')); - } - - /** - * @test - */ - public function shouldUpdateVariableWithProtectedAndEnvironment() - { - $expectedArray = array( - 'key' => 'DEPLOY_SERVER', - 'value' => 'stage.example.com', - 'protected' => true, - 'environment_scope' => 'staging', - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('put') - ->with('projects/1/variables/DEPLOY_SERVER', array('value' => 'stage.example.com', 'protected' => true, 'environment_scope' => 'staging')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->updateVariable(1, 'DEPLOY_SERVER', 'stage.example.com', true, 'staging')); - } - - /** - * @test - */ - public function shouldRemoveVariable() - { - $expectedBool = true; - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('delete') - ->with('projects/1/variables/ftp_password') - ->will($this->returnValue($expectedBool)) - ; - - $this->assertEquals($expectedBool, $api->removeVariable(1, 'ftp_password')); - } - - protected function getMultipleProjectsRequestMock($path, $expectedArray = array(), $expectedParameters = array()) - { - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with($path, $expectedParameters) - ->will($this->returnValue($expectedArray)) - ; - - return $api; - } - - /** - * @test - */ - public function shouldGetDeployments() - { - $expectedArray = array( - array('id' => 1, 'sha' => '0000001'), - array('id' => 2, 'sha' => '0000002'), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/deployments', array()) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->deployments(1)); - } - - /** - * @test - */ - public function shouldGetDeploymentsWithPagination() - { - $expectedArray = array( - array('id' => 1, 'sha' => '0000001'), - array('id' => 2, 'sha' => '0000002'), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/deployments', array( - 'page' => 2, - 'per_page' => 15 - )) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->deployments(1, ['page' => 2, 'per_page' => 15])); - } - - protected function getMultipleProjectsData() - { - return array( - array('id' => 1, 'name' => 'A project'), - array('id' => 2, 'name' => 'Another project') - ); - } - - protected function getApiClass() - { - return 'Gitlab\Api\Projects'; - } -} diff --git a/test/Gitlab/Tests/Api/RepositoriesTest.php b/test/Gitlab/Tests/Api/RepositoriesTest.php deleted file mode 100644 index 350b3b389..000000000 --- a/test/Gitlab/Tests/Api/RepositoriesTest.php +++ /dev/null @@ -1,502 +0,0 @@ - 'master'), - array('name' => 'develop') - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/repository/branches') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->branches(1)); - } - - /** - * @test - */ - public function shouldGetBranch() - { - $expectedArray = array('name' => 'master'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/repository/branches/master') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->branch(1, 'master')); - } - - /** - * @test - */ - public function shouldCreateBranch() - { - $expectedArray = array('name' => 'feature'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/repository/branches', array('branch' => 'feature', 'ref' => 'master')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->createBranch(1, 'feature', 'master')); - } - - /** - * @test - */ - public function shouldDeleteBranch() - { - $expectedBool = true; - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('delete') - ->with('projects/1/repository/branches/feature%2FTEST-15') - ->will($this->returnValue($expectedBool)) - ; - - $this->assertEquals($expectedBool, $api->deleteBranch(1, 'feature/TEST-15')); - } - - /** - * @test - */ - public function shouldProtectBranch() - { - $expectedArray = array('name' => 'master'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('put') - ->with('projects/1/repository/branches/master/protect', array('developers_can_push' => false, 'developers_can_merge' => false)) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->protectBranch(1, 'master')); - } - - /** - * @test - */ - public function shouldProtectBranchWithPermissions() - { - $expectedArray = array('name' => 'master'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('put') - ->with('projects/1/repository/branches/master/protect', array('developers_can_push' => true, 'developers_can_merge' => true)) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->protectBranch(1, 'master', true, true)); - } - - /** - * @test - */ - public function shouldUnprotectBranch() - { - $expectedArray = array('name' => 'master'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('put') - ->with('projects/1/repository/branches/master/unprotect') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->unprotectBranch(1, 'master')); - } - - /** - * @test - */ - public function shouldGetTags() - { - $expectedArray = array( - array('name' => '1.0'), - array('name' => '1.1') - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/repository/tags') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->tags(1)); - } - - /** - * @test - */ - public function shouldCreateTag() - { - $expectedArray = array('name' => '1.0'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/repository/tags', array( - 'tag_name' => '1.0', - 'ref' => 'abcd1234', - 'message' => '1.0 release' - )) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->createTag(1, '1.0', 'abcd1234', '1.0 release')); - } - - /** - * @test - */ - public function shouldCreateRelease() - { - $project_id = 1; - $tagName = 'sometag'; - $description = '1.0 release'; - - $expectedArray = array( 'name' => $tagName ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/' . $project_id . '/repository/tags/' . $tagName . '/release', array( - 'id' => $project_id, - 'tag_name' => $tagName, - 'description' => $description - )) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->createRelease($project_id, $tagName, $description)); - } - - /** - * @test - */ - public function shouldUpdateRelease() - { - $project_id = 1; - $tagName = 'sometag'; - $description = '1.0 release'; - - $expectedArray = array( 'description' => $tagName ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('put') - ->with('projects/' . $project_id . '/repository/tags/' . $tagName . '/release', array( - 'id' => $project_id, - 'tag_name' => $tagName, - 'description' => $description - )) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->updateRelease($project_id, $tagName, $description)); - } - - /** - * @test - */ - public function shouldGetCommits() - { - $expectedArray = array( - array('id' => 'abcd1234', 'title' => 'A commit'), - array('id' => 'efgh5678', 'title' => 'Another commit') - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/repository/commits', array()) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->commits(1)); - } - - /** - * @test - */ - public function shouldGetCommitsWithParams() - { - $expectedArray = array( - array('id' => 'abcd1234', 'title' => 'A commit'), - array('id' => 'efgh5678', 'title' => 'Another commit') - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/repository/commits', array('page' => 2, 'per_page' => 25, 'ref_name' => 'master')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->commits(1, ['page' => 2, 'per_page' => 25, 'ref_name' => 'master'])); - } - - /** - * @test - */ - public function shouldGetCommitsWithTimeParams() - { - $expectedArray = [ - ['id' => 'abcd1234', 'title' => 'A commit'], - ['id' => 'efgh5678', 'title' => 'Another commit'] - ]; - - $since = new \DateTime('2018-01-01 00:00:00'); - $until = new \DateTime('2018-01-31 00:00:00'); - - $expectedWithArray = [ - 'since' => $since->format(DATE_ATOM), - 'until' => $until->format(DATE_ATOM), - ]; - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/repository/commits', $expectedWithArray) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->commits(1, ['since' => $since, 'until' => $until])); - } - - /** - * @test - */ - public function shouldGetCommit() - { - $expectedArray = array('id' => 'abcd1234', 'title' => 'A commit'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/repository/commits/abcd1234') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->commit(1, 'abcd1234')); - } - - /** - * @test - */ - public function shouldCreateCommit() - { - $expectedArray = array('title' => 'Initial commit.', 'author_name' => 'John Doe', 'author_email' => 'john@example.com'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/repository/commits') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->createCommit(1, [ - 'branch' => 'master', - 'commit_message' => 'Initial commit.', - 'actions' => [ - [ - 'action' => 'create', - 'file_path' => 'README.md', - 'content' => '# My new project', - ], - [ - 'action' => 'create', - 'file_path' => 'LICENSE', - 'content' => 'MIT License...', - ], - ], - 'author_name' => 'John Doe', - 'author_email' => 'john@example.com', - ])); - } - - /** - * @test - */ - public function shouldGetCommitComments() - { - $expectedArray = array( - array('note' => 'A commit message'), - array('note' => 'Another commit message') - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/repository/commits/abcd1234/comments') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->commitComments(1, 'abcd1234')); - } - - /** - * @test - */ - public function shouldCreateCommitComment() - { - $expectedArray = array('id' => 2, 'title' => 'A new comment'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/repository/commits/abcd1234/comments', array('note' => 'A new comment')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->createCommitComment(1, 'abcd1234', 'A new comment')); - } - - /** - * @test - */ - public function shouldCreateCommitCommentWithParams() - { - $expectedArray = array('id' => 2, 'title' => 'A new comment'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/repository/commits/abcd1234/comments', array( - 'note' => 'A new comment', - 'path' => '/some/file.txt', - 'line' => 123, 'line_type' => 'old' - )) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->createCommitComment(1, 'abcd1234', 'A new comment', array( - 'path' => '/some/file.txt', - 'line' => 123, - 'line_type' => 'old' - ))); - } - - /** - * @test - */ - public function shouldCompare() - { - $expectedArray = array('commit' => 'object'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/repository/compare?from=master&to=feature') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->compare(1, 'master', 'feature')); - } - - /** - * @test - */ - public function shouldGetDiff() - { - $expectedArray = array( - array('diff' => '--- ...'), - array('diff' => '+++ ...') - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/repository/commits/abcd1234/diff') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->diff(1, 'abcd1234')); - } - - /** - * @test - */ - public function shouldGetTree() - { - $expectedArray = array( - array('name' => 'file1.txt'), - array('name' => 'file2.csv') - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/repository/tree') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->tree(1)); - } - - /** - * @test - */ - public function shouldGetTreeWithParams() - { - $expectedArray = array( - array('name' => 'dir/file1.txt'), - array('name' => 'dir/file2.csv') - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/repository/tree', array('path' => 'dir/', 'ref_name' => 'master')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->tree(1, array('path' => 'dir/', 'ref_name' => 'master'))); - } - - /** - * @test - */ - public function shouldGetContributors() - { - $expectedArray = array( - array('name' => 'Matt'), - array('name' => 'Bob') - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/repository/contributors') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->contributors(1)); - } - - protected function getApiClass() - { - return 'Gitlab\Api\Repositories'; - } -} diff --git a/test/Gitlab/Tests/Api/SnippetsTest.php b/test/Gitlab/Tests/Api/SnippetsTest.php deleted file mode 100644 index f6782607f..000000000 --- a/test/Gitlab/Tests/Api/SnippetsTest.php +++ /dev/null @@ -1,134 +0,0 @@ - 1, 'title' => 'A snippet'), - array('id' => 2, 'title' => 'Another snippet'), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/snippets') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->all(1)); - } - - /** - * @test - */ - public function shouldShowSnippet() - { - $expectedArray = array('id' => 2, 'title' => 'Another snippet'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/snippets/2') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->show(1, 2)); - } - - /** - * @test - */ - public function shouldCreateSnippet() - { - $expectedArray = array('id' => 3, 'title' => 'A new snippet'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/snippets', array('title' => 'A new snippet', 'code' => 'A file', 'file_name' => 'file.txt')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->create(1, 'A new snippet', 'file.txt', 'A file')); - } - - /** - * @test - */ - public function shouldUpdateSnippet() - { - $expectedArray = array('id' => 3, 'title' => 'Updated snippet'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('put') - ->with('projects/1/snippets/3', array('title' => 'Updated snippet', 'code' => 'New content', 'file_name' => 'new_file.txt')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->update(1, 3, array('file_name' => 'new_file.txt', 'code' => 'New content', 'title' => 'Updated snippet'))); - } - - /** - * @test - */ - public function shouldShowContent() - { - $expectedString = 'New content'; - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/snippets/3/raw') - ->will($this->returnValue($expectedString)) - ; - - $this->assertEquals($expectedString, $api->content(1, 3)); - } - - /** - * @test - */ - public function shouldRemoveSnippet() - { - $expectedBool = true; - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('delete') - ->with('projects/1/snippets/3') - ->will($this->returnValue($expectedBool)) - ; - - $this->assertEquals($expectedBool, $api->remove(1, 3)); - } - - /** - * @test - */ - public function shouldGetSnippetAwardEmoji() - { - $expectedArray = array( - array('id' => 1, 'name' => 'sparkles'), - array('id' => 2, 'name' => 'heart_eyes'), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/snippets/2/award_emoji') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->awardEmoji(1, 2)); - } - - protected function getApiClass() - { - return 'Gitlab\Api\Snippets'; - } -} diff --git a/test/Gitlab/Tests/Api/SystemHooksTest.php b/test/Gitlab/Tests/Api/SystemHooksTest.php deleted file mode 100644 index b65eb442d..000000000 --- a/test/Gitlab/Tests/Api/SystemHooksTest.php +++ /dev/null @@ -1,80 +0,0 @@ - 1, 'url' => 'http://www.example.com'), - array('id' => 2, 'url' => 'http://www.example.org'), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('hooks') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->all()); - } - - /** - * @test - */ - public function shouldCreateHook() - { - $expectedArray = array('id' => 3, 'url' => 'http://www.example.net'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('hooks', array('url' => 'http://www.example.net')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->create('http://www.example.net')); - } - - /** - * @test - */ - public function shouldTestHook() - { - $expectedBool = true; - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('hooks/3') - ->will($this->returnValue($expectedBool)) - ; - - $this->assertEquals($expectedBool, $api->test(3)); - } - - /** - * @test - */ - public function shouldRemoveHook() - { - $expectedBool = true; - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('delete') - ->with('hooks/3') - ->will($this->returnValue($expectedBool)) - ; - - $this->assertEquals($expectedBool, $api->remove(3)); - } - - protected function getApiClass() - { - return 'Gitlab\Api\SystemHooks'; - } -} diff --git a/test/Gitlab/Tests/Api/TagsTest.php b/test/Gitlab/Tests/Api/TagsTest.php deleted file mode 100644 index 2a1d5e89b..000000000 --- a/test/Gitlab/Tests/Api/TagsTest.php +++ /dev/null @@ -1,85 +0,0 @@ - 'v1.0.0'), - array('name' => 'v1.1.0'), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/repository/tags') - ->will($this->returnValue($expectedArray)); - $this->assertEquals($expectedArray, $api->all(1)); - } - - /** - * @test - */ - public function shouldShowTag() - { - $expectedArray = array( - array('name' => 'v1.0.0'), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('projects/1/repository/tags/v1.0.0') - ->will($this->returnValue($expectedArray)); - $this->assertEquals($expectedArray, $api->show(1, 'v1.0.0')); - } - - /** - * @test - */ - public function shouldCreateTag() - { - $expectedArray = array( - array('name' => 'v1.1.0'), - ); - - $params = array( - 'id' => 1, - 'tag_name' => 'v1.1.0', - 'ref' => 'ref/heads/master' - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('projects/1/repository/tags', $params) - ->will($this->returnValue($expectedArray)); - - $this->assertEquals($expectedArray, $api->create(1, $params)); - } - - /** - * @test - */ - public function shouldRemoveTag() - { - $expectedArray = array( - array('name' => 'v1.1.0'), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('delete') - ->with('projects/1/repository/tags/v1.1.0') - ->will($this->returnValue($expectedArray)); - $this->assertEquals($expectedArray, $api->remove(1, 'v1.1.0')); - } - - protected function getApiClass() - { - return 'Gitlab\Api\Tags'; - } -} diff --git a/test/Gitlab/Tests/Api/TestCase.php b/test/Gitlab/Tests/Api/TestCase.php deleted file mode 100644 index a48c1a01a..000000000 --- a/test/Gitlab/Tests/Api/TestCase.php +++ /dev/null @@ -1,35 +0,0 @@ -getMockBuilder(HttpClient::class) - ->setMethods(array('sendRequest')) - ->getMock(); - $httpClient - ->expects($this->any()) - ->method('sendRequest'); - - $client = Client::createWithHttpClient($httpClient); - - return $this->getMockBuilder($this->getApiClass()) - ->setMethods(array_merge(array('getAsResponse', 'get', 'post', 'postRaw', 'patch', 'delete', 'put', 'head'), $methods)) - ->setConstructorArgs(array($client)) - ->getMock(); - } -} diff --git a/test/Gitlab/Tests/Api/UsersTest.php b/test/Gitlab/Tests/Api/UsersTest.php deleted file mode 100644 index d0bcc659a..000000000 --- a/test/Gitlab/Tests/Api/UsersTest.php +++ /dev/null @@ -1,413 +0,0 @@ - 1, 'name' => 'Matt'), - array('id' => 2, 'name' => 'John'), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('users', array()) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->all()); - } - - /** - * @test - */ - public function shouldGetActiveUsers() - { - $expectedArray = array( - array('id' => 1, 'name' => 'Matt'), - array('id' => 2, 'name' => 'John'), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('users', array('active' => true)) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->all(['active' => true])); - } - - /** - * @test - */ - public function shouldGetUsersWithDateTimeParams() - { - $expectedArray = [ - ['id' => 1, 'name' => 'Matt'], - ['id' => 2, 'name' => 'John'], - ]; - - $createdAfter = new \DateTime('2018-01-01 00:00:00'); - $createdBefore = new \DateTime('2018-01-31 00:00:00'); - - $expectedWithArray = [ - 'created_after' => $createdAfter->format(DATE_ATOM), - 'created_before' => $createdBefore->format(DATE_ATOM), - ]; - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('users', $expectedWithArray) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals( - $expectedArray, - $api->all(['created_after' => $createdAfter, 'created_before' => $createdBefore]) - ); - } - - /** - * @test - */ - public function shouldShowUser() - { - $expectedArray = array('id' => 1, 'name' => 'Matt'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('users/1') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->show(1)); - } - - /** - * @test - */ - public function shouldCreateUser() - { - $expectedArray = array('id' => 3, 'name' => 'Billy'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('users', array('email' => 'billy@example.com', 'password' => 'password')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->create('billy@example.com', 'password')); - } - - /** - * @test - */ - public function shouldCreateUserWithAdditionalInfo() - { - $expectedArray = array('id' => 3, 'name' => 'Billy'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('users', array('email' => 'billy@example.com', 'password' => 'password', 'name' => 'Billy', 'bio' => 'A person')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->create('billy@example.com', 'password', array('name' => 'Billy', 'bio' => 'A person'))); - } - - /** - * @test - */ - public function shouldUpdateUser() - { - $expectedArray = array('id' => 3, 'name' => 'Billy Bob'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('put') - ->with('users/3', array('name' => 'Billy Bob')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->update(3, array('name' => 'Billy Bob'))); - } - - /** - * @test - */ - public function shouldRemoveUser() - { - $expectedBool = true; - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('delete') - ->with('users/1') - ->will($this->returnValue($expectedBool)) - ; - - $this->assertEquals($expectedBool, $api->remove(1)); - } - - /** - * @test - */ - public function shouldBlockUser() - { - $expectedBool = true; - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('users/1/block') - ->will($this->returnValue($expectedBool)) - ; - - $this->assertEquals($expectedBool, $api->block(1)); - } - - /** - * @test - */ - public function shouldUnblockUser() - { - $expectedBool = true; - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('users/1/unblock') - ->will($this->returnValue($expectedBool)) - ; - - $this->assertEquals($expectedBool, $api->unblock(1)); - } - - /** - * @test - */ - public function shouldShowCurrentUser() - { - $expectedArray = array('id' => 1, 'name' => 'Matt'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('user') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->me()); - } - - /** - * @test - */ - public function shouldGetCurrentUserKeys() - { - $expectedArray = array( - array('id' => 1, 'title' => 'A key'), - array('id' => 2, 'name' => 'Another key'), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('user/keys') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->keys(1)); - } - - /** - * @test - */ - public function shouldGetCurrentUserKey() - { - $expectedArray = array('id' => 1, 'title' => 'A key'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('user/keys/1') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->key(1)); - } - - /** - * @test - */ - public function shouldCreateKeyForCurrentUser() - { - $expectedArray = array('id' => 3, 'title' => 'A new key'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('user/keys', array('title' => 'A new key', 'key' => '...')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->createKey('A new key', '...')); - } - - /** - * @test - */ - public function shouldDeleteKeyForCurrentUser() - { - $expectedBool = true; - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('delete') - ->with('user/keys/3') - ->will($this->returnValue($expectedBool)) - ; - - $this->assertEquals($expectedBool, $api->removeKey(3)); - } - - /** - * @test - */ - public function shouldGetUserKeys() - { - $expectedArray = array( - array('id' => 1, 'title' => 'A key'), - array('id' => 2, 'name' => 'Another key'), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('users/1/keys') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->userKeys(1)); - } - - /** - * @test - */ - public function shouldGetUserKey() - { - $expectedArray = array('id' => 2, 'title' => 'Another key'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('users/1/keys/2') - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->userKey(1, 2)); - } - - /** - * @test - */ - public function shouldCreateKeyForUser() - { - $expectedArray = array('id' => 3, 'title' => 'A new key'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('post') - ->with('users/1/keys', array('title' => 'A new key', 'key' => '...')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->createKeyForUser(1, 'A new key', '...')); - } - - /** - * @test - */ - public function shouldDeleteKeyForUser() - { - $expectedBool = true; - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('delete') - ->with('users/1/keys/3') - ->will($this->returnValue($expectedBool)) - ; - - $this->assertEquals($expectedBool, $api->removeUserKey(1, 3)); - } - - /** - * @test - */ - public function shouldAttemptLogin() - { - $expectedArray = array('id' => 1, 'name' => 'Matt'); - - $api = $this->getApiMock(); - $api->expects($this->exactly(2)) - ->method('post') - ->with('session', array('login' => 'matt', 'password' => 'password', 'email' => 'matt')) - ->will($this->returnValue($expectedArray)) - ; - - $this->assertEquals($expectedArray, $api->session('matt', 'password')); - $this->assertEquals($expectedArray, $api->login('matt', 'password')); - } - - /** - * @test - */ - public function shouldGetUserEmails() - { - $expectedArray = array( - array('id' => 1, 'email' => 'foo@bar.baz'), - array('id' => 2, 'email' => 'foo@bar.qux'), - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('user/emails') - ->will($this->returnValue($expectedArray)); - - $this->assertEquals($expectedArray, $api->emails()); - } - - /** - * @test - */ - public function shouldGetSpecificUserEmail() - { - $expectedArray = array('id' => 1, 'email' => 'foo@bar.baz'); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('user/emails/1') - ->will($this->returnValue($expectedArray)); - - $this->assertEquals($expectedArray, $api->email(1)); - } - - protected function getApiClass() - { - return 'Gitlab\Api\Users'; - } -} diff --git a/test/Gitlab/Tests/Api/VersionTest.php b/test/Gitlab/Tests/Api/VersionTest.php deleted file mode 100644 index c85a2414e..000000000 --- a/test/Gitlab/Tests/Api/VersionTest.php +++ /dev/null @@ -1,26 +0,0 @@ - "8.13.0-pre", - "revision" => "4e963fe", - ); - - $api = $this->getApiMock(); - $api->expects($this->once()) - ->method('get') - ->with('version') - ->will($this->returnValue($expectedArray)); - $this->assertEquals($expectedArray, $api->show()); - } - protected function getApiClass() - { - return 'Gitlab\Api\Version'; - } -} diff --git a/test/Gitlab/Tests/HttpClient/BuilderTest.php b/test/Gitlab/Tests/HttpClient/BuilderTest.php deleted file mode 100644 index 1792f68ff..000000000 --- a/test/Gitlab/Tests/HttpClient/BuilderTest.php +++ /dev/null @@ -1,55 +0,0 @@ - - */ -class BuilderTest extends \PHPUnit_Framework_TestCase -{ - /** - * @var Builder - */ - private $subject; - - public function setUp() - { - $this->subject = new Builder( - $this->getMock(HttpClient::class), - $this->getMock(RequestFactory::class), - $this->getMock(StreamFactory::class) - ); - } - - public function testAddPluginShouldInvalidateHttpClient() - { - $client = $this->subject->getHttpClient(); - - $this->subject->addPlugin($this->getMock(Plugin::class)); - - $this->assertNotSame($client, $this->subject->getHttpClient()); - } - - public function testRemovePluginShouldInvalidateHttpClient() - { - $this->subject->addPlugin($this->getMock(Plugin::class)); - - $client = $this->subject->getHttpClient(); - - $this->subject->removePlugin(Plugin::class); - - $this->assertNotSame($client, $this->subject->getHttpClient()); - } - - public function testHttpClientShouldBeAnHttpMethodsClient() - { - $this->assertInstanceOf(HttpMethodsClient::class, $this->subject->getHttpClient()); - } -} diff --git a/test/Gitlab/Tests/HttpClient/Message/QueryStringBuilderTest.php b/test/Gitlab/Tests/HttpClient/Message/QueryStringBuilderTest.php deleted file mode 100644 index 4dc2e9c3e..000000000 --- a/test/Gitlab/Tests/HttpClient/Message/QueryStringBuilderTest.php +++ /dev/null @@ -1,95 +0,0 @@ -assertEquals($expected, QueryStringBuilder::build($query)); - } - - public function queryStringProvider() - { - //Scalar value. - yield [ - 'a project', - 'a%20project', - ]; - - //Indexed array. - yield [ - ['iids' => [88, 86]], - //iids[]=88&iids[]=86 - 'iids%5B%5D=88&iids%5B%5D=86', - ]; - - //Non indexed array with only numeric keys. - yield [ - ['iids' => [0 => 88, 2 => 86]], - //iids[0]=88&iids[2]=86 - 'iids%5B0%5D=88&iids%5B2%5D=86' - ]; - - //Boolean encoding - yield [ - ['push_events' => false, 'merge_requests_events' => 1], - 'push_events=0&merge_requests_events=1' - ]; - - //A deeply nested array. - yield [ - [ - 'search' => 'a project', - 'owned' => 'true', - 'iids' => [88, 86], - 'assoc' => [ - 'a' => 'b', - 'c' => [ - 'd' => 'e', - 'f' => 'g', - ], - ], - 'nested' => [ - 'a' => [ - [ - 'b' => 'c', - ], - [ - 'd' => 'e', - 'f' => [ - 'g' => 'h', - 'i' => 'j', - 'k' => [87, 89], - ], - ], - ], - ], - ], - //search=a project - //&owned=true - //&iids[]=88&iids[]=86 - //&assoc[a]=b&assoc[c][d]=e&assoc[c][f]=g - //&nested[a][][b]=c&nested[a][][d]=e - //&nested[a][][f][g]=h&nested[a][][f][i]=j - //&nested[a][][f][k][]=87&nested[a][][f][k][]=89 - 'search=a%20project'. - '&owned=true'. - '&iids%5B%5D=88&iids%5B%5D=86'. - '&assoc%5Ba%5D=b&assoc%5Bc%5D%5Bd%5D=e&assoc%5Bc%5D%5Bf%5D=g'. - '&nested%5Ba%5D%5B%5D%5Bb%5D=c&nested%5Ba%5D%5B%5D%5Bd%5D=e'. - '&nested%5Ba%5D%5B%5D%5Bf%5D%5Bg%5D=h&nested%5Ba%5D%5B%5D%5Bf%5D%5Bi%5D=j'. - '&nested%5Ba%5D%5B%5D%5Bf%5D%5Bk%5D%5B%5D=87&nested%5Ba%5D%5B%5D%5Bf%5D%5Bk%5D%5B%5D=89' - , - ]; - } -} diff --git a/test/Gitlab/Tests/HttpClient/Message/ResponseMediatorTest.php b/test/Gitlab/Tests/HttpClient/Message/ResponseMediatorTest.php deleted file mode 100644 index 661e6ad6a..000000000 --- a/test/Gitlab/Tests/HttpClient/Message/ResponseMediatorTest.php +++ /dev/null @@ -1,77 +0,0 @@ - - */ -class ResponseMediatorTest extends \PHPUnit_Framework_TestCase -{ - public function testGetContent() - { - $body = array('foo' => 'bar'); - $response = new Response( - 200, - array('Content-Type'=>'application/json'), - \GuzzleHttp\Psr7\stream_for(json_encode($body)) - ); - - $this->assertEquals($body, ResponseMediator::getContent($response)); - } - - /** - * If content-type is not json we should get the raw body. - */ - public function testGetContentNotJson() - { - $body = 'foobar'; - $response = new Response( - 200, - array(), - \GuzzleHttp\Psr7\stream_for($body) - ); - - $this->assertEquals($body, ResponseMediator::getContent($response)); - } - - /** - * Make sure we return the body if we have invalid json - */ - public function testGetContentInvalidJson() - { - $body = 'foobar'; - $response = new Response( - 200, - array('Content-Type'=>'application/json'), - \GuzzleHttp\Psr7\stream_for($body) - ); - - $this->assertEquals($body, ResponseMediator::getContent($response)); - } - - public function testGetPagination() - { - $header = <<; rel="first", -; rel="next", -; rel="prev", -; rel="last", -TEXT; - - $pagination = array( - 'first' => 'https://example.gitlab.com', - 'next' => 'https://example.gitlab.com', - 'prev' => 'https://example.gitlab.com', - 'last' => 'https://example.gitlab.com' - ); - - // response mock - $response = new Response(200, array('link'=>$header)); - $result = ResponseMediator::getPagination($response); - - $this->assertEquals($pagination, $result); - } -} diff --git a/test/Gitlab/Tests/HttpClient/Plugin/ApiVersionTest.php b/test/Gitlab/Tests/HttpClient/Plugin/ApiVersionTest.php deleted file mode 100644 index cf8577a73..000000000 --- a/test/Gitlab/Tests/HttpClient/Plugin/ApiVersionTest.php +++ /dev/null @@ -1,69 +0,0 @@ -getMockBuilder(\stdClass::class) - ->setMethods(['next']) - ->getMock() - ; - $callback->expects($this->once()) - ->method('next') - ->with($this->isInstanceOf(RequestInterface::class)) - ->willReturn($promise) - ; - - $this->assertEquals($promise, $plugin->handleRequest($request, [$callback, 'next'], function () { - })); - } - - public function testPrefixRequestPath() - { - $request = new Request('GET', 'projects'); - $expected = new Request('GET', '/api/v4/projects'); - $plugin = new ApiVersion(); - - $callback = $this->getMockBuilder(\stdClass::class) - ->setMethods(['next']) - ->getMock() - ; - $callback->expects($this->once()) - ->method('next') - ->with($expected) - ; - - $plugin->handleRequest($request, [$callback, 'next'], function () { - }); - } - - public function testNoPrefixingRequired() - { - $request = new Request('GET', '/api/v4/projects'); - $plugin = new ApiVersion(); - - $callback = $this->getMockBuilder(\stdClass::class) - ->setMethods(['next']) - ->getMock() - ; - $callback->expects($this->once()) - ->method('next') - ->with($request) - ; - - $plugin->handleRequest($request, [$callback, 'next'], function () { - }); - } -} diff --git a/test/Gitlab/Tests/ResultPagerTest.php b/test/Gitlab/Tests/ResultPagerTest.php deleted file mode 100644 index e91cb5fbd..000000000 --- a/test/Gitlab/Tests/ResultPagerTest.php +++ /dev/null @@ -1,112 +0,0 @@ -getMockBuilder(Client::class) - ->disableOriginalConstructor() - ->getMock() - ; - - $api = $this->getMockBuilder(ApiInterface::class) - ->setMethods(['__construct', 'all']) - ->getMock() - ; - $api->expects($this->once()) - ->method('all') - ->willReturn(['project1', 'project2']) - ; - - $pager = new ResultPager($client); - - $result = $pager->fetch($api, 'all'); - - $this->assertEquals(['project1', 'project2'], $result); - } - - public function testFetchAll() - { - $client = $this->getMockBuilder(Client::class) - ->disableOriginalConstructor() - ->getMock() - ; - - $history = $this->getMockBuilder(History::class) - ->disableOriginalConstructor() - ->getMock() - ; - - $response1 = (new Response)->withHeader('Link', '; rel="next",'); - $response2 = (new Response)->withHeader('Link', '; rel="next",') - ->withHeader('Content-Type', 'application/json') - ->withBody(stream_for('["project3", "project4"]')) - ; - $response3 = (new Response)->withHeader('Content-Type', 'application/json') - ->withBody(stream_for('["project5", "project6"]')) - ; - - $history - ->method('getLastResponse') - ->will($this->onConsecutiveCalls( - $response1, - $response1, - $response1, - $response2, - $response2, - $response2, - $response3 - )) - ; - - $httpClient = $this->getMockBuilder(HttpMethodsClient::class) - ->disableOriginalConstructor() - ->getMock() - ; - - $httpClient->expects($this->exactly(2)) - ->method('get') - ->withConsecutive( - ['https://example.gitlab.com/projects?page=2'], - ['https://example.gitlab.com/projects?page=3'] - ) - ->will($this->onConsecutiveCalls( - $response2, - $response3 - )) - ; - - $client - ->method('getResponseHistory') - ->willReturn($history) - ; - $client - ->method('getHttpClient') - ->willReturn($httpClient) - ; - - $api = $this->getMockBuilder(ApiInterface::class) - ->setMethods(['__construct', 'all']) - ->getMock(); - $api->expects($this->exactly(1)) - ->method('all') - ->willReturn(['project1', 'project2']) - ; - - $pager = new ResultPager($client); - - $result = $pager->fetchAll($api, 'all'); - - $this->assertEquals(['project1', 'project2', 'project3', 'project4', 'project5', 'project6'], $result); - } -} diff --git a/test/Gitlab/Tests/Api/DeployKeysTest.php b/tests/Api/DeployKeysTest.php similarity index 51% rename from test/Gitlab/Tests/Api/DeployKeysTest.php rename to tests/Api/DeployKeysTest.php index 25a796040..bbe1648b9 100644 --- a/test/Gitlab/Tests/Api/DeployKeysTest.php +++ b/tests/Api/DeployKeysTest.php @@ -1,44 +1,59 @@ - + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\DeployKeys; +use PHPUnit\Framework\Attributes\Test; class DeployKeysTest extends TestCase { - /** - * @test - */ - public function shouldGetAllDeployKeys() + #[Test] + public function shouldGetAllDeployKeys(): void { $expectedArray = $this->getMultipleDeployKeysData(); $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with('deploy_keys', array('page' => 2, 'per_page' => 5)) - ->will($this->returnValue($expectedArray)) + ->with('deploy_keys', ['page' => 2, 'per_page' => 5]) + ->willReturn($expectedArray) ; $this->assertEquals($expectedArray, $api->all(['page' => 2, 'per_page' => 5])); } - protected function getMultipleDeployKeysData() + protected function getMultipleDeployKeysData(): array { - return array( - array( + return [ + [ 'id' => 1, 'title' => 'Public key', 'key' => 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=', - 'created_at' => '2013-10-02T10:12:29Z' - ), - array( + 'created_at' => '2013-10-02T10:12:29Z', + ], + [ 'id' => 3, 'title' => 'Another Public key', 'key' => 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=', - 'created_at' => '2013-10-02T11:12:29Z' - ) - ); + 'created_at' => '2013-10-02T11:12:29Z', + ], + ]; } - protected function getApiClass() + protected function getApiClass(): string { - return 'Gitlab\Api\DeployKeys'; + return DeployKeys::class; } } diff --git a/tests/Api/DeploymentsTest.php b/tests/Api/DeploymentsTest.php new file mode 100644 index 000000000..d6859206b --- /dev/null +++ b/tests/Api/DeploymentsTest.php @@ -0,0 +1,326 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\Deployments; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\MockObject\MockObject; + +class DeploymentsTest extends TestCase +{ + #[Test] + public function shouldGetAllDeployments(): void + { + $expectedArray = $this->getMultipleDeploymentsData(); + + $api = $this->getMultipleDeploymentsRequestMock('projects/1/deployments', $expectedArray, []); + + $this->assertEquals($expectedArray, $api->all(1)); + } + + #[Test] + public function shouldShowDeployment(): void + { + $expectedArray = [ + [ + 'created_at' => '2016-08-11T11:32:35.444Z', + 'deployable' => [ + 'commit' => [ + 'author_email' => 'admin@example.com', + 'author_name' => 'Administrator', + 'created_at' => '2016-08-11T13:28:26.000+02:00', + 'id' => 'a91957a858320c0e17f3a0eca7cfacbff50ea29a', + 'message' => 'Merge branch \'rename-readme\' into \'master\' + +Rename README + + + +See merge request !2', + 'short_id' => 'a91957a8', + 'title' => 'Merge branch \'rename-readme\' into \'master\' +', + ], + 'coverage' => null, + 'created_at' => '2016-08-11T11:32:24.456Z', + 'finished_at' => '2016-08-11T11:32:35.145Z', + 'id' => 664, + 'name' => 'deploy', + 'ref' => 'master', + 'runner' => null, + 'stage' => 'deploy', + 'started_at' => null, + 'status' => 'success', + 'tag' => false, + 'user' => [ + 'avatar_url' => 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + 'bio' => null, + 'created_at' => '2016-08-11T07:09:20.351Z', + 'id' => 1, + 'linkedin' => '', + 'location' => null, + 'name' => 'Administrator', + 'skype' => '', + 'state' => 'active', + 'twitter' => '', + 'username' => 'root', + 'web_url' => 'http://localhost:3000/root', + 'website_url' => '', + ], + ], + 'environment' => [ + 'external_url' => 'https://about.gitlab.com', + 'id' => 9, + 'name' => 'production', + ], + 'id' => 42, + 'iid' => 2, + 'ref' => 'master', + 'sha' => 'a91957a858320c0e17f3a0eca7cfacbff50ea29a', + 'user' => [ + 'avatar_url' => 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + 'id' => 1, + 'name' => 'Administrator', + 'state' => 'active', + 'username' => 'root', + 'web_url' => 'http://localhost:3000/root', + ], + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/deployments/42') + ->willReturn($expectedArray); + $this->assertEquals($expectedArray, $api->show(1, 42)); + } + + private function getMultipleDeploymentsData(): array + { + return [ + [ + 'created_at' => '2016-08-11T07:36:40.222Z', + 'deployable' => [ + 'commit' => [ + 'author_email' => 'admin@example.com', + 'author_name' => 'Administrator', + 'created_at' => '2016-08-11T09:36:01.000+02:00', + 'id' => '99d03678b90d914dbb1b109132516d71a4a03ea8', + 'message' => 'Merge branch \'new-title\' into \'master\' + +Update README + + + +See merge request !1', + 'short_id' => '99d03678', + 'title' => 'Merge branch \'new-title\' into \'master\' +', + ], + 'coverage' => null, + 'created_at' => '2016-08-11T07:36:27.357Z', + 'finished_at' => '2016-08-11T07:36:39.851Z', + 'id' => 657, + 'name' => 'deploy', + 'ref' => 'master', + 'runner' => null, + 'stage' => 'deploy', + 'started_at' => null, + 'status' => 'success', + 'tag' => false, + 'user' => [ + 'avatar_url' => 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + 'bio' => null, + 'created_at' => '2016-08-11T07:09:20.351Z', + 'id' => 1, + 'linkedin' => '', + 'location' => null, + 'name' => 'Administrator', + 'skype' => '', + 'state' => 'active', + 'twitter' => '', + 'username' => 'root', + 'web_url' => 'http://localhost:3000/root', + 'website_url' => '', + ], + ], + 'environment' => [ + 'external_url' => 'https://about.gitlab.com', + 'id' => 9, + 'name' => 'production', + ], + 'id' => 41, + 'iid' => 1, + 'ref' => 'master', + 'sha' => '99d03678b90d914dbb1b109132516d71a4a03ea8', + 'user' => [ + 'avatar_url' => 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + 'id' => 1, + 'name' => 'Administrator', + 'state' => 'active', + 'username' => 'root', + 'web_url' => 'http://localhost:3000/root', + ], + ], + [ + 'created_at' => '2016-08-11T11:32:35.444Z', + 'deployable' => [ + 'commit' => [ + 'author_email' => 'admin@example.com', + 'author_name' => 'Administrator', + 'created_at' => '2016-08-11T13:28:26.000+02:00', + 'id' => 'a91957a858320c0e17f3a0eca7cfacbff50ea29a', + 'message' => 'Merge branch \'rename-readme\' into \'master\' + +Rename README + + + +See merge request !2', + 'short_id' => 'a91957a8', + 'title' => 'Merge branch \'rename-readme\' into \'master\' +', + ], + 'coverage' => null, + 'created_at' => '2016-08-11T11:32:24.456Z', + 'finished_at' => '2016-08-11T11:32:35.145Z', + 'id' => 664, + 'name' => 'deploy', + 'ref' => 'master', + 'runner' => null, + 'stage' => 'deploy', + 'started_at' => null, + 'status' => 'success', + 'tag' => false, + 'user' => [ + 'avatar_url' => 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + 'bio' => null, + 'created_at' => '2016-08-11T07:09:20.351Z', + 'id' => 1, + 'linkedin' => '', + 'location' => null, + 'name' => 'Administrator', + 'skype' => '', + 'state' => 'active', + 'twitter' => '', + 'username' => 'root', + 'web_url' => 'http://localhost:3000/root', + 'website_url' => '', + ], + ], + 'environment' => [ + 'external_url' => 'https://about.gitlab.com', + 'id' => 9, + 'name' => 'production', + ], + 'id' => 42, + 'iid' => 2, + 'ref' => 'master', + 'sha' => 'a91957a858320c0e17f3a0eca7cfacbff50ea29a', + 'user' => [ + 'avatar_url' => 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + 'id' => 1, + 'name' => 'Administrator', + 'state' => 'active', + 'username' => 'root', + 'web_url' => 'http://localhost:3000/root', + ], + ], + ]; + } + + protected function getMultipleDeploymentsRequestMock(string $path, array $expectedArray, array $expectedParameters): MockObject + { + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with($path, $expectedParameters) + ->willReturn($expectedArray); + + return $api; + } + + #[Test] + public function shouldGetAllDeploymentsSortedByCreatedAt(): void + { + $expectedArray = $this->getMultipleDeploymentsData(); + + $api = $this->getMultipleDeploymentsRequestMock( + 'projects/1/deployments', + $expectedArray, + ['page' => 1, 'per_page' => 5, 'order_by' => 'created_at', 'sort' => 'asc'] + ); + + $this->assertEquals( + $expectedArray, + $api->all(1, ['page' => 1, 'per_page' => 5, 'order_by' => 'created_at', 'sort' => 'asc']) + ); + } + + protected function getApiClass(): string + { + return Deployments::class; + } + + #[Test] + public function shouldAllowDeploymentFilterByStatus(): void + { + $expectedArray = $this->getMultipleDeploymentsData(); + + $api = $this->getMultipleDeploymentsRequestMock( + 'projects/1/deployments', + $expectedArray, + ['status' => 'success'] + ); + + $this->assertEquals( + $expectedArray, + $api->all(1, ['status' => 'success']) + ); + } + + #[Test] + public function shouldAllowFilterByEnvironment(): void + { + $expectedArray = $this->getMultipleDeploymentsData(); + + $api = $this->getMultipleDeploymentsRequestMock( + 'projects/1/deployments', + $expectedArray, + ['environment' => 'production'] + ); + + $this->assertEquals( + $expectedArray, + $api->all(1, ['environment' => 'production']) + ); + } + + #[Test] + public function shouldAllowEmptyArrayIfAllExcludedByFilter(): void + { + $expectedArray = $this->getMultipleDeploymentsData(); + + $api = $this->getMultipleDeploymentsRequestMock( + 'projects/1/deployments', + [], + ['environment' => 'test'] + ); + + $this->assertEquals([], $api->all(1, ['environment' => 'test']) + ); + } +} diff --git a/tests/Api/EnvironmentsTest.php b/tests/Api/EnvironmentsTest.php new file mode 100644 index 000000000..5af2c4adf --- /dev/null +++ b/tests/Api/EnvironmentsTest.php @@ -0,0 +1,202 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\Environments; +use PHPUnit\Framework\Attributes\Test; + +class EnvironmentsTest extends TestCase +{ + #[Test] + public function shouldGetAllEnvironments(): void + { + $expectedArray = [ + [ + 'id' => 1, + 'name' => 'review/fix-foo', + 'slug' => 'review-fix-foo-dfjre3', + 'external_url' => 'https://review-fix-foo-dfjre3.example.gitlab.com', + ], + [ + 'id' => 2, + 'name' => 'review/fix-bar', + 'slug' => 'review-fix-bar-dfjre4', + 'external_url' => 'https://review-fix-bar-dfjre4.example.gitlab.com', + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/environments') + ->willReturn($expectedArray); + $this->assertEquals($expectedArray, $api->all(1)); + } + + #[Test] + public function shouldFilterEnvironmentByName(): void + { + $expected = [ + [ + 'id' => 2, + 'name' => 'review/fix-bar', + 'slug' => 'review-fix-bar-dfjre4', + 'external_url' => 'https://review-fix-bar-dfjre4.example.gitlab.com', + ], + ]; + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/environments') + ->willReturn($expected); + $this->assertEquals($expected, $api->all(1, ['name' => 'review/fix-bar'])); + } + + #[Test] + public function shouldGetSingleEnvironment(): void + { + $expected = [ + 'id' => 1, + 'name' => 'review/fix-foo', + 'slug' => 'review-fix-foo-dfjre3', + 'external_url' => 'https://review-fix-foo-dfjre3.example.gitlab.com', + 'latest_deployment' => [ + 'created_at' => '2016-08-11T07:36:40.222Z', + 'deployable' => [ + 'commit' => [ + 'author_email' => 'admin@example.com', + 'author_name' => 'Administrator', + 'created_at' => '2016-08-11T09:36:01.000+02:00', + 'id' => '99d03678b90d914dbb1b109132516d71a4a03ea8', + 'message' => 'Merge branch \'new-title\' into \'master\' + +Update README + + + +See merge request !1', + 'short_id' => '99d03678', + 'title' => 'Merge branch \'new-title\' into \'master\' +', + ], + 'coverage' => null, + 'created_at' => '2016-08-11T07:36:27.357Z', + 'finished_at' => '2016-08-11T07:36:39.851Z', + 'id' => 657, + 'name' => 'deploy', + 'ref' => 'master', + 'runner' => null, + 'stage' => 'deploy', + 'started_at' => null, + 'status' => 'success', + 'tag' => false, + 'user' => [ + 'avatar_url' => 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + 'bio' => null, + 'created_at' => '2016-08-11T07:09:20.351Z', + 'id' => 1, + 'linkedin' => '', + 'location' => null, + 'name' => 'Administrator', + 'skype' => '', + 'state' => 'active', + 'twitter' => '', + 'username' => 'root', + 'web_url' => 'http://localhost:3000/root', + 'website_url' => '', + ], + ], + 'id' => 41, + 'iid' => 1, + 'ref' => 'master', + 'sha' => '99d03678b90d914dbb1b109132516d71a4a03ea8', + 'user' => [ + 'avatar_url' => 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + 'id' => 1, + 'name' => 'Administrator', + 'state' => 'active', + 'username' => 'root', + 'web_url' => 'http://localhost:3000/root', + ], + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/environments/1') + ->willReturn($expected); + $this->assertEquals($expected, $api->show(1, 1)); + } + + #[Test] + public function shouldCreateEnvironment(): void + { + $expectedArray = [ + [ + 'id' => 3, + 'name' => 'review/fix-baz', + 'slug' => 'review-fix-baz-dfjre5', + 'external_url' => 'https://review-fix-baz-dfjre5.example.gitlab.com', + 'tier' => 'production', + ], + ]; + + $params = [ + 'name' => 'review/fix-baz', + 'external_url' => 'https://review-fix-baz-dfjre5.example.gitlab.com', + 'tier' => 'production', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/environments', $params) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->create(1, $params)); + } + + #[Test] + public function shouldRemoveEnvironment(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/environments/3') + ->willReturn($expectedBool); + $this->assertEquals($expectedBool, $api->remove(1, 3)); + } + + #[Test] + public function shouldStopEnvironment(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/environments/3/stop') + ->willReturn($expectedBool); + $this->assertEquals($expectedBool, $api->stop(1, 3)); + } + + protected function getApiClass(): string + { + return Environments::class; + } +} diff --git a/tests/Api/EventsTest.php b/tests/Api/EventsTest.php new file mode 100644 index 000000000..fb39b9c84 --- /dev/null +++ b/tests/Api/EventsTest.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\Events; +use PHPUnit\Framework\Attributes\Test; + +class EventsTest extends TestCase +{ + protected function getApiClass(): string + { + return Events::class; + } + + #[Test] + public function shouldGetAllEvents(): void + { + $expectedArray = [ + ['id' => 1, 'target_type' => 'Issue'], + ['id' => 2, 'target_type' => null], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('events', []) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->all()); + } + + #[Test] + public function shouldGetEventsAfter(): void + { + $expectedArray = [ + ['id' => 1, 'target_type' => 'Issue'], + ['id' => 2, 'target_type' => null], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('events', ['after' => '1970-01-01']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->all(['after' => new \DateTime('1970-01-01')])); + } +} diff --git a/tests/Api/GroupBoardsTest.php b/tests/Api/GroupBoardsTest.php new file mode 100644 index 000000000..4ebf53bf5 --- /dev/null +++ b/tests/Api/GroupBoardsTest.php @@ -0,0 +1,225 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\GroupsBoards; +use PHPUnit\Framework\Attributes\Test; + +class GroupBoardsTest extends TestCase +{ + #[Test] + public function shouldGetAllBoards(): void + { + $expectedArray = [ + ['id' => 1, 'title' => 'A board'], + ['id' => 2, 'title' => 'Another board'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('boards', []) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->all()); + } + + #[Test] + public function shouldShowIssueBoard(): void + { + $expectedArray = ['id' => 2, 'name' => 'Another issue board']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/boards/2') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->show(1, 2)); + } + + #[Test] + public function shouldCreateIssueBoard(): void + { + $expectedArray = ['id' => 3, 'name' => 'A new issue board']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('groups/1/boards', ['name' => 'A new issue board']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->create(1, ['name' => 'A new issue board'])); + } + + #[Test] + public function shouldUpdateIssueBoard(): void + { + $expectedArray = ['id' => 2, 'name' => 'A renamed issue board']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('groups/1/boards/2', ['name' => 'A renamed issue board', 'labels' => 'foo']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->update(1, 2, ['name' => 'A renamed issue board', 'labels' => 'foo'])); + } + + #[Test] + public function shouldRemoveIssueBoard(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('groups/1/boards/2') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->remove(1, 2)); + } + + #[Test] + public function shouldGetAllLists(): void + { + $expectedArray = [ + [ + 'id' => 1, + 'label' => [ + 'name' => 'First label', + 'color' => '#F0AD4E', + 'description' => null, + ], + 'position' => 1, + ], [ + 'id' => 2, + 'label' => [ + 'name' => 'Second label', + 'color' => '#F0AD4E', + 'description' => null, + ], + 'position' => 2, + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/boards/2/lists') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->allLists(1, 2)); + } + + #[Test] + public function shouldGetList(): void + { + $expectedArray = [ + [ + 'id' => 3, + 'label' => [ + 'name' => 'Some label', + 'color' => '#F0AD4E', + 'description' => null, + ], + 'position' => 3, + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/boards/2/lists/3') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->showList(1, 2, 3)); + } + + #[Test] + public function shouldCreateList(): void + { + $expectedArray = [ + [ + 'id' => 3, + 'label' => [ + 'name' => 'Some label', + 'color' => '#F0AD4E', + 'description' => null, + ], + 'position' => 3, + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('groups/1/boards/2/lists', ['label_id' => 4]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->createList(1, 2, 4)); + } + + #[Test] + public function shouldUpdateList(): void + { + $expectedArray = [ + [ + 'id' => 3, + 'label' => [ + 'name' => 'Some label', + 'color' => '#F0AD4E', + 'description' => null, + ], + 'position' => 1, + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('groups/5/boards/2/lists/3', ['position' => 1]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->updateList(5, 2, 3, 1)); + } + + #[Test] + public function shouldDeleteList(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('groups/1/boards/2/lists/3') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->deleteList(1, 2, 3)); + } + + protected function getApiClass(): string + { + return GroupsBoards::class; + } +} diff --git a/tests/Api/GroupsEpicsTest.php b/tests/Api/GroupsEpicsTest.php new file mode 100644 index 000000000..8ba51f1ba --- /dev/null +++ b/tests/Api/GroupsEpicsTest.php @@ -0,0 +1,121 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\GroupsEpics; +use PHPUnit\Framework\Attributes\Test; + +class GroupsEpicsTest extends TestCase +{ + #[Test] + public function shouldGetAllEpics(): void + { + $expectedArray = [ + ['id' => 1, 'title' => 'A epic'], + ['id' => 2, 'title' => 'Another epic'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/epics') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->all(1)); + } + + #[Test] + public function shouldShowEpic(): void + { + $expectedArray = ['id' => 1, 'name' => 'A epic']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/epics/2') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->show(1, 2)); + } + + #[Test] + public function shouldCreateEpic(): void + { + $expectedArray = ['id' => 3, 'title' => 'A new epic']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('groups/1/epics', ['description' => 'Some text', 'title' => 'A new epic']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->create(1, ['description' => 'Some text', 'title' => 'A new epic'])); + } + + #[Test] + public function shouldUpdateEpic(): void + { + $expectedArray = ['id' => 3, 'title' => 'Updated epic']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('groups/1/epics/3', ['title' => 'Updated epic', 'description' => 'Updated description', 'state_event' => 'close']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->update(1, 3, ['title' => 'Updated epic', 'description' => 'Updated description', 'state_event' => 'close'])); + } + + #[Test] + public function shouldRemoveEpic(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('groups/1/epics/2') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->remove(1, 2)); + } + + #[Test] + public function shouldGetEpicsIssues(): void + { + $expectedArray = [ + ['id' => 1, 'title' => 'An issue'], + ['id' => 2, 'title' => 'Another issue'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/epics/2/issues') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->issues(1, 2)); + } + + protected function getApiClass(): string + { + return GroupsEpics::class; + } +} diff --git a/tests/Api/GroupsMilestonesTest.php b/tests/Api/GroupsMilestonesTest.php new file mode 100644 index 000000000..963761170 --- /dev/null +++ b/tests/Api/GroupsMilestonesTest.php @@ -0,0 +1,227 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\GroupsMilestones; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Test; + +class GroupsMilestonesTest extends TestCase +{ + #[Test] + public function shouldGetAllMilestones(): void + { + $expectedArray = [ + ['id' => 1, 'title' => 'A milestone'], + ['id' => 2, 'title' => 'Another milestone'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/milestones') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->all(1)); + } + + #[Test] + public function shouldGetAllMilestonesWithParameterOneIidsValue(): void + { + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/milestones', ['iids' => [456]]) + ; + + $api->all(1, ['iids' => [456]]); + } + + #[Test] + public function shouldGetAllMilestonesWithParameterTwoIidsValues(): void + { + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/milestones', ['iids' => [456, 789]]) + ; + + $api->all(1, ['iids' => [456, 789]]); + } + + public static function getAllMilestonesWithParameterStateDataProvider(): array + { + return [ + GroupsMilestones::STATE_ACTIVE => [GroupsMilestones::STATE_ACTIVE], + GroupsMilestones::STATE_CLOSED => [GroupsMilestones::STATE_CLOSED], + ]; + } + + #[Test] + #[DataProvider('getAllMilestonesWithParameterStateDataProvider')] + public function shouldGetAllMilestonesWithParameterState(string $state): void + { + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/milestones', ['state' => $state]) + ; + + $api->all(1, ['state' => $state]); + } + + #[Test] + public function shouldGetAllMilestonesWithParameterSearch(): void + { + $searchValue = 'abc'; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/milestones', ['search' => $searchValue]) + ; + + $api->all(1, ['search' => $searchValue]); + } + + #[Test] + public function shouldGetAllMilestonesWithParameterUpdatedBefore(): void + { + $updatedBefore = new \DateTimeImmutable('2023-11-25T08:00:00Z'); + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/milestones', ['updated_before' => '2023-11-25T08:00:00.000Z']) + ; + + $api->all(1, ['updated_before' => $updatedBefore]); + } + + #[Test] + public function shouldGetAllMilestonesWithParameterUpdatedAfter(): void + { + $updatedAfter = new \DateTimeImmutable('2023-11-25T08:00:00Z'); + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/milestones', ['updated_after' => '2023-11-25T08:00:00.000Z']) + ; + + $api->all(1, ['updated_after' => $updatedAfter]); + } + + #[Test] + public function shouldShowMilestone(): void + { + $expectedArray = ['id' => 1, 'name' => 'A milestone']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/milestones/2') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->show(1, 2)); + } + + #[Test] + public function shouldCreateMilestone(): void + { + $expectedArray = ['id' => 3, 'title' => 'A new milestone']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('groups/1/milestones', ['description' => 'Some text', 'title' => 'A new milestone']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->create(1, ['description' => 'Some text', 'title' => 'A new milestone'])); + } + + #[Test] + public function shouldUpdateMilestone(): void + { + $expectedArray = ['id' => 3, 'title' => 'Updated milestone']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('groups/1/milestones/3', ['title' => 'Updated milestone', 'due_date' => '2015-04-01', 'state_event' => 'close']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->update(1, 3, ['title' => 'Updated milestone', 'due_date' => '2015-04-01', 'state_event' => 'close'])); + } + + #[Test] + public function shouldRemoveMilestone(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('groups/1/milestones/2') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->remove(1, 2)); + } + + #[Test] + public function shouldGetMilestonesIssues(): void + { + $expectedArray = [ + ['id' => 1, 'title' => 'An issue'], + ['id' => 2, 'title' => 'Another issue'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/milestones/3/issues') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->issues(1, 3)); + } + + #[Test] + public function shouldGetMilestonesMergeRequests(): void + { + $expectedArray = [ + ['id' => 1, 'title' => 'A merge request'], + ['id' => 2, 'title' => 'Another merge request'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/milestones/3/merge_requests') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->mergeRequests(1, 3)); + } + + protected function getApiClass(): string + { + return GroupsMilestones::class; + } +} diff --git a/tests/Api/GroupsTest.php b/tests/Api/GroupsTest.php new file mode 100644 index 000000000..3b90e6f2f --- /dev/null +++ b/tests/Api/GroupsTest.php @@ -0,0 +1,885 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use DateTime; +use Gitlab\Api\Groups; +use PHPUnit\Framework\Attributes\Test; + +class GroupsTest extends TestCase +{ + #[Test] + public function shouldGetAllGroups(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'A group'], + ['id' => 2, 'name' => 'Another group'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups', ['page' => 1, 'per_page' => 10]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->all(['page' => 1, 'per_page' => 10])); + } + + #[Test] + public function shouldGetAllGroupsWithBooleanParam(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'A group'], + ['id' => 2, 'name' => 'Another group'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups', ['all_available' => 'false']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->all(['all_available' => false])); + } + + #[Test] + public function shouldGetAllTopLevelGroupsWithoutSubgroups(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'A group'], + ['id' => 2, 'name' => 'Another group'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups', ['top_level_only' => 'true']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->all(['top_level_only' => true])); + } + + #[Test] + public function shouldGetAllGroupProjectsWithBooleanParam(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'A group'], + ['id' => 2, 'name' => 'Another group'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/projects', ['archived' => 'false']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->projects(1, ['archived' => false])); + } + + #[Test] + public function shouldNotNeedPaginationWhenGettingGroups(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'A group'], + ['id' => 2, 'name' => 'Another group'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups', []) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->all()); + } + + #[Test] + public function shouldShowGroup(): void + { + $expectedArray = ['id' => 1, 'name' => 'A group']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->show(1)); + } + + #[Test] + public function shouldCreateGroup(): void + { + $expectedArray = ['id' => 1, 'name' => 'A new group']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('groups', ['name' => 'A new group', 'path' => 'a-new-group', 'visibility' => 'private']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->create('A new group', 'a-new-group')); + } + + #[Test] + public function shouldCreateGroupWithDescriptionAndVisibility(): void + { + $expectedArray = ['id' => 1, 'name' => 'A new group', 'visibility_level' => 2]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('groups', ['name' => 'A new group', 'path' => 'a-new-group', 'description' => 'Description', 'visibility' => 'public']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->create('A new group', 'a-new-group', 'Description', 'public')); + } + + #[Test] + public function shouldCreateGroupWithDescriptionVisibilityAndParentId(): void + { + $expectedArray = ['id' => 1, 'name' => 'A new group', 'visibility_level' => 2, 'parent_id' => 666]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('groups', ['name' => 'A new group', 'path' => 'a-new-group', 'description' => 'Description', 'visibility' => 'public', 'parent_id' => 666]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->create('A new group', 'a-new-group', 'Description', 'public', null, null, 666)); + } + + #[Test] + public function shouldUpdateGroup(): void + { + $expectedArray = ['id' => 3, 'name' => 'Group name', 'path' => 'group-path']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('groups/3', ['name' => 'Group name', 'path' => 'group-path']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->update(3, ['name' => 'Group name', 'path' => 'group-path'])); + } + + #[Test] + public function shouldTransferProjectToGroup(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('groups/1/projects/2') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->transfer(1, 2)); + } + + #[Test] + public function shouldGetAllMembers(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'Matt'], + ['id' => 2, 'name' => 'Bob'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/members/all') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->allMembers(1)); + } + + #[Test] + public function shouldGetAllMember(): void + { + $expectedArray = ['id' => 2, 'name' => 'Bob']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/members/all/2') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->allMember(1, 2)); + } + + #[Test] + public function shouldGetMembers(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'Matt'], + ['id' => 2, 'name' => 'Bob'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/members') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->members(1)); + } + + #[Test] + public function shouldAddMember(): void + { + $tomorrow = (new DateTime('tomorrow')); + $expectedArray = ['id' => 1, 'name' => 'Matt', 'expires_at' => $tomorrow]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('groups/1/members', [ + 'user_id' => 2, 'access_level' => 10, 'expires_at' => $tomorrow->format('Y-m-d'), + ]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->addMember(1, 2, 10, ['expires_at' => $tomorrow])); + } + + #[Test] + public function shouldSaveMember(): void + { + $expectedArray = ['id' => 1, 'name' => 'Matt']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('groups/1/members/2', ['access_level' => 4]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->saveMember(1, 2, 4)); + } + + #[Test] + public function shouldRemoveMember(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('groups/1/members/2') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->removeMember(1, 2)); + } + + #[Test] + public function shouldRemoveGroup(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('groups/1') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->remove(1)); + } + + #[Test] + public function shouldGetAllSubgroups(): void + { + $expectedArray = [ + ['id' => 101, 'name' => 'A subgroup'], + ['id' => 1 - 2, 'name' => 'Another subggroup'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/subgroups', ['page' => 1, 'per_page' => 10]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->subgroups(1, ['page' => 1, 'per_page' => 10])); + } + + #[Test] + public function shouldGetAllIssues(): void + { + $expectedArray = [ + ['id' => 101, 'name' => 'An issue'], + ['id' => 102, 'name' => 'Another issue'], + ['id' => 103, 'name' => 'A third issue'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/issues', ['page' => 1, 'per_page' => 10]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->issues(1, ['page' => 1, 'per_page' => 10])); + } + + #[Test] + public function shouldGetLabels(): void + { + $expectedArray = [ + ['id' => 987, 'name' => 'bug', 'color' => '#000000'], + ['id' => 123, 'name' => 'feature', 'color' => '#ff0000'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/labels') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->labels(1)); + } + + #[Test] + public function shouldAddLabel(): void + { + $expectedArray = ['name' => 'bug', 'color' => '#000000']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('groups/1/labels', ['name' => 'wont-fix', 'color' => '#ffffff']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->addLabel(1, ['name' => 'wont-fix', 'color' => '#ffffff'])); + } + + #[Test] + public function shouldUpdateLabel(): void + { + $expectedArray = ['name' => 'bug', 'color' => '#00ffff']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('groups/1/labels/123', ['new_name' => 'big-bug', 'color' => '#00ffff']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->updateLabel(1, 123, ['new_name' => 'big-bug', 'color' => '#00ffff'])); + } + + #[Test] + public function shouldRemoveLabel(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('groups/1/labels/456', []) + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->removeLabel(1, 456)); + } + + public function shouldGetVariables(): void + { + $expectedArray = [ + ['key' => 'ftp_username', 'value' => 'ftp'], + ['key' => 'ftp_password', 'value' => 'somepassword'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/variables') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->variables(1)); + } + + #[Test] + public function shouldGetVariable(): void + { + $expectedArray = ['key' => 'ftp_username', 'value' => 'ftp']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/variables/ftp_username') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->variable(1, 'ftp_username')); + } + + public function shouldAddVariable(): void + { + $expectedKey = 'ftp_port'; + $expectedValue = '21'; + + $expectedArray = [ + 'key' => $expectedKey, + 'value' => $expectedValue, + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('groups/1/variables', $expectedArray) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->addVariable(1, $expectedKey, $expectedValue)); + } + + #[Test] + public function shouldAddVariableWithProtected(): void + { + $expectedArray = [ + 'key' => 'DEPLOY_SERVER', + 'value' => 'stage.example.com', + 'protected' => true, + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('groups/1/variables', $expectedArray) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->addVariable(1, 'DEPLOY_SERVER', 'stage.example.com', true)); + } + + #[Test] + public function shouldUpdateVariable(): void + { + $expectedKey = 'ftp_port'; + $expectedValue = '22'; + + $expectedArray = [ + 'key' => 'ftp_port', + 'value' => '22', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('groups/1/variables/'.$expectedKey, ['value' => $expectedValue]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->updateVariable(1, $expectedKey, $expectedValue)); + } + + #[Test] + public function shouldUpdateVariableWithProtected(): void + { + $expectedArray = [ + 'key' => 'DEPLOY_SERVER', + 'value' => 'stage.example.com', + 'protected' => true, + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('groups/1/variables/DEPLOY_SERVER', ['value' => 'stage.example.com', 'protected' => true]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->updateVariable(1, 'DEPLOY_SERVER', 'stage.example.com', true)); + } + + #[Test] + public function shouldRemoveVariable(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('groups/1/variables/ftp_password') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->removeVariable(1, 'ftp_password')); + } + + protected function getApiClass(): string + { + return Groups::class; + } + + #[Test] + public function shouldGetAllGroupProjectsWithIssuesEnabled(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'A group', 'issues_enabled' => true], + ['id' => 2, 'name' => 'Another group', 'issues_enabled' => true], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/projects', ['with_issues_enabled' => 'true']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->projects(1, ['with_issues_enabled' => true])); + } + + #[Test] + public function shouldGetAllGroupProjectsWithMergeRequestsEnabled(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'A group', 'merge_requests_enabled' => true], + ['id' => 2, 'name' => 'Another group', 'merge_requests_enabled' => true], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/projects', ['with_merge_requests_enabled' => 'true']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->projects(1, ['with_merge_requests_enabled' => true])); + } + + #[Test] + public function shouldGetAllGroupProjectsSharedToGroup(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'A project', 'shared_with_groups' => [1]], + ['id' => 2, 'name' => 'Another project', 'shared_with_groups' => [1]], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/projects', ['with_shared' => 'true']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->projects(1, ['with_shared' => true])); + } + + #[Test] + public function shouldGetAllGroupProjectsIncludingSubsgroups(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'A project'], + ['id' => 2, 'name' => 'Another project', 'shared_with_groups' => [1]], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/projects', ['include_subgroups' => 'true']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->projects(1, ['include_subgroups' => true])); + } + + #[Test] + public function shouldGetAllGroupProjectsIncludingCustomAttributes(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'A project', 'custom_Attr' => true], + ['id' => 2, 'name' => 'Another project', 'custom_Attr' => true], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/projects', ['with_custom_attributes' => 'true']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->projects(1, ['with_custom_attributes' => true])); + } + + #[Test] + public function shouldGetIterations(): void + { + $expectedArray = [ + [ + 'id' => 5, + 'iid' => 2, + 'sequence' => 1, + 'group_id' => 123, + 'title' => '2022: Sprint 1', + 'description' => '', + 'state' => 3, + 'created_at' => '2021-09-29T21:24:43.913Z', + 'updated_at' => '2022-03-29T19:09:08.368Z', + 'start_date' => '2022-01-10', + 'due_date' => '2022-01-23', + 'web_url' => 'https://example.com/groups/example/-/iterations/34', + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/iterations') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->iterations(1)); + } + + #[Test] + public function shouldGetPackages(): void + { + $expectedArray = [ + 'id' => 2, + 'name' => '@foo/bar', + 'version' => '1.0.3', + 'package_type' => 'npm', + '_links' => [ + 'web_path' => '/namespace1/project1/-/packages/1', + 'delete_api_path' => '/namespace1/project1/-/packages/1', + ], + 'created_at' => '2019-11-27T03:37:38.711Z', + 'pipelines' => [ + 'id' => 123, + 'status' => 'pending', + 'ref' => 'new-pipeline', + 'sha' => 'a91957a858320c0e17f3a0eca7cfacbff50ea29a', + 'web_url' => 'https://example.com/foo/bar/pipelines/47', + 'created_at' => '2016-08-11T11:28:34.085Z', + 'updated_at' => '2016-08-11T11:32:35.169Z', + 'user' => [ + 'name' => 'Administrator', + 'avatar_url' => 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + ], + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/packages') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->packages(1)); + } + + #[Test] + public function shouldGetGroupMergeRequests(): void + { + $expectedArray = [ + ['id' => 1, 'title' => 'A merge request'], + ['id' => 2, 'title' => 'Another merge request'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/merge_requests') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->mergeRequests(1, [])); + } + + #[Test] + public function shouldGetDeployTokens(): void + { + $expectedArray = [ + [ + 'id' => 1, + 'name' => 'MyToken', + 'username' => 'gitlab+deploy-token-1', + 'expires_at' => '2020-02-14T00:00:00.000Z', + 'revoked' => false, + 'expired' => false, + 'scopes' => [ + 'read_repository', + 'read_registry', + ], + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/deploy_tokens') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->deployTokens(1)); + } + + #[Test] + public function shouldGetActiveDeployTokens(): void + { + $expectedArray = [ + [ + 'id' => 1, + 'name' => 'MyToken', + 'username' => 'gitlab+deploy-token-1', + 'expires_at' => '2020-02-14T00:00:00.000Z', + 'revoked' => false, + 'expired' => true, + 'scopes' => [ + 'read_repository', + 'read_registry', + ], + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/deploy_tokens', ['active' => true]) + ->willReturn([]); + + $this->assertEquals([], $api->deployTokens(1, true)); + } + + #[Test] + public function shouldGetInactiveDeployTokens(): void + { + $expectedArray = [ + [ + 'id' => 1, + 'name' => 'MyToken', + 'username' => 'gitlab+deploy-token-1', + 'expires_at' => '2020-02-14T00:00:00.000Z', + 'revoked' => false, + 'expired' => true, + 'scopes' => [ + 'read_repository', + 'read_registry', + ], + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/deploy_tokens', ['active' => false]) + ->willReturn([]); + + $this->assertEquals([], $api->deployTokens(1, false)); + } + + #[Test] + public function shouldCreateDeployToken(): void + { + $expectedArray = [ + 'id' => 1, + 'name' => 'My Deploy Token', + 'username' => 'custom-user', + 'token' => 'jMRvtPNxrn3crTAGukpZ', + 'expires_at' => '2021-01-01T00:00:00.000Z', + 'revoked' => false, + 'expired' => false, + 'scopes' => [ + 'read_repository', + 'read_registry', + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with( + 'groups/1/deploy_tokens', + [ + 'name' => 'My Deploy Token', + 'scopes' => [ + 'read_repository', + 'read_registry', + ], + 'expires_at' => (new DateTime('2021-01-01'))->format('c'), + ] + ) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->createDeployToken(1, [ + 'name' => 'My Deploy Token', + 'scopes' => [ + 'read_repository', + 'read_registry', + ], + 'expires_at' => new DateTime('2021-01-01'), + ])); + } + + #[Test] + public function shouldDeleteDeployToken(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('groups/1/deploy_tokens/2') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->deleteDeployToken(1, 2)); + } + + #[Test] + public function shouldSearchGroups(): void + { + $expectedArray = [ + ['id' => 6, 'name' => 'Project 6 bla'], + ['id' => 7, 'name' => 'Project 7 bla'], + ['id' => 8, 'name' => 'Project 8 bla'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/123/search', [ + 'scope' => 'projects', + 'confidential' => 'false', + 'search' => 'bla', + 'order_by' => 'created_at', + 'sort' => 'desc', + ]) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->search(123, [ + 'scope' => 'projects', + 'confidential' => false, + 'search' => 'bla', + 'order_by' => 'created_at', + 'sort' => 'desc', + ])); + } +} diff --git a/tests/Api/IssueBoardsTest.php b/tests/Api/IssueBoardsTest.php new file mode 100644 index 000000000..45f83fb17 --- /dev/null +++ b/tests/Api/IssueBoardsTest.php @@ -0,0 +1,225 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\IssueBoards; +use PHPUnit\Framework\Attributes\Test; + +class IssueBoardsTest extends TestCase +{ + #[Test] + public function shouldGetAllBoards(): void + { + $expectedArray = [ + ['id' => 1, 'title' => 'A board'], + ['id' => 2, 'title' => 'Another board'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('boards', []) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->all()); + } + + #[Test] + public function shouldShowIssueBoard(): void + { + $expectedArray = ['id' => 2, 'name' => 'Another issue board']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/boards/2') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->show(1, 2)); + } + + #[Test] + public function shouldCreateIssueBoard(): void + { + $expectedArray = ['id' => 3, 'name' => 'A new issue board']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/boards', ['name' => 'A new issue board']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->create(1, ['name' => 'A new issue board'])); + } + + #[Test] + public function shouldUpdateIssueBoard(): void + { + $expectedArray = ['id' => 2, 'name' => 'A renamed issue board']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/boards/2', ['name' => 'A renamed issue board', 'labels' => 'foo']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->update(1, 2, ['name' => 'A renamed issue board', 'labels' => 'foo'])); + } + + #[Test] + public function shouldRemoveIssueBoard(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/boards/2') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->remove(1, 2)); + } + + #[Test] + public function shouldGetAllLists(): void + { + $expectedArray = [ + [ + 'id' => 1, + 'label' => [ + 'name' => 'First label', + 'color' => '#F0AD4E', + 'description' => null, + ], + 'position' => 1, + ], [ + 'id' => 2, + 'label' => [ + 'name' => 'Second label', + 'color' => '#F0AD4E', + 'description' => null, + ], + 'position' => 2, + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/boards/2/lists') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->allLists(1, 2)); + } + + #[Test] + public function shouldGetList(): void + { + $expectedArray = [ + [ + 'id' => 3, + 'label' => [ + 'name' => 'Some label', + 'color' => '#F0AD4E', + 'description' => null, + ], + 'position' => 3, + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/boards/2/lists/3') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->showList(1, 2, 3)); + } + + #[Test] + public function shouldCreateList(): void + { + $expectedArray = [ + [ + 'id' => 3, + 'label' => [ + 'name' => 'Some label', + 'color' => '#F0AD4E', + 'description' => null, + ], + 'position' => 3, + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/boards/2/lists', ['label_id' => 4]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->createList(1, 2, 4)); + } + + #[Test] + public function shouldUpdateList(): void + { + $expectedArray = [ + [ + 'id' => 3, + 'label' => [ + 'name' => 'Some label', + 'color' => '#F0AD4E', + 'description' => null, + ], + 'position' => 1, + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/5/boards/2/lists/3', ['position' => 1]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->updateList(5, 2, 3, 1)); + } + + #[Test] + public function shouldDeleteList(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/boards/2/lists/3') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->deleteList(1, 2, 3)); + } + + protected function getApiClass(): string + { + return IssueBoards::class; + } +} diff --git a/tests/Api/IssueLinksTest.php b/tests/Api/IssueLinksTest.php new file mode 100644 index 000000000..ed400ad7f --- /dev/null +++ b/tests/Api/IssueLinksTest.php @@ -0,0 +1,83 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\IssueLinks; +use PHPUnit\Framework\Attributes\Test; + +class IssueLinksTest extends TestCase +{ + /** + * {@inheritdoc} + */ + protected function getApiClass(): string + { + return IssueLinks::class; + } + + #[Test] + public function shouldGetIssueLinks(): void + { + $expectedArray = [ + ['issue_link_id' => 100], + ['issue_link_id' => 101], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/issues/10/links') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->all(1, 10)); + } + + #[Test] + public function shouldCreateIssueLink(): void + { + $expectedArray = [ + 'source_issue' => ['iid' => 10, 'project_id' => 1], + 'target_issue' => ['iid' => 20, 'project_id' => 2], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/issues/10/links', ['target_project_id' => 2, 'target_issue_iid' => 20]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->create(1, 10, 2, 20)); + } + + #[Test] + public function shouldRemoveIssueLink(): void + { + $expectedArray = [ + 'source_issue' => ['iid' => 10, 'project_id' => 1], + 'target_issue' => ['iid' => 20, 'project_id' => 2], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/issues/10/links/100') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->remove(1, 10, 100)); + } +} diff --git a/tests/Api/IssueSubscribeTest.php b/tests/Api/IssueSubscribeTest.php new file mode 100644 index 000000000..140a83130 --- /dev/null +++ b/tests/Api/IssueSubscribeTest.php @@ -0,0 +1,52 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\Issues; + +/** + * @method Issues|\PHPUnit_Framework_MockObject_MockObject getApiMock() + */ +class IssueSubscribeTest extends TestCase +{ + public function testSubscribeIssue(): void + { + $expectedValue = ''; + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/issues/2/subscribe') + ->willReturn($expectedValue); + + $this->assertEquals($expectedValue, $api->subscribe(1, 2)); + } + + public function testUnsubscribeIssue(): void + { + $expectedValue = ''; + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/issues/2/unsubscribe') + ->willReturn($expectedValue); + + $this->assertEquals($expectedValue, $api->unsubscribe(1, 2)); + } + + protected function getApiClass(): string + { + return Issues::class; + } +} diff --git a/tests/Api/IssuesStatisticsTest.php b/tests/Api/IssuesStatisticsTest.php new file mode 100644 index 000000000..1b83cdb92 --- /dev/null +++ b/tests/Api/IssuesStatisticsTest.php @@ -0,0 +1,100 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\IssuesStatistics; +use PHPUnit\Framework\Attributes\Test; + +class IssuesStatisticsTest extends TestCase +{ + #[Test] + public function shouldGetAll(): void + { + $expectedArray = []; + + $now = new \DateTime(); + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('issues_statistics', [ + 'milestone' => '', + 'labels' => '', + 'scope' => 'created-by-me', + 'author_id' => 1, + 'author_username' => '', + 'assignee_id' => 1, + 'assignee_username' => '', + 'my_reaction_emoji' => '', + 'search' => '', + 'created_after' => $now->format('c'), + 'created_before' => $now->format('c'), + 'updated_after' => $now->format('c'), + 'updated_before' => $now->format('c'), + 'confidential' => 'false', + ]) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->all([ + 'milestone' => '', + 'labels' => '', + 'scope' => 'created-by-me', + 'author_id' => 1, + 'author_username' => '', + 'assignee_id' => 1, + 'assignee_username' => '', + 'my_reaction_emoji' => '', + 'search' => '', + 'created_after' => $now, + 'created_before' => $now, + 'updated_after' => $now, + 'updated_before' => $now, + 'confidential' => false, + ])); + } + + #[Test] + public function shouldGetProject(): void + { + $expectedArray = []; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/issues_statistics', []) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->project(1, [])); + } + + #[Test] + public function shouldGetGroup(): void + { + $expectedArray = []; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/issues_statistics', []) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->group(1, [])); + } + + protected function getApiClass(): string + { + return IssuesStatistics::class; + } +} diff --git a/tests/Api/IssuesTest.php b/tests/Api/IssuesTest.php new file mode 100644 index 000000000..ce72acb41 --- /dev/null +++ b/tests/Api/IssuesTest.php @@ -0,0 +1,602 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\Issues; +use PHPUnit\Framework\Attributes\Test; + +class IssuesTest extends TestCase +{ + #[Test] + public function shouldGetAllIssues(): void + { + $expectedArray = [ + ['id' => 1, 'title' => 'An issue'], + ['id' => 2, 'title' => 'Another issue'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('issues', []) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->all()); + } + + #[Test] + public function shouldGetAllGroupIssues(): void + { + $expectedArray = [ + ['id' => 1, 'title' => 'An issue'], + ['id' => 2, 'title' => 'Another issue'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/issues', []) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->group(1)); + } + + #[Test] + public function shouldGetGroupIssuesWithPagination(): void + { + $expectedArray = [ + ['id' => 1, 'title' => 'An issue'], + ['id' => 2, 'title' => 'Another issue'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/issues', ['page' => 2, 'per_page' => 5]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->group(1, ['page' => 2, 'per_page' => 5])); + } + + #[Test] + public function shouldGetGroupIssuesWithParams(): void + { + $expectedArray = [ + ['id' => 1, 'title' => 'An issue'], + ['id' => 2, 'title' => 'Another issue'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('groups/1/issues', ['order_by' => 'created_at', 'sort' => 'desc', 'labels' => 'foo,bar', 'state' => 'opened', 'iteration_title' => 'Title', 'assignee_id' => 1]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->group(1, ['order_by' => 'created_at', 'sort' => 'desc', 'labels' => 'foo,bar', 'state' => 'opened', 'iteration_title' => 'Title', 'assignee_id' => 1])); + } + + #[Test] + public function shouldGetProjectIssuesWithPagination(): void + { + $expectedArray = [ + ['id' => 1, 'title' => 'An issue'], + ['id' => 2, 'title' => 'Another issue'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/issues', ['page' => 2, 'per_page' => 5]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->all(1, ['page' => 2, 'per_page' => 5])); + } + + #[Test] + public function shouldGetProjectIssuesWithParams(): void + { + $expectedArray = [ + ['id' => 1, 'title' => 'An issue'], + ['id' => 2, 'title' => 'Another issue'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/issues', ['order_by' => 'created_at', 'sort' => 'desc', 'labels' => 'foo,bar', 'state' => 'opened', 'iteration_id' => 1, 'assignee_id' => 2]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->all(1, ['order_by' => 'created_at', 'sort' => 'desc', 'labels' => 'foo,bar', 'state' => 'opened', 'iteration_id' => 1, 'assignee_id' => 2])); + } + + #[Test] + public function shouldShowIssue(): void + { + $expectedArray = ['id' => 2, 'title' => 'Another issue']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/issues/2') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->show(1, 2)); + } + + #[Test] + public function shouldCreateIssue(): void + { + $expectedArray = ['id' => 3, 'title' => 'A new issue']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/issues', ['title' => 'A new issue', 'labels' => 'foo,bar']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->create(1, ['title' => 'A new issue', 'labels' => 'foo,bar'])); + } + + #[Test] + public function shouldUpdateIssue(): void + { + $expectedArray = ['id' => 2, 'title' => 'A renamed issue']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/issues/2', ['title' => 'A renamed issue', 'labels' => 'foo']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->update(1, 2, ['title' => 'A renamed issue', 'labels' => 'foo'])); + } + + #[Test] + public function shouldReorderIssue(): void + { + $expectedArray = ['id' => 2, 'title' => 'A reordered issue']; + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/issues/2/reorder', ['move_after_id' => 3, 'move_before_id' => 4]) + ->willReturn($expectedArray) + ; + $this->assertEquals($expectedArray, $api->reorder(1, 2, ['move_after_id' => 3, 'move_before_id' => 4])); + } + + #[Test] + public function shouldMoveIssue(): void + { + $expectedArray = ['id' => 2, 'title' => 'A moved issue']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/issues/2/move', ['to_project_id' => 3]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->move(1, 2, 3)); + } + + #[Test] + public function shouldGetNotes(): void + { + $expectedArray = [ + ['id' => 1, 'body' => 'A note'], + ['id' => 2, 'body' => 'Another note'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/issues/2/notes') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->showNotes(1, 2)); + } + + #[Test] + public function shouldGetNote(): void + { + $expectedArray = ['id' => 3, 'body' => 'A new note']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/issues/2/notes/3') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->showNote(1, 2, 3)); + } + + #[Test] + public function shouldCreateNote(): void + { + $expectedArray = ['id' => 3, 'body' => 'A new note']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/issues/2/notes', ['body' => 'A new note']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->addNote(1, 2, 'A new note')); + } + + #[Test] + public function shouldUpdateNote(): void + { + $expectedArray = ['id' => 3, 'body' => 'An edited comment']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/issues/2/notes/3', ['body' => 'An edited comment']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->updateNote(1, 2, 3, 'An edited comment')); + } + + #[Test] + public function shouldRemoveNote(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/issues/2/notes/3') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->removeNote(1, 2, 3)); + } + + #[Test] + public function shouldGetIssueDiscussions(): void + { + $expectedArray = [ + ['id' => 'abc', 'body' => 'A discussion'], + ['id' => 'def', 'body' => 'Another discussion'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/issues/2/discussions') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->showDiscussions(1, 2)); + } + + #[Test] + public function shouldGetIssueDiscussion(): void + { + $expectedArray = ['id' => 'abc', 'body' => 'A discussion']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/issues/2/discussions/abc') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->showDiscussion(1, 2, 'abc')); + } + + #[Test] + public function shouldCreateDiscussion(): void + { + $expectedArray = ['id' => 'abc', 'body' => 'A new discussion']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/issues/2/discussions', ['body' => 'A new discussion']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->addDiscussion(1, 2, 'A new discussion')); + } + + #[Test] + public function shouldCreateDiscussionNote(): void + { + $expectedArray = ['id' => 3, 'body' => 'A new discussion note']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/issues/2/discussions/abc/notes', ['body' => 'A new discussion note']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->addDiscussionNote(1, 2, 'abc', 'A new discussion note')); + } + + #[Test] + public function shouldUpdateDiscussionNote(): void + { + $expectedArray = ['id' => 3, 'body' => 'An edited discussion note']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/issues/2/discussions/abc/notes/3', ['body' => 'An edited discussion note']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->updateDiscussionNote(1, 2, 'abc', 3, 'An edited discussion note')); + } + + #[Test] + public function shouldRemoveDiscussionNote(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/issues/2/discussions/abc/notes/3') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->removeDiscussionNote(1, 2, 'abc', 3)); + } + + #[Test] + public function shouldSetTimeEstimate(): void + { + $expectedArray = ['time_estimate' => 14400, 'total_time_spent' => 0, 'human_time_estimate' => '4h', 'human_total_time_spent' => null]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/issues/2/time_estimate', ['duration' => '4h']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->setTimeEstimate(1, 2, '4h')); + } + + #[Test] + public function shouldResetTimeEstimate(): void + { + $expectedArray = ['time_estimate' => 0, 'total_time_spent' => 0, 'human_time_estimate' => null, 'human_total_time_spent' => null]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/issues/2/reset_time_estimate') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->resetTimeEstimate(1, 2)); + } + + #[Test] + public function shouldAddSpentTime(): void + { + $expectedArray = ['time_estimate' => 0, 'total_time_spent' => 14400, 'human_time_estimate' => null, 'human_total_time_spent' => '4h']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/issues/2/add_spent_time', ['duration' => '4h']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->addSpentTime(1, 2, '4h')); + } + + #[Test] + public function shouldResetSpentTime(): void + { + $expectedArray = ['time_estimate' => 0, 'total_time_spent' => 0, 'human_time_estimate' => null, 'human_total_time_spent' => null]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/issues/2/reset_spent_time') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->resetSpentTime(1, 2)); + } + + #[Test] + public function shouldGetIssueTimeStats(): void + { + $expectedArray = ['time_estimate' => 14400, 'total_time_spent' => 5400, 'human_time_estimate' => '4h', 'human_total_time_spent' => '1h 30m']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/issues/2/time_stats') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->getTimeStats(1, 2)); + } + + #[Test] + public function shouldIssueAwardEmoji(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'sparkles'], + ['id' => 2, 'name' => 'heart_eyes'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/issues/2/award_emoji') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->awardEmoji(1, 2)); + } + + #[Test] + public function shouldRevokeAwardEmoji(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/issues/2/award_emoji/3') + ->willReturn($expectedBool); + + $this->assertEquals(true, $api->removeAwardEmoji(1, 2, 3)); + } + + #[Test] + public function shouldGetIssueClosedByMergeRequests(): void + { + $expectedArray = [ + ['id' => 1, 'iid' => '1111', 'title' => 'Just saving the world'], + ['id' => 2, 'iid' => '1112', 'title' => 'Adding new feature to get merge requests that close an issue'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/issues/2/closed_by') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->closedByMergeRequests(1, 2)); + } + + #[Test] + public function shouldGetIssueRelatedMergeRequests(): void + { + $expectedArray = [ + ['id' => 1, 'iid' => '1111', 'title' => 'Just saving the world'], + ['id' => 2, 'iid' => '1112', 'title' => 'Adding new feature to get merge requests that close an issue'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/issues/2/related_merge_requests') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->relatedMergeRequests(1, 2)); + } + + #[Test] + public function shouldGetProjectIssuesByAssignee(): void + { + $expectedArray = [ + ['id' => 1, 'title' => 'An issue'], + ['id' => 2, 'title' => 'Another issue'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/issues', ['assignee_id' => 1]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->all(1, ['assignee_id' => 1])); + } + + #[Test] + public function shouldGetIssueParticipants(): void + { + $expectedArray = [ + [ + 'id' => 1, + 'name' => 'John Doe1', + 'username' => 'user1', + 'state' => 'active', + 'avatar_url' => 'http://www.gravatar.com/avatar/c922747a93b40d1ea88262bf1aebee62?s=80&d=identicon', + 'web_url' => 'http://localhost/user1', + ], + [ + 'id' => 5, + 'name' => 'John Doe5', + 'username' => 'user5', + 'state' => 'active', + 'avatar_url' => 'http://www.gravatar.com/avatar/4aea8cf834ed91844a2da4ff7ae6b491?s=80&d=identicon', + 'web_url' => 'http://localhost/user5', + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/issues/2/participants') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->showParticipants(1, 2)); + } + + #[Test] + public function shouldGetIssueResourceLabelEvents(): void + { + $expectedArray = [ + ['id' => 1, 'resource_type' => 'Issue', 'action' => 'add'], + ['id' => 2, 'resource_type' => 'Issue', 'action' => 'add'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/issues/2/resource_label_events') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->showResourceLabelEvents(1, 2)); + } + + #[Test] + public function shouldGetIssueResourceLabelEvent(): void + { + $expectedArray = ['id' => 1, 'resource_type' => 'Issue', 'action' => 'add']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/issues/2/resource_label_events/3') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->showResourceLabelEvent(1, 2, 3)); + } + + protected function getApiClass(): string + { + return Issues::class; + } +} diff --git a/tests/Api/JobsTest.php b/tests/Api/JobsTest.php new file mode 100644 index 000000000..6753353dd --- /dev/null +++ b/tests/Api/JobsTest.php @@ -0,0 +1,272 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\Jobs; +use GuzzleHttp\Psr7\Response; +use PHPUnit\Framework\Attributes\Test; + +class JobsTest extends TestCase +{ + #[Test] + public function shouldGetAllJobs(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'A job'], + ['id' => 2, 'name' => 'Another job'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/jobs', [ + 'scope' => ['pending'], + ]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->all(1, ['scope' => Jobs::SCOPE_PENDING])); + } + + #[Test] + public function shouldGetPipelineJobs(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'A job'], + ['id' => 2, 'name' => 'Another job'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/pipelines/2/jobs', [ + 'scope' => ['pending', 'running'], + ]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->pipelineJobs(1, 2, ['scope' => [Jobs::SCOPE_PENDING, Jobs::SCOPE_RUNNING]])); + } + + #[Test] + public function shouldGetPipelineJobsIncludingRetried(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'A job'], + ['id' => 2, 'name' => 'Another job'], + ['id' => 3, 'name' => 'A job'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/pipelines/2/jobs', [ + 'scope' => ['pending', 'running'], + 'include_retried' => true, + ]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->pipelineJobs(1, 2, ['scope' => [Jobs::SCOPE_PENDING, Jobs::SCOPE_RUNNING], 'include_retried' => true])); + } + + #[Test] + public function shouldGetPipelineBridges(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'A bridge job'], + ['id' => 2, 'name' => 'Another bridge job'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/pipelines/2/bridges', [ + 'scope' => ['pending', 'running'], + ]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->pipelineBridges(1, 2, ['scope' => [Jobs::SCOPE_PENDING, Jobs::SCOPE_RUNNING]])); + } + + #[Test] + public function shouldGetJob(): void + { + $expectedArray = ['id' => 3, 'name' => 'A job']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/jobs/3') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->show(1, 3)); + } + + #[Test] + public function shouldGetArtifacts(): void + { + $returnedStream = new Response(200, [], 'foobar'); + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('getAsResponse') + ->with('projects/1/jobs/3/artifacts') + ->willReturn($returnedStream); + + $this->assertEquals('foobar', $api->artifacts(1, 3)->getContents()); + } + + #[Test] + public function shouldGetArtifactsByJobId(): void + { + $returnedStream = new Response(200, [], 'foobar'); + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('getAsResponse') + ->with('projects/1/jobs/3/artifacts/artifact_path') + ->willReturn($returnedStream); + + $this->assertEquals('foobar', $api->artifactByJobId(1, 3, 'artifact_path')->getContents()); + } + + #[Test] + public function shouldGetArtifactsByRefName(): void + { + $returnedStream = new Response(200, [], 'foobar'); + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('getAsResponse') + ->with('projects/1/jobs/artifacts/master/download', [ + 'job' => 'job name', + ]) + ->willReturn($returnedStream); + + $this->assertEquals('foobar', $api->artifactsByRefName(1, 'master', 'job name')->getContents()); + } + + #[Test] + public function shouldGetArtifactByRefName(): void + { + $returnedStream = new Response(200, [], 'foobar'); + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('getAsResponse') + ->with('projects/1/jobs/artifacts/master/raw/artifact_path', [ + 'job' => 'job name', + ]) + ->willReturn($returnedStream); + + $this->assertEquals('foobar', $api->artifactByRefName(1, 'master', 'job name', 'artifact_path')->getContents()); + } + + #[Test] + public function shouldGetTrace(): void + { + $expectedString = 'some trace'; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/jobs/3/trace') + ->willReturn($expectedString); + + $this->assertEquals($expectedString, $api->trace(1, 3)); + } + + #[Test] + public function shouldCancel(): void + { + $expectedArray = ['id' => 3, 'name' => 'A job']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/jobs/3/cancel') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->cancel(1, 3)); + } + + #[Test] + public function shouldRetry(): void + { + $expectedArray = ['id' => 3, 'name' => 'A job']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/jobs/3/retry') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->retry(1, 3)); + } + + #[Test] + public function shouldErase(): void + { + $expectedArray = ['id' => 3, 'name' => 'A job']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/jobs/3/erase') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->erase(1, 3)); + } + + #[Test] + public function shouldKeepArtifacts(): void + { + $expectedArray = ['id' => 3, 'name' => 'A job']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/jobs/3/artifacts/keep') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->keepArtifacts(1, 3)); + } + + #[Test] + public function shouldPlay(): void + { + $expectedArray = ['id' => 3, 'name' => 'A job']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/jobs/3/play') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->play(1, 3)); + } + + protected function getApiClass(): string + { + return Jobs::class; + } +} diff --git a/tests/Api/KeysTest.php b/tests/Api/KeysTest.php new file mode 100644 index 000000000..08c7f2680 --- /dev/null +++ b/tests/Api/KeysTest.php @@ -0,0 +1,39 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\Keys; +use PHPUnit\Framework\Attributes\Test; + +class KeysTest extends TestCase +{ + #[Test] + public function shouldShowKey(): void + { + $expectedArray = ['id' => 1, 'title' => 'A key', 'key' => 'ssh-rsa key', 'created_at' => '2016-01-01T01:00:00.000Z']; + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('keys/1') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->show(1)); + } + + protected function getApiClass(): string + { + return Keys::class; + } +} diff --git a/tests/Api/MergeRequestsTest.php b/tests/Api/MergeRequestsTest.php new file mode 100644 index 000000000..c0803014a --- /dev/null +++ b/tests/Api/MergeRequestsTest.php @@ -0,0 +1,825 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\MergeRequests; +use PHPUnit\Framework\Attributes\Test; + +class MergeRequestsTest extends TestCase +{ + #[Test] + public function shouldGetAll(): void + { + $expectedArray = $this->getMultipleMergeRequestsData(); + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/merge_requests', []) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->all(1)); + } + + #[Test] + public function shouldGetAllWithNoProject(): void + { + $expectedArray = $this->getMultipleMergeRequestsData(); + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('merge_requests', []) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->all()); + } + + #[Test] + public function shouldGetAllWithParams(): void + { + $expectedArray = $this->getMultipleMergeRequestsData(); + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/merge_requests', [ + 'page' => 2, + 'per_page' => 5, + 'labels' => 'label1,label2,label3', + 'milestone' => 'milestone1', + 'order_by' => 'updated_at', + 'state' => 'all', + 'sort' => 'desc', + 'scope' => 'all', + 'author_id' => 1, + 'assignee_id' => 1, + 'source_branch' => 'develop', + 'target_branch' => 'master', + 'with_merge_status_recheck' => true, + 'approved_by_ids' => [1], + ]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->all(1, [ + 'page' => 2, + 'per_page' => 5, + 'labels' => 'label1,label2,label3', + 'milestone' => 'milestone1', + 'order_by' => 'updated_at', + 'state' => 'all', + 'sort' => 'desc', + 'scope' => 'all', + 'author_id' => 1, + 'assignee_id' => 1, + 'source_branch' => 'develop', + 'target_branch' => 'master', + 'with_merge_status_recheck' => true, + 'approved_by_ids' => [1], + ])); + } + + #[Test] + public function shouldGetAllWithDateTimeParams(): void + { + $expectedArray = $this->getMultipleMergeRequestsData(); + + $createdAfter = new \DateTime('2018-01-01 00:00:00'); + $createdBefore = new \DateTime('2018-01-31 12:00:00.123+03:00'); + + $expectedWithArray = [ + 'created_after' => '2018-01-01T00:00:00.000Z', + 'created_before' => '2018-01-31T09:00:00.123Z', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/merge_requests', $expectedWithArray) + ->willReturn($expectedArray) + ; + + $this->assertEquals( + $expectedArray, + $api->all(1, ['created_after' => $createdAfter, 'created_before' => $createdBefore]) + ); + } + + #[Test] + public function shouldShowMergeRequest(): void + { + $expectedArray = ['id' => 2, 'name' => 'A merge request']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/merge_requests/2') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->show(1, 2)); + } + + #[Test] + public function shouldShowMergeRequestWithOptionalParameters(): void + { + $expectedArray = [ + 'id' => 2, + 'name' => 'A merge request', + 'diverged_commits_count' => 0, + 'rebase_in_progress' => false, + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/merge_requests/2', ['include_diverged_commits_count' => true, 'include_rebase_in_progress' => true]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->show(1, 2, [ + 'include_diverged_commits_count' => true, + 'include_rebase_in_progress' => true, + ])); + } + + #[Test] + public function shouldCreateMergeRequestWithoutOptionalParams(): void + { + $expectedArray = ['id' => 3, 'title' => 'Merge Request']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/merge_requests', [ + 'title' => 'Merge Request', + 'target_branch' => 'master', + 'source_branch' => 'develop', + ]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->create(1, 'develop', 'master', 'Merge Request')); + } + + #[Test] + public function shouldCreateMergeRequestWithOptionalParams(): void + { + $expectedArray = ['id' => 3, 'title' => 'Merge Request']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/merge_requests', [ + 'title' => 'Merge Request', + 'target_branch' => 'master', + 'source_branch' => 'develop', + 'assignee_id' => 6, + 'target_project_id' => 20, + 'description' => 'Some changes', + 'remove_source_branch' => true, + ]) + ->willReturn($expectedArray) + ; + + $this->assertEquals( + $expectedArray, + $api->create( + 1, + 'develop', + 'master', + 'Merge Request', + ['assignee_id' => 6, 'target_project_id' => 20, 'description' => 'Some changes', 'remove_source_branch' => true] + ) + ); + } + + #[Test] + public function shouldUpdateMergeRequest(): void + { + $expectedArray = ['id' => 2, 'title' => 'Updated title']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/merge_requests/2', ['title' => 'Updated title', 'description' => 'No so many changes now', 'state_event' => 'close']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->update(1, 2, [ + 'title' => 'Updated title', + 'description' => 'No so many changes now', + 'state_event' => 'close', + ])); + } + + #[Test] + public function shouldMergeMergeRequest(): void + { + $expectedArray = ['id' => 2, 'title' => 'Updated title']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/merge_requests/2/merge', ['merge_commit_message' => 'Accepted']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->merge(1, 2, ['merge_commit_message' => 'Accepted'])); + } + + #[Test] + public function shouldGetNotes(): void + { + $expectedArray = [ + ['id' => 1, 'body' => 'A note'], + ['id' => 2, 'body' => 'Another note'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/merge_requests/2/notes') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->showNotes(1, 2)); + } + + #[Test] + public function shouldGetNote(): void + { + $expectedArray = ['id' => 3, 'body' => 'A new note']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/merge_requests/2/notes/3') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->showNote(1, 2, 3)); + } + + #[Test] + public function shouldCreateNote(): void + { + $expectedArray = ['id' => 3, 'body' => 'A new note']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/merge_requests/2/notes', ['body' => 'A new note']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->addNote(1, 2, 'A new note')); + } + + #[Test] + public function shouldUpdateNote(): void + { + $expectedArray = ['id' => 3, 'body' => 'An edited comment']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/merge_requests/2/notes/3', ['body' => 'An edited comment']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->updateNote(1, 2, 3, 'An edited comment')); + } + + #[Test] + public function shouldRemoveNote(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/merge_requests/2/notes/3') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->removeNote(1, 2, 3)); + } + + #[Test] + public function shouldGetMergeRequestParticipants(): void + { + $expectedArray = [ + [ + 'id' => 1, + 'name' => 'John Doe1', + 'username' => 'user1', + 'state' => 'active', + 'avatar_url' => 'http://www.gravatar.com/avatar/c922747a93b40d1ea88262bf1aebee62?s=80&d=identicon', + 'web_url' => 'http://localhost/user1', + ], + [ + 'id' => 5, + 'name' => 'John Doe5', + 'username' => 'user5', + 'state' => 'active', + 'avatar_url' => 'http://www.gravatar.com/avatar/4aea8cf834ed91844a2da4ff7ae6b491?s=80&d=identicon', + 'web_url' => 'http://localhost/user5', + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/merge_requests/2/participants') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->showParticipants(1, 2)); + } + + #[Test] + public function shouldGetMergeRequestChanges(): void + { + $expectedArray = ['id' => 1, 'title' => 'A merge request']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/merge_requests/2/changes') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->changes(1, 2)); + } + + #[Test] + public function shouldGetMergeRequestDiscussions(): void + { + $expectedArray = [ + ['id' => 'abc', 'body' => 'A discussion'], + ['id' => 'def', 'body' => 'Another discussion'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/merge_requests/2/discussions') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->showDiscussions(1, 2)); + } + + #[Test] + public function shouldGetMergeRequestDiscussion(): void + { + $expectedArray = ['id' => 'abc', 'body' => 'A discussion']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/merge_requests/2/discussions/abc') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->showDiscussion(1, 2, 'abc')); + } + + #[Test] + public function shouldCreateDiscussion(): void + { + $expectedArray = ['id' => 'abc', 'body' => 'A new discussion']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/merge_requests/2/discussions', ['body' => 'A new discussion']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->addDiscussion(1, 2, ['body' => 'A new discussion'])); + } + + #[Test] + public function shouldResolveDiscussion(): void + { + $expectedArray = ['id' => 'abc', 'resolved' => true]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/merge_requests/2/discussions/abc', ['resolved' => true]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->resolveDiscussion(1, 2, 'abc', true)); + } + + #[Test] + public function shouldUnresolveDiscussion(): void + { + $expectedArray = ['id' => 'abc', 'resolved' => false]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/merge_requests/2/discussions/abc', ['resolved' => false]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->resolveDiscussion(1, 2, 'abc', false)); + } + + #[Test] + public function shouldCreateDiscussionNote(): void + { + $expectedArray = ['id' => 3, 'body' => 'A new discussion note']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/merge_requests/2/discussions/abc/notes', ['body' => 'A new discussion note']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->addDiscussionNote(1, 2, 'abc', 'A new discussion note')); + } + + #[Test] + public function shouldUpdateDiscussionNote(): void + { + $expectedArray = ['id' => 3, 'body' => 'An edited discussion note']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/merge_requests/2/discussions/abc/notes/3', ['body' => 'An edited discussion note']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->updateDiscussionNote(1, 2, 'abc', 3, ['body' => 'An edited discussion note'])); + } + + #[Test] + public function shouldRemoveDiscussionNote(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/merge_requests/2/discussions/abc/notes/3') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->removeDiscussionNote(1, 2, 'abc', 3)); + } + + #[Test] + public function shouldGetIssuesClosedByMergeRequest(): void + { + $expectedArray = ['id' => 1, 'title' => 'A merge request']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/merge_requests/2/closes_issues') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->closesIssues(1, 2)); + } + + #[Test] + public function shouldGetMergeRequestByIid(): void + { + $expectedArray = ['id' => 1, 'title' => 'A merge request']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/merge_requests', ['iids' => [2]]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->all(1, ['iids' => [2]])); + } + + #[Test] + public function shouldApproveMergeRequest(): void + { + $expectedArray = ['id' => 1, 'title' => 'Approvals API']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/merge_requests/2/approve') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->approve(1, 2)); + } + + #[Test] + public function shouldUnApproveMergeRequest(): void + { + $expectedArray = ['id' => 1, 'title' => 'Approvals API']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/merge_requests/2/unapprove') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->unapprove(1, 2)); + } + + #[Test] + public function shouldGetMergeRequestApprovals(): void + { + $expectedArray = ['id' => 1, 'title' => 'Approvals API']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/merge_requests', ['iids' => [2]]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->all(1, ['iids' => [2]])); + } + + #[Test] + public function shouldIssueMergeRequestAwardEmoji(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'sparkles'], + ['id' => 2, 'name' => 'heart_eyes'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/merge_requests/2/award_emoji') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->awardEmoji(1, 2)); + } + + #[Test] + public function shouldRevokeMergeRequestAwardEmoji(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/merge_requests/2/award_emoji/3') + ->willReturn($expectedBool); + + $this->assertEquals(true, $api->removeAwardEmoji(1, 2, 3)); + } + + #[Test] + public function shoudGetApprovalState(): void + { + $expectedArray = [ + 'approval_rules_overwritten' => 1, + 'rules' => [], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/merge_requests/2/approval_state') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->approvalState(1, 2)); + } + + #[Test] + public function shoudGetLevelRules(): void + { + $expectedArray = [ + [ + 'id' => 1, + 'name' => 'Foo', + 'rule_type' => 'regular', + 'eligible_approvers' => [], + 'approvals_required' => 1, + 'users' => [], + 'groups' => [], + 'contains_hidden_groups' => null, + 'section' => null, + 'source_rule' => null, + 'overridden' => null, + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/merge_requests/2/approval_rules') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->levelRules(1, 2)); + } + + #[Test] + public function shoudCreateLevelRuleWithoutOptionalParameters(): void + { + $expectedArray = [ + 'id' => 20892835, + 'name' => 'Foo', + 'rule_type' => 'regular', + 'eligible_approvers' => [], + 'approvals_required' => 3, + 'users' => [], + 'groups' => [], + 'contains_hidden_groups' => null, + 'section' => null, + 'source_rule' => null, + 'overridden' => null, + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with( + 'projects/1/merge_requests/2/approval_rules', + [ + 'name' => 'Foo', + 'approvals_required' => 3, + ] + ) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->createLevelRule(1, 2, 'Foo', 3)); + } + + #[Test] + public function shoudCreateLevelRuleWithOptionalParameters(): void + { + $expectedArray = [ + 'id' => 20892835, + 'name' => 'Foo', + 'rule_type' => 'regular', + 'eligible_approvers' => [], + 'approvals_required' => 3, + 'users' => [1951878], + 'groups' => [104121], + 'contains_hidden_groups' => null, + 'section' => null, + 'source_rule' => null, + 'overridden' => null, + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with( + 'projects/1/merge_requests/2/approval_rules', + [ + 'name' => 'Foo', + 'approvals_required' => 3, + 'user_ids' => [1951878], + 'group_ids' => [104121], + ] + ) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->createLevelRule(1, 2, 'Foo', 3, [ + 'user_ids' => [1951878], + 'group_ids' => [104121], + ])); + } + + #[Test] + public function shoudUpdateLevelRuleWithoutOptionalParameters(): void + { + $expectedArray = [ + 'id' => 20892835, + 'name' => 'Foo', + 'rule_type' => 'regular', + 'eligible_approvers' => [], + 'approvals_required' => 3, + 'users' => [], + 'groups' => [], + 'contains_hidden_groups' => null, + 'section' => null, + 'source_rule' => null, + 'overridden' => null, + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with( + 'projects/1/merge_requests/2/approval_rules/20892835', + [ + 'name' => 'Foo', + 'approvals_required' => 3, + ] + ) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->updateLevelRule(1, 2, 20892835, 'Foo', 3)); + } + + #[Test] + public function shoudUpdateLevelRuleWithOptionalParameters(): void + { + $expectedArray = [ + 'id' => 20892835, + 'name' => 'Foo', + 'rule_type' => 'regular', + 'eligible_approvers' => [], + 'approvals_required' => 3, + 'users' => [1951878], + 'groups' => [104121], + 'contains_hidden_groups' => null, + 'section' => null, + 'source_rule' => null, + 'overridden' => null, + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with( + 'projects/1/merge_requests/2/approval_rules/20892835', + [ + 'name' => 'Foo', + 'approvals_required' => 3, + 'user_ids' => [1951878], + 'group_ids' => [104121], + ] + ) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->updateLevelRule(1, 2, 20892835, 'Foo', 3, [ + 'user_ids' => [1951878], + 'group_ids' => [104121], + ])); + } + + #[Test] + public function shoudDeleteLevelRule(): void + { + $expectedValue = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/merge_requests/2/approval_rules/3') + ->willReturn($expectedValue); + + $this->assertEquals($expectedValue, $api->deleteLevelRule(1, 2, 3)); + } + + protected function getMultipleMergeRequestsData(): array + { + return [ + ['id' => 1, 'title' => 'A merge request'], + ['id' => 2, 'title' => 'Another merge request'], + ]; + } + + protected function getApiClass(): string + { + return MergeRequests::class; + } + + #[Test] + public function shouldRebaseMergeRequest(): void + { + $expectedArray = ['rebase_in_progress' => true]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/merge_requests/2/rebase', ['skip_ci' => true]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->rebase(1, 2, [ + 'skip_ci' => true, + ])); + } +} diff --git a/tests/Api/MilestonesTest.php b/tests/Api/MilestonesTest.php new file mode 100644 index 000000000..bb9104be1 --- /dev/null +++ b/tests/Api/MilestonesTest.php @@ -0,0 +1,139 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\Milestones; +use PHPUnit\Framework\Attributes\Test; + +class MilestonesTest extends TestCase +{ + #[Test] + public function shouldGetAllMilestones(): void + { + $expectedArray = [ + ['id' => 1, 'title' => 'A milestone'], + ['id' => 2, 'title' => 'Another milestone'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/milestones') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->all(1)); + } + + #[Test] + public function shouldShowMilestone(): void + { + $expectedArray = ['id' => 1, 'name' => 'A milestone']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/milestones/2') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->show(1, 2)); + } + + #[Test] + public function shouldCreateMilestone(): void + { + $expectedArray = ['id' => 3, 'title' => 'A new milestone']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/milestones', ['description' => 'Some text', 'title' => 'A new milestone']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->create(1, ['description' => 'Some text', 'title' => 'A new milestone'])); + } + + #[Test] + public function shouldUpdateMilestone(): void + { + $expectedArray = ['id' => 3, 'title' => 'Updated milestone']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/milestones/3', ['title' => 'Updated milestone', 'due_date' => '2015-04-01', 'state_event' => 'close']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->update(1, 3, ['title' => 'Updated milestone', 'due_date' => '2015-04-01', 'state_event' => 'close'])); + } + + #[Test] + public function shouldRemoveMilestone(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/milestones/2') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->remove(1, 2)); + } + + #[Test] + public function shouldGetMilestonesIssues(): void + { + $expectedArray = [ + ['id' => 1, 'title' => 'An issue'], + ['id' => 2, 'title' => 'Another issue'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/milestones/3/issues') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->issues(1, 3)); + } + + #[Test] + public function shouldGetMilestonesMergeRequests(): void + { + $expectedArray = [ + ['id' => 1, 'title' => 'A merge request'], + ['id' => 2, 'title' => 'Another merge request'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/milestones/3/merge_requests') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->mergeRequests(1, 3)); + } + + protected function getApiClass(): string + { + return Milestones::class; + } +} diff --git a/tests/Api/PackagesTest.php b/tests/Api/PackagesTest.php new file mode 100644 index 000000000..96553b6df --- /dev/null +++ b/tests/Api/PackagesTest.php @@ -0,0 +1,116 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\Packages; +use PHPUnit\Framework\Attributes\Test; + +final class PackagesTest extends TestCase +{ + #[Test] + public function shouldGetAllPackages(): void + { + $expectedArray = [ + [ + 'id' => 3, + 'name' => 'Hello/0.1@mycompany/stable', + 'conan_package_name' => 'Hello', + 'version' => '0.1', + 'package_type' => 'conan', + '_links' => [ + 'web_path' => '/foo/bar/-/packages/3', + 'delete_api_path' => 'https://gitlab.example.com/api/v4/projects/1/packages/3', + ], + 'created_at' => '2029-12-16T20:33:34.316Z', + 'tags' => [], + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/packages') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->all(1)); + } + + #[Test] + public function shouldShowPackage(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'com/mycompany/my-app', 'version' => '1.0-SNAPSHOT', 'package_type' => 'maven'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/packages/1') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->show(1, 1)); + } + + #[Test] + public function shouldGetAllPackageFiles(): void + { + $expectedArray = [ + ['id' => 25, 'file_name' => 'my-app-1.5-20181107.152550-1.jar', 'size' => 2421], + ['id' => 26, 'file_name' => 'my-app-1.5-20181107.152550-1.pom', 'size' => 1122], + ['id' => 27, 'file_name' => 'maven-metadata.xml', 'size' => 767], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/packages/1/package_files') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->allFiles(1, 1)); + } + + #[Test] + public function shouldRemovePackage(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/packages/1') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->remove(1, 1)); + } + + #[Test] + public function shouldRemovePackageFile(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/packages/1/package_files/25') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->removeFile(1, 1, 25)); + } + + protected function getApiClass(): string + { + return Packages::class; + } +} diff --git a/tests/Api/ProjectNamespacesTest.php b/tests/Api/ProjectNamespacesTest.php new file mode 100644 index 000000000..7ac81c59b --- /dev/null +++ b/tests/Api/ProjectNamespacesTest.php @@ -0,0 +1,59 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\ProjectNamespaces; +use PHPUnit\Framework\Attributes\Test; + +class ProjectNamespacesTest extends TestCase +{ + #[Test] + public function shouldGetAllNamespaces(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'bespokes'], + ['id' => 2, 'name' => 'internal'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('namespaces', []) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->all()); + } + + #[Test] + public function shouldShowNamespace(): void + { + $expectedArray = ['id' => 1, 'name' => 'internal']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('namespaces/1') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->show(1)); + } + + protected function getApiClass(): string + { + return ProjectNamespaces::class; + } +} diff --git a/tests/Api/ProjectsTest.php b/tests/Api/ProjectsTest.php new file mode 100644 index 000000000..62b5dfcc1 --- /dev/null +++ b/tests/Api/ProjectsTest.php @@ -0,0 +1,2768 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use DateTime; +use Gitlab\Api\Projects; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\MockObject\MockObject; + +class ProjectsTest extends TestCase +{ + #[Test] + public function shouldGetAllProjects(): void + { + $expectedArray = $this->getMultipleProjectsData(); + + $api = $this->getMultipleProjectsRequestMock('projects', $expectedArray); + + $this->assertEquals($expectedArray, $api->all()); + } + + #[Test] + public function shouldGetAllProjectsSortedByName(): void + { + $expectedArray = $this->getMultipleProjectsData(); + + $api = $this->getMultipleProjectsRequestMock( + 'projects', + $expectedArray, + ['page' => 1, 'per_page' => 5, 'order_by' => 'name', 'sort' => 'asc'] + ); + + $this->assertEquals( + $expectedArray, + $api->all(['page' => 1, 'per_page' => 5, 'order_by' => 'name', 'sort' => 'asc']) + ); + } + + #[Test] + public function shouldNotNeedPaginationWhenGettingProjects(): void + { + $expectedArray = $this->getMultipleProjectsData(); + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->all()); + } + + #[Test] + public function shouldGetAccessibleProjects(): void + { + $expectedArray = $this->getMultipleProjectsData(); + + $api = $this->getMultipleProjectsRequestMock('projects', $expectedArray); + + $this->assertEquals($expectedArray, $api->all()); + } + + #[Test] + public function shouldGetOwnedProjects(): void + { + $expectedArray = $this->getMultipleProjectsData(); + + $api = $this->getMultipleProjectsRequestMock('projects', $expectedArray, ['owned' => 'true']); + + $this->assertEquals($expectedArray, $api->all(['owned' => true])); + } + + #[Test] + public function shouldGetNotArchivedProjects(): void + { + $expectedArray = $this->getMultipleProjectsData(); + + $api = $this->getMultipleProjectsRequestMock('projects', $expectedArray, ['archived' => 'false']); + + $this->assertEquals($expectedArray, $api->all(['archived' => false])); + } + + #[Test] + #[DataProvider('possibleAccessLevels')] + public function shouldGetProjectsWithMinimumAccessLevel($level): void + { + $expectedArray = $this->getMultipleProjectsData(); + + $api = $this->getMultipleProjectsRequestMock('projects', $expectedArray, ['min_access_level' => $level]); + + $this->assertEquals($expectedArray, $api->all(['min_access_level' => $level])); + } + + #[Test] + public function shouldSearchProjects(): void + { + $expectedArray = $this->getMultipleProjectsData(); + + $api = $this->getMultipleProjectsRequestMock('projects', $expectedArray, ['search' => 'a project']); + $this->assertEquals($expectedArray, $api->all(['search' => 'a project'])); + } + + #[Test] + public function shouldSearchProjectsWithNamespace(): void + { + $expectedArray = $this->getMultipleProjectsDataWithNamespace(); + + $api = $this->getMultipleProjectsRequestMock('projects', $expectedArray, ['search' => 'a_project', 'search_namespaces' => 'true']); + $this->assertEquals($expectedArray, $api->all(['search' => 'a_project', 'search_namespaces' => true])); + } + + #[Test] + public function shouldGetProjectsAfterId(): void + { + $expectedArray = $this->getMultipleProjectsData(); + + $api = $this->getMultipleProjectsRequestMock('projects', $expectedArray, ['id_after' => 0]); + + $this->assertEquals($expectedArray, $api->all(['id_after' => 0])); + } + + #[Test] + public function shouldGetProjectsWithLastActivityAfter(): void + { + $unixEpochDateTime = new DateTime('@0'); + + $expectedArray = $this->getMultipleProjectsData(); + + $api = $this->getMultipleProjectsRequestMock('projects', $expectedArray, ['last_activity_after' => $unixEpochDateTime->format('c')]); + + $this->assertEquals($expectedArray, $api->all(['last_activity_after' => $unixEpochDateTime])); + } + + #[Test] + public function shouldGetProjectsWithLastActivityBefore(): void + { + $now = new DateTime(); + + $expectedArray = $this->getMultipleProjectsData(); + + $api = $this->getMultipleProjectsRequestMock('projects', $expectedArray, ['last_activity_before' => $now->format('c')]); + + $this->assertEquals($expectedArray, $api->all(['last_activity_before' => $now])); + } + + #[Test] + public function shouldGetProjectsWithoutFailedRepositoryChecksum(): void + { + $expectedArray = $this->getMultipleProjectsData(); + + $api = $this->getMultipleProjectsRequestMock('projects', $expectedArray, ['repository_checksum_failed' => 'false']); + + $this->assertEquals($expectedArray, $api->all(['repository_checksum_failed' => false])); + } + + #[Test] + public function shouldGetProjectsWithDefaultRepositoryStorage(): void + { + $expectedArray = $this->getMultipleProjectsData(); + + $api = $this->getMultipleProjectsRequestMock('projects', $expectedArray, ['repository_storage' => 'default']); + + $this->assertEquals($expectedArray, $api->all(['repository_storage' => 'default'])); + } + + #[Test] + public function shouldGetStarredProjects(): void + { + $expectedArray = $this->getMultipleProjectsData(); + + $api = $this->getMultipleProjectsRequestMock('projects', $expectedArray, ['starred' => 'true']); + + $this->assertEquals($expectedArray, $api->all(['starred' => true])); + } + + #[Test] + public function shouldGetProjectsWithoutFailedWikiChecksum(): void + { + $expectedArray = $this->getMultipleProjectsData(); + + $api = $this->getMultipleProjectsRequestMock('projects', $expectedArray, ['wiki_checksum_failed' => 'false']); + + $this->assertEquals($expectedArray, $api->all(['wiki_checksum_failed' => false])); + } + + #[Test] + public function shouldGetProjectsWithCustomAttributes(): void + { + $expectedArray = $this->getMultipleProjectsData(); + + $api = $this->getMultipleProjectsRequestMock('projects', $expectedArray, ['with_custom_attributes' => 'true']); + + $this->assertEquals($expectedArray, $api->all(['with_custom_attributes' => true])); + } + + #[Test] + public function shouldGetProjectsWithPhpProgrammingLanguage(): void + { + $expectedArray = $this->getMultipleProjectsData(); + + $api = $this->getMultipleProjectsRequestMock('projects', $expectedArray, ['with_programming_language' => 'php']); + + $this->assertEquals($expectedArray, $api->all(['with_programming_language' => 'php'])); + } + + #[Test] + public function shouldShowProject(): void + { + $expectedArray = ['id' => 1, 'name' => 'Project Name']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->show(1)); + } + + #[Test] + public function shouldShowProjectWithStatistics(): void + { + $expectedArray = [ + 'id' => 1, + 'name' => 'Project Name', + 'statistics' => [ + 'commit_count' => 37, + 'storage_size' => 1038090, + 'repository_size' => 1038090, + 'lfs_objects_size' => 0, + 'job_artifacts_size' => 0, + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1', ['statistics' => true]) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->show(1, ['statistics' => true])); + } + + #[Test] + public function shouldCreateProject(): void + { + $expectedArray = ['id' => 1, 'name' => 'Project Name']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects', ['name' => 'Project Name', 'issues_enabled' => true]) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->create('Project Name', [ + 'issues_enabled' => true, + ])); + } + + #[Test] + public function shouldUpdateProject(): void + { + $expectedArray = ['id' => 1, 'name' => 'Updated Name']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1', ['name' => 'Updated Name', 'issues_enabled' => true]) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->update(1, [ + 'name' => 'Updated Name', + 'issues_enabled' => true, + ])); + } + + #[Test] + public function shouldArchiveProject(): void + { + $expectedArray = ['id' => 1, 'archived' => true]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/archive') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->archive(1)); + } + + #[Test] + public function shouldUnarchiveProject(): void + { + $expectedArray = ['id' => 1, 'archived' => false]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/unarchive') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->unarchive(1)); + } + + #[Test] + public function shouldCreateProjectForUser(): void + { + $expectedArray = ['id' => 1, 'name' => 'Project Name']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/user/1', ['name' => 'Project Name', 'issues_enabled' => true]) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->createForUser(1, 'Project Name', [ + 'issues_enabled' => true, + ])); + } + + #[Test] + public function shouldRemoveProject(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->remove(1)); + } + + #[Test] + public function shouldGetPipelines(): void + { + $expectedArray = [ + ['id' => 1, 'status' => 'success', 'ref' => 'new-pipeline'], + ['id' => 2, 'status' => 'failed', 'ref' => 'new-pipeline'], + ['id' => 3, 'status' => 'pending', 'ref' => 'test-pipeline'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/pipelines') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->pipelines(1)); + } + + #[Test] + public function shouldGetTriggers(): void + { + $expectedArray = [ + ['id' => 1, 'description' => 'foo', 'token' => '6d056f63e50fe6f8c5f8f4aa10edb7'], + ['id' => 2, 'description' => 'bar', 'token' => '7bde01aa4f8f5c8f6ef05e36f650d6'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/triggers') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->triggers(1)); + } + + #[Test] + public function shouldGetTrigger(): void + { + $expectedArray = [ + 'id' => 3, + 'description' => 'foo', + 'token' => '6d056f63e50fe6f8c5f8f4aa10edb7', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/triggers/3') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->trigger(1, 3)); + } + + #[Test] + public function shouldGetProjectIssues(): void + { + $expectedArray = $this->getProjectIssuesExpectedArray(); + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/issues') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->issues(1)); + } + + #[Test] + public function shouldGetProjectUsers(): void + { + $expectedArray = $this->getProjectUsersExpectedArray(); + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/users') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->users(1)); + } + + #[Test] + public function shouldGetProjectIssuesParameters(): void + { + $expectedArray = $this->getProjectIssuesExpectedArray(); + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/issues') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->issues(1, ['state' => 'opened'])); + } + + /** + * Get expected array for tests which check project issues method. + * + * @return array + * Project issues list + */ + public function getProjectIssuesExpectedArray(): array + { + return [ + [ + 'state' => 'opened', + 'description' => 'Ratione dolores corrupti mollitia soluta quia.', + 'author' => [ + 'state' => 'active', + 'id' => 18, + 'web_url' => 'https://gitlab.example.com/eileen.lowe', + 'name' => 'Alexandra Bashirian', + 'avatar_url' => null, + 'username' => 'eileen.lowe', + ], + 'milestone' => [ + 'project_id' => 1, + 'description' => 'Ducimus nam enim ex consequatur cumque ratione.', + 'state' => 'closed', + 'due_date' => null, + 'iid' => 2, + 'created_at' => '2016-01-04T15:31:39.996Z', + 'title' => 'v4.0', + 'id' => 17, + 'updated_at' => '2016-01-04T15:31:39.996Z', + ], + 'project_id' => 1, + 'assignees' => [ + [ + 'state' => 'active', + 'id' => 1, + 'name' => 'Administrator', + 'web_url' => 'https://gitlab.example.com/root', + 'avatar_url' => null, + 'username' => 'root', + ], + ], + 'assignee' => [ + 'state' => 'active', + 'id' => 1, + 'name' => 'Administrator', + 'web_url' => 'https://gitlab.example.com/root', + 'avatar_url' => null, + 'username' => 'root', + ], + 'updated_at' => '2016-01-04T15:31:51.081Z', + 'closed_at' => null, + 'closed_by' => null, + 'id' => 76, + 'title' => 'Consequatur vero maxime deserunt laboriosam est voluptas dolorem.', + 'created_at' => '2016-01-04T15:31:51.081Z', + 'iid' => 6, + 'labels' => [], + 'user_notes_count' => 1, + 'due_date' => '2016-07-22', + 'web_url' => 'http://example.com/example/example/issues/6', + 'confidential' => false, + 'weight' => null, + 'discussion_locked' => false, + 'time_stats' => [ + 'time_estimate' => 0, + 'total_time_spent' => 0, + 'human_time_estimate' => null, + 'human_total_time_spent' => null, + ], + ], + ]; + } + + /** + * Get expected array for tests which check project users method. + */ + public function getProjectUsersExpectedArray(): array + { + return [ + [ + 'id' => 1, + 'name' => 'John Doe', + 'username' => 'john.doe', + 'state' => 'active', + 'avatar_url' => 'https://example.com', + 'web_url' => 'https://gitlab.com/john.doe', + ], + ]; + } + + #[Test] + public function shouldGetBoards(): void + { + $expectedArray = $this->getProjectIssuesExpectedArray(); + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/boards') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->boards(1)); + } + + /** + * Get expected array for tests which check project boards. + * + * @return array + * Project issues list + */ + public function getProjectBoardsExpectedArray(): array + { + return [ + [ + 'id' => 1, + 'project' => [ + 'id' => 5, + 'name' => 'Diaspora Project Site', + 'name_with_namespace' => 'Diaspora / Diaspora Project Site', + 'path' => 'diaspora-project-site', + 'path_with_namespace' => 'diaspora/diaspora-project-site', + 'http_url_to_repo' => 'http://example.com/diaspora/diaspora-project-site.git', + 'web_url' => 'http://example.com/diaspora/diaspora-project-site', + ], + 'milestone' => [ + 'id' => 12, + 'title' => '10.0', + ], + 'lists' => [ + [ + 'id' => 1, + 'label' => [ + 'name' => 'Testing', + 'color' => '#F0AD4E', + 'description' => null, + ], + 'position' => 1, + ], + [ + 'id' => 2, + 'label' => [ + 'name' => 'Ready', + 'color' => '#FF0000', + 'description' => null, + ], + 'position' => 2, + ], + [ + 'id' => 3, + 'label' => [ + 'name' => 'Production', + 'color' => '#FF5F00', + 'description' => null, + ], + 'position' => 3, + ], + ], + ], + ]; + } + + #[Test] + public function shouldGetIterations(): void + { + $expectedArray = [ + [ + 'id' => 5, + 'iid' => 2, + 'sequence' => 1, + 'group_id' => 123, + 'title' => '2022: Sprint 1', + 'description' => '', + 'state' => 3, + 'created_at' => '2021-09-29T21:24:43.913Z', + 'updated_at' => '2022-03-29T19:09:08.368Z', + 'start_date' => '2022-01-10', + 'due_date' => '2022-01-23', + 'web_url' => 'https://example.com/groups/example/-/iterations/34', + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/iterations') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->iterations(1)); + } + + #[Test] + public function shouldCreateTrigger(): void + { + $expectedArray = [ + 'id' => 4, + 'description' => 'foobar', + 'token' => '6d056f63e50fe6f8c5f8f4aa10edb7', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/triggers', ['description' => 'foobar']) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->createTrigger(1, 'foobar')); + } + + #[Test] + public function shouldRemoveTrigger(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/triggers/2') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->removeTrigger(1, 2)); + } + + #[Test] + public function shouldTriggerPipeline(): void + { + $expectedArray = [ + 'id' => 4, + 'sha' => 'commit_hash', + 'ref' => 'master', + 'status' => 'pending', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with( + 'projects/1/trigger/pipeline', + ['ref' => 'master', 'token' => 'some_token', 'variables' => ['VAR_1' => 'value 1']] + ) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->triggerPipeline(1, 'master', 'some_token', ['VAR_1' => 'value 1'])); + } + + #[Test] + public function shouldGetPipelinesWithBooleanParam(): void + { + $expectedArray = [ + ['id' => 1, 'status' => 'success', 'ref' => 'new-pipeline'], + ['id' => 2, 'status' => 'failed', 'ref' => 'new-pipeline'], + ['id' => 3, 'status' => 'pending', 'ref' => 'test-pipeline'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/pipelines', ['yaml_errors' => 'false']) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->pipelines(1, ['yaml_errors' => false])); + } + + #[Test] + public function shouldGetPipelineWithDateParam(): void + { + $expectedArray = [ + ['id' => 1, 'status' => 'success', 'ref' => 'new-pipeline'], + ['id' => 2, 'status' => 'failed', 'ref' => 'new-pipeline'], + ['id' => 3, 'status' => 'pending', 'ref' => 'test-pipeline'], + ]; + + $updated_after = new DateTime('2018-01-01 00:00:00'); + $updated_before = new DateTime('2018-01-31 00:00:00'); + + $expectedWithArray = [ + 'updated_after' => $updated_after->format('Y-m-d'), + 'updated_before' => $updated_before->format('Y-m-d'), + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/pipelines', $expectedWithArray) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->pipelines(1, [ + 'updated_after' => $updated_after, + 'updated_before' => $updated_before, + ])); + } + + #[Test] + public function shouldGetPipelinesWithSHA(): void + { + $expectedArray = [ + ['id' => 1, 'status' => 'success', 'ref' => 'new-pipeline'], + ['id' => 2, 'status' => 'failed', 'ref' => 'new-pipeline'], + ['id' => 3, 'status' => 'pending', 'ref' => 'test-pipeline'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/pipelines', ['sha' => '123']) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->pipelines(1, ['sha' => '123'])); + } + + #[Test] + public function shouldGetPipeline(): void + { + $expectedArray = [ + ['id' => 1, 'status' => 'success', 'ref' => 'new-pipeline'], + ['id' => 2, 'status' => 'failed', 'ref' => 'new-pipeline'], + ['id' => 3, 'status' => 'pending', 'ref' => 'test-pipeline'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/pipelines/3') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->pipeline(1, 3)); + } + + #[Test] + public function shouldGetPipelineJobs(): void + { + $expectedArray = [ + ['id' => 1, 'status' => 'success', 'stage' => 'Build'], + ['id' => 2, 'status' => 'failed', 'stage' => 'Build'], + ['id' => 3, 'status' => 'pending', 'stage' => 'Build'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/pipelines/3/jobs') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->pipelineJobs(1, 3)); + } + + #[Test] + public function shouldGetPipelineVariables(): void + { + $expectedArray = [ + ['key' => 'foo', 'value' => 'bar'], + ['key' => 'baz', 'value' => '1234'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/pipelines/3/variables') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->pipelineVariables(1, 3)); + } + + #[Test] + public function shouldGetPipelineTestReport(): void + { + $expectedArray = [ + 'total_time' => 0.011809, + 'total_count' => 8, + 'success_count' => 8, + 'failed_count' => 0, + 'skipped_count' => 0, + 'error_count' => 0, + 'test_suites' => [], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/pipelines/3/test_report') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->pipelineTestReport(1, 3)); + } + + #[Test] + public function shouldGetPipelineTestReportSummary(): void + { + $expectedArray = [ + 'total_time' => 0.011809, + 'total_count' => 8, + 'success_count' => 8, + 'failed_count' => 0, + 'skipped_count' => 0, + 'error_count' => 0, + 'test_suites' => [], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/pipelines/3/test_report_summary') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->pipelineTestReportSummary(1, 3)); + } + + #[Test] + public function shouldCreatePipeline(): void + { + $expectedArray = [ + ['id' => 4, 'status' => 'created', 'ref' => 'test-pipeline'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/pipeline', [], [], [], ['ref' => 'test-pipeline']) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->createPipeline(1, 'test-pipeline')); + } + + #[Test] + public function shouldCreatePipelineWithVariables(): void + { + $expectedArray = [ + ['id' => 4, 'status' => 'created', 'ref' => 'test-pipeline'], + ]; + $variables = [ + [ + 'key' => 'test_var_1', + 'value' => 'test_value_1', + ], + [ + 'key' => 'test_var_2', + 'variable_type' => 'file', + 'value' => 'test_value_2', + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/pipeline', ['variables' => $variables], [], [], ['ref' => 'test-pipeline']) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->createPipeline(1, 'test-pipeline', $variables)); + } + + #[Test] + public function shouldRetryPipeline(): void + { + $expectedArray = [ + ['id' => 5, 'status' => 'pending', 'ref' => 'test-pipeline'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/pipelines/4/retry') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->retryPipeline(1, 4)); + } + + #[Test] + public function shouldCancelPipeline(): void + { + $expectedArray = [ + ['id' => 6, 'status' => 'cancelled', 'ref' => 'test-pipeline'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/pipelines/6/cancel') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->cancelPipeline(1, 6)); + } + + #[Test] + public function shouldDeletePipeline(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/pipelines/3') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->deletePipeline(1, 3)); + } + + #[Test] + public function shouldGetAllMembers(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'Matt'], + ['id' => 2, 'name' => 'Bob'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/members/all') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->allMembers(1)); + } + + #[Test] + public function shouldGetAllMember(): void + { + $expectedArray = ['id' => 2, 'name' => 'Bob']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/members/all/2') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->allMember(1, 2)); + } + + #[Test] + public function shouldGetMembers(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'Matt'], + ['id' => 2, 'name' => 'Bob'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/members') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->members(1)); + } + + #[Test] + public function shouldGetMembersWithQuery(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'Matt'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/members', ['query' => 'at']) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->members(1, ['query' => 'at'])); + } + + #[Test] + public function shouldGetMembersWithNullQuery(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'Matt'], + ['id' => 2, 'name' => 'Bob'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/members') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->members(1)); + } + + #[Test] + public function shouldGetMembersWithPagination(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'Matt'], + ['id' => 2, 'name' => 'Bob'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/members', [ + 'page' => 2, + 'per_page' => 15, + ]) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->members(1, ['page' => 2, 'per_page' => 15])); + } + + #[Test] + public function shouldGetMember(): void + { + $expectedArray = ['id' => 2, 'name' => 'Matt']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/members/2') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->member(1, 2)); + } + + #[Test] + public function shouldAddMember(): void + { + $expectedArray = ['id' => 1, 'name' => 'Matt']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/members', ['user_id' => 2, 'access_level' => 3]) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->addMember(1, 2, 3)); + } + + #[Test] + public function shouldAddMemberWithExpiration(): void + { + // tomorrow + $expiration = \date('Y-m-d', \time() + 86400); + $expectedArray = [ + 'user_id' => 3, + 'access_level' => 3, + 'expires_at' => $expiration, + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/members', ['user_id' => 3, 'access_level' => 3, 'expires_at' => $expiration]) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->addMember(1, 3, 3, $expiration)); + } + + #[Test] + public function shouldSaveMember(): void + { + $expectedArray = ['id' => 1, 'name' => 'Matt']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/members/2', ['access_level' => 4]) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->saveMember(1, 2, 4)); + } + + #[Test] + public function shouldSaveMemberWithExpiration(): void + { + // tomorrow + $expiration = \date('Y-m-d', \time() + 86400); + $expectedArray = [ + 'user_id' => 3, + 'access_level' => 4, + 'expires_at' => $expiration, + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/members/3', ['access_level' => 4, 'expires_at' => $expiration]) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->saveMember(1, 3, 4, $expiration)); + } + + #[Test] + public function shouldRemoveMember(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/members/2') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->removeMember(1, 2)); + } + + #[Test] + public function shouldGetHooks(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'Test hook'], + ['id' => 2, 'name' => 'Another hook'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/hooks') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->hooks(1)); + } + + #[Test] + public function shouldGetHook(): void + { + $expectedArray = ['id' => 2, 'name' => 'Another hook']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/hooks/2') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->hook(1, 2)); + } + + #[Test] + public function shouldAddHook(): void + { + $expectedArray = ['id' => 3, 'name' => 'A new hook', 'url' => 'http://www.example.com']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/hooks', [ + 'url' => 'http://www.example.com', + 'push_events' => true, + 'issues_events' => true, + 'merge_requests_events' => true, + ]) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->addHook( + 1, + 'http://www.example.com', + ['push_events' => true, 'issues_events' => true, 'merge_requests_events' => true] + )); + } + + #[Test] + public function shouldAddHookWithOnlyUrl(): void + { + $expectedArray = ['id' => 3, 'name' => 'A new hook', 'url' => 'http://www.example.com']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/hooks', ['url' => 'http://www.example.com', 'push_events' => true]) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->addHook(1, 'http://www.example.com')); + } + + #[Test] + public function shouldAddHookWithoutPushEvents(): void + { + $expectedArray = ['id' => 3, 'name' => 'A new hook', 'url' => 'http://www.example.com']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/hooks', ['url' => 'http://www.example.com', 'push_events' => false]) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->addHook(1, 'http://www.example.com', ['push_events' => false])); + } + + #[Test] + public function shouldUpdateHook(): void + { + $expectedArray = ['id' => 3, 'name' => 'A new hook', 'url' => 'http://www.example.com']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/hooks/3', ['url' => 'http://www.example-test.com', 'push_events' => false]) + ->willReturn($expectedArray); + + $this->assertEquals( + $expectedArray, + $api->updateHook(1, 3, ['url' => 'http://www.example-test.com', 'push_events' => false]) + ); + } + + #[Test] + public function shouldRemoveHook(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/hooks/2') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->removeHook(1, 2)); + } + + #[Test] + public function shouldTransfer(): void + { + $expectedArray = [ + 'id' => 1, + 'name' => 'Project Name', + 'namespace' => ['name' => 'a_namespace'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/transfer', ['namespace' => 'a_namespace']) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->transfer(1, 'a_namespace')); + } + + #[Test] + public function shouldGetDeployKeys(): void + { + $expectedArray = [ + ['id' => 1, 'title' => 'test-key'], + ['id' => 2, 'title' => 'another-key'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/deploy_keys') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->deployKeys(1)); + } + + #[Test] + public function shouldGetDeployKey(): void + { + $expectedArray = ['id' => 2, 'title' => 'another-key']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/deploy_keys/2') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->deployKey(1, 2)); + } + + #[Test] + public function shouldAddKey(): void + { + $expectedArray = ['id' => 3, 'title' => 'new-key', 'can_push' => false]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/deploy_keys', ['title' => 'new-key', 'key' => '...', 'can_push' => false]) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->addDeployKey(1, 'new-key', '...')); + } + + #[Test] + public function shouldAddKeyWithPushOption(): void + { + $expectedArray = ['id' => 3, 'title' => 'new-key', 'can_push' => true]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/deploy_keys', ['title' => 'new-key', 'key' => '...', 'can_push' => true]) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->addDeployKey(1, 'new-key', '...', true)); + } + + #[Test] + public function shouldDeleteDeployKey(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/deploy_keys/3') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->deleteDeployKey(1, 3)); + } + + #[Test] + public function shoudEnableDeployKey(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/deploy_keys/3/enable') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->enableDeployKey(1, 3)); + } + + #[Test] + public function shouldGetDeployTokens(): void + { + $expectedArray = [ + [ + 'id' => 1, + 'name' => 'MyToken', + 'username' => 'gitlab+deploy-token-1', + 'expires_at' => '2020-02-14T00:00:00.000Z', + 'revoked' => false, + 'expired' => false, + 'scopes' => [ + 'read_repository', + 'read_registry', + ], + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/deploy_tokens') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->deployTokens(1)); + } + + #[Test] + public function shouldGetActiveDeployTokens(): void + { + $expectedArray = [ + [ + 'id' => 1, + 'name' => 'MyToken', + 'username' => 'gitlab+deploy-token-1', + 'expires_at' => '2020-02-14T00:00:00.000Z', + 'revoked' => false, + 'expired' => true, + 'scopes' => [ + 'read_repository', + 'read_registry', + ], + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/deploy_tokens', ['active' => true]) + ->willReturn([]); + + $this->assertEquals([], $api->deployTokens(1, true)); + } + + #[Test] + public function shouldGetInactiveDeployTokens(): void + { + $expectedArray = [ + [ + 'id' => 1, + 'name' => 'MyToken', + 'username' => 'gitlab+deploy-token-1', + 'expires_at' => '2020-02-14T00:00:00.000Z', + 'revoked' => false, + 'expired' => true, + 'scopes' => [ + 'read_repository', + 'read_registry', + ], + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/deploy_tokens', ['active' => false]) + ->willReturn([]); + + $this->assertEquals([], $api->deployTokens(1, false)); + } + + #[Test] + public function shouldCreateDeployToken(): void + { + $expectedArray = [ + 'id' => 1, + 'name' => 'My Deploy Token', + 'username' => 'custom-user', + 'token' => 'jMRvtPNxrn3crTAGukpZ', + 'expires_at' => '2021-01-01T00:00:00.000Z', + 'revoked' => false, + 'expired' => false, + 'scopes' => [ + 'read_repository', + 'read_registry', + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with( + 'projects/1/deploy_tokens', + [ + 'name' => 'My Deploy Token', + 'scopes' => [ + 'read_repository', + 'read_registry', + ], + 'expires_at' => (new DateTime('2021-01-01'))->format('c'), + ] + ) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->createDeployToken(1, [ + 'name' => 'My Deploy Token', + 'scopes' => [ + 'read_repository', + 'read_registry', + ], + 'expires_at' => new DateTime('2021-01-01'), + ])); + } + + #[Test] + public function shouldDeleteDeployToken(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/deploy_tokens/2') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->deleteDeployToken(1, 2)); + } + + #[Test] + public function shouldGetEvents(): void + { + $expectedArray = [ + ['id' => 1, 'title' => 'An event'], + ['id' => 2, 'title' => 'Another event'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/events', []) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->events(1)); + } + + #[Test] + public function shouldGetEventsWithDateTimeParams(): void + { + $expectedArray = [ + ['id' => 1, 'title' => 'An event'], + ['id' => 2, 'title' => 'Another event'], + ]; + + $after = new DateTime('2018-01-01 00:00:00'); + $before = new DateTime('2018-01-31 00:00:00'); + + $expectedWithArray = [ + 'after' => $after->format('Y-m-d'), + 'before' => $before->format('Y-m-d'), + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/events', $expectedWithArray) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->events(1, ['after' => $after, 'before' => $before])); + } + + #[Test] + public function shouldGetEventsWithPagination(): void + { + $expectedArray = [ + ['id' => 1, 'title' => 'An event'], + ['id' => 2, 'title' => 'Another event'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/events', [ + 'page' => 2, + 'per_page' => 15, + ]) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->events(1, ['page' => 2, 'per_page' => 15])); + } + + #[Test] + public function shouldGetLabels(): void + { + $expectedArray = [ + ['id' => 987, 'name' => 'bug', 'color' => '#000000'], + ['id' => 123, 'name' => 'feature', 'color' => '#ff0000'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/labels') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->labels(1)); + } + + #[Test] + public function shouldAddLabel(): void + { + $expectedArray = ['name' => 'bug', 'color' => '#000000']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/labels', ['name' => 'wont-fix', 'color' => '#ffffff']) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->addLabel(1, ['name' => 'wont-fix', 'color' => '#ffffff'])); + } + + #[Test] + public function shouldUpdateLabel(): void + { + $expectedArray = ['name' => 'bug', 'color' => '#00ffff']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/labels/123', ['new_name' => 'big-bug', 'color' => '#00ffff']) + ->willReturn($expectedArray); + + $this->assertEquals( + $expectedArray, + $api->updateLabel(1, 123, ['new_name' => 'big-bug', 'color' => '#00ffff']) + ); + } + + #[Test] + public function shouldRemoveLabel(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/labels/456', []) + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->removeLabel(1, 456)); + } + + #[Test] + public function shouldGetLanguages(): void + { + $expectedArray = ['php' => 100]; + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->languages(1)); + } + + #[Test] + public function shouldForkWithNamespace(): void + { + $expectedArray = [ + 'namespace' => 'new_namespace', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/fork', $expectedArray) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->fork(1, [ + 'namespace' => 'new_namespace', + ])); + } + + #[Test] + public function shouldForkWithNamespaceAndPath(): void + { + $expectedArray = [ + 'namespace' => 'new_namespace', + 'path' => 'new_path', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/fork', $expectedArray) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->fork(1, [ + 'namespace' => 'new_namespace', + 'path' => 'new_path', + ])); + } + + #[Test] + public function shouldForkWithNamespaceAndPathAndName(): void + { + $expectedArray = [ + 'namespace' => 'new_namespace', + 'path' => 'new_path', + 'name' => 'new_name', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/fork', $expectedArray) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->fork(1, [ + 'namespace' => 'new_namespace', + 'path' => 'new_path', + 'name' => 'new_name', + ])); + } + + #[Test] + public function shouldCreateForkRelation(): void + { + $expectedArray = ['project_id' => 1, 'forked_id' => 2]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/fork/2') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->createForkRelation(1, 2)); + } + + #[Test] + public function shouldRemoveForkRelation(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/2/fork') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->removeForkRelation(2)); + } + + #[Test] + public function shouldGetForks(): void + { + $expectedArray = [ + [ + 'id' => 2, + 'forked_from_project' => [ + 'id' => 1, + ], + ], + [ + 'id' => 3, + 'forked_from_project' => [ + 'id' => 1, + ], + ], + ]; + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/forks') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->forks(1)); + } + + #[Test] + public function shouldGetForksUsingParameters(): void + { + $expectedArray = [ + [ + 'id' => 2, + 'forked_from_project' => [ + 'id' => 1, + ], + ], + [ + 'id' => 3, + 'forked_from_project' => [ + 'id' => 1, + ], + ], + ]; + $updated_after = new DateTime('2018-01-01 00:00:00'); + $updated_before = new DateTime('2018-01-31 00:00:00'); + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/forks', [ + 'archived' => 'false', + 'visibility' => 'public', + 'order_by' => 'id', + 'sort' => 'asc', + 'search' => 'term', + 'simple' => 'true', + 'owned' => 'false', + 'membership' => 'false', + 'starred' => 'false', + 'statistics' => 'false', + 'with_issues_enabled' => 'false', + 'with_merge_requests_enabled' => 'false', + 'min_access_level' => 30, + 'updated_after' => $updated_after->format('c'), + 'updated_before' => $updated_before->format('c'), + 'with_custom_attributes' => 'true', + ]) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->forks(1, [ + 'archived' => false, + 'visibility' => 'public', + 'order_by' => 'id', + 'sort' => 'asc', + 'search' => 'term', + 'simple' => true, + 'owned' => false, + 'membership' => false, + 'starred' => false, + 'statistics' => false, + 'with_issues_enabled' => false, + 'with_merge_requests_enabled' => false, + 'min_access_level' => 30, + 'updated_after' => $updated_after, + 'updated_before' => $updated_before, + 'with_custom_attributes' => true, + ])); + } + + #[Test] + public function shouldSetService(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/services/hipchat', ['param' => 'value']) + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->setService(1, 'hipchat', ['param' => 'value'])); + } + + #[Test] + public function shouldRemoveService(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/services/hipchat') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->removeService(1, 'hipchat')); + } + + #[Test] + public function shouldGetVariables(): void + { + $expectedArray = [ + ['key' => 'ftp_username', 'value' => 'ftp'], + ['key' => 'ftp_password', 'value' => 'somepassword'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/variables') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->variables(1)); + } + + #[Test] + public function shouldGetVariable(): void + { + $expectedArray = ['key' => 'ftp_username', 'value' => 'ftp']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/variables/ftp_username') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->variable(1, 'ftp_username')); + } + + #[Test] + public function shouldAddVariable(): void + { + $expectedKey = 'ftp_port'; + $expectedValue = '21'; + + $expectedArray = [ + 'key' => $expectedKey, + 'value' => $expectedValue, + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/variables', $expectedArray) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->addVariable(1, $expectedKey, $expectedValue)); + } + + #[Test] + public function shouldAddVariableWithProtected(): void + { + $expectedArray = [ + 'key' => 'DEPLOY_SERVER', + 'value' => 'stage.example.com', + 'protected' => true, + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/variables', $expectedArray) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->addVariable(1, 'DEPLOY_SERVER', 'stage.example.com', true)); + } + + #[Test] + public function shouldAddVariableWithEnvironment(): void + { + $expectedArray = [ + 'key' => 'DEPLOY_SERVER', + 'value' => 'stage.example.com', + 'environment_scope' => 'staging', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/variables', $expectedArray) + ->willReturn($expectedArray); + + $this->assertEquals( + $expectedArray, + $api->addVariable(1, 'DEPLOY_SERVER', 'stage.example.com', null, 'staging') + ); + } + + #[Test] + public function shouldAddVariableWithProtectionAndEnvironment(): void + { + $expectedArray = [ + 'key' => 'DEPLOY_SERVER', + 'value' => 'stage.example.com', + 'protected' => true, + 'environment_scope' => 'staging', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/variables', $expectedArray) + ->willReturn($expectedArray); + + $this->assertEquals( + $expectedArray, + $api->addVariable(1, 'DEPLOY_SERVER', 'stage.example.com', true, 'staging') + ); + } + + #[Test] + public function shouldAddVariableWithEnvironmentAndVariableType(): void + { + $expectedArray = [ + 'key' => 'DEPLOY_SERVER', + 'value' => 'stage.example.com', + 'environment_scope' => 'staging', + 'variable_type' => 'file', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/variables', $expectedArray) + ->willReturn($expectedArray); + + $this->assertEquals( + $expectedArray, + $api->addVariable(1, 'DEPLOY_SERVER', 'stage.example.com', null, 'staging', ['variable_type' => 'file']) + ); + } + + #[Test] + public function shouldAddVariableWithEnvironmentFromParameterList(): void + { + $expectedArray = [ + 'key' => 'DEPLOY_SERVER', + 'value' => 'stage.example.com', + 'environment_scope' => 'staging', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/variables', $expectedArray) + ->willReturn($expectedArray); + + $this->assertEquals( + $expectedArray, + $api->addVariable(1, 'DEPLOY_SERVER', 'stage.example.com', null, 'staging', ['environment_scope' => 'production']) + ); + } + + #[Test] + public function shouldUpdateVariable(): void + { + $expectedKey = 'ftp_port'; + $expectedValue = '22'; + + $expectedArray = [ + 'key' => 'ftp_port', + 'value' => '22', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/variables/'.$expectedKey, ['value' => $expectedValue]) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->updateVariable(1, $expectedKey, $expectedValue)); + } + + #[Test] + public function shouldUpdateVariableWithProtected(): void + { + $expectedArray = [ + 'key' => 'DEPLOY_SERVER', + 'value' => 'stage.example.com', + 'protected' => true, + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/variables/DEPLOY_SERVER', ['value' => 'stage.example.com', 'protected' => true]) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->updateVariable(1, 'DEPLOY_SERVER', 'stage.example.com', true)); + } + + #[Test] + public function shouldUpdateVariableWithEnvironment(): void + { + $expectedArray = [ + 'key' => 'DEPLOY_SERVER', + 'value' => 'stage.example.com', + 'environment_scope' => 'staging', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with( + 'projects/1/variables/DEPLOY_SERVER', + ['value' => 'stage.example.com', 'environment_scope' => 'staging'] + ) + ->willReturn($expectedArray); + + $this->assertEquals( + $expectedArray, + $api->updateVariable(1, 'DEPLOY_SERVER', 'stage.example.com', null, 'staging') + ); + } + + #[Test] + public function shouldUpdateVariableWithProtectedAndEnvironment(): void + { + $expectedArray = [ + 'key' => 'DEPLOY_SERVER', + 'value' => 'stage.example.com', + 'protected' => true, + 'environment_scope' => 'staging', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with( + 'projects/1/variables/DEPLOY_SERVER', + ['value' => 'stage.example.com', 'protected' => true, 'environment_scope' => 'staging'] + ) + ->willReturn($expectedArray); + + $this->assertEquals( + $expectedArray, + $api->updateVariable(1, 'DEPLOY_SERVER', 'stage.example.com', true, 'staging') + ); + } + + #[Test] + public function shouldUpdateVariableWithEnvironmentAndVariableType(): void + { + $expectedArray = [ + 'key' => 'DEPLOY_SERVER', + 'value' => 'stage.example.com', + 'environment_scope' => 'staging', + 'variable_type' => 'file', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with( + 'projects/1/variables/DEPLOY_SERVER', + ['value' => 'stage.example.com', 'environment_scope' => 'staging', 'variable_type' => 'file'] + ) + ->willReturn($expectedArray); + + $this->assertEquals( + $expectedArray, + $api->updateVariable(1, 'DEPLOY_SERVER', 'stage.example.com', null, 'staging', ['variable_type' => 'file']) + ); + } + + #[Test] + public function shouldUpdateVariableWithEnvironmentFromParameterList(): void + { + $expectedArray = [ + 'key' => 'DEPLOY_SERVER', + 'value' => 'stage.example.com', + 'environment_scope' => 'staging', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with( + 'projects/1/variables/DEPLOY_SERVER', + ['value' => 'stage.example.com', 'environment_scope' => 'staging'] + ) + ->willReturn($expectedArray); + + $this->assertEquals( + $expectedArray, + $api->updateVariable(1, 'DEPLOY_SERVER', 'stage.example.com', null, 'staging', ['environment_scope' => 'production']) + ); + } + + #[Test] + public function shouldRemoveVariable(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/variables/ftp_password') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->removeVariable(1, 'ftp_password')); + } + + protected function getMultipleProjectsRequestMock($path, $expectedArray = [], $expectedParameters = []): MockObject + { + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with($path, $expectedParameters) + ->willReturn($expectedArray); + + return $api; + } + + #[Test] + public function shouldGetDeployments(): void + { + $expectedArray = [ + ['id' => 1, 'sha' => '0000001'], + ['id' => 2, 'sha' => '0000002'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/deployments', []) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->deployments(1)); + } + + #[Test] + public function shouldGetDeploymentsWithPagination(): void + { + $expectedArray = [ + ['id' => 1, 'sha' => '0000001'], + ['id' => 2, 'sha' => '0000002'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/deployments', [ + 'page' => 2, + 'per_page' => 15, + ]) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->deployments(1, ['page' => 2, 'per_page' => 15])); + } + + #[Test] + public function shouldGetDeploymentsSorted(): void + { + $expectedArray = [ + ['id' => 1, 'sha' => '0000001'], + ['id' => 2, 'sha' => '0000002'], + ['id' => 3, 'sha' => '0000003'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/deployments', [ + 'order_by' => 'id', + 'sort' => 'asc', + ]) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->deployments(1, ['order_by' => 'id', 'sort' => 'asc'])); + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/deployments', [ + 'order_by' => 'id', + 'sort' => 'desc', + ]) + ->willReturn(\array_reverse($expectedArray)); + + $this->assertEquals(\array_reverse($expectedArray), $api->deployments(1, ['order_by' => 'id', 'sort' => 'desc'])); + } + + #[Test] + public function shouldGetDeploymentsFiltered(): void + { + $expectedArray = [ + ['id' => 1, 'sha' => '0000001'], + ['id' => 2, 'sha' => '0000002'], + ['id' => 3, 'sha' => '0000003'], + ]; + + $time = new DateTime('now'); + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/deployments', [ + 'updated_after' => $time->format('c'), + ]) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->deployments(1, ['updated_after' => $time])); + } + + protected function getMultipleProjectsData(): array + { + return [ + ['id' => 1, 'name' => 'A project'], + ['id' => 2, 'name' => 'Another project'], + ]; + } + + protected function getMultipleProjectsDataWithNamespace(): array + { + return [ + ['id' => 1, 'name' => 'A project', 'namespace' => ['id' => 4, 'name' => 'A namespace', 'path' => 'a_namespace']], + ['id' => 2, 'name' => 'Another project', 'namespace' => ['id' => 5, 'name' => 'Another namespace', 'path' => 'another_namespace']], + ]; + } + + public static function possibleAccessLevels(): array + { + return [ + [10], + [20], + [30], + [40], + [50], + ]; + } + + public function getBadgeExpectedArray(): array + { + return [ + [ + 'id' => 1, + 'link_url' => 'http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}', + 'image_url' => 'https://shields.io/my/badge', + 'rendered_link_url' => 'http://example.com/ci_status.svg?project=example-org/example-project&ref=master', + 'rendered_image_url' => 'https://shields.io/my/badge', + 'kind' => 'project', + ], + [ + 'id' => 2, + 'link_url' => 'http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}', + 'image_url' => 'https://shields.io/my/badge', + 'rendered_link_url' => 'http://example.com/ci_status.svg?project=example-org/example-project&ref=master', + 'rendered_image_url' => 'https://shields.io/my/badge', + 'kind' => 'group', + ], + ]; + } + + #[Test] + public function shouldGetBadges(): void + { + $expectedArray = $this->getBadgeExpectedArray(); + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/badges') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->badges(1)); + } + + #[Test] + public function shouldGetBadge(): void + { + $expectedBadgesArray = $this->getBadgeExpectedArray(); + $expectedArray = [ + $expectedBadgesArray[0], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/badges/1') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->badge(1, 1)); + } + + #[Test] + public function shouldAddBadge(): void + { + $link_url = 'http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}'; + $image_url = 'https://shields.io/my/badge'; + $expectedArray = [ + 'id' => 3, + 'link_url' => $link_url, + 'image_url' => $image_url, + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/badges', ['link_url' => $link_url, 'image_url' => $image_url]) + ->willReturn($expectedArray); + + $this->assertEquals( + $expectedArray, + $api->addBadge(1, ['link_url' => $link_url, 'image_url' => $image_url]) + ); + } + + #[Test] + public function shouldUpdateBadge(): void + { + $image_url = 'https://shields.io/my/new/badge'; + $expectedArray = [ + 'id' => 2, + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/badges/2') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->updateBadge(1, 2, ['image_url' => $image_url])); + } + + #[Test] + public function shouldRemoveBadge(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/badges/1') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->removeBadge(1, 1)); + } + + #[Test] + public function shouldAddProtectedBranch(): void + { + $expectedArray = [ + 'name' => 'master', + 'push_access_level' => [ + 'access_level' => 0, + 'access_level_description' => 'No one', + ], + 'merge_access_levels' => [ + 'access_level' => 0, + 'access_level_description' => 'Developers + Maintainers', + ], + ]; + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with( + 'projects/1/protected_branches', + ['name' => 'master', 'push_access_level' => 0, 'merge_access_level' => 30] + ) + ->willReturn($expectedArray); + $this->assertEquals($expectedArray, $api->addProtectedBranch(1, ['name' => 'master', 'push_access_level' => 0, 'merge_access_level' => 30])); + } + + #[Test] + public function shouldRemoveProtectedBranch(): void + { + $expectedBool = true; + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with( + 'projects/1/protected_branches/test-branch' + ) + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->deleteProtectedBranch(1, 'test-branch')); + } + + #[Test] + public function shoudGetApprovalsConfiguration(): void + { + $expectedArray = [ + 'approvers' => [], + 'approver_groups' => [], + 'approvals_before_merge' => 1, + 'reset_approvals_on_push' => true, + 'disable_overriding_approvers_per_merge_request' => null, + 'merge_requests_author_approval' => null, + 'merge_requests_disable_committers_approval' => null, + 'require_password_to_approve' => null, + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/approvals') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->approvalsConfiguration(1)); + } + + #[Test] + public function shoudGetApprovalsRules(): void + { + $expectedArray = [ + [ + 'id' => 1, + 'name' => 'All Members', + 'rule_type' => 'any_approver', + 'eligible_approvers' => [], + 'approvals_required' => 1, + 'users' => [], + 'groups' => [], + 'contains_hidden_groups' => false, + 'protected_branches' => [], + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/approval_rules') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->approvalsRules(1)); + } + + #[Test] + public function shoudCreateApprovalsRule(): void + { + $expectedArray = [ + [ + 'id' => 1, + 'name' => 'All Members', + 'rule_type' => 'any_approver', + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/approval_rules/', ['name' => 'All Members', 'rule_type' => 'any_approver']) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->createApprovalsRule(1, [ + 'name' => 'All Members', + 'rule_type' => 'any_approver', + ])); + } + + #[Test] + public function shoudUpdateApprovalsRule(): void + { + $expectedArray = [ + [ + 'name' => 'Updated Name', + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/approval_rules/1', ['name' => 'Updated Name']) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->updateApprovalsRule(1, 1, [ + 'name' => 'Updated Name', + ])); + } + + #[Test] + public function shoudDeleteApprovalsRule(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/approval_rules/1') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->deleteApprovalsRule(1, 1)); + } + + #[Test] + public function shouldDeleteAllMergedBranches(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/repository/merged_branches') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->deleteAllMergedBranches(1)); + } + + #[Test] + public function shouldGetProtectedBranches(): void + { + $expectedArray = [ + [ + 'id' => 1, + 'name' => 'master', + 'push_access_levels' => [ + 'access_level' => 0, + 'access_level_description' => 'No one', + 'user_id' => null, + 'group_id' => null, + ], + 'merge_access_levels' => [ + 'access_level' => 40, + 'access_level_description' => 'Maintainers', + 'user_id' => null, + 'group_id' => null, + ], + 'unprotect_access_levels' => [], + 'code_owner_approval_required' => false, + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/protected_branches') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->protectedBranches(1)); + } + + #[Test] + public function shouldGetProjectAccessTokens(): void + { + $expectedArray = [ + [ + 'user_id' => 141, + 'scopes' => [ + 'api', + ], + 'name' => 'token', + 'expires_at' => '2021-01-31', + 'id' => 42, + 'active' => true, + 'created_at' => '2021-01-20T22:11:48.151Z', + 'revoked' => false, + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/access_tokens') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->projectAccessTokens(1)); + } + + #[Test] + public function shouldGetProjectAccessToken(): void + { + $expectedArray = [ + 'user_id' => 141, + 'scopes' => [ + 'api', + ], + 'name' => 'token', + 'expires_at' => '2021-01-31', + 'id' => 42, + 'active' => true, + 'created_at' => '2021-01-20T22:11:48.151Z', + 'revoked' => false, + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/access_tokens/42') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->projectAccessToken(1, 42)); + } + + #[Test] + public function shouldCreateProjectAccessToken(): void + { + $expectedArray = [ + 'scopes' => [ + 'api', + 'read_repository', + ], + 'active' => true, + 'name' => 'test', + 'revoked' => false, + 'created_at' => '2021-01-21T19:35:37.921Z', + 'user_id' => 166, + 'id' => 58, + 'expires_at' => '2021-01-31', + 'token' => 'D4y...Wzr', + ]; + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with( + 'projects/1/access_tokens', + [ + 'name' => 'test_token', + 'scopes' => [ + 'api', + 'read_repository', + ], + 'access_level' => 30, + 'expires_at' => '2021-01-31', + ] + ) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->createProjectAccessToken(1, [ + 'name' => 'test_token', + 'scopes' => [ + 'api', + 'read_repository', + ], + 'access_level' => 30, + 'expires_at' => new DateTime('2021-01-31'), + ])); + } + + #[Test] + public function shouldDeleteProjectAccessToken(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/access_tokens/2') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->deleteProjectAccessToken(1, 2)); + } + + #[Test] + public function shouldUploadAvatar(): void + { + $emptyPNGContents = 'iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAACYElEQVR42u3UMQEAAAjDMFCO9GEAByQSerQrmQJeagMAAwAMADAAwAAAAwAMADAAwAAAAwAMADAAwAAAAwAMADAAwAAAAwAMADAAwAAAAwAMADAAwAAAAwAMADAAwAAAAwAMADAAwAAAAwAMADAAwADAAAwADAAwAMAAAAMADAAwAMAAAAMADAAwAMAAAAMADAAwAMAAAAMADAAwAMAAAAMADAAwAMAAAAMADAAwAMAAAAMADAAwAMAAAAMADAAwAMAAAAMAAzAAMADAAAADAAwAMADAAAADAAwAMADAAAADAAwAMADAAAADAAwAMADAAAADAAwAMADAAAADAAwAMADAAAADAAwAMADAAAADAAwAMADAAAADAAwADMAAwAAAAwAMADAAwAAAAwAMADAAwAAAAwAMADAAwAAAAwAMADAAwAAAAwAMADAAwAAAAwAMADAAwAAAAwAMADAAwAAAAwAMADAAwAAAAwAMADAAMAAZwAAAAwAMADAAwAAAAwAMADAAwAAAAwAMADAAwAAAAwAMADAAwAAAAwAMADAAwAAAAwAMADAAwAAAAwAMADAAwAAAAwAMADAAwAAAAwAMADAAMADAAAADAAwAMADAAAADAAwAMADAAAADAAwAMADAAAADAAwAMADAAAADAAwAMADAAAADAAwAMADAAAADAAwAMADAAAADAAwAMADAAAADAAwAMAAwAMAAAAMADAAwAMAAAAMADAAwAMAAAAMADAAwAMAAAAMADAAwAMAAAAMADAAwAMAAAAMADAAwAMAAAAMADAAwAMAAAAMADAAwAOCybrx+H1CTHLYAAAAASUVORK5CYII='; + $fileName = \uniqid().'.png'; + $expectedArray = ['id' => 1, 'name' => 'Project Name', 'avatar_url' => 'https://gitlab.example.com/uploads/-/system/project/avatar/1/'.$fileName]; + \file_put_contents($fileName, \base64_decode($emptyPNGContents)); + $this->assertFileExists($fileName); + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1', [], [], ['avatar' => $fileName]) + ->willReturn($expectedArray); + $this->assertEquals($expectedArray, $api->uploadAvatar(1, $fileName)); + \unlink($fileName); + } + + #[Test] + public function shouldAddProtectedTag(): void + { + $expectedArray = [ + 'name' => 'release-*', + 'create_access_level' => [ + ['access_level' => 40, 'access_level_description' => 'Maintainers'], + ['group_id' => 123], + ], + ]; + $api = $this->getApiMock(); + $params = [ + 'name' => 'release-*', + 'create_access_level' => 40, + 'allowed_to_create' => [['group_id' => 123]], + ]; + $api->expects($this->once()) + ->method('post') + ->with('projects/1/protected_tags', $params) + ->willReturn($expectedArray); + $this->assertEquals($expectedArray, $api->addProtectedTag(1, $params)); + } + + #[Test] + public function shouldRemoveProtectedTag(): void + { + $expectedBool = true; + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with( + 'projects/1/protected_tags/release-%2A' + ) + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->deleteProtectedTag(1, 'release-*')); + } + + protected function getApiClass(): string + { + return Projects::class; + } + + #[Test] + public function shouldSearchGroups(): void + { + $expectedArray = [ + ['id' => 6, 'title' => 'Issue 6 bla'], + ['id' => 7, 'title' => 'Issue 7 bla'], + ['id' => 8, 'title' => 'Issue 8 bla'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/123/search', [ + 'scope' => 'issues', + 'confidential' => 'false', + 'search' => 'bla', + 'order_by' => 'created_at', + 'sort' => 'desc', + ]) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->search(123, [ + 'scope' => 'issues', + 'confidential' => false, + 'search' => 'bla', + 'order_by' => 'created_at', + 'sort' => 'desc', + ])); + } +} diff --git a/tests/Api/RepositoriesTest.php b/tests/Api/RepositoriesTest.php new file mode 100644 index 000000000..3b5d73342 --- /dev/null +++ b/tests/Api/RepositoriesTest.php @@ -0,0 +1,659 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\Repositories; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Test; + +class RepositoriesTest extends TestCase +{ + #[Test] + public function shouldGetBranches(): void + { + $expectedArray = [ + ['name' => 'master'], + ['name' => 'develop'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/repository/branches', ['search' => '^term']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->branches(1, ['search' => '^term'])); + } + + #[Test] + public function shouldGetBranch(): void + { + $expectedArray = ['name' => 'master']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/repository/branches/master') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->branch(1, 'master')); + } + + #[Test] + public function shouldCreateBranch(): void + { + $expectedArray = ['name' => 'feature']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/repository/branches', ['branch' => 'feature', 'ref' => 'master']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->createBranch(1, 'feature', 'master')); + } + + #[Test] + public function shouldDeleteBranch(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/repository/branches/feature%2FTEST-15') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->deleteBranch(1, 'feature/TEST-15')); + } + + #[Test] + public function shouldProtectBranch(): void + { + $expectedArray = ['name' => 'master']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/repository/branches/master/protect', ['developers_can_push' => false, 'developers_can_merge' => false]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->protectBranch(1, 'master')); + } + + #[Test] + public function shouldProtectBranchWithPermissions(): void + { + $expectedArray = ['name' => 'master']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/repository/branches/master/protect', ['developers_can_push' => true, 'developers_can_merge' => true]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->protectBranch(1, 'master', true, true)); + } + + #[Test] + public function shouldUnprotectBranch(): void + { + $expectedArray = ['name' => 'master']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/repository/branches/master/unprotect') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->unprotectBranch(1, 'master')); + } + + #[Test] + public function shouldGetTags(): void + { + $expectedArray = [ + ['name' => '1.0'], + ['name' => '1.1'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/repository/tags') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->tags(1, ['search' => '^term'])); + } + + #[Test] + public function shouldCreateTag(): void + { + $expectedArray = ['name' => '1.0']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/repository/tags', [ + 'tag_name' => '1.0', + 'ref' => 'abcd1234', + 'message' => '1.0 release', + ]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->createTag(1, '1.0', 'abcd1234', '1.0 release')); + } + + #[Test] + public function shouldCreateRelease(): void + { + $project_id = 1; + $tagName = 'sometag'; + $description = '1.0 release'; + + $expectedArray = ['name' => $tagName]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/'.$project_id.'/releases', [ + 'id' => $project_id, + 'tag_name' => $tagName, + 'description' => $description, + ]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->createRelease($project_id, $tagName, $description)); + } + + #[Test] + public function shouldUpdateRelease(): void + { + $project_id = 1; + $tagName = 'sometag'; + $description = '1.0 release'; + + $expectedArray = ['description' => $tagName]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/'.$project_id.'/releases/'.$tagName, [ + 'id' => $project_id, + 'tag_name' => $tagName, + 'description' => $description, + ]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->updateRelease($project_id, $tagName, $description)); + } + + #[Test] + public function shouldGetReleases(): void + { + $project_id = 1; + + $expectedArray = [ + [ + 'tag_name' => 'v0.2', + 'description' => '## CHANGELOG\r\n\r\n- Escape label and milestone titles to prevent XSS in GFM autocomplete. !2740\r\n- Prevent private snippets from being embeddable.\r\n- Add subresources removal to member destroy service.', + 'name' => 'Awesome app v0.2 beta', + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/releases') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->releases($project_id)); + } + + #[Test] + public function shouldGetCommits(): void + { + $expectedArray = [ + ['id' => 'abcd1234', 'title' => 'A commit'], + ['id' => 'efgh5678', 'title' => 'Another commit'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/repository/commits', []) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->commits(1)); + } + + #[Test] + public function shouldGetCommitsWithParams(): void + { + $expectedArray = [ + ['id' => 'abcd1234', 'title' => 'A commit'], + ['id' => 'efgh5678', 'title' => 'Another commit'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/repository/commits', ['page' => 2, 'per_page' => 25, 'ref_name' => 'master', 'all' => 'true', 'with_stats' => 'true', 'path' => 'file_path/file_name']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->commits(1, ['page' => 2, 'per_page' => 25, 'ref_name' => 'master', 'all' => true, 'with_stats' => true, 'path' => 'file_path/file_name'])); + } + + #[Test] + public function shouldGetCommitsWithTimeParams(): void + { + $expectedArray = [ + ['id' => 'abcd1234', 'title' => 'A commit'], + ['id' => 'efgh5678', 'title' => 'Another commit'], + ]; + + $since = new \DateTime('2018-01-01 00:00:00'); + $until = new \DateTime('2018-01-31 00:00:00'); + + $expectedWithArray = [ + 'since' => $since->format(\DATE_ATOM), + 'until' => $until->format(\DATE_ATOM), + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/repository/commits', $expectedWithArray) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->commits(1, ['since' => $since, 'until' => $until])); + } + + #[Test] + public function shouldGetCommit(): void + { + $expectedArray = ['id' => 'abcd1234', 'title' => 'A commit']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/repository/commits/abcd1234') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->commit(1, 'abcd1234')); + } + + #[Test] + public function shouldGetCommitRefs(): void + { + $expectedArray = [ + ['type' => 'branch', 'name' => 'master'], + ['type' => 'tag', 'name' => 'v1.1.0'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/repository/commits/abcd1234/refs') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->commitRefs(1, 'abcd1234')); + } + + #[Test] + #[DataProvider('dataGetCommitRefsWithParams')] + public function shouldGetCommitRefsWithParams(string $type, array $expectedArray): void + { + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/repository/commits/abcd1234/refs', ['type' => $type]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->commitRefs(1, 'abcd1234', ['type' => $type])); + } + + public static function dataGetCommitRefsWithParams(): array + { + return [ + 'type_tag' => [ + 'type' => Repositories::TYPE_TAG, + 'expectedArray' => [['type' => 'tag', 'name' => 'v1.1.0']], + ], + 'type_branch' => [ + 'type' => Repositories::TYPE_BRANCH, + 'expectedArray' => [['type' => 'branch', 'name' => 'master']], + ], + ]; + } + + #[Test] + public function shouldCreateCommit(): void + { + $expectedArray = ['title' => 'Initial commit.', 'author_name' => 'John Doe', 'author_email' => 'john@example.com']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/repository/commits') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->createCommit(1, [ + 'branch' => 'master', + 'commit_message' => 'Initial commit.', + 'actions' => [ + [ + 'action' => 'create', + 'file_path' => 'README.md', + 'content' => '# My new project', + ], + [ + 'action' => 'create', + 'file_path' => 'LICENSE', + 'content' => 'MIT License...', + ], + ], + 'author_name' => 'John Doe', + 'author_email' => 'john@example.com', + ])); + } + + #[Test] + public function shouldRevertCommit(): void + { + $expectedArray = ['title' => 'Initial commit.', 'author_name' => 'John Doe', 'author_email' => 'john@example.com']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/repository/commits/abcd1234/revert') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->revertCommit(1, 'develop', 'abcd1234')); + } + + #[Test] + public function shouldGetCommitComments(): void + { + $expectedArray = [ + ['note' => 'A commit message'], + ['note' => 'Another commit message'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/repository/commits/abcd1234/comments') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->commitComments(1, 'abcd1234')); + } + + #[Test] + public function shouldCreateCommitComment(): void + { + $expectedArray = ['id' => 2, 'title' => 'A new comment']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/repository/commits/abcd1234/comments', ['note' => 'A new comment']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->createCommitComment(1, 'abcd1234', 'A new comment')); + } + + #[Test] + public function shouldCreateCommitCommentWithParams(): void + { + $expectedArray = ['id' => 2, 'title' => 'A new comment']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/repository/commits/abcd1234/comments', [ + 'note' => 'A new comment', + 'path' => '/some/file.txt', + 'line' => 123, 'line_type' => 'old', + ]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->createCommitComment(1, 'abcd1234', 'A new comment', [ + 'path' => '/some/file.txt', + 'line' => 123, + 'line_type' => 'old', + ])); + } + + #[Test] + public function shouldCompareStraight(): void + { + $expectedArray = ['commit' => 'object']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/repository/compare', ['from' => 'master', 'to' => 'feature', 'straight' => 'true']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->compare(1, 'master', 'feature', true)); + } + + #[Test] + public function shouldNotCompareStraight(): void + { + $expectedArray = ['commit' => 'object']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/repository/compare', ['from' => 'master', 'to' => 'feature', 'straight' => 'false']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->compare(1, 'master', 'feature')); + } + + #[Test] + public function shouldCompareComplexBranchName(): void + { + $expectedArray = ['commit' => 'object']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/repository/compare', ['from' => 'master', 'to' => 'feature/760.fake-branch', 'straight' => 'true']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->compare(1, 'master', 'feature/760.fake-branch', true)); + } + + #[Test] + public function shouldCompareWithFromProjectId(): void + { + $expectedArray = ['commit' => 'object']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/repository/compare', ['from' => 'master', 'to' => 'feature', 'straight' => 'true', 'from_project_id' => '123']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->compare(1, 'master', 'feature', true, '123')); + } + + #[Test] + public function shouldGetDiff(): void + { + $expectedArray = [ + ['diff' => '--- ...'], + ['diff' => '+++ ...'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/repository/commits/abcd1234/diff') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->diff(1, 'abcd1234')); + } + + #[Test] + public function shouldGetTree(): void + { + $expectedArray = [ + ['name' => 'file1.txt'], + ['name' => 'file2.csv'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/repository/tree') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->tree(1)); + } + + #[Test] + public function shouldGetTreeWithParams(): void + { + $expectedArray = [ + ['name' => 'dir/file1.txt'], + ['name' => 'dir/file2.csv'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/repository/tree', ['path' => 'dir/', 'ref_name' => 'master']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->tree(1, ['path' => 'dir/', 'ref_name' => 'master'])); + } + + #[Test] + public function shouldGetContributors(): void + { + $expectedArray = [ + ['name' => 'Matt'], + ['name' => 'Bob'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/repository/contributors') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->contributors(1)); + } + + #[Test] + public function shouldGetMergeBase(): void + { + $expectedArray = [ + 'id' => 'abcd1234abcd1234abcd1234abcd1234abcd1234', + 'short_id' => 'abcd1234', + 'title' => 'A commit', + 'created_at' => '2018-01-01T00:00:00.000Z', + 'parent_ids' => [ + 'efgh5678efgh5678efgh5678efgh5678efgh5678', + ], + 'message' => 'A commit', + 'author_name' => 'Jane Doe', + 'author_email' => 'jane@example.org', + 'authored_date' => '2018-01-01T00:00:00.000Z', + 'committer_name' => 'Jane Doe', + 'committer_email' => 'jane@example.org', + 'committed_date' => '2018-01-01T00:00:00.000Z', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/repository/merge_base', ['refs' => ['efgh5678efgh5678efgh5678efgh5678efgh5678', '1234567812345678123456781234567812345678']]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->mergeBase(1, ['efgh5678efgh5678efgh5678efgh5678efgh5678', '1234567812345678123456781234567812345678'])); + } + + #[Test] + public function shouldCherryPick(): void + { + $expectedArray = [ + 'id' => 'abcd1234abcd1234abcd1234abcd1234abcd1234', + 'short_id' => 'abcd1234', + 'title' => 'A commit', + 'author_name' => 'Example User', + 'author_email' => 'jane@example.org', + 'authored_date' => '2018-01-01T00:00:00.000Z', + 'created_at' => '2018-01-01T00:00:00.000Z', + 'committer_name' => 'Jane Doe', + 'committer_email' => 'jane@example.org', + 'committed_date' => '2018-01-01T00:00:00.000Z', + 'message' => 'A commit', + 'parent_ids' => [ + 'efgh5678efgh5678efgh5678efgh5678efgh5678', + ], + 'web_url' => 'https://gitlab.example.com/thedude/gitlab-foss/-/commit/abcd1234abcd1234abcd1234abcd1234abcd1234', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/repository/commits/123456123456/cherry_pick', ['branch' => 'feature_branch']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->cherryPick(1, '123456123456', ['branch' => 'feature_branch'])); + } + + protected function getApiClass(): string + { + return Repositories::class; + } +} diff --git a/test/Gitlab/Tests/Api/RepositoryFilesTest.php b/tests/Api/RepositoryFilesTest.php similarity index 64% rename from test/Gitlab/Tests/Api/RepositoryFilesTest.php rename to tests/Api/RepositoryFilesTest.php index d37b60f47..d9dfdf3b4 100644 --- a/test/Gitlab/Tests/Api/RepositoryFilesTest.php +++ b/tests/Api/RepositoryFilesTest.php @@ -1,63 +1,68 @@ + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ namespace Gitlab\Tests\Api; use Gitlab\Api\RepositoryFiles; +use PHPUnit\Framework\Attributes\Test; class RepositoryFilesTest extends TestCase { - /** - * @test - */ - public function shouldGetBlob() + #[Test] + public function shouldGetBlob(): void { $expectedString = 'something in a file'; $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with('projects/1/repository/files/dir%2Ffile1%2Etxt/raw', array('ref' => 'abcd1234')) - ->will($this->returnValue($expectedString)) - ; + ->with('projects/1/repository/files/dir%2Ffile1.txt/raw', ['ref' => 'abcd1234']) + ->willReturn($expectedString); $this->assertEquals($expectedString, $api->getRawFile(1, 'dir/file1.txt', 'abcd1234')); } - /** - * @test - */ - public function shouldGetFile() + #[Test] + public function shouldGetFile(): void { - $expectedArray = array('file_name' => 'file1.txt', 'file_path' => 'dir/file1.txt'); + $expectedArray = ['file_name' => 'file1.txt', 'file_path' => 'dir/file1.txt']; $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with('projects/1/repository/files/dir%2Ffile1%2Etxt', array('ref' => 'abcd1234')) - ->will($this->returnValue($expectedArray)) + ->with('projects/1/repository/files/dir%2Ffile1.txt', ['ref' => 'abcd1234']) + ->willReturn($expectedArray) ; $this->assertEquals($expectedArray, $api->getFile(1, 'dir/file1.txt', 'abcd1234')); } - /** - * @test - */ - public function shouldCreateFile() + #[Test] + public function shouldCreateFile(): void { - $expectedArray = array('file_name' => 'file1.txt', 'file_path' => 'dir/file1.txt'); + $expectedArray = ['file_name' => 'file1.txt', 'file_path' => 'dir/file1.txt']; $api = $this->getApiMock(); $api->expects($this->once()) ->method('post') - ->with('projects/1/repository/files/dir%2Ffile1%2Etxt', array( + ->with('projects/1/repository/files/dir%2Ffile1.txt', [ 'file_path' => 'dir/file1.txt', 'branch' => 'master', 'content' => 'some contents', 'commit_message' => 'Added new file', - )) - ->will($this->returnValue($expectedArray)) + ]) + ->willReturn($expectedArray) ; $this->assertEquals($expectedArray, $api->createFile(1, [ @@ -68,24 +73,22 @@ public function shouldCreateFile() ])); } - /** - * @test - */ - public function shouldCreateFileWithEncoding() + #[Test] + public function shouldCreateFileWithEncoding(): void { - $expectedArray = array('file_name' => 'file1.txt', 'file_path' => 'dir/file1.txt'); + $expectedArray = ['file_name' => 'file1.txt', 'file_path' => 'dir/file1.txt']; $api = $this->getApiMock(); $api->expects($this->once()) ->method('post') - ->with('projects/1/repository/files/dir%2Ffile1%2Etxt', array( + ->with('projects/1/repository/files/dir%2Ffile1.txt', [ 'file_path' => 'dir/file1.txt', 'branch' => 'master', 'encoding' => 'text', 'content' => 'some contents', 'commit_message' => 'Added new file', - )) - ->will($this->returnValue($expectedArray)) + ]) + ->willReturn($expectedArray) ; $this->assertEquals($expectedArray, $api->createFile(1, [ @@ -97,25 +100,23 @@ public function shouldCreateFileWithEncoding() ])); } - /** - * @test - */ - public function shouldCreateFileWithAuthor() + #[Test] + public function shouldCreateFileWithAuthor(): void { - $expectedArray = array('file_name' => 'file1.txt', 'file_path' => 'dir/file1.txt'); + $expectedArray = ['file_name' => 'file1.txt', 'file_path' => 'dir/file1.txt']; $api = $this->getApiMock(); $api->expects($this->once()) ->method('post') - ->with('projects/1/repository/files/dir%2Ffile1%2Etxt', array( + ->with('projects/1/repository/files/dir%2Ffile1.txt', [ 'file_path' => 'dir/file1.txt', 'branch' => 'master', 'content' => 'some contents', 'commit_message' => 'Added new file', 'author_email' => 'gitlab@example.com', 'author_name' => 'GitLab User', - )) - ->will($this->returnValue($expectedArray)) + ]) + ->willReturn($expectedArray) ; $this->assertEquals($expectedArray, $api->createFile(1, [ @@ -128,23 +129,21 @@ public function shouldCreateFileWithAuthor() ])); } - /** - * @test - */ - public function shouldUpdateFile() + #[Test] + public function shouldUpdateFile(): void { - $expectedArray = array('file_name' => 'file1.txt', 'file_path' => 'dir/file1.txt'); + $expectedArray = ['file_name' => 'file1.txt', 'file_path' => 'dir/file1.txt']; $api = $this->getApiMock(); $api->expects($this->once()) ->method('put') - ->with('projects/1/repository/files/dir%2Ffile1%2Etxt', array( + ->with('projects/1/repository/files/dir%2Ffile1.txt', [ 'file_path' => 'dir/file1.txt', 'branch' => 'master', 'content' => 'some new contents', 'commit_message' => 'Updated new file', - )) - ->will($this->returnValue($expectedArray)) + ]) + ->willReturn($expectedArray) ; $this->assertEquals($expectedArray, $api->updateFile(1, [ @@ -155,24 +154,22 @@ public function shouldUpdateFile() ])); } - /** - * @test - */ - public function shouldUpdateFileWithEncoding() + #[Test] + public function shouldUpdateFileWithEncoding(): void { - $expectedArray = array('file_name' => 'file1.txt', 'file_path' => 'dir/file1.txt'); + $expectedArray = ['file_name' => 'file1.txt', 'file_path' => 'dir/file1.txt']; $api = $this->getApiMock(); $api->expects($this->once()) ->method('put') - ->with('projects/1/repository/files/dir%2Ffile1%2Etxt', array( + ->with('projects/1/repository/files/dir%2Ffile1.txt', [ 'file_path' => 'dir/file1.txt', 'branch' => 'master', 'encoding' => 'base64', 'content' => 'c29tZSBuZXcgY29udGVudHM=', 'commit_message' => 'Updated file', - )) - ->will($this->returnValue($expectedArray)) + ]) + ->willReturn($expectedArray) ; $this->assertEquals($expectedArray, $api->updateFile(1, [ @@ -184,25 +181,23 @@ public function shouldUpdateFileWithEncoding() ])); } - /** - * @test - */ - public function shouldUpdateFileWithAuthor() + #[Test] + public function shouldUpdateFileWithAuthor(): void { - $expectedArray = array('file_name' => 'file1.txt', 'file_path' => 'dir/file1.txt'); + $expectedArray = ['file_name' => 'file1.txt', 'file_path' => 'dir/file1.txt']; $api = $this->getApiMock(); $api->expects($this->once()) ->method('put') - ->with('projects/1/repository/files/dir%2Ffile1%2Etxt', array( + ->with('projects/1/repository/files/dir%2Ffile1.txt', [ 'file_path' => 'dir/file1.txt', 'branch' => 'master', 'content' => 'some new contents', 'commit_message' => 'Updated file', 'author_email' => 'gitlab@example.com', 'author_name' => 'GitLab User', - )) - ->will($this->returnValue($expectedArray)) + ]) + ->willReturn($expectedArray) ; $this->assertEquals($expectedArray, $api->updateFile(1, [ @@ -215,49 +210,45 @@ public function shouldUpdateFileWithAuthor() ])); } - /** - * @test - */ - public function shouldDeleteFile() + #[Test] + public function shouldDeleteFile(): void { - $expectedArray = ["file_name" => "app/project.rb", "branch" => "master"]; + $expectedArray = ['file_name' => 'app/project.rb', 'branch' => 'master']; $api = $this->getApiMock(); $api->expects($this->once()) ->method('delete') - ->with('projects/1/repository/files/dir%2Ffile1%2Etxt', array( + ->with('projects/1/repository/files/dir%2Ffile1.txt', [ 'file_path' => 'dir/file1.txt', 'branch' => 'master', 'commit_message' => 'Deleted file', - )) - ->will($this->returnValue($expectedArray)) + ]) + ->willReturn($expectedArray) ; $this->assertEquals($expectedArray, $api->deleteFile(1, [ 'file_path' => 'dir/file1.txt', 'branch' => 'master', - 'commit_message' => 'Deleted file' + 'commit_message' => 'Deleted file', ])); } - /** - * @test - */ - public function shouldDeleteFileWithAuthor() + #[Test] + public function shouldDeleteFileWithAuthor(): void { - $expectedArray = ["file_name" => "app/project.rb", "branch" => "master"]; + $expectedArray = ['file_name' => 'app/project.rb', 'branch' => 'master']; $api = $this->getApiMock(); $api->expects($this->once()) ->method('delete') - ->with('projects/1/repository/files/dir%2Ffile1%2Etxt', array( + ->with('projects/1/repository/files/dir%2Ffile1.txt', [ 'file_path' => 'dir/file1.txt', 'branch' => 'master', 'commit_message' => 'Deleted file', 'author_email' => 'gitlab@example.com', 'author_name' => 'GitLab User', - )) - ->will($this->returnValue($expectedArray)) + ]) + ->willReturn($expectedArray) ; $this->assertEquals($expectedArray, $api->deleteFile(1, [ @@ -269,10 +260,7 @@ public function shouldDeleteFileWithAuthor() ])); } - /** - * @return string - */ - protected function getApiClass() + protected function getApiClass(): string { return RepositoryFiles::class; } diff --git a/tests/Api/ResourceIterationEventsTest.php b/tests/Api/ResourceIterationEventsTest.php new file mode 100644 index 000000000..28b86ecd6 --- /dev/null +++ b/tests/Api/ResourceIterationEventsTest.php @@ -0,0 +1,135 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\ResourceIterationEvents; +use PHPUnit\Framework\Attributes\Test; + +class ResourceIterationEventsTest extends TestCase +{ + #[Test] + public function shouldGetAllEvents(): void + { + $expectedArray = [ + [ + 'id' => 142, + 'user' => [ + 'id' => 1, + 'name' => 'Administrator', + 'username' => 'root', + 'state' => 'active', + 'avatar_url' => 'https=>//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + 'web_url' => 'http=>//gitlab.example.com/root', + ], + 'created_at' => '2018-08-20T13=>38=>20.077Z', + 'resource_type' => 'Issue', + 'resource_id' => 253, + 'iteration' => [ + 'id' => 50, + 'iid' => 9, + 'group_id' => 5, + 'title' => 'Iteration I', + 'description' => 'Ipsum Lorem', + 'state' => 1, + 'created_at' => '2020-01-27T05=>07=>12.573Z', + 'updated_at' => '2020-01-27T05=>07=>12.573Z', + 'due_date' => null, + 'start_date' => null, + ], + 'action' => 'add', + ], + [ + 'id' => 143, + 'user' => [ + 'id' => 1, + 'name' => 'Administrator', + 'username' => 'root', + 'state' => 'active', + 'avatar_url' => 'https=>//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + 'web_url' => 'http=>//gitlab.example.com/root', + ], + 'created_at' => '2018-08-21T14=>38=>20.077Z', + 'resource_type' => 'Issue', + 'resource_id' => 253, + 'iteration' => [ + 'id' => 53, + 'iid' => 13, + 'group_id' => 5, + 'title' => 'Iteration II', + 'description' => 'Ipsum Lorem ipsum', + 'state' => 2, + 'created_at' => '2020-01-27T05=>07=>12.573Z', + 'updated_at' => '2020-01-27T05=>07=>12.573Z', + 'due_date' => null, + 'start_date' => null, + ], + 'action' => 'remove', + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/issues/253/resource_iteration_events', []) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->all(1, 253)); + } + + #[Test] + public function shouldShowEvent(): void + { + $expectedArray = [ + 'id' => 142, + 'user' => [ + 'id' => 1, + 'name' => 'Administrator', + 'username' => 'root', + 'state' => 'active', + 'avatar_url' => 'https=>//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + 'web_url' => 'http=>//gitlab.example.com/root', + ], + 'created_at' => '2018-08-20T13=>38=>20.077Z', + 'resource_type' => 'Issue', + 'resource_id' => 253, + 'iteration' => [ + 'id' => 50, + 'iid' => 9, + 'group_id' => 5, + 'title' => 'Iteration I', + 'description' => 'Ipsum Lorem', + 'state' => 1, + 'created_at' => '2020-01-27T05=>07=>12.573Z', + 'updated_at' => '2020-01-27T05=>07=>12.573Z', + 'due_date' => null, + 'start_date' => null, + ], + 'action' => 'add', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/issues/253/resource_iteration_events/142', []) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->show(1, 253, 142)); + } + + protected function getApiClass(): string + { + return ResourceIterationEvents::class; + } +} diff --git a/tests/Api/ResourceLabelEventsTest.php b/tests/Api/ResourceLabelEventsTest.php new file mode 100644 index 000000000..8a2651efb --- /dev/null +++ b/tests/Api/ResourceLabelEventsTest.php @@ -0,0 +1,117 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\ResourceLabelEvents; +use PHPUnit\Framework\Attributes\Test; + +class ResourceLabelEventsTest extends TestCase +{ + #[Test] + public function shouldGetAllEvents(): void + { + $expectedArray = [ + [ + 'id' => 142, + 'user' => [ + 'id' => 1, + 'name' => 'Administrator', + 'username' => 'root', + 'state' => 'active', + 'avatar_url' => 'https=>//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + 'web_url' => 'http=>//gitlab.example.com/root', + ], + 'created_at' => '2018-08-20T13=>38=>20.077Z', + 'resource_type' => 'Issue', + 'resource_id' => 253, + 'label' => [ + 'id' => 73, + 'name' => 'a1', + 'color' => '#34495E', + 'description' => '', + ], + 'action' => 'add', + ], + [ + 'id' => 143, + 'user' => [ + 'id' => 1, + 'name' => 'Administrator', + 'username' => 'root', + 'state' => 'active', + 'avatar_url' => 'https=>//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + 'web_url' => 'http=>//gitlab.example.com/root', + ], + 'created_at' => '2018-08-20T13=>38=>20.077Z', + 'resource_type' => 'Issue', + 'resource_id' => 253, + 'label' => [ + 'id' => 74, + 'name' => 'p1', + 'color' => '#0033CC', + 'description' => '', + ], + 'action' => 'remove', + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/issues/253/resource_label_events', []) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->all(1, 253)); + } + + #[Test] + public function shouldShowEvent(): void + { + $expectedArray = [ + 'id' => 142, + 'user' => [ + 'id' => 1, + 'name' => 'Administrator', + 'username' => 'root', + 'state' => 'active', + 'avatar_url' => 'https=>//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + 'web_url' => 'http=>//gitlab.example.com/root', + ], + 'created_at' => '2018-08-20T13=>38=>20.077Z', + 'resource_type' => 'Issue', + 'resource_id' => 253, + 'label' => [ + 'id' => 73, + 'name' => 'a1', + 'color' => '#34495E', + 'description' => '', + ], + 'action' => 'add', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/issues/253/resource_label_events/142', []) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->show(1, 253, 142)); + } + + protected function getApiClass(): string + { + return ResourceLabelEvents::class; + } +} diff --git a/tests/Api/ResourceMilestoneEventsTest.php b/tests/Api/ResourceMilestoneEventsTest.php new file mode 100644 index 000000000..4fb353355 --- /dev/null +++ b/tests/Api/ResourceMilestoneEventsTest.php @@ -0,0 +1,138 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\ResourceMilestoneEvents; +use PHPUnit\Framework\Attributes\Test; + +class ResourceMilestoneEventsTest extends TestCase +{ + #[Test] + public function shouldGetAllEvents(): void + { + $expectedArray = [ + [ + 'id' => 142, + 'user' => [ + 'id' => 1, + 'name' => 'Administrator', + 'username' => 'root', + 'state' => 'active', + 'avatar_url' => 'https=>//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + 'web_url' => 'http=>//gitlab.example.com/root', + ], + 'created_at' => '2018-08-20T13=>38=>20.077Z', + 'resource_type' => 'Issue', + 'resource_id' => 253, + 'milestone' => [ + 'id' => 61, + 'iid' => 9, + 'project_id' => 7, + 'title' => 'v1.2', + 'description' => 'Ipsum Lorem', + 'state' => 'active', + 'created_at' => '2020-01-27T05=>07=>12.573Z', + 'updated_at' => '2020-01-27T05=>07=>12.573Z', + 'due_date' => null, + 'start_date' => null, + 'web_url' => 'http=>//gitlab.example.com=>3000/group/project/-/milestones/9', + ], + 'action' => 'add', + ], + [ + 'id' => 143, + 'user' => [ + 'id' => 1, + 'name' => 'Administrator', + 'username' => 'root', + 'state' => 'active', + 'avatar_url' => 'https=>//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + 'web_url' => 'http=>//gitlab.example.com/root', + ], + 'created_at' => '2018-08-21T14=>38=>20.077Z', + 'resource_type' => 'Issue', + 'resource_id' => 253, + 'milestone' => [ + 'id' => 61, + 'iid' => 9, + 'project_id' => 7, + 'title' => 'v1.2', + 'description' => 'Ipsum Lorem', + 'state' => 'active', + 'created_at' => '2020-01-27T05=>07=>12.573Z', + 'updated_at' => '2020-01-27T05=>07=>12.573Z', + 'due_date' => null, + 'start_date' => null, + 'web_url' => 'http=>//gitlab.example.com=>3000/group/project/-/milestones/9', + ], + 'action' => 'remove', + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/issues/253/resource_milestone_events', []) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->all(1, 253)); + } + + #[Test] + public function shouldShowEvent(): void + { + $expectedArray = [ + 'id' => 142, + 'user' => [ + 'id' => 1, + 'name' => 'Administrator', + 'username' => 'root', + 'state' => 'active', + 'avatar_url' => 'https=>//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + 'web_url' => 'http=>//gitlab.example.com/root', + ], + 'created_at' => '2018-08-20T13=>38=>20.077Z', + 'resource_type' => 'Issue', + 'resource_id' => 253, + 'milestone' => [ + 'id' => 61, + 'iid' => 9, + 'project_id' => 7, + 'title' => 'v1.2', + 'description' => 'Ipsum Lorem', + 'state' => 'active', + 'created_at' => '2020-01-27T05=>07=>12.573Z', + 'updated_at' => '2020-01-27T05=>07=>12.573Z', + 'due_date' => null, + 'start_date' => null, + 'web_url' => 'http=>//gitlab.example.com=>3000/group/project/-/milestones/9', + ], + 'action' => 'add', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/issues/253/resource_milestone_events/142', []) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->show(1, 253, 142)); + } + + protected function getApiClass(): string + { + return ResourceMilestoneEvents::class; + } +} diff --git a/tests/Api/ResourceStateEventsTest.php b/tests/Api/ResourceStateEventsTest.php new file mode 100644 index 000000000..cad7b7109 --- /dev/null +++ b/tests/Api/ResourceStateEventsTest.php @@ -0,0 +1,99 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\ResourceStateEvents; +use PHPUnit\Framework\Attributes\Test; + +class ResourceStateEventsTest extends TestCase +{ + #[Test] + public function shouldGetAllEvents(): void + { + $expectedArray = [ + [ + 'id' => 142, + 'user' => [ + 'id' => 1, + 'name' => 'Administrator', + 'username' => 'root', + 'state' => 'active', + 'avatar_url' => 'https=>//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + 'web_url' => 'http=>//gitlab.example.com/root', + ], + 'created_at' => '2018-08-20T13=>38=>20.077Z', + 'resource_type' => 'Issue', + 'resource_id' => 11, + 'state' => 'opened', + ], + [ + 'id' => 143, + 'user' => [ + 'id' => 1, + 'name' => 'Administrator', + 'username' => 'root', + 'state' => 'active', + 'avatar_url' => 'https=>//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + 'web_url' => 'http=>//gitlab.example.com/root', + ], + 'created_at' => '2018-08-21T14=>38=>20.077Z', + 'resource_type' => 'Issue', + 'resource_id' => 11, + 'state' => 'closed', + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/issues/11/resource_state_events', []) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->all(1, 11)); + } + + #[Test] + public function shouldShowEvent(): void + { + $expectedArray = [ + 'id' => 142, + 'user' => [ + 'id' => 1, + 'name' => 'Administrator', + 'username' => 'root', + 'state' => 'active', + 'avatar_url' => 'https=>//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + 'web_url' => 'http=>//gitlab.example.com/root', + ], + 'created_at' => '2018-08-20T13=>38=>20.077Z', + 'resource_type' => 'Issue', + 'resource_id' => 11, + 'state' => 'opened', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/issues/11/resource_state_events/142', []) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->show(1, 11, 142)); + } + + protected function getApiClass(): string + { + return ResourceStateEvents::class; + } +} diff --git a/tests/Api/ResourceWeightEventsTest.php b/tests/Api/ResourceWeightEventsTest.php new file mode 100644 index 000000000..d554de7da --- /dev/null +++ b/tests/Api/ResourceWeightEventsTest.php @@ -0,0 +1,96 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\ResourceWeightEvents; +use PHPUnit\Framework\Attributes\Test; + +class ResourceWeightEventsTest extends TestCase +{ + #[Test] + public function shouldGetAllEvents(): void + { + $expectedArray = [ + [ + 'id' => 142, + 'user' => [ + 'id' => 1, + 'name' => 'Administrator', + 'username' => 'root', + 'state' => 'active', + 'avatar_url' => 'https=>//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + 'web_url' => 'http=>//gitlab.example.com/root', + ], + 'created_at' => '2018-08-20T13=>38=>20.077Z', + 'issue_id' => 253, + 'weight' => 3, + ], + [ + 'id' => 143, + 'user' => [ + 'id' => 1, + 'name' => 'Administrator', + 'username' => 'root', + 'state' => 'active', + 'avatar_url' => 'https=>//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + 'web_url' => 'http=>//gitlab.example.com/root', + ], + 'created_at' => '2018-08-21T14=>38=>20.077Z', + 'issue_id' => 253, + 'weight' => 2, + ], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/issues/253/resource_weight_events', []) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->all(1, 253)); + } + + #[Test] + public function shouldShowEvent(): void + { + $expectedArray = [ + 'id' => 142, + 'user' => [ + 'id' => 1, + 'name' => 'Administrator', + 'username' => 'root', + 'state' => 'active', + 'avatar_url' => 'https=>//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + 'web_url' => 'http=>//gitlab.example.com/root', + ], + 'created_at' => '2018-08-20T13=>38=>20.077Z', + 'issue_id' => 253, + 'weight' => 3, + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/issues/253/resource_weight_events/142', []) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->show(1, 253, 142)); + } + + protected function getApiClass(): string + { + return ResourceWeightEvents::class; + } +} diff --git a/tests/Api/ScheduleTest.php b/tests/Api/ScheduleTest.php new file mode 100644 index 000000000..e5f64e98f --- /dev/null +++ b/tests/Api/ScheduleTest.php @@ -0,0 +1,220 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\Schedules; +use PHPUnit\Framework\Attributes\Test; + +class ScheduleTest extends TestCase +{ + #[Test] + public function shouldCreateSchedule(): void + { + $expectedArray = [ + 'id' => 13, + 'description' => 'Test schedule pipeline', + 'ref' => 'master', + 'cron' => '* * * * *', + 'cron_timezone' => 'Asia/Tokyo', + 'next_run_at' => '2017-05-19T13:41:00.000Z', + 'active' => true, + 'created_at' => '2017-05-19T13:31:08.849Z', + 'updated_at' => '2017-05-19T13:40:17.727Z', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/pipeline_schedules', [ + 'id' => 13, + 'description' => 'Test schedule pipeline', + 'ref' => 'master', + 'cron' => '* * * * *', + 'cron_timezone' => 'Asia/Tokyo', + 'next_run_at' => '2017-05-19T13:41:00.000Z', + 'active' => true, + 'created_at' => '2017-05-19T13:31:08.849Z', + 'updated_at' => '2017-05-19T13:40:17.727Z', + ]) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->create( + 1, + [ + 'id' => 13, + 'description' => 'Test schedule pipeline', + 'ref' => 'master', + 'cron' => '* * * * *', + 'cron_timezone' => 'Asia/Tokyo', + 'next_run_at' => '2017-05-19T13:41:00.000Z', + 'active' => true, + 'created_at' => '2017-05-19T13:31:08.849Z', + 'updated_at' => '2017-05-19T13:40:17.727Z', + ] + )); + } + + #[Test] + public function shouldShowSchedule(): void + { + $expectedArray = ['id' => 1, 'name' => 'A schedule']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/pipeline_schedules/2') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->show(1, 2)); + } + + #[Test] + public function shouldShowAllSchedule(): void + { + $expectedArray = ['id' => 1, 'name' => 'A schedule']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/pipeline_schedules') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->showAll(1)); + } + + #[Test] + public function shouldUpdateSchedule(): void + { + $expectedArray = ['id' => 3, 'title' => 'Updated schedule']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/pipeline_schedules/3', ['title' => 'Updated schedule', 'due_date' => '2015-04-01', 'state_event' => 'close']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->update(1, 3, ['title' => 'Updated schedule', 'due_date' => '2015-04-01', 'state_event' => 'close'])); + } + + #[Test] + public function shouldRemoveSchedule(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/pipeline_schedules/2') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->remove(1, 2)); + } + + #[Test] + public function shouldCreateScheduleVariable(): void + { + $expectedArray = [ + 'key' => 'FOO_BAR', + 'variable_type' => 'env_var', + 'value' => 'BAZ', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/pipeline_schedules/2/variables', $expectedArray) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->addVariable( + 1, + 2, + $expectedArray + )); + } + + #[Test] + public function shouldUpdateScheduleVariable(): void + { + $variabelName = 'FOO_BAR'; + $expectedArray = [ + 'key' => $variabelName, + 'variable_type' => 'env_var', + 'value' => 'BAZ', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/pipeline_schedules/2/variables/'.$variabelName, $expectedArray) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->updateVariable( + 1, + 2, + $variabelName, + $expectedArray + )); + } + + #[Test] + public function shouldRemoveScheduleVariable(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/pipeline_schedules/2/variables/FOO_BAR') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->removeVariable(1, 2, 'FOO_BAR')); + } + + #[Test] + public function shouldTakeOwnership(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/pipeline_schedules/2/take_ownership') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->takeOwnership(1, 2)); + } + + #[Test] + public function shouldPlay(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/pipeline_schedules/2/play') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->play(1, 2)); + } + + protected function getApiClass(): string + { + return Schedules::class; + } +} diff --git a/tests/Api/SearchTest.php b/tests/Api/SearchTest.php new file mode 100644 index 000000000..cbb39a2e8 --- /dev/null +++ b/tests/Api/SearchTest.php @@ -0,0 +1,56 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\Search; +use PHPUnit\Framework\Attributes\Test; + +class SearchTest extends TestCase +{ + #[Test] + public function shouldGetAll(): void + { + $expectedArray = [ + ['id' => 6, 'name' => 'Project 6 bla'], + ['id' => 7, 'name' => 'Project 7 bla'], + ['id' => 8, 'name' => 'Project 8 bla'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('search', [ + 'scope' => 'projects', + 'confidential' => 'false', + 'search' => 'bla', + 'order_by' => 'created_at', + 'sort' => 'desc', + ]) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->all([ + 'scope' => 'projects', + 'confidential' => false, + 'search' => 'bla', + 'order_by' => 'created_at', + 'sort' => 'desc', + ])); + } + + protected function getApiClass(): string + { + return Search::class; + } +} diff --git a/tests/Api/SnippetsTest.php b/tests/Api/SnippetsTest.php new file mode 100644 index 000000000..0d75c5c94 --- /dev/null +++ b/tests/Api/SnippetsTest.php @@ -0,0 +1,226 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\Snippets; +use PHPUnit\Framework\Attributes\Test; + +class SnippetsTest extends TestCase +{ + #[Test] + public function shouldGetAllSnippets(): void + { + $expectedArray = [ + ['id' => 1, 'title' => 'A snippet'], + ['id' => 2, 'title' => 'Another snippet'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/snippets') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->all(1)); + } + + #[Test] + public function shouldShowSnippet(): void + { + $expectedArray = ['id' => 2, 'title' => 'Another snippet']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/snippets/2') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->show(1, 2)); + } + + #[Test] + public function shouldCreateSnippet(): void + { + $expectedArray = ['id' => 3, 'title' => 'A new snippet']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/snippets', ['title' => 'A new snippet', 'code' => 'A file', 'file_name' => 'file.txt', 'visibility' => 'public']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->create(1, 'A new snippet', 'file.txt', 'A file', 'public')); + } + + #[Test] + public function shouldUpdateSnippet(): void + { + $expectedArray = ['id' => 3, 'title' => 'Updated snippet']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/snippets/3', ['title' => 'Updated snippet', 'code' => 'New content', 'file_name' => 'new_file.txt']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->update(1, 3, ['file_name' => 'new_file.txt', 'code' => 'New content', 'title' => 'Updated snippet'])); + } + + #[Test] + public function shouldShowContent(): void + { + $expectedString = 'New content'; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/snippets/3/raw') + ->willReturn($expectedString); + + $this->assertEquals($expectedString, $api->content(1, 3)); + } + + #[Test] + public function shouldRemoveSnippet(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/snippets/3') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->remove(1, 3)); + } + + #[Test] + public function shouldGetNotes(): void + { + $expectedArray = [ + ['id' => 1, 'body' => 'A note'], + ['id' => 2, 'body' => 'Another note'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/snippets/2/notes') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->showNotes(1, 2)); + } + + #[Test] + public function shouldGetNote(): void + { + $expectedArray = ['id' => 3, 'body' => 'A new note']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/snippets/2/notes/3') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->showNote(1, 2, 3)); + } + + #[Test] + public function shouldCreateNote(): void + { + $expectedArray = ['id' => 3, 'body' => 'A new note']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/snippets/2/notes', ['body' => 'A new note']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->addNote(1, 2, 'A new note')); + } + + #[Test] + public function shouldUpdateNote(): void + { + $expectedArray = ['id' => 3, 'body' => 'An edited comment']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/snippets/2/notes/3', ['body' => 'An edited comment']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->updateNote(1, 2, 3, 'An edited comment')); + } + + #[Test] + public function shouldRemoveNote(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/snippets/2/notes/3') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->removeNote(1, 2, 3)); + } + + #[Test] + public function shouldIssueSnippetAwardEmoji(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'sparkles'], + ['id' => 2, 'name' => 'heart_eyes'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/snippets/2/award_emoji') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->awardEmoji(1, 2)); + } + + #[Test] + public function shouldRevokeSnippetAwardEmoji(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/snippets/2/award_emoji/3') + ->willReturn($expectedBool); + + $this->assertEquals(true, $api->removeAwardEmoji(1, 2, 3)); + } + + protected function getApiClass(): string + { + return Snippets::class; + } +} diff --git a/tests/Api/SystemHooksTest.php b/tests/Api/SystemHooksTest.php new file mode 100644 index 000000000..6c0d38457 --- /dev/null +++ b/tests/Api/SystemHooksTest.php @@ -0,0 +1,87 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\SystemHooks; +use PHPUnit\Framework\Attributes\Test; + +class SystemHooksTest extends TestCase +{ + #[Test] + public function shouldGetAllHooks(): void + { + $expectedArray = [ + ['id' => 1, 'url' => 'http://www.example.com'], + ['id' => 2, 'url' => 'http://www.example.org'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('hooks') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->all()); + } + + #[Test] + public function shouldCreateHook(): void + { + $expectedArray = ['id' => 3, 'url' => 'http://www.example.net']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('hooks', ['url' => 'http://www.example.net']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->create('http://www.example.net')); + } + + #[Test] + public function shouldTestHook(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('hooks/3') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->test(3)); + } + + #[Test] + public function shouldRemoveHook(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('hooks/3') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->remove(3)); + } + + protected function getApiClass(): string + { + return SystemHooks::class; + } +} diff --git a/tests/Api/TagsTest.php b/tests/Api/TagsTest.php new file mode 100644 index 000000000..2be94e67e --- /dev/null +++ b/tests/Api/TagsTest.php @@ -0,0 +1,151 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\Tags; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Test; + +class TagsTest extends TestCase +{ + #[Test] + public function shouldGetAllTags(): void + { + $expectedArray = [ + ['name' => 'v1.0.0'], + ['name' => 'v1.1.0'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/repository/tags') + ->willReturn($expectedArray); + $this->assertEquals($expectedArray, $api->all(1)); + } + + #[Test] + public function shouldShowTag(): void + { + $expectedArray = [ + ['name' => 'v1.0.0'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/repository/tags/v1.0.0') + ->willReturn($expectedArray); + $this->assertEquals($expectedArray, $api->show(1, 'v1.0.0')); + } + + #[Test] + public function shouldCreateTag(): void + { + $expectedArray = [ + ['name' => 'v1.1.0'], + ]; + + $params = [ + 'id' => 1, + 'tag_name' => 'v1.1.0', + 'ref' => 'ref/heads/master', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/repository/tags', $params) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->create(1, $params)); + } + + #[Test] + public function shouldRemoveTag(): void + { + $expectedArray = [ + ['name' => 'v1.1.0'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/repository/tags/v1.1.0') + ->willReturn($expectedArray); + $this->assertEquals($expectedArray, $api->remove(1, 'v1.1.0')); + } + + #[Test] + #[DataProvider('releaseDataProvider')] + public function shouldCreateRelease(string $releaseName, string $description, array $expectedResult): void + { + $params = [ + 'description' => $description, + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/repository/tags/'.\str_replace('/', '%2F', $releaseName).'/release', $params) + ->willReturn($expectedResult); + + $this->assertEquals($expectedResult, $api->createRelease(1, $releaseName, $params)); + } + + #[Test] + #[DataProvider('releaseDataProvider')] + public function shouldUpdateRelease(string $releaseName, string $description, array $expectedResult): void + { + $params = [ + 'description' => $description, + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/repository/tags/'.\str_replace('/', '%2F', $releaseName).'/release', $params) + ->willReturn($expectedResult); + + $this->assertEquals($expectedResult, $api->updateRelease(1, $releaseName, $params)); + } + + public static function releaseDataProvider(): array + { + return [ + [ + 'releaseName' => 'v1.1.0', + 'description' => 'Amazing release. Wow', + 'expectedResult' => [ + 'tag_name' => '1.0.0', + 'description' => 'Amazing release. Wow', + ], + ], + [ + 'releaseName' => 'version/1.1.0', + 'description' => 'Amazing release. Wow', + 'expectedResult' => [ + 'tag_name' => 'version/1.1.0', + 'description' => 'Amazing release. Wow', + ], + ], + ]; + } + + protected function getApiClass(): string + { + return Tags::class; + } +} diff --git a/tests/Api/TestCase.php b/tests/Api/TestCase.php new file mode 100644 index 000000000..34f192c60 --- /dev/null +++ b/tests/Api/TestCase.php @@ -0,0 +1,42 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Client; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase as BaseTestCase; +use Psr\Http\Client\ClientInterface; + +abstract class TestCase extends BaseTestCase +{ + abstract protected function getApiClass(): string; + + protected function getApiMock(array $methods = []): MockObject + { + $httpClient = $this->getMockBuilder(ClientInterface::class) + ->onlyMethods(['sendRequest']) + ->getMock(); + $httpClient + ->expects($this->any()) + ->method('sendRequest'); + + $client = Client::createWithHttpClient($httpClient); + + return $this->getMockBuilder($this->getApiClass()) + ->onlyMethods(\array_merge(['getAsResponse', 'get', 'post', 'delete', 'put'], $methods)) + ->setConstructorArgs([$client, null]) + ->getMock(); + } +} diff --git a/tests/Api/UsersTest.php b/tests/Api/UsersTest.php new file mode 100644 index 000000000..fdda26930 --- /dev/null +++ b/tests/Api/UsersTest.php @@ -0,0 +1,885 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\Users; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\MockObject\MockObject; + +class UsersTest extends TestCase +{ + #[Test] + public function shouldGetAllUsers(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'Matt'], + ['id' => 2, 'name' => 'John'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('users', []) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->all()); + } + + #[Test] + public function shouldGetActiveUsers(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'Matt'], + ['id' => 2, 'name' => 'John'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('users', ['active' => true]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->all(['active' => true])); + } + + #[Test] + public function shouldGetUsersWithDateTimeParams(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'Matt'], + ['id' => 2, 'name' => 'John'], + ]; + + $createdAfter = new \DateTime('2018-01-01 00:00:00'); + $createdBefore = new \DateTime('2018-01-31 00:00:00'); + + $expectedWithArray = [ + 'created_after' => $createdAfter->format(\DATE_ATOM), + 'created_before' => $createdBefore->format(\DATE_ATOM), + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('users', $expectedWithArray) + ->willReturn($expectedArray) + ; + + $this->assertEquals( + $expectedArray, + $api->all(['created_after' => $createdAfter, 'created_before' => $createdBefore]) + ); + } + + #[Test] + public function shouldShowUser(): void + { + $expectedArray = ['id' => 1, 'name' => 'Matt']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('users/1') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->show(1)); + } + + protected function getUsersMembershipsData(): array + { + return [ + [ + 'source_id' => 1, + 'source_name' => 'Project one', + 'source_type' => 'Project', + 'access_level' => '20', + ], + [ + 'source_id' => 3, + 'source_name' => 'Group three', + 'source_type' => 'Namespace', + 'access_level' => '20', + ], + ]; + } + + protected function getUsersMembershipsRequestMock($path, $expectedArray = [], $expectedParameters = []): MockObject + { + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with($path, $expectedParameters) + ->willReturn($expectedArray) + ; + + return $api; + } + + #[Test] + public function shouldShowUsersMemberships(): void + { + $expectedArray = $this->getUsersMembershipsData(); + + $api = $this->getUsersMembershipsRequestMock('users/1/memberships', $expectedArray); + + $this->assertEquals($expectedArray, $api->usersMemberships(1)); + } + + #[Test] + public function shouldShowUsersMembershipsWithTypeProject(): void + { + $expectedArray = [$this->getUsersMembershipsData()[0]]; + + $api = $this->getUsersMembershipsRequestMock('users/1/memberships', $expectedArray, ['type' => 'Project']); + + $this->assertEquals($expectedArray, $api->usersMemberships(1, ['type' => 'Project'])); + } + + #[Test] + public function shouldShowUsersMembershipsWithTypeNamespace(): void + { + $expectedArray = [$this->getUsersMembershipsData()[1]]; + + $api = $this->getUsersMembershipsRequestMock('users/1/memberships', $expectedArray, ['type' => 'Namespace']); + + $this->assertEquals($expectedArray, $api->usersMemberships(1, ['type' => 'Namespace'])); + } + + protected function getUsersProjectsData(): array + { + return [ + ['id' => 1, 'name' => 'matt-project-1'], + ['id' => 2, 'name' => 'matt-project-2'], + ]; + } + + protected function getUsersProjectsRequestMock($path, $expectedArray = [], $expectedParameters = []): MockObject + { + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with($path, $expectedParameters) + ->willReturn($expectedArray) + ; + + return $api; + } + + #[Test] + public function shouldShowUsersProjects(): void + { + $expectedArray = $this->getUsersProjectsData(); + + $api = $this->getUsersProjectsRequestMock('users/1/projects', $expectedArray); + + $this->assertEquals($expectedArray, $api->usersProjects(1)); + } + + #[Test] + public function shouldShowUsersProjectsWithLimit(): void + { + $expectedArray = [$this->getUsersProjectsData()[0]]; + + $api = $this->getUsersProjectsRequestMock('users/1/projects', $expectedArray, ['per_page' => 1]); + + $this->assertEquals($expectedArray, $api->usersProjects(1, ['per_page' => 1])); + } + + #[Test] + public function shouldGetAllUsersProjectsSortedByName(): void + { + $expectedArray = $this->getUsersProjectsData(); + + $api = $this->getUsersProjectsRequestMock( + 'users/1/projects', + $expectedArray, + ['page' => 1, 'per_page' => 5, 'order_by' => 'name', 'sort' => 'asc'] + ); + + $this->assertEquals( + $expectedArray, + $api->usersProjects(1, ['page' => 1, 'per_page' => 5, 'order_by' => 'name', 'sort' => 'asc']) + ); + } + + #[Test] + public function shouldGetNotArchivedUsersProjects(): void + { + $expectedArray = $this->getUsersProjectsData(); + + $api = $this->getUsersProjectsRequestMock('users/1/projects', $expectedArray, ['archived' => 'false']); + + $this->assertEquals($expectedArray, $api->usersProjects(1, ['archived' => false])); + } + + #[Test] + public function shouldGetOwnedUsersProjects(): void + { + $expectedArray = $this->getUsersProjectsData(); + + $api = $this->getUsersProjectsRequestMock('users/1/projects', $expectedArray, ['owned' => 'true']); + + $this->assertEquals($expectedArray, $api->usersProjects(1, ['owned' => true])); + } + + public static function possibleAccessLevels(): array + { + return [ + [10], + [20], + [30], + [40], + [50], + ]; + } + + #[Test] + #[DataProvider('possibleAccessLevels')] + public function shouldGetProjectsWithMinimumAccessLevel($level): void + { + $expectedArray = $this->getUsersProjectsData(); + + $api = $this->getUsersProjectsRequestMock('users/1/projects', $expectedArray, ['min_access_level' => $level]); + + $this->assertEquals($expectedArray, $api->usersProjects(1, ['min_access_level' => $level])); + } + + #[Test] + public function shouldSearchUsersProjects(): void + { + $expectedArray = $this->getUsersProjectsData(); + + $api = $this->getUsersProjectsRequestMock('users/1/projects', $expectedArray, ['search' => 'a project']); + $this->assertEquals($expectedArray, $api->usersProjects(1, ['search' => 'a project'])); + } + + #[Test] + public function shouldShowUsersStarredProjects(): void + { + $expectedArray = $this->getUsersProjectsData(); + + $api = $this->getUsersProjectsRequestMock('users/1/starred_projects', $expectedArray); + + $this->assertEquals($expectedArray, $api->usersStarredProjects(1)); + } + + #[Test] + public function shouldShowUsersStarredProjectsWithLimit(): void + { + $expectedArray = [$this->getUsersProjectsData()[0]]; + + $api = $this->getUsersProjectsRequestMock('users/1/starred_projects', $expectedArray, ['per_page' => 1]); + + $this->assertEquals($expectedArray, $api->usersStarredProjects(1, ['per_page' => 1])); + } + + #[Test] + public function shouldGetAllUsersStarredProjectsSortedByName(): void + { + $expectedArray = $this->getUsersProjectsData(); + + $api = $this->getUsersProjectsRequestMock( + 'users/1/starred_projects', + $expectedArray, + ['page' => 1, 'per_page' => 5, 'order_by' => 'name', 'sort' => 'asc'] + ); + + $this->assertEquals( + $expectedArray, + $api->usersStarredProjects(1, ['page' => 1, 'per_page' => 5, 'order_by' => 'name', 'sort' => 'asc']) + ); + } + + #[Test] + public function shouldGetNotArchivedUsersStarredProjects(): void + { + $expectedArray = $this->getUsersProjectsData(); + + $api = $this->getUsersProjectsRequestMock('users/1/starred_projects', $expectedArray, ['archived' => 'false']); + + $this->assertEquals($expectedArray, $api->usersStarredProjects(1, ['archived' => false])); + } + + #[Test] + public function shouldGetOwnedUsersStarredProjects(): void + { + $expectedArray = $this->getUsersProjectsData(); + + $api = $this->getUsersProjectsRequestMock('users/1/starred_projects', $expectedArray, ['owned' => 'true']); + + $this->assertEquals($expectedArray, $api->usersStarredProjects(1, ['owned' => true])); + } + + #[Test] + #[DataProvider('possibleAccessLevels')] + public function shouldGetStarredProjectsWithMinimumAccessLevel($level): void + { + $expectedArray = $this->getUsersProjectsData(); + + $api = $this->getUsersProjectsRequestMock('users/1/starred_projects', $expectedArray, ['min_access_level' => $level]); + + $this->assertEquals($expectedArray, $api->usersStarredProjects(1, ['min_access_level' => $level])); + } + + #[Test] + public function shouldSearchUsersStarredProjects(): void + { + $expectedArray = $this->getUsersProjectsData(); + + $api = $this->getUsersProjectsRequestMock('users/1/starred_projects', $expectedArray, ['search' => 'a project']); + $this->assertEquals($expectedArray, $api->usersStarredProjects(1, ['search' => 'a project'])); + } + + #[Test] + public function shouldCreateUser(): void + { + $expectedArray = ['id' => 3, 'name' => 'Billy']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('users', ['email' => 'billy@example.com', 'password' => 'password']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->create('billy@example.com', 'password')); + } + + #[Test] + public function shouldCreateUserWithAdditionalInfo(): void + { + $expectedArray = ['id' => 3, 'name' => 'Billy']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('users', ['email' => 'billy@example.com', 'password' => 'password', 'name' => 'Billy', 'bio' => 'A person']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->create('billy@example.com', 'password', ['name' => 'Billy', 'bio' => 'A person'])); + } + + #[Test] + public function shouldUpdateUser(): void + { + $expectedArray = ['id' => 3, 'name' => 'Billy Bob']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('users/3', ['name' => 'Billy Bob']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->update(3, ['name' => 'Billy Bob'])); + + $expectedArray = ['id' => 4, 'avatar_url' => 'http://localhost:3000/uploads/user/avatar/4/image.jpg']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('users/4', [], [], ['avatar' => '/some/image.jpg']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->update(4, [], ['avatar' => '/some/image.jpg'])); + } + + #[Test] + public function shouldRemoveUser(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('users/1') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->remove(1)); + } + + #[Test] + public function shouldBlockUser(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('users/1/block') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->block(1)); + } + + #[Test] + public function shouldUnblockUser(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('users/1/unblock') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->unblock(1)); + } + + #[Test] + public function shouldActivateUser(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('users/1/activate') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->activate(1)); + } + + #[Test] + public function shouldDeactivateUser(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('users/1/deactivate') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->deactivate(1)); + } + + #[Test] + public function shouldShowCurrentUser(): void + { + $expectedArray = ['id' => 1, 'name' => 'Matt']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('user') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->me()); + } + + #[Test] + public function shouldGetCurrentUserKeys(): void + { + $expectedArray = [ + ['id' => 1, 'title' => 'A key'], + ['id' => 2, 'name' => 'Another key'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('user/keys') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->keys(1)); + } + + #[Test] + public function shouldGetCurrentUserKey(): void + { + $expectedArray = ['id' => 1, 'title' => 'A key']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('user/keys/1') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->key(1)); + } + + #[Test] + public function shouldCreateKeyForCurrentUser(): void + { + $expectedArray = ['id' => 3, 'title' => 'A new key']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('user/keys', ['title' => 'A new key', 'key' => '...']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->createKey('A new key', '...')); + } + + #[Test] + public function shouldDeleteKeyForCurrentUser(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('user/keys/3') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->removeKey(3)); + } + + #[Test] + public function shouldGetUserKeys(): void + { + $expectedArray = [ + ['id' => 1, 'title' => 'A key'], + ['id' => 2, 'name' => 'Another key'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('users/1/keys') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->userKeys(1)); + } + + #[Test] + public function shouldGetUserKey(): void + { + $expectedArray = ['id' => 2, 'title' => 'Another key']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('users/1/keys/2') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->userKey(1, 2)); + } + + #[Test] + public function shouldCreateKeyForUser(): void + { + $expectedArray = ['id' => 3, 'title' => 'A new key']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('users/1/keys', ['title' => 'A new key', 'key' => '...']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->createKeyForUser(1, 'A new key', '...')); + } + + #[Test] + public function shouldDeleteKeyForUser(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('users/1/keys/3') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->removeUserKey(1, 3)); + } + + #[Test] + public function shouldGetUserEmails(): void + { + $expectedArray = [ + ['id' => 1, 'email' => 'foo@bar.baz'], + ['id' => 2, 'email' => 'foo@bar.qux'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('user/emails') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->emails()); + } + + #[Test] + public function shouldGetSpecificUserEmail(): void + { + $expectedArray = ['id' => 1, 'email' => 'foo@bar.baz']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('user/emails/1') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->email(1)); + } + + #[Test] + public function shouldGetEmailsForUser(): void + { + $expectedArray = [ + ['id' => 1, 'email' => 'foo@bar.baz'], + ['id' => 2, 'email' => 'foo@bar.qux'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('users/1/emails') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->userEmails(1)); + } + + #[Test] + public function shouldCreateEmailForUser(): void + { + $expectedArray = ['id' => 3, 'email' => 'foo@bar.example']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('users/1/emails', ['email' => 'foo@bar.example', 'skip_confirmation' => false]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->createEmailForUser(1, 'foo@bar.example')); + } + + #[Test] + public function shouldCreateConfirmedEmailForUser(): void + { + $expectedArray = ['id' => 4, 'email' => 'foo@baz.example']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('users/1/emails', ['email' => 'foo@baz.example', 'skip_confirmation' => true]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->createEmailForUser(1, 'foo@baz.example', true)); + } + + #[Test] + public function shouldDeleteEmailForUser(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('users/1/emails/3') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->removeUserEmail(1, 3)); + } + + #[Test] + public function shouldGetCurrentUserImpersonationTokens(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'A Name', 'revoked' => false], + ['id' => 2, 'name' => 'A Name', 'revoked' => false], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('users/1/impersonation_tokens') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->userImpersonationTokens(1)); + } + + #[Test] + public function shouldGetUserImpersonationToken(): void + { + $expectedArray = ['id' => 2, 'name' => 'name']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('users/1/impersonation_tokens/1') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->userImpersonationToken(1, 1)); + } + + #[Test] + public function shouldCreateImpersonationTokenForUser(): void + { + $expectedArray = ['id' => 1, 'name' => 'name']; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('users/1/impersonation_tokens', ['name' => 'name', 'scopes' => ['api'], 'expires_at' => null]) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->createImpersonationToken(1, 'name', ['api'])); + } + + #[Test] + public function shouldDeleteImpersonationTokenForUser(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('users/1/impersonation_tokens/1') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->removeImpersonationToken(1, 1)); + } + + #[Test] + public function shouldGetCurrentUserActiveImpersonationTokens(): void + { + $expectedArray = [ + ['id' => 1, 'name' => 'A Name', 'revoked' => true], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('users/1/impersonation_tokens') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->userImpersonationTokens(1, ['state' => 'active'])); + } + + #[Test] + public function shouldGetCurrentUserInactiveImpersonationTokens(): void + { + $expectedArray = [ + ['id' => 2, 'name' => 'A Name', 'revoked' => false], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('users/1/impersonation_tokens') + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->userImpersonationTokens(1, ['state' => 'inactive'])); + } + + protected function getApiClass(): string + { + return Users::class; + } + + #[Test] + public function shouldGetEvents(): void + { + $expectedArray = [ + ['id' => 1, 'title' => 'An event'], + ['id' => 2, 'title' => 'Another event'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('users/1/events', []) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->events(1)); + } + + #[Test] + public function shouldGetEventsWithDateTimeParams(): void + { + $expectedArray = [ + ['id' => 1, 'title' => 'An event'], + ['id' => 2, 'title' => 'Another event'], + ]; + + $after = new \DateTime('2018-01-01 00:00:00'); + $before = new \DateTime('2018-01-31 00:00:00'); + + $expectedWithArray = [ + 'after' => $after->format('Y-m-d'), + 'before' => $before->format('Y-m-d'), + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('users/1/events', $expectedWithArray) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->events(1, ['after' => $after, 'before' => $before])); + } + + #[Test] + public function shouldGetEventsWithPagination(): void + { + $expectedArray = [ + ['id' => 1, 'title' => 'An event'], + ['id' => 2, 'title' => 'Another event'], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('users/1/events', [ + 'page' => 2, + 'per_page' => 15, + ]) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->events(1, ['page' => 2, 'per_page' => 15])); + } + + #[Test] + public function getRemoveUserIdentity(): void + { + $expectedArray = [ + ['id' => 1, 'identities' => []], + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('users/1/identities/test') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->removeUserIdentity(1, 'test')); + } +} diff --git a/tests/Api/VersionTest.php b/tests/Api/VersionTest.php new file mode 100644 index 000000000..63080a79c --- /dev/null +++ b/tests/Api/VersionTest.php @@ -0,0 +1,42 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\Version; +use PHPUnit\Framework\Attributes\Test; + +class VersionTest extends TestCase +{ + #[Test] + public function shouldShowVersion(): void + { + $expectedArray = [ + 'version' => '8.13.0-pre', + 'revision' => '4e963fe', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('version') + ->willReturn($expectedArray); + $this->assertEquals($expectedArray, $api->show()); + } + + protected function getApiClass(): string + { + return Version::class; + } +} diff --git a/tests/Api/WikiTest.php b/tests/Api/WikiTest.php new file mode 100644 index 000000000..4bb28b52c --- /dev/null +++ b/tests/Api/WikiTest.php @@ -0,0 +1,129 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\Api; + +use Gitlab\Api\Wiki; +use PHPUnit\Framework\Attributes\Test; + +class WikiTest extends TestCase +{ + #[Test] + public function shouldCreateWiki(): void + { + $expectedArray = [ + 'format' => 'markdown', + 'slug' => 'Test-Wiki', + 'title' => 'Test Wiki', + 'content' => 'This is the test Wiki', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('post') + ->with('projects/1/wikis', [ + 'format' => 'markdown', + 'title' => 'Test Wiki', + 'content' => 'This is the test Wiki', + ]) + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->create( + 1, + [ + 'format' => 'markdown', + 'title' => 'Test Wiki', + 'content' => 'This is the test Wiki', + ] + )); + } + + #[Test] + public function shouldShowWiki(): void + { + $expectedArray = [ + 'slug' => 'Test-Wiki', + 'title' => 'Test Wiki', + 'format' => 'markdown', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/wikis/Test-Wiki') + ->willReturn($expectedArray); + + $this->assertEquals($expectedArray, $api->show(1, 'Test-Wiki')); + } + + #[Test] + public function shouldShowAllWiki(): void + { + $expectedArray = [ + 'slug' => 'Test-Wiki', + 'title' => 'Test Wiki', + 'format' => 'markdown', + ]; + + $params = ['with_content' => true]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('projects/1/wikis', $params) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->showAll(1, $params)); + } + + #[Test] + public function shouldUpdateWiki(): void + { + $expectedArray = [ + 'slug' => 'Test-Wiki', + 'title' => 'Test Wiki', + 'format' => 'markdown', + 'content' => 'This is the test Wiki that has been updated', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('put') + ->with('projects/1/wikis/Test-Wiki', ['content' => 'This is the test Wiki that has been updated']) + ->willReturn($expectedArray) + ; + + $this->assertEquals($expectedArray, $api->update(1, 'Test-Wiki', ['content' => 'This is the test Wiki that has been updated'])); + } + + #[Test] + public function shouldRemoveWiki(): void + { + $expectedBool = true; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('delete') + ->with('projects/1/wikis/Test-Wiki') + ->willReturn($expectedBool); + + $this->assertEquals($expectedBool, $api->remove(1, 'Test-Wiki')); + } + + protected function getApiClass(): string + { + return Wiki::class; + } +} diff --git a/tests/ClientTest.php b/tests/ClientTest.php new file mode 100644 index 000000000..72bff09db --- /dev/null +++ b/tests/ClientTest.php @@ -0,0 +1,30 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests; + +use Gitlab\Client; +use Http\Client\Common\HttpMethodsClient; +use PHPUnit\Framework\TestCase; + +class ClientTest extends TestCase +{ + public function testCreateClient(): void + { + $client = new Client(); + + $this->assertInstanceOf(Client::class, $client); + $this->assertInstanceOf(HttpMethodsClient::class, $client->getHttpClient()); + } +} diff --git a/tests/HttpClient/BuilderTest.php b/tests/HttpClient/BuilderTest.php new file mode 100644 index 000000000..56ce8a5a7 --- /dev/null +++ b/tests/HttpClient/BuilderTest.php @@ -0,0 +1,72 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\HttpClient; + +use Gitlab\HttpClient\Builder; +use Http\Client\Common\HttpMethodsClientInterface; +use Http\Client\Common\Plugin; +use PHPUnit\Framework\Attributes\Before; +use PHPUnit\Framework\TestCase; +use Psr\Http\Client\ClientInterface; +use Psr\Http\Message\RequestFactoryInterface; +use Psr\Http\Message\StreamFactoryInterface; + +/** + * @author Fabien Bourigault + */ +class BuilderTest extends TestCase +{ + private Builder $subject; + + #[Before] + public function initBuilder(): void + { + $this->subject = new Builder( + $this->createMock(ClientInterface::class), + $this->createMock(RequestFactoryInterface::class), + $this->createMock(StreamFactoryInterface::class) + ); + } + + public function testAddPluginShouldInvalidateHttpClient(): void + { + $client = $this->subject->getHttpClient(); + + $this->subject->addPlugin($this->createMock(Plugin::class)); + + $this->assertNotSame($client, $this->subject->getHttpClient()); + } + + public function testRemovePluginShouldInvalidateHttpClient(): void + { + $this->subject->addPlugin($this->createMock(Plugin::class)); + + $client = $this->subject->getHttpClient(); + + $this->subject->removePlugin(Plugin::class); + + $this->assertNotSame($client, $this->subject->getHttpClient()); + } + + public function testHttpClientShouldBeAnHttpMethodsClient(): void + { + $this->assertInstanceOf(HttpMethodsClientInterface::class, $this->subject->getHttpClient()); + } + + public function testStreamFactoryShouldBeAStreamFactory(): void + { + $this->assertInstanceOf(StreamFactoryInterface::class, $this->subject->getStreamFactory()); + } +} diff --git a/tests/HttpClient/Message/ResponseMediatorTest.php b/tests/HttpClient/Message/ResponseMediatorTest.php new file mode 100644 index 000000000..7266ecfa5 --- /dev/null +++ b/tests/HttpClient/Message/ResponseMediatorTest.php @@ -0,0 +1,93 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\HttpClient\Message; + +use Gitlab\Exception\RuntimeException; +use Gitlab\HttpClient\Message\ResponseMediator; +use GuzzleHttp\Psr7\Response; +use GuzzleHttp\Psr7\Utils; +use PHPUnit\Framework\TestCase; + +/** + * @author Tobias Nyholm + * @author Graham Campbell + */ +class ResponseMediatorTest extends TestCase +{ + public function testGetContent(): void + { + $response = new Response( + 200, + ['Content-Type' => 'application/json'], + Utils::streamFor('{"foo": "bar"}') + ); + + $this->assertSame(['foo' => 'bar'], ResponseMediator::getContent($response)); + } + + public function testGetContentNotJson(): void + { + $response = new Response( + 200, + [], + Utils::streamFor('foobar') + ); + + $this->assertSame('foobar', ResponseMediator::getContent($response)); + } + + public function testGetContentInvalidJson(): void + { + $response = new Response( + 200, + ['Content-Type' => 'application/json'], + Utils::streamFor('foobar') + ); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('json_decode error: Syntax error'); + + ResponseMediator::getContent($response); + } + + public function testGetErrrorMessageInvalidJson(): void + { + $response = new Response( + 200, + ['Content-Type' => 'application/json'], + Utils::streamFor('foobar') + ); + + $this->assertNull(ResponseMediator::getErrorMessage($response)); + } + + public function testGetPagination(): void + { + $header = '; rel="first",; rel="next",; rel="prev",; rel="last"'; + + $pagination = [ + 'first' => 'https://example.gitlab.com', + 'next' => 'https://example.gitlab.com', + 'prev' => 'https://example.gitlab.com', + 'last' => 'https://example.gitlab.com', + ]; + + // response mock + $response = new Response(200, ['link' => $header]); + $result = ResponseMediator::getPagination($response); + + $this->assertSame($pagination, $result); + } +} diff --git a/tests/HttpClient/Util/QueryStringBuilderTest.php b/tests/HttpClient/Util/QueryStringBuilderTest.php new file mode 100644 index 000000000..e3466897a --- /dev/null +++ b/tests/HttpClient/Util/QueryStringBuilderTest.php @@ -0,0 +1,98 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests\HttpClient\Util; + +use Generator; +use Gitlab\HttpClient\Util\QueryStringBuilder; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +class QueryStringBuilderTest extends TestCase +{ + #[DataProvider('queryStringProvider')] + public function testBuild(array $query, string $expected): void + { + $this->assertSame(\sprintf('?%s', $expected), QueryStringBuilder::build($query)); + } + + public static function queryStringProvider(): Generator + { + yield 'indexed array' => [ + [ + 'iids' => [88, 86], + ], + 'iids%5B%5D=88&iids%5B%5D=86', + ]; + + yield 'non-indexed array with only numeric keys' => [ + [ + 'iids' => [0 => 88, 2 => 86], + ], + 'iids%5B0%5D=88&iids%5B2%5D=86', + ]; + + yield 'indexed array with multiple entries' => [ + [ + 'source_branch' => 'test_source', + 'target_branch' => 'test_master', + 'title' => 'test', + ], + 'source_branch=test_source&target_branch=test_master&title=test', + ]; + + yield 'boolean encoding with multiple entries' => [ + [ + 'push_events' => false, + 'merge_requests_events' => 1, + ], + 'push_events=0&merge_requests_events=1', + ]; + + yield 'deeply nested array' => [ + [ + 'search' => 'a project', + 'owned' => 'true', + 'iids' => [88, 86], + 'assoc' => [ + 'a' => 'b', + 'c' => [ + 'd' => 'e', + 'f' => 'g', + ], + ], + 'nested' => [ + 'a' => [ + [ + 'b' => 'c', + ], + [ + 'd' => 'e', + 'f' => [ + 'g' => 'h', + 'i' => 'j', + 'k' => [87, 89], + ], + ], + ], + ], + ], + 'search=a%20project&owned=true&iids%5B%5D=88&iids%5B%5D=86'. + '&assoc%5Ba%5D=b&assoc%5Bc%5D%5Bd%5D=e&assoc%5Bc%5D%5Bf%5D=g'. + '&nested%5Ba%5D%5B%5D%5Bb%5D=c&nested%5Ba%5D%5B%5D%5Bd%5D=e'. + '&nested%5Ba%5D%5B%5D%5Bf%5D%5Bg%5D=h&nested%5Ba%5D%5B%5D%5Bf%5D%5Bi%5D=j'. + '&nested%5Ba%5D%5B%5D%5Bf%5D%5Bk%5D%5B%5D=87&nested%5Ba%5D%5B%5D%5Bf%5D%5Bk%5D%5B%5D=89', + ]; + } +} diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php new file mode 100644 index 000000000..e095c4eac --- /dev/null +++ b/tests/IntegrationTest.php @@ -0,0 +1,47 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gitlab\Tests; + +use Gitlab\Client; +use Gitlab\Exception\RuntimeException; +use PHPUnit\Framework\TestCase; + +class IntegrationTest extends TestCase +{ + public function testRepoContributors(): void + { + $client = new Client(); + + $response = $client + ->repositories() + ->contributors(16155465); + + $this->assertIsArray($response); + $this->assertTrue(isset($response[2])); + $this->assertTrue(isset($response[2]['name'])); + } + + public function testRepoNotFound(): void + { + $client = new Client(); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('404 Project Not Found'); + + $client + ->repositories() + ->contributors(1); + } +} diff --git a/vendor-bin/phpstan/composer.json b/vendor-bin/phpstan/composer.json new file mode 100644 index 000000000..a1bd73c47 --- /dev/null +++ b/vendor-bin/phpstan/composer.json @@ -0,0 +1,11 @@ +{ + "require": { + "php": "^8.1", + "phpstan/phpstan": "2.1.6", + "phpstan/phpstan-deprecation-rules": "2.0.1", + "phpstan/phpstan-strict-rules": "2.0.3" + }, + "config": { + "preferred-install": "dist" + } +} diff --git a/vendor-bin/phpunit/composer.json b/vendor-bin/phpunit/composer.json new file mode 100644 index 000000000..21464a1e9 --- /dev/null +++ b/vendor-bin/phpunit/composer.json @@ -0,0 +1,9 @@ +{ + "require": { + "php": "^8.1", + "phpunit/phpunit": "^10.5.45 || ^11.5.9" + }, + "config": { + "preferred-install": "dist" + } +} diff --git a/vendor-bin/psalm/composer.json b/vendor-bin/psalm/composer.json new file mode 100644 index 000000000..feebf503a --- /dev/null +++ b/vendor-bin/psalm/composer.json @@ -0,0 +1,9 @@ +{ + "require": { + "php": "^8.1", + "psalm/phar": "5.26.1" + }, + "config": { + "preferred-install": "dist" + } +}