Loading tests/test_camera_atik.pydeleted 100644 → 0 +0 −135 Original line number Diff line number Diff line import ctypes import os import tempfile import unittest from unittest.mock import patch, MagicMock, ANY from noctua.devices.atik import Camera class TestAtikCameraMethods(unittest.TestCase): """Unit tests for atik.Camera (devices.cam2).""" def setUp(self): with patch('ctypes.CDLL'): self.camera = Camera("dummy") # Replace lib with a controllable mock; simulate a connected camera self.mock_lib = MagicMock() self.camera._lib = self.mock_lib self.camera._handle = MagicMock(name='fake_handle') # truthy → _check_connection returns it self.camera._props.nPixelsX = 4096 self.camera._props.nPixelsY = 4126 self.camera._subframe = [0, 0, 4096, 4126] self.camera.error = [] # --- abort --- def test_abort(self): self.camera.abort() self.mock_lib.ArtemisAbortExposure.assert_called_once_with(self.camera._handle) # --- start --- def test_start_light(self): self.camera.start(10.0, 1) self.mock_lib.ArtemisSetDarkMode.assert_called_once_with(self.camera._handle, False) self.mock_lib.ArtemisStartExposure.assert_called_once() def test_start_dark(self): self.camera.start(10.0, 0) self.mock_lib.ArtemisSetDarkMode.assert_called_once_with(self.camera._handle, True) def test_start_bias_is_dark(self): self.camera.start(0.0, 2) self.mock_lib.ArtemisSetDarkMode.assert_called_once_with(self.camera._handle, True) def test_start_accepts_datetime_kwarg(self): # datetime is accepted but unused by the Atik SDK path self.camera.start(5.0, 1, datetime="2024-06-01T00:00:00.000") self.mock_lib.ArtemisStartExposure.assert_called_once() # --- full_frame / half_frame / small_frame --- def test_full_frame_returns_size(self): result = self.camera.full_frame() self.assertEqual(result, [4096, 4126]) self.mock_lib.ArtemisSubframe.assert_called_once_with( self.camera._handle, 0, 0, 4096, 4126) self.assertEqual(self.camera._subframe, [0, 0, 4096, 4126]) def test_half_frame_returns_size(self): result = self.camera.half_frame() self.assertEqual(result, [4096, 4126 // 2]) self.mock_lib.ArtemisSubframe.assert_called_once_with( self.camera._handle, 0, 4126 // 4, 4096, 4126 // 2) self.assertEqual(self.camera._subframe, [0, 4126 // 4, 4096, 4126 // 2]) def test_small_frame_returns_size(self): result = self.camera.small_frame() self.assertEqual(result, [4096, 500]) self.mock_lib.ArtemisSubframe.assert_called_once_with( self.camera._handle, 0, 1500, 4096, 500) self.assertEqual(self.camera._subframe, [0, 1500, 4096, 500]) # --- state --- def test_state(self): self.mock_lib.ArtemisCameraState.return_value = 0 self.assertEqual(self.camera.state, 0) # --- binning --- def test_binning_get_calls_sdk(self): result = self.camera.binning self.assertIsInstance(result, list) self.assertEqual(len(result), 2) self.mock_lib.ArtemisGetBin.assert_called_once_with(self.camera._handle, ANY, ANY) def test_binning_set(self): self.camera.binning = [2, 2] self.mock_lib.ArtemisBin.assert_called_once_with(self.camera._handle, 2, 2) # --- filter --- def test_filter_get_returns_zero(self): self.assertEqual(self.camera.filter, 0) def test_filter_set_is_noop(self): self.camera.filter = 3 self.assertEqual(self.mock_lib.method_calls, []) # no SDK calls # --- all --- def test_all_has_all_keys(self): self.mock_lib.ArtemisCameraState.return_value = 0 result = self.camera.all expected_keys = { "ambient", "setpoint", "temperature", "cooler", "fan", "binning", "max_range", "xystart", "xyend", "xrange", "yrange", "center", "state", "description", } self.assertEqual(set(result.keys()), expected_keys) def test_all_xystart_reflects_subframe(self): self.camera._subframe = [512, 256, 4096, 500] self.mock_lib.ArtemisCameraState.return_value = 0 result = self.camera.all # binning defaults to 0 from mock → guard gives 0; check keys exist self.assertIn("xystart", result) self.assertIn("xyend", result) # --- download --- def test_download_polls_image_ready(self): self.mock_lib.ArtemisImageReady.return_value = 1 # ready immediately self.mock_lib.ArtemisImageBuffer.return_value = 0 # null ptr → skip write with tempfile.TemporaryDirectory() as tmpdir: self.camera.download(filepath=os.path.join(tmpdir, "test.fits")) self.mock_lib.ArtemisImageReady.assert_called() self.mock_lib.ArtemisGetImageData.assert_called() self.mock_lib.ArtemisImageBuffer.assert_called() if __name__ == '__main__': unittest.main() tests/test_camera_stx.py→tests/test_cameras.py +272 −0 Original line number Diff line number Diff line import os import tempfile import unittest from unittest.mock import patch, MagicMock from unittest.mock import patch, MagicMock, ANY from noctua.devices.stx import Camera # Patch ctypes.CDLL so the Atik SDK is not loaded on the dev machine with patch('ctypes.CDLL'): from noctua import devices def mock_resp(text): Loading @@ -15,43 +17,42 @@ def mock_resp(text): return r class TestSTXCameraMethods(unittest.TestCase): """Unit tests for stx.Camera (devices.cam).""" # ───────────────────────────────────────────────────────────── # STX Camera (devices.cam) # ───────────────────────────────────────────────────────────── def setUp(self): self.camera = Camera("http://mock-stx") self.camera._command_interval = 0 # skip 50ms throttle in tests self.camera.error = [] class TestSTXCamera(unittest.TestCase): """Unit tests for stx.Camera via devices.cam.""" # --- abort --- def setUp(self): devices.cam._command_interval = 0 devices.cam.error = [] @patch('noctua.devices.stx.requests.get') def test_abort(self, mock_get): mock_get.return_value = mock_resp("OK\r\n") self.camera.abort() devices.cam.abort() args, _ = mock_get.call_args self.assertIn("ImagerAbortExposure", args[0]) # --- start --- @patch('noctua.devices.stx.requests.get') def test_start_when_idle(self, mock_get): mock_get.side_effect = [ mock_resp("0\r\n"), # state → 0.0 (idle) mock_resp("OK\r\n"), # StartExposure ] self.camera.start(10.0, 1) devices.cam.start(10.0, 1) self.assertEqual(mock_get.call_count, 2) self.assertEqual(self.camera.error, []) self.assertEqual(devices.cam.error, []) @patch('noctua.devices.stx.requests.get') def test_start_not_idle(self, mock_get): mock_get.return_value = mock_resp("2\r\n") # state = 2 (exposing) self.camera.start(10.0, 1) # state is queried twice: once in `if self.state` and once in the log message devices.cam.start(10.0, 1) # state is queried twice: in `if self.state` and in the log message for call_args in mock_get.call_args_list: self.assertIn("ImagerState", call_args[0][0]) self.assertIn("Camera not idle", self.camera.error) self.assertIn("Camera not idle", devices.cam.error) @patch('noctua.devices.stx.requests.get') def test_start_passes_datetime(self, mock_get): Loading @@ -59,20 +60,18 @@ class TestSTXCameraMethods(unittest.TestCase): mock_resp("0\r\n"), mock_resp("OK\r\n"), ] self.camera.start(5.0, 1, datetime="2024-06-01T00:00:00.000") devices.cam.start(5.0, 1, datetime="2024-06-01T00:00:00.000") _, kwargs = mock_get.call_args self.assertIn("DateTime", kwargs.get('params', '')) # --- full_frame / half_frame / small_frame --- @patch('noctua.devices.stx.requests.get') def test_full_frame_returns_size(self, mock_get): mock_get.side_effect = [ mock_resp("4096\r\n4126\r\n"), # GetSettings → cam_x, cam_y mock_resp("0\r\n"), # state check inside set_window mock_resp("4096\r\n4126\r\n"), # CameraXSize, CameraYSize mock_resp("0\r\n"), # state in set_window mock_resp("OK\r\n"), # SetSettings ] result = self.camera.full_frame() result = devices.cam.full_frame() self.assertEqual(result, [4096, 4126]) @patch('noctua.devices.stx.requests.get') Loading @@ -82,7 +81,7 @@ class TestSTXCameraMethods(unittest.TestCase): mock_resp("0\r\n"), mock_resp("OK\r\n"), ] result = self.camera.half_frame() result = devices.cam.half_frame() self.assertEqual(result, [4096 // 2, 4126 // 2]) @patch('noctua.devices.stx.requests.get') Loading @@ -92,38 +91,32 @@ class TestSTXCameraMethods(unittest.TestCase): mock_resp("0\r\n"), mock_resp("OK\r\n"), ] result = self.camera.small_frame() result = devices.cam.small_frame() self.assertEqual(result, [4096 // 10, 4126 // 10]) # --- state --- @patch('noctua.devices.stx.requests.get') def test_state(self, mock_get): mock_get.return_value = mock_resp("0\r\n") self.assertEqual(self.camera.state, 0.0) # --- binning --- self.assertEqual(devices.cam.state, 0.0) @patch('noctua.devices.stx.requests.get') def test_binning_get(self, mock_get): mock_get.return_value = mock_resp("2\r\n2\r\n") self.assertEqual(self.camera.binning, [2, 2]) self.assertEqual(devices.cam.binning, [2, 2]) @patch('noctua.devices.stx.requests.get') def test_binning_set(self, mock_get): mock_get.side_effect = [ mock_resp("0\r\n"), # state check mock_resp("OK\r\n"), # SetSettings mock_resp("0\r\n"), mock_resp("OK\r\n"), ] self.camera.binning = [2, 2] devices.cam.binning = [2, 2] self.assertEqual(mock_get.call_count, 2) # --- filter --- @patch('noctua.devices.stx.requests.get') def test_filter_get(self, mock_get): mock_get.return_value = mock_resp("3\r\n") self.assertEqual(self.camera.filter, 3.0) self.assertEqual(devices.cam.filter, 3.0) @patch('noctua.devices.stx.requests.get') def test_filter_set(self, mock_get): Loading @@ -131,27 +124,25 @@ class TestSTXCameraMethods(unittest.TestCase): mock_resp("0\r\n"), # is_moving check mock_resp("OK\r\n"), # ChangeFilter ] self.camera.filter = 2 devices.cam.filter = 2 self.assertEqual(mock_get.call_count, 2) # --- all --- @patch('noctua.devices.stx.requests.get') def test_all_has_all_keys(self, mock_get): # 13 values matching the ImagerGetSettings params list # 13 values matching ImagerGetSettings params order settings = ( "20.5\r\n-10.0\r\n-5.3\r\n" # ambient, setpoint, temp "1\r\n45\r\n" # cooler, fan "1\r\n1\r\n" # binX, binY "4096\r\n4126\r\n" # camX, camY "0\r\n0\r\n4096\r\n4126\r\n" # startX, startY, numX, numY "20.5\r\n-10.0\r\n-5.3\r\n" "1\r\n45\r\n" "1\r\n1\r\n" "4096\r\n4126\r\n" "0\r\n0\r\n4096\r\n4126\r\n" ) mock_get.side_effect = [ mock_resp(settings), mock_resp("0\r\n"), # state mock_resp("STX-16803\r\n"), # description mock_resp("0\r\n"), mock_resp("STX-16803\r\n"), ] result = self.camera.all result = devices.cam.all expected_keys = { "ambient", "setpoint", "temperature", "cooler", "fan", "binning", "max_range", "xystart", "xyend", Loading @@ -159,19 +150,123 @@ class TestSTXCameraMethods(unittest.TestCase): } self.assertEqual(set(result.keys()), expected_keys) # --- download --- @patch('noctua.devices.stx.requests.get') def test_download_writes_file(self, mock_get): fake_content = b"SIMPLE = T" mock_get.return_value = mock_resp(fake_content) with tempfile.TemporaryDirectory() as tmpdir: filepath = os.path.join(tmpdir, "test.fits") self.camera.download(filepath=filepath) devices.cam.download(filepath=filepath) self.assertTrue(os.path.exists(filepath)) with open(filepath, 'rb') as f: self.assertEqual(f.read(), fake_content) # ───────────────────────────────────────────────────────────── # Atik Camera (devices.cam2) # ───────────────────────────────────────────────────────────── class TestAtikCamera(unittest.TestCase): """Unit tests for atik.Camera via devices.cam2.""" def setUp(self): self.mock_lib = MagicMock() devices.cam2._lib = self.mock_lib devices.cam2._handle = MagicMock(name='fake_handle') # truthy → skips _check_connection devices.cam2._props.nPixelsX = 4096 devices.cam2._props.nPixelsY = 4126 devices.cam2._subframe = [0, 0, 4096, 4126] devices.cam2.error = [] def test_abort(self): devices.cam2.abort() self.mock_lib.ArtemisAbortExposure.assert_called_once_with(devices.cam2._handle) def test_start_light(self): devices.cam2.start(10.0, 1) self.mock_lib.ArtemisSetDarkMode.assert_called_once_with(devices.cam2._handle, False) self.mock_lib.ArtemisStartExposure.assert_called_once() def test_start_dark(self): devices.cam2.start(10.0, 0) self.mock_lib.ArtemisSetDarkMode.assert_called_once_with(devices.cam2._handle, True) def test_start_bias_is_dark(self): devices.cam2.start(0.0, 2) self.mock_lib.ArtemisSetDarkMode.assert_called_once_with(devices.cam2._handle, True) def test_start_accepts_datetime_kwarg(self): devices.cam2.start(5.0, 1, datetime="2024-06-01T00:00:00.000") self.mock_lib.ArtemisStartExposure.assert_called_once() def test_full_frame_returns_size(self): result = devices.cam2.full_frame() self.assertEqual(result, [4096, 4126]) self.mock_lib.ArtemisSubframe.assert_called_once_with( devices.cam2._handle, 0, 0, 4096, 4126) self.assertEqual(devices.cam2._subframe, [0, 0, 4096, 4126]) def test_half_frame_returns_size(self): result = devices.cam2.half_frame() self.assertEqual(result, [4096, 4126 // 2]) self.mock_lib.ArtemisSubframe.assert_called_once_with( devices.cam2._handle, 0, 4126 // 4, 4096, 4126 // 2) self.assertEqual(devices.cam2._subframe, [0, 4126 // 4, 4096, 4126 // 2]) def test_small_frame_returns_size(self): result = devices.cam2.small_frame() self.assertEqual(result, [4096, 500]) self.mock_lib.ArtemisSubframe.assert_called_once_with( devices.cam2._handle, 0, 1500, 4096, 500) self.assertEqual(devices.cam2._subframe, [0, 1500, 4096, 500]) def test_state(self): self.mock_lib.ArtemisCameraState.return_value = 0 self.assertEqual(devices.cam2.state, 0) def test_binning_get_calls_sdk(self): result = devices.cam2.binning self.assertIsInstance(result, list) self.assertEqual(len(result), 2) self.mock_lib.ArtemisGetBin.assert_called_once_with(devices.cam2._handle, ANY, ANY) def test_binning_set(self): devices.cam2.binning = [2, 2] self.mock_lib.ArtemisBin.assert_called_once_with(devices.cam2._handle, 2, 2) def test_filter_get_returns_zero(self): self.assertEqual(devices.cam2.filter, 0) def test_filter_set_is_noop(self): devices.cam2.filter = 3 self.assertEqual(self.mock_lib.method_calls, []) def test_all_has_all_keys(self): self.mock_lib.ArtemisCameraState.return_value = 0 result = devices.cam2.all expected_keys = { "ambient", "setpoint", "temperature", "cooler", "fan", "binning", "max_range", "xystart", "xyend", "xrange", "yrange", "center", "state", "description", } self.assertEqual(set(result.keys()), expected_keys) def test_all_xystart_reflects_subframe(self): devices.cam2._subframe = [512, 256, 4096, 500] self.mock_lib.ArtemisCameraState.return_value = 0 result = devices.cam2.all self.assertIn("xystart", result) self.assertIn("xyend", result) def test_download_polls_image_ready(self): self.mock_lib.ArtemisImageReady.return_value = 1 self.mock_lib.ArtemisImageBuffer.return_value = 0 # null ptr → skip write with tempfile.TemporaryDirectory() as tmpdir: devices.cam2.download(filepath=os.path.join(tmpdir, "test.fits")) self.mock_lib.ArtemisImageReady.assert_called() self.mock_lib.ArtemisGetImageData.assert_called() self.mock_lib.ArtemisImageBuffer.assert_called() if __name__ == '__main__': unittest.main() Loading
tests/test_camera_atik.pydeleted 100644 → 0 +0 −135 Original line number Diff line number Diff line import ctypes import os import tempfile import unittest from unittest.mock import patch, MagicMock, ANY from noctua.devices.atik import Camera class TestAtikCameraMethods(unittest.TestCase): """Unit tests for atik.Camera (devices.cam2).""" def setUp(self): with patch('ctypes.CDLL'): self.camera = Camera("dummy") # Replace lib with a controllable mock; simulate a connected camera self.mock_lib = MagicMock() self.camera._lib = self.mock_lib self.camera._handle = MagicMock(name='fake_handle') # truthy → _check_connection returns it self.camera._props.nPixelsX = 4096 self.camera._props.nPixelsY = 4126 self.camera._subframe = [0, 0, 4096, 4126] self.camera.error = [] # --- abort --- def test_abort(self): self.camera.abort() self.mock_lib.ArtemisAbortExposure.assert_called_once_with(self.camera._handle) # --- start --- def test_start_light(self): self.camera.start(10.0, 1) self.mock_lib.ArtemisSetDarkMode.assert_called_once_with(self.camera._handle, False) self.mock_lib.ArtemisStartExposure.assert_called_once() def test_start_dark(self): self.camera.start(10.0, 0) self.mock_lib.ArtemisSetDarkMode.assert_called_once_with(self.camera._handle, True) def test_start_bias_is_dark(self): self.camera.start(0.0, 2) self.mock_lib.ArtemisSetDarkMode.assert_called_once_with(self.camera._handle, True) def test_start_accepts_datetime_kwarg(self): # datetime is accepted but unused by the Atik SDK path self.camera.start(5.0, 1, datetime="2024-06-01T00:00:00.000") self.mock_lib.ArtemisStartExposure.assert_called_once() # --- full_frame / half_frame / small_frame --- def test_full_frame_returns_size(self): result = self.camera.full_frame() self.assertEqual(result, [4096, 4126]) self.mock_lib.ArtemisSubframe.assert_called_once_with( self.camera._handle, 0, 0, 4096, 4126) self.assertEqual(self.camera._subframe, [0, 0, 4096, 4126]) def test_half_frame_returns_size(self): result = self.camera.half_frame() self.assertEqual(result, [4096, 4126 // 2]) self.mock_lib.ArtemisSubframe.assert_called_once_with( self.camera._handle, 0, 4126 // 4, 4096, 4126 // 2) self.assertEqual(self.camera._subframe, [0, 4126 // 4, 4096, 4126 // 2]) def test_small_frame_returns_size(self): result = self.camera.small_frame() self.assertEqual(result, [4096, 500]) self.mock_lib.ArtemisSubframe.assert_called_once_with( self.camera._handle, 0, 1500, 4096, 500) self.assertEqual(self.camera._subframe, [0, 1500, 4096, 500]) # --- state --- def test_state(self): self.mock_lib.ArtemisCameraState.return_value = 0 self.assertEqual(self.camera.state, 0) # --- binning --- def test_binning_get_calls_sdk(self): result = self.camera.binning self.assertIsInstance(result, list) self.assertEqual(len(result), 2) self.mock_lib.ArtemisGetBin.assert_called_once_with(self.camera._handle, ANY, ANY) def test_binning_set(self): self.camera.binning = [2, 2] self.mock_lib.ArtemisBin.assert_called_once_with(self.camera._handle, 2, 2) # --- filter --- def test_filter_get_returns_zero(self): self.assertEqual(self.camera.filter, 0) def test_filter_set_is_noop(self): self.camera.filter = 3 self.assertEqual(self.mock_lib.method_calls, []) # no SDK calls # --- all --- def test_all_has_all_keys(self): self.mock_lib.ArtemisCameraState.return_value = 0 result = self.camera.all expected_keys = { "ambient", "setpoint", "temperature", "cooler", "fan", "binning", "max_range", "xystart", "xyend", "xrange", "yrange", "center", "state", "description", } self.assertEqual(set(result.keys()), expected_keys) def test_all_xystart_reflects_subframe(self): self.camera._subframe = [512, 256, 4096, 500] self.mock_lib.ArtemisCameraState.return_value = 0 result = self.camera.all # binning defaults to 0 from mock → guard gives 0; check keys exist self.assertIn("xystart", result) self.assertIn("xyend", result) # --- download --- def test_download_polls_image_ready(self): self.mock_lib.ArtemisImageReady.return_value = 1 # ready immediately self.mock_lib.ArtemisImageBuffer.return_value = 0 # null ptr → skip write with tempfile.TemporaryDirectory() as tmpdir: self.camera.download(filepath=os.path.join(tmpdir, "test.fits")) self.mock_lib.ArtemisImageReady.assert_called() self.mock_lib.ArtemisGetImageData.assert_called() self.mock_lib.ArtemisImageBuffer.assert_called() if __name__ == '__main__': unittest.main()
tests/test_camera_stx.py→tests/test_cameras.py +272 −0 Original line number Diff line number Diff line import os import tempfile import unittest from unittest.mock import patch, MagicMock from unittest.mock import patch, MagicMock, ANY from noctua.devices.stx import Camera # Patch ctypes.CDLL so the Atik SDK is not loaded on the dev machine with patch('ctypes.CDLL'): from noctua import devices def mock_resp(text): Loading @@ -15,43 +17,42 @@ def mock_resp(text): return r class TestSTXCameraMethods(unittest.TestCase): """Unit tests for stx.Camera (devices.cam).""" # ───────────────────────────────────────────────────────────── # STX Camera (devices.cam) # ───────────────────────────────────────────────────────────── def setUp(self): self.camera = Camera("http://mock-stx") self.camera._command_interval = 0 # skip 50ms throttle in tests self.camera.error = [] class TestSTXCamera(unittest.TestCase): """Unit tests for stx.Camera via devices.cam.""" # --- abort --- def setUp(self): devices.cam._command_interval = 0 devices.cam.error = [] @patch('noctua.devices.stx.requests.get') def test_abort(self, mock_get): mock_get.return_value = mock_resp("OK\r\n") self.camera.abort() devices.cam.abort() args, _ = mock_get.call_args self.assertIn("ImagerAbortExposure", args[0]) # --- start --- @patch('noctua.devices.stx.requests.get') def test_start_when_idle(self, mock_get): mock_get.side_effect = [ mock_resp("0\r\n"), # state → 0.0 (idle) mock_resp("OK\r\n"), # StartExposure ] self.camera.start(10.0, 1) devices.cam.start(10.0, 1) self.assertEqual(mock_get.call_count, 2) self.assertEqual(self.camera.error, []) self.assertEqual(devices.cam.error, []) @patch('noctua.devices.stx.requests.get') def test_start_not_idle(self, mock_get): mock_get.return_value = mock_resp("2\r\n") # state = 2 (exposing) self.camera.start(10.0, 1) # state is queried twice: once in `if self.state` and once in the log message devices.cam.start(10.0, 1) # state is queried twice: in `if self.state` and in the log message for call_args in mock_get.call_args_list: self.assertIn("ImagerState", call_args[0][0]) self.assertIn("Camera not idle", self.camera.error) self.assertIn("Camera not idle", devices.cam.error) @patch('noctua.devices.stx.requests.get') def test_start_passes_datetime(self, mock_get): Loading @@ -59,20 +60,18 @@ class TestSTXCameraMethods(unittest.TestCase): mock_resp("0\r\n"), mock_resp("OK\r\n"), ] self.camera.start(5.0, 1, datetime="2024-06-01T00:00:00.000") devices.cam.start(5.0, 1, datetime="2024-06-01T00:00:00.000") _, kwargs = mock_get.call_args self.assertIn("DateTime", kwargs.get('params', '')) # --- full_frame / half_frame / small_frame --- @patch('noctua.devices.stx.requests.get') def test_full_frame_returns_size(self, mock_get): mock_get.side_effect = [ mock_resp("4096\r\n4126\r\n"), # GetSettings → cam_x, cam_y mock_resp("0\r\n"), # state check inside set_window mock_resp("4096\r\n4126\r\n"), # CameraXSize, CameraYSize mock_resp("0\r\n"), # state in set_window mock_resp("OK\r\n"), # SetSettings ] result = self.camera.full_frame() result = devices.cam.full_frame() self.assertEqual(result, [4096, 4126]) @patch('noctua.devices.stx.requests.get') Loading @@ -82,7 +81,7 @@ class TestSTXCameraMethods(unittest.TestCase): mock_resp("0\r\n"), mock_resp("OK\r\n"), ] result = self.camera.half_frame() result = devices.cam.half_frame() self.assertEqual(result, [4096 // 2, 4126 // 2]) @patch('noctua.devices.stx.requests.get') Loading @@ -92,38 +91,32 @@ class TestSTXCameraMethods(unittest.TestCase): mock_resp("0\r\n"), mock_resp("OK\r\n"), ] result = self.camera.small_frame() result = devices.cam.small_frame() self.assertEqual(result, [4096 // 10, 4126 // 10]) # --- state --- @patch('noctua.devices.stx.requests.get') def test_state(self, mock_get): mock_get.return_value = mock_resp("0\r\n") self.assertEqual(self.camera.state, 0.0) # --- binning --- self.assertEqual(devices.cam.state, 0.0) @patch('noctua.devices.stx.requests.get') def test_binning_get(self, mock_get): mock_get.return_value = mock_resp("2\r\n2\r\n") self.assertEqual(self.camera.binning, [2, 2]) self.assertEqual(devices.cam.binning, [2, 2]) @patch('noctua.devices.stx.requests.get') def test_binning_set(self, mock_get): mock_get.side_effect = [ mock_resp("0\r\n"), # state check mock_resp("OK\r\n"), # SetSettings mock_resp("0\r\n"), mock_resp("OK\r\n"), ] self.camera.binning = [2, 2] devices.cam.binning = [2, 2] self.assertEqual(mock_get.call_count, 2) # --- filter --- @patch('noctua.devices.stx.requests.get') def test_filter_get(self, mock_get): mock_get.return_value = mock_resp("3\r\n") self.assertEqual(self.camera.filter, 3.0) self.assertEqual(devices.cam.filter, 3.0) @patch('noctua.devices.stx.requests.get') def test_filter_set(self, mock_get): Loading @@ -131,27 +124,25 @@ class TestSTXCameraMethods(unittest.TestCase): mock_resp("0\r\n"), # is_moving check mock_resp("OK\r\n"), # ChangeFilter ] self.camera.filter = 2 devices.cam.filter = 2 self.assertEqual(mock_get.call_count, 2) # --- all --- @patch('noctua.devices.stx.requests.get') def test_all_has_all_keys(self, mock_get): # 13 values matching the ImagerGetSettings params list # 13 values matching ImagerGetSettings params order settings = ( "20.5\r\n-10.0\r\n-5.3\r\n" # ambient, setpoint, temp "1\r\n45\r\n" # cooler, fan "1\r\n1\r\n" # binX, binY "4096\r\n4126\r\n" # camX, camY "0\r\n0\r\n4096\r\n4126\r\n" # startX, startY, numX, numY "20.5\r\n-10.0\r\n-5.3\r\n" "1\r\n45\r\n" "1\r\n1\r\n" "4096\r\n4126\r\n" "0\r\n0\r\n4096\r\n4126\r\n" ) mock_get.side_effect = [ mock_resp(settings), mock_resp("0\r\n"), # state mock_resp("STX-16803\r\n"), # description mock_resp("0\r\n"), mock_resp("STX-16803\r\n"), ] result = self.camera.all result = devices.cam.all expected_keys = { "ambient", "setpoint", "temperature", "cooler", "fan", "binning", "max_range", "xystart", "xyend", Loading @@ -159,19 +150,123 @@ class TestSTXCameraMethods(unittest.TestCase): } self.assertEqual(set(result.keys()), expected_keys) # --- download --- @patch('noctua.devices.stx.requests.get') def test_download_writes_file(self, mock_get): fake_content = b"SIMPLE = T" mock_get.return_value = mock_resp(fake_content) with tempfile.TemporaryDirectory() as tmpdir: filepath = os.path.join(tmpdir, "test.fits") self.camera.download(filepath=filepath) devices.cam.download(filepath=filepath) self.assertTrue(os.path.exists(filepath)) with open(filepath, 'rb') as f: self.assertEqual(f.read(), fake_content) # ───────────────────────────────────────────────────────────── # Atik Camera (devices.cam2) # ───────────────────────────────────────────────────────────── class TestAtikCamera(unittest.TestCase): """Unit tests for atik.Camera via devices.cam2.""" def setUp(self): self.mock_lib = MagicMock() devices.cam2._lib = self.mock_lib devices.cam2._handle = MagicMock(name='fake_handle') # truthy → skips _check_connection devices.cam2._props.nPixelsX = 4096 devices.cam2._props.nPixelsY = 4126 devices.cam2._subframe = [0, 0, 4096, 4126] devices.cam2.error = [] def test_abort(self): devices.cam2.abort() self.mock_lib.ArtemisAbortExposure.assert_called_once_with(devices.cam2._handle) def test_start_light(self): devices.cam2.start(10.0, 1) self.mock_lib.ArtemisSetDarkMode.assert_called_once_with(devices.cam2._handle, False) self.mock_lib.ArtemisStartExposure.assert_called_once() def test_start_dark(self): devices.cam2.start(10.0, 0) self.mock_lib.ArtemisSetDarkMode.assert_called_once_with(devices.cam2._handle, True) def test_start_bias_is_dark(self): devices.cam2.start(0.0, 2) self.mock_lib.ArtemisSetDarkMode.assert_called_once_with(devices.cam2._handle, True) def test_start_accepts_datetime_kwarg(self): devices.cam2.start(5.0, 1, datetime="2024-06-01T00:00:00.000") self.mock_lib.ArtemisStartExposure.assert_called_once() def test_full_frame_returns_size(self): result = devices.cam2.full_frame() self.assertEqual(result, [4096, 4126]) self.mock_lib.ArtemisSubframe.assert_called_once_with( devices.cam2._handle, 0, 0, 4096, 4126) self.assertEqual(devices.cam2._subframe, [0, 0, 4096, 4126]) def test_half_frame_returns_size(self): result = devices.cam2.half_frame() self.assertEqual(result, [4096, 4126 // 2]) self.mock_lib.ArtemisSubframe.assert_called_once_with( devices.cam2._handle, 0, 4126 // 4, 4096, 4126 // 2) self.assertEqual(devices.cam2._subframe, [0, 4126 // 4, 4096, 4126 // 2]) def test_small_frame_returns_size(self): result = devices.cam2.small_frame() self.assertEqual(result, [4096, 500]) self.mock_lib.ArtemisSubframe.assert_called_once_with( devices.cam2._handle, 0, 1500, 4096, 500) self.assertEqual(devices.cam2._subframe, [0, 1500, 4096, 500]) def test_state(self): self.mock_lib.ArtemisCameraState.return_value = 0 self.assertEqual(devices.cam2.state, 0) def test_binning_get_calls_sdk(self): result = devices.cam2.binning self.assertIsInstance(result, list) self.assertEqual(len(result), 2) self.mock_lib.ArtemisGetBin.assert_called_once_with(devices.cam2._handle, ANY, ANY) def test_binning_set(self): devices.cam2.binning = [2, 2] self.mock_lib.ArtemisBin.assert_called_once_with(devices.cam2._handle, 2, 2) def test_filter_get_returns_zero(self): self.assertEqual(devices.cam2.filter, 0) def test_filter_set_is_noop(self): devices.cam2.filter = 3 self.assertEqual(self.mock_lib.method_calls, []) def test_all_has_all_keys(self): self.mock_lib.ArtemisCameraState.return_value = 0 result = devices.cam2.all expected_keys = { "ambient", "setpoint", "temperature", "cooler", "fan", "binning", "max_range", "xystart", "xyend", "xrange", "yrange", "center", "state", "description", } self.assertEqual(set(result.keys()), expected_keys) def test_all_xystart_reflects_subframe(self): devices.cam2._subframe = [512, 256, 4096, 500] self.mock_lib.ArtemisCameraState.return_value = 0 result = devices.cam2.all self.assertIn("xystart", result) self.assertIn("xyend", result) def test_download_polls_image_ready(self): self.mock_lib.ArtemisImageReady.return_value = 1 self.mock_lib.ArtemisImageBuffer.return_value = 0 # null ptr → skip write with tempfile.TemporaryDirectory() as tmpdir: devices.cam2.download(filepath=os.path.join(tmpdir, "test.fits")) self.mock_lib.ArtemisImageReady.assert_called() self.mock_lib.ArtemisGetImageData.assert_called() self.mock_lib.ArtemisImageBuffer.assert_called() if __name__ == '__main__': unittest.main()