I'm using swagger codegen for my REST API calls. For authentication purposes i need to send a session-token within the headers of every request. This is currently done, via APIClients' defaultHeaders
open class ApiClient(val baseUrl: String) {
companion object {
...
#JvmStatic
var defaultHeaders: Map<String, String> by ApplicationDelegates.setOnce(mapOf(ContentType to JsonMediaType, Accept to JsonMediaType))
...
}
}
The way swagger generates the code, these headers can only be modified once.
ApiClient.defaultHeaders += mapOf("Authorization" to userSession!!.idToken.jwtToken)
The problem with this is, that i cannot change the token (e.g. because another user logged in within the application lifetime). Looking deeper into the generated code, before each request is sent, a merge of both defaultHeaders and requestConfig.headers (=contentHeaders) is being made.
inline protected fun <reified T: Any?> request(requestConfig: RequestConfig, body : Any? = null): ApiInfrastructureResponse<T?> {
...
val headers = defaultHeaders + requestConfig.headers
...
}
The given RequestConfig object comes from every api call. However it is not possible to change these contentHeaders. Also they are empty by default.
fun someAPIRestCall(someParam: kotlin.String) : Unit {
val localVariableBody: kotlin.Any? = type
val localVariableQuery: MultiValueMap = mapOf()
val contentHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf() // THESE WILL BE MERGED WITH defaultHeaders
val acceptsHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf("Accept" to "application/json")
val localVariableHeaders: kotlin.collections.MutableMap<kotlin.String,kotlin.String> = mutableMapOf()
localVariableHeaders.putAll(contentHeaders)
localVariableHeaders.putAll(acceptsHeaders)
val localVariableConfig = RequestConfig(
RequestMethod.POST,
"someEndpointURL"),
query = localVariableQuery,
headers = localVariableHeaders // THESE WILL BE MERGED WITH defaultHeaders
)
val response = request<Unit>(
localVariableConfig,
localVariableBody
)
...
}
Is it possible to tell swagger-codegen to include some kind of parameter to the generated method signature to add values to those contentHeaders?
EDIT:
This is the current code-gen call within my gradle build chain
task generateSwagger(type: JavaExec) {
main = "-jar"
args "swagger-codegen-cli-2.4.7.jar", "generate", "-i", "./swagger_core.yml", "-l", "kotlin", "-o", "./tmp/RestApi", "--type-mappings", "number=kotlin.Long"
}
By now, i found a solution, that is more of a hack, but it works.
As i am using gradle to build the app, i introduced a task, that changes the generated swagger code, before it actually compiles.
task editAPISources {
def token = "Map<String, String> by ApplicationDelegates.setOnce(mapOf(ContentType to JsonMediaType, Accept to JsonMediaType))"
def value = "MutableMap<String, String> = mutableMapOf(ContentType to JsonMediaType, Accept to JsonMediaType)"
def file = new File("./app/tmp/RestApi/src/main/kotlin/io/swagger/client/infrastructure/ApiClient.kt")
def newConfig = file.text.replace(token, value)
file.write newConfig
}
The result is a now changeable header :=
#JvmStatic
var defaultHeaders: MutableMap<String, String> = mutableMapOf(ContentType to JsonMediaType, Accept to JsonMediaType)
Related
I want to build a Gatling scenario from a collection of structured data (StructuredDataCollection).
My problem is, that I'm unable to pass in the "method" (as in HTTP method) from an element from the collection into the http call of the actual test.
Here's a code snippet.
def testScenario(duration: Int) = scenario("SO").during(duration) {
exec {
session => {
val test = StructuredDataCollection.next()
val title = test.title
val method = test.method // Not being used, because it does not work like that :(
val endpoint = test.endpoint
val requiredParameters = test.requiredParameters
val code = test.code
session
.set("title", title)
.set("methodFUG", method).set("endpoint", endpoint)
.set("requiredParameters", requiredParameters)
.set("code", code)
}
}
.exec(
http("${title}")
.httpRequest("get", "${endpoint}") // TODO: method can't be passed in as an expression.
.queryParamMap("${requiredParameters}")
.check(status.is("${code}"))
)
}
As you can see, I've hard-coded "get", but I'll need that to be replaced with the actual value from the method property from the current selected item from the collection.
Unfortunately, Gatling's DSL isn't available in all the places where you'd expect it to be, and it's just reading that as a string.
It took me some time to realize, that
http("${title}").httpRequest("${methodFUG}", "${endpoint}") will actually make a HTTP call with the invalid method "${methodFUG}" and not the value from the collection element, which could be "GET", "POST", "PUT", "DELETE", and so on.
httpRequest signature is (method: String, url: Expression[String]), see documentation.
It cannot take an Expression, only a static String.
I'm writting unit tests and i would like to know if it's possible to mock an object that is instanced inside the method that i'm testing.
Here is an example of a method that i would like to test:
def sendMessageToBroker(message:Message) = {
val soapBody = xmlBody("user", "pass", message.identifier,
message.to, message.message)
val response = new WebServiceUtil().doPost("uri", soapBody.toString(),
"text/xml; charset=utf-8", "action")
response
}
I was wondering if it's possible to do something like:
when call doPost, return new Response(200, 'Success')
Is it possible?
I've tried do it using spy() and mock, but no sucess:
val ws = new WebServiceUtil
val spiedObj = spy(ws)
spiedObj.doPost("uri", xml,
"text/xml; charset=utf-8",
"action") returns new Response(200, "Success")
val xx = messageService.sendMessageToBroker(new Message())
Any ideas on how can i do it?
You could write
val webService = mock[WebServiceUtil]
webService.doPost("uri", xml, "text/xml; charset=utf-8", "action") returns
new Response(200, "Success")
// pass the mock webservice as an argument
sendMessageToBroker(new Message, webService)
The important part is that you need to be able to pass a mock WebServiceUtil to your method being tested! There are many ways to do that. The simplest one is to pass the instance to a constructor method of our "class under test":
class MyClass(webService: WebServiceUtil) {
def sendMessageToBroker(message: Message) = {
// use the webservice
}
}
A more involved method would use Guice and the Inject annotation to pass the service (especially since you tagged the question as a play-framework-2.0 one). You will be interested in following this SOF question then.
I'm trying to make GET and then PUT call on XML REST web service.
I do it this way:
#Grab('org.codehaus.groovy.modules.http-builder:http-builder:0.7')
import groovyx.net.http.HTTPBuilder
import static groovyx.net.http.ContentType.*
import static groovyx.net.http.Method.*
import groovy.xml.XmlUtil
def url = "http://localhost:81"
def pathPrefix = "/api/v1"
def http = new HTTPBuilder(url)
def profile = http.request(GET, XML) { req ->
uri.path = "$pathPrefix/profiles/55"
response.success = {resp, xml ->
xml
}
}
println XmlUtil.serialize(profile) // this is fine!
Now i'm going to change and save
profile.name = "New Name"
// this is not fine (i have 400 Bad Request)
// because it sends body not in XML
def savedProfile = http.request(PUT, XML) { req ->
uri.path = "$pathPrefix/profiles/55"
body = profile
response.success = {resp, xml ->
xml
}
}
println XmlUtil.serialize(savedProfile)
When i make PUT request HTTPBuilder do not send XML. It sends string, made of profile.toString().
It it not what i'm expecting.
How to send XmlSlurper object (that i obtained earlier) in PUT request?
Thank you.
I think i found the solution.
When i define body configuration value, i have to write
body = {
mkp.yield profile
}
I am using playframework 2.6 and play-slick 0.8.0.
Action code:
def addCompany = Authenticated {
DBAction(parse.json) {
implicit rs => {
val newCompany = rs.request.body
val result = CompanyTable.insert(newCompany.as[Company])(rs.dbSession)
if(result > 0)
Ok("{\"id\":"+result+"}")
else
Ok("New company was not created.")
}
}
}
The Action is a composition of an Action that just checks for a valid session and the DBAction, which requires the request body to have a valid JSON object.
Test code:
"should create a Company from a Json request" in new InMemoryDB {
val newCompany = Company(name = "New Company1")
val fr = FakeRequest(POST, "/company")
.withSession(("email", "bob#villa.com"))
.withHeaders(CONTENT_TYPE -> "application/json")
.withJsonBody(Json.toJson(newCompany))
val action = controllers.CompanyController.addCompany
val result = action(fr).run
status(result) should be_==(OK)
(contentAsJson(result) \ "id").as[Long] should be_>(1L)
}
The InMemoryDB class is just a FakeApplication with a pre-populated in memory database.
The issue that I am having is that when the test runs the result is always a 400 with body content containing a message saying [Invalid Json]. When I call the service using curl with the same JSON body content, it works and the id is returned.
I decided to build a separate test project, and I used the activator to create a seed for the new project. I noticed that in the generated test that a different method of calling the action was used, so I switched my project to use this method. It worked, but I don't know why.
New code:
"should create a Company from a Json request" in new InMemoryDB {
val newCompany = Company(name = "New Company1")
val action = route(
FakeRequest(POST, "/company")
.withSession(("email", "bob#villa.com"))
.withHeaders(CONTENT_TYPE -> "application/json")
.withJsonBody(Json.toJson(newCompany))
).get
status(action) should be_==(OK)
(contentAsJson(action) \ "id").as[Long] should be_>(1L)
}
As you can see it uses a call to route instead of calling the controller.
I want to add the CacheControl intormation to a GET service that use the json binding.
I found that to add the cacheControl to a response the REST service sound like this:
#GET
#Path("cacheheadertest")
#Produces({"*/*"})
def testcache():javax.ws.rs.core.Response {
val rb:Response.ResponseBuilder = javax.ws.rs.core.Response.ok("chached test message")
val cc = new CacheControl()
cc.setMaxAge(60)
cc.setNoCache(false)
rb.cacheControl(cc).build()
}
but I have a REST service that produce json messages and the jersey library transform automaticcally the java object from java to xml/json.
#GET
#Path("jsontestcache")
#Produces(Array(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML))
def myjsontestservice(#HeaderParam("X-TOKENID") tokenId: String,
#QueryParam("clientId") clientId: String):com.test.MyResultClass = {
val response= new com.test.MyResultClass
[...]
response
}
How can I add the cache control to the response of myjsontestservice service? Do I need to use a filter and append the cachecontrol once the response has been created by jersey?
thanks million
Flavio
You would still need to return a Response object.
def somejson() : Response = {
val builder = Response.ok(new com.test.MyResultClass);
val cc = new CacheControl()
cc.setMaxAge(60)
cc.setNoCache(false)
builder.cacheControl(cc).build()
}
Jersey's interceptors will automatically convert your class into a JSON object.