amplify-excel-migrator 1.1.5__py3-none-any.whl → 1.2.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- amplify_excel_migrator/__init__.py +17 -0
- amplify_excel_migrator/auth/__init__.py +6 -0
- amplify_excel_migrator/auth/cognito_auth.py +306 -0
- amplify_excel_migrator/auth/provider.py +42 -0
- amplify_excel_migrator/cli/__init__.py +5 -0
- amplify_excel_migrator/cli/commands.py +165 -0
- amplify_excel_migrator/client.py +47 -0
- amplify_excel_migrator/core/__init__.py +5 -0
- amplify_excel_migrator/core/config.py +98 -0
- amplify_excel_migrator/data/__init__.py +7 -0
- amplify_excel_migrator/data/excel_reader.py +23 -0
- amplify_excel_migrator/data/transformer.py +119 -0
- amplify_excel_migrator/data/validator.py +48 -0
- amplify_excel_migrator/graphql/__init__.py +8 -0
- amplify_excel_migrator/graphql/client.py +137 -0
- amplify_excel_migrator/graphql/executor.py +405 -0
- amplify_excel_migrator/graphql/mutation_builder.py +80 -0
- amplify_excel_migrator/graphql/query_builder.py +194 -0
- amplify_excel_migrator/migration/__init__.py +8 -0
- amplify_excel_migrator/migration/batch_uploader.py +23 -0
- amplify_excel_migrator/migration/failure_tracker.py +92 -0
- amplify_excel_migrator/migration/orchestrator.py +143 -0
- amplify_excel_migrator/migration/progress_reporter.py +57 -0
- amplify_excel_migrator/schema/__init__.py +6 -0
- model_field_parser.py → amplify_excel_migrator/schema/field_parser.py +100 -22
- amplify_excel_migrator/schema/introspector.py +95 -0
- {amplify_excel_migrator-1.1.5.dist-info → amplify_excel_migrator-1.2.15.dist-info}/METADATA +121 -26
- amplify_excel_migrator-1.2.15.dist-info/RECORD +40 -0
- amplify_excel_migrator-1.2.15.dist-info/entry_points.txt +2 -0
- amplify_excel_migrator-1.2.15.dist-info/top_level.txt +2 -0
- tests/__init__.py +1 -0
- tests/test_cli_commands.py +292 -0
- tests/test_client.py +187 -0
- tests/test_cognito_auth.py +363 -0
- tests/test_config_manager.py +347 -0
- tests/test_field_parser.py +615 -0
- tests/test_mutation_builder.py +391 -0
- tests/test_query_builder.py +384 -0
- amplify_client.py +0 -941
- amplify_excel_migrator-1.1.5.dist-info/RECORD +0 -9
- amplify_excel_migrator-1.1.5.dist-info/entry_points.txt +0 -2
- amplify_excel_migrator-1.1.5.dist-info/top_level.txt +0 -3
- migrator.py +0 -437
- {amplify_excel_migrator-1.1.5.dist-info → amplify_excel_migrator-1.2.15.dist-info}/WHEEL +0 -0
- {amplify_excel_migrator-1.1.5.dist-info → amplify_excel_migrator-1.2.15.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
"""Tests for ConfigManager class"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import pytest
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from unittest.mock import patch, MagicMock
|
|
7
|
+
from amplify_excel_migrator.core import ConfigManager
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.fixture
|
|
11
|
+
def sample_config():
|
|
12
|
+
"""Sample configuration for testing"""
|
|
13
|
+
return {
|
|
14
|
+
"excel_path": "test_data.xlsx",
|
|
15
|
+
"api_endpoint": "https://test.appsync-api.us-east-1.amazonaws.com/graphql",
|
|
16
|
+
"region": "us-east-1",
|
|
17
|
+
"user_pool_id": "us-east-1_testpool",
|
|
18
|
+
"client_id": "test-client-id",
|
|
19
|
+
"username": "test@example.com",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.fixture
|
|
24
|
+
def temp_config_manager(tmp_path):
|
|
25
|
+
"""Create ConfigManager with temporary config file"""
|
|
26
|
+
config_file = tmp_path / "test_config.json"
|
|
27
|
+
return ConfigManager(str(config_file))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TestConfigManagerInitialization:
|
|
31
|
+
"""Test ConfigManager initialization"""
|
|
32
|
+
|
|
33
|
+
def test_default_initialization(self):
|
|
34
|
+
"""Test ConfigManager with default config path"""
|
|
35
|
+
manager = ConfigManager()
|
|
36
|
+
assert manager.config_path == Path.home() / ".amplify-migrator" / "config.json"
|
|
37
|
+
assert manager._config == {}
|
|
38
|
+
|
|
39
|
+
def test_custom_path_initialization(self, tmp_path):
|
|
40
|
+
"""Test ConfigManager with custom config path"""
|
|
41
|
+
custom_path = tmp_path / "custom_config.json"
|
|
42
|
+
manager = ConfigManager(str(custom_path))
|
|
43
|
+
assert manager.config_path == custom_path
|
|
44
|
+
assert manager._config == {}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class TestConfigManagerLoad:
|
|
48
|
+
"""Test ConfigManager.load() method"""
|
|
49
|
+
|
|
50
|
+
def test_load_existing_config(self, temp_config_manager, sample_config):
|
|
51
|
+
"""Test loading existing config file"""
|
|
52
|
+
temp_config_manager.config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
53
|
+
with open(temp_config_manager.config_path, "w") as f:
|
|
54
|
+
json.dump(sample_config, f)
|
|
55
|
+
|
|
56
|
+
result = temp_config_manager.load()
|
|
57
|
+
assert result == sample_config
|
|
58
|
+
assert temp_config_manager._config == sample_config
|
|
59
|
+
|
|
60
|
+
def test_load_nonexistent_config(self, temp_config_manager):
|
|
61
|
+
"""Test loading when config file doesn't exist"""
|
|
62
|
+
result = temp_config_manager.load()
|
|
63
|
+
assert result == {}
|
|
64
|
+
assert temp_config_manager._config == {}
|
|
65
|
+
|
|
66
|
+
def test_load_corrupted_json(self, temp_config_manager):
|
|
67
|
+
"""Test loading corrupted JSON file"""
|
|
68
|
+
temp_config_manager.config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
69
|
+
temp_config_manager.config_path.write_text("invalid json {")
|
|
70
|
+
|
|
71
|
+
result = temp_config_manager.load()
|
|
72
|
+
assert result == {}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class TestConfigManagerSave:
|
|
76
|
+
"""Test ConfigManager.save() method"""
|
|
77
|
+
|
|
78
|
+
def test_save_config(self, temp_config_manager, sample_config):
|
|
79
|
+
"""Test saving configuration"""
|
|
80
|
+
temp_config_manager.save(sample_config)
|
|
81
|
+
|
|
82
|
+
assert temp_config_manager.config_path.exists()
|
|
83
|
+
with open(temp_config_manager.config_path) as f:
|
|
84
|
+
loaded = json.load(f)
|
|
85
|
+
assert loaded == sample_config
|
|
86
|
+
|
|
87
|
+
def test_save_creates_directory(self, tmp_path):
|
|
88
|
+
"""Test that save creates parent directories"""
|
|
89
|
+
config_file = tmp_path / "nested" / "dir" / "config.json"
|
|
90
|
+
manager = ConfigManager(str(config_file))
|
|
91
|
+
|
|
92
|
+
assert not config_file.parent.exists()
|
|
93
|
+
manager.save({"key": "value"})
|
|
94
|
+
assert config_file.parent.exists()
|
|
95
|
+
assert config_file.exists()
|
|
96
|
+
|
|
97
|
+
def test_save_excludes_sensitive_keys(self, temp_config_manager):
|
|
98
|
+
"""Test that sensitive keys are not saved"""
|
|
99
|
+
config_with_secrets = {
|
|
100
|
+
"username": "test@example.com",
|
|
101
|
+
"password": "secret123",
|
|
102
|
+
"ADMIN_PASSWORD": "admin_secret",
|
|
103
|
+
"api_endpoint": "https://api.example.com",
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
temp_config_manager.save(config_with_secrets)
|
|
107
|
+
|
|
108
|
+
with open(temp_config_manager.config_path) as f:
|
|
109
|
+
loaded = json.load(f)
|
|
110
|
+
assert "password" not in loaded
|
|
111
|
+
assert "ADMIN_PASSWORD" not in loaded
|
|
112
|
+
assert "username" in loaded
|
|
113
|
+
assert "api_endpoint" in loaded
|
|
114
|
+
|
|
115
|
+
def test_save_updates_internal_config(self, temp_config_manager, sample_config):
|
|
116
|
+
"""Test that save updates the internal _config"""
|
|
117
|
+
temp_config_manager.save(sample_config)
|
|
118
|
+
# Note: internal _config excludes sensitive keys
|
|
119
|
+
expected = {k: v for k, v in sample_config.items() if k not in ConfigManager.SENSITIVE_KEYS}
|
|
120
|
+
assert temp_config_manager._config == expected
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class TestConfigManagerGet:
|
|
124
|
+
"""Test ConfigManager.get() method"""
|
|
125
|
+
|
|
126
|
+
def test_get_existing_key(self, temp_config_manager, sample_config):
|
|
127
|
+
"""Test getting existing key"""
|
|
128
|
+
temp_config_manager._config = sample_config
|
|
129
|
+
result = temp_config_manager.get("excel_path")
|
|
130
|
+
assert result == "test_data.xlsx"
|
|
131
|
+
|
|
132
|
+
def test_get_missing_key_with_default(self, temp_config_manager):
|
|
133
|
+
"""Test getting missing key with default value"""
|
|
134
|
+
temp_config_manager._config = {}
|
|
135
|
+
result = temp_config_manager.get("missing_key", "default_value")
|
|
136
|
+
assert result == "default_value"
|
|
137
|
+
|
|
138
|
+
def test_get_missing_key_without_default(self, temp_config_manager):
|
|
139
|
+
"""Test getting missing key without default"""
|
|
140
|
+
temp_config_manager._config = {}
|
|
141
|
+
result = temp_config_manager.get("missing_key")
|
|
142
|
+
assert result is None
|
|
143
|
+
|
|
144
|
+
def test_get_loads_config_if_empty(self, temp_config_manager, sample_config):
|
|
145
|
+
"""Test that get() loads config if _config is empty"""
|
|
146
|
+
temp_config_manager.config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
147
|
+
with open(temp_config_manager.config_path, "w") as f:
|
|
148
|
+
json.dump(sample_config, f)
|
|
149
|
+
|
|
150
|
+
temp_config_manager._config = {}
|
|
151
|
+
result = temp_config_manager.get("excel_path")
|
|
152
|
+
assert result == "test_data.xlsx"
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class TestConfigManagerSet:
|
|
156
|
+
"""Test ConfigManager.set() method"""
|
|
157
|
+
|
|
158
|
+
def test_set_new_key(self, temp_config_manager):
|
|
159
|
+
"""Test setting a new key"""
|
|
160
|
+
temp_config_manager._config = {}
|
|
161
|
+
temp_config_manager.set("new_key", "new_value")
|
|
162
|
+
assert temp_config_manager._config["new_key"] == "new_value"
|
|
163
|
+
|
|
164
|
+
def test_set_existing_key(self, temp_config_manager, sample_config):
|
|
165
|
+
"""Test updating an existing key"""
|
|
166
|
+
temp_config_manager._config = sample_config.copy()
|
|
167
|
+
temp_config_manager.set("excel_path", "new_path.xlsx")
|
|
168
|
+
assert temp_config_manager._config["excel_path"] == "new_path.xlsx"
|
|
169
|
+
|
|
170
|
+
def test_set_loads_config_if_empty(self, temp_config_manager, sample_config):
|
|
171
|
+
"""Test that set() loads config if _config is empty"""
|
|
172
|
+
temp_config_manager.config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
173
|
+
with open(temp_config_manager.config_path, "w") as f:
|
|
174
|
+
json.dump(sample_config, f)
|
|
175
|
+
|
|
176
|
+
temp_config_manager._config = {}
|
|
177
|
+
temp_config_manager.set("new_key", "new_value")
|
|
178
|
+
assert "excel_path" in temp_config_manager._config # From loaded config
|
|
179
|
+
assert temp_config_manager._config["new_key"] == "new_value"
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class TestConfigManagerUpdate:
|
|
183
|
+
"""Test ConfigManager.update() method"""
|
|
184
|
+
|
|
185
|
+
def test_update_config(self, temp_config_manager, sample_config):
|
|
186
|
+
"""Test updating config with multiple values"""
|
|
187
|
+
temp_config_manager._config = sample_config.copy()
|
|
188
|
+
updates = {"excel_path": "updated.xlsx", "new_key": "new_value"}
|
|
189
|
+
|
|
190
|
+
temp_config_manager.update(updates)
|
|
191
|
+
|
|
192
|
+
assert temp_config_manager._config["excel_path"] == "updated.xlsx"
|
|
193
|
+
assert temp_config_manager._config["new_key"] == "new_value"
|
|
194
|
+
assert temp_config_manager.config_path.exists()
|
|
195
|
+
|
|
196
|
+
def test_update_saves_to_file(self, temp_config_manager):
|
|
197
|
+
"""Test that update() persists changes to file"""
|
|
198
|
+
temp_config_manager._config = {"old_key": "old_value"}
|
|
199
|
+
temp_config_manager.update({"new_key": "new_value"})
|
|
200
|
+
|
|
201
|
+
with open(temp_config_manager.config_path) as f:
|
|
202
|
+
loaded = json.load(f)
|
|
203
|
+
assert loaded["old_key"] == "old_value"
|
|
204
|
+
assert loaded["new_key"] == "new_value"
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class TestConfigManagerPromptForValue:
|
|
208
|
+
"""Test ConfigManager.prompt_for_value() method"""
|
|
209
|
+
|
|
210
|
+
def test_prompt_with_default_empty_input(self, temp_config_manager):
|
|
211
|
+
"""Test prompt with default when user provides empty input"""
|
|
212
|
+
with patch("builtins.input", return_value=""):
|
|
213
|
+
result = temp_config_manager.prompt_for_value("Test prompt", "default_value")
|
|
214
|
+
assert result == "default_value"
|
|
215
|
+
|
|
216
|
+
def test_prompt_with_user_input(self, temp_config_manager):
|
|
217
|
+
"""Test prompt with user input"""
|
|
218
|
+
with patch("builtins.input", return_value="user_value"):
|
|
219
|
+
result = temp_config_manager.prompt_for_value("Test prompt", "default_value")
|
|
220
|
+
assert result == "user_value"
|
|
221
|
+
|
|
222
|
+
def test_prompt_without_default(self, temp_config_manager):
|
|
223
|
+
"""Test prompt without default"""
|
|
224
|
+
with patch("builtins.input", return_value="custom_value"):
|
|
225
|
+
result = temp_config_manager.prompt_for_value("Test prompt")
|
|
226
|
+
assert result == "custom_value"
|
|
227
|
+
|
|
228
|
+
def test_prompt_secret_input(self, temp_config_manager):
|
|
229
|
+
"""Test prompting for secret (password) input"""
|
|
230
|
+
with patch("amplify_excel_migrator.core.config.getpass", return_value="secret123"):
|
|
231
|
+
result = temp_config_manager.prompt_for_value("Password", secret=True)
|
|
232
|
+
assert result == "secret123"
|
|
233
|
+
|
|
234
|
+
def test_prompt_strips_whitespace(self, temp_config_manager):
|
|
235
|
+
"""Test that input is stripped of whitespace"""
|
|
236
|
+
with patch("builtins.input", return_value=" value with spaces "):
|
|
237
|
+
result = temp_config_manager.prompt_for_value("Test prompt")
|
|
238
|
+
assert result == "value with spaces"
|
|
239
|
+
|
|
240
|
+
def test_prompt_displays_default_in_brackets(self, temp_config_manager):
|
|
241
|
+
"""Test that default is shown in brackets in prompt"""
|
|
242
|
+
with patch("builtins.input", return_value="") as mock_input:
|
|
243
|
+
temp_config_manager.prompt_for_value("Test prompt", "default123")
|
|
244
|
+
mock_input.assert_called_once_with("Test prompt [default123]: ")
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class TestConfigManagerGetOrPrompt:
|
|
248
|
+
"""Test ConfigManager.get_or_prompt() method"""
|
|
249
|
+
|
|
250
|
+
def test_get_or_prompt_returns_cached(self, temp_config_manager, sample_config):
|
|
251
|
+
"""Test that cached value is returned without prompting"""
|
|
252
|
+
temp_config_manager._config = sample_config
|
|
253
|
+
with patch("builtins.input", return_value="should_not_be_used"):
|
|
254
|
+
result = temp_config_manager.get_or_prompt("excel_path", "Excel path")
|
|
255
|
+
assert result == "test_data.xlsx"
|
|
256
|
+
|
|
257
|
+
def test_get_or_prompt_prompts_when_missing(self, temp_config_manager):
|
|
258
|
+
"""Test that prompt is shown when key is missing"""
|
|
259
|
+
temp_config_manager._config = {}
|
|
260
|
+
with patch("builtins.input", return_value="new_value"):
|
|
261
|
+
result = temp_config_manager.get_or_prompt("missing_key", "Test prompt")
|
|
262
|
+
assert result == "new_value"
|
|
263
|
+
|
|
264
|
+
def test_get_or_prompt_with_default(self, temp_config_manager):
|
|
265
|
+
"""Test get_or_prompt with default value"""
|
|
266
|
+
temp_config_manager._config = {}
|
|
267
|
+
with patch("builtins.input", return_value=""):
|
|
268
|
+
result = temp_config_manager.get_or_prompt("missing_key", "Test prompt", "default123")
|
|
269
|
+
assert result == "default123"
|
|
270
|
+
|
|
271
|
+
def test_get_or_prompt_loads_config_if_empty(self, temp_config_manager, sample_config):
|
|
272
|
+
"""Test that get_or_prompt loads config if _config is empty"""
|
|
273
|
+
temp_config_manager.config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
274
|
+
with open(temp_config_manager.config_path, "w") as f:
|
|
275
|
+
json.dump(sample_config, f)
|
|
276
|
+
|
|
277
|
+
temp_config_manager._config = {}
|
|
278
|
+
result = temp_config_manager.get_or_prompt("excel_path", "Excel path")
|
|
279
|
+
assert result == "test_data.xlsx"
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class TestConfigManagerExists:
|
|
283
|
+
"""Test ConfigManager.exists() method"""
|
|
284
|
+
|
|
285
|
+
def test_exists_when_file_exists(self, temp_config_manager):
|
|
286
|
+
"""Test exists() returns True when config file exists"""
|
|
287
|
+
temp_config_manager.config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
288
|
+
temp_config_manager.config_path.touch()
|
|
289
|
+
assert temp_config_manager.exists() is True
|
|
290
|
+
|
|
291
|
+
def test_exists_when_file_not_exists(self, temp_config_manager):
|
|
292
|
+
"""Test exists() returns False when config file doesn't exist"""
|
|
293
|
+
assert temp_config_manager.exists() is False
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
class TestConfigManagerClear:
|
|
297
|
+
"""Test ConfigManager.clear() method"""
|
|
298
|
+
|
|
299
|
+
def test_clear_removes_file(self, temp_config_manager, sample_config):
|
|
300
|
+
"""Test that clear removes the config file"""
|
|
301
|
+
temp_config_manager.save(sample_config)
|
|
302
|
+
assert temp_config_manager.config_path.exists()
|
|
303
|
+
|
|
304
|
+
temp_config_manager.clear()
|
|
305
|
+
assert not temp_config_manager.config_path.exists()
|
|
306
|
+
|
|
307
|
+
def test_clear_resets_internal_config(self, temp_config_manager, sample_config):
|
|
308
|
+
"""Test that clear resets internal _config"""
|
|
309
|
+
temp_config_manager._config = sample_config
|
|
310
|
+
temp_config_manager.clear()
|
|
311
|
+
assert temp_config_manager._config == {}
|
|
312
|
+
|
|
313
|
+
def test_clear_when_file_not_exists(self, temp_config_manager):
|
|
314
|
+
"""Test that clear doesn't fail when file doesn't exist"""
|
|
315
|
+
assert not temp_config_manager.config_path.exists()
|
|
316
|
+
temp_config_manager.clear() # Should not raise
|
|
317
|
+
assert temp_config_manager._config == {}
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
class TestConfigManagerIntegration:
|
|
321
|
+
"""Integration tests for ConfigManager"""
|
|
322
|
+
|
|
323
|
+
def test_full_workflow(self, temp_config_manager, sample_config):
|
|
324
|
+
"""Test complete workflow: save, load, update, clear"""
|
|
325
|
+
temp_config_manager.save(sample_config)
|
|
326
|
+
assert temp_config_manager.exists()
|
|
327
|
+
|
|
328
|
+
loaded = temp_config_manager.load()
|
|
329
|
+
assert loaded["excel_path"] == "test_data.xlsx"
|
|
330
|
+
|
|
331
|
+
temp_config_manager.update({"excel_path": "updated.xlsx"})
|
|
332
|
+
assert temp_config_manager.get("excel_path") == "updated.xlsx"
|
|
333
|
+
|
|
334
|
+
temp_config_manager.clear()
|
|
335
|
+
assert not temp_config_manager.exists()
|
|
336
|
+
assert temp_config_manager._config == {}
|
|
337
|
+
|
|
338
|
+
def test_multiple_managers_same_file(self, tmp_path, sample_config):
|
|
339
|
+
"""Test multiple ConfigManager instances with same file"""
|
|
340
|
+
config_file = tmp_path / "shared_config.json"
|
|
341
|
+
|
|
342
|
+
manager1 = ConfigManager(str(config_file))
|
|
343
|
+
manager1.save(sample_config)
|
|
344
|
+
|
|
345
|
+
manager2 = ConfigManager(str(config_file))
|
|
346
|
+
loaded = manager2.load()
|
|
347
|
+
assert loaded == sample_config
|