├── README.md ├── .github ├── CODEOWNERS └── workflows │ └── efm-ip-use-case.yml ├── renovate.json ├── tests └── k6 │ ├── docker-compose.yml │ ├── src │ ├── config.js │ ├── errorhandler.js │ ├── apiHelpers.js │ ├── tests │ │ ├── availability-check.js │ │ └── failed-shipment-check.js │ └── api │ │ └── integrationPoint.js │ └── readme.md └── .gitattributes /README.md: -------------------------------------------------------------------------------- 1 | # Altinn Eformidling 2 | 3 | 4 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | /.github/CODEOWNERS @altinn/team-core 2 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "local>Altinn/renovate-config" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /tests/k6/docker-compose.yml: -------------------------------------------------------------------------------- 1 | networks: 2 | k6: 3 | 4 | services: 5 | k6: 6 | image: grafana/k6:1.4.2 7 | networks: 8 | - k6 9 | ports: 10 | - "6565:6565" 11 | volumes: 12 | - ./src:/src 13 | -------------------------------------------------------------------------------- /tests/k6/src/config.js: -------------------------------------------------------------------------------- 1 | // Baseurls for platform 2 | export var baseUrls = { 3 | tt02: "tt02.altinn.no", 4 | prod: "altinn.no", 5 | }; 6 | 7 | //Get values from environment 8 | const environment = __ENV.env.toLowerCase(); 9 | export let baseUrl = baseUrls[environment]; 10 | 11 | 12 | // Integration point 13 | export var integrationPoint = { 14 | status: 15 | "https://platform." + baseUrl + "/eformidling/api/statuses/", 16 | availability: 17 | "https://platform." + baseUrl + "/eformidling/api/manage/availability/", 18 | conversations: 19 | "https://platform." + baseUrl + "/eformidling/api/conversations/", 20 | } -------------------------------------------------------------------------------- /tests/k6/src/errorhandler.js: -------------------------------------------------------------------------------- 1 | import { Counter } from "k6/metrics"; 2 | import { fail } from "k6"; 3 | 4 | let ErrorCount = new Counter("errors"); 5 | 6 | //Adds a count to the error counter when value of success is false 7 | export function addErrorCount(success) { 8 | if (!success) { 9 | ErrorCount.add(1); 10 | } 11 | } 12 | 13 | /** 14 | * Stops k6 iteration when success is false and prints test name with response code 15 | * @param {String} failReason The reason for stopping the tests 16 | * @param {boolean} success The result of a check 17 | */ 18 | export function stopIterationOnFail(failReason, success) { 19 | if (!success) { 20 | fail(failReason); 21 | } 22 | } -------------------------------------------------------------------------------- /tests/k6/src/apiHelpers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Build a string in a format of query param to the endpoint 3 | * @param {*} queryparams a json object with key as query name and value as query value 4 | * @example {"key1": "value1", "key2": "value2"} 5 | * @returns string a string like key1=value&key2=value2 6 | */ 7 | export function generateQueryParamString(queryparams) { 8 | var query = "?"; 9 | Object.keys(queryparams).forEach(function (key) { 10 | if (Array.isArray(queryparams[key])) { 11 | queryparams[key].forEach((value) => { 12 | query += key + "=" + value + "&"; 13 | }); 14 | } else { 15 | query += key + "=" + queryparams[key] + "&"; 16 | } 17 | }); 18 | query = query.slice(0, -1); 19 | return query; 20 | } -------------------------------------------------------------------------------- /tests/k6/src/tests/availability-check.js: -------------------------------------------------------------------------------- 1 | /* 2 | Test script for availability check in eformidling 3 | Command: 4 | podman compose run k6 run /src/tests/availability-check.js ` 5 | -e env=*** ` 6 | -e subscriptionKey=*** ` 7 | */ 8 | import { check } from "k6"; 9 | import { addErrorCount, stopIterationOnFail } from "../errorhandler.js"; 10 | import * as integrationPointApi from "../api/integrationPoint.js"; 11 | 12 | export const options = { 13 | thresholds: { 14 | errors: ["count<1"], 15 | }, 16 | }; 17 | 18 | export default function () { 19 | var response, success; 20 | 21 | response = integrationPointApi.GetAvailability(); 22 | success = check(response, { 23 | "GET integration point availability. Status is 200 OK": (r) => 24 | r.status === 200, 25 | "GET integration point availability. Response in 'UP'": (r) => 26 | r.body === "UP", 27 | }); 28 | 29 | addErrorCount(success); 30 | if (!success) { 31 | stopIterationOnFail('GET integration point availability request failed', success); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # General setting that applies Git's binary detection for file-types not specified below 2 | # Meaning, for 'text-guessed' files: 3 | # use normalization (convert crlf -> lf on commit, i.e. use `text` setting) 4 | # & do unspecified diff behavior (if file content is recognized as text & filesize < core.bigFileThreshold, do text diff on file changes) 5 | * text=auto 6 | 7 | 8 | # Override with explicit specific settings for known and/or likely text files in our repo that should be normalized 9 | # where diff{=optional_pattern} means "do text diff {with specific text pattern} and -diff means "don't do text diffs". 10 | # Unspecified diff behavior is decribed above 11 | *.cer text -diff 12 | *.cmd text 13 | *.cs text diff=csharp 14 | *.csproj text 15 | *.css text diff=css 16 | Dockerfile text 17 | *.json text 18 | *.md text diff=markdown 19 | *.msbuild text 20 | *.pem text -diff 21 | *.ps1 text 22 | *.sln text 23 | *.yaml text 24 | *.yml text 25 | 26 | # Files that should be treated as binary ('binary' is a macro for '-text -diff', i.e. "don't normalize or do text diff on content") 27 | *.jpeg binary 28 | *.pfx binary 29 | *.png binary -------------------------------------------------------------------------------- /tests/k6/readme.md: -------------------------------------------------------------------------------- 1 | ## k6 test project for automated tests 2 | 3 | # Getting started 4 | 5 | 6 | Install pre-requisites 7 | ## Install k6 8 | 9 | *We recommend running the tests through a docker container.* 10 | 11 | From the command line: 12 | 13 | > docker pull grafana/k6 14 | 15 | 16 | Further information on [installing k6 for running in docker is available here.](https://k6.io/docs/get-started/installation/#docker) 17 | 18 | 19 | Alternatively, it is possible to run the tests directly on your machine as well. 20 | 21 | [General installation instructions are available here.](https://k6.io/docs/get-started/installation/) 22 | 23 | 24 | ## Running tests 25 | 26 | All tests are defined in `src/tests` and in the top of each test file an example of the cmd to run the test is available. 27 | 28 | The command should be run from the root of the k6 folder. 29 | 30 | >$> cd /test/k6 31 | 32 | Run test suite by specifying filename. 33 | 34 | For example: 35 | 36 | >$> podman-compose run k6 run /src/tests/availability-check.js -e env=*** -e apimSubscriptionKey=*** 37 | 38 | The comand consists of three sections 39 | 40 | `podman-compose run` to run the test in a docker container 41 | 42 | `k6 run {path to test file}` pointing to the test file you want to run e.g. `/src/tests/availability-check.js` 43 | 44 | 45 | `-e env=*** -e apimSubscriptionKey=***` all environment variables that should be included in the request. 46 | -------------------------------------------------------------------------------- /.github/workflows/efm-ip-use-case.yml: -------------------------------------------------------------------------------- 1 | name: Use case - TT02 & PROD 2 | permissions: 3 | contents: read 4 | 5 | on: 6 | workflow_dispatch: 7 | schedule: 8 | - cron: '*/15 * * * *' 9 | 10 | jobs: 11 | test: 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | environment: [TT02, PROD] 16 | environment: ${{ matrix.environment }} 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 20 | - name: Run availability check 21 | run: | 22 | cd tests/k6 23 | docker compose run k6 run /src/tests/availability-check.js -e env=${{ vars.ENV }} -e apimSubscriptionKey=${{ secrets.APIM_SUBSKEY }} 24 | - name: Run failed shipment check 25 | if: ${{ matrix.environment != 'TT02' }} 26 | run: | 27 | cd tests/k6 28 | docker compose run k6 run /src/tests/failed-shipment-check.js -e env=${{ vars.ENV }} -e apimSubscriptionKey=${{ secrets.APIM_SUBSKEY }} 29 | - name: Build failure report 30 | if: failure() 31 | run: | 32 | report=":warning: eFormidling integration point - Failed checks identified in ${{ vars.ENV }} :warning: \n" 33 | report+="\n Workflow available here: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" 34 | echo "stepreport="$report >> $GITHUB_ENV 35 | - name: Report failure to Slack 36 | if: failure() 37 | id: slack 38 | uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1 39 | with: 40 | webhook-type: incoming-webhook 41 | webhook: ${{ secrets.SLACK_WEBHOOK_URL_PROD }} 42 | payload: | 43 | { 44 | "text": "${{ env.stepreport }}" 45 | } 46 | -------------------------------------------------------------------------------- /tests/k6/src/tests/failed-shipment-check.js: -------------------------------------------------------------------------------- 1 | /* 2 | Test script for failed shipment check in eformidling 3 | Command: 4 | podman compose run k6 run /src/tests/failed-shipment-check.js ` 5 | -e env=*** ` 6 | -e apimSubscriptionKey=*** ` 7 | */ 8 | import { check } from "k6"; 9 | import { addErrorCount, stopIterationOnFail } from "../errorhandler.js"; 10 | import * as integrationPointApi from "../api/integrationPoint.js"; 11 | 12 | export const options = { 13 | thresholds: { 14 | errors: ["count<1"], 15 | }, 16 | }; 17 | 18 | export default function () { 19 | let failedMessages = []; 20 | 21 | let messages = GetLevetidUtloptMessages(); 22 | 23 | messages.forEach((message) => { 24 | let id = message.messageId; 25 | 26 | // only outgoing conversations should be considered. We don't expect any incoming messages 27 | if (IsOutgoingConversation(id)) { 28 | failedMessages.push(id); 29 | } 30 | }); 31 | 32 | if (failedMessages.length > 0){ 33 | console.log("Levetid utløpt registered for messageId(s):", failedMessages); 34 | } 35 | 36 | let success = check(failedMessages, { 37 | "GET levetid utløpt instances. No failed shipments": (failedMessages) => 38 | failedMessages.length === 0, 39 | }); 40 | addErrorCount(success); 41 | 42 | } 43 | 44 | function GetLevetidUtloptMessages() { 45 | var response, success; 46 | 47 | response = integrationPointApi.GetLevetidUtloptLast20(); 48 | success = check(response, { 49 | "GET levetid utløpt instances. Status is 200 OK": (r) => r.status === 200, 50 | }); 51 | 52 | addErrorCount(success); 53 | if (!success) { 54 | stopIterationOnFail('GET levetid utløpt messages request failed',success); 55 | } 56 | 57 | let messages = JSON.parse(response.body)["content"]; 58 | 59 | return messages; 60 | } 61 | 62 | function IsOutgoingConversation(messageId) { 63 | let response = integrationPointApi.GetConversation(messageId); 64 | let conversationDir = JSON.parse(response.body).content[0].direction; 65 | 66 | return conversationDir === "OUTGOING"; 67 | } 68 | -------------------------------------------------------------------------------- /tests/k6/src/api/integrationPoint.js: -------------------------------------------------------------------------------- 1 | import http from "k6/http"; 2 | import { generateQueryParamString } from "../apiHelpers.js"; 3 | import * as config from "../config.js"; 4 | import { stopIterationOnFail } from "../errorhandler.js"; 5 | 6 | const apimSubscriptionKey = __ENV.apimSubscriptionKey; 7 | 8 | export function GetLevetidUtloptLast20() { 9 | if (!apimSubscriptionKey) { 10 | stopIterationOnFail( 11 | "Required environment variable APIM subscription key (apimSubscriptionKey) was not provided", 12 | false 13 | ); 14 | } 15 | 16 | const now = new Date(); 17 | 18 | const fromDateTime = new Date(now.getTime() - 20 * 60 * 1000).toISOString(); 19 | const toDateTime = new Date().toISOString(); 20 | 21 | var params = { 22 | "subscription-key": apimSubscriptionKey, 23 | status: "LEVETID_UTLOPT", 24 | fromDateTime: fromDateTime, 25 | toDateTime: toDateTime, 26 | }; 27 | 28 | var endpoint = 29 | config.integrationPoint.status + generateQueryParamString(params); 30 | 31 | var response = http.get(endpoint); 32 | 33 | return response; 34 | } 35 | 36 | export function GetAvailability() { 37 | if (!apimSubscriptionKey) { 38 | stopIterationOnFail( 39 | "Required environment variable APIM subscription key (apimSubscriptionKey) was not provided", 40 | false 41 | ); 42 | } 43 | 44 | var endpoint = 45 | config.integrationPoint.availability + 46 | "?subscription-key=" + 47 | apimSubscriptionKey; 48 | 49 | var response = http.get(endpoint); 50 | 51 | return response; 52 | } 53 | 54 | export function GetConversation(messageId){ 55 | if (!apimSubscriptionKey) { 56 | stopIterationOnFail( 57 | "Required environment variable APIM subscription key (apimSubscriptionKey) was not provided", 58 | false 59 | ); 60 | } 61 | 62 | var params = { 63 | "subscription-key": apimSubscriptionKey, 64 | messageId: messageId, 65 | }; 66 | 67 | var endpoint = 68 | config.integrationPoint.conversations + generateQueryParamString(params); 69 | 70 | var response = http.get(endpoint); 71 | 72 | return response; 73 | } --------------------------------------------------------------------------------