hunterMakesPy 0.1.1__py3-none-any.whl → 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,327 +1,10 @@
1
1
  """Pytest tests you can use in your package to test some hunterMakesPy functions.
2
2
 
3
3
  Each function in this module returns a list of test functions that can be used with `pytest.parametrize`.
4
- """
5
- from collections.abc import Callable, Iterable, Iterator
6
- from hunterMakesPy import defineConcurrencyLimit, intInnit, oopsieKwargsie
7
- from typing import Any, NoReturn
8
- from unittest.mock import Mock, patch
9
- import pytest
10
-
11
- def PytestFor_defineConcurrencyLimit(callableToTest: Callable[..., int] = defineConcurrencyLimit, cpuCount: int = 8) -> list[tuple[str, Callable[[], None]]]: # noqa: C901
12
- """Return a list of test functions to validate concurrency limit behavior.
13
-
14
- This function provides a comprehensive test suite for validating concurrency limit parsing
15
- and computation, checking both valid and invalid input scenarios.
16
-
17
- Parameters
18
- ----------
19
- callableToTest : Callable[[bool | float | int | None, int], int] = defineConcurrencyLimit
20
- The function to test, which should accept various input types and return an integer
21
- representing the concurrency limit.
22
- cpuCount : int = 8
23
- The number of CPUs to simulate in the test environment.
24
-
25
- Returns
26
- -------
27
- listOfTestFunctions : list[tuple[str, Callable[[], None]]]
28
- A list of tuples, each containing a string describing the test case and a callable
29
- test function that implements the test case.
30
-
31
- Examples
32
- --------
33
- Run each test on `hunterMakesPy.defineConcurrencyLimit`:
34
- ```python
35
- from hunterMakesPy.pytestForYourUse import PytestFor_defineConcurrencyLimit
36
-
37
- listOfTests = PytestFor_defineConcurrencyLimit()
38
- for nameOfTest, callablePytest in listOfTests:
39
- callablePytest()
40
- ```
41
-
42
- Or, run each test on your function that has a compatible signature:
43
- ```python
44
- from hunterMakesPy.pytestForYourUse import PytestFor_defineConcurrencyLimit
45
- from packageLocal import functionLocal
46
-
47
- @pytest.mark.parametrize("nameOfTest,callablePytest", PytestFor_defineConcurrencyLimit(callableToTest=functionLocal))
48
- def test_functionLocal(nameOfTest, callablePytest):
49
- callablePytest()
50
- ```
51
-
52
- """
53
- @patch('multiprocessing.cpu_count', return_value=cpuCount)
54
- def testDefaults(_mockCpu: Mock) -> None:
55
- listOfParameters: list[bool | int | None] = [None, False, 0]
56
- for limitParameter in listOfParameters:
57
- assert callableToTest(limit=limitParameter, cpuTotal=cpuCount) == cpuCount
58
-
59
- @patch('multiprocessing.cpu_count', return_value=cpuCount)
60
- def testDirectIntegers(_mockCpu: Mock) -> None:
61
- for limitParameter in [1, 4, 16]:
62
- assert callableToTest(limit=limitParameter, cpuTotal=cpuCount) == limitParameter
63
-
64
- @patch('multiprocessing.cpu_count', return_value=cpuCount)
65
- def testFractionalFloats(_mockCpu: Mock) -> None:
66
- testCases: dict[float, int] = {
67
- 0.5: cpuCount // 2,
68
- 0.25: cpuCount // 4,
69
- 0.75: int(cpuCount * 0.75)
70
- }
71
- for limit, expected in testCases.items():
72
- assert callableToTest(limit=limit, cpuTotal=cpuCount) == expected
73
-
74
- @patch('multiprocessing.cpu_count', return_value=cpuCount)
75
- def testMinimumOne(_mockCpu: Mock) -> None:
76
- listOfParameters: list[float | int] = [-10, -0.99, 0.1]
77
- for limitParameter in listOfParameters:
78
- assert callableToTest(limit=limitParameter, cpuTotal=cpuCount) >= 1
79
-
80
- @patch('multiprocessing.cpu_count', return_value=cpuCount)
81
- def testBooleanTrue(_mockCpu: Mock) -> None:
82
- assert callableToTest(limit=True, cpuTotal=cpuCount) == 1
83
- assert callableToTest(limit='True', cpuTotal=cpuCount) == 1 # pyright: ignore[reportArgumentType]
84
- assert callableToTest(limit='TRUE', cpuTotal=cpuCount) == 1 # pyright: ignore[reportArgumentType]
85
- assert callableToTest(limit=' true ', cpuTotal=cpuCount) == 1 # pyright: ignore[reportArgumentType]
86
-
87
- @patch('multiprocessing.cpu_count', return_value=cpuCount)
88
- def testInvalidStrings(_mockCpu: Mock) -> None:
89
- for stringInput in ["invalid", "True but not quite", "None of the above"]:
90
- with pytest.raises(ValueError, match="must be a number, `True`, `False`, or `None`"):
91
- callableToTest(limit=stringInput, cpuTotal=cpuCount) # pyright: ignore[reportArgumentType]
92
-
93
- @patch('multiprocessing.cpu_count', return_value=cpuCount)
94
- def testStringNumbers(_mockCpu: Mock) -> None:
95
- testCases: list[tuple[str, int]] = [
96
- ("1.51", 2),
97
- ("-2.51", 5),
98
- ("4", 4),
99
- ("0.5", 4),
100
- ("-0.25", 6),
101
- ]
102
- for stringNumber, expectedLimit in testCases:
103
- assert callableToTest(limit=stringNumber, cpuTotal=cpuCount) == expectedLimit # pyright: ignore[reportArgumentType]
104
-
105
- return [
106
- ('testDefaults', testDefaults),
107
- ('testDirectIntegers', testDirectIntegers),
108
- ('testFractionalFloats', testFractionalFloats),
109
- ('testMinimumOne', testMinimumOne),
110
- ('testBooleanTrue', testBooleanTrue),
111
- ('testInvalidStrings', testInvalidStrings),
112
- ('testStringNumbers', testStringNumbers)
113
- ]
114
-
115
- def PytestFor_intInnit(callableToTest: Callable[[Iterable[int], str | None, type[Any] | None], list[int]] = intInnit) -> list[tuple[str, Callable[[], None]]]: # noqa: C901
116
- """Return a list of test functions to validate integer initialization behavior.
117
-
118
- This function provides a comprehensive test suite for validating integer parsing
119
- and initialization, checking both valid and invalid input scenarios.
120
-
121
- Parameters
122
- ----------
123
- callableToTest : Callable[[Iterable[int], str | None, type[Any] | None], list[int]] = intInnit
124
- The function to test. Should accept a sequence of integer-compatible values,
125
- an optional parameter name string, and an optional parameter type.
126
- Returns a list of validated integers.
127
-
128
- Returns
129
- -------
130
- listOfTestFunctions : list[tuple[str, Callable[[], None]]]
131
- A list of tuples containing a string describing the test case and a callable
132
- test function implementing the test case.
133
-
134
- Examples
135
- --------
136
- Run tests on `hunterMakesPy.intInnit`:
137
- ```python
138
- from hunterMakesPy.pytestForYourUse import PytestFor_intInnit
139
-
140
- listOfTests = PytestFor_intInnit()
141
- for nameOfTest, callablePytest in listOfTests:
142
- callablePytest()
143
- ```
144
-
145
- Run tests on your compatible function:
146
- ```python
147
- from hunterMakesPy.pytestForYourUse import PytestFor_intInnit
148
- from packageLocal import functionLocal
149
-
150
- @pytest.mark.parametrize("nameOfTest,callablePytest",
151
- PytestFor_intInnit(callableToTest=functionLocal))
152
- def test_functionLocal(nameOfTest, callablePytest):
153
- callablePytest()
154
- ```
155
-
156
- """
157
- def testHandlesValidIntegers() -> None:
158
- assert callableToTest([2, 3, 5, 8], 'test', None) == [2, 3, 5, 8]
159
- assert callableToTest([13.0, 21.0, 34.0], 'test', None) == [13, 21, 34] # pyright: ignore[reportArgumentType]
160
- assert callableToTest(['55', '89', '144'], 'test', None) == [55, 89, 144] # pyright: ignore[reportArgumentType]
161
- assert callableToTest([' 233 ', '377', '-610'], 'test', None) == [233, 377, -610] # pyright: ignore[reportArgumentType]
162
-
163
- def testRejectsNonWholeNumbers() -> None:
164
- listInvalidNumbers: list[float] = [13.7, 21.5, 34.8, -55.9]
165
- for invalidNumber in listInvalidNumbers:
166
- with pytest.raises(ValueError):
167
- callableToTest([invalidNumber], 'test', None) # pyright: ignore[reportArgumentType]
168
-
169
- def testRejectsBooleans() -> None:
170
- with pytest.raises(TypeError):
171
- callableToTest([True, False], 'test', None)
172
4
 
173
- def testRejectsInvalidStrings() -> None:
174
- for invalidString in ['NW', '', ' ', 'SE.SW']:
175
- with pytest.raises(ValueError):
176
- callableToTest([invalidString], 'test', None) # pyright: ignore[reportArgumentType]
177
-
178
- def testRejectsEmptyList() -> None:
179
- with pytest.raises(ValueError):
180
- callableToTest([], 'test', None)
181
-
182
- def testHandlesMixedValidTypes() -> None:
183
- assert callableToTest([13, '21', 34.0], 'test', None) == [13, 21, 34] # pyright: ignore[reportArgumentType]
184
-
185
- def testHandlesBytes() -> None:
186
- validCases: list[tuple[list[bytes], str, list[int]]] = [
187
- ([b'123'], '123', [123]),
188
- ]
189
- for inputData, testName, expected in validCases:
190
- assert callableToTest(inputData, testName, None) == expected # pyright: ignore[reportArgumentType]
191
-
192
- extendedCases: list[tuple[list[bytes], str, list[int]]] = [
193
- ([b'123456789'], '123456789', [123456789]),
194
- ]
195
- for inputData, testName, expected in extendedCases:
196
- assert callableToTest(inputData, testName, None) == expected # pyright: ignore[reportArgumentType]
197
-
198
- invalidCases: list[list[bytes]] = [[b'\x00']]
199
- for inputData in invalidCases:
200
- with pytest.raises(ValueError):
201
- callableToTest(inputData, 'test', None) # pyright: ignore[reportArgumentType]
202
-
203
- def testHandlesMemoryview() -> None:
204
- validCases: list[tuple[list[memoryview], str, list[int]]] = [
205
- ([memoryview(b'123')], '123', [123]),
206
- ]
207
- for inputData, testName, expected in validCases:
208
- assert callableToTest(inputData, testName, None) == expected # pyright: ignore[reportArgumentType]
209
-
210
- largeMemoryviewCase: list[memoryview] = [memoryview(b'9999999999')]
211
- assert callableToTest(largeMemoryviewCase, 'test', None) == [9999999999] # pyright: ignore[reportArgumentType]
212
-
213
- invalidMemoryviewCases: list[list[memoryview]] = [[memoryview(b'\x00')]]
214
- for inputData in invalidMemoryviewCases:
215
- with pytest.raises(ValueError):
216
- callableToTest(inputData, 'test', None) # pyright: ignore[reportArgumentType]
217
-
218
- def testRejectsMutableSequence() -> None:
219
- class MutableList(list[int]):
220
- def __iter__(self) -> Iterator[int]:
221
- self.append(89)
222
- return super().__iter__()
223
- with pytest.raises(RuntimeError, match=".*modified during iteration.*"):
224
- callableToTest(MutableList([13, 21, 34]), 'test', None)
225
-
226
- def testHandlesComplexIntegers() -> None:
227
- testCases: list[tuple[list[complex], list[int]]] = [
228
- ([13+0j], [13]),
229
- ([21+0j, 34+0j], [21, 34])
230
- ]
231
- for inputData, expected in testCases:
232
- assert callableToTest(inputData, 'test', None) == expected # pyright: ignore[reportArgumentType]
233
-
234
- def testRejectsInvalidComplex() -> None:
235
- for invalidComplex in [13+1j, 21+0.5j, 34.5+0j]:
236
- with pytest.raises(ValueError):
237
- callableToTest([invalidComplex], 'test', None) # pyright: ignore[reportArgumentType]
238
-
239
- return [
240
- ('testHandlesValidIntegers', testHandlesValidIntegers),
241
- ('testRejectsNonWholeNumbers', testRejectsNonWholeNumbers),
242
- ('testRejectsBooleans', testRejectsBooleans),
243
- ('testRejectsInvalidStrings', testRejectsInvalidStrings),
244
- ('testRejectsEmptyList', testRejectsEmptyList),
245
- ('testHandlesMixedValidTypes', testHandlesMixedValidTypes),
246
- ('testHandlesBytes', testHandlesBytes),
247
- ('testHandlesMemoryview', testHandlesMemoryview),
248
- ('testRejectsMutableSequence', testRejectsMutableSequence),
249
- ('testHandlesComplexIntegers', testHandlesComplexIntegers),
250
- ('testRejectsInvalidComplex', testRejectsInvalidComplex)
251
- ]
252
-
253
- def PytestFor_oopsieKwargsie(callableToTest: Callable[[str], bool | None | str] = oopsieKwargsie) -> list[tuple[str, Callable[[], None]]]: # noqa: C901
254
- """Return a list of test functions to validate string-to-boolean/None conversion behavior.
255
-
256
- This function provides a comprehensive test suite for validating string parsing and conversion
257
- to boolean or None values, with fallback to the original string when appropriate.
258
-
259
- Parameters
260
- ----------
261
- callableToTest : Callable[[str], bool | None | str] = oopsieKwargsie
262
- The function to test, which should accept a string and return either a boolean, None,
263
- or the original input.
264
-
265
- Returns
266
- -------
267
- listOfTestFunctions : list[tuple[str, Callable[[], None]]]
268
- A list of tuples, each containing a string describing the test case and a callable
269
- test function that implements the test case.
270
-
271
- Examples
272
- --------
273
- Run each test on `hunterMakesPy.oopsieKwargsie`:
274
- ```python
275
- from hunterMakesPy.pytestForYourUse import PytestFor_oopsieKwargsie
276
-
277
- listOfTests = PytestFor_oopsieKwargsie()
278
- for nameOfTest, callablePytest in listOfTests:
279
- callablePytest()
280
- ```
281
-
282
- Or, run each test on your function that has a compatible signature:
283
- ```python
284
- from hunterMakesPy.pytestForYourUse import PytestFor_oopsieKwargsie
285
- from packageLocal import functionLocal
286
-
287
- @pytest.mark.parametrize("nameOfTest,callablePytest", PytestFor_oopsieKwargsie(callableToTest=functionLocal))
288
- def test_functionLocal(nameOfTest, callablePytest):
289
- callablePytest()
290
- ```
291
-
292
- """
293
- def testHandlesTrueVariants() -> None:
294
- for variantTrue in ['True', 'TRUE', ' true ', 'TrUe']:
295
- assert callableToTest(variantTrue) is True
296
-
297
- def testHandlesFalseVariants() -> None:
298
- for variantFalse in ['False', 'FALSE', ' false ', 'FaLsE']:
299
- assert callableToTest(variantFalse) is False
300
-
301
- def testHandlesNoneVariants() -> None:
302
- for variantNone in ['None', 'NONE', ' none ', 'NoNe']:
303
- assert callableToTest(variantNone) is None
304
-
305
- def testReturnsOriginalString() -> None:
306
- for stringInput in ['hello', '123', 'True story', 'False alarm']:
307
- assert callableToTest(stringInput) == stringInput
308
-
309
- def testHandlesNonStringObjects() -> None:
310
- class NeverGonnaStringIt:
311
- def __str__(self) -> NoReturn:
312
- message = "Cannot be stringified"
313
- raise TypeError(message)
314
-
315
- assert callableToTest(123) == "123" # pyright: ignore[reportArgumentType]
316
-
317
- neverGonnaStringIt = NeverGonnaStringIt()
318
- result = callableToTest(neverGonnaStringIt) # pyright: ignore[reportArgumentType]
319
- assert result is neverGonnaStringIt
5
+ Note: These test functions are now in `hunterMakesPy.tests` with all other tests.
6
+ """
320
7
 
321
- return [
322
- ('testHandlesTrueVariants', testHandlesTrueVariants),
323
- ('testHandlesFalseVariants', testHandlesFalseVariants),
324
- ('testHandlesNoneVariants', testHandlesNoneVariants),
325
- ('testReturnsOriginalString', testReturnsOriginalString),
326
- ('testHandlesNonStringObjects', testHandlesNonStringObjects)
327
- ]
8
+ from hunterMakesPy.tests.test_parseParameters import (
9
+ PytestFor_defineConcurrencyLimit as PytestFor_defineConcurrencyLimit, PytestFor_intInnit as PytestFor_intInnit,
10
+ PytestFor_oopsieKwargsie as PytestFor_oopsieKwargsie)
@@ -0,0 +1,5 @@
1
+ """Test modules for hunterMakesPy package.
2
+
3
+ This module contains comprehensive test suites for all major functions and features in the hunterMakesPy package. Tests can be run
4
+ independently or imported into your project.
5
+ """
@@ -5,8 +5,7 @@ import pathlib
5
5
  import pytest
6
6
 
7
7
  # SSOT for test data paths and filenames
8
- # SSOT for test data paths and filenames
9
- pathDataSamples = pathlib.Path("tests/dataSamples")
8
+ pathDataSamples = pathlib.Path("hunterMakesPy/tests/dataSamples")
10
9
 
11
10
  # Fixture to provide a temporary directory for filesystem tests
12
11
  @pytest.fixture
@@ -0,0 +1,216 @@
1
+ from hunterMakesPy import PackageSettings, raiseIfNone
2
+ from hunterMakesPy.coping import getIdentifierPackagePACKAGING, getPathPackageINSTALLING
3
+ from hunterMakesPy.tests.conftest import uniformTestFailureMessage
4
+ from pathlib import Path
5
+ import pytest
6
+
7
+ @pytest.mark.parametrize(
8
+ "returnTarget, expected",
9
+ [
10
+ (13, 13),
11
+ (17, 17),
12
+ ("fibonacci", "fibonacci"),
13
+ ("prime", "prime"),
14
+ ([], []),
15
+ ({}, {}),
16
+ (False, False),
17
+ (0, 0),
18
+ ]
19
+ )
20
+ def testRaiseIfNoneReturnsNonNoneValues(returnTarget: object, expected: object) -> None:
21
+ actual = raiseIfNone(returnTarget)
22
+ assert actual == expected, uniformTestFailureMessage(expected, actual, "testRaiseIfNoneReturnsNonNoneValues", returnTarget)
23
+ assert actual is returnTarget, uniformTestFailureMessage(returnTarget, actual, "testRaiseIfNoneReturnsNonNoneValues identity check", returnTarget)
24
+
25
+
26
+ def testRaiseIfNoneRaisesValueErrorWhenGivenNone() -> None:
27
+ with pytest.raises(ValueError, match="A function unexpectedly returned `None`. Hint: look at the traceback immediately before `raiseIfNone`."):
28
+ raiseIfNone(None)
29
+
30
+
31
+ @pytest.mark.parametrize(
32
+ "customMessage",
33
+ [
34
+ "Configuration must include 'host' setting",
35
+ "Database connection failed",
36
+ "User input is required",
37
+ "Network request returned empty response",
38
+ ]
39
+ )
40
+ def testRaiseIfNoneRaisesValueErrorWithCustomMessage(customMessage: str) -> None:
41
+ with pytest.raises(ValueError, match=customMessage):
42
+ raiseIfNone(None, customMessage)
43
+
44
+
45
+ def testRaiseIfNoneWithEmptyStringMessage() -> None:
46
+ with pytest.raises(ValueError, match="A function unexpectedly returned `None`. Hint: look at the traceback immediately before `raiseIfNone`."):
47
+ raiseIfNone(None, "")
48
+
49
+
50
+ def testRaiseIfNonePreservesTypeAnnotations() -> None:
51
+ integerValue: int = raiseIfNone(23)
52
+ assert isinstance(integerValue, int), uniformTestFailureMessage(int, type(integerValue), "testRaiseIfNonePreservesTypeAnnotations", integerValue)
53
+
54
+ stringValue: str = raiseIfNone("cardinal")
55
+ assert isinstance(stringValue, str), uniformTestFailureMessage(str, type(stringValue), "testRaiseIfNonePreservesTypeAnnotations", stringValue)
56
+
57
+ listValue: list[int] = raiseIfNone([29, 31])
58
+ assert isinstance(listValue, list), uniformTestFailureMessage(list, type(listValue), "testRaiseIfNonePreservesTypeAnnotations", listValue)
59
+
60
+ # Tests for PackageSettings dataclass
61
+ @pytest.mark.parametrize(
62
+ "identifierPackageFALLBACK, expectedIdentifierPackage",
63
+ [
64
+ ("astToolFactory", "hunterMakesPy"), # Should read from pyproject.toml
65
+ ("nonExistentPackage", "hunterMakesPy"), # Should read from pyproject.toml
66
+ ("customPackage", "hunterMakesPy"), # Should read from pyproject.toml
67
+ ]
68
+ )
69
+ def testPackageSettingsWithFallbackUsesProjectToml(identifierPackageFALLBACK: str, expectedIdentifierPackage: str) -> None:
70
+ """Test that PackageSettings reads package name from pyproject.toml when using fallback."""
71
+ packageSettings = PackageSettings(identifierPackageFALLBACK)
72
+ assert packageSettings.identifierPackage == expectedIdentifierPackage, uniformTestFailureMessage(
73
+ expectedIdentifierPackage, packageSettings.identifierPackage, "PackageSettings fallback", identifierPackageFALLBACK
74
+ )
75
+
76
+ @pytest.mark.parametrize(
77
+ "explicitIdentifierPackage, expectedIdentifierPackage",
78
+ [
79
+ ("customPackageName", "customPackageName"),
80
+ ("fibonacci", "fibonacci"),
81
+ ("prime", "prime"),
82
+ ("astToolFactory", "astToolFactory"),
83
+ ]
84
+ )
85
+ def testPackageSettingsWithExplicitIdentifierPackage(explicitIdentifierPackage: str, expectedIdentifierPackage: str) -> None:
86
+ """Test that PackageSettings respects explicitly provided identifierPackage."""
87
+ packageSettings = PackageSettings(identifierPackage=explicitIdentifierPackage)
88
+ assert packageSettings.identifierPackage == expectedIdentifierPackage, uniformTestFailureMessage(
89
+ expectedIdentifierPackage, packageSettings.identifierPackage, "PackageSettings explicit identifierPackage", explicitIdentifierPackage
90
+ )
91
+
92
+ @pytest.mark.parametrize(
93
+ "explicitPathPackage, expectedPathPackage",
94
+ [
95
+ (Path("C:/fibonacci/path"), Path("C:/fibonacci/path")),
96
+ (Path("C:/prime/directory"), Path("C:/prime/directory")),
97
+ (Path("/usr/local/lib/package"), Path("/usr/local/lib/package")),
98
+ (Path("relative/path"), Path("relative/path")),
99
+ ]
100
+ )
101
+ def testPackageSettingsWithExplicitPathPackage(explicitPathPackage: Path, expectedPathPackage: Path) -> None:
102
+ """Test that PackageSettings respects explicitly provided pathPackage."""
103
+ packageSettings = PackageSettings(pathPackage=explicitPathPackage)
104
+ assert packageSettings.pathPackage == expectedPathPackage, uniformTestFailureMessage(
105
+ expectedPathPackage, packageSettings.pathPackage, "PackageSettings explicit pathPackage", explicitPathPackage
106
+ )
107
+
108
+ @pytest.mark.parametrize(
109
+ "fileExtension, expectedFileExtension",
110
+ [
111
+ (".fibonacci", ".fibonacci"),
112
+ (".prime", ".prime"),
113
+ (".txt", ".txt"),
114
+ (".md", ".md"),
115
+ (".json", ".json"),
116
+ ]
117
+ )
118
+ def testPackageSettingsWithCustomFileExtension(fileExtension: str, expectedFileExtension: str) -> None:
119
+ """Test that PackageSettings respects custom file extensions."""
120
+ packageSettings = PackageSettings(fileExtension=fileExtension)
121
+ assert packageSettings.fileExtension == expectedFileExtension, uniformTestFailureMessage(
122
+ expectedFileExtension, packageSettings.fileExtension, "PackageSettings custom fileExtension", fileExtension
123
+ )
124
+
125
+ def testPackageSettingsDefaultValues() -> None:
126
+ """Test that PackageSettings has correct default values when no arguments provided."""
127
+ packageSettings = PackageSettings()
128
+
129
+ # Should have default file extension
130
+ assert packageSettings.fileExtension == '.py', uniformTestFailureMessage(
131
+ '.py', packageSettings.fileExtension, "PackageSettings default fileExtension"
132
+ )
133
+
134
+ # identifierPackage should be empty when no fallback provided
135
+ assert packageSettings.identifierPackage == '', uniformTestFailureMessage(
136
+ '', packageSettings.identifierPackage, "PackageSettings default identifierPackage"
137
+ )
138
+
139
+ # pathPackage should remain as Path() when identifierPackage is empty
140
+ expectedPath = Path()
141
+ assert packageSettings.pathPackage == expectedPath, uniformTestFailureMessage(
142
+ expectedPath, packageSettings.pathPackage, "PackageSettings default pathPackage"
143
+ )
144
+
145
+ @pytest.mark.parametrize(
146
+ "identifierPackageFALLBACK, identifierPackage, pathPackage, fileExtension",
147
+ [
148
+ ("fallback", "custom", Path("C:/custom/path"), ".txt"),
149
+ ("fibonacci", "prime", Path("C:/fibonacci/lib"), ".md"),
150
+ ("defaultFallback", "overridePackage", Path("/usr/local/override"), ".json"),
151
+ ]
152
+ )
153
+ def testPackageSettingsAllParametersCombined(
154
+ identifierPackageFALLBACK: str,
155
+ identifierPackage: str,
156
+ pathPackage: Path,
157
+ fileExtension: str
158
+ ) -> None:
159
+ """Test PackageSettings with all parameters provided."""
160
+ packageSettings = PackageSettings(
161
+ identifierPackageFALLBACK,
162
+ identifierPackage=identifierPackage,
163
+ pathPackage=pathPackage,
164
+ fileExtension=fileExtension
165
+ )
166
+
167
+ assert packageSettings.identifierPackage == identifierPackage, uniformTestFailureMessage(
168
+ identifierPackage, packageSettings.identifierPackage, "PackageSettings combined identifierPackage"
169
+ )
170
+ assert packageSettings.pathPackage == pathPackage, uniformTestFailureMessage(
171
+ pathPackage, packageSettings.pathPackage, "PackageSettings combined pathPackage"
172
+ )
173
+ assert packageSettings.fileExtension == fileExtension, uniformTestFailureMessage(
174
+ fileExtension, packageSettings.fileExtension, "PackageSettings combined fileExtension"
175
+ )
176
+
177
+ def testPackageSettingsFallbackIgnoredWhenExplicitIdentifierProvided() -> None:
178
+ """Test that fallback is ignored when explicit identifierPackage is provided."""
179
+ packageSettings = PackageSettings("shouldBeIgnored", identifierPackage="explicit")
180
+ assert packageSettings.identifierPackage == "explicit", uniformTestFailureMessage(
181
+ "explicit", packageSettings.identifierPackage, "PackageSettings fallback ignored"
182
+ )
183
+
184
+ # Tests for helper functions
185
+ @pytest.mark.parametrize(
186
+ "identifierPackageFALLBACK, expectedResult",
187
+ [
188
+ ("fibonacci", "hunterMakesPy"), # Should read from pyproject.toml
189
+ ("prime", "hunterMakesPy"), # Should read from pyproject.toml
190
+ ("nonExistentPackage", "hunterMakesPy"), # Should read from pyproject.toml
191
+ ]
192
+ )
193
+ def testGetIdentifierPackagePACKAGING(identifierPackageFALLBACK: str, expectedResult: str) -> None:
194
+ """Test that getIdentifierPackagePACKAGING reads from pyproject.toml correctly."""
195
+ actual = getIdentifierPackagePACKAGING(identifierPackageFALLBACK)
196
+ assert actual == expectedResult, uniformTestFailureMessage(
197
+ expectedResult, actual, "getIdentifierPackagePACKAGING", identifierPackageFALLBACK
198
+ )
199
+
200
+ @pytest.mark.parametrize(
201
+ "identifierPackage",
202
+ [
203
+ "hunterMakesPy", # This package exists
204
+ "fibonacci", # Non-existent package should fallback to cwd
205
+ "prime", # Non-existent package should fallback to cwd
206
+ ]
207
+ )
208
+ def testGetPathPackageINSTALLING(identifierPackage: str) -> None:
209
+ """Test that getPathPackageINSTALLING returns valid Path objects."""
210
+ actual = getPathPackageINSTALLING(identifierPackage)
211
+ assert isinstance(actual, Path), uniformTestFailureMessage(
212
+ Path, type(actual), "getPathPackageINSTALLING type", identifierPackage
213
+ )
214
+ assert actual.exists() or actual == Path.cwd(), uniformTestFailureMessage(
215
+ "existing path or cwd", actual, "getPathPackageINSTALLING existence", identifierPackage
216
+ )