Page Menu
Home
DevCentral
Search
Configure Global Search
Log In
Files
F12239507
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
20 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/app/Features.php b/app/Config/Features.php
similarity index 98%
rename from app/Features.php
rename to app/Config/Features.php
index c6d2c53..c9d13c3 100644
--- a/app/Features.php
+++ b/app/Config/Features.php
@@ -1,91 +1,91 @@
<?php
-namespace Nasqueron\Notifications;
+namespace Nasqueron\Notifications\Config;
use Config;
/**
* The features class offers a sugar syntax to check if a feature is enabled
* in the Config repository, at app.features.
*
* Features could be added to config/app.php to the features array.
*/
class Features {
///
/// Feature information
///
/**
* @param string $feature The feature to get the config key
* @return string The config key
*/
private static function getFeatureConfigKey (string $feature) : string {
return 'app.features.' . $feature;
}
/**
* Determines if the specified feature is enabled.
*
* @param string $feature The feature to check in the config
* @return bool
*/
public static function isEnabled (string $feature) : bool {
$key = self::getFeatureConfigKey($feature);
return Config::has($key) && (bool)Config::get($key);
}
/**
* Enables a feature in our current configuration instance.
*
* @param string $feature The feature
*/
public static function enable (string $feature) : void {
$key = self::getFeatureConfigKey($feature);
Config::set($key, true);
}
/**
* Disables a feature in our current configuration instance.
*
* @param string $feature The feature
*/
public static function disable (string $feature) : void {
$key = self::getFeatureConfigKey($feature);
Config::set($key, false);
}
///
/// Features lists
///
/**
* Gets all the features, with the toggle status.
*
* @return array An array with features as keys, bool as values (true if enabled)
*/
public static function getAll () : array {
return Config::get('app.features');
}
/**
* Lists all the features.
*
* @return string[] a list of all features
*/
public static function getAvailable () : array {
$features = self::getAll();
return array_keys($features);
}
/**
* Lists the enabled features
*
* @return string[] a list of enabled features
*/
public static function getEnabled () : array {
$features = self::getAll();
$enabledFeatures = array_filter($features);
return array_keys($enabledFeatures);
}
}
diff --git a/app/Console/Commands/ConfigShow.php b/app/Console/Commands/ConfigShow.php
index 86f11ba..626898c 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\Features;
+use Nasqueron\Notifications\Config\Features;
use Nasqueron\Notifications\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[]
*/
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
* @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 adaf680..ab8aae7 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\Features;
+use Nasqueron\Notifications\Config\Features;
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.
*/
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/Http/routes.php b/app/Http/routes.php
index e5118bb..98d64d9 100644
--- a/app/Http/routes.php
+++ b/app/Http/routes.php
@@ -1,32 +1,32 @@
<?php
-use Nasqueron\Notifications\Features;
+use Nasqueron\Notifications\Config\Features;
/*
|--------------------------------------------------------------------------
| Application Routes
|--------------------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It's a breeze. Simply tell Laravel the URIs it should respond to
| and give it the controller to call when that URI is requested.
|
*/
Route::get('/', function () {
return view('welcome');
});
// Allows to external tool to ping your instalation and know if the site is up.
Route::get('/status', function() {
return "ALIVE";
});
// Gate controllers
if (Features::isEnabled('Gate')) {
foreach (Config::get('gate.controllers') as $controller) {
$controllerRoute = '/gate/' . $controller . '/';
Route::get($controllerRoute . '{door?}', "Gate\\${controller}GateController@onGet");
Route::post($controllerRoute . '{door}', "Gate\\${controller}GateController@onPost");
}
}
diff --git a/tests/Console/Commands/ConfigShowTest.php b/tests/Console/Commands/ConfigShowTest.php
index 3209042..59507dc 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\Features;
+use Nasqueron\Notifications\Config\Features;
use Nasqueron\Notifications\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/FeaturesTest.php b/tests/FeaturesTest.php
index 500f5af..cbf85f2 100644
--- a/tests/FeaturesTest.php
+++ b/tests/FeaturesTest.php
@@ -1,28 +1,28 @@
<?php
namespace Nasqueron\Notifications\Tests;
-use Nasqueron\Notifications\Features;
+use Nasqueron\Notifications\Config\Features;
class FeaturesTest extends TestCase {
public function testEnable () {
// Find it (en vain …)
$this->assertNotContains('Quux', Features::getEnabled());
$this->assertFalse(Features::isEnabled('Quux'));
// Enable it
Features::enable('Quux');
$this->assertTrue(Features::isEnabled('Quux'));
$this->assertContains('Quux', Features::getEnabled());
// Disable it
Features::disable('Quux');
$this->assertFalse(Features::isEnabled('Quux'));
// Count it
$this->assertContains('Quux', Features::getAll());
$this->assertContains('Quux', Features::getAvailable());
$this->assertNotContains('Quux', Features::getEnabled());
}
}
diff --git a/tests/Http/PayloadFullTest.php b/tests/Http/PayloadFullTest.php
index 86973e6..0461da3 100644
--- a/tests/Http/PayloadFullTest.php
+++ b/tests/Http/PayloadFullTest.php
@@ -1,183 +1,183 @@
<?php
namespace Nasqueron\Notifications\Tests;
use Keruald\Broker\BlackholeBroker;
-use Nasqueron\Notifications\Features;
+use Nasqueron\Notifications\Config\Features;
class PayloadFullTest extends TestCase {
public function setUp () {
parent::setUp();
$this->disableBroker();
}
/**
* Sends a GitHub ping payload to the application, with a valid signature
*/
protected function sendValidTestPayload () {
return $this->sendTestPayload('sha1=25f6cbd17ea4c6c69958b95fb88c879de4b66dcc');
}
/**
* Sends a GitHub ping payload to the application, with a valid signature
*/
protected function sendInvalidTestPayload () {
return $this->sendTestPayload('sha1=somethingwrong');
}
protected function sendTestPayload ($signature) {
$payload = file_get_contents(__DIR__ . '/../data/payloads/GitHubPingPayload.json');
$this->sendPayload(
'/gate/GitHub/Acme', // A gate existing in data/credentials.json
$payload,
'POST',
[
'X-Github-Event' => 'ping',
'X-Github-Delivery' => 'e5dd9fc7-17ac-11e5-9427-73dad6b9b17c',
'X-Hub-Signature' => $signature,
]
);
return $this;
}
/**
* Tests a GitHub gate payload.
*/
public function testPost () {
$this->sendValidTestPayload()->seeJson([
'gate' => 'GitHub',
'door' => 'Acme',
'action' => 'AMQPAction'
]);
$this->assertResponseOk();
}
/**
* Tests a DockerHub gate payload.
*/
public function testDockerHubPayload () {
$payload = file_get_contents(__DIR__ . '/../data/payloads/DockerHubPushPayload.json');
$this->sendPayload(
'/gate/DockerHub/Acme', // A gate existing in data/credentials.json
$payload,
'POST',
[]
)->seeJson([
'gate' => 'DockerHub',
'door' => 'Acme',
'action' => 'AMQPAction'
]);
$this->assertResponseOk();
}
/**
* Tests a Jenkins gate payload.
*/
public function testJenkinsPayload () {
$payload = file_get_contents(__DIR__ . '/../data/payloads/JenkinsPayload.json');
$this->sendPayload(
'/gate/Jenkins/Acme', // A gate existing in data/credentials.json
$payload,
'POST',
[]
)->seeJson([
'gate' => 'Jenkins',
'door' => 'Acme',
'action' => 'AMQPAction'
]);
$this->assertResponseOk();
}
private function getDataForPhabricatorPayloadTests () {
return [
'storyID' => 3849,
'storyType' => 'PhabricatorApplicationTransactionFeedStory',
'storyData[objectPHID]' => 'PHID-TASK-l34fw5wievp6n6rnvpuk',
'storyData[transactionPHIDs][PHID-XACT-TASK-by2g3dtlfq3l2wc]' => 'PHID-XACT-TASK-by2g3dtlfq3l2wc',
'storyAuthorPHID' => 'PHID-USER-fnetlprx7zdotfm2hdrz',
'storyText' => 'quux moved T123: Lorem ipsum dolor to Backlog on the Foo workboard.',
'epoch' => 1450654419,
];
}
/**
* Tests a Phabricator gate payload.
*/
public function testPhabricatorPayload () {
$data = $this->getDataForPhabricatorPayloadTests();
$this->post('/gate/Phabricator/Acme', $data)->seeJson([
'gate' => 'Phabricator',
'door' => 'Acme',
'action' => 'AMQPAction'
]);
$this->assertResponseOk();
}
/**
* Tests a Phabricator gate payload, when the door doesn't exist.
*/
public function testPhabricatorPayloadOnNotExistingDoor () {
$data = $this->getDataForPhabricatorPayloadTests();
$this->post('/gate/Phabricator/NotExistingDoor', $data);
$this->assertResponseStatus(404);
}
/**
* Same than testPost, but without actions report.
*/
public function testPostWithoutActionsReport () {
Features::disable("ActionsReport");
$this->sendValidTestPayload();
$this->assertEmpty($this->response->getContent());
$this->assertResponseOk();
// Let's throw an Exception at broker level.
// Without ActionsReport, the client must always receive a 200 OK.
$this->app->instance('broker', function ($app) {
// A non omnipotent instance, so it doesn't mock connect().
return new BlackholeBroker;
});
$this->sendValidTestPayload();
$this->assertEmpty($this->response->getContent());
$this->assertResponseOk();
}
/**
* Tests a GitHub gate payload.
*/
public function testInvalidSignature () {
$this->sendInvalidTestPayload()
->assertResponseStatus(403);
}
public function testBrokerIssue () {
$this->mockNotOperationalBroker();
$payload = file_get_contents(__DIR__ . '/../data/payloads/GitHubPingPayload.json');
$this->sendPayload(
'/gate/GitHub/Acme', // A gate existing in data/credentials.json
$payload,
'POST',
[
'X-Github-Event' => 'ping',
'X-Github-Delivery' => 'e5dd9fc7-17ac-11e5-9427-73dad6b9b17c',
'X-Hub-Signature' => 'sha1=25f6cbd17ea4c6c69958b95fb88c879de4b66dcc',
]
)->seeJson([
'gate' => 'GitHub',
'door' => 'Acme',
'action' => 'AMQPAction',
'type' => 'RuntimeException',
]);
$this->assertResponseStatus(503);
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Oct 11, 22:14 (1 d, 10 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3064159
Default Alt Text
(20 KB)
Attached To
Mode
rNOTIF Notifications center
Attached
Detach File
Event Timeline
Log In to Comment