Files
SystemSimulationApp/tests/test_component_catalog.py
T

706 lines
29 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
import json
from pathlib import Path
import unittest
from app.main import get_component_catalog
from app.simulation.registry import (
COMPONENT_MODEL_REGISTRY,
validate_component_registries,
)
from app.simulation.core.metadata import ParameterDefinition
class ComponentCatalogTests(unittest.TestCase):
def setUp(self) -> None:
self.catalog = get_component_catalog()
self.libraries = {
library["id"]: library for library in self.catalog["libraries"]
}
self.library = self.libraries["experimental"]
self.components = {
component["modelType"]: component
for library in self.catalog["libraries"]
for component in library["components"]
}
def test_experimental_library_is_exposed_as_temporary(self) -> None:
self.assertEqual(self.catalog["schemaVersion"], 1)
self.assertEqual(self.library["id"], "experimental")
self.assertEqual(self.library["label"], "临时测试组件库")
self.assertEqual(self.library["version"], "0.1.0")
self.assertTrue(self.library["temporary"])
self.assertEqual(
self.library["sourcePackage"],
"app.simulation.components.experimental",
)
self.assertEqual(
[category["id"] for category in self.library["categories"]],
["storage", "flow", "junctions"],
)
def test_amesim_library_exposes_pneumatic_models(self) -> None:
library = self.libraries["amesim"]
components = {
component["modelType"]: component for component in library["components"]
}
self.assertEqual(library["label"], "AMESim 组件库")
self.assertEqual([category["id"] for category in library["categories"]], ["media", "storage", "flow", "junctions", "boundary", "signals", "mechanical"])
self.assertEqual(set(components), {"amesim_ideal_air_medium", "amesim_helium_medium", "amesim_pnpl01", "amesim_step0", "amesim_ud00", "amesim_f000", "amesim_forc", "amesim_mecmas21", "amesim_lstp00a", "amesim_lmechn1", "amesim_pnrp17", "amesim_pnch023", "amesim_pnch012", "amesim_pnor001", "amesim_pnvo001_fixed", "amesim_pnvo001", "amesim_pnl00r", "amesim_pnl0001", "amesim_pnl0002", "amesim_pnl0003", "amesim_pn3node2", "amesim_p4node2"})
for model_type, component in components.items():
with self.subTest(model_type=model_type):
self.assertEqual(component["symbol"], model_type)
self.assertEqual(
components["amesim_ideal_air_medium"]["role"],
"amesimGasMediumDefinition",
)
self.assertEqual(
components["amesim_ideal_air_medium"]["category"]["id"],
"media",
)
self.assertEqual(
components["amesim_ideal_air_medium"]["label"],
"空气介质定义",
)
self.assertEqual(
components["amesim_ideal_air_medium"]["modelVersion"],
"0.2.0",
)
medium_parameters = {
parameter["name"]: parameter
for parameter in components["amesim_ideal_air_medium"]["parameters"]
}
self.assertEqual(
list(medium_parameters),
["gi", "property_model"],
)
self.assertEqual(
medium_parameters["property_model"]["editor"],
"amesimGasPropertyModel",
)
self.assertEqual(
medium_parameters["property_model"]["options"],
[{"value": 0, "label": "理想气体"}],
)
helium_medium = components["amesim_helium_medium"]
self.assertEqual(helium_medium["role"], "amesimGasMediumDefinition")
self.assertEqual(helium_medium["category"]["id"], "media")
self.assertEqual(helium_medium["label"], "氦气介质定义")
self.assertEqual(helium_medium["modelVersion"], "0.1.0")
helium_parameters = {
parameter["name"]: parameter
for parameter in helium_medium["parameters"]
}
self.assertEqual(list(helium_parameters), ["gi", "property_model"])
self.assertEqual(
helium_parameters["property_model"]["editor"],
"amesimGasPropertyModel",
)
self.assertEqual(
helium_parameters["property_model"]["options"],
[{"value": 0, "label": "Peng–Robinson"}],
)
self.assertEqual(
[port["name"] for port in components["amesim_p4node2"]["ports"]],
["port_1", "port_2", "port_3", "port_4"],
)
self.assertEqual(components["amesim_pnpl01"]["category"]["id"], "boundary")
self.assertEqual([port["name"] for port in components["amesim_pnpl01"]["ports"]], ["port_1"])
pnch_parameters = {
parameter["name"]: parameter
for parameter in components["amesim_pnch023"]["parameters"]
}
self.assertEqual(components["amesim_pnch023"]["category"]["id"], "storage")
self.assertEqual(pnch_parameters["cvol"]["quantity"], "volume")
self.assertEqual(pnch_parameters["kth"]["quantity"], "heat_transfer_coefficient")
pnch012_parameters = {
parameter["name"]: parameter
for parameter in components["amesim_pnch012"]["parameters"]
}
self.assertEqual(components["amesim_pnch012"]["category"]["id"], "storage")
self.assertEqual([port["name"] for port in components["amesim_pnch012"]["ports"]], ["port_1", "port_2", "port_3", "port_4"])
self.assertEqual(pnch012_parameters["cvol0"]["quantity"], "volume")
self.assertEqual(pnch012_parameters["dvol1"]["unit"], "m3/s")
pnor_parameters = {
parameter["name"]: parameter
for parameter in components["amesim_pnor001"]["parameters"]
}
self.assertEqual(components["amesim_pnor001"]["category"]["id"], "flow")
self.assertEqual(pnor_parameters["area"]["quantity"], "area")
self.assertEqual(pnor_parameters["area"]["unit"], "m2")
pnvo_parameters = {
parameter["name"]: parameter
for parameter in components["amesim_pnvo001_fixed"]["parameters"]
}
self.assertEqual(components["amesim_pnvo001_fixed"]["category"]["id"], "flow")
self.assertEqual(pnvo_parameters["opening"]["maximum"], 1.0)
self.assertEqual([port["name"] for port in components["amesim_pnvo001_fixed"]["ports"]], ["port_2", "port_3"])
self.assertEqual(components["amesim_step0"]["category"]["id"], "signals")
self.assertEqual([port["name"] for port in components["amesim_step0"]["ports"]], ["out"])
ud00_parameters = {
parameter["name"]: parameter
for parameter in components["amesim_ud00"]["parameters"]
}
self.assertEqual(components["amesim_ud00"]["category"]["id"], "signals")
self.assertEqual([port["name"] for port in components["amesim_ud00"]["ports"]], ["out"])
self.assertEqual(ud00_parameters["tstart"]["quantity"], "time")
self.assertEqual(ud00_parameters["nstages"]["maximum"], 8.0)
self.assertEqual(components["amesim_f000"]["category"]["id"], "mechanical")
self.assertEqual([port["name"] for port in components["amesim_f000"]["ports"]], ["port_1"])
self.assertEqual(components["amesim_f000"]["ports"][0]["domain"], "mechanical")
self.assertEqual(components["amesim_forc"]["category"]["id"], "mechanical")
self.assertEqual([port["name"] for port in components["amesim_forc"]["ports"]], ["res", "port_2"])
self.assertEqual(components["amesim_forc"]["ports"][1]["domain"], "mechanical")
mecmas_parameters = {
parameter["name"]: parameter
for parameter in components["amesim_mecmas21"]["parameters"]
}
self.assertEqual(components["amesim_mecmas21"]["category"]["id"], "mechanical")
self.assertEqual([port["name"] for port in components["amesim_mecmas21"]["ports"]], ["port_2", "port_1"])
self.assertEqual(components["amesim_mecmas21"]["modelVersion"], "0.2.0")
self.assertEqual(
components["amesim_mecmas21"]["parameterGroups"],
[
{
"id": "friction",
"label": "摩擦",
"parameters": [
"frictionType",
"strib",
"astrib",
"fstick",
"fcoul",
"rvisc",
"wind",
"dvel",
],
"order": 10,
"defaultExpanded": False,
},
{
"id": "endstops",
"label": "限位",
"parameters": [
"discContactOption",
"xmax",
"Kbmax",
"Dbmax",
"Pdmax",
"xmin",
"Kbmin",
"Dbmin",
"Pdmin",
"restcoeff",
"restdvel",
],
"order": 20,
"defaultExpanded": False,
},
],
)
self.assertNotIn("parameterGroups", components["amesim_f000"])
self.assertEqual(mecmas_parameters["mass"]["unit"], "kg")
self.assertEqual(mecmas_parameters["Kbmin"]["unit"], "N/m")
self.assertEqual(mecmas_parameters["theta"]["label"], "倾角(度)")
self.assertIn("+90°", mecmas_parameters["theta"]["description"])
self.assertIn("-90°", mecmas_parameters["theta"]["description"])
self.assertEqual(
{
name: (
mecmas_parameters[name]["editor"],
[
(option["value"], option["label"])
for option in mecmas_parameters[name]["options"]
],
)
for name in (
"useFriction",
"stoptype",
"discContactOption",
"strib",
"frictionType",
)
},
{
"useFriction": ("choice", [(1.0, "否"), (2.0, "是")]),
"stoptype": (
"choice",
[
(1.0, "理想限位"),
(2.0, "弹性限位"),
(3.0, "恢复碰撞"),
(4.0, "无限位"),
],
),
"discContactOption": (
"choice",
[
(1.0, "允许负接触力"),
(2.0, "不允许负接触力"),
],
),
"strib": ("choice", [(1.0, "否"), (2.0, "是")]),
"frictionType": (
"choice",
[(1.0, "简单"), (2.0, "高级")],
),
},
)
self.assertEqual(
{
name: mecmas_parameters[name]["visibleWhen"]
for name in (
"fstick",
"fcoul",
"rvisc",
"wind",
"dvel",
"restdvel",
"restcoeff",
"astrib",
"xmin",
"Kbmin",
"Dbmin",
"Pdmin",
"xmax",
"Kbmax",
"Dbmax",
"Pdmax",
"discContactOption",
"strib",
"frictionType",
)
},
{
"fstick": [
{"parameter": "stoptype", "values": [1.0, 2.0, 4.0]},
{"parameter": "useFriction", "values": [2.0]},
],
"fcoul": [
{"parameter": "stoptype", "values": [1.0, 2.0, 4.0]},
{"parameter": "useFriction", "values": [2.0]},
],
"rvisc": [
{"parameter": "useFriction", "values": [2.0]},
],
"wind": [
{"parameter": "useFriction", "values": [2.0]},
],
"dvel": [
{"parameter": "stoptype", "values": [1.0, 2.0, 4.0]},
{"parameter": "useFriction", "values": [2.0]},
{"parameter": "frictionType", "values": [2.0]},
],
"restdvel": [
{"parameter": "stoptype", "values": [3.0]},
],
"restcoeff": [
{"parameter": "stoptype", "values": [3.0]},
],
"astrib": [
{"parameter": "stoptype", "values": [1.0, 2.0, 4.0]},
{"parameter": "useFriction", "values": [2.0]},
{"parameter": "frictionType", "values": [2.0]},
{"parameter": "strib", "values": [2.0]},
],
"xmin": [
{"parameter": "stoptype", "values": [1.0, 2.0, 3.0]},
],
"Kbmin": [
{"parameter": "stoptype", "values": [2.0]},
],
"Dbmin": [
{"parameter": "stoptype", "values": [2.0]},
],
"Pdmin": [
{"parameter": "stoptype", "values": [2.0]},
],
"xmax": [
{"parameter": "stoptype", "values": [1.0, 2.0, 3.0]},
],
"Kbmax": [
{"parameter": "stoptype", "values": [2.0]},
],
"Dbmax": [
{"parameter": "stoptype", "values": [2.0]},
],
"Pdmax": [
{"parameter": "stoptype", "values": [2.0]},
],
"discContactOption": [
{"parameter": "stoptype", "values": [2.0]},
],
"strib": [
{"parameter": "stoptype", "values": [1.0, 2.0, 4.0]},
{"parameter": "useFriction", "values": [2.0]},
{"parameter": "frictionType", "values": [2.0]},
],
"frictionType": [
{"parameter": "stoptype", "values": [1.0, 2.0, 4.0]},
{"parameter": "useFriction", "values": [2.0]},
],
},
)
for name in ("mass", "theta", "useFriction", "stoptype", "v0", "x0"):
with self.subTest(always_visible=name):
self.assertNotIn("visibleWhen", mecmas_parameters[name])
lstp_parameters = {
parameter["name"]: parameter
for parameter in components["amesim_lstp00a"]["parameters"]
}
self.assertEqual(components["amesim_lstp00a"]["category"]["id"], "mechanical")
self.assertEqual([port["name"] for port in components["amesim_lstp00a"]["ports"]], ["port_1", "port_2"])
self.assertEqual(lstp_parameters["gap0"]["unit"], "m")
self.assertEqual(lstp_parameters["kcont"]["unit"], "N/m")
lmechn_parameters = {
parameter["name"]: parameter
for parameter in components["amesim_lmechn1"]["parameters"]
}
self.assertEqual(components["amesim_lmechn1"]["category"]["id"], "mechanical")
self.assertEqual([port["name"] for port in components["amesim_lmechn1"]["ports"]], [f"port_{index}" for index in range(1, 10)])
pnrp_parameters = {
parameter["name"]: parameter
for parameter in components["amesim_pnrp17"]["parameters"]
}
self.assertEqual(components["amesim_pnrp17"]["category"]["id"], "mechanical")
self.assertEqual([port["name"] for port in components["amesim_pnrp17"]["ports"]], ["port_1", "port_3", "port_2", "port_4", "port_5"])
self.assertEqual(pnrp_parameters["dp"]["unit"], "m")
self.assertEqual(lmechn_parameters["v1"]["maximum"], 8.0)
self.assertEqual(components["amesim_pnvo001"]["category"]["id"], "flow")
self.assertEqual([port["name"] for port in components["amesim_pnvo001"]["ports"]], ["res", "port_2", "port_3"])
pnl_parameters = {
parameter["name"]: parameter
for parameter in components["amesim_pnl00r"]["parameters"]
}
self.assertEqual(components["amesim_pnl00r"]["category"]["id"], "flow")
self.assertEqual(pnl_parameters["diam"]["quantity"], "length")
self.assertEqual(pnl_parameters["le"]["unit"], "m")
pnl0001_parameters = {
parameter["name"]: parameter
for parameter in components["amesim_pnl0001"]["parameters"]
}
self.assertEqual(components["amesim_pnl0001"]["category"]["id"], "flow")
self.assertEqual(pnl0001_parameters["kth"]["quantity"], "heat_transfer_coefficient")
self.assertEqual(pnl0001_parameters["p0"]["unit"], "Pa")
self.assertEqual(components["amesim_pnl0002"]["category"]["id"], "flow")
self.assertEqual(components["amesim_pnl0003"]["category"]["id"], "flow")
pnl0003_parameters = {
parameter["name"]: parameter
for parameter in components["amesim_pnl0003"]["parameters"]
}
self.assertEqual(pnl0003_parameters["p1_0"]["unit"], "Pa")
self.assertEqual(pnl0003_parameters["T2_0"]["quantity"], "temperature")
def test_amesim_configurable_parameters_expose_choices_and_groups(self) -> None:
def parameters(model_type: str) -> dict[str, dict[str, object]]:
return {
parameter["name"]: parameter
for parameter in self.components[model_type]["parameters"]
}
flow_options = [
{"value": 1.0, "label": "Cq"},
{"value": 2.0, "label": "Cv"},
{"value": 3.0, "label": "Kv"},
]
for model_type, area_name in (
("amesim_pnor001", "area"),
("amesim_pnvo001_fixed", "area0"),
("amesim_pnvo001", "area0"),
):
with self.subTest(model_type=model_type):
component = self.components[model_type]
model_parameters = parameters(model_type)
self.assertEqual(
component["modelVersion"],
"0.3.0" if model_type == "amesim_pnor001" else "0.2.0",
)
self.assertEqual(model_parameters["flowset"]["editor"], "choice")
self.assertEqual(model_parameters["flowset"]["options"], flow_options)
self.assertEqual(
model_parameters["cq"]["visibleWhen"],
[{"parameter": "flowset", "values": [1.0]}],
)
self.assertEqual(
model_parameters[area_name]["visibleWhen"],
[{"parameter": "flowset", "values": [1.0]}],
)
self.assertEqual(
model_parameters["Cv"]["visibleWhen"],
[{"parameter": "flowset", "values": [2.0]}],
)
self.assertEqual(
model_parameters["Kv"]["visibleWhen"],
[{"parameter": "flowset", "values": [3.0]}],
)
self.assertEqual(
component["parameterGroups"],
[
{
"id": "flow_coefficient",
"label": "流量系数",
"parameters": ["cq", area_name, "Cv", "Kv"],
"order": 10,
"defaultExpanded": False,
}
],
)
thermal_options = [
{"value": 1.0, "label": "多方过程"},
{"value": 2.0, "label": "带换热"},
]
for model_type in ("amesim_pnl0001", "amesim_pnl0002", "amesim_pnl0003"):
with self.subTest(model_type=model_type):
component = self.components[model_type]
model_parameters = parameters(model_type)
expected_version = (
"0.3.0" if model_type == "amesim_pnl0002" else "0.2.0"
)
self.assertEqual(component["modelVersion"], expected_version)
self.assertEqual(model_parameters["mode"]["editor"], "choice")
self.assertEqual(model_parameters["mode"]["options"], thermal_options)
self.assertEqual(
model_parameters["k"]["visibleWhen"],
[{"parameter": "mode", "values": [1.0]}],
)
for name in ("kth", "extemp"):
self.assertEqual(
model_parameters[name]["visibleWhen"],
[{"parameter": "mode", "values": [2.0]}],
)
self.assertEqual(
component["parameterGroups"],
[
{
"id": "thermodynamics",
"label": "热力学",
"parameters": ["k", "kth", "extemp"],
"order": 10,
"defaultExpanded": False,
}
],
)
ud00 = self.components["amesim_ud00"]
ud00_parameters = parameters("amesim_ud00")
self.assertEqual(ud00["modelVersion"], "0.2.0")
self.assertEqual(ud00_parameters["nstages"]["editor"], "choice")
self.assertEqual(
ud00_parameters["nstages"]["options"],
[
{"value": float(stage_count), "label": str(stage_count)}
for stage_count in range(1, 9)
],
)
self.assertEqual(
ud00_parameters["iscyclic"]["options"],
[{"value": 0.0, "label": "否"}, {"value": 1.0, "label": "是"}],
)
self.assertNotIn("visibleWhen", ud00_parameters["start1"])
self.assertEqual(
ud00_parameters["start2"]["visibleWhen"],
[{"parameter": "nstages", "values": [2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]}],
)
self.assertEqual(
ud00_parameters["t8"]["visibleWhen"],
[{"parameter": "nstages", "values": [8.0]}],
)
self.assertEqual(
ud00["parameterGroups"][0]["parameters"],
[
name
for stage_index in range(1, 9)
for name in (
f"start{stage_index}",
f"end{stage_index}",
f"t{stage_index}",
)
],
)
lstp = self.components["amesim_lstp00a"]
lstp_parameters = parameters("amesim_lstp00a")
self.assertEqual(lstp["modelVersion"], "0.2.0")
self.assertEqual(
lstp_parameters["stiffmode"]["options"],
[
{"value": 1.0, "label": "数值刚度"},
{"value": 2.0, "label": "几何参数"},
],
)
self.assertEqual(
lstp_parameters["discContactOption"]["options"],
[
{"value": 1.0, "label": "允许负接触力"},
{"value": 2.0, "label": "不允许负接触力"},
],
)
self.assertEqual(
lstp_parameters["kcont"]["visibleWhen"],
[{"parameter": "stiffmode", "values": [1.0]}],
)
for name in ("na", "G", "sdiam", "wdiam"):
self.assertEqual(
lstp_parameters[name]["visibleWhen"],
[{"parameter": "stiffmode", "values": [2.0]}],
)
self.assertEqual(
lstp["parameterGroups"],
[
{
"id": "stiffness",
"label": "刚度",
"parameters": ["kcont", "G", "sdiam", "wdiam", "na"],
"order": 10,
"defaultExpanded": False,
},
{
"id": "contact",
"label": "接触",
"parameters": ["gap0", "rcont", "Pdis"],
"order": 20,
"defaultExpanded": False,
},
],
)
def test_every_registered_component_is_in_the_catalog(self) -> None:
self.assertEqual(
set(self.components),
set(COMPONENT_MODEL_REGISTRY),
)
def test_catalog_reuses_model_parameter_contracts(self) -> None:
cylinder = self.components["cylinder"]
parameters = {
parameter["name"]: parameter for parameter in cylinder["parameters"]
}
self.assertEqual(cylinder["label"], "气瓶")
self.assertEqual(cylinder["modelVersion"], "1.0.0")
self.assertEqual(parameters["volume"]["default"], 0.01)
self.assertEqual(parameters["volume"]["quantity"], "volume")
self.assertEqual(parameters["p0"]["unit"], "Pa")
self.assertTrue(parameters["p0"]["minimumExclusive"])
def test_parameter_description_is_optional_interface_metadata(self) -> None:
described = ParameterDefinition(
"diam",
0.01,
label="管径",
quantity="length",
unit="m",
minimum=0.0,
minimum_exclusive=True,
description="用于计算流通面积。",
).as_interface_dict()
undescribed = ParameterDefinition(
"gain",
1.0,
label="增益",
).as_interface_dict()
self.assertEqual(described["description"], "用于计算流通面积。")
self.assertEqual(described["default"], 0.01)
self.assertEqual(described["unit"], "m")
self.assertEqual(described["minimum"], 0.0)
self.assertTrue(described["minimumExclusive"])
self.assertNotIn("description", undescribed)
def test_catalog_exposes_descriptions_for_core_amesim_pneumatic_parameters(
self,
) -> None:
medium_parameters = {
parameter["name"]: parameter
for parameter in self.components["amesim_helium_medium"]["parameters"]
}
chamber_parameters = {
parameter["name"]: parameter
for parameter in self.components["amesim_pnch023"]["parameters"]
}
pipe_parameters = {
parameter["name"]: parameter
for parameter in self.components["amesim_pnl00r"]["parameters"]
}
orifice_parameters = {
parameter["name"]: parameter
for parameter in self.components["amesim_pnor001"]["parameters"]
}
self.assertIn("自动分配", medium_parameters["gi"]["description"])
self.assertIn(
"Peng–Robinson",
medium_parameters["property_model"]["description"],
)
self.assertIn("固定有效容积", chamber_parameters["cvol"]["description"])
self.assertIn("Darcy", pipe_parameters["rr"]["description"])
self.assertIn("有效孔口面积", orifice_parameters["area"]["description"])
self.assertEqual(chamber_parameters["cvol"]["default"], 0.057)
self.assertEqual(pipe_parameters["diam"]["unit"], "m")
def test_catalog_adds_port_layout_without_changing_physical_contract(self) -> None:
tee_ports = {
port["name"]: port for port in self.components["tee"]["ports"]
}
self.assertEqual(tee_ports["port_in"]["side"], "left")
self.assertEqual(tee_ports["port_out1"]["side"], "right")
self.assertEqual(tee_ports["port_out2"]["side"], "right")
self.assertEqual(tee_ports["port_in"]["kind"], "physical")
self.assertEqual(tee_ports["port_in"]["domain"], "pneumatic")
self.assertEqual(tee_ports["port_in"]["nominalRole"], "bidirectional")
def test_registry_can_repeat_discovery_and_validation(self) -> None:
validate_component_registries()
def test_catalog_schema_is_committed_and_targets_version_one(self) -> None:
schema_path = (
Path(__file__).resolve().parent.parent
/ "schemas"
/ "component-catalog-v1.schema.json"
)
schema = json.loads(schema_path.read_text(encoding="utf-8"))
self.assertEqual(schema["properties"]["schemaVersion"]["const"], 1)
self.assertIn("modelVersion", schema["$defs"]["component"]["required"])
self.assertIn("version", schema["$defs"]["library"]["required"])
parameter_schema = schema["$defs"]["parameter"]
parameter_group_schema = schema["$defs"]["parameterGroup"]
self.assertEqual(
parameter_schema["properties"]["description"],
{"type": "string", "minLength": 1},
)
self.assertNotIn("description", parameter_schema["required"])
self.assertFalse(parameter_schema["additionalProperties"])
self.assertNotIn("unknownField", parameter_schema["properties"])
self.assertEqual(
parameter_group_schema["required"],
["id", "label", "parameters", "order", "defaultExpanded"],
)
self.assertTrue(
parameter_group_schema["properties"]["parameters"]["uniqueItems"]
)
self.assertIn(
"parameterGroups",
schema["$defs"]["component"]["properties"],
)
self.assertNotIn(
"parameterGroups",
schema["$defs"]["component"]["required"],
)
if __name__ == "__main__":
unittest.main()