diff --git a/.github/workflows/dispatch-k6-performance.yml b/.github/workflows/dispatch-k6-performance.yml index b7e434d06..478b0c853 100644 --- a/.github/workflows/dispatch-k6-performance.yml +++ b/.github/workflows/dispatch-k6-performance.yml @@ -10,12 +10,12 @@ on: environment: description: 'Environment' required: true - default: 'staging' + default: 'yt01' type: choice options: - test - staging - - performance + - yt01 tokens: description: 'Tokens to generate; for create dialog, search, none, or both' required: true @@ -26,10 +26,15 @@ on: - enterprise - personal - none + tag: + description: 'tag the performance test' + required: true + default: 'Performance test' + type: string vus: description: 'Number of VUS' required: true - default: 10 + default: 1 type: number duration: description: 'Duration of test, ie 30s, 1m, 10m' @@ -43,8 +48,11 @@ on: type: choice options: - 'tests/k6/tests/serviceowner/performance/create-dialog.js' - - 'tests/k6/tests/enduser/performance/simple-search.js' + - 'tests/k6/tests/serviceowner/performance/create-remove-dialog.js' + - 'tests/k6/tests/enduser/performance/enduser-search.js' + - 'tests/k6/tests/graphql/performance/graphql-search.js' +run-name: ${{ inputs.tag }} vus ${{ inputs.vus }} duration ${{ inputs.duration }} jobs: k6-performance: name: "Run K6 performance test" diff --git a/.github/workflows/workflow-run-k6-performance.yml b/.github/workflows/workflow-run-k6-performance.yml index cd876c9e5..214c69173 100644 --- a/.github/workflows/workflow-run-k6-performance.yml +++ b/.github/workflows/workflow-run-k6-performance.yml @@ -34,6 +34,7 @@ on: jobs: k6-test: runs-on: ubuntu-latest + environment: ${{ inputs.environment }} permissions: checks: write pull-requests: write diff --git a/tests/k6/common/config.js b/tests/k6/common/config.js index faf9a4500..0478cff55 100644 --- a/tests/k6/common/config.js +++ b/tests/k6/common/config.js @@ -1,22 +1,41 @@ +const localBaseUrl = "https://localhost:7214/"; +const localDockerBaseUrl = "https://host.docker.internal:7214/"; +const testBaseUrl = "https://altinn-dev-api.azure-api.net/dialogporten/"; +const yt01BaseUrl = "https://platform.yt01.altinn.cloud/dialogporten/"; +const stagingBaseUrl = "https://platform.tt02.altinn.no/dialogporten/"; +const prodBaseUrl = "https://platform.altinn.no/dialogporten/"; + +const endUserPath = "api/v1/enduser/"; +const serviceOwnerPath = "api/v1/serviceowner/"; +const graphqlPath = "graphql"; + export const baseUrls = { v1: { enduser: { - localdev: "https://localhost:7214/api/v1/enduser/", - localdev_docker: "https://host.docker.internal:7214/api/v1/enduser/", - test: "https://altinn-dev-api.azure-api.net/dialogporten/api/v1/enduser/", - yt01: "https://platform.yt01.altinn.cloud/dialogporten/api/v1/enduser/", - staging: "https://platform.tt02.altinn.no/dialogporten/api/v1/enduser/", - prod: "https://platform.altinn.no/dialogporten/api/v1/enduser/" + localdev: localBaseUrl + endUserPath, + localdev_docker: localDockerBaseUrl + endUserPath, + test: testBaseUrl + endUserPath, + yt01: yt01BaseUrl + endUserPath, + staging: stagingBaseUrl + endUserPath, + prod: prodBaseUrl + endUserPath }, serviceowner: { - localdev: "https://localhost:7214/api/v1/serviceowner/", - localdev_docker: "https://host.docker.internal:7214/api/v1/serviceowner/", - test: "https://altinn-dev-api.azure-api.net/dialogporten/api/v1/serviceowner/", - yt01: "https://platform.yt01.altinn.cloud/dialogporten/api/v1/serviceowner/", - staging: "https://platform.tt02.altinn.no/dialogporten/api/v1/serviceowner/", - prod: "https://platform.altinn.no/dialogporten/api/v1/serviceowner/" - } - } + localdev: localBaseUrl + serviceOwnerPath, + localdev_docker: localDockerBaseUrl + serviceOwnerPath, + test: testBaseUrl + serviceOwnerPath, + yt01: yt01BaseUrl + serviceOwnerPath, + staging: stagingBaseUrl + serviceOwnerPath, + prod: prodBaseUrl + serviceOwnerPath + }, + graphql: { + localdev: localBaseUrl + graphqlPath, + localdev_docker: localDockerBaseUrl + graphqlPath, + test: testBaseUrl + graphqlPath, + yt01: yt01BaseUrl + graphqlPath, + staging: stagingBaseUrl + graphqlPath, + prod: prodBaseUrl + graphqlPath + }, + } }; export const defaultEndUserOrgNo = "310923044"; // ÆRLIG UROKKELIG TIGER AS @@ -43,4 +62,6 @@ export const baseUrlEndUser = baseUrls[__ENV.API_VERSION]["enduser"][__ENV.API_E export const baseUrlServiceOwner = baseUrls[__ENV.API_VERSION]["serviceowner"][__ENV.API_ENVIRONMENT]; export const tokenGeneratorEnv = __ENV.API_ENVIRONMENT == "yt01" ? "yt01" : "tt02"; // yt01 is the only environment that has a separate token generator environment +export const baseUrlGraphql = baseUrls[__ENV.API_VERSION]["graphql"][__ENV.API_ENVIRONMENT]; + export const sentinelValue = "dialogporten-e2e-sentinel"; diff --git a/tests/k6/common/request.js b/tests/k6/common/request.js index cc2be8457..206a45241 100644 --- a/tests/k6/common/request.js +++ b/tests/k6/common/request.js @@ -1,5 +1,5 @@ import { default as http } from 'k6/http'; -import { baseUrlEndUser, baseUrlServiceOwner } from './config.js' +import { baseUrlEndUser, baseUrlGraphql, baseUrlServiceOwner } from './config.js' import { getServiceOwnerTokenFromGenerator, getEnduserTokenFromGenerator } from './token.js' import { extend } from './extend.js' @@ -125,3 +125,9 @@ export function patchEU(url, body, params = null, tokenOptions = null) { export function deleteEU(url, params = null, tokenOptions = null) { return http.request('DELETE', baseUrlEndUser + url, getEnduserRequestParams(params, tokenOptions)); } + +export function postGQ(body, params = null) { + body = JSON.stringify({ query: body }) + params = extend(true, {}, params, { headers: { 'Content-Type': 'application/json' }}); + return http.post(baseUrlGraphql, body, params); +} diff --git a/tests/k6/common/testimports.js b/tests/k6/common/testimports.js index 14f1e919f..6463ecfd8 100644 --- a/tests/k6/common/testimports.js +++ b/tests/k6/common/testimports.js @@ -14,7 +14,8 @@ export { putSO, patchSO, deleteSO, - purgeSO + purgeSO, + postGQ } from './request.js'; export { setTitle, diff --git a/tests/k6/tests/enduser/performance/enduser-search.js b/tests/k6/tests/enduser/performance/enduser-search.js new file mode 100644 index 000000000..360d8e7d3 --- /dev/null +++ b/tests/k6/tests/enduser/performance/enduser-search.js @@ -0,0 +1,27 @@ +import { enduserSearch } from '../../performancetest_common/simpleSearch.js' +import { getDefaultThresholds } from '../../performancetest_common/getDefaultThresholds.js'; +import { endUsersWithTokens } from '../../performancetest_common/readTestdata.js'; + +export let options = { + summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(95)', 'p(99)', 'p(99.5)', 'p(99.9)', 'count'], + thresholds: getDefaultThresholds(['http_req_duration', 'http_reqs'],['enduser search', + 'get dialog', + 'get dialog activities', + 'get dialog activity', + 'get seenlogs', + 'get seenlog', + 'get transmissions', + 'get transmission', + 'get labellog' + ]) +}; + +export default function() { + if ((options.vus === undefined || options.vus === 1) && (options.iterations === undefined || options.iterations === 1)) { + enduserSearch(endUsersWithTokens[0]); + } + else { + enduserSearch(randomItem(endUsersWithTokens)); + } +} + diff --git a/tests/k6/tests/enduser/performance/simple-search.js b/tests/k6/tests/enduser/performance/simple-search.js deleted file mode 100644 index be99517b2..000000000 --- a/tests/k6/tests/enduser/performance/simple-search.js +++ /dev/null @@ -1,57 +0,0 @@ -import { getEU, expect, expectStatusFor, describe } from "../../../common/testimports.js"; -import { SharedArray } from 'k6/data'; -import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js'; -import { randomItem } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js'; - -const filenameEndusers = '../../performancetest_data/.endusers-with-tokens.csv'; - -const endUsers = new SharedArray('endUsers', function () { - try { - const csvData = papaparse.parse(open(filenameEndusers), { header: true, skipEmptyLines: true }).data; - if (!csvData.length) { - throw new Error('No data found in CSV file'); - } - csvData.forEach((user, index) => { - if (!user.token || !user.ssn) { - throw new Error(`Missing required fields at row ${index + 1}`); - } - }); - return csvData; - } catch (error) { - throw new Error(`Failed to load end users: ${error.message}`); - } -}); - -export let options = { - summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(95)', 'p(99)', 'p(99.5)', 'p(99.9)', 'count'], - thresholds: { - 'http_req_duration{name:simple search}': [], - 'http_reqs{name:simple search}': [], - }, -}; - -export default function() { - if ((options.vus === undefined || options.vus === 1) && (options.iterations === undefined || options.iterations === 1)) { - simpleSearch(endUsers[0]); - } - else { - simpleSearch(randomItem(endUsers)); - } -} - -export function simpleSearch(enduser) { - let paramsWithToken = { - headers: { - Authorization: "Bearer " + enduser.token - }, - tags: { name: 'simple search' } - } - let defaultParty = "urn:altinn:person:identifier-no:" + enduser.ssn; - let defaultFilter = "?Party=" + defaultParty; - describe('Perform simple dialog list', () => { - let r = getEU('dialogs' + defaultFilter, paramsWithToken); - expectStatusFor(r).to.equal(200); - expect(r, 'response').to.have.validJsonBody(); - }); -} - diff --git a/tests/k6/tests/graphql/performance/graphql-search.js b/tests/k6/tests/graphql/performance/graphql-search.js new file mode 100644 index 000000000..de5a9c04d --- /dev/null +++ b/tests/k6/tests/graphql/performance/graphql-search.js @@ -0,0 +1,37 @@ +/** + * The performance test for GraphQL search. + * Run: k6 run tests/k6/tests/graphql/performance/graphql-search.js --vus 1 --iterations 1 -e env=yt01 + */ + +import { randomItem } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js'; +import { getDefaultThresholds } from '../../performancetest_common/getDefaultThresholds.js'; +import { endUsersWithTokens as endUsers } from '../../performancetest_common/readTestdata.js'; +import { graphqlSearch } from "../../performancetest_common/simpleSearch.js"; + +/** + * The options object for configuring the performance test for GraphQL search. + * + * @property {string[]} summaryTrendStats - The summary trend statistics to include in the test results. + * @property {object} thresholds - The thresholds for the test metrics. + */ +export let options = { + summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(95)', 'p(99)', 'p(99.5)', 'p(99.9)', 'count'], + thresholds: getDefaultThresholds(['http_req_duration', 'http_reqs'],['graphql search']) +}; + +/** + * The default function for the performance test for GraphQL search. + */ +export default function() { + if (!endUsers || endUsers.length === 0) { + throw new Error('No end users loaded for testing'); + } + if ((options.vus === undefined || options.vus === 1) && (options.iterations === undefined || options.iterations === 1)) { + graphqlSearch(endUsers[0]); + } + else { + graphqlSearch(randomItem(endUsers)); + } +} + + diff --git a/tests/k6/tests/performancetest_common/createDialog.js b/tests/k6/tests/performancetest_common/createDialog.js new file mode 100644 index 000000000..ecd7591cb --- /dev/null +++ b/tests/k6/tests/performancetest_common/createDialog.js @@ -0,0 +1,61 @@ +/** + * Common functions for creating dialogs. + */ +import { uuidv4 } from 'https://jslib.k6.io/k6-utils/1.4.0/index.js'; +import { describe } from "../../common/describe.js"; +import { postSO, purgeSO } from "../../common/request.js"; +import { expect } from "../../common/testimports.js"; +import dialogToInsert from "../performancetest_data/01-create-dialog.js"; + +/** + * Creates a dialog. + * + * @param {Object} serviceOwner - The service owner object. + * @param {Object} endUser - The end user object. + */ +export function createDialog(serviceOwner, endUser) { + var paramsWithToken = { + headers: { + Authorization: "Bearer " + serviceOwner.token, + traceparent: uuidv4() + }, + tags: { name: 'create dialog' } + }; + + describe('create dialog', () => { + let r = postSO('dialogs', dialogToInsert(endUser.ssn, endUser.resource), paramsWithToken); + expect(r.status, 'response status').to.equal(201); + }); + +} + +/** + * Creates a dialog and removes it. + * + * @param {Object} serviceOwner - The service owner object. + * @param {Object} endUser - The end user object. + */ +export function createAndRemoveDialog(serviceOwner, endUser) { + var paramsWithToken = { + headers: { + Authorization: "Bearer " + serviceOwner.token + }, + tags: { name: 'create dialog' } + } + + let dialogId = 0; + describe('create dialog', () => { + paramsWithToken.tags.name = 'create dialog'; + let r = postSO('dialogs', dialogToInsert(endUser.ssn, endUser.resource), paramsWithToken); + expect(r.status, 'response status').to.equal(201); + dialogId = r.json(); + }); + + describe('remove dialog', () => { + paramsWithToken.tags.name = 'remove dialog'; + if (dialogId) { + let r = purgeSO('dialogs/' + dialogId, paramsWithToken); + expect(r.status, 'response status').to.equal(204); + } + }); +} diff --git a/tests/k6/tests/performancetest_common/getDefaultThresholds.js b/tests/k6/tests/performancetest_common/getDefaultThresholds.js new file mode 100644 index 000000000..12b912711 --- /dev/null +++ b/tests/k6/tests/performancetest_common/getDefaultThresholds.js @@ -0,0 +1,21 @@ +/** + * Creates default thresholds configuration for K6 tests. + * @param {string[]} counters - Array of counter names + * @param {string[]} labels - Array of label names + * @returns {Object} Threshold configuration object + * @throws {Error} If inputs are invalid + */ +export function getDefaultThresholds(counters, labels) { + if (!Array.isArray(counters) || !Array.isArray(labels)) { + throw new Error('Both counters and labels must be arrays'); + } + let thresholds = { + http_req_failed: ['rate<0.01'] + }; + for (const counter of counters) { + for (const label of labels) { + thresholds[`${counter}{name:${label}}`] = []; + } + } + return thresholds; +} diff --git a/tests/k6/tests/performancetest_common/readTestdata.js b/tests/k6/tests/performancetest_common/readTestdata.js new file mode 100644 index 000000000..0fbea3624 --- /dev/null +++ b/tests/k6/tests/performancetest_common/readTestdata.js @@ -0,0 +1,52 @@ +/** + * This file contains the implementation of reading test data from CSV files. + * The test data includes service owners, end users, and end users with tokens. + * The data is read using the PapaParse library and stored in SharedArray variables. + * + * @module readTestdata + */ + +import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js'; +import { SharedArray } from "k6/data"; + +const filenameServiceowners = '../performancetest_data/.serviceowners-with-tokens.csv'; +if (!__ENV.API_ENVIRONMENT) { + throw new Error('API_ENVIRONMENT must be set'); +} +const filenameEndusers = `../performancetest_data/endusers-${__ENV.API_ENVIRONMENT}.csv`; +const filenameEndusersWithTokens = '../performancetest_data/.endusers-with-tokens.csv'; + +/** + * SharedArray variable that stores the service owners data. + * The data is parsed from the CSV file specified by the filenameServiceowners variable. + * + * @name serviceOwners + * @type {SharedArray} + */ +export const serviceOwners = new SharedArray('serviceOwners', function () { + return papaparse.parse(open(filenameServiceowners), { header: true, skipEmptyLines: true }).data; +}); + +/** + * SharedArray variable that stores the end users data. + * The data is parsed from the CSV file specified by the filenameEndusers variable. + * The filenameEndusers variable is dynamically generated based on the value of the API_ENVIRONMENT environment variable. + * + * @name endUsers + * @type {SharedArray} + */ +export const endUsers = new SharedArray('endUsers', function () { + return papaparse.parse(open(filenameEndusers), { header: true, skipEmptyLines: true }).data; +}); + +/** + * SharedArray variable that stores the end users with tokens data. + * The data is parsed from the CSV file specified by the filenameEndusersWithTokens variable. + * + * @name endUsersWithTokens + * @type {SharedArray} + */ +export const endUsersWithTokens = new SharedArray('endUsersWithTokens', function () { + return papaparse.parse(open(filenameEndusersWithTokens), { header: true, skipEmptyLines: true }).data; +}); + diff --git a/tests/k6/tests/performancetest_common/simpleSearch.js b/tests/k6/tests/performancetest_common/simpleSearch.js new file mode 100644 index 000000000..8feb8ef37 --- /dev/null +++ b/tests/k6/tests/performancetest_common/simpleSearch.js @@ -0,0 +1,129 @@ +/** + * This file contains common functions for performing simple searches + * and GraphQL searches. + */ +import { randomItem, uuidv4 } from 'https://jslib.k6.io/k6-utils/1.4.0/index.js'; +import { expect, expectStatusFor } from "../../common/testimports.js"; +import { describe } from '../../common/describe.js'; +import { getEU, postGQ } from '../../common/request.js'; +import { getGraphqlParty } from '../performancetest_data/graphql-search.js'; + + +/** + * Retrieves the content for a dialog. + * Get dialog, dialog activities, seenlogs, labellog, and transmissions. + * @param {Object} response - The response object. + * @param {Object} paramsWithToken - The parameters with token. + * @returns {void} + */ +function retrieveDialogContent(response, paramsWithToken) { + const items = response.json().items; + if (!items?.length) return; + + const dialogId = items[0].id; + if (!dialogId) return; + + getContent(dialogId, paramsWithToken, 'get dialog'); + getContentChain(dialogId, paramsWithToken, 'get dialog activities', 'get dialog activity', '/activities/') + getContentChain(dialogId, paramsWithToken, 'get seenlogs', 'get seenlog', '/seenlog/') + getContent(dialogId, paramsWithToken, 'get labellog', '/labellog'); + getContentChain(dialogId, paramsWithToken, 'get transmissions', 'get transmission', '/transmissions/') +} + +/** + * Performs a simple search. + * @param {Object} enduser - The end user. + * @returns {void} + */ +export function enduserSearch(enduser) { + let paramsWithToken = { + headers: { + Authorization: "Bearer " + enduser.token, + traceparent: uuidv4() + }, + tags: { name: 'enduser search' } + } + let defaultParty = "urn:altinn:person:identifier-no:" + enduser.ssn; + let defaultFilter = "?Party=" + defaultParty; + describe('Perform enduser dialog list', () => { + let r = getEU('dialogs' + defaultFilter, paramsWithToken); + expectStatusFor(r).to.equal(200); + expect(r, 'response').to.have.validJsonBody(); + retrieveDialogContent(r, paramsWithToken); + }); +} + +/** + * Performs a enduser search. + * @param {string} dialogId - The dialog id. + * @param {Object} paramsWithToken - The parameters with token. + * @param {string} tag - Tagging the request. + * @param {string} path - The path to append to the URL. Can be empty or /labellog. + * @returns {void} + */ +export function getContent(dialogId, paramsWithToken, tag, path = '') { + const listParams = { + ...paramsWithToken, + tags: { ...paramsWithToken.tags, name: tag } + }; + getUrl('dialogs/' + dialogId + path, listParams); +} + +/** + * Retrieves the content chain. + * @param {string} dialogId - The dialog id. + * @param {Object} paramsWithToken - The parameters with token. + * @param {string} tag - Tagging the request. + * @param {string} subtag - Tagging the sub request. + * @param {string} endpoint - The endpoint to append to the URL. + * @returns {void} + */ +export function getContentChain(dialogId, paramsWithToken, tag, subtag, endpoint) { + const listParams = { + ...paramsWithToken, + tags: { ...paramsWithToken.tags, name: tag } + }; + let d = getUrl('dialogs/' + dialogId + endpoint, listParams); + let json = d.json(); + if (json.length > 0) { + const detailParams = { + ...paramsWithToken, + tags: { ...paramsWithToken.tags, name: subtag } + }; + getUrl('dialogs/' + dialogId + endpoint + randomItem(json).id, detailParams); + } +} + +/** + * Performs a GET request to the specified URL with the provided parameters. + * @param {string} url - The URL to send the GET request to. + * @param {Object} paramsWithToken - The parameters with token. + * @returns {Object} The response object. + */ +export function getUrl(url, paramsWithToken) { + let r = getEU(url, paramsWithToken); + expectStatusFor(r).to.equal(200); + expect(r, 'response').to.have.validJsonBody(); + return r; +} + +/** + * Performs a GraphQL search using the provided enduser token. + * + * @param {Object} enduser - The enduser object containing the token. + * @returns {void} + */ +export function graphqlSearch(enduser) { + let paramsWithToken = { + headers: { + Authorization: "Bearer " + enduser.token, + traceparent: uuidv4() + }, + tags: { name: 'graphql search' } + }; + describe('Perform graphql dialog list', () => { + let r = postGQ(getGraphqlParty(enduser.ssn), paramsWithToken); + expectStatusFor(r).to.equal(200); + expect(r, 'response').to.have.validJsonBody(); + }); +} diff --git a/tests/k6/tests/performancetest_data/01-create-dialog.js b/tests/k6/tests/performancetest_data/01-create-dialog.js new file mode 100644 index 000000000..a5e994cb8 --- /dev/null +++ b/tests/k6/tests/performancetest_data/01-create-dialog.js @@ -0,0 +1,54 @@ +import {default as createDialogPayload} from "../serviceowner/testdata/01-create-dialog.js" + +const ACTIVITY_TYPE_INFORMATION = 'Information'; + +function cleanUp(originalPayload) { + if (!originalPayload || typeof originalPayload !== 'object') { + throw new Error('Invalid payload'); + } + + const payload = { ...originalPayload }; + const { visibleFrom, ...payloadWithoutVisibleFrom } = payload; + + const activities = payload.activities?.map(activity => { + if (activity.type !== ACTIVITY_TYPE_INFORMATION) { + return activity; + } + + const { performedBy, ...rest } = activity; + const { actorId, ...performedByRest } = performedBy; + + return { + ...rest, + performedBy: { + ...performedByRest, + actorName: "some name" + } + }; + }) ?? []; + + return { + ...payloadWithoutVisibleFrom, + activities + }; +} + +/** + * Creates a dialog payload for performance testing + * @param {string} endUser - Norwegian national ID number (11 digits) + * @param {string} resource - Resource identifier + * @returns {Object} Dialog payload + * @throws {Error} If inputs are invalid + */ +export default function (endUser, resource) { + if (!endUser?.match(/^\d{11}$/)) { + throw new Error('endUser must be a 11-digit number'); + } + if (!resource?.trim()) { + throw new Error('resource is required'); + } + let payload = createDialogPayload(); + payload.serviceResource = "urn:altinn:resource:" +resource; + payload.party = "urn:altinn:person:identifier-no:" + endUser; + return cleanUp(payload); +} diff --git a/tests/k6/tests/performancetest_data/endusers-yt01.csv b/tests/k6/tests/performancetest_data/endusers-yt01.csv new file mode 100644 index 000000000..c5d6abd02 --- /dev/null +++ b/tests/k6/tests/performancetest_data/endusers-yt01.csv @@ -0,0 +1,73 @@ +ssn,resource,scopes +14886498226,ttd-dialogporten-performance-test-01,digdir:dialogporten +10865299538,ttd-dialogporten-performance-test-01,digdir:dialogporten +19886497337,ttd-dialogporten-performance-test-01,digdir:dialogporten +29878198024,ttd-dialogporten-performance-test-01,digdir:dialogporten +28826498708,ttd-dialogporten-performance-test-01,digdir:dialogporten +10867196183,ttd-dialogporten-performance-test-01,digdir:dialogporten +08837297959,ttd-dialogporten-performance-test-01,digdir:dialogporten +02818596203,ttd-dialogporten-performance-test-01,digdir:dialogporten +19866498574,ttd-dialogporten-performance-test-01,digdir:dialogporten +26916397126,ttd-dialogporten-performance-test-01,digdir:dialogporten +19815997363,ttd-dialogporten-performance-test-01,digdir:dialogporten +11897397503,ttd-dialogporten-performance-test-01,digdir:dialogporten +27845299582,ttd-dialogporten-performance-test-01,digdir:dialogporten +04825997135,ttd-dialogporten-performance-test-01,digdir:dialogporten +07907197896,ttd-dialogporten-performance-test-01,digdir:dialogporten +28827097898,ttd-dialogporten-performance-test-01,digdir:dialogporten +31905999977,ttd-dialogporten-performance-test-01,digdir:dialogporten +02834699772,ttd-dialogporten-performance-test-01,digdir:dialogporten +07876497993,ttd-dialogporten-performance-test-01,digdir:dialogporten +22929874319,ttd-dialogporten-performance-test-01,digdir:dialogporten +27917298128,ttd-dialogporten-performance-test-01,digdir:dialogporten +01846698058,ttd-dialogporten-performance-test-01,digdir:dialogporten +03866096766,ttd-dialogporten-performance-test-01,digdir:dialogporten +03885996786,ttd-dialogporten-performance-test-01,digdir:dialogporten +25926298626,ttd-dialogporten-performance-test-01,digdir:dialogporten +12826398025,ttd-dialogporten-performance-test-01,digdir:dialogporten +14858095353,ttd-dialogporten-performance-test-01,digdir:dialogporten +24913649709,ttd-dialogporten-performance-test-01,digdir:dialogporten +22852749311,ttd-dialogporten-performance-test-01,digdir:dialogporten +07926198712,ttd-dialogporten-performance-test-01,digdir:dialogporten +03876498730,ttd-dialogporten-performance-test-01,digdir:dialogporten +08856299083,ttd-dialogporten-performance-test-01,digdir:dialogporten +01884099192,ttd-dialogporten-performance-test-01,digdir:dialogporten +18826599975,ttd-dialogporten-performance-test-01,digdir:dialogporten +23894899573,ttd-dialogporten-performance-test-01,digdir:dialogporten +05906599602,ttd-dialogporten-performance-test-01,digdir:dialogporten +02826198799,ttd-dialogporten-performance-test-01,digdir:dialogporten +06906497962,ttd-dialogporten-performance-test-01,digdir:dialogporten +12856395543,ttd-dialogporten-performance-test-01,digdir:dialogporten +06857897119,ttd-dialogporten-performance-test-01,digdir:dialogporten +06876599986,ttd-dialogporten-performance-test-01,digdir:dialogporten +24878297780,ttd-dialogporten-performance-test-01,digdir:dialogporten +14926297903,ttd-dialogporten-performance-test-01,digdir:dialogporten +22909398049,ttd-dialogporten-performance-test-01,digdir:dialogporten +07905398150,ttd-dialogporten-performance-test-01,digdir:dialogporten +11824596141,ttd-dialogporten-performance-test-01,digdir:dialogporten +05887496988,ttd-dialogporten-performance-test-01,digdir:dialogporten +28896796951,ttd-dialogporten-performance-test-01,digdir:dialogporten +19825998147,ttd-dialogporten-performance-test-01,digdir:dialogporten +01917196806,ttd-dialogporten-performance-test-01,digdir:dialogporten +10918397944,ttd-dialogporten-performance-test-01,digdir:dialogporten +03844797469,ttd-dialogporten-performance-test-01,digdir:dialogporten +25862849763,ttd-dialogporten-performance-test-01,digdir:dialogporten +23835399729,ttd-dialogporten-performance-test-01,digdir:dialogporten +09886998144,ttd-dialogporten-performance-test-01,digdir:dialogporten +26897799382,ttd-dialogporten-performance-test-01,digdir:dialogporten +27866897323,ttd-dialogporten-performance-test-01,digdir:dialogporten +11854397992,ttd-dialogporten-performance-test-01,digdir:dialogporten +04848197152,ttd-dialogporten-performance-test-01,digdir:dialogporten +21858699425,ttd-dialogporten-performance-test-01,digdir:dialogporten +13899798567,ttd-dialogporten-performance-test-01,digdir:dialogporten +30905596574,ttd-dialogporten-performance-test-01,digdir:dialogporten +18826698779,ttd-dialogporten-performance-test-01,digdir:dialogporten +07876099769,ttd-dialogporten-performance-test-01,digdir:dialogporten +25887899889,ttd-dialogporten-performance-test-01,digdir:dialogporten +01905999954,ttd-dialogporten-performance-test-01,digdir:dialogporten +24826296980,ttd-dialogporten-performance-test-01,digdir:dialogporten +06886696920,ttd-dialogporten-performance-test-01,digdir:dialogporten +26877199125,ttd-dialogporten-performance-test-01,digdir:dialogporten +15917599510,ttd-dialogporten-performance-test-01,digdir:dialogporten +16896795523,ttd-dialogporten-performance-test-01,digdir:dialogporten +21849198170,ttd-dialogporten-performance-test-01,digdir:dialogporten \ No newline at end of file diff --git a/tests/k6/tests/performancetest_data/graphql-search.js b/tests/k6/tests/performancetest_data/graphql-search.js new file mode 100644 index 000000000..c7e1901df --- /dev/null +++ b/tests/k6/tests/performancetest_data/graphql-search.js @@ -0,0 +1,70 @@ +export function getGraphqlParty(identifier) { + return ` + query getAllDialogsForParties { + searchDialogs(input: { party: ["urn:altinn:person:identifier-no:${identifier}"]}) { + items { + id + party + org + progress + guiAttachmentCount + status + createdAt + updatedAt + extendedStatus + seenSinceLastUpdate { + id + seenAt + seenBy { + actorType + actorId + actorName + } + isCurrentEndUser + } + latestActivity { + description { + value + languageCode + } + performedBy { + actorType + actorId + actorName + } + } + content { + title { + mediaType + value { + value + languageCode + } + } + summary { + mediaType + value { + value + languageCode + } + } + senderName { + mediaType + value { + value + languageCode + } + } + extendedStatus { + mediaType + value { + value + languageCode + } + } + } + systemLabel + } + } + }` +} \ No newline at end of file diff --git a/tests/k6/tests/performancetest_data/serviceowners-yt01.csv b/tests/k6/tests/performancetest_data/serviceowners-yt01.csv new file mode 100644 index 000000000..449a5e7c6 --- /dev/null +++ b/tests/k6/tests/performancetest_data/serviceowners-yt01.csv @@ -0,0 +1,2 @@ +org,orgno,scopes,resource +digdir,991825827,digdir:dialogporten.serviceprovider,super-simple-service diff --git a/tests/k6/tests/scenarios/performance/create-dialog-and-search.js b/tests/k6/tests/scenarios/performance/create-dialog-and-search.js new file mode 100644 index 000000000..8329e7402 --- /dev/null +++ b/tests/k6/tests/scenarios/performance/create-dialog-and-search.js @@ -0,0 +1,65 @@ +/** + * Performance test for creating dialogs and searching dialogs. + * Run: k6 run tests/k6/tests/scenarios/performance/create-dialog-and-search.js -e env=yt01 -e svus=1 -e evus=1 -e duration=1m + */ +import { randomItem } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js'; +import { enduserSearch } from '../../performancetest_common/simpleSearch.js'; +import { createDialog } from '../../performancetest_common/createDialog.js'; +import { getDefaultThresholds } from '../../performancetest_common/getDefaultThresholds.js'; +import { serviceOwners, endUsersWithTokens } from '../../performancetest_common/readTestdata.js'; + + +/** + * Options for performance scenarios. + * + * @typedef {Object} Options + * @property {Object} scenarios - The performance scenarios. + * @property {Object} summaryTrendStats - The summary trend statistics. + * @property {Object} thresholds - The thresholds for performance metrics. + */ + +/** + * Performance options. + * + * @type {Options} + */ +export const options = { + scenarios: { + create_dialogs: { + executor: 'constant-vus', + tags: { name: 'create dialog'}, + exec: 'createDialogs', + vus: __ENV.svus, + duration: __ENV.duration + }, + simple_search: { + executor: 'constant-vus', + tags: { name: 'search dialogs'}, + exec: 'enduserSearches', + vus: __ENV.evus, + duration: __ENV.duration, + } + }, + summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(95)', 'p(99)', 'p(99.5)', 'p(99.9)', 'count'], + thresholds: getDefaultThresholds(['http_req_duration', 'http_reqs'],[ + 'simple search', + 'create dialog', + 'get dialog', + 'get dialog activities', + 'get dialog activity', + 'get seenlogs', + 'get seenlog', + 'get transmissions', + 'get transmission', + 'get labellog' + ]) + +}; + +export function createDialogs() { + createDialog(randomItem(serviceOwners), randomItem(endUsersWithTokens)); +} + +export function enduserSearches() { + enduserSearch(randomItem(endUsersWithTokens)); +} \ No newline at end of file diff --git a/tests/k6/tests/scripts/generate_tokens.sh b/tests/k6/tests/scripts/generate_tokens.sh index 06df1ab24..353587043 100755 --- a/tests/k6/tests/scripts/generate_tokens.sh +++ b/tests/k6/tests/scripts/generate_tokens.sh @@ -28,7 +28,7 @@ case $API_ENVIRONMENT in env="at21" ;; "staging") env="tt02" ;; - "performance") + "yt01") env="yt01" ;; *) echo "Error: Unknown api environment $API_ENVIRONMENT" diff --git a/tests/k6/tests/serviceowner/performance/create-dialog.js b/tests/k6/tests/serviceowner/performance/create-dialog.js index 60aef04e9..df51bde99 100644 --- a/tests/k6/tests/serviceowner/performance/create-dialog.js +++ b/tests/k6/tests/serviceowner/performance/create-dialog.js @@ -1,27 +1,15 @@ -import { postSO, expect, describe } from "../../../common/testimports.js"; -import { SharedArray } from 'k6/data'; -import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js'; -import { default as dialogToInsert } from '../testdata/01-create-dialog.js'; +/** + * Performance test for creating a dialog + * Run: k6 run tests/k6/tests/serviceowner/performance/create-dialog.js --vus 1 --iterations 1 + */ import { randomItem } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js'; - -const filenameServiceowners = '../../performancetest_data/.serviceowners-with-tokens.csv'; -const filenameEndusers = `../../performancetest_data/endusers-${__ENV.API_ENVIRONMENT}.csv`; - -const serviceOwners = new SharedArray('serviceOwners', function () { - return papaparse.parse(open(filenameServiceowners), { header: true, skipEmptyLines: true }).data; -}); - -const endUsers = new SharedArray('endUsers', function () { - return papaparse.parse(open(filenameEndusers), { header: true, skipEmptyLines: true }).data; - }); +import { getDefaultThresholds } from '../../performancetest_common/getDefaultThresholds.js'; +import { createDialog } from '../../performancetest_common/createDialog.js'; +import { serviceOwners, endUsers } from '../../performancetest_common/readTestdata.js'; export let options = { summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(95)', 'p(99)', 'p(99.5)', 'p(99.9)', 'count'], - thresholds: { - 'http_req_duration{scenario:default}': [`max>=0`], - 'http_req_duration{name:create dialog}': [], - 'http_reqs{name:create dialog}': [], - }, + thresholds: getDefaultThresholds(['http_req_duration', 'http_reqs'],['create dialog']) }; export default function() { @@ -33,17 +21,3 @@ export default function() { } } -export function createDialog(serviceOwner, endUser) { - var paramsWithToken = { - headers: { - Authorization: "Bearer " + serviceOwner.token - }, - tags: { name: 'create dialog' } - } - - describe('create dialog', () => { - let r = postSO('dialogs', dialogToInsert(endUser.ssn), paramsWithToken); - expect(r.status, 'response status').to.equal(201); - }); - -} \ No newline at end of file diff --git a/tests/k6/tests/serviceowner/performance/create-remove-dialog.js b/tests/k6/tests/serviceowner/performance/create-remove-dialog.js new file mode 100644 index 000000000..544f40d20 --- /dev/null +++ b/tests/k6/tests/serviceowner/performance/create-remove-dialog.js @@ -0,0 +1,25 @@ +/** + * Performance test for creating and removing a dialog + * Run: k6 run tests/k6/tests/serviceowner/performance/create-remove-dialog.js --vus 1 --iterations 1 + */ +import { randomItem } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js'; +import { getDefaultThresholds } from '../../performancetest_common/getDefaultThresholds.js'; +import { serviceOwners, endUsers } from "../../performancetest_common/readTestdata.js"; +import { createAndRemoveDialog } from '../../performancetest_common/createDialog.js'; + +export let options = { + summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(95)', 'p(99)', 'p(99.5)', 'p(99.9)', 'count'], + thresholds: getDefaultThresholds(['http_req_duration', 'http_reqs'],[ + 'create dialog', + 'remove dialog' + ]) +}; + +export default function() { + if ((options.vus === undefined || options.vus === 1) && (options.iterations === undefined || options.iterations === 1)) { + createAndRemoveDialog(serviceOwners[0], endUsers[0]); + } + else { + createAndRemoveDialog(randomItem(serviceOwners), randomItem(endUsers)); + } + }