Files
laravel-quickbooks/tests/ClientTest.php
Joel Male 32266f6dd5 fix: revert JSON serialization workaround, upgrade SDK to 6.3.1 instead
v2.0.1's useJson() workaround was wrong: the JSON request serializer
silently drops nested single-object fields (e.g. CustomerRef, which
QuickBooks requires), and the JSON response deserializer never returns
a typed entity, so $result->Id is always null downstream. That breaks
every dsCall() site, worse than the bug it was meant to fix.

The actual root cause was that quickbooks/v3-php-sdk 5.4.7 predates
QuickBooks Online's new invoice AllowOnlinePayPalPayment field, so its
XML deserializer throws on responses that include it. SDK 6.3.1
declares the field, so upgrading resolves the original error while
keeping the fully-supported XML path (and closes out the SDK bump
Dependabot already had queued in #3).

Verified: deserializing a response containing AllowOnlinePayPalPayment
throws RuntimeException on 5.4.7 and returns a correctly typed
IPPInvoice (with Id populated) on 6.3.1.
2026-07-27 07:15:59 +03:00

199 lines
5.4 KiB
PHP

<?php
use QuickBooksOnline\API\DataService\DataService;
use QuickBooksOnline\API\Exception\ServiceException;
use Wearepixel\QuickBooks\Client;
use Wearepixel\QuickBooks\Token;
beforeEach(function () {
$this->configs = [
'data_service' => [
'auth_mode' => 'oauth2',
'base_url' => 'Development',
'client_id' => 'QUICKBOOKS_CLIENT_ID',
'client_secret' => 'QUICKBOOKS_CLIENT_SECRET',
'scope' => 'com.intuit.quickbooks.accounting',
],
];
$this->token_mock = Mockery::mock(Token::class);
$this->client = new Client($this->configs, $this->token_mock);
});
it('can be constructed', function () {
expect($this->client)->toBeInstanceOf(Client::class);
});
it('returns a data service configured to request oauth token when token is empty', function () {
$this->token_mock
->shouldReceive('getAttribute')
->twice()
->with('hasValidAccessToken')
->andReturnFalse();
$this->token_mock
->shouldReceive('getAttribute')
->once()
->with('hasValidRefreshToken')
->andReturnFalse();
expect(
$this->client
->getDataService()
->getServiceContext()
->IppConfiguration->Security->isAccessTokenSet(),
)->toBeFalse();
});
it('caches the data service once it is made', function () {
$this->token_mock
->shouldReceive('getAttribute')
->twice()
->with('hasValidAccessToken')
->andReturnFalse();
$this->token_mock
->shouldReceive('getAttribute')
->once()
->with('hasValidRefreshToken')
->andReturnFalse();
expect($this->client->getDataService())->toBeInstanceOf(DataService::class);
});
it('returns a data service with oauth token when valid access token exists', function () {
$this->token_mock
->shouldReceive('getAttribute')
->twice()
->with('hasValidAccessToken')
->andReturnTrue();
$this->token_mock
->shouldReceive('getAttribute')
->once()
->with('access_token')
->andReturn('access_token');
$this->token_mock
->shouldReceive('getAttribute')
->once()
->with('realm_id')
->andReturn('realm_id');
$this->token_mock
->shouldReceive('getAttribute')
->once()
->with('refresh_token')
->andReturn('refresh_token');
expect(
$this->client
->getDataService()
->getServiceContext()
->IppConfiguration->Security->isAccessTokenSet(),
)->toBeTrue();
});
it('returns a data service with refreshed token when access token expired but refresh token valid', function () {
$this->token_mock
->shouldReceive('getAttribute')
->twice()
->with('hasValidAccessToken')
->andReturnFalse();
$this->token_mock
->shouldReceive('getAttribute')
->once()
->with('hasValidRefreshToken')
->andReturnTrue();
$this->token_mock
->shouldReceive('getAttribute')
->never()
->with('access_token');
$this->token_mock
->shouldReceive('getAttribute')
->once()
->with('realm_id')
->andReturn('realm_id');
$this->token_mock
->shouldReceive('getAttribute')
->once()
->with('refresh_token')
->andReturn('refresh_token');
$this->client->getDataService();
})->throws(ServiceException::class);
it('returns a report service using the data service')
->skip('Once we figure out how to test around the static DataService::Configure');
it('has logging off by default')
->skip('Have to figure out how to test this with the new code in 5.x');
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');
it('returns self after deleting token', function () {
$this->token_mock
->shouldReceive('remove')
->once()
->withNoArgs()
->andReturnSelf();
expect($this->client->deleteToken())->toBeInstanceOf(Client::class);
});
it('clears cached data service when token is changed', function () {
$this->token_mock
->shouldReceive('getAttribute')
->with('hasValidAccessToken')
->andReturnFalse();
$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();
});