hatch-xclam 0.7.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.
Files changed (93) hide show
  1. hatch/__init__.py +21 -0
  2. hatch/cli_hatch.py +2748 -0
  3. hatch/environment_manager.py +1375 -0
  4. hatch/installers/__init__.py +25 -0
  5. hatch/installers/dependency_installation_orchestrator.py +636 -0
  6. hatch/installers/docker_installer.py +545 -0
  7. hatch/installers/hatch_installer.py +198 -0
  8. hatch/installers/installation_context.py +109 -0
  9. hatch/installers/installer_base.py +195 -0
  10. hatch/installers/python_installer.py +342 -0
  11. hatch/installers/registry.py +179 -0
  12. hatch/installers/system_installer.py +588 -0
  13. hatch/mcp_host_config/__init__.py +38 -0
  14. hatch/mcp_host_config/backup.py +458 -0
  15. hatch/mcp_host_config/host_management.py +572 -0
  16. hatch/mcp_host_config/models.py +602 -0
  17. hatch/mcp_host_config/reporting.py +181 -0
  18. hatch/mcp_host_config/strategies.py +513 -0
  19. hatch/package_loader.py +263 -0
  20. hatch/python_environment_manager.py +734 -0
  21. hatch/registry_explorer.py +171 -0
  22. hatch/registry_retriever.py +335 -0
  23. hatch/template_generator.py +179 -0
  24. hatch_xclam-0.7.0.dist-info/METADATA +150 -0
  25. hatch_xclam-0.7.0.dist-info/RECORD +93 -0
  26. hatch_xclam-0.7.0.dist-info/WHEEL +5 -0
  27. hatch_xclam-0.7.0.dist-info/entry_points.txt +2 -0
  28. hatch_xclam-0.7.0.dist-info/licenses/LICENSE +661 -0
  29. hatch_xclam-0.7.0.dist-info/top_level.txt +2 -0
  30. tests/__init__.py +1 -0
  31. tests/run_environment_tests.py +124 -0
  32. tests/test_cli_version.py +122 -0
  33. tests/test_data/packages/basic/base_pkg/hatch_mcp_server.py +18 -0
  34. tests/test_data/packages/basic/base_pkg/mcp_server.py +21 -0
  35. tests/test_data/packages/basic/base_pkg_v2/hatch_mcp_server.py +18 -0
  36. tests/test_data/packages/basic/base_pkg_v2/mcp_server.py +21 -0
  37. tests/test_data/packages/basic/utility_pkg/hatch_mcp_server.py +18 -0
  38. tests/test_data/packages/basic/utility_pkg/mcp_server.py +21 -0
  39. tests/test_data/packages/dependencies/complex_dep_pkg/hatch_mcp_server.py +18 -0
  40. tests/test_data/packages/dependencies/complex_dep_pkg/mcp_server.py +21 -0
  41. tests/test_data/packages/dependencies/docker_dep_pkg/hatch_mcp_server.py +18 -0
  42. tests/test_data/packages/dependencies/docker_dep_pkg/mcp_server.py +21 -0
  43. tests/test_data/packages/dependencies/mixed_dep_pkg/hatch_mcp_server.py +18 -0
  44. tests/test_data/packages/dependencies/mixed_dep_pkg/mcp_server.py +21 -0
  45. tests/test_data/packages/dependencies/python_dep_pkg/hatch_mcp_server.py +18 -0
  46. tests/test_data/packages/dependencies/python_dep_pkg/mcp_server.py +21 -0
  47. tests/test_data/packages/dependencies/simple_dep_pkg/hatch_mcp_server.py +18 -0
  48. tests/test_data/packages/dependencies/simple_dep_pkg/mcp_server.py +21 -0
  49. tests/test_data/packages/dependencies/system_dep_pkg/hatch_mcp_server.py +18 -0
  50. tests/test_data/packages/dependencies/system_dep_pkg/mcp_server.py +21 -0
  51. tests/test_data/packages/error_scenarios/circular_dep_pkg/hatch_mcp_server.py +18 -0
  52. tests/test_data/packages/error_scenarios/circular_dep_pkg/mcp_server.py +21 -0
  53. tests/test_data/packages/error_scenarios/circular_dep_pkg_b/hatch_mcp_server.py +18 -0
  54. tests/test_data/packages/error_scenarios/circular_dep_pkg_b/mcp_server.py +21 -0
  55. tests/test_data/packages/error_scenarios/invalid_dep_pkg/hatch_mcp_server.py +18 -0
  56. tests/test_data/packages/error_scenarios/invalid_dep_pkg/mcp_server.py +21 -0
  57. tests/test_data/packages/error_scenarios/version_conflict_pkg/hatch_mcp_server.py +18 -0
  58. tests/test_data/packages/error_scenarios/version_conflict_pkg/mcp_server.py +21 -0
  59. tests/test_data/packages/schema_versions/schema_v1_1_0_pkg/main.py +11 -0
  60. tests/test_data/packages/schema_versions/schema_v1_2_0_pkg/main.py +11 -0
  61. tests/test_data/packages/schema_versions/schema_v1_2_1_pkg/hatch_mcp_server.py +18 -0
  62. tests/test_data/packages/schema_versions/schema_v1_2_1_pkg/mcp_server.py +21 -0
  63. tests/test_data_utils.py +472 -0
  64. tests/test_dependency_orchestrator_consent.py +266 -0
  65. tests/test_docker_installer.py +524 -0
  66. tests/test_env_manip.py +991 -0
  67. tests/test_hatch_installer.py +179 -0
  68. tests/test_installer_base.py +221 -0
  69. tests/test_mcp_atomic_operations.py +276 -0
  70. tests/test_mcp_backup_integration.py +308 -0
  71. tests/test_mcp_cli_all_host_specific_args.py +303 -0
  72. tests/test_mcp_cli_backup_management.py +295 -0
  73. tests/test_mcp_cli_direct_management.py +453 -0
  74. tests/test_mcp_cli_discovery_listing.py +582 -0
  75. tests/test_mcp_cli_host_config_integration.py +823 -0
  76. tests/test_mcp_cli_package_management.py +360 -0
  77. tests/test_mcp_cli_partial_updates.py +859 -0
  78. tests/test_mcp_environment_integration.py +520 -0
  79. tests/test_mcp_host_config_backup.py +257 -0
  80. tests/test_mcp_host_configuration_manager.py +331 -0
  81. tests/test_mcp_host_registry_decorator.py +348 -0
  82. tests/test_mcp_pydantic_architecture_v4.py +603 -0
  83. tests/test_mcp_server_config_models.py +242 -0
  84. tests/test_mcp_server_config_type_field.py +221 -0
  85. tests/test_mcp_sync_functionality.py +316 -0
  86. tests/test_mcp_user_feedback_reporting.py +359 -0
  87. tests/test_non_tty_integration.py +281 -0
  88. tests/test_online_package_loader.py +202 -0
  89. tests/test_python_environment_manager.py +882 -0
  90. tests/test_python_installer.py +327 -0
  91. tests/test_registry.py +51 -0
  92. tests/test_registry_retriever.py +250 -0
  93. tests/test_system_installer.py +733 -0
@@ -0,0 +1,242 @@
1
+ """
2
+ Test suite for consolidated MCPServerConfig Pydantic model.
3
+
4
+ This module tests the consolidated MCPServerConfig model that supports
5
+ both local and remote server configurations with proper validation.
6
+ """
7
+
8
+ import unittest
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ # Add the parent directory to the path to import wobble
13
+ sys.path.insert(0, str(Path(__file__).parent.parent))
14
+
15
+ try:
16
+ from wobble.decorators import regression_test, integration_test
17
+ except ImportError:
18
+ # Fallback decorators if wobble is not available
19
+ def regression_test(func):
20
+ return func
21
+
22
+ def integration_test(scope="component"):
23
+ def decorator(func):
24
+ return func
25
+ return decorator
26
+
27
+ from test_data_utils import MCPHostConfigTestDataLoader
28
+ from hatch.mcp_host_config.models import MCPServerConfig
29
+ from pydantic import ValidationError
30
+
31
+
32
+ class TestMCPServerConfigModels(unittest.TestCase):
33
+ """Test suite for consolidated MCPServerConfig Pydantic model."""
34
+
35
+ def setUp(self):
36
+ """Set up test environment."""
37
+ self.test_data_loader = MCPHostConfigTestDataLoader()
38
+
39
+ @regression_test
40
+ def test_mcp_server_config_local_server_validation_success(self):
41
+ """Test successful local server configuration validation."""
42
+ config_data = self.test_data_loader.load_mcp_server_config("local")
43
+ config = MCPServerConfig(**config_data)
44
+
45
+ self.assertEqual(config.command, "python")
46
+ self.assertEqual(len(config.args), 3)
47
+ self.assertEqual(config.env["API_KEY"], "test")
48
+ self.assertTrue(config.is_local_server)
49
+ self.assertFalse(config.is_remote_server)
50
+
51
+ @regression_test
52
+ def test_mcp_server_config_remote_server_validation_success(self):
53
+ """Test successful remote server configuration validation."""
54
+ config_data = self.test_data_loader.load_mcp_server_config("remote")
55
+ config = MCPServerConfig(**config_data)
56
+
57
+ self.assertEqual(config.url, "https://api.example.com/mcp")
58
+ self.assertEqual(config.headers["Authorization"], "Bearer token")
59
+ self.assertFalse(config.is_local_server)
60
+ self.assertTrue(config.is_remote_server)
61
+
62
+ @regression_test
63
+ def test_mcp_server_config_validation_fails_both_command_and_url(self):
64
+ """Test validation fails when both command and URL are provided."""
65
+ config_data = {
66
+ "command": "python",
67
+ "args": ["server.py"],
68
+ "url": "https://example.com/mcp" # Invalid: both command and URL
69
+ }
70
+
71
+ with self.assertRaises(ValidationError) as context:
72
+ MCPServerConfig(**config_data)
73
+
74
+ self.assertIn("Cannot specify both 'command' and 'url'", str(context.exception))
75
+
76
+ @regression_test
77
+ def test_mcp_server_config_validation_fails_neither_command_nor_url(self):
78
+ """Test validation fails when neither command nor URL are provided."""
79
+ config_data = {
80
+ "env": {"TEST": "value"}
81
+ # Missing both command and url
82
+ }
83
+
84
+ with self.assertRaises(ValidationError) as context:
85
+ MCPServerConfig(**config_data)
86
+
87
+ self.assertIn("Either 'command' (local server) or 'url' (remote server) must be provided",
88
+ str(context.exception))
89
+
90
+ @regression_test
91
+ def test_mcp_server_config_validation_args_without_command_fails(self):
92
+ """Test validation fails when args provided without command."""
93
+ config_data = {
94
+ "url": "https://example.com/mcp",
95
+ "args": ["--flag"] # Invalid: args without command
96
+ }
97
+
98
+ with self.assertRaises(ValidationError) as context:
99
+ MCPServerConfig(**config_data)
100
+
101
+ self.assertIn("'args' can only be specified with 'command'", str(context.exception))
102
+
103
+ @regression_test
104
+ def test_mcp_server_config_validation_headers_without_url_fails(self):
105
+ """Test validation fails when headers provided without URL."""
106
+ config_data = {
107
+ "command": "python",
108
+ "headers": {"Authorization": "Bearer token"} # Invalid: headers without URL
109
+ }
110
+
111
+ with self.assertRaises(ValidationError) as context:
112
+ MCPServerConfig(**config_data)
113
+
114
+ self.assertIn("'headers' can only be specified with 'url'", str(context.exception))
115
+
116
+ @regression_test
117
+ def test_mcp_server_config_url_format_validation(self):
118
+ """Test URL format validation."""
119
+ invalid_urls = ["ftp://example.com", "example.com", "not-a-url"]
120
+
121
+ for invalid_url in invalid_urls:
122
+ with self.assertRaises(ValidationError):
123
+ MCPServerConfig(url=invalid_url)
124
+
125
+ @regression_test
126
+ def test_mcp_server_config_no_future_extension_fields(self):
127
+ """Test that extra fields are allowed for host-specific extensions."""
128
+ # Current design allows extra fields to support host-specific configurations
129
+ # (e.g., Gemini's timeout, VS Code's envFile, etc.)
130
+ config_data = {
131
+ "command": "python",
132
+ "timeout": 30, # Allowed (host-specific field)
133
+ "retry_attempts": 3, # Allowed (host-specific field)
134
+ "ssl_verify": True # Allowed (host-specific field)
135
+ }
136
+
137
+ # Should NOT raise ValidationError (extra="allow")
138
+ config = MCPServerConfig(**config_data)
139
+
140
+ # Verify core fields are set correctly
141
+ self.assertEqual(config.command, "python")
142
+
143
+ # Note: In Phase 3B, strict validation will be enforced in host-specific models
144
+
145
+ @regression_test
146
+ def test_mcp_server_config_command_empty_validation(self):
147
+ """Test validation fails for empty command."""
148
+ config_data = {
149
+ "command": " ", # Empty/whitespace command
150
+ "args": ["server.py"]
151
+ }
152
+
153
+ with self.assertRaises(ValidationError) as context:
154
+ MCPServerConfig(**config_data)
155
+
156
+ self.assertIn("Command cannot be empty", str(context.exception))
157
+
158
+ @regression_test
159
+ def test_mcp_server_config_command_strip_whitespace(self):
160
+ """Test command whitespace is stripped."""
161
+ config_data = {
162
+ "command": " python ",
163
+ "args": ["server.py"]
164
+ }
165
+
166
+ config = MCPServerConfig(**config_data)
167
+ self.assertEqual(config.command, "python")
168
+
169
+ @regression_test
170
+ def test_mcp_server_config_minimal_local_server(self):
171
+ """Test minimal local server configuration."""
172
+ config_data = self.test_data_loader.load_mcp_server_config("local_minimal")
173
+ config = MCPServerConfig(**config_data)
174
+
175
+ self.assertEqual(config.command, "python")
176
+ self.assertEqual(config.args, ["minimal_server.py"])
177
+ self.assertIsNone(config.env)
178
+ self.assertTrue(config.is_local_server)
179
+ self.assertFalse(config.is_remote_server)
180
+
181
+ @regression_test
182
+ def test_mcp_server_config_minimal_remote_server(self):
183
+ """Test minimal remote server configuration."""
184
+ config_data = self.test_data_loader.load_mcp_server_config("remote_minimal")
185
+ config = MCPServerConfig(**config_data)
186
+
187
+ self.assertEqual(config.url, "https://minimal.example.com/mcp")
188
+ self.assertIsNone(config.headers)
189
+ self.assertFalse(config.is_local_server)
190
+ self.assertTrue(config.is_remote_server)
191
+
192
+ @regression_test
193
+ def test_mcp_server_config_serialization_roundtrip(self):
194
+ """Test serialization and deserialization roundtrip."""
195
+ # Test local server
196
+ local_config_data = self.test_data_loader.load_mcp_server_config("local")
197
+ local_config = MCPServerConfig(**local_config_data)
198
+
199
+ # Serialize and deserialize
200
+ serialized = local_config.model_dump()
201
+ roundtrip_config = MCPServerConfig(**serialized)
202
+
203
+ self.assertEqual(local_config.command, roundtrip_config.command)
204
+ self.assertEqual(local_config.args, roundtrip_config.args)
205
+ self.assertEqual(local_config.env, roundtrip_config.env)
206
+ self.assertEqual(local_config.is_local_server, roundtrip_config.is_local_server)
207
+
208
+ # Test remote server
209
+ remote_config_data = self.test_data_loader.load_mcp_server_config("remote")
210
+ remote_config = MCPServerConfig(**remote_config_data)
211
+
212
+ # Serialize and deserialize
213
+ serialized = remote_config.model_dump()
214
+ roundtrip_config = MCPServerConfig(**serialized)
215
+
216
+ self.assertEqual(remote_config.url, roundtrip_config.url)
217
+ self.assertEqual(remote_config.headers, roundtrip_config.headers)
218
+ self.assertEqual(remote_config.is_remote_server, roundtrip_config.is_remote_server)
219
+
220
+ @regression_test
221
+ def test_mcp_server_config_json_serialization(self):
222
+ """Test JSON serialization compatibility."""
223
+ import json
224
+
225
+ config_data = self.test_data_loader.load_mcp_server_config("local")
226
+ config = MCPServerConfig(**config_data)
227
+
228
+ # Test JSON serialization
229
+ json_str = config.model_dump_json()
230
+ self.assertIsInstance(json_str, str)
231
+
232
+ # Test JSON deserialization
233
+ parsed_data = json.loads(json_str)
234
+ roundtrip_config = MCPServerConfig(**parsed_data)
235
+
236
+ self.assertEqual(config.command, roundtrip_config.command)
237
+ self.assertEqual(config.args, roundtrip_config.args)
238
+ self.assertEqual(config.env, roundtrip_config.env)
239
+
240
+
241
+ if __name__ == '__main__':
242
+ unittest.main()
@@ -0,0 +1,221 @@
1
+ """
2
+ Test suite for MCPServerConfig type field (Phase 3A).
3
+
4
+ This module tests the type field addition to MCPServerConfig model,
5
+ including validation and property behavior.
6
+ """
7
+
8
+ import unittest
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ # Add the parent directory to the path to import wobble
13
+ sys.path.insert(0, str(Path(__file__).parent.parent))
14
+
15
+ try:
16
+ from wobble.decorators import regression_test
17
+ except ImportError:
18
+ # Fallback decorator if wobble is not available
19
+ def regression_test(func):
20
+ return func
21
+
22
+ from hatch.mcp_host_config.models import MCPServerConfig
23
+ from pydantic import ValidationError
24
+
25
+
26
+ class TestMCPServerConfigTypeField(unittest.TestCase):
27
+ """Test suite for MCPServerConfig type field validation."""
28
+
29
+ @regression_test
30
+ def test_type_stdio_with_command_success(self):
31
+ """Test successful stdio type with command."""
32
+ config = MCPServerConfig(
33
+ name="test-server",
34
+ type="stdio",
35
+ command="python",
36
+ args=["server.py"]
37
+ )
38
+
39
+ self.assertEqual(config.type, "stdio")
40
+ self.assertEqual(config.command, "python")
41
+ self.assertTrue(config.is_local_server)
42
+ self.assertFalse(config.is_remote_server)
43
+
44
+ @regression_test
45
+ def test_type_sse_with_url_success(self):
46
+ """Test successful sse type with url."""
47
+ config = MCPServerConfig(
48
+ name="test-server",
49
+ type="sse",
50
+ url="https://api.example.com/mcp"
51
+ )
52
+
53
+ self.assertEqual(config.type, "sse")
54
+ self.assertEqual(config.url, "https://api.example.com/mcp")
55
+ self.assertFalse(config.is_local_server)
56
+ self.assertTrue(config.is_remote_server)
57
+
58
+ @regression_test
59
+ def test_type_http_with_url_success(self):
60
+ """Test successful http type with url."""
61
+ config = MCPServerConfig(
62
+ name="test-server",
63
+ type="http",
64
+ url="https://api.example.com/mcp",
65
+ headers={"Authorization": "Bearer token"}
66
+ )
67
+
68
+ self.assertEqual(config.type, "http")
69
+ self.assertEqual(config.url, "https://api.example.com/mcp")
70
+ self.assertFalse(config.is_local_server)
71
+ self.assertTrue(config.is_remote_server)
72
+
73
+ @regression_test
74
+ def test_type_stdio_without_command_fails(self):
75
+ """Test validation fails when type=stdio without command."""
76
+ with self.assertRaises(ValidationError) as context:
77
+ MCPServerConfig(
78
+ name="test-server",
79
+ type="stdio",
80
+ url="https://api.example.com/mcp" # Invalid: stdio with url
81
+ )
82
+
83
+ self.assertIn("'type=stdio' requires 'command' field", str(context.exception))
84
+
85
+ @regression_test
86
+ def test_type_stdio_with_url_fails(self):
87
+ """Test validation fails when type=stdio with url."""
88
+ with self.assertRaises(ValidationError) as context:
89
+ MCPServerConfig(
90
+ name="test-server",
91
+ type="stdio",
92
+ command="python",
93
+ url="https://api.example.com/mcp" # Invalid: both command and url
94
+ )
95
+
96
+ # The validate_server_type() validator catches this first
97
+ self.assertIn("Cannot specify both 'command' and 'url'", str(context.exception))
98
+
99
+ @regression_test
100
+ def test_type_sse_without_url_fails(self):
101
+ """Test validation fails when type=sse without url."""
102
+ with self.assertRaises(ValidationError) as context:
103
+ MCPServerConfig(
104
+ name="test-server",
105
+ type="sse",
106
+ command="python" # Invalid: sse with command
107
+ )
108
+
109
+ self.assertIn("'type=sse' requires 'url' field", str(context.exception))
110
+
111
+ @regression_test
112
+ def test_type_http_without_url_fails(self):
113
+ """Test validation fails when type=http without url."""
114
+ with self.assertRaises(ValidationError) as context:
115
+ MCPServerConfig(
116
+ name="test-server",
117
+ type="http",
118
+ command="python" # Invalid: http with command
119
+ )
120
+
121
+ self.assertIn("'type=http' requires 'url' field", str(context.exception))
122
+
123
+ @regression_test
124
+ def test_type_sse_with_command_fails(self):
125
+ """Test validation fails when type=sse with command."""
126
+ with self.assertRaises(ValidationError) as context:
127
+ MCPServerConfig(
128
+ name="test-server",
129
+ type="sse",
130
+ command="python",
131
+ url="https://api.example.com/mcp" # Invalid: both command and url
132
+ )
133
+
134
+ # The validate_server_type() validator catches this first
135
+ self.assertIn("Cannot specify both 'command' and 'url'", str(context.exception))
136
+
137
+ @regression_test
138
+ def test_backward_compatibility_no_type_field_local(self):
139
+ """Test backward compatibility: local server without type field."""
140
+ config = MCPServerConfig(
141
+ name="test-server",
142
+ command="python",
143
+ args=["server.py"]
144
+ )
145
+
146
+ self.assertIsNone(config.type)
147
+ self.assertEqual(config.command, "python")
148
+ self.assertTrue(config.is_local_server)
149
+ self.assertFalse(config.is_remote_server)
150
+
151
+ @regression_test
152
+ def test_backward_compatibility_no_type_field_remote(self):
153
+ """Test backward compatibility: remote server without type field."""
154
+ config = MCPServerConfig(
155
+ name="test-server",
156
+ url="https://api.example.com/mcp"
157
+ )
158
+
159
+ self.assertIsNone(config.type)
160
+ self.assertEqual(config.url, "https://api.example.com/mcp")
161
+ self.assertFalse(config.is_local_server)
162
+ self.assertTrue(config.is_remote_server)
163
+
164
+ @regression_test
165
+ def test_type_field_with_env_variables(self):
166
+ """Test type field with environment variables."""
167
+ config = MCPServerConfig(
168
+ name="test-server",
169
+ type="stdio",
170
+ command="python",
171
+ args=["server.py"],
172
+ env={"API_KEY": "test-key", "DEBUG": "true"}
173
+ )
174
+
175
+ self.assertEqual(config.type, "stdio")
176
+ self.assertEqual(config.env["API_KEY"], "test-key")
177
+ self.assertEqual(config.env["DEBUG"], "true")
178
+
179
+ @regression_test
180
+ def test_type_field_serialization(self):
181
+ """Test type field is included in serialization."""
182
+ config = MCPServerConfig(
183
+ name="test-server",
184
+ type="stdio",
185
+ command="python",
186
+ args=["server.py"]
187
+ )
188
+
189
+ # Test model_dump includes type field
190
+ data = config.model_dump()
191
+ self.assertEqual(data["type"], "stdio")
192
+ self.assertEqual(data["command"], "python")
193
+
194
+ # Test JSON serialization
195
+ import json
196
+ json_str = config.model_dump_json()
197
+ parsed = json.loads(json_str)
198
+ self.assertEqual(parsed["type"], "stdio")
199
+
200
+ @regression_test
201
+ def test_type_field_roundtrip(self):
202
+ """Test type field survives serialization roundtrip."""
203
+ original = MCPServerConfig(
204
+ name="test-server",
205
+ type="sse",
206
+ url="https://api.example.com/mcp",
207
+ headers={"Authorization": "Bearer token"}
208
+ )
209
+
210
+ # Serialize and deserialize
211
+ data = original.model_dump()
212
+ roundtrip = MCPServerConfig(**data)
213
+
214
+ self.assertEqual(roundtrip.type, "sse")
215
+ self.assertEqual(roundtrip.url, "https://api.example.com/mcp")
216
+ self.assertEqual(roundtrip.headers["Authorization"], "Bearer token")
217
+
218
+
219
+ if __name__ == '__main__':
220
+ unittest.main()
221
+