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.
Files changed (45) hide show
  1. amplify_excel_migrator/__init__.py +17 -0
  2. amplify_excel_migrator/auth/__init__.py +6 -0
  3. amplify_excel_migrator/auth/cognito_auth.py +306 -0
  4. amplify_excel_migrator/auth/provider.py +42 -0
  5. amplify_excel_migrator/cli/__init__.py +5 -0
  6. amplify_excel_migrator/cli/commands.py +165 -0
  7. amplify_excel_migrator/client.py +47 -0
  8. amplify_excel_migrator/core/__init__.py +5 -0
  9. amplify_excel_migrator/core/config.py +98 -0
  10. amplify_excel_migrator/data/__init__.py +7 -0
  11. amplify_excel_migrator/data/excel_reader.py +23 -0
  12. amplify_excel_migrator/data/transformer.py +119 -0
  13. amplify_excel_migrator/data/validator.py +48 -0
  14. amplify_excel_migrator/graphql/__init__.py +8 -0
  15. amplify_excel_migrator/graphql/client.py +137 -0
  16. amplify_excel_migrator/graphql/executor.py +405 -0
  17. amplify_excel_migrator/graphql/mutation_builder.py +80 -0
  18. amplify_excel_migrator/graphql/query_builder.py +194 -0
  19. amplify_excel_migrator/migration/__init__.py +8 -0
  20. amplify_excel_migrator/migration/batch_uploader.py +23 -0
  21. amplify_excel_migrator/migration/failure_tracker.py +92 -0
  22. amplify_excel_migrator/migration/orchestrator.py +143 -0
  23. amplify_excel_migrator/migration/progress_reporter.py +57 -0
  24. amplify_excel_migrator/schema/__init__.py +6 -0
  25. model_field_parser.py → amplify_excel_migrator/schema/field_parser.py +100 -22
  26. amplify_excel_migrator/schema/introspector.py +95 -0
  27. {amplify_excel_migrator-1.1.5.dist-info → amplify_excel_migrator-1.2.15.dist-info}/METADATA +121 -26
  28. amplify_excel_migrator-1.2.15.dist-info/RECORD +40 -0
  29. amplify_excel_migrator-1.2.15.dist-info/entry_points.txt +2 -0
  30. amplify_excel_migrator-1.2.15.dist-info/top_level.txt +2 -0
  31. tests/__init__.py +1 -0
  32. tests/test_cli_commands.py +292 -0
  33. tests/test_client.py +187 -0
  34. tests/test_cognito_auth.py +363 -0
  35. tests/test_config_manager.py +347 -0
  36. tests/test_field_parser.py +615 -0
  37. tests/test_mutation_builder.py +391 -0
  38. tests/test_query_builder.py +384 -0
  39. amplify_client.py +0 -941
  40. amplify_excel_migrator-1.1.5.dist-info/RECORD +0 -9
  41. amplify_excel_migrator-1.1.5.dist-info/entry_points.txt +0 -2
  42. amplify_excel_migrator-1.1.5.dist-info/top_level.txt +0 -3
  43. migrator.py +0 -437
  44. {amplify_excel_migrator-1.1.5.dist-info → amplify_excel_migrator-1.2.15.dist-info}/WHEEL +0 -0
  45. {amplify_excel_migrator-1.1.5.dist-info → amplify_excel_migrator-1.2.15.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: amplify-excel-migrator
3
- Version: 1.1.5
3
+ Version: 1.2.15
4
4
  Summary: A CLI tool to migrate Excel data to AWS Amplify
5
5
  Home-page: https://github.com/EyalPoly/amplify-excel-migrator
6
6
  Author: Eyal Politansky
@@ -59,14 +59,6 @@ Install the latest stable version from PyPI:
59
59
  pip install amplify-excel-migrator
60
60
  ```
61
61
 
62
- ### From GitHub
63
-
64
- Install directly from GitHub:
65
-
66
- ```bash
67
- pip install git+https://github.com/EyalPoly/amplify-excel-migrator.git
68
- ```
69
-
70
62
  ### From Source
71
63
 
72
64
  Clone the repository and install:
@@ -77,16 +69,6 @@ cd amplify-excel-migrator
77
69
  pip install .
78
70
  ```
79
71
 
80
- ### For Development
81
-
82
- Install with development dependencies:
83
-
84
- ```bash
85
- pip install -e ".[dev]"
86
- ```
87
-
88
- This installs the package in editable mode with pytest and other development tools.
89
-
90
72
  ## Usage
91
73
 
92
74
  The tool has three subcommands:
@@ -107,7 +89,7 @@ This will prompt you for:
107
89
  - Cognito Client ID
108
90
  - Admin username
109
91
 
110
- Configuration is saved to `~/.amplify-migrator/config.json` (passwords are never saved).
92
+ Configuration is saved to `~/.amplify-migrator/config.json`
111
93
 
112
94
  ### 2. Show Configuration
113
95
 
@@ -186,13 +168,26 @@ Admin Password: ********
186
168
 
187
169
  ## Features
188
170
 
171
+ ### Data Processing & Conversion
172
+ - **Automatic type parsing** - Smart field type detection for all GraphQL types including scalars, enums, and custom types
173
+ - **Custom types and enums** - Full support for Amplify custom types with automatic conversion
174
+ - **Duplicate detection** - Automatically skips existing records to prevent duplicates
175
+ - **Foreign key resolution** - Automatic relationship handling with pre-fetching for performance
176
+
177
+ ### AWS Integration
189
178
  - **Configuration caching** - Save your setup, reuse it for multiple migrations
190
- - **Interactive prompts** - Easy step-by-step configuration
191
- - **Custom types and enums** - Full support for Amplify custom types
192
- - **Duplicate detection** - Automatically skips existing records
193
- - **Async uploads** - Fast parallel uploads for better performance
194
179
  - **MFA support** - Works with multi-factor authentication
195
- - **Automatic type parsing** - Smart field type detection and conversion
180
+ - **Admin group validation** - Ensures proper authorization before migration
181
+
182
+ ### Performance
183
+ - **Async uploads** - Fast parallel uploads with configurable batch size
184
+ - **Connection pooling** - Efficient HTTP connection reuse for better performance
185
+ - **Pagination support** - Handles large datasets efficiently
186
+
187
+ ### User Experience
188
+ - **Interactive prompts** - Easy step-by-step configuration
189
+ - **Progress reporting** - Real-time feedback on migration status
190
+ - **Detailed error messages** - Clear context for troubleshooting failures
196
191
 
197
192
  ## Excel File Format
198
193
 
@@ -201,19 +196,119 @@ The Excel file should have:
201
196
  - Column names matching the model field names
202
197
  - First row as headers
203
198
 
204
- ### Example Excel Structure
199
+ ### Basic Structure
205
200
 
206
201
  **Sheet: User**
202
+
207
203
  | name | email | age |
208
204
  |------|-------|-----|
209
205
  | John | john@example.com | 30 |
210
206
  | Jane | jane@example.com | 25 |
211
207
 
212
208
  **Sheet: Post**
209
+
213
210
  | title | content | userId |
214
211
  |-------|---------|--------|
215
212
  | First Post | Hello World | john@example.com |
216
213
 
214
+ ### Relationships (Foreign Keys)
215
+
216
+ To reference related records, use the primary key value of the related model:
217
+
218
+ **Sheet: Comment**
219
+
220
+ | content | postId | userId |
221
+ |--------------|----------|------------------|
222
+ | Great post! | post-123 | john@example.com |
223
+
224
+ The tool automatically resolves foreign keys by looking up the related records.
225
+
226
+ ### Array/List Fields
227
+
228
+ Array fields support multiple input formats:
229
+
230
+ - **JSON format:** `["tag1", "tag2", "tag3"]`
231
+ - **Semicolon-separated:** `tag1; tag2; tag3`
232
+ - **Comma-separated:** `tag1, tag2, tag3`
233
+
234
+ ## Advanced Features
235
+
236
+ - **Foreign Key Resolution** - Automatically resolves relationships between models with pre-fetching for optimal performance
237
+ - **Schema Introspection** - Dynamically queries your GraphQL schema to understand model structures and field types
238
+ - **Configurable Batch Processing** - Tune upload performance with adjustable batch sizes (default: 20 records per batch)
239
+ - **Progress Reporting** - Real-time batch progress with per-sheet confirmation prompts before upload
240
+
241
+ ## Error Handling & Recovery
242
+
243
+ When records fail to upload, the tool provides a robust recovery mechanism to help you identify and fix issues without starting over.
244
+
245
+ ### How It Works
246
+
247
+ 1. **Automatic Error Capture** - Each failed record is logged with detailed error messages explaining what went wrong
248
+ 2. **Failed Records Export** - After migration completes, you'll be prompted to export failed records to a new Excel file with a timestamp (e.g., `data_failed_records_20251201_143022.xlsx`)
249
+ 3. **Easy Retry** - Fix the issues in the exported file and run the migration again using only the failed records
250
+ 4. **Progress Visibility** - Detailed summary shows success/failure counts, percentages, and specific error reasons for each failed record
251
+
252
+ ### Recovery Workflow Example
253
+
254
+ ```bash
255
+ # Run initial migration
256
+ amplify-migrator migrate
257
+
258
+ # Migration completes with some failures:
259
+ # ✅ Successfully uploaded: 95 records (95%)
260
+ # ❌ Failed to upload: 5 records (5%)
261
+ #
262
+ # Export failed records? (y/n): y
263
+ # Failed records exported to: data_failed_records_20251201_143022.xlsx
264
+
265
+ # Fix the errors in the exported Excel file
266
+ # Then re-run migration with the failed records file
267
+
268
+ amplify-migrator migrate
269
+ # Excel file path: data_failed_records_20251201_143022.xlsx
270
+ ```
271
+
272
+ The tool tracks which records succeeded and failed, providing row-level context to help you quickly identify and resolve issues.
273
+
274
+ ## Troubleshooting
275
+
276
+ ### Authentication Errors
277
+
278
+ **Error: Unable to authenticate with Cognito**
279
+ - Verify your Cognito User Pool ID and Client ID are correct
280
+ - Ensure your username and password are valid
281
+ - Check that your user is in the ADMINS group
282
+
283
+ ### MFA Issues
284
+
285
+ **Error: MFA required but not configured**
286
+ - Enable MFA in your Cognito User Pool settings
287
+ - Ensure your user has MFA set up (SMS or software token)
288
+
289
+ ### AWS Credentials
290
+
291
+ **Error: AWS credentials not found**
292
+ - Set up AWS credentials in `~/.aws/credentials`
293
+ - Or set environment variables: `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_DEFAULT_REGION`
294
+ - Or use `aws configure` to set up your default profile
295
+
296
+ ### Excel File Format
297
+
298
+ **Error: Sheet name does not match any model**
299
+ - Ensure sheet names exactly match your Amplify GraphQL model names (case-sensitive)
300
+ - Check for extra spaces or special characters in sheet names
301
+
302
+ **Error: Required field missing**
303
+ - Verify all required fields in your GraphQL schema have corresponding columns in Excel
304
+ - Check column names match field names (case-sensitive)
305
+
306
+ ### Permission Errors
307
+
308
+ **Error: User is not in ADMINS group**
309
+ - Add your user to the ADMINS group in Cognito User Pool
310
+ - Contact your AWS administrator if you don't have permission
311
+
217
312
  ## License
218
313
 
219
314
  MIT
@@ -0,0 +1,40 @@
1
+ amplify_excel_migrator/__init__.py,sha256=g1LICPlL6J3KYfJuOMEtu0SLXqD_2n3N59_hx18aARQ,525
2
+ amplify_excel_migrator/client.py,sha256=i-FV9uMrPcRG_rKNk5_i4Xu_D-tz2YFRxrM4SYF2-A0,1953
3
+ amplify_excel_migrator/auth/__init__.py,sha256=_vvKeC-szE__R4ctSvqxsabdUU8AcFBmQc1EtETPGUI,209
4
+ amplify_excel_migrator/auth/cognito_auth.py,sha256=t8gNg4oaTZgJaJgV3xH2HXpjpp_lu87dI-mBY5Ni1lw,11855
5
+ amplify_excel_migrator/auth/provider.py,sha256=GaQ2cSuFDN4RDa86YxMNRhBRxMbmhOoWgflDq8igS0Q,1046
6
+ amplify_excel_migrator/cli/__init__.py,sha256=kQ7VrL50-TdB4lrB70gPgJXX7YxBRDGAGmyGkr0yKc8,169
7
+ amplify_excel_migrator/cli/commands.py,sha256=zrT6aaDx_M1ByCY6Fboyl_dcXYPVMKZx53-EwPEwZN0,6708
8
+ amplify_excel_migrator/core/__init__.py,sha256=anEP7jSxUQzXlrgclsbGSoj2GXlz2PM_n2hlp2T90ng,115
9
+ amplify_excel_migrator/core/config.py,sha256=0wENxoUfiEiCm5WpnJX_Q4tE6IuU03VnWcQTbc2Nmbw,3143
10
+ amplify_excel_migrator/data/__init__.py,sha256=rDJxPoHEoWepJFCEc2Wd5TG8Tp1XSMaUqWGbxuu3JmU,238
11
+ amplify_excel_migrator/data/excel_reader.py,sha256=wXh1lNpC53mhq-UQIDJbRq990H6onXuuwrbpnW2tKJI,736
12
+ amplify_excel_migrator/data/transformer.py,sha256=rHpJQvftj_XULMnSYwngmOup8sOxApyHWL5QxiF709E,4484
13
+ amplify_excel_migrator/data/validator.py,sha256=iZYGHLVMtSyjBHQIzRkBTu5nH9CpfdmL4KCH0kJFRRM,1499
14
+ amplify_excel_migrator/graphql/__init__.py,sha256=PGj3f5VGUGiPdKCJu_SqAhnTN2PxbE6gyFqeAhlhYWk,366
15
+ amplify_excel_migrator/graphql/client.py,sha256=56DPQU8atjI3c62VYxbErAF4aTiRMlhDzUPPE4Pn_nU,4882
16
+ amplify_excel_migrator/graphql/executor.py,sha256=e904NBB5XIB2PKRZ3plCt-t80LSEXYQ9EtDMstwFZnA,16018
17
+ amplify_excel_migrator/graphql/mutation_builder.py,sha256=e33tX7gGjMdtOrt5MLXXfAzD16atmrbfOgVSjAa9KlM,2089
18
+ amplify_excel_migrator/graphql/query_builder.py,sha256=etV8ku1NaFG8Akk1oWLEpwUKaXVt5FaVnvDiw_QXPAY,4679
19
+ amplify_excel_migrator/migration/__init__.py,sha256=k85cEbTI96WC0RD5sLj6nRWWE3VU9iHfAu4BWsDNJcI,312
20
+ amplify_excel_migrator/migration/batch_uploader.py,sha256=Z_cp03VyZk0fxbm4e8tns3aLi_x-bbPak9N9_GMI7vk,684
21
+ amplify_excel_migrator/migration/failure_tracker.py,sha256=92BB2o2uJ1J5nKctctXbj5SYXZeHWAF4SBhzyG3gmUg,3249
22
+ amplify_excel_migrator/migration/orchestrator.py,sha256=pAllQZa5RzlKbSYQ6dBrOsBUc_WMgYavHiZ2qd-MPfc,5910
23
+ amplify_excel_migrator/migration/progress_reporter.py,sha256=91YS_Qfys-0ALoeBoWA5v7SwvWOk3DLrsVwyTnz0vus,2156
24
+ amplify_excel_migrator/schema/__init__.py,sha256=U12KKPjFvDhlXUXwc4JD0T4TAPjdLvBVfbwn-xGit-4,179
25
+ amplify_excel_migrator/schema/field_parser.py,sha256=UqJVnllZmdUjPh3hCnhMfY1GYUBSjdMaRQpCttR-mb4,14840
26
+ amplify_excel_migrator/schema/introspector.py,sha256=L6ZY-Uk4zo2Ixh9O9Sx-s5DM9TJkgvxE2OPSS_p01qc,3612
27
+ amplify_excel_migrator-1.2.15.dist-info/licenses/LICENSE,sha256=i8Sf8mXscGI9l-HTQ5RLQkAJU6Iv5hPYctJksPY70U0,1071
28
+ tests/__init__.py,sha256=-j_kF6eQuLjtULc_VPYxuIY0bqpDCVfDikK8RS1YIQY,39
29
+ tests/test_cli_commands.py,sha256=-QNkQqF2s5VB1H4wQ4H3HWWX1DHDf4dwLY2Sw4gxnOA,12010
30
+ tests/test_client.py,sha256=Wso4zn_TcoyjEIZKAopqTO3yPOsKP9JpJmI2UpUz0zU,7966
31
+ tests/test_cognito_auth.py,sha256=sRptE9GGiLW2ur4sm6Zs4FIH8DH61xwap9p4D5GffI0,16302
32
+ tests/test_config_manager.py,sha256=Wc5D3tx8fBwVvGPg0Lkzjjgv6aYlXxYGHjPILpufPLU,14454
33
+ tests/test_field_parser.py,sha256=jbpaWxrgZv-K-QUq7rPg9cCpTpbRjfcDsPfcOLQL4uI,23861
34
+ tests/test_mutation_builder.py,sha256=yvlRW7Cimzv0pNCyfnEgre4NYWEW2DyfUnfyQHWneYE,16485
35
+ tests/test_query_builder.py,sha256=Br3sJELVpC3v1LMWvkNdLWk0BMmTSwZiGTJzOKvJy_M,15332
36
+ amplify_excel_migrator-1.2.15.dist-info/METADATA,sha256=V9r1vipoT8LC3vfrOFmZ8E2imq6KI8dI7XxMf4TCnWA,10236
37
+ amplify_excel_migrator-1.2.15.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
38
+ amplify_excel_migrator-1.2.15.dist-info/entry_points.txt,sha256=Ru4V8BBdNJLIksS5taxjtu5bAnXt1EKpjmpGxYcOA2E,78
39
+ amplify_excel_migrator-1.2.15.dist-info/top_level.txt,sha256=zJf_3yBinR9ufYWKB3UzqJXfoXIToIwxmEcJAVzMufI,29
40
+ amplify_excel_migrator-1.2.15.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ amplify-migrator = amplify_excel_migrator.cli.commands:main
@@ -0,0 +1,2 @@
1
+ amplify_excel_migrator
2
+ tests
tests/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """Tests for amplify-excel-migrator"""
@@ -0,0 +1,292 @@
1
+ """Tests for CLI commands"""
2
+
3
+ import json
4
+ import sys
5
+ import pytest
6
+ from pathlib import Path
7
+ from unittest.mock import patch, MagicMock
8
+ from amplify_excel_migrator.cli.commands import (
9
+ cmd_show,
10
+ cmd_config,
11
+ cmd_migrate,
12
+ )
13
+ from amplify_excel_migrator.core import ConfigManager
14
+
15
+
16
+ @pytest.fixture
17
+ def sample_config():
18
+ """Sample configuration for testing"""
19
+ return {
20
+ "excel_path": "test_data.xlsx",
21
+ "api_endpoint": "https://test.appsync-api.us-east-1.amazonaws.com/graphql",
22
+ "region": "us-east-1",
23
+ "user_pool_id": "us-east-1_testpool",
24
+ "client_id": "test-client-id",
25
+ "username": "test@example.com",
26
+ }
27
+
28
+
29
+ @pytest.fixture
30
+ def mock_config_manager(tmp_path):
31
+ """Create a mock ConfigManager for testing"""
32
+ test_config_file = tmp_path / "config.json"
33
+
34
+ def init_mock(self, config_path=None):
35
+ self.config_path = test_config_file
36
+ self._config = {}
37
+
38
+ return init_mock
39
+
40
+
41
+ class TestCmdShow:
42
+ """Test 'show' command"""
43
+
44
+ def test_show_with_no_config(self, capsys, mock_config_manager):
45
+ """Test show command with no config file"""
46
+ with patch.object(ConfigManager, "__init__", mock_config_manager):
47
+ cmd_show()
48
+
49
+ captured = capsys.readouterr()
50
+ assert "❌ No configuration found!" in captured.out
51
+ assert "amplify-migrator config" in captured.out
52
+
53
+ def test_show_with_existing_config(self, capsys, tmp_path, sample_config):
54
+ """Test show command with existing config"""
55
+ test_config_file = tmp_path / "config.json"
56
+ test_config_file.parent.mkdir(parents=True, exist_ok=True)
57
+
58
+ with open(test_config_file, "w") as f:
59
+ json.dump(sample_config, f)
60
+
61
+ def init_mock(self, config_path=None):
62
+ self.config_path = test_config_file
63
+ self._config = {}
64
+
65
+ with patch.object(ConfigManager, "__init__", init_mock):
66
+ cmd_show()
67
+
68
+ captured = capsys.readouterr()
69
+ assert "test_data.xlsx" in captured.out
70
+ assert "test@example.com" in captured.out
71
+ assert "us-east-1" in captured.out
72
+ assert "test-client-id" in captured.out
73
+
74
+ def test_show_displays_config_location(self, capsys, tmp_path, sample_config):
75
+ """Test that show command displays config file location"""
76
+ test_config_file = tmp_path / "config.json"
77
+ test_config_file.parent.mkdir(parents=True, exist_ok=True)
78
+
79
+ with open(test_config_file, "w") as f:
80
+ json.dump(sample_config, f)
81
+
82
+ def init_mock(self, config_path=None):
83
+ self.config_path = test_config_file
84
+ self._config = {}
85
+
86
+ with patch.object(ConfigManager, "__init__", init_mock):
87
+ cmd_show()
88
+
89
+ captured = capsys.readouterr()
90
+ assert str(test_config_file) in captured.out
91
+
92
+
93
+ class TestCmdConfig:
94
+ """Test 'config' command"""
95
+
96
+ def test_config_prompts_for_all_values(self, tmp_path):
97
+ """Test that config command prompts for all required values"""
98
+ test_config_file = tmp_path / "config.json"
99
+
100
+ def init_mock(self, config_path=None):
101
+ self.config_path = test_config_file
102
+ self._config = {}
103
+
104
+ # Mock all input prompts
105
+ inputs = [
106
+ "test.xlsx",
107
+ "https://test.appsync-api.us-east-1.amazonaws.com/graphql",
108
+ "us-east-1",
109
+ "us-east-1_test",
110
+ "test-client",
111
+ "admin@test.com",
112
+ ]
113
+
114
+ with patch.object(ConfigManager, "__init__", init_mock):
115
+ with patch("builtins.input", side_effect=inputs):
116
+ cmd_config()
117
+
118
+ # Verify config was saved
119
+ assert test_config_file.exists()
120
+ with open(test_config_file) as f:
121
+ saved_config = json.load(f)
122
+ assert saved_config["excel_path"] == "test.xlsx"
123
+ assert saved_config["username"] == "admin@test.com"
124
+
125
+ def test_config_saves_to_correct_location(self, capsys, tmp_path):
126
+ """Test that config is saved to the correct location"""
127
+ test_config_file = tmp_path / "config.json"
128
+
129
+ def init_mock(self, config_path=None):
130
+ self.config_path = test_config_file
131
+ self._config = {}
132
+
133
+ inputs = ["test.xlsx", "https://test.com", "us-east-1", "pool", "client", "user"]
134
+
135
+ with patch.object(ConfigManager, "__init__", init_mock):
136
+ with patch("builtins.input", side_effect=inputs):
137
+ cmd_config()
138
+
139
+ captured = capsys.readouterr()
140
+ assert "✅ Configuration saved successfully!" in captured.out
141
+ assert "amplify-migrator migrate" in captured.out
142
+
143
+ def test_config_uses_cached_values_as_defaults(self, tmp_path, sample_config):
144
+ """Test that config command shows cached values as defaults"""
145
+ test_config_file = tmp_path / "config.json"
146
+ test_config_file.parent.mkdir(parents=True, exist_ok=True)
147
+
148
+ with open(test_config_file, "w") as f:
149
+ json.dump(sample_config, f)
150
+
151
+ def init_mock(self, config_path=None):
152
+ self.config_path = test_config_file
153
+ self._config = {}
154
+
155
+ # Press enter to accept all defaults (from cached config)
156
+ inputs = ["", "", "", "", "", ""] # Empty strings use cached values
157
+
158
+ with patch.object(ConfigManager, "__init__", init_mock):
159
+ with patch("builtins.input", side_effect=inputs):
160
+ cmd_config()
161
+
162
+ with open(test_config_file) as f:
163
+ saved_config = json.load(f)
164
+ assert saved_config["excel_path"] == "test_data.xlsx"
165
+ assert saved_config["region"] == "us-east-1"
166
+
167
+
168
+ class TestCmdMigrate:
169
+ """Test 'migrate' command"""
170
+
171
+ def test_migrate_fails_without_config(self, capsys, tmp_path):
172
+ """Test that migrate command fails when no config exists"""
173
+ test_config_file = tmp_path / "config.json"
174
+
175
+ def init_mock(self, config_path=None):
176
+ self.config_path = test_config_file
177
+ self._config = {}
178
+
179
+ with patch.object(ConfigManager, "__init__", init_mock):
180
+ with pytest.raises(SystemExit) as exc_info:
181
+ cmd_migrate()
182
+
183
+ assert exc_info.value.code == 1
184
+
185
+ captured = capsys.readouterr()
186
+ assert "❌ No configuration found!" in captured.out
187
+ assert "amplify-migrator config" in captured.out
188
+
189
+ def test_migrate_uses_cached_config(self, tmp_path, sample_config):
190
+ """Test that migrate command uses cached configuration"""
191
+ test_config_file = tmp_path / "config.json"
192
+ test_config_file.parent.mkdir(parents=True, exist_ok=True)
193
+
194
+ with open(test_config_file, "w") as f:
195
+ json.dump(sample_config, f)
196
+
197
+ def init_mock(self, config_path=None):
198
+ self.config_path = test_config_file
199
+ self._config = {}
200
+
201
+ # Mock the entire migration process
202
+ with patch.object(ConfigManager, "__init__", init_mock):
203
+ with patch("amplify_excel_migrator.auth.CognitoAuthProvider") as mock_auth_provider_class:
204
+ with patch("amplify_excel_migrator.cli.commands.ExcelReader") as mock_excel_reader_class:
205
+ with patch("amplify_excel_migrator.cli.commands.MigrationOrchestrator") as mock_orchestrator_class:
206
+ with patch("amplify_excel_migrator.core.config.getpass", return_value="password123"):
207
+ mock_auth_instance = MagicMock()
208
+ mock_auth_instance.authenticate.return_value = True
209
+ mock_auth_provider_class.return_value = mock_auth_instance
210
+
211
+ mock_excel_reader_instance = MagicMock()
212
+ mock_excel_reader_class.return_value = mock_excel_reader_instance
213
+
214
+ mock_orchestrator_instance = MagicMock()
215
+ mock_orchestrator_class.return_value = mock_orchestrator_instance
216
+
217
+ cmd_migrate()
218
+
219
+ # Verify auth provider was initialized with cached values
220
+ mock_auth_provider_class.assert_called_once()
221
+ call_args = mock_auth_provider_class.call_args
222
+ assert call_args[1]["user_pool_id"] == "us-east-1_testpool"
223
+ assert call_args[1]["client_id"] == "test-client-id"
224
+ assert call_args[1]["region"] == "us-east-1"
225
+
226
+ # Verify authenticate was called
227
+ mock_auth_instance.authenticate.assert_called_once_with("test@example.com", "password123")
228
+
229
+ # Verify orchestrator was called
230
+ mock_orchestrator_instance.run.assert_called_once()
231
+
232
+ def test_migrate_prompts_for_password(self, tmp_path, sample_config):
233
+ """Test that migrate command always prompts for password"""
234
+ test_config_file = tmp_path / "config.json"
235
+ test_config_file.parent.mkdir(parents=True, exist_ok=True)
236
+
237
+ with open(test_config_file, "w") as f:
238
+ json.dump(sample_config, f)
239
+
240
+ def init_mock(self, config_path=None):
241
+ self.config_path = test_config_file
242
+ self._config = {}
243
+
244
+ with patch.object(ConfigManager, "__init__", init_mock):
245
+ with patch("amplify_excel_migrator.auth.CognitoAuthProvider") as mock_auth_provider_class:
246
+ with patch("amplify_excel_migrator.cli.commands.ExcelReader") as mock_excel_reader_class:
247
+ with patch("amplify_excel_migrator.cli.commands.MigrationOrchestrator") as mock_orchestrator_class:
248
+ with patch(
249
+ "amplify_excel_migrator.core.config.getpass", return_value="secret_password"
250
+ ) as mock_getpass:
251
+ mock_auth_instance = MagicMock()
252
+ mock_auth_instance.authenticate.return_value = True
253
+ mock_auth_provider_class.return_value = mock_auth_instance
254
+
255
+ mock_excel_reader_instance = MagicMock()
256
+ mock_excel_reader_class.return_value = mock_excel_reader_instance
257
+
258
+ mock_orchestrator_instance = MagicMock()
259
+ mock_orchestrator_class.return_value = mock_orchestrator_instance
260
+
261
+ cmd_migrate()
262
+
263
+ # Verify getpass was called (for password prompt)
264
+ mock_getpass.assert_called()
265
+
266
+ def test_migrate_stops_if_authentication_fails(self, tmp_path, sample_config):
267
+ """Test that migrate stops if authentication fails"""
268
+ test_config_file = tmp_path / "config.json"
269
+ test_config_file.parent.mkdir(parents=True, exist_ok=True)
270
+
271
+ with open(test_config_file, "w") as f:
272
+ json.dump(sample_config, f)
273
+
274
+ def init_mock(self, config_path=None):
275
+ self.config_path = test_config_file
276
+ self._config = {}
277
+
278
+ with patch.object(ConfigManager, "__init__", init_mock):
279
+ with patch("amplify_excel_migrator.auth.CognitoAuthProvider") as mock_auth_provider_class:
280
+ with patch("amplify_excel_migrator.cli.commands.MigrationOrchestrator") as mock_orchestrator_class:
281
+ with patch("amplify_excel_migrator.core.config.getpass", return_value="wrong_password"):
282
+ mock_auth_instance = MagicMock()
283
+ mock_auth_instance.authenticate.return_value = False # Authentication fails
284
+ mock_auth_provider_class.return_value = mock_auth_instance
285
+
286
+ mock_orchestrator_instance = MagicMock()
287
+ mock_orchestrator_class.return_value = mock_orchestrator_instance
288
+
289
+ cmd_migrate()
290
+
291
+ # Verify run() was NOT called
292
+ mock_orchestrator_instance.run.assert_not_called()