Skip to content

Commit 960e4a2

Browse files
simonhampclaude
andauthored
Allow GitHub webhook ping events for unapproved plugins (#347)
Move the X-GitHub-Event header check before the plugin approval gate so that GitHub's test ping returns 200 regardless of plugin status. This lets developers verify webhook setup before their plugin is approved. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6085793 commit 960e4a2

2 files changed

Lines changed: 76 additions & 2 deletions

File tree

app/Http/Controllers/PluginWebhookController.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,16 @@ public function __invoke(Request $request, string $secret, PluginSyncService $sy
1818
return response()->json(['error' => 'Invalid webhook secret'], 404);
1919
}
2020

21+
$event = $request->header('X-GitHub-Event');
22+
23+
if ($event === 'ping') {
24+
return response()->json(['success' => true, 'message' => 'pong']);
25+
}
26+
2127
if (! $plugin->isApproved()) {
2228
return response()->json(['error' => 'Plugin is not approved'], 403);
2329
}
2430

25-
$event = $request->header('X-GitHub-Event');
26-
2731
if ($event === 'release') {
2832
// Sync plugin metadata to update latest_version
2933
$syncService->sync($plugin);
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
namespace Tests\Feature;
4+
5+
use App\Models\Plugin;
6+
use Illuminate\Foundation\Testing\RefreshDatabase;
7+
use PHPUnit\Framework\Attributes\Test;
8+
use Tests\TestCase;
9+
10+
class PluginWebhookTest extends TestCase
11+
{
12+
use RefreshDatabase;
13+
14+
#[Test]
15+
public function ping_event_succeeds_for_unapproved_plugin(): void
16+
{
17+
$plugin = Plugin::factory()->create();
18+
19+
$response = $this->postJson(
20+
route('webhooks.plugins', $plugin->webhook_secret),
21+
[],
22+
['X-GitHub-Event' => 'ping']
23+
);
24+
25+
$response->assertOk()
26+
->assertJson(['success' => true, 'message' => 'pong']);
27+
}
28+
29+
#[Test]
30+
public function ping_event_succeeds_for_approved_plugin(): void
31+
{
32+
$plugin = Plugin::factory()->approved()->create();
33+
34+
$response = $this->postJson(
35+
route('webhooks.plugins', $plugin->webhook_secret),
36+
[],
37+
['X-GitHub-Event' => 'ping']
38+
);
39+
40+
$response->assertOk()
41+
->assertJson(['success' => true, 'message' => 'pong']);
42+
}
43+
44+
#[Test]
45+
public function non_ping_event_returns_403_for_unapproved_plugin(): void
46+
{
47+
$plugin = Plugin::factory()->create();
48+
49+
$response = $this->postJson(
50+
route('webhooks.plugins', $plugin->webhook_secret),
51+
[],
52+
['X-GitHub-Event' => 'push']
53+
);
54+
55+
$response->assertForbidden()
56+
->assertJson(['error' => 'Plugin is not approved']);
57+
}
58+
59+
#[Test]
60+
public function invalid_secret_returns_404(): void
61+
{
62+
$response = $this->postJson(
63+
route('webhooks.plugins', 'invalid-secret'),
64+
[],
65+
['X-GitHub-Event' => 'ping']
66+
);
67+
68+
$response->assertNotFound();
69+
}
70+
}

0 commit comments

Comments
 (0)