from django.test import TestCase from django.urls import reverse from rest_framework import status from rest_framework.test import APIClient from verifier.engine.base import BaseVendor, VendorResult, VerificationStatus from verifier.engine.registry import VendorRegistry, registry from verifier.engine.orchestrator import VerificationEngine from verifier.vendors.codeplus import CodeplusVendor from verifier.models import VerificationTransaction, VendorAttemptLog class MockFailingVendor(BaseVendor): """Simulates a primary vendor that encounters an API error/failure.""" name = "vendor_alpha" priority = 10 is_active = True supported_documents = ["pan", "aadhaar"] def execute_verification(self, document_type, data, files=None): return VendorResult( vendor_name=self.name, is_success=False, status=VerificationStatus.ERROR, error_code="VENDOR_API_DOWNTIME", error_message="Primary vendor Alpha API returned HTTP 503 Service Unavailable", retryable=True, ) class MockSucceedingVendor(BaseVendor): """Simulates a fallback vendor that successfully completes verification.""" name = "vendor_beta" priority = 20 is_active = True supported_documents = ["pan", "aadhaar", "driving_license"] def execute_verification(self, document_type, data, files=None): return VendorResult( vendor_name=self.name, is_success=True, status=VerificationStatus.SUCCESS, data={ "document_number": data.get("pan_number", "ABCDE1234F"), "full_name": "Test User", "verification_status": "VALID", }, ) class VerificationEngineWaterfallTests(TestCase): """Tests the core failover / waterfall mechanism of the VerificationEngine.""" def setUp(self): self.custom_registry = VendorRegistry() self.vendor_alpha = MockFailingVendor() self.vendor_beta = MockSucceedingVendor() self.custom_registry.register(self.vendor_alpha) self.custom_registry.register(self.vendor_beta) self.engine = VerificationEngine(vendor_registry=self.custom_registry) def test_waterfall_falls_back_when_first_vendor_errors(self): """When vendor_alpha throws an error, engine must fall back to vendor_beta and succeed.""" result = self.engine.verify( document_type="pan", data={"pan_number": "ABCDE1234F", "name": "Test User"}, ) self.assertTrue(result.is_success) self.assertEqual(result.status, VerificationStatus.SUCCESS) self.assertEqual(result.successful_vendor, "vendor_beta") self.assertEqual(len(result.attempts), 2) # Check attempt 1 (failed) attempt_1 = result.attempts[0] self.assertEqual(attempt_1["vendor_name"], "vendor_alpha") self.assertFalse(attempt_1["is_success"]) self.assertEqual(attempt_1["error_code"], "VENDOR_API_DOWNTIME") # Check attempt 2 (succeeded) attempt_2 = result.attempts[1] self.assertEqual(attempt_2["vendor_name"], "vendor_beta") self.assertTrue(attempt_2["is_success"]) self.assertEqual(attempt_2["data"]["verification_status"], "VALID") def test_unsupported_document_type_returns_error(self): """When no registered vendor supports the document, engine returns appropriate error.""" result = self.engine.verify( document_type="unsupported_doc", data={"id": "12345"}, ) self.assertFalse(result.is_success) self.assertIn("No active vendors", result.error_message) def test_preferred_vendor_overrides_priority(self): """Specifying preferred_vendor places it at the front of the chain.""" vendors = self.custom_registry.get_vendors_for_document("pan", preferred_vendor="vendor_beta") self.assertEqual(vendors[0].name, "vendor_beta") self.assertEqual(vendors[1].name, "vendor_alpha") class CodeplusVendorTests(TestCase): """Tests Codeplus vendor adapter configuration and handling.""" def setUp(self): self.codeplus = CodeplusVendor( config={ "base_url": "https://apihub.services", "client_api_id": "CAPI-YYR0YJIE0VMTDENY", "client_api_secret": "test_secret_123", } ) def test_codeplus_metadata(self): self.assertEqual(self.codeplus.name, "codeplus") self.assertTrue(self.codeplus.supports("pan")) self.assertTrue(self.codeplus.supports("aadhaar")) self.assertTrue(self.codeplus.supports("driving_license")) def test_codeplus_headers(self): headers = self.codeplus.get_headers() self.assertEqual(headers["X-Client-Api-ID"], "CAPI-YYR0YJIE0VMTDENY") self.assertEqual(headers["X-Client-Api-Secret"], "test_secret_123") self.assertEqual(headers["Content-Type"], "application/json") def test_codeplus_pan_v5_success(self): """Test parsing valid PAN response via v5 Recommended API.""" mock_response_payload = { "success": True, "message": "PAN verification completed", "data": { "transactionId": "TXN_1787136465045_demo12345", "service": "PAN_VERIFICATION", "success": True, "resultCode": 101, "status": "SUCCESS", "message": "PAN verified successfully", "providerTransactionId": "TXN_1787136465045_demo12345", "pan": "ABCDE1234F", "panType": "Individual", "fullName": "RAHUL KUMAR", "firstName": "RAHUL", "middleName": None, "lastName": "KUMAR", "gender": "male", "dob": "15/06/1995", "aadhaarNumber": "XXXXXXXX1234", "aadhaarLinked": True, "addressDetails": { "building_name": "12/45", "locality": "Central Area", "street_name": "MG Road", "pincode": "641001", "city": "COIMBATORE", "state": "Tamil Nadu", "country": "India", }, "clientRefNum": "REQ_1787136465034_demo12345", }, "requestId": "REQ_1787136465034_demo12345", "timestamp": "2026-08-19T10:47:45.735Z", "statusCode": 200, "status": "SUCCESS", } class MockResponse: status_code = 200 def json(self): return mock_response_payload from unittest.mock import patch with patch.object(self.codeplus.session, "post", return_value=MockResponse()): result = self.codeplus.verify("pan", {"pan": "ABCDE1234F"}) self.assertTrue(result.is_success) self.assertEqual(result.status, VerificationStatus.SUCCESS) self.assertEqual(result.data["full_name"], "RAHUL KUMAR") self.assertEqual(result.data["dob"], "15/06/1995") self.assertEqual(result.data["pan_type"], "Individual") self.assertEqual(result.data["address_details"]["city"], "COIMBATORE") self.assertTrue(result.data["aadhaar_linked"]) def test_codeplus_pan_advanced_success(self): """Test parsing valid PAN response via v4 Advanced API (HTTP 200, code 1000).""" mock_response_payload = { "request_id": "8a42f1c7-63d9-4e15-b820-7c91a5d34628", "transaction_id": "d5b73e91-2a46-4c08-9f61-83e2b7a51439", "status": 200, "data": { "code": "1000", "message": "Valid PAN.", "pan_data": { "document_type": "PAN", "document_id": "ABCDE1234F", "name": "RAHUL KUMAR", "first_name": "RAHUL", "last_name": "KUMAR", "category": "P", "category_type": "Individual", "is_individual": True, "individual_tax_compliance_status": "Operative", "aadhaar_linked": True, }, }, "timestamp": 1787031203055, "path": "/pan-api/fetch-advanced", } class MockResponse: status_code = 200 def json(self): return mock_response_payload from unittest.mock import patch with patch.object(self.codeplus.session, "post", return_value=MockResponse()): result = self.codeplus.verify("pan_advanced", {"pan": "ABCDE1234F"}) self.assertTrue(result.is_success) self.assertEqual(result.status, VerificationStatus.SUCCESS) self.assertEqual(result.data["name"], "RAHUL KUMAR") self.assertEqual(result.data["document_id"], "ABCDE1234F") self.assertTrue(result.data["aadhaar_linked"]) def test_codeplus_pan_advanced_error_nonexistent(self): """Test parsing non-existent PAN (code 1004).""" mock_error_payload = { "request_id": "23bc5fa5-a02e-4246-aefc-3c455bcd1853", "transaction_id": "542f956c-db4f-48f5-8d6b-d1877a0b930a", "status": 200, "data": { "code": "1004", "message": "PAN does not exist.", }, "timestamp": 1787031223795, "path": "/pan-api/fetch-advanced", } class MockResponse: status_code = 400 def json(self): return mock_error_payload from unittest.mock import patch with patch.object(self.codeplus.session, "post", return_value=MockResponse()): result = self.codeplus.verify("pan_advanced", {"pan": "INVALID999Z"}) self.assertFalse(result.is_success) self.assertEqual(result.error_code, "1004") self.assertEqual(result.error_message, "PAN does not exist.") def test_codeplus_pan_detailed_success(self): """Test parsing valid Detailed PAN response (HTTP 200, code 1000 with address/email/phone).""" mock_response_payload = { "request_id": "7f3a1c92-5b84-4d21-a7e6-91c2f0d84b56", "transaction_id": "b6e8d4a1-2c73-49f5-9a10-5f7c3e821d64", "status": 200, "data": { "code": "1000", "message": "Valid PAN.", "pan_data": { "document_type": "PAN", "document_id": "ABCDE1234F", "name": "RAHUL KUMAR", "first_name": "RAHUL", "last_name": "KUMAR", "category": "P", "date_of_birth": "1995-06-15", "masked_aadhaar_number": "XXXXXXXX1234", "address_data": { "line_1": "12/45", "city": "COIMBATORE", "state": "TAMIL NADU", "pincode": "641001", }, "email": "ra******@example.com", "phone": "98XXXXXX10", "gender": "MALE", "aadhaar_linked": True, "taxpayer_profile": True, }, }, "timestamp": 1787028660489, "path": "/pan-api/fetch-detailed", } class MockResponse: status_code = 200 def json(self): return mock_response_payload from unittest.mock import patch with patch.object(self.codeplus.session, "post", return_value=MockResponse()): result = self.codeplus.verify("pan_detailed", {"pan_number": "ABCDE1234F"}) self.assertTrue(result.is_success) self.assertEqual(result.status, VerificationStatus.SUCCESS) self.assertEqual(result.data["name"], "RAHUL KUMAR") self.assertEqual(result.data["email"], "ra******@example.com") self.assertEqual(result.data["phone"], "98XXXXXX10") self.assertEqual(result.data["date_of_birth"], "1995-06-15") self.assertEqual(result.data["address_data"]["city"], "COIMBATORE") def test_codeplus_pan_detailed_error(self): """Test parsing invalid Detailed PAN error (HTTP 400 with error block).""" mock_error_payload = { "request_id": "b89afa37-8282-4cf7-827f-985700f84ab8", "transaction_id": "d796efe7-26e2-41c0-a1e7-2f7387b2d5aa", "status": 400, "error": { "code": "INVALID_PAN", "message": "Invalid PAN number.", }, "timestamp": 1787028631589, "path": "/pan-api/fetch-detailed", } class MockResponse: status_code = 400 def json(self): return mock_error_payload from unittest.mock import patch with patch.object(self.codeplus.session, "post", return_value=MockResponse()): result = self.codeplus.verify("pan_detailed", {"pan_number": "INVALID999Z"}) self.assertFalse(result.is_success) self.assertEqual(result.error_code, "INVALID_PAN") self.assertEqual(result.error_message, "Invalid PAN number.") def test_codeplus_bank_verify_success(self): """Test successful Bank Account verification.""" mock_response_payload = { "request_id": "b1a2c3d4-e5f6-7890-abcd-ef1234567890", "transaction_id": "tx-bank-998877", "status": 200, "data": { "code": "1000", "message": "Bank account verified successfully.", "bank_data": { "account_number": "123456789012", "ifsc": "HDFC0001234", "name_at_bank": "PRIYA SHARMA", "micr": "641240002", "bank_name": "HDFC BANK", "branch": "MAIN BRANCH", "city": "COIMBATORE", "state": "TAMIL NADU", "pincode": "641001", "account_exists": True, }, }, } class MockResponse: status_code = 200 def json(self): return mock_response_payload from unittest.mock import patch with patch.object(self.codeplus.session, "post", return_value=MockResponse()): result = self.codeplus.verify( "bank_account", {"account_number": "123456789012", "ifsc": "HDFC0001234"}, ) self.assertTrue(result.is_success) self.assertEqual(result.status, VerificationStatus.SUCCESS) self.assertEqual(result.data["beneficiary_name"], "PRIYA SHARMA") self.assertEqual(result.data["bank_name"], "HDFC BANK") self.assertEqual(result.data["micr"], "641240002") self.assertEqual(result.data["city"], "COIMBATORE") def test_codeplus_bank_verify_missing_fields(self): """Test bank verification validation for missing account or IFSC.""" result_missing_acc = self.codeplus.verify("bank_account", {"ifsc": "HDFC0001234"}) self.assertFalse(result_missing_acc.is_success) self.assertEqual(result_missing_acc.error_code, "MISSING_ACCOUNT_NUMBER") result_missing_ifsc = self.codeplus.verify("bank_account", {"account_number": "123456"}) self.assertFalse(result_missing_ifsc.is_success) self.assertEqual(result_missing_ifsc.error_code, "MISSING_IFSC") def test_codeplus_bank_verify_pd_v5_success(self): """Test successful Bank Verify (Penny Drop - v5) API.""" mock_v5_response = { "status": "success", "result": { "failure_reason": None, "account_number": "********7270", "user": None, "is_valid": True, "account_holder_name": "MANOJ PRASAD", "account_ifsc": "UTIB0000373", "npci_error_code": None, }, "tranx_id": "9eeb9d58-04f8-4f8b-9608-99731850993f", "timestamp": "2026-05-22T05:10:43.561554+00:00", "chargeble": "true", "user_consent": "true", } class MockResponse: status_code = 200 def json(self): return mock_v5_response from unittest.mock import patch with patch.object(self.codeplus.session, "post", return_value=MockResponse()) as mock_post: result = self.codeplus.verify( "bank_verify_pd", {"account_number": "123456787270", "ifsc": "UTIB0000373"}, ) self.assertTrue(result.is_success) self.assertEqual(result.status, VerificationStatus.SUCCESS) self.assertEqual(result.data["beneficiary_name"], "MANOJ PRASAD") self.assertEqual(result.data["account_holder_name"], "MANOJ PRASAD") self.assertEqual(result.data["account_ifsc"], "UTIB0000373") self.assertEqual(result.data["masked_account_number"], "********7270") self.assertEqual(result.data["tranx_id"], "9eeb9d58-04f8-4f8b-9608-99731850993f") self.assertEqual(result.data["verification_mode"], "penny_drop_v5") self.assertTrue(result.data["is_valid"]) mock_post.assert_called_once() call_url = mock_post.call_args[0][0] self.assertTrue(call_url.endswith("/v5/bank-verify-pd")) def test_codeplus_bank_verify_pd_v5_invalid_account(self): """Test Bank Verify (Penny Drop - v5) when bank rejects account validity.""" mock_v5_failure = { "status": "failed", "result": { "failure_reason": "Account Does Not Exist", "account_number": "********9999", "user": None, "is_valid": False, "account_holder_name": None, "account_ifsc": "UTIB0000373", "npci_error_code": "NPCI_ACC_INVALID", }, "tranx_id": "8eeb9d58-04f8-4f8b-9608-99731850991a", "timestamp": "2026-05-22T05:10:43.561554+00:00", "chargeble": "false", "user_consent": "true", } class MockResponse: status_code = 200 def json(self): return mock_v5_failure from unittest.mock import patch with patch.object(self.codeplus.session, "post", return_value=MockResponse()): result = self.codeplus.verify( "bank_verify_pd", {"account_number": "999999999999", "ifsc": "UTIB0000373"}, ) self.assertFalse(result.is_success) self.assertEqual(result.status, VerificationStatus.FAILED) self.assertEqual(result.error_code, "NPCI_ACC_INVALID") self.assertEqual(result.error_message, "Account Does Not Exist") self.assertFalse(result.data["is_valid"]) def test_codeplus_bank_verify_pd_v5_http_400(self): """Test Bank Verify (Penny Drop - v5) HTTP 400 Bad Request error.""" class MockResponse: status_code = 400 def json(self): return {"message": "Invalid IFSC code format", "status": "error"} from unittest.mock import patch with patch.object(self.codeplus.session, "post", return_value=MockResponse()): result = self.codeplus.verify( "bank_verify_pd", {"account_number": "1234567890", "ifsc": "INVALID_IFSC"}, ) self.assertFalse(result.is_success) self.assertEqual(result.status, VerificationStatus.FAILED) self.assertEqual(result.error_code, "HTTP_400") self.assertIn("Invalid IFSC", result.error_message) def test_codeplus_bank_verify_pd_v5_doctype_aliases(self): """Test aliases: bank_verify_v5 and bank_pd route to v5 Penny Drop.""" mock_v5_response = { "status": "success", "result": { "failure_reason": None, "account_number": "********7270", "is_valid": True, "account_holder_name": "MANOJ PRASAD", "account_ifsc": "UTIB0000373", "npci_error_code": None, }, "tranx_id": "9eeb9d58-04f8-4f8b-9608-99731850993f", } class MockResponse: status_code = 200 def json(self): return mock_v5_response from unittest.mock import patch with patch.object(self.codeplus.session, "post", return_value=MockResponse()): res1 = self.codeplus.verify("bank_verify_v5", {"account_number": "123456787270", "ifsc": "UTIB0000373"}) self.assertTrue(res1.is_success) self.assertEqual(res1.data["account_holder_name"], "MANOJ PRASAD") res2 = self.codeplus.verify("bank_pd", {"account_number": "123456787270", "ifsc": "UTIB0000373"}) self.assertTrue(res2.is_success) self.assertEqual(res2.data["account_holder_name"], "MANOJ PRASAD") def test_codeplus_fetch_gst_success(self): """Test successful fetch GST by PAN.""" mock_response_payload = { "request_id": "b27c9087-f061-4d38-b7aa-96498f8e667e", "status": 200, "data": { "code": "1002", "message": "Found 3 records", "results": [ { "document_type": "GSTIN", "document_id": "20AXXXXXXXXXXX", "status": "Active", "state": "Jharkhand", "state_code": "20", }, { "document_type": "GSTIN", "document_id": "27AXXXXXXXXXXX", "status": "Active", "state": "Maharashtra", "state_code": "27", }, { "document_type": "GSTIN", "document_id": "29AXXXXXXXXXXX", "status": "Active", "state": "Karnataka", "state_code": "29", }, ], }, } class MockResponse: status_code = 200 def json(self): return mock_response_payload from unittest.mock import patch with patch.object(self.codeplus.session, "post", return_value=MockResponse()): result = self.codeplus.verify("gst_by_pan", {"pan": "ABCDE1234F"}) self.assertTrue(result.is_success) self.assertEqual(result.status, VerificationStatus.SUCCESS) self.assertEqual(result.data["records_count"], 3) self.assertEqual(len(result.data["results"]), 3) self.assertEqual(result.data["results"][0]["document_id"], "20AXXXXXXXXXXX") def test_codeplus_fetch_gst_missing_pan(self): """Test validation for missing PAN in GST by PAN.""" result = self.codeplus.verify("gst_by_pan", {}) self.assertFalse(result.is_success) self.assertEqual(result.error_code, "MISSING_PAN") def test_codeplus_fetch_msme_no_record(self): """Test MSME by PAN returning code 1011 (No record found).""" mock_response_payload = { "request_id": "cd7a7566-3122-4942-a110-d2b0ab81224d", "transaction_id": "87186c96-f51d-436b-9fce-3a9d1ab8c44c", "status": 200, "data": { "code": "1011", "message": "No record found.", }, "timestamp": 1741349527562, "path": "/msme-api/udyam/fetch-by-pan", } class MockResponse: status_code = 200 def json(self): return mock_response_payload from unittest.mock import patch with patch.object(self.codeplus.session, "post", return_value=MockResponse()): result = self.codeplus.verify("msme", {"pan": "ABCDE1234F"}) self.assertTrue(result.is_success) self.assertEqual(result.status, VerificationStatus.SUCCESS) self.assertFalse(result.data["has_msme"]) self.assertEqual(result.data["code"], "1011") self.assertEqual(result.data["message"], "No record found.") def test_codeplus_fetch_msme_missing_pan(self): """Test validation for missing PAN in MSME.""" result = self.codeplus.verify("msme", {}) self.assertFalse(result.is_success) self.assertEqual(result.error_code, "MISSING_PAN") def test_codeplus_gst_verify_v5_success(self): """Test successful GST verification v5.""" mock_response_payload = { "success": True, "message": "GST verification completed", "data": { "service": "GST_VERIFICATION", "gstNumber": "27ABCDE1234F1Z5", "legalName": "ABCDE PRIVATE LIMITED", "registrationStatus": "ACTIVE", }, "statusCode": 200, } class MockResponse: status_code = 200 def json(self): return mock_response_payload from unittest.mock import patch with patch.object(self.codeplus.session, "post", return_value=MockResponse()): result = self.codeplus.verify("gstin", {"gst_number": "27ABCDE1234F1Z5"}) self.assertTrue(result.is_success) self.assertEqual(result.status, VerificationStatus.SUCCESS) self.assertEqual(result.data["gst_number"], "27ABCDE1234F1Z5") self.assertEqual(result.data["legal_name"], "ABCDE PRIVATE LIMITED") self.assertEqual(result.data["registration_status"], "ACTIVE") def test_codeplus_gst_verify_v5_missing_gst(self): """Test validation for missing GST number.""" result = self.codeplus.verify("gstin", {}) self.assertFalse(result.is_success) self.assertEqual(result.error_code, "MISSING_GST_NUMBER") class VerificationAPITests(TestCase): """Tests the DRF API endpoints with waterfall execution and persistence.""" def setUp(self): self.client = APIClient() def test_vendor_list_api(self): url = reverse("verifier:list_vendors") response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIn("vendors", response.data) vendor_names = [v["name"] for v in response.data["vendors"]] self.assertIn("codeplus", vendor_names) def test_verify_api_validation_error(self): url = reverse("verifier:verify_document") response = self.client.post(url, {}, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertFalse(response.data["is_success"]) def test_verify_api_database_persistence(self): """Ensure full transaction and attempt logs (including raw response and client response) are saved in DB.""" from unittest.mock import patch from verifier.vendors import codeplus_vendor mock_v5_response = { "success": True, "message": "PAN verification completed", "data": { "transactionId": "TXN_TEST_DB_123", "service": "PAN_VERIFICATION", "success": True, "resultCode": 101, "status": "SUCCESS", "pan": "ABCDE1234F", "fullName": "DATABASE PERSISTENCE USER", "dob": "01/01/1990", "aadhaarLinked": True, }, "requestId": "REQ_TEST_DB_123", "statusCode": 200, } class MockResponse: status_code = 200 def json(self): return mock_v5_response url = reverse("verifier:verify_document") with patch.object(codeplus_vendor.session, "post", return_value=MockResponse()): res = self.client.post( url, { "document_type": "pan", "data": {"pan": "ABCDE1234F"}, }, format="json", ) self.assertEqual(res.status_code, status.HTTP_200_OK) ref_id = res.data["reference_id"] # Verify transaction in PostgreSQL DB tx = VerificationTransaction.objects.get(reference_id=ref_id) self.assertEqual(tx.document_type, "pan") self.assertEqual(tx.status, "SUCCESS") self.assertEqual(tx.successful_vendor, "codeplus") self.assertEqual(tx.final_data["full_name"], "DATABASE PERSISTENCE USER") self.assertEqual(tx.response_payload["reference_id"], str(ref_id)) self.assertEqual(tx.attempts_count, 1) # Verify attempt log in DB attempts = tx.attempts.all() self.assertEqual(attempts.count(), 1) attempt_1 = attempts.first() self.assertEqual(attempt_1.vendor_name, "codeplus") self.assertTrue(attempt_1.is_success) self.assertEqual(attempt_1.raw_response["statusCode"], 200) # Verify GET /api/transactions// detail_url = reverse("verifier:transaction_detail", kwargs={"reference_id": ref_id}) detail_res = self.client.get(detail_url) self.assertEqual(detail_res.status_code, status.HTTP_200_OK) self.assertEqual(detail_res.data["reference_id"], str(ref_id)) self.assertEqual(len(detail_res.data["attempts"]), 1) # Verify GET /api/transactions/ (List endpoint) list_url = reverse("verifier:transaction_list") list_res = self.client.get(list_url) self.assertEqual(list_res.status_code, status.HTTP_200_OK) self.assertGreaterEqual(list_res.data["count"], 1) def test_portal_view_renders(self): """Test GET / and /api/portal/ render the verification playground portal.""" # Test root path res_root = self.client.get("/") self.assertEqual(res_root.status_code, status.HTTP_200_OK) self.assertContains(res_root, "Tradhox Document Verification Studio") self.assertContains(res_root, "PAN Verification (v5 Recommended)") # Test /api/portal/ res_portal = self.client.get(reverse("verifier:portal")) self.assertEqual(res_portal.status_code, status.HTTP_200_OK) self.assertContains(res_portal, "Test Execution Console") def test_seller_pan_gst_verification_success(self): """Test composite seller PAN verification, name matching, and GST pull.""" from verifier.views import compare_names # Test name matcher directly m1 = compare_names("RAHUL KUMAR", "RAHUL KUMAR") self.assertTrue(m1["matched"]) self.assertEqual(m1["score"], 100.0) m2 = compare_names("Mr. Rahul Kumar", "RAHUL KUMAR") self.assertTrue(m2["matched"]) m_diff = compare_names("PRIYA SHARMA", "ROHIT SHARMA") self.assertFalse(m_diff["matched"]) # Test POST /api/verify/seller/ with mocked vendor responses mock_pan_resp = { "statusCode": 200, "data": { "name": "RAHUL KUMAR", "pan_type": "Individual", "status": "VALID", "aadhaar_seeding_status": "LINKED", }, } mock_gst_resp = { "request_id": "req-gst-1", "status": 200, "data": { "code": "1002", "message": "Found 1 records", "results": [ { "document_type": "GSTIN", "document_id": "27ABCDE1234F1Z5", "status": "Active", "state": "Maharashtra", "state_code": "27", } ], }, } class MockResp: def __init__(self, d): self.d = d self.status_code = 200 def json(self): return self.d codeplus_vendor = registry.get_vendor("codeplus") from unittest.mock import patch with patch.object(codeplus_vendor.session, "post") as mock_post: mock_post.side_effect = [MockResp(mock_pan_resp), MockResp(mock_gst_resp)] url = reverse("verifier:verify_seller_pan_gst") res = self.client.post( url, { "seller_name": "RAHUL KUMAR", "pan": "ABCDE1234F", "gstin": "27ABCDE1234F1Z5", }, format="json", ) self.assertEqual(res.status_code, status.HTTP_200_OK) self.assertTrue(res.data["is_success"]) self.assertEqual(res.data["status"], "SUCCESS") self.assertTrue(res.data["data"]["name_matched"]) self.assertEqual(res.data["data"]["primary_gstin"], "27ABCDE1234F1Z5") self.assertTrue(res.data["data"]["gstin_matched"]) self.assertEqual(len(res.data["data"]["gst_records"]), 1) # Check DB persistence tx = VerificationTransaction.objects.get(reference_id=res.data["reference_id"]) self.assertEqual(tx.document_type, "SELLER_PAN_GST") self.assertEqual(tx.status, "SUCCESS") self.assertEqual(tx.final_data["verified_pan_name"], "RAHUL KUMAR") def test_seller_pan_gst_name_mismatch(self): """Test seller PAN verification when entered seller name does not match PAN holder.""" mock_pan_resp = { "statusCode": 200, "data": { "name": "ROHIT SHARMA", "pan_type": "Individual", "status": "VALID", }, } mock_gst_resp = {"status": 200, "data": {"code": "1011", "message": "No record found."}} class MockResp: def __init__(self, d): self.d = d self.status_code = 200 def json(self): return self.d codeplus_vendor = registry.get_vendor("codeplus") from unittest.mock import patch with patch.object(codeplus_vendor.session, "post") as mock_post: mock_post.side_effect = [MockResp(mock_pan_resp), MockResp(mock_gst_resp)] url = reverse("verifier:verify_seller_pan_gst") res = self.client.post( url, { "seller_name": "PRIYA VERMA", "pan": "ABCDE1234F", }, format="json", ) self.assertEqual(res.status_code, status.HTTP_422_UNPROCESSABLE_ENTITY) self.assertFalse(res.data["is_success"]) self.assertEqual(res.data["status"], "NAME_MISMATCH") self.assertFalse(res.data["data"]["name_matched"]) self.assertIn("PAN Name Mismatch", res.data["error_message"]) # DB persistence check tx = VerificationTransaction.objects.get(reference_id=res.data["reference_id"]) self.assertEqual(tx.status, "NAME_MISMATCH") def test_seller_bank_verification_success(self): """Test composite seller bank verification via /api/verify/seller/bank/ and DB persistence.""" from verifier.models import SellerBankVerification mock_v5_response = { "status": "success", "result": { "failure_reason": None, "account_number": "********7270", "user": None, "is_valid": True, "account_holder_name": "MANOJ PRASAD", "account_ifsc": "UTIB0000373", "npci_error_code": None, }, "tranx_id": "9eeb9d58-04f8-4f8b-9608-99731850993f", "timestamp": "2026-05-22T05:10:43.561554+00:00", "chargeble": "true", "user_consent": "true", } class MockResp: status_code = 200 def json(self): return mock_v5_response codeplus_vendor = registry.get_vendor("codeplus") from unittest.mock import patch with patch.object(codeplus_vendor.session, "post", return_value=MockResp()): url = reverse("verifier:verify_seller_bank") res = self.client.post( url, { "seller_id": "seller_1001", "account_number": "123456787270", "ifsc": "UTIB0000373", "user_consent": "true", }, format="json", ) self.assertEqual(res.status_code, status.HTTP_200_OK) self.assertTrue(res.data["is_success"]) self.assertEqual(res.data["seller_id"], "seller_1001") self.assertEqual(res.data["beneficiary_name"], "MANOJ PRASAD") self.assertEqual(res.data["account_ifsc"], "UTIB0000373") self.assertEqual(res.data["tranx_id"], "9eeb9d58-04f8-4f8b-9608-99731850993f") # Verify DB persistence in SellerBankVerification table with seller_id as primary key record = SellerBankVerification.objects.get(seller_id="seller_1001") self.assertEqual(record.seller_id, "seller_1001") self.assertEqual(record.account_number, "123456787270") self.assertEqual(record.ifsc, "UTIB0000373") self.assertEqual(record.account_ifsc, "UTIB0000373") self.assertEqual(record.beneficiary_name, "MANOJ PRASAD") self.assertTrue(record.is_valid) self.assertTrue(record.account_exists) self.assertIsNone(record.failure_reason) self.assertIsNone(record.npci_error_code) self.assertEqual(record.tranx_id, "9eeb9d58-04f8-4f8b-9608-99731850993f") self.assertEqual(record.timestamp, "2026-05-22T05:10:43.561554+00:00") self.assertEqual(record.user_consent, "true") self.assertEqual(record.vendor_name, "codeplus") self.assertEqual(record.status, "SUCCESS") self.assertGreaterEqual(record.latency_ms, 0.0)