diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
new file mode 100644
index 0000000..b4ae1c4
--- /dev/null
+++ b/.github/CONTRIBUTING.md
@@ -0,0 +1,55 @@
+# Contributing
+
+Contributions are **welcome** and will be fully **credited**.
+
+Please read and understand the contribution guide before creating an issue or pull request.
+
+## Etiquette
+
+This project is open source, and as such, the maintainers give their free time to build and maintain the source code
+held within. They make the code freely available in the hope that it will be of use to other developers. It would be
+extremely unfair for them to suffer abuse or anger for their hard work.
+
+Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the
+world that developers are civilized and selfless people.
+
+It's the duty of the maintainer to ensure that all submissions to the project are of sufficient
+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.
+
+## Viability
+
+When requesting or submitting new features, first consider whether it might be useful to others. Open
+source projects are used by many developers, who may have entirely different needs to your own. Think about
+whether or not your feature is likely to be used by other users of the project.
+
+## Procedure
+
+Before filing an issue:
+
+- Attempt to replicate the problem, to ensure that it wasn't a coincidental incident.
+- Check to make sure your feature suggestion isn't already present within the project.
+- Check the pull requests tab to ensure that the bug doesn't have a fix in progress.
+- Check the pull requests tab to ensure that the feature isn't already in progress.
+
+Before submitting a pull request:
+
+- Check the codebase to ensure that your feature doesn't already exist.
+- Check the pull requests to ensure that another person hasn't already submitted the feature or fix.
+
+## Requirements
+
+If the project maintainer has any additional requirements, you will find them listed here.
+
+- **[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).
+
+- **Add tests!** - Your patch won't be accepted if it doesn't have tests.
+
+- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.
+
+- **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option.
+
+- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
+
+- **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.
+
+**Happy coding**!
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 0000000..8b9c8e3
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,54 @@
+name: Bug Report
+description: Report an Issue or Bug with the Package
+title: "[Bug]: "
+labels: ["bug"]
+body:
+ - type: markdown
+ attributes:
+ value: "|
+We're sorry to hear you have a problem. Can you help us solve it by providing the following details."
+ - type: textarea
+ id: what-happened
+ attributes:
+ label: What happened?
+ description: What did you expect to happen?
+ placeholder: "I cannot currently do X thing because when I do, it breaks X thing."
+ validations:
+ required: true
+ - type: input
+ id: package-version
+ attributes:
+ label: Package Version
+ description: What version of our Package are you running? Please be as specific as possible
+ placeholder: "11.0"
+ value: "11.0"
+ validations:
+ required: true
+ - type: input
+ id: php-version
+ attributes:
+ label: PHP Version
+ description: What version of PHP are you running? Please be as specific as possible
+ placeholder: "8.3.0"
+ value: "8.3.0"
+ validations:
+ required: true
+ - type: input
+ id: laravel-version
+ attributes:
+ label: Laravel Version
+ description: What version of Laravel are you running? Please be as specific as possible
+ placeholder: "11.0.0"
+ value: "11.0.0"
+ validations:
+ required: true
+ - type: dropdown
+ id: operating-systems
+ attributes:
+ label: Which operating systems does with happen with?
+ description: You may select more than one.
+ multiple: true
+ options:
+ - macOS
+ - Windows
+ - Linux
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
new file mode 100644
index 0000000..b259caf
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -0,0 +1,12 @@
+name: Question or Feature
+description: Open a Question or Feature Request
+title: "[Feature]: "
+labels: ["feature"]
+body:
+ - type: textarea
+ id: question-feature
+ attributes:
+ label: Question or Feature?
+ description: Enter a Question or Feature Request
+ validations:
+ required: true
diff --git a/.github/SECURITY.md b/.github/SECURITY.md
new file mode 100644
index 0000000..dd923a2
--- /dev/null
+++ b/.github/SECURITY.md
@@ -0,0 +1,3 @@
+# Security Policy
+
+If you discover any security related issues, please email info@codebar.ch instead of using the issue tracker.
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..30c8a49
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,12 @@
+# Please see the documentation for all configuration options:
+# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
+
+version: 2
+updates:
+
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ labels:
+ - "dependencies"
\ No newline at end of file
diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml
new file mode 100644
index 0000000..eb537d8
--- /dev/null
+++ b/.github/workflows/dependabot-auto-merge.yml
@@ -0,0 +1,32 @@
+name: dependabot-auto-merge
+on: pull_request_target
+
+permissions:
+ pull-requests: write
+ contents: write
+
+jobs:
+ dependabot:
+ runs-on: ubuntu-latest
+ if: ${{ github.actor == 'dependabot[bot]' }}
+ steps:
+
+ - name: Dependabot metadata
+ id: metadata
+ uses: dependabot/fetch-metadata@v2.2.0
+ with:
+ github-token: "${{ secrets.GITHUB_TOKEN }}"
+
+ - name: Auto-merge Dependabot PRs for semver-minor updates
+ if: ${{steps.metadata.outputs.update-type == 'version-update:semver-minor'}}
+ run: gh pr merge --auto --merge "$PR_URL"
+ env:
+ PR_URL: ${{github.event.pull_request.html_url}}
+ GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
+
+ - name: Auto-merge Dependabot PRs for semver-patch updates
+ if: ${{steps.metadata.outputs.update-type == 'version-update:semver-patch'}}
+ run: gh pr merge --auto --merge "$PR_URL"
+ env:
+ PR_URL: ${{github.event.pull_request.html_url}}
+ GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml
new file mode 100644
index 0000000..0d4a013
--- /dev/null
+++ b/.github/workflows/dependency-review.yml
@@ -0,0 +1,20 @@
+# Dependency Review Action
+#
+# This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging.
+#
+# Source repository: https://github.com/actions/dependency-review-action
+# Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement
+name: 'Dependency Review'
+on: [pull_request]
+
+permissions:
+ contents: read
+
+jobs:
+ dependency-review:
+ runs-on: ubuntu-latest
+ steps:
+ - name: 'Checkout Repository'
+ uses: actions/checkout@v4
+ - name: 'Dependency Review'
+ uses: actions/dependency-review-action@v4
diff --git a/.github/workflows/fix-php-code-style-issues.yml b/.github/workflows/fix-php-code-style-issues.yml
new file mode 100644
index 0000000..9384d28
--- /dev/null
+++ b/.github/workflows/fix-php-code-style-issues.yml
@@ -0,0 +1,21 @@
+name: Fix PHP code style issues
+
+on: [push]
+
+jobs:
+ php-code-styling:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ github.head_ref }}
+
+ - name: Fix PHP code style issues
+ uses: aglipanci/laravel-pint-action@2.4
+
+ - name: Commit changes
+ uses: stefanzweifel/git-auto-commit-action@v5
+ with:
+ commit_message: Fix styling
diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml
new file mode 100644
index 0000000..d9306d6
--- /dev/null
+++ b/.github/workflows/phpstan.yml
@@ -0,0 +1,26 @@
+name: PHPStan
+
+on:
+ push:
+ paths:
+ - '**.php'
+ - 'phpstan.neon.dist'
+
+jobs:
+ phpstan:
+ name: phpstan
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: '8.3'
+ coverage: none
+
+ - name: Install composer dependencies
+ uses: ramsey/composer-install@v3
+
+ - name: Run PHPStan
+ run: ./vendor/bin/phpstan --error-format=github
diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml
new file mode 100644
index 0000000..a44a477
--- /dev/null
+++ b/.github/workflows/run-tests.yml
@@ -0,0 +1,55 @@
+name: run-tests
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ test:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: true
+ max-parallel: 1
+ matrix:
+ os: [ ubuntu-latest ]
+ php: [ 8.3 ]
+ laravel: [ 11.* ]
+ stability: [ prefer-lowest, prefer-stable ]
+
+ name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }}
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
+ coverage: none
+
+ - name: Setup problem matchers
+ run: |
+ echo "::add-matcher::${{ runner.tool_cache }}/php.json"
+ echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
+
+ - name: Install dependencies
+ run: |
+ composer require "laravel/framework:${{ matrix.laravel }}" --no-interaction --no-update
+ composer update --${{ matrix.stability }} --prefer-dist --no-interaction
+
+ - name: Execute tests
+ run: cp phpunit.xml.dist phpunit.xml
+
+ - name: Execute tests
+ run: vendor/bin/pest
+
+ - name: Store Log Artifacts
+ if: failure()
+ uses: actions/upload-artifact@v4
+ with:
+ name: Store report artifacts
+ path: ./vendor/orchestra/testbench-core/laravel/storage/logs
diff --git a/CHANGELOG.md b/CHANGELOG.md
old mode 100644
new mode 100755
diff --git a/LICENSE.md b/LICENSE.md
old mode 100644
new mode 100755
diff --git a/README.md b/README.md
old mode 100644
new mode 100755
index 100a439..cd52040
--- a/README.md
+++ b/README.md
@@ -59,7 +59,7 @@ Or:
You can publish the config file with:
```bash
-php artisan vendor:publish --tag="laravel-instagram-config"
+php artisan vendor:publish --tag="instagram-config"
```
This is the contents of the published config file:
@@ -89,6 +89,44 @@ INSTAGRAM_CLIENT_ID=your-client-id
INSTAGRAM_CLIENT_SECRET=your-client-secret
```
+## Overriding the default routes
+
+If you want to override the default routes, you can do so by creating a `instagram.php` file in your routes directory and adding the following code:
+
+```php
+name('instagram.')->group(function () {
+ Route::get('/auth', [InstagramController::class, 'auth'])->name('auth');
+
+ Route::get('/callback', [InstagramController::class, 'callback'])->name('callback');
+});
+```
+
+Then you should register the routes in your `bootstrap\app.php`:
+
+```php
+ ->withRouting(
+ web: __DIR__ . '/../routes/web.php',
+ // api: __DIR__ . '/../routes/api.php',
+ then: function () {
+ Route::middleware('web')->group(base_path('routes/instagram.php'));
+ },
+ )
+```
+
+or in your `RouteServiceProvider`:
+
+```php
+$this->routes(function () {
+ Route::middleware('web')->group(base_path('routes/web.php'));
+ Route::middleware('web')->group(base_path('routes/instagram.php'));
+});
+```
+
You can get your client id and client secret by registering your app on the [Instagram Developer Portal](https://developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login)
When configuring your app on the Instagram Developer Portal, you will need to set the redirect uri to: `http://your-app-url.com/instagram/callback`
diff --git a/composer.json b/composer.json
old mode 100644
new mode 100755
index 5170e0e..cd98b22
--- a/composer.json
+++ b/composer.json
@@ -23,10 +23,10 @@
"require": {
"php": "^8.3",
"guzzlehttp/guzzle": "^7.8",
- "illuminate/contracts": "^11.0",
+ "illuminate/contracts": "^10.0|^11.0",
"nesbot/carbon": "^2.72",
"saloonphp/cache-plugin": "^3.0",
- "saloonphp/laravel-plugin": "^3.5",
+ "saloonphp/laravel-plugin": "^3.0",
"saloonphp/saloon": "^3.7",
"spatie/laravel-package-tools": "^1.16"
},
diff --git a/config/instagram.php b/config/instagram.php
old mode 100644
new mode 100755
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
old mode 100644
new mode 100755
index ea9f28a..2064984
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -17,8 +17,8 @@
-
-
+
+
diff --git a/ray.php b/ray.php
old mode 100644
new mode 100755
diff --git a/routes/instagram.php b/routes/instagram.php
old mode 100644
new mode 100755
index b148886..c6fa6d4
--- a/routes/instagram.php
+++ b/routes/instagram.php
@@ -7,8 +7,4 @@
Route::get('/auth', [InstagramController::class, 'auth'])->name('auth');
Route::get('/callback', [InstagramController::class, 'callback'])->name('callback');
-
- Route::get('/me', [InstagramController::class, 'me'])->name('me');
-
- Route::get('/media', [InstagramController::class, 'media'])->name('media');
});
diff --git a/src/Actions/InstagramHandler.php b/src/Actions/InstagramHandler.php
old mode 100644
new mode 100755
diff --git a/src/Authenticator/InstagramAuthenticator.php b/src/Authenticator/InstagramAuthenticator.php
old mode 100644
new mode 100755
diff --git a/src/Commands/LaravelInstagramCommand.php b/src/Commands/LaravelInstagramCommand.php
old mode 100644
new mode 100755
diff --git a/src/Connectors/InstagramConnector.php b/src/Connectors/InstagramConnector.php
old mode 100644
new mode 100755
diff --git a/src/Data/InstagramImage.php b/src/Data/InstagramImage.php
old mode 100644
new mode 100755
diff --git a/src/Data/InstagramUser.php b/src/Data/InstagramUser.php
old mode 100644
new mode 100755
diff --git a/src/Http/Controllers/InstagramController.php b/src/Http/Controllers/InstagramController.php
old mode 100644
new mode 100755
index a251e2d..f07777d
--- a/src/Http/Controllers/InstagramController.php
+++ b/src/Http/Controllers/InstagramController.php
@@ -50,6 +50,8 @@ public function callback(Request $request)
$json = $response->json();
+ ray($json);
+
Cache::store(config('instagram.cache_store'))->put('instagram.authenticated', $json, now()->addDays(60));
return response('Authenticated Instagram account: '.Arr::get($json, 'username'), 200);
diff --git a/src/LaravelInstagramServiceProvider.php b/src/LaravelInstagramServiceProvider.php
old mode 100644
new mode 100755
diff --git a/src/Requests/Authentication/GetAccessTokenRequest.php b/src/Requests/Authentication/GetAccessTokenRequest.php
old mode 100644
new mode 100755
diff --git a/src/Requests/Authentication/GetShortLivedAccessTokenRequest.php b/src/Requests/Authentication/GetShortLivedAccessTokenRequest.php
old mode 100644
new mode 100755
diff --git a/src/Requests/GetInstagramMe.php b/src/Requests/GetInstagramMe.php
old mode 100644
new mode 100755
index 6d1acbe..5b24b7d
--- a/src/Requests/GetInstagramMe.php
+++ b/src/Requests/GetInstagramMe.php
@@ -4,8 +4,11 @@
namespace CodebarAg\LaravelInstagram\Requests;
+use CodebarAg\LaravelInstagram\Data\InstagramUser;
+use CodebarAg\LaravelInstagram\Responses\CreateInstagramUserFromResponse;
use Saloon\Enums\Method;
use Saloon\Http\Request;
+use Saloon\Http\Response;
use Saloon\Traits\Plugins\AcceptsJson;
class GetInstagramMe extends Request
@@ -39,4 +42,9 @@ public function defaultQuery(): array
'fields' => implode(',', $fields),
];
}
+
+ public function createDtoFromResponse(Response $response): InstagramUser
+ {
+ return CreateInstagramUserFromResponse::fromResponse($response);
+ }
}
diff --git a/src/Requests/GetInstagramMedia.php b/src/Requests/GetInstagramMedia.php
old mode 100644
new mode 100755
index 99cae90..c548a5a
--- a/src/Requests/GetInstagramMedia.php
+++ b/src/Requests/GetInstagramMedia.php
@@ -20,6 +20,7 @@ class GetInstagramMedia extends Request
public function __construct(
protected bool $withChildren = true,
+ protected mixed $user_id = null,
) {}
/**
@@ -27,7 +28,13 @@ public function __construct(
*/
public function resolveEndpoint(): string
{
- return InstagramHandler::user()->user_id.'/media';
+ $user_id = $this->user_id;
+
+ if (empty($user_id)) {
+ $user_id = InstagramHandler::user()->user_id;
+ }
+
+ return $user_id.'/media';
}
public function defaultQuery(): array
diff --git a/src/Responses/CreateInstagramUserFromResponse.php b/src/Responses/CreateInstagramUserFromResponse.php
new file mode 100755
index 0000000..e7ae796
--- /dev/null
+++ b/src/Responses/CreateInstagramUserFromResponse.php
@@ -0,0 +1,22 @@
+json();
+
+ ray($data);
+
+ if (! $data) {
+ throw new \Exception('No data found in response');
+ }
+
+ return InstagramUser::make($data);
+ }
+}
diff --git a/src/Responses/CreateMediaCollectionFromResponse.php b/src/Responses/CreateMediaCollectionFromResponse.php
old mode 100644
new mode 100755
diff --git a/tests/Feature/ConnectorTest.php b/tests/Feature/Connectors/InstagramConnectorTest.php
old mode 100644
new mode 100755
similarity index 67%
rename from tests/Feature/ConnectorTest.php
rename to tests/Feature/Connectors/InstagramConnectorTest.php
index 99fbe77..664e84f
--- a/tests/Feature/ConnectorTest.php
+++ b/tests/Feature/Connectors/InstagramConnectorTest.php
@@ -20,34 +20,24 @@
->toContain('scope=instagram_business_basic,instagram_business_manage_messages,instagram_business_manage_comments,instagram_business_content_publish');
})->group('authorization');
-test(/**
- * @throws \Saloon\Exceptions\OAuthConfigValidationException
- * @throws \Saloon\Exceptions\InvalidStateException
- * @throws DateMalformedIntervalStringException
- */ 'can getAccessToken', function () {
+test('can getAccessToken', function () {
MockClient::global([
- GetShortLivedAccessTokenRequest::class => MockResponse::make(
- body: [
- 'access_token' => 'some_short_access_token',
- 'user_id' => 12345678901234567,
- 'permissions' => [
- 'instagram_business_basic',
- 'instagram_business_manage_messages',
- 'instagram_business_content_publish',
- 'instagram_business_manage_insights',
- 'instagram_business_manage_comments',
- ],
+ GetShortLivedAccessTokenRequest::class => MockResponse::make([
+ 'access_token' => 'some_short_access_token',
+ 'user_id' => 12345678901234567,
+ 'permissions' => [
+ 'instagram_business_basic',
+ 'instagram_business_manage_messages',
+ 'instagram_business_content_publish',
+ 'instagram_business_manage_insights',
+ 'instagram_business_manage_comments',
],
- status: 200
- ),
- GetAccessTokenRequest::class => MockResponse::make(
- body: [
- 'access_token' => 'some_long_access_token',
- 'refresh_token' => 'some_refresh_token',
- 'expires_in' => 5184000,
- ],
- status: 200
- ),
+ ]),
+ GetAccessTokenRequest::class => MockResponse::make([
+ 'access_token' => 'some_long_access_token',
+ 'refresh_token' => 'some_refresh_token',
+ 'expires_in' => 5184000,
+ ]),
]);
$connector = new InstagramConnector;
@@ -74,4 +64,4 @@
->refreshToken->toBe('some_refresh_token')
->expiresAt->toBeInstanceOf(DateTimeImmutable::class)
->expiresAt->format('Y-m-d H:i:s')->toBe($date->format('Y-m-d H:i:s'));
-});
+})->group('authorization');
diff --git a/tests/Feature/Requests/InstagramMediaTest.php b/tests/Feature/Requests/InstagramMediaTest.php
new file mode 100755
index 0000000..3dd4e94
--- /dev/null
+++ b/tests/Feature/Requests/InstagramMediaTest.php
@@ -0,0 +1,78 @@
+ MockResponse::make([
+ 'id' => '12345678901234567',
+ 'user_id' => '76543210987654321',
+ 'username' => 'some_username',
+ 'name' => 'Some Name',
+ 'account_type' => 'BUSINESS',
+ 'profile_picture_url' => 'https://some-profile-picture-url.com',
+ 'followers_count' => 123,
+ 'follows_count' => 321,
+ 'media_count' => 100,
+ ]),
+ GetInstagramMedia::class => MockResponse::make([
+ 'data' => [
+ [
+ 'id' => '11223344556677889',
+ 'caption' => 'Some caption',
+ 'media_type' => 'CAROUSEL_ALBUM',
+ 'media_url' => 'https://some-media-url.com',
+ 'permalink' => 'https://some-permalink.com',
+ 'timestamp' => '2022-10-31T19:07:12+0000',
+ 'username' => 'some_username',
+ 'children' => [
+ 'data' => [
+ [
+ 'id' => '98877665544332211',
+ 'media_type' => 'IMAGE',
+ 'media_url' => 'https://some-media-url-two.com',
+ 'permalink' => 'https://some-permalink-two.com',
+ 'timestamp' => '2022-10-31T19:07:10+0000',
+ 'username' => 'some_username',
+ ],
+ ],
+ ],
+ ],
+ ],
+ ]),
+ ]);
+
+ $connector = new InstagramConnector;
+
+ $response = $connector->send(new GetInstagramMedia(user_id: '12345678901234567'));
+
+ expect($response->dto())->toBeInstanceOf(Collection::class)
+ ->and($response->dto()->first())
+ ->toBeInstanceOf(InstagramImage::class)
+ ->id->toBe('11223344556677889')
+ ->caption->toBe('Some caption')
+ ->media_type->toBe('CAROUSEL_ALBUM')
+ ->media_url->toBe('https://some-media-url.com')
+ ->permalink->toBe('https://some-permalink.com')
+ ->timestamp->toBeInstanceOf(CarbonImmutable::class)
+ ->timestamp->format('Y-m-d H:i:s')->toBe(CarbonImmutable::parse('2022-10-31T19:07:12+0000')->format('Y-m-d H:i:s'))
+ ->username->toBe('some_username')
+ ->and($response->dto()->first()->children)->toBeInstanceOf(Collection::class)
+ ->and($response->dto()->first()->children->first())
+ ->toBeInstanceOf(InstagramImage::class)
+ ->id->toBe('98877665544332211')
+ ->media_type->toBe('IMAGE')
+ ->media_url->toBe('https://some-media-url-two.com')
+ ->permalink->toBe('https://some-permalink-two.com')
+ ->timestamp->toBeInstanceOf(CarbonImmutable::class)
+ ->timestamp->format('Y-m-d H:i:s')->toBe(CarbonImmutable::parse('2022-10-31T19:07:10+0000')->format('Y-m-d H:i:s'))
+ ->username->toBe('some_username');
+
+})->group('media');
diff --git a/tests/Feature/Requests/InstagramUserTest.php b/tests/Feature/Requests/InstagramUserTest.php
new file mode 100755
index 0000000..6626847
--- /dev/null
+++ b/tests/Feature/Requests/InstagramUserTest.php
@@ -0,0 +1,39 @@
+ MockResponse::make([
+ 'id' => '12345678901234567',
+ 'user_id' => '76543210987654321',
+ 'username' => 'some_username',
+ 'name' => 'Some Name',
+ 'account_type' => 'BUSINESS',
+ 'profile_picture_url' => 'https://some-profile-picture-url.com',
+ 'followers_count' => 123,
+ 'follows_count' => 321,
+ 'media_count' => 100,
+ ]),
+ ]);
+
+ $connector = new InstagramConnector;
+
+ $response = $connector->send(new GetInstagramMe);
+
+ expect($response->dto())->toBeInstanceOf(InstagramUser::class)
+ ->id->toBe('12345678901234567')
+ ->user_id->toBe('76543210987654321')
+ ->username->toBe('some_username')
+ ->name->toBe('Some Name')
+ ->account_type->toBe('BUSINESS')
+ ->profile_picture_url->toBe('https://some-profile-picture-url.com')
+ ->followers_count->toBe(123)
+ ->follows_count->toBe(321)
+ ->media_count->toBe(100);
+
+})->group('user');
diff --git a/tests/Pest.php b/tests/Pest.php
old mode 100644
new mode 100755
index 6fab4bc..698900b
--- a/tests/Pest.php
+++ b/tests/Pest.php
@@ -1,6 +1,11 @@
beforeEach(fn () => MockClient::destroyGlobal())
->in(__DIR__);
diff --git a/tests/TestCase.php b/tests/TestCase.php
old mode 100644
new mode 100755