diff --git a/cuegui/cuegui/MainWindow.py b/cuegui/cuegui/MainWindow.py index e9b4bc9a3..353c3ca6a 100644 --- a/cuegui/cuegui/MainWindow.py +++ b/cuegui/cuegui/MainWindow.py @@ -27,6 +27,7 @@ from builtins import range import sys import time +import yaml from qtpy import QtCore from qtpy import QtGui @@ -71,7 +72,7 @@ def __init__(self, app_name, app_version, window_name, parent = None): self.name = window_name else: self.name = self.windows_names[0] - self.__isEnabled = bool(int(QtGui.qApp.settings.value("EnableJobInteraction", 0))) + self.__isEnabled = yaml.safe_load(self.app.settings.value("EnableJobInteraction", "False")) # Provides a location for widgets to the right of the menu menuLayout = QtWidgets.QHBoxLayout() @@ -201,14 +202,16 @@ def __createMenus(self): if self.__isEnabled is False: # Menu Bar: File -> Enable Job Interaction - enableJobInteraction = QtWidgets.QAction(QtGui.QIcon('icons/exit.png'), '&Enable Job Interaction', self) + enableJobInteraction = QtWidgets.QAction(QtGui.QIcon('icons/exit.png'), + '&Enable Job Interaction', self) enableJobInteraction.setStatusTip('Enable Job Interaction') enableJobInteraction.triggered.connect(self.__enableJobInteraction) self.fileMenu.addAction(enableJobInteraction) # allow user to disable the job interaction else: # Menu Bar: File -> Disable Job Interaction - enableJobInteraction = QtWidgets.QAction(QtGui.QIcon('icons/exit.png'), '&Disable Job Interaction', self) + enableJobInteraction = QtWidgets.QAction(QtGui.QIcon('icons/exit.png'), + '&Disable Job Interaction', self) enableJobInteraction.setStatusTip('Disable Job Interaction') enableJobInteraction.triggered.connect(self.__enableJobInteraction) self.fileMenu.addAction(enableJobInteraction) diff --git a/cuegui/cuegui/MenuActions.py b/cuegui/cuegui/MenuActions.py index c85df27b9..961561871 100644 --- a/cuegui/cuegui/MenuActions.py +++ b/cuegui/cuegui/MenuActions.py @@ -79,6 +79,11 @@ class AbstractActions(object): __iconCache = {} + # Template for permission alert messages + USER_INTERACTION_PERMISSIONS = "You do not have permissions to {0} owned by {1}" \ + "\n\nJob actions can still be enabled at File > Enable Job Interaction," \ + " but caution is advised." + def __init__(self, caller, updateCallable, selectedRpcObjectsCallable, sourceCallable): self._caller = caller self.__selectedRpcObjects = selectedRpcObjectsCallable @@ -214,9 +219,6 @@ def getText(self, title, body, default): class JobActions(AbstractActions): """Actions for jobs.""" - # Template for permission alert messages - USER_INTERACTION_PERMISSIONS = "You do not have permissions to {0} owned by {2}" - def __init__(self, *args): AbstractActions.__init__(self, *args) @@ -396,7 +398,7 @@ def kill(self, rpcObjects=None): self.killDependents(authorized_jobs) if blocked_job_owners: cuegui.Utils.showErrorMessageBox( - JobActions.USER_INTERACTION_PERMISSIONS.format( + AbstractActions.USER_INTERACTION_PERMISSIONS.format( "kill some of the selected jobs", ", ".join(blocked_job_owners))) self._update() @@ -477,7 +479,7 @@ def eatDead(self, rpcObjects=None): job.eatFrames(state=[opencue.compiled_proto.job_pb2.DEAD]) if blocked_job_owners: cuegui.Utils.showErrorMessageBox( - JobActions.USER_INTERACTION_PERMISSIONS.format( + AbstractActions.USER_INTERACTION_PERMISSIONS.format( "eat dead for some of the selected jobs", ", ".join(blocked_job_owners))) self._update() @@ -487,9 +489,18 @@ def eatDead(self, rpcObjects=None): def autoEatOn(self, rpcObjects=None): jobs = self._getOnlyJobObjects(rpcObjects) if jobs: + blocked_job_owners = [] for job in jobs: - job.setAutoEat(True) - job.eatFrames(state=[opencue.compiled_proto.job_pb2.DEAD]) + if not cuegui.Utils.isPermissible(job): + blocked_job_owners.append(job.username()) + else: + job.setAutoEat(True) + job.eatFrames(state=[opencue.compiled_proto.job_pb2.DEAD]) + if blocked_job_owners: + cuegui.Utils.showErrorMessageBox( + AbstractActions.USER_INTERACTION_PERMISSIONS.format( + "enable auto eating frames", + ", ".join(blocked_job_owners))) self._update() autoEatOff_info = ["Disable auto eating", None, "eat"] @@ -497,8 +508,17 @@ def autoEatOn(self, rpcObjects=None): def autoEatOff(self, rpcObjects=None): jobs = self._getOnlyJobObjects(rpcObjects) if jobs: + blocked_job_owners = [] for job in jobs: - job.setAutoEat(False) + if not cuegui.Utils.isPermissible(job): + blocked_job_owners.append(job.username()) + else: + job.setAutoEat(False) + if blocked_job_owners: + cuegui.Utils.showErrorMessageBox( + AbstractActions.USER_INTERACTION_PERMISSIONS.format( + "disable auto eating frames", + ", ".join(blocked_job_owners))) self._update() retryDead_info = ["Retry dead frames", None, "retry"] @@ -519,7 +539,7 @@ def retryDead(self, rpcObjects=None): state=[opencue.compiled_proto.job_pb2.DEAD]) if blocked_job_owners: cuegui.Utils.showErrorMessageBox( - JobActions.USER_INTERACTION_PERMISSIONS.format( + AbstractActions.USER_INTERACTION_PERMISSIONS.format( "retry dead for some of the selected jobs", ", ".join(blocked_job_owners))) self._update() @@ -817,7 +837,7 @@ def kill(self, rpcObjects=None): #check permissions if not cuegui.Utils.isPermissible(self._getSource()): cuegui.Utils.showErrorMessageBox( - JobActions.USER_INTERACTION_PERMISSIONS.format( + AbstractActions.USER_INTERACTION_PERMISSIONS.format( "kill layers", self._getSource().username())) else: @@ -835,7 +855,7 @@ def eat(self, rpcObjects=None): if layers: if not cuegui.Utils.isPermissible(self._getSource()): cuegui.Utils.showErrorMessageBox( - JobActions.USER_INTERACTION_PERMISSIONS.format( + AbstractActions.USER_INTERACTION_PERMISSIONS.format( "eat layers", self._getSource().username()) ) @@ -854,7 +874,7 @@ def retry(self, rpcObjects=None): if layers: if not cuegui.Utils.isPermissible(self._getSource()): cuegui.Utils.showErrorMessageBox( - JobActions.USER_INTERACTION_PERMISSIONS.format( + AbstractActions.USER_INTERACTION_PERMISSIONS.format( "retry layers", self._getSource().username()) ) @@ -873,7 +893,7 @@ def retryDead(self, rpcObjects=None): if layers: if not cuegui.Utils.isPermissible(self._getSource()): cuegui.Utils.showErrorMessageBox( - JobActions.USER_INTERACTION_PERMISSIONS.format( + AbstractActions.USER_INTERACTION_PERMISSIONS.format( "retry dead layers", self._getSource().username()) ) @@ -1109,7 +1129,7 @@ def retry(self, rpcObjects=None): # check permissions if not cuegui.Utils.isPermissible(job): cuegui.Utils.showErrorMessageBox( - JobActions.USER_INTERACTION_PERMISSIONS.format( + AbstractActions.USER_INTERACTION_PERMISSIONS.format( "retry frames", job.username()) ) @@ -1151,11 +1171,9 @@ def previewAovs(self, rpcObjects=None): def eat(self, rpcObjects=None): names = [frame.data.name for frame in self._getOnlyFrameObjects(rpcObjects)] if names: - #check permissions - print(self._getSource()) if not cuegui.Utils.isPermissible(self._getSource()): cuegui.Utils.showErrorMessageBox( - JobActions.USER_INTERACTION_PERMISSIONS.format( + AbstractActions.USER_INTERACTION_PERMISSIONS.format( "eat frames", self._getSource().username()) ) @@ -1173,7 +1191,7 @@ def kill(self, rpcObjects=None): if names: if not cuegui.Utils.isPermissible(self._getSource(), self): cuegui.Utils.showErrorMessageBox( - JobActions.USER_INTERACTION_PERMISSIONS.format( + AbstractActions.USER_INTERACTION_PERMISSIONS.format( "kill frames", self._getSource().username())) else: @@ -1289,51 +1307,52 @@ def eatandmarkdone(self, rpcObjects=None): #check permissions if not cuegui.Utils.isPermissible(self._getSource(), self): cuegui.Utils.showErrorMessageBox( - JobActions.USER_INTERACTION_PERMISSIONS.format( + AbstractActions.USER_INTERACTION_PERMISSIONS.format( "eat and mark done frames", self._getSource().username()) ) - else: - if cuegui.Utils.questionBoxYesNo( - self._caller, "Confirm", - "Eat and Mark done all selected frames?\n" - "(Drops any dependencies that are waiting on these frames)\n\n" - "If a frame is part of a layer that will now only contain\n" - "eaten or succeeded frames, any dependencies on the\n" - "layer will be dropped as well.", - frameNames): - - # Mark done the layers to drop their dependencies if the layer is done - - if len(frames) == 1: - # Since only a single frame selected, check if layer is only one frame - layer = opencue.api.findLayer(self._getSource().data.name, - frames[0].data.layer_name) - if layer.data.layer_stats.total_frames == 1: - # Single frame selected of single frame layer, mark done and eat it all - layer.eat() - layer.markdone() - - self._update() - return - - self._getSource().eatFrames(name=frameNames) - self._getSource().markdoneFrames(name=frameNames) - - # Warning: The below assumes that eaten frames are desired to be markdone - - # Wait for the markDoneFrames to be processed, then drop the dependencies on - # the layer if all frames are done. - layerNames = [frame.data.layer_name for frame in frames] - time.sleep(1) - for layer in self._getSource().getLayers(): - if layer.data.name in layerNames: - if ( - layer.data.layer_stats.eaten_frames + - layer.data.layer_stats.succeeded_frames == - layer.data.layer_stats.total_frames): - layer.markdone() + return + if not cuegui.Utils.questionBoxYesNo( + self._caller, "Confirm", + "Eat and Mark done all selected frames?\n" + "(Drops any dependencies that are waiting on these frames)\n\n" + "If a frame is part of a layer that will now only contain\n" + "eaten or succeeded frames, any dependencies on the\n" + "layer will be dropped as well.", + frameNames): + return + + # Mark done the layers to drop their dependencies if the layer is done + + if len(frames) == 1: + # Since only a single frame selected, check if layer is only one frame + layer = opencue.api.findLayer(self._getSource().data.name, + frames[0].data.layer_name) + if layer.data.layer_stats.total_frames == 1: + # Single frame selected of single frame layer, mark done and eat it all + layer.eat() + layer.markdone() + self._update() + return + + self._getSource().eatFrames(name=frameNames) + self._getSource().markdoneFrames(name=frameNames) + + # Warning: The below assumes that eaten frames are desired to be markdone + + # Wait for the markDoneFrames to be processed, then drop the dependencies on + # the layer if all frames are done. + layerNames = [frame.data.layer_name for frame in frames] + time.sleep(1) + for layer in self._getSource().getLayers(): + if layer.data.name in layerNames: + if ( + layer.data.layer_stats.eaten_frames + + layer.data.layer_stats.succeeded_frames == + layer.data.layer_stats.total_frames): + layer.markdone() + self._update() class ShowActions(AbstractActions): @@ -1766,17 +1785,19 @@ def view(self, rpcObjects=None): def kill(self, rpcObjects=None): procs = self._getOnlyProcObjects(rpcObjects) if procs: - print(self._getSource()) - if not cuegui.Utils.isPermissible(self._getSource(), self): + if not cuegui.Utils.isPermissible(self._getSource()): cuegui.Utils.showErrorMessageBox( - JobActions.USER_INTERACTION_PERMISSIONS.format( + AbstractActions.USER_INTERACTION_PERMISSIONS.format( "eat and mark done frames", self._getSource().username()) ) else: if cuegui.Utils.questionBoxYesNo( self._caller, "Confirm", "Kill selected frames?", - ["%s -> %s @ %s" % (proc.data.job_name, proc.data.frame_name, proc.data.name) + ["%s -> %s @ %s" % ( + proc.data.job_name, + proc.data.frame_name, + proc.data.name) for proc in procs]): for proc in procs: self.cuebotCall(proc.kill, diff --git a/cuegui/cuegui/Utils.py b/cuegui/cuegui/Utils.py index da8b596d2..cf84a7fd5 100644 --- a/cuegui/cuegui/Utils.py +++ b/cuegui/cuegui/Utils.py @@ -35,7 +35,6 @@ from qtpy import QtCore from qtpy import QtGui from qtpy import QtWidgets -import getpass import six import opencue @@ -683,14 +682,10 @@ def isPermissible(jobObject): :ptype userName: Opencue Job Object :return: """ - hasPermissions = False - # Case 1. Check if current user is the job owner + # Read cached setting from user config file + hasPermissions = yaml.safe_load(cuegui.app().settings.value("EnableJobInteraction", "False")) + # If not set by default, check if current user is the job owner currentUser = getpass.getuser() - if currentUser.lower() == jobObject.username().lower(): - hasPermissions = True - - # Case 2. Check if "Enable not owned Job Interactions" is Enabled - if bool(int(QtGui.qApp.settings.value("EnableJobInteraction", 0))): + if not hasPermissions and currentUser.lower() == jobObject.username().lower(): hasPermissions = True - return hasPermissions diff --git a/cuegui/tests/MenuActions_tests.py b/cuegui/tests/MenuActions_tests.py index 7c116b62c..9bf3a74ed 100644 --- a/cuegui/tests/MenuActions_tests.py +++ b/cuegui/tests/MenuActions_tests.py @@ -230,7 +230,8 @@ def test_resume(self): job.resume.assert_called() @mock.patch('cuegui.Utils.questionBoxYesNo', return_value=True) - def test_kill(self, yesNoMock): + @mock.patch('cuegui.Utils.isPermissible', return_value=True) + def test_kill(self, isPermissibleMock, yesNoMock): job = opencue.wrappers.job.Job(opencue.compiled_proto.job_pb2.Job(name='job-name')) job.kill = mock.Mock() job.getWhatDependsOnThis = mock.Mock() @@ -241,7 +242,8 @@ def test_kill(self, yesNoMock): job.kill.assert_called() @mock.patch('cuegui.Utils.questionBoxYesNo', return_value=False) - def test_killCanceled(self, yesNoMock): + @mock.patch('cuegui.Utils.isPermissible', return_value=True) + def test_killCanceled(self, isPermissibleMock, yesNoMock): job = opencue.wrappers.job.Job(opencue.compiled_proto.job_pb2.Job(name='job-name')) job.kill = mock.Mock() @@ -250,7 +252,8 @@ def test_killCanceled(self, yesNoMock): job.kill.assert_not_called() @mock.patch('cuegui.Utils.questionBoxYesNo', return_value=True) - def test_eatDead(self, yesNoMock): + @mock.patch('cuegui.Utils.isPermissible', return_value=True) + def test_eatDead(self, isPermissibleMock, yesNoMock): job = opencue.wrappers.job.Job(opencue.compiled_proto.job_pb2.Job(name='job-name')) job.eatFrames = mock.Mock() @@ -259,7 +262,8 @@ def test_eatDead(self, yesNoMock): job.eatFrames.assert_called_with(state=[opencue.compiled_proto.job_pb2.DEAD]) @mock.patch('cuegui.Utils.questionBoxYesNo', return_value=False) - def test_eatDeadCanceled(self, yesNoMock): + @mock.patch('cuegui.Utils.isPermissible', return_value=True) + def test_eatDeadCanceled(self, isPermissibleMock, yesNoMock): job = opencue.wrappers.job.Job(opencue.compiled_proto.job_pb2.Job(name='job-name')) job.eatFrames = mock.Mock() @@ -267,7 +271,8 @@ def test_eatDeadCanceled(self, yesNoMock): job.eatFrames.assert_not_called() - def test_autoEatOn(self): + @mock.patch('cuegui.Utils.isPermissible', return_value=True) + def test_autoEatOn(self, isPermissibleMock): job = opencue.wrappers.job.Job(opencue.compiled_proto.job_pb2.Job(name='job-name')) job.setAutoEat = mock.Mock() job.eatFrames = mock.Mock() @@ -277,7 +282,8 @@ def test_autoEatOn(self): job.setAutoEat.assert_called_with(True) job.eatFrames.assert_called_with(state=[opencue.compiled_proto.job_pb2.DEAD]) - def test_autoEatOff(self): + @mock.patch('cuegui.Utils.isPermissible', return_value=True) + def test_autoEatOff(self, isPermissibleMock): job = opencue.wrappers.job.Job(opencue.compiled_proto.job_pb2.Job(name='job-name')) job.setAutoEat = mock.Mock() @@ -286,7 +292,8 @@ def test_autoEatOff(self): job.setAutoEat.assert_called_with(False) @mock.patch('cuegui.Utils.questionBoxYesNo', return_value=True) - def test_retryDead(self, yesNoMock): + @mock.patch('cuegui.Utils.isPermissible', return_value=True) + def test_retryDead(self, isPermissibleMock, yesNoMock): job = opencue.wrappers.job.Job(opencue.compiled_proto.job_pb2.Job(name='job-name')) job.retryFrames = mock.Mock() @@ -295,7 +302,8 @@ def test_retryDead(self, yesNoMock): job.retryFrames.assert_called_with(state=[opencue.compiled_proto.job_pb2.DEAD]) @mock.patch('cuegui.Utils.questionBoxYesNo', return_value=False) - def test_retryDeadCanceled(self, yesNoMock): + @mock.patch('cuegui.Utils.isPermissible', return_value=True) + def test_retryDeadCanceled(self, isPermissibleMock, yesNoMock): job = opencue.wrappers.job.Job(opencue.compiled_proto.job_pb2.Job(name='job-name')) job.retryFrames = mock.Mock() @@ -669,7 +677,8 @@ def test_setTags(self, layerTagsDialogMock): @mock.patch.object(opencue.wrappers.layer.Layer, 'kill') @mock.patch('cuegui.Utils.questionBoxYesNo', return_value=True) - def test_kill(self, yesNoMock, killMock): + @mock.patch('cuegui.Utils.isPermissible', return_value=True) + def test_kill(self, isPermissibleMock, yesNoMock, killMock): layer = opencue.wrappers.layer.Layer( opencue.compiled_proto.job_pb2.Layer(name='arbitrary-name')) @@ -679,7 +688,8 @@ def test_kill(self, yesNoMock, killMock): @mock.patch.object(opencue.wrappers.layer.Layer, 'kill') @mock.patch('cuegui.Utils.questionBoxYesNo', return_value=False) - def test_killCanceled(self, yesNoMock, killMock): + @mock.patch('cuegui.Utils.isPermissible', return_value=True) + def test_killCanceled(self, isPermissibleMock, yesNoMock, killMock): layer = opencue.wrappers.layer.Layer( opencue.compiled_proto.job_pb2.Layer(name='arbitrary-name')) @@ -689,7 +699,8 @@ def test_killCanceled(self, yesNoMock, killMock): @mock.patch.object(opencue.wrappers.layer.Layer, 'eat') @mock.patch('cuegui.Utils.questionBoxYesNo', return_value=True) - def test_eat(self, yesNoMock, eatMock): + @mock.patch('cuegui.Utils.isPermissible', return_value=True) + def test_eat(self, isPermissibleMock, yesNoMock, eatMock): layer = opencue.wrappers.layer.Layer( opencue.compiled_proto.job_pb2.Layer(name='arbitrary-name')) @@ -699,7 +710,8 @@ def test_eat(self, yesNoMock, eatMock): @mock.patch.object(opencue.wrappers.layer.Layer, 'eat') @mock.patch('cuegui.Utils.questionBoxYesNo', return_value=False) - def test_eatCanceled(self, yesNoMock, eatMock): + @mock.patch('cuegui.Utils.isPermissible', return_value=True) + def test_eatCanceled(self, isPermissibleMock, yesNoMock, eatMock): layer = opencue.wrappers.layer.Layer( opencue.compiled_proto.job_pb2.Layer(name='arbitrary-name')) @@ -709,7 +721,8 @@ def test_eatCanceled(self, yesNoMock, eatMock): @mock.patch.object(opencue.wrappers.layer.Layer, 'retry', autospec=True) @mock.patch('cuegui.Utils.questionBoxYesNo', return_value=True) - def test_retry(self, yesNoMock, retryMock): + @mock.patch('cuegui.Utils.isPermissible', return_value=True) + def test_retry(self, isPermissibleMock, yesNoMock, retryMock): layer = opencue.wrappers.layer.Layer( opencue.compiled_proto.job_pb2.Layer(name='arbitrary-name')) @@ -719,7 +732,8 @@ def test_retry(self, yesNoMock, retryMock): @mock.patch.object(opencue.wrappers.layer.Layer, 'retry') @mock.patch('cuegui.Utils.questionBoxYesNo', return_value=False) - def test_retryCanceled(self, yesNoMock, retryMock): + @mock.patch('cuegui.Utils.isPermissible', return_value=True) + def test_retryCanceled(self, isPermissibleMock, yesNoMock, retryMock): layer = opencue.wrappers.layer.Layer( opencue.compiled_proto.job_pb2.Layer(name='arbitrary-name')) @@ -728,7 +742,8 @@ def test_retryCanceled(self, yesNoMock, retryMock): retryMock.assert_not_called() @mock.patch('cuegui.Utils.questionBoxYesNo', return_value=True) - def test_retryDead(self, yesNoMock): + @mock.patch('cuegui.Utils.isPermissible', return_value=True) + def test_retryDead(self, isPermissibleMock, yesNoMock): layer_name = 'arbitrary-name' layer = opencue.wrappers.layer.Layer( opencue.compiled_proto.job_pb2.Layer(name=layer_name)) @@ -914,7 +929,8 @@ def test_getWhatDependsOnThis(self): self.frame_actions.getWhatDependsOnThis(rpcObjects=[frame]) @mock.patch('cuegui.Utils.questionBoxYesNo', return_value=True) - def test_retry(self, yesNoMock): + @mock.patch('cuegui.Utils.isPermissible', return_value=True) + def test_retry(self, isPermissibleMock, yesNoMock): frame_name = 'arbitrary-frame-name' frame = opencue.wrappers.frame.Frame(opencue.compiled_proto.job_pb2.Frame(name=frame_name)) @@ -943,7 +959,8 @@ def test_previewAovs(self, previewProcessorDialogMock): previewProcessorDialogMock.return_value.exec_.assert_called() @mock.patch('cuegui.Utils.questionBoxYesNo', return_value=True) - def test_eat(self, yesNoMock): + @mock.patch('cuegui.Utils.isPermissible', return_value=True) + def test_eat(self, isPermissibleMock, yesNoMock): frame_name = 'arbitrary-frame-name' frame = opencue.wrappers.frame.Frame(opencue.compiled_proto.job_pb2.Frame(name=frame_name)) @@ -952,7 +969,8 @@ def test_eat(self, yesNoMock): self.job.eatFrames.assert_called_with(name=[frame_name]) @mock.patch('cuegui.Utils.questionBoxYesNo', return_value=True) - def test_kill(self, yesNoMock): + @mock.patch('cuegui.Utils.isPermissible', return_value=True) + def test_kill(self, isPermissibleMock, yesNoMock): frame_name = 'arbitrary-frame-name' frame = opencue.wrappers.frame.Frame(opencue.compiled_proto.job_pb2.Frame(name=frame_name)) @@ -1019,7 +1037,8 @@ def test_copyLogFileName(self, getFrameLogFileMock, clipboardMock): @mock.patch.object(opencue.wrappers.layer.Layer, 'markdone', autospec=True) @mock.patch('cuegui.Utils.questionBoxYesNo', return_value=True) - def test_eatandmarkdone(self, yesNoMock, markdoneMock): + @mock.patch('cuegui.Utils.isPermissible', return_value=True) + def test_eatandmarkdone(self, isPermissibleMock, yesNoMock, markdoneMock): layer_name = 'layer-name' frames = [ opencue.wrappers.frame.Frame( @@ -1339,7 +1358,7 @@ def setUp(self): self.app = test_utils.createApplication() self.widgetMock = mock.Mock() self.proc_actions = cuegui.MenuActions.ProcActions( - self.widgetMock, mock.Mock(), None, None) + self.widgetMock, mock.Mock(), mock.Mock(), mock.Mock()) @mock.patch('opencue.api.findJob') def test_view(self, findJobMock): @@ -1353,8 +1372,9 @@ def test_view(self, findJobMock): self.app.view_object.emit.assert_called_once_with(job) - @mock.patch('cuegui.Utils.questionBoxYesNo', new=mock.Mock(return_value=True)) - def test_kill(self): + @mock.patch('cuegui.Utils.questionBoxYesNo', return_value=True) + @mock.patch('cuegui.Utils.isPermissible', return_value=True) + def test_kill(self, isPermissibleMock, yesNoMock): proc = opencue.wrappers.proc.Proc(opencue.compiled_proto.host_pb2.Proc()) proc.kill = mock.MagicMock()