C# PACT - Consumer Driven Contact testing - writing test for provider - c#

I am trying to get my head about PACT and I am using the PACT-Net library to achieve this.
My tests on the Consumer are working fine but I am trying to setup the tests on the Provider. I am using the basic Web API project that loads when you use the Web API template within Visual Studio - which creates the Values API controller. I am just testing the Get IEumerable<string> method as an end to end test of the process. I am also following the example on the PACT-Net github site. Here is the test I have so far:
[Fact]
public void EnsureValuesReturnedFromApi()
{
var config = new PactVerifierConfig
{
Outputters = new List<IOutput>
{
new XUnitOutput(_output)
}
};
using (WebApp.Start<TestStartup>(serviceUri))
{
var pactVerifier = new PactVerifier(config);
pactVerifier.ProviderState($"{serviceUri}/provider-states")
.ServiceProvider("Values API", serviceUri)
.HonoursPactWith("Consumer")
.PactUri("http://localhost:8080/pacts/provider/Values%20API/consumer/Consumer/latest")
.Verify();
}
}
When ever I run the unit test I get the following error:
Reading pact at http://localhost:8080/pacts/provider/Values%20API/consumer/Consumer/latest
Verifying a pact between Consumer and Values API
Given When I want the values
Getting a list
with GET /api/values
returns a response which
has status code 200 (FAILED - 1)
has a matching body (FAILED - 2)
includes headers
"Accept" with value "application/json" (FAILED - 3)
"Content-Type" with value "application/json" (FAILED - 4)
Failures:
1) Verifying a pact between Consumer and Values API Given When I want the values Getting a list with GET /api/values returns a response which has status code 200
Failure/Error: set_up_provider_state interaction.provider_state, options[:consumer]
Pact::ProviderVerifier::SetUpProviderStateError:
Error setting up provider state 'When I want the values' for consumer 'Consumer' at http://localhost:9222/provider-states. response status=500 response body=
2) Verifying a pact between Consumer and Values API Given When I want the values Getting a list with GET /api/values returns a response which has a matching body
Failure/Error: set_up_provider_state interaction.provider_state, options[:consumer]
Pact::ProviderVerifier::SetUpProviderStateError:
Error setting up provider state 'When I want the values' for consumer 'Consumer' at http://localhost:9222/provider-states. response status=500 response body=
3) Verifying a pact between Consumer and Values API Given When I want the values Getting a list with GET /api/values returns a response which includes headers "Accept" with value "application/json"
Failure/Error: set_up_provider_state interaction.provider_state, options[:consumer]
Pact::ProviderVerifier::SetUpProviderStateError:
Error setting up provider state 'When I want the values' for consumer 'Consumer' at http://localhost:9222/provider-states. response status=500 response body=
4) Verifying a pact between Consumer and Values API Given When I want the values Getting a list with GET /api/values returns a response which includes headers "Content-Type" with value "application/json"
Failure/Error: set_up_provider_state interaction.provider_state, options[:consumer]
Pact::ProviderVerifier::SetUpProviderStateError:
Error setting up provider state 'When I want the values' for consumer 'Consumer' at http://localhost:9222/provider-states. response status=500 response body=
1 interaction, 1 failure
Failed interactions:
* Getting a list given When I want the values
I guess my question is, do I need to actually test the HTTP calls to /api/values or am I missing something else?
Thanks

So after speaking to a colleague and getting a hint, it was down to the Startup class. I hadn't realised it effectively starts the web application so
public class TestStartup
{
public void Configuration(IAppBuilder app)
{
var startup = new Startup();
app.Use<ProviderStateMiddleware>();
startup.Configuration(app);
}
}
calls the startup class within the application:
public class Startup
{
public void Configuration(IAppBuilder app)
{
var httpConfig = new HttpConfiguration();
httpConfig.MapHttpAttributeRoutes();
httpConfig.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
var appXmlType = httpConfig.Formatters.XmlFormatter.SupportedMediaTypes
.FirstOrDefault(t => t.MediaType == "application/xml");
httpConfig.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);
app.UseWebApi(httpConfig);
}
}
and my unit test passed. Hope this helps someone.

Related

ASP.NET Web Site Not Allowing Axios PUT Access (405 Method Not Allowed)

I've got a controller set up and working for all of my GET requests, but when it comes to the PUT requests my Web Site (not a Web App, if that makes any difference) is returning a 405.
I've got the route defined in my Global.asax Application_Start() with the other routes, and it's the first one, so it should be evaluated first (if my understanding is correct):
void Application_Start(object sender, EventArgs e)
{
System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls
| System.Net.SecurityProtocolType.Tls11
| System.Net.SecurityProtocolType.Tls12;
RouteTable.Routes.MapHttpRoute("FilteredSpecialOrders",
routeTemplate: "api/sales/filteredRequests",
defaults: new { controller = "Sales", action = "filteredRequests" });
// subsequent routes are here
}
My SalesController has a method with the right attributes for type of request (Put) and the action name that matches my routeTemplate, as well as the [FromBody] attribute on the parameter:
[HttpPut, ActionName("filteredRequests")]
public IHttpActionResult PutSpecialOrders([FromBody] RequestFilter filter)
{
// do the needful
}
...and my client side code creates the body of the message (filter) as a JavaScript object and sends the .put requests via axios:
getFilteredRequests: async function () {
let filter = {
name: this.name,
age: this.yearsOld,
/* other name/value pairs of course */
};
const response = await axios.put(salesApi + 'filteredRequests', filter);
let data = response.data;
return data;
}
...but I'm always getting back a 405 - Method Not Allowed. What am I forgetting? I'm not sure how the JSON object that gets sent in the body is serialized into my RequestFilter object - does that happen automagically or do I need to define that somewhere? I've made sure that the names are the same on both ends, but other than that...
I edited my web.config to remove the WebDAV bits per this post - but I also had to switch my pipeline from classic mode to integrated to get it to work.

Azure does not display custom error message for hosted .net core API

I have the following API endpoint:
[AllowAnonymous]
[HttpPost("login")]
public async Task<IActionResult> Authenticate([FromBody] LoginViewModel credentials)
{
try
{
var result = await _facade.SomeMethodThatFailsAndThrowsCustomCode4001(credentials);
return Ok(result);
}
catch (CustomException cEx)
{
return StatusCode(4001, new { Message = cEx.FriendlyMessage ?? "" }); //Message = Our custom user friendly message
}
}
When hosted on external server through IIS, these messages were returned perfectly. When hosted on Azure, the messages are not showing, and this is the response received:
{
"Message": ""
}
I have read about Policies allowed in on-error, but this means I will need to register my .Net Core API with the API management in Azure, and not sure if this is necessary.
What would be required to be configured to allow our custom messages returned through our API hosted in Azure?
You should read RFC doc about HTTP Status Codes.
1xx: Informational - Request received, continuing process
2xx: Success - The action was successfully received, understood, and accepted
3xx: Redirection - Further action must be taken in order to complete the request
4xx: Client Error - The request contains bad syntax or cannot be fulfilled
5xx: Server Error - The server failed to fulfill an apparently valid request
First, after testing, the upper limit of the usable range of StatusCode is 999. When StatusCode >999, errors will be reported all the time.
Second, you also can handle custom status code in Startup.cs.
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseStatusCodePages(async context =>
{
if (context.HttpContext.Response.StatusCode == 401)
{
await context.HttpContext.Response.WriteAsync("Custom Unauthorized request");
}
});
//other middlewars
Related Post:
1. How can I return custom error message for error status code 401 in .Net Core?
2. How to return a specific status code and no contents from Controller?

Is it possible to call an Dynamics CRM action with a JSON request through a WebApi with IOrganizationService?

TL;DR:
I am calling a WebApi, the WebApi authenticates against the CRM and use the IOrganizationService, so my request is a JObject and not an Entity or EntityReference, it gives me this error:
Error: Type 'Newtonsoft.Json.Linq.JToken' is a recursive collection data contract which is not supported. Consider modifying the definition of collection 'Newtonsoft.Json.Linq.JToken' to remove references to itself.
Context:
I built a web application in angular and I built a WebApi so I can call some custom actions in CRM:
Angular APP | WebApi | OnPremise CRM
So, when I call the WebApi, there is a controller that turns my request into a OrganizationRequest:
Request for WebApi:
{
"ActionName": "custom_actionname",
"Parameters":
[
{
"Key": "EntityInputParameter1",
"Value": {"#odata.type":"Microsoft.Dynamics.CRM.any_entity"}
}
]
}
I read this request on my WebApi and turn that into a request for CRM
Request for CRM:
OrganizationRequest request = new OrganizationRequest("custom_actionname");
request.Parameters["EntityInputParameter1"] = {"#odata.type":"Microsoft.Dynamics.CRM.any_entity"} // This is a JObject
OrganizationResponse response = service.Execute(request);
When I make the request, it gives me the following error:
Error: Type 'Newtonsoft.Json.Linq.JToken' is a recursive collection data contract which is not supported. Consider modifying the definition of collection 'Newtonsoft.Json.Linq.JToken' to remove references to itself.
If I make the request directly to the action it works, but I cannot do that due security policies.
One option could be turn the request into a valid CRM request (parsing {"#odata.type":"Microsoft.Dynamics.CRM.any_entity} into a Entity type) but CRM has a lot of parsing escenarios and could be very complex.
Another option could be sending the request through web and stop using the IOrganizationService but I cannot change that.
I am making this question so anybody that has this error can find the "solution" because I searched a lot and nobody refers this behavior directly.
I am probably turning my InputEntityParameter into string, and I will send the JSON, so I can parse the JSON on my action, but I was looking if anybody else had this error or another approach.
I tested it on one of my Dev Environment with Entity as Parameter.
Below is the code I used in console application to fire Action with Entity as parameter. It ran successfully
var request = new OrganizationRequest("new_test");
//request.Parameters.Add("Target", xAccountReference);
request.Parameters.Add("Param2", "abc");
request.Parameters.Add("Param1", new Entity("account",Guid.Parse("2fe32f22-d01d-ea11-80fa-005056936c69")));
Service.Execute(request);
Below is the Javascript code which used CRM Webapi to execute Action with Parameter. Ignore the XRM.Webapi command but interesting for you would be passing parameters in webapi.
var parameters = {};
parameters.Param2 = "abcd";
var param1 = {};
param1.accountid = "2fe32f22-d01d-ea11-80fa-005056936c69"; //Delete if creating new record
param1["#odata.type"] = "Microsoft.Dynamics.CRM.account";
parameters.Param1 = param1;
var new_testRequest = {
Param2: parameters.Param2,
Param1: parameters.Param1,
getMetadata: function() {
return {
boundParameter: null,
parameterTypes: {
"Param2": {
"typeName": "Edm.String",
"structuralProperty": 1
},
"Param1": {
"typeName": "mscrm.account",
"structuralProperty": 5
}
},
operationType: 0,
operationName: "new_test"
};
}
};
Xrm.WebApi.online.execute(new_testRequest).then(
function success(result) {
if (result.ok) {
//Success - No Return Data - Do Something
}
},
function(error) {
Xrm.Utility.alertDialog(error.message);
}
);
I can confirm that you are mixing Webapi and orgservice call. You can definitely call Action from Webapi of Dynamics. I just used Postman to call Action and I was successful. Blog reference to use Postman for CRM webapi
Below Body as json in Postman and I get Action to run.
{
"Param1":"string test",
"Param2":{
"accountid":"b6b35fd0-b9c3-e311-88e2-00505693000c",
"#odata.type":"Microsoft.Dynamics.CRM.account"
}
}

Cognitive Services Operation Status API not returning any results

As per this link, Cognitive Services Topic Detection API not returning any results!
I have tried calling the OperationStatus API after the request from DetectTopics API has successfully been submitted. I created an AzureMachineLearningTextAnalytics object that I call the OperationStatus (OperationId , Key).
The response that I got back was : Status : RanToCompletion.
The result is however coming as null and the error message that I got when I debug into the generated REST API call was
"Internal error while executing BES operation".*
Has anyone been able to get results from the OperationStatus API? Can anyone please provide any suggestions regarding this issue ?
When using the Azure Cognative Services Text Analytics REST APIS with the generated Swagger definitions, use the WithOperationalResponseAsync method to get the operational response related to the request. From that you can query the status of the original request. A simplified C# example is as follows:
AzureMachineLearningTextAnalytics textAnalyzer = new AzureMachineLearningTextAnalytics();
var topicResult = textAnalyzer.DetectTopicsWithOperationResponseAsync(textAnalyticsAccountKey, null, null, null, topicDetection);
string operationId = topicResult.Result.Response.Headers.Location.Segments[topicResult.Result.Response.Headers.Location.Segments.Length - 1];
var status = textAnalyzer.OperationStatus(operationId, textAnalyticsAccountKey);
while ((((MySample.TextAnalytics.Models.OperationResult)status).Status == "NotStarted") ||
(((MySample.TextAnalytics.Models.OperationResult)status).Status == "Running"))
{
System.Threading.Thread.Sleep(20000);
status = textAnalyzer.OperationStatus(operationId, textAnalyticsAccountKey);
}
if (((Terawe.Retail.TextAnalytics.Models.OperationResult)status).Status == "Failed")
{
// Log an error to the console
Console.WriteLine($"Topic detection failed with status: {((MySample.TextAnalytics.Models.OperationResult)status).Message}");
}
else
{
// Process the phrases
}

MapHttpRoute with custom HttpMessageHandler

So I'm having a troubles getting the route to work properly after the messagehandler has finished.
Error that shows up are:
No HTTP resource was found that matches the request URI
this is what I got so far:
http://localhost:51077/api/v1/project/getprojects?apikey=123456
// all actions under /project routes require authentication
config.Routes.MapHttpRoute(
"ProjectApi",
"api/v1/{controller}/{action}/{apikey}",
new { apikey = RouteParameter.Optional },
null,
HttpClientFactory.CreatePipeline(
new HttpControllerDispatcher(config),
new DelegatingHandler[]{new BasicAuthHandler(config)}));
// all routes requires an api key
config.MessageHandlers.Add(new ApiKeyHandler());
config.MapHttpAttributeRoutes();
[RoutePrefix("api/v1/ProjectController")]
public class ProjectController : BaseApiController
{
[HttpGet]
[Route("getprojects")]
public HttpResponseMessage GetProjects()
{
HttpResponseMessage resp = new HttpResponseMessage(HttpStatusCode.OK);
if(User.Identity.IsAuthenticated)
{
}
return resp;
}
}
So, all calls will first be checked if they have an ApiKey included to be able to connect (ApiKeyHandler) Then a popup appears and asks for username and password(BasicAuthHandler). If the log in is a success then it should be forwarded to the getprojects method under /project...
ApiKey is checked, username/password popup appears and is granted but then the error above comes and the route seems to be to invalid. I've tried different ways to get this to work but it seems I'm missing something here.
PROBLEM SOLVED
Just comment this line out and it works
[Route("getprojects")] // <--- COMMENT/REMOVE THIS LINE
Any ideas to why this was causing the problem?
Your issue doesn't replicate in my test that I tried at my end. It seems you have different names given to your controller in your RoutePrefix attribute. According to the request Url given:
http://localhost:51077/api/v1/project/getprojects?apikey=123456
RoutePrefix attibute on ProjectController class should look like:
[RoutePrefix("api/v1/Project")]

Categories