@@ -4,32 +4,64 @@ import * as sinon from "sinon";
44import { LoggerStub , ProcessServiceStub } from "../../stubs" ;
55import { AndroidLivesyncTool } from "../../../lib/services/livesync/android-livesync-tool" ;
66import { MobileHelper } from "../../../lib/common/mobile/mobile-helper" ;
7+ import { FileSystem } from "../../../lib/common/file-system" ;
78import * as net from "net" ;
8- import { Duplex } from "stream" ;
99import { MobilePlatformsCapabilities } from "../../../lib/mobile-platforms-capabilities" ;
1010import { DevicePlatformsConstants } from "../../../lib/common/mobile/device-platforms-constants" ;
11+ import * as path from "path" ;
12+ import temp = require( "temp" ) ;
13+ import * as crypto from "crypto" ;
1114
12- const createTestInjector = ( socket : INetSocket ) : IInjector => {
13- const testInjector = new Yok ( ) ;
15+ temp . track ( ) ;
16+ const protocolVersion = "0.2.0" ;
17+
18+ class TestSocket extends net . Socket {
19+ public accomulatedData : Buffer [ ] = [ ] ;
20+ public connect ( ) { }
21+ public write ( data : Buffer | string , cb ?: string | Function , encoding ?: Function | string ) : boolean {
22+ if ( data instanceof Buffer ) {
23+ this . accomulatedData . push ( data ) ;
24+ } else {
25+ const buffer = Buffer . from ( data , 'utf8' ) ;
26+ this . accomulatedData . push ( buffer ) ;
27+ }
1428
15- testInjector . register ( "fs" , {
16- createReadStream : ( filename : string , encoding ?: IReadFileOptions | string ) : NodeJS . ReadableStream => {
17- const fileStream = new Duplex ( ) ;
18- fileStream . write ( "testContent" ) ;
19- fileStream . end ( ) ;
20- return fileStream ;
21- } ,
29+ if ( cb && cb instanceof Function ) {
30+ cb . call ( this ) ;
31+ }
2232
23- exists : ( ) : boolean => true ,
33+ return true ;
34+ }
35+ }
2436
25- readJson : ( filePath : string ) : any => null ,
37+ const rootTestFileJs = "test.js" ;
38+ const rootTestFileCss = "test.css" ;
39+ const dirTestFileJs = path . join ( "testdir" , "testdir.js" ) ;
40+ const dirTestFileCss = path . join ( "testdir" , "testdir.css" ) ;
2641
27- enumerateFilesInDirectorySync : ( directoryPath : string ,
28- filterCallback ?: ( _file : string , _stat : IFsStats ) => boolean ,
29- opts ?: { enumerateDirectories ?: boolean , includeEmptyDirectories ?: boolean } ,
30- foundFiles ?: string [ ] ) : string [ ] => [ ]
31- } ) ;
42+ const fileContents = {
43+ [ rootTestFileJs ] : "Test js content" ,
44+ [ rootTestFileCss ] : "Test css content" ,
45+ [ dirTestFileJs ] : "Test js in dir" ,
46+ [ dirTestFileCss ] : "Test css in dir"
47+ }
48+
49+ const projectCreated = false ;
50+ const testAppPath = temp . mkdirSync ( "testsyncapp" ) ;
51+ const testAppPlatformPath = path . join ( testAppPath , "platforms" , "android" , "app" , "src" , "main" , "assets" , "app" ) ;
52+ const createTestProject = ( testInjector : IInjector ) => {
53+ if ( ! projectCreated ) {
54+ const fs = testInjector . resolve ( "fs" ) ;
55+ _ . forEach ( fileContents , ( value , key ) => {
56+ fs . writeFile ( path . join ( testAppPlatformPath , key ) , value ) ;
57+ } )
58+ }
59+ }
60+
61+ const createTestInjector = ( socket : INetSocket , fileStreams : IDictionary < NodeJS . ReadableStream > ) : IInjector => {
62+ const testInjector = new Yok ( ) ;
3263
64+ testInjector . register ( "fs" , FileSystem ) ;
3365 testInjector . register ( "logger" , LoggerStub ) ;
3466 testInjector . register ( "processService" , ProcessServiceStub ) ;
3567 testInjector . register ( "injector" , testInjector ) ;
@@ -49,13 +81,77 @@ const createTestInjector = (socket: INetSocket): IInjector => {
4981 return testInjector ;
5082} ;
5183
84+ const getSendFileData = ( buffers : Buffer [ ] ) => {
85+ const buffer = Buffer . concat ( buffers ) ;
86+ const operation = getOperation ( buffer ) ;
87+ const { fileName, fileNameEnd } = getFileName ( buffer ) ;
88+
89+ const { fileContentSize, fileContentSizeEnd } = getFileContentSize ( buffer , fileNameEnd ) ;
90+ const headerHashMatch = compareHash ( buffer , 0 , fileContentSizeEnd , fileContentSizeEnd )
91+ const headerHashEnd = fileContentSizeEnd + 16 ;
92+ const { fileContent, fileContentEnd} = getFileContent ( buffer , headerHashEnd , fileContentSize ) ;
93+ const fileHashMatch = compareHash ( buffer , headerHashEnd , fileContentEnd , fileContentEnd ) ;
94+
95+ return { operation, fileName, fileContent, headerHashMatch, fileHashMatch} ;
96+ }
97+
98+ const getRemoveFileData = ( buffers : Buffer [ ] ) => {
99+ const buffer = Buffer . concat ( buffers ) ;
100+ const operation = getOperation ( buffer ) ;
101+ const { fileName, fileNameEnd } = getFileName ( buffer ) ;
102+ const headerHashMatch = compareHash ( buffer , 0 , fileNameEnd , fileNameEnd )
103+
104+ return { operation, fileName, headerHashMatch} ;
105+ }
106+
107+ const getFileName = ( buffer : Buffer ) => {
108+ const fileNameSizeLength = buffer . readUInt8 ( 1 ) ;
109+ const fileNameSizeEnd = fileNameSizeLength + 2 ;
110+ const fileNameSize = buffer . toString ( "utf8" , 2 , fileNameSizeEnd ) ;
111+ const fileNameEnd = fileNameSizeEnd + Number ( fileNameSize )
112+ const fileName = buffer . toString ( "utf8" , fileNameSizeEnd , fileNameEnd ) ;
113+
114+ return { fileName, fileNameEnd } ;
115+ }
116+
117+ const getFileContentSize = ( buffer : Buffer , offset : number ) => {
118+ const fileContentSizeLength = buffer . readUInt8 ( offset ) ;
119+ const fileContentSizeBegin = offset + 1 ;
120+ const fileContentSizeEnd = fileContentSizeBegin + fileContentSizeLength ;
121+ const fileContentSize = Number ( buffer . toString ( "utf8" , fileContentSizeBegin , fileContentSizeEnd ) ) ;
122+
123+ return { fileContentSize, fileContentSizeEnd}
124+ }
125+
126+ const getFileContent = ( buffer : Buffer , offset : number , contentLength : number ) => {
127+ const fileContentEnd = offset + Number ( contentLength )
128+ const fileContent = buffer . toString ( "utf8" , offset , fileContentEnd ) ;
129+
130+ return { fileContent, fileContentEnd} ;
131+ }
132+
133+ const getOperation = ( buffer : Buffer ) => {
134+ const operation = buffer . toString ( "utf8" , 0 , 1 ) ;
135+
136+ return Number ( operation ) ;
137+ }
138+
139+ const compareHash = ( buffer : Buffer , dataStart : number , dataEnd : number , hashStart : number ) => {
140+ const headerBuffer = buffer . slice ( dataStart , dataEnd ) ;
141+ const hashEnd = hashStart + 16 ;
142+ const headerHash = buffer . slice ( hashStart , hashEnd ) ;
143+ const computedHash = crypto . createHash ( "md5" ) . update ( headerBuffer ) . digest ( ) ;
144+ const headerHashMatch = headerHash . equals ( computedHash ) ;
145+
146+ return headerHashMatch ;
147+ }
148+
52149const getHandshakeBuffer = ( ) => {
53- const protocolVersion = "0.2.0" ;
54150 const packageName = "org.comp.test" ;
55151 const handshakeBuffer = Buffer . alloc ( Buffer . byteLength ( protocolVersion ) + Buffer . byteLength ( packageName ) + 1 ) ;
56152
57153 let offset = handshakeBuffer . writeUInt8 ( Buffer . byteLength ( protocolVersion ) , 0 ) ;
58- offset = + handshakeBuffer . write ( protocolVersion , offset ) ;
154+ offset = offset + handshakeBuffer . write ( protocolVersion , offset ) ;
59155 handshakeBuffer . write ( packageName , offset ) ;
60156
61157 return handshakeBuffer ;
@@ -66,19 +162,14 @@ describe.only("AndroidLivesyncTool", () => {
66162 let livesyncTool : IAndroidLivesyncTool = null ;
67163 let testSocket : INetSocket ;
68164 let sandbox : sinon . SinonSandbox = null ;
165+ let fileStreams : IDictionary < NodeJS . ReadableStream > = null ;
69166
70167 beforeEach ( ( ) => {
71168 sandbox = sinon . sandbox . create ( ) ;
72- testSocket = new net . Socket ( ) ;
73- var stub = sandbox . stub ( testSocket , 'write' ) . callsFake ( function ( ) {
74-
75- var args = stub . args ;
76-
77- // this will echo whatever they wrote
78- if ( args . length > 0 )
79- testSocket . emit ( 'data' , stub . args [ stub . callCount - 1 ] [ 0 ] ) ;
80- } ) ;
81- testInjector = createTestInjector ( testSocket ) ;
169+ testSocket = new TestSocket ( ) ;
170+ fileStreams = { } ;
171+ testInjector = createTestInjector ( testSocket , fileStreams ) ;
172+ createTestProject ( testInjector ) ;
82173 livesyncTool = testInjector . resolve ( AndroidLivesyncTool ) ;
83174 } ) ;
84175
@@ -98,7 +189,7 @@ describe.only("AndroidLivesyncTool", () => {
98189 if ( event === "close" ) {
99190 closeAttachCount ++ ;
100191 if ( closeAttachCount === 1 ) {
101- testSocket . emit ( " close" , false ) ;
192+ testSocket . emit ( ' close' , false ) ;
102193 }
103194 }
104195 } ) ;
@@ -108,7 +199,7 @@ describe.only("AndroidLivesyncTool", () => {
108199 if ( event === "data" ) {
109200 dataAttachCount ++
110201 if ( dataAttachCount === 2 ) {
111- testSocket . write ( getHandshakeBuffer ( ) ) ;
202+ testSocket . emit ( 'data' , getHandshakeBuffer ( ) ) ;
112203 }
113204 }
114205 } ) ;
@@ -117,15 +208,110 @@ describe.only("AndroidLivesyncTool", () => {
117208
118209 return connectPromise . then ( ( ) => {
119210 assert ( connectStub . calledTwice ) ;
211+ assert . isFulfilled ( connectPromise ) ;
212+ assert . equal ( livesyncTool . protocolVersion , protocolVersion ) ;
120213 }
121214 ) ;
122215 } ) ;
123216
124217 it ( "should fail eventually" , ( ) => {
125- sandbox . stub ( testSocket , "connect" ) ;
126218 const connectPromise = livesyncTool . connect ( { appIdentifier : "test" , deviceIdentifier : "test" , appPlatformsPath : "test" , connectTimeout : 400 } ) ;
219+
220+ return assert . isRejected ( connectPromise ) ;
221+ } ) ;
222+
223+ it ( "should fail if connection alreday exists" , ( ) => {
224+ const originalOnce = testSocket . once ;
225+
226+ sandbox . stub ( testSocket , "once" ) . callsFake ( function ( event : string ) {
227+ originalOnce . apply ( this , arguments ) ;
228+ if ( event === "data" ) {
229+ testSocket . emit ( 'data' , getHandshakeBuffer ( ) ) ;
230+ }
231+ } ) ;
127232
128- return connectPromise . should . be . rejected ;
233+ const connectPromise = livesyncTool . connect ( { appIdentifier : "test" , deviceIdentifier : "test" , appPlatformsPath : "test" , connectTimeout : 400 } ) . then ( ( ) => {
234+ return livesyncTool . connect ( { appIdentifier : "test" , deviceIdentifier : "test" , appPlatformsPath : "test" , connectTimeout : 400 } ) ;
235+ } ) ;
236+
237+ return assert . isRejected ( connectPromise ) ;
238+ } ) ;
239+ } ) ;
240+
241+ describe ( "sendFile" , ( ) => {
242+ it ( "sends correct information" , async ( ) => {
243+ const originalOnce = testSocket . once ;
244+ let dataAttachCount = 0 ;
245+ const filePath = path . join ( testAppPlatformPath , rootTestFileJs ) ;
246+ sandbox . stub ( testSocket , "once" ) . callsFake ( function ( event : string ) {
247+ originalOnce . apply ( this , arguments ) ;
248+ if ( event === "data" ) {
249+ dataAttachCount ++
250+ if ( dataAttachCount === 1 ) {
251+ testSocket . emit ( 'data' , getHandshakeBuffer ( ) ) ;
252+ }
253+ }
254+ } ) ;
255+ await livesyncTool . connect ( { appIdentifier : "test" , deviceIdentifier : "test" , appPlatformsPath : testAppPlatformPath } ) ;
256+
257+ await livesyncTool . sendFile ( filePath ) ;
258+
259+ const sendFileData = getSendFileData ( ( testSocket as TestSocket ) . accomulatedData ) ;
260+
261+ assert . equal ( sendFileData . fileContent , fileContents [ rootTestFileJs ] ) ;
262+ assert . equal ( sendFileData . fileName , rootTestFileJs ) ;
263+ assert ( sendFileData . headerHashMatch ) ;
264+ assert ( sendFileData . fileHashMatch ) ;
265+ assert . equal ( sendFileData . operation , AndroidLivesyncTool . CREATE_FILE_OPERATION ) ;
266+ } ) ;
267+ } ) ;
268+
269+ describe ( "remove file" , ( ) => {
270+ it ( "sends correct information" , async ( ) => {
271+ const originalOnce = testSocket . once ;
272+ let dataAttachCount = 0 ;
273+ const filePath = path . join ( testAppPlatformPath , rootTestFileJs ) ;
274+ sandbox . stub ( testSocket , "once" ) . callsFake ( function ( event : string ) {
275+ originalOnce . apply ( this , arguments ) ;
276+ if ( event === "data" ) {
277+ dataAttachCount ++
278+ if ( dataAttachCount === 1 ) {
279+ testSocket . emit ( 'data' , getHandshakeBuffer ( ) ) ;
280+ }
281+ }
282+ } ) ;
283+ await livesyncTool . connect ( { appIdentifier : "test" , deviceIdentifier : "test" , appPlatformsPath : testAppPlatformPath } ) ;
284+ await livesyncTool . removeFile ( filePath ) ;
285+
286+ const removeData = getRemoveFileData ( ( testSocket as TestSocket ) . accomulatedData ) ;
287+
288+ assert . equal ( removeData . fileName , rootTestFileJs ) ;
289+ assert . equal ( removeData . operation , AndroidLivesyncTool . DELETE_FILE_OPERATION ) ;
290+ assert ( removeData . headerHashMatch ) ;
291+ } ) ;
292+ } ) ;
293+
294+ describe ( "sendFiles" , ( ) => {
295+ it ( "calls sendFile for each file" , async ( ) => {
296+ const filePaths = _ . keys ( fileContents ) . map ( filePath => path . join ( testAppPlatformPath , filePath ) ) ;
297+ const sendFileStub = sandbox . stub ( livesyncTool , "sendFile" ) . callsFake ( ( ) => Promise . resolve ( ) ) ;
298+ await livesyncTool . sendFiles ( filePaths ) ;
299+
300+ _ . forEach ( filePaths , ( filePath ) => {
301+ assert ( sendFileStub . calledWith ( filePath ) ) ;
302+ } )
303+ } ) ;
304+ } ) ;
305+
306+ describe ( "removeFiles" , ( ) => {
307+ it ( "calls sendFile for each file" , async ( ) => {
308+ const filePaths = _ . keys ( fileContents ) . map ( filePath => path . join ( testAppPlatformPath , filePath ) ) ;
309+ const removeFileStub = sandbox . stub ( livesyncTool , "removeFile" ) . callsFake ( ( ) => Promise . resolve ( ) ) ;
310+ await livesyncTool . removeFiles ( filePaths ) ;
311+
312+ _ . forEach ( filePaths , ( filePath ) => {
313+ assert ( removeFileStub . calledWith ( filePath ) ) ;
314+ } )
129315 } ) ;
130316 } ) ;
131317 } ) ;
0 commit comments