bmc_hub/tests/test_internet_connections_module.py
Christian 1cfe5aee76 feat(migrations): add AI benchmark and vTiger archive tables
- Created tables for AI benchmark runs and results to facilitate model evaluation.
- Added expected answers column to benchmark results.
- Introduced tables for internet connection change cases and vTiger archive management, including records, relations, and checkpoints.
- Implemented triggers to enforce append-only behavior for vTiger archive records and files.
- Enhanced solution management with soft delete capabilities for sag_solutions and knowledge_articles.

feat(scripts): add CRM benchmarking script for Ollama models

- Developed a Python script to benchmark CRM models exposed through Ollama, including various test cases and scoring mechanisms.

test(tests): add comprehensive tests for new features

- Implemented tests for internet change case service, vTiger archive functionality, and sag solution knowledge management.
- Ensured coverage for edge cases and error handling in the new features.
2026-08-31 13:01:35 +02:00

1032 lines
40 KiB
Python

import sys
import asyncio
import io
import zipfile
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
from fastapi.testclient import TestClient
from main import app
def _build_ip_nordic_test_xlsx():
rows = [
["Company", "Name", "Startdate", "Salgspris", "Kostpris", "InstallationAddress"],
["99773", "BMC Denmark ApS", "43418", "2495", "1386", "Engholm Parkvej 8, 3450 Allerød"],
["99773", "BMC Denmark ApS", "43418", "129", "88", "Engholm Parkvej 8, 3450 Allerød "],
]
xml_rows = []
for row_number, values in enumerate(rows, start=1):
cells = []
for column_number, value in enumerate(values):
column = chr(ord('A') + column_number)
if row_number == 1 or column in {'A', 'B', 'F'}:
cells.append(f'<c r="{column}{row_number}" t="inlineStr"><is><t>{value}</t></is></c>')
else:
cells.append(f'<c r="{column}{row_number}"><v>{value}</v></c>')
xml_rows.append(f'<row r="{row_number}">{"".join(cells)}</row>')
sheet = (
'<?xml version="1.0" encoding="UTF-8"?>'
'<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">'
f'<sheetData>{"".join(xml_rows)}</sheetData></worksheet>'
)
output = io.BytesIO()
with zipfile.ZipFile(output, 'w') as archive:
archive.writestr('xl/worksheets/sheet1.xml', sheet)
return output.getvalue()
def test_internet_connections_module_routes_are_available():
client = TestClient(app)
health_response = client.get('/api/v1/internet-connections/health')
assert health_response.status_code == 200
assert health_response.json()['service'] == 'internet-connections-module'
page_response = client.get('/economy/internet-connections')
assert page_response.status_code == 200
assert (
'Internetforbindelser' in page_response.text
or "window.location.href = '/login'" in page_response.text
)
detail_response = client.get('/economy/internet-connections/1')
assert detail_response.status_code == 200
assert (
'IP-ranges' in detail_response.text
or "window.location.href = '/login'" in detail_response.text
)
def test_ip_nordic_xlsx_parser_groups_lines_by_company_and_address():
from app.modules.internet_connections.backend import router as internet_router
items = internet_router._parse_ip_nordic_xlsx(_build_ip_nordic_test_xlsx())
assert len(items) == 1
assert items[0]['line_count'] == 2
assert float(items[0]['monthly_cost']) == 1474
assert float(items[0]['sales_price']) == 2624
assert items[0]['address'] == 'Engholm Parkvej 8, 3450 Allerød'
def test_ip_nordic_import_preview_never_assigns_customer(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
monkeypatch.setattr(internet_router, 'execute_query_single', lambda query, params=None: None)
client = TestClient(app)
response = client.post(
'/api/v1/internet-connections/import/ip-nordic',
files={'file': ('IP_Nordic.xlsx', _build_ip_nordic_test_xlsx(), 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')},
data={'commit': 'false'},
)
assert response.status_code == 200
assert response.json()['create_count'] == 1
assert response.json()['customer_auto_assignment'] is False
def test_ip_nordic_existing_connection_creates_one_change_case_without_customer_assignment(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
writes = []
cases = []
monkeypatch.setattr(internet_router, 'execute_query_single', lambda query, params=None: {
'id': 44, 'name': 'IP Nordic forbindelse', 'address': 'Engholm Parkvej 8, 3450 Allerød',
'customer_id': None, 'monthly_cost': 1000, 'sales_price': 2000,
'provider': 'IP Nordic', 'status': 'active', 'technology': 'fiber',
'connection_type': 'fiber', 'circuit_number': None,
})
monkeypatch.setattr(internet_router, 'execute_query', lambda query, params=None, fetch=True: writes.append((query, params)))
monkeypatch.setattr(internet_router, '_create_history_entry', lambda *args: writes.append(args))
monkeypatch.setattr(internet_router, 'ensure_external_change_case', lambda **kwargs: cases.append(kwargs) or {
'case_id': 88, 'created': True, 'error': None,
})
response = TestClient(app).post(
'/api/v1/internet-connections/import/ip-nordic',
files={'file': ('IP_Nordic.xlsx', _build_ip_nordic_test_xlsx(), 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')},
data={'commit': 'true'},
)
assert response.status_code == 200
assert response.json()['updated_count'] == 1
assert response.json()['change_case_ids'] == [88]
assert len(cases) == 1
assert cases[0]['owner_customer_id'] is None
assert cases[0]['changes']['monthly_cost']['to'] == 1474
assert all('customer_id=' not in str(query).lower() for query, *_ in writes if isinstance(query, str))
def test_internet_connections_page_has_ip_nordic_preview_import():
template = Path('app/modules/internet_connections/templates/index.html').read_text()
assert 'Importér IP Nordic' in template
assert 'previewIpNordicImport()' in template
assert 'commitIpNordicImport()' in template
def test_create_ip_range_rejects_invalid_cidr(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
def fake_execute_query(query, params=None):
return []
monkeypatch.setattr(internet_router, 'execute_query', fake_execute_query)
client = TestClient(app)
response = client.post('/api/v1/internet-connections/2/ip-ranges', json={
'name': 'LAN',
'cidr': 'not-a-cidr',
'description': 'Test range',
})
assert response.status_code == 400
assert 'CIDR' in response.json()['detail']
def test_document_entity_extraction_canonicalizes_cidr_with_host_bits_and_spaces():
from app.modules.internet_connections.backend import router as internet_router
entities = internet_router._extract_segment_entities(
"WAN range 192.0.2.1 / 29 og gateway 192.0.2.2"
)
assert entities["cidr_blocks"] == ["192.0.2.0/29"]
assert entities["ip_addresses"] == ["192.0.2.2"]
def test_create_connection_requires_address(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
monkeypatch.setattr(internet_router, 'execute_query', lambda query, params=None: [])
client = TestClient(app)
response = client.post('/api/v1/internet-connections', json={
'name': 'Uden adresse',
'provider': 'GlobalConnect A/S',
'status': 'active',
'allocation_model': 'shared',
'value_type': 'bmc_networks',
})
assert response.status_code == 400
assert 'address is required' in response.json()['detail']
def test_list_ip_ranges_includes_ipam_details(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
def fake_execute_query(query, params=None):
if 'FROM internet_connections_ip_ranges' in query:
return [{
'id': 9,
'connection_id': 2,
'name': 'LAN',
'cidr': '10.0.0.0/24',
'description': 'Test range',
}]
if 'FROM internet_connections_ip_addresses' in query:
return [{
'id': 1,
'range_id': 9,
'ip_address': '10.0.0.1',
'status': 'in_use',
}]
return []
monkeypatch.setattr(internet_router, 'execute_query', fake_execute_query)
client = TestClient(app)
response = client.get('/api/v1/internet-connections/2/ip-ranges')
assert response.status_code == 200
payload = response.json()[0]
assert payload['network_address'] == '10.0.0.0'
assert payload['usable_hosts'] == 254
assert payload['used_addresses'] == 1
def test_migration_wizard_v2_query_requires_real_segment_match(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
def fake_execute_query(query, params=None):
if "FROM internet_connections_customer_documents" in query:
return [{
"id": 101,
"customer_id": 77,
"connection_id": None,
"original_filename": "management_net.txt",
"filename": "management_net.txt",
"file_size": 1024,
"mime_type": "text/plain",
"extracted_text": "1 UNTAGGED Native Management\nVlan 50 Not in Use\nIP Informationer",
"notes": None,
"created_at": None,
}]
if "FROM internet_connections_customer_document_segments" in query:
return [{
"id": 201,
"document_id": 101,
"block_index": 0,
"block_title": "IP Informationer",
"content": "1 UNTAGGED Native Management\nVlan 50 Not in Use\nIP Informationer",
"ip_addresses": [],
"cidr_blocks": [],
"references_json": [],
"socket_numbers": [],
}]
return []
monkeypatch.setattr(internet_router, "execute_query", fake_execute_query)
monkeypatch.setattr(internet_router, "_ensure_document_segments", lambda document_id, extracted_text: 1)
payload = asyncio.run(internet_router._build_customer_document_hits(77, "Karise", "stageone"))
assert payload["segments"] == []
assert payload["documents"] == []
def test_migration_wizard_v2_query_keeps_precise_segment_hits(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
def fake_execute_query(query, params=None):
if "FROM internet_connections_customer_documents" in query:
return [{
"id": 102,
"customer_id": 77,
"connection_id": None,
"original_filename": "karise.txt",
"filename": "karise.txt",
"file_size": 2048,
"mime_type": "text/plain",
"extracted_text": "StageOne uplink til Karise\nPort 1 StageOne WAN U20",
"notes": None,
"created_at": None,
}]
if "FROM internet_connections_customer_document_segments" in query:
return [{
"id": 202,
"document_id": 102,
"block_index": 0,
"block_title": "StageOne uplink til Karise",
"content": "StageOne uplink til Karise\nPort 1 StageOne WAN U20",
"ip_addresses": [],
"cidr_blocks": [],
"references_json": [],
"socket_numbers": ["U20"],
}]
return []
monkeypatch.setattr(internet_router, "execute_query", fake_execute_query)
monkeypatch.setattr(internet_router, "_ensure_document_segments", lambda document_id, extracted_text: 1)
payload = asyncio.run(internet_router._build_customer_document_hits(77, "Karise", "stageone"))
assert len(payload["segments"]) == 1
assert payload["segments"][0]["title"] == "StageOne uplink til Karise"
assert payload["documents"][0]["snippet_count"] == 1
def test_migration_wizard_v2_returns_full_text_for_selected_segment(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
monkeypatch.setattr(internet_router, "execute_query_single", lambda query, params: {
"segment_id": 202,
"document_id": 102,
"block_index": 3,
"title": "StageOne uplink",
"original_filename": "karise.txt",
"content": "Hele den valgte tekstblok\nmed alle linjer.",
})
payload = asyncio.run(internet_router.get_customer_document_segment(202))
assert payload["title"] == "StageOne uplink"
assert payload["content"] == "Hele den valgte tekstblok\nmed alle linjer."
def test_migration_wizard_block_search_requires_all_words(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
def fake_execute_query(query, params=None):
if "FROM internet_connections_customer_documents" in query:
return [{
"id": 103, "customer_id": 77, "connection_id": None,
"original_filename": "sales.txt", "filename": "sales.txt",
"file_size": 1, "mime_type": "text/plain", "notes": None,
"created_at": None, "extracted_text": "Management network notes",
}]
if "FROM internet_connections_customer_document_segments" in query:
return [{
"id": 203, "document_id": 103, "block_index": 0,
"block_title": "Management", "content": "Management network notes",
"ip_addresses": [], "cidr_blocks": [], "references_json": [], "socket_numbers": [],
}]
return []
monkeypatch.setattr(internet_router, "execute_query", fake_execute_query)
monkeypatch.setattr(internet_router, "_ensure_document_segments", lambda document_id, extracted_text: 1)
payload = asyncio.run(internet_router._build_customer_document_hits(77, "Karise", "sales management"))
assert payload["segments"] == []
assert payload["documents"] == []
def test_create_ip_range_auto_generates_addresses_from_cidr(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
created_addresses = []
def fake_execute_query(query, params=None):
if 'INSERT INTO internet_connections_ip_ranges' in query:
return [{
'id': 15,
'connection_id': 2,
'name': 'LAN',
'cidr': '192.168.1.0/30',
'description': 'Auto generated',
}]
if 'INSERT INTO internet_connections_ip_addresses' in query:
created_addresses.append(params[1])
return [{
'id': len(created_addresses),
'range_id': 15,
'ip_address': params[1],
'status': 'available',
}]
if 'INSERT INTO internet_connections_history' in query:
return [{
'id': 20,
'connection_id': 2,
'event_type': 'ip_range_created',
'summary': 'Created IP range',
'details': {},
}]
return []
monkeypatch.setattr(internet_router, 'execute_query', fake_execute_query)
client = TestClient(app)
response = client.post('/api/v1/internet-connections/2/ip-ranges', json={
'name': 'LAN',
'cidr': '192.168.1.0/30',
'description': 'Auto generated',
})
assert response.status_code == 200
assert len(created_addresses) == 2
assert created_addresses == ['192.168.1.1', '192.168.1.2']
def test_create_ip_range_skips_duplicate_existing_ip_addresses(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
created_addresses = []
def fake_execute_query(query, params=None):
if 'INSERT INTO internet_connections_ip_ranges' in query:
return [{
'id': 15,
'connection_id': 2,
'name': 'LAN',
'cidr': '192.168.1.0/30',
'description': 'Auto generated',
}]
if 'FROM internet_connections_ip_addresses' in query and 'WHERE ip_address = %s' in query:
if params[0] == '192.168.1.1':
return [{'id': 99, 'ip_address': '192.168.1.1'}]
return []
if 'INSERT INTO internet_connections_ip_addresses' in query:
created_addresses.append(params[1])
return [{
'id': len(created_addresses),
'range_id': 15,
'ip_address': params[1],
'status': 'available',
}]
if 'INSERT INTO internet_connections_history' in query:
return [{'id': 20}]
return []
monkeypatch.setattr(internet_router, 'execute_query', fake_execute_query)
client = TestClient(app)
response = client.post('/api/v1/internet-connections/2/ip-ranges', json={
'name': 'LAN',
'cidr': '192.168.1.0/30',
'description': 'Auto generated',
})
assert response.status_code == 200
assert created_addresses == ['192.168.1.2']
def test_update_ip_address_status_changes_record(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
updated = []
def fake_execute_query(query, params=None):
if 'UPDATE internet_connections_ip_addresses' in query:
updated.append(params)
return [{'id': 4, 'status': 'reserved'}]
return []
monkeypatch.setattr(internet_router, 'execute_query', fake_execute_query)
client = TestClient(app)
response = client.put('/api/v1/internet-connections/2/ip-addresses/4', json={
'status': 'reserved',
'assigned_to': 'Test device',
'comment': 'Reserved for switch',
})
assert response.status_code == 200
assert updated[0][0] == 'reserved'
assert updated[0][1] == 'Test device'
def test_create_ip_address_rejects_duplicate_ip(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
def fake_execute_query(query, params=None):
if 'FROM internet_connections_ip_addresses' in query and 'WHERE ip_address = %s' in query:
return [{'id': 4, 'range_id': 2}]
return []
monkeypatch.setattr(internet_router, 'execute_query', fake_execute_query)
client = TestClient(app)
response = client.post('/api/v1/internet-connections/2/ip-addresses', json={
'range_id': 7,
'ip_address': '10.0.0.10',
'status': 'available',
})
assert response.status_code == 409
assert 'findes allerede' in response.json()['detail']
def test_list_ip_addresses_returns_structured_payload(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
def fake_execute_query(query, params=None):
if 'FROM internet_connections_ip_addresses' in query and 'JOIN internet_connections_ip_ranges' in query:
return [{
'id': 7,
'range_id': 3,
'ip_address': '10.0.0.10',
'status': 'in_use',
'assigned_to': 'Router',
'assigned_type': 'device',
'comment': 'Main gateway',
}]
return []
monkeypatch.setattr(internet_router, 'execute_query', fake_execute_query)
client = TestClient(app)
response = client.get('/api/v1/internet-connections/2/ip-addresses')
assert response.status_code == 200
payload = response.json()[0]
assert payload['status_label'] == 'I brug'
assert payload['badge_class'] == 'bg-primary'
def test_create_ip_range_writes_history(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
calls = []
def fake_execute_query(query, params=None):
calls.append((query, params))
if 'INSERT INTO internet_connections_ip_ranges' in query:
return [{
'id': 1,
'connection_id': 2,
'name': 'LAN',
'cidr': '10.0.0.0/24',
'description': 'Test range',
}]
if 'INSERT INTO internet_connections_history' in query:
return [{
'id': 11,
'connection_id': 2,
'event_type': 'ip_range_created',
'summary': 'Created IP range',
'details': {},
}]
return []
monkeypatch.setattr(internet_router, 'execute_query', fake_execute_query)
client = TestClient(app)
response = client.post('/api/v1/internet-connections/2/ip-ranges', json={
'name': 'LAN',
'cidr': '10.0.0.0/24',
'description': 'Test range',
})
assert response.status_code == 200
assert any('INSERT INTO internet_connections_history' in query for query, _ in calls)
def test_ip_address_status_summary_endpoint(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
def fake_execute_query(query, params=None):
if 'COUNT(*)' in query and 'internet_connections_ip_addresses' in query:
return [{
'available': 2,
'in_use': 1,
'reserved': 1,
}]
return []
monkeypatch.setattr(internet_router, 'execute_query', fake_execute_query)
client = TestClient(app)
response = client.get('/api/v1/internet-connections/2/ip-addresses/summary')
assert response.status_code == 200
assert response.json()['available'] == 2
assert response.json()['in_use'] == 1
def test_pricing_history_can_be_created(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
def fake_execute_query(query, params=None):
if 'INSERT INTO internet_connections_pricing' in query:
return [{
'id': 7,
'connection_id': 2,
'effective_from': '2026-07-01',
'purchase_price': 1500,
'sales_price': 1800,
'notes': 'Ny aftale',
}]
return []
monkeypatch.setattr(internet_router, 'execute_query', fake_execute_query)
client = TestClient(app)
response = client.post('/api/v1/internet-connections/2/pricing', json={
'effective_from': '2026-07-01',
'purchase_price': 1500,
'sales_price': 1800,
'notes': 'Ny aftale',
})
assert response.status_code == 200
assert response.json()['sales_price'] == 1800
def test_contract_overview_endpoint_reports_status(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
def fake_execute_query(query, params=None):
if 'FROM internet_connections_connections' in query and 'contract_end' in query:
return [{
'id': 3,
'name': 'Test connection',
'provider': 'BMC',
'contract_start': '2025-01-01',
'contract_end': '2025-12-31',
'status': 'active',
'sales_price': 900,
}]
return []
monkeypatch.setattr(internet_router, 'execute_query', fake_execute_query)
client = TestClient(app)
response = client.get('/api/v1/internet-connections/contracts')
assert response.status_code == 200
assert response.json()[0]['contract_status'] == 'expired'
def test_contract_overview_endpoint_supports_status_filter(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
def fake_execute_query(query, params=None):
if 'status = %s' in query:
return [{
'id': 4,
'name': 'Filtered contract',
'provider': 'Nordic',
'contract_start': '2026-01-01',
'contract_end': '2026-12-31',
'status': 'active',
'sales_price': 1200,
}]
return []
monkeypatch.setattr(internet_router, 'execute_query', fake_execute_query)
client = TestClient(app)
response = client.get('/api/v1/internet-connections/contracts', params={'status': 'active'})
assert response.status_code == 200
assert response.json()[0]['status'] == 'active'
def test_contract_overview_page_contains_table_controls():
client = TestClient(app)
response = client.get('/economy/internet-connections')
assert response.status_code == 200
assert (
'connectionsTableBody' in response.text
or "window.location.href = '/login'" in response.text
)
assert (
'pageSummaryText' in response.text
or "window.location.href = '/login'" in response.text
)
def test_contract_overview_returns_empty_list_when_query_fails(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
def fail_execute_query(query, params=None):
raise RuntimeError('database unavailable')
monkeypatch.setattr(internet_router, 'execute_query', fail_execute_query)
client = TestClient(app)
response = client.get('/api/v1/internet-connections/contracts')
assert response.status_code == 200
assert response.json() == []
def test_list_connections_returns_visible_error_when_query_fails(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
def fail_execute_query(query, params=None):
raise RuntimeError('database unavailable')
monkeypatch.setattr(internet_router, 'execute_query', fail_execute_query)
client = TestClient(app)
response = client.get('/api/v1/internet-connections')
assert response.status_code == 500
assert response.json()['detail'] == 'Kunne ikke hente internetforbindelser'
def test_pricing_summary_returns_zeroes_when_query_fails(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
def fail_execute_query(query, params=None):
raise RuntimeError('database unavailable')
monkeypatch.setattr(internet_router, 'execute_query', fail_execute_query)
client = TestClient(app)
response = client.get('/api/v1/internet-connections/pricing/summary')
assert response.status_code == 200
assert response.json() == {
'total_connections': 0,
'active_connections': 0,
'shared_head_connections': 0,
'total_purchase_cost': 0,
'total_sales_price': 0,
'total_margin': 0,
}
def test_create_connection_requires_subscription_id_for_subscription_value(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
monkeypatch.setattr(internet_router, 'execute_query', lambda query, params=None: [])
client = TestClient(app)
response = client.post('/api/v1/internet-connections', json={
'name': 'Shared transit',
'address': 'Testvej 1, 8000 Aarhus C',
'allocation_model': 'shared',
'value_type': 'subscription',
})
assert response.status_code == 400
assert 'subscription_id' in response.json()['detail']
def test_create_connection_requires_value_label_for_other(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
monkeypatch.setattr(internet_router, 'execute_query', lambda query, params=None: [])
client = TestClient(app)
response = client.post('/api/v1/internet-connections', json={
'name': 'Carrier edge',
'address': 'Testvej 1, 8000 Aarhus C',
'allocation_model': 'dedicated',
'value_type': 'other',
})
assert response.status_code == 400
assert 'value_label' in response.json()['detail']
def test_list_connections_supports_shared_only_filter(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
def fake_execute_query(query, params=None):
assert "ic.allocation_model = 'shared' AND ic.parent_id IS NULL" in query
return [{
'id': 10,
'parent_id': None,
'name': 'BMC Networks · NKA008225',
'provider': 'GlobalConnect',
'customer_id': 1662,
'customer_name': 'BMC Networks',
'parent_name': None,
'address': None,
'status': 'active',
'monthly_cost': 256,
'sales_price': 0,
'margin_amount': -256,
'technology': 'Fiber',
'connection_type': 'fiber',
'circuit_number': 'NKA008225',
'speed_mbps': 100,
'upload_mbps': 100,
'download_mbps': 100,
'monitoring_url': None,
'contract_start': None,
'contract_end': None,
'allocation_model': 'shared',
'value_type': 'bmc_networks',
'value_label': None,
'subscription_id': None,
'subscription_number': None,
'subscription_product_name': None,
'subscription_customer_name': None,
'ip_range_count': 3,
'total_ip_addresses': 62,
'in_use_ip_addresses': 0,
'reserved_ip_addresses': 0,
'available_ip_addresses': 62,
}]
monkeypatch.setattr(internet_router, 'execute_query', fake_execute_query)
client = TestClient(app)
response = client.get('/api/v1/internet-connections', params={'shared_only': 'true'})
assert response.status_code == 200
payload = response.json()[0]
assert payload['allocation_model_label'] == 'Delt'
assert payload['value_type_label'] == 'BMC Networks'
assert payload['is_shared_head'] is True
def test_first_bmcnet_child_promotes_head_and_preserves_customer(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
single_results = iter([
{'id': 12, 'allocation_model': 'dedicated', 'value_type': 'other', 'value_label': 'Internetforbindelse'},
{'child_count': 1},
])
writes = []
monkeypatch.setattr(internet_router, 'execute_query_single', lambda query, params=None: next(single_results))
monkeypatch.setattr(internet_router, 'execute_query', lambda query, params=None: writes.append((query, params)) or [])
assert internet_router._sync_bmcnet_parent_classification(12) is True
classification_query, params = writes[0]
assert 'customer_id' not in classification_query
assert params == ('shared', 'delefiber', None, 12)
def test_last_bmcnet_child_removal_returns_head_to_dedicated(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
single_results = iter([
{'id': 12, 'allocation_model': 'shared', 'value_type': 'delefiber', 'value_label': None},
{'child_count': 0},
])
writes = []
monkeypatch.setattr(internet_router, 'execute_query_single', lambda query, params=None: next(single_results))
monkeypatch.setattr(internet_router, 'execute_query', lambda query, params=None: writes.append((query, params)) or [])
assert internet_router._sync_bmcnet_parent_classification(12) is True
assert writes[0][1] == ('dedicated', 'other', 'Internetforbindelse', 12)
def test_manually_marked_delefiber_stays_shared_without_children(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
single_results = iter([
{'id': 12, 'allocation_model': 'shared', 'value_type': 'delefiber', 'value_label': None, 'is_manual_shared': True},
{'child_count': 0},
])
writes = []
monkeypatch.setattr(internet_router, 'execute_query_single', lambda query, params=None: next(single_results))
monkeypatch.setattr(internet_router, 'execute_query', lambda query, params=None: writes.append((query, params)) or [])
assert internet_router._sync_bmcnet_parent_classification(12) is False
assert writes == []
def test_bmcnet_wizard_is_available_on_dedicated_root_connection():
template = Path('app/modules/internet_connections/templates/detail.html').read_text()
assert "const canCreateBmcnet = Boolean(connection && !connection.parent_id);" in template
assert "filter((item) => !item?.parent_id)" in template
def test_list_connections_supports_unallocated_filter(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
def fake_execute_query(query, params=None):
assert "ic.customer_id IS NULL" in query
return []
monkeypatch.setattr(internet_router, 'execute_query', fake_execute_query)
response = TestClient(app).get('/api/v1/internet-connections', params={'unallocated_only': 'true'})
assert response.status_code == 200
assert response.json() == []
def test_internet_connection_tabs_include_dedicated_and_unallocated():
template = Path('app/modules/internet_connections/templates/index.html').read_text()
assert "setActiveTab('dedicated')" in template
assert "setActiveTab('unallocated')" in template
assert "params.set('allocation_model', 'dedicated')" in template
assert "params.set('allocated_only', 'true')" in template
assert "params.set('unallocated_only', 'true')" in template
def test_processed_internet_invoices_have_their_own_tab():
template = Path('app/modules/internet_connections/templates/index.html').read_text()
assert "setActiveTab('invoices')" in template
assert 'id="invoiceProcessingOverview"' in template
assert "document.getElementById('connectionsOverview').classList.toggle('d-none', invoiceMode)" in template
assert "document.getElementById('invoiceProcessingOverview').classList.toggle('d-none', !invoiceMode)" in template
assert "if (activeTab === 'invoices') return loadInvoiceSyncRuns();" in template
def test_connections_show_and_allocate_sla_subscriptions():
index_template = Path('app/modules/internet_connections/templates/index.html').read_text()
detail_template = Path('app/modules/internet_connections/templates/detail.html').read_text()
migration = Path('migrations/1025_internet_connections_sla_subscription.sql').read_text()
assert 'sla_subscription_id' in migration
assert 'Ingen SLA-aftale' in index_template
assert 'id="slaSubscriptionSelect"' in detail_template
assert 'Prisen skal kontrolleres' in detail_template
assert "JSON.stringify({ sla_subscription_id: value })" in detail_template
def test_manual_bmc_shared_fiber_does_not_suggest_customer_allocation():
template = Path('app/modules/internet_connections/templates/detail.html').read_text()
assert 'const isBmcSharedFiber = Boolean(' in template
assert 'connection.is_manual_shared' in template
assert 'const shouldSuggestCustomer = !connection.customer_id && !isBmcSharedFiber;' in template
def test_invoice_review_reconciles_ranges_that_are_already_allocated():
template = Path('app/modules/internet_connections/templates/index.html').read_text()
from app.modules.internet_connections.backend import router as internet_router
assert "invoice-sync-runs/reconcile', { method: 'POST' }" in template
assert hasattr(internet_router, 'reconcile_internet_invoice_reviews')
def test_invoice_reconciliation_is_explicit_and_shared_fiber_has_no_sla_warning():
index_template = Path('app/modules/internet_connections/templates/index.html').read_text()
detail_template = Path('app/modules/internet_connections/templates/detail.html').read_text()
load_body = index_template.split('async function loadInvoiceSyncRuns()', 1)[1].split('async function reconcileInvoiceSyncRuns', 1)[0]
assert "await fetch('/api/v1/internet-connections/invoice-sync-runs/reconcile'" not in load_body
assert 'onclick="reconcileInvoiceSyncRuns()"' in index_template
assert "if (isBmcSharedFiber)" in detail_template
assert "banner.className = 'd-none';" in detail_template
def test_unallocated_tab_has_compact_customer_suggestion_workflow(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
query_results = iter([[], []])
monkeypatch.setattr(internet_router, 'execute_query', lambda query, params=None: next(query_results))
response = TestClient(app).get('/api/v1/internet-connections/allocation-overview')
template = Path('app/modules/internet_connections/templates/index.html').read_text()
assert response.status_code == 200
assert response.json() == {'items': []}
assert 'assignSuggestedCustomer' in template
assert 'unique_suggestion' in template
def test_list_connections_supports_allocated_filter(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
def fake_execute_query(query, params=None):
assert "ic.customer_id IS NOT NULL" in query
return []
monkeypatch.setattr(internet_router, 'execute_query', fake_execute_query)
response = TestClient(app).get('/api/v1/internet-connections', params={
'allocation_model': 'dedicated', 'allocated_only': 'true',
})
assert response.status_code == 200
assert response.json() == []
def test_allocation_suggestions_return_all_customers_on_exact_address(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
monkeypatch.setattr(internet_router, 'execute_query_single', lambda query, params=None: {
'id': 155, 'address': 'Testvej 1, 8000 Aarhus C', 'customer_id': None,
})
monkeypatch.setattr(internet_router, 'execute_query', lambda query, params=None: [
{'customer_id': 10, 'customer_name': 'Firma A', 'candidate_address': 'Testvej 1, 8000 Aarhus C', 'address_source': 'customer', 'location_name': None},
{'customer_id': 11, 'customer_name': 'Firma B', 'candidate_address': 'Testvej 1, 8000 Aarhus C', 'address_source': 'location', 'location_name': 'Kontor'},
])
response = TestClient(app).get('/api/v1/internet-connections/155/allocation-suggestions')
assert response.status_code == 200
assert [item['customer_id'] for item in response.json()['items']] == [10, 11]
def test_allocation_suggestions_match_boulevard_abbreviation_and_house_range(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
monkeypatch.setattr(internet_router, 'execute_query_single', lambda query, params=None: {
'id': 155, 'address': 'Arnold Nielsens Boulevard 81, 2650 Hvidovre', 'customer_id': None,
})
monkeypatch.setattr(internet_router, 'execute_query', lambda query, params=None: [{
'customer_id': 214, 'customer_name': 'Glarmester Svensson ApS',
'candidate_address': 'Arnold Nielsens Blv. 81 - 83, 2650 Hvidovre',
'address_source': 'customer', 'location_name': None,
}])
response = TestClient(app).get('/api/v1/internet-connections/155/allocation-suggestions')
assert response.status_code == 200
assert response.json()['items'][0]['customer_id'] == 214
assert response.json()['items'][0]['match_score'] == 90
def test_connection_detail_has_unallocated_customer_banner_and_subscription_linking():
template = Path('app/modules/internet_connections/templates/detail.html').read_text()
assert 'Forbindelsen er ikke tildelt en kunde' in template
assert '/allocation-suggestions' in template
assert 'customer_id=${customerId}' in template
assert 'saveConnectionAllocation()' in template
assert 'BMC Delefiber' in template
assert 'fieldManualShared' in template
def test_subscription_options_endpoint_returns_lookup_rows(monkeypatch):
from app.modules.internet_connections.backend import router as internet_router
def fake_execute_query(query, params=None):
if 'FROM sag_subscriptions s' in query:
return [{
'id': 9,
'subscription_number': 'SUB-1001',
'product_name': 'Internet 1G',
'customer_id': 1662,
'customer_name': 'BMC Networks',
'status': 'active',
}]
return []
monkeypatch.setattr(internet_router, 'execute_query', fake_execute_query)
client = TestClient(app)
response = client.get('/api/v1/internet-connections/subscription-options', params={'q': '1G'})
assert response.status_code == 200
assert response.json()[0]['subscription_number'] == 'SUB-1001'
def test_connection_detail_has_polished_network_header_and_ip_overview():
template = Path("app/modules/internet_connections/templates/detail.html").read_text()
assert 'id="detailCircuitBadge"' in template
assert 'class="detail-section-nav"' in template
assert 'class="detail-metrics-grid mb-4"' in template
assert 'id="connection-ip"' in template
assert "(summary.available || 0) + (summary.in_use || 0) + (summary.reserved || 0)" in template
assert "detail-grid-card ip-range-card" in template
assert "<span class=\"label\">Binding</span>" not in template
assert "Netværksmodel" in template