Is there a way to validate soap action inside the mock feature files in Karate? - karate

I have a scenario to mock a soap service containing multiple soap actions. The response for every soap action is different. Is there a way to define all these in the same mock feature file?
I am looking for something like below.
When Scenario pathmatches(url);
if either soap action is the operation name or request contains a specific xml tag;
return response 1;
else
return response 2;

Is there a way to define all these in the same mock feature file?
Yes. Why not.
The selling point of Karate is that you can express any complicated logic as JavaScript expressions. Please refer to this section on conditional logic for ideas: https://github.com/intuit/karate#conditional-logic
The link above is for the http-client, but you can use the same concepts for mocks. A simple approach I recommend is to duplicate multiple Scenario: sections, maybe the pathMatches() will be repeated, but it will be very readable.
Scenario: pathMatches('/foo') && paramValue('client') != null
# do something
Scenario: pathMatches('/foo') && requestHeaders['SOAPAction'][0] == 'myaction'
You can also create a helper function in the Background:
Background:
* def isAction = function(a){ var tmp = requestHeaders['SOAPAction']; return tmp ? tmp[0] == a : null }
Scenario: pathMatches('/foo') && isAction('myaction')

You can also use the headerContains() function.
Scenario: pathMatches('/foo') && headerContains('SOAPAction','myaction')
see: https://intuit.github.io/karate/karate-netty/#headercontains

Related

Loopback 4: Authorization decoration of CRUDRestController - is it in anyway possible?

Wondering if anybody in the community has any experience or guidance on how one could use
Authorization decorators (or any custom decoration?)(https://loopback.io/doc/en/lb4/Decorators_authorize.html) on CrudRestController endpoints? (https://loopback.io/doc/en/lb4/Creating-crud-rest-apis.html).
Looked at the src for crud-rest.controller.ts and it just seems like there is no way to really do it.
It seems like it's not easily possible to use any decoration of endpoints in a CrudRestController without taking a very hacky approach and/or wholesale duplicating the code in crud-rest.controller.ts and that we'll have to basically write every endpoint for every model by hand.
Maybe someone has come up with something or has some guidance on an approach? Is the only way to use auth with CrudRestController with the AuthorizationComponent as of now to use Authorizer functions (https://loopback.io/doc/en/lb4/Authorization-component-authorizer.html)
Seems like one part lies in this :
https://github.com/loopbackio/loopback4-example-shopping/blob/9188104c01516a5cbd4ce13f28abe18bafef821e/packages/shopping/src/services/basic.authorizor.ts
/**
* Allow access only to model owners, using route as source of truth
*
* eg. #post('/users/{userId}/orders', ...) returns `userId` as args[0]
*/
if (currentUser[securityId] === authorizationCtx.invocationContext.args[0]) {
return AuthorizationDecision.ALLOW;
}
So I ended up doing :
async authorize(
context: AuthorizationContext,
metadata: AuthorizationMetadata,
) {
const parent = context.invocationContext?.parent
const request = parent?.getBinding("rest.http.request").getValue(parent)
const givenUserId = request?.body?.userId
// next line finds out the user id in the JWT payload
const jwtUserId = context?.principals[0]?.payload?.sub
if (!jwtUserId || (givenUserId && givenUserId != jwtUserId)) {
return AuthorizationDecision.DENY;
} else {
return AuthorizationDecision.ALLOW;
}
}
as my userId is provided in the http parameters (post form or get parameters)
I also use a custom JTWService to read the payload and make it available in the UserProfile.
This may not be the best way to do it, but so far it works. I am still working on finding out how to deal with read requests and add a filter on all of them by userId too using decorators I will post my finding here, if nothing better show up first here.

Api Platform pagination custom page_parameter_name

I have very specific question on which I cannot find any answer and/or solution provided for Api Platform.
By default, the documentation states, that if you want to pass a page parameter for paging action, you must do the following:
pagination:
page_parameter_name: _page
However, due to the nature of our frontend we're not able to pass this variable to the request. It is hardcoded to the frontend request and is something like page[number]=1.
Is it possible to configure page_parameter_name to receive this variable or we need to transform it somehow in the Api itself?
Thank you!
ApiPlatform\Core\EventListener\ReadListener::onKernelRequest gets $context['filters'] from the request through ApiPlatform\Core\Util\RequestParser::parseRequestParams which ultimately uses PHP's parse_str function so the value of 'page[number]' will be in $context$context['filters']['page']['number'].
ApiPlatform\Core\DataProvider\Pagination::getPage retrieves the page number from $context['filters'][$parameterName] so whatever the value of [$parameterName] it will at best retrieve the array ['number'=> 1].
Then ::getPage casts that to int, which happens to be 1. But will (at least with PHP7) be 1 for any value under 'number'.
Conclusion: You need to transform it somehow in the Api itself. For example by decoration of the ApiPlatform\Core\DataProvider\Pagination service (api_platform.pagination).
API_URL?page[number]=2
print_r($request->attributes->get('_api_pagination'));
Array(
[number] => 2
)
The value of the "page_parameter_name" parameter should be "number" .
api_platform.yaml
collection:
pagination:
page_parameter_name: number
This may not work in version 3
vendor/api-platform/core/src/JsonApi/EventListener/TransformPaginationParametersListener.php
public function onKernelRequest(RequestEvent $event): void
{
$request = $event->getRequest();
$pageParameter = $request->query->all()['page'] ?? null;
...
/* #TODO remove the `_api_pagination` attribute in 3.0 */
$request->attributes->set('_api_pagination', $pageParameter);
}

Reuse feature/scenario response

I have an architectural problem, as I am wondering how to structure my features. I will give an example what I want to achieve, without code duplication.
-scenarios
--directoryA
---feature1
----scenario1
----scenario2
--directoryB
---feature2
----scenario3
Feature1: Users
#create_user
Scenario1: Create a User
* url 'someUrl'
Given request `somerequest`
When method post
Then status 201
And match response == { id: #uuid }
* def userId = response.id
#create_user_key
Scenario2: Create a User key
* url 'someUrl'
* def createUser = call read('this:users.feature#create_user')
Given path 'userId', 'keys'
When method post
Then status 201
And match response == { key: #string }
* def userKey = response.key
Feature2: Tokens
Scenario3: Create token
* url 'someUrl'
* def createUser = call read('classpath:scenarios/directoryA/feature1.feature#create_user_key')
* def userHeader =
"""
function() {
return "Bearer " + userKey
}
"""
Given path 'userId', 'tokens'
And header Authorization = userHeader
When method post
Then status 201
As far as I know, it is suggested Scenario1 and Scenario2 to be in a separate file. Here comes my question:
I need both Scenario1 and Scenario2 in order Scenario3 to be executed (userId and userKey are needed). If I call Scenario2, where should I store values in order to avoid code duplication?
I am aware that Scenario does not store values, but I do not want to create Background with Scenario1 and Scenario2 in Feature2, when these are stored in another feature. It doesn't matter whether it's a one scenario per feature or more.
Thanks
Take some time and read this section of the documentation very carefully: https://github.com/intuit/karate#calling-other-feature-files - and then read the section on "Shared Scope".
Short answer - you can "return" variables from a called feature. I don't see the problem, after the call - you can start using variables from createUser like this (or refer to it directly by path):
* def userId = createUser.userId
All this said - I will caution you that you appear to have gotten into the trap of attempting "too much reuse" - a common problem with teams doing test-automation. Yes, sometimes "reuse is bad" - it is okay if you don't believe me - but this needs to be said. For an example, please read this answer: https://stackoverflow.com/a/54126724/143475

How to use a JSON response data set from a feature to build multiple new endpoints

I have a json file with different sets of data (array of more than 5 rows). I would like to use it as path and query parameters to build new endpoints and validate the response of newly built endpoints to find a specific value. I would like to run it as loop.
And I would like you to actually take some time to read the documentation and then ask specific questions. Please refer this:
https://github.com/intuit/karate#dynamic-scenario-outline
Feature: scenario outline using a dynamic table
Background:
* def kittens = read('../callarray/kittens.json')
Scenario Outline: cat name: <name>
Given url demoBaseUrl
And path 'cats'
And request { name: '<name>' }
When method post
Then status 200
And match response == { id: '#number', name: '<name>' }
# the single cell can be any valid karate expression
# and even reference a variable defined in the Background
Examples:
| kittens |
Thanks Peter. I am trying to test whether an event is published into kafka topic. It is 2 step validation
Step 1: Find out the start & end offset and partitions present in a topic ( a JSON response)
Step 2: Use the data from JSON response from Step 1 to dig through each partition to find the right event.
Checking if Karate can support this.

how to do pagination in RESTFUL API in a effective way?

I want to support pagination in my RESTful API.
My API method should return a JSON list of product via http://localhost/products/v1/getproductsbycategory, there are potentially thousands of products, and I want to page through them, so my request should look something like this:
public function getProductsByCategory($product_id,$page){
$perPage=5;
$start=($page-1)*$perPage;
$stmt=$this->conn->prepare("SELECT id,product,description,destination_url,expiry_type,savings,expiry,title,last_updated_on FROM products WHERE product_id=? ORDER BY last_updted_on DESC LIMIT $start ,$perPage");
$stmt->bind_param('i',$category_id);
$stmt->execute();
$productbycategory=$stmt->get_result();
$stmt->close();
return $productbycategory;
}
}
Firstly, in a RESTful call, the URL should ideally be noun-based and not verbs. We are using HTTP verbs (GET, PUT, POST, etc) to do an action on a noun - product in your case.
So, the URL should be http://localhost/products/v1/category
This effectively means you are GETting product of type v1 based on category. To get a given page number, simply add it as a query parameter -
http://localhost/products/v1/category?page=1
and handle it accordingly in your GET implementation corresponding to localhost/products/v1/category
Hope this helps.
Pagination has nothing to do with the JSON format per se - it's all about the query string in the URL and how the server interprets that.
Expanding on #Sampada's answer, you can have a URL like
http://localhost/products/v1/category?pageSize=5&pageNumber=2
and then you'll simply pick the corresponding elements on the server side (consider whether you'll want 0 or 1-based index for the pageNumber), and return them.
Additionally you can wrap this collection in an object that also provides links as to navigate to the previous/next/specific page - see HATEOAS & Richardson's Maturity Model level 3.