From e6302ef9d580f99f1704e29dfece28aef04e0579 Mon Sep 17 00:00:00 2001
From: Ingo Bauersachs <ingo@jitsi.org>
Date: Sat, 17 Feb 2024 21:56:48 +0100
Subject: [PATCH] Remove mix of how SetResponse is constructed

---
 src/main/java/org/xbill/DNS/Cache.java        |  34 +--
 src/main/java/org/xbill/DNS/SetResponse.java  | 119 +++-----
 .../java/org/xbill/DNS/SetResponseType.java   |  48 +++
 src/main/java/org/xbill/DNS/Zone.java         |  20 +-
 .../java/org/xbill/DNS/SetResponseTest.java   | 274 ++++++------------
 5 files changed, 207 insertions(+), 288 deletions(-)
 create mode 100644 src/main/java/org/xbill/DNS/SetResponseType.java

diff --git a/src/main/java/org/xbill/DNS/Cache.java b/src/main/java/org/xbill/DNS/Cache.java
index e1c88ea..a93af2a 100644
--- a/src/main/java/org/xbill/DNS/Cache.java
+++ b/src/main/java/org/xbill/DNS/Cache.java
@@ -422,7 +422,6 @@ public class Cache {
     Element element;
     Name tname;
     Object types;
-    SetResponse sr;
 
     labels = name.labels();
 
@@ -449,8 +448,8 @@ public class Cache {
        * Otherwise, look for a DNAME.
        */
       if (isExact && type == Type.ANY) {
-        sr = new SetResponse(SetResponse.SUCCESSFUL);
         Element[] elements = allElements(types);
+	SetResponse sr = SetResponse.ofType(SetResponseType.SUCCESSFUL);
         int added = 0;
         for (Element value : elements) {
           element = value;
@@ -474,40 +473,37 @@ public class Cache {
       } else if (isExact) {
         element = oneElement(tname, types, type, minCred);
         if (element instanceof CacheRRset) {
-          sr = new SetResponse(SetResponse.SUCCESSFUL);
-          sr.addRRset((CacheRRset) element);
-          return sr;
+	  return SetResponse.ofType(SetResponseType.SUCCESSFUL, (CacheRRset) element);
         } else if (element != null) {
-          sr = new SetResponse(SetResponse.NXRRSET);
-          return sr;
+	  return SetResponse.ofType(SetResponseType.NXRRSET);
         }
 
         element = oneElement(tname, types, Type.CNAME, minCred);
         if (element instanceof CacheRRset) {
-          return new SetResponse(SetResponse.CNAME, (CacheRRset) element);
+	  return SetResponse.ofType(SetResponseType.CNAME, (CacheRRset) element);
         }
       } else {
         element = oneElement(tname, types, Type.DNAME, minCred);
         if (element instanceof CacheRRset) {
-          return new SetResponse(SetResponse.DNAME, (CacheRRset) element);
+	  return SetResponse.ofType(SetResponseType.DNAME, (CacheRRset) element);
         }
       }
 
       /* Look for an NS */
       element = oneElement(tname, types, Type.NS, minCred);
       if (element instanceof CacheRRset) {
-        return new SetResponse(SetResponse.DELEGATION, (CacheRRset) element);
+	return SetResponse.ofType(SetResponseType.DELEGATION, (CacheRRset) element);
       }
 
       /* Check for the special NXDOMAIN element. */
       if (isExact) {
         element = oneElement(tname, types, 0, minCred);
         if (element != null) {
-          return SetResponse.ofType(SetResponse.NXDOMAIN);
+	  return SetResponse.ofType(SetResponseType.NXDOMAIN);
         }
       }
     }
-    return SetResponse.ofType(SetResponse.UNKNOWN);
+    return SetResponse.ofType(SetResponseType.UNKNOWN);
   }
 
   /**
@@ -641,7 +637,7 @@ public class Cache {
         completed = true;
         if (curname == qname) {
           if (response == null) {
-            response = new SetResponse(SetResponse.SUCCESSFUL);
+	    response = SetResponse.ofType(SetResponseType.SUCCESSFUL);
           }
           response.addRRset(answer);
         }
@@ -650,7 +646,7 @@ public class Cache {
         CNAMERecord cname;
         addRRset(answer, cred);
         if (curname == qname) {
-          response = new SetResponse(SetResponse.CNAME, answer);
+	  response = SetResponse.ofType(SetResponseType.CNAME, answer);
         }
         cname = (CNAMERecord) answer.first();
         curname = cname.getTarget();
@@ -658,7 +654,7 @@ public class Cache {
         DNAMERecord dname;
         addRRset(answer, cred);
         if (curname == qname) {
-          response = new SetResponse(SetResponse.DNAME, answer);
+	  response = SetResponse.ofType(SetResponseType.DNAME, answer);
         }
         dname = (DNAMERecord) answer.first();
         try {
@@ -691,13 +687,13 @@ public class Cache {
         }
         addNegative(curname, cachetype, soarec, cred);
         if (response == null) {
-          int responseType;
+	  SetResponseType responseType;
           if (rcode == Rcode.NXDOMAIN) {
-            responseType = SetResponse.NXDOMAIN;
+	    responseType = SetResponseType.NXDOMAIN;
           } else {
-            responseType = SetResponse.NXRRSET;
+	    responseType = SetResponseType.NXRRSET;
           }
-          response = SetResponse.ofType(responseType);
+	  response = SetResponse.ofType(SetResponseType.DELEGATION, ns);
         }
         /* DNSSEC records are not cached. */
       } else {
diff --git a/src/main/java/org/xbill/DNS/SetResponse.java b/src/main/java/org/xbill/DNS/SetResponse.java
index 3fbf855..b67db66 100644
--- a/src/main/java/org/xbill/DNS/SetResponse.java
+++ b/src/main/java/org/xbill/DNS/SetResponse.java
@@ -3,8 +3,17 @@
 
 package org.xbill.DNS;
 
+import static org.xbill.DNS.SetResponseType.CNAME;
+import static org.xbill.DNS.SetResponseType.DELEGATION;
+import static org.xbill.DNS.SetResponseType.DNAME;
+import static org.xbill.DNS.SetResponseType.NXDOMAIN;
+import static org.xbill.DNS.SetResponseType.NXRRSET;
+import static org.xbill.DNS.SetResponseType.SUCCESSFUL;
+import static org.xbill.DNS.SetResponseType.UNKNOWN;
+
 import java.util.ArrayList;
 import java.util.List;
+import lombok.Getter;
 
 /**
  * The Response from a query to {@link Cache#lookupRecords(Name, int, int)} or {@link
@@ -15,93 +24,64 @@ import java.util.List;
  * @author Brian Wellington
  */
 public class SetResponse {
+  private static final SetResponse SR_UNKNOWN = new SetResponse(UNKNOWN, null, false);
+  private static final SetResponse SR_UNKNOWN_AUTH = new SetResponse(UNKNOWN, null, true);
+  private static final SetResponse SR_NXDOMAIN = new SetResponse(NXDOMAIN, null, false);
+  private static final SetResponse SR_NXDOMAIN_AUTH = new SetResponse(NXDOMAIN, null, true);
+  private static final SetResponse SR_NXRRSET = new SetResponse(NXRRSET, null, false);
+  private static final SetResponse SR_NXRRSET_AUTH = new SetResponse(NXRRSET, null, true);
 
-  /** The Cache contains no information about the requested name/type */
-  static final int UNKNOWN = 0;
+  private final SetResponseType type;
 
   /**
-   * The Zone does not contain the requested name, or the Cache has determined that the name does
-   * not exist.
+   * @since 3.6
    */
-  static final int NXDOMAIN = 1;
-
-  /**
-   * The Zone contains the name, but no data of the requested type, or the Cache has determined that
-   * the name exists and has no data of the requested type.
-   */
-  static final int NXRRSET = 2;
-
-  /** A delegation enclosing the requested name was found. */
-  static final int DELEGATION = 3;
+  @Getter private boolean isAuthenticated;
 
-  /**
-   * The Cache/Zone found a CNAME when looking for the name.
-   *
-   * @see CNAMERecord
-   */
-  static final int CNAME = 4;
-
-  /**
-   * The Cache/Zone found a DNAME when looking for the name.
-   *
-   * @see DNAMERecord
-   */
-  static final int DNAME = 5;
-
-  /** The Cache/Zone has successfully answered the question for the requested name/type/class. */
-  static final int SUCCESSFUL = 6;
-
-  private static final SetResponse unknown = new SetResponse(UNKNOWN);
-  private static final SetResponse nxdomain = new SetResponse(NXDOMAIN);
-  private static final SetResponse nxrrset = new SetResponse(NXRRSET);
-
-  private int type;
   private List<RRset> data;
-
-  private SetResponse() {}
-
-  SetResponse(int type, RRset rrset) {
-    if (type < 0 || type > 6) {
-      throw new IllegalArgumentException("invalid type");
-    }
+  private SetResponse(SetResponseType type, RRset rrset, boolean isAuthenticated) {
     this.type = type;
-    this.data = new ArrayList<>();
-    this.data.add(rrset);
+    this.isAuthenticated = isAuthenticated;
+    if (rrset != null) {
+      addRRset(rrset);
+    }
   }
 
-  SetResponse(int type) {
-    if (type < 0 || type > 6) {
-      throw new IllegalArgumentException("invalid type");
-    }
-    this.type = type;
-    this.data = null;
+  static SetResponse ofType(SetResponseType type) {
+    return ofType(type, null, false);
   }
 
-  static SetResponse ofType(int type) {
+  static SetResponse ofType(SetResponseType type, RRset rrset) {
+    return ofType(type, rrset, false);
+  }
+
+  static SetResponse ofType(SetResponseType type, RRset rrset, boolean isAuthenticated) {
     switch (type) {
       case UNKNOWN:
-        return unknown;
+	return isAuthenticated ? SR_UNKNOWN_AUTH : SR_UNKNOWN;
       case NXDOMAIN:
-        return nxdomain;
+	return isAuthenticated ? SR_NXDOMAIN_AUTH : SR_NXDOMAIN;
       case NXRRSET:
-        return nxrrset;
+	return isAuthenticated ? SR_NXRRSET_AUTH : SR_NXRRSET;
       case DELEGATION:
       case CNAME:
       case DNAME:
       case SUCCESSFUL:
-        SetResponse sr = new SetResponse();
-        sr.type = type;
-        sr.data = null;
-        return sr;
+	return new SetResponse(type, rrset, isAuthenticated);
       default:
         throw new IllegalArgumentException("invalid type");
     }
   }
 
   void addRRset(RRset rrset) {
+    if (type.isSealed()) {
+      throw new IllegalStateException("Attempted to add RRset to sealed response of type " + type);
+    }
+
     if (data == null) {
       data = new ArrayList<>();
     }
+
     data.add(rrset);
   }
 
@@ -160,29 +140,12 @@ public class SetResponse {
 
   /** If the query hit a delegation point, return the NS set. */
   public RRset getNS() {
-    return (data != null) ? data.get(0) : null;
+    return data != null ? data.get(0) : null;
   }
 
   /** Prints the value of the SetResponse */
   @Override
   public String toString() {
-    switch (type) {
-      case UNKNOWN:
-        return "unknown";
-      case NXDOMAIN:
-        return "NXDOMAIN";
-      case NXRRSET:
-        return "NXRRSET";
-      case DELEGATION:
-        return "delegation: " + data.get(0);
-      case CNAME:
-        return "CNAME: " + data.get(0);
-      case DNAME:
-        return "DNAME: " + data.get(0);
-      case SUCCESSFUL:
-        return "successful";
-      default:
-        throw new IllegalStateException();
-    }
+    return type + (type.isPrintRecords() ? ": " + data.get(0) : "");
   }
 }
diff --git a/src/main/java/org/xbill/DNS/SetResponseType.java b/src/main/java/org/xbill/DNS/SetResponseType.java
new file mode 100644
index 0000000..791c774
--- /dev/null
+++ b/src/main/java/org/xbill/DNS/SetResponseType.java
@@ -0,0 +1,48 @@
+package org.xbill.DNS;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor
+enum SetResponseType {
+  /** The Cache contains no information about the requested name/type */
+  UNKNOWN(false, true),
+
+  /**
+   * The Zone does not contain the requested name, or the Cache has determined that the name does
+   * not exist.
+   */
+  NXDOMAIN(false, true),
+
+  /**
+   * The Zone contains the name, but no data of the requested type, or the Cache has determined that
+   * the name exists and has no data of the requested type.
+   */
+  NXRRSET(false, true),
+
+  /** A delegation enclosing the requested name was found. */
+  DELEGATION(true, false),
+
+  /**
+   * The Cache/Zone found a CNAME when looking for the name.
+   *
+   * @see CNAMERecord
+   */
+  CNAME(true, false),
+
+  /**
+   * The Cache/Zone found a DNAME when looking for the name.
+   *
+   * @see DNAMERecord
+   */
+  DNAME(true, false),
+
+  /** The Cache/Zone has successfully answered the question for the requested name/type/class. */
+  SUCCESSFUL(false, false);
+
+  private final boolean printRecords;
+
+  /** If true, no RRsets can be added. Intended for static NX* instances. */
+  private final boolean isSealed;
+}
diff --git a/src/main/java/org/xbill/DNS/Zone.java b/src/main/java/org/xbill/DNS/Zone.java
index e335a27..7c9e7d1 100644
--- a/src/main/java/org/xbill/DNS/Zone.java
+++ b/src/main/java/org/xbill/DNS/Zone.java
@@ -338,7 +338,7 @@ public class Zone implements Serializable {
 
   private synchronized SetResponse lookup(Name name, int type) {
     if (!name.subdomain(origin)) {
-      return SetResponse.ofType(SetResponse.NXDOMAIN);
+      return SetResponse.ofType(SetResponseType.NXDOMAIN);
     }
 
     int labels = name.labels();
@@ -366,13 +366,13 @@ public class Zone implements Serializable {
       if (!isOrigin) {
         RRset ns = oneRRset(types, Type.NS);
         if (ns != null) {
-          return new SetResponse(SetResponse.DELEGATION, ns);
+	  return SetResponse.ofType(SetResponseType.DELEGATION, ns);
         }
       }
 
       /* If this is an ANY lookup, return everything. */
       if (isExact && type == Type.ANY) {
-        SetResponse sr = new SetResponse(SetResponse.SUCCESSFUL);
+	SetResponse sr = SetResponse.ofType(SetResponseType.SUCCESSFUL);
         for (RRset set : allRRsets(types)) {
           sr.addRRset(set);
         }
@@ -386,22 +386,22 @@ public class Zone implements Serializable {
       if (isExact) {
         RRset rrset = oneRRset(types, type);
         if (rrset != null) {
-          return new SetResponse(SetResponse.SUCCESSFUL, rrset);
+	  return SetResponse.ofType(SetResponseType.SUCCESSFUL, rrset);
         }
         rrset = oneRRset(types, Type.CNAME);
         if (rrset != null) {
-          return new SetResponse(SetResponse.CNAME, rrset);
+	  return SetResponse.ofType(SetResponseType.CNAME, rrset);
         }
       } else {
         RRset rrset = oneRRset(types, Type.DNAME);
         if (rrset != null) {
-          return new SetResponse(SetResponse.DNAME, rrset);
+	  return SetResponse.ofType(SetResponseType.DNAME, rrset);
         }
       }
 
       /* We found the name, but not the type. */
       if (isExact) {
-        return SetResponse.ofType(SetResponse.NXRRSET);
+	return SetResponse.ofType(SetResponseType.NXRRSET);
       }
     }
 
@@ -414,7 +414,7 @@ public class Zone implements Serializable {
         }
 
         if (type == Type.ANY) {
-          SetResponse sr = new SetResponse(SetResponse.SUCCESSFUL);
+	  SetResponse sr = SetResponse.ofType(SetResponseType.SUCCESSFUL);
           for (RRset set : allRRsets(types)) {
             sr.addRRset(expandSet(set, name));
           }
@@ -422,13 +422,13 @@ public class Zone implements Serializable {
         } else {
           RRset rrset = oneRRset(types, type);
           if (rrset != null) {
-            return new SetResponse(SetResponse.SUCCESSFUL, expandSet(rrset, name));
+	    return SetResponse.ofType(SetResponseType.SUCCESSFUL, expandSet(rrset, name));
           }
         }
       }
     }
 
-    return SetResponse.ofType(SetResponse.NXDOMAIN);
+    return SetResponse.ofType(SetResponseType.NXDOMAIN);
   }
 
   private RRset expandSet(RRset set, Name tname) {
diff --git a/src/test/java/org/xbill/DNS/SetResponseTest.java b/src/test/java/org/xbill/DNS/SetResponseTest.java
index 36d59d7..7bc460d 100644
--- a/src/test/java/org/xbill/DNS/SetResponseTest.java
+++ b/src/test/java/org/xbill/DNS/SetResponseTest.java
@@ -45,145 +45,85 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
 
 class SetResponseTest {
-  @Test
-  void ctor_1arg() {
-    final int[] types =
-        new int[] {
-          SetResponse.UNKNOWN,
-          SetResponse.NXDOMAIN,
-          SetResponse.NXRRSET,
-          SetResponse.DELEGATION,
-          SetResponse.CNAME,
-          SetResponse.DNAME,
-          SetResponse.SUCCESSFUL
-        };
-
-    for (int type : types) {
-      SetResponse sr = new SetResponse(type);
-      assertNull(sr.getNS());
-      assertEquals(type == SetResponse.UNKNOWN, sr.isUnknown());
-      assertEquals(type == SetResponse.NXDOMAIN, sr.isNXDOMAIN());
-      assertEquals(type == SetResponse.NXRRSET, sr.isNXRRSET());
-      assertEquals(type == SetResponse.DELEGATION, sr.isDelegation());
-      assertEquals(type == SetResponse.CNAME, sr.isCNAME());
-      assertEquals(type == SetResponse.DNAME, sr.isDNAME());
-      assertEquals(type == SetResponse.SUCCESSFUL, sr.isSuccessful());
-    }
-  }
-
-  @Test
-  void ctor_1arg_toosmall() {
-    assertThrows(IllegalArgumentException.class, () -> new SetResponse(-1));
-  }
-
-  @Test
-  void ctor_1arg_toobig() {
-    assertThrows(IllegalArgumentException.class, () -> new SetResponse(7));
-  }
-
-  @Test
-  void ctor_2arg() {
-    final int[] types =
-        new int[] {
-          SetResponse.UNKNOWN,
-          SetResponse.NXDOMAIN,
-          SetResponse.NXRRSET,
-          SetResponse.DELEGATION,
-          SetResponse.CNAME,
-          SetResponse.DNAME,
-          SetResponse.SUCCESSFUL
-        };
-
-    for (int type : types) {
-      RRset rs = new RRset();
-      SetResponse sr = new SetResponse(type, rs);
-      assertSame(rs, sr.getNS());
-      assertEquals(type == SetResponse.UNKNOWN, sr.isUnknown());
-      assertEquals(type == SetResponse.NXDOMAIN, sr.isNXDOMAIN());
-      assertEquals(type == SetResponse.NXRRSET, sr.isNXRRSET());
-      assertEquals(type == SetResponse.DELEGATION, sr.isDelegation());
-      assertEquals(type == SetResponse.CNAME, sr.isCNAME());
-      assertEquals(type == SetResponse.DNAME, sr.isDNAME());
-      assertEquals(type == SetResponse.SUCCESSFUL, sr.isSuccessful());
-    }
-  }
-
-  @Test
-  void ctor_2arg_toosmall() {
-    assertThrows(IllegalArgumentException.class, () -> new SetResponse(-1, new RRset()));
-  }
-
-  @Test
-  void ctor_2arg_toobig() {
-    assertThrows(IllegalArgumentException.class, () -> new SetResponse(7, new RRset()));
-  }
-
-  @Test
-  void ofType_basic() {
-    final int[] types =
-        new int[] {
-          SetResponse.DELEGATION, SetResponse.CNAME, SetResponse.DNAME, SetResponse.SUCCESSFUL
-        };
-
-    for (int type : types) {
-      SetResponse sr = SetResponse.ofType(type);
-      assertNull(sr.getNS());
-      assertEquals(type == SetResponse.UNKNOWN, sr.isUnknown());
-      assertEquals(type == SetResponse.NXDOMAIN, sr.isNXDOMAIN());
-      assertEquals(type == SetResponse.NXRRSET, sr.isNXRRSET());
-      assertEquals(type == SetResponse.DELEGATION, sr.isDelegation());
-      assertEquals(type == SetResponse.CNAME, sr.isCNAME());
-      assertEquals(type == SetResponse.DNAME, sr.isDNAME());
-      assertEquals(type == SetResponse.SUCCESSFUL, sr.isSuccessful());
-
-      SetResponse sr2 = SetResponse.ofType(type);
-      assertNotSame(sr, sr2);
-    }
-  }
-
-  @Test
-  void ofType_singleton() {
-    final int[] types = new int[] {SetResponse.UNKNOWN, SetResponse.NXDOMAIN, SetResponse.NXRRSET};
-
-    for (int type : types) {
-      SetResponse sr = SetResponse.ofType(type);
-      assertNull(sr.getNS());
-      assertEquals(type == SetResponse.UNKNOWN, sr.isUnknown());
-      assertEquals(type == SetResponse.NXDOMAIN, sr.isNXDOMAIN());
-      assertEquals(type == SetResponse.NXRRSET, sr.isNXRRSET());
-      assertEquals(type == SetResponse.DELEGATION, sr.isDelegation());
-      assertEquals(type == SetResponse.CNAME, sr.isCNAME());
-      assertEquals(type == SetResponse.DNAME, sr.isDNAME());
-      assertEquals(type == SetResponse.SUCCESSFUL, sr.isSuccessful());
-
-      SetResponse sr2 = SetResponse.ofType(type);
-      assertSame(sr, sr2);
-    }
-  }
-
-  @Test
-  void ofType_toosmall() {
-    assertThrows(IllegalArgumentException.class, () -> SetResponse.ofType(-1));
-  }
-
-  @Test
-  void ofType_toobig() {
-    assertThrows(IllegalArgumentException.class, () -> SetResponse.ofType(7));
-  }
-
-  @Test
-  void addRRset() throws TextParseException, UnknownHostException {
+  private static final ARecord A_RECORD_1 =
+      new ARecord(
+          Name.fromConstantString("The.Name."),
+          DClass.IN,
+          0xABCD,
+          new byte[] {(byte) 192, (byte) 168, 0, 1});
+  private static final ARecord A_RECORD_2 =
+      new ARecord(
+          Name.fromConstantString("The.Name."),
+          DClass.IN,
+          0xABCD,
+          new byte[] {(byte) 192, (byte) 168, 0, 2});
+
+  @ParameterizedTest
+  @EnumSource(value = SetResponseType.class)
+  void ctor_1arg(SetResponseType type) {
+    SetResponse sr = SetResponse.ofType(type);
+    assertNull(sr.getNS());
+    assertEquals(type == SetResponseType.UNKNOWN, sr.isUnknown());
+    assertEquals(type == SetResponseType.NXDOMAIN, sr.isNXDOMAIN());
+    assertEquals(type == SetResponseType.NXRRSET, sr.isNXRRSET());
+    assertEquals(type == SetResponseType.DELEGATION, sr.isDelegation());
+    assertEquals(type == SetResponseType.CNAME, sr.isCNAME());
+    assertEquals(type == SetResponseType.DNAME, sr.isDNAME());
+    assertEquals(type == SetResponseType.SUCCESSFUL, sr.isSuccessful());
+  }
+
+  @ParameterizedTest
+  @EnumSource(
+      value = SetResponseType.class,
+      names = {
+        "DELEGATION",
+        "CNAME",
+        "DNAME",
+        "SUCCESSFUL",
+      })
+  void ofType_basic(SetResponseType type) {
+    RRset rs = new RRset();
+    SetResponse sr = SetResponse.ofType(type, rs);
+    assertSame(rs, sr.getNS());
+    assertEquals(type == SetResponseType.DELEGATION, sr.isDelegation());
+    assertEquals(type == SetResponseType.CNAME, sr.isCNAME());
+    assertEquals(type == SetResponseType.DNAME, sr.isDNAME());
+    assertEquals(type == SetResponseType.SUCCESSFUL, sr.isSuccessful());
+
+    SetResponse sr2 = SetResponse.ofType(type, rs);
+    assertNotSame(sr, sr2);
+  }
+
+  @ParameterizedTest
+  @EnumSource(
+      value = SetResponseType.class,
+      names = {
+        "UNKNOWN",
+        "NXDOMAIN",
+        "NXRRSET",
+      })
+  void ofType_singleton(SetResponseType type) {
+    SetResponse sr = SetResponse.ofType(type);
+    assertNull(sr.getNS());
+    assertEquals(type == SetResponseType.UNKNOWN, sr.isUnknown());
+    assertEquals(type == SetResponseType.NXDOMAIN, sr.isNXDOMAIN());
+    assertEquals(type == SetResponseType.NXRRSET, sr.isNXRRSET());
+    assertThrows(IllegalStateException.class, () -> sr.addRRset(new RRset()));
+
+    SetResponse sr2 = SetResponse.ofType(type);
+    assertSame(sr, sr2);
+  }
+
+  @Test
+  void addRRset() {
     RRset rrs = new RRset();
-    rrs.addRR(
-        new ARecord(
-            Name.fromString("The.Name."), DClass.IN, 0xABCD, InetAddress.getByName("192.168.0.1")));
-    rrs.addRR(
-        new ARecord(
-            Name.fromString("The.Name."), DClass.IN, 0xABCD, InetAddress.getByName("192.168.0.2")));
-    SetResponse sr = new SetResponse(SetResponse.SUCCESSFUL);
-    sr.addRRset(rrs);
+    rrs.addRR(A_RECORD_1);
+    rrs.addRR(A_RECORD_2);
+    SetResponse sr = SetResponse.ofType(SetResponseType.SUCCESSFUL, rrs);
 
     RRset[] exp = new RRset[] {rrs};
     assertArrayEquals(exp, sr.answers().toArray());
@@ -192,12 +132,8 @@ class SetResponseTest {
   @Test
   void addRRset_multiple() throws TextParseException, UnknownHostException {
     RRset rrs = new RRset();
-    rrs.addRR(
-        new ARecord(
-            Name.fromString("The.Name."), DClass.IN, 0xABCD, InetAddress.getByName("192.168.0.1")));
-    rrs.addRR(
-        new ARecord(
-            Name.fromString("The.Name."), DClass.IN, 0xABCD, InetAddress.getByName("192.168.0.2")));
+    rrs.addRR(A_RECORD_1);
+    rrs.addRR(A_RECORD_2);
 
     RRset rrs2 = new RRset();
     rrs2.addRR(
@@ -213,7 +149,7 @@ class SetResponseTest {
             0xABCE,
             InetAddress.getByName("192.168.1.2")));
 
-    SetResponse sr = new SetResponse(SetResponse.SUCCESSFUL);
+    SetResponse sr = SetResponse.ofType(SetResponseType.SUCCESSFUL);
     sr.addRRset(rrs);
     sr.addRRset(rrs2);
 
@@ -223,63 +159,39 @@ class SetResponseTest {
 
   @Test
   void answers_nonSUCCESSFUL() {
-    SetResponse sr = new SetResponse(SetResponse.UNKNOWN, new RRset());
+    SetResponse sr = SetResponse.ofType(SetResponseType.UNKNOWN, new RRset());
     assertNull(sr.answers());
   }
 
   @Test
   void getCNAME() throws TextParseException {
-    RRset rrs = new RRset();
     CNAMERecord cr =
         new CNAMERecord(
             Name.fromString("The.Name."), DClass.IN, 0xABCD, Name.fromString("The.Alias."));
-    rrs.addRR(cr);
-    SetResponse sr = new SetResponse(SetResponse.CNAME, rrs);
+    RRset rrs = new RRset(cr);
+    SetResponse sr = SetResponse.ofType(SetResponseType.CNAME, rrs);
     assertEquals(cr, sr.getCNAME());
   }
 
   @Test
   void getDNAME() throws TextParseException {
-    RRset rrs = new RRset();
     DNAMERecord dr =
         new DNAMERecord(
             Name.fromString("The.Name."), DClass.IN, 0xABCD, Name.fromString("The.Alias."));
-    rrs.addRR(dr);
-    SetResponse sr = new SetResponse(SetResponse.DNAME, rrs);
+    RRset rrs = new RRset(dr);
+    SetResponse sr = SetResponse.ofType(SetResponseType.DNAME, rrs);
     assertEquals(dr, sr.getDNAME());
   }
 
-  @Test
-  void test_toString() throws TextParseException, UnknownHostException {
-    final int[] types =
-        new int[] {
-          SetResponse.UNKNOWN,
-          SetResponse.NXDOMAIN,
-          SetResponse.NXRRSET,
-          SetResponse.DELEGATION,
-          SetResponse.CNAME,
-          SetResponse.DNAME,
-          SetResponse.SUCCESSFUL
-        };
-    RRset rrs = new RRset();
-    rrs.addRR(
-        new ARecord(
-            Name.fromString("The.Name."), DClass.IN, 0xABCD, InetAddress.getByName("192.168.0.1")));
-
-    final String[] labels =
-        new String[] {
-          "unknown",
-          "NXDOMAIN",
-          "NXRRSET",
-          "delegation: " + rrs,
-          "CNAME: " + rrs,
-          "DNAME: " + rrs,
-          "successful"
-        };
-
-    for (int i = 0; i < types.length; ++i) {
-      SetResponse sr = new SetResponse(types[i], rrs);
-      assertEquals(labels[i], sr.toString());
+  @ParameterizedTest
+  @EnumSource(SetResponseType.class)
+  void test_toString(SetResponseType type) {
+    RRset rrs = new RRset(A_RECORD_1);
+    SetResponse sr = SetResponse.ofType(type, rrs);
+    if (type.isPrintRecords()) {
+      assertEquals(type + ": " + rrs, sr.toString());
+    } else {
+      assertEquals(type.toString(), sr.toString());
     }
   }
 }
-- 
2.33.0

