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
@@ -0,0 +1,384 @@
1
+ """Tests for QueryBuilder class"""
2
+
3
+ import pytest
4
+ from amplify_excel_migrator.graphql import QueryBuilder
5
+
6
+
7
+ class TestBuildListQuery:
8
+ """Test QueryBuilder.build_list_query() method"""
9
+
10
+ def test_basic_list_query_with_pagination(self):
11
+ """Test basic list query with pagination enabled"""
12
+ query = QueryBuilder.build_list_query("User", fields=["id", "name", "email"])
13
+
14
+ assert "query ListUsers" in query
15
+ assert "$limit: Int" in query
16
+ assert "$nextToken: String" in query
17
+ assert "listUsers(limit: $limit, nextToken: $nextToken)" in query
18
+ assert "items {" in query
19
+ assert "id" in query
20
+ assert "name" in query
21
+ assert "email" in query
22
+ assert "nextToken" in query
23
+
24
+ def test_list_query_without_pagination(self):
25
+ """Test list query without pagination"""
26
+ query = QueryBuilder.build_list_query("Product", fields=["id", "title"], with_pagination=False)
27
+
28
+ assert "query ListProducts" in query
29
+ assert "$limit: Int" in query
30
+ assert "$nextToken: String" not in query
31
+ assert "listProducts(limit: $limit)" in query
32
+ assert "nextToken" not in query # Should not appear in response
33
+ assert "id" in query
34
+ assert "title" in query
35
+
36
+ def test_list_query_default_fields(self):
37
+ """Test list query with default fields (only id)"""
38
+ query = QueryBuilder.build_list_query("Post")
39
+
40
+ assert "query ListPosts" in query
41
+ assert "id" in query
42
+ assert "listPosts" in query
43
+
44
+ def test_list_query_multiple_fields(self):
45
+ """Test list query with multiple fields"""
46
+ fields = ["id", "title", "content", "authorID", "createdAt"]
47
+ query = QueryBuilder.build_list_query("Article", fields=fields)
48
+
49
+ for field in fields:
50
+ assert field in query
51
+
52
+
53
+ class TestBuildListQueryWithFilter:
54
+ """Test QueryBuilder.build_list_query_with_filter() method"""
55
+
56
+ def test_filtered_query_with_pagination(self):
57
+ """Test filtered list query with pagination"""
58
+ query = QueryBuilder.build_list_query_with_filter("User", fields=["id", "name"])
59
+
60
+ assert "query ListUsers" in query
61
+ assert "$filter: ModelUserFilterInput" in query
62
+ assert "$limit: Int" in query
63
+ assert "$nextToken: String" in query
64
+ assert "listUsers(filter: $filter, limit: $limit, nextToken: $nextToken)" in query
65
+ assert "items {" in query
66
+ assert "nextToken" in query
67
+
68
+ def test_filtered_query_without_pagination(self):
69
+ """Test filtered list query without pagination"""
70
+ query = QueryBuilder.build_list_query_with_filter("Product", fields=["id"], with_pagination=False)
71
+
72
+ assert "query ListProducts" in query
73
+ assert "$filter: ModelProductFilterInput" in query
74
+ assert "$limit: Int" in query
75
+ assert "$nextToken: String" not in query
76
+ assert "listProducts(filter: $filter, limit: $limit)" in query
77
+ assert "nextToken" not in query
78
+
79
+ def test_filtered_query_default_fields(self):
80
+ """Test filtered query with default fields"""
81
+ query = QueryBuilder.build_list_query_with_filter("Comment")
82
+
83
+ assert "query ListComments" in query
84
+ assert "$filter: ModelCommentFilterInput" in query
85
+ assert "id" in query
86
+
87
+
88
+ class TestBuildSecondaryIndexQuery:
89
+ """Test QueryBuilder.build_secondary_index_query() method"""
90
+
91
+ def test_secondary_index_query_with_pagination(self):
92
+ """Test secondary index query with pagination"""
93
+ query = QueryBuilder.build_secondary_index_query(
94
+ "User", "email", fields=["id", "email", "name"], field_type="String"
95
+ )
96
+
97
+ assert "query listUserByEmail" in query
98
+ assert "$email: String!" in query
99
+ assert "$limit: Int" in query
100
+ assert "$nextToken: String" in query
101
+ assert "listUserByEmail(email: $email, limit: $limit, nextToken: $nextToken)" in query
102
+ assert "id" in query
103
+ assert "email" in query
104
+ assert "name" in query
105
+ assert "nextToken" in query
106
+
107
+ def test_secondary_index_query_without_pagination(self):
108
+ """Test secondary index query without pagination"""
109
+ query = QueryBuilder.build_secondary_index_query(
110
+ "Product", "sku", fields=["id", "sku"], field_type="String", with_pagination=False
111
+ )
112
+
113
+ assert "query listProductBySku" in query
114
+ assert "$sku: String!" in query
115
+ assert "$limit: Int" not in query
116
+ assert "$nextToken: String" not in query
117
+ assert "listProductBySku(sku: $sku)" in query
118
+ assert "nextToken" not in query
119
+
120
+ def test_secondary_index_query_different_field_types(self):
121
+ """Test secondary index query with different field types"""
122
+ # String type
123
+ query_string = QueryBuilder.build_secondary_index_query("Order", "customerId", field_type="String")
124
+ assert "$customerId: String!" in query_string
125
+
126
+ # Int type
127
+ query_int = QueryBuilder.build_secondary_index_query("Order", "orderNumber", field_type="Int")
128
+ assert "$orderNumber: Int!" in query_int
129
+
130
+ # ID type
131
+ query_id = QueryBuilder.build_secondary_index_query("Comment", "postID", field_type="ID")
132
+ assert "$postID: ID!" in query_id
133
+
134
+ def test_secondary_index_query_capitalization(self):
135
+ """Test that secondary index query name is properly capitalized"""
136
+ query = QueryBuilder.build_secondary_index_query("User", "username", field_type="String")
137
+
138
+ # Should capitalize first letter of field name
139
+ assert "listUserByUsername" in query
140
+
141
+ def test_secondary_index_query_default_fields(self):
142
+ """Test secondary index query with default fields"""
143
+ query = QueryBuilder.build_secondary_index_query("Product", "category", field_type="String")
144
+
145
+ # Default should include id and the index field
146
+ assert "id" in query
147
+ assert "category" in query
148
+
149
+
150
+ class TestBuildGetByIdQuery:
151
+ """Test QueryBuilder.build_get_by_id_query() method"""
152
+
153
+ def test_get_by_id_basic(self):
154
+ """Test basic get by ID query"""
155
+ query = QueryBuilder.build_get_by_id_query("User", fields=["id", "name", "email"])
156
+
157
+ assert "query GetUser" in query
158
+ assert "$id: ID!" in query
159
+ assert "getUser(id: $id)" in query
160
+ assert "id" in query
161
+ assert "name" in query
162
+ assert "email" in query
163
+
164
+ def test_get_by_id_default_fields(self):
165
+ """Test get by ID with default fields (only id)"""
166
+ query = QueryBuilder.build_get_by_id_query("Product")
167
+
168
+ assert "query GetProduct" in query
169
+ assert "$id: ID!" in query
170
+ assert "getProduct(id: $id)" in query
171
+ assert "id" in query
172
+
173
+ def test_get_by_id_single_field(self):
174
+ """Test get by ID with single field"""
175
+ query = QueryBuilder.build_get_by_id_query("Post", fields=["title"])
176
+
177
+ assert "query GetPost" in query
178
+ assert "title" in query
179
+
180
+ def test_get_by_id_many_fields(self):
181
+ """Test get by ID with many fields"""
182
+ fields = ["id", "title", "content", "authorID", "createdAt", "updatedAt", "published"]
183
+ query = QueryBuilder.build_get_by_id_query("Article", fields=fields)
184
+
185
+ for field in fields:
186
+ assert field in query
187
+
188
+
189
+ class TestBuildIntrospectionQuery:
190
+ """Test QueryBuilder.build_introspection_query() method"""
191
+
192
+ def test_introspection_query_structure(self):
193
+ """Test introspection query has correct structure"""
194
+ query = QueryBuilder.build_introspection_query("User")
195
+
196
+ assert "query IntrospectModel" in query
197
+ assert '__type(name: "User")' in query
198
+ assert "name" in query
199
+ assert "fields {" in query
200
+ assert "type {" in query
201
+ assert "kind" in query
202
+ assert "ofType {" in query
203
+
204
+ def test_introspection_query_different_models(self):
205
+ """Test introspection query with different model names"""
206
+ models = ["User", "Product", "Order", "Comment"]
207
+
208
+ for model in models:
209
+ query = QueryBuilder.build_introspection_query(model)
210
+ assert f'__type(name: "{model}")' in query
211
+ assert "query IntrospectModel" in query
212
+
213
+
214
+ class TestBuildVariablesForList:
215
+ """Test QueryBuilder.build_variables_for_list() method"""
216
+
217
+ def test_variables_with_limit_only(self):
218
+ """Test variables with only limit"""
219
+ variables = QueryBuilder.build_variables_for_list(limit=100)
220
+
221
+ assert variables == {"limit": 100}
222
+
223
+ def test_variables_with_limit_and_next_token(self):
224
+ """Test variables with limit and next token"""
225
+ variables = QueryBuilder.build_variables_for_list(limit=50, next_token="abc123")
226
+
227
+ assert variables == {"limit": 50, "nextToken": "abc123"}
228
+
229
+ def test_variables_default_limit(self):
230
+ """Test variables with default limit"""
231
+ variables = QueryBuilder.build_variables_for_list()
232
+
233
+ assert variables == {"limit": 1000}
234
+
235
+ def test_variables_next_token_none(self):
236
+ """Test that None next_token is not included"""
237
+ variables = QueryBuilder.build_variables_for_list(limit=100, next_token=None)
238
+
239
+ assert "nextToken" not in variables
240
+ assert variables == {"limit": 100}
241
+
242
+
243
+ class TestBuildVariablesForFilter:
244
+ """Test QueryBuilder.build_variables_for_filter() method"""
245
+
246
+ def test_variables_with_simple_filter(self):
247
+ """Test variables with simple filter"""
248
+ filter_dict = {"name": {"eq": "John"}}
249
+ variables = QueryBuilder.build_variables_for_filter(filter_dict, limit=100)
250
+
251
+ assert variables == {"filter": {"name": {"eq": "John"}}, "limit": 100}
252
+
253
+ def test_variables_with_filter_and_next_token(self):
254
+ """Test variables with filter and next token"""
255
+ filter_dict = {"status": {"eq": "active"}}
256
+ variables = QueryBuilder.build_variables_for_filter(filter_dict, limit=50, next_token="xyz789")
257
+
258
+ assert variables == {"filter": {"status": {"eq": "active"}}, "limit": 50, "nextToken": "xyz789"}
259
+
260
+ def test_variables_with_complex_filter(self):
261
+ """Test variables with complex filter"""
262
+ filter_dict = {"and": [{"age": {"gt": 18}}, {"status": {"eq": "active"}}]}
263
+ variables = QueryBuilder.build_variables_for_filter(filter_dict)
264
+
265
+ assert variables["filter"] == filter_dict
266
+ assert variables["limit"] == 1000
267
+
268
+ def test_variables_filter_none_next_token(self):
269
+ """Test that None next_token is not included"""
270
+ filter_dict = {"email": {"contains": "@example.com"}}
271
+ variables = QueryBuilder.build_variables_for_filter(filter_dict, next_token=None)
272
+
273
+ assert "nextToken" not in variables
274
+ assert variables == {"filter": filter_dict, "limit": 1000}
275
+
276
+
277
+ class TestBuildVariablesForSecondaryIndex:
278
+ """Test QueryBuilder.build_variables_for_secondary_index() method"""
279
+
280
+ def test_variables_with_index_value(self):
281
+ """Test variables with index field and value"""
282
+ variables = QueryBuilder.build_variables_for_secondary_index("email", "test@example.com", limit=100)
283
+
284
+ assert variables == {"email": "test@example.com", "limit": 100}
285
+
286
+ def test_variables_with_index_and_next_token(self):
287
+ """Test variables with index field, value, and next token"""
288
+ variables = QueryBuilder.build_variables_for_secondary_index("sku", "PROD-123", limit=50, next_token="token456")
289
+
290
+ assert variables == {"sku": "PROD-123", "limit": 50, "nextToken": "token456"}
291
+
292
+ def test_variables_default_limit(self):
293
+ """Test variables with default limit"""
294
+ variables = QueryBuilder.build_variables_for_secondary_index("username", "john_doe")
295
+
296
+ assert variables == {"username": "john_doe", "limit": 1000}
297
+
298
+ def test_variables_different_value_types(self):
299
+ """Test variables with different value types"""
300
+ # String value
301
+ vars_string = QueryBuilder.build_variables_for_secondary_index("name", "Alice")
302
+ assert vars_string["name"] == "Alice"
303
+
304
+ # Integer value
305
+ vars_int = QueryBuilder.build_variables_for_secondary_index("age", 25)
306
+ assert vars_int["age"] == 25
307
+
308
+ # Boolean value
309
+ vars_bool = QueryBuilder.build_variables_for_secondary_index("isActive", True)
310
+ assert vars_bool["isActive"] is True
311
+
312
+
313
+ class TestBuildFilterEquals:
314
+ """Test QueryBuilder.build_filter_equals() method"""
315
+
316
+ def test_filter_equals_string(self):
317
+ """Test filter equals with string value"""
318
+ filter_dict = QueryBuilder.build_filter_equals("name", "John")
319
+
320
+ assert filter_dict == {"name": {"eq": "John"}}
321
+
322
+ def test_filter_equals_number(self):
323
+ """Test filter equals with number value"""
324
+ filter_dict = QueryBuilder.build_filter_equals("age", 30)
325
+
326
+ assert filter_dict == {"age": {"eq": 30}}
327
+
328
+ def test_filter_equals_boolean(self):
329
+ """Test filter equals with boolean value"""
330
+ filter_dict = QueryBuilder.build_filter_equals("isActive", True)
331
+
332
+ assert filter_dict == {"isActive": {"eq": True}}
333
+
334
+ def test_filter_equals_none(self):
335
+ """Test filter equals with None value"""
336
+ filter_dict = QueryBuilder.build_filter_equals("deletedAt", None)
337
+
338
+ assert filter_dict == {"deletedAt": {"eq": None}}
339
+
340
+
341
+ class TestQueryBuilderIntegration:
342
+ """Integration tests for QueryBuilder"""
343
+
344
+ def test_list_query_builds_valid_graphql(self):
345
+ """Test that list query builds syntactically valid GraphQL"""
346
+ query = QueryBuilder.build_list_query("User", fields=["id", "name"])
347
+
348
+ # Should have matching braces
349
+ assert query.count("{") == query.count("}")
350
+ # Should have query keyword
351
+ assert query.startswith("query ")
352
+
353
+ def test_mutation_and_query_are_different(self):
354
+ """Test that queries don't contain mutation keywords"""
355
+ query = QueryBuilder.build_list_query("User")
356
+
357
+ assert "mutation" not in query.lower()
358
+ assert "query" in query.lower()
359
+
360
+ def test_field_indentation_consistent(self):
361
+ """Test that fields are properly indented in queries"""
362
+ query = QueryBuilder.build_list_query("User", fields=["id", "name", "email"])
363
+
364
+ # Fields should be indented (have leading spaces)
365
+ lines = query.split("\n")
366
+ field_lines = [line for line in lines if "id" in line or "name" in line or "email" in line]
367
+
368
+ for line in field_lines:
369
+ # Fields inside items should be indented
370
+ if line.strip() in ["id", "name", "email"]:
371
+ assert line.startswith(" ") # 12 spaces for items fields
372
+
373
+ def test_pagination_variables_match_query_signature(self):
374
+ """Test that pagination variables match the query signature"""
375
+ query_with_pagination = QueryBuilder.build_list_query("User", with_pagination=True)
376
+ query_without_pagination = QueryBuilder.build_list_query("User", with_pagination=False)
377
+
378
+ # With pagination should have both limit and nextToken
379
+ assert "$limit: Int" in query_with_pagination
380
+ assert "$nextToken: String" in query_with_pagination
381
+
382
+ # Without pagination should only have limit
383
+ assert "$limit: Int" in query_without_pagination
384
+ assert "$nextToken: String" not in query_without_pagination