From 97c04ec693e8100901fe8bcb3b6c470e82725dcf Mon Sep 17 00:00:00 2001 From: Joel Male Date: Wed, 18 Feb 2026 10:38:20 +1000 Subject: [PATCH] feat: migrate test suite from PHPUnit to Pest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace phpunit/phpunit with pestphp/pest ^3.0 - Drop scrutinizer/ocular (incompatible with Pest's symfony/console) - Add tests/Pest.php with MockeryPHPUnitIntegration trait binding - Extract namespace-level function stubs (config, route, dir) to tests/Overrides.php, loaded via autoload-dev files - Convert all 5 test classes to Pest closure-based syntax: setUp() → beforeEach(), assertions → expect(), skip() for skipped tests - Delete tests/TestCase.php (logic moved to Pest.php) - Stubs (TokenStub, User) unchanged --- composer.json | 13 +- tests/ClientTest.php | 371 +++++++--------------- tests/HasQuickBooksTokenTest.php | 40 +-- tests/Http/Controllers/ControllerTest.php | 336 ++++++++------------ tests/Http/Middleware/FilterTest.php | 167 ++++------ tests/Overrides.php | 26 ++ tests/Pest.php | 5 + tests/TestCase.php | 75 ----- tests/TokenTest.php | 223 ++++--------- 9 files changed, 426 insertions(+), 830 deletions(-) create mode 100644 tests/Overrides.php create mode 100644 tests/Pest.php delete mode 100644 tests/TestCase.php diff --git a/composer.json b/composer.json index bcb6bff..7db4550 100644 --- a/composer.json +++ b/composer.json @@ -27,9 +27,8 @@ "require-dev": { "laravel/pint": "^1.2", "mockery/mockery": "^1.6", - "phpunit/phpunit": "^10.5|^11.0", - "psy/psysh": "^0.12", - "scrutinizer/ocular": "^1.9" + "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 diff --git a/tests/ClientTest.php b/tests/ClientTest.php index aa7f1a4..6ff3083 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -1,282 +1,147 @@ configs = [ + 'data_service' => [ + 'auth_mode' => 'oauth2', + 'base_url' => 'Development', + 'client_id' => 'QUICKBOOKS_CLIENT_ID', + 'client_secret' => 'QUICKBOOKS_CLIENT_SECRET', + 'scope' => 'com.intuit.quickbooks.accounting', + ], + ]; - /** - * @var array - */ - protected $configs = []; + $this->token_mock = Mockery::mock(Token::class); + $this->client = new Client($this->configs, $this->token_mock); +}); - /** - * @var Mock - */ - protected $token_mock; +it('can be constructed', function () { + expect($this->client)->toBeInstanceOf(Client::class); +}); - protected function setUp(): void - { - $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', - ], - ]; +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 = Mockery::mock(Token::class); + $this->token_mock + ->shouldReceive('getAttribute') + ->once() + ->with('hasValidRefreshToken') + ->andReturnFalse(); - $this->client = $this->makeClient(); - } + expect( + $this->client + ->getDataService() + ->getServiceContext() + ->IppConfiguration->Security->isAccessTokenSet(), + )->toBeFalse(); +}); - private function makeClient($configs = null) - { - if (! is_null($configs)) { - return new Client($configs, $this->token_mock); - } +it('caches the data service once it is made', function () { + $this->token_mock + ->shouldReceive('getAttribute') + ->twice() + ->with('hasValidAccessToken') + ->andReturnFalse(); - return new Client($this->configs, $this->token_mock); - } + $this->token_mock + ->shouldReceive('getAttribute') + ->once() + ->with('hasValidRefreshToken') + ->andReturnFalse(); - #[Test] - public function it_can_be_constructed() - { - $this->assertInstanceOf(Client::class, $this->client); - } + expect($this->client->getDataService())->toBeInstanceOf(DataService::class); +}); - #[Test] - public function it_returns_a_data_service_configured_to_request_oauth_token_when_token_is_empty() - { - $this->token_mock - ->shouldReceive('getAttribute') - ->twice() - ->with('hasValidAccessToken') - ->andReturnFalse(); +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('hasValidRefreshToken') - ->andReturnFalse(); + $this->token_mock + ->shouldReceive('getAttribute') + ->once() + ->with('access_token') + ->andReturn('access_token'); - $this->assertFalse( - $this->client - ->getDataService() - ->getServiceContext() - ->IppConfiguration->Security->isAccessTokenSet(), - ); - } + $this->token_mock + ->shouldReceive('getAttribute') + ->once() + ->with('realm_id') + ->andReturn('realm_id'); - #[Test] - public function it_caches_the_data_service_once_it_is_made() - { - $this->token_mock - ->shouldReceive('getAttribute') - ->twice() - ->with('hasValidAccessToken') - ->andReturnFalse(); + $this->token_mock + ->shouldReceive('getAttribute') + ->once() + ->with('refresh_token') + ->andReturn('refresh_token'); - $this->token_mock - ->shouldReceive('getAttribute') - ->once() - ->with('hasValidRefreshToken') - ->andReturnFalse(); + expect( + $this->client + ->getDataService() + ->getServiceContext() + ->IppConfiguration->Security->isAccessTokenSet(), + )->toBeTrue(); +}); - // 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())); - } +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(); - #[Test] - public function it_returns_a_data_service_with_oauth_token_when_valid_access_token_exist() - { - $this->token_mock - ->shouldReceive('getAttribute') - ->twice() - ->with('hasValidAccessToken') - ->andReturnTrue(); + $this->token_mock + ->shouldReceive('getAttribute') + ->once() + ->with('hasValidRefreshToken') + ->andReturnTrue(); - $this->token_mock - ->shouldReceive('getAttribute') - ->once() - ->with('access_token') - ->andReturn('access_token'); + $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('realm_id') + ->andReturn('realm_id'); - $this->token_mock - ->shouldReceive('getAttribute') - ->once() - ->with('refresh_token') - ->andReturn('refresh_token'); + $this->token_mock + ->shouldReceive('getAttribute') + ->once() + ->with('refresh_token') + ->andReturn('refresh_token'); - $this->assertTrue( - $this->client - ->getDataService() - ->getServiceContext() - ->IppConfiguration->Security->isAccessTokenSet(), - ); - } + $this->client->getDataService(); +})->throws(ServiceException::class); - #[Test] - public function it_returns_a_data_service_with_refreshed_token_when_access_token_expired_but_refresh_token_valid() - { - $this->expectException(ServiceException::class); +it('returns a report service using the data service') + ->skip('Once we figure out how to test around the static DataService::Configure'); - $this->token_mock - ->shouldReceive('getAttribute') - ->twice() - ->with('hasValidAccessToken') - ->andReturnFalse(); +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('hasValidRefreshToken') - ->andReturnTrue(); +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') - ->never() - ->with('access_token'); +it('returns self after deleting token', function () { + $this->token_mock + ->shouldReceive('remove') + ->once() + ->withNoArgs() + ->andReturnSelf(); - $this->token_mock - ->shouldReceive('getAttribute') - ->once() - ->with('realm_id') - ->andReturn('realm_id'); - - $this->token_mock - ->shouldReceive('getAttribute') - ->once() - ->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(), - ); - } - - #[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', - ); - } - - #[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'); - - $this->token_mock - ->shouldReceive('getAttribute') - ->once() - ->with('hasValidAccessToken') - ->andReturnFalse(); - - $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() - { - $this->token_mock - ->shouldReceive('remove') - ->once() - ->withNoArgs() - ->andReturnSelf(); - - $this->assertInstanceOf(Client::class, $this->client->deleteToken()); - } -} - -function dir($path) -{ - return $path === '/some/valid/path'; -} - -function route($name) -{ - return $name; -} + expect($this->client->deleteToken())->toBeInstanceOf(Client::class); +}); diff --git a/tests/HasQuickBooksTokenTest.php b/tests/HasQuickBooksTokenTest.php index fcc0fb3..ef722fe 100644 --- a/tests/HasQuickBooksTokenTest.php +++ b/tests/HasQuickBooksTokenTest.php @@ -1,36 +1,16 @@ 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_has_one_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); +}); diff --git a/tests/Http/Controllers/ControllerTest.php b/tests/Http/Controllers/ControllerTest.php index 95b57f0..f74b5a6 100644 --- a/tests/Http/Controllers/ControllerTest.php +++ b/tests/Http/Controllers/ControllerTest.php @@ -1,7 +1,5 @@ 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); - /** - * @var Mock - */ - protected $data_service_mock; + $this->controller = new Controller; +}); - /** - * @var Mock - */ - protected $quickbooks_mock; +it('can be constructed', function () { + expect($this->controller)->toBeInstanceOf(Controller::class); +}); - /** - * @var Mock - */ - protected $redirector_mock; +it('shows view to disconnect if account linked', function () { + $this->data_service_mock + ->shouldReceive('getCompanyInfo') + ->once() + ->withNoArgs() + ->andReturn(['name' => 'Company']); - /** - * @var Mock - */ - protected $request_mock; + $this->quickbooks_mock + ->shouldReceive('hasValidRefreshToken') + ->once() + ->withNoArgs() + ->andReturnTrue(); - /** - * @var Mock - */ - protected $session_mock; + $this->quickbooks_mock + ->shouldReceive('getDataService') + ->once() + ->withNoArgs() + ->andReturn($this->data_service_mock); - /** - * @var Mock - */ - protected $url_generator_mock; + $this->view_factory_mock + ->shouldReceive('make') + ->once() + ->with('quickbooks::disconnect') + ->andReturn($this->view_mock); - /** - * @var Mock - */ - protected $view_factory_mock; + $this->view_mock + ->shouldReceive('with') + ->once() + ->withArgs(['company', ['name' => 'Company']]) + ->andReturnSelf(); - /** - * @var Mock - */ - protected $view_mock; + $this->controller->connect($this->quickbooks_mock, $this->view_factory_mock); +}); - protected function setUp(): void - { - $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); +it('shows view to connect if account not linked', function () { + $this->quickbooks_mock + ->shouldReceive('hasValidRefreshToken') + ->once() + ->withNoArgs() + ->andReturnFalse(); - $this->controller = new Controller; - } + $this->quickbooks_mock + ->shouldReceive('authorizationUri') + ->once() + ->withNoArgs() + ->andReturn('http://uri'); - #[Test] - public function it_can_be_constructed() - { - $this->assertInstanceOf(Controller::class, $this->controller); - } + $this->view_factory_mock + ->shouldReceive('make') + ->once() + ->with('quickbooks::connect') + ->andReturn($this->view_mock); - #[Test] - public function it_shows_view_to_connect_if_account_not_linked() - { - $this->data_service_mock - ->shouldReceive('getCompanyInfo') - ->once() - ->withNoArgs() - ->andReturn([ - 'name' => 'Company', - ]); + $this->view_mock + ->shouldReceive('with') + ->once() + ->withArgs(['authorization_uri', 'http://uri']) + ->andReturnSelf(); - $this->quickbooks_mock - ->shouldReceive('hasValidRefreshToken') - ->once() - ->withNoArgs() - ->andReturnTrue(); + $this->controller->connect($this->quickbooks_mock, $this->view_factory_mock); +}); - $this->quickbooks_mock - ->shouldReceive('getDataService') - ->once() - ->withNoArgs() - ->andReturn($this->data_service_mock); +it('disconnects from quickbooks when requested', function () { + $this->request_mock + ->shouldReceive('session') + ->once() + ->andReturn($this->session_mock); - $this->view_factory_mock - ->shouldReceive('make') - ->once() - ->with('quickbooks::disconnect') - ->andReturn($this->view_mock); + $this->session_mock + ->shouldReceive('flash') + ->once() + ->withAnyArgs(); - $this->view_mock - ->shouldReceive('with') - ->once() - ->withArgs([ - 'company', - [ - 'name' => 'Company', - ], - ]) - ->andReturnSelf(); + $this->redirector_mock + ->shouldReceive('back') + ->once() + ->andReturn(new RedirectResponse('/test', 302)); - $this->controller->connect($this->quickbooks_mock, $this->view_factory_mock); - } + $this->quickbooks_mock + ->shouldReceive('deleteToken') + ->once() + ->withNoArgs(); - #[Test] - public function it_shows_view_to_disconnect_if_account_linked() - { - $this->quickbooks_mock - ->shouldReceive('hasValidRefreshToken') - ->once() - ->withNoArgs() - ->andReturnFalse(); + $result = $this->controller->disconnect( + $this->redirector_mock, + $this->request_mock, + $this->quickbooks_mock, + ); - $this->quickbooks_mock - ->shouldReceive('authorizationUri') - ->once() - ->withNoArgs() - ->andReturn('http://uri'); + expect($result)->toBeInstanceOf(RedirectResponse::class); +}); - $this->view_factory_mock - ->shouldReceive('make') - ->once() - ->with('quickbooks::connect') - ->andReturn($this->view_mock); +it('finishes connecting to quickbooks when given a valid token', function () { + $realmId = random_int(1, 9999); - $this->view_mock - ->shouldReceive('with') - ->once() - ->withArgs(['authorization_uri', 'http://uri']) - ->andReturnSelf(); + $this->quickbooks_mock + ->shouldReceive('exchangeCodeForToken') + ->once() + ->withArgs(['code', $realmId]); - $this->controller->connect($this->quickbooks_mock, $this->view_factory_mock); - } + $this->request_mock + ->shouldReceive('get') + ->once() + ->withArgs(['code']) + ->andReturn('code'); - #[Test] - public function it_disconnects_from_quickbooks_when_requested() - { - $this->request_mock - ->shouldReceive('session') - ->once() - ->andReturn($this->session_mock); + $this->request_mock + ->shouldReceive('get') + ->once() + ->withArgs(['realmId']) + ->andReturn($realmId); - $this->session_mock - ->shouldReceive('flash') - ->once() - ->withAnyArgs(); + $this->request_mock + ->shouldReceive('session') + ->once() + ->andReturn($this->session_mock); - $this->redirector_mock - ->shouldReceive('back') - ->once() - ->andReturn(new RedirectResponse('/test', 302)); + $this->session_mock + ->shouldReceive('flash') + ->once() + ->withAnyArgs(); - $this->quickbooks_mock - ->shouldReceive('deleteToken') - ->once() - ->withNoArgs(); + $this->redirector_mock + ->shouldReceive('intended') + ->once() + ->withAnyArgs() + ->andReturn(new RedirectResponse('/test', 302)); - $result = $this->controller->disconnect( - $this->redirector_mock, - $this->request_mock, - $this->quickbooks_mock, - ); + $this->url_generator_mock + ->shouldReceive('route') + ->withArgs(['quickbooks.connect']) + ->once(); - $this->assertInstanceOf(RedirectResponse::class, $result); - } + $result = $this->controller->token( + $this->redirector_mock, + $this->request_mock, + $this->quickbooks_mock, + $this->url_generator_mock, + ); - #[Test] - public function it_finishes_connecting_to_quickbooks_when_given_a_valid_token_by_quickbooks() - { - $realmId = random_int(1, 9999); - - $this->quickbooks_mock - ->shouldReceive('exchangeCodeForToken') - ->once() - ->withArgs(['code', $realmId]); - - $this->request_mock - ->shouldReceive('get') - ->once() - ->withArgs(['code']) - ->andReturn('code'); - - $this->request_mock - ->shouldReceive('get') - ->once() - ->withArgs(['realmId']) - ->andReturn($realmId); - - $this->request_mock - ->shouldReceive('session') - ->once() - ->andReturn($this->session_mock); - - $this->session_mock - ->shouldReceive('flash') - ->once() - ->withAnyArgs(); - - $this->redirector_mock - ->shouldReceive('intended') - ->once() - ->withAnyArgs() - ->andReturn(new RedirectResponse('/test', 302)); - - $this->url_generator_mock - ->shouldReceive('route') - ->withArgs(['quickbooks.connect']) - ->once(); - - $result = $this->controller->token( - $this->redirector_mock, - $this->request_mock, - $this->quickbooks_mock, - $this->url_generator_mock, - ); - - $this->assertInstanceOf(RedirectResponse::class, $result); - } -} + expect($result)->toBeInstanceOf(RedirectResponse::class); +}); diff --git a/tests/Http/Middleware/FilterTest.php b/tests/Http/Middleware/FilterTest.php index 1843bfb..0380c20 100644 --- a/tests/Http/Middleware/FilterTest.php +++ b/tests/Http/Middleware/FilterTest.php @@ -1,128 +1,79 @@ 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(Session::class); + $this->url_generator_mock = Mockery::mock(UrlGenerator::class); - /** - * @var Mock - */ - protected $quickbooks_mock; + $this->filter = new Filter( + $this->quickbooks_mock, + $this->redirector_mock, + $this->session_mock, + $this->url_generator_mock, + ); +}); - /** - * @var Mock - */ - protected $redirector_mock; +it('can be constructed', function () { + expect($this->filter)->toBeInstanceOf(Filter::class); +}); - /** - * @var Mock - */ - protected $request_mock; +it('passes the request to the next middleware if account linked to quickbooks', function () { + $next_middleware = function ($request) { + expect($request)->toBe($this->request_mock); + }; - /** - * @var Mock - */ - protected $session_mock; + $this->quickbooks_mock + ->shouldReceive('hasValidRefreshToken') + ->once() + ->withNoArgs() + ->andReturnTrue(); - /** - * @var Mock - */ - protected $url_generator_mock; + $this->filter->handle($this->request_mock, $next_middleware); +}); - protected function setUp(): void - { - $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(Session::class); - $this->url_generator_mock = Mockery::mock(UrlGenerator::class); +it('redirects to quickbooks connect route after setting intended session if account not linked', function () { + $this->request_mock + ->shouldReceive('path') + ->once() + ->withNoArgs() + ->andReturn('path'); - $this->filter = new Filter( - $this->quickbooks_mock, - $this->redirector_mock, - $this->session_mock, - $this->url_generator_mock, - ); - } + $this->url_generator_mock + ->shouldReceive('to') + ->once() + ->with('path') + ->andReturn('http://to/path'); - #[Test] - public function it_can_be_constructed() - { - $this->assertInstanceOf(Filter::class, $this->filter); - } + $this->session_mock + ->shouldReceive('put') + ->once() + ->withArgs(['url.intended', 'http://to/path']) + ->andReturnNull(); - #[Test] - public function it_just_passes_the_request_to_the_next_middleware_if_account_linked_to_quickbooks() - { - $next_middleware = function ($request) { - $this->assertEquals($this->request_mock, $request); - }; + $this->redirector_mock + ->shouldReceive('route') + ->once() + ->with('quickbooks.connect') + ->andReturnSelf(); - $this->quickbooks_mock - ->shouldReceive('hasValidRefreshToken') - ->once() - ->withNoArgs() - ->andReturnTrue(); + $next_middleware = function ($request) { + throw new RuntimeException('Next middleware should not be called'); + }; - $this->filter->handle($this->request_mock, $next_middleware); - } + $this->quickbooks_mock + ->shouldReceive('hasValidRefreshToken') + ->once() + ->withNoArgs() + ->andReturnFalse(); - #[Test] - public function it_redirects_to_quickbooks_connect_route_after_setting_intended_session_if_account_not_linked() - { - $this->request_mock - ->shouldReceive('path') - ->once() - ->withNoArgs() - ->andReturn('path'); - - $this->url_generator_mock - ->shouldReceive('to') - ->once() - ->with('path') - ->andReturn('http://to/path'); - - $this->session_mock - ->shouldReceive('put') - ->once() - ->withArgs(['url.intended', 'http://to/path']) - ->andReturnNull(); - - $this->redirector_mock - ->shouldReceive('route') - ->once() - ->with('quickbooks.connect') - ->andReturnSelf(); - - $next_middleware = function ($request) { - // If this is called, then fail test - $this->assertTrue(false); - }; - - $this->quickbooks_mock - ->shouldReceive('hasValidRefreshToken') - ->once() - ->withNoArgs() - ->andReturnFalse(); - - $this->filter->handle($this->request_mock, $next_middleware); - } -} + $this->filter->handle($this->request_mock, $next_middleware); +}); diff --git a/tests/Overrides.php b/tests/Overrides.php new file mode 100644 index 0000000..f35dd7b --- /dev/null +++ b/tests/Overrides.php @@ -0,0 +1,26 @@ + [ + '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'; +} diff --git a/tests/Pest.php b/tests/Pest.php new file mode 100644 index 0000000..11c45d0 --- /dev/null +++ b/tests/Pest.php @@ -0,0 +1,5 @@ +use(MockeryPHPUnitIntegration::class); diff --git a/tests/TestCase.php b/tests/TestCase.php deleted file mode 100644 index d077b9c..0000000 --- a/tests/TestCase.php +++ /dev/null @@ -1,75 +0,0 @@ - $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)); - } - } -} diff --git a/tests/TokenTest.php b/tests/TokenTest.php index 44878f8..66ca2e6 100644 --- a/tests/TokenTest.php +++ b/tests/TokenTest.php @@ -1,189 +1,102 @@ 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()); - } +it('knows if access token expires at is less than now it is not expired', function () { + Carbon::setTestNow(Carbon::now()); - #[Test] - public function it_know_if_access_token_expires_at_is_less_than_now_it_is_not_expired() - { - $now = Carbon::now(); - Carbon::setTestNow($now); + $this->token->access_token_expires_at = Carbon::now()->addSecond(); - $this->token->access_token_expires_at = Carbon::now()->addSecond(); + expect($this->token->getHasValidAccessTokenAttribute())->toBeTrue(); +}); - $this->assertTrue($this->token->getHasValidAccessTokenAttribute()); - } +it('knows if refresh token expires at is less than now it is not expired', function () { + Carbon::setTestNow(Carbon::now()); - #[Test] - public function it_know_if_refresh_token_expires_at_is_less_than_now_it_is_not_expired() - { - $now = Carbon::now(); - Carbon::setTestNow($now); + $this->token->refresh_token_expires_at = Carbon::now()->addSecond(); - $this->token->refresh_token_expires_at = Carbon::now()->addSecond(); + expect($this->token->getHasValidRefreshTokenAttribute())->toBeTrue(); +}); - $this->assertTrue($this->token->getHasValidRefreshTokenAttribute()); - } +it('knows if access token expires at is greater than or equal to now it is expired', function () { + Carbon::setTestNow(Carbon::now()); - #[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); + $this->token->access_token_expires_at = Carbon::now(); + expect($this->token->getHasValidAccessTokenAttribute())->toBeFalse(); - $this->token->access_token_expires_at = Carbon::now(); + $this->token->access_token_expires_at->subSecond(); + expect($this->token->getHasValidAccessTokenAttribute())->toBeFalse(); +}); - $this->assertFalse($this->token->getHasValidAccessTokenAttribute()); +it('knows if refresh token expires at is greater than or equal to now it is expired', function () { + Carbon::setTestNow(Carbon::now()); - $this->token->access_token_expires_at->subSecond(); + $this->token->refresh_token_expires_at = Carbon::now(); + expect($this->token->getHasValidRefreshTokenAttribute())->toBeFalse(); - $this->assertFalse($this->token->getHasValidAccessTokenAttribute()); - } + $this->token->refresh_token_expires_at->subSecond(); + expect($this->token->getHasValidRefreshTokenAttribute())->toBeFalse(); +}); - #[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('stores the oauth token parts in expected properties', function () { + $oauth_token_mock = Mockery::mock(QuickBooksOnline\API\Core\OAuth\OAuth2\OAuth2AccessToken::class); - $this->token->refresh_token_expires_at = Carbon::now(); + $oauth_token_mock->shouldReceive('getAccessToken')->once()->withNoArgs()->andReturn('access_token'); + $oauth_token_mock->shouldReceive('getAccessTokenExpiresAt')->once()->withNoArgs()->andReturn('now'); + $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->assertFalse($this->token->getHasValidRefreshTokenAttribute()); + $this->token->parseOauthToken($oauth_token_mock); +}); - $this->token->refresh_token_expires_at->subSecond(); +it('allows itself to be deleted and returns new token', function () { + $this->token->user = Mockery::mock(User::class); - $this->assertFalse($this->token->getHasValidRefreshTokenAttribute()); - } + $has_one_mock = Mockery::mock(HasOne::class); + $token_mock = Mockery::mock(Token::class); - #[Test] - public function it_stores_the_oauth_token_parts_in_expected_properties() - { - $oauth_token_mock = Mockery::mock(OAuth2AccessToken::class); + $has_one_mock->shouldReceive('make')->once()->withNoArgs()->andReturn($token_mock); - $oauth_token_mock - ->shouldReceive('getAccessToken') - ->once() - ->withNoArgs() - ->andReturn('access_token'); + $this->token->user + ->shouldReceive('quickBooksToken') + ->once() + ->withNoArgs() + ->andReturn($has_one_mock); - $oauth_token_mock - ->shouldReceive('getAccessTokenExpiresAt') - ->once() - ->withNoArgs() - ->andReturn('now'); + $this->token->user->id = 1; - $oauth_token_mock - ->shouldReceive('getRealmID') - ->once() - ->withNoArgs() - ->andReturn('realm_id'); + expect($this->token->remove())->toBe($token_mock); +}); - $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() - { - $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); - - $this->token->user - ->shouldReceive('quickBooksToken') - ->once() - ->withNoArgs() - ->andReturn($has_one_mock); - - $this->token->user->id = 1; - - $this->assertEquals($token_mock, $this->token->remove()); - } - - #[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); +});