#!/bin/bash
# -*- mode: sh; sh-basic-offset: 2; indent-tabs-mode: t; -*-
# vim:set ft=sh sw=2 ts=2:
#
# Test suite for ddns-functions
#
# shellcheck disable=SC2034,SC1090,SC2123

NMG_XTEST=${NMG_XTEST:-conf/nmg_xtest}
{ [[ -r ${NMG_XTEST} ]] && . "${NMG_XTEST}"; } ||
  { echo >&2 "Unable to load ${NMG_XTEST}"; exit 2; }

# min-version for tests
NMDDNS_REQUIRED="1.5.0"

xtest::group2::addr() {

  local avar=() aref=() anull=()

  shtest::title "Address Function Tests (addr group)"

  # Test A/AAAA ip query functions

  xwrap2 "0" nmddns::get_A_addrs avar "wan0"
  xtest A1 t "returns true"
  atest A1a avar anull "sets var empty"
  ftest A1f "" "does not log"

  xwrap2 "1 priv" nmddns::get_A_addrs avar "eth0"
  xtest A2 t "returns true"
  atest A2a avar anull "sets var empty"
  ftest A2f "" "does not log"

  xwrap2 "1 priv allowed" nmddns::get_A_addrs avar "eth0" 1
  xtest A3 t "returns true"
  aref=("192.168.66.4")
  atest A3a avar aref "sets address"
  ftest A3f "" "does not log"

  xwrap2 "2 priv" nmddns::get_A_addrs avar "eth2"
  xtest A4 t "returns true"
  atest A4a avar anull "sets var empty"
  ftest A4f "" "does not log"

  xwrap2 "2 priv allowed" nmddns::get_A_addrs avar "eth2" 1
  xtest A5 t "returns true"
  aref=("10.1.10.12" "10.2.10.12")
  atest A5a avar aref "sets addresses"
  ftest A5f "" "does not log"

  xwrap2 "0" nmddns::get_AAAA_addrs avar "lo"
  xtest A11 t "returns true"
  atest A11v avar anull "sets var empty"
  ftest A11f "" "does not log"

  xwrap2 "1 priv" nmddns::get_AAAA_addrs avar "br0"
  xtest A12 t "returns true"
  atest A12a avar anull "sets var empty"
  ftest A12f "" "does not log"

  xwrap2 "1 priv allowed" nmddns::get_AAAA_addrs avar "br0" 1
  xtest A13 t "returns true"
  aref=("fdc0:4455:b240::1")
  atest A13a avar aref "sets address"
  ftest A13f "" "does not log"

  xwrap2 "2 pub" nmddns::get_AAAA_addrs avar "eth0"
  xtest A14 t "returns true"
  aref=("2001:db8:871a:28c1::1" "2001:db8:4860:4860::8888")
  atest A14a avar aref  "sets addresses"
  ftest A14f "" "does not log"

  nmddns_reset_config
}

xtest::onexit::ddns() {
  # remove ddns tmpfiles
  [[ ${TEST_OUT-} ]] || return 0
  xrm "$TEST_OUT/ddns-state A"
  xrm "$TEST_OUT/ddns-state AAAA"
  xrm "$TEST_OUT/ddns-state"
  xrm "$TEST_OUT/ddns-test.sh"
}

xtest::group4::ddns() {
  local out xpat xstate cnf

  # don't use helper until we're ready
  local NMDDNS_DHELPER
  unset NMDDNS_DHELPER

  shtest::title "nmddns tests"

  # Call nmddns_update directly with various RRECs, A/AAAA are
  # validated for format (not against interface)

  DDNS_ZONE=example.test DDNS_RREC_A_NAME=www.example.test

  xwrap2 "<remove A>" nmddns_update A
  xtest D1 t "returns true"
  xread_value out <<-EOF
	Removing www.example.test A
	${NMDDNS_NSUPDATE} => nsupdate '-t' '10'
	server 127.0.0.1
	zone example.test
	update delete www.example.test A
	send
	EOF
  ftest D1f "$out" "logs and removes A"

  export DIG_MOCK_FAIL=5

  xwrap2 "<dig fail 5>" nmddns_update A
  xtest D2 25 "returns 25"
  xread_value out <<-EOF
	ERR: FAIL(5) ${NMDDNS_DIG} @127.0.0.1 +short +retry=2 +time=3\
 +time=1 +retry=2 A www.example.test
	ERR: Removal of www.example.test A failed
	EOF
  ftest D2f "$out" "logs error"
  unset DIG_MOCK_FAIL

  export NSUPDATE_MOCK_FAIL=5

  xwrap2 "<nsupdate fail 5>" nmddns_update A
  xtest D3 5 "returns 5"
  xread_value out <<-EOF
	Removing www.example.test A
	ERR: FAIL(5) ${NMDDNS_NSUPDATE} -t 10
	ERR: DNS update to server 127.0.0.1 failed for www.example.test A
	EOF
  ftest D3f "$out" "logs error"
  unset NSUPDATE_MOCK_FAIL

  xwrap2 "A <private>" nmddns_update A 192.168.55.1
  xtest D4 t "returns true"
  xread_value out <<-EOF
	Removing www.example.test A
	${NMDDNS_NSUPDATE} => nsupdate '-t' '10'
	server 127.0.0.1
	zone example.test
	update delete www.example.test A
	send
	EOF
  ftest D4f "$out" "logs and removes A"

  DDNS_RREC_A_FALLBACK=203.0.113.8

  xwrap2 "A <private/FALLBACK>" nmddns_update A 192.168.55.1/24
  xtest D5 t "returns true"
  xread_value out <<-EOF
	Setting www.example.test A to 203.0.113.8
	${NMDDNS_NSUPDATE} => nsupdate '-t' '10'
	server 127.0.0.1
	zone example.test
	update delete www.example.test A
	update add www.example.test 600 A 203.0.113.8
	send
	EOF
  ftest D5f "$out" "logs and sets A to fallback"
  unset DDNS_RREC_A_FALLBACK

  DDNS_RREC_A_PRIVATE=1
  xpat="$TEST_OUT/ddns-state @RREC@"
  xstate=${xpat//@RREC@/A}
  xrm "$xstate"

  xwrap2 "A <private/PRIVATE=1>" nmddns_update A 192.168.55.1/24 "$xpat"
  xtest D6 t "returns true"
  xread_value out <<-EOF
	Setting www.example.test A to 192.168.55.1
	${NMDDNS_NSUPDATE} => nsupdate '-t' '10'
	server 127.0.0.1
	zone example.test
	update delete www.example.test A
	update add www.example.test 600 A 192.168.55.1
	send
	EOF
  ftest D6f "$out" "logs and sets A (no plen)"
  shtest::check_file D6s "$xstate" "192.168.55.1/24" \
                     "generates state file (with plen)"
  xrm "$xstate"

  xstate="$TEST_OUT/ddns-state"
  xrm "$xstate"

  # pass bad pattern to test error log
  xwrap2 "<bad state-pat>" nmddns_update A "" "$xstate"
  xtest D7 t "returns true"
  xread_value out <<-EOF
	ERR: nmddns_update: <state-pat> must contain '@RREC@'
	Removing www.example.test A
	${NMDDNS_NSUPDATE} => nsupdate '-t' '10'
	server 127.0.0.1
	zone example.test
	update delete www.example.test A
	send
	EOF
  ftest D7f "$out" "logs error and performs action"
  [[ -f $xstate ]]
  xtest D7s f "should not create state file"
  xrm "$xstate"

  xwrap nmddns_update
  xtest D8 f "(<no args>) returns false"
  ftest D8f "ERR: nmddns_update: missing <rrec>" "logs error"

  unset DDNS_ZONE

  xwrap2 "A <unset ZONE>" nmddns_update A
  xtest D9 5 "returns 5"
  ftest D9f "ERR: Missing required DDNS_ZONE config" "logs error"

  unset DDNS_RREC_A_NAME

  xwrap2 "A <unset NAME>" nmddns_update A
  xtest D10 t "returns true"
  ftest D10f "" "does not log"

  DDNS_ZONE=example.test DDNS_RREC_A_NAME=www.example.test
  DDNS_RREC_A_VALUE="*"

  xwrap2 "A <VALUE=*>" nmddns_update A 203.0.113.10
  xtest D11 t "returns true"
  xread_value out <<-EOF
	Removing www.example.test A
	${NMDDNS_NSUPDATE} => nsupdate '-t' '10'
	server 127.0.0.1
	zone example.test
	update delete www.example.test A
	send
	EOF
  ftest D11f "$out" "logs and removes A"

  unset DDNS_RREC_A_VALUE

  xwrap2 "A <pub>" nmddns_update A 203.0.113.10
  xtest D12 t "returns true"
  xread_value out <<-EOF
	Setting www.example.test A to 203.0.113.10
	${NMDDNS_NSUPDATE} => nsupdate '-t' '10'
	server 127.0.0.1
	zone example.test
	update delete www.example.test A
	update add www.example.test 600 A 203.0.113.10
	send
	EOF
  ftest D12f "$out" "logs and adds A"

  xwrap2 "A <2 pub>" nmddns_update A "203.0.113.10,203.0.113.20"
  xtest D13 t "returns true"
  xread_value out <<-EOF
	Setting www.example.test A to 203.0.113.10,203.0.113.20
	${NMDDNS_NSUPDATE} => nsupdate '-t' '10'
	server 127.0.0.1
	zone example.test
	update delete www.example.test A
	update add www.example.test 600 A 203.0.113.10
	update add www.example.test 600 A 203.0.113.20
	send
	EOF
  ftest D13f "$out" "logs and adds both A records"

  xwrap2 "A <2 match>" nmddns_update A "203.0.113.4,192.0.2.8"
  xtest D14 t "returns true"
  ftest D14f "" "does not log"

  DDNS_RREC_TXT_NAME=mail.example.test
  DDNS_RREC_TXT_LISTSEP=:

  xwrap2 "TXT <2 new vals>" nmddns_update TXT \
         "\"some value\":\"another value\""
  xtest D15 t "returns true"
  xread_value out <<-EOF
	Setting mail.example.test TXT to "some value":"another value"
	${NMDDNS_NSUPDATE} => nsupdate '-t' '10'
	server 127.0.0.1
	zone example.test
	update delete mail.example.test TXT
	update add mail.example.test 600 TXT "some value"
	update add mail.example.test 600 TXT "another value"
	send
	EOF
  ftest D15f "$out" "logs and adds both TXT records"

  xwrap2 "TXT <2 old vals>" nmddns_update TXT \
         "\"some value\":\"v=spf1 mx ~all\""
  xtest D16 t "returns true"
  ftest D16f "" "does not log"

  xstate=${xpat//@RREC@/A}
  xrm "$xstate"

  # This tests !<interface> value, which pulls addresses from the
  # interface.

  # ip-mock has 10.1.10.12,10.2.10.12 on eth2

  xwrap2 "A <eth2>" nmddns_update A "!eth2" "$xpat"
  xtest D17 t "returns true"
  xread_value out <<-EOF
	Setting www.example.test A to 10.1.10.12,10.2.10.12
	${NMDDNS_NSUPDATE} => nsupdate '-t' '10'
	server 127.0.0.1
	zone example.test
	update delete www.example.test A
	update add www.example.test 600 A 10.1.10.12
	update add www.example.test 600 A 10.2.10.12
	send
	EOF
  ftest D17f "$out" "logs and adds both A records"
  shtest::check_file D17s "$xstate" "!eth2" "generates state file (intf)"
  xrm "$xstate"

  DDNS_RREC_AAAA_NAME=www.example.test
  xstate=${xpat//@RREC@/AAAA}
  xrm "$xstate"

  # this sleeps
  sleep() {
    # override ip-mock results
    nmg_info "resolving DAD"
    export IP_MOCK_OUTPUT="inet6 2001:db8:1::1/64 scope global valid_lft forever preferred_lft forever"
  }

  # ip-mock has 2001:db8:1::1(tentative) on eth2
  xwrap2 "AAAA <eth2 tentative>" nmddns_update AAAA "!eth2" "$xpat"
  xtest D18 t "returns true"
  xread_value out <<-EOF
	resolving DAD
	Setting www.example.test AAAA to 2001:db8:1::1
	${NMDDNS_NSUPDATE} => nsupdate '-t' '10'
	server 127.0.0.1
	zone example.test
	update delete www.example.test AAAA
	update add www.example.test 600 AAAA 2001:db8:1::1
	send
	EOF
  ftest D18f "$out" "logs and removes AAAA records"
  shtest::check_file D18s "$xstate" "!eth2" "generates state file (intf)"

  unset IP_MOCK_OUTPUT
  unset -f sleep

  xrm "$xstate"

  # ip-mock has 2001:db8:5::1(dadfailed) on eth3
  xwrap2 "AAAA <eth3 dadfail>" nmddns_update AAAA "!eth3" "$xpat"
  xtest D19 t "returns true"
  xread_value out <<-EOF
	Removing www.example.test AAAA
	${NMDDNS_NSUPDATE} => nsupdate '-t' '10'
	server 127.0.0.1
	zone example.test
	update delete www.example.test AAAA
	send
	EOF
  ftest D19f "$out" "logs and removes AAAA records"
  shtest::check_file D19s "$xstate" "!eth3" "generates state file (intf)"
  xrm "$xstate"

  nmddns_reset_config

  shtest::prefix "nmddns_update_all"

  # multi-update with overrides
  DDNS_ZONE=example.test DDNS_RREC_AAAA_NAME=ipv6.example.test
  DDNS_RREC_MX_NAME=www.example.test DDNS_RREC_MX_VALUE="5 mx.example.test."
  DDNS_RREC_MX_FALLBACK="10 backup.example.test."
  DDNS_RREC_A_NAME=www.example.test DDNS_RREC_A_VALUE="203.0.113.4"
  DDNS_TTL=300

  xwrap2 "<up multi>" nmddns_update_all up 203.0.113.8 2001:db8:1000:2000::1/64
  xtest D21 t "returns true"
  xread_value out <<-EOF
	Setting ipv6.example.test AAAA to 2001:db8:1000:2000::1
	${NMDDNS_NSUPDATE} => nsupdate '-t' '10'
	server 127.0.0.1
	zone example.test
	update delete ipv6.example.test AAAA
	update add ipv6.example.test 300 AAAA 2001:db8:1000:2000::1
	send
	Setting www.example.test A to 203.0.113.4
	${NMDDNS_NSUPDATE} => nsupdate '-t' '10'
	server 127.0.0.1
	zone example.test
	update delete www.example.test A
	update add www.example.test 300 A 203.0.113.4
	send
	Setting www.example.test MX to 5 mx.example.test.
	${NMDDNS_NSUPDATE} => nsupdate '-t' '10'
	server 127.0.0.1
	zone example.test
	update delete www.example.test MX
	update add www.example.test 300 MX 5 mx.example.test.
	send
	EOF
  ftest D21f "$out" "performs actions"

  DDNS_NSUPDATE_TIMEOUT=5

  xwrap2 "<down multi>" nmddns_update_all down
  xtest D22 t "returns true"
  xread_value out <<-EOF
	Removing ipv6.example.test AAAA
	${NMDDNS_NSUPDATE} => nsupdate '-t' '5'
	server 127.0.0.1
	zone example.test
	update delete ipv6.example.test AAAA
	send
	Removing www.example.test A
	${NMDDNS_NSUPDATE} => nsupdate '-t' '5'
	server 127.0.0.1
	zone example.test
	update delete www.example.test A
	send
	Setting www.example.test MX to 10 backup.example.test.
	${NMDDNS_NSUPDATE} => nsupdate '-t' '5'
	server 127.0.0.1
	zone example.test
	update delete www.example.test MX
	update add www.example.test 300 MX 10 backup.example.test.
	send
	EOF
  ftest D22f "$out" "performs actions"

  shtest::prefix "nmddns_spawn_update"

  # Test the attempted use of the helper script, which is disabled
  # to force fallback

  xrm "$XNOFILE"

  xwrap2 "<no conf>" nmddns_spawn_update "$XNOFILE" "A"
  xtest D31 t "returns true"
  ftest D31f "" "does not log"

  cnf="$TEST_CONF/test-ddns.conf"
  xpat="$TEST_OUT/ddns-state @RREC@"
  xstate=${xpat//@RREC@/A}

  xrm "$xstate"
  printf "" >>"$xstate"

  xwrap2 "<no rrec>" nmddns_spawn_update "$cnf" "" "" "$xpat"
  xtest D32 f "returns false"
  ftest D32f "ERR: nmddns_spawn_update: <rrec> must be a RREC name" \
        "logs error"
  [[ -f $xstate ]]
  xtest D32s t "leaves state file"

  printf "" >>"$xstate"

  xwrap2 "<remove A, no helper>" nmddns_spawn_update "$cnf" A "" "$xpat"
  xtest D33 t "returns true"
  [[ -e $xstate ]]
  xtest D33s f "removes state file"
  xrm "$xstate"
  xread_value out <<-EOF
	Removing www.example.test A
	${NMDDNS_NSUPDATE} => nsupdate '-t' '2'
	server 127.0.0.1
	zone example.test
	update delete www.example.test A
	send
	EOF
  ftest D33f "$out" "logs error, removes A rrec"

  # Test the use of the helper script, which is replaced with
  # a test script

  xstate="$TEST_OUT/ddns-test.sh"
  xrm "$XFILE"
  xcat >"$XFILE" <<-EOF
	#!/bin/bash
	echo >"$xstate" "action=\$NMDDNSH_ACTION"
	EOF
  command -p chmod +x "$XFILE"
  NMDDNS_DHELPER=$XFILE

  xwrap2 "<remove A, helper>" nmddns_spawn_update "$cnf" A "" "$xpat"
  xtest D34 t "returns true"
  ftest D34f "" "does not log"
  shtest::last_check_ok && wait $!
  shtest::check_file D34o "$xstate" "action=update" "runs helper"
  xrm "$XFILE" "$xstate"

  shtest::prefix "nmddns_spawn_update_all"

  unset NMDDNS_DHELPER

  xwrap2 "up" nmddns_spawn_update_all up "$cnf"
  xtest D41 t "returns true"
  xread_value out <<-EOF
  	Removing www.example.test A
	${NMDDNS_NSUPDATE} => nsupdate '-t' '2'
	server 127.0.0.1
	zone example.test
	update delete www.example.test A
	send
	EOF
  ftest D41f "${out}" "removes current entry"

  nmddns_cleanup

  xtest::onexit::ddns
}

xtest::onexit::helper() {
  [[ ${TEST_OUT-} ]] || return 0
  xrm "$TEST_OUT/ddnsh-state A"
  xrm "$TEST_OUT/ddnsh-state AAAA"
  xrm "$TEST_OUT/ddnsh-state TXT"
}

xtest::group5::helper() {

  local out

  shtest::title "09-ddns helper tests"

  # Spawns real helper in various modes

  xwrap nmg_need_progs "${NMDDNS_DHELPER}"
  xtest H0 t "${NMDDNS_DHELPER} is runnable"
  shtest::last_check_ok || return 0

  local cnf="$TEST_CONF/testweb-ddns.conf"
  local xpat="$TEST_OUT/ddnsh-state @RREC@"
  local state1=${xpat//@RREC@/A} state2=${xpat//@RREC@/AAAA}
  local state3=${xpat//@RREC@/TXT}

  xwrap2 "up <multi-addrs>" nmddns_spawn_update_all up "$cnf" \
	 "203.0.113.55,203.0.113.56,192.168.4.1" \
	 "fdc0:4455:8700::1,2001:db8:8700:2000::1" "$xpat"
  xtest H1 t "returns true"
  # wait for background process to finish
  shtest::last_check_ok && wait $!
  xread_value out <<-EOF
	Setting web.example.test AAAA to 2001:db8:8700:2000::1
	${NMDDNS_NSUPDATE} => nsupdate '-t' '10'
	server 127.0.0.1
	zone example.test
	update delete web.example.test AAAA
	update add web.example.test 600 AAAA 2001:db8:8700:2000::1
	send
	Setting web.example.test A to 203.0.113.55,203.0.113.56
	${NMDDNS_NSUPDATE} => nsupdate '-t' '10'
	server 127.0.0.1
	zone example.test
	update delete web.example.test A
	update add web.example.test 600 A 203.0.113.55
	update add web.example.test 600 A 203.0.113.56
	send
	Setting web.example.test TXT to replacement txt
	${NMDDNS_NSUPDATE} => nsupdate '-t' '10'
	server 127.0.0.1
	zone example.test
	update delete web.example.test TXT
	update add web.example.test 600 TXT replacement txt
	send
	EOF
  ftest H1f "$out" "logs and adds A/AAAA/TXT records"
  shtest::check_file H1i "$state1" "203.0.113.55,203.0.113.56,192.168.4.1" \
		     "creates A state file"
  shtest::check_file H1j "$state2" "fdc0:4455:8700::1,2001:db8:8700:2000::1" \
		     "creates AAAA state file"

  xwrap2 "up <no addrs>" nmddns_spawn_update_all up "$cnf" "" "" "$xpat"
  xtest H2 t "returns true"
  shtest::last_check_ok && wait $!
  xread_value out <<-EOF
	Removing web.example.test AAAA
	${NMDDNS_NSUPDATE} => nsupdate '-t' '10'
	server 127.0.0.1
	zone example.test
	update delete web.example.test AAAA
	send
	Removing web.example.test A
	${NMDDNS_NSUPDATE} => nsupdate '-t' '10'
	server 127.0.0.1
	zone example.test
	update delete web.example.test A
	send
	Setting web.example.test TXT to replacement txt
	${NMDDNS_NSUPDATE} => nsupdate '-t' '10'
	server 127.0.0.1
	zone example.test
	update delete web.example.test TXT
	update add web.example.test 600 TXT replacement txt
	send
	EOF
  ftest H2f "$out" "logs and removes A/AAAA records, sets TXT"
  [[ -e $state1 ]]
  xtest H2i f "removes A state file"
  [[ -e $state2 ]]
  xtest H2j f "removes AAAA state file"

  echo "203.0.113.55" >"$state1"
  echo "2001:db8:8700:2000::1" >"$state2"

  xwrap2 "down" nmddns_spawn_update_all down "$cnf" "" "" "$xpat"
  xtest H3 t "returns true"
  shtest::last_check_ok && wait $!
  xread_value out <<-EOF
	Removing web.example.test AAAA
	${NMDDNS_NSUPDATE} => nsupdate '-t' '10'
	server 127.0.0.1
	zone example.test
	update delete web.example.test AAAA
	send
	Removing web.example.test A
	${NMDDNS_NSUPDATE} => nsupdate '-t' '10'
	server 127.0.0.1
	zone example.test
	update delete web.example.test A
	send
	Removing web.example.test TXT
	${NMDDNS_NSUPDATE} => nsupdate '-t' '10'
	server 127.0.0.1
	zone example.test
	update delete web.example.test TXT
	send
	EOF
  ftest H3f "$out" "logs and removes all records"
  [[ -e $state1 ]]
  xtest H3i f "removes A state file"
  [[ -e $state2 ]]
  xtest H3j f "removes AAAA state file"
  [[ -e $state3 ]]
  xtest H3k f "removes TXT state file"

  xwrap2 "up <multi-addrs>" nmddns_spawn_update "$cnf" A \
	 "203.0.113.75,203.0.113.76" "$xpat"
  xtest H11 t "returns true"
  # wait for background process to finish
  shtest::last_check_ok && wait $!
  xread_value out <<-EOF
	Setting web.example.test A to 203.0.113.75,203.0.113.76
	${NMDDNS_NSUPDATE} => nsupdate '-t' '10'
	server 127.0.0.1
	zone example.test
	update delete web.example.test A
	update add web.example.test 600 A 203.0.113.75
	update add web.example.test 600 A 203.0.113.76
	send
	EOF
  ftest H11f "$out" "logs and adds A records"
  shtest::check_file H11i "$state1" "203.0.113.75,203.0.113.76" \
		     "creates A state file"

  xwrap2 "up <multi-addrs>" nmddns_spawn_update "$cnf" TXT "new value" "$xpat"
  xtest H12 t "returns true"
  # wait for background process to finish
  shtest::last_check_ok && wait $!
  xread_value out <<-EOF
	Setting web.example.test TXT to replacement txt
	${NMDDNS_NSUPDATE} => nsupdate '-t' '10'
	server 127.0.0.1
	zone example.test
	update delete web.example.test TXT
	update add web.example.test 600 TXT replacement txt
	send
	EOF
  ftest H12f "$out" "logs and adds override TXT record"
  shtest::check_file H12k "$state3" "new value" "creates TXT state file"

  nmddns_reset_config
  xtest::onexit::helper
}

test_version() {
  local NMG_REQUIRED="99.0.0" nmg_log_stderr=1

  shtest::title "Check version requirements"

  shtest::whitelist source

  (source 2>/dev/null "${NMDDNS}") &&
    xtest::fail "  FATAL: ${NMDDNS} loaded when NMG_REQUIRED=${NMG_REQUIRED}"

  unset NMG_REQUIRED
  local NMDDNS_REQUIRED="99.0.0"

  (source 2>/dev/null "${NMDDNS}") &&
    xtest::fail "FATAL: ${NMDDNS} loaded when\
 NMDDNS_REQUIRED=${NMDDNS_REQUIRED}"

  shtest::reset_state

  shtest::log "  Version enforcement working"
}

xtest::onexit() {
  xrm "${XFILE-}"
}

xmain() { # <args>
  local XNOFILE="$TEST_OUT/no-file" XFILE="$TEST_OUT/ddns file"
  local NMDDNS=${NMDDNS:-${NMUTILS}/ddns-functions}

  xload_script "${NMDDNS}"

  test_version

  xtest::run_tests "ddns-functions Test Summary" "$@"
  local rc=$?

  xtest::onexit

  return $rc
}

xstart "$@"
