Files
rspade_system/app/RSpade/tests/README.md
root 9ebcc359ae Fix code quality violations and enhance ROUTE-EXISTS-01 rule
Implement JQHTML function cache ID system and fix bundle compilation
Implement underscore prefix for system tables
Fix JS syntax linter to support decorators and grant exception to Task system
SPA: Update planning docs and wishlists with remaining features
SPA: Document Navigation API abandonment and future enhancements
Implement SPA browser integration with History API (Phase 1)
Convert contacts view page to SPA action
Convert clients pages to SPA actions and document conversion procedure
SPA: Merge GET parameters and update documentation
Implement SPA route URL generation in JavaScript and PHP
Implement SPA bootstrap controller architecture
Add SPA routing manual page (rsx:man spa)
Add SPA routing documentation to CLAUDE.md
Phase 4 Complete: Client-side SPA routing implementation
Update get_routes() consumers for unified route structure
Complete SPA Phase 3: PHP-side route type detection and is_spa flag
Restore unified routes structure and Manifest_Query class
Refactor route indexing and add SPA infrastructure
Phase 3 Complete: SPA route registration in manifest
Implement SPA Phase 2: Extract router code and test decorators
Rename Jqhtml_Component to Component and complete SPA foundation setup

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 17:48:15 +00:00

423 lines
10 KiB
Markdown
Executable File

# RSpade Testing Framework
## Philosophy
These tests verify RSpade framework functionality using real integration tests:
- **Real database** (`rspade_test` - completely isolated from production)
- **Real browser** (Playwright with Chromium for UI tests)
- **Real HTTP requests** (curl, Playwright)
- **No mocks** - test actual behavior, not simulations
Tests are designed to be:
1. **Self-contained** - Each test is a standalone directory with `run_test.sh`
2. **Explicit** - Verbose over DRY, clear over clever
3. **LLM-friendly** - Easy for AI to read, write, and maintain
4. **Evolutionary** - Patterns emerge organically as tests are written
## Running Tests
**Run all automated tests:**
```bash
./run_all_tests.sh
```
Automatically creates fresh database snapshot before running tests.
**Run all tests with existing snapshot (faster):**
```bash
./run_all_tests.sh --use-existing-snapshot
```
**Run all tests with full migration output:**
```bash
./run_all_tests.sh --full-output
```
**Run specific test:**
```bash
./basic/01_framework_verification/run_test.sh
```
**Run specific test without database reset:**
```bash
./basic/01_framework_verification/run_test.sh --skip-reset
```
Use with caution - only for manual testing or when composing tests.
## Test Database
All tests use the `rspade_test` database:
- Completely separate from `rspade` production database
- Created fresh: `rspade_test` (utf8mb4_unicode_ci)
- Full access granted to `rspade` user
- Reset before each top-level test (ensures deterministic behavior)
**Database reset process:**
1. Drop `rspade_test` database
2. Create `rspade_test` database
3. Either:
- **With snapshot**: Restore snapshot + run new migrations (~1 second)
- **Without snapshot**: Run all migrations (~5-10 seconds)
**Create snapshot** (first time or after adding migrations):
```bash
./_lib/db_snapshot_create.sh # Quiet mode
./_lib/db_snapshot_create.sh --full-output # Show all migration output
```
This creates `_lib/test_db_snapshot.sql` which speeds up all future test runs.
If snapshot creation fails, run with `--full-output` to see detailed error messages.
## Test Contract
Every test directory MUST have:
**`run_test.sh`** - Executable script that:
- Accepts `--skip-reset` flag to skip database reset (for test composition)
- Sets up test prerequisites
- Runs the actual test
- Cleans up after itself (trap EXIT)
- Exits with code 0 (pass) or 1 (fail)
- Outputs EXACTLY ONE LINE to stdout:
- `PASS: Test description`
- `FAIL: Test description - reason`
- `SKIP: Test description - reason`
### Test Composition with --skip-reset
Tests can call other tests to set up complex state:
```bash
# Complex test that needs attachments and users
echo "[SETUP] Creating prerequisite state..." >&2
"$TEST_DIR/../01_create_user/run_test.sh" --skip-reset
"$TEST_DIR/../02_add_attachment/run_test.sh" --skip-reset
# Now test the feature that requires both users and attachments
echo "[TEST] Testing feature with complex state..." >&2
# ... your test logic ...
```
**Important**: Only the top-level test (called without --skip-reset) resets the database. Sub-tests called with --skip-reset build upon the existing state.
**`README.md`** - Documents:
- What this test verifies
- Prerequisites/assumptions
- How to run standalone
- Known limitations
## Standard Test Pattern
### Basic Test Template
```bash
#!/bin/bash
set -e
TEST_NAME="Example Test"
TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Source test environment helpers
source "$TEST_DIR/../../_lib/test_env.sh"
# Parse arguments
SKIP_RESET=false
for arg in "$@"; do
case $arg in
--skip-reset)
SKIP_RESET=true
shift
;;
esac
done
# Ensure test mode exits on script exit (success or failure)
trap test_trap_exit EXIT
# SETUP
echo "[SETUP] Preparing test environment..." >&2
# Reset database unless --skip-reset
if [ "$SKIP_RESET" = false ]; then
"$TEST_DIR/../../_lib/db_reset.sh"
fi
# Enter test mode (switches Laravel to rspade_test database)
test_mode_enter
# TEST LOGIC
echo "[TEST] Running test logic..." >&2
# Your test code here
count=$(test_db_count users)
if [ "$count" -eq 0 ]; then
echo "PASS: $TEST_NAME"
exit 0
else
echo "FAIL: $TEST_NAME - Expected 0 users, found $count"
exit 1
fi
# TEARDOWN happens automatically via trap
```
### Key Patterns
- `set -e` - Exit on first error
- `trap test_trap_exit EXIT` - Ensures `.env` always restored
- `--skip-reset` flag - Enables test composition (calling other tests to build state)
- `test_mode_enter` - Switches Laravel to test database
- Log to stderr `>&2`, only PASS/FAIL to stdout
- Single exit point with clear status
## Shared Helpers
Located in `_lib/`, source in your test:
### test_env.sh
```bash
source "$TEST_DIR/../../_lib/test_env.sh"
test_mode_enter # Switch Laravel to rspade_test + run rsx:clean
test_mode_exit # Restore Laravel to rspade + run rsx:clean
test_trap_exit # Cleanup (call in trap EXIT)
test_db_query "SQL" # Execute SQL on test database
test_db_count "table" # Count rows in table
```
**Important**: `test_mode_enter` and `test_mode_exit` automatically run `rsx:clean` after switching the database in `.env` to ensure the manifest is rebuilt for the correct database environment.
### db_reset.sh
```bash
"$TEST_DIR/../../_lib/db_reset.sh" # Drop, create, migrate rspade_test
```
**Note**: Always check `--skip-reset` flag before calling db_reset.sh
## Test Organization
```
tests/
├── _lib/ Shared utilities (grow organically)
├── basic/ Template and fundamental tests
├── core/ Framework core functionality tests
└── features/ Feature-specific tests
```
## Writing New Tests
1. **Create test directory:**
```bash
mkdir -p basic/my_new_test
```
2. **Copy template:**
```bash
cp basic/01_framework_verification/run_test.sh basic/my_new_test/
```
3. **Edit test logic:**
- Update `TEST_NAME`
- Modify test logic section
- Update assertions
4. **Create README.md:**
```markdown
# My New Test
Tests that [feature] works correctly.
## What it verifies
- Thing 1
- Thing 2
## How to run
```bash
./basic/my_new_test/run_test.sh
```
5. **Make executable:**
```bash
chmod +x basic/my_new_test/run_test.sh
```
6. **Run standalone:**
```bash
./basic/my_new_test/run_test.sh
```
## Interactive Tests
Some tests require human verification and use `run_interactive_test.sh`:
- Not run by default in `run_all_tests.sh`
- Set up environment, provide instructions, wait for user input
- Useful for visual testing, complex flows, accessibility
**Example:**
```bash
#!/bin/bash
set -e
# ... setup code ...
echo "Instructions:"
echo "1. Visit http://localhost/login"
echo "2. Verify form displays correctly"
echo "3. Did test pass? (yes/no/skip): "
read -r response
case "$response" in
yes|y) echo "PASS: $TEST_NAME"; exit 0 ;;
no|n) echo "FAIL: $TEST_NAME"; exit 1 ;;
skip|s) echo "SKIP: $TEST_NAME"; exit 0 ;;
esac
```
Run interactive tests:
```bash
./run_interactive_tests.sh
```
## Best Practices
### Database
- Always use `--skip-reset` flag in test contract
- Default: reset database (safe but slow)
- Only skip reset when you know database state is correct
- Future: Snapshot/restore will make this fast
### Output
- Log progress to stderr: `echo "..." >&2`
- Only PASS/FAIL/SKIP to stdout
- Include useful context in failures: `FAIL: Test - Expected X got Y`
### Error Handling
- Use `set -e` to exit on first error
- Add cleanup trap: `trap test_trap_exit EXIT`
- Test edge cases (empty results, missing data)
### Test Independence
- Each test should work standalone
- Don't rely on other tests running first
- Reset database or seed required data
- Clean up temp files
## Common Patterns
### Pattern: Database Query Test
```bash
# Query test database
result=$(test_db_query "SELECT name FROM users WHERE id=1")
if [ "$result" = "Test User" ]; then
echo "PASS: User name correct"
else
echo "FAIL: Expected 'Test User', got '$result'"
exit 1
fi
```
### Pattern: Count Test
```bash
count=$(test_db_count users)
if [ "$count" -eq 5 ]; then
echo "PASS: Correct user count"
else
echo "FAIL: Expected 5 users, found $count"
exit 1
fi
```
### Pattern: API Test
```bash
response=$(curl -s http://localhost/_ajax/Test/method)
if echo "$response" | jq -e '.success == true' > /dev/null 2>&1; then
echo "PASS: API returns success"
else
echo "FAIL: API failed - $response"
exit 1
fi
```
## Database Snapshot System
The testing framework uses database snapshots to speed up test runs.
**How it works:**
1. First time: Run `_lib/db_snapshot_create.sh` to create pristine snapshot
2. Tests restore from snapshot (~1 second) instead of running all migrations (~5-10 seconds)
3. After snapshot restore, any new migrations run automatically
4. When you add new migrations, recreate snapshot for maximum speed
**When to recreate snapshot:**
- After adding new migration files
- After modifying existing migration files
- If you notice tests taking longer than expected
**Snapshot file:**
- Location: `_lib/test_db_snapshot.sql`
- Not committed to git (add to .gitignore if needed)
- Each developer creates their own snapshot locally
## Troubleshooting
**Test hangs:**
- Check for missing cleanup trap
- Look for processes not cleaned up
- Add timeout to long operations
**Database errors:**
- Verify `rspade_test` database exists
- Check credentials work: `mysql -urspade -prspadepass rspade_test`
- Ensure migrations ran: `mysql -urspade -prspadepass rspade_test -e "SHOW TABLES"`
**Environment not restored:**
- Check trap is set: `trap test_trap_exit EXIT`
- Verify `.env.backup` doesn't exist after test
- Manually restore: `test_mode_exit`
**Test passes alone, fails in suite:**
- Database state contamination
- Missing `--skip-reset` flag handling
- Shared resource conflict
## Evolution
This framework is intentionally minimal. As you write tests:
- Extract common patterns into `_lib/` helpers
- Update templates with better patterns
- Document new best practices here
- Don't over-abstract - keep tests explicit
When in doubt, favor:
- **Explicit** over implicit
- **Verbose** over DRY
- **Working** over elegant
## Testing the Test Framework
The first test is a meta-test that verifies the framework itself works:
```bash
./basic/01_framework_verification/run_test.sh
```
This test verifies:
- Test environment helpers load correctly
- Test mode enter/exit works
- Database reset works
- Migrations run successfully
- Test database is accessible