gitmap-core 0.1.0__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.
@@ -0,0 +1,567 @@
1
+ """Tests for JSON diffing and comparison module.
2
+
3
+ Tests layer comparison, map diffing, and diff formatting.
4
+
5
+ Execution Context:
6
+ Test module - run via pytest
7
+
8
+ Dependencies:
9
+ - pytest: Test framework
10
+ - gitmap_core.diff: Module under test
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import pytest
15
+
16
+ from gitmap_core.diff import (
17
+ LayerChange,
18
+ MapDiff,
19
+ diff_json,
20
+ diff_layers,
21
+ diff_maps,
22
+ format_diff_summary,
23
+ )
24
+
25
+
26
+ # ---- Fixtures ------------------------------------------------------------------------------------------------
27
+
28
+
29
+ @pytest.fixture
30
+ def sample_layer_1() -> dict:
31
+ """Create first sample layer."""
32
+ return {
33
+ "id": "layer-001",
34
+ "title": "Roads",
35
+ "url": "https://example.com/roads",
36
+ "opacity": 1.0,
37
+ "visible": True,
38
+ }
39
+
40
+
41
+ @pytest.fixture
42
+ def sample_layer_2() -> dict:
43
+ """Create second sample layer."""
44
+ return {
45
+ "id": "layer-002",
46
+ "title": "Buildings",
47
+ "url": "https://example.com/buildings",
48
+ "opacity": 0.8,
49
+ "visible": True,
50
+ }
51
+
52
+
53
+ @pytest.fixture
54
+ def sample_layer_3() -> dict:
55
+ """Create third sample layer."""
56
+ return {
57
+ "id": "layer-003",
58
+ "title": "Parks",
59
+ "url": "https://example.com/parks",
60
+ "opacity": 0.9,
61
+ "visible": False,
62
+ }
63
+
64
+
65
+ @pytest.fixture
66
+ def sample_map(sample_layer_1: dict, sample_layer_2: dict) -> dict:
67
+ """Create a sample web map."""
68
+ return {
69
+ "operationalLayers": [sample_layer_1, sample_layer_2],
70
+ "tables": [],
71
+ "baseMap": {"title": "Topographic"},
72
+ "version": "2.29",
73
+ "authoringApp": "GitMap",
74
+ }
75
+
76
+
77
+ # ---- LayerChange Tests ---------------------------------------------------------------------------------------
78
+
79
+
80
+ class TestLayerChange:
81
+ """Tests for LayerChange dataclass."""
82
+
83
+ def test_create_added_layer(self) -> None:
84
+ """Test creating an added layer change."""
85
+ change = LayerChange(
86
+ layer_id="layer-001",
87
+ layer_title="Test Layer",
88
+ change_type="added",
89
+ )
90
+
91
+ assert change.layer_id == "layer-001"
92
+ assert change.layer_title == "Test Layer"
93
+ assert change.change_type == "added"
94
+ assert change.details == {}
95
+
96
+ def test_create_modified_layer_with_details(self) -> None:
97
+ """Test creating a modified layer change with details."""
98
+ details = {"values_changed": {"root['opacity']": {"new_value": 0.5, "old_value": 1.0}}}
99
+ change = LayerChange(
100
+ layer_id="layer-002",
101
+ layer_title="Updated Layer",
102
+ change_type="modified",
103
+ details=details,
104
+ )
105
+
106
+ assert change.change_type == "modified"
107
+ assert "values_changed" in change.details
108
+
109
+ def test_create_removed_layer(self) -> None:
110
+ """Test creating a removed layer change."""
111
+ change = LayerChange(
112
+ layer_id="layer-003",
113
+ layer_title="Deleted Layer",
114
+ change_type="removed",
115
+ )
116
+
117
+ assert change.change_type == "removed"
118
+
119
+
120
+ # ---- MapDiff Tests -------------------------------------------------------------------------------------------
121
+
122
+
123
+ class TestMapDiff:
124
+ """Tests for MapDiff dataclass."""
125
+
126
+ def test_empty_diff_has_no_changes(self) -> None:
127
+ """Test that empty MapDiff reports no changes."""
128
+ diff = MapDiff()
129
+
130
+ assert not diff.has_changes
131
+ assert diff.layer_changes == []
132
+ assert diff.table_changes == []
133
+ assert diff.property_changes == {}
134
+
135
+ def test_has_changes_with_layer_changes(self) -> None:
136
+ """Test has_changes with layer changes."""
137
+ diff = MapDiff(
138
+ layer_changes=[
139
+ LayerChange("l1", "Layer 1", "added"),
140
+ ]
141
+ )
142
+
143
+ assert diff.has_changes
144
+
145
+ def test_has_changes_with_table_changes(self) -> None:
146
+ """Test has_changes with table changes."""
147
+ diff = MapDiff(
148
+ table_changes=[
149
+ LayerChange("t1", "Table 1", "removed"),
150
+ ]
151
+ )
152
+
153
+ assert diff.has_changes
154
+
155
+ def test_has_changes_with_property_changes(self) -> None:
156
+ """Test has_changes with property changes."""
157
+ diff = MapDiff(property_changes={"values_changed": {"root['version']": {}}})
158
+
159
+ assert diff.has_changes
160
+
161
+ def test_added_layers_filter(self) -> None:
162
+ """Test added_layers property filters correctly."""
163
+ diff = MapDiff(
164
+ layer_changes=[
165
+ LayerChange("l1", "Layer 1", "added"),
166
+ LayerChange("l2", "Layer 2", "removed"),
167
+ LayerChange("l3", "Layer 3", "added"),
168
+ ]
169
+ )
170
+
171
+ added = diff.added_layers
172
+ assert len(added) == 2
173
+ assert all(c.change_type == "added" for c in added)
174
+
175
+ def test_removed_layers_filter(self) -> None:
176
+ """Test removed_layers property filters correctly."""
177
+ diff = MapDiff(
178
+ layer_changes=[
179
+ LayerChange("l1", "Layer 1", "added"),
180
+ LayerChange("l2", "Layer 2", "removed"),
181
+ LayerChange("l3", "Layer 3", "removed"),
182
+ ]
183
+ )
184
+
185
+ removed = diff.removed_layers
186
+ assert len(removed) == 2
187
+ assert all(c.change_type == "removed" for c in removed)
188
+
189
+ def test_modified_layers_filter(self) -> None:
190
+ """Test modified_layers property filters correctly."""
191
+ diff = MapDiff(
192
+ layer_changes=[
193
+ LayerChange("l1", "Layer 1", "modified"),
194
+ LayerChange("l2", "Layer 2", "added"),
195
+ ]
196
+ )
197
+
198
+ modified = diff.modified_layers
199
+ assert len(modified) == 1
200
+ assert modified[0].layer_id == "l1"
201
+
202
+ def test_table_change_filters(self) -> None:
203
+ """Test table change filter properties."""
204
+ diff = MapDiff(
205
+ table_changes=[
206
+ LayerChange("t1", "Table 1", "added"),
207
+ LayerChange("t2", "Table 2", "removed"),
208
+ LayerChange("t3", "Table 3", "modified"),
209
+ ]
210
+ )
211
+
212
+ assert len(diff.added_tables) == 1
213
+ assert len(diff.removed_tables) == 1
214
+ assert len(diff.modified_tables) == 1
215
+
216
+
217
+ # ---- diff_layers Tests ---------------------------------------------------------------------------------------
218
+
219
+
220
+ class TestDiffLayers:
221
+ """Tests for diff_layers function."""
222
+
223
+ def test_empty_layers_no_changes(self) -> None:
224
+ """Test comparing empty layer lists."""
225
+ changes = diff_layers([], [])
226
+
227
+ assert changes == []
228
+
229
+ def test_identical_layers_no_changes(self, sample_layer_1: dict) -> None:
230
+ """Test comparing identical layers."""
231
+ changes = diff_layers([sample_layer_1], [sample_layer_1.copy()])
232
+
233
+ assert changes == []
234
+
235
+ def test_detect_added_layer(self, sample_layer_1: dict, sample_layer_2: dict) -> None:
236
+ """Test detecting an added layer."""
237
+ layers1 = [sample_layer_1, sample_layer_2]
238
+ layers2 = [sample_layer_1]
239
+
240
+ changes = diff_layers(layers1, layers2)
241
+
242
+ added = [c for c in changes if c.change_type == "added"]
243
+ assert len(added) == 1
244
+ assert added[0].layer_id == "layer-002"
245
+ assert added[0].layer_title == "Buildings"
246
+
247
+ def test_detect_removed_layer(self, sample_layer_1: dict, sample_layer_2: dict) -> None:
248
+ """Test detecting a removed layer."""
249
+ layers1 = [sample_layer_1]
250
+ layers2 = [sample_layer_1, sample_layer_2]
251
+
252
+ changes = diff_layers(layers1, layers2)
253
+
254
+ removed = [c for c in changes if c.change_type == "removed"]
255
+ assert len(removed) == 1
256
+ assert removed[0].layer_id == "layer-002"
257
+
258
+ def test_detect_modified_layer(self, sample_layer_1: dict) -> None:
259
+ """Test detecting a modified layer."""
260
+ layer1_modified = sample_layer_1.copy()
261
+ layer1_modified["opacity"] = 0.5
262
+ layer1_modified["visible"] = False
263
+
264
+ changes = diff_layers([layer1_modified], [sample_layer_1])
265
+
266
+ modified = [c for c in changes if c.change_type == "modified"]
267
+ assert len(modified) == 1
268
+ assert modified[0].layer_id == "layer-001"
269
+ assert "values_changed" in modified[0].details
270
+
271
+ def test_detect_multiple_changes(
272
+ self, sample_layer_1: dict, sample_layer_2: dict, sample_layer_3: dict
273
+ ) -> None:
274
+ """Test detecting multiple types of changes."""
275
+ # layers1: layer1 (modified), layer3 (added)
276
+ # layers2: layer1 (original), layer2 (will be removed)
277
+ layer1_modified = sample_layer_1.copy()
278
+ layer1_modified["opacity"] = 0.5
279
+
280
+ layers1 = [layer1_modified, sample_layer_3]
281
+ layers2 = [sample_layer_1, sample_layer_2]
282
+
283
+ changes = diff_layers(layers1, layers2)
284
+
285
+ added = [c for c in changes if c.change_type == "added"]
286
+ removed = [c for c in changes if c.change_type == "removed"]
287
+ modified = [c for c in changes if c.change_type == "modified"]
288
+
289
+ assert len(added) == 1
290
+ assert len(removed) == 1
291
+ assert len(modified) == 1
292
+
293
+ def test_layer_without_id_ignored(self) -> None:
294
+ """Test that layers without IDs are ignored."""
295
+ layer_no_id = {"title": "No ID Layer", "url": "https://example.com"}
296
+ layer_with_id = {"id": "layer-001", "title": "Has ID"}
297
+
298
+ changes = diff_layers([layer_no_id, layer_with_id], [layer_with_id])
299
+
300
+ # Should only see the layer_with_id, layer_no_id is ignored
301
+ assert len(changes) == 0
302
+
303
+ def test_untitled_layer_uses_default(self) -> None:
304
+ """Test that layers without title use 'Untitled'."""
305
+ layer = {"id": "layer-001"}
306
+
307
+ changes = diff_layers([layer], [])
308
+
309
+ assert changes[0].layer_title == "Untitled"
310
+
311
+
312
+ # ---- diff_maps Tests -----------------------------------------------------------------------------------------
313
+
314
+
315
+ class TestDiffMaps:
316
+ """Tests for diff_maps function."""
317
+
318
+ def test_identical_maps_no_changes(self, sample_map: dict) -> None:
319
+ """Test comparing identical maps."""
320
+ import copy
321
+ map2 = copy.deepcopy(sample_map)
322
+
323
+ diff = diff_maps(sample_map, map2)
324
+
325
+ assert not diff.has_changes
326
+
327
+ def test_detect_layer_addition(self, sample_map: dict, sample_layer_3: dict) -> None:
328
+ """Test detecting layer addition in maps."""
329
+ import copy
330
+ map2 = copy.deepcopy(sample_map)
331
+ sample_map["operationalLayers"].append(sample_layer_3)
332
+
333
+ diff = diff_maps(sample_map, map2)
334
+
335
+ assert len(diff.added_layers) == 1
336
+ assert diff.added_layers[0].layer_id == "layer-003"
337
+
338
+ def test_detect_layer_removal(self, sample_map: dict) -> None:
339
+ """Test detecting layer removal in maps."""
340
+ import copy
341
+ map2 = copy.deepcopy(sample_map)
342
+ # Remove first layer from map1
343
+ removed_layer = sample_map["operationalLayers"].pop(0)
344
+
345
+ diff = diff_maps(sample_map, map2)
346
+
347
+ assert len(diff.removed_layers) == 1
348
+ assert diff.removed_layers[0].layer_id == removed_layer["id"]
349
+
350
+ def test_detect_table_changes(self) -> None:
351
+ """Test detecting table changes."""
352
+ map1 = {
353
+ "operationalLayers": [],
354
+ "tables": [{"id": "table-001", "title": "Data Table"}],
355
+ }
356
+ map2 = {
357
+ "operationalLayers": [],
358
+ "tables": [],
359
+ }
360
+
361
+ diff = diff_maps(map1, map2)
362
+
363
+ assert len(diff.added_tables) == 1
364
+ assert diff.added_tables[0].layer_id == "table-001"
365
+
366
+ def test_detect_property_changes(self, sample_map: dict) -> None:
367
+ """Test detecting map property changes."""
368
+ import copy
369
+ map2 = copy.deepcopy(sample_map)
370
+ sample_map["version"] = "2.30"
371
+ sample_map["authoringApp"] = "GitMap Pro"
372
+
373
+ diff = diff_maps(sample_map, map2)
374
+
375
+ assert diff.property_changes
376
+ assert "values_changed" in diff.property_changes
377
+
378
+ def test_ignores_layer_and_table_keys_in_properties(self, sample_map: dict) -> None:
379
+ """Test that operationalLayers and tables are not in property_changes."""
380
+ import copy
381
+ map2 = copy.deepcopy(sample_map)
382
+ # Only change layers, not properties
383
+ sample_map["operationalLayers"].append({"id": "new", "title": "New"})
384
+
385
+ diff = diff_maps(sample_map, map2)
386
+
387
+ # Should have layer change but no property changes
388
+ assert diff.has_changes
389
+ assert not diff.property_changes
390
+ assert len(diff.added_layers) == 1
391
+
392
+ def test_handles_missing_keys(self) -> None:
393
+ """Test handling maps with missing keys."""
394
+ map1 = {"version": "1.0"}
395
+ map2 = {"version": "1.0", "authoringApp": "Test"}
396
+
397
+ diff = diff_maps(map1, map2)
398
+
399
+ # Should detect the removed key
400
+ assert diff.property_changes
401
+
402
+
403
+ # ---- diff_json Tests -----------------------------------------------------------------------------------------
404
+
405
+
406
+ class TestDiffJson:
407
+ """Tests for diff_json function."""
408
+
409
+ def test_identical_objects_no_diff(self) -> None:
410
+ """Test that identical objects have no diff."""
411
+ obj = {"key": "value", "number": 42}
412
+
413
+ result = diff_json(obj, obj.copy())
414
+
415
+ assert result == {}
416
+
417
+ def test_detect_value_change(self) -> None:
418
+ """Test detecting value changes."""
419
+ obj1 = {"key": "new_value"}
420
+ obj2 = {"key": "old_value"}
421
+
422
+ result = diff_json(obj1, obj2)
423
+
424
+ assert "values_changed" in result
425
+
426
+ def test_detect_added_key(self) -> None:
427
+ """Test detecting added keys."""
428
+ obj1 = {"key1": "value1", "key2": "value2"}
429
+ obj2 = {"key1": "value1"}
430
+
431
+ result = diff_json(obj1, obj2)
432
+
433
+ assert "dictionary_item_added" in result
434
+
435
+ def test_detect_removed_key(self) -> None:
436
+ """Test detecting removed keys."""
437
+ obj1 = {"key1": "value1"}
438
+ obj2 = {"key1": "value1", "key2": "value2"}
439
+
440
+ result = diff_json(obj1, obj2)
441
+
442
+ assert "dictionary_item_removed" in result
443
+
444
+ def test_ignore_order_by_default(self) -> None:
445
+ """Test that list order is ignored by default."""
446
+ obj1 = {"items": [1, 2, 3]}
447
+ obj2 = {"items": [3, 2, 1]}
448
+
449
+ result = diff_json(obj1, obj2)
450
+
451
+ assert result == {}
452
+
453
+ def test_respect_order_when_requested(self) -> None:
454
+ """Test that list order can be respected."""
455
+ obj1 = {"items": [1, 2, 3]}
456
+ obj2 = {"items": [3, 2, 1]}
457
+
458
+ result = diff_json(obj1, obj2, ignore_order=False)
459
+
460
+ assert result != {}
461
+
462
+
463
+ # ---- format_diff_summary Tests -------------------------------------------------------------------------------
464
+
465
+
466
+ class TestFormatDiffSummary:
467
+ """Tests for format_diff_summary function."""
468
+
469
+ def test_no_changes_message(self) -> None:
470
+ """Test message for no changes."""
471
+ diff = MapDiff()
472
+
473
+ result = format_diff_summary(diff)
474
+
475
+ assert result == "No changes detected."
476
+
477
+ def test_format_added_layers(self) -> None:
478
+ """Test formatting added layers."""
479
+ diff = MapDiff(
480
+ layer_changes=[
481
+ LayerChange("l1", "Roads", "added"),
482
+ LayerChange("l2", "Buildings", "added"),
483
+ ]
484
+ )
485
+
486
+ result = format_diff_summary(diff)
487
+
488
+ assert "Added layers (2):" in result
489
+ assert "+ Roads (l1)" in result
490
+ assert "+ Buildings (l2)" in result
491
+
492
+ def test_format_removed_layers(self) -> None:
493
+ """Test formatting removed layers."""
494
+ diff = MapDiff(
495
+ layer_changes=[
496
+ LayerChange("l1", "Old Layer", "removed"),
497
+ ]
498
+ )
499
+
500
+ result = format_diff_summary(diff)
501
+
502
+ assert "Removed layers (1):" in result
503
+ assert "- Old Layer (l1)" in result
504
+
505
+ def test_format_modified_layers(self) -> None:
506
+ """Test formatting modified layers."""
507
+ diff = MapDiff(
508
+ layer_changes=[
509
+ LayerChange("l1", "Updated Layer", "modified"),
510
+ ]
511
+ )
512
+
513
+ result = format_diff_summary(diff)
514
+
515
+ assert "Modified layers (1):" in result
516
+ assert "~ Updated Layer (l1)" in result
517
+
518
+ def test_format_table_changes(self) -> None:
519
+ """Test formatting table changes."""
520
+ diff = MapDiff(
521
+ table_changes=[
522
+ LayerChange("t1", "Data Table", "added"),
523
+ LayerChange("t2", "Old Table", "removed"),
524
+ LayerChange("t3", "Updated Table", "modified"),
525
+ ]
526
+ )
527
+
528
+ result = format_diff_summary(diff)
529
+
530
+ assert "Added tables (1):" in result
531
+ assert "Removed tables (1):" in result
532
+ assert "Modified tables (1):" in result
533
+
534
+ def test_format_property_changes(self) -> None:
535
+ """Test formatting property changes."""
536
+ diff = MapDiff(
537
+ property_changes={
538
+ "values_changed": {"root['version']": {}},
539
+ "dictionary_item_added": {"root['newKey']": {}},
540
+ }
541
+ )
542
+
543
+ result = format_diff_summary(diff)
544
+
545
+ assert "Map properties changed:" in result
546
+ assert "* values_changed" in result
547
+ assert "* dictionary_item_added" in result
548
+
549
+ def test_format_mixed_changes(self) -> None:
550
+ """Test formatting multiple types of changes."""
551
+ diff = MapDiff(
552
+ layer_changes=[
553
+ LayerChange("l1", "New Layer", "added"),
554
+ LayerChange("l2", "Changed Layer", "modified"),
555
+ ],
556
+ table_changes=[
557
+ LayerChange("t1", "Old Table", "removed"),
558
+ ],
559
+ property_changes={"values_changed": {}},
560
+ )
561
+
562
+ result = format_diff_summary(diff)
563
+
564
+ assert "Added layers" in result
565
+ assert "Modified layers" in result
566
+ assert "Removed tables" in result
567
+ assert "Map properties changed" in result