Files
opensourcepos/tests
b9ad77ba07 fix(security): report unwritable .env.lock, make throttle limits configurable (#4714)
* fix(security): report unwritable .env.lock, make throttle limits configurable

envFileIsWritable() previously checked is_writable(.env) (the file), which passes in Docker even when the mutex file is root-owned by a prior env:provision run. It now checks the real write path: the directory (to create .env.tmp.* + .env.lock) and any existing .env.lock must be writable, so the app throws the clear 'run env:provision' error instead of crashing on 'Unable to open .env.lock'.

The Throttle filter now reads throttle.capacity / throttle.seconds from env (default 5 per 60s); capacity <= 0 disables throttling, so operators serving sequential HTTP clients (e.g. Zabbix) are not caught by the lockout.

* fix(security): address CodeRabbit review findings

- Throttle: validate throttle.capacity as an integer before treating a non-positive value as 'disabled', so a non-numeric value (e.g. 'five') falls back to the default instead of silently bypassing the lockout. Apply the same validation to throttle.seconds.

- envFileIsWritable(): also reject an existing non-writable .env on Windows (where rename() cannot replace a read-only destination); keep the check Windows-only since POSIX rename() replaces a read-only dest when the directory is writable.

- Tests: restore the prior throttle.capacity env state in ThrottleTest (capture/restore instead of delete); add an invalid-capacity fallback case; skip the not-writable fixtures when running as root (where is_writable() is bypassed).

* fix(migration): guard ConvertToCI4 key-write branches with envFileIsWritable()

The migration's 'no key' and 'CI3 key' branches called rotateEncryptionKey()/rotateEncryptionKeyTransaction() directly, bypassing the envFileIsWritable() guard that checkEncryption() uses. On a fresh Docker/Compose install where the web runtime cannot write /app/.env.lock, this produced a raw 'fopen(/app/.env.lock): Permission denied' error instead of the actionable 'run php spark env:provision' message.

A valid CI4 key now short-circuits to checkEncryption() (no write); every write branch is gated on envFileIsWritable() first.

* fix: read provisioned encryption.key so config sees it

env:provision persists the key as 'encryption.key' in .env (matching the
throttle path and .env.example), but Config\Encryption only read the
ENCRYPTION_KEY env var. On a fresh Docker instance the provisioned key was
invisible to config('Encryption')->key, so the app believed no key existed
and tried to write one -- hitting the .env.lock permission wall.

Read encryption.key (via $_SERVER/$_ENV/getenv) first, then fall back to
ENCRYPTION_KEY for Docker '-e' usage. Mirrors checkThrottleEncryption().

* fix: cascade encryption.key lookup past empty-string sources

* refactor: extract Encryption::resolveKey() and test it without global env mutation

* fix(security): decode fallback-selected encryption key like BaseConfig

A key picked in the constructor fallback (notably ENCRYPTION_KEY, which
BaseConfig never inspects) was assigned verbatim, bypassing the
hex2bin:/base64: decode the parent applies to `encryption.key`. Route the
selected key through a parseKey() helper mirroring BaseConfig's
parseEncryptionKey() so prefixed values decrypt consistently, and add a
pure regression test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: remove advisory ID from comments and tighten verbose comments

Per review: treat GHSA advisory IDs like secrets (drop from code) and
replace the multi-paragraph comments with concise one-liners that keep
the non-obvious "why". No logic changes.

---------

Co-authored-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-09-30 15:50:11 +02:00
..
2024-06-15 17:19:15 +02:00

Running Application Tests

This is the quick-start to CodeIgniter testing. Its intent is to describe what it takes to set up your application and get it ready to run unit tests. It is not intended to be a full description of the test features that you can use to test your application. Those details can be found in the documentation.

Resources

Requirements

It is recommended to use the latest version of PHPUnit. At the time of this writing, we are running version 9.x. Support for this has been built into the composer.json file that ships with CodeIgniter and can easily be installed via Composer if you don't already have it installed globally.

> composer install

If running under macOS or Linux, you can create a symbolic link to make running tests a touch nicer.

> ln -s ./vendor/bin/phpunit ./phpunit

You also need to install XDebug in order for code coverage to be calculated successfully. After installing XDebug, you must add xdebug.mode=coverage in the php.ini file to enable code coverage.

Setting Up

A number of the tests use a running database. In order to set up the database edit the details for the tests group in app/Config/Database.php or .env. Make sure that you provide a database engine that is currently running on your machine. More details on a test database setup are in the Testing Your Database section of the documentation.

Running the tests

The entire test suite can be run by simply typing one command-line command from the main directory.

> ./phpunit

If you are using Windows, use the following command.

> vendor\bin\phpunit

You can limit tests to those within a single test directory by specifying the directory name after phpunit.

> ./phpunit app/Models

Generating Code Coverage

To generate coverage information, including HTML reports you can view in your browser, you can use the following command:

> ./phpunit --colors --coverage-text=tests/coverage.txt --coverage-html=tests/coverage/ -d memory_limit=1024m

This runs all of the tests again collecting information about how many lines, functions, and files are tested. It also reports the percentage of the code that is covered by tests. It is collected in two formats: a simple text file that provides an overview as well as a comprehensive collection of HTML files that show the status of every line of code in the project.

The text file can be found at tests/coverage.txt. The HTML files can be viewed by opening tests/coverage/index.html in your favorite browser.

PHPUnit XML Configuration

The repository has a phpunit.xml.dist file in the project root that's used for PHPUnit configuration. This is used to provide a default configuration if you do not have your own configuration file in the project root.

The normal practice would be to copy phpunit.xml.dist to phpunit.xml (which is git ignored), and to tailor it as you see fit. For instance, you might wish to exclude database tests, or automatically generate HTML code coverage reports.

Test Cases

Every test needs a test case, or class that your tests extend. CodeIgniter 4 provides one class that you may use directly:

  • CodeIgniter\Test\CIUnitTestCase

Most of the time you will want to write your own test cases that extend CIUnitTestCase to hold functions and services common to your test suites.

Creating Tests

All tests go in the tests/ directory. Each test file is a class that extends a Test Case (see above) and contains methods for the individual tests. These method names must start with the word "test" and should have descriptive names for precisely what they are testing: testUserCanModifyFile() testOutputColorMatchesInput() testIsLoggedInFailsWithInvalidUser()

Writing tests is an art, and there are many resources available to help learn how. Review the links above and always pay attention to your code coverage.

Database Tests

Tests can include migrating, seeding, and testing against a mock or live database. Be sure to modify the test case (or create your own) to point to your seed and migrations and include any additional steps to be run before tests in the setUp() method. See Testing Your Database for details.