diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 253690e..146f3af 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -84,6 +84,41 @@ jobs: path: examples/with-${{ matrix.runtime }}/.nmt/dist retention-days: 1 + e2e-local: + runs-on: ubuntu-latest + needs: + - build + - get-matrix + timeout-minutes: 10 + strategy: + matrix: + include: ${{fromJson(needs.get-matrix.outputs.matrix)}} + + steps: + - name: Download Artifact + uses: actions/download-artifact@v4 + with: + name: ${{ matrix.resource_identifier_key }} + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v1 + + - name: Use Node.js ${{ matrix.version }} + if: matrix.runtime == 'node' + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.version }} + + - uses: pnpm/action-setup@v3 + name: Install pnpm + with: + version: ${{ env.pnpm_version }} + - run: pnpm install + + - name: Build the project + run: pnpm exec nx run @infra/azure-functions:test:e2e-local + env: + RUNTIME: ${{ matrix.runtime }} + deploy: runs-on: ubuntu-latest needs: @@ -111,7 +146,7 @@ jobs: app-name: nmt-e2e-${{ matrix.target }}-${{ secrets[matrix.resource_identifier_key] }} package: . - e2e: + e2e-azure: if: always() runs-on: ubuntu-latest needs: @@ -140,7 +175,7 @@ jobs: - run: pnpm install - name: Run E2E tests - run: pnpm exec nx run @infra/azure-functions:test + run: pnpm exec nx run @infra/azure-functions:test:e2e-remote env: AZURE_FUNCTIONS_URL: https://nmt-e2e-${{ matrix.target }}-${{ secrets[matrix.resource_identifier_key] }}.azurewebsites.net AZURE_FUNCTIONS_API_KEY: ${{ secrets.AZURE_FUNCTIONS_HOST_KEY }} diff --git a/infra/azure-functions/package.json b/infra/azure-functions/package.json index dd202e0..8dfc204 100644 --- a/infra/azure-functions/package.json +++ b/infra/azure-functions/package.json @@ -6,13 +6,15 @@ "test-os": "tsx src/test-os.ts", "apply": "bun run src/main.ts", "plan": "PLAN_MODE=true bun run src/main.ts", - "test": "bun test --timeout 120000", + "test:e2e-remote": "bun test --timeout 120000 -t e2e-remote", + "test:e2e-local": "bun test --timeout 120000 -t e2e-local", "github-actions": "bun run src/github-actions.ts", "lint": "tsc --noEmit && eslint ./src && prettier -c src", "lint:fix": "eslint --fix ./src && prettier -w src" }, "dependencies": { "@actions/core": "^1.10.1", + "execa": "^9.1.0", "nammatham": "workspace:*" }, "devDependencies": { diff --git a/infra/azure-functions/src/e2e-local.test.ts b/infra/azure-functions/src/e2e-local.test.ts new file mode 100644 index 0000000..1aef31e --- /dev/null +++ b/infra/azure-functions/src/e2e-local.test.ts @@ -0,0 +1,57 @@ +import { test, expect, describe, beforeAll, afterAll } from 'bun:test'; +import { execa, type ResultPromise } from 'execa'; + +const url = 'http://localhost:7071'; +const healthPath = '/api/SimpleHttpTrigger'; + +function waitForServer(url: string) { + return new Promise((resolve, reject) => { + const checkServer = async () => { + try { + const response = await fetch(url); + if (response.ok) { + resolve(); + } else { + setTimeout(checkServer, 1000); + } + } catch (error) { + setTimeout(checkServer, 1000); + } + }; + checkServer(); + }); +} + +describe('e2e-local', () => { + let serverProcess: + | ResultPromise<{ + stdout: 'inherit'; + killSignal: 'SIGKILL'; + }> + | undefined; + let serverLogs = ''; + + beforeAll(async () => { + // Place Azure Functions into the current directory + serverProcess = execa({ stdout: 'inherit', killSignal: 'SIGKILL' })`func start --verbose`; + // Wait for the server to be ready + await waitForServer(new URL(healthPath, url).toString()); + }); + + afterAll(async () => { + if (serverProcess) { + console.log('Killing server process'); + serverProcess.kill(); + } + }); + + test('should return 200 OK', async () => { + try { + const response = await fetch(new URL('/api/SimpleHttpTrigger', url).toString()); + expect(response.status).toBe(200); + } catch (error) { + console.error(serverLogs); // Print server logs on failure + throw new Error('Server did not respond with 200 OK'); + } + }); +}); diff --git a/infra/azure-functions/src/e2e-remote.test.ts b/infra/azure-functions/src/e2e-remote.test.ts new file mode 100644 index 0000000..ebd54a7 --- /dev/null +++ b/infra/azure-functions/src/e2e-remote.test.ts @@ -0,0 +1,23 @@ +import { test, expect, describe, beforeAll } from 'bun:test'; + +describe('e2e-remote', () => { + + let url: string | undefined; + let apiKey: string | undefined; + + beforeAll(() => { + url = process.env.AZURE_FUNCTIONS_URL; + apiKey = process.env.AZURE_FUNCTIONS_API_KEY; + if (!url) throw new Error('AZURE_FUNCTIONS_URL not set'); + if (!apiKey) throw new Error('AZURE_FUNCTIONS_API_KEY not set'); + console.log(`Testing remote Azure Functions at ${url}`); + }); + + test('should return 200 OK', async () => { + const response = await fetch(new URL(`/api/SimpleHttpTrigger?code=${apiKey}`, url).toString(), { + method: 'GET', + }); + + expect(response.status).toBe(200); + }); +}); diff --git a/infra/azure-functions/src/e2e.test.ts b/infra/azure-functions/src/e2e.test.ts deleted file mode 100644 index a7d6eb2..0000000 --- a/infra/azure-functions/src/e2e.test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { test, expect } from 'bun:test'; -// import supertest from 'supertest'; - -const url = process.env.AZURE_FUNCTIONS_URL; -const apiKey = process.env.AZURE_FUNCTIONS_API_KEY; -if (!url) throw new Error('AZURE_FUNCTIONS_URL not set'); -if (!apiKey) throw new Error('AZURE_FUNCTIONS_API_KEY not set'); - -test('e2e', async () => { - // const response = await supertest(url).get(`/api/SimpleHttpTrigger?code=${apiKey}`); - const response = await fetch(new URL(`/api/SimpleHttpTrigger?code=${apiKey}`, url).toString(), { - method: 'GET', - }); - expect(response.status).toBe(200); -}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a29e682..665dff9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -118,6 +118,9 @@ importers: '@actions/core': specifier: ^1.10.1 version: 1.10.1 + execa: + specifier: ^9.1.0 + version: 9.1.0 nammatham: specifier: workspace:* version: link:../../packages/main @@ -1124,7 +1127,6 @@ packages: /@sec-ant/readable-stream@0.4.1: resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} - dev: true /@sinclair/typebox@0.27.8: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} @@ -1133,7 +1135,6 @@ packages: /@sindresorhus/merge-streams@4.0.0: resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} - dev: true /@types/bun@1.1.2: resolution: {integrity: sha512-pRBDD3EDqPf83qe95i3EpYu5G2J8bbb78a3736vnCm2K8YWtEE5cvJUq2jkKvJhW07YTfQtbImywIwRhWL8z3Q==} @@ -2525,6 +2526,24 @@ packages: yoctocolors: 2.0.0 dev: true + /execa@9.1.0: + resolution: {integrity: sha512-lSgHc4Elo2m6bUDhc3Hl/VxvUDJdQWI40RZ4KMY9bKRc+hgMOT7II/JjbNDhI8VnMtrCb7U/fhpJIkLORZozWw==} + engines: {node: '>=18'} + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + cross-spawn: 7.0.3 + figures: 6.1.0 + get-stream: 9.0.1 + human-signals: 7.0.0 + is-plain-obj: 4.1.0 + is-stream: 4.0.1 + npm-run-path: 5.2.0 + pretty-ms: 9.0.0 + signal-exit: 4.1.0 + strip-final-newline: 4.0.0 + yoctocolors: 2.0.0 + dev: false + /expand-template@2.0.3: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} @@ -2587,7 +2606,6 @@ packages: engines: {node: '>=18'} dependencies: is-unicode-supported: 2.0.0 - dev: true /file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} @@ -2770,7 +2788,6 @@ packages: dependencies: '@sec-ant/readable-stream': 0.4.1 is-stream: 4.0.1 - dev: true /get-symbol-description@1.0.0: resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} @@ -2961,7 +2978,6 @@ packages: /human-signals@7.0.0: resolution: {integrity: sha512-74kytxOUSvNbjrT9KisAbaTZ/eJwD/LrbM/kh5j0IhPuJzwuA19dWvniFGwBzN9rVjg+O/e+F310PjObDXS+9Q==} engines: {node: '>=18.18.0'} - dev: true /iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} @@ -3136,7 +3152,6 @@ packages: /is-plain-obj@4.1.0: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} - dev: true /is-regex@1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} @@ -3164,7 +3179,6 @@ packages: /is-stream@4.0.1: resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} engines: {node: '>=18'} - dev: true /is-string@1.0.7: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} @@ -3195,7 +3209,6 @@ packages: /is-unicode-supported@2.0.0: resolution: {integrity: sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q==} engines: {node: '>=18'} - dev: true /is-weakref@1.0.2: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} @@ -3891,7 +3904,6 @@ packages: /parse-ms@4.0.0: resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} engines: {node: '>=18'} - dev: true /path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} @@ -4158,7 +4170,6 @@ packages: engines: {node: '>=18'} dependencies: parse-ms: 4.0.0 - dev: true /process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -4707,7 +4718,6 @@ packages: /strip-final-newline@4.0.0: resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} engines: {node: '>=18'} - dev: true /strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} @@ -5433,4 +5443,3 @@ packages: /yoctocolors@2.0.0: resolution: {integrity: sha512-esbDnt0Z1zI1KgvOZU90hJbL6BkoUbrP9yy7ArNZ6TmxBxydMJTYMf9FZjmwwcA8ZgEQzriQ3hwZ0NYXhlFo8Q==} engines: {node: '>=18'} - dev: true diff --git a/vitest.config.ts b/vitest.config.ts index 6662083..ecf174a 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -4,6 +4,10 @@ import { configDefaults, defineConfig } from 'vitest/config' export default defineConfig({ test: { + exclude: [ + ...configDefaults.exclude, + 'infra/**', + ], coverage: { exclude: [ ...configDefaults.coverage.exclude ?? [],