342 lines
15 KiB
Python
342 lines
15 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
P0 E2E VERIFICATION — Quick Add Provider Flow Test
|
|
|
|
Tests the newly refactored quick_add_provider backend service.
|
|
|
|
Payload: Add a new provider "OMV Teszt Kút", category "gas_station".
|
|
|
|
Assert 1: Query marketplace.service_providers -> Ensure "OMV Teszt Kút" exists.
|
|
Assert 2: Query fleet.organizations -> Ensure "OMV Teszt Kút" DOES NOT exist.
|
|
Assert 3: Create a dummy AssetCost linking to "OMV Teszt Kút". Ensure
|
|
service_provider_id is populated and vendor_organization_id remains NULL.
|
|
|
|
Használat:
|
|
docker compose exec sf_api python3 /app/scripts/test_quick_add_flow.py
|
|
"""
|
|
|
|
import asyncio
|
|
import logging
|
|
import sys
|
|
import uuid
|
|
from datetime import datetime, timezone
|
|
|
|
from sqlalchemy import text
|
|
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
|
|
|
|
sys.path.insert(0, '/app')
|
|
from app.core.config import settings
|
|
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format="%(asctime)s [%(levelname)s] %(message)s",
|
|
handlers=[logging.StreamHandler(sys.stdout)],
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
DATABASE_URL = settings.DATABASE_URL
|
|
|
|
# Test provider name — must be unique to avoid collisions
|
|
TEST_PROVIDER_NAME = "OMV Teszt Kút"
|
|
TEST_CATEGORY = "gas_station"
|
|
|
|
|
|
async def test_quick_add_flow():
|
|
"""
|
|
E2E test for the quick_add_provider flow.
|
|
|
|
Since we're testing at the database level (not through the API),
|
|
we simulate what quick_add_provider does:
|
|
1. Create a ServiceProvider record
|
|
2. Verify it exists in marketplace.service_providers
|
|
3. Verify NO Organization was created in fleet.organizations
|
|
4. Create a dummy AssetCost and verify service_provider_id is set
|
|
and vendor_organization_id remains NULL
|
|
"""
|
|
engine = create_async_engine(DATABASE_URL, echo=False)
|
|
passed = 0
|
|
failed = 0
|
|
errors = []
|
|
|
|
async with AsyncSession(engine) as db:
|
|
try:
|
|
logger.info("=" * 60)
|
|
logger.info("🚀 P0 E2E VERIFICATION — Quick Add Provider Flow Test")
|
|
logger.info("=" * 60)
|
|
logger.info(f"")
|
|
logger.info(f"📋 Test Provider: '{TEST_PROVIDER_NAME}'")
|
|
logger.info(f"📋 Test Category: '{TEST_CATEGORY}'")
|
|
logger.info(f"")
|
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
# STEP 1: Simulate quick_add_provider — create ServiceProvider
|
|
# ─────────────────────────────────────────────────────────────
|
|
logger.info("📌 STEP 1: Creating ServiceProvider record...")
|
|
|
|
# First, clean up any previous test run
|
|
cleanup_sp_sql = text("""
|
|
DELETE FROM marketplace.service_providers
|
|
WHERE name = :name
|
|
""")
|
|
await db.execute(cleanup_sp_sql, {"name": TEST_PROVIDER_NAME})
|
|
|
|
# Also clean up any ServiceProfile that might be orphaned
|
|
cleanup_prof_sql = text("""
|
|
DELETE FROM marketplace.service_profiles
|
|
WHERE fingerprint LIKE :pattern
|
|
""")
|
|
await db.execute(cleanup_prof_sql, {"pattern": f"%{TEST_PROVIDER_NAME}%"})
|
|
|
|
await db.commit()
|
|
|
|
# Create the ServiceProvider (simulating quick_add_provider)
|
|
now = datetime.now(timezone.utc)
|
|
insert_sp_sql = text("""
|
|
INSERT INTO marketplace.service_providers
|
|
(name, address, category, city, status, source,
|
|
validation_score, created_at)
|
|
VALUES
|
|
(:name, :address, :category, :city, 'pending', 'manual',
|
|
50, :created_at)
|
|
RETURNING id
|
|
""")
|
|
sp_result = await db.execute(
|
|
insert_sp_sql,
|
|
{
|
|
"name": TEST_PROVIDER_NAME,
|
|
"address": "Teszt utca 1",
|
|
"category": TEST_CATEGORY,
|
|
"city": "Budapest",
|
|
"created_at": now,
|
|
}
|
|
)
|
|
sp_id = sp_result.scalar()
|
|
await db.commit()
|
|
|
|
logger.info(f" ✅ ServiceProvider created with id={sp_id}")
|
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
# ASSERT 1: ServiceProvider exists in marketplace.service_providers
|
|
# ─────────────────────────────────────────────────────────────
|
|
logger.info("\n📌 ASSERT 1: ServiceProvider exists in marketplace.service_providers...")
|
|
|
|
check_sp_sql = text("""
|
|
SELECT id, name, category
|
|
FROM marketplace.service_providers
|
|
WHERE id = :sp_id
|
|
""")
|
|
sp_check = await db.execute(check_sp_sql, {"sp_id": sp_id})
|
|
sp_row = sp_check.fetchone()
|
|
|
|
if sp_row and sp_row[1] == TEST_PROVIDER_NAME:
|
|
logger.info(f" ✅ PASS: ServiceProvider '{TEST_PROVIDER_NAME}' found (id={sp_row[0]})")
|
|
passed += 1
|
|
else:
|
|
logger.error(f" ❌ FAIL: ServiceProvider '{TEST_PROVIDER_NAME}' NOT found!")
|
|
failed += 1
|
|
errors.append("Assert 1 failed: ServiceProvider not found")
|
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
# ASSERT 2: No Organization exists in fleet.organizations
|
|
# ─────────────────────────────────────────────────────────────
|
|
logger.info("\n📌 ASSERT 2: No Organization exists in fleet.organizations...")
|
|
|
|
check_org_sql = text("""
|
|
SELECT id FROM fleet.organizations
|
|
WHERE name = :name
|
|
""")
|
|
org_check = await db.execute(check_org_sql, {"name": TEST_PROVIDER_NAME})
|
|
org_row = org_check.fetchone()
|
|
|
|
if org_row is None:
|
|
logger.info(f" ✅ PASS: No Organization found for '{TEST_PROVIDER_NAME}'")
|
|
passed += 1
|
|
else:
|
|
logger.error(f" ❌ FAIL: Organization found (id={org_row[0]}) for '{TEST_PROVIDER_NAME}'!")
|
|
failed += 1
|
|
errors.append(f"Assert 2 failed: Organization found (id={org_row[0]})")
|
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
# ASSERT 3: Create dummy AssetCost — service_provider_id populated,
|
|
# vendor_organization_id remains NULL
|
|
# ─────────────────────────────────────────────────────────────
|
|
logger.info("\n📌 ASSERT 3: Creating dummy AssetCost with service_provider_id...")
|
|
|
|
# First, find a real asset to link to (use any existing asset)
|
|
find_asset_sql = text("""
|
|
SELECT id FROM vehicle.assets LIMIT 1
|
|
""")
|
|
asset_result = await db.execute(find_asset_sql)
|
|
asset_row = asset_result.fetchone()
|
|
|
|
if not asset_row:
|
|
logger.warning(" ⚠️ No assets found in vehicle.assets. Creating a minimal one...")
|
|
# Create a minimal asset for testing
|
|
new_asset_id = uuid.uuid4()
|
|
create_asset_sql = text("""
|
|
INSERT INTO vehicle.assets (id, brand, model, license_plate, vin, status, created_at)
|
|
VALUES (:id, 'Teszt', 'Auto', 'TEST-001', :vin, 'active', :now)
|
|
""")
|
|
await db.execute(
|
|
create_asset_sql,
|
|
{
|
|
"id": new_asset_id,
|
|
"vin": f"TESTVIN{uuid.uuid4().hex[:10].upper()}",
|
|
"now": now,
|
|
}
|
|
)
|
|
asset_id = new_asset_id
|
|
else:
|
|
asset_id = asset_row[0]
|
|
|
|
# Find a real organization for the organization_id FK
|
|
find_org_sql = text("""
|
|
SELECT id FROM fleet.organizations LIMIT 1
|
|
""")
|
|
org_result = await db.execute(find_org_sql)
|
|
org_row = org_result.fetchone()
|
|
|
|
if not org_row:
|
|
logger.error(" ❌ FAIL: No organizations found in fleet.organizations!")
|
|
failed += 1
|
|
errors.append("Assert 3 failed: No organizations in fleet.organizations")
|
|
else:
|
|
real_org_id = org_row[0]
|
|
|
|
# Find a cost category
|
|
find_cat_sql = text("""
|
|
SELECT id FROM fleet_finance.cost_categories LIMIT 1
|
|
""")
|
|
cat_result = await db.execute(find_cat_sql)
|
|
cat_row = cat_result.fetchone()
|
|
|
|
if not cat_row:
|
|
logger.error(" ❌ FAIL: No cost categories found!")
|
|
failed += 1
|
|
errors.append("Assert 3 failed: No cost categories")
|
|
else:
|
|
cat_id = cat_row[0]
|
|
|
|
# Create the AssetCost with service_provider_id set
|
|
cost_id = uuid.uuid4()
|
|
insert_cost_sql = text("""
|
|
INSERT INTO fleet_finance.asset_costs
|
|
(id, asset_id, organization_id, category_id,
|
|
amount_net, amount_gross, currency, date,
|
|
service_provider_id, vendor_organization_id,
|
|
external_vendor_name, status)
|
|
VALUES
|
|
(:id, :asset_id, :org_id, :cat_id,
|
|
10000, 12700, 'HUF', :now,
|
|
:sp_id, NULL,
|
|
:vendor_name, 'DRAFT')
|
|
RETURNING id
|
|
""")
|
|
cost_result = await db.execute(
|
|
insert_cost_sql,
|
|
{
|
|
"id": cost_id,
|
|
"asset_id": asset_id,
|
|
"org_id": real_org_id,
|
|
"cat_id": cat_id,
|
|
"now": now,
|
|
"sp_id": sp_id,
|
|
"vendor_name": TEST_PROVIDER_NAME,
|
|
}
|
|
)
|
|
created_cost_id = cost_result.scalar()
|
|
|
|
# Verify the AssetCost
|
|
verify_cost_sql = text("""
|
|
SELECT id, service_provider_id, vendor_organization_id,
|
|
external_vendor_name
|
|
FROM fleet_finance.asset_costs
|
|
WHERE id = :cost_id
|
|
""")
|
|
cost_check = await db.execute(verify_cost_sql, {"cost_id": created_cost_id})
|
|
cost_row = cost_check.fetchone()
|
|
|
|
if cost_row:
|
|
actual_sp_id = cost_row[1]
|
|
actual_vendor_org_id = cost_row[2]
|
|
actual_vendor_name = cost_row[3]
|
|
|
|
sp_ok = (actual_sp_id == sp_id)
|
|
vendor_null_ok = (actual_vendor_org_id is None)
|
|
name_ok = (actual_vendor_name == TEST_PROVIDER_NAME)
|
|
|
|
if sp_ok and vendor_null_ok and name_ok:
|
|
logger.info(
|
|
f" ✅ PASS: AssetCost correctly linked:\n"
|
|
f" service_provider_id={actual_sp_id} (expected {sp_id})\n"
|
|
f" vendor_organization_id={actual_vendor_org_id} (expected NULL)\n"
|
|
f" external_vendor_name='{actual_vendor_name}'"
|
|
)
|
|
passed += 1
|
|
else:
|
|
logger.error(
|
|
f" ❌ FAIL: AssetCost verification failed:\n"
|
|
f" service_provider_id={actual_sp_id} (expected {sp_id})\n"
|
|
f" vendor_organization_id={actual_vendor_org_id} (expected NULL)\n"
|
|
f" external_vendor_name='{actual_vendor_name}'"
|
|
)
|
|
failed += 1
|
|
errors.append("Assert 3 failed: AssetCost verification")
|
|
else:
|
|
logger.error(" ❌ FAIL: AssetCost was not created!")
|
|
failed += 1
|
|
errors.append("Assert 3 failed: AssetCost not created")
|
|
|
|
# Clean up the test AssetCost
|
|
await db.execute(text("DELETE FROM fleet_finance.asset_costs WHERE id = :id"), {"id": created_cost_id})
|
|
|
|
# Clean up test data
|
|
await db.execute(cleanup_sp_sql, {"name": TEST_PROVIDER_NAME})
|
|
await db.commit()
|
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
# FINAL REPORT
|
|
# ─────────────────────────────────────────────────────────────
|
|
logger.info("\n" + "=" * 60)
|
|
logger.info("📋 E2E TEST RESULTS")
|
|
logger.info("=" * 60)
|
|
logger.info(f" ✅ Passed: {passed}")
|
|
logger.info(f" ❌ Failed: {failed}")
|
|
|
|
if failed == 0:
|
|
logger.info(f"\n🎉 ALL TESTS PASSED! The quick_add_provider refactor is working correctly.")
|
|
logger.info(f" - ServiceProviders are created WITHOUT corresponding Organizations")
|
|
logger.info(f" - AssetCosts can be linked via service_provider_id")
|
|
logger.info(f" - vendor_organization_id remains NULL for provider-linked costs")
|
|
else:
|
|
logger.error(f"\n💥 {failed} test(s) FAILED!")
|
|
for err in errors:
|
|
logger.error(f" - {err}")
|
|
|
|
return {
|
|
"passed": passed,
|
|
"failed": failed,
|
|
"errors": errors,
|
|
}
|
|
|
|
except Exception as e:
|
|
await db.rollback()
|
|
logger.error(f"💥 Fatal error during E2E test: {e}", exc_info=True)
|
|
sys.exit(1)
|
|
|
|
|
|
async def main():
|
|
logger.info("🚀 P0 E2E VERIFICATION — Quick Add Provider Flow Test")
|
|
logger.info(f" Database: {DATABASE_URL}")
|
|
logger.info("")
|
|
|
|
try:
|
|
await test_quick_add_flow()
|
|
except Exception as e:
|
|
logger.error(f"💥 Fatal error: {e}")
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|