调整部分前端布局和后端增删改逻辑
This commit is contained in:
@@ -58,8 +58,6 @@ cd shierfeng-fastapi-test
|
||||
```bash
|
||||
# MySQL版本
|
||||
docker compose -f docker-compose.test.my.yml up -d --build
|
||||
# PostgreSQL版本
|
||||
docker compose -f docker-compose.test.pg.yml up -d --build
|
||||
```
|
||||
|
||||
#### 运行测试
|
||||
@@ -105,11 +103,17 @@ python -m pytest -v
|
||||
|
||||
## 配置说明
|
||||
|
||||
使用 `docker-compose.test.my.yml`或`docker-compose.test.pg.yml`启动服务,默认前端端口为 `80`,后端端口为 `9099`。测试环境已禁用验证码功能。
|
||||
使用 `docker-compose.test.my.yml` 启动服务,默认前端端口为 `80`,后端端口为 `9099`。测试环境已禁用验证码功能。
|
||||
|
||||
测试账号密码不写入代码、文档或日志。运行测试前,请在受控环境注入以下环境变量:
|
||||
|
||||
- `SHIERFENG_TEST_ADMIN_PASSWORD`:管理员账号 `admin` 的测试密码
|
||||
- `SHIERFENG_TEST_NIANGAO_PASSWORD`:`niangao` 账号的测试密码
|
||||
- `SHIERFENG_TEST_USER_PASSWORD`:用户管理测试中新建账号的初始密码
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 确保系统已安装 Docker 和 Docker Compose
|
||||
2. 确保端口 `80` 和 `9099` 未被占用
|
||||
3. 首次运行时 Docker 镜像构建可能需要几分钟时间
|
||||
4. 测试使用默认管理员账户:用户名 `admin`,密码 `admin123`
|
||||
4. 测试使用管理员账户 `admin`;密码必须通过 `SHIERFENG_TEST_ADMIN_PASSWORD` 注入,不在仓库中记录
|
||||
|
||||
@@ -15,7 +15,7 @@ class BasePageTest:
|
||||
"""初始化浏览器和登录"""
|
||||
# 首先登录获取token
|
||||
helper = LoginHelper()
|
||||
self.token = helper.login(username='admin', password='admin123')
|
||||
self.token = helper.login(username='admin')
|
||||
assert self.token is not None, '登录应该成功'
|
||||
|
||||
# 启动浏览器
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import os
|
||||
|
||||
import requests
|
||||
|
||||
from common.config import Config
|
||||
@@ -12,10 +14,13 @@ class LoginHelper:
|
||||
self.base_url = base_url
|
||||
self.session = requests.Session()
|
||||
|
||||
def login(self, username: str = 'admin', password: str = 'admin123', max_retries: int = 3) -> str | None:
|
||||
def login(self, username: str = 'admin', password: str | None = None, max_retries: int = 3) -> str | None:
|
||||
"""
|
||||
执行登录操作(在测试环境中,验证码已禁用)
|
||||
"""
|
||||
if password is None:
|
||||
password = get_test_password(username)
|
||||
|
||||
for _attempt in range(max_retries):
|
||||
# 在测试环境中,验证码已禁用,所以直接登录
|
||||
login_data = {'username': username, 'password': password}
|
||||
@@ -47,6 +52,15 @@ class LoginHelper:
|
||||
return None
|
||||
|
||||
|
||||
def get_test_password(username: str) -> str:
|
||||
"""读取测试账号密码,避免把凭据写入测试代码或日志。"""
|
||||
env_name = f'SHIERFENG_TEST_{username.upper()}_PASSWORD'
|
||||
password = os.getenv(env_name)
|
||||
if not password:
|
||||
raise RuntimeError(f'未设置测试账号密码环境变量: {env_name}')
|
||||
return password
|
||||
|
||||
|
||||
# 使用示例
|
||||
if __name__ == '__main__':
|
||||
helper = LoginHelper()
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
services:
|
||||
# 前端服务
|
||||
shierfeng-frontend:
|
||||
build:
|
||||
context: ../shierfeng-fastapi-frontend
|
||||
dockerfile: Dockerfile
|
||||
image: shierfeng-frontend:latest
|
||||
container_name: shierfeng-frontend-test
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- ../shierfeng-fastapi-frontend/bin/nginx.dockerpg.conf:/etc/nginx/conf.d/default.conf
|
||||
depends_on:
|
||||
- shierfeng-backend-pg
|
||||
networks:
|
||||
- shierfeng-network-test
|
||||
|
||||
# 后端服务(PostgreSQL版本)
|
||||
shierfeng-backend-pg:
|
||||
build:
|
||||
context: ../shierfeng-fastapi-backend
|
||||
dockerfile: Dockerfile.pg
|
||||
image: shierfeng-backend-pg:latest
|
||||
container_name: shierfeng-backend-pg-test
|
||||
ports:
|
||||
- "9099:9099"
|
||||
environment:
|
||||
TRANSPORT_CRYPTO_ENABLED: "false"
|
||||
TRANSPORT_CRYPTO_MODE: "off"
|
||||
depends_on:
|
||||
shierfeng-pg:
|
||||
condition: service_healthy
|
||||
shierfeng-redis:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- shierfeng-network-test
|
||||
|
||||
# PostgreSQL服务
|
||||
shierfeng-pg:
|
||||
image: postgres:14
|
||||
container_name: shierfeng-pg-test
|
||||
environment:
|
||||
POSTGRES_PASSWORD: root
|
||||
POSTGRES_DB: shierfeng-fastapi
|
||||
POSTGRES_INITDB_ARGS: --encoding=UTF8 --lc-collate=C --lc-ctype=C
|
||||
ports:
|
||||
- "15433:5432"
|
||||
volumes:
|
||||
- ../shierfeng-fastapi-backend/sql/shierfeng-fastapi-pg.sql:/docker-entrypoint-initdb.d/01-shierfeng-fastapi-pg.sql
|
||||
- ./disable_captcha.sql:/docker-entrypoint-initdb.d/02-disable-captcha.sql
|
||||
networks:
|
||||
- shierfeng-network-test
|
||||
healthcheck:
|
||||
test: ["CMD", "pg_isready", "-U", "postgres"]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
|
||||
# Redis服务
|
||||
shierfeng-redis:
|
||||
image: redis:latest
|
||||
container_name: shierfeng-redis-test
|
||||
ports:
|
||||
- "16381:6379"
|
||||
networks:
|
||||
- shierfeng-network-test
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
|
||||
# 网络配置
|
||||
networks:
|
||||
shierfeng-network-test:
|
||||
name: shierfeng-network-test
|
||||
driver: bridge
|
||||
@@ -3,6 +3,7 @@ from playwright.async_api import async_playwright
|
||||
|
||||
from common.base_page_test import BasePageTest
|
||||
from common.config import Config
|
||||
from common.login_helper import get_test_password
|
||||
|
||||
|
||||
class OnlineUserTest(BasePageTest):
|
||||
@@ -16,21 +17,11 @@ class OnlineUserTest(BasePageTest):
|
||||
niangao_page = await niangao_context.new_page()
|
||||
|
||||
# 登录 niangao
|
||||
try:
|
||||
await niangao_page.goto(Config.frontend_url + '/login')
|
||||
await niangao_page.locator('div.el-form-item', has_text='账号').locator('input').fill('niangao')
|
||||
await niangao_page.locator('div.el-form-item', has_text='密码').locator('input').fill('admin123')
|
||||
await niangao_page.get_by_role('button', name='登 录').click()
|
||||
# 等待登录成功
|
||||
await niangao_page.wait_for_url('**/index')
|
||||
except Exception as e:
|
||||
print(f'Niangao login failed: {e}. Trying admin123')
|
||||
try:
|
||||
await niangao_page.locator('div.el-form-item', has_text='密码').locator('input').fill('123456')
|
||||
await niangao_page.get_by_role('button', name='登 录').click()
|
||||
await niangao_page.wait_for_url('**/index')
|
||||
except Exception as e2:
|
||||
print(f'Niangao login failed again: {e2}')
|
||||
await niangao_page.goto(Config.frontend_url + '/login')
|
||||
await niangao_page.locator('div.el-form-item', has_text='账号').locator('input').fill('niangao')
|
||||
await niangao_page.locator('div.el-form-item', has_text='密码').locator('input').fill(get_test_password('niangao'))
|
||||
await niangao_page.get_by_role('button', name='登 录').click()
|
||||
await niangao_page.wait_for_url('**/index')
|
||||
|
||||
# 2. 切换回 admin (self.page) 进行操作
|
||||
await self.page.goto(Config.frontend_url + '/monitor/online')
|
||||
|
||||
@@ -5,6 +5,7 @@ from playwright.async_api import async_playwright
|
||||
|
||||
from common.base_page_test import BasePageTest
|
||||
from common.config import Config
|
||||
from common.login_helper import get_test_password
|
||||
|
||||
|
||||
class UserManagementTest(BasePageTest):
|
||||
@@ -44,7 +45,7 @@ class UserManagementTest(BasePageTest):
|
||||
# 这里使用 get_by_placeholder 或者 filter 来定位更稳妥,或者直接 scope 到 dialog
|
||||
await dialog.locator('div.el-form-item').filter(has_text='手机号码').get_by_role('textbox').fill(phone)
|
||||
await dialog.locator('div.el-form-item').filter(has_text='邮箱').get_by_role('textbox').fill('test@example.com')
|
||||
await dialog.locator('div.el-form-item').filter(has_text='用户密码').get_by_role('textbox').fill('123456')
|
||||
await dialog.locator('div.el-form-item').filter(has_text='用户密码').get_by_role('textbox').fill(get_test_password('user'))
|
||||
|
||||
# 用户性别
|
||||
# 定位 label 为 "用户性别" 的父级 form-item,然后找里面的 "请选择" 文本
|
||||
|
||||
@@ -2,7 +2,7 @@ import pytest
|
||||
from playwright.async_api import async_playwright
|
||||
|
||||
from common.config import Config
|
||||
from common.login_helper import LoginHelper
|
||||
from common.login_helper import LoginHelper, get_test_password
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -51,7 +51,7 @@ def test_login_without_captcha() -> None:
|
||||
helper = LoginHelper()
|
||||
|
||||
# 尝试登录(测试环境中验证码已禁用)
|
||||
token = helper.login(username='admin', password='admin123')
|
||||
token = helper.login(username='admin')
|
||||
|
||||
# 验证登录结果
|
||||
assert token is not None, '登录应该成功'
|
||||
@@ -73,7 +73,7 @@ async def test_login_flow_with_playwright() -> None:
|
||||
|
||||
# 输入用户名和密码
|
||||
await page.fill('input[placeholder="账号"]', 'admin')
|
||||
await page.fill('input[placeholder="密码"]', 'admin123')
|
||||
await page.fill('input[placeholder="密码"]', get_test_password('admin'))
|
||||
|
||||
# 在测试环境中验证码已禁用,所以不需要处理验证码
|
||||
# 直接点击登录按钮
|
||||
@@ -151,7 +151,7 @@ async def test_authenticated_access() -> None:
|
||||
"""测试认证后的访问"""
|
||||
# 首先通过API登录获取token
|
||||
helper = LoginHelper()
|
||||
token = helper.login(username='admin', password='admin123')
|
||||
token = helper.login(username='admin')
|
||||
|
||||
assert token is not None, '登录应该成功'
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ async def test_dashboard_page() -> None:
|
||||
"""测试仪表盘页面"""
|
||||
# 首先登录获取token
|
||||
helper = LoginHelper()
|
||||
token = helper.login(username='admin', password='admin123')
|
||||
token = helper.login(username='admin')
|
||||
assert token is not None, '登录应该成功'
|
||||
|
||||
async with async_playwright() as p:
|
||||
@@ -48,7 +48,7 @@ async def test_druid_page() -> None:
|
||||
"""测试数据监控页面"""
|
||||
# 首先登录获取token
|
||||
helper = LoginHelper()
|
||||
token = helper.login(username='admin', password='admin123')
|
||||
token = helper.login(username='admin')
|
||||
assert token is not None, '登录应该成功'
|
||||
|
||||
async with async_playwright() as p:
|
||||
@@ -86,7 +86,7 @@ async def test_build_page() -> None:
|
||||
"""测试表单构建页面"""
|
||||
# 首先登录获取token
|
||||
helper = LoginHelper()
|
||||
token = helper.login(username='admin', password='admin123')
|
||||
token = helper.login(username='admin')
|
||||
assert token is not None, '登录应该成功'
|
||||
|
||||
async with async_playwright() as p:
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import pytest
|
||||
|
||||
from common.login_helper import LoginHelper
|
||||
|
||||
|
||||
class _Response:
|
||||
status_code = 401
|
||||
|
||||
def json(self) -> dict[str, object]:
|
||||
return {'code': 401, 'msg': '测试响应'}
|
||||
|
||||
|
||||
class _RecordingSession:
|
||||
def __init__(self) -> None:
|
||||
self.requests: list[dict[str, str]] = []
|
||||
|
||||
def post(self, url: str, data: dict[str, str], headers: dict[str, str]) -> _Response:
|
||||
self.requests.append(data)
|
||||
return _Response()
|
||||
|
||||
|
||||
def test_login_uses_admin_password_from_environment_when_password_is_omitted(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
monkeypatch.setenv('SHIERFENG_TEST_ADMIN_PASSWORD', 'test-only-admin-secret')
|
||||
helper = LoginHelper()
|
||||
session = _RecordingSession()
|
||||
helper.session = session
|
||||
|
||||
assert helper.login(username='admin', max_retries=1) is None
|
||||
assert session.requests == [
|
||||
{'username': 'admin', 'password': 'test-only-admin-secret'},
|
||||
]
|
||||
|
||||
|
||||
def test_login_fails_fast_when_account_password_environment_is_missing(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.delenv('SHIERFENG_TEST_ADMIN_PASSWORD', raising=False)
|
||||
helper = LoginHelper()
|
||||
|
||||
with pytest.raises(RuntimeError, match='SHIERFENG_TEST_ADMIN_PASSWORD'):
|
||||
helper.login(username='admin', max_retries=1)
|
||||
Reference in New Issue
Block a user