|
13 | 13 |
|
14 | 14 | from datacustomcode.deploy import ( |
15 | 15 | DloPermission, |
| 16 | + DmoPermission, |
16 | 17 | Permissions, |
17 | 18 | get_config, |
18 | 19 | ) |
@@ -934,6 +935,56 @@ def test_verify_data_transform_config_missing_fields(self, mock_file, mock_exist |
934 | 935 | ): |
935 | 936 | get_config("/test/dir/payload") |
936 | 937 |
|
| 938 | + @patch( |
| 939 | + "builtins.open", |
| 940 | + new_callable=mock_open, |
| 941 | + read_data=( |
| 942 | + '{"sdkVersion": "1.0.0", "entryPoint": "entrypoint.py", ' |
| 943 | + '"dataspace": "test_dataspace", ' |
| 944 | + '"permissions": {"read": {"dmo": ["input_dmo__dlm"]}, ' |
| 945 | + '"write": {"dmo": ["output_dmo__dlm"]}}}' |
| 946 | + ), |
| 947 | + ) |
| 948 | + def test_get_config_dmo_permissions(self, mock_file): |
| 949 | + """DMO-only config.json parses into DmoPermission on both sides.""" |
| 950 | + result = get_config("/test/dir") |
| 951 | + assert isinstance(result, DataTransformConfig) |
| 952 | + assert isinstance(result.permissions.read, DmoPermission) |
| 953 | + assert isinstance(result.permissions.write, DmoPermission) |
| 954 | + assert result.permissions.read.dmo == ["input_dmo__dlm"] |
| 955 | + assert result.permissions.write.dmo == ["output_dmo__dlm"] |
| 956 | + |
| 957 | + @patch( |
| 958 | + "builtins.open", |
| 959 | + new_callable=mock_open, |
| 960 | + read_data=( |
| 961 | + '{"sdkVersion": "1.0.0", "entryPoint": "entrypoint.py", ' |
| 962 | + '"dataspace": "test_dataspace", ' |
| 963 | + '"permissions": {"read": {"dlo": ["input_dlo"]}, ' |
| 964 | + '"write": {"dmo": ["output_dmo__dlm"]}}}' |
| 965 | + ), |
| 966 | + ) |
| 967 | + def test_get_config_mixed_dlo_dmo_raises(self, mock_file): |
| 968 | + """A config that mixes DLO read with DMO write is rejected.""" |
| 969 | + with pytest.raises(ValueError) as excinfo: |
| 970 | + get_config("/test/dir") |
| 971 | + msg = str(excinfo.value) |
| 972 | + assert "read" in msg and "write" in msg |
| 973 | + |
| 974 | + @patch( |
| 975 | + "builtins.open", |
| 976 | + new_callable=mock_open, |
| 977 | + read_data=( |
| 978 | + '{"sdkVersion": "1.0.0", "entryPoint": "entrypoint.py", ' |
| 979 | + '"dataspace": "test_dataspace", ' |
| 980 | + '"permissions": {"read": {}, "write": {"dlo": ["output_dlo"]}}}' |
| 981 | + ), |
| 982 | + ) |
| 983 | + def test_get_config_empty_permission_raises(self, mock_file): |
| 984 | + """A permission block with neither dlo nor dmo is rejected.""" |
| 985 | + with pytest.raises(ValueError): |
| 986 | + get_config("/test/dir") |
| 987 | + |
937 | 988 |
|
938 | 989 | class TestCreateDataTransform: |
939 | 990 | @patch("datacustomcode.deploy.get_config") |
@@ -972,18 +1023,108 @@ def test_create_data_transform(self, mock_make_api_call, mock_get_config): |
972 | 1023 | request_body = mock_make_api_call.call_args[1]["json"] |
973 | 1024 | assert request_body["definition"]["type"] == "DCSQL" |
974 | 1025 | assert request_body["dataSpaceName"] == "test_dataspace" |
975 | | - assert "nodes" in request_body["definition"]["manifest"] |
976 | | - assert "sources" in request_body["definition"]["manifest"] |
977 | | - assert "macros" in request_body["definition"]["manifest"] |
978 | | - assert ( |
979 | | - request_body["definition"]["manifest"]["macros"]["macro.byoc"]["arguments"][ |
980 | | - 0 |
981 | | - ]["name"] |
982 | | - == "test_job" |
983 | | - ) |
| 1026 | + manifest = request_body["definition"]["manifest"] |
| 1027 | + assert manifest["nodes"] == { |
| 1028 | + "node1": { |
| 1029 | + "relation_name": "output_dlo", |
| 1030 | + "config": {"materialized": "table"}, |
| 1031 | + "compiled_code": "", |
| 1032 | + } |
| 1033 | + } |
| 1034 | + assert manifest["sources"] == {"source1": {"relation_name": "input_dlo"}} |
| 1035 | + assert manifest["macros"]["macro.byoc"]["arguments"][0]["name"] == "test_job" |
984 | 1036 |
|
985 | 1037 | assert result == {"id": "transform_id"} |
986 | 1038 |
|
| 1039 | + @patch("datacustomcode.deploy.get_config") |
| 1040 | + @patch("datacustomcode.deploy._make_api_call") |
| 1041 | + def test_create_data_transform_dmo(self, mock_make_api_call, mock_get_config): |
| 1042 | + """DMO permissions emit nodes/sources with DMO relation names.""" |
| 1043 | + access_token = AccessTokenResponse( |
| 1044 | + access_token="test_token", instance_url="https://instance.example.com" |
| 1045 | + ) |
| 1046 | + metadata = CodeExtensionMetadata( |
| 1047 | + name="dmo_job", |
| 1048 | + version="1.0.0", |
| 1049 | + description="DMO job", |
| 1050 | + computeType="CPU_M", |
| 1051 | + codeType="script", |
| 1052 | + ) |
| 1053 | + |
| 1054 | + data_transform_config = DataTransformConfig( |
| 1055 | + sdkVersion="1.0.0", |
| 1056 | + entryPoint="entrypoint.py", |
| 1057 | + dataspace="test_dataspace", |
| 1058 | + permissions=Permissions( |
| 1059 | + read=DmoPermission(dmo=["input_dmo__dlm"]), |
| 1060 | + write=DmoPermission(dmo=["output_dmo__dlm"]), |
| 1061 | + ), |
| 1062 | + ) |
| 1063 | + mock_make_api_call.return_value = {"id": "transform_id"} |
| 1064 | + |
| 1065 | + create_data_transform( |
| 1066 | + "/test/dir", access_token, metadata, data_transform_config |
| 1067 | + ) |
| 1068 | + |
| 1069 | + request_body = mock_make_api_call.call_args[1]["json"] |
| 1070 | + manifest = request_body["definition"]["manifest"] |
| 1071 | + assert manifest["nodes"] == { |
| 1072 | + "node1": { |
| 1073 | + "relation_name": "output_dmo__dlm", |
| 1074 | + "config": {"materialized": "table"}, |
| 1075 | + "compiled_code": "", |
| 1076 | + } |
| 1077 | + } |
| 1078 | + assert manifest["sources"] == {"source1": {"relation_name": "input_dmo__dlm"}} |
| 1079 | + assert manifest["macros"]["macro.byoc"]["arguments"][0]["name"] == "dmo_job" |
| 1080 | + assert request_body["dataSpaceName"] == "test_dataspace" |
| 1081 | + |
| 1082 | + @patch("datacustomcode.deploy.get_config") |
| 1083 | + @patch("datacustomcode.deploy._make_api_call") |
| 1084 | + def test_create_data_transform_multiple_dmos( |
| 1085 | + self, mock_make_api_call, mock_get_config |
| 1086 | + ): |
| 1087 | + """Multiple read DMOs become multiple sources; one write DMO is one node.""" |
| 1088 | + access_token = AccessTokenResponse( |
| 1089 | + access_token="test_token", instance_url="https://instance.example.com" |
| 1090 | + ) |
| 1091 | + metadata = CodeExtensionMetadata( |
| 1092 | + name="dmo_multi", |
| 1093 | + version="1.0.0", |
| 1094 | + description="DMO multi", |
| 1095 | + computeType="CPU_M", |
| 1096 | + codeType="script", |
| 1097 | + ) |
| 1098 | + |
| 1099 | + data_transform_config = DataTransformConfig( |
| 1100 | + sdkVersion="1.0.0", |
| 1101 | + entryPoint="entrypoint.py", |
| 1102 | + dataspace="test_dataspace", |
| 1103 | + permissions=Permissions( |
| 1104 | + read=DmoPermission(dmo=["in1__dlm", "in2__dlm"]), |
| 1105 | + write=DmoPermission(dmo=["out__dlm"]), |
| 1106 | + ), |
| 1107 | + ) |
| 1108 | + mock_make_api_call.return_value = {"id": "transform_id"} |
| 1109 | + |
| 1110 | + create_data_transform( |
| 1111 | + "/test/dir", access_token, metadata, data_transform_config |
| 1112 | + ) |
| 1113 | + |
| 1114 | + request_body = mock_make_api_call.call_args[1]["json"] |
| 1115 | + manifest = request_body["definition"]["manifest"] |
| 1116 | + assert manifest["sources"] == { |
| 1117 | + "source1": {"relation_name": "in1__dlm"}, |
| 1118 | + "source2": {"relation_name": "in2__dlm"}, |
| 1119 | + } |
| 1120 | + assert manifest["nodes"] == { |
| 1121 | + "node1": { |
| 1122 | + "relation_name": "out__dlm", |
| 1123 | + "config": {"materialized": "table"}, |
| 1124 | + "compiled_code": "", |
| 1125 | + } |
| 1126 | + } |
| 1127 | + |
987 | 1128 |
|
988 | 1129 | class TestDeployFull: |
989 | 1130 | @patch("datacustomcode.deploy.get_config") |
|
0 commit comments