From b4166608acc0f621205b168cdd8ef176dabb515c Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Thu, 10 Mar 2016 22:38:14 -0500 Subject: [PATCH 01/47] json and ignore --- .gitignore | 10 ++++++++++ composer.json | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 .gitignore create mode 100644 composer.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..008dc11 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +vendor/* +composer.phar +coverage +package.xml +*.tgz + +# You should be keeping these in a global gitignore, see for example +# https://help.github.com/articles/ignoring-files#global-gitignore +.DS_Store +.idea \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..5e2bfaf --- /dev/null +++ b/composer.json @@ -0,0 +1,20 @@ +{ + "name": "grnhse/greenhouse-job-board-api-php", + "type": "library", + "description": "A PHP wrapper for Greenhouse's Job Board API", + "keywords": ["api", "greenhouse", "job board"], + "homepage": "https://github.com/grnhse/greenhouse-job-board-api-php", + "authors": [ + { + "name": "Greenhouse" + }, + { + "name": "Tom Phillips" + } + ], + "require": { + "php": ">=5.6", + "guzzlehttp/guzzle": "~6.0", + "phpunit/phpunit": "4.5.*" + } +} \ No newline at end of file From 5348a7edfe81324b0fd59514e170a2554c211ada Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Thu, 10 Mar 2016 22:39:10 -0500 Subject: [PATCH 02/47] Composer lock --- composer.lock | 1192 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1192 insertions(+) create mode 100644 composer.lock diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..694ed09 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1192 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "92cf987c11262c3e71d3e4309cc29054", + "content-hash": "38de0f6c1d4ab1582e4af684357e27a7", + "packages": [ + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14 21:17:01" + }, + { + "name": "guzzlehttp/guzzle", + "version": "6.1.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "c6851d6e48f63b69357cbfa55bca116448140e0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/c6851d6e48f63b69357cbfa55bca116448140e0c", + "reference": "c6851d6e48f63b69357cbfa55bca116448140e0c", + "shasum": "" + }, + "require": { + "guzzlehttp/promises": "~1.0", + "guzzlehttp/psr7": "~1.1", + "php": ">=5.5.0" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "~4.0", + "psr/log": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.1-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2015-11-23 00:47:50" + }, + { + "name": "guzzlehttp/promises", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "bb9024c526b22f3fe6ae55a561fd70653d470aa8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/bb9024c526b22f3fe6ae55a561fd70653d470aa8", + "reference": "bb9024c526b22f3fe6ae55a561fd70653d470aa8", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2016-03-08 01:15:46" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "2e89629ff057ebb49492ba08e6995d3a6a80021b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/2e89629ff057ebb49492ba08e6995d3a6a80021b", + "reference": "2e89629ff057ebb49492ba08e6995d3a6a80021b", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "PSR-7 message implementation", + "keywords": [ + "http", + "message", + "stream", + "uri" + ], + "time": "2016-02-18 21:54:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8", + "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "dflydev/markdown": "~1.0", + "erusev/parsedown": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "phpDocumentor": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "mike.vanriel@naenius.com" + } + ], + "time": "2015-02-03 12:10:50" + }, + { + "name": "phpspec/prophecy", + "version": "v1.6.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3c91bdf81797d725b14cb62906f9a4ce44235972", + "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "~2.0", + "sebastian/comparator": "~1.1", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "phpspec/phpspec": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2016-02-15 07:46:21" + }, + { + "name": "phpunit/php-code-coverage", + "version": "2.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.2", + "phpunit/php-token-stream": "~1.3", + "sebastian/environment": "^1.3.2", + "sebastian/version": "~1.0" + }, + "require-dev": { + "ext-xdebug": ">=2.1.4", + "phpunit/phpunit": "~4" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.2.1", + "ext-xmlwriter": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2015-10-06 15:47:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.3.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/acd690379117b042d1c8af1fafd61bde001bf6bb", + "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "File/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2013-10-10 15:34:57" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21 13:50:34" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.7", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3e82f4e9fc92665fafd9157568e4dcb01d014e5b", + "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2015-06-21 08:01:12" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", + "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2015-09-15 10:49:45" + }, + { + "name": "phpunit/phpunit", + "version": "4.5.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "d6429b0995b24a2d9dfe5587ee3a7071c1161af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d6429b0995b24a2d9dfe5587ee3a7071c1161af4", + "reference": "d6429b0995b24a2d9dfe5587ee3a7071c1161af4", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.3.3", + "phpspec/prophecy": "~1.3,>=1.3.1", + "phpunit/php-code-coverage": "~2.0,>=2.0.11", + "phpunit/php-file-iterator": "~1.3.2", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "~1.0.2", + "phpunit/phpunit-mock-objects": "~2.3", + "sebastian/comparator": "~1.1", + "sebastian/diff": "~1.1", + "sebastian/environment": "~1.2", + "sebastian/exporter": "~1.2", + "sebastian/global-state": "~1.0", + "sebastian/version": "~1.0", + "symfony/yaml": "~2.0" + }, + "suggest": { + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.5.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2015-03-29 09:24:05" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "2.3.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": ">=5.3.3", + "phpunit/php-text-template": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2015-10-02 06:51:40" + }, + { + "name": "psr/http-message", + "version": "1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/85d63699f0dbedb190bbd4b0d2b9dc707ea4c298", + "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2015-05-04 20:22:00" + }, + { + "name": "sebastian/comparator", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22", + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2015-07-26 15:48:44" + }, + { + "name": "sebastian/diff", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2015-12-08 07:14:41" + }, + { + "name": "sebastian/environment", + "version": "1.3.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf", + "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-02-26 18:40:46" + }, + { + "name": "sebastian/exporter", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "7ae5513327cb536431847bcc0c10edba2701064e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/7ae5513327cb536431847bcc0c10edba2701064e", + "reference": "7ae5513327cb536431847bcc0c10edba2701064e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2015-06-21 07:55:53" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12 03:26:01" + }, + { + "name": "sebastian/recursion-context", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "913401df809e99e4f47b27cdd781f4a258d58791" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791", + "reference": "913401df809e99e4f47b27cdd781f4a258d58791", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2015-11-11 19:50:13" + }, + { + "name": "sebastian/version", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "shasum": "" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2015-06-21 13:59:46" + }, + { + "name": "symfony/yaml", + "version": "v2.8.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "2a4ee40acb880c56f29fb1b8886e7ffe94f3b995" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/2a4ee40acb880c56f29fb1b8886e7ffe94f3b995", + "reference": "2a4ee40acb880c56f29fb1b8886e7ffe94f3b995", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2016-02-23 07:41:20" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.6" + }, + "platform-dev": [] +} From 33e811ad8679ae0f1eca8241f34421898e5cfece Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Sat, 19 Mar 2016 00:17:09 -0400 Subject: [PATCH 03/47] config stuff. --- composer.json | 16 ++- composer.lock | 256 ++++++++++++++++++++++++++++++++++---------- phpunit.xml | 7 ++ tests/Bootstrap.php | 14 +++ 4 files changed, 231 insertions(+), 62 deletions(-) create mode 100644 phpunit.xml create mode 100644 tests/Bootstrap.php diff --git a/composer.json b/composer.json index 5e2bfaf..912a709 100644 --- a/composer.json +++ b/composer.json @@ -1,9 +1,9 @@ { - "name": "grnhse/greenhouse-job-board-api-php", + "name": "grnhse/greenhouse-job-board-php", "type": "library", - "description": "A PHP wrapper for Greenhouse's Job Board API", + "description": "A PHP package containing services to interact with Greenhouse's job board.", "keywords": ["api", "greenhouse", "job board"], - "homepage": "https://github.com/grnhse/greenhouse-job-board-api-php", + "homepage": "https://github.com/grnhse/greenhouse-job-board-php", "authors": [ { "name": "Greenhouse" @@ -15,6 +15,12 @@ "require": { "php": ">=5.6", "guzzlehttp/guzzle": "~6.0", - "phpunit/phpunit": "4.5.*" + "phpunit/phpunit": "~5.0" + }, + "autoload": { + "psr-4": { "Greenhouse\\GreenhouseJobBoardPhp\\": "src/" } + }, + "autoload-dev": { + "psr-4": { "Greenhouse\\GreenhouseJobBoardPhp\\Tests\\": "tests/" } } -} \ No newline at end of file +} diff --git a/composer.lock b/composer.lock index 694ed09..a99c2f3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "92cf987c11262c3e71d3e4309cc29054", - "content-hash": "38de0f6c1d4ab1582e4af684357e27a7", + "hash": "8634a07cc5ad16ba20f06b6186ac1048", + "content-hash": "da9267426b27e3338df6d0a8914812af", "packages": [ { "name": "doctrine/instantiator", @@ -232,6 +232,48 @@ ], "time": "2016-02-18 21:54:00" }, + { + "name": "myclabs/deep-copy", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "e3abefcd7f106677fd352cd7c187d6c969aa9ddc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/e3abefcd7f106677fd352cd7c187d6c969aa9ddc", + "reference": "e3abefcd7f106677fd352cd7c187d6c969aa9ddc", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "doctrine/collections": "1.*", + "phpunit/phpunit": "~4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "homepage": "https://github.com/myclabs/DeepCopy", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2015-11-07 22:20:37" + }, { "name": "phpdocumentor/reflection-docblock", "version": "2.0.4", @@ -345,29 +387,30 @@ }, { "name": "phpunit/php-code-coverage", - "version": "2.2.4", + "version": "3.3.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" + "reference": "fe33716763b604ade4cb442c0794f5bd5ad73004" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", - "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/fe33716763b604ade4cb442c0794f5bd5ad73004", + "reference": "fe33716763b604ade4cb442c0794f5bd5ad73004", "shasum": "" }, "require": { - "php": ">=5.3.3", + "php": "^5.6 || ^7.0", "phpunit/php-file-iterator": "~1.3", "phpunit/php-text-template": "~1.2", - "phpunit/php-token-stream": "~1.3", + "phpunit/php-token-stream": "^1.4.2", + "sebastian/code-unit-reverse-lookup": "~1.0", "sebastian/environment": "^1.3.2", - "sebastian/version": "~1.0" + "sebastian/version": "~1.0|~2.0" }, "require-dev": { "ext-xdebug": ">=2.1.4", - "phpunit/phpunit": "~4" + "phpunit/phpunit": "~5" }, "suggest": { "ext-dom": "*", @@ -377,7 +420,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.2.x-dev" + "dev-master": "3.3.x-dev" } }, "autoload": { @@ -403,35 +446,37 @@ "testing", "xunit" ], - "time": "2015-10-06 15:47:00" + "time": "2016-03-03 08:49:08" }, { "name": "phpunit/php-file-iterator", - "version": "1.3.4", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb" + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/acd690379117b042d1c8af1fafd61bde001bf6bb", - "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", "shasum": "" }, "require": { "php": ">=5.3.3" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, "autoload": { "classmap": [ - "File/" + "src/" ] }, "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "" - ], "license": [ "BSD-3-Clause" ], @@ -448,7 +493,7 @@ "filesystem", "iterator" ], - "time": "2013-10-10 15:34:57" + "time": "2015-06-21 13:08:43" }, { "name": "phpunit/php-text-template", @@ -583,16 +628,16 @@ }, { "name": "phpunit/phpunit", - "version": "4.5.1", + "version": "5.2.12", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "d6429b0995b24a2d9dfe5587ee3a7071c1161af4" + "reference": "6f0948bab32270352f97d1913d82a49338dcb0da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d6429b0995b24a2d9dfe5587ee3a7071c1161af4", - "reference": "d6429b0995b24a2d9dfe5587ee3a7071c1161af4", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6f0948bab32270352f97d1913d82a49338dcb0da", + "reference": "6f0948bab32270352f97d1913d82a49338dcb0da", "shasum": "" }, "require": { @@ -601,20 +646,22 @@ "ext-pcre": "*", "ext-reflection": "*", "ext-spl": "*", - "php": ">=5.3.3", - "phpspec/prophecy": "~1.3,>=1.3.1", - "phpunit/php-code-coverage": "~2.0,>=2.0.11", - "phpunit/php-file-iterator": "~1.3.2", + "myclabs/deep-copy": "~1.3", + "php": "^5.6 || ^7.0", + "phpspec/prophecy": "^1.3.1", + "phpunit/php-code-coverage": "^3.3.0", + "phpunit/php-file-iterator": "~1.4", "phpunit/php-text-template": "~1.2", - "phpunit/php-timer": "~1.0.2", - "phpunit/phpunit-mock-objects": "~2.3", + "phpunit/php-timer": ">=1.0.6", + "phpunit/phpunit-mock-objects": ">=3.0.5", "sebastian/comparator": "~1.1", - "sebastian/diff": "~1.1", - "sebastian/environment": "~1.2", + "sebastian/diff": "~1.2", + "sebastian/environment": "~1.3", "sebastian/exporter": "~1.2", "sebastian/global-state": "~1.0", - "sebastian/version": "~1.0", - "symfony/yaml": "~2.0" + "sebastian/resource-operations": "~1.0", + "sebastian/version": "~1.0|~2.0", + "symfony/yaml": "~2.1|~3.0" }, "suggest": { "phpunit/php-invoker": "~1.1" @@ -625,7 +672,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.5.x-dev" + "dev-master": "5.2.x-dev" } }, "autoload": { @@ -651,30 +698,30 @@ "testing", "xunit" ], - "time": "2015-03-29 09:24:05" + "time": "2016-03-15 05:59:58" }, { "name": "phpunit/phpunit-mock-objects", - "version": "2.3.8", + "version": "3.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" + "reference": "49bc700750196c04dd6bc2c4c99cb632b893836b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", - "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/49bc700750196c04dd6bc2c4c99cb632b893836b", + "reference": "49bc700750196c04dd6bc2c4c99cb632b893836b", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", - "php": ">=5.3.3", + "php": ">=5.6", "phpunit/php-text-template": "~1.2", "sebastian/exporter": "~1.2" }, "require-dev": { - "phpunit/phpunit": "~4.4" + "phpunit/phpunit": "~5" }, "suggest": { "ext-soap": "*" @@ -682,7 +729,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3.x-dev" + "dev-master": "3.0.x-dev" } }, "autoload": { @@ -707,7 +754,7 @@ "mock", "xunit" ], - "time": "2015-10-02 06:51:40" + "time": "2015-12-08 08:47:06" }, { "name": "psr/http-message", @@ -758,6 +805,51 @@ ], "time": "2015-05-04 20:22:00" }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/c36f5e7cfce482fde5bf8d10d41a53591e0198fe", + "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "~5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2016-02-13 06:45:14" + }, { "name": "sebastian/comparator", "version": "1.2.0", @@ -1094,21 +1186,71 @@ "homepage": "http://www.github.com/sebastianbergmann/recursion-context", "time": "2015-11-11 19:50:13" }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28 20:34:47" + }, { "name": "sebastian/version", - "version": "1.0.6", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" + "reference": "c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", - "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5", + "reference": "c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5", "shasum": "" }, + "require": { + "php": ">=5.6" + }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, "autoload": { "classmap": [ "src/" @@ -1127,29 +1269,29 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", - "time": "2015-06-21 13:59:46" + "time": "2016-02-04 12:56:52" }, { "name": "symfony/yaml", - "version": "v2.8.3", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "2a4ee40acb880c56f29fb1b8886e7ffe94f3b995" + "reference": "b5ba64cd67ecd6887f63868fa781ca094bd1377c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/2a4ee40acb880c56f29fb1b8886e7ffe94f3b995", - "reference": "2a4ee40acb880c56f29fb1b8886e7ffe94f3b995", + "url": "https://api.github.com/repos/symfony/yaml/zipball/b5ba64cd67ecd6887f63868fa781ca094bd1377c", + "reference": "b5ba64cd67ecd6887f63868fa781ca094bd1377c", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": ">=5.5.9" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.8-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -1176,7 +1318,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2016-02-23 07:41:20" + "time": "2016-02-23 15:16:06" } ], "packages-dev": [], diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..222cf8a --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,7 @@ + + + + ./tests/ + + + \ No newline at end of file diff --git a/tests/Bootstrap.php b/tests/Bootstrap.php new file mode 100644 index 0000000..6157336 --- /dev/null +++ b/tests/Bootstrap.php @@ -0,0 +1,14 @@ + Date: Sat, 19 Mar 2016 00:17:36 -0400 Subject: [PATCH 04/47] Job board service. --- src/Services/JobBoardService.php | 73 +++++++++++++++++++++++++ tests/Services/JobBoardServiceTest.php | 75 ++++++++++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 src/Services/JobBoardService.php create mode 100644 tests/Services/JobBoardServiceTest.php diff --git a/src/Services/JobBoardService.php b/src/Services/JobBoardService.php new file mode 100644 index 0000000..f88d8ec --- /dev/null +++ b/src/Services/JobBoardService.php @@ -0,0 +1,73 @@ +_clientToken = $clientToken; + } + + /** + * Get the Greenhouse div tag that will hold the iframe. + * + * @return string + */ + public function jobBoardTag() + { + return '
'; + } + + /** + * Get the script tag that will populate the job board iframe. + * + * @return string + */ + public function scriptTag() + { + return ""; + } + + /** + * If you'd prefer to render the Greenhouse tags together, this just wraps the + * preceding two methods. + * + * @return string + */ + public function embedGreenhouseJobBoard() + { + return $this->jobBoardTag() . "\n" . $this->scriptTag(); + } + + /** + * Returns a link to the Greenhouse-hosted job board. Note this only returns the + * default link to a Greenhouse-hosted board. It will not update if the job board + * link in Greenhouse is changed. + * + * @param string $linkText What you want the job board link to say. + * @return string + */ + public function linkToGreenhouseJobBoard($linkText="Open Positions") + { + return "$linkText"; + } + + /** + * Returns a link to a Greenhouse hosted job application. Same as above, this does + * not change if you change the job application link in Greenhouse. + * + * @param int $jobId The Job ID you want to apply to. + * @param string $linkText What you want the job board link to say. + */ + public function linkToGreenhouseJobApplication($jobId, $linkText="Apply to this job") + { + return "$linkText"; + } +} \ No newline at end of file diff --git a/tests/Services/JobBoardServiceTest.php b/tests/Services/JobBoardServiceTest.php new file mode 100644 index 0000000..5f19962 --- /dev/null +++ b/tests/Services/JobBoardServiceTest.php @@ -0,0 +1,75 @@ +jobBoardService = new JobBoardService('test_token'); + } + + public function testJobBoardTag() + { + $this->assertEquals( + $this->jobBoardService->jobBoardTag(), + '
' + ); + } + + public function testScriptTag() + { + $this->assertEquals( + $this->jobBoardService->scriptTag(), + "' + ); + } + + public function testEmbedGreenhouseJobBoard() + { + $this->assertEquals( + $this->jobBoardService->embedGreenhouseJobBoard(), + '
' . + "\n" . + "" + ); + } + + public function testLinkToGreenhouseJobBoardWithDefault() + { + $this->assertEquals( + $this->jobBoardService->linkToGreenhouseJobBoard(), + "Open Positions" + ); + } + + public function testLinkToGreenhouseJobBoardWithArgument() + { + $this->assertEquals( + $this->jobBoardService->linkToGreenhouseJobBoard('Link to jobs!'), + "Link to jobs!" + ); + } + + public function testLinkToGreenhouseJobApplicationWithDefault() + { + $this->assertEquals( + $this->jobBoardService->linkToGreenhouseJobApplication(12345), + "" . + 'Apply to this job' + ); + } + + public function testLinkToGreenhouseJobApplicationWithArgument() + { + $this->assertEquals( + $this->jobBoardService->linkToGreenhouseJobApplication(12345, 'Some work'), + "" . + 'Some work' + ); + } +} \ No newline at end of file From a0d72435e32baf6a97755919a7f9d759238e345b Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Sun, 20 Mar 2016 01:11:21 -0400 Subject: [PATCH 05/47] Exceptions. --- src/Clients/Exceptions/GreenhouseAPIClientException.php | 7 +++++++ src/Clients/Exceptions/GreenhouseAPIResponseException.php | 7 +++++++ src/Clients/Exceptions/GreenhouseApplicationException.php | 7 +++++++ src/Exceptions/GreenhouseException.php | 5 +++++ 4 files changed, 26 insertions(+) create mode 100644 src/Clients/Exceptions/GreenhouseAPIClientException.php create mode 100644 src/Clients/Exceptions/GreenhouseAPIResponseException.php create mode 100644 src/Clients/Exceptions/GreenhouseApplicationException.php create mode 100644 src/Exceptions/GreenhouseException.php diff --git a/src/Clients/Exceptions/GreenhouseAPIClientException.php b/src/Clients/Exceptions/GreenhouseAPIClientException.php new file mode 100644 index 0000000..a683aa2 --- /dev/null +++ b/src/Clients/Exceptions/GreenhouseAPIClientException.php @@ -0,0 +1,7 @@ + Date: Sun, 20 Mar 2016 01:12:51 -0400 Subject: [PATCH 06/47] Guzzle client and Interface. --- src/Clients/ApiClientInterface.php | 18 +++++++++ src/Clients/GuzzleClient.php | 62 ++++++++++++++++++++++++++++++ tests/Clients/GuzzleClientTest.php | 39 +++++++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 src/Clients/ApiClientInterface.php create mode 100644 src/Clients/GuzzleClient.php create mode 100644 tests/Clients/GuzzleClientTest.php diff --git a/src/Clients/ApiClientInterface.php b/src/Clients/ApiClientInterface.php new file mode 100644 index 0000000..e8c675a --- /dev/null +++ b/src/Clients/ApiClientInterface.php @@ -0,0 +1,18 @@ +_client = new Client($options); + } + + /** + * Fetch the URL. As this is guzzle, this can take a relative URL. See the Guzzle + * docs more info. + * + * @params string $url A relative URL off the base URL. A full URL + * should work, too + * @return string The Raw JSON response from Greenhouse + * @throws GreenhouseAPIClientException if URL is blank. + * @raise GreenhouseAPIResponseException if the get request fails + */ + public function get($url="") + { + if (empty($url)) { + throw new GreenhouseAPIClientException('Url must be set for get method.'); + } + + try { + $guzzleResponse = $this->_client->request('GET', $url); + } catch (RequestException $e) { + throw new GreenhouseAPIResponseException($e->getMessage()); + } + + return $guzzleResponse->getBody(); + } + + public function post() + { + + } + + public function getClient() + { + return $this->_client; + } +} \ No newline at end of file diff --git a/tests/Clients/GuzzleClientTest.php b/tests/Clients/GuzzleClientTest.php new file mode 100644 index 0000000..3e12417 --- /dev/null +++ b/tests/Clients/GuzzleClientTest.php @@ -0,0 +1,39 @@ +baseUrl = 'https://api.greenhouse.io/v1/boards/greenhouse/embed/'; + } + + public function testGuzzleInitialize() + { + $client = new GuzzleClient(array('base_uri' => 'http://www.example.com')); + $this->assertInstanceOf( + '\Greenhouse\GreenhouseJobBoardPhp\Clients\GuzzleClient', + $client + ); + + $this->assertInstanceOf('\GuzzleHttp\Client', $client->getClient()); + } + + public function testGetBlankUrlException() + { + $client = new GuzzleClient(array('base_uri' => $this->baseUrl)); + $this->expectException('\Greenhouse\GreenhouseJobBoardPhp\Clients\Exceptions\GreenhouseAPIClientException'); + $response = $client->get(); + } + + public function testGetException() + { + $errorUrl = 'https://api.greenhouse.io/v1/boards/exception_co/embed/'; + $client = new GuzzleClient(array('base_uri' => $errorUrl)); + $this->expectException('\Greenhouse\GreenhouseJobBoardPhp\Clients\Exceptions\GreenhouseAPIResponseException'); + $response = $client->get('jobs'); + } +} \ No newline at end of file From ab591295334502d27205944cc4ef0d729f1e5113 Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Mon, 21 Mar 2016 01:12:38 -0400 Subject: [PATCH 07/47] Cast the response as a string so we don't return a Guzzle object. --- src/Clients/GuzzleClient.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Clients/GuzzleClient.php b/src/Clients/GuzzleClient.php index 402a4fe..8c6a08b 100644 --- a/src/Clients/GuzzleClient.php +++ b/src/Clients/GuzzleClient.php @@ -32,22 +32,21 @@ public function __construct($options) * @params string $url A relative URL off the base URL. A full URL * should work, too * @return string The Raw JSON response from Greenhouse - * @throws GreenhouseAPIClientException if URL is blank. - * @raise GreenhouseAPIResponseException if the get request fails + * @throws GreenhouseAPIResponseException if the get request fails */ public function get($url="") { - if (empty($url)) { - throw new GreenhouseAPIClientException('Url must be set for get method.'); - } - try { $guzzleResponse = $this->_client->request('GET', $url); } catch (RequestException $e) { throw new GreenhouseAPIResponseException($e->getMessage()); } - return $guzzleResponse->getBody(); + /** + * Just return the response cast as a string. The rest of the universe need + * not be aware of Guzzle's details. + */ + return (string) $guzzleResponse->getBody(); } public function post() From cc1780e6c438ca253995fe17d9950f7b855c12e5 Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Mon, 21 Mar 2016 01:33:42 -0400 Subject: [PATCH 08/47] Job Api Service. --- src/Services/ApiService.php | 20 +++ .../Exceptions/GreenhouseServiceException.php | 7 + src/Services/JobApiService.php | 144 ++++++++++++++++++ tests/Services/JobApiServiceTest.php | 96 ++++++++++++ 4 files changed, 267 insertions(+) create mode 100644 src/Services/ApiService.php create mode 100644 src/Services/Exceptions/GreenhouseServiceException.php create mode 100644 src/Services/JobApiService.php create mode 100644 tests/Services/JobApiServiceTest.php diff --git a/src/Services/ApiService.php b/src/Services/ApiService.php new file mode 100644 index 0000000..7079520 --- /dev/null +++ b/src/Services/ApiService.php @@ -0,0 +1,20 @@ +_apiClient = $apiClient; + } + + public static function jobBoardBaseUrl($clientToken) + { + return "https://api.greenhouse.io/v1/boards/{$clientToken}/embed/"; + } +} diff --git a/src/Services/Exceptions/GreenhouseServiceException.php b/src/Services/Exceptions/GreenhouseServiceException.php new file mode 100644 index 0000000..26e4453 --- /dev/null +++ b/src/Services/Exceptions/GreenhouseServiceException.php @@ -0,0 +1,7 @@ +_clientToken = $clientToken; + $client = new GuzzleClient(array('base_uri' => self::jobBoardBaseUrl($clientToken))); + $this->setClient($client); + } + + /** + * GET $baseUrl/offices + * + * @return string JSON response string from Greenhouse API. + * @throws GreenhouseAPIResponseException for non-200 responses + */ + public function getOffices() + { + return $this->_apiClient->get('offices'); + } + + /** + * GET $baseUrl/office?id=$id + * + * @param $id number The id of the office to retrieve + * @return string JSON response string from Greenhouse API. + * @throws GreenhouseAPIResponseException for non-200 responses + */ + public function getOffice($id) + { + return $this->_apiClient->get("office?id=$id"); + } + + /** + * GET $baseUrl/departments + * + * @return string JSON response string from Greenhouse API. + * @throws GreenhouseAPIResponseException for non-200 responses + */ + public function getDepartments() + { + return $this->_apiClient->get('departments'); + } + + /** + * GET $baseUrl/office?id=$id + * + * @param $id number The id of the department to retrieve + * @return string JSON response string from Greenhouse API. + * @throws GreenhouseAPIResponseException for non-200 responses + */ + public function getDepartment($id) + { + return $this->_apiClient->get("department?id=$id"); + } + + /** + * GET $baseUrl (The Job board name and intro) + * + * @return string JSON response string from Greenhouse API. + * @throws GreenhouseAPIResponseException for non-200 responses + */ + public function getBoard() + { + return $this->_apiClient->get(); + } + + /** + * GET $baseUrl/jobs(?content=true) + * + * @param boolean $content Append the content paramenter to get the + * job post content, department, and office. + * @return string JSON response string from Greenhouse API. + * @throws GreenhouseAPIResponseException for non-200 responses + */ + public function getJobs($content=false) + { + $queryString = $this->getContentQuery('jobs', $content); + return $this->_apiClient->get($queryString); + } + + /** + * GET $baseUrl/job?id=$id(?questions=true) + * + * @param $id number The id of the job to retrieve + * @param $question boolean Append the question paramenter to get the + * question info in the job response. + * @return string JSON response string from Greenhouse API. + * @throws GreenhouseAPIResponseException for non-200 responses + */ + public function getJob($id, $questions=false) + { + $queryString = $this->getQuestionsQuery("job?id=$id", $questions); + return $this->_apiClient->get($queryString); + } + + /** + * Method appends the content parameter to the URL if content is true, returns + * just the uriString if it's false. + * + * @param string $uriString A base string. + * @param boolean $showConent If true, appends ?content=true to $uriString + * @return string + */ + public function getContentQuery($uriString, $showContent=false) + { + $queryString = $showContent ? '?content=true' : ''; + return $uriString . $queryString; + } + + /** + * Shortcut method appends questions=true to the query string for a single + */ + public function getQuestionsQuery($uriString, $showQuestions=false) + { + $queryString = $showQuestions ? '&questions=true' : ''; + return $uriString . $queryString; + } +} diff --git a/tests/Services/JobApiServiceTest.php b/tests/Services/JobApiServiceTest.php new file mode 100644 index 0000000..925cbbc --- /dev/null +++ b/tests/Services/JobApiServiceTest.php @@ -0,0 +1,96 @@ +jobApiService = new JobApiService('greenhouse'); + $this->errorService = new JobApiService('exception_co'); + $this->baseUrl = JobApiService::jobBoardBaseUrl('greenhouse'); + } + + public function testConstructorRequiresToken() + { + $this->expectException('\Greenhouse\GreenhouseJobBoardPhp\Services\Exceptions\GreenhouseServiceException'); + $service = new JobApiService(); + } + + public function testGetContentQueryTrue() + { + $this->assertEquals( + 'test?content=true', + $this->jobApiService->getContentQuery('test', true) + ); + } + + public function testGetContentQueryFalse() + { + $this->assertEquals( + 'test', + $this->jobApiService->getContentQuery('test') + ); + } + + public function testGetQuestionsQueryTrue() + { + $this->assertEquals( + 'test?id=12345&questions=true', + $this->jobApiService->getQuestionsQuery('test?id=12345', true) + ); + } + + public function testGetQuestionsQueryFalse() + { + $this->assertEquals( + 'test?id=12345', + $this->jobApiService->getQuestionsQuery('test?id=12345') + ); + } + + public function testGetOfficesException() + { + $this->expectException('\Greenhouse\GreenhouseJobBoardPhp\Clients\Exceptions\GreenhouseAPIResponseException'); + $this->errorService->getOffices(); + } + + public function testGetOfficeException() + { + $this->expectException('\Greenhouse\GreenhouseJobBoardPhp\Clients\Exceptions\GreenhouseAPIResponseException'); + $this->errorService->getOffice(12345); + } + + public function testGetDepartmentsException() + { + $this->expectException('\Greenhouse\GreenhouseJobBoardPhp\Clients\Exceptions\GreenhouseAPIResponseException'); + $this->errorService->getDepartments(); + } + + public function testGetDepartmentException() + { + $this->expectException('\Greenhouse\GreenhouseJobBoardPhp\Clients\Exceptions\GreenhouseAPIResponseException'); + $this->errorService->getDepartment(12345); + } + + public function testGetBoardException() + { + $this->expectException('\Greenhouse\GreenhouseJobBoardPhp\Clients\Exceptions\GreenhouseAPIResponseException'); + $this->errorService->getBoard(); + } + + public function testGetJobsException() + { + $this->expectException('\Greenhouse\GreenhouseJobBoardPhp\Clients\Exceptions\GreenhouseAPIResponseException'); + $this->errorService->getJobs(); + } + + public function testGetJobException() + { + $this->expectException('\Greenhouse\GreenhouseJobBoardPhp\Clients\Exceptions\GreenhouseAPIResponseException'); + $this->errorService->getJob(1); + } + +} \ No newline at end of file From d241a374a35c4468eb04140f444bf8a3ba5c6d14 Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Mon, 21 Mar 2016 11:31:53 -0400 Subject: [PATCH 09/47] Add some methods to make testing easier. --- src/Services/ApiService.php | 16 +++++++++++++++- src/Services/JobApiService.php | 1 - 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Services/ApiService.php b/src/Services/ApiService.php index 7079520..f7935c3 100644 --- a/src/Services/ApiService.php +++ b/src/Services/ApiService.php @@ -3,6 +3,7 @@ namespace Greenhouse\GreenhouseJobBoardPhp\Services; use Greenhouse\GreenhouseJobBoardPhp\Services\ApiService; +use Greenhouse\GreenhouseJobBoardPhp\Services\Exceptions\GreenhouseServiceException; class ApiService { @@ -13,8 +14,21 @@ public function setClient($apiClient) $this->_apiClient = $apiClient; } - public static function jobBoardBaseUrl($clientToken) + public function jobBoardBaseUrl($clientToken) { return "https://api.greenhouse.io/v1/boards/{$clientToken}/embed/"; } + + public function getJobBoardBaseUrl() + { + if empty($this->_clientToken) { + raise new GreenhouseServiceException('A client token must be defined to get the base URL.'); + } + return self::jobBoardBaseUrl($this->_clientToken); + } + + public function getClient() + { + return $this->_apiClient; + } } diff --git a/src/Services/JobApiService.php b/src/Services/JobApiService.php index 0af3198..3e58e16 100644 --- a/src/Services/JobApiService.php +++ b/src/Services/JobApiService.php @@ -4,7 +4,6 @@ use Greenhouse\GreenhouseJobBoardPhp\Services\ApiService; use Greenhouse\GreenhouseJobBoardPhp\Clients\GuzzleClient; -use Greenhouse\GreenhouseJobBoardPhp\Services\Exceptions\GreenhouseServiceException; /** * This class interacts with Greenhouse's job board API. This class interacts with the GET From 4d8bd6e30bfcbdfb7aed98385022c4a6ddd78a21 Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Mon, 21 Mar 2016 11:38:56 -0400 Subject: [PATCH 10/47] Move the clientToken to protected in the parent. --- src/Services/ApiService.php | 2 ++ src/Services/JobApiService.php | 2 -- src/Services/JobBoardService.php | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Services/ApiService.php b/src/Services/ApiService.php index f7935c3..2ee749a 100644 --- a/src/Services/ApiService.php +++ b/src/Services/ApiService.php @@ -8,6 +8,8 @@ class ApiService { protected $_apiClient; + protected $_clientToken; + protected $_apiKey; public function setClient($apiClient) { diff --git a/src/Services/JobApiService.php b/src/Services/JobApiService.php index 3e58e16..0539508 100644 --- a/src/Services/JobApiService.php +++ b/src/Services/JobApiService.php @@ -13,8 +13,6 @@ */ class JobApiService extends ApiService { - private $_clientToken; - /** * The client token is your job board token. For instance in the following * API get URL: https://api.greenhouse.io/v1/boards/example_co/embed/ diff --git a/src/Services/JobBoardService.php b/src/Services/JobBoardService.php index f88d8ec..e7a7cff 100644 --- a/src/Services/JobBoardService.php +++ b/src/Services/JobBoardService.php @@ -4,8 +4,6 @@ class JobBoardService { - private $_clientToken; - public function __construct($clientToken) { $this->_clientToken = $clientToken; From 4c6ecdbb616ccd6a2814bebc607714d380d5db13 Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Mon, 21 Mar 2016 11:45:07 -0400 Subject: [PATCH 11/47] The public service that returns the services. --- src/GreenhouseService.php | 52 ++++++++++++++++++ tests/GreenhouseServiceTest.php | 93 +++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 src/GreenhouseService.php create mode 100644 tests/GreenhouseServiceTest.php diff --git a/src/GreenhouseService.php b/src/GreenhouseService.php new file mode 100644 index 0000000..4ab5613 --- /dev/null +++ b/src/GreenhouseService.php @@ -0,0 +1,52 @@ +_apiKey = $options['apiKey']; + $this->_boardToken = $options['boardToken']; + } + + /** + * The Job API Service does not require an API key. This service interacts with + * the GET endpoints in the Greenhouse job boards. + */ + public function getJobApiService() + { + $apiService = new \Greenhouse\GreenhouseJobBoardPhp\Services\JobApiService($this->_boardToken); + $apiClient = new GuzzleClient(array( + 'base_uri' => "https://api.greenhouse.io/v1/boards/{$this->_boardToken}/embed/" + )); + $apiService->setClient($apiClient); + + return $apiService; + } + + /** + * The Appliction API Service handles posting applications in to Greenhouse. This + * requires your API key to be set. + */ + public function getApplicationApiService() + { + $applicationService = new \Greenhouse\GreenhouseJobBoardPhp\Services\ApplicationService($this->_apiKey); + $apiClient = new GuzzleClient(array( + 'base_uri' => 'https://api.greenhouse.io/v1/applications/' + )); + $apiService->setClient($apiClient); + + return $apiService; + } + + public function getJobBoardService() + { + return new \Greenhouse\GreenhouseJobBoardPhp\Services\JobBoardService($this->_boardToken); + } +} \ No newline at end of file diff --git a/tests/GreenhouseServiceTest.php b/tests/GreenhouseServiceTest.php new file mode 100644 index 0000000..6496c24 --- /dev/null +++ b/tests/GreenhouseServiceTest.php @@ -0,0 +1,93 @@ +apiKey = 'testapikey'; + $this->boardToken = 'test_token'; + $this->greenhouseService = new GreenhouseService(array( + 'apiKey' => $this->apiKey, + 'boardToken'=> $this->boardToken + )); + } + + public function testGetJobBoardService() + { + $service = $this->greenhouseService->getJobBoardService(); + $this->assertInstanceOf( + '\Greenhouse\GreenhouseJobBoardPhp\Services\JobBoardService', + $service + ); + $this->assertContains($this->boardToken, $service->scriptTag()); + } + + public function testGetJobApiService() + { + $service = $this->greenhouseService->getJobApiService(); + $this->assertInstanceOf( + '\Greenhouse\GreenhouseJobBoardPhp\Services\JobApiService', + $service + ); + + $this->assertEquals( + 'https://api.greenhouse.io/v1/boards/test_token/embed/', + $service->getJobBoardBaseUrl() + ); + + $this->assertInstanceOf('\GuzzleHttp\Client', $service->getClient()); + } + + public function testGetJobBoardService() + { + $service = $this->greenhouseService->getJobBoardService(); + $this->assertInstanceOf( + '\Greenhouse\GreenhouseJobBoardPhp\Services\JobBoardService', + $service + ); + $this->assertContains($this->boardToken, $service->scriptTag()); + } + +} + +/** + +_apiKey = $options['apiKey']; + $this->_boardToken = $options['boardToken']; + } + + public function getApiService($boardToken='') + { + + } + + public function getApplicationService($apiKey='') + { + + } + + public function getJobBoardService() + { + return new \Greenhouse\GreenhouseJobBoardPhp\Services\JobBoardService($this->_boardToken); + } + + public function getFormService() + { + + } +}**/ \ No newline at end of file From 0183baa408b6f04126d38afdec2dfced2f6337b4 Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Tue, 22 Mar 2016 10:57:06 -0400 Subject: [PATCH 12/47] move this exception to the right folder. --- .../Exceptions/GreenhouseApplicationException.php | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{Clients => Services}/Exceptions/GreenhouseApplicationException.php (100%) diff --git a/src/Clients/Exceptions/GreenhouseApplicationException.php b/src/Services/Exceptions/GreenhouseApplicationException.php similarity index 100% rename from src/Clients/Exceptions/GreenhouseApplicationException.php rename to src/Services/Exceptions/GreenhouseApplicationException.php From 0a081b587c83603a8034614b1cdbae1aa115d0e7 Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Tue, 22 Mar 2016 11:24:48 -0400 Subject: [PATCH 13/47] Service dispatcher. --- src/GreenhouseService.php | 6 ++-- tests/GreenhouseServiceTest.php | 54 ++++++--------------------------- 2 files changed, 13 insertions(+), 47 deletions(-) diff --git a/src/GreenhouseService.php b/src/GreenhouseService.php index 4ab5613..1fa4c3f 100644 --- a/src/GreenhouseService.php +++ b/src/GreenhouseService.php @@ -36,13 +36,13 @@ public function getJobApiService() */ public function getApplicationApiService() { - $applicationService = new \Greenhouse\GreenhouseJobBoardPhp\Services\ApplicationService($this->_apiKey); + $applicationService = new \Greenhouse\GreenhouseJobBoardPhp\Services\ApplicationService($this->_apiKey, $this->_boardToken); $apiClient = new GuzzleClient(array( 'base_uri' => 'https://api.greenhouse.io/v1/applications/' )); - $apiService->setClient($apiClient); + $applicationService->setClient($apiClient); - return $apiService; + return $applicationService; } public function getJobBoardService() diff --git a/tests/GreenhouseServiceTest.php b/tests/GreenhouseServiceTest.php index 6496c24..abb2fd1 100644 --- a/tests/GreenhouseServiceTest.php +++ b/tests/GreenhouseServiceTest.php @@ -3,6 +3,7 @@ namespace Greenhouse\GreenhouseJobBoardPhp\Tests; use Greenhouse\GreenhouseJobBoardPhp\GreenhouseService; +use Greenhouse\GreenhouseJobBoardPhp\Services\ApiService; class GreenhouseServiceTest extends \PHPUnit_Framework_TestCase { @@ -39,55 +40,20 @@ public function testGetJobApiService() $service->getJobBoardBaseUrl() ); - $this->assertInstanceOf('\GuzzleHttp\Client', $service->getClient()); + $this->assertInstanceOf('\Greenhouse\GreenhouseJobBoardPhp\Clients\GuzzleClient', $service->getClient()); } - public function testGetJobBoardService() + public function testGetApplicationService() { - $service = $this->greenhouseService->getJobBoardService(); + $service = $this->greenhouseService->getApplicationApiService(); $this->assertInstanceOf( - '\Greenhouse\GreenhouseJobBoardPhp\Services\JobBoardService', + '\Greenhouse\GreenhouseJobBoardPhp\Services\ApplicationService', $service ); - $this->assertContains($this->boardToken, $service->scriptTag()); + + $baseUrl = ApiService::jobBoardBaseUrl($this->boardToken); + $authHeader = 'Basic ' . base64_encode($this->apiKey); + $this->assertEquals($baseUrl, $service->getJobBoardBaseUrl()); + $this->assertEquals($authHeader, $service->getAuthorizationHeader()); } - } - -/** - -_apiKey = $options['apiKey']; - $this->_boardToken = $options['boardToken']; - } - - public function getApiService($boardToken='') - { - - } - - public function getApplicationService($apiKey='') - { - - } - - public function getJobBoardService() - { - return new \Greenhouse\GreenhouseJobBoardPhp\Services\JobBoardService($this->_boardToken); - } - - public function getFormService() - { - - } -}**/ \ No newline at end of file From 58a022c4b1d3bb12517bfd168f0fb370f8d18f5c Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Tue, 22 Mar 2016 11:38:26 -0400 Subject: [PATCH 14/47] Re-namespace. --- src/Clients/Exceptions/GreenhouseAPIClientException.php | 4 ++-- src/Clients/Exceptions/GreenhouseAPIResponseException.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Clients/Exceptions/GreenhouseAPIClientException.php b/src/Clients/Exceptions/GreenhouseAPIClientException.php index a683aa2..e8d0dbf 100644 --- a/src/Clients/Exceptions/GreenhouseAPIClientException.php +++ b/src/Clients/Exceptions/GreenhouseAPIClientException.php @@ -1,7 +1,7 @@ Date: Tue, 22 Mar 2016 12:12:40 -0400 Subject: [PATCH 15/47] Namespace update. --- composer.json | 13 +++++++------ composer.lock | 16 ++++++++-------- src/GreenhouseService.php | 10 +++++----- tests/GreenhouseServiceTest.php | 14 +++++++------- 4 files changed, 27 insertions(+), 26 deletions(-) diff --git a/composer.json b/composer.json index 912a709..cdf428a 100644 --- a/composer.json +++ b/composer.json @@ -1,15 +1,16 @@ { - "name": "grnhse/greenhouse-job-board-php", + "name": "grnhse/greenhouse-tools-php", "type": "library", - "description": "A PHP package containing services to interact with Greenhouse's job board.", + "description": "A PHP package containing services to interact with Greenhouse's job board and APIs.", "keywords": ["api", "greenhouse", "job board"], - "homepage": "https://github.com/grnhse/greenhouse-job-board-php", + "homepage": "https://github.com/grnhse/greenhouse-tools-php", "authors": [ { "name": "Greenhouse" }, { - "name": "Tom Phillips" + "name": "Tom Phillips", + "email": "tphillips@greenhouse.io" } ], "require": { @@ -18,9 +19,9 @@ "phpunit/phpunit": "~5.0" }, "autoload": { - "psr-4": { "Greenhouse\\GreenhouseJobBoardPhp\\": "src/" } + "psr-4": { "Greenhouse\\GreenhouseToolsPhp\\": "src/" } }, "autoload-dev": { - "psr-4": { "Greenhouse\\GreenhouseJobBoardPhp\\Tests\\": "tests/" } + "psr-4": { "Greenhouse\\GreenhouseToolsPhp\\Tests\\": "tests/" } } } diff --git a/composer.lock b/composer.lock index a99c2f3..6e96d8d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "8634a07cc5ad16ba20f06b6186ac1048", - "content-hash": "da9267426b27e3338df6d0a8914812af", + "hash": "cef6a2c0634f155a93b0204c87a33d34", + "content-hash": "ccf50ee845f688fa406c14854281bbde", "packages": [ { "name": "doctrine/instantiator", @@ -63,16 +63,16 @@ }, { "name": "guzzlehttp/guzzle", - "version": "6.1.1", + "version": "6.2.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "c6851d6e48f63b69357cbfa55bca116448140e0c" + "reference": "d094e337976dff9d8e2424e8485872194e768662" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/c6851d6e48f63b69357cbfa55bca116448140e0c", - "reference": "c6851d6e48f63b69357cbfa55bca116448140e0c", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d094e337976dff9d8e2424e8485872194e768662", + "reference": "d094e337976dff9d8e2424e8485872194e768662", "shasum": "" }, "require": { @@ -88,7 +88,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "6.1-dev" + "dev-master": "6.2-dev" } }, "autoload": { @@ -121,7 +121,7 @@ "rest", "web service" ], - "time": "2015-11-23 00:47:50" + "time": "2016-03-21 20:02:09" }, { "name": "guzzlehttp/promises", diff --git a/src/GreenhouseService.php b/src/GreenhouseService.php index 1fa4c3f..187e867 100644 --- a/src/GreenhouseService.php +++ b/src/GreenhouseService.php @@ -1,8 +1,8 @@ _boardToken); + $apiService = new \Greenhouse\GreenhouseToolsPhp\Services\JobApiService($this->_boardToken); $apiClient = new GuzzleClient(array( 'base_uri' => "https://api.greenhouse.io/v1/boards/{$this->_boardToken}/embed/" )); @@ -36,7 +36,7 @@ public function getJobApiService() */ public function getApplicationApiService() { - $applicationService = new \Greenhouse\GreenhouseJobBoardPhp\Services\ApplicationService($this->_apiKey, $this->_boardToken); + $applicationService = new \Greenhouse\GreenhouseToolsPhp\Services\ApplicationService($this->_apiKey, $this->_boardToken); $apiClient = new GuzzleClient(array( 'base_uri' => 'https://api.greenhouse.io/v1/applications/' )); @@ -47,6 +47,6 @@ public function getApplicationApiService() public function getJobBoardService() { - return new \Greenhouse\GreenhouseJobBoardPhp\Services\JobBoardService($this->_boardToken); + return new \Greenhouse\GreenhouseToolsPhp\Services\JobBoardService($this->_boardToken); } } \ No newline at end of file diff --git a/tests/GreenhouseServiceTest.php b/tests/GreenhouseServiceTest.php index abb2fd1..84a54a8 100644 --- a/tests/GreenhouseServiceTest.php +++ b/tests/GreenhouseServiceTest.php @@ -1,9 +1,9 @@ greenhouseService->getJobBoardService(); $this->assertInstanceOf( - '\Greenhouse\GreenhouseJobBoardPhp\Services\JobBoardService', + '\Greenhouse\GreenhouseToolsPhp\Services\JobBoardService', $service ); $this->assertContains($this->boardToken, $service->scriptTag()); @@ -31,7 +31,7 @@ public function testGetJobApiService() { $service = $this->greenhouseService->getJobApiService(); $this->assertInstanceOf( - '\Greenhouse\GreenhouseJobBoardPhp\Services\JobApiService', + '\Greenhouse\GreenhouseToolsPhp\Services\JobApiService', $service ); @@ -40,14 +40,14 @@ public function testGetJobApiService() $service->getJobBoardBaseUrl() ); - $this->assertInstanceOf('\Greenhouse\GreenhouseJobBoardPhp\Clients\GuzzleClient', $service->getClient()); + $this->assertInstanceOf('\Greenhouse\GreenhouseToolsPhp\Clients\GuzzleClient', $service->getClient()); } public function testGetApplicationService() { $service = $this->greenhouseService->getApplicationApiService(); $this->assertInstanceOf( - '\Greenhouse\GreenhouseJobBoardPhp\Services\ApplicationService', + '\Greenhouse\GreenhouseToolsPhp\Services\ApplicationService', $service ); From 223a99535256305af213e563927fdb8ef45b49c8 Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Tue, 22 Mar 2016 12:47:29 -0400 Subject: [PATCH 16/47] JsonHelper and test. --- src/Tools/JsonHelper.php | 20 ++++ tests/Tools/JsonHelperTest.php | 33 +++++ .../files/test_json/single_job_response.json | 113 ++++++++++++++++++ ...ingle_job_response_no_required_fields.json | 113 ++++++++++++++++++ 4 files changed, 279 insertions(+) create mode 100644 src/Tools/JsonHelper.php create mode 100644 tests/Tools/JsonHelperTest.php create mode 100644 tests/files/test_json/single_job_response.json create mode 100644 tests/files/test_json/single_job_response_no_required_fields.json diff --git a/src/Tools/JsonHelper.php b/src/Tools/JsonHelper.php new file mode 100644 index 0000000..734fd12 --- /dev/null +++ b/src/Tools/JsonHelper.php @@ -0,0 +1,20 @@ +json = file_get_contents("$root/../files/test_json/single_job_response.json"); + } + + public function testDecodeToObjectsSingleJob() + { + $item = JsonHelper::decodeToObjects($this->json); + $this->assertInstanceOf('stdClass', $item); + $this->assertEquals($item->id, 167538); + $this->assertInstanceOf('stdClass', $item->departments[0]); + $this->assertEquals($item->departments[0]->id, 7219); + } + + public function testDecodeToHashSingleJob() + { + $item = JsonHelper::decodeToHash($this->json); + $this->assertEquals($item['id'], 167538); + $this->assertEquals($item['departments'][0]['id'], 7219); + } +} diff --git a/tests/files/test_json/single_job_response.json b/tests/files/test_json/single_job_response.json new file mode 100644 index 0000000..ee5da29 --- /dev/null +++ b/tests/files/test_json/single_job_response.json @@ -0,0 +1,113 @@ +{ + "id": 167538, + "internal_job_id": 187771, + "title": "Controller", + "updated_at": "2016-03-21T15:53:06-04:00", + "location": { + "name": "New York" + }, + "absolute_url": "http://www.greenhouse.io/careers/job?gh_jid=167538", + "metadata": null, + "content": "test", + "departments": [{ + "id": 7219, + "name": "Finance" + }], + "offices": [{ + "id": 13, + "name": "New York", + "location": "Manhattan, NY 10007" + }], + "questions": [{ + "label": "First Name", + "fields": [{ + "name": "first_name", + "type": "input_text" + }], + "required": true + }, { + "label": "Last Name", + "fields": [{ + "name": "last_name", + "type": "input_text" + }], + "required": true + }, { + "label": "Email", + "fields": [{ + "name": "email", + "type": "input_text" + }], + "required": true + }, { + "label": "Phone", + "fields": [{ + "name": "phone", + "type": "input_text" + }], + "required": false + }, { + "label": "Resume", + "fields": [{ + "name": "resume", + "type": "input_file" + }, { + "name": "resume_text", + "type": "textarea" + }], + "required": true + }, { + "label": "Cover Letter", + "fields": [{ + "name": "cover_letter", + "type": "input_file" + }, { + "name": "cover_letter_text", + "type": "textarea" + }], + "required": true + }, { + "label": "LinkedIn Profile", + "fields": [{ + "name": "question_1042159", + "type": "input_text" + }], + "required": true + }, { + "label": "How did you hear about this job?", + "fields": [{ + "name": "question_1042161", + "type": "input_text" + }], + "required": false + }, { + "label": "Are you authorized to work in the United States?", + "fields": [{ + "name": "question_1042162", + "type": "multi_value_single_select", + "values": [{ + "label": "No", + "value": 0 + }, { + "label": "Yes", + "value": 1 + }] + }], + "required": false + }, { + "label": "Will you now or in the future require visa sponsorship?", + "fields": [{ + "name": "question_1042163", + "type": "multi_value_single_select", + "values": [{ + "label": "No", + "value": 0 + }, { + "label": "Yes", + "value": 1 + }] + }], + "required": false + }], + "compliance": null +} \ No newline at end of file diff --git a/tests/files/test_json/single_job_response_no_required_fields.json b/tests/files/test_json/single_job_response_no_required_fields.json new file mode 100644 index 0000000..5602362 --- /dev/null +++ b/tests/files/test_json/single_job_response_no_required_fields.json @@ -0,0 +1,113 @@ +{ + "id": 167538, + "internal_job_id": 187771, + "title": "Controller", + "updated_at": "2016-03-21T15:53:06-04:00", + "location": { + "name": "New York" + }, + "absolute_url": "http://www.greenhouse.io/careers/job?gh_jid=167538", + "metadata": null, + "content": "test", + "departments": [{ + "id": 7219, + "name": "Finance" + }], + "offices": [{ + "id": 13, + "name": "New York", + "location": "Manhattan, NY 10007" + }], + "questions": [{ + "label": "First Name", + "fields": [{ + "name": "first_name", + "type": "input_text" + }], + "required": false + }, { + "label": "Last Name", + "fields": [{ + "name": "last_name", + "type": "input_text" + }], + "required": false + }, { + "label": "Email", + "fields": [{ + "name": "email", + "type": "input_text" + }], + "required": false + }, { + "label": "Phone", + "fields": [{ + "name": "phone", + "type": "input_text" + }], + "required": false + }, { + "label": "Resume", + "fields": [{ + "name": "resume", + "type": "input_file" + }, { + "name": "resume_text", + "type": "textarea" + }], + "required": false + }, { + "label": "Cover Letter", + "fields": [{ + "name": "cover_letter", + "type": "input_file" + }, { + "name": "cover_letter_text", + "type": "textarea" + }], + "required": false + }, { + "label": "LinkedIn Profile", + "fields": [{ + "name": "question_1042159", + "type": "input_text" + }], + "required": false + }, { + "label": "How did you hear about this job?", + "fields": [{ + "name": "question_1042161", + "type": "input_text" + }], + "required": false + }, { + "label": "Are you authorized to work in the United States?", + "fields": [{ + "name": "question_1042162", + "type": "multi_value_single_select", + "values": [{ + "label": "No", + "value": 0 + }, { + "label": "Yes", + "value": 1 + }] + }], + "required": false + }, { + "label": "Will you now or in the future require visa sponsorship?", + "fields": [{ + "name": "question_1042163", + "type": "multi_value_single_select", + "values": [{ + "label": "No", + "value": 0 + }, { + "label": "Yes", + "value": 1 + }] + }], + "required": false + }], + "compliance": null +} \ No newline at end of file From 032fc7a13b048ba411661a569b18b4f95eaa0430 Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Tue, 22 Mar 2016 12:48:27 -0400 Subject: [PATCH 17/47] Re-namespace. --- src/Services/Exceptions/GreenhouseApplicationException.php | 4 ++-- src/Services/Exceptions/GreenhouseServiceException.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Services/Exceptions/GreenhouseApplicationException.php b/src/Services/Exceptions/GreenhouseApplicationException.php index 3450a0e..5ec20ce 100644 --- a/src/Services/Exceptions/GreenhouseApplicationException.php +++ b/src/Services/Exceptions/GreenhouseApplicationException.php @@ -1,7 +1,7 @@ Date: Tue, 22 Mar 2016 12:55:28 -0400 Subject: [PATCH 18/47] Fixes tests. --- src/Services/JobApiService.php | 7 ++++--- tests/Services/JobApiServiceTest.php | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/Services/JobApiService.php b/src/Services/JobApiService.php index 0539508..be622a8 100644 --- a/src/Services/JobApiService.php +++ b/src/Services/JobApiService.php @@ -1,9 +1,10 @@ expectException('\Greenhouse\GreenhouseJobBoardPhp\Services\Exceptions\GreenhouseServiceException'); + $this->expectException('\Greenhouse\GreenhouseToolsPhp\Services\Exceptions\GreenhouseServiceException'); $service = new JobApiService(); } @@ -53,43 +53,43 @@ public function testGetQuestionsQueryFalse() public function testGetOfficesException() { - $this->expectException('\Greenhouse\GreenhouseJobBoardPhp\Clients\Exceptions\GreenhouseAPIResponseException'); + $this->expectException('\Greenhouse\GreenhouseToolsPhp\Clients\Exceptions\GreenhouseAPIResponseException'); $this->errorService->getOffices(); } public function testGetOfficeException() { - $this->expectException('\Greenhouse\GreenhouseJobBoardPhp\Clients\Exceptions\GreenhouseAPIResponseException'); + $this->expectException('\Greenhouse\GreenhouseToolsPhp\Clients\Exceptions\GreenhouseAPIResponseException'); $this->errorService->getOffice(12345); } public function testGetDepartmentsException() { - $this->expectException('\Greenhouse\GreenhouseJobBoardPhp\Clients\Exceptions\GreenhouseAPIResponseException'); + $this->expectException('\Greenhouse\GreenhouseToolsPhp\Clients\Exceptions\GreenhouseAPIResponseException'); $this->errorService->getDepartments(); } public function testGetDepartmentException() { - $this->expectException('\Greenhouse\GreenhouseJobBoardPhp\Clients\Exceptions\GreenhouseAPIResponseException'); + $this->expectException('\Greenhouse\GreenhouseToolsPhp\Clients\Exceptions\GreenhouseAPIResponseException'); $this->errorService->getDepartment(12345); } public function testGetBoardException() { - $this->expectException('\Greenhouse\GreenhouseJobBoardPhp\Clients\Exceptions\GreenhouseAPIResponseException'); + $this->expectException('\Greenhouse\GreenhouseToolsPhp\Clients\Exceptions\GreenhouseAPIResponseException'); $this->errorService->getBoard(); } public function testGetJobsException() { - $this->expectException('\Greenhouse\GreenhouseJobBoardPhp\Clients\Exceptions\GreenhouseAPIResponseException'); + $this->expectException('\Greenhouse\GreenhouseToolsPhp\Clients\Exceptions\GreenhouseAPIResponseException'); $this->errorService->getJobs(); } public function testGetJobException() { - $this->expectException('\Greenhouse\GreenhouseJobBoardPhp\Clients\Exceptions\GreenhouseAPIResponseException'); + $this->expectException('\Greenhouse\GreenhouseToolsPhp\Clients\Exceptions\GreenhouseAPIResponseException'); $this->errorService->getJob(1); } From 6b9d9ee4a7159cffa5f55f1f3803e7d3d0131f52 Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Tue, 22 Mar 2016 12:56:04 -0400 Subject: [PATCH 19/47] Renamespace. --- src/Services/JobBoardService.php | 2 +- tests/Services/JobBoardServiceTest.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Services/JobBoardService.php b/src/Services/JobBoardService.php index e7a7cff..341c75f 100644 --- a/src/Services/JobBoardService.php +++ b/src/Services/JobBoardService.php @@ -1,6 +1,6 @@ Date: Tue, 22 Mar 2016 12:56:18 -0400 Subject: [PATCH 20/47] Renamespace. --- src/Exceptions/GreenhouseException.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Exceptions/GreenhouseException.php b/src/Exceptions/GreenhouseException.php index a425ef4..ce71018 100644 --- a/src/Exceptions/GreenhouseException.php +++ b/src/Exceptions/GreenhouseException.php @@ -1,5 +1,5 @@ Date: Tue, 22 Mar 2016 16:07:07 -0400 Subject: [PATCH 21/47] Application service. --- src/Clients/ApiClientInterface.php | 29 ++- src/Clients/GuzzleClient.php | 61 +++++- src/Services/ApplicationService.php | 182 +++++++++++++++++ tests/Clients/GuzzleClientTest.php | 62 ++++-- tests/Services/ApplicationServiceTest.php | 235 ++++++++++++++++++++++ 5 files changed, 549 insertions(+), 20 deletions(-) create mode 100644 src/Services/ApplicationService.php create mode 100644 tests/Services/ApplicationServiceTest.php diff --git a/src/Clients/ApiClientInterface.php b/src/Clients/ApiClientInterface.php index e8c675a..b709933 100644 --- a/src/Clients/ApiClientInterface.php +++ b/src/Clients/ApiClientInterface.php @@ -1,6 +1,6 @@ formatPostParameters and send it to the destination URL. The second argument is an array of + * any additional headers that should be sent with the delivery. + * + * @params Array $postVars An array of post parameters. + * @params Array $headers Additional headers. Each item should be a full header string + * ex [0] => 'Basic ' + * @params string $url The $url should be set in the constructor but if it isn't, you can + * optionally include a POST url here. + * @return boolean + * @throws GreenhouseAPIResponseException for non-200 responses + */ + public function post(Array $postParams, Array $headers, $url); + + /** + * This method should take the Greenhouse format of post parameters and transform + * them to post parameters this client understands. If both things are the same, + * Then this should just return $postParameters. The format of Greenhouse Post + * Parameters is defined in Services/ApplicationService.php + * + * @params Array $postParamters Greenhouse-format post parameters + * @return mixed Whatever method of sending POST that $this->post understands + */ + public function formatPostParameters(Array $postParameters); } \ No newline at end of file diff --git a/src/Clients/GuzzleClient.php b/src/Clients/GuzzleClient.php index 8c6a08b..87845dd 100644 --- a/src/Clients/GuzzleClient.php +++ b/src/Clients/GuzzleClient.php @@ -1,12 +1,12 @@ getBody(); } - public function post() + /** + * Post to the application endpoint. + * + * @params Array $postVars A guzzle compliant multipart array of post parameters. + * @params Array $headers This should only contain the Authorization header. + * @params string $url This can be left blank. Url is set in the constructor. + * @return string + * @throws GreenhouseAPIResponseException for non-200 responses + */ + public function post(Array $postVars, Array $headers, $url=null) { + try { + $guzzleResponse = $this->_client->request( + 'POST', + $url, + array('multipart' => $postVars, 'headers' => $headers) + ); + } catch (RequestException $e) { + throw new GreenhouseAPIResponseException($e->getMessage()); + } + + return (string) $guzzleResponse->getBody(); + } + /** + * Return a Guzzle post parameter array that can be entered in to the 'multipart' + * argument of a post request. For details on this, see the Guzzle + * documentation here: http://docs.guzzlephp.org/en/latest/request-options.html#multipart + * + * @params Array $postParameters + * @return Array + */ + public function formatPostParameters(Array $postParameters=array()) + { + $guzzleParams = array(); + /** + * There are three possibile inputs for $postParameter values. Strings, Curlfiles, and arrays. + * This look should process them in to something that Guzzle understands. + */ + foreach ($postParameters as $key => $value) { + if ($value instanceof \CURLFile) { + $guzzleParams[] = array( + 'name' => $key, + 'contents' => fopen($value->getFilename(), 'r'), + 'filename' => $value->getPostFilename() + ); + } else if (is_array($value)) { + foreach ($value as $val) $guzzleParams[] = array('name' => $key . '[]', 'contents' => "$val"); + } else { + $guzzleParams[] = array('name' => $key, 'contents' => "$value"); + } + } + + return $guzzleParams; } public function getClient() diff --git a/src/Services/ApplicationService.php b/src/Services/ApplicationService.php new file mode 100644 index 0000000..292d5db --- /dev/null +++ b/src/Services/ApplicationService.php @@ -0,0 +1,182 @@ +_apiKey = $apiKey; + $this->_clientToken = $clientToken; + $this->_authorizationHeader = $this->getAuthorizationHeader($apiKey); + + $client = new GuzzleClient(array('base_uri' => self::APPLICATION_URL)); + $this->setClient($client); + $jobService = new JobApiService($this->_clientToken); + $this->_jobApiService = $jobService; + } + + /** + * Allow the job service to be overwritten by a new JobService. Mostly for testing. + * + * @params JobApiService $jobService A new job service. + */ + public function setJobApiService(JobApiService $jobService) + { + $this->_jobApiService = $jobService; + } + + /** + * Post an application to Greenhouse. The post parameters should be defined in an + * array similar to the way you would send the item using libcurl. The difference is + * that instead of sending multiselect with indexed arrays; ie: + * + * 'var[0]' => 'foo', + * 'var[1]' => 'bar' + * + * You will instead send them as an array + * + * 'var' => array('foo', 'bar') + * + * We'll use the client interface to transform this in to whatever works for the + * given client and then post the application. + * + * Document attachments may also be defined with CurlFile. + * + * This method will also verify that required fields are not empty. As this currently + * requires verification on the client-side, we will run that verification before we + * submit anything to the API. + * + * @params Array $postVars An array of questions to be posted to the + * Applications endpoint. + * @return boolean + * @throws GreenhouseApplicationException if required fields are not included. + * @throws GreenhouseAPIResponseException if a non-200 response is returned. + */ + public function postApplication($postVars=array()) + { + $this->validateRequiredFields($postVars); + + $postParams = $this->_apiClient->formatPostParameters($postVars); + $headers = array('Authorization' => $this->_authorizationHeader); + + print_r($headers); + + $this->_apiClient->post($postParams, $headers); + + return true; + } + + /** + * Method ensures that a non-empty response is contained in all fields marked as required. If not, + * an Exception is thrown. + * + * @params Array $postVars The Greenhouse-formatted post parameters. + * @return boolean + * @throws GreenhouseApplicationException + */ + public function validateRequiredFields($postVars) + { + $requiredFields = $this->getRequiredFields($postVars['id']); + $missingKeys = array(); + + foreach ($requiredFields as $human => $keys) { + if (!$this->hasRequiredValue($postVars, $keys)) { + $missingKeys[] = $human; + } + } + + if (sizeof($missingKeys)) { + throw new GreenhouseApplicationException('Submission missing required answers for: ' . implode(', ', $missingKeys)); + } + + return true; + } + + /** + * Since a required field can have multiple possible inputs, this method just checks the + * field has a value and returns true if it does. If it finds no values in any of the inputs + * it returns false. + * + * @params Array $postVars Greenhouse post parameters. + * @params Array $keys The keys to check for in $postVars + * @return boolean + */ + public function hasRequiredValue($postVars, $keys) + { + // If there is only one key (most of the time) short-circuit the array. + if (sizeof($keys) == 1) { + return array_key_exists($keys[0], $postVars) && $postVars[$keys[0]] !== ''; + + // This is the O(N) case. Happens rarely. + } else { + foreach ($keys as $key) { + if (array_key_exists($key, $postVars) && $postVars[$key] !== '') return true; + } + } + + return false; + } + + /** + * Given a job id, make a requrest to the Greenhouse API for those questions and build an associative + * array indexed on the human-readable name containing an array of the indices that must be set. The + * array is due to the fact that one required question can have one of two things required. For example, + * if first name, last name, and resume are required, your response would look like this: + * + * + * array( + * 'First Name' => array('first_name'), + * 'Last Name' => array('last_name'), + * 'Resume' => array('resume', 'resume_text') + * ); + * + * + * Where either resume or resume_text must have a value. + * + * @params number $jobId A Greenhouse job id. + * @returns Array + * @throws GreenhouseAPIResponseException if getJob returns a non-200 response. + */ + public function getRequiredFields($jobId) + { + $job = $this->_jobApiService->getJob($jobId, true); + $jobHash = JsonHelper::decodeToHash($job); + $requiredFields = array(); + + foreach ($jobHash['questions'] as $question) { + if ($question['required']) { + $questionFields = array(); + foreach ($question['fields'] as $field) { + $questionFields[] = $field['name']; + } + $requiredFields[$question['label']] = $questionFields; + } + } + + return $requiredFields; + } +} diff --git a/tests/Clients/GuzzleClientTest.php b/tests/Clients/GuzzleClientTest.php index 3e12417..18042be 100644 --- a/tests/Clients/GuzzleClientTest.php +++ b/tests/Clients/GuzzleClientTest.php @@ -1,39 +1,75 @@ baseUrl = 'https://api.greenhouse.io/v1/boards/greenhouse/embed/'; + $this->client = new GuzzleClient(array('base_uri' => 'http://www.example.com')); + $this->resumePath = realpath(dirname(__FILE__)) . '/../files/documents/test_resume.docx'; } public function testGuzzleInitialize() { $client = new GuzzleClient(array('base_uri' => 'http://www.example.com')); $this->assertInstanceOf( - '\Greenhouse\GreenhouseJobBoardPhp\Clients\GuzzleClient', + '\Greenhouse\GreenhouseToolsPhp\Clients\GuzzleClient', $client ); $this->assertInstanceOf('\GuzzleHttp\Client', $client->getClient()); } - public function testGetBlankUrlException() - { - $client = new GuzzleClient(array('base_uri' => $this->baseUrl)); - $this->expectException('\Greenhouse\GreenhouseJobBoardPhp\Clients\Exceptions\GreenhouseAPIClientException'); - $response = $client->get(); - } - public function testGetException() { $errorUrl = 'https://api.greenhouse.io/v1/boards/exception_co/embed/'; $client = new GuzzleClient(array('base_uri' => $errorUrl)); - $this->expectException('\Greenhouse\GreenhouseJobBoardPhp\Clients\Exceptions\GreenhouseAPIResponseException'); + $this->expectException('\Greenhouse\GreenhouseToolsPhp\Clients\Exceptions\GreenhouseAPIResponseException'); $response = $client->get('jobs'); } + + public function testFormatPostParametersNoFiles() + { + $postVars = array( + 'first_name' => 'Hiram', + 'last_name' => 'Abiff', + 'talents' => array('building', 'things', 'and', 'stuff') + ); + $expected = array( + ['name' => 'first_name', 'contents' => 'Hiram'], + ['name' => 'last_name', 'contents' => 'Abiff'], + ['name' => 'talents[]', 'contents' => 'building'], + ['name' => 'talents[]', 'contents' => 'things'], + ['name' => 'talents[]', 'contents' => 'and'], + ['name' => 'talents[]', 'contents' => 'stuff'] + ); + + $this->assertEquals($expected, $this->client->formatPostParameters($postVars)); + } + + public function testFormatPostParametersWithFiles() + { + $testDoc = new \CURLFile($this->resumePath, 'application/msword', 'resume'); + + $postVars = array( + 'first_name' => 'Hiram', + 'last_name' => 'Abiff', + 'talents' => array('building', 'things', 'and', 'stuff'), + 'resume' => $testDoc + ); + + $response = $this->client->formatPostParameters($postVars); + + $this->assertEquals($response[0], ['name' => 'first_name', 'contents' => 'Hiram']); + $this->assertEquals($response[1], ['name' => 'last_name', 'contents' => 'Abiff']); + $this->assertEquals($response[2], ['name' => 'talents[]', 'contents' => 'building']); + $this->assertEquals($response[3], ['name' => 'talents[]', 'contents' => 'things']); + $this->assertEquals($response[4], ['name' => 'talents[]', 'contents' => 'and']); + $this->assertEquals($response[5], ['name' => 'talents[]', 'contents' => 'stuff']); + $this->assertEquals($response[6]['name'], 'resume'); + $this->assertEquals($response[6]['filename'], 'resume'); + } } \ No newline at end of file diff --git a/tests/Services/ApplicationServiceTest.php b/tests/Services/ApplicationServiceTest.php new file mode 100644 index 0000000..5900b69 --- /dev/null +++ b/tests/Services/ApplicationServiceTest.php @@ -0,0 +1,235 @@ +appService = new ApplicationService('test_api_key', 'greenhouse'); + } + + public function getTestJobJson() + { + $root = realpath(dirname(__FILE__)); + return file_get_contents("$root/../files/test_json/single_job_response.json"); + } + + public function getTestJobJsonNothingRequired() + { + $root = realpath(dirname(__FILE__)); + return file_get_contents("$root/../files/test_json/single_job_response_no_required_fields.json"); + } + + /** + * This is not a real test, but a programmatic way to test submissions. Commented out because + * it's externally dependent. + public function testPost() + { + $greenhouse = new \Greenhouse\GreenhouseToolsPhp\GreenhouseService(array( + 'apiKey' => 'key', + 'boardToken' => 'dungeons' + )); + + $appService = $greenhouse->getApplicationApiService(); + + $resume = realpath(dirname(__FILE__)) . '/../files/documents/test_resume.docx'; + $cover = realpath(dirname(__FILE__)) . '/../files/documents/test_cover_letter.docx'; + + $postVars = array( + 'id' => 141993, + 'first_name' => 'Thomas', + 'last_name' => 'Tester', + 'email' => 'thomas.tester@example.com', + 'phone' => '1-555-555-5555', + 'resume' => new \CURLFile($resume, null, 'my_api_resume.docx'), + 'cover_letter' => new \CURLFile($cover, null, 'my_api_cover_letter.docx'), + 'question_884945' => 'https://www.linkedin.com', + 'question_884947' => 'The internet', + 'question_1115883' => array(542885, 542886) + ); + + $appService->postApplication($postVars); + } + **/ + + public function testValidateRequiredFieldsPass() + { + $apiStub = $this->getMockBuilder('\Greenhouse\GreenhouseToolsPhp\Services\JobApiService') + ->disableOriginalConstructor() + ->getMock(); + $apiStub->method('getJob')->willReturn($this->getTestJobJson()); + $this->appService->setJobApiService($apiStub); + + $postVars = array( + 'id' => '12345', + 'first_name' => 'Hiram', + 'last_name' => 'Abiff', + 'email' => 'widowson@example.com', + 'resume_text' => 'Builder', + 'cover_letter_text' => 'I built things', + 'question_1042159' => 'stuff' + ); + + $this->assertTrue($this->appService->validateRequiredFields($postVars)); + } + + public function testValidateRequiredFieldsFailSingle() + { + $apiStub = $this->getMockBuilder('\Greenhouse\GreenhouseToolsPhp\Services\JobApiService') + ->disableOriginalConstructor() + ->getMock(); + $apiStub->method('getJob')->willReturn($this->getTestJobJson()); + $this->appService->setJobApiService($apiStub); + + $postVars = array( + 'id' => '12345', + 'first_name' => 'Hiram', + 'last_name' => 'Abiff', + 'resume_text' => 'Builder', + 'cover_letter_text' => 'I built things', + 'question_1042159' => 'stuff' + ); + + $this->expectException('\Greenhouse\GreenhouseToolsPhp\Services\Exceptions\GreenhouseApplicationException'); + $this->expectExceptionMessage('Submission missing required answers for: Email'); + $this->appService->validateRequiredFields($postVars); + } + + public function testValidateRequiredFieldsFailMultiple() + { + $apiStub = $this->getMockBuilder('\Greenhouse\GreenhouseToolsPhp\Services\JobApiService') + ->disableOriginalConstructor() + ->getMock(); + $apiStub->method('getJob')->willReturn($this->getTestJobJson()); + $this->appService->setJobApiService($apiStub); + + $postVars = array( + 'id' => '12345', + 'last_name' => 'Abiff', + 'resume_text' => 'Builder', + 'cover_letter_text' => 'I built things', + 'question_1042159' => 'stuff' + ); + + $this->expectException('\Greenhouse\GreenhouseToolsPhp\Services\Exceptions\GreenhouseApplicationException'); + $this->expectExceptionMessage('Submission missing required answers for: First Name, Email'); + $this->appService->validateRequiredFields($postVars); + } + + public function testHasRequredValueSingleHappy() + { + $postVars = array('foo' => 'something', 'bar' => 'something else'); + $keys = array('foo'); + + $this->assertTrue($this->appService->hasRequiredValue($postVars, $keys)); + } + + public function testHasRequiredValueSingleKeyNotFound() + { + $postVars = array('foo' => 'something', 'bar' => 'something else'); + $keys = array('baz'); + + $this->assertFalse($this->appService->hasRequiredValue($postVars, $keys)); + } + + public function testHasRequiredValueSingleKeyEmpty() + { + $postVars = array('foo' => '', 'bar' => 'something else'); + $keys = array('foo'); + + $this->assertFalse($this->appService->hasRequiredValue($postVars, $keys)); + } + + public function testHasRequiredValueSingleKeyZeroTrue() + { + $postVars = array('foo' => 0, 'bar' => 'something else'); + $keys = array('foo'); + + $this->assertTrue($this->appService->hasRequiredValue($postVars, $keys)); + } + + public function testHasRequiredValueMultipleHappy() + { + $postVars = array('foo' => 'something', 'bar' => 'something else'); + $keys = array('foo', 'bar'); + + $this->assertTrue($this->appService->hasRequiredValue($postVars, $keys)); + } + + public function testHasRequiredValueMultipleFirstIndex() + { + $postVars = array('foo' => 'something', 'bar' => 'something else'); + $keys = array('foo', 'baz'); + + $this->assertTrue($this->appService->hasRequiredValue($postVars, $keys)); + } + + public function testHasRequiredValueMultipleSecondIndex() + { + $postVars = array('foo' => 'something', 'bar' => 'something else'); + $keys = array('baz', 'bar'); + + $this->assertTrue($this->appService->hasRequiredValue($postVars, $keys)); + } + + public function testHasRequiredValueMultipleKeyNotFound() + { + $postVars = array('foo' => 'something', 'bar' => 'something else'); + $keys = array('oof', 'baz'); + + $this->assertFalse($this->appService->hasRequiredValue($postVars, $keys)); + } + + public function testHasRequiredValueMultipleEmpty() + { + $postVars = array('foo' => 'something', 'bar' => 'something else', 'baz' => ''); + $keys = array('oof', 'baz'); + + $this->assertFalse($this->appService->hasRequiredValue($postVars, $keys)); + } + + public function testHasRequiredValueMultipleZero() + { + $postVars = array('foo' => 0, 'bar' => 'something else'); + $keys = array('foo', 'baz'); + + $this->assertTrue($this->appService->hasRequiredValue($postVars, $keys)); + } + + public function testGetRequiredFields() + { + $apiStub = $this->getMockBuilder('\Greenhouse\GreenhouseToolsPhp\Services\JobApiService') + ->disableOriginalConstructor() + ->getMock(); + $apiStub->method('getJob')->willReturn($this->getTestJobJson()); + $this->appService->setJobApiService($apiStub); + + $expected = array( + 'First Name' => array('first_name'), + 'Last Name' => array('last_name'), + 'Email' => array('email'), + 'Resume' => array('resume', 'resume_text'), + 'Cover Letter' => array('cover_letter', 'cover_letter_text'), + 'LinkedIn Profile' => array('question_1042159') + ); + + $this->assertEquals($expected, $this->appService->getRequiredFields(0)); + } + + public function testGetRequiredFieldsNoRequiredFields() + { + $apiStub = $this->getMockBuilder('\Greenhouse\GreenhouseToolsPhp\Services\JobApiService') + ->disableOriginalConstructor() + ->getMock(); + $apiStub->method('getJob')->willReturn($this->getTestJobJsonNothingRequired()); + $this->appService->setJobApiService($apiStub); + + $this->assertEquals(array(), $this->appService->getRequiredFields(0)); + } +} + + From be83b2069b7eac130b8c0ab5b008af1128785c83 Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Tue, 22 Mar 2016 16:07:33 -0400 Subject: [PATCH 22/47] Test files --- tests/files/documents/test_cover_letter.docx | Bin 0 -> 26538 bytes tests/files/documents/test_resume.docx | Bin 0 -> 27116 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/files/documents/test_cover_letter.docx create mode 100644 tests/files/documents/test_resume.docx diff --git a/tests/files/documents/test_cover_letter.docx b/tests/files/documents/test_cover_letter.docx new file mode 100644 index 0000000000000000000000000000000000000000..046cd7992ca9e6efbd1e2ef0b83b23b7fd597f94 GIT binary patch literal 26538 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 literal 0 HcmV?d00001 From 8300bed6c443289207b8928d1f7a69fa3d7b9835 Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Wed, 23 Mar 2016 13:49:03 -0400 Subject: [PATCH 23/47] Parent API Service class. --- src/Services/ApiService.php | 58 +++++++++++++++++++++++--- tests/Services/ApiServiceTest.php | 67 +++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 6 deletions(-) create mode 100644 tests/Services/ApiServiceTest.php diff --git a/src/Services/ApiService.php b/src/Services/ApiService.php index 2ee749a..f9efec1 100644 --- a/src/Services/ApiService.php +++ b/src/Services/ApiService.php @@ -1,9 +1,9 @@ _apiClient = $apiClient; } - public function jobBoardBaseUrl($clientToken) + /** + * This is in a static method instead of a constant because it has a variable in it. + * If I wanted to be crafty I could assemble a constant with a variable here but + * since it's still in one place, who cares? + * + * @param string $clientToken Your company's URL token. + * @return string + */ + public static function jobBoardBaseUrl($clientToken) { return "https://api.greenhouse.io/v1/boards/{$clientToken}/embed/"; } + /** + * This wraps the above static method so you don't have to call it statically from + * within the package. + */ public function getJobBoardBaseUrl() { - if empty($this->_clientToken) { - raise new GreenhouseServiceException('A client token must be defined to get the base URL.'); + if (empty($this->_clientToken)) { + throw new GreenhouseServiceException('A client token must be defined to get the base URL.'); } return self::jobBoardBaseUrl($this->_clientToken); } + /** + * Return whatever client we're using. This should be something that implements the + * GreenhouseClientInterface + */ public function getClient() { return $this->_apiClient; } + + /** + * Base64 Encode an API key. Greenhouse's encoding treats the key as the username with + * a blank password. Here we append the : for convenience. We also handle the case + * where the user appends the : themselves. If no key is provided, we will attempt + * to encode the private api key property. + * + * @params string $apiKey A greenhouse job board API key. + * @return string The other side of an Authorization header (Basic: ) + */ + public function getAuthorizationHeader($apiKey="") + { + if (empty($apiKey)) { + $apiKey = $this->_apiKey; + } + + if (empty($apiKey)) { + throw new GreenhouseServiceException('No key provided to encode.'); + } + + if (substr($apiKey, -1) == ':') { + $key = $apiKey; + } else { + $key = $apiKey . ':'; + } + + return 'Basic ' . base64_encode($key); + } } diff --git a/tests/Services/ApiServiceTest.php b/tests/Services/ApiServiceTest.php new file mode 100644 index 0000000..2b90729 --- /dev/null +++ b/tests/Services/ApiServiceTest.php @@ -0,0 +1,67 @@ +apiService = new ApiService(); + } + + public function testJobBoardBaseUrl() + { + $expected = 'https://api.greenhouse.io/v1/boards/test_token/embed/'; + $this->assertEquals($expected, ApiService::jobBoardBaseUrl('test_token')); + } + + public function testGetJobBoardBaseUrl() + { + $jobService = new JobApiService('test_token'); + $expected = 'https://api.greenhouse.io/v1/boards/test_token/embed/'; + $this->assertEquals($expected, $jobService->getJobBoardBaseUrl()); + } + + public function testGetJobBoardBaseUrlFailsWithNoTokenSet() + { + $this->expectException('\Greenhouse\GreenhouseToolsPhp\Services\Exceptions\GreenhouseServiceException'); + $this->apiService->getJobBoardBaseUrl(); + } + + public function testGetAndSetClient() + { + $this->assertNull($this->apiService->getClient()); + $this->apiService->setClient('some_client'); + $this->assertEquals($this->apiService->getClient(), 'some_client'); + } + + public function testGetAuthorizationHeaderFromPrivateVariable() + { + $appService = new ApplicationService('test_this_api_key1', 'test_token'); + $expected = 'Basic dGVzdF90aGlzX2FwaV9rZXkxOg=='; + $this->assertEquals($expected, $appService->getAuthorizationHeader()); + } + + public function testGetAuthorizationHeaderFromArgument() + { + $expected = 'Basic dGhpc19pc19hbm90aGVyX2FwaV9rZXk6'; + $this->assertEquals($expected, $this->apiService->getAuthorizationHeader('this_is_another_api_key')); + } + + public function testGetAuthorizationHeaderPrefersArgument() + { + $appService = new ApplicationService('test_this_api_key1', 'test_token'); + $expected = 'Basic dGhpc19pc19hbm90aGVyX2FwaV9rZXk6'; + $this->assertEquals($expected, $appService->getAuthorizationHeader('this_is_another_api_key')); + } + + public function testGetAuthorizationHeaderExceptionBlank() + { + $this->expectException('\Greenhouse\GreenhouseToolsPhp\Services\Exceptions\GreenhouseServiceException'); + $this->apiService->getAuthorizationHeader(); + } +} From 308d1431636af66408afe394bf5653efc4020846 Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Wed, 23 Mar 2016 13:50:34 -0400 Subject: [PATCH 24/47] Add colon to test to match auth header. --- tests/GreenhouseServiceTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GreenhouseServiceTest.php b/tests/GreenhouseServiceTest.php index 84a54a8..b1a6e9c 100644 --- a/tests/GreenhouseServiceTest.php +++ b/tests/GreenhouseServiceTest.php @@ -52,7 +52,7 @@ public function testGetApplicationService() ); $baseUrl = ApiService::jobBoardBaseUrl($this->boardToken); - $authHeader = 'Basic ' . base64_encode($this->apiKey); + $authHeader = 'Basic ' . base64_encode($this->apiKey . ':'); $this->assertEquals($baseUrl, $service->getJobBoardBaseUrl()); $this->assertEquals($authHeader, $service->getAuthorizationHeader()); } From 94860a93815e3c02d52e430203f9f5e3df1d4eb6 Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Wed, 23 Mar 2016 14:54:51 -0400 Subject: [PATCH 25/47] Curl Client. --- src/Clients/CurlClient.php | 98 ++++++++++++++++++++++++++++++++ src/Clients/GuzzleClient.php | 2 + tests/Clients/CurlClientTest.php | 65 +++++++++++++++++++++ 3 files changed, 165 insertions(+) create mode 100644 src/Clients/CurlClient.php create mode 100644 tests/Clients/CurlClientTest.php diff --git a/src/Clients/CurlClient.php b/src/Clients/CurlClient.php new file mode 100644 index 0000000..f352372 --- /dev/null +++ b/src/Clients/CurlClient.php @@ -0,0 +1,98 @@ +_client = curl_init(); + curl_setopt($this->_client, CURLOPT_RETURNTRANSFER, 1); + } + + /** + * Fetch the URL. As this is guzzle, this can take a relative URL. See the Guzzle + * docs more info. + * + * @params string $url A full URL to get. + * @return string The Raw JSON response from Greenhouse + * @throws GreenhouseAPIResponseException if the get request fails + */ + public function get($url) + { + curl_setopt($this->_client, CURLOPT_URL, $url); + curl_setopt($this->_client, CURLOPT_POST, 0); + return $this->_execute(); + } + + /** + * Post to the application endpoint. + * + * @params Array $postVars A guzzle compliant multipart array of post parameters. + * @params Array $headers This should only contain the Authorization header. + * @params string $url This can be left blank. Url is set in the constructor. + * @return string + * @throws GreenhouseAPIResponseException for non-200 responses + */ + public function post(Array $postVars, Array $headers, $url) + { + $this->formatPostParameters($postVars); + + curl_setopt($this->_client, CURLOPT_URL, $url); + curl_setopt($this->_client, CURLOPT_POST, 1); + curl_setopt($this->_client, CURLOPT_POSTFIELDS, $postVars); + curl_setopt($this->_client, CURLOPT_HTTPHEADER, $headers); + + return $this->_execute(); + } + + private function _execute() + { + $response = curl_exec($this->_client); + $httpCode = curl_getinfo($this->_client, CURLINFO_HTTP_CODE); + if ($httpCode < 200 || $httpCode >= 300) { + throw new GreenhouseApiResponseException("$httpCode -- $response"); + } + return $response; + } + + /** + * Return a Guzzle post parameter array that can be entered in to the 'multipart' + * argument of a post request. For details on this, see the Guzzle + * documentation here: http://docs.guzzlephp.org/en/latest/request-options.html#multipart + * + * @params Array $postParameters + * @return Array + */ + public function formatPostParameters(Array $postParameters=array()) + { + foreach ($postParameters as $key => $value) { + if (is_array($value)) { + throw new GreenhouseAPIClientException('CurlClient does not support array post parameters.'); + } + } + + return $postParameters; + } + + public function getClient() + { + return $this->_client; + } +} \ No newline at end of file diff --git a/src/Clients/GuzzleClient.php b/src/Clients/GuzzleClient.php index 87845dd..658c161 100644 --- a/src/Clients/GuzzleClient.php +++ b/src/Clients/GuzzleClient.php @@ -13,6 +13,8 @@ */ class GuzzleClient implements ApiClientInterface { + private $_client; + /** * Constructor should receive an array that would be understood by the Guzzle * client constructor. Constructor hands off an unmodified array to the Guzzle diff --git a/tests/Clients/CurlClientTest.php b/tests/Clients/CurlClientTest.php new file mode 100644 index 0000000..674f6b0 --- /dev/null +++ b/tests/Clients/CurlClientTest.php @@ -0,0 +1,65 @@ +client = new CurlClient(); + $this->resumePath = realpath(dirname(__FILE__)) . '/../files/documents/test_resume.docx'; + } + + public function testGuzzleInitialize() + { + $client = new CurlClient(array('base_uri' => 'http://www.example.com')); + $this->assertInstanceOf( + '\Greenhouse\GreenhouseToolsPhp\Clients\CurlClient', + $client + ); + } + + public function testGetException() + { + $errorUrl = 'https://api.greenhouse.io/v1/boards/exception_co/embed/'; + $client = new CurlClient(); + $this->expectException('\Greenhouse\GreenhouseToolsPhp\Clients\Exceptions\GreenhouseAPIResponseException'); + $response = $client->get($errorUrl); + } + + public function testFormatPostParameters() + { + $postVars = array( + 'first_name' => 'Hiram', + 'last_name' => 'Abiff', + ); + + $this->assertEquals($postVars, $this->client->formatPostParameters($postVars)); + } + + public function testFormatPostParametersException() + { + $postVars = array( + 'first_name' => 'Hiram', + 'last_name' => 'Abiff', + 'talents' => array('building', 'things', 'and', 'stuff') + ); + + $this->expectException('\Greenhouse\GreenhouseToolsPhp\Clients\Exceptions\GreenhouseAPIClientException'); + $this->client->formatPostParameters($postVars); + } + + public function testPostThrowsExceptionsWithArrayValues() + { + $postVars = array( + 'first_name' => 'Hiram', + 'last_name' => 'Abiff', + 'talents' => array('building', 'things', 'and', 'stuff') + ); + + $this->expectException('\Greenhouse\GreenhouseToolsPhp\Clients\Exceptions\GreenhouseAPIClientException'); + $this->client->post($postVars, array(), 'http://www.example.com'); + } +} \ No newline at end of file From a84d4eba8413be72d8c7115f071001fa33c09b48 Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Wed, 23 Mar 2016 15:04:40 -0400 Subject: [PATCH 26/47] Add me as author. --- composer.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/composer.json b/composer.json index cdf428a..c8f7a03 100644 --- a/composer.json +++ b/composer.json @@ -5,9 +5,6 @@ "keywords": ["api", "greenhouse", "job board"], "homepage": "https://github.com/grnhse/greenhouse-tools-php", "authors": [ - { - "name": "Greenhouse" - }, { "name": "Tom Phillips", "email": "tphillips@greenhouse.io" From 8dee5d5df7bcfaa5cee69493161951a097544b69 Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Wed, 23 Mar 2016 15:04:50 -0400 Subject: [PATCH 27/47] Travis. --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..44184ae --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +language: php +php: + - '5.6' + - '7.0' + - hhvm + - nightly From 073f50f2fa18002231435c6fcbb45e94328a49ac Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Wed, 23 Mar 2016 15:13:56 -0400 Subject: [PATCH 28/47] Spacing. --- tests/Clients/CurlClientTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Clients/CurlClientTest.php b/tests/Clients/CurlClientTest.php index 674f6b0..ad71848 100644 --- a/tests/Clients/CurlClientTest.php +++ b/tests/Clients/CurlClientTest.php @@ -8,8 +8,8 @@ class CurlClientTest extends \PHPUnit_Framework_TestCase { public function setUp() { - $this->client = new CurlClient(); - $this->resumePath = realpath(dirname(__FILE__)) . '/../files/documents/test_resume.docx'; + $this->client = new CurlClient(); + $this->resumePath = realpath(dirname(__FILE__)) . '/../files/documents/test_resume.docx'; } public function testGuzzleInitialize() From 4804782d89bfe00fec8687af8603c4b7ae8dda69 Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Wed, 23 Mar 2016 15:17:45 -0400 Subject: [PATCH 29/47] Travis phpunit. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 44184ae..f422064 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,3 +4,4 @@ php: - '7.0' - hhvm - nightly +script: phpunit From 8960c23d350c1e031e30fe4ea2ffb918b3f81a96 Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Wed, 23 Mar 2016 15:20:14 -0400 Subject: [PATCH 30/47] Travis phpunit. --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index f422064..09cbb2b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,3 +5,7 @@ php: - hhvm - nightly script: phpunit +branches: + only: + - master +sudo: false From aac1cfdb0789ae3b20d520bc773e34cbaaea27cb Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Wed, 23 Mar 2016 15:50:48 -0400 Subject: [PATCH 31/47] Travis phpunit. --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 09cbb2b..14ae2fd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ php: - '5.6' - '7.0' - hhvm - - nightly script: phpunit branches: only: From 432ce0f5a4746cef1d6f61a740c0cc9c6054b60f Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Wed, 23 Mar 2016 16:03:42 -0400 Subject: [PATCH 32/47] Add composer install to travis. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 14ae2fd..3b7be20 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ php: - '7.0' - hhvm script: phpunit +install: composer install branches: only: - master From 6186c270da21fa108f7d1fea8f18412fd1292811 Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Wed, 23 Mar 2016 18:17:01 -0400 Subject: [PATCH 33/47] Check readme. --- README.md | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6ab1e99..012d368 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,30 @@ -# greenhouse-job-board-api-php -A PHP Package for interacting with Greenhouse's Job Boards +# Greenhouse Service Tools For PHP + +This package of tools is provided by Greenhouse for customers who use PHP. There are three tools provided. + +1. **Job Board Service**: Used to embed iframes in your template or view files. +2. **Job API Service**: Used to fetch data from the Greenhouse Job Board API. +3. **Application Service**: Used to send applications in to Greenhouse. + +# Greenhouse Service +The Greenhouse Service is a parent service that returns the other Greenhouse Services. By using this service, you have access to all the other services. The Greenhouse service takes an array that optionally includes your job board URL Token [https://app.greenhouse.io/configure/dev_center/config/](found here in Greenhouse) and your Job Board API Credentials [https://app.greenhouse.io/configure/dev_center/credentials](found here in Greenhouse). Create a Greenhouse Service object like this: + +``` + '', + 'boardToken' => '' + ]); +?> +``` + +Using this service, you can easily access the other Greenhouse Services and it will use the board token and client token as appropriate. + +# The Job Board Service + +# The Job API Service + +# The Application Service + From ab4ff855421a2c3cd5a497952f44378c44c34205 Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Wed, 23 Mar 2016 18:22:29 -0400 Subject: [PATCH 34/47] Fix url I think? --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 012d368..24aa009 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This package of tools is provided by Greenhouse for customers who use PHP. Ther 3. **Application Service**: Used to send applications in to Greenhouse. # Greenhouse Service -The Greenhouse Service is a parent service that returns the other Greenhouse Services. By using this service, you have access to all the other services. The Greenhouse service takes an array that optionally includes your job board URL Token [https://app.greenhouse.io/configure/dev_center/config/](found here in Greenhouse) and your Job Board API Credentials [https://app.greenhouse.io/configure/dev_center/credentials](found here in Greenhouse). Create a Greenhouse Service object like this: +The Greenhouse Service is a parent service that returns the other Greenhouse Services. By using this service, you have access to all the other services. The Greenhouse service takes an array that optionally includes your job board URL Token [found here in Greenhouse](https://app.greenhouse.io/configure/dev_center/config/) and your Job Board API Credentials [(found here in Greenhouse)] (https://app.greenhouse.io/configure/dev_center/credentials). Create a Greenhouse Service object like this: ``` Date: Thu, 24 Mar 2016 01:05:10 -0400 Subject: [PATCH 35/47] Add a source token option. --- src/Services/JobBoardService.php | 12 +++++------- tests/Services/JobBoardServiceTest.php | 9 +++++++++ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Services/JobBoardService.php b/src/Services/JobBoardService.php index 341c75f..412cf73 100644 --- a/src/Services/JobBoardService.php +++ b/src/Services/JobBoardService.php @@ -26,9 +26,7 @@ public function jobBoardTag() */ public function scriptTag() { - return ""; + return ""; } /** @@ -62,10 +60,10 @@ public function linkToGreenhouseJobBoard($linkText="Open Positions") * @param int $jobId The Job ID you want to apply to. * @param string $linkText What you want the job board link to say. */ - public function linkToGreenhouseJobApplication($jobId, $linkText="Apply to this job") + public function linkToGreenhouseJobApplication($jobId, $linkText="Apply to this job", $sourceToken="") { - return "$linkText"; + $ghSrc = empty($sourceToken) ? '' : "?gh_src={$sourceToken}"; + + return "{$linkText}"; } } \ No newline at end of file diff --git a/tests/Services/JobBoardServiceTest.php b/tests/Services/JobBoardServiceTest.php index 6ffb00f..6acb59d 100644 --- a/tests/Services/JobBoardServiceTest.php +++ b/tests/Services/JobBoardServiceTest.php @@ -72,4 +72,13 @@ public function testLinkToGreenhouseJobApplicationWithArgument() 'Some work' ); } + + public function testLinkToGreenhouseJobApplicationWithToken() + { + $this->assertEquals( + $this->jobBoardService->linkToGreenhouseJobApplication(12345, 'Some work', 'abcde'), + "" . + 'Some work' + ); + } } \ No newline at end of file From 102cde994cdaeb434dfd3701fdab20436b0408a1 Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Thu, 24 Mar 2016 01:05:32 -0400 Subject: [PATCH 36/47] Update docs. --- README.md | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 24aa009..6f908e7 100644 --- a/README.md +++ b/README.md @@ -7,22 +7,43 @@ This package of tools is provided by Greenhouse for customers who use PHP. Ther 3. **Application Service**: Used to send applications in to Greenhouse. # Greenhouse Service -The Greenhouse Service is a parent service that returns the other Greenhouse Services. By using this service, you have access to all the other services. The Greenhouse service takes an array that optionally includes your job board URL Token [found here in Greenhouse](https://app.greenhouse.io/configure/dev_center/config/) and your Job Board API Credentials [(found here in Greenhouse)] (https://app.greenhouse.io/configure/dev_center/credentials). Create a Greenhouse Service object like this: +The Greenhouse Service is a parent service that returns the other Greenhouse Services. By using this service, you have access to all the other services. The Greenhouse service takes an array that optionally includes your job board URL Token [(found here in Greenhouse)](https://app.greenhouse.io/configure/dev_center/config/) and your Job Board API Credentials [(found here in Greenhouse)] (https://app.greenhouse.io/configure/dev_center/credentials). Create a Greenhouse Service object like this: ``` '', - 'boardToken' => '' - ]); +$service = new GreenhouseService([ + 'apiKey' => '', + 'boardToken' => '' +]); + ?> ``` Using this service, you can easily access the other Greenhouse Services and it will use the board token and client token as appropriate. # The Job Board Service +This service generates the appropriate HTML tags for use with the Greenhouse iframe integration. Use this service to generate either links to a Greenhouse-hosted job board or the appropriate tags for a Greenhouse iframe. Access the job board service by calling: + +``` +getJobBoardService(); + +// Link to a Greenhouse hosted job board +$service->linkToGreenhouseJobBoard(); + +// Link to a Greenhouse hosted job application +$service->linkToGreenhouseJobApplication(12345, 'Apply to this job!', 'source_token'); + +// Embed a Greenhouse iframe in your page +$service->embedGreenhouseJobBoard(); + +?> +``` + # The Job API Service From 560e0db333ae4eae1b71ada5ec3150f60da1d49c Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Thu, 24 Mar 2016 10:46:35 -0400 Subject: [PATCH 37/47] check this in. --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 6f908e7..df04237 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,8 @@ $service->embedGreenhouseJobBoard(); ?> ``` - - # The Job API Service +Use this service # The Application Service From 25a1d57905d43a7e682927951af4da3629c37648 Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Thu, 24 Mar 2016 11:13:53 -0400 Subject: [PATCH 38/47] Update comments to be not Guzzle comments. --- src/Clients/CurlClient.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Clients/CurlClient.php b/src/Clients/CurlClient.php index f352372..315ffe1 100644 --- a/src/Clients/CurlClient.php +++ b/src/Clients/CurlClient.php @@ -27,8 +27,7 @@ public function __construct() } /** - * Fetch the URL. As this is guzzle, this can take a relative URL. See the Guzzle - * docs more info. + * Fetch the URL. * * @params string $url A full URL to get. * @return string The Raw JSON response from Greenhouse @@ -49,9 +48,11 @@ public function get($url) * @params string $url This can be left blank. Url is set in the constructor. * @return string * @throws GreenhouseAPIResponseException for non-200 responses + * @throws GreenhouseAPIClientException if $postParameters contains an array(), indicating multiselect. */ public function post(Array $postVars, Array $headers, $url) { + // Use this to check for multi-selects. $this->formatPostParameters($postVars); curl_setopt($this->_client, CURLOPT_URL, $url); @@ -73,12 +74,14 @@ private function _execute() } /** - * Return a Guzzle post parameter array that can be entered in to the 'multipart' - * argument of a post request. For details on this, see the Guzzle - * documentation here: http://docs.guzzlephp.org/en/latest/request-options.html#multipart + * Greenhouse's POST format matches CURL. However, it will reject any attempt to POST an array because + * libcurl has a bug that prevents submitting it correctly to Ruby applications. For more details, see + * the PHP bug here: https://bugs.php.net/bug.php?id=51634 + * This will check for multiselects, throw an exception if one exists, or just reflect the parameters. * * @params Array $postParameters * @return Array + * @throws GreenhouseAPIClientException if $postParameters contains an array(), indicating multiselect. */ public function formatPostParameters(Array $postParameters=array()) { From 2c7a661aeb869d5539e3990dcd35f53df0b57660 Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Thu, 24 Mar 2016 12:50:11 -0400 Subject: [PATCH 39/47] Update Readme. --- README.md | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 69 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index df04237..84bfcf8 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ The Greenhouse Service is a parent service that returns the other Greenhouse Ser use \Greenhouse\GreenhouseToolsPhp\GreenhouseService; -$service = new GreenhouseService([ +$greenhouseService = new GreenhouseService([ 'apiKey' => '', 'boardToken' => '' ]); @@ -30,21 +30,84 @@ This service generates the appropriate HTML tags for use with the Greenhouse ifr ``` getJobBoardService(); +$greenhouseService->getJobBoardService(); // Link to a Greenhouse hosted job board -$service->linkToGreenhouseJobBoard(); +$greenhouseService->linkToGreenhouseJobBoard(); // Link to a Greenhouse hosted job application -$service->linkToGreenhouseJobApplication(12345, 'Apply to this job!', 'source_token'); +$greenhouseService->linkToGreenhouseJobApplication(12345, 'Apply to this job!', 'source_token'); // Embed a Greenhouse iframe in your page -$service->embedGreenhouseJobBoard(); +$greenhouseService->embedGreenhouseJobBoard(); ?> ``` # The Job API Service -Use this service +Use this service to fetch public job board information from our job board API. This services does not require an API key. This is used to interact with the GET endpoints in the Greenhouse Job Board API. These methods can be [found here](https://developers.greenhouse.io/job-board.html). Access this service via: +``` +$greenhouseService->getJobApiService(); +``` + +The methods in this service are named in relation to the endpoints, so to use the GET Offices endpoint, you'd call: + +``` +$greenhouseService->getOffices(); +``` + +And to get a specific office: + +``` +$greenhouseService->getOffice($officeId); +``` + +The only additional parameters used in any case are for the "content" and "questions" parameters in the Jobs endpoint. These are managed with boolean arguments that default to `false` in the `getJobs` and `getJob` methods. To get all jobs with their content included, you'd call: + +``` +$service->getJobs(true); +``` + +while to get a job with its questions included, you'd call: + +``` +$service->getJob($jobId, true); +``` # The Application Service +Use this service to post Applications in to Greenhouse. Use of this Service requires a Job Board API key which can be generated in Greenhouse. Example usage of this service follows: + +``` +getApplicationService(); +$postParams = array(' + 'first_name' => 'Johnny', + 'last_name' => 'Test', + 'email' => 'jt@example.com', + 'resume' => new \CurlFile('path/to/file.pdf', 'application/pdf', 'resume.pdf'),j + 'question_12345' => 'The answer you seek', + 'question_123456' => array(12345, 23456, 34567) +); +$params = $appService->formatPostParameter($postParams); +$appService->postApplication($params); + +?> +``` +The Application will handle generating an Authorization header based on your API key and posting the application as a multi-part form. This parameter array follows the PHP convention except for the case of multiselect submission (submitting parameters with the same name). While the PHP docs want users to submit multiple values like this: + +``` +'question_123456[0]' => 23456, +'question_123456[1]' => 12345, +'question_123456[2]' => 34567, +``` + +The Greenhouse packages requires you to do it like this: + +``` +'question_123456' => array(23456,12345,34567), +``` + +This prevents issues that arise for systems that do not understand the array-indexed nomenclature preferred by Libcurl. +# Exceptions +All exceptions raised by the Greenhouse Service library extend from `GreenhouseException`. Catch this exception to catch anything thrown from this library. \ No newline at end of file From 171b2900ac2bc8e106a2f1e2a9421aa5135937a8 Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Thu, 24 Mar 2016 14:13:21 -0400 Subject: [PATCH 40/47] Fix comment. --- src/Clients/CurlClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Clients/CurlClient.php b/src/Clients/CurlClient.php index 315ffe1..6bf2f70 100644 --- a/src/Clients/CurlClient.php +++ b/src/Clients/CurlClient.php @@ -75,7 +75,7 @@ private function _execute() /** * Greenhouse's POST format matches CURL. However, it will reject any attempt to POST an array because - * libcurl has a bug that prevents submitting it correctly to Ruby applications. For more details, see + * libcurl has a bug that prevents submitting it correctly to non-PHP applications. For more details, see * the PHP bug here: https://bugs.php.net/bug.php?id=51634 * This will check for multiselects, throw an exception if one exists, or just reflect the parameters. * From b6e53e50dd44c1f9485f5d5275a630bbe0340338 Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Thu, 24 Mar 2016 14:19:43 -0400 Subject: [PATCH 41/47] Require PHP Unit in dev only. --- .travis.yml | 2 +- composer.json | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3b7be20..fe31d15 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ php: - '7.0' - hhvm script: phpunit -install: composer install +before_install: composer install --dev branches: only: - master diff --git a/composer.json b/composer.json index c8f7a03..0ebb5b5 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,9 @@ ], "require": { "php": ">=5.6", - "guzzlehttp/guzzle": "~6.0", + "guzzlehttp/guzzle": "~6.0" + }, + "require-dev": { "phpunit/phpunit": "~5.0" }, "autoload": { From 28fdc1b7c9fa9af966190b14823d0679d7da79cd Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Mon, 28 Mar 2016 13:44:23 -0400 Subject: [PATCH 42/47] code review comments --- src/Services/ApiService.php | 12 ++---------- src/Services/ApplicationService.php | 5 +---- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/src/Services/ApiService.php b/src/Services/ApiService.php index f9efec1..305161f 100644 --- a/src/Services/ApiService.php +++ b/src/Services/ApiService.php @@ -64,18 +64,10 @@ public function getClient() public function getAuthorizationHeader($apiKey="") { if (empty($apiKey)) { + if (empty($this->_apiKey)) throw new GreenhouseServiceException('No key provided to encode.'); $apiKey = $this->_apiKey; } - - if (empty($apiKey)) { - throw new GreenhouseServiceException('No key provided to encode.'); - } - - if (substr($apiKey, -1) == ':') { - $key = $apiKey; - } else { - $key = $apiKey . ':'; - } + $key = rtrim($apiKey, ':') . ':'; return 'Basic ' . base64_encode($key); } diff --git a/src/Services/ApplicationService.php b/src/Services/ApplicationService.php index 292d5db..d429830 100644 --- a/src/Services/ApplicationService.php +++ b/src/Services/ApplicationService.php @@ -79,12 +79,9 @@ public function setJobApiService(JobApiService $jobService) public function postApplication($postVars=array()) { $this->validateRequiredFields($postVars); - $postParams = $this->_apiClient->formatPostParameters($postVars); $headers = array('Authorization' => $this->_authorizationHeader); - print_r($headers); - $this->_apiClient->post($postParams, $headers); return true; @@ -109,7 +106,7 @@ public function validateRequiredFields($postVars) } } - if (sizeof($missingKeys)) { + if (!empty($missingKeys)) { throw new GreenhouseApplicationException('Submission missing required answers for: ' . implode(', ', $missingKeys)); } From aad485fc73f31c2e4030ac12a39a33dab0c9e0ce Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Tue, 29 Mar 2016 14:12:09 -0400 Subject: [PATCH 43/47] Review comments. --- src/Clients/CurlClient.php | 2 +- src/GreenhouseService.php | 4 ++-- src/Services/ApiService.php | 3 ++- src/Services/ApplicationService.php | 13 ++----------- src/Services/JobApiService.php | 5 +---- tests/Bootstrap.php | 2 +- tests/GreenhouseServiceTest.php | 18 ++++++++++++++++++ tests/Services/JobApiServiceTest.php | 6 ------ 8 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/Clients/CurlClient.php b/src/Clients/CurlClient.php index 6bf2f70..984fdf4 100644 --- a/src/Clients/CurlClient.php +++ b/src/Clients/CurlClient.php @@ -7,7 +7,7 @@ use Greenhouse\GreenhouseToolsPhp\Clients\Exceptions\GreenhouseAPIResponseException; /** - * Client to wrap the Guzzle package. + * Client to wrap libcurl */ class CurlClient implements ApiClientInterface { diff --git a/src/GreenhouseService.php b/src/GreenhouseService.php index 187e867..c8ea294 100644 --- a/src/GreenhouseService.php +++ b/src/GreenhouseService.php @@ -11,8 +11,8 @@ class GreenhouseService public function __construct($options=array()) { - $this->_apiKey = $options['apiKey']; - $this->_boardToken = $options['boardToken']; + $this->_apiKey = isset($options['apiKey']) ? $options['apiKey'] : null; + $this->_boardToken = isset($options['boardToken']) ? $options['boardToken'] : null; } /** diff --git a/src/Services/ApiService.php b/src/Services/ApiService.php index 305161f..8d44622 100644 --- a/src/Services/ApiService.php +++ b/src/Services/ApiService.php @@ -12,6 +12,7 @@ class ApiService protected $_apiKey; const APPLICATION_URL = 'https://api.greenhouse.io/v1/applications/'; + const API_V1_URL = 'https://api.greenhouse.io/v1/'; public function setClient($apiClient) { @@ -28,7 +29,7 @@ public function setClient($apiClient) */ public static function jobBoardBaseUrl($clientToken) { - return "https://api.greenhouse.io/v1/boards/{$clientToken}/embed/"; + return self::API_V1_URL . "boards/{$clientToken}/embed/"; } /** diff --git a/src/Services/ApplicationService.php b/src/Services/ApplicationService.php index d429830..089a748 100644 --- a/src/Services/ApplicationService.php +++ b/src/Services/ApplicationService.php @@ -83,8 +83,6 @@ public function postApplication($postVars=array()) $headers = array('Authorization' => $this->_authorizationHeader); $this->_apiClient->post($postParams, $headers); - - return true; } /** @@ -124,15 +122,8 @@ public function validateRequiredFields($postVars) */ public function hasRequiredValue($postVars, $keys) { - // If there is only one key (most of the time) short-circuit the array. - if (sizeof($keys) == 1) { - return array_key_exists($keys[0], $postVars) && $postVars[$keys[0]] !== ''; - - // This is the O(N) case. Happens rarely. - } else { - foreach ($keys as $key) { - if (array_key_exists($key, $postVars) && $postVars[$key] !== '') return true; - } + foreach ($keys as $key) { + if (array_key_exists($key, $postVars) && $postVars[$key] !== '') return true; } return false; diff --git a/src/Services/JobApiService.php b/src/Services/JobApiService.php index be622a8..406b606 100644 --- a/src/Services/JobApiService.php +++ b/src/Services/JobApiService.php @@ -21,11 +21,8 @@ class JobApiService extends ApiService * * @param string $clientToken As above */ - public function __construct($clientToken="") + public function __construct($clientToken) { - if (empty($clientToken)) { - throw new GreenhouseServiceException('JobApiService requires a job board token.'); - } $this->_clientToken = $clientToken; $client = new GuzzleClient(array('base_uri' => self::jobBoardBaseUrl($clientToken))); $this->setClient($client); diff --git a/tests/Bootstrap.php b/tests/Bootstrap.php index 6157336..0e32baf 100644 --- a/tests/Bootstrap.php +++ b/tests/Bootstrap.php @@ -3,7 +3,7 @@ error_reporting(E_ALL | E_STRICT); ini_set('display_errors', 1); -$root = realpath(dirname(dirname(__FILE__))); +$root = realpath(__DIR__ . '/..'); $library = "$root/src"; $tests = "$root/tests"; $path = array($library, $tests, get_include_path()); diff --git a/tests/GreenhouseServiceTest.php b/tests/GreenhouseServiceTest.php index b1a6e9c..3f55779 100644 --- a/tests/GreenhouseServiceTest.php +++ b/tests/GreenhouseServiceTest.php @@ -17,6 +17,24 @@ public function setUp() )); } + public function testConstructWithNoBoardToken() + { + $service = new GreenhouseService(array('apiKey' => 'test_key')); + $this->assertInstanceOf( + '\Greenhouse\GreenhouseToolsPhp\GreenhouseService', + $service + ); + } + + public function testConstructWithNoApiKey() + { + $service = new GreenhouseService(array('boardToken' => 'test_token')); + $this->assertInstanceOf( + '\Greenhouse\GreenhouseToolsPhp\GreenhouseService', + $service + ); + } + public function testGetJobBoardService() { $service = $this->greenhouseService->getJobBoardService(); diff --git a/tests/Services/JobApiServiceTest.php b/tests/Services/JobApiServiceTest.php index 0fcf61f..a894b1e 100644 --- a/tests/Services/JobApiServiceTest.php +++ b/tests/Services/JobApiServiceTest.php @@ -13,12 +13,6 @@ public function setUp() $this->baseUrl = JobApiService::jobBoardBaseUrl('greenhouse'); } - public function testConstructorRequiresToken() - { - $this->expectException('\Greenhouse\GreenhouseToolsPhp\Services\Exceptions\GreenhouseServiceException'); - $service = new JobApiService(); - } - public function testGetContentQueryTrue() { $this->assertEquals( From 605dc42e83e4354e78fe861590c56639c0ae9cdb Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Tue, 29 Mar 2016 15:18:14 -0400 Subject: [PATCH 44/47] Add the previous exception to the Greenhouse Exception --- src/Clients/GuzzleClient.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Clients/GuzzleClient.php b/src/Clients/GuzzleClient.php index 658c161..33cd394 100644 --- a/src/Clients/GuzzleClient.php +++ b/src/Clients/GuzzleClient.php @@ -41,7 +41,7 @@ public function get($url="") try { $guzzleResponse = $this->_client->request('GET', $url); } catch (RequestException $e) { - throw new GreenhouseAPIResponseException($e->getMessage()); + throw new GreenhouseAPIResponseException($e->getMessage(), 0, $e); } /** @@ -69,7 +69,7 @@ public function post(Array $postVars, Array $headers, $url=null) array('multipart' => $postVars, 'headers' => $headers) ); } catch (RequestException $e) { - throw new GreenhouseAPIResponseException($e->getMessage()); + throw new GreenhouseAPIResponseException($e->getMessage(), 0, $e); } return (string) $guzzleResponse->getBody(); From fbf0a764a107cfc49291d9284a7c38014b8f44db Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Tue, 29 Mar 2016 15:56:05 -0400 Subject: [PATCH 45/47] Add URL constants. --- src/GreenhouseService.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/GreenhouseService.php b/src/GreenhouseService.php index c8ea294..78fa4f7 100644 --- a/src/GreenhouseService.php +++ b/src/GreenhouseService.php @@ -3,6 +3,7 @@ namespace Greenhouse\GreenhouseToolsPhp; use Greenhouse\GreenhouseToolsPhp\Clients\GuzzleClient; +use Greenhouse\GreenhouseToolsPhp\Services\ApiService; class GreenhouseService { @@ -23,7 +24,7 @@ public function getJobApiService() { $apiService = new \Greenhouse\GreenhouseToolsPhp\Services\JobApiService($this->_boardToken); $apiClient = new GuzzleClient(array( - 'base_uri' => "https://api.greenhouse.io/v1/boards/{$this->_boardToken}/embed/" + 'base_uri' => ApiService::jobBoardBaseUrl($this->_boardToken) )); $apiService->setClient($apiClient); @@ -38,7 +39,7 @@ public function getApplicationApiService() { $applicationService = new \Greenhouse\GreenhouseToolsPhp\Services\ApplicationService($this->_apiKey, $this->_boardToken); $apiClient = new GuzzleClient(array( - 'base_uri' => 'https://api.greenhouse.io/v1/applications/' + 'base_uri' => ApiService::APPLICATION_URL )); $applicationService->setClient($apiClient); From 3fc5eedff417b64330ecd4d41edaebfca9d23126 Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Tue, 29 Mar 2016 16:02:42 -0400 Subject: [PATCH 46/47] Use https urls. Use a constant for boards url. --- src/Services/JobBoardService.php | 6 ++++-- tests/Services/JobBoardServiceTest.php | 10 +++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Services/JobBoardService.php b/src/Services/JobBoardService.php index 412cf73..975a83e 100644 --- a/src/Services/JobBoardService.php +++ b/src/Services/JobBoardService.php @@ -4,6 +4,8 @@ class JobBoardService { + const BOARDS_URL = 'https://boards.greenhouse.io/'; + public function __construct($clientToken) { $this->_clientToken = $clientToken; @@ -50,7 +52,7 @@ public function embedGreenhouseJobBoard() */ public function linkToGreenhouseJobBoard($linkText="Open Positions") { - return "$linkText"; + return "$linkText"; } /** @@ -64,6 +66,6 @@ public function linkToGreenhouseJobApplication($jobId, $linkText="Apply to this { $ghSrc = empty($sourceToken) ? '' : "?gh_src={$sourceToken}"; - return "{$linkText}"; + return "{$linkText}"; } } \ No newline at end of file diff --git a/tests/Services/JobBoardServiceTest.php b/tests/Services/JobBoardServiceTest.php index 6acb59d..46a87bb 100644 --- a/tests/Services/JobBoardServiceTest.php +++ b/tests/Services/JobBoardServiceTest.php @@ -43,7 +43,7 @@ public function testLinkToGreenhouseJobBoardWithDefault() { $this->assertEquals( $this->jobBoardService->linkToGreenhouseJobBoard(), - "Open Positions" + "Open Positions" ); } @@ -51,7 +51,7 @@ public function testLinkToGreenhouseJobBoardWithArgument() { $this->assertEquals( $this->jobBoardService->linkToGreenhouseJobBoard('Link to jobs!'), - "Link to jobs!" + "Link to jobs!" ); } @@ -59,7 +59,7 @@ public function testLinkToGreenhouseJobApplicationWithDefault() { $this->assertEquals( $this->jobBoardService->linkToGreenhouseJobApplication(12345), - "" . + "" . 'Apply to this job' ); } @@ -68,7 +68,7 @@ public function testLinkToGreenhouseJobApplicationWithArgument() { $this->assertEquals( $this->jobBoardService->linkToGreenhouseJobApplication(12345, 'Some work'), - "" . + "" . 'Some work' ); } @@ -77,7 +77,7 @@ public function testLinkToGreenhouseJobApplicationWithToken() { $this->assertEquals( $this->jobBoardService->linkToGreenhouseJobApplication(12345, 'Some work', 'abcde'), - "" . + "" . 'Some work' ); } From 8490ac08b5c141bb26def1d7b2ec050261c076e0 Mon Sep 17 00:00:00 2001 From: Tom Phillips Date: Tue, 29 Mar 2016 16:09:20 -0400 Subject: [PATCH 47/47] Remove unused client. --- src/Clients/CurlClient.php | 101 ------------------------------- tests/Clients/CurlClientTest.php | 65 -------------------- 2 files changed, 166 deletions(-) delete mode 100644 src/Clients/CurlClient.php delete mode 100644 tests/Clients/CurlClientTest.php diff --git a/src/Clients/CurlClient.php b/src/Clients/CurlClient.php deleted file mode 100644 index 984fdf4..0000000 --- a/src/Clients/CurlClient.php +++ /dev/null @@ -1,101 +0,0 @@ -_client = curl_init(); - curl_setopt($this->_client, CURLOPT_RETURNTRANSFER, 1); - } - - /** - * Fetch the URL. - * - * @params string $url A full URL to get. - * @return string The Raw JSON response from Greenhouse - * @throws GreenhouseAPIResponseException if the get request fails - */ - public function get($url) - { - curl_setopt($this->_client, CURLOPT_URL, $url); - curl_setopt($this->_client, CURLOPT_POST, 0); - return $this->_execute(); - } - - /** - * Post to the application endpoint. - * - * @params Array $postVars A guzzle compliant multipart array of post parameters. - * @params Array $headers This should only contain the Authorization header. - * @params string $url This can be left blank. Url is set in the constructor. - * @return string - * @throws GreenhouseAPIResponseException for non-200 responses - * @throws GreenhouseAPIClientException if $postParameters contains an array(), indicating multiselect. - */ - public function post(Array $postVars, Array $headers, $url) - { - // Use this to check for multi-selects. - $this->formatPostParameters($postVars); - - curl_setopt($this->_client, CURLOPT_URL, $url); - curl_setopt($this->_client, CURLOPT_POST, 1); - curl_setopt($this->_client, CURLOPT_POSTFIELDS, $postVars); - curl_setopt($this->_client, CURLOPT_HTTPHEADER, $headers); - - return $this->_execute(); - } - - private function _execute() - { - $response = curl_exec($this->_client); - $httpCode = curl_getinfo($this->_client, CURLINFO_HTTP_CODE); - if ($httpCode < 200 || $httpCode >= 300) { - throw new GreenhouseApiResponseException("$httpCode -- $response"); - } - return $response; - } - - /** - * Greenhouse's POST format matches CURL. However, it will reject any attempt to POST an array because - * libcurl has a bug that prevents submitting it correctly to non-PHP applications. For more details, see - * the PHP bug here: https://bugs.php.net/bug.php?id=51634 - * This will check for multiselects, throw an exception if one exists, or just reflect the parameters. - * - * @params Array $postParameters - * @return Array - * @throws GreenhouseAPIClientException if $postParameters contains an array(), indicating multiselect. - */ - public function formatPostParameters(Array $postParameters=array()) - { - foreach ($postParameters as $key => $value) { - if (is_array($value)) { - throw new GreenhouseAPIClientException('CurlClient does not support array post parameters.'); - } - } - - return $postParameters; - } - - public function getClient() - { - return $this->_client; - } -} \ No newline at end of file diff --git a/tests/Clients/CurlClientTest.php b/tests/Clients/CurlClientTest.php deleted file mode 100644 index ad71848..0000000 --- a/tests/Clients/CurlClientTest.php +++ /dev/null @@ -1,65 +0,0 @@ -client = new CurlClient(); - $this->resumePath = realpath(dirname(__FILE__)) . '/../files/documents/test_resume.docx'; - } - - public function testGuzzleInitialize() - { - $client = new CurlClient(array('base_uri' => 'http://www.example.com')); - $this->assertInstanceOf( - '\Greenhouse\GreenhouseToolsPhp\Clients\CurlClient', - $client - ); - } - - public function testGetException() - { - $errorUrl = 'https://api.greenhouse.io/v1/boards/exception_co/embed/'; - $client = new CurlClient(); - $this->expectException('\Greenhouse\GreenhouseToolsPhp\Clients\Exceptions\GreenhouseAPIResponseException'); - $response = $client->get($errorUrl); - } - - public function testFormatPostParameters() - { - $postVars = array( - 'first_name' => 'Hiram', - 'last_name' => 'Abiff', - ); - - $this->assertEquals($postVars, $this->client->formatPostParameters($postVars)); - } - - public function testFormatPostParametersException() - { - $postVars = array( - 'first_name' => 'Hiram', - 'last_name' => 'Abiff', - 'talents' => array('building', 'things', 'and', 'stuff') - ); - - $this->expectException('\Greenhouse\GreenhouseToolsPhp\Clients\Exceptions\GreenhouseAPIClientException'); - $this->client->formatPostParameters($postVars); - } - - public function testPostThrowsExceptionsWithArrayValues() - { - $postVars = array( - 'first_name' => 'Hiram', - 'last_name' => 'Abiff', - 'talents' => array('building', 'things', 'and', 'stuff') - ); - - $this->expectException('\Greenhouse\GreenhouseToolsPhp\Clients\Exceptions\GreenhouseAPIClientException'); - $this->client->post($postVars, array(), 'http://www.example.com'); - } -} \ No newline at end of file