Skip to content

User Acceptance Test Plan — Server Infrastructure

Test Series: 1 to 4

Environment: AWS EC2 — Ubuntu 24.04.4 LTS, 1 GB RAM, 60 GB Storage

1. Purpose & Scope

This UAT plan validates that the server infrastructure supporting the Django/DRF application is correctly provisioned, secured, and stable enough for acceptance sign-off. It covers OS/environment setup, service configuration, security controls, resource constraints, background processing, third-party integrations, and recovery procedures.

It does not cover application business logic — that should be a separate functional UAT plan.

2. Environment Under Test

Component Detail
Cloud provider AWS EC2
OS Ubuntu 24.04.4 LTS
RAM 1 GB
Storage 60 GB
App framework Django 5.2.3 / DRF 3.15.1
Web/WSGI server Apache2 + mod_wsgi
Task queue Celery 5.3.6 + Redis 5.0.3
Database MongoDB (pymongo 4.13.2)
Auth SimpleJWT 5.3.1, Argon2 (argon2-cffi)
Static files WhiteNoise 6.6.0
Scheduled jobs django-crontab 0.7.1
Object storage AWS S3 (boto3)
Push/notifications Firebase Admin

3. Entry Criteria

  • Application code deployed to EC2 instance
  • Environment variables / .env configured (python-decouple / django-environ)
  • Domain/SSL (if applicable) pointed to instance
  • All services (Apache2, Redis, cron) enabled

4. Test Cases

4.1 Environment & Deployment Verification

ID Test Case Steps Expected Result Pass/Fail
ENV-01 OS version confirmed lsb_release -a Reports Ubuntu 24.04.4 LTS
ENV-02 Python version matches project requirement python3 --version Matches version pinned in project docs
ENV-03 Virtual environment isolated Activate venv, pip freeze Matches requirements.txt exactly, no stray global packages
ENV-04 /Config/config/config.py / secrets not committed to repo Inspect .gitignore, repo history No secrets in version control
ENV-05 DEBUG=False in production settings Check settings/env var Confirmed False
ENV-06 ALLOWED_HOSTS restricted Check settings Only intended domain(s)/IP present, no *
ENV-07 Disk usage healthy df -h Used space < 80% of 60 GB
ENV-08 System packages up to date apt list --upgradable No critical security patches pending

4.2 Service Configuration

ID Test Case Steps Expected Result Pass/Fail
SVC-01 Apache2 running as systemd service systemctl status apache2 Active (running), enabled on boot
SVC-02 mod_wsgi module loaded and configured apache2ctl -M \| grep wsgi wsgi_module listed
SVC-03 Apache VirtualHost routes correctly to WSGI app Hit public URL Serves Django app, not default Apache placeholder page
SVC-04 Apache recovers from worker/process crash Kill a wsgi daemon process (if using daemon mode) Apache respawns process automatically
SVC-05 Apache config syntax valid apache2ctl configtest Returns "Syntax OK"
SVC-06 Redis service active systemctl status redis / redis-cli ping Returns PONG
SVC-07 Celery worker running systemctl status celery or celery -A app status Worker online
SVC-08 Celery task executes end-to-end Trigger a known async task Task completes, result/logs confirm success
SVC-09 MongoDB reachable pymongo connection test from Django shell Connects without timeout/auth error
SVC-10 WhiteNoise/Apache serving static files correctly Request a static asset URL directly Returns 200 with correct content-type, cache headers
SVC-11 django-crontab jobs registered python manage.py crontab show Expected jobs listed
SVC-12 Cron job executes on schedule Wait for/simulate scheduled time Job runs, expected side effect occurs (log entry, DB update, etc.)

4.3 Security Configuration

ID Test Case Steps Expected Result Pass/Fail
SEC-01 HTTPS enforced Request via http:// Redirects to https:// (if SSL configured)
SEC-02 JWT auth required on protected endpoints Hit protected API without token Returns 401
SEC-03 JWT expiry enforced Use expired token Returns 401, not silently accepted
SEC-04 Password hashing uses Argon2 Inspect a stored password hash Prefixed argon2$...
SEC-05 django-axes lockout works Attempt failed logins past threshold Account/IP locked per configured limit
SEC-06 Axes lockout resets correctly Wait cooldown period or admin reset Access restored
SEC-07 CORS restricted to intended origins Request from disallowed origin Blocked by CORS policy
SEC-08 CORS allows intended frontend origin Request from approved origin Succeeds
SEC-09 Security headers present Inspect response headers (CSP, X-Frame-Options, HSTS if applicable) Present per configuration
SEC-10 EC2 Security Group least-privilege Review AWS console/CLI Only required ports open (22 restricted to known IP, 80/443 public, DB port not public)
SEC-11 SSH key-based auth only Check sshd_config Password auth disabled
SEC-12 Firewall (ufw) active ufw status Enabled with expected rules
SEC-13 django-watchman health checks pass Hit /watchman/ endpoint All checks (DB, cache, storage) report OK

4.4 Resource Constraints (1 GB RAM specific)

Given only 1 GB RAM, this tier needs explicit validation — it's the most likely failure point.

ID Test Case Steps Expected Result Pass/Fail
RES-01 Swap configured swapon --show, free -h Swap space present (recommended if not already) to avoid OOM kills
RES-02 mod_wsgi process/thread count appropriate Check WSGIDaemonProcess directive (processes/threads) Sized conservatively for 1 GB RAM (e.g. 2 processes × 2–5 threads, not left at high defaults)
RES-03 No OOM kills under normal load dmesg | grep -i "out of memory" after test traffic No OOM kill events logged
RES-04 Memory usage under idle free -h at rest Headroom exists (not >85% used at idle)
RES-05 Memory usage under concurrent requests Load test with realistic concurrent users (e.g. 10–20) App remains responsive, no crash/restart loop
RES-06 Celery worker memory footprint acceptable Monitor top/htop during task processing No runaway memory growth
RES-07 Redis memory limit configured Check maxmemory in redis.conf Set explicitly, not left unbounded on a 1GB host
RES-08 CPU load sustainable uptime during load test Load average stays reasonable for available vCPUs
RES-09 Apache MPM appropriate for RAM budget apache2ctl -M \| grep mpm and review MPM config event/worker MPM with mod_wsgi daemon mode preferred over prefork on 1 GB; process/thread limits explicitly capped
RES-10 mod_wsgi daemon mode in use (not embedded) Check VirtualHost/wsgi.conf for WSGIDaemonProcess Daemon mode configured, isolating app processes from Apache's own workers

4.5 Logging & Monitoring

ID Test Case Steps Expected Result Pass/Fail
LOG-01 Request IDs present in logs Make a request, check logs Unique request ID (django-log-request-id) traceable across log lines
LOG-02 Application errors logged, not exposed to user Trigger a 500 error User sees generic error page, stack trace only in server logs
LOG-03 Log rotation configured Check logrotate config or app-level rotation Logs don't fill disk over time
LOG-04 Apache access/error logs writing correctly Inspect /var/log/apache2/access.log and error.log Populated, timestamps current

4.6 Third-Party Integrations

ID Test Case Steps Expected Result Pass/Fail
INT-01 S3 upload works Trigger a file upload flow File appears in target S3 bucket
INT-02 S3 bucket permissions correct Attempt public access to a private object URL Access denied unless intentionally public
INT-03 Firebase Admin SDK initializes Trigger a Firebase-dependent action (e.g. push notification) No credential/init errors, action succeeds
INT-04 IAM role / credentials scoped correctly Review AWS IAM policy attached to EC2 instance or service account Least-privilege, no wildcard * resource access

4.7 Backup & Recovery

ID Test Case Steps Expected Result Pass/Fail
BAK-01 MongoDB backup process exists Run backup script/verify managed backup (if Atlas) Backup completes and is restorable
BAK-02 Restore from backup tested Restore to a staging instance Data integrity confirmed
BAK-03 EC2 instance recovery plan Review AMI snapshot schedule or IaC redeploy process Instance can be rebuilt within acceptable RTO
BAK-04 Environment variables recoverable Confirm secrets stored in a secrets manager/secure vault, not only on the instance Can redeploy without data loss

4.8 Deployment & Rollback

ID Test Case Steps Expected Result Pass/Fail
DEP-01 Deployment process documented and repeatable Follow deployment steps from scratch (on staging) App comes up successfully following documented steps
DEP-02 Rollback to previous version possible Simulate rollback Previous stable version restored without data loss
DEP-03 Zero/minimal downtime on deploy Deploy during monitored window Downtime within acceptable threshold (define SLA)

5. Exit Criteria

  • All Critical/High severity test cases (SEC, RES, BAK) pass
  • Any failed Medium/Low cases have documented remediation plan with owner and date
  • Sign-off obtained from [stakeholder/role]

6. Sign-off

Role Name Signature Date
Infrastructure Owner
Project Lead

Notes on Your Specific Setup

  • Apache's MPM matters. Check which MPM is active (apache2ctl -M | grep mpm) — prefork spawns a full process per connection and is heavy on 1 GB; event or worker MPM paired with mod_wsgi daemon mode is lighter. This is worth its own test case (call it RES-09) if you haven't settled on an MPM yet.
  • Swap is worth confirming — without it, an OOM spike (e.g. a large file upload processed via python-magic, or a burst of Celery tasks) can kill Apache/mod_wsgi processes outright rather than degrading gracefully.
  • Redis on the same 1 GB box competing with Apache/mod_wsgi/Celery for memory is a common bottleneck — RES-07 (maxmemory + eviction policy) matters more than usual here.
  • Static files: with Apache in front, you can serve static assets directly via an Apache Alias (bypassing Django/WhiteNoise entirely for those requests), which is lighter on the 1 GB instance than routing static files through the WSGI app. Worth deciding which approach you're using and testing accordingly in SVC-10.