dhisana 0.0.1.dev220__tar.gz → 0.0.1.dev222__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/PKG-INFO +1 -1
  2. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/setup.py +1 -1
  3. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/schemas/sales.py +1 -0
  4. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/apollo_tools.py +14 -2
  5. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/test_connect.py +63 -0
  6. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana.egg-info/PKG-INFO +1 -1
  7. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana.egg-info/SOURCES.txt +1 -0
  8. dhisana-0.0.1.dev222/tests/test_apollo_lead_search.py +119 -0
  9. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/README.md +0 -0
  10. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/pyproject.toml +0 -0
  11. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/setup.cfg +0 -0
  12. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/__init__.py +0 -0
  13. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/cli/__init__.py +0 -0
  14. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/cli/cli.py +0 -0
  15. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/cli/datasets.py +0 -0
  16. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/cli/models.py +0 -0
  17. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/cli/predictions.py +0 -0
  18. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/schemas/__init__.py +0 -0
  19. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/schemas/common.py +0 -0
  20. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/ui/__init__.py +0 -0
  21. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/ui/components.py +0 -0
  22. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/__init__.py +0 -0
  23. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/add_mapping.py +0 -0
  24. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/agent_tools.py +0 -0
  25. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/assistant_tool_tag.py +0 -0
  26. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/built_with_api_tools.py +0 -0
  27. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/cache_output_tools.py +0 -0
  28. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/cache_output_tools_local.py +0 -0
  29. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/check_email_validity_tools.py +0 -0
  30. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/check_for_intent_signal.py +0 -0
  31. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/check_linkedin_url_validity.py +0 -0
  32. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/clay_tools.py +0 -0
  33. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/clean_properties.py +0 -0
  34. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/company_utils.py +0 -0
  35. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/compose_salesnav_query.py +0 -0
  36. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/compose_search_query.py +0 -0
  37. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/compose_three_step_workflow.py +0 -0
  38. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/composite_tools.py +0 -0
  39. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/dataframe_tools.py +0 -0
  40. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/domain_parser.py +0 -0
  41. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/email_parse_helpers.py +0 -0
  42. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/email_provider.py +0 -0
  43. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/enrich_lead_information.py +0 -0
  44. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/extract_email_content_for_llm.py +0 -0
  45. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/fetch_openai_config.py +0 -0
  46. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/field_validators.py +0 -0
  47. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/g2_tools.py +0 -0
  48. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/generate_content.py +0 -0
  49. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/generate_email.py +0 -0
  50. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/generate_email_response.py +0 -0
  51. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/generate_flow.py +0 -0
  52. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/generate_leads_salesnav.py +0 -0
  53. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/generate_linkedin_connect_message.py +0 -0
  54. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/generate_linkedin_response_message.py +0 -0
  55. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/generate_structured_output_internal.py +0 -0
  56. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/google_custom_search.py +0 -0
  57. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/google_oauth_tools.py +0 -0
  58. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/google_workspace_tools.py +0 -0
  59. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/hubspot_clearbit.py +0 -0
  60. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/hubspot_crm_tools.py +0 -0
  61. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/instantly_tools.py +0 -0
  62. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/linkedin_crawler.py +0 -0
  63. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/lusha_tools.py +0 -0
  64. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/mailgun_tools.py +0 -0
  65. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/microsoft365_tools.py +0 -0
  66. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/openai_assistant_and_file_utils.py +0 -0
  67. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/openai_helpers.py +0 -0
  68. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/openapi_spec_to_tools.py +0 -0
  69. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/openapi_tool/__init__.py +0 -0
  70. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/openapi_tool/api_models.py +0 -0
  71. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/openapi_tool/convert_openai_spec_to_tool.py +0 -0
  72. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/openapi_tool/openapi_tool.py +0 -0
  73. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/parse_linkedin_messages_txt.py +0 -0
  74. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/profile.py +0 -0
  75. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/proxy_curl_tools.py +0 -0
  76. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/proxycurl_search_leads.py +0 -0
  77. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/python_function_to_tools.py +0 -0
  78. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/research_lead.py +0 -0
  79. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/sales_navigator_crawler.py +0 -0
  80. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/salesforce_crm_tools.py +0 -0
  81. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/search_router.py +0 -0
  82. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/search_router_jobs.py +0 -0
  83. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/sendgrid_tools.py +0 -0
  84. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/serarch_router_local_business.py +0 -0
  85. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/serpapi_additional_tools.py +0 -0
  86. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/serpapi_google_jobs.py +0 -0
  87. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/serpapi_google_search.py +0 -0
  88. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/serpapi_local_business_search.py +0 -0
  89. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/serpapi_search_tools.py +0 -0
  90. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/serperdev_google_jobs.py +0 -0
  91. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/serperdev_local_business.py +0 -0
  92. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/serperdev_search.py +0 -0
  93. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/smtp_email_tools.py +0 -0
  94. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/trasform_json.py +0 -0
  95. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/web_download_parse_tools.py +0 -0
  96. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/workflow_code_model.py +0 -0
  97. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/utils/zoominfo_tools.py +0 -0
  98. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/workflow/__init__.py +0 -0
  99. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/workflow/agent.py +0 -0
  100. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/workflow/flow.py +0 -0
  101. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/workflow/task.py +0 -0
  102. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana/workflow/test.py +0 -0
  103. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana.egg-info/dependency_links.txt +0 -0
  104. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana.egg-info/entry_points.txt +0 -0
  105. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana.egg-info/requires.txt +0 -0
  106. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/src/dhisana.egg-info/top_level.txt +0 -0
  107. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/tests/test_agent_tools.py +0 -0
  108. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/tests/test_apollo_company_search.py +0 -0
  109. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/tests/test_connectivity.py +0 -0
  110. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/tests/test_google_document.py +0 -0
  111. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/tests/test_hubspot_call_logs.py +0 -0
  112. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/tests/test_linkedin_serper.py +0 -0
  113. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/tests/test_mcp_connectivity.py +0 -0
  114. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/tests/test_proxycurl_get_company_search_id.py +0 -0
  115. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/tests/test_proxycurl_job_count.py +0 -0
  116. {dhisana-0.0.1.dev220 → dhisana-0.0.1.dev222}/tests/test_structured_output_with_mcp.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dhisana
3
- Version: 0.0.1.dev220
3
+ Version: 0.0.1.dev222
4
4
  Summary: A Python SDK for Dhisana AI Platform
5
5
  Home-page: https://github.com/dhisana-ai/dhisana-python-sdk
6
6
  Author: Admin
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name='dhisana',
5
- version='0.0.1-dev220',
5
+ version='0.0.1-dev222',
6
6
  description='A Python SDK for Dhisana AI Platform',
7
7
  author='Admin',
8
8
  author_email='contact@dhisana.ai',
@@ -49,6 +49,7 @@ class Lead(BaseModel):
49
49
  engaged: bool = False
50
50
  last_contact: Optional[int] = None
51
51
  research_summary: Optional[str] = None
52
+ task_ids: Optional[List[str]] = None
52
53
  email_validation_status: Optional[str] = None
53
54
  linkedin_validation_status: Optional[str] = None
54
55
  research_status: Optional[str] = None
@@ -573,7 +573,7 @@ async def search_leads_with_apollo(
573
573
  # Important: handle personNotTitles as well
574
574
  "personNotTitles": "person_not_titles",
575
575
 
576
- "qOrganizationJobTitles": "q_keywords",
576
+ "qOrganizationJobTitles": "q_organization_job_titles",
577
577
  "sortAscending": "sort_ascending",
578
578
  "sortByField": "sort_by_field",
579
579
  "contactEmailStatusV2": "contact_email_status",
@@ -646,6 +646,8 @@ async def search_leads_with_apollo(
646
646
  "organization_ids",
647
647
  "organization_num_employees_ranges",
648
648
  "person_not_titles", # <--- added so single item is forced into list
649
+ "q_organization_job_titles",
650
+ "organization_latest_funding_stage_cd",
649
651
  ):
650
652
  if isinstance(final_value, str):
651
653
  final_value = [final_value]
@@ -675,6 +677,10 @@ async def search_leads_with_apollo(
675
677
  "page": 1,
676
678
  "per_page": min(max_items, 100),
677
679
  }
680
+ if query.job_openings_with_titles:
681
+ dynamic_payload["q_organization_job_titles"] = query.job_openings_with_titles
682
+ if query.latest_funding_stages:
683
+ dynamic_payload["organization_latest_funding_stage_cd"] = query.latest_funding_stages
678
684
  if query.sort_by_field is not None:
679
685
  dynamic_payload["sort_by_field"] = query.sort_by_field
680
686
  if query.sort_ascending is not None:
@@ -785,7 +791,7 @@ async def search_leads_with_apollo_page(
785
791
  "organizationNumEmployeesRanges": "organization_num_employees_ranges",
786
792
  "personTitles": "person_titles",
787
793
  "personNotTitles": "person_not_titles",
788
- "qOrganizationJobTitles": "q_keywords",
794
+ "qOrganizationJobTitles": "q_organization_job_titles",
789
795
  "sortAscending": "sort_ascending",
790
796
  "sortByField": "sort_by_field",
791
797
  "contactEmailStatusV2": "contact_email_status",
@@ -843,6 +849,8 @@ async def search_leads_with_apollo_page(
843
849
  "organization_ids",
844
850
  "organization_num_employees_ranges",
845
851
  "person_not_titles",
852
+ "q_organization_job_titles",
853
+ "organization_latest_funding_stage_cd",
846
854
  ):
847
855
  if isinstance(final_value, str):
848
856
  final_value = [final_value]
@@ -866,6 +874,10 @@ async def search_leads_with_apollo_page(
866
874
  or [f"{query.min_employees_in_organization or 1},{query.max_employees_in_organization or 1000}"]
867
875
  ),
868
876
  }
877
+ if query.job_openings_with_titles:
878
+ dynamic_payload["q_organization_job_titles"] = query.job_openings_with_titles
879
+ if query.latest_funding_stages:
880
+ dynamic_payload["organization_latest_funding_stage_cd"] = query.latest_funding_stages
869
881
  if query.sort_by_field is not None:
870
882
  dynamic_payload["sort_by_field"] = query.sort_by_field
871
883
  if query.sort_ascending is not None:
@@ -296,6 +296,68 @@ async def test_proxycurl(api_key: str) -> Dict[str, Any]:
296
296
  return {"success": False, "status_code": 0, "error_message": str(e)}
297
297
 
298
298
 
299
+ async def test_exa(api_key: str) -> Dict[str, Any]:
300
+ """Verify Exa connectivity by issuing a minimal search request."""
301
+
302
+ url = "https://api.exa.ai/search"
303
+ headers = {
304
+ "x-api-key": api_key,
305
+ "Content-Type": "application/json",
306
+ "Accept": "application/json",
307
+ }
308
+ payload = {
309
+ "query": "Dhisana connectivity check",
310
+ "numResults": 1,
311
+ }
312
+
313
+ try:
314
+ timeout = aiohttp.ClientTimeout(total=10)
315
+ async with aiohttp.ClientSession(timeout=timeout) as session:
316
+ async with session.post(url, headers=headers, json=payload) as response:
317
+ status = response.status
318
+ data = await safe_json(response)
319
+
320
+ if status != 200:
321
+ err_message = None
322
+ if isinstance(data, dict):
323
+ err_message = (
324
+ data.get("message")
325
+ or data.get("error")
326
+ or data.get("detail")
327
+ )
328
+ if isinstance(err_message, dict):
329
+ err_message = err_message.get("message") or str(err_message)
330
+ return {
331
+ "success": False,
332
+ "status_code": status,
333
+ "error_message": err_message or f"Non-200 from Exa: {status}",
334
+ }
335
+
336
+ if isinstance(data, dict):
337
+ if "error" in data:
338
+ error_value = data["error"]
339
+ if isinstance(error_value, dict):
340
+ error_value = error_value.get("message") or str(error_value)
341
+ return {
342
+ "success": False,
343
+ "status_code": status,
344
+ "error_message": str(error_value),
345
+ }
346
+
347
+ results = data.get("results")
348
+ if isinstance(results, list):
349
+ return {"success": True, "status_code": status, "error_message": None}
350
+
351
+ return {
352
+ "success": False,
353
+ "status_code": status,
354
+ "error_message": "Unexpected response from Exa API.",
355
+ }
356
+ except Exception as exc:
357
+ logger.error(f"Exa test failed: {exc}")
358
+ return {"success": False, "status_code": 0, "error_message": str(exc)}
359
+
360
+
299
361
  async def test_apollo(api_key: str) -> Dict[str, Any]:
300
362
  organization_domain = 'microsoft.com'
301
363
  url = f'https://api.apollo.io/api/v1/organizations/enrich?domain={organization_domain}'
@@ -1179,6 +1241,7 @@ async def test_connectivity(tool_config: List[Dict[str, Any]]) -> Dict[str, Dict
1179
1241
  "serpapi": test_serpapi,
1180
1242
  "serperdev": test_serperdev,
1181
1243
  "proxycurl": test_proxycurl,
1244
+ "exa": test_exa,
1182
1245
  "apollo": test_apollo,
1183
1246
  "hubspot": test_hubspot,
1184
1247
  "github": test_github,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dhisana
3
- Version: 0.0.1.dev220
3
+ Version: 0.0.1.dev222
4
4
  Summary: A Python SDK for Dhisana AI Platform
5
5
  Home-page: https://github.com/dhisana-ai/dhisana-python-sdk
6
6
  Author: Admin
@@ -103,6 +103,7 @@ src/dhisana/workflow/task.py
103
103
  src/dhisana/workflow/test.py
104
104
  tests/test_agent_tools.py
105
105
  tests/test_apollo_company_search.py
106
+ tests/test_apollo_lead_search.py
106
107
  tests/test_connectivity.py
107
108
  tests/test_google_document.py
108
109
  tests/test_hubspot_call_logs.py
@@ -0,0 +1,119 @@
1
+ import pytest
2
+ from unittest.mock import AsyncMock, patch
3
+
4
+ from src.dhisana.schemas.sales import LeadsQueryFilters
5
+ from src.dhisana.utils.apollo_tools import search_leads_with_apollo_page
6
+
7
+
8
+ @pytest.fixture
9
+ def apollo_people_response():
10
+ return {
11
+ "pagination": {
12
+ "page": 1,
13
+ "per_page": 25,
14
+ "total_entries": 1,
15
+ "total_pages": 1,
16
+ },
17
+ "people": [
18
+ {
19
+ "id": "person_1",
20
+ "name": "Alex Example",
21
+ "first_name": "Alex",
22
+ "last_name": "Example",
23
+ "title": "VP Sales",
24
+ "headline": "Revenue leader",
25
+ "email": "alex@example.com",
26
+ "linkedin_url": "https://linkedin.com/in/alex-example",
27
+ "city": "San Francisco",
28
+ "state": "California",
29
+ "organization": {
30
+ "name": "Example Inc",
31
+ "primary_domain": "example.com",
32
+ "linkedin_url": "https://linkedin.com/company/example",
33
+ "website_url": "https://example.com",
34
+ "keywords": ["SaaS", "Sales"],
35
+ },
36
+ "contact": {"sanitized_phone": "+1-555-0100"},
37
+ }
38
+ ],
39
+ "contacts": [],
40
+ }
41
+
42
+
43
+ @pytest.fixture
44
+ def tool_config():
45
+ return [
46
+ {
47
+ "name": "apollo",
48
+ "configuration": [
49
+ {"name": "apiKey", "value": "test_api_key"},
50
+ ],
51
+ }
52
+ ]
53
+
54
+
55
+ @pytest.mark.asyncio
56
+ async def test_search_leads_with_apollo_page_parses_url_filters(apollo_people_response, tool_config):
57
+ example_url = (
58
+ "https://app.apollo.io/#/people?page=1&personTitles[]=VP%20Sales&"
59
+ "organizationLatestFundingStageCd[]=3&organizationLatestFundingStageCd[]=4&"
60
+ "organizationLatestFundingStageCd[]=5&qOrganizationJobTitles[]=SDR&"
61
+ "qOrganizationJobTitles[]=account%20executive"
62
+ )
63
+
64
+ with patch(
65
+ "src.dhisana.utils.apollo_tools.fetch_apollo_data",
66
+ new=AsyncMock(return_value=apollo_people_response),
67
+ ) as mock_fetch_data, patch(
68
+ "src.dhisana.utils.apollo_tools.get_apollo_access_token",
69
+ return_value="test_api_key",
70
+ ):
71
+ result = await search_leads_with_apollo_page(
72
+ query=LeadsQueryFilters(),
73
+ page=1,
74
+ per_page=25,
75
+ example_url=example_url,
76
+ tool_config=tool_config,
77
+ )
78
+
79
+ assert result["current_page"] == 1
80
+ assert result["total_entries"] == 1
81
+ assert len(result["results"]) == 1
82
+
83
+ payload = mock_fetch_data.call_args[0][3]
84
+ assert payload["q_organization_job_titles"] == ["SDR", "account executive"]
85
+ assert payload["organization_latest_funding_stage_cd"] == ["3", "4", "5"]
86
+
87
+
88
+ @pytest.mark.asyncio
89
+ async def test_search_leads_with_apollo_page_uses_query_filters(apollo_people_response, tool_config):
90
+ query = LeadsQueryFilters(
91
+ person_current_titles=["Head of Sales"],
92
+ job_openings_with_titles=["SDR", "Account Executive"],
93
+ latest_funding_stages=["3", "4"],
94
+ organization_num_employees_ranges=["201,500"],
95
+ )
96
+
97
+ with patch(
98
+ "src.dhisana.utils.apollo_tools.fetch_apollo_data",
99
+ new=AsyncMock(return_value=apollo_people_response),
100
+ ) as mock_fetch_data, patch(
101
+ "src.dhisana.utils.apollo_tools.get_apollo_access_token",
102
+ return_value="test_api_key",
103
+ ):
104
+ result = await search_leads_with_apollo_page(
105
+ query=query,
106
+ page=2,
107
+ per_page=50,
108
+ tool_config=tool_config,
109
+ )
110
+
111
+ assert result["current_page"] == 1
112
+ assert len(result["results"]) == 1
113
+
114
+ payload = mock_fetch_data.call_args[0][3]
115
+ assert payload["page"] == 2
116
+ assert payload["per_page"] == 50
117
+ assert payload["person_titles"] == ["Head of Sales"]
118
+ assert payload["q_organization_job_titles"] == ["SDR", "Account Executive"]
119
+ assert payload["organization_latest_funding_stage_cd"] == ["3", "4"]
File without changes
File without changes