Skip to content

Commit

Permalink
build: k6 load test OH-42 (#199)
Browse files Browse the repository at this point in the history
  • Loading branch information
terovirtanen authored Jan 16, 2024
1 parent e9bb27d commit 57f73f3
Show file tree
Hide file tree
Showing 6 changed files with 274 additions and 0 deletions.
29 changes: 29 additions & 0 deletions load-tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Hauki load testing

Scripts for load testing Hauki API.

## Prerequisities

* [k6](https://k6.io/docs/getting-started/installation) installed

## How to run the tests

This script works as a wrapper for the k6 command and ensures that the proper authentication token is provided in the requests. Thus you can pass all the same parameters as you would do when using k6 directly.

```bash
./run.sh -v 1 -i 1 <SCRIPT>
```

## How to run Hauki scenarios

```bash
./run.sh hauki-scenarios.js
```

## HTML report
The script is using [k6-reporter](https://github.com/benc-uk/k6-reporter) for outputting html test report. The results `summary.html` is created under this folder.

You can also open it automatically after the test like this.
```bash
./run.sh hauki-scenarios.js && open summary.html
```
128 changes: 128 additions & 0 deletions load-tests/hauki-scenarios.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/* eslint-disable */
import { check, group, sleep } from 'k6';
import { Httpx } from 'https://jslib.k6.io/httpx/0.0.1/index.js';
import { createOpeningHour } from './helpers/mocks.js';
import commands from './helpers/commands.js';
import { formatDate, getRandomArbitrary } from './helpers/utils.js';
import { htmlReport } from 'https://raw.githubusercontent.com/benc-uk/k6-reporter/main/dist/bundle.js';
import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.1/index.js';

const env = global['__ENV'] || process.env;

const apiUrl = env.API_URL || 'https://hauki.api.stage.hel.ninja/v1';
// const authParams = env.AUTH_PARAMS;
const tprekResourceId = env.HAUKI_RESOURCE || 'tprek:41683';

const IDLE_TIME = 10;

const session = new Httpx();
session.setBaseUrl(`${apiUrl}`);
// session.addHeader('Authorization', `haukisigned ${authParams}`);

const { addNewDatePeriod, viewResource } = commands(session);

export const options = {
thresholds: {
http_req_duration: ['p(90) < 2000'],
},
scenarios: {
// add requires authorization
// addOpeningHours: {
// executor: 'constant-vus',
// exec: 'addOpeningHours',
// vus: 5,
// duration: '1m',
// },
requestOpeningHours: {
executor: 'constant-vus',
exec: 'requestOpeningHours',
vus: 50,
duration: '1m',
},
},
teardownTimeout: '5m',
};

export function setup() {
const { id } = session
.get(`/resource/${tprekResourceId}/?format=json`)
.json();

session.post('/date_period/', JSON.stringify(createOpeningHour(id)), {
headers: { 'Content-Type': 'application/json' },
});

const resources = session.get('/resource/').json();

return { resourceId: id, resources };
}

export function addOpeningHours({ resourceId }) {
viewResource(tprekResourceId, resourceId);
sleep(getRandomArbitrary(10, IDLE_TIME));
addNewDatePeriod(resourceId);
viewResource(tprekResourceId, resourceId);
}

export function requestOpeningHours({ resources }) {
const random = Math.floor(Math.random() * resources.results.length);
const resourceId = resources.results[random].id;

group('Get opening hours', () => {
sleep(getRandomArbitrary(1, 5));

const startDate = new Date();
const endDate = new Date();
endDate.setDate(startDate.getDate() + 365);

const url = `/resource/${resourceId}/opening_hours/?start_date=${formatDate(
startDate
)}&end_date=${formatDate(endDate)}`;

const start = new Date().toISOString();
const result = session.get(url);

if (result.status !== 200) {
console.log(
JSON.stringify(
{
url: `${apiUrl}/${url}`,
status: result.status,
start,
end: new Date().toISOString(),
body: JSON.stringify(result.body),
},
null,
2
)
);
}

check(result, {
'Fetching opening hours returns 200': (r) => r.status === 200,
});
});
}

export function teardown({ resourceId }) {
const response = session
.get(`/date_period/?resource=${resourceId}&end_date_gte=-1d&format=json`)
.json();

const toBeDeleted = response.filter((datePeriod) =>
datePeriod.name.fi.includes('load-test')
);

toBeDeleted.forEach((datePeriod) => {
session.delete(`/date_period/${datePeriod.id}/`);
});

console.log(`Deleted date periods: ${toBeDeleted.length}`);
}

export function handleSummary(data) {
return {
'summary.html': htmlReport(data),
stdout: textSummary(data, { indent: ' ', enableColors: true }),
};
}
69 changes: 69 additions & 0 deletions load-tests/helpers/commands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { check, sleep } from 'k6';
import { createOpeningHour } from './mocks.js';
import { getRandomArbitrary } from './utils.js';

const commands = (session) => {
const viewResource = (origId, resourceId) => {
session.post(`/resource/${origId}/permission_check/`);
session.request('OPTIONS', '/date_period/');

const response = session.get(
`/date_period/?resource=${resourceId}&end_date_gte=-1d&format=json`
);

check(response, {
'GET date periods status is 200': (r) => r.status === 200,
});

check(session.get(`/resource/${origId}/?format=json`), {
'GET resource status is status 200': (r) => r.status === 200,
});

return response.json();
};

const viewDatePeriod = (resourceId, datePeriodId) => {
session.post(`/resource/${resourceId}/permission_check/`);

check(session.get(`/resource/${resourceId}/?format=json`), {
'GET resource status is status 200': (r) => r.status === 200,
});

check(session.get(`/date_period/${datePeriodId}/?format=json`), {
'GET date period status is 200': (r) => r.status === 200,
});

session.request('OPTIONS', '/date_period/');
};

const addNewDatePeriod = (resourceId) => {
session.post(`/resource/${resourceId}/permission_check/`);
session.request('OPTIONS', '/date_period/');

check(session.get(`/resource/${resourceId}/?format=json`), {
'GET resource status is status 200': (r) => r.status === 200,
});

sleep(getRandomArbitrary(10, 20));

const response = session.post(
'/date_period/',
JSON.stringify(createOpeningHour(resourceId)),
{
headers: { 'Content-Type': 'application/json' },
}
);

check(response, {
'POST date period status is status 201': (r) => r.status === 201,
});
};

return {
addNewDatePeriod,
viewDatePeriod,
viewResource,
};
};

export default commands;
33 changes: 33 additions & 0 deletions load-tests/helpers/mocks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* eslint-disable import/prefer-default-export */
export const createOpeningHour = (resource) => ({
resource,
name: { fi: `load-test ${new Date().toISOString()}`, sv: '', en: '' },
description: { fi: '', sv: '', en: '' },
start_date: '2022-01-01',
end_date: '2022-12-31',
resource_state: 'undefined',
override: false,
time_span_groups: [
{
rules: [],
time_spans: [
{
description: { fi: null, sv: null, en: null },
end_time: '16:00:00',
start_time: '08:00:00',
resource_state: 'open',
full_day: false,
weekdays: [1, 2, 3, 4, 5],
},
{
description: { fi: null, sv: null, en: null },
end_time: '18:00:00',
start_time: '10:00:00',
resource_state: 'open',
full_day: false,
weekdays: [6],
},
],
},
],
});
6 changes: 6 additions & 0 deletions load-tests/helpers/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/* eslint-disable import/prefer-default-export */
export const getRandomArbitrary = (min, max) => {
return Math.random() * (max - min) + min;
};

export const formatDate = (d) => d.toISOString().split('T')[0];
9 changes: 9 additions & 0 deletions load-tests/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
cd $(dirname "$0")

export HAUKI_USER='[email protected]';
export HAUKI_RESOURCE='tprek:41683'; #40759 41835 41683
export API_URL=https://hauki.api.stage.hel.ninja/v1

# export AUTH_PARAMS=$(node ../scripts/generate-auth-params.js)

k6 run "$@"

0 comments on commit 57f73f3

Please sign in to comment.