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:
264
backend/tests/contract/test_subscriptions_post.py
Normal file
264
backend/tests/contract/test_subscriptions_post.py
Normal file
@@ -0,0 +1,264 @@
|
||||
"""
|
||||
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'
|
||||
Reference in New Issue
Block a user