From 328229dce90e12b158fcfca0ef175773ccc1eb9a Mon Sep 17 00:00:00 2001 From: Holger Schmermbeck Date: Sat, 1 Nov 2025 18:05:58 +0100 Subject: [PATCH 1/4] feat: Add tenant_keys and person migrations with schema tests - Install laravel/sanctum ^4.2 and spatie/laravel-permission ^6.22 - Create tenant_keys table with envelope key storage (DEK/idx_key as BYTEA) - Create person table with encrypted fields (BYTEA), blind indexes, and tsvector for FTS - Add PostgreSQL GIN index on note_tsv for full-text search - Add composite indexes on (tenant_id, email_idx) and (tenant_id, phone_idx) - Switch phpunit.xml to use pgsql for testing (remove sqlite in-memory DB) - Add comprehensive PEST schema tests for both tables - Verify BYTEA types, tsvector type, indexes, and foreign key constraints Relates to #50 (PR-1) --- composer.json | 4 +- composer.lock | 149 +++++++++++++++++- ..._11_01_165633_create_tenant_keys_table.php | 38 +++++ .../2025_11_01_165901_create_person_table.php | 46 ++++++ phpunit.xml | 3 +- tests/Feature/PersonSchemaTest.php | 104 ++++++++++++ tests/Feature/TenantKeysSchemaTest.php | 54 +++++++ 7 files changed, 394 insertions(+), 4 deletions(-) create mode 100644 database/migrations/2025_11_01_165633_create_tenant_keys_table.php create mode 100644 database/migrations/2025_11_01_165901_create_person_table.php create mode 100644 tests/Feature/PersonSchemaTest.php create mode 100644 tests/Feature/TenantKeysSchemaTest.php diff --git a/composer.json b/composer.json index d9d219c..9e27d72 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,9 @@ "require": { "php": "^8.4", "laravel/framework": "^12.36", - "laravel/tinker": "^2.10.1" + "laravel/sanctum": "^4.2", + "laravel/tinker": "^2.10.1", + "spatie/laravel-permission": "^6.22" }, "require-dev": { "fakerphp/faker": "^1.23", diff --git a/composer.lock b/composer.lock index 9e2dfa4..d7c0c2d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "bc4e2c9929c273c34ede98c3b2d1bed7", + "content-hash": "8d74c4e4f05036508492120903d1e85f", "packages": [ { "name": "brick/math", @@ -1331,6 +1331,70 @@ }, "time": "2025-09-19T13:47:56+00:00" }, + { + "name": "laravel/sanctum", + "version": "v4.2.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/sanctum.git", + "reference": "fd6df4f79f48a72992e8d29a9c0ee25422a0d677" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/fd6df4f79f48a72992e8d29a9c0ee25422a0d677", + "reference": "fd6df4f79f48a72992e8d29a9c0ee25422a0d677", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/console": "^11.0|^12.0", + "illuminate/contracts": "^11.0|^12.0", + "illuminate/database": "^11.0|^12.0", + "illuminate/support": "^11.0|^12.0", + "php": "^8.2", + "symfony/console": "^7.0" + }, + "require-dev": { + "mockery/mockery": "^1.6", + "orchestra/testbench": "^9.0|^10.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Sanctum\\SanctumServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Sanctum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.", + "keywords": [ + "auth", + "laravel", + "sanctum" + ], + "support": { + "issues": "https://github.com/laravel/sanctum/issues", + "source": "https://github.com/laravel/sanctum" + }, + "time": "2025-07-09T19:45:24+00:00" + }, { "name": "laravel/serializable-closure", "version": "v2.0.6", @@ -3278,6 +3342,89 @@ }, "time": "2025-09-04T20:59:21+00:00" }, + { + "name": "spatie/laravel-permission", + "version": "6.22.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-permission.git", + "reference": "8c87966ddc21893bfda54b792047473703992625" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/8c87966ddc21893bfda54b792047473703992625", + "reference": "8c87966ddc21893bfda54b792047473703992625", + "shasum": "" + }, + "require": { + "illuminate/auth": "^8.12|^9.0|^10.0|^11.0|^12.0", + "illuminate/container": "^8.12|^9.0|^10.0|^11.0|^12.0", + "illuminate/contracts": "^8.12|^9.0|^10.0|^11.0|^12.0", + "illuminate/database": "^8.12|^9.0|^10.0|^11.0|^12.0", + "php": "^8.0" + }, + "require-dev": { + "laravel/passport": "^11.0|^12.0", + "laravel/pint": "^1.0", + "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0|^10.0", + "phpunit/phpunit": "^9.4|^10.1|^11.5" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\Permission\\PermissionServiceProvider" + ] + }, + "branch-alias": { + "dev-main": "6.x-dev", + "dev-master": "6.x-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\Permission\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Permission handling for Laravel 8.0 and up", + "homepage": "https://github.com/spatie/laravel-permission", + "keywords": [ + "acl", + "laravel", + "permission", + "permissions", + "rbac", + "roles", + "security", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-permission/issues", + "source": "https://github.com/spatie/laravel-permission/tree/6.22.0" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-10-27T21:58:45+00:00" + }, { "name": "symfony/clock", "version": "v7.3.0", diff --git a/database/migrations/2025_11_01_165633_create_tenant_keys_table.php b/database/migrations/2025_11_01_165633_create_tenant_keys_table.php new file mode 100644 index 0000000..ab44406 --- /dev/null +++ b/database/migrations/2025_11_01_165633_create_tenant_keys_table.php @@ -0,0 +1,38 @@ +id(); + $table->binary('dek_wrapped'); + $table->binary('dek_nonce'); + $table->binary('idx_wrapped'); + $table->binary('idx_nonce'); + $table->integer('key_version')->default(1); + $table->timestamp('created_at')->useCurrent(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('tenant_keys'); + } +}; diff --git a/database/migrations/2025_11_01_165901_create_person_table.php b/database/migrations/2025_11_01_165901_create_person_table.php new file mode 100644 index 0000000..5046fcc --- /dev/null +++ b/database/migrations/2025_11_01_165901_create_person_table.php @@ -0,0 +1,46 @@ +id(); + $table->foreignId('tenant_id')->constrained('tenant_keys')->onDelete('cascade'); + $table->binary('email_enc'); + $table->binary('email_idx'); + $table->binary('phone_enc'); + $table->binary('phone_idx'); + $table->text('note_enc')->nullable(); + $table->timestamps(); + + $table->index(['tenant_id', 'email_idx']); + $table->index(['tenant_id', 'phone_idx']); + }); + + DB::statement('ALTER TABLE person ADD COLUMN note_tsv tsvector'); + DB::statement('CREATE INDEX person_note_tsv_idx ON person USING GIN (note_tsv)'); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('person'); + } +}; diff --git a/phpunit.xml b/phpunit.xml index 3d22d0e..174ff05 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -30,8 +30,7 @@ SPDX-License-Identifier: CC0-1.0 - - + diff --git a/tests/Feature/PersonSchemaTest.php b/tests/Feature/PersonSchemaTest.php new file mode 100644 index 0000000..3d10bae --- /dev/null +++ b/tests/Feature/PersonSchemaTest.php @@ -0,0 +1,104 @@ +toBeTrue(); +}); + +test('person has correct columns', function () { + expect(Schema::hasColumns('person', [ + 'id', + 'tenant_id', + 'email_enc', + 'email_idx', + 'phone_enc', + 'phone_idx', + 'note_enc', + 'note_tsv', + 'created_at', + 'updated_at', + ]))->toBeTrue(); +}); + +test('person binary columns have bytea type', function () { + $columns = DB::select(" + SELECT column_name, data_type + FROM information_schema.columns + WHERE table_name = 'person' + AND column_name IN ('email_enc', 'email_idx', 'phone_enc', 'phone_idx') + "); + + expect($columns)->toHaveCount(4); + + foreach ($columns as $column) { + expect($column)->toBeObject(); + expect($column->data_type)->toBe('bytea'); // @phpstan-ignore property.nonObject + } +}); + +test('person note_tsv has tsvector type', function () { + $column = DB::selectOne(" + SELECT data_type + FROM information_schema.columns + WHERE table_name = 'person' + AND column_name = 'note_tsv' + "); + + expect($column)->toBeObject(); + expect($column->data_type)->toBe('tsvector'); // @phpstan-ignore property.nonObject +}); + +test('person has tenant_id email_idx composite index', function () { + $index = DB::selectOne(" + SELECT indexname + FROM pg_indexes + WHERE tablename = 'person' + AND indexdef LIKE '%tenant_id%email_idx%' + "); + + expect($index)->not->toBeNull(); +}); + +test('person has tenant_id phone_idx composite index', function () { + $index = DB::selectOne(" + SELECT indexname + FROM pg_indexes + WHERE tablename = 'person' + AND indexdef LIKE '%tenant_id%phone_idx%' + "); + + expect($index)->not->toBeNull(); +}); + +test('person has GIN index on note_tsv', function () { + $index = DB::selectOne(" + SELECT indexname, indexdef + FROM pg_indexes + WHERE tablename = 'person' + AND indexname = 'person_note_tsv_idx' + "); + + expect($index)->not->toBeNull(); + expect($index)->toBeObject(); + expect($index->indexdef)->toContain('USING gin'); // @phpstan-ignore property.nonObject +}); + +test('person has foreign key to tenant_keys', function () { + $fk = DB::selectOne(" + SELECT constraint_name + FROM information_schema.table_constraints + WHERE table_name = 'person' + AND constraint_type = 'FOREIGN KEY' + AND constraint_name LIKE '%tenant_id%' + "); + + expect($fk)->not->toBeNull(); +}); diff --git a/tests/Feature/TenantKeysSchemaTest.php b/tests/Feature/TenantKeysSchemaTest.php new file mode 100644 index 0000000..7284849 --- /dev/null +++ b/tests/Feature/TenantKeysSchemaTest.php @@ -0,0 +1,54 @@ +toBeTrue(); +}); + +test('tenant_keys has correct columns', function () { + expect(Schema::hasColumns('tenant_keys', [ + 'id', + 'dek_wrapped', + 'dek_nonce', + 'idx_wrapped', + 'idx_nonce', + 'key_version', + 'created_at', + ]))->toBeTrue(); +}); + +test('tenant_keys binary columns have bytea type', function () { + $columns = DB::select(" + SELECT column_name, data_type + FROM information_schema.columns + WHERE table_name = 'tenant_keys' + AND column_name IN ('dek_wrapped', 'dek_nonce', 'idx_wrapped', 'idx_nonce') + "); + + expect($columns)->toHaveCount(4); + + foreach ($columns as $column) { + expect($column)->toBeObject(); + expect($column->data_type)->toBe('bytea'); // @phpstan-ignore property.nonObject + } +}); + +test('tenant_keys key_version has integer type', function () { + $column = DB::selectOne(" + SELECT data_type + FROM information_schema.columns + WHERE table_name = 'tenant_keys' + AND column_name = 'key_version' + "); + + expect($column)->toBeObject(); + expect($column->data_type)->toBe('integer'); // @phpstan-ignore property.nonObject +}); From 6ec785eeaae7f4d9bf9fbc650c57c6b09dd27c39 Mon Sep 17 00:00:00 2001 From: Holger Schmermbeck Date: Sat, 1 Nov 2025 18:19:23 +0100 Subject: [PATCH 2/4] fix: Add PostgreSQL service to CI and run ALL tests - Replace reusable workflow call with inline job + PostgreSQL service - PostgreSQL 17-alpine service with health checks - Configure DB env vars for test job (localhost:5432) - Add driver check for PostgreSQL-specific tsvector/GIN index - All 17 tests pass (no skipped tests) - Pint, PHPStan, PEST all pass Addresses Copilot review comment about database-specific SQL Fixes CI test failures (was using SQLite, now PostgreSQL) --- .github/workflows/php-ci.yml | 53 ++++++++++++++++++- .../2025_11_01_165901_create_person_table.php | 7 ++- tests/Feature/PersonSchemaTest.php | 3 ++ tests/Feature/TenantKeysSchemaTest.php | 3 ++ 4 files changed, 63 insertions(+), 3 deletions(-) diff --git a/.github/workflows/php-ci.yml b/.github/workflows/php-ci.yml index 8316eb6..e94b34c 100644 --- a/.github/workflows/php-ci.yml +++ b/.github/workflows/php-ci.yml @@ -24,4 +24,55 @@ jobs: test: name: PEST Tests - uses: SecPal/.github/.github/workflows/reusable-php-test.yml@main + runs-on: ubuntu-latest + if: ${{ !startsWith(github.head_ref, 'spike/') && !startsWith(github.ref, 'refs/heads/spike/') }} + + services: + postgres: + image: postgres:17-alpine + env: + POSTGRES_DB: db + POSTGRES_USER: db + POSTGRES_PASSWORD: db + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.4" + extensions: mbstring, xml, ctype, iconv, intl, pdo, pdo_pgsql + coverage: xdebug + + - name: Get Composer Cache Directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + + - name: Cache Composer dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --prefer-dist --no-progress --no-interaction + + - name: Run tests + env: + DB_CONNECTION: pgsql + DB_HOST: localhost + DB_PORT: 5432 + DB_DATABASE: db + DB_USERNAME: db + DB_PASSWORD: db + run: php artisan test diff --git a/database/migrations/2025_11_01_165901_create_person_table.php b/database/migrations/2025_11_01_165901_create_person_table.php index 5046fcc..70e6d0c 100644 --- a/database/migrations/2025_11_01_165901_create_person_table.php +++ b/database/migrations/2025_11_01_165901_create_person_table.php @@ -32,8 +32,11 @@ public function up(): void $table->index(['tenant_id', 'phone_idx']); }); - DB::statement('ALTER TABLE person ADD COLUMN note_tsv tsvector'); - DB::statement('CREATE INDEX person_note_tsv_idx ON person USING GIN (note_tsv)'); + // PostgreSQL-specific full-text search support + if (DB::connection()->getDriverName() === 'pgsql') { + DB::statement('ALTER TABLE person ADD COLUMN note_tsv tsvector'); + DB::statement('CREATE INDEX person_note_tsv_idx ON person USING GIN (note_tsv)'); + } } /** diff --git a/tests/Feature/PersonSchemaTest.php b/tests/Feature/PersonSchemaTest.php index 3d10bae..31d7147 100644 --- a/tests/Feature/PersonSchemaTest.php +++ b/tests/Feature/PersonSchemaTest.php @@ -6,9 +6,12 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; +uses(RefreshDatabase::class); + test('person table exists', function () { expect(Schema::hasTable('person'))->toBeTrue(); }); diff --git a/tests/Feature/TenantKeysSchemaTest.php b/tests/Feature/TenantKeysSchemaTest.php index 7284849..7c58540 100644 --- a/tests/Feature/TenantKeysSchemaTest.php +++ b/tests/Feature/TenantKeysSchemaTest.php @@ -6,9 +6,12 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; +uses(RefreshDatabase::class); + test('tenant_keys table exists', function () { expect(Schema::hasTable('tenant_keys'))->toBeTrue(); }); From ccbc1c84f3f858e1f67975e38d7677989faa33eb Mon Sep 17 00:00:00 2001 From: Holger Schmermbeck Date: Sat, 1 Nov 2025 18:32:40 +0100 Subject: [PATCH 3/4] fix: Address all Copilot review comments and CI test failures - Add explicit return types to all test closures per Laravel guidelines - Add test for note_enc column in PersonSchemaTest (TEXT type verification) - Add DB_DATABASE=testing to phpunit.xml to avoid .env fallback confusion - Replace reusable quality workflow with inline PostgreSQL service job (Quality Checks PEST tests were failing with SQLite, now use postgres:17-alpine) - Both PHP CI and Quality Checks workflows now use identical PostgreSQL setup This resolves all 5 Copilot review comments: 1. Driver check for tsvector (already fixed in previous commit) 2. Missing return types in test closures 3. Missing note_enc column test 4. DB_DATABASE env var missing 5. Quality workflow tests failing due to missing PostgreSQL service All tests pass locally (18 passed, 42 assertions) with DDEV PostgreSQL. --- .github/workflows/quality.yml | 57 ++++++++++++++++++++++++-- phpunit.xml | 1 + tests/Feature/PersonSchemaTest.php | 28 +++++++++---- tests/Feature/TenantKeysSchemaTest.php | 8 ++-- 4 files changed, 78 insertions(+), 16 deletions(-) diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index a9659df..7a5b0f8 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -44,7 +44,56 @@ jobs: pest: name: PEST Tests - uses: SecPal/.github/.github/workflows/reusable-php-test.yml@main - with: - php-version: "8.4" - test-command: "./vendor/bin/pest" + runs-on: ubuntu-latest + # Skip tests for spike branches (exploration/prototyping) + # Spike branches are for experimentation and cannot be merged to main + # See: CONTRIBUTING.md - Spike Branch Policy + if: ${{ !startsWith(github.head_ref, 'spike/') && !startsWith(github.ref, 'refs/heads/spike/') }} + services: + postgres: + image: postgres:17-alpine + env: + POSTGRES_DB: testing + POSTGRES_USER: testing + POSTGRES_PASSWORD: testing + options: >- + --health-cmd="pg_isready -U testing" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + ports: + - 5432:5432 + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.4" + extensions: mbstring, xml, ctype, iconv, intl, pdo_pgsql + coverage: xdebug + + - name: Get Composer Cache Directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + + - name: Cache Composer dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --prefer-dist --no-progress --no-interaction + + - name: Run PEST tests + env: + DB_CONNECTION: pgsql + DB_HOST: localhost + DB_PORT: 5432 + DB_DATABASE: testing + DB_USERNAME: testing + DB_PASSWORD: testing + run: ./vendor/bin/pest diff --git a/phpunit.xml b/phpunit.xml index 174ff05..e27f3b5 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -31,6 +31,7 @@ SPDX-License-Identifier: CC0-1.0 + diff --git a/tests/Feature/PersonSchemaTest.php b/tests/Feature/PersonSchemaTest.php index 31d7147..4083796 100644 --- a/tests/Feature/PersonSchemaTest.php +++ b/tests/Feature/PersonSchemaTest.php @@ -12,11 +12,11 @@ uses(RefreshDatabase::class); -test('person table exists', function () { +test('person table exists', function (): void { expect(Schema::hasTable('person'))->toBeTrue(); }); -test('person has correct columns', function () { +test('person has correct columns', function (): void { expect(Schema::hasColumns('person', [ 'id', 'tenant_id', @@ -31,7 +31,7 @@ ]))->toBeTrue(); }); -test('person binary columns have bytea type', function () { +test('person binary columns have bytea type', function (): void { $columns = DB::select(" SELECT column_name, data_type FROM information_schema.columns @@ -47,7 +47,19 @@ } }); -test('person note_tsv has tsvector type', function () { +test('person note_enc column exists and has text type', function (): void { + $column = DB::selectOne(" + SELECT data_type + FROM information_schema.columns + WHERE table_name = 'person' + AND column_name = 'note_enc' + "); + + expect($column)->toBeObject(); + expect($column->data_type)->toBe('text'); // @phpstan-ignore property.nonObject +}); + +test('person note_tsv has tsvector type', function (): void { $column = DB::selectOne(" SELECT data_type FROM information_schema.columns @@ -59,7 +71,7 @@ expect($column->data_type)->toBe('tsvector'); // @phpstan-ignore property.nonObject }); -test('person has tenant_id email_idx composite index', function () { +test('person has tenant_id email_idx composite index', function (): void { $index = DB::selectOne(" SELECT indexname FROM pg_indexes @@ -70,7 +82,7 @@ expect($index)->not->toBeNull(); }); -test('person has tenant_id phone_idx composite index', function () { +test('person has tenant_id phone_idx composite index', function (): void { $index = DB::selectOne(" SELECT indexname FROM pg_indexes @@ -81,7 +93,7 @@ expect($index)->not->toBeNull(); }); -test('person has GIN index on note_tsv', function () { +test('person has GIN index on note_tsv', function (): void { $index = DB::selectOne(" SELECT indexname, indexdef FROM pg_indexes @@ -94,7 +106,7 @@ expect($index->indexdef)->toContain('USING gin'); // @phpstan-ignore property.nonObject }); -test('person has foreign key to tenant_keys', function () { +test('person has foreign key to tenant_keys', function (): void { $fk = DB::selectOne(" SELECT constraint_name FROM information_schema.table_constraints diff --git a/tests/Feature/TenantKeysSchemaTest.php b/tests/Feature/TenantKeysSchemaTest.php index 7c58540..f93221e 100644 --- a/tests/Feature/TenantKeysSchemaTest.php +++ b/tests/Feature/TenantKeysSchemaTest.php @@ -12,11 +12,11 @@ uses(RefreshDatabase::class); -test('tenant_keys table exists', function () { +test('tenant_keys table exists', function (): void { expect(Schema::hasTable('tenant_keys'))->toBeTrue(); }); -test('tenant_keys has correct columns', function () { +test('tenant_keys has correct columns', function (): void { expect(Schema::hasColumns('tenant_keys', [ 'id', 'dek_wrapped', @@ -28,7 +28,7 @@ ]))->toBeTrue(); }); -test('tenant_keys binary columns have bytea type', function () { +test('tenant_keys binary columns have bytea type', function (): void { $columns = DB::select(" SELECT column_name, data_type FROM information_schema.columns @@ -44,7 +44,7 @@ } }); -test('tenant_keys key_version has integer type', function () { +test('tenant_keys key_version has integer type', function (): void { $column = DB::selectOne(" SELECT data_type FROM information_schema.columns From 316da27087a085f3d019d56600512fe5a0d3e08b Mon Sep 17 00:00:00 2001 From: Holger Schmermbeck Date: Sat, 1 Nov 2025 18:41:47 +0100 Subject: [PATCH 4/4] fix: Rename PEST test step to match required check name Required status check expects 'PEST Tests / Run Tests' but step was named 'Run PEST tests'. Rename to 'Run Tests' to match branch protection rule. --- .github/workflows/quality.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 7a5b0f8..4427c92 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -88,7 +88,7 @@ jobs: - name: Install dependencies run: composer install --prefer-dist --no-progress --no-interaction - - name: Run PEST tests + - name: Run Tests env: DB_CONNECTION: pgsql DB_HOST: localhost