Skip to content

Commit

Permalink
refactor to use result type
Browse files Browse the repository at this point in the history
  • Loading branch information
clauyan committed Nov 11, 2024
1 parent 5a74b42 commit 329f573
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 52 deletions.
49 changes: 35 additions & 14 deletions loadtest/pages/login.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { check, fail, group } from "k6";
import { check, group } from "k6";
import { get, post, RefinedResponse, ResponseType } from "k6/http";
import { getLogin, removeQueryString } from "../util/api.ts";
import { getFrontendUrl } from "../util/config.ts";
import { loadLinkedResourcesAndCheck } from "../util/load-linked-resources.ts";
import { loadPage } from "../util/page.ts";
import { getError, SYSTEM } from "../util/types.ts";
import { LoginData } from "../util/users.ts";
import { PageObject } from "./index.ts";

Expand All @@ -21,15 +22,20 @@ class LoginPage implements PageObject {
* @param user data for login
*/
login(user: LoginData) {
group("login", () => {
const result = group("login", () => {
this.navigate();
let response = this.goToKeycloakLogin();
response = this.submitForm(response, user);
response = this.complete(response);
if (response.status !== 200 && response.status !== 302) {
fail("login failed");
const response = this.goToKeycloakLogin();
this.submitForm(response, user);
const completeResult = this.complete(response);
if (
completeResult.ok &&
!["200", "302"].includes(completeResult.data!.status.toString())
) {
return { ok: false, error: getError(SYSTEM.KEYCLOAK, "login failed") };
}
return { ok: true };
});
return result;
}

goToKeycloakLogin() {
Expand All @@ -50,16 +56,25 @@ class LoginPage implements PageObject {
"action url found": (url) => url != undefined,
})
)
fail("action for #kc-form-login was not found");
return {
error: getError(
SYSTEM.KEYCLOAK,
"action for #kc-form-login was not found",
),
ok: false,
};

const loginData = {
...user,
credentialId: "",
};
return post(actionUrl!, loginData, {
redirects: 0,
tags: { name: removeQueryString(actionUrl!) },
});
return {
data: post(actionUrl!, loginData, {
redirects: 0,
tags: { name: removeQueryString(actionUrl!) },
}),
ok: true,
};
}

complete(response: RefinedResponse<ResponseType | undefined>) {
Expand All @@ -71,14 +86,20 @@ class LoginPage implements PageObject {
"login url found": (url) => url != undefined,
})
) {
fail("did not find Location in kc response");
return {
error: getError(
SYSTEM.KEYCLOAK,
"did not find Location in kc response",
),
ok: false,
};
}
// this redirects to the start page
const loginResponse = get(loginUrl, {
tags: { name: removeQueryString(loginUrl) },
});
loadLinkedResourcesAndCheck(loginResponse);
return loginResponse;
return { data: loginResponse, ok: true };
}
}

Expand Down
48 changes: 27 additions & 21 deletions loadtest/usecases/1_login.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { check, group } from "k6";
import { RefinedResponse, ResponseType } from "k6/http";
import { Counter, Trend } from "k6/metrics";
import { loginPage } from "../pages/login.ts";
import { defaultHttpCheck, defaultTimingCheck } from "../util/checks.ts";
import {
defaultHttpCheck,
defaultTimingCheck,
getStatusChecker,
} from "../util/checks.ts";
import { getDefaultOptions } from "../util/config.ts";
import { Result } from "../util/types.ts";
import { wrapTestFunction } from "../util/usecase-wrapper.ts";
import { getDefaultUserMix } from "../util/users.ts";

Expand All @@ -17,45 +21,47 @@ export const options = {

export default wrapTestFunction(main);

function main() {
function main(): Result<undefined> {
/**
* URL for final login, which we obtain from keycloak during oidc-login
*/
let keycloakFormResponse: RefinedResponse<ResponseType | undefined>;
let loginPageResponse: RefinedResponse<ResponseType | undefined>;

const startTime = new Date();

group("go to kc login", () => {
const loginPageResponse = group("go to kc login", () => {
loginPage.navigate();
loginPageResponse = loginPage.goToKeycloakLogin();
check(loginPageResponse, defaultHttpCheck);
const response = loginPage.goToKeycloakLogin();
check(response, defaultHttpCheck);
return response;
});

group("submit form", () => {
// submit form
const keycloakFormResponse = group("submit form", () => {
const user = users.getLogin();
keycloakFormResponse = loginPage.submitForm(loginPageResponse, user);
check(keycloakFormResponse, {
"submitting login form to kc succeeded": () =>
keycloakFormResponse.status === 302,
const result = loginPage.submitForm(loginPageResponse, user);
check(result.data!, {
"submitting login form to kc succeeded": (r) => r.status === 302,
...defaultTimingCheck,
});
return result;
});
if (!keycloakFormResponse.ok)
return { ok: false, error: keycloakFormResponse.error! };

return group("finish login", () => {
const response = loginPage.complete(keycloakFormResponse.data!);
if (!response.ok) return { ok: false, error: response.error! };

group("finish login", () => {
const response = loginPage.complete(keycloakFormResponse);
const loginSucceeded = check(response, {
"login succeeded": () =>
response.status === 200 || response.status === 302,
const loginSucceeded = check(response.data!, {
"login succeeded": getStatusChecker([200, 302]),
});
check(response, {
check(response.data!, {
...defaultTimingCheck,
});

const endTime = new Date();
if (loginSucceeded) {
successfulLoginCounter.add(1);
successfulLoginDuration.add(endTime.valueOf() - startTime.valueOf());
}
return { ok: true, data: undefined };
});
}
14 changes: 14 additions & 0 deletions loadtest/util/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { vu } from "k6/execution";

export type Result<T, E = string> =
| { data: T; ok: true }
| { error: E; ok: false };

export enum SYSTEM {
KEYCLOAK = "keycloak",
BACKEND = "backend",
}

export function getError(system: SYSTEM, message: string) {
return `${vu.idInInstance}:${vu.iterationInInstance} | ${system} error | ${message}`;
}
34 changes: 17 additions & 17 deletions loadtest/util/usecase-wrapper.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
import { sleep } from "k6";
import { Counter, Trend } from "k6/metrics";
import { Result } from "./types.ts";

const completedCounter = new Counter("usecases_completed");
const abortedCounter = new Counter("usecases_aborted");

const completedDuration = new Trend("usecases_completed_duration", true);
const abortedDuration = new Trend("usecases_aborted_duration", true);

export function wrapTestFunction(testFunction: () => void): () => void {
return () => {
export type WrappableTestFunction<T> = (testData?: T) => Result<undefined>;
export type TestFunction<T> = (testData?: T) => void;

export function wrapTestFunction<T>(
testFunction: WrappableTestFunction<T>,
): TestFunction<T> {
return (testData?: T) => {
const start = Date.now();
try {
testFunction();
completedCounter.add(1);
completedDuration.add(Date.now() - start);
} catch (error: unknown) {
abortedCounter.add(1, { name: getErrorName(error as Error) });
abortedDuration.add(Date.now() - start);
console.log(`${__VU} - ${__ITER}`);
console.log(error);
const result = testFunction(testData);
if (result.ok) {
completedCounter.add(1);
completedDuration.add(Date.now() - start);
} else {
abortedCounter.add(1, { name: result.error });
abortedDuration.add(Date.now() - start);
console.error(`${__VU} - ${__ITER} | ${result.error}`);
}
} finally {
sleep(1);
}
};
}

function getErrorName(error: Error): string {
let name = "unknown error";
if (error.name) name = `${error.name}`;
else if (error.message) name = `${error.message}`;
return name;
}

0 comments on commit 329f573

Please sign in to comment.