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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ jobs:
if: ${{ matrix.os == 'ubuntu-22.04' && matrix.dotnet == '7.0.x' }}
run: dotnet test APIMatic.Core.Test/APIMatic.Core.Test.csproj -p:CollectCoverage=true -p:CoverletOutputFormat=lcov

- name: Upload coverage report
if: ${{ matrix.os == 'ubuntu-22.04' && matrix.dotnet == '7.0.x' && github.actor != 'dependabot[bot]' }}
uses: paambaati/codeclimate-action@v3.0.0
env:
CC_TEST_REPORTER_ID: ${{ secrets.CODE_CLIMATE_KEY }}
with:
coverageLocations: |
${{github.workspace}}/APIMatic.Core.Test/coverage.info:lcov
# - name: Upload coverage report
# if: ${{ matrix.os == 'ubuntu-22.04' && matrix.dotnet == '7.0.x' && github.actor != 'dependabot[bot]' }}
# uses: paambaati/codeclimate-action@v3.0.0
# env:
# CC_TEST_REPORTER_ID: ${{ secrets.CODE_CLIMATE_KEY }}
# with:
# coverageLocations: |
# ${{github.workspace}}/APIMatic.Core.Test/coverage.info:lcov
185 changes: 185 additions & 0 deletions APIMatic.Core.Test/Request/ParameterTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
using System.Collections.Generic;
using System.Linq;
using APIMatic.Core.Request;
using APIMatic.Core.Request.Parameters;
using NUnit.Framework;

namespace APIMatic.Core.Test.Request
{
[TestFixture]
public class ParameterTests : TestBase
{
const string ServerUrl = "https://parameter.api.server.com";

private static readonly Dictionary<string, object> QueryParams = new Dictionary<string, object>
{
["field"] = "query_value",
["field1"] = "query_value1",
["field2"] = "query_value2",
["field3"] = "query_value3",
};

private static readonly Dictionary<string, object> AdditionalQueryParams = new Dictionary<string, object>
{
["field"] = "additional_query_value",
["field1"] = "additional_query_value1",
["field2"] = "additional_query_value2",
["field3"] = "additional_query_value3",
};

[Test]
public void AdditionalForms_WhenKeyIsNull_ShouldAssignInnerFieldsCorrectly()
{
// Arrange
var parameters = new Parameter.Builder();
var fieldParameters = new Dictionary<string, object> { ["field"] = "field", };

parameters
.AdditionalForms(additionalForms => additionalForms.Setup(fieldParameters));

var requestBuilder = new RequestBuilder(LazyGlobalConfiguration.Value, ServerUrl);

// Act
parameters.Validate().Apply(requestBuilder);

// Assert
var formParam = requestBuilder.formParameters.FirstOrDefault(p => p.Key == "field");

Assert.Multiple(() =>
{
Assert.That(formParam, Is.Not.Null, "Expected form parameter with key 'field'");
Assert.That(formParam.Value, Is.EqualTo("field"), "Expected value mismatch for 'field'");
});
}

[Test]
public void DuplicateHeaderKeys_Should_UseLastOneInRequestBuilder()
{
// Arrange
var parameters = new Parameter.Builder();

parameters
.Header(h => h.Setup("Authorization", "Basic OLD1").Required())
.Header(h => h.Setup("Authorization", "Basic OLD2").Required())
.Header(h => h.Setup("Authorization", "Basic OLD3").Required())
.Header(h => h.Setup("Authorization", "Basic OLD4").Required())
.Header(h => h.Setup("Authorization", "Basic OLD5").Required())
.Header(h => h.Setup("Authorization", "Basic OLD6").Required())
.Header(h => h.Setup("Authorization", "Basic NEW").Required());

var requestBuilder = new RequestBuilder(LazyGlobalConfiguration.Value, ServerUrl);

// Act
parameters.Validate().Apply(requestBuilder);

// Assert
Assert.That(requestBuilder.headersParameters.TryGetValue("Authorization", out var value), Is.True);
Assert.That(value, Is.EqualTo("Basic NEW"));
}

[Test]
public void DuplicateQueryKeys_Should_UseLastOneInRequestBuilder()
{
// Arrange
var parameters = new Parameter.Builder();

parameters
.Query(q => q.Setup("status", "pending").Required())
.Query(q => q.Setup("status", "approved").Required());

var requestBuilder = new RequestBuilder(LazyGlobalConfiguration.Value, ServerUrl);

// Act
parameters.Validate().Apply(requestBuilder);

// Assert
Assert.That(requestBuilder.queryParameters.TryGetValue("status", out var value), Is.True);
Assert.That(value, Is.EqualTo("approved"));
}

[Test]
public void DuplicateFormKey_WhenSetInAdditionalFormsAndForm_ShouldRetainBothWithSameKey()
{
// Arrange
var parameters = new Parameter.Builder();
var fieldParameters = new Dictionary<string, object> { ["field"] = "additional_form_value", };

parameters
.AdditionalForms(additionalForms => additionalForms.Setup(fieldParameters))
.Form(f => f.Setup("field", "form_value"));

var requestBuilder = new RequestBuilder(LazyGlobalConfiguration.Value, ServerUrl);

// Act
parameters.Validate().Apply(requestBuilder);

// Assert
var formParams = requestBuilder.formParameters
.Where(p => p.Key == "field")
.ToList();

Assert.Multiple(() =>
{
Assert.That(formParams.Count, Is.EqualTo(2), "Expected 2 form parameters with the key 'field'");
Assert.That(formParams.Any(p => Equals(p.Value, "form_value")), Is.True,
"Expected 'form_value' to be present");
Assert.That(formParams.Any(p => Equals(p.Value, "additional_form_value")), Is.True,
"Expected 'additional_form_value' to be present");
});
}

[Test]
public void When_QueryAddedAfterAdditionalQueries_ShouldRetainQueryValues()
{
// Arrange
var parameters = new Parameter.Builder();
parameters.AdditionalQueries(q => q.Setup(AdditionalQueryParams));
foreach (var (key, value) in QueryParams)
parameters.Query(q => q.Setup(key, value));

var requestBuilder = new RequestBuilder(LazyGlobalConfiguration.Value, ServerUrl);

// Act
parameters.Validate().Apply(requestBuilder);

// Assert
Assert.Multiple(() =>
{
foreach (var (key, expectedValue) in QueryParams)
{
Assert.That(requestBuilder.queryParameters.TryGetValue(key, out var actualValue), Is.True,
$"Expected query parameter with key '{key}'");
Assert.That(actualValue, Is.EqualTo(expectedValue),
$"Expected value for key '{key}' to be '{expectedValue}', but was '{actualValue}'");
}
});
}

[Test]
public void When_AdditionalQueriesAddedAfterQuery_ShouldRetainAdditionalQueryValues()
{
// Arrange
var parameters = new Parameter.Builder();
foreach (var (key, value) in QueryParams)
parameters.Query(q => q.Setup(key, value));
parameters.AdditionalQueries(q => q.Setup(AdditionalQueryParams));

var requestBuilder = new RequestBuilder(LazyGlobalConfiguration.Value, ServerUrl);

// Act
parameters.Validate().Apply(requestBuilder);

// Assert
Assert.Multiple(() =>
{
foreach (var (key, expectedValue) in AdditionalQueryParams)
{
Assert.That(requestBuilder.queryParameters.TryGetValue(key, out var actualValue), Is.True,
$"Expected query parameter with key '{key}'");
Assert.That(actualValue, Is.EqualTo(expectedValue),
$"Expected value for key '{key}' to be '{expectedValue}', but was '{actualValue}'");
}
});
}
}
}
52 changes: 35 additions & 17 deletions APIMatic.Core/Request/Parameters/Parameter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using APIMatic.Core.Utilities;
using Microsoft.Json.Pointer;
using Newtonsoft.Json.Linq;

namespace APIMatic.Core.Request.Parameters
{
Expand All @@ -22,9 +19,11 @@ public abstract class Parameter
private Func<object, object> valueSerializer = value => value;
protected bool validated = false;
protected string typeName;

private string GetName() => key == "" ? typeName : key;

private string IdentifierKey => string.IsNullOrEmpty(key) ? $"_{nameof(Parameter)}_{Guid.NewGuid()}" : key;

public Parameter Setup(string key, object value)
{
this.key = key;
Expand Down Expand Up @@ -80,7 +79,10 @@ internal virtual void Validate()
/// </summary>
public class Builder
{
private readonly ConcurrentBag<Parameter> parameters = new ConcurrentBag<Parameter>();
private readonly ConcurrentDictionary<string, Parameter> _parameters =
new ConcurrentDictionary<string, Parameter>();
private readonly ConcurrentQueue<string> _insertionOrder = new ConcurrentQueue<string>();
private readonly object _sync = new object();

internal Builder() { }

Expand All @@ -93,7 +95,7 @@ public Builder Template(Action<TemplateParam> _template)
{
var template = new TemplateParam();
_template(template);
parameters.Add(template);
AddOrUpdate(template);
return this;
}

Expand All @@ -106,7 +108,7 @@ public Builder Header(Action<HeaderParam> _header)
{
var header = new HeaderParam();
_header(header);
parameters.Add(header);
AddOrUpdate(header);
return this;
}

Expand All @@ -120,7 +122,7 @@ public Builder AdditionalHeaders(Action<AdditionalHeaderParams> _headers)
{
var headers = new AdditionalHeaderParams();
_headers(headers);
parameters.Add(headers);
AddOrUpdate(headers);
return this;
}

Expand All @@ -133,7 +135,7 @@ public Builder Query(Action<QueryParam> _query)
{
var query = new QueryParam();
_query(query);
parameters.Add(query);
AddOrUpdate(query);
return this;
}

Expand All @@ -146,7 +148,7 @@ public Builder AdditionalQueries(Action<AdditionalQueryParams> _queries)
{
var queries = new AdditionalQueryParams();
_queries(queries);
parameters.Add(queries);
AddOrUpdate(queries);
return this;
}

Expand All @@ -159,7 +161,7 @@ public Builder Form(Action<FormParam> _form)
{
var form = new FormParam();
_form(form);
parameters.Add(form);
AddOrUpdate(form);
return this;
}

Expand All @@ -172,7 +174,7 @@ public Builder AdditionalForms(Action<AdditionalFormParams> _forms)
{
var forms = new AdditionalFormParams();
_forms(forms);
parameters.Add(forms);
AddOrUpdate(forms);
return this;
}

Expand All @@ -185,7 +187,7 @@ public Builder Body(Action<BodyParam> _body)
{
var body = new BodyParam();
_body(body);
parameters.Add(body);
AddOrUpdate(body);
return this;
}

Expand All @@ -196,11 +198,11 @@ public Builder Body(Action<BodyParam> _body)
internal Builder Validate()
{
var missingArgErrors = new List<string>();
foreach (var p in parameters)
foreach (var p in _parameters)
{
try
{
p.Validate();
p.Value.Validate();
}
catch (ArgumentNullException exp)
{
Expand All @@ -220,9 +222,25 @@ internal Builder Validate()
/// <returns></returns>
internal void Apply(RequestBuilder requestBuilder)
{
foreach (var p in parameters)
foreach (var p in _insertionOrder)
{
_parameters[p].Apply(requestBuilder);
}
}

private void AddOrUpdate(Parameter param)
{
var key = param.IdentifierKey;

lock (_sync)
{
p.Apply(requestBuilder);
// Only record order on first insertion
if (!_parameters.ContainsKey(key))
{
_insertionOrder.Enqueue(key);
}

_parameters[key] = param;
}
}
}
Expand Down
Loading