From 37a83d6a48ed68fb9dec17da2f721f7e9bb0b1f0 Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Thu, 31 Oct 2024 15:01:41 +0100 Subject: [PATCH 01/34] Added more tests --- .github/workflows/dispatch-k6-performance.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dispatch-k6-performance.yml b/.github/workflows/dispatch-k6-performance.yml index d06c594fa..0cbe7d230 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 @@ -40,6 +40,12 @@ on: description: 'Path to test suite to run' required: true default: 'tests/k6/tests/serviceowner/performance/create-dialog.js' + type: choice + options: + - 'tests/k6/tests/serviceowner/performance/create-dialog.js' + - 'tests/k6/tests/serviceowner/performance/create-remove-dialog.js' + - 'tests/k6/tests/enduser/performance/simple-search.js' + - 'tests/k6/tests/enduser/performance/graph-search.js' jobs: k6-performance: @@ -54,7 +60,7 @@ jobs: environment: ${{ inputs.environment }} apiVersion: ${{ inputs.apiVersion }} testSuitePath: ${{ inputs.testSuitePath }} - vus: ${{ inputs.vus }} + vus: ${{ fromJson(inputs.vus) }} duration: ${{ inputs.duration }} tokens: ${{ inputs.tokens }} From 0c2acaef77379cfa3a28a33059ed3cae8a1fc7ef Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Thu, 31 Oct 2024 15:02:25 +0100 Subject: [PATCH 02/34] add graphql endpoints --- tests/k6/common/config.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/k6/common/config.js b/tests/k6/common/config.js index 9841e1c16..14703e1c9 100644 --- a/tests/k6/common/config.js +++ b/tests/k6/common/config.js @@ -15,7 +15,15 @@ export const baseUrls = { 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/" - } + }, + graphql: { + localdev: "https://localhost:7214/graphql", + localdev_docker: "https://host.docker.internal:7214/graphql", + test: "https://altinn-dev-api.azure-api.net/dialogporten/graphql", + yt01: "https://platform.yt01.altinn.cloud/dialogporten/graphql", + staging: "https://platform.tt02.altinn.no/dialogporten/graphql", + prod: "https://platform.altinn.no/dialogporten/api/v1/graphql" + }, } }; @@ -42,4 +50,6 @@ if (!baseUrls[__ENV.API_VERSION]["serviceowner"][__ENV.API_ENVIRONMENT]) { export const baseUrlEndUser = baseUrls[__ENV.API_VERSION]["enduser"][__ENV.API_ENVIRONMENT]; export const baseUrlServiceOwner = baseUrls[__ENV.API_VERSION]["serviceowner"][__ENV.API_ENVIRONMENT]; +export const baseUrlGraphql = baseUrls[__ENV.API_VERSION]["graphql"][__ENV.API_ENVIRONMENT]; + export const sentinelValue = "dialogporten-e2e-sentinel"; From d124a540ffd8e627f431a5a6e5a8dbf5732473dc Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Thu, 31 Oct 2024 15:03:12 +0100 Subject: [PATCH 03/34] add POST request for graphql --- tests/k6/common/request.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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); +} From 3f060c82687f187807a9dc214fbb6b6548f892d1 Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Thu, 31 Oct 2024 15:03:50 +0100 Subject: [PATCH 04/34] add POST request for graphql --- tests/k6/common/testimports.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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, From da0e851557265683e53fa94d10f2f4580871b8dd Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Thu, 31 Oct 2024 15:04:51 +0100 Subject: [PATCH 05/34] add get for all enduser dialog paths --- .../enduser/performance/simple-search.js | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/tests/k6/tests/enduser/performance/simple-search.js b/tests/k6/tests/enduser/performance/simple-search.js index be99517b2..01d58ceb2 100644 --- a/tests/k6/tests/enduser/performance/simple-search.js +++ b/tests/k6/tests/enduser/performance/simple-search.js @@ -27,6 +27,22 @@ export let options = { thresholds: { 'http_req_duration{name:simple search}': [], 'http_reqs{name:simple search}': [], + 'http_req_duration{name:get dialog}': [], + 'http_reqs{name:get dialog}': [], + 'http_req_duration{name:get dialog activities}': [], + 'http_reqs{name:get dialog activities}': [], + 'http_req_duration{name:get dialog activity}': [], + 'http_reqs{name:get dialog activity}': [], + 'http_req_duration{name:get seenlogs}': [], + 'http_reqs{name:get seenlogs}': [], + 'http_req_duration{name:get seenlog}': [], + 'http_reqs{name:get seenlog}': [], + 'http_req_duration{name:get transmissions}': [], + 'http_reqs{name:get transmissions}': [], + 'http_req_duration{name:get transmission}': [], + 'http_reqs{name:get transmission}': [], + 'http_req_duration{name:get labellog}': [], + 'http_reqs{name:get labellog}': [], }, }; @@ -52,6 +68,63 @@ export function simpleSearch(enduser) { let r = getEU('dialogs' + defaultFilter, paramsWithToken); expectStatusFor(r).to.equal(200); expect(r, 'response').to.have.validJsonBody(); + if (r.json().items && r.json().items.length > 0) { + let dialogId = r.json().items[0].id; + if (dialogId) { + getDialog(dialogId, paramsWithToken); + getDialogActivities(dialogId, paramsWithToken); + getSeenLog(dialogId, paramsWithToken); + getLabelLog(dialogId, paramsWithToken); + getDialogTransmissions(dialogId, paramsWithToken); + } + } }); } +export function getDialog(dialogId, paramsWithToken) { + paramsWithToken.tags.name = 'get dialog' + getUrl('dialogs/' + dialogId, paramsWithToken); +} + +export function getDialogActivities(dialogId, paramsWithToken) { + paramsWithToken.tags.name = 'get dialog activities' + let d = getUrl('dialogs/' + dialogId + '/activities', paramsWithToken); + let dialog_activites = d.json(); + if (dialog_activites.length > 0) { + paramsWithToken.tags.name = 'get dialog activity' + getUrl('dialogs/' + dialogId + '/activities/' + randomItem(dialog_activites).id, paramsWithToken); + } +} + +export function getSeenLog(dialogId, paramsWithToken) { + paramsWithToken.tags.name = 'get seenlogs' + let s = getEU('dialogs/' + dialogId + '/seenlog', paramsWithToken); + let seen_logs = s.json(); + if (seen_logs.length > 0) { + paramsWithToken.tags.name = 'get seenlog' + getUrl('dialogs/' + dialogId + '/seenlog/' + randomItem(seen_logs).id, paramsWithToken); + } +} + +export function getLabelLog(dialogId, paramsWithToken) { + paramsWithToken.tags.name = 'get labellog' + getEU('dialogs/' + dialogId + '/labellog', paramsWithToken); +} + +export function getDialogTransmissions(dialogId, paramsWithToken) { + paramsWithToken.tags.name = 'get transmissions' + let d = getUrl('dialogs/' + dialogId + '/transmissions', paramsWithToken); + let dialog_transmissions = d.json(); + if (dialog_transmissions.length > 0) { + paramsWithToken.tags.name = 'get transmission' + getUrl('dialogs/' + dialogId + '/transmissions/' + randomItem(dialog_transmissions).id, paramsWithToken); + } +} + +export function getUrl(url, paramsWithToken) { + let r = getEU(url, paramsWithToken); + expectStatusFor(r).to.equal(200); + expect(r, 'response').to.have.validJsonBody(); + return r; +} + From 2010d94b45851de87aafd686e43183802401e5d5 Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Thu, 31 Oct 2024 15:05:35 +0100 Subject: [PATCH 06/34] change performance to yt01 --- tests/k6/tests/scripts/generate_tokens.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" From fa65063a96f8e0cde22cf94b364b2a0a6910980e Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Thu, 31 Oct 2024 15:06:31 +0100 Subject: [PATCH 07/34] Use different create dialog mal --- tests/k6/tests/serviceowner/performance/create-dialog.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/k6/tests/serviceowner/performance/create-dialog.js b/tests/k6/tests/serviceowner/performance/create-dialog.js index 60aef04e9..55f85eb73 100644 --- a/tests/k6/tests/serviceowner/performance/create-dialog.js +++ b/tests/k6/tests/serviceowner/performance/create-dialog.js @@ -1,7 +1,7 @@ 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'; +import { default as dialogToInsert } from '../../performancetest_data/01-create-dialog.js'; import { randomItem } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js'; const filenameServiceowners = '../../performancetest_data/.serviceowners-with-tokens.csv'; @@ -42,7 +42,7 @@ export function createDialog(serviceOwner, endUser) { } describe('create dialog', () => { - let r = postSO('dialogs', dialogToInsert(endUser.ssn), paramsWithToken); + let r = postSO('dialogs', dialogToInsert(endUser.ssn, endUser.resource), paramsWithToken); expect(r.status, 'response status').to.equal(201); }); From 3a94b76e2178737567fea2183b111cd63572cd1e Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Thu, 31 Oct 2024 15:07:26 +0100 Subject: [PATCH 08/34] simple search trough graphQL --- .../enduser/performance/graphql-search.js | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 tests/k6/tests/enduser/performance/graphql-search.js diff --git a/tests/k6/tests/enduser/performance/graphql-search.js b/tests/k6/tests/enduser/performance/graphql-search.js new file mode 100644 index 000000000..5a1cd6728 --- /dev/null +++ b/tests/k6/tests/enduser/performance/graphql-search.js @@ -0,0 +1,58 @@ +import { postGQ, 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'; +import { getGraphqlParty } from '../../performancetest_data/graphql-search.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:graph search}': [], + 'http_reqs{name:graph search}': [], + }, +}; + +export default function() { + if ((options.vus === undefined || options.vus === 1) && (options.iterations === undefined || options.iterations === 1)) { + graphqlSearch(endUsers[0]); + } + else { + graphqlSearch(randomItem(endUsers)); + } +} + +export function graphqlSearch(enduser) { + let paramsWithToken = { + headers: { + Authorization: "Bearer " + enduser.token, + }, + tags: { name: 'graphql search' } + } + describe('Perform graphql dialog list', () => { + //let r = ('dialogs' + defaultFilter, paramsWithToken); + let r = postGQ(getGraphqlParty(enduser.ssn), paramsWithToken); + expectStatusFor(r).to.equal(200); + expect(r, 'response').to.have.validJsonBody(); + }); +} + From 3f4753bd3769bf0cabca37b37483106708af3c94 Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Thu, 31 Oct 2024 15:08:59 +0100 Subject: [PATCH 09/34] graphql post sample --- .../performancetest_data/01-create-dialog.js | 315 ++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 tests/k6/tests/performancetest_data/01-create-dialog.js 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..51f89bea1 --- /dev/null +++ b/tests/k6/tests/performancetest_data/01-create-dialog.js @@ -0,0 +1,315 @@ +export default function (endUser, resource) { + return { + "serviceResource": "urn:altinn:resource:" +resource, // urn starting with urn:altinn:resource: + "party": "urn:altinn:person:identifier-no:" + endUser, // or urn:altinn:organization:identifier-no:<9 digits> + "status": "new", // valid values: new, inprogress, waiting, signing, cancelled, completed + "extendedStatus": "urn:any/valid/uri", + "dueAt": "2033-11-25T06:37:54.2920190Z", // must be UTC + "expiresAt": "2053-11-25T06:37:54.2920190Z", // must be UTC + "process": "urn:test:process:1", + "searchTags": [ + { "value": "something searchable" }, + { "value": "something else searchable" }, + { "value": "Created by performancetest" } // Do not remove this, this is used to look for unpurged dialogs after a run + ], + "content": { + "Title": { + "value": [{ "languageCode": "nb", "value": "Skjema for rapportering av et eller annet" }] + }, + "SenderName": { + "value": [{ "languageCode": "nb", "value": "Avsendernavn" }] + }, + "Summary": { + "value": [{ "languageCode": "nb", "value": "Et sammendrag her. Maks 200 tegn, ingen HTML-støtte. Påkrevd. Vises i liste." }] + }, + "AdditionalInfo": { + "mediaType": "text/plain", + "value": [{ "languageCode": "nb", "value": "Utvidet forklaring (enkel HTML-støtte, inntil 1023 tegn). Ikke påkrevd. Vises kun i detaljvisning." }] + }, + "ExtendedStatus": { + "value": [{ "languageCode": "nb", "value": "Utvidet Status" }] + }, + }, + "transmissions": [ + { + "type": "Information", + "authorizationAttribute": "element1", + "sender": { + "actorType": "serviceOwner", + }, + "attachments": [ + { + "displayName": [ + { + "languageCode": "nb", + "value": "Forsendelse visningsnavn" + }, + { + "languageCode": "en", + "value": "Transmission attachment display name" + } + ], + "urls": [ + { + "url": "https://digdir.apps.tt02.altinn.no/some-other-url", + "consumerType": "Gui" + } + ] + } + ], + "content": { + "title": { + "value": [ + { + "languageCode": "nb", + "value": "Forsendelsestittel" + }, + { + "languageCode": "en", + "value": "Transmission title" + } + ] + }, + "summary": { + "value": [ + { + "languageCode": "nb", + "value": "Forsendelse oppsummering" + }, + { + "languageCode": "en", + "value": "Transmission summary" + } + ] + } + } + }, + { + "type": "Information", + "sender": { + "actorType": "serviceOwner" + }, + "attachments": [ + { + "displayName": [ + { + "languageCode": "nb", + "value": "Visningsnavn for forsendelsesvedlegg " + }, + { + "languageCode": "en", + "value": "Transmission attachment display name" + } + ], + "urls": [ + { + "url": "https://digdir.apps.tt02.altinn.no/some-other-url", + "consumerType": "Gui" + } + ] + } + ], + "content": { + "title": { + "value": [ + { + "languageCode": "nb", + "value": "Forsendelsesstittel" + }, + { + "languageCode": "en", + "value": "Transmission title" + } + ] + }, + "summary": { + "value": [ + { + "languageCode": "nb", + "value": "Transmisjon oppsummering" + }, + { + "languageCode": "en", + "value": "Transmission summary" + } + ] + } + } + }, + { + "type": "Information", + "authorizationAttribute": "elementius", + "sender": { + "actorType": "serviceOwner" + }, + "attachments": [ + { + "displayName": [ + { + "languageCode": "nb", + "value": "Visningsnavn for forsendelsesvedlegg" + }, + { + "languageCode": "en", + "value": "Transmission attachment display name" + } + ], + "urls": [ + { + "url": "https://digdir.apps.tt02.altinn.no/some-other-url", + "consumerType": "Gui" + } + ] + } + ], + "content": { + "title": { + "value": [ + { + "languageCode": "nb", + "value": "Forsendelsetittel" + }, + { + "languageCode": "en", + "value": "Transmission title" + } + ] + }, + "summary": { + "value": [ + { + "languageCode": "nb", + "value": "Forsendelsesoppsummering" + }, + { + "languageCode": "en", + "value": "Transmission summary" + } + ] + } + } + } + ], + "guiActions": [ + { + "action": "read", + "url": "https://digdir.no", + "priority": "Primary", + "title": [ + { + "value": "Gå til dialog", + "languageCode": "nb" + } + ] + }, + { + "action": "read", + "url": "https://digdir.no", + "priority": "Secondary", + "httpMethod": "POST", + "title": [ + { + "value": "Utfør handling uten navigasjon", + "languageCode": "nb" + } + ], + "prompt": [{ "value": "Er du sikker?", "languageCode": "nb" }] + } + ], + "apiActions": [ + { + "action": "some_unauthorized_action", + "endPoints": [ + { + "url": "https://digdir.no", + "httpMethod": "GET" + }, + { + "url": "https://digdir.no/deprecated", + "httpMethod": "GET", + "deprecated": true + } + ] + } + ], + "attachments": [ + { + "displayName": [ + { + "languageCode": "nb", + "value": "Et vedlegg" + } + ], + "urls": [ + { + "consumerType": "gui", + "url": "https://foo.com/foo.pdf", + "mediaType": "application/pdf" + } + ] + }, + { + "displayName": [ + { + "languageCode": "nb", + "value": "Et annet vedlegg" + } + ], + "urls": [ + { + "consumerType": "gui", + "url": "https://foo.com/foo.pdf", + "mediaType": "application/pdf" + } + ] + }, + { + "displayName": [ + { + "languageCode": "nb", + "value": "Nok et vedlegg" + } + ], + "urls": [ + { + "consumerType": "gui", + "url": "https://foo.com/foo.pdf", + "mediaType": "application/pdf" + } + ] + } + ], + "activities": [ + { + "type": "DialogCreated", + "performedBy": { + "actorType": "partyRepresentative", + "actorName": "Some custom name" + } + }, + { + "type": "PaymentMade", + "performedBy": { + "actorType": "serviceOwner" + } + }, + { + "type": "Information", + "performedBy": { + "actorType": "partyRepresentative", + "actorName": "somename" + }, + "description": [ + { + "value": "Brukeren har begått skattesvindel", + "languageCode": "nb" + }, + { + "value": "Tax fraud", + "languageCode": "en" + } + ] + } + ] + }; +}; From 7a7b63d937af8443eb81cdae393b0389d599b0c6 Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Thu, 31 Oct 2024 15:09:25 +0100 Subject: [PATCH 10/34] endusers for yt01 --- .../performancetest_data/endusers-yt01.csv | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 tests/k6/tests/performancetest_data/endusers-yt01.csv 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 From 8200dc09e2cfdfe49d725b6fb140d4d9b73ce2bb Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Thu, 31 Oct 2024 15:09:50 +0100 Subject: [PATCH 11/34] graphql post sample --- .../performancetest_data/graphql-search.js | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 tests/k6/tests/performancetest_data/graphql-search.js 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 From e213d25369823fb15e79cc382fd544376382faae Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Thu, 31 Oct 2024 15:10:44 +0100 Subject: [PATCH 12/34] serviceowner for yt01 --- tests/k6/tests/performancetest_data/serviceowners-yt01.csv | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 tests/k6/tests/performancetest_data/serviceowners-yt01.csv 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 From 968eef375e184e22515fe687324767bfced1bde6 Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Thu, 31 Oct 2024 15:12:05 +0100 Subject: [PATCH 13/34] test that runs create dialog and search in parallell --- .../performance/create-dialog-and-search.js | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 tests/k6/tests/serviceowner/performance/create-dialog-and-search.js diff --git a/tests/k6/tests/serviceowner/performance/create-dialog-and-search.js b/tests/k6/tests/serviceowner/performance/create-dialog-and-search.js new file mode 100644 index 000000000..994608b1f --- /dev/null +++ b/tests/k6/tests/serviceowner/performance/create-dialog-and-search.js @@ -0,0 +1,106 @@ +import { postSO, getEU, expectStatusFor, 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 '../../performancetest_data/01-create-dialog.js'; +import { randomItem } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js'; + +const filenameServiceowners = '../../performancetest_data/.serviceowners-with-tokens.csv'; + +const serviceOwners = new SharedArray('serviceOwners', function () { + return papaparse.parse(open(filenameServiceowners), { header: true, skipEmptyLines: true }).data; +}); + +const filenameEndusers = '../../performancetest_data/.endusers-with-tokens.csv'; + +const endUsers = new SharedArray('endUsers', function () { + return papaparse.parse(open(filenameEndusers), { header: true, skipEmptyLines: true }).data; + }); + +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: 'simple search'}, + exec: 'simpleSearches', + vus: __ENV.evus, + duration: __ENV.duration, + } + + + }, + summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(95)', 'p(99)', 'p(99.5)', 'p(99.9)', 'count'], + thresholds: { + 'http_req_duration{name:create dialog}': [], + 'http_reqs{name:create dialog}': [], + 'http_req_duration{name:simple search}': [], + 'http_reqs{name:simple search}': [], + 'http_req_duration{name:get dialog}': [], + 'http_reqs{name:get dialog}': [], + }, +}; + +export function createDialogs() { + if ((options.vus === undefined || options.vus === 1) && (options.iterations === undefined || options.iterations === 1)) { + createDialog(serviceOwners[0], endUsers[0]); + } + else { + createDialog(randomItem(serviceOwners), randomItem(endUsers)); + } +} + +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, endUser.resource), paramsWithToken); + expect(r.status, 'response status').to.equal(201); + }); + +} + +export function simpleSearches() { + 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', () => { + paramsWithToken.tags.name = 'simple search' + let r = getEU('dialogs' + defaultFilter, paramsWithToken); + expectStatusFor(r).to.equal(200); + expect(r, 'response').to.have.validJsonBody(); + if ( r.json().items.length > 0 ) { + let dialogId = r.json().items[0].id; + if (dialogId) { + paramsWithToken.tags.name = 'get dialog' + let d = getEU('dialogs/' + dialogId, paramsWithToken); + expectStatusFor(d).to.equal(200); + expect(d, 'response').to.have.validJsonBody(); + } + } + }); +} \ No newline at end of file From 401127d342c5bb0e0bb80611bc558c7ea73590d4 Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Thu, 31 Oct 2024 15:12:47 +0100 Subject: [PATCH 14/34] test that first create and then delete a dialog --- .../performance/create-remove-dialog.js | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 tests/k6/tests/serviceowner/performance/create-remove-dialog.js 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..e3b2195c0 --- /dev/null +++ b/tests/k6/tests/serviceowner/performance/create-remove-dialog.js @@ -0,0 +1,62 @@ +import { postSO, purgeSO, 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 '../../performancetest_data/01-create-dialog.js'; +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; + }); + +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}': [], + 'http_req_duration{name:remove dialog}': [], + 'http_reqs{name:remove dialog}': [], + }, +}; + +export default function() { + if ((options.vus === undefined || options.vus === 1) && (options.iterations === undefined || options.iterations === 1)) { + createDialog(serviceOwners[0], endUsers[0]); + } + else { + createDialog(randomItem(serviceOwners), randomItem(endUsers)); + } + } + +export function createDialog(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); + } + }); + +} \ No newline at end of file From cdcff0e0ccc8dc0b31bb96215792a78fba275727 Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Thu, 31 Oct 2024 15:19:02 +0100 Subject: [PATCH 15/34] fixed filepath for graphql --- .github/workflows/dispatch-k6-performance.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dispatch-k6-performance.yml b/.github/workflows/dispatch-k6-performance.yml index 0cbe7d230..b7b161a76 100644 --- a/.github/workflows/dispatch-k6-performance.yml +++ b/.github/workflows/dispatch-k6-performance.yml @@ -45,7 +45,7 @@ on: - 'tests/k6/tests/serviceowner/performance/create-dialog.js' - 'tests/k6/tests/serviceowner/performance/create-remove-dialog.js' - 'tests/k6/tests/enduser/performance/simple-search.js' - - 'tests/k6/tests/enduser/performance/graph-search.js' + - 'tests/k6/tests/enduser/performance/graphql-search.js' jobs: k6-performance: From c677bd4b29a65a4092eac24b1882727261769c97 Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Fri, 1 Nov 2024 07:22:39 +0100 Subject: [PATCH 16/34] using dynamic name of workflow run --- .github/workflows/dispatch-k6-performance.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/dispatch-k6-performance.yml b/.github/workflows/dispatch-k6-performance.yml index b7b161a76..a69f5ae36 100644 --- a/.github/workflows/dispatch-k6-performance.yml +++ b/.github/workflows/dispatch-k6-performance.yml @@ -47,6 +47,7 @@ on: - 'tests/k6/tests/enduser/performance/simple-search.js' - 'tests/k6/tests/enduser/performance/graphql-search.js' +run-name: Performance test ${{ inputs.testSuitePath }} vus ${{ inputs.vus }} duration ${{ inputs.duration }} jobs: k6-performance: name: "Run K6 performance test" From 2b9264d7f31637551140e15316a869470ded6ab0 Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Fri, 1 Nov 2024 08:37:25 +0100 Subject: [PATCH 17/34] using dynamic name of workflow run --- .github/workflows/dispatch-k6-performance.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dispatch-k6-performance.yml b/.github/workflows/dispatch-k6-performance.yml index a69f5ae36..86bb92041 100644 --- a/.github/workflows/dispatch-k6-performance.yml +++ b/.github/workflows/dispatch-k6-performance.yml @@ -47,7 +47,7 @@ on: - 'tests/k6/tests/enduser/performance/simple-search.js' - 'tests/k6/tests/enduser/performance/graphql-search.js' -run-name: Performance test ${{ inputs.testSuitePath }} vus ${{ inputs.vus }} duration ${{ inputs.duration }} +run-name: ${{ inputs.testSuitePath }} vus ${{ inputs.vus }} duration ${{ inputs.duration }} jobs: k6-performance: name: "Run K6 performance test" From 155fb44bc47703cd57c3e4598d5c5ee75b4fd320 Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Fri, 1 Nov 2024 08:41:50 +0100 Subject: [PATCH 18/34] using dynamic name of workflow run --- .github/workflows/dispatch-k6-performance.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dispatch-k6-performance.yml b/.github/workflows/dispatch-k6-performance.yml index 86bb92041..184b17429 100644 --- a/.github/workflows/dispatch-k6-performance.yml +++ b/.github/workflows/dispatch-k6-performance.yml @@ -26,6 +26,11 @@ on: - enterprise - personal - none + tag: + description: 'tag the performance test' + required: true + default: 'Performance test' + type: string vus: description: 'Number of VUS' required: true @@ -47,7 +52,7 @@ on: - 'tests/k6/tests/enduser/performance/simple-search.js' - 'tests/k6/tests/enduser/performance/graphql-search.js' -run-name: ${{ inputs.testSuitePath }} vus ${{ inputs.vus }} duration ${{ inputs.duration }} +run-name: ${{ inputs.tag }} vus ${{ inputs.vus }} duration ${{ inputs.duration }} jobs: k6-performance: name: "Run K6 performance test" From 828851d9b6edf349d1d422b8bce41f237e2cb2a2 Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Fri, 1 Nov 2024 08:49:32 +0100 Subject: [PATCH 19/34] add http_req_failed rate to thresholds --- tests/k6/tests/enduser/performance/graphql-search.js | 1 + tests/k6/tests/enduser/performance/simple-search.js | 1 + tests/k6/tests/serviceowner/performance/create-remove-dialog.js | 1 + 3 files changed, 3 insertions(+) diff --git a/tests/k6/tests/enduser/performance/graphql-search.js b/tests/k6/tests/enduser/performance/graphql-search.js index 5a1cd6728..27e229cba 100644 --- a/tests/k6/tests/enduser/performance/graphql-search.js +++ b/tests/k6/tests/enduser/performance/graphql-search.js @@ -27,6 +27,7 @@ const endUsers = new SharedArray('endUsers', function () { export let options = { summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(95)', 'p(99)', 'p(99.5)', 'p(99.9)', 'count'], thresholds: { + http_req_failed: ['rate<0.01'], 'http_req_duration{name:graph search}': [], 'http_reqs{name:graph search}': [], }, diff --git a/tests/k6/tests/enduser/performance/simple-search.js b/tests/k6/tests/enduser/performance/simple-search.js index 01d58ceb2..c6baef295 100644 --- a/tests/k6/tests/enduser/performance/simple-search.js +++ b/tests/k6/tests/enduser/performance/simple-search.js @@ -25,6 +25,7 @@ const endUsers = new SharedArray('endUsers', function () { export let options = { summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(95)', 'p(99)', 'p(99.5)', 'p(99.9)', 'count'], thresholds: { + http_req_failed: ['rate<0.01'], 'http_req_duration{name:simple search}': [], 'http_reqs{name:simple search}': [], 'http_req_duration{name:get dialog}': [], diff --git a/tests/k6/tests/serviceowner/performance/create-remove-dialog.js b/tests/k6/tests/serviceowner/performance/create-remove-dialog.js index e3b2195c0..de164f73e 100644 --- a/tests/k6/tests/serviceowner/performance/create-remove-dialog.js +++ b/tests/k6/tests/serviceowner/performance/create-remove-dialog.js @@ -18,6 +18,7 @@ const endUsers = new SharedArray('endUsers', function () { export let options = { summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(95)', 'p(99)', 'p(99.5)', 'p(99.9)', 'count'], thresholds: { + http_req_failed: ['rate<0.01'], 'http_req_duration{scenario:default}': [`max>=0`], 'http_req_duration{name:create dialog}': [], 'http_reqs{name:create dialog}': [], From cb037e4d449c5b04fcd0785bda02c627189ee23d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Fri, 1 Nov 2024 14:48:29 +0100 Subject: [PATCH 20/34] chore: perf test url config refactor suggestion (#1375) --- tests/k6/common/config.js | 51 ++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/tests/k6/common/config.js b/tests/k6/common/config.js index 14703e1c9..e9a38ca28 100644 --- a/tests/k6/common/config.js +++ b/tests/k6/common/config.js @@ -1,30 +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: "https://localhost:7214/graphql", - localdev_docker: "https://host.docker.internal:7214/graphql", - test: "https://altinn-dev-api.azure-api.net/dialogporten/graphql", - yt01: "https://platform.yt01.altinn.cloud/dialogporten/graphql", - staging: "https://platform.tt02.altinn.no/dialogporten/graphql", - prod: "https://platform.altinn.no/dialogporten/api/v1/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 From 80abedfc31f408fa3484c65310d85caa70c27abd Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Fri, 1 Nov 2024 15:16:01 +0100 Subject: [PATCH 21/34] refactored to lower duplicated code rate --- .../enduser/performance/graphql-search.js | 26 +---- .../enduser/performance/simple-search.js | 106 +++++------------- .../k6/tests/performancetest_common/common.js | 31 +++++ 3 files changed, 61 insertions(+), 102 deletions(-) create mode 100644 tests/k6/tests/performancetest_common/common.js diff --git a/tests/k6/tests/enduser/performance/graphql-search.js b/tests/k6/tests/enduser/performance/graphql-search.js index 27e229cba..9a9fc5023 100644 --- a/tests/k6/tests/enduser/performance/graphql-search.js +++ b/tests/k6/tests/enduser/performance/graphql-search.js @@ -1,36 +1,16 @@ import { postGQ, 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'; import { getGraphqlParty } from '../../performancetest_data/graphql-search.js'; +import { getEndusers, getDefaultThresholds } from '../../performancetest_common/common.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}`); - } -}); +const endUsers = getEndusers(filenameEndusers); export let options = { summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(95)', 'p(99)', 'p(99.5)', 'p(99.9)', 'count'], - thresholds: { - http_req_failed: ['rate<0.01'], - 'http_req_duration{name:graph search}': [], - 'http_reqs{name:graph search}': [], - }, + thresholds: getDefaultThresholds(['http_req_duration', 'http_reqs'],['graphql search']) }; export default function() { diff --git a/tests/k6/tests/enduser/performance/simple-search.js b/tests/k6/tests/enduser/performance/simple-search.js index c6baef295..531137c6d 100644 --- a/tests/k6/tests/enduser/performance/simple-search.js +++ b/tests/k6/tests/enduser/performance/simple-search.js @@ -1,50 +1,23 @@ 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'; +import { getEndusers, getDefaultThresholds } from '../../performancetest_common/common.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}`); - } -}); +const endUsers = getEndusers(filenameEndusers); export let options = { summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(95)', 'p(99)', 'p(99.5)', 'p(99.9)', 'count'], - thresholds: { - http_req_failed: ['rate<0.01'], - 'http_req_duration{name:simple search}': [], - 'http_reqs{name:simple search}': [], - 'http_req_duration{name:get dialog}': [], - 'http_reqs{name:get dialog}': [], - 'http_req_duration{name:get dialog activities}': [], - 'http_reqs{name:get dialog activities}': [], - 'http_req_duration{name:get dialog activity}': [], - 'http_reqs{name:get dialog activity}': [], - 'http_req_duration{name:get seenlogs}': [], - 'http_reqs{name:get seenlogs}': [], - 'http_req_duration{name:get seenlog}': [], - 'http_reqs{name:get seenlog}': [], - 'http_req_duration{name:get transmissions}': [], - 'http_reqs{name:get transmissions}': [], - 'http_req_duration{name:get transmission}': [], - 'http_reqs{name:get transmission}': [], - 'http_req_duration{name:get labellog}': [], - 'http_reqs{name:get labellog}': [], - }, + thresholds: getDefaultThresholds(['http_req_duration', 'http_reqs'],['simple search', + 'get dialog', + 'get dialog activities', + 'get dialog activity', + 'get seenlogs', + 'get seenlog', + 'get transmissions', + 'get transmission', + 'get labellog' + ]) }; export default function() { @@ -72,53 +45,28 @@ export function simpleSearch(enduser) { if (r.json().items && r.json().items.length > 0) { let dialogId = r.json().items[0].id; if (dialogId) { - getDialog(dialogId, paramsWithToken); - getDialogActivities(dialogId, paramsWithToken); - getSeenLog(dialogId, paramsWithToken); - getLabelLog(dialogId, paramsWithToken); - getDialogTransmissions(dialogId, paramsWithToken); + 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/') } } }); } -export function getDialog(dialogId, paramsWithToken) { - paramsWithToken.tags.name = 'get dialog' - getUrl('dialogs/' + dialogId, paramsWithToken); -} - -export function getDialogActivities(dialogId, paramsWithToken) { - paramsWithToken.tags.name = 'get dialog activities' - let d = getUrl('dialogs/' + dialogId + '/activities', paramsWithToken); - let dialog_activites = d.json(); - if (dialog_activites.length > 0) { - paramsWithToken.tags.name = 'get dialog activity' - getUrl('dialogs/' + dialogId + '/activities/' + randomItem(dialog_activites).id, paramsWithToken); - } -} - -export function getSeenLog(dialogId, paramsWithToken) { - paramsWithToken.tags.name = 'get seenlogs' - let s = getEU('dialogs/' + dialogId + '/seenlog', paramsWithToken); - let seen_logs = s.json(); - if (seen_logs.length > 0) { - paramsWithToken.tags.name = 'get seenlog' - getUrl('dialogs/' + dialogId + '/seenlog/' + randomItem(seen_logs).id, paramsWithToken); - } -} - -export function getLabelLog(dialogId, paramsWithToken) { - paramsWithToken.tags.name = 'get labellog' - getEU('dialogs/' + dialogId + '/labellog', paramsWithToken); +export function getContent(dialogId, paramsWithToken, tag, path = '') { + paramsWithToken.tags.name = tag + getUrl('dialogs/' + dialogId + path, paramsWithToken); } -export function getDialogTransmissions(dialogId, paramsWithToken) { - paramsWithToken.tags.name = 'get transmissions' - let d = getUrl('dialogs/' + dialogId + '/transmissions', paramsWithToken); - let dialog_transmissions = d.json(); - if (dialog_transmissions.length > 0) { - paramsWithToken.tags.name = 'get transmission' - getUrl('dialogs/' + dialogId + '/transmissions/' + randomItem(dialog_transmissions).id, paramsWithToken); +export function getContentChain(dialogId, paramsWithToken, tag, subtag, endpoint) { + paramsWithToken.tags.name = tag; + let d = getUrl('dialogs/' + dialogId + endpoint, paramsWithToken); + let json = d.json(); + if (json.length > 0) { + paramsWithToken.tags.name = subtag; + getUrl('dialogs/' + dialogId + endpoint + randomItem(json).id, paramsWithToken); } } diff --git a/tests/k6/tests/performancetest_common/common.js b/tests/k6/tests/performancetest_common/common.js new file mode 100644 index 000000000..17a03c647 --- /dev/null +++ b/tests/k6/tests/performancetest_common/common.js @@ -0,0 +1,31 @@ +import { SharedArray } from 'k6/data'; +import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js'; + +export function getEndusers(filenameEndusers) { + + 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}`); + } + }); + return endUsers; +} + +export function getDefaultThresholds(counters, labels) { + let thresholds = { + http_req_failed: ['rate<0.01'] + } + counters.flatMap(t => labels.map(l => thresholds[`${t}{name:${l}}`] = [])); + return thresholds; +} From 28679dc131516a88ea6c4551b7fb7c4bfcb561dc Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Mon, 4 Nov 2024 08:11:52 +0100 Subject: [PATCH 22/34] implements coderabbitai suggestions --- .../enduser/performance/graphql-search.js | 5 +- .../enduser/performance/simple-search.js | 46 ++++++++++++------- .../k6/tests/performancetest_common/common.js | 16 ++++++- 3 files changed, 49 insertions(+), 18 deletions(-) diff --git a/tests/k6/tests/enduser/performance/graphql-search.js b/tests/k6/tests/enduser/performance/graphql-search.js index 9a9fc5023..88f819d61 100644 --- a/tests/k6/tests/enduser/performance/graphql-search.js +++ b/tests/k6/tests/enduser/performance/graphql-search.js @@ -4,7 +4,7 @@ import { getGraphqlParty } from '../../performancetest_data/graphql-search.js'; import { getEndusers, getDefaultThresholds } from '../../performancetest_common/common.js' -const filenameEndusers = '../../performancetest_data/.endusers-with-tokens.csv'; +const filenameEndusers = __ENV.ENDUSERS_CSV_PATH || '../../performancetest_data/.endusers-with-tokens.csv'; const endUsers = getEndusers(filenameEndusers); @@ -14,6 +14,9 @@ export let options = { }; 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]); } diff --git a/tests/k6/tests/enduser/performance/simple-search.js b/tests/k6/tests/enduser/performance/simple-search.js index 531137c6d..87fe46827 100644 --- a/tests/k6/tests/enduser/performance/simple-search.js +++ b/tests/k6/tests/enduser/performance/simple-search.js @@ -29,6 +29,20 @@ export default function() { } } +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/') +} + export function simpleSearch(enduser) { let paramsWithToken = { headers: { @@ -42,31 +56,31 @@ export function simpleSearch(enduser) { let r = getEU('dialogs' + defaultFilter, paramsWithToken); expectStatusFor(r).to.equal(200); expect(r, 'response').to.have.validJsonBody(); - if (r.json().items && r.json().items.length > 0) { - let dialogId = r.json().items[0].id; - if (dialogId) { - 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/') - } - } + retrieveDialogContent(r, paramsWithToken); }); } export function getContent(dialogId, paramsWithToken, tag, path = '') { - paramsWithToken.tags.name = tag - getUrl('dialogs/' + dialogId + path, paramsWithToken); + const listParams = { + ...paramsWithToken, + tags: { ...paramsWithToken.tags, name: tag } + }; + getUrl('dialogs/' + dialogId + path, listParams); } export function getContentChain(dialogId, paramsWithToken, tag, subtag, endpoint) { - paramsWithToken.tags.name = tag; - let d = getUrl('dialogs/' + dialogId + endpoint, paramsWithToken); + const listParams = { + ...paramsWithToken, + tags: { ...paramsWithToken.tags, name: tag } + }; + let d = getUrl('dialogs/' + dialogId + endpoint, listParams); let json = d.json(); if (json.length > 0) { - paramsWithToken.tags.name = subtag; - getUrl('dialogs/' + dialogId + endpoint + randomItem(json).id, paramsWithToken); + const detailParams = { + ...paramsWithToken, + tags: { ...paramsWithToken.tags, name: subtag } + }; + getUrl('dialogs/' + dialogId + endpoint + randomItem(json).id, detailParams); } } diff --git a/tests/k6/tests/performancetest_common/common.js b/tests/k6/tests/performancetest_common/common.js index 17a03c647..c5ed5abf4 100644 --- a/tests/k6/tests/performancetest_common/common.js +++ b/tests/k6/tests/performancetest_common/common.js @@ -22,10 +22,24 @@ export function getEndusers(filenameEndusers) { return endUsers; } +/** + * 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'] } - counters.flatMap(t => labels.map(l => thresholds[`${t}{name:${l}}`] = [])); + for (const counter of counters) { + for (const label of labels) { + thresholds[`${counter}{name:${label}}`] = []; + } + } return thresholds; } From 7e02e63b86ac97efeb46fd17d3e1dfec95e4d1c1 Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Mon, 4 Nov 2024 09:46:42 +0100 Subject: [PATCH 23/34] gets duplicated code rate down --- .../performancetest_data/01-create-dialog.js | 332 +----------------- 1 file changed, 18 insertions(+), 314 deletions(-) diff --git a/tests/k6/tests/performancetest_data/01-create-dialog.js b/tests/k6/tests/performancetest_data/01-create-dialog.js index 51f89bea1..09560531a 100644 --- a/tests/k6/tests/performancetest_data/01-create-dialog.js +++ b/tests/k6/tests/performancetest_data/01-create-dialog.js @@ -1,315 +1,19 @@ +import {default as createDialogPayload} from "../serviceowner/testdata/01-create-dialog.js" + +function cleanUp(payload) { + delete payload.visibleFrom; + var activitiesInfo = payload.activities.find(obj => { + return obj.type === 'Information' + }); + if (activitiesInfo) { + delete activitiesInfo.performedBy.actorId; + activitiesInfo.performedBy['actorName'] = "some name"; + } + return payload; +} export default function (endUser, resource) { - return { - "serviceResource": "urn:altinn:resource:" +resource, // urn starting with urn:altinn:resource: - "party": "urn:altinn:person:identifier-no:" + endUser, // or urn:altinn:organization:identifier-no:<9 digits> - "status": "new", // valid values: new, inprogress, waiting, signing, cancelled, completed - "extendedStatus": "urn:any/valid/uri", - "dueAt": "2033-11-25T06:37:54.2920190Z", // must be UTC - "expiresAt": "2053-11-25T06:37:54.2920190Z", // must be UTC - "process": "urn:test:process:1", - "searchTags": [ - { "value": "something searchable" }, - { "value": "something else searchable" }, - { "value": "Created by performancetest" } // Do not remove this, this is used to look for unpurged dialogs after a run - ], - "content": { - "Title": { - "value": [{ "languageCode": "nb", "value": "Skjema for rapportering av et eller annet" }] - }, - "SenderName": { - "value": [{ "languageCode": "nb", "value": "Avsendernavn" }] - }, - "Summary": { - "value": [{ "languageCode": "nb", "value": "Et sammendrag her. Maks 200 tegn, ingen HTML-støtte. Påkrevd. Vises i liste." }] - }, - "AdditionalInfo": { - "mediaType": "text/plain", - "value": [{ "languageCode": "nb", "value": "Utvidet forklaring (enkel HTML-støtte, inntil 1023 tegn). Ikke påkrevd. Vises kun i detaljvisning." }] - }, - "ExtendedStatus": { - "value": [{ "languageCode": "nb", "value": "Utvidet Status" }] - }, - }, - "transmissions": [ - { - "type": "Information", - "authorizationAttribute": "element1", - "sender": { - "actorType": "serviceOwner", - }, - "attachments": [ - { - "displayName": [ - { - "languageCode": "nb", - "value": "Forsendelse visningsnavn" - }, - { - "languageCode": "en", - "value": "Transmission attachment display name" - } - ], - "urls": [ - { - "url": "https://digdir.apps.tt02.altinn.no/some-other-url", - "consumerType": "Gui" - } - ] - } - ], - "content": { - "title": { - "value": [ - { - "languageCode": "nb", - "value": "Forsendelsestittel" - }, - { - "languageCode": "en", - "value": "Transmission title" - } - ] - }, - "summary": { - "value": [ - { - "languageCode": "nb", - "value": "Forsendelse oppsummering" - }, - { - "languageCode": "en", - "value": "Transmission summary" - } - ] - } - } - }, - { - "type": "Information", - "sender": { - "actorType": "serviceOwner" - }, - "attachments": [ - { - "displayName": [ - { - "languageCode": "nb", - "value": "Visningsnavn for forsendelsesvedlegg " - }, - { - "languageCode": "en", - "value": "Transmission attachment display name" - } - ], - "urls": [ - { - "url": "https://digdir.apps.tt02.altinn.no/some-other-url", - "consumerType": "Gui" - } - ] - } - ], - "content": { - "title": { - "value": [ - { - "languageCode": "nb", - "value": "Forsendelsesstittel" - }, - { - "languageCode": "en", - "value": "Transmission title" - } - ] - }, - "summary": { - "value": [ - { - "languageCode": "nb", - "value": "Transmisjon oppsummering" - }, - { - "languageCode": "en", - "value": "Transmission summary" - } - ] - } - } - }, - { - "type": "Information", - "authorizationAttribute": "elementius", - "sender": { - "actorType": "serviceOwner" - }, - "attachments": [ - { - "displayName": [ - { - "languageCode": "nb", - "value": "Visningsnavn for forsendelsesvedlegg" - }, - { - "languageCode": "en", - "value": "Transmission attachment display name" - } - ], - "urls": [ - { - "url": "https://digdir.apps.tt02.altinn.no/some-other-url", - "consumerType": "Gui" - } - ] - } - ], - "content": { - "title": { - "value": [ - { - "languageCode": "nb", - "value": "Forsendelsetittel" - }, - { - "languageCode": "en", - "value": "Transmission title" - } - ] - }, - "summary": { - "value": [ - { - "languageCode": "nb", - "value": "Forsendelsesoppsummering" - }, - { - "languageCode": "en", - "value": "Transmission summary" - } - ] - } - } - } - ], - "guiActions": [ - { - "action": "read", - "url": "https://digdir.no", - "priority": "Primary", - "title": [ - { - "value": "Gå til dialog", - "languageCode": "nb" - } - ] - }, - { - "action": "read", - "url": "https://digdir.no", - "priority": "Secondary", - "httpMethod": "POST", - "title": [ - { - "value": "Utfør handling uten navigasjon", - "languageCode": "nb" - } - ], - "prompt": [{ "value": "Er du sikker?", "languageCode": "nb" }] - } - ], - "apiActions": [ - { - "action": "some_unauthorized_action", - "endPoints": [ - { - "url": "https://digdir.no", - "httpMethod": "GET" - }, - { - "url": "https://digdir.no/deprecated", - "httpMethod": "GET", - "deprecated": true - } - ] - } - ], - "attachments": [ - { - "displayName": [ - { - "languageCode": "nb", - "value": "Et vedlegg" - } - ], - "urls": [ - { - "consumerType": "gui", - "url": "https://foo.com/foo.pdf", - "mediaType": "application/pdf" - } - ] - }, - { - "displayName": [ - { - "languageCode": "nb", - "value": "Et annet vedlegg" - } - ], - "urls": [ - { - "consumerType": "gui", - "url": "https://foo.com/foo.pdf", - "mediaType": "application/pdf" - } - ] - }, - { - "displayName": [ - { - "languageCode": "nb", - "value": "Nok et vedlegg" - } - ], - "urls": [ - { - "consumerType": "gui", - "url": "https://foo.com/foo.pdf", - "mediaType": "application/pdf" - } - ] - } - ], - "activities": [ - { - "type": "DialogCreated", - "performedBy": { - "actorType": "partyRepresentative", - "actorName": "Some custom name" - } - }, - { - "type": "PaymentMade", - "performedBy": { - "actorType": "serviceOwner" - } - }, - { - "type": "Information", - "performedBy": { - "actorType": "partyRepresentative", - "actorName": "somename" - }, - "description": [ - { - "value": "Brukeren har begått skattesvindel", - "languageCode": "nb" - }, - { - "value": "Tax fraud", - "languageCode": "en" - } - ] - } - ] - }; -}; + let payload = createDialogPayload(); + payload.serviceResource = "urn:altinn:resource:" +resource; + payload.party = "urn:altinn:person:identifier-no:" + endUser; + return cleanUp(payload); +} From e7cf0da80d12efa1fa13db6c8c0ce4dff555783c Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Mon, 4 Nov 2024 10:05:57 +0100 Subject: [PATCH 24/34] gets duplicated code rate down --- .../performance/create-dialog-and-search.js | 18 +++++++++--------- .../serviceowner/performance/create-dialog.js | 11 +++++------ .../performance/create-remove-dialog.js | 16 +++++++--------- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/tests/k6/tests/serviceowner/performance/create-dialog-and-search.js b/tests/k6/tests/serviceowner/performance/create-dialog-and-search.js index 994608b1f..94ef097fa 100644 --- a/tests/k6/tests/serviceowner/performance/create-dialog-and-search.js +++ b/tests/k6/tests/serviceowner/performance/create-dialog-and-search.js @@ -1,9 +1,11 @@ import { postSO, getEU, expectStatusFor, 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 '../../performancetest_data/01-create-dialog.js'; import { randomItem } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js'; +import { getDefaultThresholds } from '../../performancetest_common/common.js' +import { default as dialogToInsert } from '../../performancetest_data/01-create-dialog.js'; + const filenameServiceowners = '../../performancetest_data/.serviceowners-with-tokens.csv'; const serviceOwners = new SharedArray('serviceOwners', function () { @@ -36,14 +38,12 @@ export const options = { }, summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(95)', 'p(99)', 'p(99.5)', 'p(99.9)', 'count'], - thresholds: { - 'http_req_duration{name:create dialog}': [], - 'http_reqs{name:create dialog}': [], - 'http_req_duration{name:simple search}': [], - 'http_reqs{name:simple search}': [], - 'http_req_duration{name:get dialog}': [], - 'http_reqs{name:get dialog}': [], - }, + thresholds: getDefaultThresholds(['http_req_duration', 'http_reqs'],[ + 'create dialog', + 'simple search', + 'get dialog' + ]) + }; export function createDialogs() { diff --git a/tests/k6/tests/serviceowner/performance/create-dialog.js b/tests/k6/tests/serviceowner/performance/create-dialog.js index 55f85eb73..d8e4bed72 100644 --- a/tests/k6/tests/serviceowner/performance/create-dialog.js +++ b/tests/k6/tests/serviceowner/performance/create-dialog.js @@ -1,9 +1,12 @@ 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 '../../performancetest_data/01-create-dialog.js'; import { randomItem } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js'; +import { getDefaultThresholds } from '../../performancetest_common/common.js' +import { default as dialogToInsert } from '../../performancetest_data/01-create-dialog.js'; + + const filenameServiceowners = '../../performancetest_data/.serviceowners-with-tokens.csv'; const filenameEndusers = `../../performancetest_data/endusers-${__ENV.API_ENVIRONMENT}.csv`; @@ -17,11 +20,7 @@ const endUsers = new SharedArray('endUsers', function () { 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() { diff --git a/tests/k6/tests/serviceowner/performance/create-remove-dialog.js b/tests/k6/tests/serviceowner/performance/create-remove-dialog.js index de164f73e..0be2fbb50 100644 --- a/tests/k6/tests/serviceowner/performance/create-remove-dialog.js +++ b/tests/k6/tests/serviceowner/performance/create-remove-dialog.js @@ -1,9 +1,11 @@ import { postSO, purgeSO, 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 '../../performancetest_data/01-create-dialog.js'; import { randomItem } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js'; +import { getDefaultThresholds } from '../../performancetest_common/common.js' +import { default as dialogToInsert } from '../../performancetest_data/01-create-dialog.js'; + const filenameServiceowners = '../../performancetest_data/.serviceowners-with-tokens.csv'; const filenameEndusers = `../../performancetest_data/endusers-${__ENV.API_ENVIRONMENT}.csv`; @@ -17,14 +19,10 @@ const endUsers = new SharedArray('endUsers', function () { export let options = { summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(95)', 'p(99)', 'p(99.5)', 'p(99.9)', 'count'], - thresholds: { - http_req_failed: ['rate<0.01'], - 'http_req_duration{scenario:default}': [`max>=0`], - 'http_req_duration{name:create dialog}': [], - 'http_reqs{name:create dialog}': [], - 'http_req_duration{name:remove dialog}': [], - 'http_reqs{name:remove dialog}': [], - }, + thresholds: getDefaultThresholds(['http_req_duration', 'http_reqs'],[ + 'create dialog', + 'remove dialog' + ]) }; export default function() { From 74d678c2b34016a420d8f00194365857c1933cab Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Mon, 4 Nov 2024 11:41:59 +0100 Subject: [PATCH 25/34] gets duplicated code rate down --- .../performance/create-dialog-and-search.js | 87 ++++--------------- .../serviceowner/performance/create-dialog.js | 1 - .../performance/create-remove-dialog.js | 6 +- 3 files changed, 20 insertions(+), 74 deletions(-) diff --git a/tests/k6/tests/serviceowner/performance/create-dialog-and-search.js b/tests/k6/tests/serviceowner/performance/create-dialog-and-search.js index 94ef097fa..090dbd6c7 100644 --- a/tests/k6/tests/serviceowner/performance/create-dialog-and-search.js +++ b/tests/k6/tests/serviceowner/performance/create-dialog-and-search.js @@ -1,23 +1,27 @@ -import { postSO, getEU, expectStatusFor, 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 { randomItem } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js'; -import { getDefaultThresholds } from '../../performancetest_common/common.js' -import { default as dialogToInsert } from '../../performancetest_data/01-create-dialog.js'; - +import { createDialog, options as dialogOptions } from './create-dialog.js'; +import { simpleSearch, options as searchOptions } from '../../enduser/performance/simple-search.js'; + const filenameServiceowners = '../../performancetest_data/.serviceowners-with-tokens.csv'; - const serviceOwners = new SharedArray('serviceOwners', function () { return papaparse.parse(open(filenameServiceowners), { header: true, skipEmptyLines: true }).data; }); -const filenameEndusers = '../../performancetest_data/.endusers-with-tokens.csv'; - -const endUsers = new SharedArray('endUsers', function () { - return papaparse.parse(open(filenameEndusers), { header: true, skipEmptyLines: true }).data; +const filenameEndusersWithTokens = '../../performancetest_data/.endusers-with-tokens.csv'; +const endUsersWithTokens = new SharedArray('endUsersWithTokens', function () { + return papaparse.parse(open(filenameEndusersWithTokens), { header: true, skipEmptyLines: true }).data; }); +function joinThresholds(t1, t2) { + for (var k in t2) { + t1[k] = t2[k]; + } + return t1; +} + export const options = { scenarios: { create_dialogs: { @@ -34,73 +38,16 @@ export const options = { 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'],[ - 'create dialog', - 'simple search', - 'get dialog' - ]) - + thresholds: joinThresholds(searchOptions.thresholds, dialogOptions.thresholds) + }; export function createDialogs() { - if ((options.vus === undefined || options.vus === 1) && (options.iterations === undefined || options.iterations === 1)) { - createDialog(serviceOwners[0], endUsers[0]); - } - else { - createDialog(randomItem(serviceOwners), randomItem(endUsers)); - } -} - -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, endUser.resource), paramsWithToken); - expect(r.status, 'response status').to.equal(201); - }); - + createDialog(randomItem(serviceOwners), randomItem(endUsersWithTokens)); } export function simpleSearches() { - 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', () => { - paramsWithToken.tags.name = 'simple search' - let r = getEU('dialogs' + defaultFilter, paramsWithToken); - expectStatusFor(r).to.equal(200); - expect(r, 'response').to.have.validJsonBody(); - if ( r.json().items.length > 0 ) { - let dialogId = r.json().items[0].id; - if (dialogId) { - paramsWithToken.tags.name = 'get dialog' - let d = getEU('dialogs/' + dialogId, paramsWithToken); - expectStatusFor(d).to.equal(200); - expect(d, 'response').to.have.validJsonBody(); - } - } - }); + simpleSearch(randomItem(endUsersWithTokens)); } \ No newline at end of file diff --git a/tests/k6/tests/serviceowner/performance/create-dialog.js b/tests/k6/tests/serviceowner/performance/create-dialog.js index d8e4bed72..7b2d48030 100644 --- a/tests/k6/tests/serviceowner/performance/create-dialog.js +++ b/tests/k6/tests/serviceowner/performance/create-dialog.js @@ -6,7 +6,6 @@ import { randomItem } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js'; import { getDefaultThresholds } from '../../performancetest_common/common.js' import { default as dialogToInsert } from '../../performancetest_data/01-create-dialog.js'; - const filenameServiceowners = '../../performancetest_data/.serviceowners-with-tokens.csv'; const filenameEndusers = `../../performancetest_data/endusers-${__ENV.API_ENVIRONMENT}.csv`; diff --git a/tests/k6/tests/serviceowner/performance/create-remove-dialog.js b/tests/k6/tests/serviceowner/performance/create-remove-dialog.js index 0be2fbb50..da6c15e4c 100644 --- a/tests/k6/tests/serviceowner/performance/create-remove-dialog.js +++ b/tests/k6/tests/serviceowner/performance/create-remove-dialog.js @@ -27,14 +27,14 @@ export let options = { export default function() { if ((options.vus === undefined || options.vus === 1) && (options.iterations === undefined || options.iterations === 1)) { - createDialog(serviceOwners[0], endUsers[0]); + createAndRemoveDialog(serviceOwners[0], endUsers[0]); } else { - createDialog(randomItem(serviceOwners), randomItem(endUsers)); + createAndRemoveDialog(randomItem(serviceOwners), randomItem(endUsers)); } } -export function createDialog(serviceOwner, endUser) { +export function createAndRemoveDialog(serviceOwner, endUser) { var paramsWithToken = { headers: { Authorization: "Bearer " + serviceOwner.token From 88b4b2bb1e745d9a4576ebabe68b7c3685f1a294 Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Mon, 4 Nov 2024 13:48:37 +0100 Subject: [PATCH 26/34] gets duplicated code rate down --- .../tests/serviceowner/performance/create-dialog.js | 4 ++-- .../performance/create-remove-dialog.js | 13 +------------ 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/tests/k6/tests/serviceowner/performance/create-dialog.js b/tests/k6/tests/serviceowner/performance/create-dialog.js index 7b2d48030..4271f6e34 100644 --- a/tests/k6/tests/serviceowner/performance/create-dialog.js +++ b/tests/k6/tests/serviceowner/performance/create-dialog.js @@ -9,11 +9,11 @@ import { default as dialogToInsert } from '../../performancetest_data/01-create- const filenameServiceowners = '../../performancetest_data/.serviceowners-with-tokens.csv'; const filenameEndusers = `../../performancetest_data/endusers-${__ENV.API_ENVIRONMENT}.csv`; -const serviceOwners = new SharedArray('serviceOwners', function () { +export const serviceOwners = new SharedArray('serviceOwners', function () { return papaparse.parse(open(filenameServiceowners), { header: true, skipEmptyLines: true }).data; }); -const endUsers = new SharedArray('endUsers', function () { +export const endUsers = new SharedArray('endUsers', function () { return papaparse.parse(open(filenameEndusers), { header: true, skipEmptyLines: true }).data; }); diff --git a/tests/k6/tests/serviceowner/performance/create-remove-dialog.js b/tests/k6/tests/serviceowner/performance/create-remove-dialog.js index da6c15e4c..7a91ec63b 100644 --- a/tests/k6/tests/serviceowner/performance/create-remove-dialog.js +++ b/tests/k6/tests/serviceowner/performance/create-remove-dialog.js @@ -5,18 +5,7 @@ import { randomItem } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js'; import { getDefaultThresholds } from '../../performancetest_common/common.js' import { default as dialogToInsert } from '../../performancetest_data/01-create-dialog.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 { serviceOwners, endUsers } from "./create-dialog.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'],[ From 65463959cd342d07bd4ba0df2b298987334d6dc8 Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Mon, 4 Nov 2024 14:22:20 +0100 Subject: [PATCH 27/34] implements suggestions from coderabbitai --- .../performancetest_data/01-create-dialog.js | 53 +++++++++++++++---- .../serviceowner/performance/create-dialog.js | 3 ++ 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/tests/k6/tests/performancetest_data/01-create-dialog.js b/tests/k6/tests/performancetest_data/01-create-dialog.js index 09560531a..a5e994cb8 100644 --- a/tests/k6/tests/performancetest_data/01-create-dialog.js +++ b/tests/k6/tests/performancetest_data/01-create-dialog.js @@ -1,17 +1,52 @@ import {default as createDialogPayload} from "../serviceowner/testdata/01-create-dialog.js" -function cleanUp(payload) { - delete payload.visibleFrom; - var activitiesInfo = payload.activities.find(obj => { - return obj.type === 'Information' - }); - if (activitiesInfo) { - delete activitiesInfo.performedBy.actorId; - activitiesInfo.performedBy['actorName'] = "some name"; +const ACTIVITY_TYPE_INFORMATION = 'Information'; + +function cleanUp(originalPayload) { + if (!originalPayload || typeof originalPayload !== 'object') { + throw new Error('Invalid payload'); } - return 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; diff --git a/tests/k6/tests/serviceowner/performance/create-dialog.js b/tests/k6/tests/serviceowner/performance/create-dialog.js index 4271f6e34..3c3a35bda 100644 --- a/tests/k6/tests/serviceowner/performance/create-dialog.js +++ b/tests/k6/tests/serviceowner/performance/create-dialog.js @@ -7,6 +7,9 @@ import { getDefaultThresholds } from '../../performancetest_common/common.js' import { default as dialogToInsert } from '../../performancetest_data/01-create-dialog.js'; 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`; export const serviceOwners = new SharedArray('serviceOwners', function () { From cc01bc55e057593a3c4cc1165404dd84c314a015 Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Mon, 4 Nov 2024 14:50:38 +0100 Subject: [PATCH 28/34] implements suggestions from coderabbitai --- tests/k6/tests/performancetest_common/common.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/k6/tests/performancetest_common/common.js b/tests/k6/tests/performancetest_common/common.js index c5ed5abf4..efe1cc5e5 100644 --- a/tests/k6/tests/performancetest_common/common.js +++ b/tests/k6/tests/performancetest_common/common.js @@ -2,21 +2,27 @@ import { SharedArray } from 'k6/data'; import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js'; export function getEndusers(filenameEndusers) { + if (!filenameEndusers || typeof filenameEndusers !== 'string') { + throw new Error('filenameEndusers must be a non-empty string'); + } 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'); + throw new Error(`No data found in CSV file: ${filenameEndusers}`); } csvData.forEach((user, index) => { if (!user.token || !user.ssn) { - throw new Error(`Missing required fields at row ${index + 1}`); + throw new Error(`Missing required fields (token or ssn) at row ${index + 1} in ${filenameEndusers}`); } }); return csvData; } catch (error) { - throw new Error(`Failed to load end users: ${error.message}`); + if (error.code === 'ENOENT') { + throw new Error(`CSV file not found: ${filenameEndusers}`); + } + throw new Error(`Failed to load end users from ${filenameEndusers}: ${error.message}`); } }); return endUsers; From d92b9ff64d691f42d44deb26f6769dc7c61e1f14 Mon Sep 17 00:00:00 2001 From: Are Almaas Date: Tue, 5 Nov 2024 10:27:33 +0100 Subject: [PATCH 29/34] ci: ensure we use secrets from github secrets from env --- .github/workflows/workflow-run-k6-performance.yml | 1 + 1 file changed, 1 insertion(+) 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 From cbadc0bc0351316e5e1ddecd37f67c7a3d03b353 Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Tue, 5 Nov 2024 10:47:49 +0100 Subject: [PATCH 30/34] Add traceparent header for otel --- tests/k6/tests/serviceowner/performance/create-dialog.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/k6/tests/serviceowner/performance/create-dialog.js b/tests/k6/tests/serviceowner/performance/create-dialog.js index 3c3a35bda..a83dfb2a5 100644 --- a/tests/k6/tests/serviceowner/performance/create-dialog.js +++ b/tests/k6/tests/serviceowner/performance/create-dialog.js @@ -2,6 +2,7 @@ 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 { randomItem } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js'; +import { uuidv4 } from 'https://jslib.k6.io/k6-utils/1.4.0/index.js'; import { getDefaultThresholds } from '../../performancetest_common/common.js' import { default as dialogToInsert } from '../../performancetest_data/01-create-dialog.js'; @@ -37,7 +38,8 @@ export default function() { export function createDialog(serviceOwner, endUser) { var paramsWithToken = { headers: { - Authorization: "Bearer " + serviceOwner.token + Authorization: "Bearer " + serviceOwner.token, + traceparent: uuidv4(), }, tags: { name: 'create dialog' } } From bd0be61dc0191fbb183c390ee92eba050b58398b Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Tue, 5 Nov 2024 12:54:57 +0100 Subject: [PATCH 31/34] refactoring; mainly moving tests and methods to new directories and files --- .github/workflows/dispatch-k6-performance.yml | 6 +- .../enduser/performance/enduser-search.js | 27 ++++ .../enduser/performance/graphql-search.js | 42 ------ .../enduser/performance/simple-search.js | 93 ------------- .../graphql/performance/graphql-search.js | 23 ++++ .../k6/tests/performancetest_common/common.js | 51 ------- .../performancetest_common/createDialog.js | 53 ++++++++ .../getDefaultThresholds.js | 23 ++++ .../performancetest_common/readTestdata.js | 21 +++ .../performancetest_common/simpleSearch.js | 125 ++++++++++++++++++ .../performance/create-dialog-and-search.js | 53 -------- .../serviceowner/performance/create-dialog.js | 37 +----- .../performance/create-remove-dialog.js | 35 +---- 13 files changed, 281 insertions(+), 308 deletions(-) create mode 100644 tests/k6/tests/enduser/performance/enduser-search.js delete mode 100644 tests/k6/tests/enduser/performance/graphql-search.js delete mode 100644 tests/k6/tests/enduser/performance/simple-search.js create mode 100644 tests/k6/tests/graphql/performance/graphql-search.js delete mode 100644 tests/k6/tests/performancetest_common/common.js create mode 100644 tests/k6/tests/performancetest_common/createDialog.js create mode 100644 tests/k6/tests/performancetest_common/getDefaultThresholds.js create mode 100644 tests/k6/tests/performancetest_common/readTestdata.js create mode 100644 tests/k6/tests/performancetest_common/simpleSearch.js delete mode 100644 tests/k6/tests/serviceowner/performance/create-dialog-and-search.js diff --git a/.github/workflows/dispatch-k6-performance.yml b/.github/workflows/dispatch-k6-performance.yml index c9ebd12d2..478b0c853 100644 --- a/.github/workflows/dispatch-k6-performance.yml +++ b/.github/workflows/dispatch-k6-performance.yml @@ -34,7 +34,7 @@ on: vus: description: 'Number of VUS' required: true - default: 10 + default: 1 type: number duration: description: 'Duration of test, ie 30s, 1m, 10m' @@ -49,8 +49,8 @@ on: options: - 'tests/k6/tests/serviceowner/performance/create-dialog.js' - 'tests/k6/tests/serviceowner/performance/create-remove-dialog.js' - - 'tests/k6/tests/enduser/performance/simple-search.js' - - 'tests/k6/tests/enduser/performance/graphql-search.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: 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/graphql-search.js b/tests/k6/tests/enduser/performance/graphql-search.js deleted file mode 100644 index 88f819d61..000000000 --- a/tests/k6/tests/enduser/performance/graphql-search.js +++ /dev/null @@ -1,42 +0,0 @@ -import { postGQ, expect, expectStatusFor, describe } from "../../../common/testimports.js"; -import { randomItem } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js'; -import { getGraphqlParty } from '../../performancetest_data/graphql-search.js'; -import { getEndusers, getDefaultThresholds } from '../../performancetest_common/common.js' - - -const filenameEndusers = __ENV.ENDUSERS_CSV_PATH || '../../performancetest_data/.endusers-with-tokens.csv'; - -const endUsers = getEndusers(filenameEndusers); - -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']) -}; - -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)); - } -} - -export function graphqlSearch(enduser) { - let paramsWithToken = { - headers: { - Authorization: "Bearer " + enduser.token, - }, - tags: { name: 'graphql search' } - } - describe('Perform graphql dialog list', () => { - //let r = ('dialogs' + defaultFilter, paramsWithToken); - let r = postGQ(getGraphqlParty(enduser.ssn), paramsWithToken); - expectStatusFor(r).to.equal(200); - expect(r, 'response').to.have.validJsonBody(); - }); -} - 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 87fe46827..000000000 --- a/tests/k6/tests/enduser/performance/simple-search.js +++ /dev/null @@ -1,93 +0,0 @@ -import { getEU, expect, expectStatusFor, describe } from "../../../common/testimports.js"; -import { randomItem } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js'; -import { getEndusers, getDefaultThresholds } from '../../performancetest_common/common.js' - -const filenameEndusers = '../../performancetest_data/.endusers-with-tokens.csv'; - -const endUsers = getEndusers(filenameEndusers); - -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'],['simple 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)) { - simpleSearch(endUsers[0]); - } - else { - simpleSearch(randomItem(endUsers)); - } -} - -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/') -} - -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(); - retrieveDialogContent(r, paramsWithToken); - }); -} - -export function getContent(dialogId, paramsWithToken, tag, path = '') { - const listParams = { - ...paramsWithToken, - tags: { ...paramsWithToken.tags, name: tag } - }; - getUrl('dialogs/' + dialogId + path, listParams); -} - -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); - } -} - -export function getUrl(url, paramsWithToken) { - let r = getEU(url, paramsWithToken); - expectStatusFor(r).to.equal(200); - expect(r, 'response').to.have.validJsonBody(); - return r; -} - 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..2f67c8a3c --- /dev/null +++ b/tests/k6/tests/graphql/performance/graphql-search.js @@ -0,0 +1,23 @@ +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"; + +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']) +}; + +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/common.js b/tests/k6/tests/performancetest_common/common.js deleted file mode 100644 index efe1cc5e5..000000000 --- a/tests/k6/tests/performancetest_common/common.js +++ /dev/null @@ -1,51 +0,0 @@ -import { SharedArray } from 'k6/data'; -import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js'; - -export function getEndusers(filenameEndusers) { - if (!filenameEndusers || typeof filenameEndusers !== 'string') { - throw new Error('filenameEndusers must be a non-empty string'); - } - - 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: ${filenameEndusers}`); - } - csvData.forEach((user, index) => { - if (!user.token || !user.ssn) { - throw new Error(`Missing required fields (token or ssn) at row ${index + 1} in ${filenameEndusers}`); - } - }); - return csvData; - } catch (error) { - if (error.code === 'ENOENT') { - throw new Error(`CSV file not found: ${filenameEndusers}`); - } - throw new Error(`Failed to load end users from ${filenameEndusers}: ${error.message}`); - } - }); - return endUsers; -} - -/** - * 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/createDialog.js b/tests/k6/tests/performancetest_common/createDialog.js new file mode 100644 index 000000000..6e23a150f --- /dev/null +++ b/tests/k6/tests/performancetest_common/createDialog.js @@ -0,0 +1,53 @@ +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. + * @param {Object} endUser - The end user. + */ + +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); + }); + +} + +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..5558f35d4 --- /dev/null +++ b/tests/k6/tests/performancetest_common/getDefaultThresholds.js @@ -0,0 +1,23 @@ +/** + * 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..07a9f061f --- /dev/null +++ b/tests/k6/tests/performancetest_common/readTestdata.js @@ -0,0 +1,21 @@ +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'; +export const serviceOwners = new SharedArray('serviceOwners', function () { + return papaparse.parse(open(filenameServiceowners), { header: true, skipEmptyLines: true }).data; +}); + +if (!__ENV.API_ENVIRONMENT) { + throw new Error('API_ENVIRONMENT must be set'); +} +const filenameEndusers = `../performancetest_data/endusers-${__ENV.API_ENVIRONMENT}.csv`; +export const endUsers = new SharedArray('endUsers', function () { + return papaparse.parse(open(filenameEndusers), { header: true, skipEmptyLines: true }).data; +}); + +const filenameEndusersWithTokens = '../performancetest_data/.endusers-with-tokens.csv'; +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..48e7f0ca2 --- /dev/null +++ b/tests/k6/tests/performancetest_common/simpleSearch.js @@ -0,0 +1,125 @@ +import { randomItem } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js'; +import { expect, expectStatusFor } from "../../common/testimports.js"; +import { describe } from '../../common/describe.js'; +import { getEU, postGQ } from '../../common/request.js'; +import { uuidv4 } from 'https://jslib.k6.io/k6-utils/1.4.0/index.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, + }, + 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/serviceowner/performance/create-dialog-and-search.js b/tests/k6/tests/serviceowner/performance/create-dialog-and-search.js deleted file mode 100644 index 090dbd6c7..000000000 --- a/tests/k6/tests/serviceowner/performance/create-dialog-and-search.js +++ /dev/null @@ -1,53 +0,0 @@ -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'; - -import { createDialog, options as dialogOptions } from './create-dialog.js'; -import { simpleSearch, options as searchOptions } from '../../enduser/performance/simple-search.js'; - -const filenameServiceowners = '../../performancetest_data/.serviceowners-with-tokens.csv'; -const serviceOwners = new SharedArray('serviceOwners', function () { - return papaparse.parse(open(filenameServiceowners), { header: true, skipEmptyLines: true }).data; -}); - -const filenameEndusersWithTokens = '../../performancetest_data/.endusers-with-tokens.csv'; -const endUsersWithTokens = new SharedArray('endUsersWithTokens', function () { - return papaparse.parse(open(filenameEndusersWithTokens), { header: true, skipEmptyLines: true }).data; - }); - -function joinThresholds(t1, t2) { - for (var k in t2) { - t1[k] = t2[k]; - } - return t1; -} - -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: 'simple search'}, - exec: 'simpleSearches', - vus: __ENV.evus, - duration: __ENV.duration, - } - }, - summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(95)', 'p(99)', 'p(99.5)', 'p(99.9)', 'count'], - thresholds: joinThresholds(searchOptions.thresholds, dialogOptions.thresholds) - -}; - -export function createDialogs() { - createDialog(randomItem(serviceOwners), randomItem(endUsersWithTokens)); -} - -export function simpleSearches() { - simpleSearch(randomItem(endUsersWithTokens)); -} \ No newline at end of file diff --git a/tests/k6/tests/serviceowner/performance/create-dialog.js b/tests/k6/tests/serviceowner/performance/create-dialog.js index a83dfb2a5..8354449d3 100644 --- a/tests/k6/tests/serviceowner/performance/create-dialog.js +++ b/tests/k6/tests/serviceowner/performance/create-dialog.js @@ -1,25 +1,9 @@ -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 { randomItem } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js'; import { uuidv4 } from 'https://jslib.k6.io/k6-utils/1.4.0/index.js'; -import { getDefaultThresholds } from '../../performancetest_common/common.js' -import { default as dialogToInsert } from '../../performancetest_data/01-create-dialog.js'; - -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`; - -export const serviceOwners = new SharedArray('serviceOwners', function () { - return papaparse.parse(open(filenameServiceowners), { header: true, skipEmptyLines: true }).data; -}); - -export 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'], @@ -35,18 +19,3 @@ export default function() { } } -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); - }); - -} \ 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 index 7a91ec63b..6be2f297a 100644 --- a/tests/k6/tests/serviceowner/performance/create-remove-dialog.js +++ b/tests/k6/tests/serviceowner/performance/create-remove-dialog.js @@ -1,11 +1,8 @@ -import { postSO, purgeSO, 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 { 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'; -import { getDefaultThresholds } from '../../performancetest_common/common.js' -import { default as dialogToInsert } from '../../performancetest_data/01-create-dialog.js'; -import { serviceOwners, endUsers } from "./create-dialog.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'],[ @@ -22,29 +19,3 @@ export default function() { createAndRemoveDialog(randomItem(serviceOwners), randomItem(endUsers)); } } - -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); - } - }); - -} \ No newline at end of file From 088fc3cceb3e21382bd160e203575ee58b97e48e Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Tue, 5 Nov 2024 12:56:38 +0100 Subject: [PATCH 32/34] refactoring, directory and file missed the previous commit --- .../performance/create-dialog-and-search.js | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 tests/k6/tests/scenarios/performance/create-dialog-and-search.js 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..ea0a26556 --- /dev/null +++ b/tests/k6/tests/scenarios/performance/create-dialog-and-search.js @@ -0,0 +1,47 @@ +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'; + +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 From 5d13c277295098c894cbdb2eebe862bb4c221ca0 Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Tue, 5 Nov 2024 13:38:28 +0100 Subject: [PATCH 33/34] documentation performed by github copilot --- .../graphql/performance/graphql-search.js | 14 +++++++ .../performancetest_common/createDialog.js | 16 ++++++-- .../getDefaultThresholds.js | 2 - .../performancetest_common/readTestdata.js | 41 ++++++++++++++++--- .../performancetest_common/simpleSearch.js | 5 +++ .../performance/create-dialog-and-search.js | 20 ++++++++- .../serviceowner/performance/create-dialog.js | 6 ++- .../performance/create-remove-dialog.js | 4 ++ 8 files changed, 94 insertions(+), 14 deletions(-) diff --git a/tests/k6/tests/graphql/performance/graphql-search.js b/tests/k6/tests/graphql/performance/graphql-search.js index 2f67c8a3c..de5a9c04d 100644 --- a/tests/k6/tests/graphql/performance/graphql-search.js +++ b/tests/k6/tests/graphql/performance/graphql-search.js @@ -1,13 +1,27 @@ +/** + * 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'); diff --git a/tests/k6/tests/performancetest_common/createDialog.js b/tests/k6/tests/performancetest_common/createDialog.js index 6e23a150f..ecd7591cb 100644 --- a/tests/k6/tests/performancetest_common/createDialog.js +++ b/tests/k6/tests/performancetest_common/createDialog.js @@ -1,3 +1,6 @@ +/** + * 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"; @@ -6,11 +9,10 @@ import dialogToInsert from "../performancetest_data/01-create-dialog.js"; /** * Creates a dialog. - * - * @param {Object} serviceOwner - The service owner. - * @param {Object} endUser - The end user. + * + * @param {Object} serviceOwner - The service owner object. + * @param {Object} endUser - The end user object. */ - export function createDialog(serviceOwner, endUser) { var paramsWithToken = { headers: { @@ -27,6 +29,12 @@ export function createDialog(serviceOwner, endUser) { } +/** + * 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: { diff --git a/tests/k6/tests/performancetest_common/getDefaultThresholds.js b/tests/k6/tests/performancetest_common/getDefaultThresholds.js index 5558f35d4..12b912711 100644 --- a/tests/k6/tests/performancetest_common/getDefaultThresholds.js +++ b/tests/k6/tests/performancetest_common/getDefaultThresholds.js @@ -5,8 +5,6 @@ * @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'); diff --git a/tests/k6/tests/performancetest_common/readTestdata.js b/tests/k6/tests/performancetest_common/readTestdata.js index 07a9f061f..0fbea3624 100644 --- a/tests/k6/tests/performancetest_common/readTestdata.js +++ b/tests/k6/tests/performancetest_common/readTestdata.js @@ -1,20 +1,51 @@ +/** + * 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'; -export const serviceOwners = new SharedArray('serviceOwners', function () { - return papaparse.parse(open(filenameServiceowners), { header: true, skipEmptyLines: true }).data; -}); - 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; }); -const filenameEndusersWithTokens = '../performancetest_data/.endusers-with-tokens.csv'; +/** + * 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 index 48e7f0ca2..f303bb120 100644 --- a/tests/k6/tests/performancetest_common/simpleSearch.js +++ b/tests/k6/tests/performancetest_common/simpleSearch.js @@ -1,3 +1,7 @@ +/** + * This file contains common functions for performing simple searches + * and GraphQL searches. + */ import { randomItem } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js'; import { expect, expectStatusFor } from "../../common/testimports.js"; import { describe } from '../../common/describe.js'; @@ -114,6 +118,7 @@ export function graphqlSearch(enduser) { let paramsWithToken = { headers: { Authorization: "Bearer " + enduser.token, + traceparent: uuidv4() }, tags: { name: 'graphql search' } }; diff --git a/tests/k6/tests/scenarios/performance/create-dialog-and-search.js b/tests/k6/tests/scenarios/performance/create-dialog-and-search.js index ea0a26556..8329e7402 100644 --- a/tests/k6/tests/scenarios/performance/create-dialog-and-search.js +++ b/tests/k6/tests/scenarios/performance/create-dialog-and-search.js @@ -1,10 +1,28 @@ +/** + * 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: { diff --git a/tests/k6/tests/serviceowner/performance/create-dialog.js b/tests/k6/tests/serviceowner/performance/create-dialog.js index 8354449d3..df51bde99 100644 --- a/tests/k6/tests/serviceowner/performance/create-dialog.js +++ b/tests/k6/tests/serviceowner/performance/create-dialog.js @@ -1,6 +1,8 @@ +/** + * 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'; -import { uuidv4 } from 'https://jslib.k6.io/k6-utils/1.4.0/index.js'; - import { getDefaultThresholds } from '../../performancetest_common/getDefaultThresholds.js'; import { createDialog } from '../../performancetest_common/createDialog.js'; import { serviceOwners, endUsers } from '../../performancetest_common/readTestdata.js'; diff --git a/tests/k6/tests/serviceowner/performance/create-remove-dialog.js b/tests/k6/tests/serviceowner/performance/create-remove-dialog.js index 6be2f297a..544f40d20 100644 --- a/tests/k6/tests/serviceowner/performance/create-remove-dialog.js +++ b/tests/k6/tests/serviceowner/performance/create-remove-dialog.js @@ -1,3 +1,7 @@ +/** + * 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"; From 87faf62a33d9d78193fd38ae0afef8dcbef998db Mon Sep 17 00:00:00 2001 From: Dagfinn Olsen Date: Tue, 5 Nov 2024 14:06:20 +0100 Subject: [PATCH 34/34] use newer version of k6-utils --- tests/k6/tests/performancetest_common/simpleSearch.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/k6/tests/performancetest_common/simpleSearch.js b/tests/k6/tests/performancetest_common/simpleSearch.js index f303bb120..8feb8ef37 100644 --- a/tests/k6/tests/performancetest_common/simpleSearch.js +++ b/tests/k6/tests/performancetest_common/simpleSearch.js @@ -2,11 +2,10 @@ * This file contains common functions for performing simple searches * and GraphQL searches. */ -import { randomItem } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js'; +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 { uuidv4 } from 'https://jslib.k6.io/k6-utils/1.4.0/index.js'; import { getGraphqlParty } from '../performancetest_data/graphql-search.js';