Files
javadandCursor b93ecf1691 Initial commit of the Kamidso API.
Keep source, config, and deploy files. Leave runtime data, uploads, and logs out of git.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-10 13:43:09 +03:30

596 lines
21 KiB
Python

import json
import pytest
from kamidso.contracts.errors import KamidsoError
from kamidso.contracts.user import CurrentUser
from kamidso.data_graph.explorer.contracts import normalize_source_map
from kamidso.data_graph.service import DataGraphService
from kamidso.data_graph.tools.graph_search import (
format_attached_links,
links_between_resources,
search_maps,
)
from kamidso.data_graph.tools.source_inspection import SourceInspector
from kamidso.repositories.store import RepositoryStore
from kamidso.resources.files import FileService
from kamidso.settings.flags import load_kamidso_settings
from kamidso.storage.local import LocalStorage
USER = CurrentUser(
id="usr_test",
email="test@kamidso.test",
name="Test",
role="developer",
workspace_id="ws_test",
)
def _user():
return USER
@pytest.mark.asyncio
async def test_excel_inspect_returns_raw_grid_not_a_header_rule(tmp_path):
import openpyxl
path = tmp_path / "finance.xlsx"
wb = openpyxl.Workbook()
ws = wb.active
ws.title = "Sheet1"
ws["A1"] = "گزارش مالی ۱۴۰۵"
ws["A3"] = "پرداخت‌کننده"
ws.merge_cells("A3:B3")
ws["A4"] = "اطلاعات فردی"
ws["C4"] = "حساب"
ws["A5"] = "نام"
ws["B5"] = "کد"
ws["C5"] = "بانک"
ws["D5"] = "مبلغ"
wb.save(path)
settings = load_kamidso_settings()
store = RepositoryStore()
storage = LocalStorage(str(tmp_path / "storage"))
doc = await FileService(store, storage, settings).upload(USER, "finance.xlsx", path.read_bytes())
inspector = SourceInspector(store, storage, USER)
overview = await inspector.inspect_resource(doc["_id"])
assert overview["kind"] == "excel"
assert overview["sheets"][0]["merged"]
grid = await inspector.inspect_path(doc["_id"], "Sheet1!A1:D5")
values = {cell["a"]: cell["v"] for cell in grid["cells"]}
assert values["A1"] == "گزارش مالی ۱۴۰۵"
assert values["A3"] == "پرداخت‌کننده"
assert values["A5"] == "نام"
assert any("A3" in item for item in grid["merged"])
assert "header_row" not in grid
@pytest.mark.asyncio
async def test_excel_overview_lists_every_data_sheet(tmp_path):
import openpyxl
path = tmp_path / "employees.xlsx"
wb = openpyxl.Workbook()
employees = wb.active
employees.title = "employees"
employees["A1"] = "employee_id"
employees["B1"] = "نام"
departments = wb.create_sheet("departments")
departments["A1"] = "department_id"
departments["B1"] = "نام واحد"
readme = wb.create_sheet("README")
readme["A1"] = "هدف فایل"
readme["B1"] = "دیتاست تست"
wb.save(path)
settings = load_kamidso_settings()
store = RepositoryStore()
storage = LocalStorage(str(tmp_path / "storage"))
doc = await FileService(store, storage, settings).upload(USER, "employees.xlsx", path.read_bytes())
inspector = SourceInspector(store, storage, USER)
overview = await inspector.inspect_resource(doc["_id"])
assert [item["name"] for item in overview["sheets"]] == ["employees", "departments", "README"]
assert overview["data_sheets"] == ["employees", "departments"]
assert next(item for item in overview["sheets"] if item["name"] == "README")["role"] == "notes"
preview = next(item for item in overview["sheets"] if item["name"] == "departments")["preview"]
assert any(cell["v"] == "department_id" for cell in preview)
@pytest.mark.asyncio
async def test_discovery_keeps_every_excel_data_sheet(tmp_path):
import openpyxl
path = tmp_path / "employees.xlsx"
wb = openpyxl.Workbook()
employees = wb.active
employees.title = "employees"
employees["A1"] = "employee_id"
employees["B1"] = "نام"
employees["A2"] = "E001"
departments = wb.create_sheet("departments")
departments["A1"] = "department_id"
departments["B1"] = "نام واحد"
departments["A2"] = "D001"
readme = wb.create_sheet("README")
readme["A1"] = "هدف فایل"
readme["B1"] = "دیتاست تست"
wb.save(path)
settings = load_kamidso_settings()
store = RepositoryStore()
storage = LocalStorage(str(tmp_path / "storage"))
doc = await FileService(store, storage, settings).upload(USER, "employees.xlsx", path.read_bytes())
service = DataGraphService(store, storage)
calls = []
async def complete(prompt: str) -> str:
calls.append(prompt)
payload = {
"output": {
"kind": "excel",
"coverage": "observed_partial",
"datasets": [
{
"name": "employees",
"profile": {"domain": "hr roster", "row_grain": "one employee"},
"fields": [
{
"name": "employee_id",
"path": ["employee_id"],
"address": "excel://employees.xlsx/employees/employee_id",
"data_type": "string",
},
{
"name": "نام",
"path": ["نام"],
"address": "excel://employees.xlsx/employees/نام",
"data_type": "string",
},
],
}
],
}
}
return "Thought: employees only.\nAction: terminate\nAction Input: " + json.dumps(payload, ensure_ascii=False)
saved = await service.discover(USER, doc["_id"], complete)
assert calls
names = [item["name"] for item in saved["datasets"]]
assert names == ["employees", "departments"]
employees = next(item for item in saved["datasets"] if item["name"] == "employees")
assert [field["name"] for field in employees["fields"]] == ["employee_id", "نام"]
dept = next(item for item in saved["datasets"] if item["name"] == "departments")
assert [field["name"] for field in dept["fields"]] == ["department_id", "نام واحد"]
@pytest.mark.asyncio
async def test_excel_inspect_caps_to_header_band(tmp_path):
import openpyxl
path = tmp_path / "employees.xlsx"
wb = openpyxl.Workbook()
ws = wb.active
ws.title = "employees"
ws["A1"] = "employee_id"
for row in range(2, 32):
ws.cell(row=row, column=1, value=f"E{row:03d}")
wb.save(path)
settings = load_kamidso_settings()
store = RepositoryStore()
storage = LocalStorage(str(tmp_path / "storage"))
doc = await FileService(store, storage, settings).upload(USER, "employees.xlsx", path.read_bytes())
inspector = SourceInspector(store, storage, USER)
grid = await inspector.inspect_path(doc["_id"], "employees!A1:A31")
addresses = {cell["a"] for cell in grid["cells"]}
assert "A1" in addresses
assert "A8" in addresses
assert "A9" not in addresses
assert grid["truncated"] is True
@pytest.mark.asyncio
async def test_sqlite_inspect_lists_declared_columns(tmp_path):
from kamidso.engine.ext.datasource.rdbms.conn_sqlite import SQLiteConnector
db_path = tmp_path / "crm.sqlite"
connector = SQLiteConnector.from_file_path(str(db_path))
connector.run("CREATE TABLE customers (id INTEGER PRIMARY KEY, name TEXT, payload TEXT)")
connector.close()
store = RepositoryStore()
doc = await store.create_datasource(
{
"workspace_id": USER.workspace_id,
"name": "CRM",
"type": "sqlite",
"db_type": "sqlite",
"db_path": str(db_path),
"status": "active",
}
)
inspector = SourceInspector(store, None, USER)
overview = await inspector.inspect_resource(doc["_id"])
assert "customers" in overview["tables"]
described = await inspector.inspect_path(doc["_id"], "customers")
names = [col["name"] for col in described["columns"]]
assert names == ["id", "name", "payload"]
assert described["coverage_hint"] == "declared_complete"
def test_source_map_requires_explicit_coverage():
with pytest.raises(KamidsoError):
normalize_source_map(
{"datasets": [{"name": "t", "fields": [{"name": "a", "address": "sql://x"}]}]},
resource_id="r",
resource_name="r",
resource_type="file",
)
def test_search_ignores_suggested_edges():
maps = [
{
"status": "ready",
"resource_id": "hr",
"resource_name": "HR",
"kind": "excel",
"datasets": [
{
"name": "employees",
"fields": [
{
"id": "field_name",
"name": "full_name",
"path": ["full_name"],
"address": "excel://hr/employees/full_name",
"meaning": "نام کارمند",
}
],
}
],
}
]
edges = [
{
"from": "field_name",
"to": "field_payer",
"from_address": "excel://hr/employees/full_name",
"to_address": "excel://finance/payer",
"type": "entity_match",
"status": "suggested",
}
]
result = search_maps(maps, edges, "نام کارمند")
assert result["relevant_fields"][0]["address"] == "excel://hr/employees/full_name"
assert result["confirmed_paths"] == []
def _two_source_maps():
return [
{
"status": "ready",
"resource_id": "file_hr",
"resource_name": "employees",
"datasets": [
{
"name": "employees",
"fields": [
{
"id": "field_name",
"name": "نام",
"address": "excel://employees.xlsx/employees/نام",
}
],
}
],
},
{
"status": "ready",
"resource_id": "file_fin",
"resource_name": "Book1",
"datasets": [
{
"name": "1404",
"fields": [
{
"id": "field_payer",
"name": "نام پرداخت کننده",
"address": "excel://Book1.xlsx/1404/نام پرداخت کننده",
}
],
}
],
},
]
def test_one_attached_source_gets_no_graph_context():
assert links_between_resources(_two_source_maps(), [], ["file_hr"]) is None
def test_two_attached_sources_get_only_confirmed_cross_links():
edges = [
{
"from": "field_name",
"to": "field_payer",
"type": "same_concept",
"status": "confirmed",
},
{
"from": "field_name",
"to": "field_payer",
"type": "same_concept",
"status": "suggested",
},
]
attached = links_between_resources(_two_source_maps(), edges, ["file_hr", "file_fin"])
assert attached is not None
assert attached["confirmed_links"] == [
{
"from_resource": "employees",
"from_field": "نام",
"from_address": "excel://employees.xlsx/employees/نام",
"to_resource": "Book1",
"to_field": "نام پرداخت کننده",
"to_address": "excel://Book1.xlsx/1404/نام پرداخت کننده",
"type": "same_concept",
}
]
text = format_attached_links(attached)
assert "Confirmed links between attached sources" in text
assert "excel://employees.xlsx/employees/نام" in text
missing = links_between_resources(_two_source_maps(), [], ["file_hr", "file_fin"])
assert missing is not None
assert missing["confirmed_links"] == []
assert "Do not invent a join" in format_attached_links(missing)
@pytest.mark.asyncio
async def test_structure_agent_saves_contract(tmp_path):
settings = load_kamidso_settings()
store = RepositoryStore()
storage = LocalStorage(str(tmp_path / "storage"))
doc = await FileService(store, storage, settings).upload(
USER, "note.json", json.dumps({"customer": {"id": "C1"}}).encode("utf-8"), mime_type="application/json"
)
service = DataGraphService(store, storage)
payload = {
"output": {
"kind": "json",
"coverage": "observed_partial",
"coverage_detail": {"documents_sampled": 1},
"datasets": [
{
"name": "customer",
"fields": [
{
"name": "id",
"path": ["customer", "id"],
"address": "json://note.json/customer/id",
"data_type": "string",
"confidence": 0.8,
}
],
}
],
}
}
async def complete(prompt: str) -> str:
if "Observation" not in prompt:
return (
"Thought: look at the file.\n"
"Action: inspect_resource\n"
f'Action Input: {{"resource_id": "{doc["_id"]}"}}\n'
)
return (
"Thought: the sample is one object.\n"
"Action: terminate\n"
f"Action Input: {json.dumps(payload, ensure_ascii=False)}\n"
)
saved = await service.discover(USER, doc["_id"], complete)
assert saved["coverage"] == "observed_partial"
assert saved["datasets"][0]["fields"][0]["address"].endswith("/customer/id")
def _json_map_payload(semantic=None):
field = {
"name": "id",
"path": ["customer", "id"],
"address": "json://note.json/customer/id",
"data_type": "string",
"confidence": 0.8,
}
if semantic:
field.update(semantic)
return {
"output": {
"kind": "json",
"coverage": "observed_partial",
"coverage_detail": {"documents_sampled": 1},
"datasets": [{"name": "customer", "fields": [field]}],
}
}
def _inspect_then(payload):
async def complete(prompt: str) -> str:
if "Observation" not in prompt:
return (
"Thought: look at the file.\n"
"Action: inspect_resource\n"
'Action Input: {"resource_id": "ignored"}\n'
)
return (
"Thought: structure is ready.\n"
"Action: terminate\n"
f"Action Input: {json.dumps(payload, ensure_ascii=False)}\n"
)
return complete
@pytest.mark.asyncio
async def test_discovery_failure_keeps_the_last_good_map(tmp_path):
settings = load_kamidso_settings()
store = RepositoryStore()
storage = LocalStorage(str(tmp_path / "storage"))
doc = await FileService(store, storage, settings).upload(
USER, "note.json", json.dumps({"customer": {"id": "C1"}}).encode("utf-8"), mime_type="application/json"
)
service = DataGraphService(store, storage)
saved = await service.discover(USER, doc["_id"], _inspect_then(_json_map_payload()))
assert saved["status"] == "ready"
async def boom(_prompt: str) -> str:
raise RuntimeError("timeout")
with pytest.raises(Exception):
await service.discover(USER, doc["_id"], boom)
kept = await service.list_maps(USER)
assert kept[0]["status"] == "ready"
assert kept[0]["datasets"][0]["fields"][0]["address"].endswith("/customer/id")
assert kept[0]["last_discovery_error"]
@pytest.mark.asyncio
async def test_user_confirm_survives_rediscovery_and_chat(tmp_path):
settings = load_kamidso_settings()
store = RepositoryStore()
storage = LocalStorage(str(tmp_path / "storage"))
doc = await FileService(store, storage, settings).upload(
USER, "note.json", json.dumps({"customer": {"id": "C1"}}).encode("utf-8"), mime_type="application/json"
)
service = DataGraphService(store, storage)
first = await service.discover(
USER,
doc["_id"],
_inspect_then(
_json_map_payload(
{
"semantic_label": "customer_id",
"meaning": "شناسه مشتری",
"semantic_status": "inferred",
"semantic_confidence": 0.7,
}
)
),
)
field_id = first["datasets"][0]["fields"][0]["id"]
confirmed = await service.update_field_semantic(
USER,
field_id,
semantic_label="customer_id",
meaning="شناسه یکتای مشتری",
status="confirmed",
)
assert confirmed["semantic_status"] == "confirmed"
assert confirmed["semantic_source"] == "user"
second = await service.discover(
USER,
doc["_id"],
_inspect_then(
_json_map_payload(
{
"semantic_label": "invoice_id",
"meaning": "شماره فاکتور",
"semantic_status": "inferred",
"semantic_confidence": 0.99,
}
)
),
)
kept = second["datasets"][0]["fields"][0]
assert kept["semantic_status"] == "confirmed"
assert kept["semantic_label"] == "customer_id"
assert kept["meaning"] == "شناسه یکتای مشتری"
async def semantic_complete(prompt: str) -> str:
if "Observation" not in prompt:
return "Thought: context\nAction: get_field_context\nAction Input: {}\n"
return (
"Thought: different guess\n"
"Action: terminate\n"
"Action Input: "
+ json.dumps(
{
"output": {
"fields": [
{
"field_id": field_id,
"semantic_label": "invoice_number",
"meaning": "شماره فاکتور",
"semantic_status": "inferred",
"semantic_confidence": 0.99,
}
]
}
},
ensure_ascii=False,
)
)
inspected = await service.inspect_field_semantics(
USER, [field_id], "مبلغ فاکتور", semantic_complete
)
assert inspected["fields"][0]["semantic_status"] == "confirmed"
assert inspected["fields"][0]["needs_user_input"] is True
stored = (await service.list_maps(USER))[0]["datasets"][0]["fields"][0]
assert stored["semantic_label"] == "customer_id"
assert stored["semantic_status"] == "confirmed"
@pytest.mark.asyncio
async def test_semantic_unresolved_asks_for_user_input(tmp_path):
settings = load_kamidso_settings()
store = RepositoryStore()
storage = LocalStorage(str(tmp_path / "storage"))
doc = await FileService(store, storage, settings).upload(
USER, "note.json", json.dumps({"customer": {"id": "C1"}}).encode("utf-8"), mime_type="application/json"
)
service = DataGraphService(store, storage)
saved = await service.discover(
USER,
doc["_id"],
_inspect_then(_json_map_payload({"name": "x17", "path": ["customer", "x17"], "address": "json://note.json/customer/x17"})),
)
field_id = saved["datasets"][0]["fields"][0]["id"]
async def semantic_complete(prompt: str) -> str:
if "Observation" not in prompt:
return "Thought: context\nAction: get_field_context\nAction Input: {}\n"
return (
"Thought: still unclear\n"
"Action: terminate\n"
"Action Input: "
+ json.dumps(
{
"output": {
"fields": [
{
"field_id": field_id,
"semantic_label": "",
"meaning": "",
"semantic_status": "unknown",
"semantic_confidence": 0.2,
"needs_user_input": True,
}
]
}
}
)
)
inspected = await service.inspect_field_semantics(USER, [field_id], "مطالبات مشتری", semantic_complete)
assert inspected["fields"][0]["semantic_status"] == "unknown"
assert inspected["fields"][0]["needs_user_input"] is True
review = await service.list_semantic_review(USER)
assert all(item["field_id"] != field_id for item in review)