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.
- {spaceforge-0.0.8 → spaceforge-0.0.9}/PKG-INFO +1 -1
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/_version_scm.py +3 -3
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/plugin.py +6 -4
- spaceforge-0.0.9/spaceforge/test_plugin.py +357 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge.egg-info/PKG-INFO +1 -1
- spaceforge-0.0.8/spaceforge/test_plugin.py +0 -357
- {spaceforge-0.0.8 → spaceforge-0.0.9}/.github/workflows/ci.yml +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/.github/workflows/release.yml +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/.gitignore +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/LICENSE +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/MANIFEST.in +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/README.md +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/go.mod +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/plugins/enviroment_manager/plugin.py +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/plugins/enviroment_manager/plugin.yaml +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/plugins/enviroment_manager/requirements.txt +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/plugins/infracost/plugin.py +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/plugins/infracost/plugin.yaml +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/plugins/sops/plugin.py +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/plugins/sops/plugin.yaml +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/plugins/sops/requirements.txt +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/plugins/wiz/plugin.py +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/plugins/wiz/plugin.yaml +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/pyproject.toml +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/setup.cfg +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/setup.py +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/README.md +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/__init__.py +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/__main__.py +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/_version.py +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/cls.py +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/conftest.py +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/generator.py +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/runner.py +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/schema.json +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/templates/binary_install.sh.j2 +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/templates/ensure_spaceforge_and_run.sh.j2 +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/test_cls.py +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/test_generator.py +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/test_generator_binaries.py +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/test_generator_core.py +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/test_generator_hooks.py +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/test_generator_parameters.py +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/test_plugin_file_operations.py +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/test_plugin_hooks.py +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/test_plugin_inheritance.py +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/test_runner.py +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/test_runner_cli.py +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/test_runner_core.py +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge/test_runner_execution.py +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge.egg-info/SOURCES.txt +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge.egg-info/dependency_links.txt +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge.egg-info/entry_points.txt +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge.egg-info/not-zip-safe +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge.egg-info/requires.txt +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/spaceforge.egg-info/top_level.txt +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/templates.go +0 -0
- {spaceforge-0.0.8 → spaceforge-0.0.9}/test.sh +0 -0
|
@@ -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'
|
|
@@ -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,
|
|
214
|
-
resp = json.loads(e.read().decode(
|
|
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(
|
|
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,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
|
|
File without changes
|
|
File without changes
|