aps-agent/tests/golden/test_project_file_upload.py

202 lines
7.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ============================================================
# Web 本地工程数据文件上传黄金测试(AG-08 扩展)
# 覆盖:multipart 上传 → 项目 workDir 自动落位 → 目录分析可见
# ============================================================
from __future__ import annotations
import hashlib
import os
from pathlib import Path
import pytest
from fastapi.testclient import TestClient
from server.aps_domain.folder_pack import analyze_work_dir, prepare_folder_schedule
from server.state.seed import seed_world
from tests.auth_provider import install_test_auth
from tests.external_data import external_dir
@pytest.fixture()
def secure_app(tmp_path, monkeypatch):
monkeypatch.setenv("APS_DB_PATH", str(tmp_path / "tenant.db"))
monkeypatch.setenv("APS_WORLD_PATH", str(tmp_path / "world.json"))
monkeypatch.setenv("APS_DATA_DIR", str(tmp_path / "aps-data"))
import server.state.store as world_store
from server.db.database import reset_engine
install_test_auth(monkeypatch, "tenant-a-000000000000000000000001")
world_store._stores.clear()
reset_engine()
from server.gateway.app import create_app
app = create_app()
yield app
reset_engine()
world_store._stores.clear()
def _login(client: TestClient) -> None:
response = client.post("/api/auth/login", json={
"method": "password", "username": "planner", "password": "test",
})
assert response.status_code == 200
def test_upload_local_files_then_folder_analysis(secure_app, monkeypatch):
client = TestClient(secure_app)
_login(client)
created = client.post("/api/projects", json={"name": "浏览器项目"}).json()
pid = created["project"]["id"]
sid = created["session"]["id"]
csv = "订单号,客户,产品编码,数量,交期\nSO-DEMO-1,比亚迪,A0050101-00280,1,2026-08-30\n"
response = client.post(
f"/api/projects/{pid}/files/upload",
files=[("files", ("订单-样例.csv", csv.encode("utf-8"), "text/csv"))],
)
assert response.status_code == 200, response.text
body = response.json()
assert body["saved"] == ["订单-样例.csv"]
assert body["workDir"] is None
assert body["workDirConfigured"] is True
assert all(project["workDir"] is None for project in body["workspace"]["projects"])
snap = client.get("/api/workspace").json()
project = next(p for p in snap["projects"] if p["id"] == pid)
assert project["workDir"]
assert os.path.isfile(os.path.join(project["workDir"], "订单-样例.csv"))
assert any(f["projectId"] == pid and f["name"] == "订单-样例.csv" for f in snap["files"])
class FakePS:
def snapshot(self, include_messages=False):
return {
"projects": [{"id": pid, "name": "浏览器项目", "workDir": project["workDir"]}],
"sessions": [{"id": sid, "projectId": pid}],
"files": [],
}
monkeypatch.setattr("server.state.projects.get_project_store", lambda: FakePS())
report = analyze_work_dir(seed_world(), sid)
assert report["ok"] is True
assert any(f["name"] == "订单-样例.csv" for f in report["files"])
KANGNI_DATA_DIR = external_dir("KANGNI_DATA_DIR", "kangni")
KANGNI_WORKBOOKS = (
"订单.xlsx", "工艺路线.xlsx", "工时.xlsx", "BOM.xlsx", "设备.xlsx",
"模具.xlsx", "物料.xlsx", "设备能力映射模板.xlsx", "模具适配映射模板.xlsx",
)
@pytest.mark.skipif(
not all((KANGNI_DATA_DIR / name).is_file() for name in KANGNI_WORKBOOKS),
reason="康尼现场只读数据不完整",
)
def test_upload_kangni_workbooks_builds_frozen_trial_payload(secure_app, monkeypatch):
client = TestClient(secure_app)
_login(client)
created = client.post("/api/projects", json={"name": "康尼上传试排"}).json()
pid = created["project"]["id"]
sid = created["session"]["id"]
source_hashes = {
name: hashlib.sha256((KANGNI_DATA_DIR / name).read_bytes()).hexdigest()
for name in KANGNI_WORKBOOKS
}
files = [
(
"files",
(
name,
(KANGNI_DATA_DIR / name).read_bytes(),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
),
)
for name in KANGNI_WORKBOOKS
]
response = client.post(f"/api/projects/{pid}/files/upload", files=files)
assert response.status_code == 200, response.text
assert set(response.json()["saved"]) == set(KANGNI_WORKBOOKS)
snap = client.get("/api/workspace").json()
project = next(row for row in snap["projects"] if row["id"] == pid)
class FakePS:
def snapshot(self, include_messages=False):
return {
"projects": [{"id": pid, "name": "康尼上传试排", "workDir": project["workDir"]}],
"sessions": [{"id": sid, "projectId": pid}],
"files": [],
}
monkeypatch.setattr("server.state.projects.get_project_store", lambda: FakePS())
report = prepare_folder_schedule(seed_world(), sid)
assert report["kangniDetected"] is True
assert report["trialReady"] is True
assert report["productionReady"] is False
assert report["kangniMeta"]["orderCount"] == 10
assert report["kangniMeta"]["routingRecordCount"] == 72
assert len(report["sourceManifest"]) == len(KANGNI_WORKBOOKS)
assert {
name: hashlib.sha256((KANGNI_DATA_DIR / name).read_bytes()).hexdigest()
for name in KANGNI_WORKBOOKS
} == source_hashes
def test_upload_rejects_unsupported_extension(secure_app):
client = TestClient(secure_app)
_login(client)
created = client.post("/api/projects", json={"name": "拒绝测试"}).json()
pid = created["project"]["id"]
response = client.post(
f"/api/projects/{pid}/files/upload",
files=[("files", ("说明.docx", b"docx", "application/octet-stream"))],
)
assert response.status_code == 400
assert "仅支持" in response.json()["detail"]
def test_same_name_upload_preserves_file_and_metadata_when_registration_fails(
secure_app, monkeypatch, tmp_path,
):
from server.state.projects import ProjectStore
client = TestClient(secure_app)
_login(client)
created = client.post(
"/api/projects", json={"name": "原子上传", "workDir": str(tmp_path)}
).json()
pid = created["project"]["id"]
first = client.post(
f"/api/projects/{pid}/files/upload",
files=[("files", ("same.dxf", b"original", "application/octet-stream"))],
)
assert first.status_code == 200, first.text
before = client.get("/api/workspace").json()
old_rows = [
row for row in before["files"]
if row["projectId"] == pid and row["name"] == "same.dxf"
]
assert len(old_rows) == 1
def fail_create_file(self, project_id, name, kind="other", note="", file_id=None):
raise RuntimeError("injected metadata failure")
monkeypatch.setattr(ProjectStore, "create_file", fail_create_file)
failed = client.post(
f"/api/projects/{pid}/files/upload",
files=[("files", ("same.dxf", b"replacement", "application/octet-stream"))],
)
assert failed.status_code == 200, failed.text
assert failed.json()["saved"] == []
assert "injected metadata failure" in failed.json()["errors"][0]["error"]
assert (tmp_path / "same.dxf").read_bytes() == b"original"
after = client.get("/api/workspace").json()
rows = [
row for row in after["files"]
if row["projectId"] == pid and row["name"] == "same.dxf"
]
assert rows == old_rows
assert not list(tmp_path.glob(".*.upload"))
assert not list(tmp_path.glob(".*.backup"))