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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: amplify-excel-migrator
|
|
3
|
-
Version: 1.
|
|
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`
|
|
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
|
-
- **
|
|
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
|
-
###
|
|
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,,
|
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()
|