From f69b5a1d59726fd8db10a5b2224659a73ea3f474 Mon Sep 17 00:00:00 2001 From: aehnh Date: Tue, 21 Jan 2025 19:41:14 +0100 Subject: [PATCH 1/2] webhook tests --- .github/workflows/e2e-test-commitly.yml | 3 + package-lock.json | 345 +++++++++++++++++- packages/tests-e2e/.env.connect.ci | 2 +- packages/tests-e2e/.env.connect.example | 4 +- packages/tests-e2e/package.json | 1 + .../tests-e2e/src/connect/models/BaseModel.ts | 22 +- .../src/connect/models/SignupModel.ts | 2 +- .../src/connect/models/WebhookModel.ts | 102 ++++++ .../src/connect/scenarios/append.spec.ts | 22 +- .../tests-e2e/src/connect/scenarios/hooks.ts | 26 +- .../src/connect/scenarios/login.spec.ts | 24 +- .../connect/scenarios/passkey-list.spec.ts | 23 +- .../tests-e2e/src/connect/utils/Constants.ts | 6 + 13 files changed, 534 insertions(+), 48 deletions(-) create mode 100644 packages/tests-e2e/src/connect/models/WebhookModel.ts diff --git a/.github/workflows/e2e-test-commitly.yml b/.github/workflows/e2e-test-commitly.yml index ccfc611ca..32cecd224 100644 --- a/.github/workflows/e2e-test-commitly.yml +++ b/.github/workflows/e2e-test-commitly.yml @@ -97,6 +97,9 @@ jobs: PLAYWRIGHT_TEST_URL=$PLAYWRIGHT_TEST_URL npx playwright test --config=playwright.config.connect.ts env: PLAYWRIGHT_NUM_CORES: 4 + BACKEND_API_BASIC_AUTH: ${{ secrets.BACKEND_API_BASIC_AUTH }} + PLAYWRIGHT_CONNECT_PROJECT_ID: ${{ secrets.PLAYWRIGHT_CONNECT_PROJECT_ID }} + PLAYWRIGHT_NGROK_AUTH_TOKEN: ${{ secrets.PLAYWRIGHT_NGROK_AUTH_TOKEN }} GITHUB_RUN_ID: ${{ github.run_id }} SLACK_BOT_USER_OAUTH_TOKEN: ${{ secrets.SLACK_BOT_USER_OAUTH_TOKEN }} GITHUB_BRANCH_NAME: ${{ env.BRANCH_NAME_RAW }} diff --git a/package-lock.json b/package-lock.json index 25c01a4f8..12822852f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6589,6 +6589,19 @@ "dev": true, "license": "MIT" }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, "node_modules/@sinonjs/commons": { "version": "1.8.6", "license": "BSD-3-Clause", @@ -7605,6 +7618,19 @@ "tslib": "^2.4.0" } }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@tailwindcss/forms": { "version": "0.5.7", "dev": true, @@ -7929,6 +7955,19 @@ "@types/node": "*" } }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, "node_modules/@types/co-body": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/@types/co-body/-/co-body-6.1.3.tgz", @@ -7984,6 +8023,13 @@ "@types/node": "*" } }, + "node_modules/@types/crypto-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz", + "integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/debounce": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@types/debounce/-/debounce-1.2.4.tgz", @@ -8047,6 +8093,13 @@ "integrity": "sha512-4+tE/lwdAahgZT1g30Jkdm9PzFRde0xwxBNUyRsCitRvCQB90iuA2uJYdUnhnANRcqGXaWOGY4FEoxeElNAK2g==", "license": "MIT" }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/http-errors": { "version": "2.0.4", "license": "MIT" @@ -8143,6 +8196,16 @@ "integrity": "sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==", "license": "MIT" }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/koa": { "version": "2.15.0", "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.15.0.tgz", @@ -8263,6 +8326,16 @@ "@types/node": "*" } }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/retry": { "version": "0.12.0", "license": "MIT" @@ -10592,6 +10665,51 @@ "node": ">= 6.0.0" } }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/call-bind": { "version": "1.0.5", "license": "MIT", @@ -10967,6 +11085,19 @@ "node": ">=0.10.0" } }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cmd-shim": { "version": "6.0.3", "dev": true, @@ -11603,6 +11734,12 @@ "node": ">= 8" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, "node_modules/crypto-random-string": { "version": "2.0.0", "license": "MIT", @@ -12099,6 +12236,35 @@ "version": "10.4.3", "license": "MIT" }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/dedent": { "version": "0.7.0", "license": "MIT" @@ -12166,6 +12332,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/define-data-property": { "version": "1.1.1", "license": "MIT", @@ -15022,6 +15198,32 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "license": "ISC" @@ -15223,6 +15425,14 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/hpagent": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-0.1.2.tgz", + "integrity": "sha512-ePqFXHtSQWAFXYmj+JtOTHr84iNrII4/QRlAAPPE+zqnKy4xJo7Ie1Y4kC7AdB+LxLxSTTzBMASsEcy0q8YyvQ==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/html-encoding-sniffer": { "version": "2.0.1", "license": "MIT", @@ -15478,6 +15688,33 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/http2-wrapper/node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "license": "MIT", @@ -17829,6 +18066,15 @@ "npm": ">=6" } }, + "node_modules/jssha": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jssha/-/jssha-3.3.1.tgz", + "integrity": "sha512-VCMZj12FCFMQYcFLPRm/0lOBbLi8uM2BhXPTqw3U4YAfs4AZfiApOoBLoN8cQE60Z50m1MYMTQVCfgF/KaCVhQ==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "license": "MIT", @@ -18685,6 +18931,16 @@ "tslib": "^2.0.3" } }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/lru-cache": { "version": "6.0.0", "license": "ISC", @@ -19064,6 +19320,16 @@ "node": ">=6" } }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/min-indent": { "version": "1.0.1", "dev": true, @@ -19536,6 +19802,40 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/ngrok": { + "version": "5.0.0-beta.2", + "resolved": "https://registry.npmjs.org/ngrok/-/ngrok-5.0.0-beta.2.tgz", + "integrity": "sha512-UzsyGiJ4yTTQLCQD11k1DQaMwq2/SsztBg2b34zAqcyjS25qjDpogMKPaCKHwe/APRTHeel3iDXcVctk5CNaCQ==", + "dev": true, + "hasInstallScript": true, + "license": "BSD-2-Clause", + "dependencies": { + "extract-zip": "^2.0.1", + "got": "^11.8.5", + "lodash.clonedeep": "^4.5.0", + "uuid": "^7.0.0 || ^8.0.0", + "yaml": "^2.2.2" + }, + "bin": { + "ngrok": "bin/ngrok" + }, + "engines": { + "node": ">=14.2" + }, + "optionalDependencies": { + "hpagent": "^0.1.2" + } + }, + "node_modules/ngrok/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/no-case": { "version": "3.0.4", "license": "MIT", @@ -20262,6 +20562,16 @@ "node": ">=0.10.0" } }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/p-finally": { "version": "1.0.0", "dev": true, @@ -23248,6 +23558,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, "node_modules/resolve-cwd": { "version": "3.0.0", "license": "MIT", @@ -23386,6 +23703,19 @@ "node": ">=10" } }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/restore-cursor": { "version": "3.1.0", "license": "MIT", @@ -25120,6 +25450,15 @@ "node": ">=0.6" } }, + "node_modules/totp-generator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/totp-generator/-/totp-generator-1.0.0.tgz", + "integrity": "sha512-Iu/1Lk60/MH8FE+5cDWPiGbwKK1hxzSq+KT9oSqhZ1BEczGIKGcN50bP0WMLiIZKRg7t29iWLxw6f81TICQdoA==", + "license": "MIT", + "dependencies": { + "jssha": "^3.3.1" + } + }, "node_modules/tough-cookie": { "version": "4.1.3", "license": "BSD-3-Clause", @@ -27169,6 +27508,7 @@ "devDependencies": { "@playwright/test": "^1.47.0", "@types/node": "^20.10.5", + "ngrok": "^5.0.0-beta.2", "playwright-slack-report": "^1.1.72" } }, @@ -27226,14 +27566,17 @@ "@corbado/connect-react": "*", "aws-sdk": "^2.1646.0", "axios": "^1.7.3", + "crypto-js": "^4.2.0", "jsonwebtoken": "^9.0.2", "jwks-rsa": "^3.1.0", "next": "14.2.4", "react": "^18", - "react-dom": "^18" + "react-dom": "^18", + "totp-generator": "^1.0.0" }, "devDependencies": { "@tailwindcss/forms": "^0.5.7", + "@types/crypto-js": "^4.2.2", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", diff --git a/packages/tests-e2e/.env.connect.ci b/packages/tests-e2e/.env.connect.ci index d49ab9346..136997e7b 100644 --- a/packages/tests-e2e/.env.connect.ci +++ b/packages/tests-e2e/.env.connect.ci @@ -1,3 +1,3 @@ +BACKEND_API_URL=https://backendapi.cloud.corbado-staging.io #DEVELOPERPANEL_API_URL=https://console.cloud.corbado-staging.io -#BACKEND_API_URL=https://backendapi.cloud.corbado-staging.io #FRONTEND_API_URL_SUFFIX=frontendapi.cloud.corbado-staging.io diff --git a/packages/tests-e2e/.env.connect.example b/packages/tests-e2e/.env.connect.example index 6f5490c95..7d08eec39 100644 --- a/packages/tests-e2e/.env.connect.example +++ b/packages/tests-e2e/.env.connect.example @@ -2,5 +2,5 @@ PLAYWRIGHT_TEST_URL=https://develop.connect-next.playground.corbado.io DEVELOPERPANEL_API_URL=https://console.cloud.corbado-staging.io BACKEND_API_URL=https://backendapi.cloud.corbado-staging.io -FRONTEND_API_URL_SUFFIX= -PLAYWRIGHT_JWT_TOKEN= +BACKEND_API_BASIC_AUTH= +PLAYWRIGHT_NGROK_AUTH_TOKEN= diff --git a/packages/tests-e2e/package.json b/packages/tests-e2e/package.json index e70418723..f302faeb5 100644 --- a/packages/tests-e2e/package.json +++ b/packages/tests-e2e/package.json @@ -24,6 +24,7 @@ "devDependencies": { "@playwright/test": "^1.47.0", "@types/node": "^20.10.5", + "ngrok": "^5.0.0-beta.2", "playwright-slack-report": "^1.1.72" }, "dependencies": { diff --git a/packages/tests-e2e/src/connect/models/BaseModel.ts b/packages/tests-e2e/src/connect/models/BaseModel.ts index 55c8227d4..dddc3aa90 100644 --- a/packages/tests-e2e/src/connect/models/BaseModel.ts +++ b/packages/tests-e2e/src/connect/models/BaseModel.ts @@ -10,6 +10,7 @@ import { HomeModel } from './HomeModel'; import { LoginModel } from './LoginModel'; import { PasskeyListModel } from './PasskeyListModel'; import { SignupModel } from './SignupModel'; +import { WebhookModel } from './WebhookModel'; export class BaseModel { page: Page; @@ -20,6 +21,7 @@ export class BaseModel { append: AppendModel; home: HomeModel; passkeyList: PasskeyListModel; + webhook: WebhookModel; email = ''; constructor(page: Page, authenticator: VirtualAuthenticator, blocker: NetworkRequestBlocker) { @@ -31,6 +33,7 @@ export class BaseModel { this.append = new AppendModel(page, authenticator); this.home = new HomeModel(page); this.passkeyList = new PasskeyListModel(page, authenticator); + this.webhook = new WebhookModel(page); } loadInvitationToken() { @@ -72,25 +75,6 @@ export class BaseModel { } } - // async deleteUser() { - // const cookies = await this.page.context().cookies(); - // const longSessionCookie = cookies.find(cookie => cookie.name === 'cbo_long_session'); - // const longSessionCookieValue = longSessionCookie?.value; - // - // expect(longSessionCookieValue).toBeDefined(); - // expect(process.env.FRONTEND_API_URL).toBeDefined(); - // - // const response = await fetch(`${process.env.FRONTEND_API_URL}/v2/me`, { - // method: 'DELETE', - // headers: { - // 'Content-Type': 'application/json', - // Cookie: `cbo_long_session=${longSessionCookieValue}`, - // }, - // }); - // - // expect(response.ok).toBeTruthy(); - // } - async clearLocalStorageAndCookies() { await this.page.evaluate(() => localStorage.clear()); await this.page.context().clearCookies(); diff --git a/packages/tests-e2e/src/connect/models/SignupModel.ts b/packages/tests-e2e/src/connect/models/SignupModel.ts index 5b9cd1097..13e46f26d 100644 --- a/packages/tests-e2e/src/connect/models/SignupModel.ts +++ b/packages/tests-e2e/src/connect/models/SignupModel.ts @@ -12,7 +12,7 @@ export class SignupModel { return await this.page.getByPlaceholder('Email').inputValue(); } - submit(): Promise { + submit() { return this.page.getByRole('button', { name: 'Sign up' }).click(); } } diff --git a/packages/tests-e2e/src/connect/models/WebhookModel.ts b/packages/tests-e2e/src/connect/models/WebhookModel.ts new file mode 100644 index 000000000..51dd6d274 --- /dev/null +++ b/packages/tests-e2e/src/connect/models/WebhookModel.ts @@ -0,0 +1,102 @@ +import { createServer } from 'node:http'; +import type { Server } from 'node:net'; + +import type { Page } from '@playwright/test'; +import { expect } from '@playwright/test'; +import ngrok from 'ngrok'; + +import { WebhookTypes } from '../utils/Constants'; + +export class WebhookModel { + page: Page; + webhookServer: Server | null = null; + webhookEndpointID: string | null = null; + latestWebhookType: WebhookTypes | null = null; + + constructor(page: Page) { + this.page = page; + } + + async createWebhookEndpoint(webhookTypes: WebhookTypes[]) { + if (this.webhookServer || this.webhookEndpointID) { + throw new Error('Webhook endpoint already created'); + } + + if (!process.env.PLAYWRIGHT_CONNECT_PROJECT_ID) { + throw new Error('PLAYWRIGHT_CONNECT_PROJECT_ID not set'); + } + + const port = 3001; + + await new Promise(resolve => { + this.webhookServer = createServer((req, res) => { + if (req.method === 'POST' && req.url === '/webhook') { + let body = ''; + req.on('data', chunk => (body += chunk)); + req.on('end', () => { + const receivedWebhookType = JSON.parse(body).type; + expect(Object.values(WebhookTypes)).toContain(receivedWebhookType); + this.latestWebhookType = receivedWebhookType as WebhookTypes; + res.writeHead(200); + res.end('OK'); + }); + } else { + res.writeHead(404); + res.end(); + } + }); + this.webhookServer.listen(port, () => { + console.log(`Webhook server running at http://localhost:${port}`); + resolve(this.webhookServer); + }); + }); + + const publicUrl = await ngrok.connect({ + addr: port, + authtoken: process.env.PLAYWRIGHT_NGROK_AUTH_TOKEN, + }); + const createRes = await fetch(`${process.env.BACKEND_API_URL}/v2/webhookEndpoints`, { + method: 'POST', + headers: { + Authorization: `Basic ${process.env.BACKEND_API_BASIC_AUTH}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + url: `${publicUrl}/webhook`, + subscribedEvents: webhookTypes, + customHeaders: { 'X-Custom-Header': 'custom-value' }, + }), + }); + expect(createRes.ok).toBeTruthy(); + + const res_data = await createRes.json(); + console.log(res_data); + this.webhookEndpointID = res_data.id; + } + + expectWebhookRequest(webhookType: WebhookTypes) { + expect(this.latestWebhookType).toEqual(webhookType); + } + + async deleteWebhookEndpoint() { + if (!this.webhookServer || !this.webhookEndpointID) { + throw new Error('Webhook endpoint not yet created'); + } + + if (!process.env.PLAYWRIGHT_CONNECT_PROJECT_ID) { + throw new Error('PLAYWRIGHT_CONNECT_PROJECT_ID not set'); + } + + const deleteRes = await fetch(`${process.env.BACKEND_API_URL}/v2/webhookEndpoints/${this.webhookEndpointID}`, { + method: 'DELETE', + headers: { + Authorization: `Basic ${process.env.BACKEND_API_BASIC_AUTH}`, + 'Content-Type': 'application/json', + }, + }); + expect(deleteRes.ok).toBeTruthy(); + + this.webhookServer.close(); + await ngrok.disconnect(); + } +} diff --git a/packages/tests-e2e/src/connect/scenarios/append.spec.ts b/packages/tests-e2e/src/connect/scenarios/append.spec.ts index 3c1f032e8..f726181ae 100644 --- a/packages/tests-e2e/src/connect/scenarios/append.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/append.spec.ts @@ -1,6 +1,6 @@ import { test } from '../fixtures/BaseTest'; -import { password, ScreenNames } from '../utils/Constants'; -import { loadPasskeyAppend, setupNetworkBlocker, setupUser, setupVirtualAuthenticator } from './hooks'; +import { password, ScreenNames, WebhookTypes } from '../utils/Constants'; +import { loadPasskeyAppend, setupNetworkBlocker, setupUser, setupVirtualAuthenticator, setupWebhooks } from './hooks'; test.describe('append component', () => { setupVirtualAuthenticator(test); @@ -28,6 +28,24 @@ test.describe('append component', () => { }); }); +test.describe('append component (webhook)', () => { + setupVirtualAuthenticator(test); + setupNetworkBlocker(test); + setupUser(test, true, false); + loadPasskeyAppend(test); + setupWebhooks(test, [WebhookTypes.Create]); + + test('successful passkey append on login (+ webhook)', async ({ model }) => { + await model.append.appendPasskey(true); + await model.expectScreen(ScreenNames.PasskeyAppended); + + await model.append.confirmAppended(); + await model.expectScreen(ScreenNames.Home); + + model.webhook.expectWebhookRequest(WebhookTypes.Create); + }); +}); + test.describe('skip append component', () => { setupVirtualAuthenticator(test); setupNetworkBlocker(test); diff --git a/packages/tests-e2e/src/connect/scenarios/hooks.ts b/packages/tests-e2e/src/connect/scenarios/hooks.ts index 5368f699e..92e2cbfa1 100644 --- a/packages/tests-e2e/src/connect/scenarios/hooks.ts +++ b/packages/tests-e2e/src/connect/scenarios/hooks.ts @@ -7,7 +7,7 @@ import type { } from '@playwright/test'; import type { BaseModel } from '../models/BaseModel'; -import { password, ScreenNames } from '../utils/Constants'; +import { password, ScreenNames, WebhookTypes } from '../utils/Constants'; export function setupVirtualAuthenticator( test: TestType< @@ -35,36 +35,30 @@ export function setupNetworkBlocker( }); } -export function loadInvitationToken( +export function setupWebhooks( test: TestType< PlaywrightTestArgs & PlaywrightTestOptions & { model: BaseModel }, PlaywrightWorkerArgs & PlaywrightWorkerOptions >, + webhookTypes: WebhookTypes[], ) { test.beforeEach(async ({ model }) => { - await model.loadInvitationToken(); + await model.webhook.createWebhookEndpoint(webhookTypes); }); -} -export function loadSignup( - test: TestType< - PlaywrightTestArgs & PlaywrightTestOptions & { model: BaseModel }, - PlaywrightWorkerArgs & PlaywrightWorkerOptions - >, -) { - test.beforeEach(async ({ model }) => { - await model.loadSignup(); + test.afterEach(async ({ model }) => { + await model.webhook.deleteWebhookEndpoint(); }); } -export function loadLogin( +export function loadInvitationToken( test: TestType< PlaywrightTestArgs & PlaywrightTestOptions & { model: BaseModel }, PlaywrightWorkerArgs & PlaywrightWorkerOptions >, ) { test.beforeEach(async ({ model }) => { - await model.loadLogin(); + await model.loadInvitationToken(); }); } @@ -85,10 +79,6 @@ export function setupUser( await model.createUser(invited, append); await model.expectScreen(ScreenNames.Home); }); - - // test.afterEach(async ({ model }) => { - // await model.deleteUser(); - // }); } // assumes that setupUser(test, true, false) has been called right before diff --git a/packages/tests-e2e/src/connect/scenarios/login.spec.ts b/packages/tests-e2e/src/connect/scenarios/login.spec.ts index 54fb22bf9..3598a04d4 100644 --- a/packages/tests-e2e/src/connect/scenarios/login.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/login.spec.ts @@ -1,6 +1,6 @@ import { test } from '../fixtures/BaseTest'; -import { ErrorTexts, password, ScreenNames } from '../utils/Constants'; -import { loadInvitationToken, setupNetworkBlocker, setupUser, setupVirtualAuthenticator } from './hooks'; +import { ErrorTexts, password, ScreenNames, WebhookTypes } from '../utils/Constants'; +import { loadInvitationToken, setupNetworkBlocker, setupUser, setupVirtualAuthenticator, setupWebhooks } from './hooks'; test.describe('login component (without invitation token)', () => { setupUser(test, false); @@ -159,3 +159,23 @@ test.describe('login component (without user)', () => { await model.expectScreen(ScreenNames.InitLoginFallback); }); }); + +test.describe('login component (webhook)', () => { + setupVirtualAuthenticator(test); + setupNetworkBlocker(test); + setupUser(test, true, true); + setupWebhooks(test, [WebhookTypes.Login]); + + test('successful login with passkey (+ webhook)', async ({ model }) => { + await model.home.logout(); + await model.expectScreen(ScreenNames.InitLoginOneTap); + + await model.login.removePasskeyButton(); + await model.expectScreen(ScreenNames.InitLogin); + + await model.login.submitEmail(model.email, true); + await model.expectScreen(ScreenNames.Home); + + model.webhook.expectWebhookRequest(WebhookTypes.Login); + }); +}); diff --git a/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts b/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts index 2a0934014..db20076f0 100644 --- a/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts @@ -1,6 +1,6 @@ import { expect, test } from '../fixtures/BaseTest'; -import { ErrorTexts, ScreenNames } from '../utils/Constants'; -import { loadPasskeyList, setupNetworkBlocker, setupUser, setupVirtualAuthenticator } from './hooks'; +import { ErrorTexts, ScreenNames, WebhookTypes } from '../utils/Constants'; +import { loadPasskeyList, setupNetworkBlocker, setupUser, setupVirtualAuthenticator, setupWebhooks } from './hooks'; test.describe('passkey-list component', () => { setupVirtualAuthenticator(test); @@ -81,6 +81,25 @@ test.describe('passkey-list component', () => { }); }); +test.describe('passkey-list component (webhook)', () => { + setupVirtualAuthenticator(test); + setupNetworkBlocker(test); + setupUser(test, true, false); + loadPasskeyList(test); + setupWebhooks(test, [WebhookTypes.Create, WebhookTypes.Delete]); + + test('list, delete, create passkey (+ webhook)', async ({ model }) => { + await model.passkeyList.expectPasskeys(0); + await model.passkeyList.createPasskey(true); + await model.passkeyList.expectPasskeys(1); + model.webhook.expectWebhookRequest(WebhookTypes.Create); + + await model.passkeyList.deletePasskey(0); + await model.passkeyList.expectPasskeys(0); + model.webhook.expectWebhookRequest(WebhookTypes.Delete); + }); +}); + test.describe('skip passkey-list component', () => { setupVirtualAuthenticator(test); setupNetworkBlocker(test); diff --git a/packages/tests-e2e/src/connect/utils/Constants.ts b/packages/tests-e2e/src/connect/utils/Constants.ts index 2dde0c2e8..4790db49e 100644 --- a/packages/tests-e2e/src/connect/utils/Constants.ts +++ b/packages/tests-e2e/src/connect/utils/Constants.ts @@ -22,6 +22,12 @@ export enum ErrorTexts { PasskeySignatureValidationFail = "We couldn't log you in with your passkey due to a system error. Use your password to log in instead.", } +export enum WebhookTypes { + Login = 'passkey-login.completed', + Create = 'passkey.created', + Delete = 'passkey.deleted', +} + export const phone = '+4915121609839'; export const password = 'asdfasdf'; From 40e69abc7b4c01c66bf2389a0cd0843790d88b4c Mon Sep 17 00:00:00 2001 From: aehnh Date: Tue, 21 Jan 2025 19:55:18 +0100 Subject: [PATCH 2/2] add new env vars to nightly test workflow --- .github/workflows/e2e-test-nightly.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/e2e-test-nightly.yml b/.github/workflows/e2e-test-nightly.yml index 5250c1979..5255660dd 100644 --- a/.github/workflows/e2e-test-nightly.yml +++ b/.github/workflows/e2e-test-nightly.yml @@ -144,6 +144,9 @@ jobs: PLAYWRIGHT_TEST_URL=$PLAYWRIGHT_TEST_URL npx playwright test --config=playwright.config.connect.ts env: PLAYWRIGHT_NUM_CORES: 4 + BACKEND_API_BASIC_AUTH: ${{ secrets.BACKEND_API_BASIC_AUTH }} + PLAYWRIGHT_CONNECT_PROJECT_ID: ${{ secrets.PLAYWRIGHT_CONNECT_PROJECT_ID }} + PLAYWRIGHT_NGROK_AUTH_TOKEN: ${{ secrets.PLAYWRIGHT_NGROK_AUTH_TOKEN }} GITHUB_RUN_ID: ${{ github.run_id }} SLACK_BOT_USER_OAUTH_TOKEN: ${{ secrets.SLACK_BOT_USER_OAUTH_TOKEN }} GITHUB_BRANCH_NAME: ${{ env.BRANCH_NAME_RAW }}