from typing import Any from unittest.mock import MagicMock import pytest from app.library.ag_utils import ( ag, ag_delete, ag_exists, ag_set, ag_sets, get_value, ) class TestGetValue: """Test the get_value function.""" def test_get_value_with_value(self): """Test get_value returns the value when not callable.""" assert get_value(42) == 42 assert get_value("test") == "test" assert get_value([1, 2, 3]) == [1, 2, 3] assert get_value({"key": "value"}) == {"key": "value"} assert get_value(None) is None def test_get_value_with_callable(self): """Test get_value calls the function when callable.""" mock_func = MagicMock(return_value="called") result = get_value(mock_func) assert result == "called" mock_func.assert_called_once() def test_get_value_with_lambda(self): """Test get_value with lambda functions.""" assert get_value(lambda: 100) == 100 assert get_value(lambda: "lambda_result") == "lambda_result" def test_get_value_with_function(self): """Test get_value with regular functions.""" def test_func(): return "function_result" assert get_value(test_func) == "function_result" class TestAgSet: """Test the ag_set function.""" def test_ag_set_simple_path(self): """Test setting a value with simple path.""" data = {} result = ag_set(data, "key", "value") assert result == {"key": "value"} assert data == {"key": "value"} def test_ag_set_nested_path(self): """Test setting a value with nested path.""" data = {} result = ag_set(data, "a.b.c", "nested_value") expected = {"a": {"b": {"c": "nested_value"}}} assert result == expected assert data == expected def test_ag_set_existing_structure(self): """Test setting a value in existing structure.""" data = {"a": {"b": {"existing": "value"}}} ag_set(data, "a.b.c", "new_value") expected = {"a": {"b": {"existing": "value", "c": "new_value"}}} assert data == expected def test_ag_set_overwrite_existing(self): """Test overwriting existing value.""" data = {"a": {"b": "old_value"}} ag_set(data, "a.b", "new_value") assert data == {"a": {"b": "new_value"}} def test_ag_set_custom_separator(self): """Test using custom separator.""" data = {} ag_set(data, "a/b/c", "value", separator="/") assert data == {"a": {"b": {"c": "value"}}} def test_ag_set_overwrite_non_dict_intermediate(self): """Test overwriting non-dict intermediate value with dict.""" data = {"a": "not_a_dict"} ag_set(data, "a.b", "value") # The function should overwrite "not_a_dict" with a dict containing the new path expected = {"a": {"b": "value"}} assert data == expected def test_ag_set_error_on_non_dict_final(self): """Test error when final target is not a dict.""" data: Any = "not_a_dict" with pytest.raises(RuntimeError, match="Cannot set value at path 'key'"): ag_set(data, "key", "value") class TestAg: """Test the ag function.""" def test_ag_with_none_path(self): """Test ag returns whole structure when path is None.""" data = {"a": 1, "b": 2} assert ag(data, None) == data assert ag(data, "") == data def test_ag_simple_dict_access(self): """Test simple dictionary key access.""" data = {"x": 10, "y": 20} assert ag(data, "x") == 10 assert ag(data, "y") == 20 def test_ag_missing_key_with_default(self): """Test accessing missing key returns default.""" data = {"x": 10} assert ag(data, "missing", default=0) == 0 assert ag(data, "missing", default="not_found") == "not_found" def test_ag_nested_dict_access(self): """Test nested dictionary access with dot notation.""" data = {"a": {"b": {"c": 42}}} assert ag(data, "a.b.c") == 42 def test_ag_nested_missing_key(self): """Test nested access with missing intermediate keys.""" data = {"a": {"b": 1}} assert ag(data, "a.missing.c", default="default") == "default" assert ag(data, "missing.b.c", default="default") == "default" def test_ag_list_access_by_index(self): """Test accessing list elements by index.""" data = [10, 20, 30] assert ag(data, 0) == 10 assert ag(data, 1) == 20 assert ag(data, 2) == 30 def test_ag_list_access_out_of_bounds(self): """Test list access with out of bounds index.""" data = [10, 20] assert ag(data, 5, default="default") == "default" assert ag(data, -3, default="default") == "default" # -3 is out of bounds for 2-element list def test_ag_list_negative_indices(self): """Test list access with valid negative indices.""" data = [10, 20, 30] assert ag(data, -1) == 30 # Last element assert ag(data, -2) == 20 # Second to last assert ag(data, -3) == 10 # First element def test_ag_mixed_dict_list_access(self): """Test accessing nested structure with dicts and lists.""" data = {"items": [{"name": "item1"}, {"name": "item2"}]} assert ag(data, "items.0.name") == "item1" assert ag(data, "items.1.name") == "item2" def test_ag_list_of_paths(self): """Test trying multiple paths and returning first found.""" data = {"a": 1, "b": 2} assert ag(data, ["missing1", "missing2", "a"], default="default") == 1 assert ag(data, ["missing1", "b", "a"], default="default") == 2 assert ag(data, ["missing1", "missing2"], default="default") == "default" def test_ag_custom_separator(self): """Test using custom separator.""" data = {"a": {"b": {"c": 100}}} assert ag(data, "a/b/c", separator="/") == 100 def test_ag_with_none_values(self): """Test ag behavior with None values.""" data = {"a": None, "b": {"c": None}} assert ag(data, "a", default="default") == "default" assert ag(data, "b.c", default="default") == "default" def test_ag_with_callable_default(self): """Test ag with callable default value.""" data = {} mock_default = MagicMock(return_value="called_default") result = ag(data, "missing", default=mock_default) assert result == "called_default" mock_default.assert_called_once() def test_ag_with_object_attributes(self): """Test ag with object attributes using vars().""" class TestObj: def __init__(self): self.attr1 = "value1" self.attr2 = {"nested": "value2"} obj = TestObj() assert ag(obj, "attr1") == "value1" assert ag(obj, "attr2.nested") == "value2" def test_ag_with_non_dict_non_list_fallback(self): """Test ag fallback for non-dict, non-list objects without vars.""" class NoVarsObj: __slots__ = ["value"] def __init__(self): self.value = "test" obj = NoVarsObj() assert ag(obj, "anything", default="fallback") == "fallback" class TestAgSets: """Test the ag_sets function.""" def test_ag_sets_multiple_paths(self): """Test setting multiple paths at once.""" data = {} path_values = {"a.b.c": "value1", "a.b.d": "value2", "x.y": "value3"} result = ag_sets(data, path_values) expected = {"a": {"b": {"c": "value1", "d": "value2"}}, "x": {"y": "value3"}} assert result == expected assert data == expected def test_ag_sets_custom_separator(self): """Test ag_sets with custom separator.""" data = {} path_values = {"a/b/c": "value1", "x/y": "value2"} ag_sets(data, path_values, separator="/") expected = {"a": {"b": {"c": "value1"}}, "x": {"y": "value2"}} assert data == expected def test_ag_sets_existing_structure(self): """Test ag_sets with existing data structure.""" data = {"a": {"existing": "value"}} path_values = {"a.b.c": "new_value", "d": "another_value"} ag_sets(data, path_values) expected = {"a": {"existing": "value", "b": {"c": "new_value"}}, "d": "another_value"} assert data == expected def test_ag_sets_empty_dict(self): """Test ag_sets with empty path_values dict.""" data = {"existing": "data"} original = data.copy() ag_sets(data, {}) assert data == original class TestAgExists: """Test the ag_exists function.""" def test_ag_exists_simple_dict_key(self): """Test checking existence of simple dict keys.""" data = {"a": "value", "b": None, "c": 0} assert ag_exists(data, "a") is True assert ag_exists(data, "b") is False # None values return False assert ag_exists(data, "c") is True # 0 is not None assert ag_exists(data, "missing") is False def test_ag_exists_nested_path(self): """Test checking existence of nested paths.""" data = {"a": {"b": {"c": "value", "d": None}}} assert ag_exists(data, "a.b.c") is True assert ag_exists(data, "a.b.d") is False # None value assert ag_exists(data, "a.b.missing") is False assert ag_exists(data, "a.missing.c") is False def test_ag_exists_list_indices(self): """Test checking existence of list indices.""" data = [10, None, 30] assert ag_exists(data, 0) is True assert ag_exists(data, 1) is False # None value assert ag_exists(data, 2) is True assert ag_exists(data, 5) is False # Out of bounds def test_ag_exists_mixed_structure(self): """Test checking existence in mixed dict/list structure.""" data = {"items": [{"name": "item1"}, None, {"name": "item3"}]} assert ag_exists(data, "items.0.name") is True assert ag_exists(data, "items.1.name") is False # items[1] is None assert ag_exists(data, "items.2.name") is True assert ag_exists(data, "items.5.name") is False # Out of bounds def test_ag_exists_custom_separator(self): """Test ag_exists with custom separator.""" data = {"a": {"b": {"c": "value"}}} assert ag_exists(data, "a/b/c", separator="/") is True assert ag_exists(data, "a/b/missing", separator="/") is False def test_ag_exists_with_object(self): """Test ag_exists with object using vars().""" class TestObj: def __init__(self): self.attr = "value" self.nested = {"key": "value"} obj = TestObj() assert ag_exists(obj, "attr") is True assert ag_exists(obj, "nested.key") is True assert ag_exists(obj, "missing") is False def test_ag_exists_with_non_vars_object(self): """Test ag_exists with object that doesn't support vars().""" class NoVarsObj: __slots__ = [] obj = NoVarsObj() assert ag_exists(obj, "anything") is False class TestAgDelete: """Test the ag_delete function.""" def test_ag_delete_simple_dict_key(self): """Test deleting simple dictionary keys.""" data = {"a": 1, "b": 2, "c": 3} result = ag_delete(data, "b") assert result == {"a": 1, "c": 3} assert data == {"a": 1, "c": 3} def test_ag_delete_nested_path(self): """Test deleting nested dictionary paths.""" data = {"a": {"b": {"c": 1, "d": 2}, "e": 3}} ag_delete(data, "a.b.c") expected = {"a": {"b": {"d": 2}, "e": 3}} assert data == expected def test_ag_delete_list_index(self): """Test deleting list elements by index.""" data = [10, 20, 30, 40] ag_delete(data, 1) assert data == [10, 30, 40] def test_ag_delete_mixed_structure(self): """Test deleting from mixed dict/list structure.""" data = {"items": [{"name": "item1"}, {"name": "item2", "value": 100}]} ag_delete(data, "items.1.value") expected = {"items": [{"name": "item1"}, {"name": "item2"}]} assert data == expected def test_ag_delete_multiple_paths(self): """Test deleting multiple paths at once.""" data = {"a": {"b": 1, "c": 2}, "d": 3, "e": 4} ag_delete(data, ["a.b", "d"]) expected = {"a": {"c": 2}, "e": 4} assert data == expected def test_ag_delete_custom_separator(self): """Test ag_delete with custom separator.""" data = {"a": {"b": {"c": 1}}} ag_delete(data, "a/b/c", separator="/") assert data == {"a": {"b": {}}} def test_ag_delete_missing_key(self): """Test deleting non-existent keys (should not raise error).""" data = {"a": {"b": 1}} original = {"a": {"b": 1}} # Should not raise error and not modify data ag_delete(data, "missing") ag_delete(data, "a.missing") ag_delete(data, "a.b.c") assert data == original def test_ag_delete_out_of_bounds_list(self): """Test deleting out of bounds list index (should not raise error).""" data = [1, 2, 3] original = [1, 2, 3] ag_delete(data, 10) # Out of bounds ag_delete(data, -1) # Negative index assert data == original def test_ag_delete_with_object(self): """Test ag_delete with object using vars().""" class TestObj: def __init__(self): self.attr1 = "value1" self.attr2 = {"nested": "value2"} obj = TestObj() ag_delete(obj, "attr1") # Check that attr1 was deleted assert not hasattr(obj, "attr1") assert hasattr(obj, "attr2") def test_ag_delete_invalid_list_string_index(self): """Test ag_delete with invalid string index for list.""" data = {"items": [1, 2, 3]} original_items = [1, 2, 3] ag_delete(data, "items.invalid_index") # Should not modify the list assert data["items"] == original_items def test_ag_delete_path_through_none(self): """Test ag_delete when path goes through None value.""" data = {"a": {"b": None}} original = {"a": {"b": None}} ag_delete(data, "a.b.c") # Can't traverse through None assert data == original class TestEdgeCases: """Test edge cases and error conditions.""" def test_empty_data_structures(self): """Test functions with empty data structures.""" empty_dict = {} empty_list = [] # ag function assert ag(empty_dict, "key", default="default") == "default" assert ag(empty_list, 0, default="default") == "default" # ag_exists function assert ag_exists(empty_dict, "key") is False assert ag_exists(empty_list, 0) is False # ag_delete function (should not raise errors) ag_delete(empty_dict, "key") ag_delete(empty_list, 0) assert empty_dict == {} assert empty_list == [] def test_deeply_nested_structures(self): """Test with deeply nested structures.""" # Create 10-level deep structure data = {} current = data for i in range(10): current[f"level{i}"] = {} current = current[f"level{i}"] current["value"] = "deep_value" path = ".".join(f"level{i}" for i in range(10)) + ".value" # Test ag function assert ag(data, path) == "deep_value" # Test ag_exists function assert ag_exists(data, path) is True # Test ag_set function ag_set(data, path.replace(".value", ".new_value"), "new_deep_value") new_path = ".".join(f"level{i}" for i in range(10)) + ".new_value" assert ag(data, new_path) == "new_deep_value" def test_special_characters_in_keys(self): """Test with special characters in dictionary keys.""" data = {"key with spaces": "value1", "key.with.dots": "value2", "key/with/slashes": "value3"} # These should work with direct key access assert ag(data, "key with spaces") == "value1" assert ag(data, "key.with.dots") == "value2" assert ag(data, "key/with/slashes") == "value3" # Test existence assert ag_exists(data, "key with spaces") is True assert ag_exists(data, "key.with.dots") is True # Test deletion ag_delete(data, "key with spaces") assert "key with spaces" not in data def test_type_consistency(self): """Test type consistency across operations.""" data = {"string": "test", "number": 42, "boolean": True, "list": [1, 2, 3], "dict": {"nested": "value"}} # All values should be retrieved correctly assert ag(data, "string") == "test" assert ag(data, "number") == 42 assert ag(data, "boolean") is True assert ag(data, "list") == [1, 2, 3] assert ag(data, "dict") == {"nested": "value"} # All should exist for key in data: assert ag_exists(data, key) is True