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/.travis.yml b/.travis.yml new file mode 100644 index 0000000..fe31d15 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ +language: php +php: + - '5.6' + - '7.0' + - hhvm +script: phpunit +before_install: composer install --dev +branches: + only: + - master +sudo: false diff --git a/README.md b/README.md index 6ab1e99..84bfcf8 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,113 @@ -# 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 [(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' => '' +]); + +?> +``` + +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 +$greenhouseService->linkToGreenhouseJobBoard(); + +// Link to a Greenhouse hosted job application +$greenhouseService->linkToGreenhouseJobApplication(12345, 'Apply to this job!', 'source_token'); + +// Embed a Greenhouse iframe in your page +$greenhouseService->embedGreenhouseJobBoard(); + +?> +``` +# The Job API 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 diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..0ebb5b5 --- /dev/null +++ b/composer.json @@ -0,0 +1,26 @@ +{ + "name": "grnhse/greenhouse-tools-php", + "type": "library", + "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-tools-php", + "authors": [ + { + "name": "Tom Phillips", + "email": "tphillips@greenhouse.io" + } + ], + "require": { + "php": ">=5.6", + "guzzlehttp/guzzle": "~6.0" + }, + "require-dev": { + "phpunit/phpunit": "~5.0" + }, + "autoload": { + "psr-4": { "Greenhouse\\GreenhouseToolsPhp\\": "src/" } + }, + "autoload-dev": { + "psr-4": { "Greenhouse\\GreenhouseToolsPhp\\Tests\\": "tests/" } + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..6e96d8d --- /dev/null +++ b/composer.lock @@ -0,0 +1,1334 @@ +{ + "_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": "cef6a2c0634f155a93b0204c87a33d34", + "content-hash": "ccf50ee845f688fa406c14854281bbde", + "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.2.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "d094e337976dff9d8e2424e8485872194e768662" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d094e337976dff9d8e2424e8485872194e768662", + "reference": "d094e337976dff9d8e2424e8485872194e768662", + "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.2-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": "2016-03-21 20:02:09" + }, + { + "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": "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", + "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": "3.3.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "fe33716763b604ade4cb442c0794f5bd5ad73004" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/fe33716763b604ade4cb442c0794f5bd5ad73004", + "reference": "fe33716763b604ade4cb442c0794f5bd5ad73004", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.2", + "phpunit/php-token-stream": "^1.4.2", + "sebastian/code-unit-reverse-lookup": "~1.0", + "sebastian/environment": "^1.3.2", + "sebastian/version": "~1.0|~2.0" + }, + "require-dev": { + "ext-xdebug": ">=2.1.4", + "phpunit/phpunit": "~5" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.2.1", + "ext-xmlwriter": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.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": "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": "2016-03-03 08:49:08" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" + }, + "dist": { + "type": "zip", + "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": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "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": "2015-06-21 13:08:43" + }, + { + "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": "5.2.12", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "6f0948bab32270352f97d1913d82a49338dcb0da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6f0948bab32270352f97d1913d82a49338dcb0da", + "reference": "6f0948bab32270352f97d1913d82a49338dcb0da", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "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.6", + "phpunit/phpunit-mock-objects": ">=3.0.5", + "sebastian/comparator": "~1.1", + "sebastian/diff": "~1.2", + "sebastian/environment": "~1.3", + "sebastian/exporter": "~1.2", + "sebastian/global-state": "~1.0", + "sebastian/resource-operations": "~1.0", + "sebastian/version": "~1.0|~2.0", + "symfony/yaml": "~2.1|~3.0" + }, + "suggest": { + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.2.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": "2016-03-15 05:59:58" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "49bc700750196c04dd6bc2c4c99cb632b893836b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/49bc700750196c04dd6bc2c4c99cb632b893836b", + "reference": "49bc700750196c04dd6bc2c4c99cb632b893836b", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": ">=5.6", + "phpunit/php-text-template": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~5" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.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-12-08 08:47:06" + }, + { + "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/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", + "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/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": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5" + }, + "dist": { + "type": "zip", + "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/" + ] + }, + "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": "2016-02-04 12:56:52" + }, + { + "name": "symfony/yaml", + "version": "v3.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "b5ba64cd67ecd6887f63868fa781ca094bd1377c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/b5ba64cd67ecd6887f63868fa781ca094bd1377c", + "reference": "b5ba64cd67ecd6887f63868fa781ca094bd1377c", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-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 15:16:06" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.6" + }, + "platform-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/src/Clients/ApiClientInterface.php b/src/Clients/ApiClientInterface.php new file mode 100644 index 0000000..b709933 --- /dev/null +++ b/src/Clients/ApiClientInterface.php @@ -0,0 +1,43 @@ +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/Exceptions/GreenhouseAPIClientException.php b/src/Clients/Exceptions/GreenhouseAPIClientException.php new file mode 100644 index 0000000..e8d0dbf --- /dev/null +++ b/src/Clients/Exceptions/GreenhouseAPIClientException.php @@ -0,0 +1,7 @@ +_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 GreenhouseAPIResponseException if the get request fails + */ + public function get($url="") + { + try { + $guzzleResponse = $this->_client->request('GET', $url); + } catch (RequestException $e) { + throw new GreenhouseAPIResponseException($e->getMessage(), 0, $e); + } + + /** + * 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(); + } + + /** + * 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(), 0, $e); + } + + 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() + { + return $this->_client; + } +} \ No newline at end of file diff --git a/src/Exceptions/GreenhouseException.php b/src/Exceptions/GreenhouseException.php new file mode 100644 index 0000000..ce71018 --- /dev/null +++ b/src/Exceptions/GreenhouseException.php @@ -0,0 +1,5 @@ +_apiKey = isset($options['apiKey']) ? $options['apiKey'] : null; + $this->_boardToken = isset($options['boardToken']) ? $options['boardToken'] : null; + } + + /** + * 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\GreenhouseToolsPhp\Services\JobApiService($this->_boardToken); + $apiClient = new GuzzleClient(array( + 'base_uri' => ApiService::jobBoardBaseUrl($this->_boardToken) + )); + $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\GreenhouseToolsPhp\Services\ApplicationService($this->_apiKey, $this->_boardToken); + $apiClient = new GuzzleClient(array( + 'base_uri' => ApiService::APPLICATION_URL + )); + $applicationService->setClient($apiClient); + + return $applicationService; + } + + public function getJobBoardService() + { + return new \Greenhouse\GreenhouseToolsPhp\Services\JobBoardService($this->_boardToken); + } +} \ No newline at end of file diff --git a/src/Services/ApiService.php b/src/Services/ApiService.php new file mode 100644 index 0000000..8d44622 --- /dev/null +++ b/src/Services/ApiService.php @@ -0,0 +1,75 @@ +_apiClient = $apiClient; + } + + /** + * 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 self::API_V1_URL . "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)) { + 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)) { + if (empty($this->_apiKey)) throw new GreenhouseServiceException('No key provided to encode.'); + $apiKey = $this->_apiKey; + } + $key = rtrim($apiKey, ':') . ':'; + + return 'Basic ' . base64_encode($key); + } +} diff --git a/src/Services/ApplicationService.php b/src/Services/ApplicationService.php new file mode 100644 index 0000000..089a748 --- /dev/null +++ b/src/Services/ApplicationService.php @@ -0,0 +1,170 @@ +_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); + + $this->_apiClient->post($postParams, $headers); + } + + /** + * 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 (!empty($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) + { + 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/src/Services/Exceptions/GreenhouseApplicationException.php b/src/Services/Exceptions/GreenhouseApplicationException.php new file mode 100644 index 0000000..5ec20ce --- /dev/null +++ b/src/Services/Exceptions/GreenhouseApplicationException.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/src/Services/JobBoardService.php b/src/Services/JobBoardService.php new file mode 100644 index 0000000..975a83e --- /dev/null +++ b/src/Services/JobBoardService.php @@ -0,0 +1,71 @@ +_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", $sourceToken="") + { + $ghSrc = empty($sourceToken) ? '' : "?gh_src={$sourceToken}"; + + return "{$linkText}"; + } +} \ No newline at end of file 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 @@ +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\GreenhouseToolsPhp\Clients\GuzzleClient', + $client + ); + + $this->assertInstanceOf('\GuzzleHttp\Client', $client->getClient()); + } + + public function testGetException() + { + $errorUrl = 'https://api.greenhouse.io/v1/boards/exception_co/embed/'; + $client = new GuzzleClient(array('base_uri' => $errorUrl)); + $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/GreenhouseServiceTest.php b/tests/GreenhouseServiceTest.php new file mode 100644 index 0000000..3f55779 --- /dev/null +++ b/tests/GreenhouseServiceTest.php @@ -0,0 +1,77 @@ +apiKey = 'testapikey'; + $this->boardToken = 'test_token'; + $this->greenhouseService = new GreenhouseService(array( + 'apiKey' => $this->apiKey, + 'boardToken'=> $this->boardToken + )); + } + + 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(); + $this->assertInstanceOf( + '\Greenhouse\GreenhouseToolsPhp\Services\JobBoardService', + $service + ); + $this->assertContains($this->boardToken, $service->scriptTag()); + } + + public function testGetJobApiService() + { + $service = $this->greenhouseService->getJobApiService(); + $this->assertInstanceOf( + '\Greenhouse\GreenhouseToolsPhp\Services\JobApiService', + $service + ); + + $this->assertEquals( + 'https://api.greenhouse.io/v1/boards/test_token/embed/', + $service->getJobBoardBaseUrl() + ); + + $this->assertInstanceOf('\Greenhouse\GreenhouseToolsPhp\Clients\GuzzleClient', $service->getClient()); + } + + public function testGetApplicationService() + { + $service = $this->greenhouseService->getApplicationApiService(); + $this->assertInstanceOf( + '\Greenhouse\GreenhouseToolsPhp\Services\ApplicationService', + $service + ); + + $baseUrl = ApiService::jobBoardBaseUrl($this->boardToken); + $authHeader = 'Basic ' . base64_encode($this->apiKey . ':'); + $this->assertEquals($baseUrl, $service->getJobBoardBaseUrl()); + $this->assertEquals($authHeader, $service->getAuthorizationHeader()); + } +} 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(); + } +} 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)); + } +} + + diff --git a/tests/Services/JobApiServiceTest.php b/tests/Services/JobApiServiceTest.php new file mode 100644 index 0000000..a894b1e --- /dev/null +++ b/tests/Services/JobApiServiceTest.php @@ -0,0 +1,90 @@ +jobApiService = new JobApiService('greenhouse'); + $this->errorService = new JobApiService('exception_co'); + $this->baseUrl = JobApiService::jobBoardBaseUrl('greenhouse'); + } + + 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\GreenhouseToolsPhp\Clients\Exceptions\GreenhouseAPIResponseException'); + $this->errorService->getOffices(); + } + + public function testGetOfficeException() + { + $this->expectException('\Greenhouse\GreenhouseToolsPhp\Clients\Exceptions\GreenhouseAPIResponseException'); + $this->errorService->getOffice(12345); + } + + public function testGetDepartmentsException() + { + $this->expectException('\Greenhouse\GreenhouseToolsPhp\Clients\Exceptions\GreenhouseAPIResponseException'); + $this->errorService->getDepartments(); + } + + public function testGetDepartmentException() + { + $this->expectException('\Greenhouse\GreenhouseToolsPhp\Clients\Exceptions\GreenhouseAPIResponseException'); + $this->errorService->getDepartment(12345); + } + + public function testGetBoardException() + { + $this->expectException('\Greenhouse\GreenhouseToolsPhp\Clients\Exceptions\GreenhouseAPIResponseException'); + $this->errorService->getBoard(); + } + + public function testGetJobsException() + { + $this->expectException('\Greenhouse\GreenhouseToolsPhp\Clients\Exceptions\GreenhouseAPIResponseException'); + $this->errorService->getJobs(); + } + + public function testGetJobException() + { + $this->expectException('\Greenhouse\GreenhouseToolsPhp\Clients\Exceptions\GreenhouseAPIResponseException'); + $this->errorService->getJob(1); + } + +} \ No newline at end of file diff --git a/tests/Services/JobBoardServiceTest.php b/tests/Services/JobBoardServiceTest.php new file mode 100644 index 0000000..46a87bb --- /dev/null +++ b/tests/Services/JobBoardServiceTest.php @@ -0,0 +1,84 @@ +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' + ); + } + + public function testLinkToGreenhouseJobApplicationWithToken() + { + $this->assertEquals( + $this->jobBoardService->linkToGreenhouseJobApplication(12345, 'Some work', 'abcde'), + "" . + 'Some work' + ); + } +} \ No newline at end of file diff --git a/tests/Tools/JsonHelperTest.php b/tests/Tools/JsonHelperTest.php new file mode 100644 index 0000000..1a6f7c7 --- /dev/null +++ b/tests/Tools/JsonHelperTest.php @@ -0,0 +1,33 @@ +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/documents/test_cover_letter.docx b/tests/files/documents/test_cover_letter.docx new file mode 100644 index 0000000..046cd79 Binary files /dev/null and b/tests/files/documents/test_cover_letter.docx differ diff --git a/tests/files/documents/test_resume.docx b/tests/files/documents/test_resume.docx new file mode 100644 index 0000000..1701a82 Binary files /dev/null and b/tests/files/documents/test_resume.docx differ 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