@@ -7575,6 +7575,94 @@ mod tests {
75757575 }
75767576 }
75777577
7578+ #[test]
7579+ fn nuget_shared_model_ignores_commented_sources() {
7580+ for sources in [
7581+ r#"<packageSources><!-- <add key="old" value="https://old.test" /> --></packageSources>"#,
7582+ r#"<!-- <packageSources><add key="old" value="https://old.test" /></packageSources> -->
7583+ <packageSources><add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
7584+ <!-- <add key="other" value="https://other.test" /> -->
7585+ </packageSources>"#,
7586+ ] {
7587+ let config = format!("<configuration>\n {sources}\n</configuration>\n");
7588+ let files = BTreeMap::from([("nuget.config".into(), config)]);
7589+ let result = rewrite_registry_redirect(&files, &[nuget_override()]);
7590+ let out = result.files.get("nuget.config").expect("config rewritten");
7591+ let parsed = crate::formats::nuget::parse_config(out).unwrap();
7592+ assert_eq!(
7593+ parsed
7594+ .sources
7595+ .iter()
7596+ .map(|(key, _)| key.as_str())
7597+ .collect::<Vec<_>>(),
7598+ ["socket-patch-uuid", "nuget.org"],
7599+ "only live sources receive mappings: {out}"
7600+ );
7601+ assert_eq!(
7602+ parsed.mappings,
7603+ [
7604+ ("socket-patch-uuid".into(), vec!["Newtonsoft.Json".into()]),
7605+ ("nuget.org".into(), vec!["*".into()]),
7606+ ],
7607+ "every catch-all must name a source NuGet reads: {out}"
7608+ );
7609+ }
7610+ }
7611+
7612+ #[test]
7613+ fn nuget_shared_model_never_splices_into_inert_markup() {
7614+ let fake = "<packageSources></packageSources><packageSourceMapping></packageSourceMapping>";
7615+ for inert in [
7616+ format!("<!-- é {fake} -->"),
7617+ format!("<![CDATA[{fake}]]>"),
7618+ format!("<?example {fake}?>"),
7619+ ] {
7620+ let config = format!(
7621+ "<configuration>\n {inert}\n <packageSources>\n \
7622+ <add key=\"nuget.org\" value=\"https://api.nuget.org/v3/index.json\" />\n \
7623+ </packageSources>\n</configuration>\n"
7624+ );
7625+ let files = BTreeMap::from([("nuget.config".into(), config)]);
7626+ let result = rewrite_registry_redirect(&files, &[nuget_override()]);
7627+ let out = result.files.get("nuget.config").expect("config rewritten");
7628+ assert!(out.contains(&inert), "inert bytes must be preserved: {out}");
7629+ let parsed = crate::formats::nuget::parse_config(out).unwrap();
7630+ assert!(parsed
7631+ .sources
7632+ .iter()
7633+ .any(|(key, _)| key == "socket-patch-uuid"));
7634+ assert!(parsed.mappings.iter().any(|(key, patterns)| {
7635+ key == "socket-patch-uuid" && patterns == &["Newtonsoft.Json"]
7636+ }));
7637+ let rerun = rewrite_registry_redirect(&result.files, &[nuget_override()]);
7638+ assert!(
7639+ rerun.files.is_empty() && rerun.edits.is_empty(),
7640+ "idempotent: {rerun:?}"
7641+ );
7642+ }
7643+ }
7644+
7645+ #[test]
7646+ fn nuget_shared_model_refuses_malformed_config_without_repinning_lock() {
7647+ for config in [
7648+ "<configuration><packageSources></configuration>",
7649+ "<configuration><!-- unterminated </configuration>",
7650+ // Even an apparently existing Socket source cannot bypass validation.
7651+ r#"<configuration><packageSources><add key="socket-patch-uuid" value="https://patch.test/nuget/index.json" /></packageSources>"#,
7652+ ] {
7653+ let files = BTreeMap::from([
7654+ ("nuget.config".into(), config.into()),
7655+ ("packages.lock.json".into(), r#"{"version":1,"dependencies":{"net8.0":{"Newtonsoft.Json":{"type":"Direct","resolved":"13.0.3","contentHash":"ORIGINAL"}}}}"#.into()),
7656+ ]);
7657+ let result = rewrite_registry_redirect(&files, &[nuget_override()]);
7658+ assert!(
7659+ result.files.is_empty() && result.edits.is_empty(),
7660+ "no half-write: {result:?}"
7661+ );
7662+ assert!(warning_codes(&result).contains(&"redirect_nuget_config_unwritable"));
7663+ }
7664+ }
7665+
75787666 /// Creating a `<packageSourceMapping>` from scratch: once ANY mapping
75797667 /// exists NuGet requires EVERY package to match some source's pattern, so
75807668 /// the rewriter must fan a `pattern="*"` mapping out to every pre-existing
0 commit comments