@@ -208,6 +208,84 @@ def test_scanpipe_pipes_outputs_to_json(self):
208208 output_file = output .to_json (project = project )
209209 self .assertIn (output_file .name , project .output_root )
210210
211+ def test_scanpipe_pipes_outputs_to_json_strips_symbols (self ):
212+ """Test that JSON output removes symbols from resources and messages."""
213+ project = make_project (name = "SymbolTest" )
214+
215+ resource_data = {
216+ "path" : "test.js" ,
217+ "extra_data" : {
218+ "source_symbols" : ["func1" , "func2" ],
219+ "source_strings" : ["hello world" ],
220+ "source_comments" : ["// test" ],
221+ "other_field" : "should_remain"
222+ }
223+ }
224+ resource = CodebaseResource .objects .create (project = project , ** resource_data )
225+
226+ message_details = {
227+ "source_symbols" : ["sym1" ],
228+ "resource_path" : "test.js"
229+ }
230+ make_message (project , details = message_details , description = "Test message" )
231+
232+ output_file = output .to_json (project = project )
233+ with output_file .open () as f :
234+ results = json .loads (f .read ())
235+
236+ resource_output = results ["files" ][0 ]
237+ self .assertNotIn ("source_symbols" , resource_output ["extra_data" ])
238+ self .assertNotIn ("source_strings" , resource_output ["extra_data" ])
239+ self .assertNotIn ("source_comments" , resource_output ["extra_data" ])
240+ self .assertIn ("other_field" , resource_output ["extra_data" ])
241+ self .assertEqual ("should_remain" , resource_output ["extra_data" ]["other_field" ])
242+
243+ message_output = results ["headers" ][0 ]["messages" ][0 ]
244+ self .assertNotIn ("source_symbols" , message_output ["details" ])
245+ self .assertIn ("resource_path" , message_output ["details" ])
246+
247+ resource .refresh_from_db ()
248+ self .assertIn ("source_symbols" , resource .extra_data )
249+
250+ def test_scanpipe_pipes_outputs_to_symbols_json (self ):
251+ project = make_project (name = "SymbolsOnly" )
252+ CodebaseResource .objects .create (
253+ project = project ,
254+ path = "has_symbols.js" ,
255+ extra_data = {
256+ "source_symbols" : ["func1" , "func2" ],
257+ "source_strings" : ["test" ],
258+ "other_data" : "value"
259+ }
260+ )
261+ CodebaseResource .objects .create (
262+ project = project ,
263+ path = "no_symbols.txt" ,
264+ extra_data = {"other_data" : "value" }
265+ )
266+ output_file = output .to_symbols_json (project )
267+ with output_file .open () as f :
268+ results = json .loads (f .read ())
269+
270+ self .assertIn ("headers" , results )
271+ self .assertIn ("files" , results )
272+
273+ # Should only have 1 file (the one with symbols)
274+ self .assertEqual (1 , len (results ["files" ]))
275+
276+ # Verify it's the right file
277+ file_output = results ["files" ][0 ]
278+ self .assertEqual ("has_symbols.js" , file_output ["path" ])
279+
280+ # Verify symbols ARE included (not stripped)
281+ self .assertIn ("source_symbols" , file_output ["extra_data" ])
282+ self .assertEqual (["func1" , "func2" ], file_output ["extra_data" ]["source_symbols" ])
283+ self .assertIn ("source_strings" , file_output ["extra_data" ])
284+
285+ # Verify headers are present
286+ self .assertEqual (1 , len (results ["headers" ]))
287+ self .assertEqual ("scanpipe" , results ["headers" ][0 ]["tool_name" ])
288+
211289 def test_scanpipe_pipes_outputs_to_xlsx (self ):
212290 fixtures = self .data / "asgiref" / "asgiref-3.3.0_fixtures.json"
213291 call_command ("loaddata" , fixtures , ** {"verbosity" : 0 })
@@ -271,6 +349,54 @@ def test_scanpipe_pipes_outputs_get_xlsx_report(self):
271349 ]
272350 self .assertEqual (expected_sheet_names , workbook .sheetnames )
273351
352+ def test_scanpipe_pipes_outputs_to_xlsx_strips_symbols (self ):
353+ project = make_project (name = "SymbolXLSX" )
354+ CodebaseResource .objects .create (
355+ project = project ,
356+ path = "test.js" ,
357+ extra_data = {
358+ "source_symbols" : ["func1" , "func2" ],
359+ "source_strings" : ["hello" ],
360+ "source_comments" : ["// test" ],
361+ }
362+ )
363+
364+ message_details = {
365+ "source_symbols" : ["symbol1" , "symbol2" ],
366+ "source_strings" : ["string1" ],
367+ "source_comments" : ["# comment" ],
368+ "resource_path" : "test.js"
369+ }
370+ make_message (
371+ project ,
372+ model = "resource" ,
373+ details = message_details ,
374+ description = "Error with symbols"
375+ )
376+
377+ output_file = output .to_xlsx (project )
378+ workbook = openpyxl .load_workbook (output_file , read_only = True , data_only = True )
379+
380+ self .assertIn ("MESSAGES" , workbook .sheetnames )
381+ messages_sheet = workbook ["MESSAGES" ]
382+
383+ headers = [cell .value for cell in next (messages_sheet .iter_rows ())]
384+ self .assertIn ("details" , headers )
385+ details_col_index = headers .index ("details" )
386+
387+ rows = list (messages_sheet .iter_rows (min_row = 2 , max_row = 2 , values_only = True ))
388+ self .assertEqual (1 , len (rows ))
389+
390+ details_value = rows [0 ][details_col_index ]
391+
392+ if details_value :
393+ self .assertNotIn ("source_symbols" , details_value )
394+ self .assertNotIn ("source_strings" , details_value )
395+ self .assertNotIn ("source_comments" , details_value )
396+
397+ # Verify other fields ARE present
398+ self .assertIn ("resource_path" , details_value )
399+
274400 def test_scanpipe_pipes_outputs_get_xlsx_fields_order (self ):
275401 output_file = output .to_xlsx (project = make_project ())
276402 workbook = openpyxl .load_workbook (output_file , read_only = True , data_only = True )
@@ -717,6 +843,35 @@ def test_scanpipe_pipes_outputs_render_template_file(self):
717843 rendered = output .render_template_file (template_location , context )
718844 self .assertEqual ("value" , rendered )
719845
846+ def test_strip_symbols_function (self ):
847+ """Test that strip_symbols removes symbol keys without mutation."""
848+ original = {
849+ "path" : "test.py" ,
850+ "source_symbols" : ["foo" , "bar" ],
851+ "source_strings" : ["hello" ],
852+ "source_comments" : ["# comment" ],
853+ "other_data" : "keep_me"
854+ }
855+
856+ result = output .strip_symbols (original )
857+ # Verify symbols removed
858+ self .assertNotIn ("source_symbols" , result )
859+ self .assertNotIn ("source_strings" , result )
860+ self .assertNotIn ("source_comments" , result )
861+
862+ # Verify other data preserved
863+ self .assertEqual ("test.py" , result ["path" ])
864+ self .assertEqual ("keep_me" , result ["other_data" ])
865+
866+ # Verify original not mutated (important!)
867+ self .assertIn ("source_symbols" , original )
868+ self .assertEqual (["foo" , "bar" ], original ["source_symbols" ])
869+
870+ # Test edge cases
871+ self .assertIsNone (output .strip_symbols (None ))
872+ self .assertEqual ({}, output .strip_symbols ({}))
873+ self .assertEqual ("string" , output .strip_symbols ("string" ))
874+
720875 def test_scanpipe_pipes_outputs_get_attribution_template (self ):
721876 project = make_project (name = "Analysis" )
722877 template_location = str (output .get_attribution_template (project ))
0 commit comments