Пример #1
0
def run_server(directory, port, token, file_size_limit, min_chunk_size,
               max_chunk_size):
    """ Start a (non-daemonized) server.

    This is a server-related command that start a non-daemonized server
    (not detached from the shell). The directory parameter is the root
    directory which will be served and therefore must be an existing
    directory. The server listens on port 6768 by default but it can be
    changed with the port parameter. If the token is not specified, it's
    generated and printed out to the console before the server starts
    running.

    Additionally, the file size limit and the chunk size range can be
    altered. The file size limit and minimum chunk size must be both be
    greater than 0, and maximum chunk size must be greater or equal to
    minimum chunk size.
    """

    if not token:
        token = generate_token()
        display_generated_token(token)

    try:
        server = Server(directory, token, file_size_limit, min_chunk_size,
                        max_chunk_size)
    except NotADirectoryError:
        print(INVALID_ROOT_DIRECTORY_MESSAGE)
        exit(1)
    except ValueError:
        print(INCORRECT_VALUE_ERROR_MESSAGE)
        exit(1)

    server.run('127.0.0.1', port)
Пример #2
0
    def setUp(self):
        # create remote working directory
        self.remote_directory = TemporaryDirectory()
        self.remote_directory_path = PosixPath(self.remote_directory.name)

        # start the server in an external thread
        self.server = Server(self.remote_directory_path, TOKEN)

        def server_loop(server):
            server.run(HOSTNAME, PORT)

        self.server_thread = threading.Thread(target=server_loop, args=(self.server,))
        self.server_thread.start()
Пример #3
0
def start_server(directory, port, token, pidfile, file_size_limit,
                 min_chunk_size, max_chunk_size):
    """ Start a daemonized server.

    This is a server-related command that start a daemonized server
    (detached from the shell). Unlike the run command, it accepts the
    --pidfile flag which tells the pidfile location. By default, the
    pidfile is created in the current working directory and named
    'daemon.pid'.

    Refer to the run command for more information.
    """

    if not token:
        token = generate_token()
        display_generated_token(token)

    try:
        server = Server(directory, token, file_size_limit, min_chunk_size,
                        max_chunk_size)
    except NotADirectoryError:
        print(INVALID_ROOT_DIRECTORY_MESSAGE)
        exit(1)
    except ValueError:
        print(INCORRECT_VALUE_ERROR_MESSAGE)
        exit(1)

    def loop():
        server.run('127.0.0.1', port)

    daemon = Daemon(loop, pidfile)
    daemon.start()
Пример #4
0
class TestClient(unittest.TestCase):
    """ Test the client class.

    It consists of testing its methods that implement all native file
    operations.
    """
    def setUp(self):
        # create local working directory
        self.local_directory = TemporaryDirectory()
        self.local_directory_path = PosixPath(self.local_directory.name)

        # create remote working directory
        self.remote_directory = TemporaryDirectory()
        self.remote_directory_path = PosixPath(self.remote_directory.name)

        # start the server in an external thread
        self.server = Server(self.remote_directory_path, TOKEN)

        def server_loop(server):
            server.run(HOSTNAME, PORT)

        self.server_thread = threading.Thread(target=server_loop,
                                              args=(self.server, ))
        self.server_thread.start()

        # change working direcory to local working directory
        self.last_working_directory = os.getcwd()
        os.chdir(self.local_directory_path)

    def tearDown(self):

        # restore current working directory
        os.chdir(self.last_working_directory)

        # terminate the server and wait until it terminates
        self.server.terminate()
        self.server_thread.join()

        # delete all testing contents in both local and remote working
        # directory
        self.local_directory.cleanup()
        self.remote_directory.cleanup()

    def _create_temporary_file(self, root, directory, name, size):
        """ Create a temporary file in the 'remote' directory.

        This function creates a temporary file with a given name in a
        given directory located in the temporary workinng 'remote'
        directory. It's filled with a given amount of random data.

        It returns a posix path of the created file and the generated
        data.
        """

        assert os.path.isabs(directory) == True
        directory = directory[1:]

        file_data = os.urandom(size)

        temporary_file_path = root / directory / name
        temporary_file = temporary_file_path.open('wb')
        temporary_file.write(file_data)
        temporary_file.close()

        return temporary_file_path, file_data

    def _create_temporary_directory(self, root, directory, name):
        """ Create a temporary directory in the 'remote' directory.

        This function creates a temporary directory with a given name
        in a given directory located in the temporary workinng 'remote'
        directory.

        It returns a posix path of the created directory.
        """

        assert os.path.isabs(directory) == True
        directory = directory[1:]

        temporary_directory_path = root / directory / name
        temporary_directory_path.mkdir(exist_ok=False)

        return temporary_directory_path

    def create_local_file(self, directory, name, size):
        return self._create_temporary_file(self.local_directory_path,
                                           directory, name, size)

    def create_local_directory(self, directory, name):
        return self._create_temporary_directory(self.local_directory_path,
                                                directory, name)

    def create_remote_file(self, directory, name, size):
        return self._create_temporary_file(self.remote_directory_path,
                                           directory, name, size)

    def create_remote_directory(self, directory, name):
        return self._create_temporary_directory(self.remote_directory_path,
                                                directory, name)

    def test_list_files(self):
        """ Test the list files method.

        It creates 'foo.bin' file and 'bar/' directory in the root
        directory and tests before and after if the correct dictionnary
        is returned.

        It ends with testing listing files with a relative path (which
        is not permitted) and listing files of a non-existing directory.
        """

        # create client instance
        client = Client(HOSTNAME, PORT, TOKEN)

        # test listing files of the empty root directory
        files = client.list_files('/')
        self.assertDictEqual(files, {})

        # test listing files of root directory  after creating 'foo.bin'
        # and 'bar/' in it
        file_path, _ = self.create_remote_file('/', 'foo.bin', 1052)
        directory_path = self.create_remote_directory('/', 'bar')

        expected_files = {
            'foo.bin': (False, 1052, file_path.stat().st_atime),
            'bar': (True, 0, directory_path.stat().st_atime)
        }

        files = client.list_files('/')
        self.assertDictEqual(files, expected_files)

        # test listing files with a relative directory
        with self.assertRaises(ValueError):
            client.list_files('bar')

        # test listing files of a non-existing directory
        with self.assertRaises(NotADirectoryError):
            client.list_files('/foo')

    def test_create_file(self):
        """ Test the create file method.

        It starts with creating a temporary working tree of files.

            /foo.bin
            /bar/qaz.txt

        Then, it attempts to create a file 'foo.bin' in 'bar' directory
        with invalid parameters to test each possible errors.

        It finishes with creating the file successfully and test if it
        has effectively been created.
        """

        # create client instance
        client = Client(HOSTNAME, PORT, TOKEN)

        # create remote working tree of files
        self.create_remote_file('/', 'foo.bin', 1052)
        self.create_remote_directory('/', 'bar')
        self.create_remote_file('/bar', 'qaz.txt', 431)

        # prepare common variables
        name = 'foo.bin'
        directory = '/bar'

        # test creating a file with an invalid name
        invalid_names = ['*baz', 'b"az', 'baz|']

        for invalid_name in invalid_names:
            with self.assertRaises(InvalidFileName):
                client.create_file(invalid_name, directory)

        # test creating a file with a conflicting name
        invalid_directory = '/'

        with self.assertRaises(FileExistsError):
            client.create_file(name, invalid_directory)

        # test creating a file with a relative directory
        invalid_directory = 'bar'

        with self.assertRaises(ValueError):
            client.create_file(name, invalid_directory)

        # test creating a file with a non-existing directory
        invalid_directory = '/foo'

        with self.assertRaises(NotADirectoryError):
            client.create_file(name, invalid_directory)

        # test creating a file with valid parameters
        created_file = self.remote_directory_path / directory[1:] / name
        self.assertFalse(created_file.exists())

        client.create_file(name, directory)

        self.assertTrue(created_file.exists())
        self.assertTrue(created_file.is_file())

    def test_make_directories(self):
        """ Test the make directory method.

        It starts with creating a temporary working tree of files.

            /foo/
            /bar/qaz/

        Then, it attempts to create a directory 'foo/' in 'bar/'
        directory with invalid parameters to test each possible errors.

        It finishes with creating the directory successfully and test
        if it has effectively been created.
        """

        # create client instance
        client = Client(HOSTNAME, PORT, TOKEN)

        # create remote working tree of files
        self.create_remote_directory('/', 'foo')
        self.create_remote_directory('/', 'bar')
        self.create_remote_directory('/', 'qaz')

        # prepare common variables
        name = 'foo'
        directory = '/bar'

        # test making a directory with an invalid name
        invalid_names = ['*baz', 'b"az', 'baz|']

        for invalid_name in invalid_names:
            with self.assertRaises(InvalidFileName):
                client.make_directory(invalid_name, directory)

        # test making a directory with a conflicting name
        invalid_directory = '/'

        with self.assertRaises(FileExistsError):
            client.make_directory(name, invalid_directory)

        # test making a directory with a relative directory
        invalid_directory = 'bar'

        with self.assertRaises(ValueError):
            client.make_directory(name, invalid_directory)

        # test making a directory with a non-existing directory
        invalid_directory = '/foo/bar'

        with self.assertRaises(NotADirectoryError):
            client.make_directory(name, invalid_directory)

        # test making a directory with valid parameters
        created_directory = self.remote_directory_path / directory[1:] / name
        self.assertFalse(created_directory.exists())

        client.make_directory(name, directory)

        self.assertTrue(created_directory.exists())
        self.assertTrue(created_directory.is_dir())

    def test_upload_file(self):
        """ Test the upload file method.

        It starts with creating a local working directory and remote
        working directory as follow.

        Local working directory.

            foo.bin

        Remote working directory.

            bar/
            qaz/foo.bin

        Then it attempts to upload the local 'foo.bin' file to the
        remote 'bar/' directory. It does it by testing different foobar.

        It finishes with barfoo.
        """

        # create client instance
        client = Client(HOSTNAME, PORT, TOKEN)

        # create local working tree of files
        _, file_data = self.create_local_file('/', 'foo.bin', 1052)

        # create remote working tree of files
        self.create_remote_directory('/', 'bar')
        self.create_remote_directory('/', 'qaz')
        self.create_remote_file('/qaz', 'foo.bin', 1052)

        # prepare common variables
        source = 'foo.bin'
        destination = '/bar'
        name = None
        chunk_size = 512
        process_chunk = None

        # prepare assert routines
        def assert_file_not_uploaded(source, destination, name=None):
            if not name:
                name = PurePosixPath(source).name

            uploaded_file_path = self.remote_directory_path / destination[
                1:] / name
            self.assertFalse(uploaded_file_path.exists())

        def assert_file_uploaded(source, destination, name=None):
            if not name:
                name = PurePosixPath(source).name

            uploaded_file_path = self.remote_directory_path / destination[
                1:] / name
            self.assertTrue(uploaded_file_path.is_file())

            uploaded_file = uploaded_file_path.open('rb')
            self.assertEqual(uploaded_file.read(), file_data)
            uploaded_file.close()

        def delete_uploaded_file(source, destination, name=None):
            if not name:
                name = PurePosixPath(source).name

            uploaded_file_path = self.remote_directory_path / destination[
                1:] / name
            os.remove(uploaded_file_path)

        # test uploading a file with source is a relative path
        assert_file_not_uploaded(source, destination, name)

        client.upload_file(source, destination, name, chunk_size,
                           process_chunk)

        assert_file_uploaded(source, destination, name)
        delete_uploaded_file(source, destination, name)

        # test uploading a file with source is an absolute path
        assert_file_not_uploaded(source, destination, name)

        client.upload_file(self.local_directory_path / source, destination,
                           name, chunk_size, process_chunk)

        assert_file_uploaded(source, destination, name)
        delete_uploaded_file(source, destination, name)

        # test uploading a file with changing its name
        assert_file_not_uploaded(source, destination, 'bar.bin')

        client.upload_file(source, destination, 'bar.bin', chunk_size,
                           process_chunk)

        assert_file_uploaded(source, destination, 'bar.bin')
        delete_uploaded_file(source, destination, 'bar.bin')

        # test uploading a file with a source file that doesn't exist
        # (todo: make 2 versions of it, one with source exists but is
        # not a file)
        with self.assertRaises(SourceNotFound):
            client.upload_file('foo.txt', destination, name, chunk_size,
                               process_chunk)

        assert_file_not_uploaded(source, destination, 'foo.txt')

        # test uploading a file with destination being a relative path
        with self.assertRaises(ValueError):
            client.upload_file(source, 'bar', name, chunk_size, process_chunk)

        assert_file_not_uploaded(source, '/bar', name)

        # test uploading a file with a destination directory that
        # doesn't exist (todo: make 2 versions of it, one with
        # destination exist but is not a directory)
        with self.assertRaises(DestinationNotFound):
            client.upload_file(source, '/foo', name, chunk_size, process_chunk)

        assert_file_not_uploaded(source, '/foo', name)

        # test uploading a file with a name that conflicts with an
        # existing file in the destination directory
        with self.assertRaises(FileExistsError):
            client.upload_file(source, '/qaz', name, chunk_size, process_chunk)

        # test uploading a file with an invalid chunk size
        with self.assertRaises(ValueError):
            client.upload_file(source, destination, name, 0, process_chunk)

        assert_file_not_uploaded(source, destination, name)

        # test uploading a file with a custom process chunk callback
        expected_chunk_data = (file_data[:512], file_data[512:1024],
                               file_data[1024:])
        expected_remaining_bytes = (1052, 1052 - 512, 1052 - 512 - 512)

        def custom_process_chunk(chunk_data, remaining_bytes, file_size,
                                 file_name):
            self.assertEqual(chunk_data,
                             expected_chunk_data[custom_process_chunk.counter])
            self.assertEqual(
                remaining_bytes,
                expected_remaining_bytes[custom_process_chunk.counter])
            self.assertEqual(file_size, 1052)

            custom_process_chunk.counter += 1

            return True

        custom_process_chunk.counter = 0

        assert_file_not_uploaded(source, destination, name)

        client.upload_file(source, destination, name, 512,
                           custom_process_chunk)
        self.assertEqual(custom_process_chunk.counter, 3)

        assert_file_uploaded(source, destination, name)
        delete_uploaded_file(source, destination, name)

        # test uploading a file with a custom process chunk callback
        # that interupts the upload
        def custom_process_chunk(chunk_data, remaining_bytes, file_size,
                                 file_name):
            custom_process_chunk.counter += 1

            if custom_process_chunk.counter == 1:
                return False

            return True

        custom_process_chunk.counter = 0

        client.upload_file(source, destination, name, chunk_size,
                           custom_process_chunk)
        assert_file_not_uploaded(source, destination, name)

        # test uploading a file with chunk size greater than the file
        # size being uploaded
        assert_file_not_uploaded(source, destination, name)

        client.upload_file(source, destination, name, 4096, process_chunk)

        assert_file_uploaded(source, destination, name)
        delete_uploaded_file(source, destination, name)

        # test uploading a file again to ensure the previous operations
        # didn't corrupt the server state
        assert_file_not_uploaded(source, destination, name)

        client.upload_file(source, destination, name, chunk_size,
                           process_chunk)

        assert_file_uploaded(source, destination, name)
        delete_uploaded_file(source, destination, name)

    def test_upload_directory(self):
        """ Test the upload directory method.

        It starts with creating a local working directory and remote
        working directory as follow.

        Local working directory.

            foo/
                bar.bin
                qaz/xyz.img

        Remote working directory.

            foo/
            bar/

        Then, it attempts to upload the 'foo/' local directory to the
        'bar/' remote directory by trying different erroueonous versions.

        It finishes with foobar.

        To be written.
        """

        # create client instance
        client = Client(HOSTNAME, PORT, TOKEN)

        # create local working directory
        self.create_local_directory('/', 'foo')
        _, bar_file_data = self.create_local_file('/foo', 'bar.bin', 1052)
        self.create_local_directory('/foo', 'qaz')
        _, xyz_file_data = self.create_local_file('/foo/qaz', 'xyz.img', 321)

        # create remote working directory
        self.create_remote_directory('/', 'foo')
        self.create_remote_directory('/', 'bar')

        # prepare common variables
        source = 'foo'
        destination = '/bar'
        name = None
        chunk_size = 512
        process_chunk = None

        # prepare assert routines
        def assert_directory_not_uploaded(source, destination, name=None):
            if not name:
                name = PosixPath(source).name

            uploaded_directory_path = self.remote_directory_path / destination[
                1:] / name
            self.assertFalse(uploaded_directory_path.exists())

        def assert_directory_uploaded(source, destination, name=None):
            if not name:
                name = PosixPath(source).name

            # assert foo/
            foo_directory_path = self.remote_directory_path / destination[
                1:] / name
            self.assertTrue(foo_directory_path.is_dir())

            # assert foo/bar.bin
            bar_file_path = self.remote_directory_path / destination[
                1:] / name / 'bar.bin'
            self.assertTrue(bar_file_path.is_file())
            self.assertEqual(bar_file_path.open('rb').read(), bar_file_data)

            # assert foo/qaz
            qaz_directory_path = self.remote_directory_path / destination[
                1:] / name / 'qaz'
            self.assertTrue(qaz_directory_path.is_dir())

            # assert foo/qaz/xyz.img
            xyz_file_path = self.remote_directory_path / destination[
                1:] / name / 'qaz/xyz.img'
            self.assertTrue(xyz_file_path.is_file())
            self.assertEqual(xyz_file_path.open('rb').read(), xyz_file_data)

        def delete_uploaded_directory(source, destination, name=None):
            if not name:
                name = PosixPath(source).name

            uploaded_directory_path = self.remote_directory_path / destination[
                1:] / name
            shutil.rmtree(uploaded_directory_path)

        # test uploading a directory with source is an relative path
        assert_directory_not_uploaded(source, destination, name)
        client.upload_directory(self.local_directory_path / source,
                                destination, name, chunk_size, process_chunk)
        assert_directory_uploaded(source, destination, name)

        delete_uploaded_directory(source, destination, name)

        # test uploading a directory with source is an absolute path
        assert_directory_not_uploaded(source, destination, name)
        client.upload_directory(source, destination, name, chunk_size,
                                process_chunk)
        assert_directory_uploaded(source, destination, name)

        delete_uploaded_directory(source, destination, name)

        # test uploading a directory with changing its name
        assert_directory_not_uploaded(source, destination, 'qux')
        client.upload_directory(source, destination, 'qux', chunk_size,
                                process_chunk)
        assert_directory_uploaded(source, destination, 'qux')

        delete_uploaded_directory(source, destination, 'qux')

        # test uploading a directory with a source directory that
        # doesn't exist (todo: make 2 versions of it, one with source
        # exists but is not a directory)
        with self.assertRaises(SourceNotFound):
            client.upload_directory('qaz', destination, name, chunk_size,
                                    process_chunk)

        # test uploading a directory with destination being a relative
        # path
        with self.assertRaises(ValueError):
            client.upload_directory(source, 'bar', name, chunk_size,
                                    process_chunk)

        # test uploading a directory with a destination directory that
        # doesn't exist (todo: make 2 versions of it, one with
        # destination exists but is not a directory)
        with self.assertRaises(DestinationNotFound):
            client.upload_directory(source, '/qaz', name, chunk_size,
                                    process_chunk)

        # test uploading a directory with an invalid chunk size
        with self.assertRaises(ValueError):
            client.upload_directory(source, destination, name, 0,
                                    process_chunk)

        # test uploading the directory again to ensure the previous
        # operations didn't correup the server state
        assert_directory_not_uploaded(source, destination, name)
        client.upload_directory(source, destination, name, chunk_size,
                                process_chunk)

        assert_directory_uploaded(source, destination, name)
        delete_uploaded_directory(source, destination, name)

    def test_download_file(self):
        """ Test the download file method.

        It starts with creating a local working directory and remote
        working directory as follow.

        Local working directory.

            /bar/foo.bin
             qaz/

        Remote working directory.

            /bar/foo.bin

        Then it attempts to download the remote 'foo.bin' file to the
        local 'qaz/' directory. It does it by testing different foobar.

        It finishes with barfoo.
        """

        # create client instance
        client = Client(HOSTNAME, PORT, TOKEN)

        # create local working tree of files
        self.create_local_directory('/', 'bar')
        self.create_local_directory('/', 'qaz')
        self.create_local_file('/bar', 'foo.bin', 1052)

        # create remote working tree of files
        self.create_remote_directory('/', 'bar')
        _, file_data = self.create_remote_file('/bar', 'foo.bin', 1052)

        # prepare common variables
        source = '/bar/foo.bin'
        destination = 'qaz'
        name = None
        chunk_size = 512
        process_chunk = None

        # prepare assert routines
        def assert_file_not_downloaded(source, destination, name=None):
            if not name:
                name = PurePosixPath(source).name

            downloaded_file_path = self.local_directory_path / destination / name
            self.assertFalse(downloaded_file_path.exists())

        def assert_file_downloaded(source, destination, name=None):
            if not name:
                name = PurePosixPath(source).name

            downloaded_file_path = self.local_directory_path / destination / name
            self.assertTrue(downloaded_file_path.is_file())

            downloaded_file = downloaded_file_path.open('rb')
            self.assertEqual(downloaded_file.read(), file_data)
            downloaded_file.close()

        def delete_downloaded_file(source, destination, name=None):
            if not name:
                name = PurePosixPath(source).name

            downloaded_file_path = self.local_directory_path / destination / name
            os.remove(downloaded_file_path)

        # test downloading a file with destination is a relative path
        assert_file_not_downloaded(source, destination, name)

        client.download_file(source, destination, name, chunk_size,
                             process_chunk)

        assert_file_downloaded(source, destination, name)
        delete_downloaded_file(source, destination, name)

        # test downloading a file with destination is an absolute path
        assert_file_not_downloaded(source, destination, name)

        client.download_file(source, self.local_directory_path / destination,
                             name, chunk_size, process_chunk)

        assert_file_downloaded(source, destination, name)
        delete_downloaded_file(source, destination, name)

        # test downloading a file with changing its name
        assert_file_not_downloaded(source, destination, 'bar.bin')

        client.download_file(source, destination, 'bar.bin', chunk_size,
                             process_chunk)

        assert_file_downloaded(source, destination, 'bar.bin')
        delete_downloaded_file(source, destination, 'bar.bin')

        # test downloading a file with source being a relative path
        with self.assertRaises(ValueError):
            client.download_file('bar/foo.bin', destination, name, chunk_size,
                                 process_chunk)

        assert_file_not_downloaded('bar/foo.bin', destination, name)

        # test downloading a file with a source file that doesn't exist
        # (todo: make 2 versions of it, one with source exists but is
        # not a file)
        with self.assertRaises(SourceNotFound):
            client.download_file('/foo.bin', destination, name, chunk_size,
                                 process_chunk)

        assert_file_not_downloaded('/foo.bin', destination, name)

        # test downloading a file with a destination directory that
        # doesn't exist (todo: make 2 versions of it, one with
        # destination exist but is not a directory)
        with self.assertRaises(DestinationNotFound):
            client.download_file(source, 'foo', name, chunk_size,
                                 process_chunk)

        assert_file_not_downloaded(source, 'foo', name)

        # test downloading a file with a name that conflicts with an
        # existing file in the destination directory
        with self.assertRaises(FileExistsError):
            client.download_file(source, 'bar', name, chunk_size,
                                 process_chunk)

        # test downloading a file with an invalid chunk size
        with self.assertRaises(ValueError):
            client.download_file(source, destination, name, 0, process_chunk)

        assert_file_not_downloaded(source, destination, name)

        # test downloading a file with a custom process chunk callback
        pass

        # test downloading a file with a custom process chunk callback
        # that interupts the upload
        pass

        # test downloading a file with chunk size greater than the file
        # size being uploaded
        pass

        # test downloading a file again to ensure the previous operations
        # didn't corrupt the server state
        pass

    def test_download_directory(self):
        """ Test the download directory method.

        Local working directory.

            bar/
            qaz/foo

        Remote working directory.

            foo/
                bar.bin
                qaz/xyz.img

        Then, it attempts to download the 'foo/' remote directory to the
        'bar/' remote directory by trying different erroueonous versions.

        To be written.
        """

        # create client instance
        client = Client(HOSTNAME, PORT, TOKEN)

        # create local working tree of files
        self.create_local_directory('/', 'bar')
        self.create_local_directory('/', 'qaz')
        self.create_local_directory('/qaz', 'foo')

        # create remote working tree of files
        self.create_remote_directory('/', 'foo')
        _, bar_file_data = self.create_remote_file('/foo', 'bar.bin', 1052)
        self.create_remote_directory('/foo', 'qaz')
        _, xyz_file_data = self.create_remote_file('/foo/qaz', 'xyz.img', 312)

        # prepare assert routines
        def assert_directory_not_downloaded(source, destination, name=None):
            if not name:
                name = PurePosixPath(source).name

            downloaded_directory_path = self.local_directory_path / destination / name
            self.assertFalse(downloaded_directory_path.exists())

        def assert_directory_downloaded(source, destination, name=None):
            if not name:
                name = PurePosixPath(source).name

            # assert foo/
            foo_directory_path = self.local_directory_path / destination / name
            self.assertTrue(foo_directory_path.is_dir())

            # assert foo/bar.bin
            bar_file_path = self.local_directory_path / destination / name / 'bar.bin'
            self.assertTrue(bar_file_path.is_file())
            self.assertEqual(bar_file_path.open('rb').read(), bar_file_data)

            # assert foo/qaz
            qaz_directory_path = self.local_directory_path / destination / name / 'qaz'
            self.assertTrue(qaz_directory_path.is_dir())

            # assert foo/qaz/xyz.img
            xyz_file_path = self.local_directory_path / destination / name / 'qaz/xyz.img'
            self.assertTrue(xyz_file_path.is_file())
            self.assertEqual(xyz_file_path.open('rb').read(), xyz_file_data)

        def delete_downloaded_directory(source, destination, name=None):
            if not name:
                name = PurePosixPath(source).name

            downloaded_directory_path = self.local_directory_path / destination / name
            shutil.rmtree(downloaded_directory_path)

        # prepare common variables
        source = '/foo'
        destination = 'bar'
        name = None
        chunk_size = 512
        process_chunk = None

        # test downloading a directory with destination is a relative path
        assert_directory_not_downloaded(source, destination, name)

        client.download_directory(source, destination, name, chunk_size,
                                  process_chunk)

        assert_directory_downloaded(source, destination, name)
        delete_downloaded_directory(source, destination, name)

        # test downloading a directory with destination is an absolute path
        assert_directory_not_downloaded(source, destination, name)

        client.download_directory(source,
                                  self.local_directory_path / destination,
                                  name, chunk_size, process_chunk)

        assert_directory_downloaded(source, destination, name)
        delete_downloaded_directory(source, destination, name)

        # test downloading a directory with changing its name
        assert_directory_not_downloaded(source, destination, 'bar')

        client.download_directory(source, destination, 'bar', chunk_size,
                                  process_chunk)

        assert_directory_downloaded(source, destination, 'bar')
        delete_downloaded_directory(source, destination, 'bar')

        # test downloading a directory with source being a relative path
        with self.assertRaises(ValueError):
            client.download_directory('foo', destination, name, chunk_size,
                                      process_chunk)

        assert_directory_not_downloaded(source, destination, name)

        # test downloading a directory with a source directory that doesn't exist
        # (todo: make 2 versions of it, one with source exists but is
        # not a file)
        with self.assertRaises(SourceNotFound):
            client.download_directory('/bar', destination, name, chunk_size,
                                      process_chunk)

        # test downloading a directory with a destination directory that
        # doesn't exist (todo: make 2 versions of it, one with
        # destination exist but is not a directory)
        with self.assertRaises(DestinationNotFound):
            client.download_directory(source, 'foo', name, chunk_size,
                                      process_chunk)

        assert_directory_not_downloaded(source, 'foo', name)

        # test downloading a directory with a name that conflicts with an
        # existing file in the destination directory
        with self.assertRaises(FileExistsError):
            client.download_directory(source, 'qaz', name, chunk_size,
                                      process_chunk)

        # test downloading a directory with an invalid chunk size
        with self.assertRaises(ValueError):
            client.download_directory(source, destination, name, 0,
                                      process_chunk)

        assert_directory_not_downloaded(source, destination, name)

        # test downloading a directory again to ensure the previous operations
        # didn't corrupt the server state
        assert_directory_not_downloaded(source, destination, name)

        client.download_directory(source, destination, name, chunk_size,
                                  process_chunk)

        assert_directory_downloaded(source, destination, name)
        delete_downloaded_directory(source, destination, name)
Пример #5
0
class TestProtocol(unittest.TestCase):
    """ Test the transfer file protocol.

    It consists of testing all request methods and their possible
    responses.
    """

    def setUp(self):
        # create remote working directory
        self.remote_directory = TemporaryDirectory()
        self.remote_directory_path = PosixPath(self.remote_directory.name)

        # start the server in an external thread
        self.server = Server(self.remote_directory_path, TOKEN)

        def server_loop(server):
            server.run(HOSTNAME, PORT)

        self.server_thread = threading.Thread(target=server_loop, args=(self.server,))
        self.server_thread.start()

    def tearDown(self):
        # terminate the server and wait until it terminates
        self.server.terminate()
        self.server_thread.join()

        # delete all testing contents in served directory
        self.remote_directory.cleanup()

    def create_temporary_file(self, directory, name, size):
        """ Create a temporary file in the 'remote' directory.

        This function creates a temporary file with a given name in a
        given directory located in the temporary workinng 'remote'
        directory. It's filled with a given amount of random data.

        It returns a posix path of the created file and the generated
        data.
        """

        assert os.path.isabs(directory) == True
        directory = directory[1:]

        file_data = os.urandom(size)

        temporary_file_path = self.remote_directory_path / directory / name
        temporary_file = temporary_file_path.open('wb')
        temporary_file.write(file_data)
        temporary_file.close()

        return temporary_file_path, file_data

    def create_temporary_directory(self, directory, name):
        """ Create a temporary directory in the 'remote' directory.

        This function creates a temporary directory with a given name
        in a given directory located in the temporary workinng 'remote'
        directory.

        It returns a posix path of the created directory.
        """

        assert os.path.isabs(directory) == True
        directory = directory[1:]

        temporary_directory_path = self.remote_directory_path / directory / name
        temporary_directory_path.mkdir(exist_ok=False)

        return temporary_directory_path

    def test_list_files_request(self):
        """ Test the LIST_FILE request.

        It starts with constructing the following working tree and
        subsequently tests during the creation if listing files of
        these two folders return the correct list of files.

            /foo.bin
             bar/qaz.txt

        It ends with testing listing files of a non-existing directory.
        """

        # create socket and connect to the server
        context = zmq.Context.instance()

        socket = context.socket(zmq.REQ)
        socket.setsockopt(zmq.IDENTITY, bytes(TOKEN, 'utf-8'))
        socket.connect('tcp://{0}:{1}'.format(HOSTNAME, str(PORT)))

        # test listing files of (empty) root directory
        expected_response = (Response.ACCEPTED, Reason.FILES_LISTED, {})

        for list_directory in ('/', ''):
            request = make_list_files_request(list_directory)
            socket.send_pyobj(request)

            response = socket.recv_pyobj()
            self.assertTupleEqual(response, expected_response)

        # test listing files of root directory  after creating 'foo.bin'
        # and 'bar/' in it
        file_path, _ = self.create_temporary_file('/', 'foo.bin', 1052)
        directory_path = self.create_temporary_directory('/', 'bar')

        expected_files_list= {
            'foo.bin' : (False, 1052, file_path.stat().st_atime),
            'bar'     : (True,     0, directory_path.stat().st_atime)
        }

        for list_directory in ('/', ''):
            request = make_list_files_request(list_directory)
            socket.send_pyobj(request)

            response = socket.recv_pyobj()

            response_type, reason_type, files_list = response

            self.assertEqual(response_type, Response.ACCEPTED)
            self.assertEqual(reason_type, Reason.FILES_LISTED)
            self.assertDictEqual(files_list, expected_files_list)

        # test listing files of (empty) bar/ directory
        expected_response = (Response.ACCEPTED, Reason.FILES_LISTED, {})

        for list_directory in ('/bar', 'bar'):
            request = make_list_files_request(list_directory)
            socket.send_pyobj(request)

            response = socket.recv_pyobj()
            self.assertTupleEqual(response, expected_response)

        # test listing files of bar/ directory after creating qaz.txt in
        # it
        file_path, _ = self.create_temporary_file('/bar', 'qaz.txt', 1024)

        expected_files_list= {
            'qaz.txt' : (False, 1024, file_path.stat().st_atime),
        }

        for list_directory in ('/bar', 'bar'):
            request = make_list_files_request(list_directory)
            socket.send_pyobj(request)

            response = socket.recv_pyobj()

            response_type, reason_type, files_list = response
            self.assertEqual(response_type, Response.ACCEPTED)
            self.assertEqual(reason_type, Reason.FILES_LISTED)
            self.assertDictEqual(files_list, expected_files_list)

        # test listing files of a non-existing directory
        expected_response = (Response.REFUSED, Reason.NOT_A_DIRECTORY)

        for list_directory in ('/foo', 'foo'):
            request = make_list_files_request(list_directory)
            socket.send_pyobj(request)

            response = socket.recv_pyobj()
            self.assertTupleEqual(response, expected_response)

    def test_create_file_request(self):
        """ Test the CREATE_FILE request.

        It starts with creating a temporary working tree of files.

          foo/
            bar.bin

        Then, it attemps to create a file 'qaz.bin' in 'foo/' directory.

        But before, it tests the create file  request by sending several
        invalid requests.

          * Send a bad request
          * Send the three invalid requests expecting the three variant of refused responses

        The last step is sending a valid request and check if the file
        has effectively been created at the right location.
        """

        # create socket and connect to the server
        context = zmq.Context.instance()

        socket = context.socket(zmq.REQ)
        socket.setsockopt(zmq.IDENTITY, bytes(TOKEN, 'utf-8'))
        socket.connect('tcp://{0}:{1}'.format(HOSTNAME, str(PORT)))

        # create temporary working tree of files
        self.create_temporary_directory('/', 'foo')
        self.create_temporary_file('/foo', 'bar.bin', 1024)

        # prepare common variables
        name       = 'qaz.bin'
        directory  = '/foo'

        # test sending invalid create file request because it's badly
        # formatted
        expected_response = (Response.ERROR, Reason.BAD_REQUEST)

        request = make_bad_request(Request.CREATE_FILE)
        socket.send_pyobj(request)

        response = socket.recv_pyobj()
        self.assertTupleEqual(response, expected_response)

        # test sending invalid create file request because the file name
        # is invalid
        invalid_names = ['*baz', 'b"az', 'baz|']
        expected_response = (Response.REFUSED, Reason.INVALID_FILE_NAME)

        for invalid_name in invalid_names:
            request = make_create_file_request(invalid_name, directory)
            socket.send_pyobj(request)

            response = socket.recv_pyobj()
            self.assertTupleEqual(response, expected_response)

        # test sending invalid create file request because it has a
        # non-existing directory
        incorrect_directory = '/bar'
        expected_response = (Response.REFUSED, Reason.NOT_A_DIRECTORY)

        request = make_create_file_request(name, incorrect_directory)
        socket.send_pyobj(request)

        response = socket.recv_pyobj()
        self.assertTupleEqual(response, expected_response)

        # test sending invalid create file request because it the file
        # name conflicts with an existing file (or directory)
        incorrect_name = 'bar.bin'
        expected_response = (Response.REFUSED, Reason.FILE_ALREADY_EXISTS)

        request = make_create_file_request(incorrect_name, directory)
        socket.send_pyobj(request)

        response = socket.recv_pyobj()
        self.assertTupleEqual(response, expected_response)

        # test sending valid create file request and test if the file
        # has effectively been created before and after the request
        expected_response = (Response.ACCEPTED, Reason.FILE_CREATED)

        created_file_path = self.remote_directory_path / directory[1:] / name
        self.assertFalse(created_file_path.exists())

        request = make_create_file_request(name, directory)
        socket.send_pyobj(request)

        response = socket.recv_pyobj()
        self.assertTupleEqual(response, expected_response)

        self.assertTrue(created_file_path.is_file())

    def test_make_directory_request(self):
        """ Test the MAKE_DIRECTORY request.

        It starts with creating a temporary working tree of files.

          foo/
            bar/

        Then, it attemps to create a directory 'qaz/' in 'foo/'
        directory.

        But before, it tests the create directory request by sending
        several invalid requests.

          - Send a bad request
          - Send the three invalid requests expecting the three variant of refused responses

        The last step is sending a valid request and check if the
        directory has effectively been created at the right location.
        """

        # create socket and connect to the server
        context = zmq.Context.instance()

        socket = context.socket(zmq.REQ)
        socket.setsockopt(zmq.IDENTITY, bytes(TOKEN, 'utf-8'))
        socket.connect('tcp://{0}:{1}'.format(HOSTNAME, str(PORT)))

        # create temporary working tree of files
        self.create_temporary_directory('/', 'foo')
        self.create_temporary_directory('/foo', 'bar')

        # prepare common variables
        name      = 'qaz'
        directory = '/foo'

        # test sending invalid make directory request because it's badly
        # formatted
        expected_response = (Response.ERROR, Reason.BAD_REQUEST)

        request = make_bad_request(Request.MAKE_DIRECTORY)
        socket.send_pyobj(request)

        response = socket.recv_pyobj()
        self.assertTupleEqual(response, expected_response)

        # test sending invalid make directory request because the
        # directory name is incorrect
        invalid_names = ['*baz', 'b"az', 'baz|']
        expected_response = (Response.REFUSED, Reason.INVALID_FILE_NAME)

        for invalid_name in invalid_names:
            request = make_make_directory_request(invalid_name, directory)
            socket.send_pyobj(request)

            response = socket.recv_pyobj()
            self.assertTupleEqual(response, expected_response)

        # test sending invalid make directory request because it has a
        # non-existing directory
        incorrect_directory = '/bar'
        expected_response = (Response.REFUSED, Reason.NOT_A_DIRECTORY)

        request = make_make_directory_request(name, incorrect_directory)
        socket.send_pyobj(request)

        response = socket.recv_pyobj()
        self.assertTupleEqual(response, expected_response)

        # test sending invalid make directory request because the
        # directory name conflicts with an existing directory (or file)
        incorrect_name = 'bar'
        expected_response = (Response.REFUSED, Reason.FILE_ALREADY_EXISTS)

        request = make_make_directory_request(incorrect_name, directory)
        socket.send_pyobj(request)

        response = socket.recv_pyobj()
        self.assertTupleEqual(response, expected_response)

        # test sending valid make directory request and check if the
        # directory has effectively been created at the right location
        expected_response = (Response.ACCEPTED, Reason.DIRECTORY_CREATED)

        created_directory_path = self.remote_directory_path / directory[1:] / name
        self.assertFalse(created_directory_path.exists())

        request = make_make_directory_request(name, directory)
        socket.send_pyobj(request)

        response = socket.recv_pyobj()
        self.assertTupleEqual(response, expected_response)

        self.assertTrue(created_directory_path.is_dir())

    def test_upload_file_request(self):
        """ Test the upload request-response cycle.

        It starts with creating a temporary working tree of files.

          foo/
            bar.bin

        Then, it attempts to upload a file 'qaz.bin' to the 'foo/'
        directory.

        But before, it tests the upload request by sending several
        invalid requests.

          - Send a bad request
          - Send the four invalid requests expecting the four variant of refused responses
          - Send a valid request, but send invalid chunks later
          - Send a valid request, but cancel transfer later

        The last step is sending a valid request and send send chunk
        requests until completion of the upload.

        It finishes with testing if the uploaded file has effectively
        been created at the right location and has the correct binary
        content.
        """

        # create socket and connect to the server
        context = zmq.Context.instance()

        socket = context.socket(zmq.REQ)
        socket.setsockopt(zmq.IDENTITY, bytes(TOKEN, 'utf-8'))
        socket.connect('tcp://{0}:{1}'.format(HOSTNAME, str(PORT)))

        # create temporary working tree of files
        self.create_temporary_directory('/', 'foo')
        self.create_temporary_file('/foo', 'bar.bin', 1024)

        # prepare common variables
        name       = 'qaz.bin'
        directory  = '/foo'
        file_size  = 1052
        chunk_size = 512

        # test sending invalid upload file request because it's badly
        # formatted
        expected_response = (Response.ERROR, Reason.BAD_REQUEST)

        request = make_bad_request(Request.UPLOAD_FILE)
        socket.send_pyobj(request)

        response = socket.recv_pyobj()
        self.assertTupleEqual(response, expected_response)

        # test sending invalid upload file request because it's has a
        # non-existing directory
        incorrect_directory = '/bar'
        expected_response = (Response.REFUSED, Reason.NOT_A_DIRECTORY)

        request = make_upload_file_request(name, incorrect_directory, file_size, chunk_size)
        socket.send_pyobj(request)

        response = socket.recv_pyobj()

        self.assertTupleEqual(response, expected_response)

        # test sending invalid upload file request because a file with
        # that name already exists
        incorrect_name = 'bar.bin'
        expected_response = (Response.REFUSED, Reason.FILE_ALREADY_EXISTS)

        request = make_upload_file_request(incorrect_name, directory, file_size, chunk_size)
        socket.send_pyobj(request)

        response = socket.recv_pyobj()
        self.assertTupleEqual(response, expected_response)

        # test sending invalid upload file request because the file size
        # is incorrect
        incorrect_file_sisze = 0
        expected_response = (Response.REFUSED, Reason.INCORRECT_FILE_SIZE)

        request = make_upload_file_request(name, directory, incorrect_file_sisze, chunk_size)
        socket.send_pyobj(request)

        response = socket.recv_pyobj()
        self.assertTupleEqual(response, expected_response)

        # test sending invalid upload file request because it has an
        # incorrect chunk size
        incorrect_chunk_size = 0
        expected_response = (Response.REFUSED, Reason.INCORRECT_CHUNK_SIZE)

        request = make_upload_file_request(name, directory, file_size, incorrect_chunk_size)
        socket.send_pyobj(request)

        response = socket.recv_pyobj()
        self.assertTupleEqual(response, expected_response)

        # test sending valid upload file request, then send one valid
        # chunk followed by one invalid chunk
        valid_chunk_data   = bytes(chunk_size)
        invalid_chunk_data = bytes(chunk_size - 1)

        expected_responses = (
            (Response.ACCEPTED, Reason.CHUNK_RECEIVED),
            (Response.ERROR, Reason.BAD_REQUEST)
        )

        request = make_upload_file_request(name, directory, file_size, chunk_size)
        socket.send_pyobj(request)

        response = socket.recv_pyobj()
        self.assertTupleEqual(response, (Response.ACCEPTED, Reason.TRANSFER_ACCEPTED))

        request = make_send_chunk_request(valid_chunk_data)
        socket.send_pyobj(request)

        response = socket.recv_pyobj()
        self.assertTupleEqual(response, expected_responses[0])

        request = make_send_chunk_request(invalid_chunk_data)
        socket.send_pyobj(request)

        response = socket.recv_pyobj()

        self.assertTupleEqual(response, expected_responses[1])

        # test sending valid upload file request, then send one valid
        # chunk followed by a cancel transfer request
        chunk_data = bytes(chunk_size)

        expected_responses = (
            (Response.ACCEPTED, Reason.CHUNK_RECEIVED),
            (Response.ACCEPTED, Reason.TRANSFER_CANCELLED)
        )

        request = make_upload_file_request(name, directory, file_size, chunk_size)
        socket.send_pyobj(request)

        response = socket.recv_pyobj()
        self.assertTupleEqual(response, (Response.ACCEPTED, Reason.TRANSFER_ACCEPTED))

        request = make_send_chunk_request(chunk_data)
        socket.send_pyobj(request)

        response = socket.recv_pyobj()
        self.assertTupleEqual(response, expected_responses[0])

        request = make_cancel_transfer_request()
        socket.send_pyobj(request)

        response = socket.recv_pyobj()
        self.assertTupleEqual(response, expected_responses[1])

        # test sending valid upload file request, send valid chunks
        # until completion of upload
        file_data = os.urandom(file_size)
        chunks = (file_data[:512], file_data[512:1024], file_data[1024:])

        expected_responses = (
            (Response.ACCEPTED, Reason.CHUNK_RECEIVED),
            (Response.ACCEPTED, Reason.CHUNK_RECEIVED),
            (Response.ACCEPTED, Reason.TRANSFER_COMPLETED)
        )

        request = make_upload_file_request(name, directory, file_size, chunk_size)
        socket.send_pyobj(request)

        response = socket.recv_pyobj()
        self.assertTupleEqual(response, (Response.ACCEPTED, Reason.TRANSFER_ACCEPTED))

        for i in range(3):
            request = make_send_chunk_request(chunks[i])
            socket.send_pyobj(request)

            response = socket.recv_pyobj()
            self.assertTupleEqual(response, expected_responses[i])

        # verify if uploaded file is at the right location and has the
        # same binary data
        uploaded_file_path = self.remote_directory_path / directory[1:] / name
        self.assertTrue(uploaded_file_path.is_file())

        uploaded_file = uploaded_file_path.open('rb')
        self.assertEqual(file_data, uploaded_file.read())
        uploaded_file.close()

    def test_download_file_request(self):
        """ Test the download request-response cycle.

        It starts with creating a temporary working tree of files.

          foo/
            bar.bin
            qaz/

        Then, it attemps to download 'bar.bin' from 'foo/' directory.

        But before, it tests the download request by sending several
        invalid requests.

          - Send a bad request
          - Send the four invalid requests expecting the four variant of refused responses
          - Send a valid request, but send invalid chunks later
          - Send a valid request, but cancel transfer later

        The last step is sending a valid request and send receive chunk
        requests until completion of the upload.
        """

        # create socket and connect to the server
        context = zmq.Context.instance()

        socket = context.socket(zmq.REQ)
        socket.setsockopt(zmq.IDENTITY, bytes(TOKEN, 'utf-8'))
        socket.connect('tcp://{0}:{1}'.format(HOSTNAME, str(PORT)))

        # create temporary working tree of files
        self.create_temporary_directory('/', 'foo')
        _, file_data = self.create_temporary_file('/foo', 'bar.bin', 1052)
        self.create_temporary_directory('/foo', 'qaz')

        # prepare common variables
        name       = 'bar.bin'
        directory  = '/foo'
        file_size  = 1052
        chunk_size = 512

        # test sending invalid download file request because it's badly
        # formatted
        expected_response = (Response.ERROR, Reason.BAD_REQUEST)

        request = make_bad_request(Request.DOWNLOAD_FILE)
        socket.send_pyobj(request)

        response = socket.recv_pyobj()
        self.assertTupleEqual(response, expected_response)

        # test sending invalid download file request because the chunk
        # size is incorrect
        incorrect_chunk_size = 0
        expected_response = (Response.REFUSED, Reason.INCORRECT_CHUNK_SIZE)

        request = make_download_file_request(name, directory, incorrect_chunk_size)
        socket.send_pyobj(request)

        response = socket.recv_pyobj()
        self.assertTupleEqual(response, expected_response)

        # test sending invalid download file request because the file
        # name is incorrect
        invalid_names = ['*baz', 'b"az', 'baz|']
        expected_response = (Response.REFUSED, Reason.INVALID_FILE_NAME)

        for invalid_name in invalid_names:
            request = make_download_file_request(invalid_name, directory, chunk_size)
            socket.send_pyobj(request)

            response = socket.recv_pyobj()
            self.assertTupleEqual(response, expected_response)

        # test sending invalid download file request because it has
        # a non-existing directory
        incorrect_directory = '/qaz'
        expected_response = (Response.REFUSED, Reason.NOT_A_DIRECTORY)

        request = make_download_file_request(name, incorrect_directory, chunk_size)
        socket.send_pyobj(request)

        response = socket.recv_pyobj()
        self.assertTupleEqual(response, expected_response)

        # test sending invalid download file request because it has a
        # file that doesn't exist
        incorrect_name = 'qaz.bin'
        expected_response = (Response.REFUSED, Reason.FILE_NOT_FOUND)

        request = make_download_file_request(incorrect_name, directory, chunk_size)
        socket.send_pyobj(request)

        response = socket.recv_pyobj()
        self.assertTupleEqual(response, expected_response)

        # test sending invalid download file request because it has a
        # name that refers to a directory instead of a file
        incorrect_name = 'qaz'
        expected_response = (Response.REFUSED, Reason.NOT_A_FILE)

        request = make_download_file_request(incorrect_name, directory, chunk_size)
        socket.send_pyobj(request)

        response = socket.recv_pyobj()
        self.assertTupleEqual(response, expected_response)

        # test sending valid download file request, then send one valid
        # receive request followed by a cancel transfer request
        expected_responses = (
            (Response.ACCEPTED, Reason.CHUNK_SENT, file_data[:512]),
            (Response.ACCEPTED, Reason.TRANSFER_CANCELLED)
        )

        request = make_download_file_request(name, directory, chunk_size)
        socket.send_pyobj(request)

        response = socket.recv_pyobj()
        self.assertTupleEqual(response, (Response.ACCEPTED, Reason.TRANSFER_ACCEPTED, file_size))

        request = make_receive_chunk_request()
        socket.send_pyobj(request)

        response = socket.recv_pyobj()
        self.assertTupleEqual(response, expected_responses[0])

        request = make_cancel_transfer_request()
        socket.send_pyobj(request)

        response = socket.recv_pyobj()
        self.assertTupleEqual(response, expected_responses[1])

        # test sending valid download file request, send receive chunks
        # requests until completion of download
        expected_responses = (
            (Response.ACCEPTED, Reason.CHUNK_SENT, file_data[:512]),
            (Response.ACCEPTED, Reason.CHUNK_SENT, file_data[512:1024]),
            (Response.ACCEPTED, Reason.TRANSFER_COMPLETED, file_data[1024:])
        )

        request = make_download_file_request(name, directory, chunk_size)
        socket.send_pyobj(request)

        response = socket.recv_pyobj()
        self.assertTupleEqual(response, (Response.ACCEPTED, Reason.TRANSFER_ACCEPTED, file_size))

        for i in range(3):
            request = make_receive_chunk_request()
            socket.send_pyobj(request)

            response = socket.recv_pyobj()
            self.assertTupleEqual(response, expected_responses[i])

    def test_remove_file_request(self):
        """ Test the REMOVE_FILE request

        Long description.
        """

        pass
Пример #6
0
class TestCLI(unittest.TestCase):
    """ Test the command-line interface.

    To be written.
    """
    def setUp(self):
        # create local working directory
        self.local_directory = TemporaryDirectory()
        self.local_directory_path = PosixPath(self.local_directory.name)

        # create remote working directory
        self.remote_directory = TemporaryDirectory()
        self.remote_directory_path = PosixPath(self.remote_directory.name)

        # start the server in an external thread
        self.server = Server(self.remote_directory_path, TOKEN)

        def server_loop(server):
            server.run(HOSTNAME, PORT)

        self.server_thread = threading.Thread(target=server_loop,
                                              args=(self.server, ))
        self.server_thread.start()

        # change working direcory to local working directory
        self.last_working_directory = os.getcwd()
        os.chdir(self.local_directory_path)

    def tearDown(self):

        # restore current working directory
        os.chdir(self.last_working_directory)

        # terminate the server and wait until it terminates
        self.server.terminate()
        self.server_thread.join()

        # delete all testing contents in both local and remote working
        # directory
        self.local_directory.cleanup()
        self.remote_directory.cleanup()

    def _create_temporary_file(self, root, directory, name, size):
        """ Create a temporary file in the 'remote' directory.

        This function creates a temporary file with a given name in a
        given directory located in the temporary workinng 'remote'
        directory. It's filled with a given amount of random data.

        It returns a posix path of the created file and the generated
        data.
        """

        assert os.path.isabs(directory) == True
        directory = directory[1:]

        file_data = os.urandom(size)

        temporary_file_path = root / directory / name
        temporary_file = temporary_file_path.open('wb')
        temporary_file.write(file_data)
        temporary_file.close()

        return temporary_file_path, file_data

    def _create_temporary_directory(self, root, directory, name):
        """ Create a temporary directory in the 'remote' directory.

        This function creates a temporary directory with a given name
        in a given directory located in the temporary workinng 'remote'
        directory.

        It returns a posix path of the created directory.
        """

        assert os.path.isabs(directory) == True
        directory = directory[1:]

        temporary_directory_path = root / directory / name
        temporary_directory_path.mkdir(exist_ok=False)

        return temporary_directory_path

    def create_local_file(self, directory, name, size):
        return self._create_temporary_file(self.local_directory_path,
                                           directory, name, size)

    def create_local_directory(self, directory, name):
        return self._create_temporary_directory(self.local_directory_path,
                                                directory, name)

    def create_remote_file(self, directory, name, size):
        return self._create_temporary_file(self.remote_directory_path,
                                           directory, name, size)

    def create_remote_directory(self, directory, name):
        return self._create_temporary_directory(self.remote_directory_path,
                                                directory, name)

    def test_list_command(self):
        """ Test the upload command.

        Remote working directory.

            foo/
                bar.bin
                qaz/xyz.img
            tox.iso

        Test invoking the following set of commands.

            rmf list
            rmf list /
            rmf list / -a
            rmf list / -r
            rmf list / -a -r
            rmf list /foo
            rmf list /foo -a
            rmf list /foo -r
            rmf list /foo -a -r

        Long description.
        """

        # create remote working tree of files
        self.create_remote_directory('/', 'foo')
        self.create_remote_file('/foo', 'bar.bin', 1052)
        self.create_remote_directory('/foo', 'qaz')
        self.create_remote_file('/foo/qaz', 'xyz.img', 312)
        self.create_remote_file('/', 'tox.iso', 860)

        # test with incorrectly configured environment
        runner = CliRunner()
        result = runner.invoke(list_files, [])
        self.assertIn('Configure your environment and try again.',
                      result.output)
        self.assertEqual(result.exit_code, 1)

        time.sleep(0.05)

        # configure the environment
        os.environ["REMOFILE_HOSTNAME"] = 'localhost'
        os.environ["REMOFILE_PORT"] = str(PORT)
        os.environ["REMOFILE_TOKEN"] = TOKEN

        # test invoking command with minimal parameter
        runner = CliRunner()
        result = runner.invoke(list_files, [])

        self.assertEqual(result.exit_code, 0)
        self.assertIn("foo", result.output)
        self.assertIn("tox.iso", result.output)
        self.assertNotIn("bar.bin", result.output)
        self.assertNotIn("qaz", result.output)
        self.assertNotIn("xyz.img", result.output)

        default_exit_code = result.exit_code
        default_output = result.output

        time.sleep(0.05)

        # test invoking command with default parameters
        runner = CliRunner()
        result = runner.invoke(list_files, ['/'])

        self.assertEqual(result.exit_code, default_exit_code)
        self.assertEqual(result.output, default_output)

        time.sleep(0.05)

        # test invoking command to list root with -a parameter
        runner = CliRunner()
        result = runner.invoke(list_files, ['/', '-a'])

        self.assertEqual(result.exit_code, 0)
        self.assertIn("[D]", result.output)
        self.assertIn("foo", result.output)
        self.assertIn("[F]", result.output)
        self.assertIn("tox.iso", result.output)
        self.assertNotIn("bar.bin", result.output)
        self.assertNotIn("qaz", result.output)
        self.assertNotIn("xyz.img", result.output)

        time.sleep(0.05)

        # test invoking command to list root with -r parameter
        runner = CliRunner()
        result = runner.invoke(list_files, ['/', '-r'])

        self.assertEqual(result.exit_code, 0)
        self.assertIn("foo", result.output)
        self.assertIn("tox.iso", result.output)
        self.assertIn("foo/bar.bin", result.output)
        self.assertIn("foo/qaz", result.output)
        self.assertIn("foo/qaz/xyz.img", result.output)
        self.assertNotIn("[F]", result.output)
        self.assertNotIn("[D]", result.output)

        time.sleep(0.05)

        # test invoking command to list root with -a and -r parameters
        runner = CliRunner()
        result = runner.invoke(list_files, ['/', '-a', '-r'])

        self.assertEqual(result.exit_code, 0)
        self.assertIn("foo", result.output)
        self.assertIn("tox.iso", result.output)
        self.assertIn("foo/bar.bin", result.output)
        self.assertIn("foo/qaz", result.output)
        self.assertIn("foo/qaz/xyz.img", result.output)
        self.assertIn("[F]", result.output)
        self.assertIn("[D]", result.output)

        time.sleep(0.05)

        # test invoking command to list a subdirectory
        runner = CliRunner()
        result = runner.invoke(list_files, ['/foo'])

        self.assertEqual(result.exit_code, 0)
        self.assertNotIn("foo", result.output)
        self.assertNotIn("tox.iso", result.output)
        self.assertIn("bar.bin", result.output)
        self.assertIn("qaz", result.output)
        self.assertNotIn("qaz/xyz.img", result.output)
        self.assertNotIn("[F]", result.output)
        self.assertNotIn("[D]", result.output)

        time.sleep(0.05)

        # test invoking command to list a subdirectory with -a parameter
        runner = CliRunner()
        result = runner.invoke(list_files, ['/foo', '-a'])

        self.assertEqual(result.exit_code, 0)
        self.assertNotIn("foo", result.output)
        self.assertNotIn("tox.iso", result.output)
        self.assertIn("bar.bin", result.output)
        self.assertIn("qaz", result.output)
        self.assertNotIn("qaz/xyz.img", result.output)
        self.assertIn("[F]", result.output)
        self.assertIn("[D]", result.output)

        time.sleep(0.05)

        # test invoking command to list a subdirectory with -r parameter
        runner = CliRunner()
        result = runner.invoke(list_files, ['/foo', '-r'])

        self.assertEqual(result.exit_code, 0)
        self.assertNotIn("foo", result.output)
        self.assertNotIn("tox.iso", result.output)
        self.assertIn("bar.bin", result.output)
        self.assertIn("qaz", result.output)
        self.assertIn("qaz/xyz.img", result.output)
        self.assertNotIn("[F]", result.output)
        self.assertNotIn("[D]", result.output)

        time.sleep(0.05)

        # test invoking command to list a subdirectory with -a and -r parameters
        runner = CliRunner()
        result = runner.invoke(list_files, ['/foo', '-a', '-r'])

        self.assertEqual(result.exit_code, 0)
        self.assertNotIn("foo", result.output)
        self.assertNotIn("tox.iso", result.output)
        self.assertIn("bar.bin", result.output)
        self.assertIn("qaz", result.output)
        self.assertIn("qaz/xyz.img", result.output)
        self.assertIn("[F]", result.output)
        self.assertIn("[D]", result.output)

        time.sleep(0.05)

        # unset the environment
        del os.environ["REMOFILE_HOSTNAME"]
        del os.environ["REMOFILE_PORT"]
        del os.environ["REMOFILE_TOKEN"]

    def test_file_command(self):
        """ Test the upload command.

        Long description.
        """

        pass

    def test_folder_command(self):
        """ Test the upload command.

        Long description.
        """

        pass

    def test_upload_command(self):
        """ Test the upload command.

        Local working directory.

            foo/
                bar.bin
                qaz/xyz.img
            tox.iso

        To simplify tests, don't check binary content of uploaded files
        and subdirectories (this is covered by client tests).

        pass
        """

        # create loal working directory
        self.create_local_directory('/', 'foo')
        self.create_local_file('/foo', 'bar.bin', 1052)
        self.create_local_directory('/foo', 'qaz')
        self.create_local_file('/foo/qaz', 'xyz.img', 312)
        self.create_local_file('/', 'tox.iso', 860)

        tox_file_path = self.remote_directory_path / 'tox.iso'
        foo_directory_path = self.remote_directory_path / 'foo'

        # prepare common variables
        pass

        # test with incorrectly configured environment
        runner = CliRunner()
        result = runner.invoke(upload_files, ['foo', 'tox.iso'])
        self.assertIn('Configure your environment and try again.',
                      result.output)
        self.assertEqual(result.exit_code, 1)

        time.sleep(0.05)

        # set the environment
        os.environ["REMOFILE_HOSTNAME"] = 'localhost'
        os.environ["REMOFILE_PORT"] = str(PORT)
        os.environ["REMOFILE_TOKEN"] = TOKEN

        # test upload one file
        self.assertFalse(tox_file_path.exists())

        runner = CliRunner()
        result = runner.invoke(upload_files, ['tox.iso', '/'])

        self.assertTrue(tox_file_path.is_file())
        self.assertEqual(result.exit_code, 0)
        print(result.output)

        os.remove(tox_file_path)

        time.sleep(0.05)

        # test upload one directory
        self.assertFalse(foo_directory_path.exists())

        runner = CliRunner()
        result = runner.invoke(upload_files, ['foo', '/'])
        self.assertEqual(result.exit_code, 0)  # might change

        self.assertFalse(foo_directory_path.exists())

        time.sleep(0.05)

        runner = CliRunner()
        result = runner.invoke(upload_files, ['foo', '/', '-r'])
        self.assertEqual(result.exit_code, 0)  # might change

        self.assertTrue(foo_directory_path.is_dir())

        shutil.rmtree(foo_directory_path)

        time.sleep(0.05)

        # test upload one file and one directory
        self.assertFalse(tox_file_path.exists())
        self.assertFalse(foo_directory_path.exists())

        runner = CliRunner()
        result = runner.invoke(upload_files, ['foo', 'tox.iso', '/'])
        self.assertEqual(result.exit_code, 0)  # might change

        self.assertTrue(tox_file_path.is_file())
        self.assertFalse(foo_directory_path.exists())
        os.remove(tox_file_path)

        time.sleep(0.05)

        runner = CliRunner()
        result = runner.invoke(upload_files, ['foo', 'tox.iso', '/', '-r'])
        self.assertEqual(result.exit_code, 0)  # might change

        self.assertTrue(tox_file_path.is_file())
        self.assertTrue(foo_directory_path.is_dir())

        os.remove(tox_file_path)
        shutil.rmtree(foo_directory_path)

        time.sleep(0.05)

        # test upload files with the progress flag
        runner = CliRunner()
        result = runner.invoke(upload_files, ['foo', 'tox.iso', '/', '-r'])
        self.assertEqual(result.exit_code, 0)  # might change

        self.assertNotIn('bar.bin', result.output)
        self.assertNotIn('xyz.img', result.output)
        self.assertNotIn('tox.iso', result.output)
        self.assertEqual(result.output.count('100.00%'), 0)

        os.remove(tox_file_path)
        shutil.rmtree(foo_directory_path)

        time.sleep(0.05)

        runner = CliRunner()
        result = runner.invoke(upload_files,
                               ['foo', 'tox.iso', '/', '-r', '-p'])
        self.assertEqual(result.exit_code, 0)  # might change

        self.assertIn('bar.bin', result.output)
        self.assertIn('xyz.img', result.output)
        self.assertIn('tox.iso', result.output)
        self.assertEqual(result.output.count('100.00%'), 3)

        os.remove(tox_file_path)
        shutil.rmtree(foo_directory_path)

        time.sleep(0.05)

        # test upload files with invalid source
        runner = CliRunner()
        result = runner.invoke(upload_files, ['foo.bin', '/'])
        self.assertEqual(result.exit_code, 1)
        self.assertIn('Unable to upload file', result.output)
        self.assertIn('no such file or directory exists', result.output)

        time.sleep(0.2)

        ## test upload files with relative destination path
        #runner = CliRunner()
        #result = runner.invoke(upload_files, ['foo', 'tox.iso', 'foo', '-r'])
        #self.assertEqual(result.exit_code, 1)
        #self.assertIn('Unable to upload files', result.output)
        #self.assertIn('destination must be an absolute path', result.output)

        #time.sleep(0.05)

        ## test upload files with unexisting destination
        #runner = CliRunner()
        #result = runner.invoke(upload_files, ['foo', 'tox.iso', '/foo', '-r'])
        #print(result.output)
        #self.assertEqual(result.exit_code, 1)
        #self.assertIn('Unable to upload files', result.output)
        #self.assertIn('no such directory exists', result.output)

        #time.sleep(0.2)

        ## test upload files with conflicting files
        #self.create_remote_file('/', 'tox.iso', 1204)

        #runner = CliRunner()
        #result = runner.invoke(upload_files, ['tox.iso', '/'])
        #self.assertEqual(result.exit_code, 1)
        #self.assertIn('Unable to upload file', result.output)
        #self.assertIn("it's conflicting with an existing file", result.output)

        #time.sleep(0.05)

        # test upload files with invalid names (shouldn't happen)
        pass

        # test min-size and max-size options
        pass

        # create remote working directory tree
        # /foo/
        #      bar/   -> existing directory
        #      qaz    -> existing file
        #
        # test uploading directory with no recursive flag enabled
        #  - bar
        #  - foo
        #
        # test uploading to incorrect destination directory
        #  - root directory (/)
        #  - directory whose parent is an unexsiting directory (/foo/qaz/bar)
        #  - directory whose parent is an existing directory but is an unexisting directory (/foo/qaz)
        #  - directory whose parent is an existing directory but is a existing file (/foo/qaz)
        #
        # test uploading a file that conflict with existing file (or
        # directory)
        #  - foo
        #  - bar
        #
        pass

    def test_download_command(self):
        """ Test the upload command.

        Long description.
        """

        pass

    def test_remove_command(self):
        """ Test the upload command.

        Long description.
        """

    def test_remove_command(self):
        """ Test the upload command.

        Long description.
        """

        pass

    def test_synchronize_local_command(self):
        """ Test the upload command.

        Long description.
        """

        pass

    def test_synchronize_remote_command(self):
        """ Test the upload command.

        Long description.
        """

        pass

    def test_generate_token_command(self):
        """ Test the upload command.

        Long description.
        """

        pass

    def test_generate_keys_command(self):
        """ Test the upload command.

        Long description.
        """

        pass