spaceforge 0.0.7__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.
- {spaceforge-0.0.7 → spaceforge-0.0.9}/PKG-INFO +1 -1
- {spaceforge-0.0.7 → spaceforge-0.0.9}/plugins/enviroment_manager/plugin.py +5 -2
- {spaceforge-0.0.7 → spaceforge-0.0.9}/plugins/enviroment_manager/plugin.yaml +5 -2
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge/_version_scm.py +3 -3
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge/plugin.py +26 -14
- spaceforge-0.0.9/spaceforge/test_plugin.py +357 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge.egg-info/PKG-INFO +1 -1
- spaceforge-0.0.7/spaceforge/test_plugin.py +0 -357
- {spaceforge-0.0.7 → spaceforge-0.0.9}/.github/workflows/ci.yml +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/.github/workflows/release.yml +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/.gitignore +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/LICENSE +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/MANIFEST.in +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/README.md +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/go.mod +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/plugins/enviroment_manager/requirements.txt +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/plugins/infracost/plugin.py +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/plugins/infracost/plugin.yaml +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/plugins/sops/plugin.py +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/plugins/sops/plugin.yaml +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/plugins/sops/requirements.txt +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/plugins/wiz/plugin.py +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/plugins/wiz/plugin.yaml +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/pyproject.toml +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/setup.cfg +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/setup.py +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge/README.md +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge/__init__.py +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge/__main__.py +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge/_version.py +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge/cls.py +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge/conftest.py +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge/generator.py +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge/runner.py +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge/schema.json +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge/templates/binary_install.sh.j2 +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge/templates/ensure_spaceforge_and_run.sh.j2 +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge/test_cls.py +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge/test_generator.py +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge/test_generator_binaries.py +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge/test_generator_core.py +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge/test_generator_hooks.py +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge/test_generator_parameters.py +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge/test_plugin_file_operations.py +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge/test_plugin_hooks.py +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge/test_plugin_inheritance.py +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge/test_runner.py +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge/test_runner_cli.py +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge/test_runner_core.py +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge/test_runner_execution.py +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge.egg-info/SOURCES.txt +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge.egg-info/dependency_links.txt +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge.egg-info/entry_points.txt +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge.egg-info/not-zip-safe +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge.egg-info/requires.txt +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/spaceforge.egg-info/top_level.txt +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/templates.go +0 -0
- {spaceforge-0.0.7 → spaceforge-0.0.9}/test.sh +0 -0
|
@@ -149,6 +149,9 @@ resource "spacelift_environment_variable" "__this" {
|
|
|
149
149
|
)
|
|
150
150
|
]
|
|
151
151
|
|
|
152
|
+
def __init__(self):
|
|
153
|
+
super().__init__()
|
|
154
|
+
|
|
152
155
|
def load_yaml_file(self, file_path):
|
|
153
156
|
"""Load YAML file and return parsed content"""
|
|
154
157
|
try:
|
|
@@ -190,13 +193,13 @@ resource "spacelift_environment_variable" "__this" {
|
|
|
190
193
|
query = "{ stack(id: \"" + stack_id + "\") { trackedCommit { hash } } }"
|
|
191
194
|
response = self.query_api(query)
|
|
192
195
|
if "errors" in response:
|
|
193
|
-
self.logger.error("Error fetching stack tracked commit:
|
|
196
|
+
self.logger.error(f"Error fetching stack tracked commit: {response['errors']}")
|
|
194
197
|
continue
|
|
195
198
|
|
|
196
199
|
# Ensure we have a tracked commit
|
|
197
200
|
try:
|
|
198
201
|
tracked_commit = response["data"]["stack"]["trackedCommit"]["hash"]
|
|
199
|
-
except TypeError:
|
|
202
|
+
except (TypeError, KeyError):
|
|
200
203
|
tracked_commit = None
|
|
201
204
|
if tracked_commit is None:
|
|
202
205
|
self.logger.error(f"Stack {stack_id} has no tracked commit. Skipping.")
|
|
@@ -286,6 +286,9 @@ contexts:
|
|
|
286
286
|
)
|
|
287
287
|
]
|
|
288
288
|
|
|
289
|
+
def __init__(self):
|
|
290
|
+
super().__init__()
|
|
291
|
+
|
|
289
292
|
def load_yaml_file(self, file_path):
|
|
290
293
|
"""Load YAML file and return parsed content"""
|
|
291
294
|
try:
|
|
@@ -327,13 +330,13 @@ contexts:
|
|
|
327
330
|
query = "{ stack(id: \"" + stack_id + "\") { trackedCommit { hash } } }"
|
|
328
331
|
response = self.query_api(query)
|
|
329
332
|
if "errors" in response:
|
|
330
|
-
self.logger.error("Error fetching stack tracked commit:
|
|
333
|
+
self.logger.error(f"Error fetching stack tracked commit: {response['errors']}")
|
|
331
334
|
continue
|
|
332
335
|
|
|
333
336
|
# Ensure we have a tracked commit
|
|
334
337
|
try:
|
|
335
338
|
tracked_commit = response["data"]["stack"]["trackedCommit"]["hash"]
|
|
336
|
-
except TypeError:
|
|
339
|
+
except (TypeError, KeyError):
|
|
337
340
|
tracked_commit = None
|
|
338
341
|
if tracked_commit is None:
|
|
339
342
|
self.logger.error(f"Stack {stack_id} has no tracked commit. Skipping.")
|
|
@@ -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.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 0,
|
|
31
|
+
__version__ = version = '0.0.9'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 0, 9)
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'geefe51823'
|
|
@@ -10,6 +10,7 @@ import subprocess
|
|
|
10
10
|
import urllib.request
|
|
11
11
|
from abc import ABC
|
|
12
12
|
from typing import Any, Dict, List, Optional, Tuple
|
|
13
|
+
from urllib.error import HTTPError
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class SpaceforgePlugin(ABC):
|
|
@@ -48,14 +49,14 @@ class SpaceforgePlugin(ABC):
|
|
|
48
49
|
if self.spacelift_domain and isinstance(self.spacelift_domain, str):
|
|
49
50
|
# this must occur after we check if spacelift domain is false
|
|
50
51
|
# because the domain could be set but not start with https://
|
|
51
|
-
if self.spacelift_domain.startswith("https://"):
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
else:
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
# if self.spacelift_domain.startswith("https://"):
|
|
53
|
+
# if self.spacelift_domain.endswith("/"):
|
|
54
|
+
# self.spacelift_domain = self.spacelift_domain[:-1]
|
|
55
|
+
# else:
|
|
56
|
+
# self.logger.warning(
|
|
57
|
+
# "SPACELIFT_DOMAIN does not start with https://, api calls will fail."
|
|
58
|
+
# )
|
|
59
|
+
# self._api_enabled = False
|
|
59
60
|
|
|
60
61
|
if self._api_enabled:
|
|
61
62
|
self._spacelift_markdown_endpoint = self.spacelift_domain.replace(
|
|
@@ -204,8 +205,17 @@ class SpaceforgePlugin(ABC):
|
|
|
204
205
|
json.dumps(data).encode("utf-8"),
|
|
205
206
|
headers,
|
|
206
207
|
)
|
|
207
|
-
|
|
208
|
-
|
|
208
|
+
|
|
209
|
+
try:
|
|
210
|
+
with urllib.request.urlopen(req) as response:
|
|
211
|
+
resp: Dict[str, Any] = json.loads(response.read().decode("utf-8"))
|
|
212
|
+
except urllib.error.HTTPError as e:
|
|
213
|
+
if hasattr(e, "read"):
|
|
214
|
+
resp = json.loads(e.read().decode("utf-8"))
|
|
215
|
+
else:
|
|
216
|
+
# We should not get here, but if we do re-raise the exception
|
|
217
|
+
self.logger.error(f"HTTP error occurred: ({e.code}) {e.reason} {e.msg}")
|
|
218
|
+
raise e
|
|
209
219
|
|
|
210
220
|
if "errors" in resp:
|
|
211
221
|
self.logger.error(f"Error: {resp['errors']}")
|
|
@@ -287,8 +297,8 @@ class SpaceforgePlugin(ABC):
|
|
|
287
297
|
headers = resp["headers"]
|
|
288
298
|
headers["Content-Type"] = "text/markdown"
|
|
289
299
|
headers["Content-Length"] = str(len(markdown))
|
|
290
|
-
except
|
|
291
|
-
self.logger.error(f"HTTP error occurred: {e}")
|
|
300
|
+
except HTTPError as e:
|
|
301
|
+
self.logger.error(f"HTTP error occurred: ({e.code}) {e.reason} {e.msg}")
|
|
292
302
|
return False
|
|
293
303
|
|
|
294
304
|
# Now we upload the markdown content to the signed URL
|
|
@@ -307,8 +317,10 @@ class SpaceforgePlugin(ABC):
|
|
|
307
317
|
)
|
|
308
318
|
return False
|
|
309
319
|
self.logger.debug("Markdown content uploaded successfully.")
|
|
310
|
-
except
|
|
311
|
-
self.logger.error(
|
|
320
|
+
except HTTPError as e:
|
|
321
|
+
self.logger.error(
|
|
322
|
+
f"HTTP error occurred during upload: ({e.code}) {e.reason} {e.msg}"
|
|
323
|
+
)
|
|
312
324
|
return False
|
|
313
325
|
|
|
314
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,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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|