mirror of
https://github.com/wearepixel/laravel-quickbooks.git
synced 2026-11-14 22:11:50 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32266f6dd5 | ||
|
|
9002e0ebca | ||
|
|
473103c243 | ||
|
|
f59295773d | ||
|
|
446f6788d2 | ||
|
|
831cd49719 | ||
|
|
bb2d375450 | ||
|
|
861a08e083 | ||
|
|
97c04ec693 | ||
|
|
8ec2c3cc38 | ||
|
|
32f5c22846 |
@@ -1,9 +0,0 @@
|
||||
* text=auto
|
||||
|
||||
/build export-ignore
|
||||
/tests export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
.travis.yml export-ignore
|
||||
nitpick.json export-ignore
|
||||
phpunit.xml.dist export-ignore
|
||||
@@ -0,0 +1,33 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
open-pull-requests-limit: 10
|
||||
groups:
|
||||
actions:
|
||||
patterns: ["*"]
|
||||
commit-message:
|
||||
prefix: "chore"
|
||||
include: "scope"
|
||||
labels:
|
||||
- "dependencies"
|
||||
|
||||
- package-ecosystem: "composer"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "tuesday"
|
||||
open-pull-requests-limit: 10
|
||||
groups:
|
||||
php-minor-patch:
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
commit-message:
|
||||
prefix: "chore"
|
||||
include: "scope"
|
||||
labels:
|
||||
- "dependencies"
|
||||
@@ -5,3 +5,6 @@ phpunit.xml
|
||||
.phpunit.result.cache
|
||||
composer.lock
|
||||
/.idea/
|
||||
.cursor
|
||||
.claude
|
||||
CLAUDE.md
|
||||
|
||||
@@ -1,115 +1,103 @@
|
||||
# Wearepixel's Laravel QuickBooks Client
|
||||
# Laravel QuickBooks Client
|
||||
|
||||
[](https://packagist.org/packages/wearepixel/laravel-quickbooks)
|
||||
[](https://packagist.org/packages/wearepixel/laravel-quickbooks)
|
||||
[](https://packagist.org/packages/wearepixel/laravel-quickbooks)
|
||||
[](https://packagist.org/packages/wearepixel/laravel-quickbooks)
|
||||
[](https://packagist.org/packages/wearepixel/laravel-quickbooks)
|
||||
[](https://packagist.org/packages/wearepixel/laravel-quickbooks)
|
||||
[](https://packagist.org/packages/wearepixel/laravel-quickbooks)
|
||||
[](https://packagist.org/packages/wearepixel/laravel-quickbooks)
|
||||
|
||||
PHP client wrapping the [QuickBooks PHP SDK](https://github.com/intuit/QuickBooks-V3-PHP-SDK).
|
||||
A Laravel package wrapping the [QuickBooks PHP SDK](https://github.com/intuit/QuickBooks-V3-PHP-SDK). Provides OAuth 2.0 authentication, automatic token management, and access to the QuickBooks Online API.
|
||||
|
||||
We solely use [Laravel](https://www.laravel.com) for our applications, so this package is written with Laravel in mind. If there is a request from the community to split this package into 2 parts to allow it to be used outside of Laravel, then we will consider doing that work.
|
||||
## Compatibility
|
||||
|
||||
## Build Status
|
||||
|
||||
| Branch | Status | Coverage | Code Quality |
|
||||
| ------ | :----: | :------: | :----------: |
|
||||
| Master | [](https://github.com/wearepixel/laravel-quickbooks/workflows/CI/badge.svg?branch=master) | [](https://scrutinizer-ci.com/g/wearepixel/laravel-quickbooks/?branch=master) | [](https://scrutinizer-ci.com/g/wearepixel/laravel-quickbooks/?branch=main) |
|
||||
| Laravel | PHP | Package |
|
||||
|---------|------|---------|
|
||||
| 12.x | ^8.3 | ^2.0 |
|
||||
| 11.x | ^8.3 | ^2.0 |
|
||||
| 10.x | ^8.3 | ^1.0 |
|
||||
|
||||
## Installation
|
||||
|
||||
1. Install QuickBooks PHP Client:
|
||||
1. Install the package:
|
||||
|
||||
```bash
|
||||
$ composer require wearepixel/laravel-quickbooks
|
||||
composer require wearepixel/laravel-quickbooks
|
||||
```
|
||||
|
||||
2. Run our migration to install the `quickbooks_tokens` table:
|
||||
2. Run the migration to create the `quickbooks_tokens` table:
|
||||
|
||||
```bash
|
||||
$ php artisan migrate --package=wearepixel/laravel-quickbooks
|
||||
php artisan migrate
|
||||
```
|
||||
|
||||
The package uses the [auto registration feature](https://laravel.com/docs/packages#package-discovery) of Laravel.
|
||||
The package uses Laravel's [auto-discovery](https://laravel.com/docs/packages#package-discovery), so no manual provider registration is needed.
|
||||
|
||||
## Configuration
|
||||
|
||||
1. You will need a ```quickBooksToken``` relationship on your ```User``` model. There is a trait named ```Wearepixel\QuickBooks\HasQuickBooksToken```, which you can include on your ```User``` model, which will setup the relationship. To do this implement the following:
|
||||
|
||||
Add ```use Wearepixel\QuickBooks\HasQuickBooksToken;``` to your service container at the top of User.php
|
||||
and also add the trait within the class. For example:
|
||||
### 1. Add the trait to your User model
|
||||
|
||||
```php
|
||||
use Wearepixel\QuickBooks\HasQuickBooksToken;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
use Notifiable, HasQuickBooksToken;
|
||||
use HasQuickBooksToken;
|
||||
}
|
||||
```
|
||||
|
||||
**NOTE: If your ```User``` model is not ```App\Models\User```, then you will need to configure the path in the ```configs/quickbooks.php```.**
|
||||
If your User model is not `App\Models\User`, publish the config and update the `user.model` value.
|
||||
|
||||
2. Add the appropriate values to your ```.env```
|
||||
### 2. Set your environment variables
|
||||
|
||||
#### Minimal Keys
|
||||
```bash
|
||||
QUICKBOOKS_CLIENT_ID=<client id given by QuickBooks>
|
||||
QUICKBOOKS_CLIENT_SECRET=<client secret>
|
||||
QUICKBOOKS_CLIENT_ID=<your client id>
|
||||
QUICKBOOKS_CLIENT_SECRET=<your client secret>
|
||||
```
|
||||
|
||||
#### Optional Keys
|
||||
Optional:
|
||||
|
||||
```bash
|
||||
QUICKBOOKS_API_URL=<Development|Production> # Defaults to App's env value
|
||||
QUICKBOOKS_DEBUG=<true|false> # Defaults to App's debug value
|
||||
QUICKBOOKS_API_URL=<Development|Production> # Defaults based on APP_ENV
|
||||
QUICKBOOKS_DEBUG=<true|false> # Defaults to APP_DEBUG
|
||||
```
|
||||
|
||||
3. _[Optional]_ Publish configs & views
|
||||
|
||||
#### Config
|
||||
A configuration file named ```quickbooks.php``` can be published to ```config/``` by running...
|
||||
### 3. Publish config and views (optional)
|
||||
|
||||
```bash
|
||||
php artisan vendor:publish --tag=quickbooks-config
|
||||
```
|
||||
|
||||
#### Views
|
||||
View files can be published by running...
|
||||
|
||||
```bash
|
||||
php artisan vendor:publish --tag=quickbooks-views
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Here is an example of getting the company information from QuickBooks:
|
||||
|
||||
### NOTE: Before doing these commands, go to your connect route (default: /quickbooks/connect) to get a QuickBooks token for your user
|
||||
First, direct users to `/quickbooks/connect` to authorize their QuickBooks account. Once connected, you can access the API:
|
||||
|
||||
```php
|
||||
php artisan tinker
|
||||
Psy Shell v0.8.17 (PHP 7.1.14 — cli) by Justin Hileman
|
||||
>>> Auth::logInUsingId(1)
|
||||
=> App\Models\User {#1668
|
||||
id: 1,
|
||||
// Other keys removed for example
|
||||
}
|
||||
>>> $quickbooks = app('Wearepixel\QuickBooks\Client') // or app('QuickBooks')
|
||||
=> Wearepixel\QuickBooks\Client {#1613}
|
||||
>>> $quickbooks->getDataService()->getCompanyInfo();
|
||||
=> QuickBooksOnline\API\Data\IPPCompanyInfo {#1673
|
||||
+CompanyName: "Sandbox Company_US_1",
|
||||
+LegalName: "Sandbox Company_US_1",
|
||||
// Other properties removed for example
|
||||
}
|
||||
>>>
|
||||
$quickbooks = app(\Wearepixel\QuickBooks\Client::class);
|
||||
|
||||
// Get company info
|
||||
$company = $quickbooks->getDataService()->getCompanyInfo();
|
||||
|
||||
// Access reports
|
||||
$reportService = $quickbooks->getReportService();
|
||||
```
|
||||
|
||||
You can call any of the resources as documented [in the SDK](https://intuit.github.io/QuickBooks-V3-PHP-SDK/quickstart.html).
|
||||
You can call any of the resources documented in the [QuickBooks PHP SDK](https://intuit.github.io/QuickBooks-V3-PHP-SDK/quickstart.html).
|
||||
|
||||
## Middleware
|
||||
|
||||
If you have routes that will be dependent on the user's account having a usable QuickBooks OAuth token, there is an included middleware ```Wearepixel\QuickBooks\Laravel\Filter``` that gets registered as ```quickbooks``` that will ensure the account is linked and redirect them to the `connect` route if needed.
|
||||
|
||||
Here is an example route definition:
|
||||
Protect routes that require a QuickBooks connection using the `quickbooks` middleware. Users without a valid token will be redirected to the connect page.
|
||||
|
||||
```php
|
||||
Route::view('some/route/needing/quickbooks/token/before/using', 'some.view')
|
||||
Route::get('quickbooks/invoices', InvoiceController::class)
|
||||
->middleware('quickbooks');
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
./vendor/bin/pest
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
# Security Policy
|
||||
|
||||
**PLEASE DON'T DISCLOSE SECURITY-RELATED ISSUES PUBLICLY, [SEE BELOW](#reporting-a-vulnerability).**
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you discover a security vulnerability, please report it privately using the below channel:
|
||||
|
||||
**GitHub Private Vulnerability Reporting** — go to the repository's **Security** tab and click **"Report a vulnerability"**. This creates a private advisory visible only to maintainers and provides a structured workflow for triage, fix coordination, and CVE assignment.
|
||||
|
||||
All security vulnerabilities will be promptly addressed.
|
||||
+19
-14
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "wearepixel/laravel-quickbooks",
|
||||
"version": "1.0.0",
|
||||
"description": "Wearepixel's Laravel Client for QuickBooks.",
|
||||
"version": "2.0.2",
|
||||
"description": "Laravel Client for QuickBooks.",
|
||||
"keywords": [
|
||||
"quickbooks",
|
||||
"client",
|
||||
@@ -16,20 +16,19 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^8.0.2",
|
||||
"illuminate/database": "^10.0|^11.0",
|
||||
"illuminate/http": "^10.0|^11.0",
|
||||
"illuminate/routing": "^10.0|^11.0",
|
||||
"illuminate/support": "^10.0|^11.0",
|
||||
"nesbot/carbon": "^2.62.1",
|
||||
"quickbooks/v3-php-sdk": "^5.4.6"
|
||||
"php": "^8.3",
|
||||
"illuminate/database": "^10.0|^11.0|^12.0",
|
||||
"illuminate/http": "^10.0|^11.0|^12.0",
|
||||
"illuminate/routing": "^10.0|^11.0|^12.0",
|
||||
"illuminate/support": "^10.0|^11.0|^12.0",
|
||||
"nesbot/carbon": "^2.62.1|^3.0",
|
||||
"quickbooks/v3-php-sdk": "^6.3.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "^1.2",
|
||||
"mockery/mockery": "^1.5.1",
|
||||
"phpunit/phpunit": "^10.0",
|
||||
"psy/psysh": "^0.11",
|
||||
"scrutinizer/ocular": "^1.9"
|
||||
"mockery/mockery": "^1.6",
|
||||
"pestphp/pest": "^3.0",
|
||||
"psy/psysh": "^0.12"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@@ -39,7 +38,10 @@
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Wearepixel\\QuickBooks\\": "tests/"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"tests/Overrides.php"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
@@ -50,6 +52,9 @@
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"pestphp/pest-plugin": true
|
||||
},
|
||||
"sort-packages": true
|
||||
},
|
||||
"prefer-stable": true
|
||||
|
||||
@@ -15,8 +15,7 @@
|
||||
public function up()
|
||||
{
|
||||
Schema::create('quickbooks_tokens', function (Blueprint $table) {
|
||||
$user_id_type =
|
||||
DB::getSchemaBuilder()->getColumnType('users', 'id') === 'bigint'
|
||||
$user_id_type = str_contains(DB::getSchemaBuilder()->getColumnType('users', 'id'), 'bigint')
|
||||
? 'unsignedBigInteger'
|
||||
: 'unsignedInteger';
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
* text=auto
|
||||
|
||||
/build export-ignore
|
||||
/tests export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
.travis.yml export-ignore
|
||||
nitpick.json export-ignore
|
||||
phpunit.xml.dist export-ignore
|
||||
+6
-23
@@ -8,15 +8,11 @@
|
||||
-->
|
||||
|
||||
<phpunit backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
backupStaticProperties="false"
|
||||
bootstrap="vendor/autoload.php"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
verbose="true">
|
||||
stopOnFailure="false">
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="Wearepixel's Laravel QuickBooks Client">
|
||||
@@ -28,26 +24,13 @@
|
||||
<!-- <env name="VARIABLE" value="value"/> -->
|
||||
</php>
|
||||
|
||||
<filter>
|
||||
<whitelist processUncoveredFilesFromWhitelist="true">
|
||||
<source>
|
||||
<include>
|
||||
<directory suffix=".php">./src</directory>
|
||||
</include>
|
||||
<exclude>
|
||||
<directory>./src/Http/Exceptions</directory>
|
||||
<directory>./src/Providers</directory>
|
||||
</exclude>
|
||||
</whitelist>
|
||||
</filter>
|
||||
|
||||
<logging>
|
||||
<log type="coverage-html"
|
||||
target="./build/phpunit/coverage"
|
||||
lowUpperBound="35"
|
||||
highLowerBound="70"/>
|
||||
<log type="coverage-text"
|
||||
target="php://stdout"
|
||||
showOnlySummary="true"
|
||||
showUncoveredFiles="false"/>
|
||||
<log type="coverage-clover" target="build/phpunit/logs/clover.xml"/>
|
||||
<log type="junit" target="./build/phpunit/logs/junit.xml"/>
|
||||
</logging>
|
||||
</source>
|
||||
</phpunit>
|
||||
|
||||
@@ -22,8 +22,7 @@ public function __construct(
|
||||
protected Redirector $redirector,
|
||||
protected Session $session,
|
||||
protected UrlGenerator $url_generator,
|
||||
) {
|
||||
}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
|
||||
@@ -3,19 +3,15 @@
|
||||
namespace Wearepixel\QuickBooks\Providers;
|
||||
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Contracts\Support\DeferrableProvider;
|
||||
use Illuminate\Support\ServiceProvider as LaravelServiceProvider;
|
||||
use Wearepixel\QuickBooks\Client;
|
||||
|
||||
/**
|
||||
* Class ClientServiceProvider
|
||||
*/
|
||||
class ClientServiceProvider extends LaravelServiceProvider
|
||||
class ClientServiceProvider extends LaravelServiceProvider implements DeferrableProvider
|
||||
{
|
||||
/**
|
||||
* Indicates if loading of the provider is deferred.
|
||||
*/
|
||||
protected bool $defer = true;
|
||||
|
||||
/**
|
||||
* Get the services provided by the provider.
|
||||
*/
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Wearepixel\QuickBooks\Providers;
|
||||
|
||||
use Illuminate\Routing\Router;
|
||||
use Illuminate\Support\ServiceProvider as LaravelServiceProvider;
|
||||
use Wearepixel\QuickBooks\Http\Controllers\Controller;
|
||||
use Wearepixel\QuickBooks\Http\Middleware\Filter;
|
||||
|
||||
/**
|
||||
@@ -89,19 +89,20 @@ protected function registerRoutes(): void
|
||||
->prefix($config['prefix'])
|
||||
->as('quickbooks.')
|
||||
->middleware($config['middleware']['default'])
|
||||
->namespace('Wearepixel\QuickBooks\Http\Controllers')
|
||||
->group(function (Router $router) use ($config) {
|
||||
$router
|
||||
->get($config['paths']['connect'], 'Controller@connect')
|
||||
->group(function () use ($config) {
|
||||
$this->app->router
|
||||
->get($config['paths']['connect'], [Controller::class, 'connect'])
|
||||
->middleware($config['middleware']['authenticated'])
|
||||
->name('connect');
|
||||
|
||||
$router
|
||||
->delete($config['paths']['disconnect'], 'Controller@disconnect')
|
||||
$this->app->router
|
||||
->delete($config['paths']['disconnect'], [Controller::class, 'disconnect'])
|
||||
->middleware($config['middleware']['authenticated'])
|
||||
->name('disconnect');
|
||||
|
||||
$router->get($config['paths']['token'], 'Controller@token')->name('token');
|
||||
$this->app->router
|
||||
->get($config['paths']['token'], [Controller::class, 'token'])
|
||||
->name('token');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+6
-3
@@ -34,11 +34,14 @@ class Token extends Model
|
||||
protected $table = 'quickbooks_tokens';
|
||||
|
||||
/**
|
||||
* The attributes that should be mutated to dates.
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $dates = ['access_token_expires_at', 'refresh_token_expires_at'];
|
||||
protected $casts = [
|
||||
'access_token_expires_at' => 'datetime',
|
||||
'refresh_token_expires_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
|
||||
+80
-181
@@ -1,34 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Wearepixel\QuickBooks;
|
||||
|
||||
use Mockery;
|
||||
use Mockery\Mock;
|
||||
use QuickBooksOnline\API\DataService\DataService;
|
||||
use QuickBooksOnline\API\Exception\ServiceException;
|
||||
use Wearepixel\QuickBooks\Client;
|
||||
use Wearepixel\QuickBooks\Token;
|
||||
|
||||
/**
|
||||
* Class ClientTest
|
||||
*/
|
||||
class ClientTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Client
|
||||
*/
|
||||
protected $client;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $configs = [];
|
||||
|
||||
/**
|
||||
* @var Mock
|
||||
*/
|
||||
protected $token_mock;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
beforeEach(function () {
|
||||
$this->configs = [
|
||||
'data_service' => [
|
||||
'auth_mode' => 'oauth2',
|
||||
@@ -40,32 +17,14 @@ protected function setUp(): void
|
||||
];
|
||||
|
||||
$this->token_mock = Mockery::mock(Token::class);
|
||||
$this->client = new Client($this->configs, $this->token_mock);
|
||||
});
|
||||
|
||||
$this->client = $this->makeClient();
|
||||
}
|
||||
it('can be constructed', function () {
|
||||
expect($this->client)->toBeInstanceOf(Client::class);
|
||||
});
|
||||
|
||||
private function makeClient($configs = null)
|
||||
{
|
||||
if (! is_null($configs)) {
|
||||
return new Client($configs, $this->token_mock);
|
||||
}
|
||||
|
||||
return new Client($this->configs, $this->token_mock);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_can_be_constructed()
|
||||
{
|
||||
$this->assertInstanceOf(Client::class, $this->client);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_returns_a_data_service_configured_to_request_oauth_token_when_token_is_empty()
|
||||
{
|
||||
it('returns a data service configured to request oauth token when token is empty', function () {
|
||||
$this->token_mock
|
||||
->shouldReceive('getAttribute')
|
||||
->twice()
|
||||
@@ -78,19 +37,15 @@ public function it_returns_a_data_service_configured_to_request_oauth_token_when
|
||||
->with('hasValidRefreshToken')
|
||||
->andReturnFalse();
|
||||
|
||||
$this->assertFalse(
|
||||
expect(
|
||||
$this->client
|
||||
->getDataService()
|
||||
->getServiceContext()
|
||||
->IppConfiguration->Security->isAccessTokenSet(),
|
||||
);
|
||||
}
|
||||
)->toBeFalse();
|
||||
});
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_caches_the_data_service_once_it_is_made()
|
||||
{
|
||||
it('caches the data service once it is made', function () {
|
||||
$this->token_mock
|
||||
->shouldReceive('getAttribute')
|
||||
->twice()
|
||||
@@ -103,15 +58,10 @@ public function it_caches_the_data_service_once_it_is_made()
|
||||
->with('hasValidRefreshToken')
|
||||
->andReturnFalse();
|
||||
|
||||
// TODO: Find out if this is the actual goal of this test, to make sure a DataService is received
|
||||
$this->assertEquals(DataService::class, get_class($this->client->getDataService()));
|
||||
}
|
||||
expect($this->client->getDataService())->toBeInstanceOf(DataService::class);
|
||||
});
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_returns_a_data_service_with_oauth_token_when_valid_access_token_exist()
|
||||
{
|
||||
it('returns a data service with oauth token when valid access token exists', function () {
|
||||
$this->token_mock
|
||||
->shouldReceive('getAttribute')
|
||||
->twice()
|
||||
@@ -136,21 +86,15 @@ public function it_returns_a_data_service_with_oauth_token_when_valid_access_tok
|
||||
->with('refresh_token')
|
||||
->andReturn('refresh_token');
|
||||
|
||||
$this->assertTrue(
|
||||
expect(
|
||||
$this->client
|
||||
->getDataService()
|
||||
->getServiceContext()
|
||||
->IppConfiguration->Security->isAccessTokenSet(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_returns_a_data_service_with_refreshed_token_when_access_token_expired_but_refresh_token_valid()
|
||||
{
|
||||
$this->expectException(ServiceException::class);
|
||||
)->toBeTrue();
|
||||
});
|
||||
|
||||
it('returns a data service with refreshed token when access token expired but refresh token valid', function () {
|
||||
$this->token_mock
|
||||
->shouldReceive('getAttribute')
|
||||
->twice()
|
||||
@@ -180,120 +124,75 @@ public function it_returns_a_data_service_with_refreshed_token_when_access_token
|
||||
->with('refresh_token')
|
||||
->andReturn('refresh_token');
|
||||
|
||||
// TODO: This really needs to test that things are getting called correctly on the DataService class
|
||||
$this->assertFalse(
|
||||
$this->client
|
||||
->getDataService()
|
||||
->getServiceContext()
|
||||
->IppConfiguration->Security->isAccessTokenSet(),
|
||||
);
|
||||
}
|
||||
$this->client->getDataService();
|
||||
})->throws(ServiceException::class);
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_returns_a_report_service_using_the_data_service()
|
||||
{
|
||||
$this->markTestSkipped(
|
||||
'Once we figure out how to test around the static DataService::Configure',
|
||||
);
|
||||
}
|
||||
it('returns a report service using the data service')
|
||||
->skip('Once we figure out how to test around the static DataService::Configure');
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_has_logging_off_by_default()
|
||||
{
|
||||
$this->markTestSkipped('Have to figure out how to test this with the new code in 5.x');
|
||||
it('has logging off by default')
|
||||
->skip('Have to figure out how to test this with the new code in 5.x');
|
||||
|
||||
$this->token_mock
|
||||
->shouldReceive('getAttribute')
|
||||
->once()
|
||||
->with('hasValidAccessToken')
|
||||
->andReturnFalse();
|
||||
it('allows logging turned on and pointed to expected file')
|
||||
->skip('Have to figure out how to test this with the new code in 5.x');
|
||||
|
||||
$this->token_mock
|
||||
->shouldReceive('getAttribute')
|
||||
->once()
|
||||
->with('hasValidRefreshToken')
|
||||
->andReturnFalse();
|
||||
|
||||
$this->assertFalse(
|
||||
filter_var(
|
||||
$this->client->getDataService()->getServiceContext()->IppConfiguration->Logger
|
||||
->RequestLog->EnableRequestResponseLogging,
|
||||
FILTER_VALIDATE_BOOLEAN,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_allows_logging_turned_on_and_pointed_to_expected_file()
|
||||
{
|
||||
$this->markTestSkipped('Have to figure out how to test this with the new code in 5.x');
|
||||
|
||||
$this->client = $this->makeClient(
|
||||
array_merge(
|
||||
[
|
||||
'logging' => [
|
||||
'enabled' => true,
|
||||
'location' => '/some/valid/path',
|
||||
],
|
||||
],
|
||||
$this->configs,
|
||||
),
|
||||
);
|
||||
|
||||
$this->token_mock
|
||||
->shouldReceive('getAttribute')
|
||||
->once()
|
||||
->with('hasValidAccessToken')
|
||||
->andReturnFalse();
|
||||
|
||||
$this->token_mock
|
||||
->shouldReceive('getAttribute')
|
||||
->once()
|
||||
->with('hasValidRefreshToken')
|
||||
->andReturnFalse();
|
||||
|
||||
$this->assertTrue(
|
||||
filter_var(
|
||||
$this->client->getDataService()->getServiceContext()->IppConfiguration->Logger
|
||||
->RequestLog->EnableRequestResponseLogging,
|
||||
FILTER_VALIDATE_BOOLEAN,
|
||||
),
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'/some/valid/path',
|
||||
$this->client->getDataService()->getServiceContext()->IppConfiguration->Logger
|
||||
->RequestLog->ServiceRequestLoggingLocation,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_returns_self_after_deleting_token()
|
||||
{
|
||||
it('returns self after deleting token', function () {
|
||||
$this->token_mock
|
||||
->shouldReceive('remove')
|
||||
->once()
|
||||
->withNoArgs()
|
||||
->andReturnSelf();
|
||||
|
||||
$this->assertInstanceOf(Client::class, $this->client->deleteToken());
|
||||
}
|
||||
}
|
||||
expect($this->client->deleteToken())->toBeInstanceOf(Client::class);
|
||||
});
|
||||
|
||||
function dir($path)
|
||||
{
|
||||
return $path === '/some/valid/path';
|
||||
}
|
||||
it('clears cached data service when token is changed', function () {
|
||||
$this->token_mock
|
||||
->shouldReceive('getAttribute')
|
||||
->with('hasValidAccessToken')
|
||||
->andReturnFalse();
|
||||
|
||||
function route($name)
|
||||
{
|
||||
return $name;
|
||||
}
|
||||
$this->token_mock
|
||||
->shouldReceive('getAttribute')
|
||||
->with('hasValidRefreshToken')
|
||||
->andReturnFalse();
|
||||
|
||||
$first = $this->client->getDataService();
|
||||
|
||||
$new_token = Mockery::mock(Token::class);
|
||||
$new_token
|
||||
->shouldReceive('getAttribute')
|
||||
->with('hasValidAccessToken')
|
||||
->andReturnFalse();
|
||||
|
||||
$new_token
|
||||
->shouldReceive('getAttribute')
|
||||
->with('hasValidRefreshToken')
|
||||
->andReturnFalse();
|
||||
|
||||
$this->client->setToken($new_token);
|
||||
|
||||
$second = $this->client->getDataService();
|
||||
|
||||
expect($second)->not->toBe($first);
|
||||
});
|
||||
|
||||
it('delegates hasValidAccessToken to token', function () {
|
||||
$this->token_mock
|
||||
->shouldReceive('getAttribute')
|
||||
->once()
|
||||
->with('hasValidAccessToken')
|
||||
->andReturnTrue();
|
||||
|
||||
expect($this->client->hasValidAccessToken())->toBeTrue();
|
||||
});
|
||||
|
||||
it('delegates hasValidRefreshToken to token', function () {
|
||||
$this->token_mock
|
||||
->shouldReceive('getAttribute')
|
||||
->once()
|
||||
->with('hasValidRefreshToken')
|
||||
->andReturnFalse();
|
||||
|
||||
expect($this->client->hasValidRefreshToken())->toBeFalse();
|
||||
});
|
||||
|
||||
@@ -1,39 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Wearepixel\QuickBooks;
|
||||
|
||||
use Wearepixel\QuickBooks\Stubs\User;
|
||||
use Wearepixel\QuickBooks\Token;
|
||||
|
||||
/**
|
||||
* Class HasQuickBooksTokenTest
|
||||
*/
|
||||
class HasQuickBooksTokenTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
protected $user;
|
||||
beforeEach(function () {
|
||||
$this->user = new User;
|
||||
});
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
it('can be constructed', function () {
|
||||
expect($this->user)->toBeInstanceOf(User::class);
|
||||
});
|
||||
|
||||
$this->user = new User();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_can_be_constructed()
|
||||
{
|
||||
$this->assertInstanceOf(User::class, $this->user);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_has_a_hasOne_relationship_to_token()
|
||||
{
|
||||
$this->assertInstanceOf(Token::class, $this->user->quickBooksToken()->getModel());
|
||||
}
|
||||
}
|
||||
it('has a hasOne relationship to token', function () {
|
||||
expect($this->user->quickBooksToken()->getModel())->toBeInstanceOf(Token::class);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace Wearepixel\QuickBooks\Http\Controllers;
|
||||
|
||||
use Illuminate\Contracts\Routing\UrlGenerator;
|
||||
use Illuminate\Contracts\View\Factory as ViewFactory;
|
||||
use Illuminate\Contracts\View\View;
|
||||
@@ -9,90 +7,33 @@
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Redirector;
|
||||
use Illuminate\Session\Store;
|
||||
use Mockery;
|
||||
use Mockery\Mock;
|
||||
use QuickBooksOnline\API\DataService\DataService;
|
||||
use Wearepixel\QuickBooks\Client as QuickBooks;
|
||||
use Wearepixel\QuickBooks\TestCase;
|
||||
use Wearepixel\QuickBooks\Http\Controllers\Controller;
|
||||
|
||||
/**
|
||||
* Class ControllerTest
|
||||
*/
|
||||
class ControllerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Controller
|
||||
*/
|
||||
protected $controller;
|
||||
|
||||
/**
|
||||
* @var Mock
|
||||
*/
|
||||
protected $data_service_mock;
|
||||
|
||||
/**
|
||||
* @var Mock
|
||||
*/
|
||||
protected $quickbooks_mock;
|
||||
|
||||
/**
|
||||
* @var Mock
|
||||
*/
|
||||
protected $redirector_mock;
|
||||
|
||||
/**
|
||||
* @var Mock
|
||||
*/
|
||||
protected $request_mock;
|
||||
|
||||
/**
|
||||
* @var Mock
|
||||
*/
|
||||
protected $session_mock;
|
||||
|
||||
/**
|
||||
* @var Mock
|
||||
*/
|
||||
protected $view_factory_mock;
|
||||
|
||||
/**
|
||||
* @var Mock
|
||||
*/
|
||||
protected $view_mock;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
beforeEach(function () {
|
||||
$this->data_service_mock = Mockery::mock(DataService::class);
|
||||
$this->quickbooks_mock = Mockery::mock(QuickBooks::class);
|
||||
$this->redirector_mock = Mockery::mock(Redirector::class);
|
||||
$this->request_mock = Mockery::mock(Request::class);
|
||||
$this->session_mock = Mockery::mock(Store::class);
|
||||
$this->url_generator_mock = Mockery::mock(UrlGenerator::class);
|
||||
$this->view_factory_mock = Mockery::mock(ViewFactory::class);
|
||||
$this->view_mock = Mockery::mock(View::class);
|
||||
|
||||
$this->controller = new Controller();
|
||||
}
|
||||
$this->controller = new Controller;
|
||||
});
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_can_be_constructed()
|
||||
{
|
||||
$this->assertInstanceOf(Controller::class, $this->controller);
|
||||
}
|
||||
it('can be constructed', function () {
|
||||
expect($this->controller)->toBeInstanceOf(Controller::class);
|
||||
});
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_shows_view_to_connect_if_account_not_linked()
|
||||
{
|
||||
it('shows view to disconnect if account linked', function () {
|
||||
$this->data_service_mock
|
||||
->shouldReceive('getCompanyInfo')
|
||||
->once()
|
||||
->withNoArgs()
|
||||
->andReturn([
|
||||
'name' => 'Company',
|
||||
]);
|
||||
->andReturn(['name' => 'Company']);
|
||||
|
||||
$this->quickbooks_mock
|
||||
->shouldReceive('hasValidRefreshToken')
|
||||
@@ -115,22 +56,13 @@ public function it_shows_view_to_connect_if_account_not_linked()
|
||||
$this->view_mock
|
||||
->shouldReceive('with')
|
||||
->once()
|
||||
->withArgs([
|
||||
'company',
|
||||
[
|
||||
'name' => 'Company',
|
||||
],
|
||||
])
|
||||
->withArgs(['company', ['name' => 'Company']])
|
||||
->andReturnSelf();
|
||||
|
||||
$this->controller->connect($this->quickbooks_mock, $this->view_factory_mock);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_shows_view_to_disconnect_if_account_linked()
|
||||
{
|
||||
it('shows view to connect if account not linked', function () {
|
||||
$this->quickbooks_mock
|
||||
->shouldReceive('hasValidRefreshToken')
|
||||
->once()
|
||||
@@ -156,13 +88,9 @@ public function it_shows_view_to_disconnect_if_account_linked()
|
||||
->andReturnSelf();
|
||||
|
||||
$this->controller->connect($this->quickbooks_mock, $this->view_factory_mock);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_disconnects_from_quickbooks_when_requested()
|
||||
{
|
||||
it('disconnects from quickbooks when requested', function () {
|
||||
$this->request_mock
|
||||
->shouldReceive('session')
|
||||
->once()
|
||||
@@ -189,15 +117,10 @@ public function it_disconnects_from_quickbooks_when_requested()
|
||||
$this->quickbooks_mock,
|
||||
);
|
||||
|
||||
$this->assertInstanceOf(RedirectResponse::class, $result);
|
||||
}
|
||||
expect($result)->toBeInstanceOf(RedirectResponse::class);
|
||||
});
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_finishes_connecting_to_quickbooks_when_given_a_valid_token_by_quickbooks()
|
||||
{
|
||||
$this->url_generator_mock = Mockery::mock(UrlGenerator::class);
|
||||
it('finishes connecting to quickbooks when given a valid token', function () {
|
||||
$realmId = random_int(1, 9999);
|
||||
|
||||
$this->quickbooks_mock
|
||||
@@ -245,6 +168,5 @@ public function it_finishes_connecting_to_quickbooks_when_given_a_valid_token_by
|
||||
$this->url_generator_mock,
|
||||
);
|
||||
|
||||
$this->assertInstanceOf(RedirectResponse::class, $result);
|
||||
}
|
||||
}
|
||||
expect($result)->toBeInstanceOf(RedirectResponse::class);
|
||||
});
|
||||
|
||||
@@ -1,53 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Wearepixel\QuickBooks\Http\Middleware;
|
||||
|
||||
use Illuminate\Contracts\Routing\UrlGenerator;
|
||||
use Illuminate\Contracts\Session\Session;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Redirector;
|
||||
use Mockery;
|
||||
use Mockery\Mock;
|
||||
use Wearepixel\QuickBooks\Client as QuickBooks;
|
||||
use Wearepixel\QuickBooks\TestCase;
|
||||
use Wearepixel\QuickBooks\Http\Middleware\Filter;
|
||||
|
||||
/**
|
||||
* Class FilterTest
|
||||
*/
|
||||
class FilterTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Filter
|
||||
*/
|
||||
protected $filter;
|
||||
|
||||
/**
|
||||
* @var Mock
|
||||
*/
|
||||
protected $quickbooks_mock;
|
||||
|
||||
/**
|
||||
* @var Mock
|
||||
*/
|
||||
protected $redirector_mock;
|
||||
|
||||
/**
|
||||
* @var Mock
|
||||
*/
|
||||
protected $request_mock;
|
||||
|
||||
/**
|
||||
* @var Mock
|
||||
*/
|
||||
protected $session_mock;
|
||||
|
||||
/**
|
||||
* @var Mock
|
||||
*/
|
||||
protected $url_generator_mock;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
beforeEach(function () {
|
||||
$this->quickbooks_mock = Mockery::mock(QuickBooks::class);
|
||||
$this->redirector_mock = Mockery::mock(Redirector::class);
|
||||
$this->request_mock = Mockery::mock(Request::class);
|
||||
@@ -60,23 +20,15 @@ protected function setUp(): void
|
||||
$this->session_mock,
|
||||
$this->url_generator_mock,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_can_be_constructed()
|
||||
{
|
||||
$this->assertInstanceOf(Filter::class, $this->filter);
|
||||
}
|
||||
it('can be constructed', function () {
|
||||
expect($this->filter)->toBeInstanceOf(Filter::class);
|
||||
});
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_just_passes_the_request_to_the_next_middleware_if_account_linked_to_quickbooks()
|
||||
{
|
||||
it('passes the request to the next middleware if account linked to quickbooks', function () {
|
||||
$next_middleware = function ($request) {
|
||||
$this->assertEquals($this->request_mock, $request);
|
||||
expect($request)->toBe($this->request_mock);
|
||||
};
|
||||
|
||||
$this->quickbooks_mock
|
||||
@@ -86,13 +38,9 @@ public function it_just_passes_the_request_to_the_next_middleware_if_account_lin
|
||||
->andReturnTrue();
|
||||
|
||||
$this->filter->handle($this->request_mock, $next_middleware);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_redirects_to_quickbooks_connect_route_after_setting_intended_session_if_account_not_linked()
|
||||
{
|
||||
it('redirects to quickbooks connect route after setting intended session if account not linked', function () {
|
||||
$this->request_mock
|
||||
->shouldReceive('path')
|
||||
->once()
|
||||
@@ -118,8 +66,7 @@ public function it_redirects_to_quickbooks_connect_route_after_setting_intended_
|
||||
->andReturnSelf();
|
||||
|
||||
$next_middleware = function ($request) {
|
||||
// If this is called, then fail test
|
||||
$this->assertTrue(false);
|
||||
throw new RuntimeException('Next middleware should not be called');
|
||||
};
|
||||
|
||||
$this->quickbooks_mock
|
||||
@@ -129,5 +76,4 @@ public function it_redirects_to_quickbooks_connect_route_after_setting_intended_
|
||||
->andReturnFalse();
|
||||
|
||||
$this->filter->handle($this->request_mock, $next_middleware);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Wearepixel\QuickBooks;
|
||||
|
||||
use Wearepixel\QuickBooks\Stubs\User;
|
||||
|
||||
function config(string $key): array
|
||||
{
|
||||
return [
|
||||
'keys' => [
|
||||
'foreign' => 'user_id',
|
||||
'owner' => 'id',
|
||||
],
|
||||
'model' => User::class,
|
||||
];
|
||||
}
|
||||
|
||||
function route(string $name): string
|
||||
{
|
||||
return $name;
|
||||
}
|
||||
|
||||
function dir(string $path): bool
|
||||
{
|
||||
return $path === '/some/valid/path';
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
|
||||
|
||||
pest()->use(MockeryPHPUnitIntegration::class);
|
||||
@@ -1,75 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Wearepixel\QuickBooks;
|
||||
|
||||
use ArrayAccess;
|
||||
use Countable;
|
||||
use Iterator;
|
||||
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
|
||||
use Mockery\MockInterface;
|
||||
use PHPUnit\Framework\TestCase as PHPUnitTestCase;
|
||||
|
||||
/**
|
||||
* Class TestCase
|
||||
*/
|
||||
abstract class TestCase extends PHPUnitTestCase
|
||||
{
|
||||
use MockeryPHPUnitIntegration;
|
||||
|
||||
/**
|
||||
* Helper to allow mocking of iterator classes.
|
||||
*
|
||||
* @link https://gist.github.com/VladaHejda/8299871
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function mockArrayIterator(MockInterface $mock, array $items)
|
||||
{
|
||||
if ($mock instanceof ArrayAccess) {
|
||||
foreach ($items as $key => $val) {
|
||||
$mock
|
||||
->shouldReceive('offsetGet')
|
||||
->with($key)
|
||||
->andReturn($val);
|
||||
|
||||
$mock
|
||||
->shouldReceive('offsetExists')
|
||||
->with($key)
|
||||
->andReturn(true);
|
||||
}
|
||||
|
||||
$mock->shouldReceive('offsetExists')->andReturn(false);
|
||||
}
|
||||
|
||||
if ($mock instanceof Iterator) {
|
||||
$counter = 0;
|
||||
|
||||
$mock->shouldReceive('rewind')->andReturnUsing(function () use (&$counter) {
|
||||
$counter = 0;
|
||||
});
|
||||
|
||||
$vals = array_values($items);
|
||||
$keys = array_values(array_keys($items));
|
||||
|
||||
$mock->shouldReceive('valid')->andReturnUsing(function () use (&$counter, $vals) {
|
||||
return isset($vals[$counter]);
|
||||
});
|
||||
|
||||
$mock->shouldReceive('current')->andReturnUsing(function () use (&$counter, $vals) {
|
||||
return $vals[$counter];
|
||||
});
|
||||
|
||||
$mock->shouldReceive('key')->andReturnUsing(function () use (&$counter, $keys) {
|
||||
return $keys[$counter];
|
||||
});
|
||||
|
||||
$mock->shouldReceive('next')->andReturnUsing(function () use (&$counter) {
|
||||
$counter++;
|
||||
});
|
||||
}
|
||||
|
||||
if ($mock instanceof Countable) {
|
||||
$mock->shouldReceive('count')->andReturn(count($items));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Contracts\Support\DeferrableProvider;
|
||||
use Wearepixel\QuickBooks\Client;
|
||||
use Wearepixel\QuickBooks\Providers\ClientServiceProvider;
|
||||
use Wearepixel\QuickBooks\Token;
|
||||
|
||||
describe('Token model structure', function () {
|
||||
it('uses the quickbooks_tokens table', function () {
|
||||
expect((new Token)->getTable())->toBe('quickbooks_tokens');
|
||||
});
|
||||
|
||||
it('casts expiration fields to datetime', function () {
|
||||
$casts = (new Token)->getCasts();
|
||||
|
||||
expect($casts)
|
||||
->toHaveKey('access_token_expires_at', 'datetime')
|
||||
->toHaveKey('refresh_token_expires_at', 'datetime');
|
||||
});
|
||||
|
||||
it('has all expected fillable attributes', function () {
|
||||
$fillable = (new Token)->getFillable();
|
||||
|
||||
expect($fillable)->toEqualCanonicalizing([
|
||||
'access_token',
|
||||
'access_token_expires_at',
|
||||
'realm_id',
|
||||
'refresh_token',
|
||||
'refresh_token_expires_at',
|
||||
'user_id',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ClientServiceProvider structure', function () {
|
||||
it('implements DeferrableProvider', function () {
|
||||
expect(ClientServiceProvider::class)
|
||||
->toImplement(DeferrableProvider::class);
|
||||
});
|
||||
|
||||
it('declares Client in provides array', function () {
|
||||
$app = Mockery::mock(\Illuminate\Contracts\Foundation\Application::class);
|
||||
$app->shouldIgnoreMissing();
|
||||
|
||||
$provider = new ClientServiceProvider($app);
|
||||
|
||||
expect($provider->provides())->toBe([Client::class]);
|
||||
});
|
||||
});
|
||||
+59
-162
@@ -1,184 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace Wearepixel\QuickBooks;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
use Mockery;
|
||||
use QuickBooksOnline\API\Core\OAuth\OAuth2\OAuth2AccessToken;
|
||||
use Wearepixel\QuickBooks\Stubs\TokenStub;
|
||||
use Wearepixel\QuickBooks\Stubs\User;
|
||||
use Wearepixel\QuickBooks\Token;
|
||||
|
||||
/**
|
||||
* Class TokenTest
|
||||
*/
|
||||
class TokenTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Token
|
||||
*/
|
||||
protected $token;
|
||||
beforeEach(function () {
|
||||
$this->token = new TokenStub;
|
||||
});
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->token = new TokenStub();
|
||||
}
|
||||
it('can be constructed', function () {
|
||||
expect($this->token)->toBeInstanceOf(Token::class);
|
||||
});
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_can_be_constructed()
|
||||
{
|
||||
$this->assertInstanceOf(Token::class, $this->token);
|
||||
}
|
||||
it('has accessor for valid access token', function () {
|
||||
expect($this->token->getHasValidAccessTokenAttribute())->not->toBeNull();
|
||||
});
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_has_accessor_for_valid_access_token()
|
||||
{
|
||||
$this->assertNotNull($this->token->getHasValidAccessTokenAttribute());
|
||||
}
|
||||
it('has accessor for valid refresh token', function () {
|
||||
expect($this->token->getHasValidRefreshTokenAttribute())->not->toBeNull();
|
||||
});
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_has_accessor_for_valid_refresh_token()
|
||||
{
|
||||
$this->assertNotNull($this->token->getHasValidRefreshTokenAttribute());
|
||||
}
|
||||
it('knows that the access token expires at has to be valid', function () {
|
||||
expect($this->token->getHasValidAccessTokenAttribute())->toBeFalse();
|
||||
});
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_knows_that_the_access_token_expires_at_has_to_be_valid()
|
||||
{
|
||||
$this->assertFalse($this->token->getHasValidAccessTokenAttribute());
|
||||
}
|
||||
it('knows that the refresh token expires at has to be valid', function () {
|
||||
expect($this->token->getHasValidRefreshTokenAttribute())->toBeFalse();
|
||||
});
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_knows_that_the_refresh_token_expires_at_has_to_be_valid()
|
||||
{
|
||||
$this->assertFalse($this->token->getHasValidRefreshTokenAttribute());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_know_if_access_token_expires_at_is_less_than_now_it_is_not_expired()
|
||||
{
|
||||
$now = Carbon::now();
|
||||
Carbon::setTestNow($now);
|
||||
it('knows if access token expires at is less than now it is not expired', function () {
|
||||
Carbon::setTestNow(Carbon::now());
|
||||
|
||||
$this->token->access_token_expires_at = Carbon::now()->addSecond();
|
||||
|
||||
$this->assertTrue($this->token->getHasValidAccessTokenAttribute());
|
||||
}
|
||||
expect($this->token->getHasValidAccessTokenAttribute())->toBeTrue();
|
||||
});
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_know_if_refresh_token_expires_at_is_less_than_now_it_is_not_expired()
|
||||
{
|
||||
$now = Carbon::now();
|
||||
Carbon::setTestNow($now);
|
||||
it('knows if refresh token expires at is less than now it is not expired', function () {
|
||||
Carbon::setTestNow(Carbon::now());
|
||||
|
||||
$this->token->refresh_token_expires_at = Carbon::now()->addSecond();
|
||||
|
||||
$this->assertTrue($this->token->getHasValidRefreshTokenAttribute());
|
||||
}
|
||||
expect($this->token->getHasValidRefreshTokenAttribute())->toBeTrue();
|
||||
});
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_know_if_access_token_expires_at_is_greater_than_or_equal_to_now_it_is_expired()
|
||||
{
|
||||
$now = Carbon::now();
|
||||
Carbon::setTestNow($now);
|
||||
it('knows if access token expires at is greater than or equal to now it is expired', function () {
|
||||
Carbon::setTestNow(Carbon::now());
|
||||
|
||||
$this->token->access_token_expires_at = Carbon::now();
|
||||
|
||||
$this->assertFalse($this->token->getHasValidAccessTokenAttribute());
|
||||
expect($this->token->getHasValidAccessTokenAttribute())->toBeFalse();
|
||||
|
||||
$this->token->access_token_expires_at->subSecond();
|
||||
expect($this->token->getHasValidAccessTokenAttribute())->toBeFalse();
|
||||
});
|
||||
|
||||
$this->assertFalse($this->token->getHasValidAccessTokenAttribute());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_know_if_refresh_token_expires_at_is_greater_than_or_equal_to_now_it_is_expired()
|
||||
{
|
||||
$now = Carbon::now();
|
||||
Carbon::setTestNow($now);
|
||||
it('knows if refresh token expires at is greater than or equal to now it is expired', function () {
|
||||
Carbon::setTestNow(Carbon::now());
|
||||
|
||||
$this->token->refresh_token_expires_at = Carbon::now();
|
||||
|
||||
$this->assertFalse($this->token->getHasValidRefreshTokenAttribute());
|
||||
expect($this->token->getHasValidRefreshTokenAttribute())->toBeFalse();
|
||||
|
||||
$this->token->refresh_token_expires_at->subSecond();
|
||||
expect($this->token->getHasValidRefreshTokenAttribute())->toBeFalse();
|
||||
});
|
||||
|
||||
$this->assertFalse($this->token->getHasValidRefreshTokenAttribute());
|
||||
}
|
||||
it('stores the oauth token parts in expected properties', function () {
|
||||
$oauth_token_mock = Mockery::mock(QuickBooksOnline\API\Core\OAuth\OAuth2\OAuth2AccessToken::class);
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_stores_the_oauth_token_parts_in_expected_properties()
|
||||
{
|
||||
$oauth_token_mock = Mockery::mock(OAuth2AccessToken::class);
|
||||
$oauth_token_mock->shouldReceive('getAccessToken')->once()->withNoArgs()->andReturn('access_token');
|
||||
$oauth_token_mock->shouldReceive('getAccessTokenExpiresAt')->once()->withNoArgs()->andReturn('2025-06-15 12:00:00');
|
||||
$oauth_token_mock->shouldReceive('getRealmID')->once()->withNoArgs()->andReturn('realm_id');
|
||||
$oauth_token_mock->shouldReceive('getRefreshToken')->once()->withNoArgs()->andReturn('refresh_token');
|
||||
$oauth_token_mock->shouldReceive('getRefreshTokenExpiresAt')->once()->withNoArgs()->andReturn('2025-09-24 12:00:00');
|
||||
|
||||
$oauth_token_mock
|
||||
->shouldReceive('getAccessToken')
|
||||
->once()
|
||||
->withNoArgs()
|
||||
->andReturn('access_token');
|
||||
$result = $this->token->parseOauthToken($oauth_token_mock);
|
||||
|
||||
$oauth_token_mock
|
||||
->shouldReceive('getAccessTokenExpiresAt')
|
||||
->once()
|
||||
->withNoArgs()
|
||||
->andReturn('now');
|
||||
expect($result)->toBeInstanceOf(Token::class)
|
||||
->and($this->token->access_token)->toBe('access_token')
|
||||
->and($this->token->realm_id)->toBe('realm_id')
|
||||
->and($this->token->refresh_token)->toBe('refresh_token')
|
||||
->and($this->token->access_token_expires_at)->toBeInstanceOf(Carbon::class)
|
||||
->and($this->token->refresh_token_expires_at)->toBeInstanceOf(Carbon::class);
|
||||
});
|
||||
|
||||
$oauth_token_mock
|
||||
->shouldReceive('getRealmID')
|
||||
->once()
|
||||
->withNoArgs()
|
||||
->andReturn('realm_id');
|
||||
|
||||
$oauth_token_mock
|
||||
->shouldReceive('getRefreshToken')
|
||||
->once()
|
||||
->withNoArgs()
|
||||
->andReturn('refresh_token');
|
||||
|
||||
$oauth_token_mock
|
||||
->shouldReceive('getRefreshTokenExpiresAt')
|
||||
->once()
|
||||
->withNoArgs()
|
||||
->andReturn('now');
|
||||
|
||||
$this->token->parseOauthToken($oauth_token_mock);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_allows_itself_to_be_deleted_and_returns_new_token()
|
||||
{
|
||||
it('allows itself to be deleted and returns new token', function () {
|
||||
$this->token->user = Mockery::mock(User::class);
|
||||
|
||||
$has_one_mock = Mockery::mock(HasOne::class);
|
||||
$token_mock = Mockery::mock(Token::class);
|
||||
|
||||
$has_one_mock
|
||||
->shouldReceive('make')
|
||||
->once()
|
||||
->withNoArgs()
|
||||
->andReturn($token_mock);
|
||||
$has_one_mock->shouldReceive('make')->once()->withNoArgs()->andReturn($token_mock);
|
||||
|
||||
$this->token->user
|
||||
->shouldReceive('quickBooksToken')
|
||||
@@ -188,25 +101,9 @@ public function it_allows_itself_to_be_deleted_and_returns_new_token()
|
||||
|
||||
$this->token->user->id = 1;
|
||||
|
||||
$this->assertEquals($token_mock, $this->token->remove());
|
||||
}
|
||||
expect($this->token->remove())->toBe($token_mock);
|
||||
});
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_get_related_user_model_from_configuration()
|
||||
{
|
||||
$this->assertInstanceOf(User::class, $this->token->user()->getModel());
|
||||
}
|
||||
}
|
||||
|
||||
function config($key)
|
||||
{
|
||||
return [
|
||||
'keys' => [
|
||||
'foreign' => 'user_id',
|
||||
'owner' => 'id',
|
||||
],
|
||||
'model' => User::class,
|
||||
];
|
||||
}
|
||||
it('gets related user model from configuration', function () {
|
||||
expect($this->token->user()->getModel())->toBeInstanceOf(User::class);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user