Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Behavioral.Automation.API.DemoScenarios/AutomationConfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"API_HOST": "https://reqres.in/"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="SpecFlow" Version="3.9.74" />
<PackageReference Include="SpecFlow.NUnit" Version="3.9.69" />
<PackageReference Include="SpecFlow.Tools.MsBuild.Generation" Version="3.9.69" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Behavioral.Automation.API\Behavioral.Automation.API.csproj" />
<ProjectReference Include="..\Behavioral.Automation.Transformations\Behavioral.Automation.Transformations.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="specflow.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="AutomationConfig.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
23 changes: 23 additions & 0 deletions Behavioral.Automation.API.DemoScenarios/Features/Demo.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Feature: reqres test

Scenario: Get all users
When user sends a "GET" request to "api/users" url
Then response json path "$..data[?(@.email == 'george.bluth@reqres.in')].first_name" value should be "["George"]"

Scenario: Get second page
When user sends a "GET" request to "api/users" url with the following parameters:
| Name | Value | Kind |
| page | 2 | Param |
| CustomHeader | test | Header |

Scenario: Complex request
Given user creates a "POST" request to "api/users" url with the json:
"""
{
"name": "morpheus",
"job": "leader"
}
"""
When user adds parameters and send request:
| Name | Value | Kind |
| CustomHeader | test | Header |
19 changes: 19 additions & 0 deletions Behavioral.Automation.API.DemoScenarios/specflow.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"bindingCulture": {
"language": "en-us"
},
"language": {
"feature": "en-us"
},
"runtime": {
"missingOrPendingStepsOutcome": "Error"
},
"stepAssemblies": [
{
"assembly": "Behavioral.Automation.API"
},
{
"assembly": "Behavioral.Automation.Transformations"
}
]
}
25 changes: 25 additions & 0 deletions Behavioral.Automation.API/Behavioral.Automation.API.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.5.1" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="Polly" Version="7.2.3" />
<PackageReference Include="SpecFlow" Version="3.9.74" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Behavioral.Automation.Configs\Behavioral.Automation.Configs.csproj" />
</ItemGroup>

</Project>
226 changes: 226 additions & 0 deletions Behavioral.Automation.API/Bindings/HttpRequestSteps.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
using System.Text;
using System.Web;
using Behavioral.Automation.API.Configs;
using Behavioral.Automation.API.Context;
using Behavioral.Automation.API.Models;
using Behavioral.Automation.API.Services;
using Behavioral.Automation.Configs;
using TechTalk.SpecFlow;
using TechTalk.SpecFlow.Assist;

namespace Behavioral.Automation.API.Bindings;

[Binding]
public class HttpRequestSteps
{
private readonly ApiContext _apiContext;
private readonly HttpService _httpService;

public HttpRequestSteps(ApiContext apiContext, HttpService httpService)
{
_apiContext = apiContext;
_httpService = httpService;
}

[When("user sends a \"(.*)\" request to \"(.*)\" url")]
public HttpResponseMessage UserSendsHttpRequest(string httpMethod, string url)
{
var method = new HttpMethod(httpMethod.ToUpper());

if (!IsAbsoluteUrl(url))
{
url = ConfigManager.GetConfig<Config>().ApiHost + url;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes sense to rewrite IsAbsoluteUrl to something like GetAbsoluteUrl since it's use throughout the code. Additionally, you can return Uri from this method that will be well-formed and wouldn't require using string concatenation

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}

_apiContext.Request = new HttpRequestMessage(method, url);
_httpService.SendContextRequest();

return _apiContext.Response;
}

[When("user sends a \"(.*)\" request to \"(.*)\" url with the following parameters:")]
public HttpResponseMessage UserSendsHttpRequestWithParameters(string httpMethod, string url, Table tableParameters)
{
UserCreatesHttpRequestWithParameters(httpMethod, url, tableParameters);
_httpService.SendContextRequest();

return _apiContext.Response;
}

[When("user sends a \"(.*)\" request to \"(.*)\" url with the json:")]
public HttpResponseMessage UserSendsHttpRequestWithParameters(string httpMethod, string url, string jsonToSend)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sure it's a bad idea to write json as string inside the .feature file, it makes file bigger, less readable and harder to format, because not Visual Studio, nor Rider can successfully format both .feature file and json inside it at once.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If JSON is not very long it looks fine for me. I've added a step and an example using a file as a JSON source.

{
//TODO: body can be:
// form-data
// x-www-form-urlencoded
// raw (Text, JavaScript, JSON, HTML, XML)
// binary
// GraphQL
// Consider adding other types
var method = new HttpMethod(httpMethod.ToUpper());
if (!IsAbsoluteUrl(url))
{
url = ConfigManager.GetConfig<Config>().ApiHost + url;
}

var uriBuilder = new UriBuilder(url);

_apiContext.Request = new HttpRequestMessage(method, uriBuilder.Uri);
_apiContext.Request.Content = new StringContent(jsonToSend, Encoding.UTF8, "application/json");

_httpService.SendContextRequest();

return _apiContext.Response;
}


[When("user sends a \"(.*)\" request to \"(.*)\" url with the application/x-www-form-urlencoded:")]
public HttpResponseMessage UserSendsHttpRequestWithFormUrlEncodedContent(string httpMethod, string url, Table parameters)
{
var method = new HttpMethod(httpMethod.ToUpper());
if (!IsAbsoluteUrl(url))
{
url = ConfigManager.GetConfig<Config>().ApiHost + url;
}

var uriBuilder = new UriBuilder(url);

_apiContext.Request = new HttpRequestMessage(method, uriBuilder.Uri);

var body = parameters.Rows.Select(row => new KeyValuePair<string, string>(row[0], row[1]));

_apiContext.Request.Content = new FormUrlEncodedContent(body);

_httpService.SendContextRequest();

return _apiContext.Response;
}

[Given("user creates a \"(.*)\" request to \"(.*)\" url with the json:")]
public HttpRequestMessage UserCreatesHttpRequestWithJson(string httpMethod, string url, string jsonToSend)
{
var method = new HttpMethod(httpMethod.ToUpper());
if (!IsAbsoluteUrl(url))
{
url = ConfigManager.GetConfig<Config>().ApiHost + url;
}

var uriBuilder = new UriBuilder(url);

_apiContext.Request = new HttpRequestMessage(method, uriBuilder.Uri);
_apiContext.Request.Content = new StringContent(jsonToSend, Encoding.UTF8, "application/json");
return _apiContext.Request;
}

[Given("user creates a \"(.*)\" request to \"(.*)\" url")]
public HttpRequestMessage GivenUserCreatesARequestToUrl(string httpMethod, string url)
{
var method = new HttpMethod(httpMethod.ToUpper());
if (!IsAbsoluteUrl(url))
{
url = ConfigManager.GetConfig<Config>().ApiHost + url;
}

var uriBuilder = new UriBuilder(url);

_apiContext.Request = new HttpRequestMessage(method, uriBuilder.Uri);
return _apiContext.Request;
}

[Given("user creates a \"(.*)\" request to \"(.*)\" url with the following parameters:")]
public HttpRequestMessage UserCreatesHttpRequestWithParameters(string httpMethod, string url, Table tableParameters)
{
var method = new HttpMethod(httpMethod.ToUpper());
if (!IsAbsoluteUrl(url))
{
url = ConfigManager.GetConfig<Config>().ApiHost + url;
}

_apiContext.Request = new HttpRequestMessage(method, url);
AddParametersToRequest(_apiContext.Request, tableParameters);
return _apiContext.Request;
}

[When("user adds a JSON body and send the request:")]
public HttpResponseMessage WhenUserAddsJsonBodyAndSendRequest(string jsonToSend)
{
_apiContext.Request.Content = new StringContent(jsonToSend, Encoding.UTF8, "application/json");

_httpService.SendContextRequest();

return _apiContext.Response;
}

[When("user adds parameters and send request:")]
public HttpResponseMessage WhenUserAddsParametersAndSendRequest(Table tableParameters)
{
AddParametersToRequest(_apiContext.Request, tableParameters);
_httpService.SendContextRequest();

return _apiContext.Response;
}

[When("user sends request")]
public HttpResponseMessage WhenUserSendsRequest()
{
_httpService.SendContextRequest();
return _apiContext.Response;
}

[Given("the response status code should be \"(\\d*)\"")]
public void ChangeResponseStatusCode(int statusCode)
{
_apiContext.ExpectedStatusCode = statusCode;
}

private static bool IsAbsoluteUrl(string url)
{
return Uri.IsWellFormedUriString(url, UriKind.Absolute);
}

private static void AddParametersToRequest(HttpRequestMessage request, Table tableParameters)
{
var uriBuilder = new UriBuilder(request.RequestUri);

var parameters = tableParameters.CreateSet<HttpParameters>();

var headers = new List<KeyValuePair<string, IEnumerable<string>>>();
if (parameters is not null)
{
var query = HttpUtility.ParseQueryString(uriBuilder.Query);
foreach (var parameter in parameters)
{
var parameterKind = Enum.Parse<RequestParameterKind>(parameter.Kind);

if (parameterKind is RequestParameterKind.Param)
{
query.Add(parameter.Name, parameter.Value);
}

if (parameterKind is RequestParameterKind.Header)
{
var headerValue = parameter.Value.Trim().Split(",");
headers.Add(new KeyValuePair<string, IEnumerable<string>>(parameter.Name, headerValue));
}
}

uriBuilder.Query = query.ToString();
}

request.RequestUri = uriBuilder.Uri;

if (headers.Any())
{
foreach (var header in headers)
{
if (header.Key.Equals("Content-Type", StringComparison.InvariantCultureIgnoreCase))
{
throw new Exception(
"Remove the Content-Type header, please. The Content-Type header is automatically added with request step bindings.");
}

request.Headers.Add(header.Key, header.Value);
}
}
}
}
Loading