from django.test import TestCase from rest_framework.test import APIClient from unittest.mock import patch, MagicMock from api.typesense_client import ( TypesenseService, safe_str, safe_int, safe_float, safe_bool, ) class TypesenseServiceUnitTests(TestCase): def test_safe_helpers(self): self.assertEqual(safe_str(None, "default"), "default") self.assertEqual(safe_str(123), "123") self.assertEqual(safe_int(None, 10), 10) self.assertEqual(safe_int("42"), 42) self.assertEqual(safe_int("invalid", 5), 5) self.assertEqual(safe_float(None, 3.14), 3.14) self.assertEqual(safe_float("99.95"), 99.95) self.assertEqual(safe_float("invalid", 0.0), 0.0) self.assertTrue(safe_bool("true")) self.assertTrue(safe_bool("1")) self.assertFalse(safe_bool("false")) self.assertFalse(safe_bool("0")) self.assertFalse(safe_bool(None, False)) def test_prepare_product_doc_handles_nulls_gracefully(self): raw_product = { 'id': 101, 'sku': 'TRDX-TEST-001', 'title': 'Test Silk Saree', 'description': None, 'category': None, 'supplier_id': None, 'supplier_name': None, 'price': None, 'compare_at_price': None, 'stock': None, 'image': None, 'is_gi_tagged': None, 'is_active': None, 'status': None, 'created_at': None } doc = TypesenseService.prepare_product_doc(raw_product) self.assertEqual(doc['id'], '101') self.assertEqual(doc['sku'], 'TRDX-TEST-001') self.assertEqual(doc['title'], 'Test Silk Saree') self.assertEqual(doc['description'], '') self.assertEqual(doc['category'], 'Uncategorized') self.assertEqual(doc['supplier_id'], 0) self.assertEqual(doc['price'], 0.0) self.assertEqual(doc['compare_at_price'], 0.0) self.assertEqual(doc['stock'], 0) self.assertFalse(doc['is_gi_tagged']) self.assertTrue(doc['is_active']) self.assertEqual(doc['status'], 'active') self.assertEqual(doc['created_at'], 0) class SearchAndIndexApiTests(TestCase): def setUp(self): self.client = APIClient() @patch.object(TypesenseService, 'search_products') def test_search_view_defaults(self, mock_search): mock_search.return_value = { 'hits': [{'document': {'id': '1', 'title': 'Product 1'}}], 'found': 1, 'facet_counts': [] } response = self.client.get('/api/search/', {'q': 'saree'}) self.assertEqual(response.status_code, 200) self.assertEqual(response.data['count'], 1) self.assertEqual(len(response.data['results']), 1) mock_search.assert_called_once() _, kwargs = mock_search.call_args self.assertEqual(kwargs['query'], 'saree') self.assertIn('is_active:=true', kwargs['filter_by']) self.assertEqual(kwargs['sort_by'], '_text_match:desc,created_at:desc') @patch.object(TypesenseService, 'search_products') def test_search_view_filters_and_sorting(self, mock_search): mock_search.return_value = {'hits': [], 'found': 0, 'facet_counts': []} response = self.client.get('/api/search/', { 'q': '*', 'category': 'Textiles & Silk', 'min_price': '100', 'max_price': '500', 'is_gi_tagged': 'false', 'supplier_id': '42', 'sort_by': 'price_asc', 'page': '2', 'per_page': '10' }) self.assertEqual(response.status_code, 200) _, kwargs = mock_search.call_args self.assertIn('category:="Textiles & Silk"', kwargs['filter_by']) self.assertIn('price:[100.0..500.0]', kwargs['filter_by']) self.assertIn('is_gi_tagged:=false', kwargs['filter_by']) self.assertIn('supplier_id:=42', kwargs['filter_by']) self.assertEqual(kwargs['sort_by'], 'price:asc') self.assertEqual(kwargs['page'], 2) self.assertEqual(kwargs['per_page'], 10) @patch.object(TypesenseService, 'index_product') def test_index_single_product(self, mock_index): mock_index.return_value = {'success': True} payload = {'id': 1, 'sku': 'SKU123', 'title': 'Test Item', 'price': 99.0} response = self.client.post('/api/index/product/', payload, format='json') self.assertEqual(response.status_code, 200) self.assertEqual(response.data['status'], 'indexed') @patch.object(TypesenseService, 'index_products_batch') def test_index_batch_products(self, mock_batch): mock_batch.return_value = [{'success': True}, {'success': True}] payload = [ {'id': 1, 'sku': 'SKU1', 'title': 'Item 1'}, {'id': 2, 'sku': 'SKU2', 'title': 'Item 2'} ] response = self.client.post('/api/index/product/', payload, format='json') self.assertEqual(response.status_code, 200) self.assertEqual(response.data['count'], 2) @patch.object(TypesenseService, 'delete_product') def test_delete_product(self, mock_delete): mock_delete.return_value = {'deleted': True} response = self.client.delete('/api/index/product/TRDX-001/') self.assertEqual(response.status_code, 200) self.assertEqual(response.data['status'], 'deleted') mock_delete.assert_called_once_with('TRDX-001')