Implemented Core client
This commit is contained in:
159
.editorconfig
Normal file
159
.editorconfig
Normal file
@@ -0,0 +1,159 @@
|
||||
# Documentation:
|
||||
# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference
|
||||
|
||||
# Top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
insert_final_newline = true
|
||||
end_of_line = crlf
|
||||
indent_style = tab
|
||||
|
||||
[*.{cs,vb}]
|
||||
# Sort using and Import directives with System.* appearing first
|
||||
dotnet_sort_system_directives_first = true
|
||||
|
||||
# Avoid "this." and "Me." if not necessary
|
||||
dotnet_style_qualification_for_event = false:warning
|
||||
dotnet_style_qualification_for_field = false:warning
|
||||
dotnet_style_qualification_for_method = false:warning
|
||||
dotnet_style_qualification_for_property = false:warning
|
||||
|
||||
# Use language keywords instead of framework type names for type references
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:warning
|
||||
dotnet_style_predefined_type_for_member_access = true:warning
|
||||
|
||||
# Suggest explicit accessibility modifiers
|
||||
dotnet_style_require_accessibility_modifiers = always:suggestion
|
||||
|
||||
# Suggest more modern language features when available
|
||||
dotnet_style_explicit_tuple_names = true:warning
|
||||
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||
dotnet_style_prefer_conditional_expression_over_assignment = true:none
|
||||
dotnet_style_prefer_conditional_expression_over_return = true:none
|
||||
dotnet_style_coalesce_expression = true:suggestion
|
||||
dotnet_style_null_propagation = true:suggestion
|
||||
|
||||
# Definitions
|
||||
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum, delegate, type_parameter
|
||||
dotnet_naming_symbols.methods_properties.applicable_kinds = method, local_function, property
|
||||
dotnet_naming_symbols.public_symbols.applicable_kinds = property, method, field, event
|
||||
dotnet_naming_symbols.public_symbols.applicable_accessibilities = public
|
||||
dotnet_naming_symbols.constant_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.constant_fields.required_modifiers = const
|
||||
dotnet_naming_symbols.private_protected_internal_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_protected_internal_fields.applicable_accessibilities = private, protected, internal
|
||||
dotnet_naming_symbols.parameters_locals.applicable_kinds = parameter, local
|
||||
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
|
||||
dotnet_naming_style.camel_case_style.capitalization = camel_case
|
||||
|
||||
# Name all types using PascalCase
|
||||
dotnet_naming_rule.types_must_be_capitalized.symbols = types
|
||||
dotnet_naming_rule.types_must_be_capitalized.style = pascal_case_style
|
||||
dotnet_naming_rule.types_must_be_capitalized.severity = warning
|
||||
|
||||
# Name all methods and properties using PascalCase
|
||||
dotnet_naming_rule.methods_properties_must_be_capitalized.symbols = methods_properties
|
||||
dotnet_naming_rule.methods_properties_must_be_capitalized.style = pascal_case_style
|
||||
dotnet_naming_rule.methods_properties_must_be_capitalized.severity = warning
|
||||
|
||||
# Name all public members using PascalCase
|
||||
dotnet_naming_rule.public_members_must_be_capitalized.symbols = public_symbols
|
||||
dotnet_naming_rule.public_members_must_be_capitalized.style = pascal_case_style
|
||||
dotnet_naming_rule.public_members_must_be_capitalized.severity = warning
|
||||
|
||||
# Name all constant fields using PascalCase
|
||||
dotnet_naming_rule.constant_fields_must_be_pascal_case.symbols = constant_fields
|
||||
dotnet_naming_rule.constant_fields_must_be_pascal_case.style = pascal_case_style
|
||||
dotnet_naming_rule.constant_fields_must_be_pascal_case.severity = suggestion
|
||||
|
||||
# Name all private and internal fields using camelCase
|
||||
dotnet_naming_rule.private_protected_internal_fields_must_be_camel_case.symbols = private_protected_internal_fields
|
||||
dotnet_naming_rule.private_protected_internal_fields_must_be_camel_case.style = camel_case_style
|
||||
dotnet_naming_rule.private_protected_internal_fields_must_be_camel_case.severity = warning
|
||||
|
||||
# Name all parameters and locals using camelCase
|
||||
dotnet_naming_rule.parameters_locals_must_be_camel_case.symbols = parameters_locals
|
||||
dotnet_naming_rule.parameters_locals_must_be_camel_case.style = camel_case_style
|
||||
dotnet_naming_rule.parameters_locals_must_be_camel_case.severity = warning
|
||||
|
||||
# Name all private fields starting with underscore
|
||||
dotnet_naming_rule.private_members_with_underscore.symbols = private_fields
|
||||
dotnet_naming_rule.private_members_with_underscore.style = prefix_underscore
|
||||
dotnet_naming_rule.private_members_with_underscore.severity = suggestion
|
||||
|
||||
dotnet_naming_symbols.private_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_fields.applicable_accessibilities = private
|
||||
|
||||
dotnet_naming_style.prefix_underscore.capitalization = camel_case
|
||||
dotnet_naming_style.prefix_underscore.required_prefix = _
|
||||
|
||||
[*.cs]
|
||||
csharp_preferred_modifier_order = public, private, protected, internal, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async:suggestion
|
||||
|
||||
# Only use "var" when it's obvious what the variable type is
|
||||
csharp_style_var_for_built_in_types = false:warning
|
||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||
csharp_style_var_elsewhere = false:none
|
||||
|
||||
# Prefer method-like constructs to have a block body
|
||||
csharp_style_expression_bodied_methods = false:none
|
||||
csharp_style_expression_bodied_constructors = false:none
|
||||
csharp_style_expression_bodied_operators = false:none
|
||||
|
||||
# Prefer property-like constructs to have an expression-body
|
||||
csharp_style_expression_bodied_properties = true:none
|
||||
csharp_style_expression_bodied_indexers = true:none
|
||||
csharp_style_expression_bodied_accessors = true:none
|
||||
|
||||
# Suggest more modern language features when available
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||
csharp_style_inlined_variable_declaration = true:suggestion
|
||||
csharp_style_throw_expression = true:suggestion
|
||||
csharp_style_conditional_delegate_call = true:suggestion
|
||||
csharp_style_pattern_local_over_anonymous_function = true:suggestion
|
||||
|
||||
# Newline settings
|
||||
csharp_new_line_before_open_brace = all
|
||||
csharp_new_line_before_else = true
|
||||
csharp_new_line_before_catch = true
|
||||
csharp_new_line_before_finally = true
|
||||
csharp_new_line_before_members_in_object_initializers = true
|
||||
csharp_new_line_before_members_in_anonymous_types = true
|
||||
|
||||
# Indentation preferences
|
||||
csharp_indent_block_contents = true
|
||||
csharp_indent_braces = false
|
||||
csharp_indent_case_contents = true
|
||||
csharp_indent_switch_labels = true
|
||||
csharp_indent_labels = one_less_than_current
|
||||
|
||||
# Space preferences
|
||||
csharp_space_after_cast = false
|
||||
csharp_space_after_colon_in_inheritance_clause = true
|
||||
csharp_space_after_comma = true
|
||||
csharp_space_after_dot = false
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
csharp_space_after_semicolon_in_for_statement = true
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
csharp_space_around_declaration_statements = do_not_ignore
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
csharp_space_before_comma = false
|
||||
csharp_space_before_dot = false
|
||||
csharp_space_before_open_square_brackets = false
|
||||
csharp_space_before_semicolon_in_for_statement = false
|
||||
csharp_space_between_empty_square_brackets = false
|
||||
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
csharp_space_between_parentheses = false
|
||||
csharp_space_between_square_brackets = false
|
||||
|
||||
[*.{xml,csproj,targets,props,json,yml}]
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
182
.gitlab-ci.yml
Normal file
182
.gitlab-ci.yml
Normal file
@@ -0,0 +1,182 @@
|
||||
image: mcr.microsoft.com/dotnet/sdk:8.0
|
||||
|
||||
variables:
|
||||
TZ: "Europe/Berlin"
|
||||
LANG: "de"
|
||||
|
||||
stages:
|
||||
- build
|
||||
- test
|
||||
- deploy
|
||||
|
||||
|
||||
|
||||
default-build:
|
||||
stage: build
|
||||
tags:
|
||||
- docker
|
||||
- lnx
|
||||
- 64bit
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG == null
|
||||
script:
|
||||
- dotnet build -c Debug --nologo
|
||||
- mkdir ./artifacts
|
||||
- shopt -s globstar
|
||||
- mv ./**/*.nupkg ./artifacts/ || true
|
||||
- mv ./**/*.snupkg ./artifacts/ || true
|
||||
artifacts:
|
||||
paths:
|
||||
- artifacts/*.nupkg
|
||||
- artifacts/*.snupkg
|
||||
expire_in: 1 days
|
||||
|
||||
default-test:
|
||||
stage: test
|
||||
dependencies:
|
||||
- default-build
|
||||
tags:
|
||||
- docker
|
||||
- lnx
|
||||
- 64bit
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG == null
|
||||
coverage: /Branch coverage[\s\S].+%/
|
||||
before_script:
|
||||
- dotnet tool install dotnet-reportgenerator-globaltool --tool-path /dotnet-tools
|
||||
script:
|
||||
- dotnet test -c Debug --nologo /p:CoverletOutputFormat=Cobertura
|
||||
- /dotnet-tools/reportgenerator "-reports:${CI_PROJECT_DIR}/**/coverage.cobertura.xml" "-targetdir:/reports" -reportType:TextSummary
|
||||
- cat /reports/Summary.txt
|
||||
artifacts:
|
||||
when: always
|
||||
reports:
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
path: ./**/coverage.cobertura.xml
|
||||
|
||||
default-deploy:
|
||||
stage: deploy
|
||||
dependencies:
|
||||
- default-build
|
||||
- default-test
|
||||
tags:
|
||||
- docker
|
||||
- lnx
|
||||
- 64bit
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG == null
|
||||
script:
|
||||
- dotnet nuget push -k $BAGET_APIKEY -s https://nuget.am-wd.de/v3/index.json --skip-duplicate artifacts/*.nupkg || true
|
||||
|
||||
|
||||
|
||||
core-build:
|
||||
stage: build
|
||||
tags:
|
||||
- docker
|
||||
- lnx
|
||||
- 64bit
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG =~ /^v[0-9.]+/
|
||||
script:
|
||||
- dotnet build -c Release --nologo Cloudflare/Cloudflare.csproj
|
||||
- mkdir ./artifacts
|
||||
- shopt -s globstar
|
||||
- mv ./**/*.nupkg ./artifacts/ || true
|
||||
- mv ./**/*.snupkg ./artifacts/ || true
|
||||
artifacts:
|
||||
paths:
|
||||
- artifacts/*.nupkg
|
||||
- artifacts/*.snupkg
|
||||
expire_in: 1 days
|
||||
|
||||
core-test:
|
||||
stage: test
|
||||
dependencies:
|
||||
- core-build
|
||||
tags:
|
||||
- docker
|
||||
- lnx
|
||||
- 64bit
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG =~ /^v[0-9.]+/
|
||||
script:
|
||||
- dotnet test -c Release --nologo /p:CoverletOutputFormat=Cobertura Cloudflare.Tests/Cloudflare.Tests.csproj
|
||||
artifacts:
|
||||
when: always
|
||||
reports:
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
path: ./**/coverage.cobertura.xml
|
||||
|
||||
core-deploy:
|
||||
stage: deploy
|
||||
dependencies:
|
||||
- core-build
|
||||
- core-test
|
||||
tags:
|
||||
- docker
|
||||
- lnx
|
||||
- 64bit
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG =~ /^v[0-9.]+/
|
||||
script:
|
||||
- dotnet nuget push -k $BAGET_APIKEY -s https://nuget.am-wd.de/v3/index.json --skip-duplicate artifacts/*.nupkg || true
|
||||
# - dotnet nuget push -k $NUGET_APIKEY -s https://api.nuget.org/v3/index.json --skip-duplicate artifacts/*.nupkg || true
|
||||
|
||||
|
||||
|
||||
extensions-build:
|
||||
stage: build
|
||||
tags:
|
||||
- docker
|
||||
- lnx
|
||||
- 64bit
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG =~ /^[a-z]+\/v[0-9.]+/
|
||||
script:
|
||||
- dotnet build -c Release --nologo
|
||||
- mkdir ./artifacts
|
||||
- shopt -s globstars
|
||||
- mv ./**/*.nupkg ./artifacts/ || true
|
||||
- mv ./**/*.snupkg ./artifacts/ || true
|
||||
artifacts:
|
||||
paths:
|
||||
- artifacts/*.nupkg
|
||||
- artifacts/*.snupkg
|
||||
expire_in: 1 days
|
||||
|
||||
extensions-test:
|
||||
stage: test
|
||||
dependencies:
|
||||
- extensions-build
|
||||
tags:
|
||||
- docker
|
||||
- lnx
|
||||
- 64bit
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG =~ /^[a-z]+\/v[0-9.]+/
|
||||
script:
|
||||
- dotnet test -c Release --nologo /p:CoverletOutputFormat=Cobertura
|
||||
artifacts:
|
||||
when: always
|
||||
reports:
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
path: ./**/coverage.cobertura.xml
|
||||
|
||||
extensions-deploy:
|
||||
stage: deploy
|
||||
dependencies:
|
||||
- extensions-build
|
||||
- extensions-test
|
||||
tags:
|
||||
- docker
|
||||
- lnx
|
||||
- 64bit
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG =~ /^[a-z]+\/v[0-9.]+/
|
||||
script:
|
||||
- dotnet nuget push -k $BAGET_APIKEY -s https://nuget.am-wd.de/v3/index.json --skip-duplicate artifacts/*.nupkg || true
|
||||
# - dotnet nuget push -k $NUGET_APIKEY -s https://api.nuget.org/v3/index.json --skip-duplicate artifacts/*.nupkg || true
|
||||
1
CHANGELOG.md
Normal file
1
CHANGELOG.md
Normal file
@@ -0,0 +1 @@
|
||||
# Changelog
|
||||
80
Cloudflare.Tests/Auth/ApiKeyAuthenticationTest.cs
Normal file
80
Cloudflare.Tests/Auth/ApiKeyAuthenticationTest.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using AMWD.Net.Api.Cloudflare.Auth;
|
||||
|
||||
namespace Cloudflare.Core.Tests.Auth
|
||||
{
|
||||
[TestClass]
|
||||
public class ApiKeyAuthenticationTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void ShouldAddHeaders()
|
||||
{
|
||||
// Arrange
|
||||
string emailAddress = "test@example.com";
|
||||
string apiKey = "some-api-key";
|
||||
|
||||
var auth = new ApiKeyAuthentication(emailAddress, apiKey);
|
||||
using var clt = new HttpClient();
|
||||
|
||||
// Act
|
||||
auth.AddHeader(clt);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(clt.DefaultRequestHeaders.Contains("X-Auth-Email"));
|
||||
Assert.IsTrue(clt.DefaultRequestHeaders.Contains("X-Auth-Key"));
|
||||
|
||||
Assert.AreEqual(emailAddress, clt.DefaultRequestHeaders.GetValues("X-Auth-Email").First());
|
||||
Assert.AreEqual(apiKey, clt.DefaultRequestHeaders.GetValues("X-Auth-Key").First());
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(null)]
|
||||
[DataRow("")]
|
||||
[DataRow(" ")]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public void ShouldArgumentNullExceptionForEmailAddress(string emailAddress)
|
||||
{
|
||||
// Arrange
|
||||
string apiKey = "some-api-key";
|
||||
|
||||
// Act
|
||||
new ApiKeyAuthentication(emailAddress, apiKey);
|
||||
|
||||
// Assert - ArgumentNullException
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(null)]
|
||||
[DataRow("")]
|
||||
[DataRow(" ")]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public void ShouldArgumentNullExceptionForApiKey(string apiKey)
|
||||
{
|
||||
// Arrange
|
||||
string emailAddress = "test@example.com";
|
||||
|
||||
// Act
|
||||
new ApiKeyAuthentication(emailAddress, apiKey);
|
||||
|
||||
// Assert - ArgumentNullException
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("test")]
|
||||
[DataRow("test@example")]
|
||||
[DataRow("example.com")]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public void ShouldArgumentExceptionForInvalidEmailAddress(string emailAddress)
|
||||
{
|
||||
// Arrange
|
||||
string apiKey = "some-api-key";
|
||||
|
||||
// Act
|
||||
new ApiKeyAuthentication(emailAddress, apiKey);
|
||||
|
||||
// Assert - ArgumentException
|
||||
}
|
||||
}
|
||||
}
|
||||
44
Cloudflare.Tests/Auth/ApiTokenAuthenticationTest.cs
Normal file
44
Cloudflare.Tests/Auth/ApiTokenAuthenticationTest.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using AMWD.Net.Api.Cloudflare.Auth;
|
||||
|
||||
namespace Cloudflare.Core.Tests.Auth
|
||||
{
|
||||
[TestClass]
|
||||
public class ApiTokenAuthenticationTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void ShouldAddHeader()
|
||||
{
|
||||
// Arrange
|
||||
string apiToken = "some-api-token";
|
||||
|
||||
var auth = new ApiTokenAuthentication(apiToken);
|
||||
using var clt = new HttpClient();
|
||||
|
||||
// Act
|
||||
auth.AddHeader(clt);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(clt.DefaultRequestHeaders.Contains("Authorization"));
|
||||
|
||||
Assert.AreEqual("Bearer", clt.DefaultRequestHeaders.Authorization.Scheme);
|
||||
Assert.AreEqual(apiToken, clt.DefaultRequestHeaders.Authorization.Parameter);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(null)]
|
||||
[DataRow("")]
|
||||
[DataRow(" ")]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public void ShouldArgumentNullExceptionForEmailAddress(string apiToken)
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
new ApiTokenAuthentication(apiToken);
|
||||
|
||||
// Assert - ArgumentNullException
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Cloudflare.Tests/Cloudflare.Tests.csproj
Normal file
30
Cloudflare.Tests/Cloudflare.Tests.csproj
Normal file
@@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<CollectCoverage>true</CollectCoverage>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.msbuild" Version="6.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="Moq" Version="4.20.72" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.6.1" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.6.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(SolutionDir)\Cloudflare\Cloudflare.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
251
Cloudflare.Tests/CloudflareClientTest.cs
Normal file
251
Cloudflare.Tests/CloudflareClientTest.cs
Normal file
@@ -0,0 +1,251 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using AMWD.Net.Api.Cloudflare;
|
||||
using AMWD.Net.Api.Cloudflare.Auth;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
|
||||
namespace Cloudflare.Core.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public class CloudflareClientTest
|
||||
{
|
||||
private Mock<HttpMessageHandler> _httpHandlerMock;
|
||||
private Mock<ClientOptions> _clientOptionsMock;
|
||||
private Mock<IAuthentication> _authenticationMock;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
_httpHandlerMock = new Mock<HttpMessageHandler>();
|
||||
_authenticationMock = new Mock<IAuthentication>();
|
||||
_clientOptionsMock = new Mock<ClientOptions>();
|
||||
|
||||
_clientOptionsMock.Setup(o => o.BaseUrl).Returns("http://localhost/api/v4/");
|
||||
_clientOptionsMock.Setup(o => o.Timeout).Returns(TimeSpan.FromSeconds(60));
|
||||
_clientOptionsMock.Setup(o => o.MaxRetries).Returns(2);
|
||||
_clientOptionsMock.Setup(o => o.DefaultHeaders).Returns(new Dictionary<string, string>());
|
||||
_clientOptionsMock.Setup(o => o.DefaultQueryParams).Returns(new Dictionary<string, string>());
|
||||
_clientOptionsMock.Setup(o => o.AllowRedirects).Returns(false);
|
||||
_clientOptionsMock.Setup(o => o.UseProxy).Returns(false);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldInitializeWithEmailAndKey()
|
||||
{
|
||||
// Arrange
|
||||
string email = "test@example.com";
|
||||
string apiKey = "some-api-key";
|
||||
|
||||
// Act
|
||||
using var client = new CloudflareClient(email, apiKey);
|
||||
|
||||
// Assert
|
||||
var httpClient = client.GetType()
|
||||
.GetField("_httpClient", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(client) as HttpClient;
|
||||
|
||||
Assert.IsNotNull(httpClient);
|
||||
Assert.AreEqual(email, httpClient.DefaultRequestHeaders.GetValues("X-Auth-Email").First());
|
||||
Assert.AreEqual(apiKey, httpClient.DefaultRequestHeaders.GetValues("X-Auth-Key").First());
|
||||
|
||||
VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldInitializeWithApiToken()
|
||||
{
|
||||
// Arrange
|
||||
string token = "some-special-api-token";
|
||||
|
||||
// Act
|
||||
using var client = new CloudflareClient(token);
|
||||
|
||||
// Assert
|
||||
var httpClient = client.GetType()
|
||||
.GetField("_httpClient", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.GetValue(client) as HttpClient;
|
||||
|
||||
Assert.IsNotNull(httpClient);
|
||||
Assert.AreEqual($"Bearer {token}", httpClient.DefaultRequestHeaders.GetValues("Authorization").First());
|
||||
|
||||
VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public void ShouldThrowArgumentNullOnMissingAuthentication()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
using var client = new CloudflareClient((IAuthentication)null);
|
||||
|
||||
// Assert - ArgumentNullException
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAddCustomDefaultHeaders()
|
||||
{
|
||||
// Arrange
|
||||
var clientOptions = new ClientOptions();
|
||||
clientOptions.DefaultHeaders.Add("SomeKey", "SomeValue");
|
||||
|
||||
// Act
|
||||
using var client = new CloudflareClient("token", clientOptions);
|
||||
|
||||
// Assert
|
||||
var httpClient = client.GetType()
|
||||
.GetField("_httpClient", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.GetValue(client) as HttpClient;
|
||||
|
||||
Assert.IsNotNull(httpClient);
|
||||
Assert.IsTrue(httpClient.DefaultRequestHeaders.Contains("SomeKey"));
|
||||
Assert.AreEqual("SomeValue", httpClient.DefaultRequestHeaders.GetValues("SomeKey").First());
|
||||
|
||||
VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldDisposeHttpClient()
|
||||
{
|
||||
// Arrange
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
client.Dispose();
|
||||
|
||||
// Assert
|
||||
_httpHandlerMock.Protected().Verify("Dispose", Times.Once(), exactParameterMatch: true, true);
|
||||
|
||||
VerifyDefault();
|
||||
VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAllowMultipleDispose()
|
||||
{
|
||||
// Arrange
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
client.Dispose();
|
||||
client.Dispose();
|
||||
|
||||
// Assert
|
||||
_httpHandlerMock.Protected().Verify("Dispose", Times.Once(), exactParameterMatch: true, true);
|
||||
|
||||
VerifyDefault();
|
||||
VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAssertClientOptions()
|
||||
{
|
||||
// Arrange + Act
|
||||
var client = GetClient();
|
||||
|
||||
// Assert
|
||||
VerifyDefault();
|
||||
VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public void ShouldThrowArgumentNullForBaseUrlOnAssertClientOptions()
|
||||
{
|
||||
// Arrange
|
||||
_clientOptionsMock
|
||||
.Setup(o => o.BaseUrl)
|
||||
.Returns((string)null);
|
||||
|
||||
// Act
|
||||
var client = GetClient();
|
||||
|
||||
// Assert - ArgumentNullException
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ShouldThrowArgumentOutOfRangeForTimeoutOnAssertClientOptions()
|
||||
{
|
||||
// Arrange
|
||||
_clientOptionsMock
|
||||
.Setup(o => o.Timeout)
|
||||
.Returns(TimeSpan.Zero);
|
||||
|
||||
// Act
|
||||
var client = GetClient();
|
||||
|
||||
// Assert - ArgumentOutOfRangeException
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(-1)]
|
||||
[DataRow(11)]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ShouldThrowArgumentOutOfRangeForMaxRetriesOnAssertClientOptions(int maxRetries)
|
||||
{
|
||||
// Arrange
|
||||
_clientOptionsMock
|
||||
.Setup(o => o.MaxRetries)
|
||||
.Returns(maxRetries);
|
||||
|
||||
// Act
|
||||
var client = GetClient();
|
||||
|
||||
// Assert - ArgumentOutOfRangeException
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public void ShouldThrowArgumentNullForUseProxyOnAssertClientOptions()
|
||||
{
|
||||
// Arrange
|
||||
_clientOptionsMock
|
||||
.Setup(o => o.UseProxy)
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
var client = GetClient();
|
||||
|
||||
// Assert - ArgumentNullException
|
||||
}
|
||||
|
||||
private void VerifyDefault()
|
||||
{
|
||||
_clientOptionsMock.VerifyGet(o => o.AllowRedirects, Times.Once);
|
||||
_clientOptionsMock.VerifyGet(o => o.BaseUrl, Times.Exactly(2));
|
||||
_clientOptionsMock.VerifyGet(o => o.DefaultHeaders, Times.Once);
|
||||
_clientOptionsMock.VerifyGet(o => o.MaxRetries, Times.Exactly(2));
|
||||
_clientOptionsMock.VerifyGet(o => o.Proxy, Times.Once);
|
||||
_clientOptionsMock.VerifyGet(o => o.Timeout, Times.Exactly(2));
|
||||
_clientOptionsMock.VerifyGet(o => o.UseProxy, Times.Exactly(2));
|
||||
|
||||
_authenticationMock.Verify(a => a.AddHeader(It.IsAny<HttpClient>()), Times.Once);
|
||||
}
|
||||
|
||||
private void VerifyNoOtherCalls()
|
||||
{
|
||||
_httpHandlerMock.VerifyNoOtherCalls();
|
||||
_clientOptionsMock.VerifyNoOtherCalls();
|
||||
_authenticationMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
private CloudflareClient GetClient()
|
||||
{
|
||||
var httpClient = new HttpClient(_httpHandlerMock.Object);
|
||||
var client = new CloudflareClient(_authenticationMock.Object, _clientOptionsMock.Object);
|
||||
|
||||
var httpClientField = client.GetType()
|
||||
.GetField("_httpClient", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
(httpClientField.GetValue(client) as HttpClient).Dispose();
|
||||
httpClientField.SetValue(client, httpClient);
|
||||
|
||||
return client;
|
||||
}
|
||||
}
|
||||
}
|
||||
326
Cloudflare.Tests/CloudflareClientTests/DeleteAsyncTest.cs
Normal file
326
Cloudflare.Tests/CloudflareClientTests/DeleteAsyncTest.cs
Normal file
@@ -0,0 +1,326 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Mime;
|
||||
using System.Reflection;
|
||||
using System.Security.Authentication;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AMWD.Net.Api.Cloudflare;
|
||||
using AMWD.Net.Api.Cloudflare.Auth;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
|
||||
namespace Cloudflare.Core.Tests.CloudflareClientTests
|
||||
{
|
||||
[TestClass]
|
||||
public class DeleteAsyncTest
|
||||
{
|
||||
private const string _baseUrl = "http://localhost/api/v4/";
|
||||
|
||||
private HttpMessageHandlerMock _httpHandlerMock;
|
||||
private Mock<ClientOptions> _clientOptionsMock;
|
||||
private Mock<IAuthentication> _authenticationMock;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
_httpHandlerMock = new HttpMessageHandlerMock();
|
||||
_authenticationMock = new Mock<IAuthentication>();
|
||||
_clientOptionsMock = new Mock<ClientOptions>();
|
||||
|
||||
_authenticationMock
|
||||
.Setup(a => a.AddHeader(It.IsAny<HttpClient>()))
|
||||
.Callback<HttpClient>(c => c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "Some-API-Token"));
|
||||
|
||||
_clientOptionsMock.Setup(o => o.BaseUrl).Returns(_baseUrl);
|
||||
_clientOptionsMock.Setup(o => o.Timeout).Returns(TimeSpan.FromSeconds(60));
|
||||
_clientOptionsMock.Setup(o => o.MaxRetries).Returns(2);
|
||||
_clientOptionsMock.Setup(o => o.DefaultHeaders).Returns(new Dictionary<string, string>());
|
||||
_clientOptionsMock.Setup(o => o.DefaultQueryParams).Returns(new Dictionary<string, string>());
|
||||
_clientOptionsMock.Setup(o => o.AllowRedirects).Returns(false);
|
||||
_clientOptionsMock.Setup(o => o.UseProxy).Returns(false);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ObjectDisposedException))]
|
||||
public async Task ShouldThrowDisposed()
|
||||
{
|
||||
// Arrange
|
||||
var client = GetClient() as CloudflareClient;
|
||||
client.Dispose();
|
||||
|
||||
// Act
|
||||
await client.DeleteAsync<object>("test");
|
||||
|
||||
// Assert - ObjectDisposedException
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(null)]
|
||||
[DataRow("")]
|
||||
[DataRow(" ")]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public async Task ShouldThrowArgumentNullOnRequestPath(string path)
|
||||
{
|
||||
// Arrange
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
await client.DeleteAsync<object>(path);
|
||||
|
||||
// Assert - ArgumentNullException
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public async Task ShouldThrowArgumentOnRequestPath()
|
||||
{
|
||||
// Arrange
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
await client.DeleteAsync<object>("foo?bar=baz");
|
||||
|
||||
// Assert - ArgumentException
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldDelete()
|
||||
{
|
||||
// Arrange
|
||||
_httpHandlerMock.Responses.Enqueue(new HttpResponseMessage
|
||||
{
|
||||
StatusCode = HttpStatusCode.OK,
|
||||
Content = new StringContent(@"{""success"": true, ""errors"": [], ""messages"": [], ""result"": { ""string"": ""some-string"", ""integer"": 123 }}", Encoding.UTF8, MediaTypeNames.Application.Json),
|
||||
});
|
||||
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
var response = await client.DeleteAsync<TestClass>("test");
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(response);
|
||||
Assert.IsTrue(response.Success);
|
||||
Assert.IsNotNull(response.Errors);
|
||||
Assert.IsNotNull(response.Messages);
|
||||
Assert.IsNull(response.ResultInfo);
|
||||
|
||||
Assert.AreEqual(0, response.Errors.Count);
|
||||
Assert.AreEqual(0, response.Messages.Count);
|
||||
|
||||
Assert.IsNotNull(response.Result);
|
||||
Assert.AreEqual("some-string", response.Result.Str);
|
||||
Assert.AreEqual(123, response.Result.Int);
|
||||
|
||||
Assert.AreEqual(1, _httpHandlerMock.Callbacks.Count);
|
||||
|
||||
var callback = _httpHandlerMock.Callbacks.First();
|
||||
Assert.AreEqual(HttpMethod.Delete, callback.Method);
|
||||
Assert.AreEqual("http://localhost/api/v4/test", callback.Url);
|
||||
Assert.IsNull(callback.Content);
|
||||
|
||||
Assert.AreEqual(3, callback.Headers.Count);
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("Accept"));
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("Authorization"));
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("User-Agent"));
|
||||
|
||||
Assert.AreEqual("application/json", callback.Headers["Accept"]);
|
||||
Assert.AreEqual("Bearer Some-API-Token", callback.Headers["Authorization"]);
|
||||
Assert.AreEqual("AMWD.CloudflareClient/1.0.0", callback.Headers["User-Agent"]);
|
||||
|
||||
_httpHandlerMock.Mock.Protected().Verify("SendAsync", Times.Once(), ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>());
|
||||
|
||||
VerifyDefaults();
|
||||
VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(HttpStatusCode.Unauthorized)]
|
||||
[DataRow(HttpStatusCode.Forbidden)]
|
||||
public async Task ShouldThrowAuthenticationExceptionOnStatusCode(HttpStatusCode statusCode)
|
||||
{
|
||||
// Arrange
|
||||
_httpHandlerMock.Responses.Enqueue(new HttpResponseMessage
|
||||
{
|
||||
StatusCode = statusCode,
|
||||
Content = new StringContent(@"{""success"": false, ""errors"": [{ ""code"": ""4711"", ""message"": ""foo & baz."" }, { ""code"": ""4712"", ""message"": ""Happy Error!"" }], ""messages"": []}", Encoding.UTF8, MediaTypeNames.Application.Json),
|
||||
});
|
||||
|
||||
var client = GetClient();
|
||||
|
||||
try
|
||||
{
|
||||
// Act
|
||||
await client.DeleteAsync<TestClass>("foo");
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (AuthenticationException ex)
|
||||
{
|
||||
// Assert
|
||||
Assert.IsNull(ex.InnerException);
|
||||
Assert.AreEqual($"4711: foo & baz.{Environment.NewLine}4712: Happy Error!", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldReturnPlainText()
|
||||
{
|
||||
// Arrange
|
||||
_clientOptionsMock.Setup(o => o.DefaultQueryParams).Returns(new Dictionary<string, string> { { "bar", "08/15" } });
|
||||
_httpHandlerMock.Responses.Enqueue(new HttpResponseMessage
|
||||
{
|
||||
StatusCode = HttpStatusCode.OK,
|
||||
Content = new StringContent("This is an awesome text ;-)", Encoding.UTF8, MediaTypeNames.Text.Plain),
|
||||
});
|
||||
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
var response = await client.DeleteAsync<string>("some-awesome-path");
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(response);
|
||||
Assert.IsTrue(response.Success);
|
||||
Assert.IsNotNull(response.Errors);
|
||||
Assert.IsNotNull(response.Messages);
|
||||
Assert.IsNotNull(response.ResultInfo);
|
||||
|
||||
Assert.AreEqual(0, response.Errors.Count);
|
||||
Assert.AreEqual(0, response.Messages.Count);
|
||||
|
||||
Assert.AreEqual(0, response.ResultInfo.Count);
|
||||
Assert.AreEqual(0, response.ResultInfo.Page);
|
||||
Assert.AreEqual(0, response.ResultInfo.PerPage);
|
||||
Assert.AreEqual(0, response.ResultInfo.TotalCount);
|
||||
|
||||
Assert.AreEqual("This is an awesome text ;-)", response.Result);
|
||||
|
||||
Assert.AreEqual(1, _httpHandlerMock.Callbacks.Count);
|
||||
|
||||
var callback = _httpHandlerMock.Callbacks.First();
|
||||
Assert.AreEqual(HttpMethod.Delete, callback.Method);
|
||||
Assert.AreEqual("http://localhost/api/v4/some-awesome-path?bar=08%2F15", callback.Url);
|
||||
Assert.IsNull(callback.Content);
|
||||
|
||||
Assert.AreEqual(3, callback.Headers.Count);
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("Accept"));
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("Authorization"));
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("User-Agent"));
|
||||
|
||||
Assert.AreEqual("application/json", callback.Headers["Accept"]);
|
||||
Assert.AreEqual("Bearer Some-API-Token", callback.Headers["Authorization"]);
|
||||
Assert.AreEqual("AMWD.CloudflareClient/1.0.0", callback.Headers["User-Agent"]);
|
||||
|
||||
_httpHandlerMock.Mock.Protected().Verify("SendAsync", Times.Once(), ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>());
|
||||
|
||||
_authenticationMock.Verify(m => m.AddHeader(It.IsAny<HttpClient>()), Times.Once);
|
||||
|
||||
_clientOptionsMock.Verify(o => o.BaseUrl, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.Timeout, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.MaxRetries, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.DefaultHeaders, Times.Once);
|
||||
_clientOptionsMock.Verify(o => o.DefaultQueryParams, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.AllowRedirects, Times.Once);
|
||||
_clientOptionsMock.Verify(o => o.UseProxy, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.Proxy, Times.Once);
|
||||
|
||||
VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(JsonReaderException))]
|
||||
public async Task ShouldThrowExceptionOnInvalidResponse()
|
||||
{
|
||||
// Arrange
|
||||
_httpHandlerMock.Responses.Enqueue(new HttpResponseMessage
|
||||
{
|
||||
StatusCode = HttpStatusCode.OK,
|
||||
Content = new StringContent("This is a bad text :p", Encoding.UTF8, MediaTypeNames.Text.Plain),
|
||||
});
|
||||
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
await client.DeleteAsync<TestClass>("some-path");
|
||||
}
|
||||
|
||||
private void VerifyDefaults()
|
||||
{
|
||||
_authenticationMock.Verify(m => m.AddHeader(It.IsAny<HttpClient>()), Times.Once);
|
||||
|
||||
_clientOptionsMock.Verify(o => o.BaseUrl, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.Timeout, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.MaxRetries, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.DefaultHeaders, Times.Once);
|
||||
_clientOptionsMock.Verify(o => o.DefaultQueryParams, Times.Once);
|
||||
_clientOptionsMock.Verify(o => o.AllowRedirects, Times.Once);
|
||||
_clientOptionsMock.Verify(o => o.UseProxy, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.Proxy, Times.Once);
|
||||
}
|
||||
|
||||
private void VerifyNoOtherCalls()
|
||||
{
|
||||
_httpHandlerMock.Mock.VerifyNoOtherCalls();
|
||||
_authenticationMock.VerifyNoOtherCalls();
|
||||
_clientOptionsMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
private ICloudflareClient GetClient()
|
||||
{
|
||||
var httpClient = new HttpClient(_httpHandlerMock.Mock.Object)
|
||||
{
|
||||
BaseAddress = new Uri(_baseUrl),
|
||||
Timeout = _clientOptionsMock.Object.Timeout,
|
||||
};
|
||||
|
||||
httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("AMWD.CloudflareClient", "1.0.0"));
|
||||
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
|
||||
if (_clientOptionsMock.Object.DefaultHeaders.Count > 0)
|
||||
{
|
||||
foreach (var headerKvp in _clientOptionsMock.Object.DefaultHeaders)
|
||||
httpClient.DefaultRequestHeaders.Add(headerKvp.Key, headerKvp.Value);
|
||||
}
|
||||
_authenticationMock.Object.AddHeader(httpClient);
|
||||
|
||||
_authenticationMock.Invocations.Clear();
|
||||
_clientOptionsMock.Invocations.Clear();
|
||||
|
||||
var client = new CloudflareClient(_authenticationMock.Object, _clientOptionsMock.Object);
|
||||
|
||||
var httpClientField = client.GetType()
|
||||
.GetField("_httpClient", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
(httpClientField.GetValue(client) as HttpClient).Dispose();
|
||||
httpClientField.SetValue(client, httpClient);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
private class TestClass
|
||||
{
|
||||
[JsonProperty("string")]
|
||||
public string Str { get; set; }
|
||||
|
||||
[JsonProperty("integer")]
|
||||
public int Int { get; set; }
|
||||
}
|
||||
|
||||
private class TestFilter : IQueryParameterFilter
|
||||
{
|
||||
public IDictionary<string, string> GetQueryParameters()
|
||||
{
|
||||
return new Dictionary<string, string>
|
||||
{
|
||||
{ "test", "filter-text" }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
326
Cloudflare.Tests/CloudflareClientTests/GetAsyncTest.cs
Normal file
326
Cloudflare.Tests/CloudflareClientTests/GetAsyncTest.cs
Normal file
@@ -0,0 +1,326 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Mime;
|
||||
using System.Reflection;
|
||||
using System.Security.Authentication;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AMWD.Net.Api.Cloudflare;
|
||||
using AMWD.Net.Api.Cloudflare.Auth;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
|
||||
namespace Cloudflare.Core.Tests.CloudflareClientTests
|
||||
{
|
||||
[TestClass]
|
||||
public class GetAsyncTest
|
||||
{
|
||||
private const string _baseUrl = "http://localhost/api/v4/";
|
||||
|
||||
private HttpMessageHandlerMock _httpHandlerMock;
|
||||
private Mock<ClientOptions> _clientOptionsMock;
|
||||
private Mock<IAuthentication> _authenticationMock;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
_httpHandlerMock = new HttpMessageHandlerMock();
|
||||
_authenticationMock = new Mock<IAuthentication>();
|
||||
_clientOptionsMock = new Mock<ClientOptions>();
|
||||
|
||||
_authenticationMock
|
||||
.Setup(a => a.AddHeader(It.IsAny<HttpClient>()))
|
||||
.Callback<HttpClient>(c => c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "Some-API-Token"));
|
||||
|
||||
_clientOptionsMock.Setup(o => o.BaseUrl).Returns(_baseUrl);
|
||||
_clientOptionsMock.Setup(o => o.Timeout).Returns(TimeSpan.FromSeconds(60));
|
||||
_clientOptionsMock.Setup(o => o.MaxRetries).Returns(2);
|
||||
_clientOptionsMock.Setup(o => o.DefaultHeaders).Returns(new Dictionary<string, string>());
|
||||
_clientOptionsMock.Setup(o => o.DefaultQueryParams).Returns(new Dictionary<string, string>());
|
||||
_clientOptionsMock.Setup(o => o.AllowRedirects).Returns(false);
|
||||
_clientOptionsMock.Setup(o => o.UseProxy).Returns(false);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ObjectDisposedException))]
|
||||
public async Task ShouldThrowDisposed()
|
||||
{
|
||||
// Arrange
|
||||
var client = GetClient() as CloudflareClient;
|
||||
client.Dispose();
|
||||
|
||||
// Act
|
||||
await client.GetAsync<object>("/test");
|
||||
|
||||
// Assert - ObjectDisposedException
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(null)]
|
||||
[DataRow("")]
|
||||
[DataRow(" ")]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public async Task ShouldThrowArgumentNullOnRequestPath(string path)
|
||||
{
|
||||
// Arrange
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
await client.GetAsync<object>(path);
|
||||
|
||||
// Assert - ArgumentNullException
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public async Task ShouldThrowArgumentOnRequestPath()
|
||||
{
|
||||
// Arrange
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
await client.GetAsync<object>("/foo?bar=baz");
|
||||
|
||||
// Assert - ArgumentException
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldGet()
|
||||
{
|
||||
// Arrange
|
||||
_httpHandlerMock.Responses.Enqueue(new HttpResponseMessage
|
||||
{
|
||||
StatusCode = HttpStatusCode.OK,
|
||||
Content = new StringContent(@"{""success"": true, ""errors"": [], ""messages"": [], ""result"": { ""string"": ""some-string"", ""integer"": 123 }}", Encoding.UTF8, MediaTypeNames.Application.Json),
|
||||
});
|
||||
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync<TestClass>("test");
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(response);
|
||||
Assert.IsTrue(response.Success);
|
||||
Assert.IsNotNull(response.Errors);
|
||||
Assert.IsNotNull(response.Messages);
|
||||
Assert.IsNull(response.ResultInfo);
|
||||
|
||||
Assert.AreEqual(0, response.Errors.Count);
|
||||
Assert.AreEqual(0, response.Messages.Count);
|
||||
|
||||
Assert.IsNotNull(response.Result);
|
||||
Assert.AreEqual("some-string", response.Result.Str);
|
||||
Assert.AreEqual(123, response.Result.Int);
|
||||
|
||||
Assert.AreEqual(1, _httpHandlerMock.Callbacks.Count);
|
||||
|
||||
var callback = _httpHandlerMock.Callbacks.First();
|
||||
Assert.AreEqual(HttpMethod.Get, callback.Method);
|
||||
Assert.AreEqual("http://localhost/api/v4/test", callback.Url);
|
||||
Assert.IsNull(callback.Content);
|
||||
|
||||
Assert.AreEqual(3, callback.Headers.Count);
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("Accept"));
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("Authorization"));
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("User-Agent"));
|
||||
|
||||
Assert.AreEqual("application/json", callback.Headers["Accept"]);
|
||||
Assert.AreEqual("Bearer Some-API-Token", callback.Headers["Authorization"]);
|
||||
Assert.AreEqual("AMWD.CloudflareClient/1.0.0", callback.Headers["User-Agent"]);
|
||||
|
||||
_httpHandlerMock.Mock.Protected().Verify("SendAsync", Times.Once(), ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>());
|
||||
|
||||
VerifyDefaults();
|
||||
VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(HttpStatusCode.Unauthorized)]
|
||||
[DataRow(HttpStatusCode.Forbidden)]
|
||||
public async Task ShouldThrowAuthenticationExceptionOnStatusCode(HttpStatusCode statusCode)
|
||||
{
|
||||
// Arrange
|
||||
_httpHandlerMock.Responses.Enqueue(new HttpResponseMessage
|
||||
{
|
||||
StatusCode = statusCode,
|
||||
Content = new StringContent(@"{""success"": false, ""errors"": [{ ""code"": ""4711"", ""message"": ""foo & baz."" }, { ""code"": ""4712"", ""message"": ""Happy Error!"" }], ""messages"": []}", Encoding.UTF8, MediaTypeNames.Application.Json),
|
||||
});
|
||||
|
||||
var client = GetClient();
|
||||
|
||||
try
|
||||
{
|
||||
// Act
|
||||
await client.GetAsync<TestClass>("foo");
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (AuthenticationException ex)
|
||||
{
|
||||
// Assert
|
||||
Assert.IsNull(ex.InnerException);
|
||||
Assert.AreEqual($"4711: foo & baz.{Environment.NewLine}4712: Happy Error!", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldReturnPlainText()
|
||||
{
|
||||
// Arrange
|
||||
_clientOptionsMock.Setup(o => o.DefaultQueryParams).Returns(new Dictionary<string, string> { { "bar", "08/15" } });
|
||||
_httpHandlerMock.Responses.Enqueue(new HttpResponseMessage
|
||||
{
|
||||
StatusCode = HttpStatusCode.OK,
|
||||
Content = new StringContent("This is an awesome text ;-)", Encoding.UTF8, MediaTypeNames.Text.Plain),
|
||||
});
|
||||
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync<string>("some-awesome-path", new TestFilter());
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(response);
|
||||
Assert.IsTrue(response.Success);
|
||||
Assert.IsNotNull(response.Errors);
|
||||
Assert.IsNotNull(response.Messages);
|
||||
Assert.IsNotNull(response.ResultInfo);
|
||||
|
||||
Assert.AreEqual(0, response.Errors.Count);
|
||||
Assert.AreEqual(0, response.Messages.Count);
|
||||
|
||||
Assert.AreEqual(0, response.ResultInfo.Count);
|
||||
Assert.AreEqual(0, response.ResultInfo.Page);
|
||||
Assert.AreEqual(0, response.ResultInfo.PerPage);
|
||||
Assert.AreEqual(0, response.ResultInfo.TotalCount);
|
||||
|
||||
Assert.AreEqual("This is an awesome text ;-)", response.Result);
|
||||
|
||||
Assert.AreEqual(1, _httpHandlerMock.Callbacks.Count);
|
||||
|
||||
var callback = _httpHandlerMock.Callbacks.First();
|
||||
Assert.AreEqual(HttpMethod.Get, callback.Method);
|
||||
Assert.AreEqual("http://localhost/api/v4/some-awesome-path?bar=08%2F15&test=filter-text", callback.Url);
|
||||
Assert.IsNull(callback.Content);
|
||||
|
||||
Assert.AreEqual(3, callback.Headers.Count);
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("Accept"));
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("Authorization"));
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("User-Agent"));
|
||||
|
||||
Assert.AreEqual("application/json", callback.Headers["Accept"]);
|
||||
Assert.AreEqual("Bearer Some-API-Token", callback.Headers["Authorization"]);
|
||||
Assert.AreEqual("AMWD.CloudflareClient/1.0.0", callback.Headers["User-Agent"]);
|
||||
|
||||
_httpHandlerMock.Mock.Protected().Verify("SendAsync", Times.Once(), ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>());
|
||||
|
||||
_authenticationMock.Verify(m => m.AddHeader(It.IsAny<HttpClient>()), Times.Once);
|
||||
|
||||
_clientOptionsMock.Verify(o => o.BaseUrl, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.Timeout, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.MaxRetries, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.DefaultHeaders, Times.Once);
|
||||
_clientOptionsMock.Verify(o => o.DefaultQueryParams, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.AllowRedirects, Times.Once);
|
||||
_clientOptionsMock.Verify(o => o.UseProxy, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.Proxy, Times.Once);
|
||||
|
||||
VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(JsonReaderException))]
|
||||
public async Task ShouldThrowExceptionOnInvalidResponse()
|
||||
{
|
||||
// Arrange
|
||||
_httpHandlerMock.Responses.Enqueue(new HttpResponseMessage
|
||||
{
|
||||
StatusCode = HttpStatusCode.OK,
|
||||
Content = new StringContent("This is a bad text :p", Encoding.UTF8, MediaTypeNames.Text.Plain),
|
||||
});
|
||||
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
await client.GetAsync<TestClass>("some-path");
|
||||
}
|
||||
|
||||
private void VerifyDefaults()
|
||||
{
|
||||
_authenticationMock.Verify(m => m.AddHeader(It.IsAny<HttpClient>()), Times.Once);
|
||||
|
||||
_clientOptionsMock.Verify(o => o.BaseUrl, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.Timeout, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.MaxRetries, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.DefaultHeaders, Times.Once);
|
||||
_clientOptionsMock.Verify(o => o.DefaultQueryParams, Times.Once);
|
||||
_clientOptionsMock.Verify(o => o.AllowRedirects, Times.Once);
|
||||
_clientOptionsMock.Verify(o => o.UseProxy, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.Proxy, Times.Once);
|
||||
}
|
||||
|
||||
private void VerifyNoOtherCalls()
|
||||
{
|
||||
_httpHandlerMock.Mock.VerifyNoOtherCalls();
|
||||
_authenticationMock.VerifyNoOtherCalls();
|
||||
_clientOptionsMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
private ICloudflareClient GetClient()
|
||||
{
|
||||
var httpClient = new HttpClient(_httpHandlerMock.Mock.Object)
|
||||
{
|
||||
BaseAddress = new Uri(_baseUrl),
|
||||
Timeout = _clientOptionsMock.Object.Timeout,
|
||||
};
|
||||
|
||||
httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("AMWD.CloudflareClient", "1.0.0"));
|
||||
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
|
||||
if (_clientOptionsMock.Object.DefaultHeaders.Count > 0)
|
||||
{
|
||||
foreach (var headerKvp in _clientOptionsMock.Object.DefaultHeaders)
|
||||
httpClient.DefaultRequestHeaders.Add(headerKvp.Key, headerKvp.Value);
|
||||
}
|
||||
_authenticationMock.Object.AddHeader(httpClient);
|
||||
|
||||
_authenticationMock.Invocations.Clear();
|
||||
_clientOptionsMock.Invocations.Clear();
|
||||
|
||||
var client = new CloudflareClient(_authenticationMock.Object, _clientOptionsMock.Object);
|
||||
|
||||
var httpClientField = client.GetType()
|
||||
.GetField("_httpClient", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
(httpClientField.GetValue(client) as HttpClient).Dispose();
|
||||
httpClientField.SetValue(client, httpClient);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
private class TestClass
|
||||
{
|
||||
[JsonProperty("string")]
|
||||
public string Str { get; set; }
|
||||
|
||||
[JsonProperty("integer")]
|
||||
public int Int { get; set; }
|
||||
}
|
||||
|
||||
private class TestFilter : IQueryParameterFilter
|
||||
{
|
||||
public IDictionary<string, string> GetQueryParameters()
|
||||
{
|
||||
return new Dictionary<string, string>
|
||||
{
|
||||
{ "test", "filter-text" }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
386
Cloudflare.Tests/CloudflareClientTests/PatchAsyncTest.cs
Normal file
386
Cloudflare.Tests/CloudflareClientTests/PatchAsyncTest.cs
Normal file
@@ -0,0 +1,386 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Mime;
|
||||
using System.Reflection;
|
||||
using System.Security.Authentication;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AMWD.Net.Api.Cloudflare;
|
||||
using AMWD.Net.Api.Cloudflare.Auth;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
|
||||
namespace Cloudflare.Core.Tests.CloudflareClientTests
|
||||
{
|
||||
[TestClass]
|
||||
public class PatchAsyncTest
|
||||
{
|
||||
private const string _baseUrl = "https://localhost/api/v4/";
|
||||
|
||||
private HttpMessageHandlerMock _httpHandlerMock;
|
||||
private Mock<ClientOptions> _clientOptionsMock;
|
||||
private Mock<IAuthentication> _authenticationMock;
|
||||
|
||||
private TestClass _request;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
_httpHandlerMock = new HttpMessageHandlerMock();
|
||||
_authenticationMock = new Mock<IAuthentication>();
|
||||
_clientOptionsMock = new Mock<ClientOptions>();
|
||||
|
||||
_authenticationMock
|
||||
.Setup(a => a.AddHeader(It.IsAny<HttpClient>()))
|
||||
.Callback<HttpClient>(c => c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "Some-API-Token"));
|
||||
|
||||
_clientOptionsMock.Setup(o => o.BaseUrl).Returns(_baseUrl);
|
||||
_clientOptionsMock.Setup(o => o.Timeout).Returns(TimeSpan.FromSeconds(60));
|
||||
_clientOptionsMock.Setup(o => o.MaxRetries).Returns(2);
|
||||
_clientOptionsMock.Setup(o => o.DefaultHeaders).Returns(new Dictionary<string, string>());
|
||||
_clientOptionsMock.Setup(o => o.DefaultQueryParams).Returns(new Dictionary<string, string>());
|
||||
_clientOptionsMock.Setup(o => o.AllowRedirects).Returns(false);
|
||||
_clientOptionsMock.Setup(o => o.UseProxy).Returns(false);
|
||||
|
||||
_request = new TestClass
|
||||
{
|
||||
Int = 54321,
|
||||
Str = "Happy Testing!"
|
||||
};
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ObjectDisposedException))]
|
||||
public async Task ShouldThrowDisposed()
|
||||
{
|
||||
// Arrange
|
||||
var client = GetClient() as CloudflareClient;
|
||||
client.Dispose();
|
||||
|
||||
// Act
|
||||
await client.PatchAsync<object, object>("test", _request);
|
||||
|
||||
// Assert - ObjectDisposedException
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(null)]
|
||||
[DataRow("")]
|
||||
[DataRow(" ")]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public async Task ShouldThrowArgumentNullOnRequestPath(string path)
|
||||
{
|
||||
// Arrange
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
await client.PatchAsync<object, object>(path, _request);
|
||||
|
||||
// Assert - ArgumentNullException
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public async Task ShouldThrowArgumentOnRequestPath()
|
||||
{
|
||||
// Arrange
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
await client.PatchAsync<object, object>("foo?bar=baz", _request);
|
||||
|
||||
// Assert - ArgumentException
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldPatch()
|
||||
{
|
||||
// Arrange
|
||||
_httpHandlerMock.Responses.Enqueue(new HttpResponseMessage
|
||||
{
|
||||
StatusCode = HttpStatusCode.OK,
|
||||
Content = new StringContent(@"{""success"": true, ""errors"": [], ""messages"": [], ""result"": { ""string"": ""some-string"", ""integer"": 123 }}", Encoding.UTF8, MediaTypeNames.Application.Json),
|
||||
});
|
||||
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
var response = await client.PatchAsync<TestClass, TestClass>("test", _request);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(response);
|
||||
Assert.IsTrue(response.Success);
|
||||
Assert.IsNotNull(response.Errors);
|
||||
Assert.IsNotNull(response.Messages);
|
||||
Assert.IsNull(response.ResultInfo);
|
||||
|
||||
Assert.AreEqual(0, response.Errors.Count);
|
||||
Assert.AreEqual(0, response.Messages.Count);
|
||||
|
||||
Assert.IsNotNull(response.Result);
|
||||
Assert.AreEqual("some-string", response.Result.Str);
|
||||
Assert.AreEqual(123, response.Result.Int);
|
||||
|
||||
Assert.AreEqual(1, _httpHandlerMock.Callbacks.Count);
|
||||
|
||||
var callback = _httpHandlerMock.Callbacks.First();
|
||||
Assert.AreEqual(HttpMethod.Patch, callback.Method);
|
||||
Assert.AreEqual("https://localhost/api/v4/test", callback.Url);
|
||||
Assert.AreEqual(@"{""string"":""Happy Testing!"",""integer"":54321}", callback.Content);
|
||||
|
||||
Assert.AreEqual(3, callback.Headers.Count);
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("Accept"));
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("Authorization"));
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("User-Agent"));
|
||||
|
||||
Assert.AreEqual("application/json", callback.Headers["Accept"]);
|
||||
Assert.AreEqual("Bearer Some-API-Token", callback.Headers["Authorization"]);
|
||||
Assert.AreEqual("AMWD.CloudflareClient/1.0.0", callback.Headers["User-Agent"]);
|
||||
|
||||
_httpHandlerMock.Mock.Protected().Verify("SendAsync", Times.Once(), ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>());
|
||||
|
||||
VerifyDefaults();
|
||||
VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldPatchHttpContentDirectly()
|
||||
{
|
||||
// Arrange
|
||||
var stringContent = new StringContent(@"{""test"":""HERE ?""}", Encoding.UTF8, "application/json");
|
||||
_httpHandlerMock.Responses.Enqueue(new HttpResponseMessage
|
||||
{
|
||||
StatusCode = HttpStatusCode.OK,
|
||||
Content = new StringContent(@"{""success"": true, ""errors"": [], ""messages"": [], ""result"": { ""string"": ""some-string"", ""integer"": 123 }}", Encoding.UTF8, MediaTypeNames.Application.Json),
|
||||
});
|
||||
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
var response = await client.PatchAsync<TestClass, StringContent>("test", stringContent);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(response);
|
||||
Assert.IsTrue(response.Success);
|
||||
Assert.IsNotNull(response.Errors);
|
||||
Assert.IsNotNull(response.Messages);
|
||||
Assert.IsNull(response.ResultInfo);
|
||||
|
||||
Assert.AreEqual(0, response.Errors.Count);
|
||||
Assert.AreEqual(0, response.Messages.Count);
|
||||
|
||||
Assert.IsNotNull(response.Result);
|
||||
Assert.AreEqual("some-string", response.Result.Str);
|
||||
Assert.AreEqual(123, response.Result.Int);
|
||||
|
||||
Assert.AreEqual(1, _httpHandlerMock.Callbacks.Count);
|
||||
|
||||
var callback = _httpHandlerMock.Callbacks.First();
|
||||
Assert.AreEqual(HttpMethod.Patch, callback.Method);
|
||||
Assert.AreEqual("https://localhost/api/v4/test", callback.Url);
|
||||
Assert.AreEqual(@"{""test"":""HERE ?""}", callback.Content);
|
||||
|
||||
Assert.AreEqual(3, callback.Headers.Count);
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("Accept"));
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("Authorization"));
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("User-Agent"));
|
||||
|
||||
Assert.AreEqual("application/json", callback.Headers["Accept"]);
|
||||
Assert.AreEqual("Bearer Some-API-Token", callback.Headers["Authorization"]);
|
||||
Assert.AreEqual("AMWD.CloudflareClient/1.0.0", callback.Headers["User-Agent"]);
|
||||
|
||||
_httpHandlerMock.Mock.Protected().Verify("SendAsync", Times.Once(), ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>());
|
||||
|
||||
VerifyDefaults();
|
||||
VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(HttpStatusCode.Unauthorized)]
|
||||
[DataRow(HttpStatusCode.Forbidden)]
|
||||
public async Task ShouldThrowAuthenticationExceptionOnStatusCode(HttpStatusCode statusCode)
|
||||
{
|
||||
// Arrange
|
||||
_httpHandlerMock.Responses.Enqueue(new HttpResponseMessage
|
||||
{
|
||||
StatusCode = statusCode,
|
||||
Content = new StringContent(@"{""success"": false, ""errors"": [{ ""code"": ""4711"", ""message"": ""foo & baz."" }, { ""code"": ""4712"", ""message"": ""Happy Error!"" }], ""messages"": []}", Encoding.UTF8, MediaTypeNames.Application.Json),
|
||||
});
|
||||
|
||||
var client = GetClient();
|
||||
|
||||
try
|
||||
{
|
||||
// Act
|
||||
await client.PatchAsync<object, object>("foo", _request);
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (AuthenticationException ex)
|
||||
{
|
||||
// Assert
|
||||
Assert.IsNull(ex.InnerException);
|
||||
Assert.AreEqual($"4711: foo & baz.{Environment.NewLine}4712: Happy Error!", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldReturnPlainText()
|
||||
{
|
||||
// Arrange
|
||||
_clientOptionsMock.Setup(o => o.DefaultQueryParams).Returns(new Dictionary<string, string> { { "bar", "08/15" } });
|
||||
_httpHandlerMock.Responses.Enqueue(new HttpResponseMessage
|
||||
{
|
||||
StatusCode = HttpStatusCode.OK,
|
||||
Content = new StringContent("This is an awesome text ;-)", Encoding.UTF8, MediaTypeNames.Text.Plain),
|
||||
});
|
||||
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
var response = await client.PatchAsync<string, TestClass>("some-awesome-path", _request);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(response);
|
||||
Assert.IsTrue(response.Success);
|
||||
Assert.IsNotNull(response.Errors);
|
||||
Assert.IsNotNull(response.Messages);
|
||||
Assert.IsNotNull(response.ResultInfo);
|
||||
|
||||
Assert.AreEqual(0, response.Errors.Count);
|
||||
Assert.AreEqual(0, response.Messages.Count);
|
||||
|
||||
Assert.AreEqual(0, response.ResultInfo.Count);
|
||||
Assert.AreEqual(0, response.ResultInfo.Page);
|
||||
Assert.AreEqual(0, response.ResultInfo.PerPage);
|
||||
Assert.AreEqual(0, response.ResultInfo.TotalCount);
|
||||
|
||||
Assert.AreEqual("This is an awesome text ;-)", response.Result);
|
||||
|
||||
Assert.AreEqual(1, _httpHandlerMock.Callbacks.Count);
|
||||
|
||||
var callback = _httpHandlerMock.Callbacks.First();
|
||||
Assert.AreEqual(HttpMethod.Patch, callback.Method);
|
||||
Assert.AreEqual("https://localhost/api/v4/some-awesome-path?bar=08%2F15", callback.Url);
|
||||
Assert.AreEqual(@"{""string"":""Happy Testing!"",""integer"":54321}", callback.Content);
|
||||
|
||||
Assert.AreEqual(3, callback.Headers.Count);
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("Accept"));
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("Authorization"));
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("User-Agent"));
|
||||
|
||||
Assert.AreEqual("application/json", callback.Headers["Accept"]);
|
||||
Assert.AreEqual("Bearer Some-API-Token", callback.Headers["Authorization"]);
|
||||
Assert.AreEqual("AMWD.CloudflareClient/1.0.0", callback.Headers["User-Agent"]);
|
||||
|
||||
_httpHandlerMock.Mock.Protected().Verify("SendAsync", Times.Once(), ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>());
|
||||
|
||||
_authenticationMock.Verify(m => m.AddHeader(It.IsAny<HttpClient>()), Times.Once);
|
||||
|
||||
_clientOptionsMock.Verify(o => o.BaseUrl, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.Timeout, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.MaxRetries, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.DefaultHeaders, Times.Once);
|
||||
_clientOptionsMock.Verify(o => o.DefaultQueryParams, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.AllowRedirects, Times.Once);
|
||||
_clientOptionsMock.Verify(o => o.UseProxy, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.Proxy, Times.Once);
|
||||
|
||||
VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(JsonReaderException))]
|
||||
public async Task ShouldThrowExceptionOnInvalidResponse()
|
||||
{
|
||||
// Arrange
|
||||
_httpHandlerMock.Responses.Enqueue(new HttpResponseMessage
|
||||
{
|
||||
StatusCode = HttpStatusCode.OK,
|
||||
Content = new StringContent("This is a bad text :p", Encoding.UTF8, MediaTypeNames.Text.Plain),
|
||||
});
|
||||
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
await client.PatchAsync<TestClass, TestClass>("some-path", _request);
|
||||
}
|
||||
|
||||
private void VerifyDefaults()
|
||||
{
|
||||
_authenticationMock.Verify(m => m.AddHeader(It.IsAny<HttpClient>()), Times.Once);
|
||||
|
||||
_clientOptionsMock.Verify(o => o.BaseUrl, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.Timeout, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.MaxRetries, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.DefaultHeaders, Times.Once);
|
||||
_clientOptionsMock.Verify(o => o.DefaultQueryParams, Times.Once);
|
||||
_clientOptionsMock.Verify(o => o.AllowRedirects, Times.Once);
|
||||
_clientOptionsMock.Verify(o => o.UseProxy, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.Proxy, Times.Once);
|
||||
}
|
||||
|
||||
private void VerifyNoOtherCalls()
|
||||
{
|
||||
_httpHandlerMock.Mock.VerifyNoOtherCalls();
|
||||
_authenticationMock.VerifyNoOtherCalls();
|
||||
_clientOptionsMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
private ICloudflareClient GetClient()
|
||||
{
|
||||
var httpClient = new HttpClient(_httpHandlerMock.Mock.Object)
|
||||
{
|
||||
BaseAddress = new Uri(_baseUrl),
|
||||
Timeout = _clientOptionsMock.Object.Timeout,
|
||||
};
|
||||
|
||||
httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("AMWD.CloudflareClient", "1.0.0"));
|
||||
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
|
||||
if (_clientOptionsMock.Object.DefaultHeaders.Count > 0)
|
||||
{
|
||||
foreach (var headerKvp in _clientOptionsMock.Object.DefaultHeaders)
|
||||
httpClient.DefaultRequestHeaders.Add(headerKvp.Key, headerKvp.Value);
|
||||
}
|
||||
_authenticationMock.Object.AddHeader(httpClient);
|
||||
|
||||
_authenticationMock.Invocations.Clear();
|
||||
_clientOptionsMock.Invocations.Clear();
|
||||
|
||||
var client = new CloudflareClient(_authenticationMock.Object, _clientOptionsMock.Object);
|
||||
|
||||
var httpClientField = client.GetType()
|
||||
.GetField("_httpClient", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
(httpClientField.GetValue(client) as HttpClient).Dispose();
|
||||
httpClientField.SetValue(client, httpClient);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
private class TestClass
|
||||
{
|
||||
[JsonProperty("string")]
|
||||
public string Str { get; set; }
|
||||
|
||||
[JsonProperty("integer")]
|
||||
public int Int { get; set; }
|
||||
}
|
||||
|
||||
private class TestFilter : IQueryParameterFilter
|
||||
{
|
||||
public IDictionary<string, string> GetQueryParameters()
|
||||
{
|
||||
return new Dictionary<string, string>
|
||||
{
|
||||
{ "test", "filter-text" }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
386
Cloudflare.Tests/CloudflareClientTests/PostAsyncTest.cs
Normal file
386
Cloudflare.Tests/CloudflareClientTests/PostAsyncTest.cs
Normal file
@@ -0,0 +1,386 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Mime;
|
||||
using System.Reflection;
|
||||
using System.Security.Authentication;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AMWD.Net.Api.Cloudflare;
|
||||
using AMWD.Net.Api.Cloudflare.Auth;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
|
||||
namespace Cloudflare.Core.Tests.CloudflareClientTests
|
||||
{
|
||||
[TestClass]
|
||||
public class PostAsyncTest
|
||||
{
|
||||
private const string _baseUrl = "https://localhost/api/v4/";
|
||||
|
||||
private HttpMessageHandlerMock _httpHandlerMock;
|
||||
private Mock<ClientOptions> _clientOptionsMock;
|
||||
private Mock<IAuthentication> _authenticationMock;
|
||||
|
||||
private TestClass _request;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
_httpHandlerMock = new HttpMessageHandlerMock();
|
||||
_authenticationMock = new Mock<IAuthentication>();
|
||||
_clientOptionsMock = new Mock<ClientOptions>();
|
||||
|
||||
_authenticationMock
|
||||
.Setup(a => a.AddHeader(It.IsAny<HttpClient>()))
|
||||
.Callback<HttpClient>(c => c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "Some-API-Token"));
|
||||
|
||||
_clientOptionsMock.Setup(o => o.BaseUrl).Returns(_baseUrl);
|
||||
_clientOptionsMock.Setup(o => o.Timeout).Returns(TimeSpan.FromSeconds(60));
|
||||
_clientOptionsMock.Setup(o => o.MaxRetries).Returns(2);
|
||||
_clientOptionsMock.Setup(o => o.DefaultHeaders).Returns(new Dictionary<string, string>());
|
||||
_clientOptionsMock.Setup(o => o.DefaultQueryParams).Returns(new Dictionary<string, string>());
|
||||
_clientOptionsMock.Setup(o => o.AllowRedirects).Returns(false);
|
||||
_clientOptionsMock.Setup(o => o.UseProxy).Returns(false);
|
||||
|
||||
_request = new TestClass
|
||||
{
|
||||
Int = 54321,
|
||||
Str = "Happy Testing!"
|
||||
};
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ObjectDisposedException))]
|
||||
public async Task ShouldThrowDisposed()
|
||||
{
|
||||
// Arrange
|
||||
var client = GetClient() as CloudflareClient;
|
||||
client.Dispose();
|
||||
|
||||
// Act
|
||||
await client.PostAsync<object, object>("test", _request);
|
||||
|
||||
// Assert - ObjectDisposedException
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(null)]
|
||||
[DataRow("")]
|
||||
[DataRow(" ")]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public async Task ShouldThrowArgumentNullOnRequestPath(string path)
|
||||
{
|
||||
// Arrange
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
await client.PostAsync<object, object>(path, _request);
|
||||
|
||||
// Assert - ArgumentNullException
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public async Task ShouldThrowArgumentOnRequestPath()
|
||||
{
|
||||
// Arrange
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
await client.PostAsync<object, object>("foo?bar=baz", _request);
|
||||
|
||||
// Assert - ArgumentException
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldPost()
|
||||
{
|
||||
// Arrange
|
||||
_httpHandlerMock.Responses.Enqueue(new HttpResponseMessage
|
||||
{
|
||||
StatusCode = HttpStatusCode.OK,
|
||||
Content = new StringContent(@"{""success"": true, ""errors"": [], ""messages"": [], ""result"": { ""string"": ""some-string"", ""integer"": 123 }}", Encoding.UTF8, MediaTypeNames.Application.Json),
|
||||
});
|
||||
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
var response = await client.PostAsync<TestClass, TestClass>("test", _request);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(response);
|
||||
Assert.IsTrue(response.Success);
|
||||
Assert.IsNotNull(response.Errors);
|
||||
Assert.IsNotNull(response.Messages);
|
||||
Assert.IsNull(response.ResultInfo);
|
||||
|
||||
Assert.AreEqual(0, response.Errors.Count);
|
||||
Assert.AreEqual(0, response.Messages.Count);
|
||||
|
||||
Assert.IsNotNull(response.Result);
|
||||
Assert.AreEqual("some-string", response.Result.Str);
|
||||
Assert.AreEqual(123, response.Result.Int);
|
||||
|
||||
Assert.AreEqual(1, _httpHandlerMock.Callbacks.Count);
|
||||
|
||||
var callback = _httpHandlerMock.Callbacks.First();
|
||||
Assert.AreEqual(HttpMethod.Post, callback.Method);
|
||||
Assert.AreEqual("https://localhost/api/v4/test", callback.Url);
|
||||
Assert.AreEqual(@"{""string"":""Happy Testing!"",""integer"":54321}", callback.Content);
|
||||
|
||||
Assert.AreEqual(3, callback.Headers.Count);
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("Accept"));
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("Authorization"));
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("User-Agent"));
|
||||
|
||||
Assert.AreEqual("application/json", callback.Headers["Accept"]);
|
||||
Assert.AreEqual("Bearer Some-API-Token", callback.Headers["Authorization"]);
|
||||
Assert.AreEqual("AMWD.CloudflareClient/1.0.0", callback.Headers["User-Agent"]);
|
||||
|
||||
_httpHandlerMock.Mock.Protected().Verify("SendAsync", Times.Once(), ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>());
|
||||
|
||||
VerifyDefaults();
|
||||
VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldPostHttpContentDirectly()
|
||||
{
|
||||
// Arrange
|
||||
var stringContent = new StringContent(@"{""test"":""HERE ?""}", Encoding.UTF8, "application/json");
|
||||
_httpHandlerMock.Responses.Enqueue(new HttpResponseMessage
|
||||
{
|
||||
StatusCode = HttpStatusCode.OK,
|
||||
Content = new StringContent(@"{""success"": true, ""errors"": [], ""messages"": [], ""result"": { ""string"": ""some-string"", ""integer"": 123 }}", Encoding.UTF8, MediaTypeNames.Application.Json),
|
||||
});
|
||||
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
var response = await client.PostAsync<TestClass, StringContent>("test", stringContent);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(response);
|
||||
Assert.IsTrue(response.Success);
|
||||
Assert.IsNotNull(response.Errors);
|
||||
Assert.IsNotNull(response.Messages);
|
||||
Assert.IsNull(response.ResultInfo);
|
||||
|
||||
Assert.AreEqual(0, response.Errors.Count);
|
||||
Assert.AreEqual(0, response.Messages.Count);
|
||||
|
||||
Assert.IsNotNull(response.Result);
|
||||
Assert.AreEqual("some-string", response.Result.Str);
|
||||
Assert.AreEqual(123, response.Result.Int);
|
||||
|
||||
Assert.AreEqual(1, _httpHandlerMock.Callbacks.Count);
|
||||
|
||||
var callback = _httpHandlerMock.Callbacks.First();
|
||||
Assert.AreEqual(HttpMethod.Post, callback.Method);
|
||||
Assert.AreEqual("https://localhost/api/v4/test", callback.Url);
|
||||
Assert.AreEqual(@"{""test"":""HERE ?""}", callback.Content);
|
||||
|
||||
Assert.AreEqual(3, callback.Headers.Count);
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("Accept"));
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("Authorization"));
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("User-Agent"));
|
||||
|
||||
Assert.AreEqual("application/json", callback.Headers["Accept"]);
|
||||
Assert.AreEqual("Bearer Some-API-Token", callback.Headers["Authorization"]);
|
||||
Assert.AreEqual("AMWD.CloudflareClient/1.0.0", callback.Headers["User-Agent"]);
|
||||
|
||||
_httpHandlerMock.Mock.Protected().Verify("SendAsync", Times.Once(), ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>());
|
||||
|
||||
VerifyDefaults();
|
||||
VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(HttpStatusCode.Unauthorized)]
|
||||
[DataRow(HttpStatusCode.Forbidden)]
|
||||
public async Task ShouldThrowAuthenticationExceptionOnStatusCode(HttpStatusCode statusCode)
|
||||
{
|
||||
// Arrange
|
||||
_httpHandlerMock.Responses.Enqueue(new HttpResponseMessage
|
||||
{
|
||||
StatusCode = statusCode,
|
||||
Content = new StringContent(@"{""success"": false, ""errors"": [{ ""code"": ""4711"", ""message"": ""foo & baz."" }, { ""code"": ""4712"", ""message"": ""Happy Error!"" }], ""messages"": []}", Encoding.UTF8, MediaTypeNames.Application.Json),
|
||||
});
|
||||
|
||||
var client = GetClient();
|
||||
|
||||
try
|
||||
{
|
||||
// Act
|
||||
await client.PostAsync<object, object>("foo", _request);
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (AuthenticationException ex)
|
||||
{
|
||||
// Assert
|
||||
Assert.IsNull(ex.InnerException);
|
||||
Assert.AreEqual($"4711: foo & baz.{Environment.NewLine}4712: Happy Error!", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldReturnPlainText()
|
||||
{
|
||||
// Arrange
|
||||
_clientOptionsMock.Setup(o => o.DefaultQueryParams).Returns(new Dictionary<string, string> { { "bar", "08/15" } });
|
||||
_httpHandlerMock.Responses.Enqueue(new HttpResponseMessage
|
||||
{
|
||||
StatusCode = HttpStatusCode.OK,
|
||||
Content = new StringContent("This is an awesome text ;-)", Encoding.UTF8, MediaTypeNames.Text.Plain),
|
||||
});
|
||||
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
var response = await client.PostAsync<string, TestClass>("some-awesome-path", _request);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(response);
|
||||
Assert.IsTrue(response.Success);
|
||||
Assert.IsNotNull(response.Errors);
|
||||
Assert.IsNotNull(response.Messages);
|
||||
Assert.IsNotNull(response.ResultInfo);
|
||||
|
||||
Assert.AreEqual(0, response.Errors.Count);
|
||||
Assert.AreEqual(0, response.Messages.Count);
|
||||
|
||||
Assert.AreEqual(0, response.ResultInfo.Count);
|
||||
Assert.AreEqual(0, response.ResultInfo.Page);
|
||||
Assert.AreEqual(0, response.ResultInfo.PerPage);
|
||||
Assert.AreEqual(0, response.ResultInfo.TotalCount);
|
||||
|
||||
Assert.AreEqual("This is an awesome text ;-)", response.Result);
|
||||
|
||||
Assert.AreEqual(1, _httpHandlerMock.Callbacks.Count);
|
||||
|
||||
var callback = _httpHandlerMock.Callbacks.First();
|
||||
Assert.AreEqual(HttpMethod.Post, callback.Method);
|
||||
Assert.AreEqual("https://localhost/api/v4/some-awesome-path?bar=08%2F15", callback.Url);
|
||||
Assert.AreEqual(@"{""string"":""Happy Testing!"",""integer"":54321}", callback.Content);
|
||||
|
||||
Assert.AreEqual(3, callback.Headers.Count);
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("Accept"));
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("Authorization"));
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("User-Agent"));
|
||||
|
||||
Assert.AreEqual("application/json", callback.Headers["Accept"]);
|
||||
Assert.AreEqual("Bearer Some-API-Token", callback.Headers["Authorization"]);
|
||||
Assert.AreEqual("AMWD.CloudflareClient/1.0.0", callback.Headers["User-Agent"]);
|
||||
|
||||
_httpHandlerMock.Mock.Protected().Verify("SendAsync", Times.Once(), ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>());
|
||||
|
||||
_authenticationMock.Verify(m => m.AddHeader(It.IsAny<HttpClient>()), Times.Once);
|
||||
|
||||
_clientOptionsMock.Verify(o => o.BaseUrl, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.Timeout, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.MaxRetries, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.DefaultHeaders, Times.Once);
|
||||
_clientOptionsMock.Verify(o => o.DefaultQueryParams, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.AllowRedirects, Times.Once);
|
||||
_clientOptionsMock.Verify(o => o.UseProxy, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.Proxy, Times.Once);
|
||||
|
||||
VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(JsonReaderException))]
|
||||
public async Task ShouldThrowExceptionOnInvalidResponse()
|
||||
{
|
||||
// Arrange
|
||||
_httpHandlerMock.Responses.Enqueue(new HttpResponseMessage
|
||||
{
|
||||
StatusCode = HttpStatusCode.OK,
|
||||
Content = new StringContent("This is a bad text :p", Encoding.UTF8, MediaTypeNames.Text.Plain),
|
||||
});
|
||||
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
await client.PostAsync<TestClass, TestClass>("some-path", _request);
|
||||
}
|
||||
|
||||
private void VerifyDefaults()
|
||||
{
|
||||
_authenticationMock.Verify(m => m.AddHeader(It.IsAny<HttpClient>()), Times.Once);
|
||||
|
||||
_clientOptionsMock.Verify(o => o.BaseUrl, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.Timeout, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.MaxRetries, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.DefaultHeaders, Times.Once);
|
||||
_clientOptionsMock.Verify(o => o.DefaultQueryParams, Times.Once);
|
||||
_clientOptionsMock.Verify(o => o.AllowRedirects, Times.Once);
|
||||
_clientOptionsMock.Verify(o => o.UseProxy, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.Proxy, Times.Once);
|
||||
}
|
||||
|
||||
private void VerifyNoOtherCalls()
|
||||
{
|
||||
_httpHandlerMock.Mock.VerifyNoOtherCalls();
|
||||
_authenticationMock.VerifyNoOtherCalls();
|
||||
_clientOptionsMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
private ICloudflareClient GetClient()
|
||||
{
|
||||
var httpClient = new HttpClient(_httpHandlerMock.Mock.Object)
|
||||
{
|
||||
BaseAddress = new Uri(_baseUrl),
|
||||
Timeout = _clientOptionsMock.Object.Timeout,
|
||||
};
|
||||
|
||||
httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("AMWD.CloudflareClient", "1.0.0"));
|
||||
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
|
||||
if (_clientOptionsMock.Object.DefaultHeaders.Count > 0)
|
||||
{
|
||||
foreach (var headerKvp in _clientOptionsMock.Object.DefaultHeaders)
|
||||
httpClient.DefaultRequestHeaders.Add(headerKvp.Key, headerKvp.Value);
|
||||
}
|
||||
_authenticationMock.Object.AddHeader(httpClient);
|
||||
|
||||
_authenticationMock.Invocations.Clear();
|
||||
_clientOptionsMock.Invocations.Clear();
|
||||
|
||||
var client = new CloudflareClient(_authenticationMock.Object, _clientOptionsMock.Object);
|
||||
|
||||
var httpClientField = client.GetType()
|
||||
.GetField("_httpClient", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
(httpClientField.GetValue(client) as HttpClient).Dispose();
|
||||
httpClientField.SetValue(client, httpClient);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
private class TestClass
|
||||
{
|
||||
[JsonProperty("string")]
|
||||
public string Str { get; set; }
|
||||
|
||||
[JsonProperty("integer")]
|
||||
public int Int { get; set; }
|
||||
}
|
||||
|
||||
private class TestFilter : IQueryParameterFilter
|
||||
{
|
||||
public IDictionary<string, string> GetQueryParameters()
|
||||
{
|
||||
return new Dictionary<string, string>
|
||||
{
|
||||
{ "test", "filter-text" }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
386
Cloudflare.Tests/CloudflareClientTests/PutAsyncTest.cs
Normal file
386
Cloudflare.Tests/CloudflareClientTests/PutAsyncTest.cs
Normal file
@@ -0,0 +1,386 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Mime;
|
||||
using System.Reflection;
|
||||
using System.Security.Authentication;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AMWD.Net.Api.Cloudflare;
|
||||
using AMWD.Net.Api.Cloudflare.Auth;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
|
||||
namespace Cloudflare.Core.Tests.CloudflareClientTests
|
||||
{
|
||||
[TestClass]
|
||||
public class PutAsyncTest
|
||||
{
|
||||
private const string _baseUrl = "https://localhost/api/v4/";
|
||||
|
||||
private HttpMessageHandlerMock _httpHandlerMock;
|
||||
private Mock<ClientOptions> _clientOptionsMock;
|
||||
private Mock<IAuthentication> _authenticationMock;
|
||||
|
||||
private TestClass _request;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
_httpHandlerMock = new HttpMessageHandlerMock();
|
||||
_authenticationMock = new Mock<IAuthentication>();
|
||||
_clientOptionsMock = new Mock<ClientOptions>();
|
||||
|
||||
_authenticationMock
|
||||
.Setup(a => a.AddHeader(It.IsAny<HttpClient>()))
|
||||
.Callback<HttpClient>(c => c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "Some-API-Token"));
|
||||
|
||||
_clientOptionsMock.Setup(o => o.BaseUrl).Returns(_baseUrl);
|
||||
_clientOptionsMock.Setup(o => o.Timeout).Returns(TimeSpan.FromSeconds(60));
|
||||
_clientOptionsMock.Setup(o => o.MaxRetries).Returns(2);
|
||||
_clientOptionsMock.Setup(o => o.DefaultHeaders).Returns(new Dictionary<string, string>());
|
||||
_clientOptionsMock.Setup(o => o.DefaultQueryParams).Returns(new Dictionary<string, string>());
|
||||
_clientOptionsMock.Setup(o => o.AllowRedirects).Returns(false);
|
||||
_clientOptionsMock.Setup(o => o.UseProxy).Returns(false);
|
||||
|
||||
_request = new TestClass
|
||||
{
|
||||
Int = 54321,
|
||||
Str = "Happy Testing!"
|
||||
};
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ObjectDisposedException))]
|
||||
public async Task ShouldThrowDisposed()
|
||||
{
|
||||
// Arrange
|
||||
var client = GetClient() as CloudflareClient;
|
||||
client.Dispose();
|
||||
|
||||
// Act
|
||||
await client.PutAsync<object, object>("test", _request);
|
||||
|
||||
// Assert - ObjectDisposedException
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(null)]
|
||||
[DataRow("")]
|
||||
[DataRow(" ")]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public async Task ShouldThrowArgumentNullOnRequestPath(string path)
|
||||
{
|
||||
// Arrange
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
await client.PutAsync<object, object>(path, _request);
|
||||
|
||||
// Assert - ArgumentNullException
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public async Task ShouldThrowArgumentOnRequestPath()
|
||||
{
|
||||
// Arrange
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
await client.PutAsync<object, object>("foo?bar=baz", _request);
|
||||
|
||||
// Assert - ArgumentException
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldPut()
|
||||
{
|
||||
// Arrange
|
||||
_httpHandlerMock.Responses.Enqueue(new HttpResponseMessage
|
||||
{
|
||||
StatusCode = HttpStatusCode.OK,
|
||||
Content = new StringContent(@"{""success"": true, ""errors"": [], ""messages"": [], ""result"": { ""string"": ""some-string"", ""integer"": 123 }}", Encoding.UTF8, MediaTypeNames.Application.Json),
|
||||
});
|
||||
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
var response = await client.PutAsync<TestClass, TestClass>("test", _request);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(response);
|
||||
Assert.IsTrue(response.Success);
|
||||
Assert.IsNotNull(response.Errors);
|
||||
Assert.IsNotNull(response.Messages);
|
||||
Assert.IsNull(response.ResultInfo);
|
||||
|
||||
Assert.AreEqual(0, response.Errors.Count);
|
||||
Assert.AreEqual(0, response.Messages.Count);
|
||||
|
||||
Assert.IsNotNull(response.Result);
|
||||
Assert.AreEqual("some-string", response.Result.Str);
|
||||
Assert.AreEqual(123, response.Result.Int);
|
||||
|
||||
Assert.AreEqual(1, _httpHandlerMock.Callbacks.Count);
|
||||
|
||||
var callback = _httpHandlerMock.Callbacks.First();
|
||||
Assert.AreEqual(HttpMethod.Put, callback.Method);
|
||||
Assert.AreEqual("https://localhost/api/v4/test", callback.Url);
|
||||
Assert.AreEqual(@"{""string"":""Happy Testing!"",""integer"":54321}", callback.Content);
|
||||
|
||||
Assert.AreEqual(3, callback.Headers.Count);
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("Accept"));
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("Authorization"));
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("User-Agent"));
|
||||
|
||||
Assert.AreEqual("application/json", callback.Headers["Accept"]);
|
||||
Assert.AreEqual("Bearer Some-API-Token", callback.Headers["Authorization"]);
|
||||
Assert.AreEqual("AMWD.CloudflareClient/1.0.0", callback.Headers["User-Agent"]);
|
||||
|
||||
_httpHandlerMock.Mock.Protected().Verify("SendAsync", Times.Once(), ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>());
|
||||
|
||||
VerifyDefaults();
|
||||
VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldPutHttpContentDirectly()
|
||||
{
|
||||
// Arrange
|
||||
var stringContent = new StringContent(@"{""test"":""HERE ?""}", Encoding.UTF8, "application/json");
|
||||
_httpHandlerMock.Responses.Enqueue(new HttpResponseMessage
|
||||
{
|
||||
StatusCode = HttpStatusCode.OK,
|
||||
Content = new StringContent(@"{""success"": true, ""errors"": [], ""messages"": [], ""result"": { ""string"": ""some-string"", ""integer"": 123 }}", Encoding.UTF8, MediaTypeNames.Application.Json),
|
||||
});
|
||||
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
var response = await client.PutAsync<TestClass, StringContent>("test", stringContent);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(response);
|
||||
Assert.IsTrue(response.Success);
|
||||
Assert.IsNotNull(response.Errors);
|
||||
Assert.IsNotNull(response.Messages);
|
||||
Assert.IsNull(response.ResultInfo);
|
||||
|
||||
Assert.AreEqual(0, response.Errors.Count);
|
||||
Assert.AreEqual(0, response.Messages.Count);
|
||||
|
||||
Assert.IsNotNull(response.Result);
|
||||
Assert.AreEqual("some-string", response.Result.Str);
|
||||
Assert.AreEqual(123, response.Result.Int);
|
||||
|
||||
Assert.AreEqual(1, _httpHandlerMock.Callbacks.Count);
|
||||
|
||||
var callback = _httpHandlerMock.Callbacks.First();
|
||||
Assert.AreEqual(HttpMethod.Put, callback.Method);
|
||||
Assert.AreEqual("https://localhost/api/v4/test", callback.Url);
|
||||
Assert.AreEqual(@"{""test"":""HERE ?""}", callback.Content);
|
||||
|
||||
Assert.AreEqual(3, callback.Headers.Count);
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("Accept"));
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("Authorization"));
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("User-Agent"));
|
||||
|
||||
Assert.AreEqual("application/json", callback.Headers["Accept"]);
|
||||
Assert.AreEqual("Bearer Some-API-Token", callback.Headers["Authorization"]);
|
||||
Assert.AreEqual("AMWD.CloudflareClient/1.0.0", callback.Headers["User-Agent"]);
|
||||
|
||||
_httpHandlerMock.Mock.Protected().Verify("SendAsync", Times.Once(), ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>());
|
||||
|
||||
VerifyDefaults();
|
||||
VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(HttpStatusCode.Unauthorized)]
|
||||
[DataRow(HttpStatusCode.Forbidden)]
|
||||
public async Task ShouldThrowAuthenticationExceptionOnStatusCode(HttpStatusCode statusCode)
|
||||
{
|
||||
// Arrange
|
||||
_httpHandlerMock.Responses.Enqueue(new HttpResponseMessage
|
||||
{
|
||||
StatusCode = statusCode,
|
||||
Content = new StringContent(@"{""success"": false, ""errors"": [{ ""code"": ""4711"", ""message"": ""foo & baz."" }, { ""code"": ""4712"", ""message"": ""Happy Error!"" }], ""messages"": []}", Encoding.UTF8, MediaTypeNames.Application.Json),
|
||||
});
|
||||
|
||||
var client = GetClient();
|
||||
|
||||
try
|
||||
{
|
||||
// Act
|
||||
await client.PutAsync<object, object>("foo", _request);
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (AuthenticationException ex)
|
||||
{
|
||||
// Assert
|
||||
Assert.IsNull(ex.InnerException);
|
||||
Assert.AreEqual($"4711: foo & baz.{Environment.NewLine}4712: Happy Error!", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldReturnPlainText()
|
||||
{
|
||||
// Arrange
|
||||
_clientOptionsMock.Setup(o => o.DefaultQueryParams).Returns(new Dictionary<string, string> { { "bar", "08/15" } });
|
||||
_httpHandlerMock.Responses.Enqueue(new HttpResponseMessage
|
||||
{
|
||||
StatusCode = HttpStatusCode.OK,
|
||||
Content = new StringContent("This is an awesome text ;-)", Encoding.UTF8, MediaTypeNames.Text.Plain),
|
||||
});
|
||||
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
var response = await client.PutAsync<string, TestClass>("some-awesome-path", _request);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(response);
|
||||
Assert.IsTrue(response.Success);
|
||||
Assert.IsNotNull(response.Errors);
|
||||
Assert.IsNotNull(response.Messages);
|
||||
Assert.IsNotNull(response.ResultInfo);
|
||||
|
||||
Assert.AreEqual(0, response.Errors.Count);
|
||||
Assert.AreEqual(0, response.Messages.Count);
|
||||
|
||||
Assert.AreEqual(0, response.ResultInfo.Count);
|
||||
Assert.AreEqual(0, response.ResultInfo.Page);
|
||||
Assert.AreEqual(0, response.ResultInfo.PerPage);
|
||||
Assert.AreEqual(0, response.ResultInfo.TotalCount);
|
||||
|
||||
Assert.AreEqual("This is an awesome text ;-)", response.Result);
|
||||
|
||||
Assert.AreEqual(1, _httpHandlerMock.Callbacks.Count);
|
||||
|
||||
var callback = _httpHandlerMock.Callbacks.First();
|
||||
Assert.AreEqual(HttpMethod.Put, callback.Method);
|
||||
Assert.AreEqual("https://localhost/api/v4/some-awesome-path?bar=08%2F15", callback.Url);
|
||||
Assert.AreEqual(@"{""string"":""Happy Testing!"",""integer"":54321}", callback.Content);
|
||||
|
||||
Assert.AreEqual(3, callback.Headers.Count);
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("Accept"));
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("Authorization"));
|
||||
Assert.IsTrue(callback.Headers.ContainsKey("User-Agent"));
|
||||
|
||||
Assert.AreEqual("application/json", callback.Headers["Accept"]);
|
||||
Assert.AreEqual("Bearer Some-API-Token", callback.Headers["Authorization"]);
|
||||
Assert.AreEqual("AMWD.CloudflareClient/1.0.0", callback.Headers["User-Agent"]);
|
||||
|
||||
_httpHandlerMock.Mock.Protected().Verify("SendAsync", Times.Once(), ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>());
|
||||
|
||||
_authenticationMock.Verify(m => m.AddHeader(It.IsAny<HttpClient>()), Times.Once);
|
||||
|
||||
_clientOptionsMock.Verify(o => o.BaseUrl, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.Timeout, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.MaxRetries, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.DefaultHeaders, Times.Once);
|
||||
_clientOptionsMock.Verify(o => o.DefaultQueryParams, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.AllowRedirects, Times.Once);
|
||||
_clientOptionsMock.Verify(o => o.UseProxy, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.Proxy, Times.Once);
|
||||
|
||||
VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(JsonReaderException))]
|
||||
public async Task ShouldThrowExceptionOnInvalidResponse()
|
||||
{
|
||||
// Arrange
|
||||
_httpHandlerMock.Responses.Enqueue(new HttpResponseMessage
|
||||
{
|
||||
StatusCode = HttpStatusCode.OK,
|
||||
Content = new StringContent("This is a bad text :p", Encoding.UTF8, MediaTypeNames.Text.Plain),
|
||||
});
|
||||
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
await client.PutAsync<TestClass, TestClass>("some-path", _request);
|
||||
}
|
||||
|
||||
private void VerifyDefaults()
|
||||
{
|
||||
_authenticationMock.Verify(m => m.AddHeader(It.IsAny<HttpClient>()), Times.Once);
|
||||
|
||||
_clientOptionsMock.Verify(o => o.BaseUrl, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.Timeout, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.MaxRetries, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.DefaultHeaders, Times.Once);
|
||||
_clientOptionsMock.Verify(o => o.DefaultQueryParams, Times.Once);
|
||||
_clientOptionsMock.Verify(o => o.AllowRedirects, Times.Once);
|
||||
_clientOptionsMock.Verify(o => o.UseProxy, Times.Exactly(2));
|
||||
_clientOptionsMock.Verify(o => o.Proxy, Times.Once);
|
||||
}
|
||||
|
||||
private void VerifyNoOtherCalls()
|
||||
{
|
||||
_httpHandlerMock.Mock.VerifyNoOtherCalls();
|
||||
_authenticationMock.VerifyNoOtherCalls();
|
||||
_clientOptionsMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
private ICloudflareClient GetClient()
|
||||
{
|
||||
var httpClient = new HttpClient(_httpHandlerMock.Mock.Object)
|
||||
{
|
||||
BaseAddress = new Uri(_baseUrl),
|
||||
Timeout = _clientOptionsMock.Object.Timeout,
|
||||
};
|
||||
|
||||
httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("AMWD.CloudflareClient", "1.0.0"));
|
||||
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
|
||||
if (_clientOptionsMock.Object.DefaultHeaders.Count > 0)
|
||||
{
|
||||
foreach (var headerKvp in _clientOptionsMock.Object.DefaultHeaders)
|
||||
httpClient.DefaultRequestHeaders.Add(headerKvp.Key, headerKvp.Value);
|
||||
}
|
||||
_authenticationMock.Object.AddHeader(httpClient);
|
||||
|
||||
_authenticationMock.Invocations.Clear();
|
||||
_clientOptionsMock.Invocations.Clear();
|
||||
|
||||
var client = new CloudflareClient(_authenticationMock.Object, _clientOptionsMock.Object);
|
||||
|
||||
var httpClientField = client.GetType()
|
||||
.GetField("_httpClient", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
(httpClientField.GetValue(client) as HttpClient).Dispose();
|
||||
httpClientField.SetValue(client, httpClient);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
private class TestClass
|
||||
{
|
||||
[JsonProperty("string")]
|
||||
public string Str { get; set; }
|
||||
|
||||
[JsonProperty("integer")]
|
||||
public int Int { get; set; }
|
||||
}
|
||||
|
||||
private class TestFilter : IQueryParameterFilter
|
||||
{
|
||||
public IDictionary<string, string> GetQueryParameters()
|
||||
{
|
||||
return new Dictionary<string, string>
|
||||
{
|
||||
{ "test", "filter-text" }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
64
Cloudflare.Tests/Extensions/EnumExtensionsTest.cs
Normal file
64
Cloudflare.Tests/Extensions/EnumExtensionsTest.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using System.Runtime.Serialization;
|
||||
using AMWD.Net.Api.Cloudflare;
|
||||
|
||||
namespace Cloudflare.Core.Tests.Extensions
|
||||
{
|
||||
[TestClass]
|
||||
public class EnumExtensionsTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void ShouldReturnEnumMemberValue()
|
||||
{
|
||||
// Arrange
|
||||
var enumValue = EnumWithAttribute.One;
|
||||
|
||||
// Act
|
||||
string val = enumValue.GetEnumMemberValue();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual("eins", val);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnStringMissingAttribute()
|
||||
{
|
||||
// Arrange
|
||||
var enumValue = EnumWithoutAttribute.Two;
|
||||
|
||||
// Act
|
||||
string val = enumValue.GetEnumMemberValue();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual("Two", val);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnString()
|
||||
{
|
||||
// Arrange
|
||||
EnumWithAttribute enumValue = 0;
|
||||
|
||||
// Act
|
||||
string val = enumValue.GetEnumMemberValue();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual("0", val);
|
||||
}
|
||||
|
||||
public enum EnumWithAttribute
|
||||
{
|
||||
[EnumMember(Value = "eins")]
|
||||
One = 1,
|
||||
|
||||
[EnumMember(Value = "zwei")]
|
||||
Two = 2,
|
||||
}
|
||||
|
||||
public enum EnumWithoutAttribute
|
||||
{
|
||||
One = 1,
|
||||
|
||||
Two = 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
52
Cloudflare.Tests/MessageHandlerMock.cs
Normal file
52
Cloudflare.Tests/MessageHandlerMock.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
|
||||
namespace Cloudflare.Core.Tests
|
||||
{
|
||||
internal class HttpMessageHandlerMock
|
||||
{
|
||||
public HttpMessageHandlerMock()
|
||||
{
|
||||
Mock = new();
|
||||
Mock.Protected()
|
||||
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
|
||||
.Callback<HttpRequestMessage, CancellationToken>(async (request, ct) =>
|
||||
{
|
||||
var callback = new HttpMessageRequestCallback
|
||||
{
|
||||
Method = request.Method,
|
||||
Url = request.RequestUri.ToString(),
|
||||
Headers = request.Headers.ToDictionary(h => h.Key, h => h.Value.First()),
|
||||
};
|
||||
|
||||
if (request.Content != null)
|
||||
callback.Content = await request.Content.ReadAsStringAsync();
|
||||
|
||||
Callbacks.Add(callback);
|
||||
})
|
||||
.ReturnsAsync(() => Responses.Dequeue());
|
||||
}
|
||||
|
||||
public List<HttpMessageRequestCallback> Callbacks { get; } = [];
|
||||
|
||||
public Queue<HttpResponseMessage> Responses { get; } = new();
|
||||
|
||||
public Mock<HttpClientHandler> Mock { get; }
|
||||
}
|
||||
|
||||
internal class HttpMessageRequestCallback
|
||||
{
|
||||
public HttpMethod Method { get; set; }
|
||||
|
||||
public string Url { get; set; }
|
||||
|
||||
public Dictionary<string, string> Headers { get; set; }
|
||||
|
||||
public string Content { get; set; }
|
||||
}
|
||||
}
|
||||
43
Cloudflare/Auth/ApiKeyAuthentication.cs
Normal file
43
Cloudflare/Auth/ApiKeyAuthentication.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace AMWD.Net.Api.Cloudflare.Auth
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements the interface to authenticate using an API key and email address.
|
||||
/// </summary>
|
||||
public class ApiKeyAuthentication : IAuthentication
|
||||
{
|
||||
private static readonly Regex _emailCheckRegex = new(@"^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$", RegexOptions.Compiled);
|
||||
private readonly string _emailAddress;
|
||||
private readonly string _apiKey;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ApiKeyAuthentication"/> class.
|
||||
/// </summary>
|
||||
/// <param name="emailAddress">The email address.</param>
|
||||
/// <param name="apiKey">The global API key.</param>
|
||||
public ApiKeyAuthentication(string emailAddress, string apiKey)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(emailAddress))
|
||||
throw new ArgumentNullException(nameof(emailAddress));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(apiKey))
|
||||
throw new ArgumentNullException(nameof(apiKey));
|
||||
|
||||
if (!_emailCheckRegex.IsMatch(emailAddress))
|
||||
throw new ArgumentException("Invalid email address", nameof(emailAddress));
|
||||
|
||||
_emailAddress = emailAddress;
|
||||
_apiKey = apiKey;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddHeader(HttpClient httpClient)
|
||||
{
|
||||
httpClient.DefaultRequestHeaders.Add("X-Auth-Email", _emailAddress);
|
||||
httpClient.DefaultRequestHeaders.Add("X-Auth-Key", _apiKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
32
Cloudflare/Auth/ApiTokenAuthentication.cs
Normal file
32
Cloudflare/Auth/ApiTokenAuthentication.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
|
||||
namespace AMWD.Net.Api.Cloudflare.Auth
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements the interface to authenticate using an API token.
|
||||
/// </summary>
|
||||
public class ApiTokenAuthentication : IAuthentication
|
||||
{
|
||||
private readonly string _apiToken;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ApiTokenAuthentication"/> class.
|
||||
/// </summary>
|
||||
/// <param name="apiToken">The API token.</param>
|
||||
public ApiTokenAuthentication(string apiToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(apiToken))
|
||||
throw new ArgumentNullException(nameof(apiToken));
|
||||
|
||||
_apiToken = apiToken;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddHeader(HttpClient httpClient)
|
||||
{
|
||||
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _apiToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
55
Cloudflare/ClientOptions.cs
Normal file
55
Cloudflare/ClientOptions.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
|
||||
namespace AMWD.Net.Api.Cloudflare
|
||||
{
|
||||
/// <summary>
|
||||
/// Options for the Cloudflare API.
|
||||
/// </summary>
|
||||
public class ClientOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the default base url for the API.
|
||||
/// </summary>
|
||||
public virtual string BaseUrl { get; set; } = "https://api.cloudflare.com/client/v4/";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default timeout for the API.
|
||||
/// </summary>
|
||||
public virtual TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(60);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum number of retries for the API.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The API may respond with an 5xx error and a X-Should-Retry header indicating that the request should be retried.
|
||||
/// </remarks>
|
||||
public virtual int MaxRetries { get; set; } = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets additional default headers to every request.
|
||||
/// </summary>
|
||||
public virtual IDictionary<string, string> DefaultHeaders { get; set; } = new Dictionary<string, string>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets additional default query parameters to every request.
|
||||
/// </summary>
|
||||
public virtual IDictionary<string, string> DefaultQueryParams { get; set; } = new Dictionary<string, string>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to allow redirects.
|
||||
/// </summary>
|
||||
public virtual bool AllowRedirects { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to use a proxy.
|
||||
/// </summary>
|
||||
public virtual bool UseProxy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the proxy information.
|
||||
/// </summary>
|
||||
public virtual IWebProxy Proxy { get; set; }
|
||||
}
|
||||
}
|
||||
62
Cloudflare/Cloudflare.csproj
Normal file
62
Cloudflare/Cloudflare.csproj
Normal file
@@ -0,0 +1,62 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
|
||||
|
||||
<NrtRevisionFormat>{semvertag:main}{!:-dev}</NrtRevisionFormat>
|
||||
|
||||
<AppendTargetFrameworkToOutputPath>true</AppendTargetFrameworkToOutputPath>
|
||||
<CopyRefAssembliesToPublishDirectory>false</CopyRefAssembliesToPublishDirectory>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<RepositoryUrl>https://github.com/AM-WD/cloudflare-api.git</RepositoryUrl>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
|
||||
<PackageIcon>package-icon.png</PackageIcon>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<PackageTags>cloudflare api</PackageTags>
|
||||
<PackageProjectUrl>https://developers.cloudflare.com/api</PackageProjectUrl>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<EmbedUntrackedSources>false</EmbedUntrackedSources>
|
||||
|
||||
<PackageId>AMWD.Net.API.Cloudflare</PackageId>
|
||||
<AssemblyName>amwd-cloudflare-core</AssemblyName>
|
||||
<RootNamespace>AMWD.Net.Api.Cloudflare</RootNamespace>
|
||||
|
||||
<Product>Cloudflare API - Core</Product>
|
||||
<Description>Core features of the Cloudflare API</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('$(CI_COMMIT_TAG)', '^v[0-9.]+'))">
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(GITLAB_CI)' == 'true'">
|
||||
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="'$(GITLAB_CI)' == 'true'">
|
||||
<SourceLinkGitLabHost Include="$(CI_SERVER_HOST)" Version="$(CI_SERVER_VERSION)" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitLab" Version="8.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="$(SolutionDir)/package-icon.png" Pack="true" PackagePath="/" />
|
||||
<None Include="README.md" Pack="true" PackagePath="/" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AMWD.NetRevisionTask" Version="1.2.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
324
Cloudflare/CloudflareClient.cs
Normal file
324
Cloudflare/CloudflareClient.cs
Normal file
@@ -0,0 +1,324 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Reflection;
|
||||
using System.Security.Authentication;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AMWD.Net.Api.Cloudflare.Auth;
|
||||
|
||||
namespace AMWD.Net.Api.Cloudflare
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements the Core of the Cloudflare API client.
|
||||
/// </summary>
|
||||
public partial class CloudflareClient : ICloudflareClient, IDisposable
|
||||
{
|
||||
private static readonly JsonSerializerSettings _jsonSerializerSettings = new JsonSerializerSettings
|
||||
{
|
||||
Culture = CultureInfo.InvariantCulture,
|
||||
Formatting = Formatting.None,
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
};
|
||||
|
||||
private readonly ClientOptions _clientOptions;
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
private bool _isDisposed = false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CloudflareClient"/> class.
|
||||
/// </summary>
|
||||
/// <param name="emailAddress">The email address of the Cloudflare account.</param>
|
||||
/// <param name="apiKey">The API key of the Cloudflare account.</param>
|
||||
/// <param name="clientOptions">The client options (optional).</param>
|
||||
public CloudflareClient(string emailAddress, string apiKey, ClientOptions clientOptions = null)
|
||||
: this(new ApiKeyAuthentication(emailAddress, apiKey), clientOptions)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CloudflareClient"/> class.
|
||||
/// </summary>
|
||||
/// <param name="apiToken">The API token.</param>
|
||||
/// <param name="clientOptions">The client options (optional).</param>
|
||||
public CloudflareClient(string apiToken, ClientOptions clientOptions = null)
|
||||
: this(new ApiTokenAuthentication(apiToken), clientOptions)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CloudflareClient"/> class.
|
||||
/// </summary>
|
||||
/// <param name="authentication">The authentication information.</param>
|
||||
/// <param name="clientOptions">The client options (optional).</param>
|
||||
public CloudflareClient(IAuthentication authentication, ClientOptions clientOptions = null)
|
||||
{
|
||||
if (authentication == null)
|
||||
throw new ArgumentNullException(nameof(authentication));
|
||||
|
||||
_clientOptions = clientOptions ?? new ClientOptions();
|
||||
ValidateClientOptions();
|
||||
|
||||
_httpClient = CreateHttpClient();
|
||||
authentication.AddHeader(_httpClient);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of the resources used by the <see cref="CloudflareClient"/> object.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDisposed)
|
||||
return;
|
||||
|
||||
_isDisposed = true;
|
||||
|
||||
_httpClient.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<CloudflareResponse<TResponse>> GetAsync<TResponse>(string requestPath, IQueryParameterFilter queryFilter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ValidateRequestPath(requestPath);
|
||||
|
||||
string requestUrl = BuildRequestUrl(requestPath, queryFilter);
|
||||
|
||||
var response = await _httpClient.GetAsync(requestUrl, cancellationToken).ConfigureAwait(false);
|
||||
return await GetCloudflareResponse<TResponse>(response, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<CloudflareResponse<TResponse>> PostAsync<TResponse, TRequest>(string requestPath, TRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ValidateRequestPath(requestPath);
|
||||
|
||||
string requestUrl = BuildRequestUrl(requestPath);
|
||||
|
||||
HttpContent httpRequestContent;
|
||||
if (request is HttpContent httpContent)
|
||||
{
|
||||
httpRequestContent = httpContent;
|
||||
}
|
||||
else
|
||||
{
|
||||
string json = JsonConvert.SerializeObject(request, _jsonSerializerSettings);
|
||||
httpRequestContent = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
}
|
||||
|
||||
var response = await _httpClient.PostAsync(requestUrl, httpRequestContent, cancellationToken).ConfigureAwait(false);
|
||||
return await GetCloudflareResponse<TResponse>(response, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<CloudflareResponse<TResponse>> PutAsync<TResponse, TRequest>(string requestPath, TRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ValidateRequestPath(requestPath);
|
||||
|
||||
string requestUrl = BuildRequestUrl(requestPath);
|
||||
|
||||
HttpContent httpRequestContent;
|
||||
if (request is HttpContent httpContent)
|
||||
{
|
||||
httpRequestContent = httpContent;
|
||||
}
|
||||
else
|
||||
{
|
||||
string json = JsonConvert.SerializeObject(request, _jsonSerializerSettings);
|
||||
httpRequestContent = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
}
|
||||
|
||||
var response = await _httpClient.PutAsync(requestUrl, httpRequestContent, cancellationToken).ConfigureAwait(false);
|
||||
return await GetCloudflareResponse<TResponse>(response, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<CloudflareResponse<TResponse>> DeleteAsync<TResponse>(string requestPath, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ValidateRequestPath(requestPath);
|
||||
|
||||
string requestUrl = BuildRequestUrl(requestPath);
|
||||
|
||||
var response = await _httpClient.DeleteAsync(requestUrl, cancellationToken).ConfigureAwait(false);
|
||||
return await GetCloudflareResponse<TResponse>(response, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<CloudflareResponse<TResponse>> PatchAsync<TResponse, TRequest>(string requestPath, TRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ValidateRequestPath(requestPath);
|
||||
|
||||
string requestUrl = BuildRequestUrl(requestPath);
|
||||
|
||||
HttpContent httpRequestContent;
|
||||
if (request is HttpContent httpContent)
|
||||
{
|
||||
httpRequestContent = httpContent;
|
||||
}
|
||||
else
|
||||
{
|
||||
string json = JsonConvert.SerializeObject(request, _jsonSerializerSettings);
|
||||
httpRequestContent = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
}
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
var response = await _httpClient.PatchAsync(requestUrl, httpRequestContent, cancellationToken).ConfigureAwait(false);
|
||||
#else
|
||||
var httpRequestMessage = new HttpRequestMessage
|
||||
{
|
||||
Version = HttpVersion.Version11,
|
||||
Method = new HttpMethod("PATCH"),
|
||||
RequestUri = new Uri(requestUrl),
|
||||
Content = httpRequestContent,
|
||||
};
|
||||
var response = await _httpClient.SendAsync(httpRequestMessage, cancellationToken).ConfigureAwait(false);
|
||||
#endif
|
||||
|
||||
return await GetCloudflareResponse<TResponse>(response, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_isDisposed)
|
||||
throw new ObjectDisposedException(GetType().FullName);
|
||||
}
|
||||
|
||||
private void ValidateClientOptions()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_clientOptions.BaseUrl))
|
||||
throw new ArgumentNullException(nameof(_clientOptions.BaseUrl));
|
||||
|
||||
if (_clientOptions.Timeout <= TimeSpan.Zero)
|
||||
throw new ArgumentOutOfRangeException(nameof(_clientOptions.Timeout), "Timeout must be positive.");
|
||||
|
||||
if (_clientOptions.MaxRetries < 0 || 10 < _clientOptions.MaxRetries)
|
||||
throw new ArgumentOutOfRangeException(nameof(_clientOptions.MaxRetries), "MaxRetries should be between 0 and 10.");
|
||||
|
||||
if (_clientOptions.UseProxy && _clientOptions.Proxy == null)
|
||||
throw new ArgumentNullException(nameof(_clientOptions.Proxy));
|
||||
}
|
||||
|
||||
private void ValidateRequestPath(string requestPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(requestPath))
|
||||
throw new ArgumentNullException(nameof(requestPath));
|
||||
|
||||
if (requestPath.Contains("?"))
|
||||
throw new ArgumentException("Query parameters are not allowed", nameof(requestPath));
|
||||
}
|
||||
|
||||
private HttpClient CreateHttpClient()
|
||||
{
|
||||
string version = typeof(CloudflareClient).Assembly
|
||||
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
|
||||
.InformationalVersion;
|
||||
|
||||
HttpMessageHandler handler;
|
||||
try
|
||||
{
|
||||
handler = new HttpClientHandler
|
||||
{
|
||||
AllowAutoRedirect = _clientOptions.AllowRedirects,
|
||||
UseProxy = _clientOptions.UseProxy,
|
||||
Proxy = _clientOptions.Proxy,
|
||||
};
|
||||
}
|
||||
catch (PlatformNotSupportedException)
|
||||
{
|
||||
handler = new HttpClientHandler
|
||||
{
|
||||
AllowAutoRedirect = _clientOptions.AllowRedirects,
|
||||
};
|
||||
}
|
||||
|
||||
var client = new HttpClient(handler, true)
|
||||
{
|
||||
BaseAddress = new Uri(_clientOptions.BaseUrl),
|
||||
Timeout = _clientOptions.Timeout,
|
||||
};
|
||||
|
||||
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("AMWD.CloudflareClient", version));
|
||||
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
|
||||
if (_clientOptions.DefaultHeaders.Count > 0)
|
||||
{
|
||||
foreach (var headerKvp in _clientOptions.DefaultHeaders)
|
||||
client.DefaultRequestHeaders.Add(headerKvp.Key, headerKvp.Value);
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
private async Task<CloudflareResponse<TRes>> GetCloudflareResponse<TRes>(HttpResponseMessage response, CancellationToken cancellationToken)
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
string content = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||
#else
|
||||
string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
#endif
|
||||
switch (response.StatusCode)
|
||||
{
|
||||
case HttpStatusCode.Forbidden:
|
||||
case HttpStatusCode.Unauthorized:
|
||||
var errorResponse = JsonConvert.DeserializeObject<CloudflareResponse<object>>(content);
|
||||
throw new AuthenticationException(string.Join(Environment.NewLine, errorResponse.Errors.Select(e => $"{e.Code}: {e.Message}")));
|
||||
|
||||
default:
|
||||
try
|
||||
{
|
||||
return JsonConvert.DeserializeObject<CloudflareResponse<TRes>>(content);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (typeof(TRes) == typeof(string))
|
||||
{
|
||||
object cObj = content.Replace("\\n", Environment.NewLine);
|
||||
return new CloudflareResponse<TRes>
|
||||
{
|
||||
Success = true,
|
||||
ResultInfo = new ResultInfo(),
|
||||
Result = (TRes)cObj,
|
||||
};
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string BuildRequestUrl(string requestPath, IQueryParameterFilter queryFilter = null)
|
||||
{
|
||||
var dict = new Dictionary<string, string>();
|
||||
|
||||
if (_clientOptions.DefaultQueryParams.Count > 0)
|
||||
{
|
||||
foreach (var paramKvp in _clientOptions.DefaultQueryParams)
|
||||
dict[paramKvp.Key] = paramKvp.Value;
|
||||
}
|
||||
|
||||
var queryParams = queryFilter?.GetQueryParameters();
|
||||
if (queryParams?.Count > 0)
|
||||
{
|
||||
foreach (var kvp in queryParams)
|
||||
dict[kvp.Key] = kvp.Value;
|
||||
}
|
||||
|
||||
if (dict.Count == 0)
|
||||
return requestPath;
|
||||
|
||||
string[] param = dict.Select(kvp => $"{kvp.Key}={WebUtility.UrlEncode(kvp.Value)}").ToArray();
|
||||
string query = string.Join("&", param);
|
||||
|
||||
return $"{requestPath}?{query}";
|
||||
}
|
||||
}
|
||||
}
|
||||
24
Cloudflare/Enums/FilterMatchType.cs
Normal file
24
Cloudflare/Enums/FilterMatchType.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace AMWD.Net.Api.Cloudflare
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether to match all search requirements or at least one (any).
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public enum FilterMatchType
|
||||
{
|
||||
/// <summary>
|
||||
/// Match all search requirements.
|
||||
/// </summary>
|
||||
[EnumMember(Value = "all")]
|
||||
All = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Match at least one search requirement.
|
||||
/// </summary>
|
||||
[EnumMember(Value = "any")]
|
||||
Any = 2,
|
||||
}
|
||||
}
|
||||
24
Cloudflare/Enums/OrderDirection.cs
Normal file
24
Cloudflare/Enums/OrderDirection.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace AMWD.Net.Api.Cloudflare
|
||||
{
|
||||
/// <summary>
|
||||
/// The direction to order the entity.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public enum OrderDirection
|
||||
{
|
||||
/// <summary>
|
||||
/// Order in ascending order.
|
||||
/// </summary>
|
||||
[EnumMember(Value = "asc")]
|
||||
Asc = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Order in descending order.
|
||||
/// </summary>
|
||||
[EnumMember(Value = "desc")]
|
||||
Desc = 2
|
||||
}
|
||||
}
|
||||
61
Cloudflare/Exceptions/CloudflareException.cs
Normal file
61
Cloudflare/Exceptions/CloudflareException.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace AMWD.Net.Api.Cloudflare
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents errors that occur during Cloudflare API calls.
|
||||
/// </summary>
|
||||
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||
public class CloudflareException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CloudflareException"/> class.
|
||||
/// </summary>
|
||||
public CloudflareException()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CloudflareException"/> class with a specified error
|
||||
/// message.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
public CloudflareException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CloudflareException"/> class with a specified error
|
||||
/// message and a reference to the inner exception that is the cause of this exception.
|
||||
/// </summary>
|
||||
/// <param name="message">The error message that explains the reason for the exception.</param>
|
||||
/// <param name="innerException">
|
||||
/// The exception that is the cause of the current exception, or a <see langword="null"/> reference
|
||||
/// if no inner exception is specified.
|
||||
/// </param>
|
||||
public CloudflareException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CloudflareException"/> class with serialized data.
|
||||
/// </summary>
|
||||
/// <param name="info">
|
||||
/// The <see cref="SerializationInfo"/> that holds the serialized
|
||||
/// object data about the exception being thrown.
|
||||
/// </param>
|
||||
/// <param name="context">
|
||||
/// The <see cref="StreamingContext"/> that contains contextual information
|
||||
/// about the source or destination.
|
||||
/// </param>
|
||||
/// <exception cref="ArgumentNullException">The info parameter is null.</exception>
|
||||
/// <exception cref="SerializationException">The class name is <see langword="null"/> or <see cref="Exception.HResult"/> is zero (0).</exception>
|
||||
protected CloudflareException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
33
Cloudflare/Extensions/EnumExtensions.cs
Normal file
33
Cloudflare/Extensions/EnumExtensions.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace AMWD.Net.Api.Cloudflare
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="Enum"/>s.
|
||||
/// </summary>
|
||||
public static class EnumExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="EnumMemberAttribute.Value"/> of the <see cref="Enum"/> when available, otherwise the <see cref="Enum.ToString()"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The enum value.</param>
|
||||
public static string GetEnumMemberValue(this Enum value)
|
||||
{
|
||||
var fieldInfo = value.GetType().GetField(value.ToString());
|
||||
if (fieldInfo == null)
|
||||
return value.ToString();
|
||||
|
||||
var enumMember = fieldInfo
|
||||
.GetCustomAttributes(typeof(EnumMemberAttribute), inherit: false)
|
||||
.Cast<EnumMemberAttribute>()
|
||||
.FirstOrDefault();
|
||||
|
||||
if (enumMember == null)
|
||||
return value.ToString();
|
||||
|
||||
return enumMember.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Cloudflare/Interfaces/IAuthentication.cs
Normal file
16
Cloudflare/Interfaces/IAuthentication.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Net.Http;
|
||||
|
||||
namespace AMWD.Net.Api.Cloudflare.Auth
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the interface to add authentication information.
|
||||
/// </summary>
|
||||
public interface IAuthentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds authentication headers to the given <see cref="HttpClient"/>.
|
||||
/// </summary>
|
||||
/// <param name="httpClient">The <see cref="HttpClient"/> to add the headers to.</param>
|
||||
void AddHeader(HttpClient httpClient);
|
||||
}
|
||||
}
|
||||
75
Cloudflare/Interfaces/ICloudflareClient.cs
Normal file
75
Cloudflare/Interfaces/ICloudflareClient.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AMWD.Net.Api.Cloudflare
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a client for the Cloudflare API.
|
||||
/// </summary>
|
||||
public interface ICloudflareClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Makes a GET request to the Cloudflare API.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The GET method requests a representation of the specified resource.
|
||||
/// Requests using GET should only retrieve data and should not contain a request content.
|
||||
/// </remarks>
|
||||
/// <typeparam name="TResponse">The response type.</typeparam>
|
||||
/// <param name="requestPath">The request path (extending the base URL).</param>
|
||||
/// <param name="queryFilter">The query parameters.</param>
|
||||
/// <param name="cancellationToken">A cancellation token used to propagate notification that this operation should be canceled.</param>
|
||||
Task<CloudflareResponse<TResponse>> GetAsync<TResponse>(string requestPath, IQueryParameterFilter queryFilter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Makes a POST request to the Cloudflare API.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The POST method submits an entity to the specified resource, often causing a change in state or side effects on the server.
|
||||
/// </remarks>
|
||||
/// <typeparam name="TResponse">The response type.</typeparam>
|
||||
/// <typeparam name="TRequest">The request type.</typeparam>
|
||||
/// <param name="requestPath">The request path (extending the base URL).</param>
|
||||
/// <param name="request">The request content.</param>
|
||||
/// <param name="cancellationToken">A cancellation token used to propagate notification that this operation should be canceled.</param>
|
||||
Task<CloudflareResponse<TResponse>> PostAsync<TResponse, TRequest>(string requestPath, TRequest request, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Makes a PUT request to the Cloudflare API.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The PUT method replaces all current representations of the target resource with the request content.
|
||||
/// </remarks>
|
||||
/// <typeparam name="TResponse">The response type.</typeparam>
|
||||
/// <typeparam name="TRequest">The request type.</typeparam>
|
||||
/// <param name="requestPath">The request path (extending the base URL).</param>
|
||||
/// <param name="request">The request content.</param>
|
||||
/// <param name="cancellationToken">A cancellation token used to propagate notification that this operation should be canceled.</param>
|
||||
Task<CloudflareResponse<TResponse>> PutAsync<TResponse, TRequest>(string requestPath, TRequest request, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Makes a DELETE request to the Cloudflare API.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The DELETE method deletes the specified resource.
|
||||
/// </remarks>
|
||||
/// <typeparam name="TResponse">The response type.</typeparam>
|
||||
/// <param name="requestPath">The request path (extending the base URL).</param>
|
||||
/// <param name="cancellationToken">A cancellation token used to propagate notification that this operation should be canceled.</param>
|
||||
/// <returns></returns>
|
||||
Task<CloudflareResponse<TResponse>> DeleteAsync<TResponse>(string requestPath, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Makes a PATCH request to the Cloudflare API.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The PATCH method applies partial modifications to a resource.
|
||||
/// </remarks>
|
||||
/// <typeparam name="TResponse">The response type.</typeparam>
|
||||
/// <typeparam name="TRequest">The request type.</typeparam>
|
||||
/// <param name="requestPath">The request path (extending the base URL).</param>
|
||||
/// <param name="request">The request content.</param>
|
||||
/// <param name="cancellationToken">A cancellation token used to propagate notification that this operation should be canceled.</param>
|
||||
Task<CloudflareResponse<TResponse>> PatchAsync<TResponse, TRequest>(string requestPath, TRequest request, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
15
Cloudflare/Interfaces/IQueryParameterFilter.cs
Normal file
15
Cloudflare/Interfaces/IQueryParameterFilter.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AMWD.Net.Api.Cloudflare
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents filter options defined via query parameters.
|
||||
/// </summary>
|
||||
public interface IQueryParameterFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the query parameters.
|
||||
/// </summary>
|
||||
IDictionary<string, string> GetQueryParameters();
|
||||
}
|
||||
}
|
||||
21
Cloudflare/Models/AccountBase.cs
Normal file
21
Cloudflare/Models/AccountBase.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace AMWD.Net.Api.Cloudflare
|
||||
{
|
||||
/// <summary>
|
||||
/// Base implementation of an account.
|
||||
/// </summary>
|
||||
public class AccountBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Identifier
|
||||
/// </summary>
|
||||
// <= 32 characters
|
||||
[JsonProperty("id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the account.
|
||||
/// </summary>
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
27
Cloudflare/Models/OwnerBase.cs
Normal file
27
Cloudflare/Models/OwnerBase.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace AMWD.Net.Api.Cloudflare
|
||||
{
|
||||
/// <summary>
|
||||
/// Base implementation of an owner.
|
||||
/// </summary>
|
||||
public class OwnerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Identifier.
|
||||
/// </summary>
|
||||
// <= 32 characters
|
||||
[JsonProperty("id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Name of the owner.
|
||||
/// </summary>
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of owner.
|
||||
/// </summary>
|
||||
[JsonProperty("type")]
|
||||
public string Type { get; set; }
|
||||
}
|
||||
}
|
||||
21
Cloudflare/README.md
Normal file
21
Cloudflare/README.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Cloudflare API for .NET | Core
|
||||
|
||||
This is the core package for all extensions of the Cloudflare API implemented by [AM.WD].
|
||||
|
||||
## Contents
|
||||
|
||||
- The `(I)CloudflareClient` with base calls for `GET`, `POST`, `PUT`, `PATCH` and `DELETE` requests.
|
||||
- Base classes to receive responses.
|
||||
- `CloudflareException` to specify some errors.
|
||||
- `IAuthentication` implementations to allow API-Token and API-Key (legacy) authentication.
|
||||
|
||||
Any specific request will be defined via extension packages.
|
||||
|
||||
---
|
||||
|
||||
Published under MIT License (see [choose a license])
|
||||
|
||||
|
||||
|
||||
[AM.WD]: https://www.nuget.org/packages?q=AMWD.&sortby=created-desc
|
||||
[choose a license]: https://choosealicense.com/licenses/mit/
|
||||
47
Cloudflare/Responses/CloudflareResponse.cs
Normal file
47
Cloudflare/Responses/CloudflareResponse.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AMWD.Net.Api.Cloudflare
|
||||
{
|
||||
/// <summary>
|
||||
/// The base Cloudflare response.
|
||||
/// </summary>
|
||||
public class CloudflareResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Information about the result of the request.
|
||||
/// </summary>
|
||||
[JsonProperty("result_info")]
|
||||
public ResultInfo ResultInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the API call was successful.
|
||||
/// </summary>
|
||||
[JsonProperty("success")]
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Errors returned by the API call.
|
||||
/// </summary>
|
||||
[JsonProperty("errors")]
|
||||
public IReadOnlyList<ResponseInfo> Errors { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Messages returned by the API call.
|
||||
/// </summary>
|
||||
[JsonProperty("messages")]
|
||||
public IReadOnlyList<ResponseInfo> Messages { get; set; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The base Cloudflare response with a result.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class CloudflareResponse<T> : CloudflareResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// The result of the API call.
|
||||
/// </summary>
|
||||
[JsonProperty("result")]
|
||||
public T Result { get; set; }
|
||||
}
|
||||
}
|
||||
20
Cloudflare/Responses/ResponseInfo.cs
Normal file
20
Cloudflare/Responses/ResponseInfo.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace AMWD.Net.Api.Cloudflare
|
||||
{
|
||||
/// <summary>
|
||||
/// A Cloudflare response information.
|
||||
/// </summary>
|
||||
public class ResponseInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// The message code.
|
||||
/// </summary>
|
||||
[JsonProperty("code")]
|
||||
public int Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The message.
|
||||
/// </summary>
|
||||
[JsonProperty("message")]
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
||||
32
Cloudflare/Responses/ResultInfo.cs
Normal file
32
Cloudflare/Responses/ResultInfo.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace AMWD.Net.Api.Cloudflare
|
||||
{
|
||||
/// <summary>
|
||||
/// Cloudflare Result Information.
|
||||
/// </summary>
|
||||
public class ResultInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Total number of results for the requested service.
|
||||
/// </summary>
|
||||
[JsonProperty("count")]
|
||||
public int Count { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current page within paginated list of results.
|
||||
/// </summary>
|
||||
[JsonProperty("page")]
|
||||
public int Page { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of results per page of results.
|
||||
/// </summary>
|
||||
[JsonProperty("per_page")]
|
||||
public int PerPage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Total results available without any search parameters.
|
||||
/// </summary>
|
||||
[JsonProperty("total_count")]
|
||||
public int TotalCount { get; set; }
|
||||
}
|
||||
}
|
||||
337
CodeMaid.config
Normal file
337
CodeMaid.config
Normal file
@@ -0,0 +1,337 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<configSections>
|
||||
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
|
||||
<section name="SteveCadwallader.CodeMaid.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
</sectionGroup>
|
||||
</configSections>
|
||||
<userSettings>
|
||||
<SteveCadwallader.CodeMaid.Properties.Settings>
|
||||
<setting name="Cleaning_AutoCleanupOnFileSave" serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_ExcludeT4GeneratedCode" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_ExclusionExpression" serializeAs="String">
|
||||
<value>.*\.Designer\.cs||.*\.resx||packages.config||.*\.min\.js||.*\.min\.css</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_IncludeCPlusPlus" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_IncludeCSS" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_IncludeCSharp" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_IncludeFSharp" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_IncludeHTML" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_IncludeJSON" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_IncludeJavaScript" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_IncludeLESS" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_IncludePHP" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_IncludeSCSS" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_IncludeTypeScript" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_IncludeVB" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_IncludeXAML" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_IncludeXML" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingAfterClasses" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingAfterDelegates"
|
||||
serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingAfterEndRegionTags"
|
||||
serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingAfterEnumerations"
|
||||
serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingAfterEvents" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingAfterFieldsMultiLine"
|
||||
serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingAfterInterfaces"
|
||||
serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingAfterMethods" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingAfterNamespaces"
|
||||
serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingAfterPropertiesMultiLine"
|
||||
serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingAfterPropertiesSingleLine"
|
||||
serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingAfterRegionTags"
|
||||
serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingAfterStructs" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingAfterUsingStatementBlocks"
|
||||
serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingBeforeCaseStatements"
|
||||
serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingBeforeClasses"
|
||||
serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingBeforeDelegates"
|
||||
serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingBeforeEndRegionTags"
|
||||
serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingBeforeEnumerations"
|
||||
serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingBeforeEvents" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingBeforeFieldsMultiLine"
|
||||
serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingBeforeInterfaces"
|
||||
serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingBeforeMethods"
|
||||
serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingBeforeNamespaces"
|
||||
serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingBeforePropertiesMultiLine"
|
||||
serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingBeforePropertiesSingleLine"
|
||||
serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingBeforeRegionTags"
|
||||
serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingBeforeSingleLineComments"
|
||||
serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingBeforeStructs"
|
||||
serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingBeforeUsingStatementBlocks"
|
||||
serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankLinePaddingBetweenPropertiesMultiLineAccessors"
|
||||
serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertBlankSpaceBeforeSelfClosingAngleBrackets"
|
||||
serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertEndOfFileTrailingNewLine" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertExplicitAccessModifiersOnClasses"
|
||||
serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertExplicitAccessModifiersOnDelegates"
|
||||
serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertExplicitAccessModifiersOnEnumerations"
|
||||
serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertExplicitAccessModifiersOnEvents"
|
||||
serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertExplicitAccessModifiersOnFields"
|
||||
serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertExplicitAccessModifiersOnInterfaces"
|
||||
serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertExplicitAccessModifiersOnMethods"
|
||||
serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertExplicitAccessModifiersOnProperties"
|
||||
serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_InsertExplicitAccessModifiersOnStructs"
|
||||
serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_RemoveBlankLinesAfterAttributes" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_RemoveBlankLinesAfterOpeningBrace" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_RemoveBlankLinesAtBottom" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_RemoveBlankLinesAtTop" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_RemoveBlankLinesBeforeClosingBrace" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_RemoveBlankLinesBeforeClosingTags" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_RemoveBlankLinesBetweenChainedStatements"
|
||||
serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_RemoveBlankSpacesBeforeClosingAngleBrackets"
|
||||
serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_RemoveEndOfFileTrailingNewLine" serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_RemoveEndOfLineWhitespace" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_RemoveMultipleConsecutiveBlankLines"
|
||||
serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_RemoveRegions" serializeAs="String">
|
||||
<value>0</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_RunVisualStudioFormatDocumentCommand"
|
||||
serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_RunVisualStudioRemoveUnusedUsingStatements"
|
||||
serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_RunVisualStudioSortUsingStatements" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_SkipRemoveUnusedUsingStatementsDuringAutoCleanupOnSave"
|
||||
serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_UpdateAccessorsToBothBeSingleLineOrMultiLine"
|
||||
serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_UpdateEndRegionDirectives" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_UpdateSingleLineMethods" serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="Cleaning_UsingStatementsToReinsertWhenRemovedExpression"
|
||||
serializeAs="String">
|
||||
<value>
|
||||
</value>
|
||||
</setting>
|
||||
<setting name="Collapsing_CollapseSolutionWhenOpened" serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="Formatting_CommentRunDuringCleanup" serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="Formatting_CommentSkipWrapOnLastWord" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Formatting_CommentWrapColumn" serializeAs="String">
|
||||
<value>100</value>
|
||||
</setting>
|
||||
<setting name="Formatting_CommentXmlAlignParamTags" serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="Formatting_CommentXmlKeepTagsTogether" serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="Formatting_CommentXmlSpaceSingleTags" serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="Formatting_CommentXmlSpaceTags" serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="Formatting_CommentXmlSplitAllTags" serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="Formatting_CommentXmlSplitSummaryTagToMultipleLines"
|
||||
serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="Formatting_CommentXmlValueIndent" serializeAs="String">
|
||||
<value>0</value>
|
||||
</setting>
|
||||
<setting name="General_ShowStartPageOnSolutionClose" serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="Progressing_HideBuildProgressOnBuildStop" serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="Progressing_ShowBuildProgressOnBuildStart" serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
</SteveCadwallader.CodeMaid.Properties.Settings>
|
||||
</userSettings>
|
||||
</configuration>
|
||||
20
Directory.Build.props
Normal file
20
Directory.Build.props
Normal file
@@ -0,0 +1,20 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<LangVersion>12.0</LangVersion>
|
||||
|
||||
<Title>Modular Cloudflare API implementation in .NET</Title>
|
||||
<Company>AM.WD</Company>
|
||||
<Authors>Andreas Müller</Authors>
|
||||
<Copyright>© {copyright:2024-} AM.WD</Copyright>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>$(SolutionDir)/cloudflare-api.snk</AssemblyOriginatorKeyFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
|
||||
<Using Include="Newtonsoft.Json"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
20
Extensions/Cloudflare.Zones/Cloudflare.Zones.csproj
Normal file
20
Extensions/Cloudflare.Zones/Cloudflare.Zones.csproj
Normal file
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
|
||||
<PackageId>AMWD.Net.API.Cloudflare.Zones</PackageId>
|
||||
<PackageTags>cloudflare api zones</PackageTags>
|
||||
|
||||
<AssemblyName>amwd-cloudflare-zones</AssemblyName>
|
||||
<RootNamespace>AMWD.Net.Api.Cloudflare.Zones</RootNamespace>
|
||||
|
||||
<Product>Cloudflare API - Zones</Product>
|
||||
<Description>Zone management features of the Cloudflare API</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('$(CI_COMMIT_TAG)', '^zones\/v[0-9.]+'))">
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
20
Extensions/Cloudflare.Zones/README.md
Normal file
20
Extensions/Cloudflare.Zones/README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Cloudflare API for .NET | Zones
|
||||
|
||||
With this extension package, you'll get all features available to manage a Zone on Cloudflare.
|
||||
|
||||
## Methods
|
||||
|
||||
- [ListZones](https://developers.cloudflare.com/api/operations/zones-get)
|
||||
- [ZoneDetails](https://developers.cloudflare.com/api/operations/zones-0-get)
|
||||
- [CreateZone](https://developers.cloudflare.com/api/operations/zones-post)
|
||||
- [DeleteZone](https://developers.cloudflare.com/api/operations/zones-0-delete)
|
||||
- [EditZone](https://developers.cloudflare.com/api/operations/zones-0-patch)
|
||||
- TBD
|
||||
|
||||
---
|
||||
|
||||
Published under MIT License (see [choose a license])
|
||||
|
||||
|
||||
|
||||
[choose a license]: https://choosealicense.com/licenses/mit/
|
||||
56
Extensions/Directory.Build.props
Normal file
56
Extensions/Directory.Build.props
Normal file
@@ -0,0 +1,56 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<NrtRevisionFormat>{semvertag:main}{!:-dev}</NrtRevisionFormat>
|
||||
|
||||
<AppendTargetFrameworkToOutputPath>true</AppendTargetFrameworkToOutputPath>
|
||||
<CopyRefAssembliesToPublishDirectory>false</CopyRefAssembliesToPublishDirectory>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<RepositoryUrl>https://github.com/AM-WD/cloudflare-api.git</RepositoryUrl>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
|
||||
<PackageIcon>package-icon.png</PackageIcon>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<PackageProjectUrl>https://developers.cloudflare.com/api</PackageProjectUrl>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<EmbedUntrackedSources>false</EmbedUntrackedSources>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(GITLAB_CI)' == 'true'">
|
||||
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="'$(GITLAB_CI)' == 'true'">
|
||||
<SourceLinkGitLabHost Include="$(CI_SERVER_HOST)" Version="$(CI_SERVER_VERSION)" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitLab" Version="8.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="$(SolutionDir)/package-icon.png" Pack="true" PackagePath="/" />
|
||||
<None Include="README.md" Pack="true" PackagePath="/" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(Configuration)' == 'Release'">
|
||||
<PackageReference Include="AMWD.Net.API.Cloudflare" Version="0.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
|
||||
<ProjectReference Include="$(SolutionDir)\Cloudflare\Cloudflare.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AMWD.NetRevisionTask" Version="1.2.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
|
||||
</Project>
|
||||
@@ -9,8 +9,9 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
The above copyright notice and this permission notice (including the next
|
||||
paragraph) shall be included in all copies or substantial portions of
|
||||
the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
|
||||
34
README.md
Normal file
34
README.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Cloudflare API for .NET
|
||||
|
||||
This project aims to implement the [Cloudflare API] in an extensible way.
|
||||
|
||||
## Overview
|
||||
|
||||
There should be a package for each API section as defined by Cloudflare.
|
||||
|
||||
|
||||
### [Cloudflare]
|
||||
|
||||
This is the base client, that will perform the request itself. It has the base url and holds the credentials.
|
||||
|
||||
|
||||
### [Cloudflare.Zones]
|
||||
|
||||
If you install this package, you will get all methods to handle a DNS zone.
|
||||
|
||||
|
||||
---
|
||||
|
||||
Published under [MIT License] (see [choose a license])
|
||||
|
||||
[](https://link.am-wd.de/donate)
|
||||
[](https://link.am-wd.de/codeium)
|
||||
|
||||
|
||||
|
||||
[Cloudflare]: Cloudflare/README.md
|
||||
[Cloudflare.Zones]: Extensions/Cloudflare.Zones/README.md
|
||||
|
||||
[Cloudflare API]: https://developers.cloudflare.com/api/
|
||||
[MIT License]: LICENSE.txt
|
||||
[choose a license]: https://choosealicense.com/licenses/mit/
|
||||
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(SolutionDir)\Extensions\Cloudflare.Zones\Cloudflare.Zones.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
28
UnitTests/Directory.Build.props
Normal file
28
UnitTests/Directory.Build.props
Normal file
@@ -0,0 +1,28 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<CollectCoverage>true</CollectCoverage>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.msbuild" Version="6.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="Moq" Version="4.20.72" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.6.1" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.6.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(SolutionDir)\Cloudflare\Cloudflare.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
|
||||
</Project>
|
||||
84
cloudflare-api.sln
Normal file
84
cloudflare-api.sln
Normal file
@@ -0,0 +1,84 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.10.35013.160
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cloudflare", "Cloudflare\Cloudflare.csproj", "{9D98650A-01CC-44B1-AC1E-D6323E1777C5}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5D69F102-CF03-4175-8C59-D457450B28E0}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", "{EE760850-ED97-4493-B0AE-326289A60145}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
Extensions\Directory.Build.props = Extensions\Directory.Build.props
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{BA35336B-7640-4C0C-B93E-06BDC1EE1872}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.gitlab-ci.yml = .gitlab-ci.yml
|
||||
Directory.Build.props = Directory.Build.props
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "config", "config", "{E72A0B89-A37E-4BB3-B2EF-26AB24D3D716}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.editorconfig = .editorconfig
|
||||
.gitignore = .gitignore
|
||||
CodeMaid.config = CodeMaid.config
|
||||
nuget.config = nuget.config
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{5AF54083-1A93-4C43-B36C-EDD9E5DE0695}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
CHANGELOG.md = CHANGELOG.md
|
||||
LICENSE.txt = LICENSE.txt
|
||||
package-icon.png = package-icon.png
|
||||
README.md = README.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cloudflare.Zones", "Extensions\Cloudflare.Zones\Cloudflare.Zones.csproj", "{290D0987-D295-4DBD-9090-14D3DED63281}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UnitTests", "UnitTests", "{A31B4929-190B-4AB8-984B-E284BB159F04}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
UnitTests\Directory.Build.props = UnitTests\Directory.Build.props
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cloudflare.Tests", "Cloudflare.Tests\Cloudflare.Tests.csproj", "{2491D707-E845-49DF-8D94-0154AAD36E42}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cloudflare.Zones.Tests", "UnitTests\Cloudflare.Zones.Tests\Cloudflare.Zones.Tests.csproj", "{835705E5-D9F9-4236-9E25-898A851C8165}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{9D98650A-01CC-44B1-AC1E-D6323E1777C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9D98650A-01CC-44B1-AC1E-D6323E1777C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9D98650A-01CC-44B1-AC1E-D6323E1777C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9D98650A-01CC-44B1-AC1E-D6323E1777C5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{290D0987-D295-4DBD-9090-14D3DED63281}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{290D0987-D295-4DBD-9090-14D3DED63281}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{290D0987-D295-4DBD-9090-14D3DED63281}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{290D0987-D295-4DBD-9090-14D3DED63281}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2491D707-E845-49DF-8D94-0154AAD36E42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2491D707-E845-49DF-8D94-0154AAD36E42}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2491D707-E845-49DF-8D94-0154AAD36E42}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2491D707-E845-49DF-8D94-0154AAD36E42}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{835705E5-D9F9-4236-9E25-898A851C8165}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{835705E5-D9F9-4236-9E25-898A851C8165}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{835705E5-D9F9-4236-9E25-898A851C8165}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{835705E5-D9F9-4236-9E25-898A851C8165}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{BA35336B-7640-4C0C-B93E-06BDC1EE1872} = {5D69F102-CF03-4175-8C59-D457450B28E0}
|
||||
{E72A0B89-A37E-4BB3-B2EF-26AB24D3D716} = {5D69F102-CF03-4175-8C59-D457450B28E0}
|
||||
{5AF54083-1A93-4C43-B36C-EDD9E5DE0695} = {5D69F102-CF03-4175-8C59-D457450B28E0}
|
||||
{290D0987-D295-4DBD-9090-14D3DED63281} = {EE760850-ED97-4493-B0AE-326289A60145}
|
||||
{835705E5-D9F9-4236-9E25-898A851C8165} = {A31B4929-190B-4AB8-984B-E284BB159F04}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {A465B60D-C946-4381-835C-29303EA4FAD1}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
BIN
cloudflare-api.snk
Normal file
BIN
cloudflare-api.snk
Normal file
Binary file not shown.
Reference in New Issue
Block a user