mirror of
https://github.com/makeplane/plane
synced 2025-08-07 19:59:33 +00:00
Compare commits
1 Commits
feat-chang
...
tests-gith
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
397cecc3a1 |
190
.github/workflows/test-pull-request.yml
vendored
Normal file
190
.github/workflows/test-pull-request.yml
vendored
Normal file
@@ -0,0 +1,190 @@
|
||||
name: Test Pull Request
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
types: ["opened", "synchronize", "ready_for_review"]
|
||||
paths:
|
||||
- 'apiserver/**'
|
||||
- '.github/workflows/test-pull-request.yml'
|
||||
|
||||
jobs:
|
||||
test-apiserver:
|
||||
if: github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:14
|
||||
env:
|
||||
POSTGRES_PASSWORD: plane
|
||||
POSTGRES_USER: plane
|
||||
POSTGRES_DB: plane
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 6379:6379
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Cache Python dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements/test.txt') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
cd apiserver
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements/test.txt
|
||||
|
||||
- name: Set up test environment
|
||||
run: |
|
||||
cd apiserver
|
||||
cat > .env << EOF
|
||||
# Basic Django settings
|
||||
DEBUG=1
|
||||
SECRET_KEY=test-secret-key-for-ci-only-do-not-use-in-production
|
||||
|
||||
# Database Configuration
|
||||
DATABASE_URL=postgres://plane:plane@localhost:5432/plane
|
||||
|
||||
# Redis Configuration
|
||||
REDIS_URL=redis://localhost:6379
|
||||
|
||||
# Email Backend for Testing
|
||||
EMAIL_BACKEND=django.core.mail.backends.locmem.EmailBackend
|
||||
|
||||
# CORS Settings for Testing
|
||||
CORS_ALLOW_ALL_ORIGINS=True
|
||||
CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:3001,http://localhost:3002
|
||||
|
||||
# Disable SSL and security features for testing
|
||||
SECURE_SSL_REDIRECT=False
|
||||
SECURE_HSTS_SECONDS=0
|
||||
SESSION_COOKIE_SECURE=False
|
||||
CSRF_COOKIE_SECURE=False
|
||||
|
||||
# Instance settings
|
||||
INSTANCE_KEY=test-instance-key-for-ci
|
||||
SKIP_ENV_VAR=1
|
||||
|
||||
# File upload settings for testing
|
||||
FILE_SIZE_LIMIT=5242880
|
||||
USE_MINIO=0
|
||||
|
||||
# Base URLs for testing
|
||||
WEB_URL=http://localhost:8000
|
||||
APP_BASE_URL=http://localhost:3000
|
||||
ADMIN_BASE_URL=http://localhost:3001
|
||||
SPACE_BASE_URL=http://localhost:3002
|
||||
LIVE_BASE_URL=http://localhost:3100
|
||||
|
||||
# Session settings
|
||||
SESSION_COOKIE_AGE=604800
|
||||
SESSION_COOKIE_NAME=session-id
|
||||
ADMIN_SESSION_COOKIE_AGE=3600
|
||||
|
||||
# API settings
|
||||
API_KEY_RATE_LIMIT=60/minute
|
||||
|
||||
# Disable external services for testing
|
||||
ENABLE_SIGNUP=1
|
||||
POSTHOG_API_KEY=
|
||||
ANALYTICS_SECRET_KEY=
|
||||
GITHUB_ACCESS_TOKEN=
|
||||
UNSPLASH_ACCESS_KEY=
|
||||
|
||||
# RabbitMQ/Celery settings (will be mocked in tests)
|
||||
RABBITMQ_HOST=localhost
|
||||
RABBITMQ_PORT=5672
|
||||
RABBITMQ_USER=guest
|
||||
RABBITMQ_PASSWORD=guest
|
||||
RABBITMQ_VHOST=/
|
||||
|
||||
# AWS/Storage settings (will be mocked in tests)
|
||||
AWS_ACCESS_KEY_ID=test-access-key
|
||||
AWS_SECRET_ACCESS_KEY=test-secret-key
|
||||
AWS_S3_BUCKET_NAME=test-uploads
|
||||
AWS_REGION=us-east-1
|
||||
EOF
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
cd apiserver
|
||||
python run_tests.py -u -v --coverage
|
||||
|
||||
- name: Run contract tests
|
||||
run: |
|
||||
cd apiserver
|
||||
python run_tests.py -c -v
|
||||
|
||||
- name: Show coverage summary
|
||||
if: always()
|
||||
run: |
|
||||
cd apiserver
|
||||
python -m coverage report --show-missing
|
||||
|
||||
- name: Upload coverage reports
|
||||
uses: codecov/codecov-action@v4
|
||||
if: always() && env.CODECOV_TOKEN != ''
|
||||
with:
|
||||
file: ./apiserver/coverage.xml
|
||||
flags: apiserver
|
||||
name: apiserver-coverage
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
- name: Generate coverage badge
|
||||
if: always()
|
||||
run: |
|
||||
cd apiserver
|
||||
coverage-badge -o coverage.svg
|
||||
continue-on-error: true
|
||||
|
||||
test-summary:
|
||||
if: always() && github.event.pull_request.draft == false
|
||||
needs: [test-apiserver]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Test Results Summary
|
||||
run: |
|
||||
echo "# Test Results Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [[ "${{ needs.test-apiserver.result }}" == "success" ]]; then
|
||||
echo "✅ **API Server Tests**: PASSED" >> $GITHUB_STEP_SUMMARY
|
||||
echo "All tests completed successfully with coverage reporting." >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "❌ **API Server Tests**: FAILED" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Tests failed. Please check the logs for details." >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "## Test Categories Executed" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Unit Tests**: Fast, isolated component tests" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Contract Tests**: API endpoint verification" >> $GITHUB_STEP_SUMMARY
|
||||
@@ -12,6 +12,29 @@ Tests are organized into the following categories:
|
||||
- **App tests**: Test the web application API endpoints (under `/api/`).
|
||||
- **Smoke tests**: Basic tests to verify that the application runs correctly.
|
||||
|
||||
## Continuous Integration (CI)
|
||||
|
||||
Tests run automatically on pull requests via GitHub Actions:
|
||||
|
||||
### Automated Testing Workflow
|
||||
|
||||
When a pull request is created or updated with changes to `apiserver/**` files, the `test-pull-request.yml` workflow automatically:
|
||||
|
||||
1. **Sets up test environment**: PostgreSQL 14, Redis 7, Python 3.11
|
||||
2. **Runs unit tests**: Fast, isolated component tests with coverage
|
||||
3. **Runs contract tests**: API endpoint verification
|
||||
4. **Generates coverage reports**: Enforces 90% threshold with HTML, terminal, and XML formats
|
||||
5. **Uploads to Codecov**: If token is configured
|
||||
|
||||
### CI Environment Variables
|
||||
|
||||
The CI automatically configures comprehensive environment variables including:
|
||||
- Database and Redis connections
|
||||
- Security settings (disabled for testing)
|
||||
- Base URLs for all components
|
||||
- File upload and storage settings
|
||||
- External service configurations (mocked)
|
||||
|
||||
## API vs App Endpoints
|
||||
|
||||
Plane has two types of API endpoints:
|
||||
@@ -32,6 +55,8 @@ Plane has two types of API endpoints:
|
||||
|
||||
## Running Tests
|
||||
|
||||
### Local Testing
|
||||
|
||||
To run all tests:
|
||||
|
||||
```bash
|
||||
@@ -54,20 +79,19 @@ python -m pytest plane/tests/contract/app/
|
||||
python -m pytest plane/tests/smoke/
|
||||
```
|
||||
|
||||
For convenience, we also provide a helper script:
|
||||
### Using the Test Runner
|
||||
|
||||
For convenience, we provide helper scripts:
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
./run_tests.py
|
||||
# Using Python script directly
|
||||
python run_tests.py --coverage --verbose # Full test suite with coverage
|
||||
python run_tests.py -u -v # Unit tests only
|
||||
python run_tests.py -c -v # Contract tests only
|
||||
python run_tests.py -p -v # Parallel execution
|
||||
|
||||
# Run only unit tests
|
||||
./run_tests.py -u
|
||||
|
||||
# Run contract tests with coverage report
|
||||
./run_tests.py -c -o
|
||||
|
||||
# Run tests in parallel
|
||||
./run_tests.py -p
|
||||
# Using shell wrapper
|
||||
./run_tests.sh --coverage --verbose # Full test suite with coverage
|
||||
```
|
||||
|
||||
## Fixtures
|
||||
@@ -134,9 +158,30 @@ Generate a coverage report with:
|
||||
|
||||
```bash
|
||||
python -m pytest --cov=plane --cov-report=term --cov-report=html
|
||||
# Or using the test runner
|
||||
python run_tests.py --coverage
|
||||
```
|
||||
|
||||
This creates an HTML report in the `htmlcov/` directory.
|
||||
This creates an HTML report in the `htmlcov/` directory and enforces the 90% coverage threshold.
|
||||
|
||||
## CI Troubleshooting
|
||||
|
||||
### Common CI Issues
|
||||
|
||||
1. **Test failures**: Check the GitHub Actions logs for specific error messages
|
||||
2. **Coverage below threshold**: Add tests for uncovered code
|
||||
3. **Database connection issues**: Ensure PostgreSQL service is healthy in CI
|
||||
4. **Redis connection issues**: Ensure Redis service is healthy in CI
|
||||
|
||||
### Local Setup for CI Testing
|
||||
|
||||
Make sure you have the test dependencies installed:
|
||||
|
||||
```bash
|
||||
pip install -r requirements/test.txt
|
||||
```
|
||||
|
||||
Set up your local environment with PostgreSQL and Redis, or use the provided Docker setup.
|
||||
|
||||
## Migration from Old Tests
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ def user_data():
|
||||
"email": "test@plane.so",
|
||||
"password": "test-password",
|
||||
"first_name": "Test",
|
||||
"last_name": "User"
|
||||
"last_name": "User",
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ def create_user(db, user_data):
|
||||
user = User.objects.create(
|
||||
email=user_data["email"],
|
||||
first_name=user_data["first_name"],
|
||||
last_name=user_data["last_name"]
|
||||
last_name=user_data["last_name"],
|
||||
)
|
||||
user.set_password(user_data["password"])
|
||||
user.save()
|
||||
@@ -75,4 +75,4 @@ def plane_server(live_server):
|
||||
Renamed version of live_server fixture to avoid name clashes.
|
||||
Returns a live Django server for testing HTTP requests.
|
||||
"""
|
||||
return live_server
|
||||
return live_server
|
||||
|
||||
@@ -6,36 +6,20 @@ import sys
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Run Plane tests")
|
||||
parser.add_argument("-u", "--unit", action="store_true", help="Run unit tests only")
|
||||
parser.add_argument(
|
||||
"-u", "--unit",
|
||||
action="store_true",
|
||||
help="Run unit tests only"
|
||||
"-c", "--contract", action="store_true", help="Run contract tests only"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c", "--contract",
|
||||
action="store_true",
|
||||
help="Run contract tests only"
|
||||
"-s", "--smoke", action="store_true", help="Run smoke tests only"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-s", "--smoke",
|
||||
action="store_true",
|
||||
help="Run smoke tests only"
|
||||
"-o", "--coverage", action="store_true", help="Generate coverage report"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o", "--coverage",
|
||||
action="store_true",
|
||||
help="Generate coverage report"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p", "--parallel",
|
||||
action="store_true",
|
||||
help="Run tests in parallel"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v", "--verbose",
|
||||
action="store_true",
|
||||
help="Verbose output"
|
||||
"-p", "--parallel", action="store_true", help="Run tests in parallel"
|
||||
)
|
||||
parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Build command
|
||||
@@ -56,7 +40,14 @@ def main():
|
||||
|
||||
# Add coverage
|
||||
if args.coverage:
|
||||
cmd.extend(["--cov=plane", "--cov-report=term", "--cov-report=html"])
|
||||
cmd.extend(
|
||||
[
|
||||
"--cov=plane",
|
||||
"--cov-report=term",
|
||||
"--cov-report=html",
|
||||
"--cov-report=xml",
|
||||
]
|
||||
)
|
||||
|
||||
# Add parallel
|
||||
if args.parallel:
|
||||
@@ -71,10 +62,10 @@ def main():
|
||||
|
||||
# Print command
|
||||
print(f"Running: {' '.join(cmd)}")
|
||||
|
||||
|
||||
# Execute command
|
||||
result = subprocess.run(cmd)
|
||||
|
||||
|
||||
# Check coverage thresholds if coverage is enabled
|
||||
if args.coverage:
|
||||
print("Checking coverage thresholds...")
|
||||
@@ -83,9 +74,9 @@ def main():
|
||||
if coverage_result.returncode != 0:
|
||||
print("Coverage below threshold (90%)")
|
||||
sys.exit(coverage_result.returncode)
|
||||
|
||||
|
||||
sys.exit(result.returncode)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user