激光材料数据平台 1.3.0
公开数据看板、四位 Key 录入面板、管理后台与三级权限、动态字段配置、 PostgreSQL/SQLite 双支持、数据库备份,以及本版新增的成品图上传与预览。 图片相关: - 录入表单支持相册选图与手机端直接拍照,每条记录最多 6 张 - 浏览器内压缩到最长边 1600 并剥离 EXIF,入库统一为 JPG/PNG - 二进制直接入库,现有备份自动覆盖图片,部署无需新增卷 - 详情页缩略图宫格与全屏 lightbox,公开看板同样可见 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
"""接口测试用的独立环境:必须在导入 app 之前写好环境变量,配置是冻结的 dataclass。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import struct
|
||||
import tempfile
|
||||
import zlib
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
os.environ.setdefault("APP_SECRET", "test-secret-0123456789abcdefghijkl")
|
||||
os.environ.setdefault("ADMIN_USERNAME", "admin")
|
||||
os.environ.setdefault("ADMIN_PASSWORD", "TestAdmin12345")
|
||||
os.environ["DATABASE_URL"] = f"sqlite:///{Path(tempfile.mkdtemp(prefix='laser-test-')) / 'test.db'}"
|
||||
|
||||
|
||||
def png_bytes(width: int = 24, height: int = 16) -> bytes:
|
||||
"""不依赖 Pillow 手工拼一张真实可解码的 PNG。"""
|
||||
def chunk(tag: bytes, payload: bytes) -> bytes:
|
||||
return struct.pack(">I", len(payload)) + tag + payload + struct.pack(">I", zlib.crc32(tag + payload))
|
||||
raw = b"".join(b"\x00" + b"\x20\x60\x90" * width for _ in range(height))
|
||||
header = chunk(b"IHDR", struct.pack(">IIBBBBB", width, height, 8, 2, 0, 0, 0))
|
||||
return b"\x89PNG\r\n\x1a\n" + header + chunk(b"IDAT", zlib.compress(raw)) + chunk(b"IEND", b"")
|
||||
|
||||
|
||||
def jpeg_bytes(width: int = 32, height: int = 20) -> bytes:
|
||||
"""只含 SOI/APP0/SOF0/EOI 的最小 JPEG,足以走完服务端的文件头校验。"""
|
||||
app0 = b"\xff\xe0" + struct.pack(">H", 16) + b"JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00"
|
||||
sof0 = b"\xff\xc0" + struct.pack(">HBHHB", 11, 8, height, width, 1) + b"\x01\x11\x00"
|
||||
return b"\xff\xd8" + app0 + sof0 + b"\xff\xd9"
|
||||
|
||||
|
||||
def record_payload(experiment_id: str = "LAS-TEST-001", **overrides) -> dict:
|
||||
payload = {
|
||||
"experiment_id": experiment_id, "test_date": "2026-07-01", "material_category": "木材", "material_name": "椴木板",
|
||||
"material_thickness": 3, "actual_output_power": 8.5, "display_current": 2, "scanning_speed": 100,
|
||||
"pulse_frequency": 20, "pulse_width": 5, "defocus_amount": 0, "scan_line_spacing": 0.1,
|
||||
"filling_method": "双向填充", "processing_size": "20×20", "is_cut_through": True,
|
||||
"carbonized_edge_width": 0.2, "etching_depth": 30, "is_fire_smolder": False,
|
||||
"pattern_clarity_score": 9, "presentation_balance_score": 8,
|
||||
"finished_image_filename": "LAS-TEST-001.jpg", "remarks": "测试记录",
|
||||
}
|
||||
payload.update(overrides)
|
||||
return payload
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def client():
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app.main import app
|
||||
|
||||
with TestClient(app) as test_client:
|
||||
yield test_client
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def admin(client):
|
||||
"""返回带管理员会话的客户端和对应 CSRF 令牌。"""
|
||||
import hashlib
|
||||
import hmac
|
||||
|
||||
from app.config import settings
|
||||
from app.main import SESSION_COOKIE
|
||||
|
||||
client.cookies.clear()
|
||||
response = client.post("/api/auth/login", data={"username": settings.admin_username, "password": settings.admin_password})
|
||||
assert response.status_code == 200, response.text
|
||||
token = client.cookies.get(SESSION_COOKIE)
|
||||
csrf = hmac.new(settings.secret.encode(), token.encode(), hashlib.sha256).hexdigest()
|
||||
return client, {"X-CSRF-Token": csrf}
|
||||
Reference in New Issue
Block a user