Renamed ebook_backend&admin_panel to ebook_backend_admin_panel The & character was being interpreted by shell as background process operator, causing 'Dockerfile not found' errors in Coolify.
406 lines
16 KiB
Python
406 lines
16 KiB
Python
import pytest
|
|
from unittest.mock import patch, MagicMock
|
|
from fastapi import HTTPException
|
|
|
|
class TestCouponRoutes:
|
|
"""Test cases for coupon management routes"""
|
|
|
|
def test_generate_single_code_unauthorized(self, client):
|
|
"""Test generate single code without authentication"""
|
|
response = client.post("/generate", data={"mode": "single", "count": 1})
|
|
assert response.status_code == 401
|
|
data = response.json()
|
|
assert data["detail"] == "Unauthorized"
|
|
|
|
def test_generate_single_code_success(self, client, auth_headers):
|
|
"""Test successful single code generation"""
|
|
with patch('routes.auth.generate_coupon') as mock_generate:
|
|
mock_generate.return_value = "ABC123DEF4"
|
|
|
|
response = client.post("/generate", data={"mode": "single", "count": 1}, headers=auth_headers)
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["code"] == "ABC123DEF4"
|
|
mock_generate.assert_called_once()
|
|
|
|
def test_generate_bulk_codes_success(self, client, auth_headers):
|
|
"""Test successful bulk code generation"""
|
|
with patch('routes.auth.generate_coupon') as mock_generate:
|
|
mock_generate.side_effect = ["CODE1", "CODE2", "CODE3"]
|
|
|
|
response = client.post("/generate", data={"mode": "bulk", "count": 3}, headers=auth_headers)
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["codes"] == ["CODE1", "CODE2", "CODE3"]
|
|
assert mock_generate.call_count == 3
|
|
|
|
def test_generate_invalid_mode(self, client, auth_headers):
|
|
"""Test code generation with invalid mode"""
|
|
response = client.post("/generate", data={"mode": "invalid", "count": 1}, headers=auth_headers)
|
|
assert response.status_code == 400
|
|
data = response.json()
|
|
assert data["detail"] == "Invalid mode"
|
|
|
|
def test_generate_bulk_zero_count(self, client, auth_headers):
|
|
"""Test bulk generation with zero count"""
|
|
response = client.post("/generate", data={"mode": "bulk", "count": 0}, headers=auth_headers)
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["codes"] == []
|
|
|
|
def test_list_codes_pagination(self, client, sample_coupons):
|
|
"""Test coupon listing with pagination"""
|
|
response = client.get("/list?page=1&limit=2")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
assert "codes" in data
|
|
assert "total" in data
|
|
assert "page" in data
|
|
assert "limit" in data
|
|
assert "total_pages" in data
|
|
|
|
assert data["page"] == 1
|
|
assert data["limit"] == 2
|
|
assert data["total"] == 3
|
|
assert len(data["codes"]) == 2
|
|
|
|
def test_list_codes_default_pagination(self, client, sample_coupons):
|
|
"""Test coupon listing with default pagination"""
|
|
response = client.get("/list")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
assert data["page"] == 1
|
|
assert data["limit"] == 20
|
|
assert len(data["codes"]) == 3
|
|
|
|
def test_list_codes_empty_database(self, client):
|
|
"""Test coupon listing with empty database"""
|
|
response = client.get("/list")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
assert data["codes"] == []
|
|
assert data["total"] == 0
|
|
assert data["page"] == 1
|
|
assert data["limit"] == 20
|
|
assert data["total_pages"] == 0
|
|
|
|
def test_list_codes_second_page(self, client, sample_coupons):
|
|
"""Test coupon listing second page"""
|
|
response = client.get("/list?page=2&limit=2")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
assert data["page"] == 2
|
|
assert data["limit"] == 2
|
|
assert len(data["codes"]) == 1 # Only 1 code left on page 2
|
|
|
|
def test_search_codes_success(self, client, sample_coupons):
|
|
"""Test successful code search"""
|
|
response = client.get("/search-codes?query=TEST")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
assert len(data) == 1
|
|
assert data[0]["code"] == "TEST123"
|
|
assert "used" in data[0]
|
|
assert "usage_count" in data[0]
|
|
assert "used_at" in data[0]
|
|
|
|
def test_search_codes_case_insensitive(self, client, sample_coupons):
|
|
"""Test case-insensitive code search"""
|
|
response = client.get("/search-codes?query=test")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
assert len(data) == 1
|
|
assert data[0]["code"] == "TEST123"
|
|
|
|
def test_search_codes_partial_match(self, client, sample_coupons):
|
|
"""Test partial code search"""
|
|
response = client.get("/search-codes?query=123")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
assert len(data) == 1
|
|
assert data[0]["code"] == "TEST123"
|
|
|
|
def test_search_codes_no_results(self, client, sample_coupons):
|
|
"""Test code search with no results"""
|
|
response = client.get("/search-codes?query=NONEXISTENT")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
assert data == []
|
|
|
|
def test_search_codes_empty_query(self, client, sample_coupons):
|
|
"""Test code search with empty query"""
|
|
response = client.get("/search-codes?query=")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
# Should return all codes when query is empty
|
|
assert len(data) == 3
|
|
|
|
def test_use_code_success(self, client, sample_coupons):
|
|
"""Test successful code usage"""
|
|
response = client.post("/use-code", json={"code": "TEST123"})
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
assert data["code"] == "TEST123"
|
|
assert "used_at" in data
|
|
|
|
def test_use_code_case_insensitive(self, client, sample_coupons):
|
|
"""Test case-insensitive code usage"""
|
|
response = client.post("/use-code", json={"code": "test123"})
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
assert data["code"] == "TEST123"
|
|
|
|
def test_use_code_not_found(self, client):
|
|
"""Test using non-existent code"""
|
|
response = client.post("/use-code", json={"code": "NONEXISTENT"})
|
|
assert response.status_code == 404
|
|
data = response.json()
|
|
assert data["detail"] == "Invalid code"
|
|
|
|
def test_use_code_already_used(self, client, used_coupon):
|
|
"""Test using already used code"""
|
|
response = client.post("/use-code", json={"code": "USED123"})
|
|
assert response.status_code == 400
|
|
data = response.json()
|
|
assert data["detail"] == "Coupon already used"
|
|
|
|
def test_use_code_whitespace_handling(self, client, sample_coupons):
|
|
"""Test code usage with whitespace"""
|
|
response = client.post("/use-code", json={"code": " TEST123 "})
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
assert data["code"] == "TEST123"
|
|
|
|
def test_check_code_success(self, client, sample_coupons):
|
|
"""Test successful code check"""
|
|
response = client.get("/check-code/TEST123")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
assert data["code"] == "TEST123"
|
|
assert data["used"] == 0
|
|
|
|
def test_check_code_case_insensitive(self, client, sample_coupons):
|
|
"""Test case-insensitive code check"""
|
|
response = client.get("/check-code/test123")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
assert data["code"] == "TEST123"
|
|
|
|
def test_check_code_not_found(self, client):
|
|
"""Test checking non-existent code"""
|
|
response = client.get("/check-code/NONEXISTENT")
|
|
assert response.status_code == 404
|
|
data = response.json()
|
|
assert data["detail"] == "Code not found"
|
|
|
|
def test_check_code_whitespace_handling(self, client, sample_coupons):
|
|
"""Test code check with whitespace"""
|
|
response = client.get("/check-code/ TEST123 ")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
assert data["code"] == "TEST123"
|
|
|
|
def test_verify_coupon_success(self, client, sample_coupons):
|
|
"""Test successful coupon verification"""
|
|
response = client.post("/verify", json={"code": "TEST123"})
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
assert data["message"] == "Coupon verified"
|
|
assert "used_at" in data
|
|
|
|
def test_verify_coupon_case_insensitive(self, client, sample_coupons):
|
|
"""Test case-insensitive coupon verification"""
|
|
response = client.post("/verify", json={"code": "test123"})
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
assert data["message"] == "Coupon verified"
|
|
|
|
def test_verify_coupon_not_found(self, client):
|
|
"""Test verifying non-existent coupon"""
|
|
response = client.post("/verify", json={"code": "NONEXISTENT"})
|
|
assert response.status_code == 404
|
|
data = response.json()
|
|
assert data["detail"] == "Invalid coupon code"
|
|
|
|
def test_verify_coupon_already_used(self, client, used_coupon):
|
|
"""Test verifying already used coupon"""
|
|
response = client.post("/verify", json={"code": "USED123"})
|
|
assert response.status_code == 400
|
|
data = response.json()
|
|
assert data["detail"] == "Coupon already used"
|
|
|
|
def test_verify_coupon_whitespace_handling(self, client, sample_coupons):
|
|
"""Test coupon verification with whitespace"""
|
|
response = client.post("/verify", json={"code": " TEST123 "})
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
assert data["message"] == "Coupon verified"
|
|
|
|
def test_add_code_unauthorized(self, client):
|
|
"""Test adding code without authentication"""
|
|
code_data = {"code": "NEW123", "usage": 0}
|
|
response = client.post("/add-code", json=code_data)
|
|
assert response.status_code == 401
|
|
data = response.json()
|
|
assert data["detail"] == "Unauthorized"
|
|
|
|
def test_add_code_success(self, client, auth_headers):
|
|
"""Test successful code addition"""
|
|
code_data = {"code": "NEW123", "usage": 0}
|
|
response = client.post("/add-code", json=code_data, headers=auth_headers)
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["message"] == "Code added successfully"
|
|
|
|
def test_add_code_already_exists(self, client, sample_coupons, auth_headers):
|
|
"""Test adding code that already exists"""
|
|
code_data = {"code": "TEST123", "usage": 0}
|
|
response = client.post("/add-code", json=code_data, headers=auth_headers)
|
|
assert response.status_code == 400
|
|
data = response.json()
|
|
assert data["detail"] == "Code already exists"
|
|
|
|
def test_add_code_case_normalization(self, client, auth_headers):
|
|
"""Test code case normalization during addition"""
|
|
code_data = {"code": "new123", "usage": 0}
|
|
response = client.post("/add-code", json=code_data, headers=auth_headers)
|
|
assert response.status_code == 200
|
|
|
|
# Verify the code was stored in uppercase
|
|
response = client.get("/check-code/NEW123")
|
|
assert response.status_code == 200
|
|
|
|
def test_add_code_negative_usage(self, client, auth_headers):
|
|
"""Test adding code with negative usage count"""
|
|
code_data = {"code": "NEW123", "usage": -5}
|
|
response = client.post("/add-code", json=code_data, headers=auth_headers)
|
|
assert response.status_code == 200
|
|
|
|
# Verify usage count was normalized to 0
|
|
response = client.get("/check-code/NEW123")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["used"] == 0
|
|
|
|
def test_delete_code_unauthorized(self, client):
|
|
"""Test deleting code without authentication"""
|
|
response = client.delete("/delete-code/TEST123")
|
|
assert response.status_code == 401
|
|
data = response.json()
|
|
assert data["detail"] == "Unauthorized"
|
|
|
|
def test_delete_code_success(self, client, sample_coupons, auth_headers):
|
|
"""Test successful code deletion"""
|
|
response = client.delete("/delete-code/TEST123", headers=auth_headers)
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["message"] == "Code deleted successfully"
|
|
|
|
# Verify code is deleted
|
|
response = client.get("/check-code/TEST123")
|
|
assert response.status_code == 404
|
|
|
|
def test_delete_code_case_insensitive(self, client, sample_coupons, auth_headers):
|
|
"""Test case-insensitive code deletion"""
|
|
response = client.delete("/delete-code/test123", headers=auth_headers)
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["message"] == "Code deleted successfully"
|
|
|
|
def test_delete_code_not_found(self, client, auth_headers):
|
|
"""Test deleting non-existent code"""
|
|
response = client.delete("/delete-code/NONEXISTENT", headers=auth_headers)
|
|
assert response.status_code == 404
|
|
data = response.json()
|
|
assert data["detail"] == "Code not found"
|
|
|
|
def test_delete_code_whitespace_handling(self, client, sample_coupons, auth_headers):
|
|
"""Test code deletion with whitespace"""
|
|
response = client.delete("/delete-code/ TEST123 ", headers=auth_headers)
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["message"] == "Code deleted successfully"
|
|
|
|
def test_upload_codes_unauthorized(self, client):
|
|
"""Test uploading codes without authentication"""
|
|
upload_data = {
|
|
"codes": [
|
|
{"code": "UPLOAD1", "usage": 0},
|
|
{"code": "UPLOAD2", "usage": 0}
|
|
]
|
|
}
|
|
response = client.post("/upload-codes", json=upload_data)
|
|
assert response.status_code == 401
|
|
data = response.json()
|
|
assert data["detail"] == "Unauthorized"
|
|
|
|
def test_upload_codes_success(self, client, auth_headers):
|
|
"""Test successful code upload"""
|
|
upload_data = {
|
|
"codes": [
|
|
{"code": "UPLOAD1", "usage": 0},
|
|
{"code": "UPLOAD2", "usage": 1}
|
|
]
|
|
}
|
|
response = client.post("/upload-codes", json=upload_data, headers=auth_headers)
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
assert data["uploaded"] == 2
|
|
assert data["skipped"] == 0
|
|
assert data["total"] == 2
|
|
|
|
def test_upload_codes_with_duplicates(self, client, sample_coupons, auth_headers):
|
|
"""Test code upload with duplicate codes"""
|
|
upload_data = {
|
|
"codes": [
|
|
{"code": "TEST123", "usage": 0}, # Already exists
|
|
{"code": "NEW123", "usage": 0} # New code
|
|
]
|
|
}
|
|
response = client.post("/upload-codes", json=upload_data, headers=auth_headers)
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
assert data["uploaded"] == 1
|
|
assert data["skipped"] == 1
|
|
assert data["total"] == 2
|
|
|
|
def test_upload_codes_case_normalization(self, client, auth_headers):
|
|
"""Test code case normalization during upload"""
|
|
upload_data = {
|
|
"codes": [
|
|
{"code": "lowercase", "usage": 0},
|
|
{"code": "MIXEDCase", "usage": 0}
|
|
]
|
|
}
|
|
response = client.post("/upload-codes", json=upload_data, headers=auth_headers)
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
assert data["uploaded"] == 2
|
|
|
|
# Verify codes were stored in uppercase
|
|
response = client.get("/check-code/LOWERCASE")
|
|
assert response.status_code == 200
|
|
|
|
response = client.get("/check-code/MIXEDCASE")
|
|
assert response.status_code == 200 |