build prospect intelligence platform MVP
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import json
|
||||
import threading
|
||||
import unittest
|
||||
from http.client import HTTPConnection
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
from app.main import create_server
|
||||
|
||||
|
||||
class ApiSmokeTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp = TemporaryDirectory()
|
||||
self.server = create_server("127.0.0.1", 0, self.tmp.name + "/test.db")
|
||||
self.thread = threading.Thread(target=self.server.serve_forever, daemon=True)
|
||||
self.thread.start()
|
||||
self.conn = HTTPConnection("127.0.0.1", self.server.server_port, timeout=3)
|
||||
|
||||
def tearDown(self):
|
||||
self.server.shutdown()
|
||||
self.server.server_close()
|
||||
self.thread.join(timeout=2)
|
||||
self.tmp.cleanup()
|
||||
|
||||
def request(self, method, path, payload=None):
|
||||
body = json.dumps(payload).encode() if payload is not None else None
|
||||
self.conn.request(method, path, body, {"Content-Type": "application/json"} if body else {})
|
||||
response = self.conn.getresponse()
|
||||
return response.status, json.loads(response.read())
|
||||
|
||||
def test_health_create_get_summary_and_import_preview(self):
|
||||
self.assertEqual(self.request("GET", "/api/v1/health/live")[0], 200)
|
||||
status, created = self.request("POST", "/api/v1/businesses", {"name": "Acme", "website": "https://acme.co.za", "email": "a@acme.co.za"})
|
||||
self.assertEqual(status, 201)
|
||||
self.assertEqual(created["organization_id"], "demo-tenant")
|
||||
status, fetched = self.request("GET", "/api/v1/businesses/" + str(created["id"]))
|
||||
self.assertEqual(status, 200)
|
||||
self.assertEqual(fetched["name"], "Acme")
|
||||
status, summary = self.request("GET", "/api/v1/dashboard/summary")
|
||||
self.assertEqual(status, 200)
|
||||
self.assertEqual(summary["businesses"], 1)
|
||||
status, preview = self.request("POST", "/api/v1/imports/preview", {"rows": [{"name": "Acme", "website": "https://acme.co.za"}, {"name": "New Co"}]})
|
||||
self.assertEqual(status, 200)
|
||||
self.assertEqual(preview["accepted"], 1)
|
||||
self.assertEqual(preview["duplicates"], 1)
|
||||
|
||||
def test_suppression_blocks_new_business(self):
|
||||
status, _ = self.request("POST", "/api/v1/suppressions", {"kind": "domain", "value": "blocked.co.za"})
|
||||
self.assertEqual(status, 201)
|
||||
status, response = self.request("POST", "/api/v1/businesses", {"name": "Blocked", "website": "https://blocked.co.za"})
|
||||
self.assertEqual(status, 409)
|
||||
self.assertEqual(response["error"], "suppressed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,46 @@
|
||||
import unittest
|
||||
|
||||
from app.domain import (
|
||||
classify_website,
|
||||
deduplicate_businesses,
|
||||
normalize_business,
|
||||
normalize_domain,
|
||||
score_business,
|
||||
is_suppressed,
|
||||
)
|
||||
|
||||
|
||||
class DomainTests(unittest.TestCase):
|
||||
def test_normalization_canonicalizes_contact_and_domain(self):
|
||||
value = normalize_business({"name": " Acme ", "website": "HTTPS://WWW.Acme.co.za/path", "email": " SALES@ACME.CO.ZA "})
|
||||
self.assertEqual(value["name"], "Acme")
|
||||
self.assertEqual(value["website_domain"], "acme.co.za")
|
||||
self.assertEqual(value["email"], "sales@acme.co.za")
|
||||
|
||||
def test_website_classification_is_conservative(self):
|
||||
self.assertEqual(classify_website("https://acme.co.za"), "business_site")
|
||||
self.assertEqual(classify_website("https://www.facebook.com/acme"), "social_profile")
|
||||
self.assertEqual(classify_website(""), "missing")
|
||||
|
||||
def test_scoring_is_versioned_and_explains_factors(self):
|
||||
result = score_business({"name": "Acme", "website_domain": "acme.co.za", "email": "a@acme.co.za", "phone": "123"})
|
||||
self.assertEqual(result["score_version"], "mvp-1")
|
||||
self.assertGreaterEqual(result["score"], 70)
|
||||
self.assertIn("business_site", result["factors"])
|
||||
|
||||
def test_suppression_matches_email_domain_or_phone(self):
|
||||
business = {"email": "person@example.com", "website_domain": "example.com", "phone": "+27123456789"}
|
||||
self.assertTrue(is_suppressed(business, [{"kind": "domain", "value": "example.com"}]))
|
||||
self.assertTrue(is_suppressed(business, [{"kind": "email", "value": "person@example.com"}]))
|
||||
self.assertTrue(is_suppressed(business, [{"kind": "phone", "value": "+27123456789"}]))
|
||||
self.assertFalse(is_suppressed(business, [{"kind": "email", "value": "other@example.com"}]))
|
||||
|
||||
def test_deduplication_prefers_richer_record(self):
|
||||
rows = [{"name": "Acme", "website": "https://acme.com"}, {"name": " acme ", "website": "https://www.acme.com", "email": "a@acme.com"}]
|
||||
result = deduplicate_businesses(rows)
|
||||
self.assertEqual(len(result), 1)
|
||||
self.assertEqual(result[0]["email"], "a@acme.com")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user