Skip to content

Commit 9a190cc

Browse files
committed
Add local test for token generate and identity map separately
1 parent 6806068 commit 9a190cc

File tree

2 files changed

+385
-99
lines changed

2 files changed

+385
-99
lines changed
Lines changed: 367 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,367 @@
1+
2+
import { crypto } from "k6/experimental/webcrypto";
3+
import encoding from 'k6/encoding';
4+
import { check } from 'k6';
5+
import http from 'k6/http';
6+
7+
const vus = 300;
8+
// Get Key and Secret from: https://start.1password.com/open/i?a=SWHBRR7FURBBXPZORJWBGP5UBM&v=cknem3yiubq6f2guyizd2ifsnm&i=ywhkqovi4p5wzoi7me4564hod4&h=thetradedesk.1password.com
9+
const baseUrl = "";
10+
const clientSecret = "";
11+
const clientKey = "";
12+
const identityMapVUs = 300;
13+
const identityMapLargeBatchVUs = 10;
14+
15+
const generateVUs = vus;
16+
const testDuration = '5m'
17+
18+
export const options = {
19+
insecureSkipTLSVerify: true,
20+
noConnectionReuse: false,
21+
scenarios: {
22+
// Warmup scenarios
23+
// tokenGenerateWarmup: {
24+
// executor: 'ramping-vus',
25+
// exec: 'tokenGenerate',
26+
// stages: [
27+
// { duration: '30s', target: generateVUs}
28+
// ],
29+
// gracefulRampDown: '0s',
30+
// },
31+
identityMapWarmup: {
32+
executor: 'ramping-vus',
33+
exec: 'identityMap',
34+
stages: [
35+
{ duration: '30s', target: identityMapVUs}
36+
],
37+
gracefulRampDown: '0s',
38+
},
39+
// Actual testing scenarios
40+
// tokenGenerate: {
41+
// executor: 'constant-vus',
42+
// exec: 'tokenGenerate',
43+
// vus: generateVUs,
44+
// duration: testDuration,
45+
// gracefulStop: '0s',
46+
// startTime: '30s',
47+
// },
48+
// identityMap: {
49+
// executor: 'constant-vus',
50+
// exec: 'identityMap',
51+
// vus: identityMapVUs,
52+
// duration: testDuration,
53+
// gracefulStop: '0s',
54+
// startTime: '40s',
55+
// },
56+
// identityMapLargeBatchSequential: {
57+
// executor: 'constant-vus',
58+
// exec: 'identityMapLargeBatch',
59+
// vus: 1,
60+
// duration: '300s',
61+
// gracefulStop: '0s',
62+
// startTime: '40s',
63+
// },
64+
identityMapLargeBatch: {
65+
executor: 'constant-vus',
66+
exec: 'identityMapLargeBatch',
67+
vus: identityMapLargeBatchVUs,
68+
duration: '300s',
69+
gracefulStop: '0s',
70+
startTime: '40s',
71+
},
72+
},
73+
// So we get count in the summary, to demonstrate different metrics are different
74+
summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(90)', 'p(95)', 'p(99)', 'count'],
75+
thresholds: {
76+
// Intentionally empty. We'll programatically define our bogus
77+
// thresholds (to generate the sub-metrics) below. In your real-world
78+
// load test, you can add any real threshoulds you want here.
79+
}
80+
};
81+
82+
// https://community.k6.io/t/multiple-scenarios-metrics-per-each/1314/3
83+
for (let key in options.scenarios) {
84+
// Each scenario automaticall tags the metrics it generates with its own name
85+
let thresholdName = `http_req_duration{scenario:${key}}`;
86+
// Check to prevent us from overwriting a threshold that already exists
87+
if (!options.thresholds[thresholdName]) {
88+
options.thresholds[thresholdName] = [];
89+
}
90+
// 'max>=0' is a bogus condition that will always be fulfilled
91+
options.thresholds[thresholdName].push('max>=0');
92+
}
93+
94+
export async function setup() {
95+
var token = await generateRefreshRequest();
96+
return {
97+
tokenGenerate: null,
98+
identityMap: null,
99+
refreshToken: token
100+
};
101+
102+
async function generateRefreshRequest() {
103+
let request = await createReq( {'optout_check': 1, 'email': '[email protected]'});
104+
var requestData = {
105+
endpoint: '/v2/token/generate',
106+
requestBody: request,
107+
}
108+
let response = await send(requestData, clientKey);
109+
let decrypt = await decryptEnvelope(response.body, clientSecret)
110+
return decrypt.body.refresh_token;
111+
};
112+
}
113+
114+
// export function handleSummary(data) {
115+
// return {
116+
// 'summary.json': JSON.stringify(data),
117+
// }
118+
// }
119+
120+
// Scenarios
121+
export async function tokenGenerate(data) {
122+
const endpoint = '/v2/token/generate';
123+
if (data.tokenGenerate == null) {
124+
var newData = await generateTokenGenerateRequestWithTime();
125+
data.tokenGenerate = newData;
126+
} else if (data.tokenGenerate.time < (Date.now() - 45000)) {
127+
data.tokenGenerate = await generateTokenGenerateRequestWithTime();
128+
}
129+
130+
var requestBody = data.tokenGenerate.requestBody;
131+
var tokenGenerateData = {
132+
endpoint: endpoint,
133+
requestBody: requestBody,
134+
}
135+
136+
execute(tokenGenerateData, true);
137+
}
138+
139+
export async function identityMap(data) {
140+
const endpoint = '/v2/identity/map';
141+
if ((data.identityMap == null) || (data.identityMap.time < (Date.now() - 45000))) {
142+
data.identityMap = await generateIdentityMapRequestWithTime(2);;
143+
}
144+
145+
var requestBody = data.identityMap.requestBody;
146+
var identityData = {
147+
endpoint: endpoint,
148+
requestBody: requestBody,
149+
}
150+
execute(identityData, true);
151+
}
152+
153+
export async function identityMapLargeBatch(data) {
154+
const endpoint = '/v2/identity/map';
155+
if ((data.identityMap == null) || (data.identityMap.time < (Date.now() - 45000))) {
156+
data.identityMap = await generateIdentityMapRequestWithTime(5000);;
157+
}
158+
159+
var requestBody = data.identityMap.requestBody;
160+
var identityData = {
161+
endpoint: endpoint,
162+
requestBody: requestBody,
163+
}
164+
execute(identityData, true);
165+
}
166+
167+
// Helpers
168+
async function createReqWithTimestamp(timestampArr, obj) {
169+
var envelope = getEnvelopeWithTimestamp(timestampArr, obj);
170+
return encoding.b64encode((await encryptEnvelope(envelope, clientSecret)).buffer);
171+
}
172+
173+
function generateIdentityMapRequest(emailCount) {
174+
var data = {
175+
'optout_check': 1,
176+
"email": []
177+
};
178+
179+
for (var i = 0; i < emailCount; ++i) {
180+
data.email.push(`test${i}@example.com`);
181+
}
182+
183+
return data;
184+
}
185+
186+
function send(data, auth) {
187+
var options = {};
188+
if (auth) {
189+
options.headers = {
190+
'Authorization': `Bearer ${clientKey}`
191+
};
192+
}
193+
194+
return http.post(`${baseUrl}${data.endpoint}`, data.requestBody, options);
195+
}
196+
197+
function execute(data, auth) {
198+
var response = send(data, auth);
199+
200+
check(response, {
201+
'status is 200': r => r.status === 200,
202+
});
203+
}
204+
205+
async function encryptEnvelope(envelope, clientSecret) {
206+
const rawKey = encoding.b64decode(clientSecret);
207+
const key = await crypto.subtle.importKey("raw", rawKey, "AES-GCM", true, [
208+
"encrypt",
209+
"decrypt",
210+
]);
211+
212+
const iv = crypto.getRandomValues(new Uint8Array(12));
213+
214+
const ciphertext = new Uint8Array(await crypto.subtle.encrypt(
215+
{
216+
name: "AES-GCM",
217+
iv: iv,
218+
},
219+
key,
220+
envelope
221+
));
222+
223+
const result = new Uint8Array(+(1 + iv.length + ciphertext.length));
224+
225+
// The version of the envelope format.
226+
result[0] = 1;
227+
228+
result.set(iv, 1);
229+
230+
// The tag is at the end of ciphertext.
231+
result.set(ciphertext, 1 + iv.length);
232+
233+
return result;
234+
}
235+
236+
async function decryptEnvelope(envelope, clientSecret) {
237+
const rawKey = encoding.b64decode(clientSecret);
238+
const rawData = encoding.b64decode(envelope);
239+
const key = await crypto.subtle.importKey("raw", rawKey, "AES-GCM", true, [
240+
"encrypt",
241+
"decrypt",
242+
]);
243+
const length = rawData.byteLength;
244+
const iv = rawData.slice(0, 12);
245+
246+
const decrypted = await crypto.subtle.decrypt(
247+
{
248+
name: "AES-GCM",
249+
iv: iv,
250+
tagLength: 128
251+
},
252+
key,
253+
rawData.slice(12)
254+
);
255+
256+
257+
const decryptedResponse = String.fromCharCode.apply(String, new Uint8Array(decrypted.slice(16)));
258+
const response = JSON.parse(decryptedResponse);
259+
260+
return response;
261+
}
262+
263+
function getEnvelopeWithTimestamp(timestampArray, obj) {
264+
var randomBytes = new Uint8Array(8);
265+
crypto.getRandomValues(randomBytes);
266+
267+
var payload = stringToUint8Array(JSON.stringify(obj));
268+
269+
var envelope = new Uint8Array(timestampArray.length + randomBytes.length + payload.length);
270+
envelope.set(timestampArray);
271+
envelope.set(randomBytes, timestampArray.length);
272+
envelope.set(payload, timestampArray.length + randomBytes.length);
273+
274+
return envelope;
275+
276+
}
277+
function getEnvelope(obj) {
278+
var timestampArr = new Uint8Array(getTimestamp());
279+
return getEnvelopeWithTimestamp(timestampArr, obj);
280+
}
281+
282+
function getTimestamp() {
283+
const now = Date.now();
284+
return getTimestampFromTime(now);
285+
}
286+
287+
function getTimestampFromTime(time) {
288+
const res = new ArrayBuffer(8);
289+
const { hi, lo } = Get32BitPartsBE(time);
290+
const view = new DataView(res);
291+
view.setUint32(0, hi, false);
292+
view.setUint32(4, lo, false);
293+
return res;
294+
}
295+
296+
// http://anuchandy.blogspot.com/2015/03/javascript-how-to-extract-lower-32-bit.html
297+
function Get32BitPartsBE(bigNumber) {
298+
if (bigNumber > 9007199254740991) {
299+
// Max int that JavaScript can represent is 2^53.
300+
throw new Error('The 64-bit value is too big to be represented in JS :' + bigNumber);
301+
}
302+
303+
var bigNumberAsBinaryStr = bigNumber.toString(2);
304+
// Convert the above binary str to 64 bit (actually 52 bit will work) by padding zeros in the left
305+
var bigNumberAsBinaryStr2 = '';
306+
for (var i = 0; i < 64 - bigNumberAsBinaryStr.length; i++) {
307+
bigNumberAsBinaryStr2 += '0';
308+
};
309+
310+
bigNumberAsBinaryStr2 += bigNumberAsBinaryStr;
311+
312+
return {
313+
hi: parseInt(bigNumberAsBinaryStr2.substring(0, 32), 2),
314+
lo: parseInt(bigNumberAsBinaryStr2.substring(32), 2),
315+
};
316+
}
317+
318+
function stringToUint8Array(str) {
319+
const buffer = new ArrayBuffer(str.length);
320+
const view = new Uint8Array(buffer);
321+
for (var i = 0; i < str.length; i++) {
322+
view[i] = str.charCodeAt(i);
323+
}
324+
return view;
325+
}
326+
327+
async function createReq(obj) {
328+
var envelope = getEnvelope(obj);
329+
return encoding.b64encode((await encryptEnvelope(envelope, clientSecret)).buffer);
330+
};
331+
332+
async function generateRequestWithTime(obj) {
333+
var time = Date.now();
334+
var timestampArr = new Uint8Array(getTimestampFromTime(time));
335+
var requestBody = await createReqWithTimestamp(timestampArr, obj);
336+
var element = {
337+
time: time,
338+
requestBody: requestBody
339+
};
340+
341+
return element;
342+
}
343+
344+
345+
async function generateTokenGenerateRequestWithTime() {
346+
let requestData = { 'optout_check': 1, 'email': '[email protected]' };
347+
return await generateRequestWithTime(requestData);
348+
}
349+
350+
async function generateIdentityMapRequestWithTime(emailCount) {
351+
let emails = generateIdentityMapRequest(emailCount);
352+
return await generateRequestWithTime(emails);
353+
}
354+
355+
async function generateKeySharingRequestWithTime() {
356+
let requestData = { };
357+
return await generateRequestWithTime(requestData);
358+
}
359+
360+
const generateSinceTimestampStr = () => {
361+
var date = new Date(Date.now() - 2 * 24 * 60 * 60 * 1000 /* 2 days ago */);
362+
var year = date.getFullYear();
363+
var month = (date.getMonth() + 1).toString().padStart(2, '0');
364+
var day = date.getDate().toString().padStart(2, '0');
365+
366+
return `${year}-${month}-${day}T00:00:00`;
367+
};

0 commit comments

Comments
 (0)