diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index fa9f80c..884a1a4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -6,6 +6,9 @@ on:
pull_request:
branches: [master]
+permissions:
+ contents: read
+
jobs:
frontend:
name: Frontend checks
@@ -36,6 +39,9 @@ jobs:
test:
name: PHP ${{ matrix.php }}
runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ id-token: write
strategy:
fail-fast: false
@@ -51,11 +57,11 @@ jobs:
with:
php-version: ${{ matrix.php }}
extensions: mbstring, xml, curl, intl, json, mysqlnd, sockets
- coverage: none
+ coverage: pcov
- name: Get Composer cache directory
id: composer-cache
- run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
+ run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"
- name: Cache Composer dependencies
uses: actions/cache@v4
@@ -84,7 +90,22 @@ jobs:
- name: Check PHP coding style
run: composer cs-check
+ - name: Run PHPUnit tests with coverage
+ if: github.event_name == 'pull_request'
+ run: composer test:coverage
+
+ - name: Upload PHP coverage to Codecov
+ if: github.event_name == 'pull_request'
+ uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
+ with:
+ fail_ci_if_error: true
+ files: build/logs/clover.xml
+ flags: php
+ name: php-8.4
+ use_oidc: true
+
- name: Run PHPUnit tests
+ if: github.event_name != 'pull_request'
run: composer test
- name: Run database integration tests
diff --git a/README.md b/README.md
index 3f2e2a5..5245114 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,7 @@
# CodeIgniter Chat
[](https://github.com/llbbl/codeigniter-chat/actions/workflows/ci.yml)
+[](https://codecov.io/gh/llbbl/codeigniter-chat)
This is a basic shoutboard built using CodeIgniter. Originally only used XML for the backend,
but was rewritten to illustrate different types of web services. The application has been migrated from
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 0000000..d370816
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,16 @@
+coverage:
+ status:
+ project:
+ default:
+ target: auto
+ threshold: 1%
+ patch:
+ default:
+ target: 80%
+
+comment:
+ layout: "condensed_header, diff, condensed_files, condensed_footer"
+ behavior: default
+ require_changes: false
+ require_base: false
+ require_head: true
diff --git a/composer.json b/composer.json
index c804fe5..4f658a9 100644
--- a/composer.json
+++ b/composer.json
@@ -59,8 +59,8 @@
"test:integration:mysql": "phpunit --testsuite Integration --group message-search --group database-constraints --group webhooks",
"test:integration:websocket": "phpunit --testsuite Integration --group integration-websocket",
"test:all": "phpunit --exclude-group integration-websocket",
- "test:coverage": "phpunit --colors --coverage-html build/logs/html",
- "test:coverage:text": "phpunit --colors --coverage-text",
+ "test:coverage": "phpunit --colors --testsuite UnitFeature --exclude-group integration,integration-websocket --coverage-clover build/logs/clover.xml --coverage-html build/logs/html",
+ "test:coverage:text": "phpunit --colors --testsuite UnitFeature --exclude-group integration,integration-websocket --coverage-text",
"infection": "@php -d memory_limit=1G vendor/bin/infection --threads=4 --show-mutations",
"analyse": "phpstan analyse --memory-limit=256M",
"cs-check": "php-cs-fixer fix --dry-run --diff",
diff --git a/docs/testing-procedures.md b/docs/testing-procedures.md
index 0620743..9520e74 100644
--- a/docs/testing-procedures.md
+++ b/docs/testing-procedures.md
@@ -48,8 +48,9 @@ Database tests are located in the `tests/database/` directory.
The CodeIgniter Chat project aims for high test coverage:
-- **Minimum Coverage**: 80% overall code coverage
-- **Target Coverage**: 90% for critical components
+- **Current Baseline**: 40.54% line coverage for the fast PHP suite
+- **Long-Term Target**: 80% line coverage and 70% branch coverage when the coverage toolchain supports that metric
+- **Pull Request Target**: At least 80% coverage for new and changed lines, without lowering project coverage by more than one percentage point
- **Uncovered Code**: Should be documented with a reason for exclusion
### Test Quality Requirements
@@ -435,4 +436,4 @@ The project uses GitHub Actions for continuous integration:
- [CodeIgniter 4 Testing Documentation](https://codeigniter.com/user_guide/testing/index.html)
- [PHPUnit Documentation](https://phpunit.readthedocs.io/)
-- [Test-Driven Development by Example](https://www.amazon.com/Test-Driven-Development-Kent-Beck/dp/0321146530) by Kent Beck
\ No newline at end of file
+- [Test-Driven Development by Example](https://www.amazon.com/Test-Driven-Development-Kent-Beck/dp/0321146530) by Kent Beck
diff --git a/docs/testing.md b/docs/testing.md
index dece7d3..f09edec 100644
--- a/docs/testing.md
+++ b/docs/testing.md
@@ -62,6 +62,20 @@ By default, the suite refuses to reuse processes already listening on its HTTP,
Shared behavior belongs in `e2e/support/chat-scenarios.ts`; keep one small spec in `e2e/` for each frontend route. Pull requests run the suite through `.github/workflows/e2e.yml`.
+## Code coverage
+
+Pull requests collect PHP line coverage with PCOV and upload the Clover report at `build/logs/clover.xml` to Codecov. Codecov records coverage history, reports project and patch coverage as status checks, and posts a coverage summary on each pull request. The project status permits a one percentage-point drop from the base commit; new and changed code must maintain at least 80% patch coverage.
+
+Run the same fast-suite report locally with PCOV enabled:
+
+```shell
+composer test:coverage
+```
+
+The initial fast-suite baseline measured on September 19, 2026 is **40.54% line coverage** (1,601 of 3,949 executable lines). The long-term target is 80% line coverage. PCOV intentionally prioritizes fast line coverage and does not collect branch coverage; the long-term branch-coverage target is 70% if the coverage toolchain later adds that metric without making routine pull-request feedback prohibitively slow.
+
+Views and route declarations are excluded because they are declarative boundaries. Coverage is one signal, not proof that assertions are meaningful. Pair it with the mutation-testing workflow below, which evaluates whether the suite detects behavioral changes.
+
## Mutation testing
[Infection](https://infection.github.io/) checks whether the PHP suite detects small changes to application behavior. Install and enable PCOV, then run:
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 63e2d70..065c4b6 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -36,6 +36,7 @@
./app/Config/Routes.php
+