Http.get is url encoding template literal placeholders

For example, here we see the variable ${profileId} being submitted correctly in the first step, but incorrectly in the last step:

 VU: 029  ITER: 030  STEP: 054  (200) - POST /api/users/200728/provision
 VU: 029  ITER: 030  STEP: 055  (200) - GET /api/devices  
 VU: 029  ITER: 030  STEP: 056  (200) - GET /api/devices/available
 VU: 029  ITER: 030  STEP: 057  (200) - GET /api/users/200728/phone/primary
Request url: /api/users/%7B200728%7D/phone/primary

It is being incorrectly submitted as /api/users/{200728}/phone/primary. This appears to happen every time, and happens with only one running virtual user.

Example code (backticks have been replaced with single quote to allow for formatting here):

Successful GET:

response = http.get('https://${tenant}.${environment}.${domain}/api/licenses/features?profileId=${profileId}', {
    headers: {
        'Accept': '*/*',
        'Cookie': 'sso="' + authToken + '"'
    },
    tags: { name: '${stepNum} - https://${tenant}.${environment}.${domain}/api/licenses/features?profileId=<profileId>' },
});
checkStatus(response, vuserID, iterNum, 200);

Failed GET:

response = http.get('https://${tenant}.${environment}.${domain}/api/users/${profileId}/phone/primary', {
    headers: {
        'Accept': '*/*',
        'Cookie': 'sso="' + authToken + '"'
    },
    tags: { name: '${stepNum} - https://${tenant}.${environment}.${domain}/api/users/<profileId>/phone/primary' },
});
checkStatus(response, vuserID, iterNum, 200);

Hi @leathej1,

This looks … very strange, by which I mean unlikely. If it wasn’t working for the whole URL it would’ve been somewhat understandable, but from the what you posted it seems like you have added { in front and } at the end. This might even be what the actual value of profileId :man_shrugging: .

Can you please copy-paste the code as-is instead of replacing anything? Including where profileId is defined.

I am pretty sure that using > as you’ve done is sufficient but if not you can wrap the coed in triple backtick - ‘```’

```
code here
```

import http from 'k6/http';
import { checkStatus, fail } from '../Libraries/helpers.js';

export function editVoice(tenant, environment, domain) {

    // Edit Profile

    response = http.get(`https://${tenant}.${environment}.${domain}/api/platform-admin/v1/users/${username}/retrieve`, {
        headers: {
            'Accept': '*/*',
            'Cookie': 'sso="' + authToken + '"'
        },
        tags: { name: `${stepNum} - https://${tenant}.${environment}.${domain}/api/platform-admin/v1/users/<username>/retrieve` },
    });
    checkStatus(response, vuserID, iterNum, 200);

    let body = response.json();
    let profileId = body.user.profiles[0].id;

    response = http.get(`https://${tenant}.${environment}.${domain}/api/platform-admin/v1/licenses/profile/${profileId}`, {
        headers: {
            'Accept': '*/*',
            'Cookie': 'sso="' + authToken + '"'
        },
        tags: { name: `${stepNum} - https://${tenant}.${environment}.${domain}/api/platform-admin/v1/licenses/profile/<profileId>` },
    });
    checkStatus(response, vuserID, iterNum, 200);

    response = http.get(`https://${tenant}.${environment}.${domain}/api//users/${profileId}/phone-numbers`, {
        headers: {
            'Accept': '*/*',
            'Cookie': 'sso="' + authToken + '"'
        },
        tags: { name: `${stepNum} - https://${tenant}.${environment}.${domain}/api//users/<profileId>/phone-numbers` },
    });

    response = http.get(`https://${tenant}.${environment}.${domain}/api//users/${profileId}/info`, {
        headers: {
            'Accept': '*/*',
            'Cookie': 'sso="' + authToken + '"'
        },
        tags: { name: `${stepNum} - https://${tenant}.${environment}.${domain}/api//users/<profileId>/info` },
    });
    checkStatus(response, vuserID, iterNum, 200);

    response = http.get(`https://${tenant}.${environment}.${domain}/api//licenses/features?profileId=${profileId}`, {
        headers: {
            'Accept': '*/*',
            'Cookie': 'sso="' + authToken + '"'
        },
        tags: { name: `${stepNum} - https://${tenant}.${environment}.${domain}/api//licenses/features?profileId=<profileId>` },
    });
    checkStatus(response, vuserID, iterNum, 200);

    response = http.get(`https://${tenant}.${environment}.${domain}/api//users/${profileId}/phone/primary`, {
        headers: {
            'Accept': '*/*',
            'Cookie': 'sso="' + authToken + '"'
        },
        tags: { name: `${stepNum} - https://${tenant}.${environment}.${domain}/api//users/<profileId>/phone/primary` },
    });
    checkStatus(response, vuserID, iterNum, 200);

}

Running

import http from 'k6/http';

function checkStatus() {}
export function editVoice(tenant, environment, domain) {

	var username = "pesho"
	var authToken = "joro"
	var stepNum = 5
var iterNum = __ITER;
	// Edit Profile

	var vuserID=__VU;
	let response = http.get(`https://${tenant}.${environment}.${domain}/api/platform-admin/v1/users/${username}/retrieve`, {
		headers: {
			'Accept': '*/*',
			'Cookie': 'sso="' + authToken + '"'
		},
		tags: { name: `${stepNum} - https://${tenant}.${environment}.${domain}/api/platform-admin/v1/users/<username>/retrieve` },
	});
	checkStatus(response, vuserID, iterNum, 200);

	let profileId = "something";

	response = http.get(`https://${tenant}.${environment}.${domain}/api/platform-admin/v1/licenses/profile/${profileId}`, {
		headers: {
			'Accept': '*/*',
			'Cookie': 'sso="' + authToken + '"'
		},
		tags: { name: `${stepNum} - https://${tenant}.${environment}.${domain}/api/platform-admin/v1/licenses/profile/<profileId>` },
	});
	checkStatus(response, vuserID, iterNum, 200);

	response = http.get(`https://${tenant}.${environment}.${domain}/api//users/${profileId}/phone-numbers`, {
		headers: {
			'Accept': '*/*',
			'Cookie': 'sso="' + authToken + '"'
		},
		tags: { name: `${stepNum} - https://${tenant}.${environment}.${domain}/api//users/<profileId>/phone-numbers` },
	});

	response = http.get(`https://${tenant}.${environment}.${domain}/api//users/${profileId}/info`, {
		headers: {
			'Accept': '*/*',
			'Cookie': 'sso="' + authToken + '"'
		},
		tags: { name: `${stepNum} - https://${tenant}.${environment}.${domain}/api//users/<profileId>/info` },
	});
	checkStatus(response, vuserID, iterNum, 200);

	response = http.get(`https://${tenant}.${environment}.${domain}/api//licenses/features?profileId=${profileId}`, {
		headers: {
			'Accept': '*/*',
			'Cookie': 'sso="' + authToken + '"'
		},
		tags: { name: `${stepNum} - https://${tenant}.${environment}.${domain}/api//licenses/features?profileId=<profileId>` },
	});
	checkStatus(response, vuserID, iterNum, 200);

	response = http.get(`https://${tenant}.${environment}.${domain}/api//users/${profileId}/phone/primary`, {
		headers: {
			'Accept': '*/*',
			'Cookie': 'sso="' + authToken + '"'
		},
		tags: { name: `${stepNum} - https://${tenant}.${environment}.${domain}/api//users/<profileId>/phone/primary` },
	});
	checkStatus(response, vuserID, iterNum, 200);

}

export default function () {
	editVoice("test", "k6", "io");

}

Which is your script with the variables given some values:
with k6.v0.31.1 run -o json=out.json forum-1639.js
grep http_req_duration out.json | jq .data.tags.url produces:


"https://test.k6.io/api/platform-admin/v1/users/pesho/retrieve"
"https://test.k6.io/api/platform-admin/v1/licenses/profile/something"
"https://test.k6.io/api//users/something/phone-numbers"
"https://test.k6.io/api//users/something/info"
"https://test.k6.io/api//licenses/features?profileId=something"
"https://test.k6.io/api//users/something/phone/primary"

which looks correct to me.

So I have no idea what is happening when you run it

I’m not even sure what a next step would be for diagnosing this. Running http-debug=full shows nothing unexpected.

Can you run my script and confirm that it works as expected ( you can obviously put the correct values :wink: )?
Other than that I would argue that you should just move the url to a variable and console.log it along w/e goes inside of it to see what’s going on.
If it’s happening every time it should be fairly easy, hopefully

Yes, that is already built into my checkStatus function:

    if (!success) { // Fail the iteration and log a message
        console.log("Request url: \n" + response.request.url);
    };

INFO[0075] Request url:
https://<scrubbed>/api/users/%7B200729%7D/phone/primary  source=console

Solved:

Okay, in a situation like this, where only one template literal is failing, it is important to look at the very last line that uses that same template literal successfully. In my case, I didn’t post it here.

    response = http.post(`https://${tenant}.${environment}.${domain}/api/users/{${profileId}}/provision`, payload, {
        headers: {
            'Accept': '*/*',
            'Cookie': 'nextiva_sso="' + authToken + '"',
            'Content-Type': 'application/json;charset=UTF-8'
        },
        tags: { name: `${stepNum} - https://${tenant}.${environment}.${domain}/api//users/<profileId>/provision` },
    });
    checkStatus(response, vuserID, iterNum, 200);

Strangely, this line never failed, but it caused the next occurrence of ${profileId} to evaluate incorrectly.

Might want to file this away in your bin of ‘edge conditions that only James encounters’.