snapshot of https://github.com/ory/kratos.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
This commit is contained in:
62
test/e2e/playwright/actions/db.ts
Normal file
62
test/e2e/playwright/actions/db.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright © 2024 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { execSync } from "child_process"
|
||||
|
||||
/**
|
||||
* Extracts the SQLite file path from the TEST_DATABASE_SQLITE env var.
|
||||
*
|
||||
* Expected format: sqlite:///path/to/db.sqlite?_fk=true
|
||||
*/
|
||||
export function getSqliteDbPath(): string {
|
||||
const dsn = process.env["TEST_DATABASE_SQLITE"]
|
||||
if (!dsn) {
|
||||
throw new Error(
|
||||
"TEST_DATABASE_SQLITE is not set — cannot manipulate legacy data",
|
||||
)
|
||||
}
|
||||
|
||||
// Strip the sqlite:// prefix and any query parameters
|
||||
const withoutScheme = dsn.replace(/^sqlite:\/\//, "")
|
||||
const pathOnly = withoutScheme.split("?")[0]
|
||||
return pathOnly
|
||||
}
|
||||
|
||||
/**
|
||||
* Downgrades an identity created via admin API to simulate a legacy (pre-normalization)
|
||||
* record in the database. This reverts the E.164 normalization that the admin API applies
|
||||
* on creation, making the DB state match what an old Kratos version would have stored.
|
||||
*
|
||||
* Updates:
|
||||
* - identity_credential_identifiers: replaces normalized identifier with legacy format
|
||||
* - identity_credentials: sets version to 0 (pre-normalization)
|
||||
* - identity_verifiable_addresses: replaces normalized value with legacy format
|
||||
* - identity_recovery_addresses: replaces normalized value with legacy format
|
||||
*/
|
||||
export function downgradeLegacyIdentity(
|
||||
identityId: string,
|
||||
legacyPhone: string,
|
||||
normalizedPhone: string,
|
||||
) {
|
||||
const dbPath = getSqliteDbPath()
|
||||
const escapedLegacy = legacyPhone.replace(/'/g, "''")
|
||||
const escapedNormalized = normalizedPhone.replace(/'/g, "''")
|
||||
|
||||
const queries = [
|
||||
// Revert credential identifiers to legacy format
|
||||
`UPDATE identity_credential_identifiers SET identifier = '${escapedLegacy}' WHERE identifier = '${escapedNormalized}' AND identity_credential_id IN (SELECT id FROM identity_credentials WHERE identity_id = '${identityId}');`,
|
||||
// Set credential version to 0 (pre-normalization)
|
||||
`UPDATE identity_credentials SET version = 0 WHERE identity_id = '${identityId}';`,
|
||||
// Revert verifiable addresses
|
||||
`UPDATE identity_verifiable_addresses SET value = '${escapedLegacy}' WHERE value = '${escapedNormalized}' AND identity_id = '${identityId}';`,
|
||||
// Revert recovery addresses
|
||||
`UPDATE identity_recovery_addresses SET value = '${escapedLegacy}' WHERE value = '${escapedNormalized}' AND identity_id = '${identityId}';`,
|
||||
]
|
||||
|
||||
for (const query of queries) {
|
||||
execSync(`sqlite3 "${dbPath}" "${query}"`, {
|
||||
encoding: "utf-8",
|
||||
timeout: 5000,
|
||||
})
|
||||
}
|
||||
}
|
||||
100
test/e2e/playwright/actions/identity.ts
Normal file
100
test/e2e/playwright/actions/identity.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright © 2024 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { faker } from "@faker-js/faker"
|
||||
import { APIRequestContext } from "@playwright/test"
|
||||
import { CreateIdentityBody } from "@ory/kratos-client"
|
||||
import { generatePhoneNumber, CountryNames } from "phone-number-generator-js"
|
||||
import { expect } from "../fixtures"
|
||||
|
||||
export async function createIdentity(
|
||||
request: APIRequestContext,
|
||||
data: Partial<CreateIdentityBody>,
|
||||
) {
|
||||
const resp = await request.post("http://localhost:4434/admin/identities", {
|
||||
data,
|
||||
})
|
||||
expect(resp.status()).toBe(201)
|
||||
return await resp.json()
|
||||
}
|
||||
|
||||
export async function createIdentityWithPhoneNumber(
|
||||
request: APIRequestContext,
|
||||
) {
|
||||
const phone = generatePhoneNumber({
|
||||
countryName: CountryNames.Germany,
|
||||
withoutCountryCode: false,
|
||||
})
|
||||
return {
|
||||
identity: await createIdentity(request, {
|
||||
schema_id: "sms",
|
||||
traits: {
|
||||
phone,
|
||||
},
|
||||
}),
|
||||
phone,
|
||||
}
|
||||
}
|
||||
|
||||
export async function createIdentityWithEmail(request: APIRequestContext) {
|
||||
const email = faker.internet.email({ provider: "ory.sh" })
|
||||
return {
|
||||
identity: await createIdentity(request, {
|
||||
schema_id: "email",
|
||||
traits: {
|
||||
email,
|
||||
website: faker.internet.url(),
|
||||
},
|
||||
}),
|
||||
email,
|
||||
}
|
||||
}
|
||||
|
||||
export async function createIdentityWithPhoneAndPassword(
|
||||
request: APIRequestContext,
|
||||
phone: string,
|
||||
password: string,
|
||||
schemaId = "sms-password",
|
||||
) {
|
||||
return {
|
||||
identity: await createIdentity(request, {
|
||||
schema_id: schemaId,
|
||||
traits: {
|
||||
phone,
|
||||
},
|
||||
credentials: {
|
||||
password: {
|
||||
config: {
|
||||
password,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
phone,
|
||||
password,
|
||||
}
|
||||
}
|
||||
|
||||
export async function createIdentityWithPassword(request: APIRequestContext) {
|
||||
const email = faker.internet.email({ provider: "ory.sh" })
|
||||
const password = faker.internet.password()
|
||||
return {
|
||||
identity: await createIdentity(request, {
|
||||
schema_id: "email",
|
||||
traits: {
|
||||
email,
|
||||
website: faker.internet.url(),
|
||||
},
|
||||
|
||||
credentials: {
|
||||
password: {
|
||||
config: {
|
||||
password,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
email,
|
||||
password,
|
||||
}
|
||||
}
|
||||
61
test/e2e/playwright/actions/login.ts
Normal file
61
test/e2e/playwright/actions/login.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright © 2024 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { APIRequestContext } from "@playwright/test"
|
||||
import { findCsrfToken } from "../lib/helper"
|
||||
import { LoginFlow, LogoutFlow, Session } from "@ory/kratos-client"
|
||||
import { expectJSONResponse } from "../lib/request"
|
||||
import { expect } from "../fixtures"
|
||||
|
||||
export async function loginWithPassword(
|
||||
user: { password: string; traits: { email: string } },
|
||||
r: APIRequestContext,
|
||||
baseUrl: string,
|
||||
): Promise<void> {
|
||||
const { ui } = await expectJSONResponse<LoginFlow>(
|
||||
await r.get(baseUrl + "/self-service/login/browser", {
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
}),
|
||||
{
|
||||
message: "Initializing login flow failed",
|
||||
},
|
||||
)
|
||||
|
||||
const res = await r.post(ui.action, {
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
data: {
|
||||
identifier: user.traits.email,
|
||||
password: user.password,
|
||||
method: "password",
|
||||
csrf_token: findCsrfToken(ui),
|
||||
},
|
||||
})
|
||||
const { session } = await expectJSONResponse<{ session: Session }>(res)
|
||||
expect(session?.identity?.traits.email).toEqual(user.traits.email)
|
||||
expect(
|
||||
res.headersArray().find(
|
||||
({ name, value }) =>
|
||||
name.toLowerCase() === "set-cookie" &&
|
||||
(value.indexOf("ory_session_") > -1 || // Ory Network
|
||||
value.indexOf("ory_kratos_session") > -1), // Locally hosted
|
||||
),
|
||||
).toBeDefined()
|
||||
}
|
||||
|
||||
export async function logoutUrl(r: APIRequestContext, baseUrl: string) {
|
||||
const { logout_url } = await expectJSONResponse<LogoutFlow>(
|
||||
await r.get(baseUrl + "/self-service/logout/browser", {
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
}),
|
||||
{
|
||||
message: "Logout failed",
|
||||
},
|
||||
)
|
||||
return logout_url
|
||||
}
|
||||
39
test/e2e/playwright/actions/mail.ts
Normal file
39
test/e2e/playwright/actions/mail.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import mailhog from "mailhog"
|
||||
import retry from "async-retry"
|
||||
|
||||
const mh = mailhog({
|
||||
basePath: "http://localhost:8025/api",
|
||||
})
|
||||
|
||||
type searchProps = {
|
||||
query: string
|
||||
kind: "to" | "from" | "containing"
|
||||
/**
|
||||
*
|
||||
* @param message an email message
|
||||
* @returns decide whether to include the message in the result
|
||||
*/
|
||||
filter?: (message: mailhog.Message) => boolean
|
||||
}
|
||||
|
||||
export function search({ query, kind, filter }: searchProps) {
|
||||
return retry(
|
||||
async () => {
|
||||
const res = await mh.search(query, kind)
|
||||
if (res.total === 0) {
|
||||
throw new Error("no emails found")
|
||||
}
|
||||
const result = filter ? res.items.filter(filter) : res.items
|
||||
if (result.length === 0) {
|
||||
throw new Error("no emails found")
|
||||
}
|
||||
return result
|
||||
},
|
||||
{
|
||||
retries: 3,
|
||||
},
|
||||
)
|
||||
}
|
||||
38
test/e2e/playwright/actions/session.ts
Normal file
38
test/e2e/playwright/actions/session.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright © 2024 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { APIRequestContext, expect } from "@playwright/test"
|
||||
import { Session } from "@ory/kratos-client"
|
||||
|
||||
export async function hasSession(
|
||||
r: APIRequestContext,
|
||||
kratosPublicURL: string,
|
||||
): Promise<void> {
|
||||
const resp = await r.get(kratosPublicURL + "/sessions/whoami", {
|
||||
failOnStatusCode: true,
|
||||
})
|
||||
const session = await resp.json()
|
||||
expect(session).toBeDefined()
|
||||
expect(session.active).toBe(true)
|
||||
}
|
||||
|
||||
export async function getSession(
|
||||
r: APIRequestContext,
|
||||
kratosPublicURL: string,
|
||||
): Promise<Session> {
|
||||
const resp = await r.get(kratosPublicURL + "/sessions/whoami", {
|
||||
failOnStatusCode: true,
|
||||
})
|
||||
return resp.json()
|
||||
}
|
||||
|
||||
export async function hasNoSession(
|
||||
r: APIRequestContext,
|
||||
kratosPublicURL: string,
|
||||
): Promise<void> {
|
||||
const resp = await r.get(kratosPublicURL + "/sessions/whoami", {
|
||||
failOnStatusCode: false,
|
||||
})
|
||||
expect(resp.status()).toBe(401)
|
||||
return resp.json()
|
||||
}
|
||||
57
test/e2e/playwright/actions/webhook.ts
Normal file
57
test/e2e/playwright/actions/webhook.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright © 2024 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { request } from "@playwright/test"
|
||||
import retry from "promise-retry"
|
||||
import { retryOptions } from "../lib/config"
|
||||
|
||||
export const WEBHOOK_TARGET = "http://127.0.0.1:4471"
|
||||
|
||||
const baseUrl = WEBHOOK_TARGET
|
||||
|
||||
/**
|
||||
* Fetches a documented (hopefully) created by web hook
|
||||
*
|
||||
* @param key
|
||||
*/
|
||||
export async function fetchDocument(key: string) {
|
||||
const r = await request.newContext()
|
||||
|
||||
return retry(async (retry) => {
|
||||
const res = await r.get(documentUrl(key))
|
||||
if (res.status() !== 200) {
|
||||
const body = await res.text()
|
||||
const message = `Expected response code 200 but received ${res.status()}: ${body}`
|
||||
return retry(message)
|
||||
}
|
||||
return await res.json()
|
||||
}, retryOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a documented (hopefully) created by web hook
|
||||
*
|
||||
* @param key
|
||||
*/
|
||||
export async function deleteDocument(key: string) {
|
||||
const r = await request.newContext()
|
||||
|
||||
return retry(async (retry) => {
|
||||
const res = await r.delete(documentUrl(key))
|
||||
if (res.status() !== 204) {
|
||||
const body = await res.text()
|
||||
const message = `Expected response code 204 but received ${res.status()}: ${body}`
|
||||
return retry(message)
|
||||
}
|
||||
return
|
||||
}, retryOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL for a specific document
|
||||
*
|
||||
* @param key
|
||||
*/
|
||||
export function documentUrl(key: string) {
|
||||
return `${baseUrl}/documents/${key}`
|
||||
}
|
||||
Reference in New Issue
Block a user