Example #1
0
    def setUp(self):
        self.plugin = DiscordRemotePlugin()
        self.plugin.discord = mock.Mock()
        self.plugin._printer = mock.Mock()
        self.plugin._plugin_manager = mock.Mock()
        self.plugin._file_manager = mock.Mock()
        self.plugin._settings = mock.Mock()
        self.plugin._settings.get = mock.Mock()
        self.plugin._settings.get.side_effect = self._mock_settings_get

        self.plugin.get_printer_name = mock.Mock()
        self.plugin.get_printer_name.return_value = 'OctoPrint'

        self.plugin.get_ip_address = mock.Mock()
        self.plugin.get_ip_address.return_value = "192.168.1.1"

        self.plugin.get_external_address = mock.Mock()
        self.plugin.get_external_address.return_value = "1.2.3.4"

        self.command = Command(self.plugin)
Example #2
0
class TestCommand(DiscordRemoteTestCase):
    def _mock_settings_get(self, *args, **kwards):
        if args[0] == ["prefix"]:
            return "/"
        elif args[0] == ['show_local_ip']:
            return True
        elif args[0] == ['show_external_ip']:
            return True
        elif args[0] == ['baseurl']:
            return None
        else:
            self.assertFalse(True, "Not mocked: %s" % args[0])

    def setUp(self):
        self.plugin = DiscordRemotePlugin()
        self.plugin.discord = mock.Mock()
        self.plugin._printer = mock.Mock()
        self.plugin._plugin_manager = mock.Mock()
        self.plugin._file_manager = mock.Mock()
        self.plugin._settings = mock.Mock()
        self.plugin._settings.get = mock.Mock()
        self.plugin._settings.get.side_effect = self._mock_settings_get

        self.plugin.get_printer_name = mock.Mock()
        self.plugin.get_printer_name.return_value = 'OctoPrint'

        self.plugin.get_ip_address = mock.Mock()
        self.plugin.get_ip_address.return_value = "192.168.1.1"

        self.plugin.get_external_address = mock.Mock()
        self.plugin.get_external_address.return_value = "1.2.3.4"

        self.command = Command(self.plugin)

    def _validate_embeds(self, embeds, color):
        self.assertIsNotNone(embeds)
        self.assertGreaterEqual(1, len(embeds))
        for embed in embeds:
            embed = embed.get_embed()
            self.assertIn('color', embed)
            self.assertEqual(color, embed['color'])
            self.assertIn('timestamp', embed)

    def _validate_simple_embed(self,
                               embeds,
                               color,
                               title=None,
                               description=None,
                               image=None):
        self.assertIsNotNone(embeds)
        self.assertEqual(1, len(embeds))
        embed = embeds[0].get_embed()

        self.assertIn('color', embed)
        self.assertEqual(color, embed['color'])
        self.assertIn('timestamp', embed)

        if title:
            self.assertIn('title', embed)
            self.assertEqual(title, embed['title'])

        if description:
            self.assertIn('description', embed)
            self.assertEqual(description, embed['description'])

        if image:
            self.assertIn('image', embed)
            self.assertEqual({'url': "attachment://%s" % image[0][0]},
                             embed['image'])
            self.assertIn(image[0], embeds[0].files)

    def test_parse_command(self):
        self.command.check_perms = mock.Mock()
        self.command.check_perms.return_value = True
        self.command.help = mock.Mock()
        self.command.help.return_value = None, None

        self.command.command_dict['help'] = {
            'cmd': self.command.help,
            'description': "Mock help."
        }

        for command in ['/asdf', "/", "/help", "/?"]:
            self.command.check_perms.reset_mock()
            self.command.help.reset_mock()
            snapshots, embeds = self.command.parse_command(command,
                                                           user="******")
            self.assertIsNone(snapshots)
            self.assertIsNone(embeds)
            self.command.help.assert_called_once()
            self.command.check_perms.assert_called_once()

        for command in ['asdf / fdsa', 'help', 'whatever']:
            self.command.check_perms.reset_mock()
            self.command.help.reset_mock()
            snapshots, embeds = self.command.parse_command(command,
                                                           user="******")
            self.assertIsNone(snapshots)
            self.assertIsNone(embeds)
            self.command.help.assert_not_called()
            self.command.check_perms.assert_not_called()

        self.command.check_perms.reset_mock()
        self.command.check_perms.return_value = False
        snapshots, embeds = self.command.parse_command("/print", user="******")
        self.assertIsNone(snapshots)
        self._validate_simple_embed(embeds,
                                    COLOR_ERROR,
                                    title="Permission Denied")
        self.command.check_perms.assert_called_once()

    def test_parse_command_list(self):
        # Success
        self.plugin.get_file_manager().list_files = mock.Mock()
        self.plugin.get_file_manager().list_files.return_value = file_list
        snapshots, embeds = self.command.parse_command("/files")
        self.plugin.get_file_manager().list_files.assert_called_once()
        self.assertIsNone(snapshots)

        message = ""
        for embed in embeds:
            message += str(embed)
        print(message)

        self._validate_embeds(embeds, COLOR_INFO)

    def test_parse_command_print(self):
        # Invalid Arguments
        snapshots, embeds = self.command.parse_command("/print")
        self._validate_simple_embed(embeds,
                                    COLOR_ERROR,
                                    title='Wrong number of arguments',
                                    description='try "%sprint [filename]"' %
                                    self.plugin.get_settings().get(["prefix"]))

        # Printer not ready
        self.plugin.get_printer().is_ready = mock.Mock()
        self.plugin.get_printer().is_ready.return_value = False
        snapshots, embeds = self.command.parse_command(
            "/print dummyfile.gcode")
        self._validate_simple_embed(embeds,
                                    COLOR_ERROR,
                                    title='Printer is not ready')

        # Printer is ready, file not found
        self.plugin.get_printer().is_ready.return_value = True
        self.command.find_file = mock.Mock()
        self.command.find_file.return_value = None
        snapshots, embeds = self.command.parse_command(
            "/print dummyfile.gcode")
        self._validate_simple_embed(embeds,
                                    COLOR_ERROR,
                                    title='Failed to find the file')

        # Printer is ready, file is found, invalid file type
        self.command.find_file.return_value = {
            'location': 'sdcard',
            'path': '/temp/path'
        }
        self.plugin.get_printer().select_file = mock.Mock()
        self.plugin.get_printer().select_file.side_effect = InvalidFileType
        snapshots, embeds = self.command.parse_command(
            "/print dummyfile.gcode")
        self._validate_simple_embed(embeds,
                                    COLOR_ERROR,
                                    title='Invalid file type selected')

        # Printer is ready, file is found, invalid file location
        self.plugin.get_printer().select_file.side_effect = InvalidFileLocation
        snapshots, embeds = self.command.parse_command(
            "/print dummyfile.gcode")
        self._validate_simple_embed(embeds,
                                    COLOR_ERROR,
                                    title='Invalid file location?')

        # Printer is ready, file is found, print started
        self.plugin.get_printer().select_file.side_effect = None
        snapshots, embeds = self.command.parse_command(
            "/print dummyfile.gcode")
        self._validate_simple_embed(embeds,
                                    COLOR_SUCCESS,
                                    title='Successfully started print',
                                    description='/temp/path')
        self.assertIsNone(snapshots)

    def test_parse_command_snapshot(self):
        # Fail: Camera not working.
        # TODO

        # Success: Camera serving images
        self.plugin.get_snapshot = mock.Mock()
        with open(self._get_path("test_pattern.png")) as input_file:
            self.plugin.get_snapshot.return_value = [('snapshot.png',
                                                      input_file)]

            snapshots, embeds = self.command.parse_command("/snapshot")

            self.assertIsNone(snapshots)
            self._validate_simple_embed(
                embeds,
                COLOR_INFO,
                image=self.plugin.get_snapshot.return_value)

    def test_parse_command_abort(self):
        # Success: Print aborted
        snapshots, embeds = self.command.parse_command("/abort")

        self._validate_simple_embed(embeds, COLOR_ERROR, title="Print aborted")
        self.assertIsNone(snapshots)

    def test_parse_command_help(self):
        # Success: Printed help
        snapshots, embeds = self.command.parse_command("/help")

        message = u""
        for embed in embeds:
            message += str(embed)
        print(message)
        for command, details in self.command.command_dict.items():
            self.assertIn(command, message)
            if details.get('params'):
                for line in details.get('params').split('\n'):
                    self.assertIn(line, message)
            for line in details.get('description').split('\n'):
                self.assertIn(line, message)
        self.assertIsNone(snapshots)

    def test_get_flat_file_list(self):
        self.plugin.get_file_manager().list_files = mock.Mock()
        self.plugin.get_file_manager().list_files.return_value = file_list
        flat_file_list = self.command.get_flat_file_list()
        self.plugin.get_file_manager().list_files.assert_called_once()
        self.assertEqual(len(flatten_file_list), len(flat_file_list))
        for file in flatten_file_list:
            self.assertIn(file, flat_file_list)

    def test_find_file(self):
        self.plugin.get_file_manager().list_files = mock.Mock()
        self.plugin.get_file_manager().list_files.return_value = file_list

        self.assertIsNone(self.command.find_file("NOT_IN_ANY_FILENAME"))
        self.assertEqual(flatten_file_list[0], self.command.find_file("ER1/T"))
        self.assertEqual(flatten_file_list[1], self.command.find_file("TEST2"))

    @mock.patch("time.sleep")
    def test_parse_command_connect(self, mock_sleep):
        # Fail: Too many parameters
        snapshots, embeds = self.command.parse_command(
            "/connect asdf asdf  asdf")
        self._validate_simple_embed(
            embeds,
            COLOR_ERROR,
            title="Too many parameters",
            description="Should be: /connect [port] [baudrate]")
        self.assertIsNone(snapshots)

        # Fail: Printer already connected
        self.plugin.get_printer().is_operational = mock.Mock()
        self.plugin.get_printer().is_operational.return_value = True
        snapshots, embeds = self.command.parse_command("/connect")
        self._validate_simple_embed(embeds,
                                    COLOR_ERROR,
                                    title="Printer already connected",
                                    description="Disconnect first")
        self.assertIsNone(snapshots)
        self.plugin.get_printer().is_operational.assert_called_once()

        # Fail: wrong format for baudrate
        self.plugin.get_printer().is_operational = mock.Mock()
        self.plugin.get_printer().is_operational.return_value = False
        snapshots, embeds = self.command.parse_command(
            "/connect port baudrate")
        self._validate_simple_embed(embeds,
                                    COLOR_ERROR,
                                    title="Wrong format for baudrate",
                                    description="should be a number")
        self.assertIsNone(snapshots)
        self.plugin.get_printer().is_operational.assert_called_once()

        # Fail: connect failed.
        self.plugin.get_printer().is_operational = mock.Mock()
        self.plugin.get_printer().is_operational.return_value = False
        self.plugin.get_printer().connect = mock.Mock()
        snapshots, embeds = self.command.parse_command("/connect port 1234")
        self._validate_simple_embed(
            embeds,
            COLOR_ERROR,
            title="Failed to connect",
            description='try: "/connect [port] [baudrate]"')
        self.assertIsNone(snapshots)
        self.assertEqual(31,
                         self.plugin.get_printer().is_operational.call_count)
        self.plugin.get_printer().connect.assert_called_once_with(
            port="port", baudrate=1234, profile=None)

    @mock.patch("time.sleep")
    def test_parse_command_disconnect(self, mock_sleep):
        # Fail: Printer already disconnected
        self.plugin.get_printer().is_operational = mock.Mock()
        self.plugin.get_printer().is_operational.return_value = False
        snapshots, embeds = self.command.parse_command("/disconnect")
        self._validate_simple_embed(embeds,
                                    COLOR_ERROR,
                                    title="Printer is not connected")
        self.assertIsNone(snapshots)
        self.plugin.get_printer().is_operational.assert_called_once()

        # Fail: disconnect failed.
        self.plugin.get_printer().is_operational = mock.Mock()
        self.plugin.get_printer().is_operational.return_value = True
        self.plugin.get_printer().disconnect = mock.Mock()
        snapshots, embeds = self.command.parse_command("/disconnect")
        self._validate_simple_embed(embeds,
                                    COLOR_ERROR,
                                    title="Failed to disconnect")
        self.assertIsNone(snapshots)
        self.assertEqual(2,
                         self.plugin.get_printer().is_operational.call_count)
        self.plugin.get_printer().disconnect.assert_called_once_with()

    @mock.patch("subprocess.Popen")
    def test_parse_command_status(self, mock_popen):
        self.plugin.get_ip_address = mock.Mock()
        self.plugin.get_ip_address.return_value = "192.168.1.1"

        self.plugin.get_external_ip_address = mock.Mock()
        self.plugin.get_external_ip_address.return_value = "8.8.8.8"

        self.plugin.get_printer().is_operational = mock.Mock()
        self.plugin.get_printer().is_operational.return_value = True

        self.plugin.get_printer().is_printing = mock.Mock()
        self.plugin.get_printer().is_printing.return_value = True

        self.plugin.get_printer().get_current_data = mock.Mock()
        self.plugin.get_printer().get_current_data.return_value = {
            'currentZ': 10,
            'job': {
                'file': {
                    'name': 'filename'
                }
            },
            'progress': {
                'completion': 15,
                'printTime': 300,
                'printTimeLeft': 500
            }
        }

        self.plugin.get_printer().get_current_temperatures = mock.Mock()
        self.plugin.get_printer().get_current_temperatures.return_value = {
            'bed': {
                'actual': 100
            },
            'extruder0': {
                'actual': 250
            },
            'extruder1': {
                'actual': 350
            }
        }

        mock_popen.return_value.communicate.return_value = [
            b"throttled=0xf000f"
        ]  # All flags raised
        expected_throttled_terms = [
            "PI is under-voltage",
            "PI has capped it's ARM frequency",
            "PI is currently throttled",
            "PI has reached temperature limit",
            "PI Under-voltage has occurred",
            "PI ARM frequency capped has occurred",
            "PI Throttling has occurred",
            "PI temperature limit has occurred",
        ]

        self.plugin.get_snapshot = mock.Mock()

        with open(self._get_path('test_pattern.png')) as input_file:
            self.plugin.get_snapshot.return_value = [('snapshot.png',
                                                      input_file)]

            snapshots, embeds = self.command.parse_command('/status')

            self.assertIsNone(snapshots)
            self.plugin.get_snapshot.assert_called_once()

        message = ""
        for embed in embeds:
            message += str(embed)
        print(message)

        expected_terms = [
            'Status', 'Operational', 'Current Z', 'Bed Temp', 'extruder0',
            'extruder1', 'File', 'Progress', 'Time Spent', 'Time Remaining',
            humanfriendly.format_timespan(300),
            humanfriendly.format_timespan(500),
            self.plugin.get_ip_address.return_value,
            self.plugin.get_external_ip_address.return_value
        ]
        expected_terms += expected_throttled_terms

        self.assertEqual(3, self.plugin.get_settings().get.call_count)

        calls = [
            mock.call(["show_local_ip"], merged=True),
            mock.call(["show_external_ip"], merged=True)
        ]
        self.plugin.get_settings().get.assert_has_calls(calls)

        for term in expected_terms:
            self.assertIn(term, message)

    def test_parse_command_pause(self):
        self.plugin.get_snapshot = mock.Mock()
        self.plugin.get_snapshot.return_value = [('snapshot.png', mock.Mock())]
        self.plugin.get_printer().pause_print = mock.Mock()
        snapshots, embeds = self.command.parse_command("/pause")

        self._validate_simple_embed(
            embeds,
            COLOR_SUCCESS,
            title="Print paused",
            image=self.plugin.get_snapshot.return_value)
        self.plugin.get_snapshot.assert_called_once()
        self.assertIsNone(snapshots)
        self.plugin.get_printer().pause_print.assert_called_once()

    def test_parse_command_resume(self):
        self.plugin.get_snapshot = mock.Mock()
        self.plugin.get_snapshot.return_value = [('snapshot.png', mock.Mock())]
        self.plugin.get_printer().resume_print = mock.Mock()
        snapshots, embeds = self.command.parse_command("/resume")

        self._validate_simple_embed(
            embeds,
            COLOR_SUCCESS,
            title="Print resumed",
            image=self.plugin.get_snapshot.return_value)
        self.plugin.get_snapshot.assert_called_once()
        self.assertIsNone(snapshots)
        self.plugin.get_printer().resume_print.assert_called_once()

    @mock.patch("requests.get")
    def test_download_file(self, mock_get):
        self.plugin.get_file_manager().path_on_disk = mock.Mock()
        self.plugin.get_file_manager(
        ).path_on_disk.return_value = "./temp.file"

        mock_request_val = mock.Mock()
        mock_request_val.iter_content = mock.Mock()
        mock_request_val.iter_content.return_value = [b'1234']
        mock_get.return_value = mock_request_val

        # Upload, no user
        snapshot, embeds = self.command.download_file("filename",
                                                      "http://mock.url", None)
        self.assertTrue(snapshot)
        self._validate_simple_embed(embeds,
                                    COLOR_SUCCESS,
                                    title="File Received")

        self.plugin.get_file_manager().path_on_disk.assert_called_once_with(
            'local', 'filename')
        self.plugin.get_file_manager().path_on_disk.reset_mock()
        mock_get.assert_called_once_with("http://mock.url", stream=True)
        mock_get.reset_mock()

        with open("./temp.file", 'rb') as f:
            self.assertEqual(b'1234', f.read())

        os.remove("./temp.file")

        # Upload with user
        self.command.check_perms = mock.Mock()
        self.command.check_perms.return_value = True

        snapshot, embeds = self.command.download_file("filename",
                                                      "http://mock.url",
                                                      "1234")
        self.assertTrue(snapshot)
        self._validate_simple_embed(embeds,
                                    COLOR_SUCCESS,
                                    title="File Received")

        self.plugin.get_file_manager().path_on_disk.assert_called_once_with(
            'local', 'filename')
        self.plugin.get_file_manager().path_on_disk.reset_mock()
        mock_get.assert_called_once_with("http://mock.url", stream=True)
        mock_get.reset_mock()

        with open("./temp.file", 'rb') as f:
            self.assertEqual(b'1234', f.read())

        # Upload denied
        self.command.check_perms.return_value = False

        snapshot, embeds = self.command.download_file("filename",
                                                      "http://mock.url",
                                                      "1234")
        self.assertFalse(snapshot)
        self._validate_simple_embed(embeds,
                                    COLOR_ERROR,
                                    title="Permission Denied")

    def test_parse_array(self):
        self.assertIsNone(self.command._parse_array(None))
        self.assertIsNone(self.command._parse_array(1))

        results = self.command._parse_array("*")
        self.assertEqual(1, len(results))
        self.assertIn("*", results)

        results = self.command._parse_array("123")
        self.assertEqual(1, len(results))
        self.assertIn("123", results)

        results = self.command._parse_array("123,456")
        self.assertEqual(2, len(results))
        self.assertIn("123", results)
        self.assertIn("456", results)

        results = self.command._parse_array("123 456")
        self.assertEqual(2, len(results))
        self.assertIn("123", results)
        self.assertIn("456", results)

        results = self.command._parse_array("123/456")
        self.assertEqual(2, len(results))
        self.assertIn("123", results)
        self.assertIn("456", results)

    def test_check_perms(self):
        get_mock = mock.Mock()

        settings_mock = mock.Mock()
        settings_mock.get = get_mock

        self.command.plugin.get_settings = mock.Mock()
        self.command.plugin.get_settings.return_value = settings_mock

        get_mock.return_value = {}
        self.assertFalse(self.command.check_perms("notallowed", "123"))

        get_mock.return_value = {'1': {'users': '*', 'commands': ''}}
        self.assertFalse(self.command.check_perms("notallowed", "123"))

        get_mock.return_value = {'1': {'users': '', 'commands': '*'}}
        self.assertFalse(self.command.check_perms("notallowed", "123"))

        get_mock.return_value = {'1': {'users': '*', 'commands': '*'}}
        self.assertTrue(self.command.check_perms("allowed", "123"))

        get_mock.return_value = {'1': {'users': '123', 'commands': '*'}}
        self.assertTrue(self.command.check_perms("allowed", "123"))

        get_mock.get.return_value = {
            '1': {
                'users': '*',
                'commands': 'allowed'
            }
        }
        self.assertTrue(self.command.check_perms("allowed", "123"))

        get_mock.return_value = {'1': {'users': '123', 'commands': 'allowed'}}
        self.assertTrue(self.command.check_perms("allowed", "123"))

        get_mock.return_value = {
            '1': {
                'users': '456',
                'commands': 'notallowed'
            },
            '2': {
                'users': '123',
                'commands': 'allowed'
            }
        }
        self.assertTrue(self.command.check_perms("allowed", "123"))

    def test_gcode(self):
        # Printer disconnected
        self.plugin.get_printer().is_operational = mock.Mock()
        self.plugin.get_printer().is_operational.return_value = False
        snapshots, embeds = self.command.gcode(["/gcode", "M0"])
        self.assertIsNone(snapshots)
        self._validate_simple_embed(embeds,
                                    COLOR_ERROR,
                                    title="Printer not connected",
                                    description="Connect to printer first.")

        # Printer connected, invalid GCODE
        self.plugin.get_printer().is_operational.return_value = True
        self.plugin.get_settings().get = mock.Mock()
        self.plugin.get_settings().get.return_value = "G0, M0|M851"
        snapshots, embeds = self.command.gcode(["/gcode", "M1"])
        self.assertIsNone(snapshots)
        self._validate_simple_embed(
            embeds,
            COLOR_ERROR,
            title="Invalid GCODE",
            description=
            "If you want to use \"M1\", add it to the allowed GCODEs")

        # Failed to send
        self.plugin.get_printer().commands = mock.Mock()
        self.plugin.get_printer().commands.side_effect = Exception("Error")
        snapshots, embeds = self.command.gcode(["/gcode", "M0"])
        self.assertIsNone(snapshots)
        self._validate_simple_embed(embeds,
                                    COLOR_ERROR,
                                    title="Failed to execute gcode",
                                    description="Error: Error")

        # Success - Case Insensitive:
        self.plugin.get_printer().commands.reset_mock()
        self.plugin.get_printer().commands.side_effect = None
        snapshots, embeds = self.command.gcode(["/gcode", "m0"])
        self.assertIsNone(snapshots)
        self._validate_simple_embed(embeds, COLOR_SUCCESS, title="Sent script")
        self.plugin.get_printer().commands.assert_called_once_with(['M0'])

    @mock.patch('octoprint_discordremote.command.upload_file')
    def test_parse_command_getfile(self, mock_upload):
        self.command.find_file = mock.Mock()
        self.command.find_file.return_value = None
        snapshots, embeds = self.command.getfile(["/getfile", "test.gcode"])
        self.assertIsNone(snapshots)
        self._validate_simple_embed(
            embeds,
            COLOR_ERROR,
            title="Failed to find file matching the name given")

        self.command.find_file.return_value = {'location': None, 'path': None}
        mock_upload.return_value = True, True
        self.plugin.get_file_manager = mock.Mock()
        mock_file_manager = mock.Mock()
        self.plugin.get_file_manager.return_value = mock_file_manager
        snapshots, embeds = self.command.getfile(["/getfile", "test.gcode"])
        self.assertTrue(snapshots)
        self.assertTrue(embeds)

    @mock.patch('os.walk')
    @mock.patch('octoprint_discordremote.command.upload_file')
    def test_parse_command_gettimelapse(self, mock_upload, mock_oswalk):
        self.plugin._data_folder = ''
        mock_oswalk.return_value = [('', [], [])]
        snapshots, embeds = self.command.gettimelapse(
            ["/gettimelapse", "test.gcode"])
        self.assertIsNone(snapshots)
        self._validate_simple_embed(
            embeds,
            COLOR_ERROR,
            title="Failed to find file matching the name given")

        mock_oswalk.return_value = [('', [], ['test.gcode'])]
        mock_upload.return_value = True, True
        snapshots, embeds = self.command.gettimelapse(
            ["/gettimelapse", "test.gcode"])
        self.assertTrue(snapshots)
        self.assertTrue(embeds)

    def test_unzip(self):

        # prepare a working  dummy zip
        with ZipFile('./testzip.zip', 'w') as zipf:
            zipf.writestr('test.gcode', "Proper GCODE file!")
            zipf.writestr('test.txt', "Don't extract that!")
            zipf.close()

        # create a multi-volume dummy zip, too
        # code adapted, taken from Jeronimo's answer in https://stackoverflow.com/questions/52193680/split-a-zip-archive-into-multiple-chunks
        outfile = './testzipMV.zip'
        packet_size = int(100)  # bytes

        with open('./testzip.zip', "rb") as output:
            filecount = 1
            while True:
                data = output.read(packet_size)
                if not data:
                    break  # we're done
                with open("{}.{:03}".format(outfile, filecount),
                          "wb") as packet:
                    packet.write(data)
                filecount += 1

        # reroute calls to Octoprint's local folder to project local folder
        def path_sideeff(*args, **kwargs):
            if args[0] == 'local':
                return os.path.join('./', args[1])
            return None

        def file_exists_sideeff(*args, **kwargs):
            if args[0] == 'local':
                return os.path.isfile(os.path.join('./', args[1]))
            return None

        self.plugin.get_file_manager().path_on_disk = mock.Mock()
        self.plugin.get_file_manager().path_on_disk.side_effect = path_sideeff

        self.plugin.get_file_manager().file_exists = mock.Mock()
        self.plugin.get_file_manager(
        ).file_exists.side_effect = file_exists_sideeff

        self.command.get_flat_file_list = mock.Mock()
        self.command.get_flat_file_list.return_value = []

        # CASE 1: File doesn't exist
        snapshot, embeds = self.command.unzip(["/unzip", "testzip.zip"])
        self.assertIsNone(snapshot)
        self._validate_simple_embed(embeds,
                                    COLOR_ERROR,
                                    title="File testzip.zip not found.")

        # CASE 2: File exists but is not a zip file
        self.plugin.get_settings().get = mock.Mock()
        self.plugin.get_settings().get.return_value = ''

        snapshot, embeds = self.command.unzip(["/unzip", "testzip.nope"])
        self.assertIsNone(snapshot)
        self._validate_simple_embed(embeds,
                                    COLOR_ERROR,
                                    title="Not a valid Zip file.")

        self.command.get_flat_file_list.return_value = zip_flat_file_list

        # CASE 3: File is a working zip file
        snapshot, embeds = self.command.unzip(["/unzip", "testzip.zip"])
        self.assertIsNone(snapshot)
        self._validate_simple_embed(embeds,
                                    COLOR_SUCCESS,
                                    title="File(s) unzipped. ")

        # make sure it didn't extract the unwanted .txt in the zip file, only the gcode file
        self.assertTrue(os.path.isfile('./test.gcode'))
        self.assertFalse(os.path.isfile('./test.txt'))

        with open('./test.gcode') as f:
            self.assertEqual("Proper GCODE file!", f.read())

        os.remove('./test.gcode')
        os.remove('./testzip.zip')

        # CASE 4: File is a working multi-volume zip file
        snapshot, embeds = self.command.unzip(["/unzip", "testzipMV.zip.001"])
        self.assertIsNone(snapshot)
        self._validate_simple_embed(embeds,
                                    COLOR_SUCCESS,
                                    title="File(s) unzipped. ")
        # no need to test for unwanted extracted files, code is the same as for single-volume zips
        os.remove('./test.gcode')
        os.remove('./testzipMV.zip')

        # CASE 5: File is a multi-volume zip file, but it's missing volumes
        os.remove('./testzipMV.zip.002')
        self.command.get_flat_file_list.return_value = [
            zip_flat_file_list[0], zip_flat_file_list[2]
        ]

        snapshot, embeds = self.command.unzip(["/unzip", "testzipMV.zip.001"])
        self.assertIsNone(snapshot)
        self._validate_simple_embed(embeds, COLOR_ERROR, title="Bad zip file.")

        os.remove('./testzipMV.zip')
        os.remove('./testzipMV.zip.001')
        os.remove('./testzipMV.zip.003')

    def test_judge_is_unzippable(self):

        # prepare a working  dummy zip
        with ZipFile('./testzip.zip', 'w') as zipf:
            zipf.writestr('test.gcode', "Proper GCODE file!")
            zipf.writestr('test.txt', "Don't extract that!")
            zipf.close()

        # create a multi-volume dummy zip, too
        # code adapted, taken from Jeronimo's answer in https://stackoverflow.com/questions/52193680/split-a-zip-archive-into-multiple-chunks
        outfile = './testzipMV.zip'
        packet_size = int(100)  # bytes

        with open('./testzip.zip', "rb") as output:
            filecount = 1
            while True:
                data = output.read(packet_size)
                if not data:
                    break  # we're done
                with open("{}.{:03}".format(outfile, filecount),
                          "wb") as packet:
                    packet.write(data)
                filecount += 1

        # reroute calls to Octoprint's local folder to project local folder
        def path_sideeff(*args, **kwargs):
            if args[0] == 'local':
                return os.path.join('./', args[1])
            return None

        self.plugin.get_file_manager().path_on_disk = mock.Mock()
        self.plugin.get_file_manager().path_on_disk.side_effect = path_sideeff

        # CASE 1: File is a .zip and we're allowed to auto-unzip
        self.plugin.get_settings().get = mock.Mock()
        self.plugin.get_settings().get.return_value = True

        payload, embeds = self.command.judge_is_unzippable("test.zip")
        self.assertTrue(payload)
        self._validate_simple_embed(
            embeds,
            COLOR_SUCCESS,
            title="File Received, starting to unzip....")

        # CASE 2: File is a .zip but we're not allowed to auto-unzip
        self.plugin.get_settings().get.return_value = False

        payload, embeds = self.command.judge_is_unzippable("test.zip")
        self.assertFalse(payload)
        self._validate_simple_embed(embeds,
                                    COLOR_SUCCESS,
                                    title="File Received")

        # CASE 3: File is not a .zip file
        self.plugin.get_settings().get.return_value = True

        payload, embeds = self.command.judge_is_unzippable("test.nope")
        self.assertFalse(payload)
        self._validate_simple_embed(embeds,
                                    COLOR_SUCCESS,
                                    title="File Received")

        # CASE 4: File is a multi-volume .zip.001, ... file, all volumes present

        self.command.get_flat_file_list = mock.Mock()
        self.command.get_flat_file_list.return_value = zip_flat_file_list

        payload, embeds = self.command.judge_is_unzippable("testzipMV.zip.002")
        self.assertTrue(payload)
        self._validate_simple_embed(
            embeds,
            COLOR_SUCCESS,
            title="All Files Received, starting to unzip....")

        # CASE 5: File is a multi-volume .zip.001, ... file, one volume present

        self.command.get_flat_file_list.return_value = [zip_flat_file_list[2]]

        payload, embeds = self.command.judge_is_unzippable("testzipMV.zip.002")
        self.assertFalse(payload)
        self._validate_simple_embed(embeds,
                                    COLOR_INFO,
                                    title="1 of ??? Files Received")

        # CASE 6: File is a multi-volume .zip.001, ... file, two volumes present, last one not included

        self.command.get_flat_file_list.return_value = [
            zip_flat_file_list[1], zip_flat_file_list[2]
        ]

        payload, embeds = self.command.judge_is_unzippable("testzipMV.zip.002")
        self.assertFalse(payload)
        self._validate_simple_embed(embeds,
                                    COLOR_INFO,
                                    title="2 of ??? Files Received")

        # CASE 7: File is a multi-volume .zip.001, ... file, two volumes present, last one included

        self.command.get_flat_file_list.return_value = [
            zip_flat_file_list[2], zip_flat_file_list[3]
        ]

        payload, embeds = self.command.judge_is_unzippable("testzipMV.zip.002")
        self.assertFalse(payload)
        self._validate_simple_embed(embeds,
                                    COLOR_INFO,
                                    title="2 of 3 Files Received")

        # CASE 8: File is a multi-volume .zip.001, ... file, all volumes present but we're not allowed to auto-unzip

        self.plugin.get_settings().get.return_value = False
        self.command.get_flat_file_list.return_value = zip_flat_file_list

        payload, embeds = self.command.judge_is_unzippable("testzipMV.zip.002")
        self.assertFalse(payload)
        self._validate_simple_embed(embeds,
                                    COLOR_SUCCESS,
                                    title="3 of 3 Files Received")

        os.remove('./testzip.zip')
        os.remove('./testzipMV.zip.001')
        os.remove('./testzipMV.zip.002')
        os.remove('./testzipMV.zip.003')
class TestCommand(DiscordRemoteTestCase):
    def _mock_settings_get(self, *args, **kwards):
        if args[0] == ["prefix"]:
            return "/"
        elif args[0] == ['show_local_ip']:
            return True
        elif args[0] == ['show_external_ip']:
            return True
        elif args[0] == ['baseurl']:
            return None
        else:
            self.assertFalse(True, "Not mocked: %s" % args[0])

    def setUp(self):
        with mock.patch('octoprint_discordremote.DiscordRemotePlugin.discord'):
            self.plugin = DiscordRemotePlugin()

            self.plugin._printer = mock.Mock()
            self.plugin._plugin_manager = mock.Mock()
            self.plugin._file_manager = mock.Mock()
            self.plugin._settings = mock.Mock()
            self.plugin._settings.get = mock.Mock()
            self.plugin._settings.get.side_effect = self._mock_settings_get

            self.plugin.get_printer_name = mock.Mock()
            self.plugin.get_printer_name.return_value = 'OctoPrint'

            self.plugin.get_ip_address = mock.Mock()
            self.plugin.get_ip_address.return_value = "192.168.1.1"

            self.plugin.get_external_address = mock.Mock()
            self.plugin.get_external_address.return_value = "1.2.3.4"

            self.command = Command(self.plugin)

    def _validate_embeds(self, embeds, color):
        self.assertIsNotNone(embeds)
        self.assertGreaterEqual(1, len(embeds))
        for embed in embeds:
            embed = embed.get_embed()
            self.assertIn('color', embed)
            self.assertEqual(color, embed['color'])
            self.assertIn('timestamp', embed)

    def _validate_simple_embed(self,
                               embeds,
                               color,
                               title=None,
                               description=None,
                               image=None):
        self.assertIsNotNone(embeds)
        self.assertEqual(1, len(embeds))
        embed = embeds[0].get_embed()

        self.assertIn('color', embed)
        self.assertEqual(color, embed['color'])
        self.assertIn('timestamp', embed)

        if title:
            self.assertIn('title', embed)
            self.assertEqual(title, embed['title'])

        if description:
            self.assertIn('description', embed)
            self.assertEqual(description, embed['description'])

        if image:
            self.assertIn('image', embed)
            self.assertEqual({'url': "attachment://%s" % image[0][0]},
                             embed['image'])
            self.assertIn(image[0], embeds[0].files)

    def test_parse_command(self):
        for command in ['help', '/asdf']:
            self.command.help = mock.Mock()
            self.command.help.return_value = None, None
            snapshots, embeds = self.command.parse_command(command,
                                                           user="******")
            self.assertIsNone(snapshots)
            self.assertIsNone(embeds)
            self.command.help.assert_called_once()

        self.command.check_perms = mock.Mock()
        self.command.check_perms.return_value = False
        snapshots, embeds = self.command.parse_command("/print", user="******")
        self.assertIsNone(snapshots)
        self._validate_simple_embed(embeds,
                                    COLOR_ERROR,
                                    title="Permission Denied")
        self.command.check_perms.assert_called_once()

    def test_parse_command_list(self):
        # Success
        self.plugin.get_file_manager().list_files = mock.Mock()
        self.plugin.get_file_manager().list_files.return_value = file_list
        snapshots, embeds = self.command.parse_command("/files")
        self.plugin.get_file_manager().list_files.assert_called_once()
        self.assertIsNone(snapshots)

        message = ""
        for embed in embeds:
            message += unicode(embed)
        print(message)

        self._validate_embeds(embeds, COLOR_INFO)

    def test_parse_command_print(self):
        # Invalid Arguments
        snapshots, embeds = self.command.parse_command("/print")
        self._validate_simple_embed(embeds,
                                    COLOR_ERROR,
                                    title='Wrong number of arguments',
                                    description='try "%sprint [filename]"' %
                                    self.plugin.get_settings().get(["prefix"]))

        # Printer not ready
        self.plugin.get_printer().is_ready = mock.Mock()
        self.plugin.get_printer().is_ready.return_value = False
        snapshots, embeds = self.command.parse_command(
            "/print dummyfile.gcode")
        self._validate_simple_embed(embeds,
                                    COLOR_ERROR,
                                    title='Printer is not ready')

        # Printer is ready, file not found
        self.plugin.get_printer().is_ready.return_value = True
        self.command.find_file = mock.Mock()
        self.command.find_file.return_value = None
        snapshots, embeds = self.command.parse_command(
            "/print dummyfile.gcode")
        self._validate_simple_embed(embeds,
                                    COLOR_ERROR,
                                    title='Failed to find the file')

        # Printer is ready, file is found, invalid file type
        self.command.find_file.return_value = {
            'location': 'sdcard',
            'path': '/temp/path'
        }
        self.plugin.get_printer().select_file = mock.Mock()
        self.plugin.get_printer().select_file.side_effect = InvalidFileType
        snapshots, embeds = self.command.parse_command(
            "/print dummyfile.gcode")
        self._validate_simple_embed(embeds,
                                    COLOR_ERROR,
                                    title='Invalid file type selected')

        # Printer is ready, file is found, invalid file location
        self.plugin.get_printer().select_file.side_effect = InvalidFileLocation
        snapshots, embeds = self.command.parse_command(
            "/print dummyfile.gcode")
        self._validate_simple_embed(embeds,
                                    COLOR_ERROR,
                                    title='Invalid file location?')

        # Printer is ready, file is found, print started
        self.plugin.get_printer().select_file.side_effect = None
        snapshots, embeds = self.command.parse_command(
            "/print dummyfile.gcode")
        self._validate_simple_embed(embeds,
                                    COLOR_SUCCESS,
                                    title='Successfully started print',
                                    description='/temp/path')
        self.assertIsNone(snapshots)

    def test_parse_command_unknown(self):
        self.command.help = mock.Mock()

        # No prefix, cry for help
        self.command.parse_command("HeLp")
        self.command.help.assert_called_once()
        self.command.help.reset_mock()

        # Invalid command, return help
        self.command.parse_command("/?")
        self.command.help.assert_called_once()
        self.command.help.reset_mock()

        # No/Wrong prefix
        snapshots, embeds = self.command.parse_command("not a command")
        self.assertIsNone(embeds)
        self.assertIsNone(snapshots)
        self.command.help.assert_not_called()

    def test_parse_command_snapshot(self):
        # Fail: Camera not working.
        # TODO

        # Success: Camera serving images
        self.plugin.get_snapshot = mock.Mock()
        with open(self._get_path("test_pattern.png")) as input_file:
            self.plugin.get_snapshot.return_value = [('snapshot.png',
                                                      input_file)]

            snapshots, embeds = self.command.parse_command("/snapshot")

            self.assertIsNone(snapshots)
            self._validate_simple_embed(
                embeds,
                COLOR_INFO,
                image=self.plugin.get_snapshot.return_value)

    def test_parse_command_abort(self):
        # Success: Print aborted
        snapshots, embeds = self.command.parse_command("/abort")

        self._validate_simple_embed(embeds, COLOR_ERROR, title="Print aborted")
        self.assertIsNone(snapshots)

    def test_parse_command_help(self):
        # Success: Printed help
        snapshots, embeds = self.command.parse_command("/help")

        message = ""
        for embed in embeds:
            message += unicode(embed)
        print(message)
        for command, details in self.command.command_dict.items():
            self.assertIn(command, message)
            if details.get('params'):
                for line in details.get('params').split('\n'):
                    self.assertIn(line, message)
            for line in details.get('description').split('\n'):
                self.assertIn(line, message)
        self.assertIsNone(snapshots)

    def test_get_flat_file_list(self):
        self.plugin.get_file_manager().list_files = mock.Mock()
        self.plugin.get_file_manager().list_files.return_value = file_list
        flat_file_list = self.command.get_flat_file_list()
        self.plugin.get_file_manager().list_files.assert_called_once()
        self.assertEqual(2, len(flat_file_list))
        self.assertEqual(flatten_file_list, flat_file_list)

    def test_find_file(self):
        self.plugin.get_file_manager().list_files = mock.Mock()
        self.plugin.get_file_manager().list_files.return_value = file_list

        self.assertIsNone(self.command.find_file("NOT_IN_ANY_FILENAME"))
        self.assertEqual(flatten_file_list[0], self.command.find_file("ER1/T"))
        self.assertEqual(flatten_file_list[1], self.command.find_file("TEST2"))

    @mock.patch("time.sleep")
    def test_parse_command_connect(self, mock_sleep):
        # Fail: Too many parameters
        snapshots, embeds = self.command.parse_command(
            "/connect asdf asdf  asdf")
        self._validate_simple_embed(
            embeds,
            COLOR_ERROR,
            title="Too many parameters",
            description="Should be: /connect [port] [baudrate]")
        self.assertIsNone(snapshots)

        # Fail: Printer already connected
        self.plugin.get_printer().is_operational = mock.Mock()
        self.plugin.get_printer().is_operational.return_value = True
        snapshots, embeds = self.command.parse_command("/connect")
        self._validate_simple_embed(embeds,
                                    COLOR_ERROR,
                                    title="Printer already connected",
                                    description="Disconnect first")
        self.assertIsNone(snapshots)
        self.plugin.get_printer().is_operational.assert_called_once()

        # Fail: wrong format for baudrate
        self.plugin.get_printer().is_operational = mock.Mock()
        self.plugin.get_printer().is_operational.return_value = False
        snapshots, embeds = self.command.parse_command(
            "/connect port baudrate")
        self._validate_simple_embed(embeds,
                                    COLOR_ERROR,
                                    title="Wrong format for baudrate",
                                    description="should be a number")
        self.assertIsNone(snapshots)
        self.plugin.get_printer().is_operational.assert_called_once()

        # Fail: connect failed.
        self.plugin.get_printer().is_operational = mock.Mock()
        self.plugin.get_printer().is_operational.return_value = False
        self.plugin.get_printer().connect = mock.Mock()
        snapshots, embeds = self.command.parse_command("/connect port 1234")
        self._validate_simple_embed(
            embeds,
            COLOR_ERROR,
            title="Failed to connect",
            description='try: "/connect [port] [baudrate]"')
        self.assertIsNone(snapshots)
        self.assertEqual(31,
                         self.plugin.get_printer().is_operational.call_count)
        self.plugin.get_printer().connect.assert_called_once_with(
            port="port", baudrate=1234, profile=None)

    @mock.patch("time.sleep")
    def test_parse_command_disconnect(self, mock_sleep):
        # Fail: Printer already disconnected
        self.plugin.get_printer().is_operational = mock.Mock()
        self.plugin.get_printer().is_operational.return_value = False
        snapshots, embeds = self.command.parse_command("/disconnect")
        self._validate_simple_embed(embeds,
                                    COLOR_ERROR,
                                    title="Printer is not connected")
        self.assertIsNone(snapshots)
        self.plugin.get_printer().is_operational.assert_called_once()

        # Fail: disconnect failed.
        self.plugin.get_printer().is_operational = mock.Mock()
        self.plugin.get_printer().is_operational.return_value = True
        self.plugin.get_printer().disconnect = mock.Mock()
        snapshots, embeds = self.command.parse_command("/disconnect")
        self._validate_simple_embed(embeds,
                                    COLOR_ERROR,
                                    title="Failed to disconnect")
        self.assertIsNone(snapshots)
        self.assertEqual(2,
                         self.plugin.get_printer().is_operational.call_count)
        self.plugin.get_printer().disconnect.assert_called_once_with()

    def test_parse_command_status(self):
        self.plugin.get_ip_address = mock.Mock()
        self.plugin.get_ip_address.return_value = "192.168.1.1"

        self.plugin.get_external_ip_address = mock.Mock()
        self.plugin.get_external_ip_address.return_value = "8.8.8.8"

        self.plugin.get_printer().is_operational = mock.Mock()
        self.plugin.get_printer().is_operational.return_value = True

        self.plugin.get_printer().is_printing = mock.Mock()
        self.plugin.get_printer().is_printing.return_value = True

        self.plugin.get_printer().get_current_data = mock.Mock()
        self.plugin.get_printer().get_current_data.return_value = {
            'currentZ': 10,
            'job': {
                'file': {
                    'name': 'filename'
                }
            },
            'progress': {
                'completion': 15,
                'printTime': 300,
                'printTimeLeft': 500
            }
        }

        self.plugin.get_printer().get_current_temperatures = mock.Mock()
        self.plugin.get_printer().get_current_temperatures.return_value = {
            'bed': {
                'actual': 100
            },
            'extruder0': {
                'actual': 250
            },
            'extruder1': {
                'actual': 350
            }
        }

        self.plugin.get_snapshot = mock.Mock()

        with open(self._get_path('test_pattern.png')) as input_file:
            self.plugin.get_snapshot.return_value = [('snapshot.png',
                                                      input_file)]

            snapshots, embeds = self.command.parse_command('/status')

            self.assertIsNone(snapshots)
            self.plugin.get_snapshot.assert_called_once()

        message = ""
        for embed in embeds:
            message += unicode(embed)
        print(message)

        expected_terms = [
            'Status', 'Operational', 'Current Z', 'Bed Temp', 'extruder0',
            'extruder1', 'File', 'Progress', 'Time Spent', 'Time Remaining',
            humanfriendly.format_timespan(300),
            humanfriendly.format_timespan(500),
            self.plugin.get_ip_address.return_value,
            self.plugin.get_external_ip_address.return_value
        ]

        self.assertEqual(3, self.plugin.get_settings().get.call_count)

        calls = [
            mock.call(["show_local_ip"], merged=True),
            mock.call(["show_external_ip"], merged=True)
        ]
        self.plugin.get_settings().get.assert_has_calls(calls)

        for term in expected_terms:
            self.assertIn(term, message)

    def test_parse_command_pause(self):
        self.plugin.get_snapshot = mock.Mock()
        self.plugin.get_snapshot.return_value = [('snapshot.png', mock.Mock())]
        self.plugin.get_printer().pause_print = mock.Mock()
        snapshots, embeds = self.command.parse_command("/pause")

        self._validate_simple_embed(
            embeds,
            COLOR_SUCCESS,
            title="Print paused",
            image=self.plugin.get_snapshot.return_value)
        self.plugin.get_snapshot.assert_called_once()
        self.assertIsNone(snapshots)
        self.plugin.get_printer().pause_print.assert_called_once()

    def test_parse_command_resume(self):
        self.plugin.get_snapshot = mock.Mock()
        self.plugin.get_snapshot.return_value = [('snapshot.png', mock.Mock())]
        self.plugin.get_printer().resume_print = mock.Mock()
        snapshots, embeds = self.command.parse_command("/resume")

        self._validate_simple_embed(
            embeds,
            COLOR_SUCCESS,
            title="Print resumed",
            image=self.plugin.get_snapshot.return_value)
        self.plugin.get_snapshot.assert_called_once()
        self.assertIsNone(snapshots)
        self.plugin.get_printer().resume_print.assert_called_once()

    @mock.patch("requests.get")
    def test_download_file(self, mock_get):
        self.plugin.get_file_manager().path_on_disk = mock.Mock()
        self.plugin.get_file_manager(
        ).path_on_disk.return_value = "./temp.file"

        mock_request_val = mock.Mock()
        mock_request_val.iter_content = mock.Mock()
        mock_request_val.iter_content.return_value = b'1234'
        mock_get.return_value = mock_request_val

        # Upload, no user
        snapshot, embeds = self.command.download_file("filename",
                                                      "http://mock.url", None)
        self.assertIsNone(snapshot)
        self._validate_simple_embed(embeds,
                                    COLOR_SUCCESS,
                                    title="File Received")

        self.plugin.get_file_manager().path_on_disk.assert_called_once_with(
            'local', 'filename')
        self.plugin.get_file_manager().path_on_disk.reset_mock()
        mock_get.assert_called_once_with("http://mock.url", stream=True)
        mock_get.reset_mock()

        with open("./temp.file", 'rb') as f:
            self.assertEqual(b'1234', f.read())

        os.remove("./temp.file")

        # Upload with user
        self.command.check_perms = mock.Mock()
        self.command.check_perms.return_value = True

        snapshot, embeds = self.command.download_file("filename",
                                                      "http://mock.url",
                                                      "1234")
        self.assertIsNone(snapshot)
        self._validate_simple_embed(embeds,
                                    COLOR_SUCCESS,
                                    title="File Received")

        self.plugin.get_file_manager().path_on_disk.assert_called_once_with(
            'local', 'filename')
        self.plugin.get_file_manager().path_on_disk.reset_mock()
        mock_get.assert_called_once_with("http://mock.url", stream=True)
        mock_get.reset_mock()

        with open("./temp.file", 'rb') as f:
            self.assertEqual(b'1234', f.read())

        # Upload denied
        self.command.check_perms.return_value = False

        snapshot, embeds = self.command.download_file("filename",
                                                      "http://mock.url",
                                                      "1234")
        self.assertIsNone(snapshot)
        self._validate_simple_embed(embeds,
                                    COLOR_ERROR,
                                    title="Permission Denied")

    def test_parse_array(self):
        self.assertIsNone(self.command._parse_array(None))
        self.assertIsNone(self.command._parse_array(1))

        results = self.command._parse_array("*")
        self.assertEqual(1, len(results))
        self.assertIn("*", results)

        results = self.command._parse_array("123")
        self.assertEqual(1, len(results))
        self.assertIn("123", results)

        results = self.command._parse_array("123,456")
        self.assertEqual(2, len(results))
        self.assertIn("123", results)
        self.assertIn("456", results)

        results = self.command._parse_array("123 456")
        self.assertEqual(2, len(results))
        self.assertIn("123", results)
        self.assertIn("456", results)

        results = self.command._parse_array("123/456")
        self.assertEqual(2, len(results))
        self.assertIn("123", results)
        self.assertIn("456", results)

    def test_check_perms(self):
        get_mock = mock.Mock()

        settings_mock = mock.Mock()
        settings_mock.get = get_mock

        self.command.plugin.get_settings = mock.Mock()
        self.command.plugin.get_settings.return_value = settings_mock

        get_mock.return_value = {}
        self.assertFalse(self.command.check_perms("notallowed", "123"))

        get_mock.return_value = {'1': {'users': '*', 'commands': ''}}
        self.assertFalse(self.command.check_perms("notallowed", "123"))

        get_mock.return_value = {'1': {'users': '', 'commands': '*'}}
        self.assertFalse(self.command.check_perms("notallowed", "123"))

        get_mock.return_value = {'1': {'users': '*', 'commands': '*'}}
        self.assertTrue(self.command.check_perms("allowed", "123"))

        get_mock.return_value = {'1': {'users': '123', 'commands': '*'}}
        self.assertTrue(self.command.check_perms("allowed", "123"))

        get_mock.get.return_value = {
            '1': {
                'users': '*',
                'commands': 'allowed'
            }
        }
        self.assertTrue(self.command.check_perms("allowed", "123"))

        get_mock.return_value = {'1': {'users': '123', 'commands': 'allowed'}}
        self.assertTrue(self.command.check_perms("allowed", "123"))

        get_mock.return_value = {
            '1': {
                'users': '456',
                'commands': 'notallowed'
            },
            '2': {
                'users': '123',
                'commands': 'allowed'
            }
        }
        self.assertTrue(self.command.check_perms("allowed", "123"))

    def test_gcode(self):
        # Printer disconnected
        self.plugin.get_printer().is_operational = mock.Mock()
        self.plugin.get_printer().is_operational.return_value = False
        snapshots, embeds = self.command.gcode(["/gcode", "M0"])
        self.assertIsNone(snapshots)
        self._validate_simple_embed(embeds,
                                    COLOR_ERROR,
                                    title="Printer not connected",
                                    description="Connect to printer first.")

        # Printer connected, invalid GCODE
        self.plugin.get_printer().is_operational.return_value = True
        self.plugin.get_settings().get = mock.Mock()
        self.plugin.get_settings().get.return_value = "G0, M0|M851"
        snapshots, embeds = self.command.gcode(["/gcode", "M1"])
        self.assertIsNone(snapshots)
        self._validate_simple_embed(
            embeds,
            COLOR_ERROR,
            title="Invalid GCODE",
            description=
            "If you want to use \"M1\", add it to the allowed GCODEs")

        # Failed to send
        self.plugin.get_printer().commands = mock.Mock()
        self.plugin.get_printer().commands.side_effect = Exception("Error")
        snapshots, embeds = self.command.gcode(["/gcode", "M0"])
        self.assertIsNone(snapshots)
        self._validate_simple_embed(embeds,
                                    COLOR_ERROR,
                                    title="Failed to execute gcode",
                                    description="Error: Error")

        # Success - Case Insensitive:
        self.plugin.get_printer().commands.reset_mock()
        self.plugin.get_printer().commands.side_effect = None
        snapshots, embeds = self.command.gcode(["/gcode", "m0"])
        self.assertIsNone(snapshots)
        self._validate_simple_embed(embeds, COLOR_SUCCESS, title="Sent script")
        self.plugin.get_printer().commands.assert_called_once_with(['M0'])

    @mock.patch('octoprint_discordremote.command.upload_file')
    def test_parse_command_getfile(self, mock_upload):
        self.command.find_file = mock.Mock()
        self.command.find_file.return_value = None
        snapshots, embeds = self.command.getfile(["/getfile", "test.gcode"])
        self.assertIsNone(snapshots)
        self._validate_simple_embed(
            embeds,
            COLOR_ERROR,
            title="Failed to find file matching the name given")

        self.command.find_file.return_value = {'location': None, 'path': None}
        mock_upload.return_value = True, True
        self.plugin.get_file_manager = mock.Mock()
        mock_file_manager = mock.Mock()
        self.plugin.get_file_manager.return_value = mock_file_manager
        snapshots, embeds = self.command.getfile(["/getfile", "test.gcode"])
        self.assertTrue(snapshots)
        self.assertTrue(embeds)

    @mock.patch('os.walk')
    @mock.patch('octoprint_discordremote.command.upload_file')
    def test_parse_command_gettimelapse(self, mock_upload, mock_oswalk):
        self.plugin._data_folder = ''
        mock_oswalk.return_value = [('', [], [])]
        snapshots, embeds = self.command.gettimelapse(
            ["/gettimelapse", "test.gcode"])
        self.assertIsNone(snapshots)
        self._validate_simple_embed(
            embeds,
            COLOR_ERROR,
            title="Failed to find file matching the name given")

        mock_oswalk.return_value = [('', [], ['test.gcode'])]
        mock_upload.return_value = True, True
        snapshots, embeds = self.command.gettimelapse(
            ["/gettimelapse", "test.gcode"])
        self.assertTrue(snapshots)
        self.assertTrue(embeds)