mirror of
https://github.com/zitadel/zitadel.git
synced 2025-04-23 22:51:35 +00:00
test: add load test for session creation (#8088)
# Which Problems Are Solved Extends load tests by testing session creation. # How the Problems Are Solved The test creates a session including a check for user id. # Additional Context - part of https://github.com/zitadel/zitadel/issues/7639
This commit is contained in:
parent
23bebc7e30
commit
82d950019f
@ -12,6 +12,10 @@ human_password_login: bundle
|
|||||||
machine_pat_login: bundle
|
machine_pat_login: bundle
|
||||||
k6 run dist/machine_pat_login.js --vus ${VUS} --duration ${DURATION}
|
k6 run dist/machine_pat_login.js --vus ${VUS} --duration ${DURATION}
|
||||||
|
|
||||||
|
.PHONY: machine_client_credentials_login
|
||||||
|
machine_client_credentials_login: bundle
|
||||||
|
k6 run dist/machine_client_credentials_login.js --vus ${VUS} --duration ${DURATION}
|
||||||
|
|
||||||
.PHONY: user_info
|
.PHONY: user_info
|
||||||
user_info: bundle
|
user_info: bundle
|
||||||
k6 run dist/user_info.js --vus ${VUS} --duration ${DURATION}
|
k6 run dist/user_info.js --vus ${VUS} --duration ${DURATION}
|
||||||
@ -26,6 +30,10 @@ introspect: ensure_modules bundle
|
|||||||
cd ../../xk6-modules && xk6 build --with xk6-zitadel=.
|
cd ../../xk6-modules && xk6 build --with xk6-zitadel=.
|
||||||
./../../xk6-modules/k6 run dist/introspection.js --vus ${VUS} --duration ${DURATION}
|
./../../xk6-modules/k6 run dist/introspection.js --vus ${VUS} --duration ${DURATION}
|
||||||
|
|
||||||
|
.PHONY: add_session
|
||||||
|
add_session: bundle
|
||||||
|
k6 run dist/session.js --vus ${VUS} --duration ${DURATION}
|
||||||
|
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint:
|
lint:
|
||||||
npm i
|
npm i
|
||||||
|
@ -36,6 +36,9 @@ Before you run the tests you need an initialized user. The tests don't implement
|
|||||||
* `make machine_pat_login`
|
* `make machine_pat_login`
|
||||||
setup: creates machines and a pat for each machine
|
setup: creates machines and a pat for each machine
|
||||||
test: calls user info endpoint with the given pats
|
test: calls user info endpoint with the given pats
|
||||||
|
* `make machine_client_credentials_login`
|
||||||
|
setup: creates machines and a client credential secret for each machine
|
||||||
|
test: calls token endpoint with the `client_credentials` grant type.
|
||||||
* `make user_info`
|
* `make user_info`
|
||||||
setup: creates human users and signs them in
|
setup: creates human users and signs them in
|
||||||
test: calls user info endpoint using the given humans
|
test: calls user info endpoint using the given humans
|
||||||
@ -44,3 +47,6 @@ Before you run the tests you need an initialized user. The tests don't implement
|
|||||||
* `make introspect`
|
* `make introspect`
|
||||||
setup: creates projects, one api per project, one key per api and generates the jwt from the given keys
|
setup: creates projects, one api per project, one key per api and generates the jwt from the given keys
|
||||||
test: calls introspection endpoint using the given JWTs
|
test: calls introspection endpoint using the given JWTs
|
||||||
|
* `make add_session`
|
||||||
|
setup: creates human users
|
||||||
|
test: creates new sessions with user id check
|
@ -72,3 +72,35 @@ export function introspect(jwt: string, token: string) {
|
|||||||
|
|
||||||
introspectTrend.add(res.timings.duration);
|
introspectTrend.add(res.timings.duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const clientCredentialsTrend = new Trend('oidc_client_credentials_duration', true);
|
||||||
|
export function clientCredentials(clientId: string, clientSecret: string): Promise<Tokens> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const response = http.asyncRequest('POST', configuration().token_endpoint,
|
||||||
|
{
|
||||||
|
grant_type: "client_credentials",
|
||||||
|
scope: 'openid profile urn:zitadel:iam:org:project:id:zitadel:aud',
|
||||||
|
client_id: clientId,
|
||||||
|
client_secret: clientSecret,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
response.then((res) => {
|
||||||
|
check(res, {
|
||||||
|
'client credentials status ok': (r) => r.status === 200,
|
||||||
|
}) || reject(`client credentials request failed (client id: ${clientId}) status: ${res.status} body: ${res.body}`);
|
||||||
|
|
||||||
|
clientCredentialsTrend.add(res.timings.duration);
|
||||||
|
const tokens = new Tokens(res.json() as JSONObject)
|
||||||
|
check(tokens, {
|
||||||
|
'client credentials token ok': (t) => t.accessToken !== undefined,
|
||||||
|
}) || reject(`client credentials access token missing (client id: ${clientId}`);
|
||||||
|
|
||||||
|
resolve(tokens)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
42
load-test/src/session.ts
Normal file
42
load-test/src/session.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { Trend } from 'k6/metrics';
|
||||||
|
import { Org } from './org';
|
||||||
|
import http from 'k6/http';
|
||||||
|
import url from './url';
|
||||||
|
import { check } from 'k6';
|
||||||
|
import { User } from './user';
|
||||||
|
|
||||||
|
export type Session = {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const addSessionTrend = new Trend('session_add_session_duration', true);
|
||||||
|
export function createSession(user: User, org: Org, accessToken: string): Promise<Session> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let response = http.asyncRequest(
|
||||||
|
'POST',
|
||||||
|
url('/v2beta/sessions'),
|
||||||
|
JSON.stringify({
|
||||||
|
checks: {
|
||||||
|
user: {
|
||||||
|
userId: user.userId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${accessToken}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-zitadel-orgid': org.organizationId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
response.then((res) => {
|
||||||
|
check(res, {
|
||||||
|
'add Session status ok': (r) => r.status === 201,
|
||||||
|
}) || reject(`unable to add Session status: ${res.status} body: ${res.body}`);
|
||||||
|
|
||||||
|
addSessionTrend.add(res.timings.duration);
|
||||||
|
resolve(res.json() as Session);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
49
load-test/src/use_cases/machine_client_credentials_login.ts
Normal file
49
load-test/src/use_cases/machine_client_credentials_login.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { loginByUsernamePassword } from '../login_ui';
|
||||||
|
import { createOrg, removeOrg } from '../org';
|
||||||
|
import {createMachine, User, addMachineSecret} from '../user';
|
||||||
|
import {clientCredentials, userinfo} from '../oidc';
|
||||||
|
import { Config, MaxVUs } from '../config';
|
||||||
|
|
||||||
|
export async function setup() {
|
||||||
|
const tokens = loginByUsernamePassword(Config.admin as User);
|
||||||
|
console.info('setup: admin signed in');
|
||||||
|
|
||||||
|
const org = await createOrg(tokens.accessToken!);
|
||||||
|
console.info(`setup: org (${org.organizationId}) created`);
|
||||||
|
|
||||||
|
let machines = (
|
||||||
|
await Promise.all(
|
||||||
|
Array.from({ length: MaxVUs() }, (_, i) => {
|
||||||
|
return createMachine(`zitachine-${i}`, org, tokens.accessToken!);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
).map((machine) => {
|
||||||
|
return { userId: machine.userId, loginName: machine.loginNames[0] };
|
||||||
|
});
|
||||||
|
console.info(`setup: ${machines.length} machines created`);
|
||||||
|
|
||||||
|
let credentials = (
|
||||||
|
await Promise.all(
|
||||||
|
machines.map((machine) => {
|
||||||
|
return addMachineSecret(machine.userId, org, tokens.accessToken!);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
).map((credentials, i) => {
|
||||||
|
return { userId: machines[i].userId, loginName: machines[i].loginName, password: credentials.clientSecret };
|
||||||
|
});
|
||||||
|
console.info(`setup: secrets added`);
|
||||||
|
|
||||||
|
return { tokens, machines: credentials, org };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function (data: any) {
|
||||||
|
clientCredentials(data.machines[__VU - 1].loginName, data.machines[__VU - 1].password)
|
||||||
|
.then((token) => {
|
||||||
|
userinfo(token.accessToken!)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function teardown(data: any) {
|
||||||
|
removeOrg(data.org, data.tokens.accessToken);
|
||||||
|
console.info('teardown: org removed');
|
||||||
|
}
|
41
load-test/src/use_cases/session.ts
Normal file
41
load-test/src/use_cases/session.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { loginByUsernamePassword } from '../login_ui';
|
||||||
|
import { createOrg, removeOrg } from '../org';
|
||||||
|
import { User, createHuman } from '../user';
|
||||||
|
import { Trend } from 'k6/metrics';
|
||||||
|
import { Config, MaxVUs } from '../config';
|
||||||
|
import { createSession } from '../session';
|
||||||
|
import { check } from 'k6';
|
||||||
|
|
||||||
|
export async function setup() {
|
||||||
|
const tokens = loginByUsernamePassword(Config.admin as User);
|
||||||
|
console.log('setup: admin signed in');
|
||||||
|
|
||||||
|
const org = await createOrg(tokens.accessToken!);
|
||||||
|
console.log(`setup: org (${org.organizationId}) created`);
|
||||||
|
|
||||||
|
const humanPromises = Array.from({ length: MaxVUs() }, (_, i) => {
|
||||||
|
return createHuman(`zitizen-${i}`, org, tokens.accessToken!);
|
||||||
|
});
|
||||||
|
|
||||||
|
const humans = (await Promise.all(humanPromises)).map((user) => {
|
||||||
|
return { userId: user.userId, loginName: user.loginNames[0], password: 'Password1!' };
|
||||||
|
});
|
||||||
|
console.log(`setup: ${humans.length} users created`);
|
||||||
|
return { tokens, users: humans, org };
|
||||||
|
}
|
||||||
|
|
||||||
|
const addSessionTrend = new Trend('add_session_duration', true);
|
||||||
|
export default async function (data: any) {
|
||||||
|
const start = new Date();
|
||||||
|
const session = await createSession(data.users[__VU - 1], data.org, data.tokens.accessToken);
|
||||||
|
|
||||||
|
check(session, {
|
||||||
|
'add session is status ok': (s) => s.id !== "",
|
||||||
|
});
|
||||||
|
|
||||||
|
addSessionTrend.add(new Date().getTime() - start.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function teardown(data: any) {
|
||||||
|
removeOrg(data.org, data.tokens.accessToken);
|
||||||
|
}
|
@ -171,6 +171,32 @@ export function addMachinePat(userId: string, org: Org, accessToken: string): Pr
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type MachineSecret = {
|
||||||
|
clientId: string;
|
||||||
|
clientSecret: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const addMachineSecretTrend = new Trend('user_add_machine_secret_duration', true);
|
||||||
|
export function addMachineSecret(userId: string, org: Org, accessToken: string): Promise<MachineSecret> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let response = http.asyncRequest('PUT', url(`/management/v1/users/${userId}/secret`), null, {
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${accessToken}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-zitadel-orgid': org.organizationId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
response.then((res) => {
|
||||||
|
check(res, {
|
||||||
|
'generate machine secret status ok': (r) => r.status === 200,
|
||||||
|
}) || reject(`unable to generate machine secret (user id: ${userId}) status: ${res.status} body: ${res.body}`);
|
||||||
|
|
||||||
|
addMachineSecretTrend.add(res.timings.duration);
|
||||||
|
resolve(res.json()! as MachineSecret);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const lockUserTrend = new Trend('lock_user_duration', true);
|
const lockUserTrend = new Trend('lock_user_duration', true);
|
||||||
export function lockUser(userId: string, org: Org, accessToken: string): Promise<RefinedResponse<any>> {
|
export function lockUser(userId: string, org: Org, accessToken: string): Promise<RefinedResponse<any>> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user