55import pytest
66from nptdms import TdmsFile , types # type: ignore
77from pytest_mock import MockFixture
8+ from sift .metadata .v1 .metadata_pb2 import MetadataKeyType
89
910from sift_py .data_import .tdms import TdmsTimeFormat , TdmsUploadService , sanitize_string
1011from sift_py .rest import SiftRestConfig
@@ -40,7 +41,14 @@ def channels(self) -> List[MockTdmsChannel]:
4041class MockTdmsFile :
4142 def __init__ (self , groups : List [MockTdmsGroup ]):
4243 self ._groups : List [MockTdmsGroup ] = groups
43- self .properties : Dict [str , str ] = {}
44+ # Example properties for each type
45+ self .properties : Dict [str , Any ] = {
46+ "string_prop" : "example" ,
47+ "int_prop" : 42 ,
48+ "float_prop" : 3.14 ,
49+ "bool_prop" : True ,
50+ "datetime_prop" : pd .Timestamp ("2024-01-01T12:00:00" ),
51+ }
4452
4553 def groups (self ) -> List [MockTdmsGroup ]:
4654 return self ._groups
@@ -50,9 +58,9 @@ def as_dataframe(self, *_, **__):
5058
5159
5260class MockResponse :
53- def __init__ (self ):
54- self .status_code = 200
55- self .text = json .dumps ({"uploadUrl" : "some_url.com" , "dataImportId" : "123-123-123" })
61+ def __init__ (self , status_code = None , text = None ):
62+ self .status_code = status_code or 200
63+ self .text = text or json .dumps ({"uploadUrl" : "some_url.com" , "dataImportId" : "123-123-123" })
5664
5765 def json (self ) -> dict :
5866 return json .loads (self .text )
@@ -730,3 +738,110 @@ def mock_tdms_file_constructor2(path):
730738 tdms_time_format = TdmsTimeFormat .TIME_CHANNEL ,
731739 ignore_errors = True ,
732740 )
741+
742+
743+ def test_tdms_upload_service_upload_with_metadata (
744+ mocker : MockFixture , mock_waveform_tdms_file : MockTdmsFile
745+ ):
746+ mock_path_is_file = mocker .patch ("sift_py.data_import.tdms.Path.is_file" )
747+ mock_path_is_file .return_value = True
748+
749+ mock_path_getsize = mocker .patch ("sift_py.data_import.csv.os.path.getsize" )
750+ mock_path_getsize .return_value = 10
751+
752+ # Patch TdmsFile to return our mock file
753+ mocker .patch ("sift_py.data_import.tdms.TdmsFile" , return_value = mock_waveform_tdms_file )
754+
755+ # Patch requests.Session.post to simulate both run creation and data import
756+ mock_requests_post = mocker .patch ("sift_py.rest.requests.Session.post" )
757+
758+ # The first call is for _create_run, second for config upload, third for file upload
759+ def post_side_effect (* args , ** kwargs ):
760+ url = kwargs .get ("url" ) or (args [1 ] if len (args ) > 1 else "" )
761+ if "run" in url :
762+ # Simulate run creation response
763+ return MockResponse (
764+ status_code = 200 ,
765+ text = json .dumps ({"run" : {"runId" : "new_run_id" }}),
766+ )
767+ elif "data-imports:upload" in url :
768+ # Simulate config upload response
769+ return MockResponse ()
770+ elif "some_url.com" in url :
771+ # Simulate file upload response
772+ return MockResponse ()
773+ else :
774+ return MockResponse ()
775+
776+ mock_requests_post .side_effect = post_side_effect
777+
778+ svc = TdmsUploadService (rest_config )
779+
780+ # Should raise if run_id is provided
781+ with pytest .raises (ValueError , match = "Metadata can only be included in new runs" ):
782+ svc .upload (
783+ "some_tdms.tdms" ,
784+ "asset_name" ,
785+ include_metadata = True ,
786+ run_id = "existing_run_id" ,
787+ run_name = "Run Name" ,
788+ )
789+
790+ # Should raise if run_name is not provided
791+ with pytest .raises (ValueError , match = "Must provide a run_name to include metadata" ):
792+ svc .upload (
793+ "some_tdms.tdms" ,
794+ "asset_name" ,
795+ include_metadata = True ,
796+ run_name = None ,
797+ )
798+
799+ # Should succeed and call _create_run via POST with metadata
800+ svc .upload (
801+ "some_tdms.tdms" ,
802+ "asset_name" ,
803+ include_metadata = True ,
804+ run_name = "Run Name" ,
805+ )
806+
807+ # Check that the first POST call was for run creation and included metadata
808+ create_run_post_call = mock_requests_post .call_args_list [0 ]
809+ create_run_post_data = json .loads (create_run_post_call .kwargs ["data" ])
810+ assert create_run_post_data ["name" ] == "Run Name"
811+
812+ # Metadata should be present and contain expected keys
813+ assert "metadata" in create_run_post_data
814+ assert create_run_post_data ["metadata" ][0 ]["key" ]["name" ] == "string_prop"
815+ assert (
816+ create_run_post_data ["metadata" ][0 ]["key" ]["type" ]
817+ == MetadataKeyType .METADATA_KEY_TYPE_STRING
818+ )
819+ assert create_run_post_data ["metadata" ][0 ]["string_value" ] == "example"
820+
821+ assert create_run_post_data ["metadata" ][1 ]["key" ]["name" ] == "int_prop"
822+ assert (
823+ create_run_post_data ["metadata" ][1 ]["key" ]["type" ]
824+ == MetadataKeyType .METADATA_KEY_TYPE_NUMBER
825+ )
826+ assert create_run_post_data ["metadata" ][1 ]["number_value" ] == 42
827+
828+ assert create_run_post_data ["metadata" ][2 ]["key" ]["name" ] == "float_prop"
829+ assert (
830+ create_run_post_data ["metadata" ][2 ]["key" ]["type" ]
831+ == MetadataKeyType .METADATA_KEY_TYPE_NUMBER
832+ )
833+ assert create_run_post_data ["metadata" ][2 ]["number_value" ] == 3.14
834+
835+ assert create_run_post_data ["metadata" ][3 ]["key" ]["name" ] == "bool_prop"
836+ assert (
837+ create_run_post_data ["metadata" ][3 ]["key" ]["type" ]
838+ == MetadataKeyType .METADATA_KEY_TYPE_BOOLEAN
839+ )
840+ assert create_run_post_data ["metadata" ][3 ]["boolean_value" ] is True
841+
842+ assert create_run_post_data ["metadata" ][4 ]["key" ]["name" ] == "datetime_prop"
843+ assert (
844+ create_run_post_data ["metadata" ][4 ]["key" ]["type" ]
845+ == MetadataKeyType .METADATA_KEY_TYPE_STRING
846+ )
847+ assert create_run_post_data ["metadata" ][4 ]["string_value" ].startswith ("2024-01-01T12:00:00" )
0 commit comments