✨ Add private, local only, API for usage in E2E tests (#1429)
Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
4
.github/workflows/generate-client.yml
vendored
4
.github/workflows/generate-client.yml
vendored
@@ -39,6 +39,10 @@ jobs:
|
||||
- run: uv run bash scripts/generate-client.sh
|
||||
env:
|
||||
VIRTUAL_ENV: backend/.venv
|
||||
ENVIRONMENT: production
|
||||
SECRET_KEY: just-for-generating-client
|
||||
POSTGRES_PASSWORD: just-for-generating-client
|
||||
FIRST_SUPERUSER_PASSWORD: just-for-generating-client
|
||||
- name: Add changes to git
|
||||
run: |
|
||||
git config --local user.email "github-actions@github.com"
|
||||
|
12
.github/workflows/playwright.yml
vendored
12
.github/workflows/playwright.yml
vendored
@@ -59,6 +59,18 @@ jobs:
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
|
||||
with:
|
||||
limit-access-to-actor: true
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v3
|
||||
with:
|
||||
version: "0.4.15"
|
||||
enable-cache: true
|
||||
- run: uv sync
|
||||
working-directory: backend
|
||||
- run: npm ci
|
||||
working-directory: frontend
|
||||
- run: uv run bash scripts/generate-client.sh
|
||||
env:
|
||||
VIRTUAL_ENV: backend/.venv
|
||||
- run: docker compose build
|
||||
- run: docker compose down -v --remove-orphans
|
||||
- name: Run Playwright tests
|
||||
|
@@ -1,9 +1,14 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from app.api.routes import items, login, users, utils
|
||||
from app.api.routes import items, login, private, users, utils
|
||||
from app.core.config import settings
|
||||
|
||||
api_router = APIRouter()
|
||||
api_router.include_router(login.router, tags=["login"])
|
||||
api_router.include_router(users.router, prefix="/users", tags=["users"])
|
||||
api_router.include_router(utils.router, prefix="/utils", tags=["utils"])
|
||||
api_router.include_router(items.router, prefix="/items", tags=["items"])
|
||||
|
||||
|
||||
if settings.ENVIRONMENT == "local":
|
||||
api_router.include_router(private.router)
|
||||
|
38
backend/app/api/routes/private.py
Normal file
38
backend/app/api/routes/private.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.api.deps import SessionDep
|
||||
from app.core.security import get_password_hash
|
||||
from app.models import (
|
||||
User,
|
||||
UserPublic,
|
||||
)
|
||||
|
||||
router = APIRouter(tags=["private"], prefix="/private")
|
||||
|
||||
|
||||
class PrivateUserCreate(BaseModel):
|
||||
email: str
|
||||
password: str
|
||||
full_name: str
|
||||
is_verified: bool = False
|
||||
|
||||
|
||||
@router.post("/users/", response_model=UserPublic)
|
||||
def create_user(user_in: PrivateUserCreate, session: SessionDep) -> Any:
|
||||
"""
|
||||
Create a new user.
|
||||
"""
|
||||
|
||||
user = User(
|
||||
email=user_in.email,
|
||||
full_name=user_in.full_name,
|
||||
hashed_password=get_password_hash(user_in.password),
|
||||
)
|
||||
|
||||
session.add(user)
|
||||
session.commit()
|
||||
|
||||
return user
|
26
backend/app/tests/api/routes/test_private.py
Normal file
26
backend/app/tests/api/routes/test_private.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlmodel import Session, select
|
||||
|
||||
from app.core.config import settings
|
||||
from app.models import User
|
||||
|
||||
|
||||
def test_create_user(client: TestClient, db: Session) -> None:
|
||||
r = client.post(
|
||||
f"{settings.API_V1_STR}/private/users/",
|
||||
json={
|
||||
"email": "pollo@listo.com",
|
||||
"password": "password123",
|
||||
"full_name": "Pollo Listo",
|
||||
},
|
||||
)
|
||||
|
||||
assert r.status_code == 200
|
||||
|
||||
data = r.json()
|
||||
|
||||
user = db.exec(select(User).where(User.id == data["id"])).first()
|
||||
|
||||
assert user
|
||||
assert user.email == "pollo@listo.com"
|
||||
assert user.full_name == "Pollo Listo"
|
@@ -5,7 +5,7 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"build": "tsc -p tsconfig.build.json && vite build",
|
||||
"lint": "biome check --apply-unsafe --no-errors-on-unmatched --files-ignore-unknown=true ./",
|
||||
"preview": "vite preview",
|
||||
"generate-client": "openapi-ts"
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import { expect, test } from "@playwright/test"
|
||||
import { firstSuperuser, firstSuperuserPassword } from "./config.ts"
|
||||
import { randomEmail, randomPassword } from "./utils/random"
|
||||
import { logInUser, logOutUser, signUpNewUser } from "./utils/user"
|
||||
import { logInUser, logOutUser } from "./utils/user"
|
||||
import { createUser } from "./utils/privateApi.ts"
|
||||
|
||||
const tabs = ["My profile", "Password", "Appearance"]
|
||||
|
||||
@@ -26,13 +27,11 @@ test.describe("Edit user full name and email successfully", () => {
|
||||
test.use({ storageState: { cookies: [], origins: [] } })
|
||||
|
||||
test("Edit user name with a valid name", async ({ page }) => {
|
||||
const fullName = "Test User"
|
||||
const email = randomEmail()
|
||||
const updatedName = "Test User 2"
|
||||
const password = randomPassword()
|
||||
|
||||
// Sign up a new user
|
||||
await signUpNewUser(page, fullName, email, password)
|
||||
await createUser({ email, password })
|
||||
|
||||
// Log in the user
|
||||
await logInUser(page, email, password)
|
||||
@@ -50,13 +49,11 @@ test.describe("Edit user full name and email successfully", () => {
|
||||
})
|
||||
|
||||
test("Edit user email with a valid email", async ({ page }) => {
|
||||
const fullName = "Test User"
|
||||
const email = randomEmail()
|
||||
const updatedEmail = randomEmail()
|
||||
const password = randomPassword()
|
||||
|
||||
// Sign up a new user
|
||||
await signUpNewUser(page, fullName, email, password)
|
||||
await createUser({ email, password })
|
||||
|
||||
// Log in the user
|
||||
await logInUser(page, email, password)
|
||||
@@ -77,13 +74,11 @@ test.describe("Edit user with invalid data", () => {
|
||||
test.use({ storageState: { cookies: [], origins: [] } })
|
||||
|
||||
test("Edit user email with an invalid email", async ({ page }) => {
|
||||
const fullName = "Test User"
|
||||
const email = randomEmail()
|
||||
const password = randomPassword()
|
||||
const invalidEmail = ""
|
||||
|
||||
// Sign up a new user
|
||||
await signUpNewUser(page, fullName, email, password)
|
||||
await createUser({ email, password })
|
||||
|
||||
// Log in the user
|
||||
await logInUser(page, email, password)
|
||||
@@ -97,13 +92,11 @@ test.describe("Edit user with invalid data", () => {
|
||||
})
|
||||
|
||||
test("Cancel edit action restores original name", async ({ page }) => {
|
||||
const fullName = "Test User"
|
||||
const email = randomEmail()
|
||||
const password = randomPassword()
|
||||
const updatedName = "Test User"
|
||||
|
||||
// Sign up a new user
|
||||
await signUpNewUser(page, fullName, email, password)
|
||||
const user = await createUser({ email, password })
|
||||
|
||||
// Log in the user
|
||||
await logInUser(page, email, password)
|
||||
@@ -114,18 +107,18 @@ test.describe("Edit user with invalid data", () => {
|
||||
await page.getByLabel("Full name").fill(updatedName)
|
||||
await page.getByRole("button", { name: "Cancel" }).first().click()
|
||||
await expect(
|
||||
page.getByLabel("My profile").getByText(fullName, { exact: true }),
|
||||
page
|
||||
.getByLabel("My profile")
|
||||
.getByText(user.full_name as string, { exact: true }),
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test("Cancel edit action restores original email", async ({ page }) => {
|
||||
const fullName = "Test User"
|
||||
const email = randomEmail()
|
||||
const password = randomPassword()
|
||||
const updatedEmail = randomEmail()
|
||||
|
||||
// Sign up a new user
|
||||
await signUpNewUser(page, fullName, email, password)
|
||||
await createUser({ email, password })
|
||||
|
||||
// Log in the user
|
||||
await logInUser(page, email, password)
|
||||
@@ -147,13 +140,11 @@ test.describe("Change password successfully", () => {
|
||||
test.use({ storageState: { cookies: [], origins: [] } })
|
||||
|
||||
test("Update password successfully", async ({ page }) => {
|
||||
const fullName = "Test User"
|
||||
const email = randomEmail()
|
||||
const password = randomPassword()
|
||||
const NewPassword = randomPassword()
|
||||
|
||||
// Sign up a new user
|
||||
await signUpNewUser(page, fullName, email, password)
|
||||
await createUser({ email, password })
|
||||
|
||||
// Log in the user
|
||||
await logInUser(page, email, password)
|
||||
@@ -177,13 +168,11 @@ test.describe("Change password with invalid data", () => {
|
||||
test.use({ storageState: { cookies: [], origins: [] } })
|
||||
|
||||
test("Update password with weak passwords", async ({ page }) => {
|
||||
const fullName = "Test User"
|
||||
const email = randomEmail()
|
||||
const password = randomPassword()
|
||||
const weakPassword = "weak"
|
||||
|
||||
// Sign up a new user
|
||||
await signUpNewUser(page, fullName, email, password)
|
||||
await createUser({ email, password })
|
||||
|
||||
// Log in the user
|
||||
await logInUser(page, email, password)
|
||||
@@ -201,14 +190,12 @@ test.describe("Change password with invalid data", () => {
|
||||
test("New password and confirmation password do not match", async ({
|
||||
page,
|
||||
}) => {
|
||||
const fullName = "Test User"
|
||||
const email = randomEmail()
|
||||
const password = randomPassword()
|
||||
const newPassword = randomPassword()
|
||||
const confirmPassword = randomPassword()
|
||||
|
||||
// Sign up a new user
|
||||
await signUpNewUser(page, fullName, email, password)
|
||||
await createUser({ email, password })
|
||||
|
||||
// Log in the user
|
||||
await logInUser(page, email, password)
|
||||
@@ -223,12 +210,10 @@ test.describe("Change password with invalid data", () => {
|
||||
})
|
||||
|
||||
test("Current password and new password are the same", async ({ page }) => {
|
||||
const fullName = "Test User"
|
||||
const email = randomEmail()
|
||||
const password = randomPassword()
|
||||
|
||||
// Sign up a new user
|
||||
await signUpNewUser(page, fullName, email, password)
|
||||
await createUser({ email, password })
|
||||
|
||||
// Log in the user
|
||||
await logInUser(page, email, password)
|
||||
|
22
frontend/tests/utils/privateApi.ts
Normal file
22
frontend/tests/utils/privateApi.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
// Note: the `PrivateService` is only available when generating the client
|
||||
// for local environments
|
||||
import { OpenAPI, PrivateService } from "../../src/client"
|
||||
|
||||
OpenAPI.BASE = `${process.env.VITE_API_URL}`
|
||||
|
||||
export const createUser = async ({
|
||||
email,
|
||||
password,
|
||||
}: {
|
||||
email: string
|
||||
password: string
|
||||
}) => {
|
||||
return await PrivateService.createUser({
|
||||
requestBody: {
|
||||
email,
|
||||
password,
|
||||
is_verified: true,
|
||||
full_name: "Test User",
|
||||
},
|
||||
})
|
||||
}
|
4
frontend/tsconfig.build.json
Normal file
4
frontend/tsconfig.build.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["tests/**/*.ts"]
|
||||
}
|
Reference in New Issue
Block a user