from unittest.mock import patch from uuid import uuid4 import pytest from orchestrator.db import SubscriptionTable from orchestrator.services import subscriptions from gso.products.product_blocks.iptrunk import IptrunkType from gso.products.product_blocks.router import RouterRole, RouterVendor from gso.products.product_blocks.site import SiteTier from gso.utils.types.phy_port import PhyPortCapacity SITE_IMPORT_ENDPOINT = "/api/v1/imports/sites" ROUTER_IMPORT_ENDPOINT = "/api/v1/imports/routers" IPTRUNK_IMPORT_API_URL = "/api/v1/imports/iptrunks" @pytest.fixture def iptrunk_data(router_subscription_factory, faker): router_side_a = router_subscription_factory() router_side_b = router_subscription_factory() return { "customer": "GÉANT", "geant_s_sid": faker.pystr(), "iptrunk_type": IptrunkType.DARK_FIBER, "iptrunk_description": faker.sentence(), "iptrunk_speed": PhyPortCapacity.HUNDRED_GIGABIT_PER_SECOND, "iptrunk_minimum_links": 5, "iptrunk_sideA_node_id": router_side_a, "iptrunk_sideA_ae_iface": faker.pystr(), "iptrunk_sideA_ae_geant_a_sid": faker.pystr(), "iptrunk_sideA_ae_members": [faker.pystr() for _ in range(5)], "iptrunk_sideA_ae_members_descriptions": [faker.sentence() for _ in range(5)], "iptrunk_sideB_node_id": router_side_b, "iptrunk_sideB_ae_iface": faker.pystr(), "iptrunk_sideB_ae_geant_a_sid": faker.pystr(), "iptrunk_sideB_ae_members": [faker.pystr() for _ in range(5)], "iptrunk_sideB_ae_members_descriptions": [faker.sentence() for _ in range(5)], "iptrunk_ipv4_network": str(faker.ipv4_network()), "iptrunk_ipv6_network": str(faker.ipv6_network()), } @pytest.fixture def mock_routers(iptrunk_data): first_call = [iptrunk_data["iptrunk_sideA_node_id"], iptrunk_data["iptrunk_sideB_node_id"], str(uuid4())] side_effects = [ first_call, first_call, [ (iptrunk_data["iptrunk_sideA_node_id"], "iptrunk_sideA_node_id description"), (iptrunk_data["iptrunk_sideB_node_id"], "iptrunk_sideB_node_id description"), (str(uuid4()), "random description"), ], ] with patch("gso.services.subscriptions.get_active_router_subscriptions") as mock_get_active_router_subscriptions: mock_get_active_router_subscriptions.side_effect = side_effects yield mock_get_active_router_subscriptions @patch("gso.api.v1.imports._start_process") def test_import_iptrunk_successful_with_mocked_process(mock_start_process, test_client, iptrunk_data, mock_routers): mock_start_process.return_value = "123e4567-e89b-12d3-a456-426655440000" response = test_client.post(IPTRUNK_IMPORT_API_URL, json=iptrunk_data) assert response.status_code == 201 assert response.json()["pid"] == "123e4567-e89b-12d3-a456-426655440000" @pytest.fixture def site_data(faker): return { "site_name": faker.name(), "site_city": faker.city(), "site_country": faker.country(), "site_country_code": faker.country_code(), "site_latitude": float(faker.latitude()), "site_longitude": float(faker.longitude()), "site_bgp_community_id": faker.pyint(), "site_internal_id": faker.pyint(), "site_tier": SiteTier.TIER1, "site_ts_address": faker.ipv4(), "customer": "GÉANT", } @pytest.fixture def router_data(faker, site_data): return { "hostname": "127.0.0.1", "router_role": RouterRole.PE, "router_vendor": RouterVendor.JUNIPER, "router_site": site_data["site_name"], "ts_port": 1234, "customer": "GÉANT", "is_ias_connected": True, "router_lo_ipv4_address": faker.ipv4(), "router_lo_ipv6_address": faker.ipv6(), "router_lo_iso_address": "TestAddress", } def test_import_site_endpoint(test_client, site_data): assert SubscriptionTable.query.all() == [] # Post data to the endpoint response = test_client.post(SITE_IMPORT_ENDPOINT, json=site_data) assert response.status_code == 201 assert "detail" in response.json() assert "pid" in response.json() subscription = subscriptions.retrieve_subscription_by_subscription_instance_value( resource_type="site_name", value=site_data["site_name"] ) assert subscription is not None def test_import_site_endpoint_with_existing_site(test_client, site_data): response = test_client.post(SITE_IMPORT_ENDPOINT, json=site_data) assert SubscriptionTable.query.count() == 1 assert response.status_code == 201 response = test_client.post(SITE_IMPORT_ENDPOINT, json=site_data) assert response.status_code == 409 assert SubscriptionTable.query.count() == 1 def test_import_site_endpoint_with_invalid_data(test_client, site_data): # invalid data, missing site_latitude and invalid site_longitude site_data.pop("site_latitude") site_data["site_longitude"] = "invalid" assert SubscriptionTable.query.count() == 0 response = test_client.post(SITE_IMPORT_ENDPOINT, json=site_data) assert response.status_code == 422 assert SubscriptionTable.query.count() == 0 response = response.json() assert response["detail"][0]["loc"] == ["body", "site_latitude"] assert response["detail"][0]["msg"] == "field required" assert response["detail"][1]["loc"] == ["body", "site_longitude"] assert response["detail"][1]["msg"] == "value is not a valid float" def test_import_router_endpoint(test_client, site_data, router_data): # Create a site first response = test_client.post(SITE_IMPORT_ENDPOINT, json=site_data) assert response.status_code == 201 assert SubscriptionTable.query.count() == 1 response = test_client.post(ROUTER_IMPORT_ENDPOINT, json=router_data) assert response.status_code == 201 assert SubscriptionTable.query.count() == 2 def test_import_router_endpoint_with_invalid_data(test_client, site_data, router_data): response = test_client.post(SITE_IMPORT_ENDPOINT, json=site_data) assert response.status_code == 201 assert SubscriptionTable.query.count() == 1 # invalid data, missing hostname and invalid router_lo_ipv6_address router_data.pop("hostname") router_data["router_lo_ipv6_address"] = "invalid" response = test_client.post(ROUTER_IMPORT_ENDPOINT, json=router_data) assert response.status_code == 422 assert SubscriptionTable.query.count() == 1 response = response.json() assert response["detail"][0]["loc"] == ["body", "hostname"] assert response["detail"][0]["msg"] == "field required" assert response["detail"][1]["loc"] == ["body", "router_lo_ipv6_address"] assert response["detail"][1]["msg"] == "value is not a valid IPv6 address" def test_import_iptrunk_successful_with_real_process(test_client, iptrunk_data, mock_routers): response = test_client.post(IPTRUNK_IMPORT_API_URL, json=iptrunk_data) assert response.status_code == 201 response = response.json() assert "detail" in response assert "pid" in response subscription = subscriptions.retrieve_subscription_by_subscription_instance_value( resource_type="geant_s_sid", value=iptrunk_data["geant_s_sid"] ) assert subscription is not None @patch("gso.api.v1.imports._start_process") def test_import_iptrunk_invalid_customer(mock_start_process, test_client, iptrunk_data, mock_routers): iptrunk_data["customer"] = "not_existing_customer" mock_start_process.return_value = "123e4567-e89b-12d3-a456-426655440000" response = test_client.post(IPTRUNK_IMPORT_API_URL, json=iptrunk_data) assert response.status_code == 422 assert response.json() == { "detail": [ {"loc": ["body", "customer"], "msg": "Customer not_existing_customer not found", "type": "value_error"} ] } @patch("gso.api.v1.imports._start_process") def test_import_iptrunk_invalid_router_id_side_a_and_b(mock_start_process, test_client, iptrunk_data): mock_start_process.return_value = "123e4567-e89b-12d3-a456-426655440000" response = test_client.post(IPTRUNK_IMPORT_API_URL, json=iptrunk_data) assert response.status_code == 422 assert response.json() == { "detail": [ {"loc": ["body", "iptrunk_sideA_node_id"], "msg": "Router not found", "type": "value_error"}, {"loc": ["body", "iptrunk_sideB_node_id"], "msg": "Router not found", "type": "value_error"}, ] } @patch("gso.api.v1.imports._start_process") def test_import_iptrunk_non_unique_members_side_a(mock_start_process, test_client, iptrunk_data, mock_routers): mock_start_process.return_value = "123e4567-e89b-12d3-a456-426655440000" iptrunk_data["iptrunk_sideA_ae_members"] = [5, 5, 5, 5, 5] iptrunk_data["iptrunk_sideB_ae_members"] = [4, 4, 4, 5, 5] response = test_client.post(IPTRUNK_IMPORT_API_URL, json=iptrunk_data) assert response.status_code == 422 assert response.json() == { "detail": [ {"loc": ["body", "iptrunk_sideA_ae_members"], "msg": "Items must be unique", "type": "value_error"}, {"loc": ["body", "iptrunk_sideB_ae_members"], "msg": "Items must be unique", "type": "value_error"}, { "loc": ["body", "__root__"], "msg": "Side A members should be at least 5 (iptrunk_minimum_links)", "type": "value_error", }, ] } @patch("gso.api.v1.imports._start_process") def test_iptrunk_import_fails_on_side_a_member_count_mismatch( mock_start_process, test_client, iptrunk_data, mock_routers ): mock_start_process.return_value = "123e4567-e89b-12d3-a456-426655440000" iptrunk_data["iptrunk_sideA_ae_members"].remove(iptrunk_data["iptrunk_sideA_ae_members"][0]) response = test_client.post(IPTRUNK_IMPORT_API_URL, json=iptrunk_data) assert response.status_code == 422 assert response.json() == { "detail": [ { "loc": ["body", "__root__"], "msg": "Side A members should be at least 5 (iptrunk_minimum_links)", "type": "value_error", } ] } @patch("gso.api.v1.imports._start_process") def test_iptrunk_import_fails_on_side_a_member_description_mismatch( mock_start_process, test_client, iptrunk_data, mock_routers ): mock_start_process.return_value = "123e4567-e89b-12d3-a456-426655440000" iptrunk_data["iptrunk_sideA_ae_members_descriptions"].remove( iptrunk_data["iptrunk_sideA_ae_members_descriptions"][0] ) response = test_client.post(IPTRUNK_IMPORT_API_URL, json=iptrunk_data) assert response.status_code == 422 assert response.json() == { "detail": [ { "loc": ["body", "__root__"], "msg": "Mismatch in Side A members and their descriptions", "type": "value_error", } ] } @patch("gso.api.v1.imports._start_process") def test_iptrunk_import_fails_on_side_a_and_b_members_mismatch( mock_start_process, test_client, iptrunk_data, mock_routers ): mock_start_process.return_value = "123e4567-e89b-12d3-a456-426655440000" iptrunk_data["iptrunk_sideB_ae_members"].remove(iptrunk_data["iptrunk_sideB_ae_members"][0]) response = test_client.post(IPTRUNK_IMPORT_API_URL, json=iptrunk_data) assert response.status_code == 422 assert response.json() == { "detail": [{"loc": ["body", "__root__"], "msg": "Mismatch between Side A and B members", "type": "value_error"}] } @patch("gso.api.v1.imports._start_process") def test_iptrunk_import_fails_on_side_b_member_description_mismatch( mock_start_process, test_client, iptrunk_data, mock_routers ): mock_start_process.return_value = "123e4567-e89b-12d3-a456-426655440000" iptrunk_data["iptrunk_sideB_ae_members_descriptions"].remove( iptrunk_data["iptrunk_sideB_ae_members_descriptions"][0] ) response = test_client.post(IPTRUNK_IMPORT_API_URL, json=iptrunk_data) assert response.status_code == 422 assert response.json() == { "detail": [ { "loc": ["body", "__root__"], "msg": "Mismatch in Side B members and their descriptions", "type": "value_error", } ] }