# --- Tests for standard rotations (Happy Path) --- import math import pytest from _pytest.python_api import approx from src.matrixmania.compute import rot_3D # --- HELPER FUNCTION --- def assert_matrices_3x3_close(m1, m2, abs_tol=1e-9): """ Helper function to compare two 3x3 matrices element-wise using math.isclose for float comparison. """ assert len(m1) == 3 and all(len(row) == 3 for row in m1), "Matrix 1 is not 3x3" assert len(m2) == 3 and all(len(row) == 3 for row in m2), "Matrix 2 is not 3x3" for i in range(3): for j in range(3): assert math.isclose(m1[i][j], m2[i][j], abs_tol=abs_tol), \ f"Mismatch at [{i}][{j}]: {m1[i][j]} != {m2[i][j]}" # --- END HELPER FUNCTION --- def test_90_deg_rotation_x_axis(): """Tests a 90-degree (pi/2) rotation around the X-axis.""" # cos(pi/2) = 0, sin(pi/2) = 1 # Expected: [[1, 0, 0], [0, 0, -1], [0, 1, 0]] expected = [[1.0, 0.0, 0.0], [0.0, 0.0, -1.0], [0.0, 1.0, 0.0]] assert_matrices_3x3_close(rot_3D(math.pi / 2, "x"), expected) def test_90_deg_rotation_y_axis(): """Tests a 90-degree (pi/2) rotation around the Y-axis.""" # cos(pi/2) = 0, sin(pi/2) = 1 # Expected: [[0, 0, 1], [0, 1, 0], [-1, 0, 0]] expected = [[0.0, 0.0, 1.0], [0.0, 1.0, 0.0], [-1.0, 0.0, 0.0]] assert_matrices_3x3_close(rot_3D(math.pi / 2, "y"), expected) def test_90_deg_rotation_z_axis(): """Tests a 90-degree (pi/2) rotation around the Z-axis.""" # cos(pi/2) = 0, sin(pi/2) = 1 # Expected: [[0, -1, 0], [1, 0, 0], [0, 0, 1]] expected = [[0.0, -1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]] assert_matrices_3x3_close(rot_3D(math.pi / 2, "z"), expected) def test_0_radian_rotation(): """Tests 0-radian rotation for all axes (should return identity matrix).""" identity = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]] assert_matrices_3x3_close(rot_3D(0, "x"), identity) assert_matrices_3x3_close(rot_3D(0.0, "y"), identity) assert_matrices_3x3_close(rot_3D(0, "z"), identity) def test_pi_rotation_y_axis(): """Tests a 180-degree (pi) rotation around Y.""" # cos(pi) = -1, sin(pi) = 0 # Expected: [[-1, 0, 0], [0, 1, 0], [0, 0, -1]] expected = [[-1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, -1.0]] assert_matrices_3x3_close(rot_3D(math.pi, "y"), expected) # --- Tests for error handling (Invalid Inputs) --- def test_error_invalid_axis_string(): """Tests that a ValueError is raised for an invalid axis string.""" with pytest.raises(ValueError, match="axis must be either x, y or z"): rot_3D(math.pi / 2, "a") def test_error_invalid_axis_case(): """Tests that the axis check is case-sensitive.""" with pytest.raises(ValueError, match="axis must be either x, y or z"): rot_3D(math.pi / 2, "X") # 'X' is not 'x' def test_error_invalid_axis_type(): """Tests that a ValueError is raised if the axis is not a string.""" with pytest.raises(ValueError, match="axis must be a string"): rot_3D(math.pi / 2, 1) def test_error_invalid_angle_type(): """Tests that a ValueError is raised if the angle is not numeric.""" with pytest.raises(ValueError, match="instance of a numeric datatype"): rot_3D("pi", "x") def test_error_invalid_angle_type_none(): """Tests that a ValueError is raised if the angle is None.""" with pytest.raises(ValueError, match="instance of a numeric datatype"): rot_3D(None, "z")