Make Pact-Test fail on additional provider field - pact

The default behavior for a Pact test is to not fail when the provider adds fields to a message that the contract of the consumer does not specify, eg:
Consumer expects:
{
"foo": "bar"
}
Provider provides:
{
"foo": "bar",
"hello": "world"
}
A Pact contract-test using the above noted messages would succeed. Is there a way to make them fail? Some "strict"-mode for example that requires exact matches of messages?

No, that's not possible according to the pact docs:
You cannot expect a field to not be present in a response
Following Postel's law, the provider may return fields that the consumer will just ignore. The provider may have a pact with another consumer that requires those fields, so your consumer cannot tell the provider what it should not do. It can only specify what it should do.

Related

Disable Only HealthCheck Related Logging

I'm trying to figure out a way to disable any health-check related ILogger logging. I am aware of LogLevel filtering, but that will not work here.
As an example, I have a healthcheck that makes an outbound call to a RabbitMQ metrics API. This results in an outbound http call with every inbound call to /health. In general, I want to log all outbound calls made using the HttpClient, but that log is now full of this particular log entry:
[2021.06.15 13:57:04] info: System.Net.Http.HttpClient.Default.LogicalHandler[101] => ConnectionId:0HM9FV5PFFL5K => RequestPath:/health RequestId:0HM9FV5PFFL5K:00000001, SpanId:|6726c52-4217ec92de4df5fb., TraceId:6726c52-4217ec92de4df5fb, ParentId: => Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckLogScope => HTTP GET http://rabbitmq/api/queues/MyVHost/MyQueue?msg_rates_age=60&msg_rates_incr=60
: End processing HTTP request after 4.6355ms - OK
So, I could apply a warning filter to the HttpClient/LogicalHandler to remove those entries, but then I'd be removing all the info logs of other outbound Http requests, which I don't want.
So, basically, I need a smarter filter that can look at the scopes (or even the text in certain cases), and can filter out based on "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckLogScope". That doesn't seem to be possible though, as the filter callback doesn't provide those details.
Does anyone have any idea how to do more specific log filtering for cases like this?
I have looked at .net core log filtering on certain requests, but extending every ILoggerProvider I use isn't possible, since some are not public classes.
You could use Serilog for logging, as it also provides great filtering, enriching, and formatting capabilities with Serilog.Expressions Nuget package.
There is even a simple example provided in the link above for filtering health checks, which fulfilled my needs to fully filter out health check logging based on the request path '/health'. Following the guide, it only required adding appsettings.json configuration, after the serilog was configured as the application logger to make it work:
{
"Serilog": {
"Using": ["Serilog.Expressions"],
"Filter": [
{
"Name": "ByExcluding",
"Args": {
"expression": "RequestPath like '/health%'"
}
}
]
}
}
To filter out those requests you can try using the Log4Net StringMatchFilter filter with the value of the health URL.
Here is the code for the configuration file if health URL is just localhost/health:
<filter type="log4net.Filter.StringMatchFilter">
<stringToMatch value="/health" />
<acceptOnMatch value="false" />
</filter>

PACT - Using provider state

I am trying to use pact for validating the spring boot microservices. I have generated the pact file from consumer and verified it in the provider side using pact broker.
I have another use case where I need to execute some code before validating the pact file against actual service response. I read about state change URL and state change with closure to achieve it but couldnt get an example of how to achieve this. Can someone help?
My specific situation is: I have created a contract to update customer with id 1234 (First Name: test Last Name: user).
If this customer doesnt exist, then i would need to insert this data into DB by reading the first name, last name, id from the update request in pact file and additional info (city, state, phone number) through state change code.
So my question is, can i read the request data from pact file through state change instead of configuring the first name,last name and id in the verification side?
The state change URL is a hook you create on the provider to allow Pact to tell the provider what state it should be in at the start of the test. Before each test runs, the mock consumer taps the state change URL on your provider, and tells it the name of the state the test expects.
You need to do two things:
Configure a state change URL
Implement the state change endpoint on the provider
Configuring the state change URL
You can configure the state change URL in the provider verification settings. For example, using the the maven plugin:
<serviceProvider>
<name>provider1</name>
<stateChangeUrl>http://localhost:8080/tasks/pactStateChange</stateChangeUrl>
...
Or using the Gradle provider plugin:
hasPactWith('consumer1') {
stateChangeUrl = url('http://localhost:8080/tasks/pactStateChange')
...
Both of these tell the mock consumer to use localhost:8080/tasks/pactStateChange to change the state of the provider before each test.
Implementing the state change endpoint
The documentation linked above tells us that by default, the format of the request is a POST request of your state string and any parameters:
{ "state" : "a provider state description", "params": { "a": "1", "b": "2" } }
To use this, you implement something like the following untested code on the provider :
#RequestMapping(value = "tasks/pactStateChange", method = RequestMethod.POST)
ResponseEntity<?> stateChange(#RequestBody ProviderState state) {
if (state.state == "no database") {
// Set up state for the "no database" case here
} else if state.state == "Some other state" {
// Set up state here
} else if ... // Other states go here
...
}
return ResponseEntity.ok().build()
}
Please excuse any spring boot errors in that example - I'm not a spring boot person, but you can see the general principle.
With the state change URL, pact doesn't tell the provider any setup details. It just tells the provider the pre-agreed state string that you used in your test. This could be something like "foo exists". Then, when implementing the handler for the state change URL, you detect "foo exists", and do any explicit setup there.
if (state.state == "foo exists") {
// do whatever you need to set up so that foo exists
repository.clear()
repository.insert(new Foo("arguments that foo needs",12))
}
If you'd like to know more about the intent of provider states, have a read of the wiki page on provider states.
How to do this in your specific case
You asked:
Can i read the request data from pact file through state change instead of configuring the first name,last name and id in the verification side?
You might be confused about the intention of the contract tests - each test is a combination of state and request.
So instead of using one test to say:
My test is to request a customer update. If the customer exists, then I expect X response, and if it doesn't, then I expect Y response
you use two tests to say:
When I submit an update to the customer record (in the state when the customer exists), then I expect X response.
When I submit an update to the customer record (in the state where the customer does NOT exist), then I expect Y response.
These tests are two separate items in your Pact contract.
The intention is not to include details of the setup in the contract. On the consumer side, your state is just a string that says something like "Customer with id=1234 exists".
On the Provider side, your state change endpoint detects that URL and creates the state as appropriate. This is usually done in a hard-coded way:
if (state == "Customer with id=1234 exists") {
Database.Clear()
Database.Insert(new Customer(1234, "John","Smith"))
} else if (state == "No customers exist") {
Database.Clear()
}
You don't want to do this in a parameterised way by parsing the state string, because then you're creating a new complex contract between the test consumer and the provider.
The consumer tests shouldn't know anything about how to set provider state, they should just know what state is required by the test (by name only). Similarly, the provider doesn't need to know what's being tested, it just needs to know how to turn state names into actual state.

Encoded / Encrypted body before verifying a Pact

A server I need to integrate with returns its answers encoded as a JWT. Worse, the response body actually is a json, of the form:
{d: token} with token = JWT.encode({id: 123, field: "John", etc.})
I'd like to use a pact verification on the content of the decoded token. I know I can easily have a pact verifying that I get back a {d: string}, I can't do an exact match on the string (as the JWT contains some varying IDs). What I want is the following, which presumes the addition of a new Pact.JWT functionality.
my_provider.
upon_receiving('my request')
.with(method: :post,
path: '/order',
headers: {'Content-Type' => 'application/json'}
).will_respond_with(
status: 200,
headers: {'Content-Type' => 'application/json; charset=utf-8'},
body: {
d: Pact::JWT( {
id: Pact.like(123),
field: Pact.term(generate: "John", matcher: /J.*/
},signing_key,algo
)
})
Short of adding this Pact::JWT, is there a way to achive this kind of result?
I am already using the pact proxy to run my verification. I know you can modify the request before sending it for verification (How do I verify pacts against an API that requires an auth token?). Can you modify the request once you receive it from the proxy server?
If that's the case, I can plan for the following work around:
a switch in my actual code to sometimes expect the answers decoded instead of in the JWT
run my tests once with the swich off (normal code behaviour, mocks returns JWT data encoded.
run my tests a second time with the swich off (code expect data already decoded, mocks return decoded data.)
use the contract json from this second run
hook into the proxy:verify task to decode the JWT on the fly, and use the existing pact mechanisms for verification. (Step that I do not know how to do).
My code is in ruby. I do not have access to the provider.
Any suggestions appreciated! Thanks
You can modify the request or response by using (another) proxy app.
class ProxyApp
def initialize real_provider_app
#real_provider_app = real_provider_app
end
def call env
response = #real_provider_app.call(env)
# modify response here
response
end
end
Pact.service_provider "My Service Provider" do
app { ProxyApp.new(RealApp) }
end
Pact as a tool, I don't expect it to give this behavior out of the box.
In my opinion, the best is,
Do not change source code only for tests
Make sure your tests verifies encoded json only (generate encoded expected json in test & verify that with actual)

Bad Argument, Invalid Subscription Key when trying to add key from Azure

I am trying to set up my LUIS app in luis.ai (because it seems like I can't set it up in Azure directly?). Anyway, I have created a Language Understanding Intelligent Service (LUIS) (preview) resource, but when I enter one of the keys from that resource into "My Keys" in luis.ai, I get this error: "Bad Argument, Invalid Subscription Key"
Also, I just tried to publish an app with the bootstrap key and got the following errors:
{ "statusCode": 401, "message": "Access denied due to invalid subscription key. Make sure to provide a valid key for an active subscription." }
As provided in comments, the solution is to use a correct end-point. This is not clear anywhere, but for the luis.ai portal, I have to use WestUS endpoint.
If I change to useing eu.luis.ai, I have to use the WestEU end-point.
To use european LUIS endpoint within Bot Builder C# SDK, just modify Luis Model parameters as follows:
[LuisModel("YOUR-LUIS-APP-ID", "YOUR-LUIS-EUROPEAN-KEY", domain:
"westeurope.api.cognitive.microsoft.com")]
Note that you'll need to export your LUIS app over EU.LUIS in order to access the correct endpoint.
Hope it helps.

The OpenID Provider issued an assertion for an Identifier whose discovery information did not match

I used sample code from DotNetOpenAuth.net to become my own OpenID Provider (OpenIDProviderWebForm) everything worked fine and I was able to test my OP against NerdDinner. now I want to customize the identifier like --->
http://www.mydomain.com/user.aspx/Hash(Username+PrivateKey)~Username.
everything works on OP side but on the NerdDinner application when the app tries to do
HttpRequestInfo clientResponseInfo = new HttpRequestInfo("GET", auth, auth.PathAndQuery, headers, null);
response = RelyingParty.GetResponse(clientResponseInfo);
(you can find these two lines of codes in AuthController.cs from NerdDinner)
the response contains below error:
The OpenID Provider issued an assertion for an Identifier whose discovery information did not match.
Assertion endpoint info:
ClaimedIdentifier: http://localhost:57571/user.aspx/76208371132EC7F7A37472C8B4CC2CC37A05B707~sohail
ProviderLocalIdentifier: http://localhost:57571/user.aspx/76208371132EC7F7A37472C8B4CC2CC37A05B707~sohail
ProviderEndpoint: http://localhost:57571/server.aspx
OpenID version: 2.0
Service Type URIs:
Discovered endpoint info: [
{
ClaimedIdentifier: http://localhost:57571/user.aspx/EA467E35736AC22EB60C04C2E9D9594263B60ECB~sohail
ProviderLocalIdentifier: http://localhost:57571/user.aspx/EA467E35736AC22EB60C04C2E9D9594263B60ECB~sohail
ProviderEndpoint: http://localhost:57571/server.aspx
OpenID version: 2.0
Service Type URIs:
http://specs.openid.net/auth/2.0/signon
http://openid.net/extensions/sreg/1.1
}, {
ClaimedIdentifier: http://localhost:57571/user.aspx/EA467E35736AC22EB60C04C2E9D9594263B60ECB~sohail
ProviderLocalIdentifier: http://localhost:57571/user.aspx/EA467E35736AC22EB60C04C2E9D9594263B60ECB~sohail
ProviderEndpoint: http://localhost:57571/server.aspx
OpenID version: 1.0
Service Type URIs:
http://openid.net/signon/1.0
http://openid.net/extensions/sreg/1.1
},
]
anybody can help me please?
The relying party is reporting that the Provider is asserting information about an identifier that doesn't match what the OpenID discovery produces about that same identifier.
Your responsibility as a Provider includes making sure your assertion matches the identifier information. So consider what is under the "Discovered endpoint info" in the error message, and why it's different than what was asserted (above it). In this case, your ClaimedIdentifier and ProviderLocalIdentifier URLs are different between the asserted and discovered data. This may be because an HTTP GET on the asserted identifier actually generates an HTTP redirect to the URL under "discovered endpoint info", which you'd need to fix.

Resources