├── .editorconfig
├── .github
├── CONTRIBUTING.md
├── SECURITY.md
└── workflows
│ ├── analyse.yml
│ ├── changelog.yml
│ ├── coverage.yml
│ ├── style.yml
│ └── tests.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── UPGRADE.md
├── art
├── banner.svg
└── footer.svg
├── composer.json
├── config
└── magento.php
├── database
└── migrations
│ └── 2023_12_11_150000_create_magento_oauth_keys_table.php
├── docs
└── migrations
│ └── GRAYLOON.md
├── phpstan.neon
├── phpunit.xml
├── routes
└── web.php
├── src
├── Actions
│ ├── AuthenticateRequest.php
│ ├── BuildRequest.php
│ ├── CheckMagento.php
│ └── OAuth
│ │ └── RequestAccessToken.php
├── Client
│ └── Magento.php
├── Contracts
│ ├── AuthenticatesRequest.php
│ ├── BuildsRequest.php
│ ├── ChecksMagento.php
│ └── OAuth
│ │ └── RequestsAccessToken.php
├── Enums
│ └── AuthenticationMethod.php
├── Events
│ └── MagentoResponseEvent.php
├── Http
│ ├── Controllers
│ │ └── OAuthController.php
│ ├── Middleware
│ │ └── OAuthMiddleware.php
│ └── Requests
│ │ ├── CallbackRequest.php
│ │ └── IdentityRequest.php
├── Jobs
│ └── Middleware
│ │ └── AvailableMiddleware.php
├── Listeners
│ └── StoreAvailabilityListener.php
├── Models
│ └── OAuthKey.php
├── OAuth
│ ├── HmacSha256Signature.php
│ ├── KeyStore
│ │ ├── DatabaseKeyStore.php
│ │ ├── FileKeyStore.php
│ │ └── KeyStore.php
│ └── MagentoServer.php
├── Providers
│ ├── BaseProvider.php
│ ├── BearerTokenProvider.php
│ └── OAuthProvider.php
├── Query
│ ├── Grammar.php
│ └── SearchCriteria.php
└── ServiceProvider.php
└── tests
├── Actions
├── CheckMagentoTest.php
└── OAuth
│ └── RequestAccessTokenTest.php
├── Client
├── ClientTest.php
└── MultiConnectionTest.php
├── Enums
└── AuthenticationMethodTest.php
├── Fakes
└── TestJob.php
├── Http
├── Controllers
│ └── OAuthControllerTest.php
└── Middleware
│ └── OAuthMiddlewareTest.php
├── Jobs
└── Middleware
│ └── AvailableMiddlewareTest.php
├── Listeners
└── StoreAvailabilityListenerTest.php
├── OAuth
├── HmacSha256SignatureTest.php
└── KeyStore
│ ├── DatabaseKeyStoreTest.php
│ └── FileKeyStoreTest.php
├── Providers
├── BearerTokenProviderTest.php
└── OAuthProviderTest.php
├── Query
└── SearchCriteriaTest.php
└── TestCase.php
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*]
2 | charset = utf-8
3 | end_of_line = lf
4 | insert_final_newline = true
5 | indent_style = space
6 | indent_size = 4
7 | trim_trailing_whitespace = true
8 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Contributions are **welcome** and will be fully **credited**.
4 |
5 | Please read and understand the contribution guide before creating an issue or pull request.
6 |
7 | ## Etiquette
8 |
9 | This project is open source, and as such, the maintainers give their free time to build and maintain the source code
10 | held within. They make the code freely available in the hope that it will be of use to other developers. It would be
11 | extremely unfair for them to suffer abuse or anger for their hard work.
12 |
13 | Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the
14 | world that developers are civilized and selfless people.
15 |
16 | It's the duty of the maintainer to ensure that all submissions to the project are of sufficient
17 | quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used.
18 |
19 | ## Viability
20 |
21 | When requesting or submitting new features, first consider whether it might be useful to others. Open
22 | source projects are used by many developers, who may have entirely different needs to your own. Think about
23 | whether or not your feature is likely to be used by other users of the project.
24 |
25 | ## Procedure
26 |
27 | Before filing an issue:
28 |
29 | - Attempt to replicate the problem, to ensure that it wasn't a coincidental incident.
30 | - Check to make sure your feature suggestion isn't already present within the project.
31 | - Check the pull requests tab to ensure that the bug doesn't have a fix in progress.
32 | - Check the pull requests tab to ensure that the feature isn't already in progress.
33 |
34 | Before submitting a pull request:
35 |
36 | - Check the codebase to ensure that your feature doesn't already exist.
37 | - Check the pull requests to ensure that another person hasn't already submitted the feature or fix.
38 |
39 | ## Requirements
40 |
41 | If the project maintainer has any additional requirements, you will find them listed here.
42 |
43 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](https://pear.php.net/package/PHP_CodeSniffer).
44 |
45 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests.
46 |
47 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.
48 |
49 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option.
50 |
51 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
52 |
53 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](https://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting.
54 |
55 | **Happy coding**!
56 |
--------------------------------------------------------------------------------
/.github/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | If you discover any security related issues, please email security@justbetter.nl instead of using the issue tracker.
4 |
--------------------------------------------------------------------------------
/.github/workflows/analyse.yml:
--------------------------------------------------------------------------------
1 | name: analyse
2 |
3 | on: ['push', 'pull_request']
4 |
5 | jobs:
6 | test:
7 | runs-on: ${{ matrix.os }}
8 | strategy:
9 | fail-fast: true
10 | matrix:
11 | os: [ubuntu-latest]
12 | php: [8.3, 8.4]
13 | laravel: [11.*]
14 | stability: [prefer-stable]
15 | include:
16 | - laravel: 11.*
17 | testbench: 9.*
18 |
19 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }}
20 |
21 | steps:
22 | - name: Checkout code
23 | uses: actions/checkout@v4
24 |
25 | - name: Setup PHP
26 | uses: shivammathur/setup-php@v2
27 | with:
28 | php-version: ${{ matrix.php }}
29 | extensions: dom, curl, libxml, mbstring, zip, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
30 | coverage: none
31 |
32 | - name: Install dependencies
33 | run: |
34 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
35 | composer update --${{ matrix.stability }} --prefer-dist --no-interaction
36 | - name: Analyse
37 | run: composer analyse
38 |
--------------------------------------------------------------------------------
/.github/workflows/changelog.yml:
--------------------------------------------------------------------------------
1 | name: "Update Changelog"
2 |
3 | on:
4 | release:
5 | types: [ published, edited, deleted ]
6 |
7 | jobs:
8 | generate:
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - name: Checkout code
13 | uses: actions/checkout@v3
14 | with:
15 | ref: ${{ github.event.release.target_commitish }}
16 |
17 | - name: Generate changelog
18 | uses: justbetter/generate-changelogs-action@main
19 | env:
20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
21 | with:
22 | repository: ${{ github.repository }}
23 |
24 | - name: Commit CHANGELOG
25 | uses: stefanzweifel/git-auto-commit-action@v4
26 | with:
27 | branch: ${{ github.event.release.target_commitish }}
28 | commit_message: Update CHANGELOG
29 | file_pattern: CHANGELOG.md
--------------------------------------------------------------------------------
/.github/workflows/coverage.yml:
--------------------------------------------------------------------------------
1 | name: coverage
2 |
3 | on: ['push', 'pull_request']
4 |
5 | jobs:
6 | test:
7 | runs-on: ${{ matrix.os }}
8 | strategy:
9 | fail-fast: true
10 | matrix:
11 | os: [ubuntu-latest]
12 | php: [8.4]
13 | laravel: [11.*]
14 | stability: [prefer-stable]
15 | include:
16 | - laravel: 11.*
17 | testbench: 9.*
18 |
19 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }}
20 |
21 | steps:
22 | - name: Checkout code
23 | uses: actions/checkout@v4
24 |
25 | - name: Setup PHP
26 | uses: shivammathur/setup-php@v2
27 | with:
28 | php-version: ${{ matrix.php }}
29 | extensions: dom, curl, libxml, mbstring, zip, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo, xdebug
30 | coverage: xdebug
31 |
32 | - name: Install dependencies
33 | run: |
34 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
35 | composer update --${{ matrix.stability }} --prefer-dist --no-interaction
36 | - name: Execute tests
37 | run: composer coverage
38 |
--------------------------------------------------------------------------------
/.github/workflows/style.yml:
--------------------------------------------------------------------------------
1 | name: style
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | jobs:
8 | style:
9 | name: Style
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Checkout code
14 | uses: actions/checkout@v4
15 |
16 | - name: Setup PHP
17 | uses: shivammathur/setup-php@v2
18 | with:
19 | php-version: 8.4
20 | extensions: dom, curl, libxml, mbstring, zip, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
21 | coverage: none
22 |
23 | - name: Install dependencies
24 | run: composer install
25 |
26 | - name: Style
27 | run: composer fix-style
28 |
29 | - name: Commit Changes
30 | uses: stefanzweifel/git-auto-commit-action@v4
31 | with:
32 | commit_message: Fix styling changes
33 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: tests
2 |
3 | on: ['push', 'pull_request']
4 |
5 | jobs:
6 | test:
7 | runs-on: ${{ matrix.os }}
8 | strategy:
9 | fail-fast: true
10 | matrix:
11 | os: [ubuntu-latest]
12 | php: [8.3, 8.4]
13 | laravel: [11.*]
14 | stability: [prefer-lowest, prefer-stable]
15 | include:
16 | - laravel: 11.*
17 | testbench: 9.*
18 |
19 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }}
20 |
21 | steps:
22 | - name: Checkout code
23 | uses: actions/checkout@v4
24 |
25 | - name: Setup PHP
26 | uses: shivammathur/setup-php@v2
27 | with:
28 | php-version: ${{ matrix.php }}
29 | extensions: dom, curl, libxml, mbstring, zip, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
30 | coverage: none
31 |
32 | - name: Install dependencies
33 | run: |
34 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
35 | composer update --${{ matrix.stability }} --prefer-dist --no-interaction
36 | - name: Execute tests
37 | run: composer test
38 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor
2 | composer.lock
3 | .phpunit.result.cache
4 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | [Unreleased changes](https://github.com/justbetter/laravel-magento-client/compare/2.7.0...main)
4 | ## [2.7.0](https://github.com/justbetter/laravel-magento-client/releases/tag/2.7.0) - 2025-02-13
5 |
6 | ### What's Changed
7 | * fix typo for docs by @fritsjustbetter in https://github.com/justbetter/laravel-magento-client/pull/44
8 | * Laravel 12 support by @VincentBean in https://github.com/justbetter/laravel-magento-client/pull/48
9 | * Drop PHP 8.1 support
10 |
11 | ### New Contributors
12 | * @fritsjustbetter made their first contribution in https://github.com/justbetter/laravel-magento-client/pull/44
13 |
14 | **Full Changelog**: https://github.com/justbetter/laravel-magento-client/compare/2.6.1...2.7.0
15 |
16 | ## [2.6.1](https://github.com/justbetter/laravel-magento-client/releases/tag/2.6.1) - 2024-09-09
17 |
18 | ### What's Changed
19 | * Increase release time to 30 seconds by @VincentBean in https://github.com/justbetter/laravel-magento-client/pull/43
20 |
21 |
22 | **Full Changelog**: https://github.com/justbetter/laravel-magento-client/compare/2.6.0...2.6.1
23 |
24 | ## [2.6.0](https://github.com/justbetter/laravel-magento-client/releases/tag/2.6.0) - 2024-08-23
25 |
26 | ### What's Changed
27 | * Check Magento availability by @VincentBean in https://github.com/justbetter/laravel-magento-client/pull/42
28 |
29 |
30 | **Full Changelog**: https://github.com/justbetter/laravel-magento-client/compare/2.5.0...2.6.0
31 |
32 | ## [2.5.0](https://github.com/justbetter/laravel-magento-client/releases/tag/2.5.0) - 2024-05-21
33 |
34 | ### What's Changed
35 | * Dispatch event on responses by @VincentBean in https://github.com/justbetter/laravel-magento-client/pull/41
36 |
37 |
38 | **Full Changelog**: https://github.com/justbetter/laravel-magento-client/compare/2.4.0...2.5.0
39 |
40 | ## [2.4.0](https://github.com/justbetter/laravel-magento-client/releases/tag/2.4.0) - 2024-03-14
41 |
42 | ### What's Changed
43 | * Added support for Laravel 11 by @ramonrietdijk in https://github.com/justbetter/laravel-magento-client/pull/37
44 |
45 |
46 | **Full Changelog**: https://github.com/justbetter/laravel-magento-client/compare/2.3.0...2.4.0
47 |
48 | ## [2.3.0](https://github.com/justbetter/laravel-magento-client/releases/tag/2.3.0) - 2024-02-05
49 |
50 | ### What's Changed
51 | * Added ordering to the search criteria by @ramonrietdijk in https://github.com/justbetter/laravel-magento-client/pull/35
52 |
53 |
54 | **Full Changelog**: https://github.com/justbetter/laravel-magento-client/compare/2.2.0...2.3.0
55 |
56 | ## [2.2.0](https://github.com/justbetter/laravel-magento-client/releases/tag/2.2.0) - 2024-01-26
57 |
58 | ### What's Changed
59 | * PendingRequest changes per cycle. by @mennotempelaar in https://github.com/justbetter/laravel-magento-client/pull/34
60 |
61 | ### New Contributors
62 | * @mennotempelaar made their first contribution in https://github.com/justbetter/laravel-magento-client/pull/34
63 |
64 | **Full Changelog**: https://github.com/justbetter/laravel-magento-client/compare/2.1.0...2.2.0
65 |
66 | ## [2.1.0](https://github.com/justbetter/laravel-magento-client/releases/tag/2.1.0) - 2024-01-19
67 |
68 | ### What's Changed
69 | * Add Graphql support by @indykoning in https://github.com/justbetter/laravel-magento-client/pull/32
70 |
71 | ### New Contributors
72 | * @indykoning made their first contribution in https://github.com/justbetter/laravel-magento-client/pull/32
73 |
74 | **Full Changelog**: https://github.com/justbetter/laravel-magento-client/compare/2.0.0...2.1.0
75 |
76 | ## [2.0.0](https://github.com/justbetter/laravel-magento-client/releases/tag/2.0.0) - 2023-12-13
77 |
78 | ### What's Changed
79 | * Support multiple connections by @VincentBean in https://github.com/justbetter/laravel-magento-client/pull/31
80 |
81 | See UPGRADE.md on how to upgrade from 1.x
82 |
83 | **Full Changelog**: https://github.com/justbetter/laravel-magento-client/compare/1.5.0...2.0.0
84 |
85 | ## [1.5.0](https://github.com/justbetter/laravel-magento-client/releases/tag/1.5.0) - 2023-09-16
86 |
87 | ### What's Changed
88 | * Upgrade Magento Class to accept bulk option by @lucassmacedo in https://github.com/justbetter/laravel-magento-client/pull/27
89 |
90 | ### New Contributors
91 | * @lucassmacedo made their first contribution in https://github.com/justbetter/laravel-magento-client/pull/27
92 |
93 | **Full Changelog**: https://github.com/justbetter/laravel-magento-client/compare/1.4.0...1.5.0
94 |
95 | ## [1.4.0](https://github.com/justbetter/laravel-magento-client/releases/tag/1.4.0) - 2023-06-07
96 |
97 | ### What's Changed
98 | * Add async methods by @VincentBean in https://github.com/justbetter/laravel-magento-client/pull/21
99 |
100 |
101 | **Full Changelog**: https://github.com/justbetter/laravel-magento-client/compare/1.3.1...1.4.0
102 |
103 | ## [1.3.1](https://github.com/justbetter/laravel-magento-client/releases/tag/1.3.1) - 2023-05-10
104 |
105 | ### What's Changed
106 | * Allow selection as a string by @ramonrietdijk in https://github.com/justbetter/laravel-magento-client/pull/20
107 |
108 |
109 | **Full Changelog**: https://github.com/justbetter/laravel-magento-client/compare/1.3.0...1.3.1
110 |
111 | ## [1.3.0](https://github.com/justbetter/laravel-magento-client/releases/tag/1.3.0) - 2023-05-05
112 |
113 | ### What's Changed
114 | * Magento OAuth 1.0 implementation by @ramonrietdijk in https://github.com/justbetter/laravel-magento-client/pull/19
115 |
116 |
117 | **Full Changelog**: https://github.com/justbetter/laravel-magento-client/compare/1.2.0...1.3.0
118 |
119 | ## [1.2.0](https://github.com/justbetter/laravel-magento-client/releases/tag/1.2.0) - 2023-03-22
120 |
121 | ### What's Changed
122 | * Add whereNull and ORs by @VincentBean in https://github.com/justbetter/laravel-magento-client/pull/17
123 |
124 |
125 | **Full Changelog**: https://github.com/justbetter/laravel-magento-client/compare/1.1.0...1.2.0
126 |
127 | ## [1.1.0](https://github.com/justbetter/laravel-magento-client/releases/tag/1.1.0) - 2023-02-24
128 |
129 | ### What's Changed
130 | * Support for Laravel 10 by @ramonrietdijk in https://github.com/justbetter/laravel-magento-client/pull/14
131 |
132 |
133 | **Full Changelog**: https://github.com/justbetter/laravel-magento-client/compare/1.0.9...1.1.0
134 |
135 | ## [1.0.9](https://github.com/justbetter/laravel-magento-client/releases/tag/1.0.9) - 2023-02-22
136 |
137 | ### What's Changed
138 | * Lazily load results from Magento by @ramonrietdijk in https://github.com/justbetter/laravel-magento-client/pull/13
139 |
140 |
141 | **Full Changelog**: https://github.com/justbetter/laravel-magento-client/compare/1.0.8...1.0.9
142 |
143 | ## [1.0.8](https://github.com/justbetter/laravel-magento-client/releases/tag/1.0.8) - 2023-01-31
144 |
145 | ### What's Changed
146 | * Updated grammar by @ramonrietdijk in https://github.com/justbetter/laravel-magento-client/pull/9
147 | * Grayloon migration guide by @ramonrietdijk in https://github.com/justbetter/laravel-magento-client/pull/10
148 |
149 |
150 | **Full Changelog**: https://github.com/justbetter/laravel-magento-client/compare/1.0.7...1.0.8
151 |
152 | ## [1.0.7](https://github.com/justbetter/laravel-magento-client/releases/tag/1.0.7) - 2023-01-25
153 |
154 | Fix typo in composer.json
155 |
156 | **Full Changelog**: https://github.com/justbetter/laravel-magento-client/compare/1.0.6...1.0.7
157 |
158 | ## [1.0.6](https://github.com/justbetter/laravel-magento-client/releases/tag/1.0.6) - 2023-01-12
159 |
160 | **Full Changelog**: https://github.com/justbetter/laravel-magento-client/compare/1.0.5...1.0.6
161 |
162 | ## [1.0.5](https://github.com/justbetter/laravel-magento-client/releases/tag/1.0.5) - 2023-01-10
163 |
164 | ### What's Changed
165 | * Add delete method by @VincentBean in https://github.com/justbetter/laravel-magento-client/pull/8
166 |
167 |
168 | **Full Changelog**: https://github.com/justbetter/laravel-magento-client/compare/1.0.4...1.0.5
169 |
170 | ## [1.0.4](https://github.com/justbetter/laravel-magento-client/releases/tag/1.0.4) - 2023-01-02
171 |
172 | ### What's Changed
173 | * Make store nullable by @ramonrietdijk in https://github.com/justbetter/laravel-magento-client/pull/7
174 |
175 |
176 | **Full Changelog**: https://github.com/justbetter/laravel-magento-client/compare/1.0.3...1.0.4
177 |
178 | ## [1.0.3](https://github.com/justbetter/laravel-magento-client/releases/tag/1.0.3) - 2022-11-25
179 |
180 | ### What's Changed
181 | * Coverage action by @VincentBean in https://github.com/justbetter/laravel-magento-client/pull/3
182 | * Badges by @VincentBean in https://github.com/justbetter/laravel-magento-client/pull/4
183 | * Add references to other packages by @VincentBean in https://github.com/justbetter/laravel-magento-client/pull/5
184 | * Added a fake method to ease testing in third party packages by @ramonrietdijk in https://github.com/justbetter/laravel-magento-client/pull/6
185 |
186 | ### New Contributors
187 | * @ramonrietdijk made their first contribution in https://github.com/justbetter/laravel-magento-client/pull/6
188 |
189 | **Full Changelog**: https://github.com/justbetter/laravel-magento-client/compare/1.0.2...1.0.3
190 |
191 | ## [1.0.2](https://github.com/justbetter/laravel-magento-client/releases/tag/1.0.2) - 2022-10-10
192 |
193 | ### What's Changed
194 | * Add GitHub Actions for tests, static analysis and style check by @VincentBean in https://github.com/justbetter/laravel-magento-client/pull/1
195 | * Default response items by @VincentBean in https://github.com/justbetter/laravel-magento-client/pull/2
196 |
197 | ### New Contributors
198 | * @VincentBean made their first contribution in https://github.com/justbetter/laravel-magento-client/pull/1
199 |
200 | **Full Changelog**: https://github.com/justbetter/laravel-magento-client/compare/1.0.1...1.0.2
201 |
202 | ## [1.0.1](https://github.com/justbetter/laravel-magento-client/releases/tag/1.0.1) - 2022-09-20
203 |
204 | **Full Changelog**: https://github.com/justbetter/laravel-magento-client/compare/1.0.0...1.0.1
205 |
206 | ## [1.0.0](https://github.com/justbetter/laravel-magento-client/releases/tag/1.0.0) - 2022-09-20
207 |
208 | Initial release of justbetter/laravel-magento-client
209 |
210 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) JustBetter
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8 | persons to whom the Software is furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11 | Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # Laravel Magento Client
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | A client to communicate with Magento from your Laravel application.
15 |
16 | ```php
17 | magento->get('products/1');
29 | }
30 |
31 | public function retrieveOrdersLazily()
32 | {
33 | $retrievedOrders = [];
34 |
35 | $searchCriteria = \JustBetter\MagentoClient\Query\SearchCriteria::make()
36 | ->where('state', 'processing');
37 |
38 | foreach ($this->magento->lazy('orders', $searchCriteria->get()) as $order) {
39 | $retrievedOrders[] = $order['increment_id'];
40 | }
41 | }
42 |
43 | public function multipleConnections()
44 | {
45 | $this->magento->connection('connection_one')->get('products/1');
46 | $this->magento->connection('connection_two')->get('products/1');
47 | }
48 | }
49 |
50 | ```
51 |
52 | > Looking to synchronize [prices](https://github.com/justbetter/laravel-magento-prices)
53 | > or [stock](https://github.com/justbetter/laravel-magento-stock) to Magento?
54 |
55 | ## Installation
56 |
57 | > Are you coming from `grayloon/laravel-magento-api`? We have written
58 | > a [migration guide](./docs/migrations/GRAYLOON.md)!
59 |
60 | Require this package:
61 |
62 | ```shell
63 | composer require justbetter/laravel-magento-client
64 | ```
65 |
66 | ## Configuration
67 |
68 | Add the following to your `.env`:
69 |
70 | ```.dotenv
71 | MAGENTO_BASE_URL=
72 | MAGENTO_ACCESS_TOKEN=
73 | ```
74 |
75 | Optionally, publish the configuration file of this package.
76 |
77 | ```bash
78 | php artisan vendor:publish --provider="JustBetter\MagentoClient\ServiceProvider" --tag=config
79 | ```
80 |
81 | ## Multiple connections
82 |
83 | This package supports connecting to multiple Magento instances.
84 | In the configuration file you will find an array with the connections where you can configure each connection.
85 |
86 | ```php
87 | 'connection' => 'default',
88 | 'connections' => [
89 | 'default' => [
90 | /* Base URL of Magento, for example: https://magento.test */
91 | 'base_url' => env('MAGENTO_BASE_URL'),
92 |
93 | // Other settings
94 | ],
95 | 'another_connection' => [
96 | 'base_url' => env('ANOTHER_MAGENTO_BASE_URL'),
97 |
98 | // Other settings
99 | ],
100 | ],
101 | ```
102 |
103 | The `connection` setting sets which connection is used by default.
104 | You can switch connections by using the `connection` method on the client.
105 |
106 | ```php
107 | /** @var \JustBetter\MagentoClient\Client\Magento $client */
108 | $client = app(\JustBetter\MagentoClient\Client\Magento::class);
109 |
110 | $client->connection('connection_one')->get('products');
111 | ```
112 |
113 | ## Authentication
114 |
115 | By default, this packages uses Bearer tokens to authenticate to Magento. Since Magento 2.4.4, this method of authentication requires additional configuration in Magento as [described here](https://developer.adobe.com/commerce/webapi/get-started/authentication/gs-authentication-token).
116 |
117 | It is recommended to authenticate to Magento using OAuth 1.0 which will also prevent the additional configuration requirements, besides setting up the integration itself.
118 |
119 | ### Setting up OAuth 1.0
120 |
121 | Start by adding the following variable to your `.env`-file.
122 |
123 | ```.dotenv
124 | MAGENTO_AUTH_METHOD=oauth
125 | ```
126 |
127 | Note that you can also remove `MAGENTO_ACCESS_TOKEN` at this point, as it will be saved in a file instead.
128 |
129 | Next, open Magento and create a new integration. When configuring, supply a `Callback URL` and `Identity link URL`. If you have not made any changes to your configuration, these are the URLs:
130 |
131 | ```
132 | Callback URL: https://example.com/magento/oauth/callback/{connection}
133 | Identity link URL: https://example.com/magento/oauth/identity/{connection}
134 | ```
135 |
136 | `connection` is the key in your connections array in the configuration file.
137 |
138 | When creating the integration, Magento will send multiple tokens and secrets to your application via the `callback`-endpoint. This information will be saved in the database, as configured in `magento.php`. You may adjust this to save the key in a JSON file or create your own implementation
139 | Magento will redirect you to the `identity`-endpoint in order to activate the integration.
140 |
141 | For more information about OAuth 1.0 in Magento, please consult the [documentation](https://developer.adobe.com/commerce/webapi/get-started/authentication/gs-authentication-oauth).
142 |
143 | #### Identity endpoint
144 |
145 | > [!CAUTION]
146 | > Be sure to add your authentication middleware
147 |
148 | Note that the `identity`-endpoint **does not** have any authentication or authorization middleware by default - you should add this in the configuration yourself. If you do not have any form of protection, anyone could change the tokens in your secret file.
149 |
150 | It is recommended that only administrators of your application are allowed to access the identity endpoint.
151 |
152 | ## Usage
153 |
154 | You can get an instance of the client by injecting it or by resolving it:
155 |
156 | ```php
157 | select('items[sku,name]')
183 | ->where('sku', '!=', '123')
184 | ->orWhere('price', '>', 10),
185 | ->whereIn('sku', ['123', '456'])
186 | ->orWhereNull('name')
187 | ->orderBy('sku')
188 | ->paginate(1, 50)
189 | ->get();
190 | ```
191 |
192 | You can view more examples in [Magento's documentation](https://developer.adobe.com/commerce/webapi/rest/use-rest/performing-searches/).
193 |
194 | ### Connections
195 |
196 | You can connect to other connections using the `connection` method on the client.
197 |
198 | ```php
199 | /** @var \JustBetter\MagentoClient\Client\Magento $client */
200 | $client = app(\JustBetter\MagentoClient\Client\Magento::class);
201 |
202 | $client->connection('connection_one')->get('products');
203 | $client->connection('connection_two')->get('products');
204 | ```
205 |
206 | ### GraphQL
207 |
208 | You can run authenticated GraphQL queries using the `graphql` method on the client.
209 |
210 | ```php
211 | /** @var \JustBetter\MagentoClient\Client\Magento $client */
212 | $client = app(\JustBetter\MagentoClient\Client\Magento::class);
213 |
214 | $client->graphql(
215 | 'query searchProducts($search: String) {
216 | products(
217 | search: $search
218 | ) {
219 | items {
220 | sku
221 | }
222 | }
223 | }',
224 | [
225 | 'search' => 'test'
226 | ]
227 | );
228 | ```
229 |
230 | ### More Examples
231 |
232 | You can view the tests for more examples.
233 |
234 | ### Checking if Magento is available
235 |
236 | This client keeps track whether Magento is available by storing the count of any >=502 and <=504 response code in cache.
237 | You may use the `available()` method on the client in order to check if Magento is available.
238 |
239 | The threshold, timespan and status codes can be configured in the configuration file per connection.
240 |
241 | #### Jobs
242 |
243 | This package provides a job middleware that releases jobs back onto the queue when Magento is not available.
244 | The middleware is located at `\JustBetter\MagentoClient\Jobs\Middleware\AvailableMiddleware`.
245 |
246 | ## Testing
247 |
248 | This package uses Laravel's HTTP client so that you can fake the requests.
249 |
250 | ```php
251 | Http::response([
255 | 'items' => [['sku' => '::some_sku::']],
256 | 'total_count' => 1,
257 | ]),
258 | ]);
259 | ```
260 |
261 | ## Quality
262 |
263 | To ensure the quality of this package, run the following command:
264 |
265 | ```shell
266 | composer quality
267 | ```
268 |
269 | This will execute three tasks:
270 |
271 | 1. Makes sure all tests are passed
272 | 2. Checks for any issues using static code analysis
273 | 3. Checks if the code is correctly formatted
274 |
275 | ## Contributing
276 |
277 | Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details.
278 |
279 | ## Security Vulnerabilities
280 |
281 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities.
282 |
283 | ## Credits
284 |
285 | - [Vincent Boon](https://github.com/VincentBean)
286 | - [Ramon Rietdijk](https://github.com/ramonrietdijk)
287 | - [All Contributors](../../contributors)
288 |
289 | ## License
290 |
291 | The MIT License (MIT). Please see [License File](LICENSE) for more information.
292 |
293 |
294 |
295 |
296 |
--------------------------------------------------------------------------------
/UPGRADE.md:
--------------------------------------------------------------------------------
1 | # Upgrade guide
2 |
3 | # 1.x to 2.x
4 |
5 | 2.x introduces support for multiple connections. To upgrade you must adjust your configuration file and OAuth urls.
6 |
7 | ## Configuration file
8 |
9 | The configuration file has been changed to support multiple connections.
10 | It is recommended to overwrite the version from the package so that you are up to date.
11 |
12 | The environment variables did not change.
13 | This means that if you have a single Magento connection you code will not break.
14 |
15 | ## OAuth
16 |
17 | > [!NOTE]
18 | > This step is only required when using OAuth authentication
19 |
20 | Reauthorize your Magento integration to create the record in your DB.
21 |
22 | ### URL
23 |
24 | The OAuth URL's now require a connection parameter, if you have a single Magento instance the connection value is `default` by default.
25 |
26 | ```
27 | Callback URL: https://example.com/magento/oauth/callback/{connection}
28 | Identity link URL: https://example.com/magento/oauth/identity/{connection}
29 | ```
30 |
31 | ### Key Location
32 |
33 | OAuth keys are now stored in the database by default.
34 | If you prefer to store them on disk you can use the `\JustBetter\MagentoClient\OAuth\KeyStore\FileKeyStore` or implement your own keystore.
35 |
36 |
37 | ## Testing
38 |
39 | The `Magento::fake()` method has been altered to use the URL `magento` instead of `http://magento.test`. You should replace this in your tests.
40 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "justbetter/laravel-magento-client",
3 | "description": "A client to interact with Magento",
4 | "type": "package",
5 | "license": "MIT",
6 | "require": {
7 | "php": "^8.3",
8 | "guzzlehttp/guzzle": "^7.5",
9 | "laravel/framework": "^11.0|^12.0",
10 | "league/oauth1-client": "^1.10"
11 | },
12 | "require-dev": {
13 | "larastan/larastan": "^3.0",
14 | "laravel/pint": "^1.20",
15 | "orchestra/testbench": "^9.0",
16 | "pestphp/pest": "^3.7",
17 | "phpstan/phpstan-mockery": "^2.0",
18 | "phpunit/phpunit": "^11.5"
19 | },
20 | "authors": [
21 | {
22 | "name": "Vincent Boon",
23 | "email": "vincent@justbetter.nl",
24 | "role": "Developer"
25 | },
26 | {
27 | "name": "Ramon Rietdijk",
28 | "email": "ramon@justbetter.nl",
29 | "role": "Developer"
30 | }
31 | ],
32 | "autoload": {
33 | "psr-4": {
34 | "JustBetter\\MagentoClient\\": "src"
35 | }
36 | },
37 | "autoload-dev": {
38 | "psr-4": {
39 | "JustBetter\\MagentoClient\\Tests\\": "tests"
40 | }
41 | },
42 | "scripts": {
43 | "test": "phpunit",
44 | "analyse": "phpstan --memory-limit=256M",
45 | "style": "pint --test",
46 | "quality": [
47 | "@style",
48 | "@analyse",
49 | "@test",
50 | "@coverage"
51 | ],
52 | "fix-style": "pint",
53 | "coverage": "XDEBUG_MODE=coverage php vendor/bin/pest --coverage --min=100"
54 | },
55 | "config": {
56 | "sort-packages": true,
57 | "allow-plugins": {
58 | "pestphp/pest-plugin": true
59 | }
60 | },
61 | "extra": {
62 | "laravel": {
63 | "providers": [
64 | "JustBetter\\MagentoClient\\ServiceProvider"
65 | ]
66 | }
67 | },
68 | "minimum-stability": "dev",
69 | "prefer-stable": true
70 | }
71 |
--------------------------------------------------------------------------------
/config/magento.php:
--------------------------------------------------------------------------------
1 | 'default',
6 |
7 | 'connections' => [
8 | 'default' => [
9 | /* Base URL of Magento, for example: https://magento.test */
10 | 'base_url' => env('MAGENTO_BASE_URL'),
11 |
12 | /* Base path, only modify if your API is not at /rest */
13 | 'base_path' => env('MAGENTO_BASE_PATH', 'rest'),
14 |
15 | /* Graphql path, only modify if your API is not at /graphql */
16 | 'graphql_path' => env('MAGENTO_GRAPHQL_PATH', 'graphql'),
17 |
18 | /* Store code, modify if you want to set a store by default. */
19 | 'store_code' => env('MAGENTO_STORE_CODE', 'all'),
20 |
21 | /* Modify if Magento has a new API version */
22 | 'version' => env('MAGENTO_API_VERSION', 'V1'),
23 |
24 | /* Magento access token of an integration */
25 | 'access_token' => env('MAGENTO_ACCESS_TOKEN'),
26 |
27 | /* Specify the timeout (in seconds) for the request. */
28 | 'timeout' => 30,
29 |
30 | /* Specify the connection timeout (in seconds) for the request. */
31 | 'connect_timeout' => 10,
32 |
33 | /* Authentication method, choose either "oauth" or "token". */
34 | 'authentication_method' => env('MAGENTO_AUTH_METHOD', 'token'),
35 |
36 | /* Availability configuration. */
37 | 'availability' => [
38 | /* The response codes that should trigger the availability check. */
39 | 'codes' => [502, 503, 504],
40 |
41 | /* The amount of failed requests before the service is marked as unavailable. */
42 | 'threshold' => 10,
43 |
44 | /* The timespan in minutes in which the failed requests should occur. */
45 | 'timespan' => 10,
46 |
47 | /* The cooldown in minutes after the threshold is reached. */
48 | 'cooldown' => 2,
49 | ],
50 | ],
51 | ],
52 |
53 | /* OAuth configuration */
54 | 'oauth' => [
55 |
56 | /* Add your middleware that authenticates users here, this is used for the identity callback. */
57 | 'middleware' => [
58 | //
59 | ],
60 |
61 | /* Prefix for the oauth routes. */
62 | 'prefix' => 'magento/oauth',
63 |
64 | /* Class that manages how the keys are stored */
65 | 'keystore' => \JustBetter\MagentoClient\OAuth\KeyStore\DatabaseKeyStore::class,
66 | ],
67 | ];
68 |
--------------------------------------------------------------------------------
/database/migrations/2023_12_11_150000_create_magento_oauth_keys_table.php:
--------------------------------------------------------------------------------
1 | id();
13 | $table->string('magento_connection')->index();
14 |
15 | $table->json('keys');
16 |
17 | $table->timestamps();
18 | });
19 | }
20 |
21 | public function down(): void
22 | {
23 | Schema::dropIfExists('magento_oauth_keys');
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/docs/migrations/GRAYLOON.md:
--------------------------------------------------------------------------------
1 | # Migration guide from grayloon/laravel-magento-api
2 |
3 | This is a migration guide to migrate from `grayloon/laravel-magento-api` to `justbetter/laravel-magento-client`.
4 |
5 | ## Installation
6 |
7 | > It is possible to use both clients in a project without issues.
8 |
9 | First of all, remove the `grayloon/laravel-magento-api` package.
10 |
11 | ```shell
12 | composer remove grayloon/laravel-magento-api
13 | ```
14 |
15 | Next, install the `justbetter/laravel-magento-client` package.
16 |
17 | ```shell
18 | composer require justbetter/laravel-magento-client
19 | ```
20 |
21 | ## Configuration
22 |
23 | If you have not modified the configuration file there are no changes necessary. The variables defined in the `.env`-file
24 | are equal, thus backwards compatible.
25 |
26 | ## Logging Failed Requests
27 |
28 | The `grayloon/laravel-magento-api` package contains a feature that logs failed API requests to the Laravel log file when
29 | the option `log_failed_requests` is enabled in the configuration file.
30 |
31 | This package does not have this functionality. We aim to keep this package simplistic and expect you to handle errors
32 | yourself.
33 |
34 | ## Update Your Code
35 |
36 | All references to Grayloon have to be updated to use the new client.
37 |
38 | ```php
39 | show($sku);
72 |
73 | // After
74 | $this->magento->get("products/$sku");
75 | }
76 |
77 | public function updateProduct(string $sku, array $data): void
78 | {
79 | // Before
80 | Magento::api('products')->edit($sku, $data);
81 |
82 | // After
83 | $this->magento->put("products/$sku", $data);
84 | }
85 |
86 | public function getSpecificCustomers(): void
87 | {
88 | // Before
89 | Magento::api('customers')->all(50, 1, [
90 | 'searchCriteria[filter_groups][0][filters][0][field]' => 'email',
91 | 'searchCriteria[filter_groups][0][filters][0][condition_type]' => 'like',
92 | 'searchCriteria[filter_groups][0][filters][0][value]' => '%@example.com',
93 | ]);
94 |
95 | // After
96 | $searchCriteria = SearchCriteria::make()
97 | ->paginate(1, 50)
98 | ->where('email', 'like', '%@example.com')
99 | ->get();
100 |
101 | $this->magento->get('customers/search', $searchCriteria);
102 | }
103 |
104 | public function getStoreConfigs(): void
105 | {
106 | // Before
107 | Magento::api('store')->storeConfigs();
108 |
109 | // After
110 | $this->magento->get('store/storeConfigs');
111 | }
112 |
113 | public function getWebsites(): void
114 | {
115 | // Before
116 | Magento::api('store')->websites();
117 |
118 | // After
119 | $this->magento->get('store/websites');
120 | }
121 | }
122 | ```
123 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | includes:
2 | - ./vendor/larastan/larastan/extension.neon
3 | - ./vendor/phpstan/phpstan-mockery/extension.neon
4 |
5 | parameters:
6 | paths:
7 | - src
8 | - tests
9 | level: 8
10 | ignoreErrors:
11 | - identifier: missingType.iterableValue
12 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ./tests/*
6 |
7 |
8 |
9 |
10 |
11 | ./src
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/routes/web.php:
--------------------------------------------------------------------------------
1 | name('magento.oauth.callback');
8 |
9 | Route::get('identity/{connection}', [OAuthController::class, 'identity'])
10 | ->middleware(config('magento.oauth.middleware'))
11 | ->name('magento.oauth.identity');
12 |
--------------------------------------------------------------------------------
/src/Actions/AuthenticateRequest.php:
--------------------------------------------------------------------------------
1 | provider()->authenticate($request, $connection);
19 | }
20 |
21 | public static function bind(): void
22 | {
23 | app()->singleton(AuthenticatesRequest::class, static::class);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Actions/BuildRequest.php:
--------------------------------------------------------------------------------
1 | timeout($options['timeout'])
23 | ->connectTimeout($options['connect_timeout'])
24 | ->acceptJson()
25 | ->asJson();
26 |
27 | return $this->request->authenticate($pendingRequest, $connection);
28 | }
29 |
30 | public static function bind(): void
31 | {
32 | app()->singleton(BuildsRequest::class, static::class);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Actions/CheckMagento.php:
--------------------------------------------------------------------------------
1 | get(static::AVAILABLE_KEY.$connection, true);
14 | }
15 |
16 | public static function bind(): void
17 | {
18 | app()->singleton(ChecksMagento::class, static::class);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Actions/OAuth/RequestAccessToken.php:
--------------------------------------------------------------------------------
1 | get($connection);
17 |
18 | $callback = $keys['callback'] ?? [];
19 |
20 | Validator::validate($callback, [
21 | 'oauth_consumer_key' => 'required|string',
22 | 'oauth_consumer_secret' => 'required|string',
23 | 'oauth_verifier' => 'required|string',
24 | ]);
25 |
26 | if ($callback['oauth_consumer_key'] !== $key) {
27 | abort(403);
28 | }
29 |
30 | $credentials = new ClientCredentials;
31 | $credentials->setIdentifier($callback['oauth_consumer_key']);
32 | $credentials->setSecret($callback['oauth_consumer_secret']);
33 | $credentials->setCallbackUri(route('magento.oauth.callback', ['connection' => $connection]));
34 |
35 | /** @var MagentoServer $server */
36 | $server = app()->makeWith(MagentoServer::class, [
37 | 'clientCredentials' => $credentials,
38 | 'signature' => new HmacSha256Signature($credentials),
39 | ]);
40 |
41 | $server->connection = $connection;
42 |
43 | $temporaryCredentials = $server->getTemporaryCredentials();
44 |
45 | $tokenCredentials = $server->getTokenCredentials(
46 | $temporaryCredentials,
47 | $temporaryCredentials->getIdentifier(),
48 | $callback['oauth_verifier']
49 | );
50 |
51 | $auth['access_token'] = $tokenCredentials->getIdentifier();
52 | $auth['access_token_secret'] = $tokenCredentials->getSecret();
53 |
54 | KeyStore::instance()->set(
55 | $connection,
56 | array_merge($callback, $auth),
57 | );
58 | }
59 |
60 | public static function bind(): void
61 | {
62 | app()->singleton(RequestsAccessToken::class, static::class);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/Client/Magento.php:
--------------------------------------------------------------------------------
1 | connection = config('magento.connection');
29 | }
30 |
31 | public function connection(string $connection): static
32 | {
33 | $this->connection = $connection;
34 |
35 | return $this;
36 | }
37 |
38 | public function store(?string $store = null): static
39 | {
40 | $this->storeCode = $store;
41 |
42 | return $this;
43 | }
44 |
45 | public function graphql(string $query, array $variables = []): Response
46 | {
47 | /** @var string $endpoint */
48 | $endpoint = config("magento.connections.{$this->connection}.graphql_path");
49 |
50 | /** @var Response $response */
51 | $response = $this->request()
52 | ->when($this->storeCode !== null, fn (PendingRequest $request): PendingRequest => $request->withHeaders(['Store' => $this->storeCode]))
53 | ->post($endpoint, [
54 | 'query' => $query,
55 | 'variables' => $variables,
56 | ]);
57 |
58 | return $this->handleResponse($response);
59 | }
60 |
61 | public function get(string $path, array $data = []): Response
62 | {
63 | $response = $this->request()->get($this->getUrl($path), $data);
64 |
65 | return $this->handleResponse($response);
66 | }
67 |
68 | public function post(string $path, array $data = []): Response
69 | {
70 | /** @var Response $response */
71 | $response = $this->request()->post($this->getUrl($path), $data);
72 |
73 | return $this->handleResponse($response);
74 | }
75 |
76 | public function postAsync(string $path, array $data = []): Response
77 | {
78 | /** @var Response $response */
79 | $response = $this->request()->post($this->getUrl($path, true), $data);
80 |
81 | return $this->handleResponse($response);
82 | }
83 |
84 | public function postBulk(string $path, array $data = []): Response
85 | {
86 | /** @var Response $response */
87 | $response = $this->request()->post($this->getUrl($path, true, true), $data);
88 |
89 | return $this->handleResponse($response);
90 | }
91 |
92 | public function patch(string $path, array $data = []): Response
93 | {
94 | /** @var Response $response */
95 | $response = $this->request()->patch($this->getUrl($path), $data);
96 |
97 | return $this->handleResponse($response);
98 | }
99 |
100 | public function patchAsync(string $path, array $data = []): Response
101 | {
102 | /** @var Response $response */
103 | $response = $this->request()->patch($this->getUrl($path, true), $data);
104 |
105 | return $this->handleResponse($response);
106 | }
107 |
108 | public function patchBulk(string $path, array $data = []): Response
109 | {
110 | /** @var Response $response */
111 | $response = $this->request()->patch($this->getUrl($path, true, true), $data);
112 |
113 | return $this->handleResponse($response);
114 | }
115 |
116 | public function put(string $path, array $data = []): Response
117 | {
118 | /** @var Response $response */
119 | $response = $this->request()->put($this->getUrl($path), $data);
120 |
121 | return $this->handleResponse($response);
122 | }
123 |
124 | public function putAsync(string $path, array $data = []): Response
125 | {
126 | /** @var Response $response */
127 | $response = $this->request()->put($this->getUrl($path, true), $data);
128 |
129 | return $this->handleResponse($response);
130 | }
131 |
132 | public function putBulk(string $path, array $data = []): Response
133 | {
134 | /** @var Response $response */
135 | $response = $this->request()->put($this->getUrl($path, true, true), $data);
136 |
137 | return $this->handleResponse($response);
138 | }
139 |
140 | public function delete(string $path, array $data = []): Response
141 | {
142 | /** @var Response $response */
143 | $response = $this->request()->delete($this->getUrl($path), $data);
144 |
145 | return $this->handleResponse($response);
146 | }
147 |
148 | public function deleteAsync(string $path, array $data = []): Response
149 | {
150 | /** @var Response $response */
151 | $response = $this->request()->delete($this->getUrl($path, true), $data);
152 |
153 | return $this->handleResponse($response);
154 | }
155 |
156 | public function deleteBulk(string $path, array $data = []): Response
157 | {
158 | /** @var Response $response */
159 | $response = $this->request()->delete($this->getUrl($path, true, true), $data);
160 |
161 | return $this->handleResponse($response);
162 | }
163 |
164 | /** @return LazyCollection */
165 | public function lazy(string $path, array $data = [], int $pageSize = 100): LazyCollection
166 | {
167 | return LazyCollection::make(function () use ($path, $data, $pageSize): Generator {
168 | $currentPage = 1;
169 | $hasNextPage = true;
170 |
171 | while ($hasNextPage) {
172 | $data['searchCriteria[pageSize]'] = $pageSize;
173 | $data['searchCriteria[currentPage]'] = $currentPage;
174 |
175 | $response = $this->get($path, $data)->throw();
176 |
177 | /** @var Enumerable> $items */
178 | $items = $response->collect('items');
179 |
180 | foreach ($items as $item) {
181 | yield $item;
182 | }
183 |
184 | $hasNextPage = $items->count() >= $pageSize;
185 | $currentPage++;
186 | }
187 | });
188 | }
189 |
190 | public function available(): bool
191 | {
192 | return $this->checksMagento->available($this->connection);
193 | }
194 |
195 | public function getUrl(string $path, bool $async = false, bool $bulk = false): string
196 | {
197 | /** @var array $config */
198 | $config = config('magento.connections.'.$this->connection);
199 |
200 | $options = [
201 | $config['base_path'] ?? 'rest',
202 | $this->storeCode ?? $config['store_code'] ?? 'all',
203 | ];
204 |
205 | if ($async || $bulk) {
206 | $options[] = 'async';
207 |
208 | if ($bulk) {
209 | $options[] = 'bulk';
210 | }
211 | }
212 |
213 | $options[] = $config['version'] ?? 'V1';
214 | $options[] = $path;
215 |
216 | return implode('/', $options);
217 | }
218 |
219 | public function intercept(Closure $callable): static
220 | {
221 | $this->interceptor = $callable;
222 |
223 | return $this;
224 | }
225 |
226 | protected function request(): PendingRequest
227 | {
228 | $request = $this->request->build($this->connection);
229 |
230 | if (isset($this->interceptor)) {
231 | call_user_func($this->interceptor, $request);
232 | $this->interceptor = null;
233 | }
234 |
235 | return $request;
236 | }
237 |
238 | protected function handleResponse(Response $response): Response
239 | {
240 | MagentoResponseEvent::dispatch($response, $this->connection);
241 |
242 | return $response;
243 | }
244 |
245 | public static function fake(): void
246 | {
247 | config()->set('magento.connection', 'default');
248 | config()->set('magento.connections.default', [
249 | 'base_url' => 'magento',
250 | 'base_path' => 'rest',
251 | 'graphql_path' => 'graphql',
252 | 'store_code' => 'all',
253 | 'version' => 'V1',
254 | 'access_token' => '::token::',
255 | 'timeout' => 30,
256 | 'connect_timeout' => 10,
257 | 'authentication_method' => 'token',
258 | ]);
259 |
260 | config()->set('magento.oauth', [
261 | 'middleware' => [],
262 | 'prefix' => 'magento/oauth',
263 | 'keystore' => FileKeyStore::class,
264 | ]);
265 | }
266 | }
267 |
--------------------------------------------------------------------------------
/src/Contracts/AuthenticatesRequest.php:
--------------------------------------------------------------------------------
1 | BearerTokenProvider::class,
18 | AuthenticationMethod::OAuth => OAuthProvider::class,
19 | };
20 |
21 | /** @var BaseProvider $instance */
22 | $instance = app($class);
23 |
24 | return $instance;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Events/MagentoResponseEvent.php:
--------------------------------------------------------------------------------
1 | merge($connection, [
18 | 'callback' => $request->validated(),
19 | ]);
20 |
21 | return response()->json();
22 | }
23 |
24 | public function identity(
25 | IdentityRequest $request,
26 | RequestsAccessToken $contract,
27 | string $connection
28 | ): RedirectResponse {
29 | $contract->request(
30 | $connection,
31 | $request->oauth_consumer_key,
32 | );
33 |
34 | return redirect()->to($request->success_call_back);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Http/Middleware/OAuthMiddleware.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | public function rules(): array
23 | {
24 | return [
25 | 'oauth_consumer_key' => 'required|string|max:32',
26 | 'oauth_consumer_secret' => 'required|string|max:32',
27 | 'oauth_verifier' => 'required|string|max:32',
28 | ];
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Http/Requests/IdentityRequest.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | public function rules(): array
22 | {
23 | return [
24 | 'oauth_consumer_key' => 'required|string|max:32',
25 | 'success_call_back' => 'required|string|max:4096',
26 | ];
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Jobs/Middleware/AvailableMiddleware.php:
--------------------------------------------------------------------------------
1 | connection = $connection ?? config('magento.connection');
17 | $this->seconds = $seconds;
18 | }
19 |
20 | public function handle(object $job, Closure $next): void
21 | {
22 | /** @var Magento $magento */
23 | $magento = app(Magento::class);
24 | $magento->connection($this->connection);
25 |
26 | if ($magento->available()) {
27 | $next($job);
28 | } elseif (method_exists($job, 'release')) {
29 | $job->release($this->seconds);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Listeners/StoreAvailabilityListener.php:
--------------------------------------------------------------------------------
1 | $codes */
15 | $codes = config('magento.connections.'.$event->connection.'.availability.codes', [502, 503, 504]);
16 |
17 | if (! in_array($event->response->status(), $codes)) {
18 | return;
19 | }
20 |
21 | $countKey = static::COUNT_KEY.$event->connection;
22 |
23 | /** @var int $count */
24 | $count = cache()->get($countKey, 0);
25 | $count++;
26 |
27 | /** @var int $threshold */
28 | $threshold = config('magento.connections.'.$event->connection.'.availability.threshold', 10);
29 |
30 | /** @var int $timespan */
31 | $timespan = config('magento.connections.'.$event->connection.'.availability.timespan', 10);
32 |
33 | /** @var int $cooldown */
34 | $cooldown = config('magento.connections.'.$event->connection.'.availability.cooldown', 2);
35 |
36 | cache()->put($countKey, $count, now()->addMinutes($timespan));
37 |
38 | if ($count >= $threshold) {
39 | cache()->put(CheckMagento::AVAILABLE_KEY.$event->connection, false, now()->addMinutes($cooldown));
40 |
41 | cache()->forget($countKey);
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Models/OAuthKey.php:
--------------------------------------------------------------------------------
1 | 'array',
23 | ];
24 | }
25 |
--------------------------------------------------------------------------------
/src/OAuth/HmacSha256Signature.php:
--------------------------------------------------------------------------------
1 | key(), true);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/OAuth/KeyStore/DatabaseKeyStore.php:
--------------------------------------------------------------------------------
1 | firstWhere('magento_connection', '=', $connection);
13 |
14 | return $key->keys ?? [];
15 | }
16 |
17 | public function set(string $connection, array $data): void
18 | {
19 | OAuthKey::query()->updateOrCreate(
20 | [
21 | 'magento_connection' => $connection,
22 | ],
23 | [
24 | 'keys' => $data,
25 | ]
26 | );
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/OAuth/KeyStore/FileKeyStore.php:
--------------------------------------------------------------------------------
1 | disk)->get($this->path.'/'.$connection.'.json') ?? '';
17 |
18 | return json_decode($content, true) ?? [];
19 | }
20 |
21 | public function set(string $connection, array $data): void
22 | {
23 | $storage = Storage::disk($this->disk);
24 |
25 | File::ensureDirectoryExists($storage->path($this->path));
26 |
27 | if ($encoded = json_encode($data)) {
28 | Storage::disk($this->disk)->put($this->path.'/'.$connection.'.json', $encoded, 'private');
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/OAuth/KeyStore/KeyStore.php:
--------------------------------------------------------------------------------
1 | $class */
10 | $class = config('magento.oauth.keystore');
11 |
12 | /** @var KeyStore $instance */
13 | $instance = app($class);
14 |
15 | return $instance;
16 | }
17 |
18 | abstract public function get(string $connection): array;
19 |
20 | abstract public function set(string $connection, array $data): void;
21 |
22 | public function merge(string $connection, array $data): void
23 | {
24 | $merged = array_merge($this->get($connection), $data);
25 |
26 | $this->set($connection, $merged);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/OAuth/MagentoServer.php:
--------------------------------------------------------------------------------
1 | connection.'.base_url').'/oauth/token/request';
21 | }
22 |
23 | public function urlAuthorization(): string
24 | {
25 | return '';
26 | }
27 |
28 | public function urlTokenCredentials(): string
29 | {
30 | return config('magento.connections.'.$this->connection.'.base_url').'/oauth/token/access';
31 | }
32 |
33 | public function getTokenCredentials(TemporaryCredentials $temporaryCredentials, $temporaryIdentifier, $verifier)
34 | {
35 | $this->verifier = $verifier;
36 |
37 | return parent::getTokenCredentials($temporaryCredentials, $temporaryIdentifier, $verifier);
38 | }
39 |
40 | protected function additionalProtocolParameters(): array
41 | {
42 | return [
43 | 'oauth_verifier' => $this->verifier,
44 | ];
45 | }
46 |
47 | protected function createTemporaryCredentials($body): TemporaryCredentials
48 | {
49 | parse_str($body, $data);
50 |
51 | if (! $data) {
52 | throw new CredentialsException('Unable to parse temporary credentials response.');
53 | }
54 |
55 | /** @var string $token */
56 | $token = $data['oauth_token'];
57 |
58 | /** @var string $secret */
59 | $secret = $data['oauth_token_secret'];
60 |
61 | $temporaryCredentials = new TemporaryCredentials;
62 | $temporaryCredentials->setIdentifier($token);
63 | $temporaryCredentials->setSecret($secret);
64 |
65 | return $temporaryCredentials;
66 | }
67 |
68 | public function urlUserDetails(): string
69 | {
70 | return '';
71 | }
72 |
73 | public function userDetails($data, TokenCredentials $tokenCredentials): User
74 | {
75 | return new User;
76 | }
77 |
78 | public function userUid($data, TokenCredentials $tokenCredentials): int
79 | {
80 | return 0;
81 | }
82 |
83 | public function userEmail($data, TokenCredentials $tokenCredentials): ?string
84 | {
85 | return null;
86 | }
87 |
88 | public function userScreenName($data, TokenCredentials $tokenCredentials): ?string
89 | {
90 | return null;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/Providers/BaseProvider.php:
--------------------------------------------------------------------------------
1 | withToken($token);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Providers/OAuthProvider.php:
--------------------------------------------------------------------------------
1 | get($connection);
23 |
24 | Validator::validate($keys, [
25 | 'oauth_consumer_key' => 'required|string',
26 | 'oauth_consumer_secret' => 'required|string',
27 | 'oauth_verifier' => 'required|string',
28 | 'access_token' => 'required|string',
29 | 'access_token_secret' => 'required|string',
30 | ]);
31 |
32 | return $request->withMiddleware(
33 | Middleware::mapRequest(function (RequestInterface $request) use ($keys) {
34 | $credentials = new ClientCredentials;
35 | $credentials->setIdentifier($keys['oauth_consumer_key']);
36 | $credentials->setSecret($keys['oauth_consumer_secret']);
37 |
38 | $server = new MagentoServer($credentials, new HmacSha256Signature($credentials));
39 | $server->verifier = $keys['oauth_verifier'];
40 |
41 | $tokenCredentials = new TokenCredentials;
42 | $tokenCredentials->setIdentifier($keys['access_token']);
43 | $tokenCredentials->setSecret($keys['access_token_secret']);
44 |
45 | $query = $request->getUri()->getQuery();
46 |
47 | $data = Str::of($query)
48 | ->explode('&')
49 | ->filter(fn (string $parameter): bool => strlen($parameter) > 0)
50 | ->mapWithKeys(function (string $parameter): array {
51 | $pair = explode('=', $parameter);
52 |
53 | return count($pair) === 2
54 | ? [$pair[0] => $pair[1]]
55 | : [$pair[0] => ''];
56 | })->toArray();
57 |
58 | $headers = $server->getHeaders(
59 | $tokenCredentials,
60 | $request->getMethod(),
61 | (string) $request->getUri()->withFragment('')->withQuery(''),
62 | $data
63 | );
64 |
65 | return $request->withHeader('Authorization', $headers['Authorization']);
66 | })
67 | );
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Query/Grammar.php:
--------------------------------------------------------------------------------
1 | 'eq',
9 | '<' => 'lt',
10 | '>' => 'gt',
11 | '<=' => 'lteq',
12 | '>=' => 'gteq',
13 | '<>' => 'neq',
14 | '!=' => 'neq',
15 | ];
16 |
17 | public function getOperator(string $operator): string
18 | {
19 | return $this->mapping[$operator] ?? $operator;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Query/SearchCriteria.php:
--------------------------------------------------------------------------------
1 | pagination = [
26 | 'searchCriteria[pageSize]' => $pageSize,
27 | 'searchCriteria[currentPage]' => $page,
28 | ];
29 |
30 | return $this;
31 | }
32 |
33 | public function select(array|string $fields): static
34 | {
35 | $this->select['fields'] = is_array($fields)
36 | ? implode(',', $fields)
37 | : $fields;
38 |
39 | return $this;
40 | }
41 |
42 | public function where(
43 | string $field,
44 | string $operator,
45 | mixed $value = null,
46 | ?int $filterGroup = null,
47 | int $filterIndex = 0
48 | ): static {
49 | // Assume the operator is equals
50 | if ($value === null) {
51 | $value = $operator;
52 | $operator = '=';
53 | }
54 |
55 | return $this->addWhere($field, $operator, $value, $filterGroup, $filterIndex);
56 | }
57 |
58 | public function orWhere(string $field, string $operator, mixed $value = null): static
59 | {
60 | $this->currentFilterIndex++;
61 |
62 | $this->where(
63 | $field,
64 | $operator,
65 | $value,
66 | $this->currentFilterGroup > 0 ? $this->currentFilterGroup - 1 : 0,
67 | $this->currentFilterIndex
68 | );
69 |
70 | return $this;
71 | }
72 |
73 | public function whereIn(string $field, array $values): static
74 | {
75 | return $this->addWhere($field, 'in', implode(',', $values));
76 | }
77 |
78 | public function orWhereIn(string $field, array $values): static
79 | {
80 | $this->currentFilterIndex++;
81 |
82 | return $this->addWhere(
83 | $field,
84 | 'in',
85 | implode(',', $values),
86 | $this->currentFilterGroup > 0 ? $this->currentFilterGroup - 1 : 0,
87 | $this->currentFilterIndex
88 | );
89 | }
90 |
91 | public function whereNotIn(string $field, array $values): static
92 | {
93 | return $this->addWhere($field, 'nin', implode(',', $values));
94 | }
95 |
96 | public function orWhereNotIn(string $field, array $values): static
97 | {
98 | $this->currentFilterIndex++;
99 |
100 | return $this->addWhere(
101 | $field,
102 | 'nin',
103 | implode(',', $values),
104 | $this->currentFilterGroup > 0 ? $this->currentFilterGroup - 1 : 0,
105 | $this->currentFilterIndex
106 | );
107 | }
108 |
109 | public function whereNull(string $field): static
110 | {
111 | return $this->addWhere($field, 'null');
112 | }
113 |
114 | public function orWhereNull(string $field): static
115 | {
116 | $this->currentFilterIndex++;
117 |
118 | return $this->addWhere(
119 | $field,
120 | 'null',
121 | null,
122 | $this->currentFilterGroup > 0 ? $this->currentFilterGroup - 1 : 0,
123 | $this->currentFilterIndex
124 | );
125 | }
126 |
127 | public function whereNotNull(string $field): static
128 | {
129 | return $this->addWhere($field, 'notnull');
130 | }
131 |
132 | public function orWhereNotNull(string $field): static
133 | {
134 | $this->currentFilterIndex++;
135 |
136 | return $this->addWhere(
137 | $field,
138 | 'notnull',
139 | null,
140 | $this->currentFilterGroup > 0 ? $this->currentFilterGroup - 1 : 0,
141 | $this->currentFilterIndex
142 | );
143 | }
144 |
145 | protected function addWhere(
146 | string $field,
147 | string $operator,
148 | mixed $value = null,
149 | ?int $filterGroup = null,
150 | int $filterIndex = 0
151 | ): static {
152 | $currentFilterGroup = $filterGroup ?? $this->currentFilterGroup;
153 |
154 | $prefix = "searchCriteria[filter_groups][$currentFilterGroup][filters][$filterIndex]";
155 |
156 | $this->wheres[$prefix.'[field]'] = $field;
157 | $this->wheres[$prefix.'[condition_type]'] = $this->grammar->getOperator($operator);
158 |
159 | if ($value !== null) {
160 | $this->wheres[$prefix.'[value]'] = $value;
161 | }
162 |
163 | if ($filterGroup === null) {
164 | $this->currentFilterGroup++;
165 | }
166 |
167 | if ($filterIndex === 0) {
168 | $this->currentFilterIndex = 0;
169 | }
170 |
171 | return $this;
172 | }
173 |
174 | public function orderBy(string $field, string $direction = 'ASC'): static
175 | {
176 | $index = count($this->orders) / 2;
177 |
178 | $this->orders["searchCriteria[sortOrders][$index][field]"] = $field;
179 | $this->orders["searchCriteria[sortOrders][$index][direction]"] = $direction;
180 |
181 | return $this;
182 | }
183 |
184 | public function orderByDesc(string $field): static
185 | {
186 | return $this->orderBy($field, 'DESC');
187 | }
188 |
189 | public function get(): array
190 | {
191 | return array_merge(
192 | $this->select,
193 | $this->wheres,
194 | $this->orders,
195 | $this->pagination,
196 | );
197 | }
198 |
199 | public function dd(): void
200 | {
201 | dd($this->get());
202 | }
203 |
204 | public static function make(): static
205 | {
206 | return app(static::class);
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/src/ServiceProvider.php:
--------------------------------------------------------------------------------
1 | registerConfig()
22 | ->registerActions();
23 | }
24 |
25 | protected function registerConfig(): static
26 | {
27 | $this->mergeConfigFrom(__DIR__.'/../config/magento.php', 'magento');
28 |
29 | return $this;
30 | }
31 |
32 | protected function registerActions(): static
33 | {
34 | RequestAccessToken::bind();
35 | AuthenticateRequest::bind();
36 | BuildRequest::bind();
37 | CheckMagento::bind();
38 |
39 | return $this;
40 | }
41 |
42 | public function boot(): void
43 | {
44 | $this
45 | ->bootConfig()
46 | ->bootEvents()
47 | ->bootRoutes()
48 | ->bootMigrations();
49 | }
50 |
51 | protected function bootConfig(): static
52 | {
53 | $this->publishes([
54 | __DIR__.'/../config/magento.php' => config_path('magento.php'),
55 | ], 'config');
56 |
57 | return $this;
58 | }
59 |
60 | protected function bootEvents(): static
61 | {
62 | Event::listen(MagentoResponseEvent::class, StoreAvailabilityListener::class);
63 |
64 | return $this;
65 | }
66 |
67 | protected function bootRoutes(): static
68 | {
69 | if (! app()->routesAreCached()) {
70 | Route::prefix(config('magento.oauth.prefix'))
71 | ->middleware([OAuthMiddleware::class])
72 | ->group(__DIR__.'/../routes/web.php');
73 | }
74 |
75 | return $this;
76 | }
77 |
78 | protected function bootMigrations(): static
79 | {
80 | $this->loadMigrationsFrom(__DIR__.'/../database/migrations');
81 |
82 | return $this;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/tests/Actions/CheckMagentoTest.php:
--------------------------------------------------------------------------------
1 | assertTrue($action->available('default'));
18 | }
19 |
20 | #[Test]
21 | public function it_can_be_unavailable(): void
22 | {
23 | /** @var CheckMagento $action */
24 | $action = app(CheckMagento::class);
25 |
26 | cache()->put(CheckMagento::AVAILABLE_KEY.config('magento.connection'), false);
27 |
28 | $this->assertFalse($action->available('default'));
29 | }
30 |
31 | #[Test]
32 | public function it_can_handle_multiple_connections(): void
33 | {
34 | /** @var CheckMagento $action */
35 | $action = app(CheckMagento::class);
36 |
37 | cache()->put(CheckMagento::AVAILABLE_KEY.'connection', false);
38 | cache()->put(CheckMagento::AVAILABLE_KEY.'another-connection', true);
39 |
40 | $this->assertFalse($action->available('connection'));
41 | $this->assertTrue($action->available('another-connection'));
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/tests/Actions/OAuth/RequestAccessTokenTest.php:
--------------------------------------------------------------------------------
1 | setIdentifier('::request-token::');
21 | $temporaryCredentials->setSecret('::request-token-secret::');
22 |
23 | $tokenCredentials = new TokenCredentials;
24 | $tokenCredentials->setIdentifier('::access-token::');
25 | $tokenCredentials->setSecret('::access-token-secret::');
26 |
27 | /** @var MagentoServer $server */
28 | $server = $this->mock(MagentoServer::class,
29 | function (MockInterface $mock) use ($temporaryCredentials, $tokenCredentials): void {
30 | $mock
31 | ->shouldReceive('getTemporaryCredentials')
32 | ->once()
33 | ->andReturn($temporaryCredentials);
34 |
35 | $mock
36 | ->shouldReceive('getTokenCredentials')
37 | ->once()
38 | ->andReturn($tokenCredentials);
39 | });
40 |
41 | app()->bind(MagentoServer::class, fn () => $server);
42 |
43 | $this->mock(FileKeyStore::class, function (MockInterface $mock): void {
44 | $mock
45 | ->shouldReceive('get')
46 | ->once()
47 | ->andReturn([
48 | 'callback' => [
49 | 'oauth_consumer_key' => '::oauth-consumer-key::',
50 | 'oauth_consumer_secret' => '::oauth-consumer-secret::',
51 | 'oauth_verifier' => '::oauth-verifier::',
52 | ],
53 | ]);
54 |
55 | $mock
56 | ->shouldReceive('set')
57 | ->once()
58 | ->with('default', [
59 | 'oauth_consumer_key' => '::oauth-consumer-key::',
60 | 'oauth_consumer_secret' => '::oauth-consumer-secret::',
61 | 'oauth_verifier' => '::oauth-verifier::',
62 | 'access_token' => '::access-token::',
63 | 'access_token_secret' => '::access-token-secret::',
64 | ]);
65 | });
66 |
67 | $key = '::oauth-consumer-key::';
68 |
69 | /** @var RequestAccessToken $action */
70 | $action = app(RequestAccessToken::class);
71 | $action->request('default', $key);
72 | }
73 |
74 | /** @test */
75 | public function it_can_throw_http_exceptions(): void
76 | {
77 | $this->expectException(HttpException::class);
78 |
79 | $this->mock(FileKeyStore::class, function (MockInterface $mock): void {
80 | $mock
81 | ->shouldReceive('get')
82 | ->once()
83 | ->andReturn([
84 | 'callback' => [
85 | 'oauth_consumer_key' => '::oauth-consumer-key::',
86 | 'oauth_consumer_secret' => '::oauth-consumer-secret::',
87 | 'oauth_verifier' => '::oauth-verifier::',
88 | ],
89 | ]);
90 | });
91 |
92 | $key = '::different-key::';
93 |
94 | /** @var RequestAccessToken $action */
95 | $action = app(RequestAccessToken::class);
96 | $action->request('default', $key);
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/tests/Client/ClientTest.php:
--------------------------------------------------------------------------------
1 | Http::response([
22 | 'data' => [
23 | 'currency' => [
24 | 'base_currency_code' => 'EUR',
25 | ],
26 | ],
27 | ]),
28 | ]);
29 |
30 | /** @var Magento $magento */
31 | $magento = app(Magento::class);
32 |
33 | $response = $magento->store('default')->graphql('query { currency { base_currency_code } }', ['testing']);
34 |
35 | $this->assertTrue($response->ok());
36 | $this->assertEquals('EUR', $response->json('data.currency.base_currency_code'));
37 |
38 | Http::assertSent(function (Request $request): bool {
39 |
40 | return $request->method() === 'POST'
41 | && $request->url() === 'magento/graphql'
42 | && $request->body() === '{"query":"query { currency { base_currency_code } }","variables":["testing"]}'
43 | && $request->header('Store') === ['default'];
44 | });
45 | }
46 |
47 | public function test_it_can_make_a_get_call(): void
48 | {
49 | Http::fake([
50 | 'magento/rest/all/V1/products*' => Http::response(['items' => []]),
51 | ]);
52 |
53 | /** @var Magento $magento */
54 | $magento = app(Magento::class);
55 |
56 | $response = $magento->get('products', [
57 | 'searchCriteria[pageSize]' => 10,
58 | 'searchCriteria[currentPage]' => 0,
59 | ]);
60 | $this->assertEquals(true, $response->ok());
61 | $this->assertCount(0, $response->json('items'));
62 |
63 | Http::assertSent(function (Request $request) {
64 | return $request->method() === 'GET' &&
65 | $request->url() == 'magento/rest/all/V1/products?searchCriteria%5BpageSize%5D=10&searchCriteria%5BcurrentPage%5D=0';
66 | });
67 | }
68 |
69 | public function test_it_can_make_a_post_call(): void
70 | {
71 | Http::fake([
72 | 'magento/rest/all/V1/products' => Http::response([
73 | 'product' => [
74 | 'entity_id' => 1,
75 | 'sku' => '::some-sku::',
76 | ],
77 | ]),
78 | ]);
79 |
80 | /** @var Magento $magento */
81 | $magento = app(Magento::class);
82 |
83 | $response = $magento->post('products', [
84 | 'product' => [
85 | 'sku' => '::some-sku::',
86 | ],
87 | ]);
88 | $this->assertEquals(true, $response->ok());
89 | $this->assertCount(2, $response->json('product'));
90 |
91 | Http::assertSent(function (Request $request) {
92 | return $request->method() === 'POST' &&
93 | $request->url() == 'magento/rest/all/V1/products' &&
94 | $request->body() === '{"product":{"sku":"::some-sku::"}}';
95 | });
96 | }
97 |
98 | public function test_it_can_make_an_async_post_call(): void
99 | {
100 | Http::fake([
101 | 'magento/rest/all/async/V1/products' => Http::response([
102 | 'product' => [
103 | 'entity_id' => 1,
104 | 'sku' => '::some-sku::',
105 | ],
106 | ]),
107 | ]);
108 |
109 | /** @var Magento $magento */
110 | $magento = app(Magento::class);
111 |
112 | $response = $magento->postAsync('products', [
113 | 'product' => [
114 | 'sku' => '::some-sku::',
115 | ],
116 | ]);
117 | $this->assertEquals(true, $response->ok());
118 | $this->assertCount(2, $response->json('product'));
119 |
120 | Http::assertSent(function (Request $request) {
121 | return $request->method() === 'POST' &&
122 | $request->url() == 'magento/rest/all/async/V1/products' &&
123 | $request->body() === '{"product":{"sku":"::some-sku::"}}';
124 | });
125 | }
126 |
127 | public function test_it_can_make_a_bulk_post_call(): void
128 | {
129 | Http::fake([
130 | 'magento/rest/all/async/bulk/V1/products' => Http::response([
131 | 'bulk_uuid' => Str::uuid()->toString(),
132 | 'request_items' => [
133 | [
134 | 'id' => 0,
135 | 'data_hash' => '0000000000000000000000000000000000000000000000000000000000000000',
136 | 'status' => 'accepted',
137 | ],
138 | [
139 | 'id' => 1,
140 | 'data_hash' => '0000000000000000000000000000000000000000000000000000000000000000',
141 | 'status' => 'accepted',
142 | ],
143 | ],
144 | 'errors' => false,
145 | ]),
146 | ]);
147 |
148 | /** @var Magento $magento */
149 | $magento = app(Magento::class);
150 |
151 | $response = $magento->postBulk('products', [
152 | [
153 | 'product' => [
154 | 'sku' => '::sku-1::',
155 | ],
156 | ],
157 | [
158 | 'product' => [
159 | 'sku' => '::sku-2::',
160 | ],
161 | ],
162 | ]);
163 |
164 | $this->assertEquals(true, $response->ok());
165 | $this->assertCount(2, $response->json('request_items'));
166 |
167 | Http::assertSent(function (Request $request): bool {
168 | return $request->method() === 'POST' &&
169 | $request->url() == 'magento/rest/all/async/bulk/V1/products' &&
170 | $request->body() === '[{"product":{"sku":"::sku-1::"}},{"product":{"sku":"::sku-2::"}}]';
171 | });
172 | }
173 |
174 | public function test_it_can_make_a_put_call(): void
175 | {
176 | Http::fake([
177 | 'magento/rest/all/V1/products' => Http::response([
178 | 'product' => [
179 | 'entity_id' => 1,
180 | 'sku' => '::some-sku::',
181 | ],
182 | ]),
183 | ]);
184 |
185 | /** @var Magento $magento */
186 | $magento = app(Magento::class);
187 |
188 | $response = $magento->put('products', [
189 | 'product' => [
190 | 'sku' => '::some-sku::',
191 | ],
192 | ]);
193 | $this->assertEquals(true, $response->ok());
194 | $this->assertCount(2, $response->json('product'));
195 |
196 | Http::assertSent(function (Request $request) {
197 | return $request->method() === 'PUT' &&
198 | $request->url() == 'magento/rest/all/V1/products' &&
199 | $request->body() === '{"product":{"sku":"::some-sku::"}}';
200 | });
201 | }
202 |
203 | public function test_it_can_make_an_async_put_call(): void
204 | {
205 | Http::fake([
206 | 'magento/rest/all/async/V1/products' => Http::response([
207 | 'product' => [
208 | 'entity_id' => 1,
209 | 'sku' => '::some-sku::',
210 | ],
211 | ]),
212 | ]);
213 |
214 | /** @var Magento $magento */
215 | $magento = app(Magento::class);
216 |
217 | $response = $magento->putAsync('products', [
218 | 'product' => [
219 | 'sku' => '::some-sku::',
220 | ],
221 | ]);
222 | $this->assertEquals(true, $response->ok());
223 | $this->assertCount(2, $response->json('product'));
224 |
225 | Http::assertSent(function (Request $request) {
226 | return $request->method() === 'PUT' &&
227 | $request->url() == 'magento/rest/all/async/V1/products' &&
228 | $request->body() === '{"product":{"sku":"::some-sku::"}}';
229 | });
230 | }
231 |
232 | public function test_it_can_make_a_bulk_put_call(): void
233 | {
234 | Http::fake([
235 | 'magento/rest/all/async/bulk/V1/products' => Http::response([
236 | 'bulk_uuid' => Str::uuid()->toString(),
237 | 'request_items' => [
238 | [
239 | 'id' => 0,
240 | 'data_hash' => '0000000000000000000000000000000000000000000000000000000000000000',
241 | 'status' => 'accepted',
242 | ],
243 | [
244 | 'id' => 1,
245 | 'data_hash' => '0000000000000000000000000000000000000000000000000000000000000000',
246 | 'status' => 'accepted',
247 | ],
248 | ],
249 | 'errors' => false,
250 | ]),
251 | ]);
252 |
253 | /** @var Magento $magento */
254 | $magento = app(Magento::class);
255 |
256 | $response = $magento->putBulk('products', [
257 | [
258 | 'product' => [
259 | 'sku' => '::sku-1::',
260 | ],
261 | ],
262 | [
263 | 'product' => [
264 | 'sku' => '::sku-2::',
265 | ],
266 | ],
267 | ]);
268 |
269 | $this->assertEquals(true, $response->ok());
270 | $this->assertCount(2, $response->json('request_items'));
271 |
272 | Http::assertSent(function (Request $request): bool {
273 | return $request->method() === 'PUT' &&
274 | $request->url() == 'magento/rest/all/async/bulk/V1/products' &&
275 | $request->body() === '[{"product":{"sku":"::sku-1::"}},{"product":{"sku":"::sku-2::"}}]';
276 | });
277 | }
278 |
279 | public function test_it_can_make_a_patch_call(): void
280 | {
281 | Http::fake([
282 | 'magento/rest/all/V1/products' => Http::response([
283 | 'product' => [
284 | 'entity_id' => 1,
285 | 'sku' => '::some-sku::',
286 | ],
287 | ]),
288 | ]);
289 |
290 | /** @var Magento $magento */
291 | $magento = app(Magento::class);
292 |
293 | $response = $magento->patch('products', [
294 | 'product' => [
295 | 'sku' => '::some-sku::',
296 | ],
297 | ]);
298 | $this->assertEquals(true, $response->ok());
299 | $this->assertCount(2, $response->json('product'));
300 |
301 | Http::assertSent(function (Request $request) {
302 | return $request->method() === 'PATCH' &&
303 | $request->url() == 'magento/rest/all/V1/products' &&
304 | $request->body() === '{"product":{"sku":"::some-sku::"}}';
305 | });
306 | }
307 |
308 | public function test_it_can_make_an_async_patch_call(): void
309 | {
310 | Http::fake([
311 | 'magento/rest/all/async/V1/products' => Http::response([
312 | 'product' => [
313 | 'entity_id' => 1,
314 | 'sku' => '::some-sku::',
315 | ],
316 | ]),
317 | ]);
318 |
319 | /** @var Magento $magento */
320 | $magento = app(Magento::class);
321 |
322 | $response = $magento->patchAsync('products', [
323 | 'product' => [
324 | 'sku' => '::some-sku::',
325 | ],
326 | ]);
327 | $this->assertEquals(true, $response->ok());
328 | $this->assertCount(2, $response->json('product'));
329 |
330 | Http::assertSent(function (Request $request) {
331 | return $request->method() === 'PATCH' &&
332 | $request->url() == 'magento/rest/all/async/V1/products' &&
333 | $request->body() === '{"product":{"sku":"::some-sku::"}}';
334 | });
335 | }
336 |
337 | public function test_it_can_make_a_bulk_patch_call(): void
338 | {
339 | Http::fake([
340 | 'magento/rest/all/async/bulk/V1/products' => Http::response([
341 | 'bulk_uuid' => Str::uuid()->toString(),
342 | 'request_items' => [
343 | [
344 | 'id' => 0,
345 | 'data_hash' => '0000000000000000000000000000000000000000000000000000000000000000',
346 | 'status' => 'accepted',
347 | ],
348 | [
349 | 'id' => 1,
350 | 'data_hash' => '0000000000000000000000000000000000000000000000000000000000000000',
351 | 'status' => 'accepted',
352 | ],
353 | ],
354 | 'errors' => false,
355 | ]),
356 | ]);
357 |
358 | /** @var Magento $magento */
359 | $magento = app(Magento::class);
360 |
361 | $response = $magento->patchBulk('products', [
362 | [
363 | 'product' => [
364 | 'sku' => '::sku-1::',
365 | ],
366 | ],
367 | [
368 | 'product' => [
369 | 'sku' => '::sku-2::',
370 | ],
371 | ],
372 | ]);
373 |
374 | $this->assertEquals(true, $response->ok());
375 | $this->assertCount(2, $response->json('request_items'));
376 |
377 | Http::assertSent(function (Request $request): bool {
378 | return $request->method() === 'PATCH' &&
379 | $request->url() == 'magento/rest/all/async/bulk/V1/products' &&
380 | $request->body() === '[{"product":{"sku":"::sku-1::"}},{"product":{"sku":"::sku-2::"}}]';
381 | });
382 | }
383 |
384 | public function test_it_can_make_a_delete_call(): void
385 | {
386 | Http::fake([
387 | 'magento/rest/all/V1/products/1' => Http::response([], 204),
388 | ]);
389 |
390 | /** @var Magento $magento */
391 | $magento = app(Magento::class);
392 |
393 | $response = $magento->delete('products/1');
394 | $this->assertEquals(204, $response->status());
395 |
396 | Http::assertSent(function (Request $request) {
397 | return $request->method() === 'DELETE' &&
398 | $request->url() == 'magento/rest/all/V1/products/1';
399 | });
400 | }
401 |
402 | public function test_it_can_make_an_async_delete_call(): void
403 | {
404 | Http::fake([
405 | 'magento/rest/all/async/V1/products/1' => Http::response([], 204),
406 | ]);
407 |
408 | /** @var Magento $magento */
409 | $magento = app(Magento::class);
410 |
411 | $response = $magento->deleteAsync('products/1');
412 | $this->assertEquals(204, $response->status());
413 |
414 | Http::assertSent(function (Request $request) {
415 | return $request->method() === 'DELETE' &&
416 | $request->url() == 'magento/rest/all/async/V1/products/1';
417 | });
418 | }
419 |
420 | public function test_it_can_make_a_bulk_delete_call(): void
421 | {
422 | Http::fake([
423 | 'magento/rest/all/async/bulk/V1/products/bySku' => Http::response([
424 | 'bulk_uuid' => Str::uuid()->toString(),
425 | 'request_items' => [
426 | [
427 | 'id' => 0,
428 | 'data_hash' => '0000000000000000000000000000000000000000000000000000000000000000',
429 | 'status' => 'accepted',
430 | ],
431 | [
432 | 'id' => 1,
433 | 'data_hash' => '0000000000000000000000000000000000000000000000000000000000000000',
434 | 'status' => 'accepted',
435 | ],
436 | ],
437 | 'errors' => false,
438 | ]),
439 | ]);
440 |
441 | /** @var Magento $magento */
442 | $magento = app(Magento::class);
443 |
444 | $response = $magento->deleteBulk('products/bySku', [
445 | [
446 | 'sku' => '::sku-1::',
447 | ],
448 | [
449 | 'sku' => '::sku-2::',
450 | ],
451 | ]);
452 |
453 | $this->assertEquals(true, $response->ok());
454 | $this->assertCount(2, $response->json('request_items'));
455 |
456 | Http::assertSent(function (Request $request): bool {
457 | return $request->method() === 'DELETE' &&
458 | $request->url() == 'magento/rest/all/async/bulk/V1/products/bySku' &&
459 | $request->body() === '[{"sku":"::sku-1::"},{"sku":"::sku-2::"}]';
460 | });
461 | }
462 |
463 | public function test_it_can_get_results_lazily(): void
464 | {
465 | Http::fake([
466 | 'magento/rest/all/V1/products?searchCriteria%5Bfilter_groups%5D%5B0%5D%5Bfilters%5D%5B0%5D%5Bfield%5D=sku&searchCriteria%5Bfilter_groups%5D%5B0%5D%5Bfilters%5D%5B0%5D%5Bcondition_type%5D=neq&searchCriteria%5Bfilter_groups%5D%5B0%5D%5Bfilters%5D%5B0%5D%5Bvalue%5D=something&searchCriteria%5BpageSize%5D=5&searchCriteria%5BcurrentPage%5D=1' => Http::response([
467 | 'items' => [
468 | ['sku' => '1000'],
469 | ['sku' => '2000'],
470 | ['sku' => '3000'],
471 | ['sku' => '4000'],
472 | ['sku' => '5000'],
473 | ],
474 | ]),
475 | 'magento/rest/all/V1/products?searchCriteria%5Bfilter_groups%5D%5B0%5D%5Bfilters%5D%5B0%5D%5Bfield%5D=sku&searchCriteria%5Bfilter_groups%5D%5B0%5D%5Bfilters%5D%5B0%5D%5Bcondition_type%5D=neq&searchCriteria%5Bfilter_groups%5D%5B0%5D%5Bfilters%5D%5B0%5D%5Bvalue%5D=something&searchCriteria%5BpageSize%5D=5&searchCriteria%5BcurrentPage%5D=2' => Http::response([
476 | 'items' => [
477 | ['sku' => '6000'],
478 | ['sku' => '7000'],
479 | ],
480 | ]),
481 | ]);
482 |
483 | /** @var Magento $magento */
484 | $magento = app(Magento::class);
485 |
486 | $products = $magento->lazy('products', [
487 | 'searchCriteria[filter_groups][0][filters][0][field]' => 'sku',
488 | 'searchCriteria[filter_groups][0][filters][0][condition_type]' => 'neq',
489 | 'searchCriteria[filter_groups][0][filters][0][value]' => 'something',
490 | ], 5);
491 |
492 | $collection = $products->collect();
493 |
494 | $this->assertEquals(7, $collection->count());
495 | }
496 |
497 | public function test_it_can_set_store_code(): void
498 | {
499 | Http::fake([
500 | 'magento/rest/store/V1/products*' => Http::response(['items' => []]),
501 | ]);
502 |
503 | /** @var Magento $magento */
504 | $magento = app(Magento::class);
505 |
506 | $magento->store('store')->get('products');
507 |
508 | Http::assertSent(function (Request $request) {
509 | return $request->url() == 'magento/rest/store/V1/products';
510 | });
511 | }
512 |
513 | public function test_it_can_intersect_the_client_and_adjust_its_body_format(): void
514 | {
515 | Http::fake([
516 | 'magento/rest/all/V1/products*' => Http::response(['items' => []]),
517 | ]);
518 |
519 | /** @var Magento $magento */
520 | $magento = app(Magento::class);
521 |
522 | $result = $magento->intercept(function (PendingRequest $request) {
523 | $request->asForm();
524 | });
525 |
526 | $this->assertInstanceOf(Magento::class, $result);
527 |
528 | $response = $magento->get('products', [
529 | 'searchCriteria[pageSize]' => 10,
530 | 'searchCriteria[currentPage]' => 0,
531 | ]);
532 |
533 | $this->assertTrue($response->ok());
534 | $this->assertCount(0, $response->json('items'));
535 |
536 | Http::assertSent(function (Request $request) {
537 | return $request->method() === 'GET' &&
538 | $request->header('Content-Type')[0] === 'application/x-www-form-urlencoded' &&
539 | $request->url() == 'magento/rest/all/V1/products?searchCriteria%5BpageSize%5D=10&searchCriteria%5BcurrentPage%5D=0';
540 | });
541 | }
542 |
543 | public function test_it_resets_the_interceptor(): void
544 | {
545 | Http::fake([
546 | 'magento/rest/all/V1/products' => Http::response([
547 | 'product' => [
548 | 'entity_id' => 1,
549 | 'sku' => '::some-sku::',
550 | ],
551 | ]),
552 | ]);
553 |
554 | /** @var Magento $magento */
555 | $magento = app(Magento::class);
556 |
557 | $magento->intercept(function (PendingRequest $request) {
558 | $request->withHeaders(['some-header' => '::test::']);
559 | })->post('products', [
560 | 'product' => [
561 | 'sku' => '::some-sku::',
562 | ],
563 | ]);
564 |
565 | $magento->post('products', [
566 | 'product' => [
567 | 'sku' => '::some-sku::',
568 | ],
569 | ]);
570 |
571 | Http::assertSentInOrder([
572 | fn (Request $request) => $request->hasHeader('some-header'),
573 | fn (Request $request) => ! $request->hasHeader('some-header'),
574 | ]);
575 | }
576 |
577 | public function test_it_dispatches_event(): void
578 | {
579 | Event::fake();
580 |
581 | Http::fake([
582 | 'magento/rest/all/V1/products*' => Http::response(['items' => ['item']]),
583 | ]);
584 |
585 | /** @var Magento $magento */
586 | $magento = app(Magento::class);
587 |
588 | $magento->get('products', [
589 | 'searchCriteria[pageSize]' => 10,
590 | 'searchCriteria[currentPage]' => 0,
591 | ]);
592 |
593 | Event::assertDispatched(MagentoResponseEvent::class, function (MagentoResponseEvent $event): bool {
594 | return $event->response->ok() && $event->response->json('items') === ['item'];
595 | });
596 | }
597 |
598 | public function test_it_checks_available(): void
599 | {
600 | $this->mock(ChecksMagento::class, function (MockInterface $mock): void {
601 | $mock->shouldReceive('available')->with('default')->once()->andReturnTrue();
602 | $mock->shouldReceive('available')->with('unavailable')->once()->andReturnFalse();
603 | });
604 |
605 | /** @var Magento $magento */
606 | $magento = app(Magento::class);
607 |
608 | $this->assertTrue($magento->available());
609 | $this->assertFalse($magento->connection('unavailable')->available());
610 | }
611 | }
612 |
--------------------------------------------------------------------------------
/tests/Client/MultiConnectionTest.php:
--------------------------------------------------------------------------------
1 | set('magento.connections.otherconnection', [
19 | 'base_url' => 'otherconnection',
20 | 'base_path' => 'rest',
21 | 'store_code' => 'all',
22 | 'version' => 'V1',
23 | 'access_token' => '::token::',
24 | 'timeout' => 30,
25 | 'connect_timeout' => 10,
26 | 'authentication_method' => 'token',
27 | ]);
28 | }
29 |
30 | /** @test */
31 | public function it_can_do_requests_to_multiple_connections(): void
32 | {
33 | Http::fake([
34 | 'magento*' => Http::response(),
35 | 'otherconnection*' => Http::response(),
36 | ])->preventStrayRequests();
37 |
38 | /** @var Magento $magento */
39 | $magento = app(Magento::class);
40 |
41 | $magento->connection('default')->get('products');
42 | $magento->connection('otherconnection')->get('products');
43 |
44 | Http::assertSentInOrder([
45 | fn (Request $request): bool => $request->url() === 'magento/rest/all/V1/products',
46 | fn (Request $request): bool => $request->url() === 'otherconnection/rest/all/V1/products',
47 | ]);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tests/Enums/AuthenticationMethodTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf($expectedProvider, $authenticationMethod->provider());
20 | }
21 |
22 | public static function providers(): array
23 | {
24 | return [
25 | [
26 | AuthenticationMethod::Token,
27 | BearerTokenProvider::class,
28 | ],
29 | [
30 | AuthenticationMethod::OAuth,
31 | OAuthProvider::class,
32 | ],
33 | ];
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/tests/Fakes/TestJob.php:
--------------------------------------------------------------------------------
1 | '::oauth_consumer_key::',
17 | 'oauth_consumer_secret' => '::oauth_consumer_secret::',
18 | 'oauth_verifier' => '::oauth_verifier::',
19 | ];
20 |
21 | $this->mock(FileKeyStore::class, function (MockInterface $mock) use ($payload): void {
22 | $mock
23 | ->shouldReceive('merge')
24 | ->with('default', ['callback' => $payload])
25 | ->once();
26 | });
27 |
28 | $this
29 | ->withoutMiddleware()
30 | ->post(route('magento.oauth.callback', ['connection' => 'default']), $payload, [
31 | 'Accept' => 'application/json',
32 | ])
33 | ->assertSuccessful();
34 | }
35 |
36 | /** @test */
37 | public function it_can_validate_the_callback_endpoint(): void
38 | {
39 | $this
40 | ->withoutMiddleware()
41 | ->post(route('magento.oauth.callback', ['connection' => 'default']), [], [
42 | 'Accept' => 'application/json',
43 | ])
44 | ->assertStatus(422);
45 | }
46 |
47 | /** @test */
48 | public function it_can_block_the_callback_endpoint_without_oauth_authentication(): void
49 | {
50 | $this->post(route('magento.oauth.callback', ['connection' => 'default']), [], [
51 | 'Accept' => 'application/json',
52 | ])->assertStatus(403);
53 | }
54 |
55 | /** @test */
56 | public function it_can_call_the_identity_endpoint(): void
57 | {
58 | $this->mock(RequestsAccessToken::class, function (MockInterface $mock): void {
59 | $mock
60 | ->shouldReceive('request')
61 | ->with('default', '::oauth_consumer_key::')
62 | ->once();
63 | });
64 |
65 | $route = route('magento.oauth.identity', [
66 | 'connection' => 'default',
67 | 'oauth_consumer_key' => '::oauth_consumer_key::',
68 | 'success_call_back' => '::success_call_back::',
69 | ]);
70 |
71 | $this->withoutMiddleware()->get($route, [
72 | 'Accept' => 'application/json',
73 | ])->assertRedirect('::success_call_back::');
74 | }
75 |
76 | /** @test */
77 | public function it_can_validate_the_identity_endpoint(): void
78 | {
79 | $this
80 | ->withoutMiddleware()
81 | ->get(route('magento.oauth.identity', ['connection' => 'default']), [
82 | 'Accept' => 'application/json',
83 | ])
84 | ->assertStatus(422);
85 | }
86 |
87 | /** @test */
88 | public function it_can_block_the_identity_endpoint_without_oauth_authentication(): void
89 | {
90 | $this->get(route('magento.oauth.identity', ['connection' => 'default']), [
91 | 'Accept' => 'application/json',
92 | ])->assertStatus(403);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/tests/Http/Middleware/OAuthMiddlewareTest.php:
--------------------------------------------------------------------------------
1 | set('magento.connections.default.authentication_method', 'oauth');
16 |
17 | /** @var OAuthMiddleware $middleware */
18 | $middleware = app(OAuthMiddleware::class);
19 |
20 | /** @var Response $response */
21 | $response = $middleware->handle(request(), function (): Response {
22 | return response('passed');
23 | });
24 |
25 | $this->assertEquals('passed', $response->getContent());
26 | }
27 |
28 | /** @test */
29 | public function it_can_abort(): void
30 | {
31 | $this->expectException(HttpException::class);
32 |
33 | /** @var OAuthMiddleware $middleware */
34 | $middleware = app(OAuthMiddleware::class);
35 | $middleware->handle(request(), function (): Response {
36 | return response('passed');
37 | });
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/Jobs/Middleware/AvailableMiddlewareTest.php:
--------------------------------------------------------------------------------
1 | handle($job, function () use (&$ran): void {
23 | $ran = true;
24 | });
25 |
26 | $this->assertTrue($ran);
27 | }
28 |
29 | #[Test]
30 | public function it_can_release_jobs(): void
31 | {
32 | $this->mock(Magento::class, function (MockInterface $mock): void {
33 | $mock->shouldReceive('connection')->with('default')->once()->andReturnSelf();
34 | $mock->shouldReceive('available')->once()->andReturnFalse();
35 | });
36 |
37 | $job = $this->mock(TestJob::class, function (MockInterface $mock): void {
38 | $mock->shouldReceive('release')->once();
39 | });
40 |
41 | $middleware = new AvailableMiddleware('default');
42 | $middleware->handle($job, function (): void {
43 | $this->fail('Job should not have run.');
44 | });
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/tests/Listeners/StoreAvailabilityListenerTest.php:
--------------------------------------------------------------------------------
1 | Http::response(null, 200),
19 | ])->preventStrayRequests();
20 |
21 | $magento = app(Magento::class);
22 | $magento->get('/');
23 |
24 | $this->assertNull(
25 | cache()->get(StoreAvailabilityListener::COUNT_KEY.'default')
26 | );
27 | }
28 |
29 | #[Test]
30 | public function it_can_keep_track_of_the_count(): void
31 | {
32 | Http::fake([
33 | '*' => Http::response(null, 503),
34 | ])->preventStrayRequests();
35 |
36 | /** @var Magento $magento */
37 | $magento = app(Magento::class);
38 | $magento->get('/');
39 |
40 | $this->assertEquals(1, cache()->get(StoreAvailabilityListener::COUNT_KEY.'default'));
41 | $this->assertNull(cache()->get(CheckMagento::AVAILABLE_KEY.'default'));
42 | }
43 |
44 | #[Test]
45 | public function it_can_be_unavailable(): void
46 | {
47 | Http::fake([
48 | '*' => Http::response(null, 503),
49 | ])->preventStrayRequests();
50 |
51 | config()->set('magento.connections.default.availability.threshold', 1);
52 |
53 | /** @var Magento $magento */
54 | $magento = app(Magento::class);
55 | $magento->get('/');
56 |
57 | $this->assertNull(cache()->get(StoreAvailabilityListener::COUNT_KEY.'default'));
58 | $this->assertFalse(cache()->get(CheckMagento::AVAILABLE_KEY.'default'));
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/tests/OAuth/HmacSha256SignatureTest.php:
--------------------------------------------------------------------------------
1 | setIdentifier('::oauth-consumer-key::');
16 | $clientCredentials->setSecret('::oauth-consumer-secret::');
17 |
18 | /** @var HmacSha256Signature $signature */
19 | $signature = app()->makeWith(HmacSha256Signature::class, [
20 | 'clientCredentials' => $clientCredentials,
21 | ]);
22 |
23 | $method = $signature->method();
24 |
25 | $this->assertEquals('HMAC-SHA256', $method);
26 |
27 | $signed = $signature->sign('test');
28 |
29 | $this->assertEquals('mpy8OxeIYvroN5WQW9f/aBMtC+qbmus1oaa0KAMoMiQ=', $signed);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/OAuth/KeyStore/DatabaseKeyStoreTest.php:
--------------------------------------------------------------------------------
1 | set('magento.oauth.keystore', DatabaseKeyStore::class);
20 | }
21 |
22 | /** @test */
23 | public function it_can_get_data(): void
24 | {
25 | $content = [
26 | 'key' => 'value',
27 | ];
28 |
29 | $store = KeyStore::instance();
30 |
31 | OAuthKey::query()->create([
32 | 'magento_connection' => 'default',
33 | 'keys' => $content,
34 | ]);
35 |
36 | $data = $store->get('default');
37 |
38 | $this->assertEquals($content, $data);
39 | }
40 |
41 | /** @test */
42 | public function it_can_set_data(): void
43 | {
44 | $content = [
45 | 'key' => 'value',
46 | ];
47 |
48 | $store = KeyStore::instance();
49 | $store->set('default', $content);
50 |
51 | $data = $store->get('default');
52 |
53 | $this->assertEquals($content, $data);
54 | }
55 |
56 | /** @test */
57 | public function it_can_merge_data(): void
58 | {
59 | $content = [
60 | 'key' => 'value',
61 | ];
62 |
63 | OAuthKey::query()->create([
64 | 'magento_connection' => 'default',
65 | 'keys' => $content,
66 | ]);
67 |
68 | $store = KeyStore::instance();
69 |
70 | $new = [
71 | 'something' => 'else',
72 | ];
73 |
74 | $store->merge('default', $new);
75 |
76 | $data = $store->get('default');
77 |
78 | $this->assertEquals(array_merge($content, $new), $data);
79 | }
80 |
81 | /** @test */
82 | public function it_can_handle_multiple_connections(): void
83 | {
84 | $store = KeyStore::instance();
85 |
86 | $store->set('connection_one', ['one']);
87 | $store->set('connection_two', ['two']);
88 |
89 | $this->assertEquals(['one'], $store->get('connection_one'));
90 | $this->assertEquals(['two'], $store->get('connection_two'));
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/tests/OAuth/KeyStore/FileKeyStoreTest.php:
--------------------------------------------------------------------------------
1 | set('magento.oauth.keystore', FileKeyStore::class);
19 | }
20 |
21 | /** @test */
22 | public function it_can_get_data(): void
23 | {
24 | $content = [
25 | 'key' => 'value',
26 | ];
27 |
28 | /** @var FileKeyStore $store */
29 | $store = KeyStore::instance();
30 |
31 | /** @var non-empty-string $encoded */
32 | $encoded = json_encode($content);
33 |
34 | Storage::disk($store->disk)->put($store->path.'/default.json', $encoded);
35 |
36 | $data = $store->get('default');
37 |
38 | $this->assertEquals($content, $data);
39 | }
40 |
41 | /** @test */
42 | public function it_can_set_data(): void
43 | {
44 | $content = [
45 | 'key' => 'value',
46 | ];
47 |
48 | /** @var FileKeyStore $store */
49 | $store = KeyStore::instance();
50 | $store->set('default', $content);
51 |
52 | $data = $store->get('default');
53 |
54 | $this->assertEquals($content, $data);
55 | }
56 |
57 | /** @test */
58 | public function it_can_merge_data(): void
59 | {
60 | $content = [
61 | 'key' => 'value',
62 | ];
63 |
64 | /** @var non-empty-string $encoded */
65 | $encoded = json_encode($content);
66 |
67 | /** @var FileKeyStore $store */
68 | $store = KeyStore::instance();
69 |
70 | Storage::disk($store->disk)->put($store->path.'/default.json', $encoded);
71 |
72 | $new = [
73 | 'something' => 'else',
74 | ];
75 |
76 | $store->merge('default', $new);
77 |
78 | $data = $store->get('default');
79 |
80 | $this->assertEquals(array_merge($content, $new), $data);
81 | }
82 |
83 | /** @test */
84 | public function it_can_handle_multiple_connections(): void
85 | {
86 | $store = KeyStore::instance();
87 |
88 | $store->set('connection_one', ['one']);
89 | $store->set('connection_two', ['two']);
90 |
91 | $this->assertEquals(['one'], $store->get('connection_one'));
92 | $this->assertEquals(['two'], $store->get('connection_two'));
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/tests/Providers/BearerTokenProviderTest.php:
--------------------------------------------------------------------------------
1 | authenticate($pendingRequest, 'default');
21 |
22 | $options = $pendingRequest->getOptions();
23 |
24 | $authorization = data_get($options, 'headers.Authorization');
25 |
26 | $this->assertEquals($authorization, 'Bearer ::token::');
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/Providers/OAuthProviderTest.php:
--------------------------------------------------------------------------------
1 | mock(FileKeyStore::class, function (MockInterface $mock): void {
20 | $mock
21 | ->shouldReceive('get')
22 | ->once()
23 | ->andReturn([
24 | 'oauth_consumer_key' => '::oauth-consumer-key::',
25 | 'oauth_consumer_secret' => '::oauth-consumer-secret::',
26 | 'oauth_verifier' => '::oauth-verifier::',
27 | 'access_token' => '::access-token::',
28 | 'access_token_secret' => '::access-token-secret::',
29 | ]);
30 | });
31 |
32 | $pendingRequest = Http::baseUrl('localhost');
33 |
34 | /** @var OAuthProvider $provider */
35 | $provider = app(OAuthProvider::class);
36 | $provider->authenticate($pendingRequest, 'default');
37 |
38 | $authorization = null;
39 |
40 | try {
41 | $pendingRequest->withMiddleware(
42 | Middleware::mapRequest(function (RequestInterface $request) use (&$authorization) {
43 | $authorization = $request->getHeader('Authorization');
44 |
45 | throw new Exception('Cancel request execution');
46 | })
47 | )->get('/', [
48 | 'key' => 'value',
49 | ]);
50 | } catch (Exception) {
51 | //
52 | }
53 |
54 | if ($authorization === null) {
55 | $this->fail('No authorization header has been set');
56 | }
57 |
58 | $authorization = $authorization[0];
59 |
60 | if (! preg_match_all('/(?[a-z_]+)(?>=)/', $authorization, $matches)) {
61 | $this->fail('Could not match any keys');
62 | }
63 |
64 | $this->assertEquals([
65 | 'oauth_consumer_key',
66 | 'oauth_nonce',
67 | 'oauth_signature_method',
68 | 'oauth_timestamp',
69 | 'oauth_version',
70 | 'oauth_verifier',
71 | 'oauth_token',
72 | 'oauth_signature',
73 | ], $matches['keys']);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/tests/Query/SearchCriteriaTest.php:
--------------------------------------------------------------------------------
1 | paginate(1, 10)
16 | ->get();
17 |
18 | $this->assertEquals([
19 | 'searchCriteria[pageSize]' => 10,
20 | 'searchCriteria[currentPage]' => 1,
21 | ], $searchCriteria);
22 | }
23 |
24 | public function test_it_can_add_a_simple_where(): void
25 | {
26 | $searchCriteria = SearchCriteria::make()
27 | ->where('sku', '::some-sku::')
28 | ->get();
29 |
30 | $this->assertEquals([
31 | 'searchCriteria[filter_groups][0][filters][0][field]' => 'sku',
32 | 'searchCriteria[filter_groups][0][filters][0][condition_type]' => 'eq',
33 | 'searchCriteria[filter_groups][0][filters][0][value]' => '::some-sku::',
34 | ], $searchCriteria);
35 | }
36 |
37 | public function test_it_can_add_multiple_wheres(): void
38 | {
39 | $searchCriteria = SearchCriteria::make()
40 | ->where('sku', '=', '::some-sku::')
41 | ->where('name', '::some-name::')
42 | ->get();
43 |
44 | $this->assertEquals([
45 | 'searchCriteria[filter_groups][0][filters][0][field]' => 'sku',
46 | 'searchCriteria[filter_groups][0][filters][0][condition_type]' => 'eq',
47 | 'searchCriteria[filter_groups][0][filters][0][value]' => '::some-sku::',
48 | 'searchCriteria[filter_groups][1][filters][0][field]' => 'name',
49 | 'searchCriteria[filter_groups][1][filters][0][condition_type]' => 'eq',
50 | 'searchCriteria[filter_groups][1][filters][0][value]' => '::some-name::',
51 | ], $searchCriteria);
52 | }
53 |
54 | public function test_it_can_add_or_where(): void
55 | {
56 | $searchCriteria = SearchCriteria::make()
57 | ->where('sku', '=', '::some-sku::')
58 | ->orWhere('name', '=', '::some-name::')
59 | ->get();
60 |
61 | $this->assertEquals([
62 | 'searchCriteria[filter_groups][0][filters][0][field]' => 'sku',
63 | 'searchCriteria[filter_groups][0][filters][0][condition_type]' => 'eq',
64 | 'searchCriteria[filter_groups][0][filters][0][value]' => '::some-sku::',
65 | 'searchCriteria[filter_groups][0][filters][1][field]' => 'name',
66 | 'searchCriteria[filter_groups][0][filters][1][condition_type]' => 'eq',
67 | 'searchCriteria[filter_groups][0][filters][1][value]' => '::some-name::',
68 | ], $searchCriteria);
69 | }
70 |
71 | public function test_it_can_add_multiple_or_where(): void
72 | {
73 | $searchCriteria = SearchCriteria::make()
74 | ->where('sku', '=', '::some-sku::')
75 | ->orWhere('name', '=', '::some-name::')
76 | ->where('some_attribute', '>', 10)
77 | ->orWhere('another_attribute', '<=', 100)
78 | ->orWhere('test_attribute', '<>', '::some_value::')
79 | ->get();
80 |
81 | $this->assertEquals([
82 | 'searchCriteria[filter_groups][0][filters][0][field]' => 'sku',
83 | 'searchCriteria[filter_groups][0][filters][0][condition_type]' => 'eq',
84 | 'searchCriteria[filter_groups][0][filters][0][value]' => '::some-sku::',
85 | 'searchCriteria[filter_groups][0][filters][1][field]' => 'name',
86 | 'searchCriteria[filter_groups][0][filters][1][condition_type]' => 'eq',
87 | 'searchCriteria[filter_groups][0][filters][1][value]' => '::some-name::',
88 | 'searchCriteria[filter_groups][1][filters][0][field]' => 'some_attribute',
89 | 'searchCriteria[filter_groups][1][filters][0][condition_type]' => 'gt',
90 | 'searchCriteria[filter_groups][1][filters][0][value]' => '10',
91 | 'searchCriteria[filter_groups][1][filters][1][field]' => 'another_attribute',
92 | 'searchCriteria[filter_groups][1][filters][1][condition_type]' => 'lteq',
93 | 'searchCriteria[filter_groups][1][filters][1][value]' => '100',
94 | 'searchCriteria[filter_groups][1][filters][2][field]' => 'test_attribute',
95 | 'searchCriteria[filter_groups][1][filters][2][condition_type]' => 'neq',
96 | 'searchCriteria[filter_groups][1][filters][2][value]' => '::some_value::',
97 | ], $searchCriteria);
98 | }
99 |
100 | public function test_it_can_add_where_in(): void
101 | {
102 | $searchCriteria = SearchCriteria::make()
103 | ->whereIn('sku', ['::sku_1::', '::sku_2::', '::sku_3::'])
104 | ->get();
105 |
106 | $this->assertEquals([
107 | 'searchCriteria[filter_groups][0][filters][0][field]' => 'sku',
108 | 'searchCriteria[filter_groups][0][filters][0][condition_type]' => 'in',
109 | 'searchCriteria[filter_groups][0][filters][0][value]' => '::sku_1::,::sku_2::,::sku_3::',
110 | ], $searchCriteria);
111 | }
112 |
113 | public function test_it_can_add_or_where_in(): void
114 | {
115 | $searchCriteria = SearchCriteria::make()
116 | ->whereIn('sku', ['::sku_1::', '::sku_2::', '::sku_3::'])
117 | ->orWhereIn('ean', ['::sku_1::', '::sku_2::', '::sku_3::'])
118 | ->get();
119 |
120 | $this->assertEquals([
121 | 'searchCriteria[filter_groups][0][filters][0][field]' => 'sku',
122 | 'searchCriteria[filter_groups][0][filters][0][condition_type]' => 'in',
123 | 'searchCriteria[filter_groups][0][filters][0][value]' => '::sku_1::,::sku_2::,::sku_3::',
124 |
125 | 'searchCriteria[filter_groups][0][filters][1][field]' => 'ean',
126 | 'searchCriteria[filter_groups][0][filters][1][condition_type]' => 'in',
127 | 'searchCriteria[filter_groups][0][filters][1][value]' => '::sku_1::,::sku_2::,::sku_3::',
128 | ], $searchCriteria);
129 | }
130 |
131 | public function test_it_can_add_where_not_in(): void
132 | {
133 | $searchCriteria = SearchCriteria::make()
134 | ->whereNotIn('sku', ['::sku_1::', '::sku_2::', '::sku_3::'])
135 | ->get();
136 |
137 | $this->assertEquals([
138 | 'searchCriteria[filter_groups][0][filters][0][field]' => 'sku',
139 | 'searchCriteria[filter_groups][0][filters][0][condition_type]' => 'nin',
140 | 'searchCriteria[filter_groups][0][filters][0][value]' => '::sku_1::,::sku_2::,::sku_3::',
141 | ], $searchCriteria);
142 | }
143 |
144 | public function test_it_can_add_or_where_not_in(): void
145 | {
146 | $searchCriteria = SearchCriteria::make()
147 | ->whereNotIn('sku', ['::sku_1::', '::sku_2::', '::sku_3::'])
148 | ->orWhereNotIn('ean', ['::sku_1::', '::sku_2::', '::sku_3::'])
149 | ->get();
150 |
151 | $this->assertEquals([
152 | 'searchCriteria[filter_groups][0][filters][0][field]' => 'sku',
153 | 'searchCriteria[filter_groups][0][filters][0][condition_type]' => 'nin',
154 | 'searchCriteria[filter_groups][0][filters][0][value]' => '::sku_1::,::sku_2::,::sku_3::',
155 |
156 | 'searchCriteria[filter_groups][0][filters][1][field]' => 'ean',
157 | 'searchCriteria[filter_groups][0][filters][1][condition_type]' => 'nin',
158 | 'searchCriteria[filter_groups][0][filters][1][value]' => '::sku_1::,::sku_2::,::sku_3::',
159 | ], $searchCriteria);
160 | }
161 |
162 | public function test_it_can_add_a_field_selection_as_string(): void
163 | {
164 | $searchCriteria = SearchCriteria::make()
165 | ->select('items[sku,price]')
166 | ->get();
167 |
168 | $this->assertEquals([
169 | 'fields' => 'items[sku,price]',
170 | ], $searchCriteria);
171 | }
172 |
173 | public function test_it_can_add_a_field_selection_as_array(): void
174 | {
175 | $searchCriteria = SearchCriteria::make()
176 | ->select(['items[sku,price]', 'total_count'])
177 | ->get();
178 |
179 | $this->assertEquals([
180 | 'fields' => 'items[sku,price],total_count',
181 | ], $searchCriteria);
182 | }
183 |
184 | public function test_it_can_dd(): void
185 | {
186 | $searchCriteria = SearchCriteria::make()
187 | ->select(['sku', 'price']);
188 |
189 | $this->expectException(Exception::class);
190 |
191 | VarDumper::setHandler(function (array $data) {
192 | $this->assertEquals([
193 | 'fields' => 'sku,price',
194 | ], $data);
195 |
196 | throw new Exception;
197 | });
198 |
199 | $searchCriteria->dd();
200 | }
201 |
202 | public function test_it_can_add_where_null(): void
203 | {
204 | $searchCriteria = SearchCriteria::make()
205 | ->where('sku', '=', '::sku::')
206 | ->whereNull('::some_field::')
207 | ->get();
208 |
209 | $this->assertEquals([
210 | 'searchCriteria[filter_groups][0][filters][0][field]' => 'sku',
211 | 'searchCriteria[filter_groups][0][filters][0][condition_type]' => 'eq',
212 | 'searchCriteria[filter_groups][0][filters][0][value]' => '::sku::',
213 | 'searchCriteria[filter_groups][1][filters][0][field]' => '::some_field::',
214 | 'searchCriteria[filter_groups][1][filters][0][condition_type]' => 'null',
215 | ], $searchCriteria);
216 | }
217 |
218 | public function test_it_can_add_or_where_null(): void
219 | {
220 | $searchCriteria = SearchCriteria::make()
221 | ->whereNull('::some_field::')
222 | ->orWhereNull('::some_other_field::')
223 | ->get();
224 |
225 | $this->assertEquals([
226 | 'searchCriteria[filter_groups][0][filters][0][field]' => '::some_field::',
227 | 'searchCriteria[filter_groups][0][filters][0][condition_type]' => 'null',
228 | 'searchCriteria[filter_groups][0][filters][1][field]' => '::some_other_field::',
229 | 'searchCriteria[filter_groups][0][filters][1][condition_type]' => 'null',
230 | ], $searchCriteria);
231 | }
232 |
233 | public function test_it_can_add_where_not_null(): void
234 | {
235 | $searchCriteria = SearchCriteria::make()
236 | ->where('sku', '=', '::sku::')
237 | ->whereNotNull('::some_field::')
238 | ->get();
239 |
240 | $this->assertEquals([
241 | 'searchCriteria[filter_groups][0][filters][0][field]' => 'sku',
242 | 'searchCriteria[filter_groups][0][filters][0][condition_type]' => 'eq',
243 | 'searchCriteria[filter_groups][0][filters][0][value]' => '::sku::',
244 | 'searchCriteria[filter_groups][1][filters][0][field]' => '::some_field::',
245 | 'searchCriteria[filter_groups][1][filters][0][condition_type]' => 'notnull',
246 | ], $searchCriteria);
247 | }
248 |
249 | public function test_it_can_add_or_where_not_null(): void
250 | {
251 | $searchCriteria = SearchCriteria::make()
252 | ->whereNotNull('::some_field::')
253 | ->orWhereNotNull('::some_other_field::')
254 | ->get();
255 |
256 | $this->assertEquals([
257 | 'searchCriteria[filter_groups][0][filters][0][field]' => '::some_field::',
258 | 'searchCriteria[filter_groups][0][filters][0][condition_type]' => 'notnull',
259 | 'searchCriteria[filter_groups][0][filters][1][field]' => '::some_other_field::',
260 | 'searchCriteria[filter_groups][0][filters][1][condition_type]' => 'notnull',
261 | ], $searchCriteria);
262 | }
263 |
264 | public function test_it_can_order_by(): void
265 | {
266 | $searchCriteria = SearchCriteria::make()
267 | ->orderBy('sku')
268 | ->get();
269 |
270 | $this->assertEquals([
271 | 'searchCriteria[sortOrders][0][field]' => 'sku',
272 | 'searchCriteria[sortOrders][0][direction]' => 'ASC',
273 | ], $searchCriteria);
274 | }
275 |
276 | public function test_it_can_order_by_desc(): void
277 | {
278 | $searchCriteria = SearchCriteria::make()
279 | ->orderByDesc('sku')
280 | ->get();
281 |
282 | $this->assertEquals([
283 | 'searchCriteria[sortOrders][0][field]' => 'sku',
284 | 'searchCriteria[sortOrders][0][direction]' => 'DESC',
285 | ], $searchCriteria);
286 | }
287 |
288 | public function test_it_can_add_multiple_orders(): void
289 | {
290 | $searchCriteria = SearchCriteria::make()
291 | ->orderBy('sku')
292 | ->orderByDesc('entity_id')
293 | ->get();
294 |
295 | $this->assertEquals([
296 | 'searchCriteria[sortOrders][0][field]' => 'sku',
297 | 'searchCriteria[sortOrders][0][direction]' => 'ASC',
298 | 'searchCriteria[sortOrders][1][field]' => 'entity_id',
299 | 'searchCriteria[sortOrders][1][direction]' => 'DESC',
300 | ], $searchCriteria);
301 | }
302 | }
303 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | set('database.default', 'testbench');
26 | $app['config']->set('database.connections.testbench', [
27 | 'driver' => 'sqlite',
28 | 'database' => ':memory:',
29 | 'prefix' => '',
30 | ]);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------