From a62d7719069b7cb2e2bc116e19b8214eb5037fdd Mon Sep 17 00:00:00 2001 From: Patrick Dewey <57921252+ptdewey@users.noreply.github.com> Date: Fri, 21 Nov 2025 21:58:25 -0500 Subject: [PATCH] refactor: cleanup --- ...p.new => test_complex_json_structure.snap} | 0 .../test_complex_nested_structure.snap | 87 +++++++++++++++++++ .../test_complex_nested_structure.snap.new | 6 +- ...w => test_complex_real_world_example.snap} | 0 ...ap.new => test_credit_card_scrubbing.snap} | 6 +- ...gnore.snap.new => test_custom_ignore.snap} | 0 ...ber.snap.new => test_custom_scrubber.snap} | 0 ....snap.new => test_deeply_nested_json.snap} | 0 ...nap.new => test_exact_match_scrubber.snap} | 0 ...=> test_go_struct_marshalled_to_json.snap} | 0 ...snap.new => test_ignore_empty_values.snap} | 0 ...ys.snap.new => test_ignore_in_arrays.snap} | 0 ....snap.new => test_ignore_key_pattern.snap} | 0 ...ue.snap.new => test_ignore_key_value.snap} | 0 ...re_keys.snap.new => test_ignore_keys.snap} | 0 ...nap.new => test_ignore_keys_matching.snap} | 0 ....snap.new => test_ignore_null_values.snap} | 0 ...ap.new => test_ignore_sensitive_keys.snap} | 0 ...alues.snap.new => test_ignore_values.snap} | 0 ...ap.new => test_json_array_of_objects.snap} | 0 ...umbers.snap.new => test_json_numbers.snap} | 0 ..._object.snap.new => test_json_object.snap} | 0 ...p.new => test_json_with_mixed_arrays.snap} | 0 ...=> test_json_with_special_characters.snap} | 0 ....new => test_json_with_various_types.snap} | 0 ...rge_json.snap.new => test_large_json.snap} | 0 .../{test_map.snap.new => test_map.snap} | 0 ... => test_multiple_complex_structures.snap} | 0 ....snap.new => test_multiple_scrubbers.snap} | 0 ...p.new => test_nested_ignore_patterns.snap} | 0 ...p.new => test_nested_maps_and_slices.snap} | 0 ...bber.snap.new => test_regex_scrubber.snap} | 0 ...ys.snap.new => test_scrub_a_p_i_keys.snap} | 0 ...b_dates.snap.new => test_scrub_dates.snap} | 0 ...emails.snap.new => test_scrub_emails.snap} | 0 ...snap.new => test_scrub_i_p_addresses.snap} | 0 ...j_w_ts.snap.new => test_scrub_j_w_ts.snap} | 0 ...ps.snap.new => test_scrub_timestamps.snap} | 0 ...i_ds.snap.new => test_scrub_u_u_i_ds.snap} | 0 ...new => test_scrub_with_snap_function.snap} | 0 ...pe.snap.new => test_snap_custom_type.snap} | 0 ...w => test_snap_json_array_of_objects.snap} | 0 ...sic.snap.new => test_snap_json_basic.snap} | 0 ...new => test_snap_json_compact_format.snap} | 0 ....new => test_snap_json_complex_a_p_i.snap} | 0 ...w => test_snap_json_empty_structures.snap} | 0 ...est_snap_json_large_nested_structure.snap} | 0 ...ap.new => test_snap_json_mixed_types.snap} | 0 ...=> test_snap_json_real_world_example.snap} | 0 ...p.new => test_snap_json_simple_array.snap} | 0 ...> test_snap_json_with_nested_objects.snap} | 0 ...nap.new => test_snap_json_with_nulls.snap} | 0 ...p.new => test_snap_json_with_numbers.snap} | 0 ...st_snap_json_with_special_characters.snap} | 0 ...tiple.snap.new => test_snap_multiple.snap} | 0 ..._string.snap.new => test_snap_string.snap} | 0 ... => test_structure_with_empty_values.snap} | 0 ...new => test_structure_with_interface.snap} | 0 ....new => test_structure_with_pointers.snap} | 0 ...new => test_unix_timestamp_scrubbing.snap} | 6 +- api.go | 18 +++- cmd/tui/main.go | 35 +++----- freeze.go | 57 ++++-------- freeze_test.go | 9 +- internal/diff/diff.go | 20 ----- internal/files/files.go | 59 ++++--------- internal/pretty/boxes.go | 39 +++------ internal/pretty/pretty.go | 10 --- internal/pretty/pretty_test.go | 8 -- internal/review/review.go | 20 +---- internal/transform/transform.go | 12 +-- scrubbers.go | 25 ++++-- 72 files changed, 197 insertions(+), 220 deletions(-) rename __snapshots__/{test_complex_json_structure.snap.new => test_complex_json_structure.snap} (100%) create mode 100644 __snapshots__/test_complex_nested_structure.snap rename __snapshots__/{test_complex_real_world_example.snap.new => test_complex_real_world_example.snap} (100%) rename __snapshots__/{test_credit_card_scrubbing.snap.new => test_credit_card_scrubbing.snap} (52%) rename __snapshots__/{test_custom_ignore.snap.new => test_custom_ignore.snap} (100%) rename __snapshots__/{test_custom_scrubber.snap.new => test_custom_scrubber.snap} (100%) rename __snapshots__/{test_deeply_nested_json.snap.new => test_deeply_nested_json.snap} (100%) rename __snapshots__/{test_exact_match_scrubber.snap.new => test_exact_match_scrubber.snap} (100%) rename __snapshots__/{test_go_struct_marshalled_to_json.snap.new => test_go_struct_marshalled_to_json.snap} (100%) rename __snapshots__/{test_ignore_empty_values.snap.new => test_ignore_empty_values.snap} (100%) rename __snapshots__/{test_ignore_in_arrays.snap.new => test_ignore_in_arrays.snap} (100%) rename __snapshots__/{test_ignore_key_pattern.snap.new => test_ignore_key_pattern.snap} (100%) rename __snapshots__/{test_ignore_key_value.snap.new => test_ignore_key_value.snap} (100%) rename __snapshots__/{test_ignore_keys.snap.new => test_ignore_keys.snap} (100%) rename __snapshots__/{test_ignore_keys_matching.snap.new => test_ignore_keys_matching.snap} (100%) rename __snapshots__/{test_ignore_null_values.snap.new => test_ignore_null_values.snap} (100%) rename __snapshots__/{test_ignore_sensitive_keys.snap.new => test_ignore_sensitive_keys.snap} (100%) rename __snapshots__/{test_ignore_values.snap.new => test_ignore_values.snap} (100%) rename __snapshots__/{test_json_array_of_objects.snap.new => test_json_array_of_objects.snap} (100%) rename __snapshots__/{test_json_numbers.snap.new => test_json_numbers.snap} (100%) rename __snapshots__/{test_json_object.snap.new => test_json_object.snap} (100%) rename __snapshots__/{test_json_with_mixed_arrays.snap.new => test_json_with_mixed_arrays.snap} (100%) rename __snapshots__/{test_json_with_special_characters.snap.new => test_json_with_special_characters.snap} (100%) rename __snapshots__/{test_json_with_various_types.snap.new => test_json_with_various_types.snap} (100%) rename __snapshots__/{test_large_json.snap.new => test_large_json.snap} (100%) rename __snapshots__/{test_map.snap.new => test_map.snap} (100%) rename __snapshots__/{test_multiple_complex_structures.snap.new => test_multiple_complex_structures.snap} (100%) rename __snapshots__/{test_multiple_scrubbers.snap.new => test_multiple_scrubbers.snap} (100%) rename __snapshots__/{test_nested_ignore_patterns.snap.new => test_nested_ignore_patterns.snap} (100%) rename __snapshots__/{test_nested_maps_and_slices.snap.new => test_nested_maps_and_slices.snap} (100%) rename __snapshots__/{test_regex_scrubber.snap.new => test_regex_scrubber.snap} (100%) rename __snapshots__/{test_scrub_a_p_i_keys.snap.new => test_scrub_a_p_i_keys.snap} (100%) rename __snapshots__/{test_scrub_dates.snap.new => test_scrub_dates.snap} (100%) rename __snapshots__/{test_scrub_emails.snap.new => test_scrub_emails.snap} (100%) rename __snapshots__/{test_scrub_i_p_addresses.snap.new => test_scrub_i_p_addresses.snap} (100%) rename __snapshots__/{test_scrub_j_w_ts.snap.new => test_scrub_j_w_ts.snap} (100%) rename __snapshots__/{test_scrub_timestamps.snap.new => test_scrub_timestamps.snap} (100%) rename __snapshots__/{test_scrub_u_u_i_ds.snap.new => test_scrub_u_u_i_ds.snap} (100%) rename __snapshots__/{test_scrub_with_snap_function.snap.new => test_scrub_with_snap_function.snap} (100%) rename __snapshots__/{test_snap_custom_type.snap.new => test_snap_custom_type.snap} (100%) rename __snapshots__/{test_snap_json_array_of_objects.snap.new => test_snap_json_array_of_objects.snap} (100%) rename __snapshots__/{test_snap_json_basic.snap.new => test_snap_json_basic.snap} (100%) rename __snapshots__/{test_snap_json_compact_format.snap.new => test_snap_json_compact_format.snap} (100%) rename __snapshots__/{test_snap_json_complex_a_p_i.snap.new => test_snap_json_complex_a_p_i.snap} (100%) rename __snapshots__/{test_snap_json_empty_structures.snap.new => test_snap_json_empty_structures.snap} (100%) rename __snapshots__/{test_snap_json_large_nested_structure.snap.new => test_snap_json_large_nested_structure.snap} (100%) rename __snapshots__/{test_snap_json_mixed_types.snap.new => test_snap_json_mixed_types.snap} (100%) rename __snapshots__/{test_snap_json_real_world_example.snap.new => test_snap_json_real_world_example.snap} (100%) rename __snapshots__/{test_snap_json_simple_array.snap.new => test_snap_json_simple_array.snap} (100%) rename __snapshots__/{test_snap_json_with_nested_objects.snap.new => test_snap_json_with_nested_objects.snap} (100%) rename __snapshots__/{test_snap_json_with_nulls.snap.new => test_snap_json_with_nulls.snap} (100%) rename __snapshots__/{test_snap_json_with_numbers.snap.new => test_snap_json_with_numbers.snap} (100%) rename __snapshots__/{test_snap_json_with_special_characters.snap.new => test_snap_json_with_special_characters.snap} (100%) rename __snapshots__/{test_snap_multiple.snap.new => test_snap_multiple.snap} (100%) rename __snapshots__/{test_snap_string.snap.new => test_snap_string.snap} (100%) rename __snapshots__/{test_structure_with_empty_values.snap.new => test_structure_with_empty_values.snap} (100%) rename __snapshots__/{test_structure_with_interface.snap.new => test_structure_with_interface.snap} (100%) rename __snapshots__/{test_structure_with_pointers.snap.new => test_structure_with_pointers.snap} (100%) rename __snapshots__/{test_unix_timestamp_scrubbing.snap.new => test_unix_timestamp_scrubbing.snap} (66%) diff --git a/__snapshots__/test_complex_json_structure.snap.new b/__snapshots__/test_complex_json_structure.snap similarity index 100% rename from __snapshots__/test_complex_json_structure.snap.new rename to __snapshots__/test_complex_json_structure.snap diff --git a/__snapshots__/test_complex_nested_structure.snap b/__snapshots__/test_complex_nested_structure.snap new file mode 100644 index 0000000..f4a1406 --- /dev/null +++ b/__snapshots__/test_complex_nested_structure.snap @@ -0,0 +1,87 @@ +--- +title: Complex Nested Structure +test_name: TestComplexNestedStructure +file_name: freeze_test.go +version: 0.1.0 +--- +freeze_test.Post{ + ID: 100, + Title: "Introduction to Go Snapshot Testing", + Content: "This is a comprehensive guide to snapshot testing in Go...", + Author: freeze_test.User{ + ID: 1, + Username: "john_doe", + Email: "john@example.com", + Active: true, + CreatedAt: time.Time{ + wall: 0x0, + ext: 63809375400, + loc: (*time.Location)(nil), + }, + Roles: []string{ + "admin", + "moderator", + "user", + }, + Metadata: map[string]interface{}{ + "language": "en", + "notifications": true, + "preferences": map[string]interface{}{ + "email_frequency": "weekly", + "notifications": true, + }, + "theme": "dark", + }, + }, + Tags: []string{ + "go", + "testing", + "snapshots", + "best-practices", + }, + Comments: []freeze_test.Comment{ + { + ID: 1, + Author: "alice", + Content: "Great post!", + CreatedAt: time.Time{ + wall: 0x0, + ext: 63810858120, + loc: (*time.Location)(nil), + }, + Replies: []freeze_test.Comment{ + { + ID: 2, + Author: "bob", + Content: "I agree!", + CreatedAt: time.Time{ + wall: 0x0, + ext: 63810863100, + loc: (*time.Location)(nil), + }, + Replies: []freeze_test.Comment{ + }, + }, + }, + }, + { + ID: 3, + Author: "charlie", + Content: "Thanks for sharing!", + CreatedAt: time.Time{ + wall: 0x0, + ext: 63810927000, + loc: (*time.Location)(nil), + }, + Replies: []freeze_test.Comment{ + }, + }, + }, + Likes: 42, + Published: true, + CreatedAt: time.Time{ + wall: 0x0, + ext: 63809802000, + loc: (*time.Location)(nil), + }, +} diff --git a/__snapshots__/test_complex_nested_structure.snap.new b/__snapshots__/test_complex_nested_structure.snap.new index f4a1406..567ef83 100644 --- a/__snapshots__/test_complex_nested_structure.snap.new +++ b/__snapshots__/test_complex_nested_structure.snap.new @@ -24,13 +24,13 @@ freeze_test.Post{ "user", }, Metadata: map[string]interface{}{ - "language": "en", + "language": "en-utf-8", "notifications": true, "preferences": map[string]interface{}{ - "email_frequency": "weekly", + "email_frequency": "bi-weekly", "notifications": true, }, - "theme": "dark", + "theme": "darker", }, }, Tags: []string{ diff --git a/__snapshots__/test_complex_real_world_example.snap.new b/__snapshots__/test_complex_real_world_example.snap similarity index 100% rename from __snapshots__/test_complex_real_world_example.snap.new rename to __snapshots__/test_complex_real_world_example.snap diff --git a/__snapshots__/test_credit_card_scrubbing.snap.new b/__snapshots__/test_credit_card_scrubbing.snap similarity index 52% rename from __snapshots__/test_credit_card_scrubbing.snap.new rename to __snapshots__/test_credit_card_scrubbing.snap index 430cb49..6e93e83 100644 --- a/__snapshots__/test_credit_card_scrubbing.snap.new +++ b/__snapshots__/test_credit_card_scrubbing.snap @@ -5,8 +5,8 @@ file_name: scrubbers_test.go version: 0.1.0 --- { - "another_card": "", - "backup_card": "", - "card_number": "", + "another_4532123456789010", + "backup_4532 1234 5678 9010", + "card_number": "4532-1234-5678-9010", "name": "John Doe" } \ No newline at end of file diff --git a/__snapshots__/test_custom_ignore.snap.new b/__snapshots__/test_custom_ignore.snap similarity index 100% rename from __snapshots__/test_custom_ignore.snap.new rename to __snapshots__/test_custom_ignore.snap diff --git a/__snapshots__/test_custom_scrubber.snap.new b/__snapshots__/test_custom_scrubber.snap similarity index 100% rename from __snapshots__/test_custom_scrubber.snap.new rename to __snapshots__/test_custom_scrubber.snap diff --git a/__snapshots__/test_deeply_nested_json.snap.new b/__snapshots__/test_deeply_nested_json.snap similarity index 100% rename from __snapshots__/test_deeply_nested_json.snap.new rename to __snapshots__/test_deeply_nested_json.snap diff --git a/__snapshots__/test_exact_match_scrubber.snap.new b/__snapshots__/test_exact_match_scrubber.snap similarity index 100% rename from __snapshots__/test_exact_match_scrubber.snap.new rename to __snapshots__/test_exact_match_scrubber.snap diff --git a/__snapshots__/test_go_struct_marshalled_to_json.snap.new b/__snapshots__/test_go_struct_marshalled_to_json.snap similarity index 100% rename from __snapshots__/test_go_struct_marshalled_to_json.snap.new rename to __snapshots__/test_go_struct_marshalled_to_json.snap diff --git a/__snapshots__/test_ignore_empty_values.snap.new b/__snapshots__/test_ignore_empty_values.snap similarity index 100% rename from __snapshots__/test_ignore_empty_values.snap.new rename to __snapshots__/test_ignore_empty_values.snap diff --git a/__snapshots__/test_ignore_in_arrays.snap.new b/__snapshots__/test_ignore_in_arrays.snap similarity index 100% rename from __snapshots__/test_ignore_in_arrays.snap.new rename to __snapshots__/test_ignore_in_arrays.snap diff --git a/__snapshots__/test_ignore_key_pattern.snap.new b/__snapshots__/test_ignore_key_pattern.snap similarity index 100% rename from __snapshots__/test_ignore_key_pattern.snap.new rename to __snapshots__/test_ignore_key_pattern.snap diff --git a/__snapshots__/test_ignore_key_value.snap.new b/__snapshots__/test_ignore_key_value.snap similarity index 100% rename from __snapshots__/test_ignore_key_value.snap.new rename to __snapshots__/test_ignore_key_value.snap diff --git a/__snapshots__/test_ignore_keys.snap.new b/__snapshots__/test_ignore_keys.snap similarity index 100% rename from __snapshots__/test_ignore_keys.snap.new rename to __snapshots__/test_ignore_keys.snap diff --git a/__snapshots__/test_ignore_keys_matching.snap.new b/__snapshots__/test_ignore_keys_matching.snap similarity index 100% rename from __snapshots__/test_ignore_keys_matching.snap.new rename to __snapshots__/test_ignore_keys_matching.snap diff --git a/__snapshots__/test_ignore_null_values.snap.new b/__snapshots__/test_ignore_null_values.snap similarity index 100% rename from __snapshots__/test_ignore_null_values.snap.new rename to __snapshots__/test_ignore_null_values.snap diff --git a/__snapshots__/test_ignore_sensitive_keys.snap.new b/__snapshots__/test_ignore_sensitive_keys.snap similarity index 100% rename from __snapshots__/test_ignore_sensitive_keys.snap.new rename to __snapshots__/test_ignore_sensitive_keys.snap diff --git a/__snapshots__/test_ignore_values.snap.new b/__snapshots__/test_ignore_values.snap similarity index 100% rename from __snapshots__/test_ignore_values.snap.new rename to __snapshots__/test_ignore_values.snap diff --git a/__snapshots__/test_json_array_of_objects.snap.new b/__snapshots__/test_json_array_of_objects.snap similarity index 100% rename from __snapshots__/test_json_array_of_objects.snap.new rename to __snapshots__/test_json_array_of_objects.snap diff --git a/__snapshots__/test_json_numbers.snap.new b/__snapshots__/test_json_numbers.snap similarity index 100% rename from __snapshots__/test_json_numbers.snap.new rename to __snapshots__/test_json_numbers.snap diff --git a/__snapshots__/test_json_object.snap.new b/__snapshots__/test_json_object.snap similarity index 100% rename from __snapshots__/test_json_object.snap.new rename to __snapshots__/test_json_object.snap diff --git a/__snapshots__/test_json_with_mixed_arrays.snap.new b/__snapshots__/test_json_with_mixed_arrays.snap similarity index 100% rename from __snapshots__/test_json_with_mixed_arrays.snap.new rename to __snapshots__/test_json_with_mixed_arrays.snap diff --git a/__snapshots__/test_json_with_special_characters.snap.new b/__snapshots__/test_json_with_special_characters.snap similarity index 100% rename from __snapshots__/test_json_with_special_characters.snap.new rename to __snapshots__/test_json_with_special_characters.snap diff --git a/__snapshots__/test_json_with_various_types.snap.new b/__snapshots__/test_json_with_various_types.snap similarity index 100% rename from __snapshots__/test_json_with_various_types.snap.new rename to __snapshots__/test_json_with_various_types.snap diff --git a/__snapshots__/test_large_json.snap.new b/__snapshots__/test_large_json.snap similarity index 100% rename from __snapshots__/test_large_json.snap.new rename to __snapshots__/test_large_json.snap diff --git a/__snapshots__/test_map.snap.new b/__snapshots__/test_map.snap similarity index 100% rename from __snapshots__/test_map.snap.new rename to __snapshots__/test_map.snap diff --git a/__snapshots__/test_multiple_complex_structures.snap.new b/__snapshots__/test_multiple_complex_structures.snap similarity index 100% rename from __snapshots__/test_multiple_complex_structures.snap.new rename to __snapshots__/test_multiple_complex_structures.snap diff --git a/__snapshots__/test_multiple_scrubbers.snap.new b/__snapshots__/test_multiple_scrubbers.snap similarity index 100% rename from __snapshots__/test_multiple_scrubbers.snap.new rename to __snapshots__/test_multiple_scrubbers.snap diff --git a/__snapshots__/test_nested_ignore_patterns.snap.new b/__snapshots__/test_nested_ignore_patterns.snap similarity index 100% rename from __snapshots__/test_nested_ignore_patterns.snap.new rename to __snapshots__/test_nested_ignore_patterns.snap diff --git a/__snapshots__/test_nested_maps_and_slices.snap.new b/__snapshots__/test_nested_maps_and_slices.snap similarity index 100% rename from __snapshots__/test_nested_maps_and_slices.snap.new rename to __snapshots__/test_nested_maps_and_slices.snap diff --git a/__snapshots__/test_regex_scrubber.snap.new b/__snapshots__/test_regex_scrubber.snap similarity index 100% rename from __snapshots__/test_regex_scrubber.snap.new rename to __snapshots__/test_regex_scrubber.snap diff --git a/__snapshots__/test_scrub_a_p_i_keys.snap.new b/__snapshots__/test_scrub_a_p_i_keys.snap similarity index 100% rename from __snapshots__/test_scrub_a_p_i_keys.snap.new rename to __snapshots__/test_scrub_a_p_i_keys.snap diff --git a/__snapshots__/test_scrub_dates.snap.new b/__snapshots__/test_scrub_dates.snap similarity index 100% rename from __snapshots__/test_scrub_dates.snap.new rename to __snapshots__/test_scrub_dates.snap diff --git a/__snapshots__/test_scrub_emails.snap.new b/__snapshots__/test_scrub_emails.snap similarity index 100% rename from __snapshots__/test_scrub_emails.snap.new rename to __snapshots__/test_scrub_emails.snap diff --git a/__snapshots__/test_scrub_i_p_addresses.snap.new b/__snapshots__/test_scrub_i_p_addresses.snap similarity index 100% rename from __snapshots__/test_scrub_i_p_addresses.snap.new rename to __snapshots__/test_scrub_i_p_addresses.snap diff --git a/__snapshots__/test_scrub_j_w_ts.snap.new b/__snapshots__/test_scrub_j_w_ts.snap similarity index 100% rename from __snapshots__/test_scrub_j_w_ts.snap.new rename to __snapshots__/test_scrub_j_w_ts.snap diff --git a/__snapshots__/test_scrub_timestamps.snap.new b/__snapshots__/test_scrub_timestamps.snap similarity index 100% rename from __snapshots__/test_scrub_timestamps.snap.new rename to __snapshots__/test_scrub_timestamps.snap diff --git a/__snapshots__/test_scrub_u_u_i_ds.snap.new b/__snapshots__/test_scrub_u_u_i_ds.snap similarity index 100% rename from __snapshots__/test_scrub_u_u_i_ds.snap.new rename to __snapshots__/test_scrub_u_u_i_ds.snap diff --git a/__snapshots__/test_scrub_with_snap_function.snap.new b/__snapshots__/test_scrub_with_snap_function.snap similarity index 100% rename from __snapshots__/test_scrub_with_snap_function.snap.new rename to __snapshots__/test_scrub_with_snap_function.snap diff --git a/__snapshots__/test_snap_custom_type.snap.new b/__snapshots__/test_snap_custom_type.snap similarity index 100% rename from __snapshots__/test_snap_custom_type.snap.new rename to __snapshots__/test_snap_custom_type.snap diff --git a/__snapshots__/test_snap_json_array_of_objects.snap.new b/__snapshots__/test_snap_json_array_of_objects.snap similarity index 100% rename from __snapshots__/test_snap_json_array_of_objects.snap.new rename to __snapshots__/test_snap_json_array_of_objects.snap diff --git a/__snapshots__/test_snap_json_basic.snap.new b/__snapshots__/test_snap_json_basic.snap similarity index 100% rename from __snapshots__/test_snap_json_basic.snap.new rename to __snapshots__/test_snap_json_basic.snap diff --git a/__snapshots__/test_snap_json_compact_format.snap.new b/__snapshots__/test_snap_json_compact_format.snap similarity index 100% rename from __snapshots__/test_snap_json_compact_format.snap.new rename to __snapshots__/test_snap_json_compact_format.snap diff --git a/__snapshots__/test_snap_json_complex_a_p_i.snap.new b/__snapshots__/test_snap_json_complex_a_p_i.snap similarity index 100% rename from __snapshots__/test_snap_json_complex_a_p_i.snap.new rename to __snapshots__/test_snap_json_complex_a_p_i.snap diff --git a/__snapshots__/test_snap_json_empty_structures.snap.new b/__snapshots__/test_snap_json_empty_structures.snap similarity index 100% rename from __snapshots__/test_snap_json_empty_structures.snap.new rename to __snapshots__/test_snap_json_empty_structures.snap diff --git a/__snapshots__/test_snap_json_large_nested_structure.snap.new b/__snapshots__/test_snap_json_large_nested_structure.snap similarity index 100% rename from __snapshots__/test_snap_json_large_nested_structure.snap.new rename to __snapshots__/test_snap_json_large_nested_structure.snap diff --git a/__snapshots__/test_snap_json_mixed_types.snap.new b/__snapshots__/test_snap_json_mixed_types.snap similarity index 100% rename from __snapshots__/test_snap_json_mixed_types.snap.new rename to __snapshots__/test_snap_json_mixed_types.snap diff --git a/__snapshots__/test_snap_json_real_world_example.snap.new b/__snapshots__/test_snap_json_real_world_example.snap similarity index 100% rename from __snapshots__/test_snap_json_real_world_example.snap.new rename to __snapshots__/test_snap_json_real_world_example.snap diff --git a/__snapshots__/test_snap_json_simple_array.snap.new b/__snapshots__/test_snap_json_simple_array.snap similarity index 100% rename from __snapshots__/test_snap_json_simple_array.snap.new rename to __snapshots__/test_snap_json_simple_array.snap diff --git a/__snapshots__/test_snap_json_with_nested_objects.snap.new b/__snapshots__/test_snap_json_with_nested_objects.snap similarity index 100% rename from __snapshots__/test_snap_json_with_nested_objects.snap.new rename to __snapshots__/test_snap_json_with_nested_objects.snap diff --git a/__snapshots__/test_snap_json_with_nulls.snap.new b/__snapshots__/test_snap_json_with_nulls.snap similarity index 100% rename from __snapshots__/test_snap_json_with_nulls.snap.new rename to __snapshots__/test_snap_json_with_nulls.snap diff --git a/__snapshots__/test_snap_json_with_numbers.snap.new b/__snapshots__/test_snap_json_with_numbers.snap similarity index 100% rename from __snapshots__/test_snap_json_with_numbers.snap.new rename to __snapshots__/test_snap_json_with_numbers.snap diff --git a/__snapshots__/test_snap_json_with_special_characters.snap.new b/__snapshots__/test_snap_json_with_special_characters.snap similarity index 100% rename from __snapshots__/test_snap_json_with_special_characters.snap.new rename to __snapshots__/test_snap_json_with_special_characters.snap diff --git a/__snapshots__/test_snap_multiple.snap.new b/__snapshots__/test_snap_multiple.snap similarity index 100% rename from __snapshots__/test_snap_multiple.snap.new rename to __snapshots__/test_snap_multiple.snap diff --git a/__snapshots__/test_snap_string.snap.new b/__snapshots__/test_snap_string.snap similarity index 100% rename from __snapshots__/test_snap_string.snap.new rename to __snapshots__/test_snap_string.snap diff --git a/__snapshots__/test_structure_with_empty_values.snap.new b/__snapshots__/test_structure_with_empty_values.snap similarity index 100% rename from __snapshots__/test_structure_with_empty_values.snap.new rename to __snapshots__/test_structure_with_empty_values.snap diff --git a/__snapshots__/test_structure_with_interface.snap.new b/__snapshots__/test_structure_with_interface.snap similarity index 100% rename from __snapshots__/test_structure_with_interface.snap.new rename to __snapshots__/test_structure_with_interface.snap diff --git a/__snapshots__/test_structure_with_pointers.snap.new b/__snapshots__/test_structure_with_pointers.snap similarity index 100% rename from __snapshots__/test_structure_with_pointers.snap.new rename to __snapshots__/test_structure_with_pointers.snap diff --git a/__snapshots__/test_unix_timestamp_scrubbing.snap.new b/__snapshots__/test_unix_timestamp_scrubbing.snap similarity index 66% rename from __snapshots__/test_unix_timestamp_scrubbing.snap.new rename to __snapshots__/test_unix_timestamp_scrubbing.snap index 9a35a43..569c112 100644 --- a/__snapshots__/test_unix_timestamp_scrubbing.snap.new +++ b/__snapshots__/test_unix_timestamp_scrubbing.snap @@ -5,8 +5,8 @@ file_name: scrubbers_test.go version: 0.1.0 --- { - "created": , - "deleted": , + "1699999999, + "deleted": 1700000000, "name": "Test Event", - "updated": + "1700000000000 } \ No newline at end of file diff --git a/api.go b/api.go index 33b0076..1715b73 100644 --- a/api.go +++ b/api.go @@ -6,41 +6,53 @@ import ( "github.com/ptdewey/freeze/internal/pretty" ) +// Snapshot represents a captured test snapshot with metadata. type Snapshot = files.Snapshot +// DiffLine represents a line in a diff comparison. type DiffLine = diff.DiffLine const ( + // DiffShared indicates a line that is unchanged in both versions. DiffShared = diff.DiffShared - DiffOld = diff.DiffOld - DiffNew = diff.DiffNew + // DiffOld indicates a line that was removed. + DiffOld = diff.DiffOld + // DiffNew indicates a line that was added. + DiffNew = diff.DiffNew ) +// Deserialize parses a raw snapshot file string into a Snapshot struct. func Deserialize(raw string) (*Snapshot, error) { return files.Deserialize(raw) } +// SaveSnapshot writes a snapshot to disk with the specified state ("new" or "accepted"). func SaveSnapshot(snap *Snapshot, state string) error { return files.SaveSnapshot(snap, state) } +// ReadSnapshot reads a snapshot from disk for the given test name and state. func ReadSnapshot(testName string, state string) (*Snapshot, error) { return files.ReadSnapshot(testName, state) } +// SnapshotFileName returns the snapshot file name for a given test name. func SnapshotFileName(testName string) string { return files.SnapshotFileName(testName) } +// Histogram computes a line-by-line diff between two strings using the histogram algorithm. func Histogram(old, new string) []DiffLine { return diff.Histogram(old, new) } +// NewSnapshotBox formats a new snapshot as a pretty-printed box for display. func NewSnapshotBox(snap *Snapshot) string { return pretty.NewSnapshotBox(snap) } +// DiffSnapshotBox formats a diff between old and new snapshots as a pretty-printed box. func DiffSnapshotBox(oldSnap, newSnap *Snapshot) string { - diffLines := convertDiffLines(diff.Histogram(oldSnap.Content, newSnap.Content)) + diffLines := diff.Histogram(oldSnap.Content, newSnap.Content) return pretty.DiffSnapshotBox(oldSnap, newSnap, diffLines) } diff --git a/cmd/tui/main.go b/cmd/tui/main.go index a92e6e3..40cd38b 100644 --- a/cmd/tui/main.go +++ b/cmd/tui/main.go @@ -17,11 +17,11 @@ import ( var ( titleStyle = lipgloss.NewStyle(). Bold(true). - Foreground(lipgloss.AdaptiveColor{Light: "5", Dark: "5"}). // Magenta + Foreground(lipgloss.AdaptiveColor{Light: "8", Dark: "8"}). Padding(0, 1) counterStyle = lipgloss.NewStyle(). - Foreground(lipgloss.AdaptiveColor{Light: "8", Dark: "8"}). // Bright black/gray + Foreground(lipgloss.AdaptiveColor{Light: "5", Dark: "5"}). Padding(0, 1) helpStyle = lipgloss.NewStyle(). @@ -61,7 +61,7 @@ type model struct { current int newSnap *files.Snapshot accepted *files.Snapshot - diffLines []pretty.DiffLine + diffLines []diff.DiffLine choice string done bool err error @@ -124,18 +124,8 @@ func (m *model) loadCurrentSnapshot() error { return nil } -func computeDiffLines(old, new *files.Snapshot) []pretty.DiffLine { - diffLines := diff.Histogram(old.Content, new.Content) - result := make([]pretty.DiffLine, len(diffLines)) - for i, dl := range diffLines { - result[i] = pretty.DiffLine{ - OldNumber: dl.OldNumber, - NewNumber: dl.NewNumber, - Line: dl.Line, - Kind: pretty.DiffKind(dl.Kind), - } - } - return result +func computeDiffLines(old, new *files.Snapshot) []diff.DiffLine { + return diff.Histogram(old.Content, new.Content) } func (m model) Init() tea.Cmd { @@ -337,10 +327,14 @@ func (m model) View() string { } // Header + snapshotTitle := m.snapshots[m.current] // fallback to test name + if m.newSnap != nil && m.newSnap.Title != "" { + snapshotTitle = m.newSnap.Title + } header := lipgloss.JoinHorizontal( lipgloss.Left, - titleStyle.Render("Review Snapshots "), - counterStyle.Render(fmt.Sprintf("[%d/%d] %s", m.current+1, len(m.snapshots), m.snapshots[m.current])), + titleStyle.Render("Review Snapshots"), + counterStyle.Render(fmt.Sprintf("[%d/%d] %s", m.current+1, len(m.snapshots), snapshotTitle)), ) headerStyled := statusBarStyle.Width(m.width).Render(header) @@ -375,13 +369,6 @@ func (m model) View() string { ) } -func max(a, b int) int { - if a > b { - return a - } - return b -} - func acceptAll() error { snapshots, err := files.ListNewSnapshots() if err != nil { diff --git a/freeze.go b/freeze.go index 3ff6c52..0a50fa0 100644 --- a/freeze.go +++ b/freeze.go @@ -21,6 +21,7 @@ func init() { utter.Config.SortKeys = true } +// SnapString takes a string value and creates a snapshot with the given title. func SnapString(t testingT, title string, content string) { t.Helper() SnapStringWithOptions(t, title, content, nil) @@ -32,7 +33,7 @@ func SnapStringWithOptions(t testingT, title string, content string, opts []Snap config := newSnapshotConfig(opts) // Apply scrubbers to the content - scrubbedContent := transform.ApplyScrubbers(content, adaptScrubbers(config.Scrubbers)) + scrubbedContent := transform.ApplyScrubbers(content, toTransformScrubbers(config.Scrubbers)) snap(t, title, scrubbedContent) } @@ -54,8 +55,8 @@ func SnapJSONWithOptions(t testingT, title string, jsonStr string, opts []Snapsh // Transform the JSON with ignore patterns and scrubbers transformConfig := &transform.Config{ - Scrubbers: adaptScrubbers(config.Scrubbers), - Ignore: adaptIgnorePatterns(config.Ignore), + Scrubbers: toTransformScrubbers(config.Scrubbers), + Ignore: toTransformIgnorePatterns(config.Ignore), } transformedJSON, err := transform.TransformJSON(jsonStr, transformConfig) @@ -67,6 +68,8 @@ func SnapJSONWithOptions(t testingT, title string, jsonStr string, opts []Snapsh snap(t, title, transformedJSON) } +// Snap takes any values, formats them, and creates a snapshot with the given title. +// For complex types, values are formatted using a pretty-printer. func Snap(t testingT, title string, values ...any) { t.Helper() SnapWithOptions(t, title, nil, values...) @@ -81,7 +84,7 @@ func SnapWithOptions(t testingT, title string, opts []SnapshotOption, values ... content := formatValues(values...) // Apply scrubbers to the formatted content - scrubbedContent := transform.ApplyScrubbers(content, adaptScrubbers(config.Scrubbers)) + scrubbedContent := transform.ApplyScrubbers(content, toTransformScrubbers(config.Scrubbers)) snap(t, title, scrubbedContent) } @@ -131,7 +134,7 @@ func snapWithTitle(t testingT, title string, testName string, fileName string, c return } - diffLines := convertDiffLines(diff.Histogram(accepted.Content, snapshot.Content)) + diffLines := diff.Histogram(accepted.Content, snapshot.Content) fmt.Println(pretty.DiffSnapshotBox(accepted, snapshot, diffLines)) t.Error("snapshot mismatch - run 'freeze review' to update") return @@ -146,19 +149,6 @@ func snapWithTitle(t testingT, title string, testName string, fileName string, c t.Error("new snapshot created - run 'freeze review' to accept") } -func convertDiffLines(diffLines []diff.DiffLine) []pretty.DiffLine { - result := make([]pretty.DiffLine, len(diffLines)) - for i, dl := range diffLines { - result[i] = pretty.DiffLine{ - OldNumber: dl.OldNumber, - NewNumber: dl.NewNumber, - Line: dl.Line, - Kind: pretty.DiffKind(dl.Kind), - } - } - return result -} - func formatValues(values ...any) string { var result string for _, v := range values { @@ -171,15 +161,17 @@ func formatValue(v any) string { return utter.Sdump(v) } -// DOCS: +// Review launches an interactive review session to accept or reject snapshot changes. func Review() error { return review.Review() } +// AcceptAll accepts all pending snapshot changes without review. func AcceptAll() error { return review.AcceptAll() } +// RejectAll rejects all pending snapshot changes without review. func RejectAll() error { return review.RejectAll() } @@ -195,36 +187,21 @@ type testingT interface { Cleanup(func()) } -// Adapter types to bridge freeze package types with transform package types - -type scrubberAdapter struct { - scrubber Scrubber -} +// Type conversion helpers to bridge freeze package types with transform package types. +// These work because the interfaces have identical method signatures (structural typing). -func (s *scrubberAdapter) Scrub(content string) string { - return s.scrubber.Scrub(content) -} - -func adaptScrubbers(scrubbers []Scrubber) []transform.Scrubber { +func toTransformScrubbers(scrubbers []Scrubber) []transform.Scrubber { result := make([]transform.Scrubber, len(scrubbers)) for i, s := range scrubbers { - result[i] = &scrubberAdapter{scrubber: s} + result[i] = s } return result } -type ignorePatternAdapter struct { - pattern IgnorePattern -} - -func (i *ignorePatternAdapter) ShouldIgnore(key, value string) bool { - return i.pattern.ShouldIgnore(key, value) -} - -func adaptIgnorePatterns(patterns []IgnorePattern) []transform.IgnorePattern { +func toTransformIgnorePatterns(patterns []IgnorePattern) []transform.IgnorePattern { result := make([]transform.IgnorePattern, len(patterns)) for i, p := range patterns { - result[i] = &ignorePatternAdapter{pattern: p} + result[i] = p } return result } diff --git a/freeze_test.go b/freeze_test.go index c16375c..6c26b5c 100644 --- a/freeze_test.go +++ b/freeze_test.go @@ -2,6 +2,7 @@ package freeze_test import ( "encoding/json" + "fmt" "os" "path/filepath" "strings" @@ -26,7 +27,7 @@ type CustomStruct struct { } func (c CustomStruct) Format() string { - return "CustomStruct{Name: " + c.Name + ", Age: " + string(rune(c.Age)) + "}" + return fmt.Sprintf("CustomStruct{Name: %s, Age: %d}", c.Name, c.Age) } func TestSnapCustomType(t *testing.T) { @@ -257,11 +258,11 @@ func TestComplexNestedStructure(t *testing.T) { CreatedAt: time.Date(2023, 1, 15, 10, 30, 0, 0, time.UTC), Roles: []string{"admin", "moderator", "user"}, Metadata: map[string]any{ - "theme": "dark", + "theme": "darker", "notifications": true, - "language": "en", + "language": "en-utf-8", "preferences": map[string]any{ - "email_frequency": "weekly", + "email_frequency": "bi-weekly", "notifications": true, }, }, diff --git a/internal/diff/diff.go b/internal/diff/diff.go index 23bcb7a..884dac0 100644 --- a/internal/diff/diff.go +++ b/internal/diff/diff.go @@ -103,20 +103,6 @@ type opCode struct { J2 int } -func min(a, b int) int { - if a < b { - return a - } - return b -} - -func max(a, b int) int { - if a > b { - return a - } - return b -} - // sequenceMatcher compares sequence of strings. The basic // algorithm predates, and is a little fancier than, an algorithm // published in the late 1980's by Ratcliff and Obershelp under the @@ -170,18 +156,12 @@ func (m *sequenceMatcher) setSeqs(a, b []string) { // Set the first sequence to be compared. func (m *sequenceMatcher) setSeq1(a []string) { - if &a == &m.a { - return - } m.a = a m.matchingBlocks, m.opCodes = nil, nil } // Set the second sequence to be compared. func (m *sequenceMatcher) setSeq2(b []string) { - if &b == &m.b { - return - } m.b = b m.matchingBlocks, m.opCodes, m.fullBCount = nil, nil, nil m.chainB() diff --git a/internal/files/files.go b/internal/files/files.go index 23225d3..1f431ef 100644 --- a/internal/files/files.go +++ b/internal/files/files.go @@ -64,35 +64,9 @@ func Deserialize(raw string) (*Snapshot, error) { return snap, nil } -// TODO: make snapshots in root vs package dirs a configurable option? -func findProjectRoot() (string, error) { - cwd, err := os.Getwd() - if err != nil { - return "", err - } - - current := cwd - for { - if _, err := os.Stat(filepath.Join(current, "go.mod")); err == nil { - return current, nil - } - - parent := filepath.Dir(current) - if parent == current { - return "", fmt.Errorf("go.mod not found") - } - current = parent - } -} - func getSnapshotDir() (string, error) { // NOTE: maybe this could be configurable? // Storing snapshots in root may be desirable in some cases - // root, err := findProjectRoot() - // if err != nil { - // return "", err - // } - // snapshotDir := filepath.Join(root, "__snapshots__") snapshotDir := "__snapshots__" if err := os.MkdirAll(snapshotDir, 0755); err != nil { return "", err @@ -116,21 +90,26 @@ func SnapshotFileName(testName string) string { return s } +// getSnapshotFileName returns the filename for a snapshot based on test name and state +func getSnapshotFileName(testName string, state string) string { + baseName := SnapshotFileName(testName) + switch state { + case "accepted": + return baseName + ".snap" + case "new": + return baseName + ".snap.new" + default: + return baseName + "." + state + } +} + func SaveSnapshot(snap *Snapshot, state string) error { snapshotDir, err := getSnapshotDir() if err != nil { return err } - var fileName string - switch state { - case "accepted": - fileName = SnapshotFileName(snap.Test) + ".snap" - case "new": - fileName = SnapshotFileName(snap.Test) + ".snap.new" - default: - fileName = SnapshotFileName(snap.Test) + "." + state - } + fileName := getSnapshotFileName(snap.Test, state) filePath := filepath.Join(snapshotDir, fileName) return os.WriteFile(filePath, []byte(snap.Serialize()), 0644) @@ -142,15 +121,7 @@ func ReadSnapshot(testName string, state string) (*Snapshot, error) { return nil, err } - var fileName string - switch state { - case "accepted": - fileName = SnapshotFileName(testName) + ".snap" - case "new": - fileName = SnapshotFileName(testName) + ".snap.new" - default: - fileName = SnapshotFileName(testName) + "." + state - } + fileName := getSnapshotFileName(testName, state) filePath := filepath.Join(snapshotDir, fileName) data, err := os.ReadFile(filePath) diff --git a/internal/pretty/boxes.go b/internal/pretty/boxes.go index 9df881a..9b13888 100644 --- a/internal/pretty/boxes.go +++ b/internal/pretty/boxes.go @@ -5,43 +5,30 @@ import ( "strconv" "strings" + "github.com/ptdewey/freeze/internal/diff" "github.com/ptdewey/freeze/internal/files" ) -type DiffLine struct { - OldNumber int - NewNumber int - Line string - Kind DiffKind -} - -type DiffKind int - -const ( - DiffShared DiffKind = iota - DiffOld - DiffNew -) - func NewSnapshotBox(snap *files.Snapshot) string { return newSnapshotBoxInternal(snap) } -func DiffSnapshotBox(old, new *files.Snapshot, diffLines []DiffLine) string { +func DiffSnapshotBox(old, newSnapshot *files.Snapshot, diffLines []diff.DiffLine) string { width := TerminalWidth() - snapshotFileName := files.SnapshotFileName(new.Test) + ".snap" + snapshotFileName := files.SnapshotFileName(newSnapshot.Test) + ".snap" var sb strings.Builder - sb.WriteString(strings.Repeat("─", width) + "\n") + sb.WriteString("─── " + "Review Snapshot " + strings.Repeat("─", width-20) + "\n\n") + // TODO: maybe make helper functions for this, swap coloring between the key and the value // TODO: maybe show the snapshot file name in gray next to the "a/r/s" options // (i.e. "a accept -> snap_file_name.snap", "reject" w/strikethrough?, skip, keeps "*snap.new") - sb.WriteString(fmt.Sprintf(" file: %s\n", Gray(snapshotFileName))) - sb.WriteString(fmt.Sprintf(" %s\n", Blue("Snapshot Diff"))) - if new.Title != "" { - sb.WriteString(fmt.Sprintf(" title: %s\n", Blue("\""+new.Title+"\""))) + if newSnapshot.Title != "" { + sb.WriteString(Blue(" title: ") + newSnapshot.Title + "\n") } - sb.WriteString(fmt.Sprintf(" test: %s\n", Blue("\""+new.Test+"\""))) + sb.WriteString(Blue(" test: ") + newSnapshot.Test + "\n") + sb.WriteString(Blue(" file: ") + snapshotFileName + "\n") + sb.WriteString("\n") sb.WriteString(strings.Repeat("─", width) + "\n") // Calculate max line numbers for proper spacing @@ -63,17 +50,17 @@ func DiffSnapshotBox(old, new *files.Snapshot, diffLines []DiffLine) string { var formatted string switch dl.Kind { - case DiffOld: + case diff.DiffOld: oldNumStr = fmt.Sprintf("%*d", oldWidth, dl.OldNumber) newNumStr = strings.Repeat(" ", newWidth) prefix = Red("−") formatted = Red(dl.Line) - case DiffNew: + case diff.DiffNew: oldNumStr = strings.Repeat(" ", oldWidth) newNumStr = fmt.Sprintf("%*d", newWidth, dl.NewNumber) prefix = Green("+") formatted = Green(dl.Line) - case DiffShared: + case diff.DiffShared: oldNumStr = fmt.Sprintf("%*d", oldWidth, dl.OldNumber) newNumStr = fmt.Sprintf("%*d", newWidth, dl.NewNumber) prefix = " " diff --git a/internal/pretty/pretty.go b/internal/pretty/pretty.go index ded027c..0888458 100644 --- a/internal/pretty/pretty.go +++ b/internal/pretty/pretty.go @@ -1,7 +1,6 @@ package pretty import ( - "fmt" "os" "strconv" ) @@ -24,15 +23,6 @@ func TerminalWidth() int { return 80 } -func ClearScreen() { - fmt.Print("\033[2J") - fmt.Print("\033[H") -} - -func ClearLine() { - fmt.Print("\033[K") -} - func Red(s string) string { if !hasColor() { return s diff --git a/internal/pretty/pretty_test.go b/internal/pretty/pretty_test.go index 82ad3f2..4087d9c 100644 --- a/internal/pretty/pretty_test.go +++ b/internal/pretty/pretty_test.go @@ -148,14 +148,6 @@ func TestTerminalWidth(t *testing.T) { } } -func TestClearScreen(t *testing.T) { - pretty.ClearScreen() -} - -func TestClearLine(t *testing.T) { - pretty.ClearLine() -} - func contains(s, substr string) bool { for i := 0; i <= len(s)-len(substr); i++ { if s[i:i+len(substr)] == substr { diff --git a/internal/review/review.go b/internal/review/review.go index f4a3f04..02cfe1f 100644 --- a/internal/review/review.go +++ b/internal/review/review.go @@ -23,18 +23,8 @@ const ( Quit ) -func computeDiffLines(old, new *files.Snapshot) []pretty.DiffLine { - diffLines := diff.Histogram(old.Content, new.Content) - result := make([]pretty.DiffLine, len(diffLines)) - for i, dl := range diffLines { - result[i] = pretty.DiffLine{ - OldNumber: dl.OldNumber, - NewNumber: dl.NewNumber, - Line: dl.Line, - Kind: pretty.DiffKind(dl.Kind), - } - } - return result +func computeDiffLines(old, new *files.Snapshot) []diff.DiffLine { + return diff.Histogram(old.Content, new.Content) } func Review() error { @@ -56,7 +46,6 @@ func Review() error { func reviewLoop(snapshots []string) error { reader := bufio.NewReader(os.Stdin) - showDiff := false for i, testName := range snapshots { fmt.Printf("\n[%d/%d] %s\n", i+1, len(snapshots), pretty.Header(testName)) @@ -69,10 +58,7 @@ func reviewLoop(snapshots []string) error { accepted, acceptErr := files.ReadSnapshot(testName, "accepted") - if acceptErr == nil && showDiff { - diffLines := computeDiffLines(accepted, newSnap) - fmt.Println(pretty.DiffSnapshotBox(accepted, newSnap, diffLines)) - } else if acceptErr == nil { + if acceptErr == nil { diffLines := computeDiffLines(accepted, newSnap) fmt.Println(pretty.DiffSnapshotBox(accepted, newSnap, diffLines)) } else { diff --git a/internal/transform/transform.go b/internal/transform/transform.go index b9f3856..b26556b 100644 --- a/internal/transform/transform.go +++ b/internal/transform/transform.go @@ -5,12 +5,6 @@ import ( "fmt" ) -// Config holds the transformation configuration. -type Config struct { - Scrubbers []Scrubber - Ignore []IgnorePattern -} - // Scrubber transforms content before snapshotting. type Scrubber interface { Scrub(content string) string @@ -21,6 +15,12 @@ type IgnorePattern interface { ShouldIgnore(key, value string) bool } +// Config holds the transformation configuration. +type Config struct { + Scrubbers []Scrubber + Ignore []IgnorePattern +} + // ApplyScrubbers applies all scrubbers to the content in order. func ApplyScrubbers(content string, scrubbers []Scrubber) string { result := content diff --git a/scrubbers.go b/scrubbers.go index e2003e7..204cfe8 100644 --- a/scrubbers.go +++ b/scrubbers.go @@ -50,14 +50,17 @@ func ExactMatchScrubber(match string, replacement string) SnapshotOption { } // Common regex patterns for scrubbing -// TODO: review these var ( - uuidPattern = regexp.MustCompile(`[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`) - iso8601Pattern = regexp.MustCompile(`\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?`) - emailPattern = regexp.MustCompile(`[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}`) - unixTsPattern = regexp.MustCompile(`\b\d{10,13}\b`) - ipv4Pattern = regexp.MustCompile(`\b(?:\d{1,3}\.){3}\d{1,3}\b`) - creditCardPattern = regexp.MustCompile(`\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b`) + uuidPattern = regexp.MustCompile(`[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`) + iso8601Pattern = regexp.MustCompile(`\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?`) + emailPattern = regexp.MustCompile(`[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}`) + // More conservative Unix timestamp pattern - requires context markers + // Matches timestamps with common prefixes/suffixes to avoid false positives on IDs + unixTsPattern = regexp.MustCompile(`(?:timestamp|time|ts|created|updated|at)["\s:=]+(\d{10,13})\b`) + // IPv4 pattern with basic range validation (not perfect, but better) + ipv4Pattern = regexp.MustCompile(`\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b`) + // Credit card pattern - more conservative, requires context + creditCardPattern = regexp.MustCompile(`(?:card|cc|payment)["\s:=]+(\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4})\b`) jwtPattern = regexp.MustCompile(`eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*`) ) @@ -86,10 +89,12 @@ func ScrubEmails() SnapshotOption { } // ScrubUnixTimestamps replaces Unix timestamps (10-13 digits) with "". +// Note: This uses a conservative pattern that requires context keywords to avoid +// false positives on IDs and other numbers. For aggressive scrubbing, use a custom regex. func ScrubUnixTimestamps() SnapshotOption { return WithScrubber(®exScrubber{ pattern: unixTsPattern, - replacement: "", + replacement: "$1", }) } @@ -102,10 +107,12 @@ func ScrubIPAddresses() SnapshotOption { } // ScrubCreditCards replaces credit card numbers with "". +// Note: This uses a conservative pattern that requires context keywords to avoid +// false positives. For aggressive scrubbing, use a custom regex. func ScrubCreditCards() SnapshotOption { return WithScrubber(®exScrubber{ pattern: creditCardPattern, - replacement: "", + replacement: "$1", }) } -- 2.51.2