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
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:
388
backend/tests/contract/test_retail_sales_post.py
Normal file
388
backend/tests/contract/test_retail_sales_post.py
Normal file
@@ -0,0 +1,388 @@
|
||||
"""
|
||||
Contract test for POST /retail/sales endpoint.
|
||||
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 RetailSalesPostContractTest(TestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.sales_url = '/api/v1/retail/sales/'
|
||||
|
||||
# Tenant authentication header
|
||||
self.tenant_auth = {'HTTP_AUTHORIZATION': 'Bearer tenant_token'}
|
||||
|
||||
# Valid sale data
|
||||
self.sale_data = {
|
||||
'customer': {
|
||||
'name': 'John Doe',
|
||||
'email': 'john.doe@example.com',
|
||||
'phone': '+60123456789',
|
||||
'address': {
|
||||
'street': '123 Customer Street',
|
||||
'city': 'Kuala Lumpur',
|
||||
'state': 'Wilayah Persekutuan',
|
||||
'postal_code': '50000',
|
||||
'country': 'Malaysia'
|
||||
}
|
||||
},
|
||||
'items': [
|
||||
{
|
||||
'product_id': 'product-001',
|
||||
'sku': 'LPT-001',
|
||||
'quantity': 2,
|
||||
'unit_price': 2499.99,
|
||||
'discount_percentage': 5.0,
|
||||
'tax_rate': 6.0
|
||||
},
|
||||
{
|
||||
'product_id': 'product-002',
|
||||
'sku': 'MOU-001',
|
||||
'quantity': 1,
|
||||
'unit_price': 99.99,
|
||||
'discount_percentage': 0.0,
|
||||
'tax_rate': 6.0
|
||||
}
|
||||
],
|
||||
'payment': {
|
||||
'method': 'CASH',
|
||||
'amount_paid': 5300.00,
|
||||
'reference_number': 'CASH-001'
|
||||
},
|
||||
'discount': {
|
||||
'type': 'percentage',
|
||||
'value': 2.0,
|
||||
'reason': 'Loyalty discount'
|
||||
},
|
||||
'notes': 'Customer requested expedited delivery',
|
||||
'sales_channel': 'IN_STORE',
|
||||
'staff_id': 'staff-001'
|
||||
}
|
||||
|
||||
def test_create_sale_success(self):
|
||||
"""Test successful sale creation."""
|
||||
response = self.client.post(
|
||||
self.sales_url,
|
||||
data=json.dumps(self.sale_data),
|
||||
content_type='application/json',
|
||||
**self.tenant_auth
|
||||
)
|
||||
|
||||
# This should fail before implementation
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
|
||||
data = response.json()
|
||||
assert 'id' in data
|
||||
assert data['status'] == 'COMPLETED'
|
||||
assert 'customer' in data
|
||||
assert 'items' in data
|
||||
assert 'payment' in data
|
||||
assert 'totals' in data
|
||||
|
||||
# Check customer information
|
||||
customer = data['customer']
|
||||
assert customer['name'] == self.sale_data['customer']['name']
|
||||
assert customer['email'] == self.sale_data['customer']['email']
|
||||
|
||||
# Check items
|
||||
items = data['items']
|
||||
assert len(items) == 2
|
||||
assert items[0]['product_id'] == self.sale_data['items'][0]['product_id']
|
||||
assert items[0]['quantity'] == self.sale_data['items'][0]['quantity']
|
||||
|
||||
# Check payment
|
||||
payment = data['payment']
|
||||
assert payment['method'] == self.sale_data['payment']['method']
|
||||
assert payment['amount_paid'] == self.sale_data['payment']['amount_paid']
|
||||
|
||||
# Check totals
|
||||
totals = data['totals']
|
||||
assert 'subtotal' in totals
|
||||
assert 'discount_amount' in totals
|
||||
assert 'tax_amount' in totals
|
||||
assert 'total_amount' in totals
|
||||
|
||||
# Should have timestamps
|
||||
assert 'created_at' in data
|
||||
assert 'updated_at' in data
|
||||
|
||||
# Should have tenant_id from context
|
||||
assert 'tenant_id' in data
|
||||
|
||||
def test_create_sale_unauthorized(self):
|
||||
"""Test sale creation without authentication."""
|
||||
response = self.client.post(
|
||||
self.sales_url,
|
||||
data=json.dumps(self.sale_data),
|
||||
content_type='application/json'
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
def test_create_sale_missing_required_fields(self):
|
||||
"""Test sale creation with missing required fields."""
|
||||
incomplete_data = self.sale_data.copy()
|
||||
del incomplete_data['customer']
|
||||
|
||||
response = self.client.post(
|
||||
self.sales_url,
|
||||
data=json.dumps(incomplete_data),
|
||||
content_type='application/json',
|
||||
**self.tenant_auth
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
|
||||
data = response.json()
|
||||
assert 'customer' in data.get('errors', {})
|
||||
|
||||
def test_create_sale_empty_items(self):
|
||||
"""Test sale creation with empty items list."""
|
||||
invalid_data = self.sale_data.copy()
|
||||
invalid_data['items'] = []
|
||||
|
||||
response = self.client.post(
|
||||
self.sales_url,
|
||||
data=json.dumps(invalid_data),
|
||||
content_type='application/json',
|
||||
**self.tenant_auth
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
|
||||
def test_create_sale_invalid_payment_method(self):
|
||||
"""Test sale creation with invalid payment method."""
|
||||
invalid_data = self.sale_data.copy()
|
||||
invalid_data['payment']['method'] = 'INVALID_METHOD'
|
||||
|
||||
response = self.client.post(
|
||||
self.sales_url,
|
||||
data=json.dumps(invalid_data),
|
||||
content_type='application/json',
|
||||
**self.tenant_auth
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
|
||||
def test_create_sale_insufficient_payment(self):
|
||||
"""Test sale creation with insufficient payment amount."""
|
||||
invalid_data = self.sale_data.copy()
|
||||
invalid_data['payment']['amount_paid'] = 100.00 # Much less than total
|
||||
|
||||
response = self.client.post(
|
||||
self.sales_url,
|
||||
data=json.dumps(invalid_data),
|
||||
content_type='application/json',
|
||||
**self.tenant_auth
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
|
||||
def test_create_sale_negative_quantity(self):
|
||||
"""Test sale creation with negative quantity."""
|
||||
invalid_data = self.sale_data.copy()
|
||||
invalid_data['items'][0]['quantity'] = -1
|
||||
|
||||
response = self.client.post(
|
||||
self.sales_url,
|
||||
data=json.dumps(invalid_data),
|
||||
content_type='application/json',
|
||||
**self.tenant_auth
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
|
||||
def test_create_sale_invalid_discount_percentage(self):
|
||||
"""Test sale creation with invalid discount percentage."""
|
||||
invalid_data = self.sale_data.copy()
|
||||
invalid_data['items'][0]['discount_percentage'] = 150.0 # Over 100%
|
||||
|
||||
response = self.client.post(
|
||||
self.sales_url,
|
||||
data=json.dumps(invalid_data),
|
||||
content_type='application/json',
|
||||
**self.tenant_auth
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
|
||||
def test_create_sale_with_installment(self):
|
||||
"""Test sale creation with installment payment."""
|
||||
installment_data = self.sale_data.copy()
|
||||
installment_data['payment']['method'] = 'INSTALLMENT'
|
||||
installment_data['payment']['installment_plan'] = {
|
||||
'down_payment': 1000.00,
|
||||
'number_of_installments': 12,
|
||||
'installment_amount': 358.33,
|
||||
'first_installment_date': '2024-02-01'
|
||||
}
|
||||
|
||||
response = self.client.post(
|
||||
self.sales_url,
|
||||
data=json.dumps(installment_data),
|
||||
content_type='application/json',
|
||||
**self.tenant_auth
|
||||
)
|
||||
|
||||
if response.status_code == status.HTTP_201_CREATED:
|
||||
data = response.json()
|
||||
assert data['status'] == 'COMPLETED'
|
||||
assert 'installment_plan' in data['payment']
|
||||
|
||||
def test_create_sale_with_multiple_payments(self):
|
||||
"""Test sale creation with multiple payment methods."""
|
||||
multi_payment_data = self.sale_data.copy()
|
||||
multi_payment_data['payment'] = [
|
||||
{
|
||||
'method': 'CASH',
|
||||
'amount_paid': 2000.00,
|
||||
'reference_number': 'CASH-001'
|
||||
},
|
||||
{
|
||||
'method': 'CARD',
|
||||
'amount_paid': 3300.00,
|
||||
'reference_number': 'CARD-001',
|
||||
'card_last4': '4242'
|
||||
}
|
||||
]
|
||||
|
||||
response = self.client.post(
|
||||
self.sales_url,
|
||||
data=json.dumps(multi_payment_data),
|
||||
content_type='application/json',
|
||||
**self.tenant_auth
|
||||
)
|
||||
|
||||
if response.status_code == status.HTTP_201_CREATED:
|
||||
data = response.json()
|
||||
assert len(data['payment']) == 2
|
||||
assert data['payment'][0]['method'] == 'CASH'
|
||||
|
||||
def test_create_sale_with_loyalty_points(self):
|
||||
"""Test sale creation with loyalty points redemption."""
|
||||
loyalty_data = self.sale_data.copy()
|
||||
loyalty_data['loyalty'] = {
|
||||
'points_used': 1000,
|
||||
'points_value': 100.00,
|
||||
'customer_id': 'customer-001'
|
||||
}
|
||||
|
||||
response = self.client.post(
|
||||
self.sales_url,
|
||||
data=json.dumps(loyalty_data),
|
||||
content_type='application/json',
|
||||
**self.tenant_auth
|
||||
)
|
||||
|
||||
if response.status_code == status.HTTP_201_CREATED:
|
||||
data = response.json()
|
||||
assert 'loyalty' in data
|
||||
assert data['loyalty']['points_used'] == 1000
|
||||
|
||||
def test_create_sale_with_delivery_info(self):
|
||||
"""Test sale creation with delivery information."""
|
||||
delivery_data = self.sale_data.copy()
|
||||
delivery_data['delivery'] = {
|
||||
'method': 'DELIVERY',
|
||||
'address': self.sale_data['customer']['address'],
|
||||
'scheduled_date': '2024-01-20',
|
||||
'delivery_fee': 50.00,
|
||||
'notes': 'Leave at front door'
|
||||
}
|
||||
|
||||
response = self.client.post(
|
||||
self.sales_url,
|
||||
data=json.dumps(delivery_data),
|
||||
content_type='application/json',
|
||||
**self.tenant_auth
|
||||
)
|
||||
|
||||
if response.status_code == status.HTTP_201_CREATED:
|
||||
data = response.json()
|
||||
assert 'delivery' in data
|
||||
assert data['delivery']['method'] == 'DELIVERY'
|
||||
|
||||
def test_create_sale_with_exchange_items(self):
|
||||
"""Test sale creation with item exchange."""
|
||||
exchange_data = self.sale_data.copy()
|
||||
exchange_data['exchange'] = {
|
||||
'items': [
|
||||
{
|
||||
'product_id': 'old-product-001',
|
||||
'sku': 'OLD-001',
|
||||
'condition': 'GOOD',
|
||||
'exchange_value': 500.00
|
||||
}
|
||||
],
|
||||
'total_exchange_value': 500.00
|
||||
}
|
||||
|
||||
response = self.client.post(
|
||||
self.sales_url,
|
||||
data=json.dumps(exchange_data),
|
||||
content_type='application/json',
|
||||
**self.tenant_auth
|
||||
)
|
||||
|
||||
if response.status_code == status.HTTP_201_CREATED:
|
||||
data = response.json()
|
||||
assert 'exchange' in data
|
||||
assert len(data['exchange']['items']) == 1
|
||||
|
||||
def test_create_sale_tax_calculation(self):
|
||||
"""Test that tax calculation is correct."""
|
||||
response = self.client.post(
|
||||
self.sales_url,
|
||||
data=json.dumps(self.sale_data),
|
||||
content_type='application/json',
|
||||
**self.tenant_auth
|
||||
)
|
||||
|
||||
if response.status_code == status.HTTP_201_CREATED:
|
||||
data = response.json()
|
||||
totals = data['totals']
|
||||
|
||||
# Verify tax calculation (6% GST on subtotal after discounts)
|
||||
# This will be validated once implementation exists
|
||||
assert 'tax_amount' in totals
|
||||
assert totals['tax_amount'] >= 0
|
||||
|
||||
def test_create_sale_tenant_isolation(self):
|
||||
"""Test that sale creation respects tenant isolation."""
|
||||
response = self.client.post(
|
||||
self.sales_url,
|
||||
data=json.dumps(self.sale_data),
|
||||
content_type='application/json',
|
||||
**self.tenant_auth
|
||||
)
|
||||
|
||||
if response.status_code == status.HTTP_201_CREATED:
|
||||
data = response.json()
|
||||
# Sale should be created in the authenticated tenant's context
|
||||
assert 'tenant_id' in data
|
||||
# This will be validated once implementation exists
|
||||
|
||||
def test_create_sale_inventory_validation(self):
|
||||
"""Test that sale creation validates inventory availability."""
|
||||
response = self.client.post(
|
||||
self.sales_url,
|
||||
data=json.dumps(self.sale_data),
|
||||
content_type='application/json',
|
||||
**self.tenant_auth
|
||||
)
|
||||
|
||||
# This test will ensure that the system checks if sufficient stock is available
|
||||
# The test should pass if implementation exists and validates inventory
|
||||
if response.status_code == status.HTTP_201_CREATED:
|
||||
# Success means inventory was available
|
||||
pass
|
||||
elif response.status_code == status.HTTP_400_BAD_REQUEST:
|
||||
# This could also be valid if inventory validation fails
|
||||
pass
|
||||
Reference in New Issue
Block a user