diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index f6925ba64aa8..1f1f13bd30bb 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -290,7 +290,7 @@ jobs: - name: Prepare docker build targets id: build_map run: | - CHUNKS=$(./scripts/ci/generate-build-chunks.sh docker-express docker-next docker-static docker-cypress) + CHUNKS=$(./scripts/ci/generate-build-chunks.sh docker-express docker-next docker-static docker-cypress docker-jest) echo "CHUNKS: '$CHUNKS'" if [[ $CHUNKS != "[]" ]]; then echo "::set-output name=BUILD_CHUNKS::$CHUNKS" @@ -302,7 +302,7 @@ jobs: env: BASE: 'origin/main' run: | - CHUNKS=$(./scripts/ci/generate-build-chunks.sh docker-express docker-next docker-static docker-cypress) + CHUNKS=$(./scripts/ci/generate-build-chunks.sh docker-express docker-next docker-static docker-cypress docker-jest) echo "CHUNKS: '$CHUNKS'" if [[ $CHUNKS != "[]" ]]; then echo "::set-output name=IMAGES::$(echo $CHUNKS | jq '.[] | fromjson | .projects' -r | tr '\n' ',')" @@ -311,7 +311,7 @@ jobs: - name: Gather unaffected docker images id: unaffected run: | - UNAFFECTED=$(./scripts/ci/list-unaffected.sh docker-next docker-express docker-static docker-cypress) + UNAFFECTED=$(./scripts/ci/list-unaffected.sh docker-next docker-express docker-static docker-cypress docker-jest) echo "::set-output name=UNAFFECTED::$UNAFFECTED" tests: diff --git a/apps/external-contracts-tests/.eslintrc b/apps/external-contracts-tests/.eslintrc new file mode 100644 index 000000000000..ce8022324655 --- /dev/null +++ b/apps/external-contracts-tests/.eslintrc @@ -0,0 +1 @@ +{"extends":"../../.eslintrc","rules":{},"ignorePatterns":["!**/*"]} \ No newline at end of file diff --git a/apps/external-contracts-tests/esbuild.json b/apps/external-contracts-tests/esbuild.json new file mode 100644 index 000000000000..8e334842c701 --- /dev/null +++ b/apps/external-contracts-tests/esbuild.json @@ -0,0 +1,34 @@ +{ + "platform": "node", + "external": [ + "fsevents", + "color-string", + "color-convert", + "@nestjs/microservices", + "class-transformer", + "cache-manager", + "@nestjs/websockets/socket-module", + "class-validator", + "class-transformer", + "@nestjs/microservices/microservices-module", + "apollo-server-fastify", + "@elastic/elasticsearch", + "fastify-swagger", + "@nestjs/mongoose", + "@nestjs/typeorm", + "dd-trace", + "express", + "http-errors", + "graphql", + "winston", + "pg", + "source-map-resolve", + "atob", + "logform", + "pg-native", + "form-data", + "bull", + "pseudomap" + ], + "keepNames": true +} diff --git a/apps/external-contracts-tests/infra/external-contracts-tests.ts b/apps/external-contracts-tests/infra/external-contracts-tests.ts new file mode 100644 index 000000000000..2d252b3e3b60 --- /dev/null +++ b/apps/external-contracts-tests/infra/external-contracts-tests.ts @@ -0,0 +1,31 @@ +import { service, ServiceBuilder } from '../../../infra/src/dsl/dsl' +import { Base, NationalRegistry } from '../../../infra/src/dsl/xroad' +import { settings } from '../../../infra/src/dsl/settings' + +export const serviceSetup = (): ServiceBuilder<'external-contracts-tests'> => { + return service('external-contracts-tests') + .namespace('external-contracts-tests') + .extraAttributes({ + dev: { schedule: '0 11 * * *' }, + staging: { schedule: '0 11 * * *' }, + prod: { schedule: '0 11 * * *' }, + }) + .env({}) + .secrets({ + SOFFIA_SOAP_URL: '/k8s/api/SOFFIA_SOAP_URL', + SOFFIA_HOST_URL: '/k8s/api/SOFFIA_HOST_URL', + SOFFIA_USER: settings.SOFFIA_USER, + SOFFIA_PASS: settings.SOFFIA_PASS, + }) + .resources({ + limits: { + cpu: '1', + memory: '1024Mi', + }, + requests: { + cpu: '500m', + memory: '512Mi', + }, + }) + .xroad(Base, NationalRegistry) +} diff --git a/apps/external-contracts-tests/jest-to-dd.ts b/apps/external-contracts-tests/jest-to-dd.ts new file mode 100644 index 000000000000..e910e6d8c39d --- /dev/null +++ b/apps/external-contracts-tests/jest-to-dd.ts @@ -0,0 +1,33 @@ +import { StatsD } from 'hot-shots' +import { readFileSync } from 'fs' +import { resolve } from 'path' + +// this is not in use yet, but will be part of the next phase in this development + +const client = new StatsD({ mock: true }) +const jestReport = JSON.parse( + readFileSync(resolve(process.argv[2]), { encoding: 'utf-8' }), +) as { + testResults: { + assertionResults: { fullName: string; status: 'passed' | 'failed' }[] + }[] +} +const testCasesInfo = jestReport.testResults.flatMap( + (testResult) => + testResult.assertionResults.map((test) => ({ + name: test.fullName as string, + status: test.status === 'passed' ? 'success' : 'failure', + })) as { name: string; status: 'success' | 'failure' }[], +) +const successfulTests = testCasesInfo + .filter((tc) => tc.status === 'success') + .map((tc) => tc.name) +const failedTests = testCasesInfo + .filter((tc) => tc.status === 'failure') + .map((tc) => tc.name) + +console.log(`Failed test: ${failedTests}`) +console.log(`Successful tests: ${successfulTests}`) + +successfulTests.forEach((tc) => client.check(tc, client.CHECKS.OK)) +failedTests.forEach((tc) => client.check(tc, client.CHECKS.CRITICAL)) diff --git a/apps/external-contracts-tests/jest.config.js b/apps/external-contracts-tests/jest.config.js new file mode 100644 index 000000000000..c1d7b7132bd4 --- /dev/null +++ b/apps/external-contracts-tests/jest.config.js @@ -0,0 +1,15 @@ +module.exports = { + preset: '../../jest.preset.js', + transform: { + '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest', + '^.+\\.[tj]sx?$': 'ts-jest', + }, + globals: { + 'ts-jest': { + tsConfig: '<rootDir>/tsconfig.json', + }, + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'], + displayName: 'external-contracts-tests', + modulePathIgnorePatterns: ['<rootDir>/main.spec.ts'], +} diff --git a/apps/external-contracts-tests/main.spec.ts b/apps/external-contracts-tests/main.spec.ts new file mode 100644 index 000000000000..5afa4e997bf4 --- /dev/null +++ b/apps/external-contracts-tests/main.spec.ts @@ -0,0 +1,3 @@ +import * as testSuites from './test-suites/' + +console.log('Forcing esbuild visit to testSuites:', testSuites) diff --git a/apps/external-contracts-tests/test-suites/index.ts b/apps/external-contracts-tests/test-suites/index.ts new file mode 100644 index 000000000000..1dad1b5e4cc3 --- /dev/null +++ b/apps/external-contracts-tests/test-suites/index.ts @@ -0,0 +1 @@ +export * from './soffia.spec' diff --git a/apps/external-contracts-tests/test-suites/soffia.spec.ts b/apps/external-contracts-tests/test-suites/soffia.spec.ts new file mode 100644 index 000000000000..18d27af719a0 --- /dev/null +++ b/apps/external-contracts-tests/test-suites/soffia.spec.ts @@ -0,0 +1,25 @@ +// NOTE: To run this locally, you'll need to portforward soffia and set +// the environment variable "SOFFIA_SOAP_URL" to https://localhost:8443 +// kubectl port-forward svc/socat-soffia 8443:443 -n socat +import { NationalRegistryApi } from '@island.is/clients/national-registry-v1' + +describe('is.island.external.national', () => { + let client: NationalRegistryApi + beforeAll(async () => { + client = await NationalRegistryApi.instantiateClass({ + baseSoapUrl: process.env.SOFFIA_SOAP_URL!, + user: process.env.SOFFIA_USER!, + password: process.env.SOFFIA_PASS!, + host: process.env.SOFFIA_HOST_URL!, + }) + }) + it('should get user correctly', async () => { + const user = await client.getUser('0101302989') + expect(user.Fulltnafn).toEqual('Gervimaður Ameríka') + }) + it('throws error if user is not found', async () => { + await expect(client.getUser('0123456789')).rejects.toThrow( + 'user with nationalId 0123456789 not found in national Registry', + ) + }) +}) diff --git a/apps/external-contracts-tests/tsconfig.json b/apps/external-contracts-tests/tsconfig.json new file mode 100644 index 000000000000..a549d92b875b --- /dev/null +++ b/apps/external-contracts-tests/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + } +} diff --git a/charts/islandis/values.dev.yaml b/charts/islandis/values.dev.yaml index af4659c1f6d7..dbe829be354a 100644 --- a/charts/islandis/values.dev.yaml +++ b/charts/islandis/values.dev.yaml @@ -630,6 +630,54 @@ endorsement-system-api: eks.amazonaws.com/role-arn: 'arn:aws:iam::013313053092:role/endorsement-system-api' create: true name: 'endorsement-system-api' +external-contracts-tests: + enabled: true + env: + NODE_OPTIONS: '--max-old-space-size=976' + SERVERSIDE_FEATURES_ON: '' + XROAD_BASE_PATH: 'http://securityserver.dev01.devland.is' + XROAD_BASE_PATH_WITH_ENV: 'http://securityserver.dev01.devland.is/r1/IS-DEV' + XROAD_NATIONAL_REGISTRY_REDIS_NODES: '["clustercfg.general-redis-cluster-group.5fzau3.euw1.cache.amazonaws.com:6379"]' + XROAD_NATIONAL_REGISTRY_SERVICE_PATH: 'IS-DEV/GOV/10001/SKRA-Protected/Einstaklingar-v1' + XROAD_TJODSKRA_API_PATH: '/SKRA-Protected/Einstaklingar-v1' + XROAD_TJODSKRA_MEMBER_CODE: '10001' + XROAD_TLS_BASE_PATH: 'https://securityserver.dev01.devland.is' + XROAD_TLS_BASE_PATH_WITH_ENV: 'https://securityserver.dev01.devland.is/r1/IS-DEV' + grantNamespaces: [] + grantNamespacesEnabled: false + healthCheck: + liveness: + initialDelaySeconds: 3 + path: '/' + timeoutSeconds: 3 + readiness: + initialDelaySeconds: 3 + path: '/' + timeoutSeconds: 3 + image: + repository: '821090935708.dkr.ecr.eu-west-1.amazonaws.com/external-contracts-tests' + namespace: 'external-contracts-tests' + replicaCount: + default: 2 + max: 3 + min: 2 + resources: + limits: + cpu: '1' + memory: '1024Mi' + requests: + cpu: '500m' + memory: '512Mi' + schedule: '0 11 * * *' + secrets: + CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY' + SOFFIA_HOST_URL: '/k8s/api/SOFFIA_HOST_URL' + SOFFIA_PASS: '/k8s/service-portal/SOFFIA_PASS' + SOFFIA_SOAP_URL: '/k8s/api/SOFFIA_SOAP_URL' + SOFFIA_USER: '/k8s/service-portal/SOFFIA_USER' + securityContext: + allowPrivilegeEscalation: false + privileged: false github-actions-cache: args: - '--tls-min-v1.0' @@ -807,6 +855,7 @@ namespaces: - 'air-discount-scheme' - 'github-actions-cache' - 'user-notification' + - 'external-contracts-tests' search-indexer-service: enabled: true env: diff --git a/infra/src/uber-charts/islandis.ts b/infra/src/uber-charts/islandis.ts index b3cf5981c0f6..39cef5a0736c 100644 --- a/infra/src/uber-charts/islandis.ts +++ b/infra/src/uber-charts/islandis.ts @@ -32,6 +32,8 @@ import { serviceSetup as adsApiSetup } from '../../../apps/air-discount-scheme/a import { serviceSetup as adsWebSetup } from '../../../apps/air-discount-scheme/web/infra/web' import { serviceSetup as adsBackendSetup } from '../../../apps/air-discount-scheme/backend/infra/backend' +import { serviceSetup as externalContractsTestsSetup } from '../../../apps/external-contracts-tests/infra/external-contracts-tests' + import { EnvironmentServices } from '.././dsl/types/charts' const endorsement = endorsementServiceSetup({}) @@ -74,6 +76,8 @@ const adsApi = adsApiSetup({ adsBackend }) const adsWeb = adsWebSetup({ adsApi }) const githubActionsCache = githubActionsCacheSetup() +const externalContractsTests = externalContractsTestsSetup() + export const Services: EnvironmentServices = { prod: [ appSystemApi, @@ -142,6 +146,7 @@ export const Services: EnvironmentServices = { githubActionsCache, userNotificationService, userNotificationWorkerService, + externalContractsTests, ], } diff --git a/nx.json b/nx.json index 44ad46d96d46..1b626d29aad5 100644 --- a/nx.json +++ b/nx.json @@ -386,6 +386,9 @@ "email-service": { "tags": [] }, + "external-contracts-tests": { + "tags": [] + }, "feature-flags": { "tags": [] }, diff --git a/package.json b/package.json index 4c81f4c22db6..4617e77ec675 100644 --- a/package.json +++ b/package.json @@ -161,6 +161,7 @@ "graphql-type-json": "0.3.2", "handlebars": "4.7.7", "http-cache-semantics": "4.1.0", + "hot-shots": "9.0.0", "http-proxy-middleware": "0.19.1", "hypher": "0.2.5", "identicon.js": "2.3.3", diff --git a/scripts/ci/90_docker-jest.sh b/scripts/ci/90_docker-jest.sh new file mode 100755 index 000000000000..90ffe36b9bfa --- /dev/null +++ b/scripts/ci/90_docker-jest.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -euxo pipefail + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +# shellcheck disable=SC1091 +source "$DIR"/_common.sh + +# Building Docker images for jest tests +exec "$DIR"/_docker.sh Dockerfile output-jest diff --git a/scripts/ci/Dockerfile b/scripts/ci/Dockerfile index c5a945d57fa9..7d00522e5fc9 100644 --- a/scripts/ci/Dockerfile +++ b/scripts/ci/Dockerfile @@ -39,26 +39,26 @@ ENV NODE_ENV=production WORKDIR /webapp +# Adding user for running the app +RUN addgroup runners && adduser -D runner -G runners + +FROM output-base as output-base-with-pg + RUN npm install -g \ sequelize \ sequelize-cli \ pg - # npx - # logform \ - # dd-trace -# Adding user for running the app -RUN addgroup runners && adduser -D runner -G runners USER runner -FROM output-base as output-express +FROM output-base-with-pg as output-express COPY --from=builder /build/${APP_DIST_HOME} /webapp/ ENTRYPOINT [] CMD [ "node", "main.js" ] -FROM output-base as output-next +FROM output-base-with-pg as output-next ENV PORT=4200 @@ -91,3 +91,15 @@ RUN apk update && \ ADD scripts/dockerfile-assets/nginx/* /etc/nginx/templates ADD scripts/dockerfile-assets/bash/extract-environment.sh /docker-entrypoint.d COPY --from=builder /build/${APP_DIST_HOME} /usr/share/nginx/html + +FROM output-base as output-jest + +RUN echo 'module.exports = {};' > jest.config.js + +RUN npm install -g jest + +COPY --from=builder /build/${APP_DIST_HOME} /webapp/ + +USER runner + +CMD [ "jest", "main.spec.js" ] \ No newline at end of file diff --git a/workspace.json b/workspace.json index 54502103e5ad..7c64bd088762 100644 --- a/workspace.json +++ b/workspace.json @@ -3993,6 +3993,51 @@ } } }, + "external-contracts-tests": { + "root": "apps/external-contracts-tests", + "sourceRoot": "apps/external-contracts-tests/src", + "projectType": "application", + "prefix": "external-contracts-tests", + "schematics": {}, + "architect": { + "lint": { + "builder": "@nrwl/linter:lint", + "options": { + "linter": "eslint", + "tsConfig": ["apps/external-contracts-tests/tsconfig.json"], + "exclude": [ + "**/node_modules/**", + "!apps/external-contracts-tests/**/*" + ] + } + }, + "build": { + "builder": "@irdnis/esbuildnx:build", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/apps/external-contracts-tests", + "main": "apps/external-contracts-tests/main.spec.ts", + "tsConfig": "apps/external-contracts-tests/tsconfig.json" + }, + "configurations": { + "production": { + "optimization": true, + "extractLicenses": true, + "inspect": false + } + } + }, + "external-test": { + "builder": "@nrwl/jest:jest", + "options": { + "jestConfig": "apps/external-contracts-tests/jest.config.js", + "passWithNoTests": true + }, + "outputs": ["coverage/apps/external-contracts-tests"] + }, + "docker-jest": {} + } + }, "feature-flags": { "root": "libs/feature-flags", "sourceRoot": "libs/feature-flags/src", diff --git a/yarn.lock b/yarn.lock index 6fb893b0608a..b24cb1afdb1b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10746,7 +10746,7 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -bindings@^1.5.0: +bindings@^1.3.0, bindings@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== @@ -17171,6 +17171,13 @@ hosted-git-info@3.0.8, hosted-git-info@^2.1.4, hosted-git-info@^3.0.6, hosted-gi dependencies: lru-cache "^6.0.0" +hot-shots@9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/hot-shots/-/hot-shots-9.0.0.tgz#b88421aa81c1ef37f4cc53ade7f1e3daaa584b5e" + integrity sha512-fpljto22PvfzDGznnzUpWgFMgccyZRtWo+fY8R4ktwipkv3ywDyeaTLijYaM629DEZvtnEDAN00yV6zxzivnpQ== + optionalDependencies: + unix-dgram "2.0.x" + hpack.js@^2.1.6: version "2.1.6" resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" @@ -29055,6 +29062,14 @@ universalify@^2.0.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== +unix-dgram@2.0.x: + version "2.0.4" + resolved "https://registry.yarnpkg.com/unix-dgram/-/unix-dgram-2.0.4.tgz#14d4fc21e539742b8fb027de16eccd4e5503a344" + integrity sha512-7tpK6x7ls7J7pDrrAU63h93R0dVhRbPwiRRCawR10cl+2e1VOvF3bHlVJc6WI1dl/8qk5He673QU+Ogv7bPNaw== + dependencies: + bindings "^1.3.0" + nan "^2.13.2" + unixify@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unixify/-/unixify-1.0.0.tgz#3a641c8c2ffbce4da683a5c70f03a462940c2090"