Fix tutorial and add automated validation #6
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Validate Tutorial | |
| on: | |
| push: | |
| branches: [ master ] | |
| pull_request: | |
| branches: [ master ] | |
| jobs: | |
| validate-tutorial: | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: examples/tenant_tutorial | |
| services: | |
| postgres: | |
| image: postgres:15 | |
| env: | |
| POSTGRES_USER: postgres | |
| POSTGRES_PASSWORD: root | |
| POSTGRES_DB: tenant_tutorial | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| ports: | |
| - 5432:5432 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Set up uv | |
| uses: astral-sh/setup-uv@v4 | |
| with: | |
| version: "latest" | |
| - name: Change to tutorial directory | |
| run: pwd # Just to verify we're in the right directory | |
| - name: Create virtual environment and install dependencies | |
| run: | | |
| uv venv | |
| uv pip sync requirements.txt | |
| - name: Verify database connection | |
| run: | | |
| echo "SELECT 1" | uv run python manage.py dbshell | |
| - name: Run initial migrations | |
| run: | | |
| uv run python manage.py migrate_schemas --shared | |
| - name: Create public tenant | |
| run: | | |
| uv run python manage.py create_client public localhost "Tutorial Public Tenant" --description "Public tenant for tutorial validation" | |
| - name: Create sample tenants | |
| run: | | |
| uv run python manage.py create_client tenant1 tenant1.example.com "Tenant 1 - Test Company" --description "First test tenant for validation" | |
| uv run python manage.py create_client tenant2 tenant2.example.com "Tenant 2 - Another Company" --description "Second test tenant for validation" | |
| - name: Verify tenants were created | |
| run: | | |
| uv run python manage.py list_tenants | |
| # Verify we have exactly 3 tenants (public + 2 test tenants) | |
| tenant_count=$(uv run python manage.py list_tenants | grep -E '^(public|tenant1|tenant2)' | wc -l) | |
| if [ "$tenant_count" != "3" ]; then | |
| echo "Expected 3 tenants, found $tenant_count" | |
| exit 1 | |
| fi | |
| echo "All tenants created successfully" | |
| - name: Start Django development server | |
| run: | | |
| uv run gunicorn -D -p django_server.pid -b 0.0.0.0:8000 tenant_tutorial.wsgi:application | |
| # Wait for server to start | |
| sleep 10 | |
| # Check if server is running | |
| if ! kill -0 $(cat django_server.pid) 2>/dev/null; then | |
| echo "Server failed to start" | |
| exit 1 | |
| fi | |
| - name: Test public tenant endpoint | |
| run: | | |
| response=$(curl -s -w "%{http_code}" -H "Host: localhost" http://localhost:8000/ -o /tmp/public_response.html) | |
| echo "Public tenant response code: $response" | |
| if [ "$response" != "200" ]; then | |
| echo "Public tenant test failed with response code: $response" | |
| echo "Response content:" | |
| cat /tmp/public_response.html | |
| exit 1 | |
| fi | |
| # Check that the response contains expected tutorial content | |
| if ! grep -q "Welcome to the Tenant Tutorial" /tmp/public_response.html; then | |
| echo "Public tenant response doesn't contain expected tutorial content" | |
| cat /tmp/public_response.html | |
| exit 1 | |
| fi | |
| echo "Public tenant test passed" | |
| - name: Test first tenant endpoint | |
| run: | | |
| response=$(curl -s -w "%{http_code}" -H "Host: tenant1.example.com" http://localhost:8000/ -o /tmp/tenant1_response.html) | |
| echo "Tenant 1 response code: $response" | |
| if [ "$response" != "200" ]; then | |
| echo "Tenant 1 test failed with response code: $response" | |
| echo "Response content:" | |
| cat /tmp/tenant1_response.html | |
| exit 1 | |
| fi | |
| # Check that the response contains tenant-specific content | |
| if ! grep -q "Tenant 1 - Test Company" /tmp/tenant1_response.html; then | |
| echo "Tenant 1 response doesn't contain expected tenant name" | |
| cat /tmp/tenant1_response.html | |
| exit 1 | |
| fi | |
| echo "Tenant 1 test passed" | |
| - name: Test second tenant endpoint | |
| run: | | |
| response=$(curl -s -w "%{http_code}" -H "Host: tenant2.example.com" http://localhost:8000/ -o /tmp/tenant2_response.html) | |
| echo "Tenant 2 response code: $response" | |
| if [ "$response" != "200" ]; then | |
| echo "Tenant 2 test failed with response code: $response" | |
| echo "Response content:" | |
| cat /tmp/tenant2_response.html | |
| exit 1 | |
| fi | |
| # Check that the response contains tenant-specific content | |
| if ! grep -q "Tenant 2 - Another Company" /tmp/tenant2_response.html; then | |
| echo "Tenant 2 response doesn't contain expected tenant name" | |
| cat /tmp/tenant2_response.html | |
| exit 1 | |
| fi | |
| echo "Tenant 2 test passed" | |
| - name: Test tenant data isolation | |
| run: | | |
| # Add users to tenant1 | |
| uv run python manage.py tenant_command shell --schema=tenant1 << 'EOF' | |
| from django.contrib.auth.models import User | |
| User.objects.create_user('tenant1user', '[email protected]', 'password') | |
| print(f"Tenant1 users count: {User.objects.count()}") | |
| EOF | |
| # Check that tenant2 doesn't have these users | |
| uv run python manage.py tenant_command shell --schema=tenant2 << 'EOF' | |
| from django.contrib.auth.models import User | |
| count = User.objects.count() | |
| print(f"Tenant2 users count: {count}") | |
| if count != 0: | |
| print("ERROR: Data isolation failed - tenant2 can see tenant1 users") | |
| exit(1) | |
| print("Data isolation test passed") | |
| EOF | |
| - name: Stop Django server | |
| if: always() | |
| run: | | |
| if [ -f django_server.pid ]; then | |
| kill $(cat django_server.pid) || true | |
| rm django_server.pid | |
| fi | |
| - name: Upload response files on failure | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: response-files | |
| path: /tmp/*_response.html | |
| retention-days: 1 |