Skip to content

Latest commit

 

History

History
178 lines (132 loc) · 8.36 KB

File metadata and controls

178 lines (132 loc) · 8.36 KB

Contributing to the UnityHPC Platform Account Portal

Conventions

  • PHP version 8.3.
  • All pure PHP files are required to be formatted with Prettier.
  • All impure PHP files (mixed into HTML) are required to be linted with PSR-12 and formatted with phpcbf.
  • The maximum line length for any PHP file is 100 characters.
  • Comments should be used sparingly.
  • Empty lines should be used sparingly.
  • No code should fail quietly, instead exceptions should be thrown. PHP builtin functions that fail quietly (ex: json_encode) should be replaced with a wrapper in resources/utils.php.
  • No code should call die() or exit(), instead UnityHTTPD::die(). This will avoid the premature death of our automated testing processes.
  • No code should call assert(), instead \ensure(). This will enforce conditions even in production.
  • No code should call json_encode(), instead \jsonEncode(). This will throw errors and escape slashes by default.
  • No code should call mb_convert_encoding(), instead \mbConvertEncoding(). This will throw an exception rather than returning false.
  • No code should call mb_detect_encoding(), instead \mbDetectEncoding(). This will enable strict mode and throw an exception rather than returning false.
  • No code should call intval(), instead \str2int(). This will enable strict mode and throw an exception rather than issuing a warning.
  • UnityHTTPD's user-facing error functionality (ex: badRequest) should only be called from webroot/**/*.php. resources/**/*.php should throw exceptions instead.
  • all pages under webroot/admin/ must check for $USER->isAdmin() and call UnityHTTPD::forbidden() if not admin.

This repository will automatically check PRs for linting compliance.

Development Environment

Setting up your Environment

  1. Clone this repo (including submodules): git clone <this-repo> --recurse-submodules
  2. Install php v8.3
  3. Install PHP extensions (apt install php-curl php-intl php-ldap php-mbstring php-mysql php-pdo php-xml on Ubuntu)
  4. install composer
  5. install PHP dependencies: composer install
  6. If you're on Windows, use WSL
  7. Download and install docker desktop
    1. Linux users only: Download and install docker compose plugin (apt install docker-compose-plugin && ln -s /usr/libexec/docker/cli-plugins/docker-compose /bin/docker-compose on Ubuntu)
  8. In tools/docker-dev Run the build script: ./build.sh
  9. Run the environment: ./run.sh. Press CTRL+C to exit
  10. Install pre-commit
  11. setup pre-commit hooks: pre-commit install
  12. Install modern implementation of grep (not macOS builtin grep): brew install grep
  13. Install npm
  14. Install nodeJS tools: npm install

Environment Usage

While the environment is running, the following is accessible:

Test Users

The test environment ships with a number of users that can be used for testing. When accessing locked down portions of the portal, you will be asked for a username and password. The password is always password. tools/docker-dev/web/htpasswd contains all valid usernames.

Notable users:

  • user1@org1.test - admin, PI
  • user2@org1.test - not admin, not PI
  • user2000@org2.test - does not yet have an account
  • user2005@org1.test - regsitered but not qualified (not a PI or in a PI group)

Changes to Dev Environment

Should the default schema change, the ldap/bootstrap.ldif and sql/bootstrap.sql must be updated for the LDAP server and the MySQL server, respectively.

Testing

Github Actions are used to execute all following tests for all pull requests. This means that if you're feeling lazy, you don't have to go out of your way to run tests, you just have to wait a bit longer for Github to do it for you.

pre-commit

We use pre-commit for enforcing (and sometimes automatically fixing) the PSR-12 code standard, whitespace discrepancies, syntax validity, secret leak detection, and whatever else can be done quickly. pre-commit runs automatically every time you commit, assuming you set it up correctly. To save time, pre-commit only runs on the files with staged changes. To run on all files, use pre-commit run --all-files.

phpunit

Since this codebase was not written with testing in mind, and this codebase makes extensive use of external resources such as SQL and LDAP, most of our testing does not focus on isolated "units", but high level functionality. Our functional tests pretend to make HTTP requests by modifying global variables in the same way that a production webserver would. This is preferred over directly calling library code because it helps to test the PHP logic in the webpages themselves, rather than just the internals. For example, one functional test would be to set $_SERVER["REMOTE_USER"] to authenticate as a user, require "resources/init.php" to setup the $USER global variable, set $_POST["key"] = "ssh-rsa ..." and require "webroot/panel/account.php" to make that user enter a new SSH key in the HTML form in the account.php page. Once a user action has been taken, internal interfaces are used to verify the results.

To run phpunit, spawn 2 shells in differnt tabs:

tab 1:

cd ./tools/docker-dev
./build.sh
./run.sh

tab 2:

$ container="$(docker container ls | grep web | awk '{print $1}')"
$ docker exec -it "$container" bash
> cd /var/www/unity-web
> ./vendor/bin/phpunit /path/to/tests

For /path/to/tests/, you usually want ./test/functional/ but you can select a specific file to save time when troubleshooting specific tests.

Note: To enhance the stack traces in phpunit's output, pipe the output to ./test/phpunit-markup.php.

code coverage

phpunit has code coverage built in. It recommends the use of "strict code coverage", where every single test explicitly lists what functions it covers. That's a lot of work, so instead we accept what phpunit refers to as "risky unintentionally covered code". Using robiningelbrecht/phpunit-coverage-tools, our Github Actions testing will fail if the coverage falls below a certain percentage of lines of code. This percentage should be increased over time to just below whatever the current coverage is.

To run a code coverage test, use the same procedure for phpunit but add this argument: --coverage-text=/dev/stdout

LDAP cleanliness

Any test that makes changes to LDAP must clean up after itself using try/finally. When a test fails to clean up after itself, it can cause other tests to fail or become otherwise un-useful. Because LDAP may not always be clean, any test that relies on a certain LDAP state should assert everything about that state. To reset LDAP to a clean slate, just re-run the build.sh script.

Note: phpunit can misbehave when using expectException and try/finally, see #258.

creating the conditions for a test

Selecting users for tests happens with the get...User... family of functions from phpunit-bootstrap.php. Since this family of functions is growing large and their names long and complicated, it is better to start with a simpler state and create the desired conditions manually. For example, rather than using getUserWithOneKey, use getUserHasNoSSHKeys and add one key for them.

The LDAP entries available in the dev environment are defined in tools/docker-dev/identity/bootstrap.ldif. These entries may be subject to change. Only phpunit-bootstrap.php should have hard-coded references to these entries.

testing the HTTP API

When writing a test, it may be tempting to use the PHP API directly, but the HTTP API must also be tested.

Example:

using the PHP API:

private function requestGroupCreation()
{
    $USER->getPIGroup()->requestGroup();
}

using the HTTP API:

private function requestGroupCreation()
{
    http_post(
        __DIR__ . "/../../webroot/panel/new_account.php",
        ["new_user_sel" => "pi", "eula" => "agree", "confirm_pi" => "agree"]
    );
}

http_post is defined in phpunit-bootstrap.php.

It is fine to use the PHP API when making assertions and doing cleanup.