Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

170 lines
5.8 KiB
Go

package languages
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zzet/gortex/internal/graph"
"github.com/zzet/gortex/internal/parser"
)
// metaString reads a string-valued Meta key, returning "" when absent.
func metaString(n *graph.Node, key string) string {
if n == nil || n.Meta == nil {
return ""
}
if v, ok := n.Meta[key].(string); ok {
return v
}
return ""
}
func TestAnsible_PlaybookEmitsPlayTasksHandlerAndModuleCalls(t *testing.T) {
src := []byte(`- name: Configure web servers
hosts: web
tasks:
- name: Copy nginx config
ansible.builtin.copy:
src: nginx.conf
dest: /etc/nginx/nginx.conf
notify: restart nginx
- name: Ensure nginx running
service:
name: nginx
state: started
handlers:
- name: restart nginx
service:
name: nginx
state: restarted
`)
e := NewYAMLExtractor()
result, err := e.Extract("playbooks/web.yml", src)
require.NoError(t, err)
nodes := nodesByID(result.Nodes)
// Play node.
playID := "playbooks/web.yml::play:Configure web servers"
require.Contains(t, nodes, playID)
assert.Equal(t, graph.KindType, nodes[playID].Kind)
assert.Equal(t, "play", metaString(nodes[playID], "ansible_kind"))
assert.Equal(t, "web", metaString(nodes[playID], "hosts"))
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeDefines, "playbooks/web.yml", playID),
"file should define the play")
// Task 1: copy with module meta + module call edge + notify.
copyID := "playbooks/web.yml::task:Copy nginx config"
require.Contains(t, nodes, copyID)
assert.Equal(t, graph.KindFunction, nodes[copyID].Kind)
assert.Equal(t, "task", metaString(nodes[copyID], "ansible_kind"))
assert.Equal(t, "ansible.builtin.copy", metaString(nodes[copyID], "module"))
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeDefines, playID, copyID),
"play should define the copy task")
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeCalls, copyID,
"unresolved::ansible_module::ansible.builtin.copy"),
"copy task should call its module")
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeReferences, copyID,
"unresolved::ansible_handler::restart nginx"),
"copy task should notify the handler")
// Task 2: service module.
svcID := "playbooks/web.yml::task:Ensure nginx running"
require.Contains(t, nodes, svcID)
assert.Equal(t, "service", metaString(nodes[svcID], "module"))
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeCalls, svcID,
"unresolved::ansible_module::service"),
"service task should call its module")
// Handler node.
handlerID := "playbooks/web.yml::handler:restart nginx"
require.Contains(t, nodes, handlerID)
assert.Equal(t, graph.KindFunction, nodes[handlerID].Kind)
assert.Equal(t, "handler", metaString(nodes[handlerID], "ansible_kind"))
assert.Equal(t, "service", metaString(nodes[handlerID], "module"))
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeDefines, playID, handlerID),
"play should define the handler")
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeCalls, handlerID,
"unresolved::ansible_module::service"),
"handler should call its module")
}
func TestAnsible_PlayRolesEmitReferences(t *testing.T) {
src := []byte(`- hosts: db
roles:
- common
- role: postgres
`)
e := NewYAMLExtractor()
result, err := e.Extract("playbooks/db.yml", src)
require.NoError(t, err)
playID := "playbooks/db.yml::play:db"
nodes := nodesByID(result.Nodes)
require.Contains(t, nodes, playID)
assert.Equal(t, "db", metaString(nodes[playID], "hosts"))
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeReferences, playID,
"unresolved::ansible_role::common"),
"play should reference the common role (scalar form)")
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeReferences, playID,
"unresolved::ansible_role::postgres"),
"play should reference the postgres role ({role: ...} form)")
}
func TestAnsible_StandaloneTasksFile(t *testing.T) {
src := []byte(`- name: Install package
ansible.builtin.apt:
name: curl
state: present
- name: Start service
service:
name: curl
state: started
`)
e := NewYAMLExtractor()
result, err := e.Extract("roles/web/tasks/main.yml", src)
require.NoError(t, err)
nodes := nodesByID(result.Nodes)
taskID := "roles/web/tasks/main.yml::task:Install package"
require.Contains(t, nodes, taskID)
assert.Equal(t, graph.KindFunction, nodes[taskID].Kind)
assert.Equal(t, "ansible.builtin.apt", metaString(nodes[taskID], "module"))
// File owns the task directly in a standalone tasks file.
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeDefines, "roles/web/tasks/main.yml", taskID),
"file should define the standalone task")
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeCalls, taskID,
"unresolved::ansible_module::ansible.builtin.apt"),
"standalone task should call its module")
}
// A plain YAML list of config objects (no hosts, not under a role tree)
// must NOT be classified as Ansible — it falls through to the generic
// top-level-keys walker, which emits no play/task nodes.
func TestAnsible_PlainListNotClassified(t *testing.T) {
src := []byte(`- name: us-east
region: us-east-1
capacity: 10
- name: eu-west
region: eu-west-1
capacity: 5
`)
// Direct detector check: returns false (not Ansible).
var negative parser.ExtractionResult
classified := extractAnsibleYAML("config/regions.yml", "config/regions.yml", src, &negative)
assert.False(t, classified, "a plain config list must not be classified as Ansible")
// And via the full extractor: no play / task / handler nodes appear.
e := NewYAMLExtractor()
result, err := e.Extract("config/regions.yml", src)
require.NoError(t, err)
for _, n := range result.Nodes {
if n.Meta != nil {
_, isAnsible := n.Meta["ansible_kind"]
assert.False(t, isAnsible, "no node should carry ansible_kind meta: %s", n.ID)
}
}
}