"""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