Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Receiver – Copilot Instructions

## Commands

```bash
# Run all tests
composer test
# or
vendor/bin/phpunit

# Run a single test file
vendor/bin/phpunit tests/GithubProviderTest.php

# Run a single test method
vendor/bin/phpunit --filter test_it_can_receive_github_webhook

# Fix code style
vendor/bin/php-cs-fixer fix
```

## Architecture

Receiver is a Laravel package that provides a driver-based webhook handling pipeline.

**Entry point:** `Receiver::driver('stripe')->receive($request)->ok()`

**Driver resolution:** `ReceiverManager` extends Laravel's `Manager` class. Each built-in driver has a corresponding `create{Name}Driver()` method that reads `services.{driver}.webhook_secret` from config and instantiates the provider.

**Provider lifecycle (in `AbstractProvider::receive()`):**
1. `handshake()` – if defined, runs first; returning a truthy value short-circuits handling and sends that as the response
2. `verify()` – if defined, returns `false` β†’ 401 abort
3. `mapWebhook()` – builds a `Webhook` object from `getEvent()` and `getData()`
4. `handle()` – resolves and dispatches a handler class

**Handler class resolution:** `\App\Http\Handlers\{DriverName}\{EventClassName}`
- `{DriverName}` = provider class basename with `Provider` stripped (e.g., `GithubProvider` β†’ `Github`)
- `{EventClassName}` = event string with non-alphanumeric chars replaced by spaces, then converted to StudlyCase (e.g., `customer.created` β†’ `CustomerCreated`)
- GitHub is a special case: event = `{X-GitHub-Event}_{action}` (e.g., `issues_opened`)

**Handlers** must use the `Dispatchable` trait and accept `(string $event, array $data)` in the constructor. Implement `ShouldQueue` to queue them.

**Custom providers** are registered via `app('receiver')->extend('name', fn($app) => new MyProvider($secret))` in a service provider. Use `php artisan receiver:make <Name>` (or `--verified`) to scaffold.

## Key Conventions

**Config key for secrets:** Always `services.{driver}.webhook_secret` β€” see `ReceiverManager::buildProvider()`.

**`FakeProvider`** is available as the `fake` driver for testing purposes.

**Testing approach:** Tests use [Orchestra Testbench](https://github.com/orchestral/testbench). The base `TestCase` extends `\Orchestra\Testbench\TestCase` and registers `ReceiverServiceProvider`. Use `Mockery` to mock `Request` objects. To test handler dispatch in isolation, call `$provider->setHandlerNamespace('Your\\Test\\Namespace')` before `receive()`.

**Fixture handlers** in `tests/Fixtures/` use `Log::info('Webhook handled.')` to signal that dispatch occurred β€” assert with `Log::partialMock()->shouldReceive('info')->withArgs(['Webhook handled.'])`.

**Code style:** PHP CS Fixer with the config in `.php-cs-fixer.php`. Notable rules: single quotes, alpha-sorted imports (`ordered_imports`), trailing commas in multiline structures, blank line before `return`.
132 changes: 87 additions & 45 deletions .github/workflows/php.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Tests
name: CI

on:
push:
Expand All @@ -7,54 +7,96 @@ on:
branches: [ main ]

jobs:
build:

lint:
name: Lint (Pint)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
coverage: none

- name: Install dependencies
run: composer install --prefer-dist --no-interaction --no-progress

- name: Check code style
run: composer run-script lint:check

analyse:
name: Analyse (PHPStan)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
coverage: none

- name: Install dependencies
run: composer install --prefer-dist --no-interaction --no-progress

- name: Run PHPStan
run: composer run-script analyse

tests:
name: P${{ matrix.php }} - L${{ matrix.laravel }}
runs-on: ubuntu-latest

strategy:
fail-fast: true
fail-fast: false
matrix:
laravel: [ 10.*, 11.* ]
php: [ 8.2, 8.3 ]
laravel: [ '10.*', '11.*', '12.*', '13.*' ]
php: [ '8.2', '8.3', '8.4' ]
include:
- laravel: 10.*
testbench: 8.*
- laravel: 11.*
testbench: 9.*
- laravel: 12.*
testbench: 10.*

name: P${{ matrix.php }} - L${{ matrix.laravel }}
- laravel: '10.*'
testbench: '8.*'
- laravel: '11.*'
testbench: '9.*'
- laravel: '12.*'
testbench: '10.*'
- laravel: '13.*'
testbench: '11.*'
exclude:
# Laravel 10 was released before PHP 8.4
- laravel: '10.*'
php: '8.4'
# Laravel 13 requires PHP ^8.3
- laravel: '13.*'
php: '8.2'

steps:
- uses: actions/checkout@v4

- name: Validate composer.json and composer.lock
run: composer validate

- name: Cache Composer packages
id: composer-cache
uses: actions/cache@v4
with:
path: vendor
key: ${{ runner.os }}-${{ matrix.php }}--${{ matrix.laravel }}--node-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-${{ matrix.php }}--${{ matrix.laravel }}-node-

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: dom, fileinfo, libxml, mbstring
coverage: none

- name: Install dependencies
if: steps.composer-cache.outputs.cache-hit != 'true'
run: |
composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
composer update --prefer-dist --no-progress --no-suggest

# Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit"
# Docs: https://getcomposer.org/doc/articles/scripts.md

- name: Run test suite
run: composer run-script test
- uses: actions/checkout@v4

- name: Validate composer.json
run: composer validate

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: dom, fileinfo, libxml, mbstring
coverage: none

- name: Cache Composer packages
id: composer-cache
uses: actions/cache@v4
with:
path: vendor
key: ${{ runner.os }}-${{ matrix.php }}-${{ matrix.laravel }}-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-${{ matrix.php }}-${{ matrix.laravel }}-

- name: Install dependencies
if: steps.composer-cache.outputs.cache-hit != 'true'
run: |
composer require --dev "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
composer update --prefer-dist --no-interaction --no-progress

- name: Run test suite
run: composer run-script test
Loading
Loading