Merge origin/3.1 into image-manager-out-of-sync

Preserve image reconciliation, locking, and regression coverage while integrating upstream release, dependency, typing, and formatting updates. Align feature code with the new Ruff checks.
This commit is contained in:
Cristi committed 2026-09-28 23:16:24 +03:00
commit 42d30651da
497 files changed
+22962 -24152

No files matched your search

+44 -16
View File
@@ -30,28 +30,56 @@ pytestmark = pytest.mark.asyncio
class TestCapabilitiesRoutes:
async def test_get(self, app: FastAPI, compute_client: AsyncClient, windows_platform) -> None:
response = await compute_client.get(app.url_path_for("compute:get_capabilities"))
assert response.status_code == status.HTTP_200_OK
assert response.json() == {'node_types': ['cloud', 'ethernet_hub', 'ethernet_switch', 'nat', 'vpcs', 'virtualbox', 'dynamips', 'frame_relay_switch', 'atm_switch', 'qemu', 'vmware', 'docker', 'iou'],
'version': __version__,
'platform': sys.platform,
'cpus': psutil.cpu_count(logical=True),
'memory': psutil.virtual_memory().total,
'disk_size': psutil.disk_usage(get_default_project_directory()).total,
}
assert response.json() == {
"node_types": [
"cloud",
"ethernet_hub",
"ethernet_switch",
"nat",
"vpcs",
"virtualbox",
"dynamips",
"frame_relay_switch",
"atm_switch",
"qemu",
"vmware",
"docker",
"iou",
],
"version": __version__,
"platform": sys.platform,
"cpus": psutil.cpu_count(logical=True),
"memory": psutil.virtual_memory().total,
"disk_size": psutil.disk_usage(get_default_project_directory()).total,
}
async def test_get_on_gns3vm(self, app: FastAPI, compute_client: AsyncClient, on_gns3vm) -> None:
response = await compute_client.get(app.url_path_for("compute:get_capabilities"))
assert response.status_code == status.HTTP_200_OK
assert response.json() == {'node_types': ['cloud', 'ethernet_hub', 'ethernet_switch', 'nat', 'vpcs', 'virtualbox', 'dynamips', 'frame_relay_switch', 'atm_switch', 'qemu', 'vmware', 'docker', 'iou'],
'version': __version__,
'platform': sys.platform,
'cpus': psutil.cpu_count(logical=True),
'memory': psutil.virtual_memory().total,
'disk_size': psutil.disk_usage(get_default_project_directory()).total,
}
assert response.json() == {
"node_types": [
"cloud",
"ethernet_hub",
"ethernet_switch",
"nat",
"vpcs",
"virtualbox",
"dynamips",
"frame_relay_switch",
"atm_switch",
"qemu",
"vmware",
"docker",
"iou",
],
"version": __version__,
"platform": sys.platform,
"cpus": psutil.cpu_count(logical=True),
"memory": psutil.virtual_memory().total,
"disk_size": psutil.disk_usage(get_default_project_directory()).total,
}
+87 -137
View File
@@ -29,231 +29,181 @@ pytestmark = pytest.mark.asyncio
class TestCloudNodesRoutes:
@pytest_asyncio.fixture
async def vm(self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, on_gns3vm) -> dict:
with asyncio_patch("gns3server.compute.builtin.nodes.cloud.Cloud._start_ubridge"):
response = await compute_client.post(app.url_path_for("compute:create_cloud", project_id=compute_project.id),
json={"name": "Cloud 1"})
response = await compute_client.post(
app.url_path_for("compute:create_cloud", project_id=compute_project.id), json={"name": "Cloud 1"}
)
assert response.status_code == status.HTTP_201_CREATED
return response.json()
async def test_cloud_create(
self, app: FastAPI,
compute_client: AsyncClient,
compute_project: Project
) -> None:
async def test_cloud_create(self, app: FastAPI, compute_client: AsyncClient, compute_project: Project) -> None:
with asyncio_patch("gns3server.compute.builtin.nodes.cloud.Cloud._start_ubridge"):
response = await compute_client.post(app.url_path_for("compute:create_cloud", project_id=compute_project.id),
json={"name": "Cloud 1"})
response = await compute_client.post(
app.url_path_for("compute:create_cloud", project_id=compute_project.id), json={"name": "Cloud 1"}
)
assert response.status_code == 201
assert response.json()["name"] == "Cloud 1"
assert response.json()["project_id"] == compute_project.id
async def test_get_cloud(
self, app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
vm: dict
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, vm: dict
) -> None:
response = await compute_client.get(app.url_path_for("compute:get_cloud", project_id=vm["project_id"], node_id=vm["node_id"]))
response = await compute_client.get(
app.url_path_for("compute:get_cloud", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "Cloud 1"
assert response.json()["project_id"] == compute_project.id
assert response.json()["status"] == "started"
async def test_cloud_nio_create_udp(
self, app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
vm: dict
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, vm: dict
) -> None:
params = {"type": "nio_udp",
"lport": 4242,
"rport": 4343,
"rhost": "127.0.0.1"}
url = app.url_path_for("compute:create_cloud_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
response = await compute_client.post(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_udp"
async def test_cloud_nio_update_udp(
self, app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
vm: dict
) -> None:
params = {
"type": "nio_udp",
"lport": 4242,
"rport": 4343,
"rhost": "127.0.0.1",
"filters": {"packet_loss": 10}
}
params = {"type": "nio_udp", "lport": 4242, "rport": 4343, "rhost": "127.0.0.1"}
url = app.url_path_for(
"compute:create_cloud_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
port_number="0",
)
response = await compute_client.post(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_udp"
async def test_cloud_nio_update_udp(
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, vm: dict
) -> None:
params = {"type": "nio_udp", "lport": 4242, "rport": 4343, "rhost": "127.0.0.1", "filters": {"packet_loss": 10}}
url = app.url_path_for(
"compute:create_cloud_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
response = await compute_client.post(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["filters"] == {"packet_loss": 10}
params["filters"].clear()
url = app.url_path_for("compute:create_cloud_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
url = app.url_path_for(
"compute:create_cloud_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
response = await compute_client.put(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_udp"
assert response.json()["filters"] == {}
async def test_cloud_delete_nio(
self, app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
vm: dict
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, vm: dict
) -> None:
params = {"type": "nio_udp",
"lport": 4242,
"rport": 4343,
"rhost": "127.0.0.1"}
params = {"type": "nio_udp", "lport": 4242, "rport": 4343, "rhost": "127.0.0.1"}
url = app.url_path_for("compute:create_cloud_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
url = app.url_path_for(
"compute:create_cloud_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
await compute_client.post(url, json=params)
url = app.url_path_for("compute:delete_cloud_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
url = app.url_path_for(
"compute:delete_cloud_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
with asyncio_patch("gns3server.compute.builtin.nodes.cloud.Cloud._start_ubridge"):
response = await compute_client.delete(url)
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_cloud_delete(
self, app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
vm: dict
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, vm: dict
) -> None:
response = await compute_client.delete(
app.url_path_for(
"compute:delete_cloud",
project_id=vm["project_id"],
node_id=vm["node_id"]
)
app.url_path_for("compute:delete_cloud", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_cloud_stop(
self, app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
vm: dict
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, vm: dict
) -> None:
response = await compute_client.post(
app.url_path_for(
"compute:stop_cloud",
project_id=vm["project_id"],
node_id=vm["node_id"]
)
app.url_path_for("compute:stop_cloud", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
async def test_cloud_suspend(
self, app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
vm: dict
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, vm: dict
) -> None:
response = await compute_client.post(
app.url_path_for(
"compute:suspend_cloud",
project_id=vm["project_id"],
node_id=vm["node_id"]
)
app.url_path_for("compute:suspend_cloud", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
async def test_cloud_update(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
async def test_cloud_update(
self, app: FastAPI,
compute_client: AsyncClient,
vm: dict
) -> None:
response = await compute_client.put(app.url_path_for("compute:update_cloud", project_id=vm["project_id"], node_id=vm["node_id"]),
json={"name": "test"})
response = await compute_client.put(
app.url_path_for("compute:update_cloud", project_id=vm["project_id"], node_id=vm["node_id"]),
json={"name": "test"},
)
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "test"
async def test_cloud_start_capture(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
async def test_cloud_start_capture(
self, app: FastAPI,
compute_client: AsyncClient,
vm: dict
) -> None:
params = {
"capture_file_name": "test.pcap",
"data_link_type": "DLT_EN10MB"
}
params = {"capture_file_name": "test.pcap", "data_link_type": "DLT_EN10MB"}
with asyncio_patch("gns3server.compute.builtin.nodes.cloud.Cloud.start_capture") as mock:
response = await compute_client.post(app.url_path_for("compute:start_cloud_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0"),
json=params)
response = await compute_client.post(
app.url_path_for(
"compute:start_cloud_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
),
json=params,
)
assert response.status_code == status.HTTP_200_OK
assert mock.called
assert "test.pcap" in response.json()["pcap_file_path"]
async def test_cloud_stop_capture(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.builtin.nodes.cloud.Cloud.stop_capture") as mock:
response = await compute_client.post(app.url_path_for("compute:stop_cloud_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0"))
response = await compute_client.post(
app.url_path_for(
"compute:stop_cloud_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
)
assert response.status_code == status.HTTP_204_NO_CONTENT
assert mock.called
# @pytest.mark.asyncio
# async def test_cloud_pcap(self, compute_api, vm, compute_project):
#
+13 -20
View File
@@ -30,18 +30,13 @@ pytestmark = pytest.mark.asyncio
class TestComputeRoutes:
async def test_udp_allocation(self, app: FastAPI, compute_client: AsyncClient, compute_project: Project) -> None:
async def test_udp_allocation(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project
) -> None:
response = await compute_client.post(app.url_path_for("compute:allocate_udp_port", project_id=compute_project.id), json={})
response = await compute_client.post(
app.url_path_for("compute:allocate_udp_port", project_id=compute_project.id), json={}
)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()['udp_port'] is not None
assert response.json()["udp_port"] is not None
async def test_interfaces(self, app: FastAPI, compute_client: AsyncClient) -> None:
@@ -49,33 +44,30 @@ class TestComputeRoutes:
assert response.status_code == status.HTTP_200_OK
assert isinstance(response.json(), list)
async def test_version_output(self, app: FastAPI, compute_client: AsyncClient) -> None:
response = await compute_client.get(app.url_path_for("compute:compute_version"))
assert response.status_code == status.HTTP_200_OK
assert response.json() == {'version': __version__}
assert response.json() == {"version": __version__}
async def test_compute_authentication(self, app: FastAPI, compute_client: AsyncClient) -> None:
response = await compute_client.get(app.url_path_for("compute:compute_version"), auth=("admin", "invalid_password"))
response = await compute_client.get(
app.url_path_for("compute:compute_version"), auth=("admin", "invalid_password")
)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
# @pytest.mark.asyncio
# async def test_debug_output(compute_api):
#
# response = await compute_api.get('/debug')
# assert response.status_code == 200
async def test_statistics_output(self, app: FastAPI, compute_client: AsyncClient) -> None:
response = await compute_client.get(app.url_path_for("compute:compute_statistics"))
assert response.status_code == status.HTTP_200_OK
async def test_compute_auth_disabled(self, app: FastAPI, compute_client: AsyncClient) -> None:
mock_settings = MagicMock()
@@ -85,9 +77,10 @@ class TestComputeRoutes:
mock_server_settings.compute_password.get_secret_value.return_value = "testpass"
mock_settings.settings.Server = mock_server_settings
with patch("gns3server.api.routes.compute.dependencies.authentication.Config.instance", return_value=mock_settings):
with patch(
"gns3server.api.routes.compute.dependencies.authentication.Config.instance", return_value=mock_settings
):
response = await compute_client.get(
app.url_path_for("compute:compute_version"),
auth=("wrong_user", "wrong_password")
app.url_path_for("compute:compute_version"), auth=("wrong_user", "wrong_password")
)
assert response.status_code == status.HTTP_200_OK
+95 -112
View File
@@ -31,7 +31,6 @@ pytestmark = pytest.mark.asyncio
class TestDockerNodesRoutes:
@pytest.fixture
def base_params(self) -> dict:
"""Return standard parameters"""
@@ -44,11 +43,10 @@ class TestDockerNodesRoutes:
"environment": "YES=1\nNO=0",
"console_type": "telnet",
"console_resolution": "1280x1024",
"extra_hosts": "test:127.0.0.1"
"extra_hosts": "test:127.0.0.1",
}
return params
# @pytest.yield_fixture(autouse=True)
# def mock_connection():
#
@@ -58,30 +56,20 @@ class TestDockerNodesRoutes:
# yield
# Docker._instance = None
@pytest_asyncio.fixture
async def vm(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
base_params: dict
) -> dict:
async def vm(self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, base_params: dict) -> dict:
with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "nginx"}]):
with asyncio_patch("gns3server.compute.docker.Docker.query", return_value={"Id": "8bd8153ea8f5"}):
with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="exited"):
response = await compute_client.post(app.url_path_for("compute:create_docker_node", project_id=compute_project.id),
json=base_params)
response = await compute_client.post(
app.url_path_for("compute:create_docker_node", project_id=compute_project.id), json=base_params
)
assert response.status_code == status.HTTP_201_CREATED
return response.json()
async def test_docker_create(
self, app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
base_params: dict
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, base_params: dict
) -> None:
with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "nginx"}]):
@@ -99,12 +87,8 @@ class TestDockerNodesRoutes:
assert response.json()["console_resolution"] == "1280x1024"
assert response.json()["extra_hosts"] == "test:127.0.0.1"
async def test_docker_create_iol_startup_config(
self, app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
base_params: dict
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, base_params: dict
) -> None:
"""
The controller materializes the GNS3_IOL_STARTUP_CONFIG knob into
@@ -157,13 +141,13 @@ class TestDockerNodesRoutes:
),
)
async def test_docker_create_with_invalid_name(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
base_params: dict,
name: str,
status_code: int
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
base_params: dict,
name: str,
status_code: int,
) -> None:
base_params["name"] = name
@@ -174,81 +158,71 @@ class TestDockerNodesRoutes:
)
assert response.status_code == status_code
async def test_docker_start(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.start", return_value=True) as mock:
response = await compute_client.post(app.url_path_for("compute:start_docker_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
response = await compute_client.post(
app.url_path_for("compute:start_docker_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_docker_stop(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.stop", return_value=True) as mock:
response = await compute_client.post(app.url_path_for("compute:stop_docker_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
response = await compute_client.post(
app.url_path_for("compute:stop_docker_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_docker_reload(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.restart", return_value=True) as mock:
response = await compute_client.post(app.url_path_for("compute:reload_docker_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
response = await compute_client.post(
app.url_path_for("compute:reload_docker_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_docker_delete(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.delete", return_value=True) as mock:
response = await compute_client.delete(app.url_path_for("compute:delete_docker_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
response = await compute_client.delete(
app.url_path_for("compute:delete_docker_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_docker_pause(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.pause", return_value=True) as mock:
response = await compute_client.post(app.url_path_for("compute:pause_docker_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
response = await compute_client.post(
app.url_path_for("compute:pause_docker_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_docker_unpause(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.unpause", return_value=True) as mock:
response = await compute_client.post(app.url_path_for("compute:unpause_docker_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
response = await compute_client.post(
app.url_path_for("compute:unpause_docker_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_docker_nio_create_udp(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
params = {
"type": "nio_udp",
"lport": 4242,
"rport": 4343,
"rhost": "127.0.0.1"}
params = {"type": "nio_udp", "lport": 4242, "rport": 4343, "rhost": "127.0.0.1"}
url = app.url_path_for("compute:create_docker_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
url = app.url_path_for(
"compute:create_docker_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
response = await compute_client.post(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_udp"
@@ -281,7 +255,6 @@ class TestDockerNodesRoutes:
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["added"] == 2
async def test_docker_update_nio(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
params = {
@@ -290,25 +263,29 @@ class TestDockerNodesRoutes:
"rport": 4343,
"rhost": "127.0.0.1",
"filters": {"packet_loss": 10},
"suspend": False
"suspend": False,
}
url = app.url_path_for("compute:create_docker_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
url = app.url_path_for(
"compute:create_docker_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
response = await compute_client.post(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["filters"] == {"packet_loss": 10}
params["filters"] = {}
params["suspend"] = True
url = app.url_path_for("compute:update_docker_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
url = app.url_path_for(
"compute:update_docker_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.adapter_update_nio_binding"):
response = await compute_client.put(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
@@ -318,30 +295,34 @@ class TestDockerNodesRoutes:
async def test_docker_delete_nio(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
url = app.url_path_for("compute:delete_docker_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
url = app.url_path_for(
"compute:delete_docker_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.adapter_remove_nio_binding"):
response = await compute_client.delete(url)
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_docker_update(self, app: FastAPI, compute_client: AsyncClient, vm: dict, free_console_port: int) -> None:
async def test_docker_update(
self, app: FastAPI, compute_client: AsyncClient, vm: dict, free_console_port: int
) -> None:
params = {
"name": "test",
"console": free_console_port,
"start_command": "yes",
"environment": "GNS3=1\nGNS4=0",
"extra_hosts": "test:127.0.0.1"
"extra_hosts": "test:127.0.0.1",
}
with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.update") as mock:
response = await compute_client.put(app.url_path_for("compute:update_docker_node",
project_id=vm["project_id"],
node_id=vm["node_id"]), json=params)
response = await compute_client.put(
app.url_path_for("compute:update_docker_node", project_id=vm["project_id"], node_id=vm["node_id"]),
json=params,
)
assert response.status_code == 200
assert mock.called
@@ -352,10 +333,7 @@ class TestDockerNodesRoutes:
assert response.json()["extra_hosts"] == "test:127.0.0.1"
async def test_docker_update_empty_strings_do_not_recreate_container(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project
) -> None:
"""
Web clients serialize empty form fields as "" while unset values are
@@ -390,14 +368,15 @@ class TestDockerNodesRoutes:
assert response.json()["start_command"] is None
assert response.json()["console_http_path"] == "/"
async def test_docker_start_capture(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
url = app.url_path_for("compute:start_docker_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
url = app.url_path_for(
"compute:start_docker_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
with patch("gns3server.compute.docker.docker_vm.DockerVM.is_running", return_value=True):
with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.start_capture") as mock:
@@ -407,14 +386,15 @@ class TestDockerNodesRoutes:
assert mock.called
assert "test.pcap" in response.json()["pcap_file_path"]
async def test_docker_stop_capture(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
url = app.url_path_for("compute:stop_docker_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
url = app.url_path_for(
"compute:stop_docker_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
with patch("gns3server.compute.docker.docker_vm.DockerVM.is_running", return_value=True):
with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.stop_capture") as mock:
@@ -422,18 +402,21 @@ class TestDockerNodesRoutes:
assert response.status_code == status.HTTP_204_NO_CONTENT
assert mock.called
async def test_docker_duplicate(self, app: FastAPI, compute_client: AsyncClient, vm: dict, base_params: dict) -> None:
async def test_docker_duplicate(
self, app: FastAPI, compute_client: AsyncClient, vm: dict, base_params: dict
) -> None:
# create destination node first
with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "nginx"}]):
with asyncio_patch("gns3server.compute.docker.Docker.query", return_value={"Id": "8bd8153ea8f5"}):
response = await compute_client.post(app.url_path_for("compute:create_docker_node",
project_id=vm["project_id"]), json=base_params)
response = await compute_client.post(
app.url_path_for("compute:create_docker_node", project_id=vm["project_id"]), json=base_params
)
assert response.status_code == status.HTTP_201_CREATED
params = {"destination_node_id": response.json()["node_id"]}
response = await compute_client.post(app.url_path_for("compute:duplicate_docker_node",
project_id=vm["project_id"],
node_id=vm["node_id"]), json=params)
response = await compute_client.post(
app.url_path_for("compute:duplicate_docker_node", project_id=vm["project_id"], node_id=vm["node_id"]),
json=params,
)
assert response.status_code == status.HTTP_201_CREATED
+19 -29
View File
@@ -28,7 +28,6 @@ pytestmark = pytest.mark.asyncio
class TestDynamipsNodesRoutes:
# @pytest.yield_fixture(scope="module")
# async def vm(compute_api, compute_project, fake_image):
#
@@ -47,7 +46,6 @@ class TestDynamipsNodesRoutes:
# #with asyncio_patch("gns3server.compute.dynamips.Dynamips.find_dynamips", return_value=dynamips_path):
# # yield response.json
# async def test_dynamips_vm_create(compute_api, compute_project, fake_image):
#
# params = {
@@ -66,7 +64,6 @@ class TestDynamipsNodesRoutes:
# assert response.json["project_id"] == compute_project.id
# assert response.json["dynamips_id"]
# def test_dynamips_vm_get(compute_api, project, vm):
# response = compute_api.get("/projects/{project_id}/dynamips/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]), example=True)
# assert response.status == 200
@@ -102,7 +99,6 @@ class TestDynamipsNodesRoutes:
# assert mock.called
# assert response.status == 204
# def test_vbox_nio_create_udp(compute_api, vm):
#
# with asyncio_patch('gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.adapter_add_nio_binding') as mock:
@@ -142,48 +138,42 @@ class TestDynamipsNodesRoutes:
# assert response.json["name"] == "test"
# assert response.json["console"] == free_console_port
@pytest.fixture
def fake_image(self, tmpdir) -> str:
"""Create a fake Dynamips image on disk"""
path = str(tmpdir / "7200.bin")
with open(path, "wb+") as f:
f.write(b'\x7fELF\x01\x02\x01')
f.write(b"\x7fELF\x01\x02\x01")
os.chmod(path, stat.S_IREAD)
return path
@pytest.fixture
def fake_file(self, tmpdir) -> str:
"""Create a fake file disk"""
path = str(tmpdir / "7200.txt")
with open(path, "w+") as f:
f.write('1')
f.write("1")
os.chmod(path, stat.S_IREAD)
return path
async def test_images(
self, app: FastAPI,
compute_client: AsyncClient,
tmpdir, fake_image: str,
fake_file: str
self, app: FastAPI, compute_client: AsyncClient, tmpdir, fake_image: str, fake_file: str
) -> None:
with patch("gns3server.utils.images.default_images_directory", return_value=str(tmpdir)):
response = await compute_client.get(app.url_path_for("compute:get_dynamips_images"))
assert response.status_code == status.HTTP_200_OK
assert response.json() == [{"filename": "7200.bin",
"path": "7200.bin",
"filesize": 7,
"md5sum": "b0d5aa897d937aced5a6b1046e8f7e2e"}]
assert response.json() == [
{"filename": "7200.bin", "path": "7200.bin", "filesize": 7, "md5sum": "b0d5aa897d937aced5a6b1046e8f7e2e"}
]
async def test_upload_image(self, app: FastAPI, compute_client: AsyncClient, images_dir: str) -> None:
response = await compute_client.post(app.url_path_for("compute:upload_dynamips_image", filename="test2"), content=b"TEST")
response = await compute_client.post(
app.url_path_for("compute:upload_dynamips_image", filename="test2"), content=b"TEST"
)
assert response.status_code == status.HTTP_204_NO_CONTENT
with open(os.path.join(images_dir, "IOS", "test2")) as f:
@@ -193,35 +183,33 @@ class TestDynamipsNodesRoutes:
checksum = f.read()
assert checksum == "033bd94b1168d7e4f0d644c3c95e35bf"
async def test_upload_image_forbidden_location(self, app: FastAPI, compute_client: AsyncClient) -> None:
file_path = "%2e%2e/hello"
response = await compute_client.post(app.url_path_for("compute:upload_dynamips_image", filename=file_path), content=b"TEST")
response = await compute_client.post(
app.url_path_for("compute:upload_dynamips_image", filename=file_path), content=b"TEST"
)
assert response.status_code == status.HTTP_403_FORBIDDEN
async def test_download_image(self, app: FastAPI, compute_client: AsyncClient, images_dir: str) -> None:
response = await compute_client.post(app.url_path_for("compute:upload_dynamips_image", filename="test3"), content=b"TEST")
response = await compute_client.post(
app.url_path_for("compute:upload_dynamips_image", filename="test3"), content=b"TEST"
)
assert response.status_code == status.HTTP_204_NO_CONTENT
response = await compute_client.get(app.url_path_for("compute:download_dynamips_image", filename="test3"))
assert response.status_code == status.HTTP_200_OK
async def test_download_image_forbidden(self, app: FastAPI, compute_client: AsyncClient, tmpdir) -> None:
file_path = "foo/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd"
response = await compute_client.get(app.url_path_for("compute:download_dynamips_image", filename=file_path))
assert response.status_code == status.HTTP_403_FORBIDDEN
@pytest.mark.skipif(os.getuid() == 0, reason="Root can delete any image")
async def test_upload_image_permission_denied(
self, app: FastAPI,
compute_client: AsyncClient,
images_dir: str
self, app: FastAPI, compute_client: AsyncClient, images_dir: str
) -> None:
os.makedirs(os.path.join(images_dir, "IOS"), exist_ok=True)
@@ -229,5 +217,7 @@ class TestDynamipsNodesRoutes:
f.write("")
os.chmod(os.path.join(images_dir, "IOS", "test2.tmp"), 0)
response = await compute_client.post(app.url_path_for("compute:upload_dynamips_image", filename="test2"), content=b"TEST")
response = await compute_client.post(
app.url_path_for("compute:upload_dynamips_image", filename="test2"), content=b"TEST"
)
assert response.status_code == status.HTTP_409_CONFLICT
@@ -34,12 +34,14 @@ pytestmark = pytest.mark.asyncio
class TestEthernetSwitchNodesRoutes:
@pytest_asyncio.fixture(autouse=True)
async def stub_ubridge(self):
"""Keep uBridge from really starting and capture every command."""
with asyncio_patch(f"{_NODE}._start_ubridge"), asyncio_patch(f"{_NODE}._stop_ubridge"), \
asyncio_patch(f"{_NODE}._ubridge_send"):
with (
asyncio_patch(f"{_NODE}._start_ubridge"),
asyncio_patch(f"{_NODE}._stop_ubridge"),
asyncio_patch(f"{_NODE}._ubridge_send"),
):
yield
@pytest_asyncio.fixture
@@ -47,8 +49,7 @@ class TestEthernetSwitchNodesRoutes:
params = {"name": "Ethernet Switch"}
response = await compute_client.post(
app.url_path_for("compute:create_ethernet_switch", project_id=compute_project.id),
json=params
app.url_path_for("compute:create_ethernet_switch", project_id=compute_project.id), json=params
)
assert response.status_code == status.HTTP_201_CREATED
@@ -65,15 +66,12 @@ class TestEthernetSwitchNodesRoutes:
return {"type": "nio_udp", "lport": 4242, "rport": 4343, "rhost": "127.0.0.1"}
async def test_ethernet_switch_create(
self, app: FastAPI,
compute_client: AsyncClient,
compute_project: Project
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project
) -> None:
params = {"name": "Ethernet Switch 1"}
response = await compute_client.post(
app.url_path_for("compute:create_ethernet_switch", project_id=compute_project.id),
json=params
app.url_path_for("compute:create_ethernet_switch", project_id=compute_project.id), json=params
)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "Ethernet Switch 1"
@@ -83,25 +81,24 @@ class TestEthernetSwitchNodesRoutes:
# creation stands up the kernel bridge with VLAN filtering
node = compute_project.get_node(response.json()["node_id"])
br = node._bridge_name
node._ubridge_send.assert_has_calls([
call(f'brctl delete "{br}"'),
call(f'brctl create "{br}"'),
call(f'link set "{br}" up'),
call(f'brctl vlanfiltering "{br}" on'),
])
node._ubridge_send.assert_has_calls(
[
call(f'brctl delete "{br}"'),
call(f'brctl create "{br}"'),
call(f'link set "{br}" up'),
call(f'brctl vlanfiltering "{br}" on'),
]
)
async def test_ethernet_switch_get(
self, app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
ethernet_switch: dict
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, ethernet_switch: dict
) -> None:
response = await compute_client.get(
app.url_path_for(
"compute:get_ethernet_switch",
project_id=ethernet_switch["project_id"],
node_id=ethernet_switch["node_id"]
node_id=ethernet_switch["node_id"],
)
)
assert response.status_code == status.HTTP_200_OK
@@ -110,18 +107,13 @@ class TestEthernetSwitchNodesRoutes:
assert response.json()["status"] == "started"
async def test_ethernet_switch_duplicate(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
ethernet_switch: dict
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, ethernet_switch: dict
) -> None:
# create destination switch first
params = {"name": "Ethernet Switch 2"}
response = await compute_client.post(
app.url_path_for("compute:create_ethernet_switch", project_id=compute_project.id),
json=params
app.url_path_for("compute:create_ethernet_switch", project_id=compute_project.id), json=params
)
assert response.status_code == status.HTTP_201_CREATED
@@ -130,16 +122,14 @@ class TestEthernetSwitchNodesRoutes:
app.url_path_for(
"compute:duplicate_ethernet_switch",
project_id=ethernet_switch["project_id"],
node_id=ethernet_switch["node_id"]), json=params
node_id=ethernet_switch["node_id"],
),
json=params,
)
assert response.status_code == status.HTTP_201_CREATED
async def test_ethernet_switch_update(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
ethernet_switch: dict
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, ethernet_switch: dict
) -> None:
params = {"name": "test", "console_type": "none"}
@@ -148,8 +138,9 @@ class TestEthernetSwitchNodesRoutes:
app.url_path_for(
"compute:update_ethernet_switch",
project_id=ethernet_switch["project_id"],
node_id=ethernet_switch["node_id"]),
json=params
node_id=ethernet_switch["node_id"],
),
json=params,
)
assert response.status_code == status.HTTP_200_OK
@@ -160,11 +151,7 @@ class TestEthernetSwitchNodesRoutes:
node._ubridge_send.assert_not_called()
async def test_ethernet_switch_update_ports_qinq_proto(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
ethernet_switch: dict
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, ethernet_switch: dict
) -> None:
# a QinQ port with the 802.1ad ethertype must switch the bridge protocol
@@ -179,8 +166,9 @@ class TestEthernetSwitchNodesRoutes:
app.url_path_for(
"compute:update_ethernet_switch",
project_id=ethernet_switch["project_id"],
node_id=ethernet_switch["node_id"]),
json=port_params
node_id=ethernet_switch["node_id"],
),
json=port_params,
)
assert response.status_code == status.HTTP_200_OK
@@ -190,55 +178,60 @@ class TestEthernetSwitchNodesRoutes:
@pytest.mark.parametrize(
"ports_settings",
(
{"name": "Ethernet0", "port_number": 0, "type": "dot42q", "vlan": 1}, # bad type
{"name": "Ethernet0", "port_number": 0, "type": "access"}, # missing vlan
{"name": "Ethernet0", "port_number": 0, "type": "dot1q", "vlan": 1,
"ethertype": "0x88A8"}, # ethertype only for qinq
{"name": "Ethernet0", "port_number": 0, "type": "qinq", "vlan": 1,
"ethertype": "0x4242"}, # bad ethertype
{"name": "Ethernet0", "port_number": 0, "type": "access", "vlan": 0}, # vlan < 1
{"name": "Ethernet0", "port_number": 0, "type": "access", "vlan": 4242}, # vlan > 4094
)
{"name": "Ethernet0", "port_number": 0, "type": "dot42q", "vlan": 1}, # bad type
{"name": "Ethernet0", "port_number": 0, "type": "access"}, # missing vlan
{
"name": "Ethernet0",
"port_number": 0,
"type": "dot1q",
"vlan": 1,
"ethertype": "0x88A8",
}, # ethertype only for qinq
{"name": "Ethernet0", "port_number": 0, "type": "qinq", "vlan": 1, "ethertype": "0x4242"}, # bad ethertype
{"name": "Ethernet0", "port_number": 0, "type": "access", "vlan": 0}, # vlan < 1
{"name": "Ethernet0", "port_number": 0, "type": "access", "vlan": 4242}, # vlan > 4094
),
)
async def test_ethernet_switch_update_ports_invalid(
self,
app: FastAPI,
compute_client: AsyncClient,
ethernet_switch: dict,
ports_settings: dict,
self,
app: FastAPI,
compute_client: AsyncClient,
ethernet_switch: dict,
ports_settings: dict,
) -> None:
response = await compute_client.put(
app.url_path_for(
"compute:update_ethernet_switch",
project_id=ethernet_switch["project_id"],
node_id=ethernet_switch["node_id"]),
json={"ports_mapping": [ports_settings]}
node_id=ethernet_switch["node_id"],
),
json={"ports_mapping": [ports_settings]},
)
assert response.status_code == status.HTTP_422_UNPROCESSABLE_CONTENT
async def test_ethernet_switch_delete(
self, app: FastAPI,
compute_client: AsyncClient,
ethernet_switch: dict
self, app: FastAPI, compute_client: AsyncClient, ethernet_switch: dict
) -> None:
response = await compute_client.delete(
app.url_path_for(
"compute:delete_ethernet_switch",
project_id=ethernet_switch["project_id"],
node_id=ethernet_switch["node_id"]
node_id=ethernet_switch["node_id"],
)
)
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_ethernet_switch_start(self, app: FastAPI, compute_client: AsyncClient, ethernet_switch: dict) -> None:
async def test_ethernet_switch_start(
self, app: FastAPI, compute_client: AsyncClient, ethernet_switch: dict
) -> None:
response = await compute_client.post(
app.url_path_for(
"compute:start_ethernet_switch",
project_id=ethernet_switch["project_id"],
node_id=ethernet_switch["node_id"]
node_id=ethernet_switch["node_id"],
)
)
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
@@ -249,39 +242,39 @@ class TestEthernetSwitchNodesRoutes:
app.url_path_for(
"compute:stop_ethernet_switch",
project_id=ethernet_switch["project_id"],
node_id=ethernet_switch["node_id"]
node_id=ethernet_switch["node_id"],
)
)
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
async def test_ethernet_switch_suspend(self, app: FastAPI, compute_client: AsyncClient, ethernet_switch: dict) -> None:
async def test_ethernet_switch_suspend(
self, app: FastAPI, compute_client: AsyncClient, ethernet_switch: dict
) -> None:
response = await compute_client.post(
app.url_path_for(
"compute:suspend_ethernet_switch",
project_id=ethernet_switch["project_id"],
node_id=ethernet_switch["node_id"]
node_id=ethernet_switch["node_id"],
)
)
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
async def test_ethernet_switch_reload(self, app: FastAPI, compute_client: AsyncClient, ethernet_switch: dict) -> None:
async def test_ethernet_switch_reload(
self, app: FastAPI, compute_client: AsyncClient, ethernet_switch: dict
) -> None:
response = await compute_client.post(
app.url_path_for(
"compute:reload_ethernet_switch",
project_id=ethernet_switch["project_id"],
node_id=ethernet_switch["node_id"]
node_id=ethernet_switch["node_id"],
)
)
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
async def test_ethernet_switch_create_udp_access(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
ethernet_switch: dict
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, ethernet_switch: dict
) -> None:
url = app.url_path_for(
@@ -289,7 +282,7 @@ class TestEthernetSwitchNodesRoutes:
project_id=ethernet_switch["project_id"],
node_id=ethernet_switch["node_id"],
adapter_number="0",
port_number="0"
port_number="0",
)
response = await compute_client.post(url, json=self._udp_params())
assert response.status_code == status.HTTP_201_CREATED
@@ -301,23 +294,21 @@ class TestEthernetSwitchNodesRoutes:
tap = f"{br}-0"
relay = f"{node.id}-0"
# access VLAN 1 (default): drop default PVID 1, re-add 1 as PVID/untagged
node._ubridge_send.assert_has_calls([
call(f"bridge create {relay}"),
call(f'bridge add_nio_tap {relay} "{tap}"'),
call(f'brctl addif "{br}" "{tap}"'),
call(f'brctl vlan_del "{br}" "{tap}" 1'),
call(f'brctl vlan_add "{br}" "{tap}" 1 pvid untagged'),
call(f"bridge add_nio_udp {relay} {nio.lport} {nio.rhost} {nio.rport}"),
call(f"bridge reset_packet_filters {relay}"),
call(f"bridge start {relay}"),
])
node._ubridge_send.assert_has_calls(
[
call(f"bridge create {relay}"),
call(f'bridge add_nio_tap {relay} "{tap}"'),
call(f'brctl addif "{br}" "{tap}"'),
call(f'brctl vlan_del "{br}" "{tap}" 1'),
call(f'brctl vlan_add "{br}" "{tap}" 1 pvid untagged'),
call(f"bridge add_nio_udp {relay} {nio.lport} {nio.rhost} {nio.rport}"),
call(f"bridge reset_packet_filters {relay}"),
call(f"bridge start {relay}"),
]
)
async def test_ethernet_switch_create_udp_dot1q(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
ethernet_switch: dict
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, ethernet_switch: dict
) -> None:
# make port 0 a dot1q trunk with native VLAN 10
@@ -325,10 +316,13 @@ class TestEthernetSwitchNodesRoutes:
app.url_path_for(
"compute:update_ethernet_switch",
project_id=ethernet_switch["project_id"],
node_id=ethernet_switch["node_id"]),
json={"ports_mapping": [
{"name": "Ethernet0", "port_number": 0, "type": "dot1q", "vlan": 10},
]}
node_id=ethernet_switch["node_id"],
),
json={
"ports_mapping": [
{"name": "Ethernet0", "port_number": 0, "type": "dot1q", "vlan": 10},
]
},
)
node = compute_project.get_node(ethernet_switch["node_id"])
node._ubridge_send.reset_mock()
@@ -338,7 +332,7 @@ class TestEthernetSwitchNodesRoutes:
project_id=ethernet_switch["project_id"],
node_id=ethernet_switch["node_id"],
adapter_number="0",
port_number="0"
port_number="0",
)
response = await compute_client.post(url, json=self._udp_params())
assert response.status_code == status.HTTP_201_CREATED
@@ -346,18 +340,16 @@ class TestEthernetSwitchNodesRoutes:
br = node._bridge_name
tap = f"{br}-0"
# trunk: drop default 1, admit all VIDs tagged, mark native 10 PVID/untagged
node._ubridge_send.assert_has_calls([
call(f'brctl vlan_del "{br}" "{tap}" 1'),
call(f'brctl vlan_add "{br}" "{tap}" 1 vid 4094'),
call(f'brctl vlan_add "{br}" "{tap}" 10 pvid untagged'),
])
node._ubridge_send.assert_has_calls(
[
call(f'brctl vlan_del "{br}" "{tap}" 1'),
call(f'brctl vlan_add "{br}" "{tap}" 1 vid 4094'),
call(f'brctl vlan_add "{br}" "{tap}" 10 pvid untagged'),
]
)
async def test_ethernet_switch_delete_nio(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
ethernet_switch: dict
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, ethernet_switch: dict
) -> None:
url = app.url_path_for(
@@ -365,7 +357,7 @@ class TestEthernetSwitchNodesRoutes:
project_id=ethernet_switch["project_id"],
node_id=ethernet_switch["node_id"],
adapter_number="0",
port_number="0"
port_number="0",
)
await compute_client.post(url, json=self._udp_params())
@@ -377,7 +369,7 @@ class TestEthernetSwitchNodesRoutes:
project_id=ethernet_switch["project_id"],
node_id=ethernet_switch["node_id"],
adapter_number="0",
port_number="0"
port_number="0",
)
response = await compute_client.delete(url)
assert response.status_code == status.HTTP_204_NO_CONTENT
@@ -386,17 +378,15 @@ class TestEthernetSwitchNodesRoutes:
br = node._bridge_name
tap = f"{br}-0"
relay = f"{node.id}-0"
node._ubridge_send.assert_has_calls([
call(f'brctl delif "{br}" "{tap}"'),
call(f"bridge delete {relay}"),
])
node._ubridge_send.assert_has_calls(
[
call(f'brctl delif "{br}" "{tap}"'),
call(f"bridge delete {relay}"),
]
)
async def test_ethernet_switch_update_nio(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
ethernet_switch: dict
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, ethernet_switch: dict
) -> None:
url = app.url_path_for(
@@ -404,7 +394,7 @@ class TestEthernetSwitchNodesRoutes:
project_id=ethernet_switch["project_id"],
node_id=ethernet_switch["node_id"],
adapter_number="0",
port_number="0"
port_number="0",
)
params = self._udp_params()
params["filters"] = {"delay": [10, 0]}
@@ -421,7 +411,7 @@ class TestEthernetSwitchNodesRoutes:
project_id=ethernet_switch["project_id"],
node_id=ethernet_switch["node_id"],
adapter_number="0",
port_number="0"
port_number="0",
)
response = await compute_client.put(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
@@ -432,11 +422,7 @@ class TestEthernetSwitchNodesRoutes:
node._ubridge_send.assert_any_call(f"bridge reset_packet_filters {relay}")
async def test_ethernet_switch_toggle_marker(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
ethernet_switch: dict
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, ethernet_switch: dict
) -> None:
# a marker installed via the NIO registers in the node's filter-bridge map
@@ -445,7 +431,7 @@ class TestEthernetSwitchNodesRoutes:
project_id=ethernet_switch["project_id"],
node_id=ethernet_switch["node_id"],
adapter_number="0",
port_number="0"
port_number="0",
)
params = self._udp_params()
params["markers"] = {"icmp": {"bpf": "icmp", "link_id": "link-1", "enabled": True}}
@@ -459,7 +445,7 @@ class TestEthernetSwitchNodesRoutes:
"compute:toggle_ethernet_switch_marker",
project_id=ethernet_switch["project_id"],
node_id=ethernet_switch["node_id"],
marker_name="icmp"
marker_name="icmp",
)
response = await compute_client.put(url, json={"enabled": False})
assert response.status_code == status.HTTP_200_OK
@@ -472,17 +458,13 @@ class TestEthernetSwitchNodesRoutes:
"compute:toggle_ethernet_switch_marker",
project_id=ethernet_switch["project_id"],
node_id=ethernet_switch["node_id"],
marker_name="nope"
marker_name="nope",
)
response = await compute_client.put(url, json={"enabled": True})
assert response.status_code == status.HTTP_404_NOT_FOUND
async def test_ethernet_switch_start_capture(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
ethernet_switch: dict
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, ethernet_switch: dict
) -> None:
# capture needs a wired port
@@ -491,7 +473,7 @@ class TestEthernetSwitchNodesRoutes:
project_id=ethernet_switch["project_id"],
node_id=ethernet_switch["node_id"],
adapter_number="0",
port_number="0"
port_number="0",
)
await compute_client.post(url, json=self._udp_params())
@@ -499,11 +481,13 @@ class TestEthernetSwitchNodesRoutes:
node._ubridge_send.reset_mock()
params = {"capture_file_name": "test.pcap", "data_link_type": "DLT_EN10MB"}
url = app.url_path_for("compute:start_ethernet_switch_capture",
project_id=ethernet_switch["project_id"],
node_id=ethernet_switch["node_id"],
adapter_number="0",
port_number="0")
url = app.url_path_for(
"compute:start_ethernet_switch_capture",
project_id=ethernet_switch["project_id"],
node_id=ethernet_switch["node_id"],
adapter_number="0",
port_number="0",
)
response = await compute_client.post(url, json=params)
assert response.status_code == status.HTTP_200_OK
@@ -512,11 +496,7 @@ class TestEthernetSwitchNodesRoutes:
node._ubridge_send.assert_any_call(f'bridge start_capture {relay} "{node.get_nio(0).pcap_output_file}"')
async def test_ethernet_switch_stop_capture(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
ethernet_switch: dict
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, ethernet_switch: dict
) -> None:
# start a capture first
@@ -526,17 +506,19 @@ class TestEthernetSwitchNodesRoutes:
project_id=ethernet_switch["project_id"],
node_id=ethernet_switch["node_id"],
adapter_number="0",
port_number="0"
port_number="0",
),
json=self._udp_params()
json=self._udp_params(),
)
await compute_client.post(
app.url_path_for("compute:start_ethernet_switch_capture",
project_id=ethernet_switch["project_id"],
node_id=ethernet_switch["node_id"],
adapter_number="0",
port_number="0"),
json={"capture_file_name": "test.pcap", "data_link_type": "DLT_EN10MB"}
app.url_path_for(
"compute:start_ethernet_switch_capture",
project_id=ethernet_switch["project_id"],
node_id=ethernet_switch["node_id"],
adapter_number="0",
port_number="0",
),
json={"capture_file_name": "test.pcap", "data_link_type": "DLT_EN10MB"},
)
node = compute_project.get_node(ethernet_switch["node_id"])
@@ -544,11 +526,13 @@ class TestEthernetSwitchNodesRoutes:
relay = f"{node.id}-0"
response = await compute_client.post(
app.url_path_for("compute:stop_ethernet_switch_capture",
project_id=ethernet_switch["project_id"],
node_id=ethernet_switch["node_id"],
adapter_number="0",
port_number="0")
app.url_path_for(
"compute:stop_ethernet_switch_capture",
project_id=ethernet_switch["project_id"],
node_id=ethernet_switch["node_id"],
adapter_number="0",
port_number="0",
)
)
assert response.status_code == status.HTTP_204_NO_CONTENT
node._ubridge_send.assert_any_call(f"bridge stop_capture {relay}")
+4 -10
View File
@@ -24,27 +24,22 @@ pytestmark = pytest.mark.asyncio
class TestImagesRoutes:
async def test_pull_docker_image(self, app: FastAPI, compute_client: AsyncClient) -> None:
with asyncio_patch("gns3server.compute.docker.Docker.pull_image") as mock:
response = await compute_client.post(
app.url_path_for("compute:pull_docker_image"),
json={"image": "nginx:latest"}
app.url_path_for("compute:pull_docker_image"), json={"image": "nginx:latest"}
)
mock.assert_called_once_with("nginx:latest", force=True)
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.parametrize("image", ["", " ", "nginx latest"])
async def test_pull_docker_image_rejects_invalid_name(
self, app: FastAPI, compute_client: AsyncClient, image: str
self, app: FastAPI, compute_client: AsyncClient, image: str
) -> None:
with asyncio_patch("gns3server.compute.docker.Docker.pull_image") as mock:
response = await compute_client.post(
app.url_path_for("compute:pull_docker_image"),
json={"image": image}
)
response = await compute_client.post(app.url_path_for("compute:pull_docker_image"), json={"image": image})
mock.assert_not_called()
assert response.status_code == status.HTTP_422_UNPROCESSABLE_CONTENT
@@ -52,8 +47,7 @@ class TestImagesRoutes:
with asyncio_patch("gns3server.compute.docker.Docker.load_image") as mock:
response = await compute_client.post(
app.url_path_for("compute:load_docker_image"),
content=b"docker-save-tar-bytes"
app.url_path_for("compute:load_docker_image"), content=b"docker-save-tar-bytes"
)
mock.assert_called_once()
# the tar is streamed to the Docker daemon as an async iterable
+152 -212
View File
@@ -32,55 +32,44 @@ pytestmark = pytest.mark.asyncio
class TestIOUNodesRoutes:
@pytest.fixture
def fake_iou_bin(self, images_dir) -> str:
"""Create a fake IOU image on disk"""
path = os.path.join(images_dir, "IOU", "iou.bin")
with open(path, "w+") as f:
f.write('\x7fELF\x01\x01\x01')
f.write("\x7fELF\x01\x01\x01")
os.chmod(path, stat.S_IREAD | stat.S_IEXEC)
return path
@pytest.fixture
def base_params(self, tmpdir, fake_iou_bin) -> dict:
"""Return standard parameters"""
return {"application_id": 42, "name": "IOU-TEST-1", "path": "iou.bin"}
@pytest_asyncio.fixture
async def vm(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
base_params: dict
) -> dict:
async def vm(self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, base_params: dict) -> dict:
response = await compute_client.post(app.url_path_for("compute:create_iou_node", project_id=compute_project.id), json=base_params)
response = await compute_client.post(
app.url_path_for("compute:create_iou_node", project_id=compute_project.id), json=base_params
)
assert response.status_code == status.HTTP_201_CREATED
return response.json()
def startup_config_file(self, compute_project: Project, vm: dict) -> str:
directory = os.path.join(compute_project.path, "project-files", "iou", vm["node_id"])
os.makedirs(directory, exist_ok=True)
return os.path.join(directory, "startup-config.cfg")
async def test_iou_create(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
base_params: dict
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, base_params: dict
) -> None:
response = await compute_client.post(app.url_path_for("compute:create_iou_node", project_id=compute_project.id), json=base_params)
response = await compute_client.post(
app.url_path_for("compute:create_iou_node", project_id=compute_project.id), json=base_params
)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "IOU-TEST-1"
assert response.json()["project_id"] == compute_project.id
@@ -90,13 +79,8 @@ class TestIOUNodesRoutes:
assert response.json()["nvram"] == 256
assert response.json()["l1_keepalives"] is False
async def test_iou_create_with_params(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
base_params: dict
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, base_params: dict
) -> None:
params = base_params
@@ -108,7 +92,9 @@ class TestIOUNodesRoutes:
params["startup_config_content"] = "hostname test"
params["use_default_iou_values"] = False
response = await compute_client.post(app.url_path_for("compute:create_iou_node", project_id=compute_project.id), json=params)
response = await compute_client.post(
app.url_path_for("compute:create_iou_node", project_id=compute_project.id), json=params
)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "IOU-TEST-1"
assert response.json()["project_id"] == compute_project.id
@@ -122,7 +108,6 @@ class TestIOUNodesRoutes:
with open(self.startup_config_file(compute_project, response.json())) as f:
assert f.read() == "hostname test"
@pytest.mark.parametrize(
"name, status_code",
(
@@ -136,13 +121,13 @@ class TestIOUNodesRoutes:
),
)
async def test_iou_create_with_invalid_name(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
base_params: dict,
name: str,
status_code: int
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
base_params: dict,
name: str,
status_code: int,
) -> None:
base_params["name"] = name
@@ -151,40 +136,33 @@ class TestIOUNodesRoutes:
)
assert response.status_code == status_code
async def test_iou_create_startup_config_already_exist(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
base_params: dict) -> None:
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, base_params: dict
) -> None:
"""We don't erase a startup-config if already exist at project creation"""
node_id = str(uuid.uuid4())
startup_config_file_path = self.startup_config_file(compute_project, {'node_id': node_id})
with open(startup_config_file_path, 'w+') as f:
startup_config_file_path = self.startup_config_file(compute_project, {"node_id": node_id})
with open(startup_config_file_path, "w+") as f:
f.write("echo hello")
params = base_params
params["node_id"] = node_id
params["startup_config_content"] = "hostname test"
response = await compute_client.post(app.url_path_for("compute:create_iou_node", project_id=compute_project.id), json=params)
response = await compute_client.post(
app.url_path_for("compute:create_iou_node", project_id=compute_project.id), json=params
)
assert response.status_code == status.HTTP_201_CREATED
with open(self.startup_config_file(compute_project, response.json())) as f:
assert f.read() == "echo hello"
async def test_iou_get(self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, vm: dict) -> None:
async def test_iou_get(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
vm: dict
) -> None:
response = await compute_client.get(app.url_path_for("compute:get_iou_node", project_id=vm["project_id"], node_id=vm["node_id"]))
response = await compute_client.get(
app.url_path_for("compute:get_iou_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "IOU-TEST-1"
assert response.json()["project_id"] == compute_project.id
@@ -194,73 +172,62 @@ class TestIOUNodesRoutes:
assert response.json()["nvram"] == 256
assert response.json()["l1_keepalives"] is False
async def test_iou_start(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.iou.iou_vm.IOUVM.start", return_value=True) as mock:
response = await compute_client.post(app.url_path_for("compute:start_iou_node",
project_id=vm["project_id"],
node_id=vm["node_id"]), json={})
response = await compute_client.post(
app.url_path_for("compute:start_iou_node", project_id=vm["project_id"], node_id=vm["node_id"]), json={}
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_iou_start_with_iourc(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
params = {"iourc_content": "test"}
with asyncio_patch("gns3server.compute.iou.iou_vm.IOUVM.start", return_value=True) as mock:
response = await compute_client.post(app.url_path_for("compute:start_iou_node",
project_id=vm["project_id"],
node_id=vm["node_id"]), json=params)
response = await compute_client.post(
app.url_path_for("compute:start_iou_node", project_id=vm["project_id"], node_id=vm["node_id"]),
json=params,
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_iou_stop(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.iou.iou_vm.IOUVM.stop", return_value=True) as mock:
response = await compute_client.post(app.url_path_for("compute:stop_iou_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
response = await compute_client.post(
app.url_path_for("compute:stop_iou_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_iou_reload(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.iou.iou_vm.IOUVM.reload", return_value=True) as mock:
response = await compute_client.post(app.url_path_for("compute:reload_iou_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
response = await compute_client.post(
app.url_path_for("compute:reload_iou_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_iou_delete(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.iou.IOU.delete_node", return_value=True) as mock:
response = await compute_client.delete(app.url_path_for("compute:delete_iou_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
response = await compute_client.delete(
app.url_path_for("compute:delete_iou_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_iou_suspend(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
response = await compute_client.delete(
app.url_path_for(
"compute:suspend_iou_node",
project_id=vm["project_id"],
node_id=vm["node_id"])
app.url_path_for("compute:suspend_iou_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
async def test_iou_update(
self, app: FastAPI,
compute_client: AsyncClient,
vm: dict,
free_console_port: int
self, app: FastAPI, compute_client: AsyncClient, vm: dict, free_console_port: int
) -> None:
params = {
@@ -274,9 +241,9 @@ class TestIOUNodesRoutes:
"use_default_iou_values": True,
}
response = await compute_client.put(app.url_path_for("compute:update_iou_node",
project_id=vm["project_id"],
node_id=vm["node_id"]), json=params)
response = await compute_client.put(
app.url_path_for("compute:update_iou_node", project_id=vm["project_id"], node_id=vm["node_id"]), json=params
)
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "test"
assert response.json()["console"] == free_console_port
@@ -287,173 +254,141 @@ class TestIOUNodesRoutes:
assert response.json()["l1_keepalives"] is True
assert response.json()["use_default_iou_values"] is True
async def test_iou_nio_create_udp(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
params = {"type": "nio_udp",
"lport": 4242,
"rport": 4343,
"rhost": "127.0.0.1"}
params = {"type": "nio_udp", "lport": 4242, "rport": 4343, "rhost": "127.0.0.1"}
url = app.url_path_for("compute:create_iou_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="1",
port_number="0")
url = app.url_path_for(
"compute:create_iou_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="1",
port_number="0",
)
response = await compute_client.post(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_udp"
async def test_iou_nio_update_udp(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
params = {
"type": "nio_udp",
"lport": 4242,
"rport": 4343,
"rhost": "127.0.0.1",
"filters": {"packet_loss": 10}
}
params = {"type": "nio_udp", "lport": 4242, "rport": 4343, "rhost": "127.0.0.1", "filters": {"packet_loss": 10}}
url = app.url_path_for("compute:create_iou_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="1",
port_number="0")
url = app.url_path_for(
"compute:create_iou_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="1",
port_number="0",
)
response = await compute_client.post(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["filters"] == {"packet_loss": 10}
params["filters"].clear()
url = app.url_path_for("compute:update_iou_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="1",
port_number="0")
url = app.url_path_for(
"compute:update_iou_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="1",
port_number="0",
)
response = await compute_client.put(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_udp"
assert response.json()["filters"] == {}
async def test_iou_nio_create_ethernet(
self,
app: FastAPI,
compute_client: AsyncClient,
vm: dict,
ethernet_device: str
self, app: FastAPI, compute_client: AsyncClient, vm: dict, ethernet_device: str
) -> None:
params = {
"type": "nio_ethernet",
"ethernet_device": ethernet_device
}
params = {"type": "nio_ethernet", "ethernet_device": ethernet_device}
url = app.url_path_for("compute:create_iou_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="1",
port_number="0")
url = app.url_path_for(
"compute:create_iou_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="1",
port_number="0",
)
response = await compute_client.post(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_ethernet"
assert response.json()["ethernet_device"] == ethernet_device
async def test_iou_nio_create_ethernet_different_port(
self,
app: FastAPI,
compute_client: AsyncClient,
vm: dict,
ethernet_device: str
self, app: FastAPI, compute_client: AsyncClient, vm: dict, ethernet_device: str
) -> None:
params = {
"type": "nio_ethernet",
"ethernet_device": ethernet_device
}
params = {"type": "nio_ethernet", "ethernet_device": ethernet_device}
url = app.url_path_for("compute:create_iou_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="3")
url = app.url_path_for(
"compute:create_iou_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="3",
)
response = await compute_client.post(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_ethernet"
assert response.json()["ethernet_device"] == ethernet_device
async def test_iou_nio_create_tap(
self,
app: FastAPI,
compute_client: AsyncClient,
vm: dict,
ethernet_device: str
self, app: FastAPI, compute_client: AsyncClient, vm: dict, ethernet_device: str
) -> None:
params = {
"type": "nio_tap",
"tap_device": ethernet_device
}
params = {"type": "nio_tap", "tap_device": ethernet_device}
url = app.url_path_for("compute:create_iou_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="1",
port_number="0")
url = app.url_path_for(
"compute:create_iou_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="1",
port_number="0",
)
with patch("gns3server.compute.base_manager.BaseManager.has_privileged_access", return_value=True):
response = await compute_client.post(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_tap"
async def test_iou_delete_nio(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
async def test_iou_delete_nio(
self,
app: FastAPI,
compute_client: AsyncClient,
vm: dict
) -> None:
params = {"type": "nio_udp", "lport": 4242, "rport": 4343, "rhost": "127.0.0.1"}
params = {
"type": "nio_udp",
"lport": 4242,
"rport": 4343,
"rhost": "127.0.0.1"
}
url = app.url_path_for("compute:create_iou_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="1",
port_number="0")
url = app.url_path_for(
"compute:create_iou_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="1",
port_number="0",
)
await compute_client.post(url, json=params)
url = app.url_path_for("compute:delete_iou_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="1",
port_number="0")
url = app.url_path_for(
"compute:delete_iou_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="1",
port_number="0",
)
response = await compute_client.delete(url)
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_iou_start_capture(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
params = {
"capture_file_name": "test.pcap",
"data_link_type": "DLT_EN10MB"
}
params = {"capture_file_name": "test.pcap", "data_link_type": "DLT_EN10MB"}
url = app.url_path_for("compute:start_iou_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
url = app.url_path_for(
"compute:start_iou_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
with patch("gns3server.compute.iou.iou_vm.IOUVM.is_running", return_value=True):
with asyncio_patch("gns3server.compute.iou.iou_vm.IOUVM.start_capture") as mock:
@@ -462,14 +397,15 @@ class TestIOUNodesRoutes:
assert mock.called
assert "test.pcap" in response.json()["pcap_file_path"]
async def test_iou_stop_capture(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
url = app.url_path_for("compute:stop_iou_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
url = app.url_path_for(
"compute:stop_iou_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
with patch("gns3server.compute.iou.iou_vm.IOUVM.is_running", return_value=True):
with asyncio_patch("gns3server.compute.iou.iou_vm.IOUVM.stop_capture") as mock:
@@ -477,7 +413,6 @@ class TestIOUNodesRoutes:
assert response.status_code == status.HTTP_204_NO_CONTENT
assert mock.called
# @pytest.mark.asyncio
# async def test_iou_pcap(compute_api, vm, compute_project):
#
@@ -486,18 +421,20 @@ class TestIOUNodesRoutes:
# response = await compute_client.get("/projects/{project_id}/iou/nodes/{node_id}/adapters/0/ports/0/pcap".format(project_id=compute_project.id, node_id=vm["node_id"]), raw=True)
# assert response.status_code == status.HTTP_200_OK
async def test_images(self, app: FastAPI, compute_client: AsyncClient, fake_iou_bin: str) -> None:
response = await compute_client.get(app.url_path_for("compute:get_iou_images"))
assert response.status_code == status.HTTP_200_OK
assert response.json() == [{"filename": "iou.bin", "path": "iou.bin", "filesize": 7, "md5sum": "e573e8f5c93c6c00783f20c7a170aa6c"}]
assert response.json() == [
{"filename": "iou.bin", "path": "iou.bin", "filesize": 7, "md5sum": "e573e8f5c93c6c00783f20c7a170aa6c"}
]
async def test_upload_image(self, app: FastAPI, compute_client: AsyncClient, tmpdir) -> None:
with patch("gns3server.compute.IOU.get_images_directory", return_value=str(tmpdir)):
response = await compute_client.post(app.url_path_for("compute:upload_iou_image", filename="test2"), content=b"TEST")
response = await compute_client.post(
app.url_path_for("compute:upload_iou_image", filename="test2"), content=b"TEST"
)
assert response.status_code == status.HTTP_204_NO_CONTENT
with open(str(tmpdir / "test2")) as f:
@@ -507,39 +444,42 @@ class TestIOUNodesRoutes:
checksum = f.read()
assert checksum == "033bd94b1168d7e4f0d644c3c95e35bf"
async def test_upload_image_forbidden_location(self, app: FastAPI, compute_client: AsyncClient) -> None:
file_path = "%2e%2e/hello"
response = await compute_client.post(app.url_path_for("compute:upload_dynamips_image", filename=file_path), content=b"TEST")
response = await compute_client.post(
app.url_path_for("compute:upload_dynamips_image", filename=file_path), content=b"TEST"
)
assert response.status_code == status.HTTP_403_FORBIDDEN
async def test_download_image(self, app: FastAPI, compute_client: AsyncClient, images_dir: str) -> None:
response = await compute_client.post(app.url_path_for("compute:upload_dynamips_image", filename="test3"), content=b"TEST")
response = await compute_client.post(
app.url_path_for("compute:upload_dynamips_image", filename="test3"), content=b"TEST"
)
assert response.status_code == status.HTTP_204_NO_CONTENT
response = await compute_client.get(app.url_path_for("compute:download_dynamips_image", filename="test3"))
assert response.status_code == status.HTTP_200_OK
async def test_download_image_forbidden(self, app: FastAPI, compute_client: AsyncClient, tmpdir) -> None:
file_path = "foo/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd"
response = await compute_client.get(app.url_path_for("compute:download_iou_image", filename=file_path))
assert response.status_code == status.HTTP_403_FORBIDDEN
async def test_iou_duplicate(self, app: FastAPI, compute_client: AsyncClient, vm: dict, base_params: dict) -> None:
# create destination node first
response = await compute_client.post(app.url_path_for("compute:create_iou_node", project_id=vm["project_id"]), json=base_params)
response = await compute_client.post(
app.url_path_for("compute:create_iou_node", project_id=vm["project_id"]), json=base_params
)
assert response.status_code == status.HTTP_201_CREATED
params = {"destination_node_id": response.json()["node_id"]}
response = await compute_client.post(app.url_path_for("compute:duplicate_iou_node",
project_id=vm["project_id"],
node_id=vm["node_id"]), json=params)
response = await compute_client.post(
app.url_path_for("compute:duplicate_iou_node", project_id=vm["project_id"], node_id=vm["node_id"]),
json=params,
)
assert response.status_code == status.HTTP_201_CREATED
+72 -97
View File
@@ -28,102 +28,81 @@ pytestmark = pytest.mark.asyncio
class TestNATNodesRoutes:
@pytest_asyncio.fixture
async def vm(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
ubridge_path: str,
on_gns3vm
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, ubridge_path: str, on_gns3vm
) -> dict:
with asyncio_patch("gns3server.compute.builtin.nodes.nat.Nat._start_ubridge"):
response = await compute_client.post(app.url_path_for("compute:create_nat_node", project_id=compute_project.id),
json={"name": "Nat 1"})
response = await compute_client.post(
app.url_path_for("compute:create_nat_node", project_id=compute_project.id), json={"name": "Nat 1"}
)
assert response.status_code == status.HTTP_201_CREATED
return response.json()
async def test_nat_create(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
on_gns3vm
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, on_gns3vm
) -> None:
with asyncio_patch("gns3server.compute.builtin.nodes.nat.Nat._start_ubridge"):
response = await compute_client.post(app.url_path_for("compute:create_nat_node", project_id=compute_project.id),
json={"name": "Nat 1"})
response = await compute_client.post(
app.url_path_for("compute:create_nat_node", project_id=compute_project.id), json={"name": "Nat 1"}
)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "Nat 1"
assert response.json()["project_id"] == compute_project.id
async def test_nat_get(self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, vm: dict) -> None:
async def test_nat_get(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
vm: dict
) -> None:
response = await compute_client.get(app.url_path_for("compute:get_nat_node", project_id=vm["project_id"], node_id=vm["node_id"]))
response = await compute_client.get(
app.url_path_for("compute:get_nat_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "Nat 1"
assert response.json()["project_id"] == compute_project.id
assert response.json()["status"] == "started"
async def test_nat_nio_create_udp(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
params = {
"type": "nio_udp",
"lport": 4242,
"rport": 4343,
"rhost": "127.0.0.1"
}
params = {"type": "nio_udp", "lport": 4242, "rport": 4343, "rhost": "127.0.0.1"}
url = app.url_path_for("compute:create_nat_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
url = app.url_path_for(
"compute:create_nat_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
with asyncio_patch("gns3server.compute.builtin.nodes.nat.Nat.add_nio"):
response = await compute_client.post(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_udp"
async def test_nat_nio_update_udp(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
params = {
"type": "nio_udp",
"lport": 4242,
"rport": 4343,
"rhost": "127.0.0.1",
"filters": {"packet_loss": 10}
}
params = {"type": "nio_udp", "lport": 4242, "rport": 4343, "rhost": "127.0.0.1", "filters": {"packet_loss": 10}}
url = app.url_path_for("compute:create_nat_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
url = app.url_path_for(
"compute:create_nat_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
response = await compute_client.post(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["filters"] == {"packet_loss": 10}
params["filters"].clear()
url = app.url_path_for("compute:update_nat_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
url = app.url_path_for(
"compute:update_nat_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
response = await compute_client.put(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_udp"
@@ -131,83 +110,79 @@ class TestNATNodesRoutes:
async def test_nat_delete_nio(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
params = {
"type": "nio_udp",
"lport": 4242,
"rport": 4343,
"rhost": "127.0.0.1"
}
params = {"type": "nio_udp", "lport": 4242, "rport": 4343, "rhost": "127.0.0.1"}
url = app.url_path_for("compute:create_nat_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
url = app.url_path_for(
"compute:create_nat_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
with asyncio_patch("gns3server.compute.builtin.nodes.nat.Nat.add_nio"):
await compute_client.post(url, json=params)
url = app.url_path_for("compute:delete_nat_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
url = app.url_path_for(
"compute:delete_nat_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
with asyncio_patch("gns3server.compute.builtin.nodes.nat.Nat.remove_nio") as mock:
response = await compute_client.delete(url)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_nat_delete(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
response = await compute_client.delete(app.url_path_for("compute:delete_nat_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
response = await compute_client.delete(
app.url_path_for("compute:delete_nat_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_nat_update(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
response = await compute_client.put(app.url_path_for("compute:update_nat_node",
project_id=vm["project_id"],
node_id=vm["node_id"]), json={"name": "test"})
response = await compute_client.put(
app.url_path_for("compute:update_nat_node", project_id=vm["project_id"], node_id=vm["node_id"]),
json={"name": "test"},
)
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "test"
async def test_nat_start_capture(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
params = {
"capture_file_name": "test.pcap",
"data_link_type": "DLT_EN10MB"
}
params = {"capture_file_name": "test.pcap", "data_link_type": "DLT_EN10MB"}
url = app.url_path_for("compute:start_nat_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
url = app.url_path_for(
"compute:start_nat_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
with asyncio_patch("gns3server.compute.builtin.nodes.nat.Nat.start_capture") as mock:
response = await compute_client.post(url, json=params)
assert response.status_code == status.HTTP_200_OK
assert mock.called
assert "test.pcap" in response.json()["pcap_file_path"]
async def test_nat_stop_capture(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
url = app.url_path_for("compute:stop_nat_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
url = app.url_path_for(
"compute:stop_nat_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
with asyncio_patch("gns3server.compute.builtin.nodes.nat.Nat.stop_capture") as mock:
response = await compute_client.post(url)
assert response.status_code == status.HTTP_204_NO_CONTENT
assert mock.called
# @pytest.mark.asyncio
# async def test_nat_pcap(self, compute_api, vm, compute_project):
#
+12 -12
View File
@@ -27,16 +27,16 @@ from gns3server.compute.notification_manager import NotificationManager
# # FIXME: how to test websockets
# pass
#with compute_api.ws("/notifications/ws") as ws:
# with compute_api.ws("/notifications/ws") as ws:
# answer = await ws.receive_text()
# print(answer)
# answer = json.loads(answer)
#
# assert answer["action"] == "ping"
#
# NotificationManager.instance().emit("test", {})
#
# answer = await ws.receive_text()
# answer = json.loads(answer)
# assert answer["action"] == "test"
# answer = await ws.receive_text()
# print(answer)
# answer = json.loads(answer)
#
# assert answer["action"] == "ping"
#
# NotificationManager.instance().emit("test", {})
#
# answer = await ws.receive_text()
# answer = json.loads(answer)
# assert answer["action"] == "test"
+50 -73
View File
@@ -31,23 +31,15 @@ pytestmark = pytest.mark.asyncio
class TestComputeProjectRoutes:
@pytest.fixture
def base_params(self, tmpdir) -> dict:
"""Return standard parameters"""
params = {
"name": "test",
"project_id": str(uuid.uuid4())
}
params = {"name": "test", "project_id": str(uuid.uuid4())}
return params
async def test_create_project_without_dir(
self,
app: FastAPI,
compute_client: AsyncClient,
base_params: dict
self, app: FastAPI, compute_client: AsyncClient, base_params: dict
) -> None:
response = await compute_client.post(app.url_path_for("compute:create_compute_project"), json=base_params)
@@ -55,32 +47,27 @@ class TestComputeProjectRoutes:
assert response.json()["project_id"] == base_params["project_id"]
assert response.json()["name"] == base_params["name"]
async def test_show_project(
self,
app: FastAPI,
compute_client: AsyncClient,
base_params: dict
) -> None:
async def test_show_project(self, app: FastAPI, compute_client: AsyncClient, base_params: dict) -> None:
response = await compute_client.post(app.url_path_for("compute:create_compute_project"), json=base_params)
assert response.status_code == status.HTTP_201_CREATED
response = await compute_client.get(app.url_path_for("compute:get_compute_project", project_id=base_params["project_id"]))
response = await compute_client.get(
app.url_path_for("compute:get_compute_project", project_id=base_params["project_id"])
)
#print(response.json().keys())
#assert len(response.json().keys()) == 3
# print(response.json().keys())
# assert len(response.json().keys()) == 3
assert response.json()["project_id"] == base_params["project_id"]
assert response.json()["name"] == base_params["name"]
assert response.json()["variables"] is None
async def test_show_project_invalid_uuid(self, app: FastAPI, compute_client: AsyncClient) -> None:
response = await compute_client.get(app.url_path_for("compute:get_compute_project",
project_id="50010203-0405-0607-0809-0a0b0c0d0e42"))
response = await compute_client.get(
app.url_path_for("compute:get_compute_project", project_id="50010203-0405-0607-0809-0a0b0c0d0e42")
)
assert response.status_code == status.HTTP_404_NOT_FOUND
async def test_list_projects(self, app: FastAPI, compute_client: AsyncClient) -> dict:
ProjectManager.instance()._projects = {}
@@ -97,41 +84,43 @@ class TestComputeProjectRoutes:
assert len(response.json()) == 2
assert "51010203-0405-0607-0809-0a0b0c0d0e0f" in [p["project_id"] for p in response.json()]
async def test_delete_project(self, app: FastAPI, compute_client: AsyncClient, compute_project: Project) -> None:
with asyncio_patch("gns3server.compute.project.Project.delete", return_value=True) as mock:
response = await compute_client.delete(app.url_path_for("compute:delete_compute_project", project_id=compute_project.id))
response = await compute_client.delete(
app.url_path_for("compute:delete_compute_project", project_id=compute_project.id)
)
assert response.status_code == status.HTTP_204_NO_CONTENT
assert mock.called
async def test_update_project(self, app: FastAPI, compute_client: AsyncClient, base_params: dict) -> None:
response = await compute_client.post(app.url_path_for("compute:create_compute_project"), json=base_params)
assert response.status_code == status.HTTP_201_CREATED
params = {"variables": [{"name": "TEST1", "value": "VAL1"}]}
response = await compute_client.put(app.url_path_for("compute:update_compute_project", project_id=base_params["project_id"]),
json=params)
response = await compute_client.put(
app.url_path_for("compute:update_compute_project", project_id=base_params["project_id"]), json=params
)
assert response.status_code == status.HTTP_200_OK
assert response.json()["variables"] == [{"name": "TEST1", "value": "VAL1"}]
async def test_delete_project_invalid_uuid(self, app: FastAPI, compute_client: AsyncClient) -> None:
response = await compute_client.delete(app.url_path_for("compute:delete_compute_project", project_id=str(uuid.uuid4())))
response = await compute_client.delete(
app.url_path_for("compute:delete_compute_project", project_id=str(uuid.uuid4()))
)
assert response.status_code == status.HTTP_404_NOT_FOUND
async def test_close_project(self, app: FastAPI, compute_client: AsyncClient, compute_project: Project) -> None:
with asyncio_patch("gns3server.compute.project.Project.close", return_value=True) as mock:
response = await compute_client.post(app.url_path_for("compute:close_compute_project", project_id=compute_project.id))
response = await compute_client.post(
app.url_path_for("compute:close_compute_project", project_id=compute_project.id)
)
assert response.status_code == status.HTTP_204_NO_CONTENT
assert mock.called
# @pytest.mark.asyncio
# async def test_close_project_two_client_connected(compute_api, compute_project):
#
@@ -141,13 +130,13 @@ class TestComputeProjectRoutes:
# assert response.status_code == status.HTTP_204_NO_CONTENT
# assert not mock.called
async def test_close_project_invalid_uuid(self, app: FastAPI, compute_client: AsyncClient) -> None:
response = await compute_client.post(app.url_path_for("compute:close_compute_project", project_id=str(uuid.uuid4())))
response = await compute_client.post(
app.url_path_for("compute:close_compute_project", project_id=str(uuid.uuid4()))
)
assert response.status_code == status.HTTP_404_NOT_FOUND
async def test_get_file(self, app: FastAPI, compute_client: AsyncClient) -> None:
project = ProjectManager.instance().create_project(project_id="01010203-0405-0607-0809-0a0b0c0d0e0b")
@@ -155,79 +144,67 @@ class TestComputeProjectRoutes:
with open(os.path.join(project.path, "hello"), "w+") as f:
f.write("world")
response = await compute_client.get(app.url_path_for("compute:get_compute_project_file", project_id=project.id, file_path="hello"))
response = await compute_client.get(
app.url_path_for("compute:get_compute_project_file", project_id=project.id, file_path="hello")
)
assert response.status_code == status.HTTP_200_OK
assert response.content == b"world"
response = await compute_client.get(app.url_path_for("compute:get_compute_project_file", project_id=project.id, file_path="false"))
response = await compute_client.get(
app.url_path_for("compute:get_compute_project_file", project_id=project.id, file_path="false")
)
assert response.status_code == status.HTTP_404_NOT_FOUND
response = await compute_client.get(app.url_path_for("compute:get_compute_project_file",
project_id=project.id,
file_path="../hello"))
response = await compute_client.get(
app.url_path_for("compute:get_compute_project_file", project_id=project.id, file_path="../hello")
)
assert response.status_code == status.HTTP_404_NOT_FOUND
async def test_get_file_forbidden_location(
self,
app: FastAPI,
compute_client: AsyncClient,
config,
tmpdir
) -> None:
async def test_get_file_forbidden_location(self, app: FastAPI, compute_client: AsyncClient, config, tmpdir) -> None:
config.settings.Server.projects_path = str(tmpdir)
project = ProjectManager.instance().create_project(project_id="01010203-0405-0607-0809-0a0b0c0d0e0b")
file_path = "foo/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd"
response = await compute_client.get(
app.url_path_for(
"compute:get_compute_project_file",
project_id=project.id,
file_path=file_path
)
app.url_path_for("compute:get_compute_project_file", project_id=project.id, file_path=file_path)
)
assert response.status_code == status.HTTP_403_FORBIDDEN
async def test_write_file(self, app: FastAPI, compute_client: AsyncClient, config, tmpdir) -> None:
config.settings.Server.projects_path = str(tmpdir)
project = ProjectManager.instance().create_project(project_id="01010203-0405-0607-0809-0a0b0c0d0e0b")
response = await compute_client.post(app.url_path_for("compute:write_compute_project_file",
project_id=project.id,
file_path="hello"), content=b"world")
response = await compute_client.post(
app.url_path_for("compute:write_compute_project_file", project_id=project.id, file_path="hello"),
content=b"world",
)
assert response.status_code == status.HTTP_204_NO_CONTENT
with open(os.path.join(project.path, "hello")) as f:
assert f.read() == "world"
response = await compute_client.post(app.url_path_for("compute:write_compute_project_file",
project_id=project.id,
file_path="../hello"))
response = await compute_client.post(
app.url_path_for("compute:write_compute_project_file", project_id=project.id, file_path="../hello")
)
assert response.status_code == status.HTTP_404_NOT_FOUND
async def test_write_file_forbidden_location(
self,
app: FastAPI,
compute_client: AsyncClient,
config,
tmpdir
self, app: FastAPI, compute_client: AsyncClient, config, tmpdir
) -> None:
config.settings.Server.projects_path = str(tmpdir)
project = ProjectManager.instance().create_project(project_id="01010203-0405-0607-0809-0a0b0c0d0e0b")
file_path = "%2e%2e/hello"
response = await compute_client.post(app.url_path_for("compute:write_compute_project_file",
project_id=project.id,
file_path=file_path), content=b"world")
response = await compute_client.post(
app.url_path_for("compute:write_compute_project_file", project_id=project.id, file_path=file_path),
content=b"world",
)
assert response.status_code == status.HTTP_403_FORBIDDEN
class TestBatchNIOEdgeCases:
@pytest.mark.asyncio
async def test_dynamips_router_dispatch_to_slot_add_nio_binding(self):
"""_add_nio_binding dispatches Dynamips router to slot_add_nio_binding."""
@@ -276,7 +253,7 @@ class TestBatchNIOEdgeCases:
dyn = _FakeDynamips()
base = _FakeBase()
assert len(inspect.signature(dyn.create_nio).parameters) == 2 # Dynamips
assert len(inspect.signature(dyn.create_nio).parameters) == 2 # Dynamips
assert len(inspect.signature(base.create_nio).parameters) == 1 # standard
assert inspect.iscoroutinefunction(dyn.create_nio)
assert not inspect.iscoroutinefunction(base.create_nio)
+239 -333
View File
@@ -33,113 +33,91 @@ pytestmark = pytest.mark.asyncio
class TestQemuNodesRoutes:
@pytest.fixture
def fake_qemu_bin(self, monkeypatch, tmpdir) -> str:
monkeypatch.setenv("PATH", str(tmpdir))
bin_path = os.path.join(os.environ["PATH"], "qemu-system-x86_64")
with open(bin_path, "w+") as f:
f.write("1")
os.chmod(bin_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
return bin_path
@pytest.fixture
def fake_qemu_vm(self, images_dir) -> str:
img_dir = os.path.join(images_dir, "QEMU")
bin_path = os.path.join(img_dir, "linux载.img")
with open(bin_path, "w+") as f:
f.write("1234567")
os.chmod(bin_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
return bin_path
@pytest.fixture
def fake_qemu_img_binary(self, tmpdir):
bin_path = str(tmpdir / "qemu-img")
with open(bin_path, "w+") as f:
f.write("1")
os.chmod(bin_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
return bin_path
@pytest.fixture
def base_params(self, tmpdir, fake_qemu_bin) -> dict:
"""Return standard parameters"""
return {"name": "QEMU-TEST-1", "qemu_path": fake_qemu_bin}
@pytest_asyncio.fixture
async def qemu_vm(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
base_params: dict
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, base_params: dict
) -> None:
response = await compute_client.post(
app.url_path_for("compute:create_qemu_node", project_id=compute_project.id),
json=base_params
app.url_path_for("compute:create_qemu_node", project_id=compute_project.id), json=base_params
)
assert response.status_code == status.HTTP_201_CREATED
return response.json()
async def test_qemu_create(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
base_params: dict,
fake_qemu_bin: str
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, base_params: dict, fake_qemu_bin: str
) -> None:
response = await compute_client.post(app.url_path_for("compute:create_qemu_node", project_id=compute_project.id), json=base_params)
response = await compute_client.post(
app.url_path_for("compute:create_qemu_node", project_id=compute_project.id), json=base_params
)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "QEMU-TEST-1"
assert response.json()["project_id"] == compute_project.id
assert response.json()["qemu_path"] == fake_qemu_bin
assert response.json()["platform"] == "x86_64"
async def test_qemu_create_platform(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
base_params: dict,
fake_qemu_bin: str
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, base_params: dict, fake_qemu_bin: str
):
base_params["qemu_path"] = None
base_params["platform"] = "x86_64"
response = await compute_client.post(app.url_path_for("compute:create_qemu_node", project_id=compute_project.id), json=base_params)
response = await compute_client.post(
app.url_path_for("compute:create_qemu_node", project_id=compute_project.id), json=base_params
)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "QEMU-TEST-1"
assert response.json()["project_id"] == compute_project.id
assert response.json()["qemu_path"] == fake_qemu_bin
assert response.json()["platform"] == "x86_64"
@pytest.mark.asyncio
async def test_qemu_create_with_params(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
base_params: dict,
fake_qemu_vm: str
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, base_params: dict, fake_qemu_vm: str
):
params = base_params
params["ram"] = 1024
params["hda_disk_image"] = "linux载.img"
response = await compute_client.post(app.url_path_for("compute:create_qemu_node", project_id=compute_project.id), json=params)
response = await compute_client.post(
app.url_path_for("compute:create_qemu_node", project_id=compute_project.id), json=params
)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "QEMU-TEST-1"
assert response.json()["project_id"] == compute_project.id
@@ -147,12 +125,7 @@ class TestQemuNodesRoutes:
assert response.json()["hda_disk_image"] == "linux载.img"
async def test_qemu_create_discards_stale_overlay_for_replacement(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
base_params: dict,
fake_qemu_vm: str
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, base_params: dict, fake_qemu_vm: str
):
node_id = str(uuid.uuid4())
@@ -182,12 +155,7 @@ class TestQemuNodesRoutes:
assert not os.path.exists(stale_checksum)
async def test_qemu_create_preserves_existing_overlay_without_reset_marker(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
base_params: dict,
fake_qemu_vm: str
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, base_params: dict, fake_qemu_vm: str
):
node_id = str(uuid.uuid4())
@@ -210,12 +178,7 @@ class TestQemuNodesRoutes:
assert os.path.exists(overlay)
async def test_qemu_create_preserves_overlay_when_replacement_is_missing(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
base_params: dict,
fake_qemu_vm: str
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, base_params: dict, fake_qemu_vm: str
):
node_id = str(uuid.uuid4())
@@ -239,12 +202,7 @@ class TestQemuNodesRoutes:
assert os.path.exists(overlay)
async def test_qemu_create_resets_valid_replacement_when_another_disk_is_missing(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
base_params: dict,
fake_qemu_vm: str
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, base_params: dict, fake_qemu_vm: str
):
node_id = str(uuid.uuid4())
@@ -267,8 +225,7 @@ class TestQemuNodesRoutes:
assert response.status_code == status.HTTP_409_CONFLICT
assert not os.path.exists(stale_overlay)
@pytest.mark.parametrize(
"name, status_code",
(
@@ -286,21 +243,21 @@ class TestQemuNodesRoutes:
),
)
async def test_qemu_create_with_invalid_name(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
base_params: dict,
name: str,
status_code: int
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
base_params: dict,
name: str,
status_code: int,
) -> None:
base_params["name"] = name
response = await compute_client.post(
app.url_path_for("compute:create_qemu_node", project_id=compute_project.id), json=base_params
)
assert response.status_code == status_code
# async def test_qemu_create_with_project_file(self, app: FastAPI,
# compute_client: AsyncClient,
# compute_project: Project,
@@ -321,16 +278,9 @@ class TestQemuNodesRoutes:
# assert response.status_code == status.HTTP_201_CREATED
# assert response.json()["hda_disk_image"] == "hello.img"
# assert response.json()["hda_disk_image_md5sum"] == "7d793037a0760186574b0282f2f435e7"
async def test_qemu_get(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
qemu_vm: dict
):
async def test_qemu_get(self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, qemu_vm: dict):
response = await compute_client.get(
app.url_path_for("compute:get_qemu_node", project_id=qemu_vm["project_id"], node_id=qemu_vm["node_id"])
)
@@ -338,151 +288,130 @@ class TestQemuNodesRoutes:
assert response.json()["name"] == "QEMU-TEST-1"
assert response.json()["project_id"] == compute_project.id
assert response.json()["node_directory"] == os.path.join(
compute_project.path,
"project-files",
"qemu",
qemu_vm["node_id"]
compute_project.path, "project-files", "qemu", qemu_vm["node_id"]
)
async def test_qemu_start(self, app: FastAPI, compute_client: AsyncClient, qemu_vm: dict) -> None:
with asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM.start", return_value=True) as mock:
response = await compute_client.post(
app.url_path_for("compute:start_qemu_node", project_id=qemu_vm["project_id"], node_id=qemu_vm["node_id"])
app.url_path_for(
"compute:start_qemu_node", project_id=qemu_vm["project_id"], node_id=qemu_vm["node_id"]
)
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_qemu_stop(self, app: FastAPI, compute_client: AsyncClient, qemu_vm: dict) -> None:
with asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM.stop", return_value=True) as mock:
response = await compute_client.post(
app.url_path_for("compute:stop_qemu_node", project_id=qemu_vm["project_id"], node_id=qemu_vm["node_id"])
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_qemu_reload(self, app: FastAPI, compute_client: AsyncClient, qemu_vm: dict) -> None:
with asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM.reload", return_value=True) as mock:
response = await compute_client.post(
app.url_path_for("compute:reload_qemu_node", project_id=qemu_vm["project_id"], node_id=qemu_vm["node_id"])
app.url_path_for(
"compute:reload_qemu_node", project_id=qemu_vm["project_id"], node_id=qemu_vm["node_id"]
)
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_qemu_suspend(self, app: FastAPI, compute_client: AsyncClient, qemu_vm: dict) -> None:
with asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM.suspend", return_value=True) as mock:
response = await compute_client.post(
app.url_path_for("compute:suspend_qemu_node", project_id=qemu_vm["project_id"], node_id=qemu_vm["node_id"])
app.url_path_for(
"compute:suspend_qemu_node", project_id=qemu_vm["project_id"], node_id=qemu_vm["node_id"]
)
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_qemu_resume(self, app: FastAPI, compute_client: AsyncClient, qemu_vm: dict) -> None:
with asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM.resume", return_value=True) as mock:
response = await compute_client.post(
app.url_path_for("compute:resume_qemu_node", project_id=qemu_vm["project_id"], node_id=qemu_vm["node_id"])
app.url_path_for(
"compute:resume_qemu_node", project_id=qemu_vm["project_id"], node_id=qemu_vm["node_id"]
)
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_qemu_delete(self, app: FastAPI, compute_client: AsyncClient, qemu_vm: dict) -> None:
with asyncio_patch("gns3server.compute.qemu.Qemu.delete_node", return_value=True) as mock:
response = await compute_client.delete(
app.url_path_for("compute:delete_qemu_node", project_id=qemu_vm["project_id"], node_id=qemu_vm["node_id"])
app.url_path_for(
"compute:delete_qemu_node", project_id=qemu_vm["project_id"], node_id=qemu_vm["node_id"]
)
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_qemu_update(
self,
app: FastAPI,
compute_client: AsyncClient,
qemu_vm: dict,
free_console_port: int,
fake_qemu_vm: str
self, app: FastAPI, compute_client: AsyncClient, qemu_vm: dict, free_console_port: int, fake_qemu_vm: str
) -> None:
params = {
"name": "test",
"console": free_console_port,
"ram": 1024,
"hdb_disk_image": "linux载.img"
}
params = {"name": "test", "console": free_console_port, "ram": 1024, "hdb_disk_image": "linux载.img"}
response = await compute_client.put(
app.url_path_for("compute:update_qemu_node", project_id=qemu_vm["project_id"], node_id=qemu_vm["node_id"]),
json=params
json=params,
)
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "test"
assert response.json()["console"] == free_console_port
assert response.json()["hdb_disk_image"] == "linux载.img"
assert response.json()["ram"] == 1024
async def test_qemu_nio_create_udp(self, app: FastAPI, compute_client: AsyncClient, qemu_vm: dict) -> None:
params = {
"type": "nio_udp",
"lport": 4242,
"rport": 4343,
"rhost": "127.0.0.1"
}
params = {"type": "nio_udp", "lport": 4242, "rport": 4343, "rhost": "127.0.0.1"}
with asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM.add_ubridge_udp_connection"):
await compute_client.put(
app.url_path_for("compute:update_qemu_node", project_id=qemu_vm["project_id"], node_id=qemu_vm["node_id"]),
json={"adapters": 2}
app.url_path_for(
"compute:update_qemu_node", project_id=qemu_vm["project_id"], node_id=qemu_vm["node_id"]
),
json={"adapters": 2},
)
url = app.url_path_for(
"compute:create_qemu_node_nio",
project_id=qemu_vm["project_id"],
node_id=qemu_vm["node_id"],
adapter_number="1",
port_number="0"
port_number="0",
)
response = await compute_client.post(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_udp"
async def test_qemu_nio_update_udp(self, app: FastAPI, compute_client: AsyncClient, qemu_vm: dict) -> None:
params = {
"type": "nio_udp",
"lport": 4242,
"rport": 4343,
"rhost": "127.0.0.1",
"filters": {"packet_loss": 10}
}
params = {"type": "nio_udp", "lport": 4242, "rport": 4343, "rhost": "127.0.0.1", "filters": {"packet_loss": 10}}
await compute_client.put(
app.url_path_for("compute:update_qemu_node", project_id=qemu_vm["project_id"], node_id=qemu_vm["node_id"]),
json={"adapters": 2}
json={"adapters": 2},
)
url = app.url_path_for(
"compute:create_qemu_node_nio",
project_id=qemu_vm["project_id"],
node_id=qemu_vm["node_id"],
adapter_number="1",
port_number="0"
port_number="0",
)
response = await compute_client.post(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["filters"] == {"packet_loss": 10}
@@ -493,127 +422,125 @@ class TestQemuNodesRoutes:
project_id=qemu_vm["project_id"],
node_id=qemu_vm["node_id"],
adapter_number="1",
port_number="0"
port_number="0",
)
response = await compute_client.put(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_udp"
assert response.json()["filters"] == {}
async def test_qemu_delete_nio(self, app: FastAPI, compute_client: AsyncClient, qemu_vm: dict) -> None:
params = {
"type": "nio_udp",
"lport": 4242,
"rport": 4343,
"rhost": "127.0.0.1"
}
params = {"type": "nio_udp", "lport": 4242, "rport": 4343, "rhost": "127.0.0.1"}
with asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM._ubridge_send"):
await compute_client.put(
app.url_path_for("compute:update_qemu_node", project_id=qemu_vm["project_id"], node_id=qemu_vm["node_id"]),
json={"adapters": 2}
app.url_path_for(
"compute:update_qemu_node", project_id=qemu_vm["project_id"], node_id=qemu_vm["node_id"]
),
json={"adapters": 2},
)
url = app.url_path_for(
"compute:create_qemu_node_nio",
project_id=qemu_vm["project_id"],
node_id=qemu_vm["node_id"],
adapter_number="1",
port_number="0"
port_number="0",
)
await compute_client.post(url, json=params)
url = app.url_path_for(
"compute:delete_qemu_node_nio",
project_id=qemu_vm["project_id"],
node_id=qemu_vm["node_id"],
adapter_number="1",
port_number="0"
port_number="0",
)
response = await compute_client.delete(url)
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_images(self, app: FastAPI, compute_client: AsyncClient, fake_qemu_vm) -> None:
response = await compute_client.get(app.url_path_for("compute:get_qemu_images"))
assert response.status_code == status.HTTP_200_OK
print(response.json())
assert {"filename": "linux载.img", "path": "linux载.img", "md5sum": "fcea920f7412b5da7be0cf42b8c93759", "filesize": 7} in response.json()
assert {
"filename": "linux载.img",
"path": "linux载.img",
"md5sum": "fcea920f7412b5da7be0cf42b8c93759",
"filesize": 7,
} in response.json()
async def test_upload_image(self, app: FastAPI, compute_client: AsyncClient, tmpdir: str) -> None:
with patch("gns3server.compute.Qemu.get_images_directory", return_value=str(tmpdir)):
response = await compute_client.post(app.url_path_for("compute:upload_qemu_image",
filename="test2使"), content=b"TEST")
response = await compute_client.post(
app.url_path_for("compute:upload_qemu_image", filename="test2使"), content=b"TEST"
)
assert response.status_code == status.HTTP_204_NO_CONTENT
with open(str(tmpdir / "test2使")) as f:
assert f.read() == "TEST"
with open(str(tmpdir / "test2使.md5sum")) as f:
checksum = f.read()
assert checksum == "033bd94b1168d7e4f0d644c3c95e35bf"
async def test_upload_image_ova(self, app: FastAPI, compute_client: AsyncClient, tmpdir:str) -> None:
async def test_upload_image_ova(self, app: FastAPI, compute_client: AsyncClient, tmpdir: str) -> None:
with patch("gns3server.compute.Qemu.get_images_directory", return_value=str(tmpdir)):
response = await compute_client.post(app.url_path_for("compute:upload_qemu_image",
filename="test2.ova/test2.vmdk"), content=b"TEST")
response = await compute_client.post(
app.url_path_for("compute:upload_qemu_image", filename="test2.ova/test2.vmdk"), content=b"TEST"
)
assert response.status_code == status.HTTP_204_NO_CONTENT
with open(str(tmpdir / "test2.ova" / "test2.vmdk")) as f:
assert f.read() == "TEST"
with open(str(tmpdir / "test2.ova" / "test2.vmdk.md5sum")) as f:
checksum = f.read()
assert checksum == "033bd94b1168d7e4f0d644c3c95e35bf"
async def test_upload_image_forbidden_location(
self, app: FastAPI,
compute_client: AsyncClient,
tmpdir: str
self, app: FastAPI, compute_client: AsyncClient, tmpdir: str
) -> None:
response = await compute_client.post(app.url_path_for("compute:upload_qemu_image",
filename="/qemu/images/../../test2"), content=b"TEST")
response = await compute_client.post(
app.url_path_for("compute:upload_qemu_image", filename="/qemu/images/../../test2"), content=b"TEST"
)
assert response.status_code == status.HTTP_403_FORBIDDEN
async def test_download_image(self, app: FastAPI, compute_client: AsyncClient, images_dir: str) -> None:
response = await compute_client.post(app.url_path_for("compute:upload_qemu_image", filename="test3"), content=b"TEST")
response = await compute_client.post(
app.url_path_for("compute:upload_qemu_image", filename="test3"), content=b"TEST"
)
assert response.status_code == status.HTTP_204_NO_CONTENT
response = await compute_client.get(app.url_path_for("compute:download_qemu_image", filename="test3"))
assert response.status_code == status.HTTP_200_OK
async def test_download_image_forbidden_location(self, app: FastAPI, compute_client: AsyncClient, tmpdir) -> None:
file_path = "foo/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd"
response = await compute_client.get(app.url_path_for("compute:download_qemu_image", filename=file_path))
assert response.status_code == status.HTTP_403_FORBIDDEN
@pytest.mark.skipif(os.getuid() == 0, reason="Root can delete any image")
async def test_upload_image_permission_denied(self, app: FastAPI, compute_client: AsyncClient, images_dir: str) -> None:
async def test_upload_image_permission_denied(
self, app: FastAPI, compute_client: AsyncClient, images_dir: str
) -> None:
with open(os.path.join(images_dir, "QEMU", "test2.tmp"), "w+") as f:
f.write("")
os.chmod(os.path.join(images_dir, "QEMU", "test2.tmp"), 0)
response = await compute_client.post(app.url_path_for("compute:upload_qemu_image", filename="test2"), content=b"TEST")
response = await compute_client.post(
app.url_path_for("compute:upload_qemu_image", filename="test2"), content=b"TEST"
)
assert response.status_code == status.HTTP_409_CONFLICT
# @pytest.mark.asyncio
# async def test_create_img_relative(self, app: FastAPI, compute_client: AsyncClient):
#
@@ -665,69 +592,62 @@ class TestQemuNodesRoutes:
# with asyncio_patch("gns3server.compute.Qemu.create_disk"):
# response = await compute_client.post(app.url_path_for("compute:create_qemu_image"), json=params)
# assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_capabilities(self, app: FastAPI, compute_client: AsyncClient) -> None:
with asyncio_patch("gns3server.compute.Qemu.get_kvm_archs", return_value=["x86_64"]):
response = await compute_client.get(app.url_path_for("compute:get_qemu_capabilities"))
assert response.json()["kvm"] == ["x86_64"]
async def test_qemu_duplicate(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
qemu_vm: dict,
base_params: dict
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, qemu_vm: dict, base_params: dict
) -> None:
# create destination node first
response = await compute_client.post(
app.url_path_for("compute:create_qemu_node", project_id=qemu_vm["project_id"]),
json=base_params
app.url_path_for("compute:create_qemu_node", project_id=qemu_vm["project_id"]), json=base_params
)
assert response.status_code == status.HTTP_201_CREATED
params = {"destination_node_id": response.json()["node_id"]}
response = await compute_client.post(
app.url_path_for("compute:duplicate_qemu_node", project_id=qemu_vm["project_id"], node_id=qemu_vm["node_id"]),
json=params
app.url_path_for(
"compute:duplicate_qemu_node", project_id=qemu_vm["project_id"], node_id=qemu_vm["node_id"]
),
json=params,
)
assert response.status_code == status.HTTP_201_CREATED
async def test_qemu_create_disk_image(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
fake_qemu_img_binary: str,
qemu_vm: dict,
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
fake_qemu_img_binary: str,
qemu_vm: dict,
):
options = {
"format": "qcow2",
"preallocation": "metadata",
"cluster_size": 64,
"refcount_bits": 12,
"lazy_refcounts": "off",
"size": 30
"size": 30,
}
with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()) as qemu_img:
response = await compute_client.post(
app.url_path_for(
"compute:create_qemu_disk_image",
project_id=qemu_vm["project_id"],
node_id=qemu_vm["node_id"],
disk_name="disk.qcow2"
disk_name="disk.qcow2",
),
json=options
json=options,
)
assert response.status_code == status.HTTP_204_NO_CONTENT
args, kwargs = qemu_img.call_args
assert args == (
fake_qemu_img_binary,
@@ -743,39 +663,34 @@ class TestQemuNodesRoutes:
"-o",
"refcount_bits=12",
os.path.join(qemu_vm["node_directory"], "disk.qcow2"),
"30M"
"30M",
)
async def test_qemu_create_disk_image_already_exists(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
fake_qemu_img_binary: str,
qemu_vm: dict,
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
fake_qemu_img_binary: str,
qemu_vm: dict,
):
node = compute_project.get_node(qemu_vm["node_id"])
shutil.copy("tests/resources/empty8G.qcow2", os.path.join(node.working_dir, "disk.qcow2"))
options = {
"format": "qcow2",
"size": 100
}
options = {"format": "qcow2", "size": 100}
response = await compute_client.post(
app.url_path_for(
"compute:create_qemu_disk_image",
project_id=qemu_vm["project_id"],
node_id=qemu_vm["node_id"],
disk_name="disk.qcow2"
disk_name="disk.qcow2",
),
json=options
json=options,
)
assert response.status_code == status.HTTP_409_CONFLICT
# async def test_qemu_create_disk_image_with_not_supported_characters_by_filesystem(
# app: FastAPI,
# compute_client: AsyncClient,
@@ -803,125 +718,116 @@ class TestQemuNodesRoutes:
# json=options
# )
# assert response.status_code == status.HTTP_409_CONFLICT
async def test_qemu_update_disk_image(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
fake_qemu_img_binary: str,
qemu_vm: dict,
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
fake_qemu_img_binary: str,
qemu_vm: dict,
) -> None:
node = compute_project.get_node(qemu_vm["node_id"])
shutil.copy("tests/resources/empty8G.qcow2", os.path.join(node.working_dir, "disk.qcow2"))
with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()) as qemu_img:
response = await compute_client.put(
app.url_path_for(
"compute:update_qemu_disk_image",
project_id=qemu_vm["project_id"],
node_id=qemu_vm["node_id"],
disk_name="disk.qcow2"
disk_name="disk.qcow2",
),
json={"extend": 10}
json={"extend": 10},
)
assert response.status_code == status.HTTP_204_NO_CONTENT
assert qemu_img.called
args, kwargs = qemu_img.call_args
assert args == (
fake_qemu_img_binary,
"resize",
os.path.join(qemu_vm["node_directory"], "disk.qcow2"),
"+10M"
"+10M",
)
async def test_qemu_delete_disk_image(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
qemu_vm: dict,
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
qemu_vm: dict,
) -> None:
node = compute_project.get_node(qemu_vm["node_id"])
shutil.copy("tests/resources/empty8G.qcow2", os.path.join(node.working_dir, "disk.qcow2"))
response = await compute_client.delete(
app.url_path_for(
"compute:delete_qemu_disk_image",
project_id=qemu_vm["project_id"],
node_id=qemu_vm["node_id"],
disk_name="disk.qcow2"
disk_name="disk.qcow2",
)
)
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_qemu_delete_disk_image_missing_image(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
qemu_vm: dict,
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
qemu_vm: dict,
) -> None:
response = await compute_client.delete(
app.url_path_for(
"compute:delete_qemu_disk_image",
project_id=qemu_vm["project_id"],
node_id=qemu_vm["node_id"],
disk_name="unknown_image.qcow2"
disk_name="unknown_image.qcow2",
)
)
assert response.status_code == status.HTTP_409_CONFLICT
@pytest.mark.asyncio
async def test_qemu_start_capture(self, app: FastAPI, compute_client: AsyncClient, qemu_vm: dict):
params = {
"capture_file_name": "test.pcap",
"data_link_type": "DLT_EN10MB"
}
params = {"capture_file_name": "test.pcap", "data_link_type": "DLT_EN10MB"}
url = app.url_path_for(
"compute:start_qemu_node_capture",
project_id=qemu_vm["project_id"],
node_id=qemu_vm["node_id"],
adapter_number="0",
port_number="0"
port_number="0",
)
with patch("gns3server.compute.qemu.qemu_vm.QemuVM.is_running", return_value=True):
with asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM.start_capture") as mock:
response = await compute_client.post(url, json=params)
assert response.status_code == status.HTTP_200_OK
assert mock.called
assert "test.pcap" in response.json()["pcap_file_path"]
@pytest.mark.asyncio
async def test_qemu_stop_capture(self, app: FastAPI, compute_client: AsyncClient, qemu_vm: dict):
url = app.url_path_for(
"compute:stop_qemu_node_capture",
project_id=qemu_vm["project_id"],
node_id=qemu_vm["node_id"],
adapter_number="0",
port_number="0"
port_number="0",
)
with patch("gns3server.compute.qemu.qemu_vm.QemuVM.is_running", return_value=True):
with asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM.stop_capture") as mock:
response = await compute_client.post(url)
assert response.status_code == status.HTTP_204_NO_CONTENT
assert mock.called
# @pytest.mark.asyncio
# async def test_qemu_pcap(self, app: FastAPI, compute_client: AsyncClient, vm, compute_project):
#
+118 -130
View File
@@ -29,129 +29,119 @@ pytestmark = pytest.mark.asyncio
class TestVirtualBoxNodesRoutes:
@pytest_asyncio.fixture
async def vm(self, app: FastAPI, compute_client: AsyncClient, compute_project: Project) -> None:
vboxmanage_path = "/fake/VboxManage"
params = {
"name": "VMTEST",
"vmname": "VMTEST",
"linked_clone": False
}
with asyncio_patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.create", return_value=True) as mock:
response = await compute_client.post(app.url_path_for("compute:create_virtualbox_node", project_id=compute_project.id),
json=params)
params = {"name": "VMTEST", "vmname": "VMTEST", "linked_clone": False}
with asyncio_patch(
"gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.create", return_value=True
) as mock:
response = await compute_client.post(
app.url_path_for("compute:create_virtualbox_node", project_id=compute_project.id), json=params
)
assert mock.called
assert response.status_code == status.HTTP_201_CREATED
with patch("gns3server.compute.virtualbox.VirtualBox.find_vboxmanage", return_value=vboxmanage_path):
return response.json()
async def test_vbox_create(self, app: FastAPI, compute_client: AsyncClient, compute_project: Project) -> None:
params = {
"name": "VM1",
"vmname": "VM1",
"linked_clone": False
}
params = {"name": "VM1", "vmname": "VM1", "linked_clone": False}
with asyncio_patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.create", return_value=True):
response = await compute_client.post(app.url_path_for("compute:create_virtualbox_node", project_id=compute_project.id),
json=params)
response = await compute_client.post(
app.url_path_for("compute:create_virtualbox_node", project_id=compute_project.id), json=params
)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "VM1"
assert response.json()["project_id"] == compute_project.id
async def test_vbox_get(self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, vm: dict) -> None:
response = await compute_client.get(app.url_path_for("compute:get_virtualbox_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
async def test_vbox_get(
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, vm: dict
) -> None:
response = await compute_client.get(
app.url_path_for("compute:get_virtualbox_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "VMTEST"
assert response.json()["project_id"] == compute_project.id
async def test_vbox_start(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.start", return_value=True) as mock:
response = await compute_client.post(app.url_path_for("compute:start_virtualbox_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
response = await compute_client.post(
app.url_path_for("compute:start_virtualbox_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_vbox_stop(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.stop", return_value=True) as mock:
response = await compute_client.post(app.url_path_for("compute:stop_virtualbox_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
response = await compute_client.post(
app.url_path_for("compute:stop_virtualbox_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_vbox_suspend(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.suspend", return_value=True) as mock:
response = await compute_client.post(app.url_path_for("compute:suspend_virtualbox_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
with asyncio_patch(
"gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.suspend", return_value=True
) as mock:
response = await compute_client.post(
app.url_path_for("compute:suspend_virtualbox_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_vbox_resume(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.resume", return_value=True) as mock:
response = await compute_client.post(app.url_path_for("compute:resume_virtualbox_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
with asyncio_patch(
"gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.resume", return_value=True
) as mock:
response = await compute_client.post(
app.url_path_for("compute:resume_virtualbox_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_vbox_reload(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.reload", return_value=True) as mock:
response = await compute_client.post(app.url_path_for("compute:reload_virtualbox_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
with asyncio_patch(
"gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.reload", return_value=True
) as mock:
response = await compute_client.post(
app.url_path_for("compute:reload_virtualbox_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_vbox_nio_create_udp(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
params = {
"type": "nio_udp",
"lport": 4242,
"rport": 4343,
"rhost": "127.0.0.1"
}
url = app.url_path_for("compute:create_virtualbox_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
with asyncio_patch('gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.adapter_add_nio_binding') as mock:
params = {"type": "nio_udp", "lport": 4242, "rport": 4343, "rhost": "127.0.0.1"}
url = app.url_path_for(
"compute:create_virtualbox_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
with asyncio_patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.adapter_add_nio_binding") as mock:
response = await compute_client.post(url, json=params)
assert mock.called
args, kwgars = mock.call_args
assert args[0] == 0
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_udp"
# @pytest.mark.asyncio
# async def test_vbox_nio_update_udp(self, app: FastAPI, compute_client: AsyncClient, vm):
#
@@ -169,78 +159,76 @@ class TestVirtualBoxNodesRoutes:
#
# assert response.status_code == status.HTTP_201_CREATED
# assert response.json()["type"] == "nio_udp"
async def test_vbox_delete_nio(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
url = app.url_path_for("compute:delete_virtualbox_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
with asyncio_patch('gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.adapter_remove_nio_binding') as mock:
url = app.url_path_for(
"compute:delete_virtualbox_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
with asyncio_patch(
"gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.adapter_remove_nio_binding"
) as mock:
response = await compute_client.delete(url)
assert mock.called
args, kwgars = mock.call_args
assert args[0] == 0
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_vbox_update(self, app: FastAPI, compute_client: AsyncClient, vm, free_console_port):
params = {
"name": "test",
"console": free_console_port
}
response = await compute_client.put(app.url_path_for("compute:update_virtualbox_node",
project_id=vm["project_id"],
node_id=vm["node_id"]), json=params)
params = {"name": "test", "console": free_console_port}
response = await compute_client.put(
app.url_path_for("compute:update_virtualbox_node", project_id=vm["project_id"], node_id=vm["node_id"]),
json=params,
)
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "test"
assert response.json()["console"] == free_console_port
@pytest.mark.asyncio
async def test_virtualbox_start_capture(self, app: FastAPI, compute_client: AsyncClient, vm):
params = {
"capture_file_name": "test.pcap",
"data_link_type": "DLT_EN10MB"
}
url = app.url_path_for("compute:start_virtualbox_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
params = {"capture_file_name": "test.pcap", "data_link_type": "DLT_EN10MB"}
url = app.url_path_for(
"compute:start_virtualbox_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
with patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.is_running", return_value=True):
with asyncio_patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.start_capture") as mock:
response = await compute_client.post(url, json=params)
assert response.status_code == status.HTTP_200_OK
assert mock.called
assert "test.pcap" in response.json()["pcap_file_path"]
@pytest.mark.asyncio
async def test_virtualbox_stop_capture(self, app: FastAPI, compute_client: AsyncClient, vm):
url = app.url_path_for("compute:stop_virtualbox_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
url = app.url_path_for(
"compute:stop_virtualbox_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
with patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.is_running", return_value=True):
with asyncio_patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.stop_capture") as mock:
response = await compute_client.post(url)
assert response.status_code == status.HTTP_204_NO_CONTENT
assert mock.called
# @pytest.mark.asyncio
# async def test_virtualbox_pcap(self, app: FastAPI, compute_client: AsyncClient, vm, compute_project):
#
+106 -141
View File
@@ -29,147 +29,121 @@ pytestmark = pytest.mark.asyncio
class TestVMwareNodesRoutes:
@pytest_asyncio.fixture
async def vm(self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, vmx_path: str) -> dict:
params = {
"name": "VMTEST",
"vmx_path": vmx_path,
"linked_clone": False
}
params = {"name": "VMTEST", "vmx_path": vmx_path, "linked_clone": False}
with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.create", return_value=True) as mock:
response = await compute_client.post(app.url_path_for("compute:create_vmware_node", project_id=compute_project.id),
json=params)
response = await compute_client.post(
app.url_path_for("compute:create_vmware_node", project_id=compute_project.id), json=params
)
assert mock.called
assert response.status_code == status.HTTP_201_CREATED
return response.json()
@pytest.fixture
def vmx_path(self, tmpdir: str) -> str:
"""
Return a fake VMX file
"""
path = str(tmpdir / "test.vmx")
with open(path, 'w+') as f:
with open(path, "w+") as f:
f.write("1")
return path
async def test_vmware_create(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
vmx_path: str
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, vmx_path: str
) -> None:
params = {
"name": "VM1",
"vmx_path": vmx_path,
"linked_clone": False
}
params = {"name": "VM1", "vmx_path": vmx_path, "linked_clone": False}
with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.create", return_value=True):
response = await compute_client.post(app.url_path_for("compute:create_vmware_node", project_id=compute_project.id),
json=params)
response = await compute_client.post(
app.url_path_for("compute:create_vmware_node", project_id=compute_project.id), json=params
)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "VM1"
assert response.json()["project_id"] == compute_project.id
async def test_vmware_get(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
vm: dict
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, vm: dict
) -> None:
response = await compute_client.get(app.url_path_for("compute:get_vmware_node", project_id=vm["project_id"], node_id=vm["node_id"]))
response = await compute_client.get(
app.url_path_for("compute:get_vmware_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "VMTEST"
assert response.json()["project_id"] == compute_project.id
async def test_vmware_start(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.start", return_value=True) as mock:
response = await compute_client.post(app.url_path_for("compute:start_vmware_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
response = await compute_client.post(
app.url_path_for("compute:start_vmware_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_vmware_stop(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.stop", return_value=True) as mock:
response = await compute_client.post(app.url_path_for("compute:stop_vmware_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
response = await compute_client.post(
app.url_path_for("compute:stop_vmware_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_vmware_suspend(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.suspend", return_value=True) as mock:
response = await compute_client.post(app.url_path_for("compute:suspend_vmware_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
response = await compute_client.post(
app.url_path_for("compute:suspend_vmware_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_vmware_resume(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.resume", return_value=True) as mock:
response = await compute_client.post(app.url_path_for("compute:resume_vmware_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
response = await compute_client.post(
app.url_path_for("compute:resume_vmware_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_vmware_reload(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.reload", return_value=True) as mock:
response = await compute_client.post(app.url_path_for("compute:reload_vmware_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
response = await compute_client.post(
app.url_path_for("compute:reload_vmware_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_vmware_nio_create_udp(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
params = {
"type": "nio_udp",
"lport": 4242,
"rport": 4343,
"rhost": "127.0.0.1"
}
url = app.url_path_for("compute:create_vmware_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
with asyncio_patch('gns3server.compute.vmware.vmware_vm.VMwareVM.adapter_add_nio_binding') as mock:
params = {"type": "nio_udp", "lport": 4242, "rport": 4343, "rhost": "127.0.0.1"}
url = app.url_path_for(
"compute:create_vmware_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.adapter_add_nio_binding") as mock:
response = await compute_client.post(url, json=params)
assert mock.called
args, kwgars = mock.call_args
assert args[0] == 0
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_udp"
# @pytest.mark.asyncio
# async def test_vmware_nio_update_udp(self, app: FastAPI, compute_client: AsyncClient, vm):
#
@@ -187,83 +161,74 @@ class TestVMwareNodesRoutes:
# response = await compute_client.put("/projects/{project_id}/vmware/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
# assert response.status_code == status.HTTP_201_CREATED
# assert response.json()["type"] == "nio_udp"
async def test_vmware_delete_nio(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
url = app.url_path_for("compute:delete_vmware_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
with asyncio_patch('gns3server.compute.vmware.vmware_vm.VMwareVM.adapter_remove_nio_binding') as mock:
url = app.url_path_for(
"compute:delete_vmware_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.adapter_remove_nio_binding") as mock:
response = await compute_client.delete(url)
assert mock.called
args, kwgars = mock.call_args
assert args[0] == 0
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_vmware_update(
self,
app: FastAPI,
compute_client: AsyncClient,
vm: dict,
free_console_port: int
self, app: FastAPI, compute_client: AsyncClient, vm: dict, free_console_port: int
) -> None:
params = {
"name": "test",
"console": free_console_port
}
response = await compute_client.put(app.url_path_for("compute:update_vmware_node",
project_id=vm["project_id"],
node_id=vm["node_id"]), json=params)
params = {"name": "test", "console": free_console_port}
response = await compute_client.put(
app.url_path_for("compute:update_vmware_node", project_id=vm["project_id"], node_id=vm["node_id"]),
json=params,
)
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "test"
assert response.json()["console"] == free_console_port
async def test_vmware_start_capture(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
params = {
"capture_file_name": "test.pcap",
"data_link_type": "DLT_EN10MB"
}
url = app.url_path_for("compute:start_vmware_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
params = {"capture_file_name": "test.pcap", "data_link_type": "DLT_EN10MB"}
url = app.url_path_for(
"compute:start_vmware_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
with patch("gns3server.compute.vmware.vmware_vm.VMwareVM.is_running", return_value=True):
with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.start_capture") as mock:
response = await compute_client.post(url, json=params)
assert response.status_code == status.HTTP_200_OK
assert mock.called
assert "test.pcap" in response.json()["pcap_file_path"]
async def test_vmware_stop_capture(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
url = app.url_path_for("compute:stop_vmware_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
url = app.url_path_for(
"compute:stop_vmware_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
with patch("gns3server.compute.vmware.vmware_vm.VMwareVM.is_running", return_value=True):
with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.stop_capture") as mock:
response = await compute_client.post(url)
assert response.status_code == status.HTTP_204_NO_CONTENT
assert mock.called
# @pytest.mark.asyncio
# async def test_vmware_pcap(self, app: FastAPI, compute_client: AsyncClient, vm, compute_project):
#
+155 -196
View File
@@ -29,288 +29,247 @@ pytestmark = pytest.mark.asyncio
class TestVPCSNodesRoutes:
@pytest_asyncio.fixture
async def vm(self, app: FastAPI, compute_client: AsyncClient, compute_project: Project) -> None:
params = {"name": "PC TEST 1"}
response = await compute_client.post(app.url_path_for("compute:create_vpcs_node", project_id=compute_project.id), json=params)
response = await compute_client.post(
app.url_path_for("compute:create_vpcs_node", project_id=compute_project.id), json=params
)
assert response.status_code == status.HTTP_201_CREATED
return response.json()
async def test_vpcs_create(self, app: FastAPI, compute_client: AsyncClient, compute_project: Project) -> None:
params = {"name": "PC TEST 1"}
response = await compute_client.post(app.url_path_for("compute:create_vpcs_node", project_id=compute_project.id), json=params)
response = await compute_client.post(
app.url_path_for("compute:create_vpcs_node", project_id=compute_project.id), json=params
)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "PC TEST 1"
assert response.json()["project_id"] == compute_project.id
async def test_vpcs_get(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
vm: dict
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, vm: dict
) -> None:
response = await compute_client.get(app.url_path_for("compute:get_vpcs_node", project_id=vm["project_id"], node_id=vm["node_id"]))
response = await compute_client.get(
app.url_path_for("compute:get_vpcs_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "PC TEST 1"
assert response.json()["project_id"] == compute_project.id
assert response.json()["status"] == "stopped"
async def test_vpcs_create_startup_script(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project
) -> None:
params = {
"name": "PC TEST 1",
"startup_script": "ip 192.168.1.2\necho TEST"
}
response = await compute_client.post(app.url_path_for("compute:create_vpcs_node", project_id=compute_project.id), json=params)
params = {"name": "PC TEST 1", "startup_script": "ip 192.168.1.2\necho TEST"}
response = await compute_client.post(
app.url_path_for("compute:create_vpcs_node", project_id=compute_project.id), json=params
)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "PC TEST 1"
assert response.json()["project_id"] == compute_project.id
async def test_vpcs_create_port(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
free_console_port: int
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, free_console_port: int
) -> None:
params = {
"name": "PC TEST 1",
"console": free_console_port
}
response = await compute_client.post(app.url_path_for("compute:create_vpcs_node", project_id=compute_project.id), json=params)
params = {"name": "PC TEST 1", "console": free_console_port}
response = await compute_client.post(
app.url_path_for("compute:create_vpcs_node", project_id=compute_project.id), json=params
)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "PC TEST 1"
assert response.json()["project_id"] == compute_project.id
assert response.json()["console"] == free_console_port
async def test_vpcs_nio_create_udp(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
params = {
"type": "nio_udp",
"lport": 4242,
"rport": 4343,
"rhost": "127.0.0.1"
}
url = app.url_path_for("compute:create_vpcs_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
params = {"type": "nio_udp", "lport": 4242, "rport": 4343, "rhost": "127.0.0.1"}
url = app.url_path_for(
"compute:create_vpcs_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.add_ubridge_udp_connection"):
response = await compute_client.post(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_udp"
async def test_vpcs_nio_update_udp(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
params = {
"type": "nio_udp",
"lport": 4242,
"rport": 4343,
"rhost": "127.0.0.1",
"filters": {"packet_loss": 10}
}
url = app.url_path_for("compute:create_vpcs_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
params = {"type": "nio_udp", "lport": 4242, "rport": 4343, "rhost": "127.0.0.1", "filters": {"packet_loss": 10}}
url = app.url_path_for(
"compute:create_vpcs_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.add_ubridge_udp_connection"):
response = await compute_client.post(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["filters"] == {"packet_loss": 10}
params["filters"].clear()
url = app.url_path_for("compute:update_vpcs_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
url = app.url_path_for(
"compute:update_vpcs_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
response = await compute_client.put(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_udp"
assert response.json()["filters"] == {}
async def test_vpcs_delete_nio(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
params = {
"type": "nio_udp",
"lport": 4242,
"rport": 4343,
"rhost": "127.0.0.1"
}
params = {"type": "nio_udp", "lport": 4242, "rport": 4343, "rhost": "127.0.0.1"}
with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM._ubridge_send"):
url = app.url_path_for("compute:create_vpcs_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
url = app.url_path_for(
"compute:create_vpcs_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
await compute_client.post(url, json=params)
url = app.url_path_for("compute:delete_vpcs_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
url = app.url_path_for(
"compute:delete_vpcs_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
response = await compute_client.delete(url)
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_vpcs_start(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.start", return_value=True) as mock:
response = await compute_client.post(app.url_path_for("compute:start_vpcs_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_vpcs_stop(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.stop", return_value=True) as mock:
response = await compute_client.post(app.url_path_for("compute:stop_vpcs_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_vpcs_reload(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.reload", return_value=True) as mock:
response = await compute_client.post(app.url_path_for("compute:reload_vpcs_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_vpcs_delete(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.vpcs.VPCS.delete_node", return_value=True) as mock:
response = await compute_client.delete(app.url_path_for("compute:delete_vpcs_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
response = await compute_client.post(
app.url_path_for("compute:start_vpcs_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_vpcs_stop(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.stop", return_value=True) as mock:
response = await compute_client.post(
app.url_path_for("compute:stop_vpcs_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_vpcs_reload(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.reload", return_value=True) as mock:
response = await compute_client.post(
app.url_path_for("compute:reload_vpcs_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_vpcs_delete(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.vpcs.VPCS.delete_node", return_value=True) as mock:
response = await compute_client.delete(
app.url_path_for("compute:delete_vpcs_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_vpcs_suspend(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
response = await compute_client.post(
app.url_path_for(
"compute:suspend_vpcs_node",
project_id=vm["project_id"],
node_id=vm["node_id"]
)
app.url_path_for("compute:suspend_vpcs_node", project_id=vm["project_id"], node_id=vm["node_id"])
)
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
async def test_vpcs_duplicate(
self,
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
vm: dict
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, vm: dict
) -> None:
# create destination node first
params = {"name": "PC TEST 1"}
response = await compute_client.post(app.url_path_for("compute:create_vpcs_node", project_id=compute_project.id), json=params)
response = await compute_client.post(
app.url_path_for("compute:create_vpcs_node", project_id=compute_project.id), json=params
)
assert response.status_code == status.HTTP_201_CREATED
params = {"destination_node_id": response.json()["node_id"]}
response = await compute_client.post(app.url_path_for("compute:duplicate_vpcs_node",
project_id=vm["project_id"],
node_id=vm["node_id"]), json=params)
response = await compute_client.post(
app.url_path_for("compute:duplicate_vpcs_node", project_id=vm["project_id"], node_id=vm["node_id"]),
json=params,
)
assert response.status_code == status.HTTP_201_CREATED
async def test_vpcs_update(
self,
app: FastAPI,
compute_client: AsyncClient,
vm: dict,
free_console_port: int
self, app: FastAPI, compute_client: AsyncClient, vm: dict, free_console_port: int
) -> None:
console_port = free_console_port
params = {
"name": "test",
"console": console_port
}
response = await compute_client.put(app.url_path_for("compute:update_vpcs_node",
project_id=vm["project_id"],
node_id=vm["node_id"]), json=params)
params = {"name": "test", "console": console_port}
response = await compute_client.put(
app.url_path_for("compute:update_vpcs_node", project_id=vm["project_id"], node_id=vm["node_id"]),
json=params,
)
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "test"
assert response.json()["console"] == console_port
async def test_vpcs_start_capture(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
params = {
"capture_file_name": "test.pcap",
"data_link_type": "DLT_EN10MB"
}
url = app.url_path_for("compute:start_vpcs_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
params = {"capture_file_name": "test.pcap", "data_link_type": "DLT_EN10MB"}
url = app.url_path_for(
"compute:start_vpcs_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
with patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.is_running", return_value=True):
with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.start_capture") as mock:
response = await compute_client.post(url, json=params)
assert response.status_code == status.HTTP_200_OK
assert mock.called
assert "test.pcap" in response.json()["pcap_file_path"]
async def test_vpcs_stop_capture(self, app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
url = app.url_path_for("compute:stop_vpcs_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
url = app.url_path_for(
"compute:stop_vpcs_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0",
)
with patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.is_running", return_value=True):
with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.stop_capture") as mock:
response = await compute_client.post(url)
assert response.status_code == status.HTTP_204_NO_CONTENT
assert mock.called
# @pytest.mark.asyncio
# async def test_vpcs_pcap(self, app: FastAPI, compute_client: AsyncClient, vm, compute_project: Project):
#
@@ -172,49 +172,38 @@ class TestAccessTicketWebSocketAuth:
monkeypatch.setattr(
HTTPClient,
"get_client",
classmethod(lambda cls: SimpleNamespace(ws_connect=lambda *args, **kwargs: compute_ws))
classmethod(lambda cls: SimpleNamespace(ws_connect=lambda *args, **kwargs: compute_ws)),
)
return compute_ws
async def test_console_ws_accepts_valid_ticket(
self,
app: FastAPI,
base_client: AsyncClient,
compute_credentials,
project: Project,
node: Node,
monkeypatch
self, app: FastAPI, base_client: AsyncClient, compute_credentials, project: Project, node: Node, monkeypatch
) -> None:
# admin is the seeded superadmin, so the RBAC privilege check is skipped
ticket = access_ticket_service.mint("admin", 0, project_id=project.id, node_id=node.id)
self._forward_compute_ws(monkeypatch, [
aiohttp.WSMessage(aiohttp.WSMsgType.TEXT, "device output", None),
])
self._forward_compute_ws(
monkeypatch,
[
aiohttp.WSMessage(aiohttp.WSMsgType.TEXT, "device output", None),
],
)
async with self._ws_client(app, base_client) as client:
async with aconnect_ws(
f"/v3/projects/{project.id}/nodes/{node.id}/console/ws",
client,
params={"token": ticket}
f"/v3/projects/{project.id}/nodes/{node.id}/console/ws", client, params={"token": ticket}
) as ws:
# reaching the compute forwarding loop means ticket auth succeeded
assert await ws.receive_text() == "device output"
async def test_console_ws_rejects_ticket_bound_to_other_node(
self,
app: FastAPI,
base_client: AsyncClient,
project: Project,
node: Node
self, app: FastAPI, base_client: AsyncClient, project: Project, node: Node
) -> None:
other_node = "00000000-0000-0000-0000-000000000000"
ticket = access_ticket_service.mint("admin", 0, project_id=project.id, node_id=other_node)
async with self._ws_client(app, base_client) as client:
async with aconnect_ws(
f"/v3/projects/{project.id}/nodes/{node.id}/console/ws",
client,
params={"token": ticket}
f"/v3/projects/{project.id}/nodes/{node.id}/console/ws", client, params={"token": ticket}
) as ws:
notification = await ws.receive_json()
assert notification["event"]["message"] == (
@@ -224,28 +213,18 @@ class TestAccessTicketWebSocketAuth:
)
async def test_console_ws_rejects_ticket_with_stale_token_version(
self,
app: FastAPI,
base_client: AsyncClient,
project: Project,
node: Node
self, app: FastAPI, base_client: AsyncClient, project: Project, node: Node
) -> None:
# logging out bumps the user's token_version: outstanding tickets must die with it
ticket = access_ticket_service.mint("admin", 999, project_id=project.id, node_id=node.id)
async with self._ws_client(app, base_client) as client:
async with aconnect_ws(
f"/v3/projects/{project.id}/nodes/{node.id}/console/ws",
client,
params={"token": ticket}
f"/v3/projects/{project.id}/nodes/{node.id}/console/ws", client, params={"token": ticket}
) as ws:
notification = await ws.receive_json()
assert "Token has been revoked for 'admin'" in notification["event"]["message"]
async def test_ticket_rejected_on_non_console_websocket(
self,
app: FastAPI,
base_client: AsyncClient
) -> None:
async def test_ticket_rejected_on_non_console_websocket(self, app: FastAPI, base_client: AsyncClient) -> None:
# the controller notification stream shares the WS auth dependency but has
# no node binding: a console ticket must not authenticate it
ticket = access_ticket_service.mint("admin", 0, project_id="p1", node_id="n1")
@@ -263,42 +242,26 @@ class TestAccessTicketRestAuth:
pytestmark = pytest.mark.asyncio
async def test_rest_accepts_path_bound_ticket(
self,
app: FastAPI,
base_client: AsyncClient
) -> None:
async def test_rest_accepts_path_bound_ticket(self, app: FastAPI, base_client: AsyncClient) -> None:
# base_client carries no Authorization header, so the ?token= parameter is used
ticket = access_ticket_service.mint("admin", 0, path="/v3/projects")
response = await base_client.get("/v3/projects", params={"token": ticket})
assert response.status_code == status.HTTP_200_OK
async def test_rest_rejects_ticket_bound_to_other_path(
self,
app: FastAPI,
base_client: AsyncClient
) -> None:
async def test_rest_rejects_ticket_bound_to_other_path(self, app: FastAPI, base_client: AsyncClient) -> None:
ticket = access_ticket_service.mint("admin", 0, path="/v3/symbols/router.svg/raw")
response = await base_client.get("/v3/projects", params={"token": ticket})
assert response.status_code == status.HTTP_401_UNAUTHORIZED
assert "Invalid or expired access ticket" in response.text
async def test_rest_rejects_node_bound_ticket(
self,
app: FastAPI,
base_client: AsyncClient
) -> None:
async def test_rest_rejects_node_bound_ticket(self, app: FastAPI, base_client: AsyncClient) -> None:
# node-bound (console) tickets must not authenticate REST resources
ticket = access_ticket_service.mint("admin", 0, project_id="p1", node_id="n1")
response = await base_client.get("/v3/projects", params={"token": ticket})
assert response.status_code == status.HTTP_401_UNAUTHORIZED
async def test_rest_rejects_ticket_with_stale_token_version(
self,
app: FastAPI,
base_client: AsyncClient
) -> None:
async def test_rest_rejects_ticket_with_stale_token_version(self, app: FastAPI, base_client: AsyncClient) -> None:
# logging out bumps the user's token_version: outstanding tickets must die with it
ticket = access_ticket_service.mint("admin", 999, path="/v3/projects")
response = await base_client.get("/v3/projects", params={"token": ticket})
+20 -68
View File
@@ -32,7 +32,6 @@ pytestmark = pytest.mark.asyncio
class TestACLRoutes:
@pytest_asyncio.fixture
async def group_id(self, db_session: AsyncSession) -> str:
@@ -50,33 +49,18 @@ class TestACLRoutes:
return role_id
async def test_create_ace(
self,
app: FastAPI,
authorized_client: AsyncClient,
db_session: AsyncSession,
test_user: User,
role_id: str
self, app: FastAPI, authorized_client: AsyncClient, db_session: AsyncSession, test_user: User, role_id: str
) -> None:
# allow the user to create an ACE
rbac_repo = RbacRepository(db_session)
admin_role_id = (await rbac_repo.get_role_by_name("Administrator")).role_id
ace = ACECreate(
path="/access/acl",
ace_type="user",
user_id=test_user.user_id,
role_id=admin_role_id
)
ace = ACECreate(path="/access/acl", ace_type="user", user_id=test_user.user_id, role_id=admin_role_id)
await rbac_repo.create_ace(ace)
# add an ACE on /projects to allow user to create a project
path = f"/projects"
new_ace = {
"path": path,
"ace_type": "user",
"user_id": str(test_user.user_id),
"role_id": role_id
}
new_ace = {"path": path, "ace_type": "user", "user_id": str(test_user.user_id), "role_id": role_id}
response = await authorized_client.post(app.url_path_for("create_ace"), json=new_ace)
assert response.status_code == status.HTTP_201_CREATED
@@ -87,21 +71,11 @@ class TestACLRoutes:
response = await authorized_client.post(app.url_path_for("create_project"), json={"name": "test"})
assert response.status_code == status.HTTP_201_CREATED
async def test_create_ace_not_existing_endpoint(
self,
app: FastAPI,
client: AsyncClient,
group_id: str,
role_id: str
self, app: FastAPI, client: AsyncClient, group_id: str, role_id: str
) -> None:
new_ace = {
"path": "/projects/invalid",
"ace_type": "group",
"group_id": group_id,
"role_id": role_id
}
new_ace = {"path": "/projects/invalid", "ace_type": "group", "group_id": group_id, "role_id": role_id}
response = await client.post(app.url_path_for("create_ace"), json=new_ace)
assert response.status_code == status.HTTP_400_BAD_REQUEST
@@ -137,35 +111,23 @@ class TestACLRoutes:
assert len(response.json()) == 2
async def test_update_ace(
self, app: FastAPI,
client: AsyncClient,
db_session: AsyncSession,
test_user: User,
role_id: str
self, app: FastAPI, client: AsyncClient, db_session: AsyncSession, test_user: User, role_id: str
) -> None:
rbac_repo = RbacRepository(db_session)
ace_in_db = await rbac_repo.get_ace_by_path(f"/projects")
update_ace = {
"path": f"/appliances",
"ace_type": "user",
"user_id": str(test_user.user_id),
"role_id": role_id
}
response = await client.put(
app.url_path_for("update_ace", ace_id=ace_in_db.ace_id),
json=update_ace
)
update_ace = {"path": f"/appliances", "ace_type": "user", "user_id": str(test_user.user_id), "role_id": role_id}
response = await client.put(app.url_path_for("update_ace", ace_id=ace_in_db.ace_id), json=update_ace)
assert response.status_code == status.HTTP_200_OK
updated_ace_in_db = await rbac_repo.get_ace(ace_in_db.ace_id)
assert updated_ace_in_db.path == f"/appliances"
async def test_delete_ace(
self,
app: FastAPI,
client: AsyncClient,
db_session: AsyncSession,
self,
app: FastAPI,
client: AsyncClient,
db_session: AsyncSession,
) -> None:
rbac_repo = RbacRepository(db_session)
@@ -174,22 +136,17 @@ class TestACLRoutes:
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_ace_cleanup(
self,
app: FastAPI,
authorized_client: AsyncClient,
db_session: AsyncSession,
test_user: User,
role_id: str,
self,
app: FastAPI,
authorized_client: AsyncClient,
db_session: AsyncSession,
test_user: User,
role_id: str,
) -> None:
# allow the user to create projects
rbac_repo = RbacRepository(db_session)
ace = ACECreate(
path="/projects",
ace_type="user",
user_id=test_user.user_id,
role_id=role_id
)
ace = ACECreate(path="/projects", ace_type="user", user_id=test_user.user_id, role_id=role_id)
await rbac_repo.create_ace(ace)
response = await authorized_client.post(app.url_path_for("create_project"), json={"name": "test2"})
@@ -197,12 +154,7 @@ class TestACLRoutes:
project_id = response.json()["project_id"]
path = f"/projects/{project_id}"
ace = ACECreate(
path=path,
ace_type="user",
user_id=test_user.user_id,
role_id=role_id
)
ace = ACECreate(path=path, ace_type="user", user_id=test_user.user_id, role_id=role_id)
await rbac_repo.create_ace(ace)
assert await rbac_repo.get_ace_by_path(path)
+14 -17
View File
@@ -27,7 +27,6 @@ pytestmark = pytest.mark.asyncio
class TestApplianceRoutes:
# @pytest.fixture(autouse=True)
# def _install_builtin_appliances(self, controller: Controller):
#
@@ -63,7 +62,9 @@ class TestApplianceRoutes:
response = await client.post(app.url_path_for("install_appliance", appliance_id=appliance_id), params=params)
assert response.status_code == status.HTTP_400_BAD_REQUEST
async def test_qemu_appliance_install_with_version(self, app: FastAPI, client: AsyncClient, images_dir: str) -> None:
async def test_qemu_appliance_install_with_version(
self, app: FastAPI, client: AsyncClient, images_dir: str
) -> None:
shutil.copy("tests/resources/empty8G.qcow2", os.path.join(images_dir, "QEMU", "empty8G.qcow2"))
appliance_id = "1cfdf900-7c30-4cb7-8f03-3f61d2581633" # Empty VM appliance
@@ -73,7 +74,9 @@ class TestApplianceRoutes:
assert response.json()["name"] == "Empty VM"
assert response.json()["version"] == "8G"
async def test_qemu_appliance_install_without_version(self, app: FastAPI, client: AsyncClient, images_dir: str) -> None:
async def test_qemu_appliance_install_without_version(
self, app: FastAPI, client: AsyncClient, images_dir: str
) -> None:
appliance_id = "1cfdf900-7c30-4cb7-8f03-3f61d2581633" # Empty VM appliance
response = await client.post(app.url_path_for("install_appliance", appliance_id=appliance_id))
@@ -82,24 +85,18 @@ class TestApplianceRoutes:
async def test_add_version_appliance(self, app: FastAPI, client: AsyncClient) -> None:
appliance_id = "1cfdf900-7c30-4cb7-8f03-3f61d2581633" # Empty VM appliance
new_version = {
"name": "99G",
"images": {
"hda_disk_image": "empty99G.qcow2"
}
}
response = await client.post(app.url_path_for("add_appliance_version", appliance_id=appliance_id), json=new_version)
new_version = {"name": "99G", "images": {"hda_disk_image": "empty99G.qcow2"}}
response = await client.post(
app.url_path_for("add_appliance_version", appliance_id=appliance_id), json=new_version
)
assert response.status_code == status.HTTP_201_CREATED
assert new_version in response.json()["versions"]
async def test_add_existing_version_appliance(self, app: FastAPI, client: AsyncClient) -> None:
appliance_id = "1cfdf900-7c30-4cb7-8f03-3f61d2581633" # Empty VM appliance
new_version = {
"name": "8G",
"images": {
"hda_disk_image": "empty8G.qcow2"
}
}
response = await client.post(app.url_path_for("add_appliance_version", appliance_id=appliance_id), json=new_version)
new_version = {"name": "8G", "images": {"hda_disk_image": "empty8G.qcow2"}}
response = await client.post(
app.url_path_for("add_appliance_version", appliance_id=appliance_id), json=new_version
)
assert response.status_code == status.HTTP_409_CONFLICT
+14 -43
View File
@@ -30,15 +30,9 @@ from tests.utils import asyncio_patch, AsyncioMagicMock
class TestComputeRoutes:
async def test_compute_create(self, app: FastAPI, client: AsyncClient) -> None:
params = {
"protocol": "http",
"host": "localhost",
"port": 84,
"user": "julien",
"password": "secure"}
params = {"protocol": "http", "host": "localhost", "port": 84, "user": "julien", "password": "secure"}
response = await client.post(app.url_path_for("create_compute"), json=params)
assert response.status_code == status.HTTP_201_CREATED
@@ -57,7 +51,8 @@ class TestComputeRoutes:
"host": "127.0.0.1",
"port": 84,
"user": "julien",
"password": "secure"}
"password": "secure",
}
response = await client.post(app.url_path_for("create_compute"), json=params)
assert response.status_code == status.HTTP_201_CREATED
@@ -82,7 +77,8 @@ class TestComputeRoutes:
async def test_compute_get_local(self, app: FastAPI, client: AsyncClient, controller) -> None:
await controller.add_compute(
compute_id="local", name="local", host="127.0.0.1", port=3080, force=True, connect=False)
compute_id="local", name="local", host="127.0.0.1", port=3080, force=True, connect=False
)
response = await client.get(app.url_path_for("get_compute", compute_id="local"))
assert response.status_code == status.HTTP_200_OK
@@ -91,13 +87,7 @@ class TestComputeRoutes:
async def test_compute_update(self, app: FastAPI, client: AsyncClient, test_compute: Compute) -> None:
params = {
"protocol": "http",
"host": "localhost",
"port": 42,
"user": "julien",
"password": "secure"
}
params = {"protocol": "http", "host": "localhost", "port": 42, "user": "julien", "password": "secure"}
response = await client.post(app.url_path_for("create_compute"), json=params)
assert response.status_code == status.HTTP_201_CREATED
@@ -118,29 +108,22 @@ class TestComputeRoutes:
class TestComputeFeatures:
async def test_compute_list_docker_images(self, app: FastAPI, client: AsyncClient) -> None:
params = {
"protocol": "http",
"host": "localhost",
"port": 84,
"user": "julien",
"password": "secure"
}
params = {"protocol": "http", "host": "localhost", "port": 84, "user": "julien", "password": "secure"}
response = await client.post(app.url_path_for("create_compute"), json=params)
assert response.status_code == status.HTTP_201_CREATED
compute_id = response.json()["compute_id"]
with asyncio_patch("gns3server.controller.compute.Compute.forward", return_value=[{"image": "docker1"}, {"image": "docker2"}]) as mock:
with asyncio_patch(
"gns3server.controller.compute.Compute.forward", return_value=[{"image": "docker1"}, {"image": "docker2"}]
) as mock:
response = await client.get(app.url_path_for("docker_get_images", compute_id=compute_id))
mock.assert_called_with("GET", "docker", "images")
assert response.json() == [{"image": "docker1"}, {"image": "docker2"}]
async def test_compute_pull_docker_image(
self, app: FastAPI, client: AsyncClient, test_compute: Compute
) -> None:
async def test_compute_pull_docker_image(self, app: FastAPI, client: AsyncClient, test_compute: Compute) -> None:
compute = MagicMock()
compute.forward = AsyncioMagicMock(return_value={})
@@ -148,20 +131,14 @@ class TestComputeFeatures:
controller.return_value.get_compute.return_value = compute
response = await client.post(
app.url_path_for("docker_pull_image", compute_id=test_compute.compute_id),
json={"image": "nginx:latest"}
json={"image": "nginx:latest"},
)
compute.forward.assert_called_with("POST", "docker", "images/pull", data={"image": "nginx:latest"})
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_compute_list_virtualbox_vms(self, app: FastAPI, client: AsyncClient) -> None:
params = {
"protocol": "http",
"host": "localhost",
"port": 4242,
"user": "julien",
"password": "secure"
}
params = {"protocol": "http", "host": "localhost", "port": 4242, "user": "julien", "password": "secure"}
response = await client.post(app.url_path_for("get_computes"), json=params)
assert response.status_code == status.HTTP_201_CREATED
compute_id = response.json()["compute_id"]
@@ -173,13 +150,7 @@ class TestComputeFeatures:
async def test_compute_list_vmware_vms(self, app: FastAPI, client: AsyncClient) -> None:
params = {
"protocol": "http",
"host": "localhost",
"port": 4243,
"user": "julien",
"password": "secure"
}
params = {"protocol": "http", "host": "localhost", "port": 4243, "user": "julien", "password": "secure"}
response = await client.post(app.url_path_for("get_computes"), json=params)
assert response.status_code == status.HTTP_201_CREATED
compute_id = response.json()["compute_id"]
@@ -28,7 +28,6 @@ pytestmark = pytest.mark.asyncio
class TestControllerRoutes:
async def test_shutdown_local(self, app: FastAPI, client: AsyncClient, config: Config, monkeypatch) -> None:
# monkeypatch (not bare assignment): a global `os.kill = MagicMock()`
@@ -41,14 +40,12 @@ class TestControllerRoutes:
response = await client.post(app.url_path_for("shutdown"))
assert response.status_code == status.HTTP_204_NO_CONTENT
assert kill_mock.called
async def test_shutdown_non_local(self, app: FastAPI, client: AsyncClient, config: Config) -> None:
response = await client.post(app.url_path_for("shutdown"))
assert response.status_code == status.HTTP_403_FORBIDDEN
# @pytest.mark.asyncio
# async def test_debug(controller_api, config, tmpdir):
#
@@ -68,9 +65,8 @@ class TestControllerRoutes:
# config.set("Server", "local", False)
# response = await controller_api.post('/debug')
# assert response.status_code == 403
async def test_statistics_output(self, app: FastAPI, client: AsyncClient) -> None:
response = await client.get(app.url_path_for("statistics"))
assert response.status_code == status.HTTP_200_OK
+25 -38
View File
@@ -27,90 +27,77 @@ pytestmark = pytest.mark.asyncio
class TestDrawingsRoutes:
async def test_create_drawing(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
params = {
"svg": '<svg height="210" width="500"><line x1="0" y1="0" x2="200" y2="200" style="stroke:rgb(255,0,0);stroke-width:2" /></svg>',
"x": 10,
"y": 20,
"z": 0
"z": 0,
}
response = await client.post(app.url_path_for("create_drawing", project_id=project.id), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["drawing_id"] is not None
async def test_get_drawing(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
params = {
"svg": '<svg height="210" width="500"><line x1="0" y1="0" x2="200" y2="200" style="stroke:rgb(255,0,0);stroke-width:2" /></svg>',
"x": 10,
"y": 20,
"z": 0
"z": 0,
}
response = await client.post(app.url_path_for("create_drawing", project_id=project.id), json=params)
response = await client.get(app.url_path_for(
"get_drawing",
project_id=project.id,
drawing_id=response.json()["drawing_id"])
response = await client.get(
app.url_path_for("get_drawing", project_id=project.id, drawing_id=response.json()["drawing_id"])
)
assert response.status_code == 200
assert response.json()["x"] == 10
async def test_update_drawing(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
params = {
"svg": '<svg height="210" width="500"><line x1="0" y1="0" x2="200" y2="200" style="stroke:rgb(255,0,0);stroke-width:2" /></svg>',
"x": 10,
"y": 20,
"z": 0
"z": 0,
}
response = await client.post(app.url_path_for("create_drawing", project_id=project.id), json=params)
response = await client.put(app.url_path_for(
"update_drawing",
project_id=project.id,
drawing_id=response.json()["drawing_id"]),
json={"x": 42}
response = await client.put(
app.url_path_for("update_drawing", project_id=project.id, drawing_id=response.json()["drawing_id"]),
json={"x": 42},
)
assert response.status_code == status.HTTP_200_OK
assert response.json()["x"] == 42
async def test_all_drawings(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
params = {
"svg": '<svg height="210" width="500"><line x1="0" y1="0" x2="200" y2="200" style="stroke:rgb(255,0,0);stroke-width:2" /></svg>',
"x": 10,
"y": 20,
"z": 0
"z": 0,
}
await client.post(app.url_path_for("create_drawing", project_id=project.id), json=params)
response = await client.get(app.url_path_for("get_drawings", project_id=project.id))
assert response.status_code == status.HTTP_200_OK
assert len(response.json()) == 1
# test listing links from a closed project
await project.close(ignore_notification=True)
response = await client.get(app.url_path_for("get_drawings", project_id=project.id))
assert response.status_code == status.HTTP_200_OK
assert len(response.json()) == 1
async def test_delete_drawing(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
drawing = Drawing(project)
project._drawings = {drawing.id: drawing}
response = await client.delete(app.url_path_for(
"delete_drawing",
project_id=project.id,
drawing_id=drawing.id)
)
response = await client.delete(app.url_path_for("delete_drawing", project_id=project.id, drawing_id=drawing.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
assert drawing.id not in project.drawings
+10 -16
View File
@@ -25,33 +25,27 @@ pytestmark = pytest.mark.asyncio
class TestGNS3VMRoutes:
async def test_list_vms(self, app: FastAPI, client: AsyncClient) -> None:
with asyncio_patch("gns3server.controller.gns3vm.vmware_gns3_vm.VMwareGNS3VM.list", return_value=[{"vmname": "test"}]):
with asyncio_patch(
"gns3server.controller.gns3vm.vmware_gns3_vm.VMwareGNS3VM.list", return_value=[{"vmname": "test"}]
):
response = await client.get(app.url_path_for("get_vms", engine="vmware"))
assert response.status_code == status.HTTP_200_OK
assert response.json() == [
{
"vmname": "test"
}
]
assert response.json() == [{"vmname": "test"}]
async def test_engines(self, app: FastAPI, client: AsyncClient) -> None:
response = await client.get(app.url_path_for("get_engines"))
assert response.status_code == status.HTTP_200_OK
assert len(response.json()) > 0
async def test_put_gns3vm(self, app: FastAPI, client: AsyncClient) -> None:
response = await client.put(app.url_path_for("update_gns3vm_settings"), json={"vmname": "TEST VM"})
assert response.status_code == status.HTTP_200_OK
assert response.json()["vmname"] == "TEST VM"
async def test_get_gns3vm(self, app: FastAPI, client: AsyncClient) -> None:
response = await client.get(app.url_path_for("get_gns3vm_settings"))
assert response.status_code == status.HTTP_200_OK
+13 -59
View File
@@ -28,7 +28,6 @@ pytestmark = pytest.mark.asyncio
class TestGroupRoutes:
async def test_create_group(self, app: FastAPI, client: AsyncClient) -> None:
new_group = {"name": "group1"}
@@ -56,47 +55,30 @@ class TestGroupRoutes:
update_group = {"name": "group42"}
response = await client.put(
app.url_path_for("update_user_group", user_group_id=group_in_db.user_group_id),
json=update_group
app.url_path_for("update_user_group", user_group_id=group_in_db.user_group_id), json=update_group
)
assert response.status_code == status.HTTP_200_OK
updated_group_in_db = await user_repo.get_user_group(group_in_db.user_group_id)
assert updated_group_in_db.name == "group42"
async def test_cannot_update_admin_group(
self,
app: FastAPI,
client: AsyncClient,
db_session: AsyncSession
) -> None:
async def test_cannot_update_admin_group(self, app: FastAPI, client: AsyncClient, db_session: AsyncSession) -> None:
user_repo = UsersRepository(db_session)
group_in_db = await user_repo.get_user_group_by_name("Administrators")
update_group = {"name": "Hackers"}
response = await client.put(
app.url_path_for("update_user_group", user_group_id=group_in_db.user_group_id),
json=update_group
app.url_path_for("update_user_group", user_group_id=group_in_db.user_group_id), json=update_group
)
assert response.status_code == status.HTTP_403_FORBIDDEN
async def test_delete_group(
self,
app: FastAPI,
client: AsyncClient,
db_session: AsyncSession
) -> None:
async def test_delete_group(self, app: FastAPI, client: AsyncClient, db_session: AsyncSession) -> None:
user_repo = UsersRepository(db_session)
group_in_db = await user_repo.get_user_group_by_name("group42")
response = await client.delete(app.url_path_for("delete_user_group", user_group_id=group_in_db.user_group_id))
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_cannot_delete_admin_group(
self,
app: FastAPI,
client: AsyncClient,
db_session: AsyncSession
) -> None:
async def test_cannot_delete_admin_group(self, app: FastAPI, client: AsyncClient, db_session: AsyncSession) -> None:
user_repo = UsersRepository(db_session)
group_in_db = await user_repo.get_user_group_by_name("Administrators")
@@ -105,32 +87,21 @@ class TestGroupRoutes:
class TestGroupMembersRoutes:
async def test_add_to_group_already_member(
self,
app: FastAPI,
client: AsyncClient,
test_user: User,
db_session: AsyncSession
self, app: FastAPI, client: AsyncClient, test_user: User, db_session: AsyncSession
) -> None:
user_repo = UsersRepository(db_session)
group_in_db = await user_repo.get_user_group_by_name("Users")
response = await client.put(
app.url_path_for(
"add_member_to_group",
user_group_id=group_in_db.user_group_id,
user_id=str(test_user.user_id)
"add_member_to_group", user_group_id=group_in_db.user_group_id, user_id=str(test_user.user_id)
)
)
assert response.status_code == status.HTTP_400_BAD_REQUEST
async def test_add_member_to_group(
self,
app: FastAPI,
client: AsyncClient,
test_user: User,
db_session: AsyncSession
self, app: FastAPI, client: AsyncClient, test_user: User, db_session: AsyncSession
) -> None:
user_repo = UsersRepository(db_session)
@@ -140,9 +111,7 @@ class TestGroupMembersRoutes:
group_in_db = await user_repo.create_user_group(new_user_group)
response = await client.put(
app.url_path_for(
"add_member_to_group",
user_group_id=group_in_db.user_group_id,
user_id=str(test_user.user_id)
"add_member_to_group", user_group_id=group_in_db.user_group_id, user_id=str(test_user.user_id)
)
)
assert response.status_code == status.HTTP_204_NO_CONTENT
@@ -150,29 +119,16 @@ class TestGroupMembersRoutes:
assert len(members) == 1
assert members[0].username == test_user.username
async def test_get_user_group_members(
self,
app: FastAPI,
client: AsyncClient,
db_session: AsyncSession
) -> None:
async def test_get_user_group_members(self, app: FastAPI, client: AsyncClient, db_session: AsyncSession) -> None:
user_repo = UsersRepository(db_session)
group_in_db = await user_repo.get_user_group_by_name("test_group")
response = await client.get(
app.url_path_for(
"get_user_group_members",
user_group_id=group_in_db.user_group_id)
)
response = await client.get(app.url_path_for("get_user_group_members", user_group_id=group_in_db.user_group_id))
assert response.status_code == status.HTTP_200_OK
assert len(response.json()) == 1
async def test_remove_member_from_group(
self,
app: FastAPI,
client: AsyncClient,
test_user: User,
db_session: AsyncSession
self, app: FastAPI, client: AsyncClient, test_user: User, db_session: AsyncSession
) -> None:
user_repo = UsersRepository(db_session)
@@ -180,9 +136,7 @@ class TestGroupMembersRoutes:
response = await client.delete(
app.url_path_for(
"remove_member_from_group",
user_group_id=group_in_db.user_group_id,
user_id=str(test_user.user_id)
"remove_member_from_group", user_group_id=group_in_db.user_group_id, user_id=str(test_user.user_id)
),
)
assert response.status_code == status.HTTP_204_NO_CONTENT
+20 -12
View File
@@ -61,6 +61,7 @@ class TestImageSyncRoutes:
async def test_overlapping_sync_is_conflict(self, client, sync_service):
from gns3server.utils.image_inventory import ImageLock
async with ImageLock("image-inventory"):
response = await client.post("/v3/images/sync", json={})
assert response.status_code == 409
@@ -99,7 +100,9 @@ class TestImageSyncRoutes:
async def test_sync_requires_allocate_privilege(self, client, sync_service, test_user):
token = auth_service.create_access_token(test_user.username, secret_key=DEFAULT_JWT_SECRET_KEY)
with patch("gns3server.db.repositories.rbac.RbacRepository.check_user_has_privilege", new=AsyncMock(return_value=False)):
with patch(
"gns3server.db.repositories.rbac.RbacRepository.check_user_has_privilege", new=AsyncMock(return_value=False)
):
response = await client.post("/v3/images/sync", json={}, headers={"Authorization": f"Bearer {token}"})
assert response.status_code == 403
assert "Image.Allocate" in response.text
@@ -110,21 +113,26 @@ class TestImageSyncRoutes:
assert response.status_code == 401
async def test_template_can_detach_missing_image(self, app, client, sync_service, db_session):
uploaded = await client.post('/v3/images/upload/detach-missing.qcow2', content=QCOW)
uploaded = await client.post("/v3/images/upload/detach-missing.qcow2", content=QCOW)
assert uploaded.status_code == 201
image = uploaded.json()
image_id = (await ImagesRepository(db_session).get_image(image['path'])).image_id
created = await client.post('/v3/templates', json={
'name': 'Detach missing image', 'compute_id': 'local',
'template_type': 'qemu', 'hda_disk_image': image['filename'],
})
image_id = (await ImagesRepository(db_session).get_image(image["path"])).image_id
created = await client.post(
"/v3/templates",
json={
"name": "Detach missing image",
"compute_id": "local",
"template_type": "qemu",
"hda_disk_image": image["filename"],
},
)
assert created.status_code == 201
template_id = created.json()['template_id']
os.unlink(image['path'])
await client.post('/v3/images/sync', json={})
template_id = created.json()["template_id"]
os.unlink(image["path"])
await client.post("/v3/images/sync", json={})
await sync_service.task
updated = await client.put(f'/v3/templates/{template_id}', json={'hda_disk_image': ''})
updated = await client.put(f"/v3/templates/{template_id}", json={"hda_disk_image": ""})
assert updated.status_code == 200
assert not await ImagesRepository(db_session).get_image_templates(image_id)
# Detaching a template does not silently remove the missing catalog row.
assert await ImagesRepository(db_session).get_image(image['path']) is not None
assert await ImagesRepository(db_session).get_image(image["path"]) is not None
+82 -97
View File
@@ -43,7 +43,7 @@ def iou_32_bit_image(tmpdir) -> str:
path = os.path.join(tmpdir, "iou_32bit.bin")
with open(path, "wb+") as f:
f.write(b'\x7fELF\x01\x01\x01')
f.write(b"\x7fELF\x01\x01\x01")
return path
@@ -55,7 +55,7 @@ def iou_64_bit_image(tmpdir) -> str:
path = os.path.join(tmpdir, "iou_64bit.bin")
with open(path, "wb+") as f:
f.write(b'\x7fELF\x02\x01\x01')
f.write(b"\x7fELF\x02\x01\x01")
return path
@@ -67,7 +67,7 @@ def ios_image(tmpdir) -> str:
path = os.path.join(tmpdir, "ios_image.bin")
with open(path, "wb+") as f:
f.write(b'\x7fELF\x01\x02\x01')
f.write(b"\x7fELF\x01\x02\x01")
return path
@@ -79,7 +79,7 @@ def qcow2_image(tmpdir) -> str:
path = os.path.join(tmpdir, "image.qcow2")
with open(path, "wb+") as f:
f.write(b'QFI\xfb\x00\x00\x00')
f.write(b"QFI\xfb\x00\x00\x00")
return path
@@ -91,7 +91,7 @@ def invalid_image(tmpdir) -> str:
path = os.path.join(tmpdir, "invalid_image.bin")
with open(path, "wb+") as f:
f.write(b'\x01\x01\x01\x01')
f.write(b"\x01\x01\x01\x01")
return path
@@ -103,23 +103,23 @@ def empty_image(tmpdir) -> str:
path = os.path.join(tmpdir, "empty_image.bin")
with open(path, "wb+") as f:
f.write(b'')
f.write(b"")
return path
class TestImageRoutes:
async def test_create_image(self, app: FastAPI, client: AsyncClient, images_dir) -> None:
async def create_disk(path, options):
with open(path, "wb") as f:
f.write(b'QFI\xfb\x00\x00\x00')
f.write(b"QFI\xfb\x00\x00\x00")
Qemu.instance().create_disk_image = AsyncioMagicMock(side_effect=create_disk)
path = os.path.join(os.path.join(images_dir, "QEMU", "new_image.qcow2"))
image_name = os.path.basename(path)
response = await client.post(
app.url_path_for("create_qemu_image", image_path=image_name), json={"format": "qcow2", "size": 30})
app.url_path_for("create_qemu_image", image_path=image_name), json={"format": "qcow2", "size": 30}
)
assert response.status_code == status.HTTP_201_CREATED
assert os.path.isfile(path)
@@ -132,18 +132,18 @@ class TestImageRoutes:
("ios", "ios_image", True),
("ios", "invalid_image", False),
("qemu", "qcow2_image", True),
("qemu", "empty_image", False)
("qemu", "empty_image", False),
),
)
async def test_upload_image(
self,
app: FastAPI,
client: AsyncClient,
images_dir: str,
image_type: str,
fixture_name: str,
valid_request: bool,
request
self,
app: FastAPI,
client: AsyncClient,
images_dir: str,
image_type: str,
fixture_name: str,
valid_request: bool,
request,
) -> None:
image_path = request.getfixturevalue(fixture_name)
@@ -153,9 +153,7 @@ class TestImageRoutes:
image_data = f.read()
image_checksum.update(image_data)
response = await client.post(
app.url_path_for("upload_image", image_path=image_name),
content=image_data)
response = await client.post(app.url_path_for("upload_image", image_path=image_name), content=image_data)
if valid_request:
assert response.status_code == status.HTTP_201_CREATED
@@ -182,21 +180,15 @@ class TestImageRoutes:
with open(qcow2_image, "rb") as f:
image_data = f.read()
response = await client.post(
app.url_path_for("upload_image", image_path="image1.qcow2"),
content=image_data)
response = await client.post(app.url_path_for("upload_image", image_path="image1.qcow2"), content=image_data)
assert response.status_code == status.HTTP_201_CREATED
# same image with same name is uploaded again, it should return 409 Conflict
response = await client.post(
app.url_path_for("upload_image", image_path="image1.qcow2"),
content=image_data)
response = await client.post(app.url_path_for("upload_image", image_path="image1.qcow2"), content=image_data)
assert response.status_code == status.HTTP_409_CONFLICT
# same image with different name but same checksum is uploaded again, it should return 409 Conflict
response = await client.post(
app.url_path_for("upload_image", image_path="image2.qcow2"),
content=image_data)
response = await client.post(app.url_path_for("upload_image", image_path="image2.qcow2"), content=image_data)
assert response.status_code == status.HTTP_409_CONFLICT
async def test_image_delete(self, app: FastAPI, client: AsyncClient, qcow2_image: str) -> None:
@@ -211,14 +203,14 @@ class TestImageRoutes:
response = await client.get(app.url_path_for("get_image", image_path=image_name))
assert response.status_code == status.HTTP_404_NOT_FOUND
async def test_image_deleted_on_disk(self, app: FastAPI, client: AsyncClient, images_dir: str, qcow2_image: str) -> None:
async def test_image_deleted_on_disk(
self, app: FastAPI, client: AsyncClient, images_dir: str, qcow2_image: str
) -> None:
image_name = os.path.basename(qcow2_image)
with open(qcow2_image, "rb") as f:
image_data = f.read()
response = await client.post(
app.url_path_for("upload_image", image_path=image_name),
content=image_data)
response = await client.post(app.url_path_for("upload_image", image_path=image_name), content=image_data)
assert response.status_code == status.HTTP_201_CREATED
response = await client.delete(app.url_path_for("delete_image", image_path=image_name))
@@ -233,42 +225,30 @@ class TestImageRoutes:
),
)
async def test_upload_image_subdir(
self,
app: FastAPI,
client: AsyncClient,
images_dir: str,
qcow2_image: str,
subdir: str,
expected_result: int,
db_session: AsyncSession
self,
app: FastAPI,
client: AsyncClient,
images_dir: str,
qcow2_image: str,
subdir: str,
expected_result: int,
db_session: AsyncSession,
) -> None:
image_name = os.path.basename(qcow2_image)
with open(qcow2_image, "rb") as f:
image_data = f.read()
image_path = os.path.join(subdir, image_name)
response = await client.post(
app.url_path_for("upload_image", image_path=image_path),
content=image_data)
response = await client.post(app.url_path_for("upload_image", image_path=image_path), content=image_data)
assert response.status_code == expected_result
async def test_image_delete_multiple_match(
self,
app: FastAPI,
client: AsyncClient,
qcow2_image: str
) -> None:
async def test_image_delete_multiple_match(self, app: FastAPI, client: AsyncClient, qcow2_image: str) -> None:
image_name = os.path.basename(qcow2_image)
response = await client.delete(app.url_path_for("delete_image", image_path=image_name))
assert response.status_code == status.HTTP_400_BAD_REQUEST
async def test_image_delete_with_subdir(
self,
app: FastAPI,
client: AsyncClient,
qcow2_image: str
) -> None:
async def test_image_delete_with_subdir(self, app: FastAPI, client: AsyncClient, qcow2_image: str) -> None:
image_name = os.path.basename(qcow2_image)
image_path = os.path.join("subdir", image_name)
@@ -305,17 +285,20 @@ class TestImageRoutes:
with open(os.path.join(project_dir, f"{name}.gns3"), "w+") as f:
json.dump(topology, f)
return await controller.add_project(
project_id=project_id, name=name, path=project_dir,
filename=f"{name}.gns3", status="closed",
project_id=project_id,
name=name,
path=project_dir,
filename=f"{name}.gns3",
status="closed",
)
async def test_image_delete_used_by_project(
self,
app: FastAPI,
client: AsyncClient,
controller: Controller,
db_session: AsyncSession,
tmpdir: str,
self,
app: FastAPI,
client: AsyncClient,
controller: Controller,
db_session: AsyncSession,
tmpdir: str,
) -> None:
"""
An image referenced by a node in a project must not be deleted,
@@ -324,15 +307,21 @@ class TestImageRoutes:
image_path = os.path.join(tmpdir, "used.qcow2")
with open(image_path, "wb+") as f:
f.write(b'\x42\x42\x42\x42')
f.write(b"\x42\x42\x42\x42")
images_repo = ImagesRepository(db_session)
await images_repo.add_image("used.qcow2", "qemu", 42, image_path, "e342eb86c1229b6c154367a5476969b5", "md5")
guarded_project = await self._add_closed_project_with_node(
controller, "Guarded",
{"node_id": str(uuid.uuid4()), "node_type": "qemu", "compute_id": "local",
"name": "n1", "properties": {"hda_disk_image_backing_file": "used.qcow2"}},
controller,
"Guarded",
{
"node_id": str(uuid.uuid4()),
"node_type": "qemu",
"compute_id": "local",
"name": "n1",
"properties": {"hda_disk_image_backing_file": "used.qcow2"},
},
)
response = await client.delete(app.url_path_for("delete_image", image_path="used.qcow2"))
@@ -347,12 +336,12 @@ class TestImageRoutes:
assert not os.path.exists(image_path)
async def test_prune_images_keeps_project_referenced(
self,
app: FastAPI,
client: AsyncClient,
controller: Controller,
db_session: AsyncSession,
tmpdir: str,
self,
app: FastAPI,
client: AsyncClient,
controller: Controller,
db_session: AsyncSession,
tmpdir: str,
) -> None:
"""
Pruning must keep images referenced by a project node while
@@ -363,13 +352,19 @@ class TestImageRoutes:
for filename in ("used.qcow2", "unused.qcow2"):
image_path = os.path.join(tmpdir, filename)
with open(image_path, "wb+") as f:
f.write(b'\x42\x42\x42\x42')
f.write(b"\x42\x42\x42\x42")
await images_repo.add_image(filename, "qemu", 42, image_path, "e342eb86c1229b6c154367a5476969b5", "md5")
await self._add_closed_project_with_node(
controller, "Guarded",
{"node_id": str(uuid.uuid4()), "node_type": "qemu", "compute_id": "local",
"name": "n1", "properties": {"hda_disk_image_backing_file": "used.qcow2"}},
controller,
"Guarded",
{
"node_id": str(uuid.uuid4()),
"node_type": "qemu",
"compute_id": "local",
"name": "n1",
"properties": {"hda_disk_image_backing_file": "used.qcow2"},
},
)
response = await client.delete(app.url_path_for("prune_images"))
@@ -381,10 +376,7 @@ class TestImageRoutes:
assert not os.path.exists(os.path.join(tmpdir, "unused.qcow2"))
async def test_image_upload_create_appliance(
self, app: FastAPI,
client: AsyncClient,
db_session: AsyncSession,
controller: Controller
self, app: FastAPI, client: AsyncClient, db_session: AsyncSession, controller: Controller
) -> None:
await controller.appliance_manager.install_builtin_appliances()
@@ -396,7 +388,8 @@ class TestImageRoutes:
response = await client.post(
app.url_path_for("upload_image", image_path=image_name),
params={"install_appliances": "true"},
content=image_data)
content=image_data,
)
assert response.status_code == status.HTTP_201_CREATED
templates_repo = TemplatesRepository(db_session)
@@ -407,19 +400,14 @@ class TestImageRoutes:
await templates_repo.delete_template(templates[0].template_id)
async def test_install_all(
self, app: FastAPI,
client: AsyncClient,
db_session: AsyncSession,
controller: Controller
self, app: FastAPI, client: AsyncClient, db_session: AsyncSession, controller: Controller
) -> None:
image_path = "tests/resources/empty100G.qcow2"
image_name = os.path.basename(image_path)
with open(image_path, "rb") as f:
image_data = f.read()
response = await client.post(
app.url_path_for("upload_image", image_path=image_name),
content=image_data)
response = await client.post(app.url_path_for("upload_image", image_path=image_name), content=image_data)
assert response.status_code == status.HTTP_201_CREATED
controller.appliance_manager.load_appliances() # make sure appliances are loaded
@@ -440,10 +428,7 @@ class TestImageRoutes:
await templates_repo.delete_template(templates[0].template_id)
async def test_install_all_skips_existing_template_name(
self, app: FastAPI,
client: AsyncClient,
db_session: AsyncSession,
controller: Controller
self, app: FastAPI, client: AsyncClient, db_session: AsyncSession, controller: Controller
) -> None:
# two images matching two versions of the same appliance must not
# produce two templates with the same name
@@ -458,8 +443,8 @@ class TestImageRoutes:
with open(image_path, "rb") as f:
image_data = f.read()
response = await client.post(
app.url_path_for("upload_image", image_path=os.path.basename(image_path)),
content=image_data)
app.url_path_for("upload_image", image_path=os.path.basename(image_path)), content=image_data
)
assert response.status_code == status.HTTP_201_CREATED
controller.appliance_manager.load_appliances() # make sure appliances are loaded
+158 -255
View File
@@ -37,57 +37,43 @@ pytestmark = pytest.mark.asyncio
class TestLinkRoutes:
@pytest_asyncio.fixture
async def nodes(self, compute: Compute, project: Project) -> Tuple[Node, Node]:
response = MagicMock()
response.json = {"console": 2048}
compute.post = AsyncioMagicMock(return_value=response)
node1 = await project.add_node(compute, "node1", None, node_type="qemu")
node1._ports = [EthernetPort("E0", 0, 0, 3)]
node2 = await project.add_node(compute, "node2", None, node_type="qemu")
node2._ports = [EthernetPort("E0", 0, 2, 4)]
return node1, node2
async def test_create_link(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
nodes: Tuple[Node, Node]
self, app: FastAPI, client: AsyncClient, project: Project, nodes: Tuple[Node, Node]
) -> None:
node1, node2 = nodes
filters = {
"delay": [10, 0],
"frequency_drop": [50]
}
filters = {"delay": [10, 0], "frequency_drop": [50]}
with asyncio_patch("gns3server.controller.udp_link.UDPLink.create") as mock:
response = await client.post(app.url_path_for("create_link", project_id=project.id), json={
"nodes": [
{
"node_id": node1.id,
"adapter_number": 0,
"port_number": 3,
"label": {
"text": "Text",
"x": 42,
"y": 0
}
},
{
"node_id": node2.id,
"adapter_number": 2,
"port_number": 4
}
],
"filters": filters
})
response = await client.post(
app.url_path_for("create_link", project_id=project.id),
json={
"nodes": [
{
"node_id": node1.id,
"adapter_number": 0,
"port_number": 3,
"label": {"text": "Text", "x": 42, "y": 0},
},
{"node_id": node2.id, "adapter_number": 2, "port_number": 4},
],
"filters": filters,
},
)
assert mock.called
assert response.status_code == status.HTTP_201_CREATED
@@ -96,264 +82,193 @@ class TestLinkRoutes:
assert response.json()["nodes"][0]["label"]["x"] == 42
assert len(project.links) == 1
assert list(project.links.values())[0].filters == filters
async def test_create_link_failure(
self,
app: FastAPI,
client: AsyncClient,
compute: Compute,
project: Project
self, app: FastAPI, client: AsyncClient, compute: Compute, project: Project
) -> None:
"""
Make sure the link is deleted if we failed to create it.
The failure is triggered by connecting the link to itself
"""
response = MagicMock()
response.json = {"console": 2048}
compute.post = AsyncioMagicMock(return_value=response)
node1 = await project.add_node(compute, "node1", None, node_type="qemu")
node1._ports = [EthernetPort("E0", 0, 0, 3), EthernetPort("E0", 0, 0, 4)]
response = await client.post(app.url_path_for("create_link", project_id=project.id), json={
"nodes": [
{
"node_id": node1.id,
"adapter_number": 0,
"port_number": 3,
"label": {
"text": "Text",
"x": 42,
"y": 0
}
},
{
"node_id": node1.id,
"adapter_number": 0,
"port_number": 4
}
]
})
assert response.status_code == status.HTTP_409_CONFLICT
assert len(project.links) == 0
async def test_get_link(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
nodes: Tuple[Node, Node]
) -> None:
node1, node2 = nodes
with asyncio_patch("gns3server.controller.udp_link.UDPLink.create") as mock:
response = await client.post(app.url_path_for("create_link", project_id=project.id), json={
response = await client.post(
app.url_path_for("create_link", project_id=project.id),
json={
"nodes": [
{
"node_id": node1.id,
"adapter_number": 0,
"port_number": 3,
"label": {
"text": "Text",
"x": 42,
"y": 0
}
"label": {"text": "Text", "x": 42, "y": 0},
},
{
"node_id": node2.id,
"adapter_number": 2,
"port_number": 4
}
{"node_id": node1.id, "adapter_number": 0, "port_number": 4},
]
})
},
)
assert response.status_code == status.HTTP_409_CONFLICT
assert len(project.links) == 0
async def test_get_link(
self, app: FastAPI, client: AsyncClient, project: Project, nodes: Tuple[Node, Node]
) -> None:
node1, node2 = nodes
with asyncio_patch("gns3server.controller.udp_link.UDPLink.create") as mock:
response = await client.post(
app.url_path_for("create_link", project_id=project.id),
json={
"nodes": [
{
"node_id": node1.id,
"adapter_number": 0,
"port_number": 3,
"label": {"text": "Text", "x": 42, "y": 0},
},
{"node_id": node2.id, "adapter_number": 2, "port_number": 4},
]
},
)
assert mock.called
link_id = response.json()["link_id"]
assert response.json()["nodes"][0]["label"]["x"] == 42
response = await client.get(app.url_path_for("get_link", project_id=project.id, link_id=link_id))
assert response.status_code == status.HTTP_200_OK
assert response.json()["nodes"][0]["label"]["x"] == 42
async def test_update_link_suspend(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
nodes: Tuple[Node, Node]
self, app: FastAPI, client: AsyncClient, project: Project, nodes: Tuple[Node, Node]
) -> None:
node1, node2 = nodes
with asyncio_patch("gns3server.controller.udp_link.UDPLink.create") as mock:
response = await client.post(app.url_path_for("create_link", project_id=project.id), json={
response = await client.post(
app.url_path_for("create_link", project_id=project.id),
json={
"nodes": [
{
"node_id": node1.id,
"adapter_number": 0,
"port_number": 3,
"label": {"text": "Text", "x": 42, "y": 0},
},
{"node_id": node2.id, "adapter_number": 2, "port_number": 4},
]
},
)
assert mock.called
link_id = response.json()["link_id"]
assert response.json()["nodes"][0]["label"]["x"] == 42
response = await client.put(
app.url_path_for("update_link", project_id=project.id, link_id=link_id),
json={
"nodes": [
{
"node_id": node1.id,
"adapter_number": 0,
"port_number": 3,
"label": {
"text": "Text",
"x": 42,
"y": 0
}
"label": {"text": "Hello", "x": 64, "y": 0},
},
{
"node_id": node2.id,
"adapter_number": 2,
"port_number": 4
}
]
})
assert mock.called
link_id = response.json()["link_id"]
assert response.json()["nodes"][0]["label"]["x"] == 42
response = await client.put(app.url_path_for("update_link", project_id=project.id, link_id=link_id), json={
"nodes": [
{
"node_id": node1.id,
"adapter_number": 0,
"port_number": 3,
"label": {
"text": "Hello",
"x": 64,
"y": 0
}
},
{
"node_id": node2.id,
"adapter_number": 2,
"port_number": 4
}
],
"suspend": True
})
{"node_id": node2.id, "adapter_number": 2, "port_number": 4},
],
"suspend": True,
},
)
assert response.status_code == status.HTTP_200_OK
assert response.json()["nodes"][0]["label"]["x"] == 64
assert response.json()["suspend"]
assert response.json()["filters"] == {}
async def test_update_link(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
nodes: Tuple[Node, Node]
self, app: FastAPI, client: AsyncClient, project: Project, nodes: Tuple[Node, Node]
) -> None:
filters = {
"delay": [10, 0],
"frequency_drop": [50]
}
filters = {"delay": [10, 0], "frequency_drop": [50]}
node1, node2 = nodes
with asyncio_patch("gns3server.controller.udp_link.UDPLink.create") as mock:
response = await client.post(app.url_path_for("create_link", project_id=project.id), json={
response = await client.post(
app.url_path_for("create_link", project_id=project.id),
json={
"nodes": [
{
"node_id": node1.id,
"adapter_number": 0,
"port_number": 3,
"label": {"text": "Text", "x": 42, "y": 0},
},
{"node_id": node2.id, "adapter_number": 2, "port_number": 4},
]
},
)
assert mock.called
link_id = response.json()["link_id"]
assert response.json()["nodes"][0]["label"]["x"] == 42
response = await client.put(
app.url_path_for("update_link", project_id=project.id, link_id=link_id),
json={
"nodes": [
{
"node_id": node1.id,
"adapter_number": 0,
"port_number": 3,
"label": {
"text": "Text",
"x": 42,
"y": 0
}
"label": {"text": "Hello", "x": 64, "y": 0},
},
{
"node_id": node2.id,
"adapter_number": 2,
"port_number": 4
}
]
})
assert mock.called
link_id = response.json()["link_id"]
assert response.json()["nodes"][0]["label"]["x"] == 42
response = await client.put(app.url_path_for("update_link", project_id=project.id, link_id=link_id), json={
"nodes": [
{
"node_id": node1.id,
"adapter_number": 0,
"port_number": 3,
"label": {
"text": "Hello",
"x": 64,
"y": 0
}
},
{
"node_id": node2.id,
"adapter_number": 2,
"port_number": 4
}
],
"filters": filters
})
{"node_id": node2.id, "adapter_number": 2, "port_number": 4},
],
"filters": filters,
},
)
assert response.status_code == status.HTTP_200_OK
assert response.json()["nodes"][0]["label"]["x"] == 64
assert list(project.links.values())[0].filters == filters
async def test_list_link(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
nodes: Tuple[Node, Node]
self, app: FastAPI, client: AsyncClient, project: Project, nodes: Tuple[Node, Node]
) -> None:
filters = {
"delay": [10, 0],
"frequency_drop": [50]
}
filters = {"delay": [10, 0], "frequency_drop": [50]}
node1, node2 = nodes
nodes = [
{
"node_id": node1.id,
"adapter_number": 0,
"port_number": 3
},
{
"node_id": node2.id,
"adapter_number": 2,
"port_number": 4
}
{"node_id": node1.id, "adapter_number": 0, "port_number": 3},
{"node_id": node2.id, "adapter_number": 2, "port_number": 4},
]
with asyncio_patch("gns3server.controller.udp_link.UDPLink.create") as mock:
await client.post(app.url_path_for("create_link", project_id=project.id), json={
"nodes": nodes,
"filters": filters
})
await client.post(
app.url_path_for("create_link", project_id=project.id), json={"nodes": nodes, "filters": filters}
)
assert mock.called
response = await client.get(app.url_path_for("get_links", project_id=project.id))
assert response.status_code == status.HTTP_200_OK
assert len(response.json()) == 1
assert response.json()[0]["filters"] == filters
# test listing links from a closed project
await project.close(ignore_notification=True)
response = await client.get(app.url_path_for("get_links", project_id=project.id))
assert response.status_code == status.HTTP_200_OK
assert len(response.json()) == 1
assert response.json()[0]["filters"] == filters
async def test_reset_link(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
link = UDPLink(project)
project._links = {link.id: link}
with asyncio_patch("gns3server.controller.udp_link.UDPLink.delete") as delete_mock:
@@ -362,28 +277,27 @@ class TestLinkRoutes:
assert delete_mock.called
assert create_mock.called
assert response.status_code == status.HTTP_200_OK
async def test_start_capture(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
link = Link(project)
project._links = {link.id: link}
with asyncio_patch("gns3server.controller.link.Link.start_capture") as mock:
response = await client.post(app.url_path_for("start_capture", project_id=project.id, link_id=link.id), json={})
response = await client.post(
app.url_path_for("start_capture", project_id=project.id, link_id=link.id), json={}
)
assert mock.called
assert response.status_code == status.HTTP_201_CREATED
async def test_stop_capture(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
link = Link(project)
project._links = {link.id: link}
with asyncio_patch("gns3server.controller.link.Link.stop_capture") as mock:
response = await client.post(app.url_path_for("stop_capture", project_id=project.id, link_id=link.id))
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
# async def test_pcap(controller_api, http_client, project):
#
# async def pcap_capture():
@@ -403,20 +317,18 @@ class TestLinkRoutes:
# assert mock.called
# assert response.status_code == 200
# assert b'hello' == response.body
async def test_delete_link(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
link = Link(project)
project._links = {link.id: link}
with asyncio_patch("gns3server.controller.link.Link.delete") as mock:
response = await client.delete(app.url_path_for("delete_link", project_id=project.id, link_id=link.id))
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_list_filters(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
link = Link(project)
project._links = {link.id: link}
with patch("gns3server.controller.link.Link.available_filters", return_value=FILTERS) as mock:
@@ -425,7 +337,6 @@ class TestLinkRoutes:
assert response.status_code == status.HTTP_200_OK
assert response.json() == FILTERS
async def test_get_udp_interface(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
"""
Test getting UDP tunnel interface information from a link.
@@ -433,12 +344,12 @@ class TestLinkRoutes:
link = Link(project)
project._links = {link.id: link}
cloud_node = MagicMock()
cloud_node.node_type = "cloud"
cloud_node.id = str(uuid.uuid4())
cloud_node.name = "Cloud1"
compute = MagicMock()
response = MagicMock()
response.json = {
@@ -449,15 +360,15 @@ class TestLinkRoutes:
"lport": 20000,
"rhost": "127.0.0.1",
"rport": 30000,
"name": "UDP tunnel 1"
"name": "UDP tunnel 1",
}
]
}
compute.get = AsyncioMagicMock(return_value=response)
cloud_node.compute = compute
link._nodes = [{"node": cloud_node, "port_number": 1}]
response = await client.get(app.url_path_for("get_iface", project_id=project.id, link_id=link.id))
assert response.status_code == status.HTTP_200_OK
@@ -468,7 +379,6 @@ class TestLinkRoutes:
assert result["rport"] == 30000
assert result["type"] == "udp"
async def test_get_ethernet_interface(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
"""
Test getting ethernet interface information from a link.
@@ -484,14 +394,7 @@ class TestLinkRoutes:
compute = MagicMock()
response = MagicMock()
response.json = {
"ports_mapping": [
{
"port_number": 1,
"type": "ethernet",
"interface": "eth0",
"name": "Ethernet 1"
}
]
"ports_mapping": [{"port_number": 1, "type": "ethernet", "interface": "eth0", "name": "Ethernet 1"}]
}
compute.get = AsyncioMagicMock(return_value=response)
cloud_node.compute = compute
@@ -504,4 +407,4 @@ class TestLinkRoutes:
result = response.json()
assert result["node_id"] == cloud_node.id
assert result["interface"] == "eth0"
assert result["type"] == "ethernet"
assert result["type"] == "ethernet"
+130 -75
View File
@@ -55,27 +55,29 @@ def _add_marker(project, tag, enabled, node_id, frames=None):
"""Create a paused/capturing link+marker and optionally its pcap."""
link = UDPLink(project)
link._markers["icmp"] = {"bpf": "icmp", "tag": tag, "enabled": enabled, "color": None,
"highlight_duration": None, "capture_node_id": node_id,
"direction": None, "data_link_type": "DLT_EN10MB"}
link._markers["icmp"] = {
"bpf": "icmp",
"tag": tag,
"enabled": enabled,
"color": None,
"highlight_duration": None,
"capture_node_id": node_id,
"direction": None,
"data_link_type": "DLT_EN10MB",
}
project._links[link.id] = link
if frames is not None:
_write_pcap(
f"{project.markers_directory}/{node_id}_{link.id}_icmp.pcap", frames
)
_write_pcap(f"{project.markers_directory}/{node_id}_{link.id}_icmp.pcap", frames)
return link
class TestReplayRoutes:
async def test_range_409_while_capturing(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
_add_marker(project, tag=7, enabled=False, node_id="n1")
running = _add_marker(project, tag=7, enabled=True, node_id="n2")
response = await client.get(
app.url_path_for("replay_tag_range", project_id=project.id, tag=7)
)
response = await client.get(app.url_path_for("replay_tag_range", project_id=project.id, tag=7))
assert response.status_code == status.HTTP_409_CONFLICT
assert "icmp" in response.json()["message"]
assert running.id in response.json()["message"]
@@ -84,23 +86,25 @@ class TestReplayRoutes:
_add_marker(project, tag=7, enabled=False, node_id="n1")
response = await client.get(
app.url_path_for("replay_tag_range", project_id=project.id, tag=99)
)
response = await client.get(app.url_path_for("replay_tag_range", project_id=project.id, tag=99))
assert response.status_code == status.HTTP_404_NOT_FOUND
async def test_range_501_without_sharkd(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
# A non-empty pcap: the engine must be consulted, and without sharkd
# the whole feature is unavailable (hard requirement, no degraded mode).
_add_marker(project, tag=7, enabled=False, node_id="n1", frames=[
(1693472000, 123456, _icmp_frame()),
])
_add_marker(
project,
tag=7,
enabled=False,
node_id="n1",
frames=[
(1693472000, 123456, _icmp_frame()),
],
)
with patch("gns3server.controller.marker_replay.shutil.which", return_value=None):
response = await client.get(
app.url_path_for("replay_tag_range", project_id=project.id, tag=7)
)
response = await client.get(app.url_path_for("replay_tag_range", project_id=project.id, tag=7))
assert response.status_code == status.HTTP_501_NOT_IMPLEMENTED
# The app's HTTPException handler unifies the body as {"message": …}.
assert "sharkd" in response.json()["message"]
@@ -115,9 +119,16 @@ class TestReplayRoutes:
project._links.update({r1.id: r1, r2.id: r2})
def _wire(link, node_id, frames):
link._markers["icmp"] = {"bpf": "icmp", "tag": 7, "enabled": False, "color": None,
"highlight_duration": None, "capture_node_id": node_id,
"direction": None, "data_link_type": "DLT_EN10MB"}
link._markers["icmp"] = {
"bpf": "icmp",
"tag": 7,
"enabled": False,
"color": None,
"highlight_duration": None,
"capture_node_id": node_id,
"direction": None,
"data_link_type": "DLT_EN10MB",
}
_write_pcap(f"{project.markers_directory}/{node_id}_{link.id}_icmp.pcap", frames)
# r1→r2 captures at t1 and t3; r2→r3 captures at t2 and t3 (same µs
@@ -132,9 +143,7 @@ class TestReplayRoutes:
monkeypatch.setattr(marker_replay, "_columns_for", fake_columns)
response = await client.get(
app.url_path_for("replay_tag_range", project_id=project.id, tag=7)
)
response = await client.get(app.url_path_for("replay_tag_range", project_id=project.id, tag=7))
assert response.status_code == status.HTTP_200_OK
body = response.json()
assert body["tag"] == 7
@@ -143,8 +152,10 @@ class TestReplayRoutes:
assert body["end"] == "1693472002.000000"
assert [f["node_id"] for f in body["frames"]] == ["n1", "n2", "n1", "n2"]
assert [f["ts"] for f in body["frames"]] == [
"1693472000.500000", "1693472001.000000",
"1693472002.000000", "1693472002.000000",
"1693472000.500000",
"1693472001.000000",
"1693472002.000000",
"1693472002.000000",
]
# Wireshark-style columns ride along on every frame entry.
assert body["frames"][0]["src"] == "10.0.0.1"
@@ -156,9 +167,15 @@ class TestReplayRoutes:
self, app: FastAPI, client: AsyncClient, project: Project, monkeypatch
) -> None:
_add_marker(project, tag=7, enabled=False, node_id="n1", frames=[
(1693472000, 0, b"a" * 60),
])
_add_marker(
project,
tag=7,
enabled=False,
node_id="n1",
frames=[
(1693472000, 0, b"a" * 60),
],
)
async def fake_columns(pcap, filter_expr):
return {1: _cols()}
@@ -172,14 +189,18 @@ class TestReplayRoutes:
assert response.status_code == status.HTTP_200_OK
assert response.json() == {"frames": []}
async def test_frames_window_hit(
self, app: FastAPI, client: AsyncClient, project: Project, monkeypatch
) -> None:
async def test_frames_window_hit(self, app: FastAPI, client: AsyncClient, project: Project, monkeypatch) -> None:
_add_marker(project, tag=7, enabled=False, node_id="n1", frames=[
(1693472000, 0, b"a" * 60),
(1693472000, 150000, b"a" * 60),
])
_add_marker(
project,
tag=7,
enabled=False,
node_id="n1",
frames=[
(1693472000, 0, b"a" * 60),
(1693472000, 150000, b"a" * 60),
],
)
async def fake_columns(pcap, filter_expr):
return {1: _cols(), 2: _cols()}
@@ -191,15 +212,19 @@ class TestReplayRoutes:
params={"ts": "1693472000.000000", "window_ms": 150},
)
assert response.status_code == status.HTTP_200_OK
assert [f["ts"] for f in response.json()["frames"]] == [
"1693472000.000000", "1693472000.150000"
]
assert [f["ts"] for f in response.json()["frames"]] == ["1693472000.000000", "1693472000.150000"]
async def test_detail_404_on_ts_mismatch(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
link = _add_marker(project, tag=7, enabled=False, node_id="n1", frames=[
(1693472000, 123456, _icmp_frame()),
])
link = _add_marker(
project,
tag=7,
enabled=False,
node_id="n1",
frames=[
(1693472000, 123456, _icmp_frame()),
],
)
response = await client.get(
app.url_path_for("replay_tag_frame_detail", project_id=project.id, tag=7),
@@ -216,9 +241,16 @@ class TestReplayRoutes:
project._links.update({r1.id: r1, r2.id: r2})
def _wire(link, node_id, frames):
link._markers["icmp"] = {"bpf": "icmp", "tag": 7, "enabled": False, "color": None,
"highlight_duration": None, "capture_node_id": node_id,
"direction": None, "data_link_type": "DLT_EN10MB"}
link._markers["icmp"] = {
"bpf": "icmp",
"tag": 7,
"enabled": False,
"color": None,
"highlight_duration": None,
"capture_node_id": node_id,
"direction": None,
"data_link_type": "DLT_EN10MB",
}
_write_pcap(f"{project.markers_directory}/{node_id}_{link.id}_icmp.pcap", frames)
_wire(r1, "n1", [(1693472000, 0, b"a" * 60), (1693472002, 0, b"a" * 60)])
@@ -254,15 +286,19 @@ class TestReplayRoutes:
self, app: FastAPI, client: AsyncClient, project: Project, no_residual_sessions
) -> None:
_add_marker(project, tag=7, enabled=False, node_id="n1", frames=[
(1693472000, 123456, _icmp_frame()),
(1693472001, 0, _tcp_syn_frame()),
])
_add_marker(
project,
tag=7,
enabled=False,
node_id="n1",
frames=[
(1693472000, 123456, _icmp_frame()),
(1693472001, 0, _tcp_syn_frame()),
],
)
# Unfiltered: both frames with real engine columns.
response = await client.get(
app.url_path_for("replay_tag_range", project_id=project.id, tag=7)
)
response = await client.get(app.url_path_for("replay_tag_range", project_id=project.id, tag=7))
assert response.status_code == status.HTTP_200_OK
frames = response.json()["frames"]
assert [f["proto"] for f in frames] == ["ICMP", "TCP"]
@@ -285,9 +321,15 @@ class TestReplayRoutes:
self, app: FastAPI, client: AsyncClient, project: Project, no_residual_sessions
) -> None:
_add_marker(project, tag=7, enabled=False, node_id="n1", frames=[
(1693472000, 123456, _icmp_frame()),
])
_add_marker(
project,
tag=7,
enabled=False,
node_id="n1",
frames=[
(1693472000, 123456, _icmp_frame()),
],
)
response = await client.get(
app.url_path_for("replay_tag_range", project_id=project.id, tag=7),
@@ -308,14 +350,19 @@ class TestReplayRoutes:
self, app: FastAPI, client: AsyncClient, project: Project, no_residual_sessions
) -> None:
link = _add_marker(project, tag=7, enabled=False, node_id="n1", frames=[
(1693472000, 123456, _icmp_frame()),
])
link = _add_marker(
project,
tag=7,
enabled=False,
node_id="n1",
frames=[
(1693472000, 123456, _icmp_frame()),
],
)
response = await client.get(
app.url_path_for("replay_tag_frame_detail", project_id=project.id, tag=7),
params={"ts": "1693472000.123456", "node_id": "n1",
"link_id": link.id, "marker": "icmp"},
params={"ts": "1693472000.123456", "node_id": "n1", "link_id": link.id, "marker": "icmp"},
)
assert response.status_code == status.HTTP_200_OK
body = response.json()
@@ -345,12 +392,17 @@ class TestReplayRoutes:
# Two frames in the same microsecond: only the explicit frame number
# (from the frame list entry) tells them apart.
link = _add_marker(project, tag=7, enabled=False, node_id="n1", frames=[
(1693472000, 123456, _icmp_frame()),
(1693472000, 123456, _tcp_syn_frame()),
])
common = {"ts": "1693472000.123456", "node_id": "n1",
"link_id": link.id, "marker": "icmp"}
link = _add_marker(
project,
tag=7,
enabled=False,
node_id="n1",
frames=[
(1693472000, 123456, _icmp_frame()),
(1693472000, 123456, _tcp_syn_frame()),
],
)
common = {"ts": "1693472000.123456", "node_id": "n1", "link_id": link.id, "marker": "icmp"}
response = await client.get(
app.url_path_for("replay_tag_frame_detail", project_id=project.id, tag=7),
@@ -369,18 +421,21 @@ class TestReplayRoutes:
assert response.json()["hex"] == _tcp_syn_frame().hex()
@sharkd_present
async def test_detail_501_when_sharkd_disappears(
self, app: FastAPI, client: AsyncClient, project: Project
) -> None:
async def test_detail_501_when_sharkd_disappears(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
link = _add_marker(project, tag=7, enabled=False, node_id="n1", frames=[
(1693472000, 123456, _icmp_frame()),
])
link = _add_marker(
project,
tag=7,
enabled=False,
node_id="n1",
frames=[
(1693472000, 123456, _icmp_frame()),
],
)
with patch("gns3server.controller.marker_replay.shutil.which", return_value=None):
response = await client.get(
app.url_path_for("replay_tag_frame_detail", project_id=project.id, tag=7),
params={"ts": "1693472000.123456", "node_id": "n1",
"link_id": link.id, "marker": "icmp"},
params={"ts": "1693472000.123456", "node_id": "n1", "link_id": link.id, "marker": "icmp"},
)
assert response.status_code == status.HTTP_501_NOT_IMPLEMENTED
+34 -19
View File
@@ -35,14 +35,17 @@ pytestmark = pytest.mark.asyncio
def _inherited(link, name="arp"):
"""Inject an inherited marker so the controller's inheritance guard can fire."""
link._markers[f"global-{name}"] = {
"bpf": name, "tag": None, "enabled": True, "color": None,
"highlight_duration": None, "capture_node_id": "node-id",
"bpf": name,
"tag": None,
"enabled": True,
"color": None,
"highlight_duration": None,
"capture_node_id": "node-id",
"inherited_from": name,
}
class TestMarkerRoutes:
# -----------------------------------------------------------------------
# Per-link markers
# -----------------------------------------------------------------------
@@ -81,7 +84,9 @@ class TestMarkerRoutes:
_, kwargs = mock.call_args
assert kwargs["name"] == "web"
async def test_create_marker_global_prefix_rejected(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
async def test_create_marker_global_prefix_rejected(
self, app: FastAPI, client: AsyncClient, project: Project
) -> None:
link = UDPLink(project)
project._links = {link.id: link}
@@ -105,7 +110,9 @@ class TestMarkerRoutes:
)
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
async def test_create_marker_name_too_long_rejected(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
async def test_create_marker_name_too_long_rejected(
self, app: FastAPI, client: AsyncClient, project: Project
) -> None:
link = UDPLink(project)
project._links = {link.id: link}
@@ -119,13 +126,17 @@ class TestMarkerRoutes:
async def test_get_markers(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
link = UDPLink(project)
link._markers["web"] = {"bpf": "tcp port 80", "tag": None, "enabled": True,
"color": None, "highlight_duration": 800, "capture_node_id": "n1"}
link._markers["web"] = {
"bpf": "tcp port 80",
"tag": None,
"enabled": True,
"color": None,
"highlight_duration": 800,
"capture_node_id": "n1",
}
project._links = {link.id: link}
response = await client.get(
app.url_path_for("get_markers", project_id=project.id, link_id=link.id)
)
response = await client.get(app.url_path_for("get_markers", project_id=project.id, link_id=link.id))
assert response.status_code == status.HTTP_200_OK
assert response.json()["web"]["highlight_duration"] == 800
@@ -197,7 +208,9 @@ class TestMarkerRoutes:
assert kwargs["bpf"] == "arp"
assert kwargs["highlight_duration"] == 1200
async def test_create_marker_definition_global_prefix_rejected(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
async def test_create_marker_definition_global_prefix_rejected(
self, app: FastAPI, client: AsyncClient, project: Project
) -> None:
with asyncio_patch("gns3server.controller.project.Project.create_marker_definition") as mock:
response = await client.post(
@@ -235,9 +248,7 @@ class TestMarkerRoutes:
}
project._links = {}
response = await client.get(
app.url_path_for("get_marker_definitions", project_id=project.id)
)
response = await client.get(app.url_path_for("get_marker_definitions", project_id=project.id))
assert response.status_code == status.HTTP_200_OK
body = response.json()
assert body["arp"]["bpf"] == "arp"
@@ -251,13 +262,17 @@ class TestMarkerRoutes:
async def test_get_project_markers(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
link = UDPLink(project)
link._markers["icmp"] = {"bpf": "icmp", "tag": 1, "enabled": True, "color": "#ff5722",
"highlight_duration": 800, "capture_node_id": "node-1"}
link._markers["icmp"] = {
"bpf": "icmp",
"tag": 1,
"enabled": True,
"color": "#ff5722",
"highlight_duration": 800,
"capture_node_id": "node-1",
}
project._links = {link.id: link}
response = await client.get(
app.url_path_for("get_project_markers", project_id=project.id)
)
response = await client.get(app.url_path_for("get_project_markers", project_id=project.id))
assert response.status_code == status.HTTP_200_OK
body = response.json()
key = f"{link.id}/icmp"
@@ -24,7 +24,6 @@ pytestmark = pytest.mark.asyncio
class TestNetmikoRoutes:
async def test_device_types(self, app: FastAPI, client: AsyncClient) -> None:
"""
Test listing the device types supported by the installed Netmiko library.
+231 -429
View File
@@ -49,74 +49,69 @@ pytestmark = pytest.mark.asyncio
class TestNodeRoutes:
@pytest.fixture
def node(self, project: Project, compute: Compute) -> Node:
node = Node(project, compute, "test", node_type="vpcs")
project._nodes[node.id] = node
return node
async def test_create_node(self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute) -> None:
response = MagicMock()
response.json = {"console": 2048}
compute.post = AsyncioMagicMock(return_value=response)
response = await client.post(app.url_path_for("create_node", project_id=project.id), json={
"name": "test",
"node_type": "vpcs",
"compute_id": "example.com",
"properties": {
"startup_script": "echo test"
}
})
response = await client.post(
app.url_path_for("create_node", project_id=project.id),
json={
"name": "test",
"node_type": "vpcs",
"compute_id": "example.com",
"properties": {"startup_script": "echo test"},
},
)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "test"
assert "name" not in response.json()["properties"]
async def test_list_node(self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute) -> None:
response = MagicMock()
response.json = {"console": 2048}
compute.post = AsyncioMagicMock(return_value=response)
await client.post(app.url_path_for("create_node", project_id=project.id), json={
"name": "test",
"node_type": "vpcs",
"compute_id": "example.com",
"properties": {
"startup_script": "echo test"
}
})
await client.post(
app.url_path_for("create_node", project_id=project.id),
json={
"name": "test",
"node_type": "vpcs",
"compute_id": "example.com",
"properties": {"startup_script": "echo test"},
},
)
response = await client.get(app.url_path_for("get_nodes", project_id=project.id))
assert response.status_code == status.HTTP_200_OK
assert response.json()[0]["name"] == "test"
# test listing nodes from a closed project
await project.close(ignore_notification=True)
response = await client.get(app.url_path_for("get_nodes", project_id=project.id))
assert response.status_code == status.HTTP_200_OK
assert response.json()[0]["name"] == "test"
async def test_list_node_with_missing_image(
self, app: FastAPI, client: AsyncClient, project: Project, node: Node
) -> None:
node._missing_images = [
{"property": "path", "image": "i86bi-linux-l2.bin", "image_type": "iou"}
]
node._missing_images = [{"property": "path", "image": "i86bi-linux-l2.bin", "image_type": "iou"}]
response = await client.get(app.url_path_for("get_nodes", project_id=project.id))
assert response.status_code == status.HTTP_200_OK
data = response.json()[0]
assert data["missing_image"] is True
assert data["missing_images"] == [
{"property": "path", "image": "i86bi-linux-l2.bin", "image_type": "iou"}
]
assert data["missing_images"] == [{"property": "path", "image": "i86bi-linux-l2.bin", "image_type": "iou"}]
@pytest.mark.parametrize(
"tags, expected_match",
@@ -129,37 +124,33 @@ class TestNodeRoutes:
),
)
async def test_list_nodes_with_tags(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
tags: list,
expected_match: bool
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, tags: list, expected_match: bool
) -> None:
response = MagicMock()
response.json = {"console": 2048}
compute.post = AsyncioMagicMock(return_value=response)
await client.post(app.url_path_for("create_node", project_id=project.id), json={
"name": "test",
"node_type": "vpcs",
"compute_id": "example.com",
"tags": ["tag1", "tag2"],
"properties": {
"startup_script": "echo test"
}
})
await client.post(
app.url_path_for("create_node", project_id=project.id),
json={
"name": "test",
"node_type": "vpcs",
"compute_id": "example.com",
"tags": ["tag1", "tag2"],
"properties": {"startup_script": "echo test"},
},
)
await client.post(app.url_path_for("create_node", project_id=project.id), json={
"name": "test2",
"node_type": "vpcs",
"compute_id": "example.com",
"tags": ["tag3", "tag4"],
"properties": {
"startup_script": "echo test"
}
})
await client.post(
app.url_path_for("create_node", project_id=project.id),
json={
"name": "test2",
"node_type": "vpcs",
"compute_id": "example.com",
"tags": ["tag3", "tag4"],
"properties": {"startup_script": "echo test"},
},
)
params = {"tags": tags}
response = await client.get(app.url_path_for("get_nodes", project_id=project.id), params=params)
@@ -169,161 +160,106 @@ class TestNodeRoutes:
else:
assert len(response.json()) == 0
async def test_get_node(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute
) -> None:
async def test_get_node(self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute) -> None:
response = MagicMock()
response.json = {"console": 2048}
compute.post = AsyncioMagicMock(return_value=response)
response = await client.post(app.url_path_for("create_node", project_id=project.id), json={
"name": "test",
"node_type": "vpcs",
"compute_id": "example.com",
"properties": {
"startup_script": "echo test"
}
})
response = await client.get(app.url_path_for("get_node", project_id=project.id, node_id=response.json()["node_id"]))
response = await client.post(
app.url_path_for("create_node", project_id=project.id),
json={
"name": "test",
"node_type": "vpcs",
"compute_id": "example.com",
"properties": {"startup_script": "echo test"},
},
)
response = await client.get(
app.url_path_for("get_node", project_id=project.id, node_id=response.json()["node_id"])
)
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "test"
async def test_update_node(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node
) -> None:
response = MagicMock()
response.json = {"console": 2048}
compute.put = AsyncioMagicMock(return_value=response)
response = await client.put(app.url_path_for("update_node", project_id=project.id, node_id=node.id), json={
"name": "test",
"node_type": "vpcs",
"compute_id": "example.com",
"tags": ["tag1", "tag2"],
"properties": {
"startup_script": "echo test"
}
})
response = await client.put(
app.url_path_for("update_node", project_id=project.id, node_id=node.id),
json={
"name": "test",
"node_type": "vpcs",
"compute_id": "example.com",
"tags": ["tag1", "tag2"],
"properties": {"startup_script": "echo test"},
},
)
assert response.status_code == 200
assert response.json()["name"] == "test"
assert "name" not in response.json()["properties"]
assert response.json()["tags"] == ["tag1", "tag2"]
async def test_start_all_nodes(self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute) -> None:
async def test_start_all_nodes(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute
) -> None:
compute.post = AsyncioMagicMock()
response = await client.post(app.url_path_for("start_all_nodes", project_id=project.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_stop_all_nodes(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute
) -> None:
async def test_stop_all_nodes(self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute) -> None:
compute.post = AsyncioMagicMock()
response = await client.post(app.url_path_for("stop_all_nodes", project_id=project.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_suspend_all_nodes(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute
) -> None:
compute.post = AsyncioMagicMock()
response = await client.post(app.url_path_for("suspend_all_nodes", project_id=project.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_reload_all_nodes(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute
) -> None:
compute.post = AsyncioMagicMock()
response = await client.post(app.url_path_for("reload_all_nodes", project_id=project.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_reset_console_all_nodes(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute
) -> None:
compute.post = AsyncioMagicMock()
response = await client.post(app.url_path_for("reset_console_all_nodes", project_id=project.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_start_node(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node
) -> None:
compute.post = AsyncioMagicMock()
response = await client.post(app.url_path_for("start_node", project_id=project.id, node_id=node.id), json={})
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_stop_node(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node
) -> None:
compute.post = AsyncioMagicMock()
response = await client.post(app.url_path_for("stop_node", project_id=project.id, node_id=node.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_suspend_node(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node
) -> None:
compute.post = AsyncioMagicMock()
@@ -331,12 +267,7 @@ class TestNodeRoutes:
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_suspend_node_unsupported(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node
) -> None:
# node types without suspend support (e.g. VPCS, IOU) must surface the
@@ -348,12 +279,7 @@ class TestNodeRoutes:
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
async def test_suspend_all_nodes_tolerates_unsupported(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node
) -> None:
# suspending all nodes of a mixed project stays best-effort: nodes
@@ -363,259 +289,162 @@ class TestNodeRoutes:
)
response = await client.post(app.url_path_for("suspend_all_nodes", project_id=project.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_reload_node(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node
):
async def test_reload_node(self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node):
compute.post = AsyncioMagicMock()
response = await client.post(app.url_path_for("reload_node", project_id=project.id, node_id=node.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_isolate_node(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node
):
compute.post = AsyncioMagicMock()
response = await client.post(app.url_path_for("isolate_node", project_id=project.id, node_id=node.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_unisolate_node(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node
) -> None:
compute.post = AsyncioMagicMock()
response = await client.post(app.url_path_for("unisolate_node", project_id=project.id, node_id=node.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_duplicate_node(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node
) -> None:
response = MagicMock()
response.json({"console": 2035})
compute.post = AsyncioMagicMock(return_value=response)
response = await client.post(app.url_path_for("duplicate_node", project_id=project.id, node_id=node.id),
json={"x": 10, "y": 5, "z": 0})
response = await client.post(
app.url_path_for("duplicate_node", project_id=project.id, node_id=node.id), json={"x": 10, "y": 5, "z": 0}
)
assert response.status_code == status.HTTP_201_CREATED
async def test_delete_node(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node
) -> None:
compute.post = AsyncioMagicMock()
response = await client.delete(app.url_path_for("delete_node", project_id=project.id, node_id=node.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_dynamips_idle_pc(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node
) -> None:
response = MagicMock()
response.json = {"idlepc": "0x60606f54"}
compute.get = AsyncioMagicMock(return_value=response)
node._node_type = "dynamips" # force Dynamips node type
response = await client.get(app.url_path_for("auto_idlepc", project_id=project.id, node_id=node.id))
assert response.status_code == status.HTTP_200_OK
assert response.json()["idlepc"] == "0x60606f54"
async def test_dynamips_idle_pc_wrong_node_type(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node
) -> None:
response = await client.get(app.url_path_for("auto_idlepc", project_id=project.id, node_id=node.id))
assert response.status_code == status.HTTP_400_BAD_REQUEST
async def test_dynamips_idlepc_proposals(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node
) -> None:
response = MagicMock()
response.json = ["0x60606f54", "0x33805a22"]
compute.get = AsyncioMagicMock(return_value=response)
node._node_type = "dynamips" # force Dynamips node type
response = await client.get(app.url_path_for("idlepc_proposals", project_id=project.id, node_id=node.id))
assert response.status_code == status.HTTP_200_OK
assert response.json() == ["0x60606f54", "0x33805a22"]
async def test_dynamips_idlepc_proposals_wrong_node_type(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node
) -> None:
response = await client.get(app.url_path_for("idlepc_proposals", project_id=project.id, node_id=node.id))
assert response.status_code == status.HTTP_400_BAD_REQUEST
async def test_qemu_disk_image_create(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node
) -> None:
response = MagicMock()
compute.post = AsyncioMagicMock(return_value=response)
node._node_type = "qemu" # force Qemu node type
response = await client.post(
app.url_path_for("create_disk_image", project_id=project.id, node_id=node.id, disk_name="hda_disk.qcow2"),
json={"format": "qcow2", "size": 30}
json={"format": "qcow2", "size": 30},
)
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_qemu_disk_image_create_wrong_node_type(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node
) -> None:
response = await client.post(
app.url_path_for("create_disk_image", project_id=project.id, node_id=node.id, disk_name="hda_disk.qcow2"),
json={"format": "qcow2", "size": 30}
json={"format": "qcow2", "size": 30},
)
assert response.status_code == status.HTTP_400_BAD_REQUEST
async def test_qemu_disk_image_update(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node
) -> None:
response = MagicMock()
compute.put = AsyncioMagicMock(return_value=response)
node._node_type = "qemu" # force Qemu node type
response = await client.put(
app.url_path_for("update_disk_image", project_id=project.id, node_id=node.id, disk_name="hda_disk.qcow2"),
json={"extend": 10}
json={"extend": 10},
)
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_qemu_disk_image_update_wrong_node_type(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node
) -> None:
response = await client.put(
app.url_path_for("update_disk_image", project_id=project.id, node_id=node.id, disk_name="hda_disk.qcow2"),
json={"extend": 10}
json={"extend": 10},
)
assert response.status_code == status.HTTP_400_BAD_REQUEST
async def test_qemu_disk_image_delete(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node
) -> None:
response = MagicMock()
compute.delete = AsyncioMagicMock(return_value=response)
node._node_type = "qemu" # force Qemu node type
response = await client.delete(
app.url_path_for("delete_disk_image", project_id=project.id, node_id=node.id, disk_name="hda_disk.qcow2")
)
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_qemu_disk_image_delete_wrong_node_type(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node
) -> None:
response = await client.delete(
app.url_path_for("delete_disk_image", project_id=project.id, node_id=node.id, disk_name="hda_disk.qcow2")
)
assert response.status_code == status.HTTP_400_BAD_REQUEST
async def test_get_file(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node
) -> None:
# Mock the streaming response
@@ -632,56 +461,52 @@ class TestNodeRoutes:
compute.http_query = AsyncioMagicMock(return_value=mock_response)
response = await client.get(app.url_path_for("get_file", project_id=project.id, node_id=node.id, file_path="hello"))
response = await client.get(
app.url_path_for("get_file", project_id=project.id, node_id=node.id, file_path="hello")
)
assert response.status_code == status.HTTP_200_OK
assert response.content == b'world'
assert response.content == b"world"
compute.http_query.assert_called_with(
"GET",
"/projects/{project_id}/files/project-files/vpcs/{node_id}/hello".format(
project_id=project.id,
node_id=node.id),
project_id=project.id, node_id=node.id
),
timeout=None,
stream=True)
stream=True,
)
response = await client.get(app.url_path_for(
"get_file",
project_id=project.id,
node_id=node.id,
file_path="../hello"))
response = await client.get(
app.url_path_for("get_file", project_id=project.id, node_id=node.id, file_path="../hello")
)
assert response.status_code == status.HTTP_404_NOT_FOUND
async def test_post_file(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node
) -> None:
compute.http_query = AsyncioMagicMock()
response = await client.post(app.url_path_for(
"post_file",
project_id=project.id,
node_id=node.id,
file_path="hello"), content=b"hello")
response = await client.post(
app.url_path_for("post_file", project_id=project.id, node_id=node.id, file_path="hello"), content=b"hello"
)
assert response.status_code == status.HTTP_201_CREATED
# Verify http_query was called with stream parameter
compute.http_query.assert_called_once()
call_args = compute.http_query.call_args
assert call_args[0][0] == "POST"
assert call_args[0][1] == "/projects/{project_id}/files/project-files/vpcs/{node_id}/hello".format(project_id=project.id, node_id=node.id)
assert call_args[0][1] == "/projects/{project_id}/files/project-files/vpcs/{node_id}/hello".format(
project_id=project.id, node_id=node.id
)
assert call_args[1]["timeout"] is None
# data should be an async generator from request.stream()
assert hasattr(call_args[1]["data"], "__aiter__")
response = await client.get("/projects/{project_id}/nodes/{node_id}/files/../hello".format(project_id=project.id, node_id=node.id))
response = await client.get(
"/projects/{project_id}/nodes/{node_id}/files/../hello".format(project_id=project.id, node_id=node.id)
)
assert response.status_code == status.HTTP_404_NOT_FOUND
# @pytest.mark.asyncio
# async def test_get_and_post_with_nested_paths_normalization(controller_api, project, node, compute):
#
@@ -701,8 +526,6 @@ class TestNodeRoutes:
# compute.http_query.assert_called_with("POST", "/projects/{project_id}/files/project-files/vpcs/{node_id}/hello/nested".format(project_id=project.id, node_id=node.id), data=b'hello', timeout=None, raw=True)
class FakeComputeConsoleWebSocket:
"""
Stand-in for the aiohttp ClientWebSocketResponse returned when connecting
@@ -823,21 +646,21 @@ class TestNodeConsoleWebSocketRoutes:
monkeypatch.setattr(
HTTPClient,
"get_client",
classmethod(lambda cls: SimpleNamespace(ws_connect=lambda *args, **kwargs: compute_ws))
classmethod(lambda cls: SimpleNamespace(ws_connect=lambda *args, **kwargs: compute_ws)),
)
return compute_ws
async def test_console_forwards_compute_output_to_client(
self,
compute_credentials,
node: Node,
monkeypatch
self, compute_credentials, node: Node, monkeypatch
) -> None:
compute_ws = self._forward_compute_ws(monkeypatch, [
aiohttp.WSMessage(aiohttp.WSMsgType.TEXT, "device output", None),
aiohttp.WSMessage(aiohttp.WSMsgType.BINARY, b"\x00\x01", None),
])
compute_ws = self._forward_compute_ws(
monkeypatch,
[
aiohttp.WSMessage(aiohttp.WSMsgType.TEXT, "device output", None),
aiohttp.WSMessage(aiohttp.WSMsgType.BINARY, b"\x00\x01", None),
],
)
websocket = FakeClientWebSocket()
await ws_console(websocket, current_user=MagicMock(), node=node)
@@ -846,19 +669,18 @@ class TestNodeConsoleWebSocketRoutes:
assert compute_ws.closed
async def test_console_client_disconnect_mid_stream(
self,
compute_credentials,
node: Node,
monkeypatch,
caplog
self, compute_credentials, node: Node, monkeypatch, caplog
) -> None:
# regression test: the client disconnects while the compute is still
# streaming console output, send must not leak WebSocketDisconnect
compute_ws = self._forward_compute_ws(monkeypatch, [
aiohttp.WSMessage(aiohttp.WSMsgType.TEXT, "line 1", None),
aiohttp.WSMessage(aiohttp.WSMsgType.TEXT, "line 2", None),
aiohttp.WSMessage(aiohttp.WSMsgType.TEXT, "line 3", None),
])
compute_ws = self._forward_compute_ws(
monkeypatch,
[
aiohttp.WSMessage(aiohttp.WSMsgType.TEXT, "line 1", None),
aiohttp.WSMessage(aiohttp.WSMsgType.TEXT, "line 2", None),
aiohttp.WSMessage(aiohttp.WSMsgType.TEXT, "line 3", None),
],
)
websocket = FakeClientWebSocket(fail_after=1)
with caplog.at_level(logging.INFO):
@@ -868,17 +690,15 @@ class TestNodeConsoleWebSocketRoutes:
assert compute_ws.closed
assert any("has disconnected from controller console WebSocket" in record.message for record in caplog.records)
async def test_vnc_console_client_disconnect_mid_stream(
self,
compute_credentials,
node: Node,
monkeypatch
) -> None:
async def test_vnc_console_client_disconnect_mid_stream(self, compute_credentials, node: Node, monkeypatch) -> None:
# regression test: same as above for the VNC console forwarding loop
compute_ws = self._forward_compute_ws(monkeypatch, [
aiohttp.WSMessage(aiohttp.WSMsgType.BINARY, b"\x01\x02", None),
aiohttp.WSMessage(aiohttp.WSMsgType.BINARY, b"\x03\x04", None),
])
compute_ws = self._forward_compute_ws(
monkeypatch,
[
aiohttp.WSMessage(aiohttp.WSMsgType.BINARY, b"\x01\x02", None),
aiohttp.WSMessage(aiohttp.WSMsgType.BINARY, b"\x03\x04", None),
],
)
websocket = FakeClientWebSocket(fail_after=0)
await vnc_console(websocket, current_user=MagicMock(), node=node)
@@ -978,10 +798,9 @@ class TestConsoleWebSocketRoutes:
stack = contextlib.ExitStack()
http_client = MagicMock()
http_client.ws_connect = MagicMock(return_value=_FakeComputeWebSocketContext(fake_ws))
stack.enter_context(patch(
"gns3server.api.routes.controller.nodes.HTTPClient.get_client",
return_value=http_client
))
stack.enter_context(
patch("gns3server.api.routes.controller.nodes.HTTPClient.get_client", return_value=http_client)
)
stack.enter_context(patch.object(ASGIWebSocketAsyncNetworkStream, "send", _conforming_stream_send))
return stack
@@ -1000,13 +819,7 @@ class TestConsoleWebSocketRoutes:
await asyncio.sleep(0.05)
async def test_console_ws_client_disconnect(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node,
caplog
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node, caplog
) -> None:
"""
A client disconnecting while the compute keeps streaming console output
@@ -1033,27 +846,23 @@ class TestConsoleWebSocketRoutes:
assert fake_ws.stream_cancelled, "the compute -> client forwarding task should have been cancelled"
assert fake_ws.closed, "the compute console WebSocket should have been closed"
assert any(
"has disconnected from controller console WebSocket" in record.getMessage()
for record in caplog.records
"has disconnected from controller console WebSocket" in record.getMessage() for record in caplog.records
)
async def test_console_ws_compute_closes_session(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node
) -> None:
"""
When the compute closes the console WebSocket the client should receive
the frames sent before the close, then the close itself.
"""
fake_ws = _FakeComputeWebSocket(messages=[
aiohttp.WSMessage(aiohttp.WSMsgType.BINARY, b"output", ""),
aiohttp.WSMessage(aiohttp.WSMsgType.TEXT, "done", ""),
])
fake_ws = _FakeComputeWebSocket(
messages=[
aiohttp.WSMessage(aiohttp.WSMsgType.BINARY, b"output", ""),
aiohttp.WSMessage(aiohttp.WSMsgType.TEXT, "done", ""),
]
)
with self._patches(fake_ws):
async with AsyncClient(base_url="http://test-api", transport=ASGIWebSocketTransport(app=app)) as ws_client:
async with aconnect_ws(
@@ -1069,13 +878,7 @@ class TestConsoleWebSocketRoutes:
assert fake_ws.closed
async def test_vnc_console_ws_client_disconnect(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node,
caplog
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node, caplog
) -> None:
"""
Same as the console test, for the VNC endpoint (binary frames only).
@@ -1097,6 +900,5 @@ class TestConsoleWebSocketRoutes:
assert fake_ws.stream_cancelled, "the compute -> client forwarding task should have been cancelled"
assert fake_ws.closed, "the compute VNC console WebSocket should have been closed"
assert any(
"has disconnected from controller VNC console WebSocket" in record.getMessage()
for record in caplog.records
"has disconnected from controller VNC console WebSocket" in record.getMessage() for record in caplog.records
)
+13 -42
View File
@@ -32,7 +32,6 @@ pytestmark = pytest.mark.asyncio
class TestPoolRoutes:
async def test_resource_pool(self, app: FastAPI, client: AsyncClient) -> None:
new_group = {"name": "pool1"}
@@ -60,28 +59,23 @@ class TestPoolRoutes:
update_pool = {"name": "pool42"}
response = await client.put(
app.url_path_for("update_resource_pool", resource_pool_id=pool_in_db.resource_pool_id),
json=update_pool
app.url_path_for("update_resource_pool", resource_pool_id=pool_in_db.resource_pool_id), json=update_pool
)
assert response.status_code == status.HTTP_200_OK
updated_pool_in_db = await pools_repo.get_resource_pool(pool_in_db.resource_pool_id)
assert updated_pool_in_db.name == "pool42"
async def test_resource_group(
self,
app: FastAPI,
client: AsyncClient,
db_session: AsyncSession
) -> None:
async def test_resource_group(self, app: FastAPI, client: AsyncClient, db_session: AsyncSession) -> None:
pools_repo = ResourcePoolsRepository(db_session)
pool_in_db = await pools_repo.get_resource_pool_by_name("pool42")
response = await client.delete(app.url_path_for("delete_resource_pool", resource_pool_id=pool_in_db.resource_pool_id))
response = await client.delete(
app.url_path_for("delete_resource_pool", resource_pool_id=pool_in_db.resource_pool_id)
)
assert response.status_code == status.HTTP_204_NO_CONTENT
class TestResourcesPoolRoutes:
@pytest_asyncio.fixture
async def project(self, app: FastAPI, client: AsyncClient, controller: Controller) -> Project:
project_id = str(uuid.uuid4())
@@ -90,11 +84,7 @@ class TestResourcesPoolRoutes:
return controller.get_project(project_id)
async def test_add_resource_to_pool(
self,
app: FastAPI,
client: AsyncClient,
db_session: AsyncSession,
project: Project
self, app: FastAPI, client: AsyncClient, db_session: AsyncSession, project: Project
) -> None:
pools_repo = ResourcePoolsRepository(db_session)
@@ -104,9 +94,7 @@ class TestResourcesPoolRoutes:
pool_in_db = await pools_repo.create_resource_pool(new_resource_pool)
response = await client.put(
app.url_path_for(
"add_resource_to_pool",
resource_pool_id=pool_in_db.resource_pool_id,
resource_id=str(project.id)
"add_resource_to_pool", resource_pool_id=pool_in_db.resource_pool_id, resource_id=str(project.id)
)
)
assert response.status_code == status.HTTP_204_NO_CONTENT
@@ -115,11 +103,7 @@ class TestResourcesPoolRoutes:
assert str(resources[0].resource_id) == project.id
async def test_add_to_resource_already_in_resource_pool(
self,
app: FastAPI,
client: AsyncClient,
db_session: AsyncSession,
project: Project
self, app: FastAPI, client: AsyncClient, db_session: AsyncSession, project: Project
) -> None:
pools_repo = ResourcePoolsRepository(db_session)
@@ -132,34 +116,23 @@ class TestResourcesPoolRoutes:
app.url_path_for(
"add_resource_to_pool",
resource_pool_id=pool_in_db.resource_pool_id,
resource_id=str(resource.resource_id)
resource_id=str(resource.resource_id),
)
)
assert response.status_code == status.HTTP_400_BAD_REQUEST
async def test_get_pool_resources(
self,
app: FastAPI,
client: AsyncClient,
db_session: AsyncSession
) -> None:
async def test_get_pool_resources(self, app: FastAPI, client: AsyncClient, db_session: AsyncSession) -> None:
pools_repo = ResourcePoolsRepository(db_session)
pool_in_db = await pools_repo.get_resource_pool_by_name("pool1")
response = await client.get(
app.url_path_for(
"get_pool_resources",
resource_pool_id=pool_in_db.resource_pool_id)
app.url_path_for("get_pool_resources", resource_pool_id=pool_in_db.resource_pool_id)
)
assert response.status_code == status.HTTP_200_OK
assert len(response.json()) == 2
async def test_remove_resource_from_pool(
self,
app: FastAPI,
client: AsyncClient,
db_session: AsyncSession,
project: Project
self, app: FastAPI, client: AsyncClient, db_session: AsyncSession, project: Project
) -> None:
pools_repo = ResourcePoolsRepository(db_session)
@@ -173,9 +146,7 @@ class TestResourcesPoolRoutes:
response = await client.delete(
app.url_path_for(
"remove_resource_from_pool",
resource_pool_id=pool_in_db.resource_pool_id,
resource_id=str(project.id)
"remove_resource_from_pool", resource_pool_id=pool_in_db.resource_pool_id, resource_id=str(project.id)
),
)
assert response.status_code == status.HTTP_204_NO_CONTENT
@@ -19,7 +19,6 @@ pytestmark = pytest.mark.asyncio
class TestPrivilegesRoute:
async def test_get_privileges(self, app: FastAPI, client: AsyncClient) -> None:
response = await client.get(app.url_path_for("get_privileges"))
assert response.status_code == status.HTTP_200_OK
+217 -291
View File
@@ -35,202 +35,157 @@ pytestmark = pytest.mark.asyncio
class TestControllerProjectRoutes:
@pytest_asyncio.fixture
async def project(self, app: FastAPI, client: AsyncClient, controller: Controller) -> Project:
project_id = str(uuid.uuid4())
params = {"name": "test", "project_id": project_id}
await client.post(app.url_path_for("create_project"), json=params)
return controller.get_project(project_id)
async def test_create_project_with_path(
self,
app: FastAPI,
client: AsyncClient,
controller: Controller,
config
self, app: FastAPI, client: AsyncClient, controller: Controller, config
) -> None:
params = {"name": "test", "path": str(config.settings.Server.projects_path), "project_id": "00010203-0405-0607-0809-0a0b0c0d0e0f"}
params = {
"name": "test",
"path": str(config.settings.Server.projects_path),
"project_id": "00010203-0405-0607-0809-0a0b0c0d0e0f",
}
response = await client.post(app.url_path_for("create_project"), json=params)
# The projects directory itself must never become a project directory
assert response.status_code == status.HTTP_403_FORBIDDEN
params = {"name": "test", "path": os.path.join(str(config.settings.Server.projects_path), "custom"), "project_id": "00010203-0405-0607-0809-0a0b0c0d0e0f"}
params = {
"name": "test",
"path": os.path.join(str(config.settings.Server.projects_path), "custom"),
"project_id": "00010203-0405-0607-0809-0a0b0c0d0e0f",
}
response = await client.post(app.url_path_for("create_project"), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "test"
assert response.json()["project_id"] == "00010203-0405-0607-0809-0a0b0c0d0e0f"
assert response.json()["status"] == "opened"
async def test_create_project_without_dir(
self,
app: FastAPI,
client: AsyncClient,
controller: Controller
) -> None:
async def test_create_project_without_dir(self, app: FastAPI, client: AsyncClient, controller: Controller) -> None:
params = {"name": "test", "project_id": "10010203-0405-0607-0809-0a0b0c0d0e0f"}
response = await client.post(app.url_path_for("create_project"), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["project_id"] == "10010203-0405-0607-0809-0a0b0c0d0e0f"
assert response.json()["name"] == "test"
async def test_create_project_with_uuid(
self,
app: FastAPI,
client: AsyncClient,
controller: Controller
) -> None:
async def test_create_project_with_uuid(self, app: FastAPI, client: AsyncClient, controller: Controller) -> None:
params = {"name": "test", "project_id": "30010203-0405-0607-0809-0a0b0c0d0e0f"}
response = await client.post(app.url_path_for("create_project"), json=params)
assert response.status_code == 201
assert response.json()["project_id"] == "30010203-0405-0607-0809-0a0b0c0d0e0f"
assert response.json()["name"] == "test"
async def test_create_project_with_variables(
self,
app: FastAPI,
client: AsyncClient,
controller: Controller
self, app: FastAPI, client: AsyncClient, controller: Controller
) -> None:
variables = [
{"name": "TEST1"},
{"name": "TEST2", "value": "value1"}
]
variables = [{"name": "TEST1"}, {"name": "TEST2", "value": "value1"}]
params = {"name": "test", "project_id": "30010203-0405-0607-0809-0a0b0c0d0e0f", "variables": variables}
response = await client.post(app.url_path_for("create_project"), json=params)
assert response.status_code == 201
assert response.json()["variables"] == [
{"name": "TEST1"},
{"name": "TEST2", "value": "value1"}
]
assert response.json()["variables"] == [{"name": "TEST1"}, {"name": "TEST2", "value": "value1"}]
async def test_create_project_with_supplier(
self,
app: FastAPI,
client: AsyncClient,
controller: Controller
self, app: FastAPI, client: AsyncClient, controller: Controller
) -> None:
supplier = {
'logo': 'logo.png',
'url': 'http://example.com/'
}
supplier = {"logo": "logo.png", "url": "http://example.com/"}
params = {"name": "test", "project_id": "30010203-0405-0607-0809-0a0b0c0d0e0f", "supplier": supplier}
response = await client.post(app.url_path_for("create_project"), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["supplier"] == supplier
async def test_update_project(self, app: FastAPI, client: AsyncClient, controller: Controller) -> None:
params = {"name": "test", "project_id": "10010203-0405-0607-0809-0a0b0c0d0e0f"}
response = await client.post(app.url_path_for("create_project"), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["project_id"] == "10010203-0405-0607-0809-0a0b0c0d0e0f"
assert response.json()["name"] == "test"
params = {"name": "test2"}
response = await client.put(app.url_path_for("update_project", project_id="10010203-0405-0607-0809-0a0b0c0d0e0f"),
json=params)
response = await client.put(
app.url_path_for("update_project", project_id="10010203-0405-0607-0809-0a0b0c0d0e0f"), json=params
)
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "test2"
async def test_update_project_with_variables(
self,
app: FastAPI,
client: AsyncClient,
controller: Controller
self, app: FastAPI, client: AsyncClient, controller: Controller
) -> None:
variables = [
{"name": "TEST1"},
{"name": "TEST2", "value": "value1"}
]
variables = [{"name": "TEST1"}, {"name": "TEST2", "value": "value1"}]
params = {"name": "test", "project_id": "10010203-0405-0607-0809-0a0b0c0d0e0f", "variables": variables}
response = await client.post(app.url_path_for("create_project"), json=params)
assert response.status_code == status.HTTP_201_CREATED
params = {"name": "test2"}
response = await client.put(app.url_path_for("update_project", project_id="10010203-0405-0607-0809-0a0b0c0d0e0f"),
json=params)
response = await client.put(
app.url_path_for("update_project", project_id="10010203-0405-0607-0809-0a0b0c0d0e0f"), json=params
)
assert response.status_code == status.HTTP_200_OK
assert response.json()["variables"] == variables
async def test_list_projects(self, app: FastAPI, client: AsyncClient, controller: Controller) -> None:
params = {"name": "test", "project_id": "00010203-0405-0607-0809-0a0b0c0d0e0f"}
await client.post(app.url_path_for("create_project"), json=params)
response = await client.get(app.url_path_for("get_projects"))
assert response.status_code == status.HTTP_200_OK
projects = response.json()
assert projects[0]["name"] == "test"
async def test_get_project(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
response = await client.get(app.url_path_for("get_project", project_id=project.id))
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "test"
async def test_delete_project(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
controller: Controller
self, app: FastAPI, client: AsyncClient, project: Project, controller: Controller
) -> None:
with asyncio_patch("gns3server.controller.project.Project.delete", return_value=True) as mock:
response = await client.delete(app.url_path_for("delete_project", project_id=project.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
assert mock.called
assert project not in controller.projects
async def test_delete_project_invalid_uuid(self, app: FastAPI, client: AsyncClient) -> None:
response = await client.delete(app.url_path_for("delete_project", project_id=str(uuid.uuid4())))
assert response.status_code == status.HTTP_404_NOT_FOUND
async def test_close_project(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
with asyncio_patch("gns3server.controller.project.Project.close", return_value=True) as mock:
response = await client.post(app.url_path_for("close_project", project_id=project.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
assert mock.called
async def test_open_project(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
with asyncio_patch("gns3server.controller.project.Project.open", return_value=True) as mock:
response = await client.post(app.url_path_for("open_project", project_id=project.id))
assert response.status_code == status.HTTP_201_CREATED
assert mock.called
async def test_load_project(self, app: FastAPI, client: AsyncClient, project: Project, config) -> None:
with asyncio_patch("gns3server.controller.Controller.load_project", return_value=project) as mock:
response = await client.post(app.url_path_for("load_project"), json={"path": "/tmp/test.gns3"})
assert response.status_code == status.HTTP_201_CREATED
mock.assert_called_with("/tmp/test.gns3")
assert response.json()["project_id"] == project.id
# @pytest.mark.asyncio
# async def test_notification(controller_api, http_client, project, controller):
#
@@ -250,8 +205,7 @@ class TestControllerProjectRoutes:
#
# response = await controller_api.get("/projects/{project_id}/notifications".format(project_id=uuid.uuid4()))
# assert response.status_code == 404
# @pytest.mark.asyncio
# async def test_notification_ws(controller_api, http_client, controller, project):
#
@@ -269,179 +223,155 @@ class TestControllerProjectRoutes:
# await ws.close()
#
# assert project.status_code == "opened"
async def test_export_with_images(
self,
app: FastAPI,
client: AsyncClient,
tmpdir,
project: Project
) -> None:
async def test_export_with_images(self, app: FastAPI, client: AsyncClient, tmpdir, project: Project) -> None:
project.dump = MagicMock()
os.makedirs(project.path, exist_ok=True)
with open(os.path.join(project.path, 'a'), 'w+') as f:
f.write('hello')
with open(os.path.join(project.path, "a"), "w+") as f:
f.write("hello")
os.makedirs(str(tmpdir / "IOS"))
with open(str(tmpdir / "IOS" / "test.image"), "w+") as f:
f.write("AAA")
topology = {
"topology": {
"nodes": [
{
"properties": {
"image": "test.image"
},
"node_type": "dynamips"
}
]
}
}
with open(os.path.join(project.path, "test.gns3"), 'w+') as f:
topology = {"topology": {"nodes": [{"properties": {"image": "test.image"}, "node_type": "dynamips"}]}}
with open(os.path.join(project.path, "test.gns3"), "w+") as f:
json.dump(topology, f)
with patch("gns3server.compute.Dynamips.get_images_directory", return_value=str(tmpdir / "IOS")):
response = await client.get(app.url_path_for("export_project", project_id=project.id),
params={"include_images": "yes"})
response = await client.get(
app.url_path_for("export_project", project_id=project.id), params={"include_images": "yes"}
)
assert response.status_code == status.HTTP_200_OK
assert response.headers['CONTENT-TYPE'] == 'application/gns3project'
assert response.headers['CONTENT-DISPOSITION'] == 'attachment; filename="{name}.gns3project"; filename*=UTF-8\'\'{name}.gns3project'.format(name=project.name)
with open(str(tmpdir / 'project.zip'), 'wb+') as f:
assert response.headers["CONTENT-TYPE"] == "application/gns3project"
assert response.headers[
"CONTENT-DISPOSITION"
] == "attachment; filename=\"{name}.gns3project\"; filename*=UTF-8''{name}.gns3project".format(
name=project.name
)
with open(str(tmpdir / "project.zip"), "wb+") as f:
f.write(response.content)
with zipfile_zstd.ZipFile(str(tmpdir / 'project.zip')) as myzip:
with zipfile_zstd.ZipFile(str(tmpdir / "project.zip")) as myzip:
with myzip.open("a") as myfile:
content = myfile.read()
assert content == b"hello"
myzip.getinfo("images/IOS/test.image")
async def test_export_without_images(self, app: FastAPI, client: AsyncClient, tmpdir, project: Project) -> None:
project.dump = MagicMock()
os.makedirs(project.path, exist_ok=True)
with open(os.path.join(project.path, 'a'), 'w+') as f:
f.write('hello')
with open(os.path.join(project.path, "a"), "w+") as f:
f.write("hello")
os.makedirs(str(tmpdir / "IOS"))
with open(str(tmpdir / "IOS" / "test.image"), "w+") as f:
f.write("AAA")
topology = {
"topology": {
"nodes": [
{
"properties": {
"image": "test.image"
},
"node_type": "dynamips"
}
]
}
}
with open(os.path.join(project.path, "test.gns3"), 'w+') as f:
topology = {"topology": {"nodes": [{"properties": {"image": "test.image"}, "node_type": "dynamips"}]}}
with open(os.path.join(project.path, "test.gns3"), "w+") as f:
json.dump(topology, f)
with patch("gns3server.compute.Dynamips.get_images_directory", return_value=str(tmpdir / "IOS"),):
response = await client.get(app.url_path_for("export_project", project_id=project.id),
params={"include_images": "0"})
with patch(
"gns3server.compute.Dynamips.get_images_directory",
return_value=str(tmpdir / "IOS"),
):
response = await client.get(
app.url_path_for("export_project", project_id=project.id), params={"include_images": "0"}
)
assert response.status_code == status.HTTP_200_OK
assert response.headers['CONTENT-TYPE'] == 'application/gns3project'
assert response.headers['CONTENT-DISPOSITION'] == 'attachment; filename="{name}.gns3project"; filename*=UTF-8\'\'{name}.gns3project'.format(name=project.name)
with open(str(tmpdir / 'project.zip'), 'wb+') as f:
assert response.headers["CONTENT-TYPE"] == "application/gns3project"
assert response.headers[
"CONTENT-DISPOSITION"
] == "attachment; filename=\"{name}.gns3project\"; filename*=UTF-8''{name}.gns3project".format(
name=project.name
)
with open(str(tmpdir / "project.zip"), "wb+") as f:
f.write(response.content)
with zipfile_zstd.ZipFile(str(tmpdir / 'project.zip')) as myzip:
with zipfile_zstd.ZipFile(str(tmpdir / "project.zip")) as myzip:
with myzip.open("a") as myfile:
content = myfile.read()
assert content == b"hello"
# Image should not exported
with pytest.raises(KeyError):
myzip.getinfo("images/IOS/test.image")
@pytest.mark.parametrize(
"compression, compression_level, status_code",
(
("none", None, status.HTTP_200_OK),
("none", 4, status.HTTP_400_BAD_REQUEST),
("zip", None, status.HTTP_200_OK),
("zip", 1, status.HTTP_200_OK),
("zip", 12, status.HTTP_400_BAD_REQUEST),
("bzip2", None, status.HTTP_200_OK),
("bzip2", 1, status.HTTP_200_OK),
("bzip2", 13, status.HTTP_400_BAD_REQUEST),
("lzma", None, status.HTTP_200_OK),
("lzma", 1, status.HTTP_400_BAD_REQUEST),
("zstd", None, status.HTTP_200_OK),
("zstd", 12, status.HTTP_200_OK),
("zstd", 23, status.HTTP_400_BAD_REQUEST),
)
("none", None, status.HTTP_200_OK),
("none", 4, status.HTTP_400_BAD_REQUEST),
("zip", None, status.HTTP_200_OK),
("zip", 1, status.HTTP_200_OK),
("zip", 12, status.HTTP_400_BAD_REQUEST),
("bzip2", None, status.HTTP_200_OK),
("bzip2", 1, status.HTTP_200_OK),
("bzip2", 13, status.HTTP_400_BAD_REQUEST),
("lzma", None, status.HTTP_200_OK),
("lzma", 1, status.HTTP_400_BAD_REQUEST),
("zstd", None, status.HTTP_200_OK),
("zstd", 12, status.HTTP_200_OK),
("zstd", 23, status.HTTP_400_BAD_REQUEST),
),
)
async def test_export_compression(
self,
app: FastAPI,
client: AsyncClient,
tmpdir,
project: Project,
compression: str,
compression_level: int,
status_code: int
self,
app: FastAPI,
client: AsyncClient,
tmpdir,
project: Project,
compression: str,
compression_level: int,
status_code: int,
) -> None:
project.dump = MagicMock()
os.makedirs(project.path, exist_ok=True)
topology = {
"topology": {
"nodes": [
{
"node_type": "qemu"
}
]
}
}
with open(os.path.join(project.path, "test.gns3"), 'w+') as f:
topology = {"topology": {"nodes": [{"node_type": "qemu"}]}}
with open(os.path.join(project.path, "test.gns3"), "w+") as f:
json.dump(topology, f)
params = {"compression": compression}
if compression_level:
params["compression_level"] = compression_level
response = await client.get(app.url_path_for("export_project", project_id=project.id), params=params)
assert response.status_code == status_code
if response.status_code == status.HTTP_200_OK:
assert response.headers['CONTENT-TYPE'] == 'application/gns3project'
assert response.headers['CONTENT-DISPOSITION'] == 'attachment; filename="{name}.gns3project"; filename*=UTF-8\'\'{name}.gns3project'.format(name=project.name)
with open(str(tmpdir / 'project.zip'), 'wb+') as f:
assert response.headers["CONTENT-TYPE"] == "application/gns3project"
assert response.headers[
"CONTENT-DISPOSITION"
] == "attachment; filename=\"{name}.gns3project\"; filename*=UTF-8''{name}.gns3project".format(
name=project.name
)
with open(str(tmpdir / "project.zip"), "wb+") as f:
f.write(response.content)
with zipfile_zstd.ZipFile(str(tmpdir / 'project.zip')) as myzip:
with zipfile_zstd.ZipFile(str(tmpdir / "project.zip")) as myzip:
with myzip.open("project.gns3") as myfile:
myfile.read()
async def test_get_file(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
os.makedirs(project.path, exist_ok=True)
with open(os.path.join(project.path, 'hello'), 'w+') as f:
f.write('world')
with open(os.path.join(project.path, "hello"), "w+") as f:
f.write("world")
response = await client.get(app.url_path_for("get_file", project_id=project.id, file_path="hello"))
assert response.status_code == status.HTTP_200_OK
assert response.content == b"world"
response = await client.get(app.url_path_for("get_file", project_id=project.id, file_path="false"))
assert response.status_code == status.HTTP_404_NOT_FOUND
response = await client.get(app.url_path_for("get_file", project_id=project.id, file_path="../hello"))
assert response.status_code == status.HTTP_404_NOT_FOUND
async def test_get_project_gns3_file(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
response = await client.get(app.url_path_for("get_project_gns3_file", project_id=project.id))
@@ -451,7 +381,6 @@ class TestControllerProjectRoutes:
assert topology["name"] == "test"
assert "topology" in topology
async def test_get_project_gns3_file_raw_content(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
# the endpoint must serve the raw file content, without the project being opened
@@ -470,145 +399,142 @@ class TestControllerProjectRoutes:
assert response.status_code == status.HTTP_200_OK
assert response.json() == topology
async def test_get_project_gns3_file_project_not_found(self, app: FastAPI, client: AsyncClient) -> None:
response = await client.get(app.url_path_for("get_project_gns3_file", project_id=str(uuid.uuid4())))
assert response.status_code == status.HTTP_404_NOT_FOUND
async def test_get_file_forbidden_location(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
file_path = "foo/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd"
response = await client.get(app.url_path_for("get_file", project_id=project.id, file_path=file_path))
assert response.status_code == status.HTTP_403_FORBIDDEN
async def test_write_file(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
response = await client.post(app.url_path_for("write_file", project_id=project.id, file_path="hello"),
content=b"world")
response = await client.post(
app.url_path_for("write_file", project_id=project.id, file_path="hello"), content=b"world"
)
assert response.status_code == status.HTTP_204_NO_CONTENT
with open(os.path.join(project.path, "hello")) as f:
assert f.read() == "world"
response = await client.post(app.url_path_for("write_file", project_id=project.id, file_path="../hello"))
assert response.status_code == status.HTTP_404_NOT_FOUND
async def test_write_file_forbidden_location(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
file_path = "%2e%2e/hello"
response = await client.post(app.url_path_for("write_file", project_id=project.id, file_path=file_path),
content=b"world")
response = await client.post(
app.url_path_for("write_file", project_id=project.id, file_path=file_path), content=b"world"
)
assert response.status_code == status.HTTP_403_FORBIDDEN
async def test_write_and_get_file_with_leading_slashes_in_filename(
self,
app: FastAPI,
client: AsyncClient,
project: Project) -> None:
response = await client.post(app.url_path_for("write_file", project_id=project.id, file_path="//hello"),
content=b"world")
self, app: FastAPI, client: AsyncClient, project: Project
) -> None:
response = await client.post(
app.url_path_for("write_file", project_id=project.id, file_path="//hello"), content=b"world"
)
assert response.status_code == status.HTTP_403_FORBIDDEN
response = await client.get(app.url_path_for("get_file", project_id=project.id, file_path="//hello"))
assert response.status_code == status.HTTP_403_FORBIDDEN
async def test_import(self, app: FastAPI, client: AsyncClient, tmpdir, controller: Controller) -> None:
with zipfile_zstd.ZipFile(str(tmpdir / "test.zip"), 'w') as myzip:
myzip.writestr("project.gns3", b'{"project_id": "c6992992-ac72-47dc-833b-54aa334bcd05", "version": "2.0.0", "name": "test"}')
with zipfile_zstd.ZipFile(str(tmpdir / "test.zip"), "w") as myzip:
myzip.writestr(
"project.gns3",
b'{"project_id": "c6992992-ac72-47dc-833b-54aa334bcd05", "version": "2.0.0", "name": "test"}',
)
myzip.writestr("demo", b"hello")
project_id = str(uuid.uuid4())
with open(str(tmpdir / "test.zip"), "rb") as f:
response = await client.post(app.url_path_for("import_project", project_id=project_id), content=f.read())
assert response.status_code == status.HTTP_201_CREATED
project = controller.get_project(project_id)
with open(os.path.join(project.path, "demo")) as f:
content = f.read()
assert content == "hello"
async def test_import_with_project_name(
self,
app: FastAPI,
client: AsyncClient,
tmpdir,
controller: Controller
self, app: FastAPI, client: AsyncClient, tmpdir, controller: Controller
) -> None:
with zipfile_zstd.ZipFile(str(tmpdir / "test.zip"), 'w') as myzip:
myzip.writestr("project.gns3", b'{"project_id": "c6992992-ac72-47dc-833b-54aa334bcd05", "version": "2.0.0", "name": "test"}')
with zipfile_zstd.ZipFile(str(tmpdir / "test.zip"), "w") as myzip:
myzip.writestr(
"project.gns3",
b'{"project_id": "c6992992-ac72-47dc-833b-54aa334bcd05", "version": "2.0.0", "name": "test"}',
)
myzip.writestr("demo", b"hello")
project_id = str(uuid.uuid4())
with open(str(tmpdir / "test.zip"), "rb") as f:
response = await client.post(
app.url_path_for("import_project", project_id=project_id),
content=f.read(),
params={"name": "my-imported-project-name"}
params={"name": "my-imported-project-name"},
)
assert response.status_code == status.HTTP_201_CREATED
project = controller.get_project(project_id)
assert project.name == "my-imported-project-name"
async def test_duplicate(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
response = await client.post(app.url_path_for("duplicate_project", project_id=project.id), json={"name": "hello"})
response = await client.post(
app.url_path_for("duplicate_project", project_id=project.id), json={"name": "hello"}
)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "hello"
async def test_lock_unlock(self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute) -> None:
# add a drawing and node to the project
params = {
"svg": '<svg height="210" width="500"><line x1="0" y1="0" x2="200" y2="200" style="stroke:rgb(255,0,0);stroke-width:2" /></svg>',
"x": 10,
"y": 20,
"z": 0
"z": 0,
}
response = await client.post(app.url_path_for("create_drawing", project_id=project.id), json=params)
assert response.status_code == status.HTTP_201_CREATED
response = MagicMock()
response.json = {"console": 2048}
compute.post = AsyncioMagicMock(return_value=response)
response = await client.post(app.url_path_for("create_node", project_id=project.id), json={
"name": "test",
"node_type": "vpcs",
"compute_id": "example.com",
"properties": {
"startup_script": "echo test"
}
})
response = await client.post(
app.url_path_for("create_node", project_id=project.id),
json={
"name": "test",
"node_type": "vpcs",
"compute_id": "example.com",
"properties": {"startup_script": "echo test"},
},
)
assert response.status_code == status.HTTP_201_CREATED
response = await client.post(app.url_path_for("lock_project", project_id=project.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
for drawing in project.drawings.values():
assert drawing.locked is True
for node in project.nodes.values():
assert node.locked is True
response = await client.get(app.url_path_for("locked_project", project_id=project.id))
assert response.status_code == status.HTTP_200_OK
assert response.json() is True
response = await client.post(app.url_path_for("unlock_project", project_id=project.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
for drawing in project.drawings.values():
assert drawing.locked is False
for node in project.nodes.values():
+11 -51
View File
@@ -27,7 +27,6 @@ pytestmark = pytest.mark.asyncio
class TestRolesRoutes:
async def test_create_role(self, app: FastAPI, client: AsyncClient) -> None:
new_role = {"name": "role1"}
@@ -54,36 +53,22 @@ class TestRolesRoutes:
role_in_db = await rbac_repo.get_role_by_name("role1")
update_role = {"name": "role42"}
response = await client.put(
app.url_path_for("update_role", role_id=role_in_db.role_id),
json=update_role
)
response = await client.put(app.url_path_for("update_role", role_id=role_in_db.role_id), json=update_role)
assert response.status_code == status.HTTP_200_OK
updated_role_in_db = await rbac_repo.get_role(role_in_db.role_id)
assert updated_role_in_db.name == "role42"
async def test_cannot_update_builtin_user_role(
self,
app: FastAPI,
client: AsyncClient,
db_session: AsyncSession
self, app: FastAPI, client: AsyncClient, db_session: AsyncSession
) -> None:
rbac_repo = RbacRepository(db_session)
role_in_db = await rbac_repo.get_role_by_name("User")
update_role = {"name": "Hackers"}
response = await client.put(
app.url_path_for("update_role", role_id=role_in_db.role_id),
json=update_role
)
response = await client.put(app.url_path_for("update_role", role_id=role_in_db.role_id), json=update_role)
assert response.status_code == status.HTTP_403_FORBIDDEN
async def test_delete_role(
self,
app: FastAPI,
client: AsyncClient,
db_session: AsyncSession
) -> None:
async def test_delete_role(self, app: FastAPI, client: AsyncClient, db_session: AsyncSession) -> None:
rbac_repo = RbacRepository(db_session)
role_in_db = await rbac_repo.get_role_by_name("role42")
@@ -91,10 +76,7 @@ class TestRolesRoutes:
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_cannot_delete_builtin_administrator_role(
self,
app: FastAPI,
client: AsyncClient,
db_session: AsyncSession
self, app: FastAPI, client: AsyncClient, db_session: AsyncSession
) -> None:
rbac_repo = RbacRepository(db_session)
@@ -104,13 +86,7 @@ class TestRolesRoutes:
class TestRolesPrivilegesRoutes:
async def test_add_privilege_to_role(
self,
app: FastAPI,
client: AsyncClient,
db_session: AsyncSession
) -> None:
async def test_add_privilege_to_role(self, app: FastAPI, client: AsyncClient, db_session: AsyncSession) -> None:
rbac_repo = RbacRepository(db_session)
role_in_db = await rbac_repo.get_role_by_name("User")
@@ -118,38 +94,24 @@ class TestRolesPrivilegesRoutes:
response = await client.put(
app.url_path_for(
"add_privilege_to_role",
role_id=role_in_db.role_id,
privilege_id=str(privilege.privilege_id)
"add_privilege_to_role", role_id=role_in_db.role_id, privilege_id=str(privilege.privilege_id)
)
)
assert response.status_code == status.HTTP_204_NO_CONTENT
privileges = await rbac_repo.get_role_privileges(role_in_db.role_id)
assert len(privileges) == 27 # 25 default privileges + 2 LLMConfig privileges
async def test_get_role_privileges(
self,
app: FastAPI,
client: AsyncClient,
db_session: AsyncSession
) -> None:
async def test_get_role_privileges(self, app: FastAPI, client: AsyncClient, db_session: AsyncSession) -> None:
rbac_repo = RbacRepository(db_session)
role_in_db = await rbac_repo.get_role_by_name("User")
response = await client.get(
app.url_path_for(
"get_role_privileges",
role_id=role_in_db.role_id)
)
response = await client.get(app.url_path_for("get_role_privileges", role_id=role_in_db.role_id))
assert response.status_code == status.HTTP_200_OK
assert len(response.json()) == 27 # 25 default privileges + 2 LLMConfig privileges
async def test_remove_privilege_from_role(
self,
app: FastAPI,
client: AsyncClient,
db_session: AsyncSession
self, app: FastAPI, client: AsyncClient, db_session: AsyncSession
) -> None:
rbac_repo = RbacRepository(db_session)
@@ -158,9 +120,7 @@ class TestRolesPrivilegesRoutes:
response = await client.delete(
app.url_path_for(
"remove_privilege_from_role",
role_id=role_in_db.role_id,
privilege_id=str(privilege.privilege_id)
"remove_privilege_from_role", role_id=role_in_db.role_id, privilege_id=str(privilege.privilege_id)
),
)
assert response.status_code == status.HTTP_204_NO_CONTENT
+53 -35
View File
@@ -46,15 +46,12 @@ def stable_jwt_secret(config):
class TestSettingsRoutes:
async def test_get_settings(self, app: FastAPI, client: AsyncClient) -> None:
response = await client.get(app.url_path_for("get_server_settings"))
assert response.status_code == status.HTTP_200_OK
body = response.json()
assert sorted(body.keys()) == [
"Controller", "Dynamips", "IOU", "Qemu", "Server", "VPCS", "WebWireshark"
]
assert sorted(body.keys()) == ["Controller", "Dynamips", "IOU", "Qemu", "Server", "VPCS", "WebWireshark"]
# deprecated sections are not exposed
assert "VirtualBox" not in body
assert "VMware" not in body
@@ -71,7 +68,8 @@ class TestSettingsRoutes:
# underlying httpx client and its default headers depend on the fixture
# instantiation order
response = await client.get(
app.url_path_for("get_server_settings"), headers={"Authorization": "Bearer invalid_token"})
app.url_path_for("get_server_settings"), headers={"Authorization": "Bearer invalid_token"}
)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
async def test_get_settings_forbidden(self, app: FastAPI, client: AsyncClient, test_user) -> None:
@@ -79,23 +77,28 @@ class TestSettingsRoutes:
# the "User" role has no Server.Audit privilege
token = auth_service.create_access_token(test_user.username, secret_key=DEFAULT_JWT_SECRET_KEY)
response = await client.get(
app.url_path_for("get_server_settings"), headers={"Authorization": f"Bearer {token}"})
app.url_path_for("get_server_settings"), headers={"Authorization": f"Bearer {token}"}
)
assert response.status_code == status.HTTP_403_FORBIDDEN
async def test_put_settings(self, app: FastAPI, client: AsyncClient, config: Config,
stable_jwt_secret: str) -> None:
async def test_put_settings(
self, app: FastAPI, client: AsyncClient, config: Config, stable_jwt_secret: str
) -> None:
response = await client.put(app.url_path_for("update_server_settings"), json={
"Server": {
"port": 3081,
"allowed_interfaces": ["eth0"],
"default_symbol_theme": "Classic",
"report_errors": False,
response = await client.put(
app.url_path_for("update_server_settings"),
json={
"Server": {
"port": 3081,
"allowed_interfaces": ["eth0"],
"default_symbol_theme": "Classic",
"report_errors": False,
},
"Qemu": {
"enable_monitor": False,
},
},
"Qemu": {
"enable_monitor": False,
},
})
)
assert response.status_code == status.HTTP_200_OK
body = response.json()
assert body["Server"]["port"] == 3081
@@ -116,7 +119,8 @@ class TestSettingsRoutes:
assert parsed["Qemu"]["enable_monitor"] == "False"
async def test_put_settings_preserves_unknown_options(
self, app: FastAPI, client: AsyncClient, config: Config, stable_jwt_secret: str) -> None:
self, app: FastAPI, client: AsyncClient, config: Config, stable_jwt_secret: str
) -> None:
with open(config._main_config_file, "w") as f:
f.write("[Server]\nhost = 127.0.0.1\nfrobnicate = 42\n")
@@ -130,11 +134,13 @@ class TestSettingsRoutes:
assert parsed["Server"]["host"] == "127.0.0.1"
async def test_put_settings_secrets(
self, app: FastAPI, client: AsyncClient, config: Config, stable_jwt_secret: str) -> None:
self, app: FastAPI, client: AsyncClient, config: Config, stable_jwt_secret: str
) -> None:
# masked secret means "unchanged": nothing is written
response = await client.put(
app.url_path_for("update_server_settings"), json={"Server": {"compute_password": SECRET_MASK}})
app.url_path_for("update_server_settings"), json={"Server": {"compute_password": SECRET_MASK}}
)
assert response.status_code == status.HTTP_200_OK
parsed = configparser.ConfigParser()
parsed.read(config._main_config_file)
@@ -142,7 +148,8 @@ class TestSettingsRoutes:
# empty string means "unchanged" too
response = await client.put(
app.url_path_for("update_server_settings"), json={"Server": {"compute_password": ""}})
app.url_path_for("update_server_settings"), json={"Server": {"compute_password": ""}}
)
assert response.status_code == status.HTTP_200_OK
parsed = configparser.ConfigParser()
parsed.read(config._main_config_file)
@@ -150,7 +157,8 @@ class TestSettingsRoutes:
# an explicit new value is written in clear text (like a hand-edited file)
response = await client.put(
app.url_path_for("update_server_settings"), json={"Server": {"compute_password": "secret123"}})
app.url_path_for("update_server_settings"), json={"Server": {"compute_password": "secret123"}}
)
assert response.status_code == status.HTTP_200_OK
assert response.json()["Server"]["compute_password"] == SECRET_MASK # masked in the response
parsed = configparser.ConfigParser()
@@ -158,7 +166,8 @@ class TestSettingsRoutes:
assert parsed["Server"]["compute_password"] == "secret123"
async def test_put_settings_null_removes_option(
self, app: FastAPI, client: AsyncClient, config: Config, stable_jwt_secret: str) -> None:
self, app: FastAPI, client: AsyncClient, config: Config, stable_jwt_secret: str
) -> None:
with open(config._main_config_file, "w") as f:
f.write("[Server]\nhost = 127.0.0.1\n")
@@ -172,7 +181,8 @@ class TestSettingsRoutes:
assert response.json()["Server"]["host"] == "0.0.0.0" # default restored
async def test_put_settings_validation_failure(
self, app: FastAPI, client: AsyncClient, config: Config, stable_jwt_secret: str) -> None:
self, app: FastAPI, client: AsyncClient, config: Config, stable_jwt_secret: str
) -> None:
with open(config._main_config_file, "w") as f:
f.write("[Server]\nhost = 127.0.0.1\n")
@@ -180,22 +190,25 @@ class TestSettingsRoutes:
content_before = f.read()
# cross-field violation: console_end_port_range must be > console_start_port_range
response = await client.put(app.url_path_for("update_server_settings"), json={
"Server": {"console_start_port_range": 10000, "console_end_port_range": 5000}})
response = await client.put(
app.url_path_for("update_server_settings"),
json={"Server": {"console_start_port_range": 10000, "console_end_port_range": 5000}},
)
assert response.status_code == status.HTTP_400_BAD_REQUEST
with open(config._main_config_file) as f:
assert f.read() == content_before
async def test_put_settings_unknown_option_rejected(
self, app: FastAPI, client: AsyncClient, stable_jwt_secret: str) -> None:
self, app: FastAPI, client: AsyncClient, stable_jwt_secret: str
) -> None:
response = await client.put(
app.url_path_for("update_server_settings"), json={"Server": {"prot": "http"}})
response = await client.put(app.url_path_for("update_server_settings"), json={"Server": {"prot": "http"}})
assert response.status_code == status.HTTP_422_UNPROCESSABLE_CONTENT
async def test_put_settings_deprecated_sections_rejected(
self, app: FastAPI, client: AsyncClient, stable_jwt_secret: str) -> None:
self, app: FastAPI, client: AsyncClient, stable_jwt_secret: str
) -> None:
response = await client.put(app.url_path_for("update_server_settings"), json={"VirtualBox": {}})
assert response.status_code == status.HTTP_422_UNPROCESSABLE_CONTENT
@@ -203,14 +216,17 @@ class TestSettingsRoutes:
assert response.status_code == status.HTTP_422_UNPROCESSABLE_CONTENT
async def test_put_settings_jwt_secret_key_rejected(
self, app: FastAPI, client: AsyncClient, stable_jwt_secret: str) -> None:
self, app: FastAPI, client: AsyncClient, stable_jwt_secret: str
) -> None:
response = await client.put(
app.url_path_for("update_server_settings"), json={"Controller": {"jwt_secret_key": "nope"}})
app.url_path_for("update_server_settings"), json={"Controller": {"jwt_secret_key": "nope"}}
)
assert response.status_code == status.HTTP_422_UNPROCESSABLE_CONTENT
async def test_put_settings_conflict(
self, app: FastAPI, client: AsyncClient, config: Config, stable_jwt_secret: str, tmpdir) -> None:
self, app: FastAPI, client: AsyncClient, config: Config, stable_jwt_secret: str, tmpdir
) -> None:
override_path = str(tmpdir / "override.conf")
with open(override_path, "w") as f:
@@ -218,7 +234,9 @@ class TestSettingsRoutes:
# a later configuration file takes precedence over the main one
Config.instance()._files.append(override_path)
response = await client.put(app.url_path_for("update_server_settings"), json={"Server": {"host": "192.168.1.1"}})
response = await client.put(
app.url_path_for("update_server_settings"), json={"Server": {"host": "192.168.1.1"}}
)
assert response.status_code == status.HTTP_409_CONFLICT
async def test_put_settings_empty_body(self, app: FastAPI, client: AsyncClient, config: Config) -> None:
+26 -22
View File
@@ -31,48 +31,52 @@ pytestmark = pytest.mark.asyncio
class TestSnapshotRoutes:
@pytest_asyncio.fixture
async def project(self, app: FastAPI, client: AsyncClient, controller: Controller) -> Project:
u = str(uuid.uuid4())
params = {"name": "test", "project_id": u}
await client.post(app.url_path_for("create_project"), json=params)
controller_project = controller.get_project(u)
return controller_project
@pytest_asyncio.fixture
async def snapshot(self, project: Project):
controller_snapshot = await project.snapshot("test")
return controller_snapshot
async def test_list_snapshots(self, app: FastAPI, client: AsyncClient, project: Project, snapshot: Snapshot) -> None:
async def test_list_snapshots(
self, app: FastAPI, client: AsyncClient, project: Project, snapshot: Snapshot
) -> None:
assert snapshot.name == "test"
response = await client.get(app.url_path_for("get_snapshots", project_id=project.id))
assert response.status_code == status.HTTP_200_OK
assert len(response.json()) == 1
async def test_delete_snapshot(self, app: FastAPI, client: AsyncClient, project: Project, snapshot: Snapshot) -> None:
response = await client.delete(app.url_path_for("delete_snapshot", project_id=project.id, snapshot_id=snapshot.id))
async def test_delete_snapshot(
self, app: FastAPI, client: AsyncClient, project: Project, snapshot: Snapshot
) -> None:
response = await client.delete(
app.url_path_for("delete_snapshot", project_id=project.id, snapshot_id=snapshot.id)
)
assert response.status_code == status.HTTP_204_NO_CONTENT
assert not os.path.exists(snapshot.path)
async def test_restore_snapshot(self, app: FastAPI, client: AsyncClient, project: Project, snapshot: Snapshot) -> None:
response = await client.post(app.url_path_for("restore_snapshot", project_id=project.id, snapshot_id=snapshot.id))
async def test_restore_snapshot(
self, app: FastAPI, client: AsyncClient, project: Project, snapshot: Snapshot
) -> None:
response = await client.post(
app.url_path_for("restore_snapshot", project_id=project.id, snapshot_id=snapshot.id)
)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == project.name
async def test_create_snapshot(self, app: FastAPI, client: AsyncClient, project: Project) -> None:
response = await client.post(app.url_path_for("create_snapshot", project_id=project.id), json={"name": "snap1"})
assert response.status_code == status.HTTP_201_CREATED
assert len(os.listdir(os.path.join(project.path, "snapshots"))) == 2
+17 -20
View File
@@ -29,42 +29,39 @@ pytestmark = pytest.mark.asyncio
class TestSymbolRoutes:
async def test_symbols(self, app: FastAPI, client: AsyncClient) -> None:
response = await client.get(app.url_path_for("get_symbols"))
assert response.status_code == status.HTTP_200_OK
assert {
'symbol_id': ':/symbols/classic/firewall.svg',
'filename': 'firewall.svg',
'builtin': True,
'theme': 'Classic'
"symbol_id": ":/symbols/classic/firewall.svg",
"filename": "firewall.svg",
"builtin": True,
"theme": "Classic",
} in response.json()
async def test_get(self, app: FastAPI, client: AsyncClient, controller: Controller) -> None:
controller.symbols.theme = "Classic"
url = app.url_path_for("get_symbol", symbol_id=urllib.parse.quote(':/symbols/classic/firewall.svg'))
url = app.url_path_for("get_symbol", symbol_id=urllib.parse.quote(":/symbols/classic/firewall.svg"))
response = await client.get(url)
assert response.status_code == status.HTTP_200_OK
assert response.headers['CONTENT-TYPE'] == 'image/svg+xml'
assert response.headers['CONTENT-LENGTH'] == '9381'
assert '</svg>' in response.text
assert response.headers["CONTENT-TYPE"] == "image/svg+xml"
assert response.headers["CONTENT-LENGTH"] == "9381"
assert "</svg>" in response.text
# Reply with the default symbol
response = await client.get(app.url_path_for("get_symbol", symbol_id="404.png"))
assert response.status_code == status.HTTP_200_OK
async def test_upload(self, app: FastAPI, client: AsyncClient, symbols_dir: str) -> None:
response = await client.post(app.url_path_for("upload_symbol", symbol_id="test2"), content=b"TEST")
assert response.status_code == status.HTTP_204_NO_CONTENT
with open(os.path.join(symbols_dir, "test2")) as f:
assert f.read() == "TEST"
response = await client.get(app.url_path_for("get_symbol", symbol_id="test2"))
assert response.status_code == status.HTTP_200_OK
File diff suppressed because it is too large. Load diff
+142 -205
View File
@@ -35,7 +35,6 @@ pytestmark = pytest.mark.asyncio
class TestUserRoutes:
async def test_route_exist(self, app: FastAPI, client: AsyncClient) -> None:
new_user = {"username": "user1", "email": "user1@email.com", "password": "test_password"}
@@ -43,10 +42,7 @@ class TestUserRoutes:
assert response.status_code == status.HTTP_201_CREATED
async def test_users_can_register_successfully(
self,
app: FastAPI,
client: AsyncClient,
db_session: AsyncSession
self, app: FastAPI, client: AsyncClient, db_session: AsyncSession
) -> None:
user_repo = UsersRepository(db_session)
@@ -73,21 +69,21 @@ class TestUserRoutes:
@pytest.mark.parametrize(
"attr, value, status_code",
(
("email", "user2@email.com", status.HTTP_400_BAD_REQUEST),
("username", "user2", status.HTTP_400_BAD_REQUEST),
("email", "invalid_email@one@two.io", status.HTTP_422_UNPROCESSABLE_CONTENT),
("password", "short", status.HTTP_422_UNPROCESSABLE_CONTENT),
("username", "user2@#$%^<>", status.HTTP_422_UNPROCESSABLE_CONTENT),
("username", "ab", status.HTTP_422_UNPROCESSABLE_CONTENT),
)
("email", "user2@email.com", status.HTTP_400_BAD_REQUEST),
("username", "user2", status.HTTP_400_BAD_REQUEST),
("email", "invalid_email@one@two.io", status.HTTP_422_UNPROCESSABLE_CONTENT),
("password", "short", status.HTTP_422_UNPROCESSABLE_CONTENT),
("username", "user2@#$%^<>", status.HTTP_422_UNPROCESSABLE_CONTENT),
("username", "ab", status.HTTP_422_UNPROCESSABLE_CONTENT),
),
)
async def test_user_registration_fails_when_credentials_are_taken(
self,
app: FastAPI,
client: AsyncClient,
attr: str,
value: str,
status_code: int,
self,
app: FastAPI,
client: AsyncClient,
attr: str,
value: str,
status_code: int,
) -> None:
new_user = {"email": "not_taken@email.com", "username": "not_taken_username", "password": "test_password"}
@@ -98,26 +94,26 @@ class TestUserRoutes:
@pytest.mark.parametrize(
"attr, value, status_code",
(
("email", "user@email.com", status.HTTP_200_OK),
("email", "user@email.com", status.HTTP_400_BAD_REQUEST),
("username", "user2", status.HTTP_400_BAD_REQUEST),
("email", "invalid_email@one@two.io", status.HTTP_422_UNPROCESSABLE_CONTENT),
("password", "short", status.HTTP_422_UNPROCESSABLE_CONTENT),
("username", "user2@#$%^<>", status.HTTP_422_UNPROCESSABLE_CONTENT),
("username", "ab", status.HTTP_422_UNPROCESSABLE_CONTENT),
("full_name", "John Doe", status.HTTP_200_OK),
("password", "password123", status.HTTP_200_OK),
("is_active", True, status.HTTP_200_OK),
)
("email", "user@email.com", status.HTTP_200_OK),
("email", "user@email.com", status.HTTP_400_BAD_REQUEST),
("username", "user2", status.HTTP_400_BAD_REQUEST),
("email", "invalid_email@one@two.io", status.HTTP_422_UNPROCESSABLE_CONTENT),
("password", "short", status.HTTP_422_UNPROCESSABLE_CONTENT),
("username", "user2@#$%^<>", status.HTTP_422_UNPROCESSABLE_CONTENT),
("username", "ab", status.HTTP_422_UNPROCESSABLE_CONTENT),
("full_name", "John Doe", status.HTTP_200_OK),
("password", "password123", status.HTTP_200_OK),
("is_active", True, status.HTTP_200_OK),
),
)
async def test_update_user(
self,
app: FastAPI,
client: AsyncClient,
db_session: AsyncSession,
attr: str,
value: str,
status_code: int,
self,
app: FastAPI,
client: AsyncClient,
db_session: AsyncSession,
attr: str,
value: str,
status_code: int,
) -> None:
user_repo = UsersRepository(db_session)
@@ -128,10 +124,7 @@ class TestUserRoutes:
assert response.status_code == status_code
async def test_users_saved_password_is_hashed(
self,
app: FastAPI,
client: AsyncClient,
db_session: AsyncSession
self, app: FastAPI, client: AsyncClient, db_session: AsyncSession
) -> None:
user_repo = UsersRepository(db_session)
@@ -156,13 +149,8 @@ class TestUserRoutes:
class TestAuthTokens:
async def test_can_create_token_successfully(
self,
app: FastAPI,
client: AsyncClient,
test_user: User,
config: Config
self, app: FastAPI, client: AsyncClient, test_user: User, config: Config
) -> None:
jwt_secret = config.settings.Controller.jwt_secret_key
@@ -173,11 +161,7 @@ class TestAuthTokens:
assert username == test_user.username
async def test_decode_token_with_wrong_algorithm(
self,
app: FastAPI,
client: AsyncClient,
test_user: User,
config: Config
self, app: FastAPI, client: AsyncClient, test_user: User, config: Config
) -> None:
jwt_secret = config.settings.Controller.jwt_secret_key
@@ -185,12 +169,7 @@ class TestAuthTokens:
with pytest.raises(ValueError):
jwt.decode(token, jwt_secret, algorithms=["HS256"])
async def test_can_retrieve_username_from_token(
self,
app: FastAPI,
client: AsyncClient,
test_user: User
) -> None:
async def test_can_retrieve_username_from_token(self, app: FastAPI, client: AsyncClient, test_user: User) -> None:
token = auth_service.create_access_token(test_user.username)
username = auth_service.get_username_from_token(token)
@@ -199,19 +178,19 @@ class TestAuthTokens:
@pytest.mark.parametrize(
"wrong_secret, wrong_token",
(
("use correct secret", "asdf"), # use wrong token
("use correct secret", ""), # use wrong token
(OctKey.generate_key().as_dict()['k'], "use correct token"), # use wrong secret
("use correct secret", "asdf"), # use wrong token
("use correct secret", ""), # use wrong token
(OctKey.generate_key().as_dict()["k"], "use correct token"), # use wrong secret
),
)
async def test_error_when_token_or_secret_is_wrong(
self,
app: FastAPI,
client: AsyncClient,
test_user: User,
wrong_secret: str,
wrong_token: Optional[str],
config,
self,
app: FastAPI,
client: AsyncClient,
test_user: User,
wrong_secret: str,
wrong_token: Optional[str],
config,
) -> None:
token = auth_service.create_access_token(test_user.username)
@@ -224,13 +203,8 @@ class TestAuthTokens:
class TestUserLogin:
async def test_user_can_login_successfully_and_receives_valid_token(
self,
app: FastAPI,
unauthorized_client: AsyncClient,
test_user: User,
config: Config
self, app: FastAPI, unauthorized_client: AsyncClient, test_user: User, config: Config
) -> None:
jwt_secret = config.settings.Controller.jwt_secret_key
@@ -290,22 +264,18 @@ class TestUserLogin:
class TestUnauthorizedUser:
async def test_user_cannot_access_own_data_if_not_authenticated(
self, app: FastAPI,
unauthorized_client: AsyncClient,
test_user: User,
self,
app: FastAPI,
unauthorized_client: AsyncClient,
test_user: User,
) -> None:
response = await unauthorized_client.get(app.url_path_for("get_logged_in_user"))
assert response.status_code == status.HTTP_401_UNAUTHORIZED
async def test_user_can_authenticate_using_json(
self,
app: FastAPI,
unauthorized_client: AsyncClient,
test_user: User,
config: Config
self, app: FastAPI, unauthorized_client: AsyncClient, test_user: User, config: Config
) -> None:
credentials = {
@@ -321,13 +291,13 @@ class TestUnauthorizedUser:
response = await unauthorized_client.get(app.url_path_for("statistics"), params={"token": token})
assert response.status_code == status.HTTP_200_OK
class TestUserMe:
class TestUserMe:
async def test_authenticated_user_can_retrieve_own_data(
self,
app: FastAPI,
authorized_client: AsyncClient,
test_user: User,
self,
app: FastAPI,
authorized_client: AsyncClient,
test_user: User,
) -> None:
response = await authorized_client.get(app.url_path_for("get_logged_in_user"))
@@ -338,10 +308,10 @@ class TestUserMe:
assert user.user_id == test_user.user_id
async def test_authenticated_user_can_update_own_data(
self,
app: FastAPI,
authorized_client: AsyncClient,
test_user: User,
self,
app: FastAPI,
authorized_client: AsyncClient,
test_user: User,
) -> None:
response = await authorized_client.get(app.url_path_for("get_logged_in_user"))
@@ -355,37 +325,33 @@ class TestUserMe:
@pytest.mark.parametrize(
"attr, value, status_code",
(
("email", "user42@email.com", status.HTTP_200_OK),
("email", "user42@email.com", status.HTTP_400_BAD_REQUEST),
("full_name", "John Doe", status.HTTP_200_OK),
("password", "password123", status.HTTP_200_OK),
)
("email", "user42@email.com", status.HTTP_200_OK),
("email", "user42@email.com", status.HTTP_400_BAD_REQUEST),
("full_name", "John Doe", status.HTTP_200_OK),
("password", "password123", status.HTTP_200_OK),
),
)
async def test_authenticated_user_can_update_own_data(
self,
app: FastAPI,
authorized_client: AsyncClient,
attr: str,
value: str,
status_code: int,
self,
app: FastAPI,
authorized_client: AsyncClient,
attr: str,
value: str,
status_code: int,
) -> None:
update_user = {}
update_user[attr] = value
response = await authorized_client.put(
app.url_path_for("update_logged_in_user"),
json=update_user
)
response = await authorized_client.put(app.url_path_for("update_logged_in_user"), json=update_user)
assert response.status_code == status_code
class TestLogout:
async def test_logout_returns_no_content(
self,
app: FastAPI,
unauthorized_client: AsyncClient,
test_user: User,
self,
app: FastAPI,
unauthorized_client: AsyncClient,
test_user: User,
) -> None:
# login to get a fresh token that includes token_version
@@ -395,17 +361,16 @@ class TestLogout:
token = response.json()["access_token"]
response = await unauthorized_client.post(
app.url_path_for("logout"),
headers={"Authorization": f"Bearer {token}"}
app.url_path_for("logout"), headers={"Authorization": f"Bearer {token}"}
)
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_token_is_rejected_after_logout(
self,
app: FastAPI,
unauthorized_client: AsyncClient,
test_user: User,
db_session: AsyncSession,
self,
app: FastAPI,
unauthorized_client: AsyncClient,
test_user: User,
db_session: AsyncSession,
) -> None:
# login and get a token
@@ -415,24 +380,20 @@ class TestLogout:
token = response.json()["access_token"]
# logout — increments token_version
await unauthorized_client.post(
app.url_path_for("logout"),
headers={"Authorization": f"Bearer {token}"}
)
await unauthorized_client.post(app.url_path_for("logout"), headers={"Authorization": f"Bearer {token}"})
# old token must now be rejected
response = await unauthorized_client.get(
app.url_path_for("get_logged_in_user"),
headers={"Authorization": f"Bearer {token}"}
app.url_path_for("get_logged_in_user"), headers={"Authorization": f"Bearer {token}"}
)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
assert response.json()["message"] == f"Token has been revoked for '{test_user.username}'"
async def test_new_token_works_after_logout_and_relogin(
self,
app: FastAPI,
unauthorized_client: AsyncClient,
test_user: User,
self,
app: FastAPI,
unauthorized_client: AsyncClient,
test_user: User,
) -> None:
credentials = {"username": test_user.username, "password": "user1_password"}
@@ -440,10 +401,7 @@ class TestLogout:
# login, then logout
response = await unauthorized_client.post(app.url_path_for("authenticate"), json=credentials)
old_token = response.json()["access_token"]
await unauthorized_client.post(
app.url_path_for("logout"),
headers={"Authorization": f"Bearer {old_token}"}
)
await unauthorized_client.post(app.url_path_for("logout"), headers={"Authorization": f"Bearer {old_token}"})
# login again to get a fresh token
response = await unauthorized_client.post(app.url_path_for("authenticate"), json=credentials)
@@ -452,18 +410,17 @@ class TestLogout:
# new token must work
response = await unauthorized_client.get(
app.url_path_for("get_logged_in_user"),
headers={"Authorization": f"Bearer {new_token}"}
app.url_path_for("get_logged_in_user"), headers={"Authorization": f"Bearer {new_token}"}
)
assert response.status_code == status.HTTP_200_OK
assert response.json()["username"] == test_user.username
async def test_stale_version_token_is_rejected(
self,
app: FastAPI,
unauthorized_client: AsyncClient,
test_user: User,
db_session: AsyncSession,
self,
app: FastAPI,
unauthorized_client: AsyncClient,
test_user: User,
db_session: AsyncSession,
) -> None:
# craft a token with ver=0 while the user's token_version is already higher
@@ -475,15 +432,14 @@ class TestLogout:
stale_token = auth_service.create_access_token(test_user.username, token_version=0)
response = await unauthorized_client.get(
app.url_path_for("get_logged_in_user"),
headers={"Authorization": f"Bearer {stale_token}"}
app.url_path_for("get_logged_in_user"), headers={"Authorization": f"Bearer {stale_token}"}
)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
async def test_logout_without_token_returns_unauthorized(
self,
app: FastAPI,
unauthorized_client: AsyncClient,
self,
app: FastAPI,
unauthorized_client: AsyncClient,
) -> None:
response = await unauthorized_client.post(app.url_path_for("logout"))
@@ -491,27 +447,26 @@ class TestLogout:
class TestRefreshToken:
async def test_login_returns_refresh_token(
self,
app: FastAPI,
unauthorized_client: AsyncClient,
test_user: User,
self,
app: FastAPI,
unauthorized_client: AsyncClient,
test_user: User,
) -> None:
credentials = {"username": test_user.username, "password": "user1_password"}
response = await unauthorized_client.post(app.url_path_for("login"),
data=credentials,
headers={"content-type": "application/x-www-form-urlencoded"})
response = await unauthorized_client.post(
app.url_path_for("login"), data=credentials, headers={"content-type": "application/x-www-form-urlencoded"}
)
assert response.status_code == status.HTTP_200_OK
assert "refresh_token" in response.json()
assert response.json().get("refresh_token") is not None
async def test_authenticate_returns_refresh_token(
self,
app: FastAPI,
unauthorized_client: AsyncClient,
test_user: User,
self,
app: FastAPI,
unauthorized_client: AsyncClient,
test_user: User,
) -> None:
credentials = {"username": test_user.username, "password": "user1_password"}
@@ -521,10 +476,10 @@ class TestRefreshToken:
assert response.json().get("refresh_token") is not None
async def test_refresh_endpoint_returns_new_tokens(
self,
app: FastAPI,
unauthorized_client: AsyncClient,
test_user: User,
self,
app: FastAPI,
unauthorized_client: AsyncClient,
test_user: User,
) -> None:
# authenticate to get a refresh token
@@ -544,10 +499,10 @@ class TestRefreshToken:
assert response.json().get("refresh_token") is not None
async def test_new_access_token_from_refresh_works_on_protected_route(
self,
app: FastAPI,
unauthorized_client: AsyncClient,
test_user: User,
self,
app: FastAPI,
unauthorized_client: AsyncClient,
test_user: User,
) -> None:
credentials = {"username": test_user.username, "password": "user1_password"}
@@ -570,10 +525,10 @@ class TestRefreshToken:
assert response.json()["username"] == test_user.username
async def test_refresh_with_stale_token_after_logout(
self,
app: FastAPI,
unauthorized_client: AsyncClient,
test_user: User,
self,
app: FastAPI,
unauthorized_client: AsyncClient,
test_user: User,
) -> None:
# authenticate and get a refresh token
@@ -583,10 +538,7 @@ class TestRefreshToken:
# logout — bumps token_version, invalidating the refresh token
access_token = auth_response.json()["access_token"]
await unauthorized_client.post(
app.url_path_for("logout"),
headers={"Authorization": f"Bearer {access_token}"}
)
await unauthorized_client.post(app.url_path_for("logout"), headers={"Authorization": f"Bearer {access_token}"})
# /refresh must now reject the stale refresh token
response = await unauthorized_client.post(
@@ -596,10 +548,10 @@ class TestRefreshToken:
assert response.status_code == status.HTTP_401_UNAUTHORIZED
async def test_refresh_rejects_access_token(
self,
app: FastAPI,
unauthorized_client: AsyncClient,
test_user: User,
self,
app: FastAPI,
unauthorized_client: AsyncClient,
test_user: User,
) -> None:
# an access token presented at /refresh must be rejected
@@ -614,10 +566,10 @@ class TestRefreshToken:
assert response.status_code == status.HTTP_401_UNAUTHORIZED
async def test_refresh_rejects_expired_token(
self,
app: FastAPI,
unauthorized_client: AsyncClient,
test_user: User,
self,
app: FastAPI,
unauthorized_client: AsyncClient,
test_user: User,
) -> None:
# a refresh token with an already-expired timestamp
@@ -630,9 +582,9 @@ class TestRefreshToken:
assert response.status_code == status.HTTP_401_UNAUTHORIZED
async def test_refresh_rejects_invalid_token(
self,
app: FastAPI,
unauthorized_client: AsyncClient,
self,
app: FastAPI,
unauthorized_client: AsyncClient,
) -> None:
response = await unauthorized_client.post(
@@ -642,10 +594,10 @@ class TestRefreshToken:
assert response.status_code == status.HTTP_401_UNAUTHORIZED
async def test_refresh_token_rejected_as_bearer(
self,
app: FastAPI,
unauthorized_client: AsyncClient,
test_user: User,
self,
app: FastAPI,
unauthorized_client: AsyncClient,
test_user: User,
) -> None:
# a refresh token used as a bearer access token must be rejected
@@ -661,25 +613,14 @@ class TestRefreshToken:
class TestSuperAdmin:
async def test_super_admin_exists(
self,
app: FastAPI,
client: AsyncClient,
db_session: AsyncSession
) -> None:
async def test_super_admin_exists(self, app: FastAPI, client: AsyncClient, db_session: AsyncSession) -> None:
user_repo = UsersRepository(db_session)
admin_in_db = await user_repo.get_user_by_username("admin")
assert admin_in_db is not None
assert auth_service.verify_password("admin", admin_in_db.hashed_password)
async def test_cannot_delete_super_admin(
self,
app: FastAPI,
client: AsyncClient,
db_session: AsyncSession
) -> None:
async def test_cannot_delete_super_admin(self, app: FastAPI, client: AsyncClient, db_session: AsyncSession) -> None:
user_repo = UsersRepository(db_session)
admin_in_db = await user_repo.get_user_by_username("admin")
@@ -687,10 +628,7 @@ class TestSuperAdmin:
assert response.status_code == status.HTTP_403_FORBIDDEN
async def test_admin_can_login_after_password_recovery(
self,
app: FastAPI,
unauthorized_client: AsyncClient,
db_session: AsyncSession
self, app: FastAPI, unauthorized_client: AsyncClient, db_session: AsyncSession
) -> None:
# set the admin password to null in the database
@@ -705,4 +643,3 @@ class TestSuperAdmin:
}
response = await unauthorized_client.post(app.url_path_for("login"), data=login_data)
assert response.status_code == status.HTTP_200_OK
+16 -20
View File
@@ -26,40 +26,36 @@ pytestmark = pytest.mark.asyncio
class TestVersionRoutes:
async def test_version_output(self, app: FastAPI, client: AsyncClient) -> None:
response = await client.get(app.url_path_for("get_version"))
assert response.status_code == status.HTTP_200_OK
assert response.json() == {'controller_host': '127.0.0.1', 'local': False, 'version': __version__}
assert response.json() == {"controller_host": "127.0.0.1", "local": False, "version": __version__}
async def test_version_input(self, app: FastAPI, client: AsyncClient) -> None:
params = {'version': __version__}
params = {"version": __version__}
response = await client.post(app.url_path_for("check_version"), json=params)
assert response.status_code == status.HTTP_200_OK
assert response.json() == {'version': __version__}
assert response.json() == {"version": __version__}
async def test_version_invalid_input(self, app: FastAPI, client: AsyncClient) -> None:
params = {'version': "0.4.2"}
params = {"version": "0.4.2"}
response = await client.post(app.url_path_for("check_version"), json=params)
assert response.status_code == status.HTTP_409_CONFLICT
assert response.json() == {'message': 'Client version 0.4.2 is not the same as server version {}'.format(__version__)}
assert response.json() == {
"message": "Client version 0.4.2 is not the same as server version {}".format(__version__)
}
async def test_version_invalid_input_schema(self, app: FastAPI, client: AsyncClient) -> None:
params = {'version': "0.4.2", "bla": "blu"}
params = {"version": "0.4.2", "bla": "blu"}
response = await client.post(app.url_path_for("check_version"), json=params)
assert response.status_code == status.HTTP_409_CONFLICT
async def test_version_invalid_json(self, app: FastAPI, client: AsyncClient) -> None:
params = "BOUM"
response = await client.post(app.url_path_for("check_version"), json=params)
assert response.status_code == status.HTTP_422_UNPROCESSABLE_CONTENT
+2 -8
View File
@@ -30,12 +30,10 @@ pytestmark = pytest.mark.asyncio
class TestIndexRoutes:
def get_static(self, filename):
current_dir = os.path.dirname(os.path.abspath(__file__))
return os.path.join(os.path.abspath(os.path.join(current_dir, '../..', '..', 'gns3server', 'static')), filename)
return os.path.join(os.path.abspath(os.path.join(current_dir, "../..", "..", "gns3server", "static")), filename)
async def test_debug(self, app: FastAPI, client: AsyncClient) -> None:
@@ -45,7 +43,6 @@ class TestIndexRoutes:
assert "Website" in html
assert __version__ in html
# @pytest.mark.asyncio
# async def test_controller(http_client, controller):
#
@@ -61,7 +58,6 @@ class TestIndexRoutes:
# response = await http_client.get('/compute')
# assert response.status_code == 200
# @pytest.mark.asyncio
# async def test_project(http_client, controller):
#
@@ -69,16 +65,14 @@ class TestIndexRoutes:
# response = await http_client.get('/projects/{}'.format(project.id))
# assert response.status_code == 200
async def test_web_ui(self, app: FastAPI, client: AsyncClient) -> None:
response = await client.get(app.url_path_for("web_ui", file_path="index.html"))
assert response.status_code == status.HTTP_200_OK
async def test_web_ui_not_found(self, app: FastAPI, client: AsyncClient, tmpdir: str) -> None:
with patch('gns3server.utils.get_resource.get_resource') as mock:
with patch("gns3server.utils.get_resource.get_resource") as mock:
mock.return_value = str(tmpdir)
response = await client.get(app.url_path_for("web_ui", file_path="not-found.txt"))
# should serve web-ui/index.html
+18 -21
View File
@@ -25,7 +25,6 @@ from httpx_ws.transport import ASGIWebSocketTransport
from typing import Iterator, Sequence, Tuple
pytestmark = pytest.mark.asyncio
ALLOWED_CONTROLLER_ENDPOINTS = [
@@ -64,14 +63,14 @@ def _join_paths(prefix: str, path: str) -> str:
def _iter_routes(
routes: Sequence[BaseRoute],
prefix: str = "",
include_mounted_routes: bool = False
routes: Sequence[BaseRoute], prefix: str = "", include_mounted_routes: bool = False
) -> Iterator[Tuple[str, BaseRoute]]:
for route in routes:
if isinstance(route, _IncludedRouter):
include_prefix = route.include_context.prefix or ""
yield from _iter_routes(route.original_router.routes, _join_paths(prefix, include_prefix), include_mounted_routes)
yield from _iter_routes(
route.original_router.routes, _join_paths(prefix, include_prefix), include_mounted_routes
)
continue
if isinstance(route, (APIRoute, APIWebSocketRoute)):
@@ -85,12 +84,9 @@ def _iter_routes(
class TestRoutes:
# Controller endpoints have a OAuth2 bearer token authentication
async def test_controller_endpoints_require_authentication(
self,
app: FastAPI,
unauthorized_client: AsyncClient
self, app: FastAPI, unauthorized_client: AsyncClient
) -> None:
for path, route in _iter_routes(app.routes):
@@ -99,23 +95,22 @@ class TestRoutes:
if (path, method) not in ALLOWED_CONTROLLER_ENDPOINTS:
request_path = path.rstrip("/")
response = await getattr(unauthorized_client, method.lower())(request_path)
assert response.status_code == status.HTTP_401_UNAUTHORIZED, f"{method} {request_path} -> {response.status_code}"
assert response.status_code == status.HTTP_401_UNAUTHORIZED, (
f"{method} {request_path} -> {response.status_code}"
)
elif isinstance(route, APIWebSocketRoute) and not path.startswith("/v3/compute"):
params = {"token": "wrong_token"}
async with AsyncClient(base_url="http://test-api", transport=ASGIWebSocketTransport(app=app)) as client:
async with aconnect_ws(path, client, params=params) as ws:
json_notification = await ws.receive_json()
assert json_notification['event'] == {
'message': 'Could not authenticate while connecting to controller WebSocket: '
'Invalid token (DecodeError) (received token sha256 prefix: 4d4f92fb)'
assert json_notification["event"] == {
"message": "Could not authenticate while connecting to controller WebSocket: "
"Invalid token (DecodeError) (received token sha256 prefix: 4d4f92fb)"
}
# Compute endpoints have a basic HTTP authentication
async def test_compute_endpoints_require_authentication(
self,
app: FastAPI,
unauthorized_client: AsyncClient
self, app: FastAPI, unauthorized_client: AsyncClient
) -> None:
for path, route in _iter_routes(app.routes, include_mounted_routes=True):
@@ -126,13 +121,15 @@ class TestRoutes:
for method in list(route.methods):
request_path = path.rstrip("/")
response = await getattr(unauthorized_client, method.lower())(request_path)
#if response.status_code == status.HTTP_307_TEMPORARY_REDIRECT:
# if response.status_code == status.HTTP_307_TEMPORARY_REDIRECT:
# response = await getattr(unauthorized_client, method.lower())(response.headers["location"])
assert response.status_code == status.HTTP_401_UNAUTHORIZED, f"{method} {request_path} -> {response.status_code}"
assert response.status_code == status.HTTP_401_UNAUTHORIZED, (
f"{method} {request_path} -> {response.status_code}"
)
elif isinstance(route, APIWebSocketRoute):
async with AsyncClient(base_url="http://test-api", transport=ASGIWebSocketTransport(app=app)) as client:
async with aconnect_ws(path, client, auth=("wrong_user", "password123")) as ws:
json_notification = await ws.receive_json()
assert json_notification['event'] == {
'message': 'Could not authenticate while connecting to compute WebSocket: Could not validate credentials'
assert json_notification["event"] == {
"message": "Could not authenticate while connecting to compute WebSocket: Could not validate credentials"
}