@@ -1292,6 +1292,93 @@ public async Task BundleChangelogs_WithResolve_CopiesChangelogContents()
12921292 bundleContent . Should ( ) . Contain ( "description: This is a test feature" ) ;
12931293 }
12941294
1295+ [ Fact ]
1296+ public async Task BundleChangelogs_WithResolve_PreservesSpecialCharactersInUtf8 ( )
1297+ {
1298+ // Arrange - Create changelog with special characters that could be corrupted
1299+ // These characters were reported as being corrupted to "&o0" and "*o0" in the original issue
1300+
1301+ // language=yaml
1302+ var changelog1 =
1303+ """
1304+ title: Feature with special characters & symbols
1305+ type: feature
1306+ products:
1307+ - product: elasticsearch
1308+ target: 9.3.0
1309+ lifecycle: ga
1310+ pr: https://github.com/elastic/elasticsearch/pull/100
1311+ description: |
1312+ This feature includes special characters:
1313+ - Ampersand: & symbol
1314+ - Asterisk: * symbol
1315+ - Other special chars: < > " ' / \
1316+ - Unicode: © ® ™ € £ ¥
1317+ """ ;
1318+
1319+ var file1 = FileSystem . Path . Combine ( _changelogDir , "1755268130-special-chars.yaml" ) ;
1320+ await FileSystem . File . WriteAllTextAsync ( file1 , changelog1 , System . Text . Encoding . UTF8 , TestContext . Current . CancellationToken ) ;
1321+
1322+ var outputPath = FileSystem . Path . Combine ( FileSystem . Path . GetTempPath ( ) , Guid . NewGuid ( ) . ToString ( ) , "bundle.yaml" ) ;
1323+ var input = new BundleChangelogsArguments
1324+ {
1325+ Directory = _changelogDir ,
1326+ All = true ,
1327+ Resolve = true ,
1328+ Output = outputPath
1329+ } ;
1330+
1331+ // Act
1332+ var result = await Service . BundleChangelogs ( Collector , input , TestContext . Current . CancellationToken ) ;
1333+
1334+ // Assert
1335+ result . Should ( ) . BeTrue ( ) ;
1336+ Collector . Errors . Should ( ) . Be ( 0 ) ;
1337+
1338+ // Read the bundle file with explicit UTF-8 encoding
1339+ var bundleContent = await FileSystem . File . ReadAllTextAsync ( input . Output , System . Text . Encoding . UTF8 , TestContext . Current . CancellationToken ) ;
1340+
1341+ // Verify special characters are preserved correctly (not corrupted)
1342+ // The original issue reported "&o0" and "*o0" corruption, so we verify the characters are correct
1343+ bundleContent . Should ( ) . Contain ( "&" ) ; // Ampersand should be preserved
1344+ bundleContent . Should ( ) . Contain ( "Feature with special characters & symbols" ) ; // Ampersand in title
1345+ bundleContent . Should ( ) . Contain ( "Ampersand: & symbol" ) ; // Ampersand in description
1346+
1347+ // Check that asterisk appears correctly (not corrupted to "*o0")
1348+ bundleContent . Should ( ) . Contain ( "*" ) ; // Asterisk should be preserved
1349+ bundleContent . Should ( ) . Contain ( "Asterisk: * symbol" ) ; // Asterisk in description
1350+
1351+ // Verify the ampersand and asterisk are not corrupted
1352+ // The corruption pattern would be "&o0" or "*o0" appearing where we expect "&" or "*"
1353+ // We check that the title contains the correct pattern, not the corrupted one
1354+ var titleLine = bundleContent . Split ( '\n ' ) . FirstOrDefault ( l => l . Contains ( "title:" ) ) ;
1355+ titleLine . Should ( ) . NotBeNull ( ) ;
1356+ titleLine . Should ( ) . Contain ( "&" ) ;
1357+ titleLine . Should ( ) . NotContain ( "&o0" ) ; // Should not be corrupted in title
1358+
1359+ // Verify no corruption patterns exist (these would indicate encoding issues)
1360+ bundleContent . Should ( ) . NotContain ( "&o0" ) ; // Should not contain corrupted ampersand
1361+ bundleContent . Should ( ) . NotContain ( "*o0" ) ; // Should not contain corrupted asterisk
1362+
1363+ // Verify other special characters are preserved
1364+ bundleContent . Should ( ) . Contain ( "<" ) ;
1365+ bundleContent . Should ( ) . Contain ( ">" ) ;
1366+ bundleContent . Should ( ) . Contain ( "\" " ) ;
1367+
1368+ // Verify Unicode characters are preserved
1369+ bundleContent . Should ( ) . Contain ( "©" ) ;
1370+ bundleContent . Should ( ) . Contain ( "®" ) ;
1371+ bundleContent . Should ( ) . Contain ( "™" ) ;
1372+ bundleContent . Should ( ) . Contain ( "€" ) ;
1373+
1374+ // Verify the content structure is correct
1375+ bundleContent . Should ( ) . Contain ( "title: Feature with special characters & symbols" ) ;
1376+ bundleContent . Should ( ) . Contain ( "type: feature" ) ;
1377+ bundleContent . Should ( ) . Contain ( "product: elasticsearch" ) ;
1378+ bundleContent . Should ( ) . Contain ( "target: 9.3.0" ) ;
1379+ bundleContent . Should ( ) . Contain ( "lifecycle: ga" ) ;
1380+ }
1381+
12951382 [ Fact ]
12961383 public async Task BundleChangelogs_WithDirectoryOutputPath_CreatesDefaultFilename ( )
12971384 {
0 commit comments