From a042d30fef23343947ffeebe4e761c52c094f652 Mon Sep 17 00:00:00 2001 From: Chris Sherlock <99017916+cshnimble@users.noreply.github.com> Date: Wed, 24 Jan 2024 14:47:44 +0000 Subject: [PATCH] Adds performance tests for supported endpoints (#451) * added a basic performance test for v4 establishments and trusts * added a basic performance test for v4 establishments and trusts * Move perf scripts to v4 folder * Adds basic performance tests for other supported endpoints in v1, 2 and 3 * Set baseUrl as env var, add README for perf tests * Remove redundant perf scripts * Move repeated functions to utils, set api key at runtime --------- Co-authored-by: mikestock-nimble --- Dfe.Academies.Performance/README.md | 28 ++++++ .../scripts/utils/utils.js | 14 +++ .../scripts/v1/keyStagePerformance.js | 25 +++++ .../scripts/v2/baselineTracker.js | 26 +++++ .../scripts/v2/fssProjects.js | 25 +++++ .../scripts/v3/trusts.js | 45 +++++++++ .../scripts/v4/establishments.js | 45 +++++++++ .../scripts/v4/trusts.js | 66 +++++++++++++ .../Integration/V4/GenerateDataTests.cs | 95 +++++++++++++++++++ 9 files changed, 369 insertions(+) create mode 100644 Dfe.Academies.Performance/README.md create mode 100644 Dfe.Academies.Performance/scripts/utils/utils.js create mode 100644 Dfe.Academies.Performance/scripts/v1/keyStagePerformance.js create mode 100644 Dfe.Academies.Performance/scripts/v2/baselineTracker.js create mode 100644 Dfe.Academies.Performance/scripts/v2/fssProjects.js create mode 100644 Dfe.Academies.Performance/scripts/v3/trusts.js create mode 100644 Dfe.Academies.Performance/scripts/v4/establishments.js create mode 100644 Dfe.Academies.Performance/scripts/v4/trusts.js create mode 100644 TramsDataApi.Test/Integration/V4/GenerateDataTests.cs diff --git a/Dfe.Academies.Performance/README.md b/Dfe.Academies.Performance/README.md new file mode 100644 index 000000000..4615c3789 --- /dev/null +++ b/Dfe.Academies.Performance/README.md @@ -0,0 +1,28 @@ +# Academies API Performance tests + +This directory holds the performance test scripts for the Academies API. + +Tests are written for the [k6](https://k6.io) performance testing tool. + +## Setup + +You will need k6 installed to be able to run these tests. Details on how to do so are available in their [documentation](https://grafana.com/docs/k6/latest/get-started/installation/). + +## Configuration + +The variables you will need to set are defined below: + +| Variable | Description | Example | +|---|---|---| +| `API_KEY` | The API key used in headers for requests | `app-key` | +| `BASE_URL` | The url of the service to be tested | `https//localhost:5001` | + +## Running the tests + +To run an individual script, navigate to the correct directory and run + +`k6 run -e API_KEY= -e BASE_URL= ` + +## Results + +By default, metrics are output to the console at the end of the tests, including any checks that are run as part of the test scripts. \ No newline at end of file diff --git a/Dfe.Academies.Performance/scripts/utils/utils.js b/Dfe.Academies.Performance/scripts/utils/utils.js new file mode 100644 index 000000000..1cfd9331a --- /dev/null +++ b/Dfe.Academies.Performance/scripts/utils/utils.js @@ -0,0 +1,14 @@ +import { check } from 'k6' + +export function isStatus200(res) { + check(res, { + 'status is 200': (r) => r.status === 200, + }) +} + +export function getHeaders() { + return { + 'ApiKey': `${__ENV.API_KEY}`, + 'Content-Type': 'application/json' + } +} diff --git a/Dfe.Academies.Performance/scripts/v1/keyStagePerformance.js b/Dfe.Academies.Performance/scripts/v1/keyStagePerformance.js new file mode 100644 index 000000000..fd5ac6bc3 --- /dev/null +++ b/Dfe.Academies.Performance/scripts/v1/keyStagePerformance.js @@ -0,0 +1,25 @@ +import http from 'k6/http' +import { check, sleep } from 'k6' +import { isStatus200, getHeaders } from '../utils/utils.js' + +export const options = { + vus: 20, + duration: '30s' +} + +const baseUrl = __ENV.BASE_URL + +export default function () { + + getEducationPerformanceByUrn('100000') + + sleep(1) +} + +function getEducationPerformanceByUrn(urn) { + const res = http.get(`${baseUrl}/educationPerformance/${urn}`, { + headers: getHeaders() + }) + + isStatus200(res) +} diff --git a/Dfe.Academies.Performance/scripts/v2/baselineTracker.js b/Dfe.Academies.Performance/scripts/v2/baselineTracker.js new file mode 100644 index 000000000..e0ec4d9ef --- /dev/null +++ b/Dfe.Academies.Performance/scripts/v2/baselineTracker.js @@ -0,0 +1,26 @@ +import http from 'k6/http' +import { check, sleep } from 'k6' +import { isStatus200, getHeaders } from '../utils/utils.js' + +export const options = { + vus: 20, + duration: '30s' +} + +const baseUrl = `${__ENV.BASE_URL}/v2` + +export default function () { + + getBaselineTrackers() + + sleep(1) +} + +function getBaselineTrackers() { + // TODO change url endpoint when spelling mistake resolved + const res = http.get(`${baseUrl}/basline-tracker?page=1&count=50`, { + headers: getHeaders() + }) + + isStatus200(res) +} diff --git a/Dfe.Academies.Performance/scripts/v2/fssProjects.js b/Dfe.Academies.Performance/scripts/v2/fssProjects.js new file mode 100644 index 000000000..636ef6ca2 --- /dev/null +++ b/Dfe.Academies.Performance/scripts/v2/fssProjects.js @@ -0,0 +1,25 @@ +import http from 'k6/http' +import { check, sleep } from 'k6' +import { isStatus200, getHeaders } from '../utils/utils.js' + +export const options = { + vus: 20, + duration: '30s' +} + +const baseUrl = `${__ENV.BASE_URL}/v2` + +export default function () { + + getFreeSchoolProjects() + + sleep(1) +} + +function getFreeSchoolProjects() { + const res = http.get(`${baseUrl}/fss/projects`, { + headers: getHeaders() + }) + + isStatus200(res) +} diff --git a/Dfe.Academies.Performance/scripts/v3/trusts.js b/Dfe.Academies.Performance/scripts/v3/trusts.js new file mode 100644 index 000000000..da8b5e256 --- /dev/null +++ b/Dfe.Academies.Performance/scripts/v3/trusts.js @@ -0,0 +1,45 @@ +import http from 'k6/http' +import { check, sleep } from 'k6' +import { isStatus200, getHeaders } from '../utils/utils.js' + +export const options = { + vus: 20, + duration: '30s' +} + +const baseUrl = `${__ENV.BASE_URL}/v3` + +export default function () { + + getTrustByUkPrn('10067112') + + searchTrustByName('SOUTH YORK MULTI ACADEMY TRUST') + + searchTrustByUkPrn('10067112') + + sleep(1) +} + +function getTrustByUkPrn(ukprn) { + const res = http.get(`${baseUrl}/trust/${ukprn}`, { + headers: getHeaders() + }) + + isStatus200(res) +} + +function searchTrustByName(name) { + const res = http.get(`${baseUrl}/trusts?groupName=${name}&page=1&count=10`, { + headers: getHeaders() + }) + + isStatus200(res); +} + +function searchTrustByUkPrn(ukprn) { + const res = http.get(`${baseUrl}/trusts?ukPrn=${ukprn}&page=1&count=10`, { + headers: getHeaders() + }) + + isStatus200(res) +} diff --git a/Dfe.Academies.Performance/scripts/v4/establishments.js b/Dfe.Academies.Performance/scripts/v4/establishments.js new file mode 100644 index 000000000..24b8106dd --- /dev/null +++ b/Dfe.Academies.Performance/scripts/v4/establishments.js @@ -0,0 +1,45 @@ +import http from 'k6/http' +import { check, sleep } from 'k6' +import { isStatus200, getHeaders } from '../utils/utils.js' + +export const options = { + vus: 20, + duration: '30s' +} + +const baseUrl = `${__ENV.BASE_URL}/v4` + +export default function () { + + getEstablishmentByUkPrn('10079319') + + getEstablishmentByUrn('100000') + + getEstablishmentsForTrust('10067112') + + sleep(1) +} + +function getEstablishmentByUkPrn(ukprn) { + const res = http.get(`${baseUrl}/establishment/${ukprn}`, { + headers: getHeaders() + }) + + isStatus200(res) +} + +function getEstablishmentByUrn(urn) { + const res = http.get(`${baseUrl}/establishment/urn/${urn}`, { + headers: getHeaders() + }) + + isStatus200(res) +} + +function getEstablishmentsForTrust(ukprn) { + const res = http.get(`${baseUrl}/establishments/trust?trustUkPrn=${ukprn}`, { + headers: getHeaders() + }) + + isStatus200(res) +} diff --git a/Dfe.Academies.Performance/scripts/v4/trusts.js b/Dfe.Academies.Performance/scripts/v4/trusts.js new file mode 100644 index 000000000..a3e398467 --- /dev/null +++ b/Dfe.Academies.Performance/scripts/v4/trusts.js @@ -0,0 +1,66 @@ +import http from 'k6/http' +import { check, sleep } from 'k6' +import { isStatus200, getHeaders } from '../utils/utils.js' + +export const options = { + vus: 20, + duration: '30s' +} + +const baseUrl = `${__ENV.BASE_URL}/v4` + +export default function () { + + getTrustByUkPrn('10067112') + + getTrustByCompaniesHouseNumber('11082297') + + getTrustByReferenceNumber('TR03739') + + searchTrustByUkPrn('10067112') + + searchTrustByName('SOUTH YORK MULTI ACADEMY TRUST') + + sleep(1) +} + +function getTrustByUkPrn(ukprn) { + const res = http.get(`${baseUrl}/trust/${ukprn}`, { + headers: getHeaders() + }) + + isStatus200(res) +} + +function getTrustByCompaniesHouseNumber(companiesHouseNumber) { + const res = http.get(`${baseUrl}/trust/companiesHouseNumber/${companiesHouseNumber}`, { + headers: getHeaders() + }) + + isStatus200(res) + +} + +function getTrustByReferenceNumber(trustReferenceNumber) { + const res = http.get(`${baseUrl}/trust/trustReferenceNumber/${trustReferenceNumber}`, { + headers: getHeaders() + }) + + isStatus200(res) +} + +function searchTrustByName(name) { + const res = http.get(`${baseUrl}/trusts?groupName=${name}&page=1&count=10`, { + headers: getHeaders() + }) + + isStatus200(res); +} + +function searchTrustByUkPrn(ukprn) { + const res = http.get(`${baseUrl}/trusts?ukPrn=${ukprn}&page=1&count=10`, { + headers: getHeaders() + }) + + isStatus200(res) +} diff --git a/TramsDataApi.Test/Integration/V4/GenerateDataTests.cs b/TramsDataApi.Test/Integration/V4/GenerateDataTests.cs new file mode 100644 index 000000000..669f1886e --- /dev/null +++ b/TramsDataApi.Test/Integration/V4/GenerateDataTests.cs @@ -0,0 +1,95 @@ +using Dfe.Academies.Academisation.Data; +using Dfe.Academies.Domain.Establishment; +using Dfe.Academies.Domain.Trust; +using System.Collections.Generic; +using System.Linq; +using TramsDataApi.Test.Fixtures; +using TramsDataApi.Test.Helpers; +using Xunit; + +namespace TramsDataApi.Test.Integration.V4 +{ + [Collection(ApiTestCollection.ApiTestCollectionName)] + public class GenerateDataTests + { + private readonly ApiTestFixture _apiFixture; + + public GenerateDataTests(ApiTestFixture fixture) + { + _apiFixture = fixture; + } + + [Fact(Skip = "Generate data for performance testing on an adhoc basis")] + public void GenerateTrustData() + { + using var context = _apiFixture.GetMstrContext(); + context.ChangeTracker.AutoDetectChangesEnabled = false; + + for (var idx = 0; idx < 4000; idx++) + { + CreateDataSet(context); + } + } + + private static TrustDataSet CreateDataSet(MstrContext context) + { + var trust = DatabaseModelBuilder.BuildTrust(); + context.Add(trust); + context.SaveChanges(); + + var establishments = new List(); + + for (var idx = 0; idx < 3; idx++) + { + var establishment = DatabaseModelBuilder.BuildEstablishment(); + var ifdPipeline = DatabaseModelBuilder.BuildIfdPipeline(); + ifdPipeline.GeneralDetailsUrn = establishment.PK_GIAS_URN; + + var establishmentDataSet = new EstablishmentDataSet() + { + Establishment = establishment, + IfdPipeline = ifdPipeline + }; + + context.Establishments.Add(establishment); + context.IfdPipelines.Add(ifdPipeline); + + establishments.Add(establishmentDataSet); + } + + context.SaveChanges(); + + var trustToEstablishmentLinks = LinkTrustToEstablishments(trust, establishments.Select(d => d.Establishment).ToList()); + + context.EducationEstablishmentTrusts.AddRange(trustToEstablishmentLinks); + + context.SaveChanges(); + + var result = new TrustDataSet() + { + Trust = trust, + Establishments = establishments + }; + + return result; + } + + private static List LinkTrustToEstablishments(Trust trust, List establishments) + { + var result = new List(); + + establishments.ForEach(establishment => + { + var educationEstablishmentTrust = new EducationEstablishmentTrust() + { + TrustId = (int)trust.SK, + EducationEstablishmentId = (int)establishment.SK + }; + + result.Add(educationEstablishmentTrust); + }); + + return result; + } + } +}