Negative (database dependent) test cases in Spring Cloud Contract - spring-cloud-contract

I am writing spring contract for a simple API that receives account number and returns "good" response
if this account exists in the database or "bad" response otherwise. How do i specify "bad" response in the contract if the request that causes "bad" responses has the same format as "good" request?
My Java classes:
#PostMapping("/postrequest")
public AccountDto postMethod(#RequestBody FindAccountRequest rq){
return service.findAccountByNumber(rq);
}
public class FindAccountRequest {
String accountNumber;
}
public class AccountDto {
Integer balance;
Integer errorCode;
}
Contracts:
Contract.make {
request {
description("Existing account — good response")
method POST()
url '/postrequest'
headers { contentType(applicationJson()) }
body( [ accountNumber: $( regex('[0-9]{20}') ) ] )
}
response {
status 200
headers { contentType(applicationJson()) }
body( [balance: anyInteger()] )
}
}
Contract.make {
request {
description("Nonexistent account — bad response")
method POST()
url '/postrequest'
headers { contentType(applicationJson()) }
body( [ accountNumber: $( regex('[0-9]{20}') ) ])
}
response {
status 200
headers { contentType(applicationJson()) }
body( [errorCode: anyInteger()] )
}
}
Request:
{
accountNumber: "12345678901234567890"
}

Prepare two different account numbers one for the positive case and one for the negative one. Two different contracts

Related

Akka http unsupported method exception

I have a plain http route defined like so
delete {
handleErrorsAndReport("delete_foo") {
fooRepository.deleteFoo(fooId)
complete(NoContent)
}
}
I wanted to add some basic authentication to it so now it looks like
seal(
authenticateBasic(
realm = "Secure foo",
scopeAuthenticator
) { scopes =>
delete {
handleErrorsAndReport("delete_foo") {
fooRepository.deleteFoo(fooId, scopes)
complete(NoContent)
}
}
}
)
The full directive is
concat(
get {
// something else which is working
},
seal(
// something else which is working
),
seal(
authenticateBasic(
realm = "Secure foo",
scopeAuthenticator
) { scopes =>
delete {
handleErrorsAndReport("delete_foo") {
fooRepository.deleteFoo(fooId, scopes)
complete(NoContent)
}
}
}
)
)
Now I am getting the following exception when I am trying to delete foos
Request DELETE http://builder/foos/fooId failed with response code 405 due to request error. Response body: HTTP method not allowed, supported methods: PUT
What could be the issue? The way I've been consuming the API has not changed but I'm afraid that something has changed with the introduction of the seal directive.
I have no idea why but this worked
concat(
get {
// something else which is working
},
seal(
authenticateBasic(
realm = "Secure foo",
scopeAuthenticator
) { scopes =>
concat(
delete {
handleErrorsAndReport("delete_foo") {
fooRepository.deleteFoo(fooId, scopes)
complete(NoContent)
},
put {//other authenticated endpoint}
)
}
}
)
)
In other words, I consolidated the two sealed directives which were before separate. Maybe it's related to this

Http post request with body parameters not working

Recently I started developing a small application in Flutter. I have an issue with making a network request. I have tried the call in postman and there it work. But in Flutter I never managed to make it work, I have spent like 3 hours trying to understand what I am doing wrong.
Any help will be greatly appreciated.
#override
Future<String> login(common.LoginParameters loginParameters) async {
try {
final String loginURL = "https://test.example.eu/api/login";
LoginModel loginResult;
Map bodyParams = { "inlognaam" : loginParameters.username , "wachtwoord" : loginParameters.password, "code" : loginParameters.smsCode};
//await API call
http.Response httpResponse = await http.put( loginURL, body: json.encode(bodyParams));
if (httpResponse.statusCode == 200) {
// If server returns an OK response, parse the JSON
loginResult= LoginModel.fromJson(json.decode(httpResponse.body));
} else {
// If that response was not OK, throw an error.
throw Exception('Failed to load post');
}
// if logged in get token, Otherwise return error
if (loginResult.ingelogd) {
// read the token
saveToken(loginResult.response);
return "Ingelogd";
} else {
return loginResult.error;
}
}
on Exception catch(error) {
print("Todor " + error.toString());
return "Controleer uw internet verbinding en probeer opnieuw";
}
}
In Postman if I select Post request with body parameters
inlognaam : someUsername
wachtwoord : somePassword
code : someCode
Then I get a success response
I pass the parameters in the following way, maybe it can work for you:
var response = await http.post(
url,
headers:{ "Accept": "application/json" } ,
body: { "state": 1}, //key value
encoding: Encoding.getByName("utf-8")
);
Another thing, you say that in postman you make a post request, but in your code you have a put request, verify what is the correct method

Settings HTTP output header parameters working with aggregation

I am trying to change the contentType from the response of an aggregated operation, here is my example code.
interface MyAggregateInterface {
RequestResponse:
op1(typeOp1Request)(typeOp1Response)
}
outputPort MyAggregatePort {
Interfaces: MyAggregateInterface
}
embedded {
Jolie:
"MyAggratedCode.ol" in MyAggregatePort
}
inputPort MyInputPortHttp {
Protocol: http {
.debug= 1;
.debug.showContent =1;
.format -> format;
.contentType -> mime;
.charset ="UTF-8";
.default = "default";
.compression = false
}
Location: "socket://localhost:8081"
Interfaces: DefaultHttpInterface
Aggregates: MyAggregatePort
}
I would like to change the return format for op1.
well I will try to answer your question
we need to define your op1 response type
type typeOp1Response:any{
.format?:string
}
or if you prefer
type typeOp1Response:undefined
I personally prefer the first one so that you can decide the mime in the aggregated service
Now you need to add a courier sessions
courier MyInputPortHttp {
[interface MyAggregateInterface( request )( response )]{
forward( request )( response );
if (is_defined(response.format)){
mime = response.format;
undef(response.format);
}
}
This implementation has a limitation that can return flat data in the root node
Another way is to use the inputType to define your output format.
type typeOp1Request:void{
.otherParameter1:string
.format?:string
}
then your courier
courier MyInputPortHttp {
[interface MyAggregateInterface( request )( response )]{
forward( request )( response );
if (request.format=="json"){
mime = "application/json"
};
if (request.format=="xml"){
mime = "application/xml"
};
}
Not sure if this answers your question
As Balint pointed out, we are missing some information on the nature of the response.
However, it seems to me that the second example better covers the general case. We abstract from any information coming from the aggregated service (which ignores the fact it is aggregated) and we decide what to do with the response, based on local logic (within the aggregator).
Following Balint's example, we can wrap the aggregated operation with a courier and define the format of the output there. I include below a minimal working example.
Aggregated service
type PersonRequestType: void {
.name: string
}
type PersonResponseType: void {
.name: string
.surname: string
}
interface MyAggregatedInterface {
RequestResponse: op1( PersonRequestType )( PersonResponseType ) throws RecordNotFound
}
inputPort IN {
Location: "local"
Interfaces: MyAggregatedInterface
}
execution { concurrent }
main
{
op1( request )( response ){
if( request.name == "Mario" ){
response.name = "Mario";
response.surname = "Mario"
} else {
throw ( RecordNotFound )
}
}
}
Aggregator service
include "aggregated.ol"
outputPort MyAggregatePort { Interfaces: MyAggregatedInterface }
embedded { Jolie: "aggregated.ol" in MyAggregatePort }
inputPort HttpPort {
Location: "socket://localhost:8000"
Protocol: http {
.format -> format
}
Aggregates: MyAggregatePort
}
courier HttpPort {
[ interface MyAggregatedInterface( request )( response ) ]{
forward( request )( response );
format = "json" // e.g., alternative xml
}
}
By changing the value set to format, e.g., from "json" to "xml", we change the format of the HTTP response.
References:
Courier sessions in the Jolie documentation
Reference introduction of couriers and detailed example of its semantics, Pre-print version, https://doi.org/10.1109/SOCA.2012.6449432

Response for preflight has invalid HTTP status code 405 with asp.net web api and Angular 2

i was trying to integrate my asp.net web api with angular 2.for checking login credential in a basic way.
asp.net web api code is
public void postvalidUser(string UserName, string Password)
{
try
{
var login = uData.Users.Where(x => x.UserName == UserName).ToList();
if (login != null && login.Count > 0)
{
foreach (var log in login)
{
if (log.UserName == UserName && log.Password == Password)
{
Console.WriteLine("login ok");
}
}
}
else
{
Console.WriteLine("login fail");
}
}
catch (Exception ex){
Console.WriteLine(ex);
}
}
login.service.ts file is as:-
#Injectable()
export class LoginService {
constructor(private _http: Http) { }
postvalidUser(UserName: string, Password: string) {
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({headers: headers });
const users = { UserName: UserName, Password: Password };
return this._http
.post("http://localhost:65440/api/User", users, options)
.map(result => result.json());
}
}
login.component.ts
export class LoginComponent implements OnInit {
constructor(private _loginservices: LoginService) { }
ngOnInit() {
}
OnClick(UserName: string, Password:string) {
this._loginservices.postvalidUser(UserName, Password).subscribe(users => console.log(users));
}
}
now when i m running my app it showing me the error as
Failed to load "my-url": Response for preflight has invalid HTTP status code 405 && OPTIONS "my-url" 405 (Method Not Allowed)
Please tell me whether my code is wrong or something is missing.
You have to authorize the OPTIONS requests in your backend.
The same way you authorize GET, POST and PUT requests, simply add OPTIONS to that list.
OPTIONS are, as stated, preflight requests sent by your browser to check the endpoints.
Your browser is sending off an OPTIONS request.
You need to either create a HttpOptions endpoint with the same route, or create a middle ware that intercepts all HttpOptions requests and returns a 200.

Response to give for partially successful post to Web API

What is the best response to give, if a number of objects are sent to my web api controller, to be inserted into my database, where some may be successful, and some may fail? A normal HTTP response I don't think will suffice - would it be better to find some way of returning a JSON string of what has been successful, and what has not? If so, how would I do that?
My Post controller is shown below.
Thanks for any help,
Mark
public HttpResponseMessage PostBooking(Booking[] bookings)
{
if (ModelState.IsValid)
{
foreach (var booking in bookings)
{
// check if there are any bookings already with this HID and RID...
var checkbooking = db.Bookings.Where(h => h.HID == booking.HID && h.RID == booking.RID).ToList();
// If so, return a response of conflict
if (checkbooking.Count != 0 || checkbooking.Any())
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Conflict));
}
else
{
// If not add the booking to the database and return a response of Created
db.Bookings.Add(booking);
db.SaveChanges();
}
}
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created);
return response;
}
else
{
// Model is not valid, so return BadRquest
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
You could return a JSON list containing the ids of the objects that failed to be inserted:
{
"failedIds": [
4,
7,
9
]
}
500 HTTP response status code also seems appropriate as the request didn't complete successfully.
You could even bring that a step further and provide an explanation why insertion failed for each particular id:
{
"failed": [
{
"id": 4,
"reason": "database unavailable"
},
{
"id": 7,
"reason": "network cable unplugged"
},
{
"id": 9,
"reason": "a thief is currently running away with our server"
}
]
}

Resources