@@ -507,6 +507,102 @@ def _upload_from_file_simple_test_helper(self, properties=None,
507507 self .assertEqual (headers ['Content-Length' ], '6' )
508508 self .assertEqual (headers ['Content-Type' ], expected_content_type )
509509
510+ def test_upload_from_file_stream (self ):
511+ from six .moves .http_client import OK
512+ from six .moves .urllib .parse import parse_qsl
513+ from six .moves .urllib .parse import urlsplit
514+ from gcloud .streaming import http_wrapper
515+
516+ BLOB_NAME = 'blob-name'
517+ UPLOAD_URL = 'http://example.com/upload/name/key'
518+ DATA = b'ABCDE'
519+ loc_response = {'status' : OK , 'location' : UPLOAD_URL }
520+ chunk1_response = {'status' : http_wrapper .RESUME_INCOMPLETE ,
521+ 'range' : 'bytes 0-4' }
522+ chunk2_response = {'status' : OK }
523+ # Need valid JSON on last response, since resumable.
524+ connection = _Connection (
525+ (loc_response , b'' ),
526+ (chunk1_response , b'' ),
527+ (chunk2_response , b'{}' ),
528+ )
529+ client = _Client (connection )
530+ bucket = _Bucket (client )
531+ blob = self ._makeOne (BLOB_NAME , bucket = bucket )
532+ blob ._CHUNK_SIZE_MULTIPLE = 1
533+ blob .chunk_size = 5
534+
535+ from gcloud .streaming .test_transfer import _Stream
536+ file_obj = _Stream (DATA )
537+
538+ # Mock stream closes at end of data, like a socket might
539+ def is_stream_closed (stream ):
540+ if stream .tell () < len (DATA ):
541+ return stream ._closed
542+ else :
543+ return stream .close () or True
544+
545+ _Stream .closed = property (is_stream_closed )
546+
547+ def fileno_mock ():
548+ from io import UnsupportedOperation
549+ raise UnsupportedOperation ()
550+
551+ file_obj .fileno = fileno_mock
552+
553+ blob .upload_from_file (file_obj )
554+
555+ # Remove the temp property
556+ delattr (_Stream , "closed" )
557+
558+ rq = connection .http ._requested
559+ self .assertEqual (len (rq ), 3 )
560+
561+ # Requested[0]
562+ headers = dict (
563+ [(x .title (), str (y )) for x , y in rq [0 ].pop ('headers' ).items ()])
564+ self .assertEqual (headers ['Content-Length' ], '0' )
565+ self .assertEqual (headers ['X-Upload-Content-Type' ],
566+ 'application/octet-stream' )
567+
568+ uri = rq [0 ].pop ('uri' )
569+ scheme , netloc , path , qs , _ = urlsplit (uri )
570+ self .assertEqual (scheme , 'http' )
571+ self .assertEqual (netloc , 'example.com' )
572+ self .assertEqual (path , '/b/name/o' )
573+ self .assertEqual (dict (parse_qsl (qs )),
574+ {'uploadType' : 'resumable' , 'name' : BLOB_NAME })
575+ self .assertEqual (rq [0 ], {
576+ 'method' : 'POST' ,
577+ 'body' : '' ,
578+ 'connection_type' : None ,
579+ 'redirections' : 5 ,
580+ })
581+
582+ # Requested[1]
583+ headers = dict (
584+ [(x .title (), str (y )) for x , y in rq [1 ].pop ('headers' ).items ()])
585+ self .assertEqual (headers ['Content-Range' ], 'bytes 0-4/*' )
586+ self .assertEqual (rq [1 ], {
587+ 'method' : 'PUT' ,
588+ 'uri' : UPLOAD_URL ,
589+ 'body' : DATA [:5 ],
590+ 'connection_type' : None ,
591+ 'redirections' : 5 ,
592+ })
593+
594+ # Requested[2]
595+ headers = dict (
596+ [(x .title (), str (y )) for x , y in rq [2 ].pop ('headers' ).items ()])
597+ self .assertEqual (headers ['Content-Range' ], 'bytes */5' )
598+ self .assertEqual (rq [2 ], {
599+ 'method' : 'PUT' ,
600+ 'uri' : UPLOAD_URL ,
601+ 'body' : DATA [5 :],
602+ 'connection_type' : None ,
603+ 'redirections' : 5 ,
604+ })
605+
510606 def test_upload_from_file_simple (self ):
511607 self ._upload_from_file_simple_test_helper (
512608 expected_content_type = 'application/octet-stream' )
0 commit comments