[30388f42] feat: Add automated test infrastructure for core services
This commit is contained in:
@@ -4,7 +4,8 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>B2B Marketing Assistant powered by Gemini</title>
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🚀</text></svg>">
|
||||||
|
<title>B2B Marketing Assistant</title>
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<script type="importmap">
|
<script type="importmap">
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,3 +3,5 @@ requests
|
|||||||
beautifulsoup4
|
beautifulsoup4
|
||||||
lxml
|
lxml
|
||||||
python-dotenv
|
python-dotenv
|
||||||
|
pytest
|
||||||
|
httpx
|
||||||
3
b2b-marketing-assistant/run_tests.sh
Normal file
3
b2b-marketing-assistant/run_tests.sh
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
export PYTHONPATH=$PYTHONPATH:/app
|
||||||
|
python3 -m pytest -v /app/tests/test_orchestrator.py
|
||||||
57
b2b-marketing-assistant/tests/test_orchestrator.py
Normal file
57
b2b-marketing-assistant/tests/test_orchestrator.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import pytest
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from unittest.mock import patch, MagicMock
|
||||||
|
|
||||||
|
# Add current dir to sys.path
|
||||||
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
import b2b_marketing_orchestrator as orchestrator
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_gemini_response():
|
||||||
|
return """## Schritt 1: Angebot (WAS)
|
||||||
|
|
||||||
|
Kurzresuemee:
|
||||||
|
* Test Summary 1
|
||||||
|
* Test Summary 2
|
||||||
|
|
||||||
|
| Produkt/Loesung | Beschreibung | Kernfunktionen | Differenzierung | Primaere Quelle (URL) |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| Test Bot | A great robot | AI | Faster | https://example.com/bot |
|
||||||
|
"""
|
||||||
|
|
||||||
|
@patch("b2b_marketing_orchestrator.load_api_key", return_value="fake_key")
|
||||||
|
@patch("b2b_marketing_orchestrator.get_text_from_url", return_value="<html><body>Test Content</body></html>")
|
||||||
|
@patch("b2b_marketing_orchestrator.find_relevant_links", return_value=[])
|
||||||
|
@patch("b2b_marketing_orchestrator.call_gemini_api")
|
||||||
|
def test_start_generation(mock_call, mock_links, mock_scrape, mock_key, mock_gemini_response):
|
||||||
|
mock_call.return_value = mock_gemini_response
|
||||||
|
|
||||||
|
result = orchestrator.start_generation("https://example.com", "de", "DACH", "Cleaning")
|
||||||
|
|
||||||
|
assert "offer" in result
|
||||||
|
assert result["offer"]["headers"] == ["Produkt/Loesung", "Beschreibung", "Kernfunktionen", "Differenzierung", "Primaere Quelle (URL)"]
|
||||||
|
assert len(result["offer"]["rows"]) == 1
|
||||||
|
assert result["offer"]["rows"][0][0] == "Test Bot"
|
||||||
|
# Note: Orchestrator uses a hardcoded summary for Step 1 in its return dict
|
||||||
|
assert "Angebotsanalyse" in result["offer"]["summary"][0]
|
||||||
|
|
||||||
|
def test_parse_markdown_table():
|
||||||
|
md = """
|
||||||
|
| Header 1 | Header 2 |
|
||||||
|
|---|---|
|
||||||
|
| Val 1 | Val 2 |
|
||||||
|
"""
|
||||||
|
result = orchestrator.parse_markdown_table(md)
|
||||||
|
assert result["headers"] == ["Header 1", "Header 2"]
|
||||||
|
assert result["rows"] == [["Val 1", "Val 2"]]
|
||||||
|
|
||||||
|
@patch("b2b_marketing_orchestrator.load_api_key", return_value="fake_key")
|
||||||
|
@patch("b2b_marketing_orchestrator.call_gemini_api", return_value="Test Product, Nice Bot, AI, Best, https://test.com")
|
||||||
|
def test_enrich_product(mock_call, mock_key):
|
||||||
|
result = orchestrator.enrich_product("Test Product", "https://test.com", "de")
|
||||||
|
assert len(result) == 5
|
||||||
|
assert result[0] == "Test Product"
|
||||||
|
assert result[1] == "Nice Bot"
|
||||||
0
company-explorer/backend/tests/__init__.py
Normal file
0
company-explorer/backend/tests/__init__.py
Normal file
107
company-explorer/backend/tests/test_api_integration.py
Normal file
107
company-explorer/backend/tests/test_api_integration.py
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import pytest
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
from sqlalchemy.pool import StaticPool
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Add backend to sys.path to resolve imports
|
||||||
|
sys.path.append(os.path.join(os.path.dirname(__file__), "../.."))
|
||||||
|
|
||||||
|
from backend.app import app, get_db, authenticate_user
|
||||||
|
from backend.database import Base
|
||||||
|
|
||||||
|
# --- Test Database Setup ---
|
||||||
|
SQLALCHEMY_DATABASE_URL = "sqlite:///:memory:"
|
||||||
|
|
||||||
|
engine = create_engine(
|
||||||
|
SQLALCHEMY_DATABASE_URL,
|
||||||
|
connect_args={"check_same_thread": False},
|
||||||
|
poolclass=StaticPool,
|
||||||
|
)
|
||||||
|
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||||
|
|
||||||
|
def override_get_db():
|
||||||
|
try:
|
||||||
|
db = TestingSessionLocal()
|
||||||
|
yield db
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
# Override Auth to bypass password check for tests
|
||||||
|
def override_authenticate_user():
|
||||||
|
return "test_user"
|
||||||
|
|
||||||
|
app.dependency_overrides[get_db] = override_get_db
|
||||||
|
app.dependency_overrides[authenticate_user] = override_authenticate_user
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def client():
|
||||||
|
# Create tables
|
||||||
|
Base.metadata.create_all(bind=engine)
|
||||||
|
with TestClient(app) as c:
|
||||||
|
yield c
|
||||||
|
# Drop tables
|
||||||
|
Base.metadata.drop_all(bind=engine)
|
||||||
|
|
||||||
|
# --- Tests ---
|
||||||
|
|
||||||
|
def test_health_check(client):
|
||||||
|
response = client.get("/api/health")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json()["status"] == "ok"
|
||||||
|
|
||||||
|
@patch("backend.app.run_discovery_task") # Mock the background task function directly
|
||||||
|
def test_provision_new_company(mock_discovery_task, client):
|
||||||
|
# Data simulating a webhook from Connector
|
||||||
|
payload = {
|
||||||
|
"so_contact_id": 12345,
|
||||||
|
"so_person_id": 999,
|
||||||
|
"crm_name": "Test Firma GmbH",
|
||||||
|
"crm_website": "www.test-firma.de",
|
||||||
|
"job_title": "CTO",
|
||||||
|
"campaign_tag": "standard"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Call the API
|
||||||
|
response = client.post("/api/provision/superoffice-contact", json=payload)
|
||||||
|
|
||||||
|
# Assertions
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert data["company_name"] == "Test Firma GmbH"
|
||||||
|
|
||||||
|
# Verify Background Task was triggered
|
||||||
|
# Note: FastAPI TestClient runs background tasks synchronously by default!
|
||||||
|
# So patching the function in app.py is the right way to intercept it.
|
||||||
|
mock_discovery_task.assert_called()
|
||||||
|
|
||||||
|
def test_create_contact_manual(client):
|
||||||
|
# First create a company to attach contact to
|
||||||
|
# We can use the DB session directly or the API
|
||||||
|
from backend.database import Company
|
||||||
|
db = TestingSessionLocal()
|
||||||
|
company = Company(name="Manual Corp", status="NEW")
|
||||||
|
db.add(company)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(company)
|
||||||
|
company_id = company.id
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"company_id": company_id,
|
||||||
|
"first_name": "Hans",
|
||||||
|
"last_name": "Tester",
|
||||||
|
"email": "hans@manual.corp",
|
||||||
|
"job_title": "CEO",
|
||||||
|
"role": "Wirtschaftlicher Entscheider",
|
||||||
|
"is_primary": True
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post("/api/contacts", json=payload)
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert data["email"] == "hans@manual.corp"
|
||||||
|
assert data["role"] == "Wirtschaftlicher Entscheider"
|
||||||
@@ -3,7 +3,8 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Company Explorer (Robotics)</title>
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🤖</text></svg>">
|
||||||
|
<title>Company Explorer</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-slate-950 text-slate-100">
|
<body class="bg-slate-950 text-slate-100">
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -10,3 +10,5 @@ thefuzz
|
|||||||
wikipedia
|
wikipedia
|
||||||
pydantic
|
pydantic
|
||||||
pydantic-settings
|
pydantic-settings
|
||||||
|
pytest
|
||||||
|
httpx
|
||||||
3
company-explorer/run_tests.sh
Normal file
3
company-explorer/run_tests.sh
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
export PYTHONPATH=$PYTHONPATH:/app
|
||||||
|
python3 -m pytest -v /app/backend/tests/test_api_integration.py
|
||||||
@@ -4,7 +4,8 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>B2B Competitor Analysis Agent</title>
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>⚔️</text></svg>">
|
||||||
|
<title>Competitor Analysis Agent</title>
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<script>
|
<script>
|
||||||
tailwind.config = {
|
tailwind.config = {
|
||||||
|
|||||||
@@ -8,3 +8,5 @@ fastapi
|
|||||||
uvicorn
|
uvicorn
|
||||||
schedule
|
schedule
|
||||||
sqlalchemy
|
sqlalchemy
|
||||||
|
pytest
|
||||||
|
httpx
|
||||||
|
|||||||
3
connector-superoffice/run_tests.sh
Normal file
3
connector-superoffice/run_tests.sh
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
export PYTHONPATH=$PYTHONPATH:/app
|
||||||
|
python3 -m pytest -v /app/tests/test_webhook.py
|
||||||
46
connector-superoffice/tests/test_webhook.py
Normal file
46
connector-superoffice/tests/test_webhook.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import pytest
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Resolve paths
|
||||||
|
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
|
||||||
|
|
||||||
|
# Mock environment before importing app
|
||||||
|
with patch.dict(os.environ, {"WEBHOOK_TOKEN": "test_secret_token"}):
|
||||||
|
from webhook_app import app, queue
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
|
||||||
|
def test_health_check():
|
||||||
|
response = client.get("/health")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() == {"status": "ok"}
|
||||||
|
|
||||||
|
def test_webhook_invalid_token():
|
||||||
|
response = client.post("/webhook?token=wrong_token", json={"Event": "test"})
|
||||||
|
assert response.status_code == 403
|
||||||
|
|
||||||
|
@patch("webhook_app.queue.add_job")
|
||||||
|
def test_webhook_valid_token(mock_add_job):
|
||||||
|
payload = {
|
||||||
|
"Event": "contact.created",
|
||||||
|
"PrimaryKey": 123,
|
||||||
|
"Name": "Test Company"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post("/webhook?token=test_secret_token", json=payload)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() == {"status": "queued"}
|
||||||
|
|
||||||
|
# Verify job was added to queue
|
||||||
|
mock_add_job.assert_called_once_with("contact.created", payload)
|
||||||
|
|
||||||
|
def test_stats_endpoint():
|
||||||
|
# Mock get_stats result
|
||||||
|
with patch.object(queue, 'get_stats', return_value={"PENDING": 5, "COMPLETED": 10}):
|
||||||
|
response = client.get("/stats")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() == {"PENDING": 5, "COMPLETED": 10}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>✍️</text></svg>">
|
||||||
<title>Content Engine</title>
|
<title>Content Engine</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
43
docs/TESTING.md
Normal file
43
docs/TESTING.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# Testing & Qualitätssicherung
|
||||||
|
|
||||||
|
Dieses Projekt nutzt automatisierte Integrationstests, um die Stabilität der Microservices sicherzustellen. Alle Tests sind so konzipiert, dass sie **innerhalb** der Docker-Container laufen und externe Abhängigkeiten (KI-APIs) mocken.
|
||||||
|
|
||||||
|
## 🚀 Alle Tests ausführen
|
||||||
|
|
||||||
|
Führen Sie die folgenden Befehle auf dem Host-System aus, um die jeweilige Test-Suite zu starten:
|
||||||
|
|
||||||
|
### 1. Company Explorer (Intelligence Core)
|
||||||
|
```bash
|
||||||
|
docker exec company-explorer /app/run_tests.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. SuperOffice Connector (CRM Sync)
|
||||||
|
```bash
|
||||||
|
docker exec connector-superoffice /app/run_tests.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Lead Engine (Trading Twins)
|
||||||
|
```bash
|
||||||
|
docker exec lead-engine /app/run_tests.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. B2B Marketing Assistant (Analysis)
|
||||||
|
```bash
|
||||||
|
docker exec b2b-marketing-assistant /app/run_tests.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ Test-Architektur
|
||||||
|
|
||||||
|
* **Framework:** `pytest`
|
||||||
|
* **API-Testing:** `httpx` & `FastAPI TestClient`
|
||||||
|
* **Datenbank:** SQLite In-Memory (oder temporäre Datei-DB)
|
||||||
|
* **Mocking:** `unittest.mock` wird verwendet, um Aufrufe an Gemini/OpenAI zu simulieren.
|
||||||
|
|
||||||
|
## 📝 Neue Tests hinzufügen
|
||||||
|
|
||||||
|
Um neue Tests für einen Dienst zu erstellen:
|
||||||
|
1. Erstellen Sie eine Datei in `service-name/tests/test_*.py`.
|
||||||
|
2. Stellen Sie sicher, dass das `run_tests.sh` Skript im Dienst-Ordner diese Datei inkludiert.
|
||||||
|
3. Kopieren Sie die Datei ggf. in den Container (`docker cp`).
|
||||||
@@ -3,7 +3,8 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>parcelLab Market Intelligence</title>
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>📊</text></svg>">
|
||||||
|
<title>Market Intelligence</title>
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Roboplanet GTM Architect</title>
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🏛️</text></svg>">
|
||||||
|
<title>GTM Architect</title>
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🗺️</text></svg>">
|
||||||
<title>Heatmap Tool</title>
|
<title>Heatmap Tool</title>
|
||||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
||||||
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
|
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
|
||||||
|
|||||||
@@ -6,3 +6,5 @@ fastapi
|
|||||||
uvicorn[standard]
|
uvicorn[standard]
|
||||||
msal
|
msal
|
||||||
sqlalchemy
|
sqlalchemy
|
||||||
|
pytest
|
||||||
|
httpx
|
||||||
3
lead-engine/run_tests.sh
Normal file
3
lead-engine/run_tests.sh
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
export PYTHONPATH=$PYTHONPATH:/app
|
||||||
|
python3 -m pytest -v /app/tests/test_lead_engine.py
|
||||||
83
lead-engine/tests/test_lead_engine.py
Normal file
83
lead-engine/tests/test_lead_engine.py
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import pytest
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Resolve paths
|
||||||
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
if BASE_DIR not in sys.path:
|
||||||
|
sys.path.append(BASE_DIR)
|
||||||
|
|
||||||
|
from trading_twins.manager import app, SessionLocal
|
||||||
|
from trading_twins.models import Base, ProposalJob
|
||||||
|
|
||||||
|
# --- Test Database Setup ---
|
||||||
|
# Use a local file for testing to ensure visibility across sessions
|
||||||
|
TEST_DB_FILE = "test_trading_twins.db"
|
||||||
|
SQLALCHEMY_DATABASE_URL = f"sqlite:///./{TEST_DB_FILE}"
|
||||||
|
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
|
||||||
|
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module", autouse=True)
|
||||||
|
def setup_database():
|
||||||
|
# Create tables
|
||||||
|
Base.metadata.create_all(bind=engine)
|
||||||
|
yield
|
||||||
|
# Cleanup
|
||||||
|
Base.metadata.drop_all(bind=engine)
|
||||||
|
if os.path.exists(TEST_DB_FILE):
|
||||||
|
os.remove(TEST_DB_FILE)
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def client():
|
||||||
|
# Patch the SessionLocal in manager.py to use our test DB
|
||||||
|
with patch("trading_twins.manager.SessionLocal", TestingSessionLocal):
|
||||||
|
with TestClient(app) as c:
|
||||||
|
yield c
|
||||||
|
|
||||||
|
def test_trigger_test_lead(client):
|
||||||
|
with patch("trading_twins.manager.process_lead") as mock_process:
|
||||||
|
response = client.get("/test_lead")
|
||||||
|
assert response.status_code == 202
|
||||||
|
assert "Test lead triggered" in response.json()["status"]
|
||||||
|
mock_process.assert_called()
|
||||||
|
|
||||||
|
def test_stop_job(client):
|
||||||
|
db = TestingSessionLocal()
|
||||||
|
job = ProposalJob(job_uuid="test_uuid_1", customer_email="test@example.com", status="pending")
|
||||||
|
db.add(job)
|
||||||
|
db.commit()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
response = client.get("/stop/test_uuid_1")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.text == "Gestoppt."
|
||||||
|
|
||||||
|
db = TestingSessionLocal()
|
||||||
|
updated_job = db.query(ProposalJob).filter(ProposalJob.job_uuid == "test_uuid_1").first()
|
||||||
|
assert updated_job.status == "cancelled"
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
def test_send_now(client):
|
||||||
|
db = TestingSessionLocal()
|
||||||
|
job = ProposalJob(job_uuid="test_uuid_2", customer_email="test@example.com", status="pending")
|
||||||
|
db.add(job)
|
||||||
|
db.commit()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
response = client.get("/send_now/test_uuid_2")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.text == "Wird gesendet."
|
||||||
|
|
||||||
|
db = TestingSessionLocal()
|
||||||
|
updated_job = db.query(ProposalJob).filter(ProposalJob.job_uuid == "test_uuid_2").first()
|
||||||
|
assert updated_job.status == "send_now"
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
def test_book_slot_fail_no_job(client):
|
||||||
|
response = client.get("/book_slot/invalid_uuid/123456789")
|
||||||
|
assert response.status_code == 400
|
||||||
|
assert response.text == "Fehler."
|
||||||
48
test_suite.sh
Normal file
48
test_suite.sh
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# --- GLOBAL TEST RUNNER ---
|
||||||
|
# Runs tests for all integrated services
|
||||||
|
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
run_container_tests() {
|
||||||
|
local container=$1
|
||||||
|
local test_dir=$2
|
||||||
|
local test_file=$3
|
||||||
|
|
||||||
|
echo -e "\n${GREEN}>>> Testing Service: $container <<<${NC}"
|
||||||
|
|
||||||
|
# 1. Install dependencies
|
||||||
|
docker exec $container pip install pytest httpx > /dev/null 2>&1
|
||||||
|
|
||||||
|
# 2. Copy tests and runner to container
|
||||||
|
docker cp $test_dir/tests/. $container:/app/tests/
|
||||||
|
docker cp $test_dir/run_tests.sh $container:/app/run_tests.sh
|
||||||
|
|
||||||
|
# 3. Execute
|
||||||
|
docker exec $container chmod +x /app/run_tests.sh
|
||||||
|
docker exec $container /app/run_tests.sh
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo -e "${GREEN}SUCCESS: $container tests passed.${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}FAILURE: $container tests failed.${NC}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 1. Company Explorer
|
||||||
|
run_container_tests "company-explorer" "company-explorer/backend" "test_api_integration.py"
|
||||||
|
|
||||||
|
# 2. Connector
|
||||||
|
run_container_tests "connector-superoffice" "connector-superoffice" "test_webhook.py"
|
||||||
|
|
||||||
|
# 3. Lead Engine
|
||||||
|
run_container_tests "lead-engine" "lead-engine" "test_lead_engine.py"
|
||||||
|
|
||||||
|
# 4. B2B Marketing Assistant
|
||||||
|
run_container_tests "b2b-marketing-assistant" "b2b-marketing-assistant" "test_orchestrator.py"
|
||||||
|
|
||||||
|
echo -e "\n${GREEN}=== Test Suite Finished ===${NC}"
|
||||||
|
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎙️</text></svg>">
|
||||||
<title>Meeting Assistant</title>
|
<title>Meeting Assistant</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-slate-50 dark:bg-slate-950">
|
<body class="bg-slate-50 dark:bg-slate-950">
|
||||||
|
|||||||
Reference in New Issue
Block a user