491 lines
13 KiB
Go
491 lines
13 KiB
Go
package ansible
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// ============================================================
|
|
// Tests for non-SSH module handlers (0% coverage)
|
|
// ============================================================
|
|
|
|
// --- moduleDebug ---
|
|
|
|
func TestExecutorExtra_ModuleDebug_Good_Message(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
result, err := e.moduleDebug(map[string]any{"msg": "Hello world"})
|
|
|
|
require.NoError(t, err)
|
|
assert.False(t, result.Changed)
|
|
assert.Equal(t, "Hello world", result.Msg)
|
|
}
|
|
|
|
func TestExecutorExtra_ModuleDebug_Good_Var(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
e.vars["my_version"] = "1.2.3"
|
|
|
|
result, err := e.moduleDebug(map[string]any{"var": "my_version"})
|
|
|
|
require.NoError(t, err)
|
|
assert.Contains(t, result.Msg, "1.2.3")
|
|
}
|
|
|
|
func TestExecutorExtra_ModuleDebug_Good_EmptyArgs(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
result, err := e.moduleDebug(map[string]any{})
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "", result.Msg)
|
|
}
|
|
|
|
// --- moduleFail ---
|
|
|
|
func TestExecutorExtra_ModuleFail_Good_DefaultMessage(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
result, err := e.moduleFail(map[string]any{})
|
|
|
|
require.NoError(t, err)
|
|
assert.True(t, result.Failed)
|
|
assert.Equal(t, "Failed as requested", result.Msg)
|
|
}
|
|
|
|
func TestExecutorExtra_ModuleFail_Good_CustomMessage(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
result, err := e.moduleFail(map[string]any{"msg": "deployment blocked"})
|
|
|
|
require.NoError(t, err)
|
|
assert.True(t, result.Failed)
|
|
assert.Equal(t, "deployment blocked", result.Msg)
|
|
}
|
|
|
|
// --- moduleAssert ---
|
|
|
|
func TestExecutorExtra_ModuleAssert_Good_PassingAssertion(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
e.vars["enabled"] = true
|
|
|
|
result, err := e.moduleAssert(map[string]any{"that": "enabled"}, "host1")
|
|
|
|
require.NoError(t, err)
|
|
assert.False(t, result.Failed)
|
|
assert.Equal(t, "All assertions passed", result.Msg)
|
|
}
|
|
|
|
func TestExecutorExtra_ModuleAssert_Bad_FailingAssertion(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
e.vars["enabled"] = false
|
|
|
|
result, err := e.moduleAssert(map[string]any{"that": "enabled"}, "host1")
|
|
|
|
require.NoError(t, err)
|
|
assert.True(t, result.Failed)
|
|
assert.Contains(t, result.Msg, "Assertion failed")
|
|
}
|
|
|
|
func TestExecutorExtra_ModuleAssert_Bad_MissingThat(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
|
|
_, err := e.moduleAssert(map[string]any{}, "host1")
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestExecutorExtra_ModuleAssert_Good_CustomFailMsg(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
e.vars["ready"] = false
|
|
|
|
result, err := e.moduleAssert(map[string]any{
|
|
"that": "ready",
|
|
"fail_msg": "Service not ready",
|
|
}, "host1")
|
|
|
|
require.NoError(t, err)
|
|
assert.True(t, result.Failed)
|
|
assert.Equal(t, "Service not ready", result.Msg)
|
|
}
|
|
|
|
func TestExecutorExtra_ModuleAssert_Good_MultipleConditions(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
e.vars["enabled"] = true
|
|
e.vars["count"] = 5
|
|
|
|
result, err := e.moduleAssert(map[string]any{
|
|
"that": []any{"enabled", "count"},
|
|
}, "host1")
|
|
|
|
require.NoError(t, err)
|
|
assert.False(t, result.Failed)
|
|
}
|
|
|
|
// --- moduleSetFact ---
|
|
|
|
func TestExecutorExtra_ModuleSetFact_Good(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
|
|
result, err := e.moduleSetFact(map[string]any{
|
|
"app_version": "2.0.0",
|
|
"deploy_env": "production",
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
assert.True(t, result.Changed)
|
|
assert.Equal(t, "2.0.0", e.vars["app_version"])
|
|
assert.Equal(t, "production", e.vars["deploy_env"])
|
|
}
|
|
|
|
func TestExecutorExtra_ModuleSetFact_Good_SkipsCacheable(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
|
|
e.moduleSetFact(map[string]any{
|
|
"my_fact": "value",
|
|
"cacheable": true,
|
|
})
|
|
|
|
assert.Equal(t, "value", e.vars["my_fact"])
|
|
_, hasCacheable := e.vars["cacheable"]
|
|
assert.False(t, hasCacheable)
|
|
}
|
|
|
|
// --- moduleIncludeVars ---
|
|
|
|
func TestExecutorExtra_ModuleIncludeVars_Good_WithFile(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
result, err := e.moduleIncludeVars(map[string]any{"file": "vars/main.yml"})
|
|
|
|
require.NoError(t, err)
|
|
assert.Contains(t, result.Msg, "vars/main.yml")
|
|
}
|
|
|
|
func TestExecutorExtra_ModuleIncludeVars_Good_WithRawParams(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
result, err := e.moduleIncludeVars(map[string]any{"_raw_params": "defaults.yml"})
|
|
|
|
require.NoError(t, err)
|
|
assert.Contains(t, result.Msg, "defaults.yml")
|
|
}
|
|
|
|
func TestExecutorExtra_ModuleIncludeVars_Good_Empty(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
result, err := e.moduleIncludeVars(map[string]any{})
|
|
|
|
require.NoError(t, err)
|
|
assert.False(t, result.Changed)
|
|
}
|
|
|
|
// --- moduleMeta ---
|
|
|
|
func TestExecutorExtra_ModuleMeta_Good(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
result, err := e.moduleMeta(map[string]any{"_raw_params": "flush_handlers"})
|
|
|
|
require.NoError(t, err)
|
|
assert.False(t, result.Changed)
|
|
}
|
|
|
|
// ============================================================
|
|
// Tests for handleLookup (0% coverage)
|
|
// ============================================================
|
|
|
|
func TestExecutorExtra_HandleLookup_Good_EnvVar(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
t.Setenv("TEST_ANSIBLE_LOOKUP", "found_it")
|
|
|
|
result := e.handleLookup("lookup('env', 'TEST_ANSIBLE_LOOKUP')")
|
|
assert.Equal(t, "found_it", result)
|
|
}
|
|
|
|
func TestExecutorExtra_HandleLookup_Good_EnvVarMissing(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
result := e.handleLookup("lookup('env', 'NONEXISTENT_VAR_12345')")
|
|
assert.Equal(t, "", result)
|
|
}
|
|
|
|
func TestExecutorExtra_HandleLookup_Bad_InvalidSyntax(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
result := e.handleLookup("lookup(invalid)")
|
|
assert.Equal(t, "", result)
|
|
}
|
|
|
|
// ============================================================
|
|
// Tests for SetInventory (0% coverage)
|
|
// ============================================================
|
|
|
|
func TestExecutorExtra_SetInventory_Good(t *testing.T) {
|
|
dir := t.TempDir()
|
|
invPath := joinPath(dir, "inventory.yml")
|
|
yaml := `all:
|
|
hosts:
|
|
web1:
|
|
ansible_host: 10.0.0.1
|
|
web2:
|
|
ansible_host: 10.0.0.2
|
|
`
|
|
require.NoError(t, writeTestFile(invPath, []byte(yaml), 0644))
|
|
|
|
e := NewExecutor(dir)
|
|
err := e.SetInventory(invPath)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, e.inventory)
|
|
assert.Len(t, e.inventory.All.Hosts, 2)
|
|
}
|
|
|
|
func TestExecutorExtra_SetInventory_Bad_FileNotFound(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
err := e.SetInventory("/nonexistent/inventory.yml")
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
// ============================================================
|
|
// Tests for iterator functions (0% coverage)
|
|
// ============================================================
|
|
|
|
func TestExecutorExtra_ParsePlaybookIter_Good(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := joinPath(dir, "playbook.yml")
|
|
yaml := `- name: First play
|
|
hosts: all
|
|
tasks:
|
|
- debug:
|
|
msg: hello
|
|
|
|
- name: Second play
|
|
hosts: localhost
|
|
tasks:
|
|
- debug:
|
|
msg: world
|
|
`
|
|
require.NoError(t, writeTestFile(path, []byte(yaml), 0644))
|
|
|
|
p := NewParser(dir)
|
|
iter, err := p.ParsePlaybookIter(path)
|
|
require.NoError(t, err)
|
|
|
|
var plays []Play
|
|
for play := range iter {
|
|
plays = append(plays, play)
|
|
}
|
|
assert.Len(t, plays, 2)
|
|
assert.Equal(t, "First play", plays[0].Name)
|
|
assert.Equal(t, "Second play", plays[1].Name)
|
|
}
|
|
|
|
func TestExecutorExtra_ParsePlaybookIter_Bad_InvalidFile(t *testing.T) {
|
|
_, err := NewParser("/tmp").ParsePlaybookIter("/nonexistent.yml")
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestExecutorExtra_ParseTasksIter_Good(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := joinPath(dir, "tasks.yml")
|
|
yaml := `- name: Task one
|
|
debug:
|
|
msg: first
|
|
|
|
- name: Task two
|
|
debug:
|
|
msg: second
|
|
`
|
|
require.NoError(t, writeTestFile(path, []byte(yaml), 0644))
|
|
|
|
p := NewParser(dir)
|
|
iter, err := p.ParseTasksIter(path)
|
|
require.NoError(t, err)
|
|
|
|
var tasks []Task
|
|
for task := range iter {
|
|
tasks = append(tasks, task)
|
|
}
|
|
assert.Len(t, tasks, 2)
|
|
assert.Equal(t, "Task one", tasks[0].Name)
|
|
}
|
|
|
|
func TestExecutorExtra_ParseTasksIter_Bad_InvalidFile(t *testing.T) {
|
|
_, err := NewParser("/tmp").ParseTasksIter("/nonexistent.yml")
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestExecutorExtra_GetHostsIter_Good(t *testing.T) {
|
|
inv := &Inventory{
|
|
All: &InventoryGroup{
|
|
Hosts: map[string]*Host{
|
|
"web1": {},
|
|
"web2": {},
|
|
"db1": {},
|
|
},
|
|
},
|
|
}
|
|
|
|
var hosts []string
|
|
for host := range GetHostsIter(inv, "all") {
|
|
hosts = append(hosts, host)
|
|
}
|
|
assert.Len(t, hosts, 3)
|
|
}
|
|
|
|
func TestExecutorExtra_AllHostsIter_Good(t *testing.T) {
|
|
group := &InventoryGroup{
|
|
Hosts: map[string]*Host{
|
|
"alpha": {},
|
|
"beta": {},
|
|
},
|
|
Children: map[string]*InventoryGroup{
|
|
"db": {
|
|
Hosts: map[string]*Host{
|
|
"gamma": {},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
var hosts []string
|
|
for host := range AllHostsIter(group) {
|
|
hosts = append(hosts, host)
|
|
}
|
|
assert.Len(t, hosts, 3)
|
|
// AllHostsIter sorts keys
|
|
assert.Equal(t, "alpha", hosts[0])
|
|
assert.Equal(t, "beta", hosts[1])
|
|
assert.Equal(t, "gamma", hosts[2])
|
|
}
|
|
|
|
func TestExecutorExtra_AllHostsIter_Good_NilGroup(t *testing.T) {
|
|
var count int
|
|
for range AllHostsIter(nil) {
|
|
count++
|
|
}
|
|
assert.Equal(t, 0, count)
|
|
}
|
|
|
|
// ============================================================
|
|
// Tests for resolveExpr with registered vars (additional coverage)
|
|
// ============================================================
|
|
|
|
func TestExecutorExtra_ResolveExpr_Good_RegisteredVarFields(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
e.results["host1"] = map[string]*TaskResult{
|
|
"cmd_result": {
|
|
Stdout: "output text",
|
|
Stderr: "error text",
|
|
RC: 0,
|
|
Changed: true,
|
|
Failed: false,
|
|
},
|
|
}
|
|
|
|
assert.Equal(t, "output text", e.resolveExpr("cmd_result.stdout", "host1", nil))
|
|
assert.Equal(t, "error text", e.resolveExpr("cmd_result.stderr", "host1", nil))
|
|
assert.Equal(t, "0", e.resolveExpr("cmd_result.rc", "host1", nil))
|
|
assert.Equal(t, "true", e.resolveExpr("cmd_result.changed", "host1", nil))
|
|
assert.Equal(t, "false", e.resolveExpr("cmd_result.failed", "host1", nil))
|
|
}
|
|
|
|
func TestExecutorExtra_ResolveExpr_Good_TaskVars(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
task := &Task{
|
|
Vars: map[string]any{"local_var": "local_value"},
|
|
}
|
|
|
|
result := e.resolveExpr("local_var", "host1", task)
|
|
assert.Equal(t, "local_value", result)
|
|
}
|
|
|
|
func TestExecutorExtra_ResolveExpr_Good_HostVars(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
e.SetInventoryDirect(&Inventory{
|
|
All: &InventoryGroup{
|
|
Hosts: map[string]*Host{
|
|
"host1": {AnsibleHost: "10.0.0.1"},
|
|
},
|
|
},
|
|
})
|
|
|
|
result := e.resolveExpr("ansible_host", "host1", nil)
|
|
assert.Equal(t, "10.0.0.1", result)
|
|
}
|
|
|
|
func TestExecutorExtra_ResolveExpr_Good_Facts(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
e.facts["host1"] = &Facts{
|
|
Hostname: "web01",
|
|
FQDN: "web01.example.com",
|
|
Distribution: "ubuntu",
|
|
Version: "22.04",
|
|
Architecture: "x86_64",
|
|
Kernel: "5.15.0",
|
|
}
|
|
|
|
assert.Equal(t, "web01", e.resolveExpr("ansible_hostname", "host1", nil))
|
|
assert.Equal(t, "web01.example.com", e.resolveExpr("ansible_fqdn", "host1", nil))
|
|
assert.Equal(t, "ubuntu", e.resolveExpr("ansible_distribution", "host1", nil))
|
|
assert.Equal(t, "22.04", e.resolveExpr("ansible_distribution_version", "host1", nil))
|
|
assert.Equal(t, "x86_64", e.resolveExpr("ansible_architecture", "host1", nil))
|
|
assert.Equal(t, "5.15.0", e.resolveExpr("ansible_kernel", "host1", nil))
|
|
}
|
|
|
|
// --- applyFilter additional coverage ---
|
|
|
|
func TestExecutorExtra_ApplyFilter_Good_B64Decode(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
// b64decode is a no-op stub currently
|
|
assert.Equal(t, "hello", e.applyFilter("hello", "b64decode"))
|
|
}
|
|
|
|
func TestExecutorExtra_ApplyFilter_Good_UnknownFilter(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
assert.Equal(t, "value", e.applyFilter("value", "unknown_filter"))
|
|
}
|
|
|
|
// --- evalCondition with default filter ---
|
|
|
|
func TestExecutorExtra_EvalCondition_Good_DefaultFilter(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
assert.True(t, e.evalCondition("myvar | default('fallback')", "host1"))
|
|
}
|
|
|
|
func TestExecutorExtra_EvalCondition_Good_UndefinedCheck(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
assert.True(t, e.evalCondition("missing_var is not defined", "host1"))
|
|
assert.True(t, e.evalCondition("missing_var is undefined", "host1"))
|
|
}
|
|
|
|
// --- resolveExpr with filter pipe ---
|
|
|
|
func TestExecutorExtra_ResolveExpr_Good_WithFilter(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
e.vars["raw_value"] = " trimmed "
|
|
|
|
result := e.resolveExpr("raw_value | trim", "host1", nil)
|
|
assert.Equal(t, "trimmed", result)
|
|
}
|
|
|
|
func TestExecutorExtra_LoopMetadata_Good_ExtendedMetadata(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
e.vars["item"] = "alpha"
|
|
|
|
metadata := e.loopMetadata("alpha", 1, 3, "item {{ item }}", "host1", &Task{})
|
|
|
|
assert.Equal(t, 2, metadata["index"])
|
|
assert.Equal(t, 1, metadata["index0"])
|
|
assert.Equal(t, 2, metadata["revindex"])
|
|
assert.Equal(t, 1, metadata["revindex0"])
|
|
assert.False(t, metadata["first"].(bool))
|
|
assert.False(t, metadata["last"].(bool))
|
|
assert.Equal(t, 3, metadata["length"])
|
|
assert.Equal(t, "alpha", metadata["item"])
|
|
assert.Equal(t, "item alpha", metadata["label"])
|
|
}
|
|
|
|
func TestExecutorExtra_TemplateString_Good_DottedMapAccess(t *testing.T) {
|
|
e := NewExecutor("/tmp")
|
|
e.vars["ansible_loop"] = map[string]any{
|
|
"index0": 1,
|
|
"first": false,
|
|
"last": true,
|
|
}
|
|
|
|
got := e.templateString("{{ ansible_loop.index0 }} {{ ansible_loop.first }} {{ ansible_loop.last }}", "host1", nil)
|
|
|
|
assert.Equal(t, "1 false true", got)
|
|
}
|