lvi-api-client 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {lvi_api_client-0.2.0 → lvi_api_client-0.2.2}/PKG-INFO +1 -1
- lvi_api_client-0.2.2/VERSION +1 -0
- {lvi_api_client-0.2.0 → lvi_api_client-0.2.2}/src/logicvein/lvi_api_client.py +210 -14
- {lvi_api_client-0.2.0 → lvi_api_client-0.2.2}/src/lvi_api_client.egg-info/PKG-INFO +1 -1
- {lvi_api_client-0.2.0 → lvi_api_client-0.2.2}/tests/test_server_request_shapes.py +138 -0
- lvi_api_client-0.2.0/VERSION +0 -1
- {lvi_api_client-0.2.0 → lvi_api_client-0.2.2}/LICENSE +0 -0
- {lvi_api_client-0.2.0 → lvi_api_client-0.2.2}/README.md +0 -0
- {lvi_api_client-0.2.0 → lvi_api_client-0.2.2}/pyproject.toml +0 -0
- {lvi_api_client-0.2.0 → lvi_api_client-0.2.2}/setup.cfg +0 -0
- {lvi_api_client-0.2.0 → lvi_api_client-0.2.2}/src/logicvein/__init__.py +0 -0
- {lvi_api_client-0.2.0 → lvi_api_client-0.2.2}/src/lvi_api_client.egg-info/SOURCES.txt +0 -0
- {lvi_api_client-0.2.0 → lvi_api_client-0.2.2}/src/lvi_api_client.egg-info/dependency_links.txt +0 -0
- {lvi_api_client-0.2.0 → lvi_api_client-0.2.2}/src/lvi_api_client.egg-info/requires.txt +0 -0
- {lvi_api_client-0.2.0 → lvi_api_client-0.2.2}/src/lvi_api_client.egg-info/top_level.txt +0 -0
- {lvi_api_client-0.2.0 → lvi_api_client-0.2.2}/tests/test_lvi_api_client.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.2.2
|
|
@@ -2907,8 +2907,14 @@ class LogicVeinAPIClient:
|
|
|
2907
2907
|
time_zone: str = "Etc/GMT",
|
|
2908
2908
|
paused: bool = False,
|
|
2909
2909
|
filter_name: str = "",
|
|
2910
|
+
trigger_id: Optional[int] = None,
|
|
2910
2911
|
) -> Optional[Dict[str, Any]]:
|
|
2911
|
-
"""Schedule a trigger for an existing job.
|
|
2912
|
+
"""Schedule a trigger for an existing job, or change one it has.
|
|
2913
|
+
|
|
2914
|
+
Without ``trigger_id`` this always adds a trigger, even one identical to
|
|
2915
|
+
a trigger the job already has, so each repeat adds another schedule. Use
|
|
2916
|
+
:meth:`get_job_triggers` to check first. With ``trigger_id`` it updates
|
|
2917
|
+
that trigger in place, and every field is set from these arguments.
|
|
2912
2918
|
|
|
2913
2919
|
For a one-time run, pass an ISO datetime string as ``cron_expression``
|
|
2914
2920
|
(e.g. ``"2026-03-17T09:00"``). Standard cron syntax works for recurring
|
|
@@ -2918,29 +2924,65 @@ class LogicVeinAPIClient:
|
|
|
2918
2924
|
job_id (int): ID of the job to schedule (returned by :meth:`save_job`).
|
|
2919
2925
|
trigger_name (str): Display name for the trigger.
|
|
2920
2926
|
cron_expression (str): ISO datetime (one-time) or cron expression (recurring).
|
|
2921
|
-
time_zone (str): Timezone name (e.g. ``"America/Halifax"``). Default ``"
|
|
2927
|
+
time_zone (str): Timezone name (e.g. ``"America/Halifax"``). Default ``"Etc/GMT"``.
|
|
2922
2928
|
paused (bool): Whether to create the trigger in a paused state.
|
|
2923
2929
|
filter_name (str): Optional filter name; pass empty string for none.
|
|
2930
|
+
trigger_id (Optional[int]): ID of the trigger to update, or None to add one.
|
|
2924
2931
|
|
|
2925
2932
|
Returns:
|
|
2926
2933
|
Dict[str, Any]: The saved trigger record (contains ``triggerId``,
|
|
2927
2934
|
``nextExpectedFireTime``, etc.), or None on failure.
|
|
2928
2935
|
"""
|
|
2929
|
-
|
|
2930
|
-
"
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
)
|
|
2936
|
+
trigger_data: Dict[str, Any] = {
|
|
2937
|
+
"triggerName": trigger_name,
|
|
2938
|
+
"paused": paused,
|
|
2939
|
+
"cronExpression": cron_expression,
|
|
2940
|
+
"timeZone": time_zone,
|
|
2941
|
+
"filterName": filter_name,
|
|
2942
|
+
"jobId": job_id,
|
|
2943
|
+
}
|
|
2944
|
+
if trigger_id is not None:
|
|
2945
|
+
trigger_data["triggerId"] = trigger_id
|
|
2946
|
+
response = self.call("Scheduler.saveTrigger", triggerData=trigger_data)
|
|
2940
2947
|
if response and isinstance(response, dict) and "result" in response:
|
|
2941
2948
|
return response["result"]
|
|
2942
2949
|
return None
|
|
2943
2950
|
|
|
2951
|
+
def get_job_triggers(self, job_id: int) -> List[Dict[str, Any]]:
|
|
2952
|
+
"""Get the triggers of a job.
|
|
2953
|
+
|
|
2954
|
+
Args:
|
|
2955
|
+
job_id (int): ID of the job. An unknown job has no triggers.
|
|
2956
|
+
|
|
2957
|
+
Returns:
|
|
2958
|
+
List[Dict[str, Any]]: The trigger records (``triggerId``, ``jobId``,
|
|
2959
|
+
``triggerName``, ``cronExpression``, ``timeZone``, ``paused``,
|
|
2960
|
+
``filterName``, ``nextExpectedFireTime``, ``accessLimited``). The list
|
|
2961
|
+
is empty only when the server says the job has none.
|
|
2962
|
+
|
|
2963
|
+
Raises:
|
|
2964
|
+
ValueError: If the server's answer is not a list. Callers check this list
|
|
2965
|
+
before adding a trigger, so a failed read must not look like no triggers.
|
|
2966
|
+
"""
|
|
2967
|
+
response = self.call("Scheduler.getTriggersOfJob", jobId=job_id)
|
|
2968
|
+
triggers = response.get("result") if isinstance(response, dict) else None
|
|
2969
|
+
if not isinstance(triggers, list):
|
|
2970
|
+
raise ValueError(f"Scheduler.getTriggersOfJob did not return a list for job {job_id}")
|
|
2971
|
+
return triggers
|
|
2972
|
+
|
|
2973
|
+
def delete_trigger(self, trigger_id: int) -> bool:
|
|
2974
|
+
"""Delete a trigger.
|
|
2975
|
+
|
|
2976
|
+
Args:
|
|
2977
|
+
trigger_id (int): ID of the trigger to delete.
|
|
2978
|
+
|
|
2979
|
+
Returns:
|
|
2980
|
+
bool: The server's answer. It answers True for a trigger that is
|
|
2981
|
+
already gone, so this does not show that one existed.
|
|
2982
|
+
"""
|
|
2983
|
+
response = self.call("Scheduler.deleteTrigger", triggerId=trigger_id)
|
|
2984
|
+
return bool(response and isinstance(response, dict) and response.get("result"))
|
|
2985
|
+
|
|
2944
2986
|
def create_command_runner_job(
|
|
2945
2987
|
self,
|
|
2946
2988
|
device_ip: str,
|
|
@@ -3845,6 +3887,10 @@ class LogicVeinAPIClient:
|
|
|
3845
3887
|
Calls Collection.addMonitorsToSet. The setId must be provided both as a top-level
|
|
3846
3888
|
parameter and within each monitor object. Returns the saved monitors with assigned IDs.
|
|
3847
3889
|
|
|
3890
|
+
This always adds: the server stores a new monitor even for a name already in the
|
|
3891
|
+
set, or with the ID of an existing monitor, and leaves the old one as it was. Use
|
|
3892
|
+
:meth:`save_monitor` to change one.
|
|
3893
|
+
|
|
3848
3894
|
Each monitor object should have:
|
|
3849
3895
|
{
|
|
3850
3896
|
"monitorId": int - Use -1 for new monitors; assigned ID returned in response,
|
|
@@ -3863,7 +3909,7 @@ class LogicVeinAPIClient:
|
|
|
3863
3909
|
|
|
3864
3910
|
Args:
|
|
3865
3911
|
set_id (int): The monitor set ID to add monitors to.
|
|
3866
|
-
monitors (List[Dict[str, Any]]): List of monitor objects to add
|
|
3912
|
+
monitors (List[Dict[str, Any]]): List of monitor objects to add.
|
|
3867
3913
|
|
|
3868
3914
|
Returns:
|
|
3869
3915
|
List[Dict[str, Any]]: List of saved monitor objects with assigned monitorIds.
|
|
@@ -3894,6 +3940,156 @@ class LogicVeinAPIClient:
|
|
|
3894
3940
|
return True
|
|
3895
3941
|
return False
|
|
3896
3942
|
|
|
3943
|
+
def create_monitor_set(
|
|
3944
|
+
self, set_name: str, managed_networks: List[str], smart_set: bool = False
|
|
3945
|
+
) -> Optional[Dict[str, Any]]:
|
|
3946
|
+
"""Create a monitor set.
|
|
3947
|
+
|
|
3948
|
+
A name already taken in those networks makes the server answer HTTP 500, which
|
|
3949
|
+
raises, and stores nothing.
|
|
3950
|
+
|
|
3951
|
+
Args:
|
|
3952
|
+
set_name (str): The set's name. Use :meth:`is_monitor_set_name_unique` to check it first.
|
|
3953
|
+
managed_networks (List[str]): The networks the set belongs to, at least one.
|
|
3954
|
+
smart_set (bool): Whether this is a smart set.
|
|
3955
|
+
|
|
3956
|
+
Returns:
|
|
3957
|
+
Dict[str, Any]: The saved set (``setId``, ``setName``, ``smartSet``,
|
|
3958
|
+
``managedNetworks``), or None on failure.
|
|
3959
|
+
|
|
3960
|
+
Raises:
|
|
3961
|
+
ValueError: If no network is given. An empty list was not tried against a
|
|
3962
|
+
server, as a failed rule set save with no networks leaves a row that
|
|
3963
|
+
cannot be removed.
|
|
3964
|
+
"""
|
|
3965
|
+
if not managed_networks:
|
|
3966
|
+
raise ValueError("A monitor set needs at least one managed network")
|
|
3967
|
+
return self._save_monitor_set(
|
|
3968
|
+
{"setId": -1, "setName": set_name, "smartSet": smart_set, "managedNetworks": managed_networks}
|
|
3969
|
+
)
|
|
3970
|
+
|
|
3971
|
+
def update_monitor_set(
|
|
3972
|
+
self,
|
|
3973
|
+
set_id: int,
|
|
3974
|
+
set_name: Optional[str] = None,
|
|
3975
|
+
managed_networks: Optional[List[str]] = None,
|
|
3976
|
+
smart_set: Optional[bool] = None,
|
|
3977
|
+
) -> Optional[Dict[str, Any]]:
|
|
3978
|
+
"""Change a monitor set, keeping what is not given.
|
|
3979
|
+
|
|
3980
|
+
Args:
|
|
3981
|
+
set_id (int): The ID of the set.
|
|
3982
|
+
set_name (Optional[str]): A new name, or None to keep it.
|
|
3983
|
+
managed_networks (Optional[List[str]]): New networks, at least one, or None to keep them.
|
|
3984
|
+
smart_set (Optional[bool]): Whether it is a smart set, or None to keep it.
|
|
3985
|
+
|
|
3986
|
+
Returns:
|
|
3987
|
+
Dict[str, Any]: The saved set, or None on failure.
|
|
3988
|
+
|
|
3989
|
+
Raises:
|
|
3990
|
+
ValueError: If the set does not exist, or an empty list of networks is given.
|
|
3991
|
+
"""
|
|
3992
|
+
if managed_networks is not None and not managed_networks:
|
|
3993
|
+
raise ValueError("A monitor set needs at least one managed network")
|
|
3994
|
+
monitor_set = self.get_monitor_set(set_id)
|
|
3995
|
+
if not monitor_set:
|
|
3996
|
+
raise ValueError(f"Monitor set {set_id} does not exist")
|
|
3997
|
+
changes = {"setName": set_name, "managedNetworks": managed_networks, "smartSet": smart_set}
|
|
3998
|
+
return self._save_monitor_set({**monitor_set, **{k: v for k, v in changes.items() if v is not None}})
|
|
3999
|
+
|
|
4000
|
+
def _save_monitor_set(self, monitor_set: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
4001
|
+
"""Save a monitor set record.
|
|
4002
|
+
|
|
4003
|
+
Args:
|
|
4004
|
+
monitor_set (Dict[str, Any]): The whole record. A ``setId`` of -1 creates a set.
|
|
4005
|
+
|
|
4006
|
+
Returns:
|
|
4007
|
+
Dict[str, Any]: The saved set, or None on failure.
|
|
4008
|
+
"""
|
|
4009
|
+
response = self.call("Collection.saveMonitorSet", monitorSet=monitor_set)
|
|
4010
|
+
if response and isinstance(response, dict) and "result" in response:
|
|
4011
|
+
return response["result"]
|
|
4012
|
+
return None
|
|
4013
|
+
|
|
4014
|
+
def delete_monitor_sets(self, set_ids: List[int]) -> bool:
|
|
4015
|
+
"""Delete monitor sets, with their monitors.
|
|
4016
|
+
|
|
4017
|
+
This also takes those monitors off the devices they were deployed to. The server
|
|
4018
|
+
answers success for a set that does not exist, so True does not show that one did.
|
|
4019
|
+
|
|
4020
|
+
Args:
|
|
4021
|
+
set_ids (List[int]): The IDs of the sets to delete.
|
|
4022
|
+
|
|
4023
|
+
Returns:
|
|
4024
|
+
bool: True if successful (result is null), otherwise False.
|
|
4025
|
+
"""
|
|
4026
|
+
response = self.call("Collection.deleteMonitorSets", setIds=set_ids)
|
|
4027
|
+
return bool(response and isinstance(response, dict) and "result" in response and response["result"] is None)
|
|
4028
|
+
|
|
4029
|
+
def is_monitor_set_name_unique(self, set_name: str, managed_networks: List[str], ignore_set_id: int = -1) -> bool:
|
|
4030
|
+
"""Check whether a monitor set name is free in some networks.
|
|
4031
|
+
|
|
4032
|
+
Args:
|
|
4033
|
+
set_name (str): The name to check.
|
|
4034
|
+
managed_networks (List[str]): The networks to check it in.
|
|
4035
|
+
ignore_set_id (int): A set to leave out, such as the one being renamed. -1 for none.
|
|
4036
|
+
|
|
4037
|
+
Returns:
|
|
4038
|
+
bool: True if the name is free.
|
|
4039
|
+
|
|
4040
|
+
Raises:
|
|
4041
|
+
ValueError: If the server's answer is not true or false.
|
|
4042
|
+
"""
|
|
4043
|
+
response = self.call(
|
|
4044
|
+
"Collection.isMonitorSetNameUnique",
|
|
4045
|
+
setName=set_name,
|
|
4046
|
+
managedNetworks=managed_networks,
|
|
4047
|
+
ignoreSetId=ignore_set_id,
|
|
4048
|
+
)
|
|
4049
|
+
unique = response.get("result") if isinstance(response, dict) else None
|
|
4050
|
+
if not isinstance(unique, bool):
|
|
4051
|
+
raise ValueError(f"Collection.isMonitorSetNameUnique did not answer true or false for {set_name}")
|
|
4052
|
+
return unique
|
|
4053
|
+
|
|
4054
|
+
def save_monitor(self, monitor: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
4055
|
+
"""Update a monitor in place.
|
|
4056
|
+
|
|
4057
|
+
The monitor keeps its ID, and a device it is deployed to gets the change.
|
|
4058
|
+
|
|
4059
|
+
Args:
|
|
4060
|
+
monitor (Dict[str, Any]): The whole record as :meth:`get_monitors_for_set` returns
|
|
4061
|
+
it, with the changes made.
|
|
4062
|
+
|
|
4063
|
+
Returns:
|
|
4064
|
+
Dict[str, Any]: The saved monitor, or None on failure.
|
|
4065
|
+
"""
|
|
4066
|
+
response = self.call("Collection.saveMonitor", monitor=monitor)
|
|
4067
|
+
if response and isinstance(response, dict) and "result" in response:
|
|
4068
|
+
return response["result"]
|
|
4069
|
+
return None
|
|
4070
|
+
|
|
4071
|
+
def get_monitors_for_host(self, device_uuid: str, network: str) -> List[Dict[str, Any]]:
|
|
4072
|
+
"""Get the monitors deployed to a device.
|
|
4073
|
+
|
|
4074
|
+
Args:
|
|
4075
|
+
device_uuid (str): UUID of the device.
|
|
4076
|
+
network (str): Network the device belongs to.
|
|
4077
|
+
|
|
4078
|
+
Returns:
|
|
4079
|
+
List[Dict[str, Any]]: The monitors, each with ``monitorId``, ``monitorName`` and
|
|
4080
|
+
``setId``. The list is empty only when the server says the device has none,
|
|
4081
|
+
which it also says for a device that does not exist.
|
|
4082
|
+
|
|
4083
|
+
Raises:
|
|
4084
|
+
ValueError: If the server's answer is not a list. Callers check this list before
|
|
4085
|
+
deploying, so a failed read must not look like no monitors.
|
|
4086
|
+
"""
|
|
4087
|
+
response = self.call("Collection.getMonitorsForHost", uuid=device_uuid, network=network)
|
|
4088
|
+
monitors = response.get("result") if isinstance(response, dict) else None
|
|
4089
|
+
if not isinstance(monitors, list):
|
|
4090
|
+
raise ValueError(f"Collection.getMonitorsForHost did not return a list for device {device_uuid}")
|
|
4091
|
+
return monitors
|
|
4092
|
+
|
|
3897
4093
|
def validate_monitor_sets_against_device(
|
|
3898
4094
|
self, monitor_set_ids: List[int], host_queries: List[str]
|
|
3899
4095
|
) -> Dict[str, Any]:
|
|
@@ -281,6 +281,144 @@ XML = (
|
|
|
281
281
|
SAVED = {"Compliance.saveRuleSet": {"ruleSetId": 5, "ruleSetName": "rs1"}}
|
|
282
282
|
|
|
283
283
|
|
|
284
|
+
class TestTriggers:
|
|
285
|
+
TRIGGER = {
|
|
286
|
+
"triggerName": "t1",
|
|
287
|
+
"paused": True,
|
|
288
|
+
"cronExpression": "0 2 * * *",
|
|
289
|
+
"timeZone": "Etc/GMT",
|
|
290
|
+
"filterName": "",
|
|
291
|
+
"jobId": 7,
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
def save(self, connect, **extra):
|
|
295
|
+
client, server = connect({"Scheduler.saveTrigger": {"triggerId": 3}})
|
|
296
|
+
saved = client.save_trigger(job_id=7, trigger_name="t1", cron_expression="0 2 * * *", paused=True, **extra)
|
|
297
|
+
return saved, server.sent("Scheduler.saveTrigger")
|
|
298
|
+
|
|
299
|
+
def test_a_save_without_an_id_sends_none_so_the_server_adds_one(self, connect):
|
|
300
|
+
saved, sent = self.save(connect)
|
|
301
|
+
assert saved == {"triggerId": 3}
|
|
302
|
+
assert sent == {"triggerData": self.TRIGGER}
|
|
303
|
+
|
|
304
|
+
def test_a_save_with_an_id_sends_it_so_the_server_updates_that_trigger(self, connect):
|
|
305
|
+
_, sent = self.save(connect, trigger_id=3)
|
|
306
|
+
assert sent == {"triggerData": {**self.TRIGGER, "triggerId": 3}}
|
|
307
|
+
|
|
308
|
+
def test_triggers_of_a_job_are_read_by_job_id(self, connect):
|
|
309
|
+
rows = [{"triggerId": 3, "triggerName": "t1"}]
|
|
310
|
+
client, server = connect({"Scheduler.getTriggersOfJob": rows})
|
|
311
|
+
assert client.get_job_triggers(7) == rows
|
|
312
|
+
assert server.sent("Scheduler.getTriggersOfJob") == {"jobId": 7}
|
|
313
|
+
|
|
314
|
+
def test_a_job_with_no_triggers_gives_an_empty_list(self, connect):
|
|
315
|
+
client, _ = connect({"Scheduler.getTriggersOfJob": []})
|
|
316
|
+
assert client.get_job_triggers(7) == []
|
|
317
|
+
|
|
318
|
+
@pytest.mark.parametrize("answer", [None, {"result": None}, {}, "<html>error</html>"])
|
|
319
|
+
def test_an_answer_that_is_not_a_list_raises_rather_than_reading_as_no_triggers(self, connect, answer):
|
|
320
|
+
client, _ = connect()
|
|
321
|
+
with patch.object(client, "call", return_value=answer):
|
|
322
|
+
with pytest.raises(ValueError):
|
|
323
|
+
client.get_job_triggers(7)
|
|
324
|
+
|
|
325
|
+
@pytest.mark.parametrize("answer", [True, False])
|
|
326
|
+
def test_a_delete_is_by_trigger_id_and_returns_the_answer(self, connect, answer):
|
|
327
|
+
client, server = connect({"Scheduler.deleteTrigger": answer})
|
|
328
|
+
assert client.delete_trigger(3) is answer
|
|
329
|
+
assert server.sent("Scheduler.deleteTrigger") == {"triggerId": 3}
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
class TestMonitorSets:
|
|
333
|
+
SET = {"setId": 5, "setName": "s1", "smartSet": False, "managedNetworks": ["n1"]}
|
|
334
|
+
|
|
335
|
+
def test_a_new_set_is_saved_with_an_id_of_minus_one(self, connect):
|
|
336
|
+
client, server = connect({"Collection.saveMonitorSet": self.SET})
|
|
337
|
+
assert client.create_monitor_set("s1", ["n1"]) == self.SET
|
|
338
|
+
assert server.sent("Collection.saveMonitorSet") == {
|
|
339
|
+
"monitorSet": {"setId": -1, "setName": "s1", "smartSet": False, "managedNetworks": ["n1"]}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
def test_a_new_smart_set_says_so(self, connect):
|
|
343
|
+
client, server = connect({"Collection.saveMonitorSet": self.SET})
|
|
344
|
+
client.create_monitor_set("s1", ["n1"], smart_set=True)
|
|
345
|
+
assert server.sent("Collection.saveMonitorSet")["monitorSet"]["smartSet"] is True
|
|
346
|
+
|
|
347
|
+
def test_a_new_set_with_no_networks_sends_nothing(self, connect):
|
|
348
|
+
client, server = connect()
|
|
349
|
+
with pytest.raises(ValueError):
|
|
350
|
+
client.create_monitor_set("s1", [])
|
|
351
|
+
assert server.requests == []
|
|
352
|
+
|
|
353
|
+
def test_an_update_keeps_what_it_is_not_given(self, connect):
|
|
354
|
+
client, server = connect({"Collection.getMonitorSet": self.SET, "Collection.saveMonitorSet": self.SET})
|
|
355
|
+
client.update_monitor_set(5, set_name="s2")
|
|
356
|
+
assert server.sent("Collection.saveMonitorSet") == {"monitorSet": {**self.SET, "setName": "s2"}}
|
|
357
|
+
|
|
358
|
+
def test_an_update_can_clear_smart_set(self, connect):
|
|
359
|
+
stored = {**self.SET, "smartSet": True}
|
|
360
|
+
client, server = connect({"Collection.getMonitorSet": stored, "Collection.saveMonitorSet": self.SET})
|
|
361
|
+
client.update_monitor_set(5, smart_set=False)
|
|
362
|
+
assert server.sent("Collection.saveMonitorSet")["monitorSet"]["smartSet"] is False
|
|
363
|
+
|
|
364
|
+
@pytest.mark.parametrize(
|
|
365
|
+
"stored, args",
|
|
366
|
+
[
|
|
367
|
+
pytest.param(None, {"set_name": "s2"}, id="no such set"),
|
|
368
|
+
pytest.param(SET, {"managed_networks": []}, id="no networks"),
|
|
369
|
+
],
|
|
370
|
+
)
|
|
371
|
+
def test_an_update_that_cannot_apply_saves_nothing(self, connect, stored, args):
|
|
372
|
+
client, server = connect({"Collection.getMonitorSet": stored})
|
|
373
|
+
with pytest.raises(ValueError):
|
|
374
|
+
client.update_monitor_set(5, **args)
|
|
375
|
+
assert "Collection.saveMonitorSet" not in [name for name, _ in server.requests]
|
|
376
|
+
|
|
377
|
+
@pytest.mark.parametrize("answer, expected", [(None, True), (False, False)])
|
|
378
|
+
def test_a_delete_is_by_set_ids_and_null_is_success(self, connect, answer, expected):
|
|
379
|
+
client, server = connect({"Collection.deleteMonitorSets": answer})
|
|
380
|
+
assert client.delete_monitor_sets([5, 6]) is expected
|
|
381
|
+
assert server.sent("Collection.deleteMonitorSets") == {"setIds": [5, 6]}
|
|
382
|
+
|
|
383
|
+
@pytest.mark.parametrize("answer", [True, False])
|
|
384
|
+
def test_a_name_check_sends_its_scope_and_returns_the_answer(self, connect, answer):
|
|
385
|
+
client, server = connect({"Collection.isMonitorSetNameUnique": answer})
|
|
386
|
+
assert client.is_monitor_set_name_unique("s1", ["n1"], ignore_set_id=5) is answer
|
|
387
|
+
assert server.sent("Collection.isMonitorSetNameUnique") == {
|
|
388
|
+
"setName": "s1",
|
|
389
|
+
"managedNetworks": ["n1"],
|
|
390
|
+
"ignoreSetId": 5,
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
def test_a_name_check_with_no_answer_raises_rather_than_reading_as_taken(self, connect):
|
|
394
|
+
client, _ = connect({"Collection.isMonitorSetNameUnique": None})
|
|
395
|
+
with pytest.raises(ValueError):
|
|
396
|
+
client.is_monitor_set_name_unique("s1", ["n1"])
|
|
397
|
+
|
|
398
|
+
def test_a_monitor_is_saved_as_a_whole_record(self, connect):
|
|
399
|
+
monitor = {"monitorId": 3, "monitorName": "m1", "setId": 5, "json": {"port": 22}}
|
|
400
|
+
client, server = connect({"Collection.saveMonitor": monitor})
|
|
401
|
+
assert client.save_monitor(monitor) == monitor
|
|
402
|
+
assert server.sent("Collection.saveMonitor") == {"monitor": monitor}
|
|
403
|
+
|
|
404
|
+
def test_a_devices_monitors_are_read_by_uuid_and_network(self, connect):
|
|
405
|
+
rows = [{"monitorId": 3, "monitorName": "m1", "setId": 5}]
|
|
406
|
+
client, server = connect({"Collection.getMonitorsForHost": rows})
|
|
407
|
+
assert client.get_monitors_for_host("u1", "n1") == rows
|
|
408
|
+
assert server.sent("Collection.getMonitorsForHost") == {"uuid": "u1", "network": "n1"}
|
|
409
|
+
|
|
410
|
+
def test_a_device_with_no_monitors_gives_an_empty_list(self, connect):
|
|
411
|
+
client, _ = connect({"Collection.getMonitorsForHost": []})
|
|
412
|
+
assert client.get_monitors_for_host("u1", "n1") == []
|
|
413
|
+
|
|
414
|
+
@pytest.mark.parametrize("answer", [None, {"result": None}, {}, "<html>error</html>"])
|
|
415
|
+
def test_an_answer_that_is_not_a_list_raises_rather_than_reading_as_no_monitors(self, connect, answer):
|
|
416
|
+
client, _ = connect()
|
|
417
|
+
with patch.object(client, "call", return_value=answer):
|
|
418
|
+
with pytest.raises(ValueError):
|
|
419
|
+
client.get_monitors_for_host("u1", "n1")
|
|
420
|
+
|
|
421
|
+
|
|
284
422
|
class TestCreateRuleSet:
|
|
285
423
|
def test_an_agreeing_document_is_sent_unchanged(self, connect):
|
|
286
424
|
client, server = connect(SAVED)
|
lvi_api_client-0.2.0/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.2.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lvi_api_client-0.2.0 → lvi_api_client-0.2.2}/src/lvi_api_client.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|