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
264 lines
9.5 KiB
Python
264 lines
9.5 KiB
Python
"""
|
|
Contract test for POST /subscriptions 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 SubscriptionsPostContractTest(TestCase):
|
|
def setUp(self):
|
|
self.client = APIClient()
|
|
self.subscriptions_url = '/api/v1/subscriptions/'
|
|
|
|
# Admin authentication header
|
|
self.admin_auth = {'HTTP_AUTHORIZATION': 'Bearer admin_token'}
|
|
|
|
# Tenant admin authentication header
|
|
self.tenant_admin_auth = {'HTTP_AUTHORIZATION': 'Bearer tenant_admin_token'}
|
|
|
|
# Valid subscription data
|
|
self.subscription_data = {
|
|
'tenant_id': 'test-tenant-id',
|
|
'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,
|
|
'notes': 'Subscription for retail business'
|
|
}
|
|
|
|
def test_create_subscription_success_admin(self):
|
|
"""Test successful subscription creation by admin."""
|
|
response = self.client.post(
|
|
self.subscriptions_url,
|
|
data=json.dumps(self.subscription_data),
|
|
content_type='application/json',
|
|
**self.admin_auth
|
|
)
|
|
|
|
# This should fail before implementation
|
|
assert response.status_code == status.HTTP_201_CREATED
|
|
|
|
data = response.json()
|
|
assert 'id' in data
|
|
assert data['tenant_id'] == self.subscription_data['tenant_id']
|
|
assert data['plan'] == self.subscription_data['plan']
|
|
assert data['pricing_model'] == self.subscription_data['pricing_model']
|
|
assert data['billing_cycle'] == self.subscription_data['billing_cycle']
|
|
assert data['status'] == 'TRIAL' # Default status with trial_days
|
|
|
|
# Should have timestamps
|
|
assert 'created_at' in data
|
|
assert 'updated_at' in data
|
|
|
|
# Should have billing period information
|
|
assert 'current_period_start' in data
|
|
assert 'current_period_end' in data
|
|
assert 'trial_end' in data
|
|
|
|
# Should include modules
|
|
assert 'modules' in data
|
|
assert data['modules'] == self.subscription_data['modules']
|
|
|
|
def test_create_subscription_success_tenant_admin(self):
|
|
"""Test successful subscription creation by tenant admin."""
|
|
# Tenant admin creates subscription for their own tenant
|
|
tenant_subscription_data = self.subscription_data.copy()
|
|
del tenant_subscription_data['tenant_id'] # Should be inferred from context
|
|
|
|
response = self.client.post(
|
|
self.subscriptions_url,
|
|
data=json.dumps(tenant_subscription_data),
|
|
content_type='application/json',
|
|
**self.tenant_admin_auth
|
|
)
|
|
|
|
# This should fail before implementation
|
|
assert response.status_code == status.HTTP_201_CREATED
|
|
|
|
data = response.json()
|
|
assert 'id' in data
|
|
assert data['plan'] == tenant_subscription_data['plan']
|
|
assert data['tenant_id'] # Should be auto-populated
|
|
|
|
def test_create_subscription_unauthorized(self):
|
|
"""Test subscription creation without authentication."""
|
|
response = self.client.post(
|
|
self.subscriptions_url,
|
|
data=json.dumps(self.subscription_data),
|
|
content_type='application/json'
|
|
)
|
|
|
|
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
|
|
|
def test_create_subscription_forbidden(self):
|
|
"""Test subscription creation by regular user (no permissions)."""
|
|
user_auth = {'HTTP_AUTHORIZATION': 'Bearer user_token'}
|
|
|
|
response = self.client.post(
|
|
self.subscriptions_url,
|
|
data=json.dumps(self.subscription_data),
|
|
content_type='application/json',
|
|
**user_auth
|
|
)
|
|
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
|
|
def test_create_subscription_missing_required_fields(self):
|
|
"""Test subscription creation with missing required fields."""
|
|
incomplete_data = self.subscription_data.copy()
|
|
del incomplete_data['plan']
|
|
|
|
response = self.client.post(
|
|
self.subscriptions_url,
|
|
data=json.dumps(incomplete_data),
|
|
content_type='application/json',
|
|
**self.admin_auth
|
|
)
|
|
|
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
|
|
|
data = response.json()
|
|
assert 'plan' in data.get('errors', {})
|
|
|
|
def test_create_subscription_invalid_plan(self):
|
|
"""Test subscription creation with invalid plan."""
|
|
invalid_data = self.subscription_data.copy()
|
|
invalid_data['plan'] = 'INVALID_PLAN'
|
|
|
|
response = self.client.post(
|
|
self.subscriptions_url,
|
|
data=json.dumps(invalid_data),
|
|
content_type='application/json',
|
|
**self.admin_auth
|
|
)
|
|
|
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
|
|
|
def test_create_subscription_invalid_billing_cycle(self):
|
|
"""Test subscription creation with invalid billing cycle."""
|
|
invalid_data = self.subscription_data.copy()
|
|
invalid_data['billing_cycle'] = 'INVALID_CYCLE'
|
|
|
|
response = self.client.post(
|
|
self.subscriptions_url,
|
|
data=json.dumps(incomplete_data),
|
|
content_type='application/json',
|
|
**self.admin_auth
|
|
)
|
|
|
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
|
|
|
def test_create_subscription_duplicate_tenant(self):
|
|
"""Test subscription creation with duplicate tenant."""
|
|
# First request should succeed (if implemented)
|
|
first_response = self.client.post(
|
|
self.subscriptions_url,
|
|
data=json.dumps(self.subscription_data),
|
|
content_type='application/json',
|
|
**self.admin_auth
|
|
)
|
|
|
|
if first_response.status_code == status.HTTP_201_CREATED:
|
|
# Second request with same tenant should fail
|
|
second_response = self.client.post(
|
|
self.subscriptions_url,
|
|
data=json.dumps(self.subscription_data),
|
|
content_type='application/json',
|
|
**self.admin_auth
|
|
)
|
|
assert second_response.status_code == status.HTTP_400_BAD_REQUEST
|
|
|
|
def test_create_subscription_without_trial(self):
|
|
"""Test subscription creation without trial period."""
|
|
no_trial_data = self.subscription_data.copy()
|
|
del no_trial_data['trial_days']
|
|
|
|
response = self.client.post(
|
|
self.subscriptions_url,
|
|
data=json.dumps(no_trial_data),
|
|
content_type='application/json',
|
|
**self.admin_auth
|
|
)
|
|
|
|
if response.status_code == status.HTTP_201_CREATED:
|
|
data = response.json()
|
|
# Should be active immediately without trial
|
|
assert data['status'] == 'ACTIVE'
|
|
assert 'trial_end' not in data or data['trial_end'] is None
|
|
|
|
def test_create_subscription_with_invalid_modules(self):
|
|
"""Test subscription creation with invalid modules."""
|
|
invalid_data = self.subscription_data.copy()
|
|
invalid_data['modules'] = ['invalid_module']
|
|
|
|
response = self.client.post(
|
|
self.subscriptions_url,
|
|
data=json.dumps(invalid_data),
|
|
content_type='application/json',
|
|
**self.admin_auth
|
|
)
|
|
|
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
|
|
|
def test_create_subscription_tenant_admin_cross_tenant(self):
|
|
"""Test that tenant admin cannot create subscription for other tenant."""
|
|
# Tenant admin trying to create subscription for different tenant
|
|
response = self.client.post(
|
|
self.subscriptions_url,
|
|
data=json.dumps(self.subscription_data),
|
|
content_type='application/json',
|
|
**self.tenant_admin_auth
|
|
)
|
|
|
|
# Should fail because tenant_id doesn't match their tenant
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
|
|
def test_create_subscription_payment_method_validation(self):
|
|
"""Test subscription creation with invalid payment method."""
|
|
invalid_data = self.subscription_data.copy()
|
|
invalid_data['payment_method'] = {
|
|
'type': 'CARD',
|
|
'card_last4': '4242',
|
|
# Missing required expiry fields
|
|
}
|
|
|
|
response = self.client.post(
|
|
self.subscriptions_url,
|
|
data=json.dumps(invalid_data),
|
|
content_type='application/json',
|
|
**self.admin_auth
|
|
)
|
|
|
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
|
|
|
def test_create_subscription_with_promo_code(self):
|
|
"""Test subscription creation with promo code."""
|
|
promo_data = self.subscription_data.copy()
|
|
promo_data['promo_code'] = 'WELCOME20'
|
|
|
|
response = self.client.post(
|
|
self.subscriptions_url,
|
|
data=json.dumps(promo_data),
|
|
content_type='application/json',
|
|
**self.admin_auth
|
|
)
|
|
|
|
if response.status_code == status.HTTP_201_CREATED:
|
|
data = response.json()
|
|
# Should include discount information
|
|
assert 'discount' in data
|
|
assert data['promo_code'] == 'WELCOME20' |