Skip to content

Commit

Permalink
Merge pull request #138 from tmikuska/master
Browse files Browse the repository at this point in the history
test compatibility with httpx and all supported CML version (2.3, 2.4 and 2.5)
  • Loading branch information
jclarke-csco authored Nov 29, 2022
2 parents 6531996 + e27b6d0 commit 419d541
Show file tree
Hide file tree
Showing 27 changed files with 300 additions and 275 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/github-actions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ jobs:
- "3.7"
- "3.8"
- "3.9"
- "3.10"
- "3.11"
env:
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
*.code-workspace
scratch*
.vscode/
.idea

# virl stuff
.virl/
Expand Down Expand Up @@ -56,6 +57,9 @@ nosetests.xml
coverage.xml
*.cover
.hypothesis/
5f0d96.yaml
default_inventory.*
default_testbed.yaml

# Translations
*.mo
Expand Down
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ clean-test: ## remove test and coverage artifacts
rm -f default_inventory.ini
rm -f default_inventory.yaml
rm -f default_testbed.yaml
rm -f .virl/cached_cml_labs/*
rm -f .virl/current_cml_lab

lint: ## check style with flake8
flake8

coverage:
coverage run --source=virl setup.py test
PYTHONWARNINGS="ignore::DeprecationWarning" coverage run --source=virl setup.py test

report: coverage
coverage html
Expand Down
5 changes: 4 additions & 1 deletion test-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ enum34
flake8
funcsigs
gunicorn
httpx
idna
importlib-metadata
itsdangerous
Expand All @@ -35,9 +36,11 @@ python-dateutil
requests-mock
requests
requests_toolbelt
respx
six
tabulate
urllib3
virl2-client>=2.2.1
virl2-client>=2.2.1,!=2.4.0
wcwidth
wheel
zipp
117 changes: 80 additions & 37 deletions tests/v2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import traceback
import pdb
import requests_mock
import respx
from httpx import Response
from virl2_client import ClientLibrary


def setup_cml_environ():
Expand Down Expand Up @@ -39,7 +42,18 @@ def wrapper(*args, **kwargs):
return decorator


CLIENT_VERSION = ClientLibrary.VERSION


class BaseCMLTest(unittest.TestCase):
def get_context(self):
if CLIENT_VERSION >= CLIENT_VERSION.__class__("2.5.0"):
self.setup_func = self.uni_respx
return respx.mock(assert_all_called=False)
else:
self.setup_func = self.uni_request
return requests_mock.Mocker()

def setUp(self):
try:
os.makedirs(".virl")
Expand All @@ -49,7 +63,7 @@ def setUp(self):
# injection of VIRL_HOST
virl = self.get_virl()
runner = CliRunner()
with requests_mock.Mocker() as m:
with self.get_context() as m:
self.setup_mocks(m)
runner.invoke(virl, ["use", self.get_test_title()])

Expand Down Expand Up @@ -85,43 +99,72 @@ def get_alt_title(self):
def get_cml23_id(self):
return "88119b68-9d08-40c4-90f5-6dc533fd0254"

def prep_respx(self, **kwargs):
if "body" in kwargs:
kwargs["content"] = kwargs.pop("body")

side_effect = None
for k, v in kwargs.copy().items():
if callable(v):
side_effect = kwargs.pop(k)

def side_effect_mod(req):
if side_effect is not None:
return Response(200, **{k: side_effect(req)})

return kwargs, side_effect_mod if side_effect is not None else None

def uni_respx(self, method, m, path, **kwargs):
kwargs, side_effect = self.prep_respx(**kwargs)
# respx uses lowercase bool
path = path.replace("False", "false").replace("True", "true")
path = self.get_api_path(path)
m.request(method.upper(), path).mock(return_value=Response(200, **kwargs), side_effect=side_effect)

def uni_request(self, method, m, path, **kwargs):
path = self.get_api_path(path)
m.request(method.upper(), path, **kwargs)

def setup_mocks(self, m):
m.get(self.get_api_path("labs"), json=MockCMLServer.get_labs)
m.get(self.get_api_path("populate_lab_tiles"), json=MockCMLServer.get_lab_tiles)
m.get(self.get_api_path("labs/{}/download".format(self.get_test_id())), text=MockCMLServer.download_lab)
m.get(self.get_api_path("labs/{}/download".format(self.get_cml23_id())), text=MockCMLServer.download_lab_23)
m.get(
self.get_api_path("labs/{}/topology?exclude_configurations=False".format(self.get_test_id())), json=MockCMLServer.get_topology
)
m.get(
self.get_api_path("labs/{}/topology?exclude_configurations=False".format(self.get_alt_id())),
json=MockCMLServer.get_alt_topology,
)
m.get(
self.get_api_path("labs/{}/topology?exclude_configurations=False".format(self.get_cml23_id())),
json=MockCMLServer.get_topology_23,
)
m.get(self.get_api_path("labs/{}/topology?exclude_configurations=True".format(self.get_test_id())), json=MockCMLServer.get_topology)
m.get(
self.get_api_path("labs/{}/topology?exclude_configurations=True".format(self.get_alt_id())),
json=MockCMLServer.get_alt_topology,
)
m.get(
self.get_api_path("labs/{}/topology?exclude_configurations=True".format(self.get_cml23_id())),
json=MockCMLServer.get_topology_23,
)
m.get(self.get_api_path("labs/{}/download".format(self.get_alt_id())), text=MockCMLServer.download_alt_lab)
m.get(self.get_api_path("labs/{}/lab_element_state".format(self.get_test_id())), json=MockCMLServer.get_lab_element_state)
m.get(self.get_api_path("labs/{}/lab_element_state".format(self.get_cml23_id())), json=MockCMLServer.get_lab_element_state_23)
m.get(self.get_api_path("system_information"), json=MockCMLServer.get_sys_info)
m.get(self.get_api_path("authok"), text=MockCMLServer.auth_ok)
m.post(self.get_api_path("authenticate"), text=MockCMLServer.authenticate)
m.get(self.get_api_path("labs/{}/state".format(self.get_test_id())), json="STARTED")
m.get(self.get_api_path("labs/{}/state".format(self.get_cml23_id())), json="STARTED")
m.get(self.get_api_path("labs/{}/state".format(self.get_alt_id())), json="STOPPED")
m.get(self.get_api_path("labs/{}/check_if_converged".format(self.get_test_id())), json=True)
m.get(self.get_api_path("labs/{}/check_if_converged".format(self.get_cml23_id())), json=True)
m.get(self.get_api_path("labs/{}/nodes/n1/check_if_converged".format(self.get_test_id())), json=True)
json_dict = {
"labs": MockCMLServer.get_labs,
"populate_lab_tiles": MockCMLServer.get_lab_tiles,
"labs/{}/topology?exclude_configurations=False".format(self.get_test_id()): MockCMLServer.get_topology,
"labs/{}/topology?exclude_configurations=False".format(self.get_alt_id()): MockCMLServer.get_alt_topology,
"labs/{}/topology?exclude_configurations=False".format(self.get_cml23_id()): MockCMLServer.get_topology_23,
"labs/{}/topology?exclude_configurations=True".format(self.get_test_id()): MockCMLServer.get_topology,
"labs/{}/topology?exclude_configurations=True".format(self.get_alt_id()): MockCMLServer.get_alt_topology,
"labs/{}/topology?exclude_configurations=True".format(self.get_cml23_id()): MockCMLServer.get_topology_23,
"labs/{}/lab_element_state".format(self.get_test_id()): MockCMLServer.get_lab_element_state,
"labs/{}/lab_element_state".format(self.get_cml23_id()): MockCMLServer.get_lab_element_state_23,
"system_information": MockCMLServer.get_sys_info,
"labs/{}/state".format(self.get_test_id()): "STARTED",
"labs/{}/state".format(self.get_cml23_id()): "STARTED",
"labs/{}/state".format(self.get_alt_id()): "STOPPED",
"labs/{}/check_if_converged".format(self.get_test_id()): True,
"labs/{}/check_if_converged".format(self.get_cml23_id()): True,
"labs/{}/nodes/n1/check_if_converged".format(self.get_test_id()): True
}

text_dict = {
"labs/{}/download".format(self.get_test_id()): MockCMLServer.download_lab,
"labs/{}/download".format(self.get_cml23_id()): MockCMLServer.download_lab_23,
"labs/{}/download".format(self.get_alt_id()): MockCMLServer.download_alt_lab,
"authok": MockCMLServer.auth_ok,
}

if isinstance(m, requests_mock.Mocker):
self.setup_func = self.uni_request
else:
self.setup_func = self.uni_respx

for path, value in json_dict.items():
self.setup_func("get", m, path, json=value)

for path, value in text_dict.items():
self.setup_func("get", m, path, text=value)

self.setup_func("post", m, "authenticate", text=MockCMLServer.authenticate)

def add_debug_mock(self, m):
m.register_uri(requests_mock.ANY, requests_mock.ANY, text=MockCMLServer.print_req)
7 changes: 3 additions & 4 deletions tests/v2/bad_plugins.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from . import BaseCMLTest
from virl.api.plugin import _test_enable_plugins
from click.testing import CliRunner
import requests_mock
import os

try:
Expand Down Expand Up @@ -29,7 +28,7 @@ def tearDownClass(cls):
def test_cmd_plugin_bad(self, secho_mock):
self.localSetUp("plugins_bad_cmd")
virl = self.get_virl()
with requests_mock.Mocker() as m:
with self.get_context() as m:
runner = CliRunner()
# Mock the request to return what we expect from the API.
self.setup_mocks(m)
Expand All @@ -44,7 +43,7 @@ def test_cmd_plugin_bad(self, secho_mock):
def test_gen_plugin_bad(self, secho_mock):
self.localSetUp("plugins_bad_gen")
virl = self.get_virl()
with requests_mock.Mocker() as m:
with self.get_context() as m:
runner = CliRunner()
# Mock the request to return what we expect from the API.
self.setup_mocks(m)
Expand All @@ -59,7 +58,7 @@ def test_gen_plugin_bad(self, secho_mock):
def test_view_plugin_bad(self, secho_mock):
self.localSetUp("plugins_bad_viewer")
virl = self.get_virl()
with requests_mock.Mocker() as m:
with self.get_context() as m:
runner = CliRunner()
# Mock the request to return what we expect from the API.
self.setup_mocks(m)
Expand Down
11 changes: 6 additions & 5 deletions tests/v2/cluster.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
from . import BaseCMLTest
import unittest
from . import BaseCMLTest, CLIENT_VERSION
from .mocks.github import MockGitHub # noqa
from click.testing import CliRunner
import requests_mock

try:
from unittest.mock import patch
except ImportError:
from mock import patch # noqa


@unittest.skipIf(CLIENT_VERSION < CLIENT_VERSION.__class__("2.4.0"), "supported since 2.4.0")
class TestCMLCluster(BaseCMLTest):
def setup_mocks(self, m):
super().setup_mocks(m)
m.get(self.get_api_path("system_health"), json=TestCMLCluster.get_system_health)
self.setup_func("get", m, "system_health", json=TestCMLCluster.get_system_health)

@staticmethod
def get_system_health(req, ctx):
def get_system_health(req, ctx=None):
response = {
"valid": True,
"computes": {
Expand All @@ -35,7 +36,7 @@ def get_system_health(req, ctx):
return response

def test_cml_cluster_info(self):
with requests_mock.Mocker() as m:
with self.get_context() as m:
# Mock the request to return what we expect from the API.
self.setup_mocks(m)
virl = self.get_virl()
Expand Down
7 changes: 3 additions & 4 deletions tests/v2/console.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from . import BaseCMLTest
from click.testing import CliRunner
import requests_mock

try:
from unittest.mock import patch
Expand All @@ -10,7 +9,7 @@

class CMLConsoleTests(BaseCMLTest):
def test_cml_console_display(self):
with requests_mock.Mocker() as m:
with self.get_context() as m:
# Mock the request to return what we expect from the API.
self.setup_mocks(m)
virl = self.get_virl()
Expand All @@ -20,7 +19,7 @@ def test_cml_console_display(self):

@patch("virl.cli.console.commands.call", autospec=False)
def test_cml_console_connect(self, call_mock):
with requests_mock.Mocker() as m:
with self.get_context() as m:
# Mock the request to return what we expect from the API.
self.setup_mocks(m)
virl = self.get_virl()
Expand All @@ -30,7 +29,7 @@ def test_cml_console_connect(self, call_mock):

@patch("virl.cli.console.commands.call", autospec=False)
def test_cml_console_connect_23(self, call_mock):
with requests_mock.Mocker() as m:
with self.get_context() as m:
# Mock the request to return what we expect from the API.
self.setup_mocks(m)
virl = self.get_virl()
Expand Down
25 changes: 12 additions & 13 deletions tests/v2/definitions.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
from . import BaseCMLTest
from click.testing import CliRunner
import requests_mock
import textwrap
import os
import traceback


class CMLDefinitionsTest(BaseCMLTest):

def test_cml_image_definitions_import(self):
with requests_mock.Mocker() as m:
with self.get_context() as m:
# Mock the request to return what we expect from the API.
self.setup_mocks(m)
m.post(self.get_api_path("image_definitions/"), json=True) # virl2_client 2.2.1 uses URI that ends in slashes
self.setup_func("post", m, "image_definitions/", json=True) # virl2_client 2.2.1 uses URI that ends in slashes
virl = self.get_virl()
runner = CliRunner()
result = runner.invoke(
Expand All @@ -28,12 +28,12 @@ def test_cml_image_definitions_import(self):
self.assertEqual(0, result.exit_code, result.stdout)

def test_node_definitions_list(self):
with requests_mock.Mocker() as m:
with self.get_context() as m:
with open(os.path.join(os.path.dirname(__file__), "static/response_get_node_defs.json"), "rb") as fh_node_defs:
# Mock the request to return what we expect from the API.
self.setup_mocks(m)
m.get(
self.get_api_path("node_definitions/"), body=fh_node_defs, headers={"content-type": "application/json; charset=utf-8"}
self.setup_func(
"get", m, "node_definitions/", body=fh_node_defs, headers={"content-type": "application/json; charset=utf-8"}
)
virl = self.get_virl()
runner = CliRunner()
Expand All @@ -46,13 +46,12 @@ def test_node_definitions_list(self):
self.assertEquals(output_text, result.output)

def test_node_definitions_list_one(self):
with requests_mock.Mocker() as m:
with self.get_context() as m:
with open(os.path.join(os.path.dirname(__file__), "static/response_get_node_defs.json"), "rb") as fh_node_defs:
# Mock the request to return what we expect from the API.
self.setup_mocks(m)
m.get(
self.get_api_path("node_definitions/"), body=fh_node_defs, headers={"content-type": "application/json; charset=utf-8"}
)
self.setup_func(
"get", m, "node_definitions/", body=fh_node_defs, headers={"content-type": "application/json; charset=utf-8"})
virl = self.get_virl()
runner = CliRunner()
result = runner.invoke(virl, ["definitions", "nodes", "ls", "--node", "nxosv9000"])
Expand All @@ -75,12 +74,12 @@ def test_node_definitions_list_legacy(self):
Check that we can still handle data in the legacy format that was
returned by the API before CML 2.3.
"""
with requests_mock.Mocker() as m:
with self.get_context() as m:
with open(os.path.join(os.path.dirname(__file__), "static/response_get_node_defs_cml22.json"), "rb") as fh_node_defs:
# Mock the request to return what we expect from the API.
self.setup_mocks(m)
m.get(
self.get_api_path("node_definitions/"), body=fh_node_defs, headers={"content-type": "application/json; charset=utf-8"}
self.setup_func(
"get", m, "node_definitions/", body=fh_node_defs, headers={"content-type": "application/json; charset=utf-8"}
)
virl = self.get_virl()
runner = CliRunner()
Expand Down
Loading

0 comments on commit 419d541

Please sign in to comment.