]> BookStack Code Mirror - bookstack/blob - tests/Actions/WebhookCallTest.php
Merge branch 'basic-pwa-support' of https://github.com/GamerClassN7/BookStack into...
[bookstack] / tests / Actions / WebhookCallTest.php
1 <?php
2
3 namespace Tests\Actions;
4
5 use BookStack\Activity\ActivityType;
6 use BookStack\Activity\DispatchWebhookJob;
7 use BookStack\Activity\Models\Webhook;
8 use BookStack\Activity\Tools\ActivityLogger;
9 use BookStack\Api\ApiToken;
10 use BookStack\Users\Models\User;
11 use GuzzleHttp\Exception\ConnectException;
12 use GuzzleHttp\Psr7\Response;
13 use Illuminate\Support\Facades\Bus;
14 use Tests\TestCase;
15
16 class WebhookCallTest extends TestCase
17 {
18     public function test_webhook_listening_to_all_called_on_event()
19     {
20         $this->newWebhook([], ['all']);
21         Bus::fake();
22         $this->runEvent(ActivityType::ROLE_CREATE);
23         Bus::assertDispatched(DispatchWebhookJob::class);
24     }
25
26     public function test_webhook_listening_to_specific_event_called_on_event()
27     {
28         $this->newWebhook([], [ActivityType::ROLE_UPDATE]);
29         Bus::fake();
30         $this->runEvent(ActivityType::ROLE_UPDATE);
31         Bus::assertDispatched(DispatchWebhookJob::class);
32     }
33
34     public function test_webhook_listening_to_specific_event_not_called_on_other_event()
35     {
36         $this->newWebhook([], [ActivityType::ROLE_UPDATE]);
37         Bus::fake();
38         $this->runEvent(ActivityType::ROLE_CREATE);
39         Bus::assertNotDispatched(DispatchWebhookJob::class);
40     }
41
42     public function test_inactive_webhook_not_called_on_event()
43     {
44         $this->newWebhook(['active' => false], ['all']);
45         Bus::fake();
46         $this->runEvent(ActivityType::ROLE_CREATE);
47         Bus::assertNotDispatched(DispatchWebhookJob::class);
48     }
49
50     public function test_webhook_runs_for_delete_actions()
51     {
52         // This test must not fake the queue/bus since this covers an issue
53         // around handling and serialization of items now deleted from the database.
54         $this->newWebhook(['active' => true, 'endpoint' => 'https://wh.example.com'], ['all']);
55         $this->mockHttpClient([new Response(500)]);
56
57         $user = $this->users->newUser();
58         $resp = $this->asAdmin()->delete($user->getEditUrl());
59         $resp->assertRedirect('/settings/users');
60
61         /** @var ApiToken $apiToken */
62         $editor = $this->users->editor();
63         $apiToken = ApiToken::factory()->create(['user_id' => $editor]);
64         $resp = $this->delete($editor->getEditUrl('/api-tokens/' . $apiToken->id));
65         $resp->assertRedirect($editor->getEditUrl('#api_tokens'));
66     }
67
68     public function test_failed_webhook_call_logs_error()
69     {
70         $logger = $this->withTestLogger();
71         $this->mockHttpClient([new Response(500)]);
72         $webhook = $this->newWebhook(['active' => true, 'endpoint' => 'https://wh.example.com'], ['all']);
73         $this->assertNull($webhook->last_errored_at);
74
75         $this->runEvent(ActivityType::ROLE_CREATE);
76
77         $this->assertTrue($logger->hasError('Webhook call to endpoint https://wh.example.com failed with status 500'));
78
79         $webhook->refresh();
80         $this->assertEquals('Response status from endpoint was 500', $webhook->last_error);
81         $this->assertNotNull($webhook->last_errored_at);
82     }
83
84     public function test_webhook_call_exception_is_caught_and_logged()
85     {
86         $this->mockHttpClient([new ConnectException('Failed to perform request', new \GuzzleHttp\Psr7\Request('GET', ''))]);
87
88         $logger = $this->withTestLogger();
89         $webhook = $this->newWebhook(['active' => true, 'endpoint' => 'https://wh.example.com'], ['all']);
90         $this->assertNull($webhook->last_errored_at);
91
92         $this->runEvent(ActivityType::ROLE_CREATE);
93
94         $this->assertTrue($logger->hasError('Webhook call to endpoint https://wh.example.com failed with error "Failed to perform request"'));
95
96         $webhook->refresh();
97         $this->assertEquals('Failed to perform request', $webhook->last_error);
98         $this->assertNotNull($webhook->last_errored_at);
99     }
100
101     public function test_webhook_uses_ssr_hosts_option_if_set()
102     {
103         config()->set('app.ssr_hosts', 'https://*.example.com');
104         $responses = $this->mockHttpClient();
105
106         $webhook = $this->newWebhook(['active' => true, 'endpoint' => 'https://wh.example.co.uk'], ['all']);
107         $this->runEvent(ActivityType::ROLE_CREATE);
108         $this->assertEquals(0, $responses->requestCount());
109
110         $webhook->refresh();
111         $this->assertEquals('The URL does not match the configured allowed SSR hosts', $webhook->last_error);
112         $this->assertNotNull($webhook->last_errored_at);
113     }
114
115     public function test_webhook_call_data_format()
116     {
117         $responses = $this->mockHttpClient([new Response(200, [], '')]);
118         $webhook = $this->newWebhook(['active' => true, 'endpoint' => 'https://wh.example.com'], ['all']);
119         $page = $this->entities->page();
120         $editor = $this->users->editor();
121
122         $this->runEvent(ActivityType::PAGE_UPDATE, $page, $editor);
123
124         $request = $responses->latestRequest();
125         $reqData = json_decode($request->getBody(), true);
126         $this->assertEquals('page_update', $reqData['event']);
127         $this->assertEquals(($editor->name . ' updated page "' . $page->name . '"'), $reqData['text']);
128         $this->assertIsString($reqData['triggered_at']);
129         $this->assertEquals($editor->name, $reqData['triggered_by']['name']);
130         $this->assertEquals($editor->getProfileUrl(), $reqData['triggered_by_profile_url']);
131         $this->assertEquals($webhook->id, $reqData['webhook_id']);
132         $this->assertEquals($webhook->name, $reqData['webhook_name']);
133         $this->assertEquals($page->getUrl(), $reqData['url']);
134         $this->assertEquals($page->name, $reqData['related_item']['name']);
135     }
136
137     protected function runEvent(string $event, $detail = '', ?User $user = null)
138     {
139         if (is_null($user)) {
140             $user = $this->users->editor();
141         }
142
143         $this->actingAs($user);
144
145         $activityLogger = $this->app->make(ActivityLogger::class);
146         $activityLogger->add($event, $detail);
147     }
148
149     protected function newWebhook(array $attrs, array $events): Webhook
150     {
151         /** @var Webhook $webhook */
152         $webhook = Webhook::factory()->create($attrs);
153
154         foreach ($events as $event) {
155             $webhook->trackedEvents()->create(['event' => $event]);
156         }
157
158         return $webhook;
159     }
160 }