How to handle when there is a separate Basic Auth endpoint and api endpoint for each environment
How to handle when there is a separate Basic Auth endpoint that returns a JWT and api endpoint for each environment
Here is the use case:
1. There are multiple environments and each environment has a tenant.
Example <env1><tenant1>, <env1><tenant2>, env2><tenant1>
2. Each environment has an auth endpoint and api endpoint. Example:
Auth endpoint: https://<env1><tenant1>auth.com
API endponts: https://<env1><tenant1>api.com/api
3. The auth endpoint is Basic Authentication and requires a username with this structure:
username: <env1><tenant1>/username
password: ***********
4. The auth endpoint returns a JWT token, in the following response
{
"access_token":"JWT",
"profile_id":"",
"token_type":"Bearer",
"expires_in":86400
}
5. The access_token must be parsed from the response and then included in the Header of the api requests.
In it's simplest form, I was able to accomplish this by using:
- No Environment
- Hardcoding the Auth endpoint
- Hardcoding the api endpoint
- Getting the token in an auth request 'Login' test step
- Grabbing the token and using it in the next step by placing this in the header: Bearer ${Login#Response#$['access_token']}
But I need to be able to automate this so that I can switch environments at run time.
I've done a lot of research and haven't found many suggestions besides 'create a script'. I'm hoping there are other ways, but if I have to go the script route, it's not the end of the world. Has anyone else encountered a similar scenario? What is the easier strategy for handling a scenario like this? I appreciate any advice!
NOTE: I can't seem to use the built in Authentication Manger in ReadyAPI because the Basic Authentication returns a response that must be parsed. Unless there is a way around this that I don't know about 🙂
I tested adding the parameters to the endpoints and that is working great.
I also defined two APIs as you suggested
However, in regards to your last bullet "Grab the token from the authorization response and save it to another project-level property". At what point do you suggest I do this? Should this be done in a script that runs at the beginning of the project? I assume yes, and if so, are there any good script examples? Hoping that ReadyAPI already has some methods I can use so I do not have to reinvent the wheel.
Otherwise you can e.g. dedicate a test case for authentication. In such test case you can invoke the authentication, process its response and save into a property. You can avoid scripting if you use property transfer test step. Also you can use a Run Test Case step to invoke it wherever needed.
Maybe you can close this thread (which relates to endpoint configuration) and start a new one for authentication if needed.
When they introduced the environments functionality several years ago i tried using it, but struggled (i think the functionality has matured since) and nmrao mentioned an approach to handling multiple environments that i've used ever since.
I create a properties file (containing custom project level properties) for each different environment. Within this properties file i have all my environment specific static properties (like hostnames, urls, usernames/passwords, db connection string variables, static tokens, etc.) and i have empty properties for my dynamic properties (like GUIDs, dynamic token values, etc.)
I load my properties file in before execution. At this point my project level custom properties and their static values and the properties with empty are created/updated. I then have a "setup testsuite" that essentially queries the environment populating the empty dynamic properties with values. Virtually all my environment specfic properties are populated by various different tests in my setup testsuite. For stuff that expires quickly (like JWT's), i do have empty project properties setup, but i generate those from within the individual tests itself.
Essentially i parameterise every piece of environment specific data and source the data values from the custom project properties (either generated when i load in the properties file (static data), or in my setup testsuite (dynamic data that doesnt expire).
Since i started using this approach i havent used any other to handle changing environments.
All credit goes to Rao for this idea....it really has helped me for the last couple of years.
Thanks for the suggestions. At my previous company we set properties in files, but I was hoping I could do something like what Karel suggested using the -E and -P and set properties at run time. Then I don't have to update or store files. I will consider this option, but will have to test it.
I'm interested in your setup suite. How do you have this configured so it always runs first?
to answer your question - I have my setup testsuites disabled, and then I have a 'Setup' testcase with a 'Run Test Case' step in each of my Functional testsuites. The 'Run Test Case' step executes the 'Setup' testcases within the disabled 'Setup' testsuites ( these 'Setup' testsuites are always listed in my project BEFORE the Functional testsuites.
Please see image below - these 'disabled' testsuites are my 'Setup' testsuites. Each one below contains a testcase that does something different:
richie_0-1651609388212.png
the 'Generate CRM Access Token' testsuite generates the token needed for later functionaltests
The 'Generate GUIDs' testsuite queries the environment for the GUIDs needed later in the functional tests (these are some of the empty project level properties)
The 'Identiy Test Data Record' testsuite queries the environment for a specific testdata record needed in later tests
The 'Generate Java JWT' generates the token I need for later tests (again, this populates an empty project level property)
My Functional TestSuites look like the following:
richie_1-1651609596420.png
So in the image above - SYSLNC001.017 is a functional test suite.
It contains a 'Setup' testcase with 2 teststeps - both of these teststeps are 'Run Test Case' steps which point to the relevant testcases within the disabled 'Setup' testsuites further up in the project hierarchy.
The 017-1 testcase is my functional testcase within the SYSLNC001.017 functional testsuite.
As I already mentioned - the way I do it is that I have a environmentName_properties file which is just a list of name value pairs like the following:
Now - the above list (I've edited it - you can see some static parameters - where there is an attribute separated with it's associated value by the equals sign (e.g. bottom row has 'DB_Database=Reference') and I have some empty properties like the line '[email protected]='
I populate the '[email protected]=' property by executing one of the Setup Testsuites.
The way I always do this is to open ReadyAPI, and then load in the envirnmentName.properties file in manually. HOWEVER - if you want to do switch environments WITHOUT bothering launching ReadyAPI, what I have done on some occasion is to launch the testRunner, then paste the content of my properties file into the relevant Project level Properties editable field - see following image:
richie_3-1651610140304.png
As you can see - I've pasted all my the content from my properties file into the field.
At this point, you can click 'Get Command Line' and this will generate the command with all those property settings built in. I then took a copy of the testRunner generated line with all the property settings and saved them in a file.
When I did this before - I repeated this process for each of my different environments. - each time I had a new environment, I
1. built up the content of my properties file, then once it was built - I then
2. copied the content into the Project level Properties editable field in the TestRunner form above,
3. selected 'Get Command Line' and copied and saved the content to a separate file.
Eventually i had like maybe 5 txt files that contained the content for the testRunner command for each environment.
Then - each time I wanted to execute my project with a specific environment - I had the correct TestRunner command with all the properties set (both static and dynamic). As I said - for the empty dynamic properties - these are all populated by specific setup testsuites querying the environment when needed via the 'Run TestCase' teststeps in 'setup' testcases within my Functional Testsuites.
Does that help explain the way I do it?
there's bound to be a number of options available - but the above setup is the one i prefer using (probably cos I'm used to it now). The advantage I've found by doing it this way is if you plan your project out correctly, essentially you can build all your ReadyAPI projects so you can lift and shift them to run on any different environment WITHOUT needing to make ANY changes to the testcontent in the project.
I do this with all my ReadyAPI projects now - it's very efficient!
I ended up using project-level tenant property (e.g. ${#Project#tenant}) to parametrize the endpoints as suggested by KarelHusa Thank you!
If anyone is interested, I also created a groovy script to grab and parse the token. Note, I'm a beginner at groovy script, so there may be an easier way to do this, but it worked for me 🙂
/**** @author: Scott Rixon
Class to get the bearer token from the oauth/accessToken endpoint
// Create Host HttpHost targetHost = new HttpHost(host, 443, "https");
// Create Credential provider CredentialsProvider credentialsPovider = new BasicCredentialsProvider();
// Set Credentials credentialsPovider.setCredentials( new AuthScope(targetHost.getHostName(), targetHost.getPort()), new UsernamePasswordCredentials(username, password));
// Create AuthCache instance AuthCache authCache = new BasicAuthCache();
// Generate BASIC scheme object and add it to the local auth cache BasicScheme basicAuth = new BasicScheme(); authCache.put(targetHost, basicAuth);
// Add AuthCache to the execution context HttpClientContext context = HttpClientContext.create(); context.setCredentialsProvider(credentialsPovider); context.setAuthCache(authCache);
// Create HTTPGet HttpGet httpget = new HttpGet(fullUrl);