@@ -278,4 +278,136 @@ class AnsibleResourceModelSourceSpec extends Specification {
278278 return yaml. dump(all)
279279 }
280280
281+ void " processHosts should filter out non-String host keys" () {
282+ given : " a plugin with mocked inventory containing non-String keys"
283+ Framework framework = Mock (Framework ) {
284+ getPropertyLookup() >> Mock (IPropertyLookup ){
285+ getProperty(" framework.tmp.dir" ) >> ' /tmp'
286+ }
287+ getBaseDir() >> Mock (File ) {
288+ getAbsolutePath() >> ' /tmp'
289+ }
290+ }
291+ AnsibleResourceModelSource plugin = new AnsibleResourceModelSource (framework)
292+ Properties config = new Properties ()
293+ config. put(' project' , ' project_1' )
294+ config. put(AnsibleDescribable . ANSIBLE_GATHER_FACTS , ' false' )
295+ plugin. configure(config)
296+ Services services = Mock (Services ) {
297+ getService(KeyStorageTree . class) >> Mock (KeyStorageTree )
298+ }
299+ plugin. setServices(services)
300+
301+ when : " YAML contains non-String keys (simulating YAML anchor/alias parsing issues)"
302+ // Create a YAML structure with valid nodes
303+ Yaml yaml = new Yaml ()
304+ def validHost = [' node-1' : [' hostname' : ' host-1' ]]
305+ def hosts = [' hosts' : validHost]
306+ def groups = [' ungrouped' : hosts]
307+ def children = [' children' : groups]
308+ def all = [' all' : children]
309+ String result = yaml. dump(all)
310+
311+ AnsibleInventoryListBuilder inventoryListBuilder = Mock (AnsibleInventoryListBuilder ) {
312+ build() >> Mock (AnsibleInventoryList ) {
313+ getNodeList() >> result
314+ }
315+ }
316+ plugin. ansibleInventoryListBuilder = inventoryListBuilder
317+ INodeSet nodes = plugin. getNodes()
318+
319+ then : " only valid String keys are processed"
320+ nodes. size() == 1
321+ nodes. getNodeNames(). contains(' node-1' )
322+ }
323+
324+ void " processHosts should filter out serialized data structure keys" () {
325+ given : " a plugin configured"
326+ Framework framework = Mock (Framework ) {
327+ getPropertyLookup() >> Mock (IPropertyLookup ){
328+ getProperty(" framework.tmp.dir" ) >> ' /tmp'
329+ }
330+ getBaseDir() >> Mock (File ) {
331+ getAbsolutePath() >> ' /tmp'
332+ }
333+ }
334+ AnsibleResourceModelSource plugin = new AnsibleResourceModelSource (framework)
335+ Properties config = new Properties ()
336+ config. put(' project' , ' project_1' )
337+ config. put(AnsibleDescribable . ANSIBLE_GATHER_FACTS , ' false' )
338+ plugin. configure(config)
339+ Services services = Mock (Services ) {
340+ getService(KeyStorageTree . class) >> Mock (KeyStorageTree )
341+ }
342+ plugin. setServices(services)
343+
344+ when : " YAML contains a suspicious key that looks like serialized data"
345+ Yaml yaml = new Yaml ()
346+ def hosts = [
347+ ' valid-node' : [' hostname' : ' valid-host' ],
348+ ' {all:{hosts:{node:data}}}' : [' hostname' : ' suspicious-host' ]
349+ ]
350+ def hostsMap = [' hosts' : hosts]
351+ def groups = [' ungrouped' : hostsMap]
352+ def children = [' children' : groups]
353+ def all = [' all' : children]
354+ String result = yaml. dump(all)
355+
356+ AnsibleInventoryListBuilder inventoryListBuilder = Mock (AnsibleInventoryListBuilder ) {
357+ build() >> Mock (AnsibleInventoryList ) {
358+ getNodeList() >> result
359+ }
360+ }
361+ plugin. ansibleInventoryListBuilder = inventoryListBuilder
362+ INodeSet nodes = plugin. getNodes()
363+
364+ then : " suspicious key is filtered out and only valid node is present"
365+ nodes. size() == 1
366+ nodes. getNodeNames(). contains(' valid-node' )
367+ ! nodes. getNodeNames(). contains(' {all:{hosts:{node:data}}}' )
368+ }
369+
370+ void " processHosts should handle valid nodes without exception" () {
371+ given : " a plugin configured"
372+ Framework framework = Mock (Framework ) {
373+ getPropertyLookup() >> Mock (IPropertyLookup ){
374+ getProperty(" framework.tmp.dir" ) >> ' /tmp'
375+ }
376+ getBaseDir() >> Mock (File ) {
377+ getAbsolutePath() >> ' /tmp'
378+ }
379+ }
380+ AnsibleResourceModelSource plugin = new AnsibleResourceModelSource (framework)
381+ Properties config = new Properties ()
382+ config. put(' project' , ' project_1' )
383+ config. put(AnsibleDescribable . ANSIBLE_GATHER_FACTS , ' false' )
384+ plugin. configure(config)
385+ Services services = Mock (Services ) {
386+ getService(KeyStorageTree . class) >> Mock (KeyStorageTree )
387+ }
388+ plugin. setServices(services)
389+
390+ when : " processing hosts through the normal flow"
391+ Yaml yaml = new Yaml ()
392+ def hosts = [' valid-node' : [' hostname' : ' valid-host' ]]
393+ def hostsMap = [' hosts' : hosts]
394+ def groups = [' ungrouped' : hostsMap]
395+ def children = [' children' : groups]
396+ def all = [' all' : children]
397+ String result = yaml. dump(all)
398+
399+ AnsibleInventoryListBuilder inventoryListBuilder = Mock (AnsibleInventoryListBuilder ) {
400+ build() >> Mock (AnsibleInventoryList ) {
401+ getNodeList() >> result
402+ }
403+ }
404+ plugin. ansibleInventoryListBuilder = inventoryListBuilder
405+ INodeSet nodes = plugin. getNodes()
406+
407+ then : " no exception thrown and node is processed correctly"
408+ notThrown(Exception )
409+ nodes. size() == 1
410+ nodes. getNodeNames(). contains(' valid-node' )
411+ }
412+
281413}
0 commit comments