Cannot create Dynamic Scenario Outline via Java call in Karate.
I can create a Dynamic Scenario Outline with "hard-coded" Json Array for example:
* def foobar = [{ 'type': 'app' }]
But when I attempt to generate the same Json Array from a Java class, I always get the following Karate warning(s) and the Dynamic Scenario Outline never executes:
WARN com.intuit.karate - ignoring dynamic expression, did not evaluate to list
-- OR --
WARN com.intuit.karate - ignoring dynamic expression list item 0, not map-like
I've tried using the Karate key-words 'def', 'string', 'json' as the var type but no luck. I've even tried hard-coding the same string shown above in the Java method with no luck.
I declare/call my Java class in 'Background:' and print what is given back and it "looks" correct.
Background:
* def IdaDataApiUtil = Java.type('data.IdaDataApiUtil')
* def foobar = IdaDataApiUtil.getClientExample('ida-sp')
* print foobar
I then try to utilize the JsonArray in my 'Example:' like such:
Examples:
| foobar |
At this point I get the above mentioned error(s), depending on what I've tried to return (JsonArray, JsonObject, Map, List).
If I simply use the hard-coded 'def':
* def foobar = [{ 'type': 'app' }]
It works as expected.
In my Java code I've tried various things:
Hard-coded Json string:
public static String getClientExample() {
return "[{ 'type': 'app' }]";
}
List:
public static List<String> getClientExample() {
List<String> list = new ArrayList<>();
list.add("'type': 'app'");
return list
}
Map:
public static Map<String, Object> getClientExample() {
Map<String, Object> map = new HashMap<>();
map.put("type", "app");
return map;
}
I've played with variations of key/values in both list/map with no luck. I've also tried with JSONObject/JSONArray but no luck there as well.
I feel like I'm missing something vary obvious but I can't see the forest through the trees at the moment...
I attempt to generate the same Json Array from a Java class,
What you return from the Java code should be a List<Map<String, Object>> and it should work fine. You have tried List<String> and that is the problem.
Read this section of the docs: https://github.com/intuit/karate#type-conversion
Another tip, you can try a "cast" to ensure it is in the JSON array form you need, so if you are too lazy to form properly nested Map-s, just return a raw JSON string from Java, and the below line will convert it correctly.
* json foobar = foobar
Related
I'm writing some tests using rest-assured and its Kotlin extensions to test some simple Spring MVC endpoints. I'm trying to understand how to extract values.
One endpoint returns a BookDetailsView POJO, the other returns a Page<BookDetailsView> (where Page is an interface provided by Spring for doing paging).
BookDetailsView is a really simple Kotlin data class with a single field:
data class BookDetailsView(val id: UUID)
For the single object endpoint, I have:
#Test
fun `single object`() {
val details = BookDetailsView(UUID.randomUUID())
whenever(bookDetailsService.getBookDetails(details.id)).thenReturn(details)
val result: BookDetailsView = Given {
mockMvc(mockMvc)
} When {
get("/book_details/${details.id}")
} Then {
statusCode(HttpStatus.SC_OK)
} Extract {
`as`(BookDetailsView::class.java)
}
assertEquals(details.id, result.id)
}
This works as expected, but trying to apply the same technique for the Page<BookDetailsView> runs afoul of all sorts of parsing challenges since Page is an interface, and even trying to use PageImpl isn't entirely straightforward. In the end, I don't even really care about the Page object, I just care about the nested list of POJOs inside it.
I've tried various permutations like the code below to just grab the bit I care about:
#Test
fun `extract nested`() {
val page = PageImpl(listOf(
BookDetailsView(UUID.randomUUID())
))
whenever(bookDetailsService.getBookDetailsPaged(any())).thenReturn(page)
val response = Given {
mockMvc(mockMvc)
} When {
get("/book_details")
} Then {
statusCode(HttpStatus.SC_OK)
body("content.size()", `is`(1))
body("content[0].id", equalTo(page.first().id.toString()))
} Extract {
path<List<BookDetailsView>>("content")
}
println(response[0].javaClass)
}
The final println spits out class java.util.LinkedHashMap. If instead I try to actually use the object, I get class java.util.LinkedHashMap cannot be cast to class BookDetailsView. There are lots of questions and answers related to this, and I understand it's ultimately an issue of the underlying JSON parser not knowing what to do, but I'm not clear on:
Why does the "simple" case parse without issue?
Shouldn't the type param passed to the path() function tell it what type to use?
What needs configuring to make the second case work, OR
Is there some other approach for grabbing a nested object that would make more sense?
Digging a bit into the code, it appears that the two cases may actually be using different json parsers/configurations (the former seems to stick to rest-assured JSON parsing, while the latter ends up in JsonPath's?)
I don't know kotlin but here is the thing:
path() doesn't know the Element in your List, so it'll be LinkedHashMap by default instead of BookDetailsView.class
to overcome it, you can provide TypeReference for this.
java example
List<BookDetailsView> response = ....then()
.extract().jsonPath()
.getObject("content", new TypeRef<List<BookDetailsView>>() {});
kotlin example
#Test
fun `extract nested`() {
var response = RestAssured.given().get("http://localhost:8000/req1")
.then()
.extract()
.jsonPath()
.getObject("content", object : TypeRef<List<BookDetailsView?>?>() {});
println(response)
//[{id=1}, {id=2}]
}
I'd like to be able to use Micronaut's declarative client to hit an a different endpoint based on whether I'm in a local development environment vs a production environment.
I'm setting my client's base uri in application.dev.yml:
myserviceclient:
baseUri: http://localhost:1080/endpoint
Reading the docs from Micronaut, they have the developer jumping through quite a few hoops to get a dynamic value piped into the actual client. They're actually quite confusing. So I've created a configuration like this:
#ConfigurationProperties(PREFIX)
class MyServiceClientConfig {
companion object {
const val PREFIX = "myserviceclient"
const val BASE_URL = "http://localhost:1080/endpoint"
}
var baseUri: String? = null
fun toMap(): MutableMap<String, Any> {
val m = HashMap<String, Any>()
if (baseUri != null) {
m["baseUri"] = baseUri!!
}
return m
}
}
But as you can see, that's not actually reading any values from application.yml, it's simply setting a const value as a static on the class. I'd like that BASE_URL value to be dynamic based on which environment I'm in.
To use this class, I've created a declarative client like this:
#Client(MyServiceClientConfig.BASE_URL)
interface MyServiceClient {
#Post("/user/kfc")
#Produces("application/json")
fun sendUserKfc(transactionDto: TransactionDto)
}
The docs show an example where they're interpolating values from the config map that's built like this:
#Get("/api/\${bintray.apiversion}/repos/\${bintray.organization}/\${bintray.repository}/packages")
But how would I make this work in the #Client() annotation?
Nowhere in that example do they show how bintray is getting defined/injected/etc. This appears to be the same syntax that's used with the #Value() annotation. I've tried using that as well, but every value I try to use ends up being null.
This is very frustrating, but I'm sure I'm missing a key piece that will make this all work.
I'm setting my client's base uri in application.dev.yml
You probably want application-dev.yml.
But how would I make this work in the #Client() annotation?
You can put a config key in the #Client value using something like #Client("${myserviceclient.baseUri}").
If you want the url somewhere in your code use this:
#Value("${micronaut.http.services.occupancy.urls}")
private String occupancyUrl;
I'm using HOCON to configure log messages and I'm looking for a way to substitute placeholder values dynamically.
I know that ${?PLACEHOLDER} will read an environment variable and returns an empty string when the PLACEHOLDER environment variable doesn't exist.
Example
This is an example of what I had in mind:
(I'm using config4k to load HOCON )
data class LogMessage(val message: String, val code: String)
fun getMessage(key: String, placeholderValues: Array<String> = arrayOf()): LogMessage {
val config = ConfigFactory.parseString("MY_LOG_MESSAGE {code = ABC-123456, message = My log message with dynamic value %0% and another dynamic value %1% }")
val messageObject = config.extract<LogMessage>(key)
var message = messageObject.message
placeholderValues.forEachIndexed { placeholderIndex, value ->
message = message.replace("%$placeholderIndex%", value)
}
return messageObject.copy(message = message)
}
fun main(args: Array<String>) {
println(getMessage("MY_LOG_MESSAGE", arrayOf("value 1", "value 2")))
// prints: LogMessage(message=My log message with dynamic value value 1 and another dynamic value value 2, code=ABC-123456)
}
Even though this works, it doesn't look like the best solution and I assume there already is a solution for this.
Could someone tell me if there is a built-in solution?
First things first.
HOCON is just a glorified JSON format.
config4k is just a wrapper.
All your work is being done by Typesafe Config, as you've probably noticed.
And judging by their documentation and code, they support placeholders only from withing the file, or from the environment:
This library limits itself to config files. If you want to load config
from a database or something, you would need to write some custom
code.
But for what you're doing, simple String.format() should be enough:
fun interpolate(message: String, vararg args: Any) = String.format(message, *args)
println(interpolate("My message was %s %s %s %s", "a", 1, 3.32, true))
// My message was a 1 3.32 true
Notice that you can use * to destructure your array.
I would like to pass a complete JSON object to a java adapter in worklight. This adapter will call multiple other remote resources to fulfill the request. I would like to pass the json structure instead of listing out all of the parameters for a number of reasons. Invoking the worklight procedure works well. I pass the following as the parameter:
{ "parm1": 1, "parm2" : "hello" }
Which the tool is fine with. When it calls my java code, I see a object type of JSObjectConverter$1 being passed. In java debug, I can see the values in the object, but I do not see any documentation on how to do this. If memory serves me, the $1 says that it is an anonymous inner class that is being passed. Is there a better way to pass a json object/structure in adapters?
Lets assume you have this in adapter code
function test(){
var jsonObject = { "param1": 1, "param2" : "hello" };
var param2value = com.mycode.MyClass.parseJsonObject(jsonObject);
return {
result: param2value
};
}
Doesn't really matter where you're getting jsonObject from, it may come as a param from client. Worklight uses Rhino JS engine, therefore com.mycode.MyClass.parseJsonObject() function will get jsonObject as a org.mozilla.javascript.NativeObject. You can easily get obj properties like this
package com.mycode;
import org.mozilla.javascript.NativeObject;
public class MyClass {
public static String parseJsonObject(NativeObject obj){
String param2 = (String) NativeObject.getProperty(obj, "param2");
return param2;
}
}
To better explain what I'm doing here, I wanted to be able to pass a javascript object into an adapter and have it return an updated javascript object. Looks like there are two ways. The first it what I answered above a few days ago with serializing and unserializing the javascript object. The other is using the ScriptableObject class. What I wanted in the end was to use the adapter framework as described to pass in the javascript object. In doing so, this is what the Adapter JS-impl code looks like:
function add2(a) {
return {
result: com.ibm.us.roberso.Calculator.add2(a)
};
The javascript code in the client application calling the above adapter. Note that I have a function to test passing the javascript object as a parameter to the adapter framework. See the invocationData.parameters below:
function runAdapterCode2() {
// x+y=z
var jsonObject = { "x": 1, "y" : 2, "z" : "?" };
var invocationData = {
adapter : "CalculatorAdapter",
procedure : 'add2',
parameters : [jsonObject]
};
var options = {
onSuccess : success2,
onFailure : failure,
invocationContext : { 'action' : 'add2 test' }
};
WL.Client.invokeProcedure(invocationData, options);
}
In runAdapterCode2(), the javascript object is passed as you would pass any parameter into the adapter. When worklight tries to execute the java method it will look for a method signature of either taking an Object or ScriptableObject (not a NativeObject). I used the java reflection api to verify the class and hierarchy being passed in. Using the static methods on ScriptableObject you can query and modify the value in the object. At the end of the method, you can have it return a Scriptable object. Doing this will give you a javascript object back in the invocationResults.result field. Below is the java code supporting this. Please note that a good chunk of the code is there as part of the investigation on what object type is really being passed. At the bottom of the method are the few lines really needed to work with the javascript object.
#SuppressWarnings({ "unused", "rawtypes" })
public static ScriptableObject add2(ScriptableObject obj) {
// code to determine object class being passed in and its heirarchy
String result = "";
Class objClass = obj.getClass();
result = "objClass = " + objClass.getName() + "\r\n";
result += "implements=";
Class[] interfaces = objClass.getInterfaces();
for (Class classInterface : interfaces) {
result += " " + classInterface.getName() ;
}
result += "\r\nsuperclasses=";
Class superClass = objClass.getSuperclass();
while(superClass != null) {
result += " " + superClass.getName();
superClass = superClass.getSuperclass();
}
// actual code working with the javascript object
String a = (String) ScriptableObject.getProperty((ScriptableObject)obj, "z");
ScriptableObject.putProperty((ScriptableObject)obj, "z", new Long(3));
return obj;
}
Note that for javascript object, a numeric value is a Long and not int. Strings are still Strings.
Summary
There are two ways to pass in a javascript object that I've found so far.
Convert to a string in javascript, pass string to java, and have it reconstitute into a JSONObject.
Pass the javascript object and use the ScriptableObject classes to manipulate on the java side.
I am a fairly new developer in Grails using STS 3.2 (grails version 2.2.0) and I have an application where I want to export data from a GSP to PDF format. I have installed the Export 1.5 plugin and have a reportController with the following:
def pdf = { results->
def table = results['tables'][params.reportNum.toInteger()]
def headers = table?.getAt(0).collect{ it.key }
def rows = table*.collect{ cleanNull(it.value.toString()) } //data
exportService.export("$params.renderAs", response.outputStream, headers, rows, parameters)
}
Can someone help me get this wired up correctly? I do know that the exportService expects this, but not sure if I have everything collected that I need to to get this to work:
export(String type, OutputStream outputStream, List objects, Map formatters, Map parameters) }
I hope I have been clear with my question...Thanks in advance!
The example of the plugin have params.format as the export type. What's the content of your params.renderAs? It seems that the value must be one of the keys of grails.mime.types.
if(params?.format && params.format != "html"){
response.contentType = grailsApplication.config.grails.mime.types[params.format]
response.setHeader("Content-disposition", "attachment; filename=books.${params.extension}")
exportService.export(params.format, response.outputStream,Book.list(params), [:], [:])
}
So in your case renderAs should have pdf.
Ok, so looking in the ExportService, and your code example, I think that the signature of method that you want to use is:
export(String type, OutputStream outputStream, List objects, List fields, Map labels, Map formatters, Map parameters)
objects: list of objects that will be exported
fields: name of fields, that exists in the objects that will be exported
labels: A map of fields and his labels
formatters: can be an empty map
parameters: can be an empty map
Considering a book domain class:
class Book {
String title
String author
}
You can create an action like:
def pdf() {
List fields = ["author", "title"]
Map labels = [author: "Author", title: "Title"]
exportService.export(params.format, response.outputStream, Book.list(params), fields, labels, [:], [:])
}