def test_basic(self): """ Upload a basic file using the FileUploadResource, in just a single chunk. """ upload_dir = FilePath(self.mktemp()) upload_dir.makedirs() temp_dir = FilePath(self.mktemp()) temp_dir.makedirs() fields = { "file_name": "resumableFilename", "mime_type": "resumableType", "total_size": "resumableTotalSize", "chunk_number": "resumableChunkNumber", "chunk_size": "resumableChunkSize", "total_chunks": "resumableTotalChunks", "content": "file", "on_progress": "on_progress", "session": "session" } mock_session = Mock() resource = FileUploadResource(upload_dir.path, temp_dir.path, fields, mock_session) mp = Multipart() mp.add_part(b"resumableChunkNumber", b"1") mp.add_part(b"resumableChunkSize", b"1048576") mp.add_part(b"resumableCurrentChunkSize", b"16") mp.add_part(b"resumableTotalSize", b"16") mp.add_part(b"resumableType", b"text/plain") mp.add_part(b"resumableIdentifier", b"16-examplefiletxt") mp.add_part(b"resumableFilename", b"examplefile.txt") mp.add_part(b"resumableRelativePath", b"examplefile.txt") mp.add_part(b"resumableTotalChunks", b"1") mp.add_part(b"on_progress", b"com.example.upload.on_progress") mp.add_part(b"session", b"6891276359801283") mp.add_part(b"file", b"hello Crossbar!\n", content_type=b"application/octet-stream", filename=b"blob") body, headers = mp.render() d = renderResource(resource, b"/", method="POST", headers=headers, body=body) res = self.successResultOf(d) res.setResponseCode.assert_called_once_with(200) self.assertEqual(len(mock_session.method_calls), 2) # Starting the upload self.assertEqual(mock_session.method_calls[0][1][0], u"com.example.upload.on_progress") self.assertEqual(mock_session.method_calls[0][1][1]["status"], "started") self.assertEqual(mock_session.method_calls[0][1][1]["id"], "examplefile.txt") # Upload complete self.assertEqual(mock_session.method_calls[1][1][0], u"com.example.upload.on_progress") self.assertEqual(mock_session.method_calls[1][1][1]["status"], "finished") self.assertEqual(mock_session.method_calls[1][1][1]["id"], "examplefile.txt") # Nothing in the temp dir, one file in the upload self.assertEqual(len(temp_dir.listdir()), 0) self.assertEqual(len(upload_dir.listdir()), 1) with upload_dir.child("examplefile.txt").open("rb") as f: self.assertEqual(f.read(), b"hello Crossbar!\n")
def test_multichunk_shuffle(self): """ Uploading files that are in multiple chunks and are uploaded in different order works. """ upload_dir = FilePath(self.mktemp()) upload_dir.makedirs() temp_dir = FilePath(self.mktemp()) temp_dir.makedirs() fields = { "file_name": "resumableFilename", "mime_type": "resumableType", "total_size": "resumableTotalSize", "chunk_number": "resumableChunkNumber", "chunk_size": "resumableChunkSize", "total_chunks": "resumableTotalChunks", "content": "file", "on_progress": "on_progress", "session": "session" } mock_session = Mock() resource = FileUploadResource(upload_dir.path, temp_dir.path, fields, mock_session) # # Chunk 2 multipart_body = b"""-----------------------------42560029919436807832069165364\r\nContent-Disposition: form-data; name="resumableChunkNumber"\r\n\r\n2\r\n-----------------------------42560029919436807832069165364\r\nContent-Disposition: form-data; name="resumableChunkSize"\r\n\r\n10\r\n-----------------------------42560029919436807832069165364\r\nContent-Disposition: form-data; name="resumableCurrentChunkSize"\r\n\r\n6\r\n-----------------------------42560029919436807832069165364\r\nContent-Disposition: form-data; name="resumableTotalSize"\r\n\r\n16\r\n-----------------------------42560029919436807832069165364\r\nContent-Disposition: form-data; name="resumableType"\r\n\r\ntext/plain\r\n-----------------------------42560029919436807832069165364\r\nContent-Disposition: form-data; name="resumableIdentifier"\r\n\r\n16-examplefiletxt\r\n-----------------------------42560029919436807832069165364\r\nContent-Disposition: form-data; name="resumableFilename"\r\n\r\nexamplefile.txt\r\n-----------------------------42560029919436807832069165364\r\nContent-Disposition: form-data; name="resumableRelativePath"\r\n\r\nexamplefile.txt\r\n-----------------------------42560029919436807832069165364\r\nContent-Disposition: form-data; name="resumableTotalChunks"\r\n\r\n2\r\n-----------------------------42560029919436807832069165364\r\nContent-Disposition: form-data; name="on_progress"\r\n\r\ncom.example.upload.on_progress\r\n-----------------------------42560029919436807832069165364\r\nContent-Disposition: form-data; name="session"\r\n\r\n8887465641628580\r\n-----------------------------42560029919436807832069165364\r\nContent-Disposition: form-data; name="file"; filename="blob"\r\nContent-Type: application/octet-stream\r\n\r\nsbar!\n\r\n-----------------------------42560029919436807832069165364--\r\n""" d = renderResource( resource, b"/", method="POST", headers={ b"content-type": [ b"multipart/form-data; boundary=---------------------------42560029919436807832069165364" ], b"Content-Length": [b"1688"] }, body=multipart_body) res = self.successResultOf(d) res.setResponseCode.assert_called_once_with(200) # One directory in the temp dir, nothing in the upload dir, temp dir # contains one chunk self.assertEqual(len(temp_dir.listdir()), 1) self.assertEqual(len(temp_dir.child("examplefile.txt").listdir()), 1) with temp_dir.child("examplefile.txt").child("chunk_2").open( "rb") as f: # print(f.read()) self.assertEqual(f.read(), b"sbar!\n") self.assertEqual(len(upload_dir.listdir()), 0) # # Chunk 1 multipart_body = b"""-----------------------------1311987731215707521443909311\r\nContent-Disposition: form-data; name="resumableChunkNumber"\r\n\r\n1\r\n-----------------------------1311987731215707521443909311\r\nContent-Disposition: form-data; name="resumableChunkSize"\r\n\r\n10\r\n-----------------------------1311987731215707521443909311\r\nContent-Disposition: form-data; name="resumableCurrentChunkSize"\r\n\r\n10\r\n-----------------------------1311987731215707521443909311\r\nContent-Disposition: form-data; name="resumableTotalSize"\r\n\r\n16\r\n-----------------------------1311987731215707521443909311\r\nContent-Disposition: form-data; name="resumableType"\r\n\r\ntext/plain\r\n-----------------------------1311987731215707521443909311\r\nContent-Disposition: form-data; name="resumableIdentifier"\r\n\r\n16-examplefiletxt\r\n-----------------------------1311987731215707521443909311\r\nContent-Disposition: form-data; name="resumableFilename"\r\n\r\nexamplefile.txt\r\n-----------------------------1311987731215707521443909311\r\nContent-Disposition: form-data; name="resumableRelativePath"\r\n\r\nexamplefile.txt\r\n-----------------------------1311987731215707521443909311\r\nContent-Disposition: form-data; name="resumableTotalChunks"\r\n\r\n2\r\n-----------------------------1311987731215707521443909311\r\nContent-Disposition: form-data; name="on_progress"\r\n\r\ncom.example.upload.on_progress\r\n-----------------------------1311987731215707521443909311\r\nContent-Disposition: form-data; name="session"\r\n\r\n8887465641628580\r\n-----------------------------1311987731215707521443909311\r\nContent-Disposition: form-data; name="file"; filename="blob"\r\nContent-Type: application/octet-stream\r\n\r\nhello Cros\r\n-----------------------------1311987731215707521443909311--\r\n""" d = renderResource( resource, b"/", method="POST", headers={ b"content-type": [ b"multipart/form-data; boundary=---------------------------1311987731215707521443909311" ], b"Content-Length": [b"1680"] }, body=multipart_body) res = self.successResultOf(d) res.setResponseCode.assert_called_once_with(200) self.assertEqual(len(mock_session.method_calls), 4) # Starting the upload self.assertEqual(mock_session.method_calls[0][1][0], u"com.example.upload.on_progress") self.assertEqual(mock_session.method_calls[0][1][1]["status"], "started") self.assertEqual(mock_session.method_calls[0][1][1]["id"], "examplefile.txt") self.assertEqual(mock_session.method_calls[0][1][1]["chunk"], 2) # Progress, first chunk done self.assertEqual(mock_session.method_calls[1][1][0], u"com.example.upload.on_progress") self.assertEqual(mock_session.method_calls[1][1][1]["status"], "progress") self.assertEqual(mock_session.method_calls[1][1][1]["id"], "examplefile.txt") self.assertEqual(mock_session.method_calls[1][1][1]["chunk"], 2) # Progress, second chunk done self.assertEqual(mock_session.method_calls[2][1][0], u"com.example.upload.on_progress") self.assertEqual(mock_session.method_calls[2][1][1]["status"], "progress") self.assertEqual(mock_session.method_calls[2][1][1]["id"], "examplefile.txt") self.assertEqual(mock_session.method_calls[2][1][1]["chunk"], 1) # Upload complete self.assertEqual(mock_session.method_calls[3][1][0], u"com.example.upload.on_progress") self.assertEqual(mock_session.method_calls[3][1][1]["status"], "finished") self.assertEqual(mock_session.method_calls[3][1][1]["id"], "examplefile.txt") self.assertEqual(mock_session.method_calls[3][1][1]["chunk"], 1) # Nothing in the temp dir, one file in the upload self.assertEqual(len(temp_dir.listdir()), 0) self.assertEqual(len(upload_dir.listdir()), 1) with upload_dir.child("examplefile.txt").open("rb") as f: self.assertEqual(f.read(), b"hello Crossbar!\n")
def create_resource(self, path_config): """ Creates child resource to be added to the parent. :param path_config: Configuration for the new child resource. :type path_config: dict :returns: Resource -- the new child resource """ # WAMP-WebSocket resource # if path_config['type'] == 'websocket': ws_factory = WampWebSocketServerFactory( self._router_session_factory, self.config.extra.cbdir, path_config, self._templates) # FIXME: Site.start/stopFactory should start/stop factories wrapped as Resources ws_factory.startFactory() return WebSocketResource(ws_factory) # Static file hierarchy resource # elif path_config['type'] == 'static': static_options = path_config.get('options', {}) if 'directory' in path_config: static_dir = os.path.abspath( os.path.join(self.config.extra.cbdir, path_config['directory'])) elif 'package' in path_config: if 'resource' not in path_config: raise ApplicationError( "crossbar.error.invalid_configuration", "missing resource") try: mod = importlib.import_module(path_config['package']) except ImportError as e: emsg = "ERROR: could not import resource '{}' from package '{}' - {}".format( path_config['resource'], path_config['package'], e) log.msg(emsg) raise ApplicationError( "crossbar.error.invalid_configuration", emsg) else: try: static_dir = os.path.abspath( pkg_resources.resource_filename( path_config['package'], path_config['resource'])) except Exception as e: emsg = "ERROR: could not import resource '{}' from package '{}' - {}".format( path_config['resource'], path_config['package'], e) log.msg(emsg) raise ApplicationError( "crossbar.error.invalid_configuration", emsg) else: raise ApplicationError("crossbar.error.invalid_configuration", "missing web spec") static_dir = static_dir.encode( 'ascii', 'ignore') # http://stackoverflow.com/a/20433918/884770 # create resource for file system hierarchy # if static_options.get('enable_directory_listing', False): static_resource_class = StaticResource else: static_resource_class = StaticResourceNoListing cache_timeout = static_options.get('cache_timeout', DEFAULT_CACHE_TIMEOUT) static_resource = static_resource_class( static_dir, cache_timeout=cache_timeout) # set extra MIME types # static_resource.contentTypes.update(EXTRA_MIME_TYPES) if 'mime_types' in static_options: static_resource.contentTypes.update( static_options['mime_types']) patchFileContentTypes(static_resource) # render 404 page on any concrete path not found # static_resource.childNotFound = Resource404( self._templates, static_dir) return static_resource # WSGI resource # elif path_config['type'] == 'wsgi': if not _HAS_WSGI: raise ApplicationError("crossbar.error.invalid_configuration", "WSGI unsupported") # wsgi_options = path_config.get('options', {}) if 'module' not in path_config: raise ApplicationError("crossbar.error.invalid_configuration", "missing WSGI app module") if 'object' not in path_config: raise ApplicationError("crossbar.error.invalid_configuration", "missing WSGI app object") # import WSGI app module and object mod_name = path_config['module'] try: mod = importlib.import_module(mod_name) except ImportError as e: raise ApplicationError( "crossbar.error.invalid_configuration", "WSGI app module '{}' import failed: {} - Python search path was {}" .format(mod_name, e, sys.path)) else: obj_name = path_config['object'] if obj_name not in mod.__dict__: raise ApplicationError( "crossbar.error.invalid_configuration", "WSGI app object '{}' not in module '{}'".format( obj_name, mod_name)) else: app = getattr(mod, obj_name) # create a Twisted Web WSGI resource from the user's WSGI application object try: wsgi_resource = WSGIResource(reactor, reactor.getThreadPool(), app) except Exception as e: raise ApplicationError( "crossbar.error.invalid_configuration", "could not instantiate WSGI resource: {}".format(e)) else: return wsgi_resource # Redirecting resource # elif path_config['type'] == 'redirect': redirect_url = path_config['url'].encode('ascii', 'ignore') return RedirectResource(redirect_url) # JSON value resource # elif path_config['type'] == 'json': value = path_config['value'] return JsonResource(value) # CGI script resource # elif path_config['type'] == 'cgi': cgi_processor = path_config['processor'] cgi_directory = os.path.abspath( os.path.join(self.config.extra.cbdir, path_config['directory'])) cgi_directory = cgi_directory.encode( 'ascii', 'ignore') # http://stackoverflow.com/a/20433918/884770 return CgiDirectory(cgi_directory, cgi_processor, Resource404(self._templates, cgi_directory)) # WAMP-Longpoll transport resource # elif path_config['type'] == 'longpoll': path_options = path_config.get('options', {}) lp_resource = WampLongPollResource( self._router_session_factory, timeout=path_options.get('request_timeout', 10), killAfter=path_options.get('session_timeout', 30), queueLimitBytes=path_options.get('queue_limit_bytes', 128 * 1024), queueLimitMessages=path_options.get('queue_limit_messages', 100), debug=path_options.get('debug', False), debug_transport_id=path_options.get('debug_transport_id', None)) lp_resource._templates = self._templates return lp_resource # Publisher resource (part of REST-bridge) # elif path_config['type'] == 'publisher': # create a vanilla session: the publisher will use this to inject events # publisher_session_config = ComponentConfig( realm=path_config['realm'], extra=None) publisher_session = ApplicationSession(publisher_session_config) # add the publisher session to the router # self._router_session_factory.add(publisher_session, authrole=path_config.get( 'role', 'anonymous')) # now create the publisher Twisted Web resource # return PublisherResource(path_config.get('options', {}), publisher_session) # Caller resource (part of REST-bridge) # elif path_config['type'] == 'caller': # create a vanilla session: the caller will use this to inject calls # caller_session_config = ComponentConfig(realm=path_config['realm'], extra=None) caller_session = ApplicationSession(caller_session_config) # add the calling session to the router # self._router_session_factory.add(caller_session, authrole=path_config.get( 'role', 'anonymous')) # now create the caller Twisted Web resource # return CallerResource(path_config.get('options', {}), caller_session) # File Upload resource # elif path_config['type'] == 'upload': upload_directory = os.path.abspath( os.path.join(self.config.extra.cbdir, path_config['directory'])) upload_directory = upload_directory.encode( 'ascii', 'ignore') # http://stackoverflow.com/a/20433918/884770 if not os.path.isdir(upload_directory): emsg = "configured upload directory '{}' in file upload resource isn't a directory".format( upload_directory) log.msg(emsg) raise ApplicationError("crossbar.error.invalid_configuration", emsg) if 'temp_directory' in path_config: temp_directory = os.path.abspath( os.path.join(self.config.extra.cbdir, path_config['temp_directory'])) temp_directory = temp_directory.encode( 'ascii', 'ignore') # http://stackoverflow.com/a/20433918/884770 else: temp_directory = os.path.abspath(tempfile.gettempdir()) temp_directory = os.path.join(temp_directory, 'crossbar-uploads') if not os.path.exists(temp_directory): os.makedirs(temp_directory) if not os.path.isdir(temp_directory): emsg = "configured temp directory '{}' in file upload resource isn't a directory".format( temp_directory) log.msg(emsg) raise ApplicationError("crossbar.error.invalid_configuration", emsg) # file upload progress and finish events are published via this session # upload_session_config = ComponentConfig(realm=path_config['realm'], extra=None) upload_session = ApplicationSession(upload_session_config) self._router_session_factory.add(upload_session, authrole=path_config.get( 'role', 'anonymous')) return FileUploadResource(upload_directory, temp_directory, path_config['form_fields'], upload_session, path_config.get('options', {})) # Generic Twisted Web resource # elif path_config['type'] == 'resource': try: klassname = path_config['classname'] if self.debug: log.msg("Starting class '{}'".format(klassname)) c = klassname.split('.') module_name, klass_name = '.'.join(c[:-1]), c[-1] module = importlib.import_module(module_name) make = getattr(module, klass_name) return make(path_config.get('extra', {})) except Exception as e: emsg = "Failed to import class '{}' - {}".format(klassname, e) log.msg(emsg) log.msg("PYTHONPATH: {}".format(sys.path)) raise ApplicationError("crossbar.error.class_import_failed", emsg) # Schema Docs resource # elif path_config['type'] == 'schemadoc': realm = path_config['realm'] if realm not in self.realm_to_id: raise ApplicationError( "crossbar.error.no_such_object", "No realm with URI '{}' configured".format(realm)) realm_id = self.realm_to_id[realm] realm_schemas = self.realms[realm_id].session._schemas return SchemaDocResource(self._templates, realm, realm_schemas) # Nested subpath resource # elif path_config['type'] == 'path': nested_paths = path_config.get('paths', {}) if '/' in nested_paths: nested_resource = self.create_resource(nested_paths['/']) else: nested_resource = Resource() # nest subpaths under the current entry # self.add_paths(nested_resource, nested_paths) return nested_resource else: raise ApplicationError( "crossbar.error.invalid_configuration", "invalid Web path type '{}'".format(path_config['type']))
def test_resumed_upload(self): """ Uploading part of a file, simulating a Crossbar restart, and continuing to upload works. """ upload_dir = FilePath(self.mktemp()) upload_dir.makedirs() temp_dir = FilePath(self.mktemp()) temp_dir.makedirs() fields = { "file_name": "resumableFilename", "mime_type": "resumableType", "total_size": "resumableTotalSize", "chunk_number": "resumableChunkNumber", "chunk_size": "resumableChunkSize", "total_chunks": "resumableTotalChunks", "content": "file", "on_progress": "on_progress", "session": "session" } mock_session = Mock() resource = FileUploadResource(upload_dir.path, temp_dir.path, fields, mock_session) # Make some stuff in the temp dir that wasn't there when it started but # is put there before a file upload temp_dir.child("otherjunk").makedirs() # # Chunk 1 mp = Multipart() mp.add_part(b"resumableChunkNumber", b"1") mp.add_part(b"resumableChunkSize", b"10") mp.add_part(b"resumableCurrentChunkSize", b"10") mp.add_part(b"resumableTotalSize", b"16") mp.add_part(b"resumableType", b"text/plain") mp.add_part(b"resumableIdentifier", b"16-examplefiletxt") mp.add_part(b"resumableFilename", b"examplefile.txt") mp.add_part(b"resumableRelativePath", b"examplefile.txt") mp.add_part(b"resumableTotalChunks", b"2") mp.add_part(b"on_progress", b"com.example.upload.on_progress") mp.add_part(b"session", b"6891276359801283") mp.add_part(b"file", b"hello Cros", content_type=b"application/octet-stream", filename=b"blob") body, headers = mp.render() d = renderResource(resource, b"/", method="POST", headers=headers, body=body) res = self.successResultOf(d) res.setResponseCode.assert_called_once_with(200) # One directory in the temp dir, nothing in the upload dir, temp dir # contains one chunk self.assertEqual(len(temp_dir.listdir()), 1) self.assertEqual(len(temp_dir.child("examplefile.txt").listdir()), 1) with temp_dir.child("examplefile.txt").child("chunk_1").open( "rb") as f: self.assertEqual(f.read(), b"hello Cros") self.assertEqual(len(upload_dir.listdir()), 0) del resource # Add some random junk in there that Crossbar isn't expecting temp_dir.child("junk").setContent(b"just some junk") temp_dir.child("examplefile.txt").child("hi").setContent(b"what") # Simulate restarting Crossbar by reinitialising the FileUploadResource resource = FileUploadResource(upload_dir.path, temp_dir.path, fields, mock_session) # # Chunk 2 mp = Multipart() mp.add_part(b"resumableChunkNumber", b"2") mp.add_part(b"resumableChunkSize", b"10") mp.add_part(b"resumableCurrentChunkSize", b"6") mp.add_part(b"resumableTotalSize", b"16") mp.add_part(b"resumableType", b"text/plain") mp.add_part(b"resumableIdentifier", b"16-examplefiletxt") mp.add_part(b"resumableFilename", b"examplefile.txt") mp.add_part(b"resumableRelativePath", b"examplefile.txt") mp.add_part(b"resumableTotalChunks", b"2") mp.add_part(b"on_progress", b"com.example.upload.on_progress") mp.add_part(b"session", b"6891276359801283") mp.add_part(b"file", b"sbar!\n", content_type=b"application/octet-stream", filename=b"blob") body, headers = mp.render() d = renderResource(resource, b"/", method="POST", headers=headers, body=body) res = self.successResultOf(d) res.setResponseCode.assert_called_once_with(200) self.assertEqual(len(mock_session.method_calls), 4) # Starting the upload self.assertEqual(mock_session.method_calls[0][1][0], u"com.example.upload.on_progress") self.assertEqual(mock_session.method_calls[0][1][1]["status"], "started") self.assertEqual(mock_session.method_calls[0][1][1]["id"], "examplefile.txt") self.assertEqual(mock_session.method_calls[0][1][1]["chunk"], 1) # Progress, first chunk done self.assertEqual(mock_session.method_calls[1][1][0], u"com.example.upload.on_progress") self.assertEqual(mock_session.method_calls[1][1][1]["status"], "progress") self.assertEqual(mock_session.method_calls[1][1][1]["id"], "examplefile.txt") self.assertEqual(mock_session.method_calls[1][1][1]["chunk"], 1) # Progress, second chunk done self.assertEqual(mock_session.method_calls[2][1][0], u"com.example.upload.on_progress") self.assertEqual(mock_session.method_calls[2][1][1]["status"], "progress") self.assertEqual(mock_session.method_calls[2][1][1]["id"], "examplefile.txt") self.assertEqual(mock_session.method_calls[2][1][1]["chunk"], 2) # Upload complete self.assertEqual(mock_session.method_calls[3][1][0], u"com.example.upload.on_progress") self.assertEqual(mock_session.method_calls[3][1][1]["status"], "finished") self.assertEqual(mock_session.method_calls[3][1][1]["id"], "examplefile.txt") self.assertEqual(mock_session.method_calls[3][1][1]["chunk"], 2) # No item in the temp dir which we made earlier, one item in the # upload dir. Otherjunk is removed because it belongs to no upload. self.assertEqual(len(temp_dir.listdir()), 0) self.assertEqual(len(upload_dir.listdir()), 1) with upload_dir.child("examplefile.txt").open("rb") as f: self.assertEqual(f.read(), b"hello Crossbar!\n")
def test_multichunk(self): """ Uploading files that are in multiple chunks works. """ upload_dir = self.mktemp() os.makedirs(upload_dir) temp_dir = self.mktemp() os.makedirs(temp_dir) fields = { "file_name": "resumableFilename", "mime_type": "resumableType", "total_size": "resumableTotalSize", "chunk_number": "resumableChunkNumber", "chunk_size": "resumableChunkSize", "total_chunks": "resumableTotalChunks", "content": "file", "on_progress": "on_progress", "session": "session" } mock_session = Mock() resource = FileUploadResource(upload_dir, temp_dir, fields, mock_session) # # Chunk 1 mp = Multipart() mp.add_part(b"resumableChunkNumber", b"1") mp.add_part(b"resumableChunkSize", b"10") mp.add_part(b"resumableCurrentChunkSize", b"10") mp.add_part(b"resumableTotalSize", b"16") mp.add_part(b"resumableType", b"text/plain") mp.add_part(b"resumableIdentifier", b"16-examplefiletxt") mp.add_part(b"resumableFilename", b"examplefile.txt") mp.add_part(b"resumableRelativePath", b"examplefile.txt") mp.add_part(b"resumableTotalChunks", b"2") mp.add_part(b"on_progress", b"com.example.upload.on_progress") mp.add_part(b"session", b"6891276359801283") mp.add_part(b"file", b"hello Cros", content_type=b"application/octet-stream", filename=b"blob") body, headers = mp.render() d = renderResource(resource, b"/", method="POST", headers=headers, body=body) res = self.successResultOf(d) self.assertEqual(res.code, 200) # One directory in the temp dir, nothing in the upload dir, temp dir # contains one chunk self.assertEqual(len(os.listdir(temp_dir)), 1) self.assertEqual( len(os.listdir(os.path.join(temp_dir, "examplefile.txt"))), 1) with open(os.path.join(temp_dir, "examplefile.txt", "chunk_1"), "rb") as f: self.assertEqual(f.read(), b"hello Cros") self.assertEqual(len(os.listdir(upload_dir)), 0) # # Chunk 2 mp = Multipart() mp.add_part(b"resumableChunkNumber", b"2") mp.add_part(b"resumableChunkSize", b"10") mp.add_part(b"resumableCurrentChunkSize", b"6") mp.add_part(b"resumableTotalSize", b"16") mp.add_part(b"resumableType", b"text/plain") mp.add_part(b"resumableIdentifier", b"16-examplefiletxt") mp.add_part(b"resumableFilename", b"examplefile.txt") mp.add_part(b"resumableRelativePath", b"examplefile.txt") mp.add_part(b"resumableTotalChunks", b"2") mp.add_part(b"on_progress", b"com.example.upload.on_progress") mp.add_part(b"session", b"6891276359801283") mp.add_part(b"file", b"sbar!\n", content_type=b"application/octet-stream", filename=b"blob") body, headers = mp.render() d = renderResource(resource, b"/", method="POST", headers=headers, body=body) res = self.successResultOf(d) self.assertEqual(res.code, 200) self.assertEqual(len(mock_session.method_calls), 4) # Starting the upload self.assertEqual(mock_session.method_calls[0][1][0], u"com.example.upload.on_progress") self.assertEqual(mock_session.method_calls[0][1][1]["status"], "started") self.assertEqual(mock_session.method_calls[0][1][1]["id"], "examplefile.txt") self.assertEqual(mock_session.method_calls[0][1][1]["chunk"], 1) # Progress, first chunk done self.assertEqual(mock_session.method_calls[1][1][0], u"com.example.upload.on_progress") self.assertEqual(mock_session.method_calls[1][1][1]["status"], "progress") self.assertEqual(mock_session.method_calls[1][1][1]["id"], "examplefile.txt") self.assertEqual(mock_session.method_calls[1][1][1]["chunk"], 1) # Progress, second chunk done self.assertEqual(mock_session.method_calls[2][1][0], u"com.example.upload.on_progress") self.assertEqual(mock_session.method_calls[2][1][1]["status"], "progress") self.assertEqual(mock_session.method_calls[2][1][1]["id"], "examplefile.txt") self.assertEqual(mock_session.method_calls[2][1][1]["chunk"], 2) # Upload complete self.assertEqual(mock_session.method_calls[3][1][0], u"com.example.upload.on_progress") self.assertEqual(mock_session.method_calls[3][1][1]["status"], "finished") self.assertEqual(mock_session.method_calls[3][1][1]["id"], "examplefile.txt") self.assertEqual(mock_session.method_calls[3][1][1]["chunk"], 2) # Nothing in the temp dir, one file in the upload self.assertEqual(len(os.listdir(temp_dir)), 0) self.assertEqual(len(os.listdir(upload_dir)), 1) with open(os.path.join(upload_dir, "examplefile.txt"), "rb") as f: self.assertEqual(f.read(), b"hello Crossbar!\n")