mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-12 01:43:08 +00:00
chore: fixes to tests
This commit is contained in:
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -76,7 +76,7 @@ jobs:
|
|||||||
if: ${{ matrix.command == 'test:acceptance' }}
|
if: ${{ matrix.command == 'test:acceptance' }}
|
||||||
|
|
||||||
- name: Run ZITADEL
|
- name: Run ZITADEL
|
||||||
run: ZITADEL_DEV_UID=root pnpm run-zitadel
|
run: ZITADEL_DEV_UID=root pnpm run-sink
|
||||||
if: ${{ matrix.command == 'test:acceptance' }}
|
if: ${{ matrix.command == 'test:acceptance' }}
|
||||||
|
|
||||||
- name: Create Production Build
|
- name: Create Production Build
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ services:
|
|||||||
- POSTGRES_HOST_AUTH_METHOD=trust
|
- POSTGRES_HOST_AUTH_METHOD=trust
|
||||||
command: postgres -c shared_preload_libraries=pg_stat_statements -c pg_stat_statements.track=all -c shared_buffers=1GB -c work_mem=16MB -c effective_io_concurrency=100 -c wal_level=minimal -c archive_mode=off -c max_wal_senders=0
|
command: postgres -c shared_preload_libraries=pg_stat_statements -c pg_stat_statements.track=all -c shared_buffers=1GB -c work_mem=16MB -c effective_io_concurrency=100 -c wal_level=minimal -c archive_mode=off -c max_wal_senders=0
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "pg_isready"]
|
test: [ "CMD-SHELL", "pg_isready" ]
|
||||||
interval: "10s"
|
interval: "10s"
|
||||||
timeout: "30s"
|
timeout: "30s"
|
||||||
retries: 5
|
retries: 5
|
||||||
@@ -52,3 +52,15 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
wait_for_zitadel:
|
wait_for_zitadel:
|
||||||
condition: "service_completed_successfully"
|
condition: "service_completed_successfully"
|
||||||
|
|
||||||
|
sink:
|
||||||
|
image: golang:1.19-alpine
|
||||||
|
container_name: sink
|
||||||
|
command: go run /sink/main.go -port 3333
|
||||||
|
ports:
|
||||||
|
- 3333:3333
|
||||||
|
volumes:
|
||||||
|
- "./sink:/sink"
|
||||||
|
depends_on:
|
||||||
|
setup:
|
||||||
|
condition: "service_completed_successfully"
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ ZITADEL_API_DOMAIN="${ZITADEL_API_DOMAIN:-localhost}"
|
|||||||
ZITADEL_API_PORT="${ZITADEL_API_PORT:-8080}"
|
ZITADEL_API_PORT="${ZITADEL_API_PORT:-8080}"
|
||||||
ZITADEL_API_URL="${ZITADEL_API_URL:-${ZITADEL_API_PROTOCOL}://${ZITADEL_API_DOMAIN}:${ZITADEL_API_PORT}}"
|
ZITADEL_API_URL="${ZITADEL_API_URL:-${ZITADEL_API_PROTOCOL}://${ZITADEL_API_DOMAIN}:${ZITADEL_API_PORT}}"
|
||||||
ZITADEL_API_INTERNAL_URL="${ZITADEL_API_INTERNAL_URL:-${ZITADEL_API_URL}}"
|
ZITADEL_API_INTERNAL_URL="${ZITADEL_API_INTERNAL_URL:-${ZITADEL_API_URL}}"
|
||||||
|
SINK_EMAIL_URL="${SINK_EMAIL_URL:-"http://localhost:3333/email"}}"
|
||||||
|
SINK_SMS_URL="${SINK_SMS_URL:-"http://localhost:3333/sms"}}"
|
||||||
|
SINK_NOTIFICATION_URL="${SINK_SMS_URL:-"http://localhost:3333/notification"}}"
|
||||||
|
|
||||||
if [ -z "${PAT}" ]; then
|
if [ -z "${PAT}" ]; then
|
||||||
echo "Reading PAT from file ${PAT_FILE}"
|
echo "Reading PAT from file ${PAT_FILE}"
|
||||||
@@ -24,6 +27,10 @@ if [ -z "${ZITADEL_SERVICE_USER_ID}" ]; then
|
|||||||
ZITADEL_SERVICE_USER_ID=$(echo "${USERINFO_RESPONSE}" | jq --raw-output '.sub')
|
ZITADEL_SERVICE_USER_ID=$(echo "${USERINFO_RESPONSE}" | jq --raw-output '.sub')
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
#################################################################
|
||||||
|
# Environment files
|
||||||
|
#################################################################
|
||||||
|
|
||||||
WRITE_ENVIRONMENT_FILE=${WRITE_ENVIRONMENT_FILE:-$(dirname "$0")/../apps/login/.env.local}
|
WRITE_ENVIRONMENT_FILE=${WRITE_ENVIRONMENT_FILE:-$(dirname "$0")/../apps/login/.env.local}
|
||||||
echo "Writing environment file to ${WRITE_ENVIRONMENT_FILE} when done."
|
echo "Writing environment file to ${WRITE_ENVIRONMENT_FILE} when done."
|
||||||
WRITE_TEST_ENVIRONMENT_FILE=${WRITE_TEST_ENVIRONMENT_FILE:-$(dirname "$0")/../acceptance/tests/.env.local}
|
WRITE_TEST_ENVIRONMENT_FILE=${WRITE_TEST_ENVIRONMENT_FILE:-$(dirname "$0")/../acceptance/tests/.env.local}
|
||||||
@@ -32,6 +39,7 @@ echo "Writing environment file to ${WRITE_TEST_ENVIRONMENT_FILE} when done."
|
|||||||
echo "ZITADEL_API_URL=${ZITADEL_API_URL}
|
echo "ZITADEL_API_URL=${ZITADEL_API_URL}
|
||||||
ZITADEL_SERVICE_USER_ID=${ZITADEL_SERVICE_USER_ID}
|
ZITADEL_SERVICE_USER_ID=${ZITADEL_SERVICE_USER_ID}
|
||||||
ZITADEL_SERVICE_USER_TOKEN=${PAT}
|
ZITADEL_SERVICE_USER_TOKEN=${PAT}
|
||||||
|
SINK_NOTIFICATION_URL=${SINK_NOTIFICATION_URL}
|
||||||
DEBUG=true"| tee "${WRITE_ENVIRONMENT_FILE}" "${WRITE_TEST_ENVIRONMENT_FILE}" > /dev/null
|
DEBUG=true"| tee "${WRITE_ENVIRONMENT_FILE}" "${WRITE_TEST_ENVIRONMENT_FILE}" > /dev/null
|
||||||
echo "Wrote environment file ${WRITE_ENVIRONMENT_FILE}"
|
echo "Wrote environment file ${WRITE_ENVIRONMENT_FILE}"
|
||||||
cat ${WRITE_ENVIRONMENT_FILE}
|
cat ${WRITE_ENVIRONMENT_FILE}
|
||||||
@@ -39,6 +47,54 @@ cat ${WRITE_ENVIRONMENT_FILE}
|
|||||||
echo "Wrote environment file ${WRITE_TEST_ENVIRONMENT_FILE}"
|
echo "Wrote environment file ${WRITE_TEST_ENVIRONMENT_FILE}"
|
||||||
cat ${WRITE_TEST_ENVIRONMENT_FILE}
|
cat ${WRITE_TEST_ENVIRONMENT_FILE}
|
||||||
|
|
||||||
|
#################################################################
|
||||||
|
# SMS provider with HTTP
|
||||||
|
#################################################################
|
||||||
|
|
||||||
|
SMSHTTP_RESPONSE=$(curl -s --request POST \
|
||||||
|
--url "${ZITADEL_API_INTERNAL_URL}/admin/v1/sms/http" \
|
||||||
|
--header "Authorization: Bearer ${PAT}" \
|
||||||
|
--header "Host: ${ZITADEL_API_DOMAIN}" \
|
||||||
|
--header "Content-Type: application/json" \
|
||||||
|
-d "{\"endpoint\": \"${SINK_SMS_URL}\", \"description\": \"test\"}")
|
||||||
|
echo "Received SMS HTTP response: ${SMSHTTP_RESPONSE}"
|
||||||
|
|
||||||
|
SMSHTTP_ID=$(echo ${SMSHTTP_RESPONSE} | jq -r '. | .id')
|
||||||
|
echo "Received SMS HTTP ID: ${SMSHTTP_ID}"
|
||||||
|
|
||||||
|
SMS_ACTIVE_RESPONSE=$(curl -s --request POST \
|
||||||
|
--url "${ZITADEL_API_INTERNAL_URL}/admin/v1/sms/${SMSHTTP_ID}/_activate" \
|
||||||
|
--header "Authorization: Bearer ${PAT}" \
|
||||||
|
--header "Host: ${ZITADEL_API_DOMAIN}" \
|
||||||
|
--header "Content-Type: application/json")
|
||||||
|
echo "Received SMS active response: ${SMS_ACTIVE_RESPONSE}"
|
||||||
|
|
||||||
|
#################################################################
|
||||||
|
# Email provider with HTTP
|
||||||
|
#################################################################
|
||||||
|
|
||||||
|
EMAILHTTP_RESPONSE=$(curl -s --request POST \
|
||||||
|
--url "${ZITADEL_API_INTERNAL_URL}/admin/v1/email/http" \
|
||||||
|
--header "Authorization: Bearer ${PAT}" \
|
||||||
|
--header "Host: ${ZITADEL_API_DOMAIN}" \
|
||||||
|
--header "Content-Type: application/json" \
|
||||||
|
-d "{\"endpoint\": \"${SINK_EMAIL_URL}\", \"description\": \"test\"}")
|
||||||
|
echo "Received Email HTTP response: ${EMAILHTTP_RESPONSE}"
|
||||||
|
|
||||||
|
EMAILHTTP_ID=$(echo ${EMAILHTTP_RESPONSE} | jq -r '. | .id')
|
||||||
|
echo "Received Email HTTP ID: ${EMAILHTTP_ID}"
|
||||||
|
|
||||||
|
EMAIL_ACTIVE_RESPONSE=$(curl -s --request POST \
|
||||||
|
--url "${ZITADEL_API_INTERNAL_URL}/admin/v1/email/${EMAILHTTP_ID}/_activate" \
|
||||||
|
--header "Authorization: Bearer ${PAT}" \
|
||||||
|
--header "Host: ${ZITADEL_API_DOMAIN}" \
|
||||||
|
--header "Content-Type: application/json")
|
||||||
|
echo "Received Email active response: ${EMAIL_ACTIVE_RESPONSE}"
|
||||||
|
|
||||||
|
#################################################################
|
||||||
|
# Wait for projection of default organization in ZITADEL
|
||||||
|
#################################################################
|
||||||
|
|
||||||
DEFAULTORG_RESPONSE_RESULTS=0
|
DEFAULTORG_RESPONSE_RESULTS=0
|
||||||
# waiting for default organization
|
# waiting for default organization
|
||||||
until [ ${DEFAULTORG_RESPONSE_RESULTS} -eq 1 ]
|
until [ ${DEFAULTORG_RESPONSE_RESULTS} -eq 1 ]
|
||||||
@@ -53,3 +109,4 @@ do
|
|||||||
DEFAULTORG_RESPONSE_RESULTS=$(echo $DEFAULTORG_RESPONSE | jq -r '.result | length')
|
DEFAULTORG_RESPONSE_RESULTS=$(echo $DEFAULTORG_RESPONSE | jq -r '.result | length')
|
||||||
echo "Received default organization response result: ${DEFAULTORG_RESPONSE_RESULTS}"
|
echo "Received default organization response result: ${DEFAULTORG_RESPONSE_RESULTS}"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|||||||
104
acceptance/sink/main.go
Normal file
104
acceptance/sink/main.go
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type serializableData struct {
|
||||||
|
ContextInfo map[string]interface{} `json:"contextInfo,omitempty"`
|
||||||
|
Args map[string]interface{} `json:"args,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type response struct {
|
||||||
|
Recipient string `json:"recipient,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
port := flag.String("port", "3333", "used port for the sink")
|
||||||
|
email := flag.String("email", "/email", "path for a sent email")
|
||||||
|
emailKey := flag.String("email-key", "recipientEmailAddress", "value in the sent context info of the email used as key to retrieve the notification")
|
||||||
|
sms := flag.String("sms", "/sms", "path for a sent sms")
|
||||||
|
smsKey := flag.String("sms-key", "recipientPhoneNumber", "value in the sent context info of the sms used as key to retrieve the notification")
|
||||||
|
notification := flag.String("notification", "/notification", "path to receive the notification")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
messages := make(map[string]serializableData)
|
||||||
|
|
||||||
|
http.HandleFunc(*email, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
data, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
serializableData := serializableData{}
|
||||||
|
if err := json.Unmarshal(data, &serializableData); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
email, ok := serializableData.ContextInfo[*emailKey].(string)
|
||||||
|
if !ok {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(email + ": " + string(data))
|
||||||
|
messages[email] = serializableData
|
||||||
|
io.WriteString(w, "Email!\n")
|
||||||
|
})
|
||||||
|
|
||||||
|
http.HandleFunc(*sms, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
data, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
serializableData := serializableData{}
|
||||||
|
if err := json.Unmarshal(data, &serializableData); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
phone, ok := serializableData.ContextInfo[*smsKey].(string)
|
||||||
|
if !ok {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(phone + ": " + string(data))
|
||||||
|
messages[phone] = serializableData
|
||||||
|
io.WriteString(w, "SMS!\n")
|
||||||
|
})
|
||||||
|
|
||||||
|
http.HandleFunc(*notification, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
data, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := response{}
|
||||||
|
if err := json.Unmarshal(data, &response); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
serializableData, err := json.Marshal(messages[response.Recipient])
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
io.WriteString(w, string(serializableData))
|
||||||
|
})
|
||||||
|
|
||||||
|
fmt.Println("Starting server on", *port)
|
||||||
|
fmt.Println(*email, " for email handling")
|
||||||
|
fmt.Println(*sms, " for sms handling")
|
||||||
|
fmt.Println(*notification, " for retrieving notifications")
|
||||||
|
err := http.ListenAndServe(":"+*port, nil)
|
||||||
|
if err != nil {
|
||||||
|
panic("Server could not be started: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
12
acceptance/tests/code-screen.ts
Normal file
12
acceptance/tests/code-screen.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { expect, Page } from "@playwright/test";
|
||||||
|
|
||||||
|
const codeTextInput = "code-text-input";
|
||||||
|
|
||||||
|
export async function codeScreen(page: Page, code: string) {
|
||||||
|
await page.getByTestId(codeTextInput).pressSequentially(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function codeScreenExpect(page: Page, code: string) {
|
||||||
|
await expect(page.getByTestId(codeTextInput)).toHaveValue(code);
|
||||||
|
await expect(page.getByTestId("error").locator("div")).toContainText("Could not verify OTP code");
|
||||||
|
}
|
||||||
19
acceptance/tests/code.ts
Normal file
19
acceptance/tests/code.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { Page } from "@playwright/test";
|
||||||
|
import { codeScreen } from "./code-screen";
|
||||||
|
import { getCodeFromSink } from "./sink";
|
||||||
|
|
||||||
|
export async function codeFromSink(page: Page, key: string) {
|
||||||
|
// wait for send of the code
|
||||||
|
await page.waitForTimeout(2000);
|
||||||
|
const c = await getCodeFromSink(key);
|
||||||
|
await code(page, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function code(page: Page, code: string) {
|
||||||
|
await codeScreen(page, code);
|
||||||
|
await page.getByTestId("submit-button").click();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function codeResend(page: Page) {
|
||||||
|
await page.getByTestId("resend-button").click();
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { expect, Page } from "@playwright/test";
|
import { expect, Page } from "@playwright/test";
|
||||||
|
import { codeFromSink } from "./code";
|
||||||
import { loginname } from "./loginname";
|
import { loginname } from "./loginname";
|
||||||
import { password } from "./password";
|
import { password } from "./password";
|
||||||
|
|
||||||
@@ -23,6 +24,12 @@ export async function loginScreenExpect(page: Page, fullName: string) {
|
|||||||
await expect(page.getByRole("heading")).toContainText(fullName);
|
await expect(page.getByRole("heading")).toContainText(fullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loginWithOTP(page: Page, username: string, password: string) {
|
export async function loginWithPasswordAndEmailOTP(page: Page, username: string, password: string, email: string) {
|
||||||
await loginWithPassword(page, username, password);
|
await loginWithPassword(page, username, password);
|
||||||
|
await codeFromSink(page, email);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loginWithPasswordAndPhoneOTP(page: Page, username: string, password: string, phone: string) {
|
||||||
|
await loginWithPassword(page, username, password);
|
||||||
|
await codeFromSink(page, phone);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { expect, Page } from "@playwright/test";
|
import { expect, Page } from "@playwright/test";
|
||||||
|
|
||||||
const usernameUserInput = "username-text-input";
|
const usernameTextInput = "username-text-input";
|
||||||
|
|
||||||
export async function loginnameScreen(page: Page, username: string) {
|
export async function loginnameScreen(page: Page, username: string) {
|
||||||
await page.getByTestId(usernameUserInput).pressSequentially(username);
|
await page.getByTestId(usernameTextInput).pressSequentially(username);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loginnameScreenExpect(page: Page, username: string) {
|
export async function loginnameScreenExpect(page: Page, username: string) {
|
||||||
await expect(page.getByTestId(usernameUserInput)).toHaveValue(username);
|
await expect(page.getByTestId(usernameTextInput)).toHaveValue(username);
|
||||||
await expect(page.getByTestId("error").locator("div")).toContainText("Could not find user");
|
await expect(page.getByTestId("error").locator("div")).toContainText("Could not find user");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
import * as http from "node:http";
|
|
||||||
|
|
||||||
let messages = new Map<string, any>();
|
|
||||||
|
|
||||||
export function startSink() {
|
|
||||||
const hostname = "127.0.0.1";
|
|
||||||
const port = 3030;
|
|
||||||
|
|
||||||
const server = http.createServer((req, res) => {
|
|
||||||
console.log("Sink received message: ");
|
|
||||||
let body = "";
|
|
||||||
req.on("data", (chunk) => {
|
|
||||||
body += chunk;
|
|
||||||
});
|
|
||||||
|
|
||||||
req.on("end", () => {
|
|
||||||
console.log(body);
|
|
||||||
const data = JSON.parse(body);
|
|
||||||
messages.set(data.contextInfo.recipientEmailAddress, data.args.code);
|
|
||||||
res.statusCode = 200;
|
|
||||||
res.setHeader("Content-Type", "text/plain");
|
|
||||||
res.write("OK");
|
|
||||||
res.end();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
server.listen(port, hostname, () => {
|
|
||||||
console.log(`Sink running at http://${hostname}:${port}/`);
|
|
||||||
});
|
|
||||||
return server;
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,19 @@
|
|||||||
|
import { faker } from "@faker-js/faker";
|
||||||
import { test } from "@playwright/test";
|
import { test } from "@playwright/test";
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
import path from "path";
|
||||||
import { loginScreenExpect } from "./login";
|
import { loginScreenExpect } from "./login";
|
||||||
import { registerWithPasskey, registerWithPassword } from "./register";
|
import { registerWithPasskey, registerWithPassword } from "./register";
|
||||||
import { removeUserByUsername } from "./zitadel";
|
import { removeUserByUsername } from "./zitadel";
|
||||||
|
|
||||||
|
// Read from ".env" file.
|
||||||
|
dotenv.config({ path: path.resolve(__dirname, ".env.local") });
|
||||||
|
|
||||||
test("register with password", async ({ page }) => {
|
test("register with password", async ({ page }) => {
|
||||||
const username = "register-password@example.com";
|
const username = faker.internet.email();
|
||||||
const password = "Password1!";
|
const password = "Password1!";
|
||||||
const firstname = "firstname";
|
const firstname = faker.person.firstName();
|
||||||
const lastname = "lastname";
|
const lastname = faker.person.lastName();
|
||||||
|
|
||||||
await removeUserByUsername(username);
|
await removeUserByUsername(username);
|
||||||
await registerWithPassword(page, firstname, lastname, username, password, password);
|
await registerWithPassword(page, firstname, lastname, username, password, password);
|
||||||
@@ -15,9 +21,9 @@ test("register with password", async ({ page }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("register with passkey", async ({ page }) => {
|
test("register with passkey", async ({ page }) => {
|
||||||
const username = "register-passkey@example.com";
|
const username = faker.internet.email();
|
||||||
const firstname = "firstname";
|
const firstname = faker.person.firstName();
|
||||||
const lastname = "lastname";
|
const lastname = faker.person.lastName();
|
||||||
|
|
||||||
await removeUserByUsername(username);
|
await removeUserByUsername(username);
|
||||||
await registerWithPasskey(page, firstname, lastname, username);
|
await registerWithPasskey(page, firstname, lastname, username);
|
||||||
|
|||||||
28
acceptance/tests/sink.ts
Normal file
28
acceptance/tests/sink.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
export async function getCodeFromSink(key: string): Promise<any> {
|
||||||
|
try {
|
||||||
|
const response = await axios.post(
|
||||||
|
process.env.SINK_NOTIFICATION_URL,
|
||||||
|
{
|
||||||
|
recipient: key,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${process.env.ZITADEL_SERVICE_USER_TOKEN}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.status >= 400) {
|
||||||
|
const error = `HTTP Error: ${response.status} - ${response.statusText}`;
|
||||||
|
console.error(error);
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
return response.data.args.oTP;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error making request:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ export interface userProps {
|
|||||||
lastName: string;
|
lastName: string;
|
||||||
organization: string;
|
organization: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
phone: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class User {
|
class User {
|
||||||
@@ -35,6 +36,10 @@ class User {
|
|||||||
email: this.props.email,
|
email: this.props.email,
|
||||||
isVerified: true,
|
isVerified: true,
|
||||||
},
|
},
|
||||||
|
phone: {
|
||||||
|
phone: this.props.phone!,
|
||||||
|
isVerified: true,
|
||||||
|
},
|
||||||
password: {
|
password: {
|
||||||
password: this.props.password!,
|
password: this.props.password!,
|
||||||
},
|
},
|
||||||
@@ -53,6 +58,7 @@ class User {
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
}
|
}
|
||||||
|
this.setUserId(response.data.userId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error making request:", error);
|
console.error("Error making request:", error);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -94,6 +100,10 @@ class User {
|
|||||||
return this.props.lastName;
|
return this.props.lastName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getPhone() {
|
||||||
|
return this.props.phone;
|
||||||
|
}
|
||||||
|
|
||||||
public getFullName() {
|
public getFullName() {
|
||||||
return `${this.props.firstName} ${this.props.lastName}`;
|
return `${this.props.firstName} ${this.props.lastName}`;
|
||||||
}
|
}
|
||||||
@@ -112,12 +122,12 @@ export interface otpUserProps {
|
|||||||
lastName: string;
|
lastName: string;
|
||||||
organization: string;
|
organization: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
phone: string;
|
||||||
type: OtpType;
|
type: OtpType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PasswordUserWithOTP extends User {
|
export class PasswordUserWithOTP extends User {
|
||||||
private type: OtpType;
|
private type: OtpType;
|
||||||
private code: string;
|
|
||||||
|
|
||||||
constructor(props: otpUserProps) {
|
constructor(props: otpUserProps) {
|
||||||
super({
|
super({
|
||||||
@@ -126,6 +136,7 @@ export class PasswordUserWithOTP extends User {
|
|||||||
lastName: props.lastName,
|
lastName: props.lastName,
|
||||||
organization: props.organization,
|
organization: props.organization,
|
||||||
password: props.password,
|
password: props.password,
|
||||||
|
phone: props.phone,
|
||||||
});
|
});
|
||||||
this.type = props.type;
|
this.type = props.type;
|
||||||
}
|
}
|
||||||
@@ -160,9 +171,6 @@ export class PasswordUserWithOTP extends User {
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: get code from SMS or Email provider
|
|
||||||
this.code = "";
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error making request:", error);
|
console.error("Error making request:", error);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -171,10 +179,6 @@ export class PasswordUserWithOTP extends User {
|
|||||||
// wait for projection of user
|
// wait for projection of user
|
||||||
await page.waitForTimeout(2000);
|
await page.waitForTimeout(2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCode() {
|
|
||||||
return this.code;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface passkeyUserProps {
|
export interface passkeyUserProps {
|
||||||
@@ -182,6 +186,7 @@ export interface passkeyUserProps {
|
|||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
organization: string;
|
organization: string;
|
||||||
|
phone: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PasskeyUser extends User {
|
export class PasskeyUser extends User {
|
||||||
@@ -194,6 +199,7 @@ export class PasskeyUser extends User {
|
|||||||
lastName: props.lastName,
|
lastName: props.lastName,
|
||||||
organization: props.organization,
|
organization: props.organization,
|
||||||
password: "",
|
password: "",
|
||||||
|
phone: props.phone,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { faker } from "@faker-js/faker";
|
||||||
import { test as base } from "@playwright/test";
|
import { test as base } from "@playwright/test";
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
@@ -10,10 +11,11 @@ dotenv.config({ path: path.resolve(__dirname, ".env.local") });
|
|||||||
const test = base.extend<{ user: PasskeyUser }>({
|
const test = base.extend<{ user: PasskeyUser }>({
|
||||||
user: async ({ page }, use) => {
|
user: async ({ page }, use) => {
|
||||||
const user = new PasskeyUser({
|
const user = new PasskeyUser({
|
||||||
email: "passkey@example.com",
|
email: faker.internet.email(),
|
||||||
firstName: "first",
|
firstName: faker.person.firstName(),
|
||||||
lastName: "last",
|
lastName: faker.person.lastName(),
|
||||||
organization: "",
|
organization: "",
|
||||||
|
phone: faker.phone.number(),
|
||||||
});
|
});
|
||||||
await user.ensure(page);
|
await user.ensure(page);
|
||||||
await use(user);
|
await use(user);
|
||||||
@@ -25,7 +27,7 @@ test("username and passkey login", async ({ user, page }) => {
|
|||||||
await loginScreenExpect(page, user.getFullName());
|
await loginScreenExpect(page, user.getFullName());
|
||||||
});
|
});
|
||||||
|
|
||||||
test("username and passkey login, if passkey enabled", async ({ user, page }) => {
|
test("username and passkey login, if passkey enabled", async ({ page }) => {
|
||||||
// Given passkey is enabled on the organization of the user
|
// Given passkey is enabled on the organization of the user
|
||||||
// Given the user has only passkey enabled as authentication
|
// Given the user has only passkey enabled as authentication
|
||||||
// enter username
|
// enter username
|
||||||
@@ -34,7 +36,7 @@ test("username and passkey login, if passkey enabled", async ({ user, page }) =>
|
|||||||
// user is redirected to app
|
// user is redirected to app
|
||||||
});
|
});
|
||||||
|
|
||||||
test("username and passkey login, multiple auth methods", async ({ user, page }) => {
|
test("username and passkey login, multiple auth methods", async ({ page }) => {
|
||||||
// Given passkey and password is enabled on the organization of the user
|
// Given passkey and password is enabled on the organization of the user
|
||||||
// Given the user has password and passkey registered
|
// Given the user has password and passkey registered
|
||||||
// enter username
|
// enter username
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { faker } from "@faker-js/faker";
|
||||||
import { test as base } from "@playwright/test";
|
import { test as base } from "@playwright/test";
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
@@ -12,11 +13,12 @@ dotenv.config({ path: path.resolve(__dirname, ".env.local") });
|
|||||||
const test = base.extend<{ user: PasswordUser }>({
|
const test = base.extend<{ user: PasswordUser }>({
|
||||||
user: async ({ page }, use) => {
|
user: async ({ page }, use) => {
|
||||||
const user = new PasswordUser({
|
const user = new PasswordUser({
|
||||||
email: "password-changed@example.com",
|
email: faker.internet.email(),
|
||||||
firstName: "first",
|
firstName: faker.person.firstName(),
|
||||||
lastName: "last",
|
lastName: faker.person.lastName(),
|
||||||
password: "Password1!",
|
|
||||||
organization: "",
|
organization: "",
|
||||||
|
phone: faker.phone.number(),
|
||||||
|
password: "Password1!",
|
||||||
});
|
});
|
||||||
await user.ensure(page);
|
await user.ensure(page);
|
||||||
await use(user);
|
await use(user);
|
||||||
|
|||||||
@@ -1,6 +1,33 @@
|
|||||||
import { test } from "@playwright/test";
|
import { faker } from "@faker-js/faker";
|
||||||
|
import { test as base } from "@playwright/test";
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
import path from "path";
|
||||||
|
import { code, codeFromSink, codeResend } from "./code";
|
||||||
|
import { codeScreenExpect } from "./code-screen";
|
||||||
|
import { loginScreenExpect, loginWithPassword, loginWithPasswordAndEmailOTP } from "./login";
|
||||||
|
import { OtpType, PasswordUserWithOTP } from "./user";
|
||||||
|
|
||||||
test("username, password and email otp login, enter code manually", async ({ page }) => {
|
// Read from ".env" file.
|
||||||
|
dotenv.config({ path: path.resolve(__dirname, ".env.local") });
|
||||||
|
|
||||||
|
const test = base.extend<{ user: PasswordUserWithOTP; sink: any }>({
|
||||||
|
user: async ({ page }, use) => {
|
||||||
|
const user = new PasswordUserWithOTP({
|
||||||
|
email: faker.internet.email(),
|
||||||
|
firstName: faker.person.firstName(),
|
||||||
|
lastName: faker.person.lastName(),
|
||||||
|
organization: "",
|
||||||
|
phone: faker.phone.number(),
|
||||||
|
password: "Password1!",
|
||||||
|
type: OtpType.email,
|
||||||
|
});
|
||||||
|
|
||||||
|
await user.ensure(page);
|
||||||
|
await use(user);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test("username, password and email otp login, enter code manually", async ({ user, page }) => {
|
||||||
// Given email otp is enabled on the organization of the user
|
// Given email otp is enabled on the organization of the user
|
||||||
// Given the user has only email otp configured as second factor
|
// Given the user has only email otp configured as second factor
|
||||||
// User enters username
|
// User enters username
|
||||||
@@ -8,6 +35,8 @@ test("username, password and email otp login, enter code manually", async ({ pag
|
|||||||
// User receives an email with a verification code
|
// User receives an email with a verification code
|
||||||
// User enters the code into the ui
|
// User enters the code into the ui
|
||||||
// User is redirected to the app (default redirect url)
|
// User is redirected to the app (default redirect url)
|
||||||
|
await loginWithPasswordAndEmailOTP(page, user.getUsername(), user.getPassword(), user.getUsername());
|
||||||
|
await loginScreenExpect(page, user.getFullName());
|
||||||
});
|
});
|
||||||
|
|
||||||
test("username, password and email otp login, click link in email", async ({ page }) => {
|
test("username, password and email otp login, click link in email", async ({ page }) => {
|
||||||
@@ -20,7 +49,7 @@ test("username, password and email otp login, click link in email", async ({ pag
|
|||||||
// User is redirected to the app (default redirect url)
|
// User is redirected to the app (default redirect url)
|
||||||
});
|
});
|
||||||
|
|
||||||
test("username, password and email otp login, resend code", async ({ page }) => {
|
test("username, password and email otp login, resend code", async ({ user, page }) => {
|
||||||
// Given email otp is enabled on the organization of the user
|
// Given email otp is enabled on the organization of the user
|
||||||
// Given the user has only email otp configured as second factor
|
// Given the user has only email otp configured as second factor
|
||||||
// User enters username
|
// User enters username
|
||||||
@@ -30,16 +59,24 @@ test("username, password and email otp login, resend code", async ({ page }) =>
|
|||||||
// User receives a new email with a verification code
|
// User receives a new email with a verification code
|
||||||
// User enters the new code in the ui
|
// User enters the new code in the ui
|
||||||
// User is redirected to the app (default redirect url)
|
// User is redirected to the app (default redirect url)
|
||||||
|
await loginWithPassword(page, user.getUsername(), user.getPassword());
|
||||||
|
await codeResend(page);
|
||||||
|
await codeFromSink(page, user.getUsername());
|
||||||
|
await loginScreenExpect(page, user.getFullName());
|
||||||
});
|
});
|
||||||
|
|
||||||
test("username, password and email otp login, wrong code", async ({ page }) => {
|
test("username, password and email otp login, wrong code", async ({ user, page }) => {
|
||||||
// Given email otp is enabled on the organization of the user
|
// Given email otp is enabled on the organization of the user
|
||||||
// Given the user has only email otp configured as second factor
|
// Given the user has only email otp configured as second factor
|
||||||
// User enters username
|
// User enters username
|
||||||
// User enters password
|
// User enters password
|
||||||
// User receives an email with a verification code
|
// User receives an email with a verification code
|
||||||
// User enters a wrond code
|
// User enters a wrong code
|
||||||
// Error message - "Invalid code" is shown
|
// Error message - "Invalid code" is shown
|
||||||
|
const c = "wrongcode";
|
||||||
|
await loginWithPassword(page, user.getUsername(), user.getPassword());
|
||||||
|
await code(page, c);
|
||||||
|
await codeScreenExpect(page, c);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("username, password and email otp login, multiple mfa options", async ({ page }) => {
|
test("username, password and email otp login, multiple mfa options", async ({ page }) => {
|
||||||
@@ -49,7 +86,7 @@ test("username, password and email otp login, multiple mfa options", async ({ pa
|
|||||||
// User enters password
|
// User enters password
|
||||||
// User receives an email with a verification code
|
// User receives an email with a verification code
|
||||||
// User clicks button to use sms otp as second factor
|
// User clicks button to use sms otp as second factor
|
||||||
// User receives an sms with a verification code
|
// User receives a sms with a verification code
|
||||||
// User enters code in ui
|
// User enters code in ui
|
||||||
// User is redirected to the app (default redirect url)
|
// User is redirected to the app (default redirect url)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,30 @@
|
|||||||
import { test } from "@playwright/test";
|
import { faker } from "@faker-js/faker";
|
||||||
|
import { test as base } from "@playwright/test";
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
import path from "path";
|
||||||
|
import { OtpType, PasswordUserWithOTP } from "./user";
|
||||||
|
|
||||||
test("username, password and sms otp login", async ({ page }) => {
|
// Read from ".env" file.
|
||||||
|
dotenv.config({ path: path.resolve(__dirname, ".env.local") });
|
||||||
|
|
||||||
|
const test = base.extend<{ user: PasswordUserWithOTP; sink: any }>({
|
||||||
|
user: async ({ page }, use) => {
|
||||||
|
const user = new PasswordUserWithOTP({
|
||||||
|
email: faker.internet.email(),
|
||||||
|
firstName: faker.person.firstName(),
|
||||||
|
lastName: faker.person.lastName(),
|
||||||
|
organization: "",
|
||||||
|
phone: faker.phone.number(),
|
||||||
|
password: "Password1!",
|
||||||
|
type: OtpType.sms,
|
||||||
|
});
|
||||||
|
|
||||||
|
await user.ensure(page);
|
||||||
|
await use(user);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test("username, password and sms otp login, enter code manually", async ({ user, page }) => {
|
||||||
// Given sms otp is enabled on the organization of the user
|
// Given sms otp is enabled on the organization of the user
|
||||||
// Given the user has only sms otp configured as second factor
|
// Given the user has only sms otp configured as second factor
|
||||||
// User enters username
|
// User enters username
|
||||||
@@ -8,9 +32,13 @@ test("username, password and sms otp login", async ({ page }) => {
|
|||||||
// User receives a sms with a verification code
|
// User receives a sms with a verification code
|
||||||
// User enters the code into the ui
|
// User enters the code into the ui
|
||||||
// User is redirected to the app (default redirect url)
|
// User is redirected to the app (default redirect url)
|
||||||
|
/* TODO fix on login, that sms is sent
|
||||||
|
await loginWithPasswordAndPhoneOTP(page, user.getUsername(), user.getPassword(), user.getPhone());
|
||||||
|
await loginScreenExpect(page, user.getFullName());
|
||||||
|
*/
|
||||||
});
|
});
|
||||||
|
|
||||||
test("username, password and sms otp login, resend code", async ({ page }) => {
|
test("username, password and sms otp login, resend code", async ({ user, page }) => {
|
||||||
// Given sms otp is enabled on the organization of the user
|
// Given sms otp is enabled on the organization of the user
|
||||||
// Given the user has only sms otp configured as second factor
|
// Given the user has only sms otp configured as second factor
|
||||||
// User enters username
|
// User enters username
|
||||||
@@ -19,9 +47,14 @@ test("username, password and sms otp login, resend code", async ({ page }) => {
|
|||||||
// User clicks resend code
|
// User clicks resend code
|
||||||
// User receives a new sms with a verification code
|
// User receives a new sms with a verification code
|
||||||
// User is redirected to the app (default redirect url)
|
// User is redirected to the app (default redirect url)
|
||||||
|
/* TODO fix on login, that sms is sent
|
||||||
|
await loginWithPassword(page, user.getUsername(), user.getPassword());
|
||||||
|
await loginWithPasswordAndPhoneOTP(page, user.getUsername(), user.getPassword(), user.getPhone());
|
||||||
|
await loginScreenExpect(page, user.getFullName());
|
||||||
|
*/
|
||||||
});
|
});
|
||||||
|
|
||||||
test("username, password and sms otp login, wrong code", async ({ page }) => {
|
test("username, password and sms otp login, wrong code", async ({ user, page }) => {
|
||||||
// Given sms otp is enabled on the organization of the user
|
// Given sms otp is enabled on the organization of the user
|
||||||
// Given the user has only sms otp configured as second factor
|
// Given the user has only sms otp configured as second factor
|
||||||
// User enters username
|
// User enters username
|
||||||
@@ -29,4 +62,10 @@ test("username, password and sms otp login, wrong code", async ({ page }) => {
|
|||||||
// User receives a sms with a verification code
|
// User receives a sms with a verification code
|
||||||
// User enters a wrong code
|
// User enters a wrong code
|
||||||
// Error message - "Invalid code" is shown
|
// Error message - "Invalid code" is shown
|
||||||
|
/* TODO fix on login, that sms is sent
|
||||||
|
const c = "wrongcode";
|
||||||
|
await loginWithPassword(page, user.getUsername(), user.getPassword());
|
||||||
|
await code(page, c);
|
||||||
|
await codeScreenExpect(page, c);
|
||||||
|
*/
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { faker } from "@faker-js/faker";
|
||||||
import { test as base } from "@playwright/test";
|
import { test as base } from "@playwright/test";
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
@@ -14,11 +15,12 @@ dotenv.config({ path: path.resolve(__dirname, ".env.local") });
|
|||||||
const test = base.extend<{ user: PasswordUser }>({
|
const test = base.extend<{ user: PasswordUser }>({
|
||||||
user: async ({ page }, use) => {
|
user: async ({ page }, use) => {
|
||||||
const user = new PasswordUser({
|
const user = new PasswordUser({
|
||||||
email: "password@example.com",
|
email: faker.internet.email(),
|
||||||
firstName: "first",
|
firstName: faker.person.firstName(),
|
||||||
lastName: "last",
|
lastName: faker.person.lastName(),
|
||||||
password: "Password1!",
|
|
||||||
organization: "",
|
organization: "",
|
||||||
|
phone: faker.phone.number(),
|
||||||
|
password: "Password1!",
|
||||||
});
|
});
|
||||||
await user.ensure(page);
|
await user.ensure(page);
|
||||||
await use(user);
|
await use(user);
|
||||||
|
|||||||
@@ -214,6 +214,7 @@ export function LoginOTP({
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
data-testid="resend-button"
|
||||||
>
|
>
|
||||||
{t("verify.resendCode")}
|
{t("verify.resendCode")}
|
||||||
</button>
|
</button>
|
||||||
@@ -226,11 +227,12 @@ export function LoginOTP({
|
|||||||
{...register("code", { required: "This field is required" })}
|
{...register("code", { required: "This field is required" })}
|
||||||
label="Code"
|
label="Code"
|
||||||
autoComplete="one-time-code"
|
autoComplete="one-time-code"
|
||||||
|
data-testid="code-text-input"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="py-4">
|
<div className="py-4" data-testid="error">
|
||||||
<Alert>{error}</Alert>
|
<Alert>{error}</Alert>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -21,7 +21,8 @@
|
|||||||
"changeset": "changeset",
|
"changeset": "changeset",
|
||||||
"version-packages": "changeset version",
|
"version-packages": "changeset version",
|
||||||
"release": "turbo run build --filter=login^... && changeset publish",
|
"release": "turbo run build --filter=login^... && changeset publish",
|
||||||
"run-zitadel": "docker compose -f ./acceptance/docker-compose.yaml run setup"
|
"run-zitadel": "docker compose -f ./acceptance/docker-compose.yaml run setup",
|
||||||
|
"run-sink": "docker compose -f ./acceptance/docker-compose.yaml up -d sink"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"overrides": {
|
"overrides": {
|
||||||
@@ -29,6 +30,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@faker-js/faker": "^9.2.0",
|
||||||
"@changesets/cli": "^2.27.9",
|
"@changesets/cli": "^2.27.9",
|
||||||
"@playwright/test": "^1.48.2",
|
"@playwright/test": "^1.48.2",
|
||||||
"@types/node": "^22.9.0",
|
"@types/node": "^22.9.0",
|
||||||
|
|||||||
49
pnpm-lock.yaml
generated
49
pnpm-lock.yaml
generated
@@ -14,6 +14,9 @@ importers:
|
|||||||
'@changesets/cli':
|
'@changesets/cli':
|
||||||
specifier: ^2.27.9
|
specifier: ^2.27.9
|
||||||
version: 2.27.9
|
version: 2.27.9
|
||||||
|
'@faker-js/faker':
|
||||||
|
specifier: ^9.2.0
|
||||||
|
version: 9.2.0
|
||||||
'@playwright/test':
|
'@playwright/test':
|
||||||
specifier: ^1.48.2
|
specifier: ^1.48.2
|
||||||
version: 1.48.2
|
version: 1.48.2
|
||||||
@@ -28,7 +31,7 @@ importers:
|
|||||||
version: link:packages/zitadel-prettier-config
|
version: link:packages/zitadel-prettier-config
|
||||||
axios:
|
axios:
|
||||||
specifier: ^1.7.7
|
specifier: ^1.7.7
|
||||||
version: 1.7.7
|
version: 1.7.7(debug@4.3.7)
|
||||||
dotenv:
|
dotenv:
|
||||||
specifier: ^16.4.5
|
specifier: ^16.4.5
|
||||||
version: 16.4.5
|
version: 16.4.5
|
||||||
@@ -847,6 +850,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==}
|
resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
|
|
||||||
|
'@faker-js/faker@9.2.0':
|
||||||
|
resolution: {integrity: sha512-ulqQu4KMr1/sTFIYvqSdegHT8NIkt66tFAkugGnHA+1WAfEn6hMzNR+svjXGFRVLnapxvej67Z/LwchFrnLBUg==}
|
||||||
|
engines: {node: '>=18.0.0', npm: '>=9.0.0'}
|
||||||
|
|
||||||
'@fastify/busboy@2.1.1':
|
'@fastify/busboy@2.1.1':
|
||||||
resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==}
|
resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
@@ -4633,7 +4640,7 @@ snapshots:
|
|||||||
'@babel/traverse': 7.25.9
|
'@babel/traverse': 7.25.9
|
||||||
'@babel/types': 7.26.0
|
'@babel/types': 7.26.0
|
||||||
convert-source-map: 2.0.0
|
convert-source-map: 2.0.0
|
||||||
debug: 4.3.7
|
debug: 4.3.7(supports-color@5.5.0)
|
||||||
gensync: 1.0.0-beta.2
|
gensync: 1.0.0-beta.2
|
||||||
json5: 2.2.3
|
json5: 2.2.3
|
||||||
semver: 6.3.1
|
semver: 6.3.1
|
||||||
@@ -4720,7 +4727,7 @@ snapshots:
|
|||||||
'@babel/parser': 7.26.2
|
'@babel/parser': 7.26.2
|
||||||
'@babel/template': 7.25.9
|
'@babel/template': 7.25.9
|
||||||
'@babel/types': 7.26.0
|
'@babel/types': 7.26.0
|
||||||
debug: 4.3.7
|
debug: 4.3.7(supports-color@5.5.0)
|
||||||
globals: 11.12.0
|
globals: 11.12.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -5110,7 +5117,7 @@ snapshots:
|
|||||||
'@eslint/eslintrc@2.1.4':
|
'@eslint/eslintrc@2.1.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
ajv: 6.12.6
|
ajv: 6.12.6
|
||||||
debug: 4.3.7
|
debug: 4.3.7(supports-color@5.5.0)
|
||||||
espree: 9.6.1
|
espree: 9.6.1
|
||||||
globals: 13.24.0
|
globals: 13.24.0
|
||||||
ignore: 5.3.2
|
ignore: 5.3.2
|
||||||
@@ -5123,6 +5130,8 @@ snapshots:
|
|||||||
|
|
||||||
'@eslint/js@8.57.1': {}
|
'@eslint/js@8.57.1': {}
|
||||||
|
|
||||||
|
'@faker-js/faker@9.2.0': {}
|
||||||
|
|
||||||
'@fastify/busboy@2.1.1': {}
|
'@fastify/busboy@2.1.1': {}
|
||||||
|
|
||||||
'@floating-ui/core@1.6.8':
|
'@floating-ui/core@1.6.8':
|
||||||
@@ -5209,7 +5218,7 @@ snapshots:
|
|||||||
'@humanwhocodes/config-array@0.13.0':
|
'@humanwhocodes/config-array@0.13.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@humanwhocodes/object-schema': 2.0.3
|
'@humanwhocodes/object-schema': 2.0.3
|
||||||
debug: 4.3.7
|
debug: 4.3.7(supports-color@5.5.0)
|
||||||
minimatch: 3.1.2
|
minimatch: 3.1.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -5756,7 +5765,7 @@ snapshots:
|
|||||||
|
|
||||||
agent-base@7.1.1:
|
agent-base@7.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.3.7
|
debug: 4.3.7(supports-color@5.5.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -5931,14 +5940,6 @@ snapshots:
|
|||||||
|
|
||||||
axe-core@4.10.0: {}
|
axe-core@4.10.0: {}
|
||||||
|
|
||||||
axios@1.7.7:
|
|
||||||
dependencies:
|
|
||||||
follow-redirects: 1.15.6
|
|
||||||
form-data: 4.0.0
|
|
||||||
proxy-from-env: 1.1.0
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- debug
|
|
||||||
|
|
||||||
axios@1.7.7(debug@4.3.7):
|
axios@1.7.7(debug@4.3.7):
|
||||||
dependencies:
|
dependencies:
|
||||||
follow-redirects: 1.15.6(debug@4.3.7)
|
follow-redirects: 1.15.6(debug@4.3.7)
|
||||||
@@ -6285,10 +6286,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.2
|
ms: 2.1.2
|
||||||
|
|
||||||
debug@4.3.7:
|
|
||||||
dependencies:
|
|
||||||
ms: 2.1.3
|
|
||||||
|
|
||||||
debug@4.3.7(supports-color@5.5.0):
|
debug@4.3.7(supports-color@5.5.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
@@ -6765,7 +6762,7 @@ snapshots:
|
|||||||
ajv: 6.12.6
|
ajv: 6.12.6
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
cross-spawn: 7.0.3
|
cross-spawn: 7.0.3
|
||||||
debug: 4.3.7
|
debug: 4.3.7(supports-color@5.5.0)
|
||||||
doctrine: 3.0.0
|
doctrine: 3.0.0
|
||||||
escape-string-regexp: 4.0.0
|
escape-string-regexp: 4.0.0
|
||||||
eslint-scope: 7.2.2
|
eslint-scope: 7.2.2
|
||||||
@@ -6955,8 +6952,6 @@ snapshots:
|
|||||||
|
|
||||||
flatted@3.3.1: {}
|
flatted@3.3.1: {}
|
||||||
|
|
||||||
follow-redirects@1.15.6: {}
|
|
||||||
|
|
||||||
follow-redirects@1.15.6(debug@4.3.7):
|
follow-redirects@1.15.6(debug@4.3.7):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
debug: 4.3.7(supports-color@5.5.0)
|
debug: 4.3.7(supports-color@5.5.0)
|
||||||
@@ -7191,7 +7186,7 @@ snapshots:
|
|||||||
http-proxy-agent@7.0.2:
|
http-proxy-agent@7.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 7.1.1
|
agent-base: 7.1.1
|
||||||
debug: 4.3.7
|
debug: 4.3.7(supports-color@5.5.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -7211,7 +7206,7 @@ snapshots:
|
|||||||
https-proxy-agent@7.0.5:
|
https-proxy-agent@7.0.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 7.1.1
|
agent-base: 7.1.1
|
||||||
debug: 4.3.7
|
debug: 4.3.7(supports-color@5.5.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -8735,7 +8730,7 @@ snapshots:
|
|||||||
cac: 6.7.14
|
cac: 6.7.14
|
||||||
chokidar: 4.0.1
|
chokidar: 4.0.1
|
||||||
consola: 3.2.3
|
consola: 3.2.3
|
||||||
debug: 4.3.7
|
debug: 4.3.7(supports-color@5.5.0)
|
||||||
esbuild: 0.24.0
|
esbuild: 0.24.0
|
||||||
joycon: 3.1.1
|
joycon: 3.1.1
|
||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
@@ -8893,7 +8888,7 @@ snapshots:
|
|||||||
vite-node@2.1.4(@types/node@22.9.0)(sass@1.80.7):
|
vite-node@2.1.4(@types/node@22.9.0)(sass@1.80.7):
|
||||||
dependencies:
|
dependencies:
|
||||||
cac: 6.7.14
|
cac: 6.7.14
|
||||||
debug: 4.3.7
|
debug: 4.3.7(supports-color@5.5.0)
|
||||||
pathe: 1.1.2
|
pathe: 1.1.2
|
||||||
vite: 5.4.11(@types/node@22.9.0)(sass@1.80.7)
|
vite: 5.4.11(@types/node@22.9.0)(sass@1.80.7)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -8909,7 +8904,7 @@ snapshots:
|
|||||||
|
|
||||||
vite-tsconfig-paths@5.1.2(typescript@5.6.3)(vite@5.4.11(@types/node@22.9.0)(sass@1.80.7)):
|
vite-tsconfig-paths@5.1.2(typescript@5.6.3)(vite@5.4.11(@types/node@22.9.0)(sass@1.80.7)):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.3.7
|
debug: 4.3.7(supports-color@5.5.0)
|
||||||
globrex: 0.1.2
|
globrex: 0.1.2
|
||||||
tsconfck: 3.1.4(typescript@5.6.3)
|
tsconfck: 3.1.4(typescript@5.6.3)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
@@ -8938,7 +8933,7 @@ snapshots:
|
|||||||
'@vitest/spy': 2.1.4
|
'@vitest/spy': 2.1.4
|
||||||
'@vitest/utils': 2.1.4
|
'@vitest/utils': 2.1.4
|
||||||
chai: 5.1.2
|
chai: 5.1.2
|
||||||
debug: 4.3.7
|
debug: 4.3.7(supports-color@5.5.0)
|
||||||
expect-type: 1.1.0
|
expect-type: 1.1.0
|
||||||
magic-string: 0.30.12
|
magic-string: 0.30.12
|
||||||
pathe: 1.1.2
|
pathe: 1.1.2
|
||||||
|
|||||||
Reference in New Issue
Block a user