-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* setup project + first uc * finish helper for loading related resources * add default test profiles * add uc user-list; logic for usermix * refactor helpers * finish show-user-list uc * refactor show-start; stub oidc-provider uc * clean-up + csv-output
- Loading branch information
Showing
22 changed files
with
2,400 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -128,3 +128,5 @@ dist | |
.yarn/build-state.yml | ||
.yarn/install-state.gz | ||
.pnp.* | ||
|
||
loadtest/data/users.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Ignore artifacts: | ||
build | ||
coverage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"eslint.useFlatConfig": true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,37 @@ | ||
# schulportal-load-tests | ||
|
||
Load and performance tests for the schulportal | ||
|
||
## How to run | ||
|
||
You will need a working installation of k6. ([How to install k6](https://grafana.com/docs/k6/latest/set-up/install-k6/)). | ||
|
||
Tests are categorized as | ||
|
||
``` | ||
spike | ||
stress | ||
breakpoint | ||
``` | ||
|
||
To run all usecases with the stress-configuration against `https://example.env/`: | ||
|
||
```sh | ||
./run.sh "https://example.env/" stress | ||
``` | ||
|
||
And you can selectively run usecases by providing a regex for the filename: | ||
|
||
```sh | ||
./run.sh "https://example.env/" stress login | ||
``` | ||
|
||
### Configuration | ||
|
||
You have to provide `loadtest/data/users.json` with usernames, passwords and role for the tests to work. An example is provided in `loadtest/data/`. | ||
|
||
## Development | ||
|
||
```sh | ||
npm run check # to format, lint, typecheck the code | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import globals from "globals"; | ||
import pluginJs from "@eslint/js"; | ||
import tseslint from "typescript-eslint"; | ||
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended"; | ||
|
||
export default [ | ||
{ files: ["**/*.{js,mjs,cjs,ts}"] }, | ||
{ | ||
languageOptions: { | ||
globals: globals.node, | ||
parserOptions: { | ||
projectService: true, | ||
tsconfigRootDir: import.meta.dirname, | ||
}, | ||
}, | ||
}, | ||
{ | ||
ignores: ["eslint.config.mjs"], | ||
}, | ||
pluginJs.configs.recommended, | ||
...tseslint.configs.recommendedTypeChecked, | ||
eslintPluginPrettierRecommended, | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
[ | ||
{ | ||
"username": "myUsername", | ||
"password": "myPassword", | ||
"role": "role value; see loadtest/util/users.ts" | ||
} | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { sleep } from "k6"; | ||
import showStart from "../usecases/1_show-start.ts"; | ||
import { getDefaultAdminMix } from "../util/users.ts"; | ||
import { goToOIDCServiceProvider } from "../util/page.ts"; | ||
|
||
export default function main(users = getDefaultAdminMix()) { | ||
showStart(users); | ||
goToOIDCServiceProvider(); | ||
sleep(1); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { check, fail, group, sleep } from "k6"; | ||
import http from "k6/http"; | ||
import { defaultHttpCheck, defaultTimingCheck } from "../util/checks.ts"; | ||
import { getDefaultOptions, getFrontendUrl } from "../util/config.ts"; | ||
import { loadLinkedResourcesAndCheck } from "../util/load-linked-resources.ts"; | ||
import { getDefaultUserMix } from "../util/users.ts"; | ||
|
||
const SPSH_BASE = getFrontendUrl(); | ||
// not needed yet | ||
// const KC_BASE = __ENV["KC_BASE"]; | ||
|
||
export const options = { | ||
...getDefaultOptions(), | ||
}; | ||
|
||
export default function main(users = getDefaultUserMix()) { | ||
/** | ||
* URL for final login, which we obtain from keycloak during oidc-login | ||
*/ | ||
let loginUrl = ""; | ||
|
||
group("load spsh", () => { | ||
const pageResponse = http.get(SPSH_BASE); | ||
check(pageResponse, defaultHttpCheck); | ||
loadLinkedResourcesAndCheck(pageResponse); | ||
}); | ||
|
||
group("go to kc login and submit form", () => { | ||
// load page | ||
const loginPageResponse = http.get( | ||
SPSH_BASE + "api/auth/login?redirectUrl=/", | ||
); | ||
check(loginPageResponse, defaultHttpCheck); | ||
loadLinkedResourcesAndCheck(loginPageResponse); | ||
|
||
// submit form | ||
const doc = loginPageResponse.html(); | ||
const actionUrl = doc.find("#kc-form-login").attr("action"); | ||
if (!actionUrl) fail("action for #kc-form-login was not found"); | ||
|
||
const user = users.getLogin(); | ||
const loginData = { | ||
...user, | ||
credentialId: "", | ||
}; | ||
const loginResponse = http.post(actionUrl, loginData, { redirects: 0 }); | ||
check(loginResponse, { | ||
"submitting login form to kc succeeded": () => | ||
loginResponse.status === 302, | ||
...defaultTimingCheck, | ||
}); | ||
|
||
// retrieve the loginUrl from the response | ||
// this includes state, session_state, iss and security code | ||
loginUrl = loginResponse.headers["Location"]; | ||
if (!loginUrl) { | ||
fail("did not find Location in kc response"); | ||
} | ||
}); | ||
|
||
group("finish login", () => { | ||
const response = http.get(loginUrl); | ||
check(response, { | ||
"login succeeded": () => | ||
response.status === 200 || response.status === 302, | ||
...defaultTimingCheck, | ||
}); | ||
loadLinkedResourcesAndCheck(response); | ||
}); | ||
|
||
sleep(1); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { group, sleep } from "k6"; | ||
import { | ||
getLoginInfo, | ||
getServiceProviderLogos, | ||
getServiceProviders, | ||
} from "../util/api.ts"; | ||
import { goToStartPage } from "../util/page.ts"; | ||
import { getDefaultAdminMix } from "../util/users.ts"; | ||
import login from "./1_login.ts"; | ||
|
||
export default function main(users = getDefaultAdminMix()) { | ||
login(users); | ||
|
||
group("load start page", () => { | ||
goToStartPage(); | ||
|
||
getLoginInfo(); | ||
const providers = getServiceProviders(); | ||
getServiceProviderLogos(providers); | ||
}); | ||
|
||
sleep(1); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
import { group, sleep } from "k6"; | ||
import { | ||
getLoginInfo, | ||
getOrganisationen, | ||
getPersonen, | ||
getPersonenUebersicht, | ||
getRollen, | ||
Paginated, | ||
PersonDatensatz, | ||
PersonenUebersicht, | ||
} from "../util/api.ts"; | ||
import { getDefaultAdminMix } from "../util/users.ts"; | ||
import goToStart from "./1_show-start.ts"; | ||
|
||
export default function main(users = getDefaultAdminMix()) { | ||
goToStart(users); | ||
|
||
// these are used to test the filters | ||
let orgId = ""; | ||
let rolleId = ""; | ||
let personenuebersicht: PersonenUebersicht | undefined = undefined; | ||
|
||
group("load user page", () => { | ||
getLoginInfo(); | ||
const organisationen = getOrganisationen([ | ||
"limit=25", | ||
"systemrechte=PERSONEN_VERWALTEN", | ||
"excludeTyp=KLASSE", | ||
]); | ||
orgId = pickRandomItem(organisationen).id; | ||
|
||
// TODO: see if this behaviour should be emulated | ||
for (let i = 0; i < 2; i++) { | ||
const personIds = getPersonenIds(); | ||
const personenuebersichten = getPersonenUebersicht(personIds); | ||
personenuebersicht = pickRandomItem(personenuebersichten); | ||
} | ||
|
||
const rollen = getRollen(["rolleName="]); | ||
rolleId = pickRandomItem(rollen).id; | ||
}); | ||
|
||
group("hit pages", () => { | ||
for (let offset = 0; offset < 5; offset++) { | ||
getPersonen([`offset=${offset}`, "limit=30", "suchFilter="]); | ||
} | ||
}); | ||
|
||
group("toggle filters", () => { | ||
group("schule", () => { | ||
getOrganisationen([ | ||
"limit=25", | ||
"searchString=", | ||
"typ=KLASSE", | ||
`administriertVon=${orgId}`, | ||
]); | ||
const personen = getPersonen([ | ||
"offset=0", | ||
"limit=30", | ||
`organisationIDs=${orgId}`, | ||
"suchFilter=", | ||
]); | ||
const personIds = getPersonenIds(personen); | ||
getPersonenUebersicht(personIds); | ||
|
||
emulateFilterReset(); | ||
}); | ||
|
||
group("rolle", () => { | ||
getOrganisationen([ | ||
"limit=25", | ||
"searchString=", | ||
"typ=KLASSE", | ||
`administriertVon=${orgId}`, | ||
]); | ||
const personen = getPersonen([ | ||
"offset=0", | ||
"limit=30", | ||
`rolleIDs=${rolleId}`, | ||
"suchFilter=", | ||
]); | ||
const personIds = getPersonenIds(personen); | ||
getPersonenUebersicht(personIds); | ||
|
||
emulateFilterReset(); | ||
}); | ||
|
||
group("filter list", () => { | ||
const filters = [ | ||
personenuebersicht?.benutzername, | ||
personenuebersicht?.vorname, | ||
personenuebersicht?.nachname, | ||
]; | ||
for (const filter of filters) { | ||
const personen = getPersonen([ | ||
"offset=0", | ||
"limit=30", | ||
`suchFilter=${filter}`, | ||
]); | ||
const personIds = getPersonenIds(personen); | ||
getPersonenUebersicht(personIds); | ||
|
||
emulateFilterReset(); | ||
} | ||
}); | ||
}); | ||
|
||
sleep(1); | ||
} | ||
|
||
function getPersonenIds(personen?: Paginated<PersonDatensatz>): Set<string> { | ||
if (!personen) personen = getPersonen(); | ||
return new Set(personen.items.map(({ person }) => person.id)); | ||
} | ||
|
||
function emulateFilterReset() { | ||
const personIds = getPersonenIds(); | ||
getPersonenUebersicht(personIds); | ||
getOrganisationen([ | ||
"limit=25", | ||
"systemrechte=PERSONEN_VERWALTEN", | ||
"excludeTyp=KLASSE", | ||
]); | ||
getOrganisationen([ | ||
"limit=25", | ||
"searchString=", | ||
"systemrechte=PERSONEN_VERWALTEN", | ||
"excludeTyp=KLASSE", | ||
]); | ||
} | ||
|
||
function pickRandomItem<T>(array: Array<T>): T { | ||
return array[Math.floor(Math.random() * array.length)]; | ||
} |
Oops, something went wrong.