Files
sfera/packages/semantic-kernel/tests/test_xml_indexing.py
T
m 3c7b1825c4
CI / python (push) Has been cancelled
CI / rust (push) Has been cancelled
Cover full 1C metadata object catalog
2026-05-21 18:05:28 +03:00

480 lines
20 KiB
Python

from pathlib import Path
from semantic_kernel import index_project
from sir import EdgeKind, NodeKind
from ui_semantics import form_semantics
def test_index_project_extracts_xml_ui_semantics(tmp_path: Path):
xml = tmp_path / "form.xml"
xml.write_text(
"""
<Form name="ФормаДокумента" qualifiedName="Документ.Заказ.ФормаДокумента">
<Command name="Провести" qualifiedName="Документ.Заказ.ФормаДокумента.Провести" />
</Form>
""",
encoding="utf-8",
)
snapshot = index_project(tmp_path, project_id="demo")
assert any(node.kind == NodeKind.FORM for node in snapshot.nodes)
assert any(edge.kind == EdgeKind.HAS_COMMAND for edge in snapshot.edges)
assert form_semantics(snapshot)[0].commands[0].name == "Провести"
def test_index_project_extracts_1c_metadata_objects(tmp_path: Path):
xml = tmp_path / "metadata.xml"
xml.write_text(
"""
<Configuration>
<Catalog name="Номенклатура" qualifiedName="Справочник.Номенклатура">
<Attribute name="Артикул" qualifiedName="Справочник.Номенклатура.Артикул" />
</Catalog>
<Document name="ЗаказПокупателя" qualifiedName="Документ.ЗаказПокупателя">
<TabularSection name="Товары" qualifiedName="Документ.ЗаказПокупателя.Товары">
<Attribute name="Номенклатура" qualifiedName="Документ.ЗаказПокупателя.Товары.Номенклатура" />
</TabularSection>
<Form name="ФормаДокумента" qualifiedName="Документ.ЗаказПокупателя.ФормаДокумента">
<Command name="Провести" qualifiedName="Документ.ЗаказПокупателя.ФормаДокумента.Провести" />
</Form>
</Document>
<Role name="Менеджер" qualifiedName="Роль.Менеджер" />
<MetadataObject type="ScheduledJob">
<Name>ОбменСКассовымУзлом</Name>
<QualifiedName>РегламентноеЗадание.ОбменСКассовымУзлом</QualifiedName>
</MetadataObject>
</Configuration>
""",
encoding="utf-8",
)
snapshot = index_project(tmp_path, project_id="metadata")
assert any(node.kind == NodeKind.CATALOG and node.name == "Номенклатура" for node in snapshot.nodes)
assert any(node.kind == NodeKind.DOCUMENT and node.name == "ЗаказПокупателя" for node in snapshot.nodes)
assert any(node.kind == NodeKind.TABULAR_SECTION and node.name == "Товары" for node in snapshot.nodes)
assert any(node.kind == NodeKind.ROLE and node.name == "Менеджер" for node in snapshot.nodes)
assert any(node.kind == NodeKind.SCHEDULED_JOB for node in snapshot.nodes)
assert any(edge.kind == EdgeKind.HAS_ATTRIBUTE for edge in snapshot.edges)
assert any(edge.kind == EdgeKind.HAS_TABULAR_SECTION for edge in snapshot.edges)
tabular_section = next(node for node in snapshot.nodes if node.kind == NodeKind.TABULAR_SECTION)
column = next(node for node in snapshot.nodes if node.kind == NodeKind.ATTRIBUTE and node.name == "Номенклатура")
assert any(
edge.kind == EdgeKind.HAS_ATTRIBUTE
and edge.source_lineage == tabular_section.lineage_id
and edge.target_lineage == column.lineage_id
for edge in snapshot.edges
)
assert any(edge.kind == EdgeKind.HAS_FORM for edge in snapshot.edges)
assert any(edge.kind == EdgeKind.HAS_COMMAND for edge in snapshot.edges)
def test_index_project_keeps_extended_1c_metadata_objects(tmp_path: Path):
xml = tmp_path / "metadata.xml"
xml.write_text(
"""
<Configuration>
<Subsystem name="Продажи" qualifiedName="Подсистема.Продажи" />
<Role name="Менеджер" qualifiedName="Роль.Менеджер" />
<Sequence name="ПроведениеДокументов" qualifiedName="Последовательность.ПроведениеДокументов" />
<DocumentNumerator name="ОбщийНумератор" qualifiedName="НумераторДокументов.ОбщийНумератор" />
<CommonForm name="ФормаПодбора" qualifiedName="ОбщаяФорма.ФормаПодбора" />
<FunctionalOption name="ДоступностьСкидок" qualifiedName="ФункциональнаяОпция.ДоступностьСкидок" />
<WebService name="Обмен" qualifiedName="WebСервис.Обмен" />
<WSReference name="ВнешнийСервис" qualifiedName="WSСсылка.ВнешнийСервис" />
<WebSocketClient name="Чат" qualifiedName="WebSocketКлиент.Чат" />
<IntegrationService name="Интеграция" qualifiedName="СервисИнтеграции.Интеграция" />
</Configuration>
""",
encoding="utf-8",
)
snapshot = index_project(tmp_path, project_id="extended-metadata")
by_kind = {node.kind for node in snapshot.nodes}
assert {
NodeKind.SUBSYSTEM,
NodeKind.ROLE,
NodeKind.SEQUENCE,
NodeKind.DOCUMENT_NUMERATOR,
NodeKind.COMMON_FORM,
NodeKind.FUNCTIONAL_OPTION,
NodeKind.WEB_SERVICE,
NodeKind.WS_REFERENCE,
NodeKind.WEBSOCKET_CLIENT,
NodeKind.INTEGRATION_SERVICE,
}.issubset(by_kind)
def test_index_project_remaps_edges_from_duplicate_metadata_nodes(tmp_path: Path):
first = tmp_path / "first.xml"
first.write_text(
"""
<Configuration>
<MetadataObject type="ChartOfCharacteristicTypes">
<Name>СвойстваОбъектов</Name>
<QualifiedName>ПланВидовХарактеристик.СвойстваОбъектов</QualifiedName>
</MetadataObject>
</Configuration>
""",
encoding="utf-8",
)
second = tmp_path / "second.xml"
second.write_text(
"""
<Configuration>
<MetadataObject type="ChartOfCharacteristicTypes">
<Name>СвойстваОбъектов</Name>
<QualifiedName>ПланВидовХарактеристик.СвойстваОбъектов</QualifiedName>
</MetadataObject>
<Attribute name="ТипЗначения" qualifiedName="ПланВидовХарактеристик.СвойстваОбъектов.ТипЗначения" />
</Configuration>
""",
encoding="utf-8",
)
snapshot = index_project(tmp_path, project_id="metadata-duplicates")
chart = next(node for node in snapshot.nodes if node.kind == NodeKind.CHART_OF_CHARACTERISTIC_TYPES)
attribute = next(node for node in snapshot.nodes if node.kind == NodeKind.ATTRIBUTE)
assert any(
edge.kind == EdgeKind.HAS_ATTRIBUTE
and edge.source_lineage == chart.lineage_id
and edge.target_lineage == attribute.lineage_id
for edge in snapshot.edges
)
def test_index_project_links_document_metadata_to_object_module(tmp_path: Path):
xml = tmp_path / "metadata.xml"
xml.write_text(
"""
<Configuration>
<Document name="ЗаказПокупателя" qualifiedName="Документ.ЗаказПокупателя" />
</Configuration>
""",
encoding="utf-8",
)
module = tmp_path / "Documents" / "ЗаказПокупателя" / "Ext" / "ObjectModule.bsl"
module.parent.mkdir(parents=True)
module.write_text(
"""
Процедура Проведение()
КонецПроцедуры
""",
encoding="utf-8",
)
snapshot = index_project(tmp_path, project_id="metadata-links")
document = next(node for node in snapshot.nodes if node.kind == NodeKind.DOCUMENT)
module_node = next(node for node in snapshot.nodes if node.kind == NodeKind.MODULE)
link = next(
edge
for edge in snapshot.edges
if edge.kind == EdgeKind.CONTAINS
and edge.source_lineage == document.lineage_id
and edge.target_lineage == module_node.lineage_id
)
assert link.attributes["module_role"] == "OBJECT_MODULE"
def test_index_project_links_common_module_metadata_to_bsl_module(tmp_path: Path):
xml = tmp_path / "metadata.xml"
xml.write_text(
"""
<Configuration>
<CommonModule name="ИнтеграцияСКассой" qualifiedName="ОбщийМодуль.ИнтеграцияСКассой" />
</Configuration>
""",
encoding="utf-8",
)
module = tmp_path / "CommonModules" / "ИнтеграцияСКассой" / "Module.bsl"
module.parent.mkdir(parents=True)
module.write_text(
"""
Процедура ОтправитьЧек()
КонецПроцедуры
""",
encoding="utf-8",
)
snapshot = index_project(tmp_path, project_id="common-module-links")
common_module = next(node for node in snapshot.nodes if node.kind == NodeKind.COMMON_MODULE)
bsl_module = next(node for node in snapshot.nodes if node.kind == NodeKind.MODULE)
assert any(
edge.kind == EdgeKind.CONTAINS
and edge.source_lineage == common_module.lineage_id
and edge.target_lineage == bsl_module.lineage_id
for edge in snapshot.edges
)
def test_index_project_reads_edt_mdo_metadata(tmp_path: Path):
catalog_dir = tmp_path / "src" / "Catalogs" / "Товары"
catalog_dir.mkdir(parents=True)
(catalog_dir / "Товары.mdo").write_text(
"""
<mdclass:Catalog xmlns:mdclass="http://g5.1c.ru/v8/dt/metadata/mdclass">
<name>Товары</name>
<attributes>
<name>Артикул</name>
</attributes>
<forms>
<name>ФормаЭлемента</name>
</forms>
</mdclass:Catalog>
""",
encoding="utf-8",
)
report_dir = tmp_path / "src" / "Reports" / "АнализПродаж"
report_dir.mkdir(parents=True)
(report_dir / "АнализПродаж.mdo").write_text(
"""
<mdclass:Report xmlns:mdclass="http://g5.1c.ru/v8/dt/metadata/mdclass">
<name>АнализПродаж</name>
</mdclass:Report>
""",
encoding="utf-8",
)
snapshot = index_project(tmp_path, project_id="edt-mdo")
catalog = next(node for node in snapshot.nodes if node.kind == NodeKind.CATALOG)
attribute = next(node for node in snapshot.nodes if node.kind == NodeKind.ATTRIBUTE and node.name == "Артикул")
form = next(node for node in snapshot.nodes if node.kind == NodeKind.FORM)
report = next(node for node in snapshot.nodes if node.kind == NodeKind.REPORT)
assert catalog.qualified_name == "Справочник.Товары"
assert attribute.qualified_name == "Справочник.Товары.Артикул"
assert form.qualified_name == "Справочник.Товары.ФормаЭлемента"
assert report.qualified_name == "Отчет.АнализПродаж"
assert any(edge.kind == EdgeKind.HAS_ATTRIBUTE and edge.source_lineage == catalog.lineage_id for edge in snapshot.edges)
assert any(edge.kind == EdgeKind.HAS_FORM and edge.source_lineage == catalog.lineage_id for edge in snapshot.edges)
def test_index_project_preserves_register_dimension_and_resource_roles(tmp_path: Path):
register_dir = tmp_path / "src" / "AccumulationRegisters" / "ОстаткиТоваров"
register_dir.mkdir(parents=True)
(register_dir / "ОстаткиТоваров.mdo").write_text(
"""
<mdclass:AccumulationRegister xmlns:mdclass="http://g5.1c.ru/v8/dt/metadata/mdclass">
<name>ОстаткиТоваров</name>
<dimensions>
<name>Номенклатура</name>
</dimensions>
<resources>
<name>Количество</name>
</resources>
</mdclass:AccumulationRegister>
""",
encoding="utf-8",
)
snapshot = index_project(tmp_path, project_id="register-fields")
register = next(node for node in snapshot.nodes if node.kind == NodeKind.REGISTER)
dimension = next(node for node in snapshot.nodes if node.kind == NodeKind.ATTRIBUTE and node.name == "Номенклатура")
resource = next(node for node in snapshot.nodes if node.kind == NodeKind.ATTRIBUTE and node.name == "Количество")
assert dimension.attributes["attribute_role"] == "DIMENSION"
assert resource.attributes["attribute_role"] == "RESOURCE"
assert any(
edge.kind == EdgeKind.HAS_ATTRIBUTE
and edge.source_lineage == register.lineage_id
and edge.target_lineage == dimension.lineage_id
for edge in snapshot.edges
)
assert any(
edge.kind == EdgeKind.HAS_ATTRIBUTE
and edge.source_lineage == register.lineage_id
and edge.target_lineage == resource.lineage_id
for edge in snapshot.edges
)
def test_index_project_links_role_rights_to_metadata_objects(tmp_path: Path):
xml = tmp_path / "roles.xml"
xml.write_text(
"""
<Configuration>
<Document name="ЗаказПокупателя" qualifiedName="Документ.ЗаказПокупателя" />
<Role name="Менеджер" qualifiedName="Роль.Менеджер">
<Right object="Документ.ЗаказПокупателя" read="true" write="true" post="true" />
</Role>
</Configuration>
""",
encoding="utf-8",
)
snapshot = index_project(tmp_path, project_id="role-rights")
role = next(node for node in snapshot.nodes if node.kind == NodeKind.ROLE)
document = next(node for node in snapshot.nodes if node.kind == NodeKind.DOCUMENT)
right = next(edge for edge in snapshot.edges if edge.kind == EdgeKind.GRANTS_ACCESS)
assert right.source_lineage == role.lineage_id
assert right.target_lineage == document.lineage_id
assert right.attributes["post"] == "true"
def test_index_project_links_child_element_command_action_and_role_right(tmp_path: Path):
xml = tmp_path / "metadata.xml"
xml.write_text(
"""
<Configuration>
<Document>
<Name>ЗаказПокупателя</Name>
<QualifiedName>Документ.ЗаказПокупателя</QualifiedName>
<Form>
<Name>ФормаДокумента</Name>
<Command>
<Name>Провести</Name>
<Action>ПровестиКоманда</Action>
</Command>
</Form>
</Document>
<Role>
<Name>Менеджер</Name>
<Right read="true">
<Object>Документ.ЗаказПокупателя</Object>
</Right>
</Role>
</Configuration>
""",
encoding="utf-8",
)
module = tmp_path / "form_module.bsl"
module.write_text("Процедура ПровестиКоманда()\nКонецПроцедуры\n", encoding="utf-8")
snapshot = index_project(tmp_path, project_id="child-element-metadata")
document = next(node for node in snapshot.nodes if node.kind == NodeKind.DOCUMENT)
role = next(node for node in snapshot.nodes if node.kind == NodeKind.ROLE)
command = next(node for node in snapshot.nodes if node.kind == NodeKind.COMMAND)
assert any(
edge.kind == EdgeKind.GRANTS_ACCESS
and edge.source_lineage == role.lineage_id
and edge.target_lineage == document.lineage_id
for edge in snapshot.edges
)
assert any(
edge.kind == EdgeKind.HANDLES
and edge.source_lineage == command.lineage_id
and edge.attributes["handler_name"] == "ПровестиКоманда"
for edge in snapshot.edges
)
def test_index_project_links_form_command_to_handler(tmp_path: Path):
xml = tmp_path / "form.xml"
xml.write_text(
"""
<Form name="ФормаДокумента" qualifiedName="Документ.Заказ.ФормаДокумента">
<Command name="Провести" qualifiedName="Документ.Заказ.ФормаДокумента.Провести" action="ПровестиКоманда" />
</Form>
""",
encoding="utf-8",
)
module = tmp_path / "form_module.bsl"
module.write_text("Процедура ПровестиКоманда()\nКонецПроцедуры\n", encoding="utf-8")
snapshot = index_project(tmp_path, project_id="ui-handlers")
assert any(edge.kind == EdgeKind.HANDLES for edge in snapshot.edges)
def test_index_project_extracts_managed_form_items_without_layouts(tmp_path: Path):
xml = tmp_path / "form.xml"
xml.write_text(
"""
<Form name="ФормаДокумента" qualifiedName="Документ.Заказ.ФормаДокумента">
<items xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="form:FormGroup" name="Основное">
<caption>Основное</caption>
<items xsi:type="form:FormField" name="Номер">
<caption>Номер</caption>
<dataPath>Объект.Номер</dataPath>
</items>
</items>
<Layout name="ПечатнаяФорма" qualifiedName="Документ.Заказ.ФормаДокумента.ПечатнаяФорма" />
</Form>
""",
encoding="utf-8",
)
snapshot = index_project(tmp_path, project_id="ui-form-items")
form = form_semantics(snapshot)[0]
assert [element.name for element in form.elements] == ["Основное", "Номер"]
assert all(element.kind == NodeKind.FORM_ELEMENT for element in form.elements)
assert form.elements[1].attributes["dataPath"] == "Объект.Номер"
assert not any(element.name == "ПечатнаяФорма" for element in form.elements)
def test_index_project_extracts_edt_form_items_from_form_file_path(tmp_path: Path):
form_dir = tmp_path / "src" / "Catalogs" / "ВидыЗаказовПокупателей" / "Forms" / "ФормаСписка"
form_dir.mkdir(parents=True)
(form_dir / "Form.form").write_text(
"""
<form:Form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:form="http://g5.1c.ru/v8/dt/form">
<items xsi:type="form:Table">
<name>Список</name>
<dataPath xsi:type="form:DataPath">
<segments>Список</segments>
</dataPath>
<items xsi:type="form:FormField">
<name>Наименование</name>
<dataPath xsi:type="form:DataPath">
<segments>Список.Description</segments>
</dataPath>
</items>
</items>
</form:Form>
""",
encoding="utf-8",
)
snapshot = index_project(tmp_path, project_id="edt-form-items")
form = next(item for item in form_semantics(snapshot) if item.form.name == "ФормаСписка")
assert form.form.qualified_name == "Справочник.ВидыЗаказовПокупателей.ФормаСписка"
assert [element.name for element in form.elements] == ["Список", "Наименование"]
assert form.elements[0].attributes["control_kind"] == "Table"
assert form.elements[1].attributes["dataPath"] == "Список.Description"
def test_index_project_links_form_events_to_handlers(tmp_path: Path):
xml = tmp_path / "form.xml"
xml.write_text(
"""
<Form
name="ФормаДокумента"
qualifiedName="Документ.Заказ.ФормаДокумента"
onCreate="ПриСозданииНаСервере"
beforeWrite="ПередЗаписью"
/>
""",
encoding="utf-8",
)
module = tmp_path / "form_module.bsl"
module.write_text(
"""
Процедура ПриСозданииНаСервере()
КонецПроцедуры
Процедура ПередЗаписью()
КонецПроцедуры
""",
encoding="utf-8",
)
snapshot = index_project(tmp_path, project_id="ui-form-events")
form = next(node for node in snapshot.nodes if node.kind == NodeKind.FORM)
handlers = {
edge.attributes["handler_name"]: edge
for edge in snapshot.edges
if edge.kind == EdgeKind.HANDLES and edge.source_lineage == form.lineage_id
}
assert set(handlers) == {"ПриСозданииНаСервере", "ПередЗаписью"}
assert {edge.attributes["link_type"] for edge in handlers.values()} == {"FORM_EVENT"}