From b1f63cf699d4101512491061797c242735ab083d Mon Sep 17 00:00:00 2001 From: edalzell Date: Fri, 13 Aug 2021 11:06:26 -0700 Subject: [PATCH] initial commit part of https://github.com/transformstudios/zakat-v3/issues/130 --- .gitignore | 7 ++ composer.json | 42 +++++++ config/uptime.php | 5 + phpunit.xml | 37 ++++++ routes/actions.php | 16 +++ src/Http/Controllers/WebhookController.php | 132 +++++++++++++++++++++ src/Notifications/AlertCleared.php | 51 ++++++++ src/ServiceProvider.php | 17 +++ tests/ExceptionHandler.php | 27 +++++ tests/PreventSavingStacheItemsToDisk.php | 30 +++++ tests/TestCase.php | 67 +++++++++++ tests/Unit/WebhookTest.php | 94 +++++++++++++++ tests/__fixtures__/contact_us.yaml | 38 ++++++ tests/__fixtures__/dev-null/.gitkeep | 0 14 files changed, 563 insertions(+) create mode 100644 .gitignore create mode 100644 composer.json create mode 100644 config/uptime.php create mode 100644 phpunit.xml create mode 100644 routes/actions.php create mode 100644 src/Http/Controllers/WebhookController.php create mode 100644 src/Notifications/AlertCleared.php create mode 100644 src/ServiceProvider.php create mode 100644 tests/ExceptionHandler.php create mode 100644 tests/PreventSavingStacheItemsToDisk.php create mode 100644 tests/TestCase.php create mode 100644 tests/Unit/WebhookTest.php create mode 100644 tests/__fixtures__/contact_us.yaml create mode 100644 tests/__fixtures__/dev-null/.gitkeep diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..454d301 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/mix-manifest.json +/node_modules +/yarn.lock +/vendor +/.phpunit.result.cache +/composer.lock +/dist diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..1de50be --- /dev/null +++ b/composer.json @@ -0,0 +1,42 @@ +{ + "name": "transformstudios/uptime", + "autoload": { + "psr-4": { + "TransformStudios\\Uptime\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "TransformStudios\\Uptime\\Tests\\": "tests" + } + }, + "require": { + "php": "^8.0", + "laravel/framework": "^8.53", + "statamic/cms": "^3.1" + }, + "require-dev": { + "cweagans/composer-patches": "^1.7", + "mockery/mockery": "^1.3.1", + "nunomaduro/collision": "^4.1 || ^5.0", + "phpunit/phpunit": "^8.0 || ^9.0", + "orchestra/testbench": "^5.0 || ^6.0", + "jasonmccreary/laravel-test-assertions": "^1.0" + }, + "extra": { + "statamic": { + "name": "Uptime", + "description": "Uptime addon" + }, + "laravel": { + "providers": [ + "TransformStudios\\Uptime\\ServiceProvider" + ] + }, + "patches": { + "statamic/cms": { + "Add getKey to User": "patches/4122.diff" + } + } + } +} diff --git a/config/uptime.php b/config/uptime.php new file mode 100644 index 0000000..c30dc8e --- /dev/null +++ b/config/uptime.php @@ -0,0 +1,5 @@ + env('UPTIME_API_KEY'), +]; diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..623ad38 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,37 @@ + + + + + tests/Unit/ + + + ./tests + + + + + ./app + + + + + + + + + + + + + + diff --git a/routes/actions.php b/routes/actions.php new file mode 100644 index 0000000..38bb37f --- /dev/null +++ b/routes/actions.php @@ -0,0 +1,16 @@ +withoutMiddleware(VerifyCsrfToken::class) + ->name('uptime.webhook'); + +Route::get('test', function(Request $request) { + // abort(500); + return response()->noContent(); +}) +->name('uptime.test'); diff --git a/src/Http/Controllers/WebhookController.php b/src/Http/Controllers/WebhookController.php new file mode 100644 index 0000000..d0f1600 --- /dev/null +++ b/src/Http/Controllers/WebhookController.php @@ -0,0 +1,132 @@ + [ + "service" => [ + "id" => 937828 + "name" => "Erin Test" + "device_id" => 537094 + "monitoring_service_type" => "HTTP" + "is_paused" => false + "msp_address" => "https://transform.share.silentz.dev/!/uptime/test" + "msp_version" => 2 + "msp_interval" => 5 + "msp_sensitivity" => 2 + "msp_num_retries" => 2 + "msp_url_scheme" => "https" + "msp_url_path" => "/!/uptime/test" + "msp_port" => null + "msp_protocol" => null + "msp_username" => null + "msp_proxy" => null + "msp_dns_server" => null + "msp_dns_record_type" => null + "msp_send_string" => null + "msp_expect_string" => null + "msp_expect_string_type" => "STRING" + "msp_encryption" => "SSL_TLS" + "msp_threshold" => 40 + "msp_notes" => null + "msp_include_in_global_metrics" => true + "msp_use_ip_version" => null + "msp_uptime_sla" => "0.9900" + "msp_response_time_sla" => "2.200" + "monitoring_service_type_display" => "HTTP(S)" + "display_name" => "Erin Test" + "short_name" => "Erin Test" + "tags" => [ + "Erin" + ] + ] + "account" => [ + "id" => 151373 + "name" => "Transform Studios" + "brand" => "uptime" + "timezone" => "America/Los_Angeles" + "site_url" => "https://uptime.com" + ] + "integration" => [ + "id" => 4310 + "name" => "Erin Test" + "module" => "webhook" + "module_verbose_name" => "Custom Postback URL (Webhook)" + "is_enabled" => true + "is_errored" => false + "is_test_supported" => true + "postback_url" => "https://transform.share.silentz.dev/!/uptime/webhook" + "headers" => null + "use_legacy_payload" => false + ] + "date" => "2021-08-06T19:16:47.928Z" + "alert" => [ + "id" => 112738275 + "created_at" => "2021-08-06T19:16:47.928Z" + "state" => "OK" + "output" => """ + HTTP OK: HTTP/1.1 204 No Content - 1178 bytes in 0.881 second response time + + time=0.881087s;;;0.000000;40.000000 size=1178B;;;0 + """ + "short_output" => "HTTP OK: HTTP/1.1 204 No Content - 1178 bytes in 0.881 second response time" + "is_up" => true + ] + "global_alert_state" => array:6 [▶] + "device" => array:5 [▶] + "downtime" => array:4 [▶] + "links" => [] + ] + "event" => "alert_cleared" + */ + public function __invoke(Request $request) + { + $method = 'handle' . Str::studly(str_replace('.', '_', $request->input('event'))); + + if (method_exists($this, $method)) { + return $this->{$method}($request->input('data')); + } + + return response()->noContent(); + } + + private function handleAlertCleared(array $payload) + { + return $this->handleAlert(AlertCleared::class, $payload); + } + + private function handleAlert($notificationClass, $payload) + { + if (! $tag = Arr::get($payload, 'service.tags.0')) { + return response()->noContent(); + } + + if (! $site = Entry::query()->where('collection', 'sites')->where('uptime_tag', $tag)->first()) { + return response()->noContent(); + } + + if (! $users = collect($site->augmentedValue('users'))->map(fn (string $id) => User::find($id))) { + return response()->noContent(); + } + + Notification::send($users, new $notificationClass($payload)); + + return response('Webhook handled'); + } +} diff --git a/src/Notifications/AlertCleared.php b/src/Notifications/AlertCleared.php new file mode 100644 index 0000000..7e42072 --- /dev/null +++ b/src/Notifications/AlertCleared.php @@ -0,0 +1,51 @@ +alert; + } +} diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php new file mode 100644 index 0000000..83c4ccf --- /dev/null +++ b/src/ServiceProvider.php @@ -0,0 +1,17 @@ + __DIR__.'/../routes/actions.php', + ]; + + protected $fieldtypes = [ + Tag::class, + ]; +} diff --git a/tests/ExceptionHandler.php b/tests/ExceptionHandler.php new file mode 100644 index 0000000..a36968c --- /dev/null +++ b/tests/ExceptionHandler.php @@ -0,0 +1,27 @@ +fakeStacheDirectory = Path::tidy($this->fakeStacheDirectory); + + Stache::stores()->each(function ($store) { + $dir = Path::tidy(__DIR__ . '/__fixtures__'); + $relative = str_after(str_after($store->directory(), $dir), '/'); + $store->directory($this->fakeStacheDirectory . '/' . $relative); + }); + } + + protected function deleteFakeStacheDirectory() + { + app('files')->deleteDirectory($this->fakeStacheDirectory); + + mkdir($this->fakeStacheDirectory); + touch($this->fakeStacheDirectory . '/.gitkeep'); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..dc3719e --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,67 @@ +deleteFakeStacheDirectory(); + + parent::tearDown(); + } + + protected function getPackageProviders($app) + { + return [StatamicServiceProvider::class, ServiceProvider::class]; + } + + protected function getPackageAliases($app) + { + return [ + 'Statamic' => Statamic::class, + ]; + } + + protected function getEnvironmentSetUp($app) + { + parent::getEnvironmentSetUp($app); + + $app->make(Manifest::class)->manifest = [ + 'transformstudios/uptime' => [ + 'id' => 'transformstudios/uptime', + 'namespace' => 'TransformStudios\\Uptime\\', + ], + ]; + } + + protected function resolveApplicationConfiguration($app) + { + parent::resolveApplicationConfiguration($app); + + $configs = ['assets', 'cp', 'forms', 'routes', 'static_caching', 'sites', 'stache', 'system', 'users']; + + foreach ($configs as $config) { + $app['config']->set("statamic.$config", require __DIR__ . "/../vendor/statamic/cms/config/{$config}.php"); + } + + // Setting the user repository to the default flat file system + $app['config']->set('statamic.users.repository', 'file'); + + // Assume the pro edition within tests + $app['config']->set('statamic.editions.pro', true); + } +} diff --git a/tests/Unit/WebhookTest.php b/tests/Unit/WebhookTest.php new file mode 100644 index 0000000..0e1d293 --- /dev/null +++ b/tests/Unit/WebhookTest.php @@ -0,0 +1,94 @@ +post(route('statamic.uptime.webhook'), []) + ->assertNoContent(); + } + + /** @test */ + public function ignores_alert_cleared_if_no_tag() + { + $data = [ + 'data' => [], + 'event' => 'alert_cleared', + ]; + + $this->post(route('statamic.uptime.webhook'), $data) + ->assertNoContent(); + } + + /** @test */ + public function ignores_alert_cleared_if_tag_not_used() + { + $data = [ + 'data' => [ + 'service' => [ + 'tags' => [ + 'foo', + ], + ], + ], + 'event' => 'alert_cleared', + ]; + + $this->post(route('statamic.uptime.webhook'), $data) + ->assertNoContent(); + } + + /** @test */ + public function sends_alert_cleared_notification() + { + $data = [ + 'data' => [ + 'service' => [ + 'tags' => [ + 'foo', + ], + ], + ], + 'event' => 'alert_cleared', + ]; + + $user = User::make()->email('foo@bar.com'); + + $user->save(); + + $collection = (new Collection)->handle('sites'); + + $collection->save(); + + /** @var \Statamic\Entries\Entry */ + $entry = Entry::make() + ->collection('sites') + ->in('default') + ->set('title', 'Test Site') + ->set('uptime_tag', 'foo') + ->set('users', [$user->id()]) + ->save(); + + Notification::fake(); + + $this->post(route('statamic.uptime.webhook'), $data) + ->assertOk(); + + Notification::assertTimesSent(1, AlertCleared::class); + } +} diff --git a/tests/__fixtures__/contact_us.yaml b/tests/__fixtures__/contact_us.yaml new file mode 100644 index 0000000..51bdf0e --- /dev/null +++ b/tests/__fixtures__/contact_us.yaml @@ -0,0 +1,38 @@ +title: Contact +sections: + main: + display: Main + fields: + - + handle: email + field: + input_type: text + type: text + localizable: false + listable: hidden + display: Email + - + handle: name + field: + input_type: text + type: text + localizable: false + listable: hidden + display: 'Name' + - + handle: permission + field: + inline: false + options: + 'true': Permission + type: checkboxes + localizable: false + listable: hidden + display: Permission + - + handle: message + field: + type: text + localizable: false + listable: hidden + display: Message diff --git a/tests/__fixtures__/dev-null/.gitkeep b/tests/__fixtures__/dev-null/.gitkeep new file mode 100644 index 0000000..e69de29