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
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
furnished to do so, subject to the following conditions:
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
The above copyright notice and this permission notice (including the next
|
||||||
all copies or substantial portions of the Software.
|
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
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
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