spaceforge 0.0.8__tar.gz → 0.0.9__tar.gz

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 (58) hide show
  1. {spaceforge-0.0.8 → spaceforge-0.0.9}/PKG-INFO +1 -1
  2. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/_version_scm.py +3 -3
  3. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/plugin.py +6 -4
  4. spaceforge-0.0.9/spaceforge/test_plugin.py +357 -0
  5. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge.egg-info/PKG-INFO +1 -1
  6. spaceforge-0.0.8/spaceforge/test_plugin.py +0 -357
  7. {spaceforge-0.0.8 → spaceforge-0.0.9}/.github/workflows/ci.yml +0 -0
  8. {spaceforge-0.0.8 → spaceforge-0.0.9}/.github/workflows/release.yml +0 -0
  9. {spaceforge-0.0.8 → spaceforge-0.0.9}/.gitignore +0 -0
  10. {spaceforge-0.0.8 → spaceforge-0.0.9}/LICENSE +0 -0
  11. {spaceforge-0.0.8 → spaceforge-0.0.9}/MANIFEST.in +0 -0
  12. {spaceforge-0.0.8 → spaceforge-0.0.9}/README.md +0 -0
  13. {spaceforge-0.0.8 → spaceforge-0.0.9}/go.mod +0 -0
  14. {spaceforge-0.0.8 → spaceforge-0.0.9}/plugins/enviroment_manager/plugin.py +0 -0
  15. {spaceforge-0.0.8 → spaceforge-0.0.9}/plugins/enviroment_manager/plugin.yaml +0 -0
  16. {spaceforge-0.0.8 → spaceforge-0.0.9}/plugins/enviroment_manager/requirements.txt +0 -0
  17. {spaceforge-0.0.8 → spaceforge-0.0.9}/plugins/infracost/plugin.py +0 -0
  18. {spaceforge-0.0.8 → spaceforge-0.0.9}/plugins/infracost/plugin.yaml +0 -0
  19. {spaceforge-0.0.8 → spaceforge-0.0.9}/plugins/sops/plugin.py +0 -0
  20. {spaceforge-0.0.8 → spaceforge-0.0.9}/plugins/sops/plugin.yaml +0 -0
  21. {spaceforge-0.0.8 → spaceforge-0.0.9}/plugins/sops/requirements.txt +0 -0
  22. {spaceforge-0.0.8 → spaceforge-0.0.9}/plugins/wiz/plugin.py +0 -0
  23. {spaceforge-0.0.8 → spaceforge-0.0.9}/plugins/wiz/plugin.yaml +0 -0
  24. {spaceforge-0.0.8 → spaceforge-0.0.9}/pyproject.toml +0 -0
  25. {spaceforge-0.0.8 → spaceforge-0.0.9}/setup.cfg +0 -0
  26. {spaceforge-0.0.8 → spaceforge-0.0.9}/setup.py +0 -0
  27. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/README.md +0 -0
  28. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/__init__.py +0 -0
  29. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/__main__.py +0 -0
  30. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/_version.py +0 -0
  31. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/cls.py +0 -0
  32. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/conftest.py +0 -0
  33. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/generator.py +0 -0
  34. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/runner.py +0 -0
  35. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/schema.json +0 -0
  36. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/templates/binary_install.sh.j2 +0 -0
  37. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/templates/ensure_spaceforge_and_run.sh.j2 +0 -0
  38. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/test_cls.py +0 -0
  39. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/test_generator.py +0 -0
  40. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/test_generator_binaries.py +0 -0
  41. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/test_generator_core.py +0 -0
  42. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/test_generator_hooks.py +0 -0
  43. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/test_generator_parameters.py +0 -0
  44. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/test_plugin_file_operations.py +0 -0
  45. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/test_plugin_hooks.py +0 -0
  46. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/test_plugin_inheritance.py +0 -0
  47. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/test_runner.py +0 -0
  48. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/test_runner_cli.py +0 -0
  49. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/test_runner_core.py +0 -0
  50. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/test_runner_execution.py +0 -0
  51. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge.egg-info/SOURCES.txt +0 -0
  52. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge.egg-info/dependency_links.txt +0 -0
  53. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge.egg-info/entry_points.txt +0 -0
  54. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge.egg-info/not-zip-safe +0 -0
  55. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge.egg-info/requires.txt +0 -0
  56. {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge.egg-info/top_level.txt +0 -0
  57. {spaceforge-0.0.8 → spaceforge-0.0.9}/templates.go +0 -0
  58. {spaceforge-0.0.8 → spaceforge-0.0.9}/test.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spaceforge
3
- Version: 0.0.8
3
+ Version: 0.0.9
4
4
  Summary: A Python framework for building Spacelift plugins
5
5
  Home-page: https://github.com/spacelift-io/plugins
6
6
  Author: Spacelift
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.0.8'
32
- __version_tuple__ = version_tuple = (0, 0, 8)
31
+ __version__ = version = '0.0.9'
32
+ __version_tuple__ = version_tuple = (0, 0, 9)
33
33
 
34
- __commit_id__ = commit_id = 'g3b76eed20'
34
+ __commit_id__ = commit_id = 'geefe51823'
@@ -8,9 +8,9 @@ import logging
8
8
  import os
9
9
  import subprocess
10
10
  import urllib.request
11
- from urllib.error import HTTPError
12
11
  from abc import ABC
13
12
  from typing import Any, Dict, List, Optional, Tuple
13
+ from urllib.error import HTTPError
14
14
 
15
15
 
16
16
  class SpaceforgePlugin(ABC):
@@ -210,8 +210,8 @@ class SpaceforgePlugin(ABC):
210
210
  with urllib.request.urlopen(req) as response:
211
211
  resp: Dict[str, Any] = json.loads(response.read().decode("utf-8"))
212
212
  except urllib.error.HTTPError as e:
213
- if hasattr(e, 'read'):
214
- resp = json.loads(e.read().decode('utf-8'))
213
+ if hasattr(e, "read"):
214
+ resp = json.loads(e.read().decode("utf-8"))
215
215
  else:
216
216
  # We should not get here, but if we do re-raise the exception
217
217
  self.logger.error(f"HTTP error occurred: ({e.code}) {e.reason} {e.msg}")
@@ -318,7 +318,9 @@ class SpaceforgePlugin(ABC):
318
318
  return False
319
319
  self.logger.debug("Markdown content uploaded successfully.")
320
320
  except HTTPError as e:
321
- self.logger.error(f"HTTP error occurred during upload: ({e.code}) {e.reason} {e.msg}")
321
+ self.logger.error(
322
+ f"HTTP error occurred during upload: ({e.code}) {e.reason} {e.msg}"
323
+ )
322
324
  return False
323
325
 
324
326
  return True
@@ -0,0 +1,357 @@
1
+ # import json
2
+ # import logging
3
+ # import os
4
+ # import subprocess
5
+ # from typing import Dict
6
+ # from unittest.mock import Mock, patch
7
+ #
8
+ # import pytest
9
+ #
10
+ # from spaceforge.plugin import SpaceforgePlugin
11
+ #
12
+ #
13
+ # class TestSpaceforgePluginInitialization:
14
+ # """Test SpaceforgePlugin initialization and configuration."""
15
+ #
16
+ # def test_should_initialize_with_defaults_when_no_environment_set(self) -> None:
17
+ # """Should set default values when no environment variables are provided."""
18
+ # # Arrange & Act
19
+ # with patch.dict(os.environ, {}, clear=True):
20
+ # plugin = SpaceforgePlugin()
21
+ #
22
+ # # Assert
23
+ # assert plugin._api_token is False
24
+ # assert plugin.spacelift_domain is False
25
+ # assert plugin._api_enabled is False
26
+ # assert plugin._workspace_root == os.getcwd()
27
+ # assert isinstance(plugin.logger, logging.Logger)
28
+ #
29
+ # def test_should_enable_api_when_valid_credentials_provided(
30
+ # self, mock_env: Dict[str, str]
31
+ # ) -> None:
32
+ # """Should enable API access when both token and endpoint are provided."""
33
+ # # Arrange & Act
34
+ # with patch.dict(os.environ, mock_env, clear=True):
35
+ # plugin = SpaceforgePlugin()
36
+ #
37
+ # # Assert
38
+ # assert plugin._api_token == "test_token"
39
+ # assert plugin.spacelift_domain == "https://test.spacelift.io"
40
+ # assert plugin._api_enabled is True
41
+ # assert plugin._workspace_root == os.getcwd()
42
+ #
43
+ # def test_should_normalize_domain_with_trailing_slash(self) -> None:
44
+ # """Should remove trailing slash from domain URL."""
45
+ # # Arrange
46
+ # test_env = {
47
+ # "SPACELIFT_API_TOKEN": "test_token",
48
+ # "TF_VAR_spacelift_graphql_endpoint": "https://test.spacelift.io/",
49
+ # }
50
+ #
51
+ # # Act
52
+ # with patch.dict(os.environ, test_env, clear=True):
53
+ # plugin = SpaceforgePlugin()
54
+ #
55
+ # # Assert
56
+ # assert plugin.spacelift_domain == "https://test.spacelift.io"
57
+ # assert plugin._api_enabled is True
58
+ #
59
+ # def test_should_disable_api_when_domain_has_no_https_prefix(self) -> None:
60
+ # """Should disable API when domain doesn't use HTTPS."""
61
+ # # Arrange
62
+ # test_env = {
63
+ # "SPACELIFT_API_TOKEN": "test_token",
64
+ # "TF_VAR_spacelift_graphql_endpoint": "test.spacelift.io",
65
+ # }
66
+ #
67
+ # # Act
68
+ # with patch.dict(os.environ, test_env, clear=True):
69
+ # plugin = SpaceforgePlugin()
70
+ #
71
+ # # Assert
72
+ # assert plugin.spacelift_domain == "test.spacelift.io"
73
+ # assert plugin._api_enabled is False
74
+ #
75
+ # def test_should_disable_api_when_only_token_provided(self) -> None:
76
+ # """Should disable API when only token is provided without domain."""
77
+ # # Arrange & Act
78
+ # with patch.dict(os.environ, {"SPACELIFT_API_TOKEN": "test_token"}, clear=True):
79
+ # plugin = SpaceforgePlugin()
80
+ #
81
+ # # Assert
82
+ # assert plugin._api_enabled is False
83
+ #
84
+ # def test_should_disable_api_when_only_domain_provided(self) -> None:
85
+ # """Should disable API when only domain is provided without token."""
86
+ # # Arrange & Act
87
+ # with patch.dict(
88
+ # os.environ,
89
+ # {"TF_VAR_spacelift_graphql_endpoint": "https://test.spacelift.io"},
90
+ # clear=True,
91
+ # ):
92
+ # plugin = SpaceforgePlugin()
93
+ #
94
+ # # Assert
95
+ # assert plugin._api_enabled is False
96
+ #
97
+ #
98
+ # class TestSpaceforgePluginLogging:
99
+ # """Test logging configuration and functionality."""
100
+ #
101
+ # def test_should_configure_logger_with_correct_name_and_level(self) -> None:
102
+ # """Should set up logger with proper name and level."""
103
+ # # Arrange & Act
104
+ # with patch.dict(os.environ, {}, clear=True):
105
+ # plugin = SpaceforgePlugin()
106
+ #
107
+ # # Assert
108
+ # assert plugin.logger.name == "spaceforge.SpaceforgePlugin"
109
+ # assert len(plugin.logger.handlers) >= 1
110
+ # assert plugin.logger.getEffectiveLevel() <= logging.INFO
111
+ #
112
+ # def test_should_enable_debug_logging_when_debug_env_set(self) -> None:
113
+ # """Should set DEBUG level when SPACELIFT_DEBUG environment variable is true."""
114
+ # # Arrange
115
+ # test_env = {"SPACELIFT_DEBUG": "true"}
116
+ #
117
+ # # Act
118
+ # with patch.dict(os.environ, test_env, clear=True):
119
+ # plugin = SpaceforgePlugin()
120
+ #
121
+ # # Assert
122
+ # assert plugin.logger.level == logging.DEBUG
123
+ #
124
+ # def test_should_include_run_id_in_log_format(self) -> None:
125
+ # """Should include run ID in log message format when available."""
126
+ # # Arrange
127
+ # test_env = {"TF_VAR_spacelift_run_id": "run-123"}
128
+ #
129
+ # # Act
130
+ # with patch.dict(os.environ, test_env, clear=True):
131
+ # plugin = SpaceforgePlugin()
132
+ # formatter = plugin.logger.handlers[0].formatter
133
+ #
134
+ # record = logging.LogRecord(
135
+ # name="test",
136
+ # level=logging.INFO,
137
+ # pathname="",
138
+ # lineno=0,
139
+ # msg="test message",
140
+ # args=(),
141
+ # exc_info=None,
142
+ # )
143
+ # record.levelname = "INFO"
144
+ #
145
+ # # Assert
146
+ # assert formatter is not None
147
+ # formatted = formatter.format(record)
148
+ # assert "[run-123]" in formatted or "[local]" in formatted
149
+ #
150
+ # def test_logger_color_formatting(self) -> None:
151
+ # """Test color formatting for different log levels."""
152
+ # plugin = SpaceforgePlugin()
153
+ # formatter = plugin.logger.handlers[0].formatter
154
+ # assert formatter is not None
155
+ #
156
+ # # Test different log levels
157
+ # levels_to_test = [
158
+ # (logging.INFO, "INFO"),
159
+ # (logging.DEBUG, "DEBUG"),
160
+ # (logging.WARNING, "WARNING"),
161
+ # (logging.ERROR, "ERROR"),
162
+ # ]
163
+ #
164
+ # for level, level_name in levels_to_test:
165
+ # record = logging.LogRecord(
166
+ # name="test",
167
+ # level=level,
168
+ # pathname="",
169
+ # lineno=0,
170
+ # msg="test message",
171
+ # args=(),
172
+ # exc_info=None,
173
+ # )
174
+ # record.levelname = level_name
175
+ # formatted = formatter.format(record)
176
+ #
177
+ # # Should contain color codes and plugin name
178
+ # assert "\033[" in formatted # ANSI color codes
179
+ # assert "(SpaceforgePlugin)" in formatted
180
+ # assert "test message" in formatted
181
+ #
182
+ #
183
+ # # Hook tests moved to test_plugin_hooks.py
184
+ #
185
+ #
186
+ # class TestSpaceforgePluginCLI:
187
+ # """Test command-line interface execution functionality."""
188
+ #
189
+ # def test_should_execute_cli_command_and_log_output_on_success(self) -> None:
190
+ # """Should run CLI command and log output when execution succeeds."""
191
+ # # Arrange
192
+ # plugin = SpaceforgePlugin()
193
+ # mock_process = Mock()
194
+ # mock_process.communicate.return_value = (b"success output\n", None)
195
+ # mock_process.returncode = 0
196
+ #
197
+ # # Act
198
+ # with patch("subprocess.Popen") as mock_popen:
199
+ # with patch.object(plugin.logger, "info") as mock_info:
200
+ # mock_popen.return_value = mock_process
201
+ # plugin.run_cli("echo", "test")
202
+ #
203
+ # # Assert
204
+ # mock_popen.assert_called_once_with(
205
+ # ("echo", "test"), stdout=subprocess.PIPE, stderr=subprocess.STDOUT
206
+ # )
207
+ # mock_info.assert_called_with("success output")
208
+ #
209
+ # def test_should_log_error_when_cli_command_fails(self) -> None:
210
+ # """Should log error details when CLI command returns non-zero exit code."""
211
+ # # Arrange
212
+ # plugin = SpaceforgePlugin()
213
+ # mock_process = Mock()
214
+ # mock_process.communicate.return_value = (None, b"error output\n")
215
+ # mock_process.returncode = 1
216
+ #
217
+ # # Act
218
+ # with patch("subprocess.Popen") as mock_popen:
219
+ # with patch.object(plugin.logger, "error") as mock_error:
220
+ # mock_popen.return_value = mock_process
221
+ # plugin.run_cli("false")
222
+ #
223
+ # # Assert
224
+ # mock_error.assert_any_call("Command failed with return code 1")
225
+ # mock_error.assert_any_call("error output")
226
+ #
227
+ # def test_run_cli_with_multiple_args(self) -> None:
228
+ # """Test CLI command with multiple arguments."""
229
+ # plugin = SpaceforgePlugin()
230
+ #
231
+ # with patch("subprocess.Popen") as mock_popen:
232
+ # mock_process = Mock()
233
+ # mock_process.communicate.return_value = (b"", None)
234
+ # mock_process.returncode = 0
235
+ # mock_popen.return_value = mock_process
236
+ #
237
+ # with patch.object(plugin.logger, "debug") as mock_debug:
238
+ # plugin.run_cli("git", "status", "--porcelain")
239
+ #
240
+ # mock_popen.assert_called_once_with(
241
+ # ("git", "status", "--porcelain"),
242
+ # stdout=subprocess.PIPE,
243
+ # stderr=subprocess.STDOUT,
244
+ # )
245
+ # mock_debug.assert_called_with("Running CLI command: git status --porcelain")
246
+ #
247
+ #
248
+ # class TestSpaceforgePluginAPI:
249
+ # """Test GraphQL API interaction functionality."""
250
+ #
251
+ # def test_should_exit_with_error_when_api_disabled(self) -> None:
252
+ # """Should exit with error message when API is not enabled."""
253
+ # # Arrange
254
+ # plugin = SpaceforgePlugin()
255
+ # plugin._api_enabled = False
256
+ #
257
+ # # Act & Assert
258
+ # with patch.object(plugin.logger, "error") as mock_error:
259
+ # with pytest.raises(SystemExit):
260
+ # plugin.query_api("query { test }")
261
+ #
262
+ # mock_error.assert_called_with(
263
+ # 'API is not enabled, please export "SPACELIFT_API_TOKEN" and "TF_VAR_spacelift_graphql_endpoint".'
264
+ # )
265
+ #
266
+ # def test_should_make_successful_api_request_with_correct_format(
267
+ # self, mock_api_response: Mock
268
+ # ) -> None:
269
+ # """Should execute GraphQL query with proper authentication and format."""
270
+ # # Arrange
271
+ # plugin = SpaceforgePlugin()
272
+ # plugin._api_enabled = True
273
+ # plugin._api_token = "test_token"
274
+ # plugin.spacelift_domain = "https://test.spacelift.io"
275
+ #
276
+ # expected_data = {"data": {"test": "result"}}
277
+ # mock_api_response.read.return_value = json.dumps(expected_data).encode("utf-8")
278
+ #
279
+ # # Act
280
+ # with patch("urllib.request.urlopen") as mock_urlopen:
281
+ # with patch("urllib.request.Request") as mock_request:
282
+ # mock_urlopen.return_value.__enter__ = Mock(
283
+ # return_value=mock_api_response
284
+ # )
285
+ # mock_urlopen.return_value.__exit__ = Mock(return_value=None)
286
+ #
287
+ # result = plugin.query_api("query { test }")
288
+ #
289
+ # # Assert
290
+ # mock_request.assert_called_once()
291
+ # call_args = mock_request.call_args[0]
292
+ # assert call_args[0] == "https://test.spacelift.io"
293
+ #
294
+ # request_data = json.loads(call_args[1].decode("utf-8"))
295
+ # assert request_data["query"] == "query { test }"
296
+ #
297
+ # headers = mock_request.call_args[0][2]
298
+ # assert headers["Content-Type"] == "application/json"
299
+ # assert headers["Authorization"] == "Bearer test_token"
300
+ #
301
+ # assert result == expected_data
302
+ #
303
+ # def test_query_api_with_variables(self) -> None:
304
+ # """Test API query with variables."""
305
+ # plugin = SpaceforgePlugin()
306
+ # plugin._api_enabled = True
307
+ # plugin._api_token = "test_token"
308
+ # plugin.spacelift_domain = "https://test.spacelift.io"
309
+ #
310
+ # mock_response_data = {"data": {"test": "result"}}
311
+ # mock_response = Mock()
312
+ # mock_response.read.return_value = json.dumps(mock_response_data).encode("utf-8")
313
+ #
314
+ # variables = {"stackId": "test-stack"}
315
+ #
316
+ # with patch("urllib.request.urlopen") as mock_urlopen:
317
+ # with patch("urllib.request.Request") as mock_request:
318
+ # mock_urlopen.return_value.__enter__ = Mock(return_value=mock_response)
319
+ # mock_urlopen.return_value.__exit__ = Mock(return_value=None)
320
+ #
321
+ # plugin.query_api(
322
+ # "query ($stackId: ID!) { stack(id: $stackId) { name } }", variables
323
+ # )
324
+ #
325
+ # # Verify request data includes variables
326
+ # request_data = json.loads(mock_request.call_args[0][1].decode("utf-8"))
327
+ # assert request_data["variables"] == variables
328
+ #
329
+ # def test_query_api_with_errors(self) -> None:
330
+ # """Test API query that returns errors."""
331
+ # plugin = SpaceforgePlugin()
332
+ # plugin._api_enabled = True
333
+ # plugin._api_token = "test_token"
334
+ # plugin.spacelift_domain = "https://test.spacelift.io"
335
+ #
336
+ # mock_response_data = {"errors": [{"message": "Test error"}]}
337
+ # mock_response = Mock()
338
+ # mock_response.read.return_value = json.dumps(mock_response_data).encode("utf-8")
339
+ #
340
+ # with patch("urllib.request.urlopen") as mock_urlopen:
341
+ # with patch.object(plugin.logger, "error") as mock_error:
342
+ # mock_urlopen.return_value.__enter__ = Mock(return_value=mock_response)
343
+ # mock_urlopen.return_value.__exit__ = Mock(return_value=None)
344
+ #
345
+ # result = plugin.query_api("query { test }")
346
+ #
347
+ # mock_error.assert_called_with("Error: [{'message': 'Test error'}]")
348
+ # assert result == mock_response_data
349
+ #
350
+ #
351
+ # # File operation tests moved to test_plugin_file_operations.py
352
+ #
353
+ #
354
+ # # Inheritance tests moved to test_plugin_inheritance.py
355
+ #
356
+ #
357
+ # # Edge case tests moved to test_plugin_inheritance.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spaceforge
3
- Version: 0.0.8
3
+ Version: 0.0.9
4
4
  Summary: A Python framework for building Spacelift plugins
5
5
  Home-page: https://github.com/spacelift-io/plugins
6
6
  Author: Spacelift
@@ -1,357 +0,0 @@
1
- import json
2
- import logging
3
- import os
4
- import subprocess
5
- from typing import Dict
6
- from unittest.mock import Mock, patch
7
-
8
- import pytest
9
-
10
- from spaceforge.plugin import SpaceforgePlugin
11
-
12
-
13
- class TestSpaceforgePluginInitialization:
14
- """Test SpaceforgePlugin initialization and configuration."""
15
-
16
- def test_should_initialize_with_defaults_when_no_environment_set(self) -> None:
17
- """Should set default values when no environment variables are provided."""
18
- # Arrange & Act
19
- with patch.dict(os.environ, {}, clear=True):
20
- plugin = SpaceforgePlugin()
21
-
22
- # Assert
23
- assert plugin._api_token is False
24
- assert plugin.spacelift_domain is False
25
- assert plugin._api_enabled is False
26
- assert plugin._workspace_root == os.getcwd()
27
- assert isinstance(plugin.logger, logging.Logger)
28
-
29
- def test_should_enable_api_when_valid_credentials_provided(
30
- self, mock_env: Dict[str, str]
31
- ) -> None:
32
- """Should enable API access when both token and endpoint are provided."""
33
- # Arrange & Act
34
- with patch.dict(os.environ, mock_env, clear=True):
35
- plugin = SpaceforgePlugin()
36
-
37
- # Assert
38
- assert plugin._api_token == "test_token"
39
- assert plugin.spacelift_domain == "https://test.spacelift.io"
40
- assert plugin._api_enabled is True
41
- assert plugin._workspace_root == os.getcwd()
42
-
43
- def test_should_normalize_domain_with_trailing_slash(self) -> None:
44
- """Should remove trailing slash from domain URL."""
45
- # Arrange
46
- test_env = {
47
- "SPACELIFT_API_TOKEN": "test_token",
48
- "TF_VAR_spacelift_graphql_endpoint": "https://test.spacelift.io/",
49
- }
50
-
51
- # Act
52
- with patch.dict(os.environ, test_env, clear=True):
53
- plugin = SpaceforgePlugin()
54
-
55
- # Assert
56
- assert plugin.spacelift_domain == "https://test.spacelift.io"
57
- assert plugin._api_enabled is True
58
-
59
- def test_should_disable_api_when_domain_has_no_https_prefix(self) -> None:
60
- """Should disable API when domain doesn't use HTTPS."""
61
- # Arrange
62
- test_env = {
63
- "SPACELIFT_API_TOKEN": "test_token",
64
- "TF_VAR_spacelift_graphql_endpoint": "test.spacelift.io",
65
- }
66
-
67
- # Act
68
- with patch.dict(os.environ, test_env, clear=True):
69
- plugin = SpaceforgePlugin()
70
-
71
- # Assert
72
- assert plugin.spacelift_domain == "test.spacelift.io"
73
- assert plugin._api_enabled is False
74
-
75
- def test_should_disable_api_when_only_token_provided(self) -> None:
76
- """Should disable API when only token is provided without domain."""
77
- # Arrange & Act
78
- with patch.dict(os.environ, {"SPACELIFT_API_TOKEN": "test_token"}, clear=True):
79
- plugin = SpaceforgePlugin()
80
-
81
- # Assert
82
- assert plugin._api_enabled is False
83
-
84
- def test_should_disable_api_when_only_domain_provided(self) -> None:
85
- """Should disable API when only domain is provided without token."""
86
- # Arrange & Act
87
- with patch.dict(
88
- os.environ,
89
- {"TF_VAR_spacelift_graphql_endpoint": "https://test.spacelift.io"},
90
- clear=True,
91
- ):
92
- plugin = SpaceforgePlugin()
93
-
94
- # Assert
95
- assert plugin._api_enabled is False
96
-
97
-
98
- class TestSpaceforgePluginLogging:
99
- """Test logging configuration and functionality."""
100
-
101
- def test_should_configure_logger_with_correct_name_and_level(self) -> None:
102
- """Should set up logger with proper name and level."""
103
- # Arrange & Act
104
- with patch.dict(os.environ, {}, clear=True):
105
- plugin = SpaceforgePlugin()
106
-
107
- # Assert
108
- assert plugin.logger.name == "spaceforge.SpaceforgePlugin"
109
- assert len(plugin.logger.handlers) >= 1
110
- assert plugin.logger.getEffectiveLevel() <= logging.INFO
111
-
112
- def test_should_enable_debug_logging_when_debug_env_set(self) -> None:
113
- """Should set DEBUG level when SPACELIFT_DEBUG environment variable is true."""
114
- # Arrange
115
- test_env = {"SPACELIFT_DEBUG": "true"}
116
-
117
- # Act
118
- with patch.dict(os.environ, test_env, clear=True):
119
- plugin = SpaceforgePlugin()
120
-
121
- # Assert
122
- assert plugin.logger.level == logging.DEBUG
123
-
124
- def test_should_include_run_id_in_log_format(self) -> None:
125
- """Should include run ID in log message format when available."""
126
- # Arrange
127
- test_env = {"TF_VAR_spacelift_run_id": "run-123"}
128
-
129
- # Act
130
- with patch.dict(os.environ, test_env, clear=True):
131
- plugin = SpaceforgePlugin()
132
- formatter = plugin.logger.handlers[0].formatter
133
-
134
- record = logging.LogRecord(
135
- name="test",
136
- level=logging.INFO,
137
- pathname="",
138
- lineno=0,
139
- msg="test message",
140
- args=(),
141
- exc_info=None,
142
- )
143
- record.levelname = "INFO"
144
-
145
- # Assert
146
- assert formatter is not None
147
- formatted = formatter.format(record)
148
- assert "[run-123]" in formatted or "[local]" in formatted
149
-
150
- def test_logger_color_formatting(self) -> None:
151
- """Test color formatting for different log levels."""
152
- plugin = SpaceforgePlugin()
153
- formatter = plugin.logger.handlers[0].formatter
154
- assert formatter is not None
155
-
156
- # Test different log levels
157
- levels_to_test = [
158
- (logging.INFO, "INFO"),
159
- (logging.DEBUG, "DEBUG"),
160
- (logging.WARNING, "WARNING"),
161
- (logging.ERROR, "ERROR"),
162
- ]
163
-
164
- for level, level_name in levels_to_test:
165
- record = logging.LogRecord(
166
- name="test",
167
- level=level,
168
- pathname="",
169
- lineno=0,
170
- msg="test message",
171
- args=(),
172
- exc_info=None,
173
- )
174
- record.levelname = level_name
175
- formatted = formatter.format(record)
176
-
177
- # Should contain color codes and plugin name
178
- assert "\033[" in formatted # ANSI color codes
179
- assert "(SpaceforgePlugin)" in formatted
180
- assert "test message" in formatted
181
-
182
-
183
- # Hook tests moved to test_plugin_hooks.py
184
-
185
-
186
- class TestSpaceforgePluginCLI:
187
- """Test command-line interface execution functionality."""
188
-
189
- def test_should_execute_cli_command_and_log_output_on_success(self) -> None:
190
- """Should run CLI command and log output when execution succeeds."""
191
- # Arrange
192
- plugin = SpaceforgePlugin()
193
- mock_process = Mock()
194
- mock_process.communicate.return_value = (b"success output\n", None)
195
- mock_process.returncode = 0
196
-
197
- # Act
198
- with patch("subprocess.Popen") as mock_popen:
199
- with patch.object(plugin.logger, "info") as mock_info:
200
- mock_popen.return_value = mock_process
201
- plugin.run_cli("echo", "test")
202
-
203
- # Assert
204
- mock_popen.assert_called_once_with(
205
- ("echo", "test"), stdout=subprocess.PIPE, stderr=subprocess.STDOUT
206
- )
207
- mock_info.assert_called_with("success output")
208
-
209
- def test_should_log_error_when_cli_command_fails(self) -> None:
210
- """Should log error details when CLI command returns non-zero exit code."""
211
- # Arrange
212
- plugin = SpaceforgePlugin()
213
- mock_process = Mock()
214
- mock_process.communicate.return_value = (None, b"error output\n")
215
- mock_process.returncode = 1
216
-
217
- # Act
218
- with patch("subprocess.Popen") as mock_popen:
219
- with patch.object(plugin.logger, "error") as mock_error:
220
- mock_popen.return_value = mock_process
221
- plugin.run_cli("false")
222
-
223
- # Assert
224
- mock_error.assert_any_call("Command failed with return code 1")
225
- mock_error.assert_any_call("error output")
226
-
227
- def test_run_cli_with_multiple_args(self) -> None:
228
- """Test CLI command with multiple arguments."""
229
- plugin = SpaceforgePlugin()
230
-
231
- with patch("subprocess.Popen") as mock_popen:
232
- mock_process = Mock()
233
- mock_process.communicate.return_value = (b"", None)
234
- mock_process.returncode = 0
235
- mock_popen.return_value = mock_process
236
-
237
- with patch.object(plugin.logger, "debug") as mock_debug:
238
- plugin.run_cli("git", "status", "--porcelain")
239
-
240
- mock_popen.assert_called_once_with(
241
- ("git", "status", "--porcelain"),
242
- stdout=subprocess.PIPE,
243
- stderr=subprocess.STDOUT,
244
- )
245
- mock_debug.assert_called_with("Running CLI command: git status --porcelain")
246
-
247
-
248
- class TestSpaceforgePluginAPI:
249
- """Test GraphQL API interaction functionality."""
250
-
251
- def test_should_exit_with_error_when_api_disabled(self) -> None:
252
- """Should exit with error message when API is not enabled."""
253
- # Arrange
254
- plugin = SpaceforgePlugin()
255
- plugin._api_enabled = False
256
-
257
- # Act & Assert
258
- with patch.object(plugin.logger, "error") as mock_error:
259
- with pytest.raises(SystemExit):
260
- plugin.query_api("query { test }")
261
-
262
- mock_error.assert_called_with(
263
- 'API is not enabled, please export "SPACELIFT_API_TOKEN" and "TF_VAR_spacelift_graphql_endpoint".'
264
- )
265
-
266
- def test_should_make_successful_api_request_with_correct_format(
267
- self, mock_api_response: Mock
268
- ) -> None:
269
- """Should execute GraphQL query with proper authentication and format."""
270
- # Arrange
271
- plugin = SpaceforgePlugin()
272
- plugin._api_enabled = True
273
- plugin._api_token = "test_token"
274
- plugin.spacelift_domain = "https://test.spacelift.io"
275
-
276
- expected_data = {"data": {"test": "result"}}
277
- mock_api_response.read.return_value = json.dumps(expected_data).encode("utf-8")
278
-
279
- # Act
280
- with patch("urllib.request.urlopen") as mock_urlopen:
281
- with patch("urllib.request.Request") as mock_request:
282
- mock_urlopen.return_value.__enter__ = Mock(
283
- return_value=mock_api_response
284
- )
285
- mock_urlopen.return_value.__exit__ = Mock(return_value=None)
286
-
287
- result = plugin.query_api("query { test }")
288
-
289
- # Assert
290
- mock_request.assert_called_once()
291
- call_args = mock_request.call_args[0]
292
- assert call_args[0] == "https://test.spacelift.io"
293
-
294
- request_data = json.loads(call_args[1].decode("utf-8"))
295
- assert request_data["query"] == "query { test }"
296
-
297
- headers = mock_request.call_args[0][2]
298
- assert headers["Content-Type"] == "application/json"
299
- assert headers["Authorization"] == "Bearer test_token"
300
-
301
- assert result == expected_data
302
-
303
- def test_query_api_with_variables(self) -> None:
304
- """Test API query with variables."""
305
- plugin = SpaceforgePlugin()
306
- plugin._api_enabled = True
307
- plugin._api_token = "test_token"
308
- plugin.spacelift_domain = "https://test.spacelift.io"
309
-
310
- mock_response_data = {"data": {"test": "result"}}
311
- mock_response = Mock()
312
- mock_response.read.return_value = json.dumps(mock_response_data).encode("utf-8")
313
-
314
- variables = {"stackId": "test-stack"}
315
-
316
- with patch("urllib.request.urlopen") as mock_urlopen:
317
- with patch("urllib.request.Request") as mock_request:
318
- mock_urlopen.return_value.__enter__ = Mock(return_value=mock_response)
319
- mock_urlopen.return_value.__exit__ = Mock(return_value=None)
320
-
321
- plugin.query_api(
322
- "query ($stackId: ID!) { stack(id: $stackId) { name } }", variables
323
- )
324
-
325
- # Verify request data includes variables
326
- request_data = json.loads(mock_request.call_args[0][1].decode("utf-8"))
327
- assert request_data["variables"] == variables
328
-
329
- def test_query_api_with_errors(self) -> None:
330
- """Test API query that returns errors."""
331
- plugin = SpaceforgePlugin()
332
- plugin._api_enabled = True
333
- plugin._api_token = "test_token"
334
- plugin.spacelift_domain = "https://test.spacelift.io"
335
-
336
- mock_response_data = {"errors": [{"message": "Test error"}]}
337
- mock_response = Mock()
338
- mock_response.read.return_value = json.dumps(mock_response_data).encode("utf-8")
339
-
340
- with patch("urllib.request.urlopen") as mock_urlopen:
341
- with patch.object(plugin.logger, "error") as mock_error:
342
- mock_urlopen.return_value.__enter__ = Mock(return_value=mock_response)
343
- mock_urlopen.return_value.__exit__ = Mock(return_value=None)
344
-
345
- result = plugin.query_api("query { test }")
346
-
347
- mock_error.assert_called_with("Error: [{'message': 'Test error'}]")
348
- assert result == mock_response_data
349
-
350
-
351
- # File operation tests moved to test_plugin_file_operations.py
352
-
353
-
354
- # Inheritance tests moved to test_plugin_inheritance.py
355
-
356
-
357
- # Edge case tests moved to test_plugin_inheritance.py
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes