Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions DnsClientX.Tests/QueryDnsByEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class QueryDnsByEndpoint {
#endif
[Theory]
public async Task ShouldWorkForTXT(DnsEndpoint endpoint) {
if (TestSkipHelpers.ShouldSkipEndpoint(endpoint)) return;
var response = await ClientX.QueryDns("github.com", DnsRecordType.TXT, endpoint);
foreach (DnsAnswer answer in response.Answers) {
Assert.True(answer.Name == "github.com");
Expand Down Expand Up @@ -48,6 +49,7 @@ public async Task ShouldWorkForTXT(DnsEndpoint endpoint) {
[InlineData(DnsEndpoint.GoogleQuic)]
#endif
public async Task ShouldWorkForA(DnsEndpoint endpoint) {
if (TestSkipHelpers.ShouldSkipEndpoint(endpoint)) return;
var response = await ClientX.QueryDns("evotec.pl", DnsRecordType.A, endpoint);
foreach (DnsAnswer answer in response.Answers) {
Assert.True(answer.Name == "evotec.pl");
Expand Down Expand Up @@ -78,6 +80,7 @@ public async Task ShouldWorkForA(DnsEndpoint endpoint) {
[InlineData(DnsEndpoint.GoogleQuic)]
#endif
public async Task ShouldWorkForPTR(DnsEndpoint endpoint) {
if (TestSkipHelpers.ShouldSkipEndpoint(endpoint)) return;
var response = await ClientX.QueryDns("1.1.1.1", DnsRecordType.PTR, endpoint);
foreach (DnsAnswer answer in response.Answers) {
Assert.True(answer.Data == "one.one.one.one");
Expand Down
7 changes: 5 additions & 2 deletions DnsClientX.Tests/RegexCultureInvariantTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading;
Expand Down Expand Up @@ -40,7 +41,8 @@ public void ConvertData_TlsaRecord_ConsistentAcrossCultures(string culture) {
[InlineData("tr-TR")]
public void FilterAnswersRegex_ConsistentAcrossCultures(string culture) {
var client = new ClientX();
MethodInfo method = typeof(ClientX).GetMethod("FilterAnswersRegex", BindingFlags.NonPublic | BindingFlags.Instance)!;
MethodInfo method = typeof(ClientX).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
.Single(m => m.Name == "FilterAnswersRegex" && m.GetParameters().Length == 3);
var answers = new[] {
new DnsAnswer {
Name = "example.com",
Expand Down Expand Up @@ -69,7 +71,8 @@ public void FilterAnswersRegex_ConsistentAcrossCultures(string culture) {
[InlineData("tr-TR")]
public void FilterAnswers_ConsistentAcrossCultures(string culture) {
var client = new ClientX();
MethodInfo method = typeof(ClientX).GetMethod("FilterAnswers", BindingFlags.NonPublic | BindingFlags.Instance)!;
MethodInfo method = typeof(ClientX).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
.Single(m => m.Name == "FilterAnswers" && m.GetParameters().Length == 3);
var answers = new[] {
new DnsAnswer {
Name = "example.com",
Expand Down
118 changes: 118 additions & 0 deletions DnsClientX.Tests/ResolveFilterAliasTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
using System.Threading.Tasks;
using Xunit;

namespace DnsClientX.Tests {
/// <summary>
/// Tests alias handling for ResolveFilter APIs.
/// </summary>
public class ResolveFilterAliasTests {
private static DnsResponse CreateResponse(string name) {
return new DnsResponse {
Answers = new[] {
new DnsAnswer { Name = name, Type = DnsRecordType.CNAME, TTL = 60, DataRaw = "alias.example.com" },
new DnsAnswer { Name = name, Type = DnsRecordType.TXT, TTL = 120, DataRaw = "v=spf1 include:example.com -all\nother=record" }
}
};
}

/// <summary>
/// Ensures alias answers are kept alongside matching TXT lines when enabled.
/// </summary>
[Fact]
public async Task ResolveFilter_IncludeAliases_KeepsCnameAndMatchingTxt() {
using var client = new ClientX(DnsEndpoint.Cloudflare);
client.ResolverOverride = (name, type, ct) => Task.FromResult(CreateResponse(name));

var options = new ResolveFilterOptions(true);
var response = await client.ResolveFilter("example.com", DnsRecordType.TXT, "v=spf1", options, retryOnTransient: false);

Assert.NotNull(response.Answers);
Assert.Equal(2, response.Answers.Length);
Assert.Contains(response.Answers, answer => answer.Type == DnsRecordType.CNAME);
Assert.Contains(response.Answers, answer => answer.Type == DnsRecordType.TXT && answer.Data == "v=spf1 include:example.com -all");
}

/// <summary>
/// Ensures alias answers are not kept when alias inclusion is disabled.
/// </summary>
[Fact]
public async Task ResolveFilter_ExcludeAliases_DropsCname() {
using var client = new ClientX(DnsEndpoint.Cloudflare);
client.ResolverOverride = (name, type, ct) => Task.FromResult(CreateResponse(name));

var options = new ResolveFilterOptions(false);
var response = await client.ResolveFilter("example.com", DnsRecordType.TXT, "v=spf1", options, retryOnTransient: false);

Assert.Single(response.Answers);
Assert.Equal(DnsRecordType.TXT, response.Answers[0].Type);
}

/// <summary>
/// Ensures array-based ResolveFilter returns responses when only aliases match.
/// </summary>
[Fact]
public async Task ResolveFilter_Array_IncludeAliases_ReturnsResponseWithOnlyCname() {
using var client = new ClientX(DnsEndpoint.Cloudflare);
client.ResolverOverride = (name, type, ct) => Task.FromResult(CreateResponse(name));

var options = new ResolveFilterOptions(true);
var responses = await client.ResolveFilter(new[] { "example.com" }, DnsRecordType.TXT, "nomatch", options, retryOnTransient: false);

Assert.Single(responses);
Assert.Single(responses[0].Answers);
Assert.Equal(DnsRecordType.CNAME, responses[0].Answers[0].Type);
}

/// <summary>
/// Ensures empty filters still keep alias and requested type answers when enabled.
/// </summary>
[Fact]
public async Task ResolveFilter_IncludeAliases_EmptyFilter_ReturnsAliasAndType() {
using var client = new ClientX(DnsEndpoint.Cloudflare);
client.ResolverOverride = (name, type, ct) => Task.FromResult(CreateResponse(name));

var options = new ResolveFilterOptions(true);
var response = await client.ResolveFilter("example.com", DnsRecordType.TXT, string.Empty, options, retryOnTransient: false);

Assert.NotNull(response.Answers);
Assert.Equal(2, response.Answers.Length);
Assert.Contains(response.Answers, answer => answer.Type == DnsRecordType.CNAME);
Assert.Contains(response.Answers, answer => answer.Type == DnsRecordType.TXT);
}

/// <summary>
/// Ensures null filters are treated as empty when alias inclusion is enabled.
/// </summary>
[Fact]
public async Task ResolveFilter_IncludeAliases_NullFilter_TreatedAsEmpty() {
using var client = new ClientX(DnsEndpoint.Cloudflare);
client.ResolverOverride = (name, type, ct) => Task.FromResult(CreateResponse(name));

var options = new ResolveFilterOptions(true);
string? filter = null;
#pragma warning disable CS8604 // Null is allowed for robustness testing.
var response = await client.ResolveFilter("example.com", DnsRecordType.TXT, filter, options, retryOnTransient: false);
#pragma warning restore CS8604

Assert.NotNull(response.Answers);
Assert.Equal(2, response.Answers.Length);
Assert.Contains(response.Answers, answer => answer.Type == DnsRecordType.CNAME);
Assert.Contains(response.Answers, answer => answer.Type == DnsRecordType.TXT);
}

/// <summary>
/// Ensures alias filtering still respects the filter when querying alias types.
/// </summary>
[Fact]
public async Task ResolveFilter_IncludeAliases_AliasType_RespectsFilter() {
using var client = new ClientX(DnsEndpoint.Cloudflare);
client.ResolverOverride = (name, type, ct) => Task.FromResult(CreateResponse(name));

var options = new ResolveFilterOptions(true);
var response = await client.ResolveFilter("example.com", DnsRecordType.CNAME, "nomatch", options, retryOnTransient: false);

Assert.NotNull(response.Answers);
Assert.Empty(response.Answers);
}
}
}
7 changes: 5 additions & 2 deletions DnsClientX.Tests/ResolveFilterLineTests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using Xunit;
Expand All @@ -15,7 +16,8 @@ public class ResolveFilterLineTests {
[Fact]
public void FilterAnswers_ReturnsMatchingLine() {
var client = new ClientX();
var method = typeof(ClientX).GetMethod("FilterAnswers", BindingFlags.NonPublic | BindingFlags.Instance)!;
var method = typeof(ClientX).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
.Single(m => m.Name == "FilterAnswers" && m.GetParameters().Length == 3);
var answers = new[] { CreateTxt("line1\nline2") };
var result = (DnsAnswer[])method.Invoke(client, new object[] { answers, "line2", DnsRecordType.TXT })!;
Assert.Single(result);
Expand All @@ -28,7 +30,8 @@ public void FilterAnswers_ReturnsMatchingLine() {
[Fact]
public void FilterAnswersRegex_ReturnsMatchingLine() {
var client = new ClientX();
var method = typeof(ClientX).GetMethod("FilterAnswersRegex", BindingFlags.NonPublic | BindingFlags.Instance)!;
var method = typeof(ClientX).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
.Single(m => m.Name == "FilterAnswersRegex" && m.GetParameters().Length == 3);
var answers = new[] { CreateTxt("line1\nline2") };
var result = (DnsAnswer[])method.Invoke(client, new object[] { answers, new Regex("line2$"), DnsRecordType.TXT })!;
Assert.Single(result);
Expand Down
13 changes: 9 additions & 4 deletions DnsClientX.Tests/ResolveFilterNullDataTests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using Xunit;
Expand All @@ -22,7 +23,8 @@ private static DnsAnswer CreateAnswer(string dataRaw, DnsRecordType type) {
[Fact]
public void FilterAnswers_ShouldIgnoreEmptyData() {
var client = new ClientX();
MethodInfo method = typeof(ClientX).GetMethod("FilterAnswers", BindingFlags.NonPublic | BindingFlags.Instance)!;
MethodInfo method = typeof(ClientX).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
.Single(m => m.Name == "FilterAnswers" && m.GetParameters().Length == 3);
var answers = new[] { CreateAnswer(string.Empty, DnsRecordType.A) };
var result = (DnsAnswer[])method.Invoke(client, new object[] { answers, "test", DnsRecordType.A })!;
Assert.Empty(result);
Expand All @@ -34,7 +36,8 @@ public void FilterAnswers_ShouldIgnoreEmptyData() {
[Fact]
public void FilterAnswersRegex_ShouldIgnoreEmptyData() {
var client = new ClientX();
MethodInfo method = typeof(ClientX).GetMethod("FilterAnswersRegex", BindingFlags.NonPublic | BindingFlags.Instance)!;
MethodInfo method = typeof(ClientX).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
.Single(m => m.Name == "FilterAnswersRegex" && m.GetParameters().Length == 3);
var answers = new[] { CreateAnswer(string.Empty, DnsRecordType.A) };
var result = (DnsAnswer[])method.Invoke(client, new object[] { answers, new Regex("test", RegexOptions.CultureInvariant), DnsRecordType.A })!;
Assert.Empty(result);
Expand All @@ -46,7 +49,8 @@ public void FilterAnswersRegex_ShouldIgnoreEmptyData() {
[Fact]
public void HasMatchingAnswers_ShouldReturnFalseForEmptyData() {
var client = new ClientX();
MethodInfo method = typeof(ClientX).GetMethod("HasMatchingAnswers", BindingFlags.NonPublic | BindingFlags.Instance)!;
MethodInfo method = typeof(ClientX).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
.Single(m => m.Name == "HasMatchingAnswers" && m.GetParameters().Length == 3);
var answers = new[] { CreateAnswer(string.Empty, DnsRecordType.A) };
var result = (bool)method.Invoke(client, new object[] { answers, "test", DnsRecordType.A })!;
Assert.False(result);
Expand All @@ -58,7 +62,8 @@ public void HasMatchingAnswers_ShouldReturnFalseForEmptyData() {
[Fact]
public void HasMatchingAnswersRegex_ShouldReturnFalseForEmptyData() {
var client = new ClientX();
MethodInfo method = typeof(ClientX).GetMethod("HasMatchingAnswersRegex", BindingFlags.NonPublic | BindingFlags.Instance)!;
MethodInfo method = typeof(ClientX).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
.Single(m => m.Name == "HasMatchingAnswersRegex" && m.GetParameters().Length == 3);
var answers = new[] { CreateAnswer(string.Empty, DnsRecordType.A) };
var result = (bool)method.Invoke(client, new object[] { answers, new Regex("test", RegexOptions.CultureInvariant), DnsRecordType.A })!;
Assert.False(result);
Expand Down
4 changes: 4 additions & 0 deletions DnsClientX.Tests/ResolveFirst.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public class ResolveFirst {
[InlineData(DnsEndpoint.OpenDNS)]
[InlineData(DnsEndpoint.OpenDNSFamily)]
public async Task ShouldWorkForTXT(DnsEndpoint endpoint) {
if (TestSkipHelpers.ShouldSkipEndpoint(endpoint)) return;
using var Client = new ClientX(endpoint);
var answer = await Client.ResolveFirst("github.com", DnsRecordType.TXT, cancellationToken: CancellationToken.None);
Assert.True(answer != null);
Expand Down Expand Up @@ -53,6 +54,7 @@ public async Task ShouldWorkForTXT(DnsEndpoint endpoint) {
[InlineData(DnsEndpoint.OpenDNS)]
[InlineData(DnsEndpoint.OpenDNSFamily)]
public async Task ShouldWorkForA(DnsEndpoint endpoint) {
if (TestSkipHelpers.ShouldSkipEndpoint(endpoint)) return;
using var Client = new ClientX(endpoint);
var answer = await Client.ResolveFirst("evotec.pl", DnsRecordType.A, cancellationToken: CancellationToken.None);
Assert.True(answer != null);
Expand All @@ -78,6 +80,7 @@ public async Task ShouldWorkForA(DnsEndpoint endpoint) {
[InlineData(DnsEndpoint.OpenDNS)]
[InlineData(DnsEndpoint.OpenDNSFamily)]
public void ShouldWorkForTXT_Sync(DnsEndpoint endpoint) {
if (TestSkipHelpers.ShouldSkipEndpoint(endpoint)) return;
using var Client = new ClientX(endpoint);
var answer = Client.ResolveFirstSync("github.com", DnsRecordType.TXT, cancellationToken: CancellationToken.None);
Assert.True(answer != null);
Expand All @@ -104,6 +107,7 @@ public void ShouldWorkForTXT_Sync(DnsEndpoint endpoint) {
[InlineData(DnsEndpoint.OpenDNS)]
[InlineData(DnsEndpoint.OpenDNSFamily)]
public void ShouldWorkForA_Sync(DnsEndpoint endpoint) {
if (TestSkipHelpers.ShouldSkipEndpoint(endpoint)) return;
using var Client = new ClientX(endpoint);
var answer = Client.ResolveFirstSync("evotec.pl", DnsRecordType.A, cancellationToken: CancellationToken.None);
Assert.True(answer != null);
Expand Down
14 changes: 13 additions & 1 deletion DnsClientX.Tests/ResolveSync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ private void LogDiagnostics(string message)
_output.WriteLine($"[Diagnostic] {message}");
}

private bool ShouldSkipEndpoint(DnsEndpoint endpoint)
{
return TestSkipHelpers.ShouldSkipEndpoint(endpoint, _output);
}

private async Task<DnsResponse> TryResolveWithDiagnostics(ClientX client, string domain, DnsRecordType recordType, int maxRetries = 3)
{
LogDiagnostics($"Attempting to resolve {domain} for record type {recordType}");
Expand Down Expand Up @@ -98,6 +103,7 @@ private async Task<DnsResponse> TryResolveWithDiagnostics(ClientX client, string
[InlineData(DnsEndpoint.OpenDNSFamily)]
public async Task ShouldWorkForTXTSync(DnsEndpoint endpoint)
{
if (ShouldSkipEndpoint(endpoint)) return;
using var client = new ClientX(endpoint);
var response = await TryResolveWithDiagnostics(client, "github.com", DnsRecordType.TXT);

Expand Down Expand Up @@ -127,8 +133,9 @@ public async Task ShouldWorkForTXTSync(DnsEndpoint endpoint)
[InlineData(DnsEndpoint.GoogleWireFormatPost)]
[InlineData(DnsEndpoint.OpenDNS)]
[InlineData(DnsEndpoint.OpenDNSFamily)]
public async Task ShouldWorkForFirstSyncTXT(DnsEndpoint endpoint)
public async Task ShouldWorkForFirstSyncTXT(DnsEndpoint endpoint)
{
if (ShouldSkipEndpoint(endpoint)) return;
using var client = new ClientX(endpoint);
var response = await TryResolveWithDiagnostics(client, "github.com", DnsRecordType.TXT);

Expand Down Expand Up @@ -158,6 +165,7 @@ public async Task ShouldWorkForFirstSyncTXT(DnsEndpoint endpoint)
[InlineData(DnsEndpoint.OpenDNSFamily)]
public async Task ShouldWorkForAllSyncTXT(DnsEndpoint endpoint)
{
if (ShouldSkipEndpoint(endpoint)) return;
using var client = new ClientX(endpoint);
var response = await TryResolveWithDiagnostics(client, "github.com", DnsRecordType.TXT);

Expand Down Expand Up @@ -189,6 +197,7 @@ public async Task ShouldWorkForAllSyncTXT(DnsEndpoint endpoint)
[InlineData(DnsEndpoint.OpenDNSFamily)]
public async Task ShouldWorkForASync(DnsEndpoint endpoint)
{
if (ShouldSkipEndpoint(endpoint)) return;
using var client = new ClientX(endpoint);
var response = await TryResolveWithDiagnostics(client, "evotec.pl", DnsRecordType.A);

Expand Down Expand Up @@ -219,6 +228,7 @@ public async Task ShouldWorkForASync(DnsEndpoint endpoint)
[InlineData(DnsEndpoint.OpenDNS)]
[InlineData(DnsEndpoint.OpenDNSFamily)]
public void ShouldWorkForPTRSync(DnsEndpoint endpoint) {
if (ShouldSkipEndpoint(endpoint)) return;
using var client = new ClientX(endpoint);
var response = client.ResolveSync("1.1.1.1", DnsRecordType.PTR);
foreach (DnsAnswer answer in response.Answers) {
Expand Down Expand Up @@ -285,6 +295,7 @@ public void ShouldWorkForMultipleTypesSync(DnsEndpoint endpoint) {
[InlineData(DnsEndpoint.OpenDNS)]
[InlineData(DnsEndpoint.OpenDNSFamily)]
public void ShouldWorkForFirstSyncA(DnsEndpoint endpoint) {
if (ShouldSkipEndpoint(endpoint)) return;
using var client = new ClientX(endpoint);
var answer = client.ResolveFirstSync("evotec.pl", DnsRecordType.A, cancellationToken: CancellationToken.None);
Assert.True(answer != null);
Expand All @@ -311,6 +322,7 @@ public void ShouldWorkForFirstSyncA(DnsEndpoint endpoint) {
[InlineData(DnsEndpoint.OpenDNS)]
[InlineData(DnsEndpoint.OpenDNSFamily)]
public void ShouldWorkForAllSyncA(DnsEndpoint endpoint) {
if (ShouldSkipEndpoint(endpoint)) return;
using var client = new ClientX(endpoint);
var answers = client.ResolveAllSync("evotec.pl", DnsRecordType.A);
foreach (DnsAnswer answer in answers) {
Expand Down
Loading
Loading