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>
This commit is contained in:
720
app/RSpade/tests/CLAUDE.md
Executable file
720
app/RSpade/tests/CLAUDE.md
Executable file
@@ -0,0 +1,720 @@
|
||||
# RSpade Testing Framework - AI Agent Instructions
|
||||
|
||||
**PURPOSE**: Comprehensive guidelines for AI agents working with RSpade framework tests.
|
||||
|
||||
## TESTING PHILOSOPHY
|
||||
|
||||
### The "Press Button, Light Turns Green" Philosophy
|
||||
|
||||
**Analogy**: You have a button that should turn a light green when pressed. If the button doesn't turn the light green, you **DO NOT**:
|
||||
- Paint the light green with a brush
|
||||
- Manually flip the light switch
|
||||
- Bypass the button with a workaround
|
||||
- Mark the test as passing
|
||||
|
||||
Instead, you **FIX THE BUTTON** so pressing it turns the light green.
|
||||
|
||||
**Application to Testing**:
|
||||
- Tests must be **fully automated** - one command to run, no manual intervention
|
||||
- Tests must be **deterministic** - same input always produces same output
|
||||
- Tests must be **repeatable** - can run multiple times with identical results
|
||||
- Tests must **require zero human assistance** to complete
|
||||
|
||||
**Manual intervention to help a test complete is FORBIDDEN.**
|
||||
|
||||
If you find yourself:
|
||||
- Running commands manually to fix test database state
|
||||
- Switching to production database because test database has issues
|
||||
- Modifying files between test runs to make tests pass
|
||||
- Skipping test assertions because they're "too hard to fix"
|
||||
|
||||
**STOP IMMEDIATELY** and report the issue to the user. These are symptoms that the test infrastructure is broken and needs fixing, NOT that you should work around the problem.
|
||||
|
||||
## CRITICAL TESTING MANDATES
|
||||
|
||||
**ABSOLUTE REQUIREMENTS - NO EXCEPTIONS:**
|
||||
|
||||
### 1. NEVER Switch to Production Database
|
||||
|
||||
Tests **MUST** pass on the test database (`rspade_test`).
|
||||
|
||||
**Why**: Switching databases to avoid errors completely defeats the purpose of testing. The test database IS the controlled environment where tests should work. If they don't work there, there's a real problem to fix.
|
||||
|
||||
**Forbidden**:
|
||||
```bash
|
||||
# ❌ WRONG - Bypassing test database
|
||||
mysql -urspade -prspadepass rspade -e "SELECT * FROM users" # Using production DB
|
||||
|
||||
# ❌ WRONG - Modifying test to use production
|
||||
DB_DATABASE=rspade ./run_test.sh
|
||||
```
|
||||
|
||||
**Correct**:
|
||||
```bash
|
||||
# ✅ CORRECT - Tests use test database exclusively
|
||||
test_db_query "SELECT * FROM users" # Helper function for test DB only
|
||||
```
|
||||
|
||||
### 2. NEVER Mark a Test as Passing If It's Failing
|
||||
|
||||
Do not write code to circumvent test failures or skip over difficult errors.
|
||||
|
||||
**Why**: Failing tests indicate real problems that must be fixed. A bypassed test is worse than no test - it creates false confidence.
|
||||
|
||||
**Forbidden**:
|
||||
```bash
|
||||
# ❌ WRONG - Hiding failures
|
||||
if ! some_test_command; then
|
||||
echo "PASS: Test (ignored failure)" # Lying about pass
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ❌ WRONG - Skipping hard assertions
|
||||
# if [ "$count" -ne 5 ]; then
|
||||
# echo "FAIL" # Commented out because "too hard to fix"
|
||||
# fi
|
||||
```
|
||||
|
||||
**Correct**:
|
||||
```bash
|
||||
# ✅ CORRECT - Fail loud and clear
|
||||
if [ "$count" -ne 5 ]; then
|
||||
echo "FAIL: Expected 5 records, found $count"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
### 3. When a Test Fails: Fix the Error OR Ask the User
|
||||
|
||||
**Priority order**:
|
||||
1. **Analyze the error** - Understand what's actually wrong
|
||||
2. **Attempt to fix the underlying issue** - Fix the button, not paint the light
|
||||
3. **If unclear or complex, STOP and ask user for direction** - Better to ask than guess
|
||||
4. **NEVER work around the failure** - Workarounds defeat the purpose
|
||||
|
||||
**Why**: Unexpected test errors are valuable - they reveal real issues. Simply skipping over them completely defeats the point of doing tests. The entire purpose of testing is to uncover unforeseen issues.
|
||||
|
||||
**Example - Wrong Approach**:
|
||||
```bash
|
||||
# Test fails because migrations didn't run
|
||||
# ❌ WRONG - Manually run migrations
|
||||
php artisan migrate --force
|
||||
# ❌ WRONG - Skip the test
|
||||
exit 0
|
||||
# ❌ WRONG - Switch to production database where migrations exist
|
||||
```
|
||||
|
||||
**Example - Correct Approach**:
|
||||
```bash
|
||||
# Test fails because migrations didn't run
|
||||
# ✅ CORRECT - Fix db_reset.sh to run migrations automatically
|
||||
# Update the db_reset.sh script to include migration step
|
||||
# Now test runs migrations automatically and passes
|
||||
```
|
||||
|
||||
**When to ask user**:
|
||||
- Error is cryptic or unclear
|
||||
- Multiple potential solutions exist
|
||||
- Fix would require architectural changes
|
||||
- Uncertain if behavior is intentional or a bug
|
||||
|
||||
**When NOT to ask user** (fix it yourself):
|
||||
- Simple typos or syntax errors
|
||||
- Missing files that should obviously exist
|
||||
- Clear logic errors in test code
|
||||
- Standard debugging (add logging, check return values, etc.)
|
||||
|
||||
## TEST STRUCTURE
|
||||
|
||||
### Test Location
|
||||
|
||||
**All framework tests**: `/system/app/RSpade/tests/`
|
||||
|
||||
```
|
||||
/system/app/RSpade/tests/
|
||||
├── CLAUDE.md # This file - AI agent instructions
|
||||
├── README.md # Human-readable test documentation
|
||||
├── _lib/ # Test utilities and helpers
|
||||
│ ├── test_env.sh # Test mode management functions
|
||||
│ ├── rsx_test_config.php # Manifest config for test mode
|
||||
│ ├── db_reset.sh # Database reset utility
|
||||
│ ├── db_snapshot_create.sh # Create database snapshot
|
||||
│ └── db_snapshot_restore.sh # Restore database snapshot
|
||||
├── services/ # Test service classes
|
||||
│ ├── service_test_service.php # Example test service
|
||||
│ └── scheduled_test_service.php # Scheduled task tests
|
||||
├── basic/ # Basic framework tests
|
||||
│ ├── 01_framework_verification/
|
||||
│ │ ├── run_test.sh # Test script
|
||||
│ │ └── README.md # Test documentation
|
||||
│ └── 02_database_connection/
|
||||
│ ├── run_test.sh
|
||||
│ └── README.md
|
||||
└── tasks/ # Task system tests
|
||||
└── 01_task_dispatch_and_execution/
|
||||
├── run_test.sh
|
||||
└── README.md
|
||||
```
|
||||
|
||||
### Test Script Pattern
|
||||
|
||||
Every test follows this pattern:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -e # Exit on any error
|
||||
|
||||
TEST_NAME="Descriptive Test Name"
|
||||
TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Source test environment helpers
|
||||
source "$TEST_DIR/../../_lib/test_env.sh"
|
||||
|
||||
# Parse arguments (for test composition)
|
||||
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..." >&2
|
||||
|
||||
# Enter test mode (switches to rspade_test database)
|
||||
test_mode_enter
|
||||
|
||||
# Reset database to known state (unless called as sub-test)
|
||||
if [ "$SKIP_RESET" = false ]; then
|
||||
echo "[SETUP] Resetting test database..." >&2
|
||||
"$TEST_DIR/../../_lib/db_reset.sh"
|
||||
fi
|
||||
|
||||
# TEST LOGIC
|
||||
echo "[TEST] Testing feature..." >&2
|
||||
|
||||
# Test assertions here
|
||||
# Use test_db_query, test_db_count helpers
|
||||
# Fail loud with clear error messages
|
||||
|
||||
# All tests passed
|
||||
echo "PASS: $TEST_NAME"
|
||||
exit 0
|
||||
|
||||
# TEARDOWN happens automatically via trap
|
||||
```
|
||||
|
||||
**Key elements**:
|
||||
- `set -e` - Any command failure fails the entire test
|
||||
- `trap test_trap_exit EXIT` - Ensures cleanup even on failure
|
||||
- `test_mode_enter` - Switches to test database
|
||||
- `db_reset.sh` - Ensures known database state
|
||||
- `--skip-reset` - For test composition (calling tests from other tests)
|
||||
- Clear `[SETUP]` and `[TEST]` prefixes in output
|
||||
- `echo ... >&2` - Test output goes to stderr, only final result to stdout
|
||||
|
||||
## TEST ENVIRONMENT MANAGEMENT
|
||||
|
||||
### Test Mode Functions
|
||||
|
||||
**Source the helpers** (required in every test script):
|
||||
```bash
|
||||
source /system/app/RSpade/tests/_lib/test_env.sh
|
||||
```
|
||||
|
||||
**Available functions**:
|
||||
|
||||
#### test_mode_enter()
|
||||
Switches environment to test mode.
|
||||
|
||||
**What it does**:
|
||||
1. Checks if already in test mode (fails if `DB_DATABASE=rspade_test`)
|
||||
2. Backs up current `.env` to `/var/www/html/system/.env.testbackup`
|
||||
3. Modifies `.env` to set `DB_DATABASE=rspade_test`
|
||||
4. Sets `RSX_ADDITIONAL_CONFIG=/var/www/html/system/app/RSpade/tests/_lib/rsx_test_config.php`
|
||||
5. Clears Laravel config cache
|
||||
6. Rebuilds manifest to include test directory
|
||||
|
||||
**Safety**: Refuses to create backup if already in test mode (prevents backing up test config as production).
|
||||
|
||||
#### test_mode_exit()
|
||||
Restores production environment.
|
||||
|
||||
**What it does**:
|
||||
1. Restores original `.env` from backup
|
||||
2. Clears Laravel config cache
|
||||
3. Rebuilds manifest without test directory
|
||||
|
||||
**Note**: Automatically called by `test_trap_exit` trap handler.
|
||||
|
||||
#### test_trap_exit()
|
||||
Cleanup handler for test scripts.
|
||||
|
||||
**Usage**:
|
||||
```bash
|
||||
trap test_trap_exit EXIT
|
||||
```
|
||||
|
||||
**What it does**:
|
||||
- Calls `test_mode_exit()` to restore environment
|
||||
- Does NOT delete backup file (contains production credentials)
|
||||
|
||||
#### test_db_query(sql)
|
||||
Execute SQL query on test database, return single value.
|
||||
|
||||
**Usage**:
|
||||
```bash
|
||||
name=$(test_db_query "SELECT name FROM users WHERE id=1")
|
||||
status=$(test_db_query "SELECT status FROM _tasks WHERE id=$task_id")
|
||||
```
|
||||
|
||||
**Returns**: Single value from first column of first row (or empty string if no results).
|
||||
|
||||
#### test_db_count(table [WHERE clause])
|
||||
Count rows in test database table.
|
||||
|
||||
**Usage**:
|
||||
```bash
|
||||
count=$(test_db_count "users")
|
||||
count=$(test_db_count "users WHERE active=1")
|
||||
count=$(test_db_count "_tasks WHERE status='pending'")
|
||||
```
|
||||
|
||||
**Returns**: Integer count of matching rows.
|
||||
|
||||
### Test Database Isolation
|
||||
|
||||
**CRITICAL**: Tests NEVER touch the production database (`rspade`). They use a completely separate database (`rspade_test`).
|
||||
|
||||
**Environment Variables During Test Mode**:
|
||||
- `DB_DATABASE=rspade_test` - Points to test database
|
||||
- `RSX_ADDITIONAL_CONFIG=.../rsx_test_config.php` - Loads test manifest config
|
||||
|
||||
**Test Manifest Config** (`/system/app/RSpade/tests/_lib/rsx_test_config.php`):
|
||||
```php
|
||||
return [
|
||||
'manifest' => [
|
||||
'scan_directories' => [
|
||||
'app/RSpade/tests', // Include framework tests in manifest
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
This allows test services, tasks, and other test code to be discovered by the manifest system **only when in test mode**, without polluting the production manifest.
|
||||
|
||||
### Database Reset Process
|
||||
|
||||
**Script**: `/system/app/RSpade/tests/_lib/db_reset.sh`
|
||||
|
||||
**What it does**:
|
||||
1. **Safety checks** - Verifies we're in test mode, fails loud if not
|
||||
2. **Drop/recreate** - `DROP DATABASE rspade_test; CREATE DATABASE rspade_test;`
|
||||
3. **Restore snapshot** - If snapshot exists, restore it (fast)
|
||||
4. **Run migrations** - `php artisan migrate --production` (runs any new migrations not in snapshot)
|
||||
|
||||
**Safety checks**:
|
||||
```bash
|
||||
# Ensures DB_DATABASE=rspade_test (fails if not)
|
||||
if ! grep -q "^DB_DATABASE=rspade_test" /var/www/html/.env; then
|
||||
echo "FATAL ERROR: db_reset.sh must be called from within test mode!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ensures test config is loaded (fails if not)
|
||||
if ! grep -q "^RSX_ADDITIONAL_CONFIG=.*rsx_test_config.php" /var/www/html/.env; then
|
||||
echo "FATAL ERROR: Test config not loaded!"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
**When to call**:
|
||||
- At the beginning of every test (unless using `--skip-reset` for test composition)
|
||||
- Ensures known database state for deterministic tests
|
||||
|
||||
**When NOT to call manually**:
|
||||
- Tests call it automatically - no manual intervention needed
|
||||
|
||||
## TEST COMPOSITION
|
||||
|
||||
### The --skip-reset Flag
|
||||
|
||||
**Purpose**: Allows tests to call other tests without resetting database state between them.
|
||||
|
||||
**Use case**: Building complex state by chaining simpler tests.
|
||||
|
||||
**Example**:
|
||||
```bash
|
||||
# Test A creates a user
|
||||
./tests/users/create_user.sh
|
||||
|
||||
# Test B needs a user to exist, calls Test A with --skip-reset
|
||||
./tests/users/create_user.sh --skip-reset
|
||||
# Now test database has a user from Test A
|
||||
# Test B can proceed with its logic without losing that state
|
||||
```
|
||||
|
||||
**Pattern**:
|
||||
```bash
|
||||
# In your test script
|
||||
SKIP_RESET=false
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--skip-reset)
|
||||
SKIP_RESET=true
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Later in setup
|
||||
if [ "$SKIP_RESET" = false ]; then
|
||||
"$TEST_DIR/../../_lib/db_reset.sh"
|
||||
fi
|
||||
```
|
||||
|
||||
**When to use**:
|
||||
- Calling prerequisite tests to set up state
|
||||
- Composing complex tests from simpler building blocks
|
||||
- Chaining tests that depend on each other's state
|
||||
|
||||
**When NOT to use**:
|
||||
- Running tests independently (always reset for determinism)
|
||||
- Tests should be runnable in isolation without `--skip-reset` by default
|
||||
|
||||
## WRITING NEW TESTS
|
||||
|
||||
### Step 1: Create Test Directory
|
||||
|
||||
```bash
|
||||
mkdir -p /system/app/RSpade/tests/category/NN_test_name
|
||||
cd /system/app/RSpade/tests/category/NN_test_name
|
||||
```
|
||||
|
||||
**Naming**:
|
||||
- `category/` - Group of related tests (basic, tasks, database, etc.)
|
||||
- `NN_` - Two-digit prefix for ordering (01_, 02_, etc.)
|
||||
- `test_name` - Descriptive name with underscores
|
||||
|
||||
### Step 2: Create run_test.sh
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
TEST_NAME="Descriptive Test Name"
|
||||
TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
source "$TEST_DIR/../../_lib/test_env.sh"
|
||||
|
||||
# --skip-reset support
|
||||
SKIP_RESET=false
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--skip-reset) SKIP_RESET=true; shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
trap test_trap_exit EXIT
|
||||
|
||||
# SETUP
|
||||
echo "[SETUP] Preparing test..." >&2
|
||||
test_mode_enter
|
||||
|
||||
if [ "$SKIP_RESET" = false ]; then
|
||||
echo "[SETUP] Resetting test database..." >&2
|
||||
"$TEST_DIR/../../_lib/db_reset.sh"
|
||||
fi
|
||||
|
||||
# TEST LOGIC
|
||||
echo "[TEST] Testing feature..." >&2
|
||||
|
||||
# Your test code here
|
||||
# Use test_db_query and test_db_count helpers
|
||||
|
||||
# Example assertion
|
||||
result=$(test_db_query "SELECT COUNT(*) FROM users")
|
||||
if [ "$result" != "0" ]; then
|
||||
echo "FAIL: $TEST_NAME - Expected 0 users, found $result"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# All tests passed
|
||||
echo "PASS: $TEST_NAME"
|
||||
exit 0
|
||||
```
|
||||
|
||||
Make it executable:
|
||||
```bash
|
||||
chmod +x run_test.sh
|
||||
```
|
||||
|
||||
### Step 3: Create README.md
|
||||
|
||||
Document what the test verifies, prerequisites, expected output, etc. See existing test READMEs for examples.
|
||||
|
||||
### Step 4: Test Your Test
|
||||
|
||||
**Critical**: Run your test multiple times to verify it's deterministic and repeatable.
|
||||
|
||||
```bash
|
||||
# Run once
|
||||
./run_test.sh
|
||||
|
||||
# Run again immediately - should pass again
|
||||
./run_test.sh
|
||||
|
||||
# Run a third time - still should pass
|
||||
./run_test.sh
|
||||
```
|
||||
|
||||
**If it fails on subsequent runs**:
|
||||
- Test is not properly resetting state
|
||||
- Test is leaving artifacts in database
|
||||
- Test has race conditions or timing issues
|
||||
- **FIX THE TEST** - don't work around it
|
||||
|
||||
### Writing Good Test Assertions
|
||||
|
||||
**Fail loud with clear error messages**:
|
||||
|
||||
```bash
|
||||
# ❌ BAD - Cryptic failure
|
||||
if [ "$count" != "5" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ✅ GOOD - Clear failure message
|
||||
if [ "$count" != "5" ]; then
|
||||
echo "FAIL: $TEST_NAME - Expected 5 records, found $count"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
**Test one thing per assertion**:
|
||||
|
||||
```bash
|
||||
# ✅ GOOD - Separate assertions
|
||||
if [ "$status" != "completed" ]; then
|
||||
echo "FAIL: $TEST_NAME - Expected status 'completed', got '$status'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$result" ]; then
|
||||
echo "FAIL: $TEST_NAME - Expected result, got empty string"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
**Use helper functions**:
|
||||
|
||||
```bash
|
||||
# ✅ GOOD - Using test_db_count helper
|
||||
count=$(test_db_count "_tasks WHERE status='pending'")
|
||||
if [ "$count" -ne 1 ]; then
|
||||
echo "FAIL: $TEST_NAME - Expected 1 pending task, found $count"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
## DEBUGGING FAILED TESTS
|
||||
|
||||
### Step 1: Read the Error Message
|
||||
|
||||
Failed tests should output clear error messages. Read them carefully.
|
||||
|
||||
```bash
|
||||
[TEST] ✓ Task dispatched with ID: 1
|
||||
FAIL: Task Dispatch and Execution - Expected status 'completed', got 'pending'
|
||||
```
|
||||
|
||||
**Analysis**: Task was dispatched but never completed. Issue is in task processing, not dispatch.
|
||||
|
||||
### Step 2: Add Debugging Output
|
||||
|
||||
Add temporary debugging to understand what's happening:
|
||||
|
||||
```bash
|
||||
# Add debugging
|
||||
echo "[DEBUG] Task ID: $task_id" >&2
|
||||
echo "[DEBUG] Status: $status" >&2
|
||||
echo "[DEBUG] Result: $result" >&2
|
||||
|
||||
# Run test
|
||||
./run_test.sh
|
||||
```
|
||||
|
||||
### Step 3: Query Database Directly
|
||||
|
||||
Use test helpers to inspect database state:
|
||||
|
||||
```bash
|
||||
# In your test script
|
||||
test_db_query "SELECT * FROM _tasks WHERE id=$task_id" >&2
|
||||
```
|
||||
|
||||
### Step 4: Verify Test Database State
|
||||
|
||||
Check that test database is properly reset:
|
||||
|
||||
```bash
|
||||
source _lib/test_env.sh
|
||||
test_mode_enter
|
||||
test_db_query "SHOW TABLES"
|
||||
test_mode_exit
|
||||
```
|
||||
|
||||
### Step 5: Fix Root Cause
|
||||
|
||||
**DO NOT**:
|
||||
- Skip the failing assertion
|
||||
- Work around the failure with try/catch or || true
|
||||
- Switch to production database
|
||||
- Modify the test to expect wrong behavior
|
||||
|
||||
**DO**:
|
||||
- Fix the underlying code being tested
|
||||
- Fix the test infrastructure (db_reset, test_env, etc.)
|
||||
- Ask user for guidance if unclear
|
||||
|
||||
## COMMON MISTAKES
|
||||
|
||||
### Mistake 1: Manual Database Setup
|
||||
|
||||
**Wrong**:
|
||||
```bash
|
||||
# Running commands manually before test
|
||||
mysql -urspade -prspadepass rspade_test -e "INSERT INTO users ..."
|
||||
./run_test.sh
|
||||
```
|
||||
|
||||
**Correct**:
|
||||
```bash
|
||||
# Test sets up its own state automatically
|
||||
./run_test.sh
|
||||
# Test calls db_reset.sh internally, sets up state, runs assertions
|
||||
```
|
||||
|
||||
### Mistake 2: Using Production Database
|
||||
|
||||
**Wrong**:
|
||||
```bash
|
||||
# Test fails on rspade_test, so switch to rspade
|
||||
sed -i 's/rspade_test/rspade/' .env
|
||||
./run_test.sh
|
||||
```
|
||||
|
||||
**Correct**:
|
||||
```bash
|
||||
# Fix the underlying issue so test passes on rspade_test
|
||||
# Maybe db_reset.sh isn't running migrations?
|
||||
# Maybe test service isn't in manifest?
|
||||
# FIX THE PROBLEM, don't bypass it
|
||||
```
|
||||
|
||||
### Mistake 3: Hiding Failures
|
||||
|
||||
**Wrong**:
|
||||
```bash
|
||||
# Test assertion fails, so comment it out
|
||||
# if [ "$count" -ne 5 ]; then
|
||||
# echo "FAIL"
|
||||
# exit 1
|
||||
# fi
|
||||
```
|
||||
|
||||
**Correct**:
|
||||
```bash
|
||||
# Fix why count isn't 5
|
||||
# Add debugging to understand what's happening
|
||||
echo "[DEBUG] Count: $count" >&2
|
||||
test_db_query "SELECT * FROM users" >&2
|
||||
# Then fix the underlying issue
|
||||
```
|
||||
|
||||
### Mistake 4: Non-Deterministic Tests
|
||||
|
||||
**Wrong**:
|
||||
```bash
|
||||
# Test depends on timestamp or random values
|
||||
expected=$(date +%s)
|
||||
actual=$(test_db_query "SELECT created_at FROM users WHERE id=1")
|
||||
if [ "$actual" != "$expected" ]; then
|
||||
echo "FAIL"
|
||||
fi
|
||||
```
|
||||
|
||||
**Correct**:
|
||||
```bash
|
||||
# Test for existence, not exact timestamp
|
||||
created_at=$(test_db_query "SELECT created_at FROM users WHERE id=1")
|
||||
if [ -z "$created_at" ]; then
|
||||
echo "FAIL: $TEST_NAME - created_at is empty"
|
||||
exit 1
|
||||
fi
|
||||
echo "[TEST] ✓ created_at is set" >&2
|
||||
```
|
||||
|
||||
## AI AGENT RESPONSIBILITIES
|
||||
|
||||
### When Writing Tests
|
||||
|
||||
1. **Follow the standard pattern** - Use existing tests as templates
|
||||
2. **Make tests deterministic** - Same input = same output, always
|
||||
3. **Make tests repeatable** - Run 3+ times to verify
|
||||
4. **Fail loud with clear messages** - Future debugging will thank you
|
||||
5. **Document what's being tested** - README.md for each test
|
||||
|
||||
### When Debugging Failed Tests
|
||||
|
||||
1. **Read the error message carefully** - It's telling you something
|
||||
2. **Analyze the root cause** - Don't jump to solutions
|
||||
3. **Fix the underlying issue** - Not the test, the actual problem
|
||||
4. **If unclear, ask the user** - Better to ask than guess wrong
|
||||
5. **Never work around failures** - Defeats the entire purpose
|
||||
|
||||
### When NOT to Create Tests
|
||||
|
||||
Tests should verify framework functionality, not individual features of user applications.
|
||||
|
||||
**Create tests for**:
|
||||
- Core framework systems (manifest, bundles, routing, auth)
|
||||
- Framework utilities (task system, file uploads, caching)
|
||||
- Critical infrastructure (database connections, migrations)
|
||||
|
||||
**Do NOT create tests for**:
|
||||
- User application features (unless explicitly requested)
|
||||
- One-off debugging (use artisan commands instead)
|
||||
- Features that aren't production-ready yet
|
||||
|
||||
### Red Flags - Stop and Ask User
|
||||
|
||||
If you encounter any of these, STOP and ask user for guidance:
|
||||
|
||||
1. **Test fails only on subsequent runs** - State isn't properly reset
|
||||
2. **Test requires manual setup steps** - Test infrastructure is broken
|
||||
3. **Test passes but output shows errors** - Something is being silently caught
|
||||
4. **Can't reproduce failure** - Race condition or non-deterministic test
|
||||
5. **Unclear what test should verify** - Specification ambiguity
|
||||
|
||||
## SUMMARY
|
||||
|
||||
**Remember these core principles**:
|
||||
|
||||
1. **Tests are fully automated** - One command, no manual steps
|
||||
2. **Tests are deterministic** - Same input = same output, always
|
||||
3. **Tests are repeatable** - Can run 100 times with identical results
|
||||
4. **Tests require zero human assistance** - Complete autonomously
|
||||
5. **Test failures are valuable** - They reveal real problems
|
||||
6. **Never work around test failures** - Fix the root cause or ask user
|
||||
7. **Manual intervention is forbidden** - Fix the infrastructure instead
|
||||
|
||||
**The "Press Button, Light Turns Green" Philosophy**: If the button doesn't turn the light green, fix the button. Don't paint the light green and declare success.
|
||||
|
||||
When in doubt, ask the user. It's better to raise a concern than silently compromise test integrity.
|
||||
422
app/RSpade/tests/README.md
Executable file
422
app/RSpade/tests/README.md
Executable file
@@ -0,0 +1,422 @@
|
||||
# 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
|
||||
64
app/RSpade/tests/_lib/db_reset.sh
Executable file
64
app/RSpade/tests/_lib/db_reset.sh
Executable file
@@ -0,0 +1,64 @@
|
||||
#!/bin/bash
|
||||
|
||||
# RSpade Test Database Reset Script
|
||||
#
|
||||
# Drops and recreates rspade_test database, then either:
|
||||
# 1. Restores from snapshot (fast) + runs any new migrations
|
||||
# 2. Runs all migrations from scratch (slow, if no snapshot exists)
|
||||
#
|
||||
# MUST be called from within test mode (after test_mode_enter)
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SNAPSHOT_FILE="$SCRIPT_DIR/test_db_snapshot.sql"
|
||||
|
||||
# SAFETY CHECK: Ensure we're in test mode
|
||||
if ! grep -q "^DB_DATABASE=rspade_test" /var/www/html/.env; then
|
||||
echo "FATAL ERROR: db_reset.sh must be called from within test mode!" >&2
|
||||
echo "" >&2
|
||||
echo "Current database: $(grep "^DB_DATABASE=" /var/www/html/.env | cut -d= -f2)" >&2
|
||||
echo "" >&2
|
||||
echo "This is a safety check to prevent accidentally resetting the production database." >&2
|
||||
echo "" >&2
|
||||
echo "Correct usage:" >&2
|
||||
echo " source /system/app/RSpade/tests/_lib/test_env.sh" >&2
|
||||
echo " test_mode_enter" >&2
|
||||
echo " _lib/db_reset.sh" >&2
|
||||
echo " # ... run tests ..." >&2
|
||||
echo " test_mode_exit" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! grep -q "^RSX_ADDITIONAL_CONFIG=.*rsx_test_config.php" /var/www/html/.env; then
|
||||
echo "FATAL ERROR: Test config not loaded!" >&2
|
||||
echo "" >&2
|
||||
echo "RSX_ADDITIONAL_CONFIG must point to rsx_test_config.php when running tests." >&2
|
||||
echo "This should be set automatically by test_mode_enter." >&2
|
||||
echo "" >&2
|
||||
echo "Current RSX_ADDITIONAL_CONFIG: $(grep "^RSX_ADDITIONAL_CONFIG=" /var/www/html/.env || echo 'NOT SET')" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[DB RESET] Dropping rspade_test database..." >&2
|
||||
mysql -h127.0.0.1 -urspade -prspadepass -e "DROP DATABASE IF EXISTS rspade_test" 2>/dev/null
|
||||
|
||||
echo "[DB RESET] Creating rspade_test database..." >&2
|
||||
mysql -h127.0.0.1 -urspade -prspadepass -e "CREATE DATABASE rspade_test CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci" 2>/dev/null
|
||||
|
||||
cd /var/www/html
|
||||
|
||||
if [ -f "$SNAPSHOT_FILE" ]; then
|
||||
# Fast path: Restore from snapshot
|
||||
echo "[DB RESET] Restoring from snapshot..." >&2
|
||||
mysql -h127.0.0.1 -urspade -prspadepass rspade_test < "$SNAPSHOT_FILE" 2>/dev/null
|
||||
|
||||
# Run any new migrations that were added after snapshot
|
||||
echo "[DB RESET] Running new migrations..." >&2
|
||||
php artisan migrate --production 2>&1 | grep -v "Nothing to migrate" >&2
|
||||
else
|
||||
# Slow path: Run all migrations from scratch
|
||||
echo "[DB RESET] No snapshot found, running all migrations..." >&2
|
||||
echo "[DB RESET] (Create snapshot with: _lib/db_snapshot_create.sh)" >&2
|
||||
php artisan migrate --production 2>&1 | grep -v "Nothing to migrate" >&2
|
||||
fi
|
||||
|
||||
echo "[DB RESET] Database reset complete" >&2
|
||||
103
app/RSpade/tests/_lib/db_snapshot_create.sh
Executable file
103
app/RSpade/tests/_lib/db_snapshot_create.sh
Executable file
@@ -0,0 +1,103 @@
|
||||
#!/bin/bash
|
||||
|
||||
# RSpade Test Database Snapshot Creator
|
||||
#
|
||||
# Creates a pristine snapshot of the test database after running all migrations.
|
||||
# This snapshot can be used to speed up test database resets.
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SNAPSHOT_FILE="$SCRIPT_DIR/test_db_snapshot.sql"
|
||||
FULL_OUTPUT=false
|
||||
|
||||
# Parse arguments
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--full-output)
|
||||
FULL_OUTPUT=true
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "[SNAPSHOT] Creating pristine test database snapshot..." >&2
|
||||
|
||||
# Source test environment helpers
|
||||
source "$SCRIPT_DIR/test_env.sh"
|
||||
|
||||
# Ensure test mode exits on script exit (success or failure)
|
||||
trap test_trap_exit EXIT
|
||||
|
||||
# Reset database and run ALL migrations from scratch
|
||||
echo "[SNAPSHOT] Resetting test database..." >&2
|
||||
|
||||
if [ "$FULL_OUTPUT" = true ]; then
|
||||
# Show full migration output
|
||||
"$SCRIPT_DIR/db_reset.sh"
|
||||
else
|
||||
# Suppress output, capture errors
|
||||
TEMP_OUTPUT="/tmp/snapshot_output_$$"
|
||||
if ! "$SCRIPT_DIR/db_reset.sh" > "$TEMP_OUTPUT" 2>&1; then
|
||||
echo "" >&2
|
||||
echo "[ERROR] Database provisioning failed" >&2
|
||||
echo "[ERROR] Run with --full-output to see details:" >&2
|
||||
echo "[ERROR] $0 --full-output" >&2
|
||||
echo "" >&2
|
||||
cat "$TEMP_OUTPUT" >&2
|
||||
rm -f "$TEMP_OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
rm -f "$TEMP_OUTPUT"
|
||||
fi
|
||||
|
||||
# Enter test mode to verify database is accessible
|
||||
test_mode_enter > /dev/null 2>&1
|
||||
|
||||
# Verify migrations ran
|
||||
migration_count=$(test_db_query "SELECT COUNT(*) FROM migrations")
|
||||
if [ -z "$migration_count" ] || [ "$migration_count" -eq 0 ]; then
|
||||
echo "[ERROR] No migrations found in database" >&2
|
||||
echo "[ERROR] Run with --full-output to see details:" >&2
|
||||
echo "[ERROR] $0 --full-output" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[SNAPSHOT] Found $migration_count migrations" >&2
|
||||
|
||||
# Exit test mode before dump
|
||||
test_mode_exit > /dev/null 2>&1
|
||||
|
||||
# Create snapshot
|
||||
echo "[SNAPSHOT] Creating snapshot file..." >&2
|
||||
|
||||
# Backup current .env
|
||||
SNAPSHOT_ENV_BACKUP="/tmp/rspade_snapshot_backup_$$"
|
||||
cp /var/www/html/.env "$SNAPSHOT_ENV_BACKUP"
|
||||
|
||||
# Switch to test database for dump
|
||||
sed -i 's/^DB_DATABASE=.*$/DB_DATABASE=rspade_test/' /var/www/html/.env
|
||||
php artisan config:clear > /dev/null 2>&1
|
||||
|
||||
# Create dump
|
||||
if ! mysqldump -h127.0.0.1 -urspade -prspadepass rspade_test \
|
||||
--no-tablespaces \
|
||||
--single-transaction \
|
||||
--quick \
|
||||
--lock-tables=false \
|
||||
> "$SNAPSHOT_FILE" 2>/dev/null; then
|
||||
echo "[ERROR] Failed to create database dump" >&2
|
||||
mv "$SNAPSHOT_ENV_BACKUP" /var/www/html/.env
|
||||
php artisan config:clear > /dev/null 2>&1
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Restore original .env
|
||||
mv "$SNAPSHOT_ENV_BACKUP" /var/www/html/.env
|
||||
php artisan config:clear > /dev/null 2>&1
|
||||
|
||||
# Get snapshot size
|
||||
snapshot_size=$(du -h "$SNAPSHOT_FILE" | cut -f1)
|
||||
|
||||
echo "[SNAPSHOT] Snapshot created successfully ($snapshot_size)" >&2
|
||||
echo "[SNAPSHOT] Tests will now use this snapshot for faster database resets" >&2
|
||||
18
app/RSpade/tests/_lib/rsx_test_config.php
Executable file
18
app/RSpade/tests/_lib/rsx_test_config.php
Executable file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
/**
|
||||
* RSpade Test Configuration
|
||||
*
|
||||
* Additional configuration loaded when RSX_ADDITIONAL_CONFIG env variable is set.
|
||||
* Used by test runner to include tests directory in manifest scanning.
|
||||
*
|
||||
* Loaded by: test_mode_enter.sh
|
||||
* Cleared by: test_mode_exit.sh
|
||||
*/
|
||||
|
||||
return [
|
||||
'manifest' => [
|
||||
'scan_directories' => [
|
||||
'app/RSpade/tests', // Include framework tests directory in manifest
|
||||
],
|
||||
],
|
||||
];
|
||||
928
app/RSpade/tests/_lib/test_db_snapshot.sql
Executable file
928
app/RSpade/tests/_lib/test_db_snapshot.sql
Executable file
@@ -0,0 +1,928 @@
|
||||
-- MySQL dump 10.13 Distrib 8.0.43, for Linux (x86_64)
|
||||
--
|
||||
-- Host: 127.0.0.1 Database: rspade_test
|
||||
-- ------------------------------------------------------
|
||||
-- Server version 8.0.43-0ubuntu0.22.04.2
|
||||
|
||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||
/*!50503 SET NAMES utf8mb4 */;
|
||||
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
||||
/*!40103 SET TIME_ZONE='+00:00' */;
|
||||
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
||||
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
||||
|
||||
--
|
||||
-- Table structure for table `client_departments`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `client_departments`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `client_departments` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`site_id` bigint NOT NULL,
|
||||
`client_id` bigint NOT NULL,
|
||||
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`created_by` bigint DEFAULT NULL,
|
||||
`created_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updated_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
`updated_by` bigint DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_site_id` (`site_id`),
|
||||
KEY `idx_client_id` (`client_id`),
|
||||
KEY `idx_name` (`name`),
|
||||
KEY `created_at` (`created_at`),
|
||||
KEY `updated_at` (`updated_at`),
|
||||
CONSTRAINT `fk_client_departments_client` FOREIGN KEY (`client_id`) REFERENCES `clients` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `client_departments`
|
||||
--
|
||||
|
||||
LOCK TABLES `client_departments` WRITE;
|
||||
/*!40000 ALTER TABLE `client_departments` DISABLE KEYS */;
|
||||
/*!40000 ALTER TABLE `client_departments` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Table structure for table `clients`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `clients`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `clients` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`site_id` bigint NOT NULL,
|
||||
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`address` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
|
||||
`city` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`state` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`zip` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`phone` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`fax` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`phone_secondary` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`website` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`billing_contact_id` bigint DEFAULT NULL,
|
||||
`priority` bigint NOT NULL DEFAULT '2',
|
||||
`notes` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
|
||||
`created_by` bigint DEFAULT NULL,
|
||||
`owner_user_id` bigint DEFAULT NULL,
|
||||
`created_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updated_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
`updated_by` bigint DEFAULT NULL,
|
||||
`address_street` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'renamed from address',
|
||||
`address_country` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT 'USA',
|
||||
`industry` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`company_size` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '1-10, 11-50, 51-200, 201-500, 501-1000, 1000+',
|
||||
`established_year` bigint DEFAULT NULL,
|
||||
`revenue_range` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`facebook_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`twitter_handle` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`linkedin_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`instagram_handle` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`tags` json DEFAULT NULL,
|
||||
`status` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'active' COMMENT 'active, inactive, prospect, archived',
|
||||
`preferred_contact_method` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT 'email' COMMENT 'email, phone, LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, any',
|
||||
`newsletter_opt_in` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`deleted_at` timestamp(3) NULL DEFAULT NULL,
|
||||
`deleted_by` bigint DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_site_id` (`site_id`),
|
||||
KEY `idx_name` (`name`),
|
||||
KEY `idx_created_by_user_id` (`created_by`),
|
||||
KEY `idx_owner_user_id` (`owner_user_id`),
|
||||
KEY `idx_billing_contact_id` (`billing_contact_id`),
|
||||
KEY `idx_priority` (`priority`),
|
||||
KEY `created_at` (`created_at`),
|
||||
KEY `updated_at` (`updated_at`),
|
||||
KEY `idx_email` (`email`),
|
||||
KEY `idx_status` (`status`),
|
||||
KEY `idx_deleted_at` (`deleted_at`),
|
||||
CONSTRAINT `fk_clients_billing_contact` FOREIGN KEY (`billing_contact_id`) REFERENCES `contacts` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `clients`
|
||||
--
|
||||
|
||||
LOCK TABLES `clients` WRITE;
|
||||
/*!40000 ALTER TABLE `clients` DISABLE KEYS */;
|
||||
/*!40000 ALTER TABLE `clients` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Table structure for table `contacts`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `contacts`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `contacts` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`site_id` bigint NOT NULL,
|
||||
`client_id` bigint NOT NULL,
|
||||
`first_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`last_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`client_department_id` bigint DEFAULT NULL,
|
||||
`email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`email_secondary` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`phone_work` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`phone_cell` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`phone_other` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`address` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
|
||||
`city` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`state` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`zip` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`reports_to_contact_id` bigint DEFAULT NULL,
|
||||
`is_active` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`priority` bigint NOT NULL DEFAULT '2',
|
||||
`notes` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
|
||||
`created_by` bigint DEFAULT NULL,
|
||||
`owner_user_id` bigint DEFAULT NULL,
|
||||
`created_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updated_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
`updated_by` bigint DEFAULT NULL,
|
||||
`deleted_at` timestamp(3) NULL DEFAULT NULL,
|
||||
`deleted_by` bigint DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_site_id` (`site_id`),
|
||||
KEY `idx_client_id` (`client_id`),
|
||||
KEY `idx_first_name` (`first_name`),
|
||||
KEY `idx_last_name` (`last_name`),
|
||||
KEY `idx_email` (`email`),
|
||||
KEY `idx_client_department_id` (`client_department_id`),
|
||||
KEY `idx_reports_to_contact_id` (`reports_to_contact_id`),
|
||||
KEY `idx_is_active` (`is_active`),
|
||||
KEY `idx_priority` (`priority`),
|
||||
KEY `idx_created_by_user_id` (`created_by`),
|
||||
KEY `idx_owner_user_id` (`owner_user_id`),
|
||||
KEY `created_at` (`created_at`),
|
||||
KEY `updated_at` (`updated_at`),
|
||||
KEY `idx_deleted_at` (`deleted_at`),
|
||||
CONSTRAINT `fk_contacts_client` FOREIGN KEY (`client_id`) REFERENCES `clients` (`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_contacts_department` FOREIGN KEY (`client_department_id`) REFERENCES `client_departments` (`id`) ON DELETE SET NULL,
|
||||
CONSTRAINT `fk_contacts_reports_to` FOREIGN KEY (`reports_to_contact_id`) REFERENCES `contacts` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `contacts`
|
||||
--
|
||||
|
||||
LOCK TABLES `contacts` WRITE;
|
||||
/*!40000 ALTER TABLE `contacts` DISABLE KEYS */;
|
||||
/*!40000 ALTER TABLE `contacts` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Table structure for table `countries`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `countries`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `countries` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`alpha2` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'ISO 3166-1 alpha-2 code (US, CA, GB)',
|
||||
`alpha3` varchar(3) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'ISO 3166-1 alpha-3 code (USA, CAN, GBR)',
|
||||
`numeric` varchar(3) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'ISO 3166-1 numeric code (840, 124, 826)',
|
||||
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'Official country name',
|
||||
`common_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'Common name if different from official',
|
||||
`enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Active status - disable instead of delete to preserve FKs',
|
||||
`created_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updated_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
`created_by` bigint DEFAULT NULL,
|
||||
`updated_by` bigint DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `alpha2` (`alpha2`),
|
||||
KEY `idx_alpha2` (`alpha2`),
|
||||
KEY `idx_alpha3` (`alpha3`),
|
||||
KEY `idx_enabled` (`enabled`),
|
||||
KEY `created_at` (`created_at`),
|
||||
KEY `updated_at` (`updated_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `countries`
|
||||
--
|
||||
|
||||
LOCK TABLES `countries` WRITE;
|
||||
/*!40000 ALTER TABLE `countries` DISABLE KEYS */;
|
||||
/*!40000 ALTER TABLE `countries` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Table structure for table `demo_products`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `demo_products`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `demo_products` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`description` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
|
||||
`price` decimal(10,2) NOT NULL,
|
||||
`status_id` bigint NOT NULL DEFAULT '1',
|
||||
`category_id` bigint NOT NULL DEFAULT '1',
|
||||
`created_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updated_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
`created_by` bigint DEFAULT NULL,
|
||||
`updated_by` bigint DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_status_id` (`status_id`),
|
||||
KEY `idx_category_id` (`category_id`),
|
||||
KEY `created_at` (`created_at`),
|
||||
KEY `updated_at` (`updated_at`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `demo_products`
|
||||
--
|
||||
|
||||
LOCK TABLES `demo_products` WRITE;
|
||||
/*!40000 ALTER TABLE `demo_products` DISABLE KEYS */;
|
||||
INSERT INTO `demo_products` VALUES (1,'Wireless Mouse','Ergonomic wireless mouse with 2.4GHz connectivity',29.99,1,1,'2025-11-16 19:37:32.241','2025-11-16 19:37:32.241',NULL,NULL),(2,'Vintage T-Shirt','Classic cotton t-shirt with retro design',19.99,1,2,'2025-11-16 19:37:32.241','2025-11-16 19:37:32.241',NULL,NULL),(3,'JavaScript: The Good Parts','Essential guide to JavaScript programming',39.99,2,3,'2025-11-16 19:37:32.241','2025-11-16 19:37:32.241',NULL,NULL),(4,'Premium Coffee Beans','Single-origin Arabica beans from Colombia',24.99,1,4,'2025-11-16 19:37:32.241','2025-11-16 19:37:32.241',NULL,NULL),(5,'Gaming Laptop','High-performance laptop with RTX graphics',1299.99,3,1,'2025-11-16 19:37:32.241','2025-11-16 19:37:32.241',NULL,NULL);
|
||||
/*!40000 ALTER TABLE `demo_products` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Table structure for table `file_attachments`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `file_attachments`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `file_attachments` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`key` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`file_storage_id` bigint NOT NULL,
|
||||
`file_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`file_extension` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`file_type_id` bigint DEFAULT NULL,
|
||||
`width` bigint DEFAULT NULL COMMENT 'Width in pixels for images/videos',
|
||||
`height` bigint DEFAULT NULL COMMENT 'Height in pixels for images/videos',
|
||||
`duration` bigint DEFAULT NULL COMMENT 'Duration in seconds for videos/animated images',
|
||||
`is_animated` tinyint(1) DEFAULT '0' COMMENT 'Whether this is an animated image (GIF, WebP, APNG)',
|
||||
`frame_count` bigint DEFAULT NULL COMMENT 'Number of frames in animated images',
|
||||
`fileable_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`fileable_id` bigint DEFAULT NULL,
|
||||
`fileable_category` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`fileable_type_meta` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`fileable_order` bigint DEFAULT NULL,
|
||||
`fileable_meta` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
|
||||
`site_id` bigint NOT NULL,
|
||||
`session_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`created_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updated_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
`created_by` bigint DEFAULT NULL,
|
||||
`updated_by` bigint DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_files_key` (`key`),
|
||||
KEY `idx_files_file_hash_id` (`file_storage_id`),
|
||||
KEY `idx_files_site_id` (`site_id`),
|
||||
KEY `idx_files_fileable` (`fileable_type`,`fileable_id`),
|
||||
KEY `idx_files_file_type_id` (`file_type_id`),
|
||||
KEY `created_at` (`created_at`),
|
||||
KEY `updated_at` (`updated_at`),
|
||||
KEY `idx_file_attachments_type_meta` (`fileable_type_meta`),
|
||||
KEY `idx_session_id` (`session_id`),
|
||||
CONSTRAINT `file_attachments_ibfk_1` FOREIGN KEY (`file_storage_id`) REFERENCES `file_storage` (`id`) ON DELETE RESTRICT,
|
||||
CONSTRAINT `file_attachments_ibfk_2` FOREIGN KEY (`site_id`) REFERENCES `sites` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `file_attachments`
|
||||
--
|
||||
|
||||
LOCK TABLES `file_attachments` WRITE;
|
||||
/*!40000 ALTER TABLE `file_attachments` DISABLE KEYS */;
|
||||
/*!40000 ALTER TABLE `file_attachments` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Table structure for table `file_storage`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `file_storage`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `file_storage` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`hash` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`size` bigint NOT NULL,
|
||||
`created_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updated_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
`created_by` bigint DEFAULT NULL,
|
||||
`updated_by` bigint DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_file_hashes_hash` (`hash`),
|
||||
KEY `idx_file_hashes_size` (`size`),
|
||||
KEY `created_at` (`created_at`),
|
||||
KEY `updated_at` (`updated_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `file_storage`
|
||||
--
|
||||
|
||||
LOCK TABLES `file_storage` WRITE;
|
||||
/*!40000 ALTER TABLE `file_storage` DISABLE KEYS */;
|
||||
/*!40000 ALTER TABLE `file_storage` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Table structure for table `flash_alerts`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `flash_alerts`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `flash_alerts` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`session_id` bigint DEFAULT NULL,
|
||||
`type_id` bigint NOT NULL,
|
||||
`message` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`created_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`created_by` bigint DEFAULT NULL,
|
||||
`updated_by` bigint DEFAULT NULL,
|
||||
`updated_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_session_id` (`session_id`),
|
||||
KEY `created_at` (`created_at`),
|
||||
KEY `updated_at` (`updated_at`),
|
||||
CONSTRAINT `flash_alerts_session_fk` FOREIGN KEY (`session_id`) REFERENCES `sessions` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `flash_alerts`
|
||||
--
|
||||
|
||||
LOCK TABLES `flash_alerts` WRITE;
|
||||
/*!40000 ALTER TABLE `flash_alerts` DISABLE KEYS */;
|
||||
/*!40000 ALTER TABLE `flash_alerts` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Table structure for table `ip_addresses`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `ip_addresses`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `ip_addresses` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`ip_address` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`city` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`state` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`country` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`lat` double DEFAULT NULL,
|
||||
`lng` double DEFAULT NULL,
|
||||
`created_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updated_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
`created_by` bigint DEFAULT NULL,
|
||||
`updated_by` bigint DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_ip_addresses_ip` (`ip_address`),
|
||||
KEY `idx_ip_addresses_country` (`country`),
|
||||
KEY `idx_ip_addresses_location` (`lat`,`lng`),
|
||||
KEY `created_at` (`created_at`),
|
||||
KEY `updated_at` (`updated_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `ip_addresses`
|
||||
--
|
||||
|
||||
LOCK TABLES `ip_addresses` WRITE;
|
||||
/*!40000 ALTER TABLE `ip_addresses` DISABLE KEYS */;
|
||||
/*!40000 ALTER TABLE `ip_addresses` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Table structure for table `login_users`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `login_users`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `login_users` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`is_activated` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`is_verified` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`status_id` bigint NOT NULL DEFAULT '1',
|
||||
`remember_token` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`last_login` timestamp(3) NULL DEFAULT NULL,
|
||||
`created_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updated_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
`created_by` bigint DEFAULT NULL,
|
||||
`updated_by` bigint DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_login_users_email` (`email`),
|
||||
KEY `idx_login_users_is_activated` (`is_activated`),
|
||||
KEY `idx_login_users_is_verified` (`is_verified`),
|
||||
KEY `idx_login_users_status_id` (`status_id`),
|
||||
KEY `idx_login_users_created_at` (`created_at`),
|
||||
KEY `idx_login_users_updated_at` (`updated_at`),
|
||||
KEY `idx_login_users_last_login` (`last_login`),
|
||||
KEY `created_at` (`created_at`),
|
||||
KEY `updated_at` (`updated_at`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `login_users`
|
||||
--
|
||||
|
||||
LOCK TABLES `login_users` WRITE;
|
||||
/*!40000 ALTER TABLE `login_users` DISABLE KEYS */;
|
||||
INSERT INTO `login_users` VALUES (1,'admin@test.com','$2y$10$jROh4YFvV3tNRCPLrNgmC.aIJfP.yLkUZCY39fNuKcWJ16h9xlhe.',1,1,1,NULL,NULL,'2025-11-16 19:37:30.000','2025-11-16 19:37:30.000',NULL,NULL);
|
||||
/*!40000 ALTER TABLE `login_users` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Table structure for table `migrations`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `migrations`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `migrations` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`migration` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`batch` bigint NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=48 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `migrations`
|
||||
--
|
||||
|
||||
LOCK TABLES `migrations` WRITE;
|
||||
/*!40000 ALTER TABLE `migrations` DISABLE KEYS */;
|
||||
INSERT INTO `migrations` VALUES (1,'2024_01_01_000000_create_sessions_table',1),(2,'2025_09_03_054656_create_sites_table',2),(3,'2025_09_03_054826_create_users_table',3),(4,'2025_09_03_054950_create_file_hashes_table',4),(5,'2025_09_03_054950_create_files_table',5),(6,'2025_09_03_054950_create_ip_addresses_table',6),(7,'2025_09_03_054950_create_site_users_table',7),(8,'2025_09_03_054951_create_user_invites_table',8),(9,'2025_09_03_054951_create_user_verifications_table',9),(10,'2025_09_03_152057_create_admin_test_user',10),(11,'2025_09_03_164754_add_rsx_columns_to_sessions_table',11),(12,'2025_09_09_102518_create_flash_alerts_table',12),(13,'2025_09_09_173210_update_flash_alerts_foreign_key_cascade',13),(14,'2025_09_11_235349_fix_flash_alerts_session_id_schema',14),(15,'2025_09_12_185559_add_csrf_token_to_sessions_table',15),(16,'2025_09_16_074547_add_status_to_users_table',16),(17,'2025_09_16_075234_rename_status_column_to_status_id',17),(18,'2025_09_16_075915_create_demo_products_table',18),(19,'2025_09_16_080116_fix_flash_alerts_foreign_key',19),(20,'2025_09_30_023430_drop_and_recreate_session_table',20),(21,'2025_09_30_050004_add_last_login_to_users_table',21),(22,'2025_10_14_172351_add_experience_id_to_sessions',22),(23,'2025_10_28_223500_create_geographic_data_tables',23),(24,'2025_11_02_063218_rename_file_tables_for_storage_attachment_refactor',24),(25,'2025_11_02_065329_add_metadata_fields_to_file_attachments',25),(26,'2025_11_02_065347_create_file_thumbnails_table',26),(27,'2025_11_02_070055_create_search_indexes_table',27),(28,'2025_11_02_162826_add_dimensions_to_file_attachments',28),(29,'2025_11_04_011033_add_name_and_phone_columns_to_users_table',29),(30,'2025_11_04_011050_create_user_profiles_table',30),(31,'2025_11_04_014825_add_session_id_to_file_attachments_table',31),(32,'2025_11_04_051746_create_login_users_table',32),(33,'2025_11_04_051828_refactor_site_users_to_users_table',33),(34,'2025_11_04_051907_update_sessions_table_for_login_user_id',34),(35,'2025_11_04_051935_update_user_profiles_foreign_key',35),(36,'2025_11_04_181832_add_invite_columns_to_users_table',36),(37,'2025_11_05_012345_make_login_user_id_nullable_in_users_table',37),(38,'2025_11_13_193740_modify_flash_alerts_table_for_type_based_system',38),(39,'2025_11_16_093331_drop_file_thumbnails_table',39),(40,'2025_10_10_021141_create_companies_table',40),(41,'2025_10_10_021204_create_company_departments_table',41),(42,'2025_10_10_021223_create_contacts_table',42),(43,'2025_10_10_021244_add_billing_contact_foreign_key_to_companies',43),(44,'2025_10_28_025035_create_projects_table',44),(45,'2025_10_28_025113_create_tasks_table',45),(46,'2025_10_28_043559_create_clients_table',46),(47,'2025_10_29_034934_add_soft_deletes_to_core_tables',47);
|
||||
/*!40000 ALTER TABLE `migrations` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Table structure for table `projects`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `projects`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `projects` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`site_id` bigint NOT NULL,
|
||||
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`description` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
|
||||
`client_id` bigint NOT NULL,
|
||||
`client_department_id` bigint DEFAULT NULL,
|
||||
`contact_id` bigint DEFAULT NULL,
|
||||
`status` bigint NOT NULL DEFAULT '1',
|
||||
`priority` bigint NOT NULL DEFAULT '2',
|
||||
`start_date` date DEFAULT NULL,
|
||||
`due_date` date DEFAULT NULL,
|
||||
`completed_date` date DEFAULT NULL,
|
||||
`budget` decimal(15,2) DEFAULT NULL,
|
||||
`notes` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
|
||||
`created_by` bigint DEFAULT NULL,
|
||||
`owner_user_id` bigint DEFAULT NULL,
|
||||
`created_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updated_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
`updated_by` bigint DEFAULT NULL,
|
||||
`deleted_at` timestamp(3) NULL DEFAULT NULL,
|
||||
`deleted_by` bigint DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_site_id` (`site_id`),
|
||||
KEY `idx_client_id` (`client_id`),
|
||||
KEY `idx_client_department_id` (`client_department_id`),
|
||||
KEY `idx_contact_id` (`contact_id`),
|
||||
KEY `idx_status` (`status`),
|
||||
KEY `idx_priority` (`priority`),
|
||||
KEY `idx_created_by` (`created_by`),
|
||||
KEY `idx_owner_user_id` (`owner_user_id`),
|
||||
KEY `idx_created_at` (`created_at`),
|
||||
KEY `idx_updated_at` (`updated_at`),
|
||||
KEY `idx_name` (`name`),
|
||||
KEY `created_at` (`created_at`),
|
||||
KEY `updated_at` (`updated_at`),
|
||||
KEY `idx_deleted_at` (`deleted_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `projects`
|
||||
--
|
||||
|
||||
LOCK TABLES `projects` WRITE;
|
||||
/*!40000 ALTER TABLE `projects` DISABLE KEYS */;
|
||||
/*!40000 ALTER TABLE `projects` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Table structure for table `regions`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `regions`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `regions` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`code` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'ISO 3166-2 code (US-CA, CA-ON, GB-ENG)',
|
||||
`country_alpha2` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'Foreign key to countries.alpha2',
|
||||
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'Subdivision name (California, Ontario, England)',
|
||||
`type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'Type: state, province, territory, country, etc.',
|
||||
`enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Active status - disable instead of delete',
|
||||
`created_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updated_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
`created_by` bigint DEFAULT NULL,
|
||||
`updated_by` bigint DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `unique_country_code` (`country_alpha2`,`code`),
|
||||
KEY `idx_country` (`country_alpha2`),
|
||||
KEY `idx_code` (`code`),
|
||||
KEY `idx_enabled` (`enabled`),
|
||||
KEY `created_at` (`created_at`),
|
||||
KEY `updated_at` (`updated_at`),
|
||||
CONSTRAINT `fk_regions_country` FOREIGN KEY (`country_alpha2`) REFERENCES `countries` (`alpha2`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `regions`
|
||||
--
|
||||
|
||||
LOCK TABLES `regions` WRITE;
|
||||
/*!40000 ALTER TABLE `regions` DISABLE KEYS */;
|
||||
/*!40000 ALTER TABLE `regions` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Table structure for table `search_indexes`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `search_indexes`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `search_indexes` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`indexable_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`indexable_id` bigint NOT NULL,
|
||||
`content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
|
||||
`metadata` json DEFAULT NULL,
|
||||
`indexed_at` timestamp(3) NULL DEFAULT NULL,
|
||||
`extraction_method` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`language` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`site_id` bigint NOT NULL,
|
||||
`created_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updated_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
`created_by` bigint DEFAULT NULL,
|
||||
`updated_by` bigint DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `unique_indexable` (`indexable_type`,`indexable_id`),
|
||||
KEY `idx_indexable` (`indexable_type`,`indexable_id`),
|
||||
KEY `idx_site` (`site_id`),
|
||||
KEY `idx_language` (`language`),
|
||||
KEY `idx_indexed_at` (`indexed_at`),
|
||||
KEY `created_at` (`created_at`),
|
||||
KEY `updated_at` (`updated_at`),
|
||||
FULLTEXT KEY `ft_content` (`content`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `search_indexes`
|
||||
--
|
||||
|
||||
LOCK TABLES `search_indexes` WRITE;
|
||||
/*!40000 ALTER TABLE `search_indexes` DISABLE KEYS */;
|
||||
/*!40000 ALTER TABLE `search_indexes` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Table structure for table `sessions`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `sessions`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `sessions` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`active` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`site_id` bigint DEFAULT '0',
|
||||
`login_user_id` bigint DEFAULT NULL,
|
||||
`session_token` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`csrf_token` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`ip_address` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`user_agent` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`last_active` datetime(3) NOT NULL,
|
||||
`version` bigint NOT NULL DEFAULT '1',
|
||||
`created_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updated_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
`created_by` bigint DEFAULT NULL,
|
||||
`updated_by` bigint DEFAULT NULL,
|
||||
`experience_id` bigint NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `sessions_session_token_unique` (`session_token`),
|
||||
KEY `sessions_active_index` (`active`),
|
||||
KEY `sessions_last_active_index` (`last_active`),
|
||||
KEY `sessions_active_last_active_index` (`active`,`last_active`),
|
||||
KEY `created_at` (`created_at`),
|
||||
KEY `updated_at` (`updated_at`),
|
||||
KEY `idx_experience_id` (`experience_id`),
|
||||
KEY `idx_token_experience` (`session_token`,`experience_id`),
|
||||
KEY `idx_sessions_login_user_id` (`login_user_id`),
|
||||
KEY `idx_sessions_login_user_id_active` (`login_user_id`,`active`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `sessions`
|
||||
--
|
||||
|
||||
LOCK TABLES `sessions` WRITE;
|
||||
/*!40000 ALTER TABLE `sessions` DISABLE KEYS */;
|
||||
/*!40000 ALTER TABLE `sessions` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Table structure for table `sites`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `sites`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `sites` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`slug` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`is_enabled` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`deleted_at` timestamp(3) NULL DEFAULT NULL,
|
||||
`created_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updated_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
`created_by` bigint DEFAULT NULL,
|
||||
`updated_by` bigint DEFAULT NULL,
|
||||
`deleted_by` bigint DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_sites_slug` (`slug`),
|
||||
KEY `idx_sites_is_enabled` (`is_enabled`),
|
||||
KEY `idx_sites_deleted_at` (`deleted_at`),
|
||||
KEY `created_at` (`created_at`),
|
||||
KEY `updated_at` (`updated_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `sites`
|
||||
--
|
||||
|
||||
LOCK TABLES `sites` WRITE;
|
||||
/*!40000 ALTER TABLE `sites` DISABLE KEYS */;
|
||||
/*!40000 ALTER TABLE `sites` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Table structure for table `tasks`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `tasks`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `tasks` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`site_id` bigint NOT NULL,
|
||||
`title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`description` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
|
||||
`taskable_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`taskable_id` bigint NOT NULL,
|
||||
`status` bigint NOT NULL DEFAULT '1',
|
||||
`priority` bigint NOT NULL DEFAULT '2',
|
||||
`due_date` date DEFAULT NULL,
|
||||
`completed_date` date DEFAULT NULL,
|
||||
`assigned_to_user_id` bigint DEFAULT NULL,
|
||||
`notes` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
|
||||
`created_by` bigint DEFAULT NULL,
|
||||
`created_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updated_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
`updated_by` bigint DEFAULT NULL,
|
||||
`deleted_at` timestamp(3) NULL DEFAULT NULL,
|
||||
`deleted_by` bigint DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_site_id` (`site_id`),
|
||||
KEY `idx_taskable` (`taskable_type`,`taskable_id`),
|
||||
KEY `idx_status` (`status`),
|
||||
KEY `idx_priority` (`priority`),
|
||||
KEY `idx_assigned_to_user_id` (`assigned_to_user_id`),
|
||||
KEY `idx_created_by` (`created_by`),
|
||||
KEY `idx_created_at` (`created_at`),
|
||||
KEY `idx_updated_at` (`updated_at`),
|
||||
KEY `idx_title` (`title`),
|
||||
KEY `created_at` (`created_at`),
|
||||
KEY `updated_at` (`updated_at`),
|
||||
KEY `idx_deleted_at` (`deleted_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `tasks`
|
||||
--
|
||||
|
||||
LOCK TABLES `tasks` WRITE;
|
||||
/*!40000 ALTER TABLE `tasks` DISABLE KEYS */;
|
||||
/*!40000 ALTER TABLE `tasks` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Table structure for table `user_invites`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `user_invites`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `user_invites` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`user_id` bigint NOT NULL,
|
||||
`site_id` bigint NOT NULL,
|
||||
`invite_code` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`expires_at` timestamp(3) NOT NULL,
|
||||
`created_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updated_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
`created_by` bigint DEFAULT NULL,
|
||||
`updated_by` bigint DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_user_invites_code` (`invite_code`),
|
||||
KEY `idx_user_invites_user_id` (`user_id`),
|
||||
KEY `idx_user_invites_site_id` (`site_id`),
|
||||
KEY `idx_user_invites_expires_at` (`expires_at`),
|
||||
KEY `created_at` (`created_at`),
|
||||
KEY `updated_at` (`updated_at`),
|
||||
CONSTRAINT `fk_user_invites_user_id` FOREIGN KEY (`user_id`) REFERENCES `login_users` (`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `user_invites_ibfk_2` FOREIGN KEY (`site_id`) REFERENCES `sites` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `user_invites`
|
||||
--
|
||||
|
||||
LOCK TABLES `user_invites` WRITE;
|
||||
/*!40000 ALTER TABLE `user_invites` DISABLE KEYS */;
|
||||
/*!40000 ALTER TABLE `user_invites` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Table structure for table `user_profiles`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `user_profiles`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `user_profiles` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`user_id` bigint NOT NULL,
|
||||
`title` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`department` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`bio` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
|
||||
`created_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updated_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
`created_by` bigint DEFAULT NULL,
|
||||
`updated_by` bigint DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_user_id` (`user_id`),
|
||||
KEY `created_at` (`created_at`),
|
||||
KEY `updated_at` (`updated_at`),
|
||||
CONSTRAINT `fk_user_profiles_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `user_profiles`
|
||||
--
|
||||
|
||||
LOCK TABLES `user_profiles` WRITE;
|
||||
/*!40000 ALTER TABLE `user_profiles` DISABLE KEYS */;
|
||||
/*!40000 ALTER TABLE `user_profiles` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Table structure for table `user_verifications`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `user_verifications`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `user_verifications` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`verification_code` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`verification_type_id` bigint NOT NULL,
|
||||
`verified_at` timestamp(3) NULL DEFAULT NULL,
|
||||
`expires_at` timestamp(3) NOT NULL,
|
||||
`created_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updated_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
`created_by` bigint DEFAULT NULL,
|
||||
`updated_by` bigint DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_user_verifications_email` (`email`),
|
||||
KEY `idx_user_verifications_code` (`verification_code`),
|
||||
KEY `idx_user_verifications_type` (`verification_type_id`),
|
||||
KEY `idx_user_verifications_expires_at` (`expires_at`),
|
||||
KEY `created_at` (`created_at`),
|
||||
KEY `updated_at` (`updated_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `user_verifications`
|
||||
--
|
||||
|
||||
LOCK TABLES `user_verifications` WRITE;
|
||||
/*!40000 ALTER TABLE `user_verifications` DISABLE KEYS */;
|
||||
/*!40000 ALTER TABLE `user_verifications` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Table structure for table `users`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `users`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `users` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`login_user_id` bigint DEFAULT NULL,
|
||||
`site_id` bigint NOT NULL,
|
||||
`first_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`last_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`role_id` bigint DEFAULT NULL,
|
||||
`is_enabled` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`user_role_id` bigint NOT NULL DEFAULT '2',
|
||||
`email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`deleted_at` timestamp(3) NULL DEFAULT NULL,
|
||||
`created_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updated_at` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
`created_by` bigint DEFAULT NULL,
|
||||
`updated_by` bigint DEFAULT NULL,
|
||||
`deleted_by` bigint DEFAULT NULL,
|
||||
`invite_code` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`invite_accepted_at` timestamp(3) NULL DEFAULT NULL,
|
||||
`invite_expires_at` timestamp(3) NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_users_login_user_site` (`login_user_id`,`site_id`),
|
||||
UNIQUE KEY `uk_users_invite_code` (`invite_code`),
|
||||
KEY `idx_site_users_site_id` (`site_id`),
|
||||
KEY `idx_site_users_role_id` (`role_id`),
|
||||
KEY `idx_site_users_is_enabled` (`is_enabled`),
|
||||
KEY `idx_site_users_deleted_at` (`deleted_at`),
|
||||
KEY `created_at` (`created_at`),
|
||||
KEY `updated_at` (`updated_at`),
|
||||
KEY `idx_users_phone` (`phone`),
|
||||
KEY `idx_users_user_role_id` (`user_role_id`),
|
||||
KEY `idx_users_email` (`email`),
|
||||
KEY `idx_users_invite_accepted_at` (`invite_accepted_at`),
|
||||
KEY `idx_users_invite_expires_at` (`invite_expires_at`),
|
||||
CONSTRAINT `fk_users_login_user_id` FOREIGN KEY (`login_user_id`) REFERENCES `login_users` (`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_users_site_id` FOREIGN KEY (`site_id`) REFERENCES `sites` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `users`
|
||||
--
|
||||
|
||||
LOCK TABLES `users` WRITE;
|
||||
/*!40000 ALTER TABLE `users` DISABLE KEYS */;
|
||||
/*!40000 ALTER TABLE `users` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
||||
|
||||
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
||||
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
||||
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||
|
||||
-- Dump completed on 2025-11-16 19:45:00
|
||||
90
app/RSpade/tests/_lib/test_env.sh
Executable file
90
app/RSpade/tests/_lib/test_env.sh
Executable file
@@ -0,0 +1,90 @@
|
||||
#!/bin/bash
|
||||
|
||||
# RSpade Test Environment Helpers
|
||||
#
|
||||
# Manages test database environment and provides utilities for test setup/teardown
|
||||
|
||||
TEST_ENV_BACKUP="/var/www/html/system/.env.testbackup"
|
||||
TEST_MODE_ACTIVE=false
|
||||
|
||||
# Enter test mode - switch to test database
|
||||
function test_mode_enter() {
|
||||
if [ "$TEST_MODE_ACTIVE" = true ]; then
|
||||
echo "[ERROR] Test mode already active" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "[TEST ENV] Entering test mode..." >&2
|
||||
|
||||
# Only backup .env if we're NOT already in test mode
|
||||
# (Don't want to backup test config and mistake it for production)
|
||||
if grep -q "^DB_DATABASE=rspade_test" /var/www/html/.env; then
|
||||
echo "[TEST ENV] WARNING: Already using test database - NOT creating backup" >&2
|
||||
echo "[TEST ENV] This indicates .env was not properly restored from a previous test run" >&2
|
||||
echo "[TEST ENV] Manually restore /var/www/html/.env to production credentials before running tests" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Backup current .env (production credentials)
|
||||
cp /var/www/html/.env "$TEST_ENV_BACKUP"
|
||||
|
||||
# Switch to test database
|
||||
sed -i 's/^DB_DATABASE=.*$/DB_DATABASE=rspade_test/' /var/www/html/.env
|
||||
|
||||
# Add test config to include tests directory in manifest
|
||||
if ! grep -q "^RSX_ADDITIONAL_CONFIG=" /var/www/html/.env; then
|
||||
echo "RSX_ADDITIONAL_CONFIG=/var/www/html/system/app/RSpade/tests/_lib/rsx_test_config.php" >> /var/www/html/.env
|
||||
else
|
||||
sed -i 's|^RSX_ADDITIONAL_CONFIG=.*$|RSX_ADDITIONAL_CONFIG=/var/www/html/system/app/RSpade/tests/_lib/rsx_test_config.php|' /var/www/html/.env
|
||||
fi
|
||||
|
||||
# Clear Laravel config cache and rebuild manifest
|
||||
cd /var/www/html
|
||||
php artisan config:clear > /dev/null 2>&1
|
||||
php artisan rsx:clean > /dev/null 2>&1
|
||||
|
||||
TEST_MODE_ACTIVE=true
|
||||
echo "[TEST ENV] Test mode active (using rspade_test database)" >&2
|
||||
}
|
||||
|
||||
# Exit test mode - restore original database
|
||||
function test_mode_exit() {
|
||||
if [ "$TEST_MODE_ACTIVE" = false ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "[TEST ENV] Exiting test mode..." >&2
|
||||
|
||||
# Restore original .env
|
||||
if [ -f "$TEST_ENV_BACKUP" ]; then
|
||||
mv "$TEST_ENV_BACKUP" /var/www/html/.env
|
||||
fi
|
||||
|
||||
# Clear Laravel config cache and rebuild manifest
|
||||
cd /var/www/html
|
||||
php artisan config:clear > /dev/null 2>&1
|
||||
php artisan rsx:clean > /dev/null 2>&1
|
||||
|
||||
TEST_MODE_ACTIVE=false
|
||||
echo "[TEST ENV] Test mode exited (restored original database)" >&2
|
||||
}
|
||||
|
||||
# Trap to ensure test mode always exits
|
||||
function test_trap_exit() {
|
||||
test_mode_exit
|
||||
|
||||
# NEVER remove backup files - they contain production credentials
|
||||
# Backup files are process-specific (/tmp/rspade_test_env_backup_$$)
|
||||
# and will be cleaned up by system /tmp cleanup
|
||||
}
|
||||
|
||||
# Query helper for test database
|
||||
function test_db_query() {
|
||||
mysql -h127.0.0.1 -urspade -prspadepass rspade_test -N -e "$1" 2>/dev/null
|
||||
}
|
||||
|
||||
# Count helper for test database
|
||||
function test_db_count() {
|
||||
local table=$1
|
||||
test_db_query "SELECT COUNT(*) FROM $table"
|
||||
}
|
||||
63
app/RSpade/tests/basic/01_framework_verification/README.md
Executable file
63
app/RSpade/tests/basic/01_framework_verification/README.md
Executable file
@@ -0,0 +1,63 @@
|
||||
# Framework Verification Test
|
||||
|
||||
Meta-test that verifies the testing framework itself works correctly.
|
||||
|
||||
## What it verifies
|
||||
|
||||
- Test environment helpers load correctly
|
||||
- `test_mode_enter()` successfully switches to test database
|
||||
- `.env` is modified to use `rspade_test`
|
||||
- `.env` backup is created
|
||||
- Database reset script works
|
||||
- Migrations run successfully on test database
|
||||
- `test_db_query()` helper works
|
||||
- Test mode cleanup/restore works via trap
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- `rspade_test` database exists
|
||||
- `rspade` user has access to `rspade_test`
|
||||
- Laravel is configured normally (using `rspade` database)
|
||||
|
||||
## How to run
|
||||
|
||||
```bash
|
||||
./run_test.sh # Full test with database reset
|
||||
./run_test.sh --skip-reset # Skip database reset (faster)
|
||||
```
|
||||
|
||||
## What happens
|
||||
|
||||
1. Loads test environment helpers
|
||||
2. Resets test database (unless --skip-reset)
|
||||
3. Enters test mode (switches to rspade_test)
|
||||
4. Runs 5 verification checks
|
||||
5. Exits test mode (restores original database)
|
||||
6. Outputs PASS or FAIL
|
||||
|
||||
## Expected output
|
||||
|
||||
```
|
||||
[SETUP] Testing framework verification...
|
||||
[SETUP] Resetting test database...
|
||||
[DB RESET] Dropping rspade_test database...
|
||||
[DB RESET] Creating rspade_test database...
|
||||
[DB RESET] Running migrations...
|
||||
[DB RESET] Database reset complete
|
||||
[TEST ENV] Entering test mode...
|
||||
[TEST ENV] Test mode active (using rspade_test database)
|
||||
[TEST] Verifying framework components...
|
||||
[TEST] ✓ Environment switched to test database
|
||||
[TEST] ✓ Test database accessible
|
||||
[TEST] ✓ Migrations ran successfully
|
||||
[TEST] ✓ Environment backup created
|
||||
[TEST] ✓ Database query helper works
|
||||
PASS: Framework Verification
|
||||
[TEST ENV] Exiting test mode...
|
||||
[TEST ENV] Test mode exited (restored original database)
|
||||
```
|
||||
|
||||
## Known limitations
|
||||
|
||||
- Database reset is slow (~5-10 seconds with migrations)
|
||||
- Future: Use database snapshots for faster reset
|
||||
87
app/RSpade/tests/basic/01_framework_verification/run_test.sh
Executable file
87
app/RSpade/tests/basic/01_framework_verification/run_test.sh
Executable file
@@ -0,0 +1,87 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
TEST_NAME="Framework Verification"
|
||||
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] Testing framework verification..." >&2
|
||||
|
||||
# Verify test environment helpers loaded
|
||||
if ! type test_mode_enter > /dev/null 2>&1; then
|
||||
echo "FAIL: $TEST_NAME - test_mode_enter function not loaded"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Enter test mode (switches Laravel to rspade_test database and loads test config)
|
||||
test_mode_enter
|
||||
|
||||
# Reset database to known state (unless called as sub-test)
|
||||
if [ "$SKIP_RESET" = false ]; then
|
||||
echo "[SETUP] Resetting test database..." >&2
|
||||
"$TEST_DIR/../../_lib/db_reset.sh"
|
||||
fi
|
||||
|
||||
# TEST LOGIC
|
||||
echo "[TEST] Verifying framework components..." >&2
|
||||
|
||||
# Test 1: Verify .env was modified
|
||||
if grep -q "DB_DATABASE=rspade_test" /var/www/html/.env; then
|
||||
echo "[TEST] ✓ Environment switched to test database" >&2
|
||||
else
|
||||
echo "FAIL: $TEST_NAME - .env not modified to use rspade_test"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 2: Verify we can query test database
|
||||
if ! mysql -h127.0.0.1 -urspade -prspadepass rspade_test -e "SELECT 1" > /dev/null 2>&1; then
|
||||
echo "FAIL: $TEST_NAME - Cannot connect to rspade_test database"
|
||||
exit 1
|
||||
fi
|
||||
echo "[TEST] ✓ Test database accessible" >&2
|
||||
|
||||
# Test 3: Verify migrations table exists (migrations ran)
|
||||
tables=$(mysql -h127.0.0.1 -urspade -prspadepass rspade_test -N -e "SHOW TABLES LIKE 'migrations'" 2>/dev/null)
|
||||
if [ -z "$tables" ]; then
|
||||
echo "FAIL: $TEST_NAME - Migrations table does not exist (migrations did not run)"
|
||||
exit 1
|
||||
fi
|
||||
echo "[TEST] ✓ Migrations ran successfully" >&2
|
||||
|
||||
# Test 4: Verify backup was created
|
||||
if [ ! -f "$TEST_ENV_BACKUP" ]; then
|
||||
echo "FAIL: $TEST_NAME - .env backup not created"
|
||||
exit 1
|
||||
fi
|
||||
echo "[TEST] ✓ Environment backup created" >&2
|
||||
|
||||
# Test 5: Verify test_db_query helper works
|
||||
test_result=$(test_db_query "SELECT 'works'" 2>/dev/null)
|
||||
if [ "$test_result" != "works" ]; then
|
||||
echo "FAIL: $TEST_NAME - test_db_query helper failed"
|
||||
exit 1
|
||||
fi
|
||||
echo "[TEST] ✓ Database query helper works" >&2
|
||||
|
||||
# All tests passed
|
||||
echo "PASS: $TEST_NAME"
|
||||
exit 0
|
||||
|
||||
# TEARDOWN happens automatically via trap
|
||||
58
app/RSpade/tests/basic/02_database_connection/README.md
Executable file
58
app/RSpade/tests/basic/02_database_connection/README.md
Executable file
@@ -0,0 +1,58 @@
|
||||
# Database Connection Test
|
||||
|
||||
Tests basic database CRUD operations using the test database.
|
||||
|
||||
## What it verifies
|
||||
|
||||
- Database insert operations work
|
||||
- Database query operations work
|
||||
- Database update operations work
|
||||
- Database delete operations work
|
||||
- Data retrieval returns correct values
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- `rspade_test` database exists
|
||||
- `rspade` user has access to `rspade_test`
|
||||
- Test environment helpers functional
|
||||
|
||||
## How to run
|
||||
|
||||
```bash
|
||||
./run_test.sh # Full test with database reset
|
||||
./run_test.sh --skip-reset # Skip database reset (faster)
|
||||
```
|
||||
|
||||
## What happens
|
||||
|
||||
1. Creates temporary table
|
||||
2. Inserts test data
|
||||
3. Verifies data was inserted
|
||||
4. Queries specific data
|
||||
5. Updates data
|
||||
6. Verifies update worked
|
||||
7. Deletes data
|
||||
8. Verifies deletion worked
|
||||
9. Cleans up (temporary table auto-removed)
|
||||
|
||||
## Expected output
|
||||
|
||||
```
|
||||
[SETUP] Preparing database connection test...
|
||||
[SETUP] Resetting test database...
|
||||
[DB RESET] Dropping rspade_test database...
|
||||
[DB RESET] Creating rspade_test database...
|
||||
[DB RESET] Running migrations...
|
||||
[DB RESET] Database reset complete
|
||||
[TEST ENV] Entering test mode...
|
||||
[TEST ENV] Test mode active (using rspade_test database)
|
||||
[TEST] Testing database connection and operations...
|
||||
[TEST] ✓ Insert operations work
|
||||
[TEST] ✓ Query operations work
|
||||
[TEST] ✓ Data retrieval works
|
||||
[TEST] ✓ Update operations work
|
||||
[TEST] ✓ Delete operations work
|
||||
PASS: Database Connection
|
||||
[TEST ENV] Exiting test mode...
|
||||
[TEST ENV] Test mode exited (restored original database)
|
||||
```
|
||||
85
app/RSpade/tests/basic/02_database_connection/run_test.sh
Executable file
85
app/RSpade/tests/basic/02_database_connection/run_test.sh
Executable file
@@ -0,0 +1,85 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
TEST_NAME="Database Connection"
|
||||
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 database connection test..." >&2
|
||||
|
||||
# Enter test mode (switches Laravel to rspade_test database and loads test config)
|
||||
test_mode_enter
|
||||
|
||||
# Reset database to known state (unless called as sub-test)
|
||||
if [ "$SKIP_RESET" = false ]; then
|
||||
"$TEST_DIR/../../_lib/db_reset.sh"
|
||||
fi
|
||||
|
||||
# TEST LOGIC
|
||||
echo "[TEST] Testing database connection and operations..." >&2
|
||||
|
||||
# Test 1: Can we insert data?
|
||||
test_db_query "CREATE TABLE IF NOT EXISTS rspade_test_temp_users (id INT, name VARCHAR(255))"
|
||||
test_db_query "TRUNCATE TABLE rspade_test_temp_users"
|
||||
test_db_query "INSERT INTO rspade_test_temp_users (id, name) VALUES (1, 'Alice'), (2, 'Bob')"
|
||||
echo "[TEST] ✓ Insert operations work" >&2
|
||||
|
||||
# Test 2: Can we query data?
|
||||
count=$(test_db_count rspade_test_temp_users)
|
||||
if [ "$count" -ne 2 ]; then
|
||||
echo "FAIL: $TEST_NAME - Expected 2 rows, found $count"
|
||||
exit 1
|
||||
fi
|
||||
echo "[TEST] ✓ Query operations work" >&2
|
||||
|
||||
# Test 3: Can we retrieve specific data?
|
||||
name=$(test_db_query "SELECT name FROM rspade_test_temp_users WHERE id=1")
|
||||
if [ "$name" != "Alice" ]; then
|
||||
echo "FAIL: $TEST_NAME - Expected 'Alice', got '$name'"
|
||||
exit 1
|
||||
fi
|
||||
echo "[TEST] ✓ Data retrieval works" >&2
|
||||
|
||||
# Test 4: Can we update data?
|
||||
test_db_query "UPDATE rspade_test_temp_users SET name='Charlie' WHERE id=1"
|
||||
updated_name=$(test_db_query "SELECT name FROM rspade_test_temp_users WHERE id=1")
|
||||
if [ "$updated_name" != "Charlie" ]; then
|
||||
echo "FAIL: $TEST_NAME - Expected 'Charlie', got '$updated_name'"
|
||||
exit 1
|
||||
fi
|
||||
echo "[TEST] ✓ Update operations work" >&2
|
||||
|
||||
# Test 5: Can we delete data?
|
||||
test_db_query "DELETE FROM rspade_test_temp_users WHERE id=2"
|
||||
remaining=$(test_db_count rspade_test_temp_users)
|
||||
if [ "$remaining" -ne 1 ]; then
|
||||
echo "FAIL: $TEST_NAME - Expected 1 row after delete, found $remaining"
|
||||
exit 1
|
||||
fi
|
||||
echo "[TEST] ✓ Delete operations work" >&2
|
||||
|
||||
# Cleanup
|
||||
test_db_query "DROP TABLE IF EXISTS rspade_test_temp_users"
|
||||
|
||||
# All tests passed
|
||||
echo "PASS: $TEST_NAME"
|
||||
exit 0
|
||||
|
||||
# TEARDOWN happens automatically via trap
|
||||
128
app/RSpade/tests/run_all_tests.sh
Executable file
128
app/RSpade/tests/run_all_tests.sh
Executable file
@@ -0,0 +1,128 @@
|
||||
#!/bin/bash
|
||||
|
||||
# RSpade Test Runner
|
||||
# Runs all automated tests and reports results
|
||||
|
||||
set -e
|
||||
|
||||
TESTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# ANSI color codes
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Track results
|
||||
PASSED=0
|
||||
FAILED=0
|
||||
SKIPPED=0
|
||||
TOTAL=0
|
||||
|
||||
echo -e "${BLUE}[RSpade Test Runner]${NC}"
|
||||
|
||||
# Parse arguments
|
||||
USE_EXISTING_SNAPSHOT=false
|
||||
FULL_OUTPUT=false
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--use-existing-snapshot)
|
||||
USE_EXISTING_SNAPSHOT=true
|
||||
shift
|
||||
;;
|
||||
--full-output)
|
||||
FULL_OUTPUT=true
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# If no arguments provided (or snapshot flag not set), recreate snapshot for fresh test run
|
||||
if [ "$USE_EXISTING_SNAPSHOT" = false ]; then
|
||||
echo -e "${BLUE}Creating fresh database snapshot...${NC}"
|
||||
if [ "$FULL_OUTPUT" = true ]; then
|
||||
"$TESTS_DIR/_lib/db_snapshot_create.sh" --full-output
|
||||
else
|
||||
"$TESTS_DIR/_lib/db_snapshot_create.sh"
|
||||
fi
|
||||
echo ""
|
||||
else
|
||||
echo -e "${BLUE}Using existing snapshot (if available)${NC}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Find all run_test.sh files (excluding run_interactive_test.sh)
|
||||
echo -e "${BLUE}Finding tests...${NC}"
|
||||
echo ""
|
||||
|
||||
# Find all test scripts
|
||||
test_scripts=$(find "$TESTS_DIR" -name "run_test.sh" -type f | sort)
|
||||
|
||||
if [ -z "$test_scripts" ]; then
|
||||
echo -e "${RED}No tests found${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Count tests
|
||||
while IFS= read -r script; do
|
||||
TOTAL=$((TOTAL + 1))
|
||||
done <<< "$test_scripts"
|
||||
|
||||
echo -e "${BLUE}Found $TOTAL tests${NC}"
|
||||
echo ""
|
||||
|
||||
# Run each test
|
||||
while IFS= read -r script; do
|
||||
# Get relative path for display
|
||||
rel_path="${script#$TESTS_DIR/}"
|
||||
|
||||
echo -e "${BLUE}Running: $rel_path${NC}"
|
||||
|
||||
# Run test and capture stdout/stderr separately
|
||||
# Stderr goes to terminal, stdout captured for result parsing
|
||||
if output=$("$script" 2>&1 | tee /dev/stderr); then
|
||||
exit_code=0
|
||||
else
|
||||
exit_code=$?
|
||||
fi
|
||||
|
||||
# Parse result from output (should be last non-empty line to stdout)
|
||||
result=$(echo "$output" | grep -E "^(PASS|FAIL|SKIP):" | tail -n 1)
|
||||
|
||||
if [ -z "$result" ]; then
|
||||
result="FAIL: $rel_path - No test result output"
|
||||
fi
|
||||
|
||||
if [ $exit_code -eq 0 ] && echo "$result" | grep -q "^PASS:"; then
|
||||
echo -e "${GREEN}✓ $result${NC}"
|
||||
PASSED=$((PASSED + 1))
|
||||
elif echo "$result" | grep -q "^SKIP:"; then
|
||||
echo -e "${YELLOW}○ $result${NC}"
|
||||
SKIPPED=$((SKIPPED + 1))
|
||||
else
|
||||
echo -e "${RED}✗ $result${NC}"
|
||||
FAILED=$((FAILED + 1))
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
done <<< "$test_scripts"
|
||||
|
||||
# Summary
|
||||
echo -e "${BLUE}======================================${NC}"
|
||||
echo -e "${BLUE}Test Results${NC}"
|
||||
echo -e "${BLUE}======================================${NC}"
|
||||
echo -e "${GREEN}Passed: $PASSED${NC}"
|
||||
echo -e "${RED}Failed: $FAILED${NC}"
|
||||
echo -e "${YELLOW}Skipped: $SKIPPED${NC}"
|
||||
echo -e "${BLUE}Total: $TOTAL${NC}"
|
||||
echo ""
|
||||
|
||||
if [ $FAILED -gt 0 ]; then
|
||||
echo -e "${RED}Some tests failed${NC}"
|
||||
exit 1
|
||||
else
|
||||
echo -e "${GREEN}All tests passed${NC}"
|
||||
exit 0
|
||||
fi
|
||||
93
app/RSpade/tests/run_interactive_tests.sh
Executable file
93
app/RSpade/tests/run_interactive_tests.sh
Executable file
@@ -0,0 +1,93 @@
|
||||
#!/bin/bash
|
||||
|
||||
# RSpade Interactive Test Runner
|
||||
# Runs tests that require human verification
|
||||
|
||||
set -e
|
||||
|
||||
TESTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# ANSI color codes
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Track results
|
||||
PASSED=0
|
||||
FAILED=0
|
||||
SKIPPED=0
|
||||
TOTAL=0
|
||||
|
||||
# Find all run_interactive_test.sh files
|
||||
echo -e "${BLUE}[RSpade Interactive Test Runner]${NC}"
|
||||
echo -e "${BLUE}Finding interactive tests...${NC}"
|
||||
echo ""
|
||||
|
||||
# Find all interactive test scripts
|
||||
test_scripts=$(find "$TESTS_DIR" -name "run_interactive_test.sh" -type f | sort)
|
||||
|
||||
if [ -z "$test_scripts" ]; then
|
||||
echo -e "${YELLOW}No interactive tests found${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Count tests
|
||||
while IFS= read -r script; do
|
||||
TOTAL=$((TOTAL + 1))
|
||||
done <<< "$test_scripts"
|
||||
|
||||
echo -e "${BLUE}Found $TOTAL interactive tests${NC}"
|
||||
echo ""
|
||||
|
||||
# Run each test
|
||||
while IFS= read -r script; do
|
||||
# Get relative path for display
|
||||
rel_path="${script#$TESTS_DIR/}"
|
||||
|
||||
echo -e "${BLUE}Running: $rel_path${NC}"
|
||||
echo ""
|
||||
|
||||
# Run test and capture output
|
||||
if output=$("$script" 2>&1); then
|
||||
exit_code=0
|
||||
else
|
||||
exit_code=$?
|
||||
fi
|
||||
|
||||
# Parse result from last line
|
||||
result=$(echo "$output" | tail -n 1)
|
||||
|
||||
if [ $exit_code -eq 0 ] && echo "$result" | grep -q "^PASS:"; then
|
||||
echo -e "${GREEN}✓ $result${NC}"
|
||||
PASSED=$((PASSED + 1))
|
||||
elif echo "$result" | grep -q "^SKIP:"; then
|
||||
echo -e "${YELLOW}○ $result${NC}"
|
||||
SKIPPED=$((SKIPPED + 1))
|
||||
else
|
||||
echo -e "${RED}✗ $result${NC}"
|
||||
FAILED=$((FAILED + 1))
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
done <<< "$test_scripts"
|
||||
|
||||
# Summary
|
||||
echo -e "${BLUE}======================================${NC}"
|
||||
echo -e "${BLUE}Interactive Test Results${NC}"
|
||||
echo -e "${BLUE}======================================${NC}"
|
||||
echo -e "${GREEN}Passed: $PASSED${NC}"
|
||||
echo -e "${RED}Failed: $FAILED${NC}"
|
||||
echo -e "${YELLOW}Skipped: $SKIPPED${NC}"
|
||||
echo -e "${BLUE}Total: $TOTAL${NC}"
|
||||
echo ""
|
||||
|
||||
if [ $FAILED -gt 0 ]; then
|
||||
echo -e "${RED}Some tests failed${NC}"
|
||||
exit 1
|
||||
else
|
||||
echo -e "${GREEN}All tests passed${NC}"
|
||||
exit 0
|
||||
fi
|
||||
76
app/RSpade/tests/services/scheduled_test_service.php
Executable file
76
app/RSpade/tests/services/scheduled_test_service.php
Executable file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/**
|
||||
* CODING CONVENTION:
|
||||
* This file follows the coding convention where variable_names and function_names
|
||||
* use snake_case (underscore_wherever_possible).
|
||||
*/
|
||||
|
||||
namespace App\RSpade\Tests\Services;
|
||||
|
||||
use App\RSpade\Core\Service\Rsx_Service_Abstract;
|
||||
use App\RSpade\Core\Task\Task_Instance;
|
||||
|
||||
/**
|
||||
* Scheduled Test Service
|
||||
*
|
||||
* Test service with scheduled tasks for testing the scheduling system.
|
||||
*/
|
||||
class Scheduled_Test_Service extends Rsx_Service_Abstract
|
||||
{
|
||||
/**
|
||||
* Test task that runs every minute
|
||||
*
|
||||
* @param Task_Instance $task Task instance for logging
|
||||
* @param array $params Task parameters
|
||||
* @return array Result data
|
||||
*/
|
||||
#[Task_Attribute('Test scheduled task that runs every minute')]
|
||||
#[Schedule('* * * * *', 'default')]
|
||||
public static function every_minute(Task_Instance $task, array $params = []): array
|
||||
{
|
||||
$task->info('Every minute task executed at ' . date('Y-m-d H:i:s'));
|
||||
|
||||
return [
|
||||
'executed_at' => date('Y-m-d H:i:s'),
|
||||
'message' => 'Every minute task completed',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test task that runs every 5 minutes
|
||||
*
|
||||
* @param Task_Instance $task Task instance for logging
|
||||
* @param array $params Task parameters
|
||||
* @return array Result data
|
||||
*/
|
||||
#[Task_Attribute('Test scheduled task that runs every 5 minutes')]
|
||||
#[Schedule('*/5 * * * *', 'default')]
|
||||
public static function every_five_minutes(Task_Instance $task, array $params = []): array
|
||||
{
|
||||
$task->info('Every 5 minutes task executed at ' . date('Y-m-d H:i:s'));
|
||||
|
||||
return [
|
||||
'executed_at' => date('Y-m-d H:i:s'),
|
||||
'message' => 'Every 5 minutes task completed',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test task that runs daily at midnight
|
||||
*
|
||||
* @param Task_Instance $task Task instance for logging
|
||||
* @param array $params Task parameters
|
||||
* @return array Result data
|
||||
*/
|
||||
#[Task_Attribute('Test scheduled task that runs daily at midnight')]
|
||||
#[Schedule('0 0 * * *', 'scheduled')]
|
||||
public static function daily_midnight(Task_Instance $task, array $params = []): array
|
||||
{
|
||||
$task->info('Daily midnight task executed at ' . date('Y-m-d H:i:s'));
|
||||
|
||||
return [
|
||||
'executed_at' => date('Y-m-d H:i:s'),
|
||||
'message' => 'Daily midnight task completed',
|
||||
];
|
||||
}
|
||||
}
|
||||
62
app/RSpade/tests/services/service_test_service.php
Executable file
62
app/RSpade/tests/services/service_test_service.php
Executable file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/**
|
||||
* CODING CONVENTION:
|
||||
* This file follows the coding convention where variable_names and function_names
|
||||
* use snake_case (underscore_wherever_possible).
|
||||
*/
|
||||
|
||||
namespace App\RSpade\Tests\Services;
|
||||
|
||||
use App\RSpade\Core\Service\Rsx_Service_Abstract;
|
||||
use App\RSpade\Core\Task\Task_Instance;
|
||||
|
||||
/**
|
||||
* Service Test Service
|
||||
*
|
||||
* Test service for validating task execution system.
|
||||
*/
|
||||
class Service_Test_Service extends Rsx_Service_Abstract
|
||||
{
|
||||
/**
|
||||
* Hello world test task
|
||||
*
|
||||
* @param Task_Instance $task Task instance for logging
|
||||
* @param array $params Task parameters
|
||||
* @return array Result data
|
||||
*/
|
||||
#[Task('Simple test task that returns hello world')]
|
||||
public static function hello_world(Task_Instance $task, array $params = []): array
|
||||
{
|
||||
$task->info('Hello world task executed');
|
||||
$task->debug('Debug message from hello_world task');
|
||||
|
||||
$name = $params['name'] ?? 'World';
|
||||
|
||||
return [
|
||||
'message' => "Hello, {$name}!",
|
||||
'timestamp' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test task with logging
|
||||
*
|
||||
* @param Task_Instance $task Task instance for logging
|
||||
* @param array $params Task parameters
|
||||
* @return array Result data
|
||||
*/
|
||||
#[Task_Attribute('Test task that demonstrates logging functionality')]
|
||||
public static function test_logging(Task_Instance $task, array $params = []): array
|
||||
{
|
||||
$task->info('Starting logging test');
|
||||
$task->debug('This is a debug message');
|
||||
$task->info('Processing step 1');
|
||||
$task->info('Processing step 2');
|
||||
$task->info('Logging test complete');
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'log_count' => 5,
|
||||
];
|
||||
}
|
||||
}
|
||||
62
app/RSpade/tests/tasks/01_task_dispatch_and_execution/README.md
Executable file
62
app/RSpade/tests/tasks/01_task_dispatch_and_execution/README.md
Executable file
@@ -0,0 +1,62 @@
|
||||
# Task Dispatch and Execution Test
|
||||
|
||||
Tests the core task system functionality including task dispatch, execution, and scheduling.
|
||||
|
||||
## What it verifies
|
||||
|
||||
- Test services are discoverable in the manifest when in test mode
|
||||
- Tasks can be dispatched to the queue
|
||||
- Tasks start with 'pending' status
|
||||
- Task processor can execute tasks
|
||||
- Tasks complete with 'completed' status
|
||||
- Task results are stored correctly
|
||||
- Task logs are recorded
|
||||
- Scheduled tasks are registered from manifest
|
||||
- `--force-scheduled` flag dispatches scheduled tasks immediately
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Test mode must be active (test services in manifest)
|
||||
- Database must have `_tasks` table (from migration)
|
||||
- Test services must exist in `/system/app/RSpade/tests/services/`
|
||||
|
||||
## How to run
|
||||
|
||||
```bash
|
||||
cd /system/app/RSpade/tests
|
||||
./tasks/01_task_dispatch_and_execution/run_test.sh
|
||||
```
|
||||
|
||||
## What it tests
|
||||
|
||||
1. **Manifest Integration**: Verifies test services with #[Task_Attribute] and #[Schedule] attributes are discovered
|
||||
2. **Task Dispatch**: Creates a task and verifies it's inserted into the database
|
||||
3. **Task Status**: Checks task starts as 'pending'
|
||||
4. **Task Execution**: Runs the task processor to execute the pending task
|
||||
5. **Task Completion**: Verifies task status changes to 'completed'
|
||||
6. **Task Results**: Checks task return value is stored as JSON
|
||||
7. **Task Logging**: Verifies Task_Instance logging is captured
|
||||
8. **Scheduled Registration**: Tests Task_Process_Command registers scheduled tasks
|
||||
9. **Force Dispatch**: Tests --force-scheduled flag creates task instances
|
||||
|
||||
## Expected output
|
||||
|
||||
```
|
||||
[SETUP] Preparing task system test...
|
||||
[SETUP] Resetting test database...
|
||||
[TEST] Testing task dispatch and execution...
|
||||
[TEST] Verifying test services in manifest...
|
||||
[TEST] ✓ Test services found in manifest
|
||||
[TEST] Dispatching task...
|
||||
[TEST] ✓ Task dispatched with ID: 1
|
||||
[TEST] ✓ Task status is pending
|
||||
[TEST] Processing task...
|
||||
[TEST] ✓ Task completed successfully
|
||||
[TEST] ✓ Task result correct
|
||||
[TEST] ✓ Task logs recorded
|
||||
[TEST] Testing scheduled task registration...
|
||||
[TEST] ✓ Scheduled tasks registered: 3
|
||||
[TEST] Testing --force-scheduled flag...
|
||||
[TEST] ✓ Force-scheduled created task instances
|
||||
PASS: Task Dispatch and Execution
|
||||
```
|
||||
145
app/RSpade/tests/tasks/01_task_dispatch_and_execution/run_test.sh
Executable file
145
app/RSpade/tests/tasks/01_task_dispatch_and_execution/run_test.sh
Executable file
@@ -0,0 +1,145 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
TEST_NAME="Task Dispatch and Execution"
|
||||
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 task system test..." >&2
|
||||
|
||||
# Enter test mode (switches Laravel to rspade_test database and loads test config)
|
||||
test_mode_enter
|
||||
|
||||
# Reset database to known state (unless called as sub-test)
|
||||
if [ "$SKIP_RESET" = false ]; then
|
||||
echo "[SETUP] Resetting test database..." >&2
|
||||
"$TEST_DIR/../../_lib/db_reset.sh"
|
||||
fi
|
||||
|
||||
# TEST LOGIC
|
||||
echo "[TEST] Testing task dispatch and execution..." >&2
|
||||
|
||||
cd /var/www/html
|
||||
|
||||
# Test 1: Verify test services are available in manifest
|
||||
echo "[TEST] Verifying test services in manifest..." >&2
|
||||
php artisan tinker --execute="
|
||||
\$tasks = App\RSpade\Core\Task\Task::get_scheduled_tasks();
|
||||
if (count(\$tasks) < 3) {
|
||||
echo 'ERROR: Expected at least 3 scheduled tasks, found ' . count(\$tasks);
|
||||
exit(1);
|
||||
}
|
||||
echo 'OK';
|
||||
" 2>&1 | grep -q "OK"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "FAIL: $TEST_NAME - Scheduled tasks not found in manifest"
|
||||
exit 1
|
||||
fi
|
||||
echo "[TEST] ✓ Test services found in manifest" >&2
|
||||
|
||||
# Test 2: Dispatch a task
|
||||
echo "[TEST] Dispatching task..." >&2
|
||||
TASK_ID=$(php artisan tinker --execute="
|
||||
echo App\RSpade\Core\Task\Task::dispatch('Service_Test_Service', 'hello_world', ['name' => 'Test']);
|
||||
" 2>&1 | tail -1)
|
||||
|
||||
if [ -z "$TASK_ID" ] || [ "$TASK_ID" = "0" ]; then
|
||||
echo "FAIL: $TEST_NAME - Task dispatch failed"
|
||||
exit 1
|
||||
fi
|
||||
echo "[TEST] ✓ Task dispatched with ID: $TASK_ID" >&2
|
||||
|
||||
# Test 3: Verify task is pending
|
||||
STATUS=$(test_db_query "SELECT status FROM _tasks WHERE id = $TASK_ID")
|
||||
if [ "$STATUS" != "pending" ]; then
|
||||
echo "FAIL: $TEST_NAME - Task status should be 'pending', got '$STATUS'"
|
||||
exit 1
|
||||
fi
|
||||
echo "[TEST] ✓ Task status is pending" >&2
|
||||
|
||||
# Test 4: Process the task
|
||||
echo "[TEST] Processing task..." >&2
|
||||
php artisan rsx:task:process --once --queue=default > /dev/null 2>&1
|
||||
|
||||
# Test 5: Verify task completed
|
||||
STATUS=$(test_db_query "SELECT status FROM _tasks WHERE id = $TASK_ID")
|
||||
if [ "$STATUS" != "completed" ]; then
|
||||
echo "FAIL: $TEST_NAME - Task should be completed, got '$STATUS'"
|
||||
exit 1
|
||||
fi
|
||||
echo "[TEST] ✓ Task completed successfully" >&2
|
||||
|
||||
# Test 6: Verify task result
|
||||
RESULT=$(test_db_query "SELECT result FROM _tasks WHERE id = $TASK_ID")
|
||||
if ! echo "$RESULT" | grep -q "Hello, Test"; then
|
||||
echo "FAIL: $TEST_NAME - Task result incorrect: $RESULT"
|
||||
exit 1
|
||||
fi
|
||||
echo "[TEST] ✓ Task result correct" >&2
|
||||
|
||||
# Test 7: Verify task logs
|
||||
LOGS=$(test_db_query "SELECT logs FROM _tasks WHERE id = $TASK_ID")
|
||||
if ! echo "$LOGS" | grep -q "Hello world task executed"; then
|
||||
echo "FAIL: $TEST_NAME - Task logs missing expected content"
|
||||
exit 1
|
||||
fi
|
||||
echo "[TEST] ✓ Task logs recorded" >&2
|
||||
|
||||
# Test 8: Test scheduled task registration
|
||||
echo "[TEST] Testing scheduled task registration..." >&2
|
||||
|
||||
# Clear any existing scheduled task records
|
||||
test_db_query "DELETE FROM _tasks WHERE next_run_at IS NOT NULL"
|
||||
|
||||
# Run task processor to register scheduled tasks
|
||||
php artisan rsx:task:process > /dev/null 2>&1
|
||||
|
||||
# Verify scheduled tasks were registered
|
||||
SCHEDULED_COUNT=$(test_db_count "_tasks WHERE next_run_at IS NOT NULL")
|
||||
if [ "$SCHEDULED_COUNT" -lt 3 ]; then
|
||||
echo "FAIL: $TEST_NAME - Expected at least 3 scheduled tasks registered, found $SCHEDULED_COUNT"
|
||||
exit 1
|
||||
fi
|
||||
echo "[TEST] ✓ Scheduled tasks registered: $SCHEDULED_COUNT" >&2
|
||||
|
||||
# Test 9: Test force-scheduled flag
|
||||
echo "[TEST] Testing --force-scheduled flag..." >&2
|
||||
|
||||
# Count tasks before
|
||||
BEFORE_COUNT=$(test_db_count "_tasks WHERE next_run_at IS NULL AND status = 'pending'")
|
||||
|
||||
# Force dispatch all scheduled tasks
|
||||
php artisan rsx:task:process --force-scheduled > /dev/null 2>&1
|
||||
|
||||
# Count tasks after
|
||||
AFTER_COUNT=$(test_db_count "_tasks WHERE next_run_at IS NULL AND status = 'pending'")
|
||||
|
||||
if [ "$AFTER_COUNT" -le "$BEFORE_COUNT" ]; then
|
||||
echo "FAIL: $TEST_NAME - --force-scheduled should have created new task instances"
|
||||
exit 1
|
||||
fi
|
||||
echo "[TEST] ✓ Force-scheduled created task instances" >&2
|
||||
|
||||
# All tests passed
|
||||
echo "PASS: $TEST_NAME"
|
||||
exit 0
|
||||
|
||||
# TEARDOWN happens automatically via trap
|
||||
Reference in New Issue
Block a user