- Implement tests for the internet connections module, covering routes, IP range creation, and connection validation. - Add tests for the Invoice2DataService to validate extraction from GlobalConnect invoices. - Create tests for subscription network provisioning, ensuring proper handling of network items and IP allocations. - Include validation checks for subtotal mismatches and ensure error handling for missing IP selections.
144 lines
5.3 KiB
Python
144 lines
5.3 KiB
Python
import json
|
|
import re
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
|
|
NETWORK_KINDS = {"internet_access", "ip_allocation"}
|
|
|
|
|
|
def parse_product_attributes(raw: Any) -> Dict[str, Any]:
|
|
if raw is None:
|
|
return {}
|
|
if isinstance(raw, dict):
|
|
return raw
|
|
if isinstance(raw, str):
|
|
text = raw.strip()
|
|
if not text:
|
|
return {}
|
|
try:
|
|
parsed = json.loads(text)
|
|
except json.JSONDecodeError:
|
|
return {}
|
|
return parsed if isinstance(parsed, dict) else {}
|
|
return {}
|
|
|
|
|
|
def _parse_speed_from_text(text: str) -> Dict[str, Optional[int]]:
|
|
match = re.search(r"(\d+)\s*/\s*(\d+)\s*(?:mbit|mbps|gbit|gbps)?", text, re.IGNORECASE)
|
|
if not match:
|
|
return {"speed_mbps": None, "download_mbps": None, "upload_mbps": None}
|
|
|
|
download = int(match.group(1))
|
|
upload = int(match.group(2))
|
|
if re.search(r"(gbit|gbps)", text, re.IGNORECASE):
|
|
download *= 1000
|
|
upload *= 1000
|
|
|
|
return {
|
|
"speed_mbps": max(download, upload),
|
|
"download_mbps": download,
|
|
"upload_mbps": upload,
|
|
}
|
|
|
|
|
|
def _parse_prefix_from_text(text: str) -> Optional[int]:
|
|
match = re.search(r"/(\d{1,2})", text)
|
|
if not match:
|
|
return None
|
|
try:
|
|
prefix = int(match.group(1))
|
|
except ValueError:
|
|
return None
|
|
return prefix if 0 <= prefix <= 32 else None
|
|
|
|
|
|
def build_network_product_profile(product: Dict[str, Any], fallback_text: Optional[str] = None) -> Dict[str, Any]:
|
|
attributes = parse_product_attributes(product.get("attributes_json"))
|
|
network = attributes.get("network") if isinstance(attributes.get("network"), dict) else {}
|
|
|
|
text = " ".join(
|
|
part for part in [
|
|
str(product.get("name") or "").strip(),
|
|
str(product.get("product_name") or "").strip(),
|
|
str(product.get("description") or "").strip(),
|
|
str(fallback_text or "").strip(),
|
|
]
|
|
if part
|
|
)
|
|
lowered = text.lower()
|
|
|
|
kind = (
|
|
network.get("kind")
|
|
or attributes.get("network_kind")
|
|
or product.get("network_kind")
|
|
or product.get("type")
|
|
)
|
|
if kind not in NETWORK_KINDS:
|
|
if "/3" in lowered and "ip" in lowered:
|
|
kind = "ip_allocation"
|
|
elif re.search(r"/\d{1,2}", lowered) and "ip" in lowered:
|
|
kind = "ip_allocation"
|
|
elif "bmcnet" in lowered or "internet" in lowered or "fiber" in lowered:
|
|
kind = "internet_access"
|
|
else:
|
|
kind = None
|
|
|
|
speeds = _parse_speed_from_text(text)
|
|
speed_mbps = network.get("speed_mbps") or attributes.get("speed_mbps") or speeds["speed_mbps"]
|
|
download_mbps = network.get("download_mbps") or attributes.get("download_mbps") or speeds["download_mbps"]
|
|
upload_mbps = network.get("upload_mbps") or attributes.get("upload_mbps") or speeds["upload_mbps"]
|
|
ip_prefix_length = network.get("ip_prefix_length") or attributes.get("ip_prefix_length") or _parse_prefix_from_text(text)
|
|
connection_type = network.get("connection_type") or attributes.get("connection_type")
|
|
|
|
return {
|
|
"kind": kind,
|
|
"is_network_product": kind in NETWORK_KINDS,
|
|
"requires_provisioning": kind in NETWORK_KINDS,
|
|
"speed_mbps": int(speed_mbps) if speed_mbps is not None else None,
|
|
"download_mbps": int(download_mbps) if download_mbps is not None else None,
|
|
"upload_mbps": int(upload_mbps) if upload_mbps is not None else None,
|
|
"ip_prefix_length": int(ip_prefix_length) if ip_prefix_length is not None else None,
|
|
"connection_type": connection_type,
|
|
"attributes": attributes,
|
|
}
|
|
|
|
|
|
def summarize_subscription_network_requirements(line_items: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
internet_items: List[Dict[str, Any]] = []
|
|
ip_items: List[Dict[str, Any]] = []
|
|
|
|
for item in line_items or []:
|
|
profile = build_network_product_profile(item, fallback_text=item.get("description"))
|
|
if not profile["requires_provisioning"]:
|
|
continue
|
|
|
|
entry = {
|
|
"subscription_item_id": item.get("id"),
|
|
"line_no": item.get("line_no"),
|
|
"product_id": item.get("product_id"),
|
|
"product_name": item.get("product_name") or item.get("description"),
|
|
"description": item.get("description"),
|
|
"quantity": item.get("quantity"),
|
|
"unit_price": item.get("unit_price"),
|
|
"line_total": item.get("line_total"),
|
|
"network_kind": profile["kind"],
|
|
"speed_mbps": profile["speed_mbps"],
|
|
"download_mbps": profile["download_mbps"],
|
|
"upload_mbps": profile["upload_mbps"],
|
|
"ip_prefix_length": profile["ip_prefix_length"],
|
|
"connection_type": profile["connection_type"],
|
|
}
|
|
if profile["kind"] == "internet_access":
|
|
internet_items.append(entry)
|
|
elif profile["kind"] == "ip_allocation":
|
|
ip_items.append(entry)
|
|
|
|
primary_internet_item = internet_items[0] if internet_items else None
|
|
return {
|
|
"requires_provisioning": bool(internet_items or ip_items),
|
|
"internet_items": internet_items,
|
|
"ip_items": ip_items,
|
|
"primary_internet_item": primary_internet_item,
|
|
"required_ip_prefixes": [item["ip_prefix_length"] for item in ip_items if item.get("ip_prefix_length") is not None],
|
|
}
|