project initialization
Some checks failed
System Monitoring / Health Checks (push) Has been cancelled
System Monitoring / Performance Monitoring (push) Has been cancelled
System Monitoring / Database Monitoring (push) Has been cancelled
System Monitoring / Cache Monitoring (push) Has been cancelled
System Monitoring / Log Monitoring (push) Has been cancelled
System Monitoring / Resource Monitoring (push) Has been cancelled
System Monitoring / Uptime Monitoring (push) Has been cancelled
System Monitoring / Backup Monitoring (push) Has been cancelled
System Monitoring / Security Monitoring (push) Has been cancelled
System Monitoring / Monitoring Dashboard (push) Has been cancelled
System Monitoring / Alerting (push) Has been cancelled
Security Scanning / Dependency Scanning (push) Has been cancelled
Security Scanning / Code Security Scanning (push) Has been cancelled
Security Scanning / Secrets Scanning (push) Has been cancelled
Security Scanning / Container Security Scanning (push) Has been cancelled
Security Scanning / Compliance Checking (push) Has been cancelled
Security Scanning / Security Dashboard (push) Has been cancelled
Security Scanning / Security Remediation (push) Has been cancelled

This commit is contained in:
2025-10-05 02:37:33 +08:00
parent 2cbb6d5fa1
commit b3fff546e9
226 changed files with 97805 additions and 35 deletions

View File

@@ -0,0 +1,626 @@
"""
Integration test for healthcare module operations.
This test MUST fail before implementation.
"""
import pytest
from django.test import TestCase
from django.urls import reverse
from rest_framework.test import APIClient
from rest_framework import status
import json
from datetime import datetime, timedelta
class HealthcareOperationsIntegrationTest(TestCase):
def setUp(self):
self.client = APIClient()
# Tenant authentication header
self.tenant_auth = {'HTTP_AUTHORIZATION': 'Bearer tenant_token'}
# Test patient data
self.patient_data = {
'ic_number': '900101-10-1234',
'name': 'Ahmad bin Hassan',
'gender': 'MALE',
'date_of_birth': '1990-01-01',
'phone': '+60123456789',
'email': 'ahmad.hassan@example.com',
'address': {
'street': '123 Jalan Healthcare',
'city': 'Kuala Lumpur',
'state': 'Wilayah Persekutuan',
'postal_code': '50400',
'country': 'Malaysia'
},
'blood_type': 'O+',
'allergies': ['Penicillin'],
'medications': ['Metformin 500mg']
}
# Test doctor data
self.doctor_data = {
'name': 'Dr. Sarah Johnson',
'specialization': 'General Practitioner',
'license_number': 'L12345',
'department': 'Primary Care',
'phone': '+60312345678',
'email': 'sarah.johnson@hospital.com'
}
def test_complete_patient_workflow(self):
"""Test complete patient workflow from registration to treatment."""
# Step 1: Patient registration (should fail before implementation)
patient_response = self.client.post(
'/api/v1/healthcare/patients/',
data=json.dumps(self.patient_data),
content_type='application/json',
**self.tenant_auth
)
assert patient_response.status_code == status.HTTP_201_CREATED
patient_data = patient_response.json()
# Verify patient structure
assert 'id' in patient_data
assert patient_data['ic_number'] == self.patient_data['ic_number']
assert patient_data['name'] == self.patient_data['name']
assert patient_data['age'] == 34 # Calculated from DOB
assert patient_data['status'] == 'ACTIVE'
# Step 2: Create doctor
doctor_response = self.client.post(
'/api/v1/healthcare/doctors/',
data=json.dumps(self.doctor_data),
content_type='application/json',
**self.tenant_auth
)
assert doctor_response.status_code == status.HTTP_201_CREATED
doctor_data = doctor_response.json()
# Step 3: Schedule appointment
appointment_data = {
'patient_id': patient_data['id'],
'doctor_id': doctor_data['id'],
'appointment_datetime': '2024-02-15T14:30:00+08:00',
'duration': 30,
'type': 'CONSULTATION',
'reason': 'Regular checkup for diabetes management',
'priority': 'NORMAL'
}
appointment_response = self.client.post(
'/api/v1/healthcare/appointments/',
data=json.dumps(appointment_data),
content_type='application/json',
**self.tenant_auth
)
assert appointment_response.status_code == status.HTTP_201_CREATED
appointment_data = appointment_response.json()
assert appointment_data['status'] == 'SCHEDULED'
# Step 4: Update appointment status to in-progress
status_update_response = self.client.put(
f'/api/v1/healthcare/appointments/{appointment_data["id"]}/status/',
data=json.dumps({'status': 'IN_PROGRESS'}),
content_type='application/json',
**self.tenant_auth
)
assert status_update_response.status_code == status.HTTP_200_OK
# Step 5: Create medical record
medical_record_data = {
'patient_id': patient_data['id'],
'appointment_id': appointment_data['id'],
'doctor_id': doctor_data['id'],
'diagnosis': 'Type 2 Diabetes - well controlled',
'treatment': 'Continue current medication regimen',
'prescriptions': [
{
'medication': 'Metformin',
'dosage': '500mg',
'frequency': 'Twice daily',
'duration': '30 days',
'instructions': 'Take with meals'
}
],
'vitals': {
'blood_pressure': '120/80',
'heart_rate': 72,
'temperature': 36.5,
'weight': 75.5,
'height': 175.0
},
'notes': 'Patient reports good compliance with medication. Blood sugar levels well controlled.'
}
record_response = self.client.post(
'/api/v1/healthcare/medical-records/',
data=json.dumps(medical_record_data),
content_type='application/json',
**self.tenant_auth
)
assert record_response.status_code == status.HTTP_201_CREATED
record_data = record_response.json()
# Step 6: Complete appointment
complete_response = self.client.put(
f'/api/v1/healthcare/appointments/{appointment_data["id"]}/status/',
data=json.dumps({'status': 'COMPLETED'}),
content_type='application/json',
**self.tenant_auth
)
assert complete_response.status_code == status.HTTP_200_OK
# Step 7: Schedule follow-up appointment
follow_up_data = {
'patient_id': patient_data['id'],
'doctor_id': doctor_data['id'],
'appointment_datetime': '2024-03-15T14:30:00+08:00',
'duration': 20,
'type': 'FOLLOW_UP',
'reason': 'Diabetes follow-up'
}
follow_up_response = self.client.post(
'/api/v1/healthcare/appointments/',
data=json.dumps(follow_up_data),
content_type='application/json',
**self.tenant_auth
)
assert follow_up_response.status_code == status.HTTP_201_CREATED
def test_medical_records_management(self):
"""Test medical records management and history."""
# Create patient first
patient_response = self.client.post(
'/api/v1/healthcare/patients/',
data=json.dumps(self.patient_data),
content_type='application/json',
**self.tenant_auth
)
assert patient_response.status_code == status.HTTP_201_CREATED
patient_data = patient_response.json()
# Create multiple medical records over time
records_data = [
{
'diagnosis': 'Hypertension',
'treatment': 'Lifestyle modifications',
'prescriptions': [
{
'medication': 'Lisinopril',
'dosage': '10mg',
'frequency': 'Once daily'
}
]
},
{
'diagnosis': 'Annual checkup - normal',
'treatment': 'Continue healthy lifestyle',
'vitals': {
'blood_pressure': '118/76',
'heart_rate': 68,
'cholesterol': 180
}
}
]
created_records = []
for record_data in records_data:
full_record_data = {
'patient_id': patient_data['id'],
'doctor_id': 'doctor-001',
'diagnosis': record_data['diagnosis'],
'treatment': record_data['treatment'],
**{k: v for k, v in record_data.items() if k not in ['diagnosis', 'treatment']}
}
record_response = self.client.post(
'/api/v1/healthcare/medical-records/',
data=json.dumps(full_record_data),
content_type='application/json',
**self.tenant_auth
)
assert record_response.status_code == status.HTTP_201_CREATED
created_records.append(record_response.json())
# Test medical history retrieval
history_response = self.client.get(
f'/api/v1/healthcare/patients/{patient_data["id"]}/medical-history/',
**self.tenant_auth
)
assert history_response.status_code == status.HTTP_200_OK
history_data = history_response.json()
assert 'medical_records' in history_data
assert 'conditions' in history_data
assert 'medications' in history_data
assert 'allergies' in history_data
# Verify records are chronological
records = history_data['medical_records']
assert len(records) == len(created_records)
# Test record search and filtering
search_response = self.client.get(
f'/api/v1/healthcare/medical-records/',
data={'patient_id': patient_data['id'], 'diagnosis': 'Hypertension'},
**self.tenant_auth
)
assert search_response.status_code == status.HTTP_200_OK
search_results = search_response.json()['records']
assert len(search_results) > 0
assert any('Hypertension' in record['diagnosis'] for record in search_results)
def test_prescription_management(self):
"""Test prescription management and dispensing."""
# Create patient
patient_response = self.client.post(
'/api/v1/healthcare/patients/',
data=json.dumps(self.patient_data),
content_type='application/json',
**self.tenant_auth
)
assert patient_response.status_code == status.HTTP_201_CREATED
patient_data = patient_response.json()
# Create prescription
prescription_data = {
'patient_id': patient_data['id'],
'doctor_id': 'doctor-001',
'medications': [
{
'name': 'Amoxicillin',
'dosage': '500mg',
'frequency': 'Three times daily',
'duration': '7 days',
'quantity': 21,
'instructions': 'Take after meals',
'refills_allowed': 0
},
{
'name': 'Ibuprofen',
'dosage': '400mg',
'frequency': 'As needed for pain',
'duration': '3 days',
'quantity': 9,
'instructions': 'Take with food',
'refills_allowed': 1
}
],
'diagnosis': 'Bacterial infection',
'notes': 'Complete full course of antibiotics'
}
prescription_response = self.client.post(
'/api/v1/healthcare/prescriptions/',
data=json.dumps(prescription_data),
content_type='application/json',
**self.tenant_auth
)
assert prescription_response.status_code == status.HTTP_201_CREATED
prescription_data = prescription_response.json()
# Test prescription status management
dispense_data = {
'dispensed_by': 'pharmacist-001',
'dispensed_at': datetime.now().isoformat(),
'notes': 'Patient counseled on medication use'
}
dispense_response = self.client.post(
f'/api/v1/healthcare/prescriptions/{prescription_data["id"]}/dispense/',
data=json.dumps(dispense_data),
content_type='application/json',
**self.tenant_auth
)
assert dispense_response.status_code == status.HTTP_200_OK
# Test refill request
refill_response = self.client.post(
f'/api/v1/healthcare/prescriptions/{prescription_data["id"]}/refill/',
data=json.dumps({}),
content_type='application/json',
**self.tenant_auth
)
assert refill_response.status_code == status.HTTP_200_OK
def test_laboratory_and_imaging_orders(self):
"""Test laboratory and imaging order management."""
# Create patient
patient_response = self.client.post(
'/api/v1/healthcare/patients/',
data=json.dumps(self.patient_data),
content_type='application/json',
**self.tenant_auth
)
assert patient_response.status_code == status.HTTP_201_CREATED
patient_data = patient_response.json()
# Create lab order
lab_order_data = {
'patient_id': patient_data['id'],
'doctor_id': 'doctor-001',
'tests': [
{
'test_code': 'CBC',
'test_name': 'Complete Blood Count',
'priority': 'ROUTINE',
'clinical_indication': 'Annual checkup'
},
{
'test_code': 'HBA1C',
'test_name': 'Hemoglobin A1C',
'priority': 'ROUTINE',
'clinical_indication': 'Diabetes monitoring'
}
],
'notes': 'Patient fasting for 12 hours'
}
lab_order_response = self.client.post(
'/api/v1/healthcare/laboratory-orders/',
data=json.dumps(lab_order_data),
content_type='application/json',
**self.tenant_auth
)
assert lab_order_response.status_code == status.HTTP_201_CREATED
lab_order = lab_order_response.json()
# Update lab results
results_data = {
'results': [
{
'test_code': 'CBC',
'result_value': 'Normal',
'reference_range': '4.5-5.5 x 10^12/L',
'units': 'x 10^12/L',
'status': 'NORMAL'
},
{
'test_code': 'HBA1C',
'result_value': '6.2',
'reference_range': '< 5.7%',
'units': '%',
'status': 'ABNORMAL',
'notes': 'Slightly elevated - monitor'
}
],
'interpreted_by': 'Dr. Lab Specialist',
'interpretation': 'HbA1c shows prediabetes range'
}
results_response = self.client.post(
f'/api/v1/healthcare/laboratory-orders/{lab_order["id"]}/results/',
data=json.dumps(results_data),
content_type='application/json',
**self.tenant_auth
)
assert results_response.status_code == status.HTTP_200_OK
def test_billing_and_insurance_integration(self):
"""Test billing and insurance claim processing."""
# Create patient with insurance
patient_with_insurance = self.patient_data.copy()
patient_with_insurance['insurance'] = {
'provider': 'Malaysia National Insurance',
'policy_number': 'MNI-123456789',
'coverage_details': 'Full coverage',
'expiry_date': '2024-12-31'
}
patient_response = self.client.post(
'/api/v1/healthcare/patients/',
data=json.dumps(patient_with_insurance),
content_type='application/json',
**self.tenant_auth
)
assert patient_response.status_code == status.HTTP_201_CREATED
patient_data = patient_response.json()
# Create consultation and generate bill
billing_data = {
'patient_id': patient_data['id'],
'services': [
{
'service_code': 'CONSULT_GP',
'description': 'General Practitioner Consultation',
'amount': 150.00,
'quantity': 1
},
{
'service_code': 'LAB_CBC',
'description': 'Complete Blood Count',
'amount': 50.00,
'quantity': 1
}
],
'insurance_claim': {
'provider': patient_data['insurance']['provider'],
'policy_number': patient_data['insurance']['policy_number'],
'pre_authorization_code': 'PA-2024-001'
}
}
billing_response = self.client.post(
'/api/v1/healthcare/billing/',
data=json.dumps(billing_data),
content_type='application/json',
**self.tenant_auth
)
assert billing_response.status_code == status.HTTP_201_CREATED
billing_data = billing_response.json()
# Verify insurance claim processing
assert 'insurance_coverage' in billing_data
assert 'patient_responsibility' in billing_data
assert 'claim_status' in billing_data
def test_healthcare_compliance_and_reporting(self):
"""Test healthcare compliance and reporting features."""
# Test PDPA compliance (Personal Data Protection Act)
compliance_response = self.client.get(
'/api/v1/healthcare/compliance/data-protection/',
**self.tenant_auth
)
assert compliance_response.status_code == status.HTTP_200_OK
compliance_data = compliance_response.json()
assert 'consent_records' in compliance_data
assert 'data_access_logs' in compliance_data
assert 'retention_policies' in compliance_data
# Test clinical reporting
clinical_report_response = self.client.get(
'/api/v1/healthcare/reports/clinical/',
data={
'period': 'monthly',
'year': 2024,
'month': 1
},
**self.tenant_auth
)
assert clinical_report_response.status_code == status.HTTP_200_OK
clinical_report = clinical_report_response.json()
assert 'patient_visits' in clinical_report
assert 'common_diagnoses' in clinical_report
assert 'prescription_trends' in clinical_report
# Test adverse event reporting
adverse_event_data = {
'patient_id': 'patient-001',
'event_type': 'MEDICATION_ERROR',
'description': 'Wrong dosage administered',
'severity': 'MINOR',
'date_occurred': datetime.now().isoformat(),
'reported_by': 'nurse-001',
'actions_taken': 'Corrected dosage, patient monitored'
}
adverse_response = self.client.post(
'/api/v1/healthcare/adverse-events/',
data=json.dumps(adverse_event_data),
content_type='application/json',
**self.tenant_auth
)
assert adverse_response.status_code == status.HTTP_201_CREATED
def test_telemedicine_integration(self):
"""Test telemedicine and virtual consultation features."""
# Create virtual appointment
virtual_appointment_data = {
'patient_id': 'patient-001',
'doctor_id': 'doctor-001',
'appointment_datetime': '2024-02-15T15:00:00+08:00',
'duration': 20,
'type': 'CONSULTATION',
'is_virtual': True,
'virtual_consultation': {
'platform': 'ZOOM',
'link': 'https://zoom.us/j/123456789',
'instructions': 'Join 5 minutes early, test audio/video',
'meeting_id': '123456789',
'password': 'health2024'
},
'reason': 'Follow-up consultation'
}
virtual_response = self.client.post(
'/api/v1/healthcare/appointments/',
data=json.dumps(virtual_appointment_data),
content_type='application/json',
**self.tenant_auth
)
assert virtual_response.status_code == status.HTTP_201_CREATED
virtual_appointment = virtual_response.json()
assert virtual_appointment['is_virtual'] is True
assert 'virtual_consultation' in virtual_appointment
# Test telemedicine session logging
session_log_data = {
'appointment_id': virtual_appointment['id'],
'start_time': '2024-02-15T15:00:00Z',
'end_time': '2024-02-15T15:18:00Z',
'duration_minutes': 18,
'connection_quality': 'GOOD',
'technical_issues': None,
'notes': 'Successful virtual consultation'
}
session_log_response = self.client.post(
'/api/v1/healthcare/telemedicine/session-logs/',
data=json.dumps(session_log_data),
content_type='application/json',
**self.tenant_auth
)
assert session_log_response.status_code == status.HTTP_201_CREATED
def test_emergency_management(self):
"""Test emergency case management and triage."""
# Create emergency appointment
emergency_data = {
'patient_id': 'patient-001',
'doctor_id': 'doctor-emergency',
'appointment_datetime': datetime.now().isoformat(),
'duration': 60,
'type': 'EMERGENCY',
'priority': 'URGENT',
'reason': 'Chest pain and shortness of breath',
'triage_level': 'YELLOW'
}
emergency_response = self.client.post(
'/api/v1/healthcare/appointments/',
data=json.dumps(emergency_data),
content_type='application/json',
**self.tenant_auth
)
assert emergency_response.status_code == status.HTTP_201_CREATED
emergency_appointment = emergency_response.json()
assert emergency_appointment['type'] == 'EMERGENCY'
assert emergency_appointment['priority'] == 'URGENT'
# Test emergency response protocol
protocol_response = self.client.get(
f'/api/v1/healthcare/emergency/protocols/{emergency_appointment["triage_level"]}/',
**self.tenant_auth
)
assert protocol_response.status_code == status.HTTP_200_OK
protocol_data = protocol_response.json()
assert 'response_time_target' in protocol_data
assert 'required_actions' in protocol_data
assert 'staffing_requirements' in protocol_data

View File

@@ -0,0 +1,579 @@
"""
Integration test for retail module operations.
This test MUST fail before implementation.
"""
import pytest
from django.test import TestCase
from django.urls import reverse
from rest_framework.test import APIClient
from rest_framework import status
import json
from datetime import datetime, timedelta
class RetailOperationsIntegrationTest(TestCase):
def setUp(self):
self.client = APIClient()
# Tenant authentication header
self.tenant_auth = {'HTTP_AUTHORIZATION': 'Bearer tenant_token'}
# Test product data
self.product_data = {
'sku': 'LPT-PRO-001',
'name': 'Professional Laptop 15"',
'description': 'High-performance laptop for business use',
'category': 'ELECTRONICS',
'price': 3499.99,
'cost': 2800.00,
'stock_quantity': 25,
'barcode': '1234567890123',
'brand': 'TechBrand',
'model': 'PRO-15-2024',
'tax_rate': 6.0
}
# Test customer data
self.customer_data = {
'name': 'John Customer',
'email': 'john.customer@example.com',
'phone': '+60123456789',
'address': {
'street': '123 Customer Street',
'city': 'Kuala Lumpur',
'state': 'Wilayah Persekutuan',
'postal_code': '50000',
'country': 'Malaysia'
}
}
def test_complete_retail_workflow(self):
"""Test complete retail workflow from product creation to sales reporting."""
# Step 1: Create product (should fail before implementation)
product_response = self.client.post(
'/api/v1/retail/products/',
data=json.dumps(self.product_data),
content_type='application/json',
**self.tenant_auth
)
assert product_response.status_code == status.HTTP_201_CREATED
product_data = product_response.json()
# Verify product structure
assert 'id' in product_data
assert product_data['sku'] == self.product_data['sku']
assert product_data['stock_quantity'] == self.product_data['stock_quantity']
assert product_data['status'] == 'ACTIVE'
# Step 2: Create additional products for inventory testing
additional_products = [
{
'sku': 'MOU-WRL-001',
'name': 'Wireless Mouse',
'category': 'ELECTRONICS',
'price': 89.99,
'cost': 45.00,
'stock_quantity': 50
},
{
'sku': 'KEY-MEC-001',
'name': 'Mechanical Keyboard',
'category': 'ELECTRONICS',
'price': 299.99,
'cost': 180.00,
'stock_quantity': 30
}
]
created_products = []
for prod_data in additional_products:
prod_response = self.client.post(
'/api/v1/retail/products/',
data=json.dumps(prod_data),
content_type='application/json',
**self.tenant_auth
)
assert prod_response.status_code == status.HTTP_201_CREATED
created_products.append(prod_response.json())
# Step 3: Process multiple sales transactions
sales_transactions = [
{
'customer': self.customer_data,
'items': [
{
'product_id': product_data['id'],
'sku': product_data['sku'],
'quantity': 2,
'unit_price': product_data['price']
},
{
'product_id': created_products[0]['id'],
'sku': created_products[0]['sku'],
'quantity': 1,
'unit_price': created_products[0]['price']
}
],
'payment': {
'method': 'CARD',
'amount_paid': 7290.00,
'reference_number': 'CARD-001'
}
},
{
'customer': {
'name': 'Jane Buyer',
'email': 'jane@example.com',
'phone': '+60198765432'
},
'items': [
{
'product_id': created_products[1]['id'],
'sku': created_products[1]['sku'],
'quantity': 3,
'unit_price': created_products[1]['price']
}
],
'payment': {
'method': 'CASH',
'amount_paid': 900.00,
'reference_number': 'CASH-001'
}
}
]
created_sales = []
for sale_data in sales_transactions:
sale_response = self.client.post(
'/api/v1/retail/sales/',
data=json.dumps(sale_data),
content_type='application/json',
**self.tenant_auth
)
assert sale_response.status_code == status.HTTP_201_CREATED
created_sales.append(sale_response.json())
# Step 4: Verify inventory updates after sales
inventory_check_response = self.client.get(
f'/api/v1/retail/products/{product_data["id"]}/',
**self.tenant_auth
)
assert inventory_check_response.status_code == status.HTTP_200_OK
updated_product = inventory_check_response.json()
# Stock should be reduced by sold quantity
expected_stock = self.product_data['stock_quantity'] - 2 # 2 laptops sold
assert updated_product['stock_quantity'] == expected_stock
# Step 5: Test sales reporting
sales_report_response = self.client.get(
'/api/v1/retail/reports/sales/',
data={
'start_date': (datetime.now() - timedelta(days=7)).isoformat(),
'end_date': datetime.now().isoformat()
},
**self.tenant_auth
)
assert sales_report_response.status_code == status.HTTP_200_OK
sales_report = sales_report_response.json()
assert 'total_sales' in sales_report
assert 'total_revenue' in sales_report
assert 'transactions_count' in sales_report
assert 'top_products' in sales_report
# Verify report data
assert sales_report['transactions_count'] == len(created_sales)
assert sales_report['total_revenue'] > 0
# Step 6: Test inventory reporting
inventory_report_response = self.client.get(
'/api/v1/retail/reports/inventory/',
**self.tenant_auth
)
assert inventory_report_response.status_code == status.HTTP_200_OK
inventory_report = inventory_report_response.json()
assert 'total_products' in inventory_report
assert 'low_stock_items' in inventory_report
assert 'total_value' in inventory_report
# Step 7: Test product search and filtering
search_response = self.client.get(
'/api/v1/retail/products/',
data={'search': 'laptop', 'category': 'ELECTRONICS'},
**self.tenant_auth
)
assert search_response.status_code == status.HTTP_200_OK
search_results = search_response.json()['products']
# Should find the laptop product
assert len(search_results) > 0
assert any(product['id'] == product_data['id'] for product in search_results)
def test_inventory_management_operations(self):
"""Test inventory management operations."""
# Create product first
product_response = self.client.post(
'/api/v1/retail/products/',
data=json.dumps(self.product_data),
content_type='application/json',
**self.tenant_auth
)
assert product_response.status_code == status.HTTP_201_CREATED
product_data = product_response.json()
# Step 1: Stock adjustment
adjustment_data = {
'type': 'ADDITION',
'quantity': 10,
'reason': 'New stock received',
'reference': 'PO-2024-001',
'unit_cost': 2750.00
}
adjustment_response = self.client.post(
f'/api/v1/retail/products/{product_data["id"]}/inventory/',
data=json.dumps(adjustment_data),
content_type='application/json',
**self.tenant_auth
)
assert adjustment_response.status_code == status.HTTP_200_OK
# Verify stock was updated
updated_product_response = self.client.get(
f'/api/v1/retail/products/{product_data["id"]}/',
**self.tenant_auth
)
assert updated_product_response.status_code == status.HTTP_200_OK
updated_product = updated_product_response.json()
expected_stock = self.product_data['stock_quantity'] + 10
assert updated_product['stock_quantity'] == expected_stock
# Step 2: Stock transfer
transfer_data = {
'quantity': 5,
'from_location': 'Warehouse A',
'to_location': 'Store Front',
'reason': 'Restocking store'
}
transfer_response = self.client.post(
f'/api/v1/retail/products/{product_data["id"]}/transfer/',
data=json.dumps(transfer_data),
content_type='application/json',
**self.tenant_auth
)
assert transfer_response.status_code == status.HTTP_200_OK
# Step 3: Low stock alerts
# Create product with low stock
low_stock_product = self.product_data.copy()
low_stock_product['sku'] = 'LOW-STOCK-001'
low_stock_product['stock_quantity'] = 2
low_stock_response = self.client.post(
'/api/v1/retail/products/',
data=json.dumps(low_stock_product),
content_type='application/json',
**self.tenant_auth
)
assert low_stock_response.status_code == status.HTTP_201_CREATED
# Check low stock report
low_stock_report_response = self.client.get(
'/api/v1/retail/reports/low-stock/',
**self.tenant_auth
)
assert low_stock_report_response.status_code == status.HTTP_200_OK
low_stock_report = low_stock_report_response.json()
assert 'low_stock_items' in low_stock_report
assert len(low_stock_report['low_stock_items']) > 0
def test_product_variant_management(self):
"""Test product variant management."""
# Create parent product with variants
parent_product = self.product_data.copy()
parent_product['variants'] = [
{
'sku': 'LPT-PRO-001-BLK',
'name': 'Professional Laptop 15" - Black',
'attributes': {'color': 'Black', 'storage': '512GB SSD'},
'price_adjustment': 0,
'stock_quantity': 10
},
{
'sku': 'LPT-PRO-001-SLV',
'name': 'Professional Laptop 15" - Silver',
'attributes': {'color': 'Silver', 'storage': '1TB SSD'},
'price_adjustment': 200,
'stock_quantity': 8
}
]
product_response = self.client.post(
'/api/v1/retail/products/',
data=json.dumps(parent_product),
content_type='application/json',
**self.tenant_auth
)
assert product_response.status_code == status.HTTP_201_CREATED
created_product = product_response.json()
# Verify variants were created
assert 'variants' in created_product
assert len(created_product['variants']) == 2
# Test variant operations
variant = created_product['variants'][0]
# Update variant stock
variant_stock_update = {
'stock_quantity': 15,
'reason': 'New stock received'
}
variant_update_response = self.client.put(
f'/api/v1/retail/products/{created_product["id"]}/variants/{variant["sku"]}/',
data=json.dumps(variant_stock_update),
content_type='application/json',
**self.tenant_auth
)
assert variant_update_response.status_code == status.HTTP_200_OK
def test_customer_management(self):
"""Test customer management operations."""
# Create customer
customer_response = self.client.post(
'/api/v1/retail/customers/',
data=json.dumps(self.customer_data),
content_type='application/json',
**self.tenant_auth
)
assert customer_response.status_code == status.HTTP_201_CREATED
customer_data = customer_response.json()
# Step 1: Customer purchase history
# Create a sale for this customer
sale_data = {
'customer_id': customer_data['id'],
'items': [
{
'product_id': 'product-001',
'sku': 'TEST-001',
'quantity': 1,
'unit_price': 99.99
}
],
'payment': {
'method': 'CASH',
'amount_paid': 99.99
}
}
sale_response = self.client.post(
'/api/v1/retail/sales/',
data=json.dumps(sale_data),
content_type='application/json',
**self.tenant_auth
)
assert sale_response.status_code == status.HTTP_201_CREATED
# Get customer purchase history
history_response = self.client.get(
f'/api/v1/retail/customers/{customer_data["id"]}/history/',
**self.tenant_auth
)
assert history_response.status_code == status.HTTP_200_OK
history_data = history_response.json()
assert 'purchases' in history_data
assert 'total_spent' in history_data
assert 'loyalty_points' in history_data
# Step 2: Customer loyalty program
loyalty_data = {
'points_earned': 100,
'notes': 'Purchase bonus'
}
loyalty_response = self.client.post(
f'/api/v1/retail/customers/{customer_data["id"]}/loyalty/',
data=json.dumps(loyalty_data),
content_type='application/json',
**self.tenant_auth
)
assert loyalty_response.status_code == status.HTTP_200_OK
def test_discount_and_promotion_management(self):
"""Test discount and promotion management."""
# Create promotion
promotion_data = {
'name': 'New Year Sale',
'type': 'PERCENTAGE',
'value': 20,
'start_date': (datetime.now() - timedelta(days=1)).isoformat(),
'end_date': (datetime.now() + timedelta(days=30)).isoformat(),
'applicable_products': ['product-001', 'product-002'],
'minimum_purchase': 100,
'usage_limit': 100
}
promotion_response = self.client.post(
'/api/v1/retail/promotions/',
data=json.dumps(promotion_data),
content_type='application/json',
**self.tenant_auth
)
assert promotion_response.status_code == status.HTTP_201_CREATED
created_promotion = promotion_response.json()
# Test promotion application in sale
sale_with_promotion = {
'customer': self.customer_data,
'items': [
{
'product_id': 'product-001',
'sku': 'TEST-001',
'quantity': 2,
'unit_price': 100.00
}
],
'promotion_code': created_promotion['code'],
'payment': {
'method': 'CARD',
'amount_paid': 160.00 # 20% discount on 200
}
}
sale_response = self.client.post(
'/api/v1/retail/sales/',
data=json.dumps(sale_with_promotion),
content_type='application/json',
**self.tenant_auth
)
assert sale_response.status_code == status.HTTP_201_CREATED
sale_data = sale_response.json()
# Verify discount was applied
assert 'discount_amount' in sale_data['totals']
assert sale_data['totals']['discount_amount'] == 40.00
def test_return_and_refund_operations(self):
"""Test return and refund operations."""
# Create a sale first
sale_data = {
'customer': self.customer_data,
'items': [
{
'product_id': 'product-001',
'sku': 'TEST-001',
'quantity': 2,
'unit_price': 100.00
}
],
'payment': {
'method': 'CARD',
'amount_paid': 200.00
}
}
sale_response = self.client.post(
'/api/v1/retail/sales/',
data=json.dumps(sale_data),
content_type='application/json',
**self.tenant_auth
)
assert sale_response.status_code == status.HTTP_201_CREATED
created_sale = sale_response.json()
# Process return
return_data = {
'sale_id': created_sale['id'],
'items': [
{
'product_id': 'product-001',
'quantity': 1,
'reason': 'Defective product',
'condition': 'DAMAGED'
}
],
'refund_method': 'ORIGINAL',
'notes': 'Customer reported defective item'
}
return_response = self.client.post(
'/api/v1/retail/returns/',
data=json.dumps(return_data),
content_type='application/json',
**self.tenant_auth
)
assert return_response.status_code == status.HTTP_201_CREATED
return_data = return_response.json()
# Verify inventory was updated (returned to stock)
# Verify refund was processed
assert 'refund_amount' in return_data
assert return_data['refund_amount'] == 100.00
def test_retail_analytics_and_reporting(self):
"""Test retail analytics and reporting."""
# Generate some test data first
# This would involve creating multiple products and sales
# Test sales analytics
analytics_response = self.client.get(
'/api/v1/retail/analytics/',
data={
'period': 'monthly',
'year': 2024,
'month': 1
},
**self.tenant_auth
)
assert analytics_response.status_code == status.HTTP_200_OK
analytics_data = analytics_response.json()
assert 'revenue' in analytics_data
assert 'profit' in analytics_data
assert 'top_products' in analytics_data
assert 'customer_metrics' in analytics_data
# Test product performance
performance_response = self.client.get(
'/api/v1/retail/reports/product-performance/',
**self.tenant_auth
)
assert performance_response.status_code == status.HTTP_200_OK
performance_data = performance_response.json()
assert 'products' in performance_data
assert 'best_sellers' in performance_data
assert 'low_performers' in performance_data

View File

@@ -0,0 +1,390 @@
"""
Integration test for subscription management.
This test MUST fail before implementation.
"""
import pytest
from django.test import TestCase
from django.urls import reverse
from rest_framework.test import APIClient
from rest_framework import status
import json
from datetime import datetime, timedelta
class SubscriptionManagementIntegrationTest(TestCase):
def setUp(self):
self.client = APIClient()
# Admin authentication header
self.admin_auth = {'HTTP_AUTHORIZATION': 'Bearer super_admin_token'}
# Tenant admin authentication header
self.tenant_admin_auth = {'HTTP_AUTHORIZATION': 'Bearer tenant_admin_token'}
# Test subscription data
self.subscription_data = {
'plan': 'GROWTH',
'pricing_model': 'SUBSCRIPTION',
'billing_cycle': 'MONTHLY',
'payment_method': {
'type': 'CARD',
'card_last4': '4242',
'expiry_month': 12,
'expiry_year': 2025,
'brand': 'visa'
},
'modules': ['retail', 'inventory'],
'trial_days': 14
}
def test_subscription_lifecycle_management(self):
"""Test complete subscription lifecycle from trial to cancellation."""
# Step 1: Create subscription with trial (should fail before implementation)
create_response = self.client.post(
'/api/v1/subscriptions/',
data=json.dumps(self.subscription_data),
content_type='application/json',
**self.tenant_admin_auth
)
assert create_response.status_code == status.HTTP_201_CREATED
subscription_data = create_response.json()
# Verify subscription structure
assert 'id' in subscription_data
assert subscription_data['plan'] == self.subscription_data['plan']
assert subscription_data['status'] == 'TRIAL'
assert subscription_data['billing_cycle'] == self.subscription_data['billing_cycle']
# Verify billing period
assert 'current_period_start' in subscription_data
assert 'current_period_end' in subscription_data
assert 'trial_end' in subscription_data
# Step 2: Test subscription upgrades during trial
upgrade_data = {
'plan': 'PRO',
'reason': 'Business growth requires more features'
}
upgrade_response = self.client.post(
f'/api/v1/subscriptions/{subscription_data["id"]}/upgrade/',
data=json.dumps(upgrade_data),
content_type='application/json',
**self.tenant_admin_auth
)
assert upgrade_response.status_code == status.HTTP_200_OK
upgraded_data = upgrade_response.json()
assert upgraded_data['plan'] == 'PRO'
assert upgraded_data['status'] == 'TRIAL' # Still in trial period
# Step 3: Simulate trial end and activation
# In real implementation, this would be handled by a background job
activate_response = self.client.post(
f'/api/v1/subscriptions/{subscription_data["id"]}/activate/',
data=json.dumps({}),
content_type='application/json',
**self.admin_auth
)
assert activate_response.status_code == status.HTTP_200_OK
activated_data = activate_response.json()
assert activated_data['status'] == 'ACTIVE'
assert activated_data['plan'] == 'PRO'
# Step 4: Test subscription usage tracking
usage_response = self.client.get(
f'/api/v1/subscriptions/{subscription_data["id"]}/usage/',
**self.tenant_admin_auth
)
assert usage_response.status_code == status.HTTP_200_OK
usage_data = usage_response.json()
assert 'usage' in usage_data
assert 'limits' in usage_data
assert 'users_count' in usage_data['usage']
assert 'storage_used' in usage_data['usage']
# Step 5: Test subscription downgrade
downgrade_data = {
'plan': 'GROWTH',
'effective_date': (datetime.now() + timedelta(days=30)).isoformat()
}
downgrade_response = self.client.post(
f'/api/v1/subscriptions/{subscription_data["id"]}/downgrade/',
data=json.dumps(downgrade_data),
content_type='application/json',
**self.tenant_admin_auth
)
assert downgrade_response.status_code == status.HTTP_200_OK
downgraded_data = downgrade_response.json()
assert downgraded_data['pending_plan'] == 'GROWTH'
assert downgraded_data['plan_change_effective_date'] == downgrade_data['effective_date']
# Step 6: Test subscription cancellation
cancel_data = {
'reason': 'Business closure',
'feedback': 'Closing down operations',
'immediate': False
}
cancel_response = self.client.post(
f'/api/v1/subscriptions/{subscription_data["id"]}/cancel/',
data=json.dumps(cancel_data),
content_type='application/json',
**self.tenant_admin_auth
)
assert cancel_response.status_code == status.HTTP_200_OK
cancelled_data = cancel_response.json()
assert cancelled_data['status'] == 'ACTIVE' # Still active until end of period
assert cancelled_data['cancel_at_period_end'] is True
def test_subscription_billing_and_payments(self):
"""Test subscription billing and payment processing."""
# Create subscription
create_response = self.client.post(
'/api/v1/subscriptions/',
data=json.dumps(self.subscription_data),
content_type='application/json',
**self.tenant_admin_auth
)
assert create_response.status_code == status.HTTP_201_CREATED
subscription_id = create_response.json()['id']
# Test billing history
billing_response = self.client.get(
f'/api/v1/subscriptions/{subscription_id}/billing/',
**self.tenant_admin_auth
)
assert billing_response.status_code == status.HTTP_200_OK
billing_data = billing_response.json()
assert 'invoices' in billing_data
assert 'payments' in billing_data
assert 'upcoming_invoice' in billing_data
# Test payment method management
payment_method_data = {
'type': 'CARD',
'card_number': '4242424242424242',
'expiry_month': 12,
'expiry_year': 2025,
'cvv': '123',
'cardholder_name': 'Test User'
}
add_payment_response = self.client.post(
f'/api/v1/subscriptions/{subscription_id}/payment-methods/',
data=json.dumps(payment_method_data),
content_type='application/json',
**self.tenant_admin_auth
)
assert add_payment_response.status_code == status.HTTP_201_CREATED
def test_subscription_plan_changes_validation(self):
"""Test validation of subscription plan changes."""
# Create subscription
create_response = self.client.post(
'/api/v1/subscriptions/',
data=json.dumps(self.subscription_data),
content_type='application/json',
**self.tenant_admin_auth
)
assert create_response.status_code == status.HTTP_201_CREATED
subscription_id = create_response.json()['id']
# Test invalid plan upgrade
invalid_upgrade_data = {
'plan': 'INVALID_PLAN'
}
invalid_upgrade_response = self.client.post(
f'/api/v1/subscriptions/{subscription_id}/upgrade/',
data=json.dumps(invalid_upgrade_data),
content_type='application/json',
**self.tenant_admin_auth
)
assert invalid_upgrade_response.status_code == status.HTTP_400_BAD_REQUEST
# Test downgrade to same plan
same_plan_data = {
'plan': self.subscription_data['plan']
}
same_plan_response = self.client.post(
f'/api/v1/subscriptions/{subscription_id}/downgrade/',
data=json.dumps(same_plan_data),
content_type='application/json',
**self.tenant_admin_auth
)
assert same_plan_response.status_code == status.HTTP_400_BAD_REQUEST
def test_subscription_module_management(self):
"""Test subscription module add-ons and management."""
# Create base subscription
base_subscription = self.subscription_data.copy()
base_subscription['modules'] = ['retail']
create_response = self.client.post(
'/api/v1/subscriptions/',
data=json.dumps(base_subscription),
content_type='application/json',
**self.tenant_admin_auth
)
assert create_response.status_code == status.HTTP_201_CREATED
subscription_id = create_response.json()['id']
# Add module
add_module_data = {
'module': 'inventory',
'pricing_model': 'PER_MODULE',
'billing_cycle': 'MONTHLY'
}
add_module_response = self.client.post(
f'/api/v1/subscriptions/{subscription_id}/modules/',
data=json.dumps(add_module_data),
content_type='application/json',
**self.tenant_admin_auth
)
assert add_module_response.status_code == status.HTTP_200_OK
# Remove module
remove_module_response = self.client.delete(
f'/api/v1/subscriptions/{subscription_id}/modules/inventory/',
**self.tenant_admin_auth
)
assert remove_module_response.status_code == status.HTTP_200_OK
def test_subscription_usage_limits(self):
"""Test subscription usage limits and overage handling."""
# Create subscription with specific limits
limited_subscription = self.subscription_data.copy()
limited_subscription['usage_limits'] = {
'users': 5,
'storage_gb': 10,
'api_calls_per_month': 10000
}
create_response = self.client.post(
'/api/v1/subscriptions/',
data=json.dumps(limited_subscription),
content_type='application/json',
**self.tenant_admin_auth
)
assert create_response.status_code == status.HTTP_201_CREATED
subscription_id = create_response.json()['id']
# Check usage limits
limits_response = self.client.get(
f'/api/v1/subscriptions/{subscription_id}/limits/',
**self.tenant_admin_auth
)
assert limits_response.status_code == status.HTTP_200_OK
limits_data = limits_response.json()
assert 'limits' in limits_data
assert 'current_usage' in limits_data
assert 'overage_charges' in limits_data
def test_subscription_discounts_and_promotions(self):
"""Test subscription discounts and promotional codes."""
# Create subscription with promo code
promo_subscription = self.subscription_data.copy()
promo_subscription['promo_code'] = 'WELCOME20'
create_response = self.client.post(
'/api/v1/subscriptions/',
data=json.dumps(promo_subscription),
content_type='application/json',
**self.tenant_admin_auth
)
assert create_response.status_code == status.HTTP_201_CREATED
subscription_data = create_response.json()
# Check discount was applied
assert 'discount' in subscription_data
assert subscription_data['promo_code'] == 'WELCOME20'
def test_subscription_notifications_and_reminders(self):
"""Test subscription notifications and renewal reminders."""
# Create subscription
create_response = self.client.post(
'/api/v1/subscriptions/',
data=json.dumps(self.subscription_data),
content_type='application/json',
**self.tenant_admin_auth
)
assert create_response.status_code == status.HTTP_201_CREATED
subscription_id = create_response.json()['id']
# Test notification settings
notification_settings = {
'email_notifications': True,
'renewal_reminders': True,
'usage_alerts': True,
'billing_notifications': True
}
settings_response = self.client.put(
f'/api/v1/subscriptions/{subscription_id}/notifications/',
data=json.dumps(notification_settings),
content_type='application/json',
**self.tenant_admin_auth
)
assert settings_response.status_code == status.HTTP_200_OK
def test_subscription_audit_trail(self):
"""Test subscription changes audit trail."""
# Create subscription
create_response = self.client.post(
'/api/v1/subscriptions/',
data=json.dumps(self.subscription_data),
content_type='application/json',
**self.tenant_admin_auth
)
assert create_response.status_code == status.HTTP_201_CREATED
subscription_id = create_response.json()['id']
# Get audit trail
audit_response = self.client.get(
f'/api/v1/subscriptions/{subscription_id}/audit/',
**self.admin_auth
)
assert audit_response.status_code == status.HTTP_200_OK
audit_data = audit_response.json()
assert 'changes' in audit_data
assert isinstance(audit_data['changes'], list)
assert len(audit_data['changes']) > 0
# First change should be subscription creation
first_change = audit_data['changes'][0]
assert first_change['action'] == 'CREATE'
assert first_change['user_id'] is not None

View File

@@ -0,0 +1,404 @@
"""
Integration test for multi-tenant data isolation.
This test MUST fail before implementation.
"""
import pytest
from django.test import TestCase
from django.urls import reverse
from rest_framework.test import APIClient
from rest_framework import status
import json
class TenantIsolationIntegrationTest(TestCase):
def setUp(self):
self.client = APIClient()
# Super admin authentication header
self.admin_auth = {'HTTP_AUTHORIZATION': 'Bearer super_admin_token'}
# Tenant 1 authentication header
self.tenant1_auth = {'HTTP_AUTHORIZATION': 'Bearer tenant1_admin_token'}
# Tenant 2 authentication header
self.tenant2_auth = {'HTTP_AUTHORIZATION': 'Bearer tenant2_admin_token'}
# Test data for different tenants
self.tenant1_user_data = {
'email': 'user1@tenant1.com',
'name': 'User One',
'role': 'MANAGER',
'department': 'Sales'
}
self.tenant2_user_data = {
'email': 'user1@tenant2.com',
'name': 'User One Duplicate',
'role': 'MANAGER',
'department': 'Marketing'
}
self.tenant1_product_data = {
'sku': 'PROD-001',
'name': 'Product A',
'category': 'ELECTRONICS',
'price': 999.99
}
self.tenant2_product_data = {
'sku': 'PROD-001', # Same SKU as tenant1
'name': 'Product A Different',
'category': 'ELECTRONICS',
'price': 899.99
}
def test_user_data_isolation(self):
"""Test that user data is properly isolated between tenants."""
# Step 1: Create users in different tenants with same email structure
tenant1_user_response = self.client.post(
'/api/v1/users/',
data=json.dumps(self.tenant1_user_data),
content_type='application/json',
**self.tenant1_auth
)
assert tenant1_user_response.status_code == status.HTTP_201_CREATED
tenant1_user = tenant1_user_response.json()
tenant2_user_response = self.client.post(
'/api/v1/users/',
data=json.dumps(self.tenant2_user_data),
content_type='application/json',
**self.tenant2_auth
)
assert tenant2_user_response.status_code == status.HTTP_201_CREATED
tenant2_user = tenant2_user_response.json()
# Step 2: Verify tenant isolation - each tenant should only see their own users
tenant1_users_response = self.client.get(
'/api/v1/users/',
**self.tenant1_auth
)
assert tenant1_users_response.status_code == status.HTTP_200_OK
tenant1_users = tenant1_users_response.json()['users']
# Should only see users from tenant1
assert len(tenant1_users) == 1
assert tenant1_users[0]['email'] == self.tenant1_user_data['email']
assert tenant1_users[0]['tenant_id'] == tenant1_user['tenant_id']
tenant2_users_response = self.client.get(
'/api/v1/users/',
**self.tenant2_auth
)
assert tenant2_users_response.status_code == status.HTTP_200_OK
tenant2_users = tenant2_users_response.json()['users']
# Should only see users from tenant2
assert len(tenant2_users) == 1
assert tenant2_users[0]['email'] == self.tenant2_user_data['email']
assert tenant2_users[0]['tenant_id'] == tenant2_user['tenant_id']
# Step 3: Super admin should see all users
admin_users_response = self.client.get(
'/api/v1/users/',
**self.admin_auth
)
assert admin_users_response.status_code == status.HTTP_200_OK
admin_users = admin_users_response.json()['users']
# Should see users from both tenants
assert len(admin_users) >= 2
user_emails = [user['email'] for user in admin_users]
assert self.tenant1_user_data['email'] in user_emails
assert self.tenant2_user_data['email'] in user_emails
def test_product_data_isolation(self):
"""Test that product data is properly isolated between tenants."""
# Step 1: Create products with same SKU in different tenants
tenant1_product_response = self.client.post(
'/api/v1/retail/products/',
data=json.dumps(self.tenant1_product_data),
content_type='application/json',
**self.tenant1_auth
)
assert tenant1_product_response.status_code == status.HTTP_201_CREATED
tenant1_product = tenant1_product_response.json()
tenant2_product_response = self.client.post(
'/api/v1/retail/products/',
data=json.dumps(self.tenant2_product_data),
content_type='application/json',
**self.tenant2_auth
)
assert tenant2_product_response.status_code == status.HTTP_201_CREATED
tenant2_product = tenant2_product_response.json()
# Step 2: Verify SKU isolation - same SKU allowed in different tenants
assert tenant1_product['sku'] == tenant2_product['sku']
assert tenant1_product['id'] != tenant2_product['id']
assert tenant1_product['tenant_id'] != tenant2_product['tenant_id']
# Step 3: Test product retrieval isolation
tenant1_products_response = self.client.get(
'/api/v1/retail/products/',
**self.tenant1_auth
)
assert tenant1_products_response.status_code == status.HTTP_200_OK
tenant1_products = tenant1_products_response.json()['products']
# Should only see products from tenant1
assert len(tenant1_products) == 1
assert tenant1_products[0]['name'] == self.tenant1_product_data['name']
assert tenant1_products[0]['tenant_id'] == tenant1_product['tenant_id']
tenant2_products_response = self.client.get(
'/api/v1/retail/products/',
**self.tenant2_auth
)
assert tenant2_products_response.status_code == status.HTTP_200_OK
tenant2_products = tenant2_products_response.json()['products']
# Should only see products from tenant2
assert len(tenant2_products) == 1
assert tenant2_products[0]['name'] == self.tenant2_product_data['name']
assert tenant2_products[0]['tenant_id'] == tenant2_product['tenant_id']
def test_healthcare_data_isolation(self):
"""Test that healthcare patient data is properly isolated."""
# Patient data for different tenants
tenant1_patient_data = {
'ic_number': '900101-10-1234',
'name': 'Ahmad bin Hassan',
'gender': 'MALE',
'date_of_birth': '1990-01-01'
}
tenant2_patient_data = {
'ic_number': '900101-10-1234', # Same IC number
'name': 'Ahmad bin Ali', # Different name
'gender': 'MALE',
'date_of_birth': '1990-01-01'
}
# Create patients in different tenants
tenant1_patient_response = self.client.post(
'/api/v1/healthcare/patients/',
data=json.dumps(tenant1_patient_data),
content_type='application/json',
**self.tenant1_auth
)
assert tenant1_patient_response.status_code == status.HTTP_201_CREATED
tenant1_patient = tenant1_patient_response.json()
tenant2_patient_response = self.client.post(
'/api/v1/healthcare/patients/',
data=json.dumps(tenant2_patient_data),
content_type='application/json',
**self.tenant2_auth
)
assert tenant2_patient_response.status_code == status.HTTP_201_CREATED
tenant2_patient = tenant2_patient_response.json()
# Verify same IC number allowed in different tenants (healthcare compliance)
assert tenant1_patient['ic_number'] == tenant2_patient['ic_number']
assert tenant1_patient['id'] != tenant2_patient['id']
# Test patient data isolation
tenant1_patients_response = self.client.get(
'/api/v1/healthcare/patients/',
**self.tenant1_auth
)
assert tenant1_patients_response.status_code == status.HTTP_200_OK
tenant1_patients = tenant1_patients_response.json()['patients']
# Should only see patients from tenant1
assert len(tenant1_patients) == 1
assert tenant1_patients[0]['name'] == tenant1_patient_data['name']
def test_cross_tenant_access_prevention(self):
"""Test that cross-tenant access is properly prevented."""
# Step 1: Create a user in tenant1
tenant1_user_response = self.client.post(
'/api/v1/users/',
data=json.dumps(self.tenant1_user_data),
content_type='application/json',
**self.tenant1_auth
)
assert tenant1_user_response.status_code == status.HTTP_201_CREATED
created_user = tenant1_user_response.json()
user_id = created_user['id']
# Step 2: Try to access tenant1 user from tenant2 (should fail)
tenant2_access_response = self.client.get(
f'/api/v1/users/{user_id}/',
**self.tenant2_auth
)
assert tenant2_access_response.status_code == status.HTTP_404_NOT_FOUND
# Step 3: Try to modify tenant1 user from tenant2 (should fail)
modify_data = {'name': 'Hacked Name'}
tenant2_modify_response = self.client.put(
f'/api/v1/users/{user_id}/',
data=json.dumps(modify_data),
content_type='application/json',
**self.tenant2_auth
)
assert tenant2_modify_response.status_code == status.HTTP_404_NOT_FOUND
# Step 4: Verify user data is unchanged
verify_response = self.client.get(
f'/api/v1/users/{user_id}/',
**self.tenant1_auth
)
assert verify_response.status_code == status.HTTP_200_OK
verified_user = verify_response.json()
assert verified_user['name'] == self.tenant1_user_data['name']
def test_database_row_level_security(self):
"""Test that database row-level security is working."""
# This test verifies that data isolation is enforced at the database level
# Create test data in both tenants
self.client.post(
'/api/v1/users/',
data=json.dumps(self.tenant1_user_data),
content_type='application/json',
**self.tenant1_auth
)
self.client.post(
'/api/v1/users/',
data=json.dumps(self.tenant2_user_data),
content_type='application/json',
**self.tenant2_auth
)
# Test direct database queries would be isolated
# This is more of an integration test that would require actual database setup
pass
def test_file_storage_isolation(self):
"""Test that file storage is properly isolated between tenants."""
# Upload files for different tenants
# This would test file storage isolation mechanisms
pass
def test_cache_isolation(self):
"""Test that cache keys are properly isolated between tenants."""
# Test that cache keys include tenant information
# This ensures cache data doesn't leak between tenants
pass
def test_tenant_context_propagation(self):
"""Test that tenant context is properly propagated through the system."""
# Create a user and verify tenant context is maintained across operations
user_response = self.client.post(
'/api/v1/users/',
data=json.dumps(self.tenant1_user_data),
content_type='application/json',
**self.tenant1_auth
)
assert user_response.status_code == status.HTTP_201_CREATED
created_user = user_response.json()
# Verify tenant ID is consistently set
assert 'tenant_id' in created_user
tenant_id = created_user['tenant_id']
# Create a product and verify same tenant context
product_response = self.client.post(
'/api/v1/retail/products/',
data=json.dumps(self.tenant1_product_data),
content_type='application/json',
**self.tenant1_auth
)
assert product_response.status_code == status.HTTP_201_CREATED
created_product = product_response.json()
assert created_product['tenant_id'] == tenant_id
def test_tenant_configuration_isolation(self):
"""Test that tenant configurations are properly isolated."""
# Set tenant-specific configurations
tenant1_config = {
'timezone': 'Asia/Kuala_Lumpur',
'currency': 'MYR',
'date_format': 'DD/MM/YYYY'
}
tenant2_config = {
'timezone': 'Asia/Singapore',
'currency': 'SGD',
'date_format': 'MM/DD/YYYY'
}
# Apply configurations (would need actual config endpoints)
# Verify configurations don't interfere
pass
def test_tenant_performance_isolation(self):
"""Test that one tenant's performance doesn't affect others."""
# This would test resource limits and performance isolation
pass
def test_audit_log_tenant_isolation(self):
"""Test that audit logs are properly isolated by tenant."""
# Perform actions in different tenants
self.client.post(
'/api/v1/users/',
data=json.dumps(self.tenant1_user_data),
content_type='application/json',
**self.tenant1_auth
)
self.client.post(
'/api/v1/users/',
data=json.dumps(self.tenant2_user_data),
content_type='application/json',
**self.tenant2_auth
)
# Check that each tenant only sees their own audit logs
tenant1_audit_response = self.client.get(
'/api/v1/audit/logs/',
**self.tenant1_auth
)
assert tenant1_audit_response.status_code == status.HTTP_200_OK
tenant1_logs = tenant1_audit_response.json()['logs']
# Should only see logs from tenant1 operations
for log in tenant1_logs:
assert log['tenant_id'] is not None
# Super admin should see all logs
admin_audit_response = self.client.get(
'/api/v1/audit/logs/',
**self.admin_auth
)
assert admin_audit_response.status_code == status.HTTP_200_OK
admin_logs = admin_audit_response.json()['logs']
# Should see logs from both tenants
assert len(admin_logs) >= len(tenant1_logs)

View File

@@ -0,0 +1,322 @@
"""
Integration test for tenant registration flow.
This test MUST fail before implementation.
"""
import pytest
from django.test import TestCase
from django.urls import reverse
from rest_framework.test import APIClient
from rest_framework import status
import json
class TenantRegistrationIntegrationTest(TestCase):
def setUp(self):
self.client = APIClient()
# Super admin authentication header
self.admin_auth = {'HTTP_AUTHORIZATION': 'Bearer super_admin_token'}
# Test tenant data
self.tenant_data = {
'name': 'Test Healthcare Sdn Bhd',
'email': 'info@testhealthcare.com',
'phone': '+60312345678',
'address': {
'street': '123 Medical Street',
'city': 'Kuala Lumpur',
'state': 'Wilayah Persekutuan',
'postal_code': '50400',
'country': 'Malaysia'
},
'business_type': 'HEALTHCARE',
'subscription_plan': 'GROWTH',
'pricing_model': 'SUBSCRIPTION',
'admin_user': {
'name': 'Dr. Sarah Johnson',
'email': 'sarah.johnson@testhealthcare.com',
'password': 'SecurePassword123!',
'role': 'TENANT_ADMIN',
'phone': '+60123456789'
}
}
def test_complete_tenant_registration_flow(self):
"""Test complete tenant registration from creation to admin setup."""
# Step 1: Create tenant (should fail before implementation)
tenant_response = self.client.post(
'/api/v1/tenants/',
data=json.dumps(self.tenant_data),
content_type='application/json',
**self.admin_auth
)
assert tenant_response.status_code == status.HTTP_201_CREATED
tenant_data = tenant_response.json()
# Verify tenant structure
assert 'id' in tenant_data
assert tenant_data['name'] == self.tenant_data['name']
assert tenant_data['email'] == self.tenant_data['email']
assert tenant_data['business_type'] == self.tenant_data['business_type']
assert tenant_data['status'] == 'PENDING'
# Step 2: Verify tenant admin user was created
# First, authenticate as super admin to get user list
users_response = self.client.get(
'/api/v1/users/',
**self.admin_auth
)
assert users_response.status_code == status.HTTP_200_OK
users_data = users_response.json()
# Find the newly created admin user
admin_user = None
for user in users_data['users']:
if user['email'] == self.tenant_data['admin_user']['email']:
admin_user = user
break
assert admin_user is not None
assert admin_user['name'] == self.tenant_data['admin_user']['name']
assert admin_user['role'] == 'TENANT_ADMIN'
assert admin_user['tenant_id'] == tenant_data['id']
# Step 3: Verify subscription was created for tenant
subscription_response = self.client.get(
'/api/v1/subscriptions/',
data={'tenant_id': tenant_data['id']},
**self.admin_auth
)
assert subscription_response.status_code == status.HTTP_200_OK
subscriptions_data = subscription_response.json()
assert len(subscriptions_data['subscriptions']) == 1
subscription = subscriptions_data['subscriptions'][0]
assert subscription['tenant_id'] == tenant_data['id']
assert subscription['plan'] == self.tenant_data['subscription_plan']
assert subscription['status'] == 'TRIAL'
# Step 4: Test tenant admin authentication
# Login as tenant admin
login_data = {
'email': self.tenant_data['admin_user']['email'],
'password': self.tenant_data['admin_user']['password']
}
auth_response = self.client.post(
'/api/v1/auth/login/',
data=json.dumps(login_data),
content_type='application/json'
)
assert auth_response.status_code == status.HTTP_200_OK
auth_data = auth_response.json()
assert 'access_token' in auth_data
assert 'refresh_token' in auth_data
assert 'user' in auth_data
# Verify user info in token
user_info = auth_data['user']
assert user_info['email'] == self.tenant_data['admin_user']['email']
assert user_info['tenant_id'] == tenant_data['id']
# Step 5: Test tenant admin can access their tenant data
tenant_admin_auth = {'HTTP_AUTHORIZATION': f'Bearer {auth_data["access_token"]}'}
tenant_own_response = self.client.get(
'/api/v1/tenants/',
**tenant_admin_auth
)
assert tenant_own_response.status_code == status.HTTP_200_OK
tenant_own_data = tenant_own_response.json()
# Should only see their own tenant
assert len(tenant_own_data['tenants']) == 1
assert tenant_own_data['tenants'][0]['id'] == tenant_data['id']
# Step 6: Test tenant isolation - cannot see other tenants
# Create another tenant as super admin
other_tenant_data = self.tenant_data.copy()
other_tenant_data['name'] = 'Other Healthcare Sdn Bhd'
other_tenant_data['email'] = 'info@otherhealthcare.com'
other_tenant_data['admin_user']['email'] = 'admin@otherhealthcare.com'
other_tenant_response = self.client.post(
'/api/v1/tenants/',
data=json.dumps(other_tenant_data),
content_type='application/json',
**self.admin_auth
)
assert other_tenant_response.status_code == status.HTTP_201_CREATED
# First tenant admin should still only see their own tenant
tenant_still_own_response = self.client.get(
'/api/v1/tenants/',
**tenant_admin_auth
)
assert tenant_still_own_response.status_code == status.HTTP_200_OK
tenant_still_own_data = tenant_still_own_response.json()
# Should still only see their own tenant
assert len(tenant_still_own_data['tenants']) == 1
assert tenant_still_own_data['tenants'][0]['id'] == tenant_data['id']
def test_tenant_registration_invalid_business_type(self):
"""Test tenant registration with invalid business type."""
invalid_data = self.tenant_data.copy()
invalid_data['business_type'] = 'INVALID_TYPE'
response = self.client.post(
'/api/v1/tenants/',
data=json.dumps(invalid_data),
content_type='application/json',
**self.admin_auth
)
assert response.status_code == status.HTTP_400_BAD_REQUEST
def test_tenant_registration_missing_admin_user(self):
"""Test tenant registration without admin user data."""
invalid_data = self.tenant_data.copy()
del invalid_data['admin_user']
response = self.client.post(
'/api/v1/tenants/',
data=json.dumps(invalid_data),
content_type='application/json',
**self.admin_auth
)
assert response.status_code == status.HTTP_400_BAD_REQUEST
def test_tenant_registration_duplicate_email(self):
"""Test tenant registration with duplicate email."""
# Create first tenant
first_response = self.client.post(
'/api/v1/tenants/',
data=json.dumps(self.tenant_data),
content_type='application/json',
**self.admin_auth
)
assert first_response.status_code == status.HTTP_201_CREATED
# Try to create second tenant with same email
second_response = self.client.post(
'/api/v1/tenants/',
data=json.dumps(self.tenant_data),
content_type='application/json',
**self.admin_auth
)
assert second_response.status_code == status.HTTP_400_BAD_REQUEST
def test_tenant_registration_unauthorized(self):
"""Test tenant registration without admin authentication."""
response = self.client.post(
'/api/v1/tenants/',
data=json.dumps(self.tenant_data),
content_type='application/json'
)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
def test_tenant_registration_weak_admin_password(self):
"""Test tenant registration with weak admin password."""
invalid_data = self.tenant_data.copy()
invalid_data['admin_user']['password'] = '123'
response = self.client.post(
'/api/v1/tenants/',
data=json.dumps(invalid_data),
content_type='application/json',
**self.admin_auth
)
assert response.status_code == status.HTTP_400_BAD_REQUEST
def test_tenant_registration_with_modules_configuration(self):
"""Test tenant registration with specific modules configuration."""
modules_data = self.tenant_data.copy()
modules_data['modules'] = ['healthcare', 'appointments', 'billing']
modules_data['modules_config'] = {
'healthcare': {
'features': ['patient_management', 'appointment_scheduling', 'medical_records'],
'settings': {
'enable_telemedicine': True,
'appointment_reminders': True
}
}
}
response = self.client.post(
'/api/v1/tenants/',
data=json.dumps(modules_data),
content_type='application/json',
**self.admin_auth
)
if response.status_code == status.HTTP_201_CREATED:
tenant_data = response.json()
# Should have modules configuration
assert 'modules' in tenant_data
assert 'modules_config' in tenant_data
def test_tenant_registration_with_branding(self):
"""Test tenant registration with branding information."""
branding_data = self.tenant_data.copy()
branding_data['branding'] = {
'logo_url': 'https://example.com/logo.png',
'primary_color': '#2563eb',
'secondary_color': '#64748b',
'company_website': 'https://testhealthcare.com',
'social_media': {
'facebook': 'testhealthcare',
'instagram': 'testhealthcare_my'
}
}
response = self.client.post(
'/api/v1/tenants/',
data=json.dumps(branding_data),
content_type='application/json',
**self.admin_auth
)
if response.status_code == status.HTTP_201_CREATED:
tenant_data = response.json()
# Should have branding information
assert 'branding' in tenant_data
assert tenant_data['branding']['primary_color'] == '#2563eb'
def test_tenant_registration_domain_setup(self):
"""Test tenant registration with custom domain setup."""
domain_data = self.tenant_data.copy()
domain_data['domain'] = 'portal.testhealthcare.com'
domain_data['settings'] = {
'custom_domain_enabled': True,
'ssl_enabled': True,
'email_domain': 'testhealthcare.com'
}
response = self.client.post(
'/api/v1/tenants/',
data=json.dumps(domain_data),
content_type='application/json',
**self.admin_auth
)
if response.status_code == status.HTTP_201_CREATED:
tenant_data = response.json()
# Should have domain configuration
assert 'domain' in tenant_data
assert 'settings' in tenant_data
assert tenant_data['domain'] == 'portal.testhealthcare.com'

View File

@@ -0,0 +1,391 @@
"""
Integration test for user authentication flow.
This test MUST fail before implementation.
"""
import pytest
from django.test import TestCase
from django.urls import reverse
from rest_framework.test import APIClient
from rest_framework import status
import json
import time
class UserAuthenticationIntegrationTest(TestCase):
def setUp(self):
self.client = APIClient()
# Test user credentials
self.test_user = {
'email': 'test.user@example.com',
'password': 'SecurePassword123!',
'name': 'Test User',
'role': 'TENANT_ADMIN'
}
def test_complete_authentication_flow(self):
"""Test complete authentication flow from login to logout."""
# Step 1: User login (should fail before implementation)
login_response = self.client.post(
'/api/v1/auth/login/',
data=json.dumps({
'email': self.test_user['email'],
'password': self.test_user['password']
}),
content_type='application/json'
)
assert login_response.status_code == status.HTTP_200_OK
login_data = login_response.json()
# Verify token structure
assert 'access_token' in login_data
assert 'refresh_token' in login_data
assert 'user' in login_data
assert 'expires_in' in login_data
access_token = login_data['access_token']
refresh_token = login_data['refresh_token']
user_info = login_data['user']
# Verify user information
assert user_info['email'] == self.test_user['email']
assert user_info['name'] == self.test_user['name']
assert user_info['role'] == self.test_user['role']
assert 'tenant_id' in user_info
# Step 2: Use access token for authenticated requests
auth_header = {'HTTP_AUTHORIZATION': f'Bearer {access_token}'}
# Test accessing protected resource
protected_response = self.client.get(
'/api/v1/users/',
**auth_header
)
assert protected_response.status_code == status.HTTP_200_OK
# Step 3: Test token refresh
refresh_response = self.client.post(
'/api/v1/auth/refresh/',
data=json.dumps({
'refresh_token': refresh_token
}),
content_type='application/json'
)
assert refresh_response.status_code == status.HTTP_200_OK
refresh_data = refresh_response.json()
# Verify new tokens
assert 'access_token' in refresh_data
assert 'refresh_token' in refresh_data
# New access token should be different (rotation)
new_access_token = refresh_data['access_token']
assert new_access_token != access_token
# New refresh token should also be different (rotation)
new_refresh_token = refresh_data['refresh_token']
assert new_refresh_token != refresh_token
# Step 4: Test new access token works
new_auth_header = {'HTTP_AUTHORIZATION': f'Bearer {new_access_token}'}
new_protected_response = self.client.get(
'/api/v1/users/',
**new_auth_header
)
assert new_protected_response.status_code == status.HTTP_200_OK
# Step 5: Test old refresh token is invalidated
old_refresh_response = self.client.post(
'/api/v1/auth/refresh/',
data=json.dumps({
'refresh_token': refresh_token # Old token
}),
content_type='application/json'
)
assert old_refresh_response.status_code == status.HTTP_401_UNAUTHORIZED
# Step 6: Test logout/blacklist tokens
logout_response = self.client.post(
'/api/v1/auth/logout/',
**new_auth_header
)
assert logout_response.status_code == status.HTTP_200_OK
logout_data = logout_response.json()
assert 'message' in logout_data
assert logout_data['message'] == 'Successfully logged out'
# Step 7: Test token is blacklisted (cannot be used after logout)
blacklisted_response = self.client.get(
'/api/v1/users/',
**new_auth_header
)
assert blacklisted_response.status_code == status.HTTP_401_UNAUTHORIZED
def test_multi_factor_authentication_flow(self):
"""Test multi-factor authentication flow."""
# Step 1: Initial login with MFA enabled user
mfa_login_response = self.client.post(
'/api/v1/auth/login/',
data=json.dumps({
'email': 'mfa.user@example.com',
'password': 'SecurePassword123!'
}),
content_type='application/json'
)
# Should return MFA challenge instead of full token
assert mfa_login_response.status_code == status.HTTP_200_OK
mfa_data = mfa_login_response.json()
assert 'mfa_required' in mfa_data
assert mfa_data['mfa_required'] is True
assert 'mfa_methods' in mfa_data
assert 'temp_token' in mfa_data
# Step 2: Complete MFA with TOTP
mfa_verify_response = self.client.post(
'/api/v1/auth/mfa/verify/',
data=json.dumps({
'temp_token': mfa_data['temp_token'],
'method': 'TOTP',
'code': '123456' # Mock TOTP code
}),
content_type='application/json'
)
assert mfa_verify_response.status_code == status.HTTP_200_OK
mfa_verify_data = mfa_verify_response.json()
assert 'access_token' in mfa_verify_data
assert 'refresh_token' in mfa_verify_data
def test_authentication_error_scenarios(self):
"""Test various authentication error scenarios."""
# Test invalid credentials
invalid_credentials_response = self.client.post(
'/api/v1/auth/login/',
data=json.dumps({
'email': self.test_user['email'],
'password': 'wrongpassword'
}),
content_type='application/json'
)
assert invalid_credentials_response.status_code == status.HTTP_401_UNAUTHORIZED
# Test missing credentials
missing_credentials_response = self.client.post(
'/api/v1/auth/login/',
data=json.dumps({
'email': self.test_user['email']
# Missing password
}),
content_type='application/json'
)
assert missing_credentials_response.status_code == status.HTTP_400_BAD_REQUEST
# Test invalid refresh token
invalid_refresh_response = self.client.post(
'/api/v1/auth/refresh/',
data=json.dumps({
'refresh_token': 'invalid_refresh_token'
}),
content_type='application/json'
)
assert invalid_refresh_response.status_code == status.HTTP_401_UNAUTHORIZED
# Test missing refresh token
missing_refresh_response = self.client.post(
'/api/v1/auth/refresh/',
data=json.dumps({}),
content_type='application/json'
)
assert missing_refresh_response.status_code == status.HTTP_400_BAD_REQUEST
def test_token_expiry_handling(self):
"""Test handling of expired tokens."""
# This test would need to simulate token expiration
# For now, we'll test the structure
pass
def test_concurrent_session_management(self):
"""Test concurrent session management."""
# Login first device
device1_response = self.client.post(
'/api/v1/auth/login/',
data=json.dumps({
'email': self.test_user['email'],
'password': self.test_user['password']
}),
content_type='application/json'
)
assert device1_response.status_code == status.HTTP_200_OK
device1_token = device1_response.json()['access_token']
# Login second device
device2_response = self.client.post(
'/api/v1/auth/login/',
data=json.dumps({
'email': self.test_user['email'],
'password': self.test_user['password']
}),
content_type='application/json'
)
assert device2_response.status_code == status.HTTP_200_OK
device2_token = device2_response.json()['access_token']
# Both tokens should work (assuming concurrent sessions are allowed)
device1_auth = {'HTTP_AUTHORIZATION': f'Bearer {device1_token}'}
device2_auth = {'HTTP_AUTHORIZATION': f'Bearer {device2_token}'}
device1_protected = self.client.get('/api/v1/users/', **device1_auth)
device2_protected = self.client.get('/api/v1/users/', **device2_auth)
assert device1_protected.status_code == status.HTTP_200_OK
assert device2_protected.status_code == status.HTTP_200_OK
def test_permission_based_access_control(self):
"""Test permission-based access control."""
# Login as regular user
user_login_response = self.client.post(
'/api/v1/auth/login/',
data=json.dumps({
'email': 'regular.user@example.com',
'password': 'SecurePassword123!'
}),
content_type='application/json'
)
assert user_login_response.status_code == status.HTTP_200_OK
user_token = user_login_response.json()['access_token']
user_auth = {'HTTP_AUTHORIZATION': f'Bearer {user_token}'}
# Regular user should not be able to access admin-only endpoints
admin_endpoint_response = self.client.get('/api/v1/tenants/', **user_auth)
assert admin_endpoint_response.status_code == status.HTTP_403_FORBIDDEN
# But should be able to access user endpoints
user_endpoint_response = self.client.get('/api/v1/users/', **user_auth)
assert user_endpoint_response.status_code == status.HTTP_200_OK
def test_tenant_isolation_in_authentication(self):
"""Test that authentication tokens include tenant isolation."""
# Login as tenant admin
tenant_admin_response = self.client.post(
'/api/v1/auth/login/',
data=json.dumps({
'email': 'tenant.admin@tenant1.com',
'password': 'SecurePassword123!'
}),
content_type='application/json'
)
assert tenant_admin_response.status_code == status.HTTP_200_OK
tenant_admin_data = tenant_admin_response.json()
# Token should include tenant information
assert 'tenant_id' in tenant_admin_data['user']
tenant1_id = tenant_admin_data['user']['tenant_id']
# Login as different tenant admin
tenant2_admin_response = self.client.post(
'/api/v1/auth/login/',
data=json.dumps({
'email': 'tenant.admin@tenant2.com',
'password': 'SecurePassword123!'
}),
content_type='application/json'
)
assert tenant2_admin_response.status_code == status.HTTP_200_OK
tenant2_admin_data = tenant2_admin_response.json()
# Should have different tenant ID
assert 'tenant_id' in tenant2_admin_data['user']
tenant2_id = tenant2_admin_data['user']['tenant_id']
assert tenant1_id != tenant2_id
def test_authentication_rate_limiting(self):
"""Test authentication rate limiting."""
# Test multiple failed login attempts
for i in range(5):
failed_response = self.client.post(
'/api/v1/auth/login/',
data=json.dumps({
'email': self.test_user['email'],
'password': 'wrongpassword'
}),
content_type='application/json'
)
# Should still allow attempts but may implement rate limiting
assert failed_response.status_code in [status.HTTP_401_UNAUTHORIZED, status.HTTP_429_TOO_MANY_REQUESTS]
def test_password_change_flow(self):
"""Test password change flow with authentication."""
# Login first
login_response = self.client.post(
'/api/v1/auth/login/',
data=json.dumps({
'email': self.test_user['email'],
'password': self.test_user['password']
}),
content_type='application/json'
)
assert login_response.status_code == status.HTTP_200_OK
access_token = login_response.json()['access_token']
auth_header = {'HTTP_AUTHORIZATION': f'Bearer {access_token}'}
# Change password
password_change_response = self.client.post(
'/api/v1/auth/change-password/',
data=json.dumps({
'current_password': self.test_user['password'],
'new_password': 'NewSecurePassword456!'
}),
content_type='application/json',
**auth_header
)
assert password_change_response.status_code == status.HTTP_200_OK
# Test login with new password
new_login_response = self.client.post(
'/api/v1/auth/login/',
data=json.dumps({
'email': self.test_user['email'],
'password': 'NewSecurePassword456!'
}),
content_type='application/json'
)
assert new_login_response.status_code == status.HTTP_200_OK
# Test old password no longer works
old_login_response = self.client.post(
'/api/v1/auth/login/',
data=json.dumps({
'email': self.test_user['email'],
'password': self.test_user['password']
}),
content_type='application/json'
)
assert old_login_response.status_code == status.HTTP_401_UNAUTHORIZED