mirror of
https://github.com/fastapi/full-stack-fastapi-template.git
synced 2026-11-03 20:36:27 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 698bdfcc2a | |||
| 8063fe54f1 | |||
| 4d1a75de20 | |||
| 68adb40d37 | |||
| 7c11b644a3 | |||
| 7cabd4bfa4 |
@@ -6,5 +6,6 @@ frontend/blob-report
|
||||
frontend/dist
|
||||
frontend/node_modules
|
||||
frontend/playwright-report
|
||||
frontend/playwright/.auth
|
||||
frontend/test-results
|
||||
node_modules
|
||||
|
||||
@@ -28,11 +28,9 @@ jobs:
|
||||
with:
|
||||
python-version-file: ".python-version"
|
||||
- name: Setup uv
|
||||
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
||||
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
||||
with:
|
||||
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
|
||||
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
|
||||
version: "0.11.18"
|
||||
version: "latest-known"
|
||||
cache-dependency-glob: |
|
||||
pyproject.toml
|
||||
uv.lock
|
||||
|
||||
@@ -29,11 +29,9 @@ jobs:
|
||||
with:
|
||||
python-version-file: ".python-version"
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
||||
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
||||
with:
|
||||
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
|
||||
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
|
||||
version: "0.11.18"
|
||||
version: "latest-known"
|
||||
- name: Extract release details
|
||||
id: release-details
|
||||
run: |
|
||||
|
||||
@@ -35,9 +35,9 @@ jobs:
|
||||
# Override frontend/.env so the deployed frontend uses the same origin as the API
|
||||
VITE_API_URL: ""
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
||||
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
||||
with:
|
||||
version: "0.11.18"
|
||||
version: "latest-known"
|
||||
- name: Prepare database
|
||||
run: uv run bash scripts/prestart.sh
|
||||
working-directory: backend
|
||||
|
||||
@@ -66,11 +66,9 @@ jobs:
|
||||
with:
|
||||
limit-access-to-actor: true
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
||||
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
||||
with:
|
||||
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
|
||||
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
|
||||
version: "0.11.18"
|
||||
version: "latest-known"
|
||||
- run: uv sync
|
||||
working-directory: backend
|
||||
- run: bun ci
|
||||
|
||||
@@ -46,11 +46,9 @@ jobs:
|
||||
with:
|
||||
python-version-file: .python-version
|
||||
- name: Setup uv
|
||||
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
||||
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
||||
with:
|
||||
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
|
||||
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
|
||||
version: "0.11.18"
|
||||
version: "latest-known"
|
||||
cache-dependency-glob: |
|
||||
requirements**.txt
|
||||
pyproject.toml
|
||||
|
||||
@@ -38,11 +38,9 @@ jobs:
|
||||
with:
|
||||
python-version-file: ".python-version"
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
||||
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
||||
with:
|
||||
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
|
||||
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
|
||||
version: "0.11.18"
|
||||
version: "latest-known"
|
||||
- name: Prepare release
|
||||
env:
|
||||
BUMP: ${{ inputs.bump }}
|
||||
|
||||
@@ -24,11 +24,9 @@ jobs:
|
||||
with:
|
||||
python-version-file: .python-version
|
||||
- name: Setup uv
|
||||
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
||||
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
||||
with:
|
||||
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
|
||||
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
|
||||
version: "0.11.18"
|
||||
version: "latest-known"
|
||||
cache-dependency-glob: |
|
||||
pyproject.toml
|
||||
uv.lock
|
||||
|
||||
@@ -22,11 +22,9 @@ jobs:
|
||||
with:
|
||||
python-version-file: .python-version
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
||||
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
||||
with:
|
||||
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
|
||||
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
|
||||
version: "0.11.18"
|
||||
version: "latest-known"
|
||||
- run: docker compose down -v --remove-orphans
|
||||
- run: docker compose up -d db mailpit
|
||||
- name: Migrate DB
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { expect, test } from "@playwright/test"
|
||||
import { emailUrl, findLastEmail } from "./utils/mailpit"
|
||||
import { waitForEmailHtml } from "./utils/mailpit"
|
||||
import { randomEmail, randomPassword } from "./utils/random"
|
||||
import { logInUser, signUpNewUser } from "./utils/user"
|
||||
|
||||
test.use({ storageState: { cookies: [], origins: [] } })
|
||||
|
||||
const resetPath = "/reset-password?token="
|
||||
|
||||
test("Password Recovery title is visible", async ({ page }) => {
|
||||
await page.goto("/recover-password")
|
||||
|
||||
@@ -44,21 +46,17 @@ test("User can reset password successfully using the link", async ({
|
||||
|
||||
await page.getByRole("button", { name: "Continue" }).click()
|
||||
|
||||
const emailData = await findLastEmail({
|
||||
const emailHtml = await waitForEmailHtml({
|
||||
request,
|
||||
query: `to:${email}`,
|
||||
timeout: 5000,
|
||||
})
|
||||
|
||||
await page.goto(emailUrl(emailData))
|
||||
|
||||
const selector = 'a[href*="/reset-password?token="]'
|
||||
|
||||
const url = await page.getAttribute(selector, "href")
|
||||
const resetUrl = new URL(url!)
|
||||
expect(emailHtml).toContain(resetPath)
|
||||
const resetUrl = emailHtml.match(/\/reset-password\?token=[^"]+/)?.[0]
|
||||
expect(resetUrl).toBeDefined()
|
||||
|
||||
// Set the new password and confirm it
|
||||
await page.goto(`${resetUrl.pathname}${resetUrl.search}`)
|
||||
await page.goto(resetUrl!)
|
||||
|
||||
await page.getByTestId("new-password-input").fill(newPassword)
|
||||
await page.getByTestId("confirm-password-input").fill(newPassword)
|
||||
@@ -95,20 +93,17 @@ test("Weak new password validation", async ({ page, request }) => {
|
||||
await page.getByTestId("email-input").fill(email)
|
||||
await page.getByRole("button", { name: "Continue" }).click()
|
||||
|
||||
const emailData = await findLastEmail({
|
||||
const emailHtml = await waitForEmailHtml({
|
||||
request,
|
||||
query: `to:${email}`,
|
||||
timeout: 5000,
|
||||
})
|
||||
|
||||
await page.goto(emailUrl(emailData))
|
||||
|
||||
const selector = 'a[href*="/reset-password?token="]'
|
||||
const url = await page.getAttribute(selector, "href")
|
||||
const resetUrl = new URL(url!)
|
||||
expect(emailHtml).toContain(resetPath)
|
||||
const resetUrl = emailHtml.match(/\/reset-password\?token=[^"]+/)?.[0]
|
||||
expect(resetUrl).toBeDefined()
|
||||
|
||||
// Set a weak new password
|
||||
await page.goto(`${resetUrl.pathname}${resetUrl.search}`)
|
||||
await page.goto(resetUrl!)
|
||||
await page.getByTestId("new-password-input").fill(weakPassword)
|
||||
await page.getByTestId("confirm-password-input").fill(weakPassword)
|
||||
await page.getByRole("button", { name: "Reset Password" }).click()
|
||||
|
||||
@@ -1,36 +1,10 @@
|
||||
import type { APIRequestContext } from "@playwright/test"
|
||||
|
||||
type Address = {
|
||||
Name: string
|
||||
Address: string
|
||||
}
|
||||
|
||||
type Email = {
|
||||
type EmailSummary = {
|
||||
ID: string
|
||||
To: Address[]
|
||||
Subject: string
|
||||
}
|
||||
|
||||
async function findEmail({
|
||||
request,
|
||||
query,
|
||||
}: {
|
||||
request: APIRequestContext
|
||||
query: string
|
||||
}) {
|
||||
const response = await request.get(
|
||||
`${process.env.MAILPIT_HOST}/api/v1/search`,
|
||||
{
|
||||
params: { query, limit: 1 },
|
||||
},
|
||||
)
|
||||
|
||||
const { messages }: { messages: Email[] } = await response.json()
|
||||
|
||||
return messages[0] ?? null
|
||||
}
|
||||
|
||||
export async function findLastEmail({
|
||||
export async function waitForEmailHtml({
|
||||
request,
|
||||
query,
|
||||
timeout = 5000,
|
||||
@@ -42,10 +16,27 @@ export async function findLastEmail({
|
||||
const deadline = Date.now() + timeout
|
||||
|
||||
while (Date.now() < deadline) {
|
||||
const email = await findEmail({ request, query })
|
||||
const response = await request.get(
|
||||
`${process.env.MAILPIT_HOST}/api/v1/search`,
|
||||
{
|
||||
params: { query, limit: 1 },
|
||||
},
|
||||
)
|
||||
const { messages }: { messages: EmailSummary[] } = await response.json()
|
||||
const email = messages[0]
|
||||
|
||||
if (email) {
|
||||
return email
|
||||
const htmlResponse = await request.get(
|
||||
`${process.env.MAILPIT_HOST}/view/${email.ID}.html`,
|
||||
)
|
||||
|
||||
if (!htmlResponse.ok()) {
|
||||
throw new Error(
|
||||
`Could not get the HTML for email "${email.ID}": ${htmlResponse.status()}`,
|
||||
)
|
||||
}
|
||||
|
||||
return htmlResponse.text()
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
@@ -53,7 +44,3 @@ export async function findLastEmail({
|
||||
|
||||
throw new Error(`Timeout while trying to get the latest email for "${query}"`)
|
||||
}
|
||||
|
||||
export function emailUrl(email: Email) {
|
||||
return `${process.env.MAILPIT_HOST}/view/${email.ID}.html`
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
### Refactors
|
||||
|
||||
* ✅ Simplify password reset E2E tests. PR [#2453](https://github.com/fastapi/full-stack-fastapi-template/pull/2453) by [@alejsdev](https://github.com/alejsdev).
|
||||
* 💄 Refactor email templates and components for improved styling. PR [#2451](https://github.com/fastapi/full-stack-fastapi-template/pull/2451) by [@alejsdev](https://github.com/alejsdev).
|
||||
* ♻️ Replace Mailcatcher with Mailpit for local email testing. PR [#2436](https://github.com/fastapi/full-stack-fastapi-template/pull/2436) by [@alejsdev](https://github.com/alejsdev).
|
||||
|
||||
@@ -17,6 +18,10 @@
|
||||
* 📝 Update README.md with Mailpit and React Email. PR [#2452](https://github.com/fastapi/full-stack-fastapi-template/pull/2452) by [@alejsdev](https://github.com/alejsdev).
|
||||
* 📝 Simplify project documentation. PR [#2442](https://github.com/fastapi/full-stack-fastapi-template/pull/2442) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
### Internal
|
||||
|
||||
* ⬆️ Bump setup-uv action to 10.0.1. PR [#2458](https://github.com/fastapi/full-stack-fastapi-template/pull/2458) by [@YuriiMotov](https://github.com/YuriiMotov).
|
||||
|
||||
## 0.12.0 (2026-08-12)
|
||||
|
||||
### Features
|
||||
|
||||
Reference in New Issue
Block a user