diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/dns/CreateDnsRecordCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/CreateDnsRecordCmd.java index ab00c8d8c7e..ff350ce065d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/dns/CreateDnsRecordCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/CreateDnsRecordCmd.java @@ -50,10 +50,10 @@ public class CreateDnsRecordCmd extends BaseAsyncCmd { description = "ID of the DNS zone") private Long dnsZoneId; - @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "Record name") + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "DNS record name") private String name; - @Parameter(name = ApiConstants.TYPE, type = CommandType.STRING, required = true, description = "Record type (A, CNAME)") + @Parameter(name = ApiConstants.TYPE, type = CommandType.STRING, required = true, description = "DNS record type (e.g., A, AAAA, CNAME, MX, TXT, etc.)") private String type; @Parameter(name = ApiConstants.CONTENTS, type = CommandType.LIST, collectionType = CommandType.STRING, required = true, diff --git a/plugins/dns/powerdns/pom.xml b/plugins/dns/powerdns/pom.xml index ef915559f47..3edb74f1ce6 100644 --- a/plugins/dns/powerdns/pom.xml +++ b/plugins/dns/powerdns/pom.xml @@ -27,11 +27,4 @@ 4.23.0.0-SNAPSHOT ../../pom.xml - - - 11 - 11 - UTF-8 - - diff --git a/plugins/dns/powerdns/src/test/java/org/apache/cloudstack/dns/DnsProviderUtilTest.java b/plugins/dns/powerdns/src/test/java/org/apache/cloudstack/dns/DnsProviderUtilTest.java index 25d8f158372..a2b571b154f 100644 --- a/plugins/dns/powerdns/src/test/java/org/apache/cloudstack/dns/DnsProviderUtilTest.java +++ b/plugins/dns/powerdns/src/test/java/org/apache/cloudstack/dns/DnsProviderUtilTest.java @@ -18,7 +18,7 @@ package org.apache.cloudstack.dns; import static org.apache.cloudstack.dns.DnsProviderUtil.appendPublicSuffixToZone; -import static org.apache.cloudstack.dns.DnsProviderUtil.normalizeDomain; +import static org.apache.cloudstack.dns.DnsProviderUtil.normalizeDomainForDb; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -89,13 +89,13 @@ public class DnsProviderUtilTest { if (Strings.isNotBlank(publicSuffix)) { result = executeAppendSuffixTest(userZoneName, publicSuffix); } else { - result = appendPublicSuffixToZone(normalizeDomain(userZoneName), publicSuffix); + result = appendPublicSuffixToZone(normalizeDomainForDb(userZoneName), publicSuffix); } assertEquals(expectedResult, result); } } String executeAppendSuffixTest(String zoneName, String domainSuffix) { - return appendPublicSuffixToZone(normalizeDomain(zoneName), domainSuffix); + return appendPublicSuffixToZone(normalizeDomainForDb(zoneName), domainSuffix); } } diff --git a/plugins/dns/powerdns/src/test/java/org/apache/cloudstack/dns/NormalizeDnsRecordValueTest.java b/plugins/dns/powerdns/src/test/java/org/apache/cloudstack/dns/NormalizeDnsRecordValueTest.java new file mode 100644 index 00000000000..f5bd16a8190 --- /dev/null +++ b/plugins/dns/powerdns/src/test/java/org/apache/cloudstack/dns/NormalizeDnsRecordValueTest.java @@ -0,0 +1,197 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.dns; + +import static org.apache.cloudstack.dns.DnsRecord.RecordType.A; +import static org.apache.cloudstack.dns.DnsRecord.RecordType.AAAA; +import static org.apache.cloudstack.dns.DnsRecord.RecordType.CNAME; +import static org.apache.cloudstack.dns.DnsRecord.RecordType.MX; +import static org.apache.cloudstack.dns.DnsRecord.RecordType.NS; +import static org.apache.cloudstack.dns.DnsRecord.RecordType.PTR; +import static org.apache.cloudstack.dns.DnsRecord.RecordType.SRV; +import static org.apache.cloudstack.dns.DnsRecord.RecordType.TXT; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.util.Arrays; +import java.util.Collection; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class NormalizeDnsRecordValueTest { + + private final String description; + private final String input; + private final DnsRecord.RecordType recordType; + private final String expected; + private final boolean expectException; + + public NormalizeDnsRecordValueTest(String description, String input, + DnsRecord.RecordType recordType, + String expected, boolean expectException) { + this.description = description; + this.input = input; + this.recordType = recordType; + this.expected = expected; + this.expectException = expectException; + } + + @Parameterized.Parameters(name = "{0}") + public static Collection data() { + return Arrays.asList(new Object[][] { + + // ---------------------------------------------------------------- + // Guard: blank/null value — all record types should throw + // ---------------------------------------------------------------- + {"null value, A record", null, A, null, true}, + {"empty value, A record", "", A, null, true}, + {"blank value, A record", " ", A, null, true}, + + {"null value, AAAA record", null, AAAA, null, true}, + {"null value, CNAME record", null, CNAME, null, true}, + {"null value, MX record", null, MX, null, true}, + {"null value, TXT record", null, TXT, null, true}, + {"null value, SRV record", null, SRV, null, true}, + {"null value, NS record", null, NS, null, true}, + {"null value, PTR record", null, PTR, null, true}, + + // ---------------------------------------------------------------- + // A record + // ---------------------------------------------------------------- + {"A: valid IPv4", "93.184.216.34", A, "93.184.216.34", false}, + {"A: valid IPv4 with whitespace", " 93.184.216.34 ", A, "93.184.216.34", false}, + {"A: loopback", "127.0.0.1", A, "127.0.0.1", false}, + {"A: all-zeros", "0.0.0.0", A, "0.0.0.0", false}, + {"A: broadcast", "255.255.255.255", A, "255.255.255.255", false}, + {"A: private 10.x", "10.0.0.1", A, "10.0.0.1", false}, + {"A: private 192.168.x", "192.168.1.1", A, "192.168.1.1", false}, + + {"A: IPv6 rejected", "2001:db8::1", A, null, true}, + {"A: domain rejected", "example.com", A, null, true}, + {"A: partial IP rejected", "192.168.1", A, null, true}, + {"A: trailing dot rejected", "93.184.216.34.", A, null, true}, + {"A: octet out of range rejected", "256.0.0.1", A, null, true}, + + // ---------------------------------------------------------------- + // AAAA record + // ---------------------------------------------------------------- + {"AAAA: full IPv6", "2001:0db8:0000:0000:0000:0000:0000:0001", AAAA, + "2001:0db8:0000:0000:0000:0000:0000:0001", false}, + + {"AAAA: compressed IPv6", "2001:db8::1", AAAA, "2001:db8::1", false}, + {"AAAA: loopback", "::1", AAAA, "::1", false}, + {"AAAA: all zeros", "::", AAAA, "::", false}, + {"AAAA: whitespace", " 2001:db8::1 ", AAAA, "2001:db8::1", false}, + + {"AAAA: IPv4 rejected", "93.184.216.34", AAAA, null, true}, + {"AAAA: domain rejected", "example.com", AAAA, null, true}, + {"AAAA: invalid hex rejected", "2001:db8::xyz", AAAA, null, true}, + + // ---------------------------------------------------------------- + // CNAME record + // ---------------------------------------------------------------- + {"CNAME: basic", "target.example.com", CNAME, "target.example.com.", false}, + {"CNAME: uppercase", "TARGET.EXAMPLE.COM", CNAME, "target.example.com.", false}, + {"CNAME: trailing dot", "target.example.com.", CNAME, "target.example.com.", false}, + {"CNAME: whitespace", " target.example.com ", CNAME, "target.example.com.", false}, + {"CNAME: subdomain", "sub.target.example.com", CNAME, "sub.target.example.com.", false}, + + {"CNAME: IP rejected", "192.168.1.1", CNAME, null, true}, + {"CNAME: invalid label", "-bad.example.com", CNAME, null, true}, + + // ---------------------------------------------------------------- + // NS record + // ---------------------------------------------------------------- + {"NS: basic", "ns1.example.com", NS, "ns1.example.com.", false}, + {"NS: uppercase", "NS1.EXAMPLE.COM", NS, "ns1.example.com.", false}, + {"NS: trailing dot", "ns1.example.com.", NS, "ns1.example.com.", false}, + {"NS: subdomain", "ns1.sub.example.com", NS, "ns1.sub.example.com.", false}, + + {"NS: IP rejected", "8.8.8.8", NS, null, true}, + {"NS: invalid label", "ns1-.example.com", NS, null, true}, + + // ---------------------------------------------------------------- + // PTR record + // ---------------------------------------------------------------- + {"PTR: basic", "host.example.com", PTR, "host.example.com.", false}, + {"PTR: in-addr.arpa", "1.168.192.in-addr.arpa", PTR, "1.168.192.in-addr.arpa.", false}, + {"PTR: uppercase", "HOST.EXAMPLE.COM", PTR, "host.example.com.", false}, + {"PTR: trailing dot", "host.example.com.", PTR, "host.example.com.", false}, + + {"PTR: IP rejected", "192.168.1.1", PTR, null, true}, + {"PTR: invalid label", "-host.example.com", PTR, null, true}, + + // ---------------------------------------------------------------- + // MX record + // ---------------------------------------------------------------- + {"MX: standard", "10 mail.example.com", MX, "10 mail.example.com.", false}, + {"MX: zero priority", "0 mail.example.com", MX, "0 mail.example.com.", false}, + {"MX: max priority", "65535 mail.example.com", MX, "65535 mail.example.com.", false}, + {"MX: uppercase", "10 MAIL.EXAMPLE.COM", MX, "10 mail.example.com.", false}, + {"MX: trailing dot", "10 mail.example.com.", MX, "10 mail.example.com.", false}, + {"MX: extra whitespace", "10 mail.example.com", MX, "10 mail.example.com.", false}, + + {"MX: missing domain", "10", MX, null, true}, + {"MX: priority out of range", "65536 mail.example.com", MX, null, true}, + {"MX: non-numeric priority", "abc mail.example.com", MX, null, true}, + {"MX: IP rejected", "10 192.168.1.1", MX, null, true}, + + // ---------------------------------------------------------------- + // SRV record + // ---------------------------------------------------------------- + {"SRV: standard", "10 20 443 target.example.com", SRV, "10 20 443 target.example.com.", false}, + {"SRV: zeros", "0 0 1 target.example.com", SRV, "0 0 1 target.example.com.", false}, + {"SRV: max values", "65535 65535 65535 target.example.com", SRV, "65535 65535 65535 target.example.com.", false}, + {"SRV: uppercase", "10 20 443 TARGET.EXAMPLE.COM", SRV, "10 20 443 target.example.com.", false}, + {"SRV: trailing dot", "10 20 443 target.example.com.", SRV, "10 20 443 target.example.com.", false}, + + {"SRV: missing target", "10 20 443", SRV, null, true}, + {"SRV: port 0", "10 20 0 target.example.com", SRV, null, true}, + {"SRV: priority out of range", "65536 20 443 target.example.com", SRV, null, true}, + {"SRV: IP rejected", "10 20 443 192.168.1.1", SRV, null, true}, + + // ---------------------------------------------------------------- + // TXT record + // ---------------------------------------------------------------- + {"TXT: trim", " hello world ", TXT, "hello world", false}, + {"TXT: already clean", "v=spf1 include:example.com ~all", TXT, "v=spf1 include:example.com ~all", false}, + {"TXT: special chars", "v=DKIM1; k=rsa; p=MIGf", TXT, "v=DKIM1; k=rsa; p=MIGf", false}, + {"TXT: unicode", "héllo wörld", TXT, "héllo wörld", false}, + {"TXT: multiple spaces", "key=value with spaces", TXT, "key=value with spaces", false}, + {"TXT: quoted", "\"quoted value\"", TXT, "\"quoted value\"", false}, + {"TXT: blank", " ", TXT, null, true}, + {"TXT: newline", "\n", TXT, null, true}, + }); + } + + @Test + public void testNormalizeDnsRecordValue() { + if (expectException) { + try { + DnsProviderUtil.normalizeDnsRecordValue(input, recordType); + fail("Expected IllegalArgumentException for [" + description + "] input='" + input + "'"); + } catch (IllegalArgumentException ignored) {} + } else { + String result = DnsProviderUtil.normalizeDnsRecordValue(input, recordType); + assertEquals(description, expected, result); + } + } +} diff --git a/server/src/main/java/org/apache/cloudstack/dns/DnsProviderManagerImpl.java b/server/src/main/java/org/apache/cloudstack/dns/DnsProviderManagerImpl.java index b0071a17d6f..5b7b6dd1b0f 100644 --- a/server/src/main/java/org/apache/cloudstack/dns/DnsProviderManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/dns/DnsProviderManagerImpl.java @@ -172,7 +172,7 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa } if (StringUtils.isNotBlank(publicDomainSuffix)) { - publicDomainSuffix = DnsProviderUtil.normalizeDomain(publicDomainSuffix); + publicDomainSuffix = DnsProviderUtil.normalizeDomainForDb(publicDomainSuffix); } DnsProviderType type = cmd.getProvider(); @@ -263,7 +263,7 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa } if (cmd.getPublicDomainSuffix() != null) { - dnsServer.setPublicDomainSuffix(DnsProviderUtil.normalizeDomain(cmd.getPublicDomainSuffix())); + dnsServer.setPublicDomainSuffix(DnsProviderUtil.normalizeDomainForDb(cmd.getPublicDomainSuffix())); } if (cmd.getNameServers() != null) { @@ -552,7 +552,7 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa throw new InvalidParameterValueException("DNS zone name cannot be empty"); } - String dnsZoneName = DnsProviderUtil.normalizeDomain(cmd.getName()); + String dnsZoneName = DnsProviderUtil.normalizeDomainForDb(cmd.getName()); DnsServerVO server = dnsServerDao.findById(cmd.getDnsServerId()); if (server == null) { throw new InvalidParameterValueException(String.format("DNS server not found for the given ID: %s", cmd.getDnsServerId())); diff --git a/server/src/main/java/org/apache/cloudstack/dns/DnsProviderUtil.java b/server/src/main/java/org/apache/cloudstack/dns/DnsProviderUtil.java index 95f04fe0940..9a2f980a740 100644 --- a/server/src/main/java/org/apache/cloudstack/dns/DnsProviderUtil.java +++ b/server/src/main/java/org/apache/cloudstack/dns/DnsProviderUtil.java @@ -17,9 +17,13 @@ package org.apache.cloudstack.dns; +import java.net.Inet4Address; +import java.net.Inet6Address; + import org.apache.commons.validator.routines.DomainValidator; import com.cloud.utils.StringUtils; +import com.google.common.net.InetAddresses; public class DnsProviderUtil { static DomainValidator validator = DomainValidator.getInstance(true); @@ -28,9 +32,9 @@ public class DnsProviderUtil { if (StringUtils.isBlank(suffixDomain)) { return zoneName; } - suffixDomain = DnsProviderUtil.normalizeDomain(suffixDomain); + suffixDomain = DnsProviderUtil.normalizeDomainForDb(suffixDomain); // Already suffixed → return as-is - if (zoneName.toLowerCase().endsWith("." + suffixDomain.toLowerCase())) { + if (zoneName.toLowerCase().endsWith("." + suffixDomain)) { return zoneName; } @@ -55,7 +59,8 @@ public class DnsProviderUtil { return labels[labels.length - 1]; } - public static String normalizeDomain(String domain) { + // lowercase, no trailing dot (used for DB storage, comparisons) + public static String normalizeDomainForDb(String domain) { if (StringUtils.isBlank(domain)) { throw new IllegalArgumentException("Domain cannot be empty"); } @@ -71,32 +76,144 @@ public class DnsProviderUtil { return normalized; } + // DNS wire form: lowercase, validated, WITH trailing dot (used in record values) public static String normalizeDnsRecordValue(String value, DnsRecord.RecordType recordType) { if (StringUtils.isBlank(value)) { throw new IllegalArgumentException("DNS record value cannot be empty"); } + String trimmedValue = value.trim(); switch (recordType) { case A: + if (!(InetAddresses.forString(trimmedValue) instanceof Inet4Address)) { + throw new IllegalArgumentException( + String.format("Invalid IP address for %s record: %s", recordType, value)); + } + return trimmedValue; case AAAA: - // IP addresses: trim only - return value.trim(); - + if (!(InetAddresses.forString(trimmedValue) instanceof Inet6Address)) { + throw new IllegalArgumentException( + String.format("Invalid IP address for %s record: %s", recordType, value)); + } + return trimmedValue; case CNAME: case NS: case PTR: + return normalizeDomainForDnsRecord(trimmedValue); case SRV: - // Domain names: normalize like zones - return normalizeDomain(value); + return normalizeSrvRecord(trimmedValue); case MX: - // PowerDNS MX: contains priority + domain, only trim and lowercase - return value.trim().toLowerCase(); - + return normalizeMxRecord(trimmedValue); case TXT: // Free text: preserve exactly - return value; - + return trimmedValue; default: throw new IllegalArgumentException("Unsupported DNS record type: " + recordType); } } + + static String normalizeDomainForDnsRecord(String domain) { + if (StringUtils.isBlank(domain)) { + throw new IllegalArgumentException("Domain name cannot be empty"); + } + String normalized = domain.trim().toLowerCase(); + // Strip trailing dot first (normalize input) + if (normalized.endsWith(".")) { + normalized = normalized.substring(0, normalized.length() - 1); + } + + // Reject IP addresses + if (InetAddresses.isInetAddress(normalized)) { + throw new IllegalArgumentException("Domain cannot be an IP address: " + normalized); + } + + // Validate total length (max 253 chars, excluding trailing dot) + if (normalized.length() > 253) { + throw new IllegalArgumentException( + "Domain name exceeds maximum length: " + normalized); + } + + // Validate labels + String[] labels = normalized.split("\\.", -1); + for (String label : labels) { + if (label.isEmpty()) { + throw new IllegalArgumentException( + "Domain contains empty label: " + normalized); + } + if (label.length() > 63) { + throw new IllegalArgumentException( + "Domain label too long: " + label); + } + if (!label.matches("[a-z0-9]([a-z0-9-]*[a-z0-9])?")) { + throw new IllegalArgumentException( + "Invalid domain label: " + label); + } + } + return normalized + "."; + } + + private static String normalizeSrvRecord(String value) { + String trimmed = value.trim(); + String[] parts = trimmed.split("\\s+", 4); + if (parts.length != 4) { + throw new IllegalArgumentException( + "Invalid SRV record value (expected ' '): " + trimmed); + } + + int priority; + int weight; + int port; + + try { + priority = Integer.parseInt(parts[0]); + weight = Integer.parseInt(parts[1]); + port = Integer.parseInt(parts[2]); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "SRV priority/weight/port must be numeric: " + trimmed); + } + + if (priority < 0 || priority > 65535) { + throw new IllegalArgumentException("SRV priority out of range (0–65535): " + parts[0]); + } + + if (weight < 0 || weight > 65535) { + throw new IllegalArgumentException("SRV weight out of range (0–65535): " + parts[1]); + } + + if (port < 1 || port > 65535) { + throw new IllegalArgumentException("SRV port out of range (1–65535): " + parts[2]); + } + + String target = normalizeDomainForDnsRecord(parts[3]); + + return priority + " " + weight + " " + port + " " + target; + } + + private static String normalizeMxRecord(String value) { + String trimmed = value.trim(); + String[] parts = trimmed.split("\\s+", 2); + + if (parts.length != 2) { + throw new IllegalArgumentException( + "Invalid MX record value (expected ' '): " + trimmed); + } + + int priority; + + try { + priority = Integer.parseInt(parts[0]); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "MX priority must be numeric: " + parts[0]); + } + + if (priority < 0 || priority > 65535) { + throw new IllegalArgumentException( + "MX priority out of range (0–65535): " + parts[0]); + } + + String mailExchanger = normalizeDomainForDnsRecord(parts[1]); + + return priority + " " + mailExchanger; + } } diff --git a/server/src/test/java/org/apache/cloudstack/dns/DnsProviderUtilTest.java b/server/src/test/java/org/apache/cloudstack/dns/DnsProviderUtilTest.java deleted file mode 100644 index 8a622f24899..00000000000 --- a/server/src/test/java/org/apache/cloudstack/dns/DnsProviderUtilTest.java +++ /dev/null @@ -1,91 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -package org.apache.cloudstack.dns; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; - -public class DnsProviderUtilTest { - - @Test - public void testNormalizeDnsRecordValueA() { - String result = DnsProviderUtil.normalizeDnsRecordValue(" 1.2.3.4 ", DnsRecord.RecordType.A); - assertEquals("1.2.3.4", result); - } - - @Test - public void testNormalizeDnsRecordValueAAAA() { - String result = DnsProviderUtil.normalizeDnsRecordValue(" 2001:db8::1 ", DnsRecord.RecordType.AAAA); - assertEquals("2001:db8::1", result); - } - - @Test - public void testNormalizeDnsRecordValueCNAME() { - // Appends dot in the process? No, normalizeDomain trims, lowercases, removes trailing dot, and checks validity. - String result = DnsProviderUtil.normalizeDnsRecordValue(" Host.Example.Com. ", DnsRecord.RecordType.CNAME); - assertEquals("host.example.com", result); - } - - @Test - public void testNormalizeDnsRecordValueNS() { - String result = DnsProviderUtil.normalizeDnsRecordValue("NS1.EXAMPLE.COM", DnsRecord.RecordType.NS); - assertEquals("ns1.example.com", result); - } - - @Test - public void testNormalizeDnsRecordValuePTR() { - String result = DnsProviderUtil.normalizeDnsRecordValue("ptr.valid.zone.", DnsRecord.RecordType.PTR); - assertEquals("ptr.valid.zone", result); - } - - @Test - public void testNormalizeDnsRecordValueSRV() { - String result = DnsProviderUtil.normalizeDnsRecordValue("srv.example.com", DnsRecord.RecordType.SRV); - assertEquals("srv.example.com", result); - } - - @Test - public void testNormalizeDnsRecordValueMX() { - // MX records just get trimmed and lowercased - String result = DnsProviderUtil.normalizeDnsRecordValue(" 10 MAIL.EXAMPLE.COM ", DnsRecord.RecordType.MX); - assertEquals("10 mail.example.com", result); - } - - @Test - public void testNormalizeDnsRecordValueTXT() { - // TXT records are preserved exactly - String result = DnsProviderUtil.normalizeDnsRecordValue(" Exact text value. ", DnsRecord.RecordType.TXT); - assertEquals(" Exact text value. ", result); - } - - @Test(expected = IllegalArgumentException.class) - public void testNormalizeDnsRecordValueEmpty() { - DnsProviderUtil.normalizeDnsRecordValue(" ", DnsRecord.RecordType.A); - } - - @Test(expected = IllegalArgumentException.class) - public void testNormalizeDnsRecordValueNull() { - DnsProviderUtil.normalizeDnsRecordValue(null, DnsRecord.RecordType.A); - } - - @Test(expected = IllegalArgumentException.class) - public void testNormalizeDnsRecordValueInvalidDomain() { - DnsProviderUtil.normalizeDnsRecordValue("invalid!domain", DnsRecord.RecordType.CNAME); - } -}