fischer-agentkit/tests/unit/bitable/test_default_fields.py

195 lines
7.8 KiB
Python

"""Tests for default field creation on new tables (U2, R2).
Covers:
- create_table auto-creates 5 default fields with correct names/types/owners
- 状态 field config.options has 3 preset options (未开始/进行中/已完成)
- create_record auto-fills 创建人 field (agent-owned, system placeholder)
- create_record auto-fills 创建时间 field (agent-owned, ISO timestamp)
- User-supplied values for agent-owned fields are overwritten (system-managed)
"""
from __future__ import annotations
import pytest
from agentkit.bitable.models import FieldOwner, FieldType
pytestmark = pytest.mark.postgres
# ---------------------------------------------------------------------------
# Default fields on table creation
# ---------------------------------------------------------------------------
async def test_create_table_creates_5_default_fields(bitable_service) -> None:
"""create_table auto-creates 5 default fields with correct names/types/owners."""
table = await bitable_service.create_table(name="客户")
fields = await bitable_service.list_fields(table.id)
assert len(fields) == 5
field_map = {f.name: f for f in fields}
# 标题 (text, user)
assert "标题" in field_map
assert field_map["标题"].field_type == FieldType.text
assert field_map["标题"].owner == FieldOwner.user
# 状态 (select, user) — options checked in separate test
assert "状态" in field_map
assert field_map["状态"].field_type == FieldType.select
assert field_map["状态"].owner == FieldOwner.user
# 日期 (date, user)
assert "日期" in field_map
assert field_map["日期"].field_type == FieldType.date
assert field_map["日期"].owner == FieldOwner.user
# 创建人 (text, agent)
assert "创建人" in field_map
assert field_map["创建人"].field_type == FieldType.text
assert field_map["创建人"].owner == FieldOwner.agent
# 创建时间 (date, agent)
assert "创建时间" in field_map
assert field_map["创建时间"].field_type == FieldType.date
assert field_map["创建时间"].owner == FieldOwner.agent
async def test_status_field_has_3_preset_options(bitable_service) -> None:
"""状态 field config.options has 3 preset options with labels and colors."""
table = await bitable_service.create_table(name="T1")
fields = await bitable_service.list_fields(table.id)
status_field = next(f for f in fields if f.name == "状态")
options = status_field.config.get("options", [])
assert len(options) == 3
labels = [o["label"] for o in options]
values = [o["value"] for o in options]
assert labels == ["未开始", "进行中", "已完成"]
assert values == ["not_started", "in_progress", "done"]
# Each option has a color
for opt in options:
assert "color" in opt
assert opt["color"] in ("default", "processing", "success")
async def test_default_fields_are_created_under_correct_table(bitable_service) -> None:
"""Default fields are created under the new table's ID, not some other table."""
table1 = await bitable_service.create_table(name="T1")
table2 = await bitable_service.create_table(name="T2")
fields1 = await bitable_service.list_fields(table1.id)
fields2 = await bitable_service.list_fields(table2.id)
# Each table has its own 5 default fields
assert len(fields1) == 5
assert len(fields2) == 5
# Field IDs are distinct across tables
ids1 = {f.id for f in fields1}
ids2 = {f.id for f in fields2}
assert ids1.isdisjoint(ids2)
# All fields point to the right table
assert all(f.table_id == table1.id for f in fields1)
assert all(f.table_id == table2.id for f in fields2)
# ---------------------------------------------------------------------------
# Agent-owned field auto-fill on record creation
# ---------------------------------------------------------------------------
async def test_create_record_auto_fills_creator_field(bitable_service) -> None:
"""create_record auto-fills 创建人 field (agent-owned)."""
table = await bitable_service.create_table(name="T1")
fields = await bitable_service.list_fields(table.id)
creator_field = next(f for f in fields if f.name == "创建人")
record = await bitable_service.create_record(table.id, values={})
# 创建人 is auto-filled with "system" placeholder
assert record.values.get(creator_field.id) == "system"
async def test_create_record_auto_fills_created_time_field(bitable_service) -> None:
"""create_record auto-fills 创建时间 field with an ISO timestamp."""
table = await bitable_service.create_table(name="T1")
fields = await bitable_service.list_fields(table.id)
time_field = next(f for f in fields if f.name == "创建时间")
record = await bitable_service.create_record(table.id, values={})
time_val = record.values.get(time_field.id)
assert time_val is not None
# Should be an ISO 8601 string (parseable by datetime.fromisoformat)
from datetime import datetime
parsed = datetime.fromisoformat(time_val)
assert parsed.tzinfo is not None # timezone-aware
async def test_create_record_overwrites_user_supplied_agent_field(bitable_service) -> None:
"""User-supplied values for agent-owned fields are overwritten by system values.
Per R2 edge case: agent-owned fields are system-managed — user input is ignored.
"""
table = await bitable_service.create_table(name="T1")
fields = await bitable_service.list_fields(table.id)
creator_field = next(f for f in fields if f.name == "创建人")
# User tries to set 创建人 to "hacker"
record = await bitable_service.create_record(table.id, values={creator_field.id: "hacker"})
# System overwrites with "system" — agent ownership means system-managed
assert record.values.get(creator_field.id) == "system"
async def test_create_record_preserves_user_owned_field_values(bitable_service) -> None:
"""User-supplied values for user-owned fields are preserved as-is."""
table = await bitable_service.create_table(name="T1")
fields = await bitable_service.list_fields(table.id)
title_field = next(f for f in fields if f.name == "标题")
status_field = next(f for f in fields if f.name == "状态")
record = await bitable_service.create_record(
table.id,
values={
title_field.id: "Acme Corp",
status_field.id: "in_progress",
},
)
assert record.values.get(title_field.id) == "Acme Corp"
assert record.values.get(status_field.id) == "in_progress"
# ---------------------------------------------------------------------------
# DEFAULT_FIELD_TEMPLATES constant (unit test, no PG needed)
# ---------------------------------------------------------------------------
def test_default_field_templates_has_5_entries() -> None:
"""DEFAULT_FIELD_TEMPLATES has exactly 5 entries (no PG required)."""
from agentkit.bitable.models import DEFAULT_FIELD_TEMPLATES
assert len(DEFAULT_FIELD_TEMPLATES) == 5
def test_default_field_templates_names_match_feishu_defaults() -> None:
"""Default field names match Feishu Bitable defaults."""
from agentkit.bitable.models import DEFAULT_FIELD_TEMPLATES
names = [t["name"] for t in DEFAULT_FIELD_TEMPLATES]
assert names == ["标题", "状态", "日期", "创建人", "创建时间"]
def test_default_field_templates_owners_match_plan() -> None:
"""Default field owners: 标题/状态/日期 are user-owned, 创建人/创建时间 are agent-owned."""
from agentkit.bitable.models import DEFAULT_FIELD_TEMPLATES
owner_map = {t["name"]: t["owner"] for t in DEFAULT_FIELD_TEMPLATES}
assert owner_map["标题"] == FieldOwner.user
assert owner_map["状态"] == FieldOwner.user
assert owner_map["日期"] == FieldOwner.user
assert owner_map["创建人"] == FieldOwner.agent
assert owner_map["创建时间"] == FieldOwner.agent