@@ -446,6 +446,56 @@ describe('updatePomFile', () => {
446446 expect ( parentBlock ) . toContain ( '<version>4.1.1</version>' ) ;
447447 } ) ;
448448
449+ it ( 'updates parent version in a deep sub-module whose parent is an intermediate aggregate pom' , ( ) => {
450+ // Regression test for spring-cloud-function-adapters:
451+ // spring-cloud-function-adapter-aws has parent spring-cloud-function-adapter-parent,
452+ // which is itself a child of the root spring-cloud-function-parent.
453+ // Without internalArtifactIds the intermediate parent name does not match the root,
454+ // so the deep sub-module's parent version was left unchanged.
455+ const srcDir = fixturePath ( 'maven-multi-level' ) ;
456+ const destRoot = path . join ( tmpDir , 'root' ) ;
457+ const destAdapters = path . join ( destRoot , 'spring-cloud-function-adapters' ) ;
458+ const destAws = path . join ( destAdapters , 'spring-cloud-function-adapter-aws' ) ;
459+ fs . mkdirSync ( destRoot , { recursive : true } ) ;
460+ fs . mkdirSync ( destAdapters , { recursive : true } ) ;
461+ fs . mkdirSync ( destAws , { recursive : true } ) ;
462+
463+ fs . copyFileSync ( path . join ( srcDir , 'pom.xml' ) , path . join ( destRoot , 'pom.xml' ) ) ;
464+ fs . copyFileSync ( path . join ( srcDir , 'spring-cloud-function-adapters' , 'pom.xml' ) , path . join ( destAdapters , 'pom.xml' ) ) ;
465+ fs . copyFileSync ( path . join ( srcDir , 'spring-cloud-function-adapters' , 'spring-cloud-function-adapter-aws' , 'pom.xml' ) , path . join ( destAws , 'pom.xml' ) ) ;
466+
467+ const rootPom = path . join ( destRoot , 'pom.xml' ) ;
468+ const adaptersPom = path . join ( destAdapters , 'pom.xml' ) ;
469+ const awsPom = path . join ( destAws , 'pom.xml' ) ;
470+
471+ const internalIds = new Set ( [ 'spring-cloud-function-parent' , 'spring-cloud-function-adapter-parent' , 'spring-cloud-function-adapter-aws' ] ) ;
472+ const versionMap = { 'spring-cloud-build' : '4.2.1' , 'spring-boot' : '3.4.2' } ;
473+ const currentRoot = '5.0.3' ;
474+ const newVersion = '5.0.3.1-SNAPSHOT' ;
475+
476+ updatePomFile ( rootPom , true , newVersion , versionMap , currentRoot , 'spring-cloud-function-parent' , internalIds ) ;
477+ updatePomFile ( adaptersPom , false , newVersion , versionMap , currentRoot , 'spring-cloud-function-parent' , internalIds ) ;
478+ updatePomFile ( awsPom , false , newVersion , versionMap , currentRoot , 'spring-cloud-function-parent' , internalIds ) ;
479+
480+ // Root: own version updated
481+ const rootContent = fs . readFileSync ( rootPom , 'utf-8' ) ;
482+ expect ( rootContent ) . toMatch ( / < a r t i f a c t I d > s p r i n g - c l o u d - f u n c t i o n - p a r e n t < \/ a r t i f a c t I d > \s * < v e r s i o n > 5 \. 0 \. 3 \. 1 - S N A P S H O T < \/ v e r s i o n > / ) ;
483+ // Root: spring-cloud-build parent version updated from versions map
484+ expect ( rootContent ) . toMatch ( / < p a r e n t > [ \s \S ] * ?< v e r s i o n > 4 \. 2 \. 1 < \/ v e r s i o n > [ \s \S ] * ?< \/ p a r e n t > / ) ;
485+
486+ // Intermediate aggregate: no own <version>, but parent version updated
487+ const adaptersContent = fs . readFileSync ( adaptersPom , 'utf-8' ) ;
488+ const adaptersParent = adaptersContent . match ( / < p a r e n t > [ \s \S ] * ?< \/ p a r e n t > / ) [ 0 ] ;
489+ expect ( adaptersParent ) . toContain ( '<version>5.0.3.1-SNAPSHOT</version>' ) ;
490+
491+ // Deep sub-module: parent (spring-cloud-function-adapter-parent) version updated
492+ const awsContent = fs . readFileSync ( awsPom , 'utf-8' ) ;
493+ const awsParent = awsContent . match ( / < p a r e n t > [ \s \S ] * ?< \/ p a r e n t > / ) [ 0 ] ;
494+ expect ( awsParent ) . toContain ( '<version>5.0.3.1-SNAPSHOT</version>' ) ;
495+ // spring-boot.version property also updated
496+ expect ( awsContent ) . toContain ( '<spring-boot.version>3.4.2</spring-boot.version>' ) ;
497+ } ) ;
498+
449499} ) ;
450500
451501// ── isChildOfRoot ─────────────────────────────────────────────────────────────
@@ -488,6 +538,24 @@ describe('isChildOfRoot', () => {
488538 it ( 'returns false with no parent element' , ( ) => {
489539 expect ( isChildOfRoot ( { } , { } , 'spring-cloud-config' ) ) . toBe ( false ) ;
490540 } ) ;
541+
542+ it ( 'returns true when parent is an intermediate project pom in internalArtifactIds' , ( ) => {
543+ // Mirrors spring-cloud-function-adapters case: the leaf adapter has
544+ // spring-cloud-function-adapter-parent (not the root) as its parent.
545+ const internal = new Set ( [ 'spring-cloud-function-parent' , 'spring-cloud-function-adapter-parent' ] ) ;
546+ expect ( isChildOfRoot ( makeProject ( 'spring-cloud-function-adapter-parent' ) , { } , null , internal ) ) . toBe ( true ) ;
547+ } ) ;
548+
549+ it ( 'returns false when parent is external even though versions map is empty (internalArtifactIds provided)' , ( ) => {
550+ const internal = new Set ( [ 'spring-cloud-function-parent' , 'spring-cloud-function-adapter-parent' ] ) ;
551+ expect ( isChildOfRoot ( makeProject ( 'spring-boot-dependencies' ) , { } , null , internal ) ) . toBe ( false ) ;
552+ } ) ;
553+
554+ it ( 'internalArtifactIds takes precedence over rootArtifactId' , ( ) => {
555+ // rootArtifactId alone would return false for adapter-parent, but internalArtifactIds knows it's internal
556+ const internal = new Set ( [ 'spring-cloud-function-parent' , 'spring-cloud-function-adapter-parent' ] ) ;
557+ expect ( isChildOfRoot ( makeProject ( 'spring-cloud-function-adapter-parent' ) , { } , 'spring-cloud-function-parent' , internal ) ) . toBe ( true ) ;
558+ } ) ;
491559} ) ;
492560
493561// ── findFiles ──────────────────────────────────────────────────────────────────
0 commit comments