diff --git a/test/conftest.py b/test/conftest.py new file mode 100644 index 0000000..63c0d43 --- /dev/null +++ b/test/conftest.py @@ -0,0 +1,43 @@ +import sys +import os +import pytest +from unittest.mock import MagicMock + +sys.path.insert(0, + os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +import notecard # noqa: E402 + + +@pytest.fixture +def run_fluent_api_notecard_api_mapping_test(): + def _run_test(fluent_api, notecard_api_name, req_params, rename_map=None): + card = notecard.Notecard() + card.Transaction = MagicMock() + + fluent_api(card, **req_params) + expected_notecard_api_req = {'req': notecard_api_name, **req_params} + + # There are certain fluent APIs that have keyword arguments that don't + # map exactly onto the Notecard API. For example, note.changes takes a + # 'maximum' parameter, but in the JSON request that gets sent to the + # Notecard, it's sent as 'max'. The rename_map allows a test to specify + # how a fluent API's keyword args map to Notecard API args, in cases + # where they differ. + if rename_map is not None: + for old_key, new_key in rename_map.items(): + expected_notecard_api_req[new_key] = expected_notecard_api_req.pop(old_key) + + card.Transaction.assert_called_once_with(expected_notecard_api_req) + + return _run_test + + +@pytest.fixture +def run_fluent_api_invalid_notecard_test(): + def _run_test(fluent_api, req_params): + with pytest.raises(Exception, match='Notecard object required'): + # Call with None instead of a valid Notecard object. + fluent_api(None, **req_params) + + return _run_test diff --git a/test/test_card.py b/test/test_card.py new file mode 100644 index 0000000..beb246d --- /dev/null +++ b/test/test_card.py @@ -0,0 +1,69 @@ +import pytest +from notecard import card + + +@pytest.mark.parametrize( + 'fluent_api,notecard_api,req_params', + [ + ( + card.attn, + 'card.attn', + { + 'mode': 'arm', + 'files': ['data.qi', 'my-settings.db'], + 'seconds': 60, + 'payload': 'ewogICJpbnRlcnZhbHMiOiI2MCwxMiwxNCIKfQ==', + 'start': True + } + ), + ( + card.status, + 'card.status', + {} + ), + ( + card.time, + 'card.time', + {} + ), + ( + card.temp, + 'card.temp', + {'minutes': 5} + ), + ( + card.version, + 'card.version', + {} + ), + ( + card.voltage, + 'card.voltage', + { + 'hours': 1, + 'offset': 2, + 'vmax': 1.1, + 'vmin': 1.2 + } + ), + ( + card.wireless, + 'card.wireless', + { + 'mode': 'auto', + 'apn': 'myapn.nb' + } + ) + ] +) +class TestCard: + def test_fluent_api_maps_notecard_api_correctly( + self, fluent_api, notecard_api, req_params, + run_fluent_api_notecard_api_mapping_test): + run_fluent_api_notecard_api_mapping_test(fluent_api, notecard_api, + req_params) + + def test_fluent_api_fails_with_invalid_notecard( + self, fluent_api, notecard_api, req_params, + run_fluent_api_invalid_notecard_test): + run_fluent_api_invalid_notecard_test(fluent_api, req_params) diff --git a/test/test_env.py b/test/test_env.py new file mode 100644 index 0000000..963af6b --- /dev/null +++ b/test/test_env.py @@ -0,0 +1,40 @@ +import pytest +from notecard import env + + +@pytest.mark.parametrize( + 'fluent_api,notecard_api,req_params', + [ + ( + env.default, + 'env.default', + {'name': 'my_var', 'text': 'my_text'} + ), + ( + env.get, + 'env.get', + {'name': 'my_var'} + ), + ( + env.modified, + 'env.modified', + {} + ), + ( + env.set, + 'env.set', + {'name': 'my_var', 'text': 'my_text'} + ) + ] +) +class TestEnv: + def test_fluent_api_maps_notecard_api_correctly( + self, fluent_api, notecard_api, req_params, + run_fluent_api_notecard_api_mapping_test): + run_fluent_api_notecard_api_mapping_test(fluent_api, notecard_api, + req_params) + + def test_fluent_api_fails_with_invalid_notecard( + self, fluent_api, notecard_api, req_params, + run_fluent_api_invalid_notecard_test): + run_fluent_api_invalid_notecard_test(fluent_api, req_params) diff --git a/test/test_file.py b/test/test_file.py new file mode 100644 index 0000000..81ff0c0 --- /dev/null +++ b/test/test_file.py @@ -0,0 +1,45 @@ +import pytest +from notecard import file + + +@pytest.mark.parametrize( + 'fluent_api,notecard_api,req_params', + [ + ( + file.changes, + 'file.changes', + { + 'tracker': 'tracker', + 'files': ['file_1', 'file_2', 'file_3'] + } + ), + ( + file.delete, + 'file.delete', + { + 'files': ['file_1', 'file_2', 'file_3'] + } + ), + ( + file.stats, + 'file.stats', + {} + ), + ( + file.pendingChanges, + 'file.changes.pending', + {} + ) + ] +) +class TestFile: + def test_fluent_api_maps_notecard_api_correctly( + self, fluent_api, notecard_api, req_params, + run_fluent_api_notecard_api_mapping_test): + run_fluent_api_notecard_api_mapping_test(fluent_api, notecard_api, + req_params) + + def test_fluent_api_fails_with_invalid_notecard( + self, fluent_api, notecard_api, req_params, + run_fluent_api_invalid_notecard_test): + run_fluent_api_invalid_notecard_test(fluent_api, req_params) diff --git a/test/test_hub.py b/test/test_hub.py new file mode 100644 index 0000000..9200ada --- /dev/null +++ b/test/test_hub.py @@ -0,0 +1,66 @@ +import pytest +from notecard import hub + + +@pytest.mark.parametrize( + 'fluent_api,notecard_api,req_params', + [ + ( + hub.get, + 'hub.get', + {} + ), + ( + hub.log, + 'hub.log', + { + 'text': 'com.blues.tester', + 'alert': True, + 'sync': True + } + ), + ( + hub.set, + 'hub.set', + { + 'product': 'com.blues.tester', + 'sn': 'foo', + 'mode': 'continuous', + 'outbound': 2, + 'inbound': 60, + 'duration': 5, + 'sync': True, + 'align': True, + 'voutbound': '2.3', + 'vinbound': '3.3', + 'host': 'http://hub.blues.foo' + } + ), + ( + hub.status, + 'hub.status', + {} + ), + ( + hub.sync, + 'hub.sync', + {} + ), + ( + hub.syncStatus, + 'hub.sync.status', + {'sync': True} + ) + ] +) +class TestHub: + def test_fluent_api_maps_notecard_api_correctly( + self, fluent_api, notecard_api, req_params, + run_fluent_api_notecard_api_mapping_test): + run_fluent_api_notecard_api_mapping_test(fluent_api, notecard_api, + req_params) + + def test_fluent_api_fails_with_invalid_notecard( + self, fluent_api, notecard_api, req_params, + run_fluent_api_invalid_notecard_test): + run_fluent_api_invalid_notecard_test(fluent_api, req_params) diff --git a/test/test_note.py b/test/test_note.py new file mode 100644 index 0000000..dd72e98 --- /dev/null +++ b/test/test_note.py @@ -0,0 +1,94 @@ +import pytest +from notecard import note + + +@pytest.mark.parametrize( + 'fluent_api,notecard_api,req_params,rename_map', + [ + ( + note.add, + 'note.add', + { + 'file': 'data.qo', + 'body': {'key_a:', 'val_a', 'key_b', 42}, + 'payload': 'ewogICJpbnRlcnZhbHMiOiI2MCwxMiwxNCIKfQ==', + 'sync': True + }, + None + ), + ( + note.changes, + 'note.changes', + { + 'file': 'my-settings.db', + 'tracker': 'inbound-tracker', + 'maximum': 2, + 'start': True, + 'stop': True, + 'delete': True, + 'deleted': True + }, + { + 'maximum': 'max' + } + ), + ( + note.delete, + 'note.delete', + { + 'file': 'my-settings.db', + 'note_id': 'my_note', + }, + { + 'note_id': 'note' + } + ), + ( + note.get, + 'note.get', + { + 'file': 'my-settings.db', + 'note_id': 'my_note', + 'delete': True, + 'deleted': True + }, + { + 'note_id': 'note' + } + ), + ( + note.template, + 'note.template', + { + 'file': 'my-settings.db', + 'body': {'key_a:', 'val_a', 'key_b', 42}, + 'length': 42 + }, + None + ), + ( + note.update, + 'note.update', + { + 'file': 'my-settings.db', + 'note_id': 'my_note', + 'body': {'key_a:', 'val_a', 'key_b', 42}, + 'payload': 'ewogICJpbnRlcnZhbHMiOiI2MCwxMiwxNCIKfQ==' + }, + { + 'note_id': 'note' + } + ) + ] +) +class TestNote: + def test_fluent_api_maps_notecard_api_correctly( + self, fluent_api, notecard_api, req_params, rename_map, + run_fluent_api_notecard_api_mapping_test): + run_fluent_api_notecard_api_mapping_test(fluent_api, notecard_api, + req_params, rename_map) + + def test_fluent_api_fails_with_invalid_notecard( + self, fluent_api, notecard_api, req_params, rename_map, + run_fluent_api_invalid_notecard_test): + run_fluent_api_invalid_notecard_test(fluent_api, req_params) diff --git a/test/test_notecard.py b/test/test_notecard.py index 1a31ad7..7bcd4b5 100644 --- a/test/test_notecard.py +++ b/test/test_notecard.py @@ -9,7 +9,6 @@ os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) import notecard # noqa: E402 -from notecard import card, hub, note, env, file # noqa: E402 def get_serial_and_port(): @@ -83,47 +82,11 @@ def test_command_fail_if_req(self): match="Please use 'cmd' instead of 'req'"): nCard.Command({"req": "card.sleep"}) - def test_hub_set(self): - nCard, port = self.get_port("{}\r\n") - - response = hub.set(nCard, - product="com.blues.tester", - sn="foo", - mode="continuous", - outbound=2, - inbound=60, - duration=5, - sync=True, - align=True, - voutbound="2.3", - vinbound="3.3", - host="http://hub.blues.foo") - - assert response == {} - def test_user_agent_sent_is_false_before_hub_set(self): nCard, _ = self.get_port() assert nCard.UserAgentSent() is False - def test_send_user_agent_in_hub_set_helper(self): - nCard, port = self.get_port("{}\r\n") - - hub.set(nCard, - product="com.blues.tester", - sn="foo", - mode="continuous", - outbound=2, - inbound=60, - duration=5, - sync=True, - align=True, - voutbound="2.3", - vinbound="3.3", - host="http://hub.blues.foo") - - assert nCard.UserAgentSent() is True - def test_send_user_agent_in_hub_set_transaction(self): nCard, port = self.get_port("{\"connected\":true}\r\n") @@ -131,249 +94,6 @@ def test_send_user_agent_in_hub_set_transaction(self): assert nCard.UserAgentSent() is True - def test_hub_set_invalid_card(self): - with pytest.raises(Exception, match="Notecard object required"): - hub.set(None, product="com.blues.tester") - - def test_hub_sync(self): - nCard, port = self.get_port("{}\r\n") - - response = hub.sync(nCard) - - assert response == {} - - def test_hub_sync_status(self): - nCard, port = self.get_port("{\"status\":\"connected\"}\r\n") - - response = hub.syncStatus(nCard, True) - - assert "status" in response - assert response["status"] == "connected" - - def test_hub_status(self): - nCard, port = self.get_port("{\"connected\":true}\r\n") - - response = hub.status(nCard) - - assert "connected" in response - assert response["connected"] is True - - def test_hub_log(self): - nCard, port = self.get_port("{}\r\n") - - response = hub.log(nCard, "there's been an issue!", False) - - assert response == {} - - def test_hub_get(self): - nCard, port = self.get_port("{\"mode\":\"continuous\"}\r\n") - - response = hub.get(nCard) - - assert "mode" in response - assert response["mode"] == "continuous" - - def test_card_time(self): - nCard, port = self.get_port("{\"time\":1592490375}\r\n") - - response = card.time(nCard) - - assert "time" in response - assert response["time"] == 1592490375 - - def test_card_status(self): - nCard, port = self.get_port( - "{\"usb\":true,\"status\":\"{normal}\"}\r\n") - - response = card.status(nCard) - - assert "status" in response - assert response["status"] == "{normal}" - - def test_card_temp(self): - nCard, port = self.get_port( - "{\"value\":33.625,\"calibration\":-3.0}\r\n") - - response = card.temp(nCard, minutes=20) - - assert "value" in response - assert response["value"] == 33.625 - - def test_card_attn(self): - nCard, port = self.get_port("{\"set\":true}\r\n") - - response = card.attn(nCard, - mode="arm, files", - files=["sensors.qo"], - seconds=10, - payload={"foo": "bar"}, - start=True) - - assert "set" in response - assert response["set"] is True - - def test_card_attn_with_invalid_card(self): - with pytest.raises(Exception, match="Notecard object required"): - card.attn(None, mode="arm") - - def test_card_voltage(self): - nCard, port = self.get_port("{\"hours\":707}\r\n") - - response = card.voltage(nCard, hours=24, offset=5, vmax=4, vmin=3) - - assert "hours" in response - assert response["hours"] == 707 - - def test_card_wireless(self): - nCard, port = self.get_port( - "{\"status\":\"{modem-off}\",\"count\":1}\r\n") - - response = card.wireless(nCard, mode="auto", apn="-") - - assert "status" in response - assert response["status"] == "{modem-off}" - - def test_card_version(self): - nCard, port = self.get_port( - "{\"version\":\"notecard-1.2.3.9950\"}\r\n") - - response = card.version(nCard) - - assert "version" in response - assert response["version"] == "notecard-1.2.3.9950" - - def test_note_add(self): - nCard, port = self.get_port("{\"total\":1}\r\n") - - response = note.add(nCard, - file="sensors.qo", - body={"temp": 72.22}, - payload="b64==", - sync=True) - - assert "total" in response - assert response["total"] == 1 - - def test_note_get(self): - nCard, port = self.get_port( - "{\"note\":\"s\",\"body\":{\"s\":\"foo\"}}\r\n") - - response = note.get(nCard, - file="settings.db", - note_id="s", - delete=True, - deleted=False) - - assert "note" in response - assert response["note"] == "s" - - def test_note_delete(self): - nCard, port = self.get_port("{}\r\n") - - response = note.delete(nCard, file="settings.db", note_id="s") - - assert response == {} - - def test_note_update(self): - nCard, port = self.get_port("{}\r\n") - - response = note.update(nCard, - file="settings.db", - note_id="s", - body={"foo": "bar"}, - payload="123dfb==") - - assert response == {} - - def test_note_changes(self): - nCard, port = self.get_port("{\"changes\":5,\"total\":15}\r\n") - - response = note.changes(nCard, - file="sensors.qo", - tracker="123", - maximum=10, - start=True, - stop=False, - deleted=False, - delete=True) - - assert "changes" in response - assert response["changes"] == 5 - - def test_note_template(self): - nCard, port = self.get_port("{\"bytes\":40}\r\n") - - response = note.template(nCard, - file="sensors.qo", - body={ - "temp": 1.1, - "hu": 1 - }, - length=5) - - assert "bytes" in response - assert response["bytes"] == 40 - - def test_env_default(self): - nCard, port = self.get_port("{}\r\n") - - response = env.default(nCard, name="pump", text="on") - - assert response == {} - - def test_env_set(self): - nCard, port = self.get_port("{}\r\n") - - response = env.set(nCard, name="pump", text="on") - - assert response == {} - - def test_env_get(self): - nCard, port = self.get_port("{}\r\n") - - response = env.get(nCard, name="pump") - - assert response == {} - - def test_env_modified(self): - nCard, port = self.get_port("{\"time\": 1605814493}\r\n") - - response = env.modified(nCard) - - assert "time" in response - assert response["time"] == 1605814493 - - def test_file_delete(self): - nCard, port = self.get_port("{}\r\n") - - response = file.delete(nCard, files=["sensors.qo"]) - - assert response == {} - - def test_file_changes(self): - nCard, port = self.get_port("{\"total\":5}\r\n") - - response = file.changes(nCard, tracker="123", files=["sensors.qo"]) - - assert "total" in response - assert response["total"] == 5 - - def test_file_stats(self): - nCard, port = self.get_port("{\"total\":24}\r\n") - - response = file.stats(nCard) - - assert "total" in response - assert response["total"] == 24 - - def test_file_pendingChanges(self): - nCard, port = self.get_port("{\"changes\":1}\r\n") - - response = file.pendingChanges(nCard) - - assert "changes" in response - assert response["changes"] == 1 - def get_port(self, response=None): raise NotImplementedError("subclasses must implement `get_port()`")