mirror of
				https://github.com/kevinveenbirkenbach/computer-playbook.git
				synced 2025-10-31 10:19:09 +00:00 
			
		
		
		
	feat(filter_plugins/url_join): add query parameter support
- Support query elements starting with '?' or '&' * First query element normalized to '?', subsequent to '&' * Each query element must be exactly one 'key=value' pair * Query elements may only appear after path elements * Once query starts, no more path elements are allowed - Extend test suite with success and failure cases for query handling See: https://chatgpt.com/share/68b537ea-d198-800f-927a-940c4de832f2
This commit is contained in:
		| @@ -13,7 +13,7 @@ from url_join import url_join | ||||
|  | ||||
|  | ||||
| class TestUrlJoinFilter(unittest.TestCase): | ||||
|     # --- success cases --- | ||||
|     # --- success cases (path only) --- | ||||
|     def test_http_basic(self): | ||||
|         self.assertEqual( | ||||
|             url_join(['http://example.com', 'foo', 'bar']), | ||||
| @@ -32,31 +32,41 @@ class TestUrlJoinFilter(unittest.TestCase): | ||||
|             'myapp+v1://host/section/item' | ||||
|         ) | ||||
|  | ||||
|     def test_scheme_with_path_in_first(self): | ||||
|     def test_only_scheme(self): | ||||
|         self.assertEqual(url_join(['https://']), 'https://') | ||||
|  | ||||
|     # --- success cases with query --- | ||||
|     def test_query_normalization_first_q_then_amp(self): | ||||
|         self.assertEqual( | ||||
|             url_join(['https://example.com/base/', '/deep/', 'leaf']), | ||||
|             'https://example.com/base/deep/leaf' | ||||
|             url_join(['https://example.com', 'api', '?a=1', '&b=2']), | ||||
|             'https://example.com/api?a=1&b=2' | ||||
|         ) | ||||
|  | ||||
|     def test_query_ignores_given_prefix_order(self): | ||||
|         self.assertEqual( | ||||
|             url_join(['https://example.com', '?a=1', '?b=2', '&c=3']), | ||||
|             'https://example.com?a=1&b=2&c=3' | ||||
|         ) | ||||
|  | ||||
|     def test_query_after_path_with_slashes(self): | ||||
|         self.assertEqual( | ||||
|             url_join(['https://example.com/', '/x/', 'y/', '?q=ok']), | ||||
|             'https://example.com/x/y?q=ok' | ||||
|         ) | ||||
|  | ||||
|     def test_query_with_numeric_value(self): | ||||
|         self.assertEqual( | ||||
|             url_join(['https://example.com', '?n=123']), | ||||
|             'https://example.com?n=123' | ||||
|         ) | ||||
|  | ||||
|     def test_none_in_list(self): | ||||
|         self.assertEqual( | ||||
|             url_join(['https://example.com', None, 'foo']), | ||||
|             'https://example.com/foo' | ||||
|             url_join(['https://example.com', None, 'foo', None, '?a=1', None]), | ||||
|             'https://example.com/foo?a=1' | ||||
|         ) | ||||
|  | ||||
|     def test_numeric_parts(self): | ||||
|         self.assertEqual( | ||||
|             url_join(['https://example.com', 123, '456']), | ||||
|             'https://example.com/123/456' | ||||
|         ) | ||||
|  | ||||
|     def test_only_scheme_returns_scheme(self): | ||||
|         self.assertEqual( | ||||
|             url_join(['https://']), | ||||
|             'https://' | ||||
|         ) | ||||
|  | ||||
|     # --- error cases with specific messages --- | ||||
|     # --- error cases --- | ||||
|     def test_none_input_raises(self): | ||||
|         with self.assertRaisesRegex(AnsibleFilterError, r"parts must be a non-empty list; got None"): | ||||
|             url_join(None) | ||||
| @@ -81,6 +91,26 @@ class TestUrlJoinFilter(unittest.TestCase): | ||||
|         with self.assertRaisesRegex(AnsibleFilterError, r"only the first element may contain a scheme"): | ||||
|             url_join(['https://example.com', 'https://elsewhere']) | ||||
|  | ||||
|     def test_path_after_query_raises(self): | ||||
|         with self.assertRaisesRegex(AnsibleFilterError, r"path element .* after query parameters started"): | ||||
|             url_join(['https://example.com', '?a=1', 'still/path']) | ||||
|  | ||||
|     def test_query_element_empty_raises(self): | ||||
|         with self.assertRaisesRegex(AnsibleFilterError, r"query element .* is empty"): | ||||
|             url_join(['https://example.com', '?']) | ||||
|  | ||||
|     def test_query_element_multiple_pairs_raises(self): | ||||
|         with self.assertRaisesRegex(AnsibleFilterError, r"must contain exactly one 'key=value' pair"): | ||||
|             url_join(['https://example.com', '?a=1&b=2']) | ||||
|  | ||||
|     def test_query_element_missing_equal_raises(self): | ||||
|         with self.assertRaisesRegex(AnsibleFilterError, r"must match 'key=value'"): | ||||
|             url_join(['https://example.com', '&a']) | ||||
|  | ||||
|     def test_query_element_bad_chars_raises(self): | ||||
|         with self.assertRaisesRegex(AnsibleFilterError, r"must match 'key=value'"): | ||||
|             url_join(['https://example.com', '?a#=1']) | ||||
|  | ||||
|     def test_unstringifiable_first_part_raises(self): | ||||
|         class Bad: | ||||
|             def __str__(self): | ||||
| @@ -92,8 +122,8 @@ class TestUrlJoinFilter(unittest.TestCase): | ||||
|         class Bad: | ||||
|             def __str__(self): | ||||
|                 raise ValueError("boom") | ||||
|         with self.assertRaisesRegex(AnsibleFilterError, r"unable to convert part at index 2"): | ||||
|             url_join(['https://example.com', 'ok', Bad()]) | ||||
|         with self.assertRaisesRegex(AnsibleFilterError, r"unable to convert part at index 3"): | ||||
|             url_join(['https://example.com', 'ok', '?a=1', Bad()]) | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|   | ||||
		Reference in New Issue
	
	Block a user