Coverage for tests/unit/no_torch/test_loading_edge_cases.py: 92%

1"""Edge case tests for zanj/loading.py to improve coverage."""

2

3 from __future__ import annotations

4

5 import pytest

6

7 from zanj.consts import _FORMAT_KEY

8 from zanj.loading import (

9 LoaderHandler,

10 _populate_externals_error_checking,

11 get_item_loader,

12 load_item_recursive,

13)

14

15

16 class TestPopulateExternalsErrorChecking:

17 """Tests for _populate_externals_error_checking function."""

18

19 def test_external_item_missing_data_field(self):

20 """Line 68: External item with format key but no data field should raise KeyError."""

21 malformed_item = {

22 _FORMAT_KEY: "list:external",

23 "some_field": "value",

24 # missing "data" field

25 }

26 with pytest.raises(KeyError, match="expected an external item"):

27 _populate_externals_error_checking("key", malformed_item)

28

29 def test_sequence_with_non_int_key(self):

30 """Line 75-76: Accessing a sequence with a non-int key should raise TypeError."""

31 sequence = [1, 2, 3]

32 with pytest.raises(TypeError, match="expected int"):

33 _populate_externals_error_checking("string_key", sequence)

34

35 def test_sequence_with_out_of_range_key(self):

36 """Line 77-78: Accessing a sequence with an out-of-range index should raise IndexError."""

37 sequence = [1, 2, 3]

38 with pytest.raises(IndexError, match="index out of range"):

39 _populate_externals_error_checking(100, sequence)

40

41 def test_mapping_with_non_str_key(self):

42 """Line 82-83: Accessing a mapping with a non-str key should raise TypeError."""

43 mapping = {"a": 1, "b": 2}

44 with pytest.raises(TypeError, match="expected str"):

45 _populate_externals_error_checking(123, mapping)

46

47 def test_mapping_with_missing_key(self):

48 """Line 84-85: Accessing a mapping with a missing key should raise KeyError."""

49 mapping = {"a": 1, "b": 2}

50 with pytest.raises(KeyError, match="key not in dict"):

51 _populate_externals_error_checking("missing_key", mapping)

52

53 def test_invalid_item_type(self):

54 """Line 88-89: Passing an invalid item type should raise TypeError."""

55 invalid_item = 42 # int is neither sequence nor mapping

56 with pytest.raises(TypeError, match="expected dict or list"):

57 _populate_externals_error_checking("key", invalid_item)

58

59 def test_valid_sequence_access(self):

60 """Valid sequence access should not raise and return False."""

61 sequence = [1, 2, 3]

62 result = _populate_externals_error_checking(1, sequence)

63 assert result is False

64

65 def test_valid_mapping_access(self):

66 """Valid mapping access should not raise and return False."""

67 mapping = {"a": 1, "b": 2}

68 result = _populate_externals_error_checking("a", mapping)

69 assert result is False

70

71 def test_external_item_with_data_returns_true(self):

72 """External item with data field should return True."""

73 external_item = {

74 _FORMAT_KEY: "list:external",

75 "data": [1, 2, 3],

76 }

77 result = _populate_externals_error_checking("key", external_item)

78 assert result is True

79

80

81 class TestLoaderHandlerFromFormattedClass:

82 """Tests for LoaderHandler.from_formattedclass method."""

83

84 def test_missing_serialize_method(self):

85 """Line 137: Class missing serialize method should raise AssertionError."""

86

87 class MissingSerialize:

88 __muutils_format__ = "test"

89

90 @classmethod

91 def load(cls, json_item, path=None, z=None):

92 pass

93

94 with pytest.raises(AssertionError):

95 LoaderHandler.from_formattedclass(MissingSerialize)

96

97 def test_missing_load_method(self):

98 """Line 139: Class missing load method should raise AssertionError."""

99

100 class MissingLoad:

101 __muutils_format__ = "test"

102

103 def serialize(self):

104 pass

105

106 with pytest.raises(AssertionError):

107 LoaderHandler.from_formattedclass(MissingLoad)

108

109 def test_missing_format_key(self):

110 """Line 141: Class missing __muutils_format__ should raise AssertionError."""

111

112 class MissingFormat:

113 def serialize(self):

114 pass

115

116 @classmethod

117 def load(cls, json_item, path=None, z=None):

118 pass

119

120 with pytest.raises(AssertionError):

121 LoaderHandler.from_formattedclass(MissingFormat)

122

123 def test_non_string_format_key(self):

124 """Line 142: Class with non-string __muutils_format__ should raise AssertionError."""

125

126 class NonStringFormat:

127 __muutils_format__ = 12345 # should be string

128

129 def serialize(self):

130 pass

131

132 @classmethod

133 def load(cls, json_item, path=None, z=None):

134 pass

135

136 with pytest.raises(AssertionError):

137 LoaderHandler.from_formattedclass(NonStringFormat)

138

139 def test_valid_formatted_class(self):

140 """Valid class should create a LoaderHandler successfully."""

141

142 class ValidClass:

143 __muutils_format__ = "test.ValidClass"

144

145 def serialize(self):

146 return {}

147

148 @classmethod

149 def load(cls, json_item, path=None, z=None):

150 return cls()

151

152 handler = LoaderHandler.from_formattedclass(ValidClass)

153 assert handler.uid == "test.ValidClass"

154 assert "ValidClass" in handler.desc

155

156

157 class TestGetItemLoader:

158 """Tests for get_item_loader function."""

159

160 def test_non_string_format_key(self):

161 """Line 306-309: Item with non-string __muutils_format__ should raise TypeError."""

162 malformed_item = {

163 _FORMAT_KEY: 12345, # should be str

164 "data": [1, 2, 3],

165 }

166 with pytest.raises(TypeError, match="invalid __muutils_format__ type"):

167 get_item_loader(malformed_item, ("test",))

168

169

170 class TestLoadItemRecursive:

171 """Tests for load_item_recursive function."""

172

173 def test_unloadable_type_strict_mode(self):

174 """Line 395-398: Unloadable type with allow_not_loading=False should raise ValueError."""

175

176 # Create a custom object that isn't handled by any loader

177 class CustomUnloadable:

178 pass

179

180 item = CustomUnloadable()

181 with pytest.raises(ValueError, match="unknown type"):

182 load_item_recursive(item, ("test",), None, allow_not_loading=False)

183

184 def test_unloadable_type_permissive_mode(self):

185 """Line 395-396: Unloadable type with allow_not_loading=True should return as-is."""

186

187 class CustomUnloadable:

188 pass

189

190 item = CustomUnloadable()

191 result = load_item_recursive(item, ("test",), None, allow_not_loading=True)

192 assert result is item