#!/usr/bin/env python3

'''Manual DDNS testing'''

from dnstest.utils import *
from dnstest.test import Test
import random

t = Test(tls=True, quic=True)

def check_soa(master, prev_soa):
    soa_resp = master.dig("ddns.", "SOA")
    compare(prev_soa, soa_resp.resp.answer, "SOA changed when it shouldn't")

def verify(master, zone, dnssec):
    if not dnssec:
        return
    master.flush(wait=True)
    master.zone_verify(zone)

def do_normal_tests(master, zone, dnssec=False):
    # add node
    check_log("Node addition")
    up = master.update(zone)
    up.add("rrtest.ddns.", 3600, "A", "1.2.3.4")
    up.send("NOERROR")
    resp = master.dig("rrtest.ddns.", "A")
    resp.check(rcode="NOERROR", rdata="1.2.3.4")
    verify(master, zone, dnssec)

    # add record to existing rrset
    check_log("Node update - new record")
    up = master.update(zone)
    up.add("rrtest.ddns.", 3600, "A", "1.2.3.5")
    up.send("NOERROR")
    resp = master.dig("rrtest.ddns.", "A")
    resp.check(rcode="NOERROR", rdata="1.2.3.4")
    resp.check(rcode="NOERROR", rdata="1.2.3.5")
    verify(master, zone, dnssec)

    # add records to existing rrset
    check_log("Node update - new records")
    up = master.update(zone)
    up.add("rrtest.ddns.", 3600, "A", "1.2.3.7")
    up.add("rrtest.ddns.", 3600, "A", "1.2.3.0")
    up.send("NOERROR")
    resp = master.dig("rrtest.ddns.", "A")
    resp.check(rcode="NOERROR", rdata="1.2.3.0")
    resp.check(rcode="NOERROR", rdata="1.2.3.4")
    resp.check(rcode="NOERROR", rdata="1.2.3.5")
    resp.check(rcode="NOERROR", rdata="1.2.3.7")
    verify(master, zone, dnssec)

    # add rrset to existing node
    check_log("Node update - new rrset")
    up = master.update(zone)
    up.add("rrtest.ddns.", 3600, "TXT", "abcedf")
    up.send("NOERROR")
    resp = master.dig("rrtest.ddns.", "TXT")
    resp.check(rcode="NOERROR", rdata="abcedf")
    resp = master.dig("rrtest.ddns.", "A")
    resp.check(rcode="NOERROR", rdata="1.2.3.0")
    resp.check(rcode="NOERROR", rdata="1.2.3.4")
    resp.check(rcode="NOERROR", rdata="1.2.3.5")
    resp.check(rcode="NOERROR", rdata="1.2.3.7")
    verify(master, zone, dnssec)

    # remove rrset
    check_log("Node update - rrset removal")
    up = master.update(zone)
    up.delete("rrtest.ddns.", "TXT")
    up.send("NOERROR")
    resp = master.dig("rrtest.ddns.", "TXT")
    resp.check(rcode="NOERROR")
    compare(resp.count(section="answer"), 0, "TXT rrset removal")
    resp = master.dig("rrtest.ddns.", "A")
    resp.check(rcode="NOERROR", rdata="1.2.3.0")
    resp.check(rcode="NOERROR", rdata="1.2.3.4")
    resp.check(rcode="NOERROR", rdata="1.2.3.5")
    resp.check(rcode="NOERROR", rdata="1.2.3.7")
    verify(master, zone, dnssec)

    # remove record
    check_log("Node update - record removal")
    up = master.update(zone)
    up.delete("rrtest.ddns.", "A", "1.2.3.5")
    up.send("NOERROR")
    resp = master.dig("rrtest.ddns.", "A")
    resp.check(rcode="NOERROR", nordata="1.2.3.5")
    resp.check(rcode="NOERROR", rdata="1.2.3.0")
    resp.check(rcode="NOERROR", rdata="1.2.3.4")
    resp.check(rcode="NOERROR", rdata="1.2.3.7")
    verify(master, zone, dnssec)

    # remove records
    check_log("Node update - records removal")
    up = master.update(zone)
    up.delete("rrtest.ddns.", "A", "1.2.3.0")
    up.delete("rrtest.ddns.", "A", "1.2.3.7")
    up.send("NOERROR")
    resp = master.dig("rrtest.ddns.", "A")
    resp.check(rcode="NOERROR", nordata="1.2.3.0")
    resp.check(rcode="NOERROR", nordata="1.2.3.7")
    resp.check(rcode="NOERROR", rdata="1.2.3.4")
    verify(master, zone, dnssec)

    # replace with different TTL
    check_log("Replace with other TTL")
    up = master.update(zone)
    up.delete("rrtest.ddns.", "ANY")
    up.add("rrtest.ddns.", 7, "A", "1.2.3.8")
    up.send("NOERROR")
    resp = master.dig("rrtest.ddns.", "A")
    resp.check(rcode="NOERROR", rdata="1.2.3.8")
    verify(master, zone, dnssec)

    # remove node
    check_log("Node removal")
    up = master.update(zone)
    up.delete("rrtest.ddns.", "ANY")
    up.send("NOERROR")
    resp = master.dig("rrtest.ddns.", "A")
    resp.check(rcode="NXDOMAIN")
    verify(master, zone, dnssec)

    # add delegation
    check_log("Delegation addition")
    up = master.update(zone)
    up.add("deleg.ddns.", 3600, "NS", "a.deleg.ddns.")
    up.add("a.deleg.ddns.", 3600, "A", "1.2.3.4")
    up.send("NOERROR")
    resp = master.dig("deleg.ddns.", "NS")
    resp.check_record(section="authority", rtype="NS", rdata="a.deleg.ddns.")
    resp.check_record(section="additional", rtype="A", rdata="1.2.3.4")
    verify(master, zone, dnssec)

    # add delegation w/o glue
    check_log("Delegation w/o glue")
    up = master.update(zone)
    up.add("deleglue.ddns.", 3600, "NS", "a.deleglue.ddns.")
    up.send("NOERROR")
    resp = master.dig("deleglue.ddns.", "NS")
    resp.check_record(section="authority", rtype="NS", rdata="a.deleglue.ddns.")
    resp.check_no_rr(section="additional", rname="a.deleglue.ddns.", rtype="A")
    verify(master, zone, dnssec)

    # add glue to delegation
    check_log("Glue for existing delegation")
    up = master.update(zone)
    up.add("a.deleglue.ddns.", 3600, "A", "10.20.30.40")
    up.send("NOERROR")
    resp = master.dig("deleglue.ddns.", "NS")
    resp.check_record(section="authority", rtype="NS", rdata="a.deleglue.ddns.")
    resp.check_record(section="additional", rtype="A", rdata="10.20.30.40")
    verify(master, zone, dnssec)

    # remove delegation, keep glue
    check_log("Remove delegation, keep glue")
    up = master.update(zone)
    up.delete("deleglue.ddns.", "NS")
    up.send("NOERROR")
    resp = master.dig("deleglue.ddns.", "NS")
    resp.check(rcode="NOERROR")
    resp.check_record(section="authority", rtype="SOA")
    resp.check_no_rr(section="additional", rname="a.deleglue.ddns.", rtype="A")
    resp = master.dig("a.deleglue.ddns.", "A")
    resp.check(rcode="NOERROR")
    resp.check_record(section="answer", rtype="A", rdata="10.20.30.40")
    verify(master, zone, dnssec)

    # add delegation to existing glue
    check_log("Add delegation to existing glue")
    up = master.update(zone)
    up.add("deleglue.ddns.", 3600, "NS", "a.deleglue.ddns.")
    up.send("NOERROR")
    resp = master.dig("deleglue.ddns.", "NS")
    resp.check_record(section="authority", rtype="NS", rdata="a.deleglue.ddns.")
    resp.check_record(section="additional", rtype="A", rdata="10.20.30.40")
    verify(master, zone, dnssec)

    # make a delegation from NONAUTH node
    check_log("NONAUTH to DELEG")
    up = master.update(zone)
    up.add("a.deleglue.ddns.", 3600, "NS", "a.deleglue.ddns.")
    up.delete("deleglue.ddns.", "NS", "a.deleglue.ddns.")
    up.send("NOERROR")
    resp = master.dig("x.a.deleglue.ddns.", "A")
    resp.check(rcode="NOERROR")
    resp.check_record(section="authority", rtype="NS", rdata="a.deleglue.ddns.")
    resp.check_record(section="additional", rtype="A", rdata="10.20.30.40")
    verify(master, zone, dnssec)

    # reverse of previous
    check_log("DELEG to NONAUTH")
    up = master.update(zone)
    up.delete("a.deleglue.ddns.", "NS", "a.deleglue.ddns.")
    up.add("deleglue.ddns.", 3600, "NS", "a.deleglue.ddns.")
    up.send("NOERROR")
    resp = master.dig("deleglue.ddns.", "NS")
    resp.check(rcode="NOERROR")
    resp.check_record(section="authority", rtype="NS", rdata="a.deleglue.ddns.")
    resp.check_record(section="additional", rtype="A", rdata="10.20.30.40")
    verify(master, zone, dnssec)

    # add SVCB w/o glue
    check_log("glueless SVCB")
    up = master.update(zone)
    up.add("svcb.ddns.", 3600, "SVCB", "0 target.svcb.svcbtarget.ddns.")
    up.send("NOERROR")
    resp = master.dig("svcb.ddns.", "SVCB", dnssec=dnssec)
    resp.check(rcode="NOERROR")
    resp.check_count(0, rtype="AAAA", section="additional")

    # add glue to SVCB
    check_log("Add glue to SVCB")
    up = master.update(zone)
    up.add("target.svcb.svcbtarget.ddns.", 3600, "AAAA", "1::2")
    up.add("target.svcb.svcbtarget.ddns.", 3600, "SVCB", "2 . alpn=h2")
    up.send("NOERROR")
    resp = master.dig("svcb.ddns.", "SVCB", dnssec=dnssec)
    resp.check(rcode="NOERROR")
    resp.check_count(1, rtype="AAAA", section="additional")
    resp.check_count(1, rtype="SVCB", section="additional")
    if dnssec:
        resp.check_count(3, rtype="RRSIG", section="additional")

    # remove glue from SVCB
    check_log("Remove glue from SVCB")
    up = master.update(zone)
    up.delete("target.svcb.svcbtarget.ddns.", "AAAA")
    up.delete("target.svcb.svcbtarget.ddns.", "SVCB")
    up.send("NOERROR")
    resp = master.dig("svcb.ddns.", "SVCB", dnssec=dnssec)
    resp.check(rcode="NOERROR")
    resp.check_count(0, rtype="AAAA", section="additional")
    resp.check_count(0, rtype="RRSIG", section="additional")

    # Add wildcard glue to SVCB
    check_log("SVCB wildcard glue")
    up = master.update(zone)
    up.add("*.svcbtarget.ddns.", 3600, "AAAA", "1::3")
    up.send("NOERROR")
    resp = master.dig("svcb.ddns.", "SVCB", dnssec=dnssec)
    resp.check(rcode="NOERROR")
    resp.check_record(section="additional", name="target.svcb.svcbtarget.ddns.", rtype="AAAA", ttl=3600, rdata="1::3")
    resp.check_count(1, rtype="AAAA", section="additional")
    if dnssec:
        resp.check_count(2, rtype="RRSIG", section="additional")

    # just shuffle with that SVCB
    check_log("SVCB shuffle")
    up.delete("svcb.ddns.", "SVCB")
    up.add("svcb.ddns.", 3600, "SVCB", "1 target.svcb.svcbtarget.ddns.")
    up.send("NOERROR")
    resp = master.dig("svcb.ddns.", "SVCB", dnssec=dnssec)
    resp.check(rcode="NOERROR")
    resp.check_record(section="additional", name="target.svcb.svcbtarget.ddns.", rtype="AAAA", ttl=3600, rdata="1::3")
    resp.check_count(1, rtype="AAAA", section="additional")
    if dnssec:
        resp.check_count(2, rtype="RRSIG", section="additional")

    # remove wildcard glue
    check_log("Remove wildcard glue")
    up = master.update(zone)
    up.delete("*.svcbtarget.ddns.", "AAAA", "1::3")
    up.send("NOERROR")
    t.sleep(1) # wait for zone_update_cleanup to trigger lost glue
    resp = master.dig("svcb.ddns.", "SVCB", dnssec=dnssec)
    resp.check(rcode="NOERROR")
    resp.check_count(0, rtype="AAAA", section="additional")
    resp.check_count(0, rtype="RRSIG", section="additional")
    # now remove SVCB in order to make ldns-verify work
    up = master.update(zone)
    up.delete("svcb.ddns.", "SVCB")
    up.send()

    # Add double-wildcard MX
    check_log("Double-wildcard MX")
    up = master.update(zone)
    up.add("double.wc.mx.ddns.", 3600, "MX", "1 a.*.ddns.")
    up.send("NOERROR")
    resp = master.dig("double.wc.mx.ddns.", "MX", dnssec=dnssec)
    resp.check(rcode="NOERROR")
    resp.check_count(1, rtype="MX", section="answer")
    resp.check_count(0, rtype="AAAA", section="additional")

    # Add double-wildcard glue
    check_log("Double-wildcard glue")
    up = master.update(zone)
    up.add("*.*.ddns.", 3600, "AAAA", "3::5")
    up.send("NOERROR")
    resp = master.dig("double.wc.mx.ddns.", "MX", dnssec=dnssec)
    resp.check(rcode="NOERROR")
    resp.check_count(1, rtype="MX", section="answer")
    resp.check_record(section="additional", name="a.*.ddns.", rtype="AAAA", ttl=3600, rdata="3::5")
    resp.check_count(1, rtype="AAAA", section="additional")

    # just shuffle with that MX
    check_log("MX shuffle")
    up = master.update(zone)
    up.delete("double.wc.mx.ddns.", "MX")
    up.add("double.wc.mx.ddns.", 3600, "MX", "2 a.*.ddns.")
    up.send("NOERROR")
    resp = master.dig("double.wc.mx.ddns.", "MX", dnssec=dnssec)
    resp.check(rcode="NOERROR")
    resp.check_count(1, rtype="MX", section="answer")
    resp.check_record(section="additional", name="a.*.ddns.", rtype="AAAA", ttl=3600, rdata="3::5")
    resp.check_count(1, rtype="AAAA", section="additional")

    # Remove double-wildcard glue
    check_log("Remove double-wildcard glue")
    up = master.update(zone)
    up.delete("*.*.ddns.", "AAAA")
    up.send("NOERROR")
    t.sleep(1) # wait for zone_update_cleanup to trigger lost glue
    resp = master.dig("double.wc.mx.ddns.", "MX", dnssec=dnssec)
    resp.check(rcode="NOERROR")
    resp.check_count(1, rtype="MX", section="answer")
    resp.check_count(0, rtype="AAAA", section="additional")

    # add CNAME to node with A records, should be ignored
    check_log("Add CNAME to A node")
    up = master.update(zone)
    up.add("dns1.ddns.", "3600", "CNAME", "ignore.me.ddns.")
    up.send("NOERROR")
    resp = master.dig("dns1.ddns.", "CNAME")
    compare(resp.count(), 0, "Added CNAME when it shouldn't")
    verify(master, zone, dnssec)

    # create new node by adding RR + try to add CNAME
    # the update should ignore the CNAME
    check_log("Add new node + add CNAME to it")
    up = master.update(zone)
    up.add("rrtest2.ddns.", "3600", "MX", "10 something.ddns.")
    up.add("rrtest2.ddns.", "3600", "CNAME", "ignore.me.ddns.")
    up.send("NOERROR")
    resp = master.dig("rrtest2.ddns.", "ANY")
    resp.check(rcode="NOERROR")
    resp.check_record(rtype="MX", rdata="10 something.ddns.")
    resp = master.dig("rrtest2.ddns.", "CNAME")
    compare(resp.count(section="answer"), 0, "Added CNAME when it shouldn't")
    verify(master, zone, dnssec)

    # add A to CNAME node, should be ignored
    check_log("Add A to CNAME node")
    up = master.update(zone)
    up.add("cname.ddns.", "3600", "A", "1.2.3.4")
    up.send("NOERROR")
    resp = master.dig("cname.ddns.", "ANY")
    resp.check(rcode="NOERROR")
    resp.check_record(rtype="A", nordata="1.2.3.4")
    resp.check_record(rtype="CNAME", rdata="mail.ddns.")
    verify(master, zone, dnssec)

    # add new node with CNAME + add A to the same node, A should be ignored
    check_log("Add new CNAME node + add A to it")
    up = master.update(zone)
    up.add("rrtest3.ddns.", "3600", "CNAME", "dont.ignore.me.ddns.")
    up.add("rrtest3.ddns.", "3600", "TXT", "ignore")
    up.send("NOERROR")
    resp = master.dig("rrtest3.ddns.", "ANY")
    resp.check(rcode="NOERROR")
    resp.check_record(rtype="TXT", nordata="ignore")
    resp.check_record(rtype="CNAME", rdata="dont.ignore.me.ddns.")
    verify(master, zone, dnssec)

    # add CNAME to CNAME node, should be replaced
    check_log("CNAME to CNAME addition")
    up = master.update(zone)
    up.add("cname.ddns.", 3600, "CNAME", "new-cname.ddns.")
    up.send("NOERROR")
    resp = master.dig("cname.ddns.", "CNAME")
    resp.check(rcode="NOERROR", rdata="new-cname.ddns.")
    resp.check(rcode="NOERROR", nordata="mail.ddns.")
    verify(master, zone, dnssec)

    # add new CNAME node + another CNAME to it; last CNAME should stay in zone
    check_log("Add two CNAMEs to a new node")
    up = master.update(zone)
    up.add("rrtest4.ddns.", "3600", "CNAME", "ignore.me.ddns.")
    up.add("rrtest4.ddns.", "3600", "CNAME", "dont.ignore.me.ddns.")
    up.send("NOERROR")
    resp = master.dig("rrtest3.ddns.", "ANY")
    resp.check(rcode="NOERROR")
    resp.check_record(rtype="CNAME", rdata="dont.ignore.me.ddns.")
    resp.check_record(rtype="CNAME", nordata="ignore.me.ddns")
    verify(master, zone, dnssec)

    # add SOA with higher than current serial, serial starting from 2010111213
    check_log("Newer SOA addition")
    up = master.update(zone)
    up.add("ddns.", 3600, "SOA",
           "dns1.ddns. hostmaster.ddns. 2011111213 10800 3600 1209600 7200")
    up.send("NOERROR")
    resp = master.dig("ddns.", "SOA")
    resp.check(rcode="NOERROR",
               rdata="dns1.ddns. hostmaster.ddns. 2011111213 10800 3600 1209600 7200")
    verify(master, zone, dnssec)

    # add SOA with higher serial + remove it in the same UPDATE
    # should result in replacing the SOA (i.e. the remove should be ignored)
    check_log("Newer SOA addition + removal")
    up = master.update(zone)
    up.add("ddns.", 3600, "SOA",
           "dns1.ddns. hostmaster.ddns. 2012111213 10800 3600 1209600 7200")
    up.delete("ddns.", "SOA",
           "dns1.ddns. hostmaster.ddns. 2012111213 10800 3600 1209600 7200")
    up.send("NOERROR")
    resp = master.dig("ddns.", "SOA")
    resp.check(rcode="NOERROR",
               rdata="dns1.ddns. hostmaster.ddns. 2012111213 10800 3600 1209600 7200")
    verify(master, zone, dnssec)

    # add SOA with higher serial + remove all SOA in the same UPDATE
    # the removal should be ignored, only replacing the SOA
    check_log("Newer SOA addition + removal of all SOA")
    up = master.update(zone)
    up.add("ddns.", 3600, "SOA",
           "dns1.ddns. hostmaster.ddns. 2013111213 10800 3600 1209600 7200")
    up.delete("ddns.", "SOA")
    up.send("NOERROR")
    resp = master.dig("ddns.", "SOA")
    resp.check(rcode="NOERROR")
    resp.check_record(rtype="SOA", rdata="dns1.ddns. hostmaster.ddns. 2013111213 10800 3600 1209600 7200")
    verify(master, zone, dnssec)

    # add SOA with lower serial, should be ignored
    check_log("Older SOA addition")
    up = master.update(zone)
    up.add("ddns.", 3600, "SOA",
           "dns1.ddns. hostmaster.ddns. 2010111213 10800 3600 1209600 7200")
    up.send("NOERROR")
    resp = master.dig("ddns.", "SOA")
    resp.check(rcode="NOERROR",
               rdata="dns1.ddns. hostmaster.ddns. 2013111213 10800 3600 1209600 7200")
    verify(master, zone, dnssec)

    # add SOA with different TTL
    check_log("SOA different TTL")
    up = master.update(zone)
    up.add("ddns.", 1800, "SOA",
           "dns1.ddns. hostmaster.ddns. 2014111213 10800 1800 1209600 7200")
    up.send("NOERROR")
    resp = master.dig("ddns.", "SOA")
    resp.check(rcode="NOERROR",
               rdata="dns1.ddns. hostmaster.ddns. 2014111213 10800 1800 1209600 7200")
    verify(master, zone, dnssec)

    # add and remove the same record
    check_log("Add and remove same record")
    up = master.update(zone)
    up.add("testaddrem.ddns.", 3600, "TXT", "record")
    up.delete("testaddrem.ddns.", "TXT", "record")
    up.send("NOERROR")
    resp = master.dig("testaddrem.ddns.", "TXT")
    resp.check(rcode="NXDOMAIN")
    verify(master, zone, dnssec)

    # add and remove the same record, delete whole RRSet
    check_log("Add and remove same record, delete whole")
    up = master.update(zone)
    up.add("testaddrem.ddns.", 3600, "TXT", "record")
    up.delete("testaddrem.ddns.", "TXT")
    up.send("NOERROR")
    resp = master.dig("testaddrem.ddns.", "TXT")
    resp.check(rcode="NXDOMAIN")
    verify(master, zone, dnssec)

    # remove non-existent record
    check_log("Remove non-existent record")
    up = master.update(zone)
    up.delete("testaddrem.ddns.", "TXT", "record")
    up.send("NOERROR")
    verify(master, zone, dnssec)

    # remove NS from APEX (NS should stay)
    check_log("Remove NS")
    up = master.update(zone)
    up.delete("ddns.", "NS")
    up.send("NOERROR")
    resp = master.dig("ddns.", "NS")
    resp.check(rcode="NOERROR")
    resp.check_record(rtype="NS", rdata="dns1.ddns.")
    resp.check_record(rtype="NS", rdata="dns2.ddns.")
    verify(master, zone, dnssec)

    # remove all from APEX (NS should stay)
    check_log("Remove all NS")
    up = master.update(zone)
    up.delete("ddns.", "ANY")
    up.send("NOERROR")
    resp = master.dig("ddns.", "NS")
    resp.check(rcode="NOERROR")
    resp.check_record(rtype="NS", rdata="dns1.ddns.")
    resp.check_record(rtype="NS", rdata="dns2.ddns.")
    resp = master.dig("ddns.", "MX")
    resp.check(rcode="NOERROR")
    compare(resp.count(section="answer"), 0, "MX rrset removal")
    verify(master, zone, dnssec)

    # remove all NS + add 1 new; result: 3 RRs
    check_log("Remove all NS + add 1 new")
    up = master.update(zone)
    up.delete("ddns.", "NS")
    up.add("ddns.", 3600, "NS", "dns3.ddns.")
    up.send("NOERROR")
    resp = master.dig("ddns.", "NS")
    resp.check(rcode="NOERROR")
    resp.check_record(rtype="NS", rdata="dns1.ddns.")
    resp.check_record(rtype="NS", rdata="dns2.ddns.")
    resp.check_record(rtype="NS", rdata="dns3.ddns.")
    verify(master, zone, dnssec)

    # remove NSs one at a time + add one new
    # the last one + the new one should remain in the zone
    check_log("Remove NSs one at a time + add 1 new")
    up = master.update(zone)
    up.delete("ddns.", "NS", "dns1.ddns.")
    up.delete("ddns.", "NS", "dns2.ddns.")
    up.delete("ddns.", "NS", "dns3.ddns.")
    up.add("ddns.", 3600, "NS", "dns4.ddns.")
    up.send("NOERROR")
    resp = master.dig("ddns.", "NS")
    resp.check(rcode="NOERROR", nordata="dns1.ddns.")
    resp.check(nordata="dns2.ddns.")
    resp.check_record(rtype="NS", rdata="dns3.ddns.")
    resp.check_record(rtype="NS", rdata="dns4.ddns.")
    verify(master, zone, dnssec)

    # add new NS + remove all one at a time
    # only the new NS should remain in the zone
    check_log("Add 1 NS + remove all NSs one at a time")
    up = master.update(zone)
    up.add("ddns.", 3600, "NS", "dns5.ddns.")
    up.delete("ddns.", "NS", "dns3.ddns.")
    up.delete("ddns.", "NS", "dns4.ddns.")
    up.send("NOERROR")
    resp = master.dig("ddns.", "NS")
    resp.check(rcode="NOERROR", nordata="dns3.ddns.")
    resp.check(nordata="dns4.ddns.")
    resp.check_record(rtype="NS", rdata="dns5.ddns.")
    verify(master, zone, dnssec)

    # add new NS + remove the old one; only the new one should remain
    check_log("Add 1 NS + remove old NS")
    up = master.update(zone)
    up.add("ddns.", 3600, "NS", "dns1.ddns.")
    up.delete("ddns.", "NS", "dns5.ddns.")
    up.send("NOERROR")
    resp = master.dig("ddns.", "NS")
    resp.check(rcode="NOERROR", nordata="dns5.ddns.")
    resp.check_record(rtype="NS", rdata="dns1.ddns.")
    verify(master, zone, dnssec)

    # remove old NS + add new NS; both should remain in the zone
    check_log("Remove old NS + add 1 NS")
    up = master.update(zone)
    up.delete("ddns.", "NS", "dns1.ddns.")
    up.add("ddns.", 3600, "NS", "dns2.ddns.")
    up.send("NOERROR")
    resp = master.dig("ddns.", "NS")
    resp.check(rcode="NOERROR")
    resp.check_record(rtype="NS", rdata="dns1.ddns.")
    resp.check_record(rtype="NS", rdata="dns2.ddns.")
    verify(master, zone, dnssec)

    # remove NSs one at a time; the last one should remain in the zone
    check_log("Remove NSs one at a time")
    up = master.update(zone)
    up.delete("ddns.", "NS", "dns1.ddns.")
    up.delete("ddns.", "NS", "dns2.ddns.")
    up.send("NOERROR")
    resp = master.dig("ddns.", "NS")
    resp.check(rcode="NOERROR", nordata="dns1.ddns.")
    resp.check_record(rtype="NS", rdata="dns2.ddns.")
    verify(master, zone, dnssec)

    # add new NS + remove ALL NS; should ignore the remove and add the NS
    check_log("Add new NS + remove ALL NSs at once")
    up = master.update(zone)
    up.add("ddns.", 3600, "NS", "dns1.ddns.")
    up.delete("ddns.", "NS")
    up.send("NOERROR")
    resp = master.dig("ddns.", "NS")
    resp.check_record(rtype="NS", rdata="dns1.ddns.")
    resp.check_record(rtype="NS", rdata="dns2.ddns.")
    verify(master, zone, dnssec)

    # add empty generic record
    check_log("Add empty generic record")
    up = master.update(zone)
    up.add("empty.ddns.", 300, "TYPE999", "\\# 0")
    up.send("NOERROR")
    resp = master.dig("empty.ddns.", "TYPE999")
    resp.check_record(rtype="TYPE999", rdata="\\# 0")
    verify(master, zone, dnssec)

    # add NAPTR record (NAPTR has special processing)
    check_log("Add NAPTR record")
    up = master.update(zone)
    up.add("3.1.1.1.1.1.1.1.1.2.7.9.9.ddns.", 172800, "NAPTR", "1 1 \"u\" \"E2U+sip\" \"!^.*$!sip:123@freeswitch.org!\" .")
    up.send("NOERROR")
    resp = master.dig("3.1.1.1.1.1.1.1.1.2.7.9.9.ddns.", "NAPTR")
    resp.check_record(rtype="NAPTR", rdata="1 1 \"u\" \"E2U+sip\" \"!^.*$!sip:123@freeswitch.org!\" .")
    verify(master, zone, dnssec)

    # modify zone apex
    check_log("Add TXT into apex")
    up = master.update(zone)
    up.add("ddns.", 300, "TXT", "This is apeeex!")
    up.send("NOERROR")
    resp = master.dig("ddns.", "TXT")
    resp.check_record(rtype="TXT", rdata="This is apeeex!")
    verify(master, zone, dnssec)

    if dnssec:
        # add DS for existing delegation
        check_log("DS addition")
        up = master.update(zone)
        up.add("deleg.ddns.", 3600, "DS",
               "54576 10 2 397E50C85EDE9CDE33F363A9E66FD1B216D788F8DD438A57A423A386869C8F06")
        up.send("NOERROR")
        resp = master.dig("deleg.ddns.", "NS", dnssec=True)
        resp.check(rcode="NOERROR")
        resp.check_record(section="authority", rtype="DS",
                          rdata="54576 10 2 397E50C85EDE9CDE33F363A9E66FD1B216D788F8DD438A57A423A386869C8F06")
        resp.check_record(section="authority", rtype="NS", rdata="a.deleg.ddns.")
        resp.check_record(section="authority", rtype="RRSIG")
        verify(master, zone, dnssec)

        # add AAAA to existing A glue
        check_log("glue augmentation")
        up = master.update(zone)
        up.add("a.deleg.ddns.", 3600, "AAAA", "1::2")
        up.send("NOERROR")
        resp = master.dig("xy.deleg.ddns.", "A", dnssec=True)
        resp.check_rr(section="authority", rname="deleg.ddns.", rtype="NS")
        resp.check_rr(section="authority", rname="deleg.ddns.", rtype="RRSIG")
        resp.check_rr(section="additional", rname="a.deleg.ddns.", rtype="AAAA")
        resp.check_no_rr(section="additional", rname="a.deleg.ddns.", rtype="RRSIG")
        verify(master, zone, dnssec)

def do_refusal_tests(master, zone, dnssec=False):
    # Store initial SOA
    soa_resp = master.dig("ddns.", "SOA")
    prev_soa = soa_resp.resp.answer

    # Out-of-zone data
    check_log("Out-of-zone data")
    up = master.update(zone)
    up.add("what.the.hell.am.i.doing.here.", "3600", "TXT", "I don't belong here")
    up.send("NOTZONE")
    check_soa(master, prev_soa)

    # Remove 'all' SOA, ignore
    check_log("Remove all SOA")
    up = master.update(zone)
    up.delete("ddns.", "SOA")
    up.send("NOERROR")
    check_soa(master, prev_soa)

    # Remove specific SOA, ignore
    check_log("Remove specific SOA")
    up = master.update(zone)
    up.delete("ddns.", "SOA", "dns1.ddns. hostmaster.ddns. 2011111213 10800 3600 1209600 7200")
    up.send("NOERROR")
    check_soa(master, prev_soa)

    # Out-of-apex NSEC3PARAM
    check_log("Out-of-apex NSEC3PARAM")
    up = master.update(zone)
    up.add("mx.ddns.", 3600, "NSEC3PARAM", "1 0 0 -")
    up.send("REFUSED")
    check_soa(master, prev_soa)

    # Added NSEC3PARAM
    check_log("Added NSEC3PARAM")
    up = master.update(zone)
    up.add("ddns.", 3600, "NSEC3PARAM", "1 0 0 -")
    up.send("REFUSED" if master.dnssec(zone).nsec3 else "NOERROR")
    resp = master.dig("ddns.", "NSEC3PARAM")
    resp.check_count(1, "NSEC3PARAM")
    if master.dnssec(zone).nsec3:
        check_soa(master, prev_soa)
        resp.check(nordata="1 0 0 -")
    else:
        resp.check(rdata="1 0 0 -")

    if dnssec:
        # Add DNSKEY
        check_log("non-apex DNSKEY addition")
        up = master.update(zone)
        up.add("nonapex.ddns.", "3600", "DNSKEY",
               "256 3 5 AwEAAbs0AlA6xWQn/lECfGt3S6TaeEmgJfEVVEMh06iNMNWMRHOfbqLF h3N52Ob7trmzlrzGlGLPnAZJvMB8lsFGC5CtaLUBD+4xCh5tl5QifZ+y o+MJvPGlVQI2cs7aMWV9CyFrRmuRcJaSZU2uBz9KFJ955UCq/WIy5KqS 7qaKLzzN")
        up.send("NOERROR")
        resp = master.dig("nonapex.ddns.", "DNSKEY")
        resp.check(rcode="NOERROR",
                   rdata="256 3 5 AwEAAbs0AlA6xWQn/lECfGt3S6TaeEmgJfEVVEMh06iNMNWMRHOfbqLF h3N52Ob7trmzlrzGlGLPnAZJvMB8lsFGC5CtaLUBD+4xCh5tl5QifZ+y o+MJvPGlVQI2cs7aMWV9CyFrRmuRcJaSZU2uBz9KFJ955UCq/WIy5KqS 7qaKLzzN")

zone = t.zone("ddns.", storage=".")

master_plain = t.server("knot")
t.link(zone, master_plain, ddns=True)

master_nsec = t.server("knot")
t.link(zone, master_nsec, ddns=True)
master_nsec.dnssec(zone).enable = True

master_nsec3 = t.server("knot")
t.link(zone, master_nsec3, ddns=True)
master_nsec3.dnssec(zone).enable = True
master_nsec3.dnssec(zone).nsec3 = True
master_nsec3.dnssec(zone).nsec3_salt_length = 8
master_nsec3.dnssec(zone).nsec3_opt_out = (random.random() < 0.5)

t.start()

# DNSSEC-less test
check_log("============ Plain test ===========")
do_normal_tests(master_plain, zone)
do_refusal_tests(master_plain, zone)

# DNSSEC with NSEC test
check_log("============ NSEC test ============")
do_normal_tests(master_nsec, zone, dnssec=True)
do_refusal_tests(master_nsec, zone, dnssec=True)

# DNSSEC with NSEC3 test
check_log("============ NSEC3 test ===========")
do_normal_tests(master_nsec3, zone, dnssec=True)
do_refusal_tests(master_nsec3, zone, dnssec=True)

t.end()
