Spring Cloud Contract with multiple requestparts - spring-cloud-contract

I'm trying to write a contract to test the following providers endpoint
#PostMapping(value = "/api/{id}/addFiles", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
ResponseEntity<Void> addFiles(#PathVariable(value = "id") String id, #RequestPart("name") String name, #RequestPart("test") String test, #RequestPart("files") MultiPartFile[] files)
I'm struggling to write a contract that works with multiple RequestParts such as this. If I change the two String RequestParts to RequestParams I can get the test to pass with the following contract:
Contract.make {
description "Add Files"
request {
urlPath("/api/idString/addFiles") {
queryParameters {
parameter 'name': value(consumer(regex(nonEmpty())), producer('name'))
parameter 'test': value(consumer(regex(nonEmpty())), producer('test'))
}
}
method POST()
headers {
contentType multipartFormData()
}
multipart(
files: named(
name: value(consumer(regex(nonEmpty())), producer('fileName')),
content: value(consumer(regex(nonEmpty())), producer('fileContent'))
)
)
}
response {
status ACCEPTED()
}
}
But is there a way of writing this contract whilst keeping everything as RequestParts? Nothing I've tried so far has come close to working!

Answering my own question since I realised the small error I had been making. The following contract appears to work, I had tried something similar but had not included the square brackets inside the multipart:
Contract.make {
description "Add Files"
request {
urlPath("/api/idString/addFiles")
method POST()
headers {
contentType multipartFormData()
}
multipart([
name: named(
name: value(consumer(regex(nonEmpty())), producer('name')),
content: value(consumer(regex(nonEmpty())), producer('name'))
),
test: named(
name: value(consumer(regex(nonEmpty())), producer('test')),
content: value(consumer(regex(nonEmpty())), producer('testName'))
),
files: named(
name: value(consumer(regex(nonEmpty())), producer('fileName')),
content: value(consumer(regex(nonEmpty())), producer('fileContent'))
)
])
}
response {
status ACCEPTED()
}
}

Related

Why does HotelSearch return an ERR.NGHP-DISTRIBUTION.INTERNAL_ERROR?

I am trying to use the HotelSearch REST api. I have tested it on the Dev Studio website by Sabre and while it is slow to respond it works on a Chrome browser. I then tried to get his working in the sample app Rest2SG Sabre provide, as well as Postman. However it returns this error on both platforms:
Here is the method I have added to the sample app:
private void getHotelSearch()
{
Job job = new Job("Getting HotelSearch")
{
#Override
protected IStatus run(IProgressMonitor monitor)
{
setText("Waiting for response...");
toggleAllButtons(false);
try
{
Rest2SgRequest request =
lockId > 0 ? new Rest2SgRequest(lockId) : new Rest2SgRequest();
// for the list of available service action names
// please refer to REST documentation
// this same as action in redapp.xml authorization
request.setUrl("/v2.0.0/hotel/search");
//request.setHeaders(getContentDescription())
// previously generated document, normally developer will
// have to prepare one by himself
String payload = getRequestBody("sample.json"); // we
// preload
request.setPayload(payload);
request.setHttpMethod(HTTPMethod.POST);
request.setContentType("application/json");
request.setAuthTokenType(AuthTokenType.SESSIONLESS);
Rest2SgServiceClient client = new Rest2SgServiceClient(COM);
ClientResponse <Rest2SgResponse> rsp = client.send(request);
LOGGER.info("Rest2Sg request processing success: " + rsp.isSuccess());
if (rsp.isSuccess())
{
// check if processing ended in with success
Rest2SgResponse response = rsp.getPayload();
String responseBody = response.getResponseBody();
response.getResponseCode();
response.getResponseHeaders();
setText(responseBody);
}
else
{
printErrors(rsp.getErrors());
System.out.println(rsp.getErrors().toString());
}
}
catch (Exception e)
{
e.printStackTrace();
}
toggleAllButtons(true);
return Status.OK_STATUS;
}
};
job.schedule();
}
Here is the sample JSON:
{
"HotelSearchRQ": {
"POS": {
"Source": {
"PseudoCityCode": "43X5"
}
},
"SearchCriteria": {
"MaxResults": 20,
"SortBy": "DistanceFrom",
"SortOrder": "ASC",
"TierLabels": false,
"GeoSearch": {
"GeoRef": {
"Radius": 2,
"UOM": "MI",
"RefPoint": {
"Value": "DFW",
"ValueContext": "CODE",
"RefPointType": "6",
"StateProv": "TX",
"CountryCode": "US"
}
}
}
}
}
}
Does anyone know why I am getting the error below?
[Error [code=400, description={"errorCode":"ERR.NGHP-DISTRIBUTION.INTERNAL_ERROR","message":"Error occurred while invoking service restish:convertToOutputFormat:1.71.3","status":"Incomplete","type":"Application","timeStamp":"2022-06-20T21:00:51-05"}, type=HTTP]]
As I mentioned this JSON works on their website.
This problem is caused by missing header information. The following line needs to be added to the request:
request.setHeaders("{\"Accept\": \"application/json\"}");

Problem sending POST body to the Firestore REST API

I want to create a new document in Firestore using the REST API.
Very good examples here using Axios to send the POST request with some fields:
https://www.jeansnyman.com/posts/google-firestore-rest-api-examples/
axios.post(
"https://firestore.googleapis.com/v1/projects/<PROJECTIDHERE>/databases/(default)/documents/<COLLECTIONNAME>",
{
fields: {
title: { stringValue: this.title },
category: { stringValue: this.category },
post: { stringValue: this.post },
summary: { stringValue: this.description },
published: { booleanValue: this.published },
created: { timestampValue: new Date() },
modified: { timestampValue: new Date() }
}
}
).then(res => { console.log("Post created") })
And an example here using Python Requests:
Using the Firestore REST API to Update a Document Field
(this is a PATCH request but the field formatting is the same as in a POST request)
import requests
import json
endpoint = "https://firestore.googleapis.com/v1/projects/[PROJECT_ID]/databases/(default)/documents/[COLLECTION]/[DOCUMENT_ID]?currentDocument.exists=true&updateMask.fieldPaths=[FIELD_1]"
body = {
"fields" : {
"[FIELD_1]" : {
"stringValue" : "random new value"
}
}
}
data = json.dumps(body)
headers = {"Authorization": "Bearer [AUTH_TOKEN]"}
print(requests.patch(endpoint, data=data, headers=headers).json())
I am using Google Apps Script UrlFetchApp.fetch to send my requests. I am able to use GET requests with no problems. For example, to get all the documents in a collection (in Google Apps Script):
function firestore_get_documents(){
var options = {
headers: { 'Authorization': 'Bearer ' + ScriptApp.getOAuthToken() },
method:'GET'
}
var response = UrlFetchApp.fetch('https://firestore.googleapis.com/v1/projects/<PROJECTIDHERE>/databases/(default)/documents/myCollection', options);
var parsed = JSON.parse(response.getContentText());
return parsed;
}
This works nicely. And changing 'method' to 'POST' creates a new document in myCollection as expected. Then I try to add a POST body with some fields (or just one field):
function firestore_create_new_document(){
var options = {
headers: { 'Authorization': 'Bearer ' + ScriptApp.getOAuthToken() },
method:'POST',
payload: {fields: { title: { stringValue: 'newTitle' } } }, // If you comment out this line, it works as expected
muteHttpExceptions:true
}
var response = UrlFetchApp.fetch('https://firestore.googleapis.com/v1/projects/<PROJECTIDHERE>/databases/(default)/documents/myCollection', options);
var contentText = response.getContentText();
var parsed = JSON.parse(response.getContentText());
return parsed;
}
I get the following errors:
code: 400 message: "Request contains an invalid argument."
status: "INVALID_ARGUMENT"
details[0][#type]: "type.googleapis.com/google.rpc.BadRequest"
details[0][fieldViolations][0][field]: "{title={stringValue=newTitle}}"
details[0][fieldViolations][0][description]: "Error expanding 'fields' parameter. Cannot find matching fields for path '{title={stringValue=newTitle}}'."
Documentation is available here:
https://firebase.google.com/docs/firestore/reference/rest/v1/projects.databases.documents/createDocument
https://firebase.google.com/docs/firestore/reference/rest/v1/projects.databases.documents#Document
The problem may be the formatting of my 'fields' object - I've tried several different formats from the documentation and examples
The problem may be that the fields don't exist yet? I think I should be able to create a new document with new fields
The problem may be with the way UrlFetchApp.fetch sends my JSON body. I have tried using payload = JSON.stringify(payload_object) and that doesn't work either.
I think UrlFetchApp is doing something slightly different than Axios or Python Requests - the body is getting sent differently, and not parsing as expected.
How about the following modification?
From:
var options = {
headers: { 'Authorization': 'Bearer ' + ScriptApp.getOAuthToken() },
method:'POST',
payload: {fields: { title: { stringValue: 'newTitle' } } }, // If you comment out this line, it works as expected
muteHttpExceptions:true
}
To:
var options = {
headers: { 'Authorization': 'Bearer ' + ScriptApp.getOAuthToken() },
method:'POST',
payload: JSON.stringify({fields: { title: { stringValue: 'newTitle' } } }),
contentType: "application/json",
muteHttpExceptions:true
}
When I tested above modified request, I could confirm that it worked. But if other error occurs, please tell me.
Reference:
Class UrlFetchApp

Is there a way to show related model ids without sideloading or embedding data

My understanding is that using serializeIds: 'always' will give me this data, but it does not.
Here's what I'm expecting:
{
id="1"
title="some title"
customerId="2"
}
Instead the output I'm receiving is:
{
id="1"
title="some title"
}
My code looks something like this:
import {
Server,
Serializer,
Model,
belongsTo,
hasMany,
Factory
} from "miragejs";
import faker from "faker";
const ApplicationSerializer = Serializer.extend({
// don't want a root prop
root: false,
// true required to have root:false
embed: true,
// will always serialize the ids of all relationships for the model or collection in the response
serializeIds: "always"
});
export function makeServer() {
let server = newServer({
models: {
invoice: Model.extend({
customer: belongsTo()
}),
customer: Model.extend({
invoices: hasMany()
})
},
factories: {
invoice: Factory.extend({
title(i) {
return `Invoice ${i}`;
},
afterCreate(invoice, server) {
if (!invoice.customer) {
invoice.update({
customer: server.create("customer")
});
}
}
}),
customer: Factory.extend({
name() {
let fullName = () =>
`${faker.name.firstName()} ${faker.name.lastName()}`;
return fullName;
}
})
},
seeds(server) {
server.createList("invoice", 10);
},
serializers: {
application: ApplicationSerializer,
invoice: ApplicationSerializer.extend({
include: ["customer"]
})
},
routes() {
this.namespace = "api";
this.get("/auth");
}
});
}
Changing the config to root: true, embed: false, provides the correct output in the invoice models, but adds the root and sideloads the customer, which I don't want.
You've run into some strange behavior with how how serializeIds interacts with embed.
First, it's confusing why you need to set embed: true when you're just trying to disable the root. The reason is because embed defaults to false, so if you remove the root and try to include related resources, Mirage doesn't know where to put them. This is a confusing mix of options and Mirage should really have different "modes" that take this into account.
Second, it seems that when embed is true, Mirage basically ignores the serializeIds option, since it thinks your resources will always be embedded. (The idea here is that a foreign key is used to fetch related resources separately, but when they're embedded they always come over together.) This is also confusing and doesn't need to be the case. I've opened a tracking issue in Mirage to help address these points.
As for you today, the best way to solve this is to leave root to true and embed false, which are both the defaults, so that serializeIds works properly, and then just write your own serialize() function to remove the key for you:
const ApplicationSerializer = Serializer.extend({
// will always serialize the ids of all relationships for the model or collection in the response
serializeIds: "always",
serialize(resource, request) {
let json = Serializer.prototype.serialize.apply(this, arguments);
let root = resource.models ? this.keyForCollection(resource.modelName) : this.keyForModel(resource.modelName)
return json[root];
}
});
You should be able to test this out on both /invoices and /invoices/1.
Check out this REPL example and try making a request to each URL.
Here's the config from the example:
import {
Server,
Serializer,
Model,
belongsTo,
hasMany,
Factory,
} from "miragejs";
import faker from "faker";
const ApplicationSerializer = Serializer.extend({
// will always serialize the ids of all relationships for the model or collection in the response
serializeIds: "always",
serialize(resource, request) {
let json = Serializer.prototype.serialize.apply(this, arguments);
let root = resource.models ? this.keyForCollection(resource.modelName) : this.keyForModel(resource.modelName)
return json[root];
}
});
export default new Server({
models: {
invoice: Model.extend({
customer: belongsTo(),
}),
customer: Model.extend({
invoices: hasMany(),
}),
},
factories: {
invoice: Factory.extend({
title(i) {
return "Invoice " + i;
},
afterCreate(invoice, server) {
if (!invoice.customer) {
invoice.update({
customer: server.create("customer"),
});
}
},
}),
customer: Factory.extend({
name() {
return faker.name.firstName() + " " + faker.name.lastName();
},
}),
},
seeds(server) {
server.createList("invoice", 10);
},
serializers: {
application: ApplicationSerializer,
},
routes() {
this.resource("invoice");
},
});
Hopefully that clears things up + sorry for the confusing APIs!

How can I verify that a map's values are not empty

Suppose I have a contract like this specified in groovy:
org.springframework.cloud.contract.spec.Contract.make {
request {
method "GET"
url "/api/profiles"
headers {
header('Accept': 'application/json;charset=UTF-8')
header('Content-Type': 'application/json;charset=UTF-8')
}
}
response {
status 200
headers {
header('Content-Type': 'application/json;charset=UTF-8')
}
body(
value(
stub(
'''\
[
{
"profile": "profile1",
"myMap": {}
},
{
"profile": "profile2",
"myMap": {
"12345": "FOO",
"asdf": "BAR"
}
}
]
'''
),
test(
[
[
"profile" : regex(nonEmpty()),
"myMap": [
[
??
]
]
]
]
)
)
)
}
}
Now I want to test that the map contains String to String entries where the values must not be empty. The map itself may be empty.
How can I test for dynamic key name?
On the response side of the contract you have to chose whether you're using the map notation or the string notation. If you want to do assertions on pieces of the response you have to embed those assertions inside the body or use the test matchers.
You can put the body as a multiline string and then write the testMatchers section
testMatchers{
jsonPath('$.[*].myMap', byCommand('assertKeys($it)'))
}
then it's enough for you to provide the assertion in the assertKeys method.

AJAX Call using Enyo Framework

I am trying to make an ajax call using the enyo framework and I am running headlong in to a problem. The error message I am getting is 0. That's it just a 0. I made sure my link to the json file was correct and I built this jsfiddle to test it out http://jsfiddle.net/mmahon512/CPU8n/2/ Any help is greatly appreciated. My host is GoDaddy and I made sure that I added the json extension to my web config correctly. The link to the json file is correct and it returns valid json. I checked it using jsonlint. Here is what the code looks like on jsfiddle:
enyo.kind({
name: "AjaxSample",
components: [
{ kind: "Button", content: "Fetch Users", ontap: "fetch" },
{ name: "repos", content: "Not loaded...", allowHtml: true }
],
fetch: function() {
var ajax = new enyo.Ajax({
url: "http://atxapps.com/_sites/atxapps.com/dev/jetstream/assets/dataUsers.json"
});
ajax.go();
ajax.response(this, "gotResponse");
ajax.error(this, this.gotError);
},
gotResponse: function(inSender, inResponse) {
var output = "";
for(i = 0; i < inResponse.length; i++) {
output += inResponse[i].Id + "";
}
output += Date.now();
this.$.repos.setContent(output);
},
gotError: function(inSender, inError) {
alert(inError);
this.$.repos.setContent(inError + " " + Date.now());
}
});
Looks like a CORS issue. I see the following in the console:
No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin fiddle.jshell.net is therefore not allowed access.
I wrapped it as a jsonp request successfully.
http://jsfiddle.net/CPU8n/3/
enyo.kind({
name: "AjaxSample",
components: [
{ kind: "Button", content: "Fetch Users", ontap: "fetch" },
{ name: "repos", content: "Not loaded...", allowHtml: true }
],
fetch: function() {
var ajax = new enyo.JsonpRequest({
url: "http://jsonpwrapper.com/?urls%5B%5D=http%3A%2F%2Fatxapps.com%2F_sites%2Fatxapps.com%2Fdev%2Fjetstream%2Fassets%2FdataUsers.json"
});
ajax.go();
ajax.response(this, "gotResponse");
ajax.error(this, this.gotError);
},
gotResponse: function(inSender, inResponse) {
var output = "";
var body = enyo.json.parse(inResponse[0].body); // jsonpwrapper.com wraps the results in a array with an index for each URL. The actual data is in the body param of that index but it isn't parsed (at least in this example)
for(i = 0; i < body.length; i++) {
output += body[i].Id + "<br />";
}
output += Date.now();
this.$.repos.setContent(output);
},
gotError: function(inSender, inError) {
alert(inError);
this.$.repos.setContent(inError + " " + Date.now());
}
});
If you're running this on the same server in prod, you wouldn't see the error (since it's not cross-origin). If it'll be on a different server, you can either convert the server-side to support jsonp or adds the appropriate CORS headers.

Resources