summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authoromnom62 <75066712+omnom62@users.noreply.github.com>2026-08-21 22:12:11 +1000
committerGitHub <noreply@github.com>2026-08-21 15:12:11 +0300
commit899a6bf7955592ec40670944a860a1bee97b432c (patch)
treefa35031ba10df9ab41376529363664dda0ea7d4c /tests
parentd80f14eef50403f0851c9a12b77890c9bf1f3c4d (diff)
downloadvyos.vyos-899a6bf7955592ec40670944a860a1bee97b432c.tar.gz
vyos.vyos-899a6bf7955592ec40670944a860a1bee97b432c.zip
T8321: vpn_ipsec modules (#489)
Add VPN IPsec module
Diffstat (limited to 'tests')
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec/defaults/main.yaml3
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec/meta/main.yaml1
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec/tasks/cli.yaml20
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec/tasks/main.yaml5
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec/tests/cli/_parsed.cfg3
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec/tests/cli/_populate.yaml8
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec/tests/cli/_remove_config.yaml4
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec/tests/cli/deleted.yaml69
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec/tests/cli/empty_config.yaml58
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec/tests/cli/gathered.yaml28
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec/tests/cli/merged.yaml37
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec/tests/cli/overridden.yaml36
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec/tests/cli/parsed.yaml14
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec/tests/cli/rendered.yaml15
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec/tests/cli/replaced.yaml61
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec/vars/main.yaml231
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec_s2s/defaults/main.yaml3
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec_s2s/meta/main.yaml1
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec_s2s/tasks/cli.yaml20
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec_s2s/tasks/main.yaml5
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/_parsed.cfg1
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/_populate.yaml37
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/_remove_config.yaml13
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/deleted.yaml60
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/empty_config.yaml58
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/gathered.yaml20
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/merged.yaml58
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/overridden.yaml39
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/parsed.yaml14
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/rendered.yaml15
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/replaced.yaml42
-rw-r--r--tests/integration/targets/vyos_vpn_ipsec_s2s/vars/main.yaml257
-rw-r--r--tests/unit/modules/network/vyos/fixtures/vyos_vpn_ipsec_config.cfg30
-rw-r--r--tests/unit/modules/network/vyos/fixtures/vyos_vpn_ipsec_s2s_config.cfg26
-rw-r--r--tests/unit/modules/network/vyos/test_vyos_vpn_ipsec.py568
-rw-r--r--tests/unit/modules/network/vyos/test_vyos_vpn_ipsec_s2s.py245
36 files changed, 2105 insertions, 0 deletions
diff --git a/tests/integration/targets/vyos_vpn_ipsec/defaults/main.yaml b/tests/integration/targets/vyos_vpn_ipsec/defaults/main.yaml
new file mode 100644
index 00000000..164afead
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec/defaults/main.yaml
@@ -0,0 +1,3 @@
+---
+testcase: "[^_].*"
+test_items: []
diff --git a/tests/integration/targets/vyos_vpn_ipsec/meta/main.yaml b/tests/integration/targets/vyos_vpn_ipsec/meta/main.yaml
new file mode 100644
index 00000000..ed97d539
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec/meta/main.yaml
@@ -0,0 +1 @@
+---
diff --git a/tests/integration/targets/vyos_vpn_ipsec/tasks/cli.yaml b/tests/integration/targets/vyos_vpn_ipsec/tasks/cli.yaml
new file mode 100644
index 00000000..daccf720
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec/tasks/cli.yaml
@@ -0,0 +1,20 @@
+---
+- name: Collect all cli test cases
+ ansible.builtin.find:
+ paths: "{{ role_path }}/tests/cli"
+ patterns: "{{ testcase }}.yaml"
+ use_regex: true
+ register: test_cases
+ delegate_to: localhost
+
+- name: Set test_items
+ ansible.builtin.set_fact:
+ test_items: "{{ test_cases.files | map(attribute='path') | list }}"
+
+- name: Run test case (connection=ansible.netcommon.network_cli)
+ ansible.builtin.include_tasks: "{{ test_case_to_run }}"
+ vars:
+ ansible_connection: ansible.netcommon.network_cli
+ with_items: "{{ test_items }}"
+ loop_control:
+ loop_var: test_case_to_run
diff --git a/tests/integration/targets/vyos_vpn_ipsec/tasks/main.yaml b/tests/integration/targets/vyos_vpn_ipsec/tasks/main.yaml
new file mode 100644
index 00000000..e6378581
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec/tasks/main.yaml
@@ -0,0 +1,5 @@
+---
+- name: Run CLI tests
+ ansible.builtin.include_tasks: cli.yaml
+ tags:
+ - network_cli
diff --git a/tests/integration/targets/vyos_vpn_ipsec/tests/cli/_parsed.cfg b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/_parsed.cfg
new file mode 100644
index 00000000..85a382e7
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/_parsed.cfg
@@ -0,0 +1,3 @@
+set vpn ipsec esp-group ESP-PARSE-TEST proposal 1 encryption 'aes256'
+set vpn ipsec esp-group ESP-PARSE-TEST proposal 1 hash 'sha256'
+set vpn ipsec ike-group IKE-PARSE-TEST key-exchange 'ikev2'
diff --git a/tests/integration/targets/vyos_vpn_ipsec/tests/cli/_populate.yaml b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/_populate.yaml
new file mode 100644
index 00000000..96842807
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/_populate.yaml
@@ -0,0 +1,8 @@
+---
+- ansible.builtin.include_tasks: _remove_config.yaml
+
+- name: POPULATE Apply the provided configuration
+ register: result
+ vyos.vyos.vyos_vpn_ipsec:
+ config: "{{ populate_config }}"
+ state: merged
diff --git a/tests/integration/targets/vyos_vpn_ipsec/tests/cli/_remove_config.yaml b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/_remove_config.yaml
new file mode 100644
index 00000000..14b032e3
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/_remove_config.yaml
@@ -0,0 +1,4 @@
+---
+- name: delete the provided configuration
+ vyos.vyos.vyos_vpn_ipsec:
+ state: deleted
diff --git a/tests/integration/targets/vyos_vpn_ipsec/tests/cli/deleted.yaml b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/deleted.yaml
new file mode 100644
index 00000000..eee2c822
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/deleted.yaml
@@ -0,0 +1,69 @@
+---
+- debug:
+ msg: START vyos_vpn_ipsec deleted integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _populate.yaml
+
+- block:
+ - name: TEST - [deleted] Remove all configuration
+ vyos.vyos.vyos_vpn_ipsec:
+ state: deleted
+ register: result
+
+ - name: TEST - [deleted] Assert that correct set of commands were generated
+ assert:
+ that:
+ - "{{ deleted['commands'] | symmetric_difference(result['commands']) | length == 0 }}"
+ - result.changed == true
+ - result.after == deleted.after
+
+ - name: TEST - [deleted] Remove all configuration again (IDEMPOTENT)
+ vyos.vyos.vyos_vpn_ipsec:
+ state: deleted
+ register: result
+
+ - name: TEST - [deleted] Assert idempotent
+ assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
+
+- block:
+ - include_tasks: _populate.yaml
+
+ - name: TEST - [deleted scoped] Remove only IKE-EXTRA by name
+ vyos.vyos.vyos_vpn_ipsec:
+ config:
+ ike_group:
+ - name: IKE-EXTRA
+ state: deleted
+ register: result
+
+ - name: TEST - [deleted scoped] Assert only IKE-EXTRA was removed
+ assert:
+ that:
+ - result.commands == ["delete vpn ipsec ike-group IKE-EXTRA"]
+ - result.changed == true
+
+ - vyos.vyos.vyos_facts:
+ gather_network_resources: vpn_ipsec
+
+ - name: TEST - [deleted scoped] Assert IKE-TEST and everything else survived
+ vars:
+ redacted: VALUE_SPECIFIED_IN_NO_LOG_PARAMETER
+ assert:
+ that:
+ - ansible_facts['network_resources']['vpn_ipsec']['ike_group'] == [populate_config.ike_group[0]]
+ - ansible_facts['network_resources']['vpn_ipsec']['esp_group'] == populate_config.esp_group
+ - ansible_facts['network_resources']['vpn_ipsec']['profile'][0]['name'] == populate_config.profile[0].name
+ - ansible_facts['network_resources']['vpn_ipsec']['profile'][0]['authentication']['mode'] == populate_config.profile[0].authentication.mode
+ - ansible_facts['network_resources']['vpn_ipsec']['profile'][0]['authentication']['pre_shared_secret'] == redacted
+ - ansible_facts['network_resources']['vpn_ipsec']['profile'][0]['bind_tunnel'] == populate_config.profile[0].bind_tunnel
+ - ansible_facts['network_resources']['vpn_ipsec']['profile'][0]['esp_group'] == populate_config.profile[0].esp_group
+ - ansible_facts['network_resources']['vpn_ipsec']['profile'][0]['ike_group'] == populate_config.profile[0].ike_group
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_vpn_ipsec/tests/cli/empty_config.yaml b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/empty_config.yaml
new file mode 100644
index 00000000..7d0329fd
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/empty_config.yaml
@@ -0,0 +1,58 @@
+---
+- debug:
+ msg: START vyos_vpn_ipsec empty_config integration tests on connection={{ ansible_connection }}
+
+- name: Merged with empty config should give appropriate error message
+ register: result
+ ignore_errors: true
+ vyos.vyos.vyos_vpn_ipsec:
+ config:
+ state: merged
+
+- assert:
+ that:
+ - result.msg == 'value of config parameter must not be empty for state merged'
+
+- name: Replaced with empty config should give appropriate error message
+ register: result
+ ignore_errors: true
+ vyos.vyos.vyos_vpn_ipsec:
+ config:
+ state: replaced
+
+- assert:
+ that:
+ - result.msg == 'value of config parameter must not be empty for state replaced'
+
+- name: Overridden with empty config should give appropriate error message
+ register: result
+ ignore_errors: true
+ vyos.vyos.vyos_vpn_ipsec:
+ config:
+ state: overridden
+
+- assert:
+ that:
+ - result.msg == 'value of config parameter must not be empty for state overridden'
+
+- name: Rendered with empty config should give appropriate error message
+ register: result
+ ignore_errors: true
+ vyos.vyos.vyos_vpn_ipsec:
+ config:
+ state: rendered
+
+- assert:
+ that:
+ - result.msg == 'value of config parameter must not be empty for state rendered'
+
+- name: Parsed with empty running_config should give appropriate error message
+ register: result
+ ignore_errors: true
+ vyos.vyos.vyos_vpn_ipsec:
+ running_config:
+ state: parsed
+
+- assert:
+ that:
+ - result.msg == 'value of running_config parameter must not be empty for state parsed'
diff --git a/tests/integration/targets/vyos_vpn_ipsec/tests/cli/gathered.yaml b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/gathered.yaml
new file mode 100644
index 00000000..716bc305
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/gathered.yaml
@@ -0,0 +1,28 @@
+---
+- debug:
+ msg: START vyos_vpn_ipsec gathered integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _populate.yaml
+
+- block:
+ - name: TEST - [gathered] Gather the existing running configuration
+ register: result
+ vyos.vyos.vyos_vpn_ipsec:
+ state: gathered
+
+ - name: TEST - [gathered] Assert
+ vars:
+ redacted: VALUE_SPECIFIED_IN_NO_LOG_PARAMETER
+ assert:
+ that:
+ - result.changed == false
+ - result.gathered.esp_group == populate_config.esp_group
+ - result.gathered.profile[0].name == populate_config.profile[0].name
+ - result.gathered.profile[0].authentication.mode == populate_config.profile[0].authentication.mode
+ - result.gathered.profile[0].authentication.pre_shared_secret == redacted
+ - result.gathered.profile[0].bind_tunnel == populate_config.profile[0].bind_tunnel
+ - result.gathered.profile[0].esp_group == populate_config.profile[0].esp_group
+ - result.gathered.profile[0].ike_group == populate_config.profile[0].ike_group
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_vpn_ipsec/tests/cli/merged.yaml b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/merged.yaml
new file mode 100644
index 00000000..6c21d107
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/merged.yaml
@@ -0,0 +1,37 @@
+---
+- debug:
+ msg: START vyos_vpn_ipsec merged integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+
+- block:
+ - name: Merge the provided configuration with the existing running configuration
+ register: result
+ vyos.vyos.vyos_vpn_ipsec: &id001
+ config: "{{ merged.config }}"
+ state: merged
+
+ - vyos.vyos.vyos_facts:
+ gather_network_resources: vpn_ipsec
+
+ - name: Assert that correct set of commands were generated
+ assert:
+ that:
+ - "{{ merged['commands'] | symmetric_difference(result['commands']) | length == 0 }}"
+ - result.changed == true
+ - result.after == ansible_facts['network_resources']['vpn_ipsec']
+ - result.after == merged.after
+ - result.before == {}
+
+ - name: Merge the provided configuration with the existing running configuration (IDEMPOTENT)
+ register: result
+ vyos.vyos.vyos_vpn_ipsec: *id001
+
+ - name: Assert that the previous task was idempotent
+ assert:
+ that:
+ - result['changed'] == false
+ - result['commands'] == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_vpn_ipsec/tests/cli/overridden.yaml b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/overridden.yaml
new file mode 100644
index 00000000..6a8eed30
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/overridden.yaml
@@ -0,0 +1,36 @@
+---
+- debug:
+ msg: START vyos_vpn_ipsec overridden integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _populate.yaml
+
+- block:
+ - name: Override with only ESP-TEST named -- everything else must be removed
+ register: result
+ vyos.vyos.vyos_vpn_ipsec: &id001
+ config: "{{ overridden.config }}"
+ state: overridden
+
+ - vyos.vyos.vyos_facts:
+ gather_network_resources: vpn_ipsec
+
+ - name: Assert that correct set of commands were generated
+ assert:
+ that:
+ - "{{ overridden['commands'] | symmetric_difference(result['commands']) | length == 0 }}"
+ - result.changed == true
+ - result.after == ansible_facts['network_resources']['vpn_ipsec']
+ - result.after == overridden.after
+
+ - name: Override with the same configuration (IDEMPOTENT)
+ register: result
+ vyos.vyos.vyos_vpn_ipsec: *id001
+
+ - name: Assert that the previous task was idempotent
+ assert:
+ that:
+ - result['changed'] == false
+ - result['commands'] == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_vpn_ipsec/tests/cli/parsed.yaml b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/parsed.yaml
new file mode 100644
index 00000000..0d1197e4
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/parsed.yaml
@@ -0,0 +1,14 @@
+---
+- debug:
+ msg: START vyos_vpn_ipsec parsed integration tests on connection={{ ansible_connection }}
+
+- name: TEST - [parsed] Parse the commands for provided configuration
+ register: result
+ vyos.vyos.vyos_vpn_ipsec:
+ running_config: "{{ lookup('file', '_parsed.cfg') }}"
+ state: parsed
+
+- assert:
+ that:
+ - result.changed == false
+ - parsed['after'] == result.parsed
diff --git a/tests/integration/targets/vyos_vpn_ipsec/tests/cli/rendered.yaml b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/rendered.yaml
new file mode 100644
index 00000000..1babcc2b
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/rendered.yaml
@@ -0,0 +1,15 @@
+---
+- debug:
+ msg: START vyos_vpn_ipsec rendered integration tests on connection={{ ansible_connection }}
+
+- block:
+ - name: Structure provided configuration into device specific commands
+ register: result
+ vyos.vyos.vyos_vpn_ipsec:
+ config: "{{ rendered.config }}"
+ state: rendered
+
+ - name: Assert that correct set of commands were generated
+ assert:
+ that:
+ - "{{ rendered['commands'] | symmetric_difference(result['rendered']) | length == 0 }}"
diff --git a/tests/integration/targets/vyos_vpn_ipsec/tests/cli/replaced.yaml b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/replaced.yaml
new file mode 100644
index 00000000..0714e8dc
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/replaced.yaml
@@ -0,0 +1,61 @@
+---
+- debug:
+ msg: START vyos_vpn_ipsec replaced integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _populate.yaml
+
+- block:
+ - name: Replace only the named esp_group -- other sections must be untouched
+ register: result
+ vyos.vyos.vyos_vpn_ipsec: &id001
+ config: "{{ replaced.config }}"
+ state: replaced
+
+ - vyos.vyos.vyos_facts:
+ gather_network_resources: vpn_ipsec
+
+ - name: Assert that correct set of commands were generated
+ assert:
+ that:
+ - "{{ replaced['commands'] | symmetric_difference(result['commands']) | length == 0 }}"
+ - result.changed == true
+
+ - name: Assert that ESP-TEST was correctly reconciled
+ assert:
+ that:
+ - ansible_facts['network_resources']['vpn_ipsec']['esp_group'] == replaced.after.esp_group
+
+ - name: Assert that un-named sections were left completely untouched
+ vars:
+ redacted: VALUE_SPECIFIED_IN_NO_LOG_PARAMETER
+ assert:
+ that:
+ - (ansible_facts['network_resources']['vpn_ipsec']['ike_group'] | sort(attribute='name')) == (populate_config.ike_group | sort(attribute='name'))
+ - ansible_facts['network_resources']['vpn_ipsec']['profile'][0]['name'] == populate_config.profile[0].name
+ - ansible_facts['network_resources']['vpn_ipsec']['profile'][0]['authentication']['mode'] == populate_config.profile[0].authentication.mode
+ # pre_shared_secret/secret carry no_log: True in the argspec --
+ # gathered facts always redact them to this literal placeholder,
+ # never the real value, so that's what must be asserted here.
+ - ansible_facts['network_resources']['vpn_ipsec']['profile'][0]['authentication']['pre_shared_secret'] == redacted
+ - ansible_facts['network_resources']['vpn_ipsec']['profile'][0]['bind_tunnel'] == populate_config.profile[0].bind_tunnel
+ - ansible_facts['network_resources']['vpn_ipsec']['profile'][0]['esp_group'] == populate_config.profile[0].esp_group
+ - ansible_facts['network_resources']['vpn_ipsec']['profile'][0]['ike_group'] == populate_config.profile[0].ike_group
+ - ansible_facts['network_resources']['vpn_ipsec']['authentication']['psk'][0]['name'] == populate_config.authentication.psk[0].name
+ - ansible_facts['network_resources']['vpn_ipsec']['authentication']['psk'][0]['secret'] == redacted
+ - (ansible_facts['network_resources']['vpn_ipsec']['authentication']['psk'][0]['id'] | sort) == (populate_config.authentication.psk[0].id | sort)
+ - ansible_facts['network_resources']['vpn_ipsec']['authentication']['ppk'][0]['name'] == populate_config.authentication.ppk[0].name
+ - ansible_facts['network_resources']['vpn_ipsec']['authentication']['ppk'][0]['secret'] == redacted
+ - ansible_facts['network_resources']['vpn_ipsec']['authentication']['ppk'][0]['id'] == populate_config.authentication.ppk[0].id
+
+ - name: Replace with the same configuration (IDEMPOTENT)
+ register: result
+ vyos.vyos.vyos_vpn_ipsec: *id001
+
+ - name: Assert that the previous task was idempotent
+ assert:
+ that:
+ - result['changed'] == false
+ - result['commands'] == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_vpn_ipsec/vars/main.yaml b/tests/integration/targets/vyos_vpn_ipsec/vars/main.yaml
new file mode 100644
index 00000000..b86a8d72
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec/vars/main.yaml
@@ -0,0 +1,231 @@
+---
+merged:
+ config:
+ esp_group:
+ - name: ESP-TEST
+ compression: true
+ proposal:
+ - proposal_id: 1
+ encryption: aes256
+ hash: sha256
+ ike_group:
+ - name: IKE-TEST
+ key_exchange: ikev2
+ disable_mobike: true
+ dead_peer_detection:
+ action: restart
+ interval: 15
+ timeout: 60
+ proposal:
+ - proposal_id: 1
+ encryption: aes256
+ hash: sha256
+ dh_group: 14
+ commands:
+ - set vpn ipsec esp-group ESP-TEST
+ - set vpn ipsec esp-group ESP-TEST compression
+ - set vpn ipsec esp-group ESP-TEST proposal 1
+ - set vpn ipsec esp-group ESP-TEST proposal 1 encryption aes256
+ - set vpn ipsec esp-group ESP-TEST proposal 1 hash sha256
+ - set vpn ipsec ike-group IKE-TEST
+ - set vpn ipsec ike-group IKE-TEST key-exchange ikev2
+ - set vpn ipsec ike-group IKE-TEST disable-mobike
+ - set vpn ipsec ike-group IKE-TEST dead-peer-detection action restart
+ - set vpn ipsec ike-group IKE-TEST dead-peer-detection interval 15
+ - set vpn ipsec ike-group IKE-TEST dead-peer-detection timeout 60
+ - set vpn ipsec ike-group IKE-TEST proposal 1
+ - set vpn ipsec ike-group IKE-TEST proposal 1 encryption aes256
+ - set vpn ipsec ike-group IKE-TEST proposal 1 hash sha256
+ - set vpn ipsec ike-group IKE-TEST proposal 1 dh-group 14
+ after:
+ esp_group:
+ - name: ESP-TEST
+ compression: true
+ proposal:
+ - proposal_id: 1
+ encryption: aes256
+ hash: sha256
+ ike_group:
+ - name: IKE-TEST
+ key_exchange: ikev2
+ disable_mobike: true
+ dead_peer_detection:
+ action: restart
+ interval: 15
+ timeout: 60
+ proposal:
+ - proposal_id: 1
+ encryption: aes256
+ hash: sha256
+ dh_group: 14
+
+# Full pre-populated state used by replaced/overridden/deleted/gathered --
+# matches the same shape as the unit test fixture, so expected
+# commands/after values here are grounded in what's already unit-tested,
+# not invented fresh.
+populate_config:
+ esp_group:
+ - name: ESP-TEST
+ compression: true
+ proposal:
+ - proposal_id: 1
+ encryption: aes256
+ hash: sha256
+ ike_group:
+ - name: IKE-TEST
+ key_exchange: ikev2
+ disable_mobike: true
+ dead_peer_detection:
+ action: restart
+ interval: 15
+ timeout: 60
+ proposal:
+ - proposal_id: 1
+ encryption: aes256
+ hash: sha256
+ dh_group: 14
+ - name: IKE-EXTRA
+ key_exchange: ikev1
+ authentication:
+ psk:
+ - name: PSK-TEST
+ id:
+ - local@example.com
+ - remote@example.com
+ secret: test-not-real-secret
+ ppk:
+ - name: PPK-TEST
+ id:
+ - ppk-id-1
+ secret: test-ppk-secret
+ profile:
+ - name: testprofile
+ authentication:
+ mode: pre-shared-secret
+ pre_shared_secret: test-not-real-secret
+ bind_tunnel:
+ - tun0
+ esp_group: ESP-TEST
+ ike_group: IKE-TEST
+ disable_uniqreqids: true
+ interface:
+ - eth0
+ - eth1
+ log:
+ level: 1
+ subsystem:
+ - ike
+ - chd
+ options:
+ flexvpn: true
+ retransmission:
+ attempts: 3
+
+replaced:
+ config:
+ esp_group:
+ - name: ESP-TEST
+ proposal:
+ - proposal_id: 1
+ encryption: aes128
+ hash: sha256
+ commands:
+ - delete vpn ipsec esp-group ESP-TEST compression
+ - set vpn ipsec esp-group ESP-TEST proposal 1 encryption aes128
+ - delete vpn ipsec disable-uniqreqids
+ - delete vpn ipsec interface eth0
+ - delete vpn ipsec interface eth1
+ - delete vpn ipsec log level 1
+ - delete vpn ipsec log subsystem chd
+ - delete vpn ipsec log subsystem ike
+ - delete vpn ipsec options flexvpn
+ - delete vpn ipsec options retransmission attempts 3
+ # NOTE: ike_group/profile/authentication are NOT in `after` deletions --
+ # they're left untouched since replaced only reconciles items it names.
+ # `after` below reflects only what replaced's scope actually changes;
+ # the un-named sections (ike_group, profile, authentication) remain
+ # exactly as populate_config left them and are checked separately in
+ # replaced.yaml via ansible_facts rather than duplicated here.
+ after:
+ esp_group:
+ - name: ESP-TEST
+ proposal:
+ - proposal_id: 1
+ encryption: aes128
+ hash: sha256
+
+overridden:
+ config:
+ esp_group:
+ - name: ESP-TEST
+ proposal:
+ - proposal_id: 1
+ encryption: aes128
+ hash: sha256
+ commands:
+ - delete vpn ipsec esp-group ESP-TEST compression
+ - set vpn ipsec esp-group ESP-TEST proposal 1 encryption aes128
+ - delete vpn ipsec ike-group IKE-TEST
+ - delete vpn ipsec ike-group IKE-EXTRA
+ - delete vpn ipsec profile testprofile
+ - delete vpn ipsec authentication psk PSK-TEST
+ - delete vpn ipsec authentication ppk PPK-TEST
+ - delete vpn ipsec disable-uniqreqids
+ - delete vpn ipsec interface eth0
+ - delete vpn ipsec interface eth1
+ - delete vpn ipsec log level 1
+ - delete vpn ipsec log subsystem chd
+ - delete vpn ipsec log subsystem ike
+ - delete vpn ipsec options flexvpn
+ - delete vpn ipsec options retransmission attempts 3
+ after:
+ esp_group:
+ - name: ESP-TEST
+ proposal:
+ - proposal_id: 1
+ encryption: aes128
+ hash: sha256
+
+deleted:
+ commands:
+ - delete vpn ipsec esp-group ESP-TEST
+ - delete vpn ipsec ike-group IKE-TEST
+ - delete vpn ipsec ike-group IKE-EXTRA
+ - delete vpn ipsec profile testprofile
+ - delete vpn ipsec authentication psk PSK-TEST
+ - delete vpn ipsec authentication ppk PPK-TEST
+ - delete vpn ipsec disable-uniqreqids
+ - delete vpn ipsec interface eth0
+ - delete vpn ipsec interface eth1
+ - delete vpn ipsec log level 1
+ - delete vpn ipsec log subsystem chd
+ - delete vpn ipsec log subsystem ike
+ - delete vpn ipsec options flexvpn
+ - delete vpn ipsec options retransmission attempts 3
+ after: {}
+
+rendered:
+ config:
+ esp_group:
+ - name: ESP-RENDER-TEST
+ proposal:
+ - proposal_id: 1
+ encryption: aes256
+ hash: sha256
+ commands:
+ - set vpn ipsec esp-group ESP-RENDER-TEST
+ - set vpn ipsec esp-group ESP-RENDER-TEST proposal 1
+ - set vpn ipsec esp-group ESP-RENDER-TEST proposal 1 encryption aes256
+ - set vpn ipsec esp-group ESP-RENDER-TEST proposal 1 hash sha256
+
+parsed:
+ after:
+ esp_group:
+ - name: ESP-PARSE-TEST
+ proposal:
+ - proposal_id: 1
+ encryption: aes256
+ hash: sha256
+ ike_group:
+ - name: IKE-PARSE-TEST
+ key_exchange: ikev2
diff --git a/tests/integration/targets/vyos_vpn_ipsec_s2s/defaults/main.yaml b/tests/integration/targets/vyos_vpn_ipsec_s2s/defaults/main.yaml
new file mode 100644
index 00000000..164afead
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/defaults/main.yaml
@@ -0,0 +1,3 @@
+---
+testcase: "[^_].*"
+test_items: []
diff --git a/tests/integration/targets/vyos_vpn_ipsec_s2s/meta/main.yaml b/tests/integration/targets/vyos_vpn_ipsec_s2s/meta/main.yaml
new file mode 100644
index 00000000..ed97d539
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/meta/main.yaml
@@ -0,0 +1 @@
+---
diff --git a/tests/integration/targets/vyos_vpn_ipsec_s2s/tasks/cli.yaml b/tests/integration/targets/vyos_vpn_ipsec_s2s/tasks/cli.yaml
new file mode 100644
index 00000000..daccf720
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/tasks/cli.yaml
@@ -0,0 +1,20 @@
+---
+- name: Collect all cli test cases
+ ansible.builtin.find:
+ paths: "{{ role_path }}/tests/cli"
+ patterns: "{{ testcase }}.yaml"
+ use_regex: true
+ register: test_cases
+ delegate_to: localhost
+
+- name: Set test_items
+ ansible.builtin.set_fact:
+ test_items: "{{ test_cases.files | map(attribute='path') | list }}"
+
+- name: Run test case (connection=ansible.netcommon.network_cli)
+ ansible.builtin.include_tasks: "{{ test_case_to_run }}"
+ vars:
+ ansible_connection: ansible.netcommon.network_cli
+ with_items: "{{ test_items }}"
+ loop_control:
+ loop_var: test_case_to_run
diff --git a/tests/integration/targets/vyos_vpn_ipsec_s2s/tasks/main.yaml b/tests/integration/targets/vyos_vpn_ipsec_s2s/tasks/main.yaml
new file mode 100644
index 00000000..e6378581
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/tasks/main.yaml
@@ -0,0 +1,5 @@
+---
+- name: Run CLI tests
+ ansible.builtin.include_tasks: cli.yaml
+ tags:
+ - network_cli
diff --git a/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/_parsed.cfg b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/_parsed.cfg
new file mode 100644
index 00000000..37b7fb4e
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/_parsed.cfg
@@ -0,0 +1 @@
+set vpn ipsec site-to-site peer PEER-PARSE-TEST ike-group 'IKE-TEST'
diff --git a/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/_populate.yaml b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/_populate.yaml
new file mode 100644
index 00000000..b196d27a
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/_populate.yaml
@@ -0,0 +1,37 @@
+---
+- ansible.builtin.include_tasks: _remove_config.yaml
+
+# Real device dependencies discovered while building this module:
+# vpn ipsec site-to-site peer references esp-group/ike-group by name
+# (from the vyos_vpn_ipsec profile-layer module), and vti.bind requires
+# the referenced interface to already exist -- neither is created by
+# this module itself. Made self-contained here rather than assuming
+# vyos_vpn_ipsec's own tests already left this state behind.
+- name: PREREQ Create the esp-group/ike-group referenced by the test peer
+ vyos.vyos.vyos_vpn_ipsec:
+ config:
+ esp_group:
+ - name: ESP-TEST
+ proposal:
+ - proposal_id: 1
+ encryption: aes256
+ hash: sha256
+ ike_group:
+ - name: IKE-TEST
+ key_exchange: ikev2
+ proposal:
+ - proposal_id: 1
+ encryption: aes256
+ hash: sha256
+ state: merged
+
+- name: PREREQ Create the vti0 interface referenced by vti.bind
+ vyos.vyos.vyos_config:
+ lines:
+ - set interfaces vti vti0
+
+- name: POPULATE Apply the provided configuration
+ register: result
+ vyos.vyos.vyos_vpn_ipsec_s2s:
+ config: "{{ populate_config }}"
+ state: merged
diff --git a/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/_remove_config.yaml b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/_remove_config.yaml
new file mode 100644
index 00000000..a5da6334
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/_remove_config.yaml
@@ -0,0 +1,13 @@
+---
+- name: delete the provided configuration
+ vyos.vyos.vyos_vpn_ipsec_s2s:
+ state: deleted
+
+- name: Remove the prerequisite esp-group/ike-group
+ vyos.vyos.vyos_vpn_ipsec:
+ state: deleted
+
+- name: Remove the prerequisite vti0 interface
+ vyos.vyos.vyos_config:
+ lines:
+ - delete interfaces vti vti0
diff --git a/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/deleted.yaml b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/deleted.yaml
new file mode 100644
index 00000000..dc870783
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/deleted.yaml
@@ -0,0 +1,60 @@
+---
+- debug:
+ msg: START vyos_vpn_ipsec_s2s deleted integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _populate.yaml
+
+- block:
+ - name: TEST - [deleted] Remove all configuration
+ vyos.vyos.vyos_vpn_ipsec_s2s:
+ state: deleted
+ register: result
+
+ - name: TEST - [deleted] Assert that correct set of commands were generated
+ assert:
+ that:
+ - "{{ deleted['commands'] | symmetric_difference(result['commands']) | length == 0 }}"
+ - result.changed == true
+ - result.after == deleted.after
+
+ - name: TEST - [deleted] Remove all configuration again (IDEMPOTENT)
+ vyos.vyos.vyos_vpn_ipsec_s2s:
+ state: deleted
+ register: result
+
+ - name: TEST - [deleted] Assert idempotent
+ assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
+
+- block:
+ - include_tasks: _populate.yaml
+
+ - name: TEST - [deleted scoped] Remove only PEER-EXTRA by name
+ vyos.vyos.vyos_vpn_ipsec_s2s:
+ config:
+ peer:
+ - name: PEER-EXTRA
+ state: deleted
+ register: result
+
+ - name: TEST - [deleted scoped] Assert only PEER-EXTRA was removed
+ assert:
+ that:
+ - result.commands == ["delete vpn ipsec site-to-site peer PEER-EXTRA"]
+ - result.changed == true
+
+ - vyos.vyos.vyos_facts:
+ gather_network_resources: vpn_ipsec_s2s
+
+ - name: TEST - [deleted scoped] Assert PEER-TEST survived
+ assert:
+ that:
+ - ansible_facts['network_resources']['vpn_ipsec_s2s']['peer'] | map(attribute='name') | list == ['PEER-TEST']
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/empty_config.yaml b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/empty_config.yaml
new file mode 100644
index 00000000..026cf22c
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/empty_config.yaml
@@ -0,0 +1,58 @@
+---
+- debug:
+ msg: START vyos_vpn_ipsec_s2s empty_config integration tests on connection={{ ansible_connection }}
+
+- name: Merged with empty config should give appropriate error message
+ register: result
+ ignore_errors: true
+ vyos.vyos.vyos_vpn_ipsec_s2s:
+ config:
+ state: merged
+
+- assert:
+ that:
+ - result.msg == 'value of config parameter must not be empty for state merged'
+
+- name: Replaced with empty config should give appropriate error message
+ register: result
+ ignore_errors: true
+ vyos.vyos.vyos_vpn_ipsec_s2s:
+ config:
+ state: replaced
+
+- assert:
+ that:
+ - result.msg == 'value of config parameter must not be empty for state replaced'
+
+- name: Overridden with empty config should give appropriate error message
+ register: result
+ ignore_errors: true
+ vyos.vyos.vyos_vpn_ipsec_s2s:
+ config:
+ state: overridden
+
+- assert:
+ that:
+ - result.msg == 'value of config parameter must not be empty for state overridden'
+
+- name: Rendered with empty config should give appropriate error message
+ register: result
+ ignore_errors: true
+ vyos.vyos.vyos_vpn_ipsec_s2s:
+ config:
+ state: rendered
+
+- assert:
+ that:
+ - result.msg == 'value of config parameter must not be empty for state rendered'
+
+- name: Parsed with empty running_config should give appropriate error message
+ register: result
+ ignore_errors: true
+ vyos.vyos.vyos_vpn_ipsec_s2s:
+ running_config:
+ state: parsed
+
+- assert:
+ that:
+ - result.msg == 'value of running_config parameter must not be empty for state parsed'
diff --git a/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/gathered.yaml b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/gathered.yaml
new file mode 100644
index 00000000..9b9adfd6
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/gathered.yaml
@@ -0,0 +1,20 @@
+---
+- debug:
+ msg: START vyos_vpn_ipsec_s2s gathered integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _populate.yaml
+
+- block:
+ - name: TEST - [gathered] Gather the existing running configuration
+ register: result
+ vyos.vyos.vyos_vpn_ipsec_s2s:
+ state: gathered
+
+ - name: TEST - [gathered] Assert
+ assert:
+ that:
+ - result.changed == false
+ - result.gathered.peer | map(attribute='name') | sort | list == ['PEER-EXTRA', 'PEER-TEST']
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/merged.yaml b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/merged.yaml
new file mode 100644
index 00000000..06b528fb
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/merged.yaml
@@ -0,0 +1,58 @@
+---
+- debug:
+ msg: START vyos_vpn_ipsec_s2s merged integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+
+- name: PREREQ Create the esp-group/ike-group referenced by the test peer
+ vyos.vyos.vyos_vpn_ipsec:
+ config:
+ esp_group:
+ - name: ESP-TEST
+ proposal:
+ - proposal_id: 1
+ encryption: aes256
+ hash: sha256
+ ike_group:
+ - name: IKE-TEST
+ key_exchange: ikev2
+ proposal:
+ - proposal_id: 1
+ encryption: aes256
+ hash: sha256
+ state: merged
+
+- name: PREREQ Create the vti0 interface referenced by vti.bind
+ vyos.vyos.vyos_config:
+ lines:
+ - set interfaces vti vti0
+
+- block:
+ - name: Merge the provided configuration with the existing running configuration
+ register: result
+ vyos.vyos.vyos_vpn_ipsec_s2s: &id001
+ config: "{{ merged.config }}"
+ state: merged
+
+ - vyos.vyos.vyos_facts:
+ gather_network_resources: vpn_ipsec_s2s
+
+ - name: Assert that correct set of commands were generated
+ assert:
+ that:
+ - "{{ merged['commands'] | symmetric_difference(result['commands']) | length == 0 }}"
+ - result.changed == true
+ - result.before == {}
+
+ - name: Merge the provided configuration with the existing running configuration (IDEMPOTENT)
+ register: result
+ vyos.vyos.vyos_vpn_ipsec_s2s: *id001
+
+ - name: Assert that the previous task was idempotent
+ assert:
+ that:
+ - result['changed'] == false
+ - result['commands'] == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/overridden.yaml b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/overridden.yaml
new file mode 100644
index 00000000..04a5a990
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/overridden.yaml
@@ -0,0 +1,39 @@
+---
+- debug:
+ msg: START vyos_vpn_ipsec_s2s overridden integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _populate.yaml
+
+- block:
+ - name: Override with only PEER-TEST named -- PEER-EXTRA must be removed
+ register: result
+ vyos.vyos.vyos_vpn_ipsec_s2s: &id001
+ config: "{{ overridden.config }}"
+ state: overridden
+
+ - vyos.vyos.vyos_facts:
+ gather_network_resources: vpn_ipsec_s2s
+
+ - name: Assert that correct set of commands were generated
+ assert:
+ that:
+ - "{{ overridden['commands'] | symmetric_difference(result['commands']) | length == 0 }}"
+ - result.changed == true
+
+ - name: Assert that only PEER-TEST remains
+ assert:
+ that:
+ - ansible_facts['network_resources']['vpn_ipsec_s2s']['peer'] | map(attribute='name') | list == ['PEER-TEST']
+
+ - name: Override with the same configuration (IDEMPOTENT)
+ register: result
+ vyos.vyos.vyos_vpn_ipsec_s2s: *id001
+
+ - name: Assert that the previous task was idempotent
+ assert:
+ that:
+ - result['changed'] == false
+ - result['commands'] == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/parsed.yaml b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/parsed.yaml
new file mode 100644
index 00000000..114d19d0
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/parsed.yaml
@@ -0,0 +1,14 @@
+---
+- debug:
+ msg: START vyos_vpn_ipsec_s2s parsed integration tests on connection={{ ansible_connection }}
+
+- name: TEST - [parsed] Parse the commands for provided configuration
+ register: result
+ vyos.vyos.vyos_vpn_ipsec_s2s:
+ running_config: "{{ lookup('file', '_parsed.cfg') }}"
+ state: parsed
+
+- assert:
+ that:
+ - result.changed == false
+ - parsed['after'] == result.parsed
diff --git a/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/rendered.yaml b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/rendered.yaml
new file mode 100644
index 00000000..dd5a8ddf
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/rendered.yaml
@@ -0,0 +1,15 @@
+---
+- debug:
+ msg: START vyos_vpn_ipsec_s2s rendered integration tests on connection={{ ansible_connection }}
+
+- block:
+ - name: Structure provided configuration into device specific commands
+ register: result
+ vyos.vyos.vyos_vpn_ipsec_s2s:
+ config: "{{ rendered.config }}"
+ state: rendered
+
+ - name: Assert that correct set of commands were generated
+ assert:
+ that:
+ - "{{ rendered['commands'] | symmetric_difference(result['rendered']) | length == 0 }}"
diff --git a/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/replaced.yaml b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/replaced.yaml
new file mode 100644
index 00000000..78e4158e
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/replaced.yaml
@@ -0,0 +1,42 @@
+---
+- debug:
+ msg: START vyos_vpn_ipsec_s2s replaced integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _populate.yaml
+
+- block:
+ - name: Replace only the named peer -- other sections must be untouched
+ register: result
+ vyos.vyos.vyos_vpn_ipsec_s2s: &id001
+ config: "{{ replaced.config }}"
+ state: replaced
+
+ - vyos.vyos.vyos_facts:
+ gather_network_resources: vpn_ipsec_s2s
+
+ - name: Assert that correct set of commands were generated
+ assert:
+ that:
+ - "{{ replaced['commands'] | symmetric_difference(result['commands']) | length == 0 }}"
+ - result.changed == true
+
+ - name: Assert that PEER-EXTRA was left completely untouched
+ assert:
+ that:
+ - >-
+ (ansible_facts['network_resources']['vpn_ipsec_s2s']['peer']
+ | selectattr('name', 'equalto', 'PEER-EXTRA') | list | first)
+ == (populate_config.peer | selectattr('name', 'equalto', 'PEER-EXTRA') | list | first)
+
+ - name: Replace with the same configuration (IDEMPOTENT)
+ register: result
+ vyos.vyos.vyos_vpn_ipsec_s2s: *id001
+
+ - name: Assert that the previous task was idempotent
+ assert:
+ that:
+ - result['changed'] == false
+ - result['commands'] == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_vpn_ipsec_s2s/vars/main.yaml b/tests/integration/targets/vyos_vpn_ipsec_s2s/vars/main.yaml
new file mode 100644
index 00000000..b592b73d
--- /dev/null
+++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/vars/main.yaml
@@ -0,0 +1,257 @@
+---
+populate_config:
+ peer:
+ - name: PEER-TEST
+ authentication:
+ local_id: local@example.com
+ mode: pre-shared-secret
+ remote_id: remote@example.com
+ childless: prefer
+ connection_type: initiate
+ default_esp_group: ESP-TEST
+ description: test peer for site-to-site module
+ force_udp_encapsulation: true
+ ike_group: IKE-TEST
+ ikev2_reauth: inherit
+ local_address: any
+ remote_address:
+ - 203.0.113.1
+ replay_window: 32
+ virtual_address:
+ - 0.0.0.0
+ tunnel:
+ - tunnel_id: 1
+ esp_group: ESP-TEST
+ priority: 10
+ protocol: tcp
+ local:
+ port: 443
+ prefix:
+ - 10.0.0.0/24
+ remote:
+ port: 443
+ prefix:
+ - 10.1.0.0/24
+ vti:
+ bind: vti0
+ esp_group: ESP-TEST
+ traffic_selector:
+ local:
+ prefix:
+ - 10.2.0.0/24
+ remote:
+ prefix:
+ - 10.3.0.0/24
+ - name: PEER-EXTRA
+ # Minimal, but genuinely commit-valid -- confirmed via FOUR
+ # separate real commit failures that VyOS requires, on every
+ # site-to-site peer regardless of how minimal: authentication, a
+ # real remote-address, a local-address (or dhcp-interface), and
+ # at least one of tunnel/vti (a cross-field constraint Ansible's
+ # argspec has no clean way to express for nested list-of-dict
+ # peer items -- worth documenting in the module itself, not just
+ # here).
+ ike_group: IKE-TEST
+ authentication:
+ mode: pre-shared-secret
+ local_id: extra-local@example.com
+ remote_id: extra-remote@example.com
+ remote_address:
+ - 198.51.100.1
+ local_address: any
+ tunnel:
+ - tunnel_id: 1
+ esp_group: ESP-TEST
+
+merged:
+ config:
+ peer:
+ - name: PEER-TEST
+ authentication:
+ local_id: local@example.com
+ mode: pre-shared-secret
+ remote_id: remote@example.com
+ childless: prefer
+ connection_type: initiate
+ default_esp_group: ESP-TEST
+ description: test peer for site-to-site module
+ force_udp_encapsulation: true
+ ike_group: IKE-TEST
+ ikev2_reauth: inherit
+ local_address: any
+ remote_address:
+ - 203.0.113.1
+ replay_window: 32
+ virtual_address:
+ - 0.0.0.0
+ tunnel:
+ - tunnel_id: 1
+ esp_group: ESP-TEST
+ priority: 10
+ protocol: tcp
+ local:
+ port: 443
+ prefix:
+ - 10.0.0.0/24
+ remote:
+ port: 443
+ prefix:
+ - 10.1.0.0/24
+ vti:
+ bind: vti0
+ esp_group: ESP-TEST
+ traffic_selector:
+ local:
+ prefix:
+ - 10.2.0.0/24
+ remote:
+ prefix:
+ - 10.3.0.0/24
+ commands:
+ - set vpn ipsec site-to-site peer PEER-TEST
+ - set vpn ipsec site-to-site peer PEER-TEST authentication local-id 'local@example.com'
+ - set vpn ipsec site-to-site peer PEER-TEST authentication remote-id 'remote@example.com'
+ - set vpn ipsec site-to-site peer PEER-TEST authentication mode 'pre-shared-secret'
+ - set vpn ipsec site-to-site peer PEER-TEST childless 'prefer'
+ - set vpn ipsec site-to-site peer PEER-TEST connection-type 'initiate'
+ - set vpn ipsec site-to-site peer PEER-TEST default-esp-group 'ESP-TEST'
+ - set vpn ipsec site-to-site peer PEER-TEST description 'test peer for site-to-site module'
+ - set vpn ipsec site-to-site peer PEER-TEST ike-group 'IKE-TEST'
+ - set vpn ipsec site-to-site peer PEER-TEST ikev2-reauth 'inherit'
+ - set vpn ipsec site-to-site peer PEER-TEST local-address 'any'
+ - set vpn ipsec site-to-site peer PEER-TEST force-udp-encapsulation
+ - set vpn ipsec site-to-site peer PEER-TEST replay-window '32'
+ - set vpn ipsec site-to-site peer PEER-TEST remote-address '203.0.113.1'
+ - set vpn ipsec site-to-site peer PEER-TEST virtual-address '0.0.0.0'
+ - set vpn ipsec site-to-site peer PEER-TEST tunnel 1
+ - set vpn ipsec site-to-site peer PEER-TEST tunnel 1 esp-group 'ESP-TEST'
+ - set vpn ipsec site-to-site peer PEER-TEST tunnel 1 protocol 'tcp'
+ - set vpn ipsec site-to-site peer PEER-TEST tunnel 1 priority '10'
+ - set vpn ipsec site-to-site peer PEER-TEST tunnel 1 local port '443'
+ - set vpn ipsec site-to-site peer PEER-TEST tunnel 1 local prefix '10.0.0.0/24'
+ - set vpn ipsec site-to-site peer PEER-TEST tunnel 1 remote port '443'
+ - set vpn ipsec site-to-site peer PEER-TEST tunnel 1 remote prefix '10.1.0.0/24'
+ - set vpn ipsec site-to-site peer PEER-TEST vti bind 'vti0'
+ - set vpn ipsec site-to-site peer PEER-TEST vti esp-group 'ESP-TEST'
+ - set vpn ipsec site-to-site peer PEER-TEST vti traffic-selector local prefix '10.2.0.0/24'
+ - set vpn ipsec site-to-site peer PEER-TEST vti traffic-selector remote prefix '10.3.0.0/24'
+
+replaced:
+ config:
+ peer:
+ - name: PEER-TEST
+ authentication:
+ local_id: local@example.com
+ mode: pre-shared-secret
+ remote_id: remote@example.com
+ childless: prefer
+ connection_type: initiate
+ default_esp_group: ESP-TEST
+ description: test peer for site-to-site module
+ force_udp_encapsulation: true
+ ike_group: IKE-TEST
+ ikev2_reauth: inherit
+ local_address: any
+ remote_address:
+ - 203.0.113.1
+ replay_window: 32
+ virtual_address:
+ - 0.0.0.0
+ tunnel:
+ - tunnel_id: 1
+ esp_group: ESP-TEST
+ priority: 99
+ protocol: tcp
+ local:
+ port: 443
+ prefix:
+ - 10.0.0.0/24
+ remote:
+ port: 443
+ prefix:
+ - 10.1.0.0/24
+ vti:
+ bind: vti0
+ esp_group: ESP-TEST
+ traffic_selector:
+ local:
+ prefix:
+ - 10.2.0.0/24
+ remote:
+ prefix:
+ - 10.3.0.0/24
+ # Only the priority differs from populate_config -- PEER-TEST is named
+ # fully/exactly otherwise, so replaced's per-item reconciliation has
+ # nothing else to change. PEER-EXTRA is not named at all and must
+ # survive untouched (checked separately in replaced.yaml).
+ commands:
+ - set vpn ipsec site-to-site peer PEER-TEST tunnel 1 priority '99'
+
+overridden:
+ config:
+ peer:
+ - name: PEER-TEST
+ authentication:
+ local_id: local@example.com
+ mode: pre-shared-secret
+ remote_id: remote@example.com
+ childless: prefer
+ connection_type: initiate
+ default_esp_group: ESP-TEST
+ description: test peer for site-to-site module
+ force_udp_encapsulation: true
+ ike_group: IKE-TEST
+ ikev2_reauth: inherit
+ local_address: any
+ remote_address:
+ - 203.0.113.1
+ replay_window: 32
+ virtual_address:
+ - 0.0.0.0
+ tunnel:
+ - tunnel_id: 1
+ esp_group: ESP-TEST
+ priority: 99
+ protocol: tcp
+ local:
+ port: 443
+ prefix:
+ - 10.0.0.0/24
+ remote:
+ port: 443
+ prefix:
+ - 10.1.0.0/24
+ vti:
+ bind: vti0
+ esp_group: ESP-TEST
+ traffic_selector:
+ local:
+ prefix:
+ - 10.2.0.0/24
+ remote:
+ prefix:
+ - 10.3.0.0/24
+ commands:
+ - delete vpn ipsec site-to-site peer PEER-EXTRA
+ - set vpn ipsec site-to-site peer PEER-TEST tunnel 1 priority '99'
+
+deleted:
+ commands:
+ - delete vpn ipsec site-to-site peer PEER-TEST
+ - delete vpn ipsec site-to-site peer PEER-EXTRA
+ after: {}
+
+rendered:
+ config:
+ peer:
+ - name: PEER-RENDER-TEST
+ ike_group: IKE-TEST
+ commands:
+ - set vpn ipsec site-to-site peer PEER-RENDER-TEST
+ - set vpn ipsec site-to-site peer PEER-RENDER-TEST ike-group 'IKE-TEST'
+
+parsed:
+ after:
+ peer:
+ - name: PEER-PARSE-TEST
+ ike_group: IKE-TEST
diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_vpn_ipsec_config.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_vpn_ipsec_config.cfg
new file mode 100644
index 00000000..d0d0e19e
--- /dev/null
+++ b/tests/unit/modules/network/vyos/fixtures/vyos_vpn_ipsec_config.cfg
@@ -0,0 +1,30 @@
+set vpn ipsec authentication ppk PPK-TEST id 'ppk-id-1'
+set vpn ipsec authentication ppk PPK-TEST secret 'test-ppk-secret'
+set vpn ipsec authentication psk PSK-TEST id 'local@example.com'
+set vpn ipsec authentication psk PSK-TEST id 'remote@example.com'
+set vpn ipsec authentication psk PSK-TEST secret 'test-not-real-secret'
+set vpn ipsec disable-uniqreqids
+set vpn ipsec esp-group ESP-TEST compression
+set vpn ipsec esp-group ESP-TEST proposal 1 encryption 'aes256'
+set vpn ipsec esp-group ESP-TEST proposal 1 hash 'sha256'
+set vpn ipsec ike-group IKE-TEST key-exchange 'ikev2'
+set vpn ipsec ike-group IKE-TEST dead-peer-detection action 'restart'
+set vpn ipsec ike-group IKE-TEST dead-peer-detection interval '15'
+set vpn ipsec ike-group IKE-TEST dead-peer-detection timeout '60'
+set vpn ipsec ike-group IKE-TEST disable-mobike
+set vpn ipsec ike-group IKE-TEST proposal 1 dh-group '14'
+set vpn ipsec ike-group IKE-TEST proposal 1 encryption 'aes256'
+set vpn ipsec ike-group IKE-TEST proposal 1 hash 'sha256'
+set vpn ipsec ike-group IKE-EXTRA key-exchange 'ikev1'
+set vpn ipsec interface 'eth0'
+set vpn ipsec interface 'eth1'
+set vpn ipsec log level '1'
+set vpn ipsec log subsystem 'ike'
+set vpn ipsec log subsystem 'chd'
+set vpn ipsec options flexvpn
+set vpn ipsec options retransmission attempts '3'
+set vpn ipsec profile testprofile authentication mode 'pre-shared-secret'
+set vpn ipsec profile testprofile authentication pre-shared-secret 'test-not-real-secret'
+set vpn ipsec profile testprofile bind tunnel 'tun0'
+set vpn ipsec profile testprofile esp-group 'ESP-TEST'
+set vpn ipsec profile testprofile ike-group 'IKE-TEST'
diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_vpn_ipsec_s2s_config.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_vpn_ipsec_s2s_config.cfg
new file mode 100644
index 00000000..0d674b8f
--- /dev/null
+++ b/tests/unit/modules/network/vyos/fixtures/vyos_vpn_ipsec_s2s_config.cfg
@@ -0,0 +1,26 @@
+set vpn ipsec site-to-site peer PEER-TEST authentication local-id 'local@example.com'
+set vpn ipsec site-to-site peer PEER-TEST authentication mode 'pre-shared-secret'
+set vpn ipsec site-to-site peer PEER-TEST authentication remote-id 'remote@example.com'
+set vpn ipsec site-to-site peer PEER-TEST childless 'prefer'
+set vpn ipsec site-to-site peer PEER-TEST connection-type 'initiate'
+set vpn ipsec site-to-site peer PEER-TEST default-esp-group 'ESP-TEST'
+set vpn ipsec site-to-site peer PEER-TEST description 'test peer for site-to-site module'
+set vpn ipsec site-to-site peer PEER-TEST force-udp-encapsulation
+set vpn ipsec site-to-site peer PEER-TEST ike-group 'IKE-TEST'
+set vpn ipsec site-to-site peer PEER-TEST ikev2-reauth 'inherit'
+set vpn ipsec site-to-site peer PEER-TEST local-address 'any'
+set vpn ipsec site-to-site peer PEER-TEST remote-address '203.0.113.1'
+set vpn ipsec site-to-site peer PEER-TEST replay-window '32'
+set vpn ipsec site-to-site peer PEER-TEST tunnel 1 esp-group 'ESP-TEST'
+set vpn ipsec site-to-site peer PEER-TEST tunnel 1 local port '443'
+set vpn ipsec site-to-site peer PEER-TEST tunnel 1 local prefix '10.0.0.0/24'
+set vpn ipsec site-to-site peer PEER-TEST tunnel 1 priority '10'
+set vpn ipsec site-to-site peer PEER-TEST tunnel 1 protocol 'tcp'
+set vpn ipsec site-to-site peer PEER-TEST tunnel 1 remote port '443'
+set vpn ipsec site-to-site peer PEER-TEST tunnel 1 remote prefix '10.1.0.0/24'
+set vpn ipsec site-to-site peer PEER-TEST virtual-address '0.0.0.0'
+set vpn ipsec site-to-site peer PEER-TEST vti bind 'vti0'
+set vpn ipsec site-to-site peer PEER-TEST vti esp-group 'ESP-TEST'
+set vpn ipsec site-to-site peer PEER-TEST vti traffic-selector local prefix '10.2.0.0/24'
+set vpn ipsec site-to-site peer PEER-TEST vti traffic-selector remote prefix '10.3.0.0/24'
+set vpn ipsec site-to-site peer PEER-EXTRA ike-group 'IKE-TEST'
diff --git a/tests/unit/modules/network/vyos/test_vyos_vpn_ipsec.py b/tests/unit/modules/network/vyos/test_vyos_vpn_ipsec.py
new file mode 100644
index 00000000..6c616ad1
--- /dev/null
+++ b/tests/unit/modules/network/vyos/test_vyos_vpn_ipsec.py
@@ -0,0 +1,568 @@
+#
+# (c) 2026, Red Hat, inc
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+#
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from textwrap import dedent
+from unittest.mock import patch
+
+from ansible_collections.vyos.vyos.plugins.modules import vyos_vpn_ipsec
+from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args
+
+from .vyos_module import TestVyosModule, load_fixture
+
+
+class TestVyosVpnIpsecModule(TestVyosModule):
+ module = vyos_vpn_ipsec
+
+ def setUp(self):
+ super(TestVyosVpnIpsecModule, self).setUp()
+
+ self.mock_get_resource_connection_config = patch(
+ "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base.get_resource_connection",
+ )
+ self.get_resource_connection_config = self.mock_get_resource_connection_config.start()
+
+ self.mock_get_resource_connection_facts = patch(
+ "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts.get_resource_connection",
+ )
+ self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start()
+
+ self.mock_execute_show_command = patch(
+ "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.vpn_ipsec.vpn_ipsec.Vpn_ipsecFacts.get_vpn_ipsec_data",
+ )
+ self.execute_show_command = self.mock_execute_show_command.start()
+
+ def tearDown(self):
+ super(TestVyosVpnIpsecModule, self).tearDown()
+ self.mock_get_resource_connection_config.stop()
+ self.mock_get_resource_connection_facts.stop()
+ self.mock_execute_show_command.stop()
+
+ # Single fixture file, reused across every test. Carries: two
+ # ike_group entries (IKE-TEST richly configured incl.
+ # dead_peer_detection/disable_mobike, IKE-EXTRA minimal, to prove
+ # "unlisted items preserved"); esp_group with compression+proposal;
+ # psk/ppk with multi-value id lists; a profile; and every top-level
+ # singleton field (disable_uniqreqids, interface, log, options).
+ def load_fixtures(self, commands=None, filename=None):
+ if filename == "EMPTY":
+ self.execute_show_command.side_effect = None
+ self.execute_show_command.return_value = ""
+ return
+
+ if filename is None:
+ filename = "vyos_vpn_ipsec_config.cfg"
+
+ def load_from_file(*args, **kwargs):
+ return load_fixture(filename)
+
+ self.execute_show_command.side_effect = load_from_file
+
+ # -------------------------------------------------------------------
+ # merged
+ # -------------------------------------------------------------------
+
+ def test_vyos_vpn_ipsec_merged_idempotent(self):
+ playbook = dict(
+ config=dict(
+ esp_group=[
+ dict(
+ name="ESP-TEST",
+ compression=True,
+ proposal=[dict(proposal_id=1, encryption="aes256", hash="sha256")],
+ ),
+ ],
+ ike_group=[
+ dict(
+ name="IKE-TEST",
+ key_exchange="ikev2",
+ disable_mobike=True,
+ dead_peer_detection=dict(action="restart", interval=15, timeout=60),
+ proposal=[
+ dict(proposal_id=1, encryption="aes256", hash="sha256", dh_group=14),
+ ],
+ ),
+ ],
+ ),
+ state="merged",
+ )
+ set_module_args(playbook)
+ result = self.execute_module(changed=False)
+ self.maxDiff = None
+ self.assertEqual(sorted(result["commands"]), [])
+
+ def test_vyos_vpn_ipsec_merged_new_group_leaves_existing_untouched(self):
+ playbook = dict(
+ config=dict(
+ esp_group=[
+ dict(
+ name="ESP-NEW",
+ proposal=[dict(proposal_id=1, encryption="aes256", hash="sha256")],
+ ),
+ ],
+ ),
+ state="merged",
+ )
+ compare_cmds = [
+ "set vpn ipsec esp-group ESP-NEW",
+ "set vpn ipsec esp-group ESP-NEW proposal 1",
+ "set vpn ipsec esp-group ESP-NEW proposal 1 encryption aes256",
+ "set vpn ipsec esp-group ESP-NEW proposal 1 hash sha256",
+ ]
+ set_module_args(playbook)
+ result = self.execute_module(changed=True)
+ self.maxDiff = None
+ self.assertEqual(sorted(result["commands"]), sorted(compare_cmds))
+
+ def test_vyos_vpn_ipsec_merged_bool_field_toggle(self):
+ # disable_mobike explicitly set to True in want when have already
+ # has it True -> no-op; here we flip a different bool
+ # (compression on ESP-TEST is already True in have) by instead
+ # adding a brand new bool-bearing field: options.virtual_ip,
+ # entirely absent from have.
+ playbook = dict(config=dict(options=dict(virtual_ip=True)), state="merged")
+ compare_cmds = ["set vpn ipsec options virtual-ip"]
+ set_module_args(playbook)
+ result = self.execute_module(changed=True)
+ self.maxDiff = None
+ self.assertEqual(sorted(result["commands"]), sorted(compare_cmds))
+
+ def test_vyos_vpn_ipsec_merged_add_psk_id(self):
+ # multi-value list field: add one id, existing ids must survive
+ playbook = dict(
+ config=dict(
+ authentication=dict(
+ psk=[dict(name="PSK-TEST", id=["third@example.com"])],
+ ),
+ ),
+ state="merged",
+ )
+ compare_cmds = ["set vpn ipsec authentication psk PSK-TEST id third@example.com"]
+ set_module_args(playbook)
+ result = self.execute_module(changed=True)
+ self.maxDiff = None
+ self.assertEqual(sorted(result["commands"]), sorted(compare_cmds))
+
+ def test_vyos_vpn_ipsec_merged_create_ppk(self):
+ playbook = dict(
+ config=dict(
+ authentication=dict(
+ ppk=[dict(name="PPK-NEW", id=["new-ppk-id"], secret="new-secret")],
+ ),
+ ),
+ state="merged",
+ )
+ compare_cmds = [
+ "set vpn ipsec authentication ppk PPK-NEW",
+ "set vpn ipsec authentication ppk PPK-NEW id new-ppk-id",
+ "set vpn ipsec authentication ppk PPK-NEW secret 'new-secret'",
+ ]
+ set_module_args(playbook)
+ result = self.execute_module(changed=True)
+ self.maxDiff = None
+ self.assertEqual(sorted(result["commands"]), sorted(compare_cmds))
+
+ def test_vyos_vpn_ipsec_merged_profile_add_bind_tunnel(self):
+ # multi-value list field on an EXISTING profile: add one tunnel,
+ # existing tun0 must survive
+ playbook = dict(
+ config=dict(
+ profile=[dict(name="testprofile", bind_tunnel=["tun1"])],
+ ),
+ state="merged",
+ )
+ compare_cmds = ["set vpn ipsec profile testprofile bind tunnel tun1"]
+ set_module_args(playbook)
+ result = self.execute_module(changed=True)
+ self.maxDiff = None
+ self.assertEqual(sorted(result["commands"]), sorted(compare_cmds))
+
+ def test_vyos_vpn_ipsec_merged_create_profile_from_scratch(self):
+ # Regression test: creating a brand new profile must correctly
+ # emit its authentication.mode/pre_shared_secret commands. This
+ # specific path was silently broken (a leftover dotted-Jinja
+ # setval caused render() to return nothing, so addcmd() quietly
+ # dropped both commands with no error) -- caught only by a real
+ # device integration test, since no existing unit test actually
+ # created a profile from scratch or touched its authentication
+ # sub-dict through generate_commands().
+ playbook = dict(
+ config=dict(
+ profile=[
+ dict(
+ name="NEWPROFILE",
+ authentication=dict(
+ mode="pre-shared-secret",
+ pre_shared_secret="brand-new-secret",
+ ),
+ esp_group="ESP-TEST",
+ ike_group="IKE-TEST",
+ ),
+ ],
+ ),
+ state="merged",
+ )
+ compare_cmds = [
+ "set vpn ipsec profile NEWPROFILE",
+ "set vpn ipsec profile NEWPROFILE authentication mode pre-shared-secret",
+ "set vpn ipsec profile NEWPROFILE authentication pre-shared-secret 'brand-new-secret'",
+ "set vpn ipsec profile NEWPROFILE esp-group ESP-TEST",
+ "set vpn ipsec profile NEWPROFILE ike-group IKE-TEST",
+ ]
+ set_module_args(playbook)
+ result = self.execute_module(changed=True)
+ self.maxDiff = None
+ self.assertEqual(sorted(result["commands"]), sorted(compare_cmds))
+
+ # -------------------------------------------------------------------
+ # replaced
+ # -------------------------------------------------------------------
+
+ def test_vyos_vpn_ipsec_replaced_only_named_item_touched(self):
+ playbook = dict(
+ config=dict(
+ esp_group=[
+ dict(
+ name="ESP-TEST",
+ proposal=[dict(proposal_id=1, encryption="aes128", hash="sha256")],
+ ),
+ ],
+ ),
+ state="replaced",
+ )
+ compare_cmds = [
+ # compression omitted from want -> reconciled away (attribute
+ # inside the selected instance is always reset under replaced)
+ "delete vpn ipsec esp-group ESP-TEST compression",
+ "set vpn ipsec esp-group ESP-TEST proposal 1 encryption aes128",
+ # ike_group/profile/psk/ppk not named at all -> left alone.
+ # Top-level singleton fields (no instance scope) are always
+ # reconciled under replaced/overridden regardless of what's
+ # named elsewhere.
+ "delete vpn ipsec disable-uniqreqids",
+ "delete vpn ipsec interface eth0",
+ "delete vpn ipsec interface eth1",
+ "delete vpn ipsec log level 1",
+ "delete vpn ipsec log subsystem chd",
+ "delete vpn ipsec log subsystem ike",
+ "delete vpn ipsec options flexvpn",
+ "delete vpn ipsec options retransmission attempts 3",
+ ]
+ set_module_args(playbook)
+ result = self.execute_module(changed=True)
+ self.maxDiff = None
+ self.assertEqual(sorted(result["commands"]), sorted(compare_cmds))
+
+ def test_vyos_vpn_ipsec_replaced_idempotent(self):
+ # want fully mirrors the ENTIRE fixture state for every top-level
+ # singleton field plus the one named esp_group -- proves
+ # replaced's per-item/per-field reconciliation doesn't spuriously
+ # touch a fully-matching configuration.
+ playbook = dict(
+ config=dict(
+ esp_group=[
+ dict(
+ name="ESP-TEST",
+ compression=True,
+ proposal=[dict(proposal_id=1, encryption="aes256", hash="sha256")],
+ ),
+ ],
+ disable_uniqreqids=True,
+ interface=["eth0", "eth1"],
+ log=dict(level=1, subsystem=["chd", "ike"]),
+ options=dict(flexvpn=True, retransmission=dict(attempts=3)),
+ ),
+ state="replaced",
+ )
+ set_module_args(playbook)
+ result = self.execute_module(changed=False)
+ self.maxDiff = None
+ self.assertEqual(sorted(result["commands"]), [])
+
+ def test_vyos_vpn_ipsec_replaced_scoped_dead_peer_detection(self):
+ # naming ike_group under replaced without dead_peer_detection ->
+ # every DPD field reconciled away (nested-dict attribute reset)
+ playbook = dict(
+ config=dict(
+ ike_group=[dict(name="IKE-TEST", key_exchange="ikev2")],
+ ),
+ state="replaced",
+ )
+ compare_cmds = [
+ "delete vpn ipsec ike-group IKE-TEST dead-peer-detection action restart",
+ "delete vpn ipsec ike-group IKE-TEST dead-peer-detection interval 15",
+ "delete vpn ipsec ike-group IKE-TEST dead-peer-detection timeout 60",
+ "delete vpn ipsec ike-group IKE-TEST disable-mobike",
+ "delete vpn ipsec ike-group IKE-TEST proposal 1",
+ "delete vpn ipsec disable-uniqreqids",
+ "delete vpn ipsec interface eth0",
+ "delete vpn ipsec interface eth1",
+ "delete vpn ipsec log level 1",
+ "delete vpn ipsec log subsystem chd",
+ "delete vpn ipsec log subsystem ike",
+ "delete vpn ipsec options flexvpn",
+ "delete vpn ipsec options retransmission attempts 3",
+ ]
+ set_module_args(playbook)
+ result = self.execute_module(changed=True)
+ self.maxDiff = None
+ self.assertEqual(sorted(result["commands"]), sorted(compare_cmds))
+
+ # -------------------------------------------------------------------
+ # overridden
+ # -------------------------------------------------------------------
+
+ def test_vyos_vpn_ipsec_overridden_removes_unlisted_instances(self):
+ playbook = dict(
+ config=dict(
+ esp_group=[
+ dict(
+ name="ESP-TEST",
+ proposal=[dict(proposal_id=1, encryption="aes128", hash="sha256")],
+ ),
+ ],
+ ),
+ state="overridden",
+ )
+ compare_cmds = [
+ "delete vpn ipsec esp-group ESP-TEST compression",
+ "set vpn ipsec esp-group ESP-TEST proposal 1 encryption aes128",
+ "delete vpn ipsec ike-group IKE-TEST",
+ "delete vpn ipsec ike-group IKE-EXTRA",
+ "delete vpn ipsec profile testprofile",
+ "delete vpn ipsec authentication psk PSK-TEST",
+ "delete vpn ipsec authentication ppk PPK-TEST",
+ "delete vpn ipsec disable-uniqreqids",
+ "delete vpn ipsec interface eth0",
+ "delete vpn ipsec interface eth1",
+ "delete vpn ipsec log level 1",
+ "delete vpn ipsec log subsystem chd",
+ "delete vpn ipsec log subsystem ike",
+ "delete vpn ipsec options flexvpn",
+ "delete vpn ipsec options retransmission attempts 3",
+ ]
+ set_module_args(playbook)
+ result = self.execute_module(changed=True)
+ self.maxDiff = None
+ self.assertEqual(sorted(result["commands"]), sorted(compare_cmds))
+
+ # -------------------------------------------------------------------
+ # deleted -- bare (delete everything)
+ # -------------------------------------------------------------------
+
+ def test_vyos_vpn_ipsec_deleted_bare(self):
+ playbook = dict(state="deleted")
+ compare_cmds = [
+ "delete vpn ipsec esp-group ESP-TEST",
+ "delete vpn ipsec ike-group IKE-TEST",
+ "delete vpn ipsec ike-group IKE-EXTRA",
+ "delete vpn ipsec profile testprofile",
+ "delete vpn ipsec authentication psk PSK-TEST",
+ "delete vpn ipsec authentication ppk PPK-TEST",
+ "delete vpn ipsec disable-uniqreqids",
+ "delete vpn ipsec interface eth0",
+ "delete vpn ipsec interface eth1",
+ "delete vpn ipsec log level 1",
+ "delete vpn ipsec log subsystem chd",
+ "delete vpn ipsec log subsystem ike",
+ "delete vpn ipsec options flexvpn",
+ "delete vpn ipsec options retransmission attempts 3",
+ ]
+ set_module_args(playbook)
+ result = self.execute_module(changed=True)
+ self.maxDiff = None
+ self.assertEqual(sorted(result["commands"]), sorted(compare_cmds))
+
+ def test_vyos_vpn_ipsec_deleted_bare_idempotent(self):
+ playbook = dict(state="deleted")
+ set_module_args(playbook)
+ result = self.execute_module(changed=False, filename="EMPTY")
+ self.maxDiff = None
+ self.assertEqual(sorted(result["commands"]), [])
+
+ # -------------------------------------------------------------------
+ # deleted -- scoped (delete only what's named)
+ # -------------------------------------------------------------------
+
+ def test_vyos_vpn_ipsec_deleted_scoped_named_item_only(self):
+ playbook = dict(config=dict(ike_group=[dict(name="IKE-EXTRA")]), state="deleted")
+ compare_cmds = ["delete vpn ipsec ike-group IKE-EXTRA"]
+ set_module_args(playbook)
+ result = self.execute_module(changed=True)
+ self.maxDiff = None
+ self.assertEqual(sorted(result["commands"]), sorted(compare_cmds))
+
+ def test_vyos_vpn_ipsec_deleted_scoped_nonexistent_name_idempotent(self):
+ playbook = dict(config=dict(ike_group=[dict(name="IKE-NONEXISTENT")]), state="deleted")
+ set_module_args(playbook)
+ result = self.execute_module(changed=False)
+ self.maxDiff = None
+ self.assertEqual(sorted(result["commands"]), [])
+
+ def test_vyos_vpn_ipsec_deleted_scoped_interface_element(self):
+ # element-level list deletion: name only eth0, eth1 must survive
+ playbook = dict(config=dict(interface=["eth0"]), state="deleted")
+ compare_cmds = ["delete vpn ipsec interface eth0"]
+ set_module_args(playbook)
+ result = self.execute_module(changed=True)
+ self.maxDiff = None
+ self.assertEqual(sorted(result["commands"]), sorted(compare_cmds))
+
+ def test_vyos_vpn_ipsec_deleted_scoped_log_subsystem_element(self):
+ playbook = dict(config=dict(log=dict(subsystem=["chd"])), state="deleted")
+ compare_cmds = ["delete vpn ipsec log subsystem chd"]
+ set_module_args(playbook)
+ result = self.execute_module(changed=True)
+ self.maxDiff = None
+ self.assertEqual(sorted(result["commands"]), sorted(compare_cmds))
+
+ def test_vyos_vpn_ipsec_deleted_scoped_options_field(self):
+ playbook = dict(config=dict(options=dict(flexvpn=True)), state="deleted")
+ compare_cmds = ["delete vpn ipsec options flexvpn"]
+ set_module_args(playbook)
+ result = self.execute_module(changed=True)
+ self.maxDiff = None
+ self.assertEqual(sorted(result["commands"]), sorted(compare_cmds))
+
+ # -------------------------------------------------------------------
+ # rendered (no device contact -- have is empty regardless of fixture)
+ # -------------------------------------------------------------------
+
+ def test_vyos_vpn_ipsec_rendered(self):
+ playbook = dict(
+ config=dict(
+ esp_group=[
+ dict(
+ name="ESP-RENDER-TEST",
+ proposal=[dict(proposal_id=1, encryption="aes256", hash="sha256")],
+ ),
+ ],
+ ),
+ state="rendered",
+ )
+ compare_cmds = [
+ "set vpn ipsec esp-group ESP-RENDER-TEST",
+ "set vpn ipsec esp-group ESP-RENDER-TEST proposal 1",
+ "set vpn ipsec esp-group ESP-RENDER-TEST proposal 1 encryption aes256",
+ "set vpn ipsec esp-group ESP-RENDER-TEST proposal 1 hash sha256",
+ ]
+ set_module_args(playbook)
+ result = self.execute_module()
+ self.maxDiff = None
+ self.assertEqual(sorted(result["rendered"]), sorted(compare_cmds))
+
+ # -------------------------------------------------------------------
+ # parsed (reads running_config directly, not the mocked show command)
+ # -------------------------------------------------------------------
+
+ def test_vyos_vpn_ipsec_parsed(self):
+ set_module_args(
+ dict(
+ running_config=dedent(
+ """\
+ set vpn ipsec esp-group ESP-PARSE-TEST proposal 1 encryption aes256
+ set vpn ipsec esp-group ESP-PARSE-TEST proposal 1 hash sha256
+ set vpn ipsec ike-group IKE-PARSE-TEST key-exchange ikev2
+ """,
+ ),
+ state="parsed",
+ ),
+ )
+ parsed = dict(
+ esp_group=[
+ dict(
+ name="ESP-PARSE-TEST",
+ proposal=[dict(proposal_id=1, encryption="aes256", hash="sha256")],
+ ),
+ ],
+ ike_group=[dict(name="IKE-PARSE-TEST", key_exchange="ikev2")],
+ )
+ result = self.execute_module(changed=False)
+ self.maxDiff = None
+ self.assertEqual(result["parsed"], parsed)
+
+ # -------------------------------------------------------------------
+ # gathered
+ # -------------------------------------------------------------------
+
+ def _normalize_scalar_lists(self, node):
+ """Multi-value scalar-list fields (id, subsystem, interface, ...)
+ have no meaningful order -- dict_merge's internal list handling
+ doesn't guarantee a stable sequence between runs. Sort them
+ in-place (recursively) before comparing so tests aren't
+ sensitive to that non-determinism, while list-of-dict
+ collections (already sorted by name in process_facts) are left
+ untouched.
+ """
+ if isinstance(node, dict):
+ for k, v in node.items():
+ if isinstance(v, list) and v and all(not isinstance(i, dict) for i in v):
+ node[k] = sorted(v)
+ else:
+ self._normalize_scalar_lists(v)
+ elif isinstance(node, list):
+ for item in node:
+ self._normalize_scalar_lists(item)
+ return node
+
+ def test_vyos_vpn_ipsec_gathered(self):
+ set_module_args(dict(state="gathered"))
+ gathered = dict(
+ authentication=dict(
+ ppk=[dict(name="PPK-TEST", id=["ppk-id-1"], secret="test-ppk-secret")],
+ psk=[
+ dict(
+ name="PSK-TEST",
+ id=["local@example.com", "remote@example.com"],
+ secret="test-not-real-secret",
+ ),
+ ],
+ ),
+ disable_uniqreqids=True,
+ esp_group=[
+ dict(
+ name="ESP-TEST",
+ compression=True,
+ proposal=[dict(proposal_id=1, encryption="aes256", hash="sha256")],
+ ),
+ ],
+ ike_group=[
+ dict(name="IKE-EXTRA", key_exchange="ikev1"),
+ dict(
+ name="IKE-TEST",
+ key_exchange="ikev2",
+ disable_mobike=True,
+ dead_peer_detection=dict(action="restart", interval=15, timeout=60),
+ proposal=[
+ dict(proposal_id=1, encryption="aes256", hash="sha256", dh_group=14),
+ ],
+ ),
+ ],
+ interface=["eth0", "eth1"],
+ log=dict(level=1, subsystem=["chd", "ike"]),
+ options=dict(flexvpn=True, retransmission=dict(attempts=3)),
+ profile=[
+ dict(
+ name="testprofile",
+ authentication=dict(
+ mode="pre-shared-secret",
+ pre_shared_secret="test-not-real-secret",
+ ),
+ bind_tunnel=["tun0"],
+ esp_group="ESP-TEST",
+ ike_group="IKE-TEST",
+ ),
+ ],
+ )
+ result = self.execute_module(changed=False)
+ self.maxDiff = None
+ self.assertEqual(
+ self._normalize_scalar_lists(result["gathered"]),
+ self._normalize_scalar_lists(gathered),
+ )
diff --git a/tests/unit/modules/network/vyos/test_vyos_vpn_ipsec_s2s.py b/tests/unit/modules/network/vyos/test_vyos_vpn_ipsec_s2s.py
new file mode 100644
index 00000000..014fb867
--- /dev/null
+++ b/tests/unit/modules/network/vyos/test_vyos_vpn_ipsec_s2s.py
@@ -0,0 +1,245 @@
+#
+# (c) 2026, Red Hat, inc
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+#
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from unittest.mock import patch
+
+from ansible_collections.vyos.vyos.plugins.modules import vyos_vpn_ipsec_s2s
+from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args
+
+from .vyos_module import TestVyosModule, load_fixture
+
+
+class TestVyosVpnIpsecS2sModule(TestVyosModule):
+ module = vyos_vpn_ipsec_s2s
+
+ def setUp(self):
+ super(TestVyosVpnIpsecS2sModule, self).setUp()
+
+ self.mock_get_resource_connection_config = patch(
+ "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base.get_resource_connection",
+ )
+ self.get_resource_connection_config = self.mock_get_resource_connection_config.start()
+
+ self.mock_get_resource_connection_facts = patch(
+ "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts.get_resource_connection",
+ )
+ self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start()
+
+ self.mock_execute_show_command = patch(
+ "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.vpn_ipsec_s2s.vpn_ipsec_s2s.Vpn_ipsec_s2sFacts.get_vpn_ipsec_s2s_data",
+ )
+ self.execute_show_command = self.mock_execute_show_command.start()
+
+ def tearDown(self):
+ super(TestVyosVpnIpsecS2sModule, self).tearDown()
+ self.mock_get_resource_connection_config.stop()
+ self.mock_get_resource_connection_facts.stop()
+ self.mock_execute_show_command.stop()
+
+ # Single fixture file, reused across every test. Carries PEER-TEST
+ # (richly configured -- authentication, tunnel, vti, every scalar
+ # type) and PEER-EXTRA (minimal, to prove "unlisted peer preserved
+ # under replaced / removed under overridden").
+ def load_fixtures(self, commands=None, filename=None):
+ if filename is None:
+ filename = "vyos_vpn_ipsec_s2s_config.cfg"
+
+ def load_from_file(*args, **kwargs):
+ return load_fixture(filename)
+
+ self.execute_show_command.side_effect = load_from_file
+
+ # -------------------------------------------------------------------
+ # merged
+ # -------------------------------------------------------------------
+
+ def test_vyos_vpn_ipsec_s2s_merged_idempotent(self):
+ playbook = dict(
+ config=dict(
+ peer=[
+ dict(
+ name="PEER-TEST",
+ authentication=dict(
+ local_id="local@example.com",
+ mode="pre-shared-secret",
+ remote_id="remote@example.com",
+ ),
+ childless="prefer",
+ connection_type="initiate",
+ default_esp_group="ESP-TEST",
+ description="test peer for site-to-site module",
+ force_udp_encapsulation=True,
+ ike_group="IKE-TEST",
+ ikev2_reauth="inherit",
+ local_address="any",
+ remote_address=["203.0.113.1"],
+ replay_window=32,
+ virtual_address=["0.0.0.0"],
+ tunnel=[
+ dict(
+ tunnel_id=1,
+ esp_group="ESP-TEST",
+ protocol="tcp",
+ priority=10,
+ local=dict(port=443, prefix=["10.0.0.0/24"]),
+ remote=dict(port=443, prefix=["10.1.0.0/24"]),
+ ),
+ ],
+ vti=dict(
+ bind="vti0",
+ esp_group="ESP-TEST",
+ traffic_selector=dict(
+ local=dict(prefix=["10.2.0.0/24"]),
+ remote=dict(prefix=["10.3.0.0/24"]),
+ ),
+ ),
+ ),
+ ],
+ ),
+ state="merged",
+ )
+ set_module_args(playbook)
+ result = self.execute_module(changed=False)
+ self.maxDiff = None
+ self.assertEqual(sorted(result["commands"]), [])
+
+ def test_vyos_vpn_ipsec_s2s_merged_new_peer_leaves_existing_untouched(self):
+ playbook = dict(
+ config=dict(peer=[dict(name="PEER-NEW", ike_group="IKE-TEST")]),
+ state="merged",
+ )
+ compare_cmds = [
+ "set vpn ipsec site-to-site peer PEER-NEW",
+ "set vpn ipsec site-to-site peer PEER-NEW ike-group 'IKE-TEST'",
+ ]
+ set_module_args(playbook)
+ result = self.execute_module(changed=True)
+ self.maxDiff = None
+ self.assertEqual(sorted(result["commands"]), sorted(compare_cmds))
+
+ # -------------------------------------------------------------------
+ # replaced
+ # -------------------------------------------------------------------
+
+ def test_vyos_vpn_ipsec_s2s_replaced_only_named_peer_touched(self):
+ # Name PEER-TEST fully (mirroring the fixture exactly) except a
+ # changed tunnel priority -- PEER-EXTRA must be left alone.
+ playbook = dict(
+ config=dict(
+ peer=[
+ dict(
+ name="PEER-TEST",
+ authentication=dict(
+ local_id="local@example.com",
+ mode="pre-shared-secret",
+ remote_id="remote@example.com",
+ ),
+ childless="prefer",
+ connection_type="initiate",
+ default_esp_group="ESP-TEST",
+ description="test peer for site-to-site module",
+ force_udp_encapsulation=True,
+ ike_group="IKE-TEST",
+ ikev2_reauth="inherit",
+ local_address="any",
+ remote_address=["203.0.113.1"],
+ replay_window=32,
+ virtual_address=["0.0.0.0"],
+ tunnel=[
+ dict(
+ tunnel_id=1,
+ esp_group="ESP-TEST",
+ protocol="tcp",
+ priority=99,
+ local=dict(port=443, prefix=["10.0.0.0/24"]),
+ remote=dict(port=443, prefix=["10.1.0.0/24"]),
+ ),
+ ],
+ vti=dict(
+ bind="vti0",
+ esp_group="ESP-TEST",
+ traffic_selector=dict(
+ local=dict(prefix=["10.2.0.0/24"]),
+ remote=dict(prefix=["10.3.0.0/24"]),
+ ),
+ ),
+ ),
+ ],
+ ),
+ state="replaced",
+ )
+ compare_cmds = ["set vpn ipsec site-to-site peer PEER-TEST tunnel 1 priority '99'"]
+ set_module_args(playbook)
+ result = self.execute_module(changed=True)
+ self.maxDiff = None
+ self.assertEqual(sorted(result["commands"]), sorted(compare_cmds))
+
+ # -------------------------------------------------------------------
+ # overridden
+ # -------------------------------------------------------------------
+
+ def test_vyos_vpn_ipsec_s2s_overridden_removes_unlisted_peer(self):
+ playbook = dict(
+ config=dict(peer=[dict(name="PEER-TEST", ike_group="IKE-TEST")]),
+ state="overridden",
+ )
+ set_module_args(playbook)
+ result = self.execute_module(changed=True)
+ self.maxDiff = None
+ self.assertIn("delete vpn ipsec site-to-site peer PEER-EXTRA", result["commands"])
+ # PEER-TEST had far more fields than just ike_group in the
+ # fixture -- overridden must reconcile all of those away too,
+ # since only ike_group was named.
+ self.assertIn(
+ "delete vpn ipsec site-to-site peer PEER-TEST default-esp-group 'ESP-TEST'",
+ result["commands"],
+ )
+
+ # -------------------------------------------------------------------
+ # deleted -- bare and scoped
+ # -------------------------------------------------------------------
+
+ def test_vyos_vpn_ipsec_s2s_deleted_bare(self):
+ set_module_args(dict(state="deleted"))
+ result = self.execute_module(changed=True)
+ self.maxDiff = None
+ self.assertIn("delete vpn ipsec site-to-site peer PEER-TEST", result["commands"])
+ self.assertIn("delete vpn ipsec site-to-site peer PEER-EXTRA", result["commands"])
+
+ def test_vyos_vpn_ipsec_s2s_deleted_scoped_named_peer_only(self):
+ set_module_args(dict(config=dict(peer=[dict(name="PEER-EXTRA")]), state="deleted"))
+ result = self.execute_module(changed=True)
+ self.maxDiff = None
+ self.assertEqual(result["commands"], ["delete vpn ipsec site-to-site peer PEER-EXTRA"])
+
+ # -------------------------------------------------------------------
+ # rendered / parsed / gathered
+ # -------------------------------------------------------------------
+
+ def test_vyos_vpn_ipsec_s2s_rendered(self):
+ playbook = dict(
+ config=dict(peer=[dict(name="PEER-RENDER-TEST", ike_group="IKE-TEST")]),
+ state="rendered",
+ )
+ compare_cmds = [
+ "set vpn ipsec site-to-site peer PEER-RENDER-TEST",
+ "set vpn ipsec site-to-site peer PEER-RENDER-TEST ike-group 'IKE-TEST'",
+ ]
+ set_module_args(playbook)
+ result = self.execute_module()
+ self.maxDiff = None
+ self.assertEqual(sorted(result["rendered"]), sorted(compare_cmds))
+
+ def test_vyos_vpn_ipsec_s2s_gathered(self):
+ set_module_args(dict(state="gathered"))
+ result = self.execute_module(changed=False)
+ self.maxDiff = None
+ names = sorted(p["name"] for p in result["gathered"]["peer"])
+ self.assertEqual(names, ["PEER-EXTRA", "PEER-TEST"])