diff --git a/.github/issue_template/release-tracking.md b/.github/issue_template/release-tracking.md new file mode 100644 index 0000000..cd62019 --- /dev/null +++ b/.github/issue_template/release-tracking.md @@ -0,0 +1,17 @@ +--- +name: New Release +about: Tracking a new, published release +title: 'New Release: ' +labels: '' +assignees: 'jeremytwfortune' + +--- + +## Description + +_Include a brief description of the release, including any new features, bug fixes, or breaking changes._ + +## Checklist + +- [ ] Ensure E2E tests pass by manually running the testing workflow with `e2e` against the `main` branch. +- [ ] Compile relevant changes and make it part of the description above. diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 6de6a7b..f270563 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -10,6 +10,8 @@ jobs: test: name: Test uses: ./.github/workflows/test.yml + with: + suite: e2e secrets: ORCHESTRATE_API_KEY: ${{ secrets.ORCHESTRATE_API_KEY }} ORCHESTRATE_IDENTITY_API_KEY: ${{ secrets.ORCHESTRATE_IDENTITY_API_KEY }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1b4b964..ff32de2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,7 +5,20 @@ on: branches: - main pull_request: + workflow_dispatch: + inputs: + suite: + required: true + description: "The test suite name: e2e, default" + default: "default" + type: string workflow_call: + inputs: + suite: + required: true + description: "The test suite name: e2e, default" + default: "default" + type: string secrets: ORCHESTRATE_API_KEY: required: true @@ -22,7 +35,7 @@ jobs: runs-on: ubuntu-latest strategy: fail-fast: true - max-parallel: 1 + max-parallel: ${{ (inputs.suite || 'default') == 'e2e' && 1 || 4 }} matrix: node-version: ["18", "20"] steps: @@ -44,12 +57,19 @@ jobs: - name: Install dependencies working-directory: typescript run: npm ci + - name: Default Tests + if: ${{ (inputs.suite || 'default') == 'default' }} + working-directory: typescript + shell: pwsh + run: npm run test - name: Run Local Hashing + if: ${{ (inputs.suite || 'default') == 'e2e' }} run: docker run -d -p 7002:7002 -e HASH__KEY=${{ secrets.TESTING_HASH_KEY }} --cap-drop ALL careevolution/bmpi-hashing-service:latest - - name: Run tests + - name: E2E Tests + if: ${{ (inputs.suite || 'default') == 'e2e' }} working-directory: typescript shell: pwsh - run: npm run test + run: npm run test:e2e python: name: Python @@ -58,7 +78,7 @@ jobs: - typescript strategy: fail-fast: true - max-parallel: 1 + max-parallel: ${{ (inputs.suite || 'default') == 'e2e' && 1 || 4 }} matrix: python-version: ["3.9", "3.10", "3.11", "3.12"] steps: @@ -97,7 +117,8 @@ jobs: working-directory: python run: poetry run black --check . - name: Run Local Hashing + if: ${{ (inputs.suite || 'default') == 'e2e' }} run: docker run -d -p 7002:7002 -e HASH__KEY=${{ secrets.TESTING_HASH_KEY }} --cap-drop ALL careevolution/bmpi-hashing-service:latest - name: Test working-directory: python - run: poetry run pytest + run: poetry run pytest -m "${{ (inputs.suite || 'default') }}" diff --git a/python/tests/identity/test_api.py b/python/tests/identity/test_api.py index dab840c..baced44 100644 --- a/python/tests/identity/test_api.py +++ b/python/tests/identity/test_api.py @@ -12,6 +12,8 @@ HashDemographicResponse, ) +pytestmark = pytest.mark.e2e + load_dotenv(override=True) _TEST_API = IdentityApi() diff --git a/python/tests/identity/test_demographic.py b/python/tests/identity/test_demographic.py index d267175..a697b3d 100644 --- a/python/tests/identity/test_demographic.py +++ b/python/tests/identity/test_demographic.py @@ -3,6 +3,9 @@ demographic_from_dict, Demographic, ) +import pytest + +pytestmark = pytest.mark.e2e def test_demographic_to_dict_should_camel_case(): diff --git a/python/tests/identity/test_local_hashing.py b/python/tests/identity/test_local_hashing.py index 6a11f8c..3a191ea 100644 --- a/python/tests/identity/test_local_hashing.py +++ b/python/tests/identity/test_local_hashing.py @@ -3,6 +3,11 @@ from orchestrate._internal.identity.local_hashing import LocalHashingApi from dotenv import load_dotenv +import pytest + +pytestmark = pytest.mark.e2e + + load_dotenv(Path(__file__).parent.parent.parent.parent / ".env", override=True) _TEST_API = LocalHashingApi() diff --git a/python/tests/test_api.py b/python/tests/test_api.py index 68a8212..a69139d 100644 --- a/python/tests/test_api.py +++ b/python/tests/test_api.py @@ -1,18 +1,13 @@ from io import BytesIO import json -import os from pathlib import Path from zipfile import ZipFile import pytest from dotenv import load_dotenv from orchestrate import OrchestrateApi -from requests import HTTPError -from orchestrate._internal.exceptions import ( - OrchestrateClientError, - OrchestrateHttpError, -) +from orchestrate._internal.exceptions import OrchestrateHttpError from .data import ( CDA, DSTU2_BUNDLE, @@ -30,6 +25,8 @@ StandardizeRequest, ) +pytestmark = [pytest.mark.e2e, pytest.mark.default] + def setup_test_api(): load_dotenv(Path(__file__).parent.parent.parent / ".env", override=True) diff --git a/python/tests/test_http_handler.py b/python/tests/test_http_handler.py index 3e6824b..79f1b90 100644 --- a/python/tests/test_http_handler.py +++ b/python/tests/test_http_handler.py @@ -6,6 +6,8 @@ from orchestrate._internal.http_handler import HttpHandler, create_http_handler from dotenv import dotenv_values +pytestmark = [pytest.mark.e2e, pytest.mark.default] + def test_api_base_url_in_environment_should_use_environment(monkeypatch): mock_http_handler = Mock(HttpHandler) diff --git a/typescript/package.json b/typescript/package.json index dc82103..f0118d9 100644 --- a/typescript/package.json +++ b/typescript/package.json @@ -10,7 +10,8 @@ "test": "vitest run", "test:watch": "vitest", "fmt": "prettier --check .", - "fmt:fix": "prettier --write ." + "fmt:fix": "prettier --write .", + "test:e2e": "vitest -c vitest.config.e2e.js" }, "author": "CareEvolution", "license": "Apache-2.0", diff --git a/typescript/tests/httpHandler.test.ts b/typescript/tests/httpHandler.test.ts index d7d9a67..56a8ec1 100644 --- a/typescript/tests/httpHandler.test.ts +++ b/typescript/tests/httpHandler.test.ts @@ -12,7 +12,7 @@ class OutcomeTestCase { id: string; } -describe("httpHandler outcomes", () => { +describe.concurrent("httpHandler outcomes", () => { const testCases: OutcomeTestCase[] = [ { contentType: "application/json", diff --git a/typescript/tests/identity/api.test.ts b/typescript/tests/identity/api.test.ts index cf7d9fc..8c01a88 100644 --- a/typescript/tests/identity/api.test.ts +++ b/typescript/tests/identity/api.test.ts @@ -70,8 +70,8 @@ async function createRandomRecord(): Promise<{ person: Person; identifier: strin return { person, identifier }; } -describe("Identity API addOrUpdateRecord", () => { - it("should add a new record", async () => { +describe("Identity API", () => { + it("addOrUpdateRecord should add a new record", async () => { const addResponse = await identityApi.addOrUpdateRecord({ source: defaultSource, identifier: Math.random().toString(36).substring(7), @@ -82,7 +82,7 @@ describe("Identity API addOrUpdateRecord", () => { expect(addResponse.matchedPerson.id).toBeDefined(); }); - it("with url unsafe identifier should add record", async () => { + it("addOrUpdateRecord with url unsafe identifier should add record", async () => { const addResponse = await identityApi.addOrUpdateRecord({ source: defaultSource, identifier: Math.random().toString(36).substring(7) + "+/=", @@ -92,10 +92,8 @@ describe("Identity API addOrUpdateRecord", () => { expect(addResponse.matchedPerson).toBeDefined(); expect(addResponse.matchedPerson.id).toBeDefined(); }); -}); -describe("Identity API addOrUpdateBlindedRecord", () => { - it("should add a new record", async () => { + it("addOrUpdateBlindedRecord should add a new record", async () => { const addResponse = await identityApi.addOrUpdateBlindedRecord({ source: defaultSource, identifier: Math.random().toString(36).substring(7), @@ -106,7 +104,7 @@ describe("Identity API addOrUpdateBlindedRecord", () => { expect(addResponse.matchedPerson.id).toBeDefined(); }); - it("with url unsafe identifier should add record", async () => { + it("addOrUpdateBlindedRecord with url unsafe identifier should add record", async () => { const addResponse = await identityApi.addOrUpdateBlindedRecord({ source: defaultSource, identifier: Math.random().toString(36).substring(7), @@ -116,10 +114,8 @@ describe("Identity API addOrUpdateBlindedRecord", () => { expect(addResponse.matchedPerson).toBeDefined(); expect(addResponse.matchedPerson.id).toBeDefined(); }); -}); -describe("Identity API getPersonByRecord", () => { - it("should get a person by record", async () => { + it("getPersonByRecord should get a person by record", async () => { const { person, identifier } = await createRandomRecord(); const getResponse = await identityApi.getPersonByRecord({ @@ -129,10 +125,8 @@ describe("Identity API getPersonByRecord", () => { expect(getResponse).toEqual(person); }); -}); -describe("Identity API getPersonByID", () => { - it("should get a person by ID", async () => { + it("getPersonByID should get a person by ID", async () => { const { person } = await createRandomRecord(); const getResponse = await identityApi.getPersonByID({ @@ -141,26 +135,20 @@ describe("Identity API getPersonByID", () => { expect(getResponse.id).toEqual(person.id); }); -}); -describe("Identity API matchDemographics", () => { - it("should match demographics", async () => { + it("matchDemographics should match demographics", async () => { const matchResponse = await identityApi.matchDemographics(demographic); expect(matchResponse).toBeDefined(); }); -}); -describe("Identity API matchBlindedDemographics", () => { - it("should match blinded demographics", async () => { + it("matchBlindedDemographics should match blinded demographics", async () => { const matchResponse = await identityApi.matchBlindedDemographics(blindedDemographic); expect(matchResponse).toBeDefined(); }); -}); -describe("Identity API deleteRecord", () => { - it("should delete a record", async () => { + it("deleteRecord should delete a record", async () => { const { person, identifier } = await createRandomRecord(); const deleteResponse = await identityApi.deleteRecord({ @@ -170,10 +158,8 @@ describe("Identity API deleteRecord", () => { expect(deleteResponse.changedPersons.map(changedPerson => changedPerson.id)).toContain(person.id); }); -}); -describe("Identity API addMatchGuidance", () => { - it("should add match guidance", async () => { + it("addMatchGuidance should add match guidance", async () => { const { person: firstPerson, identifier: firstIdentifier } = await createRandomRecord(); const { person: secondPerson, identifier: secondIdentifier } = await createRandomRecord(); @@ -187,10 +173,8 @@ describe("Identity API addMatchGuidance", () => { expect(addMatchGuidanceResponse.changedPersons.map(changedPerson => changedPerson.id)).toContain(firstPerson.id); expect(addMatchGuidanceResponse.changedPersons.map(changedPerson => changedPerson.id)).toContain(secondPerson.id); }); -}); -describe("Identity API removeMatchGuidance", () => { - it("should remove match guidance", async () => { + it("removeMatchGuidance should remove match guidance", async () => { const { person: firstPerson, identifier: firstIdentifier } = await createRandomRecord(); const { person: secondPerson, identifier: secondIdentifier } = await createRandomRecord(); const recordOne = { source: defaultSource, identifier: firstIdentifier }; diff --git a/typescript/tests/identity/localHashing.test.ts b/typescript/tests/identity/localHashing.test.ts index 99e8d17..6dd909d 100644 --- a/typescript/tests/identity/localHashing.test.ts +++ b/typescript/tests/identity/localHashing.test.ts @@ -13,7 +13,7 @@ const demographic: Demographic = { gender: "male", }; -describe("LocalHashingApi hash", () => { +describe.concurrent("LocalHashingApi", () => { it("should hash by demographic", async () => { const result = await localHashingApi.hash(demographic); @@ -30,9 +30,7 @@ describe("LocalHashingApi hash", () => { expect(result.version).greaterThan(0); expect(result.advisories.invalidDemographicFields).toEqual(["dob"]); }); -}); -describe("LocalHashingApi constructor", () => { it("should build from passed url", () => { const localHashingApi = new LocalHashingApi({ url: "http://localhost:8080" }); diff --git a/typescript/vitest.config.e2e.js b/typescript/vitest.config.e2e.js new file mode 100644 index 0000000..b50027c --- /dev/null +++ b/typescript/vitest.config.e2e.js @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + testTimeout: 300000, + hookTimeout: 300000, + }, +}); diff --git a/typescript/vitest.config.js b/typescript/vitest.config.js index b50027c..2875667 100644 --- a/typescript/vitest.config.js +++ b/typescript/vitest.config.js @@ -2,7 +2,6 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { - testTimeout: 300000, - hookTimeout: 300000, + exclude: ["tests/identity/*.test.ts", "node_modules/**/*"] }, });