Week 4: User Acceptance Testing (UAT) and Sign-Off
Test Matrix Week 4
┌────┬─────────────────────────────────────────────────────────┬────────┐
│ ID │ UAT Scenario │ Pass │
├────┼─────────────────────────────────────────────────────────┼────────┤
│4.1 │ Simulated workday: Teams + O365 + Salesforce user flow │ No err │
│4.2 │ Developer: GCP Vertex AI model inference │ <2s │
│4.3 │ Finance: Azure SQL query from branch │ <500ms │
│4.4 │ IOT device: MQTT publish to cloud │ <100ms │
│4.5 │ Security: Umbrella blocks malicious domain │ Blocked│
│4.6 │ SIEM: Splunk alert triggered on simulated threat │ <5min │
│4.7 │ AppDynamics: Full trace branch→hub→Azure transaction │ Traced │
│4.8 │ ThousandEyes: Path change correlates with Splunk alert │ Correl │
│4.9 │ Performance baseline documentation │ Signed │
│4.10│ NOC: Runbook execution drill │ Passed │
└────┴─────────────────────────────────────────────────────────┴────────┘
TEST 4.1: Simulated Workday (End-User Experience)
#!/usr/bin/env python3
"""
Simulated workday user experience test
Runs sequentially as a typical Abhavtech employee would work
"""
import requests
import time
import subprocess
import json
def measure_latency(url, count=5, timeout=5):
"""Measure HTTP response time"""
times = []
for _ in range(count):
try:
start = time.time()
r = requests.get(url, timeout=timeout, verify=False)
elapsed = (time.time() - start) * 1000
times.append((r.status_code, elapsed))
except requests.Timeout:
times.append((0, timeout * 1000))
time.sleep(0.5)
return times
def run_workday_simulation():
print("=" * 65)
print(" SIMULATED WORKDAY — END-USER EXPERIENCE TEST")
print(f" Started: {time.strftime('%Y-%m-%d %H:%M:%S')}")
print("=" * 65)
tests = [
{
"name": "Outlook Web Access (Exchange Online)",
"url": "https://outlook.office365.com",
"threshold_ms": 500,
"expected_status": 200
},
{
"name": "Teams Web Client",
"url": "https://teams.microsoft.com",
"threshold_ms": 500,
"expected_status": 200
},
{
"name": "SharePoint (Document Access)",
"url": "https://abhavtech.sharepoint.com",
"threshold_ms": 800,
"expected_status": 200
},
{
"name": "Salesforce CRM",
"url": "https://abhavtech.my.salesforce.com",
"threshold_ms": 1000,
"expected_status": 200
},
{
"name": "ServiceNow ITSM",
"url": "https://abhavtech.service-now.com",
"threshold_ms": 1000,
"expected_status": 200
},
{
"name": "Workday HCM",
"url": "https://wd3.myworkday.com/abhavtech/login.htmld",
"threshold_ms": 1500,
"expected_status": 200
},
{
"name": "Azure SQL via Private Endpoint (ODBC)",
"url": None,
"host": "10.100.10.10",
"port": 1433,
"threshold_ms": 500
},
{
"name": "GCP Vertex AI API",
"url": "https://asia-south1-aiplatform.googleapis.com",
"threshold_ms": 50,
"expected_status": 401 # Unauthorized without token — still proves reachability
}
]
results = []
for test in tests:
print(f"\n--- {test['name']} ---")
if test.get("url"):
measurements = measure_latency(test["url"])
avg_ms = sum(m[1] for m in measurements) / len(measurements)
status_codes = [m[0] for m in measurements]
ok = avg_ms < test["threshold_ms"] and any(
s in [test.get("expected_status", 200), 200, 201, 302]
for s in status_codes
)
icon = "✅" if ok else "❌"
print(f" {icon} Avg latency: {avg_ms:.0f}ms | Threshold: {test['threshold_ms']}ms | "
f"Status: {status_codes[0]}")
else:
# TCP port test (Azure SQL)
import socket
start = time.time()
try:
s = socket.create_connection((test["host"], test["port"]), timeout=5)
elapsed = (time.time() - start) * 1000
s.close()
ok = elapsed < test["threshold_ms"]
icon = "✅" if ok else "⚠️"
print(f" {icon} TCP connect: {elapsed:.0f}ms | Threshold: {test['threshold_ms']}ms")
except Exception as e:
print(f" ❌ Connection failed: {e}")
ok = False
results.append({"test": test["name"], "passed": ok})
## Summary
passed = sum(1 for r in results if r["passed"])
total = len(results)
print(f"\n{'=' * 65}")
print(f" UAT 4.1 RESULT: {passed}/{total} applications meeting SLA")
print(f" {'✅ PASS — Ready for production sign-off' if passed == total else '❌ FAIL — Resolve failures before sign-off'}")
print("=" * 65)
if __name__ == "__main__":
run_workday_simulation()
TEST 4.5: Umbrella Security (Malicious Domain Block)
#!/bin/bash
## umbrella_security_test.sh
echo "=== TEST 4.5: UMBRELLA SECURITY CONTROLS ==="
## Test from Bangalore branch — these should all be BLOCKED by Umbrella DNS
MALICIOUS_DOMAINS=(
"malwaredomainlist.com" # Known malware C2
"phishtank.org" # Phishing test domain
"www.eicar.org" # EICAR malware test
)
BRANCH="blr-br-01"
UMBRELLA_BLOCK_IP="146.112.62.40" # Umbrella block page IP
echo ""
echo "Testing from Bangalore branch (DNS via Umbrella 208.67.222.222):"
for DOMAIN in "${MALICIOUS_DOMAINS[@]}"; do
RESULT=$(ssh admin@$BRANCH "nslookup $DOMAIN 208.67.222.222 2>/dev/null | grep 'Address' | tail -1 | awk '{print \$2}'" 2>/dev/null)
if [[ "$RESULT" == "146.112.62.40" ]] || [[ "$RESULT" == "146.112.63."* ]] || [[ "$RESULT" == "NXDOMAIN" ]]; then
echo " ✅ BLOCKED: $DOMAIN → $RESULT (Umbrella block page)"
else
echo " ❌ NOT BLOCKED: $DOMAIN → $RESULT"
fi
done
## Test DNSCrypt is active (DNS queries encrypted)
echo ""
echo "--- DNSCrypt Encryption ---"
ssh admin@$BRANCH "show umbrella dnscrypt" 2>/dev/null | grep -i "enabled\|disabled"
## Expected: DNSCrypt: Enabled
## Test SWG is blocking a banned category (gambling)
echo ""
echo "--- SWG URL Category Block (Gambling) ---"
GAMBLING_TEST=$(ssh admin@$BRANCH "curl -s -o /dev/null -w '%{http_code}' --max-time 5 https://www.bet365.com" 2>/dev/null)
[[ "$GAMBLING_TEST" == "403" ]] || [[ "$GAMBLING_TEST" == "000" ]] && \
echo " ✅ Gambling site blocked by SWG (HTTP $GAMBLING_TEST)" || \
echo " ⚠️ Gambling site returned HTTP $GAMBLING_TEST (check SWG policy)"
TEST 4.6: Splunk SIEM Alert Triggered
#!/bin/bash
## splunk_alert_test.sh
echo "=== TEST 4.6: SPLUNK SIEM ALERT PIPELINE ==="
## Generate a simulated security event: repeated auth failure
## This should trigger Splunk MLTK alert → ServiceNow ticket
echo "--- Generating test event: 5 failed SSH logins to 10.100.1.50 ---"
for i in $(seq 1 5); do
ssh -o StrictHostKeyChecking=no -o ConnectTimeout=2 \
baduser@10.100.1.50 "test" 2>/dev/null || true
sleep 1
done
echo "Events generated. Waiting 5 minutes for Splunk to alert..."
sleep 300
## Check if Splunk alert fired
SPLUNK_HOST="splunk.abhavtech.com"
ALERT_CHECK=$(curl -s -k -u "admin:<PASSWORD>" \
"https://$SPLUNK_HOST:8089/services/alerts/fired_alerts" \
-G --data "output_mode=json" | \
python3 -c "
import json, sys
data = json.load(sys.stdin)
alerts = data.get('entry', [])
recent = [a for a in alerts if 'auth' in a.get('name','').lower() or 'fail' in a.get('name','').lower()]
print(f'Recent auth-related alerts: {len(recent)}')
for a in recent[:3]:
print(f' {a[\"name\"]} — {a.get(\"content\",{}).get(\"triggered_at\",\"?\")}')
" 2>/dev/null)
echo "$ALERT_CHECK"
echo ""
echo "Pass Criteria: Auth failure alert triggered within 5 minutes"
TEST 4.8: ThousandEyes + Splunk Correlation
#!/bin/bash
## thousandeyes_splunk_correlation.sh
echo "=== TEST 4.8: THOUSANDEYES ↔ SPLUNK CORRELATION ==="
TE_TOKEN="<TE_BEARER_TOKEN>"
## Step 1: Get a known ThousandEyes alert (recent)
TE_ALERTS=$(curl -s -X GET "https://api.thousandeyes.com/v7/alerts?state=active" \
-H "Authorization: Bearer $TE_TOKEN" | \
python3 -c "
import json,sys
data = json.load(sys.stdin)
alerts = data.get('alerts', [])
if alerts:
a = alerts[0]
print(f'Alert ID: {a.get(\"alertId\")}')
print(f'Test: {a.get(\"testName\")}')
print(f'Time: {a.get(\"startDate\")}')
print(f'Type: {a.get(\"type\")}')
else:
print('No active TE alerts — simulating one...')
")
echo "$TE_ALERTS"
## Step 2: Verify same event is in Splunk
echo ""
echo "--- Checking Splunk for correlated ThousandEyes event ---"
curl -s -k -u "admin:<PASSWORD>" \
"https://splunk.abhavtech.com:8089/services/search/jobs/oneshot" \
--data-urlencode "search=index=thousandeyes sourcetype=thousandeyes:alert earliest=-30m | head 3" \
-d "output_mode=json" | \
python3 -c "
import json,sys
data = json.load(sys.stdin)
results = data.get('results', [])
print(f'TE alerts in Splunk (last 30 min): {len(results)}')
for r in results[:3]:
print(f' {r}')
"
echo ""
echo "Pass Criteria: ThousandEyes alert appears in Splunk within 5 minutes (polling interval)"