Skip to content

Commit c8139e5

Browse files
committed
Added unit tests
1 parent 4f3ffc2 commit c8139e5

2 files changed

Lines changed: 249 additions & 0 deletions

File tree

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package com.rundeck.plugins.ansible.ansible
2+
3+
import com.google.gson.Gson
4+
import spock.lang.Specification
5+
6+
/**
7+
* AnsibleInventory test
8+
*/
9+
class AnsibleInventorySpec extends Specification {
10+
11+
void "addHost should sanitize group names with invalid characters"() {
12+
given: "an AnsibleInventory instance"
13+
AnsibleInventory inventory = new AnsibleInventory()
14+
Gson gson = new Gson()
15+
16+
when: "adding a host with tags containing colons"
17+
Map<String, String> attributes = new HashMap<>()
18+
attributes.put("tags", "runner:tag:ansible,testnodes")
19+
inventory.addHost("test-node", "127.0.0.1", attributes)
20+
21+
then: "group names should have colons replaced with underscores"
22+
String json = gson.toJson(inventory)
23+
json.contains("runner_tag_ansible")
24+
json.contains("testnodes")
25+
!json.contains("runner:tag:ansible")
26+
}
27+
28+
void "addHost should sanitize group names with special characters"() {
29+
given: "an AnsibleInventory instance"
30+
AnsibleInventory inventory = new AnsibleInventory()
31+
Gson gson = new Gson()
32+
33+
when: "adding a host with tags containing various special characters"
34+
Map<String, String> attributes = new HashMap<>()
35+
attributes.put("tags", "tag@with#special!chars,normal-tag_ok")
36+
inventory.addHost("test-node", "127.0.0.1", attributes)
37+
38+
then: "special characters should be replaced with underscores"
39+
String json = gson.toJson(inventory)
40+
json.contains("tag_with_special_chars")
41+
json.contains("normal-tag_ok")
42+
}
43+
44+
void "addHost should remove reserved tags attribute from host variables"() {
45+
given: "an AnsibleInventory instance"
46+
AnsibleInventory inventory = new AnsibleInventory()
47+
Gson gson = new Gson()
48+
49+
when: "adding a host with tags attribute"
50+
Map<String, String> attributes = new HashMap<>()
51+
attributes.put("tags", "tag1,tag2")
52+
attributes.put("custom_attr", "value")
53+
inventory.addHost("test-node", "127.0.0.1", attributes)
54+
55+
then: "tags should not appear in host variables but custom attributes should"
56+
String json = gson.toJson(inventory)
57+
json.contains("custom_attr")
58+
json.contains("value")
59+
// Verify tags is not in the host's attributes (not in quoted key context)
60+
!json.contains('"tags"')
61+
}
62+
63+
void "addHost should remove reserved Ansible variables"() {
64+
given: "an AnsibleInventory instance"
65+
AnsibleInventory inventory = new AnsibleInventory()
66+
Gson gson = new Gson()
67+
68+
when: "adding a host with reserved Ansible variables"
69+
Map<String, String> attributes = new HashMap<>()
70+
attributes.put("hostvars", "should_be_removed")
71+
attributes.put("group_names", "should_be_removed")
72+
attributes.put("groups", "should_be_removed")
73+
attributes.put("environment", "should_be_removed")
74+
attributes.put("custom_var", "should_remain")
75+
inventory.addHost("test-node", "127.0.0.1", attributes)
76+
77+
then: "reserved variables should be removed"
78+
String json = gson.toJson(inventory)
79+
json.contains("custom_var")
80+
json.contains("should_remain")
81+
!json.contains("hostvars")
82+
!json.contains("group_names")
83+
!json.contains("should_be_removed")
84+
}
85+
86+
void "addHost should create groups from osFamily attribute"() {
87+
given: "an AnsibleInventory instance"
88+
AnsibleInventory inventory = new AnsibleInventory()
89+
Gson gson = new Gson()
90+
91+
when: "adding a host with osFamily attribute"
92+
Map<String, String> attributes = new HashMap<>()
93+
attributes.put("osFamily", "unix")
94+
inventory.addHost("test-node", "127.0.0.1", attributes)
95+
96+
then: "unix group should be created"
97+
String json = gson.toJson(inventory)
98+
json.contains("unix")
99+
}
100+
101+
void "addHost should handle multiple tags and create multiple groups"() {
102+
given: "an AnsibleInventory instance"
103+
AnsibleInventory inventory = new AnsibleInventory()
104+
Gson gson = new Gson()
105+
106+
when: "adding a host with multiple tags"
107+
Map<String, String> attributes = new HashMap<>()
108+
attributes.put("tags", "linux,web-server,production")
109+
inventory.addHost("test-node", "127.0.0.1", attributes)
110+
111+
then: "all groups should be created"
112+
String json = gson.toJson(inventory)
113+
json.contains("linux")
114+
json.contains("web-server")
115+
json.contains("production")
116+
}
117+
}

src/test/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSourceSpec.groovy

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)