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'{value}') else: cells.append(f'{value}') xml_rows.append(f'{"".join(cells)}') sheet = ( '' '' f'{"".join(xml_rows)}' ) 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_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 "Binding" not in template assert "Netværksmodel" in template