Skip to content

Commit 0f914ec

Browse files
authored
feature: E2E tests additional auth methods, easier config, and pipeline yaml (#864)
* add other auth methods * update host code to use environment * add pipeline yaml * rename device structure and fixture
1 parent 8d9601d commit 0f914ec

18 files changed

+464
-167
lines changed

device_e2e/aio/conftest.py

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import e2e_settings
99
import test_config
1010
import logging
11+
from utils import create_client_object
1112
from service_helper import ServiceHelper
1213
from azure.iot.device.iothub.aio import IoTHubDeviceClient, IoTHubModuleClient
1314

@@ -23,31 +24,10 @@ def event_loop():
2324

2425

2526
@pytest.fixture(scope="function")
26-
async def brand_new_client(client_kwargs):
27-
client = None
28-
29-
if test_config.config.identity == test_config.IDENTITY_DEVICE_CLIENT:
30-
ClientClass = IoTHubDeviceClient
31-
elif test_config.config.identity == test_config.IDENTITY_MODULE_CLIENT:
32-
ClientClass = IoTHubModuleClient
33-
else:
34-
raise Exception("config.identity invalid")
35-
36-
if test_config.config.auth == test_config.AUTH_CONNECTION_STRING:
37-
# TODO: This is currently using a connection string stored in _e2e_settings.xml. This will move to be a dynamically created identity similar to the way node's device_identity_helper.js works.
38-
logger.info(
39-
"Creating {} using create_from_connection_string with kwargs={}".format(
40-
ClientClass, client_kwargs
41-
)
42-
)
43-
client = ClientClass.create_from_connection_string(
44-
e2e_settings.DEVICE_CONNECTION_STRING, **client_kwargs
45-
)
46-
elif test_config.config.auth == test_config.X509:
47-
# need to implement
48-
raise Exception("X509 Auth not yet implemented")
49-
else:
50-
raise Exception("config.auth invalid")
27+
async def brand_new_client(device_identity, client_kwargs):
28+
client = create_client_object(
29+
device_identity, client_kwargs, IoTHubDeviceClient, IoTHubModuleClient
30+
)
5131

5232
yield client
5333

device_e2e/aio/test_sas_renewal.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,12 @@
1313
pytestmark = pytest.mark.asyncio
1414

1515

16+
@pytest.mark.skipif(
17+
test_config.config.auth not in test_config.AUTH_WITH_RENEWING_TOKEN,
18+
reason="{} auth does not support token renewal".format(test_config.config.auth),
19+
)
1620
@pytest.mark.describe("Client sas renewal code")
17-
class TestSasRenewalReconnectEnabled(object):
21+
class TestSasRenewal(object):
1822
@pytest.fixture(scope="class")
1923
def extra_client_kwargs(self):
2024
# should renew after 10 seconds

device_e2e/aio/test_send_message.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def extra_client_kwargs(self):
4444
return {"keep_alive": 5}
4545

4646
@pytest.mark.it("Sends if connection drops before sending")
47+
@pytest.mark.uses_iptables
4748
async def test_sends_if_drop_before_sending(
4849
self, client, random_message, dropper, get_next_eventhub_arrival
4950
):
@@ -74,6 +75,7 @@ async def test_sends_if_drop_before_sending(
7475
logger.info("Success")
7576

7677
@pytest.mark.it("Sends if connection rejects send")
78+
@pytest.mark.uses_iptables
7779
async def test_sends_if_reject_before_sending(
7880
self, client, random_message, dropper, get_next_eventhub_arrival
7981
):
@@ -137,6 +139,7 @@ async def test_connect_if_necessary(self, client, random_message, get_next_event
137139
assert json.dumps(event.message_body) == random_message.data
138140

139141
@pytest.mark.it("Automatically connects if transport automatically disconnected before sending")
142+
@pytest.mark.uses_iptables
140143
async def test_connects_after_automatic_disconnect(
141144
self, client, random_message, dropper, get_next_eventhub_arrival
142145
):
@@ -156,6 +159,7 @@ async def test_connects_after_automatic_disconnect(
156159
assert json.dumps(event.message_body) == random_message.data
157160

158161
@pytest.mark.it("Fails if connection disconnects before sending")
162+
@pytest.mark.uses_iptables
159163
async def test_fails_if_disconnect_before_sending(self, client, random_message, dropper):
160164

161165
assert client.connected
@@ -170,6 +174,7 @@ async def test_fails_if_disconnect_before_sending(self, client, random_message,
170174
await send_task
171175

172176
@pytest.mark.it("Fails if connection drops before sending")
177+
@pytest.mark.uses_iptables
173178
async def test_fails_if_drop_before_sending(self, client, random_message, dropper):
174179

175180
assert client.connected

device_e2e/aio/test_twin.py

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,29 +21,35 @@
2121
@pytest.mark.describe("Device Client Reported Properties")
2222
class TestReportedProperties(object):
2323
@pytest.mark.it("Can set a simple reported property")
24-
async def test_simple_patch(self, client, reported_props, get_next_reported_patch_arrival):
24+
async def test_simple_patch(
25+
self, client, random_reported_props, get_next_reported_patch_arrival
26+
):
2527

2628
# patch properties
27-
await client.patch_twin_reported_properties(reported_props)
29+
await client.patch_twin_reported_properties(random_reported_props)
2830

2931
# wait for patch to arrive at service and verify
3032
received_patch = await get_next_reported_patch_arrival()
3133
assert (
32-
received_patch[const.REPORTED][const.TEST_CONTENT] == reported_props[const.TEST_CONTENT]
34+
received_patch[const.REPORTED][const.TEST_CONTENT]
35+
== random_reported_props[const.TEST_CONTENT]
3336
)
3437

3538
# get twin from the service and verify content
3639
twin = await client.get_twin()
37-
assert twin[const.REPORTED][const.TEST_CONTENT] == reported_props[const.TEST_CONTENT]
40+
assert twin[const.REPORTED][const.TEST_CONTENT] == random_reported_props[const.TEST_CONTENT]
3841

3942
@pytest.mark.it("Can clear a reported property")
40-
async def test_clear_property(self, client, reported_props, get_next_reported_patch_arrival):
43+
async def test_clear_property(
44+
self, client, random_reported_props, get_next_reported_patch_arrival
45+
):
4146

4247
# patch properties and verify that the service received the patch
43-
await client.patch_twin_reported_properties(reported_props)
48+
await client.patch_twin_reported_properties(random_reported_props)
4449
received_patch = await get_next_reported_patch_arrival()
4550
assert (
46-
received_patch[const.REPORTED][const.TEST_CONTENT] == reported_props[const.TEST_CONTENT]
51+
received_patch[const.REPORTED][const.TEST_CONTENT]
52+
== random_reported_props[const.TEST_CONTENT]
4753
)
4854

4955
# send a patch clearing properties and verify that the service received that patch
@@ -60,22 +66,23 @@ async def test_clear_property(self, client, reported_props, get_next_reported_pa
6066

6167
@pytest.mark.it("Connects the transport if necessary")
6268
async def test_connect_if_necessary(
63-
self, client, reported_props, get_next_reported_patch_arrival
69+
self, client, random_reported_props, get_next_reported_patch_arrival
6470
):
6571

6672
await client.disconnect()
6773

6874
assert not client.connected
69-
await client.patch_twin_reported_properties(reported_props)
75+
await client.patch_twin_reported_properties(random_reported_props)
7076
assert client.connected
7177

7278
received_patch = await get_next_reported_patch_arrival()
7379
assert (
74-
received_patch[const.REPORTED][const.TEST_CONTENT] == reported_props[const.TEST_CONTENT]
80+
received_patch[const.REPORTED][const.TEST_CONTENT]
81+
== random_reported_props[const.TEST_CONTENT]
7582
)
7683

7784
twin = await client.get_twin()
78-
assert twin[const.REPORTED][const.TEST_CONTENT] == reported_props[const.TEST_CONTENT]
85+
assert twin[const.REPORTED][const.TEST_CONTENT] == random_reported_props[const.TEST_CONTENT]
7986

8087

8188
@pytest.mark.dropped_connection
@@ -89,13 +96,15 @@ def extra_client_kwargs(self):
8996

9097
@pytest.mark.it("Sends if connection drops before sending")
9198
async def test_sends_if_drop_before_sending(
92-
self, client, reported_props, dropper, get_next_reported_patch_arrival
99+
self, client, random_reported_props, dropper, get_next_reported_patch_arrival
93100
):
94101

95102
assert client.connected
96103
dropper.drop_outgoing()
97104

98-
send_task = asyncio.create_task(client.patch_twin_reported_properties(reported_props))
105+
send_task = asyncio.create_task(
106+
client.patch_twin_reported_properties(random_reported_props)
107+
)
99108
while client.connected:
100109
await asyncio.sleep(1)
101110

@@ -109,18 +118,21 @@ async def test_sends_if_drop_before_sending(
109118

110119
received_patch = await get_next_reported_patch_arrival()
111120
assert (
112-
received_patch[const.REPORTED][const.TEST_CONTENT] == reported_props[const.TEST_CONTENT]
121+
received_patch[const.REPORTED][const.TEST_CONTENT]
122+
== random_reported_props[const.TEST_CONTENT]
113123
)
114124

115125
@pytest.mark.it("Sends if connection rejects send")
116126
async def test_sends_if_reject_before_sending(
117-
self, client, reported_props, dropper, get_next_reported_patch_arrival
127+
self, client, random_reported_props, dropper, get_next_reported_patch_arrival
118128
):
119129

120130
assert client.connected
121131
dropper.reject_outgoing()
122132

123-
send_task = asyncio.create_task(client.patch_twin_reported_properties(reported_props))
133+
send_task = asyncio.create_task(
134+
client.patch_twin_reported_properties(random_reported_props)
135+
)
124136
while client.connected:
125137
await asyncio.sleep(1)
126138

@@ -134,7 +146,8 @@ async def test_sends_if_reject_before_sending(
134146

135147
received_patch = await get_next_reported_patch_arrival()
136148
assert (
137-
received_patch[const.REPORTED][const.TEST_CONTENT] == reported_props[const.TEST_CONTENT]
149+
received_patch[const.REPORTED][const.TEST_CONTENT]
150+
== random_reported_props[const.TEST_CONTENT]
138151
)
139152

140153

device_e2e/client_fixtures.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,7 @@
88
import functools
99
import time
1010
import sys
11-
import const
1211
import test_config
13-
from azure.iot.device.iothub import Message
14-
from utils import get_random_message, get_random_dict
15-
from azure.iot.device.iothub import IoTHubDeviceClient
1612

1713

1814
@pytest.fixture(scope="function")
@@ -26,23 +22,13 @@ def module_id(brand_new_client):
2622
return brand_new_client._mqtt_pipeline._pipeline.pipeline_configuration.module_id
2723

2824

29-
@pytest.fixture(scope="function")
30-
def reported_props():
31-
return {const.TEST_CONTENT: get_random_dict()}
32-
33-
3425
@pytest.fixture(scope="function")
3526
def watches_events(service_helper, device_id, module_id):
3627
service_helper.start_watching(device_id, module_id)
3728
yield
3829
service_helper.stop_watching(device_id, module_id)
3930

4031

41-
@pytest.fixture(scope="function")
42-
def random_message():
43-
return get_random_message()
44-
45-
4632
@pytest.fixture(scope="function")
4733
def connection_retry():
4834
return True

device_e2e/conftest.py

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
import logging
66
import concurrent.futures
77
import test_config
8+
import device_identity_helper
9+
import const
10+
import sys
11+
from utils import get_random_message, get_random_dict, is_windows
812

913
# noqa: F401 defined in .flake8 file in root of repo
1014

@@ -27,15 +31,16 @@
2731
websockets,
2832
device_id,
2933
module_id,
30-
reported_props,
3134
watches_events,
32-
random_message,
3335
)
3436

3537
logging.basicConfig(level=logging.WARNING)
3638
logging.getLogger("e2e").setLevel(level=logging.DEBUG)
3739
logging.getLogger("paho").setLevel(level=logging.DEBUG)
38-
logging.getLogger("azure.iot").setLevel(level=logging.DEBUG)
40+
logging.getLogger("azure.iot").setLevel(level=logging.INFO)
41+
42+
logger = logging.getLogger(__name__)
43+
logger.setLevel(level=logging.INFO)
3944

4045

4146
@pytest.fixture(scope="module")
@@ -48,6 +53,44 @@ def executor():
4853
return concurrent.futures.ThreadPoolExecutor()
4954

5055

56+
@pytest.fixture(scope="function")
57+
def random_message():
58+
return get_random_message()
59+
60+
61+
@pytest.fixture(scope="function")
62+
def random_reported_props():
63+
return {const.TEST_CONTENT: get_random_dict()}
64+
65+
66+
@pytest.fixture(scope="session")
67+
def device_identity():
68+
69+
if test_config.config.auth == test_config.AUTH_CONNECTION_STRING:
70+
device_identity = device_identity_helper.create_device_with_symmetric_key()
71+
logger.info(
72+
"Created connection string device with deviceId = {}".format(device_identity.device_id)
73+
)
74+
elif test_config.config.auth == test_config.AUTH_SYMMETRIC_KEY:
75+
device_identity = device_identity_helper.create_device_with_symmetric_key()
76+
logger.info(
77+
"Created symmetric key device with deviceId = {}".format(device_identity.device_id)
78+
)
79+
elif test_config.config.auth == test_config.AUTH_SAS_TOKEN:
80+
device_identity = device_identity_helper.create_device_with_sas()
81+
logger.info("Created sas token device with deviceId = {}".format(device_identity.device_id))
82+
elif test_config.config.auth in test_config.AUTH_CHOICES:
83+
# need to implement
84+
raise Exception("{} Auth not yet implemented".format(test_config.config.auth))
85+
else:
86+
raise Exception("config.auth invalid")
87+
88+
yield device_identity
89+
90+
logger.info("Deleting device with deviceId = {}".format(device_identity.device_id))
91+
device_identity_helper.delete_device(device_identity.device_id)
92+
93+
5194
def pytest_addoption(parser):
5295
parser.addoption(
5396
"--transport",
@@ -68,11 +111,26 @@ def pytest_addoption(parser):
68111
help="Identity (client type) to use for tests",
69112
type=str,
70113
choices=test_config.IDENTITY_CHOICES,
71-
default=test_config.IDENTITY_DEVICE_CLIENT,
114+
default=test_config.IDENTITY_DEVICE,
72115
)
73116

74117

75118
def pytest_configure(config):
76119
test_config.config.transport = config.getoption("transport")
77120
test_config.config.auth = config.getoption("auth")
78121
test_config.config.identity = config.getoption("identity")
122+
123+
124+
def pytest_runtest_setup(item):
125+
# tests that use iptables need to be skipped on Windows
126+
if is_windows():
127+
for x in item.iter_markers("uses_iptables"):
128+
pytest.skip("test uses iptables")
129+
break
130+
131+
132+
collect_ignore = ["test_settings.py"]
133+
134+
# Ignore Async tests if below Python 3.5
135+
if sys.version_info < (3, 5):
136+
collect_ignore.append("aio")

0 commit comments

Comments
 (0)