1# Copyright 2020 The Cirq Developers
2# Licensed under the Apache License, Version 2.0 (the "License");
3# you may not use this file except in compliance with the License.
4# You may obtain a copy of the License at
5#
6#     https://www.apache.org/licenses/LICENSE-2.0
7#
8# Unless required by applicable law or agreed to in writing, software
9# distributed under the License is distributed on an "AS IS" BASIS,
10# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11# See the License for the specific language governing permissions and
12# limitations under the License.
13
14import contextlib
15import datetime
16import io
17from unittest import mock
18
19import requests
20import pytest
21
22import cirq_ionq as ionq
23
24
25def test_ionq_exception_str():
26    ex = ionq.IonQException('err', status_code=501)
27    assert str(ex) == 'Status code: 501, Message: \'err\''
28
29
30def test_ionq_not_found_exception_str():
31    ex = ionq.IonQNotFoundException('err')
32    assert str(ex) == 'Status code: 404, Message: \'err\''
33
34
35def test_ionq_client_invalid_remote_host():
36    for invalid_url in ('', 'url', 'http://', 'ftp://', 'http://'):
37        with pytest.raises(AssertionError, match='not a valid url'):
38            _ = ionq.ionq_client._IonQClient(remote_host=invalid_url, api_key='a')
39        with pytest.raises(AssertionError, match=invalid_url):
40            _ = ionq.ionq_client._IonQClient(remote_host=invalid_url, api_key='a')
41
42
43def test_ionq_client_invalid_api_version():
44    with pytest.raises(AssertionError, match='is accepted'):
45        _ = ionq.ionq_client._IonQClient(
46            remote_host='http://example.com', api_key='a', api_version='v0.0'
47        )
48    with pytest.raises(AssertionError, match='0.0'):
49        _ = ionq.ionq_client._IonQClient(
50            remote_host='http://example.com', api_key='a', api_version='v0.0'
51        )
52
53
54def test_ionq_client_invalid_target():
55    with pytest.raises(AssertionError, match='the store'):
56        _ = ionq.ionq_client._IonQClient(
57            remote_host='http://example.com', api_key='a', default_target='the store'
58        )
59    with pytest.raises(AssertionError, match='Target'):
60        _ = ionq.ionq_client._IonQClient(
61            remote_host='http://example.com', api_key='a', default_target='the store'
62        )
63
64
65def test_ionq_client_time_travel():
66    with pytest.raises(AssertionError, match='time machine'):
67        _ = ionq.ionq_client._IonQClient(
68            remote_host='http://example.com', api_key='a', max_retry_seconds=-1
69        )
70
71
72def test_ionq_client_attributes():
73    client = ionq.ionq_client._IonQClient(
74        remote_host='http://example.com',
75        api_key='to_my_heart',
76        default_target='qpu',
77        max_retry_seconds=10,
78        verbose=True,
79    )
80    assert client.url == 'http://example.com/v0.1'
81    assert client.headers == {
82        'Authorization': 'apiKey to_my_heart',
83        'Content-Type': 'application/json',
84    }
85    assert client.default_target == 'qpu'
86    assert client.max_retry_seconds == 10
87    assert client.verbose == True
88
89
90@mock.patch('requests.post')
91def test_ionq_client_create_job(mock_post):
92    mock_post.return_value.status_code.return_value = requests.codes.ok
93    mock_post.return_value.json.return_value = {'foo': 'bar'}
94
95    client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
96    program = ionq.SerializedProgram(body={'job': 'mine'}, metadata={'a': '0,1'})
97    response = client.create_job(
98        serialized_program=program, repetitions=200, target='qpu', name='bacon'
99    )
100    assert response == {'foo': 'bar'}
101
102    expected_json = {
103        'target': 'qpu',
104        'body': {'job': 'mine'},
105        'lang': 'json',
106        'name': 'bacon',
107        'shots': '200',
108        'metadata': {'shots': '200', 'a': '0,1'},
109    }
110    expected_headers = {'Authorization': 'apiKey to_my_heart', 'Content-Type': 'application/json'}
111    mock_post.assert_called_with(
112        'http://example.com/v0.1/jobs', json=expected_json, headers=expected_headers
113    )
114
115
116@mock.patch('requests.post')
117def test_ionq_client_create_job_default_target(mock_post):
118    mock_post.return_value.status_code.return_value = requests.codes.ok
119    mock_post.return_value.json.return_value = {'foo'}
120
121    client = ionq.ionq_client._IonQClient(
122        remote_host='http://example.com', api_key='to_my_heart', default_target='simulator'
123    )
124    _ = client.create_job(ionq.SerializedProgram(body={'job': 'mine'}, metadata={}))
125    assert mock_post.call_args[1]['json']['target'] == 'simulator'
126
127
128@mock.patch('requests.post')
129def test_ionq_client_create_job_target_overrides_default_target(mock_post):
130    mock_post.return_value.status_code.return_value = requests.codes.ok
131    mock_post.return_value.json.return_value = {'foo'}
132
133    client = ionq.ionq_client._IonQClient(
134        remote_host='http://example.com', api_key='to_my_heart', default_target='simulator'
135    )
136    _ = client.create_job(
137        serialized_program=ionq.SerializedProgram(body={'job': 'mine'}, metadata={}),
138        target='qpu',
139        repetitions=1,
140    )
141    assert mock_post.call_args[1]['json']['target'] == 'qpu'
142
143
144def test_ionq_client_create_job_no_targets():
145    client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
146    with pytest.raises(AssertionError, match='neither were set'):
147        _ = client.create_job(
148            serialized_program=ionq.SerializedProgram(body={'job': 'mine'}, metadata={})
149        )
150
151
152@mock.patch('requests.post')
153def test_ionq_client_create_job_unauthorized(mock_post):
154    mock_post.return_value.ok = False
155    mock_post.return_value.status_code = requests.codes.unauthorized
156
157    client = ionq.ionq_client._IonQClient(
158        remote_host='http://example.com', api_key='to_my_heart', default_target='simulator'
159    )
160    with pytest.raises(ionq.IonQException, match='Not authorized'):
161        _ = client.create_job(
162            serialized_program=ionq.SerializedProgram(body={'job': 'mine'}, metadata={})
163        )
164
165
166@mock.patch('requests.post')
167def test_ionq_client_create_job_not_found(mock_post):
168    mock_post.return_value.ok = False
169    mock_post.return_value.status_code = requests.codes.not_found
170
171    client = ionq.ionq_client._IonQClient(
172        remote_host='http://example.com', api_key='to_my_heart', default_target='simulator'
173    )
174    with pytest.raises(ionq.IonQNotFoundException, match='not find'):
175        _ = client.create_job(
176            serialized_program=ionq.SerializedProgram(body={'job': 'mine'}, metadata={})
177        )
178
179
180@mock.patch('requests.post')
181def test_ionq_client_create_job_not_retriable(mock_post):
182    mock_post.return_value.ok = False
183    mock_post.return_value.status_code = requests.codes.not_implemented
184
185    client = ionq.ionq_client._IonQClient(
186        remote_host='http://example.com', api_key='to_my_heart', default_target='simulator'
187    )
188    with pytest.raises(ionq.IonQException, match='Status: 501'):
189        _ = client.create_job(
190            serialized_program=ionq.SerializedProgram(body={'job': 'mine'}, metadata={})
191        )
192
193
194@mock.patch('requests.post')
195def test_ionq_client_create_job_retry(mock_post):
196    response1 = mock.MagicMock()
197    response2 = mock.MagicMock()
198    mock_post.side_effect = [response1, response2]
199    response1.ok = False
200    response1.status_code = requests.codes.service_unavailable
201    response2.ok = True
202    client = ionq.ionq_client._IonQClient(
203        remote_host='http://example.com',
204        api_key='to_my_heart',
205        default_target='simulator',
206        verbose=True,
207    )
208    test_stdout = io.StringIO()
209    with contextlib.redirect_stdout(test_stdout):
210        _ = client.create_job(
211            serialized_program=ionq.SerializedProgram(body={'job': 'mine'}, metadata={})
212        )
213    assert test_stdout.getvalue().strip() == 'Waiting 0.1 seconds before retrying.'
214    assert mock_post.call_count == 2
215
216
217@mock.patch('requests.post')
218def test_ionq_client_create_job_retry_request_error(mock_post):
219    response2 = mock.MagicMock()
220    mock_post.side_effect = [requests.exceptions.ConnectionError(), response2]
221    response2.ok = True
222    client = ionq.ionq_client._IonQClient(
223        remote_host='http://example.com', api_key='to_my_heart', default_target='simulator'
224    )
225    _ = client.create_job(
226        serialized_program=ionq.SerializedProgram(body={'job': 'mine'}, metadata={})
227    )
228    assert mock_post.call_count == 2
229
230
231@mock.patch('requests.post')
232def test_ionq_client_create_job_timeout(mock_post):
233    mock_post.return_value.ok = False
234    mock_post.return_value.status_code = requests.codes.service_unavailable
235
236    client = ionq.ionq_client._IonQClient(
237        remote_host='http://example.com',
238        api_key='to_my_heart',
239        default_target='simulator',
240        max_retry_seconds=0.2,
241    )
242    with pytest.raises(TimeoutError):
243        _ = client.create_job(
244            serialized_program=ionq.SerializedProgram(body={'job': 'mine'}, metadata={})
245        )
246
247
248@mock.patch('requests.get')
249def test_ionq_client_get_job(mock_get):
250    mock_get.return_value.ok = True
251    mock_get.return_value.json.return_value = {'foo': 'bar'}
252    client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
253    response = client.get_job(job_id='job_id')
254    assert response == {'foo': 'bar'}
255
256    expected_headers = {'Authorization': 'apiKey to_my_heart', 'Content-Type': 'application/json'}
257    mock_get.assert_called_with('http://example.com/v0.1/jobs/job_id', headers=expected_headers)
258
259
260@mock.patch('requests.get')
261def test_ionq_client_get_job_unauthorized(mock_get):
262    mock_get.return_value.ok = False
263    mock_get.return_value.status_code = requests.codes.unauthorized
264
265    client = ionq.ionq_client._IonQClient(
266        remote_host='http://example.com', api_key='to_my_heart', default_target='simulator'
267    )
268    with pytest.raises(ionq.IonQException, match='Not authorized'):
269        _ = client.get_job('job_id')
270
271
272@mock.patch('requests.get')
273def test_ionq_client_get_job_not_found(mock_get):
274    (mock_get.return_value).ok = False
275    (mock_get.return_value).status_code = requests.codes.not_found
276
277    client = ionq.ionq_client._IonQClient(
278        remote_host='http://example.com', api_key='to_my_heart', default_target='simulator'
279    )
280    with pytest.raises(ionq.IonQNotFoundException, match='not find'):
281        _ = client.get_job('job_id')
282
283
284@mock.patch('requests.get')
285def test_ionq_client_get_job_not_retriable(mock_get):
286    mock_get.return_value.ok = False
287    mock_get.return_value.status_code = requests.codes.not_implemented
288
289    client = ionq.ionq_client._IonQClient(
290        remote_host='http://example.com', api_key='to_my_heart', default_target='simulator'
291    )
292    with pytest.raises(ionq.IonQException, match='Status: 501'):
293        _ = client.get_job('job_id')
294
295
296@mock.patch('requests.get')
297def test_ionq_client_get_job_retry(mock_get):
298    response1 = mock.MagicMock()
299    response2 = mock.MagicMock()
300    mock_get.side_effect = [response1, response2]
301    response1.ok = False
302    response1.status_code = requests.codes.service_unavailable
303    response2.ok = True
304    client = ionq.ionq_client._IonQClient(
305        remote_host='http://example.com', api_key='to_my_heart', default_target='simulator'
306    )
307    _ = client.get_job('job_id')
308    assert mock_get.call_count == 2
309
310
311@mock.patch('requests.get')
312def test_ionq_client_list_jobs(mock_get):
313    mock_get.return_value.ok = True
314    mock_get.return_value.json.return_value = {'jobs': [{'id': '1'}, {'id': '2'}]}
315    client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
316    response = client.list_jobs()
317    assert response == [{'id': '1'}, {'id': '2'}]
318
319    expected_headers = {'Authorization': 'apiKey to_my_heart', 'Content-Type': 'application/json'}
320    mock_get.assert_called_with(
321        'http://example.com/v0.1/jobs', headers=expected_headers, json={'limit': 1000}, params={}
322    )
323
324
325@mock.patch('requests.get')
326def test_ionq_client_list_jobs_status(mock_get):
327    mock_get.return_value.ok = True
328    mock_get.return_value.json.return_value = {'jobs': [{'id': '1'}, {'id': '2'}]}
329    client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
330    response = client.list_jobs(status='canceled')
331    assert response == [{'id': '1'}, {'id': '2'}]
332
333    expected_headers = {'Authorization': 'apiKey to_my_heart', 'Content-Type': 'application/json'}
334    mock_get.assert_called_with(
335        'http://example.com/v0.1/jobs',
336        headers=expected_headers,
337        json={'limit': 1000},
338        params={'status': 'canceled'},
339    )
340
341
342@mock.patch('requests.get')
343def test_ionq_client_list_jobs_limit(mock_get):
344    mock_get.return_value.ok = True
345    mock_get.return_value.json.return_value = {'jobs': [{'id': '1'}, {'id': '2'}, {'id': 3}]}
346    client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
347    response = client.list_jobs(limit=2)
348    assert response == [{'id': '1'}, {'id': '2'}]
349
350    expected_headers = {'Authorization': 'apiKey to_my_heart', 'Content-Type': 'application/json'}
351    mock_get.assert_called_with(
352        'http://example.com/v0.1/jobs', headers=expected_headers, json={'limit': 1000}, params={}
353    )
354
355
356@mock.patch('requests.get')
357def test_ionq_client_list_jobs_batches(mock_get):
358    mock_get.return_value.ok = True
359    mock_get.return_value.json.side_effect = [
360        {'jobs': [{'id': '1'}], 'next': 'a'},
361        {'jobs': [{'id': '2'}], 'next': 'b'},
362        {'jobs': [{'id': '3'}]},
363    ]
364    client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
365    response = client.list_jobs(batch_size=1)
366    assert response == [{'id': '1'}, {'id': '2'}, {'id': '3'}]
367
368    expected_headers = {'Authorization': 'apiKey to_my_heart', 'Content-Type': 'application/json'}
369    url = 'http://example.com/v0.1/jobs'
370    mock_get.assert_has_calls(
371        [
372            mock.call(url, headers=expected_headers, json={'limit': 1}, params={}),
373            mock.call().json(),
374            mock.call(url, headers=expected_headers, json={'limit': 1}, params={'next': 'a'}),
375            mock.call().json(),
376            mock.call(url, headers=expected_headers, json={'limit': 1}, params={'next': 'b'}),
377            mock.call().json(),
378        ]
379    )
380
381
382@mock.patch('requests.get')
383def test_ionq_client_list_jobs_batches_does_not_divide_total(mock_get):
384    mock_get.return_value.ok = True
385    mock_get.return_value.json.side_effect = [
386        {'jobs': [{'id': '1'}, {'id': '2'}], 'next': 'a'},
387        {'jobs': [{'id': '3'}]},
388    ]
389    client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
390    response = client.list_jobs(batch_size=2)
391    assert response == [{'id': '1'}, {'id': '2'}, {'id': '3'}]
392
393    expected_headers = {'Authorization': 'apiKey to_my_heart', 'Content-Type': 'application/json'}
394    url = 'http://example.com/v0.1/jobs'
395    mock_get.assert_has_calls(
396        [
397            mock.call(url, headers=expected_headers, json={'limit': 2}, params={}),
398            mock.call().json(),
399            mock.call(url, headers=expected_headers, json={'limit': 2}, params={'next': 'a'}),
400            mock.call().json(),
401        ]
402    )
403
404
405@mock.patch('requests.get')
406def test_ionq_client_list_jobs_unauthorized(mock_get):
407    mock_get.return_value.ok = False
408    mock_get.return_value.status_code = requests.codes.unauthorized
409    client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
410    with pytest.raises(ionq.IonQException, match='Not authorized'):
411        _ = client.list_jobs()
412
413
414@mock.patch('requests.get')
415def test_ionq_client_list_jobs_not_retriable(mock_get):
416    mock_get.return_value.ok = False
417    mock_get.return_value.status_code = requests.codes.not_implemented
418    client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
419    with pytest.raises(ionq.IonQException, match='Status: 501'):
420        _ = client.list_jobs()
421
422
423@mock.patch('requests.get')
424def test_ionq_client_list_jobs_retry(mock_get):
425    response1 = mock.MagicMock()
426    response2 = mock.MagicMock()
427    mock_get.side_effect = [response1, response2]
428    response1.ok = False
429    response1.status_code = requests.codes.service_unavailable
430    response2.ok = True
431    client = ionq.ionq_client._IonQClient(
432        remote_host='http://example.com', api_key='to_my_heart', default_target='simulator'
433    )
434    client.list_jobs()
435    assert mock_get.call_count == 2
436
437
438@mock.patch('requests.put')
439def test_ionq_client_cancel_job(mock_put):
440    mock_put.return_value.ok = True
441    mock_put.return_value.json.return_value = {'foo': 'bar'}
442    client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
443    response = client.cancel_job(job_id='job_id')
444    assert response == {'foo': 'bar'}
445
446    expected_headers = {'Authorization': 'apiKey to_my_heart', 'Content-Type': 'application/json'}
447    mock_put.assert_called_with(
448        'http://example.com/v0.1/jobs/job_id/status/cancel', headers=expected_headers
449    )
450
451
452@mock.patch('requests.put')
453def test_ionq_client_cancel_job_unauthorized(mock_put):
454    mock_put.return_value.ok = False
455    mock_put.return_value.status_code = requests.codes.unauthorized
456
457    client = ionq.ionq_client._IonQClient(
458        remote_host='http://example.com', api_key='to_my_heart', default_target='simulator'
459    )
460    with pytest.raises(ionq.IonQException, match='Not authorized'):
461        client.cancel_job('job_id')
462
463
464@mock.patch('requests.put')
465def test_ionq_client_cancel_job_not_found(mock_put):
466    (mock_put.return_value).ok = False
467    (mock_put.return_value).status_code = requests.codes.not_found
468
469    client = ionq.ionq_client._IonQClient(
470        remote_host='http://example.com', api_key='to_my_heart', default_target='simulator'
471    )
472    with pytest.raises(ionq.IonQNotFoundException, match='not find'):
473        client.cancel_job('job_id')
474
475
476@mock.patch('requests.put')
477def test_ionq_client_cancel_job_not_retriable(mock_get):
478    mock_get.return_value.ok = False
479    mock_get.return_value.status_code = requests.codes.not_implemented
480
481    client = ionq.ionq_client._IonQClient(
482        remote_host='http://example.com', api_key='to_my_heart', default_target='simulator'
483    )
484    with pytest.raises(ionq.IonQException, match='Status: 501'):
485        client.cancel_job('job_id')
486
487
488@mock.patch('requests.put')
489def test_ionq_client_cancel_job_retry(mock_put):
490    response1 = mock.MagicMock()
491    response2 = mock.MagicMock()
492    mock_put.side_effect = [response1, response2]
493    response1.ok = False
494    response1.status_code = requests.codes.service_unavailable
495    response2.ok = True
496    client = ionq.ionq_client._IonQClient(
497        remote_host='http://example.com', api_key='to_my_heart', default_target='simulator'
498    )
499    client.cancel_job('job_id')
500    assert mock_put.call_count == 2
501
502
503@mock.patch('requests.delete')
504def test_ionq_client_delete_job(mock_delete):
505    mock_delete.return_value.ok = True
506    mock_delete.return_value.json.return_value = {'foo': 'bar'}
507    client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
508    response = client.delete_job(job_id='job_id')
509    assert response == {'foo': 'bar'}
510
511    expected_headers = {'Authorization': 'apiKey to_my_heart', 'Content-Type': 'application/json'}
512    mock_delete.assert_called_with('http://example.com/v0.1/jobs/job_id', headers=expected_headers)
513
514
515@mock.patch('requests.delete')
516def test_ionq_client_delete_job_unauthorized(mock_delete):
517    mock_delete.return_value.ok = False
518    mock_delete.return_value.status_code = requests.codes.unauthorized
519
520    client = ionq.ionq_client._IonQClient(
521        remote_host='http://example.com', api_key='to_my_heart', default_target='simulator'
522    )
523    with pytest.raises(ionq.IonQException, match='Not authorized'):
524        client.delete_job('job_id')
525
526
527@mock.patch('requests.delete')
528def test_ionq_client_delete_job_not_found(mock_put):
529    (mock_put.return_value).ok = False
530    (mock_put.return_value).status_code = requests.codes.not_found
531
532    client = ionq.ionq_client._IonQClient(
533        remote_host='http://example.com', api_key='to_my_heart', default_target='simulator'
534    )
535    with pytest.raises(ionq.IonQNotFoundException, match='not find'):
536        client.delete_job('job_id')
537
538
539@mock.patch('requests.delete')
540def test_ionq_client_delete_job_not_retriable(mock_delete):
541    mock_delete.return_value.ok = False
542    mock_delete.return_value.status_code = requests.codes.not_implemented
543
544    client = ionq.ionq_client._IonQClient(
545        remote_host='http://example.com', api_key='to_my_heart', default_target='simulator'
546    )
547    with pytest.raises(ionq.IonQException, match='Status: 501'):
548        client.delete_job('job_id')
549
550
551@mock.patch('requests.delete')
552def test_ionq_client_delete_job_retry(mock_put):
553    response1 = mock.MagicMock()
554    response2 = mock.MagicMock()
555    mock_put.side_effect = [response1, response2]
556    response1.ok = False
557    response1.status_code = requests.codes.service_unavailable
558    response2.ok = True
559    client = ionq.ionq_client._IonQClient(
560        remote_host='http://example.com', api_key='to_my_heart', default_target='simulator'
561    )
562    client.delete_job('job_id')
563    assert mock_put.call_count == 2
564
565
566@mock.patch('requests.get')
567def test_ionq_client_get_current_calibrations(mock_get):
568    mock_get.return_value.ok = True
569    mock_get.return_value.json.return_value = {'foo': 'bar'}
570    client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
571    response = client.get_current_calibration()
572    assert response == {'foo': 'bar'}
573
574    expected_headers = {'Authorization': 'apiKey to_my_heart', 'Content-Type': 'application/json'}
575    mock_get.assert_called_with(
576        'http://example.com/v0.1/calibrations/current', headers=expected_headers
577    )
578
579
580@mock.patch('requests.get')
581def test_ionq_client_get_current_calibration_unauthorized(mock_get):
582    mock_get.return_value.ok = False
583    mock_get.return_value.status_code = requests.codes.unauthorized
584
585    client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
586    with pytest.raises(ionq.IonQException, match='Not authorized'):
587        _ = client.get_current_calibration()
588
589
590@mock.patch('requests.get')
591def test_ionq_client_get_current_calibration_not_found(mock_get):
592    (mock_get.return_value).ok = False
593    (mock_get.return_value).status_code = requests.codes.not_found
594
595    client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
596    with pytest.raises(ionq.IonQNotFoundException, match='not find'):
597        _ = client.get_current_calibration()
598
599
600@mock.patch('requests.get')
601def test_ionq_client_get_current_calibration_not_retriable(mock_get):
602    mock_get.return_value.ok = False
603    mock_get.return_value.status_code = requests.codes.not_implemented
604
605    client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
606    with pytest.raises(ionq.IonQException, match='Status: 501'):
607        _ = client.get_current_calibration()
608
609
610@mock.patch('requests.get')
611def test_ionq_client_get_calibration_retry(mock_get):
612    response1 = mock.MagicMock()
613    response2 = mock.MagicMock()
614    mock_get.side_effect = [response1, response2]
615    response1.ok = False
616    response1.status_code = requests.codes.service_unavailable
617    response2.ok = True
618    client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
619    _ = client.get_current_calibration()
620    assert mock_get.call_count == 2
621
622
623@mock.patch('requests.get')
624def test_ionq_client_list_calibrations(mock_get):
625    mock_get.return_value.ok = True
626    mock_get.return_value.json.return_value = {'calibrations': [{'id': '1'}, {'id': '2'}]}
627    client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
628    response = client.list_calibrations()
629    assert response == [{'id': '1'}, {'id': '2'}]
630
631    expected_headers = {'Authorization': 'apiKey to_my_heart', 'Content-Type': 'application/json'}
632    mock_get.assert_called_with(
633        'http://example.com/v0.1/calibrations',
634        headers=expected_headers,
635        json={'limit': 1000},
636        params={},
637    )
638
639
640@mock.patch('requests.get')
641def test_ionq_client_list_calibrations_dates(mock_get):
642    mock_get.return_value.ok = True
643    mock_get.return_value.json.return_value = {'calibrations': [{'id': '1'}, {'id': '2'}]}
644    client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
645    response = client.list_calibrations(
646        start=datetime.datetime.utcfromtimestamp(1284286794),
647        end=datetime.datetime.utcfromtimestamp(1284286795),
648    )
649    assert response == [{'id': '1'}, {'id': '2'}]
650
651    expected_headers = {'Authorization': 'apiKey to_my_heart', 'Content-Type': 'application/json'}
652    mock_get.assert_called_with(
653        'http://example.com/v0.1/calibrations',
654        headers=expected_headers,
655        json={'limit': 1000},
656        params={'start': 1284286794000, 'end': 1284286795000},
657    )
658
659
660@mock.patch('requests.get')
661def test_ionq_client_list_calibrations_limit(mock_get):
662    mock_get.return_value.ok = True
663    mock_get.return_value.json.return_value = {
664        'calibrations': [{'id': '1'}, {'id': '2'}, {'id': 3}]
665    }
666    client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
667    response = client.list_calibrations(limit=2)
668    assert response == [{'id': '1'}, {'id': '2'}]
669
670    expected_headers = {'Authorization': 'apiKey to_my_heart', 'Content-Type': 'application/json'}
671    mock_get.assert_called_with(
672        'http://example.com/v0.1/calibrations',
673        headers=expected_headers,
674        json={'limit': 1000},
675        params={},
676    )
677
678
679@mock.patch('requests.get')
680def test_ionq_client_list_calibrations_batches(mock_get):
681    mock_get.return_value.ok = True
682    mock_get.return_value.json.side_effect = [
683        {'calibrations': [{'id': '1'}], 'next': 'a'},
684        {'calibrations': [{'id': '2'}], 'next': 'b'},
685        {'calibrations': [{'id': '3'}]},
686    ]
687    client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
688    response = client.list_calibrations(batch_size=1)
689    assert response == [{'id': '1'}, {'id': '2'}, {'id': '3'}]
690
691    expected_headers = {'Authorization': 'apiKey to_my_heart', 'Content-Type': 'application/json'}
692    url = 'http://example.com/v0.1/calibrations'
693    mock_get.assert_has_calls(
694        [
695            mock.call(url, headers=expected_headers, json={'limit': 1}, params={}),
696            mock.call().json(),
697            mock.call(url, headers=expected_headers, json={'limit': 1}, params={'next': 'a'}),
698            mock.call().json(),
699            mock.call(url, headers=expected_headers, json={'limit': 1}, params={'next': 'b'}),
700            mock.call().json(),
701        ]
702    )
703
704
705@mock.patch('requests.get')
706def test_ionq_client_list_calibrations_batches_does_not_divide_total(mock_get):
707    mock_get.return_value.ok = True
708    mock_get.return_value.json.side_effect = [
709        {'calibrations': [{'id': '1'}, {'id': '2'}], 'next': 'a'},
710        {'calibrations': [{'id': '3'}]},
711    ]
712    client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
713    response = client.list_calibrations(batch_size=2)
714    assert response == [{'id': '1'}, {'id': '2'}, {'id': '3'}]
715
716    expected_headers = {'Authorization': 'apiKey to_my_heart', 'Content-Type': 'application/json'}
717    url = 'http://example.com/v0.1/calibrations'
718    mock_get.assert_has_calls(
719        [
720            mock.call(url, headers=expected_headers, json={'limit': 2}, params={}),
721            mock.call().json(),
722            mock.call(url, headers=expected_headers, json={'limit': 2}, params={'next': 'a'}),
723            mock.call().json(),
724        ]
725    )
726
727
728@mock.patch('requests.get')
729def test_ionq_client_list_calibrations_unauthorized(mock_get):
730    mock_get.return_value.ok = False
731    mock_get.return_value.status_code = requests.codes.unauthorized
732    client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
733    with pytest.raises(ionq.IonQException, match='Not authorized'):
734        _ = client.list_calibrations()
735
736
737@mock.patch('requests.get')
738def test_ionq_client_list_calibrations_not_retriable(mock_get):
739    mock_get.return_value.ok = False
740    mock_get.return_value.status_code = requests.codes.not_implemented
741    client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
742    with pytest.raises(ionq.IonQException, match='Status: 501'):
743        _ = client.list_calibrations()
744
745
746@mock.patch('requests.get')
747def test_ionq_client_list_calibrations_retry(mock_get):
748    response1 = mock.MagicMock()
749    response2 = mock.MagicMock()
750    mock_get.side_effect = [response1, response2]
751    response1.ok = False
752    response1.status_code = requests.codes.service_unavailable
753    response2.ok = True
754    client = ionq.ionq_client._IonQClient(
755        remote_host='http://example.com', api_key='to_my_heart', default_target='simulator'
756    )
757    client.list_calibrations()
758    assert mock_get.call_count == 2
759