Skip to content

Commit

Permalink
Create k6 scripts and http to test
Browse files Browse the repository at this point in the history
  • Loading branch information
kevariable committed Dec 29, 2024
1 parent 6aea7b6 commit bc1e92c
Show file tree
Hide file tree
Showing 15 changed files with 323 additions and 12 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ yarn-error.log
/.phpstorm.meta.php
/_ide_helper.php
/coverage
/dist
/dist
/k6/node_modules/
17 changes: 14 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: '3.8'

services:
app:
image: ts-restful-api:dev
Expand All @@ -11,10 +9,23 @@ services:
- /app/node_modules
command: ["npm", "run", "dev"]
ports:
- 3000:3000
- "3000:3000"
depends_on:
- mysql

k6:
image: grafana/k6
working_dir: /app
environment:
K6_WEB_DASHBOARD: true
tty: true
command: >
run src/ping.js
ports:
- "5665:5665"
volumes:
- ./k6:/app

mysql:
image: mysql/mysql-server:8.0
restart: always
Expand Down
15 changes: 15 additions & 0 deletions k6/node_modules/@types/k6/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions k6/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions k6/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"devDependencies": {
"@types/k6": "^0.54.2"
}
}
42 changes: 42 additions & 0 deletions k6/src/create-contact.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { vu } from 'k6/execution'
import {loginUser} from "./supports/user.js";
import {createContact} from "./supports/contact.js";

export const options = {
vus: 10,
duration: '30s'
}

export function setup() {
const data = []

for (let i = 0; i < 10; i++) {
data.push({
first_name: 'Contact',
last_name: `No ${i}`,
email: `contact${i}@example.com`,
phone: `i`
})
}

return data
}

const getToken = () => {
const username = `kevin${vu.idInInstance}`
const loginRequest = JSON.stringify({
username,
password: 'password'
})


return loginUser(loginRequest)
}

export default function (data) {
const { token } = getToken()

for (const value of data) {
createContact(JSON.stringify(value), token)
}
}
11 changes: 11 additions & 0 deletions k6/src/ping.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import http from 'k6/http';
import { sleep } from 'k6';

export const options = {
vus: 10,
duration: '30s',
};

export default function() {
http.get('http://app:3000/api/ping');
}
97 changes: 97 additions & 0 deletions k6/src/register.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import http from 'k6/http';
import {check, fail} from 'k6';
import { uuidv4 } from 'https://jslib.k6.io/k6-utils/1.4.0/index.js';

export const options = {
vus: 10,
duration: '10s',
};

const currentUserCallback = (data, { uniqueId }) => {
const currentUser = http.get('http://app:3000/api/users/current', {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-API-TOKEN': data.token
}
});

const currentUserResponse = currentUser.json()

const currentUserCheck = check(currentUser, {
'Current user response status must 200': currentUser.status === 200,
'Current user response data must not null': currentUserResponse.data !== null ,
})

if (! currentUserCheck) {
fail(`Failed to get current user-${uniqueId}`)
}

return currentUserResponse
}

const loginCallback = (data, { uniqueId, password }) => {
const loginRequest = JSON.stringify({
username: data?.username || '',
password,
})

const login = http.post('http://app:3000/api/login', loginRequest, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});

const loginResponse = login.json()

const loginCheck = check(login, {
'Login response status must 200': login.status === 200,
'Login response token must not null': (loginResponse.data?.token || null) !== null ,
})

if (! loginCheck) {
fail(`Failed to get user-${uniqueId}`)
}

return loginResponse
}

const registerCallback = ({ uniqueId, password }) => {
const data = JSON.stringify({
username: `user-${uniqueId}`,
password,
name: `user-${uniqueId}`
})

const userRegistered = http.post('http://app:3000/api/users', data, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})

const userRegisteredResponse = userRegistered.json()

const userRegisteredCheck = check(userRegistered, {
'Register response status must 201': userRegistered.status === 201,
'Register response data must not null': userRegisteredResponse.data !== null ,
})

if (! userRegisteredCheck) {
fail(`Failed to registering user-${uniqueId}`)
}

return userRegisteredResponse
}

export default function() {
const uniqueId = uuidv4()
const password = 'password'

const registerResponse = registerCallback({ uniqueId, password })

const loginResponse = loginCallback(registerResponse.data, { uniqueId, password })

return currentUserCallback(loginResponse.data, { uniqueId })
}
22 changes: 22 additions & 0 deletions k6/src/stages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import http from 'k6/http';

export const options = {
stages: [
{
duration: '10s',
target: 20
},
{
duration: '10s',
target: 10
},
{
duration: '10s',
target: 0
},
]
};

export default function() {
http.get('http://app:3000/api/ping');
}
19 changes: 19 additions & 0 deletions k6/src/supports/contact.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import http from "k6/http";
import {check} from "k6";

export const createContact = (createContactRequest, token) => {
const createContact = http.post('http://app:3000/api/contacts', createContactRequest, {
headers: {
'X-API-TOKEN': token,
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})

const createContactResponse = createContact.json()

check(createContact, {
'response must be 200': createContact.status === 200,
'response contains id': createContactResponse.data.id !== undefined,
})
}
24 changes: 24 additions & 0 deletions k6/src/supports/user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import http from "k6/http";
import {check, fail} from "k6";

export function loginUser(loginUserRequest){
const login = http.post('http://app:3000/api/login', loginUserRequest, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});

const { data: response = {} } = login.json()

const checkLogin = check(login, {
'login response must be 200': login.status === 200,
'login token response must be exists': response.token !== undefined
})

if (!checkLogin) {
fail(`Login failed user ${username}`)
}

return response
}
10 changes: 2 additions & 8 deletions src/action/user/create-user.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import {
CreateUserRequest,
toUserResponse,
UserResponse
} from '../../model/user-model'
import { CreateUserRequest } from '../../model/user-model'
import { Validation } from '../../validation/validation'
import { UserValidation } from '../../validation/user-validation'
import { prismaClient } from '../../application/database'
Expand Down Expand Up @@ -34,10 +30,8 @@ export default class CreateUser {
await bcrypt.hash(registerRequest.password, 10)
).toString()

const user = await prismaClient.user.create({
return await prismaClient.user.create({
data: registerRequest
})

return user
}
}
9 changes: 9 additions & 0 deletions src/controller/ping-controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { NextFunction, Request, Response } from 'express'

export class PingController {
static async ping(req: Request, res: Response, next: NextFunction) {
res.status(200).json({
success: true
})
}
}
2 changes: 2 additions & 0 deletions src/route/public-api.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import express from 'express'
import { UserController } from '../controller/user-controller'
import {PingController} from "../controller/ping-controller";

export const publicRouter = express.Router()

publicRouter.get('/api/ping', PingController.ping)
publicRouter.post('/api/login', UserController.login)
publicRouter.post('/api/users', UserController.register)
41 changes: 41 additions & 0 deletions test.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
### Send POST request with json body
< {%
request.variables.set("clients", [
{"username": "kevin1", "name": "kevin1", "password": "password"},
{"username": "kevin2", "name": "kevin2", "password": "password"},
{"username": "kevin3", "name": "kevin3", "password": "password"},
{"username": "kevin4", "name": "kevin4", "password": "password"},
{"username": "kevin5", "name": "kevin5", "password": "password"},
{"username": "kevin6", "name": "kevin6", "password": "password"},
{"username": "kevin7", "name": "kevin7", "password": "password"},
{"username": "kevin8", "name": "kevin8", "password": "password"},
{"username": "kevin9", "name": "kevin9", "password": "password"},
{"username": "kevin10", "name": "kevin10", "password": "password"}
])
%}

POST http://localhost:3000/api/users
Content-Type: application/json

{
"username": "{{$.clients..username}}",
"name": "{{$.clients..name}}",
"password": "{{$.clients..password}}"
}

> {%
let currentClient = request.variables.get("clients")[request.iteration()];
client.test(`Account creation for ${currentClient.username}`, () => {
let username = jsonPath(response.body, 'data.username')
client.assert(username === currentClient.username, "Usernames do not match");
});
%}

### Send POST request with json body
POST http://localhost:3000/api/login
Content-Type: application/json

{
"username": "kevin1",
"password": "password"
}

0 comments on commit bc1e92c

Please sign in to comment.