diff --git a/app/Services/Service.php b/app/Config/Services/Service.php similarity index 91% rename from app/Services/Service.php rename to app/Config/Services/Service.php index c43e569..72c2b7f 100644 --- a/app/Services/Service.php +++ b/app/Config/Services/Service.php @@ -1,38 +1,38 @@ <?php -namespace Nasqueron\Notifications\Services; +namespace Nasqueron\Notifications\Config\Services; class Service { /** * @var string */ public $gate; /** * @var string */ public $door; /** * @var string */ public $instance; /** * @var string */ public $secret; /** * Gets instance name * * @return string The instance name or "ø" if omitted */ public function getInstanceName () : string { if (!isset($this->instance)) { return "ø"; } return $this->instance; } } diff --git a/app/Services/Services.php b/app/Config/Services/Services.php similarity index 98% rename from app/Services/Services.php rename to app/Config/Services/Services.php index f93ad45..47716f0 100644 --- a/app/Services/Services.php +++ b/app/Config/Services/Services.php @@ -1,104 +1,104 @@ <?php -namespace Nasqueron\Notifications\Services; +namespace Nasqueron\Notifications\Config\Services; use Storage; class Services { /// /// Properties /// /** * @var Service[] */ public $services = []; /// /// Constructors /// /** * Initializes a new instance of the Services class deserializing a JSON file. * * @param string $file The JSON file to deserialize * @return Services The deserialized instance */ public static function loadFromJson (string $file) : Services { $data = json_decode(Storage::get($file)); $mapper = new \JsonMapper(); return $mapper->map($data, new self()); } /// /// Methods to get a list of services /// /** * Gets the services found in credentials.json configuration file. * * @return Service[] */ public function get () { return $this->services; } /** * Gets all the services for a specific gate. * * @param string $gate The gate (e.g. GitHub) * @return Service[] */ public function getForGate (string $gate) : array { $services = []; foreach ($this->services as $service) { if ($service->gate === $gate) { $services[] = $service; } } return $services; } /// /// Methods to find a service matching criteria /// /** * Gets the service for a specific gate and door * * @param string $gate The gate (e.g. GitHub) * @param string $door The door (e.g. Nasqueron) * @return Service|null The service information is found; otherwise, null. */ public function findServiceByDoor (string $gate, string $door) : ?Service { foreach ($this->services as $service) { if ($service->gate === $gate && $service->door === $door) { return $service; } } return null; } /** * Finds a service for a specific gate, property and value * * @param string $gate The gate (e.g. Phabricator) * @param string $property The property to check (e.g. instance) * @param mixed $value The property value to find (e.g. 'http://devcentral.nasqueron.org') * @return Service|null The service information is found; otherwise, null. */ public function findServiceByProperty (string $gate, string $property, $value) : ?Service { foreach ($this->services as $service) { if ($service->gate === $gate && $service->$property === $value) { return $service; } } return null; } } diff --git a/app/Console/Commands/ConfigShow.php b/app/Console/Commands/ConfigShow.php index 626898c..4a2aac7 100644 --- a/app/Console/Commands/ConfigShow.php +++ b/app/Console/Commands/ConfigShow.php @@ -1,131 +1,131 @@ <?php namespace Nasqueron\Notifications\Console\Commands; use Illuminate\Console\Command; use Nasqueron\Notifications\Config\Features; -use Nasqueron\Notifications\Services\Service; +use Nasqueron\Notifications\Config\Services\Service; use Config; use ProjectsMap; use Services; class ConfigShow extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'config:show'; /** * The console command description. * * @var string */ protected $description = 'Show notifications center configuration'; /** * Creates a new command instance. */ public function __construct () { parent::__construct(); } /// /// Prepare information tables /// /** * Gets the services (defined in credentials.json) as table rows. * - * @return \Nasqueron\Notifications\Services\Service[] + * @return \Nasqueron\Notifications\Config\Services\Service[] */ protected function getServicesTableRows () : array { $rows = []; foreach (Services::get() as $service) { $rows[] = [ $service->gate, $service->door, $service->getInstanceName(), $this->getServiveStatus($service) ]; } return $rows; } /** * Gets service status. * - * @param \Nasqueron\Notifications\Services\Service $service The service to check + * @param \Nasqueron\Notifications\Config\Services\Service $service The service to check * @return string A description of the issue if something is wrong; otherwise, "✓". */ protected function getServiveStatus (Service $service) : string { if ($service->gate === 'Phabricator') { // Ensure the projects map is cached $map = \ProjectsMap::fetch($service->door); if (!$map->isCached()) { return "Projects map not cached."; } } return "✓"; } /** * Gets features as table rows * * @return array */ protected function getFeaturesTableRows () : array { $rows = []; foreach (Features::getAll() as $key => $value) { if ($value) { $checkMark = '✓'; } else { $checkMark = ''; } $rows[] = [$key, $checkMark]; } return $rows; } /// /// Handle the command /// /** * Executes the console command. */ public function handle () : void { $this->printGates(); $this->printFeatures(); $this->printServices(); } protected final function printGates () : void { $this->info("Gates:\n"); foreach (Config::get('gate.controllers') as $gate) { $this->line('- ' . $gate); } } protected final function printFeatures () : void { $this->info("\nFeatures:\n"); $this->table( ['Feature', 'Enabled'], $this->getFeaturesTableRows() ); } protected final function printServices () : void { $this->info("\nServices declared in credentials:\n"); $this->table( ['Gate', 'Door', 'Instance', 'Status'], $this->getServicesTableRows() ); } } diff --git a/app/Http/Controllers/Gate/GateController.php b/app/Http/Controllers/Gate/GateController.php index ab8aae7..a25d29d 100644 --- a/app/Http/Controllers/Gate/GateController.php +++ b/app/Http/Controllers/Gate/GateController.php @@ -1,120 +1,120 @@ <?php namespace Nasqueron\Notifications\Http\Controllers\Gate; use Nasqueron\Notifications\Config\Features; +use Nasqueron\Notifications\Config\Services\Service; use Nasqueron\Notifications\Http\Controllers\Controller; -use Nasqueron\Notifications\Services\Service; use Symfony\Component\HttpFoundation\Response as BaseResponse; use Illuminate\View\View; use App; use Log; use Report; use Response; use Services; /** * Represents a controller handling an entry-point for API payloads */ class GateController extends Controller { /// /// Private members /// /** * @var string */ protected $door; /// /// Requests /// /** * Handles GET requests */ public function onGet () : View { // Virtually all the push APIs will send they payloads // using a POST request, so we can provide a sensible // default GET error message. return view('gate/ispostonly'); } /** * Logs the request */ protected function logRequest (array $extraContextualData = []) : void { Log::info('[Gate] New payload.', [ 'service' => static::SERVICE_NAME, 'door' => $this->door, ] + $extraContextualData); } /// /// Reports /// /** * Initializes the report and registers it */ protected function initializeReport () : void { if (Features::isEnabled('ActionsReport')) { Report::attachToGate(static::SERVICE_NAME, $this->door); } } /** * Renders the report * * @return \Symfony\Component\HttpFoundation\Response */ protected function renderReport () : BaseResponse { if (!Features::isEnabled('ActionsReport')) { return response(""); } $report = App::make('report'); $statusCode = $report->containsError() ? 503 : 200; return Response::json($report) ->setStatusCode($statusCode); } /// /// Credentials /// /** * Gets service credentials for this gate and door * - * @return Nasqueron\Notifications\Services\Service|null The service information is found; otherwise, null. + * @return \Nasqueron\Notifications\Config\Services\Service|null The service information is found; otherwise, null. */ public function getService () : ?Service { return Services::findServiceByDoor(static::SERVICE_NAME, $this->door); } /** * Checks if a registered service exists for this service and door. */ protected function doesServiceExist () : bool { return $this->getService() !== null; } /** * Gets secret for this service and door. * * @return string the secret, or if unknown, an empty string */ protected function getSecret () : string { $service= $this->getService(); if ($service !== null) { return $service->secret; } return ""; } } diff --git a/app/Providers/ServicesServiceProvider.php b/app/Providers/ServicesServiceProvider.php index d4d8953..00c911d 100644 --- a/app/Providers/ServicesServiceProvider.php +++ b/app/Providers/ServicesServiceProvider.php @@ -1,26 +1,26 @@ <?php namespace Nasqueron\Notifications\Providers; use Illuminate\Contracts\Foundation\Application; use Illuminate\Support\ServiceProvider; -use Nasqueron\Notifications\Services\Services; +use Nasqueron\Notifications\Config\Services\Services; class ServicesServiceProvider extends ServiceProvider { /** * Registers the application services. * * @return void */ public function register() { $this->app->singleton('services', function (Application $app) { $path = config('services.gate.credentials'); if (strlen($path) > 0 && $app->make('filesystem')->has($path)) { return Services::loadFromJson($path); } return new Services; }); } } diff --git a/tests/Console/Commands/ConfigShowTest.php b/tests/Console/Commands/ConfigShowTest.php index 59507dc..6f6291b 100644 --- a/tests/Console/Commands/ConfigShowTest.php +++ b/tests/Console/Commands/ConfigShowTest.php @@ -1,97 +1,97 @@ <?php namespace Nasqueron\Notifications\Tests\Console\Commands; use Nasqueron\Notifications\Config\Features; -use Nasqueron\Notifications\Services\Service; +use Nasqueron\Notifications\Config\Services\Service; use Mockery; class ConfigShowTest extends TestCase { /** * @var string */ protected $class = 'Nasqueron\Notifications\Console\Commands\ConfigShow'; /** * Nasqueron\Notifications\Services\Services */ private $servicesMock; public function setUp () { parent::setUp(); $this->servicesMock = $this->mockServices(); } public function testRegularExecute () { //Our command calls Services::get() $this->servicesMock->shouldReceive('get')->once()->andReturn([]); $this->tester->execute(['command' => $this->command->getName()]); $this->assertRegexp('/Gates/', $this->tester->getDisplay()); $this->assertRegexp('/Features/', $this->tester->getDisplay()); $this->assertRegexp('/Services declared/', $this->tester->getDisplay()); } public function testRegularExecuteWithService () { $service = $this->mockService(); $this->servicesMock ->shouldReceive('get') ->once() ->andReturn([$service]); $this->tester->execute(['command' => $this->command->getName()]); $this->assertRegexp('/Storm/', $this->tester->getDisplay()); } public function testRegularExecuteWithPhabricatorService () { $this->mockPhabricatorAPIForProjectsMap(); $service = $this->mockService('Phabricator'); $this->servicesMock ->shouldReceive('get') ->once() ->andReturn([$service]); $this->servicesMock ->shouldReceive('findServiceByProperty'); $this->tester->execute(['command' => $this->command->getName()]); $this->assertRegexp('/Phabricator.*Projects map not cached./', $this->tester->getDisplay()); } protected function mockProjectsMap () { $mock = Mockery::mock('Nasqueron\Notifications\Phabricator\ProjectsMap'); $this->app->instance('phabricator-projectsmap', $mock); return $mock; } public function testRegularExecuteWithPhabricatorServiceWhenTheProjectsMapIsCached () { // The services list will return only one, for the Phabricator gate. $service = $this->mockService('Phabricator'); $this->servicesMock ->shouldReceive('get')->once()->andReturn([$service]); // The project map (built by the factory) will say it's cached. $this->mockProjectsMap() ->shouldReceive('fetch->isCached')->once()->andReturn(true); $this->tester->execute(['command' => $this->command->getName()]); $this->assertRegexp('/Phabricator.*✓/', $this->tester->getDisplay()); } public function testExecuteWhenSomeFeatureIsDisabled () { Features::disable('ActionsReport'); $this->servicesMock->shouldReceive('get')->once()->andReturn([]); $this->tester->execute(['command' => $this->command->getName()]); $this->assertRegexp('/Gate *\| *✓ *\|/', $this->tester->getDisplay()); $this->assertRegexp('/ActionsReport *\| *\|/', $this->tester->getDisplay()); } } diff --git a/tests/Console/Commands/PhabricatorProjectsMapTest.php b/tests/Console/Commands/PhabricatorProjectsMapTest.php index b3fdefd..018b337 100644 --- a/tests/Console/Commands/PhabricatorProjectsMapTest.php +++ b/tests/Console/Commands/PhabricatorProjectsMapTest.php @@ -1,31 +1,31 @@ <?php namespace Nasqueron\Notifications\Tests\Console\Commands; -use Nasqueron\Notifications\Services\Service; +use Nasqueron\Notifications\Config\Services\Service; class PhabricatorProjectsMapTest extends TestCase { /** * @var string */ protected $class = 'Nasqueron\Notifications\Console\Commands\PhabricatorProjectsMap'; public function setUp () { parent::setUp(); $service = $this->mockService('Phabricator'); $this->mockServices() ->shouldReceive('getForGate') ->once() ->andReturn([$service]); $this->mockPhabricatorAPIForProjectsMap(); } public function testRegularExecute () { $this->tester->execute(['command' => $this->command->getName()]); $this->assertRegexp('/PHID.*Project name/', $this->tester->getDisplay()); $this->assertRegexp('/PHID-PROJ-cztcgpvqr6smnnekotq7.*Agora/', $this->tester->getDisplay()); } } diff --git a/tests/Console/Commands/TestCase.php b/tests/Console/Commands/TestCase.php index 4deb467..54ea728 100644 --- a/tests/Console/Commands/TestCase.php +++ b/tests/Console/Commands/TestCase.php @@ -1,82 +1,82 @@ <?php namespace Nasqueron\Notifications\Tests\Console\Commands; use Symfony\Component\Console\Tester\CommandTester; -use Nasqueron\Notifications\Services\Service; +use Nasqueron\Notifications\Config\Services\Service; use Nasqueron\Notifications\Tests\TestCase as BaseTestCase; use Artisan; use Mockery; class TestCase extends BaseTestCase { /// /// Commands test environment /// /** * @var Symfony\Component\Console\Command */ protected $command; /** * @var Symfony\Component\Console\Tester\CommandTester; */ protected $tester; public function setUp () { parent::setUp(); $this->command = $this->findCommand($this->class); $this->tester = new CommandTester($this->command); } /// /// Helper methods to manipulate command arrays /// /** * Finds the first instance of the expected type in the specified array. * * @param mixed $expectedType The type to find among the array elements * @param array $haystack The array where to find * @return mixed|null If not found, null. Otherwise, the found item. */ protected static function findInstanceOf ($expectedType, $haystack) { foreach ($haystack as $item) { if ($item instanceof $expectedType) { return $item; } } return null; } protected function findCommand ($expectedType) { return self::findInstanceOf($expectedType, Artisan::all()); } /// /// Helper methods to mock services /// protected function mockServices () { // Inject into our container a mock of Services $mock = Mockery::mock('Nasqueron\Notifications\Services\Services'); $this->app->instance('services', $mock); return $mock; } protected function mockService ($gate = 'Storm') { $service = new Service; $service->gate = $gate; $service->door = 'Acme'; $service->instance = "http://www.perdu.com"; return $service; } } diff --git a/tests/Providers/ServicesServiceProviderTest.php b/tests/Providers/ServicesServiceProviderTest.php index e76d76b..fc93b9b 100644 --- a/tests/Providers/ServicesServiceProviderTest.php +++ b/tests/Providers/ServicesServiceProviderTest.php @@ -1,41 +1,41 @@ <?php namespace Nasqueron\Notifications\Tests\Providers; use Nasqueron\Notifications\Providers\ServicesServiceProvider; use Config; class ServicesServiceProviderTest extends TestCase { public function testType () { $this->assertServiceInstanceOf( - 'Nasqueron\Notifications\Services\Services', + "Nasqueron\Notifications\Config\Services\Services", 'services' ); } /// /// Tests specific to this service provider /// public function testWithCredentialsFile () { $services = $this->app->make('services'); $this->assertGreaterThan(0, count($services->services)); } public function testWithoutCredentialsFile () { Config::set('services.gate.credentials', null); $services = $this->app->make('services'); $this->assertSame(0, count($services->services)); } public function testWithNontFoundCredentialsFile () { Config::set('services.gate.credentials', 'notfound.json'); $services = $this->app->make('services'); $this->assertSame(0, count($services->services)); } } diff --git a/tests/Services/ServiceTest.php b/tests/Services/ServiceTest.php index 0d640ea..fdd7c28 100644 --- a/tests/Services/ServiceTest.php +++ b/tests/Services/ServiceTest.php @@ -1,45 +1,45 @@ <?php namespace Nasqueron\Notifications\Tests\Services; -use Nasqueron\Notifications\Services\Service; +use Nasqueron\Notifications\Config\Services\Service; use Nasqueron\Notifications\Tests\TestCase; class ServiceTest extends TestCase { /** * @var \Nasqueron\Notifications\Services\Service */ private $serviceWithInstance; /** * @var \Nasqueron\Notifications\Services\Service */ private $serviceWithoutInstance; public function setUp () { $this->serviceWithoutInstance = new Service(); $this->serviceWithInstance = clone $this->serviceWithoutInstance; $this->serviceWithInstance->instance = "http://www.perdu.com"; } /// /// Tests for getInstanceName() /// public function testGetInstanceName () { $this->assertSame( "http://www.perdu.com", $this->serviceWithInstance->getInstanceName() ); } public function testGetInstanceNameWhenThereIsNoInstance () { $this->assertSame( "ø", $this->serviceWithoutInstance->getInstanceName() ); } } diff --git a/tests/Services/ServicesTest.php b/tests/Services/ServicesTest.php index e4d27c4..d996306 100644 --- a/tests/Services/ServicesTest.php +++ b/tests/Services/ServicesTest.php @@ -1,90 +1,90 @@ <?php namespace Nasqueron\Notifications\Tests\Services; -use Nasqueron\Notifications\Services\Services; +use Nasqueron\Notifications\Config\Services\Services; use Nasqueron\Notifications\Tests\TestCase; class ServicesTest extends TestCase { private $services; public function setUp () { parent::setUp(); $this->services = Services::loadFromJson('credentials.json'); } public function testGet () { $actualServices = $this->services->get(); $this->assertGreaterThan(0, $actualServices); $this->assertSame( $this->services->services, // This is public, so testable $actualServices ); foreach ($actualServices as $service) { $this->assertInstanceOf( - 'Nasqueron\Notifications\Services\Service', + 'Nasqueron\Notifications\Config\Services\Service', $service ); } } public function testGetForGate () { $actualServices = $this->services->getForGate('GitHub'); $this->assertGreaterThan(0, $actualServices); foreach ($actualServices as $service) { $this->assertInstanceOf( - 'Nasqueron\Notifications\Services\Service', + 'Nasqueron\Notifications\Config\Services\Service', $service ); $this->assertSame('GitHub', $service->gate); } } public function testFindServiceByDoor () { // Search gives a result $service = $this->services->findServiceByDoor('GitHub', 'Acme'); $this->assertInstanceOf( - 'Nasqueron\Notifications\Services\Service', + 'Nasqueron\Notifications\Config\Services\Service', $service ); $this->assertSame('GitHub', $service->gate); $this->assertSame('Acme', $service->door); // Search doesn't give any result $service = $this->services->findServiceByDoor('GitHub', 'Quux'); $this->assertNull($service); } public function testFindServiceByProperty () { // Search gives a result $service = $this->services->findServiceByProperty( 'Phabricator', 'instance', 'https://phabricator.acme.tld' ); $this->assertInstanceOf( - 'Nasqueron\Notifications\Services\Service', + 'Nasqueron\Notifications\Config\Services\Service', $service ); $this->assertSame('Phabricator', $service->gate); $this->assertSame('Acme', $service->door); // Search doesn't give any result $service = $this->services->findServiceByProperty( 'Phabricator', 'instance', 'https://notfound.acme.tld' ); $this->assertNull($service); } }