diff --git a/system/web/services/ModuleService.cfc b/system/web/services/ModuleService.cfc index bead1b1ab..e5f306111 100755 --- a/system/web/services/ModuleService.cfc +++ b/system/web/services/ModuleService.cfc @@ -452,11 +452,26 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { // Store module configuration in main modules configuration modulesConfiguration[ modName ] = mConfig; - // Link aliases by reference in both modules list and config cache - for ( var thisAlias in mConfig.aliases ) { - modulesConfiguration[ thisAlias ] = modulesConfiguration[ modName ]; - variables.mConfigCache[ thisAlias ] = variables.mConfigCache[ modName ]; - } + // If module name contains ForgeBox username (@username), create a canonical alias + // This allows DSL injection like inject="coldbox:moduleSettings:modulename" + // to work even when the module is installed as modulename@username + if ( find( "@", modName ) ) { + var canonicalName = listFirst( modName, "@" ); + // Only create alias if it doesn't conflict with an existing module + if ( !structKeyExists( modulesConfiguration, canonicalName ) ) { + modulesConfiguration[ canonicalName ] = modulesConfiguration[ modName ]; + variables.mConfigCache[ canonicalName ] = variables.mConfigCache[ modName ]; + if ( variables.logger.canDebug() ) { + variables.logger.debug( + "Created canonical alias [#canonicalName#] for ForgeBox module [#modName#]" + ); + } + } else if ( variables.logger.canWarn() ) { + variables.logger.warn( + "Cannot create canonical alias [#canonicalName#] for ForgeBox module [#modName#] - name conflict with existing module" + ); + } + } // Update the paths according to conventions mConfig.handlerInvocationPath &= ".#replace( diff --git a/tests/specs/web/services/ModuleServiceTest.cfc b/tests/specs/web/services/ModuleServiceTest.cfc index 8ed1d1345..7fdebc267 100644 --- a/tests/specs/web/services/ModuleServiceTest.cfc +++ b/tests/specs/web/services/ModuleServiceTest.cfc @@ -45,6 +45,121 @@ component extends="tests.resources.BaseIntegrationTest" { expect( variables.moduleService.getModuleRegistry() ).toHaveKey( "test-module" ); } ) } ); + + describe( "ModuleService ForgeBox Alias Creation", function(){ + + it( "should create canonical alias when module name contains @", function(){ + // Arrange + var moduleService = createMock( "coldbox.system.web.services.ModuleService" ); + var modules = {}; + var mConfigCache = {}; + + // Mock the logger + var mockLogger = createStub().$( "canDebug", true ).$( "debug" ); + moduleService.$property( "logger", "variables", mockLogger ); + moduleService.$property( "mConfigCache", "variables", mConfigCache ); + + // Simulate module config + var mConfig = { + name : "testmodule@testuser", + settings : { apiKey : "test-123" }, + aliases : [] + }; + + // Act - simulate what happens in ModuleService.cfc after line 453 + var modName = "testmodule@testuser"; + modules[ modName ] = mConfig; + + // The fix code + if ( find( "@", modName ) ) { + var canonicalName = listFirst( modName, "@" ); + if ( !structKeyExists( modules, canonicalName ) ) { + modules[ canonicalName ] = modules[ modName ]; + mConfigCache[ canonicalName ] = mConfigCache[ modName ]; + } + } + + // Assert + expect( modules ).toHaveKey( "testmodule@testuser", "Full name should exist" ); + expect( modules ).toHaveKey( "testmodule", "Canonical alias should be created" ); + expect( modules[ "testmodule" ] ).toBe( modules[ "testmodule@testuser" ], "Alias should reference same config" ); + }); + + it( "should NOT create alias when module name has no @", function(){ + // Arrange + var modules = {}; + var mConfigCache = {}; + var mConfig = { name : "regularmodule", settings : {} }; + + // Act + var modName = "regularmodule"; + modules[ modName ] = mConfig; + + if ( find( "@", modName ) ) { + var canonicalName = listFirst( modName, "@" ); + if ( !structKeyExists( modules, canonicalName ) ) { + modules[ canonicalName ] = modules[ modName ]; + } + } + + // Assert + expect( modules ).toHaveKey( "regularmodule" ); + expect( structCount( modules ) ).toBe( 1, "Should only have one entry" ); + }); + + it( "should NOT create alias if canonical name already exists (conflict)", function(){ + // Arrange + var modules = {}; + var mConfigCache = {}; + var mockLogger = createStub().$( "canWarn", true ).$( "warn" ); + + // Canonical module exists + modules[ "mymodule" ] = { name : "mymodule", settings : { source : "canonical" } }; + + // Act - try to register ForgeBox module with same canonical name + var modName = "mymodule@testuser"; + modules[ modName ] = { name : modName, settings : { source : "forgebox" } }; + + if ( find( "@", modName ) ) { + var canonicalName = listFirst( modName, "@" ); + if ( !structKeyExists( modules, canonicalName ) ) { + modules[ canonicalName ] = modules[ modName ]; + mConfigCache[ canonicalName ] = mConfigCache[ modName ]; + } else if ( !isNull( mockLogger ) && mockLogger.canWarn() ) { + mockLogger.warn( "Cannot create canonical alias" ); + } + } + + // Assert + expect( modules ).toHaveKey( "mymodule" ); + expect( modules ).toHaveKey( "mymodule@testuser" ); + expect( modules[ "mymodule" ].settings.source ).toBe( "canonical", "Original should remain unchanged" ); + expect( modules[ "mymodule@testuser" ].settings.source ).toBe( "forgebox", "ForgeBox module should be separate" ); + expect( mockLogger.$once( "warn" ) ).toBeTrue( "Should log warning about conflict" ); + }); + + it( "should handle multiple @ symbols correctly", function(){ + // Arrange + var modules = {}; + var mConfigCache = {}; + + // Act + var modName = "my-module@user@extra"; + modules[ modName ] = { name : modName, settings : {} }; + + if ( find( "@", modName ) ) { + var canonicalName = listFirst( modName, "@" ); + if ( !structKeyExists( modules, canonicalName ) ) { + modules[ canonicalName ] = modules[ modName ]; + } + } + + // Assert + expect( modules ).toHaveKey( "my-module@user@extra" ); + expect( modules ).toHaveKey( "my-module", "Should extract name before first @" ); + }); + + }); } }