@@ -39,10 +39,13 @@ def decrypt(self, packet, padding_obj):
3939 blob_b64 = base64 .b64encode (blob ).decode ('utf-8' ).rstrip ('=' )
4040
4141 # Mock the endpoints used by get_locations:
42+ client .access_token = "dummy-token"
43+ # Ensure session is created before entering aioresponses context
44+ await client ._ensure_session ()
45+
4246 with aioresponses () as m :
4347 m .put ("https://fmd.example.com/api/v1/locationDataSize" , payload = {"Data" : "1" })
4448 m .put ("https://fmd.example.com/api/v1/location" , payload = {"Data" : blob_b64 })
45- client .access_token = "dummy-token"
4649 try :
4750 locations = await client .get_locations (num_to_get = 1 )
4851 assert len (locations ) == 1
@@ -94,3 +97,207 @@ async def test_export_data_zip_stream(monkeypatch, tmp_path):
9497 assert content .startswith (b'PK\x03 \x04 ' )
9598 finally :
9699 await client .close ()
100+
101+ @pytest .mark .asyncio
102+ async def test_take_picture_validation ():
103+ """Test take_picture validates camera parameter."""
104+ client = FmdClient ("https://fmd.example.com" )
105+ client .access_token = "token"
106+ class DummySigner :
107+ def sign (self , message_bytes , pad , algo ):
108+ return b"\xAB " * 64
109+ client .private_key = DummySigner ()
110+
111+ with aioresponses () as m :
112+ # Valid cameras should work
113+ m .post ("https://fmd.example.com/api/v1/command" , status = 200 , body = "OK" )
114+ m .post ("https://fmd.example.com/api/v1/command" , status = 200 , body = "OK" )
115+ try :
116+ assert await client .take_picture ("front" ) is True
117+ assert await client .take_picture ("back" ) is True
118+ finally :
119+ await client .close ()
120+
121+ # Invalid camera should raise ValueError
122+ client2 = FmdClient ("https://fmd.example.com" )
123+ client2 .access_token = "token"
124+ client2 .private_key = DummySigner ()
125+ try :
126+ with pytest .raises (ValueError , match = "Invalid camera.*Must be 'front' or 'back'" ):
127+ await client2 .take_picture ("rear" )
128+ finally :
129+ await client2 .close ()
130+
131+ @pytest .mark .asyncio
132+ async def test_set_ringer_mode_validation ():
133+ """Test set_ringer_mode validates mode parameter."""
134+ client = FmdClient ("https://fmd.example.com" )
135+ client .access_token = "token"
136+ class DummySigner :
137+ def sign (self , message_bytes , pad , algo ):
138+ return b"\xAB " * 64
139+ client .private_key = DummySigner ()
140+
141+ with aioresponses () as m :
142+ # Valid modes should work
143+ m .post ("https://fmd.example.com/api/v1/command" , status = 200 , body = "OK" )
144+ m .post ("https://fmd.example.com/api/v1/command" , status = 200 , body = "OK" )
145+ m .post ("https://fmd.example.com/api/v1/command" , status = 200 , body = "OK" )
146+ try :
147+ assert await client .set_ringer_mode ("normal" ) is True
148+ assert await client .set_ringer_mode ("vibrate" ) is True
149+ assert await client .set_ringer_mode ("silent" ) is True
150+ finally :
151+ await client .close ()
152+
153+ # Invalid mode should raise ValueError
154+ client2 = FmdClient ("https://fmd.example.com" )
155+ client2 .access_token = "token"
156+ client2 .private_key = DummySigner ()
157+ try :
158+ with pytest .raises (ValueError , match = "Invalid ringer mode.*Must be" ):
159+ await client2 .set_ringer_mode ("loud" )
160+ finally :
161+ await client2 .close ()
162+
163+ @pytest .mark .asyncio
164+ async def test_request_location_providers ():
165+ """Test request_location with different providers."""
166+ client = FmdClient ("https://fmd.example.com" )
167+ client .access_token = "token"
168+ class DummySigner :
169+ def sign (self , message_bytes , pad , algo ):
170+ return b"\xAB " * 64
171+ client .private_key = DummySigner ()
172+
173+ with aioresponses () as m :
174+ # Mock all provider requests
175+ m .post ("https://fmd.example.com/api/v1/command" , status = 200 , body = "OK" )
176+ m .post ("https://fmd.example.com/api/v1/command" , status = 200 , body = "OK" )
177+ m .post ("https://fmd.example.com/api/v1/command" , status = 200 , body = "OK" )
178+ m .post ("https://fmd.example.com/api/v1/command" , status = 200 , body = "OK" )
179+ try :
180+ assert await client .request_location ("all" ) is True
181+ assert await client .request_location ("gps" ) is True
182+ assert await client .request_location ("cell" ) is True
183+ assert await client .request_location ("last" ) is True
184+ finally :
185+ await client .close ()
186+
187+ @pytest .mark .asyncio
188+ async def test_set_bluetooth_and_dnd ():
189+ """Test set_bluetooth and set_do_not_disturb commands."""
190+ client = FmdClient ("https://fmd.example.com" )
191+ client .access_token = "token"
192+ class DummySigner :
193+ def sign (self , message_bytes , pad , algo ):
194+ return b"\xAB " * 64
195+ client .private_key = DummySigner ()
196+
197+ with aioresponses () as m :
198+ # Mock commands
199+ m .post ("https://fmd.example.com/api/v1/command" , status = 200 , body = "OK" )
200+ m .post ("https://fmd.example.com/api/v1/command" , status = 200 , body = "OK" )
201+ m .post ("https://fmd.example.com/api/v1/command" , status = 200 , body = "OK" )
202+ m .post ("https://fmd.example.com/api/v1/command" , status = 200 , body = "OK" )
203+ try :
204+ assert await client .set_bluetooth (True ) is True
205+ assert await client .set_bluetooth (False ) is True
206+ assert await client .set_do_not_disturb (True ) is True
207+ assert await client .set_do_not_disturb (False ) is True
208+ finally :
209+ await client .close ()
210+
211+ @pytest .mark .asyncio
212+ async def test_get_device_stats ():
213+ """Test get_device_stats sends stats command."""
214+ client = FmdClient ("https://fmd.example.com" )
215+ client .access_token = "token"
216+ class DummySigner :
217+ def sign (self , message_bytes , pad , algo ):
218+ return b"\xAB " * 64
219+ client .private_key = DummySigner ()
220+
221+ with aioresponses () as m :
222+ m .post ("https://fmd.example.com/api/v1/command" , status = 200 , body = "OK" )
223+ try :
224+ assert await client .get_device_stats () is True
225+ finally :
226+ await client .close ()
227+
228+ @pytest .mark .asyncio
229+ async def test_decrypt_data_blob_too_small ():
230+ """Test decrypt_data_blob raises FmdApiException for small blobs."""
231+ from fmd_api .exceptions import FmdApiException
232+
233+ client = FmdClient ("https://fmd.example.com" )
234+ class DummyKey :
235+ def decrypt (self , packet , padding_obj ):
236+ return b"\x00 " * 32
237+ client .private_key = DummyKey ()
238+
239+ # Blob must be at least RSA_KEY_SIZE_BYTES (384) + AES_GCM_IV_SIZE_BYTES (12) = 396 bytes
240+ too_small = base64 .b64encode (b"x" * 100 ).decode ('utf-8' )
241+
242+ with pytest .raises (FmdApiException , match = "Blob too small for decryption" ):
243+ client .decrypt_data_blob (too_small )
244+
245+ @pytest .mark .asyncio
246+ async def test_get_pictures_direct ():
247+ """Test get_pictures endpoint directly."""
248+ client = FmdClient ("https://fmd.example.com" )
249+ client .access_token = "token"
250+
251+ with aioresponses () as m :
252+ # Mock pictures endpoint returning list of blobs
253+ m .put ("https://fmd.example.com/api/v1/pictures" , payload = ["blob1" , "blob2" , "blob3" ])
254+ try :
255+ pics = await client .get_pictures (num_to_get = 2 )
256+ assert len (pics ) == 2
257+ # Should get the 2 most recent (last 2 in reverse)
258+ assert pics == ["blob3" , "blob2" ]
259+ finally :
260+ await client .close ()
261+
262+ @pytest .mark .asyncio
263+ async def test_http_error_handling ():
264+ """Test client handles various HTTP errors."""
265+ from fmd_api .exceptions import FmdApiException
266+
267+ client = FmdClient ("https://fmd.example.com" )
268+ client .access_token = "token"
269+
270+ # Test 404
271+ with aioresponses () as m :
272+ m .put ("https://fmd.example.com/api/v1/locationDataSize" , status = 404 )
273+ try :
274+ with pytest .raises (FmdApiException ):
275+ await client .get_locations ()
276+ finally :
277+ await client .close ()
278+
279+ # Test 500
280+ client2 = FmdClient ("https://fmd.example.com" )
281+ client2 .access_token = "token"
282+ with aioresponses () as m :
283+ m .put ("https://fmd.example.com/api/v1/locationDataSize" , status = 500 )
284+ try :
285+ with pytest .raises (FmdApiException ):
286+ await client2 .get_locations ()
287+ finally :
288+ await client2 .close ()
289+
290+ @pytest .mark .asyncio
291+ async def test_empty_location_response ():
292+ """Test handling of empty location data."""
293+ client = FmdClient ("https://fmd.example.com" )
294+ client .access_token = "token"
295+
296+ with aioresponses () as m :
297+ # Server reports 0 locations
298+ m .put ("https://fmd.example.com/api/v1/locationDataSize" , payload = {"Data" : "0" })
299+ try :
300+ locs = await client .get_locations ()
301+ assert locs == []
302+ finally :
303+ await client .close ()
0 commit comments