1- import json
1+ #
2+ # Copyright (c) 2025 NSONE, Inc.
3+ #
4+ # License under The MIT License (MIT). See LICENSE in project root.
5+ #
26import pytest
3- import responses
47
5- from ns1 .alerting import UsageAlertsAPI , USAGE_SUBTYPES
8+ try : # Python 3.3 +
9+ import unittest .mock as mock
10+ except ImportError :
11+ import mock
12+
13+ import json
614
715
816@pytest .fixture
9- def usage_alerts_client ():
17+ def usage_alerts_client (config ):
18+ config .loadFromDict (
19+ {
20+ "endpoint" : "api.nsone.net" ,
21+ "default_key" : "test1" ,
22+ "keys" : {
23+ "test1" : {
24+ "key" : "key-1" ,
25+ "desc" : "test key number 1" ,
26+ }
27+ },
28+ }
29+ )
1030 from ns1 import NS1
11- client = NS1 (apiKey = "test1" )
12- client .config ["endpoint" ] = "https://api.nsone.net"
31+ client = NS1 (config = config )
1332 return client
1433
1534
16- @responses .activate
1735def test_create_usage_alert (usage_alerts_client ):
1836 """Test creating a usage alert"""
1937 client = usage_alerts_client
2038
21- # Mock response for create
22- responses .add (
23- responses .POST ,
24- "https://api.nsone.net/alerting/v1/alerts" ,
25- json = {
26- "id" : "a1b2c3" ,
27- "name" : "Test Alert" ,
28- "type" : "account" ,
29- "subtype" : "query_usage" ,
30- "data" : {"alert_at_percent" : 85 },
31- "notifier_list_ids" : ["n1" ],
32- "zone_names" : [],
33- "created_at" : 1597937213 ,
34- "updated_at" : 1597937213
35- },
36- status = 200 ,
37- )
39+ # Create a mock for the _post method in alerting().usage
40+ client .alerting ()._c ._post = mock .MagicMock ()
41+ client .alerting ()._c ._post .return_value = {
42+ "id" : "a1b2c3" ,
43+ "name" : "Test Alert" ,
44+ "type" : "account" ,
45+ "subtype" : "query_usage" ,
46+ "data" : {"alert_at_percent" : 85 },
47+ "notifier_list_ids" : ["n1" ],
48+ "zone_names" : [],
49+ "created_at" : 1597937213 ,
50+ "updated_at" : 1597937213
51+ }
3852
3953 alert = client .alerting ().usage .create (
4054 name = "Test Alert" ,
@@ -43,139 +57,142 @@ def test_create_usage_alert(usage_alerts_client):
4357 notifier_list_ids = ["n1" ]
4458 )
4559
60+ # Verify _post was called with correct arguments
61+ expected_body = {
62+ "name" : "Test Alert" ,
63+ "type" : "account" ,
64+ "subtype" : "query_usage" ,
65+ "data" : {"alert_at_percent" : 85 },
66+ "notifier_list_ids" : ["n1" ],
67+ "zone_names" : []
68+ }
69+ client .alerting ()._c ._post .assert_called_once_with ("/alerting/v1/alerts" , json = expected_body )
70+
71+ # Verify result
4672 assert alert ["id" ] == "a1b2c3"
4773 assert alert ["name" ] == "Test Alert"
4874 assert alert ["type" ] == "account"
4975 assert alert ["subtype" ] == "query_usage"
5076 assert alert ["data" ]["alert_at_percent" ] == 85
51- assert alert ["notifier_list_ids" ] == ["n1" ]
5277
5378
54- @responses .activate
5579def test_get_usage_alert (usage_alerts_client ):
5680 """Test retrieving a usage alert"""
5781 client = usage_alerts_client
5882 alert_id = "a1b2c3"
5983
60- # Mock response for get
61- responses .add (
62- responses .GET ,
63- f"https://api.nsone.net/alerting/v1/alerts/{ alert_id } " ,
64- json = {
65- "id" : alert_id ,
66- "name" : "Test Alert" ,
67- "type" : "account" ,
68- "subtype" : "query_usage" ,
69- "data" : {"alert_at_percent" : 85 },
70- "notifier_list_ids" : ["n1" ],
71- "zone_names" : []
72- },
73- status = 200 ,
74- )
84+ # Create a mock for the _get method
85+ client .alerting ()._c ._get = mock .MagicMock ()
86+ client .alerting ()._c ._get .return_value = {
87+ "id" : alert_id ,
88+ "name" : "Test Alert" ,
89+ "type" : "account" ,
90+ "subtype" : "query_usage" ,
91+ "data" : {"alert_at_percent" : 85 },
92+ "notifier_list_ids" : ["n1" ],
93+ "zone_names" : []
94+ }
7595
7696 alert = client .alerting ().usage .get (alert_id )
7797
98+ # Verify _get was called with correct URL
99+ client .alerting ()._c ._get .assert_called_once_with (f"/alerting/v1/alerts/{ alert_id } " )
100+
101+ # Verify result
78102 assert alert ["id" ] == alert_id
79103 assert alert ["name" ] == "Test Alert"
80104 assert alert ["data" ]["alert_at_percent" ] == 85
81105
82106
83- @responses .activate
84107def test_patch_usage_alert (usage_alerts_client ):
85108 """Test patching a usage alert - verify type/subtype are not sent"""
86109 client = usage_alerts_client
87110 alert_id = "a1b2c3"
88111
89- def request_callback (request ):
90- payload = json .loads (request .body )
91- # Verify type and subtype are not in the request
92- assert "type" not in payload
93- assert "subtype" not in payload
94- # Verify data contains the right alert_at_percent
95- assert payload ["data" ]["alert_at_percent" ] == 90
96-
97- resp_body = {
98- "id" : alert_id ,
99- "name" : "Updated Alert" ,
100- "type" : "account" ,
101- "subtype" : "query_usage" ,
102- "data" : {"alert_at_percent" : 90 },
103- "notifier_list_ids" : ["n1" ],
104- "zone_names" : []
105- }
106- return (200 , {}, json .dumps (resp_body ))
107-
108- responses .add_callback (
109- responses .PATCH ,
110- f"https://api.nsone.net/alerting/v1/alerts/{ alert_id } " ,
111- callback = request_callback ,
112- content_type = "application/json" ,
113- )
112+ # Create a mock for the _patch method
113+ client .alerting ()._c ._patch = mock .MagicMock ()
114+ client .alerting ()._c ._patch .return_value = {
115+ "id" : alert_id ,
116+ "name" : "Updated Alert" ,
117+ "type" : "account" ,
118+ "subtype" : "query_usage" ,
119+ "data" : {"alert_at_percent" : 90 },
120+ "notifier_list_ids" : ["n1" ],
121+ "zone_names" : []
122+ }
114123
115124 alert = client .alerting ().usage .patch (
116125 alert_id ,
117126 name = "Updated Alert" ,
118127 alert_at_percent = 90
119128 )
120129
130+ # Verify _patch was called with correct arguments
131+ expected_body = {
132+ "name" : "Updated Alert" ,
133+ "data" : {"alert_at_percent" : 90 }
134+ }
135+ client .alerting ()._c ._patch .assert_called_once_with (f"/alerting/v1/alerts/{ alert_id } " , json = expected_body )
136+
137+ # Verify type/subtype are not in the arguments
138+ call_args = client .alerting ()._c ._patch .call_args [1 ]["json" ]
139+ assert "type" not in call_args
140+ assert "subtype" not in call_args
141+
142+ # Verify result
121143 assert alert ["id" ] == alert_id
122144 assert alert ["name" ] == "Updated Alert"
123145 assert alert ["data" ]["alert_at_percent" ] == 90
124146
125147
126- @responses .activate
127148def test_delete_usage_alert (usage_alerts_client ):
128149 """Test deleting a usage alert"""
129150 client = usage_alerts_client
130151 alert_id = "a1b2c3"
131152
132- responses .add (
133- responses .DELETE ,
134- f"https://api.nsone.net/alerting/v1/alerts/{ alert_id } " ,
135- status = 204 ,
136- )
153+ # Create a mock for the _delete method
154+ client .alerting ()._c ._delete = mock .MagicMock ()
137155
138156 client .alerting ().usage .delete (alert_id )
139157
140- # If we got here without exception, the test passes
158+ # Verify _delete was called with correct URL
159+ client .alerting ()._c ._delete .assert_called_once_with (f"/alerting/v1/alerts/{ alert_id } " )
141160
142161
143- @responses .activate
144162def test_list_usage_alerts (usage_alerts_client ):
145163 """Test listing usage alerts with pagination params"""
146164 client = usage_alerts_client
147165
148- responses .add (
149- responses .GET ,
150- "https://api.nsone.net/alerting/v1/alerts" ,
151- json = {
152- "limit" : 1 ,
153- "next" : "next_token" ,
154- "total_results" : 2 ,
155- "results" : [
156- {
157- "id" : "a1" ,
158- "name" : "Alert 1" ,
159- "type" : "account" ,
160- "subtype" : "query_usage" ,
161- "data" : {"alert_at_percent" : 80 }
162- }
163- ]
164- },
165- status = 200 ,
166- match = [
167- responses .matchers .query_param_matcher ({
168- "limit" : "1" ,
169- "order_descending" : "true"
170- })
166+ # Create a mock for the _get method
167+ client .alerting ()._c ._get = mock .MagicMock ()
168+ client .alerting ()._c ._get .return_value = {
169+ "limit" : 1 ,
170+ "next" : "next_token" ,
171+ "total_results" : 2 ,
172+ "results" : [
173+ {
174+ "id" : "a1" ,
175+ "name" : "Alert 1" ,
176+ "type" : "account" ,
177+ "subtype" : "query_usage" ,
178+ "data" : {"alert_at_percent" : 80 }
179+ }
171180 ]
172- )
181+ }
173182
174183 response = client .alerting ().usage .list (
175184 limit = 1 ,
176185 order_descending = True
177186 )
178187
188+ # Verify _get was called with correct URL and params
189+ expected_params = {
190+ "limit" : 1 ,
191+ "order_descending" : "true"
192+ }
193+ client .alerting ()._c ._get .assert_called_once_with ("/alerting/v1/alerts" , params = expected_params )
194+
195+ # Verify result
179196 assert "results" in response
180197 assert "next" in response
181198 assert response ["next" ] == "next_token"
@@ -215,51 +232,19 @@ def test_validation_threshold_bounds(usage_alerts_client):
215232 assert "alert_at_percent must be int in 1..100" in str (excinfo .value )
216233
217234
218- def test_validation_subtype (usage_alerts_client ):
235+ def test_validation_subtype ():
219236 """Test validation of subtype values"""
220- client = usage_alerts_client
237+ from ns1 .alerting import USAGE_SUBTYPES
238+ from ns1 .alerting .usage_alerts import _validate
221239
222- # Verify all defined subtypes are accepted
240+ # Valid subtypes should pass validation
223241 for subtype in USAGE_SUBTYPES :
224242 try :
225- # Just validate, don't make an actual request
226- client .alerting ().usage ._c = None # This will cause an error if validation passes
227- with pytest .raises (AttributeError ):
228- client .alerting ().usage .create (
229- name = "Test Alert" ,
230- subtype = subtype ,
231- alert_at_percent = 85
232- )
243+ _validate ("Test Alert" , subtype , 85 )
233244 except ValueError :
234245 pytest .fail (f"Valid subtype '{ subtype } ' was rejected" )
235246
236- # Test invalid subtype
247+ # Invalid subtype should fail validation
237248 with pytest .raises (ValueError ) as excinfo :
238- client .alerting ().usage .create (
239- name = "Test Alert" ,
240- subtype = "invalid_subtype" ,
241- alert_at_percent = 85
242- )
249+ _validate ("Test Alert" , "invalid_subtype" , 85 )
243250 assert "invalid subtype" in str (excinfo .value )
244-
245-
246- @responses .activate
247- def test_404_error_handling (usage_alerts_client ):
248- """Test proper error handling for 404 responses"""
249- client = usage_alerts_client
250- alert_id = "nonexistent"
251-
252- # Mock 404 response with error message
253- responses .add (
254- responses .GET ,
255- f"https://api.nsone.net/alerting/v1/alerts/{ alert_id } " ,
256- json = {"message" : "alert not found" },
257- status = 404 ,
258- )
259-
260- # This should raise an exception through the REST transport
261- with pytest .raises (Exception ) as excinfo :
262- client .alerting ().usage .get (alert_id )
263-
264- # Verify error contains the message from the server
265- assert "alert not found" in str (excinfo .value )
0 commit comments