Skip to content

Commit 77693b0

Browse files
authored
UID2-4776 Add local test for token generate and identity map separately (#94)
* Add local test for token generate and identity map separately * Use environment variable to pass in secret values * Put back handleSummary function
1 parent 3d1617b commit 77693b0

File tree

4 files changed

+372
-123
lines changed

4 files changed

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

0 commit comments

Comments
 (0)