import sys import asyncio from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent)) from fastapi.testclient import TestClient from main import app 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_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_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_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') assert response.status_code == 200 assert response.json() == [] 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_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'