Skip to content

Commit

Permalink
Added tests for Google API
Browse files Browse the repository at this point in the history
  • Loading branch information
Finesse committed Jun 1, 2018
1 parent 23dafdc commit 977dfe5
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 5 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"require-dev": {
"phpunit/phpunit": "^4.8",
"google/apiclient": "^2.0",
"symfony/console": "^3.4"
"symfony/console": "^3.4",
"mikey179/vfsStream": "^1.6"
},
"suggest": {
"google/apiclient": "To use the Google API helper (version ^2.0 is required)",
Expand Down
9 changes: 5 additions & 4 deletions src/GoogleAPI/GoogleAPIClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public function authenticate(
$this->client->setAuthConfig($this->loadCredentialJSON($clientSecretFile));

// Getting an access token
if ($accessTokenFile !== null && !$forceAuthenticate && file_exists($accessTokenFile)) {
if ($accessTokenFile !== null && !$forceAuthenticate && is_file($accessTokenFile)) {
$this->logger->info('Getting the last Google API access token from the `'.$accessTokenFile.'` file');
$this->client->setAccessToken($this->loadCredentialJSON($accessTokenFile));
} else {
Expand Down Expand Up @@ -125,7 +125,7 @@ public function authenticate(
// Refreshing the access token if required
if ($this->client->isAccessTokenExpired()) {
$this->logger->info('The access token is expired; refreshing the token');
$accessToken = $this->client->fetchAccessTokenWithRefreshToken($this->client->getRefreshToken());
$accessToken = $this->client->fetchAccessTokenWithRefreshToken();
if (isset($accessToken['error_description'])) {
throw new \RuntimeException('Google has declined refreshing the token: '.$accessToken['error_description']);
}
Expand All @@ -147,9 +147,10 @@ public function authenticate(
public function __get($name)
{
if (substr($name, -7) === 'Service') {
$serviceClass = 'Google_Service_'.ucfirst(substr($name, 0, -7));
$service = ucfirst(substr($name, 0, -7));
$serviceClass = 'Google_Service_'.$service;
if (!class_exists($serviceClass)) {
throw new \LogicException('The '.$name.' Google service doesn\'t exist');
throw new \LogicException('The '.$service.' Google service doesn\'t exist');
}

return new $serviceClass($this->client);
Expand Down
171 changes: 171 additions & 0 deletions tests/GoogleAPI/GoogleAPIClientTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
<?php

namespace Forikal\Library\Tests\GoogleAPI;

use Forikal\Library\GoogleAPI\GoogleAPIClient;
use org\bovigo\vfs\vfsStream;
use org\bovigo\vfs\vfsStreamDirectory;
use org\bovigo\vfs\vfsStreamWrapper;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;

class GoogleAPIClientTest extends TestCase
{
/**
* @var \Google_Client|\PHPUnit_Framework_MockObject_MockObject Google API client mock. The original class methods
* are never called
*/
protected $googleClientMock;

/**
* @var LoggerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $loggerMock;

protected function setUp()
{
parent::setUp();

$this->googleClientMock = $this
->getMockBuilder('Google_Client')
->disableOriginalConstructor()
->disableProxyingToOriginalMethods()
->getMock();
$this->googleClientMock->method('setApplicationName')->with('Forikal Tools');
$this->googleClientMock->method('setAccessType')->with('offline');

$this->loggerMock = $this->getMock(LoggerInterface::class);

// Taken from https://phpunit.de/manual/4.8/en/test-doubles.html#test-doubles.mocking-the-filesystem
vfsStreamWrapper::register();
vfsStreamWrapper::setRoot(new vfsStreamDirectory('google'));
}

public function testAuthenticateWithoutSecretFile()
{
$this->googleClientMock->method('setScopes')->with([]);

$secretPath = vfsStream::url('google/secret.json');
$this->setExpectedException('RuntimeException', 'The `'.$secretPath.'` file doesn\'t exist');
$client = new GoogleAPIClient($this->googleClientMock);
$client->authenticate($secretPath, null, [], function () {});
}

public function testAuthenticateWithNotExistingAccessToken()
{
$secretPath = vfsStream::url('google/secret.json');
$tokenPath = vfsStream::url('google/token.json');

file_put_contents($secretPath, '{"secret": "qwerty"}');

$this->googleClientMock->method('setScopes')->with([\Google_Service_Drive::DRIVE_READONLY]);
$this->googleClientMock->method('setAuthConfig')->with(['secret' => 'qwerty']);
$this->googleClientMock->method('createAuthUrl')->willReturn('https://google.com/auth');
$this->googleClientMock->method('fetchAccessTokenWithAuthCode')->with('foo1')->willReturn(['token' => '9892ll']);
$this->googleClientMock->method('isAccessTokenExpired')->willReturn(false);

$this->loggerMock->method('info')->withConsecutive(
[$this->equalTo('Getting the Google API client secret from the `'.$secretPath.'` file')],
[$this->equalTo('Sending the authentication code to Google')],
[$this->equalTo('Saving the access token to the `'.$tokenPath.'` file, so subsequent executions will not prompt for authorization')],
[$this->equalTo('The Google authentication is completed')]
);
$this->loggerMock->method('notice')->with('Authenticated successfully');

$client = new GoogleAPIClient($this->googleClientMock, $this->loggerMock);
$client->authenticate(
$secretPath,
$tokenPath,
[\Google_Service_Drive::DRIVE_READONLY],
function ($url) {
$this->assertEquals('https://google.com/auth', $url);
return 'foo1';
}
);
$this->assertFileExists($tokenPath);
$this->assertEquals(['token' => '9892ll'], json_decode(file_get_contents($tokenPath), true), 'Created JSON token file is incorrect');
}

public function testAuthenticateWithExistingAccessTokenAndRefresh()
{
$secretPath = vfsStream::url('google/secret.json');
$tokenPath = vfsStream::url('google/token.json');

file_put_contents($secretPath, '{"secret": "qwerty"}');
file_put_contents($tokenPath, '{"token": ")*F)SD*&"}');

$this->googleClientMock->method('setScopes')->with([]);
$this->googleClientMock->method('setAuthConfig')->with(['secret' => 'qwerty']);
$this->googleClientMock->method('setAccessToken')->with(['token' => ')*F)SD*&']);
$this->googleClientMock->method('isAccessTokenExpired')->willReturn(true);
$this->googleClientMock->method('fetchAccessTokenWithRefreshToken')->willReturn(['token' => '15$sDAF']);

$this->loggerMock->method('info')->withConsecutive(
[$this->equalTo('Getting the Google API client secret from the `'.$secretPath.'` file')],
[$this->equalTo('Getting the last Google API access token from the `'.$tokenPath.'` file')],
[$this->equalTo('The access token is expired; refreshing the token')],
[$this->equalTo('Saving the refreshed access token to the `'.$tokenPath.'` file')],
[$this->equalTo('The Google authentication is completed')]
);

$client = new GoogleAPIClient($this->googleClientMock, $this->loggerMock);
$client->authenticate(
$secretPath,
$tokenPath,
[],
function () {
$this->fail('The auth code getter function must not be called');
}
);
$this->assertEquals(['token' => '15$sDAF'], json_decode(file_get_contents($tokenPath), true), 'Created JSON token file is incorrect');
}

public function testAuthenticationError()
{
$secretPath = vfsStream::url('google/secret.json');
$tokenPath = vfsStream::url('google/token.json');

file_put_contents($secretPath, '{"secret": "qwerty"}');

$this->googleClientMock->method('setScopes')->with([]);
$this->googleClientMock->method('setAuthConfig')->with(['secret' => 'qwerty']);
$this->googleClientMock->method('createAuthUrl')->willReturn('https://google.com/auth');
$this->googleClientMock->method('fetchAccessTokenWithAuthCode')->with('foo1')->willReturn(['error' => 'test', 'error_description' => 'This is a test']);

$this->loggerMock->method('info')->withConsecutive(
[$this->equalTo('Getting the Google API client secret from the `'.$secretPath.'` file')],
[$this->equalTo('Sending the authentication code to Google')]
);

$this->setExpectedException('RuntimeException', 'Google has declined the auth code: This is a test');
$client = new GoogleAPIClient($this->googleClientMock, $this->loggerMock);
$client->authenticate($secretPath, $tokenPath, [], function () { return 'foo1'; });
$this->assertFileNotExists($tokenPath);
}

/**
* @dataProvider getServiceProvider
*/
public function testGetService($property, $shouldExist, $expectedClass = null)
{
$client = new GoogleAPIClient($this->googleClientMock);

if ($shouldExist) {
$this->assertInstanceOf($expectedClass, $client->$property);
} else {
$this->setExpectedException('LogicException');
$client->$property;
}
}

public function getServiceProvider()
{
return [
['driveService', true, 'Google_Service_Drive'],
['sheetsService', true, 'Google_Service_Sheets'],
['slidesService', true, 'Google_Service_Slides'],
['fooService', false],
['bar', false]
];
}
}
22 changes: 22 additions & 0 deletions tests/GoogleAPI/GoogleAPIFactoryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Forikal\Library\Tests\GoogleAPI;

use Forikal\Library\GoogleAPI\GoogleAPIFactory;
use PHPUnit\Framework\TestCase;
use Psr\Log\NullLogger;

class GoogleAPIFactoryTest extends TestCase
{
public function testMake()
{
$factory = new GoogleAPIFactory();

$client = $factory->make();
$this->assertAttributeInstanceOf(NullLogger::class, 'logger', $client);

$logger = new NullLogger();
$client = $factory->make($logger);
$this->assertAttributeSame($logger, 'logger', $client);
}
}

0 comments on commit 977dfe5

Please sign in to comment.