# API Test Runner --- ## CRED-6043 — 2026-04-30 ### Overview A lightweight, API-accessible test runner that lets any team member trigger test suites via an HTTP call — no CLI access, no browser session required. Each Jira ticket gets its own test suite folder. Tests run against the real Laravel controller endpoints, not isolated copies of the logic. --- ### Why This Exists Previously, tests for blade-side JS logic lived in a `unit_tests/` Jest folder. Those tests ran against extracted JS functions — meaning if someone changed the actual blade or controller logic, the tests would still pass. The new test runner solves this by: - Calling the actual `AccountingController` methods (the same code the blade calls via AJAX) - Being triggered via a single HTTP endpoint (curl, cron, CI pipeline) - Being organised by Jira ticket so every feature has a clear test home --- ### Architecture ``` app/TestRunner/ Contracts/ TestSuiteInterface.php — interface every suite must implement BaseTestSuite.php — shared run(), assert(), runCase() logic Tests/ CRED6043/ TestSuite.php — test suite for CRED-6043 (foreclosure logic) {JIRA_ID}/ TestSuite.php — add new suites here for future tickets app/Http/Middleware/ TestRunnerAuth.php — API key auth (reads TEST_RUNNER_API_KEY from .env) app/Http/Controllers/TestRunner/ TestRunnerController.php — single controller: resolves Jira ID → TestSuite class routes/ test_runner.php — two routes: GET (run suite) + POST (custom input) ``` --- ### Endpoints | Method | URL | Purpose | |--------|-----|---------| | `GET` | `/test-runner/{jira_id}` | Run all pre-defined test cases for a ticket | | `POST` | `/test-runner/{jira_id}` | Run with your own custom inputs | Both endpoints require a Bearer token (see Auth section below). Only active when `APP_ENV != production`. --- ### Auth The routes are protected by an API key. Add `TEST_RUNNER_API_KEY` to your `.env`: ```env TEST_RUNNER_API_KEY=your-secret-key-here ``` Pass it as a Bearer token in every request: ```bash curl http://your-domain/test-runner/CRED-6043 \ -H "Authorization: Bearer your-secret-key-here" ``` Missing or wrong token returns: ```json { "error": "Unauthorized. Provide a valid Bearer token." } ``` --- ### Usage #### Run all test cases for a ticket ```bash GET /test-runner/CRED-6043 Authorization: Bearer {TEST_RUNNER_API_KEY} ``` Response: ```json { "jira_id": "CRED-6043", "summary": { "total": 27, "passed": 27, "failed": 0, "status": "PASS" }, "results": [ { "suite": "validateForeclosurePayableAmount", "description": "bounce>0: rejects amount below minimum (9000 < 9500)", "passed": true, "expected": { "valid": false, "minimumAmount": 9500, "alertMessage": "The minimum amount payable is 9500" }, "actual": { "valid": false, "minimumAmount": 9500, "alertMessage": "The minimum amount payable is 9500" } } ] } ``` > `passed: true` means the **test assertion passed** (expected matched actual). > `valid: false` inside the result means the **payment was correctly rejected**. > These are independent — a test passes when the system behaves as expected. --- #### Test your own custom input ```bash POST /test-runner/CRED-6043 Authorization: Bearer {TEST_RUNNER_API_KEY} Content-Type: application/json { "payment_type": "foreclosure", "payable_amount": 9000, "bounce_charges": 500, "penal_charges": 0, "total_amount_due": 10000 } ``` Response: ```json { "valid": false, "minimumAmount": 9500, "alertMessage": "The minimum amount payable is 9500" } ``` #### Empty body — get a sample request If you POST with an empty body, the runner returns a sample input to help you get started: ```bash POST /test-runner/CRED-6043 Authorization: Bearer {TEST_RUNNER_API_KEY} Content-Type: application/json {} ``` Response `422`: ```json { "message": "Body is empty. Please retry with the sample input below.", "sample_body": { "payment_type": "foreclosure", "payable_amount": 9000, "bounce_charges": 500, "penal_charges": 0, "total_amount_due": 10000 } } ``` #### Ticket not found ```json { "error": "No test suite found for CRED-9999", "hint": "Create app/TestRunner/Tests/CRED9999/TestSuite.php extending App\\TestRunner\\BaseTestSuite" } ``` --- ### Adding a Test Suite for a New Ticket 1. Create the folder and file: ``` app/TestRunner/Tests/{JIRA_ID_WITHOUT_HYPHEN}/TestSuite.php ``` Example for `CRED-1234`: ``` app/TestRunner/Tests/CRED1234/TestSuite.php ``` 2. Extend `BaseTestSuite` and implement the four required methods: ```php 'example_value', ]; } public function custom(array $params): array { // Call the real controller/service with $params // Return the result as an array } protected function execute(array $case) { // Dispatch $case['fn'] to the right method // $case['input'] contains the test inputs } protected function cases(): array { return [ [ 'suite' => 'your_function_name', 'description' => 'what this test proves', 'fn' => 'your_function_name', 'input' => ['param1', 'param2'], 'expected' => 'expected_result', ], ]; } } ``` 3. Hit `GET /test-runner/CRED-1234` — no registration or config changes needed. --- ### How Tests Call Real Application Code The test suite calls the actual Laravel controller directly using a fake `Request` object. This means if the controller logic changes, the tests reflect it immediately — there is no separate copy of the logic to keep in sync. ```php // Inside CRED6043/TestSuite.php $request = new Request([ 'payment_type' => $params['payment_type'], 'payable_amount' => $params['payable_amount'], 'bounce_charges' => $params['bounce_charges'], 'penal_charges' => $params['penal_charges'], 'total_amount_due' => $params['total_amount_due'], ]); $response = (new AccountingController())->validateForeclosureAmount($request); return json_decode($response->getContent(), true); ``` --- ### CRED-6043 — Foreclosure Logic Tests Covers two changes made in `payment_v1.blade.php`: **1. `shouldLockPayableAmountField`** The payable-amount input is locked (readonly) only when `is_foreclosure = 1` AND both `interest_due = 0` AND `penal_charges = 0`. If either charge is present, the field stays editable so the admin can enter a custom amount. **2. `validateForeclosurePayableAmount`** When sharing a foreclosure payment link, if bounce or penal charges are present the admin cannot enter an amount below the minimum: ``` minimum = total_amount_due - (bounce_charges + penal_charges) ``` The blade now validates this server-side via `POST /admin/payment/validate-foreclosure-amount` instead of inline JS, so the logic is testable end-to-end. --- ### Related Files | File | Purpose | |------|---------| | `app/TestRunner/Contracts/TestSuiteInterface.php` | Interface all suites implement | | `app/TestRunner/BaseTestSuite.php` | Shared run/assert logic | | `app/TestRunner/Tests/CRED6043/TestSuite.php` | Foreclosure test cases | | `app/Http/Controllers/TestRunner/TestRunnerController.php` | Route handler | | `app/Http/Middleware/TestRunnerAuth.php` | API key auth | | `app/Services/Foreclosure/ForeclosureValidationService.php` | Foreclosure validation logic | | `app/Http/Controllers/Admin/AccountingController.php` | `validateForeclosureAmount()` method | | `routes/test_runner.php` | Test runner routes | | `routes/foreclosure_validation.php` | Admin endpoint for blade AJAX calls |