mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 00:11:59 +00:00
测试:随机名字不带数字——新增 tests/fixtures/names.letters(uuid4 的十六进制里数字换成字母,同 test_run_data_versions 的写法),dual_check_shop 的数据源名、盲算的调用编号,评测、应用说明检查、导入、遮罩、通知几处测试里拼进文本的随机名字都改用它(以前带着「330」「300」「48」这类数时撞上「文本里不许出现这个数」的断言);test_eval_schedule 查通知里有没有数时先去掉 32 位编号和发送时间(随机的编号带过「310」);test_null_not_null_values_fall_back_to_the_model_default 显式导入 app.db.models(单独跑时 schedules 还没登记进元数据,KeyError)。deploy.sh 注明内置库不用 copy-to-postgres 的 --direct-url
This commit is contained in:
+3
-3
@@ -19,7 +19,6 @@ from __future__ import annotations
|
||||
import json
|
||||
import re
|
||||
import sqlite3
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
|
||||
from langchain_core.messages import AIMessage, ToolMessage
|
||||
@@ -27,6 +26,7 @@ from langchain_core.messages import AIMessage, ToolMessage
|
||||
from app.data import introspect
|
||||
from app.db.base import SessionLocal
|
||||
from app.db.models import DataSource
|
||||
from tests.fixtures.names import letters
|
||||
|
||||
ROWS = [
|
||||
(1, "华东", 101, 100.0, "paid", "2026-08-31", "2026-09-01", "online", 0),
|
||||
@@ -67,7 +67,7 @@ def build(path: Path, *, date_type: str = "DATE") -> Path:
|
||||
|
||||
async def add_source(path: Path, *, options: dict | None = None) -> tuple[str, str]:
|
||||
"""登记成只读的手工源,探查好表结构。返回 (数据源名, id)。"""
|
||||
name = f"dc_shop_{uuid.uuid4().hex[:8]}"
|
||||
name = f"dc_shop_{letters(8)}"
|
||||
async with SessionLocal() as session:
|
||||
row = DataSource(name=name, kind="sqlite", database=str(path), readonly=True, enabled=True,
|
||||
options=dict(options or {}))
|
||||
@@ -135,7 +135,7 @@ class Blind:
|
||||
hook, blind.on_query = blind.on_query, None
|
||||
hook()
|
||||
call = {"name": f"db_query__{blind.source}", "args": {"sql": sqls[done]},
|
||||
"id": f"blind-{uuid.uuid4().hex[:6]}"}
|
||||
"id": f"blind-{letters(6)}"}
|
||||
return AIMessage(content="查一下。", tool_calls=[call])
|
||||
return AIMessage(content="查完了。")
|
||||
blind.writer_prompts.append(text)
|
||||
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
"""测试里随机起的名字不带数字。
|
||||
|
||||
名字会拼进导出的文件、通知、任务说明这类文本,而不少断言写的是「文本里不许出现某个数」(不许出现 330、300、48……):
|
||||
随机的十六进制里带着那几个数字的概率不大但不是零(8 位里出现「330」约千分之一点五),一撞上就无缘无故地失败。
|
||||
十六进制里的数字换成字母(同 test_run_data_versions 的写法),随机性来自 uuid4(不受 pytest-randomly 每个测试重设
|
||||
random 种子的影响,不会在不同测试里撞名)。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
|
||||
#: 十六进制里的数字换成字母:名字里不带数字,数字的断言才只看得见数据
|
||||
NO_DIGITS = str.maketrans("0123456789", "ghijklmnop")
|
||||
|
||||
|
||||
def letters(n: int = 8) -> str:
|
||||
"""n 个随机小写字母(a–p),n 最多 32。"""
|
||||
return uuid.uuid4().hex[:n].translate(NO_DIGITS)
|
||||
@@ -6,7 +6,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -21,6 +20,7 @@ from app.apps.spec import empty_spec, parse_spec
|
||||
from app.core.artifact_store import graph_hash
|
||||
from app.db.base import SessionLocal, utcnow
|
||||
from app.db.models import DataSource, Schedule, Workflow, WorkflowVersion
|
||||
from tests.fixtures.names import letters
|
||||
|
||||
|
||||
def node(nid, ntype, label=None, **config):
|
||||
@@ -57,7 +57,7 @@ def approval():
|
||||
|
||||
async def make_workflow(graph, *, versions=((1, "governed"),), current=1, status="governed") -> tuple[str, dict]:
|
||||
async with SessionLocal() as session:
|
||||
wf = Workflow(name=f"周报-{uuid.uuid4().hex[:6]}", graph=graph, status=status, published_version=current,
|
||||
wf = Workflow(name=f"周报-{letters(6)}", graph=graph, status=status, published_version=current,
|
||||
version=max(v for v, _ in versions))
|
||||
session.add(wf)
|
||||
await session.flush()
|
||||
@@ -281,7 +281,7 @@ async def test_process_pins(monkeypatch):
|
||||
|
||||
|
||||
async def test_on_demand_views_on_live_sources_need_a_ttl(tmp_path):
|
||||
name = f"live_{uuid.uuid4().hex[:6]}"
|
||||
name = f"live_{letters(6)}"
|
||||
async with SessionLocal() as session:
|
||||
session.add(DataSource(name=name, kind="sqlite", database=str(tmp_path / "x.db"), enabled=True))
|
||||
await session.commit()
|
||||
|
||||
@@ -23,6 +23,7 @@ from app.db.base import SessionLocal
|
||||
from app.db.models import DataSource
|
||||
from app.engine import masking
|
||||
from app.engine.nodes import challenge as challenge_mod
|
||||
from tests.fixtures.names import letters
|
||||
from tests.test_challenge_node import Models, Play, engine_up, finish, graph, output_of # noqa: F401 - engine_up 是自动用上的夹具
|
||||
|
||||
#: 四笔订单,买家手机号设了遮罩;备注没设遮罩,其中一条写着换了写法的号码
|
||||
@@ -42,7 +43,7 @@ async def masked_shop(tmp_path):
|
||||
db.executescript(SCRIPT)
|
||||
db.commit()
|
||||
db.close()
|
||||
name = f"shop_{uuid.uuid4().hex[:8]}"
|
||||
name = f"shop_{letters(8)}"
|
||||
async with SessionLocal() as session:
|
||||
row = DataSource(name=name, kind="sqlite", database=str(path), readonly=True, enabled=True,
|
||||
options={"mask_columns": ["buyer_phone"]})
|
||||
@@ -139,7 +140,7 @@ async def big_shop(tmp_path):
|
||||
db.executescript(BIG)
|
||||
db.commit()
|
||||
db.close()
|
||||
name = f"shop_{uuid.uuid4().hex[:8]}"
|
||||
name = f"shop_{letters(8)}"
|
||||
async with SessionLocal() as session:
|
||||
row = DataSource(name=name, kind="sqlite", database=str(path), readonly=True, enabled=True,
|
||||
options={"mask_columns": ["amount", "buyer_name"]})
|
||||
|
||||
@@ -409,6 +409,7 @@ async def test_an_old_database_with_nulls_and_orphans_imports_cleanly(tmp_path):
|
||||
|
||||
|
||||
def test_null_not_null_values_fall_back_to_the_model_default():
|
||||
from app.db import models # noqa: F401 表登记进元数据(这个进程里之前没有测试导入过模型时 schedules 还不在)
|
||||
from app.db.base import Base
|
||||
|
||||
schedules = Base.metadata.tables["schedules"]
|
||||
|
||||
@@ -12,6 +12,7 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import re
|
||||
import uuid
|
||||
from datetime import timedelta, timezone
|
||||
|
||||
@@ -218,8 +219,10 @@ async def test_a_regression_is_pushed_without_numbers(client, monkeypatch, recei
|
||||
assert message["event"] == "eval.regressed" and message["title"] == "定时评测「虚构周回归」有退步"
|
||||
text = message["text"]
|
||||
assert "通过:0 / 1 题" in text and "退步:1 题" in text
|
||||
# 消息里的编号(32 位十六进制)和发送时间是随机的、可能正好带着「310」:去掉再查,查的是写出来的文字
|
||||
readable = re.sub(r"[0-9a-f]{32}", "<id>", json.dumps({**message, "sent_at": ""}, ensure_ascii=False))
|
||||
for secret in ("330", "310", "gmv", "orders", "paid_date"):
|
||||
assert secret not in json.dumps(message, ensure_ascii=False), f"通知里不该有 {secret}"
|
||||
assert secret not in readable, f"通知里不该有 {secret}"
|
||||
from app.db.models import NotifyDelivery
|
||||
|
||||
async with SessionLocal() as session:
|
||||
|
||||
@@ -7,7 +7,6 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sqlite3
|
||||
import uuid
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from urllib.parse import quote
|
||||
@@ -27,6 +26,7 @@ from app.main import app
|
||||
from tests.fixtures import dual_check_shop as shop
|
||||
from tests.fixtures.acceptance import retail
|
||||
from tests.fixtures.catalog.fake_model import FakeModel
|
||||
from tests.fixtures.names import letters
|
||||
from tests.fixtures.sources import drop_source
|
||||
|
||||
SUITE_FILE = Path(__file__).parent / "fixtures" / "eval" / "retail_suite.json"
|
||||
@@ -57,7 +57,7 @@ async def source(tmp_path):
|
||||
|
||||
|
||||
async def new_suite(client, **body) -> dict:
|
||||
res = await client.post("/api/evals/suites", json={"name": f"虚构题集_{uuid.uuid4().hex[:6]}", **body}, headers=ACTOR)
|
||||
res = await client.post("/api/evals/suites", json={"name": f"虚构题集_{letters(6)}", **body}, headers=ACTOR)
|
||||
assert res.status_code == 201, res.text
|
||||
return res.json()
|
||||
|
||||
@@ -264,7 +264,7 @@ def _weekly(source_name: str, *, refunds: bool = False) -> dict:
|
||||
|
||||
async def test_caliber_cases_list_and_dispose_upgrades(client, source):
|
||||
async with SessionLocal() as session:
|
||||
wf = Workflow(name=f"周销售_{uuid.uuid4().hex[:6]}", graph=_weekly(source["name"]), status="published",
|
||||
wf = Workflow(name=f"周销售_{letters(6)}", graph=_weekly(source["name"]), status="published",
|
||||
published_version=1, version=1)
|
||||
session.add(wf)
|
||||
await session.flush()
|
||||
@@ -329,7 +329,7 @@ async def test_caliber_cases_list_and_dispose_upgrades(client, source):
|
||||
|
||||
async def test_caliber_cases_pin_only_published_versions(client, source):
|
||||
async with SessionLocal() as session:
|
||||
wf = Workflow(name=f"草稿口径_{uuid.uuid4().hex[:6]}", graph=_weekly(source["name"]), status="draft", version=1)
|
||||
wf = Workflow(name=f"草稿口径_{letters(6)}", graph=_weekly(source["name"]), status="draft", version=1)
|
||||
session.add(wf)
|
||||
await session.flush()
|
||||
session.add(WorkflowVersion(workflow_id=wf.id, version=1, graph=_weekly(source["name"])))
|
||||
|
||||
@@ -8,7 +8,6 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import uuid
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
@@ -19,6 +18,7 @@ from app.db.base import SessionLocal
|
||||
from app.db.models import App, AppVersion, NotifyChannel, NotifyDelivery, Run, Setting
|
||||
from app.engine.runner import run_manager
|
||||
from app.notify import core, service
|
||||
from tests.fixtures.names import letters
|
||||
|
||||
URL = "https://hooks.example.com/agentlab/inbox-7f3a9c2e1b"
|
||||
|
||||
@@ -141,7 +141,7 @@ async def test_task_opened_is_pushed_to_the_app_subscription_without_numbers(rec
|
||||
async with SessionLocal() as session:
|
||||
channel = NotifyChannel(name="财务群", format="generic", url=core_encrypt(URL), events=[])
|
||||
session.add(channel)
|
||||
app = App(name=f"报销-{uuid.uuid4().hex[:6]}", published_version=1)
|
||||
app = App(name=f"报销-{letters(6)}", published_version=1)
|
||||
session.add(app)
|
||||
await session.flush()
|
||||
session.add(AppVersion(app_id=app.id, version=1, spec_hash="h" * 64, bindings={},
|
||||
|
||||
@@ -10,7 +10,6 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import uuid
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
@@ -21,6 +20,7 @@ from app.db.base import SessionLocal
|
||||
from app.db.models import App, AppVersion, NotifyChannel, NotifyDelivery, Run, User
|
||||
from app.engine.runner import run_manager
|
||||
from app.notify import core, service
|
||||
from tests.fixtures.names import letters
|
||||
|
||||
URL = "https://hooks.example.com/agentlab/run-3b8e1f"
|
||||
|
||||
@@ -68,9 +68,9 @@ def flow(**human):
|
||||
|
||||
async def _app(events: list[str]) -> tuple[str, str]:
|
||||
async with SessionLocal() as session:
|
||||
channel = NotifyChannel(name=f"群-{uuid.uuid4().hex[:6]}", format="generic", url=encrypt(URL), events=[])
|
||||
channel = NotifyChannel(name=f"群-{letters(6)}", format="generic", url=encrypt(URL), events=[])
|
||||
session.add(channel)
|
||||
app = App(name=f"报销-{uuid.uuid4().hex[:6]}", published_version=1)
|
||||
app = App(name=f"报销-{letters(6)}", published_version=1)
|
||||
session.add(app)
|
||||
await session.flush()
|
||||
session.add(AppVersion(app_id=app.id, version=1, spec_hash="h" * 64, bindings={},
|
||||
@@ -180,7 +180,7 @@ async def _finished_instance(app_id: str, *, started_by: str, verified: bool) ->
|
||||
async def test_the_initiator_s_personal_channel_receives_my_instance_finished(received):
|
||||
"""B1:个人渠道订阅了 run.finished,发起人自己发起的实例结束了收得到;应用自己没订阅也一样。"""
|
||||
app_id, _ = await _app([])
|
||||
name = f"uma{uuid.uuid4().hex[:6]}"
|
||||
name = f"uma{letters(6)}"
|
||||
mine = await _personal(name, ["run.finished"])
|
||||
run_id = await _finished_instance(app_id, started_by=name, verified=True)
|
||||
assert await service.notify_run(run_id, "run.finished") == 1
|
||||
@@ -192,7 +192,7 @@ async def test_the_initiator_s_personal_channel_receives_my_instance_finished(re
|
||||
async def test_a_self_declared_initiator_does_not_reach_a_personal_channel(received):
|
||||
"""关登录时署名是自己填的,谁都能填别人的名字:不按它推给那个人的个人渠道。"""
|
||||
app_id, _ = await _app([])
|
||||
name = f"vic{uuid.uuid4().hex[:6]}"
|
||||
name = f"vic{letters(6)}"
|
||||
mine = await _personal(name, ["run.finished"])
|
||||
run_id = await _finished_instance(app_id, started_by=name, verified=False)
|
||||
assert await service.notify_run(run_id, "run.finished") == 0
|
||||
|
||||
@@ -42,6 +42,7 @@ from app.data.xlsx_scan import Bounds, SheetScan, UnsupportedTable, WorkbookScan
|
||||
from app.db.base import SessionLocal
|
||||
from app.db.models import DataSource, ImportStaging, SourceSnapshot, TableImport, TableRecipe
|
||||
from app.main import app
|
||||
from tests.fixtures.names import letters
|
||||
|
||||
FLOW_RECIPE = json.loads((Path(__file__).parent / "fixtures" / "recipes" / "flow_recipe.json").read_text("utf-8"))
|
||||
FLOW_FULL = Recipe.model_validate(FLOW_RECIPE).model_dump(mode="json")
|
||||
@@ -294,7 +295,7 @@ async def client():
|
||||
|
||||
|
||||
def unique(prefix: str = "wp5c") -> str:
|
||||
return f"{prefix}_{uuid.uuid4().hex[:10]}"
|
||||
return f"{prefix}_{letters(10)}"
|
||||
|
||||
|
||||
def raw_bytes() -> bytes:
|
||||
|
||||
@@ -11,7 +11,6 @@ from __future__ import annotations
|
||||
|
||||
import datetime as dt
|
||||
import json
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@@ -26,6 +25,7 @@ from app.data.engine import engines, run_query
|
||||
from app.db.base import SessionLocal
|
||||
from app.db.models import DataSource, ImportStaging, SourceSnapshot, TableImport, TableRecipe
|
||||
from app.main import app
|
||||
from tests.fixtures.names import letters
|
||||
from tests.fixtures.xlsx.flow import FLOW_EXPECT, flow_workbook
|
||||
from tests.test_recipe_imports import ( # noqa: F401 - fake 是夹具,导入即可用
|
||||
FLOW_FULL, api_stage, fake, raw_bytes, recipe_source, staged,
|
||||
@@ -52,7 +52,7 @@ async def client():
|
||||
|
||||
|
||||
def unique(prefix: str = "wp5c") -> str:
|
||||
return f"{prefix}_{uuid.uuid4().hex[:10]}"
|
||||
return f"{prefix}_{letters(10)}"
|
||||
|
||||
|
||||
async def stage(client, name: str, raw: bytes, filename: str, **form: str):
|
||||
|
||||
@@ -901,6 +901,7 @@ migrate_rollback() {
|
||||
|
||||
copy_to_pg() {
|
||||
# --assume-stopped:服务是这里刚停的(强杀留下的 -wal 也不挡);快照放数据目录里(和库同一块盘,空间够)。
|
||||
# 内置库是直连(没有 PgBouncer),断点跟着目标地址走,不用 --direct-url。
|
||||
# 逐表的结果里来源是空表的几十行不列(没什么可核对的),别的原样打出来;退出码照 copy-to-postgres 的(pipefail)
|
||||
docker run --rm --network "$DOCKER_NETWORK" -v "$DATA_ABS:/data" -e "PGPASSFILE=$PG_PASSFILE" \
|
||||
--entrypoint python "$IMAGE" -m app.db copy-to-postgres /data \
|
||||
|
||||
Reference in New Issue
Block a user