1- import { BSON } from 'bsonfy' ;
21import { Struct , Timestamp , type JsonValue } from '@bufbuild/protobuf' ;
32import { createRouterTransport , type Transport } from '@connectrpc/connect' ;
3+ import { BSON } from 'bsonfy' ;
44import { beforeEach , describe , expect , it , vi } from 'vitest' ;
55import { DataService } from '../gen/app/data/v1/data_connect' ;
66import {
@@ -33,6 +33,8 @@ import {
3333 Filter ,
3434 GetDatabaseConnectionRequest ,
3535 GetDatabaseConnectionResponse ,
36+ GetLatestTabularDataRequest ,
37+ GetLatestTabularDataResponse ,
3638 RemoveBinaryDataFromDatasetByIDsRequest ,
3739 RemoveBinaryDataFromDatasetByIDsResponse ,
3840 RemoveBoundingBoxFromImageByIDRequest ,
@@ -50,9 +52,23 @@ import {
5052 TagsByFilterRequest ,
5153 TagsByFilterResponse ,
5254 TagsFilter ,
53- GetLatestTabularDataRequest ,
54- GetLatestTabularDataResponse ,
5555} from '../gen/app/data/v1/data_pb' ;
56+ import { DataPipelinesService } from '../gen/app/datapipelines/v1/data_pipelines_connect' ;
57+ import {
58+ CreateDataPipelineRequest ,
59+ CreateDataPipelineResponse ,
60+ DataPipeline ,
61+ DataPipelineRun ,
62+ DataPipelineRunStatus ,
63+ DeleteDataPipelineRequest ,
64+ DeleteDataPipelineResponse ,
65+ GetDataPipelineRequest ,
66+ GetDataPipelineResponse ,
67+ ListDataPipelineRunsRequest ,
68+ ListDataPipelineRunsResponse ,
69+ ListDataPipelinesRequest ,
70+ ListDataPipelinesResponse ,
71+ } from '../gen/app/datapipelines/v1/data_pipelines_pb' ;
5672import { DatasetService } from '../gen/app/dataset/v1/dataset_connect' ;
5773import {
5874 CreateDatasetRequest ,
@@ -72,34 +88,25 @@ import {
7288 DataCaptureUploadRequest ,
7389 DataCaptureUploadResponse ,
7490 DataType ,
91+ FileData ,
92+ FileUploadRequest ,
93+ FileUploadResponse ,
7594 SensorData ,
7695 SensorMetadata ,
7796 UploadMetadata ,
7897} from '../gen/app/datasync/v1/data_sync_pb' ;
79- import { DataClient , type FilterOptions } from './data-client' ;
8098import {
81- DataPipeline ,
82- ListDataPipelinesRequest ,
83- ListDataPipelinesResponse ,
84- GetDataPipelineRequest ,
85- GetDataPipelineResponse ,
86- CreateDataPipelineRequest ,
87- CreateDataPipelineResponse ,
88- DeleteDataPipelineRequest ,
89- DeleteDataPipelineResponse ,
90- DataPipelineRun ,
91- DataPipelineRunStatus ,
92- ListDataPipelineRunsRequest ,
93- ListDataPipelineRunsResponse ,
94- } from '../gen/app/datapipelines/v1/data_pipelines_pb' ;
95- import { DataPipelinesService } from '../gen/app/datapipelines/v1/data_pipelines_connect' ;
99+ DataClient ,
100+ type FileUploadOptions ,
101+ type FilterOptions ,
102+ } from './data-client' ;
96103vi . mock ( '../gen/app/data/v1/data_pb_service' ) ;
97104
98105let mockTransport : Transport ;
99106const subject = ( ) => new DataClient ( mockTransport ) ;
100107
101108describe ( 'DataClient tests' , ( ) => {
102- const filter = subject ( ) . createFilter ( {
109+ const filter = DataClient . createFilter ( {
103110 componentName : 'testComponentName' ,
104111 componentType : 'testComponentType' ,
105112 } ) ;
@@ -1035,13 +1042,13 @@ describe('DataClient tests', () => {
10351042
10361043 describe ( 'createFilter tests' , ( ) => {
10371044 it ( 'create empty filter' , ( ) => {
1038- const testFilter = subject ( ) . createFilter ( { } ) ;
1045+ const testFilter = DataClient . createFilter ( { } ) ;
10391046 expect ( testFilter ) . toEqual ( new Filter ( ) ) ;
10401047 } ) ;
10411048
10421049 it ( 'create filter' , ( ) => {
10431050 const opts = { componentName : 'camera' } ;
1044- const testFilter = subject ( ) . createFilter ( opts ) ;
1051+ const testFilter = DataClient . createFilter ( opts ) ;
10451052
10461053 const expectedFilter = new Filter ( {
10471054 componentName : 'camera' ,
@@ -1089,7 +1096,7 @@ describe('DataClient tests', () => {
10891096 endTime,
10901097 tags : tagsList ,
10911098 } ;
1092- const testFilter = subject ( ) . createFilter ( opts ) ;
1099+ const testFilter = DataClient . createFilter ( opts ) ;
10931100 expect ( testFilter . componentType ) . toEqual ( 'testComponentType' ) ;
10941101
10951102 const expectedFilter = new Filter ( {
@@ -1716,3 +1723,129 @@ describe('DataPipelineClient tests', () => {
17161723 } ) ;
17171724 } ) ;
17181725} ) ;
1726+
1727+ describe ( 'fileUpload tests' , ( ) => {
1728+ const partId = 'testPartId' ;
1729+ const binaryData = new Uint8Array ( [ 1 , 2 , 3 , 4 , 5 ] ) ;
1730+ const options : FileUploadOptions = {
1731+ componentType : 'componentType' ,
1732+ componentName : 'componentName' ,
1733+ methodName : 'methodName' ,
1734+ fileName : 'fileName' ,
1735+ fileExtension : '.png' ,
1736+ tags : [ 'testTag1' , 'testTag2' ] ,
1737+ datasetIds : [ 'dataset1' , 'dataset2' ] ,
1738+ } ;
1739+
1740+ const expectedFileId = 'testFileId' ;
1741+ const expectedBinaryDataId = 'testBinaryDataId' ;
1742+
1743+ let capturedRequests : FileUploadRequest [ ] ;
1744+
1745+ beforeEach ( ( ) => {
1746+ capturedRequests = [ ] ;
1747+ mockTransport = createRouterTransport ( ( { service } ) => {
1748+ service ( DataSyncService , {
1749+ fileUpload : async ( requests : AsyncIterable < FileUploadRequest > ) => {
1750+ for await ( const request of requests ) {
1751+ capturedRequests . push ( request ) ;
1752+ }
1753+ return new FileUploadResponse ( {
1754+ fileId : expectedFileId ,
1755+ binaryDataId : expectedBinaryDataId ,
1756+ } ) ;
1757+ } ,
1758+ } ) ;
1759+ } ) ;
1760+ } ) ;
1761+
1762+ it ( 'uploads file with metadata and file contents' , async ( ) => {
1763+ const result = await subject ( ) . fileUpload ( binaryData , partId , options ) ;
1764+
1765+ expect ( result ) . toBe ( expectedBinaryDataId ) ;
1766+ expect ( capturedRequests ) . toHaveLength ( 2 ) ;
1767+
1768+ // Check metadata request
1769+ const metadataRequest = capturedRequests [ 0 ] ! ;
1770+ expect ( metadataRequest . uploadPacket . case ) . toBe ( 'metadata' ) ;
1771+ const metadata = metadataRequest . uploadPacket . value as UploadMetadata ;
1772+ expect ( metadata . partId ) . toBe ( partId ) ;
1773+ expect ( metadata . type ) . toBe ( DataType . FILE ) ;
1774+ expect ( metadata . componentType ) . toBe ( options . componentType ) ;
1775+ expect ( metadata . componentName ) . toBe ( options . componentName ) ;
1776+ expect ( metadata . methodName ) . toBe ( options . methodName ) ;
1777+ expect ( metadata . fileName ) . toBe ( options . fileName ) ;
1778+ expect ( metadata . fileExtension ) . toBe ( options . fileExtension ) ;
1779+ expect ( metadata . tags ) . toStrictEqual ( options . tags ) ;
1780+ expect ( metadata . datasetIds ) . toStrictEqual ( options . datasetIds ) ;
1781+
1782+ // Check file contents request
1783+ const fileContentsRequest = capturedRequests [ 1 ] ! ;
1784+ expect ( fileContentsRequest . uploadPacket . case ) . toBe ( 'fileContents' ) ;
1785+ const fileContents = fileContentsRequest . uploadPacket . value as FileData ;
1786+ expect ( fileContents . data ) . toEqual ( binaryData ) ;
1787+ } ) ;
1788+
1789+ it ( 'uploads file without optional parameters' , async ( ) => {
1790+ const result = await subject ( ) . fileUpload ( binaryData , partId ) ;
1791+
1792+ expect ( result ) . toBe ( expectedBinaryDataId ) ;
1793+ expect ( capturedRequests ) . toHaveLength ( 2 ) ;
1794+
1795+ // Check metadata request
1796+ const metadataRequest = capturedRequests [ 0 ] ! ;
1797+ expect ( metadataRequest . uploadPacket . case ) . toBe ( 'metadata' ) ;
1798+ const metadata = metadataRequest . uploadPacket . value as UploadMetadata ;
1799+ expect ( metadata . partId ) . toBe ( partId ) ;
1800+ expect ( metadata . type ) . toBe ( DataType . FILE ) ;
1801+ expect ( metadata . componentType ) . toBe ( '' ) ;
1802+ expect ( metadata . componentName ) . toBe ( '' ) ;
1803+ expect ( metadata . methodName ) . toBe ( '' ) ;
1804+ expect ( metadata . fileName ) . toBe ( '' ) ;
1805+ expect ( metadata . fileExtension ) . toBe ( '' ) ;
1806+ expect ( metadata . tags ) . toStrictEqual ( [ ] ) ;
1807+ expect ( metadata . datasetIds ) . toStrictEqual ( [ ] ) ;
1808+
1809+ // Check file contents request
1810+ const fileContentsRequest = capturedRequests [ 1 ] ! ;
1811+ expect ( fileContentsRequest . uploadPacket . case ) . toBe ( 'fileContents' ) ;
1812+ const fileContents = fileContentsRequest . uploadPacket . value as FileData ;
1813+ expect ( fileContents . data ) . toEqual ( binaryData ) ;
1814+ } ) ;
1815+
1816+ it ( 'chunks file data' , async ( ) => {
1817+ const numChunks = 3 ;
1818+ const data = Uint8Array . from (
1819+ { length : DataClient . UPLOAD_CHUNK_SIZE * numChunks } ,
1820+ ( ) => Math . floor ( Math . random ( ) * 256 )
1821+ ) ;
1822+
1823+ const result = await subject ( ) . fileUpload ( data , partId ) ;
1824+ expect ( result ) . toBe ( expectedBinaryDataId ) ;
1825+ expect ( capturedRequests ) . toHaveLength ( 1 + numChunks ) ;
1826+
1827+ const metadataRequest = capturedRequests [ 0 ] ! ;
1828+ expect ( metadataRequest . uploadPacket . case ) . toBe ( 'metadata' ) ;
1829+
1830+ const contentRequests = capturedRequests . slice ( 1 ) ;
1831+ expect ( contentRequests ) . toHaveLength ( numChunks ) ;
1832+
1833+ const receivedLength = contentRequests . reduce (
1834+ ( acc , val ) => acc + ( val . uploadPacket . value as FileData ) . data . length ,
1835+ 0
1836+ ) ;
1837+ expect ( receivedLength ) . toEqual ( numChunks * DataClient . UPLOAD_CHUNK_SIZE ) ;
1838+
1839+ const receivedData = new Uint8Array ( receivedLength ) ;
1840+ let offset = 0 ;
1841+ for ( const req of contentRequests ) {
1842+ expect ( req . uploadPacket . case ) . toBe ( 'fileContents' ) ;
1843+ const fileData = req . uploadPacket . value as FileData ;
1844+ expect ( fileData . data ) . toHaveLength ( DataClient . UPLOAD_CHUNK_SIZE ) ;
1845+ receivedData . set ( fileData . data , offset ) ;
1846+ offset += fileData . data . length ;
1847+ }
1848+
1849+ expect ( receivedData ) . toStrictEqual ( data ) ;
1850+ } ) ;
1851+ } ) ;
0 commit comments